[APUE]UNIX進程的環境(下),apueunix

來源:互聯網
上載者:User

[APUE]UNIX進程的環境(下),apueunix
一、共用庫

  共用庫使得可執行檔中不再需要包含常用的庫函數,而只需在所有進程都可存取的儲存區中儲存這種庫常式的一個副本。程式第一次執行的時候或第一次調用某個庫函數的時候,用動態連結方法將程式與共用庫函數相連結,這減少了每個可執行檔的長度,但增加了一些已耗用時間開銷。另一個優點就是可以用庫函數的新版本來替換老版本而無需對該庫的程式重新連結編譯。   

  不同的系統使用不同的方法說明程式是否需要使用共用庫。比較典型的有cc和ld命令的可選項。

二、 儲存空間分配

  ANSI C說明了三個儲存空間動態分配的函數
(1) malloc。分配指定位元組數的儲存區。此儲存區中的初始值不確定。 (2) calloc。在記憶體中動態地分配nobj個長度為size的連續空間。該空間中的每一位都初始化為0。 (3) realloc。更改以前分配區的長度(增加或減少)。當增加長度時,可能需要將以前分配區的內容移到另一個足夠大的地區,而且新增地區內的初始值不確定。

#include <stdlib.h>void *malloc(size_t size);void *calloc(size_t nboj, size_t size);void *realloc(void *ptr, size_t newsize);三個函數返回:成功返回為非null 指標,出錯為NULLvoid free(void *ptr);

  這三個分配函數返回的指標一定是適當對齊的,使其可以用於任何資料對象。在一個特定的系統上,如果最苛刻的對齊要求是double,則對齊必須在8的倍數的地址單元處,那麼這三個函數返回的指標都應這樣對齊。   free函數釋放的空間通常被送入可用儲存區池,以後可在調用分配函數時再調用。   realloc如果在原儲存區後有足夠的空間可供擴充,則可在原儲存區位置上向高地址方向擴充。並返回傳給它的同樣的指標值。如果在原儲存區後沒有足夠的空間則realloc分配一個足夠大的儲存區,將現存的內容複寫到新分配的儲存區中。因為這種儲存區會移動位置所以不應使任何指標指到該區。   realloc的最後一個參數是儲存區的newsize而不是新舊長度之差。如果ptr是null 指標,則realloc功能與malloc相同。用於分配一個制定長度newsize的儲存區。
  這些分配常式通常通過sbrk系統調用實現。該系統調用擴充或縮小進程的堆。
  雖然sbrk可以擴充或縮小一個進程的儲存空間,但是大多數malloc和free的實現都不減小進程的儲存空間而是將它們儲存在malloc池中而不返回給核心。
  大多數實現所分配的儲存空間比所要求的要大,額外的空間用來記錄管理資訊--分配塊的長度,指向下一個分配塊的指標等等。這就意味著如果寫過一個已指派區的尾端,則會改寫後一塊的管理資訊。將指向分配塊的指標向後移動可能也會改寫本塊的管理資訊。
  其他可能出現的錯誤:釋放一個已經釋放了的塊;調用free所用的指標不是三個alloc函數的傳回值等。

alloca函數

 alloca函數是在當前函數的棧幀上分配儲存空間。優點是:當函數返回時自動釋放它所使用的棧幀,缺點是:某些系統在函數已經被調用後不能增加棧幀長度,於是也就不能支援alloca函數。

三、環境變數

  ANSI C定義了一個函數getenv,可以用其取環境變數值,但是該標準又稱環境的內容是由實現定義。

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

  POSIX.1和XPG3定義了某些環境變數。下表列出了由這兩個標準定義並受到SVR4和4.3+BSD支援的環境變數。

  

  除了取環境變數值,有時也需要設定環境變數,或者是改變現有變數的值,或者是增加新的環境變數。但是不是所有系統都支援這些操作。下表列出了不同的標準及實現支援的各種函數:

    

 

  中間三個函數的原型是:

#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已存在,(a)如果rewrite非0,則覆蓋.(b)如果rewrite為0,則不覆蓋而且也不出錯。
  • unsetenv刪除name的定義,即使name不存在也不出錯。
    **這些函數在修改環境表時是如何進行操作的呢?**上一節中記憶體配置的那張圖中,環境表和環境字串典型的存放在進程儲存空間的頂部(棧之上)。刪除一個字串很簡單--只要找到該指標,然後將所有後續指標都向下移一個位置。但是增加一個字串或修改一個現存的字串就比較困難。棧以上的空間因為已處於進程儲存空間的頂部所以無法擴充,即無法向上擴充也無法向下擴充。
    (1) 如果修改一個現存的name:
      (a) 如果新value的長度少於或等於value的長度,則只要在原字串所用空間中寫入新字串。
      (b) 如果新value的長度大於原長度,則必須調用malloc為新字串分配空間,然後將新字元寫入該空間中,然後使環境表中針對name的指標指向新分配區。
    (2) 如果要增加一個新的name,則操作更為複雜。首先調用malloc為name=value分配空間然後將該字串寫入該空間。然後:
      (a) 如果這是第一次增加一個新name,則必須調用malloc為新的指標表分配空間。將原來的環境表複製到新分配區。並將指向新name=value的指標存在該指標表的表尾,然後又將一個null 指標存在其後。最後使environ指標指向新指標表。再看上一節中的記憶體配置圖,如果原來的環境表位於棧頂之上(這是常見情況)那麼必須將此表移至堆中。但是此表中的大多數指標仍指向棧頂之上的個name=value字串。
      (b) 如果這不是第一次增加一個新name,則可知以前調用malloc在堆中為環境表分匹配了空間,所以只要調用realloc,以分配比原來空間多一個指標的空間。然後將該指向新name=value字串的指標存放在該表表尾,後面跟著一個null 指標。
四、setjpm和longjmp函數

  在C中不允許使用跳躍函數的goto語句。而執行這種跳轉功能的是非局部跳轉函數setjmp和longjmp。非局部表示這不是子啊一個函數內的普通的C語言goto語句,而是在棧上跳過若干調用棧,返回到當前函數調用路徑上的一個函數中。

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

  在希望返回到的位置調用setjmp,因為我們直接調用該函數所以其傳回值為0。setjmp的參數env是一個特殊類型jmp_buf。這一資料類型是某種形式的數組,其中存放在調用longjmp時能用恢複棧狀態的所有資訊。一般,env變數是個全域變數,因為需要從另一個函數中引用它。
  當檢查到一個錯誤時,則調用longjmp函數,第一個參數就是在調用setjmp時所用的env,第二個val是個非0值,它成為從setjmp處返回的值。使用第二個參數的原因是對於一個setjmp可以有多個longjmp。

  下面是APUE上使用setjmp/longjmp的執行個體

  

 

  執行main時,調用setjmp,它將所需的資訊記入變數jmpbuffer中返回0。然後調用do_line,它又調用cm_add,假定在其中檢測到一個錯誤。在cmd_add中調用longjmp之前,棧的形式
  

 

  但是longjmp使棧回到執行main函數時的情況,也就是拋棄了cmd_add和do_line的棧幀。調用longjmp造成main中setjmp的返回。但是,這一次的傳回值是1(longjmp的第二個參數)。

1. 自動、寄存器和易失變數

  在main函數中,自動變數和寄存器變數的狀態如何?當longjmp返回到main函數時,這些變數的值是否能恢複到以前調用setjmp時的值(即滾回原先值),或者這些變數的值保持為調用do_line時的值(do_line調用cmd_add,cmd_add又調用longjmp)?大多數實現並不滾回這些自動變數和寄存器變數的值,而所有標準則說它們的值是不確定的。如果有一個自動變數而又不想使其數值滾回可以定義其為具有volatile屬性。說明為全域和靜態變數的值在執行longjmp時保持不變。   我們通過以下程式來說明在調用longjmp後,自動變數、寄存器變數和易失變數的不同情況。

  

#include <setjmp.h>static void f1(int, int, int);static void f2(void);static jmp_buf jmpbuffer;int main(void){    int count;    register int val;    volatile int sum;    count 2; val = 3; sum = 4;    if (setjmp(jmpbuffer) != 0) {        printf("after longjmp: count = %d, val = %d, sum = %d\n", count, val, sum);        exit(0);    }      count = 97; val = 98; sum = 99;    f1(count, val, sum);}static void f1(int i, int j, int k){    printf("in f1():count = %d, val = %d, sum = %d\n", i, j, k);    f2();}static void f2(void){    longjmp(jmpbuffer, 1);}

  如果以不帶最佳化和帶最佳化對此程式分別進行編譯,然後運行它們得到的結果是不同的:
  

  易失變數不受最佳化的影響,在longjmp之後的值,是它在調用f1時的值。存放在儲存空間中的變數將具有longjmp時的值,而在CPU和浮點寄存器中的變數則恢複為調用setjmp時的值。不進行最佳化時所有這三個變數都存放在儲存空間中(會忽略val寄存器儲存最佳化)。而進行最佳化時,count和val都存放在寄存器中。sum由於加了volatile限定符(該限定符修飾表示告訴編譯器不要對這個變數進行最佳化)所以不會放到寄存器中。

