简介:本资源是一份面向计算机专业本科生的操作系统课程期末复习笔记,聚焦核心概念梳理与考点精讲,帮助学生高效应对期末考试。笔记系统覆盖操作系统目标(方便性、有效性、可扩充性、开放性)、发展历程(人工操作→批处理→分时→实时系统)、四大基本特性(并发性、共享性、虚拟性、异步性),以及处理机、存储器、设备和文件四大管理功能;重点详解进程状态转换、PCB结构、进程同步机制(信号量P/V操作、整型/记录型/AND型/信号量集)、经典同步互斥问题(生产者-消费者、哲学家进餐)等高频难点。资源为1个1.17MB的Word文档(.docx),内容排版清晰、术语准确、逻辑连贯,适合作为考前速记与知识脉络梳理工具。目前已有779人学习下载,内容源自一线学习实践,知识点与教材章节(如第一章目标特性、第二章进程管理、第三章调度)高度对应,便于对照复习、查漏补缺。
1. 操作系统期末复习笔记:不是抄概念,是把“并发”“死锁”“页表”这些黑匣子拆开重装一遍
你是不是也经历过——考前翻完教材、背完定义,一看到“请用信号量实现哲学家进餐问题”,脑子瞬间空白?不是记不住,是没真正见过它们怎么动。这份操作系统期末复习笔记,不是知识罗列,而是一线工程师拆解真实内核逻辑后反向还原的“可执行思维导图”。它把“并发”从教科书里的四个字,变成你能画出三态转换图、能手写 wait/signal 顺序、能一眼看出哪里会死锁的肌肉记忆;把“虚拟内存”从“逻辑地址→物理地址”的抽象箭头,变成你能在纸上推演 TLB 命中/未命中时 EAT(有效访问时间)怎么算的计算过程;把“SPOOLing”从“假脱机”三个字,变成你清楚知道输入井在哪块磁盘、输出进程何时抢占 CPU、井管理程序怎么维护队列的现场感。它专为两类人设计:一类是刚学完课但做题总卡在“不知道该设几个信号量”的同学;另一类是已经调过 Linux 进程调度但对“为什么 HRRN 要算响应比”仍模糊的实践者。笔记覆盖全部核心考点,但拒绝堆砌术语——每个定义背后都跟着一个“它在内存里长什么样”或“它在 CPU 上跑哪几步”的具象锚点。现在,我们不背目标,我们直接进内核看它怎么干活。
2. 并发与同步:从“宏观同时”到“微观抢锁”,手撕进程状态与信号量实现
2.1 并发的本质不是并行,而是“停停走走”的确定性控制
很多人混淆并发(concurrency)和并行(parallelism)。并行是物理上多个 CPU 核心真正在同一时刻执行不同指令;而并发是单核 CPU 通过快速切换(context switch),让多个进程在宏观上看起来同时运行,但微观上永远只有一个在执行。这种“停停走走”不是随机的,而是由操作系统严格调度的——它依赖两个基石:进程控制块(PCB)和状态机。
PCB 是进程存在的唯一标志,它不是一个虚的概念,而是一段真实存在于内核内存中的数据结构。以 Linux 为例,task_struct结构体就是 PCB 的实体化。它包含:
pid_t pid:进程标识符(用户可见的 PID)long state:当前状态(TASK_RUNNING,TASK_INTERRUPTIBLE,TASK_UNINTERRUPTIBLE等)struct mm_struct *mm:指向该进程的内存管理结构(含页表基址)struct list_head tasks:链入全局进程链表的指针
提示:PCB 不是“进程本身”,而是操作系统管理进程的“身份证+档案袋+操作手册”。没有 PCB,OS 就不认识这个进程。
进程的三种基本状态(就绪、执行、阻塞)构成最简状态机。但真实系统远比这复杂。例如,当一个进程被创建但尚未获得 CPU 时间片时,它处于创建状态(New);当它完成或被终止后,进入终止状态(Terminated),此时 PCB 尚未被回收,需等待父进程调用wait()获取其退出码。更关键的是挂起状态(Suspended):当内存紧张,OS 会将就绪态或阻塞态的进程“换出”(swap out)到外存,并将其状态改为挂起。此时它既不在内存就绪队列,也不在 I/O 等待队列,而是在外存的挂起队列中。只有当内存空闲且该进程被重新换入(swap in)后,才可能回到就绪态。这解释了为什么你在top中看到S(sleeping)和T(stopped)之外还有Z(zombie)——僵尸进程就是已终止但父进程未收割的挂起态残留。
2.2 临界区与互斥:为什么“先检查后上锁”会翻车?
并发带来的核心问题是资源共享冲突。打印机、共享缓冲区、计数器变量……这些资源一次只能被一个进程安全访问,这段访问代码就是临界区(Critical Section)。保证互斥的朴素想法是:“我先看看锁有没有被占,没被占我就上锁”。但这就是经典的竞态条件(Race Condition)—— 在“检查”和“上锁”这两个操作之间,另一个进程可能插进来。
我们用伪代码演示双标志先检查法(Two-Flag Pre-Check)的致命缺陷:
// 全局变量 int flag[2] = {0, 0}; // flag[i] = 1 表示进程 i 想进入临界区 int turn = 0; // 轮到谁 // 进程 0 的代码 while (flag[1]) { // 先检查进程1是否想进 if (turn == 1) { // 如果轮到进程1 flag[0] = 0; // 我放弃,让给它 while (turn == 1); // 等它让出 flag[0] = 1; } } // 进入临界区... flag[0] = 0; // 退出临界区现象:两个进程可能同时卡在while(flag[1])和while(flag[0])中,互相等待对方turn变化,形成死锁。
原因:flag[i] = 1和turn = j不是原子操作,中间可被中断;且while循环是忙等(busy-wait),CPU 白白空转。
解决:必须用硬件支持的原子指令(如 x86 的LOCK XCHG)或操作系统提供的原语(Primitive)。原语是关中断后执行的一小段不可分割的代码,确保wait(S)或signal(S)执行期间不会被调度打断。
2.3 信号量机制:从整型到记录型,为什么“让权等待”是分水岭?
信号量(Semaphore)是解决互斥与同步的通用工具。它的核心是两个原子操作:wait(S)(P 操作,申请资源)和signal(S)(V 操作,释放资源)。但不同实现方式能力天差地别。
整型信号量(Integer Semaphore)最简单:
typedef int semaphore; semaphore S = 1; // 初值为1,表示一个互斥资源 void wait(semaphore *S) { while (*S <= 0); // 忙等!CPU空转 (*S)--; } void signal(semaphore *S) { (*S)++; }特征:仅靠一个整数,实现简单。
作用:初值为1时,可实现互斥。
致命缺陷:while(*S<=0)导致忙等,浪费 CPU;且不满足“让权等待”原则——进程应主动放弃 CPU,让给其他就绪进程。
记录型信号量(Record Semaphore)彻底解决此问题:
typedef struct { int value; // 当前可用资源数 struct list_head L; // 阻塞在此信号量上的进程等待队列 } semaphore; void wait(semaphore *S) { S->value--; if (S->value < 0) { add_current_to_list(&S->L); // 将当前进程加入等待队列 block(); // 主动阻塞自己,让出CPU } } void signal(semaphore *S) { S->value++; if (S->value <= 0) { PCB *p = remove_from_list(&S->L); // 从队列取一个进程 wakeup(p); // 唤醒它,使其进入就绪态 } }特征:value为负时,其绝对值即为等待进程数;有显式等待队列L。
作用:完美实现“让权等待”;value具有明确物理意义(资源数/等待数)。
参数说明:S->value初始值决定资源数量。互斥场景设为1;同步场景(如生产者-消费者)常设为0(表示“前操作未完成,后操作不能开始”)。
注意:Linux 内核中
struct semaphore的实现正是记录型。down()对应wait(),up()对应signal()。down_interruptible()还支持被信号中断,这是整型信号量完全无法做到的。
2.4 经典同步问题实战:生产者-消费者,四步法拆解无死角
生产者-消费者问题是检验同步理解的试金石。它要求:1)生产者不能往满缓冲区放数据;2)消费者不能从空缓冲区取数据;3)缓冲区是临界资源,需互斥访问。四步法是解题铁律:
识别临界资源与同步点:
- 临界资源:共享缓冲区
buffer[N]→ 需互斥信号量mutex(初值=1) - 同步点1:生产者需等待“缓冲区有空位” → 信号量
empty(初值=N) - 同步点2:消费者需等待“缓冲区有数据” → 信号量
full(初值=0)
- 临界资源:共享缓冲区
初始化信号量:
semaphore mutex = 1; // 互斥访问buffer semaphore empty = N; // 空槽数 semaphore full = 0; // 已填槽数生产者流程(严格顺序!):
while (1) { produce_item(&item); // 生产一个item wait(&empty); // ① 先等空位(否则阻塞) wait(&mutex); // ② 再上互斥锁(保护buffer) insert_item(&item); // ③ 放入buffer signal(&mutex); // ④ 解锁 signal(&full); // ⑤ 通知消费者:有新数据了! }关键:
wait(&empty)必须在wait(&mutex)之前!如果反过来,生产者先锁住 buffer,再等 empty,会导致所有生产者都卡在锁上,消费者永远无法进入取数据,造成死锁。消费者流程(对称):
while (1) { wait(&full); // ① 先等有数据 wait(&mutex); // ② 再上锁 remove_item(&item); // ③ 取出item signal(&mutex); // ④ 解锁 signal(&empty); // ⑤ 通知生产者:空出一个位 consume_item(&item); // ⑥ 消费 }
避坑 / 常见问题 / 排查
现象:程序运行一会儿就卡死,
top显示 CPU 占用率 100%。
原因:信号量初值设错(如full=1),或wait/signal顺序颠倒导致死锁。
解决:用printf("P: wait empty\n")加日志,确认每一步执行顺序;用strace -e trace=semop跟踪系统调用。现象:生产者很快填满缓冲区,但消费者只取了几个就停止。
原因:signal(&full)被放在signal(&mutex)之后,但signal(&full)本身不需互斥(它只修改计数器),位置正确;更可能是remove_item()逻辑错误,导致缓冲区索引越界,后续wait(&full)失败。
解决:检查remove_item()是否正确更新读指针in/out,并做模 N 运算。现象:多消费者时,偶尔取出重复数据或跳过数据。
原因:mutex保护范围不足。remove_item()和consume_item()应在同一个mutex临界区内?不!consume_item()是纯计算,无需互斥。但若remove_item()返回的是指针而非拷贝,则consume_item()必须在临界区内,否则指针可能被下一个消费者覆盖。
解决:统一用值传递(item = remove_item()),确保临界区只包含remove_item()和指针更新。现象:程序启动后立即报
sem_wait: Invalid argument。
原因:信号量未初始化。POSIX 信号量需sem_init(&sem, 0, 1);System V 信号量需semget()创建。
解决:检查初始化代码,用perror()打印错误。现象:在多线程程序中,
pthread_mutex_lock()成功,但数据仍错乱。
原因:误以为mutex能解决所有问题。mutex只保证互斥,不保证同步!empty/full信号量缺失,生产者可能在缓冲区满时仍尝试insert_item()。
解决:牢记:互斥(mutex)管“能不能进”,同步(semaphore)管“该不该进”。
3. 内存管理:从“装入”到“置换”,亲手推演页表与缺页中断全过程
3.1 装入方式选择:为什么现代 OS 必须用动态运行时装入?
程序要运行,必须经历编译→链接→装入三步。装入方式决定了程序如何从磁盘搬到内存,直接影响内存利用率和灵活性。
绝对装入(Absolute Loading):编译时就确定所有指令和数据的物理地址(如
0x1000),装入程序直接按此地址复制。
适用场景:单道批处理系统,内存中永远只有一个程序。
致命缺陷:程序无法在内存中移动(重定位),无法支持多道程序——因为多个程序的绝对地址必然冲突。可重定位装入(Relocatable Loading):编译生成的是逻辑地址(从0开始的相对地址),装入时由装入程序将所有地址加上一个偏移量(如基址
0x8000),得到物理地址。这叫静态重定位。
优势:支持多道程序,各程序逻辑地址空间独立。
缺陷:一旦装入,偏移量固定,程序在内存中位置无法改变。如果内存碎片化,一个大程序找不到连续空间,即使总空闲内存足够也无法装入。动态运行时装入(Dynamic Run-time Loading):程序装入内存时,不进行地址转换,所有地址仍保持逻辑地址。真正的地址转换(逻辑地址→物理地址)推迟到每条指令执行时才由硬件(MMU)完成。
核心机制:CPU 生成逻辑地址(页号+页内偏移),MMU 查页表得物理块号,拼成物理地址。
为什么必须用它?- 支持紧凑(Compaction):当内存碎片多时,OS 可将所有运行进程“挪动”到内存一端,合并出大块空闲区。若用静态重定位,挪动后所有进程的基址都要重算并修改代码,开销巨大;而动态装入只需修改页表中各页的物理块号,进程代码零改动。
- 支持虚拟内存:逻辑地址空间可远大于物理内存,页表项中“存在位(P)”为0时,访问触发缺页中断,OS 从外存调入页面。
- 支持共享库:多个进程可映射同一物理页面(如 libc.so),页表项指向相同块号,节省内存。
提示:
ldd命令显示的libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f...)中的0x00007f...就是该共享库被动态装入到当前进程逻辑地址空间的起始地址,每次运行都可能不同(ASLR 机制),这只有动态装入才能实现。
3.2 分页地址变换:没有快表 vs 有快表,EAT 计算差 3 倍!
分页将逻辑地址分为页号(Page Number)和页内偏移(Page Offset)。假设页面大小为 4KB(2^12 字节),则低 12 位是偏移量,高位是页号。
地址变换步骤(无快表):
- CPU 生成逻辑地址
LA - MMU 提取页号
PN = LA >> 12 - MMU 用
PN作索引,访问内存中的页表,读取对应页表项(PTE) - PTE 中含物理块号
FN(Frame Number)和存在位P - 若
P=0,触发缺页中断;若P=1,计算物理地址PA = (FN << 12) | (LA & 0xFFF) - MMU 用
PA访问内存取数据
问题:步骤3和步骤6都是内存访问,共需2次访存,严重拖慢速度。
快表(TLB, Translation Lookaside Buffer)是 MMU 内置的高速缓存,存储最近使用的页表项(PN→FN 映射)。它使地址变换变为:
| TLB 状态 | 访问步骤 | 有效访问时间(EAT)计算 |
|---|---|---|
| TLB 命中 | 1. TLB 查PN→FN2. `PA = (FN<<12) | offset<br>3. 用PA` 访存 |
| TLB 未命中 | 1. TLB 查失败 2. 访存页表得 FN3. `PA = (FN<<12) | offset<br>4. 用PA` 访存5. 将新 PTE 写入 TLB |
EAT 公式(综合命中率):
设 TLB 访问时间t_TLB = 1ns,内存访问时间t_mem = 100ns,TLB 命中率h = 0.95(95%)
- 无 TLB:
EAT = 2 * t_mem = 200ns - 有 TLB:
EAT = h*(t_TLB + t_mem) + (1-h)*(t_TLB + t_mem + t_mem + t_TLB)= 0.95*(1+100) + 0.05*(1+100+100+1) = 95.95 + 10.1 = 106.05ns
结论:TLB 将平均访存时间从 200ns 降到 106ns,性能提升近一倍。若命中率升至 0.99,EAT ≈ 101ns;若降至 0.8,EAT ≈ 121ns。因此,TLB 容量和替换算法(如 LRU)直接影响系统性能。
3.3 页面置换算法:为什么 FIFO 是最差的,而 LRU 是理想标杆?
当进程访问一个不在内存的页(P=0)时,触发缺页中断。OS 必须:1)从外存调入该页;2)若内存已满,需置换一个现有页。置换算法的好坏,直接决定缺页率(Page Fault Rate)。
| 算法 | 核心思想 | 优缺点 | 实现难度 | 典型应用 |
|---|---|---|---|---|
| 最佳置换(OPT) | 淘汰未来最久不使用的页 | 性能最优(缺页率最低),但需预知未来,无法实现 | 不可实现 | 仅作理论基准 |
| 先进先出(FIFO) | 淘汰最早装入内存的页 | 实现极简(队列),但可能淘汰常用页(Belady 异常:增加内存帧数,缺页率反而上升) | ★☆☆☆☆ | 教学演示 |
| 最近最久未使用(LRU) | 淘汰最久未被访问的页 | 最接近 OPT,符合局部性原理,缺页率低 | ★★★★☆(需硬件支持访问位/时间戳) | 主流 OS(Linuxactive/inactiveLRU 链表) |
| Clock(时钟) | LRU 近似:页表项加访问位 A,循环扫描,A=0 则淘汰,A=1 则清0继续 | 软硬结合,开销远小于真 LRU,效果好 | ★★★☆☆ | 广泛使用(Windows, Linux) |
Clock 算法详解(带修改位):
为避免淘汰已修改(脏)页导致额外 I/O,改进型 Clock 增加修改位 M。页表项含<A, M>。算法维护一个指针,循环扫描:
- 若
(A, M) = (0, 0):淘汰,无 I/O - 若
(A, M) = (0, 1):淘汰,需写回外存 - 若
(A, M) = (1, 0)或(1, 1):将 A 置 0,指针移向下一页
为什么 FIFO 最差?
考虑访问序列1,2,3,4,1,2,5,1,2,3,4,5,内存帧数=3:
- FIFO:
[1,2,3] → [2,3,4] → [3,4,1] → [4,1,2] → [1,2,5] → [2,5,1] → [5,1,2] → [1,2,3] → [2,3,4] → [3,4,5],缺页 10 次 - LRU:
[1,2,3] → [2,3,4] → [3,4,1] → [4,1,2] → [1,2,5] → [2,5,1] → [5,1,2] → [1,2,3] → [2,3,4] → [3,4,5],但因 LRU 会保留最近用的,实际缺页约 8 次(优于 FIFO)
避坑 / 常见问题 / 排查
现象:系统运行缓慢,
vmstat 1显示si(swap in)和so(swap out)持续为正,free显示可用内存极少。
原因:内存严重不足,页面置换频繁(Thrashing),CPU 大量时间花在换页而非执行。
解决:ps aux --sort=-%mem | head -10查内存大户;echo 1 > /proc/sys/vm/swappiness降低交换倾向;终极方案:加物理内存。现象:
cat /proc/[pid]/maps显示某段内存r-xp(可读可执行)但P位为0,访问时报Segmentation fault。
原因:该页被换出,但进程未触发缺页中断(如访问的是未映射区域)。
解决:确保访问前已通过mmap()或malloc()分配;用mincore()检查页是否驻留。现象:用
perf record -e page-faults测得缺页率极高,但free显示内存充足。
原因:程序分配了大量虚拟内存(malloc成功),但未真正访问(touch),这些页P=0,首次访问才触发缺页。这是正常行为,非错误。
解决:用mlock()锁定关键页防止换出;或用memset()预热内存。现象:自定义页表遍历时,发现
PTE中FN字段为0,但P=1。
原因:FN=0是合法物理块号(第0块),并非错误。页表项格式由 CPU 架构定义(x86-64 中FN占高40位)。
解决:查阅 Intel SDM Vol3A 确认 PTE 位域;用pagemap文件解析/proc/[pid]/pagemap。现象:在 QEMU/KVM 中调试,
info mem显示页表基址,但手动查页表得到的物理地址与readelf -l显示的LOAD段不符。
原因:readelf显示的是链接时的虚拟地址(VMA),而页表映射的是运行时的物理地址(PMA),二者通过 MMU 转换。
解决:用gdb的info proc mappings查进程实际 VMA;用pahole分析内核结构体布局。
4. 设备管理与文件系统:从“轮询”到“SPOOLing”,看 OS 如何驯服慢速外设
4.1 I/O 控制方式演进:为什么 DMA 是现代系统的基石?
CPU 与 I/O 设备速度差异巨大(CPU GHz 级,磁盘 ms 级)。若让 CPU 主动等待设备,效率极低。I/O 控制方式经历了四代演进:
程序 I/O(轮询):CPU 执行
while(!device_ready);循环查询设备状态。
问题:CPU 100% 空转,无法干其他事。
适用:极简单嵌入式系统,或设备响应极快(如 GPIO)。中断驱动 I/O:CPU 发出 I/O 命令后去干别的,设备就绪后发中断,CPU 暂停当前任务,执行中断处理程序(ISR)完成数据传输。
进步:CPU 利用率大幅提升。
瓶颈:每次传输一个字节/字,中断开销大(保存/恢复上下文约 1μs);高速设备(如千兆网卡)每秒数万中断,CPU 疲于奔命。DMA(Direct Memory Access):引入DMA 控制器,作为 CPU 和设备间的“搬运工”。CPU 仅需告诉 DMA:源地址、目的地址、长度、方向。DMA 自行完成数据块传输,完成后发一个中断通知 CPU。
优势:一次中断处理一个数据块(如 64KB),中断次数减少万倍;CPU 完全不参与数据搬运,专注计算。
关键寄存器:DMA_ADDR(内存地址)、DMA_COUNT(字节数)、DMA_CMD(启动/停止)。
Linux 示例:dma_map_single()建立内存与设备的 DMA 映射;dmaengine_submit()提交传输请求。I/O 通道(Channel):DMA 的升级版,通道是专用处理器,可执行微码(Channel Program)完成复杂 I/O 操作(如读多个扇区、校验、纠错)。CPU 只需发一条“启动通道”命令,通道自主执行整个 I/O 序列。
现状:现代服务器的 SCSI/SAS HBA、NVMe SSD 控制器,本质就是智能通道。
提示:
lspci -vv | grep -A 10 "DMA"可查看设备的 DMA 能力;/sys/class/dma/目录下是 Linux 的 DMA 引擎接口。
4.2 SPOOLing 技术:如何把一台打印机变成“云打印”?
SPOOLing(Simultaneous Peripheral Operations On-Line)是操作系统实现虚拟设备的核心技术。它把独占的慢速设备(如打印机),通过磁盘缓冲和后台进程,变成多个用户可同时使用的逻辑设备。
SPOOLing 四部分详解:
| 组件 | 位置 | 功能 | 关键细节 |
|---|---|---|---|
| 输入井 / 输出井 | 外存(磁盘) | 输入井:暂存从输入设备(如键盘)读入的数据;输出井:暂存用户要打印的数据 | 本质是磁盘上的大文件(如/var/spool/lpd/input/),按作业分目录 |
| 输入缓冲区 / 输出缓冲区 | 内存 | 缓冲区:暂存设备与井之间的数据,缓解 CPU-磁盘速度差 | 大小通常为 4KB~64KB,由kmalloc()分配 |
| 输入进程 / 输出进程 | 内存(内核线程) | 输入进程:模拟外围机,将输入设备数据写入输入井;输出进程:从输出井取数据,送至输出设备 | Linux 中lpd或cupsd进程承担此角色 |
| 井管理程序 | 内存(内核模块) | 管理井中作业队列,处理作业提交、优先级、状态查询 | lpq查队列,lprm删作业,lpc控制打印机 |
打印机 SPOOLing 工作流:
- 用户执行
lpr report.txt lpr命令:- 在输出井(磁盘)创建文件
cfA001xxx(控制文件,含用户、优先级等) - 创建数据文件
dfA001xxx(report.txt内容) - 将两文件名写入假脱机文件队列(内存链表)
- 在输出井(磁盘)创建文件
- 假脱机管理进程(如
lpd):- 监听队列,发现新作业
- 为作业分配打印缓冲区(内存)
- 假脱机打印进程:
- 从队列取作业
- 将
dfA001xxx数据读入打印缓冲区 - 调用
ioctl(fd, LPSETIRQ, ...)向打印机驱动发命令 - 驱动通过 DMA 将缓冲区数据传给打印机控制器
- 打印完成,删除井中文件,更新队列
为什么叫“脱机”?
因为用户进程lpr提交后立即返回,不等待打印完成;输入/输出操作由后台进程在“脱机”(用户不可见)状态下完成。用户感觉是“即时”打印,实则是 OS 在后台调度。
4.3 文件系统基础:Inode 与 Block,Linux 文件存储的真相
文件系统是 OS 管理外存的抽象层。Linux ext4 文件系统核心是Inode(索引节点)和Data Block(数据块)。
Inode:每个文件/目录有唯一 Inode(编号),存储元数据:
st_mode(权限)、st_uid/st_gid(所有者)、st_size(大小)、st_atime/st_mtime/st_ctime(时间戳)、st_blocks(占用块数)、15个直接块指针 + 1级间接块指针 + 2级间接块指针 + 3级间接块指针。注意:Inode不存储文件名!文件名存在目录的 Data Block 中,作为
filename -> inode_number的映射。Data Block:存储文件实际内容。块大小通常 4KB。Inode 中的指针指向这些块。
文件读取过程(以cat file.txt为例):
- Shell 解析
file.txt,在当前目录的 Data Block 中查找file.txt→ 得到其 Inode 编号i=12345 - 读取 Inode Table 中第 12345 个 Inode → 获取
st_size=10240字节,及块指针列表 - 若
st_size ≤ 48B(12×4B 直接指针),数据全在 Inode 中(ext4 的 inline data 特性) - 若
st_size ≤ 48KB(12×4KB),用 12 个直接块指针,依次读取 Data Block - 若更大,用一级间接块:读取间接块(一个 4KB 块,存 1024 个指针)→ 得到更多 Data Block 地址
- MMU 将 Data Block 物理地址映射到用户空间,
cat进程读取
ls -li命令揭示真相:
$ ls -li /etc/passwd 123456 -rw-r--r-- 1 root root 2142 Jan 1 10:00 /etc/passwd # 123456 是 Inode 号,不是文件大小!debugfs直接窥探:
# 查看 /etc/passwd 的 Inode 信息 $ sudo debugfs -R 'stat /etc/passwd' /dev/sda1 <p> <a href="https://download.csdn.net/download/qq_52048052/87384446" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>