linux top進程狀態D


什么是D狀態

運行在KVM虛擬機里的一些進程突然出了問題,這些出了問題的進程無法用kill殺掉,使用ps可以看到這些進程處於D狀態:

[build@kbuild-john ~]$ ps -a -o pid,ppid,stat,command
 PID  PPID STAT COMMAND
17009     1 Ds   -bash
17065     1 D    ls --color=tty -al
17577     1 D    /usr/java/jdk1.5.0_17/bin/java -Xmx512m -classpath /usr/local/a
17629     1 D    /usr/java/jdk1.5.0_17/bin/java -Xmx512m -classpath /usr/local/a

ps 的手冊里說D狀態是uninterruptible sleep.

Linux進程有兩種睡眠狀態:

  1. 一種是interruptible sleep,處在這種睡眠狀態的進程是可以通過給它發信號來喚醒的,比如發HUP信號給nginx的master進程可以讓nginx重新加載配置文件而不需要重新啟動nginx進程;
  2. 另外一種睡眠狀態是uninterruptible sleep,處在這種狀態的進程不接受外來的任何信號,這也是為什么之前我無法用kill殺掉這些處於D狀態的進程,無論是”kill”, “kill -9″還是”kill -15″,因為它們壓根兒就不受這些信號的支配。

進程為什么會被置於uninterruptible sleep狀態呢?

處於uninterruptible sleep狀態的進程通常是在等待IO,比如磁盤IO,網絡IO,其他外設IO,如果進程正在等待的IO在較長的時間內都沒有響應,那么就很會不幸地被 ps看到了,同時也就意味着很有可能有IO出了問題,可能是外設本身出了故障,也可能是比如掛載的遠程文件系統已經不可訪問了,我這里遇到的問題就是由 down掉的NFS服務器引起的。

正是因為得不到IO的相應,進程才進入了uninterruptible sleep狀態,所以要想使進程從uninterruptible sleep狀態恢復,就得使進程等待的IO恢復,比如如果是因為從遠程掛載的NFS卷不可訪問導致進程進入uninterruptible sleep狀態的,那么可以通過恢復該NFS卷的連接來使進程的IO請求得到滿足,除此之外,要想干掉處在D狀態進程就只能重啟整個Linux系統了。

看到有人說如果要想殺掉D狀態的進程,通常可以去殺掉它的父進程(通常是shell,我理解的這種情況是在shell下直接運行的該進程,之后該進程轉入了D狀態),於是我就照做了,之后就出現了上面的狀態:他們的父進程被殺掉了,但是他們的父進程PID都變成了1,也就是init進程,這下可如何是好?此時我這些D狀態的進程已經影響到其他一些進程的運行,而已經無法訪問的NFS卷又在段時間內無法恢復,那么,只好重新啟動了,root不是玉皇大帝,也有無奈的時候。

跟czhang說起這個事,覺得Linux如果有這么一個專用的垃圾回收進程就好了:系統自動或者用戶手動把僵屍進程,和比如之前我遇到的D狀態進程的PPID設為這個垃圾回收進程,那么通過干掉這個垃圾回收進程來清理這些僵屍們,這樣該有多美好…

長期生活在 Linux 環境里,漸漸地就有一種環保意識油然而生。比如,我們會在登錄提示里寫上“悟空,我跟你說過叫你不要亂扔東西,亂扔東西是不對的。哎呀我話沒說完你怎么把棍子扔掉了?月光寶盒是寶物,亂扔它會污染環境,要是砸到小朋友怎么辦?就算砸不到小朋友,砸到了花花草草也不好嘛...”;在用戶缺省目錄里放一個題為 “自覺保護環境 請勿堆放垃圾”的空文件,並用 chattr +i 設為不可修改;看到垃圾文件就立即掃入 /tmp 目錄,然后發廣播通知垃圾制造者自己去 /tmp 認領,且警告其下不為例...我們深知,系統環境的整潔有利於系統管理員保持良好的心情、清晰的思路和穩定的工作狀態。

