大家都知道,目前操作系统为了更有效的利用cpu,会使多个任务共享一颗物理cpu:时间片轮换执行,分时共享。通过这种方式,使我们看起来他们在同时执行。
但是,使用这种虚拟化机制将面临如下挑战:
- 如何在不增加系统开销的情况下实现虚拟化
- 如何有效的允许进程,同时保留cpu的控制权
受限直接运行
直接运行
顾名思义,直接运行就是直接在cpu上运行程序。
如上图所示,在直接运行协议下,当OS希望启动程序执行时,只需在进程列表中为其创建进程条目,将目标代码加载至内存,找到程序入口点并跳转即可。
问题
- 操作系统如何确保程序不做任何我们不希望它做的事
- 当运行一个进程时,操作系统如何让它停下来并切换到另一个进程,以实现cpu的时分共享
受限制的操作
一个进程必须能够执行I/O和其他一些受限制的操作,但又不能完成控制系统。
用户模式/内核模式
在用户模式下,应用程序不能完全访问硬件资源。
在用户模式下运行的代码会受到限制,例如:不能发出I/O请求。
在内核模式下,操作系统可以访问机器的全部资源。
在内核模式下可以做任何事,包括特权指令。
Q:如果用户需要执行某种特权操作?
A:使用操作系统提供的系统调用
系统调用
为了让用户可以执行某种特权操作,几乎所有的现代硬件都提供了用户程序执行系统调用的能力。
为了执行系统调用,程序必须将系统调用参数以及系统调用号放入约定好的地方(栈或寄存器),然后执行trap指令,使跳入内核模式,操作系统将执行对应的系统调用。
下面这段代码即为glibc中对open系统调用的封装:
# define INTERNAL_SYSCALL_RAW(name, nr, args...) \
({ \
register int _a1 asm ("r0"), _nr asm ("r7"); \
LOAD_ARGS_##nr (args) \
_nr = name; \
asm volatile ("swi 0x0 @ syscall " #name \
: "=r" (_a1) \
: "r" (_nr) ASM_ARGS_##nr \
: "memory"); \
_a1; })
#endif
可以看到,上述代码采用了C中嵌入汇编的形式,其中,swi即为软中断指令,实现从用户模式切换到特权模式。
如何重获cpu控制权(进程间切换)
如果一个进程在cpu上执行,那么操作系统就没有运行。
如果操作系统没有运行,他怎么决定停止一个进程并开始另外一个进程。
协作方式
操作系统相信进程会合理的运行。
进程通过系统调用放弃cpu,将控制权交给操作系统。如果应用程序执行了某些非法操作,也会将控制转移给操作系统。
因此,在协作调度系统中,OS通过等待系统调用,或某种非法操作发生,从而重新获得CPU的控制权。
非协作方式
即使经常不协作,操作系统如何获得CPU的控制权?操作系统可以做什么来确保流氓进程不会占用机器?
答案是: 时钟中断
时钟设备可以每隔几毫秒产生一个中断,产生中断时,当前运行的进程会停止,操作系统中预先配置的中断处理程序会运行,操作系统重新获得CPU的控制权。
上下文切换
如果操作系统决定要切换至另外一个进程,OS就会执行一些底层代码,即上下文切换(context switch):为当前正在执行的进程保存一些寄存器的值,并为即将执行的进程恢复一些寄存器的值。
在上图中,有两种类型的寄存器保存/恢复。
- 在发生时钟中断的时候,运行进程的用户寄存器由硬件隐式保存至进程的内核栈。
- 当操作系统决定从A切换至B时,内核寄存器被OS明确的保存至该进程的进程结构内存中。
参考
- 《操作系统导论(Three Easy Pieces)》