当前位置:嗨网首页>书籍在线阅读

09-进程

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

2.7 进程

简而言之,进程是正在执行的程序实例。执行程序时,内核会将程序代码载入虚拟内存,为程序变量分配空间,建立内核记账(bookkeeping)数据结构,以记录与进程有关的各种信息(比如,进程ID、用户ID、组ID以及终止状态等)。

在内核看来,进程是一个个实体,内核必须在它们之间共享各种计算机资源。对于像内存这样的受限资源来说,内核一开始会为进程分配一定数量的资源,并在进程的生命周期内,统筹该进程和整个系统对资源的需求,对这一分配进行调整。程序终止时,内核会释放所有此类资源,供其他进程重新使用。其他资源(如CPU、网络带宽等)都属于可再生资源,但必须在所有进程间平等共享。

进程的内存布局

逻辑上将一个进程划分为以下几部分(也称为段)。

  • 文本:程序的指令。
  • 数据:程序使用的静态变量。
  • 堆:程序可从该区域动态分配额外内存。
  • 栈:随函数调用、返回而增减的一片内存,用于为局部变量和函数调用链接信息分配存储空间。

创建进程和执行程序

进程可使用系统调用fork()来创建一个新进程。调用fork()的进程被称为父进程,新创建的进程则被称为子进程。内核通过对父进程的复制来创建子进程。子进程从父进程处继承数据段、栈段以及堆段的副本后,可以修改这些内容,不会影响父进程的“原版”内容。(在内存中被标记为只读的程序文本段则由父、子进程共享。)

然后,子进程要么去执行与父进程共享代码段中的另一组不同函数,或者,更为常见的情况是使用系统调用execve()去加载并执行一个全新程序。execve()会销毁现有的文本段、数据段、栈段及堆段,并根据新程序的代码,创建新段来替换它们。

以execve()为基础,C语言库还提供了几个相关函数,接口虽然略有不同,但功能全都相同。以上所有库函数的名称均以字符串“exec”打头,在函数间差异无关宏旨的场合,本书会用符号exec()作为这些库函数的统称。不过,请读者牢记,实际上根本不存在名为exec()的库函数。

一般情况下,书中会使用“执行”一词来指代execve()及其衍生函数所实施的操作。

进程ID和父进程ID

每一进程都有一个唯一的整数型进程标识符(PID)。此外,每一进程还具有一个父进程标识符(PPID)属性,用以标识请求内核创建自己的进程。

进程终止和终止状态

可使用以下两种方式之一来终止一个进程:其一,进程可使用_exit()系统调用(或相关的exit()库函数),请求退出;其二,向进程传递信号,将其“杀死”。无论以何种方式退出,进程都会生成“终止状态”,一个非负小整数,可供父进程的wait()系统调用检测。在调用_exit()的情况下,进程会指明自己的终止状态。若由信号来“杀死”进程,则会根据导致进程“死亡”的信号类型来设置进程的终止状态。(有时会将传递进_exit()的参数称为进程的“退出状态”,以示与终止状态有所不同,后者要么指传递给_exit()的参数值,要么表示“杀死”进程的信号。)

根据惯例,终止状态为0表示进程“功成身退”,非0则表示有错误发生。大多数shell会将前一执行程序的终止状态保存于shell变量$?中。

进程的用户和组标识符(凭证)

每个进程都有一组与之相关的用户ID (UID)和组ID (GID),如下所示。

  • 真实用户 ID 和组 ID:用来标识进程所属的用户和组。新进程从其父进程处继承这些ID。登录shell则会从系统密码文件的相应字段中获取其真实用户ID和组ID。
  • 有效用户ID和组ID:进程在访问受保护资源(比如,文件和进程间通信对象)时,会使用这两个 ID(并结合下述的补充组ID)来确定访问权限。一般情况下,进程的有效ID与相应的真实ID值相同。正如即将讨论的那样,改变进程的有效ID实为一种机制,可使进程具有其他用户或组的权限。
  • 补充组ID:用来标识进程所属的额外组。新进程从其父进程处继承补充组ID。登录shell则从系统组文件中获取其补充组ID。

