一、基本概念
1、守護進程也成爲精靈進程,是生存週期較長的一種進程。它們常常在系統自舉時啓動,在系統關閉時才終止。因爲沒有控制終端,所以說它們是在後臺運行的。
2、父進程ID爲0的各進程通常是內核進程,它們作爲系統自舉過程都得一部分而啓動。
3、大多數守護進程都以超級用戶(用戶ID爲0)特權運行。沒有一個守護進程具有控制終端,其終端名設置爲問號(?),終端前臺進程組ID設置爲-1。內核守護進程以無控制終端方式啓動。用戶層守護進程缺少控制終端可能是守護進程調用了setsid的結果。所有用戶層守護進程都是進程組的組長進程以及會話的首進程,而且是這些進程組和會話中的唯一進程。最後,注意,大多數守護進程的父進程是init進程。
注:
二、編程規則:
(1)首先要做的是調用umask將文件模式創建屏蔽字設置爲0。由繼承得來的文件模式創建屏蔽字可能會拒絕設置某些權限。
例如,若守護進程要創建一組可讀、可寫的文件,而繼承的文件模式創建屏蔽字可能屏蔽了這兩種權限,於是所要求的組可讀、寫就不能起作用。
(2)調用fork,然後使父進程退出(exit)。這樣做實現了以下幾點:第一:如果守護進程是作爲一條簡單shell命令啓動的,但具有一個新的ID,這就保證了子進程不是一個進程組的組長進程。這對於下面就要做的setsid調用是必要的前提條件。
(3)調用setsid以創建一個新會話。於是執行三個操作:
使得調用進程完成以下動作:
--稱爲新會話的首進程
--稱爲一個新進程組的組長進程
--沒有控制終端
(4)將當前工作目錄更改爲根目錄。從父進程繼承過來的當前工作目錄可能在一個裝配文件 系統中。因爲守護進程通常在系統再引導之前是一直存在的,所以如果守護進程的當前工作目錄在一個裝配文件系統中,那麼文件系統就不能被拆卸。這與裝配文件系統的原意不符。
另外,某些守護進程可能會把當前工作目錄更改到某個指定位置,在那裏做它們的工作。
(5)關閉不再需要的文件描述符。這使得守護進程不再持有從其父進程繼承來的某些文件描述符(父進程可能是shell進程或者其他某個進程)。
(6)某些守護進程打開/dev/null使其具有文件描述符0、1和2,這樣,任何一個試圖讀標準輸入、寫標準輸出或標準出錯的庫例程都不會產生任何效果。
因爲守護進程並不與終端設備相關聯,所以不能再終端設備上顯示其輸出,也無處從交互式用戶那裏接受輸入。
即使守護進程是從交互式會話啓動的,但因爲守護進程是後臺運行的,所以登錄會話的終止並不影響守護進程。如果其他用戶在同一終端設備上登錄,我們也不會見到守護進程的輸出,用戶也不希望他們在終端上的輸入會由守護進程讀取。
注:某些時候可能需要處理SIGCHLD信號:
雖然處理SIGCHLD並不是必須的,但是對於某些進程,特別是服務器進程往往在請求到來時生成子進程處理請求。如果父進程不等待子進程結束,子進程將成爲殭屍進程(zombie)從而佔用系統資源。如果父進程等待子進程結束,將增加父進程 的負擔,影響服務進程的併發性能。
在Linux下可以簡單的將SIGCHLD信號的操作設爲SIG_IGN。
signal(SIGCHLD, SIG_IGN);
這樣,內核在子進程結束時不會產生殭屍進程。
轉:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>
void my_daemon() {
int pid, fd;
// 1.轉變爲後臺進程
if ((pid = fork()) == -1) exit(1);
if (pid != 0) exit(0); // 父進程(前臺進程)退出
// 2.離開原先的進程組,會話
if (setsid() == -1) exit(1); // 開啓一個新會話
// 3.禁止再次打開控制終端
if ((pid = fork()) == -1) exit(1);
if (pid != 0) exit(0); // 父進程(會話領頭進程)退出
// 4.關閉打開的文件描述符,避免浪費系統資源
for (int i = 0; i < NOFILE; i++)
close(i);
// 5.改變當前的工作目錄,避免卸載不了文件系統
if (chdir("/") == -1) exit(1);
// 6.重設文件掩碼,防止某些屬性被父進程屏蔽
if (umask(0) == -1) exit(1);
// 7.重定向標準輸入,輸出,錯誤流,因爲守護進程沒有控制終端
if ((fd = open("/dev/null", O_RDWR)) == -1) exit(1); // 打開一個指向/dev/null的文件描述符
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
close(fd);
// 8.本守護進程的子進程若不需要返回信息,那麼交給init進程回收,避免產生殭屍進程
if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) exit(1);
}
#define INTERVAL 2
int main(int argc, char *argv[]) {
my_daemon(); // 首先使之成爲守護進程
int t = 0;
FILE *fp = fopen("/root/tmp.txt", "a");
fprintf(fp, "ppid = %d, pid = %d, sid = %d, pgrp = %d\n", getppid(), getpid(), getsid(0), getpgrp());
fflush(fp);
do { // 測試此後臺進程,每INTERVAL秒打印當前時間t,30秒後退出此後臺進程
fprintf(fp, "%d\n", t);
fflush(fp); // 輸出緩衝區內容到文件中
sleep(INTERVAL);
t += INTERVAL;
} while(t < 30);
fclose(fp);
return 0;
}
保存爲daemon.c
編譯命令 gcc daemon.c
運行 ./a.out
查看tmp.txt文件內容 cat /root/tmp.txt
三、出錯記錄
由於守護進程沒有控制終端,所以需要有一個集中的守護進程出錯記錄設施。
四、守護進程慣例
在UNIX系統中,守護進程遵循以下公共慣例:
1、若守護進程使用鎖文件,那麼該文件通常存放在/var/run目錄中,注意,守護進程可能需要具有超級用戶權限才能在此目錄下創建文件。鎖文件的名字通常是name.pid,其中,name是該守護進程或服務的名字。例如cron守護進程鎖文件的的名字是/var/run/cron.pid
2、若守護進程支持配置選項,那麼配置文件通常存放在/etc目錄中。配置文件的名字通常是name.conf,其中,name是該守護進程或服務的名字。syslogd守護進程的配置文件是/etc/syslog.conf
3、守護進程可用命令行啓動,但通常它們是由系統初始化腳本之一(/etc/rc* 或 /etc/init.d/* )啓動的。如果在守護進程終止時,應當自動重啓它,則我們可以再/etc/inittab中爲該守護進程包括_respawn記錄項,這樣,init就將重啓該守護進程。
4、若一個守護進程有一個配置文件,那麼當該守護進程啓動時,它讀該文件,但在此之後一般不會再查看它。