什么是用户态和内核态?一次讲透两种运行模式的区别
这是“操作系统系列教程”的第四篇。上一篇我们讲了系统调用,知道了应用程序必须通过它才能请求内核服务。那么,系统调用发生时,CPU 到底发生了什么变化?为什么应用程序不能直接执行内核代码?这一切的答案,都藏在 CPU 的两种运行模式里——用户态和内核态。
一个“权限”问题
先来看一个实验。如果你在 Linux 终端里输入下面这行命令:
rm -rf /
系统会毫不犹豫地拒绝你:
rm: cannot remove '/': Operation not permitted
但如果你加上 sudo:
sudo rm -rf /
系统就会开始删除文件(当然,千万不要真的执行)。
同样是删除操作,为什么加了 sudo 就能执行?sudo 到底做了什么?
答案和 CPU 的特权级有关。普通用户运行的程序处于用户态,权限受限,不能执行某些敏感操作。而 sudo 会让程序以内核态的权限运行,从而可以执行特权指令。
用户态和内核态,是操作系统中最基础、也最重要的概念之一。不理解它们,就无法真正理解系统调用、中断、进程切换、内存保护等核心机制。
这篇文章,我们就来把这两种运行模式讲透。
说明:本文涉及 CPU 特权级、寄存器、内存保护等底层知识。初学者如果遇到暂时看不懂的术语,可以先跳过细节,重点理解“用户态权限低、内核态权限高,两者通过特定机制切换”这一核心思想。后续文章会逐步展开相关知识点。
一、为什么需要两种运行模式?
1.1 从一个假设开始
假设操作系统只有一种运行模式,所有程序都拥有最高权限。会发生什么?
- 你写的一个小 bug,可能直接覆盖内核数据,导致系统崩溃;
- 一个恶意程序可以随意读取另一个程序的内存,窃取密码;
- 一个程序可以关闭系统关键服务,让整个系统瘫痪;
- 一个程序可以格式化硬盘,删除所有数据。
这显然是不可接受的。现代计算机需要同时运行多个程序,还要保证它们互不干扰、系统稳定安全。于是,CPU 设计者引入了特权级的概念。
1.2 特权级:给代码分等级
CPU 的特权级(Privilege Level)把运行状态分为多个等级。x86 架构提供了 4 个特权级,称为 Ring 0 到 Ring 3:
| 特权级 | 名称 | 典型用途 |
|---|---|---|
| Ring 0 | 内核态 | 内核、设备驱动 |
| Ring 1 | 保留 | 早期用于设备驱动,现代系统很少使用 |
| Ring 2 | 保留 | 早期用于设备驱动,现代系统很少使用 |
| Ring 3 | 用户态 | 应用程序、Shell、GUI |
现代操作系统(Linux、Windows、macOS)通常只使用 Ring 0 和 Ring 3 两个级别。Ring 1 和 Ring 2 基本被弃用。

图 1:x86 特权级与操作系统。 内核和设备驱动运行在 Ring 0,应用程序运行在 Ring 3。
1.3 两种模式的核心区别
用户态和内核态的区别,可以用一张表来概括:
| 对比项 | 用户态(Ring 3) | 内核态(Ring 0) |
|---|---|---|
| 权限等级 | 最低 | 最高 |
| 可执行指令 | 普通指令 | 所有指令,包括特权指令 |
| 硬件访问 | 不能直接访问 | 可直接访问 |
| 内存访问 | 只能访问自己的地址空间 | 可访问全部物理内存 |
| 运行内容 | 应用程序、Shell、GUI | 内核、设备驱动 |
| 出错影响 | 通常只影响当前进程 | 可能导致整个系统崩溃 |
| 切换方式 | 系统调用、中断、异常 | 返回指令 |
特权指令是只有内核态才能执行的指令,例如:
- 修改页表基址寄存器(
CR3); - 关闭中断(
cli); - 加载中断描述符表(
lidt); - 停机指令(
hlt); - 读写 I/O 端口(
in、out)。
应用程序如果尝试执行这些指令,CPU 会触发异常,操作系统通常会终止该程序。
二、用户态和内核态的内存划分
除了指令权限,用户态和内核态在内存访问上也有严格区分。
2.1 虚拟地址空间划分
以 32 位 Linux 为例,虚拟地址空间被划分为两部分:
- 用户空间:低地址部分,通常为 0~3GB,每个进程独立拥有;
- 内核空间:高地址部分,通常为 3GB~4GB,所有进程共享。
图 2:32 位 Linux 的虚拟地址空间划分。 每个进程有独立的用户空间,但共享同一个内核空间。
2.2 为什么这样划分?
这种划分方式有几个好处:
- 隔离:进程 A 不能访问进程 B 的用户空间,防止互相干扰。
- 共享:所有进程共享内核空间,内核代码和数据只需一份。
- 高效:系统调用时,不需要切换页表,因为内核空间已经映射在同一个地址空间中。
- 保护:CPU 通过页表权限位,限制用户态程序访问内核空间。
当程序运行在用户态时,CPU 会检查每次内存访问。如果访问的是内核空间地址,会触发页错误(Page Fault),内核会终止该程序或将其杀死。
2.3 64 位系统的划分
在 64 位 Linux 中,虚拟地址空间远大于 4GB。典型的划分是:
- 用户空间:低地址部分,通常为 0~128TB;
- 内核空间:高地址部分,通常为 128TB~256TB。
虽然地址空间更大,但划分原则和 32 位系统一致。
三、用户态与内核态的切换
用户态和内核态不是固定不变的,它们之间会发生切换。切换的触发方式主要有三种:系统调用、中断、异常。

