linux(一)進程的狀態(tài)

進程的狀態(tài)

一、linux的進程查看 一般使用 ps -aux 來看
  1. linux的進程有幾個關(guān)鍵的信息
  • pid: 進程的id
  • ppid:該進程的父進程id
  • stat:狀態(tài) —— R、S、D、T、Z
  • command:產(chǎn)生該進程的命令
二、linux的進程狀態(tài)
  1. 主要狀態(tài)為STAT列的值的第一個字母
  • R(Running):該進程正在運行
  • S(Sleep):正在睡眠等待喚醒的進程
  • D:不可中斷的睡眠,一般都是等待IO
  • T(Stopped):停止狀態(tài),可能是后臺暫停或者traced狀態(tài)
  • Z(Zombie):僵尸狀態(tài),進程已經(jīng)結(jié)束了,但是刪不掉
  • I (idle):空閑狀態(tài)
  1. 附加狀態(tài)信息
  • <:high-priority (not nice to other users) —— 高優(yōu)先級
  • N:low-priority(nice to other users)—— 低優(yōu)先級
  • L:has pages locked into memory (for real-time and custom IO) —— 正在運行
  • s:is a session leader —— 某個session的管理進程
  • l:is multi-threaded (using CLONE_THREAD, like NPTL pthreads do) —— 多線程
  • +: is in the foreground process group —— 處于前臺進程組
