1. 程式人生 > >37.Linux/Unix 系統程式設計手冊(下) -- DAEMON

37.Linux/Unix 系統程式設計手冊(下) -- DAEMON

1.daemon特徵
	1.它的生命週期很長,通常,一個daemon會在系統啟動的時候被建立並一直執行直至系統被關閉
	2.它在後臺執行並且不擁有控制終端。控制終端的缺失,確保了核心永遠不會為daemon自動生成
	  任何控制訊號以及終端相關的訊號(SIGINT,SIGTSTP和SIGHUP)


2.建立一個daemon	
	要變成一個daemon,一個程式需要完成如下步驟:
	1.執行一個 fork(), 關閉父程序,子程序繼續執行。原因如下:
		1.假設daemon是從命令列啟動,父程序的終止會被shell發現,shell發現之後會顯示出另一個shell
		  提示符並讓子程序繼續在後臺執行。
		2.子程序被確保不會成為程序組的首程序,因為它從父程序那裡繼承了程序組ID並且擁有了自己唯一的程序ID,
		  而這個程序ID與繼承而來的程序組ID是不同的,這樣才能成功執行下面的步驟
	2.子程序呼叫 setsid()開啟一個新的會話並釋放它與控制終端之間的所有關聯。
	3.如果daemon從來沒有開啟過終端裝置,那麼久無需擔心daemon會重新請求一個控制終端了。如果daemon後面可能會開啟
	  一個控制終端,那麼必須要採取措施來確保這個裝置不會成為控制終端。可以通過下面2種方式實現:
	  	1.在所欲可能應用到的一個終端裝置上的open()呼叫指定O_NOCTTY標記
	  	2.或者更簡單的說,在 setsid() 呼叫之後執行第二個 fork(),然後再次讓父程序退出,並讓孫子程序繼續。這樣就確保了
	  	  子程序不會成為會話組長。
	4.清除程序的umask以確保daemon建立檔案和目錄時擁有所需的許可權
	5.修改程序的當前工作目錄,通常會修改為根目錄(/)。這樣做是必要的,因為daemon通常會一直執行直到系統關閉。如果daemon的當前工作
      目錄為不包含/的檔案系統,那麼久無法解除安裝該檔案系統。或者daemon可以將工作目錄修改為完成任務時所在的目錄後者配置檔案中定義的一個
      目錄,只要包含這個目錄的檔案系統永遠不會被解除安裝即可。
    6.關閉daemon從父程序繼承而來的所有開啟的檔案描述符。
    	由於daemon失去了控制終端並且是在後臺執行的,因此讓daemon保持檔案描述符0,1,2的開啟狀態毫無意義,因為它們就是指向終端。
      此外,由於無法解除安裝長期執行的daemon開啟的檔案所在的檔案系統,因此,通常的做法是關閉所有無用的開啟著的檔案描述符,因為檔案
      描述符是一種有限的資源。
    7.在關閉了檔案描述符0,1,2之後,daemon 通常會開啟 /dev/null 並使用dup2()使這些描述符指向這個裝置。之所以這麼做原因如下:
    	1.它確保了daemon呼叫了在這些檔案描述符上執行IO的庫函式時,不會出現意外的失敗
    	2.它防止了daemon後面使用描述符1或者2開啟一個檔案的情況,因為庫函式會將這些誒描述符當作標準輸入和標準錯誤來寫資料。

    ? 表示沒有控制終端


3.編寫daemon指南
	一個daemon通常只有在系統關閉時才會終止。很多標準的daemon是通過在系統關閉時執行特定的應用程式的指令碼來停止的。而不以這種方式
  終止的daemon會收到一個 SIGTERM 訊號,因為在系統關閉時 init 程序會向所有的子程序傳送這個訊號。在預設情況下,SIGTERM 會終止
  一個程序,如果daemon在終止之前需要做清理工作,那麼就需要為這個訊號建立一個處理器。這個處理器必須能夠快速的完成清理工作,因為init
  會在發完 SIGTERM 訊號的 5 秒後,傳送一個 SIGKILL 訊號。
  	由於 daemon 是長期執行的,因此要特別小心記憶體洩漏問題和檔案描述符洩漏(即應用程式沒有關閉開啟的檔案描述符)。
  	很多deamon需要確保同一時刻只要一個例項處於活躍狀態(檔案鎖?)。


4.使用 SIGHUP 重新初始化一個 daemon
	由於很多daemon需要持續執行,因此在設計daemon程式時需要克服一些障礙:
		1.通常daemon會在啟動時從相關的配置檔案中讀取操作引數,但有些時候需要在不重啟daemon的情況下快速修改這些引數(重新讀取配置)
		2.一些daemon會產生日誌。如果daemon永遠不關閉日誌的話,那麼日誌檔案就會無限的增長,最終會阻塞檔案系統(即使刪除了一個檔案的檔名,
		  只要有程序還開啟這個檔案,那麼這個檔案就會一直存在下去)。這裡需要有一個機制來告訴daemon關閉其日誌並開啟一個新檔案,這樣就能夠在需要的
		  時候輪轉日誌檔案了。(日誌切割)
	在解決這2個問題的方案是讓 daemon 為 SIGHUP 建立一個處理器,並且在收到這個訊號的時候採取所需的措施。當控制程序與控制終端斷開連線之後會生成 SIGHUP訊號。
  由於 daemon沒有控制終端,因此核心永遠不會向 daemon 傳送這個訊號。這樣 daemon 就可以使用 SIGHUP 訊號來達到目的了。


5.使用syslog記錄訊息和錯誤
	在編寫daemon時碰到一個問題是如何顯示錯誤訊息。由於daemon是在後臺執行的,因此無法像其他應用程式一樣將訊息輸出到關聯終端。
	syslog 主要有2個元件:syslogd daemon 和 syslog(3)庫函式
	System Log daemon syslogd 從2個不同的源接收日誌訊息:
		1.UNIX domain socket /dev/log, 它儲存本地產生的日誌
		2.Internet domain socket ,它儲存通過 TCP/IP 網路傳送的訊息

	每條由 syslogd 處理的訊息都具備幾個特性,其中包括一個 facility ,它指定了產生訊息的程式型別;還有一個是 level,它指定了日誌的嚴重程度。
  syslogd daemon 會檢查每個訊息的 facility 和 level,然後根據一個相關的配置檔案 /etc/syslog.conf 中的指令將訊息傳遞到計劃人可能的目的地之一。
  可能的目的地包括終端或者虛擬控制檯,磁碟檔案,FIFO,一個或者多個登入過的使用者以及另外一個系統上的通過 TCP/IP 網路連線的程序(將訊息傳送到另外一個系統
  的程序有助於將多個系統中的日誌集中到一個位置以降低管理負擔)。一條訊息可能被髮送到多個目的地,具備不同的 facility 和 level 組合的訊息可以被髮送到不同
  的目的地或者不同的目的地例項。
    傳送到另外一個系統,還有助於發現系統非法入侵。攻擊者會刪除日誌,就意味著也要攻破另外一個系統。
    通常任意程序都可以使用 syslog(3)庫函式來記錄訊息。這個函式會將傳入的引數以標準的格式構建一條訊息,然後將這條訊息寫入 /dev/log socket 以供 syslogd讀取。


6.syslog API
	openlog(); // 可選的
	syslog();
	closelog();
	setlogmask();


7./etc/syslog.conf
	格式:
	facility.level action
	facility 和 level 組合在一起被稱為選擇器,因為它們選擇了需應用規則的訊息。action指定了與選擇器匹配的訊息被髮送到何處。例如:
	*.err /dev/tty10
	auth.notice root