(转载)虚拟化(2):程序在限制环境执行。

转自:https://zhuanlan.zhihu.com/p/37918052

为了能够虚拟化cpu,操作系统就需要将物理的cpu让多个运行中的任务共享,来产生他们在同时运行的感觉。最简单的方法就是让一个进程运行一会然后再让另外一个进程运行一会,这样往复下去。但是,在构建此类虚拟化方面存在一些挑战.首先是性能:我们如何在实现虚拟化的同时没有给系统增加过多的开销?第二是控制:如何在进程运行很流畅的情况下保持对CPU的控制?控制对操作系统尤其重要,因为它负责资源管理; 没有控制,进程会访问不应该被允许的信息。因此,在保持控制的同时获得高性能是构建操作系统的核心挑战之一。

 

为了让程序运行的尽可能的快,os的开发者使用了一种技术,我们称之为“限制的直接运行”(limited direct execution)。直接运行表示直接将程序运行在cpu上。这样当os想开始一个程序运行的时候,就会创建一个进程的实例添加到进程链表中,然后分配对应的资源,跳转到开始指令然后运行。下图表示这种方式的一个流程图。

如果直接让程序直接运行在没有OS干涉的硬件上,会有什么问题?

 

 

 

这个是书中给出的直接运行的流程图,其实流程很简单,首先os初始化后就去运行用户的main方法,当用户程序运行完成后主动调用返回os的指令再返回到os中,接着os进行一些对这个进程的一些回收的动作。仔细思考后,上面的流程会带来如下的问题:

(1)怎么限制程序的行为,因为一个程序不能肆无忌惮的对硬件进行操作,不然一个恶意的程序很快就可以破坏整个系统。

(2)os怎么主动的切换进程呢?因为在上图的流程中,只有程序主动返回后才将机器的控制权转到了os。

 

怎么让程序执行的行为得到控制?

cpu提供了user mode和kernel mode的不同模式,这个属于cpu的硬件支持。在user mode下,很多指令是无法执行的,比如对于内存、硬盘的操作。在开机进入保护模式后,只有os才能进入kernel mode,进而对硬件进行了保护。当然用户程序不可能不对内存、硬盘进行访问,所以 OS提供system call,让程序可以执行kernel mode才能执行的行为。为了运行一个system call,程序必须执行一个特定的trap指令。这个指令会跳转到内核程序并且将特权级改为kernel mode,这样就可以执行任何指令了。执行完毕后,os会调用一个特定的return-from-trap指令,这个指令会将程序返回到用户程序并且将特权级改为user mode。

当运行trap指令的时候,硬件需要做一些额外的事情,保证该保存的信息都已经保存了,这样不至于从return-from-trap返回后不能正常的运行。在x86平台下,处理器会将pc寄存器,标志寄存器和其他的一些寄存器的信息放入内核栈,return-from-trap的时候会将这些信息返回到相应的寄存器中并且继续运行用户程序。其他的架构会有一些不同,不过概念是通用的。

上面是书中给出的在限制程序的行为后,硬件,os,用户程序间是如何交互的。我觉得这也是这本书最值得称赞的地方,因为书中清晰的划分了硬件,软件的界限,并且很清楚的说明了它们之间的交互,这样即使你没有汇编的知识,你也会收获很多(当然认真的学习下汇编对更加深入理解是至关重要的)。

图中首先初始化一个trap table。每个system call都会对应一个整数的编号以及相应的代码,os的代码决定了这些system call具体要做的事情,所以trap table相当于一个map,一个数字的编号对应一个代码的开始指令(其实第一个数字编号可以省略,省略后就相当于一个数组,自带编号,说成map可以好理解写)。这个trap table的地址需要单独记录下来,不然后面用的时候就找不到啦。记录的工作就需要硬件来了, 因此cpu提供了相应的寄存器来记录这个地址。

接着OS进行初始化(分配内存,建立进程的链表等),初始化后开始运行用户的进程,在切换的时候硬件将寄存器的信息恢复,然后再切换回user mode运行用户程序。当用户程序调用一个system call 的时候,首先是硬件将寄存器信息保存在内核栈中,然后切换回kernel mode,根据开机初始化的trap table找到对应的处理代码,接下来就是运行os的处理代码,在运行的最后,是一个返回的指令,这个时候硬件将寄存器的信息从内核栈中恢复,然后切换回user mode,将PC指向产生system call 的下一条指令,这样就又恢复到用户程序中了,在用户程序执行完后,调用exit()函数,又会回到内核,这个时候os再做一些进程的最后的清理工作。

上面介绍的这个方式展示了在用户程序和os之间切换的步骤,不过怎么主动实现进程间的切换呢,首先就是进程能主动的放弃cpu,这种方式在早期的os中会用到,我们称为非抢占式,不过这种方式的弊端太大,比如假如进程死循环了整个机器就挂了。。。所以现在的os一般都是抢占式的,也就是os控制进程的切换,但是os如何能够实现呢?实现的方式很简单,就是使用时钟中断(timer interrupt)。我们使用一个特定的时间装置,能够在固定的时间发起一次中断,硬件在收到这个中断的时候,按照开机的时候就初始化好的trap table 运行对应的代码。

下面是这种方式的流程图,和上面的步骤差不多,只是多了timer interrupt

 

 到这里你可能会想,如果正在运行一个system call,同时一个time interrupt也发生了,这个时候内核要怎么处理呢?这一块的内容就是这本书的第二块并发的内容了,大家很快就可以看到。

 

posted @ 2021-08-19 16:32  胖白白  阅读(58)  评论(0编辑  收藏  举报