异常控制以及进程调度
1.异常控制
异常是一种形式的异常控制流,它的一部分是由操作系统实现,一部分是由硬件实现的.因为有一部分是有由硬件实现的,所以具体细节会随着操作系统的不同而不同,然而基本的思想都是相同的.
异常就是控制流中的突变,用来响应处理器状态中的某些变化.任何情况,当处理器检测到有事件发生时,它就通过一张异常表,进行一个间接过程调用(异常),到一个专门处理这类事件的操作系统子程序-异常处理程序:
当异常处理程序完成后,根据引起异常的事件类型,会发生以下三种情况之一:
1>处理程序将控制返回给当前指令
2>处理程序将控制返回给,如果没有发生异常时,将会执行的下一条指令
3>处理程序终止被中断的程序
异常类似于过程调用,但是有一些重要的不同之处:
1>过程调用时,在跳转到处理程序之前,处理器将返回地址压到栈中.然而,根据异常的类型返回地址要么是当前指令,要么是下一条指令.
2>处理器也把一些额外的处理器状态压到栈里,在处理程序返回时,重新开始被中断的程序会需要这些状态.
3>如果控制从一个用户程序转到内核,所有这些项目都被压到内核栈中,而不是压到用户栈中.
4>异常处理程序运行在内核模式下,这意味着它们对所有的系统资源都有完全的访问权限.
异常的类别:
异常可以分为四类:中断,陷阱,故障和终止.
1>中断
中断是异步发生的,是来自处理器外部的I/O设备的信号的结果.硬件中断不是由任何一条专门的指令造成的,从这个意义上说,它是异步的.硬件中断的处理程序被称为中断处理程序.
在当前指令完成执行之前,处理器检测到来自外部I/O设备的信号,然后从系统总线中读取异常号,调用中断处理程序.当处理器程序返回时,它就将控制返回给下一条指令.
其余的异常类型(陷阱,故障,终止)都是同步发生的,是执行当前指令的结果.
2>陷阱
陷阱是有意的异常,是执行一条指令的结果.就如同中断处理程序一样,陷阱处理程序将控制返回到下一条指令.陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用.
用户程序经常需要向内核请求服务,比如,读取一个文件,创建一个新的进程,加载一个新的程序.为了允许对这些内核服务的受控访问,处理器提供一条特殊的"syscall n"指令,当用户程序想要请求服务n时,可以执行这条指令.执行syscall指令会导致一个到异常处理程序的陷阱,这个程序对参数译码,并调用适当的内核程序.
从一个程序员角度来看,系统调用和普通函数调用是一样的.然而,它们的实现是非常不同的.普通的函数运行在用户模式中,用户模式限制了函数可以执行的指令的类型.而且它们只能访问与调用函数相同的栈.系统调用运行在内核模式中,内核模式允许系统调用执行指令,并访问定义在内核中的栈.
3>故障
故障由错误情况一起,它可能被故障处理程序修正.当一个故障发生时,处理器将控制转移给故障处理程序.如果处理程序能够修正这个错误情况,它就将控制返回到故障指令(当前指令),从而重新执行它.否则,处理程序返回到内核中的abort例程,终止应用程序.
故障的一个经典示例就是缺页异常.当指令引用一个虚拟地址,而与该地址相应的物理地址页面不在存储器汇总,因此必须从磁盘中取出,就会发生这种故障.
4>终止
终止时不可恢复的致命错误造成的结果-典型的是一些硬件错误.比如DRAM或者SRAM位被损坏时发生的奇偶错误.终止处理程序从不将控制返回给应用程序,而是将控制返回给一个abort例程,终止这个应用程序.
2.进程调度
当我们在一个现代系统上运行一个程序时,我们会得到一个假象,就好像我们的程序时系统中当前运行的唯一程序.我们的程序好像独占地使用处理器和存储器.处理器就好像是无间断的一条接一条地执行我们程序中的指令.最后,我们程序中的代码和数据显得好像是系统存储器中唯一的对象.这些假象都是进程这个概念给我们提供的.
进程的经典定义就是一个执行中程序的实例.系统中每个程序都是运行在某个进程上下文中.上下文是由程序正确运行所需的状态组合.这个状态包括存放在存储器中的程序的代码和数据,它的栈,它的调用的寄存器的内容,它的程序计数器,环境变量以及打开文件描述符的集合.
进程提供给应用程序两个关键的抽象:
1>一个独立的逻辑控制流:它提供一种假象,使我们觉得我们的程序独占地使用处理器.
2>一个私有的地址空间:它提供一个假象,使我们觉得我们的程序独占地使用存储器系统.
逻辑控制流:
如果我们用调试器单步地执行我们的程序,就会看到一些列PC值,这些值惟一地对应于包含在我们程序汇总的可执行目标文件中的指令或是包含在运行时动态链接到我们程序的共享对象中的指令.这个PC值的序列叫做逻辑控制流.
一般而言,和不同进程相关的逻辑流并不影响任何其他进程的状态,从这个意义上说,每个逻辑流都是与其他逻辑流独立的.当进程使用进程间通信(IPC)机制,比如管道,套接口,共享存储器和信号量,显示地与其他进程交互时,这条规则的惟一例外就会发生.
任何逻辑流在实践上和另外的逻辑流重叠的进程被称为并发进程.进程和其他进程轮换运行的概念成为多任务,一个进程执行它的控制流的一部分的每个时间段叫做时间片.因此,多任务也叫做时间分片.
私有地址空间:
在一台n为地址的机器上,地址空间是0到2的n此方-1.一个进程为每个程序提供它自己的私有地址控制.一般而言,和这个空间中某个地址相关的那个存储器字节是不能被其他进程读取或者写入的.从这个意义上说,这个地址空间是私有的.
尽管和每个私有地址空间相关的存储器的内容一般是不同的,但是每个这样的空间都有相同的结构.
用户模式和内核模式
进程运行在内核模式时,可以执行指令集中的任何指令,并且可以访问系统中的任何存储器位置.
进程运行在用户模式时,不允许执行特权指令,比如,停止处理器,改变模式位的值,或者发起一个I/O操作,也不允许直接引用地址空间中的内核区内的代码和数据.
一个运行应用程序代码的进程初始时是在用户模式中的.进程从用户模式变为内核模式的惟一方法就是通过诸如中断,故障或者陷阱这样的异常.
上下文切换:
内核为每个进程维持一个上下文.上下文就是内核重新启动一个被抢占进程所需的状态.它由一些对象的值组成,包括通用目的寄存器,浮点寄存器,程序计数器,用户栈,状态寄存器,内核栈和各种内核数据结构,比如描绘地址空间的页表,包含有关当前进程信息的进程表,以及包含进程已打开文件的信息的文件表.
在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程.这种决定就叫做调度,是由内核中称为调度器的代码处理的.当内核选择一个新的进程运行时,我们就说内核调度了这个进程.在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程.上下文切换可以:
1>保存当前进程的上下文
2>恢复某个先前被抢占进程所保存的上下文
3>将控制传递给这个新恢复的进程
进程A与进程B之间上下文的切换示例,如下图:
初始时,进程A运行在用户模式中,直到它通过执行read系统调用陷入到内核.内核中的陷阱处理程序请求来自磁盘控制器的DMA传输,并在磁盘控制器完成从磁盘到存储器的数据传输后,要求磁盘中断处理器.
磁盘读取要用一段相对较长的时间,所以内核执行从进程A到进程B的上下文切换,而不是在这个间歇时间内等待,什么都不做.在切换之前,内核代表进程A在用户模式下执行指令.在切换的第一步中,内核代表进程A在内核模式下执行指令.然后在某一个时刻,它开始代表进程B(仍然是在内核模式下)执行指令.在切换完成之后,内核代表进程B在用户模式下执行指令
随后,进程B在用户模式下运行一段时间,直到磁盘发出一个中断信号,表示数据已经从磁盘传送到了存储器.内核盘点进程B已经运行了足够的时间,就执行一个从进程B到进程A的上下文切换,将控制返回给进程A中紧跟随read系统调用之后的那条指令.进程A继续运行,直到下一次异常发生.