说完了进程,我们来说一下进程间关系。
进程组
一个或者多个进程的集合,每一个进程除了有一个进程ID之外,它还属于一个进程组。通常来说,进程组和作业相关联,可以来接收同一个终端下的各种信号,每一个进程组也有对应的唯一进程组ID。
这一列PGID就是进程组,我们可以看到上面那三个mydaemon的守护进程所在不同进程组。这里下面的带[]的都是内核进程,可以看到它们都在同一进程组。
组长进程:组长进程就是进程组id==进程id。
组长进程可以创建一个进程组,创建该进程组当中的进程,另外只要一个进程组当中有一个进程存在,进程组就存在。与组长进程的存在与否无关。所以一个进程组的周期就是从创建到最后一个进程终止。
会话
会话是一个或多个进程组的集合。
一个会话有一个控制终端,简历与控制终端相连接的会话首进程叫做控制进程。一个会话当中分为一个前台进程组和多个后台进程组。内核通常发信号给前台进程组的所有进程。
会话的意义在于将多个工作囊括在一个终端,并且取其中的一个工作作为前台,来直接接受该终端的输入输出以及终端信号。其他的工作在后台运行。
建立新会话,有一个函数setsid
这个函数调用进程如果不是一个进程组组长进程,则这个函数会干三件事:
1. 该进程变为新会话的首进程,它成为新会话的唯一的进程。
2. 该进程成为一个新进程组的组长进程。新进程组ID是该调用进程的进程ID。
3. 该进程没有控制终端,如果之前有,那么被中断。
看上面刚才看过的那副图,新标注的那一栏SID就是会话。
控制终端
在前面的叙述中我们提到一个概念叫做控制终端,当会话的手进程打开第一个尚未与一个会话相关联的终端设备是,系统将此作为控制终端分配给此会话。
在UNIX系统中,用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端(Controlling Terminal),控制终端是保存在PCB中的信息,而我们知道fork会复制PCB中的信息,因此由Shell进程启动的其它进程的控制终端也是这个终端。默认情况下(没有重定向),每个进程的标准输入、标准输出和标准错误输出都指向控制终端,进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写也就是输出到显示器上。在控制终端输入一些特殊的控制键可以给前台进程发信号,例如Ctrl-C表示SIGINT,Ctrl-\表示SIGQUIT。
每个进程都可以通过一个特殊的设备文件/dev/tty访问它的控制终端。事实上每个终端设备都对应一个不同的设备文件,/dev/tty提供了一个通用的接口,一个进程要访问它的控制终端既可以通过/dev/tty也可以通过该终端设备所对应的设备文件来访问。ttyname函数可以由文件描述符查出对应的文件名,该文件描述符必须指向一个终端设备而不能是任意文件。在Linux上的命令tty 也可以查看到当前的终端。
比如我们在图形界面下打开一个终端可能是/dev/pts/0, 第二个可能是/dev/pts/1 …(网络终端)
而切换到字符界面下可能是/dev/tty1 …(虚拟终端)
作业控制
事实上Shell分前后台来进行控制的不是进程而是作业。一个前台作业由多个进程组成,一个后台作业也可以由多个进程组成,Shell可以运行一个前台作业和多个后台作业。
作业也有作业号:
操作 | 含义 | 例子 |
---|---|---|
& | 在运行一个进程后面加上&,表示让该进程在后台进行运行。 | ./a.out & |
jobs | 查看作业 | jobs |
bg 作业号 | 切换作业到后台运行 | bg 1 |
fg 作业号 | 切换作业到前台运行 | fg 2 |
Ctrl+Z | 自动把进程切回后台,切换Shell回前台 | |
Ctrl+C | 作业终止,进程发信号给前台进程 |
终端的登录过程
最后我们来说一下终端的登录过程。
从gettty exec进程程序替换到login,然后再exec到bash,都是在一个initfork的子进程当中进行的。所以控制终端的进程是没有发生变化的。文件描述符 0、1、2也依然指向控制终端,当进行从bashfork的时候,这个时候会复制PCB信息给子进程,所以其他进程的0、1、2也就一开始默认固定了。