天天看點

linux守護程序 編寫要點

守護程序(Daemon)是運作在背景的一種特殊程序,脫離于終端。它在執行過程中産生的資訊也不會在終端上顯示。守護程序周期性地執行某種任務或等待處理某些事件的發生。Linux的大多數伺服器就是用守護程序實作的。      
編寫要點:(最後有完整示例)      
1.屏蔽一些有關終端操作的信号。這是為了防止在守護程序還沒有正常運作起來前,受到終端幹擾退出或者挂起。代碼:      
signal(SIGTTOU, SIG_IGN);      
signal(SIGTTIN, SIG_IGN);      
signal(SIGTSTP, SIG_IGN);      
signal(SIGHUP, SIG_IGN);      
2.在背景運作。為避免挂起控制終端将其放入背景執行。方法是在程序中調用fork使父程序終止,讓Daemon在子程序中背景執行。代碼:      
if( (pid = fork()) > 0)
    exit(0);    //父程序,結束父程序,子程序繼續      
3.脫離控制終端和程序組。代碼:
setsid();  //剛才fork的子程序調用      
調用成功後,程序成為新的會話組長和新的程序組長,并與原來的登入會話和程序組脫離。由于會話過程對控制終端的獨占性,程序同時與控制終端脫離。      
4.禁止程序重新打開控制終端。現在,程序已經成為無終端的會話組長,并且該會話沒有控制終端,但會話組長可以重新申請打開一個控制終端。是以要讓程序不是會話組長,來禁止程序重新打開控制終端。代碼:      
if( (pid = fork()) > 0)      
exit(0);  //之前的子程序結束,子子程序繼續運作      
此時的程序組id、會話id是之前子程序的pid,現在程式運作的是子子程序。雖然剛才的會話組長結束了,但是它pid還在,因為程序組、會話的生命期結束消失是在這個組、會話中所有程序都結束時才發生的。      
5.關閉打開的檔案描述符。程序從建立它的父程序那裡繼承了打開的檔案描述符。如不關閉,将會浪費系統資源,造成程序所在的檔案系統無法卸下以及引起無法預料的錯誤。代碼:      
#define MAX_FILENO 1024      
for(i = 0; i < MAX_FILENO; ++i)      
close(i);      
程式能打開的檔案最大個數,在頭檔案<linux/limits.h>中 NR_OPEN 指定,檢視并使用此值。      
6.改變目前工作目錄。程序活動時,其工作目錄所在的檔案系統不能卸下。一般需要将工作目錄改變到根目錄。寫運作日志的程序将工作目錄改變到特定目錄如/tmp。代碼:      
chdir("/");      
7.重設檔案建立掩碼。程序從建立它的父程序那裡繼承了檔案建立掩模。它可能修改守護程序所建立的檔案的存取位。為防止這一點,将檔案建立掩模清除。代碼:      
umask(0);      
8.處理SIGCHLD信号。對于某些程序,特别是伺服器程序往往在請求到來時生成子程序處理請求。如果父程序不等待子程序結束,子程序将成為僵屍程序(zombie)進而占用系統資源。如果父程序等待子程序結束,将增加父程序的負擔,影響伺服器程序的并發性能。在Linux下可以簡單地将 SIGCHLD信号的操作設為SIG_IGN來解決問題。代碼:      
signal(SIGCHLD, SIG_IGN);      
上面說了這麼多都是守護程序的編寫要點,值得一提的是系統已經給我們提供了API讓程序直接成為守護程序:      
#include <unistd.h>

int daemon(int nochdir, int noclose);  //使程序成為守護程序

//0,則改變工作目錄到“/”; 0,則重定向stdin、stdout、stderr到“/dev/null”
      
此外,守護程序可以與日志守護程序建立聯系,将日志資訊寫入日志檔案。用到的函數:      
#include <syslog.h>      
void openlog(const char *ident, int option, int facility);      
//打開目前程式與日志守護程序之間的聯系。      
ident,是一個标記,ident所表示的字元串将固定地加在每行日志的前面以辨別這個日志,通常就寫成目前程式的名稱以作标記。      
option,一般是下列選項值取“與”運算(使用“|”表示,如“LOG_CONS | LOG_PID”)的結果:      
  LOG_CONS:如果送到system logger時發生問題,直接寫入系統終端;
  LOG_NDELAY:立即開啟連接配接,通常連接配接是在第一次寫入消息時才打開的;      
LOG_NOWAIT: Don't wait for child processes that may have  been  created while logging the message.        
(The GNU C library does not create a child process, so this option has no effect on Linux.)      
LOG_ODELAY:The converse of LOG_NDELAY; opening of the connection is delayed until syslog() is called.      
(This is the default,and need not be specified.)
  LOG_PERROR:将消息也同時送到stderr裝置;
  LOG_PID:将程序PID含入所有消息中。
      
facility,指明記錄日志的程式的類型,它主要具有如下幾類日志類型:
  LOG_AUTH :安全/授權消息
  LOG_AUTHPRIV:安全/授權消息(私有)
  LOG_CRON:時間守護程序(cron和at)專用
  LOG_DAEMON:其它系統守護程序
  LOG_KERN:核心消息
  LOG_LOCAL0到LOG_LOCAL7:系統保留
  LOG_LPR:printer子系統
  LOG_MAIL:mail子系統
  LOG_NEWS:USENET新聞子系統
  LOG_SYSLOG:syslogd程序内部所産生的消息
  LOG_USER(預設):一般使用者預設使用消息
  LOG_UUCP:UUCP子系統
  LOG_FTP:FTP子系統使用
      
void syslog(int priority, const char *format, ...);      
//産生一條日志資訊,然後由日志守護程式将其發送到各日志檔案。      
level priority,緊急級别:      
LOG_EMERG:緊急狀況      
  LOG_ALERT:高優先級問題,比如說資料庫崩潰等,必須要立即采取反應行動
  LOG_CRIT:重要狀況發生,比如硬體故障
  LOG_ERR:錯誤發生
  LOG_WARNING:警告發生
  LOG_NOTICE:一般狀況,需要引起注意
  LOG_INFO:資訊狀況
  LOG_DEBUG:調試消息      
第二個參數是消息及其格式,之後是格式對應的參數,如同C語言裡面printf輸出函數一樣使用。      
  在實際使用中,如果我們的程式要使用系統日志功能,隻需要在程式啟動時使用openlog函數來連接配接syslogd程式,後面随時用syslog函數寫日志就行了。
      
void closelog(void);
      
//關閉日志。雖然這個函數簡單,但必不可少,因為日志也是資源,隻打開不管可能會造成記憶體不足。      
守護程式完整示例:      
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syslog.h>
#include <sys/param.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MAX_FILENO 1024
int init_daemon()
{
	int pid;
	int i;
	signal(SIGTTOU,SIG_IGN);
	signal(SIGTTIN,SIG_IGN);
	signal(SIGTSTP,SIG_IGN);
	signal(SIGHUP ,SIG_IGN);
	if( (pid = fork()) > 0)
	{
		exit(EXIT_SUCCESS);
	}
	else if(pid< 0)
	{
		perror("fork");
		exit(EXIT_FAILURE);
	}
	setsid();
	if( (pid = fork()) > 0)
	{
		exit(EXIT_SUCCESS);
	}
	else if(pid< 0)
	{
		perror("fork");
		exit(EXIT_FAILURE);
	}
	for(i = 0; i < MAX_FILENO; ++i)
	{
		close(i);
	}
	open("/dev/null", O_RDONLY);
	open("/dev/null", O_RDWR);
	open("/dev/null", O_RDWR);
	chdir("/tmp");
	umask(0);
	signal(SIGCHLD,SIG_IGN);
	return 0;
}
int main(int argc,char *argv[])
{
	init_daemon();
	openlog("MyLog", LOG_PID, LOG_KERN);
	while(1)
	{
		sleep(1);
		syslog(LOG_INFO,"This is my log : %s\n",argv[0]);
	}
}
           
此時,ps -axj,顯示:                
PPID   PID  PGID   SID  TTY   TPGID STAT   UID   TIME  COMMAND
      
1    4613  4612   4612  ?     -1    S     1000   0:00  ./main
         
打開/var/log/messages 檔案:      
MyLog[4613]: This is my log : ./main

繼續閱讀