首頁 > 軟體

關於Linux進程環境

2020-06-16 17:08:35

進程的啟動和終止

核心執行c程式時,利用exec函數呼叫一個特殊的啟動例程,該啟動例程叢核心中獲取命令列引數和環境變數值。

進程終止的情況

5種正常終止的情況:

(1)從main函數返回;
(2)呼叫exit;
(3)呼叫_exit和_Exit函數;
(4)最後一個執行緒呼叫pthread_exit;
(5)最後一個執行緒從其啟動例程返回;  

3種異常終止情況

1)呼叫abort;
(2)接到一個信號;
(3)最後一個執行緒對取消請求做出響應;

進程啟動和終止圖

atexit函數

一個進程最多可以登記32和函數(例如:signal函數),這些函數由exit函數自動呼叫。在程式終止時呼叫這些函數,形成終止處理程式,來進行結束進程前的收尾工作。而exit函數通過atexit函數的登記記錄來判斷呼叫哪些函數。

exit函數

此函數由ISO C 定義,其操作包括處理終止處理程式,然後關閉所有標準I/O流。需要注意的是,它不會處理檔案描述符、多進程(父子進程)以及作業控制。

_e(E)xit函數
ISO C 定義這個函數的目的是為進程提供一種無需執行終止處理程式或信號處理常式的方法而終止程式。但ISO C 對標準I/O流是否進行沖洗,這取決於作業系統的實現。在unix中,是不進行沖洗的。

exit和_e(E)ixt函數的狀態碼

無論進程怎樣結束,它都會在核心上執行同一段程式碼(由進程啟動和退出圖可知)。這段程式碼來關閉所有的檔案描述符,釋放所有的儲存空間。

程式退出後,利用退出碼告知該進程的父進程。父進程通過wait或waitpid函數來完成該子進程的善後工作(獲取子進程相關資訊 釋放子進程佔用資源)。若父進程沒有處理子進程的退出狀態,則子進程變成僵死進程。相反的,若父進程在子進程前終止,則子進程變成孤兒進程。孤兒進程會由1號進程(init進程)接收,大致過程如下:

(1)進程終止時,核心逐個檢查所有活動的進程;
(2)分析查詢該終止進程的子進程;
(3)將該進程的子進程的父進程ID改為1;

wait和waitpid函數

程式正常或異常終止時,核心都會向父進程傳送SIGNAL信號。子進程終止是非同步事件,所以該信號也是非同步信號。而該信號一般會被父進程預設忽略。或者提供一個信號處理常式來善後。wait和waitpid函數就是其中的信號處理常式的一部分。

wait和waitpid函數區別如下:

1wait會阻塞呼叫者進程等待直至第一個終止的子進程到來;
(2waitpid可以通過引數設定,來實現呼叫者進程不阻塞,或選擇要阻
塞等待的子進程;

這裡的呼叫者指的是父進程

環境表和環境變數

環境表結構圖

  • 每個程式都接收到一張環境表
  • 環境表也是一個字元指標陣列
  • enrivon叫做環境指標
  • 指標陣列叫做環境表
  • 各個指標指向的字串叫做環境字串

環境變數

  • unix核心並不檢查環境字串,它們的解釋完全取決於各個應用進程
  • 通常在一個shell啟動檔案中設定環境變數來控制shell的動作
  • 修改或者增加環境變數時,只能影響當前進程以及其後(之前的不行)生成和呼叫的任何子進程的環境,但不能影響其父進程的環境

和環境變數相關的函數如下:

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

int putenv(char *str);
                       返回值:若成功,返回0;若出錯,返回非0
                       
int setenv(const char *name, const char *value,
            int rewrite);
int unsetenv(const char *name);
                兩個函數返回值:若成功,返回0;若出錯,返回-1 

這些函數如何修改環境表的

環境表和環境字串通常存放在記憶體空間的高地址處(頂部)。所以在修改它的值時,記憶體是不能繼續向高地址延伸;但又因為,它之下是各個棧幀,所以也不能向下延伸。如何修改它的值的過程如下:

(1)修改環境表

1)新value <= 舊value,直接覆蓋舊value的儲存空間
2)新value >= 舊value,呼叫malloc函數,在堆區開闢新的儲存空間,
將新value複製到這裡,再將這片儲存區首地址寫到環境表相應的位置處。

(2)新增環境表

1)新增一個環境變數,呼叫malloc函數開闢新的儲存空間,將原來的環
境表複製到該儲存區,其次再新增一個環境變數,然後在尾部賦值為NULL,
最後將environ指向該區域;
2)在 1)過程的基礎上,呼叫realloc函數,多次新增環境變數;

注意:以這種方式修改的環境變數只在當下程式執行時有效,當程式結束時,相應的儲存區被系統回收,這些修改就會失效。

記憶體儲存結構補充說明

記憶體管理結構圖

  • 未初始化資料段(block started by symbol):在程式開始執
    行之前,核心將此段中的資料初始化為0或空指標;
  • 棧:每次函數呼叫時,其返回地址以及呼叫者的環境資訊(如某些機器暫存器的值)都存放在棧中;
  • 共用庫:只需在所有進程都可參照的儲存區中儲存這種庫例程的一個副本;

儲存空間分配函數

#include<stdlib.h>
void *malloc(size_t size);
void *calloc(size_t nojy, size_t size);
void *realloc(void *ptr, size_t newsize);
         3個函數返回值:若成功,返回非空指標;若出錯,返回NULL
  • malloc函數:初始值不確定;底層通過呼叫sbrk函數實現;
  • calloc函數:初始值為0;
  • realloc函數:增加或減少以前分配區的長度;當增加長度時,可能將以前分配區的內容移到另一個足夠大的區域,以便在分配區末尾增加儲存區,而新增儲存區初始值不確定(例如:可變陣列的使用);

注意:這些動態分配的函數一般在分配儲存空間時,會比要求的大。因為在開闢空間的前後部分儲存記錄管理資訊。因此,在使用時,千萬不要越界存取,以免造成不可預知的後果。

函數間跳轉策略

在c語言中,goto語句是不能跨函數跳轉的。尤其是在函數深層呼叫時的跳轉需求,在出錯處理的情況下非常有用。

#include<setjmp.h>
int setjmp(jmp_buf env);
          返回值:若直接呼叫,返回0;若從longjmp返回,返回非0
void longjmp(jmp_buf env, int val);

變數值回滾問題:自動變數和暫存器變數會存在回滾現象。利用volatile屬性來避免此類情況的發生。(在給變數賦值時,賦的值回首先儲存在記憶體(記憶體變數)中,然後在由cpu取走,儲存在cpu的暫存器上(暫存器變數)。在做系統優化時,那些頻繁使用的變數,會直接儲存到暫存器中而不經過記憶體。)

暫存器變數會存在回滾現象的探究

在呼叫setjmp函數時,核心會把當前的棧頂指標儲存在env變數中,所以在呼叫longjmp函數返回該位置時,全域性變數、靜態變數、易失變數和自動變數如果在呼叫setjmp和longjmp函數之間它們的值被修改過,是不會回滾到setjmp函數呼叫之前的值(當然,編譯器將auto變數優化為暫存器變數除外)。因為,這些記憶體變數的值是儲存在記憶體相應的段中,回到原先棧頂狀態時,同樣存取的還是原先的記憶體空間。

然而,對於暫存器變數來說,首先要明確一點:暫存器變數是用動態儲存的方式。意思是暫存器變數的值可能存在不同的暫存器中。如果在調setjmp和longjmp函數之間它們的值被修改過,這個值可能不會存到setjmp之前的對其賦值的暫存器中,而在呼叫longjmp函數後,又回到了呼叫setjmp函數時的狀態。這個時候再讀取暫存器變數的值時,讀到的是原先那個暫存器中儲存的值而不是修改過的那個暫存器中儲存的值,所以出現的回滾現象。

本文永久更新連結地址http://www.linuxidc.com/Linux/2017-09/146739.htm


IT145.com E-mail:sddin#qq.com