我们知道在多进程环境下,操作系统通过不停地在各进程间切换来分享CPU,以达到CPU虚拟化的目的。那么CPU是怎样实现这一目的的呢?
1. 不加限制
先来看看操作系统不限制进程行为时的模型。
若要运行程序,操作系统要进行以下几个步骤:
- 在进程列表中为程序创建一条记录
- 为程序分配内存
- 把程序从磁盘读取到内存
- 设置启动参数
argc/argv
- 清空寄存器(被上一个进程使用过)
- 定位程序入口(比如
main()
)所在地址 - 跳转到程序入口,执行用户程序
- 等待程序运行结束,释放该程序所使用的内存,从进程列表将其移除
以上整个步骤看起来非常简单,感觉本就理应如此。但是其中存在有一些安全隐患:
- 用户程序里的非法操作会直接得到执行:既然程序掌握了CPU控制权,它会不会申请更多的内存?会不会发起非法的I/O操作?
- 用户程序可能会霸占CPU:若用户程序一直不返回,操作系统将无法切换进程。
2 限制程序行为
为了在用户程序运行时限制其操作,操作系统把一些核心的操作保护起来,封装成系统调用(system call),比如I/O操作、申请内存、更改文件权限等操作。当用户程序调用系统调用时,CPU会进入内核态(kernel mode),在内核态执行相关的系统程序,执行完成后退出内核态,进入用户态(user mode),把CPU的控制权返回给用户程序。
也就是说,操作系统禁止用户程序直接执行敏感操作。用户程序必须在user mode
向操作系统申请执行某项敏感操作,在获得操作系统同意后,系统会在kernel mode
替用户程序完成这项操作。
那么当用户程序执行系统调用时,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的控制权。
interrupt
与trap
基本同义,都是在操作系统启动时初始化处理程序,并由硬件记录其地址。
关于进程如何调度,暂且不表。
(完)
References: