Linux守护进程设计

守护(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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值