Skip to content

用户态和内核态 (User Mode & Kernel Mode)

一、核心概念

什么是用户态和内核态

内核态 (Kernel Mode / Supervisor Mode / System Mode / Ring 0):

  • CPU 运行的最高特权级,能访问所有硬件资源和全部内存
  • 操作系统的内核代码运行在此模式
  • 可以执行特权指令 (privileged instructions),如 I/O、修改寄存器、配置 MMU 等

用户态 (User Mode / Ring 3):

  • CPU 运行的低特权级,只能访问受限资源
  • 应用程序(浏览器、游戏、Office 等)运行在此模式
  • 不能直接访问硬件,不能访问内核内存,不能执行特权指令
  • 需要通过系统调用 (System Call) 才能进入内核态

一句话总结:内核态是"上帝模式",可以做任何事;用户态是"平民模式",只能按规矩办事。


二、为什么要区分用户态和内核态

1. 保护操作系统内核

  • 用户程序千千万万,质量参差不齐
  • 如果允许用户程序直接操作硬件,一个 bug 就会让整个系统崩溃
  • 例如:用户程序直接写硬盘控制器,可能把磁盘格式化

2. 保护进程之间的隔离

  • 每个进程拥有独立的虚拟地址空间
  • 进程 A 看不到进程 B 的内存,更无法修改
  • 防止恶意程序窃取其他程序的数据

3. 稳定性

  • 用户程序崩溃,内核可以清理资源,系统不挂
  • 内核崩溃,整个系统挂(著名的 Linux Kernel Panic、Windows 蓝屏)

4. 安全性

  • 防止恶意软件直接控制硬件
  • 所有特权操作都必须经过 OS 审核

5. 资源可控

  • 内核统一管理 CPU、内存、I/O 设备
  • 公平调度,避免某个程序独占资源

三、CPU 特权级详解

各架构的特权级

架构 特权级 模式名
x86 4 级: Ring 0~3 Ring 0 = 内核,Ring 3 = 用户,Ring 1/2 几乎不用
ARM 4 级: EL0~EL3 EL0 = 用户,EL1 = 内核,EL2 = 虚拟化,EL3 = 安全(TrustZone)
RISC-V 3 级: M/S/U M = 机器,S = 监管者(内核),U = 用户

x86 Ring 详解 (从高到低):

  • Ring 0:操作系统内核,可执行所有指令
  • Ring 1:基本不用,早期为驱动设计
  • Ring 2:基本不用
  • Ring 3:应用程序,只能执行非特权指令

ARM Exception Level (EL):

  • EL0:用户应用程序
  • EL1:操作系统内核(非安全)
  • EL2:Hypervisor(虚拟化)
  • EL3:Secure Monitor(TrustZone 安全世界)

关键寄存器:CPL (Current Privilege Level)

  • CPL:当前特权级,记录在 CS 段寄存器的低 2 位
  • Ring 0 → CPL=0,Ring 3 → CPL=3
  • 某些指令(如 LGDT、MOV CR0)只能在 CPL=0 时执行

关键标志

x86 通过EFLAGS 寄存器中的标志位控制某些行为:

  • IOPL (I/O Privilege Level):决定能否直接执行 I/O 指令
  • IF (Interrupt Flag):是否响应可屏蔽中断
  • TF (Trap Flag):单步调试
  • AC (Alignment Check):对齐检查

四、用户态 ↔ 内核态 切换

切换的 3 种方式

              ┌─────────────────┐
              │   用户态 (Ring 3) │
              └─────────────────┘
                    ↕ 切换
        ┌──────────────────────────┐
        │      1. 系统调用           │
        │      2. 中断              │
        │      3. 异常              │
        └──────────────────────────┘
                    ↕
              ┌─────────────────┐
              │  内核态 (Ring 0)  │
              └─────────────────┘

1. 系统调用 (System Call) — 主动进入

场景:应用程序主动请求 OS 服务。

例子: - read() 读文件 - write() 写文件 - open() 打开文件 - malloc() 申请内存(底层走 brk/mmap) - fork() 创建进程 - socket() 创建网络连接

实现机制:

x86 上的两种系统调用方式:

方式 指令 速度 现状
旧方式 INT 0x80 慢 已废弃
快速方式 SYSENTER / SYSCALL 快 主流

Linux x86_64 系统调用流程:

