天天看點

[ Linux ] Linux程序狀态

Linux核心源代碼中,程序的狀态是用數字來表示的,為了弄明白正在運作的程序是什麼意思,我們需要知道程序的不同狀态。一個程序可以有幾個狀态(在Linux核心裡面,程序有時候也叫任務)

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};      

下面這張圖檔展示了程序狀态之間的關系,現在我們完全不知道這是什麼東西!我們接下來逐一分析。

[ Linux ] Linux程式狀态

下面是程序的幾種狀态,接下來我們将逐一介紹

  • R運作狀态(running): 并不意味着程序一定在運作中,它表明程序要麼是在運作中要麼在運作隊列裡。
  • S睡眠狀态(sleeping): 意味着程序在等待事件完成(這裡的睡眠有時候也叫做可中斷睡(interruptible sleep))。
  • D磁盤休眠狀态(Disk sleep)有時候也叫不可中斷睡眠狀态(uninterruptible sleep),在這個狀态的程序通常會等待IO的結束。
  • T停止狀态(stopped): 可以通過發送 SIGSTOP 信号給程序來停止(T)程序。這個被暫停的程序可以通過發送 SIGCONT 信号讓程序繼續運作。
  • X死亡狀态(dead):這個狀态隻是一個傳回狀态,你不會在任務清單裡看到這個狀态。

1.程序各狀态的概念

1.1 R-運作态(running)

  1. 運作态是程序正在CPU上運作,還是程序隻要在運作隊列中就叫做運作态呢?

答:所謂的運作态,是隻要在運作隊列中就叫做運作态,代表我已經準備好了,随時可以被排程!

[ Linux ] Linux程式狀态

1.2 終止狀态(stopped)

  1. 終止狀态是這個程序已經被釋放,就叫做終止态?還是該程序還在,隻不過永遠不運作了,随時等待被釋放?

答:程序還在,隻不過不運作。

  1. 此時,我們想問,程序都終止了,為什麼不立馬釋放對應的資源嗎,而要維護一個終止态??

答:我們要釋放程序需要花費時間,有沒有可能,目前作業系統很忙呢。

1.3 運作阻塞

  1. 一個程序使用資源的時候,可不僅僅是在申請CPU資源!‘
  2. 程序可能申請更多的其他資源:磁盤,網卡,顯示卡,顯示器資源,聲霸卡/音響

如果我們申請CPU資源,暫時無法得到滿足,需要排隊的--運作隊列

那麼如果我們申請其他慢裝置的資源呢? --- 也是需要排隊的!(tast_struct在程序排隊)

當程序通路某些資源(磁盤,網卡),該資源如果暫時沒有準備好,或者正在給其他程序提供服務,此時:1.目前程序要從runqueue中移除;目前程序放入對應裝置的描述結構體中的等待隊列!(wait_queue)

當我們的程序此時在等待外部資源的時候,該程序的代碼不會被執行,此時程序處于的狀态叫做程序阻塞。

阻塞是一種臨時狀态。

1.4 運作挂起

  1. 之前我們提到過,代碼是存放在磁盤中的,當運作程式時,先将程式加載到記憶體,那麼當記憶體不足了怎麼辦?

當記憶體不足的時候,作業系統會将該程序的代碼進行輾轉騰挪,如何輾轉騰挪--短期内不會被排程的程序,他的代碼和資料依然在記憶體中!就是在白白浪費空間!作業系統就會把該程序的代碼和資料置換到磁盤上!此時被暫時置換到磁盤上的程序就叫做程序挂起。

[ Linux ] Linux程式狀态

此時我們将程序的狀态都一一介紹了,我們使用Linux作業系統再一一檢視一下

2.Linux程序狀态

2.1 R 運作态

  • R運作狀态(running): 并不意味着程序一定在運作中,它表明程序要麼是在運作中要麼在運作隊列裡。

首先我們寫一段簡單的C語言代碼擷取pid

#include <stdio.h>
#include <unistd.h>
int main()
{
  while(1)
  {
    printf("這是一個程序  %d\n",getpid());
    sleep(1);
  }
  return 0;
}      

我們輸入指令檢視

ps ajx | head -1 && ps ajx | grep 'test'|grep -v grep      
[ Linux ] Linux程式狀态

發現運作的程序怎麼是S呢? 這是因為printf函數太快了,而顯示器顯示速度太慢啦,是以這個程序看似實在死循環,其實大多數時間都在等待顯示器。那麼怎麼才能進入運作态呢,我們屏蔽掉printf函數,隻保留while循環再次進行檢視發現已經處于運作态了

[ Linux ] Linux程式狀态

2.2 S 睡眠狀态

s對應的狀态一般叫做阻塞狀态!

如何把睡眠狀态叫醒呢?隻需要把程序狀态從S睡眠态轉換成R運作态即可。

我們常常把S狀态叫做淺度睡眠(也叫做可中斷睡眠),淺度睡眠可以被喚醒或者殺掉他。

[ Linux ] Linux程式狀态

隻要存在淺度睡眠,那麼一定存在深度睡眠,就是D狀态

2.3 D 深度睡眠

