`

守护进程惯例

阅读更多
    UNIX 系统中,守护进程遵循下列通用惯例。
    (1)若守护进程使用锁文件,则该文件通常存储在 /var/run 目录中。不过守护进程可能需要具有超级用户权限才能在此目录下创建文件。锁文件的名字通常是 name.pid,其中,name 是该守护进程或服务的名字。例如,cron 守护进程锁文件的名字是 /var/run/crond.pid。
    (2)若守护进程支持配置选项,则配置文件通常存放在 /etc 目录中,名字一般为 name.conf,其中,name 是该守护进程或服务的名字。例如,syslogd 守护进程的配置文件通常是 /etc/syslog.conf。
    (3)守护进程可用命令行启动,但通常它们是由系统初始化脚本之一(/etc/rc* 或 /etc/init.d/*)启动的。如果要在守护进程终止时自动地重新启动它,可以在 /etc/inittab 中为该守护进程包括 respawn 记录项,这样 init 就将重新启动该守护进程。
    (4)守护进程的配置文件一般会在守护进程启动时读取(如果有的话),之后就不会再查看。若更改了配置文件,则该守护进程可能需要重启以使更改生效。为避免这种麻烦,某些守护进程会捕捉 SIGHUP 信号。当它们接收到该信号时,就重新读取配置文件。因为守护进程并不与终端相结合,它们或者是无控制终端的会话首进程,或者是孤儿进程组的成员,所以没有理由接收 SIGHUP,因而可以安全地重复使用 SIGHUP。
    此外,为了正常运作,某些守护进程在任一时刻只允许运行该守护进程的一个副本。例如,该守护进程可能需要排他地访问一个设备。这种情况可以采取文件和记录锁机制。这种方法保证一个守护进程只有一个副本在运行。如果每一个守护进程创建一个有固定名字的文件,并对该文件加一把写锁,那么只允许创建一把这样的写锁,之后创建写锁的尝试都会失败。而在该守护进程终止时,这把锁将被自动删除。
    下面这个函数演示了如何使用文件和记录锁来保证只允许一个守护进程的一个副本。其中的 lockfile 函数的实现见后面的fcntl 记录锁一节。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <syslog.h>
#include <sys/stat.h>

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

extern int lockfile(int);

int already_running(void){
	int fd = open(LOCKFILE, O_RDWR|O_CREAT, LOCKMODE);
	if(fd < 0){
		syslog(LOG_ERR, "can't open %s: %s", LOCKFILE, strerror(errno));
		exit(1);
	}
	if(lockfile(fd) < 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);
	char buf[16];
	sprintf(buf, "%ld", (long)getpid());
	write(fd, buf, strlen(buf)+1);
	return 0;
}

    这里,如果操作的文件已经加了锁,那么 lockfile 函数就会失败,errno 会被设置为 EACCES 或 EAGAIN,表明该守护进程已在运行。否则将文件长度截断为 0,并写入进程 ID(将文件长度截断为 0 的原因是之前的守护进程的进程 ID 字符串可能长于当前的进程 ID 字符串。比如以前为 12345,现在的为 9999,那么写入后,文件中留下的将是 99995 而非 9999)。
    接下来的这个程序演示了守护进程在多线程中重读其配置文件的一种方法。其中用到了上面定义的这个 already_running 函数,而另一个函数 daemonize 的实现见守护进程编写规则与出错记录
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <syslog.h>
#include <pthread.h>

extern void daemonize(const char *);
extern int already_running(void);

sigset_t mask;

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

void *thr_fn(void *arg){
	int signo;
	for(;;){
		if(sigwait(&mask, &signo) != 0){
			syslog(LOG_ERR, "sigwait error");
			exit(1);
		}
		switch(signo){
		case SIGHUP:
			syslog(LOG_INFO, "reread configuration");
			reread();
			break;
		case SIGTERM:
			syslog(LOG_INFO, "got SIGTEM; thread exit");
			exit(0);
		default:
			syslog(LOG_INFO, "unexpected signal %d\n", signo);
		}
	}
	return 0;
}

int main(int argc, char *argv[]){
	char *cmd = NULL;
	if((cmd = strrchr(argv[0], '/')) == NULL)
		cmd = argv[0];
	else
		cmd++;
	daemonize(cmd);			// become a daemon
	if(already_running()){	// ensure only one copy of the daemon is running.
		syslog(LOG_ERR, "daemon already running");
		exit(1);
	}
	struct sigaction act;
	act.sa_handler = SIG_DFL;	// restore SIGHUP default
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	if(sigaction(SIGHUP, &act, NULL) != 0){
		syslog(LOG_ERR, "can't restore SIGHUP default");
		exit(1);
	}
	sigfillset(&mask);			// and block all signals
	if(pthread_sigmask(SIG_BLOCK, &mask, NULL) != 0){
		syslog(LOG_ERR, "pthread_sigmask error");
		exit(1);
	}
	pthread_t tid;		// create a thread to handle SIGHUP and SIGTERM
	if(pthread_create(&tid, NULL, thr_fn, 0) != 0){
		syslog(LOG_ERR, "pthread_create error\n");
		exit(1);
	}
	/* preceed with the rest of the daemon */
	for(;;){
		pause();
	}
	exit(0);
}

    这里要注意的是,在 daemonize 函数中修改了 SIGHUP 的默认处理行为,因此应该在调用之后恢复默认处理动作,否则调用 sigwait 的线程决不会见到该信号。如同对多线程程序所推荐的那样,这里阻塞了所有信号,并创建了单独的一个线程来负责处理 SIGHUP 和 SIGTERM 信号。因为 SIGHUP 和 SIGTERM 的默认动作是终止进程,所以在阻塞了这些信号后,当它们中的其中一个被发送到守护进程时,守护进程不会消亡。
    运行结果如下(省略了 grep 自身所在的行和一些不重要的列):
$ sudo ./rereadConfig.out             # 需要 root 权限来运行
$ ps -efj | grep rereadConfig.out     # 查看守护进程
UID      PID     PPID  PGID    SID     TTY    CMD  
root     109758  1     109757  109757  ?      ./rereadConfig.out
$
$ sudo kill -s SIGHUP 109758          # 发送 SIGHUP 信号来重读配置
$ ps -efj | grep rereadConfig.out     # SIGHUP 信号不会终止守护进程
UID      PID     PPID  PGID    SID     TTY    CMD  
root     109758  1     109757  109757  ?      ./rereadConfig.out
$ 
$ sudo kill -s SIGTERM 109758         # 发送 SIGTERM 信号来重读配置
$ ps -efj | grep rereadConfig.out     # SIGTERM 信号终止了守护进程
$

    当然,若要使用单线程守护进程来重读配置,可以在初始化守护进程后,分别为 SIGHUP 和 SIGTERM 配置信号处理程序,然后将重读逻辑放在信号处理程序中,也可以在信号处理程序中设置一个标志,并由守护进程的主线程来完成所有的工作。比如上面这个程序可以这样修改。
int main(){
	...
	sa.sa_handler = sigterm;
	sigemptyset(&sa.sa_mask);
	sigaddset(&sa.sa_mask, SIGHUP);
	sa.sa_flags = 0;
	sigaction(SIGTERM, &sa, NULL);
	sa.sa_handler = sighup;
	sigemptyset(&sa.sa_mask);
	sigaddset(&sa.sa_mask, SIGTERM);
	sa.sa_flags = 0;
	sigaction(SIGHUP, &sa, NULL);
	...
}
分享到:
评论

相关推荐

    学习《高级Linux环境编程》读书笔记(APUE读书笔记)

    这里会介绍守护进程的设计规则、系统日志机制、daemon函数以及守护进程设计中的一些常见惯例。 14. 高级I/O:包括记录锁、SysVSTREAMS机制、非阻塞I/O、I/O多路转接、异步I/O、readv和writev函数、存储映射I/O等。 ...

    我的APUE2读书笔记

    守护进程设计的另外一些常见惯例** 如双叉技术等。 #### 第十四章 高级I/O **1. 记录锁** 用于防止文件的多个进程同时写入同一位置。 **2. SysV STREAMS机制** 早期Unix系统提供的流式I/O框架。 **3. 非阻塞I/O...

    UNIX环境高级编程_第二版中文

    13.6 守护进程的惯例  13.7 客户进程-服务器进程模型  13.8 小结  习题  第14章 高级I/O  14.1 引言  14.2 非阻塞I/O  14.3 记录锁  14.4 STREAMS  14.5 I/O多路转接  14.5.1 select和pselect...

    APUE读书笔记(Unix高级环境编程)

    - **守护进程设计的惯例**:例如错误处理、资源释放等。 #### 第十四章 高级I/O - **记录锁**:防止多个进程同时修改同一文件。 - **SysV STREAMS机制**:用于实现网络通信的一种模型。 - **非阻塞I/O**:使I/O...

    UNIX环境高级编程(第二版中文)

    13.6 守护进程的惯例 350 13.7 客户进程-服务器进程模型 354 13.8 小结 354 习题 354 第14章 高级I/O 355 14.1 引言 355 14.2 非阻塞I/O 355 14.3 记录锁 357 14.4 STREAMS 370 14.5 I/O多路转接...

    unix环境编程电子书

    守护进程 341 13.1 引言 341 13.2 守护进程的特征 341 13.3 编程规则 342 13.4 出错记录 345 13.5 单实例守护进程 348 13.6 守护进程的惯例 350 13.7 客户进程-服务器进程模型 354 13.8 小结 354 ...

    UNIX环境高级编程_第2版.part1

    13.6 守护进程的惯例350 13.7 客户进程-服务器进程模型354 13.8 小结354 目录 3 习题354 第14章高级i/o355 14.1 引言355 14.2 非阻塞i/o 355 14.3 记录锁357 14.4 streams 370 14.5 i/o多路转接379 ...

    UNIX环境高级编程_第2版.part2

    13.6 守护进程的惯例350 13.7 客户进程-服务器进程模型354 13.8 小结354 目录 3 习题354 第14章高级i/o355 14.1 引言355 14.2 非阻塞i/o 355 14.3 记录锁357 14.4 streams 370 14.5 i/o多路转接379 ...

    宋劲彬的嵌入式C语言一站式编程

    34. 终端、作业控制与守护进程 1. 终端 1.1. 终端的基本概念 1.2. 终端登录过程 1.3. 网络登录过程 2. 作业控制 2.1. Session与进程组 2.2. 与作业控制有关的信号 3. 守护进程 35. 线程 1. 线程的概念 2. 线程控制 ...

    Java专业术语标准化规范表

    - **Daemon**:守护进程,在后台持续运行的程序,通常用于执行后台任务。 - **DAO (Data Access Object)**:数据访问对象,一种软件设计模式,用于提供应用程序与数据存储之间的接口。 - **Data**:数据,信息的...

Global site tag (gtag.js) - Google Analytics