Linux下創建維護服務進程Daemon的注意點,附有詳細案例講解

unix系統下所有進程都工作在前臺和後臺,在前臺工作的進程通常和用戶直接交互(通常是通過terminal,比如我們ls -ltr,會在當前terminal顯示文件列表),但是在後臺運行的進程都是自己運行。用戶可以檢查它的工作狀態,但是不知道它在幹嘛。維護進程指的就是這種工作在後臺的進程。


接下來分幾步來講解下如何創建一個維護進程,並且實現標準輸入輸出重定向的工作。


1. 維護進程化(讓進程工作在後臺程序)【fork】

fork()函數會讓系統調複製我們當前的進程,然後讓父進程退出(此時,子進程就會被unix第一進程init()接管)我們稱這樣的子進程爲孤兒進程,作爲此時的結果,子進程會完全脫離它之前的父進程開始在後臺工作。

        i=fork();
	if (i<0) exit(1); /* fork error */
	if (i>0) exit(0); /* parent exits */
	/* child (daemon) continues */


2. 讓子進程獨立【setsid】

一個進程通常通過tty來接受一些信號,任何繼承這個進程的子進程也會被tty控制,一個服務進程不應該從開始它的進程中接受信息(父進程),所以它必須完全脫離控制它的tty。


在unix系統中,進程在一個進程組中運行,所以,在同一個進程組的所有進程都會被認爲一個整體,進程組和對話組也會被繼承。服務進程應當和其他所有進程獨立開來。

setsid() ; // obtain a new process group


上面這個函數會將服務進程放在一個新的進程組,並且完全脫離它的控制tty。 setpgrp() 同樣也適用於這種情況


3. 繼承的文件符,標準輸入輸出文件

打開的文件號同樣會被子進程繼承,這樣同樣會導致系統中可用的文件符不夠用,不必要的文件號需要在fork()前被關閉,

for (i=getdtablesize();i>=0;--i) close(i); /* close all descriptors */


在unix系統中,通常會有三個默認的輸入輸出,stdin,stdout,stderr。

i=open("/dev/null",O_RDWR); /* open stdin */
	dup(i); /* stdout */
	dup(i); /* stderr */

由於unix系統順次分配文件符,fopen會stdin(0)然後dup會提供一份複製給stdout,stderr。


轉載該文章請標明出處: http://blog.csdn.net/elfprincexu

4.文件符mask 【umask】

絕大部分服務進程以超級用戶運行,處於安全因素考慮,他們理應保護他們生成的文件,設置文件保護屬性會防止不安全文件屬性的產生。

umask(027);
上述會限制文件權限爲750, (027相補)


5. 運行文件路徑 【chdir】

一個服務進程應該在一個確定的路徑下運行,這有很多優勢,同樣的也會有很多壞處,試想在用戶更目錄下運行,它不可能找到輸入輸出文件。

chdir("/servers/");


6.文件互斥鎖【open,lockf,getpid】

大部分服務進程要求同一時間下只有一個進程訪問,文件鎖是一個很好地方法來解決進程之間的互斥,只有第一個實例可以訪問,操作,其他的所有進程需要等待文件鎖的釋放。這保證了文件的安全。


        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. 信號處理 【signal()】

進程可以從用戶或者其他進程獲取信號,最好是對這些信號進行適當處理。子進程會釋放SIGCHLD信號當他們結束的時候。服務進程必須忽略或者正確處理這些信號。

    signal(SIG_IGN,SIGCHLD); /* child terminate signal */

上面的代碼會忽略子進程釋放出來的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. 記錄信息【syslogd,syslog.conf,openlog,syslog,closelog】

服務進程通常會創建一些信息,有些會很重要。對之後的維護很重要,因此文件重定向也變得尤爲重要。


#mydeamon 2> erro.log

上一行代碼會將輸出信息重定向到文件中,當然這是最爲簡單的terminal重定向。

