OS: Concept: Limit Direct Execution

 20th August 2020 at 2:19pm

操作系统在运行用户程序时需要做到几点:

  • 限制用户程序的操作,比如用户程序无能直接访问底层硬件,用户程序读写文件时需要经过操作系统文件系统的权限管理
  • 保持对系统的控制,比如操作系统一旦开始运行用户程序,需要有能力使 CPU 运行回操作系统的代码

限制用户程序的操作

操作系统为了实现一些统一的调度、安全等,不会直接开放用户直接做核心的操作。最简单的例子是,用户程序无法直接访问硬盘设备,而需要通过操作系统提供的 API (open(), read() 等)操作文件系统,在经过权限检查后才可以访问。如果操作系统不做这层限制,它就无法抽象出文件系统。

因此操作系统发展出 user modekernel mode。上文提到的核心操作都必须在 kernel mode 下由操作系统本身的代码完成。而用户程序想使用这些核心操作时,是通过调用 OS 提供的 system call 来实现的(后面简称为 syscall)。syscall 看起来跟普通函数很像,但调用它时,会触发系统进入 kernel mode 执行相应操作。

这一过程需要 硬件配合。syscall 事实上也是一些长驻在内存中的代码,也有相应的入口地址;当用户调用它时,由于安全考虑,用户程序并不能知道它的内存地址,因此需要硬件来配合执行相应的程序。事实上操作系统启动时,会初始化一个 trap table(也叫 trap handler table),把不同的 syscall 的号码和相应的 handler 地址交给 CPU 记忆。当用户程序调用 syscall 时,用户程序触发一个 trap CPU 指令,并附带相应的号码;CPU 便从 trap table 中找到相应的 handler 去执行。因为注意的是,syscall 也有自己的 stack(kernel stack)和寄存器值等,因此从用户程序切换到 syscall 的过程了也涉及 上下文切换。整个过程如下:

保持对系统的控制

当 CPU 在执行应用程序时,它自然就没有执行操作系统的代码。那怎样 使操作系统重新获得控制权,而不是让应用程序一直运行呢?有两种方式:

  • 协作式的(cooperative):等待应用程序调用 syscall 时,将控制权回到操作系统。早期的操作系统有这样设计的,同时也有专门的指令使应用程序就算不调用 syscall 也可以把控制权给回
  • 非协作式的(non-cooperative,也称抢占式):实现一套操作系统和硬件配合的 中断定时器(timer interrupt),每隔一段时间(也称处理器时间片,processor time slice)将控制权交回操作系统

整个流程如下,其中 k-stack 指 kernel stack,proc_t 指 process table:

结合此图和上图可以发现,这个过程中有一个核心是上下文切换(context switch)。有两个场景需要上下文切换

  • 从用户态变为内核态时,保存用户态进程的 reg 和 stack,恢复 kernel 的 reg 和 stack
  • 切换进程时,保存当前进程的上下文,恢复将被运行进程的上下文

值得注意的是,操作系统和硬件都会做上下文切换。硬件做的可能更底层,比如只包含寄存器;操作系统应该会保存更多进程的状态数据。