APUE 笔记 守护进程

1. daemon 是后台进程

后台进程有些是内核的(kernel daemons),通常他们的父进程号是0,他们在系统启动阶段启动;有些是用户层的(user-level daemons),所有用户层后台程序是 process group leaders 和 session leaders,而且进程组和会话中只有它一个进程,它们中大部分的父进程是init;

在Ubuntu 10.10 下,ps -efjc 查看运行中的进程,可以看到像kswapd这样的内核后台进程,它们的父进程号是2,而不是0,进程号为2的进程就是kthreadd,如下图:

daemon 没有控制终端,显示为?


init 进程号是1,是第一个启动用户进程,用于启动系统服务;(具体参考LSAH p32)

initd 用于启动网络服务,不过不是在系统初始化时启动,而是延迟到有网络连接请求的时候在启动相应的服务,它会帧听配置文件中的所有网络端口,所以它也叫super-server;linux下的inet是xinetd,即extended inet;(具体参考man xinet)


2. 编程规则

1. 调用 umask 将文件模式创建屏蔽字设置为0,不屏蔽任何权限;

2. 调用 fork,任何使父进程退出;

执行 shell 命令的时候,shell 会 fork 一个进程,如果是通过 shell 命令来启动该 daemon 的,那么 shell 认为这条命令已经执行完毕了,而 daemon 程序已经在后台执行了;

子进程继承了父进程的进程组ID,但不是进程组的 leader,这是调用 setsid,并成为 session leader 的前提;

3. 调用 setsid 创建一个新会话。那么这个 daemon 程序就成为了新进程组的leader,而且没有控制终端,如果调用之前有,也会被解除关联。(控制终端,参考 9.6)

4. 当前工作目录改为根目录。daemon 的父进程的当前目录可以是一个 mounted 的目录,如果daemon继承后不改变的话,那么这个文件系统不能在 daemon 运行的时候不能被卸载,这不是我们希望的,而根文件系统随系统一直存在;

一些特殊的 daemon 也会设置到其他目录;

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

6. 在/dev/null 上 打开文件描述符 0, 1, 2,这么做是为了关闭不必要的交互功能,因为有些例程试图读取标准输入/输出/错误;因为没有控制终端,所以 daemon 的任何输出,不会出现在认识显示设备上,也会从交互设备上的到任何输入(键盘等);同时,会话的关闭也不会影响 daemon ;


下面是一个书中 daemon 初始化的代码,关注第二个 fork:

#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 */ // 结束 shell 命令父进程
        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");
    if ((pid = fork()) < 0) // 保证 daemon 不是 seesion leader,不会有机会得到控制终端,参考 1.3.3 ruler 3,9.6
        err_quit("%s: can't fork", cmd);
    else if (pid != 0) /* parent */ 
        exit(0);
    /*
     * Change the current working directory to the root so
     * we won't prevent file systems from being unmounted.
     */
    if (chdir("/") < 0)
        err_quit("%s: can't change directory to /");
    /*
     * 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, and 2 to /dev/null.
     */
    fd0 = open("/dev/null", O_RDWR);
    fd1 = dup(0);
    fd2 = dup(0);
    /*
     * Initialize the log file.
     */
    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);
    }
}


3. 日志

daemon 不能写写终端,它通过日志记录出错信息;

产生日志消息的三种方式:

内核调用 log 函数

大多数用户进程调用 syslog 函数

通过网络编程,发送 UDP 数据包到端口 514

上面三种方式产生的日志,都会被 syslogd 读取,syslogd 相关资料参考 LAH 第10章


用户进程调用的 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);
indent 指向的字符串会被加入到日志消息中去,一般是写 程序名称;

option 指示日志消息处理方式,LOG_CONS 必要是发送到终端;LOG_NDELAY 直接打开用于发送日志消息的 socket ,而不能等到写入第一个消息的时候才产生,和LOG_ODELAY相反;LOG_NOWAIT 和等待子进程的 wait 函数相关;LOG_PERROR 不仅把日志消息发送非 syslogd ,还发送到标准输出上;LOG_PID 消息中包含进程ID

facility 表示消息的来源,或者类型


Figure 13.4. The facility argument for openlog

level 指示日志消息的重要程度,这里可以了解一些单词的用法:emergency > alert > critical > error > warning > notice > information > debug


例子:

syslog (LOG_ERR | LOG_LPR, "open error for %s: %m", filename);

syslog 之前没有 openlog的话,可以通过上面的方式来指定 priority 类型;

4. daemon 有时候需要保证只有一个例程在运行,这是需要用到文件和记录锁机制:daemon 创建一个文件,然后给这个文件加一把写入锁。-->这个文件一般就是 /var/run/ 下面的 *.pid 文件;


5. Daemon Conventions

1. 锁文件通常是/var/run/*.pid;

2. 配置文件通常在/etc/,名字为name.conf,如/etc/syslog.conf;

3. 守护进程可用命令行启动,通常由系统初始化脚本( /etc/rc* 或者 /etc/inittab )启动。如果需要在守护进程终止是,能自动重启它,需要在 /etc/inittab 中为该守护进程添加 _respawn 记录项;

4. 守护进程可以通过捕捉 SIGHUP 信号,来重新读取配置文件;






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值