daemon程序包括第一次fork、setsid、第二次fork、改变工作目录、关闭所有打开的描述符以及重定向stdin、stdout、stderr。
setsid发挥的作用是使得当前进程成为新会话的会话头进程以及新进程组的进程组头进程,从而不再有控制终端。
将stdin、stdout和stderr重定向到/dev/null,其中一个原因是打开正常文件占用这些描述符时,诸如perror等之类函数会把非预期的数据写到正常文件中。
daemon程序中setsid的作用以及重定向stdin、stdout、stderr的原因,验证如下:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#define MAX_FD 64
typedef void (*sighandler_t)(int);
int main()
{
sighandler_t ret = NULL;
int fd = -1;
pid_t pid = -1;
if ((pid=fork()) < 0)
{
perror("fork error");
return -1;
}
else if (pid > 0)//parent
exit(0);
if ((fd=open("/dev/tty", O_RDONLY)) < 0) //是否有控制终端,通过打开/dev/tty来判断。
perror("open tty error");
else
{
perror("open tty ok");
close(fd);
}
sleep(10);
if (setsid() < 0)
{
perror("setsid error");
return -1;
}
if ((fd=open("/dev/tty", O_RDONLY)) < 0) //是否有控制终端,通过打开/dev/tty来判断。
perror("open tty error");
else
{
perror("open tty ok");
close(fd);
}
if ((ret=signal(SIGHUP, SIG_IGN)) == SIG_ERR)
{
perror("signal error");
return -1;
}
sleep(10);
if ((pid=fork()) < 0)
{
perror("fork error");
return -1;
}
else if (pid > 0)
exit(0);
umask(022);
chdir("/");
perror("close fd begin...");
for (fd=0; fd<MAX_FD; fd++)
close(fd);
perror("close fd end...");
perror("open fd start...");
open("/dev/null", O_RDONLY);
open("/dev/null", O_RDWR);
//open("/dev/null", O_RDWR);
open("log", O_CREAT | O_RDWR);
perror("open fd end...");
sleep(20);
return 0;
}
终端打印:
open tty ok: Success
open tty error: No such device or address
close fd begin...: No such device or address
log文件内容:
open fd end...: Bad file descriptor
ps查看进程情况:
STAT EUID RUID TT TPGID SESS PGRP PPID PID %CPU COMMAND
S 0 0 pts/0 19585 19585 6533 1 6534 0.0 start_daemon (在第一次fork之后,setsid之前)
Ss 0 0 ? -1 6534 6534 1 6534 0.0 start_daemon (在setsid之后,第二次fork之前)
S 0 0 ? -1 6534 6534 1 6636 0.0 start_daemon (在第二次fork之后)
结论:
在上述daemon程序中,setsid后进程组ID改变了,没有控制终端了;当打开log文件占用描述符std_err时,perror将内容写到log中了,所以为了防止不期望的事情发生,
std_in、std_out、std_err需要重定向到/dev/null。