1. 概念
守护进程(daemon)是生存期长的一种进程。它们常常在系统引导装入时启动,仅在系统关闭时才终止。因为它们没有控制终端,所以说它们是在后台运行的。
Linux的大多数服务就是用守护进程实现的。这些守护进程名通常以d
结尾,如inetd提供网络服务,sshd提供ssh登录服务,httpd提供web服务等待。
-
大多数守护进程都以超级用户权限运行。
-
所有守护进程都没有控制终端。用户层守护进程缺少控制终端可能是守护进程调用了setsid的结果(setsid会断开与控制终端的联系)。
-
大多数用户层守护进程都是进程组的组长进程以及会话的首进程,而且是这些进程组和会话中的唯一进程
-
用户层进程的父进程是init(1)进程。
2. 守护进程编程规则
编写守护进程程序时需要遵循一些基本规则,以防止产生不必要的交互作用。
-
调用umask将文件模式创建屏蔽字设置为一个已知值(通常是0)。由继承(如fork)得来的文件模式创建屏蔽字可能会被设置为拒绝某些权限。如果守护进程要创建文件,那么它可能要设置特定的权限。
-
调用fork,然后使父进程exit退出,这样会实现以下几点:
- 如果该守护进程是shell命令启动的,那么父进程终止会让shell认为这条命令已经执行完毕
- 虽然子进程继承了父进程的进程组ID,但是获得了一个新的进程ID,因此子进程不是该进程组的组长进程,这是接下来进行setsid调用的先决条件
-
调用setsid创建一个新会话,这样会使调用进程:
- 成为新会话的首进程
- 成为新进程组的组长进程
- 没有控制终端
有些建议此时再次调用fork,终止父进程,继续使用子进程中的守护进程,这就保证了该守护进程不是会话首进程,可以防止它取得控制终端。
为了避免取得控制终端的另一种方法是:当用open函数打开终端设备时,设置O_NOCTTY标志
-
将当前工作目录更改为根目录。
从父进程处继承过来的当前工作目录可能在一个挂载的文件系统处。因为是守护进程通常在系统再引导前一直存在,所以如果守护进程的当前工作目录在一个挂载文件系统中,那么该文件系统就不能被卸载。
-
关闭不再需要的文件描述符。这使得守护进程不再持有从其父进程继承来的任何文件描述符:可以通过getrlimit函数判定最高文件描述符值,并关闭直到该值的所有描述符。
-
某些守护进程打开/dev/null使文件描述符0/1/2指向该文件。这样使得任何一个试图读标准输入、写标准输出或标准错误的例程都不会产生任何效果。因为守护进程不与终端设备关联,因此其输出无处显式,也无处从交互式用户那里接收输入。
/dev/null文件:
一个字符设备文件。称为空设备,它丢弃一切写入其中的数据(但报告写入操作成功),读取它则会立即得到一个EOF。
/dev/null 被称为位桶(bit bucket)或者黑洞(black hole)。空设备通常被用于丢弃不需要的输出流,或作为用于输入流的空文件。这些操作通常由重定向完成。
/dev/zero文件:
一个字符设备文件。当你读它的时候,它会提供无限的空字符(NULL, 即0x00)。写入/dev/zero的内容会丢失不见。
/dev/random和/dev/urandom文件:
字符设备文件。随机数设备,提供不间断的随机字节流。二者的区别是/dev/random产生随机数据依赖系统中断,当系统中断不足时,/dev/random设备会“挂起”,因而产生数据速度较慢,但随机性好;/dev/urandom不依赖系统中断,数据产生速度快,但随机性较低。
**示例程序,初始化一个守护进程:**其中关于出错记录部分函数在下一节讲解
void daemonize(const char * cmd) {
//清空文件模式创建屏蔽字
umask(0);
//获取最大文件描述符
struct rlimit rl;
if(getrlimit(RLIMIT_NOFILE,&rl) < 0) {
perror("无法获取最大文件描述符");
}
//成为会话首进程并失去控制终端
pid_t pid;
if(fork() != 0) {
// 父进程
exit(0);
}
setsid();
//确保该守护进程不会有控制终端
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset