news 2026/11/2 2:02:58

操作系统核心概念拆解:并发、内存管理与I/O原理实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
操作系统核心概念拆解:并发、内存管理与I/O原理实战

简介:本资源是一份面向计算机专业本科生的操作系统课程期末复习笔记,聚焦核心概念梳理与考点精讲,帮助学生高效应对期末考试。笔记系统覆盖操作系统目标(方便性、有效性、可扩充性、开放性)、发展历程(人工操作→批处理→分时→实时系统)、四大基本特性(并发性、共享性、虚拟性、异步性),以及处理机、存储器、设备和文件四大管理功能;重点详解进程状态转换、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)缓冲区是临界资源,需互斥访问。四步法是解题铁律:

  1. 识别临界资源与同步点:

    • 临界资源:共享缓冲区buffer[N]→ 需互斥信号量mutex(初值=1)
    • 同步点1:生产者需等待“缓冲区有空位” → 信号量empty(初值=N)
    • 同步点2:消费者需等待“缓冲区有数据” → 信号量full(初值=0)
  2. 初始化信号量:

    semaphore mutex = 1; // 互斥访问buffer semaphore empty = N; // 空槽数 semaphore full = 0; // 已填槽数
  3. 生产者流程(严格顺序!):

    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,会导致所有生产者都卡在锁上,消费者永远无法进入取数据,造成死锁。

  4. 消费者流程(对称):

    while (1) { wait(&full); // ① 先等有数据 wait(&mutex); // ② 再上锁 remove_item(&item); // ③ 取出item signal(&mutex); // ④ 解锁 signal(&empty); // ⑤ 通知生产者:空出一个位 consume_item(&item); // ⑥ 消费 }

避坑 / 常见问题 / 排查

  1. 现象:程序运行一会儿就卡死,top显示 CPU 占用率 100%。
    原因:信号量初值设错(如full=1),或wait/signal顺序颠倒导致死锁。
    解决:用printf("P: wait empty\n")加日志,确认每一步执行顺序;用strace -e trace=semop跟踪系统调用。

  2. 现象:生产者很快填满缓冲区,但消费者只取了几个就停止。
    原因:signal(&full)被放在signal(&mutex)之后,但signal(&full)本身不需互斥(它只修改计数器),位置正确;更可能是remove_item()逻辑错误,导致缓冲区索引越界,后续wait(&full)失败。
    解决:检查remove_item()是否正确更新读指针in/out,并做模 N 运算。

  3. 现象:多消费者时,偶尔取出重复数据或跳过数据。
    原因:mutex保护范围不足。remove_item()和consume_item()应在同一个mutex临界区内?不!consume_item()是纯计算,无需互斥。但若remove_item()返回的是指针而非拷贝,则consume_item()必须在临界区内,否则指针可能被下一个消费者覆盖。
    解决:统一用值传递(item = remove_item()),确保临界区只包含remove_item()和指针更新。

  4. 现象:程序启动后立即报sem_wait: Invalid argument。
    原因:信号量未初始化。POSIX 信号量需sem_init(&sem, 0, 1);System V 信号量需semget()创建。
    解决:检查初始化代码,用perror()打印错误。

  5. 现象:在多线程程序中,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 查页表得物理块号,拼成物理地址。
    为什么必须用它?

    1. 支持紧凑(Compaction):当内存碎片多时,OS 可将所有运行进程“挪动”到内存一端,合并出大块空闲区。若用静态重定位,挪动后所有进程的基址都要重算并修改代码,开销巨大;而动态装入只需修改页表中各页的物理块号,进程代码零改动。
    2. 支持虚拟内存:逻辑地址空间可远大于物理内存,页表项中“存在位(P)”为0时,访问触发缺页中断,OS 从外存调入页面。
    3. 支持共享库:多个进程可映射同一物理页面(如 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 位是偏移量,高位是页号。

地址变换步骤(无快表):

  1. CPU 生成逻辑地址LA
  2. MMU 提取页号PN = LA >> 12
  3. MMU 用PN作索引,访问内存中的页表,读取对应页表项(PTE)
  4. PTE 中含物理块号FN(Frame Number)和存在位P
  5. 若P=0,触发缺页中断;若P=1,计算物理地址PA = (FN << 12) | (LA & 0xFFF)
  6. MMU 用PA访问内存取数据

问题:步骤3和步骤6都是内存访问,共需2次访存,严重拖慢速度。

快表(TLB, Translation Lookaside Buffer)是 MMU 内置的高速缓存,存储最近使用的页表项(PN→FN 映射)。它使地址变换变为:

TLB 状态访问步骤有效访问时间(EAT)计算
TLB 命中1. TLB 查PN→FN
2. `PA = (FN<<12)
offset<br>3. 用PA` 访存
TLB 未命中1. TLB 查失败
2. 访存页表得FN
3. `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)

避坑 / 常见问题 / 排查

  1. 现象:系统运行缓慢,vmstat 1显示si(swap in)和so(swap out)持续为正,free显示可用内存极少。
    原因:内存严重不足,页面置换频繁(Thrashing),CPU 大量时间花在换页而非执行。
    解决:ps aux --sort=-%mem | head -10查内存大户;echo 1 > /proc/sys/vm/swappiness降低交换倾向;终极方案:加物理内存。

  2. 现象:cat /proc/[pid]/maps显示某段内存r-xp(可读可执行)但P位为0,访问时报Segmentation fault。
    原因:该页被换出,但进程未触发缺页中断(如访问的是未映射区域)。
    解决:确保访问前已通过mmap()或malloc()分配;用mincore()检查页是否驻留。

  3. 现象:用perf record -e page-faults测得缺页率极高,但free显示内存充足。
    原因:程序分配了大量虚拟内存(malloc成功),但未真正访问(touch),这些页P=0,首次访问才触发缺页。这是正常行为,非错误。
    解决:用mlock()锁定关键页防止换出;或用memset()预热内存。

  4. 现象:自定义页表遍历时,发现PTE中FN字段为0,但P=1。
    原因:FN=0是合法物理块号(第0块),并非错误。页表项格式由 CPU 架构定义(x86-64 中FN占高40位)。
    解决:查阅 Intel SDM Vol3A 确认 PTE 位域;用pagemap文件解析/proc/[pid]/pagemap。

  5. 现象:在 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 控制方式经历了四代演进:

  1. 程序 I/O(轮询):CPU 执行while(!device_ready);循环查询设备状态。
    问题:CPU 100% 空转,无法干其他事。
    适用:极简单嵌入式系统,或设备响应极快(如 GPIO)。

  2. 中断驱动 I/O:CPU 发出 I/O 命令后去干别的,设备就绪后发中断,CPU 暂停当前任务,执行中断处理程序(ISR)完成数据传输。
    进步:CPU 利用率大幅提升。
    瓶颈:每次传输一个字节/字,中断开销大(保存/恢复上下文约 1μs);高速设备(如千兆网卡)每秒数万中断,CPU 疲于奔命。

  3. 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()提交传输请求。

  4. 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 工作流:

  1. 用户执行lpr report.txt
  2. lpr命令:
    • 在输出井(磁盘)创建文件cfA001xxx(控制文件,含用户、优先级等)
    • 创建数据文件dfA001xxx(report.txt内容)
    • 将两文件名写入假脱机文件队列(内存链表)
  3. 假脱机管理进程(如lpd):
    • 监听队列,发现新作业
    • 为作业分配打印缓冲区(内存)
  4. 假脱机打印进程:
    • 从队列取作业
    • 将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为例):

  1. Shell 解析file.txt,在当前目录的 Data Block 中查找file.txt→ 得到其 Inode 编号i=12345
  2. 读取 Inode Table 中第 12345 个 Inode → 获取st_size=10240字节,及块指针列表
  3. 若st_size ≤ 48B(12×4B 直接指针),数据全在 Inode 中(ext4 的 inline data 特性)
  4. 若st_size ≤ 48KB(12×4KB),用 12 个直接块指针,依次读取 Data Block
  5. 若更大,用一级间接块:读取间接块(一个 4KB 块,存 1024 个指针)→ 得到更多 Data Block 地址
  6. 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>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 1:59:53

【QT C++】常用设计模式

前言 本篇的设计模式主要是&#xff1a;单例模式、工厂方法模式、策略模式、观察者模式、适配器模式。 单例模式 保证一个类仅有一个实例&#xff0c;并提供一个访问它的全局访问点。 特点 唯一实例&#xff1a;类自己控制实例创建&#xff0c;保证全局只有一个对象。私有构…

作者头像 李华
网站建设 2026/11/2 1:59:42

AnythingLLM:让 AI 读懂你的文档,搭建能问答的知识库

AnythingLLM 可以把产品手册、内部 FAQ 等资料接入文档问答&#xff0c;让你直接问“首次配置要准备什么”“连接超时先查哪里”。第一次上手&#xff0c;建议从同一产品的一份手册和一份 FAQ 开始&#xff1a;创建 Workspace、配置模型、导入并嵌入资料&#xff0c;再用原文中…

作者头像 李华
网站建设 2026/11/2 1:59:10

论文查AI率、降AI率、查重降重:10个AI工具汇总,2026年10月最新

相信很多小伙伴在写完文章之后都会有着或多或少的烦恼 有的可能在烦自己的文章ai率太高&#xff0c;有的在烦自己文章的查重率太高&#xff0c;有的更苦恼的是自己文章的ai率和查重率都很高。还有的小伙伴可能在烦恼文章写完了之后怎么查呢&#xff1f;用什么工具查呢&#xff…

作者头像 李华
网站建设 2026/11/2 1:58:43

16个扬声器全部PASS,智能座舱低音却被自己抵消了!为什么两只喇叭同时工作,声音反而比一只更小?

16个扬声器全部PASS&#xff0c;智能座舱低音却被自己抵消了&#xff01;为什么两只喇叭同时工作&#xff0c;声音反而比一只更小&#xff1f;《智能驾驶与智能座舱硬件技术实战》第47篇 关键词&#xff1a;Class-D功放、TDM、IS、扬声器极性、DSP分频、声学相位、群延迟、低频…

作者头像 李华