??關(guān)于專欄:Linux的淺學(xué)到熟知專欄用于記錄Linux系統(tǒng)編程、網(wǎng)絡(luò)編程等內(nèi)容。
??每天努力一點點,技術(shù)變化看得見
進程狀態(tài)
操作系統(tǒng)進程狀態(tài)概覽(理論版),實際的一款操作系統(tǒng)進程狀態(tài)與理論狀態(tài)會有一定區(qū)別?!?/p>
新建狀態(tài):字面意思,當進程剛創(chuàng)建時,就是新建狀態(tài)。
運行狀態(tài):等待CPU資源時,當還沒有輪到某個進程時,該進程會被鏈接在CPU的等待隊列中(該隊列被稱為run_queue,操作系統(tǒng)會按一定策略調(diào)度該隊列中的進程到CPU上執(zhí)行),位于該隊列中的進程都處于運行狀態(tài),而不是正在CPU上運行的程序才叫做運行狀態(tài)。
阻塞狀態(tài):等待非CPU資源就緒,該進程被從運行隊列上取下,被掛接到等待的資源的等待隊列中,該進程就稱為阻塞狀態(tài)。
掛起狀態(tài):當內(nèi)存不足的時候,操作系統(tǒng)通過適當?shù)闹脫Q進程的代碼和數(shù)據(jù)到磁盤,進程的狀態(tài)就稱為掛起狀態(tài)。
關(guān)于進程狀態(tài),我們來看看Linux源代碼中總共有幾種狀態(tài)↓↓↓
static const char* cinst task_state_array[] = {
"R(runnning)",/* 0 */
"S(sleeping)",/* 1 */
"D(disk sleep)", /* 2 */
"T(stopped)",/* 3 */
"t(tracing stop)",/* 4 */
"X(dead)", /* 16 */
"Z(zombie)" /* 32 */
};
在介紹這些狀態(tài)時,將使用使用描述+代碼驗證的方式(但部分狀態(tài)無法使用代碼驗證)。在開始介紹前,我們需要了解如何查看進程狀態(tài)↓↓↓
進程狀態(tài)查看
ps aux / ps axj
查看進程狀態(tài)總共有兩種方式,分別是ps aux
及ps axj
。使用它們查看進程的效果如下圖所示↓↓↓
★ps:關(guān)于ps命令的更多用法,可以查詢man手冊。
R運行狀態(tài)(running)
我們在創(chuàng)建了進程之后,操作系統(tǒng)會給該進程創(chuàng)建一個task_struct結(jié)構(gòu)體,該結(jié)構(gòu)體中包含進程狀態(tài)、pid、ppid、優(yōu)先級等字段,該結(jié)構(gòu)體就是PCB(進程控制塊),用于記錄進程各類信息。
管理好這些進程,我們需要只要管理好task_struct結(jié)構(gòu)體即可。因此,操作系統(tǒng)將task_struct鏈成一個鏈表(隊列)。
由于計算機中的各類資源(包括CPU、內(nèi)存、外部設(shè)備等)均十分寶貴,各個進程在獲取某個資源時,可能需要到某個資源上排隊。而等待CPU資源的進程將被鏈成一個隊列,這個隊列叫做運行隊列(run_queue)。而處于運行隊列上的進程的狀態(tài)就是運行狀態(tài),即R狀態(tài)。
下面,我們編寫一個死循環(huán),并查看該進程的狀態(tài)↓↓↓
#include <stdio.h>
int main()
{
while(1)
{}
return 0;
}
上面的STAT的R就是運行狀態(tài)。
S睡眠狀態(tài)(sleeping)
如果我們編寫一個循環(huán)打印"hello world"的程序,則執(zhí)行該程序的進程的狀態(tài)是R狀態(tài)嗎?
#include <stdio.h>
int main()
{
while(1)
{
printf("hello world\n");
}
retrun 0;
}
這里我執(zhí)行ps axj | head -1 && ps axj | grep test
命令,得到的進程狀態(tài)是S狀態(tài),即睡眠狀態(tài)。這是為什么呢?那什么是睡眠狀態(tài)呢?
我們在執(zhí)行上面的程序時,進程需要訪問外設(shè),而外設(shè)相比與cpu而言,速度非常慢。該進程為了打印"hello world",它需要到對應(yīng)的外設(shè)上等待(這里的外設(shè)是顯示器),在它需要使用外設(shè)資源時,cpu將該進程的PCB從運行隊列中取下來,并鏈入顯示器的等待隊列中。在除了cpu以外的隊列中等待時,這時的狀態(tài)就是S休眠狀態(tài)(也就是理論狀態(tài)中的阻塞狀態(tài))。等該進程打印完畢后,再鏈入cpu,繼續(xù)向下執(zhí)行。但由于上面的程序頻繁訪問外設(shè),導(dǎo)致它在運行隊列中的時間非常短。因而,我們在查看該進程狀態(tài)時,絕大多數(shù)情況下,它都處于睡眠狀態(tài)。
D磁盤休眠狀態(tài)(Disk sleep)
在操作系統(tǒng)中,如果一個進程處于睡眠狀態(tài),即S狀態(tài)。如果此時操作系統(tǒng)負載過大,則可能殺死該進程。這種睡眠狀態(tài)也被稱為淺度睡眠,因為它可以被操作系統(tǒng)終止。這也就是為什么某些軟件服務(wù)在用戶量過大時,出現(xiàn)某些用戶無法獲取服務(wù)的原因,因為應(yīng)用服務(wù)器的操作系統(tǒng)覆蓋過大時,這些用戶的進程被操作系統(tǒng)終止了。
從操作系統(tǒng)角度來說,操作系統(tǒng)為了維護服務(wù)器能穩(wěn)定運行,不得不殺死某些進程;從進程角度來說,進程正常運行而被操作系統(tǒng)強制關(guān)閉,進程也無能為力。
如果我們希望某些關(guān)鍵性進程,即使在操作系統(tǒng)負載過大時也不會被殺死,則可以將該進程設(shè)置為D狀態(tài),即磁盤休眠狀態(tài)(也稱為磁盤睡眠狀態(tài)、深度睡眠狀態(tài))。處于該狀態(tài)的進程不能被操作系統(tǒng)中斷,也不能被操作系統(tǒng)喚醒。該進程只有自己醒來,即該進程不再處于D狀態(tài),才能被操作系統(tǒng)調(diào)度執(zhí)行、回收等。
★ps:該狀態(tài)無法使用程序演示
T停止狀態(tài)(stopped)
在操作系統(tǒng)中,可以給某些進程發(fā)送信號,發(fā)送信號的格式為:
kill -[信號編號] [進程pid]
下面,我們使用kill -l
查看所有信號及其對應(yīng)的編號↓↓↓
上圖的18號信號SIGCONT為進程繼續(xù)執(zhí)行信號,19號信號為SIGSTOP為進程暫停信號。如果我們給某個進程發(fā)送19號信號,則它將處于T狀態(tài),即暫停狀態(tài)。
下面,我們給上面循環(huán)打印"hello world"的進程發(fā)送19號信號,再對比發(fā)送信號前后的狀態(tài)變化
kill -19 19093
在接收到19號SIGSTOP信號后,19093號進程停止打印"hello world",并且此時它的狀態(tài)為T狀態(tài),即暫停狀態(tài)。
如果我們給19093號進程發(fā)送18號SIGCONT信號,會是什么效果呢?
發(fā)送信號后,19093號進程繼續(xù)打印"hello world",并且它的狀態(tài)又變回S狀態(tài),即睡眠狀態(tài)。但這里不同的是,原先的狀態(tài)是S+,而此時的狀態(tài)是S。這兩者有什么區(qū)別呢?
S+狀態(tài)下的進程,在使用ctrl+C時,可以被終止;而S狀態(tài)下的進程使用ctrl+C卻無法被終止。這里帶有+號的稱為前臺進程,不帶+號的稱為后臺進程。前臺進程占用用戶當前的bash命令行;后臺進程不占用用戶的bash命令行,但后臺進程需要使用kill -9 [進程號]
發(fā)送9號信號來終止。
★ps:T狀態(tài)與S狀態(tài)的區(qū)別:T狀態(tài)單純暫停,并不等待某種資源;而S狀態(tài)是為了等待某種資源。
t調(diào)試/追蹤狀態(tài)(tracing stop)
我們在編寫完程序后,可以在使用gcc編譯,如果在編譯命令的末尾加上-g
選項,則會生成一個debug版本的程序。如果不帶-g
選項,gcc默認生成的是release版本。
我們使用gcc生成下面程序的release和debug版本↓↓↓
#include <stdio.h>
int Add(int left, int right)
{
return left + right;
}
int main()
{
int num1 = 10;
int num2 = 20;
printf("%d + %d = %d\n", num1. num2, Add(num1, num2));
return 0;
}
從上圖可以發(fā)現(xiàn),debug版本所占的內(nèi)存空間會大于release版本(因為debug版本中包含調(diào)試信息)。
下面我們使用gdb對test_g進行調(diào)試↓↓↓
由于在12行處打了斷點,此時程序停止在12行處。我們使用ps axj | head -1 && ps axj | grep test
查看當前進程狀態(tài)↓↓↓
此時的進程狀態(tài)為t狀態(tài),即調(diào)試狀態(tài)(也稱為追蹤狀態(tài))。
X死亡狀態(tài)/終止狀態(tài)(dead)
如果進程執(zhí)行結(jié)束了,操作系統(tǒng)會馬上回收該進程的資源嗎(進程此時占用內(nèi)存等資源)?不一定。如果此時cpu上此時正在處理更加重要、緊急的進程,則操作系統(tǒng)此時不會馬上回收已經(jīng)執(zhí)行結(jié)束的進程,而是將該進程標識為X狀態(tài),即死亡狀態(tài)(也成為終止狀態(tài))。
標記為X狀態(tài)的進程,表示該進程可以被操作系統(tǒng)回收。但具體什么時候回收,取決于操作系統(tǒng)。由于X狀態(tài)瞬時性較強,難以使用程序演示,這里就不使用程序演示了。
Z僵尸狀態(tài)/僵尸進程(zombie)
僵尸狀態(tài)(zombie,也稱為僵死狀態(tài))是一種比較特殊的狀態(tài)。該狀態(tài)發(fā)生在子進程退出,而父進程沒有回收子進程資源時(即父進程沒有使用waitpid讀取子進程的退出信息),此時子進程就會進入僵尸狀態(tài)。
僵尸進程會以終止狀態(tài)保持在進程表中,并且會一直等待父進程讀取退出狀態(tài)碼。所以,只要子進程退出,父進程還在運行,但父進程沒有讀取子進程狀態(tài),則子進程會進入Z狀態(tài)。
下面創(chuàng)建的程序,使得子進程保持5秒的僵尸狀態(tài)↓↓↓
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
printf("I am child process! pid = %d, ppid = %d\n", getpid(), getppid());
sleep(5);
exit(0);
}
printf("I am parent process! pid = %d\n", getpid());
sleep(10);
return 0;
}
執(zhí)行上述程序并使用while :; do ps axj | head -1 && ps axj | grep test; sleep 1; echo "#############" done;
腳本,每1秒鐘對執(zhí)行內(nèi)容做監(jiān)視。
我們可以發(fā)現(xiàn),在子進程退出后,父進程沒有退出,此時的子進程的狀態(tài)變?yōu)閆狀態(tài),即僵尸狀態(tài)。
僵尸進程的危害:
進程的退出狀態(tài)必須被維持下去,因為他要告訴關(guān)心它的進程(父進程),你交給我的任務(wù),我辦的怎么樣了??筛高M程如果一直不讀取,那子進程就一直處于Z狀態(tài)。
維護退出狀態(tài)本身就是要用數(shù)據(jù)維護,也屬于進程基本信息,所以保存在task_struct(PCB)中,換句話說,Z狀態(tài)一直不退出,PCB一直都要維護。
那一個父進程創(chuàng)建了很多子進程,如果不回收,就會造成內(nèi)存資源的浪費(內(nèi)存泄漏)。因為數(shù)據(jù)結(jié)構(gòu)對象本身就要占用內(nèi)存,想想C語言中定義一個結(jié)構(gòu)體變量(對象),是要在內(nèi)存的某個位置進行開辟空間!
★ps:關(guān)于僵尸進程如何被處理的問題,將在后序文章中的介紹。
孤兒進程
上面已經(jīng)將進程的各種狀態(tài)講述完畢,接下來,我們再了解另一種進程——孤兒進程。
父進程如果提前退出,子進程后退出,則此時父進程無法再回收子進程資源了,那該如何處理呢?
如果讓子進程一直保持Z狀態(tài),則會造成內(nèi)存泄漏;但此時子進程的父進程已經(jīng)執(zhí)行結(jié)束,沒有進程可以來清理子進程的資源了,這種子進程被稱為孤兒進程。操作系統(tǒng)為了解決這個問題,對于父進程已經(jīng)執(zhí)行結(jié)束,而子進程后退出的,該子進程將被1號init進程領(lǐng)養(yǎng),其資源將由init進程進行回收。
下面代碼中,父進程比子進程執(zhí)行結(jié)束前5秒就退出↓↓↓
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
int cnt = 10;
while(cnt > 0)
{
printf("I am child process, pid = %d, ppid = %d\n", getpid(), getppid());
sleep(1);
cnt--;
}
exit(0);
}
printf("I am parent process, pid = %d\n", getpid());
sleep(5);
return 0;
}
由程序執(zhí)行結(jié)果可以看出,子進程前5秒的父進程pid為15480,由于父進程在子進程推出前5秒就推出了,此時子進程被1號init進程領(lǐng)養(yǎng),故此時該進程的父進程pid為1。
進程優(yōu)先級
基本概念
優(yōu)先權(quán)高的進程有優(yōu)先執(zhí)行權(quán)利,可以優(yōu)先獲得cpu資源。
配置進程優(yōu)先權(quán)對多任務(wù)環(huán)境的linux很有用,可以改善系統(tǒng)性能。對于多核計算機,可以把進程運行到指定的CPU上,這樣一來,把不重要的進程安排到某個CPU,可以大大改善系統(tǒng)整體性能。
★ps:優(yōu)先級是重要的調(diào)度指標,CPU中的調(diào)度器依據(jù)優(yōu)先級選擇哪些進程優(yōu)先到CPU運行
查看系統(tǒng)進程
我們可以執(zhí)行ps -le
來查看系統(tǒng)中所有進程的詳細信息(-e選項表示所有進程,-l選項表示顯示進程詳細信息)
上圖中:
UID : 代表執(zhí)行者的身份
PID : 代表這個進程的代號
PPID :代表這個進程是由哪個進程發(fā)展衍生而來的,亦即父進程的代號
PRI :代表這個進程可被執(zhí)行的優(yōu)先級,其值越小越早被執(zhí)行
NI :代表這個進程的nice值
PRI及NI
PRI是進程的優(yōu)先級,或者通俗點說就是程序被CPU執(zhí)行的先后順序,此值越小進程的優(yōu)先級別越高。NI就是我們所要說的nice值了,其表示進程可被執(zhí)行的優(yōu)先級的修正數(shù)值。PRI值越小越快被執(zhí)行,那么加入nice值后,將會使得PRI變?yōu)椋篜RI(new)=PRI(old)+nice。
這樣,當nice值為負值的時候,那么該程序?qū)?yōu)先級值將變小,即其優(yōu)先級會變高,則其越快被執(zhí)行。所以,調(diào)整進程優(yōu)先級,在Linux下,就是調(diào)整進程nice值。nice其取值范圍是-20至19,一共40個級別。
那如果PRI(new)=PRI(old)+nice,那么我的程序起始優(yōu)先級PRI為80,我對它重復(fù)設(shè)置10000次nice值為-20,它的優(yōu)先級PRI是不是變成80-20*10000呢?答案是否定的。在Linux操作系統(tǒng)中,PRI起始都為80,nice值得范圍為-19到20。當我們設(shè)置了新的nice值時,該進程的PRI=80+nice值。也就是說PRI的范圍在[80-20,80+19]之間。
★ps:進程的nice值不是進程的優(yōu)先級,進程優(yōu)先級與nice值不是一個概念,但是進程nice值會影響到進程的優(yōu)先級變化。可以理解nice值是進程優(yōu)先級的修正修正數(shù)據(jù)。
修改進程優(yōu)先級
top命令
首先,我們運行一個名為test的死循環(huán)程序。此時它的PRI為80,NI為0。
top命令修改進程優(yōu)先級的方法(如果要設(shè)置小于0的nice值,即提高進程優(yōu)先級,此時需要使用sudo提權(quán)):
①執(zhí)行top命令
②輸入r,并輸入待修改進程優(yōu)先級的進程pid。
③輸入nice值,這里輸入10。
我們使用ps -el | head -1 && ps -el | grep test
查看進程優(yōu)先級發(fā)現(xiàn),test的PRI變?yōu)?0,NI變?yōu)?0。
★ps:上圖中,我們將nice值設(shè)置為10,則該進程的PRI=80+10=90。如果我們在此基礎(chǔ)上設(shè)置nice值為15,則該進程的優(yōu)先級為PRI=80+15=95。
renice命令
renice命令使用格式為:
renice [nice值] -p [進程pid]
下圖演示將20269號進程的nice值修改為15。
我們使用ps -el | head -1 && ps -el | grep test
查看進程優(yōu)先級發(fā)現(xiàn),test的PRI變?yōu)?5,NI變?yōu)?5。
★ps:如果要給進程設(shè)置比原nice值更小的nice值,需要使用sudo提權(quán)。
關(guān)于進程的相關(guān)概念
競爭性: 系統(tǒng)進程數(shù)目眾多,而CPU資源只有少量,甚至1個,所以進程之間是具有競爭屬性的。為了高效完成任務(wù),更合理競爭相關(guān)資源,便具有了優(yōu)先級。
獨立性: 多進程運行,需要獨享各種資源,多進程運行期間互不干擾。
并行: 多個進程在多個CPU下分別,同時進行運行,這稱之為并行。
并發(fā): 多個進程在一個CPU下采用進程切換的方式,在一段時間之內(nèi),讓多個進程都得以推進,稱之為并發(fā)。
關(guān)于并行和并發(fā)這里做一下更詳細的解釋:
并行就是多個進程同時執(zhí)行,例如某計算機有3個CPU,3個進程各占用一個CPU,同時執(zhí)行,這就是并行。而并發(fā)并不是,例如某計算機只有1個CPU,我們有3個進程在一個CPU上并發(fā)執(zhí)行,第1個進程先執(zhí)行1ms,接下來由第2個進程執(zhí)行1ms,再由第3個進程執(zhí)行1ms,然后又由第1個進程執(zhí)行,以此類推…像這種明明各個進程是交替執(zhí)行的,但由于各個進程都在向前運行,而進程交替運行的操作用戶感知不到,用戶以為這3個進程是各占用1個CPU并同時執(zhí)行的,這種被稱為并發(fā)。
★ps:關(guān)于進程的切換問題↓↓↓
計算機中的CPU內(nèi)有大量的寄存器,它保存著正在執(zhí)行的進程的臨時數(shù)據(jù)。如果進程A正在被執(zhí)行,CPU內(nèi)的寄存器里面一定保存的是進程A的臨時數(shù)據(jù)。寄存器中的臨時數(shù)據(jù),就叫做A的上下文。
上下文數(shù)據(jù)可以丟棄嗎?絕對不可以!由于計算機CPU數(shù)量小于進程數(shù)量,則當進程A暫時被切換下來的時候,進程A需要順便帶走自己的上下文數(shù)據(jù)。帶走暫時保存的目的就是為了下次回來的時候,能恢復(fù)上去,就能繼續(xù)按照之前的邏輯繼續(xù)向后運行,就如同沒有中斷過一樣。但如果沒有保存上下文數(shù)據(jù),進程A回來再執(zhí)行時,就無法判斷進程A原先執(zhí)行到哪里了。
注意:CPU內(nèi)的寄存器只有一份,但是上下文可以有多份,分別對應(yīng)不同的進程??!
文章來源:http://www.zghlxwxcb.cn/news/detail-854994.html
??歡迎進入從淺學(xué)到熟知Linux專欄,查看更多文章。
如果上述內(nèi)容有任何問題,歡迎在下方留言區(qū)指正b( ̄▽ ̄)d文章來源地址http://www.zghlxwxcb.cn/news/detail-854994.html
到了這里,關(guān)于【從淺學(xué)到熟知Linux】進程狀態(tài)與進程優(yōu)先級(含進程R/S/T/t/D/X/Z狀態(tài)介紹、僵尸進程、孤兒進程、使用top及renice調(diào)整進程優(yōu)先級)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!