tft每日頭條

 > 生活

 > linux的進程詳解

linux的進程詳解

生活 更新时间:2024-07-29 23:06:58

linux的進程詳解?Linux進程狀态:R (TASK_RUNNING),可執行狀态,下面我們就來說一說關于linux的進程詳解?我們一起去了解并探讨一下這個問題吧!

linux的進程詳解(Linux中的進程狀态)1

linux的進程詳解

linux是一個多用戶,多任務的系統,可以同時運行多個用戶的多個程序,就必然會産生很多的進程,而每個進程會有不同的狀态。

Linux進程狀态:R (TASK_RUNNING),可執行狀态。

隻有在該狀态的進程才可能在CPU上運行。而同一時刻可能有多個進程處于可執行狀态,這些進程的task_struct結構(進程控制塊)被放入對應CPU的可執行隊列中(一個進程最多隻能出現在一個CPU的可執行隊列中)。進程調度器的任務就是從各個CPU的可執行隊列中分别選擇一個進程在該CPU上運行。

很多操作系統教科書将正在CPU上執行的進程定義為RUNNING狀态、而将可執行但是尚未被調度執行的進程定義為READY狀态,這兩種狀态在linux下統一為 TASK_RUNNING狀态。

Linux進程狀态:S (TASK_INTERRUPTIBLE),可中斷的睡眠狀态。

處于這個狀态的進程因為等待某某事件的發生(比如等待socket連接、等待信号量),而被挂起。這些進程的task_struct結構被放入對應事件的等待隊列中。當這些事件發生時(由外部中斷觸發、或由其他進程觸發),對應的等待隊列中的一個或多個進程将被喚醒。

通過ps命令我們會看到,一般情況下,進程列表中的絕大多數進程都處于TASK_INTERRUPTIBLE狀态(除非機器的負載很高)。畢竟CPU就這麼一兩個,進程動辄幾十上百個,如果不是絕大多數進程都在睡眠,CPU又怎麼響應得過來。

Linux進程狀态:D (TASK_UNINTERRUPTIBLE),不可中斷的睡眠狀态。

與TASK_INTERRUPTIBLE狀态類似,進程處于睡眠狀态,但是此刻進程是不可中斷的。不可中斷,指的并不是CPU不響應外部硬件的中斷,而是指進程不響應異步信号。絕大多數情況下,進程處在睡眠狀态時,總是應該能夠響應異步信号的。否則你将驚奇的發現,kill -9竟然殺不死一個正在睡眠的進程了!于是我們也很好理解,為什麼ps命令看到的進程幾乎不會出現TASK_UNINTERRUPTIBLE狀态,而總是TASK_INTERRUPTIBLE狀态。

而TASK_UNINTERRUPTIBLE狀态存在的意義就在于,内核的某些處理流程是不能被打斷的。如果響應異步信号,程序的執行流程中就會被插入一段用于處理異步信号的流程(這個插入的流程可能隻存在于内核态,也可能延伸到用戶态),于是原有的流程就被中斷了。(參見《linux内核異步中斷淺析》)在進程對某些硬件進行操作時(比如進程調用read系統調用對某個設備文件進行讀操作,而read系統調用最終執行到對應設備驅動的代碼,并與對應的物理設備進行交互),可能需要使用TASK_UNINTERRUPTIBLE狀态對進程進行保護,以避免進程與設備交互的過程被打斷,造成設備陷入不可控的狀态。這種情況下的TASK_UNINTERRUPTIBLE狀态總是非常短暫的,通過ps命令基本上不可能捕捉到。

linux系統中也存在容易捕捉的TASK_UNINTERRUPTIBLE狀态。執行vfork系統調用後,父進程将進入TASK_UNINTERRUPTIBLE狀态,直到子進程調用exit或exec(參見《神奇的vfork》)。通過下面的代碼就能得到處于TASK_UNINTERRUPTIBLE狀态的進程:

#include<unistd.h> void main() { if (!vfork()) sleep(100); }

編譯運行,然後ps一下:

:~/test$ ps -ax | grep a\.out 4371 pts/0 D 0:00 ./a.out 4372 pts/0 S 0:00 ./a.out 4374 pts/1 S 0:00 grep a.out

然後我們可以試驗一下TASK_UNINTERRUPTIBLE狀态的威力。不管kill還是kill -9,這個TASK_UNINTERRUPTIBLE狀态的父進程依然屹立不倒。

Linux進程狀态:T (TASK_STOPPED or TASK_TRACED),暫停狀态或跟蹤狀态。

向進程發送一個SIGSTOP信号,它就會因響應該信号而進入TASK_STOPPED狀态(除非該進程本身處于TASK_UNINTERRUPTIBLE狀态而不響應信号)。(SIGSTOP與SIGKILL信号一樣,是非常強制的。不允許用戶進程通過signal系列的系統調用重新設置對應的信号處理函數。)向進程發送一個SIGCONT信号,可以讓其從TASK_STOPPED狀态恢複到TASK_RUNNING狀态。

當進程正在被跟蹤時,它處于TASK_TRACED這個特殊的狀态。“正在被跟蹤”指的是進程暫停下來,等待跟蹤它的進程對它進行操作。比如在gdb中對被跟蹤的進程下一個斷點,進程在斷點處停下來的時候就處于TASK_TRACED狀态。而在其他時候,被跟蹤的進程還是處于前面提到的那些狀态。

對于進程本身來說,TASK_STOPPED和TASK_TRACED狀态很類似,都是表示進程暫停下來。而TASK_TRACED狀态相當于在TASK_STOPPED之上多了一層保護,處于TASK_TRACED狀态的進程不能響應SIGCONT信号而被喚醒。隻能等到調試進程通過ptrace系統調用執行PTRACE_CONT、PTRACE_DETACH等操作(通過ptrace系統調用的參數指定操作),或調試進程退出,被調試的進程才能恢複TASK_RUNNING狀态。

Linux進程狀态:Z (TASK_DEAD - EXIT_ZOMBIE),退出狀态,進程成為僵屍進程。

進程在退出的過程中,處于TASK_DEAD狀态。

在這個退出過程中,進程占有的所有資源将被回收,除了task_struct結構(以及少數資源)以外。于是進程就隻剩下task_struct這麼個空殼,故稱為僵屍。之所以保留task_struct,是因為task_struct裡面保存了進程的退出碼、以及一些統計信息。而其父進程很可能會關心這些信息。比如在shell中,$?變量就保存了最後一個退出的前台進程的退出碼,而這個退出碼往往被作為if語句的判斷條件。當然,内核也可以将這些信息保存在别的地方,而将task_struct結構釋放掉,以節省一些空間。但是使用task_struct結構更為方便,因為在内核中已經建立了從pid到task_struct查找關系,還有進程間的父子關系。釋放掉task_struct,則需要建立一些新的數據結構,以便讓父進程找到它的子進程的退出信息。

父進程可以通過wait系列的系統調用(如wait4、waitid)來等待某個或某些子進程的退出,并獲取它的退出信息。然後wait系列的系統調用會順便将子進程的屍體(task_struct)也釋放掉。子進程在退出的過程中,内核會給其父進程發送一個信号,通知父進程來“收屍”。這個信号默認是SIGCHLD,但是在通過clone系統調用創建子進程時,可以設置這個信号。

通過下面的代碼能夠制造一個EXIT_ZOMBIE狀态的進程:

#include void main() { if (fork()) while(1) sleep(100); }

編譯運行,然後ps一下:

~/test$ ps -ax | grep a\.out 10410 pts/0 S 0:00 ./a.out 10411 pts/0 Z 0:00 [a.out] 10413 pts/1 S 0:00 grep a.out

