目录

进程组

会话

作业控制

实现守护进程


我们在写完一些网络服务后,如果想让这个服务一直在云服务器的后台运行着,那该如何实现呢?其实就用到了这篇博客要讲的守护进程

进程组

我们首先需要了解进程组的概念,其实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重定向到fd
            dup2(fd, 1);
            dup2(fd, 2);
            close(fd);
        }
    }
}

用上面的代码后查询进程

可以看到它的PPID是OS,证明它是孤儿进程,所以守护进程一定是孤儿进程

这是改变工作目录和关闭文件描述符

这是不改变工作目录和不关闭文件描述符

其实OS系统给我们提供了类似于上面代码的系统调用

我们平时直接用即可

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部