麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 系統 > 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》


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 久久99亚洲精品久久99果 | 中文国产在线视频 | 国产一区二区三区四区波多野结衣 | 最近免费观看高清韩国日本大全 | 黑人一区 | 欧美国产一区二区三区 | 国产精品久久久久久模特 | 久久99精品久久久久久园产越南 | 欧美综合日韩 | 成人午夜在线免费视频 | 制服丝袜成人动漫 | 欧美一级淫片免费视频黄 | 蜜桃视频在线免费播放 | 欧美大荫蒂xxx | 国产精品免费看 | 黄色片网页 | 香蕉黄色网 | 国产精品成人av片免费看最爱 | 精品国产一区二区在线观看 | 视频一区二区三区在线播放 | 亚洲最大av网站 | 四季久久免费一区二区三区四区 | www.99av| 成人福利免费在线观看 | 天天看天天摸天天操 | 激情综合婷婷久久 | h网站在线观看 | 国产网站黄 | 性生活视频一级 | 日韩色视频在线观看 | 欧美精品激情在线 | 欧美日韩一区,二区,三区,久久精品 | 亚洲精品永久视频 | 日本高清黄色片 | 国产日韩在线 | 国产在线免费 | 精品国产久 | 欧美一区二区黄 | 国产成人aⅴ| 久久久久久久亚洲精品 | 亚洲一区在线国产 |