《unix环境高级编程》--- 守护进程

守护进程也称精灵进程,是生存期较长的一种进程。它们常常在系统自举时启动,仅在系统关闭时才终止。因为它们没有
控制终端,所以说它们是在后台运行的。

编写守护进程规则
1、调用umask将文件模式创建屏蔽字设置为0。
2、调用fork,然后使父进程退出。
3、调用setsid创建一个新会话。使调用进程:a、成为新会话的首进程,b、成为一个新进程组的组长进程,c、没有控制终端。
4、将当前工作目录改为根目录。
5、关闭不再需要的文件描述符。
6、某些守护进程打开/dev/null使其具有文件描述符0、1、2,这样,任何一个试图读标准输入、写标准输出或标准出错的库 例程都不会产生任何效果。

为什么会有以上几步:https://www.jianshu.com/p/d9bfbee5e915

#include "apue.h"
#include <syslog.h>
#include <fcntl.h>
#include <sys/resource.h>

void daemonize(const char *cmd)
{
	int i, fd0, fd1, fd2;
	pid_t pid;
	struct rlimit rl;
	struct sigaction sa;

	/* Clear file creation mask */
	umask(0);

	/* Get maximum number of file descriptors */
	if(getrlimit(RLIMIT_NOFILE, &rl) < 0)
		err_quit("%s: can't get file limit", cmd);

	/* Become a session leader to lose controlling TTY */
	if((pid = fork()) < 0)
		err_quit("%s: can't fork", cmd);
	else if(pid != 0)  /* parent */
		exit(0);
	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_quit("%s: can't ignore SIGHUP", cmd);
	if((pid = fork()) < 0)
		err_quit("%s: can't fork", cmd);
	else if(pid != 0)  /* parent */
		exit(0);

	/* Change the current working diretory to the root so
	   we won't prevent file systems from being unmounted */
	if(chdir("/") < 0)
		err_quit("%s: can't change directory to /", cmd);

	/* Close all open file descriptors */
	if(rl.rlim_max == RLIM_INFINITY)
		rl.rlim_max = 1024;
	for(i=0; i<rl.rlim_max; i++)
		close(i);

	/* Attach file descriptors 0, 1, 2 to /dev/null */
	fd0 = open("/dev/null", O_RDWR);
	fd1 = dup(0);
	fd2 = dup(0);

	/* Initialize the log file */
	/*
	   void openlog(const char *ident, int option, int facility);
	   ident:程序名称。
	   option:指定许多项的位屏蔽。
		       LOG_CONS---若日志消息不能通过UNIX域数据报送至syslogd,
                   则将该消息写至控制台。
	   facility:目的是可以让配置文件说明,来自不同设施的消息将以不同方式进行处理。
	   	         LOG_DAEMON---系统守护进程。
	*/
	openlog(cmd, LOG_CONS, LOG_DAEMON);
	if(fd0 != 0 || fd1 != 1 || fd2 != 2)
	{
		syslog(LOG_ERR, "unexpected file descriptors %d %d %d", fd0, fd1, fd2);
		exit(1);
	}
}

int main(void)
{
	daemonize("cron");
	pause();
	return 0;
}

结果
这里写图片描述
这里写图片描述
用ps命令验证,没有一个活动线程的ID是2625,说明守护进程在一个孤儿进程组中,它不是一个会话首进程,于是不会有机会分配到
一个控制终端。这是由第二个fork造成的。可见此守护进程已被正确初始化。

保证只运行某个守护进程的一个副本
文件锁和记录锁时可用来保证一个守护进程只有一个副本在运行。如果每个守护进程创建一个文件,并且在整个文件上加上一把写锁,那就只允许创建一把这样的写锁,所以在此之后如试图创建一把这样的写锁就将失败。当该守护进程终止时,这把锁将被自动删除。

single.h

#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <syslog.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <sys/stat.h>

#define LOCKFILE "/val/run/daemon.pid"
#define LOCKMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

/*extern int lockfile(int);*/

int already_running(void)
{
	int fd;
	char buf[16];

	fd = open(LOCKFILE, O_RDWR|O_CREAT, LOCKMODE);
	if(fd < 0)
	{
		/*
		   void openlog(const char *ident, int option, int facility);
		   void syslog(int priority, const char *fromat, ...);
		   如果不调用openlog,第一次调用syslog时,自动调用openlog
		   priority:facility和level的组合。level--LOG_ERR--出错状态。
		*/
		syslog(LOG_ERR, "can't open %s: %s", LOCKFILE, strerror(errno));
		exit(1);
	}

	/*
	   int lockf(int fd, int cmd, off_t len);
	   如果 len 为0,则锁定从当前偏移量到文件结尾的区域
	   #define F_LOCK 1 //互斥锁定区域
	*/
	if(lockf(fd, F_TLOCK, 0) < 0)
	{
		if(errno == EACCES || errno == EAGAIN)
		{
			close(fd);
			return (1);
		}
		syslog(LOG_ERR, "can't lock %s: %s", LOCKFILE, strerror(errno));
		exit(1);
	}
	ftruncate(fd, 0);
	sprintf(buf, "%ld", (long)getpid());
	write(fd, buf, strlen(buf)+1);
	return (0);
}

