前言

常在 linux 下玩耍的開發(fā)者肯定會(huì)經(jīng)常遇到需要對(duì)進(jìn)程調(diào)度的情況,在 windows 中點(diǎn)擊 最小化 去干別的就 OK 了,那么在 linux 下怎么辦呢。

可能有的小伙伴會(huì)說,再開一個(gè)終端窗口不就好了么??墒情_很多窗口管理會(huì)很不方便,還有萬一手賤點(diǎn)了x,或者長(zhǎng)時(shí)間不操作,遠(yuǎn)程終端斷開了連接,進(jìn)程停止了,再次打開,又是一番折騰。

今天來介紹幾個(gè)命令,幫大家系統(tǒng)地梳理一下 linux 的進(jìn)程調(diào)度,并附上一些自己的使用心得和踩過的坑。

名詞

在此之前,我們必須(當(dāng)然也不是必須,但了解原理有利于理解和解決錯(cuò)誤)先弄懂幾個(gè)名詞。

進(jìn)程組

進(jìn)程組是一個(gè)或多個(gè)進(jìn)程的集合,進(jìn)程組方便了對(duì)多個(gè)進(jìn)程的控制,在進(jìn)程數(shù)較多的情況下,向進(jìn)程組發(fā)送信號(hào)就行了。

它的 ID 由它的組長(zhǎng)進(jìn)程的進(jìn)程 ID 決定。組長(zhǎng)進(jìn)程創(chuàng)建了進(jìn)程組,但它并不能決定進(jìn)程組的存活時(shí)間,只要進(jìn)程組內(nèi)還有一個(gè)進(jìn)程存在,進(jìn)程就存在,與組長(zhǎng)進(jìn)程是否已終止無關(guān)。

會(huì)話

會(huì)話(session)是一個(gè)或多個(gè)進(jìn)程組的集合,它開始于用戶登陸終端,結(jié)束于用戶退出登陸。其義如其名,就是指用戶與系統(tǒng)的一次對(duì)話的全程。

會(huì)話包括控制進(jìn)程(與終端建立連接的領(lǐng)頭進(jìn)程),一個(gè)前臺(tái)進(jìn)程組和任意后臺(tái)進(jìn)程組。一個(gè)會(huì)話只能有一個(gè)控制終端,通常是登錄到其上的終端設(shè)備或偽終端設(shè)備,產(chǎn)生在控制終端上的輸入和信號(hào)將發(fā)送給會(huì)話的前臺(tái)進(jìn)程組中的所有進(jìn)程。

控制終端

每當(dāng)我們使用終端工具打開一個(gè)本地或遠(yuǎn)程 shell,我們便打開了一個(gè)控制終端,通過 ps 命令可以查看到 command 為 ttyn 的就是它對(duì)應(yīng)的進(jìn)程了,同時(shí)它對(duì)應(yīng) linux /dev/ 目錄下的一個(gè)文件。

作業(yè)

作業(yè)的概念與進(jìn)程組類似,同樣由一個(gè)或多個(gè)進(jìn)程組成,它分為前臺(tái)作業(yè)和后臺(tái)作業(yè),一個(gè)會(huì)話會(huì)有一個(gè)前臺(tái)作業(yè)和多個(gè)后臺(tái)作業(yè),與進(jìn)程組不同的是,作業(yè)內(nèi)的某個(gè)進(jìn)程產(chǎn)生的子進(jìn)程并不屬于這個(gè)作業(yè)。

類比

以上幾個(gè)概念可以類比為我們一次通過 QQ 聊天的全程,控制終端就是 QQ軟件,關(guān)閉了此軟件代表著聊天結(jié)束。聊天時(shí)發(fā)送的每一條信息都是一個(gè)進(jìn)程,作業(yè)或進(jìn)程組就是我們?cè)诹牡哪骋患?,它由很多條相互的信息構(gòu)成。而會(huì)話則是我們指我們從開始聊天到結(jié)束聊天的全過程,可能會(huì)聊很多個(gè)事。

它們之間的相關(guān)圖如下所示:

萬碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

后臺(tái)執(zhí)行

我們每次在終端窗口執(zhí)行命令的時(shí)候,進(jìn)程總會(huì)一直占用著終端,走到進(jìn)程結(jié)束,這段時(shí)間內(nèi),我們?cè)诮K端的輸入是沒有用的。而且,當(dāng)終端窗口關(guān)閉或網(wǎng)絡(luò)連接失敗后,再次打開終端,會(huì)發(fā)現(xiàn)進(jìn)程已經(jīng)中斷了。這是因?yàn)橛脩糇N或者網(wǎng)絡(luò)斷開時(shí),SIGHUP信號(hào)會(huì)被發(fā)送到會(huì)話所屬的子進(jìn)程,而此 SIGHUP 的默認(rèn)處理方式是終止收到該信號(hào)的進(jìn)程。所以若程序中沒有捕捉該信號(hào),當(dāng)終端關(guān)閉后,會(huì)話所屬進(jìn)程就會(huì)退出。

我們要實(shí)現(xiàn)后臺(tái)執(zhí)行的目的,實(shí)際上是要完成如下兩個(gè)目標(biāo):

  • 使進(jìn)程讓出前臺(tái)終端,讓我們可以繼續(xù)通過終端與系統(tǒng)進(jìn)行交互。

  • 使進(jìn)程不再受終端關(guān)閉的影響,即系統(tǒng)在終端關(guān)閉后不再向進(jìn)程發(fā)送 SIGHUP 信號(hào)或即使發(fā)送了信號(hào)程序也不會(huì)退出。

以下的命令就圍繞著這兩個(gè)目標(biāo)來實(shí)現(xiàn)。

&

首先是我們最經(jīng)常遇到的符號(hào) &,將它附在命令后面可以使進(jìn)程在后臺(tái)執(zhí)行,不會(huì)占用前臺(tái)界面。它實(shí)際上是在會(huì)話中開啟了一個(gè)后臺(tái)作業(yè),對(duì)作業(yè)的操作我們后面再說。

但我們會(huì)發(fā)現(xiàn),如果此時(shí)終端被關(guān)閉后,進(jìn)程還是會(huì)退出。這是因?yàn)椋?code style="margin: 0px; padding: 1px 3px; font-family: Monaco, "Andale Mono", "Courier New", monospace; background-color: rgb(254, 233, 204); color: rgba(0, 0, 0, 0.75);">& 符號(hào)只有讓進(jìn)程讓出前臺(tái)終端的功能,無法讓進(jìn)程不受 SIGHUP 信號(hào)的影響。

nohup

nohup 應(yīng)該是另外一個(gè)我們常用的命令了,它的作用如其字面意思,使進(jìn)程不受 SIGHUP 信號(hào)的影響。但我們?cè)谑褂?nbsp;nohup php test.php 后會(huì)發(fā)現(xiàn),進(jìn)程還會(huì)一直占用前臺(tái)終端,但即使終端被關(guān)閉或連接斷開了,程序還是會(huì)執(zhí)行,另外我們會(huì)發(fā)現(xiàn)在當(dāng)前文件夾下多了個(gè)名為 nohup.out 的文件。

這是因?yàn)?nohup 的功能僅僅是讓進(jìn)程不受 SIGHUP 信號(hào)的影響,并不會(huì)讓出前臺(tái)終端,而且它還會(huì)在命令執(zhí)行目錄下建立 nohup.out用以存儲(chǔ)進(jìn)程的輸出。如果進(jìn)程不需要輸出,且不想讓 nohup 創(chuàng)建文件,可以將標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出重定向。

我們常將 nohup 和 & 搭配到一塊使用,執(zhí)行命令如下 nohup command >/dev/null 2>&1 & 這樣,就可以放心的等待進(jìn)程運(yùn)行結(jié)果了。

setsid

setsid 是另一個(gè)讓進(jìn)程在后臺(tái)執(zhí)行的命令,它的作用是讓進(jìn)程打開一個(gè)新的會(huì)話并運(yùn)行進(jìn)程,使用方式為 setsid command。

根據(jù)上面的概念我們得知終端關(guān)閉后進(jìn)程退出是因?yàn)闀?huì)話首進(jìn)程向進(jìn)程發(fā)送了 SIGHUP 信號(hào),setsid 就厲害了,它直接打開一個(gè)新的會(huì)話來執(zhí)行命令,那么原會(huì)話的終端的狀態(tài)就再也不會(huì)影響到此進(jìn)程了。

我們使用 pstree 來查看使用 setsid 和 nohup ... & 兩種命令來運(yùn)行進(jìn)程時(shí)的進(jìn)程樹狀態(tài)。

  • 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 遠(yuǎn)程登陸的機(jī)器,所以 test.php 進(jìn)程是掛在 sshd 進(jìn)程下的。正常情況下,一旦 sshd 進(jìn)程結(jié)束,則 test.php也無法幸免。

  • setsid php test.php

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