有一類垃圾卻並非這么容易打掃,那就是我們常見的狀態為D (Uninterruptible sleep),以及狀態為 Z (Zombie) 的垃圾進程。這些垃圾進程要么是求而不得,像怨婦一般等待資源(D),要么是僵而不死,像冤魂一樣等待超度(Z),它們在 CPU run_queue 里滯留不去,把 Load Average 弄的老高老高,沒看過我前一篇blog的國際友人還以為這兒民怨沸騰又出了什么大事呢。怎么辦?開槍!kill -9!看你們走是不走。但這兩種垃圾進程偏偏是刀槍不入的,不管換哪種槍法都殺不掉它們。無奈,只好reboot,像剿滅禽流感那樣不分青紅皂白地一律撲殺!

悟空,我們所運維的可是24*7全天候對外部客戶服務的系統,怎么能動不動就 reboot ?我們的考核指標可是4個9(99.99%,全年計划外當機時間不得超過52分鍾34秒),又不是4個8,你稍微遇到點事就reboot,還要不要可用性了?再說,現在社會都開始奔和諧去了,我們對於 D 和 Z 這兩種垃圾進程,也該盡可能采取慈悲手段,能解決其困難的,就創造條件,解決其實際困難,能消除其冤結的,就誦經燒紙,消除其前世冤結,具體問題應具體分析具體解決,濫殺無辜只會導致冤冤相報因果循環...

貧僧還是回來說正題。怨婦 D,往往是由於 I/O 資源得不到滿足,而引發等待,在內核源碼 fs/proc/array.c 里,其文字定義為“ "D (disk sleep)", /* 2 */ ”(由此可知 D 原是Disk的打頭字母),對應着 include/linux/sched.h 里的“ #define TASK_UNINTERRUPTIBLE 2 ”

舉個例子,當 NFS 服務端關閉之時,若未事先 umount 相關目錄,在 NFS 客戶端執行 df 就會掛住整個登錄會話,按Ctrl+C 、Ctrl+Z都無濟於事。斷開連接再登錄,執行 ps axf 則看到剛才的 df 進程狀態位已變成了 D ,kill -9 無法殺滅。正確的處理方式,是馬上恢復 NFS 服務端,再度提供服務,剛才掛起的 df 進程發現了其苦苦等待的資源,便完成任務,自動消亡。若 NFS 服務端無法恢復服務,在 reboot 之前也應將 /etc/mtab 里的相關 NFS mount 項刪除,以免 reboot 過程例行調用 netfs stop 時再次發生等待資源,導致系統重啟過程掛起。

冤魂 Z 之所以殺不死,是因為它已經死了,否則怎么叫 Zombie(僵屍)呢?冤魂不散,自然是生前有結未解之故。在UNIX/Linux中,每個進程都有一個父進程,進程號叫PID(Process ID),相應地,父進程號就叫PPID(Parent PID)。當進程死亡時,它會自動關閉已打開的文件,舍棄已占用的內存、交換空間等等系統資源,然后向其父進程返回一個退出狀態值,報告死訊。如果程序有 bug,就會在這最后一步出問題。兒子說我死了,老子卻沒聽見,沒有及時收棺入殮,兒子便成了僵屍。在UNIX/Linux中消滅僵屍的手段比較殘忍,執行 ps axjf 找出僵屍進程的父進程號(PPID,第一列),先殺其父,然后再由進程天子 init(其PID為1,PPID為0)來一起收拾父子僵屍,超度亡魂,往生極樂。注意,子進程變成僵屍只是礙眼而已,並不礙事,如果僵屍的父進程當前有要務在身,則千萬不可貿然殺之。

關於ZOMBIE進程

