本节书摘来自异步社区《现代体系结构上的UNIX系统:内核程序员的对称多处理和缓存技术(修订版)》一书中的第1章,第1.2节,作者:【美】Curt Schimmel著,更多章节内容可以访问云栖社区“异步社区”公众号查看
1.2 进程、程序和线程
程序(program)被定义为执行某项任务所需的指令和数据集。进程(process)则是程序加上其执行状态的组合,进程最少要包括所有变量的值、硬件状态(如程序计数器(PC)、寄存器、条件码等),以及地址空间的内容说明。简而言之,一个进程就是一个执行中的程序。
当一个用户请求运行某个程序的时候,系统就会创建一个新进程来包含该程序的执行。直到进程终止之前,它都存在于系统中,最后它不是自愿终止就是内核使它终止,要么就是用户请求它终止。进程还可以在一定程度上通过那些影响诸如进程的调度优先级的系统调用进行控制。
通过进程的抽象概念,内核就让程序有了它自己是在硬件上运行的假象。除非用户程序明确想要和系统中的其他程序以某种方式进行通信(有几种服务可以来完成这个任务),否则它们不需要关心自己与那些程序的交互作用。每个进程都获得了各自的虚拟地址空间(virtual address space),并且(在大多数实现上)按时间片来运行,于是多个进程可以共享硬件。系统上其他进程的存在性对于该用户程序是透明的。这使得开发新程序的工作更容易,也有助于确保程序的可移植性。
许多现代的UNIX系统提供了一种称为线程(thread)的机制。线程掌握了一个进程内一条执行流的状态。一个线程的状态最少要由硬件状态,往往还有一个栈构成。所有的UNIX进程内部都至少有一个控制线程(control thread),这个控制线程代表了程序的执行。对于所有的UNIX系统,无论是过去的还是现在的系统都是如此。支持线程的系统允许在一个进程内同时有多个控制线程。在这种情况下,每个线程都有其自己的硬件状态,但是所有的线程都在同一个地址空间中执行。在单处理器上,一次只能执行一个线程。在多处理器上,一个进程中的不同线程可以同时在不同的处理器上执行。线程的优点之一就是创建线程的开销要比创建进程的开销小,在一个进程内实现一组协调工作的线程要比实现一组协调工作的独立进程效率更高。一般而言,在进程内部执行的线程数量对于本书所涵盖的主题没有影响。因此,后面章节中只提及进程,它暗含了在进程内部执行的所有线程。
除了几处例外(下面会详细说明),所有的程序,不论是在用户级上还是在内核级上执行的,都出现在某个进程的上下文(context)内(大多数传统的UNIX内核实现都是如此,但是对于专门的实现则可能不同)。所有的用户程序都在它们自己的进程的上下文中运行。当这些用户进程通过系统调用请求内核服务的时候,实现该系统调用的内核代码继续在请求进程的进程上下文内执行。这就能让内核方便地访问进程的所有状态及其地址空间。它还提供了一种代表用户程序记录内核执行的当前状态的方式。例如,如果需要挂起一次系统调用的执行来等待I/O操作完成,那么内核有关系统调用处理的状态就被保存在进程中。
由于所有的系统活动,无论是在用户级的还是在内核级的,都发生在某个进程的上下文中,因此UNIX内核只调度进程的执行。当使用传统的分时调度(time-sharing scheduling)策略的时候,在用户级执行的进程可能被划分成任意数量的时间片,从而让所有的进程公平地共享CPU。在内核级执行的进程则不会被划分时间片。只有在当前的内核进程明确允许的情况下,才能切换到在内核级执行的另一个进程。
“所有的系统活动都发生在进程内部”这一规则的一种例外情况就是中断处理程序(interrupt handler)的执行。中断是由I/O设备在它们有状态信息要返回给操作系统的时候产生的。例如,状态信息可能包括一次I/O操作完成的信息。由于中断总是在任意时刻没有警告就发生了,所以它们对于进程的执行来说是异步的。当它们发生的时候,UNIX内核允许它们中断当前进程的执行。接着,系统执行中断处理程序,直到该程序完成,或者它被一次优先级更高的中断所打断为止。内核级进程如果愿意,它们有权屏蔽中断。之所以这样做,仅仅是为了保护进程级代码和中断处理程序代码所共享的数据结构的完整性。
这一规则的第二种例外情况则伴随流(stream)服务过程而出现。来自AT&T的UNIX实现SVR3(System V Release 3)中引入了流机制,它提供了一种网络协议实现的框架。虽然详细讨论流超出了本书的范围,但是这里要说明一点是,出于性能方面的原因,服务过程是在任何进程的上下文之外运行的,就像中断处理程序一样。