一、引子系统调用到底是怎么调进去的操作系统在软件设计上只提供两样东西系统调用号每个系统调用有一个唯一下标如__NR_open 5陷入机制int 0x8032 位/syscall64 位并为 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 ModeCPU 当前特权级别为3进程以用户身份运行只允许访问 0-3GB的用户区无权访问 3-4GB 的内核区内核态Kernel ModeCPU 当前特权级别为0进程以内核身份运行允许通过系统调用访问 3-4GB的内核区用一句话说用户态 我只能跑我自己的代码内核态 我被允许以系统调用的固定方式执行操作系统的代码。五、硬件实现CS 段寄存器的低 2 位 —— CPL当前是用户态还是内核态不是软件约定而是CPU 硬件层面的状态CPU 的CS 段寄存器低 2 位表示当前特权级别称为CPLCurrent Privilege LevelCPL 0→ 内核态CPL 3→ 用户态平时跑用户代码时CPL 3陷入内核时发生了什么执行int 0x80/syscall的瞬间CPU 硬件自动完成两件事CS 转而指向操作系统对应的代码段CPL 由 3 改为 0—— 这就叫陷入内核陷入之后就能为所欲为了吗✖ 不能。即使 CPL 已经是 0OS 依旧禁止你以地址方式直接访问内核的任意代码和数据陷入后执行的是 0x80 绑定的固定处理历程你必须提供系统调用号走系统调用号 查系统调用表这一条路其他路径全被封死一切越权动作都会被拦截 这就是 OS 的自保逻辑让你进来但只让你走我给你铺好的那一条道。六、总结对比项用户态内核态CPLCS 低 2 位30可访问范围0-3GB 用户区3-4GB 内核区仅限系统调用路径页表进程私有的用户级页表全系统唯一的内核页表典型场景执行自己的 main、库函数执行系统调用的内核实现、中断处理核心链条回顾所有进程的 3-4GB 都映射同一个 OS →任何进程都能找到内核但 OS 不信任任何人 →直接访问 3-4GB 会被拦截想调内核 → 系统调用号入 EAX int 0x80/syscallCPU 硬件配合CS 指向内核代码段 CPL 3→0 →陷入内核陷入后只能走固定历程查系统调用表执行做完返回CPL 0→3 回用户态 三个概念的对应关系为下一篇信号捕捉铺垫陷阱trapint 0x80/syscall—— CPU 主动执行指令陷入内核异常exception除零、野指针、缺页 —— CPU 执行出错被动陷入硬件中断interrupt键盘、时钟等外设触发