# 守护进程-02-daemon

如何看一个进程是不是守护进程呢?一个简单的方式是看下它的tty是不是? 比如nginx的

root@lan-dev-215:~/test# ps -e -o pid,ppid,cmd,tty | grep nginx
 1436     1 nginx: master process /usr/ ?
 1437  1436 nginx: worker process       ?
 1438  1436 nginx: worker process       ?
 1439  1436 nginx: worker process       ?
 1440  1436 nginx: worker process       ?
 1441  1436 nginx: worker process       ?
 1442  1436 nginx: worker process       ?
 1443  1436 nginx: worker process       ?
 1444  1436 nginx: worker process       ?
 1445  1436 nginx: worker process       ?
 1446  1436 nginx: worker process       ?
 1447  1436 nginx: worker process       ?
 1448  1436 nginx: worker process       ?
 1449  1436 nginx: worker process       ?
 1450  1436 nginx: worker process       ?
 1451  1436 nginx: worker process       ?
 1452  1436 nginx: worker process       ?

可以看到它的tty都是? 上次介绍了守护进程的一个工具tmux,今天介绍linux的daemon

# daemon

daemon是干啥的,man看一下

man daemon

回到上次那个例子,我们改下代码便于观察。

#include <stdio.h>
main(void)
{
    int d = 1;
    while(1){
        printf("hello %d \n",d);
        fflush(stdout);
        sleep(1);
        d += 1;
    };
    return 0;
}

gcc编译后,使用daemon命令运行a.out

daemon  --name=test_daemon --respawn -o /tmp/test.log -- /root/test/a.out 

这里用了三个参数:name 和 -o 。name是取个daemon的名字,-o是指定标准输出的文件,--respawn 重启进程

root@lan-dev-215:~/test#  ps aux | grep a.out
root      2544  0.0  0.0  20388   188 ?        S    18:47   0:00 daemon --name=test_daemon --respawn -o /tmp/test.log -- /root/test/a.out
root      2545  0.0  0.0   4508   740 ?        S    18:47   0:00 /root/test/a.out

此时查看/tmp/test.log会看到有相关的输出

root@lan-dev-215:~/test# tail /tmp/test.log 
hello 7 
hello 8 
hello 9 
hello 10 
hello 11 
hello 12 

杀掉a.out,看下是否还有

root@lan-dev-215:~/test# kill 2545
root@lan-dev-215:~/test# ps aux | grep a.out
root      2544  0.0  0.0  20388  1764 ?        S    18:47   0:00 daemon --name=test_daemon --respawn -o /tmp/test.log -- /root/test/a.out
root      2549  0.0  0.0   4508   744 ?        S    18:47   0:00 /root/test/a.out

可以看到a.out重启了,分配了一个新的pid,而且tty是?

除了使用daemon命令,在C里面也可以直接调用daemon,让a.out变成守护进程。我们把代码改下,增加一行daemon调用:

#include <stdio.h>
main(void)
{
    daemon(0,0);
    int d = 1;
    while(1){
        printf("hello %d \n",d);
        fflush(stdout);
        sleep(1);
        d += 1;
    };
    return 0;
}

此时重新gcc编译一下,运行a.out,可以看到该进程变成了守护进程。

# daemon是怎么实现的

看下daemon的主函数 https://github.com/lattera/glibc/blob/master/misc/daemon.c 直接注释着来看吧。

daemon (int nochdir, int noclose)
{
	int fd;

	switch (__fork()) { 
	case -1:
		return (-1);
	case 0:
		break;
	default:
	    // fork成功后,父进程正常退出,子进程托孤,继续往下走
		_exit(0);
	}

	if (__setsid() == -1) 
	/*
	调用setsid,将自己改成一个新的sid,即变成一组新的会话,不受到原sid的影响。
	比如在terminal上运行普通程序时,sid是shell的,此时该程序受到terminal的影响 
	这里调用setsid后,sid是新的了,不再依赖原sid
	*/
		return (-1);

    // 后面是修改程序运行路径,标准输入输出等
	if (!nochdir)
		(void)__chdir("/");

	if (!noclose) {
		struct stat64 st;

		if ((fd = __open_nocancel(_PATH_DEVNULL, O_RDWR, 0)) != -1
		    && (__builtin_expect (__fxstat64 (_STAT_VER, fd, &st), 0)
			== 0)) {
			if (__builtin_expect (S_ISCHR (st.st_mode), 1) != 0
#if defined DEV_NULL_MAJOR && defined DEV_NULL_MINOR
			    && (st.st_rdev
				== makedev (DEV_NULL_MAJOR, DEV_NULL_MINOR))
#endif
			    ) {
				(void)__dup2(fd, STDIN_FILENO);
				(void)__dup2(fd, STDOUT_FILENO);
				(void)__dup2(fd, STDERR_FILENO);
				if (fd > 2)
					(void)__close (fd);
			} else {
				/* We must set an errno value since no
				   function call actually failed.  */
				__close_nocancel_nostatus (fd);
				__set_errno (ENODEV);
				return -1;
			}
		} else {
			__close_nocancel_nostatus (fd);
			return -1;
		}
	}
	return (0);
}