[ Linux ] Linux程式狀态

一般而言,Linux中如果我們等待的是磁盤資源,當程序等待磁盤資源拷貝寫入的過程,此時程序處于阻塞狀态,這種程序阻塞狀态就是D狀态(不可被中斷),作業系統也不能殺掉該程序,隻能等D狀态程序自己醒來。等待磁盤給回應才能醒來。D狀态和磁盤挂鈎,一般不好示範得出。我們有感性的了解即可。

2.4 Z 和 X 狀态

X(dead)狀态就是所謂的死亡狀态。

Z(zombie)狀态是僵屍狀态。Z狀态是一種已經死亡的狀态,但是死了之後,不要讓作業系統釋放它。那該狀态存在的意義是什麼呢?當一個Linux中的程序退出的時候,一般不會直接進入X狀态(死亡,資源可以立馬回收),而是進入Z狀态,為什麼?

因為程序被建立出來一定是要有任務完成,當程序退出的時候,我們怎麼知道程序把任務給我們完成了呢?需要将程序的執行結果告知給父程序或者作業系統。

子程序退出,維護Z狀态,就是為了讓父程序或者作業系統來讀取執行結果!父程序和作業系統通過程序等待來讀取僵屍程序的資訊。下圖是核心源碼中的退出資訊。

[ Linux ] Linux程式狀态

模拟僵屍程序?

建立子程序,子程序退出了,父程序不退出,也不等待子程序,子程序退出之後所處的狀态就是僵屍狀态。我們寫一段代碼來模拟一下!

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    //child
    int cnt = 5;
    while(cnt)
    {
      printf("我是子程序,我還剩%dS\n",cnt--);
      sleep(1);
    }
    printf("我是子程序,我已經僵屍了,等待被檢測\n");
    exit(0);
  }
  else
  {
    //父
    while(1)
    {
      sleep(1);
    }
  }
  return 0;
}      

我們在指令行寫一段腳本,循環列印程序狀态資訊

while :; do ps ajx | head -1 && ps ajx | grep 'test'|grep -v grep; 
sleep 1; echo"#########################################################";done      

我們發現一開始當子程序還沒有僵屍的時候,父子程序都處于S狀态

[ Linux ] Linux程式狀态

當子程序僵屍時,我們發現此時子程序的狀态變成了Z狀态

[ Linux ] Linux程式狀态

僵屍狀态後<defunct> 表示死者,死亡之意。

長時間僵屍,有什麼問題呢?

如果沒有人回收子程序的僵屍,該狀态會一直維護!該程序的相關資源(tast_struct)不會被釋放!會造成記憶體洩漏,是以一般必須要求父程序進行回收。

說到了僵屍程序,我們再談談與之相關的孤兒程序。

在我們們剛剛的代碼中,子程序先退出,而父程序一直存在,那如果父程序先退出,子程序一直存在,此時子程序的父程序已經被回收了,子程序沒有了父程序,我們把這種子程序處于的狀态叫做孤兒程序。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    //child
    int cnt = 5;
    while(1)
    {
      printf("我是子程序,我還剩%dS\n",cnt--);
      sleep(1);
    }
    printf("我是子程序,我已經僵屍了,等待被檢測\n");
    exit(0);
  }
  else
  {
    //f
    int cnt = 3;
    while(cnt--)
    {
      sleep(1);
    }
  }

  return 0;
}      

大約執行3秒過後,我們發現父程序不見了,之間子程序了,此時子程序就是孤兒程序,那麼孤兒程序要被1号程序領養。這個1号程序就是作業系統。

[ Linux ] Linux程式狀态

我們發現子程序狀态原先是S+,而後面為什麼變成了S呢?并且這段代碼不能被ctrl +c強制終止了

[ Linux ] Linux程式狀态

其中帶+号表示這個程序是前台程序,而前台程序能夠被ctrl C的,而不帶+的被稱為背景程序,我們要終止這個代碼,輸入kill -9 pid即可殺掉。

2.5 T狀态

在上面我們看到暫停狀态有T和t兩種狀态,這兩種狀态都叫暫停狀态,這兩種狀态沒有特别大的差別,而有一種特殊情況對應着t狀态。

暫停狀态就是讓運作的程序暫停一下,這個暫停狀态可以了解為我們下載下傳一個東西,我們讓他暫停一下;在看視訊的時候,暫停了一下。此時這個程序就處于暫停狀态。

我們在Linux下模拟一下暫停狀态,首先寫一段死循環程式

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
  while(1)
  {
    printf("hello\n");
    sleep(1);
  }
}      

當程式運作起來時,我們輸入

kill -19 pid      

此時我們做運作的代碼被暫停了,此時程序所處的狀态就是暫停狀态,當我們想讓其繼續運作時,我們輸入

kill -18 pid      
[ Linux ] Linux程式狀态
[ Linux ] Linux程式狀态
[ Linux ] Linux程式狀态

而t狀态時,當我們使用gdb調試代碼時,打上斷點後,我們r起來,我們發現了t狀态,t表示tracing,表示追蹤的意思,當程序被調試的時候,遇到斷點所處的狀态就是t狀态!

繼續閱讀