译者:飞龙
在当前的许多系统上,CPU包含多个核心,也就是说它可以同时运行多个进程。而且,每个核心都具有“多任务”的能力,也就是说它可以从一个进程快速切换到另一个进程,创造出同时运行许多进程的幻象。
操作系统中,实现多任务的这部分叫做“内核”。在坚果或者种子中,内核是最内层的部分,由外壳所包围。在操作系统各种,内核是软件的最底层,由一些其它层包围,包括称为“Shell”的界面。计算机科学家喜欢引喻。
究其本质,内核的工作就是处理中断。“中断”是一个事件,它会停止通常的指令周期,并且使执行流跳到称为“中断处理器”的特殊代码区域内。
当一个设备向CPU发送信号时,会发生硬件中断。例如,网络设备可能在数据包到达时会产生中断,或者磁盘驱动器会在数据传送完成时产生中断。多数系统也带有以固定周期产生中断的计时器。
软件中断由运行中的程序所产生。例如,如果一条指令由于某种原因没有完成,可能就会触发中断,便于这种情况可被操作系统处理。一些浮点数的错误,例如除零错误,会由中断处理。
当程序需要访问硬件设备时,会进行“系统调用”,它就像函数调用,除了并非跳到函数的起始位置,而是执行一条特殊的指令来触发中断,使执行流跳到内核中。内核读取系统调用的参数,执行所请求的操作,之后使被中断进程恢复运行。
中断的处理需要硬件和软件的配合。当中断发生时,CPU上可能正在运行多条指令,寄存器中也储存着数据。
通常硬件负责将CPU设为一致状态。例如,每条指令应该执行完毕,或者完全没有执行,不应该出现执行到一半的指令。而且,硬件也负责保存程序计数器(PC),便于内核了解从哪里恢复执行。
之后,中断处理器通常负责保存寄存器的上下文。为了完成工作,内核需要执行指令,这会修改数据寄存器和位寄存器。所以这个“硬件状态”需要在修改之前保存,并在被中断的进程恢复运行后复原。
下面是这一系列事件的大致过程:
如果这一机制正常工作,被中断进程通常没有办法知道这是一个中断,除非它检测到了指令间的小变化。
中断处理器非常快,因为它们不需要保存整个硬件状态。它们只需要保存打算使用的寄存器。
但是当中断发生时,内核并不总会恢复被中断的进程。它可以选择切换到其它进程,这种机制叫做“上下文切换”。
通常,内核并不知道一个进程会用到哪个寄存器,所以需要全部保存。而且,当它切换到新的进程时,它可能需要清除储存在内存管理单元(MMU)中的数据。以及在上下文切换之后,它可能要花费一些时间,为新的进程将数据加载到缓存中。 出于这些因素,上下文切换相对较慢,大约是几千个周期或几毫秒。
在多任务的系统中,每个进程都允许运行一小段时间,叫做“时间片”或“quantum”。在上下文切换的过程中,内核会设置一些硬件计数器,它们会在时间片的末尾产生中断。当中断发生时,内核可以切换到另一个进程,或者允许被中断的进程继续执行。操作系统中做决策的这一部分叫做“调度器”。
当进程被创建时,操作系统会为进程分配包含进程信息的数据结构,称为“进程控制块”(PCB)。在其它方面,PCB跟踪进程的状态,这包括:
下面是一些可导致进程状态转换的事件:
fork
的系统调用时诞生。在系统调用的末尾,新的进程通常就绪。之后调度器可能恢复原有的进程(“父进程”),或者启动新的进程(“子进程”)。exit
时,中断处理器在PCB中储存退出代码,并将进程的状态变为终止。就像我们在2.3节中看到的那样,一台计算机上可能运行着成百上千条进程,但是通常大多数进程都是阻塞的。大多数情况下,只有一小部分进程是就绪或者运行的。当中断发生时,调度器会决定那个进程应启动或恢复。
在工作站或笔记本上,调度器的首要目标就是最小化响应时间,也就是说,计算机应该快速响应用户的操作。响应时间在服务器上也很重要,但是调度器同时也可能尝试最大化吞吐量,它是单位时间内所完成的请求。
调度器通常不需要关于进程所做事情的大量信息,所以它基于一些启发来做决策:
大多数调度器使用一些基于优先级的调度形式,其中每个进程都有可以调上或调下的优先级。当调度器运行时,它会选择最高优先级的就绪进程。
下面是决定进程优先级的一些因素:
nice
允许进程降低(但不能升高)自己的优先级,并允许程序员向调度器传递显式的信息。对于运行普通工作负载的多数系统,调度算法对性能并没有显著的影响。简单的调度策略就足够好了。
但是,对于与真实世界交互的程序,调度非常重要。例如,从传感器和控制马达读取数据的程序,可能需要以最小的频率完成重复的任务,并且以最大的响应时间对外界事件做出反应。这些需求通常表述为必须在“截止期限”之前完成的“任务”。
调度满足截止期限的任务叫做“实时调度”。对于一些应用,类似于Linux的通用操作系统可以被修改来处理实时调度。这些修改可能包括:
对于更苛刻的应用,尤其是实时响应是生死攸关的领域,“实时操作系统”提供了专用能力,通常比通用操作系统拥有更简单的设计。