進程是一個很是複雜的概念,涉及的內容也很是很是多。在這一小節所列出內容,已是我極度簡化後的內容了,應該儘量都理解下來,我以爲這些理論好比何使用命令來查看狀態更重要,並且不明白這些理論,後面查看狀態信息時基本上不知道狀態對應的是什麼意思。html
但對於非編程人員來講,更多的進程細節也沒有必要去深究,固然,多多益善是確定的。nginx
程序是二進制文件,是靜態存放在磁盤上的,不會佔用系統運行資源(cpu/內存)。正則表達式
進程是用戶執行程序或者觸發程序的結果,能夠認爲進程是程序的一個運行實例。進程是動態的,會申請和使用系統資源,並與操做系統內核進行交互。在後文中,很多狀態統計工具的結果中顯示的是system類的狀態,其實system狀態的同義詞就是內核狀態。shell
如今全部的操做系統都能"同時"運行多個進程,也就是多任務或者說是並行執行。但實際上這是人類的錯覺,一顆物理cpu在同一時刻只能運行一個進程,只有多顆物理cpu才能真正意義上實現多任務。數據庫
人類會產生錯覺,覺得操做系統能並行作幾件事情,這是經過在極短期內進行進程間切換實現的,由於時間極短,前一刻執行的是進程A,下一刻切換到進程B,不斷的在多個進程間進行切換,使得人類覺得在同時處理多件事情。apache
不過,cpu如何選擇下一個要執行的進程,這是一件很是複雜的事情。在Linux上,決定下一個要運行的進程是經過"調度類"(調度程序)來實現的。程序什麼時候運行,由進程的優先級決定,但要注意,優先級值越低,優先級就越高,就越快被調度類選中。除此以外,優先級還影響分配給進程的時間片長短。在Linux中,改變進程的nice值,能夠影響某類進程的優先級值。編程
有些進程比較重要,要讓其儘快完成,有些進程則比較次要,早點或晚點完成不會有太大影響,因此操做系統要可以知道哪些進程比較重要,哪些進程比較次要。比較重要的進程,應該多給它分配一些cpu的執行時間,讓其儘快完成。下圖是cpu時間片的概念。vim
由此能夠知道,全部的進程都有機會運行,但重要的進程老是會得到更多的cpu時間,這種方式是"搶佔式多任務處理":內核能夠強制在時間片耗盡的狀況下收回cpu使用權,並將cpu交給調度類選中的進程,此外,在某些狀況下也能夠直接搶佔當前運行的進程。隨着時間的流逝,分配給進程的時間也會被逐漸消耗,當分配時間消耗完畢時,內核收回此進程的控制權,並讓下一個進程運行。但由於前面的進程尚未完成,在將來某個時候調度類仍是會選中它,因此內核應該將每一個進程臨時中止時的運行時環境(寄存器中的內容和頁表)保存下來(保存位置爲內核佔用的內存),這稱爲保護現場,在下次進程恢復運行時,將原來的運行時環境加載到cpu上,這稱爲恢復現場,這樣cpu能夠在當初的運行時環境下繼續執行。安全
看書上說,Linux的調度器不是經過cpu的時間片流逝來選擇下一個要運行的進程的,而是考慮進程的等待時間,即在就緒隊列等待了多久,那些對時間需求最嚴格的進程應該儘早安排其執行。另外,重要的進程分配的cpu運行時間天然會較多。bash
調度類選中了下一個要執行的進程後,要進行底層的任務切換,也就是上下文切換,這一過程須要和cpu進程緊密的交互。進程切換不該太頻繁,也不該太慢。切換太頻繁將致使cpu閒置在保護和恢復現場的時間過長,保護和恢復現場對人類或者進程來講是沒有產生生產力的(由於它沒有在執行程序)。切換太慢將致使進程調度切換慢,極可能下一個進程要等待好久才能輪到它執行,直白的說,若是你發出一個ls命令,你可能要等半天,這顯然是不容許的。
至此,也就知道了cpu的衡量單位是時間,就像內存的衡量單位是空間大小同樣。進程佔用的cpu時間長,說明cpu運行在它身上的時間就長。注意,cpu的百分比值不是其工做強度或頻率高低,而是"進程佔用cpu時間/cpu總時間",這個衡量概念必定不要搞錯。
根據執行程序的用戶UID以及其餘標準,會爲每個進程分配一個惟一的PID。
父子進程的概念,簡單來講,在某進程(父進程)的環境下執行或調用程序,這個程序觸發的進程就是子進程,而進程的PPID表示的是該進程的父進程的PID。由此也知道了,子進程老是由父進程建立。
在Linux,父子進程以樹型結構的方式存在,父進程建立出來的多個子進程之間稱爲兄弟進程。CentOS 6上,init進程是全部進程的父進程,CentOS 7上則爲systemd。
Linux上建立子進程的方式有三種(極其重要的概念):一種是fork出來的進程,一種是exec出來的進程,一種是clone出來的進程。
(1).fork是複製進程,它會複製當前進程的副本(不考慮寫時複製的模式),以適當的方式將這些資源交給子進程。因此子進程掌握的資源和父進程是同樣的,包括內存中的內容,因此也包括環境變量和變量。但父子進程是徹底獨立的,它們是一個程序的兩個實例。
(2).exec是加載另外一個應用程序,替代當前運行的進程,也就是說在不建立新進程的狀況下加載一個新程序。exec還有一個動做,在進程執行完畢後,退出exec所在環境(其實是進程直接跳轉到exec上,執行完exec就直接退出。而非exec加載程序的方式是:父進程睡眠,而後執行子進程,執行完後回到父進程,因此不會當即退出當前環境)。因此爲了保證進程安全,若要造成新的且獨立的子進程,都會先fork一份當前進程,而後在fork出來的子進程上調用exec來加載新程序替代該子進程。例如在bash下執行cp命令,會先fork出一個bash,而後再exec加載cp程序覆蓋子bash進程變成cp進程。但要注意,fork進程時會複製全部內存頁,但使用exec加載新程序時會初始化地址空間,意味着複製動做徹底是多餘的操做,固然,有了寫時複製技術不用過多考慮這個問題。
(3).clone用於實現線程。clone的工做原理和fork相同,但clone出來的新進程不獨立於父進程,它只會和父進程共享某些資源,在clone進程的時候,能夠指定要共享的是哪些資源。
題外知識:如何建立一個子進程?
每次fork一個進程的時候,雖然調用一次fork(),會分別爲兩個進程返回兩個值:對子進程的返回值爲0,對父進程的返回值是子進程的pid。因此,可使用下面的shell僞代碼來描述運行一個ls命令時的過程:
fpid=`fork()` if [ $fpid = 0 ]{ exec(ls) || echo "Can't exec ls"
exit } wait($fpid)假設上面是在shell腳本中執行ls命令,那麼fork的是shell腳本進程。fork後,父進程將繼續執行,且if語句判斷失敗,因而執行wait;而子進程執行時將檢測到fpid=0,因而執行exec(ls),當ls執行結束,子進程由於exec的緣由將退出。因而父進程的wait等待完成,繼續執行後面的代碼。
若是在這個shell腳本中某個位置,執行exec命令(exec命令調用的其實就是exec家族函數),shell腳本進程直接切換到exec命令上,執行完exec命令,就表示進程終止,因而exec命令後面的全部命令都不會再執行。
+--------+ | pid=7 | | ppid=4 | | bash | +--------+ | | calls fork V +--------+ +--------+ | pid=7 | forks | pid=22 | | ppid=4 | ----------> | ppid=7 | | bash | | bash | +--------+ +--------+ | | | waits for pid 22 | calls exec to run ls | V | +--------+ | | pid=22 | | | ppid=7 | | | ls | V +--------+ +--------+ | | pid=7 | | exits | ppid=4 | <---------------+ | bash | +--------+ | | continues V
通常狀況下,兄弟進程之間是相互獨立、互不可見的,但有時候經過特殊手段,它們會實現進程間通訊。例如管道協調了兩邊的進程,兩邊的進程屬於同一個進程組,它們的PPID是同樣的,管道使得它們能夠以"管道"的方式傳遞數據。
進程是有全部者的,也就是它的發起者,某個用戶若是它非進程發起者、非父進程發起者、非root用戶,那麼它沒法殺死進程。且殺死父進程(非終端進程),會致使子進程變成孤兒進程,孤兒進程的父進程老是init/systemd。
進程並不是老是處於運行中,至少cpu沒運行在它身上時它就是非運行的。進程有幾種狀態,不一樣的狀態之間能夠實現狀態切換。下圖是很是經典的進程狀態描述圖,我的感受右圖更加易於理解。
運行態:進程正在運行,也便是cpu正在它身上。
就緒(等待)態:進程能夠運行,已經處於等待隊列中,也就是說調度類下次可能會選中它
睡眠(阻塞)態:進程睡眠了,不可運行。
各狀態之間的轉換方式爲:(也許可能不太好理解,能夠結合稍後的例子)
(1)新狀態->就緒態:當等待隊列容許接納新進程時,內核便把新進程移入等待隊列。
(2)就緒態->運行態:調度類選中等待隊列中的某個進程,該進程進入運行態。
(3)運行態->睡眠態:正在運行的進程因須要等待某事件(如IO等待、信號等待等)的出現而沒法執行,進入睡眠態。
(4)睡眠態->就緒態:進程所等待的事件發生了,進程就從睡眠態排入等待隊列,等待下次被選中執行。
(5)運行態->就緒態:正在執行的進程因時間片用完而被暫停執行;或者在搶佔式調度方式中,高優先級進程強制搶佔了正在執行的低優先級進程。
(6)運行態->終止態:一個進程已完成或發生某種特殊事件,進程將變爲終止狀態。對於命令來講,通常都會返回退出狀態碼。
注意上面的圖中,沒有"就緒-->睡眠"和"睡眠-->運行"的狀態切換。這很容易理解。對於"就緒-->睡眠",等待中的進程本就已經進入了等待隊列,表示可運行,而進入睡眠態表示暫時不可運行,這自己就是衝突的;對於"睡眠-->運行"這也是行不通的,由於調度類只會從等待隊列中挑出下一次要運行的進程。
再說說運行態-->睡眠態。從運行態到睡眠態通常是等待某事件的出現,例如等待信號通知,等待IO完成。信號通知很容易理解,而對於IO等待,程序要運行起來,cpu就要執行該程序的指令,同時還須要輸入數據,多是變量數據、鍵盤輸入數據或磁盤文件中的數據,後兩種數據相對cpu來講,都是極慢極慢的。但無論怎樣,若是cpu在須要數據的那一刻卻得不到數據,cpu就只能閒置下來,這確定是不該該的,由於cpu是極其珍貴的資源,因此內核應該讓正在運行且須要數據的進程暫時進入睡眠,等它的數據都準備好了再回到等待隊列等待被調度類選中。這就是IO等待。
其實上面的圖中少了一種進程的特殊狀態——殭屍態。殭屍態進程表示的是進程已經轉爲終止態,它已經完成了它的使命並消逝了,可是內核尚未來得及將它在進程列表中的項刪除,也就是說內核沒給它料理後事,這就形成了一個進程是死的也是活着的假象,說它死了是由於它再也不消耗額外資源(但可能會佔用未釋放的資源),調度類也不可能選中它並讓它運行,說它活着是由於在進程列表中還存在對應的表項,能夠被捕捉到。殭屍態進程並不必定會佔用多少資源(除非fork出來的大量進程都佔用了未釋放的資源且成了殭屍進程),正常狀況下的大多數殭屍進程僅在進程列表中佔用一點點的內存,大多數殭屍進程的出現都是由於進程正常終止(包括kill -9),但父進程沒有確認該進程已經終止,內核也不知道該進程已經終止了。殭屍進程更具體說明見後文。
另外,睡眠態是一個很是寬泛的概念,分爲可中斷睡眠和不可中斷睡眠。可中斷睡眠是容許接收外界信號和內核信號而被喚醒的睡眠,絕大多數睡眠都是可中斷睡眠,能ps或top捕捉到的睡眠也幾乎老是可中斷睡眠;不可中斷睡眠只能由內核發起信號來喚醒,外界沒法經過信號來喚醒,主要表如今和硬件交互的時候。例如cat一個文件時,從硬盤上加載數據到內存中,在和硬件交互的那一小段時間必定是不可中斷的,不然在加載數據的時候忽然被人爲發送的信號手動喚醒,而被喚醒時和硬件交互的過程又還沒完成,因此即便喚醒了也無法將cpu交給它運行,因此cat一個文件的時候不可能只顯示一部份內容。並且,不可中斷睡眠若能被人爲喚醒,更嚴重的後果是硬件崩潰。由此可知,不可中斷睡眠是爲了保護某些重要進程,也是爲了讓cpu不被浪費。
其實只要發現進程存在,且非殭屍態進程,還不佔用cpu資源,那麼它就是睡眠的。包括後文中出現的暫停態、追蹤態,它們也都是睡眠態。
進程間狀態的轉換狀況可能很複雜,這裏舉一個例子,儘量詳細地描述它們。
以在bash下執行cp命令爲例。在當前bash環境下,處於可運行狀態(即就緒態)時,當執行cp命令時,首先fork出一個bash子進程,而後在子bash上exec加載cp程序,cp子進程進入等待隊列,因爲在命令行下敲的命令,因此優先級較高,調度類很快選中它。在cp這個子進程執行過程當中,父進程bash會進入睡眠狀態(不只是由於cpu只有一顆的狀況下一次只能執行一個進程,還由於進程等待),並等待被喚醒,此刻bash沒法和人類交互。當cp命令執行完畢,它將本身的退出狀態碼告知父進程,這次複製是成功仍是失敗,而後cp進程本身消逝掉,父進程bash被喚醒再次進入等待隊列,而且此時bash已經得到了cp退出狀態碼。根據狀態碼這個"信號",父進程bash知道了子進程已經終止,因此通告給內核,內核收到通知後將進程列表中的cp進程項刪除。至此,整個cp進程正常完成。
假如cp這個子進程複製的是一個大文件,一個cpu時間片沒法完成複製,那麼在一個cpu時間片消耗盡的時候它將進入等待隊列。
假如cp這個子進程複製文件時,目標位置已經有了同名文件,那麼默認會詢問是否覆蓋,發出詢問時它等待yes或no的信號,因此它進入了睡眠狀態(可中斷睡眠),當在鍵盤上敲入yes或no信號給cp的時候,cp收到信號,從睡眠態轉入就緒態,等待調度類選中它完成cp進程。
在cp複製時,它須要和磁盤交互,在和硬件交互的短暫過程當中,cp將處於不可中斷睡眠。
假如cp進程結束了,可是結束的過程出現了某種意外,使得bash這個父進程不知道它已經結束了(此例中是不可能出現這種狀況的),那麼bash就不會通知內核回收進程列表中的cp表項,cp此時就成了殭屍進程。
說到這了,應該解釋下子shell,這個特殊的子進程。
通常fork出來的子進程,內容和父進程是同樣的,包括變量,例如執行cp命令時也能獲取到父進程的變量。可是cp命令是在哪裏執行的呢?在子shell中。執行cp命令敲入回車後,當前的bash進程fork出一個子bash,而後子bash經過exec加載cp程序替代子bash。請不要在此糾結子bash和子shell,若是搞不清它們的關係,就當它是同一種東西好了。
那是否能夠理解爲全部命令、腳本其運行環境都是在子shell中呢?顯然,上面所說的bash內置命令不是在子shell中運行的。其餘的全部方式,都是在子shell中完成,只不過方式不盡相同。
分爲幾種狀況(只列出幾種比較能說明問題的例子,還有其它不少種會進入子shell的狀況):
最後須要說明的是,子shell的環境設置不會粘滯到父shell環境,也就是說子shell的變量等不會影響父shell。
還有兩種特殊的腳本調用方式:exec和source。
大部分進程都能將其放入後臺,這時它就是一個後臺任務,因此常稱爲job,每一個開啓的shell會維護一個job table,後臺中的每一個job都在job table中對應一個Job項。
手動將命令或腳本放入後臺運行的方式是在命令行後加上"&"符號。例如:
[root@server2 ~]# cp /etc/fstab /tmp/ & [1] 8701
將進程放入後臺後,會當即返回其父進程,通常對於手動放入後臺的進程都是在bash下進行的,因此當即返回bash環境。在返回父進程的同時,還會返回給父進程其jobid和pid。將來要引用jobid,都應該在jobid前加上百分號"%",其中"%%"表示當前job,例如"kill -9 %1"表示殺掉jobid爲1的後臺進程,若是不加百分號,完了,把Init進程給殺了。
經過jobs命令能夠查看後臺job信息。
jobs [-lrs] [jobid]
選項說明:
-l:jobs默認不會列出後臺工做的PID,加上-l會列出進程的PID -r:顯示後臺工做處於run狀態的jobs -s:顯示後臺工做處於stopped狀態的jobs
經過"&"放入後臺的任務,在後臺中仍會處於運行中。固然,對於那種交互式如vim類的命令,將轉入暫停運行狀態。
[root@server2 ~]# sleep 10 & [1] 8710 [root@server2 ~]# jobs [1]+ Running sleep 10 &
必定要注意,此處看到的是running和ps或top顯示的R狀態,它們並不老是表示正在運行,處於等待隊列的進程也屬於running。它們都屬於task_running標識。
另外一種手動加入後臺的方式是按下CTRL+Z鍵,這能夠將正在運行中的進程加入到後臺,但這樣加入後臺的進程會在後臺暫停運行。
[root@server2 ~]# sleep 10 ^Z [1]+ Stopped sleep 10 [root@server2 ~]# jobs [1]+ Stopped sleep 10
從jobs信息也看到了在每一個jobid的後面有個"+"號,還有"-",或者不帶符號。
[root@server2 ~]# sleep 30&vim /etc/my.cnf&sleep 50& [1] 8915 [2] 8916 [3] 8917
[root@server2 ~]# jobs
[1] Running sleep 30 & [2]+ Stopped vim /etc/my.cnf [3]- Running sleep 50 &
發現vim的進程後是加號,"+"表示執行中的任務,也就是說cpu正在它身上,"-"表示被調度類選中的下個要執行的任務,從第三個任務開始不會再對其標註。從jobs的狀態能夠分析出來,後臺任務表中running但沒有"+"的表示處於等待隊列,running且帶有"+"的表示正在執行,stopped狀態的表示處於睡眠狀態。但不能認爲job列表中任務一直是這樣的狀態,由於每一個任務分配到的時間片實際上都很短,在很短的時間內執行完這一次時間片長度的任務,馬上切換到下一個任務並執行。只不過實際過程當中,由於切換速度和每一個任務的時間片都極短,因此任務列表較小時,顯示出來的順序可能不怎麼會出現變更。
就上面的例子而言,下一個要執行的任務是vim,但它是stop的,難道由於這個第一順位的進程stop,其餘進程就不執行嗎?顯然不是這樣的。事實上,過不了多久,會發現另外兩個sleep任務已經完成了,但vim仍處於stop狀態。
[root@server2 ~]# jobs
[1] Done sleep 30 [2]+ Stopped vim /etc/my.cnf [3]- Done sleep 50
經過這個job例子,是否是更深刻的理解了一點內核調度進程的方式呢?
迴歸正題。既然能手動將進程放入後臺,那確定能調回到前臺,調到前臺查看了下執行進度,又想調入後臺,這確定也得有方法,總不能使用CTRL+Z以暫停方式加到後臺吧。
fg和bg命令分別是foreground和background的縮寫,也就是放入前臺和放入後臺,嚴格的說,是以運行狀態放入前臺和後臺,即便原來任務是stopped狀態的。
操做方式也很簡單,直接在命令後加上jobid便可(即[fg|bg] [%jobid]),不給定jobid時操做的將是當前任務,即帶有"+"的任務項。
[root@server2 ~]# sleep 20 ^Z # 按下CTRL+Z進入暫停並放入後臺 [3]+ Stopped sleep 20
[root@server2 ~]# jobs
[2]- Stopped vim /etc/my.cnf [3]+ Stopped sleep 20 # 此時爲stopped狀態
[root@server2 ~]# bg %3 # 使用bg或fg可讓暫停狀態的進程變會運行態 [3]+ sleep 20 &
[root@server2 ~]# jobs
[2]+ Stopped vim /etc/my.cnf [3]- Running sleep 20 & # 已經變成運行態
使用disown命令能夠從job table中直接移除一個job,僅僅只是移出job table,並不是是結束任務。並且移除job table後,任務將掛在init/systemd進程下,使其不依賴於終端。
disown [-ar] [-h] [%jobid ...] 選項說明: -h:給定該選項,將不從job table中移除job,而是將其設置爲不接受shell發送的sighup信號。具體說明見"信號"小節。 -a:若是沒有給定jobid,該選項表示針對Job table中的全部job進行操做。 -r:若是沒有給定jobid,該選項嚴格限定爲只對running狀態的job進行操做
若是不給定任何選項,該shell中全部的job都會被移除,移除是disown的默認操做,若是也沒給定jobid,並且也沒給定-a或-r,則表示只針對當前任務即帶有"+"號的任務項。
使用pstree命令查看下當前的進程,不難發如今某個終端執行的進程其父進程或上幾個級別的父進程老是會是終端的鏈接程序。
例以下面篩選出了兩個終端下的父子進程關係,第一個行是tty終端(即直接在虛擬機中)中執行的進程狀況,第二行和第三行是ssh鏈接到Linux上執行的進程。
[root@server2 ~]# pstree -c | grep bash |-login---bash---bash---vim |-sshd-+-sshd---bash | `-sshd---bash-+-grep
正常狀況下殺死父進程會致使子進程變爲孤兒進程,即其PPID改變,可是殺掉終端這種特殊的進程,會致使該終端上的全部進程都被殺掉。這在不少執行長時間任務的時候是很不方便的。好比要下班了,可是你鏈接的終端上還在執行數據庫備份腳本,這可能會花掉很長時間,若是直接退出終端,備份就終止了。因此應該保證一種安全的退出方法。
通常的方法也是最簡單的方法是使用nohup命令帶上要執行的命令或腳本放入後臺,這樣任務就脫離了終端的關聯。當終端退出時,該任務將自動掛到init(或systemd)進程下執行。如:
shell> nohup tar rf a.tar.gz /tmp/*.txt &
另外一種方法是使用screen這個工具,該工具能夠模擬多個物理終端,雖然模擬後screen進程仍然掛在其所在的終端上的,但同nohup同樣,當其所在終端退出後將自動掛到init/systemd進程下繼續存在,只要screen進程仍存在,其所模擬的物理終端就會一直存在,這樣就保證了模擬終端中的進程繼續執行。它的實現方式其實和nohup差很少,只不過它花樣更多,管理方式也更多。通常對於簡單的後臺持續運行進程,使用nohup足以。
另外,在子shell中的後臺進程在終端被關閉時也會脫離終端,所以也不受shell和終端的控制。例如shell腳本中的後臺進程,再如"(sleep 10 &)"。
可能你已經發現了,不少進程是和終端無關的,也就是不依賴於終端,這類進程通常是內核類進程/線程以及daemon類進程,若它們也依賴於終端,則終端一被終止,這類進程也當即被終止,這是絕對不容許的。
信號在操做系統中控制着進程的絕大多數動做,信號可讓進程知道某個事件發生了,也指示着進程下一步要作出什麼動做。信號的來源能夠是硬件信號(如按下鍵盤或其餘硬件故障),也能夠是軟件信號(如kill信號,還有內核發送的信號)。不過,不少能夠感覺到的信號都是從進程所在的控制終端發送出去的。
Linux中支持很是多種信號,它們都以SIG字符串開頭,SIG字符串後的纔是真正的信號名稱,信號還有對應的數值,其實數值纔是操做系統真正認識的信號。但因爲很多信號在不一樣架構的計算機上數值不一樣(例如CTRL+Z發送的SIGSTP信號就有三種值18,20,24),因此在不肯定信號數值是否惟一的時候,最好指定其字符名稱。
如下是須要了解的信號。
Signal Value Comment
─────────────────────────────
SIGHUP 1 終端退出時,此終端內的進程都將被終止 SIGINT 2 中斷進程,可被捕捉和忽略,幾乎等同於sigterm,因此也會盡量的釋放執行clean-up,釋放資源,保存狀態等(CTRL+C) SIGQUIT 3 從鍵盤發出殺死(終止)進程的信號 SIGKILL 9 強制殺死進程,該信號不可被捕捉和忽略,進程收到該信號後不會執行任何clean-up行爲,因此資源不會釋放,狀態不會保存 SIGTERM 15 殺死(終止)進程,可被捕捉和忽略,幾乎等同於sigint信號,會盡量的釋放執行clean-up,釋放資源,保存狀態等 SIGCHLD 17 當子進程中斷或退出時,發送該信號告知父進程本身已完成,父進程收到信號將告知內核清理進程列表。因此該信號能夠解除殭屍進 程,也可讓非正常退出的進程工做得以正常的clean-up,釋放資源,保存狀態等。 SIGSTOP 19 該信號是不可被捕捉和忽略的進程中止信息,收到信號後會進入stopped狀態 SIGTSTP 20 該信號是可被忽略的進程中止信號(CTRL+Z) SIGCONT 18 發送此信號使得stopped進程進入running,該信號主要用於jobs,例如bg & fg 都會發送該信號。 能夠直接發送此信號給stopped進程使其運行起來
SIGUSR1 10 用戶自定義信號1 SIGUSR2 12 用戶自定義信號2
除了這些信號外,還須要知道一個特殊信號:代碼爲0的信號。此信號爲EXIT信號,表示直接退出。若是kill發送的信號是0(即kill -0)則表示不作任何處理直接退出,但執行錯誤檢查:當檢查發現給定的pid進程存在,則返回0,不然返回1。也就是說,0信號能夠用來檢測進程是否存在,能夠代替 ps aux | grep proc_name 。(man kill中的原文爲:If sig is 0, then no signal is sent, but error checking is still performed。而man bash的trap小節中有以下描述:If a sigspec is EXIT (0),這說明0信號就是EXIT信號)
以上所列的信號中,只有SIGKILL和SIGSTOP這兩個信號是不可被捕捉且不可被忽略的信號,其餘全部信號均可以經過trap或其餘編程手段捕捉到或忽略掉。
此外,常常看到有些服務程序(如httpd/nginx)的啓動腳本中使用WINCH和USR1這兩個信號,發送這兩個信號時它們分別表示graceful stop和graceful restart。所謂的graceful,譯爲優雅,不過使用這兩個字去描述這種環境實在有點不三不四。它對於後臺服務程序而言,傳達了幾個意思:(1)當前已經運行的進程再也不接受新請求(2)給當前正在運行的進程足夠多的時間去完成正在處理的事情(3)容許啓動新進程接受新請求(4)可能還有日誌文件是否應該滾動、pid文件是否修改的可能,這要看服務程序對信號的具體實現。
再來講說,爲何後臺服務程序可使用這兩個信號。以httpd的爲例,在其頭文件mpm_common.h中有以下幾行代碼:
/* Signal used to gracefully restart */ #define AP_SIG_GRACEFUL SIGUSR1 /* Signal used to gracefully stop */ #define AP_SIG_GRACEFUL_STOP SIGWINCH
這說明註冊了對應信號的處理函數,它們分別表示將接收到信號時,執行對應的GRACEFUL函數。
注意,SIGWINCH是窗口程序的尺寸改變時發送改信號,如vim的窗口改變了就會發送該信號。可是對於後臺服務程序,它們根本就沒有窗口,因此WINCH信號對它們來講是沒有任何做用的。所以,大概是約定俗成的,你們都喜歡用它來做爲後臺服務程序的GRACEFUL信號。但注意,WINCH信號對前臺程序多是有影響的,不要亂髮這種信號。同理,USR1和USR2也是同樣的,若是源代碼中明確爲這兩個信號註冊了對應函數,那麼發送這兩個信號就能夠實現對應的功能,反之,若是沒有註冊,則這兩個信號對進程來講是錯誤信號。
更多更詳細的信號理解或說明,能夠參考wiki的兩篇文章:
jobs控制機制:https://en.wikipedia.org/wiki/Job_control_(Unix)
信號說明:https://en.wikipedia.org/wiki/Unix_signal
(1).當控制終端退出時,會向該終端中的進程發送sighup信號,所以該終端上運行的shell進程、其餘普通進程以及任務都會收到sighup而致使進程終止。
多種方式能夠改變因終端中斷髮送sighup而致使子進程也被結束的行爲,這裏僅介紹比較常見的三種:一是使用nohup命令啓動進程,它會忽略全部的sighup信號,使得該進程不會隨着終端退出而結束;二是將待執行命令放入子shell中並放入後臺運行,例如"(sleep 10 &)";三是使用disown,將任務列表中的任務移除出job table或者直接使用disown -h的功能設置其不接收終端發送的sighup信號。但無論是何種實現方式,終端退出後未被終止的進程將只能掛靠在init/systemd下。
(2).對於daemon類的程序(即服務性進程),這類程序不依賴於終端(它們的父進程都是init或systemd),它們收到sighup信號時會重讀配置文件並從新打開日誌文件,使得服務程序能夠不用重啓就能夠加載配置文件。
一個編程完善的程序,在子進程終止、退出的時候,內核會發送SIGCHLD信號給其父進程,父進程收到信號就會對該子進程進行善後(接收子進程的退出狀態、釋放未關閉的資源),同時內核也會進行一些善後操做(好比清理進程表項、關閉打開的文件等)。
在子進程死亡的那一剎那,子進程的狀態就是殭屍進程,但由於發出了SIGCHLD信號給父進程,父進程只要收到該信號,子進程就會被清理也就再也不是殭屍進程。因此正常狀況下,全部終止的進程都會有一小段時間處於殭屍態(發送SIGCHLD信號到父進程收到該信號之間),只不過這種殭屍進程存在時間極短(倒黴的殭屍),幾乎是不可被ps或top這類的程序捕捉到的。
若是在特殊狀況下,子進程終止了,但父進程沒收到SIGCHLD信號,沒收到這信號的緣由多是多種的,無論如何,此時子進程已經成了永存的殭屍,能輕易的被ps或top捕捉到。殭屍不倒黴,人類就要倒黴,可是殭屍爸爸並不知道它兒子已經變成了殭屍,由於有殭屍爸爸的掩護,殭屍道長即內核見不到小殭屍,因此也無法收屍。悲催的是,人類能力不足,直接發送信號(如kill)給殭屍進程是無效的,由於殭屍進程本就是終結了的進程,它收不到信號,只有內核從進程列表中將殭屍進程表項移除纔算完成收屍。
要解決掉永存的殭屍有幾種方法:
(1).殺死殭屍進程的父進程。沒有了殭屍爸爸的掩護,小殭屍就暴露給了殭屍道長的直系弟子init/systemd,init/systemd會按期清理它下面的各類殭屍進程。因此這種方法有點不講道理,殭屍爸爸是正常的啊,不過若是殭屍爸爸下面有不少殭屍兒子,這殭屍爸爸確定是有問題的,好比編程不完善,殺掉是應該的。
(2).手動發送SIGCHLD信號給殭屍進程的父進程。殭屍道長找不到殭屍,但被殭屍禍害的人類能發現殭屍,因此人類主動通知殭屍爸爸,讓殭屍爸爸知道本身的兒子死而不僵,而後通知內核來收屍。
固然,第二種手動發送SIGCHLD信號的方法要求父進程能收到信號,而SIGCHLD信號默認是被忽略的,因此應該顯式地在程序中加上獲取信號的代碼。也就是人類主動通知殭屍爸爸的時候,默認殭屍爸爸是不搭理人類的,因此要強制讓殭屍爸爸收到通知。不過通常daemon類的程序在編程上都是很完善的,發送SIGCHLD老是會收到,不用擔憂。
使用kill命令能夠手動發送信號給指定的進程。
kill [-s signal] pid... kill [-signal] pid... kill -l
使用kill -l能夠列出Linux中支持的信號,有64種之多,但絕大多數非編程人員都用不上。
使用-s或-signal均可以發送信號,不給定發送的信號時,默認爲TREM信號,即kill -15。
shell> kill -9 pid1 pid2... shell> kill -TREM pid1 pid2... shell> kill -s TREM pid1 pid2...
這兩個命令均可以直接指定進程名來發送信號,不指定信號時,默認信號都是TERM。
(1).pkill
pkill和pgrep命令是同族命令,都是先經過給定的匹配模式搜索到指定的進程,而後發送信號(pkill)或列出匹配的進程(pgrep),pgrep就不介紹了。
pkill可以指定模式匹配,因此可使用進程名來刪除,想要刪除指定pid的進程,反而還要使用"-s"選項來指定。默認發送的信號是SIGTERM即數值爲15的信號。
pkill [-signal] [-v] [-P ppid,...] [-s pid,...][-U uid,...] [-t term,...] [pattern]
選項說明:
-P ppid,... :匹配PPID爲指定值的進程
-s pid,... :匹配PID爲指定值的進程
-U uid,... :匹配UID爲指定值的進程,可使用數值UID,也可使用用戶名稱
-t term,... :匹配給定終端,終端名稱不能帶上"/dev/"前綴,其實"w"命令得到終端名就知足此處條件了,因此pkill能夠直接殺掉整個終端 -v :反向匹配 -signal :指定發送的信號,能夠是數值也能夠是字符表明的信號 -f :默認狀況下,pgrep/pkill只會匹配進程名。使用-f將匹配命令行
在CentOS 7上,還有兩個好用的新功能選項。
-F, --pidfile file:匹配進程時,讀取進程的pid文件從中獲取進程的pid值。這樣就不用去寫獲取進程pid命令的匹配模式 -L, --logpidfile :若是"-F"選項讀取的pid文件未加鎖,則pkill或pgrep將匹配失敗。
例如:
[root@xuexi ~]# ps x | grep ssh[d] 1291 ? Ss 0:00 /usr/sbin/sshd 13193 ? Ss 0:02 sshd: root@pts/1,pts/3,pts/0
如今想匹配/usr/sbin/sshd。
[root@xuexi ~]# pgrep bin/sshd
[root@xuexi ~]# pgrep -f bin/sshd 1291
能夠看到第一個什麼也不返回。由於不加-f選項時,pgrep只能匹配進程名,而進程名指的是sshd,而非/usr/sbin/sshd,因此匹配失敗。加上-f後,就能匹配成功。因此,當pgrep或pkill匹配不到進程時,考慮加上-f選項。
踢出終端:
shell> pkill -t pts/0
(2).killall
killall主要用於殺死一批進程,例如殺死整個進程組。其強大之處還體如今能夠經過指定文件來搜索哪一個進程打開了該文件,而後對該進程發送信號,在這一點上,fuser和lsof命令也同樣能實現。
killall [-r,--regexp] [-s,--signal signal] [-u,--user user] [-v,--verbose] [-w,--wait] [-I,--ignore-case] [--] name ... 選項說明: -I :匹配時不區分大小寫 -r :使用擴展正則表達式進行模式匹配 -s, --signal :發送信號的方式能夠是-HUP或-SIGHUP,或數值的"-1",或使用"-s"選項指定信號 -u, --user :匹配該用戶的進程 -v, :給出詳細信息 -w, --wait :等待直到該殺的進程徹底死透了才返回。默認killall每秒檢查一次該殺的進程是否還存在,只有不存在了纔會給出退出狀態碼。 若是一個進程忽略了發送的信號、信號未產生效果、或者是殭屍進程將永久等待下去
fuser能夠查看文件或目錄所屬進程的pid,即由此知道該文件或目錄被哪一個進程使用。例如,umount的時候提示the device busy能夠判斷出來哪一個進程在使用。而lsof則反過來,它是經過進程來查看進程打開了哪些文件,但要注意的是,一切皆文件,包括普通文件、目錄、連接文件、塊設備、字符設備、套接字文件、管道文件,因此lsof出來的結果可能會很是多。
fuser [-ki] [-signal] file/dir -k:找出文件或目錄的pid,並試圖kill掉該pid。發送的信號是SIGKILL -i:通常和-k一塊兒使用,指的是在kill掉pid以前詢問。 -signal:發送信號,如-1 -15,若是不寫,默認-9,即kill -9 不加選項:直接顯示出文件或目錄的pid
在不加選項時,顯示結果中文件或目錄的pid後會帶上一個修飾符:
c:在當前目錄下
e:可被執行的
f:是一個被開啓的文件或目錄
F:被打開且正在寫入的文件或目錄
r:表明root directory
例如:
[root@xuexi ~]# fuser /usr/sbin/crond /usr/sbin/crond: 1425e
表示/usr/sbin/crond被1425這個進程打開了,後面的修飾符e表示該文件是一個可執行文件。
[root@xuexi ~]# ps aux | grep 142[5] root 1425 0.0 0.1 117332 1276 ? Ss Jun10 0:00 crond
例如:
輸出信息中各列意義:
lsof的各類用法:
lsof /path/to/somefile:顯示打開指定文件的全部進程之列表;建議配合grep使用
lsof -c string:顯示其COMMAND列中包含指定字符(string)的進程全部打開的文件;可屢次使用該選項 lsof -p PID:查看該進程打開了哪些文件 lsof -U:列出套接字類型的文件。通常和其餘條件一塊兒使用。如lsof -u root -a -U lsof -u uid/name:顯示指定用戶的進程打開的文件;可以使用脫字符"^"取反,如"lsof -u ^root"將顯示非root用戶打開的全部文件 lsof +d /DIR/:顯示指定目錄下被進程打開的文件 lsof +D /DIR/:基本功能同上,但lsof會對指定目錄進行遞歸查找,注意這個參數要比grep版本慢 lsof -a:按"與"組合多個條件,如lsof -a -c apache -u apache lsof -N:列出全部NFS(網絡文件系統)文件 lsof -n:不反解IP至HOSTNAME lsof -i:用以顯示符合條件的進程狀況 lsof -i[46] [protocol][@host][:service|port] 46:IPv4或IPv6 protocol:TCP or UDP host:host name或ip地址,表示搜索哪臺主機上的進程信息 service:服務名稱(能夠不僅一個) port:端口號 (能夠不僅一個)
大概"-i"是使用最多的了,而"-i"中使用最多的又是服務名或端口了。
[root@www ~]# lsof -i :22 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME sshd 1390 root 3u IPv4 13050 0t0 TCP *:ssh (LISTEN) sshd 1390 root 4u IPv6 13056 0t0 TCP *:ssh (LISTEN) sshd 36454 root 3r IPv4 94352 0t0 TCP xuexi:ssh->172.16.0.1:50018 (ESTABLISHED)