如何使一个进程变成daemon进程(守护进程)?

daemon进程又被称为守护进程,一般来说它有以下两个特点:

  • 生命周期很长
    一旦启动,正常情况下不会终止,一直运行到系统退出。但凡事无绝对:daemon进程其实也是可以停止的,如很多daemon提供了stop命令,执行stop命令就可以终止daemon,或者通过发送信号将其杀死,又或者因为daemon进程代码存在bug而异常退出。这些退出一般都是由手工操作或因异常引发的。
  • 在后台执行,并且不与任何控制终端相关联。
    即使daemon进程是从终端命令行启动的,终端相关的信号如SIGINT、SIGQUIT和SIGTSTP,以及关闭终端,都不会影响到daemon进程的继续执行。

一般来讲,创建一个daemon进程的步骤被概括地称为double-fork magic。细细说来,需要以下步骤:
在这里插入图片描述

对于(1)执行fork()函数,父进程退出,子进程继续。

执行这一步,原因有二:

  • 父进程有可能是进程组的组长(在命令行启动的情况下),从而不能够执行后面要执行的setsid函数。因此调用fork函数,子进程继承了父进程的进程组ID,并且拥有自己的进程ID,一定不会是进程组的组长,所以子进程一定可以执行后面要执行的setsid函数。
  • 守护进程(daemon)是一类在后台运行的特殊进程。如果daemon是从终端命令行启动的,那么父进程退出会被shell检测到,shell会显示shell提示符, 从而让子进程在后台执行。

对于(2)
①修改进程的当前目录为根目录(/)。

执行这一步,原因如下:
因为daemon一直在运行,如果当前工作路径上包含有根文件系统以外的其他文件系统,那么这些文件系统在需要卸载时无法卸载。也可以不修改目录,只要确保该目录所在的文件系统不会被卸载即可。
对于操作系统而言, 设置进程的工作目录为根目录,是为了防止某些目录不可卸载。
对于守护进程而言,是为了确保他执行期间,工作目录始终存在。
举个例子,假设守护进程的工作目录是u盘。那么对于操作系统而言,守护进程在执行期间,我们没办法通过点击“弹出U盘”来卸载U盘。 所以要把进程的工作目录设置到别处(这是为了确保文件系统能卸载)
反过来,对于守护进程,如果在执行期间,我们直接把U盘拔掉了, 那守护进程的运行就可能出错。所以要把工作目录设置到一个一定不会被卸载的文件系统上(根目录),(这是为了不卸载)。

对于(2)
②调用setsid函数。

这个函数的目的是切断与控制终端的所有关系,并且创建一个新的会话。 这一步确保了子进程不再归属于控制终端所关联的会话。因此无论终端是否发送SIGINT、SIGQUIT或SIGTSTP信号,也无论终端是否断开,都与要创建的daemon进程无关,不 会影响到daemon进程的继续执行。

对于(2)
③设置文件模式创建掩码为0。

这一步的目的是让daemon进程创建文件的权限属性与shell脱离关系。因为默认情况下,进程的umask来源于父进程shell的umask。如果不执行umask(0),那么父进程shell的umask就会影响到daemon 进程的umask。如果用户改变了shell的umask,那么也就相当于改变了daemon的umask,就会造成daemon 进程每次执行的umask信息可能会不一致。

对于(3)再次执行fork,父进程退出,子进程继续。

执行完前面两步之后:新建会话,进程是会话的首进程,也是进程组的 首进程。进程ID、进程组ID和会话ID,三者的值相同,进程和终端无关联。
再执行一次fork函数的原因是,daemon进程有可能会打开一个终端设备。
如果daemon进程是会话的首进程,这个打开的终端设备有可能会成为daemon进程的控制终端。
为了确保万无一失,只有确保daemon进程不是会话的首进程,才能保证打开的终端设备不会自动成为控制终端。因此,不得不执行第二次fork,fork之后,父进程退出,子进程继续。这时,子进程不再是会话的首进程,也不是进程组的首进程了。

对于(4)关闭标准输入(stdin)、标准输出(stdout)和标准错误(stderr)。

因为文件描述符0、1和2指向的就是控制终端。daemon进程已经不再与任意控制终端相关联,因此这三者都没有意义。
一般来讲,关闭了之后,会打开/dev/null,并执行dup2函数,将0、1和2重定向到/dev/null。
这个重定向是有意义的,防止了后面的程序在文件描述符0、1和2上执行I/O库函数而导致报错。


daemon函数

上述步骤比较繁琐,对于C语言而言,glibc提供了daemon函数,从而帮我们将程序转化成daemon进程。

#include <unistd.h>
int daemon(int nochdir, int noclose);

该函数有两个入参,分别控制一种行为,具体如下:

  • nochdir,用来控制是否将当前工作目录切换到根目录。 ·
    0:将当前工作目录切换到/。
    1:保持当前工作目录不变。
  • noclose,用来控制是否将标准输入、标准输出和标准错误重定向到/dev/null。
  • 0:将标准输入、标准输出和标准错误重定向到/dev/null。
  • 1:保持标准输入、标准输出和标准错误不变。

一般情况下,这两个入参都要为0。

ret = daemon(0,0)

成功时,daemon函数返回0;失败时,返回-1,并置errno。因为daemon函数内部会调用fork函数和 setsid函数,所以出错时errno可以查看fork函数和setsid函数的出错情形。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值