用户态和内核态的区别

概念

内核态:内核态不是一个"地方",而是 CPU 的一种运行权限状态,内核态拥有最高权限,可执行任何执行,可访问任何内存,可操作所有硬件。

用户态:最低权限,只能执行普通指令,只能访问自己的内存,不能直接操作硬件。

用户态和内核态的根本区别是CPU的权限级别不同,其他所有的差异都是从这一点衍生出来的。

现代 CPU 设计了"保护环"(Protection Ring)机制。x86 架构实际有 Ring 0-3,但主流操作系统只用 0 和 3,其中Ring 0为内核态,Ring 1为用户态。这个权限级别存储在哪里?CPU 的 CS 寄存器(代码段寄存器)的低 2 位 称为 CPL(Current Privilege Level,当前特权级)。

CPL = 0 → 内核态

CPL = 3 → 用户态

区别

内存隔离

用户空间和内核空间在物理内存上是隔离的,内核空间在内存中有独立的内存,用户空间一般都是一个一个独立进程。在整个物理内存上分为了内核空间和用户空间。

系统调用的完整切换过程

以 Linux x86-64 的 read() 系统调用为例:

─────────────────────────────────────────────────────

阶段一:用户态准备(CPL = 3)

─────────────────────────────────────────────────────

用户程序调用 read(fd, buf, 1024)

C 库(glibc)的 read() 包装函数执行:

; 把系统调用号放入 rax 寄存器

; read 的系统调用号 = 0

mov rax, 0

; 参数按约定放入寄存器

mov rdi, fd ; 第1个参数:文件描述符

mov rsi, buf ; 第2个参数:缓冲区地址

mov rdx, 1024 ; 第3个参数:读取字节数

; 执行 syscall 指令

; ← 这一条指令触发态切换

syscall

─────────────────────────────────────────────────────

阶段二:syscall 指令的硬件动作(微秒级)

─────────────────────────────────────────────────────

CPU 硬件自动完成以下操作(不可中断):

① 保存返回地址

将 rip(下一条指令地址)保存到 rcx 寄存器

(记住用户态执行到哪里了)

② 保存用户态的 rflags 到 r11 寄存器

③ 切换 CPL

CS 寄存器的低2位从 11(Ring 3)改为 00(Ring 0)

这一步完成后,CPU 就处于内核态了

④ 跳转到内核入口点

从 MSR_LSTAR 寄存器读取内核系统调用处理函数地址

(这个地址在系统启动时由内核写入)

rip = MSR_LSTAR 的值

→ 开始执行内核代码

─────────────────────────────────────────────────────

阶段三:内核态处理(CPL = 0)

─────────────────────────────────────────────────────

内核入口函数(entry_SYSCALL_64)执行:

① 切换到内核栈

用户态的栈指针 rsp 保存起来

rsp 切换到当前进程的内核栈

(每个进程都有独立的内核栈,约 8-16 KB)

② 保存用户态寄存器

把所有通用寄存器压栈

形成一个 pt_regs 结构体

(这样内核执行完后能完整恢复用户态现场)

③ 根据系统调用号分发

以 rax=0 为索引查 sys_call_table 数组

找到对应的处理函数:sys_read()

④ 执行 sys_read()

检查 fd 是否有效

检查 buf 地址是否合法

操作文件系统,读取数据到内核缓冲区

把数据从内核缓冲区复制到用户的 buf

(这步涉及特权内存操作,必须在内核态完成)

⑤ 把返回值放入 rax

成功:rax = 实际读取的字节数

失败:rax = 负的错误码

─────────────────────────────────────────────────────

阶段四:返回用户态(CPL 从 0 → 3)

─────────────────────────────────────────────────────

内核执行 sysretq 指令:

① 从 r11 恢复 rflags

② 切换 CPL

CS 寄存器低2位从 00 改回 11

CPU 回到用户态

③ 从 rcx 恢复 rip

跳回用户态 syscall 指令的下一条指令

④ 切换回用户栈

rsp 恢复为用户态的栈指针

用户程序继续执行,拿到 read() 的返回值

─────────────────────────────────────────────────────

完整流程图:

─────────────────────────────────────────────────────

用户态(CPL=3) 内核态(CPL=0)

───────────── ─────────────

read(fd,buf,n)

设置寄存器

syscall指令 ──────────→ 保存用户现场

切换内核栈

查系统调用表

sys_read()

操作文件系统

复制数据

恢复用户现场

sysretq指令 ←────────── sysretq

继续执行

拿到返回值

相关推荐
HLC++13 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室14 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy17 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕17 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫18 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生19 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1120 小时前
linux基础IO
linux·运维·服务器
BelongPanda1 天前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯1 天前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_777998341 天前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器