cpu虚拟化:受限直接运行(limited direct execution)

1,049 阅读4分钟

大家都知道,目前操作系统为了更有效的利用cpu,会使多个任务共享一颗物理cpu:时间片轮换执行,分时共享。通过这种方式,使我们看起来他们在同时执行。

但是,使用这种虚拟化机制将面临如下挑战:
  • 如何在不增加系统开销的情况下实现虚拟化
  • 如何有效的允许进程,同时保留cpu的控制权

受限直接运行

直接运行

顾名思义,直接运行就是直接在cpu上运行程序。

如上图所示,在直接运行协议下,当OS希望启动程序执行时,只需在进程列表中为其创建进程条目,将目标代码加载至内存,找到程序入口点并跳转即可。

问题

  1. 操作系统如何确保程序不做任何我们不希望它做的事
  2. 当运行一个进程时,操作系统如何让它停下来并切换到另一个进程,以实现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)》