2. 自動變數的潛在問題

  對於如下程式,有一個open_data的函數,它開啟了一個標準IO流,然後為該流設定緩衝:

#include <stdio.h>#define DATAFILE "datafile"FILE *open_data(void){    FILE *fp;    char databuf[BUFSIZ]; /* setvbuf設定的標準IO緩衝 */        if ((fp = fopen(DATAFILE, "r")) == NULL) {        return (NULL);    }        if (setvbuf(fp, databuf, _IOLBF, BUFSIZ) != 0) {        return (NULL);    }        return (fp);}

  該程式存在的問題是:當open_data返回時,它在棧上所使用的空間將由下一個被調用函數的棧幀使用。但是,標準IO函數仍然使用原先在棧上分配的儲存空間作為流的緩衝。這就產生了問題。為了改正這個問題應該在全域空間靜態(如static或extern),或者動態為數組分配空間(malloc在堆上分配)。

五、getrlimit和settlimit函數

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

#include <sys/time.h>#inlcude <sys/resource.h>int getrlimit(int resource, struct rlimit *rlptr);int setrlimit(int resource, const struct rlimit *rlptr);傳回值:成功為0,出錯非0.

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

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

這兩個函數不屬於POSIX.1,但SVR4和4.3+BSD提供
SVR4在上面的結構中使用基本系統資料類型rlim_t,其他系統則將這兩個成員定義為整型或長整型。
進程的資源限制通常是在系統初始化時由0進程建立的,然後由後續進程繼承。在SVR4中,系統預設值可以查看檔案/etc/conf/cf.d/mtune。在4.3+BSD中,系統預設值分散在多個標頭檔中。

  在更改資源限制時,須遵循下列三條規則:

  (1) 任何一個進程都可將一個軟式節流更改為小於或等於其軟式節流。

  (2) 任何一個進程都可降低其硬限制值,但它必須大於或等於其軟式節流值。這種降低,對普通使用者是無法復原反的。

  (3) 只有超級使用者可以提高硬限制。

 

  一個無限量的限制通常由常數RLIM_INFINITY指定。

  這兩個函數的resource參數取下列值之一。並非所有資源限制都受到SVR4和4.3+BSD的支援。

  • RLIMIT_CODE (SVR4及4.3+BSD) core檔案的最大位元組數,若其值為0則組織建立core檔案。
  • RLIMIT_CPU(SVR4及4.3+BSD) CPU時間的最大量值(秒),當超過此限制時,向該進程發送SIGXCPU訊號。
  • RLIMIT_DATA(SVR4及4.3+BSD) 資料區段的最大位元組長度。這是圖中初始化資料、非初始化資料以及堆的總和。
  • RLIMIT_FSIZE (SVR4及4.3+BSD) 可以建立的檔案的最大位元組長度。當超過此軟式節流時,則向該進程發送SIGXFSZ訊號。
  • RLIMIT_MEMLOCK (4.3+BSD) 鎖定在儲存空間地址空間(尚未實現)。
  • RLIMIT_NOFILE (SVR4) 每個進程能開啟的最多檔案數。更改此限制將影響到sysconf函數在參數_SO_OPEN_MAX中返回的值。
  • RLIMIT_NPROC (4.3+BSD) 每個實際使用者ID所擁有的最大子進程數。更改此限制將影響到sysconf函數在參數_SC_CHILD_MAX中返回的值。
  • RLIMIT_OFILE (4.3+BSD) 與SVR4的RLIMIT_NOFILE相同
  • RLIMIT_RSS (4.3+BSD) 最大駐記憶體集位元組長度(RSS)。如果實體儲存體器供不應求則核心將從進程處取回超過RSS的部分。
  • RLIMIT_STACK (SVR4及4.3+BSD)棧的最大位元組長度。
  • RLIMIT_VMEN (SVR4) 可映照地址空間的最大位元組長度。這影響到mmap函數。

  資源限制將影響到調用進程並由其子進程繼承。這就意味著為了影響一個使用者的所有後續進程,需將資源限制設定構造在shell中。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.