歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> 學習Linux >> [APUE]UNIX進程的環境(下),apueunix

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

日期:2017/3/3 17:42:24   编辑:學習Linux

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

[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
void free(void *ptr);

這三個分配函數返回的指針一定是適當對齊的,使其可以用於任何數據對象。在一個特定的系統上,如果最苛刻的對齊要求是double,則對齊必須在8的倍數的地址單元處,那麼這三個函數返回的指針都應這樣對齊。 free函數釋放的空間通常被送入可用存儲區池,以後可在調用分配函數時再調用。 realloc如果在原存儲區後有足夠的空間可供擴充,則可在原存儲區位置上向高地址方向擴充。並返回傳給它的同樣的指針值。如果在原存儲區後沒有足夠的空間則realloc分配一個足夠大的存儲區,將現存的內容復制到新分配的存儲區中。因為這種存儲區會移動位置所以不應使任何指針指到該區。 realloc的最後一個參數是存儲區的newsize而不是新舊長度之差。如果ptr是空指針,則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的指針存在該指針表的表尾,然後又將一個空指針存在其後。最後使environ指針指向新指針表。再看上一節中的內存分配圖,如果原來的環境表位於棧頂之上(這是常見情況)那麼必須將此表移至堆中。但是此表中的大多數指針仍指向棧頂之上的個name=value字符串。
    (b) 如果這不是第一次增加一個新name,則可知以前調用malloc在堆中為環境表分匹配了空間,所以只要調用realloc,以分配比原來空間多一個指針的空間。然後將該指向新name=value字符串的指針存放在該表表尾,後面跟著一個空指針。

四、setjpm和longjmp函數

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

#include <setjmp.h>

int setjmp(jmp_buf env);
返回值:直接調用則為0,若從longjmp返回則為非0
void 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中。

http://xxxxxx/Linuxjc/1185099.html TechArticle

Copyright © Linux教程網 All Rights Reserved