通常我們會使用unix系統自帶的syslog進程。該進程會收集信息加以分類,syslog同時使用配置文件/etc/syslog.conf來重定向

        openlog("mydaemon",LOG_PID,LOG_DAEMON)
	syslog(LOG_INFO, "Connection from host %d", callinghostname);
	syslog(LOG_ALERT, "Database Error !");
	closelog();
openlog中,mydeamon用來識別我們的進程,LOG_PID使用syslog來記錄信息,第一個參數是優先級,其餘的類似於sprintf的信息。


這裏也附上常用的重定向方法:

#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <io.h>

int main(void)
{
   #define STDOUT 1   //標準輸出文件描述符 號

   int nul, oldstdout;
   char msg[] = "This is a test";

   /* create a file */

//打開一個文件,操作者具有讀寫權限 如果文件不存在就創建
   nul = open("DUMMY.FIL", O_CREAT | O_RDWR,
      S_IREAD | S_IWRITE);

   /* create a duplicate handle for standard
      output */

//創建STDOUT的描述符備份
   oldstdout = dup(STDOUT);
   /*
      redirect standard output to DUMMY.FIL
      by duplicating the file handle onto the
      file handle for standard output.
   */

//重定向nul到STDOUT
   dup2(nul, STDOUT);

   /* close the handle for DUMMY.FIL */

//重定向之後要關閉nul
   close(nul);

   /* will be redirected into DUMMY.FIL */

//寫入數據
   write(STDOUT, msg, strlen(msg));

   /* restore original standard output
      handle */

//還原
   dup2(oldstdout, STDOUT);

   /* close duplicate handle for STDOUT */
   close(oldstdout);

   return 0;
}
 

//結果就是msg寫到了文件中而不是STDOUT


9. 完整的案例:

該案例來自Levent Karakas:


/*
UNIX Daemon Server Programming Sample Program
Levent Karakas <levent at mektup dot at> May 2001

To compile:	cc -o exampled examped.c
To run:		./exampled
To test daemon:	ps -ef|grep exampled (or ps -aux on BSD systems)
To test log:	tail -f /tmp/exampled.log
To test signal:	kill -HUP `cat /tmp/exampled.lock`
To terminate:	kill `cat /tmp/exampled.lock`
*/

#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>

#define RUNNING_DIR	"/tmp"
#define LOCK_FILE	"exampled.lock"
#define LOG_FILE	"exampled.log"

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);
}

void signal_handler(sig)
int sig;
{
	switch(sig) {
	case SIGHUP:
		log_message(LOG_FILE,"hangup signal catched");
		break;
	case SIGTERM:
		log_message(LOG_FILE,"terminate signal catched");
		exit(0);
		break;
	}
}

void daemonize()
{
int i,lfp;
char str[10];
	if(getppid()==1) return; /* already a daemon */
	i=fork();
	if (i<0) exit(1); /* fork error */
	if (i>0) exit(0); /* parent exits */
	/* child (daemon) continues */
	setsid(); /* obtain a new process group */
	for (i=getdtablesize();i>=0;--i) close(i); /* close all descriptors */
	i=open("/dev/null",O_RDWR); dup(i); dup(i); /* handle standart I/O */
	umask(027); /* set newly created file permissions */
	chdir(RUNNING_DIR); /* change running directory */
	lfp=open(LOCK_FILE,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 */
	/* first instance continues */
	sprintf(str,"%d\n",getpid());
	write(lfp,str,strlen(str)); /* record pid to lockfile */
	signal(SIGCHLD,SIG_IGN); /* ignore child */
	signal(SIGTSTP,SIG_IGN); /* ignore tty signals */
	signal(SIGTTOU,SIG_IGN);
	signal(SIGTTIN,SIG_IGN);
	signal(SIGHUP,signal_handler); /* catch hangup signal */
	signal(SIGTERM,signal_handler); /* catch kill signal */
}

main()
{
	daemonize();
	while(1) sleep(1); /* run */
}

/* EOF */
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章