? ? ? 進(jìn)程的啟動(dòng)和終止
內(nèi)核執(zhí)行c程序時(shí),利用exec函數(shù)調(diào)用一個(gè)特殊的啟動(dòng)例程,該啟動(dòng)例程叢內(nèi)核中獲取命令行參數(shù)和環(huán)境變量值。
進(jìn)程終止的情況
5種正常終止的情況:
(1)從main函數(shù)返回;(2)調(diào)用exit;(3)調(diào)用_exit和_Exit函數(shù);(4)最后一個(gè)線程調(diào)用pthread_exit;(5)最后一個(gè)線程從其啟動(dòng)例程返回;
3種異常終止情況
(1)調(diào)用abort;(2)接到一個(gè)信號(hào);(3)最后一個(gè)線程對(duì)取消請(qǐng)求做出響應(yīng);
進(jìn)程啟動(dòng)和終止圖
atexit函數(shù)
一個(gè)進(jìn)程最多可以登記32和函數(shù)(例如:signal函數(shù)),這些函數(shù)由exit函數(shù)自動(dòng)調(diào)用。在程序終止時(shí)調(diào)用這些函數(shù),形成終止處理程序,來進(jìn)行結(jié)束進(jìn)程前的收尾工作。而exit函數(shù)通過atexit函數(shù)的登記記錄來判斷調(diào)用哪些函數(shù)。
exit函數(shù)
此函數(shù)由ISO C 定義,其操作包括處理終止處理程序,然后關(guān)閉所有標(biāo)準(zhǔn)I/O流。需要注意的是,它不會(huì)處理文件描述符、多進(jìn)程(父子進(jìn)程)以及作業(yè)控制。
_e(E)xit函數(shù)
ISO C 定義這個(gè)函數(shù)的目的是為進(jìn)程提供一種無需運(yùn)行終止處理程序或信號(hào)處理函數(shù)的方法而終止程序。但I(xiàn)SO C 對(duì)標(biāo)準(zhǔn)I/O流是否進(jìn)行沖洗,這取決于操作系統(tǒng)的實(shí)現(xiàn)。在unix中,是不進(jìn)行沖洗的。
exit和_e(E)ixt函數(shù)的狀態(tài)碼
無論進(jìn)程怎樣結(jié)束,它都會(huì)在內(nèi)核上執(zhí)行同一段代碼(由進(jìn)程啟動(dòng)和退出圖可知)。這段代碼來關(guān)閉所有的文件描述符,釋放所有的存儲(chǔ)空間。
程序退出后,利用退出碼告知該進(jìn)程的父進(jìn)程。父進(jìn)程通過wait或waitpid函數(shù)來完成該子進(jìn)程的善后工作(獲取子進(jìn)程相關(guān)信息 釋放子進(jìn)程占用資源)。若父進(jìn)程沒有處理子進(jìn)程的退出狀態(tài),則子進(jìn)程變成僵死進(jìn)程。相反的,若父進(jìn)程在子進(jìn)程前終止,則子進(jìn)程變成孤兒進(jìn)程。孤兒進(jìn)程會(huì)由1號(hào)進(jìn)程(init進(jìn)程)接收,大致過程如下:
(1)進(jìn)程終止時(shí),內(nèi)核逐個(gè)檢查所有活動(dòng)的進(jìn)程;(2)分析查找該終止進(jìn)程的子進(jìn)程;(3)將該進(jìn)程的子進(jìn)程的父進(jìn)程ID改為1;
wait和waitpid函數(shù)
程序正?;虍惓=K止時(shí),內(nèi)核都會(huì)向父進(jìn)程發(fā)送SIGNAL信號(hào)。子進(jìn)程終止是異步事件,所以該信號(hào)也是異步信號(hào)。而該信號(hào)一般會(huì)被父進(jìn)程默認(rèn)忽略?;蛘咛峁┮粋€(gè)信號(hào)處理函數(shù)來善后。wait和waitpid函數(shù)就是其中的信號(hào)處理函數(shù)的一部分。
wait和waitpid函數(shù)區(qū)別如下:
(1)wait會(huì)阻塞調(diào)用者進(jìn)程等待直至第一個(gè)終止的子進(jìn)程到來;(2)waitpid可以通過參數(shù)設(shè)置,來實(shí)現(xiàn)調(diào)用者進(jìn)程不阻塞,或選擇要阻塞等待的子進(jìn)程;
這里的調(diào)用者指的是父進(jìn)程
環(huán)境表和環(huán)境變量
環(huán)境表結(jié)構(gòu)圖
每個(gè)程序都接收到一張環(huán)境表
環(huán)境表也是一個(gè)字符指針數(shù)組
enrivon叫做環(huán)境指針
指針數(shù)組叫做環(huán)境表
各個(gè)指針指向的字符串叫做環(huán)境字符串
環(huán)境變量
unix內(nèi)核并不檢查環(huán)境字符串,它們的解釋完全取決于各個(gè)應(yīng)用進(jìn)程
通常在一個(gè)shell啟動(dòng)文件中設(shè)置環(huán)境變量來控制shell的動(dòng)作
修改或者增加環(huán)境變量時(shí),只能影響當(dāng)前進(jìn)程以及其后(之前的不行)生成和調(diào)用的任何子進(jìn)程的環(huán)境,但不能影響其父進(jìn)程的環(huán)境
和環(huán)境變量相關(guān)的函數(shù)如下:
#includechar *getenv(const char *name); 返回值:指向與name關(guān)聯(lián)的value的指針;若未找到,返回NULLint putenv(char *str); 返回值:若成功,返回0;若出錯(cuò),返回非0 int setenv(const char *name, const char *value, int rewrite);int unsetenv(const char *name); 兩個(gè)函數(shù)返回值:若成功,返回0;若出錯(cuò),返回-1
這些函數(shù)如何修改環(huán)境表的
環(huán)境表和環(huán)境字符串通常存放在內(nèi)存空間的高地址處(頂部)。所以在修改它的值時(shí),內(nèi)存是不能繼續(xù)向高地址延伸;但又因?yàn)?,它之下是各個(gè)棧幀,所以也不能向下延伸。如何修改它的值的過程如下:
(1)修改環(huán)境表
1)新value <= 舊value,直接覆蓋舊value的存儲(chǔ)空間2)新value >= 舊value,調(diào)用malloc函數(shù),在堆區(qū)開辟新的存儲(chǔ)空間,將新value復(fù)制到這里,再將這片存儲(chǔ)區(qū)首地址寫到環(huán)境表相應(yīng)的位置處。
(2)新增環(huán)境表
1)新增一個(gè)環(huán)境變量,調(diào)用malloc函數(shù)開辟新的存儲(chǔ)空間,將原來的環(huán)境表復(fù)制到該存儲(chǔ)區(qū),其次再添加一個(gè)環(huán)境變量,然后在尾部賦值為NULL,最后將environ指向該區(qū)域;2)在 1)過程的基礎(chǔ)上,調(diào)用realloc函數(shù),多次添加環(huán)境變量;
注意:以這種方式修改的環(huán)境變量只在當(dāng)下程序運(yùn)行時(shí)有效,當(dāng)程序結(jié)束時(shí),相應(yīng)的存儲(chǔ)區(qū)被系統(tǒng)回收,這些修改就會(huì)失效。
內(nèi)存存儲(chǔ)結(jié)構(gòu)補(bǔ)充說明
內(nèi)存管理結(jié)構(gòu)圖
未初始化數(shù)據(jù)段(block started by symbol):在程序開始執(zhí)
行之前,內(nèi)核將此段中的數(shù)據(jù)初始化為0或空指針;
棧:每次函數(shù)調(diào)用時(shí),其返回地址以及調(diào)用者的環(huán)境信息(如某些機(jī)器寄存器的值)都存放在棧中;
共享庫(kù):只需在所有進(jìn)程都可引用的存儲(chǔ)區(qū)中保存這種庫(kù)例程的一個(gè)副本;
存儲(chǔ)空間分配函數(shù)
#includevoid *malloc(size_t size);void *calloc(size_t nojy, size_t size);void *realloc(void *ptr, size_t newsize); 3個(gè)函數(shù)返回值:若成功,返回非空指針;若出錯(cuò),返回NULL
malloc函數(shù):初始值不確定;底層通過調(diào)用sbrk函數(shù)實(shí)現(xiàn);
calloc函數(shù):初始值為0;
realloc函數(shù):增加或減少以前分配區(qū)的長(zhǎng)度;當(dāng)增加長(zhǎng)度時(shí),可能將以前分配區(qū)的內(nèi)容移到另一個(gè)足夠大的區(qū)域,以便在分配區(qū)末尾增加存儲(chǔ)區(qū),而新增存儲(chǔ)區(qū)初始值不確定(例如:可變數(shù)組的使用);
注意:這些動(dòng)態(tài)分配的函數(shù)一般在分配存儲(chǔ)空間時(shí),會(huì)比要求的大。因?yàn)樵陂_辟空間的前后部分存儲(chǔ)記錄管理信息。因此,在使用時(shí),千萬不要越界訪問,以免造成不可預(yù)知的后果。
函數(shù)間跳轉(zhuǎn)策略
在c語(yǔ)言中,goto語(yǔ)句是不能跨函數(shù)跳轉(zhuǎn)的。尤其是在函數(shù)深層調(diào)用時(shí)的跳轉(zhuǎn)需求,在出錯(cuò)處理的情況下非常有用。
#includeint setjmp(jmp_buf env); 返回值:若直接調(diào)用,返回0;若從longjmp返回,返回非0void longjmp(jmp_buf env, int val);
變量值回滾問題:自動(dòng)變量和寄存器變量會(huì)存在回滾現(xiàn)象。利用volatile屬性來避免此類情況的發(fā)生。(在給變量賦值時(shí),賦的值回首先存儲(chǔ)在內(nèi)存(存儲(chǔ)器變量)中,然后在由cpu取走,存儲(chǔ)在cpu的寄存器上(寄存器變量)。在做系統(tǒng)優(yōu)化時(shí),那些頻繁使用的變量,會(huì)直接存儲(chǔ)到寄存器中而不經(jīng)過內(nèi)存。)
寄存器變量會(huì)存在回滾現(xiàn)象的探究
在調(diào)用setjmp函數(shù)時(shí),內(nèi)核會(huì)把當(dāng)前的棧頂指針保存在env變量中,所以在調(diào)用longjmp函數(shù)返回該位置時(shí),全局變量、靜態(tài)變量、易失變量和自動(dòng)變量如果在調(diào)用setjmp和longjmp函數(shù)之間它們的值被修改過,是不會(huì)回滾到setjmp函數(shù)調(diào)用之前的值(當(dāng)然,編譯器將auto變量?jī)?yōu)化為寄存器變量除外)。因?yàn)?,這些存儲(chǔ)器變量的值是存儲(chǔ)在內(nèi)存相應(yīng)的段中,回到原先棧頂狀態(tài)時(shí),同樣訪問的還是原先的內(nèi)存空間。
然而,對(duì)于寄存器變量來說,首先要明確一點(diǎn):寄存器變量是用動(dòng)態(tài)存儲(chǔ)的方式。意思是寄存器變量的值可能存在不同的寄存器中。如果在調(diào)setjmp和longjmp函數(shù)之間它們的值被修改過,這個(gè)值可能不會(huì)存到setjmp之前的對(duì)其賦值的寄存器中,而在調(diào)用longjmp函數(shù)后,又回到了調(diào)用setjmp函數(shù)時(shí)的狀態(tài)。這個(gè)時(shí)候再讀取寄存器變量的值時(shí),讀到的是原先那個(gè)寄存器中存儲(chǔ)的值而不是修改過的那個(gè)寄存器中存儲(chǔ)的值,所以出現(xiàn)的回滾現(xiàn)象。
?
評(píng)論