image.png
三、linux進程狀態(tài) 詳解
  1. R (task_running) : 可執(zhí)行狀態(tài)
  • 只有在該狀態(tài)的進程才可能在CPU上運行。而同一時刻可能有多個進程處于可執(zhí)行狀態(tài),這些進程的task_struct結(jié)構(gòu)(進程控制塊)被放入對應(yīng)CPU的可執(zhí)行隊列中(一個進程最多只能出現(xiàn)在一個CPU的可執(zhí)行隊列中)。進程調(diào)度器的任務(wù)就是從各個CPU的可執(zhí)行隊列中分別選擇一個進程在該CPU上運行。
  • 很多操作系統(tǒng)教科書將正在CPU上執(zhí)行的進程定義為RUNNING狀態(tài)、而將可執(zhí)行但是尚未被調(diào)度執(zhí)行的進程定義為READY狀態(tài),這兩種狀態(tài)在linux下統(tǒng)一為TASK_RUNNING狀態(tài)。
  1. S (task_interruptible): 可中斷的睡眠狀態(tài)
  • 處于這個狀態(tài)的進程因為等待某某事件的發(fā)生(比如等待socket連接、等待信號量),而被掛起。這些進程的task_struct結(jié)構(gòu)被放入對應(yīng)事件的等待隊列中。當這些事件發(fā)生時(由外部中斷觸發(fā)、或由其他進程觸發(fā)),對應(yīng)的等待隊列中的一個或多個進程將被喚醒。
  • 通過ps命令我們會看到,一般情況下,進程列表中的絕大多數(shù)進程都處于task_interruptible狀態(tài)(除非機器的負載很高)。畢竟CPU就這么一兩個,進程動輒幾十上百個,如果不是絕大多數(shù)進程都在睡眠,CPU又怎么響應(yīng)得過來。
  1. D (task_uninterruptible): 不可中斷的睡眠狀態(tài)
  • 與task_interruptible狀態(tài)類似,進程處于睡眠狀態(tài),但是此刻進程是不可中斷的。不可中斷,指的并不是CPU不響應(yīng)外部硬件的中斷,而是指進程不響應(yīng)異步信號。
  • 絕大多數(shù)情況下,進程處在睡眠狀態(tài)時,總是應(yīng)該能夠響應(yīng)異步信號的。但是uninterruptible sleep 狀態(tài)的進程不接受外來的任何信號,因此無法用kill殺掉這些處于D狀態(tài)的進程,無論是”kill”, “kill -9″還是”kill -15″,這種情況下,一個可選的方法就是reboot。
  • 處于uninterruptible sleep狀態(tài)的進程通常是在等待IO,比如磁盤IO,網(wǎng)絡(luò)IO,其他外設(shè)IO,如果進程正在等待的IO在較長的時間內(nèi)都沒有響應(yīng),那么就被ps看到了,同時也就意味著很有可能有IO出了問題,可能是外設(shè)本身出了故障,也可能是比如掛載的遠程文件系統(tǒng)已經(jīng)不可訪問了.
  • 而task_uninterruptible狀態(tài)存在的意義就在于,內(nèi)核的某些處理流程是不能被打斷的。如果響應(yīng)異步信號,程序的執(zhí)行流程中就會被插入一段用于處理異步信號的流程(這個插入的流程可能只存在于內(nèi)核態(tài),也可能延伸到用戶態(tài)),于是原有的流程就被中斷了。
  • 在進程對某些硬件進行操作時(比如進程調(diào)用read系統(tǒng)調(diào)用對某個設(shè)備文件進行讀操作,而read系統(tǒng)調(diào)用最終執(zhí)行到對應(yīng)設(shè)備驅(qū)動的代碼,并與對應(yīng)的物理設(shè)備進行交互),可能需要使用task_uninterruptible狀態(tài)對進程進行保護,以避免進程與設(shè)備交互的過程被打斷,造成設(shè)備陷入不可控的狀態(tài)。這種情況下的task_uninterruptible狀態(tài)總是非常短暫的,通過ps命令基本上不可能捕捉到。
  • 我們通過vmstat 命令中procs下的b 可以來查看是否有處于uninterruptible 狀態(tài)的進程。 該命令只能顯示數(shù)量。
  • In computer operating systems terminology, a sleeping process can either be interruptible (woken via signals) or uninterruptible (woken explicitly). An uninterruptible sleep state is a sleep state that cannot handle a signal (such as waiting for disk or network IO (input/output)).
    When the process is sleeping uninterruptibly, the signal will be noticed when the process returns from the system call or trap.
    -- 這句是關(guān)鍵。 當處于uninterruptibly sleep 狀態(tài)時,只有當進程從system 調(diào)用返回時,才通知signal。
  • A process which ends up in “D” state for any measurable length of time is trapped in the midst of a system call (usually an I/O operation on a device — thus the initial in the ps output).
    Such a process cannot be killed — it would risk leaving the kernel in an inconsistent state, leading to a panic. In general you can consider this to be a bug in the device driver that the process is accessing.
  1. T(task_stopped or task_traced):暫停狀態(tài)或跟蹤狀態(tài):
  • 向進程發(fā)送一個sigstop信號,它就會因響應(yīng)該信號而進入task_stopped狀態(tài)(除非該進程本身處于task_uninterruptible狀態(tài)而不響應(yīng)信號)。(sigstop與sigkill信號一樣,是非常強制的。不允許用戶進程通過signal系列的系統(tǒng)調(diào)用重新設(shè)置對應(yīng)的信號處理函數(shù)。)
  • 向進程發(fā)送一個sigcont信號,可以讓其從task_stopped狀態(tài)恢復到task_running狀態(tài)。
  • 當進程正在被跟蹤時,它處于task_traced這個特殊的狀態(tài)?!罢诒桓櫋敝傅氖沁M程暫停下來,等待跟蹤它的進程對它進行操作。比如在gdb中對被跟蹤的進程下一個斷點,進程在斷點處停下來的時候就處于task_traced狀態(tài)。而在其他時候,被跟蹤的進程還是處于前面提到的那些狀態(tài)。
  • 對于進程本身來說,task_stopped和task_traced狀態(tài)很類似,都是表示進程暫停下來。
  • 而task_traced狀態(tài)相當于在task_stopped之上多了一層保護,處于task_traced狀態(tài)的進程不能響應(yīng)sigcont信號而被喚醒。只能等到調(diào)試進程通過ptrace系統(tǒng)調(diào)用執(zhí)行ptrace_cont、ptrace_detach等操作(通過ptrace系統(tǒng)調(diào)用的參數(shù)指定操作),或調(diào)試進程退出,被調(diào)試的進程才能恢復task_running狀態(tài)。
  1. Z (task_dead - exit_zombie):退出狀態(tài),進程成為僵尸進程:
  • 在Linux進程的狀態(tài)中,僵尸進程是非常特殊的一種,它是已經(jīng)結(jié)束了的進程,但是沒有從進程表中刪除。太多了會導致進程表里面條目滿了,進而導致系統(tǒng)崩潰,倒是不占用其他系統(tǒng)資源。
  • 它已經(jīng)放棄了幾乎所有內(nèi)存空間,沒有任何可執(zhí)行代碼,也不能被調(diào)度,僅僅在進程列表中保留一個位置,記載該進程的退出狀態(tài)等信息供其他進程收集,除此之外,僵尸進程不再占有任何內(nèi)存空間。
  • 進程在退出的過程中,處于TASK_DEAD狀態(tài)。在這個退出過程中,進程占有的所有資源將被回收,除了task_struct結(jié)構(gòu)(以及少數(shù)資源)以外。于是進程就只剩下task_struct這么個空殼,故稱為僵尸。
  • 之所以保留task_struct,是因為task_struct里面保存了進程的退出碼、以及一些統(tǒng)計信息。而其父進程很可能會關(guān)心這些信息。比如在shell中,$?變量就保存了最后一個退出的前臺進程的退出碼,而這個退出碼往往被作為if語句的判斷條件。
  • 當然,內(nèi)核也可以將這些信息保存在別的地方,而將task_struct結(jié)構(gòu)釋放掉,以節(jié)省一些空間。但是使用task_struct結(jié)構(gòu)更為方便,因為在內(nèi)核中已經(jīng)建立了從pid到task_struct查找關(guān)系,還有進程間的父子關(guān)系。釋放掉task_struct,則需要建立一些新的數(shù)據(jù)結(jié)構(gòu),以便讓父進程找到它的子進程的退出信息。
  • 子進程在退出的過程中,內(nèi)核會給其父進程發(fā)送一個信號,通知父進程來“收尸”。 父進程可以通過wait系列的系統(tǒng)調(diào)用(如wait4、waitid)來等待某個或某些子進程的退出,并獲取它的退出信息。然后wait系列的系統(tǒng)調(diào)用會順便將子進程的尸體(task_struct)也釋放掉。
  • 這個信號默認是SIGCHLD,但是在通過clone系統(tǒng)調(diào)用創(chuàng)建子進程時,可以設(shè)置這個信號。
  • 如果他的父進程沒安裝SIGCHLD信號處理函數(shù)調(diào)用wait或waitpid()等待子進程結(jié)束,又沒有顯式忽略該信號,那么它就一直保持僵尸狀態(tài),子進程的尸體(task_struct)也就無法釋放掉。
  • 如果這時父進程結(jié)束了,那么init進程自動會接手這個子進程,為它收尸,它還是能被清除的。但是如果如果父進程是一個循環(huán),不會結(jié)束,那么子進程就會一直保持僵尸狀態(tài),這就是為什么系統(tǒng)中有時會有很多的僵尸進程。
  • 當進程退出的時候,會將它的所有子進程都托管給別的進程(使之成為別的進程的子進程)。托管的進程可能是退出進程所在進程組的下一個進程(如果存在的話),或者是1號進程。所以每個進程、每時每刻都有父進程存在。除非它是1號進程。1號進程,pid為1的進程,又稱init進程。
