Linux - 請容許我靜靜地後臺運行

 

前言

常在 linux 下玩耍的開發者確定會常常遇到須要對進程調度的狀況,在 windows 中點擊 最小化 去幹別的就 OK 了,那麼在 linux 下怎麼辦呢。php

可能有的小夥伴會說,再開一個終端窗口不就行了麼。但是開不少窗口管理會很不方便,還有萬一手賤點了x,或者長時間不操做,遠程終端斷開了鏈接,進程中止了,再次打開,又是一番折騰。css

今天來介紹幾個命令,幫你們系統地梳理一下 linux 的進程調度,並附上一些本身的使用心得和踩過的坑。html

名詞

在此以前,咱們必須(固然也不是必須,但瞭解原理有利於理解和解決錯誤)先弄懂幾個名詞。linux

進程組

進程組是一個或多個進程的集合,進程組方便了對多個進程的控制,在進程數較多的狀況下,向進程組發送信號就好了。git

它的 ID 由它的組長進程的進程 ID 決定。組長進程建立了進程組,但它並不能決定進程組的存活時間,只要進程組內還有一個進程存在,進程就存在,與組長進程是否已終止無關。github

會話

會話(session)是一個或多個進程組的集合,它開始於用戶登錄終端,結束於用戶退出登錄。其義如其名,就是指用戶與系統的一次對話的全程。web

會話包括控制進程(與終端創建鏈接的領頭進程),一個前臺進程組和任意後臺進程組。一個會話只能有一個控制終端,一般是登陸到其上的終端設備或僞終端設備,產生在控制終端上的輸入和信號將發送給會話的前臺進程組中的全部進程。shell

控制終端

每當咱們使用終端工具打開一個本地或遠程 shell,咱們便打開了一個控制終端,經過 ps 命令能夠查看到 command 爲 ttyn 的就是它對應的進程了,同時它對應 linux /dev/ 目錄下的一個文件。windows

做業

做業的概念與進程組相似,一樣由一個或多個進程組成,它分爲前臺做業和後臺做業,一個會話會有一個前臺做業和多個後臺做業,與進程組不一樣的是,做業內的某個進程產生的子進程並不屬於這個做業。bash

類比

以上幾個概念能夠類比爲咱們一次經過 QQ 聊天的全程,控制終端就是 QQ軟件,關閉了此軟件表明着聊天結束。聊天時發送的每一條信息都是一個進程,做業或進程組就是咱們在聊的某一件事,它由不少條相互的信息構成。而會話則是咱們指咱們從開始聊天到結束聊天的全過程,可能會聊不少個事。

它們之間的相關圖以下所示:

後臺執行

咱們每次在終端窗口執行命令的時候,進程總會一直佔用着終端,走到進程結束,這段時間內,咱們在終端的輸入是沒有用的。並且,當終端窗口關閉或網絡鏈接失敗後,再次打開終端,會發現進程已經中斷了。這是由於用戶註銷或者網絡斷開時,SIGHUP信號會被髮送到會話所屬的子進程,而此 SIGHUP 的默認處理方式是終止收到該信號的進程。因此若程序中沒有捕捉該信號,當終端關閉後,會話所屬進程就會退出。

咱們要實現後臺執行的目的,其實是要完成以下兩個目標:

  • 使進程讓出前臺終端,讓咱們能夠繼續經過終端與系統進行交互。
  • 使進程再也不受終端關閉的影響,即系統在終端關閉後再也不向進程發送 SIGHUP 信號或即便發送了信號程序也不會退出。

如下的命令就圍繞着這兩個目標來實現。

&

首先是咱們最常常遇到的符號 &,將它附在命令後面可使進程在後臺執行,不會佔用前臺界面。它其實是在會話中開啓了一個後臺做業,對做業的操做咱們後面再說。

但咱們會發現,若是此時終端被關閉後,進程仍是會退出。這是由於,& 符號只有讓進程讓出前臺終端的功能,沒法讓進程不受 SIGHUP 信號的影響。

nohup

