linux進程狀態

1)進程的狀態的概述:

1.1)Running(R),運行或將要運行
1.2)Interruptible(S),被阻斷而等待一個事件,可能會被一個信號激活
1.3)Uninterruptible(D),被阻斷而等待一個事件,不會被信號激活
1.4)Stopped(T),由於任務的控制或者外部的追蹤而被終止,比如:strace
1.5)Zombie(Z),僵死,但是它的父進程尚未調用wait函數.
1.6)Deal(X),這個永遠看不見

在內核源代碼中的定義如下:


=====================================================
/usr/src/linux/fs/proc/array.c


static const char *task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"T (tracing stop)", /* 8 */
"Z (zombie)", /* 16 */
"X (dead)" /* 32 */
};
=====================================================

在ps命令的幫助中定義如下:

PROCESS STATE CODES
Here are the different values that the s, stat and state output specifiers (header "STAT" or "S") will display to
describe the state of a process.
D Uninterruptible sleep (usually IO)
R Running or runnable (on run queue)
S Interruptible sleep (waiting for an event to complete)
T Stopped, either by a job control signal or because it is being traced.
W paging (not valid since the 2.6.xx kernel)
X dead (should never be seen)
Z Defunct ("zombie") process, terminated but not reaped by its parent.

For BSD formats and when the stat keyword is used, additional characters may be displayed:
< high-priority (not nice to other users)
N low-priority (nice to other users)
L has pages locked into memory (for real-time and custom IO)
s is a session leader
l is multi-threaded (using CLONE_THREAD, like NPTL pthreads do)
+ is in the foreground process group
======================================================

關於D和Z一段有趣的解釋:

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

怨婦 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)來一起收拾父子殭屍,超度亡魂,往生極樂。注意,子進程變成殭屍只是礙眼而已,並不礙事,如果殭屍的父進程當前有要務在身,則千萬不可貿然殺之。



2)分析不可被中斷的睡眠進程:


2.1)重現:

終端1)

vi test.c
#include <unistd.h>

void main() {
if (!vfork()) sleep(100);
}
gcc test.c -o test

./test

終端2)
ps aux|grep test
root 19884 0.0 0.0 3640 360 pts/0 D+ 16:38 0:00 ./test
root 19885 0.0 0.0 3640 360 pts/0 S+ 16:38 0:00 ./test


2.2)分析:
系統進入這種不可中斷是很少發生的,即使發生也是一個短暫的狀態,引起這種狀態的發生一般是驅動程序.
例如:驅動程序可能正在特殊的設備上等待通過檢測的響應,但又要保證自己不在可中斷睡眠狀態(S)被中斷.所以驅動程序會把進程切換到不可中斷的睡眠狀態,直到硬件已返回到已知狀態.


可以通過訪問一個慢設備來觀察不可中斷的睡眠狀態,比如CDROM這樣的設備
例如:
dd if=/dev/cdrom f=/dev/null &


進程在一個不可中斷的狀態是十分危險的,你不能用kill -9殺掉它
例如:
一個有問題的驅動程序訪問一個有問題的設備,設備不給驅動程序響應,驅動程序永遠得不到響應,而又永遠等待響應.

3)分析被跟蹤或被停止的進程狀態(T)


3.1)重現被跟蹤時的狀態:

終端1)
strace top

終端2)
ps auxf|grep top
root 980 9.4 0.0 1716 608 pts/0 S 00:31 0:12 | \_ strace top
root 981 3.7 0.1 10084 7076 pts/0 T 00:31 0:05 | \_ top

在用strace跟蹤top執行的時候,top進程爲T的狀態


3.2)重現被停止的進程狀態:

停止進程有三種手段:
3.2.1)發送SIGTSTP信停止進程.
-SIGTSTP的信號相當於CTRL+Z的組合鍵來終止正在前臺運行的進程.

終端1)
vi /etc/passwd

終端2)
kill -SIGTSTP 12029

查看進程狀態:
ps auxf

root 10297 0.0 1.0 5124 2696 pts/0 Ss+ Dec16 0:00 \_ -bash
root 12029 0.0 0.8 5348 2200 pts/0 T 05:15 0:00 | \_ vi test.c


終端1)
查看放到後臺的作業
jobs
[1]+ Stopped vi test.c

用fg將作業切換到前臺
fg


3.2.2)進程自已終止自己,標準輸入引起進程停止

一個終端利用常規的後臺和前臺進程管理進程,一個終端有且只有一個前臺進程,只有這個進程可以接受鍵盤的輸入,其它任何開始於終端的進程都被認爲是後臺進程,但是當後臺進程試圖從標準輸入讀取數據時,終端將發送一個SIGTTIN終端它,因爲這裏只有一個輸入設備鍵盤,只能用於前臺進程.
這裏的前後臺進程概念僅限於終端的範圍.

SIGTTIN 當後臺作業要從用戶終端讀數據時, 該作業中的所有進程會收到SIGTTIN 信號. 缺省時這些進程會停止執行.


終端1)

嘗試在後臺運行read命令,因爲後臺進程不能從終端獲取標準輸入,所以進程將會收到信號SIGTTIN,使進程進入停止狀態.
read x &
[1] 12057

[1]+ Stopped read x

終端2)
jobs
[1]+ Stopped read x
查看進程,12057這個PID就是read x&,現在看到是-bash,它的狀態已經是T了
ps aux
root 12057 0.0 0.5 5124 1476 pts/0 T 05:26 0:00 -bash

用SIGCONT來喚醒
kill -SIGCONT 12057