1. 用户程序调用 glibc 包装函数,如 read()
2. glibc 把系统调用号(0 = read)放入 rax 寄存器
3. 执行 syscall 指令
4. CPU 切换到 Ring 0,跳到内核的 syscall_entry 入口
5. 内核根据 rax 中的调用号,跳到 sys_read() 实现
6. 内核执行真正的读操作
7. 返回值放回 rax
8. 执行 sysret 指令,CPU 切回 Ring 3
9. glibc 把 rax 返回给用户程序

系统调用开销:约 100-1000 个 CPU 周期,所以频繁系统调用是性能杀手。

2. 中断 (Interrupt) — 被动进入

场景:硬件通知 CPU 有事件发生。

分类:

  • 硬中断 (Hardware Interrupt):
  • 来自硬件设备:键盘按键、网卡收包、磁盘完成 I/O、定时器
  • 由中断控制器(8259A、APIC)传递给 CPU
  • 软中断 (Soft Interrupt):软件触发的中断(INT n)
  • 系统调用 INT 0x80 就是软中断
  • BIOS 调用 INT 10h/13h/16h
  • 可屏蔽中断 (Maskable Interrupt, IRQ):可以用 CLI 指令关闭
  • 不可屏蔽中断 (Non-Maskable Interrupt, NMI):无法关闭,系统最紧急事件(电源故障、内存校验错)

中断处理流程:

1. 硬件触发中断,中断控制器把中断号送到 CPU
2. CPU 立即停下当前指令(可能保存现场)
3. CPU 查中断向量表 (IDT),获得中断处理函数地址
4. CPU 切换到 Ring 0
5. 跳到中断处理函数执行
6. 中断处理完,执行 IRET 指令
7. CPU 恢复现场,切回 Ring 3

3. 异常 (Exception) — 被动进入

场景:CPU 执行指令时遇到错误。

分类:

  • Fault (故障):可恢复,回到原指令重新执行
  • 缺页异常 (Page Fault)
  • 除零错误
  • 段错误 (Segfault)
  • Trap (陷阱):报告后继续
  • 断点 (INT 3)
  • 单步 (Trap Flag)
  • Abort (终止):致命,无法恢复
  • 机器检查异常 (Machine Check)
  • 三重错误 (Triple Fault) → 通常重启

典型异常处理:

  • Page Fault (缺页):
  • 访问虚拟地址时,该页不在物理内存
  • 内核从磁盘换入页面,然后重新执行该指令
  • 用户态进程触发的页错误会进入内核,如果是合法访问,内核处理后返回用户态继续
  • 非法访问(如访问空指针)→ SIGSEGV,进程被杀掉
  • 除零错误:算术异常,SIGFPE,进程被杀掉
  • 断点 (INT 3):调试器用,触发后陷入内核,内核通知调试器

4. 切换过程详解

进入内核态 (以 syscall 为例):

1. CPU 处于 Ring 3,执行 syscall 指令
2. CPU 自动做以下事:
   a. 切换 CS/SS 段寄存器(指向内核代码/数据段)
   b. 保存用户态的 RIP(下一条指令地址)到 RCX
   c. 保存用户态的 RFLAGS 到 R11
   d. 加载新的 RIP(MSR 寄存器 IA32_LSTAR 指向的内核入口)
   e. 加载新的 RFLAGS(关闭中断 IF=0)
   f. 切换到 Ring 0
3. 跳到内核入口(syscall_entry)执行

回到用户态 (以 sysret 为例):

1. 内核执行 sysret 指令
2. CPU 自动:
   a. 从 RCX 恢复用户态 RIP
   b. 从 R11 恢复用户态 RFLAGS
   c. 切换 CS/SS 回用户态
   d. 切换到 Ring 3
3. 用户程序从 syscall 后的下一条指令继续

五、内核态的进一步细分

内核态中的"用户"与"内核"

  • Ring 0 = 内核态,但内核态内部也可以有"低特权"区
  • Linux:
  • 用户态:Ring 3
  • 内核态:Ring 0
  • 内核态内部没有更细的特权级(除了硬件本身 Ring 1/2)
  • Windows:
  • 用户态:Ring 3
  • 内核态:Ring 0
  • 内核有PatchGuard、Driver Signature Enforcement 等机制防止恶意内核代码

虚拟化后的更多层

┌──────────────────────┐
│  Guest OS 用户态      │ (VM Ring 3)
├──────────────────────┤
│  Guest OS 内核态      │ (VM Ring 0)
├──────────────────────┤
│  Hypervisor          │ (Host Ring 0 / -1)
├──────────────────────┤
│  Host 硬件            │
└──────────────────────┘
  • Guest OS 以为自己在 Ring 0
  • 实际运行在 VMX Non-Root Mode
  • 敏感指令触发 VM Exit,回到 Hypervisor
  • Hypervisor 处理后,执行 VM Entry 让 Guest 继续

ARM TrustZone

┌──────────────────────┐  ┌──────────────────────┐
│  Normal World 用户    │  │  Secure World 用户    │
├──────────────────────┤  ├──────────────────────┤
│  Normal World OS      │  │  Secure OS (TEE)     │
└──────────────────────┘  └──────────────────────┘
              ↕
       SMC 指令切换(EL3 监控)
  • TEE (Trusted Execution Environment):安全世界,跑 TrustZone OS,处理密码、生物特征等敏感数据
  • REE (Rich Execution Environment):普通世界,跑 Android / iOS / Linux

六、用户态与内核态的内存布局

典型 64 位 Linux 进程地址空间

  高地址 0xFFFFFFFFFFFFFFFF
  ┌─────────────────────┐
  │    内核空间           │ ← 4GB ~ 128TB,所有进程共享
  │  (Kernel Space)      │
  │  - 内核代码          │
  │  - 内核数据          │
  │  - 内核栈            │
  ├─────────────────────┤ ← 0x00007FFFFFFFFFFF (用户/内核边界)
  │   ... 栈 (Stack) ... │ ← 高地址,向下增长
  │   ... mmap 区 ...   │
  │   ... 堆 (Heap) ... │ ← 向上增长
  │   ... BSS ...       │
  │   ... 数据段 ...     │
  │   ... 代码段 ...     │
  └─────────────────────┘
  低地址 0x0
  • 用户空间:每个进程独立,大小 128TB(只用到一部分)
  • 内核空间:所有进程共享同一份映射(但通过页表保护)

进程切换时的细节

  • 进程 A 切换到进程 B:
  • 保存 A 的寄存器(包括 RIP、CR3 页表基址等)
  • 切换 CR3 → B 的页表(硬件自动完成,仅在内核态可配置 CR3)
  • 加载 B 的寄存器
  • 跳到 B 上次停止的地方
  • TIB / TLS:线程本地存储,每个线程独立

七、关键系统调用详解

Linux 常用系统调用

系统调用 编号 (x86_64) 用途 用户态函数
read 0 读文件 read()
write 1 写文件 write()
open 2 打开文件 open()
close 3 关闭文件 close()
stat 4 取文件状态 stat()
fstat 5 取文件状态 fstat()
mmap 9 内存映射 mmap()
brk 12 调整堆 brk()
fork 57 创建子进程 fork()
execve 59 加载程序 execve()
exit 60 进程退出 exit()
wait4 61 等待子进程 waitpid()
kill 62 发信号 kill()
getpid 39 取进程 ID getpid()
socket 41 创建 socket socket()
connect 42 连接 socket connect()
accept 43 接受连接 accept()
sendto 44 发数据 sendto()
recvfrom 45 收数据 recvfrom()
ioctl 16 设备控制 ioctl()

系统调用性能优化

为什么系统调用贵:

  1. 上下文切换(保存/恢复寄存器):~50 ns
  2. CPU 模式切换(Ring 3 → Ring 0):~50 ns
  3. 切换页表(可能):~50 ns
  4. 内核参数检查、安全检查:~几十 ns
  5. 真正的服务执行:几十 ns ~ μs

优化策略:

  • vDSO (virtual Dynamic Shared Object):Linux 把某些无需真正进入内核的调用(如 gettimeofday)映射到用户态,避免切换
  • 批处理 (Batching):用 readv/writev 一次系统调用做多次操作
  • io_uring (Linux 5.1+):异步 I/O,共享 ring buffer,极少进入内核
  • eBPF:在内核态运行用户定义的程序,避免切换

八、用户态/内核态交互方式

1. 系统调用 (System Call)

最常用,前面已详述。

2. 中断 (Interrupt)

硬件设备用中断通知 CPU,内核响应中断,处理完回到原任务。

3. 信号 (Signal)

Linux 特有的用户态 ↔ 内核态交互:

  • 内核可以给进程发信号 (kill)
  • 进程间可以发信号 (kill)
  • 进程自己也可以给自己发信号 (raise)

