守护进程
1.守护进程
守护进程是在后台运行且不与任何控制终端管理的进程。Unix系统中有很多这样的进程,使用命令ps -axj可以显示此类进程。
一个守护进程的父进程是init进程,它是一个孤儿进程,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都被丢到了/dev/null中。守护进程一般用作服务器进程,如httpd,syslogd等。
2.守护进程和后台运行的区别
[1]守护进程已经完全脱离终端控制台了,而后台程序并未完全脱离终端,在终端未关闭前还是会往终端输出结果。
[2]守护进程在关闭终端控制台时不会受影响,而后台程序会随用户退出而停止,需要在以nohup command & 格式运行才能避免影响,或者在代码中屏蔽SIGHUP信号(signal(SIGHUP, SIG_IGN);)。
[3]守护进程的会话组和当前目录,文件描述符都是独立的。而后台运行只是终端进行了一次fork,让程序在后台执行,这些都没改变,即继承了终端的会话组、当前目录和文件描述符。
3.nohup和&的作用
默认情况下,键入Ctrl+C ,程序会收到一个SIGINT信号,程序的默认行为是终止,而后台运行时则不会终止。因此加上&后台运行时,会屏蔽SIGINT信号。
默认情况下,关闭session(控制台),程序会收到一个SIGHUP信号,程序的默认行为是终止,而使用nohup启动时则不会终止。因此加上nohup会屏蔽SIGHUP信号。
因此使用nohup command &启动程序会屏蔽SIGINT和SIGHUP信号,此时只能用kill来关闭程序。
4.编写守护进程的规则和步骤
[1]在父进程中调用umask将文件模式创建屏蔽字设置为一个已知值,通常为0,防止子进程继承得来的文件模式权限不够。
[2]fork子进程,然后父进程exit。这样做的目的:如果进程是作为shell命令启动的,父进程终止为让shell认为该命令执行完毕,不用挂在终端输入上。此时子进程继承了父进程的进程组ID,并获得了一个新的进程ID,这就保证了子进程不是一个进程组的组长进程,这也是后面继续调用setsid的先决条件。
[3]调用setsid创建一个新的会话。调用成功会使得调用进程成为新会话的首进程,并且成为一个新进程组的组长,且调用进程没有控制终端,如果先前有联系也会切断。
[4]将当前工作目录更改为根目录。防止继承来的工作目录在文件系统中无法卸载,因为从父进程继承过来的当前工作目录可能是在一个挂载的文件系统中。
[5]关闭不再需要的文件描述符。
[6]忽略SIGHUP信号并再次fork。再次fork的原因是防止当前的进程再次打开一个终端。因为只有会话的组长进程可以打开终端设备,此时再fork一次,把父进程退出,子进程继续运行,确保了最后的子进程不是组长进程。而这里必须忽略SIGHUP信号,因为会话组长进程终止时,其会话的所有进程都会受到SIGHUP信号。
[7]打开dev/null使其具有文件描述符0、1和2(Attach file descriptors 0, 1, and 2 to /dev/null.)。这样,任何一个试图读标准输入、写标准输出或标准错误的例程都不会产生任何效果。 因为守护进程不与终端设备相关联,所以输出无处显示,也无法接收输入。
5.单实例守护进程
单实例守护进程就是限制一个时刻只能运行守护进程的一个副本。
实现方法是给同一个文件加写锁。加锁失败表示当前已经有进程在运行了。
6.守护进程的使用惯例
[1]若守护进程使用锁文件,那么该文件放在/var/run目录中,名字通常是name.pid,name是守护进程的名字。
[2]守护进程的配置选项通常存放在/etc目录中,名字通常是name.conf,name是守护进程的名字。
[3]守护进程可以用命令行启动,通常是系统初始化脚本之一(/etc/rc*或/etc/init.d/*)启动的。
7.守护进程的实现
void daemonize(const char *cmd) { int i, fd0, fd1, fd2; pid_t pid; struct rlimit rl; struct sigaction sa; /* * Clear file creation mask. */ umask(0); /* * Get maximum number of file descriptors. */ if(getrlimit(RLIMIT_NOFILE, &rl) < 0) err_quit("%s: can't get file limit", cmd); /* * Become a session leader to lose controlling TTY. */ if((pid = fork()) < 0) err_quit("%s: can't fork", cmd); else if (pid != 0) /* parent */ exit(0); setsid(); /* * Ensure future opens won't allocate controlling TTYs. */ sa.sa_handler = SIG_IGN; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; if(sigaction(SIGHUP, &sa, NULL) < 0) err_quit("%s: can't ignore SIGHUP"); if((pid = fork()) < 0) err_quit("%s: can't fork", cmd); else if( pid != 0 ) /* parent */ exit(0); /* * Change the current working directory to the root so * we won't prevent file system from being unmounted. */ if(chdir("/") < 0) err_quit("%s: can't change directory to /"); /* * Close all open file descriptors. */ if(rl.rlim_max = RLIM_INFINITY) rl.rlim_max = 1024; for(i = 0; i < rl.rlim_max; i++) close(i); /* * Attach file descriptors 0, 1, and 2 to /dev/null. */ fd0 = open("/dev/null", O_RDWR); fd1 = dup(0); fd2 = dup(0); /* * Initialize the log file. */ 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); } }