從進程組、會話、終端的概念深入理解守護進程


從進程組、會話、終端的概念深入理解守護進程

一、寫在前面

「守護進程」是 Linux 的一種長期運行的后台服務進程,也有人稱它為「精靈進程」。我們常見的 httpd、named、sshd 等服務都是以守護進程 Daemon 方式運行的,通常服務名稱以字母d結尾,也就是 Daemon 第一個字母。與普通進程相比它大概有如下特點:

  • 無需控制終端(不需要與用戶交互)
  • 在后台運行
  • 生命周期比較長,一般是隨系統啟動和關閉

二、守護進程必要性

為什么要設置為守護進程,普通進程不可以嗎?

當我們在命令行提示符后輸入類似./helloworld程序時,在程序運行時終端被占用,此時無法執行其它操作。即使使用./helloworld &方式后台運行,當連接終端的網絡出現問題,那么也會導致運行程序中斷。這些因素對於長期運行的服務來說很不友好,而「守護進程」可以很好的解決這個問題。

三、對進程組、會話、終端的理解

「守護進程」理解起來並不復雜,代碼編寫上有基本固定的套路。如果想要深入理解「守護進程」基本原理,那么必須要首先理解 Linux 的進程、進程組、會話、終端等概念。

1、進程

  • 進程是 Linux 進行資源分配的最小單位
  • 前台進程,例如這樣:$ ./hello
  • 后台進程,例如這樣:$ ./hello & 釋放對控制終端的占用

2、進程組

每個進程都會屬於一個進程組,進程組中可以包含一個或多個進程。進程組中有一個進程組長,組長的進程 ID 是進程組 ID(PGID)

$ ps -o pid,pgid,ppid,comm | cat
  PID  PGID  PPID  COMMAND
10179  10179 10177 bash
10263  10263 10179 ps
10264  10263 10179 cat

下邊通過簡單的示例來理解進程組

  • bash:進程和進程組ID都是 10179,父進程其實是 sshd(10177)
  • ps:進程和進程組ID都是 10263,父進程是 bash(10179),因為是在 Shell 上執行的命令
  • cat:進程組 ID 與 ps 的進程組 ID 相同,父進程同樣是 bash(10179)

容易理解 Bash 就是Shell進程,Shell 父進程是 sshd;ps 與 cat 通過管道符號一起運行,屬於一個進程組,其父進程都是 Bash;一個進程組也被稱為「作業」。

3、會話(session)

多個進程組構成一個「會話」,建立會話的進程是會話的領導進程,該進程 ID 為會話的 SID。會話中的每個進程組稱為一個「作業」。會話可以有一個進程組稱為會話的「前台作業」,其它進程組為「后台作業」

一個會話可以有一個控制終端,當控制終端有輸入和輸出時都會傳遞給前台進程組,比如Ctrl + Z。會話的意義在於能將多個作業通過一個終端控制,一個前台操作,其它后台運行。

4、前后台作業相關操作

讓作業由進入后台運行:

$ ping localhost >/dev/null &
[1] 10269    # 終端顯示
# [1]:作業ID  10269:進程組ID

給后台作業發信號 SIGTERM

$ kill -SIGTERM -10269  # 發信號給進程組
$ kill -SIGTERM %1      # 發信號給作業1

讓后台進程切換到前台:

$ fg %1
# ping 進程重新切到前台

四、編寫守護進程

編寫守護進程看似復雜,但實際上也是遵循一個特定的流程。

1、創建子進程,父進程退出

進程 fork 后,父進程退出。這么做的原因有 2 點:

  • 如果守護進程是通過 Shell 啟動,父進程退出,Shell 就會認為任務執行完畢,這時子進程由 init 收養
  • 子進程繼承父進程的進程組 ID,保證了子進程不是進程組組長,因為后邊調用setsid()要求必須不是進程組長

2、子進程創建新會話

調用setsid()創建一個新的會話,並成為新會話組長。這個步驟主要是要與繼承父進程的會話、進程組、終端脫離關系。

3、禁止子進程重新打開終端

此刻子進程是會話組長,為了防止子進程重新打開終端,再次 fork 后退出父進程,也就是此子進程。這時子進程 2 不再是會話組長,無法再打開終端。其實這一步驟不是必須的,不過加上這一步驟會顯得更加嚴謹。

4、設置當前目錄為根目錄

如果守護進程的當前工作目錄是/usr/home目錄,那么管理員在卸載/usr分區時會報錯的。為了避免這個問題,可以調用chdir()函數將工作目錄設置為根目錄/

5、設置文件權限掩碼

文件權限掩碼是指屏蔽掉文件權限中的對應位。由於使用 fork()函數新建的子進程繼承了父進程的文件權限掩碼,這就給該子進程使用文件帶來了諸多的麻煩。因此,把文件權限掩碼設置為 0,可以大大增強該守護進程的靈活性。通常使用方法是umask(0)

6、關閉文件描述符

子進程會繼承已經打開的文件,它們占用系統資源,且可能導致所在文件系統無法卸載。此時守護進程與終端脫離,常說的輸入、輸出、錯誤描述符也應該關閉。

五、守護進程的出錯處理

由於守護進程脫離了終端,不能將錯誤信息輸出到控制終端,即使 gdb 也無法正常調試。常用的方法是使用 syslog 服務,將錯誤信息輸入到/var/log/messages中。

syslog 是 Linux 中的系統日志管理服務,通過守護進程 syslogd 來維護。該守護進程在啟動時會讀一個配置文件/etc/syslog.conf。該文件決定了不同種類的消息會發送向何處。

六、守護進程編碼示例

pid_t pid, sid;
int i;
openlog("daemon_syslog", LOG_PID, LOG_DAEMON);
pid = fork(); // 第1步
if (pid < 0) exit(-1);
else if (pid > 0) exit(0);  // 父進程第一次退出
if ((sid = setsid()) < 0)   // 第2步
{
    syslog(LOG_ERR, "%s\n", "setsid");
    exit(-1);
}
// 第3步 第二次父進程退出
if ((pid = fork()) > 0) exit(0);
if ((sid = chdir("/")) < 0) // 第4步
{
    syslog(LOG_ERR, "%s\n", "chdir");
    exit(-1);
}
umask(0); // 第5步
// 第6步:關閉繼承的文件描述符
for(i = 0; i < getdtablesize(); i++)
{
    close(i);
}
while(1)
{
    do_something();
}
closelog();
exit(0);

到這里基本上把守護進程的內容全部說清楚了,內容不少,概念比較晦澀,如果希望理解的比較透徹的話,可能需要多看幾遍了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM