1. 程式人生 > >如何實現守護進程?

如何實現守護進程?

相關 文件描述符 完全 else 關聯 再次 ref std 作業

守護進程(Daemon)是運行在後臺的一種特殊進程。它獨立於控制終端並且周期性地執行某種任務或等待處理某些發生的事件。守護進程是一種很有用的進程。

1、守護進程最重要的特性是後臺運行。
2、守護進程必須與其運行前的環境隔離開來。這些環境包括未關閉的文件描述符,控制終端,會話和進程組,工作目錄以及文件創建掩模等。這些環境通常是守護進程從執行它的父進程(特別是shell)中繼承下來的。
3、守護進程的啟動方式有其特殊之處。它可以在Linux系統啟動時從啟動腳本/etc/rc.d中啟動,可以由作業規劃進程crond啟動,還可以由用戶終端(shell)執行。

  總之,除開這些特殊性以外,守護進程與普通進程基本上沒有什麽區別。因此,編寫守護進程實際上是把一個普通進程按照上述的守護進程的特性改造成為守護進程。如果對進程有比較深入的認識就更容易理解和編程了。

守護進程之編程規則
(1)首先要做的是調用umask將文件模式創建屏蔽字設置為0。
  文件權限掩碼:是指屏蔽掉文件權限中的對應位。例如,有個文件權限掩碼是050,它就屏蔽了文件組擁有者的可讀與可執行權限(對應二進制為,rwx, 101)。由於fork函數創建的子進程繼承了父進程的文件權限掩碼,這就給子進程使用文件帶來了諸多的麻煩。因此,把文件權限掩碼設置為0(即,不屏蔽任何權限),可以增強該守護進程的靈活性。設置文件權限掩碼的函數是umask。通常的使用方法為umask(0)。

(2)調用fork,然後使父進程退出(exit)if(pid=fork()) exit(0);

(3)調用setsid以創建一個新會話,脫離控制終端和進程組

。setsid函數作用:用於創建一個新的會話,並擔任該會話組的組長。

技術分享

  調用setsid有3個作用:(a) 讓進程擺脫原會話的控制;(b) 讓進程擺脫原進程組的控制;(c) 讓進程擺脫原控制終端的控制; setsid()
  使用setsid函數的目的:由於創建守護進程的第一步調用了fork函數來創建子進程再將父進程退出。由於在調用fork函數時,子進程拷貝了父進程的會話期、進程組、控制終端等,雖然父進程退出了,但會話期、進程組、控制終端等並沒有改變,因此,這還不是真正意義上的獨立開了。使用setsid函數後,能夠使進程完全獨立出來,從而擺脫其他進程的控制。

(4)將當前工作目錄更改為根目錄。

#define NOFILE 256 for(i=0;i<NOFILE;i++) close(i);

(5)關閉不再需要的文件描述符。這使守護進程不再持有從其父進程繼承來的某些文件描述符(父進程可能是shell進程,或某個其他進程)。

(6)某些守護進程打開/dev/null使其具有文件描述符0、1和2,這樣,任何一個試圖讀標準輸入、寫標準輸出和標準出錯的庫例程都不會產生任何效果。因為守護進程並不與終端設備相關聯,所以不能在終端設備上顯示其輸出,也無處從交互式用戶那裏接受輸入。

#include <sys/types.h>
#include<sys/stat.h>
#include<sys/time.h>
#include<sys/resource.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include<syslog.h>
  
void daemonize(const char *cmd)  
{  
    int         i, fd0, fd1, fd2;  
    pid_t           pid;  
    struct rlimit       rl;  
    struct sigaction    sa;  
    
    umask(0);   // Clear file creation mask. 

    if (getrlimit(RLIMIT_NOFILE, &rl) < 0)  {   // Get maximum number of file descriptors. 
        err_quit("%s: can‘t get file limit", cmd);  
    }  
    if ((pid = fork()) < 0)  {  //這一步fork保證進程不是進程組組長進程 
        err_quit("%s: can‘t fork", cmd);  
    }  
    else if (pid != 0) {    /* parent */  
        exit(0);  
    }  
    setsid();  // 創建一個回話,會話只包含子進程,且子進程是會話首進程 
    /* 
    會話首進程的退出會出發SIGHUP信號 
    默認此信號的操作會終止進程 
     */  
    sa.sa_handler = SIG_IGN;  
    sigemptyset(&sa.sa_mask);  
    sa.sa_flags = 0;  
    if (sigaction(SIGHUP, &sa, NULL) < 0)  {  
        err_quit("%s: can‘t ignore SIGHUP", cmd);  
    }  
    /* 
    再次創建子進程,退出父進程,保證守護進程不是會話首進程,這樣open的時候就不會被分配終端 
    */  
    if ((pid = fork()) < 0)  {  
        err_quit("%s: can‘t fork", cmd);  
    }  
    else if (pid != 0) {  /* parent */  
        exit(0);  
    }  
    
    if (chdir("/") < 0)   {  // 改變當前工作路徑為根目錄
        err_quit("%s: can‘t change directory to /", cmd);  
    }  
    if (rl.rlim_max == RLIM_INFINITY)   {  //關閉所有打開的文件描述符
        rl.rlim_max = 1024;  
    }  
    for (i = 0; i < rl.rlim_max; i++)    {  
        close(i);  
    }  
    /* 
     因為前面關閉了所有的文件描述符,此時open返回的必定是最小的0,後面兩次dup返回的依次是1、2,
     也就完成了對標準輸入、標準輸出、標準錯誤重定向至/dev/null的操作 
     */  
    fd0 = open("/dev/null", O_RDWR);  
    fd1 = dup(0);  
    fd2 = dup(0);  

    /* 
     * Initialize the log file. 
     */  
    openlog(cmd, LOG_CONS, LOG_DAEMON);  
    if (fd0 != 0 || fd1 != 1 || fd2 != 2)   
    {  
        syslog(LOG_ERR, "unexpected file descriptors %d %d %d",fd0, fd1, fd2);  
        exit(1);  
    }  
}  

技術分享

http://www.frankyang.cn/2017/05/25/daemon/

如何實現守護進程?