linux结束进程_生人勿近之Linux里养僵尸
Linux里養僵尸是怎么回事呢?Linux相信大家都很熟悉,但是Linux里養僵尸是怎么回事呢,下面就讓小編帶大家一起了解吧。
- 1?-
上一篇挖了個 SIGHUP 的坑,這篇試著填一下。
之前在《程序員面試指北:面試官視角》里面說過,在結構化面試中,我們會從各個方向去考查候選人,其中之一是操作系統。
上篇介紹了一套題,我還有另一套,一般這么開場:
在終端下啟動一個命令,如果在命令結束前關掉終端,它還能正常運行嗎?
- 2?-
這其實是一個很常見的case,但凡 Linux 或者 Mac 用得多一點,都會遇到。
在我還是一個窮酸學生的2009年,每個月都需要支付 20 元巨款(當時能買3根鴨脖),通過一個禁止分享網絡的認證客戶端接入校園網。
為了共建和諧宿舍?節省網費,我歷經千辛萬苦,交叉編譯開源的Linux認證客戶端,集成到固件里,并刷到了我的 NETGEAR 路由器上。
然后山水 BBS 的 Linux 版主把我的帖子置頂了 11 年。可見他有多痛恨禁止共享網絡。
這么一回憶,感覺自己的共享經濟思維真是前衛,當時怎么就沒想到去搞共享單車呢?
扯遠了,在搗騰的過程中,我就踩了這么個坑:當我ssh到路由器上、剛啟動認證時,能夠正常聯網;但是退出ssh后一會,網就斷了。
經過一番搗騰后發現,只要一退出ssh,認證程序就涼了,而不是繼續在后臺保持和認證服務器的通信。
- 3 -
所以前面那個問題,我以為大部分候選人應該會回答“否”,但沒想到竟然還有不少人回答“是”。
其實回答“是”也沒什么錯,因為確實也有些命令不會隨著終端關閉而結束。
問題是當我追問當時執行的是什么命令時,候選人往往又說不出個所以然來。
(借學長的表情一用)
然后我就感到很強的挫敗感:這不按劇本來,沒法問了啊……只好換題。
當然大部分候選人確實被坑過,于是我可以接著問:
如果確實需要在后臺繼續執行命令怎么辦呢?
有些人只記得要在后面加個 & ;但也有不少人知道前面還得加個 nohup,就像這樣:
$ nohup python process.py &[1]?1806824nohup:?ignoring?input?and?appending?output?to?'nohup.out'注:其實我更喜歡 screen(或 tmux),偶爾也用 setsid。
然后就可以放心地關閉終端?開始放羊?了。
但我的套題還沒結束:為什么加上 nohup 就可以讓進程在后臺繼續運行呢?
(這表情熟悉嗎)
- 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 呢?(嚴正申明:這一問不在套題里)
我們知道,在上古時代,捉 bug 就已經是碼農的必備技能(更準確地說是 moth)。
(我總覺得這個圖是假的)
到了遠古時代,他們不再需要去機房,通過基于 RS-232 協議的串行線路連接到大型機的終端上,就可以開始收福報。
收完福報,程序員通知自己的貓(modem)掛斷(Hang Up)連接;大型機的 OS 檢測到連接斷開,就會給進程發送信號 —— 所以這信號被稱為 SIGHUP 。
這果然是毫無卵用的知識啊。
- 5 -
很多同學在操作系統的課程上學習了“進程間的通信方式有信號、管道、消息隊列、共享內存……”,但是對信號到底是個什么東西,并沒有現實的概念。
課堂教學的理論和實踐往往是割裂的,在此特別推薦《Unix環境高級編程》(簡稱APUE)。
APUE在 1.9 - 信號 中寫到:信號是通知進程已發生某種條件的一種技術。
而在 Linux/Unix 下,進程對信號的處理有三種選擇:
按系統默認方式處理
提供一個回調函數
或忽略該信號(有些信號例外,不允許被忽略)
以 SIGHUP 信號為例,系統默認處理方式就是結束進程。
當然終端下打開的第一個進程通常都是shell(例如bash)。shell會給 SIGHUP 信號注冊一個回調函數,用于給該 shell 下所有的子進程發送 SIGHUP 信號,然后再主動退出。
對于求生欲很強的程序(例如nohup),可以主動選擇忽略該信號。
有一些進程本來就被設計成在后臺運行,不需要控制終端,因此它們將?SIGHUP 挪作它用,一個常見的用法就是重新讀取配置文件(例如Apache、Nginx),上篇提到的 logrotate 正是利用了這一點。
終于填完了坑。
- 6 -
說了這么多都還是紙上談兵,實操中如何主動忽略 SIGHUP 呢?
實際上也很簡單,使用 Linux 的 signal 系統調用即可:
#include #include 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 -9 $PID,就是給進程發送了一個 SIGKILL 信號。可能和你期望有出入的是,SIGKILL 是可以被進程忽略的。所以有時候你得用 SIGTERM。
你還可以使用可自定義的 SIGUSR1、SIGUSR2、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 。
這就是所謂的僵尸進程,這時候你找九叔都沒用。
(大半夜找這圖還挺滲人的)
所以Linux里養僵尸,其實就是子進程死了父進程不收尸,大家可能會很驚訝Linux里怎么會養僵尸呢?但事實就是這樣,小編也感到非常驚訝。
這就是關于Linux里養僵尸的事情了,大家有什么想法呢,歡迎在評論區告訴小編一起討論哦!
推薦閱讀
程序員面試指北:面試官視角
踩坑記:go服務內存暴漲
TCP:學得越多越不懂
UTF-8:一些好像沒什么用的冷知識
[譯] C程序員該知道的內存知識 (1)
總結
以上是生活随笔為你收集整理的linux结束进程_生人勿近之Linux里养僵尸的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: abaqus实例详解_Abaqus接触分
- 下一篇: linux 其他常用命令