特权进程

在UNIX系统上,就传统意义而言,特权进程是指有效用户ID为0(超级用户)的进程。通常由内核所施加的权限限制对此类进程无效。与之相反,术语“无特权”(或非特权)进程是指由其他用户运行的进程。此类进程的有效用户ID为非0值,且必须遵守由内核所强加的权限规则。

由某一特权进程创建的进程,也可以是特权进程。例如,一个由root(超级用户)发起的登录shell。成为特权进程的另一方法是利用set-user-ID机制,该机制允许某进程的有效用户ID等同于该进程所执行程序文件的用户ID。

能力(Capabilities)

始于内核2.2,Linux把传统上赋予超级用户的权限划分为一组相互独立的单元(称之为“能力”)。每次特权操作都与特定的能力相关,仅当进程具有特定能力时,才能执行相应操作。传统意义上的超级用户进程(有效用户ID为0)则相应开启了所有能力。

赋予某进程部分能力,使得其既能够执行某些特权级操作,又防止其执行其他特权级操作。

本书第39章会对能力做深入讨论。在本书后文中,当述及只能由特权进程执行的特殊操作时,一般都会在括号中标明其具体能力。能力的命名以CAP_为前缀,例如,CAP_KILL。

init进程

系统引导时,内核会创建一个名为init的特殊进程,即“所有进程之父”,该进程的相应程序文件为/sbin/init。系统的所有进程不是由init(使用 frok())“亲自”创建,就是由其后代进程创建。init进程的进程号总为1,且总是以超级用户权限运行。谁(哪怕是超级用户)都不能“杀死”init进程,只有关闭系统才能终止该进程。init 的主要任务是创建并监控系统运行所需的一系列进程。(手册页init(8)中包含了init进程的详细信息。)

守护进程

守护进程指的是具有特殊用途的进程,系统创建和处理此类进程的方式与其他进程相同,但以下特征是其所独有的:

  • “长生不老”。守护进程通常在系统引导时启动,直至系统关闭前,会一直“健在”。
  • 守护进程在后台运行,且无控制终端供其读取或写入数据。

守护进程中的例子有syslogd(在系统日志中记录消息)和httpd(利用HTTP分发Web页面)。

环境列表

每个进程都有一份环境列表,即在进程用户空间内存中维护的一组环境变量。这份列表的每一元素都由一个名称及其相关值组成。由fork()创建的新进程,会继承父进程的环境副本。这也为父子进程间通信提供了一种机制。当进程调用exec()替换当前正在运行的程序时,新程序要么继承老程序的环境,要么在exec()调用的参数中指定新环境并加以接收。

在绝大多数shell中,可使用export命令来创建环境变量(C shell使用setenv命令),如下所示:

4.png

本书在展示交互式输入、输出的shell会话日志时,总是以黑体字来呈现输入文本。有时也会在日志中以斜体字形式加注,以解释输入的命令和产生的输出。

C语言程序可使用外部变量(char **environ)来访问环境,而库函数也允许进程去获取或修改自己环境中的值。

环境变量的用途多种多样。例如,shell定义并使用了一系列变量,供shell执行的脚本和程序访问。其中包括:变量HOME(明确定义了用户登录目录的路径名)、变量PATH(指明了用户输入命令后,shell查找与之相应程序时所搜索的目录列表)。

资源限制

每个进程都会消耗诸如打开文件、内存以及CPU时间之类的资源。使用系统调用setrlimit(),进程可为自己消耗的各类资源设定一个上限。此类资源限制的每一项均有两个相关值:软限制(soft limit)限制了进程可以消耗的资源总量,硬限制(hard limit)软限制的调整上限。非特权进程在针对特定资源调整软限制值时,可将其设置为0到相应硬限制值之间的任意值,但硬限制值则只能调低,不能调高。

由fork()创建的新进程,会继承其父进程对资源限制的设置。

使用ulimit命令(在C shell中为limit)可调整shell的资源限制。shell为执行命令所创建的子进程会继承上述资源设置。