【Linux C編程】第十章 進(jìn)程及進(jìn)程控制
一、整體大綱

二、基礎(chǔ)知識(shí)
1. 進(jìn)程相關(guān)概念
1)程序和進(jìn)程
程序,是指編譯好的二進(jìn)制文件,在磁盤(pán)上,不占用系統(tǒng)資源(cpu、內(nèi)存、打開(kāi)的文件、設(shè)備、鎖....)
進(jìn)程,是一個(gè)抽象的概念,與操作系統(tǒng)原理聯(lián)系緊密。進(jìn)程是活躍(運(yùn)行起來(lái)的)的程序,占用系統(tǒng)資源。在內(nèi)存中執(zhí)行。(程序運(yùn)行起來(lái),產(chǎn)生一個(gè)進(jìn)程)。
程序 → 劇本(紙) 進(jìn)程 → 戲(舞臺(tái)、演員、燈光、道具...),同一個(gè)劇本可以在多個(gè)舞臺(tái)同時(shí)上演。同樣,同一個(gè)程序也可以加載為不同的進(jìn)程(彼此之間互不影響)
如:同時(shí)開(kāi)兩個(gè)終端。各自都有一個(gè)bash但彼此ID不同。
2)并發(fā)
并發(fā),在操作系統(tǒng)中,一個(gè)時(shí)間段中有多個(gè)進(jìn)程都處于已啟動(dòng)運(yùn)行到運(yùn)行完畢之間的狀態(tài)。但,任一個(gè)時(shí)刻點(diǎn)上仍只有一個(gè)進(jìn)程在運(yùn)行。理論依據(jù):時(shí)鐘中斷
例如,當(dāng)下,我們使用計(jì)算機(jī)時(shí)可以邊聽(tīng)音樂(lè)邊聊天邊上網(wǎng)。 若籠統(tǒng)的將他們均看做一個(gè)進(jìn)程的話(huà),為什么可以同時(shí)運(yùn)行呢,因?yàn)椴l(fā)。
3)單道程序設(shè)計(jì)
所有進(jìn)程一個(gè)一個(gè)排對(duì)執(zhí)行。若A阻塞,B只能等待,即使CPU處于空閑狀態(tài)。而在人機(jī)交互時(shí)阻塞的出現(xiàn)時(shí)必然的。所有這種模型在系統(tǒng)資源利用上及其不合理,在計(jì)算機(jī)發(fā)展歷史上存在不久,大部分便被淘汰了。
4)多道程序設(shè)計(jì)
在計(jì)算機(jī)內(nèi)存中同時(shí)存放幾道相互獨(dú)立的程序,它們?cè)诠芾沓绦蚩刂浦拢嗷ゴ┎宓倪\(yùn)行。多道程序設(shè)計(jì)必須有硬件基礎(chǔ)作為保證。
時(shí)鐘中斷即為多道程序設(shè)計(jì)模型的理論基礎(chǔ)。 并發(fā)時(shí),任意進(jìn)程在執(zhí)行期間都不希望放棄cpu。因此系統(tǒng)需要一種強(qiáng)制讓進(jìn)程讓出cpu資源的手段。時(shí)鐘中斷有硬件基礎(chǔ)作為保障,對(duì)進(jìn)程而言不可抗拒。 操作系統(tǒng)中的中斷處理函數(shù),來(lái)負(fù)責(zé)調(diào)度程序執(zhí)行。
在多道程序設(shè)計(jì)模型中,多個(gè)進(jìn)程輪流使用CPU (分時(shí)復(fù)用CPU資源)。而當(dāng)下常見(jiàn)CPU為納秒級(jí),1秒可以執(zhí)行大約10億條指令。由于人眼的反應(yīng)速度是毫秒級(jí),所以看似同時(shí)在運(yùn)行。
1s = 1000ms, 1ms = 1000us, 1us = 1000ns ???1000000000
實(shí)質(zhì)上,并發(fā)是宏觀(guān)并行,微觀(guān)串行!推動(dòng)了計(jì)算機(jī)蓬勃發(fā)展,將人類(lèi)引入了多媒體時(shí)代。
5)CPU和MMU
我們?cè)诔绦蛑杏玫降乃械膬?nèi)存都是虛擬內(nèi)存,但是虛擬內(nèi)存在計(jì)算機(jī)中是不實(shí)際存在的,我們存儲(chǔ)的數(shù)據(jù)都是存儲(chǔ)在物理內(nèi)存中。
內(nèi)存管理單元MMU
6)進(jìn)程控制塊PCB
我們知道,每個(gè)進(jìn)程在內(nèi)核中都有一個(gè)進(jìn)程控制塊(PCB)來(lái)維護(hù)進(jìn)程相關(guān)的信息,Linux內(nèi)核的進(jìn)程控制塊是task_struct結(jié)構(gòu)體。補(bǔ)充:打開(kāi)文件的信息也是一個(gè)結(jié)構(gòu)體
/usr/src/kernels/3.10.0-957.10.1.el7.x86_64/include/linux/sched.h(Centos7)文件中可以查看struct task_struct 結(jié)構(gòu)體定義。其內(nèi)部成員有很多,我們重點(diǎn)掌握以下部分即可:
* 進(jìn)程id。系統(tǒng)中每個(gè)進(jìn)程有唯一的id,在C語(yǔ)言中用pid_t類(lèi)型表示,其實(shí)就是一個(gè)非負(fù)整數(shù)。 * 進(jìn)程的狀態(tài),有初始化、就緒、運(yùn)行、掛起、停止等狀態(tài)。 * 進(jìn)程切換時(shí)需要保存和恢復(fù)的一些CPU寄存器的值。 * 描述虛擬地址空間的信息。 * 描述控制終端的信息。 * 當(dāng)前工作目錄(Current Working Directory)。 * umask掩碼。 * 文件描述符表,包含很多指向已經(jīng)打開(kāi)的文件的file結(jié)構(gòu)體的指針的一個(gè)數(shù)組。 (注:pcb中有一根指針,指針存儲(chǔ)的是文件描述符表的首地址) * 和信號(hào)相關(guān)的信息。 * 用戶(hù)id和組id。 * 會(huì)話(huà)(Session)和進(jìn)程組。 * 進(jìn)程可以使用的資源上限(Resource Limit)。
ulimit -a 查看當(dāng)前Linux下的資源上線(xiàn)
用戶(hù)空間 映射到物理內(nèi)存是獨(dú)立的
7)進(jìn)程狀態(tài)
進(jìn)程基本的狀態(tài)有5種。分別為初始態(tài),就緒態(tài),運(yùn)行態(tài),掛起態(tài)與終止態(tài)。其中初始態(tài)為進(jìn)程準(zhǔn)備階段,常與就緒態(tài)結(jié)合來(lái)看。
2. 環(huán)境變量
(1)定義:
環(huán)境變量,是指在操作系統(tǒng)中用來(lái)指定操作系統(tǒng)運(yùn)行環(huán)境的一些參數(shù)。通常具備以下特征:
① 字符串(本質(zhì)) ② 有統(tǒng)一的格式:名=值[:值] ③ 值用來(lái)描述進(jìn)程環(huán)境信息。
存儲(chǔ)形式:與命令行參數(shù)類(lèi)似。char *[]數(shù)組,數(shù)組名environ,內(nèi)部存儲(chǔ)字符串,NULL作為哨兵結(jié)尾。
使用形式:與命令行參數(shù)類(lèi)似。
加載位置:與命令行參數(shù)類(lèi)似。位于用戶(hù)區(qū),高于stack的起始位置。
引入環(huán)境變量表:須聲明環(huán)境變量。extern char ** environ;
(2)常見(jiàn)環(huán)境變量:
按照慣例,環(huán)境變量字符串都是name=value這樣的形式,大多數(shù)name由大寫(xiě)字母加下劃線(xiàn)組成,一般把name的部分叫做環(huán)境變量,value的部分則是環(huán)境變量的值。環(huán)境變量定義了進(jìn)程的運(yùn)行? ? ? ? 環(huán)境,一些比較重要的環(huán)境變量的含義如下:
PATH
可執(zhí)行文件的搜索路徑。ls命令也是一個(gè)程序,執(zhí)行它不需要提供完整的路徑名/bin/ls,然而通常我們執(zhí)行當(dāng)前目錄下的程序a.out卻需要提供完整的路徑名./a.out,這是因?yàn)镻ATH環(huán)境變量的值里面包含了ls命令所在的目錄/bin,卻不包含a.out所在的目錄。PATH環(huán)境變量的值可以包含多個(gè)目錄,用:號(hào)隔開(kāi)。在Shell中用echo命令可以查看這個(gè)環(huán)境變量的值:
$ echo $PATH
SHELL
當(dāng)前Shell,它的值通常是/bin/bash。
TERM
當(dāng)前終端類(lèi)型,在圖形界面終端下它的值通常是xterm,終端類(lèi)型決定了一些程序的輸出顯示方式,比如圖形界面終端可以顯示漢字,而字符終端一般不行。
LANG
語(yǔ)言和locale,決定了字符編碼以及時(shí)間、貨幣等信息的顯示格式。
HOME
當(dāng)前用戶(hù)主目錄的路徑,很多程序需要在主目錄下保存配置文件,使得每個(gè)用戶(hù)在運(yùn)行該程序時(shí)都有自己的一套配置。
(3)相關(guān)函數(shù)
1)getenv
函數(shù)作用:獲取當(dāng)前進(jìn)程環(huán)境變量
頭文件
#include
函數(shù)原型
char *getenv(const char *name);
參數(shù)說(shuō)明:
name環(huán)境變量名
返回值
成功:指向環(huán)境變值得指針
失敗:返回NULL
示例:
獲取當(dāng)前進(jìn)程家目錄
1 #include
2)setenv
函數(shù)作用:設(shè)置環(huán)境變量。
頭文件:
#include
函數(shù)原型:
int setenv(const char *name, const char *value, int overwrite);
參數(shù)說(shuō)明:
name 環(huán)境變量名
value 要設(shè)置的環(huán)境變量值
overwrite取值: 1:覆蓋原環(huán)境變量。0:不覆蓋
返回值:
成功:0;失敗:-1
3)unsetenv
函數(shù)作用:刪除環(huán)境變量name的定義
頭文件:
#include
函數(shù)原型:
int unsetenv(const char *name);
參數(shù)說(shuō)明:
name 環(huán)境變量名
返回值
成功:0;
失敗:-1
注意:name不存在仍返回0(成功),當(dāng)name命名為"ABC="時(shí)則會(huì)出錯(cuò)。因?yàn)椤?”是構(gòu)成環(huán)境變量中的一個(gè)組成部分。
setenv 和 unsetenv 后兩個(gè)不常用。
3. 進(jìn)程控制
(1)進(jìn)程相關(guān)函數(shù)
1)fork
函數(shù)作用:創(chuàng)建子進(jìn)程
頭文件
#include
函數(shù)原型
pid_t fork(void);
返回值
成功:兩次返回,父進(jìn)程返回子進(jìn)程的id,子進(jìn)程返回0
失敗:返回-1給父進(jìn)程,設(shè)置errno
示例:
fork示例
1 #include
執(zhí)行結(jié)果分析:
執(zhí)行結(jié)果: begin...end... begin...end... 結(jié)果分析: 為何會(huì)打印兩次begin? 這是由于printf("Begin ...");執(zhí)行之后并不會(huì)打印到屏幕,而是在緩沖區(qū),因此fork之后子進(jìn)程在執(zhí)行printf("End ...\n");遇到\n則全部打印出來(lái)。如果修改為printf("Begin …\n");(在遇到\n時(shí)會(huì)將緩沖區(qū)內(nèi)容打印到屏幕。)則子進(jìn)程不會(huì)打印begin…
2)getpid與getppid
函數(shù)作用:獲取進(jìn)程id
頭文件
#include
函數(shù)原型
pid_t getpid(void); 獲得當(dāng)前進(jìn)程id pid_t getppid(void); 獲得當(dāng)前進(jìn)程的父進(jìn)程id
返回值:
getpid 獲得當(dāng)前進(jìn)程的pid,getppid獲取當(dāng)前進(jìn)程父進(jìn)程的pid。
示例1:
getpid與getppid示例
1 #include
補(bǔ)充:
(1)查看進(jìn)程信息:
init進(jìn)程是所有進(jìn)程的祖先。
ps命令:
ps aux
ps ajx ---可以追溯進(jìn)程之間的血緣關(guān)系
kill命令:
給進(jìn)程發(fā)送一個(gè)信號(hào)
SIGKILL 9信號(hào)
kill -SIGKILL pid 殺死進(jìn)程
(2)循環(huán)創(chuàng)建n個(gè)子進(jìn)程
一次fork函數(shù)調(diào)用可以創(chuàng)建一個(gè)子進(jìn)程。那么創(chuàng)建N個(gè)子進(jìn)程應(yīng)該怎樣實(shí)現(xiàn)呢?
簡(jiǎn)單想,for(i = 0; i < n; i++) { fork() } 即可。但這樣創(chuàng)建的是N個(gè)子進(jìn)程嗎?
示例2:創(chuàng)建多個(gè)線(xiàn)程?
bug版本(創(chuàng)建2個(gè)線(xiàn)程)
1 #include
執(zhí)行結(jié)果:
總共產(chǎn)生4個(gè)進(jìn)程,但是本來(lái)想產(chǎn)生2個(gè),因此將代碼中的break打開(kāi),在fork進(jìn)程之后,將子進(jìn)程退出。
結(jié)果分析:
從上圖我們可以很清晰的看到,當(dāng)n為3時(shí)候,循環(huán)創(chuàng)建了(2^n)-1個(gè)子進(jìn)程,而不是N的子進(jìn)程。需要在循環(huán)的過(guò)程,保證子進(jìn)程不再執(zhí)行fork ,因此當(dāng)(fork() == 0)時(shí),子進(jìn)程應(yīng)該立即break;才正確。
如何修改成預(yù)期創(chuàng)建兩個(gè)線(xiàn)程?
將代碼中的break解注釋?zhuān)?dāng)為子線(xiàn)程的時(shí)候直接退出。
重點(diǎn):通過(guò)該練習(xí)掌握框架:循環(huán)創(chuàng)建n個(gè)子進(jìn)程,使用循環(huán)因子i對(duì)創(chuàng)建的子進(jìn)程加以區(qū)分。
示例3:進(jìn)程先創(chuàng)建先退出?
進(jìn)程先創(chuàng)建先退出
1 #include
3)getuid
uid_t getuid(void); --> 獲取當(dāng)前進(jìn)程實(shí)際用戶(hù)ID
uid_t geteuid(void); -->?獲取當(dāng)前進(jìn)程有效用戶(hù)ID
4)getgid
gid_t getgid(void); -->?獲取當(dāng)前進(jìn)程使用用戶(hù)組ID
gid_t getegid(void); -->?獲取當(dāng)前進(jìn)程有效用戶(hù)組ID
(2)進(jìn)程共享
父子進(jìn)程之間在fork后。有哪些相同,那些相異之處呢?
剛fork之后:
父子相同處(0-3G的用戶(hù)區(qū)及3-4G的內(nèi)核區(qū)大部分): 全局變量、.data、.text、棧、堆、環(huán)境變量、用戶(hù)ID、宿主目錄、進(jìn)程工作目錄、信號(hào)處理方式...
父子不同處(3-4G中的內(nèi)核區(qū)的PCB區(qū)): 1.進(jìn)程ID ??2.fork返回值 ??3.父進(jìn)程ID ???4.進(jìn)程運(yùn)行時(shí)間 ???5.鬧鐘(定時(shí)器)(定時(shí)器是以進(jìn)程為單位進(jìn)行分配,每個(gè)進(jìn)程有且僅有一個(gè)) 6.未決信號(hào)集。
似乎,子進(jìn)程復(fù)制了父進(jìn)程0-3G用戶(hù)空間內(nèi)容,以及父進(jìn)程的PCB,但pid不同。真的每fork一個(gè)子進(jìn)程都要將父進(jìn)程的0-3G地址空間完全拷貝一份,然后在映射至物理內(nèi)存嗎?
當(dāng)然不是!父子進(jìn)程間遵循讀時(shí)共享寫(xiě)時(shí)復(fù)制的原則(針對(duì)的是物理地址)。這樣設(shè)計(jì),無(wú)論子進(jìn)程執(zhí)行父進(jìn)程的邏輯還是執(zhí)行自己的邏輯都能節(jié)省內(nèi)存開(kāi)銷(xiāo)。
練習(xí):編寫(xiě)程序測(cè)試,父子進(jìn)程是否共享全局變?
驗(yàn)證父子進(jìn)程不共享全局變量
1 #include
結(jié)論:父子進(jìn)程不共享全局變量。
父子進(jìn)程共享:1. 文件描述符(打開(kāi)文件的結(jié)構(gòu)體) ?2. mmap建立的映射區(qū) (進(jìn)程間通信詳解)
特別的,fork之后父進(jìn)程先執(zhí)行還是子進(jìn)程先執(zhí)行不確定。取決于內(nèi)核所使用的調(diào)度算法,即隨機(jī)爭(zhēng)奪。
如上圖:如果有一個(gè)全局變量 i = 5,當(dāng)fork出子進(jìn)程之后,此時(shí)父子進(jìn)程指向同一片物理內(nèi)存,父子進(jìn)程讀到的 i = 5,但是當(dāng)子進(jìn)程或者父進(jìn)程去修改全局變量(i = 10),則此時(shí)系統(tǒng)會(huì)開(kāi)辟一片新內(nèi)存,則父子進(jìn)程的 i 就不是同一個(gè)值。這樣做為了減少系統(tǒng)開(kāi)銷(xiāo),也就是讀時(shí)共享,寫(xiě)時(shí)復(fù)制。
(3)gdb調(diào)試
使用gdb調(diào)試的時(shí)候,gdb只能跟蹤一個(gè)進(jìn)程。可以在fork函數(shù)調(diào)用之前,通過(guò)指令設(shè)置gdb調(diào)試工具跟蹤父進(jìn)程或者是跟蹤子進(jìn)程。默認(rèn)跟蹤父進(jìn)程。
set follow-fork-mode child 命令設(shè)置gdb在fork之后跟蹤子進(jìn)程。 set follow-fork-mode parent 設(shè)置跟蹤父進(jìn)程。
注意,一定要在fork函數(shù)調(diào)用之前設(shè)置才有效。
4. exec函數(shù)族
fork創(chuàng)建子進(jìn)程后執(zhí)行的是和父進(jìn)程相同的程序(但有可能執(zhí)行不同的代碼分支),子進(jìn)程往往要調(diào)用一種exec函數(shù)以執(zhí)行另一個(gè)程序。當(dāng)進(jìn)程調(diào)用一種exec函數(shù)時(shí),該進(jìn)程的用戶(hù)空間代碼和數(shù)據(jù)完全被新程序替換,從新程序的啟動(dòng)例程開(kāi)始執(zhí)行。調(diào)用exec并不創(chuàng)建新進(jìn)程,所以調(diào)用exec前后該進(jìn)程的id并未改變。
將當(dāng)前進(jìn)程的.text、.data替換為所要加載的程序的.text、.data,然后讓進(jìn)程從新的.text第一條指令開(kāi)始執(zhí)行,但進(jìn)程ID不變,換核不換殼。也就是調(diào)用完exec函數(shù)中的命令之后,原來(lái)函數(shù)后面的代碼就不會(huì)執(zhí)行。
其實(shí)有六種以exec開(kāi)頭的函數(shù),統(tǒng)稱(chēng)exec函數(shù):
int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ..., char *const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execve(const char *path, char *const argv[], char *const envp[]);
1)execlp
函數(shù)作用:
頭文件
#include
函數(shù)原型
int execlp(const char *file, const char *arg, ...);
參數(shù)說(shuō)明:
file 需要加載的程序的名字
arg 一般是程序名
... 參數(shù)名,可變參數(shù)
返回值:
成功:無(wú)返回
失敗:-1
注意:p是PATH的縮寫(xiě),?execlp 加載一個(gè)進(jìn)程,借助PATH環(huán)境變量 (不用寫(xiě)該命令的絕對(duì)路徑,會(huì)到當(dāng)前進(jìn)程的環(huán)境變量中去找),當(dāng)PATH中所有目錄搜索后沒(méi)有參數(shù)1則出錯(cuò)返回。
該函數(shù)通常用來(lái)調(diào)用系統(tǒng)程序。如:ls、date、cp、cat等命令。
比如:execlp("ls", "ls", "-l", "-F", NULL);? 使用程序名在PATH中搜索。
注意:int execlp(const char *file, const char *arg, ...);? --->??int execlp(const char *file, const char *argv[]); 因此 arg 就相當(dāng)于第一個(gè)參數(shù)(argv[0])。
execlp示例
1 #include
2)execl函數(shù)
其中 l 是 list 的縮寫(xiě),基本同execlp函數(shù),只是該函數(shù)在加載程序式,需要寫(xiě)絕對(duì)路徑。
比如:execl("/bin/ls", "ls", "-l", "-F", NULL); ???使用參數(shù)1給出的絕對(duì)路徑搜索。
execl示例
1 #include
3)execvp函數(shù)
加載一個(gè)進(jìn)程,使用自定義環(huán)境變量env
int execvp(const char *file, const char *argv[]);
變參形式: 1)... 2)argv[] ?(main函數(shù)也是變參函數(shù),形式上等同于 int main(int argc, char *argv0, ...))
變參終止條件:1)NULL結(jié)尾 2)固參指定
execvp與execlp參數(shù)形式不同,原理一致。
4)exec函數(shù)族一般規(guī)律
exec函數(shù)一旦調(diào)用成功即執(zhí)行新的程序,不返回。只有失敗才返回,錯(cuò)誤值-1。所以通常我們直接在exec函數(shù)調(diào)用后直接調(diào)用perror()和exit(),無(wú)需if判斷。
l (list) 命令行參數(shù)列表
p (path) 搜素file時(shí)使用path變量
v (vector) 使用命令行參數(shù)數(shù)組
e (environment) 使用環(huán)境變量數(shù)組,不使用進(jìn)程原有的環(huán)境變量,設(shè)置新加載程序運(yùn)行的環(huán)境變量
事實(shí)上,只有execve是真正的系統(tǒng)調(diào)用,其它五個(gè)函數(shù)最終都調(diào)用execve,所以execve在man手冊(cè)第2節(jié),其它函數(shù)在man手冊(cè)第3節(jié)。這些函數(shù)之間的關(guān)系如下圖所示。
5. 回收子進(jìn)程
孤兒進(jìn)程:父進(jìn)程先于子進(jìn)程結(jié)束,則子進(jìn)程成為孤兒進(jìn)程,子進(jìn)程的父進(jìn)程成為init進(jìn)程,稱(chēng)為init進(jìn)程領(lǐng)養(yǎng)孤兒進(jìn)程。
僵尸進(jìn)程:子進(jìn)程終止,父進(jìn)程尚未回收,子進(jìn)程殘留資源(PCB)存放于內(nèi)核中,變成僵尸(Zombie)進(jìn)程。
特別注意,僵尸進(jìn)程是不能使用kill命令清除掉的。因?yàn)閗ill命令只是用來(lái)終止進(jìn)程的,而僵尸進(jìn)程已經(jīng)終止。思考!用什么辦法可清除掉僵尸進(jìn)程呢?
方一:wait函數(shù)。
方二:殺死他的父進(jìn)程使其變成孤兒進(jìn)程,進(jìn)而被系統(tǒng)處理。
示例:
孤兒進(jìn)程
1 #include
僵尸進(jìn)程
1 #include
(1)相關(guān)函數(shù)
1)wait
一個(gè)進(jìn)程在終止時(shí)會(huì)關(guān)閉所有文件描述符,釋放在用戶(hù)空間分配的內(nèi)存,但它的PCB還保留著,內(nèi)核在其中保存了一些信息:如果是正常終止則保存著退出狀態(tài),如果是異常終止則保存著導(dǎo)致該進(jìn)程終止的信號(hào)是哪個(gè)。這個(gè)進(jìn)程的父進(jìn)程可以調(diào)用wait或waitpid獲取這些信息,然后徹底清除掉這個(gè)進(jìn)程。我們知道一個(gè)進(jìn)程的退出狀態(tài)可以在Shell中用特殊變量$?查看,因?yàn)镾hell是它的父進(jìn)程,當(dāng)它終止時(shí)Shell調(diào)用wait或waitpid得到它的退出狀態(tài)同時(shí)徹底清除掉這個(gè)進(jìn)程。
父進(jìn)程調(diào)用wait函數(shù)可以回收子進(jìn)程終止信息。該函數(shù)有三個(gè)功能:
阻塞等待子進(jìn)程退出
回收子進(jìn)程殘留資源
獲取子進(jìn)程結(jié)束狀態(tài)(退出原因)
當(dāng)進(jìn)程終止時(shí),操作系統(tǒng)的隱式回收機(jī)制會(huì):1.關(guān)閉所有文件描述符 2. 釋放用戶(hù)空間分配的內(nèi)存。內(nèi)核的PCB仍存在。其中保存該進(jìn)程的退出狀態(tài)。(正常終止→退出值;異常終止→終止信號(hào))
可使用wait函數(shù)傳出參數(shù)status來(lái)保存進(jìn)程的退出狀態(tài)(status只是一個(gè)整型變量,不能很精確的描述出狀態(tài)),因此需要借助宏函數(shù)來(lái)進(jìn)一步判斷進(jìn)程終止的具體原因。宏函數(shù)可分為如下三組:
WIFEXITED(status) 為非0 → 進(jìn)程正常結(jié)束
WEXITSTATUS(status) 如上宏為真,使用此宏 → 獲取進(jìn)程退出狀態(tài) (exit的參數(shù))
WIFSIGNALED(status) 為非0 → 進(jìn)程異常終止
WTERMSIG(status) 如上宏為真,使用此宏 → 取得使進(jìn)程終止的那個(gè)信號(hào)的編號(hào)。
WIFSTOPPED(status) 為非0 → 進(jìn)程處于暫停狀態(tài)
WSTOPSIG(status) 如上宏為真,使用此宏 → 取得使進(jìn)程暫停的那個(gè)信號(hào)的編號(hào)。
WIFCONTINUED(status) 為真 → 進(jìn)程暫停后已經(jīng)繼續(xù)運(yùn)行
wait 函數(shù)作用:1)阻塞等待 2)回收子進(jìn)程資源 3)查看死亡原因
頭文件
#include
函數(shù)原型
pid_t wait(int *status);
參數(shù)說(shuō)明:
status傳出參數(shù),用來(lái)獲取子進(jìn)程退出的狀態(tài)。
返回值:
成功:返回終止的子進(jìn)程pid
失敗 返回-1,設(shè)置errno
子進(jìn)程的死亡原因:
正常死亡 WIFEXITED,如果WIFEXITED為真,使用WEXITSTATUS得到退出狀態(tài)。
非正常死亡WIFSIGNALED,如果WIFSIGNALED為真,使用WTERMSIG得到信號(hào)。
wait回收子進(jìn)程
1 #include
wait查看子進(jìn)程死亡原因
1 #include
2)waitpid
作用同wait,但可指定pid進(jìn)程清理,可以不阻塞。
頭文件
#include
函數(shù)原型
pid_t waitpid(pid_t pid, int *status, int options);
參數(shù)說(shuō)明:
pid:
? < -1 組id
? -1 回收任意
? 0 回收和調(diào)用進(jìn)程組id相同組內(nèi)的子進(jìn)程
? >0 回收指定的pid
options
0與wait形同,也會(huì)阻塞
WNOHANG 如果當(dāng)前沒(méi)有子進(jìn)程退出的,會(huì)立即返回。
返回值:
如果設(shè)置了WNOHANG,那么如果沒(méi)有子進(jìn)程退出,返回0。
如果有子進(jìn)程退出,返回退出子進(jìn)程的pid
失敗: 返回-1(沒(méi)有子進(jìn)程),設(shè)置errno
注意:一次wait或waitpid調(diào)用只能清理一個(gè)子進(jìn)程,清理多個(gè)子進(jìn)程應(yīng)使用循環(huán)。
waitpid回收子進(jìn)程
1 #include
wait回收多個(gè)子進(jìn)程
1 #include
waitpid回收多個(gè)子進(jìn)程
1 #include
練習(xí):
1. 父進(jìn)程fork 3 個(gè)子進(jìn)程,三個(gè)子進(jìn)程一個(gè)調(diào)用ps命令, 一個(gè)調(diào)用自定義程序1(正常),一個(gè)調(diào)用自定義程序2(會(huì)出段錯(cuò)誤)。父進(jìn)程使用waitpid對(duì)其子進(jìn)程進(jìn)行回收?
2. 驗(yàn)證子進(jìn)程是否共享文件描述符,子進(jìn)程負(fù)責(zé)寫(xiě)入數(shù)據(jù),父進(jìn)程負(fù)責(zé)讀數(shù)據(jù)?
父子進(jìn)程共享文件描述符
1 #include
結(jié)論:父子進(jìn)程共享文件描述符。
Linux 任務(wù)調(diào)度
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶(hù)投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶(hù)投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。