守护进程的惯例
1、若守护进程使用锁文件,那么该文件通常存放在/var/run目录中,文件名通常为name.pid。
2、若守护进程支持配置选项,那么配置文件通常存放在/etc目录中,文件名通常为name.conf。
3、守护进程可用命令行启动,但通常它们是由系统初始化脚本之一启动的(/etc/rc或/etc/init.d/)。
4、若一守护进程有一配置文件,那么当该守护进程启动时,它读该文件,但此之后一般不会再查看它。

守护进程重读配置文件
若管理员更改了配置文件,那么守护进程可能需要被停止,然后再启动,以使配置文件的更改生效。为避免此种麻烦,某些守护进程将捕捉SIGHUP信号,当接收到该信号时,重读配置文件。因为守护进程并不与终端结合,可能是无控制终端的会话首进程,可能是孤儿进程组的成员,所以守护进程不期望接收SIGHUP。

使用sigwait及多线程重读配置文件。
1、调用daemonize初始化守护进程。
2、调用alread_runing确保该守护进程只有一个副本在运行。
3、SIGHUP信号仍被忽略,需恢复对信号SIGHUP的系统默认处理方式,否则调用sigwait的线程不会见到该信号。
4、阻塞所有信号,然后创建一线程来处理信号,该线程只需等待SIGHUP和SIGTERM。当收到SIGHUP信号时,调用reread重读配置文件。当收到SIGTERM时,记录一消息,然后终止。

#include "apue.h"
#include <pthread.h>
#include <syslog.h>
#include "single.h"

sigset_t mask;

void reread(void)
{
	/* ... */
}

void *thr_fn(void *args)
{
	int err, signo;
	for(;;)
	{
		err = sigwait(&mask, &signo);
		if(err != 0)
		{
			syslog(LOG_ERR, "sigwait failed");
			exit(1);
		}
		switch(signo)
		{
		case SIGHUP:
			syslog(LOG_INFO, "Re-reading configuration file");
			reread();
			break;
		case SIGTERM:
			syslog(LOG_INFO, "got SIGTERM; exiting");
			exit(0);
		default:
			syslog(LOG_INFO, "unexpected signal %d\n", signo);
		}
	}
	return (0);
}

int main(int argc, char *argv[])
{
	int err;
	pthread_t tid;
	char *cmd;
	struct sigaction sa;

	if((cmd = strchr(argv[0], '/')) == NULL)
		cmd = argv[0];
	else
		cmd++;
	
	/* Become a daemon */
	daemonize(cmd);

	/* Make sure only one copy of the daemon is running */
	if(already_running())
	{
		syslog(LOG_ERR, "daemon already running");
		exit(1);
	}

	/* Restore 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_quit("%s: can't restore SIGHUP default");
	sigfillset(&mask);
	if((err = pthread_sigmask(SIG_BLOCK, &mask, NULL)) != 0)
		err_exit(err, "SIG_BLOCK err");
	
	/* Create a thread to handle SIGHUP and SIGTERM */
	err = pthread_create(&tid, NULL, thr_fn, 0);
	if(err != 0)
		err_exit(err, "can't create thread");

	/* Proceed with the rest of the daemon */
	/* ... */
	exit(0);
	
}

守护进程重读配置文件的另一种实现
守护进程无需使用多线程也可以捕捉SIGHUP并重读其配置文件。
在初始化守护进程后,为SIGHUP和SIGTERM配置信号处理程序。可将重读逻辑放在信号处理程序中,也可只在其中设置一个标志,有守护进程的主线程做所需的工作。

#include "apue.h"
#include <syslog.h>
#include <errno.h>
#include "single.h"

void reread(void)
{
	/* ... */
}

void sigterm(int signo)
{
	syslog(LOG_INFO, "got SIGTERM; exiting");
	exit(0);
}

void sighup(int signo)
{
	syslog(LOG_INFO, "Re-reading configuration file");
	reread();
}

int main(int argc, char *argv[])
{
	char *cmd;
	struct sigaction sa;
	
	if((cmd = strchr(argv[0], '/')) == NULL)
		cmd = argv[0];
	else
		cmd++;

	/* Become a daemon */
	daemonize(cmd);

	/* Make sure only one copy of the daemon is running */
	if(already_running())
	{
		syslog(LOG_ERR, "daemon already running");
		exit(1);
	}

	/* Handle signals of interest */
	sa.sa_handler = sigterm;
	sigemptyset(&sa.sa_mask);
	sigaddset(&sa.sa_mask, SIGHUP);
	sa.sa_flags = 0;
	if(sigaction(SIGTERM, &sa, NULL) < 0)
	{
		syslog(LOG_ERR, "can't catch SIGTERM: %s", strerror(errno));
		exit(1);
	}
	sa.sa_handler = sighup;
	sigemptyset(&sa.sa_mask);
	sigaddset(&sa.sa_mask, SIGTERM);
	sa.sa_flags = 0;
	if(sigaction(SIGHUP, &sa, NULL) < 0)
	{
		syslog(LOG_ERR, "can't catch SIGHUP: %s", strerror(errno));
		exit(1);
	}

	/* Proceed with the rest of the daemon */
	/* ... */
	exit(0);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值