西安知名網(wǎng)站建設(shè)公司排名seo網(wǎng)絡(luò)優(yōu)化前景怎么樣
🌈環(huán)境變量
🍄初識
系統(tǒng)帶的命令可以直接運行(ls ll命令等),但是我們自己寫的命令必須要帶上路徑才能運行(./myproc),這是什么原因?qū)е碌?#xff1f;如果我們也想自己寫的命令直接運行不帶路徑怎么搞? ----接下來揭曉答案
?用ll明了直接顯示處理三個文件。但是要運行我們自己創(chuàng)建的文件就要帶上目錄:這些就和環(huán)境變量有關(guān)。
- 看環(huán)境變量內(nèi)容:
echo $PATH //顯示操作系統(tǒng)搜索可執(zhí)行文件的路徑
- 可以看到環(huán)境變量PATH當(dāng)中有多條路徑,這些路徑由冒號隔開,當(dāng)你使用ls命令時,系統(tǒng)就會查看環(huán)境變量PATH,然后默認(rèn)從左到右依次在各個路徑當(dāng)中進行查找。
- ?像上面的ls命令啥的都在環(huán)境變量中儲存了相應(yīng)路徑。?
🐴設(shè)置自己的環(huán)境變量
這里我們也可以將自己寫的命令設(shè)置成環(huán)境變量,一般有兩種做法,這里我只推薦一種。
將我們的命令復(fù)制到環(huán)境變量下:
- pwd查看變量路徑
- 將路徑復(fù)制到環(huán)境變量路徑set PATH=$PATH+復(fù)制的路徑
- echo $PATH,如果目錄上沒有就把2命令的set換成export再來一遍就行了。
🐴常見的環(huán)境變量:
PATH:用于指定系統(tǒng)中可執(zhí)行文件的路徑,當(dāng)你在命令行中輸入一個命令時,系統(tǒng)會在 PATH 變量指定的路徑中查找該命令的可執(zhí)行文件。
HOME:用于指定當(dāng)前用戶的主目錄路徑。
USER:用于指定當(dāng)前用戶名。
SHELL:用于指定當(dāng)前用戶所使用的 Shell 程序路徑。
🐴對于 Linux 操作系統(tǒng),下面是一些常用的環(huán)境變量命令:
env
:顯示當(dāng)前所有環(huán)境變量。export var=value
:設(shè)置一個環(huán)境變量。echo $var
:顯示一個環(huán)境變量的值。echo $PATH
:顯示當(dāng)前的 PATH 環(huán)境變量設(shè)置。export PATH=$PATH:new_path
:在 PATH 環(huán)境變量中添加一個新的路徑。clear
:清除屏幕上的輸出。exit
:關(guān)閉當(dāng)前的終端窗口。
🍄環(huán)境變量對進程的作用?
環(huán)境變量對進程的作用非常重要,因為它們提供了進程可以訪問的信息和資源,包括但不限于以下內(nèi)容:
系統(tǒng)配置信息:例如PATH環(huán)境變量包含了可執(zhí)行文件的路徑列表,進程可以通過訪問該變量來查找和執(zhí)行命令。
用戶信息:例如HOME環(huán)境變量包含了當(dāng)前用戶的家目錄路徑,進程可以通過訪問該變量來獲取用戶的工作目錄。
語言和區(qū)域設(shè)置:例如LANG環(huán)境變量包含了當(dāng)前系統(tǒng)的語言設(shè)置,進程可以通過訪問該變量來確定如何顯示和處理文本數(shù)據(jù)。
總之,環(huán)境變量是進程與操作系統(tǒng)之間進行通信和協(xié)作的一種重要方式。它們?yōu)檫M程提供了必要的信息和資源,使得進程能夠正常運行和完成任務(wù)。
🌈程序地址空間
程序地址空間指的是一個進程在運行時所使用的虛擬內(nèi)存空間。在現(xiàn)代操作系統(tǒng)中,每個進程都被分配了一個獨立的虛擬地址空間,該空間包含了程序的代碼、數(shù)據(jù)、堆棧等部分。
- 程序地址空間通常被分為以下幾個部分:
-
代碼段(text segment):包含程序的機器指令,通常是只讀的。
-
數(shù)據(jù)段(data segment):包含程序中已經(jīng)被初始化的全局變量和靜態(tài)變量。
-
BSS段(bss segment):包含程序中未被初始化的全局變量和靜態(tài)變量,其值通常為0。
-
堆(heap):包含由程序在運行時動態(tài)分配的內(nèi)存,通常是由調(diào)用malloc等函數(shù)分配的。
-
棧(stack):包含函數(shù)調(diào)用時的局部變量、函數(shù)參數(shù)以及函數(shù)返回地址等信息。
程序地址空間在虛擬內(nèi)存中,需要由操作系統(tǒng)管理,操作系統(tǒng)為每個進程分配一段獨立的虛擬地址空間,并映射到物理內(nèi)存中,以便程序能夠訪問所需的內(nèi)存。操作系統(tǒng)還提供了一些系統(tǒng)調(diào)用,使得進程可以動態(tài)地請求更多的內(nèi)存,或者釋放已經(jīng)不需要的內(nèi)存。
?
在這個代碼中,變量a是第一個被定義的,因此它被分配了最高的地址。變量b是第二個被定義的,因此它被分配在a的下方。同樣,變量c是第三個被定義的,因此它被分配在b的下方。因此,這些變量在棧上的地址大小應(yīng)該是按照以下順序分配的:
- 變量a:最高的地址,地址值最大。(棧底)
- 變量b:次高的地址,地址值稍小于a的地址。
- 變量c:最低的地址,地址值最小。(棧頂)
這個棧是倒著放的,不是我們固定思維的正放。
接下來再看一個代碼:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>int un_g_val; //未初始化全局變量int g_val = 100; //初始化全局變量int main(int argc, char *argv[]){printf("text: %p\n", main);printf("uninit: %p\n", &un_g_val);printf("init: %p\n", &g_val);char* p = (char*)malloc(16); printf("heap: % p\n", p); //堆printf("stack:%p\n", &p); //棧return 0;}
?這個代碼就驗證了上面地址的情況,注意!我寫的未初始化全局區(qū)和已初始化全局區(qū)的順序錯了,所以地址才不是連續(xù)增大的。
- 接下來我們 我們看一段代碼
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()
{pid_t id = fork();if(id < 0){perror("fork");return 0;}else if(id == 0){ //child,子進程肯定先跑完,也就是子進程先修改,完成之后,父進程再讀取g_val=100;printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);}else{ //parentsleep(3);printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);}sleep(1);return 0;
}
?兩者輸入的內(nèi)容不一樣,但是地址確實一樣的那說明啥?他們肯定不是存在物理內(nèi)存上。如果存的物理地址一樣,那這個地址存的內(nèi)容必定一樣。就比如從學(xué)?;丶?。今天可以做1路汽車在少年宮下,然后轉(zhuǎn)2路到家。你也可以坐66路公交到天涯海角棧在坐666路到家。所以他們一定不是存在物理內(nèi)存。而是存在虛擬地址上
👿進程地址空間不是物理地址
鐵汁們,進程地址空間不是實際上的物理地址,而是虛擬的地址。所以原來我們在C語言學(xué)的地址空間也不是真正意義上的物理地址。
- 進程地址空間通常是虛擬地址空間,而不是物理地址空間。在操作系統(tǒng)中,每個進程都有自己的地址空間,其中包含該進程的代碼、數(shù)據(jù)和堆棧等。這個地址空間是虛擬的,因為它只是一個進程視為自己的地址空間,而不是實際的物理地址空間。
- 當(dāng)進程在執(zhí)行時,它的虛擬地址需要被映射到實際的物理地址。這個映射由操作系統(tǒng)內(nèi)核中的內(nèi)存管理單元(MMU)完成,它將進程的虛擬地址轉(zhuǎn)換為對應(yīng)的物理地址。這個轉(zhuǎn)換是透明的,因此進程可以像它正在訪問實際物理內(nèi)存一樣訪問它的虛擬地址空間。
- 總之,進程的地址空間是虛擬地址空間,需要通過內(nèi)存管理單元將其映射到物理地址空間才能在實際的硬件上執(zhí)行。
- 以前學(xué)的地址都不是物理地址全部都是虛擬地址。
🍄進程地址空間
?每一個進程在創(chuàng)建后操作系統(tǒng)就會給他配置一個虛擬地址用來存放這個進程的內(nèi)容。進程地址空間的本質(zhì)也是內(nèi)核的一種數(shù)據(jù)結(jié)構(gòu)和PCB相似。在Linux當(dāng)中進程地址空間具體由結(jié)構(gòu)體mm_struct實現(xiàn)。進程地址空間就類似于一把尺子,尺子的刻度由0x00000000到0xffffffff,尺子按照刻度被劃分為各個區(qū)域,例如代碼區(qū)、堆區(qū)、棧區(qū)等。而在結(jié)構(gòu)體mm_struct當(dāng)中,便記錄了各個邊界刻度,例如代碼區(qū)的開始刻度與結(jié)束刻度,如下圖所示:
?而想把進程地址空間和物理空間進行聯(lián)系起來需要用到一個介質(zhì)頁表(也是一種數(shù)據(jù)結(jié)構(gòu))虛擬地址通過頁表的映射存儲到物理內(nèi)存的不同地方。到這里,肯定有很多疑問,別急,我們來一一講解。
?👿為什么不是直接存到物理地址
內(nèi)存管理的需要:操作系統(tǒng)需要對內(nèi)存進行管理,包括內(nèi)存的分配、釋放、保護和共享等。如果讓進程直接存在物理內(nèi)存,就會給內(nèi)存管理帶來很大的困難,而虛擬地址空間可以方便地進行內(nèi)存管理。
多道程序設(shè)計的需要:現(xiàn)代操作系統(tǒng)通常支持多道程序設(shè)計,這意味著多個進程可以同時運行在計算機上。如果讓進程直接存在物理內(nèi)存,就會導(dǎo)致不同進程之間的內(nèi)存沖突和資源競爭,從而影響系統(tǒng)的正常運行。
內(nèi)存保護的需要:操作系統(tǒng)需要保護系統(tǒng)的內(nèi)存不被未經(jīng)授權(quán)的進程訪問和破壞。如果讓進程直接存在物理內(nèi)存,就很難進行內(nèi)存保護,而虛擬地址空間可以為每個進程分配獨立的地址空間,從而實現(xiàn)內(nèi)存保護。
內(nèi)存利用率的需要:直接存到物理內(nèi)存會導(dǎo)致物理內(nèi)存的使用不充分,出現(xiàn)碎片化現(xiàn)象。
👿給進程虛擬地址空間的好處
提高內(nèi)存利用率:虛擬地址空間允許多個進程共享同一塊物理內(nèi)存,從而提高內(nèi)存利用率。當(dāng)一個進程需要更多的內(nèi)存時,操作系統(tǒng)可以動態(tài)地為其分配更多的虛擬地址空間,而不必實際增加物理內(nèi)存的大小。
實現(xiàn)內(nèi)存保護:虛擬地址空間可以為每個進程分配單獨的地址空間,從而防止進程相互干擾或破壞。操作系統(tǒng)可以利用內(nèi)存保護機制來確保每個進程只能訪問其分配的虛擬地址空間,從而保護系統(tǒng)的安全性和穩(wěn)定性。
支持多道程序設(shè)計:虛擬地址空間支持多道程序設(shè)計,使得多個進程可以在同一時間運行。每個進程都可以擁有自己的地址空間,從而避免了不同進程之間的地址沖突和資源競爭,確保了系統(tǒng)的正常運行。
支持動態(tài)鏈接庫:虛擬地址空間還可以支持動態(tài)鏈接庫,這使得多個進程可以共享同一份庫文件,從而減少了內(nèi)存使用和磁盤空間的占用。
便于內(nèi)存管理:虛擬地址空間的使用使得操作系統(tǒng)可以更方便地進行內(nèi)存管理。
👿虛擬地址空間和物理內(nèi)存的鏈接
虛擬地址要想和物理內(nèi)存聯(lián)系起來就需要一種內(nèi)核數(shù)據(jù)結(jié)構(gòu)----頁表。
為什么會有頁表?
- 如果這個進程是錯誤的,那它的虛擬地址寫到物理內(nèi)存上后操作系統(tǒng)無法正確讀取這個信息,相當(dāng)于浪費了很多不必要的空間。
- 其次進程這么多,這個虛擬地址映射到這里,那個映射到那里。如果我們像運行這個進程,那咋去找到它?所以操作系統(tǒng)需要記錄下每一個進程存在的位置。
所以進程的作用:
內(nèi)存管理:頁表可以將虛擬地址空間劃分為固定大小的頁,每個頁對應(yīng)著一段連續(xù)的物理內(nèi)存區(qū)域。這樣一來,多個進程可以共享同一塊物理內(nèi)存,而不需要每個進程都分配獨立的物理內(nèi)存空間。
虛擬化:頁表實現(xiàn)了虛擬內(nèi)存的概念,使得進程可以在自己的虛擬地址空間中執(zhí)行,而不需要考慮物理內(nèi)存的具體細(xì)節(jié)。這樣一來,操作系統(tǒng)可以更加靈活地管理內(nèi)存資源,從而提高系統(tǒng)的整體性能和穩(wěn)定性。
動態(tài)分配:當(dāng)一個進程需要更多的內(nèi)存時,操作系統(tǒng)可以動態(tài)地為其分配新的虛擬地址空間,并將其映射到物理內(nèi)存上,而不需要為其分配新的物理內(nèi)存空間。反之,當(dāng)一個進程不再需要某個虛擬地址空間時,操作系統(tǒng)可以將其對應(yīng)的物理內(nèi)存釋放,并將虛擬地址空間與物理內(nèi)存之間的映射關(guān)系刪除,以便于更好地利用內(nèi)存資源。
內(nèi)存保護:頁表還可以提供一些額外的保護和安全特性,如權(quán)限控制、內(nèi)存保護等。例如,操作系統(tǒng)可以將某些頁面標(biāo)記為只讀,從而防止程序?qū)ζ溥M行寫入操作,保護系統(tǒng)的穩(wěn)定性和安全性。
🍄寫時拷貝?
我們再回到上面那個奇怪的代碼:為啥父子進程的物理內(nèi)存一樣,但是內(nèi)容卻不一樣?
這里就用到了一門新技術(shù):寫時拷貝
- 寫時拷貝(Copy-On-Write,COW)是一種優(yōu)化技術(shù),用于優(yōu)化在進程復(fù)制時涉及的內(nèi)存使用。在寫時拷貝技術(shù)中,操作系統(tǒng)在內(nèi)存中為子進程創(chuàng)建一個共享原始內(nèi)存區(qū)域的虛擬副本,而不是創(chuàng)建一個完整的父進程物理副本。
- 寫時拷貝技術(shù)的主要優(yōu)點是可以避免在進程復(fù)制時產(chǎn)生額外的物理內(nèi)存開銷。因為新進程與原始進程共享相同的物理內(nèi)存頁面,所以只有在其中一個進程需要修改這些頁面時,才會產(chǎn)生額外的物理內(nèi)存開銷。
- 如果說無腦為子進程創(chuàng)建父進程的副本,那子進程的很多代碼和數(shù)據(jù)用不到就會造成資源和空間的浪費。所以寫時拷貝非常不錯。
👿寫時拷貝的優(yōu)點?
節(jié)省內(nèi)存開銷:寫時拷貝技術(shù)可以在進程復(fù)制時避免不必要的物理內(nèi)存開銷。因為新進程與原始進程共享相同的物理內(nèi)存頁面,所以只有在其中一個進程需要修改這些頁面時,才會產(chǎn)生額外的物理內(nèi)存開銷。
提高程序運行效率:寫時拷貝技術(shù)可以提高程序的運行效率,因為它避免了不必要的物理內(nèi)存拷貝。如果進程在復(fù)制時需要完全復(fù)制物理內(nèi)存,那么進程復(fù)制的時間和開銷將會很大。使用寫時拷貝技術(shù),操作系統(tǒng)只需要復(fù)制虛擬地址空間的頁表條目,而不需要真正復(fù)制整個物理內(nèi)存頁面,因此可以節(jié)省很多時間和資源。
保護數(shù)據(jù)安全:寫時拷貝技術(shù)可以保護原始數(shù)據(jù)的安全性。因為新進程與原始進程共享相同的物理內(nèi)存頁面,所以當(dāng)一個進程對共享頁面進行寫操作時,它不會影響到原始進程的數(shù)據(jù)。這可以避免數(shù)據(jù)的意外修改和損壞。
支持共享內(nèi)存:寫時拷貝技術(shù)支持共享內(nèi)存,使得多個進程可以共享同一塊物理內(nèi)存區(qū)域。如果沒有寫時拷貝技術(shù),共享內(nèi)存將會很困難,因為多個進程可能會同時修改同一塊物理內(nèi)存,導(dǎo)致數(shù)據(jù)的混亂和錯誤。而使用寫時拷貝技術(shù),多個進程可以共享相同的物理內(nèi)存頁面,并且只有在其中一個進程需要修改頁面時才會發(fā)生復(fù)制,從而保證了共享內(nèi)存的正確性和安全性。
所以我們再分析上面的代碼:?
- 剛開始父進程拷貝了一個子進程,然后父與子進程共享數(shù)據(jù)和代碼。后面子進程的全局變量g_val修改,修改后的全局變量區(qū)映射到頁表的其他地方,然后頁表又映射到?物理內(nèi)存的其他位置。所以才會出現(xiàn)地址相同,但是打印的結(jié)果不同。
- 這里通過寫時拷貝把修改后的子進程虛擬地址給頁表。而不是子進程完全復(fù)制一份父進程。
- 物理地址肯定是發(fā)生改變的,那個不變的地址是虛擬地址,別搞混虛擬地址和物理地址。
注意:之所以頁表映射有藍色,有紅色是因為不同內(nèi)容要存在不同地方。全局變量存在已初始化全局區(qū)。函數(shù)等存在棧區(qū)等等,所以有藍色,有紅色?。
🌈進程終止
🍄進程退出的場景
- 代碼運行完畢, 結(jié)果正確------皆大歡喜
- 代碼運行完畢,結(jié)果錯誤------- 小小失落
- 代碼異常終止------撓頭抓耳
- 進程終止是指進程的運行被終止或者結(jié)束。進程終止可以是正常的結(jié)束,也可以是異常的終止。正常的結(jié)束通常是指進程完成了它的任務(wù),或者根據(jù)某些條件主動地調(diào)用了系統(tǒng)調(diào)用來終止自己。在這種情況下,操作系統(tǒng)會釋放進程所占用的資源,并將進程從系統(tǒng)中移除。
- 而異常的終止則是指進程在執(zhí)行過程中出現(xiàn)了錯誤或者被強制終止。例如,進程訪問了不存在的內(nèi)存地址,或者收到了操作系統(tǒng)發(fā)出的信號來強制終止。在這種情況下,操作系統(tǒng)會立即中止進程的執(zhí)行,并回收進程所占用的資源。
?🍄進程退出碼
echo $?
#include<stdio.h>
#include<stdlib.h>int main()
{printf("hello world!");return 0;
}
?0就表示進程正常退出,如果是非0值代表的進程異常退出,并且這個值還代表這個進程所報錯誤。可以通過strerror來看錯誤碼!
#include<stdio.h>
#include<string.h>int main()
{int i=0; //有的編譯器不支持把i=0寫道for里面for(i;i<150;i++){printf("%d:%s\n",i, strerror(i)); }return 0;
}
- ?如果我們隨便輸入一個命令:
?🍄進程正常退出
👿return退出
在函數(shù)中我們經(jīng)常都是return 0 ,這個就告訴函數(shù)程序執(zhí)行完要退出了。但是要想要想進程退出那就不能用它了。
👿exit/_exit退出
如果我們想讓進程退出,那我們就要用到其他的函數(shù)exit, _exit。
exit函數(shù)
使用exit函數(shù)退出進程也是我們常用的方法,exit函數(shù)可以在代碼中的任何地方退出進程,并且exit函數(shù)在退出進程前會做一系列工作:
- 執(zhí)行用戶通過atexit或on_exit定義的清理函數(shù)。
- 關(guān)閉所有打開的流,所有的緩存數(shù)據(jù)均被寫入。
- 調(diào)用_exit函數(shù)終止進程。
?這個細(xì)節(jié)就是在執(zhí)行exit進行退出時編譯器打印了上面的命令 I am a precess!.注意下面操作
?_exit
這個不常用,相比exit,但是我們可以了解一下,_exit函數(shù)也可以在代碼中的任何地方退出進程,但是_exit函數(shù)會直接終止進程,并不會在退出進程前會做任何收尾工作。
我們把前面的代碼稍微修改一下即可:
?👿return, exit, _exit區(qū)別
return
語句用于函數(shù)返回一個值,并將控制流程返回到調(diào)用該函數(shù)的地方。exit()
函數(shù)用于終止程序的運行并返回到操作系統(tǒng),它會在退出之前執(zhí)行所有必要的清理操作(例如關(guān)閉打開的文件、釋放內(nèi)存等)。在調(diào)用exit()
函數(shù)之前,程序可以正常地執(zhí)行所有必要的操作,因此exit()
函數(shù)是安全的程序退出方式。_exit()
函數(shù)與exit()
函數(shù)類似,都用于終止程序的運行并返回到操作系統(tǒng),但它會立即退出程序,不會執(zhí)行任何清理操作,這可能會導(dǎo)致數(shù)據(jù)損壞或資源泄漏等問題。因此,除非必須要立即退出程序且不需要進行清理操作,否則應(yīng)該使用exit()
函數(shù)。
🌈進程等待?
🍄什么是?為啥有?
進程等待是指父進程在創(chuàng)建子進程后,通過調(diào)用
wait()
或waitpid()
等函數(shù),等待子進程結(jié)束并獲取子進程的退出狀態(tài)。進程等待的主要目的是讓父進程能夠控制子進程的執(zhí)行順序,并在必要時對子進程進行處理。進程等待的必要性
- 獲取子進程退出狀態(tài):進程等待還可以讓父進程獲取子進程的退出狀態(tài),這對于調(diào)試和排錯非常有用。父進程可以根據(jù)子進程的退出狀態(tài)來判斷子進程是否正常退出,以及出錯的原因。
- 控制子進程執(zhí)行順序:如果父進程需要等待多個子進程執(zhí)行完畢后再進行后續(xù)操作,那么可以使用進程等待來控制子進程的執(zhí)行順序。父進程可以在需要等待的地方調(diào)用
wait()
或waitpid()
函數(shù),以確保子進程已經(jīng)執(zhí)行完畢。- 避免子進程成為僵尸進程:如果父進程沒有及時處理子進程的退出狀態(tài),那么子進程就會成為僵尸進程,占用系統(tǒng)資源并導(dǎo)致一些問題。通過進程等待,父進程可以及時處理子進程的退出狀態(tài),避免出現(xiàn)僵尸進程。
🍄進程等待的方法?
👿wait等待
wait()是一個系統(tǒng)調(diào)用,用于等待子進程結(jié)束并獲取子進程的退出狀態(tài)。它的原型如下:
#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int *status);
- wait() 函數(shù)的返回值是子進程的進程號,如果出錯則返回 -1。
- 如果父進程沒有子進程,那么 wait() 函數(shù)會立即返回。?
wait()
函數(shù)的參數(shù)status
是一個整型指針,用于存儲子進程的退出狀態(tài)。如果子進程正常結(jié)束,它的退出狀態(tài)會被存儲在status
中,父進程可以通過WIFEXITED(status)
和WEXITSTATUS(status)
宏來獲取子進程的退出狀態(tài)。如果子進程異常結(jié)束,例如被信號終止,那么status
中存儲的是一個描述異常結(jié)束原因的信號值。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{pid_t id = fork();if(id == 0){//childint count = 10;while(count--){printf("I am child...PID:%d, PPID:%d\n", getpid(), getppid());sleep(1);}exit(0);}//fatherint status = 0;pid_t ret = wait(&status);if(ret > 0){//wait successprintf("wait child success...\n");if(WIFEXITED(status)){//exit normalprintf("exit code:%d\n", WEXITSTATUS(status));}}sleep(3);return 0;
}
我們用這個腳本:?
while :; do ps axj | head -1 && ps axj | grep proc | grep -v grep;echo "######################";sleep 1;done
👿waitpid等待
waitpid
是一個系統(tǒng)調(diào)用函數(shù),它用于等待指定進程的狀態(tài)改變,例如進程終止或暫停等。waitpid允許父進程等待它的一個子進程的狀態(tài)改變,而不必阻塞整個進程。這使得父進程能夠同時等待多個子進程的狀態(tài)改變。
- 函數(shù)原型:
pid_t waitpid(pid_t pid, int *status, int options);
pid
參數(shù)指定要等待的進程的進程ID,如果指定為-1
,則等待任何子進程。status
參數(shù)是一個整數(shù)指針,用于存儲子進程的退出狀態(tài)或其他信息。options
參數(shù)用于指定等待的進程狀態(tài),如WNOHANG
選項表示在沒有狀態(tài)改變的情況下立即返回,而不是阻塞等待狀態(tài)改變。當(dāng)
waitpid
成功時,返回終止或暫停進程的進程ID,如果沒有進程處于指定狀態(tài),則返回0。如果出錯,則返回-1。請注意,
waitpid
只能等待子進程的狀態(tài)改變,如果要等待其他進程的狀態(tài)改變,可以使用其他函數(shù),如wait
和waitid
。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{pid_t id = fork();if (id == 0){//child int count = 10;while (count--){printf("I am child...PID:%d, PPID:%d\n", getpid(), getppid());sleep(1);}exit(0);}//father int status = 0;pid_t ret = waitpid(id, &status, 0);if (ret >= 0){//wait success printf("wait child success...\n");if (WIFEXITED(status)){//exit normal printf("exit code:%d\n", WEXITSTATUS(status));}else{//signal killed printf("killed by siganl %d\n", status & 0x7F);}}sleep(3);return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{pid_t id = fork();if (id == 0){int count = 10;while (count--){printf("I am child...PID:%d, PPID:%d\n", getpid(), getppid());sleep(1);}exit(0);}int status = 0;pid_t ret = waitpid(id, &status, 0);if (ret >= 0){printf("wait child success...\n");if (WIFEXITED(status)){printf("exit code:%d\n", WEXITSTATUS(status));}else{printf("killed by siganl %d\n", status & 0x7F);}}sleep(3);return 0;
}
在父進程運行過程中,我們可以嘗試使用kill -9命令將子進程殺死,這時父進程也能等待子進程成功。 但是被信號殺死而退出的進程,其退出碼將沒有意義。
🍄獲取子進程status
- wait, waitpid函數(shù)都有status,status存的是進程的狀態(tài)。
- 如果傳遞NULL,表示不關(guān)心子進程的退出狀態(tài)信息。
- 否則,操作系統(tǒng)會根據(jù)該參數(shù),將子進程的退出信息反饋給父進程。
- status不能簡單的當(dāng)作整形來看待,可以當(dāng)作位圖來看待,具體細(xì)節(jié)如下圖(只研究status低16比特位):
在status的低16比特位當(dāng)中,高8位表示進程的退出狀態(tài),即退出碼。進程若是被信號所殺,則低7位表示終止信號,而第8位比特位是core dump標(biāo)志。
如果我們想得到進程的退出碼和退出信號就可以使用位運算:
exitCode = (status >> 8) & 0xFF; //退出碼
exitSignal = status & 0x7F; //退出信號
對于此,系統(tǒng)當(dāng)中提供了兩個宏來獲取退出碼和退出信號。
- WIFEXITED(status):用于查看進程是否是正常退出,本質(zhì)是檢查是否收到信號。
- WEXITSTATUS(status):用于獲取進程的退出碼。
exitNormal = WIFEXITED(status); ?//是否正常退出
exitCode = WEXITSTATUS(status); ?//獲取退出碼
需要注意的是,當(dāng)一個進程非正常退出時,說明該進程是被信號所殺,那么該進程的退出碼也就沒有意義了。
👿wait和waitpid的區(qū)別:
- wait函數(shù)會等待任何子進程結(jié)束并獲取其退出狀態(tài),而waitpid函數(shù)只會等待指定的子進程結(jié)束并獲取其退出狀態(tài)。
- wait函數(shù)沒有提供獲取子進程終止原因的選項,而waitpid函數(shù)可以通過指定選項來獲取子進程的終止原因(例如,被信號終止還是正常退出)。
waitpid函數(shù)的原型為:
pid_t waitpid(pid_t pid, int *status, int options);
其中,pid參數(shù)指定要等待的子進程的PID。如果pid為-1,則等待任何子進程結(jié)束。status參數(shù)用于存儲子進程的退出狀態(tài)。options參數(shù)可以指定等待子進程的行為,例如是否阻塞等待、是否只等待被特定信號中斷的子進程等等。
wait函數(shù)的原型為:
pid_t wait(int *status);
其中,status參數(shù)用于存儲子進程的退出狀態(tài)。
- 如果沒有子進程在運行,wait函數(shù)會阻塞程序,直到有子進程結(jié)束為止,而waitpid函數(shù)可以通過指定WNOHANG選項來避免阻塞。
- 如果子進程已經(jīng)結(jié)束,wait函數(shù)和waitpid函數(shù)都會立即返回,且獲取到子進程的退出狀態(tài)。如果子進程尚未結(jié)束,wait函數(shù)和waitpid函數(shù)都會等待子進程結(jié)束并獲取其退出狀態(tài)。
- 在多進程程序中,waitpid函數(shù)可以通過指定pid參數(shù)來等待特定的子進程結(jié)束。如果不指定pid參數(shù)或pid為-1,則等待任何子進程結(jié)束。
- 在使用這些函數(shù)時,需要注意避免出現(xiàn)僵尸進程,即已經(jīng)結(jié)束但父進程尚未調(diào)用wait或waitpid來獲取退出狀態(tài)的子進程??梢允褂眯盘柼幚砗瘮?shù)或者使用waitpid函數(shù)中的WNOHANG選項來避免出現(xiàn)僵尸進程。