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

守护进程,也就是通常说的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. 【bzoj1316】树上的询问 树的点分治+STL-set

    题目描述 一棵n个点的带权有根树,有p个询问,每次询问树中是否存在一条长度为Len的路径,如果是,输出Yes否输出No. 输入 第一行两个整数n, p分别表示点的个数和询问的个数. 接下来n-1行每行 ...

  2. Proteus中常用元器件名字

    运放   OPAMP 稳压管  zener MOS管 IRC 串口  COMPIM

  3. 在Visual studio 2010中为C#的“///”注释内容生成XML文档 .

    实际上该方法适合于所有版本的Visual studio,方法很简单,设置一下Visual studio的项目属性和工具选项即可. 1.在菜单栏的“Project”中选择当前项目的“*** Proper ...

  4. idea工具开发注意事项

    pom.xml中不需要有包 <dependency> <groupId>javax</groupId> <artifactId>javaee-api&l ...

  5. ZJOI2017D1

    假装我还活着. 去温州前沉迷各种奇怪的动画片..嗯补了不少高达.. 到温州以后继续看片..嗯ZG还是挺不错的..然后接着就FA♂现我什么都不会写..有点尴尬.. 因为宾馆离温州中学比较远就完全没去听课 ...

  6. 最长k可重区间集(cogs 743)

    «问题描述:«编程任务:对于给定的开区间集合I和正整数k,计算开区间集合I的最长k可重区间集的长度.«数据输入:由文件interv.in提供输入数据.文件的第1 行有2 个正整数n和k,分别表示开区间 ...

  7. GridView 动态绑定控件 OnRowCommand事件触发

    主题:GridView动态生成的控件不能触发OnRowCommand事件,且点击控件按钮后,控件的值会消失. 案例, 由于公司需要绑定的数据列顺序是动态生成的,且有的数据列需要绑定Button控件.所 ...

  8. AVRStudio 6 设置F_CPU时钟频率

    具体如下: 1>右键项目属性 2>根据语言选择一下,C或C++

  9. 语法错误: 标识符“__RPC__out_xcount_part” 解决方法

    1.错误描述 2.解决方案:将 $(DXSDK_DIR)\Include; 放到最后面,如下

  10. 《Linux命令行与shell脚本编程大全 第3版》Linux命令行---20

    以下为阅读<Linux命令行与shell脚本编程大全 第3版>的读书笔记,为了方便记录,特地与书的内容保持同步,特意做成一节一次随笔,特记录如下: