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