☰
信号_用户态与内核态
2026/9/28 5:04:00 网站建设 项目流程

一、引子:系统调用到底是怎么"调进去"的

  • 操作系统在软件设计上只提供两样东西:
    • 系统调用号:每个系统调用有一个唯一下标(如__NR_open = 5)
    • 陷入机制:int 0x80(32 位)/syscall(64 位),并为 0x80 号中断注册了固定的处理方法
  • 我们调的open/fork/read其实是glibc 的封装函数:
    • 追踪 glibc 2.9 源码:open→_IO_new_file_open→ … →INLINE_SYSCALL宏
    • 宏里做的事:把系统调用号放进 EAX 寄存器 → 执行int 0x80/syscall
  • 陷入内核后,0x80 的中断处理方法拿着 EAX 里的系统调用号,去查系统调用表(函数指针数组),执行对应内核函数

💡 结论:OS 只提供"系统调用号 + 陷入机制",上层接口全是 C 库封装的。所以才有"C 生万物"——几乎所有语言 runtime 最终都站在 glibc 之上。


二、重谈地址空间:0-3GB 用户区,3-4GB 内核区

进程的整个地址空间(4GB)被切成两段:

区间名称里面装的是什么
0 ~ 3GB用户区本进程自己的代码、数据、堆、栈等(个性化内容)
3 ~ 4GB内核区操作系统的代码和数据(全系统共享的内容)

对应的页表也分两套:

  • 用户级页表:每个进程一份
    • 每个进程都有自己独立的 0-3GB,各自映射到自己的代码和数据
    • 进程之间互不干扰,随便你怎么折腾
  • 内核页表:全系统只有一份
    • 所有进程的 3-4GB 都通过同一份内核页表映射到同一个操作系统
    • 无论系统里有 10 个还是 1000 个进程在调度,每一个进程都能在自己的地址空间里看到完整的 OS

这套设计意味着什么

✔任何一个进程都能找到操作系统。只要从自己的代码区跳转到 3-4GB 区域,就能访问到内核的所有代码和数据——与"当前是哪个进程在跑"无关。

✔CPU 再也不怕中断了。上节课说"进程跑着跑着来个中断,CPU 就跑过去执行中断向量表里的方法"——为什么能做到?因为不管 CPU 在执行哪个进程,中断来了之后,都可以通过当前进程地址空间里的 3-4GB 内核映射找到操作系统的一切。


三、矛盾出现了:用户能不能直接访问 3-4GB?

既然 OS 就映射在每个进程的 3-4GB,那我在代码里捏造一个 3-4GB 的地址,解引用、跳转过去,不就能随便读写内核了?

✖不行。操作系统不相信任何人。

  • OS 虽然把内核映射进了你的地址空间,但禁止你以"直接访问地址"的方式碰它
  • 想使用操作系统,必须走系统调用:系统调用号入 EAX →int 0x80/syscall陷入内核 → 查系统调用表
  • 这是唯一的固定路径,其他一切动作都会被操作系统拦截

💡 所以问题的关键变成:CPU 怎么知道"当前执行的是用户代码还是内核代码"?—— 这就引出了用户态和内核态。


四、正式定义:用户态与内核态

  • 用户态(User Mode):CPU 当前特权级别为3,进程以"用户身份"运行,只允许访问 0-3GB的用户区,无权访问 3-4GB 的内核区
  • 内核态(Kernel Mode):CPU 当前特权级别为0,进程以"内核身份"运行,允许通过系统调用访问 3-4GB的内核区

用一句话说:

用户态 = 我只能跑我自己的代码;内核态 = 我被允许(以系统调用的固定方式)执行操作系统的代码。


五、硬件实现:CS 段寄存器的低 2 位 —— CPL

"当前是用户态还是内核态"不是软件约定,而是CPU 硬件层面的状态:

  • CPU 的CS 段寄存器低 2 位表示当前特权级别,称为CPL(Current Privilege Level)
    • CPL = 0→ 内核态
    • CPL = 3→ 用户态
  • 平时跑用户代码时,CPL = 3

陷入内核时发生了什么

执行int 0x80/syscall的瞬间,CPU 硬件自动完成两件事:

  1. CS 转而指向操作系统对应的代码段
  2. CPL 由 3 改为 0—— 这就叫"陷入内核"

陷入之后就能为所欲为了吗

✖ 不能。即使 CPL 已经是 0,OS 依旧禁止你以地址方式直接访问内核的任意代码和数据:

  • 陷入后执行的是 0x80 绑定的固定处理历程
  • 你必须提供系统调用号,走"系统调用号 + 查系统调用表"这一条路
  • 其他路径全被封死,一切越权动作都会被拦截

💡 这就是 OS 的自保逻辑:让你进来,但只让你走我给你铺好的那一条道。


六、总结

对比项用户态内核态
CPL(CS 低 2 位)30
可访问范围0-3GB 用户区3-4GB 内核区(仅限系统调用路径)
页表进程私有的用户级页表全系统唯一的内核页表
典型场景执行自己的 main、库函数执行系统调用的内核实现、中断处理

核心链条回顾:

  1. 所有进程的 3-4GB 都映射同一个 OS →任何进程都能找到内核
  2. 但 OS 不信任任何人 →直接访问 3-4GB 会被拦截
  3. 想调内核 → 系统调用号入 EAX +int 0x80/syscall
  4. CPU 硬件配合:CS 指向内核代码段 + CPL 3→0 →陷入内核
  5. 陷入后只能走固定历程:查系统调用表执行,做完返回,CPL 0→3 回用户态

💡 三个概念的对应关系(为下一篇"信号捕捉"铺垫):

  • 陷阱(trap):int 0x80/syscall—— CPU 主动执行指令陷入内核
  • 异常(exception):除零、野指针、缺页 —— CPU 执行出错被动陷入
  • 硬件中断(interrupt):键盘、时钟等外设触发

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询