a亚洲精品_精品国产91乱码一区二区三区_亚洲精品在线免费观看视频_欧美日韩亚洲国产综合_久久久久久久久久久成人_在线区

首頁 > 系統 > Unix > 正文

UNIX高級環境編程(8)進程環境(Process Environment)- 進程的啟動和退出、內存布局、環境變量列表

2024-06-28 13:21:41
字體:
來源:轉載
供稿:網友
UNIX高級環境編程(8)進程環境(PRocess Environment)- 進程的啟動和退出、內存布局、環境變量列表

在學習進程控制相關知識之前,我們需要了解一個單進程的運行環境。

本章我們將了解一下的內容:

  • 程序運行時,main函數是如何被調用的;
  • 命令行參數是如何被傳入到程序中的;
  • 一個典型的內存布局是怎樣的;
  • 如何分配內存;
  • 程序如何使用環境變量;
  • 程序終止的各種方式;
  • 跳轉(longjmp和setjmp)函數的工作方式,以及如何和棧交互;
  • 進程的資源限制

?

1 main函數

main函數聲明:

int main (int argc, char *argv[]);

參數說明:

  • argc:命令行參數個數
  • argv:指向參數列表數組的指針

main函數啟動前:

  • C程序由內核執行,通過系統調用exec;
  • main函數調用前,執行指定的啟動路徑(start-up routine);
  • 可執行文件認為此地址為程序的啟動地址,該地址由鏈接器指定;
  • 啟動路徑從內核獲取參數列表和環境變量,使得main函數可以在稍后被調用時可以獲取這些變量。

?

2 進程終止

一共有8中終止進程的方式,5種正常終止和3種異常終止。

5種正常終止:

  1. 從main函數返回;
  2. 調用exit;
  3. 調用_exit或_Exit;
  4. 最后一個線程返回;
  5. 最后一個線程調用pthread_exit。

3種異常終止:

  1. 調用abort;
  2. 接收到一個信號;
  3. 最后一個線程應答或者一個接收到一個退出請求

啟動地址(start-up routine)同樣也是main函數的返回地址。

要獲取該地址,可以通過以下的方式:

exit (main(argc, argv));

?

退出函數

函數聲明:

#include <stdlib.h>

void exit(int status);

void _Exit(int status);

#include <unistd.h>

void _exit(int status);

函數細節:

  • _exit和_Exit立刻返回到內核;
  • exit函數返回內核前會進行一些清理環境工作;

返回一個整數和調用exit函數,并傳入該整數的作用是相同的:

exit(0);

return 0;

?

atexit函數

函數聲明

#include <stdlib.h>

int atexit(void (*func)(void));

函數細節

  • 每個進程可以注冊32個函數,這些函數可以在主函數調用exit時自動被調用
  • 通過atexit注冊的退出時處理函數稱為退出句柄(exit handlers)
  • 這些退出句柄的調用順序為注冊時的相反順序
  • exit函數第一次調用退出句柄時,會關閉所有打開的流
  • 如果主程序調用了exec系列函數,則所有注冊的退出句柄都會被清空

?

程序啟動和終止流程圖

?NewImage

Example:

#include "apue.h"

?

static void my_exit1(void);

static void my_exit2(void);

?

int

main(void)

{

? ? if (atexit(my_exit2) != 0)

? ? ? ? err_sys("can't register my_exit2");

?

? ? if (atexit(my_exit1) != 0)

? ? ? ? err_sys("can't register my_exit1");

? ? if (atexit(my_exit1) != 0)

? ? ? ? err_sys("can't register my_exit1");

?

? ? printf("main is done/n");

? ? return(0);

}

?

static void

my_exit1(void)

{

? ? printf("first exit handler/n");

}

?

static void

my_exit2(void)

{

? ? printf("second exit handler/n");

}

?執行結果:

NewImage

?

3 命令行參數Example:

#include "apue.h"

?

int

main(int argc, char *argv[])

{

? ? int ? ? i;

?

? ? for (i = 0; i < argc; i++)? ? ? /* echo all command-line args */

? ? ? ? printf("argv[%d]: %s/n", i, argv[i]);

? ? exit(0);

}

執行結果:

NewImage?

?

4 環境變量列表

每個程序會接受一個環境變量列表,該列表是一個數組,由一個數組指針指向,該數組指針類型為:

extern char **environ;

例如,如果環境變量里有5個字符串(C風格字符串),如下圖所示:

NewImage

5 C程序的內存布局

典型的C程序的內存布局如下圖所示:

NewImage

上圖說明:

  • 文本段(Text Segment),保存CPU將要執行的機器指令。文本段是可共享的,所以某個程序多次執行時,對應的文本段只需要在內存中存有一份拷貝。文本段是只讀的(read-only),防止程序的指令被修改。
  • 已初始化數據段(initialized data segment),保存程序中被初始化的全局變量(定義在任何函數之外)。例如:int maxcount = 99; 全局變量變量maxcount被保存在初始化數據段。
  • 未初始化數據段(uninitialized data segment),也被稱為BSS(block started by symbol),這個段中的數據在程序執行之前被內核初始化為0或者null。;例如定義一個全局變量(定義在任何函數之外),long sum[1000]; ?該變量保存在未初始化數據段中。
  • 棧(Stack):存儲臨時變量,函數相關信息。當一個函數被調用時,返回地址、調用者相關信息(如寄存器信息)會被保存在棧中。該被調用的函數會在棧上分配一部分空間保存它的臨時變量。函數的遞歸調用也是應用這個原理。每一次函數調用自己,都會保存當前函數的信息,然后再棧上開辟一個新的空間用于保存該次函數的信息,和以前的函數并沒有影響。
  • 堆(Heap):動態內存分配位置。堆的位置位于未初始化數據段和棧的中間。

?

6 內存分配(Memory Allocation)

有三個函數可以用于內存分配:

  • malloc:分配指定字節數的內存,未初始化。
  • calloc:分配指定數目的對象大小的內存,內存初始化為0;
  • realloc:增加或減小之前分配的內存。移動舊內存的內容到新的更大的內存塊,多余的部分內存未初始化。

函數聲明:

#include <stdlib.h>

void *malloc(size_t size);

void *calloc(size_t nobj, size_t size);

void *realloc(void *ptr, size_t newsize);

void free(void* ptr);

函數細節:

  1. 三個函數返回的內存指針一定是內存對齊的,這樣可以用來保存于不同的對象;
  2. free函數用于釋放ptr指向的內存,被分配的內存放入內存池中用于下次的內存分配;
  3. realloc函數用于改變之前分配的內存的大小。比如運行時我們申請了一段內存用于存儲512個元素的數組,后來發現內存大小不夠,這時可以調用realloc。如果操作系統發現在當前內存的后面有足夠的內存,則直接分配多余的內存到當前內存中,然后返回傳入的指針(即直接擴展內存)。但是如果當前內存后面沒有足夠大小的空間,則系統重新分配一個足夠大的內存,將舊內存塊中得內容拷貝到新內存塊中,然后返回新內存的地址。
  4. 內存分配函數使用系統調用sbrk來實現。該系統調用的作用是擴展進程的堆。
  5. 一般實際分配的內存塊都比請求的要大,多出來的部分用來存儲內存塊大小、指向下一內存塊的指針等信息。寫覆蓋信息記錄區的錯誤是非常隱蔽而且嚴重的。

?

7 環境變量(Environment Variable)

環境變量的字符串形式:

name=value

?內核不關注環境變量,各種應用才會使用環境變量。

獲取環境變量值使用函數getenv。

#include <stdlib.h>

char* getenv(const char* name);

// Returns: pointer to value associated with name, NULL if not found

修改環境變量的函數:

#include <stdlib.h>

int putenv(char* str);

int setenv(const char* name, const char* value, int rewrite);

int unsetenv(const char* name);

?函數細節:

  • 函數putenv傳入一個字符串,形式為name=value,加入到環境變量列表中。如果name已經存在,先刪除舊的定義。
  • 函數setenv傳入一個name和一個value,如果name已經存在,則參數rewrite決定是否覆蓋舊的定義,如果rewrite為非零,則會覆蓋舊的定義。
  • 函數unsetenv刪除name的定義,如果name不存在,也不報錯。?

?修改環境變量列表的過程是一件很有趣的事情

從上面的C程序內存布局圖中可以看到,環境變量列表(保存指向環境變量字符串的一組指針)保存在棧的上方內存中。

在該內存中,刪除一個字符串很簡單。我們只需要找到該指針,刪除該指針和該指針指向的字符串。

但是增加或修改一個環境變量困難得多。因為環境變量列表所在的內存往往在進程的內存空間頂部,下面是棧。所以該內存空間無法被向上或者向下擴展。

所以修改環境變量列表的過程如下所述:

  • 如果我們修改一個已經存在的name:
    • 如果新的value的大小比已經存在的value小或者相當,直接覆蓋舊的value;
    • 如果新的value的大小比已經存在的value大,則我們必須為新的value malloc一個新的內存空間,拷貝新value到該內存中,替換指向舊value的指針為指向新value的指針。
  • 如果我們新增一個環境變量:
    • 首先我們需要調用malloc為字符串name=value分配空間,拷貝該字符串到目標內存中;
    • 如果這是我們第一次添加環境變量,我們需要調用malloc分配一個新的空間,拷貝老的環境量列表到新的內存中,并在列表后新增目標環境變量。然后我們設置environ指向新的環境變量列表。
    • 如果這不是我們第一次新增環境變量,則我們只需要realloc多分配一個環境變量的空間,新增的環境變量保存在列表尾部,列表最后仍然是一個null指針。

?

小結

本篇介紹了進程的啟動和退出、內存布局、環境變量列表和環境變量的修改。

下一篇將接著學習四個函數setjmp、longjmp、getrlimit和setrlimit。

?

?

參考資料:

《Advanced Programming in the UNIX Envinronment 3rd》


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 日一日啪一啪 | 91精品一区二区三区久久久久久 | 99成人精品 | 91精品国产综合久久精品 | 日本三级国产 | 九九精品在线 | y111111国产精品久久婷婷 | 成人激情视频在线观看 | 欧美极品一区二区 | 成年无码av片在线 | 亚洲 成人 av| 九九免费在线观看 | 日韩毛片 | 亚洲欧美日韩另类精品一区二区三区 | 国产欧美精品一区二区三区四区 | av网站观看 | 97人人爽人人澡人人精品 | 日韩在线欧美 | 男女羞羞视频网站18 | 国产乱码精品一区二区三区手机版 | 亚洲高清在线观看视频 | 久久国产久 | 四季久久免费一区二区三区四区 | 久久99久久久久 | 青青草国产在线 | 日韩一区二区三区免费视频 | 蜜臀影院 | 欧美日韩第一区 | 成人欧美一区二区 | 哪里有免费的黄色网址 | 精品久久久久久久 | 午夜激情电影在线 | 亚洲婷婷一区二区三区 | 日本一区二区视频在线 | 成人精品视频在线观看 | 亚洲自拍电影网 | 中文字幕第一页在线 | 91国产精品 | 国产午夜精品一区二区三区 | 自拍偷拍第一页 | 国产精品一区二区三区视频网站 |