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 (¤t_time);??????????? /* Get current time */
??????? tm = localtime (¤t_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程序就完成了。