隻要父進程不退出,這個僵屍狀态的子進程就一直存在。那麼如果父進程退出了呢,誰又來給子進程“收屍”?當進程退出的時候,會将它的所有子進程都托管給别的進程(使之成為别的進程的子進程)。托管給誰呢?可能是退出進程所在進程組的下一個進程(如果存在的話),或者是1号進程。所以每個進程、每時每刻都有父進程存在。除非它是1号進程。

1号進程,pid為1的進程,又稱init進程。linux系統啟動後,第一個被創建的用戶态進程就是init進程。它有兩項使命:1、執行系統初始化腳本,創建一系列的進程(它們都是init進程的子孫);2、在一個死循環中等待其子進程的退出事件,并調用waitid系統調用來完成“收屍”工作;init進程不會被暫停、也不會被殺死(這是由内核來保證的)。它在等待子進程退出的過程中處于TASK_INTERRUPTIBLE狀态,“收屍”過程中則處于TASK_RUNNING狀态。

Linux進程狀态:X (TASK_DEAD - EXIT_DEAD),退出狀态,進程即将被銷毀。

而進程在退出過程中也可能不會保留它的task_struct。比如這個進程是多線程程序中被detach過的進程(進程?線程?參見《linux線程淺析》)。或者父進程通過設置SIGCHLD信号的handler為SIG_IGN,顯式的忽略了SIGCHLD信号。(這是posix的規定,盡管子進程的退出信号可以被設置為SIGCHLD以外的其他信号。)此時,進程将被置于EXIT_DEAD退出狀态,這意味着接下來的代碼立即就會将該進程徹底釋放。所以EXIT_DEAD狀态是非常短暫的,幾乎不可能通過ps命令捕捉到。

進程的初始狀态

進程是通過fork系列的系統調用(fork、clone、vfork)來創建的,内核(或内核模塊)也可以通過kernel_thread函數創建内核進程。這些創建子進程的函數本質上都完成了相同的功能——将調用進程複制一份,得到子進程。(可以通過選項參數來決定各種資源是共享、還是私有。)那麼既然調用進程處于TASK_RUNNING狀态(否則,它若不是正在運行,又怎麼進行調用?),則子進程默認也處于TASK_RUNNING狀态。另外,在系統調用調用clone和内核函數kernel_thread也接受CLONE_STOPPED選項,從而将子進程的初始狀态置為 TASK_STOPPED。

進程狀态變遷

進程自創建以後,狀态可能發生一系列的變化,直到進程退出。而盡管進程狀态有好幾種,但是進程狀态的變遷卻隻有兩個方向——從TASK_RUNNING狀态變為非TASK_RUNNING狀态、或者從非TASK_RUNNING狀态變為TASK_RUNNING狀态。也就是說,如果給一個TASK_INTERRUPTIBLE狀态的進程發送SIGKILL信号,這個進程将先被喚醒(進入TASK_RUNNING狀态),然後再響應SIGKILL信号而退出(變為TASK_DEAD狀态)。并不會從TASK_INTERRUPTIBLE狀态直接退出。

進程從非TASK_RUNNING狀态變為TASK_RUNNING狀态,是由别的進程(也可能是中斷處理程序)執行喚醒操作來實現的。執行喚醒的進程設置被喚醒進程的狀态為TASK_RUNNING,然後将其task_struct結構加入到某個CPU的可執行隊列中。于是被喚醒的進程将有機會被調度執行。

而進程從TASK_RUNNING狀态變為非TASK_RUNNING狀态,則有兩種途徑:1、響應信号而進入TASK_STOPED狀态、或TASK_DEAD狀态;2、執行系統調用主動進入TASK_INTERRUPTIBLE狀态(如nanosleep系統調用)、或TASK_DEAD狀态(如exit系統調用);或由于執行系統調用需要的資源得不到滿足,而進入TASK_INTERRUPTIBLE狀态或TASK_UNINTERRUPTIBLE狀态(如select系統調用)。顯然,這兩種情況都隻能發生在進程正在CPU上執行的情況下。

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关生活资讯推荐

热门生活资讯推荐

网友关注

Copyright 2023-2024 - www.tftnews.com All Rights Reserved