生人勿近之 Linux 裏養殭屍

zoombie-banner.jpg

Linux裏養殭屍是怎麼回事呢?Linux相信你們都很熟悉,可是Linux裏養殭屍是怎麼回事呢,下面就讓小編帶你們一塊兒瞭解吧。python

- 1 -

上一篇挖了個 SIGHUP 的坑,這篇試着填一下。程序員

以前在《程序員面試指北:面試官視角》裏面說過,在結構化面試中,咱們會從各個方向去考查候選人,其中之一是操做系統。面試

上篇介紹了一套題,我還有另外一套,通常這麼開場:shell

在終端下啓動一個命令,若是在命令結束前關掉終端,它還能正常運行嗎?編程

又是一道送分題.jpeg

- 2 -

這實際上是一個很常見的case,但凡 Linux 或者 Mac 用得多一點,都會遇到。bash

在我仍是一個窮酸學生的2009年,每月都須要支付 20 元鉅款(當時能買3根鴨脖),經過一個禁止分享網絡的認證客戶端接入校園網。服務器

爲了共建和諧宿舍 節省網費 ,我歷經千辛萬苦,交叉編譯開源的Linux認證客戶端,集成到固件裏,並刷到了個人 NETGEAR 路由器上。網絡

而後山水 BBS 的 Linux 版主把個人帖子置頂了 11 年。可見他有多痛恨禁止共享網絡app

這麼一回憶,感受本身的共享經濟思惟真是前衛,當時怎麼就沒想到去搞共享單車呢?ssh

ofo押金.jpeg

扯遠了,在搗騰的過程當中,我就踩了這麼個坑:當我ssh到路由器上、剛啓動認證時,可以正常聯網;可是退出ssh後一會,網就斷了。

通過一番搗騰後發現,只要一退出ssh,認證程序就涼了,而不是繼續在後臺保持和認證服務器的通訊。

- 3 -

因此前面那個問題,我覺得大部分候選人應該會回答「否」,但沒想到居然還有很多人回答「是」。

其實回答「是」也沒什麼錯,由於確實也有些命令不會隨着終端關閉而結束。

問題是當我追問當時執行的是什麼命令時,候選人每每又說不出個因此然來。

are-you-ok.jpeg

(借學長的表情一用)

而後我就感到很強的挫敗感:這不按劇原本,無法問了啊……只好換題。

固然大部分候選人確實被坑過,因而我能夠接着問:

若是確實須要在後臺繼續執行命令怎麼辦呢?

有些人只記得要在後面加個 & ;但也有很多人知道前面還得加個 nohup,就像這樣:

$ nohup python process.py &
[1] 1806824
nohup: ignoring input and appending output to 'nohup.out'

注:其實我更喜歡 screen(或 tmux),偶爾也用 setsid 。

而後就能夠放心地關閉終端 開始放羊 了。

但個人套題還沒結束:爲何加上 nohup 就可讓進程在後臺繼續運行呢?

王寶強.png

(這表情熟悉嗎)

- 4 -

鋪墊了這麼多,總算是能夠開始填坑了。

答案其實很好找,man nohup 就能看到:

The nohup utility invokes utility with its arguments and at this time sets the signal SIGHUP to be ignored

nohup工具在啓動命令的同時會將 SIGHUP 信號設置爲忽略。
而關於 SIGHUP,Wikipedia原文是這樣介紹的:

On POSIX-compliant platforms, SIGHUP ("signal hang up") is a signal sent to a process when its controlling terminal is closed.

wikipedia.org/wiki/SIGHUP

對於 POSIX 兼容的平臺(如Unix、Linux、BSD、Mac),當進程所在的控制終端關閉時,系統會給進程發送 SIGHUP 信號(Signal Hang Up,掛斷信號)。

爲何叫 SIGHUP 呢?(嚴正申明:這一問不在套題裏[doge])

咱們知道,在上古時代,捉 bug 就已是碼農的必備技能(更準確地說是 moth)。

first-actual-case-of-bug.jpeg

(我總以爲這個圖是假的)

到了遠古時代,他們再也不須要去機房,經過基於 RS-232 協議的串行線路鏈接到大型機的終端上,就能夠開始收福報。

收完福報,程序員通知本身的貓(modem)掛斷(Hang Up)鏈接;大型機的 OS 檢測到鏈接斷開,就會給進程發送信號 —— 因此這信號被稱爲 SIGHUP 。

