Linux裏養殭屍是怎麼回事呢?Linux相信你們都很熟悉,可是Linux裏養殭屍是怎麼回事呢,下面就讓小編帶你們一塊兒瞭解吧。python
上一篇挖了個 SIGHUP 的坑,這篇試着填一下。程序員
以前在《程序員面試指北:面試官視角》裏面說過,在結構化面試中,咱們會從各個方向去考查候選人,其中之一是操做系統。面試
上篇介紹了一套題,我還有另外一套,通常這麼開場:shell
在終端下啓動一個命令,若是在命令結束前關掉終端,它還能正常運行嗎?編程
這實際上是一個很常見的case,但凡 Linux 或者 Mac 用得多一點,都會遇到。bash
在我仍是一個窮酸學生的2009年,每月都須要支付 20 元鉅款(當時能買3根鴨脖),經過一個禁止分享網絡的認證客戶端接入校園網。服務器
爲了共建和諧宿舍 節省網費 ,我歷經千辛萬苦,交叉編譯開源的Linux認證客戶端,集成到固件裏,並刷到了個人 NETGEAR 路由器上。網絡
而後山水 BBS 的 Linux 版主把個人帖子置頂了 11 年。可見他有多痛恨禁止共享網絡app
這麼一回憶,感受本身的共享經濟思惟真是前衛,當時怎麼就沒想到去搞共享單車呢?ssh
扯遠了,在搗騰的過程當中,我就踩了這麼個坑:當我ssh到路由器上、剛啓動認證時,可以正常聯網;可是退出ssh後一會,網就斷了。
通過一番搗騰後發現,只要一退出ssh,認證程序就涼了,而不是繼續在後臺保持和認證服務器的通訊。
因此前面那個問題,我覺得大部分候選人應該會回答「否」,但沒想到居然還有很多人回答「是」。
其實回答「是」也沒什麼錯,由於確實也有些命令不會隨着終端關閉而結束。
問題是當我追問當時執行的是什麼命令時,候選人每每又說不出個因此然來。
(借學長的表情一用)
而後我就感到很強的挫敗感:這不按劇原本,無法問了啊……只好換題。
固然大部分候選人確實被坑過,因而我能夠接着問:
若是確實須要在後臺繼續執行命令怎麼辦呢?
有些人只記得要在後面加個 & ;但也有很多人知道前面還得加個 nohup,就像這樣:
$ nohup python process.py & [1] 1806824 nohup: ignoring input and appending output to 'nohup.out'
注:其實我更喜歡 screen(或 tmux),偶爾也用 setsid 。
而後就能夠放心地關閉終端 開始放羊 了。
但個人套題還沒結束:爲何加上 nohup 就可讓進程在後臺繼續運行呢?
(這表情熟悉嗎)
鋪墊了這麼多,總算是能夠開始填坑了。
答案其實很好找,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)。
(我總以爲這個圖是假的)
到了遠古時代,他們再也不須要去機房,經過基於 RS-232 協議的串行線路鏈接到大型機的終端上,就能夠開始收福報。
收完福報,程序員通知本身的貓(modem)掛斷(Hang Up)鏈接;大型機的 OS 檢測到鏈接斷開,就會給進程發送信號 —— 因此這信號被稱爲 SIGHUP 。
這果真是毫無卵用的知識啊。
不少同窗在操做系統的課程上學習了「進程間的通訊方式有信號、管道、消息隊列、共享內存……」,可是對信號究竟是個什麼東西,並無現實的概念。
課堂教學的理論和實踐每每是割裂的,在此特別推薦《Unix環境高級編程》(簡稱APUE)。
APUE在 1.9 - 信號 中寫到:信號是通知進程已發生某種條件的一種技術。
而在 Linux/Unix 下,進程對信號的處理有三種選擇:
以 SIGHUP 信號爲例,系統默認處理方式就是結束進程。
固然終端下打開的第一個進程一般都是shell(例如bash)。shell會給 SIGHUP 信號註冊一個回調函數,用於給該 shell 下全部的子進程發送 SIGHUP 信號,而後再主動退出。
對於求生欲很強的程序(例如nohup),能夠主動選擇忽略該信號。
有一些進程原本就被設計成在後臺運行,不須要控制終端,所以它們將 SIGHUP 挪做它用,一個常見的用法就是從新讀取配置文件(例如Apache、Nginx),上篇提到的 logrotate 正是利用了這一點。
終於填完了坑。
說了這麼多都仍是紙上談兵,實操中如何主動忽略 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,再按需執行相應任務。
SIGHUP 只是常見的一個信號,在 Linux 下,信號還有大量其餘的場景和應用。
當你按下 Ctrl + C ,就是給進程發送了一個 SIGINT 信號。
當你執行 kill -TERM $PID,就是給進程發送了一個 SIGTERM 信號。可能和你指望有出入的是,SIGTERM 是能夠被進程忽略的。因此有時候你得用 SIGKILL (kill -9) 。
你還可使用可自定義的 SIGUSR一、SIGUSR二、SIGURG 來實現一些功能,好比《踩坑記#2:Go服務鎖死》中提到 Golang 在其 goroutine 調度中使用了 SIGURG 。
此次就不總結了,最後再用一個和信號有關的 case 收尾。
Linux 內核會爲每個進程分配一個 task_struct 結構體,用於保存進程的相關信息。
在進程死亡後,系統會發送一個 SIGCHLD 信號給它的父進程。
正確的父進程實現,一般應當使用 wait 系統調用來給子進程收屍 —— 父進程每每須要知道子進程結束這個事件,並且可能還須要得知其退出緣由(exit code)。
而後內核纔會將對應的 task_struct 釋放。
若是父進程沒有收屍,task_struct 裏的 state 會一直保持爲 EXIT_ZOMBIE,這時在 ps 或 top 等命令裏,就能夠看到該進程的狀態爲 Z ,並且沒法被 kill 。
這就是所謂的殭屍進程,這時候你找九叔都沒用。
(大半夜找這圖還挺滲人的)
因此Linux裏養殭屍,其實就是子進程死了父進程不收屍,你們可能會很驚訝Linux裏怎麼會養殭屍呢?但事實就是這樣,小編也感到很是驚訝。
這就是關於Linux裏養殭屍的事情了,你們有什麼想法呢,歡迎在評論區告訴小編一塊兒討論哦!
推薦閱讀