使用了 setsid 后,test.php 進(jìn)程已經(jīng)與 sshd 進(jìn)程同級(jí),屬于 init 進(jìn)程的子進(jìn)程了。

但是 setsid 并沒有為進(jìn)程分配一個(gè)輸出終端,所以進(jìn)程還是會(huì)輸出到當(dāng)前終端上。

setsid的坑

另外,setsid 有個(gè)略坑的地方: 在終端中直接使用 setsid command 運(yùn)行進(jìn)程時(shí),終端前臺(tái)并不會(huì)被影響,command 會(huì)在后臺(tái)默默運(yùn)行。而在 shell 腳本中,我們會(huì)發(fā)現(xiàn)運(yùn)行 setsid 的進(jìn)程會(huì)一直阻塞住,直到 command 進(jìn)程執(zhí)行結(jié)束。

這是因?yàn)?,setsid 在其是進(jìn)程組長(zhǎng)時(shí)會(huì) fork() 一個(gè)進(jìn)程,但它不會(huì) wait() 它的子進(jìn)程,而是立刻退出,所以在終端內(nèi)直接使用 setsid 時(shí),setsid 作為進(jìn)程組長(zhǎng)不會(huì)占用終端界面。

而在 shell 腳本內(nèi),setsid 不是進(jìn)程組長(zhǎng),它不會(huì) fork() 子進(jìn)程,而是由 bash 來fork() 一個(gè)子進(jìn)程,而 bash 會(huì) wait() 子進(jìn)程,所以表現(xiàn)得像 setsid 在 wait() 子進(jìn)程一樣。

要解決這個(gè)問題,有兩個(gè)辦法:

  • 使用上面介紹的 &符號(hào),使 setsid 強(qiáng)行到后臺(tái)執(zhí)行。

  • 使用 . 或 source 命令由終端執(zhí)行 setsid;

其他

除了上面介紹的命令,還有 screen 和 tmux 等會(huì)話工具,他們都有自己的一套規(guī)范,也比較復(fù)雜,掌握本文的命令已經(jīng)足夠你馳騁 linux 進(jìn)程控制了。當(dāng)然有想了解新知識(shí)的可以查詢學(xué)習(xí)一下,應(yīng)該會(huì)比基礎(chǔ)命令好用。

作業(yè)命令

使用上面的后臺(tái)執(zhí)行命令時(shí)可能還會(huì)遇到一些小狀況:

  • 被我們放在后臺(tái)的進(jìn)程執(zhí)行時(shí)間過長(zhǎng),而我們又忘記使用 nohup 命令,那么終端一旦斷開,進(jìn)程又需要被重新執(zhí)行。

  • 我們直接開啟了某個(gè)進(jìn)程,又想在不中斷進(jìn)程的情況下讓它讓出前臺(tái)終端;

這些都要牽涉到今天的第二個(gè)模塊--作業(yè);

我們?cè)诮K端里運(yùn)行的命令都可以理解為一個(gè)作業(yè),有的占用前臺(tái)終端,有的在后臺(tái)默默執(zhí)行,下面的命令就是為了調(diào)度這些作業(yè)。

jobs

jobs 是作業(yè)的基礎(chǔ)命令,用它可以查看正在運(yùn)行的作業(yè)的信息,其輸出如下:

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

前面[ ]內(nèi)的數(shù)字是作業(yè) ID,也是后面我們要操作作業(yè)的標(biāo)識(shí),然后是作業(yè)狀態(tài)和命令。

ctrl+z

ctrl+z 嚴(yán)格來說并是作業(yè)命令,它只是向當(dāng)前進(jìn)程發(fā)送一個(gè) SIGSTOP 信號(hào),促使進(jìn)程進(jìn)入暫停(stopped)狀態(tài),此狀態(tài)下,進(jìn)程狀態(tài)會(huì)被系統(tǒng)保存,此進(jìn)程會(huì)被放置到作業(yè)隊(duì)列中去,而讓出進(jìn)程終端。

使用它,我們可以暫停正在占用終端的進(jìn)程而不停止它,從而讓我們使用終端命令來操作此進(jìn)程。

bg

bg是 backgroud 的縮寫,顧名思義,bg %id 把作業(yè)放到后臺(tái)進(jìn)程中執(zhí)行。

結(jié)合 ctrl+z 和 bg 命令,我們可以解決上面提出的第一個(gè)問題,不停止地將正在占用終端的進(jìn)程放到后臺(tái)執(zhí)行。

fg

fg 與 bg 相對(duì),使用它可以把作業(yè)放到前臺(tái)來執(zhí)行。

disown

disown 用來將作業(yè)從作業(yè)列表中移除,即使它 不屬于 會(huì)話,這樣終端關(guān)閉后不再向此作業(yè)發(fā)送 SIGHUP 信號(hào),以阻止終端對(duì)進(jìn)程的影響。

使用 disown 我們可以解決上面提出的第二個(gè)問題,不重新執(zhí)行將一個(gè)沒使用 nohup 命令的進(jìn)程不受終端關(guān)閉影響。

守護(hù)進(jìn)程

以上介紹的都是一些臨時(shí)進(jìn)程的處理,后臺(tái)運(yùn)行的進(jìn)程的最終方法是將進(jìn)程變成守護(hù)進(jìn)程。

守護(hù)進(jìn)程

守護(hù)進(jìn)程(daemon)是生存期較長(zhǎng)的一種進(jìn)程,一般在系統(tǒng)啟動(dòng)時(shí)啟動(dòng),系統(tǒng)關(guān)閉時(shí)停止,沒有控制終端,也不會(huì)輸出。如我們的服務(wù)器、fpm 等進(jìn)程就是以守護(hù)進(jìn)程的形式存在的。

創(chuàng)建過程

要?jiǎng)?chuàng)建一個(gè)守護(hù)進(jìn)程,步驟為:

必選項(xiàng)

  1. fork 子進(jìn)程,退出父進(jìn)程,子進(jìn)程作為孤兒進(jìn)程被 init 進(jìn)程收養(yǎng);

  2. 使用 setsid, 打開新會(huì)話,進(jìn)程成為會(huì)話組長(zhǎng),正式脫離終端控制;

  3. 設(shè)置信號(hào)處理(特別是子進(jìn)程退出處理);

    可選項(xiàng):

  4. 使用 chdir 改變進(jìn)程工作目錄,一般到根目錄下,防止占用可卸載文件系統(tǒng);

  5. 用 umask 重設(shè)文件權(quán)限掩碼,不再繼承父進(jìn)程的文件權(quán)限設(shè)置;

  6. 關(guān)閉父進(jìn)程打開的文件描述符;

代碼

以下是 php 創(chuàng)建守護(hù)進(jìn)程的偽代碼,另外我的另一篇博客 初探PHP多進(jìn)程 也稍微介紹了一些相關(guān)內(nèi)容:

$pid = pcntl_fork();
if ($pid > 0) {
    exit; // 父進(jìn)程直接退出
} elseif ($pid < 0) {
    throw_error(); // 進(jìn)程創(chuàng)建失敗
}

posix_setsid(); // setsid成為會(huì)話領(lǐng)導(dǎo)進(jìn)程
chdir($dir); // 切換目錄
umask(0); // 重置文件權(quán)限mask
close_fd(); // 關(guān)閉父進(jìn)程的文件描述符
pcntl_signal($signal, $func); // 注冊(cè)信號(hào)處理函數(shù)

while (true) {
    do_job(); // 處理進(jìn)程任務(wù)
    pcntl_signal_dispatch(); // 分發(fā)信號(hào)處理
}

總結(jié)

linux 是開發(fā)者的基礎(chǔ)技能,而進(jìn)程的調(diào)度更是我們常用的功能,希望讀完本文的同學(xué)們能有所收獲。

又有大半個(gè)月沒發(fā)博客了,最近鼓搗著重構(gòu)代碼,經(jīng)常會(huì)在一個(gè)點(diǎn)上糾結(jié)半天,不知不覺就加了個(gè)班。而且這個(gè)是個(gè)沒法精確度量工作量和目標(biāo)的活兒,優(yōu)化沒有盡頭嘛。不過由于要更多地考慮一下代碼的抽象、效率和擴(kuò)展,對(duì)自己也是個(gè)挑戰(zhàn),算是樂在其中吧~

最近可能會(huì)考慮寫一個(gè)守護(hù)進(jìn)程和 cron 進(jìn)程調(diào)度器,嗯,希望給我算到工作量里,哈哈~想寫的太多了,只怨自己還不夠強(qiáng)大。。。

如果您覺得本文對(duì)您有幫助,可以點(diǎn)擊下面的 推薦 支持一下我。博客一直在更新,歡迎 關(guān)注 。

參考:

http://www.cnblogs.com/zhenbianshu/p/7152327.html