跳转至

操作系统

信号量

  1. 信号量可以是负值,每P一次就会减1。

I/O层次结构

1. 场景联想记忆法

  • 第一层:用户层 I/O 软件 = “大老板”
  • 角色: 只负责提需求,完全不管细节。
  • 动作: 老板说“给我打印这份文件”(调用 printf 等库函数)。老板还会把任务丢给秘书排队(SPOOLing 技术就是在用户层实现的)。
  • 第二层:设备独立性软件 = “部门经理”
  • 角色: 承上启下,做统一的管理、安保和资源分配。他不干具体脏活,但要负责统筹。
  • 动作:
    • 查字典(映射): 老板说用“1号打印机”(逻辑设备),经理去查表,发现“1号”对应的是走廊那台惠普打印机(物理设备)。这叫逻辑设备到物理设备的映射。
    • 安保(保护): 检查老板有没有权限用这台打印机。
    • 分配资源与缓冲: 安排缓冲区,统一管理所有设备的接口。
  • 第三层:设备驱动程序 = “专业操作员”
  • 角色: 懂具体机器的技术骨干,跟具体的硬件(比如那台惠普打印机)直接打交道。
  • 动作: 经理让他打印,他得把指令翻译成机器听得懂的话。比如“把纸张送入、加热到180度、控制墨盒移动”。这就是计算具体的物理地址(如磁盘的柱面/磁道),并向硬件的寄存器发送控制命令。换个牌子的打印机,就得换个懂行的操作员(所以驱动程序是与设备直接相关的)。
  • 第四层:中断处理程序 = “机器报警器”
  • 角色: 最底层,只负责听硬件的动静。
  • 动作: 机器打印完了,“滴”一声响(硬件中断),它就负责接收入这个信号,然后层层往上汇报“活儿干完了,把操作员唤醒吧”。

2. 考试“抓题眼”速记表

在做单选题时,只要看到题干里出现特定的关键词(题眼),就可以直接秒杀对应层次,不需要每次都从头推导:

看到这些关键词... 直接选这个层次!
库函数、SPOOLing、系统调用接口(向用户提供) 用户层 I/O 软件
逻辑设备、映射、保护、统一接口、分配、缓冲 设备独立性软件 (与设备无关的软件)
寄存器、硬件细节、逻辑地址转物理地址、启动设备 设备驱动程序
唤醒阻塞进程、保存被中断进程的CPU环境 中断处理程序

页

PDBR

虚拟地址是给程序(软件)看的,而 PDBR 是给 MMU(硬件)指路的。硬件找硬件,只能用物理地址。

一个操作系统中存在多个进程,因此也会有多个顶级页表,因此进程切换时需要更改PDBR中的内容。

主存物理地址与虚拟地址

  1. 主存物理地址(Physical Address)是指计算机内存(主存)中实际存储单元的硬件地址。

在采用了虚拟存储管理的计算机中,CPU 运行程序时产生的是虚拟地址(逻辑地址),这个虚拟地址需要经过转换,才能变成实际去内存里拿数据的物理地址。

物理地址的构成为:物理地址 = 实页号(也叫物理页号)+ 页内地址。

  1. 虚拟地址(对CPU)

虚拟地址的组成:页目录号 + 页号 + 页内地址。(由二级页表举例)

每一个“页目录项”里面存放的是某一张具体的二级页表在物理内存中的起始地址。

计算虚拟空间(虚拟存储空间)的大小,唯一需要看的指标就是虚拟地址的总位数。

虚拟地址的前面部分(在图中叫“虚拟号”,通常也叫“虚拟页号”)的位数,直接决定了整个虚拟存储空间里总共包含多少个虚拟页。

页面置换算法

image-20260918165218652

计算文件最大大小

算文件系统能支持的最大文件长度时,应当是用簇大小 / 地址项长度,而不是 / 索引节点大小。

用户级线程与内核级线程

用户级线程由线程库管理,是“黑户”;内核级线程由操作系统管理,并有自己的”户口本“(TCB)存放在内核空间。

前者切换快,后者切换慢。详细请看强化/3。

“被唤醒”(Wakeup)指的是进程从阻塞态(等待态)转换到就绪态。

死锁

image-20260921152925732

image-20260921153113797

限制用户申请资源顺序的只有死锁预防,如通过顺序分配资源来破坏循环等待。

内部碎片和外部碎片

各内存管理方式的碎片情况

连续分配方式

  • 单一连续分配:有内部碎片,无外部碎片。(整个内存只给一个进程用)
  • 固定分区分配:有内部碎片,无外部碎片。(按固定大小分区,进程装不满分区)
  • 动态分区分配:无内部碎片,有外部碎片。(要多少切多少,绝不浪费内部空间,但切剩下的缝隙形成外部碎片)

非连续分配方式 (离散分配)

  • 分页式存储:有内部碎片,无外部碎片。(物理内存被切成固定大小的页框。进程的最后一页通常装不满)
  • 分段式存储:无内部碎片,有外部碎片。(每个段的大小是动态按需分配的,本质和动态分区一样)
  • 段页式存储:有内部碎片,无外部碎片。(先分段,再把段打碎成页。底层分配单位依然是固定大小的“页”,所以每个段的最后一页都可能产生内部碎片)

重点易混淆:动态分区分配的 4 种算法

所有动态分区分配算法都只会产生外部碎片。但它们产生外部碎片的特征不同,是选择题高频考点:

  1. 首次适应算法 (First Fit)
  2. 特征:每次从低地址开始找。
  3. 碎片分布:低地址产生大量微小外部碎片。
  4. 考点:保留了高地址的大块连续空间,综合性能最好,最容易满足大进程需求。
  5. 循环首次适应算法 (Next Fit / 邻近适应)
  6. 特征:接着上次找的位置往下找(雨露均沾)。
  7. 碎片分布:外部碎片均匀分布在整个内存中。
  8. 考点:查找速度最快(不用每次跨越低地址碎片),但会破坏高地址的大块空间。
  9. 最佳适应算法 (Best Fit)
  10. 特征:每次挑“能装下且最小”的空闲块。
  11. 碎片分布:产生最多、最细碎的外部碎片。(因为每次剩下的边角料都极其微小)。
  12. 最坏适应算法 (Worst Fit / 最大适应)
  13. 特征:每次挑最大的空闲块切。
  14. 碎片分布:产生最少的外部碎片。(因为切完剩下的部分通常还很大,别人还能用)。
  15. 考点:最容易破坏大块连续空间,导致后续大进程无法运行。

影响请求分页系统平均访存时间的因素

缺页率、内存访问时间、执行缺页处理程序的CPU时间以及磁盘读写时间,因为请求分页系统由于修改过的页是需要写回外存的,因此磁盘读写时间也是会影响到请求分页系统的平均访存时间。

父进程与子进程

在操作系统中,当一个进程通过系统调用(如 UNIX/Linux 中的 fork())创建了另一个新进程时,创建者被称为父进程,被创建的新进程被称为子进程。它们之间形成了一种树型的层次结构,主要关系体现在以下四个核心维度:

资源分配与独立性

  • 资源继承与写时复制(Copy-on-Write, COW): 子进程在创建时,会获得父进程数据空间、堆、栈的副本。在现代操作系统中,为了提高效率,通常采用写时复制技术——父子进程最初共享同一块物理内存,只有当其中一个进程试图修改数据时,操作系统才会为子进程分配独立的物理内存页并复制数据。
  • 独立的地址空间: 尽管子进程是父进程的“克隆体”,但创建完成后,二者拥有各自独立的虚拟地址空间。一个进程对其变量的修改对另一个进程是不可见的。
  • 共享文件描述符: 子进程会继承父进程打开的所有文件描述符。这意味着父子进程共享相同的文件偏移量(文件指针),如果子进程读取了共享文件,父进程的文件指针也会随之前移。

进程控制块(PCB)的关联

  • 每个进程都有自己独一无二的进程标识符(PID)和独立的 PCB,用于被操作系统独立调度。
  • 子进程的 PCB 中会记录其父进程的 PID(即 PPID)。通过这种记录机制,操作系统能够维护进程家族树的拓扑结构。

并发执行与同步

  • 并发调度: 任意两个进程都是可以并发执行的。 父子进程是两个独立的调度实体,它们在系统中并发运行,共同竞争 CPU 时间片。至于 fork() 之后是父进程先执行还是子进程先执行,完全由操作系统的调度算法决定。
  • 同步等待: 父进程可以使用 wait() 或 waitpid() 系统调用主动阻塞自己,等待子进程执行完毕并收集其退出状态。

生命周期与异常状态

父子进程的生命周期并不是完全绑定的,结束顺序的不同会引发不同的特殊进程状态:

  • 僵尸进程(Zombie Process): 如果子进程先结束,但父进程没有调用 wait() 来读取子进程的退出状态,子进程的系统资源会被释放,但它的 PCB 依然会保留在内存中,占用一个 PID,此时子进程就变成了僵尸进程。
  • 孤儿进程(Orphan Process): 如果父进程先结束,而子进程还在运行,这个子进程就会成为孤儿进程。为了防止孤儿进程结束后变成无人回收的僵尸进程,操作系统通常会安排 init 进程(PID 为 1 的系统初始进程)收养它,负责在它退出时回收资源。

多级页表

各级页表(或页目录)项中存放的都是目标对象(次级页表或最终数据页)的物理页框号。MMU 提取该页框号后,若在末尾拼上全零,可获得该对象所在页的起始物理地址;若在最后一级拼上页内偏移量,即可获得最终数据的实际物理地址。

程序查询方式

程序查询方式适用于低速设备,因为低俗设备查询不用高速设备那样频繁,每次处理相同的时间高速设备耽误CPU的时间反而会更多。

中断响应和处理

对于要求画出CPU运行过程的图

image-20260926104038551

上图是一个很好的案例,并且不要忘了中断屏蔽字,注意哪个不会被响应。

系统调用

系统调用都需要切换到内核态。调用库函数不一定切换到内核态。比如 rand() 只是库函数,通常一直在用户态执行;而 fork()、read()、write() 这类系统调用会进入内核态。

关于磁盘和操作系统的顺序

  1. 系统启动过程中,操作系统是怎么被启动的
  2. 激活CPU(激活CPU读取ROM中的BIOS)
  3. 硬件自检(检测是否存在硬件问题)
  4. 加载系统盘(加载含有操作系统的硬盘)
  5. 记载主引导记录(通过主引导记录获取操作系统)
  6. 加载分区引导记录(通过分区引导记录获取操作系统所在分区)

  7. 把一块硬盘制作成启动盘,需要做什么

  8. 低级格式化或物理格式化(划分磁道、扇区等物理结构)
  9. 分区(把磁盘划分成若干分区,建立分区表)
  10. 高级格式化或逻辑格式化(在分区中建立文件系统)
  11. 安装操作系统

文件系统的管理依赖操作系统,但磁盘上存在文件系统不代表磁盘上已经安装操作系统。

关键是区分两个概念:

  1. 文件系统的磁盘结构:根目录、空闲块位示图、文件分配表等,存放在磁盘上。
  2. 文件系统的管理程序:属于操作系统的一部分,负责创建、读取、修改和管理这些结构。

举个例子:

假设电脑已经安装了 Windows,现在插入一块新硬盘。

  • Windows 中的格式化程序对新硬盘进行逻辑格式化。
  • 逻辑格式化建立根目录、空闲空间管理结构等。
  • 新硬盘因此具有了文件系统,但新硬盘上并没有安装 Windows。
  • 后续 Windows 可以通过文件系统驱动程序管理这块硬盘中的文件。

多道程序系统

并行才是真正的同时。

不必实现虚拟存储管理,可以单独划区域给不同的进程。

影响缺页率的因素

页置换算法、工作集大小、进程的数量。

页缓冲队列不会影响缺页率,只降低缺页中断的处理代价。因为他设计出来是为了减少IO次数。可以简单理解为页面被置换出去以后,物理页框先别急着彻底清空,而是放到一个“缓冲队列”里待命。被淘汰的页面内容暂时还留在页框里。通过延迟/成批写回修改页以及复用尚未被覆盖的页面,减少磁盘 I/O。但是,这个队列里的页如果被访问算缺页,因为页表项中它的有效位是被置为0的。

单重中断与多重中断

单重中断中开关中断都是硬件自动完成的;多重中断在刚开始硬件检测到中断后,也会关中断来保存断点和跳转到中断服务程序入口,然后中断服务程序内操作系统保存完现场后会开中断,当所有中断服务程序结束后返回后,还是由硬件开中断。

中断引发的进程状态切换

  1. 中断处理程序是操作系统内核代码,本身不是进程,因此执行它不代表着发生进程切换;

  2. 进程等待 I/O 时由运行态变为阻塞态,I/O 完成中断使其由阻塞态变为就绪态;若进程 Q 正在执行且时间片尚未用完,此时发生了与 Q 无关的中断,执行中断处理程序本身不会导致 Q 的进程状态改变;只有中断处理后触发了进程调度,Q 的状态才可能发生变化。

进程调度与进程切换

调度没有真正换进程,只是选出来,真正换是进程切换

虚拟空间大小

虚拟空间大小由系统地址结构决定,如:一个32位的系统可以支持最大4GB的虚拟地址空间。而非由内存、硬盘大小决定。

MMU

MMU(Memory Management Unit,内存管理单元)是CPU中的硬件部件,主要负责虚拟地址到物理地址的转换以及存储保护。

分页系统中:虚拟地址 = 虚页号 + 页内偏移

MMU利用虚页号查询TLB或页表,将虚页号转换成页框号:物理地址 = 页框号 + 页内偏移。其中页内偏移在地址转换过程中保持不变。

页表由操作系统建立和维护,地址转换由MMU硬件完成。

TLB可以看作MMU中的页表项高速缓存:

  • TLB命中 -> 直接获得页框号

  • TLB未命中 -> 查询页表

  • TLB未命中不等于缺页。

若页表项表明页面不在主存,则MMU产生缺页异常,由操作系统执行缺页异常处理程序,将页面调入主存并修改页表。

因此:OS负责内存管理,MMU负责地址转换和权限检查(当前页面是否可读、可写、可执行)。

同一进程下的线程

线程共享“进程拥有的资源”,但执行现场(PC、寄存器、栈)各自独立。若线程 T 调用 open() ,打开文件后形成的文件描述符会加入进程 P 的文件描述符表,不是放进“线程 T 专属的文件表”,因此为同一进程下的线程共享。如果是变量,则会放到对应线程的对应栈空间中,因此是独立的。

SCAN和C-SCAN

详见操作系统强化第六章

影响进程正常运行所需的最少页框数的因素

image-20261008143616666

这是因为进程运行所需的最少页框数,取决于执行一条指令时可能同时涉及的最大页面数。

假设某条指令执行时,需要访问:

  • 指令本身所在的页面:1 个页框
  • 第一个操作数所在的页面:1 个页框
  • 第二个操作数所在的页面:1 个页框

那么至少需要 3 个页框,才能保证这条指令正常执行。

但是,如果支持间接寻址,还可能需要额外访问存放有效地址的页面。

VFS

1. 概念及作用

VFS(Virtual File System,虚拟文件系统)是操作系统内核中的软件层,位于系统调用接口与具体文件系统之间。

主要作用:

  1. 提供统一接口:使用户程序能够通过统一的接口(如 open、read、write、close)访问不同类型的文件系统。
  2. 屏蔽底层差异:用户程序无需关心底层是 FAT32、NTFS 还是 ext4。
  3. 支持多种文件系统:可以同时管理不同类型的文件系统,包括本地文件系统和网络文件系统。
  4. 提供统一的文件对象抽象:例如文件对象、索引节点对象、目录项对象等。

image-20261008144806532

内存映射文件

  1. 将磁盘文件的全部或部分内容映射到进程的虚拟地址空间,使进程能够像访问普通内存一样访问文件。
  2. 建立映射时通常不需要将整个文件装入内存,而是通过请求调页机制按需调入。
  3. 映射建立后,可以不使用 read/write 系统调用,直接通过内存访问指令读写文件内容。
  4. 内存映射文件不能消除磁盘 I/O,但可以减少系统调用及内核与用户空间之间的数据复制。
  5. 多个进程可以映射同一个文件,实现数据共享;共享映射与私有映射的修改行为不同。
  6. 内存映射文件依赖虚拟内存管理,仍然需要页表、MMU 和缺页异常处理机制。
  7. 内存映射 I/O(MMIO)是将设备寄存器与主存统一编址,不同于内存映射文件。

最后记住两个区别:

  • 内存映射文件:文件 → 虚拟地址空间,目的是方便、高效地访问文件。
  • 内存映射 I/O:设备寄存器 → 统一地址空间,目的是通过访存指令访问设备。

文件大小和蔟

  1. 规定一个蔟进用于存放一个文件的内容,不能由多个文件共享。

  2. 文件所占用的空间必须是蔟大小的整数倍。

  3. 文件的实际大小不一定是簇大小的整数倍,最后一个簇未使用的部分会造成内部碎片。

文件访问控制

文件访问权限的位表示(408)

  1. 权限位图:每种权限用 1 位表示,1 表示允许,0 表示不允许,多种权限可以同时拥有。
  2. 计算公式:总位数 = 用户类别数 × 权限种类数。
  3. 注意区分:若多个状态互斥,只需用足够的二进制位进行编码;若多种权限可以同时拥有,按题目规定分别设置权限位。

因为一个用户对文件的访问权限可以同时拥有多种,并不是多选一。

SPOOLing 技术(假脱机技术)

核心思想

SPOOLing 利用磁盘空间和软件模拟脱机输入/输出,将独占设备改造成逻辑上的共享设备。

  • 本质:虚拟设备技术。
  • 典型应用:共享打印机。
  • 主要作用:提高设备利用率,减少进程等待慢速 I/O 设备的时间。
  • 注意:物理设备仍然是独占设备,只是在逻辑上实现共享。

SPOOLing 系统的组成

组成 所在位置 作用
输入井 磁盘 暂存输入设备送来的数据
输出井 磁盘 暂存用户进程提交的输出数据
输入缓冲区 内存 暂存数据传输过程中的输入数据
输出缓冲区 内存 暂存数据传输过程中的输出数据
输入进程 软件 控制数据从输入设备进入输入井
输出进程 软件 控制数据从输出井送往输出设备
井管理程序 软件 管理输入井、输出井及作业

井相当于仓库,缓冲区相当于临时周转台,输入/输出进程负责搬运数据。

数据传输过程

阶段 数据流向
外部输入 输入设备 → 输入缓冲区1 → 输入井
作业读取 输入井 → 输入缓冲区2 → 运行作业
作业输出 运行作业 → 输出缓冲区2 → 输出井
外部输出 输出井 → 输出缓冲区1 → 输出设备

其中缓冲区1、2对应教材图中的不同传输环节。

  • 井:位于磁盘,用于大量数据的暂存和排队。
  • 缓冲区:位于内存,用于数据传输时的临时周转。
  • 进程与井交互并不意味着绕过内存,实际数据传输仍然需要经过内存。

实现条件

  1. 大容量、高速度的外存:建立输入井和输出井。
  2. 内存缓冲区:缓冲数据传输,协调不同设备的速度差异。
  3. SPOOLing 软件:包括输入进程、输出进程和井管理程序。
  4. 多道程序设计技术的支持:使输入、输出操作与用户程序的执行能够并发进行。

外围机与外围设备

  • 外围设备:具体的 I/O 硬件,如打印机、读卡机。
  • 外围机:传统脱机 I/O 中,专门管理慢速设备和数据转储的辅助计算机。

传统脱机技术需要外围机,而 SPOOLing 利用软件模拟脱机 I/O,无需专门的外围机。

408 易错点

  • SPOOLing 实现的是逻辑共享,不是物理设备真正同时共享。
  • 输入井、输出井位于磁盘,输入/输出缓冲区位于内存。
  • SPOOLing 需要多道程序设计技术的支持。
  • 进程提交输出任务后,无需等待设备实际完成输出即可继续执行。
  • SPOOLing 不需要专门的外围机,但仍需要实际的 I/O 设备。
  • 由操作系统调用井管理程序来控制输入/输出井之间的数据传送。

FAT 文件分配表

FAT(文件分配表)采用链接分配,将各簇的链接信息集中存放在 FAT 中。

  • 目录项:保存文件的起始簇号。
  • FAT 表项编号:对应当前簇号。
  • FAT 表项内容:保存下一个簇号,最后一个簇对应文件结束标志(EOF)。
  • FAT 通常缓存在内存中:查询簇链不一定需要访问磁盘。

示例

文件簇链:100 → 106 → 108 → EOF

位置 存储内容
目录项 起始簇号 100
FAT[100] 106
FAT[106] 108
FAT[108] EOF

核心公式:FAT[当前簇号] = 下一个簇号。

注意:FAT 表项编号是当前簇号,表项内容是后继簇号,不要混淆。

死锁的必要条件与处理策略

死锁的四个必要条件

四个条件必须同时成立才可能发生死锁,破坏任意一个即可预防死锁。

必要条件 含义 破坏方法
互斥 资源只能被一个进程独占 允许资源共享(如 SPOOLing)
请求并保持 持有资源的同时申请新资源 一次性申请全部资源
不可剥夺 已获得的资源不能被强行收回 允许剥夺资源
循环等待 多个进程形成循环等待链 资源有序分配法

注意:

  • 互斥条件通常难以破坏,因为部分资源天然不可共享。
  • 资源有序分配法破坏的是循环等待条件。

死锁的四种处理方法

对比 死锁预防 死锁避免 死锁检测 死锁解除
核心思想 破坏必要条件 保持安全状态 检测是否发生死锁 解除已发生的死锁
典型方法 资源有序分配 银行家算法 资源分配图、检测算法 剥夺资源、撤销进程、进程回退
限制申请顺序 可以 不要求 不要求 不涉及
预知最大资源需求量 不要求 需要(银行家算法) 不需要 不需要
分配前安全性检查 不需要 需要 不需要 不涉及
是否允许死锁发生 不允许 不允许 允许 发生后解除

死锁解除的三种方法

  • 资源剥夺法:强制收回某些进程的资源,分配给其他进程。
  • 撤销进程法:终止部分或全部死锁进程,释放资源。
  • 进程回退法:让进程回退到之前保存的安全状态,释放部分资源。

易错点

  • 死锁预防 ≠ 死锁避免:前者破坏必要条件,后者动态检查安全性。
  • 银行家算法 = 试探性分配 + 安全性检查 + 决定是否正式分配。
  • 死锁检测需要当前资源分配和请求信息,但不需要预知最大资源需求量。
  • 不安全状态 ≠ 死锁状态,只表示存在发生死锁的可能。
  • 死锁检测与解除通常配套使用,部分教材将二者合并为一种处理策略。

记忆:预防破条件,避免查安全,检测找死锁,解除靠剥夺、撤销、回退。

页面分配策略与页面置换策略

基本概念

  • 固定分配:进程运行期间,分配的物理页框数量不变。
  • 可变分配:进程运行期间,分配的物理页框数量可以动态变化。
  • 局部置换:只能从当前进程占有的页框中选择被淘汰页面。
  • 全局置换:可从系统范围内选择页框,包括其他进程占有的页框。

