本章节介绍,进程间的各种关系:亲缘关系,终端进程,进程组,会话,孤儿进程,守护进程
1、亲缘关系
Linux或unix操作系统,进程间具备亲缘关系,分为强亲缘与弱亲缘
强亲缘:父进程负责创建,继承数据,并且回收
弱亲缘:只有继承关系
ps ajx——查看进程关系
2、终端进程
如果在bash终端内执行程序,例如./app,这个app进程是终端子进程,是被终端创建的
是bash进程调用fork函数,创建出一个终端子进程,这个子进程将app的用户空间重载
这种终端的进程,可以称作终端进程
终端进程受终端限制,如果终端关闭,会杀死进程,因此需要了解如何将进程和终端分离
3、进程组关系
进程组知识点
操作系统为了管理多进程,将进程编为进程组关系,每个进程组由一个组长进程和若干个组员进程构成
终端子进程被创建后都是组长进程,进程组里可以没有组长
每个组长都有一个组id,PGID——如果某个进程的PID等于PGID,那么这个进程为组长
进程组的生命周期比里面组成员的生命周期长——进程组释放与某个特定的进程无关,当组成员全部结束或转移,当进程组为空时,系统才释放组
就近原则——如果组长进程创建子进程,子进程默认与父进程一组,并为父进程的组员
进程组关系与亲缘关系没有必然联系——不能确定组员一定是子进程
进程组关系与亲缘关系没有必然联系——即使子进程转移到其他组,或子进程变为组长,亲缘关系仍然不变,子进程结束后依旧由父进程回收
进程id函数
getpid()——获取进程id
getppid()——获取父进程id
getpgrp()——获取进程组id
setpgid()——创建进程组
setpgid(getpid(),getpid())——让某个进程变成组长进程,组长进程无法创建,只有组员进程可以
setpgid(getpid(),gpid)——将某个组成员转移到其他组中
组长无法转移到其他进程组内,但是组员可以
想要转移到其他组中——1、必须对目标组拥有权限—2、目标组必须存在
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>int main()
{pid_t pid;int i;for(i=0;i<3;i++){pid=fork();if(pid==0){break;}}if(pid>0){printf("parent pid %d,pgid %d\n",getpid(),getpgrp());while(1)sleep(1);}else if(pid==0){if(i==2){printf("child pid %d,pgid %d\n",getpid(),getpgrp());sleep(5);setpgid(getpid(),getpid());printf("child pid %d,pgid %d\n",getpid(),getpgrp());while(1)sleep(1);}printf("child pid %d,pgid %d\n",getpid(),getpgrp());while(1)sleep(1);}else{perror("faild");exit(0);}return 0;
}
父进程id——3478,组id为3478
子进程id——3479,3480,3481,组id为父进程id3478
当休眠5秒后,将id为3481的子进程改为组长,创建了组id为3481的组
4、进程会话关系
一般当终端管理终端下的进程,会组成一个会话,分为会话发起者和会话参与者
如果bash进程是会话发起者,设定终端进程为一个会话参与者,由这个终端进程创建出的多个终端子进程也属于会话参与者
会话发起者的标志——pid==pgid==sid
bash为会话发起者,因此bash的pid和pgid和sid都为相同的,均为1000
终端进程为bash进程的会话参与者,因此sid与bash相同,为1000,设进程pid和组id为3000
将终端子进程1的pid设为3001,gid与父进程相同,为3000,sid为会话id1000
当会话发起者退出,系统以组为单位杀死会话参与者,按进程组杀死
让进程创建新的进程组,可以避免被终端退出杀死,即可脱离终端——实现终端与进程分离
setsid()——创建新会话
setsid包含两个步骤,第一个步骤是创建组,第二个步骤是申请会话
getsid(getpid)——获取会话id
终端进程不能再创建进程组,不能转移到其他组,也不能创建会话,因此终端进程一定会被杀死
5、孤儿进程
父进程先于子进程退出,子进程变成孤儿进程
托管进程不会控制子进程,只负责回收
孤儿进程会影响新的进程创建,而且孤儿进程为存活状态,存在很多不确定因素,需要早处理
孤儿进程的危害是弹性的,取决于孤儿进程的任务
假如最开始,托管进程id为1000,父进程id为3000,子进程的id为3001,父进程id为3000
当父进程异常结束,子进程会被寄存给托管进程,子进程的ppid会与托管进程相同,变为1000
孤儿进程危害很大,因此要进行孤儿进程检测,发现孤儿进程,立即让其退出
孤儿进程的检测可以使用管道,父进程作为管道写端,子进程都是读端,写端退出,读端读管道读到0,子进程退出即可,利用管道实现孤儿的检测机制
检测孤儿进程的代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
#include<signal.h>
#include<pthread.h>
#include<sys/fcntl.h>int main()
{pid_t pid;int i;int fds[2];pipe(fds);int flag;int len;for(i=0;i<3;i++){ pid=fork();if(pid==0){break;}} if(pid>0){ int timeout=8;close(fds[0]);while(timeout--){printf("parent running\n");sleep(1);}close(fds[1]);exit(0);} else if(pid==0){ char buf[1024];close(fds[1]);flag=fcntl(fds[0],F_GETFL);flag=O_NONBLOCK;fcntl(fds[0],F_SETFL,flag);while(1){printf("child pid %d,running...\n",getpid());if((len=read(fds[0],buf,sizeof(buf)))==0){printf("child id %d check parent exit,child exit...\n",i);close(fds[0]);exit(0);}if(len==-1){if(errno==EAGAIN){}}sleep(1);}}else{perror("faild");exit(0);}return 0;
}
6、守护进程(daemon process精灵进程/守护进程)
后台服务进程,不占用前台资源,后台服务一般不与用户直接交互,与用户交互的是前台程序,UI进程,前台程序
1、常规进程生命周期随用户使用持续,守护进程生命周期随系统持续,开机启动关机结束
2、守护进程的任务,保障系统稳定,如果是软件的守护进程,为软件提供支持与服务
3、守护进程的工作模式是低消耗模式(间隔执行)(定时执行)(条件触发)
守护进程也是孤儿进程,但是是工程师人为创建的孤儿进程,低开销模式运行,对系统没有压力