本章将说明守护进程结构,以及如何编写守护进程程序。

守护进程,也就是通常说的Daemon进程,是Unix中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。

编程规则

在编写守护进程程序时需遵循一些基本规则,以防止产生不必要的交互作用。下面将说明这些规则。

1.调用umask将文件模式创建屏蔽字设置为一个已知值(通常是0)

2.调用fork,然后使父进程exit,保证了子进程不是进程组的组长进程,是下面进行setsid调用的先决条件

3.调用setsid创建一个新会话。然后它会执行3个步骤:(a)成为新会话的首进程 (b)成为一个新进程组的组长进程 (c)没有控制终端

4.将当前工作目录更改为根目录。

5.关闭不再需要的文件描述符

6.某些守护进程打开/dev/null使其具有文件描述符0、1、2

下面函数演示了创建守护进程的基本步骤

 #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(); /*
* Get maximum number of file descriptors.
*/
if (getrlimit(RLIMIT_NOFILE, &rl) < )
err_quit("%s: can't get file limit", cmd); /*
* Become a session leader to lose controlling TTY.
*/
if ((pid = fork()) < )
err_quit("%s: can't fork", cmd);
else if (pid != ) /* parent */
exit();
setsid(); /*
* Ensure future opens won't allocate controlling TTYs.
*/
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = ;
if (sigaction(SIGHUP, &sa, NULL) < )
err_quit("%s: can't ignore SIGHUP", cmd);
if ((pid = fork()) < )
err_quit("%s: can't fork", cmd);
else if (pid != ) /* parent */
exit(); /*
* Change the current working directory to the root so
* we won't prevent file systems from being unmounted.
*/
if (chdir("/") < )
err_quit("%s: can't change directory to /", cmd); /*
* Close all open file descriptors.
*/
if (rl.rlim_max == RLIM_INFINITY)
rl.rlim_max = ;
for (i = ; i < rl.rlim_max; i++)
close(i); /*
* Attach file descriptors 0, 1, and 2 to /dev/null.
*/
fd0 = open("/dev/null", O_RDWR);
fd1 = dup();
fd2 = dup(); /*
* Initialize the log file.
*/
openlog(cmd, LOG_CONS, LOG_DAEMON);
if (fd0 != || fd1 != || fd2 != ) {
syslog(LOG_ERR, "unexpected file descriptors %d %d %d",
fd0, fd1, fd2);
exit();
}
}

出错记录

因为守护进程本就不应该有控制终端,所以不能简单地把出错信息写到标准错误上。

syslog设施是一个集中的守护进程出错记录设施,下图演示了syslog设施的详细组织结构:

有以下3种产生日志消息的方法:

1.内核例程可以调用log函数。任何一个用户进程都可以通过打开并读取/dev/klog设备来读取这些信息。

2.大多数用户进程(守护进程)调用syslog函数来产生日志消息。这使消息被发送至UNIX域数据报套接字/dev/log。

3.可将日志消息发向UDP端口514

通常,syslogd守护进程读取所有3种格式的日志消息。此守护进程在启动时读一个配置文件,其文件名一般为/etc/syslog.conf,该文件决定了不同类型的消息应送向何处。

该设施的接口是syslog函数

#include <syslog.h>
void openlog(const char *ident,int option,int facility);
void syslog(int priority,const char *format,...);
void closelog(void);
int setlogmask(int maskpri);

调用openlog是可选的。如果不调用openlog,则在第一次调用syslog时,自动调用openlog。

调用closelog也是可选的。因为它只是关闭增被用于与syslogd守护进程进行通信的描述符。

调用openlog使我们可以指定一个ident(一般是程序的名称),以后,此ident将被加至每则日志消息中。

option参数是指定各种选项的位屏蔽。下图介绍了可用的option选项:

facility参数值选取自下图,用来让配置文件说明来自不同设施的消息将以不同的方式进行处理。

调用syslog产生一个日志消息。其priority参数是facility和level的组合。其中level的值按优先级由最高到最低一次排列如下:

将format参数以及其他所有参数传至vsprintf函数以便进行格式化。其中,format中每个出现的%m字符都先被替换成与errno值相对应的出错消息字符串。

setlogmask函数用于设置进程的记录优先级屏蔽字。各条消息除非已在记录优先级屏蔽字中进行了设置,否则将不被记录。

除了syslog,很多平台还提供它的一种辩题来处理可变参数列表。

#include <syslog.h>
#include <stdarg.h>
void vsyslog(int priority,const char *format,va_list arg);

单实例守护进程

为了正常运作,某些守护进程会实现为在任一时刻只运行该守护进程的一个副本。

文件和记录锁机制(第十四章)为一种方法提供了基础,该方法保证了一个守护进程只有一个副本,下面函数将演示这一点。

 #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 "/var/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[]; fd = open(LOCKFILE, O_RDWR|O_CREAT, LOCKMODE);
if (fd < ) {
syslog(LOG_ERR, "can't open %s: %s", LOCKFILE, strerror(errno));
exit();
}
if (lockfile(fd) < ) {
if (errno == EACCES || errno == EAGAIN) {
close(fd);
return();
}
syslog(LOG_ERR, "can't lock %s: %s", LOCKFILE, strerror(errno));
exit();
}
ftruncate(fd, );
sprintf(buf, "%ld", (long)getpid());
write(fd, buf, strlen(buf)+);
return();
}

其中lockfile函数如下

 #include <unistd.h>
#include <fcntl.h> int
lockfile(int fd)
{
struct flock fl; fl.l_type = F_WRLCK;
fl.l_start = ;
fl.l_whence = SEEK_SET;
fl.l_len = ;
return(fcntl(fd, F_SETLK, &fl));
}

守护进程实例

下面程序说明了守护进程可以重读其配置文件的一种方法

 #include "apue.h"
#include <pthread.h>
#include <syslog.h> sigset_t mask; extern int already_running(void); void
reread(void)
{
/* ... */
} void *
thr_fn(void *arg)
{
int err, signo; for (;;) {
err = sigwait(&mask, &signo);
if (err != ) {
syslog(LOG_ERR, "sigwait failed");
exit();
} switch (signo) {
case SIGHUP:
syslog(LOG_INFO, "Re-reading configuration file");
reread();
break; case SIGTERM:
syslog(LOG_INFO, "got SIGTERM; exiting");
exit(); default:
syslog(LOG_INFO, "unexpected signal %d\n", signo);
}
}
return();
} int
main(int argc, char *argv[])
{
int err;
pthread_t tid;
char *cmd;
struct sigaction sa; if ((cmd = strrchr(argv[], '/')) == NULL)
cmd = argv[];
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();
} /*
* Restore SIGHUP default and block all signals.
*/
sa.sa_handler = SIG_DFL;
sigemptyset(&sa.sa_mask);
sa.sa_flags = ;
if (sigaction(SIGHUP, &sa, NULL) < )
err_quit("%s: can't restore SIGHUP default");
sigfillset(&mask);
if ((err = pthread_sigmask(SIG_BLOCK, &mask, NULL)) != )
err_exit(err, "SIG_BLOCK error"); /*
* Create a thread to handle SIGHUP and SIGTERM.
*/
err = pthread_create(&tid, NULL, thr_fn, );
if (err != )
err_exit(err, "can't create thread"); /*
* Proceed with the rest of the daemon.
*/
/* ... */
exit();
}

下面程序说明一个单线程守护进程然后捕捉SIGHUP并重读其配置文件

 #include "apue.h"
#include <syslog.h>
#include <errno.h> extern int lockfile(int);
extern int already_running(void); void
reread(void)
{
/* ... */
} void
sigterm(int signo)
{
syslog(LOG_INFO, "got SIGTERM; exiting");
exit();
} 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 = strrchr(argv[], '/')) == NULL)
cmd = argv[];
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();
} /*
* Handle signals of interest.
*/
sa.sa_handler = sigterm;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGHUP);
sa.sa_flags = ;
if (sigaction(SIGTERM, &sa, NULL) < ) {
syslog(LOG_ERR, "can't catch SIGTERM: %s", strerror(errno));
exit();
}
sa.sa_handler = sighup;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGTERM);
sa.sa_flags = ;
if (sigaction(SIGHUP, &sa, NULL) < ) {
syslog(LOG_ERR, "can't catch SIGHUP: %s", strerror(errno));
exit();
} /*
* Proceed with the rest of the daemon.
*/
/* ... */
exit();
}