這些進程已經死亡,但沒有釋放系統資源,包括內存和一些一些系統表等,如果這樣的進程很多,會引發系統問題。用ps -el看出的進程狀態如果是Z,就是僵屍進程。
ps -ef|grep defunc可以找出僵屍進程.
有些ZOMBIE進程時用kill -9也不能殺死,而且消耗了很多系統資源不能釋放,如果系統在shutdown時發出信息:some process wouldn’t die. 這就意味這有些進程不能被reboot發出的kill –9殺掉,這些很可能就是僵屍進程。
可以用ps 的 – l 選項,得到更詳細的進程信息.
F(Flag):一系列數字的和,表示進程的當前狀態。這些數字的含義為:

00:若單獨顯示,表示此進程已被終止。 
01:進程是核心進程的一部分,常駐於系統主存。如:    sched、 vhand 、bdflush 等。 
02:Parent is tracing process. 
04 :Tracing parent's signal has stopped the process; the parent is waiting ( ptrace(S)). 
10:進程在優先級低於或等於25時,進入休眠狀態,而且不能用信號喚醒,例如在等待一個inode被創建時    
20:進程被裝入主存(primary memory) 
40:進程被鎖在主存,在事務完成前不能被置換   e 
S(state of? the process ) 
O:進程正在處理器運行  ms這個狀態從來木見過, 倒是R常見
S:休眠狀態(sleeping) 
R:等待運行(runable)    R Running or runnable (on run queue) 進程處於運行或就緒狀態
I:空閑狀態(idle) 
Z:僵屍狀態(zombie)    
T:跟蹤狀態(Traced) 
B:進程正在等待更多的內存頁 
D:不可中斷的深度睡眠,一般由IO引起,同步IO在做讀或寫操作時,cpu不能做其它事情,只能等待,這時進程處於這種狀態,如果程序采用異步IO,這種狀態應該就很少見到了
C(cpu usage):cpu利用率的估算值

清除ZOMBIE(僵屍)進程可以使用如下方法:

  1. kill –18 PPID (PPID是其父進程)

這個信號是告訴父進程,該子進程已經死亡了,請收回分配給他的資源。

  1. 如果不行則看能否終止其父進程(如果其父進程不需要的話)。先看其父進程又無其他子進程,如果有,可能需要先kill其他子進程,也就是兄弟進程。方法是:
    kill –15 PID1 PID2(PID1,PID2是僵屍進程的父進程的其它子進程)。
    然后再kill父進程:kill –15 PPID 這樣僵屍進程就可能被完全殺掉了。

如何殺掉D狀態的進程?

基本想法就是修改內核,遍歷進程列表,找到處於D狀態的進程,將其狀態轉換為別的狀態就可以kill掉了。
這是一種比較粗魯的方法,可能會引起一些不良后果,暫時沒有考慮。對於確切知道已經沒有什么用處,不用做清理工作的,處於D狀態怎么也殺不死的進程來說,確是很有效。
內核模塊代碼:

----------------killd.c----------------
#include 
#include 
#include  //for_each_process
MODULE_LICENSE("BSD");
static int pid = -1;
module_param(pid, int, S_IRUGO);
static int killd_init(void)
{
   struct task_struct * p;
   printk(KERN_ALERT "killd: force D status process to death\n");
   printk(KERN_ALERT "killd: pid=%d\n", pid);
   //read_lock(&tasklist_lock);
   for_each_process(p){
       if(p->pid == pid){
           printk("killd: found\n");
           set_task_state(p, TASK_STOPPED);
           printk(KERN_ALERT "killd: aha, dead already\n");
           return 0;
       }
   }
   printk("not found");
   //read_unlock(&tasklist_lock);
   return 0;
}
static void killd_exit(void)
{
   printk(KERN_ALERT "killd: bye\n");
}
module_init(killd_init);
module_exit(killd_exit);
-----Makefile------------
obj-m := killd.o

編譯模塊

make -C yourkerneltree M=`pwd` modules

插入模塊的時候提供D狀態的進程號,就可以將其轉換為stopped狀態,使用普通kill就可以殺死。

./insmod ./killd.ko pid=1234

如何看各個狀態的進程

進程級別

#ps axwf -eo pid,stat | grep D 

線程級別

ps -eL -eo pid,stat,pcpu   | grep D


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM