APUE------守护进程

守护进程是生存期长的一种进程。他们常常在系统引导装入时启动,仅在系统关闭时才终止。因为他们没有控制终端,所以说它们是在后台运行的。

守护进程的特征

系统进程依赖于操作系统实现。父进程ID为0的各进程通常是内核进程,他们作为系统引导装入过程的一部分而启动。内核进程是特殊的,通常存在于系统的整个生命期中。他们以超级 用户特权运行,无控制终端,无命令行。

编程规则

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

1. 首先要做的是调用umask将文件模式创建屏蔽字设置为一个已知值。由继承得来的文件模式创建屏蔽字可能会被设置为拒绝某些权限。如果守护进程要创建文件,那么它可能要设置特定的权限。

2. 调用fork,然后使父进程exit。这样做实现了下面几点。第一,如果该守护进程是作为一条简单的shell命令启动的,那么父进程终止会让shell认为这条命令已经执行完毕。第二,虽然子进程继承了父进程的进程组ID,但获得了一个新的进程ID,这就保证了子进程不是一个进程组的组长进程。这是下面将要进行的setsid调用的先决条件。

3. 调用setsid创建的一个新会话。是调用进程:a.称为新会话的首进程,b.称为一个新进程组的组长进程,c.没有控制终端

4. 将当前工作目录更改为根目录。从父进程处继承过来的当前工作目录可能在一个挂载的文件系统中。因为守护进程通常在系统再引导之前是一直存在的,所以如果守护进程的当前工作目录在一个挂载文件系统中,那么该文件系统就不能被卸载。

或者,某些守护进程还可能会把当前工作目录更改到某个指定位置,并在此位置进行它们的全部工作。

