一、概念
Linux Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务(要么是对整个系统,要么是对某个用户程序提供服务)。
Linux 系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程 syslogd、 web 服务器 httpd、邮件服务器 sendmail 和数据库服务器 mysqld 等。守护进程的名称通常以 d 结尾,比如 sshd、xinetd、crond 等。
守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机都保持运行。守护进程经常以超级用户(root)权限运行,因为它们要使用特殊的端口(1-1024)或访问某些特殊的资源。
一个守护进程的父进程是 init 进程,因为它真正的父进程在 fork 出子进程后就先于子进程 exit 退出了,所以守护进程是一个由 init 继承的孤儿进程(实际上 init 也是一个守护进程,它专门用于托管那些父进程死掉但子进程还继续运行的孤儿进程)。
守护进程是非交互式程序,没有控制终端,所以任何输出(无论是向标准输出设备 stdout 还是标准出错设备 stderr 的输出)都需要特殊处理。
二、命令行创建守护进程
当用户注销(logout)或者网络断开时,终端会收到 HUP(hangup)信号从而关闭其所有子进程。因此,我们的解决办法就有两种途径:要么让进程忽略 HUP 信号,要么让进程运行在新的会话里从而成为不属于此终端的子进程。
方法一:忽略 HUP 信号
① nohup
nohup 能通过忽略 HUP 信号来使我们的进程避免中途被中断:
# nohup 命令或进程 &
其中 & 代表想要以后台的方式运行这个进程(将它变成一个后台作业),如果不加这个参数,命令行就不能继续输入别的命令了。标准输出和标准错误缺省会被重定向到 nohup.out 文件中。例如:
# nohup ping www.ibm.com &
[1] 3059
nohup: appending output to `nohup.out'
# ps -ef | grep 3059
root 3059 984 0 21:06 pts/3 00:00:00 ping www.ibm.com
root 3067 984 0 21:06 pts/3 00:00:00 grep 3059
② setsid
换个角度思考,如果我们的进程不属于接受 HUP 信号的终端的子进程,那么自然也就不会受到 HUP 信号的影响了。setsid 就能帮助我们做到这一点:
# setsid ping www.ibm.com
# ps -ef | grep www.ibm.com
root 31094 1 0 07:28 ? 00:00:00 ping www.ibm.com
root 31102 29217 0 07:29 pts/4 00:00:00 grep www.ibm.com
方法二:让进程运行在新的会话里
③ ( &)
将命令和"&"放入“()”中,会发现所提交的作业并不在作业列表中,也可以躲过 HUP 信号的影响:
# (ping www.ibm.com &)
# ps -ef | grep www.ibm.com
root 16270 1 0 14:13 pts/4 00:00:00 ping www.ibm.com
root 16278 15362 0 14:13 pts/4 00:00:00 grep www.ibm.com
从上例中可以看出,新提交的进程的父 ID(PPID)为1(init 进程的 PID),并不是当前终端的进程 ID。因此不属于当前终端的子进程,也就不会受到当前终端的 HUP 信号的影响了。
④ disown
如果已经未加任何处理就提交了命令,该如何补救才能让它避免 HUP 信号的影响呢?这时想加 nohup 或者 setsid 已经为时已晚,只能通过作业调度和 disown 来解决这个问题了:
使某个作业忽略 HUP 信号:
# disown -h 作业号
使所有的作业都忽略 HUP 信号:
# disown -ah
使正在运行的作业忽略 HUP 信号:
# disown -rh
例子:
# cp -r testLargeFile largeFile &
[1] 4825
# jobs
[1]+ Running cp -i -r testLargeFile largeFile &
# disown -h %1
# ps -ef | grep largeFile
root 4825 968 1 09:46 pts/4 00:00:00 cp -i -r testLargeFile largeFile
root 4853 968 0 09:46 pts/4 00:00:00 grep largeFile
需要注意的是,当使用过 disown 之后,会把目标作业从作业列表中移除,我们将不能再使用 jobs
来查看它,但是依然能够用 ps -ef
查找到它。
总结:
nohup/setsid 无疑是临时需要时最方便的方法,disown 能帮助我们来事后补救当前已经在运行了的作业。
三、编程创建守护进程
在编程实现时一般采取上文介绍到的 setsid 方法。一个会话期开始于用户 login,结束于 logout。一般 login 的是 shell 终端,所以 shell 终端又是此次会话期的首进程。对于非进程组长,它可以调用 setsid() 创建一个新的会话,脱离父进程的会话期。
void mydaemon(void)
{
pid_t pid;
int fd, i, nfiles;
struct rlimit rl;
pid = fork();
if(pid < 0)
ERROR_EXIT("First fork failed!");
// 父进程退出, 使子进程成为后台进程
if(pid > 0)
exit(EXIT_SUCCESS);
// 子进程成为新会话的组长, 脱离父进程的会话期
if(setsid() == -1)
ERROR_EXIT("setsid failed!");
pid = fork();
if(pid < 0)
ERROR_EXIT("Second fork failed!");
// 因为会话组的组长有权限重新打开控制终端, 所以这里将子进程结束, 保留孙进程
// 孙进程不是会话组的组长所以没有权利再打开控制终端, 这样整个程序就与控制终端隔离了
if(pid > 0)
exit(EXIT_SUCCESS);
#ifdef RLIMIT_NOFILE
// 关闭从父进程继承来的文件描述符
// 如不关闭, 将会浪费系统资源, 造成进程所在的文件系统无法卸下以及引起无法预料的错误
if (getrlimit(RLIMIT_NOFILE, &rl) == -1)
ERROR_EXIT("getrlimit failed!");
nfiles = rl.rlim_cur = rl.rlim_max;
setrlimit(RLIMIT_NOFILE, &rl);
for(i=3; i<nfiles; i++)
close(i);
#endif
// 将标准的 3 个文件描述符 0, 1, 2 重定向到 /dev/null
if(fd = open("/dev/null", O_RDWR) < 0)
ERROR_EXIT("open /dev/null failed!");
for(i=0; i<3; i++)
dup2(fd, i);
if(fd > 2) close(fd);
// 进程活动时, 其工作目录所在的文件系统不能卸下, 一般需要将工作目录改变到根目录
// 进程从创建它的父进程那里继承了文件创建掩码, 它可能修改守护进程所创建的文件的存取位. 为防止这一点, 将文件创建掩码清除
chdir("/");
umask(0);
}
四、使用库函数 daemon() 创建守护进程
#include <unistd.h>
int daemon(int nochdir, int noclose);
/*
参数:
nochdir:=0 时将当前目录更改至“/”
noclose:=0 时将标准输入、标准输出、标准错误重定向至“/dev/null”
返回值:
成功:0
失败:-1
**/