守护(daemon)进程是为系统提供某种服务的进程,跟随系统启动时启动,在系统关闭时才终止,没有控制终端,属于后台运行的进程。它的设计有如下一些规则和惯例:
1.设置umask(0),umask设置的是拒绝的访问权限位,和chomd命令正好相反,为了防止由父进程继承该umask,这里需要把默认文件模式的创建屏蔽字设置为0,也就是不拒绝任何权限设置。
2.调用fork(),然后使父进程退出exit,这么做防止我们从shell启动,父进程退出就直接退出了。子进程继续以后台形式运行,脱离控制终端,同时保证子进程可以执行setsid创建新会话。
3.调用setsid()创建新会话,使子进程完全独立出来,此时子进程变为一个会话首进程,也成为一个进程组长,没有了控制终端。
4.再次调用fork()并且退出父进程,继续以子进程运行,这一步的目的是为了失去进程组长身份,保证后续即使主动打开一个终端,也不会自动成为控制终端。
5.chdir("/")将当前工作目录修改为根目录,之所以这么做是防止进程占用一个可能未来被卸载的目录
6.关闭所有从父进程继承过来的文件描述符
7.打开/dev/null,使其成为0,1,2文件描述符,也就是标准输入、标准输出,标准错误都指向了/dev/null。
8.在/var/run/{name}.pid处创建一个记录锁文件,保证守护进程是单实例运行的
9.守护进程的配置文件一般放置到/etc/{name}.conf中。
10.启动守护进程在/etc/rc*中配置,如果守护进程终止,需要自动重启它,可以在/etc/inittab中为守护进程配置上respawn选项。
11.守护进程log记录一般都是使用logd,为了嵌入式系统使用,也可以自己把log输出到特定文件保存。
示例:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <pthread.h>
#define CONFIGFILE "/etc/daemon.conf"
#define LOCKFILE "/var/run/daemon.pid"
#define LOCKMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
#define pr_debug(fmt,...) do{ printf("[%ld]DEBUG:"fmt,(long)getpid(),##__VA_ARGS__); fflush(stdout); }while(0)
#define pr_info(fmt,...) do{ printf("[%ld]INFO:"fmt,(long)getpid(),##__VA_ARGS__); fflush(stdout); }while(0)
#define pr_error(fmt,...) do{ printf("[%ld]ERROR:"fmt,(long)getpid(),##__VA_ARGS__);fflush(stdout); }while(0)
#define err_exit(fmt,...) do{ printf("[%ld]ERROR:"fmt,(long)getpid(),##__VA_ARGS__); exit(1); }while(0)
int file_lock(int fd)
{
struct flock fl;
/*
* Get lock for the whole file
*/
fl.l_type = F_WRLCK;
fl.l_start = 0;
fl.l_whence = SEEK_SET;
fl.l_len = 0;
return(fcntl(fd, F_SETLK, &fl));
}
int is_daemon_running(void)
{
int fd;
char buf[16];
fd = open(LOCKFILE, O_RDWR|O_CREAT, LOCKMODE);
if (fd < 0) {
err_exit("can't open %s: %s\n", LOCKFILE, strerror(errno));
}
/*
* Get the file lock
*/
if (file_lock(fd) < 0) {
if (errno == EACCES || errno == EAGAIN) {
close(fd);
return 1;
}
err_exit("can't lock %s: %s\n", LOCKFILE, strerror(errno));
}
/*
* Cut the file to offset 0
*/
ftruncate(fd, 0);
sprintf(buf, "%ld", (long)getpid());
/*
* Update pid to the lock file
*/
write(fd, buf, strlen(buf)+1);
return 0;
}
void reread(void)
{
/* ... */
pr_info("trigger re-read from config file\n");
}
void *thr_fn(void *arg)
{
int err, signo;
sigset_t mask;
sigfillset(&mask);
for (;;) {
err = sigwait(&mask, &signo);
if (err != 0) {
err_exit("sigwait failed\n");
}
switch (signo) {
case SIGHUP:
pr_info("Re-reading configuration file\n");
reread();
break;
case SIGTERM:
pr_info("Catch SIGTERM; exiting\n");
exit(0);
default:
pr_info("Unexpected signal %d\n", signo);
}
}
return(0);
}
void signal_setup(void)
{
int err;
struct sigaction sa;
sigset_t mask;
pthread_t tid;
/*
* Set SIGHUP default and block all signals.
*/
sa.sa_handler = SIG_DFL;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0)
err_exit("can't restore SIGHUP default\n");
/*
* Block all signals in main thread
*/
sigfillset(&mask);
if ((err = pthread_sigmask(SIG_BLOCK, &mask, NULL)) != 0)
err_exit("SIG_BLOCK error\n");
/*
* Create a child thread to handle SIGHUP and SIGTERM.
*/
err = pthread_create(&tid, NULL, thr_fn, 0);
if (err != 0)
err_exit("can't create thread\n");
}
int main(int argc, char *argv[])
{
int i, fd0, fd1, fd2;
pid_t pid;
struct rlimit rl;
struct sigaction sa;
/*
* Clear file creation mask.
*/
umask(0);
/*
* Fork first time, parent should exit
*/
if ((pid = fork()) < 0)
err_exit("fork failed, can't fork\n");
else if (pid != 0)
exit(0);
/*
* setsid() become a session leader to lose controlling TTY.
*/
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_exit("sigaction can't ignore SIGHUP\n");
/* Fork again, parent should exit */
if ((pid = fork()) < 0)
err_exit("fork failed, can't fork\n");
else if (pid != 0)
exit(0);
/*
* Change the current working directory to the root so
* we won't prevent file systems from being unmounted.
*/
if (chdir("/") < 0)
err_exit("can't change directory to /\n");
/*
* Close all open file descriptors.
*/
if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
err_exit("can't get file limit\n");
if (rl.rlim_max == RLIM_INFINITY)
rl.rlim_max = 1024;
for (i = 0; i < rl.rlim_max; i++)
close(i);
/*
* Normally, we should attach file descriptors 0, 1, and 2 to /dev/null.
* But here, we just redirect stdout and stderr to logfile for log print
*/
fd0 = open("/dev/null", O_RDWR);
fd1 = open("/tmp/daemon.log", O_RDWR | O_APPEND | O_CREAT, (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH));
fd2 = dup(1);
/*
* Make sure only one daemon process is running.
*/
if (is_daemon_running())
err_exit("daemon already running, just exit\n");
/*
* Setup signal handlers
*/
signal_setup();
pr_info("Daemon start!\n");
/*
* Proceed with the rest of the daemon.
*/
while(1);
exit(0);
}
编译过程:
gcc daemon.c -o daemon -lpthread
运行:
sudo chown root daemon
sudo chmod +s daemon
./daemon
之所以需要改变文件所有者为root,并且设置+s标志位,目的是为了能够以root权限运行,否则受权限影响,记录锁是无法在/var/run目录创建成功的,当然你可以选择修改记录锁的位置。
另外由于没有控制终端,所以把该daemon的标准输出和标准错误输出都重定向到了/tmp/daemon.log,当服务启动后,我们可以查看对应的log文件:
[24861]INFO:Daemon start!
[24861]INFO:Re-reading configuration file
[24861]INFO:trigger re-read from config file
[24861]INFO:Re-reading configuration file
[24861]INFO:trigger re-read from config file
[24861]INFO:Re-reading configuration file
[24861]INFO:trigger re-read from config file
[24861]INFO:Catch SIGTERM; exiting