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