linux系統(tǒng)啟動后,第一個被創(chuàng)建的用戶態(tài)進程就是init進程。它有兩項使命:
1、執(zhí)行系統(tǒng)初始化腳本,創(chuàng)建一系列的進程(它們都是init進程的子孫);
2、在一個死循環(huán)中等待其子進程的退出事件,并調(diào)用waitid系統(tǒng)調(diào)用來完成“收尸”工作;

init進程不會被暫停、也不會被殺死(這是由內(nèi)核來保證的)。它在等待子進程退出的過程中處于task_interruptible狀態(tài),“收尸”過程中則處于task_running狀態(tài)。

5.1 Unix/Linux 處理僵尸進程的方法:

  • 找出父進程號,然后kill 父進程,之后子進程(僵尸進程)會被托管到其他進程,如init進程,然后由init進程將子進程的尸體(task_struct)釋放掉。
除了通過ps 的狀態(tài)來查看Zombi進程,還可以用如下命令查看:

[oracle@rac1 ~]$ ps -ef|grep defun
oracle   13526 12825  0 16:48 pts/1    00:00:00 grep defun
oracle   28330 28275  0 May18 ?        00:00:00 [Xsession] <defunct>

5.2 僵尸進程解決辦法:

(1)改寫父進程,在子進程死后要為它收尸。
  具體做法是接管SIGCHLD信號。子進程死后,會發(fā)送SIGCHLD信號給父進程,父進程收到此信號后,執(zhí)行 waitpid()函數(shù)為子進程收尸。這是基于這樣的原理:就算父進程沒有調(diào)用wait,內(nèi)核也會向它發(fā)送SIGCHLD消息,盡管對的默認處理是忽略,如果想響應(yīng)這個消息,可以設(shè)置一個處理函數(shù)。

