進程環境
-
main函數
C程序總是從main函數開始執行,main函數的原型:
int main(int argc, char *argv[])
內核在執行C程序(exec函數調用)時,在調用main函數前先調用一個特殊的啟動程序,啟動程序從內核取得命令行參數和環境變量,為調用main函數做好準備。
-
進程終止
正常終止:
(1)從main返回
啟動程序是這樣編寫的,從main返回後立即調用exit函數。啟動程序調用main函數的形式可能是(實際上是用匯編編寫的):
exit(main(argc, argv));
(2)調用exit函數
exit函數先調用各終止處理程序,然後關閉所有打開流(fclose)。終止處理函數,可用atexit來進行登記。
exit函數最後調用_exit或_Exit函數。
(3)調用_exit或_Exit
這兩個函數立即進入內核。
#include <stdlib.h> void exit(int status); void _Exit(int status); //上述是ISO C說明的 #include <unistd.h> void _exit(int status); //由POSIX.1說明的
status是終止狀態。
(4)最後一個線程從其啟動歷程返回
(5)從最後一個線程調用pthread_exit
異常終止:
(6)調用abort
(7)接到一個信號
(8)最後一個線程對取消請求做出響應
函數atexit:
#include <stdlib.h> int atexit(void (*func)(void));
atexit登記終止處理程序(exit handler),exit調用這些函數的順序與登記時的順序相反,同一函數登記多次會被調用多次。
函數啟動和終止的流程如圖1所示:
圖1 一個C程序啟動和終止的流程
啟動程序的唯一方法是調用一個exec函數,進程自願終止的唯一方式是顯示或隱式(exit)地調用_exit或_Exit,進程也可非自願地由一個信號終止。
-
命令行參數
當執行一個程序時,調用exec的進程可將命令行參數傳遞給該新程序。
main函數中,argc參數是命令行參數的個數,argv參數指向一個字符指針數組,字符指針指向各個命令行參數。
ISO C和POSIX.1都要求argv[argc]是一個空指針。
-
環境表
每個程序都將收到一張環境表,與argv一樣,指向一個字符指針數組,示意圖如圖2所示。
圖2 環境表示意圖
environ是環境指針,指向環境表(字符指針數組),環境表的各個指針指向環境字符串,環境字符串由name=value這樣的字符串組成。
-
C程序的存儲空間布局
C程序由下列幾部分組成:正文段、初始化數據段、未初始化數據段(bss段)、棧、堆。
這些段的典型安排方式,如圖3所示。
圖3 典型的存儲空間安排
正文段,是CPU執行的機器指令部分。正文段通常是只讀的,以防止程序被修改。
初始化數據段,通常稱此段為數據段,包含了程序已賦初值的變量。
未初始化數據段,通常稱為bss段(block started by symbol)。
棧,自動變量及函數調用所需保存的信息都存放在此段中。
堆,動態存儲分配在堆進行。
-
共享庫
共享庫使得可執行文件不需要包含公用的庫函數,而只需在所有進程中都可引用的存儲區中保存這種庫函數的一個副本。
程序第一次執行時,用動態鏈接方法將程序與共享庫函數相鏈接。
圖4中展示了典型的helloworld程序,使用共享庫和靜態庫的區別。
可看到用靜態庫編譯出來的可執行文件static,比用共享庫編譯出來的可執行文件sharedlib,長度要大。
圖4 Hello World用靜態庫和動態庫編譯的區別
-
存儲空間分配
ISO C說明了3個用於存儲空間動態分配的函數:這些存儲空間分配函數通常用sbrk系統調用實現。
#include <stdlib.h> void *malloc(size_t size); void *calloc(size_t nobj, size_t size); void *realloc(void *ptr, size_t newsize); All three return: non-null pointer if OK, NULL on error void free(void *ptr);
(1)malloc,分配指定字節數的存儲區,初始值不確定。
(2)calloc,為指定數量指定長度的對象分配存儲空間。該空間的每個bit都初始化為0。
(3)realloc,增加或減少以前分配區的長度。增加長度時,可能需要移動到另一個足夠大的區域,新增區域內的初始值不確定。
這三個函數所返回的指針一定是適當對齊的。
(4)函數free釋放ptr指向的存儲空間,被釋放的空間通常被送入可用存儲區池,後續可用上述3個分配函數再分配。
註:realloc,存儲區可能會移動位置,所以不應當使任何指針指在該區域中。
在動態分配的緩沖區前或後進行寫操作,破壞的可能不僅僅是該區的管理記錄信息,很可能破壞其他動態分配的對象。這些錯誤很難被發現。
其他可能產生致命性錯誤的是:
(1)釋放一個已經釋放了的塊
(2)調用free所用的指針不是3個alloc函數的返回值
(3)若一個進程調用malloc函數,卻忘記調用free函數,會發生內存泄露。
其他的存儲空間分配程序:
libmalloc/vmalloc/quick-fit/jemalloc/TCMalloc/alloca等
-
環境變量
環境字符串的形式:
name=value
UNIX內核並不查看這些字符串,它們的解釋完全取決於各個應用程序。
可用getenv獲取環境變量值:
#include <stdlib.h> char *getenv(const char *name); Returns: pointer to value associated with name, NULL if not found
應當使用getenv從環境中取一個指定環境變量的值,而不是直接訪問environ。
除了獲取環境變量值,還需要設置環境變量(改變或新增環境變量),當前進程只能影響當前進程及其後生成和調用的任何子進程的環境,不能影響父進程的環境。
#include <stdlib.h> int putenv(char *str); Returns: 0 if OK, nonzero on error int setenv(const char *name, const char *value, int rewrite); int unsetenv(const char *name); Both return: 0 if OK, −1 on error
putenv默認刪除原來的定義,setenv由變量rewrite控制是否刪除原來的定義。unsetenv刪除name的定義,即使不存在這種定義也不出錯。
setenv必須分配存儲空間,putenv可將傳遞給它的參數字符串直接放到環境中。
這些函數在修改環境表時底層是怎麽操作的?
1、刪除一個字符串,只要先找到該指針,然後將所有後續指針都向環境表首部順次移動一個位置。
2、修改一個現有的name
a、新value長度小於現有value的長度,則只要將新字符串復制到原字符串所用的空間中。
b、新value長度大於現有value的長度,則必須調用malloc為新字符串分配空間,新字符串放到該空間,環境表針對name的指針指向新分配空間。
3、新增一個新的name
需要調用malloc為新字符串分配新空間,字符串放到該空間。但還需要為環境表分配新空間。
a、第一個次新增一個name
需要為新的指針表調用malloc分配空間,將原來的指針表復制到新的分配區,將新的字符串指針存放在表尾,NULL指針存放在其後,最後environ指向新環境表(指針表)。
b、不是第一個新增一個name
以前已調用malloc在堆中為環境表分配了空間,所以只需要realloc,以分配多一個指針的空間,將新的字符串指針存放在表尾,NULL指針存放在其後。
-
函數setjmp和longjmp
goto語句是不能跨越函數跳轉的,setjmp和longjmp這兩個函數可以實現非局部goto(在棧上跳過若幹調用幀,返回到當前函數調用路徑上的某一個函數中),這對發生在很深層嵌套函數調用中的出錯情況非常有用。
#include <setjmp.h> int setjmp(jmp_buf env); Returns: 0 if called directly, nonzero if returning from a call to longjmp void longjmp(jmp_buf env, int val);
在希望返回到的位置調用setjmp,然後在其他地方【當前函數調用路徑上的某一個函數中】調用longjmp則會返回到此位置,實現跨函數跳轉。
setjmp函數,參數env是一個特殊類型jmp_buf,是某種形式的數組,用於存放調用longjmp時能用來恢復棧狀態的所有信息。通常將env變量定義為全局變量。
longjmp函數,第一個參數就是在調用setjmp時所用的env,第二個參數是setjmp將返回的值。
jmp_buffer jmpbuffer; int fun(void) { ... if(setjmp(jmpbuffer) != 0) printf("error"); ... } int fun2(void) { ... longjmp(jmpbuffer, 1); ... }
在fun中直接調用setjmp函數,其返回0,設置longjmp返回的位置。
在fun2中調用longjmp,將返回到fun1調用setjmp的地方繼續運行,此時setjmp的返回值是longjmp設置的val值。通過val值可區分不同的錯誤。
longjmp跳轉後自動變量和寄存器變量的狀態如何變化?
a、回滾到setjmp時的值,回滾到原先值
b、保持longjmp時的值,即最新值
這個問題是不確定的。
如果有一個自動變量,而又不想其值回滾,則可定義為volatile屬性;全局變量或靜態變量的值在執行longjmp時也保持不變。
-
函數getrlimit和setrlimit
每個進程都有一組資源限制,其中一些可用getrlimit和setrlimit函數查詢和更改。
#include <sys/resource.h> int getrlimit(int resource, struct rlimit *rlptr); int setrlimit(int resource, const struct rlimit *rlptr); Both return: 0 if OK, −1 on error
進程環境