第13章 守护进程和inetd超级服务器

概述

守护进程是在后台运行且不与任何控制终端关联的进程。

守护进程有多种启动方法:

(1)在系统启动阶段,许多守护进程由系统初始化脚本启动。这些脚本通常位于 /etc 目录或以 /etc/rc 开头的某个目录中,这些脚本启动的守护进程一开始时拥有超级用户特权。如 inetd 超级服务器,web 服务器、邮件服务器。

(2)许多网络服务器由 inetd 超级服务器启动。

(3)cron 守护进程本身由方法(1)启动,而由该进程启动执行的程序同样作为守护进程运行。

(4)at 命令用于指定将来某个时刻的程序执行。这些程序的执行时刻到来时通常由 cron 守护进程启动执行它们。

(5)守护进程还可以从用户终端或在前台或在后台启动。这么做往往是测试守护进程,或者重启因某种原因而终止的某个守护进程。


syslogd 守护进程

Unix 系统的 syslogd 守护进程是由系统的初始化脚本启动的。该进程在系统工作期间一直运行。

源自 Berkeley 的 syslogd 实现:

(1)读取配置文件:/etc/syslog.conf 的配置文件指定本守护进程可能收取的各种日志消息应该如何处理。

(2)创建一个 Unix 域数据报套接字,给它捆绑路径名 /var/run/log (在某些系统上是 /dev/log)。

(3)创建一个 UDP 套接字,给它捆绑端口 514(syslog 服务器使用的端口号)。较新的 syslogd 实现禁止创建 UDP 套接字,除非管理员明确要求。因为允许任何进程往这个套接字发送 UDP 数据报会让系统易遭拒绝服务攻击,其文件系统可能被填满,来自合法的进程的日志消息可能被排挤掉。

(4)打开路径名 /dev/klog。来自内核中的任何出错消息看着像是这个设备的输入。

此后 syslogd 守护进程在一个无限循环中运行:调用 select 以等待它的 3 个描述符(分别来自上面的 2,3,4)之一变为可读,读入日志消息,并按照配置文件进行处理。如果守护进程收到 SIGHUP 信号,那就重新读取配置文件。


syslog 函数

守护进程没有控制终端,它们就不能打印消息在 stderr 上。从守护进程中登记消息的常用技巧就是调用 syslog 函数。

       #include <syslog.h>

       void openlog(const char *ident, int option, int facility);
       void syslog(int priority, const char *format, ...);
       void closelog(void);

priority 参数是 级别(level)和设施(facility)的组合。message 参数类似 printf 的格式串,不过多了一个 m% 规范,它将被替换成与当前 errno 值对应的出错消息。如下:

syslog(LOG_INFO|LOG_LOCAL2, "rename(%s,%s):%m", file1, file2);

当 syslog 被应用进程首次调用时,它创建一个 Unix 域数据报套接字,然后调用 connect 连接到由 syslogd 守护进程创建的 Unix 域数据报套接字的众所周知路径名(如/var/run/log)。这个套接字一直保持打开,直到进程终止为止。进程也可以调用 openlog 和 closelog 来作为替换。

openlog 在首次调用 syslog 前调用,closelog 在进程不需要发送日志消息时调用。openlog 被调用时通常并不立即创建 Unix 域套接字。相反直到首次调用 syslog 时才打开。options 参数有 LOG_NDELAY 选项时迫使该套接字在 openlog 被调用时就创建。其 facility 参数为后续的 syslog 函数指定默认值。


守护进程用例

#include <syslog.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <stdarg.h>

#define MAXFD 64
#define MAXLINE 1024

int		daemon_proc;		/* set nonzero by daemon_init() */

typedef	void	Sigfunc(int);