常见信号:

信号 编号 含义 默认动作
SIGHUP 1 终端挂起 终止
SIGINT 2 Ctrl+C 终止
SIGQUIT 3 Ctrl+\ 终止 + core
SIGKILL 9 强制终止,不可捕获 终止
SIGSEGV 11 段错误 终止 + core
SIGTERM 15 优雅终止 终止
SIGCHLD 17 子进程结束 忽略
SIGSTOP 19 暂停 暂停
SIGCONT 18 继续 继续
SIGALRM 14 定时器到时 终止

信号处理流程:

1. 内核决定给某进程发信号
2. 设置进程的信号位图
3. 当进程从内核态返回用户态时,内核检查信号位图
4. 如果有未处理信号:
   - 默认动作:终止/暂停/忽略/core
   - 注册了 handler:跳到用户注册的信号处理函数
   - 用户处理完,通过 sigreturn 系统调用返回内核
   - 内核恢复现场,继续原任务

4. 共享内存 (Shared Memory)

  • 多个进程把同一段物理内存映射到各自的虚拟地址空间
  • 是最快的 IPC 方式,无需进入内核
  • 典型:mmap、shmget/shmat

5. 内存映射文件 (mmap)

  • 把文件映射到进程地址空间
  • 读写内存就是读写文件
  • 适合大文件处理

6. procfs / sysfs (Linux)

  • /proc 和 /sys 是内核暴露给用户态的窗口
  • 通过读写文件就能查询/配置内核参数
  • 例如:cat /proc/cpuinfo、echo 1 > /proc/sys/net/ipv4/ip_forward
  • 用户态 ↔ 内核态的双向通信机制
  • 网络配置、路由、防火墙都靠它

九、内核态的关键任务

内核态处理的主要工作

功能 核心任务
进程管理 创建/销毁进程、调度、上下文切换、信号处理
内存管理 物理内存分配、虚拟内存映射、缺页处理、回收
文件系统 VFS 分发、文件系统挂载、inode/目录管理、读写缓冲
设备管理 设备驱动、I/O 调度、中断处理、DMA
网络管理 TCP/IP 协议栈、Socket、Netfilter、网卡驱动
安全管理 权限检查、ACL、SELinux/AppArmor、加密
时钟/定时器 定时器中断、jiffies 更新、定时任务
电源管理 待机、休眠、CPU 调频
同步原语 自旋锁、信号量、RCU、内存屏障

内核态 vs 用户态的对比

维度 用户态 内核态
特权级 Ring 3 (CPL=3) Ring 0 (CPL=0)
可访问内存 受限(用户空间) 全部(用户+内核空间)
可执行指令 非特权指令 所有指令
硬件访问 必须通过系统调用 直接访问
出错后果 进程被 kill (SIGSEGV 等) 系统崩溃 (Kernel Panic / 蓝屏)
栈空间 MB 级 几 KB(必须小)
代码来源 用户编译 内核编译
调试 gdb 简单 kgdb、printk 困难
性能 受保护,不能滥用硬件 直接操作,快但危险

十、内核态的安全与攻击

内核态是攻击的高价值目标

  • Rootkit:恶意程序植入内核,完全控制系统
  • 隐藏进程、文件、连接
  • 截获系统调用,篡改返回结果
  • 直接读写磁盘、网络包
  • 内核漏洞利用:用户态通过漏洞进入内核态
  • Dirty COW (CVE-2016-5195):mmap 竞态条件
  • Dirty Pipe (CVE-2022-0847):管道缓冲区漏洞
  • 各类驱动漏洞
  • 侧信道攻击:Meltdown、Spectre 利用 CPU 推测执行漏洞越权读内存

内核态保护机制

  • KASLR (Kernel Address Space Layout Randomization):内核代码地址随机化
  • SMEP/SMAP (x86):禁止内核访问用户空间内存
  • PXN/PAN (ARM):等同 SMEP/SMAP
  • Driver Signature Enforcement (Windows):只允许加载签名的驱动
  • Secure Boot:启动链验证
  • eBPF Verifier (Linux):eBPF 程序在加载前进行形式化验证
  • Hypervisor:Windows VBS、Linux KVM 提供内核态隔离

十一、调试用户态和内核态

用户态调试

  • gdb:GNU 调试器,设置断点、看变量、看栈
  • strace:跟踪系统调用
  • ltrace:跟踪库函数调用
  • valgrind:内存错误检测
