守護進程解析
時間:2018-03-09作者:華清遠見
一:守護進程的簡介我們常用的進程一般分為三類:<1>交互進程 <2>批處理進程<3>守護進程。守護進程(Daemon)是一種運行在后臺的一種特殊的進程,它獨立于控制終端并且周期性的執行某種任務或等待處理某些發生的事件。守護進程也叫精靈進程。由于在linux中,每個系統與用戶進行交流的界面成為終端,每一個從此終端開始運行的進程都會依附于這個終端,這個終端被稱為這些進程的控制終端,當控制終端被關閉的時候,相應的進程都會自動關閉。但是守護進程卻能突破這種限制,它脫離于終端并且在后臺運行,并且它脫離終端的目的是為了避免進程在運行的過程中的信息在任何終端中顯示并且進程也不會被任何終端所產生的終端信息所打斷。它從被執行的時候開始運轉,知道整個系統關閉才退出(當然可以認為的殺死相應的守護進程)。如果想讓某個進程不因為用戶或中斷或其他變化而影響,那么就必須把這個進程變成一個守護進程。 二:相關概念 查看守護進程第一行的信息。ps axj | head -1 ubuntu@farsight:/etc/profile.d$ ps axj | head -1 PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 父進程ID 進程ID 進程組ID 會話期ID 終端ID 終端進程組ID 狀態 用戶 運行時間 指令 1:進程組 每運行一個程序或是命令就會產生一個進程組,而每一個進程組有一個組長進程。 進程組由進程組號(GID)標識,進程組號(GID)為組長進程PID,一般進程組的第一個進程是組長進程。 注:進程組中的這些進程之間不是孤立的,他們彼此之間或者存在者父子、兄弟關系,或者在功能有相近的聯系。 那linux為什么要有進程組呢?其實提供進程組就是方便管理這些進程。 進程組的概念有很多用途,最常見的是我們在終端上向前臺執行程序發出終止信號(Ctrl-C),同時終止整個進程組的所有進程。 每個進程必定屬于一個進程組,也只能屬于一個進程組。
函數getpgrp可以返回調用進程的進程組ID pid_t getpgrp(void); //得到進程組的ID 返回值:成功則返回進程組ID,失敗返回-1。
2:會話 一次登錄形成一個會話,一個會話可包含多個進程組(前臺或后臺), 但只能有一個前臺進程組,多個進程組組成我們的會話。 setsid()可建立一個新的會話,注意進程組的組長進程不能調用,調用進程是新會話的首進程(session leader) 3:控制終端 會話的首進程(session leader)打開一個終端之后, 該終端就成為該會話的控制終端 與控制終端建立連接的會話首進程稱為控制進程,一個會話只能有一個控制終端 在控制終端上產生的輸入和信號將發送給會話的前臺進程組中的所有進程 終端上的連接斷開時 (比如網絡斷開或 Modem 斷開), 掛起信號將發送到控制進程
linux是一個多用戶多任務的分時操作系統,必須要支持多個用戶同時登陸同一個操作系統,當一個用戶登陸一次終端時就會產生一個會話。
每個會話有一個會話首進程,即創建會話的進程,建立與終端連接的就是這個會話首進程,也被稱為控制進程。 為什么要這么分呢?前臺進程組是指需要與終端進行交互的進程組(只能有一個比如有些進程是需要完成IO操作的,那么這個進程就會被設置為前臺進程組.當我們鍵入終端的中斷鍵和退出鍵時,就會將信號發送到前臺進程組中的所有進程。而后臺進程組是指不需要與終端進程交互的進程組,比如:一些進程不需要完成IO 操作,或者一些守護進程就會 被設置為后臺進程組(可以有多個)。 如果終端接口檢測到網絡已經斷開連接,則會將掛斷信號發送給會話首進程。 三:創建守護進程 創建一個守護進程的步驟如下: <1>創建子進程,父進程退出 這是創建守護進程的第一步。由于守護進程是脫離控制終端的,因此,完成第一步后就會在Shell終端里造成一程序已經運行完畢的假象。之后的所有工作都在子進程中完成,而用戶在Shell終端里則可以執行其他命令,從而在形式上做到了與控制終端的脫離。在Linux 中父進程先于子進程退出會造成子進程成為孤兒進程,而每當系統發現一個孤兒進程是,就會自動由1號進程(init)收養它,這樣,原先的子進程就會變成init進程的子進程。 <2>在子進程中創建的新會話 [脫離控制終端] Linux是一個多用戶多任務系統,每個進程都有一個進程ID,同時每個進程還都屬于某一個進程組,而每個進程組都有一個組長進程,組長進程的標識ID等于進程組的ID,且該進程組ID不會因組長進程的退出而受到影響。會話期是一個或多個進程組的集合,通常,一個會話開始與用戶登錄,終止于用戶退出,在此期間該用戶運行的所有進程都屬于這個會話期。我們這里要用到setsid()函數。 setsid()函數的作用:創建一個新的會話,并且擔任該會話組的組長。具體作用包括:讓一個進程擺脫原會話的控制,讓進程擺脫原進程的控制,讓進程擺脫原控制終端的控制。 pid_t setsid(void); (1)此進程變成該對話期的首進程。 (2)此進程變成一個新進程組的組長進程。 (3)此進程沒有控制終端,如果在調用setsid前,該進程有控制終端,那么與該終端的聯系被解除。 (4)這個系統調用,只能由非組長進程來調用。組長進程不能調用。 思考:有了前邊兩個步驟,有沒有人有這樣的疑問,為什么要創建個子進程然后父進程退出,然后setsid()脫離當前會話?直接就在之前的進程中setsid()把當前進程脫離會話不可以么? 答案:組長進程,一般是這個程序執行的第一個進程,那么我們的父進程一般就是這組進程的組長,而setsid()這個函數的使用,是不能由組長進程來調用的。所以必須先創建一個非組長進程來調用它。 <3>改變進程的工作目錄到"/" 使用fork創建的子進程繼承了父進程的當前工作目錄。 守護進程不應當使用父進程的工作目錄,應該設置自己的工作目錄,通常可以通過 chdir()來完成,一般可以將其設置為根目錄。 chdir("/"); <4>重設文件掩碼 umask(0) 守護進程從父進程繼承來的文件創建方式掩碼可能會拒絕設置某些許可權限,文件權限掩碼是指屏蔽掉文件權限中的對應位。 umask(0); <5>關掉所有不需要的文件描述符號 如果創建它的進程之前打開了某個文件,然后創建這個守護進程,這樣子進程就繼承了 fd,如果守護進程不關閉這個fd,一個是會占用資源,二個與改變工作目錄一樣,如果這個文件是位于掛載目錄,那么就無法umount了。 這里可以關閉所有當前系統的文件描述符。怎么獲取當前進程最大的可以打開的文件描述符個數呢?sysconf(_SC_OPEN_MAX); <6>重新處理一下[0,1,2]==>標準輸入、標準輸出、標準出錯。 這里需要把0,1,2重新定位一下,定位到/dev/null。這樣后邊再使用open的時候,獲得的文件描述符還可以從3開始。不然就是從0開始的,而如果我們程序中,有從0中獲取標準輸入,從1中做輸出,那么就成了對那個文件的讀寫了,這顯然是錯誤的了。
把/dev/null看作"黑洞"。 它非常等價于一個只寫文件, 所有寫入它的內容都會永遠丟失,而嘗試從它那兒讀取內容則什么也讀不到.。 int main(int argc, const char *argv[]) { pid_t pid = fork(); if (pid ==-1) { perror("fork()"); exit(-1); } else if (0 == pid) { //當前是子進程 //1.重新設置會話 if (setsid() < 0) { perror("setsid()"); exit(-1); } //2.把工作目錄設置到根目錄 chdir("/"); //3.重新設置文件權限掩碼 umask(0); //4.關閉所有文件描述符 int i; for (i = 1; i < sysconf(_SC_OPEN_MAX); i++) close(i); //5. 特殊處理0,1,2文件描述符 open("/dev/null", O_RDWR); open("/dev/null", O_RDWR); open("/dev/null", O_RDWR); } else { / /當前是父進程,直接退出 return 0; } return 0; } 拓展:守護進程創建之后,那么中間的調試信息不能通過屏幕輸出出來了,那么我們看不到中間的過程,怎么來調試呢?可以通過syslog來將調試信息寫到日志中。 頭文件:#include<syslog.h> 函數原型:void openlog(const char *ident,int option,int facil-ity); 函數功能:打開日志文件。 函數參數:@ ident:對哪個進程進行日志記錄,為進程名,如果不指定,則默認也是進程名。 @ option常用選項: LOG_CONS:如果當前寫日志失敗了,就將信息輸出到終端控制臺。 LOG_PID:打印的每一條日志信息包含當前進程的PID @ facil-ity常用選項: LOG_USER:打印的每一條日志信息包含當前用戶的等級信息 函數原型:void syslog(int priority, const char *format, ...); 函數功能:向日志文件中寫日志信息。 函數參數:@ format:輸出日志信息的參數列表,用法類同printf @ priority:輸出日志的等級信息 LOG_EMERG system is unusable LOG_ALERT action must be taken immediately LOG_CRIT critical conditions LOG_ERR error conditions LOG_WARNING warning conditions LOG_NOTICE normal, but significant, condition LOG_INFO informational message LOG_DEBUG debug-level message 注意:寫完的日志信息在哪里呢?一般是在/var/log目錄下,不同的發行版對應的具體的文件不一樣,ubuntu中對應的是/var/log/syslog文件中。 函數原型:void closelog(void); 函數功能:關閉日志文件。 一般就是在進程啟動的時候打開日志,在進程結束的時候去關閉日志,這樣中間就不同的記錄就可以了,不需要每次記錄都打開關閉,甚至打開和關閉也可以不需要,系統在第一次調用syslog記錄日志的時候,如果發現日志是沒有打開的,就按照自己默認的方式幫我們先打開日志文件,但是默認的記錄的內容相對簡單,如果我們自己需要控制日志中都輸出什么信息,我們最好按照自己需要的方式打開一下。 相關資訊
發表評論
|