守護進程(daemon進程)是後臺守護進程,有時候也叫精靈進程(agent).linux 下server都是daemon進程。
其特點是:
1)其父進程是一號進程,通常以d結尾
2)在後臺運行,獨立於終端,週期性的以某種任務或等待處理某些發生的事
3)自成進程組,自成會話,不受登陸註銷等影響
4)一般是孤兒進程
daemon函數存在的原因是因爲控制終端由於某些原因(如斷開終端鏈接)會發送一些信號的原因。而接收進程處理這些信號缺省動作會讓進程退出。這些信號會由於終端上敲一些特殊按鍵而產生。
創建守護進程最關鍵的⼀一步是調⽤用setsid函數創建⼀一個新的Session,併成爲Session Leader。
該函數調⽤用成功時返回新創建的Session的id(其實也就是當前進程的id),出錯返回-1。注意,調 ⽤用這個函數之前,當前進程不允許是進程組的Leader,否則該函數返回-1。要保證當前進程不 是進 程組的Leader也很容易,只要先fork再調⽤用setsid就⾏行了。fork創建的⼦子進程和⽗父進程在同 ⼀一個進 程組中,進程組的Leader必然是該組的第⼀一個進程,所以⼦子進程不可能是該組的第⼀一個 進程,在⼦子 進程中調⽤用setsid就不會有問題了。
成功調⽤用該函數的結果是:
1. 創建⼀一個新的Session,當前進程成爲Session Leader,當前進程的id就是Session的id。
2. 創建⼀一個新的進程組,當前進程成爲進程組的Leader,當前進程的id就是進程組的id。
3. 如果當前進程原本有⼀一個控制終端,則它失去這個控制終端,成爲⼀一個沒有控制終端的進 程。所謂失去控制終端是指,原來的控制終端仍然是打開的,仍然可以讀寫,但只是⼀一個普 通的打開⽂文件⽽而不是控制終端了。
創建守護進程
1. 調⽤用umask將⽂文件模式創建屏蔽字設置爲0.
2. 調⽤用fork,⽗父進程退出(exit)。原因:1)如果該守護進程是作爲⼀一條簡單的shell命令 啓動的,那麼⽗父進程終⽌止使得shell認爲該命令已經執⾏行完畢。2)保證⼦子進程不是⼀一個 進程組的組長進程。
3. 調⽤用setsid創建⼀一個新會話。setsid會導致:1)調⽤用進程成爲新會話的⾸首進程。 2)調⽤用 進程成爲⼀一個進程組的組長進程 。3)調⽤用進程沒有控制終端。(再次fork⼀一次,保證 daemon進程,之後不會打開tty設備)
4. 將當前⼯工作⽬目錄更改爲根⽬目錄。
5. 關閉不在需要的⽂文件描述符。
6. 其他:忽略SIGCHLD信號
貼一個daemon函數常見的實現:
int daemon(void)
{
pid_t pid = fork();
if( pid != 0 ) exit(0);//parent
//first children
if(setsid() == -1)
{
printf("setsid failed\n");
assert(0);
exit(-1);
}
umask(0);
pid = fork();
if( pid != 0) exit(0);
//second children
chdir ("/");
for (int i = 0; i < 3; i++)
{
close (i);
}
int stdfd = open ("/dev/null", O_RDWR);
dup2(stdfd, STDOUT_FILENO);
dup2(stdfd, STDERR_FILENO);
return 0;
}
1 、第一次fork的作用是讓shell 認爲本條命令 已經終止,不用掛在終端輸入上。還有一個作用是爲後面setsid服務。setsid的調用者不能是進程組組長(group leader). 此時父進程是進程組組長。
2 、setsid() 是本函數最重要的一個調用。它完成了daemon函數想要做的大部分事情。調用完整個函數。子進程是會話組長(sid==pid),也是進程組組長(pgid == pid),並且脫離了原來控制終端。到了這一步,基本上不管控制終端如何怎麼樣。新的進程都不會收到那些信號。
3 、經過前面2個步驟,基本想要做的都做了。第2次fork不是必須的。也看到很多開源服務沒有fork第二次。fork第二次主要目的是。防止進程再次打開一個控制終端。因爲打開一個控制終端的前臺條件是該進程必須是會話組長。再fork一次,子進程ID != sid(sid是進程父進程的sid)。所以也無法打開新的控制終端。
daemon目的就是防止終端產生的一些信號讓進程退出。上面函數並沒有直接調用signal函數去處理它。而是間接通過fork和setsid函數使用更少代碼優雅處理。而被有些人誤以爲是僵死進程的原因需要這樣處理。
當然,也有很多程序不是像上面函數那樣去實現。而是直接通過忽略信號方式處理。這樣其實也不錯,因爲這些信號很少會有用到的價值。直接忽略基本上不存在誤殺的情況。反正達到最終目的就可以。條條大路通羅馬。
下面羅列一下控制終端會產生哪些信號。程序中只要處理好這些信號,同樣能達到上面函數實現的目的。
//後臺進程讀取/寫入終端輸入產生下面兩個信號,或者控制終端不存在情況讀取和寫入會產生
signal(SIGTTOU, SIG_IGN);
signal(SIGTTIN, SIG_IGN);
//按CTRL-C ,CTRL-\ CTRL-Z會向前臺進程組發送下面這些信號
signal(SIGINT, SIG_IGN );
signal(SIGQUIT, SIG_IGN );
signal(SIGTSTP, SIG_IGN );
//終端斷開,會給會話組長或孤兒進程組所有成員發送下面信號
signal(SIGHUP, SIG_IGN );
還有有些信號也可以由終端shell產生,需要關注
signal(SIGCONT, SIG_IGN );
signal(SIGSTOP, SIG_IGN );
上面這些信號,應該有些程序缺省處理(SIG_DFL)本身動作就是忽略(SIG_IGN),不是退出進程。不過按照上面寫也不會造成什麼問題。