終端1)
輸入回車後,12057的進程依然會進入停止狀態,也就是阻塞,只有會放到前臺後,它才能完成輸入.
fg
read x
hello

3.2.3)進程自已終止自己,標準輸出引起進程停止

終端有一個tostop(終端輸出終止)設置,在大多數系統裏默認是關閉.
當是關閉的狀態時,後臺進程可以隨時在終端寫內容,如果是開啓狀態時,後臺進程向標準輸出寫數據時就會被終止.


開啓tostop
stty tostop

向標準輸出寫數據,被停止了
echo hello world &
[1] 12125

[1]+ Stopped echo hello world

jobs
[1]+ Stopped echo hello world

關閉tostop
stty -tostop

jobs
[1]+ Stopped echo hello world

向標準輸出寫數據恢復正常了
fg
echo hello world
hello world

4)分析進程的可中斷睡眠態與運行態



編寫一個小程序測試睡眠態與運行態之後的轉換 :
=====================================================
#include <stdio.h>
#include <math.h>
#include <unistd.h>
#include <stdlib.h>

void
run_status(void)
{
double pi = M_PI;
double pisqrt;
long i;

for (i=0; i<100000000; ++i){
pisqrt = sqrt(pi);
}
}

int
main (void)
{
run_status();
sleep(10);
run_status();

exit(EXIT_SUCCESS);
}
======================================================
編譯鏈接後:
gcc -Wall -o pisqrt a.c -lm


終端1)
監控pisqrt進程
watch -n 1 "ps aux|grep pisqrt|grep -v ps|awk '{print $2}'|sort -k 8"


終端2)
strace ./pisqrt
顯示如下:
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\200X\1"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1572440, ...}) = 0
old_mmap(NULL, 1279916, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0x49e000
old_mmap(0x5d1000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x132000) = 0x5d1000
old_mmap(0x5d4000, 10156, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x5d4000
close(3) = 0
set_thread_area({entry_number:-1 -> 6, base_addr:0xb75e3a60, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
munmap(0xb75e4000, 75579) = 0

此時切換到終端1看pisqrt進程狀態,此時爲R狀態:
root 3792 99.9 0.0 1516 268 pts/2 R 02:40 0:01 ./pisqrt
root 3801 0.0 0.0 3700 672 pts/1 S 02:40 0:00 grep pisqrt
root 3791 1.0 0.0 1728 564 pts/2 S 02:40 0:00 strace ./pisqr

之後pisqrt進程進入S狀態,因爲執行了sleep(10)函數,10秒之後pisqrt再次進入R狀態.最終退出.

分析:
pisqrt佔用CPU時間片時狀態爲R,而在調用sleep函數後爲S,系統大多數進程狀態都爲S,比如APACHE和ORACLE,
而處於S狀態不一定是調用了sleep函數,因爲IO也會讓進程處於睡眠態.
而我們可以啓動多個pisqrt程序,這時在系統中會有多個R狀態的進程.也就是說CPU個各數與R進程是沒有直接關聯的.


5)分析進程的僵死態(Z)

當一個進程退出時,它並不是完全消失,而是等到它的父進程發出wait系統調用纔會完全消失,除非父進程發出wait系統調用終止進程,
否則該進程將一直處於所謂的僵死狀態,等待它的父進程終止它.如果父進程終止了運行而沒有撤銷子進程,那麼這些進程將被進程init收養.
init進程定期調用wait來收養這些未被撤消的進程.

先製造一段殭屍程序,如下:
=============================
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int
main (){
if(!fork()){
printf("child pid=%d\n", getpid());
exit(5);
}
sleep(20);
printf("parent pid=%d\n", getpid());
exit(EXIT_SUCCESS);
}
===========================================
編譯
gcc -Wall defunct.c -o defunct

終端1:
watch -n 1 "ps auxf|grep defunct|grep -v ps|grep -v grep|awk '{print $2}'|sort -k 8"


終端2:
執行./defunct

查看終端1:
root 7280 0.0 0.0 1380 240 pts/2 S 03:05 0:00 | \_ ./defunct
root 7281 0.0 0.0 0 0 pts/2 Z 03:05 0:00 | \_ [defunct <defunct>]

20秒後查看終端2:
child pid=7281
parent pid=7280

關於信號集的描述:/usr/include/bits/signum.h

#define SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */
#define SIGCHLD 17 /* Child status has changed (POSIX). */

在上面程序的基礎上加入wait函數即可將SIGCHLD信號回收

修改後的程序如下:
=================================
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int
main (){
int status,i;
if(!fork()){
printf("child pid=%d\n", getpid());
exit(5);
}
wait(&status);
i = WEXITSTATUS(status);
sleep(20);
printf("parent pid=%d,child process exit/status=%d\n", getpid(),i);
exit(EXIT_SUCCESS);
}
==========================================


6)最後的進程X (dead)
指死掉的進程

最後4種附加的狀態....
W狀態:不駐留內存
<狀態:nice小於0
N狀態:nice大於0
L狀態:有鎖住的頁面

這部分在ps的源代碼(output.c)有描述:
===================================
static int
pr_stat(void)
{
int end = 0;
outbuf[end++] = pp->state;
if (pp->rss == 0 && pp->state != 'Z')
outbuf[end++] = 'W';
if (pp->nice < 0)
outbuf[end++] = '<';
if (pp->nice > 0)
outbuf[end++] = 'N';
if (pp->vm_lock)
outbuf[end++] = 'L';
outbuf[end] = '\0';
return end;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章