本文目錄: 9.1 進程的簡單說明 9.11 進程和程式的區別 9.12 多任務和cpu時間片 9.13 父子進程及創建進程的方式 9.14 進程的狀態 9.15 舉例分析進程狀態轉換過程 9.16 進程結構和子shell 9.2 job任務 9.3 終端和進程的關係 9.4 信號 9.41 需知道 ...
本文目錄:
9.1 進程簡單說明
進程是一個非常複雜的概念,涉及的內容也非常非常多。在這一小節所列出內容,已經是我極度簡化後的內容了,應該儘可能都理解下來,我覺得這些理論比如何使用命令來查看狀態更重要,而且不明白這些理論,後面查看狀態信息時基本上不知道狀態對應的是什麼意思。
但對於非編程人員來說,更多的進程細節也沒有必要去深究,當然,多多益善是肯定的。
9.1.1 進程和程式的區別
程式是二進位文件,是靜態存放在磁碟上的,不會占用系統運行資源(cpu/記憶體)。
進程是用戶執行程式或者觸發程式的結果,可以認為進程是程式的一個運行實例。進程是動態的,會申請和使用系統資源,並與操作系統內核進行交互。在後文中,不少狀態統計工具的結果中顯示的是system類的狀態,其實system狀態的同義詞就是內核狀態。
9.1.2 多任務和cpu時間片
現在所有的操作系統都能"同時"運行多個進程,也就是多任務或者說是並行執行。但實際上這是人類的錯覺,一顆物理cpu在同一時刻只能運行一個進程,只有多顆物理cpu才能真正意義上實現多任務。
人類會產生錯覺,以為操作系統能並行做幾件事情,這是通過在極短時間內進行進程間切換實現的,因為時間極短,前一刻執行的是進程A,下一刻切換到進程B,不斷的在多個進程間進行切換,使得人類以為在同時處理多件事情。
不過,cpu如何選擇下一個要執行的進程,這是一件非常複雜的事情。在Linux上,決定下一個要運行的進程是通過"調度類"(調度程式)來實現的。程式何時運行,由進程的優先順序決定,但要註意,優先順序值越低,優先順序就越高,就越快被調度類選中。在Linux中,改變進程的nice值,可以影響某類進程的優先順序值。
有些進程比較重要,要讓其儘快完成,有些進程則比較次要,早點或晚點完成不會有太大影響,所以操作系統要能夠知道哪些進程比較重要,哪些進程比較次要。比較重要的進程,應該多給它分配一些cpu的執行時間,讓其儘快完成。下圖是cpu時間片的概念。
由此可以知道,所有的進程都有機會運行,但重要的進程總是會獲得更多的cpu時間,這種方式是"搶占式多任務處理":內核可以強制在時間片耗盡的情況下收回cpu使用權,並將cpu交給調度類選中的進程,此外,在某些情況下也可以直接搶占當前運行的進程。隨著時間的流逝,分配給進程的時間也會被逐漸消耗,當分配時間消耗完畢時,內核收回此進程的控制權,並讓下一個進程運行。但因為前面的進程還沒有完成,在未來某個時候調度類還是會選中它,所以內核應該將每個進程臨時停止時的運行時環境(寄存器中的內容和頁表)保存下來(保存位置為內核占用的記憶體),這稱為保護現場,在下次進程恢復運行時,將原來的運行時環境載入到cpu上,這稱為恢復現場,這樣cpu可以在當初的運行時環境下繼續執行。
看書上說,Linux的調度器不是通過cpu的時間片流逝來選擇下一個要運行的進程的,而是考慮進程的等待時間,即在就緒隊列等待了多久,那些對時間需求最嚴格的進程應該儘早安排其執行。另外,重要的進程分配的cpu運行時間自然會較多。
調度類選中了下一個要執行的進程後,要進行底層的任務切換,也就是上下文切換,這一過程需要和cpu進程緊密的交互。進程切換不應太頻繁,也不應太慢。切換太頻繁將導致cpu閑置在保護和恢復現場的時間過長,保護和恢復現場對人類或者進程來說是沒有產生生產力的(因為它沒有在執行程式)。切換太慢將導致進程調度切換慢,很可能下一個進程要等待很久才能輪到它執行,直白的說,如果你發出一個ls命令,你可能要等半天,這顯然是不允許的。
至此,也就知道了cpu的衡量單位是時間,就像記憶體的衡量單位是空間大小一樣。進程占用的cpu時間長,說明cpu運行在它身上的時間就長。註意,cpu的百分比值不是其工作強度或頻率高低,而是"進程占用cpu時間/cpu總時間",這個衡量概念一定不要搞錯。
9.1.3 父子進程及創建進程的方式
根據執行程式的用戶UID以及其他標準,會為每一個進程分配一個唯一的PID。
父子進程的概念,簡單來說,在某進程(父進程)的環境下執行或調用程式,這個程式觸發的進程就是子進程,而進程的PPID表示的是該進程的父進程的PID。由此也知道了,子進程總是由父進程創建。
在Linux,父子進程以樹型結構的方式存在,父進程創建出來的多個子進程之間稱為兄弟進程。CentOS 6上,init進程是所有進程的父進程,CentOS 7上則為systemd。
Linux上創建子進程的方式有三種(極其重要的概念):一種是fork出來的進程,一種是exec出來的進程,一種是clone出來的進程。
(1).fork是複製進程,它會複製當前進程的副本(不考慮寫時複製的模式),以適當的方式將這些資源交給子進程。所以子進程掌握的資源和父進程是一樣的,包括記憶體中的內容,所以也包括環境變數和變數。但父子進程是完全獨立的,它們是一個程式的兩個實例。
(2).exec是載入另一個應用程式,替代當前運行的進程,也就是說在不創建新進程的情況下載入一個新程式。exec還有一個動作,在進程執行完畢後,退出exec所在的shell。所以為了保證進程安全,若要形成新的且獨立的子進程,都會先fork一份當前進程,然後在fork出來的子進程上調用exec來載入新程式替代該子進程。例如在bash下執行cp命令,會先fork出一個bash,然後再exec載入cp程式覆蓋子bash進程變成cp進程。
(3).clone用於實現線程。clone的工作原理和fork相同,但clone出來的新進程不獨立於父進程,它只會和父進程共用某些資源,在clone進程的時候,可以指定要共用的是哪些資源。
一般情況下,兄弟進程之間是相互獨立、互不可見的,但有時候通過特殊手段,它們會實現進程間通信。例如管道協調了兩邊的進程,兩邊的進程屬於同一個進程組,它們的PPID是一樣的,管道使得它們可以以"管道"的方式傳遞數據。
進程是有所有者的,也就是它的發起者,某個用戶如果它非進程發起者、非父進程發起者、非root用戶,那麼它無法殺死進程。且殺死父進程(非終端進程),會導致子進程變成孤兒進程,孤兒進程的父進程總是init/systemd。
9.1.4 進程的狀態
進程並非總是處於運行中,至少cpu沒運行在它身上時它就是非運行的。進程有幾種狀態,不同的狀態之間可以實現狀態切換。下圖是非常經典的進程狀態描述圖,個人感覺右圖更加易於理解。
運行態:進程正在運行,也即是cpu正在它身上。
就緒(等待)態:進程可以運行,已經處於等待隊列中,也就是說調度類下次可能會選中它
睡眠(阻塞)態:進程睡眠了,不可運行。
各狀態之間的轉換方式為:(也許可能不太好理解,可以結合稍後的例子)
(1)新狀態->就緒態:當等待隊列允許接納新進程時,內核便把新進程移入等待隊列。
(2)就緒態->運行態:調度類選中等待隊列中的某個進程,該進程進入運行態。
(3)運行態->睡眠態:正在運行的進程因需要等待某事件(如IO等待、信號等待等)的出現而無法執行,進入睡眠態。
(4)睡眠態->就緒態:進程所等待的事件發生了,進程就從睡眠態排入等待隊列,等待下次被選中執行。
(5)運行態->就緒態:正在執行的進程因時間片用完而被暫停執行;或者在搶占式調度方式中,高優先順序進程強制搶占了正在執行的低優先順序進程。
(6)運行態->終止態:一個進程已完成或發生某種特殊事件,進程將變為終止狀態。對於命令來說,一般都會返回退出狀態碼。
註意上面的圖中,沒有"就緒-->睡眠"和"睡眠-->運行"的狀態切換。這很容易理解。對於"就緒-->睡眠",等待中的進程本就已經進入了等待隊列,表示可運行,而進入睡眠態表示暫時不可運行,這本身就是衝突的;對於"睡眠-->運行"這也是行不通的,因為調度類只會從等待隊列中挑出下一次要運行的進程。
再說說運行態-->睡眠態。從運行態到睡眠態一般是等待某事件的出現,例如等待信號通知,等待IO完成。信號通知很容易理解,而對於IO等待,程式要運行起來,cpu就要執行該程式的指令,同時還需要輸入數據,可能是變數數據、鍵盤輸入數據或磁碟文件中的數據,後兩種數據相對cpu來說,都是極慢極慢的。但不管怎樣,如果cpu在需要數據的那一刻卻得不到數據,cpu就只能閑置下來,這肯定是不應該的,因為cpu是極其珍貴的資源,所以內核應該讓正在運行且需要數據的進程暫時進入睡眠,等它的數據都準備好了再回到等待隊列等待被調度類選中。這就是IO等待。
其實上面的圖中少了一種進程的特殊狀態——僵屍態。僵屍態進程表示的是進程已經轉為終止態,它已經完成了它的使命並消逝了,但是內核還沒有來得及將它在進程列表中的項刪除,也就是說內核沒給它料理後事,這就造成了一個進程是死的也是活著的假象,說它死了是因為它不再消耗資源,調度類也不可能選中它並讓它運行,說它活著是因為在進程列表中還存在對應的表項,可以被捕捉到。僵屍態進程並不占用多少資源,它僅在進程列表中占用一點點的記憶體。大多數僵屍進程的出現都是因為進程正常終止(包括kill -9),但父進程沒有確認該進程已經終止,所以沒有通告給內核,內核也就不知道該進程已經終止了。僵屍進程更具體說明見後文。
另外,睡眠態是一個非常寬泛的概念,分為可中斷睡眠和不可中斷睡眠。可中斷睡眠是允許接收外界信號和內核信號而被喚醒的睡眠,絕大多數睡眠都是可中斷睡眠,能ps或top捕捉到的睡眠也幾乎總是可中斷睡眠;不可中斷睡眠只能由內核發起信號來喚醒,外界無法通過信號來喚醒,主要表現在和硬體交互的時候。例如cat一個文件時,從硬碟上載入數據到記憶體中,在和硬體交互的那一小段時間一定是不可中斷的,否則在載入數據的時候突然被人為發送的信號手動喚醒,而被喚醒時和硬體交互的過程又還沒完成,所以即使喚醒了也沒法將cpu交給它運行,所以cat一個文件的時候不可能只顯示一部分內容。而且,不可中斷睡眠若能被人為喚醒,更嚴重的後果是硬體崩潰。由此可知,不可中斷睡眠是為了保護某些重要進程,也是為了讓cpu不被浪費。一般不可中斷睡眠的存在時間極短,也極難通過非編程方式捕捉到。
其實只要發現進程存在,且非僵屍態進程,還不占用cpu資源,那麼它就是睡眠的。包括後文中出現的暫停態、追蹤態,它們也都是睡眠態。
9.1.5 舉例分析進程狀態轉換過程
進程間狀態的轉換情況可能很複雜,這裡舉一個例子,儘可能詳細地描述它們。
以在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此時就成了僵屍進程。
9.1.6 進程結構和子shell
- 前臺進程:一般命令(如cp命令)在執行時都會fork子進程來執行,在子進程執行過程中,父進程會進入睡眠,這類是前臺進程。前臺進程執行時,其父進程睡眠,因為cpu只有一顆,即使是多顆cpu,也會因為執行流(進程等待)的原因而只能執行一個進程,要想實現真正的多任務,應該使用進程內多線程實現多個執行流。
- 後臺進程:若在執行命令時,在命令的結尾加上符號"&",它會進入後臺。將命令放入後臺,會立即返回父進程,並返回該後臺進程的的jobid和pid,所以後臺進程的父進程不會進入睡眠。當後臺進程出錯,或者執行完成,總之後臺進程終止時,父進程會收到信號。所以,通過在命令後加上"&",再在"&"後給定另一個要執行的命令,可以實現"偽並行"執行的方式,例如"cp /etc/fstab /tmp & cat /etc/fstab"。
- bash內置命令:bash內置命令是非常特殊的,父進程不會創建子進程來執行這些命令,而是直接在當前bash進程中執行。但如果將內置命令放在管道後,則此內置命令將和管道左邊的進程同屬於一個進程組,所以仍然會創建子進程。
說到這了,應該解釋下子shell,這個特殊的子進程。
一般fork出來的子進程,內容和父進程是一樣的,包括變數,例如執行cp命令時也能獲取到父進程的變數。但是cp命令是在哪裡執行的呢?在子shell中。執行cp命令敲入回車後,當前的bash進程fork出一個子bash,然後子bash通過exec載入cp程式替代子bash。請不要在此糾結子bash和子shell,如果搞不清它們的關係,就當它是同一種東西好了。
那是否可以這樣理解,所有命令其運行環境都是在子shell中呢?顯然,上面所說的bash內置命令不是在子shell中運行的。其他的所有方式,都是在子shell中完成,只不過方式不盡相同。完整的子shell參見man bash,在其中非常多的地方都提到了子shell。以下列出幾種常見的方式。
- (1).直接執行bash命令。這是一個很巧合的命令。bash命令本身是bash內置命令,在當前shell環境下執行內置命令本不會創建子shell,也就是說不會有獨立的bash進程出現,而實際結果則表現為新的bash是一個子進程。其中一個原因是執行bash命令會載入各種環境配置項,為了父bash的環境得到保護而不被覆蓋,所以應該讓其以子shell的方式存在。雖然fork出來的bash子進程內容完全繼承父shell,但因重新載入了環境配置項,所以子shell沒有繼承普通變數,更準確的說是覆蓋了從父shell中繼承的變數。不妨試試在/etc/bashrc文件中定義一個變數,再在父shell中導出名稱相同值卻不同的環境變數,然後到子shell中看看該變數的值為何?
- (2).執行shell腳本。因為腳本中第一行總是"#!/bin/bash"或者直接"bash xyz.sh",所以這和上面的執行bash進入子shell其實是一回事,都是使用bash命令進入子shell。只不過執行腳本多了一個動作:命令執行完畢後自動退出子shell。也因此執行腳本時,腳本中不會繼承父shell的環境變數。
- (3).非內置命令的命令替換。當命令中包含了命令替換部分時,將先執行這部分內容,如果這部分內容不是內置命令,將在子shell中完成,再將執行結果返回給當前命令。因為這次的子shell不是通過bash命令進入的子shell,所以它會繼承父shell的所有變數內容。這也就解釋了"$(echo $$)"中"$$"的結果是當前bash的pid號,而不是子shell的pid號,因為它不是使用bash命令進入的子shell。
還有兩種特殊的腳本調用方式:exec和source。
- exec:exec是載入程式替換當前進程,所以它不開啟子shell,而是直接在當前shell中執行命令或腳本,執行完exec後直接退出exec所在的shell。這就解釋了為何bash下執行cp命令時,cp執行完畢後會自動退出cp所在的子shell。
- source:source一般用來載入環境配置類腳本,無法直接載入命令。它也不會開啟子shell,直接在當前shell中執行調用腳本且執行腳本後不退出當前shell,所以腳本會繼承當前已有的變數,且腳本執行完畢後載入的環境變數會粘滯給當前shell,在當前shell生效。
9.2 job任務
大部分進程都能將其放入後臺,這時它就是一個後臺任務,所以常稱為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,則表示只針對當前任務即帶有"+"號的任務項。
9.3 終端和進程的關係
使用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足以。
另外,可能你已經發現了,很多進程是和終端無關的,也就是不依賴於終端,這類進程一般是內核類進程/線程以及daemon類進程,若它們也依賴於終端,則終端一被終止,這類進程也立即被終止,這是絕對不允許的。
9.4 信號
信號在操作系統中控制著進程的絕大多數動作,信號可以讓進程知道某個事件發生了,也指示著進程下一步要做出什麼動作。信號的來源可以是硬體信號(如按下鍵盤或其他硬體故障),也可以是軟體信號(如kill信號,還有內核發送的信號)。不過,很多可以感受到的信號都是從進程所在的控制終端發送出去的。
9.4.1 需知道的信號
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
只有SIGKILL和SIGSTOP這兩個信號是不可被捕捉且不可被忽略的信號,其他所有信號都可以通過trap或其他編程手段捕捉到或忽略掉。
更多更詳細的信號理解或說明,可以參考wiki的兩篇文章:
jobs控制機制:https://en.wikipedia.org/wiki/Job_control_(Unix)
信號說明:https://en.wikipedia.org/wiki/Unix_signal
9.4.2 SIGHUP
(1).當控制終端退出時,會向該終端中的進程發送sighup信號,因此該終端上行的shell進程、其他普通進程以及任務都會收到sighup而導致進程終止。
兩種方式可以改變因終端中斷發送sighup而導致子進程也被結束的行為:一是使用nohup命令啟動進程,它會忽略所有的sighup信號,使得該進程不會隨著終端退出而結束;二是使用disown,將任務列表中的任務移除出job table或者直接使用disown -h的功能設置其不接收終端發送的sighup信號。但不管是何種實現方式,終端退出後未被終止的進程將只能掛靠在init/systemd下。
(2).對於daemon類的程式(即服務性進程),這類程式不依賴於終端(它們的父進程都是Init或systemd),它們收到sighup信號時會重讀配置文件並重新打開日誌文件,使得服務程式可以不用重啟就可以載入配置文件。
9.4.3 僵屍進程和SIGCHLD
一個編程完善的程式,在子進程終止、退出的時候,會發送SIGCHLD信號給父進程,父進程收到信號就會通知內核清理該子進程相關信息。
在子進程死亡的那一剎那,子進程的狀態就是僵屍進程,但因為發出了SIGCHLD信號給父進程,父進程只要收到該信號,子進程就會被清理也就不再是僵屍進程。所以正常情況下,所有終止的進程都會有一小段時間處於僵屍態(發送SIGCHLD信號到父進程收到該信號之間),只不過這種僵屍進程存在時間極短(倒霉的僵屍),幾乎是不可被ps或top這類的程式捕捉到的。
如果在特殊情況下,子進程終止了,但父進程沒收到SIGCHLD信號,沒收到這信號的原因可能是多種的,不管如何,此時子進程已經成了永存的僵屍,能輕易的被ps或top捕捉到。僵屍不倒霉,人類就要倒霉,但是僵屍爸爸並不知道它兒子已經變成了僵屍,因為有僵屍爸爸的掩護,僵屍道長即內核見不到小僵屍,所以也沒法收屍。悲催的是,人類能力不足,直接發送信號(如kill)給僵屍進程是無效的,因為僵屍進程本就是終結了的進程,不占用任何運行資源,也收不到信號,只有內核從進程列表中將僵屍進程表項移除才能收屍。
要解決掉永存的僵屍有幾種方法:
(1).殺死僵屍進程的父進程。沒有了僵屍爸爸的掩護,小僵屍就暴露給了僵屍道長的直系弟子init/systemd,init/systemd會定期清理它下麵的各種僵屍進程。所以這種方法有點不講道理,僵屍爸爸是正常的啊,不過如果僵屍爸爸下麵有很多僵屍兒子,這僵屍爸爸肯定是有問題的,比如編程不完善,殺掉是應該的。
(2).手動發送SIGCHLD信號給僵屍進程的父進程。僵屍道長找不到僵屍,但被僵屍禍害的人類能發現僵屍,所以人類主動通知僵屍爸爸,讓僵屍爸爸知道自己的兒子死而不僵,然後通知內核來收屍。
當然,第二種手動發送SIGCHLD信號的方法要求父進程能收到信號,而SIGCHLD信號預設是被忽略的,所以應該顯式地在程式中加上獲取信號的代碼。也就是人類主動通知僵屍爸爸的時候,預設僵屍爸爸是不搭理人類的,所以要強制讓僵屍爸爸收到通知。不過一般daemon類的程式在編程上都是很完善的,發送SIGCHLD總是會收到,不用擔心。
9.4.4 手動發送信號(kill命令)
使用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...
9.4.5 pkill和killall
這兩個命令都可以直接指定進程名來發送信號,不指定信號時,預設信號都是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 :指定發送的信號,可以是數值也可以是字元代表的信號
在CentOS 7上,還有兩個好用的新功能選項。
-F, --pidfile file:匹配進程時,讀取進程的pid文件從中獲取進程的pid值。這樣就不用去寫獲取進程pid命令的匹配模式 -L, --logpidfile :如果"-F"選項讀取的pid文件未加鎖,則pkill或pgrep將匹配失敗。
例如踢出終端:
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每秒檢查一次該殺的進程是否還存在,只有不存在了才會給出退出狀態碼。 如果一個進程忽略了發送的信號、信號未產生效果、或者是僵屍進程將永久等待下去
9.5 fuser和lsof
fuser可以查看文件或目錄所屬進程的pid,即由此知道該文件或目錄被哪個進程使用。例如,umount的時候提示the device busy可以判斷出來哪個進程在使用。而lsof則反過來,它是通過進程來查看進程打開了哪些文件,但要註意的是,一切皆文件,包括普通文件、目錄、鏈接文件、塊設備、字元設備、套接字文件、管道文件,所以lsof出來的結果可能會非常多。
9.5.1 fuser
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
9.5.2 lsof
例如:
輸出信息中各列意義:
- COMMAND:進程的名稱
- PID:進程標識符
- USER:進程所有者
- FD:文件描述符,應用程式通過文件描述符識別該文件。如cwd、txt等
- TYPE:文件類型,如DIR、REG等
- DEVICE:指定磁碟的名稱
- SIZE/OFF:文件的大小或文件的偏移量(單位kb)(size and offset)
- NODE:索引節點(文件在磁碟上的標識)
- NAME:打開文件的確切名稱
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)
回到系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7048359.html
轉載請註明出處:http://www.cnblogs.com/f-ck-need-u/p/7058920.html