CPU 运行模式与特权指令
一个程序崩溃不该拖垮整个系统——CPU 靠”双模式”把普通程序和操作系统隔离开。
CPU 上同时跑着你的浏览器、音乐播放器、操作系统内核。如果浏览器里一行 STOP_CPU 就能把整台机器停掉,那谁还敢多开程序?
所以这一章本质上在解决一个问题:怎么防止一个程序搞坏整个系统?
要解决什么问题
早期的单任务系统没有保护——程序直接操作硬件,写飞一个指针就能把内存里别人的数据踩烂。多道程序并发之后,这个问题更致命:
- 一个程序把时钟改了,所有依赖时钟的程序全部乱套
- 一个程序直接写磁盘,绕过文件系统,数据安全形同虚设
- 一个程序执行停机指令,整个系统挂了
解决思路很朴素:给指令分等级。有些指令太危险,只有操作系统才能动。
怎么解决的:用户态与内核态
CPU 在硬件里加了一个标志位——程序状态字(PSW)里的模式位。这个位决定了 CPU 当前处于什么”身份”:
PSW 本身是 CPU 内部的一个特殊寄存器,属于计算机组成原理的范畴,详见 寄存器 — PSW 章节。
- 用户态(模式位 = 1):普通程序跑在这里,只能执行安全指令
- 内核态(模式位 = 0):操作系统内核跑在这里,什么指令都能执行
指令分三档
| 分类 | 指令 | 谁可以用 |
|---|---|---|
| 🟢 普通指令 | 存数、寄存器清零、跳转(JMP / JZ / JNZ) | 用户态 |
| 🟡 陷入指令 | syscall / int 0x80 | 用户态 → 内核态 |
| 🔴 特权指令 | 置时钟、停机(HLT)、屏蔽中断(CLI / STI) | 只有内核态 |
判断标准很简单:能影响全局状态、能绕过 OS 管控的,就是特权指令。让用户程序随便停机、关中断,系统就没法管了。
用户程序怎么请求特权服务
用户程序不能直接执行特权指令,但它可以”敲门”——执行陷入指令(syscall)(访管制令)。CPU 硬件做三件事:
- 保存现场(PC、PSW、通用寄存器压栈)
- 模式位切到内核态
- 跳到 OS 预设的中断处理程序入口
处理完,OS 执行”返回用户态”指令,CPU 恢复现场,程序继续跑,全程无感。
这就像一个办事窗口:你不能进去翻档案(不能直接执行特权指令),但你可以在窗口提交申请(系统调用),工作人员进库房帮你拿(内核代劳)。
态切换的具体机制和三种中断类型(访管中断、时钟中断、缺页中断)详见 中断与任务协作。
再往前一步:从双模式到多级特权
用户态/内核态两级够用吗?对于简单的嵌入式系统够了。但复杂场景下,两层不够细腻。
- Intel x86:Ring 0 ~ Ring 3 共四级。Ring 0 是内核,Ring 1/2 留给设备驱动(实际很少用),Ring 3 是用户程序。虚拟机监控器(Hypervisor)跑在 Ring -1,比内核权限还高。
- ARM:EL0(用户程序)→ EL1(内核)→ EL2(Hypervisor)→ EL3(安全监控,TrustZone)。Cortex-M 因为面向嵌入式,只保留了 Handler 模式(类似内核态)和 Thread 模式(类似用户态),简化了特权级设计。
对照 FreeRTOS 看:Cortex-M 上 FreeRTOS 的任务跑在 Thread 模式(非特权),中断和 SVC 调用跑在 Handler 模式(特权)。中断与任务协作 里展开了中断和任务的边界设计。
多级特权的本质没变:每一层都只能管自己该管的事,越权就要往上交。
大内核 vs 微内核
特权级决定了怎么隔离,但还没回答另一个问题:内核里到底放什么? 这是操作系统结构设计的核心分歧。
要解决什么
内核态有最高权限,但代价是出了 bug 整个系统挂掉。所以内核里放多少东西,本质上是在性能和可靠性之间做取舍。
三种设计
| 大内核(Monolithic) | 微内核(Microkernel) | 外核(Exokernel) | |
|---|---|---|---|
| 内核里放什么 | 全部——文件系统、网络栈、驱动、调度、内存管理 | 只放必需的——调度、IPC、地址空间管理 | 几乎不放——只做硬件资源的安全复用 |
| 其他服务在哪 | 内核态 | 用户态,独立进程 | 用户态,应用直接操作硬件 |
| 服务间通信 | 函数调用,快 | IPC,有开销 | 不需要——内核不提供服务 |
| 一个服务崩了 | 整个内核可能挂 | 只挂那个进程 | 只挂那个应用 |
| 典型系统 | Linux、Unix、Windows | QNX、MINIX、L4 | MIT Exokernel、Nemesis |
可以这样理解:大内核是”什么活都自己干”的老板,微内核是”只抓核心、其余外包”的指挥中心,外核是”只当保安、不管业务”——它只负责拦住不该进的人,别的什么都不管。
外核的思路最极端:内核不提供任何抽象。传统 OS 把磁盘抽象成文件、把内存抽象成虚拟地址空间——外核觉得这些都是多余的,应用最清楚自己需要什么。外核只做一件事:安全地把硬件资源(磁盘块、物理页框、CPU 时间片)分配给应用,应用自己决定怎么用。好处是极致灵活和性能,代价是应用要自己实现文件系统、内存管理——开发极其复杂。至今没有商业 OS 采用纯外核。
Linux 为什么选大内核
Linux 内核里塞了文件系统、网络栈、成千上万的驱动——是标准的大内核。但它不是纯血大内核,通过内核模块(.ko 文件)把驱动动态加载到内核空间,不用重新编译内核就能扩展功能。严格说 Linux 是大内核 + 模块化,取了微内核”可扩展”的好处,但没有微内核的 IPC 开销。
嵌入式里的实际情况
微内核理论更优雅(隔离好、更安全),但嵌入式里大内核和 RTOS 占绝对主流:
- Linux(大内核):跑在 Cortex-A 上,要的是生态和驱动
- FreeRTOS:不分用户态/内核态,所有任务和内核跑在同一特权级,连微内核都不是——它是单体实时内核,一切以”快”和”确定”为优先
- QNX(微内核):少数嵌入式里跑微内核的例子,用在汽车、医疗等对安全要求极高的场景
所以很多时候不是”大内核 vs 微内核谁更好”的问题,而是你的硬件有没有 MMU、你的场景能不能接受 IPC 延迟。
如果你正在跟随梳理, 返回 MOC←