nohup 應該是另一個咱們經常使用的命令了,它的做用如其字面意思,使進程不受 SIGHUP 信號的影響。但咱們在使用 nohup php test.php 後會發現,進程還會一直佔用前臺終端,但即便終端被關閉或鏈接斷開了,程序仍是會執行,另外咱們會發如今當前文件夾下多了個名爲 nohup.out 的文件。

這是由於 nohup 的功能僅僅是讓進程不受 SIGHUP 信號的影響,並不會讓出前臺終端,並且它還會在命令執行目錄下創建 nohup.out 用以存儲進程的輸出。若是進程不須要輸出,且不想讓 nohup 建立文件,能夠將標準輸出和標準錯誤輸出重定向。

咱們常將 nohup& 搭配到一塊使用,執行命令以下 nohup command >/dev/null 2>&1 & 這樣,就能夠放心的等待進程運行結果了。

setsid

setsid 是另外一個讓進程在後臺執行的命令,它的做用是讓進程打開一個新的會話並運行進程,使用方式爲 setsid command

根據上面的概念咱們得知終端關閉後進程退出是由於會話首進程向進程發送了 SIGHUP 信號,setsid 就厲害了,它直接打開一個新的會話來執行命令,那麼原會話的終端的狀態就不再會影響到此進程了。

咱們使用 pstree 來查看使用 setsidnohup ... & 兩種命令來運行進程時的進程樹狀態。

  • nohup php test.php &
pstree -a |grep -C 6 test
  |-sshd
  |   `-sshd
  |       `-sshd
  |           `-bash
  |               `-sudo -s
  |                   `-bash
  |                       |-grep -C 6 test
  |                       |-php test.php
  |                       `-pstree -a

我是用 ssh 遠程登錄的機器,因此 test.php 進程是掛在 sshd 進程下的。正常狀況下,一旦 sshd 進程結束,則 test.php也沒法倖免。

  • setsid php test.php
