本篇索引:
1、引言
2、终端登录
3、进程组
4、会话期
1、引言
通过上一篇的学习,我们已经知道了如何控制一个进程,fork函数从父进程中复制出子进程,我们可以通过exec函数让子进程运行新的程序,进程可以通过调用exit系列函数(return,_exit)终止,父进程可以利用wait或waitpid函数等待某个进程的终止状态。
在操作系统上同时运行着很多的进程,那么这些进程之间有没有什么什么关系呢?答案是肯定的,最起码这些进程之间是有父子关系的,当然只这里要除掉0、1、2这三个进程。
那么本篇的主要内容就是进一步讨论这些进程之间的相互关系。
2、终端登录
大家现在登陆一个linux操作系统已经熟练了,只要输入用户名和密码即可,但是对于这个的登陆的过程就不是很熟悉了,我们通过RS-232终端登陆的过程来窥见下这个具体的登陆过程是怎样的。
3、进程组
进程组是一个进程或多个进程的集合,每个进程还属于某个唯一的进程组,组ID就是进程组组长的进程ID,getpgrp函数可以返回当前进程所在进程组的组ID。
3.1、getpgrp函数
1)、函数原型和所需头文件
#include <unistd.h>
pid_t getpgrp(void);
2)、函数功能:获取调用该函数的当前进程所在进程组的组ID。
3)、函数参数:无参数。
4)、函数返回值:成功,返回当前进程所在进程组的组ID,失败返回-1,errno被设置。
5)、注意:
a)、即使是组长死了,但只要该组中还有一个组员进程正在运行,那么进程组依然存在。
B)、从进程组创建到该组中最后一个进程结束的这段时间,我们称为进程组的生命期。
c)、从shell终端开始运行的第一个进程担任组长,其进程ID就是组ID,随后从该进程fork 出的所有的进程全部属于该组。
d)、当然除了组长进程外任何一个组员都能够通过调用setpgid函数自成一组或则加入其 它组。
6)、测试用例
test.c
#include <stdio.h> #include <unistd.h> int main(void) { int ret = 0; ret = fork(); if(0 == ret) //原始父进程的第一个子进程 { ret = fork(); if(0 == ret){ printf("pgrp = %d\n", getpgrp()); } else if(ret > 0){ printf("pgrp = %d\n", getpgrp()); } } else if(ret > 0){ ret = fork(); //原始父进程的第二个子进程 if(0 == ret){ printf("pgrp = %d\n", getpgrp()); } else if(ret > 0){ //原始父进程 sleep(1);//让所有子进程先运行结束 printf("pgrp = %d\n", getpgrp()); } } return 0; } return 0; }
上例运行结果如下:
pgrp = 4163
pgrp = 4163
pgrp = 4163
pgrp = 4163
可以看出所有进程都同属于一个组,组长由原始父进程担任,从原始父进程复制出来所有的进程都属于该组。
3.2、setpgid函数
1)、函数原型和所需头文件
#include <unistd.h>
pid_t getpgid(pid_t pid, pid_t pgid);
2)、函数功能:调用该函数以指定某个进程参加一个现存的组或者创建一个新的进程组。
3)、函数参数
pid_t pid:指定需要被修改为或设置到新进程组的进程。
pid_t pgid:进程组ID,指定某个进程组。
4)、函数返回值:成功返回进程组的组ID,失败返回-1,errno被设置。
5)、注意:
这个函数的功能是将pid进程的进程组修改为pgid对应的进程组。但是需要注意如下特殊情况。
1、当这两个参数相等时,则将pid对应的进程直接的变为进程组组长。
2、pid填0的话,pid默认使用调用者的进程ID。
3、Pgid填0的话,pgid默认使用pid指定的进程ID为进程组ID。
4、一个进程只能为它自己或者子进程设置进程组ID,但是子进程一旦exec之后就不能 再改变它的进程组ID了。
6)、例子
a)、在shell命令行执行了新的程序之时,shell会fork出子进程以运行新的程序,shell 父进程会调用该函数将子进程设置为新的进程组组长,子进程也会调用该函数将自己设 置为新的进程组组长,这两个操作有一个冗余的,但是这能够保证无论父子进程谁先运 行,子进程都能被设置为新的进程组组长,否者的话就会产生一个竟态,结果就会依赖 于哪一个进程先运行,之后从该子进程复制出的所有的子进程都属于该进程组。
b)、测试用例
#include <stdio.h> #include <unistd.h> int main(void) { int ret = 0; ret = fork(); if(0 == ret){ //原始父进程的第一个子进程 setpgid(getpid(), getpid()); printf("in chaild pgrp = %d\n", getpgrp()); } else if(ret > 0){ //原始父进程,有shell父进程复制而来 sleep(1); printf("in pareant pgrp = %d\n", getpgrp()); } return 0; }
程序运行结果如下:
in child pgrp = 7930
in pareant pgrp = 7929
本来复制出来的子进程和原始父进程同属于一个进程组,原始父进程亲自担任组长,但是子进程由于调用setpgid(getpid(), getpid());将自己变成了一个新的进程组,自己亲自担任组长,所以我们看到这里的父子进程各自都属于自己的一组,所以此后从子进程复制出的子进程就会属于新的一组,父进程父子出的子进程属于父进程这一组。
4、会话期
我们知道一个或多个进程组合在一起就组成为了一个进程组,但当多个进程组被组合在一起的集合,这个集合就被称为会话期,如下图所示,该会话期中有三个进程组,进程组之间由shell管道线连接组成。
4.1、setsid函数
1)、函数原型和所需头文件
#include <unistd.h>
pid_t setsid(void);
2)、函数功能:将调用该函数的进程变成一个新的会话期,但是进程组组长不能被设置为一 个新的会话期。
3)、函数参数:无参数。
4)、函数返回值:成功,返回新的会话期ID,失败返回-1,errno被设置。
5)、注意
·进程组中担任组长的进程不能被设置为新的会话期,组长调用了该函数后会出错返回, 为了避免这种情况的发生,通常先fork出子进程,然后使其父进程终止,子进程继续运 行,新的子进程与父进程同属于一个进程组,但是子进程不可能是进程组组长。
·创建新的会话期的进程就是会话期的首进程,会话期ID就是该进程的进程ID。
·会话期首进程也成为了会话期中第一个进程组中的组长。
·此进程没有控制进终端,如果在调用setsid函数之前此进程有一个控制终端,那么这 种关系也会被解除了。
6)、测试用例
5、控制终端
5.1、会话期的特点
1)、一个会话期可以有一个控制终端,这个通常是我们登录的终端设备或伪终端设备。
2)、建立与控制终端连接的对话器首进程被称为控制进程。
3)、一个会话期中的多个进程组被分为一个前台进程组和一个或多个后台进程组。
4)、一个会话期可以有0个或1个前台进程组,但是至少要有一个后台进程组。
5)、如果会话期有控制终端的话,一定有一个前台进程组,前台进程组有与控制终端交互的 权利,其它所有的进程组的都是后台进程组。
6)、无论何时按下中断键(ctrl+c或ctrl+\),其参生的SIGINT或SIGQUIT信号被发送前台 进程组中的所有进程。
7)、如果中断界面已经检测到调制解调器已经脱离或则终端已经断开,则挂断信号会被 送至控制进程。
例子说明:
1、当我们打开一个终端时,这个终端本身就是一个运行的进程,该进程创建了一个新的会话期, 该shell进程就是创建此会话期的首进程,也是控制进程,这个会话期中就只有一个进程组,就 是会话期首进程所在的组,也是前台进程组,这时我们能够直接输入shell命令到shell终端,因 为前台进程组拥有与控制终端交互的权利。
2、当我们./a.out在该shell终端上运行新的程序时,不加&,不把程序放到后台运行的情况下, 该程序的第一个进程独自成立一组自己亲自担任组长,它所复制出来的进程均属于这一进程组, 该进程组就变为了前台进程组,我们拥有与前台该进程组的组长进程的终端交互权,如果我们将 新程序放到后台运行的话(如./a.out &)。新运行的程序创建出的新的一组属于后台进程组,shell 终端命令行仍然控制着终端交互权。
3、从新运行的程序所在进程组中分列出的所有的进程组全部都将变为后台进程组。