内存测试是在干嘛?
内存颗粒测试的本质是通过特定电学应力验证存储单元的电气特性和物理完整性,涵盖功能正确性、时序参数和工艺可靠性三个层面。测试算法需平衡覆盖率与效率。 Android应用无法直接获取内存颗粒的关键指标(如BER、时序参数等),因受操作系统、缓存和权限限制。真正的颗粒级测试需在uboot/EFI shell/kernel模块层进行,才能绕过虚拟内存、直接控制时序电压并隔离特定颗粒。 从写入0x55到读取
本质就是,类比总结:你只是填了表(按键),真正办成事的是窗口后面那套流程(系统各层级)。
1问:内存颗粒的测试本质是在做什么?
1答:
内存颗粒测试的本质是通过施加特定的电学应力(电压、时序、温度)和读写数据pattern,验证存储阵列中每个存储单元的电气特性与物理完整性是否达标。
具体来说,测试覆盖三个层面:
- 功能正确性 — 每个bit能否正确写入/读出0和1,有无stuck-at fault、coupling fault、retention fault
- 时序参数裕量 — 是否满足tRCD、tCL、tRP等时序规格,能否在标称频率下稳定工作
- 工艺可靠性 — 剔除因氧化物击穿、字线/位线短路、电容漏电等物理缺陷导致的早期失效颗粒
测试算法(如March C+、Checkerboard、Worst-case patterns)本质上是以最低的时间复杂度覆盖尽可能多的故障模型,在量产测试中平衡覆盖率与测试时间。
2问:就是往内存里写入和读取数据吧?用什么指标衡量呢,Android app能拿到这些指标的参数吗
2答:
对,核心就是写→读→比较,但不同算法覆盖的故障模型不同。
衡量指标主要有:
- Bit Error Rate (BER) / Error count
- Retention time — 数据能保持多久不丢失
- Access time / tRCD, tCL — 时序参数
- Operating voltage margin — 电压窗口
- Temperature sensitivity
Android app(用户态)基本拿不到这些参数:
- BER、Error count 只能通过全量读写对比得到(需避开文件系统,直接 mmap /dev/block 或通过 HAL)
- 时序参数(tRCD/tCL/tRP等)存储在颗粒的 SPD(I2C总线)或 MR寄存器 中,普通 app 无权限访问
- 电压/温度 sensor 数据通常只对 kernel 或 vendor 层开放
App 层面最多做到:申请root权限后通过ioctl或spdtool读取SPD信息,或者用 用户态memtester模式 跑有限pattern的读写测试(但受系统调度、cache、文件系统干扰,结果不可靠)。真正的颗粒级测试需要在 uboot/EFI shell/kernel module 层完成。
3问:uboot/EFI shell/kernel module层才行吗?为什么
3答:
几个根本原因:
1. 操作系统/MMU干扰
用户态访问的都是虚拟地址,中间隔着一层页表和cache。你写一个值,可能被:
- L1/L2 cache命中,根本没到DRAM
- TLB miss导致page walk引入额外延迟
- DMA/GPU/其他核在背后刷内存,产生假error
真正的颗粒测试需要绕过cache、关掉MMU、物理地址直接访问,这只有kernel module或firmware层能做到。
2. 缺页/swap/文件系统
Android app通过mmap /dev/block或malloc拿到的内存,随时可能被:
- kswapd换出到zRAM或swap分区
- kernel oom killer回收
- dm-crypt/fscrypt透明加密篡改内容
你写入0x55读出来0x55,到底是颗粒存对了,还是dm-crypt解密后恢复的?无法区分。
3. 无root/ioctl权限
/dev/mem、/dev/port默认只对root开放,普通app的SELinux policy直接blocki2c-dev(读SPD/温度sensor)需要system权限或root- MSR寄存器(读RAS/ECC信息)需cpuid或msr kernel模块
4. 无法控制时序/电压
颗粒测试的关键是压时序窗口(比如把tRC设到spec下限再写/读)。DRAM timing由memory controller控制:
- firmware层:直接写MC寄存器调tCL/tRCD/tRP
- kernel层:通过
set_memory_timing()或ACPI _DSM方法 - 用户态:没有任何接口
5. 无法隔离颗粒
你要测的是某颗DDR颗粒还是 DDR channel 0 rank 0?用户态看到的只是一个/dev/block或匿名页,kernel把物理页随机分配到不同rank/channel上,你根本无法定向打击某个颗粒。
总结层次对比:
| 层次 | 能否绕过cache | 能否调时序/电压 | 能否定址到颗粒 | 抗OS干扰 | 测试可信度 |
|---|---|---|---|---|---|
| App用户态 | ❌ | ❌ | ❌ | ❌ | 低 |
| Kernel module | ✅ | ✅(via MC reg) | ✅(PFN物理连续) | ✅ | 高 |
| EFI shell | ✅(关MMU) | ✅ | ✅ | ✅(无OS) | 最高 |
| U-Boot | ✅(关MMU/ICache) | ✅(直接写MC) | ✅ | ✅(裸机) | 最高 |
所以量产测试/QA认证都在firmware层做,Android app最多只能算个功能性冒烟测试,测不出颗粒的电气边际(timing/voltage margin)。
4问:你写入0x55读出来0x55,此过程中经历了多少个隐藏的步骤和层级,给我讲讲
4答:
从app写0x55读到0x55,一条指令背后跨越十几层,我按从顶到底再把结果传回来的路径逐层拆:
写入0x55
① 用户态指令
*p = 0x55;
编译器生成类似 str r0, [r1] 的ARM store指令。
② VAS + MMU
r1是虚拟地址 → MMU查 TLB(快表)- TLB miss → 硬件逐级遍历页表(PGD→PUD→PMD→PTE)
- 找到物理页帧号(PFN)+ 属性(cacheable/write-back等)
- TLB entry填充,TLB walk消耗几十~几百cycle
③ Cache层
- 页面属性是 Write-Back + Write-Allocate(典型)
- CPU检查 L1 D-cache → miss
- 检查 L2 cache → miss
- 从DRAM中**分配一个cacheline(64字节)**到L1/L2
④ Store Buffer
- CPU不直接写cache,而是把0x55推入 Store Buffer(异步合并写)
- 后续load指令有可能从store buffer forward数据(不经过cache)
- Store buffer满了才冲刷到L1 cache
⑤ Cache Coherence(多核)
- 如果在其他核cache中有这行的脏副本 → MESI协议 snoop invalidate
- 写失效广播到其他核,保证一致性
⑥ Memory Controller(MC)
- Store buffer最终要写回DRAM(cache eviction或explicit flush)
- Cacheline填充/eviction触发 MC请求队列
- MC内部:查 open page table,决定是page hit还是miss
- 仲裁:多通道/多rank间round-robin或priority调度
⑦ DRAM Command Sequence
MC对DIMM发出DRAM命令序列:
| 步骤 | 命令 | 说明 |
|---|---|---|
| 1 | ACTIVATE | 激活行(ROWS),打开page到row buffer |
| 2 | WRITE | 列地址+数据,CAS写延迟(WL=CL-1)后数据上线 |
| 3 | PRECHARGE | 关闭行,row buffer写回cell阵列(也可能auto-precharge) |
⑧ 物理电气层面
- 数据通过 DQ/DQS/DM引脚 以单端/差分信号传输到颗粒
- 颗粒内部:数据由I/O gating→读放大器→列选择→行选择
- 最终:行选通(WL)拉高 → 位线(BL)预充电 → 存储电容充电/放电,写入0x55 = 电容对应电平(每个bit一个电容)
读出并比较
⑨ 读路径
- ACTIVATE(如果page未打开)→ READ命令 → CL(CAS Latency)个clock后数据出现在DQ上
- 行buffer里的sense amplifier检测BL与参考电压差,锁存数据
- 读出的cacheline走回环总线传回MC → System Bus → Cache填充
⑩ Cache + Store Buffer Forwarding
- 读指令(
ldr r2, [r1])→ 先查 store buffer(看有没有未提交的写被forward)→ 再查L1→L2→DRAM - 数据回到CPU寄存器
r2
⑪ 比较
cmp r2, #0x55→ CPU ALU里比较- 条件跳转/状态寄存器记录结果
完整层级总览
用户态app
┌─────────────────────┐
│ str r0, [r1] │ ← 1条指令
│ ldr r2, [r1] │
│ cmp r2, #0x55 │
└──────┬──────────────┘
│ 虚拟地址
▼
┌─────────────────────────────────────────────┐
│ MMU + TLB │ ← 虚拟→物理翻译
│ 页表遍历(4级) → 权限检查 → 属性解析 │
└──────────────────┬──────────────────────────┘
│ 物理地址 + cacheable标志
▼
┌─────────────────────────────────────────────┐
│ Cache Hierarchy (L1→L2→L3) │ ← 写分配/写回
│ Store Buffer / Write Combiner │ ← 合并写
│ MESI Coherence Protocol │ ← 多核同步
└──────────────────┬──────────────────────────┘
│ cacheline miss/eviction
▼
┌─────────────────────────────────────────────┐
│ Memory Controller (MC) │ ← 排队/仲裁
│ Open Page Table → Page Hit/Miss决策 │
│ DDR Protocol Engine │
└──────────────────┬──────────────────────────┘
│ DDR command bus
▼
┌─────────────────────────────────────────────┐
│ DRAM Command Sequence │
│ ACT → WRITE/READ → PRECHARGE/REF │ ← tRCD/tCL/tRP约束
└──────────────────┬──────────────────────────┘
│ 物理信号 (DQ/DQS/CMD/ADDR)
▼
┌─────────────────────────────────────────────┐
│ DRAM Die 内部 │
│ Row Decoder → Word Line ↑ │
│ Column Decoder → Bit Line ←→ │
│ Sense Amplifier → 存储电容充放电 │ ← 模拟电气层面
│ Refresh Counter (每64ms刷新所有行) │
└─────────────────────────────────────────────┘
关键结论
写/读0x55到看到结果,经过**~15+层抽象**,每一层都可能引入:
- 延迟(TLB walk → 几百ns,DRAM page miss → 几十ns,RAS刷新冲突 → 额外延迟)
- 重排(Store buffer合并、MC reorder、DRAM bank interleaving)
- 篡改误判(dm-crypt解密后读出、zRAM压缩后解压、ECC纠正后读出)
这就是为什么用户态test和firmware物理test看到的结果可以完全不同——上层全是"代笔",底层才是颗粒自己的声音。
5问:为什么要搞这么多层?
5答:
每一层解决一个特定的矛盾,缺一不可:
| 层 | 解决什么问题 | 不存在的后果 |
|---|---|---|
| Cache | DRAM延迟~100ns,CPU cycle~0.3ns,差300倍。 | CPU 99%时间在等待,IPC趋近于0.1 |
| Store Buffer | 写操作不需等DRAM确认再执行下条指令。 | CPU流水线每写一次就stall一次 |
| MMU/TLB | 进程需要独立的虚拟地址空间 + 物理内存碎片化。 | 所有进程挤在物理地址里,互相踩踏;无法做页换出/swap |
| Memory Controller | 多rank/bank/channel需要调度最大化带宽。 | 每次只服务一个请求,带宽利用率<20% |
| DRAM协议(ACT/RD/WR/PRECHARGE) | 存储电容和行缓冲物理特性决定必须按时序操作。 | 电荷冲突、数据毁坏 |
| Refresh | DRAM电容会漏电,必须每64ms重写一次。 | 几秒后全部数据归零 |
本质是 cost × speed × capacity 的三角折中:
- CPU要最快 → SRAM(~1ns,但$5000/GB)
- DRAM折中 → ~50ns,$5/GB
- 想大容量但便宜 → 只能用电容(DRAM),电容有物理限制必须RAS/refresh
- 想屏蔽这些复杂性让程序员写简单代码 → MMU/VAS + cache coherence
没这些层,要么CPU跑飞但等内存,要么程序员手动管页表刷cache,要么内存贵到买不起。现代计算机就是用复杂度换性能/价格的工程产物。
6问:再说清楚一点,从用户使用一个写字软件到存到机器的存储,然后再读出这个数据到显示到屏幕上,具体经过了哪些步骤,用户态内核态,软硬件层级,具体都做了什么事?
6答:
好,用你在写字软件里按一个’a’键 → 显示到屏幕 → 保存到SSD → 下次打开读出显示,这条完整链路。
一、按’a’键 → 字符出现在屏幕上
① 硬件捕获
键盘薄膜触点闭合 → 扫描矩阵检测到行列交叉点
↓
键盘MCU(如Holtek) → 查keymap表(0x04 = 'a')
↓
USB HID Report: [0x00, 0x04, 0x00, ...] ← 8字节HID包
↓
USB串行编码(NRZI) → D+/D-差分信号 → xHCI主机控制器
② 内核态 — 输入子系统
xHCI控制器检测到USB事务 → DMA把HID report写入主存ring buffer
↓
硬件中断IRQ → 内核读取中断向量表 → 调用xhci_irq()
↓
usb_hcd_giveback_urb() → usb_hid核心层 → hid_input_report()
↓
hid_submit_event() → 生成struct input_event
{type=EV_KEY, code=KEY_A, value=1} ← press事件
↓
input_event() → 通过 /dev/input/eventN 的read接口
将input_event拷贝到用户态等待队列
③ 用户态 — 显示服务栈
libinput (或旧版evdev) → read(fd, &ev, sizeof(ev)) 取得ev
↓
Wayland/Windowing System → 判断焦点窗口是word processor
↓
通过Unix socket (wayland) 发送key event给word processor
④ 应用程序
word processor event loop收到key event
↓
Insert字符到text buffer(内存中struct gap_buffer / rope)
↓
标记整个text buffer为"dirty"(需要重绘)
↓
调用显示后端的绘制API → 字体渲染(freetype/harfbuzz)
→ 将字符'a'转换成像素bitmap放到framebuffer (GPU)
⑤ GPU合成输出
Word processor → wl_surface.commit()
↓
Wayland compositor (如weston/kwin) 收集各窗口surface
↓
调用GPU driver → DRM/KMS: drmModeAtomicCommit()
↓
内核drm驱动 → 配置CRTC/plane/connector
↓
GPU从主存或显存读取framebuffer → scanout
↓
GPU内部: 像素流水线 → rasterizer → 查color LUT → DAC
↓
DisplayPort/HDMI TMDS信号 → 屏幕T-Con芯片
↓
T-Con驱动source driver → LCD像素电容充电 → 液晶偏转
↓
背光透过 → 你看到屏幕上显示 'a'
二、用户点"保存" → 数据写到SSD
① 用户态
Word processor 收到 Ctrl+S 或 点击保存按钮
↓
调用 save() → 将 text buffer 序列化为 Unicode (UTF-8)
↓
open("/home/user/doc.txt", O_WRONLY|O_CREAT)
↓
write(fd, "Hello, world!\n", 14) ← 返回ssize_t
② 内核 — VFS + Page Cache
sys_write() → 系统调用 → 进入内核态(ring 0)
↓
kern_path() → 查找路径 → dentry cache命中 → vfs_inode
↓
文件系统类型EXT4/XFS → ext4_file_write_iter()
↓
generic_perform_write() → 写入Page Cache
① 检查page是否在cache中,miss → do_read_fault → 从磁盘读到page
② 数据从用户态copy_from_user拷贝到内核page
③ page标记为 Dirty (PG_dirty)
← write()返回 14,还没写到磁盘!
③ 内核 — Flush回写
时机:① 定时(30s) ② dirty page比例超阈值 ③ 用户调用fsync()
↓
用户点保存后,app调用 fsync(fd) ← 保证落盘
↓
sys_fsync() → ext4_sync_file() → jbd2事务
↓
日志Journal: 先写metadata到journal区(atomic保证crash安全)
↓
Writeback: submit_bio → block层合并/排序(电梯算法elevator)
④ Block层 + 驱动
struct bio 构造 → 包含 LBA(逻辑块地址), 数据, 回调
↓
NVMe/M.2协议层 → 生成 NVMe SQ entry
↓
PCIe DMA 写: 将数据从page cache 直接DMA到SSD内部DRAM buffer
↓
SSD主控FTL(Flash Translation Layer):
① 查逻辑→物理映射表 (L2P table in DRAM)
② 写时复制到一个空block (F2FS/GC机制)
③ ECC编码(如BCH/LDPC)附加到每个page
④ 配置NAND控制器的各电压阈值 → Program指令
⑤ NAND物理
NAND控制其拉高Bit Line电压(~20V)
↓
Fowler-Nordheim隧穿效应 → 电子注入floating gate
↓
floating gate电荷改变cell阈值电压(Vt)分布
↓
TLC/QLC: 每个cell存3/4bit → 对应8/16个电压区间
'a'的ASCII 0x61 → bits 01100001 → 分布到2个NAND page的cell
此时文件已存在NAND flash里,但数据在断电后的保留期取决于电荷泄漏速度(消费级通常标1年@30°C)。
三、打开文件 → 读到内存 → 显示到屏幕
① VFS读路径
open("/home/user/doc.txt", O_RDONLY) → 查dentry → inode → ext4找到文件block号
↓
read(fd, buf, 4096) → sys_read() → generic_file_read_iter()
↓
检查Page Cache → miss → 调用 address_space_operations.readpage()
② 块设备读 (逆写流程)
ext4_get_block() → 逻辑偏移 → 物理block号(LBA)
↓
submit_bio(READ) → NVMe驱动 → SQ entry发送到SSD
↓
SSD主控: L2P查表 → 物理NAND page地址 → 发Read命令
③ NAND读
R/B# Ready/Busy pin拉低 → 等待tR(~50μs,NAND最慢操作)
↓
Word Line加读电压 → Cell导通与否 → Sense Amp检测bit line电流
↓
读出原始电压 → LDPC硬判决/软判决解码 → ECC纠错
↓
如果错误位超过ECC纠错能力 → 不可纠正错误(UE)→返回读失败
④ DMA向上传递
SSD用PCIe DMA写到主机DRAM的指定page cache地址
↓
DMA完成中断 → NVMe IRQ handler → bio回调 → wake_up_page()
↓
数据从Page Cache copy_to_user到app buffer
⑤ 显示(同上面GPU/Display流程)
App解析UTF-8 → Freetype检索字形 → GPU rasterize → scanout → 屏幕
完整层级透视
┌─────────────────────────────────────────────────────────────┐
│ 用户态 User Space │
│ Word Processor (app逻辑, 信号槽, text buffer) │
│ ↓ │
│ GUI Toolkit (Qt/GTK) → Wayland/X11 Client │
│ ↓ │
│ libc (fwrite, read, fopen 包装) │
│ ↓ │
│ Font Engine (Freetype/HarfBuzz) → 字形像素化 │
├───────────────────────────┬─────────────────────────────────┤
│ 系统调用边界 (syscall) │ ring 3 → ring 0 │
├───────────────────────────┴─────────────────────────────────┤
│ 内核态 Kernel Space │
│ sys_write / sys_read → VFS layer │
│ ↓ │
│ EXT4/XFS → journal → block map → inode→block转换 │
│ ↓ │
│ Page Cache → 缺页处理 → dirty回写策略 │
│ ↓ │
│ Block Layer → bio请求 → IO scheduler(合并/排序) │
│ ↓ │
│ NVMe驱动 → SQ/CQ队列管理 → doorbell寄存器 │
├───────────────────────────┬─────────────────────────────────┤
│ 中断/AXI/PCIe │ 硬件边界 │
├───────────────────────────┴─────────────────────────────────┤
│ 硬件层 Hardware │
│ CPU MMU → TLB → L1/L2/L3 Cache │
│ Memory Controller → DDR Protocol → DRAM │
│ ↓ │
│ PCIe Root Complex → NVMe SSD (PCIe Gen4 x4 ~8GB/s) │
│ ↓ │
│ SSD主控: ARM Cortex-R系列MCU → DRAM(L2P表) → NAND │
│ ↓ │
│ NAND Flash Array: Word Line/Bit Line/Sense Amp/Floating Gate│
│ ↓ │
│ GPU: Raster Pipe → VRAM → DAC → DisplayPort → T-Con → LCD│
└─────────────────────────────────────────────────────────────┘
关键领悟
从键盘按下去到屏幕上显示 ‘a’,这一个字符走过了:
| 阶段 | 涉及中间步骤 | 约数 |
|---|---|---|
| 不同执行体切换 | USB MCU → kernel ISR → input core → libinput → wayland → app | 6层进程/线程切换 |
| syscall上下文切换 | save/restore regs, TLB刷新, spectre mitigation | ~100ns每次 |
| 模式切换 | user→kernel→user (写入page cache就返回) | 2次 ring切换 |
| 数据拷贝次数 | USB DMA buf → kernel buffer → user buffer (input event) | 至少3次拷贝 |
| Cache颠簸 | 每次切换上下文,cache cold,TLB flush | 损耗大量性能 |
| DMA外设交互 | USB xHCI / NVMe / GPU 三者各一次DMA | 3次 |
每个 ‘a’ 都付出这几千条指令和几十μs的代价,但你感觉不到——这就是现代OS抽象层的意义:用多层间接换来了通用性、安全性、便捷编程,代价是性能和复杂度的指数级增长。
7问:我能否这样笼统的理解为,从键盘键入一个字母后,从输入设备到了内存的输入段,然后从这一段转移到内存的内核段,然后从内核段,再拷贝到内存中对接硬盘的那一段,写入硬盘;再拷贝到显示设备的内存段,然后CPU执行到这里就把这里要显示的字母显示出来了
7答:
你的直觉方向对,但几个细节需要修正:
你说的路径(简版):
键盘 → 内存"输入段" → 内核段 → 硬盘段 → 显示段 → 屏幕
实际更准确的划分:
| 你说的 | 实际是什么 |
|---|---|
| 内存"输入段" | USB HID ring buffer(内核DMA区域,不经过用户态) |
| 转移到内核段 | copy_to_user() 送到用户态app的text buffer(不是内核段) |
| 对接硬盘的那一段 | Page Cache(内核管理,app不可见) |
| 显示设备的内存段 | Framebuffer / GPU显存(app通过DRM/KMS操作,不走CPU拷贝) |
核心纠正:
- 没有独立的"输入段" — 键盘数据直接DMA到内核的环形缓冲区,不是用户可见的内存段
- "内核段"不是中间中转站 — app的text buffer在用户态虚拟地址空间,不是内核段;只有Page Cache在内核空间
- 写入页不是"拷贝到硬盘段" — write() 只是拷贝到Page Cache就返回了,真正写SSD是异步的(除非fsync)
- 显示数据不经过CPU拷贝 — app告诉GPU “这个内存区域是显示内容”,GPU直接通过DMA读取,不经过CPU搬运
笼统理解倒是可以的,但要把"段"改一下:
键盘 → DMA到内核ring buffer
→ 拷贝到用户态app text buffer
→ (保存时)拷贝到内核Page Cache
→ SSD主控DMA读Page Cache写到NAND
→ (读取时)SSD DMA回Page Cache
→ 拷贝回用户态buffer
→ app把buffer地址告诉GPU
→ GPU DMA读 → scanout → 屏幕
关键原则:数据只在需要时才跨越用户/内核边界,不搞三段式搬运。
8问:这个操作示例中,数据为什么一会儿要给内核一会儿要给用户态,什么情况下要给内核态,什么情况下要给用户态
8答:核心原则:谁有权限处理这个资源,数据就在谁的空间里。
| 资源类型 | 拥有者 | 原因 |
|---|---|---|
| 键盘硬件 | 内核 | 内核管理所有硬件中断、DMA、寄存器;用户态无权直接操作IO端口 |
| 进程的文本数据 | 用户态 | 编辑、渲染、逻辑处理是app的业务,不需要内核干预 |
| 磁盘数据 | 内核 | 文件系统元数据、block分配、设备驱动都在内核;用户态无权直接写磁盘 |
| 屏幕显示 | 用户态(绘图) + 内核(模式设置) | app算像素(用户态),但配置CRTC/resolution/提交framebuffer需内核(DRM/KMS) |
判断标准一问便知:允许用户态任意读写这个资源,会不会崩了整个系统或看到别人的数据?
- 会 → 内核管(硬件/文件系统/进程内存隔离)
- 不会 → 用户态管(拼字符串/算颜色/排版布局)
所以数据流转不是瞎折腾,而是每次跨越都在说:“我要让有权限的那一方干活了。”
9问:其实用户在修改文档的内容,也只是使用系统让渡出来的键盘,或者鼠标输入的权限,从这里收集到的数据再由系统帮用户修改这里的数据。这样理解对吗
9答:
对,本质就是这样。
更精准的表述:
用户输入 → 经系统多层权限校验+抽象翻译后,才修改到进程的数据空间。
每一层都在说"不":
| 层级 | 拦截了什么 |
|---|---|
| 输入子系统 | 键盘中断→合成key event,不是直接写内存 |
| Wayland/X11 | 检查焦点窗口,不是任意进程都能收 |
| SELinux/AppArmor | 验证进程是否有权读取输入事件 |
| VFS权限 | 写文件前检查uid/gid、ACL、capability |
| DRM/KMS | 改显示模式需root或drm master权限 |
所以用户的每一次按键,本质上都是向系统"请求"帮自己做事,系统确认你有资格后,才代劳修改数据。
你点保存那一下,跟去政府窗口递申请表没什么区别——你只是填了表(按键),真正办成事的是窗口后面那套流程(系统各层级)。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)