course:nctu-高等unix程式設計:chapter13

Daemon Processes

0x00 Outline

  • Introduction
  • Daemon conventions
  • Client-Server model
  • Coding rules
  • Error logging
  • Single-instance daemon

0x01 Introduction

  • daemon 一般會在開啟時就啟動,直到關機才終止
  • daemon 沒有 controlling terminal,在 background 執行
  • 可以使用 ps ajx 查看,有 [ ] 包圍的是 kernel process

0x02 Daemon conventions

  • 如果 daemon 有使用 lock file,通常會存放在 /var/run/xxx.pid
  • 如果 daemon 支援 configuration options,則他的設定檔通常放在 /etc 底下
  • daemon 也可以在 command line 啟動,但通常在開機時就會被 etc/rc* or /etc/init.d/* 執行
  • 如果 daemon 有設定檔,通常 daemon 只會在開始執行時讀取一次,有些 daemon 則會抓取 SIGHUP signal,並重新讀取 config

0x03 Client-Server model

  • 一般 daemon process 會用在 server 上
  • server daemon 會等待 client 連線
  • server 和 client 間的 communication channel 可以是 one- way 也可以是 two-way

0x04 Coding rules

  • 使用 umask 設置 daemon 若建立檔案時所要求的正確權限
  • 呼叫 fork 然後 exit,一來不會 block shell,二來不會成為 process leader,如此才能成為 session leader
  • 呼叫 setsid,如此如此能成為一個新的 session leader 和 process leader,且沒有 controlling terminal
  • 改變 current working directory
  • 關閉不使用的 file descriptors,並將 file descriptors 0,1,2 重導到 /dev/null,忽略 stdin,stdout,stderr 對 daemon 的影響
  • 設定 logfile 或 system log 處理 daemon output
#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 */
        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", cmd);
    if ((pid = fork()) < 0)
        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 /", cmd);
 
    /*
     * 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);
    }
}

0x05 Error Logging

  • 因為 daemon 沒有controlling terminal,因此無法寫 error message 到 stdout
  • daemon 不該輸出訊息到 console
  • 系統管理者會更偏好有中央通一管理所有 daemon 的 log
  • BSD 有 syslog,而 Linux 則有 rsyslogd(reliable and extended syslogd)

#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);
 
/* Returns: previous log priority mask value */
  • call openlog 是可選用而非必需,在第一次呼叫 syslog 時 openlog 會自動被呼叫
  • call closelog 也是可選用的,他僅僅簡單關閉 file descriptor
  • openlog option 有一些處理 syslog 的選項可用
    • LOG_CONS
    • LOG_NDELAY
    • LOG_NOWAIT
    • LOG_ODELAY
    • LOG_PERROR
    • LOG_PID
  • facility 則可指定一些 log 的來源
    • LOG_AUTH
    • LOG_AUTHPRIV
    • LOG_CRON
    • LOG_DAEMON
    • LOG_FTP
    • LOG_KERN
    • LOG_LPR
    • LOG_MAIL
    • LOG_NEWS
    • LOG_SYSLOG
    • LOG_USER
    • LOG_UUCP
    • LOG_LOCAL0 ~ LOG_LOCAL7
  • syslog 的 priority 則可指定紀錄 log 的等級,以下由高到低排列
    • LOG_EMERG
    • LOG_ALERT
    • LOG_CRIT
    • LOG_ERR
    • LOG_WARNING
    • LOG_NOTICE
    • LOG_INFO
    • LOG_DEBUG

Example:

openlog("lpd", LOG_PID, LOG_LPR);
syslog(LOG_ERR, "open error for %s: %m", filename);
  • %m 用來印出 errno 對應的 error string

0x06 Single-Instance Daemons

  • 有些 daemon 會設計成一次只允許一隻 process 執行,這時候一般會使用到 lockfile,這部分在 Advanced I/O 會有更詳細的說明
#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[16];
 
    fd = open(LOCKFILE, O_RDWR|O_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);
    sprintf(buf, "%ld", (long)getpid());
    write(fd, buf, strlen(buf)+1);
    return(0);
}

course/nctu-高等unix程式設計/chapter13.txt · 上一次變更: 127.0.0.1