系统调用
1.3.1 的三种受控入口中,中断和异常都是”被动进入”——程序自己没打算进内核,是被打断或出错了。
系统调用是唯一一种”主动进入”的方式,也是 1.1.2 说的用户程序请求操作系统服务的唯一途径。
机制
什么样的操作必须走系统调用
判据只有一句:凡是与资源有关的操作、以及会直接影响其他进程的操作,都必须由操作系统内核来完成。
理由是共享——资源是大家共用的,若允许应用程序自己去分配内存、自己去驱动打印机,多个程序之间必然打架。 必须有一个统一的裁判,而这个裁判就是内核。
因此这类请求只能通过系统调用提出,由内核检查合法性后代为执行。
系统调用按功能分为五类:设备管理(申请、释放、启动设备)、文件管理(读、写、创建、删除文件)、进程控制(创建、撤销、阻塞、唤醒进程)、进程通信(消息传递、共享存储)、内存管理(内存的分配与回收)。
这五类恰好覆盖了 1.1.2 的四项功能(进程控制与进程通信同属处理机管理),这不是巧合:系统调用就是四项管理功能对外暴露的接口。
系统调用的执行过程
四步:
① 传递系统调用参数 —— 告诉内核要调哪个服务、参数是什么。
② 执行陷入指令(在用户态执行)—— 陷入指令又称访管指令、trap 指令。执行它会立即引发一个内中断(陷入 trap),CPU 随即进入内核态。
③ 执行相应的内核程序处理系统调用(在内核态执行)—— 内核根据参数找到对应的处理例程,检查合法性并执行。
④ 返回应用程序 —— 恢复用户态,从陷入指令的下一条指令继续执行。
注意第 ② 步和第 ③ 步的分界正好是模式的分界:陷入指令本身在用户态执行,但对陷入指令的处理在内核态进行。 这是选择题的常见落点。
陷入指令的特殊地位
陷入指令是唯一一个只能在用户态执行、而不可在内核态执行的指令。
理由在 1.3.1 已给出:它的作用就是”从用户态进入内核态”,已经在内核态了再执行它没有意义。
同时要注意它不是特权指令——恰恰相反,它必须是非特权指令,否则用户程序连它都执行不了,就永远进不了内核。
边界
系统调用不是普通函数调用
两者在源代码里看起来一样(都是 f(x) 的形式),但发生的事情完全不同:
普通函数调用在同一个模式内完成——用户态的程序调用用户态的函数,只是压栈跳转,不涉及任何模式切换。
系统调用要执行陷入指令、引发内中断、切换到内核态、由内核代为执行、再切回用户态。它的开销比普通函数调用高出一到两个数量级。
这解释了一条常见的性能建议:频繁的小量读写(比如每次 read 一个字节)性能极差,不是因为磁盘慢,而是因为系统调用本身贵——这也正是 5.2.2 缓冲区存在的理由之一。
库函数不等于系统调用
这一对最容易混,判据是”它有没有进内核”。
有些库函数只是纯粹的用户态计算,例如 strlen()、sqrt(),它们从头到尾没有碰过内核。
有些库函数是对系统调用的封装,例如 C 语言的 fopen() / fread(),它们内部最终会执行系统调用,但在此之外还做了缓冲等额外工作。
因此准确的表述是:库函数是用户态的函数,系统调用是内核提供的服务;部分库函数封装了系统调用,但两者不是一回事。
一个实际后果:fread() 读一个字节不一定会触发一次系统调用——C 标准库自带缓冲,它可能一次性 read 了 4KB 存在用户态缓冲区里,后续 4095 次读取全都不进内核。
系统调用一定切换模式,但不一定切换进程
这一条在 1.3.1 和 2.2.4 都出现过,是全书最高频的结论之一:
系统调用必然发生”用户态 → 内核态”的转换(这是它的定义决定的)。
但它不一定引起进程切换——getpid() 这类调用进内核取个数就回来了,全程都是同一个进程。
只有当系统调用导致本进程阻塞(如读磁盘要等 I/O),或返回前发现有更该运行的进程时,才会发生进程切换。
陷入指令是”内中断”,不是”外中断”
这条容易记反。
陷入指令引发的是内中断(异常)中的”陷入”类——因为它由当前正在执行的这条指令直接引发,完全符合内中断的判据。
它不是外中断:外中断来自 CPU 外部、与当前指令无关,而陷入指令恰恰是当前指令本身。
由此还能推出返回位置:陷入属于三类内中断中的”陷入”,处理完毕后返回到下一条指令(不像缺页故障那样要重新执行原指令)。因为陷入指令是执行成功了的,它的使命就是把控制权交出去,而它做到了。
系统调用与”广义指令”是同一个东西
“广义指令”是系统调用的另一个名字,这个称呼强调的是:在应用程序员看来,系统调用就像是机器指令集的扩充——机器本来只有加减乘除跳转,加上系统调用之后,这台机器仿佛还有了”读文件""创建进程”这样的指令。
这正是 1.1.2 说的”扩充机器”的具体含义:扩充出来的那些”指令”,就是系统调用。
对照速查
| 系统调用四步 | 在哪个模式 |
|---|---|
| ① 传递系统调用参数 | 用户态 |
| ② 执行陷入指令 | 用户态 |
| ③ 执行内核程序处理系统调用 | 内核态 |
| ④ 返回应用程序 | 回到用户态,从下一条指令继续 |
| 五类系统调用 | 对应的管理功能 |
|---|---|
| 设备管理 | 设备管理 |
| 文件管理 | 文件管理 |
| 进程控制 | 处理机管理 |
| 进程通信 | 处理机管理 |
| 内存管理 | 存储器管理 |
| 陷入指令的三条特殊性 | |
|---|---|
| ① | 唯一只能在用户态执行、不可在内核态执行的指令 |
| ② | 不是特权指令(否则用户程序就永远进不了内核) |
| ③ | 引发的是内中断中的”陷入”类,返回到下一条指令 |
| 易混三对 | 判据 |
|---|---|
| 系统调用 vs 普通函数调用 | 有没有模式切换 |
| 库函数 vs 系统调用 | 有没有进内核;部分库函数封装了系统调用 |
| 切换模式 vs 切换进程 | 系统调用一定切模式,不一定切进程 |
考点
- 凡是与资源有关、会影响其他进程的操作,都必须由内核完成
- 五类系统调用;系统调用是四项管理功能对外暴露的接口
- 陷入指令在用户态执行,对它的处理在内核态(高频落点)
- 陷入指令是唯一只能在用户态执行的指令,且它不是特权指令
- 陷入属于内中断,返回到下一条指令
- 库函数 ≠ 系统调用;部分库函数封装了系统调用且自带缓冲
- 系统调用一定发生模式切换,不一定发生进程切换
- 系统调用又称广义指令,它就是”扩充机器”扩充出来的那部分
链接
- 🏠 返回总览:操作系统第 1 章:计算机系统概述总览
- ⬅️ 上一节:1.3.2 中断和异常的概念
- ➡️ 下一节:1.4 操作系统结构
- 🔗 三种受控入口的对照,见 1.3.1 处理器运行模式
- 🔗 进程控制类系统调用的实现,见 2.1.4 进程控制
- 📖 名词库:第 1 章名词库
考纲与教材编排的一处差异:408 考纲把”程序的链接与装入”和”程序的内存映像与地址空间” 也列在”程序运行环境”之下,但王道教材把它们放在了第 3 章。 本笔记跟随教材编排——链接与装入见 3.1.1, 进程的内存映像见 2.1.2。