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

来源:http://www.cnblogs.com/orlion/archive/2017/01/04/6246367.html
-Advertisement-
Play Games

一、共用庫 共用庫使得可執行文件中不再需要包含常用的庫函數,而只需在所有進程都可存取的存儲區中保存這種庫常式的一個副本。程式第一次執行的時候或第一次調用某個庫函數的時候,用動態鏈接方法將程式與共用庫函數相鏈接,這減少了每個可執行文件的長度,但增加了一些運行時間開銷。另一個優點就是可以用庫函數的新版本 ...


一、共用庫

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

  不同的系統使用不同的方法說明程式是否需要使用共用庫。比較典型的有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中。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • SQL Check? 一款實時監測SQL資料庫性能、實時排查的問題的免費工具。 可以實時監測20個左右的SQL關鍵性能指標,每個指標都已圖形化動態直播形式展現。 適合DBA、資料庫管理人員應急處理資料庫突發的性能問題,如死鎖、阻塞等,迅速定位性能瓶頸的根源。 主要特點 1. 吞吐量監控實時、直觀 實 ...
  • 資料庫:資料庫是一種以某種有組織的方式存儲的數據集合。其本質就是一個容器,通常是一個或者一組文件。 表:表示一種結構化的文件,可用來存儲某種特定類型的數據。 模式:描述資料庫中特定的表以及整個資料庫和其中表的關係。表具有一些特性,這些特性定義了數據在表中如何存儲,可以存儲什麼樣的數據,數據如何分解, ...
  • openresty 前端開發入門五之Mysql篇 這章主要演示怎麼通過lua連接mysql,並根據用戶輸入的name從mysql獲取數據,並返回給用戶 操作mysql主要用到了lua resty mysql庫,代碼可以在 "github" 上找得到 而且上面也有實例代碼 由於官網給出的例子比較基本, ...
  • HBase定義 HBase 是一個高可靠、高性能、面向列、可伸縮的分散式存儲系統,利用Hbase技術可在廉價PC Server上搭建 大規模結構化存儲集群。 HBase 是Google Bigtable 的開源實現,與Google Bigtable 利用GFS作為其文件存儲系統類似, HBase 利 ...
  • 在連接mysql jdbc時候,拋出了 com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Communication link failure, message from server: "Can't get  ...
  • 如果我想得到這樣一個結果集:分組排序,並且每組限定記錄集的數量,用一條SQL語句能辦到嗎? 比如說,我想找出學生期末考試中,每科的前3名,並按成績排序,只用一條SQL語句,該怎麼寫? 表[TScore]的結構 code 學號 char subject 科目 int score 成績 int 可以這樣 ...
  • 親測配置: 系統:Linux lite 3.2 x86_64(Ubuntu其他版本可參考修改) 筆記本:華碩(asus)1201N 達到的效果: 可以正常使用Fn+F5調暗,Fn+F6調亮. 設置步驟: 修改/etc/default/目錄下文件grub 將15~16行內容: 修改為如下內容 備註:上 ...
  • 出於安全性的考慮,不建議在bash腳本中註釋掉不使用的代碼。也就是說如果某段代碼不使用了,那麼應該刪除掉,而不是簡單地註釋掉。假如你突然意識到這一點,而以前並沒有遵從這個原則,現在需要找出腳本中的註釋性代碼,這可能是一個不小的工作量,讓我們寫一個腳本來幫助你吧,準確性無法達到百分之百,但是能夠提升效 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...