(2)把父進程殺掉。
   父進程死后,僵尸進程成為"孤兒進程",過繼給1號進程init,init始終會負責清理僵尸進程.它產(chǎn)生的所有僵尸進程也跟著消失。如:

       kill -9 `ps -ef | grep "Process Name" | awk '{ print $3 }'`
       其中,“Process Name”為處于zombie狀態(tài)的進程名。

(3)殺父進程不行的話,就嘗試用skill -t TTY關(guān)閉相應(yīng)終端,TTY是進程相應(yīng)的tty號(終端號)。但是,ps可能會查不到特定進程的tty號,這時就需要自己判斷了。
(4)重啟系統(tǒng),這也是最常用到方法之一。
  1. X (task_dead - exit_dead):退出狀態(tài),進程即將被銷毀
  • 進程在退出過程中也可能不會保留它的task_struct。比如這個進程是多線程程序中被detach過的進程。或者父進程通過設(shè)置sigchld信號的handler為sig_ign,顯式的忽略了sigchld信號。(這是posix的規(guī)定,盡管子進程的退出信號可以被設(shè)置為sigchld以外的其他信號。)
  • 此時,進程將被置于exit_dead退出狀態(tài),這意味著接下來的代碼立即就會將該進程徹底釋放。所以exit_dead狀態(tài)是非常短暫的,幾乎不可能通過ps命令捕捉到。
四、進程狀態(tài)在linux中的轉(zhuǎn)變說明
  1. 進程的初始狀態(tài):
  • 進程是通過fork系列的系統(tǒng)調(diào)用(fork、clone、vfork)來創(chuàng)建的,內(nèi)核(或內(nèi)核模塊)也可以通過kernel_thread函數(shù)創(chuàng)建內(nèi)核進程。這些創(chuàng)建子進程的函數(shù)本質(zhì)上都完成了相同的功能——將調(diào)用進程復制一份,得到子進程。(可以通過選項參數(shù)來決定各種資源是共享、還是私有。)
  • 那么既然調(diào)用進程處于task_running狀態(tài)(否則,它若不是正在運行,又怎么進行調(diào)用?),則子進程默認也處于task_running狀態(tài)。
  • 另外,在系統(tǒng)調(diào)用調(diào)用clone和內(nèi)核函數(shù)kernel_thread也接受clone_stopped選項,從而將子進程的初始狀態(tài)置為 task_stopped。
  1. 進程狀態(tài)變遷:
  • 進程自創(chuàng)建以后,狀態(tài)可能發(fā)生一系列的變化,直到進程退出。而盡管進程狀態(tài)有好幾種,但是進程狀態(tài)的變遷卻只有兩個方向——從task_running狀態(tài)變?yōu)榉莟ask_running狀態(tài)、或者從非task_running狀態(tài)變?yōu)閠ask_running狀態(tài)。
  • 也就是說,如果給一個task_interruptible狀態(tài)的進程發(fā)送sigkill信號,這個進程將先被喚醒(進入task_running狀態(tài)),然后再響應(yīng)sigkill信號而退出(變?yōu)閠ask_dead狀態(tài))。并不會從task_interruptible狀態(tài)直接退出。
  • 進程從非task_running狀態(tài)變?yōu)閠ask_running狀態(tài),是由別的進程(也可能是中斷處理程序)執(zhí)行喚醒操作來實現(xiàn)的。執(zhí)行喚醒的進程設(shè)置被喚醒進程的狀態(tài)為task_running,然后將其task_struct結(jié)構(gòu)加入到某個cpu的可執(zhí)行隊列中。于是被喚醒的進程將有機會被調(diào)度執(zhí)行。
而進程從task_running狀態(tài)變?yōu)榉莟ask_running狀態(tài),則有兩種途徑:

       1、響應(yīng)信號而進入task_stoped狀態(tài)、或task_dead狀態(tài);
       2、執(zhí)行系統(tǒng)調(diào)用主動進入task_interruptible狀態(tài)(如nanosleep系統(tǒng)調(diào)用)、或task_dead狀態(tài)(如exit系統(tǒng)調(diào)用);或由于執(zhí)行系統(tǒng)調(diào)用需要的資源得不到滿足,而進入task_interruptible狀態(tài)或task_uninterruptible狀態(tài)(如select系統(tǒng)調(diào)用)。

顯然,這兩種情況都只能發(fā)生在進程正在cpu上執(zhí)行的情況下。
附:原文地址
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容