A daemon process is a process which runs in background and has no controlling terminal.
守护进程是在后台运行并且没有控制终端的进程。
由于守护进程通常没有控制终端,所以,几乎不需要与用户交互。守护进程用于提供哪些没有任何用户交互就可以在后台做得很好的服务。
例如,一个在后台运行观察网络活动并且记录任何可疑通信日志的进程就可以开发成守护进程。
守护进程设计
开发守护进程就像开发其他进程一样,但有一件事情即没有控制终端使它与任何其他普通进程区别开来。这就是创建守护进程时的主要设计问题。这可以通过以下步骤实现:- 创建一个普通进程(父进程)
- 从上述父进程内部创建一个子进程
- 在这个时候,进程的层次关系看起来像:终端à父进程à子进程
- 终止父进程
- 子进程现在成为孤儿然后被init进程收养
- 在新会话中调用setsid()函数运行这个进程并拥有一个新的用户组
- 经过以上步骤之后,可以说现在这个进程成为没有控制终端的守护进程
- 将守护进程的工作目录改为根目录,然后关闭stdin、stdout和stderr文件描述符
- 让守护进程的主逻辑运行
C语言fork()函数
按以上所述的设计步骤创建可实际运行的守护进程之前,让我们首先了解一点fork()系统调用的知识。
fork()系统调用创建的进程是一个与父进程完全一样的复制品。这个新进程被称为‘子’进程。
这个系统调用(在父进程中)被调用一次,但返回两次(一次返回父进程,第二次返回子进程)。注意,在fork()系统调用之后,是父进程还是子进程先运行是不确定的。它完全依赖于上下文切换机制。这个调用在子进程中返回0而在父进程中返回子进程的PID。
这个系统调用的一些重要方面如下:
- 子进程拥有属于自己的惟一的进程ID,而且这一PID与现存任何进程组ID不一致。
- 子进程的父进程ID与父进程的进程ID相同。
- 子进程没有继承其父进程的内存锁。
- 子进程中的进程资源使用和CPU时间计数器重置为0。
- 子进程挂起信号集初始化为空。
- 子进程不继承父进程的信号量调节值。
- 子进程不继承父进程的记录锁。
- 子进程不继承父进程的定时器。
- 子进程不继承父进程未完成的异步I/O操作,也不继承父进程的任何异步I/O上下文。
实现
根据第一节提及的设计,完整的实现如下:#include
#include
#include
#include
#include
#include
int main(int argc, char* argv[])
{
FILE *fp= NULL;
pid_t process_id = 0;
pid_t sid = 0;
// Create child process
process_id = fork();
// Indication of fork() failure
if (process_id < 0)
{
printf("fork failed!n");
// Return failure in exit status
exit(1);
}
// PARENT PROCESS. Need to kill it.
if (process_id > 0)
{
printf("process_id of child process %d n", process_id);
// return success in exit status
exit(0);
}
//unmask the file mode
umask(0);
//set new session
sid = setsid();
if(sid < 0)
{
// Return failure
exit(1);
}
// Change the current working directory to root.
chdir("/");
// Close stdin. stdout and stderr
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// Open a log file in write mode.
fp = fopen ("Log.txt", "w+");
while (1)
{
//Dont block context switches, let the process sleep for some time
sleep(1);
fprintf(fp, "Logging info...n");
fflush(fp);
// Implement and call some function that does core work for this daemon.
}
fclose(fp);
return (0);
}
Following is the way through which the code was compiled and executed:
程序代码通过以下方法编译并执行:
$ gcc -Wall deamon.c -o deamon
$ sudo ./deamon
process_id of child process 2936
刚刚注意到控制立即回到终端,即守护进程现在与任何终端都没有关联。
当你查看位于根目录的log.txt文件时,可以看到,这个守护进程正在运行。
$
$ tail -f /Log.txt
Logging info...
Logging info...
Logging info...
Logging info...
Logging info...
Logging info...
Logging info...
Logging info...
Logging info...
Logging info...
另外一篇帖子的方法:
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <syslog.h> #include <errno.h> #include <unistd.h> #include <fcntl.h> #include <signal.h> #include <sys/wait.h> #define TCP_PORT 8888 int daemon(int,int); int fork2(void); void closeall(int); void errexit(const char *str) { syslog(LOG_INFO, "%s failed: %d (%m)", str, errno); exit(1); } void errreport(const char *str) { syslog(LOG_INFO, "%s failed: %d (%m)", str, errno); } /* 实际的子进程在此. */ void run_child(int sock) { FILE *in = fdopen(sock,"r"); FILE *out = fdopen(sock,"w"); int ch; setvbuf(in, NULL, _IOFBF, 1024); setvbuf(out, NULL, _IOLBF, 1024); while ((ch = fgetc(in)) != EOF) fputc(toupper(ch), out); fclose(out); } /* 这是守护程序的主要工作 -- 侦听连接并生成子进程 */ void process() { struct sockaddr_in addr; int addrlen = sizeof(addr); int sock = socket(AF_INET, SOCK_STREAM, 0); int flag = 1; int rc = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)); if (rc < 0) errexit("setsockopt"); addr.sin_family = AF_INET; addr.sin_port = htons(TCP_PORT); addr.sin_addr.s_addr = INADDR_ANY; rc = bind(sock, (struct sockaddr *) &addr, addrlen); if (rc < 0) errexit("bind"); rc = listen(sock, 5); if (rc < 0) errexit("listen"); for (;;) { rc = accept(sock, (struct sockaddr *) &addr, &addrlen); if (rc >= 0) switch (fork2()) { case 0: close(sock); run_child(rc); exit(0); case -1: errreport("fork2"); close(rc); break; default: close(rc); } } } int main() { if (daemon(0,0) < 0) { perror("daemon"); exit(2); } openlog("test", LOG_PID, LOG_DAEMON); process(); return 0; } /* closeall() -- 关闭所有>;=给定值的文件描述符 */ void closeall(int fd) { int fdlimit = sysconf(_SC_OPEN_MAX); while (fd < fdlimit) close(fd++); } /* daemon() - 将进程从用户端脱离并消失进入后台,若失败返回-1, * 但是在那种情况下你只能退出,因为我们可能已经生成了子进程。 * 这是基于BSD的版本,所以调用方需负责类似umask等等其它的工作。 */ /* 相信在所有Posix系统上都能工作 */ int daemon(int nochdir, int noclose) { switch (fork()) { case 0: break; case -1: return -1; default: exit(0); /* 原进程退出 */ } if (setsid() < 0) /* 不应该失败 */ return -1; /* 如果你希望将来获得一个控制tty,则排除(dyke)以下的switch语句 */ /* -- 正常情况不建议用于守护程序 */ switch (fork()) { case 0: break; case -1: return -1; default: _exit(0); } if (!nochdir) chdir("/"); if (!noclose) { closeall(0); open("/dev/null",O_RDWR); dup(0); dup(0); } return 0; } /* fork2() -- 类似fork函数,但子进程立刻变成孤儿进程 * (当它退出时不产生僵死进程) * 返回1给父进程,不是任何有意义的进程号. * 父进程不能使用wait函数等待子进程结束 (它们是无关的). */ /* 这个版本假设你没有捕获和忽略SIGCHLD信号. */ /* 如果你有设定,则不管怎样应使用fork函数 */ int fork2() { pid_t pid; int rc; int status; if (!(pid = fork())) { switch (fork()) { case 0: return 0; case -1: _exit(errno); /* 假设错误码都小于256 */ default: _exit(0); } } if (pid < 0 || waitpid(pid,&status,0) < 0) return -1; if (WIFEXITED(status)) if (WEXITSTATUS(status) == 0) return 1; else errno = WEXITSTATUS(status); else errno = EINTR; /* 唉,类似这个 :-) */ return -1; }