我们知道在多进程环境下,操作系统通过不停地在各进程间切换来分享CPU,以达到CPU虚拟化的目的。那么CPU是怎样实现这一目的的呢?

1. 不加限制

先来看看操作系统不限制进程行为时的模型。

Screen Shot 2017-11-09 at 10.33.12

若要运行程序,操作系统要进行以下几个步骤:

  1. 在进程列表中为程序创建一条记录
  2. 为程序分配内存
  3. 把程序从磁盘读取到内存
  4. 设置启动参数argc/argv
  5. 清空寄存器(被上一个进程使用过)
  6. 定位程序入口(比如main())所在地址
  7. 跳转到程序入口,执行用户程序
  8. 等待程序运行结束,释放该程序所使用的内存,从进程列表将其移除

以上整个步骤看起来非常简单,感觉本就理应如此。但是其中存在有一些安全隐患:

  • 用户程序里的非法操作会直接得到执行:既然程序掌握了CPU控制权,它会不会申请更多的内存?会不会发起非法的I/O操作?
  • 用户程序可能会霸占CPU:若用户程序一直不返回,操作系统将无法切换进程。

2 限制程序行为

为了在用户程序运行时限制其操作,操作系统把一些核心的操作保护起来,封装成系统调用(system call),比如I/O操作、申请内存、更改文件权限等操作。当用户程序调用系统调用时,CPU会进入内核态(kernel mode),在内核态执行相关的系统程序,执行完成后退出内核态,进入用户态(user mode),把CPU的控制权返回给用户程序。

也就是说,操作系统禁止用户程序直接执行敏感操作。用户程序必须在user mode向操作系统申请执行某项敏感操作,在获得操作系统同意后,系统会在kernel mode替用户程序完成这项操作。

Screen Shot 2017-11-09 at 13.42.05

那么当用户程序执行系统调用时,CPU的控制权该怎么移交给操作系统呢?

之前我们说到,程序就是有序的指令序列,系统调用也不例外。系统调用会被编译成特殊的trap指令,当CPU执行到trap指令时,会跳转到其对应的处理程序。然后进入kernel mode,完成相应的处理操作。处理完之后,操作系统会执行特殊的return-from-trap指令,进入user mode,继续执行用户程序。

那么,如何跳转到trap对应的trap-handle程序呢?

操作系统在启动时会维护一张trap table,记录了多个trap以及其trap-handler的位置,然后通过特殊的指令告知硬件trap handler的位置。当然,这些指令必须在kernel mode下执行

3. 回收控制权

现在来解决第二个问题,操作系统如何在用户程序运行时收回CPU的控制权呢?等待用户程序发起系统调用?要是用户程序不发起系统调用,一直无限循环呢?

答案很简单,使用定时器引发中断(timer interrupt)。操作系统每隔一段时间就会启动timer,超时之后引发中断,中断由硬件实现。发生中断后会跳转到操作系统中的中断处理程序,这样操作系统就重新获得了CPU的控制权。

Screen Shot 2017-11-09 at 14.04.55

interrupttrap基本同义,都是在操作系统启动时初始化处理程序,并由硬件记录其地址。

关于进程如何调度,暂且不表。

(完)

References: