apue:UNIX進程的環境

    當執行程序時,其main函數是如何被調用的,命令行參數是如何傳送給執行程序的;典型的存儲器佈局是什麼樣式;如何分配另外的存儲空間;進程如何使用環境變量;進程終止的不同方式等;longjmp和setjmp函數以及它們與棧的交互作用。

main函數

    C程序總是從main函數開始執行,main函數的原型是:

int main(int argc, char* argv[]);

    其中,argc是命令行參數的數目,argv是指向參數的各個指針所構成的數組。當內核起動C程序時,在調用main前先調用一個特殊的起動例程。可執行程序文件將此起動例程指定爲程序的起始地址——這是由連接編輯程序設置的,而連接編輯程序則由C編譯程序調用。起動例程從內核取得命令行參數和環境變量值,然後爲調用main函數作好安排。

進程終止

    有五種方式使進程終止:
- 正常終止:
1.從main返回。
2.調用exit。
3.調用_exit。
- 異常終止:
1.調用abort(見第10章)。
2.由一個信號終止(見第10章)。

exit和_exit函數

    exit和_exit函數用於正常終止一個程序:_exit立即進入內核,exit則先執行一些清除處理(包括調用執行各終止處理程序,關閉所有標準I/O流等),然後進入內核。

#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);

    exit函數總是執行一個標準I/O庫的清除關閉操作:對於所有打開流調用fclose函數。exit和_exit都帶一個整型參數,稱之爲終止狀態(exit status)。

atexit函數

    按照ANSIC的規定,一個進程可以登記多至32個函數,這些函數將由exit自動調用。我們稱這些函數爲終止處理程序(exit handler),並用atexit函數來登記這些函數。

#include <stdlib.h>
int atexit(void (*func)(void));
/*返回:若成功則爲0,若出錯則爲非0*/

    其中,atexit的參數是一個函數地址,當調用此函數時無需向它傳送任何參數,也不期望它返回一個值。exit以登記這些函數的相反順序調用它們。同一函數如若登記多次,則也被調用多次。
    一個C程序的啓動和終止示意圖如下:
一個C程序的啓動和終止
    注意,內核使程序執行的唯一方法是調用一個exec函數。進程自願終止的唯一方法是顯式或隱式地(調用exit)調用_exit。

命令行參數

    當執行一個程序時,調用exec的進程可將命令行參數傳遞給該新程序。這是UNIXshell的一部分常規操作。

環境表

    每個程序都接收到一張環境表。與參數表一樣,環境表也是一個字符指針數組,其中每個指針包含一個以null結束的字符串的地址。

C程序的存儲空間佈局

    C程序由下列幾部分組成:
存儲器安排示意圖
- 正文段,由CPU執行的機器指令部分。
- 初始化數據段。通常將此段稱爲數據段,它包含了程序中需賦初值的變量。
- 非初始化數據段。通常將此段稱爲bss段,意思是“block started by symbol(由符號開始的塊)”,在程序開始執行之前,內核將此段初始化爲0。
- 棧。自動變量以及每次函數調用時所需保存的信息都存放在此段中。每次函數調用時,其返回地址、以及調用者的環境信息都存放在棧中。然後,新被調用的函數在棧上爲其自動和臨時變量分配存儲空間。通過以這種方式使用棧,C函數可以遞歸調用。
- 堆。通常在堆中進行動態存儲分配。堆位於非初始化數據段頂和棧底之間。

共享庫

    共享庫使得可執行文件中不再需要包含常用的庫函數,而只需在所有進程都可存取的存儲區中保存這種庫例程的一個副本。程序第一次執行或者第一次調用某個庫函數時,用動態連接方法將程序與共享庫函數相連接。這減少了每個可執行文件的長度,但增加了一些運行時間開銷。共享庫的另一個優點是可以用庫函數的新版本代替老版本而無需對使用該庫的程序重新連接編輯。不同的系統使用不同的方法使說明程序是否要使用共享庫。

存儲器分配

    ANSI C說明了三個用於存儲空間動態分配的函數。
(1)malloc。分配指定字節數的存儲區。此存儲區中的初始值不確定。
(2)calloc。爲指定長度的對象,分配能容納其指定個數的存儲空間。該空間中的每一位(bit)都初始化爲0。
(3)realloc。更改以前分配區的長度(增加或減少)。當增加長度時,可能需將以前分配區的內容移到另一個足夠大的區域,而新增區域內的初始值則不確定。

#include <stdlib.h>
void* malloc(size_t size);
void* calloc(size_t nobj, size_t size);
void* realloc(void* ptr, size_t newsize);
/*三個函數返回:若成功則爲非空指針,若出錯則爲NULL*/
void free(void* ptr)

    這三個分配函數所返回的指針一定是適當對齊的,使其可用於任何數據對象。函數free釋放ptr指向的存儲空間。被釋放的空間通常被送入可用存儲區池,以後可在調用分配函數時再分配。realloc使我們可以增、減以前分配區的長度(最常見的用法是增加該區)。如果在該存儲區後有足夠的空間可供擴充,則可在原存儲區位置上向高地址方向擴充,並返回傳送給它的同樣的指針值。如果在原存儲區後沒有足夠的空間,則realloc分配另一個足夠大的存儲區,將現存的內容複製到新分配的存儲區。因爲這種存儲區可能會移動位置,所以不應當使用任何指針指在該區中。

環境變量

    環境字符串的形式是:name=value。UNIX內核並不關心這種字符串的意義,它們的解釋完全取決於各個應用程序。ANSI C定義了一個函數getenv,可以用其取環境變量值,但是該標準又稱環境的內容是由實現定義的。

#include <stdlib.h>
char* getenv(const char* name);
/*返回:指向與name關聯的value的指針,若未找到則爲NULL*/

    除了取環境變量值,有時也需要設置環境變量,或者是改變現有變量的值,或者是增加新的環境變量。

#include <stdlib.h>
int putenv(const char* str);
int setenv(const char* name, const char* value, int rewrite);
/*兩個函數返回:若成功則爲0,若出錯則爲非0*/
void unsetenv(const char* name);

    這三個函數的操作是:
- putenv取形式爲name=value的字符串,將其放到環境表中。如果name已經存在,則先刪除其原來的定義。
- setenv將name設置爲value。如果在環境中name已經存在,若rewrite非0,則首先刪除其現存的定義;若rewrite爲0,則不刪除其現存定義。
- unsetenv刪除name的定義。即使不存在這種定義也不算出錯。

setjmp和longjmp函數

    在C中,不允許使用跳越函數的goto語句。而執行這種跳轉功能的是函數setjmp和longjmp。這兩個函數對於處理髮生在很深的嵌套函數調用中的出錯情況非常有用。

#include <setjmp.h>
int setjmp(jmp_buf env);
/*返回:若直接調用則爲0,若從longjmp返回則爲非0*/
void longjmp(jmp_buf env, int val);

    在希望返回到的位置調用setjmp,setjmp的參數env是一個特殊類型jmp_buf。這一數據類型是某種形式的數組,其中存放在調用longjmp時能用來恢復棧狀態的所有信息。一般,env變量是個全局變量,因爲需從另一個函數中引用它。

自動、寄存器和易失變量

    在main函數中,自動變量和寄存器變量的狀態如何?大多數實現並不滾回這些自動變量和寄存器變量的值,而所有標準則說它們的值是不確定的。如果你有一個自動變量,而又不想使其值滾回,則可定義其爲具有volatile屬性。說明爲全局和靜態變量的值在執行longjmp時保持不變。

自動變量的潛在問題

    自動變量的函數已經返回後,就不能再引用這些自動變量。

getrlimith和setrlimit函數

    每個進程都有一組資源限制,其中某一些可以用getrlimit和setrlimit函數查詢和更改。

#include <sys/time.h>
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit* rlptr);
int setrlimit(int resource, const struct rlimit* rlptr);
/*兩個函數返回:若成功則爲0,若出錯則爲非0*/

    對這兩個函數的每一次調用都指定一個資源以及一個指向下列結構的指針。

struct rlimit {
rlim_t rlim_cur; /*soft limit: current limit*/
rlim_t rlim_max; /*hard limit: maximum value for rlim_cur*/
};

    在更改資源限制時,須遵循下列三條規則:
- 任何一個進程都可將一個軟限制更改爲小於或等於其硬限制。
- 任何一個進程都可降低其硬限制值,但它必須大於或等於其軟限制值。這種降低,對普通用戶而言是不可逆反的。
- 只有超級用戶可以提高硬限制。
    一個無限量的限制由常數RLIM_INFINITY指定。這兩個函數的resource參數取下列值之一。
- RLIMIT_CORE,core文件的最大字節數,若其值爲0則阻止創建core文件。
- RLIMIT_CPU,CPU時間的最大量值(秒),當超過此軟限制時,向該進程發送SIGXCPU信號。
- RLIMIT_DATA,數據段的最大字節長度。這是圖7-3中初始化數據、非初始化數據以及堆的總和。
- RLIMIT_FSIZE,可以創建的文件的最大字節長度。當超過此軟限制時,則向該進程發送SIGXFSZ信號。
- RLIMIT_MEMLOCK,鎖定在存儲器地址空間(尚未實現)。
- RLIMIT_NOFILE,每個進程能打開的最多文件數。
- RLIMIT_NPROC,每個實際用戶ID所擁有的最大子進程數。
- RLIMIT_O,FILE與SVR4的RLIMIT_NOFILE相同。
- RLIMIT_RSS,最大駐內存集字節長度(RSS)。如果物理存儲器供不應求,則內核將從進程處取回超過RSS的部分。
- RLIMIT_STACK,棧的最大字節長度。
- RLIMIT_VMEM,可映照地址空間的最大字節長度。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章