查看原文
其他

如何让程序真正地在后台运行?

守望先生 编程珠玑 2022-09-10
来源:公众号【编程珠玑】
作者:守望先生
ID:shouwangxiansheng


如何实现一个守护进程?如何让程序在后台运行?这是后台开发面试常问的一道题,那么守护进程到底是什么?又该如何实现?

守护进程

守护进程通常生存期长,很多是在系统启动时启动,系统退出时才关闭。它们的特点通常没有控制终端,后台运行
有人可能会会心一笑,后台运行程序,我知道呀。还有两种方式呢
$ ./hello &
看,多么简单。但是运行之后,你试着关闭当前终端,你会发现程序会停止运行,因为一旦关闭终端,程序会收到一个信号SIGHUP,而收到该信号默认的动作就是程序退出。
没关系啊,我还有招:
$ nohup ./hello & #注意这里&是必要的,否则不会变成后台进程
$ jobs
[1]+  Running                 nohup ./hello &
我使用nohup命令总可以了吧?
挺好的,nohup会忽略SIGHUP命令,并有了&的加持,即便终端关了,也能继续执行。但它的终端输出还会记录默认还在nohup.out文件中,同时,如果将huponexit关闭,它同样难逃命运:
$ shopt -s huponexit  #shopt -u huponexit 设置为off
$ shopt |grep onexit
huponexit       on
一旦终端退出(ctrl+D)后,nohup也救不了。
下面要介绍的守护进程一一种完全脱离终端,有着自己的会话。
如果你在你的Linux系统中执行下面的命令:
$ ps -elf
就会发现一些进程的tty列是?,当然这并不是说明它们是守护进程,而那些用[]括起来的,是内核守护进程
想象一下,如果没有任何人登录的服务器上面的运行程序,难道每次执行的时候都要使用nuhup+&?况且,一旦系统的huponexit选项是打开的,这种方式仍然无法避免终端关闭程序就退出的命运!
那么就需要实现用户守护进程了,或者说daemon化。

如何实现

其实现过程基本遵循以下原则:
  • 调用umask设置文件模式,通常设置为0。为了便于后续创建文件,不使用继承而来的父进程的设置,需要设置新的权限掩码。
  • 调用fork,创建子进程,并且父进程退出
  • 调用setdid创建新的会话(一个或多个进程组的集合),由于当前进程不是一个进程组的组长,因此会创建一个新的会话,却成为组长进程,同时没有控制终端。
  • 将当前工作目录切换为根目录。同样的,其工作目录可能是从父进程继承而来的,可以自己另立山头。
  • 关闭不需要的文件描述符。同样的,可能从父进程继承了一些打开的文件描述符,而这些描述符可能再也不需要,因此可以关闭。
  • 重定向标准输出,标准输入和标准错误到/dev/null(相关阅读:Linux下你还知道这些特殊文件?
实际上,从上面的描述可以发现,这些规则都有几乎相同的目标,那就是不想成为富二代,摆脱父亲的控制。(在fork的介绍中,我们说到,儿子从父亲那里继承了很多东西)
  • 重新设置权限掩码,避免受父进程影响
  • 创建新的会话,脱离终端
  • 使用新的工作目录
  • 关闭不需要的文件描述符
  • 关闭标准输入,标准输出和标准错误
所以通过这些也可以明白,有些规则并不是完全强制的,可根据实际程序的情况进行设置,不过按照常规做法是一个比较好的选择。

具体实现

参考代码如下:
//来源:公众号【编程珠玑】
//https://www.yanbinghu.com
#include<stdio.h>
#include<sys/resource.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
/*实现仅供参考,可根据实际情况调整*/
int daemonize()
{

    /*清除文件权限掩码*/
    umask(0);

    /*父进程退出*/
    pid_t pid;
    if((pid=fork()) < 0)
    {
        /*for 出错*/
        perror("fork error");
        return -1;
    }
    else if(0 != pid)/*父进程*/
    {
        printf("father exit\n");
        exit(0);
    }
    /*子进程,成为组长进程,并且摆脱终端*/
    setsid();

    /*修改工作目录*/
    if(chdir("/") < 0)
    {
        perror("change dir failed");
        return -1;
    }


    struct rlimit rl;
    /*先获取文件描述符最大值*/
    if(getrlimit(RLIMIT_NOFILE,&rl) < 0)
    {
        perror("get file decription failed");
        return -1;
    }
    /*如果无限制,则设置为1024*/
    if(rl.rlim_max == RLIM_INFINITY)
        rl.rlim_max = 1024;

    /*为了使得终端有输出,保留了文件描述符0,1,2;实际上父进程可能没有打开2以上的文件描述符*/
    int i;
    for(i = 3;i < rl.rlim_max;i++)
        close(i);
    return 0;
}
int main(void)
{

    if(0 == daemonize())
    {
        printf("daemonize ok\n");
        sleep(20);
    }
    else
    {
        printf("daemonize failed\n");
        sleep(20);
    }
    return 0;
}
编译运行,你就会发现,它已经可以欢脱地运行啦。
代码中有几个点,需要关注一下。为了保留printf的输出,我在daemonize函数中,并没有关闭所有的文件描述符,0,1,2可以参考《如何理解 Linux shell中“2>&1”?》,当然了,如果想让printf的输出保存到文件,也有方法,可以参考《如何优雅地将printf的打印保存在文件中?》,这里就不再赘述了。

实际实现

实际上,已经有一个接口可以帮我们做这些事情:
#include <unistd.h>
int daemon(int nochdir, int noclose);
即daemon函数,它有两个参数
  • nochdir 为0时,表示修改其根目录为/,否则不变
  • noclose,为0时,表示将标准输入,标准输出,标准错误重定向到/dev/null。
简单例子:
//来源:公众号【编程珠玑】
//https://www.yanbinghu.com
#include<stdio.h>
#include<unistd.h>
int main(void)
{

    if(0 == daemon(1,1))
    {
        printf("daemon ok\n");
        sleep(20);
    }
    else
    {
        printf("daemon failed\n");
        sleep(20);
    }
    return 0;
}
如果你还要实现单例化,可以参考《如何让你的程序同时只能运行一个?》,使得同时只有一个该进程运行。

总结

以上就进程后台运行以及是守护进程实现的介绍,关键点有
  • 创建子进程,父进程退出
  • 创建新的会话,脱离终端
附上daemon的源码:
int daemon(nochdir, noclose)
    int nochdir, noclose;

{
    int fd;

    switch (fork()) 
    {
    case -1:
        return (-1);
    case 0:
        break;
    default:
        _exit(0);
    }

    if (setsid() == -1)
        return (-1);

    if (!nochdir)
        (void)chdir("/");

    if (!noclose && (fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) 
    {
        (void)dup2(fd, STDIN_FILENO);
        (void)dup2(fd, STDOUT_FILENO);
        (void)dup2(fd, STDERR_FILENO);
        if (fd > 2)
            (void)close (fd);
    }
    return (0);
}


相关精彩推荐

如何创建多进程程序?(文末福利)

如何让你的程序同时只能运行一个?

如何优雅地将printf的打印保存在文件中?

Linux下你还知道这些特殊文件?


关注公众号【编程珠玑】,获取更多Linux/C/C++/数据结构与算法/计算机基础/工具等原创技术文章。后台免费获取经典电子书和视频资源

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存