S.l.e!ep.¢%

像打了激速一样,以四倍的速度运转,开心的工作
简单、开放、平等的公司文化;尊重个性、自由与个人价值;
posts - 1098, comments - 335, trackbacks - 0, articles - 1
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

如何编写UNIX DAEMON程序

Posted on 2011-01-28 21:47 S.l.e!ep.¢% 阅读(576) 评论(0)  编辑 收藏 引用 所属分类: Unix
如何编写UNIX DAEMON程序
分享
2008-12-04 16:35

unix 进程可以运行在前台或是后台,运行在前台的(9php.com)程序通过终端与用户进行交互,而后以进程是单独运行的(9php.com),用户可以查看它的(9php.com)状态,但不清楚它具体在做什么。我们将运行在后台的(9php.com)程序叫做"daemon"程序。如常见的(9php.com)httpd,nfsd,sshd都属于"daemon"程序,要写这样的(9php.com)程序,你可能要考虑如下的(9php.com)一些处理。
1,后台运行处理
使用FORK()调用创建一个子进程,然后让父进程退出。这样子进程就与父进程分离了并运行在后台。
    i=fork();
    if (i<0) exit(1); /* fork error */
    if (i>0) exit(0); /* parent exits */
    /* child (daemon) continues */

2,进程依赖处理
每个子进程继承父进程的(9php.com)控制终端,进程将会接收关联终端的(9php.com)信号。为了防止daemon程序从创建它的(9php.com)进程接收信号,必需去除与控制终端的(9php.com)关联。
在UNIX系统中,进程运行在一个进程组,子进程会从父进程继承进程组,一个DAEMON进程应该不依赖于进程组的(9php.com)其它进程.
  setsid() /* obtain a new process group */
这个函数将DAEMON进程放置于一个新的(9php.com)进程组,并去除了与控制终端的(9php.com)关联。
3,继承的(9php.com)文件描述符号和标准I/O处理
子进程会继承父进程打开的(9php.com)文件描述符,这样会导致文件描述符资源的(9php.com)浪费,所以应该关闭所有继承过来的(9php.com)文件描述符。
    for (i=getdtablesize();i>=0;--i) close(i); /* close all descriptors */
对于stdin,stdout,stderr,将它们重定向到/dev/null。
    i=open("/dev/null",O_RDWR); /* open stdin */
    dup(i); /* stdout */
    dup(i); /* stderr */
由于关闭了所有文件描述符,上面的(9php.com)操作会从0开始顺序产生文件描述符。

4,文件掩码处理
为了安全的(9php.com)考虑,需要将设置文件掩码
  umask(027);

5,运行目录处理
一个daemon进程应该运行在一个固定的(9php.com)目录,防止用户在不同的(9php.com)目录下运行程序后找不到相关的(9php.com)文件。
  chdir("/servers/");

6,保持单DAEMON进程处理
为了保持单daemon进程,防止运行多个daemon进程,有效的(9php.com)方式是建立一个文件,并对其加锁,在进程退出时进行解锁。
   lfp=open("exampled.lock",O_RDWR|O_CREAT,0640);
    if (lfp<0) exit(1); /* can not open */
    if (lockf(lfp,F_TLOCK,0)<0) exit(0); /* can not lock */
    /* only first instance continues */

    sprintf(str,"%d\n",getpid());
    write(lfp,str,strlen(str)); /* record pid to lockfile */

7,信号处理
对于daemon进程要忽略和处理一些接收到的(9php.com)信号,如子进程结束时会发送SIGCHLD信号组父进程,有些daemon进程通过SIGHUP信号来重启。
  signal(SIG_IGN,SIGCHLD); /* child terminate signal */
上面的(9php.com)代码将忽略SIGCHLD信号。
  void Signal_Handler(sig) /* signal handler function */
    int sig;
    {
        switch(sig){
            case SIGHUP:
                /* rehash the server */
                break;        
            case SIGTERM:
                /* finalize the server */
                exit(0)
                break;        
        }   
    }

    signal(SIGHUP,Signal_Handler); /* hangup signal */
    signal(SIGTERM,Signal_Handler); /* software termination signal from kill */
  建立一个信号处理函数,并与信号关联。
8,日志处理
  运行程序有一些重要的(9php.com)信息需要记日志,有如下几种方法记录日志:
  重定向输出到标准I/O,这种方式将日志直接输出到终端(显示器),实际上这个程序是运行在前台的(9php.com)。这种方式不适用于daemon进程。
  写日志文件,将日志写入文件。
   void log_message(filename,message)
    char *filename;
    char *message;
    {
    FILE *logfile;
        logfile=fopen(filename,"a");
        if(!logfile) return;
        fprintf(logfile,"%s\n",message);
        fclose(logfile);
    }

    log_message("conn.log","connection accepted");
    log_message("error.log","can not open file");