Sigfunc *signal(int signo, Sigfunc *func)
{
    struct sigaction	act, oact;

    act.sa_handler = func;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if (signo == SIGALRM) {
#ifdef	SA_INTERRUPT
        act.sa_flags |= SA_INTERRUPT;	/* SunOS 4.x */
#endif
    } else {
#ifdef	SA_RESTART
        act.sa_flags |= SA_RESTART;		/* SVR4, 44BSD */
#endif
    }
    if (sigaction(signo, &act, &oact) < 0)
        return(SIG_ERR);
    return(oact.sa_handler);
}
/* end signal */

Sigfunc *Signal(int signo, Sigfunc *func)	/* for our signal() function */
{
    Sigfunc	*sigfunc;

    if ( (sigfunc = signal(signo, func)) == SIG_ERR)
        printf("signal error");
    return(sigfunc);
}

/* Print message and return to caller
 * Caller specifies "errnoflag" and "level" */

static void err_doit(int errnoflag, int level, const char *fmt, va_list ap)
{
    int		errno_save, n;
    char	buf[MAXLINE + 1];

    errno_save = errno;		/* value caller might want printed */
#ifdef	HAVE_VSNPRINTF
    vsnprintf(buf, MAXLINE, fmt, ap);	/* safe */
#else
    vsprintf(buf, fmt, ap);					/* not safe */
#endif
    n = strlen(buf);
    if (errnoflag)
        snprintf(buf + n, MAXLINE - n, ": %s", strerror(errno_save));
    strcat(buf, "\n");

    if (daemon_proc) {
        syslog(level, buf);
    } else {
        fflush(stdout);		/* in case stdout and stderr are the same */
        fputs(buf, stderr);
        fflush(stderr);
    }
    return;
}

void err_msg(const char *fmt, ...)
{
    va_list		ap;

    va_start(ap, fmt);
    err_doit(0, LOG_INFO, fmt, ap);
    va_end(ap);
    return;
}

int daemon_init(const char *pname, int facility)
{
    int i;
    pid_t pid;

    // 1、转变为后台进程
    if((pid = fork()) < 0)
        return -1;
    else if(pid)
        _exit(0);       /* parent terminates */

    /* child 1 continues... */

    // 2、离开原先的进程组、会话,开启一个新会话,从而当前子进程1
    // 变为新会话的会话头进程以及新进程组的进程组头进程,从而不再有
    // 控制终端
    if(setsid() < 0)    /* become session leader */
        return -1;

    // 3、忽略 SIGHUP 信号,并且再次调用 fork 是确保本守护进程将来
    // 即是打开了一个终端设备,也不会自动获得控制终端。因为这次 fork
    // 后,产生的子进程2不再是会话头进程,即是有新的终端打开也是会话头进程获得。
    // 同时:这里必须忽略 SIGHUP 信号,因为当会话头进程终止时(第一次fork产生
    // 的子进程),其会话中的所有进程(即再次fork产生的子进程)都收到SIGHUP信号。
    signal(SIGHUP, SIG_IGN);
    if((pid=fork()) < 0)
        return -1;
    else if(pid)
        _exit(0);       /* child 1 terminates */

    /* child 2 continues... */

    daemon_proc = 1;    /* for err_msg() functions */

    // 4、改变当前的工作目录,避免卸载不了文件系统
    chdir("/");         /* change working directory */

    // 5、关闭打开的文件描述符,避免浪费系统资源
    /* close off file descriptors */
    for(i=0; i<MAXFD; i++)
        close(i);

    // 6、重定向标准输入,输出,错误流,因为守护进程没有控制终端
    /* redirect stdin, stdout, and stderr to /dev/null */
    int fd = open("dev/null", O_RDWR);
    if (fd == -1) exit(1);
    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2);
    if(fd>2)
        close(fd);

    openlog(pname, LOG_PID, facility);
    return 0;
}

int main(int argc, char **argv)
{
    ...
    daemon_init(argv[0], 0);
    ...
    
    if(error)
        err_msg("..."); //在守护进程中就应避免调用诸如 printf fprintf 
                        //之类的函数,daemon_init之后错误消息进入 syslog
}

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值