7.1引言
將介紹進程控制原語恨统,在此之前需先了解進程的環(huán)境刺覆。本章 中將學習:當程序執(zhí)行時黔州,其main函數(shù)是如何被調(diào)用的;命令行參數(shù)是 如何傳遞給新程序的;典型的存儲空間布局是什么樣式;如何分配另外 的存儲空間;進程如何使用環(huán)境變量;進程的各種不同終止方式等碘橘。另 外,還將說明longjmp和setjmp函數(shù)以及它們與棧的交互作用碉钠。本章結(jié)束 之前纲缓,還將查看進程的資源限制。
7.2 main函數(shù)
C程序總是從main函數(shù)開始執(zhí)行放钦。main函數(shù)的原型是:
int main(int argc, char *argv[]); 其中色徘,argc是命令行參數(shù)的數(shù)目,argv是指向參數(shù)的各個指針所構(gòu)成的數(shù)組操禀。7.4 節(jié)將對命令行參數(shù)進行說明。 當內(nèi)核執(zhí)行C程序時(使用一個exec函數(shù)横腿,8.10節(jié)將說明exec函數(shù))颓屑,在調(diào)用main前先調(diào)用一個特殊的啟動例程」⒑福可執(zhí)行程序文件將此 啟動例程指定為程序的起始地址——這是由連接編輯器設置的揪惦,而連接 編輯器則由C編譯器調(diào)用。啟動例程從內(nèi)核取得命令行參數(shù)和環(huán)境變量 值罗侯,然后為按上述方式調(diào)用main函數(shù)做好安排器腋。
7.3 進程終止
有8種方式使進程終止(termination),其中 5種為正常終止钩杰,它們 是:
- (1)從main返回;
- (2)調(diào)用exit;
- (3)調(diào)用_exit或_Exit;
- (4)最后一個線程從其啟動例程返回(11.5節(jié));
- (5)從最后一個線程調(diào)用pthread_exit(11.5節(jié))纫塌。
異常終止有3種方式,它們是:
- (6)調(diào)用abort(10.17節(jié));
- (7)接到一個信號(10.2節(jié));
- (8)最后一個線程對取消請求做出響應(11.5節(jié)和12.7節(jié))讲弄。
在第11章和第12章討論線程之前措左,我們暫不考慮專門針對線程的3
種終止方式。
上節(jié)提及的啟動例程是這樣編寫的避除,使得從main返回后立即調(diào)用 exit函數(shù)怎披。如果將啟動例程以C代碼形式表示(實際上該例程常常用匯編 語言編寫),則它調(diào)用main函數(shù)的形式可能是:
exit(main(argc, argv));
- 1.退出函數(shù)
3個函數(shù)用于正常終止一個程序:_exit和_Exit立即進入內(nèi)核瓶摆,exit則 先執(zhí)行一些清理處理凉逛,然后返回內(nèi)核。
#include <stdlib.h>
void exit(int status);
void _Exit(int status);
#include <unistd.h>
void _exit(int status);
由于歷史原因群井,exit 函數(shù)總是執(zhí)行一個標準 I/O 庫的清理關(guān)閉操 作:對于所有打開流調(diào)用fclose函數(shù)状飞。回憶5.5節(jié),這造成輸出緩沖中的 所有數(shù)據(jù)都被沖洗(寫到文件上)昔瞧。
3個退出函數(shù)都帶一個整型參數(shù)指蚁,稱為終止狀態(tài)(或退出狀態(tài),exit status)自晰。大多 數(shù)UNIX系統(tǒng)shell都提供檢查進程終止狀態(tài)的方法凝化。如果 (a)調(diào)用這些函數(shù)時不帶終止狀態(tài),或(b)main執(zhí)行了一個無返回值 的return語句酬荞,或(c)main沒有聲明返回類型為整型搓劫,則該進程的終止 狀態(tài)是未定義的。但是混巧,若main的返回類型是整型枪向,并且main執(zhí)行到最 后一條語句時返回(隱式返回),那么該進程的終止狀態(tài)是0咧党。
這種處理是ISO C標準1999版引入的秘蛔。歷史上,若main函數(shù)終止時 沒有顯式使用return語句或調(diào)用exit函數(shù)傍衡,那么進程終止狀態(tài)是未定義 的深员。
main函數(shù)返回一個整型值與用該值調(diào)用exit是等價的。于是在main 函數(shù)中
exit(0);
等價于
return(0);
#include <stdio.h>
int main() {
printf("hello world\n");
}
-2 函數(shù)atexit
按照ISO C的規(guī)定蛙埂,一個進程可以登記多至32個函數(shù)倦畅,這些函數(shù)將 由exit自動調(diào)用。我們稱這些函數(shù)為終止處理程序(exit handler)绣的,并調(diào) 用atexit函數(shù)來登記這些函數(shù)叠赐。
#include <stdlib.h>
int atexit(void (*func)(void));
返回值:若成功,返回0;若出錯屡江,返回非0
其中芭概,atexit 的參數(shù)是一個函數(shù)地址,當調(diào)用此函數(shù)時無需向它傳
遞任何參數(shù)盼理,也不期望它返回一個值谈山。exit調(diào)用這些函數(shù)的順序與它們 登記時候的順序相反。同一函數(shù)如若登記多次宏怔,也會被調(diào)用多次奏路。
ISO C要求,系統(tǒng)至少應支持32個終止處理程序臊诊,但實現(xiàn)經(jīng)常會提 供更多的支持(參見圖2-15)鸽粉。為了確定一個給定的平臺支持的最大終
止處理程序數(shù),可以使用sysconf函數(shù)(如圖2-14所示)抓艳。
根據(jù)ISO C和POSIX.1触机,exit首先調(diào)用各終止處理程序,然后關(guān)閉 (通過fclose)所有打開流。POSIX.1擴展了ISO C標準儡首,它說明片任,如若 程序調(diào)用exec函數(shù)族中的任一函數(shù),則將清除所有已安裝的終止處理程 序蔬胯。圖7-2顯示了一個C程序是如何啟動的对供,以及它終止的各種方式。
注意氛濒,內(nèi)核使程序執(zhí)行的唯一方法是調(diào)用一個exec函數(shù)产场。進程自愿 終止的唯一方法是顯式或隱式地(通過調(diào)用 exit)調(diào)用_exit 或_Exit。進 程也可非自愿地由一個信號使其終止(圖 7-2中沒有顯示)舞竿。
#include <stdio.h>
#include <iostream>
#include <unistd.h>
#include <string>
#include <stdlib.h>
static void my_exit1() {
std::cout << "my_exit" << std::endl;
}
static void my_exit2() {
std::cout << "my_exit2" << std::endl;
}
int main()
{
atexit(my_exit1);
atexit(my_exit2);
printf("main function is done\n");
return 0;
}
7.4 命令行參數(shù)
當執(zhí)行一個程序時京景,調(diào)用exec的進程可將命令行參數(shù)傳遞給該新程 序。這是UNIX shell的一部分常規(guī)操作骗奖。在前幾章的很多實例中确徙,我們 已經(jīng)看到了這一點。
所示的程序?qū)⑵渌忻钚袇?shù)都回顯到標準輸出上重归。注 意米愿,通常的 echo程序不回顯第0個參數(shù)。
#include <iostream>
#include <stdio.h>
int main(int argc ,char * argv[]) {
int i = 0;
for (i = 0; i < argc; ++i ) {
printf("argv[%d] is : %s\n", argc, argv[i]);
}
return 0;
}
ISO C和POSIX.1都要求argv[argc]是一個空指針鼻吮。這就使我們可以將 參數(shù)處理循環(huán)改寫為:
for (int i =0 ; argv[i]!= NULL; ++i)
7.5環(huán)境表
每個程序都接收到一張環(huán)境表。與參數(shù)表一樣较鼓,環(huán)境表也是一個字 符指針數(shù)組椎木,其中每個指針包含一個以null結(jié)束的C字符串的地址。全局 變量environ則包含了該指針數(shù)組的地址:
extern char **environ;
例如博烂,如果該環(huán)境包含5個字符串香椎,那么它看起來如圖7-5中所示。 其中禽篱,每個字符串的結(jié)尾處都顯式地有一個null字節(jié)畜伐。我們稱environ為 環(huán)境指針(environment pointer),指針數(shù)組為環(huán)境表躺率,其中各指針指向 的字符串為環(huán)境字符串玛界。
按照慣例,環(huán)境由
name = value
這樣的字符串組成悼吱,如圖7-5中所示慎框。大多數(shù)預定義名完全由大寫字母組成,但這只是一個慣例后添。
在歷史上笨枯,大多數(shù)UNIX系統(tǒng)支持main函數(shù)帶3個參數(shù),其中第3個
參數(shù)就是環(huán)境表地址:
int main(int argc, char *argv[], char *envp[]);
因為ISO C規(guī)定main函數(shù)只有兩個參數(shù),而且第3個參數(shù)與全局變量 environ相比也沒有帶來更多益處馅精,所以 POSIX.1 也規(guī)定應使用 environ 而不使用第 3 個參數(shù)严嗜。通常用 getenv 和putenv函數(shù)(見7.9節(jié))來訪問特 定的環(huán)境變量,而不是用environ變量洲敢。但是讳窟,如果要查看整個環(huán)境,則 必須使用environ指針帽蝶。
我們寫一個打印環(huán)境變量的代碼
#include <iostream>
#include <stdio.h>
extern char **environ;
int main() {
/*----------------------------------- display environ ----------------------------------------*/
for (int i = 0; environ[i] != NULL; ++i) {
printf("environ: %s\n", environ[i]);
}
}
7.6 C程序儲存空間布局
歷史沿襲至今造虎,C程序一直由下列幾部分組成:
- 代碼段:程序的所有指令會存放在這個區(qū)域,這是已經(jīng)編譯后的機器碼哮塞。
這是由CPU執(zhí)行的機器指令部分刨秆。通常,正文段是可共 享的忆畅,所以即使是頻繁執(zhí)行的程序(如文本編輯器衡未、C編譯器和shell 等)在存儲器中也只需有一個副本,另外家凯,正文段常常是只讀的缓醋,以防 止程序由于意外而修改其指令。
字面量池是程序初始化時的一些字符串字面量绊诲,在程序中用于顯示文字
全局數(shù)據(jù)段:通常將此段稱為數(shù)據(jù)段送粱,程序初始化時的常量和全局/靜態(tài)的變量。C/C++ 用global/static聲明的變量都存放在這個區(qū)域掂之,對所有函數(shù)公開可見抗俄。(static或extern 已經(jīng)初始化的全局變量都在這里)
它包含了程序中需明確 地賦初值的變量。例如世舰, C程序中任何函數(shù)之外的聲明:
int maxcount = 99;
?未初始化數(shù)據(jù)段动雹。通常將此段稱為bss段,這一名稱來源于早期匯 編程序一個操作符跟压,意思是“由符號開始的塊”(block started by symbol)胰蝠,在程序開始執(zhí)行之前,內(nèi)核將此段中的數(shù)據(jù)初始化為0或空 指針震蒋。函數(shù)外的聲明:
long sum[1000];
使此變量存放在非初始化數(shù)據(jù)段中茸塞。
堆: 通常在堆中進行動態(tài)存儲分配。由于歷史上形成的慣例喷好,堆位于未初始化數(shù)據(jù)段和棧之間翔横。這里保存的數(shù)據(jù)只是為了臨時存儲一些值而創(chuàng)建的,而我們可能在程序運行過程中可能會回收此內(nèi)存梗搅。因為我們在程序執(zhí)行期間不需要很長時間禾唁,所以使用C中的new或malloc這類內(nèi)存分配程序來為我們所需的特定數(shù)據(jù)類型提供新的空間效览,并且隨著我們要求越來越多的動態(tài)數(shù)據(jù)空間而該區(qū)域不斷擴大,并且在內(nèi)存中逐漸增長到更高的地址荡短。
-
棧:
自動變量以及每次函數(shù)調(diào)用時所需保存的信息都存放在此段 中丐枉。每次函數(shù)調(diào)用時,其返回地址以及調(diào)用者的環(huán)境信息(如某些機器 寄存器的值)都存放在棧中掘托。然后瘦锹,最近被調(diào)用的函數(shù)在棧上為其自動 和臨時變量分配存儲空間。通過以這種方式使用棧闪盔,C遞歸函數(shù)可以工 作弯院。遞歸函數(shù)每次調(diào)用自身時,就用一個新的棧幀泪掀,因此一次函數(shù)調(diào)用 實例中的變量集不會影響另一次函數(shù)調(diào)用實例中的變量听绳。當我們執(zhí)行這些過程調(diào)用時,堆的基本特性是LIFO,存儲著該程序“上下文”,它將從內(nèi)存的高層地址開始异赫,然后向另一個方向向下擴展椅挣。上下文其實就是程序中各個函數(shù)之間調(diào)用的先后順序。
7.7 共享庫
即動態(tài)庫塔拳,linux系統(tǒng)上的.so
文件
現(xiàn)在鼠证,大多數(shù)UNIX系統(tǒng)支持共享庫。Arnold[1986]說明了System V 上共享庫的一個早期實現(xiàn)靠抑,Gingell等[1987]則說明了SunOS上的另一個實 現(xiàn)量九。共享庫使得可執(zhí)行文件中不再需要包含公用的庫函數(shù),而只需在所 有進程都可引用的存儲區(qū)中保存這種庫例程的一個副本颂碧。程序第一次執(zhí) 行或者第一次調(diào)用某個庫函數(shù)時娩鹉,用動態(tài)鏈接方法將程序與共享庫函數(shù) 相鏈接。這減少了每個可執(zhí)行文件的長度稚伍,但增加了一些運行時間開 銷。
這種時間開銷發(fā)生在該程序第一次被執(zhí)行時戚宦,或者每個共享庫函數(shù) 第一次被調(diào)用時个曙。共享庫的另一個優(yōu)點是可以用庫函數(shù)的新版本代替老 版本而無需對使用該庫的程序重新連接編輯(假定參數(shù)的數(shù)目和類型都 沒有發(fā)生改變)。
在不同的系統(tǒng)中受楼,程序可能使用不同的方法說明是否要使用共享 庫垦搬。比較典型的有 cc(1)和ld(1)命令的選項。作為長度方面發(fā)生變化的例 子艳汽,先用無共享庫方式創(chuàng)建下列可執(zhí)行文件(典型的hello.c程序):
$ gcc -static hello1.c 阻止gcc 使用共享庫
7.8 儲存空間分配
ISO C說明了3個用于存儲空間動態(tài)分配的函數(shù)猴贰。
- (1)malloc,分配指定字節(jié)數(shù)的存儲區(qū)河狐。此存儲區(qū)中的初始值不確 定米绕。
- (2)calloc瑟捣,為指定數(shù)量指定長度的對象分配存儲空間。該空間中 的每一位(bit)都初始化為0栅干。
- (3)realloc迈套,增加或減少以前分配區(qū)的長度。當增加長度時碱鳞,可能 需將以前分配區(qū)的內(nèi)容移到另一個足夠大的區(qū)域桑李,以便在尾端提供增加的存儲區(qū),而新增 區(qū)域內(nèi)的初始值則不確定窿给。
#include <stdlib.h>
void *malloc(size_t size);
void *calloc(size_t nobj, size_t size);
void *realloc(void *ptr, size_t newsize);
3個函數(shù)返回值:若成功贵白,返回非空指針;若出錯,返回NULL
void free(void *ptr);
這3個分配函數(shù)所返回的指針一定是適當對齊的崩泡,使其可用于任何 數(shù)據(jù)對象禁荒。例如,在一個特定的系統(tǒng)上允华,如果最苛刻的對齊要求是圈浇, double必須在8的倍數(shù)地址單元處開始,那么這3個函數(shù)返回的指針都應 這樣對齊靴寂。
因為這 3 個 alloc 函數(shù)都返回通用指針 void *磷蜀,所以如果在程序中包 括了#include<stdlib.h>(以獲得函數(shù)原型),那么當我們將這些函數(shù)返 回的指針賦予一個不同類型的指針時百炬,就不需要顯式地執(zhí)行強制類型轉(zhuǎn)換褐隆。未聲明函數(shù)的默認返回值為int,所以使用沒有正確函數(shù)聲明的強制 類型轉(zhuǎn)換可能會隱藏系統(tǒng)錯誤剖踊,因為int類型的長度與函數(shù)返回類型值的 長度不同(本例中是指針)庶弃。
函數(shù)free 釋放ptr指向的存儲空間。被釋放的空間通常被送入可用存 儲區(qū)池德澈,以后歇攻,可在調(diào)用上述3個分配函數(shù)時再分配。
realloc函數(shù)使我們可以增梆造、減以前分配的存儲區(qū)的長度(最常見的 用法是增加該區(qū))缴守。例如,如果先為一個數(shù)組分配存儲空間镇辉,該數(shù)組長 度為 512屡穗,然后在運行時填充它,但運行一段時間后發(fā)現(xiàn)該數(shù)組原先的 長度不夠用忽肛,此時就可調(diào)用 realloc 擴充相應存儲空間村砂。如果在該存儲區(qū) 后有足夠的空間可供擴充,則可在原存儲區(qū)位置上向高地址方向擴充屹逛, 無需移動任何原先的內(nèi)容础废,并返回與傳給它相同的指針值汛骂。如果在原存 儲區(qū)后沒有足夠的空間,則 realloc 分配另一個足夠大的存儲區(qū)色迂,將現(xiàn)存 的512個元素數(shù)組的內(nèi)容復制到新分配的存儲區(qū)香缺。然后,釋放原存儲 區(qū)歇僧,返回新分配區(qū)的指針图张。因為這種存儲區(qū)可能會移動位置,所以不應 當使任何指針指在該區(qū)中诈悍。
注意祸轮,realloc的最后一個參數(shù)是存儲區(qū)的新長度,不是新侥钳、舊存儲 區(qū)長度之差适袜。作為一個特例,若ptr是一個空指針舷夺,則realloc的功能與 malloc相同苦酱,用于分配一個指定長度為newsize的存儲區(qū)。
這些函數(shù)的早期版本允許調(diào)用realloc分配自上次malloc给猾、realloc 或calloc調(diào)用以來所釋放的塊疫萤。這種技巧可回溯到 V7,它利用 malloc 的搜索策略敢伸,實現(xiàn)存儲器緊縮扯饶。Solaris仍支持這一功能,而很多其他 平臺則不支持池颈。這種功能不被贊同尾序,不應再使用。
這些分配例程通常用sbrk(2)系統(tǒng)調(diào)用實現(xiàn)躯砰。該系統(tǒng)調(diào)用擴充(或縮 小)進程的堆
雖然sbrk可以擴充或縮小進程的存儲空間每币,但是大多數(shù)malloc和free 的實現(xiàn)都不減小進程的存儲空間。釋放的空間可供以后再分配琢歇,但將它 們保持在malloc池中而不返回給內(nèi)核脯爪。
大多數(shù)實現(xiàn)所分配的存儲空間比所要求的要稍大一些,額外的空間 用來記錄管理信息——分配塊的長度矿微、指向下一個分配塊的指針等。這 就意味著尚揣,如果超過一個已分配區(qū)的尾端或者在已分配區(qū)起始位置之前 進行寫操作涌矢,則會改寫另一塊的管理記錄信息。這種類型的錯誤是災難 性的快骗,但是因為這種錯誤不會很快就暴露出來娜庇,所以也就很難發(fā)現(xiàn)塔次。
在動態(tài)分配的緩沖區(qū)前或后進行寫操作,破壞的可能不僅僅是該區(qū) 的管理記錄信息名秀。在動態(tài)分配的緩沖區(qū)前后的存儲空間很可能用于其他 動態(tài)分配的對象励负。這些對象與破壞它們的代碼可能無關(guān),這造成尋求信 息破壞的源頭更加困難匕得。
其他可能產(chǎn)生的致命性的錯誤是:釋放一個已經(jīng)釋放了的塊;調(diào)用 free時所用的指針不是3個alloc函數(shù)的返回值等继榆。如若一個進程調(diào)用 malloc函數(shù),但卻忘記調(diào)用free函數(shù)汁掠,那么該進程占用的存儲空間就會連 續(xù)增加略吨,這被稱為泄漏(leakage)。如果不調(diào)用free函數(shù)釋放不再使用 的空間考阱,那么進程地址空間長度就會慢慢增加翠忠,直至不再有空閑空間。 此時乞榨,由于過度的換頁開銷秽之,會造成性能下降。
因為存儲空間分配出錯很難跟蹤吃既,所以某些系統(tǒng)提供了這些函數(shù)的 另一種實現(xiàn)版本考榨。每次調(diào)用這3個分配函數(shù)中的任意一個或free時,它們 都進行附加的檢錯态秧。在調(diào)用連接編輯器時指定一個專用庫董虱,在程序中就 可使用這種版本的函數(shù)。此外還有公共可用的資源申鱼,在對其進行編譯時 使用一個特殊標志就會使附加的運行時檢查生效愤诱。
有很多可替代malloc和free的函數(shù)。某些系統(tǒng)已經(jīng)提供替代存儲空間 分配函數(shù)的庫捐友。另一些系統(tǒng)只提供標準的存儲空間分配程序淫半。如果需 要,軟件開發(fā)者可以下載替代函數(shù)匣砖。下面討論某些替代函數(shù)和庫科吭。
1.libmalloc
基于SVR4的UNIX系統(tǒng),如Solaries猴鲫,包含了libmalloc庫对人,它提供了 一套與ISO C存儲空間分配函數(shù)相匹配的接口。libmalloc庫包括mallopt函 數(shù)拂共,它使進程可以設置一些變量牺弄,并用它們來控制存儲空間分配程序的 操作。還可使用另一個名為mallinfo的函數(shù)宜狐,以對存儲空間分配程序的 操作進行統(tǒng)計势告。2.vmalloc
Vo[1996]說明一種存儲空間分配程序蛇捌,它允許進程對于不同的存儲 區(qū)使用不同的技術(shù)。除了一些vmalloc特有的函數(shù)外咱台,該庫也提供了ISO C存儲空間分配函數(shù)的仿真器络拌。3.quick-fit
歷史上所使用的標準 malloc 算法是最佳適配或首次適配存儲分配策 略。quick-fit(快速適配)算法比上述兩種算法快回溺,但可能使用較多存 儲空間春贸。Weinstock和Wulf[1988]對該算法進行了描述,該算法基于將存 儲空間分裂成各種長度的緩沖區(qū)馅而,并將未使用的緩沖區(qū)按其長度組成不 同的空閑區(qū)列表∠榉蹋現(xiàn)在許多分配程序都基于快速適配。4.jemalloc
jemalloc函數(shù)實現(xiàn)是FreeBSD 8.0中的默認存儲空間分配程序瓮恭,它是 庫函數(shù)malloc族在FreeBSD中的實現(xiàn)雄坪。它的設計具有良好的可擴展性,可 用于多處理器系統(tǒng)中使用多線程的應用程序屯蹦。Evans[2006]說明了具體實 現(xiàn)及其性能評估维哈。5.TCMalloc
TCMalloc函數(shù)用于替代malloc函數(shù)族以提供高性能、高擴展性和高 存儲效率登澜。從高速緩存中分配緩沖區(qū)以及釋放緩沖區(qū)到高速緩存中時阔挠,
它使用線程-本地高速緩存來避免鎖開銷。它還有內(nèi)置的堆檢查程序和 堆分析程序幫助調(diào)試和分析動態(tài)存儲的使用脑蠕。TCMalloc庫是開源可用 的购撼,是Google-perftools工具中的一個。Ghemawat和Menage[2005]對此做 了簡單介紹谴仙。6.函數(shù)alloca
還有一個函數(shù)也值得一提迂求,這就是alloca。它的調(diào)用序列與malloc相 同晃跺,但是它是在當前函數(shù)的棧幀上分配存儲空間揩局,而不是在堆中。其優(yōu) 點是:當函數(shù)返回時掀虎,自動釋放它所使用的棧幀凌盯,所以不必再為釋放空 間而費心。其缺點是:alloca 函數(shù)增加了棧幀的長度烹玉,而某些系統(tǒng)在函 數(shù)已被調(diào)用后不能增加棧幀長度驰怎,于是也就不能支持alloca函數(shù)。盡管如 此二打,很多軟件包還是使用alloca函數(shù)砸西,也有很多系統(tǒng)實現(xiàn)了該函數(shù)。
本書中討論的4個平臺都提供了alloca函數(shù)。
7.9環(huán)境變量
如同前述芹枷,環(huán)境字符串的形式是:
name=value
UNIX內(nèi)核并不查看這些字符串,它們的解釋完全取決于各個應用
程序莲趣。例如鸳慈,shell使用了大量的環(huán)境變量。其中某一些在登錄時自動設 置(如HOME喧伞、USER等)走芋,有些則由用戶設置。我們通常在一個shell 啟動文件中設置環(huán)境變量以控制shell的動作潘鲫。例如翁逞,若設置了環(huán)境變量 MAILPATH,則它告訴Bourne shell溉仑、GNU Bourne-again shell和Korn shell 到哪里去查看郵件挖函。
ISO C定義了一個函數(shù)getenv,可以用其取環(huán)境變量值浊竟,但是該標準 又稱環(huán)境的內(nèi)容是由實現(xiàn)定義的怨喘。
#include <stdlib.h>
char *getenv(const char *name);
返回值:指向與name關(guān)聯(lián)的value的指針;若未找到,返回NULL 注意振定,此函數(shù)返回一個指針必怜,它指向name=value字符串中的value。
我們應當使用getenv從環(huán)境中取一個指定環(huán)境變量的值后频,而不是直接訪 問environ梳庆。
Single UNIX Specification中的POSIX.1定義了某些環(huán)境變量。如果支 持XSI擴展卑惜,那么其中也包含了另外一些環(huán)境變量定義膏执。圖7-7列出了由 Single UNIX Specification定義的環(huán)境變量,并指明本書討論的4種實現(xiàn)對 它們的支持情況残揉。由POSIX.1定義的各環(huán)境變量標記為?胧后,否則為XSI擴 展。本書討論的4種UNIX實現(xiàn)使用了很多依賴于實現(xiàn)的環(huán)境變量抱环。注 意壳快,ISO C沒有定義任何環(huán)境變量。
除了獲取環(huán)境變量值镇草,有時也需要設置環(huán)境變量眶痰。我們可能希望改 變現(xiàn)有變量的值,或者是增加新的環(huán)境變量梯啤。(在下一章將會了解到竖伯, 我們能影響的只是當前進程及其后生成和調(diào)用的任何子進程的環(huán)境,但 不能影響父進程的環(huán)境,這通常是一個shell進程七婴。盡管如此祟偷,修改環(huán)境 表的能力仍然是很有用的。)遺憾的是打厘,并不是所有系統(tǒng)都支持這種能 力修肠。圖7-8列出了由不同的標準及實現(xiàn)支持的各種函數(shù)。
clearenv不是Single UNIX Specification的組成部分户盯。它被用來刪除環(huán) 境表中的所有項嵌施。在圖7-8中,中間3個函數(shù)的原型是:
#include <stdlib.h>
int putenv(char *str);
函數(shù)返回值:若成功莽鸭,返回0;若出錯吗伤,返回非0
int setenv(const char *name, const char *value, int rewrite);
int unsetenv(const char *name);
兩個函數(shù)返回值:若成功,返回0;若出錯硫眨,返回?1 這3個函數(shù)的操作如下足淆。
putenv取形式為name=value的字符串,將其放到環(huán)境表中捺球。如果 name已經(jīng)存在缸浦,則先刪除其原來的定義。
setenv將name設置為value氮兵。如果在環(huán)境中name已經(jīng)存在裂逐,那么 (a)若rewrite非0,則首先刪除其現(xiàn)有的定義;(b)若rewrite為0泣栈,則 不刪除其現(xiàn)有定義(name不設置為新的value卜高,而且也不出錯)。
unsetenv刪除name的定義南片。即使不存在這種定義也不算出錯掺涛。
注意,putenv和setenv之間的差別疼进。setenv必須分配存儲空間薪缆,以 便依據(jù)其參數(shù)創(chuàng)建name=value字符串。putenv可以自由地將傳遞給它的 參數(shù)字符串直接放到環(huán)境中伞广。確實拣帽,許多實現(xiàn)就是這么做的,因此嚼锄,將 存放在棧中的字符串作為參數(shù)傳遞給putenv就會發(fā)生錯誤减拭,其原因是, 從當前函數(shù)返回時区丑,其棧幀占用的存儲區(qū)可能將被重用拧粪。
我們寫一個獲取HOME目錄環(huán)境變量的小代碼
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
/*----------------------------------- test getenv ----------------------------------------*/
/* env name. */
char name[255];
memset(name, 0, 255);
char *p = name;
/* get env. */
p = getenv("HOME");
printf("env HOME: %s\n", p);
}
這些函數(shù)在修改環(huán)境表時是如何進行操作的呢?對這一問題進行研 究修陡、考察是非常有益的】肾回憶程序的內(nèi)存布局魄鸦,其中,環(huán)境表(指向?qū)嶋H name=value字符串的指針數(shù)組)和環(huán)境字符串通常存放在進程存儲空間 的頂部(棧之上)癣朗。刪除一個字符串很簡單——只要先在環(huán)境表中找到 該指針号杏,然后將所有后續(xù)指針都向環(huán)境表首部順次移動一個位置。但是 增加一個字符串或修改一個現(xiàn)有的字符串就困難得多斯棒。環(huán)境表和環(huán)境字符串通常占用的是進程地址空間的頂部,所以它不能再向高地址方向 (向上)擴展:同時也不能移動在它之下的各棧幀主经,所以它也不能向低 地址方向(向下)擴展荣暮。兩者組合使得該空間的長度不能再增加。
(1)如果修改一個現(xiàn)有的name:
a.如果新value的長度少于或等于現(xiàn)有value的長度罩驻,則只要將新字 符串復制到原字符串所用的空間中;
b.如果新value的長度大于原長度穗酥,則必須調(diào)用malloc為新字符串分 配空間,然后將新字符串復制到該空間中惠遏,接著使環(huán)境表中針對name的 指針指向新分配區(qū)砾跃。
(2)如果要增加一個新的name,則操作就更加復雜节吮。首先抽高,必須 調(diào)用malloc為name=value字符串分配空間,然后將該字符串復制到此空 間中透绩。
a.如果這是第一次增加一個新name翘骂,則必須調(diào)用malloc為新的指針 表分配空間。接著帚豪,將原來的環(huán)境表復制到新分配區(qū)碳竟,并將指向新 name=value字符串的指針存放在該指針表的表尾,然后又將一個空指針 存放在其后狸臣。最后使environ指向新指針表莹桅。再看一下圖7-6,如果原來 的環(huán)境表位于棧頂之上(這是一種常見情況)烛亦,那么必須將此表移至堆 中诈泼。
但是,此表中的大多數(shù)指針仍指向棧頂之上的各name=value字符 串此洲。b.如果這不是第一次增加一個新name厂汗,則可知以前已調(diào)用malloc在 堆中為環(huán)境表分配了空間,所以只要調(diào)用 realloc呜师,以分配比原空間多存 放一個指針的空間娶桦。然后將指向新name=value字符串的指針存放在該表 表尾栏渺,后面跟著一個空指針。
7.10 setjmp 和 long jmp
在C中棋电,goto語句是不能跨越函數(shù)的摇锋,而執(zhí)行這種類型跳轉(zhuǎn)功能的 是函數(shù)setjmp和longjmp。這兩個函數(shù)對于處理發(fā)生在很深層嵌套函數(shù)調(diào) 用中的出錯情況是非常有用的祈争。
非局部goto——setjmp和longjmp函 數(shù)斤程。非局部指的是,這不是由普通的C語言goto語句在一個函數(shù)內(nèi)實施 的跳轉(zhuǎn)菩混,而是在棧上跳過若干調(diào)用幀忿墅,返回到當前函數(shù)調(diào)用路徑上的某 一個函數(shù)中。
#include <setjmp.h>
int setjmp(jmp_buf env);
返回值:若直接調(diào)用沮峡,返回0;若從longjmp返回疚脐,則為非0
void longjmp(jmp_buf env, int val);
setjmp參數(shù)env的類 型是一個特殊類型jmp_buf。這一數(shù)據(jù)類型是某種形式的數(shù)組邢疙,其中存 放在調(diào)用 longjmp 時能用來恢復棧狀態(tài)的所有信息棍弄。因為需在另一個函 數(shù)中引用env變量,所以通常將env變量定義為全局變量疟游。
要保證env不會因為棧幀切換改變呼畸,可以保存為static /extern 或在堆區(qū)建設
7.11 函數(shù)getrlimit 和setrlimit
每個進程都有一組資源限制,其中一些可以用getrlimit和setrlimit函 數(shù)查詢和更改颁虐。
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlptr);
int setrlimit(int resource, const struct rlimit *rlptr);
兩個函數(shù)返回值:若成功蛮原,返回0;若出錯,返回非0
這兩個函數(shù)在Single UNIX Specification的XSI擴展中定義聪廉。進程 的資源限制通常是在系統(tǒng)初始化時由0進程建立的瞬痘,然后由后續(xù)進程繼 承。每種實現(xiàn)都可以用自己的方法對資源限制做出調(diào)整板熊。
對這兩個函數(shù)的每一次調(diào)用都指定一個資源以及一個指向下列結(jié)構(gòu) 的指針框全。
struct rlimit {
rlim_t rlim_cur; /* soft limit: current limit */
rlim_t rlim_max; /* hard limit: maximum value for rlim_cur */ };
在更改資源限制時,須遵循下列3條規(guī)則干签。
(1)任何一個進程都可將一個軟限制值更改為小于或等于其硬限 制值津辩。
(2)任何一個進程都可降低其硬限制值,但它必須大于或等于其 軟限制值容劳。這種降低喘沿,對普通用戶而言是不可逆的。
(3)只有超級用戶進程可以提高硬限制值竭贩。
常量RLIM_INFINITY指定了一個無限量的限制蚜印。
這兩個函數(shù)的 resource 參數(shù)取下列值之一。圖 7-15 顯示哪些資源限 制是由 Single UNIX Specification定義并由本書討論的4種UNIX系統(tǒng)實現(xiàn) 支持的留量。
RLIMIT_AS 進程總的可用存儲空間的最大長度(字節(jié))窄赋。這影響 到 sbrk 函數(shù)(1.11節(jié))和mmap函數(shù)(14.8節(jié))哟冬。
RLIMIT_CORE core文件的最大字節(jié)數(shù),若其值為0則阻止創(chuàng)建core 文件忆绰。
RLIMIT_CPU CPU時間的最大量值(秒)浩峡,當超過此軟限制時,向 該進程發(fā)送SIGXCPU信號错敢。
RLIMIT_DATA 數(shù)據(jù)段的最大字節(jié)長度翰灾。這是始化數(shù)據(jù)、非初始以及堆的總和稚茅。
RLIMIT_FSIZE 可以創(chuàng)建的文件的最大字節(jié)長度纸淮。當超過此軟限制時,則向該進程發(fā)送SIGXFSZ信號亚享。
RLIMIT_MEMLOCK 一個進程使用mlock(2)能夠鎖定在存儲空間中
的最大字節(jié)長度萎馅。
RLIMIT_MSGQUEUE 進程為POSIX消息隊列可分配的最大存儲字
節(jié)數(shù)。
RLIMIT_NICE 為了影響進程的調(diào)度優(yōu)先級虹蒋,nice值(8.16節(jié))可設
置的最大限制。
RLIMIT_NOFILE 每個進程能打開的最多文件數(shù)飒货。
RLIMIT_NPROC 每個實際用戶 ID 可擁有的最大子進程數(shù)魄衅。更改此 限制將影響到sysconf函數(shù)在參數(shù)_SC_CHILD_MAX中返回的值(見2.5.4 節(jié))。
RLIMIT_SWAP 用戶可消耗的交換空間的最大字節(jié)數(shù)
RLIMIT_VMEM 這是RLIMIT_AS的同義詞塘辅。
資源限制影響到調(diào)用進程并由其子進程繼承晃虫。這就意味著,為了影
響一個用戶的所有后續(xù)進程扣墩,需將資源限制的設置構(gòu)造在shell之中哲银。確 實,Bourne shell呻惕、GNU Bourne-again shell和Korn shell具有內(nèi)置的ulimit命令荆责,C shell具有內(nèi)置limit命令。(umask和chdir函數(shù)也必須是shell內(nèi)置 的亚脆。)
打印由系統(tǒng)支持的所有資源當前的軟限制和硬限制做院。 為了在各種實現(xiàn)上編譯該程序,我們已經(jīng)條件地包括了各種不同的資源 名濒持。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>
rlimit my_rlimit;
int main() {
/*----------------------------------- get cpu rlimit ----------------------------------------*/
int ret = getrlimit(RLIMIT_CPU, &my_rlimit);
printf("cpu rlimit: (%llu) (%llu)\n", my_rlimit.rlim_cur, my_rlimit.rlim_max);
/*----------------------------------- get nire rlimit ----------------------------------------*/
ret = getrlimit(RLIMIT_AS, &my_rlimit);
printf("AS rlimit: (%llu) (%llu)\n", my_rlimit.rlim_cur, my_rlimit.rlim_max);
/*----------------------------------- get stack rlimit ----------------------------------------*/
ret = getrlimit(RLIMIT_STACK, &my_rlimit);
printf("stack rlimit: (%llu) (%llu)\n", my_rlimit.rlim_cur, my_rlimit.rlim_max);
return 0;
}
也可以通過系統(tǒng)命令行來修改硬上限
- linux可以通過ulimit命令查看棧上限和設置上限
ulimit -a 查看進程所有資源上限
ulimit -s xx 修改棧上限
7.12 小節(jié)
理解UNIX系統(tǒng)環(huán)境中C程序的環(huán)境是理解UNIX系統(tǒng)進程控制特性 的先決條件键耕。本章說明了一個進程是如何啟動和終止的,如何向其傳遞 參數(shù)表和環(huán)境柑营。雖然參數(shù)表和環(huán)境都不是由內(nèi)核進行解釋的屈雄,但內(nèi)核起 到了從exec的調(diào)用者將這兩者傳遞給新進程的作用。
本章也說明了C程序的典型存儲空間布局官套,以及一個進程如何動態(tài) 地分配和釋放存儲空間酒奶。詳細地了解用于維護環(huán)境的一些函數(shù)是有意義 的蚁孔,因為它們涉及存儲空間分配。本章也介紹了setjmp 和 longjmp 函 數(shù)讥蟆,它們提供了一種在進程內(nèi)非局部轉(zhuǎn)移的方法勒虾。最后介紹了各種實現(xiàn) 提供的資源限制功能。