15 November 2010

这篇文章属于《恰同学少年》系列,写于2010年。

进程指的是装载进内存、处于可执行状态的程序,它不仅指指令代码和用户数据,还包括运行时的动态环境和资源。现在操作系统都十分重视进程的管理和调度。本文对Linux的进程管理和调度机制作简要的介绍。

提到进程还需要了解一下线程。线程可认为是在进程基础上的进一步抽象。可将进程分为线程集合和资源集合,一个进程内可能有多个线程(独立指令流)共享该进程的资源空间,当然,各个线程的计数器、寄存器、堆栈等也是独立的。一般把进程作为操作系统进行资源管理的最小单元,而线程则是程序执行的最小单元。对于Linux操作系统,从内核的角度并没有进程与线程的区分:线程在内核中也认为是进程,只不过是“轻量级”进程,且与其他(轻量级)进程共享某些资源。在编写多线程应用程序时,用户可能会创建新线程,Linux内核把用户创建的新线程作为一个轻量级进程对待,分别赋予不同的进程描述符;而把属于同一进程的线程用一个轻量级进程的集合组来表示,赋予其“group id”。总之,在Linux操作系统中,进程是内核管理和调度的实体。下面先说进程管理的一些详情,再着重介绍进程调度。

Linux内核使用进程描述符(结构体)来描述一个进程相关的所有信息。其中包括以下几个方面的信息:

  • 进程状态信息,包括进程当前的状态和退出时状态。如“可运行状态”表示该进程已准备就绪,可以有调度程序来调度执行,“中断等待状态”表示该进程需要一个硬件中断或某条件为真时才被唤醒进入“可运行状态”;此外还有“暂停”、“跟踪”、“不可交互等待”等多种状态,以及“僵死”(ZOMBIE)、“死亡”(DEAD)等退出状态。
  • 进程标识信息,主要是指明该进程的id,它所处的进程组,它所属的用户和用户组、文件系统等表明该进程身份的一些标识符。
  • 进程间关系信息,如子进程链表、兄弟进程链表等。在Linux系统中,除初始化进程init外,所有进程都是由父进程继承而来的,各进程之间有条理的进行组织。另外,还有可运行队列、任务队列等为实现调度而存在的一些组织关系。

除以上所列,进程描述符中还储存着进程的内核栈等资源和环境信息。

进程可以被创建,被调度运行,而最终都会被退出。因为一个操作系统主要是运行其他程序的,所以不难理解,Linux中的进程中除了idle进程(进程0,即什么也不干)和内核进程(即整个操作系统核心进程,只运行在内核态)外,大部分进程都是用户进程。Linux操作系统提供内核调用(即一些函数)来允许用户程序进行进程的创建和终止等操作。这些具体的函数不再展开。总之它们可以使得每个进程在需要的时候被创建,而最终都有一个合适的归宿。

以上对进程管理的介绍中已经提到了进程调度。进程调度可以说是一个操作系统运行效率的主要体现。因为CPU“在同一时间只能干一件事”,所以运行中的多个进程是分享CPU而不是独占。这就需要一种策略来协调某个时刻由哪个进程使用CPU。对CPU访问的协调和裁决过程就是所谓的进程调度(Scheduling)。好的进程调度可以创造一种所有进程都在同时运行的逼真假象。下面简要介绍Linux系统的进程调度机制。

进程调度追求的目标是高效率(即相同时间完成尽可能多的事)、强交互性(即要保证尽快响应用户)、保证公平(避免有些进程过度饥饿)等等。为实现这些目标,Linux采用“优先级”来量化表示进程的重要程度。优先级又分为静态优先级和动态优先级,后者又考虑了对运行时I/O消耗型进程的奖励和CPU消耗型进程的惩罚。调度策略根据各个进程的优先级信息为每个进程分配CPU的时间片,使它们有序的分享CPU时间。具体操作时,调度器会决定什么时候停止一个进程的运行以便其他进程得到运行的机会,这种对进程的强制挂起叫做抢占(preemption);而从一个进程转为另一个进程的执行称为进程切换(process switch)。调度器就是根据进程优先级和运行时的一些动态信息来执行进程抢占和切换,以实现其调度目标。在这个过程中,有许多复杂的、不断优化的算法,包括对优先级数组和运行队列的维护、时间片的计算、对进程交互性的判断,以及多个CPU时进行负载均衡等。由于进程调度的重要性,Linux内核的调度策略经过了几次更换和发展。目前最新被采纳的是CFS调度器,其中引入了模块化、完全公平调度、组调度等一些列特性。这里对其细节不再详述,可参考Linux内核的相关资料。

Linux作为开源的操作系统,其进程管理和调度在世界各地程序员的共同努力下变得非常优秀。本文只涉及基本知识,且仅限于个人的理解。