图 3:用户态与内核态切换的三种触发方式。
3.1 系统调用:主动请求
系统调用是应用程序主动请求内核服务的方式。上一篇我们详细讲过,应用程序通过执行 syscall 指令,主动从用户态切换到内核态。
流程回顾:
- 应用程序调用库函数(如
write); - 库函数把系统调用号放入寄存器,执行
syscall指令; - CPU 保存用户态上下文,切换到 Ring 0;
- 内核根据系统调用号执行对应处理函数;
- 执行完毕,CPU 恢复用户态上下文,切换回 Ring 3;
- 应用程序继续执行。
3.2 中断:被动响应
中断是外部设备通知 CPU 的方式。当硬件设备完成操作或发生事件时,会发出中断信号,CPU 收到后暂停当前程序,切换到内核态执行中断处理程序。
常见的中断来源:
- 键盘按下按键;
- 鼠标移动;
- 网卡收到数据包;
- 硬盘完成读写;
- 定时器到期。
图 4:中断处理流程。 中断是被动触发的,应用程序无法预知。
3.3 异常:错误或特殊事件
异常是 CPU 在执行指令时遇到的特殊情况,例如:
- 除零错误:执行
1 / 0; - 缺页异常:访问的页面不在内存中;
- 非法指令:执行了无效的指令;
- 权限错误:用户态程序试图执行特权指令。
异常也会导致从用户态切换到内核态,由内核处理。有些异常(如缺页异常)处理后可以恢复,程序继续执行;有些异常(如非法指令)则会导致程序终止。
3.4 三种触发方式的对比
| 触发方式 | 来源 | 是否主动 | 典型场景 |
|---|---|---|---|
| 系统调用 | 应用程序 | 主动 | 读写文件、发送网络数据 |
| 中断 | 外部设备 | 被动 | 键盘输入、网卡收包 |
| 异常 | CPU 执行指令 | 被动 | 除零、缺页、非法指令 |
四、特权级切换的底层机制
系统调用、中断、异常都会触发特权级切换。这个切换过程非常底层,涉及 CPU 硬件和内核的紧密配合。
4.1 切换时发生了什么?
从用户态切换到内核态,CPU 需要完成以下步骤:
- 保存用户态上下文:把当前程序的寄存器、程序计数器、栈指针等保存起来;
- 切换栈:从用户栈切换到内核栈;
- 提升特权级:从 Ring 3 切换到 Ring 0;
- 跳转到处理程序:根据中断向量表或系统调用表,跳转到对应的处理函数;
- 执行内核代码:在内核态下完成操作;
- 恢复用户态上下文:执行返回指令,恢复寄存器、程序计数器等;
- 降低特权级:从 Ring 0 切换回 Ring 3;
- 继续执行用户程序:从刚才中断的地方继续。
图 5:特权级切换的底层流程。 保存上下文、切换栈、提升特权级、执行处理程序、恢复上下文。
4.2 内核栈:为什么需要它?
每个进程有两套栈:
- 用户栈:位于用户空间,供应用程序使用;
- 内核栈:位于内核空间,供内核代码使用。
当从用户态切换到内核态时,CPU 会自动切换到内核栈。为什么需要两套栈?
- 安全:内核栈位于内核空间,用户程序无法访问,防止被篡改;
- 隔离:内核代码使用独立的栈,避免与用户程序的数据混淆;
- 容量:内核栈通常较小(如 8KB 或 16KB),因为内核代码调用层次不深。
4.3 中断描述符表
CPU 如何知道中断或异常发生时该跳转到哪里?答案是中断描述符表(Interrupt Descriptor Table,IDT)。
IDT 是内核维护的一张表,每一项对应一个中断或异常,记录着处理函数的地址、特权级等信息。当中断或异常发生时,CPU 根据中断号在 IDT 中查找对应的处理函数。
图 6:中断描述符表(IDT)。 CPU 根据中断号在 IDT 中查找对应的处理函数。
五、特权级切换的开销
特权级切换虽然强大,但并不是免费的。每次切换都涉及:
- 保存和恢复上下文:寄存器、程序计数器、栈指针等;
- 切换栈:用户栈到内核栈;
- 刷新 TLB:部分切换可能导致 TLB 失效;
- 缓存影响:内核代码和数据可能不在 CPU 缓存中。
这些开销加起来,一次特权级切换大约需要几百纳秒到几微秒。相比之下,一次普通函数调用只需要几纳秒。
5.1 为什么开销这么大?
图 7:特权级切换的主要开销。 保存和恢复上下文、切换栈、缓存失效是主要开销来源。
5.2 如何减少切换开销?
操作系统和硬件设计者采取了多种优化手段:
- vDSO:把部分系统调用(如
gettimeofday)映射到用户空间,避免特权级切换; - 快速系统调用指令:
syscall/sysret比传统的int 0x80更快; - 减少系统调用次数:使用缓冲、批量操作;
- CPU 缓存优化:现代 CPU 对内核代码和数据有专门的缓存策略。
六、用户态和内核态的实际应用
6.1 sudo 命令
回到开头的例子。sudo 为什么能让普通用户执行特权操作?
sudo 本身是一个用户态程序,但它有一个特殊权限位:setuid。当普通用户执行 sudo 时,程序会临时以 root 用户的身份运行。sudo 内部会调用系统调用(如 setuid、execve)来提升权限,然后执行目标命令。
6.2 驱动程序
设备驱动运行在内核态,因为它们需要直接操作硬件。驱动程序中的错误可能导致整个系统崩溃,这也是为什么驱动开发要求特别高。
6.3 容器与虚拟机
- 容器:共享宿主机内核,容器内的进程仍然运行在用户态,但通过命名空间和 cgroups 实现隔离;
- 虚拟机:每个虚拟机有独立的内核,虚拟机中的用户态和内核态切换在虚拟机内部完成。
6.4 用户态驱动
为了减少内核态代码的规模,现代系统引入了用户态驱动(User-space Driver)。例如:
- FUSE:用户态文件系统,允许在用户态实现文件系统;
- DPDK:用户态网络驱动,绕过内核直接处理网络数据包;
- UIO:用户态 I/O 框架,允许用户态程序访问设备。
这些技术把部分驱动功能移到用户态,降低了内核崩溃的风险,但也带来了一定的性能开销。
七、不同操作系统的实现
7.1 Linux
Linux 使用 Ring 0 和 Ring 3 两个特权级。内核运行在 Ring 0,应用程序运行在 Ring 3。系统调用通过 syscall 指令触发,中断和异常通过 IDT 处理。
7.2 Windows
Windows 同样使用 Ring 0 和 Ring 3。内核和驱动运行在 Ring 0,应用程序运行在 Ring 3。Windows 的系统调用通过 syscall 指令触发,但应用程序通常不直接调用,而是通过 ntdll.dll 间接调用。
7.3 国产操作系统
国产操作系统大多基于 Linux 内核,特权级模型与 Linux 一致。鸿蒙 HarmonyOS 采用微内核设计,内核态代码更少,大部分服务运行在用户态,提高了系统的可靠性和安全性。

图 8:不同操作系统的特权级模型。 宏内核把大部分功能放在内核态,微内核只保留最核心的功能。
八、常见误区澄清
| 误区 | 真相 |
|---|---|
| “用户态和内核态是软件概念” | 特权级是 CPU 硬件提供的机制,软件只是使用它 |
| “所有程序都运行在内核态” | 只有内核和设备驱动运行在内核态,应用程序运行在用户态 |
| “系统调用是唯一的切换方式” | 中断和异常也会触发切换 |
| “用户态程序不能访问内核空间” | 不能直接访问,但可以通过系统调用请求内核代为访问 |
| “特权级切换没有开销” | 切换涉及保存上下文、切换栈、刷新 TLB,开销较大 |
| “所有操作系统都用 Ring 0 和 Ring 3” | x86 提供 4 个 Ring,但现代系统通常只用 2 个 |
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)