linux c编程:进程控制(四)进程关系

每一个进程除了有一个进程ID外,还属于一个进程组。  进程组是一个或多个进程的集合,通常情况下,他们是在同一作业中结合起来的,同一进程组的个进程接受来自同一终端的各种信号。 每一个进程组有一个唯一的进程ID。

组长进程

每个进程组都有一个组长进程,组长进程的进程组ID等于其进程ID。 进程组组长可以创建一个进程组,创建进程组中的进程然后种植。只要进程组中还有任意一个进程存在,那么这个进程组就存在。 从进程组的创建到最后一个进程离开的时间去成为进程组的生命周期

函数getpgrp返回调用进程的进程组ID

#include<unistd.h>

pid_t getpgrp(void);

下面的代码来验证下进程组

void pgroup_func(){

    pid_t pid;

    pid=fork();

    if (pid == 0){;

        printf("child id is:%d, the group id is :%d\n",getpid(),getpgrp());

        sleep(1);

    }

    else{

        printf("parent id is:%d,the group id is:%d\n",getpid(),getpgrp());

        sleep(1);

    }

}

从下面的运行结果可以看出,子进程和父进程的进程组ID是一样的。说明子进程和父进程同属一个进程组。父进程是这个进程组的组长。

那么如何修改进程组呢,setpgid函数将pid进程的进程组ID设置为pgid. 如果这两个参数相等,则由pid指定的进程变成进程组组长。如果pid0,则使用调用者的进程ID。如果pgid=0,则由pid指定的进程ID用做进程组ID。但是如果子进程一旦执行exec,父进程就无法调用setpgid函数来设置子进程的进程组ID

代码增加一个setpgid

void pgroup_func(){

    pid_t pid;

    pid=fork();

    if (pid == 0){

        setpgid(pid,0);

        printf("child id is:%d, the group id is :%d\n",getpid(),getpgrp());

        sleep(1);

    }

    else{

        printf("parent id is:%d,the group id is:%d\n",getpid(),getpgrp());

        sleep(1);

    }

}

可以看到父进程和子进程的进程组ID不一样了。

有了创建进程组的接口,新创建的进程组就不必继承父进程的进程组ID了。最常见的创建进程组的场景就是在shell中执行管道命令,代码如下:cmd1 | cmd2 | cmd3

下面用一个最简单的命令来说明,其进程之间的关系如下所示。

ps ax|grep nfsd 

ps进程和grep进程都是bash创建的子进程,两者通过管道协同完成一项工作,它们隶属于同一个进程组,其中ps进程是进程组的组长。

进程组的概念并不难理解,可以将人与人之间的关系做类比。一起工作的同事,自然比毫不相干的路人更加亲近。shell中协同工作的进程属于同一个进程组,就如同协同工作的人属于同一个部门一样。

引入了进程组的概念,可以更方便地管理这一组进程了。比如这项工作放弃了,不必向每个进程一一发送信号,可以直接将信号发送给进程组,进程组内的所有进程都会收到该信号。

前面提到过,子进程一旦执行exec,父进程就无法调用setpgid函数来设置子进程的进程组ID了,这条规则会影响shell的作业控制。出于保险的考虑,一般父进程在调用fork创建子进程后,会调用setpgid函数设置子进程的进程组ID,同时子进程也要调用setpgid函数来设置自身的进程组ID。这两次调用有一次是多余的,但是这样做能够保证无论是父进程先执行,还是子进程先执行,子进程一定已经进入了指定的进程组中。由于fork之后,父子进程的执行顺序是不确定的,因此如果不这样做,就会造成在一定的时间窗口内,无法确定子进程是否进入了相应的进程组。

会话

会话是一个或多个进程组的集合。进程调用setsid函数建立一个新会话。

如果调用此函数的进程不是一个进程组的组长,则此函数就会创建一个新会话,该进餐变成会话的首进程,然后该进程成为一个新进程组的组长进程,该进程没有控制终端。因为会话首进程是具有唯一进程ID的单个进程,所以可以将会话首进程的进程ID视为会话Id。

#include <unistd.h>

pid_t setsid(void);

pid_t getsid(pid_t pid);

来看下面的2个例子:

void session_func(){

    pid_t pid;

    pid=fork();

    if(pid == 0){

        printf("child id is:%d, the group id is:%d,the session id is:%d\n",getpid(),getpgrp(),getsid(pid));

    }

    else{

        printf("parent id is:%d,the group id is:%d,the session id is:%d\n",getpid(),getpgrp(),getsid(pid));

    }

}

在子进程中创建会话

void session_func(){

    pid_t pid;

    pid=fork();

    if(pid == 0){

        setsid();

        printf("child id is:%d, the group id is:%d,the session id is:%d\n",getpid(),getpgrp(),getsid(pid));

    }

    else{

        printf("parent id is:%d,the group id is:%d,the session id is:%d\n",getpid(),getpgrp(),getsid(pid));

    }

}

}

我们可以看到在子进程没用setsid函数建立一个会话之前,子进程是和父进程在同一会话里的,当子进程用setsid函数建立一个会话,会话的首进程ID就是子进程ID也就是会话ID。

 

一个会话可以有一个控制终端。这通常是登陆到其上的终端设备(在终端登陆情况下)或伪终端设备(在网络登陆情况下)。建立与控制终端连接的会话首进程被称为控制进程。

一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组。所以一个会话中,应该包括控制进程(会话首进程),一个前台进程组和任意后台进程组。

无论何时键入终端的终端键,都会将中断信号发送到前台进程组的所有进程

还是来看之前的例子,

void session_func(){

    pid_t pid;

    pid_t pid1;

pid=fork();

    pid1=tcgetpgrp(0);

    printf("The pid1 is %d\n",pid1);

    if(pid == 0){

        setsid();

        printf("child id is:%d, the group id is:%d,the session id is:%d\n",getpid(),getpgrp(),getsid(pid));

    }

    else{

        printf("parent id is:%d,the group id is:%d,the session id is:%d\n",getpid(),getpgrp(),getsid(pid));

    }

}

tcgetpgrp的原型如下,通过终端的文件描述符fd返回前台进程组pid.

#include<unistd.h>

pid_t tcgetpgrp(int fd)

通过tcgetpgrp(0)得到终端1pid,可以看到和父进程的pid是一样的。

猜你喜欢

转载自www.cnblogs.com/zhanghongfeng/p/9090820.html