目录
进程组
会话
作业控制
实现守护进程
我们在写完一些网络服务后,如果想让这个服务一直在云服务器的后台运行着,那该如何实现呢?其实就用到了这篇博客要讲的守护进程
进程组
我们首先需要了解进程组的概念,其实sleep 1000这条命令运行起来就是进程,我们可以看下面这个例子
我们让这个命令在一个会话(这个概念我们下面会提到)下运行着,新起一个会话查一下sleep
我们可以看到它们三个的PGID(process group)相同,都为sleep 1000的PID(进程组ID一般是第一个进程的ID),也就是说它们属于同一个进程组
并且我们可以看到它们的PPID(父进程ID)相同,其实它们的父进程就是bash(bash的pid和它们的ppid相同)
bash的PID和PGID相同,其实它就是自成一组的,因为会话一建立,bash进程肯定是第一个进程,我们后面启动的不管是命令还是程序都是bash的子进程
所以哪怕只有一个进程,它也会自成进程组
父子进程是同属于一个进程组的,父进程是组长,我们可以验证一下
我们可以从这上面看到父子关系,它们的PGID是相同的
会话
下面我们来谈这个概念,其实OS在有新用户登录的时候是会有一些行为的,一个是新建终端文件,用来和用户交流;另一个就是新建一个bash进程,用来解析用户的命令。
我们说上面的这样一个过程其实就是新建会话(session)
不管是我们新打开一个Xshell登录上还是复制一个ssh渠道
都叫做新起一个会话(复制会话会重新建立1个ssh进程。复制ssh渠道不会重新建立1个ssh进程,只会增加1个pts终端(和当前进程复用))
上面说的新建终端文件在/pts/dev目录下
证明我当前有两个会话
我们可以向这个终端文件中写入东西,其实就会打印到当前的终端上
我们也可以看bash进程的个数
我们可以把三个sleep和父子进程同时启动
这个&符号就是把前面的进程放到后台去运行,这时我们再去查一下
可以看到这是两个进程组,它们的SID(会话ID)相同,它们确实都属于当前这个会话,并且这个SID就是会话中第一个进程的ID,就是bash的PID
所以一个会话是可以建立多个进程组的
前台进程只允许有一个,后台进程可以有一个或多个
并且前台进程是可以被键盘输入的按键(信号)给杀掉的
所以会话刚建立时bash就是前台进程,如果我们运行自己的程序,我们的程序就会变成前台进程,那么bash自动就会切换成后台进程,所以这时我们输入一些指令是没有响应的
上面说不管是前台进程还是后台进程还是进程组,它们都属于一个会话,所以当用户退出时(会话释放),有可能会影响到进程组,这也就是为什么我们要将自己写的网络服务变成守护进程(它有独立的会话,不会受到用户登录或注销的影响)
并且Windows也是有注销功能的,这其实就是关闭当前的会话(关闭掉跟用户有关的进程),然后新起一个会话
作业控制
为什么会有进程组的概念呢?其实就是因为一个进程组完成一项完整的工作。这里的一个进程组就是一个作业
这里把这个进程组放到后台运行,这里显示的1就是作业号,391888是第三个进程的PID
我们可以用下面的指令把1号作业放到前台
当然如果想再放回后台我们现在输指令肯定不行了,前台进程不是bash了,所以ctrl+z先让前台进程暂停,然后用bash把它放到后台,再让它运行起来
这样就可以实现了上面说的作业控制
实现守护进程
有了上面的理论铺垫,下面就可以写一个函数,使调用此函数的程序变成一个守护进程
setsid这个系统调用就是新建一个会话,把当前的程序放到新会话中,但是不能是进程组的组长调用,因为调用setsid的进程会成为新进程组的组长,一个进程不能成为两个进程组的组长!况且,进程组PGID就是进程组组长的PID,难道两个进程组的PGID还能相同?
除此之外我们还要考虑进程的运行路径是否要改变,进程的输出消息到哪里
#include <iostream>
#include <signal.h>
#include <fcntl.h>
void Daemon(bool ischdir, bool isclose)
{signal(SIGCHLD, SIG_IGN);//忽略不需要的信号signal(SIGPIPE, SIG_IGN);if (fork() > 0)exit(0);//父进程为进程组组长,所以退出,用子进程// 子进程setsid();//新建独立会话if (ischdir)chdir("/");//更改工作目录if (isclose)//要么关掉012,要么重定向{close(0);close(1);close(2);}else{int fd = open("/dev/null", O_RDWR);if (fd > 0){dup2(fd, 0); // 将0重定向到fddup2(fd, 1);dup2(fd, 2);close(fd);}}
}
用上面的代码后查询进程
可以看到它的PPID是OS,证明它是孤儿进程,所以守护进程一定是孤儿进程
这是改变工作目录和关闭文件描述符
这是不改变工作目录和不关闭文件描述符
其实OS系统给我们提供了类似于上面代码的系统调用
我们平时直接用即可