美团二面:什么叫进入内核态?( 二 )


  1. CPU 必须至少有两种不同的状态:操作系统状态和应用程序状态 。不同状态下,相同指令会产生不同的结果,也就保证某些任务只有操作系统能执行,某些任务只有应用程序能执行 。
  2. 操作系统必须有办法配合 CPU,设置哪些内存可以访问,哪些内存不能访问(或者说只有操作系统状态下能访问),不能访问的包括操作系统自己的代码区和数据区、中断向量表等 。
  3. 应用程序状态下不能直接访问硬件设备
  4. CPU 在触发中断时需要自动切换到操作系统状态(否则无法进行多任务切换)
  5. 操作系统状态可以自由切换到应用程序状态;应用程序状态不能任意切换到操作系统状态,但也需要有触发进入操作系统代码并切换到操作系统状态的能力(否则无法调用操作系统功能)
现在我们回到实际 CPU 的设计上,显然实际 CPU 的设计者的思路跟我们是差不多的 。这里我们叫做操作系统状态的,在实际操作系统概念中就叫做内核状态,在 CPU 设计上则叫做特权模式;我们叫做应用程序状态的,在实际操作系统概念中叫做用户态,CPU 设计上叫做用户模式 。
注意到,内核态并不是一个东西,没有处于什么地方一说,它是 CPU 的两种状态之一 。如果不是说进入内核态,而是说切换到内核态,可能你就没有这种误解了 。都怪intel将系统调用的指令起名字叫sysenter,所以大家都比较习惯说“进入”内核态 。
实际上 CPU 可能被细分为更多的运行模式,而不仅仅是特权和用户两种模式,不过操作系统至少需要这两种 。有的时候特权和用户模式也指的并不是一种真正的模式,而是一类模式,比如好几种类似的但略有区别的运行模式都合成特权模式之类 。
这种特权 + 用户的多模式切换的运行方式,就叫做(x86)CPU 的保护模式功能 。保护模式之所以是一个模式,有一定的历史原因,因为 intel CPU 每一代产品都会尽量兼容之前的产品,早期的 CPU 启动时是实时模式,没有这种模式切换的功能,后来的 CPU 为了兼容早期的 CPU,启动时也处于实模式,需要引导程序主动进入保护模式,然后才拥有多模式切换的能力 。这些是历史原因和一些细节问题 。
对于 CPU 本身来说,CPU 是不知道究竟哪一段代码属于应用程序、哪一段代码属于操作系统的,它没有能力识别当前执行的代码究竟应不应该有权限,因此它只负责按照程序逻辑来执行:如果指令自己要求自己进入用户模式,CPU 就进入用户模式,但进去之后,就只有特定的方法才能再回到特权模式 。所以并不是说进入特权模式就一定是操作系统代码了,CPU 并没有这个保证 。但是,我们说了,保护模式设计的目标就是为了让应用程序代码受到限制,如果应用程序的代码进入了特权模式,这个限制就完全失效了,所以操作系统设计上会使用各种各样的巧妙手段,配合CPU的功能,保障应用程序只能通过跳转到操作系统代码的方式来切换到内核态上,这样也就间接保障了内核态下执行的都是操作系统(包括驱动)的代码 。
接下来我们讨论如何限制内存访问的问题,这也是这个设计中最困难的一部分 。相比来说,在用户模式下禁用一部分指令功能比较简单,无非是控制器里加入相应的组合逻辑,判断当前状态,如果状态为用户模式则拒绝执行特权指令而已 。而内存读写则不一样,指令是相同的,只是访问的内存地址不同,这时候有些地址是可以访问的,有些地址则不能访问,能不能访问的区别仅仅在内存地址上 。要知道,CPU 是支持利用寄存器间接寻址的,因此这个非法的指令不可能在译码的阶段就发现,而是必须在执行期间发现;同时,哪些地址可以访问,哪些地址不能访问,必须完全是可配置的,操作系统有极大的自由 。最后,这个系统还必须对应用程序有最基础的友好性,不能让应用程序太难写 。
既然内存里每一个单元是否允许访问都需要能够设置,而内存的大小是不确定的,那这个设置的数量也不确定,而且会较为庞大,在寸土寸金(?)的 CPU 里放这么多、这么复杂的设置是很不合适的,唯一可行的方案就是通过内存自己来管理内存——使用一部分内存用来存储其他内存应该如何使用的配置 。这样,实际访问内存时,就需要——


推荐阅读