日志服务:这种方式是将日志送给一个日志服务,由日志服务来记录日志,并提供查阅,
UNIX系统带有SYSLOGD日志服务,可以通过如下方式将日志写入SYSLOGD。
  openlog("mydaemon",LOG_PID,LOG_DAEMON)
    syslog(LOG_INFO, "Connection from host %d", callinghostname);
    syslog(LOG_ALERT, "Database Error !");
    closelog();
就写这么多了,要变成你自己的(9php.com)东东,还得你自己去研究.希望这篇文章对初学者有个指引.

Daemon设计原则

Daemon程序设计主要原则包括:

(1)       程序运行后调用fork,并让父进程退出。子进程获得一个新的进程ID,但继承了父进程的进程组ID。

(2)       调用setsid创建一个新的session,使自己成为新session和新进程组的leader,并使进程没有控制终端(tty)。

(3)       设置文件创建mask为0,避免创建文件时权限的影响。

(4)       关闭不需要的打开文件描述符。因为Daemon程序在后台执行,不需要于终端交互,通常就关闭STDIN、STDOUT和STDERR。其它根据实际情况处理。

(5)       Daemon无法输出信息,可以使用SYSLOG或自己的日志系统进行日志处理。(可选)

(6)       编写管理Daemon的SHELL脚本,使用service对Daemon进行管理和监控。(可选)

Daemon程序框架

int init_daemon(void)

{

  pid_t pid;

  int i;

 

  /* parent exits , child continues */

  if((pid = fork()) < 0)

    return -1;

  else if(pid != 0)

    exit(0);

 

  setsid(); /* become session leader */

  for(i=0;i< NOFILE ;++i) /* close STDOUT, STDIN, STDERR, */

    close(i);

 

  umask(0); /* clear file mode creation mask */

  return 0;

}

 

void sig_term(int signo)

{

  if(signo == SIGTERM)  /* catched signal sent by kill(1) command */

  {

     wsio_logit("", "wsiod stopped\n");

     exit(0);

 }

}

 

/* main program of daemon */

int main(void)

{

if(init_daemon() == -1){

printf("can't fork self\n");

exit(0);

  }

  wsio_logit("", "wsiod started\n");

  signal(SIGTERM, sig_term); /* arrange to catch the signal */

 

  while (1) {

    // Do what you want here

    … …

  }

  exit(0);

}

Daemon日志

这里使用自己的日志系统,当然也可以使用SYSLOG。

#define LOGBUFSZ 256     /*log buffer size*/

#define LOGFILE  "/var/log/wsiod.log"  /*log filename*/

int wsio_logit(char * func, char *msg, ...)

{

        va_list args;

        char prtbuf[LOGBUFSZ];

        int save_errno;

        struct tm *tm;

        time_t current_time;

        int fd_log;

 

        save_errno = errno;

        va_start (args, msg);

        (void) time (&current_time);            /* Get current time */

        tm = localtime (&current_time);

        sprintf (prtbuf, "%02d/%02d %02d:%02d:%02d %s ", tm->tm_mon+1,

                    tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, func);

        vsprintf (prtbuf+strlen(prtbuf), msg, args);

        va_end (args);

        fd_log = open (LOGFILE, O_WRONLY | O_CREAT | O_APPEND, 0664);

        write (fd_log, prtbuf, strlen(prtbuf));

        close (fd_log);

        errno = save_errno;

        return 0;

}

Daemon管理

Daemon程序可以使用service工具进行管理,包括启动、停止、查看状态等,但前题是需要编写一个如下的简单SHELL脚本。

# /etc/rc.d/init.d/wsiod

#!/bin/sh

#

# wsiod         This shell script takes care of starting and stopping wsiod.

#

# chkconfig: 35 65 35

# description: wsiod is web servce I/O server, which is used to access files on remote hosts.

 

# Source function library.

. /etc/rc.d/init.d/functions

 

# Source networking configuration.

. /etc/sysconfig/network

 

# Check that networking is up.

[ ${NETWORKING} = "no" ] && exit 0

 

RETVAL=0

prog="wsiod"

WSIOARGS="-h $HOSTNAME -p 80 -t STANDALONE -k -c -d"

start() {

        # Start daemons.

        echo -n $"Starting $prog: "

        daemon /usr/local/bin/wsiod ${WSIOARGS}

        RETVAL=$?

        echo

        [ $RETVAL -eq 0 ] && touch /var/lock/subsys/wsiod

        return $RETVAL

}

stop() {

        # Stop daemons.

        echo -n $"Shutting down $prog: "

        killproc wsiod

        RETVAL=$?

        echo

        [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/wsiod

        return $RETVAL

}

 

# See how we were called.

case "$1" in

  start)

        start

        ;;

  stop)

        stop

        ;;

  restart|reload)

        stop

        start

        RETVAL=$?

        ;;

  status)

        status wsiod

        RETVAL=$?

        ;;

  *)

        echo $"Usage: $0 {start|stop|restart|status}"

        exit 1

esac

 

exit $RETVAL

 

OK,到这儿为止,一个完整的Linux Daemon程序就完成了。


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理