背景
这个系列的第三篇笔记对应的是书中的《Mechanism: Limited Direct Execution》,主要内容是对之前说过的 time sharing 介绍了一点稍微深入的东西。
Challenges Of Time Sharing
Time sharing 从定义来说很简单,就是单个 CPU 运行一个 process 一段时间,然后在运行其它 process 直到所有 process 结束。但是在建立这个机制之前,我们还需要考虑两个问题:
- performance
- control
第一点很容易理解,这几乎是任何一个大型系统都要考虑的问题,性能问题。操作系统进行 process 的切换会不会对性能产生影响。第二点 control,这个在操作系统是特别重要的。因为这个涉及到如何停止一个 process,并且切换到另一个 process,涉及到如何限制一个 program 的权限,操作系统不可能让一个 program 想做什么就做什么,想访问哪块内存就访问哪块内存。所以最终我们要解决的问题就是,如何在做到保持各个 process 的控制的同时,有效率的运行,切换 process?
Limited Direct Execution
操作系统的开发者提出了一种技术 - limited direct execution 来解决这个问题。首先 direct execution 指的是直接在 CPU 上运行这个程序
when the OS wishes to start a program running, it creates a process entry for it in a process list, allocates some memory for it, loads the program code into memory (from disk), locates its entry point (i.e., the main() routine or something similar), jumps to it, and starts running the user’s code
这个解决了效率问题,但是并没有解决第二个问题。所以接下来我们着重讲一下操作系统是如何解决 control 的问题。这里具体要解决两个问题:
- 如何设定 user program 的权限
- 如何进行 process 的切换
Restricted Operations
process 会有两种不同的 mode, user mode 和 kernel mode。当 CPU 运行 user program 的程序的时候,CPU 会处于 user mode,而这个 mode 就规定了 user program 可以做的操作和权限。如果这个时候 user program 越界了,CPU 就会抛出一个异常,接着操作系统收到这个异常后,就有可能会 kill 这个 user program。而当 CPU 处于 kernel mode,CPU 可以接触并管理整个硬件系统(操作系统运行时,CPU 是处于 kernel mode)。
但是,肯定会有这样的一类需求,就是当 CPU 处于 user mode,但是 user program 又希望做一些高权限的操作(比如,请求访问硬盘)。这个时候 user program 可以通过调用 system call,将 CPU mode 提升为 kernel model。(这里有点让我想到 runloop 里面的各种 mode),这篇文章详细的介绍了这两种mode
System Call
什么是 system call,像我们前面一章用过的 open,fork 函数都是 system call,看起来就是一个普通的方法,只是说这个方法是由系统提供的。它与普通的方法区别在于,system call 最终都要执行一个 trap instruction,这个指令会将 processor mode 从 user mode 提升到 kernel mode,然后开始执行一些指令,执行完之后,再执行 return-from-trap 指令,返回 user program。不同的 system call 对应着不同的执行指令,这个对应的关系是存储在一张表里,也叫 trap table,这个 table 的建立是在机器启动的时候,由操作系统来确定的。详细的流程,可以看书中给的一副图:
这样,通过 processor mode 和 system call 就解决了第一个问题,现在再来看下如何解决第二个问题。
Process Switching
首先,计算机中都会有个计时器,每隔一段时间,就会触发一个 timer interrupt,当操作系统接收到这个 interrupt 时,就会进行 process switch。
switch 的具体流程:
(1) 计时器发出 timer interrupt
(2) 保存 user register
(3) processor 提升到 kernel model
(4) 跳转到 trap table 指定的指令
(5) 操作系统执行 switch 函数
(6) 保存 kernel register
(7) 加载新的进程的 kernel register
(8) 栈指针指向新的进程的栈
(9) 加载新的进程的 user register
(10) 执行 B 程序
关于寄存器的分类,可以看这篇。
最后书里还讲了关于 switch 函数的具体实现,是用汇编实现,这里不在陈述,有兴趣的可以去原文看代码。