什么是用户态、内核态?如何切换的?

#计算机网络#操作系统#面试八股

什么是用户态、内核态?如何切换的?

CPU 有不同特权级,操作系统把"能执行特权指令、能访问全部内存"的那部分代码放在内核态(kernel mode), 应用代码跑在用户态(user mode)。两者之间不是进程切换,而是同一条线程在两种执行环境之间穿越, 入口只有三类:系统调用(system call)、中断(interrupt)、异常(exception)。

1. 为什么要分级

如果应用能直接改页表、关中断、写磁盘控制器寄存器,一个野指针就能搞崩整台机器,一个恶意程序就能读别人密码。 分级的本质是把危险操作收归内核,由内核做检查后再代劳。

层面用户态内核态
x86 特权级Ring 3Ring 0
指令普通算术、跳转cli/sti、hlt、改 CR3、in/out
内存访问只能访问页表标记为用户页的地址全部物理内存
栈用户栈内核栈(每线程一份,通常 8~16KB)
崩溃影响当前进程被杀内核 panic,整机挂

2. 切换的三条通道

用户态                                内核态
  │  syscall 指令(rax=号,rdi/rsi/rdx 传参)
  ├──────────────────────────────────►  entry_SYSCALL_64
  │                                      保存现场 → 切换内核栈
  │                                      按 sys_call_table 分发
  │  sysret(返回值放 rax)              执行 → 恢复现场
  ◄──────────────────────────────────┤
通道触发方式典型场景是否同步
系统调用主动执行 syscall / int 0x80read、write、futex同步,当前线程进入
中断外部设备或时钟网卡收包、定时器 tick异步,随时可能
异常指令执行失败缺页(page fault)、除零、断点同步

系统调用号与参数寄存器是 ABI 约定,x86-64 上前 6 个参数走 rdi, rsi, rdx, r10, r8, r9, 返回值放 rax,负数表示 -errno。

3. 切换的代价

一次系统调用并不免费:要保存通用寄存器、切换栈、做安全缓解(KPTI 下还要换页表基址), 返回前再做一次反向操作,一次大概几百纳秒。真正贵的是上下文切换(context switch): 换页表导致 TLB 失效、缓存变冷,动辄几微秒。

# 数一下程序到底进了多少次内核
strace -c -f ./server

# 看上下文切换与系统调用次数
vmstat 1                 # cs 列:每秒上下文切换
pidstat -w -p 1234 1     # cswch/s 自愿切换、nvcswch/s 非自愿

# 单次系统调用的耗时(约 100~300ns,受 mitigations 影响)
perf stat -e cycles,instructions -r 10 ./bench_getpid
// 减少穿越的常见手段:批量、合并、共享内存
struct iovec iov[3];
writev(fd, iov, 3);                 // 3 次写合并成 1 次系统调用

// 更彻底的方案:vDSO 把只读数据映射进用户态,读时间完全不进内核
clock_gettime(CLOCK_MONOTONIC, &ts);   // 通常由 vDSO 直接返回,无 syscall

高并发场景下还有两条路:io_uring 用共享内存环提交/收割请求,一次 io_uring_enter 可提交成百上千个 IO; DPDK、用户态协议栈则把设备直接交给用户态驱动,绕过内核网络栈。

4. 常见追问

Q:用户态线程能直接读内核地址吗? 不能。页表项有 U/S 位,内核页对用户态不可见;强行访问触发缺页异常,内核判定非法后给进程发 SIGSEGV。 KPTI 之后,进入内核还会切换一套只含内核映射的页表,进一步限制旁路攻击面。

Q:系统调用和函数调用差在哪? 函数调用只是 call 指令改 RIP,仍在同一特权级、同一地址空间;系统调用要跨特权级,切换栈、校验参数指针、 检查权限,返回时还要处理信号。量级上一个几十纳秒,一个几百纳秒。

Q:切换时内核怎么知道参数在哪? 约定寄存器里放着系统调用号和参数,入口处把用户寄存器压到内核栈,按 sys_call_table[号] 调函数。 指针参数必须先 copy_from_user 拷进内核并用 access_ok 校验,防止用户传内核地址骗内核读写。

小结

用户态与内核态是特权级隔离,切换由系统调用、中断、异常三类事件触发,代价是保存现场与缓存/TLB 变冷。 性能优化的一条主线就是"少穿越":批量(writev、io_uring)、免穿越(vDSO、用户态协议栈)、 把多次小操作合成一次大操作。

笔记加载中…