pstree -a |grep -C 6 test 
  |-{nscd}
  |-php test.php
  |-php-fpm
 --
  |-sshd
  |   `-sshd

使用了 setsid 後,test.php 進程已經與 sshd 進程同級,屬於 init 進程的子進程了。

可是 setsid 並無爲進程分配一個輸出終端,因此進程仍是會輸出到當前終端上。

setsid的坑

另外,setsid 有個略坑的地方: 在終端中直接使用 setsid command 運行進程時,終端前臺並不會被影響,command 會在後臺默默運行。而在 shell 腳本中,咱們會發現運行 setsid 的進程會一直阻塞住,直到 command 進程執行結束。

這是由於,setsid 在其是進程組長時會 fork() 一個進程,但它不會 wait() 它的子進程,而是馬上退出,因此在終端內直接使用 setsid 時,setsid 做爲進程組長不會佔用終端界面。

而在 shell 腳本內,setsid 不是進程組長,它不會 fork() 子進程,而是由 bash 來fork() 一個子進程,而 bash 會 wait() 子進程,因此表現得像 setsid 在 wait() 子進程同樣。

要解決這個問題,有兩個辦法:

  • 使用上面介紹的 &符號,使 setsid 強行到後臺執行。
  • 使用 .source 命令由終端執行 setsid;

其餘

除了上面介紹的命令,還有 screen 和 tmux 等會話工具,他們都有本身的一套規範,也比較複雜,掌握本文的命令已經足夠你馳騁 linux 進程控制了。固然有想了解新知識的能夠查詢學習一下,應該會比基礎命令好用。

做業命令

使用上面的後臺執行命令時可能還會遇到一些小情況:

  • 被咱們放在後臺的進程執行時間過長,而咱們又忘記使用 nohup 命令,那麼終端一旦斷開,進程又須要被從新執行。
  • 咱們直接開啓了某個進程,又想在不中斷進程的狀況下讓它讓出前臺終端;

這些都要牽涉到今天的第二個模塊--做業;

咱們在終端裏運行的命令均可以理解爲一個做業,有的佔用前臺終端,有的在後臺默默執行,下面的命令就是爲了調度這些做業。

jobs

jobs 是做業的基礎命令,用它能夠查看正在運行的做業的信息,其輸出以下:

jobs
[1]-  Running                 php test.php &
[2]+  Stopped                 php test.php

前面[ ]內的數字是做業 ID,也是後面咱們要操做做業的標識,而後是做業狀態和命令。

ctrl+z

ctrl+z 嚴格來講並是做業命令,它只是向當前進程發送一個 SIGSTOP 信號,促使進程進入暫停(stopped)狀態,此狀態下,進程狀態會被系統保存,此進程會被放置到做業隊列中去,而讓出進程終端。

使用它,咱們能夠暫停正在佔用終端的進程而不中止它,從而讓咱們使用終端命令來操做此進程。

bg

bg是 backgroud 的縮寫,顧名思義,bg %id 把做業放到後臺進程中執行。

結合 ctrl+zbg 命令,咱們能夠解決上面提出的第一個問題,不中止地將正在佔用終端的進程放到後臺執行。

fg

fg 與 bg 相對,使用它能夠把做業放到前臺來執行。

disown

disown 用來將做業從做業列表中移除,即便它 不屬於 會話,這樣終端關閉後再也不向此做業發送 SIGHUP 信號,以阻止終端對進程的影響。

使用 disown 咱們能夠解決上面提出的第二個問題,不從新執行將一個沒使用 nohup 命令的進程不受終端關閉影響。

守護進程

以上介紹的都是一些臨時進程的處理,後臺運行的進程的最終方法是將進程變成守護進程。

守護進程

守護進程(daemon)是生存期較長的一種進程,通常在系統啓動時啓動,系統關閉時中止,沒有控制終端,也不會輸出。如咱們的服務器、fpm 等進程就是以守護進程的形式存在的。

建立過程

要建立一個守護進程,步驟爲:

必選項

  1. fork 子進程,退出父進程,子進程做爲孤兒進程被 init 進程收養;
  2. 使用 setsid, 打開新會話,進程成爲會話組長,正式脫離終端控制;
  3. 設置信號處理(特別是子進程退出處理);

    可選項:

  4. 使用 chdir 改變進程工做目錄,通常到根目錄下,防止佔用可卸載文件系統;

  5. umask 重設文件權限掩碼,再也不繼承父進程的文件權限設置;

  6. 關閉父進程打開的文件描述符;

代碼

如下是 php 建立守護進程的僞代碼,另外個人另外一篇博客 初探PHP多進程 也稍微介紹了一些相關內容:

$pid = pcntl_fork();
if ($pid > 0) {
    exit; // 父進程直接退出
} elseif ($pid < 0) {
    throw_error(); // 進程建立失敗
}

posix_setsid(); // setsid成爲會話領導進程
chdir($dir); // 切換目錄
umask(0); // 重置文件權限mask
close_fd(); // 關閉父進程的文件描述符
pcntl_signal($signal, $func); // 註冊信號處理函數

while (true) {
    do_job(); // 處理進程任務
    pcntl_signal_dispatch(); // 分發信號處理
}

總結

linux 是開發者的基礎技能,而進程的調度更是咱們經常使用的功能,但願讀完本文的同窗們能有所收穫。

又有大半個月沒發博客了,最近鼓搗着重構代碼,常常會在一個點上糾結半天,不知不覺就加了個班。並且這個是個無法精確度量工做量和目標的活兒,優化沒有盡頭嘛。不過因爲要更多地考慮一下代碼的抽象、效率和擴展,對本身也是個挑戰,算是樂在其中吧~

最近可能會考慮寫一個守護進程和 cron 進程調度器,嗯,但願給我算到工做量裏,哈哈~想寫的太多了,只怨本身還不夠強大。。。

若是您以爲本文對您有幫助,能夠點擊下面的 推薦 支持一下我。博客一直在更新,歡迎 關注

參考:

setsid爲何會在腳本中阻塞-StackoOerflow

Linux 進程、進程組、會話週期、控制終端

相關文章
相關標籤/搜索