APUE第9章 进程关系、进程组、会话

9 进程关系

4064394-588376d922c00527.png
image.png
4064394-65bdf93184829e7f.png
image.png

在第8章学习了进程的控制原语,通过各种进程原语可以对进程进行控制,包括新建进程、执行新程序、终止进程等。在使用fork( )产生新进程后,就出现了进程父子进程的概念,这是进程间的关系。本章更加详细地说明进程间的关系,包括:进程组、会话、作业等。

9.1 终端登录

当我们通过硬件终端而非网络终端登录到系统时,UNIX会有一个登录流程,该流程是个大概的过程,各个实现可能存在细微差别,但总体流程不变。

在通过终端登录时,init进程负责为每个终端fork一个子进程,由子进程对应登录终端设备。init进程会fork一份副本init,然后exec加载一个程序,该程序负责打开终端设备,后续用户通过该终端设备来与系统交互。当exec加载一个程序并打开终端之后,该程序提示用户输入用户名,之后再exec或者fork子进程来加载login程序进行登录,login程序负责验证密码,并通过读取配置文件来初始化用户终端环境。

9.2 网络登录

网络登录和终端不太相同,网络登录的需求衍生了伪终端,关于通过伪终端的登录将会在第19章说明。

9.3 进程组

每个进程都有一个进程ID。另外,每个进程都属于一个进程组。进程组是一个进程或多个进程的集合。和进程ID类似,每个进程组都有一个进程组ID,进程组ID等于组长进程ID,然而组长进程不一定会一直存在,组长进程可能在创建进程组之后结束,但组长进程结束之后,进程组依旧存在,此时没了组长。进程组的生命周期是从进程组创建开始,一直到最后一个组员离开。最后一个组员离开的方式有两种:一是组员结束死亡,二是组员脱离进程组,加入到它人的进程组。

UNIX提供了两个接口可以用于返回进程组的ID,还提供了一个创建或者加入别的进程组的接口,其头文件及函数原型如下:

#include <unistd.h>

pid_t getpgrp (void);

pid_t getpgid (pid_t pid);

int setpgid (pid_t pid, pid_t pgid);

对于第一个函数没有出错返回,其返回值是调用进程的进程组ID;

对于第二个函数,当参数为0时,成功则返回进程组ID,出错则返回-1。

对于第三个函数,成功时返回0,出错返回-1。

一个进程只能为自己或者它的子进程设置进程组ID,但子进程调用exec之后,执行了新程序,父进程就不能再为子进程设置进程组ID,因为exec执行的新程序可能与父进程再无任何关系。

9.4 会话

会话
会话是一个或多个进程组的集合。通常是有shell将多个进程组成一个会话。进程也可以通过UNIX提供的接口来主动创建一个新会话。其头文件及函数原型如下:

#include <unistd.h>

pid_t setsid (void);

该函数成功是返回0,失败返回-1。

调用该函数的进程不能是一个进程组的组长,否则会失败。如果不是进程组组长,则该调用会创建一个新会话,并且调用进程称为会话leader,同时该调用会成为一个创建一个新进程组,并成为该新进程组的组长,最后该进程的会失去终端。

一个会话最多只有一个控制终端,也允许没有终端。一个会话中的进程组可以被分为前台进程组以及一或多个后台进程组。如果会话有控制终端,那么该会话中就分为前台进程和后台进程,对终端的一些操作而引发的信号会发送到前端进程。

9.6控制终端

一个会话最多只有一个控制终端,也允许没有终端。一个会话中的进程组可以被分为前台进程组以及一或多个后台进程组。如果会话有控制终端,那么该会话中就分为前台进程和后台进程,对终端的一些操作而引发的信号会发送到前端进程。

会话和进程组还有一些其他特性。
•一个会话可以有一个控制终端(controlling terminal)。这通常是终 端设备(在终端登录情况下)或伪终端设备(在网络登录情况下)。
•建立与控制终端连接的会话首进程被称为控制进程(controlling process)。
•一个会话中的几个进程组可被分成一个前台进程组(foreground process group)以及一个或多个后台进程组(background process group)。
•如果一个会话有一个控制终端,则它有一个前台进程组,其他进 程组为后台进程组。
•无论何时键入终端的中断键(常常是Delete或Ctrl+C),都会将中 断信号发送至前台进程组的所有进程。
• 无论何时键入终端的退出键(常常是Ctrl+),都会将退出信号 发送至前台进程组的所有进程。
• 如果终端接口检测到调制解调器(或网络)已经断开连接,则将 挂断信号发送至控制进程(会话首进程)。

4064394-588376d922c00527.png
image.png

9.7 函数tcgetpgrp、tcsetpgrp和tcgetsid

需要有一种方法来通知内核哪一个进程组是前台进程组,这样,终 端设备驱动程序就能知道将终端输入和终端产生的信号发送到何处(见 图9-7)。

#include <unistd.h>
pid_t tcgetpgrp(int fd);
int tcsetpgrp(int fd, pid_t pgrpid);

返回值:若成功,返回前台进程组ID;若出错,返回−1 返回值:若成功,返回0;若出错,返回−1
函数tcgetpgrp返回前台进程组ID,它与在fd上打开的终端相关联。
如果进程有一个控制终端,则该进程可以调用tcsetpgrp将前台进程 组ID设置为pgrpid。pgrpid值应当是在同一会话中的一个进程组的ID。fd 必须引用该会话的控制终端。

给出控制TTY的文件描述符,通过tcgetsid函数,应用程序就能获得 会话首进程的进程组ID。

#include <termios.h>
 pid_t tcgetsid(int fd);

返回值:若成功,返回会话首进程的进程组ID;若出错,返回−1 需要管理控制终端的应用程序可以调用 tcgetsid 函数识别出控制终
端的会话首进程的会话ID(它等价于会话首进程的进程组ID)。

9.8 作业控制

作业控制是BSD在1980年左右增加的一个特性。它允许在一个终端 上启动多个作业(进程组),它控制哪一个作业可以访问该终端以及哪 些作业在后台运行。作业控制要求以下3种形式的支持。
(1)支持作业控制的shell。 (2)内核中的终端驱动程序必须支持作业控制。 (3)内核必须提供对某些作业控制信号的支持。 SVR3提供了一种不同的作业控制,称为shell层(shell layer)。
但是 POSIX.1选择了BSD形式的作业控制,这也是我们在这里所说明 的。POSIX.1 的早期版本中,对作业控制的支持是可选择的,现在则要 求所有平台都支持它。
从shell使用作业控制功能的角度观察,用户可以在前台或后台启动 一个作业。一个作业只是几个进程的集合,通常是一个进程管道。例 如:
vi main.c
在前台启动了只有一个进程组成的作业。下面的命令:
pr *.c | lpr &
make all & 在后台启动了两个作业。这两个后台作业调用的所有进程都在后台运行。

我们可以键入一个影响前台作业的特殊字符——挂起键(通常采用 Ctrl+Z),与终端驱动程序进行交互作用。键入此字符使终端驱动程序 将信号SIGTSTP发送至前台进程组中的所有进程,后台进程组作业则不 受影响。实际上有3个特殊字符可使终端驱动程序产生信号,并将它们 发送至前台进程组,它们是:

•中断字符(一般采用Delete或Ctrl+C)产生SIGINT;
•退出字符(一般采用Ctrl+\)产生SIGQUIT; 
•挂起字符(一般采用Ctrl+Z)产生SIGTSTP。

第 18 章中将说明可将这 3 个字符更改为用户选择的任意其他字
符,以及如何使终端驱动程序不处理这些特殊字符。 终端驱动程序必须处理与作业控制有关的另一种情况。我们可以有
一个前台作业,若干个后台作业,这些作业中哪一个接收我们在终端上 键入的字符呢?只有前台作业接收终端输入。如果后台作业试图读终 端,这并不是一个错误,但是终端驱动程序将检测这种情况,并且向后 台作业发送一个特定信号SIGTTIN。该信号通常会停止此后台作业,而 shell则向有关用户发出这种情况的通知,然后用户就可用shell命令将此 作业转为前台作业运行,于是它就可读终端。

4064394-e48fc98b3c2c374b.png
image.png

9.9 shell

让我们检验一下shell是如何执行程序的,以及这与进程组、控制终 端和会话等概念的关系。为此,再次使用ps命令。
首先使用不支持作业控制的、在Solaris上运行的经典Bourne shell。 如果执行:
ps -o pid,ppid,pgid,sid,comm

ps的父进程是shell,这正是我们所期望的。shell和ps命令两者位于
同一会话和前台进程组(949)中。因为我们是用一个不支持作业控制 的shell执行命令时得到该值的,所以称其为前台进程组。
某些平台支持一个选项,它使 ps(1)命令打印与会话控制终端相关 联的进程组 ID。该值在TPGID列中显示。遗憾的是,ps(1)命令的输出 在各个UNIX版本中都有所不同。例如,Solaris 10不支持该选项。在 FreeBSD 8.0、Linux 3.2.0和Mac OS X 10.6.8中,命令
ps -o pid, ppid, pgid, sid, tpgid, comm 准确地打印我们想要的信息。


4064394-f6cc33a9d30ef4b8.png
image.png
4064394-555c938bdc1bc8ae.png
image.png

9.10 孤儿进程组

我们曾提及,一个其父进程已终止的进程称为孤儿进程(orphan process),这种进程由init进程“收养”。现在我们要说明整个进程组也 可成为“孤儿”,以及POSIX.1如何处理它。
实例
考虑一个进程,它fork了一个子进程然后终止。这在系统中是经常 发生的,并无异常之处,但是在父进程终止时,如果该子进程停止(用 作业控制)又将如何呢?子进程如何继续,以及子进程是否知道它已经 是孤儿进程?图9-11显示了这种情形:父进程已经fork了子进程,该子 进程停止,父进程则将退出。
构成此种情形的程序示于图9-12中。下面要说明该程序的某些新特 性。这里,假定使用了一个作业控制 shell。回忆前面所述,shell 将前台 进程放在它(指前台进程)自已的进程组中(本例中是6099),shell则 留在自己的进程组内(2837)。子进程继承其父进程(6099)的进程 组。在fork之后:

4064394-d7aa46b712a7fce3.png
image.png

•父进程睡眠5秒,这是一种让子进程在父进程终止之前运行的一种 权宜之计。
•子进程为挂断信号(SIGHUP)建立信号处理程序。这样就能观察 到SIGHUP信号是否已发送给子进程。(第10章将讨论信号处理程 序。)
•子进程用kill函数向其自身发送停止信号(SIGTSTP)。这将停止 子进程,类似于用终端挂起字符(Ctrl+Z)停止一个前台作业。
•当父进程终止时,该子进程成为孤儿进程,所以其父进程ID成为 1,也就是init进程ID。
•现在,子进程成为一个孤儿进程组的成员。POSIX.1将孤儿进程组 (orphaned process group)定义为:该组中每个成员的父进程要么是该
组的一个成员,要么不是该组所属会话的成员。对孤儿进程组的另一种 描述可以是:一个进程组不是孤儿进程组的条件是——该组中有一个进 程,其父进程在属于同一会话的另一个组中。如果进程组不是孤儿进程 组,那么在属于同一会话的另一个组中的父进程就有机会重新启动该组 中停止的进程。在这里,进程组中每一个进程的父进程(例如,进程 6100的父进程是进程1)都属于另一个会话。所以此进程组是孤儿进程 组。

9.11 FreeBSD实现

前面说明了进程、进程组、会话和控制终端的各种属性,值得观察 一下所有这些是如何实现的。下面简要说明FreeBSD中的实现。SVR4实 现的某些详细情况则请参阅Williams[1989]。图9-13显示了FreeBSD使用的 各种有关数据结构。
下面从session结构开始说明图中标出的各个字段。每个会话都分配 一个session结构(例如,每次调用setsid时)。
•s_count是该会话中的进程组数。当此计数器减至0时,则可释放此 结构。
•s_leader是指向会话首进程proc结构的指针。 •s_ttyvp是指向控制终端vnode结构的指针。 •s_ttyp是指向控制终端tty结构的指针。 •s_sid是会话ID。请记住会话ID这一概念并非Single UNIX
Specification的组成部分。 在调用setsid时,在内核中分配一个新的session结构。s_count设置为
1,s_leader设置为调用进程 proc 结构的指针,s_sid 设置为进程 ID,因 为新会话没有控制终端,所以s_ttyvp和s_ttyp设置为空指针。
接着说明 tty 结构。每个终端设备和每个伪终端设备均在内核中分 配这样一种结构(第 19章将对伪终端做更多说明)。
•t_session指向将此终端作为控制终端的session结构(注意,tty结构 指向session结构,session结构也指向tty结构)。终端在失去载波信号时 使用此指针将挂起信号发送给会话首进程(见图9-7)。

4064394-a4f384d0125459c5.png
image.png

• t_pgrp指向前台进程组的pgrp结构。终端驱动程序用此字段将信号 发送给前台进程组。由输入特殊字符(中断、退出和挂起)而产生的3 个信号被发送至前台进程组。
• t_termios是包含所有这些特殊字符和与该终端有关信息(如波特 率、回显打开或关闭等)的结构。第18章将再说明此结构。
• t_winsize是包含终端窗口当前大小的winsize型结构。当终端窗口大 小改变时,信号SIGWINCH被发送至前台进程组。18.12节将说明如何设 置和获取终端当前窗口大小。
为了找到特定会话的前台进程组,内核从session结构开始,然后用
s_ttyp得到控制终端的tty结构,再用t_pgrp得到前台进程组的pgrp结构。 pgrp结构包含一个特定进程组的信息。其中各相关字段具体如下。 •pg_id是进程组ID。 •pg_session指向此进程组所属会话的session结构。
• pg_members 是指向此进程组proc 结构表的指针,该 proc 结构代表 进程组的成员。proc结构中p_pglist结构是双向链表,指向该组中的下一 个进程和上一个进程。直到遇到进程组中的最后一个进程,它的proc结 构中p_pglist结构为空指针。
proc结构包含一个进程的所有信息。
•p_pid包含进程ID。
•p_pptr是指向父进程proc结构的指针。 •p_pgrp指向本进程所属的进程组的pgrp结构的指针。 •p_pglist是一个结构,其中包含两个指针,分别指向进程组中上一
个和下一个进程。 最后还有一个vnode结构。如前所述,在打开控制终端设备时分配
此结构。进程对/dev/tty的所有访问都通过vnode结构。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值