APUE第7章 進程環(huán)境

7.1引言

image.png

將介紹進程控制原語恨统,在此之前需先了解進程的環(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");
}

image.png

-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程序是如何啟動的对供,以及它終止的各種方式。

image.png

注意氛濒,內(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;
}
image.png

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;
}
image.png

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)境字符串玛界。


image.png

按照慣例,環(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]);
    }
}
image.png

7.6 C程序儲存空間布局

歷史沿襲至今造虎,C程序一直由下列幾部分組成:


image.png
  • 代碼段:程序的所有指令會存放在這個區(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 儲存空間分配

image.png

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)境變量。

image.png

除了獲取環(huán)境變量值镇草,有時也需要設置環(huán)境變量眶痰。我們可能希望改 變現(xiàn)有變量的值,或者是增加新的環(huán)境變量梯啤。(在下一章將會了解到竖伯, 我們能影響的只是當前進程及其后生成和調(diào)用的任何子進程的環(huán)境,但 不能影響父進程的環(huán)境,這通常是一個shell進程七婴。盡管如此祟偷,修改環(huán)境 表的能力仍然是很有用的。)遺憾的是打厘,并不是所有系統(tǒng)都支持這種能 力修肠。圖7-8列出了由不同的標準及實現(xiàn)支持的各種函數(shù)。

image.png

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);
}
image.png

這些函數(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) 支持的留量。


image.png
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;
}
image.png

也可以通過系統(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) 提供的資源限制功能。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瘸彤,一起剝皮案震驚了整個濱河市修然,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌质况,老刑警劉巖愕宋,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異结榄,居然都是意外死亡中贝,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門臼朗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來邻寿,“玉大人,你說我怎么就攤上這事视哑⌒宸瘢” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵挡毅,是天一觀的道長蒜撮。 經(jīng)常有香客問我,道長跪呈,這世上最難降的妖魔是什么段磨? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮耗绿,結(jié)果婚禮上苹支,老公的妹妹穿的比我還像新娘。我一直安慰自己误阻,他們只是感情好沐序,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著堕绩,像睡著了一般策幼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上奴紧,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天特姐,我揣著相機與錄音,去河邊找鬼黍氮。 笑死唐含,一個胖子當著我的面吹牛浅浮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捷枯,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼滚秩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了淮捆?” 一聲冷哼從身側(cè)響起郁油,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎攀痊,沒想到半個月后桐腌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡苟径,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年案站,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棘街。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蟆盐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出遭殉,到底是詐尸還是另有隱情舱禽,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布恩沽,位于F島的核電站,受9級特大地震影響翔始,放射性物質(zhì)發(fā)生泄漏罗心。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一城瞎、第九天 我趴在偏房一處隱蔽的房頂上張望渤闷。 院中可真熱鬧,春花似錦脖镀、人聲如沸飒箭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弦蹂。三九已至,卻和暖如春强窖,著一層夾襖步出監(jiān)牢的瞬間凸椿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工翅溺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留脑漫,地道東北人髓抑。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像优幸,于是被迫代替她去往敵國和親吨拍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容