- 每个进程除了有一进程ID之外,还属于一个进程组
- 进程组是一个或多个进程的集合
- 进程组中的进程在同一作业中结合起来,同一进程组的各进程接受来自同一终端的各种信号
- 进程组周期:从进程组创建开始到其中最后一个进程离开为止的时间区间称为进程组的生命期。某个进程组中的最后一个进程可以终 止,也可以参加另一个进程组
- 每个进程组有一个组长进程。组长进程的进程组ID等于其进程ID
- 进程组组长可以创建一个进程组,创建该组中的进程,然后终止
- 只要在某个进程组中有 一个进程存在,则该进程组就存在,这与其组长进程是否终止无关
- 进程组组长可以使用setpgid函数创建
- 每个进程组有一个唯一的进程组ID。进程组ID是一个正整数,用pid_t数据类型表示
getpgrp函数
- 功能:返回调用该函数的进程的进程组ID
#include <unistd.h> pid_t getpgrp(void); //返回值:调用进程的进程组ID
getpgid函数
- 功能:返回参数pid进程的进程组ID
- 如果参数为0,则返回调用此函数的进程组ID。此时getpgid(0)函数等同于getpgrp()函数
#include <unistd.h> //返回值:成功返回进程组ID;出错返回-1
四、进程组的创建、更改函数(setpgid)演示案例
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { pid_t pid; printf("start fork......\n"); if((pid=fork())<0){ perror("fork"); exit(1); }else if(pid==0){ printf("I am child,My pgid=%d\n",getpgrp()); }else{ sleep(1); printf("I am father,My pid=%d,My pgid=%d\n",getpid(),getpgrp()); } exit(0); }
如果想使用getpgid函数,可以更改下面部分代码
#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);
//返回值:成功返回0;出错返回-1
- 功能:将参数pid进程的进程组ID设置为pgid(将pid加入到pgid进程组)
参数注意事项
- 如果两个参数相等:则pid进程变为pgid进程组的组长(此时相当于创建了一个新的进程组)
- 如果pid等于0:则使用调用此函数的进程ID(即把调用此函数的进程的进程组设为pgid)
- 如果pgid等于0:则由pid指定的进程ID用作进程组ID
使用此函数的一些注意事项
- 一个进程只能为它自己或它的子进程设置进程组ID
- 在它的子进程调用了exec后,它就不再能改变该子进程的进程组ID
父进程和子进程同时调用此函数
- 在大多数作业控制shell中,在fork之后调用此函数,使父进程设置其子进程的进程组ID, 然后使子进程设置其自己的进程组ID。这两个调用中有一个是冗余的
- 但让父进程和子进程都这样做可以保证:父、子 进程在进一步操作之前,子进程都进入了该进程组。如果不这样做的话,在fork之后,由于父进程和子进程运行的先后次序不确定,会因为子进程的组员身份取决于哪个进程首先执行而产生竞争条件
六、会话(session)的概念演示案例
- 下面子进程使用setpgid()创建了一个新的进程组,并且进程组的组长就是自己。从而脱离了父进程的进程组
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { pid_t pid; printf("start fork......\n"); if((pid=fork())<0){ perror("fork"); exit(1); }else if(pid==0){ printf("Child:before setpgid,my pid=%d,My pgid=%d\n",getpid(),getpgrp()); setpgid(getpid(),getpid()); printf("Child:after setpgid,my pid=%d,My pgid=%d\n",getpid(),getpgrp()); }else{ wait(NULL); printf("child exit\n"); printf("Father:My pid=%d,My pgid=%d\n",getpid(),getpgid(getpid())); } exit(0); }
- 会话(session)是一个或多个进程组的集合
- 没有会话ID这个概念
- 通常:一个进程的会话ID是登录shell的ID(登录shell是一个会话的会话首进程 )
七、会话的建立(setsid函数)
- 例如:下面的一个会话中,有3个进程组
- 通常是由shell的管道线将几个进程编成一组的,则上图的安排可能是由下列形式的shell命令形成的
#include <unistd.h>
pid_t setsid(void);
//返回值:成功返回进程组ID;出错返回-1
八、获取会话首进程的进程组ID(getsid函数)调用此函数分为以下两种情况
①如果调用此函数的进程不是一个进程组组长,则此函数创建一个新会话:
- 此进程变成该新会话的会话首进程(session leader,会话首进程是创建该会话的进程)。此时,该进程是该会话中的唯一进程
- 此进程成为一个新进程组的组长进程。新进程组ID是该调用进程的进程ID
- 此进程没有控制终端。如果在调用setsid之前此进程有一个控制终端,那么这种联系也被解除
②如果调用此函数的进程是一个进程组组长,则此函数出错返回
- 为了保证不处于这种情况,通常先调用fork,然后使其父进程终止,而子进程则继续。因为子进程继承了父进程的进程组ID, 而其进程ID则是新分配的,两者不可能相等,所以这就保证了子进程不是一个进程组的组长
设计此函数的原理:
- Single UNIX Specification只说明了会话首进程,而没有会话ID这个概念
- 所以,可以将会话首进程的进程ID视为会话ID
- 功能:getsid函数返回参数所指向的进程所在的会话的会话首进程的进程组ID(因为会话首进程总是一个进程组的组长,所以返回的进程组ID与首进程ID是相同的)
- 通常:一个进程的会话ID是登录shell的ID(登录shell是一个会话的会话首进程 )
#include <unistd.h>
pid_t getsid(pid_t pid);
//返回值:成功返回会话首进程的进程组ID;失败返回-1
参数注意事项:
- 如果pid等于0,getsid返回调用进程的会话首进程的进程组ID
- 处于安全方面考虑,一些实现有如下限制:如若pid并不属于调用者所在的会话,那么调用进程就不能得到该会话首进程的进程组ID
演示案例
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { pid_t pid; if((pid=fork())<0){ perror("fork"); exit(1); }else if(pid==0){ printf("Child:pid=%d,ppid=%d,sid=%d\n",getpid(),getppid(),getsid()); exit(0); }else{ wait(NULL); printf("Father:pid=%d,ppid=%d,sid=%d\n",getpid(),getppid(),getsid()); exit(0); } }
- 从运行结果可以看出当前程序的父进程和子进程的会话都是当前终端的进程ID(因为当前的程序是在当前的终端打开的,所以当终端打开之后,该终端ID就成为该会话的ID了)