strace -p <pid>       # 跟踪指定进程的系统调用
strace ls -la         # 跟踪 ls 的系统调用

内核态调试

  • printk:内核打印,信息在 dmesg 中
  • kgdb:远程 gdb,需要双机
  • /proc/kmsg:内核消息缓冲
  • perf / ftrace / bpftrace:Linux 内核追踪工具
  • kdump:内核崩溃转储
// 在内核中
printk(KERN_INFO "my module loaded\n");

十二、操作系统实例对比

Linux

  • 用户态 ↔ 内核态:syscall / int 0x80
  • 系统调用约 300+ 个,glibc 包装成 POSIX API
  • 信号处理完整实现
  • /proc / /sys 暴露大量内核信息
  • io_uring:最新高性能异步 I/O

Windows

  • 用户态 ↔ 内核态:syscall (Windows 10+),int 0x2e (老)
  • Win32 API 包装成 Nt* 系统调用
  • APC (Asynchronous Procedure Call):类似但更复杂的机制
  • ETW (Event Tracing for Windows):内核追踪
  • WDF (Windows Driver Framework):驱动开发框架

macOS (XNU)

  • Mach 微内核 + BSD 层
  • BSD 系统调用 + Mach 陷阱 (mach_trap)
  • POSIX 兼容,信号、procfs、sysctl
  • kqueue:事件通知机制

十三、CPU 模式相关概念

32 位 vs 64 位

  • x86 (32 位):Ring 0~3 完整支持
  • x86_64 (64 位):只实现了 Ring 0 和 Ring 3,Ring 1/2 保留
  • 长模式 (Long Mode):x86_64 的 64 位运行模式
  • 兼容模式 (Compatibility Mode):x86_64 运行 32 位代码的方式

实模式 vs 保护模式 vs 长模式

模式 时代 特点
实模式 8086 时代 16 位,1MB 地址空间,无保护,无分页
保护模式 80286 起 32 位,4GB 地址空间,有保护、有分页
长模式 x86_64 起 64 位,128TB 地址空间,兼容保护模式
虚拟 8086 模式 保护模式内 模拟实模式,跑老 DOS 程序

OS 启动中的模式切换:

开机 → 实模式 (BIOS)
   ↓ 进入保护模式
   ↓ 启动内核
   ↓ 切换到长模式 (x86_64)
   ↓ 启动用户程序

十四、关键概念辨析

概念 含义
内核态 CPU 特权级 Ring 0,可执行所有指令
用户态 CPU 特权级 Ring 3,只能执行非特权指令
特权指令 只能在 Ring 0 执行的指令(如 CLI、HALT)
系统调用 用户态进入内核态的合法入口
中断 硬件或软件触发的事件
异常 CPU 执行指令时的错误
上下文切换 进程/线程间切换
模式切换 Ring 3 ↔ Ring 0 切换
进程切换 A 进程 → B 进程(均在内核态)
线程切换 A 线程 → B 线程
协程切换 用户态内的"伪线程"切换
POSIX Unix 系统调用标准
CPL Current Privilege Level,当前特权级
DPL Descriptor Privilege Level,描述符特权级
RPL Requestor's Privilege Level,请求者特权级

十五、核心要点速记

  • 内核态 = Ring 0 = 上帝模式 = 可做一切
  • 用户态 = Ring 3 = 平民模式 = 受限
  • 3 种进入内核方式:系统调用(主动)、中断(被动硬件)、异常(被动错误)
  • 3 种返回用户方式:sysret / iret / signal return
  • 每种架构有不同特权级:x86 Ring 0~3,ARM EL0~EL3,RISC-V M/S/U
  • 系统调用开销:约 100-1000 CPU 周期,频繁调用是性能瓶颈
  • 优化方式:vDSO、io_uring、批处理、eBPF
  • 进程崩溃只影响该进程;内核崩溃 = 系统挂
  • Linux 进程地址空间:用户空间(独立)+ 内核空间(共享)
  • 信号是用户态 ↔ 内核态的异步通知机制
  • 共享内存是最快的 IPC(无需进入内核)
  • Rootkit 攻击内核态 = 完全控制系统
  • CPU 模式:实模式 → 保护模式 → 长模式
  • 虚拟化又增加一层 Ring -1 (Hypervisor)
  • TrustZone 增加 EL3 安全世界
  • 关键寄存器和标志:CPL、IOPL、IF、AC