進程的啟動和終止 內核執行c程式時,利用exec函數調用一個特殊的啟動常式,該啟動常式叢內核中獲取命令行參數和環境變數值。 進程終止的情況 5種正常終止的情況: 3種異常終止情況 進程啟動和終止圖 atexit函數 一個進程最多可以登記32和函數(例如:signal函數),這些函數由exit函數自動 ...
進程的啟動和終止
內核執行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函數
程式正常或異常終止時,內核都會向父進程發送SIGNAL信號。子進程終止是非同步事件,所以該信號也是非同步信號。而該信號一般會被父進程預設忽略。或者提供一個信號處理函數來善後。wait和waitpid函數就是其中的信號處理函數的一部分。
wait和waitpid函數區別如下:
(1)wait會阻塞調用者進程等待直至第一個終止的子進程到來;
(2)waitpid可以通過參數設置,來實現調用者進程不阻塞,或選擇要阻
塞等待的子進程;
這裡的調用者指的是父進程
環境表和環境變數
環境表結構圖
- 每個程式都接收到一張環境表
- 環境表也是一個字元指針數組
- enrivon叫做環境指針
- 指針數組叫做環境表
- 各個指針指向的字元串叫做環境字元串
環境變數
- unix內核並不檢查環境字元串,它們的解釋完全取決於各個應用進程
- 通常在一個shell啟動文件中設置環境變數來控制shell的動作
- 修改或者增加環境變數時,只能影響當前進程以及其後(之前的不行)生成和調用的任何子進程的環境,但不能影響其父進程的環境
和環境變數相關的函數如下:
#include<stdlib.h>
char *getenv(const char *name);
返回值:指向與name關聯的value的指針;若未找到,返回NULL
int 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或空指針; - 棧:每次函數調用時,其返回地址以及調用者的環境信息(如某些機器寄存器的值)都存放在棧中;
- 共用庫:只需在所有進程都可引用的存儲區中保存這種庫常式的一個副本;
存儲空間分配函數
#include<stdlib.h>
void *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語句是不能跨函數跳轉的。尤其是在函數深層調用時的跳轉需求,在出錯處理的情況下非常有用。
#include<setjmp.h>
int setjmp(jmp_buf env);
返回值:若直接調用,返回0;若從longjmp返回,返回非0
void longjmp(jmp_buf env, int val);
變數值回滾問題:自動變數和寄存器變數會存在回滾現象。利用volatile屬性來避免此類情況的發生。(在給變數賦值時,賦的值迴首先存儲在記憶體(存儲器變數)中,然後在由cpu取走,存儲在cpu的寄存器上(寄存器變數)。在做系統優化時,那些頻繁使用的變數,會直接存儲到寄存器中而不經過記憶體。)
寄存器變數會存在回滾現象的探究
在調用setjmp函數時,內核會把當前的棧頂指針保存在env變數中,所以在調用longjmp函數返回該位置時,全局變數、靜態變數、易失變數和自動變數如果在調用setjmp和longjmp函數之間它們的值被修改過,是不會回滾到setjmp函數調用之前的值(當然,編譯器將auto變數優化為寄存器變數除外)。因為,這些存儲器變數的值是存儲在記憶體相應的段中,回到原先棧頂狀態時,同樣訪問的還是原先的記憶體空間。
然而,對於寄存器變數來說,首先要明確一點:寄存器變數是用動態存儲的方式。意思是寄存器變數的值可能存在不同的寄存器中。如果在調setjmp和longjmp函數之間它們的值被修改過,這個值可能不會存到setjmp之前的對其賦值的寄存器中,而在調用longjmp函數後,又回到了調用setjmp函數時的狀態。這個時候再讀取寄存器變數的值時,讀到的是原先那個寄存器中存儲的值而不是修改過的那個寄存器中存儲的值,所以出現的回滾現象。