用户态和内核态的区别

概念

内核态:内核态不是一个"地方",而是 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

继续执行

拿到返回值

相关推荐
琥珀色糖6 小时前
Linux守护进程
linux·终端·守护进程·会话·进程组
开心大爆炸10 小时前
xrdp 连接 登录对话框输入密码后闪退
linux·运维·服务器
oushaojun210 小时前
大厂 C++ 面试:深度剖析 Linux Signal 信号机制(转)
linux
苏生Susheng11 小时前
【软件实施】Linux系统Shell脚本教程
linux·运维·服务器·chrome·spring boot·学习·实施
Ruiery11 小时前
Linux 6.6内核 IOMMU 深度解析(七):DMA API 与 IOMMU 集成 — 从 dma_map_single 到 iommu_map
linux·运维·服务器
分支预测失败11 小时前
RISC-V AIA 中断架构实战:从 PLIC 到 APLIC 与 IMSIC 的迁移
linux·后端
GeW11 小时前
RHCE学习拿证计划:锁定核心考点,实现备考效率最大化
linux
snow@li12 小时前
服务器运维:Alibaba Cloud Linux 4 LTS 64位 根目录全景深度解析文章
linux·运维·服务器
苏生Susheng12 小时前
【软件实施】Linux企业运维常用命令手册
java·linux·运维·服务器·springboot·springcloud·软件实施
额额额对了12 小时前
Linux 进程间通信(IPC)核心机制:消息队列、共享内存与信号量详解
linux·网络·网络协议