守护进程详解

目录

    • 守护进程的特点
    • 常见的守护进程
    • 守护进程的编程规则
    • 编程规则设计原理
    • 守护进程的编程代码
    • 总结
    • 参考资料

守护进程的特点

守护进程是脱离终端并在后台运行的进程。守护进程不具有控制终端,我们不会再终端上见到守护进程的输出。守护进程也不会被终端发出的信号打断。

常见的守护进程

init 系统守护进程,启动系统服务
inetd 侦听网络接口,以便取得来自网络各种网络服务进程的请求

守护进程的编程规则

  1. 调用umask将文件模式创建屏蔽字设置为0。
  2. 调用fork,父进程退出。
  3. 调用setsid创建一个新会话。
  4. 将当前工作目录更改为根目录。
  5. 关闭不再需要的文件描述符。
  6. 打开/dev/null使其具有文件描述符 0,1,2。

编程规则设计原理

有很多博文对守护进程的编程规则进行了阐述,但是很多不是直接抄书就是没讲清楚。接下来让我详细的分析,让大家把守护进程的生成过程弄明白。

调用umask将文件模式创建屏蔽字设置为0

在fork过程子进程继承会父进程的文件权限掩码,由继承得到的文件模式创建的屏蔽字可能会拒绝设置某些权限。例如,若守护进程要创建一个组可读/写的文件,而继承的文件模式创建屏蔽字可能屏蔽了这两种权限,创建的组就无法实现读写功能。调用umask将文件模式创建屏蔽字设置为0就消除了这种烦恼。

调用fork,父进程退出。

1)如果该守护进程是作为一条简单的shell命令启动的,那么⽗父进程终止使得shell认为该命令已经执行完毕。
2)保证子进程不是一个进程组的组长进程。
第二条没看懂不碍事,让我们结合下面一条编程规则一起看。

调用setsid创建一个新会话

编程步骤二其实是基于编程步骤三,因为进程不是一个进程组的组长时,调用setsid函数时才成功。
调用成功后,函数就会新建一个会话,结果将发生下面3件事:
1)该进程变为新会话首进程。此时,该进程是新会话中唯一的进程。
2)该进程成为新进程组的组长进程。新进程组ID是调用该进程的进程ID。
3)该进程没有控制终端
重点来了:
编程步骤二其实是就是希望能够成功调用setsid函数
编程步骤三调用setsid函数其实是使该进程没有控制终端!(还记得守护进程的特点吗)

调用setsid创建一个新会话之后,还会再次调用一次fork函数。那么第二次调用fork函数的目的是什么呢?
第二次调用fork函数生成的进程我把它叫孙子进程。由于进程组有子进程,在生成孙子进程之后,孙子进程也不会成为进程组的组长进程,孙子进程就没有权力打开终端。说白了目的还是为了使守护进程没有控制终端!

将当前工作目录更改为根目录。

如果守护进程的当前工作目录在一个装配文件中,那么该文件系统就不能被拆卸。

关闭不再需要的文件描述符

守护进程有能从父进程继承文件描述符,如果不进行关团的话将会浪费录统资源。

打开/dev/null使其具有文件描述符 0,1,2

文件描述符 0,1,2在改变之前指的是读标准输入、写标准输出和标准出错。这些都是与终端相连的文件描述符。说白了就是切断守护进程与终端的可能性,可见在守护进程的编程过程中,在断绝与终端的关系做足了功夫。

守护进程的编程代码

结合以上编程原理理解这个代码,看起来顺畅多了。

void daemonize(const char *cmd)
{
	int i, fd0, fd1, fd2;
	pid_t pid;
	struct rlimit rl;
	struct sigaction sa;
	
	umask(0);
	
	if(getrlimit(RLIMIT_NOFILE, &rl) < 0)
		err_quit("%s: cannot get file limit",cmd);
	
	if((pid = fork()) < 0)
		err_quir("%s: cannot fork", cmd);
	else if(pid != 0)
		exit(0);
	setsid();
	
	sa.sa_handler = SIG_IGN;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	if(sigaction(SIGHUP, &SA, NULL) < 0)
		err_quit("s: cannot ignore SIGHUP");
	if((pid = fork() < 0)
		err_quit("%s: cannot fork", cmd);
	else if(pid != 0)
		exit(0);
	
	if(chdir("/") <0)
		err_quit("%s: cnnot change directory to /");
	
	if(rl.rlim_max == RLIM_INFINITY)
		rl.rlim_max = 1024;
	for(i = 0; i < rl.rlim_max; i++)
		close(i);
	
	fd0 = open("/dev/null", ORDWR);
	fd1 = dup(0);
	fd2 = fup(0);
	
	openlog(cmd, LOG_CONS, LOG_DAEMON);
	if(fd0 != 0 || fd1 != 1 || fd2 != 2){
		syslog(LOG_ERR, "unexpected file descriptors %d %d %d", fd0, fd1, fd2);
		exit(1);
	}
}

总结

守护进程是脱离终端并在后台运行的进程。守护进程的编程过程其实使一个切断与终端联系的过程。

参考资料

UNIX环境高级编程
创建守护进程为什么要fork两次

你可能感兴趣的:(守护进程详解)