這果真是毫無卵用的知識啊。

然並卵.jpg

- 5 -

不少同窗在操做系統的課程上學習了「進程間的通訊方式有信號、管道、消息隊列、共享內存……」,可是對信號究竟是個什麼東西,並無現實的概念。

課堂教學的理論和實踐每每是割裂的,在此特別推薦《Unix環境高級編程》(簡稱APUE)。

APUE在 1.9 - 信號 中寫到:信號是通知進程已發生某種條件的一種技術。

而在 Linux/Unix 下,進程對信號的處理有三種選擇:

  • 按系統默認方式處理
  • 提供一個回調函數
  • 或忽略該信號(有些信號例外,不容許被忽略)

以 SIGHUP 信號爲例,系統默認處理方式就是結束進程

固然終端下打開的第一個進程一般都是shell(例如bash)。shell會給 SIGHUP 信號註冊一個回調函數,用於給該 shell 下全部的子進程發送 SIGHUP 信號,而後再主動退出。

對於求生欲很強的程序(例如nohup),能夠主動選擇忽略該信號

sighup搶救.png

有一些進程原本就被設計成在後臺運行,不須要控制終端,所以它們將 SIGHUP 挪做它用,一個常見的用法就是從新讀取配置文件(例如Apache、Nginx),上篇提到的 logrotate 正是利用了這一點。

終於填完了坑。

- 6 -

說了這麼多都仍是紙上談兵,實操中如何主動忽略 SIGHUP 呢?

實際上也很簡單,使用 Linux 的 signal 系統調用便可:

#include <signal.h>
#include <unistd.h>

int main() {
    signal(SIGHUP, SIG_IGN);
    sleep(1000);
    return 0;
}

不妨試試看,編譯運行起來,即便關閉終端,它也會在後臺繼續運行。

signal 也能夠用於指定回調函數(或重置爲系統默認處理方式),這裏就不展開了,感興趣的同窗能夠參考 APUE 裏的代碼,以及閱讀 signal 的manual。

使用回調函數還須要注意一個坑:

因爲回調函數可能在任意時刻被觸發,所以要避免調用不可重入的函數(典型如printf)。常見的作法是 set 一個 flag,而後在程序的主循環中檢測該 flag,再按需執行相應任務。

- 7 -

SIGHUP 只是常見的一個信號,在 Linux 下,信號還有大量其餘的場景和應用。

當你按下 Ctrl + C ,就是給進程發送了一個 SIGINT 信號。

當你執行 kill -TERM $PID,就是給進程發送了一個 SIGTERM 信號。可能和你指望有出入的是,SIGTERM 是能夠被進程忽略的。因此有時候你得用 SIGKILL (kill -9) 。

你還可使用可自定義的 SIGUSR一、SIGUSR二、SIGURG 來實現一些功能,好比《踩坑記#2:Go服務鎖死》中提到 Golang 在其 goroutine 調度中使用了 SIGURG 。

- 8 -

此次就不總結了,最後再用一個和信號有關的 case 收尾。

Linux 內核會爲每個進程分配一個 task_struct 結構體,用於保存進程的相關信息。

在進程死亡後,系統會發送一個 SIGCHLD 信號給它的父進程。

正確的父進程實現,一般應當使用 wait 系統調用來給子進程收屍 —— 父進程每每須要知道子進程結束這個事件,並且可能還須要得知其退出緣由(exit code)。

而後內核纔會將對應的 task_struct 釋放。

若是父進程沒有收屍,task_struct 裏的 state 會一直保持爲 EXIT_ZOMBIE,這時在 ps 或 top 等命令裏,就能夠看到該進程的狀態爲 Z ,並且沒法被 kill 。

這就是所謂的殭屍進程,這時候你找九叔都沒用。

林正英.jpeg

(大半夜找這圖還挺滲人的)

因此Linux裏養殭屍,其實就是子進程死了父進程不收屍,你們可能會很驚訝Linux裏怎麼會養殭屍呢?但事實就是這樣,小編也感到很是驚訝。

這就是關於Linux裏養殭屍的事情了,你們有什麼想法呢,歡迎在評論區告訴小編一塊兒討論哦!


推薦閱讀

歡迎關注

weixin1.png

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息