5. 关闭不再需要的文件描述符。这使守护进程不再持有从其父进程继承来的任何文件描述符。可以使用open_max函数或getrlimit函数来判定最高文件描述符值,并关闭直到该值的所有描述符。

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     r1;
    struct sigaction  sa;

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

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

    /*Become a session leader to lose controlling TTY*/
    if((pid = fork()) < 0)
        err_qiut("%s: can‘t fork", cmd);
    else if(pid != 0)
        exit(0);
    setsid();

    /*Ensure future opens won‘t allocate controlling TTYs*/
    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    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)
        exit(0);

    /*Change the current working directory to the root so
    *wo  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(r1.rlim_max == RLIM_INFINITY)
        r1.rlim_max = 1024;
    for(i = 0; I < r1.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种产生日志消息的方法。

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

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

3. 无论一个用户进程是在此主机上,还是在通过TCP/IP网络连接到此主机的其他主机上,都可将日志消息发向UDP端口514。注意,syslog函数从不产生这些UDP数据报,它们要求产生此日志消息的进程进行显示的网络编程。

通常,syslogd守护进程读取所有3种格式的日子消息。此守护进程在启动时读一个配置文件,其文件名一般为/etc/syslog.conf,该文件决定了不同种类的消息应送向何处。例如,紧急消息可发送至系统管理员,并在控制台上打印,而警告消息则可记录到 一个文件中。

#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将被加至每则日志消息中。ident一般是程序的名称。option参数是指定各种选项的位屏蔽。

option 说明
LOG_CONS 若日志消息不能通过UNIX域数据报送至syslogd,则将该消息写至控制台
LOG_NDELAY 立即打开至syslogd守护进程的UNIX域数据报套接字,不要等到第一条消息已经被记录时再打开。通常,在记录第一条消息之前,不打开该套接字
LOG_NOWAIT 不要等待在将消息记入日志过程中可能已创建的子进程。
LOG_ODELAY 在第一条消息被记录之前延迟打开至syslogd守护进程的连接
LOG_PERROR 除将日志消息发送给syslogd以外,还将它写至标准出错
LOG_PID 记录每条消息都要包含进程ID。此选项可供对每个不同的请求都fork一个子进程的守护进程使用。

openlog的facility参数值选自如下

facility 说明
LOG_AUDIT 审计设施
LOG_AUTH 授权程序:login、su、getty等
LOG_ AUTHPRIV 与LOG_AUTH相同,但写日志文件时具有权限限制
LOG_CONSOLE 消息写入/dev/console
LOG_CRON cron和at
LOG_DAEMON 系统守护进程:inetd、routed等
LOG_FTP FTP守护进程
LOG_KERN 内核产生的消息
LOG_LOCAL0~7 保留由本地使用
LOG_LPR 行式打印机系统:lpd、lpc等
LOG_MAIL 邮件系统
LOG_NEWS Usenet网络新闻系统
LOG_ NTP 网络时间协议系统
LOG_SECURITY 安全子系统
LOG_SYSLOG stslogd守护进程本身
LOG_USER 来自其他用户进程的消息
LOG_UUCP UUCP系统

设置facility参数的目的是可以让配置文件说明,来自不同设施的消息将以不同的方式进行处理。如果不调用openlog,或者以facility为0来调用它,那么在调用syslog时,可将facility作为priority参数的一部分进行说明。

调用syslog产生一个日志消息。其priority参数是facility和level 的组合

level 说明
LOG_EMERG 紧急
LOG_ALERT 必须立即修复的情况
LOG_CRIT 严重情况
LOG_ERR 出错情况
LOG_WARNING 警告情况
LOG_NOTICE 正常但重要的情况
LOG_INFO 信息性消息
LOG_DEBUG 调试消息

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

srtlogmask函数用于设置进程的记录优先级屏蔽字。

单实例守护进程

保证只运行一个守护进程的一个副本

#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;
    fd = open(LOCKFILE, O_RDWR | 0_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);
    sprint(buf, "%ld", (long)getpid());
    write(fd, buf, strlen(buf) +1);
    return 0;
}

守护进程的惯例

  1. 若守护进程使用锁文件,那么该文件通常存储在/var/run目录中。然而需要注意的是,守护进程可能需要具有超级用户权限才能在此目录下创建文件。锁文件的名字通常是name.pid,其中,name是该守护进程或服务的名字。
  2. 若守护进程支持配置选项,那么配置文件通常存放在/etc目录中。配置文件的名字通常是name.conf,其中,name是该守护进程或服务的名字。
  3. 守护进程可用命令行启动,但通常它们是由系统初始化脚本之一启动的。
  4. 若一个守护进程有一个配置文件,那么当该守护进程启动时会读该文件,但在此之后一般就不会再查看它。若某个管理员更改了配置文件,那么该守护进程可能需要被停止,然后再启动,以使配置文件的更改生效。为了避免此种麻烦,某些守护进程将捕捉SIGHUP信号,当它们接受到该信号时,重新读配置文件。

守护进程重读配置文件

#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 arr,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 = strrchr(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 error");

    /*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);
}

客户进程-服务器进程模型

守护进程尝尝用作服务器进程。

一般而言,服务器进程等待客户进程与其联系,提出某种类型的服务要求。由syslogd服务器进程提供的服务是将一条出错消息记录到日志文件中。

在服务器进程中调用fork然后exec另一个程序来向客户进程提供服务是很常见的。这些服务器进程通常管理着多个文件描述符:通信端点、配置文件、日志文件和类似的文件。最好的情况下,让子进程中的这些文件描述符保持打开状态并无大碍,因为它们很可能不会被在子进程中执行的程序所使用,尤其是那些与服务器端无关的程序。最坏情况下,保持它们的打开状态会导致安全问题——被执行的程序可能有一些恶意行为,如更改服务器端配置文件或欺骗客户端程序使其认为正在与服务器端通信,从而获取未授权的信息。

解决此问题的一个简单方法是对所有被执行程序不需要的文件描述符设置执行时关闭标志

时间: 2024-11-13 08:15:39

APUE------守护进程的相关文章

linux下的守护进程daemon

什么是守护进程?其实感觉守护进程并没有什么明确的定义,只是守护进程有一些特征,这是它需要遵循的. 守护进程的第一个特征是长时间在后台运行的程序,并且主要是为了提供某种服务,而为了能够让服务尽可能随时都可用,就要求这个服务是一直运行的,于是守护进程就守护着这个服务不挂掉.linux里面常见的守护进程一般都是以d结尾的,比如apache的httpd,samba的smbd,ssh的sshd. 它的第二个特征是与启动它的进程的环境隔离,包括关闭它打开的所有文件描述符,终端,会话,进程组,某些环境变量(如

(七) 一起学 Unix 环境高级编程(APUE) 之 进程关系 和 守护进程

. . . . . 目录 (一) 一起学 Unix 环境高级编程(APUE) 之 标准IO (二) 一起学 Unix 环境高级编程(APUE) 之 文件 IO (三) 一起学 Unix 环境高级编程(APUE) 之 文件和目录 (四) 一起学 Unix 环境高级编程(APUE) 之 系统数据文件和信息 (五) 一起学 Unix 环境高级编程(APUE) 之 进程环境 (六) 一起学 Unix 环境高级编程(APUE) 之 进程控制 (七) 一起学 Unix 环境高级编程(APUE) 之 进程关系

apue学习笔记(第十三章 守护进程)

本章将说明守护进程结构,以及如何编写守护进程程序. 守护进程,也就是通常说的Daemon进程,是Unix中的后台服务进程.它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件. 编程规则 在编写守护进程程序时需遵循一些基本规则,以防止产生不必要的交互作用.下面将说明这些规则. 1.调用umask将文件模式创建屏蔽字设置为一个已知值(通常是0) 2.调用fork,然后使父进程exit,保证了子进程不是进程组的组长进程,是下面进行setsid调用的先决条件 3

APUE:守护进程

重置文件屏蔽字 umask(0) fork(),父进程 exit(0) 以响应启动者 setsid(),断开所有控制终端 修改工作目录为 / 关闭文件描述符 将 0.1.2 重定位到 /dev/null 检测是否已经存在相同的守护进程正在运行,对 /var/run/xxxd.pid 进行 lockfile() 恢复 SIGHUP 的信号处理程序为默认 SIG_DFL 主线程调用 pthread_sigmask() 屏蔽所有信号 开新线程,使用 sigwait() 等待信号,遇到 SIGHUP 重

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

守护进程 守护进程是生存期较长的一种进程,它们常常在系统自举时启动,仅在系统关闭时才终止.因为它们没有控制终端,所以说它们是在后台运行的.UNIX系统由很多守护进程,它们执行日常事务活动. 本章主要介绍守护进程的结构,以及如何编写守护进程程序和守护进程如何报告错误情况. 一.守护进程的编程规则 (1)首先要做的是调用umask将文件模式创建屏蔽字设置为0.这是由于继承得来的文件模式创建屏蔽字可能会拒绝设置某些权限. (2)调用fork,然后使父进程退出(exit). (3)调用setsid以创建

Unix网络编程代码 第13章 守护进程和inetd超级服务器

1. 概述 守护进程是在后台运行且不与任何控制终端关联的进程.unix系统通常有很多守护进程在后台运行,执行不同的管理任务.    守护进程没有控制终端通常源于它们由系统初始化脚本启动.然而守护进程也可能从某个终端由用户在shell提示符下键入命令行启动,这样的守护进程必须亲自脱离与控制终端的关联,从而避免与作业控制,终端会话管理,终端产生信号等发生任何不期望的交互,也可以避免在后台运行的守护进程非预期的输出到终端.    守护进程有多种启动方法:    1.在系统启动阶段,许多守护进程由系统初

ASIO例子中的,守护进程初始化

// daemon.cpp // 该例子演示结合ASIO和POSIX标准系统的fork系统调用,产生一个守护进程. //时间服务器? // Copyright (c) 2003-2014 Christopher M. Kohlhoff (chris at kohlhoff dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.t

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

1. 引言 守护进程是生存期长的一种进程.它们常常在系统引导装入时启动,仅在系统关闭时才终止.它们没有控制终端,在后台运行. 本章说明守护进程结构.如何编写守护进程程序.守护进程如何报告出错情况. 2. 守护进程的特征 基于BSD的系统下执行:ps -axj -a 显示由其他用户所拥有的进程的状态:-x 显示没有控制终端的进程状态:-j 显示与作业有关的信息 基于System V的系统下执行:ps -efj Linux下执行以上两个命令输出一致 常见的守护进程: kswapd,内存换页守护进程.

第十三章:守护进程

13.1:引言 守护进程也称精灵进程(daemon)是生存期较长的一种进程.它们常常在系统自举时启动,尽在系统关闭时才终止.因为它们没有控制终端,所以说它们是在后台运行的.Unixi有很多守护进程,它们执行日常事务活动. 13.2:守护进程的特征 查看守护进程: ps -axj 注意: 大多数守护进程都以超级用户特权运行.没有一个守护进程拥有控制终端,其终端名设置为问号(?),终端前台进程组ID设置为-1.内核守护进程以无控制终端方式启动.用户层守护进程缺少控制终端可能是守护进程调用了setsi

Linux进程实践(5) --守护进程

概述 守护进程是在需要在后台长期运行不受终端控制的进程,通常情况下守护进程在系统启动时自动运行,在服务器关闭的时候自动关闭:守护进程的名称通常以d结尾,比如sshd.xinetd.crond.atd等. 守护进程编程规则 调用umask将文件模式创建屏蔽字设置为一个已知值(通常是0) 调用fork(),创建新进程,它会是将来的守护进程 然后使父进程exit,保证子进程不是进程组组长 调用setsid创建新的会话 会话:是一个或者多个进程组的集合,通常一个会话开始与用户登录,终止于用户退出.在此期