apue学习笔记(第十三章 守护进程)的更多相关文章

  1. APUE读书笔记-第13章-守护进程

    第13章 守护进程 13.1 引言 *守护进程也称精灵进程(daemon)是生存期较长的一种进程.它们常常在系统自举时启动,仅在系统关闭时才终止.因为它们没有控制终端,所以说它们是在后台运行的.UNI ...

  2. 《APUE》读书笔记第十三章-守护进程

    守护进程 守护进程是生存期较长的一种进程,它们常常在系统自举时启动,仅在系统关闭时才终止.因为它们没有控制终端,所以说它们是在后台运行的.UNIX系统由很多守护进程,它们执行日常事务活动. 本章主要介 ...

  3. UNP学习笔记(第十三章 守护进程和inetd超级服务器)

    关于守护进程可以查看apue的笔记 http://www.cnblogs.com/runnyu/p/4645046.html daemon_init函数 下面给出名为daemon_init函数,通过调 ...

  4. apue学习笔记(第九章 进程关系)

    本章将详细地说明进程组以及POSIX.1引入的会话的概念.还将介绍登录shell和所有从登录shell启动的进程之间的关系 终端登录 BSD终端登录.系统管理者创建通常名为/etc/ttys的文件,其 ...

  5. 《Unix环境高级编程》读书笔记 第13章-守护进程

    1. 引言 守护进程是生存期长的一种进程.它们常常在系统引导装入时启动,仅在系统关闭时才终止.它们没有控制终端,在后台运行. 本章说明守护进程结构.如何编写守护进程程序.守护进程如何报告出错情况. 2 ...

  6. Linux学习笔记(9)-守护进程

    明天学这个!! ---------------------------------------------------------- 守护进程(Daemon)是运行在后台的一种特殊进程.它独立于控制终 ...

  7. [汇编学习笔记][第十三章int指令]

    第十三章int指令 13.1 int指令 格式: int n, n 为中断类型码 可以用int指令调用任何一个中断的中断处理程序(简称中断例程). 13.4 BIOS和DOS 所提供的中断例程 BIO ...

  8. docker 学习笔记20:docker守护进程的配置与启动

    安装好docker后,需要启动docker守护进程.有多种启动方式. 一.服务的方式 因为docker守护进程被安装成服务.所以,可以通过服务的方式启停docker守护进程,包括查看状态. sudo ...

  9. apue学习笔记(第一章UNIX基础知识)

    总所周知,UNIX环境高级编程是一本很经典的书,之前我粗略的看了一遍,感觉理解得不够深入. 听说写博客可以提高自己的水平,因此趁着这个机会我想把它重新看一遍,并把每一章的笔记写在博客里面. 我学习的时 ...

随机推荐

  1. UVALive 6609 Minimal Subarray Length(RMQ-ST+二分)

    题意:给定长度为N的数组,求一段连续的元素之和大于等于K,并且让这段元素的长度最小,输出最小长度即可,若不存在这样的元素集合,则输出-1 题目链接:UVAlive 6609 做法:做一个前缀和pref ...

  2. [ZJOI2011][bzoj2229] 最小割 [最小割树]

    题面 传送门 思路 首先我们明确一点:这道题不是让你把$n^2$个最小割跑一遍[废话] 但是最小割过程是必要的,因为最小割并没有别的效率更高的算法(Stoer-Wagner之类的?) 那我们就要尽量找 ...

  3. Windows系统——后缀为.zip.00X的zip分卷解压

    Windows下后缀为*.zip.001文件的解压方法: 后缀为*.zip.001文件用winrar无法解压, 解决办法是在windows下打开命令行界面, 输入:copy /B xx.zip.001 ...

  4. Nodejs项目网页图标的处理

    今天,我要说的是Nodejs中,关于网页图标的处理. 在讲解怎么处理之前,我们的了解一下什么是网页图标.网页图标就是我们网页打开之后,标签页的图标,比如下面这个 前面的小人就是我们博客园的网页图标. ...

  5. 妹子(girls)

    妹子(girls) 题目描述 万人迷皮皮轩收到了很多妹子的礼物,由于皮皮轩觉得每个妹子都不错,所以将她们礼物的包装盒都好好保存,但长此以往皮皮轩的房间里都堆不下了,所以只能考虑将一些包装盒放进其他包装 ...

  6. vue单文件组件互相通讯

    在vue中,我们可以把一个页面各个部分单独封装起来,做成各种小组件,然后加载,这样的好处是代码维护起来比较容易,重复的代码多处调用! 在一个vue项目中,单文件组件以.vue形式文件命名 每个组件之间 ...

  7. [水] POJ 3096

    Surprising Strings Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 7659   Accepted: 487 ...

  8. js5:框架的使用,使框架之间无痕连接

    原文发布时间为:2008-11-08 -- 来源于本人的百度文章 [由搬家工具导入] <html> <head> <base target="js4" ...

  9. [MySQL] lock知识梳理

    MySQL Lock机制 INDEX: MySQL事务隔离级别 MVCC MySQL Lock类型 MySQL MDL CONTENT: 1. MySQL事务隔离级别 Read Uncommit RU ...

  10. Linux一些防攻击策略

    来自http://www.imooc.com/learn/344