核心区别:页面分配看数量,页面置换看范围。

四种组合

组合 是否可行 原因
固定分配 + 局部置换 可以 页框数固定,只替换自己的页面
固定分配 + 全局置换 不可以 全局置换可能改变各进程的页框数量
可变分配 + 局部置换 可以 页框数可调整,置换仍限于本进程
可变分配 + 全局置换 可以 页框数可调整,也能从其他进程获取页框

易错点

  • 固定分配只能搭配局部置换。
  • 可变分配既可以搭配局部置换,也可以搭配全局置换。
  • 局部置换不意味着页框数量固定。
  • 固定分配与全局置换不能组合,这是 408 的经典考点。

文件描述符 fd 与打开文件表

fd 的本质

文件描述符(fd)是进程用于标识已打开文件的非负整数,本质上是进程文件描述符表的索引。

  • open():打开文件,成功返回 fd,失败返回 -1。
  • read(fd, ...):通过 fd 读取文件数据。
  • write(fd, ...):通过 fd 写入文件数据。
  • close(fd):关闭文件描述符。

fd 不是文件地址,也不是数据缓冲区指针。

fd 与文件表的关系

fd → 进程文件描述符表 → 系统打开文件表 → FCB/inode → 文件数据

  • 进程文件描述符表:保存 fd 与系统打开文件表项的关联。
  • 系统打开文件表:记录文件偏移量、访问模式等。
  • FCB/inode:记录文件元数据,用于定位文件数据。

同一进程中不同线程的 fd 归属

同一进程内的线程共享文件描述符表,fd 属于进程,而不是某个线程独占。

情况 结果
线程 A 调用 open() 得到 fd = 3 线程 B 也可以使用 fd = 3
线程 A、B 分别调用 open() 打开同一文件 通常得到 fd = 3、4,分别对应不同的打开文件表项
  • 两次独立 open():建立不同的打开文件表项,文件偏移量独立。
  • 多线程使用同一个 fd:共享同一个打开文件表项及文件偏移量。
  • 一个线程执行 close(fd):其他线程也不能继续将该 fd 当作原来的有效描述符使用。

408 易错点

  • 不同进程:通常各有自己的文件描述符表。
  • 同一进程的不同线程:共享文件描述符表。
  • 多次独立 open() 同一文件:文件偏移量独立。
  • 多线程使用同一个 fd:文件偏移量共享。
  • open() 返回 fd,而不是数据缓冲区首地址。

记忆:fd 是进程的文件编号;线程共享 fd 表,但每次独立 open 通常创建新的打开文件表项。

管道通信(Pipe)

基本特点

  • 本质:操作系统内核维护的有限容量缓冲区,用于进程间通信(IPC)。
  • 先进先出(FIFO):先写入的数据先被读取。
  • 半双工通信:一个管道只能单向传输,双向通信通常需要两个管道。
  • 字节流通信:不保留消息边界。
  • 读出即消费:数据被读取后从管道中移除。

管道读写规则

默认阻塞模式下:

管道状态 读写行为
管道为空,仍有写端 读进程阻塞
管道已满,仍有读端 写进程阻塞
管道有数据 可以读取,读后数据被移除
管道有空闲空间 可以写入
所有写端关闭且数据读完 读操作返回 0(EOF)
所有读端关闭后继续写 触发 SIGPIPE 或返回 EPIPE 错误

同步与互斥

  • 同步:管道为空时等待写入,管道满时等待读取。
  • 互斥:操作系统协调多个进程对共享管道缓冲区的访问,防止并发操作破坏缓冲区状态。
  • 写入原子性:不超过 PIPE_BUF 的单次写入具有原子性保证;超过该大小可能与其他进程的写入交错。
  • 一个管道可以有多个读进程和写进程对其操作,但是为了避免数据混乱,可以利用互斥锁机制来保证每次只有一个进程对管道进行读/写操作。

记忆:管道是内核缓冲区,FIFO、半双工、读出即消费;空读阻塞、满写阻塞,同时需要同步与互斥。