天天看點

守護程序Daemon---fork兩次

1.什麼是守護程序?

守護程序也叫精靈程序,是在背景運作的一種特殊程序,它獨立于控制終端并且周期性的執行某種任務或等待處理某些發生的事件;

在 linux系統啟動時會有很多系統服務程序,這些系統服務程序沒有控制終端,不能直接和使用者互動,除守護程序外其他程序都是在使用者登入或運作程式時建立,在系統登出時終止,但系統服務程式不受使用者登入登出的影響(原因:它跟終端沒有關系),我們把這種程序叫做守護程序。

一個守護程序的父程序是init程序,因為它真正的父程序在fork出子程序後就先于子程序exit退出了,是以它是一個由init繼承的孤兒程序。守護程序是非互動式程式,沒有控制終端,是以任何輸出,無論是向标準輸出裝置stdout還是标準出錯裝置stderr的輸出都需要特殊處理。

2.為什麼要有守護程序??

控制終端因為某些原因會發送一些信号,接受到信号的程序去執行這些信号的預設處理動作會導緻程序退出。這就使得程序不能正常的處理某些任務,是以就需要像守護程序這樣接受不到信号的程序。讓程序獨立與控制終端,執行某些任務或處理某些事件。

3.我們通過ps ajx指令檢視系統的程序

ps ajx中ajx各參數代表的意思

  • 參數a,表示不僅列出目前程序,也列出所有其他使用者程序;
  • 參數x,表示不僅列出控制終端的程序,也列出所有無控制終端的程序;
  • 參數j,表示列出與作業控制相關的資訊;
    守護程式Daemon---fork兩次

上圖顯示的是部分守護程序,因為精靈程序跟終端沒有關系,是以TTY顯示是‘?’的程序都為精靈程序;同時我們也可以看到,精靈程序的TPGID都為-1,且程序的COMMAND(執行指令)都用[]括号;

守護程序通常都已‘d’結尾,我們可以通過ps ajx|grep -E ‘d$’指令檢視

守護程式Daemon---fork兩次
守護程式Daemon---fork兩次

init程序的id為1,是以上述父程序id為1的都是init程序建立的子程序,且每個守護程序的會話id、組id。程序id都是相同的(此時一個會話裡隻有一個程序);

4.setsid函數

  • 關鍵函數 setsid
    守護程式Daemon---fork兩次
  • 該函數建立了一個新的會話,但是該函數有其建立的要求,如下圖:
    守護程式Daemon---fork兩次
    要求:調用這個函數的目前程序不允許是目前程序組的組長id,否則的話該函數調用失敗傳回-1,(解決方法:fork出一個子程序來調用setsid就可以滿足要求)
  • 成功調用該函數後的結果

    <1>.建立了一個新的Session,目前程序為會話首程序,目前程序的id記為會話為id;

    <2>.建立了一個新的程序組,目前程序為程序組的組長程序,目前程序的id記為目前程序組的id;

    <3>.如果目前程序有一個控制終端,那麼它将失去這個控制終端;(此時将控制終端看成一個普通檔案,檔案是打開的,可以都希望和關閉)

5.建立守護程序

  • 調用 unmask将檔案模式建立屏蔽字設定為0;(保證精靈程序建立的檔案是不受權限的限制的);
  • 調用fork函數,父程序退出

    -如果該程序作為簡單的shell指令啟動,父程序終止使得shell認為前台作業結束,将該程序轉換到背景運作(為什麼麼父程序exit);

    -保證子程序不是一個程序的組長程序(為什麼fork);

  • 在fork中調用setsid函數,建立一個新的會話,調用成功後得到三個結果(會話、程序組、控制終端);
  • 将目前的工作目錄設定為根目錄(因為系統可以删除掉除根目錄的所有路徑,設定為根目錄,不會影響守護程序 chdir);
    守護程式Daemon---fork兩次
  • 關閉不需要的檔案描述符(标準輸入、标準輸出、标準錯誤);
  • 忽略信号(SIGCHID);

6.根據守護程序的建立步驟編寫代碼

  • fork一次
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>

void create_daemon()
{
    umask();//掩碼設定為0,不會屏蔽任何檔案權限操作
    pid_t id = fork();//建立子程序

    if(id<){//子程序建立出錯
        perror("fork");
    }else if(id>){//father
        exit();//父程序直接退出,程序轉到背景運作
    }else{//child
        setsid();//建立新的會話

        chdir("/");//設定工作目錄為根目錄

        close();//因為控制終端現在隻是檔案,關閉預設打開的檔案描述符,節省資源,防止終端檔案不能卸下
        close();
        close();

        signal(SIGCHLD,SIG_IGN);
    }
}
int main()
{
    create_daemon();
    while()
    {
        sleep();
    }
    return ;
}
           

結果圖:

守護程式Daemon---fork兩次

由上圖我們可以看見,運作./myDaemon指令生成了一個守護程序,TTY為?,PID、PGID、SID都相同,PPID為1,父程序為init程序,都說明該程序為守護程序;

  • fork兩次
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>

void create_daemon()
{
    umask();
    pid_t id = fork();//fork一次

    if(id<){
        perror("fork");
    }else if(id>){//father
        exit();
    }else{//child
        setsid();

        signal(SIGCHLD,SIG_IGN);

        if(id = fork()<){//fork第二次
            perror("fork");
        }else if(id > ){
            exit();
        }else{
            chdir("/");
            close();
            close();
            close();
        }
    }
}
int main()
{
    create_daemon();
    while()
    {
        sleep();
    }
    return ;
}
           

結果圖:

守護程式Daemon---fork兩次

可以看到,fork兩次後,pid 與PGID和SID不相同;

7.fork一次與fork兩次的差別:

8.我們也可以通過系統調用函數來建立守護程序

守護程式Daemon---fork兩次
  • nochdir表示是否要将目前工作目錄;當為0時,修改其為根目錄”/“,否則不修改
  • noclose表示是否要關閉終端原來打開的檔案;當為0時,将标準輸入輸出、标準錯誤關閉,否則不關閉;

繼續閱讀