unix 進(jìn)程可以運(yùn)行在前臺或是后臺,運(yùn)行在前臺的(9php.com)程序通過終端與用戶進(jìn)行交互,而后以進(jìn)程是單獨(dú)運(yùn)行的(9php.com),用戶可以查看它的(9php.com)狀態(tài),但不清楚它具體在做什么。我們將運(yùn)行在后臺的(9php.com)程序叫做"daemon"程序。如常見的(9php.com)httpd,nfsd,sshd都屬于"daemon"程序,要寫這樣的(9php.com)程序,你可能要考慮如下的(9php.com)一些處理。
1,后臺運(yùn)行處理
使用FORK()調(diào)用創(chuàng)建一個(gè)子進(jìn)程,然后讓父進(jìn)程退出。這樣子進(jìn)程就與父進(jìn)程分離了并運(yùn)行在后臺。
? ? i=fork();
? ? if (i<0) exit(1); /* fork error */
? ? if (i>0) exit(0); /* parent exits */
? ? /* child (daemon) continues */
2,進(jìn)程依賴處理
每個(gè)子進(jìn)程繼承父進(jìn)程的(9php.com)控制終端,進(jìn)程將會接收關(guān)聯(lián)終端的(9php.com)信號。為了防止daemon程序從創(chuàng)建它的(9php.com)進(jìn)程接收信號,必需去除與控制終端的(9php.com)關(guān)聯(lián)。
在UNIX系統(tǒng)中,進(jìn)程運(yùn)行在一個(gè)進(jìn)程組,子進(jìn)程會從父進(jìn)程繼承進(jìn)程組,一個(gè)DAEMON進(jìn)程應(yīng)該不依賴于進(jìn)程組的(9php.com)其它進(jìn)程.
setsid() /* obtain a new process group */
這個(gè)函數(shù)將DAEMON進(jìn)程放置于一個(gè)新的(9php.com)進(jìn)程組,并去除了與控制終端的(9php.com)關(guān)聯(lián)。
3,繼承的(9php.com)文件描述符號和標(biāo)準(zhǔn)I/O處理
子進(jìn)程會繼承父進(jìn)程打開的(9php.com)文件描述符,這樣會導(dǎo)致文件描述符資源的(9php.com)浪費(fèi),所以應(yīng)該關(guān)閉所有繼承過來的(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 */
由于關(guān)閉了所有文件描述符,上面的(9php.com)操作會從0開始順序產(chǎn)生文件描述符。
4,文件掩碼處理
為了安全的(9php.com)考慮,需要將設(shè)置文件掩碼
umask(027);
5,運(yùn)行目錄處理
一個(gè)daemon進(jìn)程應(yīng)該運(yùn)行在一個(gè)固定的(9php.com)目錄,防止用戶在不同的(9php.com)目錄下運(yùn)行程序后找不到相關(guān)的(9php.com)文件。
chdir("/servers/");
6,保持單DAEMON進(jìn)程處理
為了保持單daemon進(jìn)程,防止運(yùn)行多個(gè)daemon進(jìn)程,有效的(9php.com)方式是建立一個(gè)文件,并對其加鎖,在進(jìn)程退出時(shí)進(jìn)行解鎖。
?? lfp=open("exampled.lock",O_RDWR|O_CREAT,0640);
? ? if (lfp<0) exit(1); /* can not open */
? ? if (lockf(lfp,F(xiàn)_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進(jìn)程要忽略和處理一些接收到的(9php.com)信號,如子進(jìn)程結(jié)束時(shí)會發(fā)送SIGCHLD信號組父進(jìn)程,有些daemon進(jìn)程通過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 */
??建立一個(gè)信號處理函數(shù),并與信號關(guān)聯(lián)。
8,日志處理
??運(yùn)行程序有一些重要的(9php.com)信息需要記日志,有如下幾種方法記錄日志:
??重定向輸出到標(biāo)準(zhǔn)I/O,這種方式將日志直接輸出到終端(顯示器),實(shí)際上這個(gè)程序是運(yùn)行在前臺的(9php.com)。這種方式不適用于daemon進(jìn)程。
??寫日志文件,將日志寫入文件。
?? 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");
日志服務(wù):這種方式是將日志送給一個(gè)日志服務(wù),由日志服務(wù)來記錄日志,并提供查閱,
UNIX系統(tǒng)帶有SYSLOGD日志服務(wù),可以通過如下方式將日志寫入SYSLOGD。
openlog("mydaemon",LOG_PID,LOG_DAEMON)
? ? syslog(LOG_INFO, "Connection from host %d", callinghostname);
? ? syslog(LOG_ALERT, "Database Error !");
? ? closelog();
就寫這么多了,要變成你自己的(9php.com)東東,還得你自己去研究.希望這篇文章對初學(xué)者有個(gè)指引.
Daemon設(shè)計(jì)原則
Daemon程序設(shè)計(jì)主要原則包括:
(1)?????? 程序運(yùn)行后調(diào)用fork,并讓父進(jìn)程退出。子進(jìn)程獲得一個(gè)新的進(jìn)程ID,但繼承了父進(jìn)程的進(jìn)程組ID。
(2)?????? 調(diào)用setsid創(chuàng)建一個(gè)新的session,使自己成為新session和新進(jìn)程組的leader,并使進(jìn)程沒有控制終端(tty)。
(3)?????? 設(shè)置文件創(chuàng)建mask為0,避免創(chuàng)建文件時(shí)權(quán)限的影響。
(4)?????? 關(guān)閉不需要的打開文件描述符。因?yàn)镈aemon程序在后臺執(zhí)行,不需要于終端交互,通常就關(guān)閉STDIN、STDOUT和STDERR。其它根據(jù)實(shí)際情況處理。
(5)?????? Daemon無法輸出信息,可以使用SYSLOG或自己的日志系統(tǒng)進(jìn)行日志處理。(可選)
(6)?????? 編寫管理Daemon的SHELL腳本,使用service對Daemon進(jìn)行管理和監(jiān)控。(可選)
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日志
這里使用自己的日志系統(tǒng),當(dāng)然也可以使用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工具進(jìn)行管理,包括啟動(dòng)、停止、查看狀態(tài)等,但前題是需要編寫一個(gè)如下的簡單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,到這兒為止,一個(gè)完整的Linux Daemon程序就完成了。