Limited Direct Execution 读书笔记

操作系统备忘:进程的执行之LDE

Limited Direct Execution

进入一个进程后一刻不停向下运行 直到这个程序运行完成,我们运行下一个程序

The basic idea is simple: run one process for a little while, then run another one, and so forth.

问题

如果不受限的话,这个进程做了什么傻逼操作,或者开了个无限循环,我们要陪他玩到天黑啊。

我们希望能够对这样运行的进程进行控制。

第一个很简单:如果我们只是运行一个程序,操作系统如何确保程序不做任何我们不想做的事,同时仍然有效地运行它?
第二:当我们运行一个进程时,操作系统如何阻止它运行并切换到另一个进程,从而实现我们要求虚拟化CPU所需的时间共享?

LDE

The “direct execution” part of the idea is simple: just run the program directly on the CPU.

直接执行

问题1 :你不能进行这项操作

问题是比方说你要进行文件IO,或者输入输出,你的所有进程可以轻易访问文件系统的话,你将丧失很多文件系统方面的保护机会。因此引入以下模式:

kernel mode 和 user mode

进程有kernel mode 和user mode, 用于进行区分和操作。kernel mode可以进行IO等操作。

trap 进入kernel mode, return-from-trap 进入 user mode。

Thus, the approach we take is to introduce a new processor mode,known as user mode; code that runs in user mode is restricted in what itcan do. For example, when running in user mode, a process can’t issueI/O requests; doing so would result in the processor raising an exception;the OS would then likely kill the process.

In contrast to user mode is kernel mode, which the operating system(or kernel) runs in. In this mode, code that runs can do what it likes, in-cluding privileged operations such as issuing I/O requests and executingall types of restricted instructions.

return-from-trap 时,注意保存足够的调用者信息。

例如,在x86上,处理器会将程序计数器,标志和其他一些寄存器推送到每个进程的内核堆栈上; return-from- trap将从堆栈弹出这些值并恢复执行用户模式程序

open 等操作帮你进行这些操作,因为它封装了 trap return-from-trap 等指令,并且用C api的样子当成函数出现在你的面前

谁在运行程序

There is one important detail left out of this discussion: how does the trap know which code to run inside the OS? Clearly, the calling process can’t specify an address to jump to (as you would when making a pro- cedure call); doing so would allow programs to jump anywhere into the kernel which clearly is a bad idea (imagine jumping into code to access a file, but just after a permission check; in fact, it is likely such ability would enable a wily programmer to get the kernel to run arbitrary code sequences [S07]). Thus the kernel must carefully control what code exe- cutes upon a trap.

我们需要知道谁在运行程序,程序运行完后怎么办。

内核通过在启动时设置陷阱表来实现。当机器启动时,它在privileged(kernel)模式下执行,因此可以根据需要自由配置机器硬件。操作系统做的第一件事就是告诉硬件在发生某些异常事件时要运行哪些代码。例如,当发生硬盘中断,发生键盘中断或程序进行系统调用时,应该运行哪些代码?操作系统通常通过某种特殊的指令通知硬件这些陷阱处理程序的位置。一旦硬件被通知,它就会记住这些处理程序(trap handlers)的位置,直到下一次重新启动机器,并且硬件知道在发生系统调用和其他异常事件时要做什么(即跳转到哪个代码)。

trap table 初始化

我们假设每个进程都有一个内核堆栈,当进入内核和从内核进入内核时,寄存器(包括通用寄存器和程序计数器)被保存并从硬件中恢复。
LDE协议有两个阶段。在第一次(在引导时),内核初始化陷阱表,并且CPU记住它的位置以供随后使用。内核通过特权指令来执行此操作(所有特权指令均以粗体突出显示)。
在第二种情况下(运行进程时),内核在使用从陷阱返回指令开始执行进程之前设置了一些内容(例如,在进程列表上分配一个节点,分配内存);这会将CPU切换到用户模式并开始运行该进程。当进程希望发出系统调用时,它会回收到操作系统,然后再次通过从陷阱返回到进程来控制操作系统。该过程然后完成其工作,并从main()返回;这通常会返回到一些存根代码,这将正确退出该程序(例如,通过调用exit()系统调用,这将陷入到OS中)。此时,OS清理完成,我们完成了。

LDE:协议

问题2: 进程间的切换

问题概述

OS必须正在运行才能完成调度等任务,要合理的进行进程切换。

等于说,OS能进行调度正因为它在运行。没有在运行的OS无法正确的处理程序。我们要考虑程序的进程间切换。让OS能重新获得CPU的掌控权。

The next problem with direct execution is achieving a switch between processes. Switching between processes should be simple, right? The OS should just decide to stop one process and start another. What’s the big deal? But it actually is a little bit tricky: specifically, if a process is running on the CPU, this by definition means the OS is not running. If the OS is not running, how can it do anything at all? (hint: it can’t) While this sounds almost philosophical, it is a real problem: there is clearly no way for the OS to take an action if it is not running on the CPU. Thus we arrive at the crux of the problem.

cooperative approach: 抢占式

One approach that some systems have taken in the past (for example, early versions of the Macintosh operating system [M11], or the old Xerox Alto system [A79]) is known as the cooperative approach.

抢占到的任务(进程)能够一直运行。这样的情况下:

事实证明,大多数进程通过进行系统调用来将CPU的控制权转移到操作系统,例如打开文件并随后读取文件,或者向另一台机器发送消息或创建新进程。像这样的系统通常包括一个显式的yield系统调用,除了将控制权交给操作系统以外,它可以运行其他进程。
应用程序也可以在操作系统执行某些非法操作时将控例如:如果应用程序被零分隔,或者尝试访问应该无法访问的内存,则会向操作系统生成trap。操作系统将再次控制CPU(并可能终止违规过程)。

  1. “系统调用(System Calls)”发生的时候,OS会着手控制,yield表示让出这份强占的优先级。
  2. 出现异常的时候,引发trap,权限交给操作系统

缺陷

因此,在协作调度系统中,OS通过等待系统调用或某种非法操作发生而重新获得CPU的控制权。 你也许会想:这种被动方式不是理想吗? 例如,如果某个进程(无论是恶意的还是充满bug的)以无限循环结束,并且从不进行系统调用,会发生什么情况? 那么操作系统能做什么?

Non-Cooperative approach: 非抢占

如果你没有系统调用还有一个while(true) {} 在上面,你就得重启(reboot)了

使用timer interrupt 来进行终端,OS 用interrupt handler来处理。通过中断,OS重新获得了CPU控制权。

需要做到这些,我们需要在启动时完成以下目标

  1. 对系统调用的trap建立trap表
  2. 运行timer

上下文存储

进程运行的时候有对应的上下文,把reg的变量存储起来即可。

重启的时候切换reg,必要的量等,进程就能原样运行了…

加入Timer Interrupt的LDE

Concurrency

系统调用和timer中断同时发生?

  1. 操作系统的trap调用被优化到运行时间极短
  2. 见下文

操作系统可能做的一件简单的事情就是在中断处理期间禁止中断;这样做可以确保在处理一个中断时,不会将其他中断传送到CPU。当然,操作系统必须小心这样做;禁用中断时间过长可能导致丢失中断,这在技术上是不好的。
操作系统还开发了许多复杂的锁定方案,以保护对内部数据结构的并发访问。这使得多个活动可以同时在内核中进行,特别适用于多处理器。正如我们在下一本关于并发的书中将会看到的那样,这种锁定可能会变得复杂,并导致各种有趣而难以发现的错误。

剩下的问题

但是一个主要问题是没有答案:我们应该在特定时间运行哪个process?调度人员必须回答这个问题,因此也是我们研究的下一个主题。