天天看點

Linux程序環境

程序是一段具有獨立功能的程式在特定資料集合上一次動态執行的過程。它是系統進行資源配置設定和管理的獨立機關,也是系統排程和執行的最小機關。

程序的特性有: 并發性、動态性、獨立性、互動性

程序的種類有: 互動式程序、批處理程序、守護程序

本節将簡要介紹程序的環境,主要包括程序的屬性,環境變量,位址空間,以及程序環境相關的操作。

1.1 程序的基本屬性

Linux系統中程序的屬性包含在一個叫做程序控制塊(PCB)結構體中。核心通過一個雙向連結清單維護系統中所有的程序資訊,每個連結清單節點就是一個PCB 結構體,其定義為 task_struct:

struct task_struct{
   volatile long state;
   pid_t pid;  
   pid_t tgid;
   ……………
};
           

PCB是由核心維護的資料結構,其中每個成員都代表着程序的某種屬性。下面我們将對PCB中部分成員簡單介紹。

1.1.1 程序辨別符

程序辨別符在核心中由PID辨別,PID是一個非負整數,在系統中是唯一的,但又是可複用的,程序消亡後該ID 就可以被賦予其它程序。其在PCB中的表示如下:

pid_t pid;
pid_t tgid;
           

Glibc中定義了一些api 可以用來擷取各種pid:

#include<unistd.h>

pid_t getpid(void)        //傳回調用程序的PID

pid_t getppid(void)       //傳回調用程序的父程序PID

pid_t getuid(void)        //傳回調用程序的實際使用者ID

pid_t geteupid(void)      //傳回調用程序的有效使用者ID

pid_t getgid(void)        //傳回調用程序的實際組ID

pid_t getegid(void)       //傳回調用程序的有效組ID
           

1.1.2 程序狀态

程序從被建立到消亡,在核心中由一個狀态機表示其各種狀态,程序狀态的在PCB 中的表示為:

volatile long state;

int exit_state;
           

程序的狀态:

#define TASK_RUNNING              0

#define TASK_INTERRUPTIBLE        1

#define TASK_UNINTERRUPTIBLE      2

#define TASK_ZOMBIE               4

#define TASK_STOPPED              8
           
  • TASK_RUNNING:正在被執行,或者已經獲得資源等待被執行;
  • TASK_INTERRUPTIBLE:表示程序被阻塞,等到條件為真,程序狀态轉換為TASK_RUNNING。這個條件可能是擷取到某些資源(wake_up)或者收到信号;
  • TASK_UNINTERRUPTIBLE:與TASK_INTERRUPTIBLE類似,唯一的差別是會忽略信号;
  • TASK_ZOMBIE:程序已經被終止,但是父程序還沒有使用wait()等系統調用來獲知它的終止資訊,PCB 沒有被釋放;
  • TASK_STOPPED:程序接收到一個SIGSTOP信号後就将運作狀态改成TASK_STOPPED狀态,然後在接收到SIGCONT信号時又恢複繼續運作,多用于調試。
Linux程式環境

此外,PCB中還有很多資訊,用來表示信号控制的,檔案系統的等等,後面的章節中我們将逐一介紹。

1.2 程序環境

我們編寫的程式都是從main函數開始執行的,那main函數又是如何被執行到的呢?其大概的過程是,連結器會将一個啟動例程設定為起始位址,核心在執行C程式時,會從這個啟動例程開始;啟動例程從核心取得指令行參數與環境變量等值傳遞給main 函數可以執行。本節我們就來介紹下程序的環境表。

每個程序都維護一個獨立的環境變量表,環境表是一個字元指針數組,其中每個指針項都是一個環境變量,它是一個以null 結束的字元串。環境表也是以一個NULL字元串結束。字元串的格式為:NAME=value。環境表格式如下圖所示:

Linux程式環境
  • 程序初始的環境表繼承自父程序(可以通過參數傳遞或者全局變量兩種方式從父程序擷取);
  • 每個程序都有一個獨立的環境表,設定的環境變量隻影響目前程序的,并不會影響到其它程序的環境變量表;
  • 每個程序有獨自的資料段,是以對應的environ 指針指向的是各自的環境表。

程序中對環境變量的操作一般是通過一組函數去通路而不是直接通路environ 指針,雖然實際上是可以這麼做的。環境變量相關的操作函數如下:

頭檔案:stdlib.h

(1). 擷取指定環境變量的值:

char *getenv(const char *name);
參數:需要擷取的環境變量name
傳回值:與name 關聯的value指針,不存在 傳回NULL

(2). 設定環境變量值:

int *setenv(const char *name,  const char *value,  int rewite);
參數:如rewite = 0,替代原有的value值;否則不替換,也不報錯
傳回值:0:success  非0:unsuccess
int *putenv(const char *str);
參數:str格式為name=value, 若name已經存在,替換原來的定義
傳回值:0:success  -1:unsuccess

(3). 删除環境變量值:

char *unsetenv(const char *name);
參數:str格式為name=value, 若name已經存在,替換原來的定義
傳回值:0:success  -1:unsuccess

環境表位于程序存儲空間的頂部(不是一定的),類似于連結清單的形式存儲。那修改環境表是如何實作的呢?當然也是類似于對連結清單的處理。

  • 删除一個表現很簡單,與删除一個連結清單中節點的方式相同。
  • 修改一個表現也很簡單,如果新的value 小于原先的value直接替換即可。反之,須用malloc 配置設定新的空間,針對這一表現的指針指向新配置設定區。
  • 增加一個表現那就複雜多了:

a. 如果是第一次新增,需要重新配置設定記憶體空間,将新的表現置于表尾,并以NULL 結尾,再使environ 指向新的位址。如果不幸的環境表位于頂部,那空間沒有辦法再增加啦,将它移到heap中吧。

b. 如果不是第一次新增,直接調用realloc 增加空間,并将該表項放至表尾,并以NULL結束。

Linux中可以用export 指令去擷取,設定環境變量。

1.3 程序位址空間

Linux中每個程序都有自己的記憶體空間,當然這個記憶體空間隻是虛拟位址空間,記憶體管理子產品會通過頁表的方式将虛拟空間映射到實際的實體位址。32bit的系統中這個虛拟位址空間大小為4G,其中kernel space和 user space的比例為1:3 。 kernel space 的1G 又可以被了解為所有程序共享的。其具體的組織形式如下圖:

Linux程式環境

關于程序位址空間簡單的說明如下:

  • 代碼段:CPU 執行的機器指令部分,隻讀,程序間可共享。這裡的共享并不是說所有的程序擁有一份某段程式。而主要是展現在連結庫中,程式在運作後再确定代碼所在的記憶體位址,這樣就能實作多個程式調用同一段代碼;
  • BSS段:程式加載時BSS會被作業系統清零,是以未賦初值或初值為0的全局變量都在BSS中。BSS段僅為未初始化的靜态配置設定變量預留位置,并不配置設定實際的記憶體空間,這樣可減少目标檔案體積。當加載器(loader)加載程式時,将為BSS段配置設定記憶體并初始化為0。在嵌入式軟體中,進入main()函數之前BSS段被C運作時系統映射到初始化為全零的記憶體。BSS段包括未賦初值或者初值為0的全局變量或靜态局部變量。是以可以将BSS 段了解為隻是儲存資訊;
  • 資料段:初值不為0的全局變量或靜态局部變量。比如定義一個int a =10 的全部變量,會在目标檔案資料段中定義這個變量,并在程式load的時候複制到相應記憶體;
  • 堆棧:堆由使用者程式控制,例如 malloc 配置設定的記憶體位址。棧由編譯器決定,主要存放非靜态局部變量和函數調用過程的資訊(棧幀),如的參數、傳回位址和寄存器的值。Linux中ulimit -s指令可檢視和設定堆棧最大值。

Linux 中可以用size 指令檢視正文段、資料段的長度。

1.4. 程序環境相關的操作函數

(1). 配置設定存儲空間

頭檔案: #include<stdlib.h>  配置設定失敗傳回NULL

void *malloc(size_t size);
配置設定指定大小的存儲區,初值未定
void *calloc(size_t nobj, size_t size);
配置設定指定資料(nobj),指定大小的存儲空間,并初始化為0
void *realloc(void *ptr, size_t newsize);
增加或減少以前配置設定的存儲區至newsize 大小,如果是增加存儲區,可能需要将以前配置設定區的内容移動到新的存儲區,新增的内容初值未定
void free(void *ptr);
釋放已配置設定的存儲區,注意以前配置設定的存儲區對應位址不能有變

(2). 跳轉功能函數

 下面介紹一組可以實作跨函數跳轉的功能函數,這恰好可以彌補goto 語句的不足。

 頭檔案: #include<setjmp.h>  ()注意哦,不是setjump.h)

int setjmp(jump_buf env);
第一次調用傳回0,如從longjump傳回,則為val
void longjmp(jmp_buf env,  int val);
釋放已配置設定的存儲區,注意以前配置設定的存儲區對應位址不能有變

這兩個函數不同于goto函數,可以實作跨函數的跳轉。使用也很簡單,在需要跳轉的點調用setjump,此時會将棧幀資訊存放到env中,并傳回0。在發生需要調回的點調用longjump,此時程式會根據env 中的資訊跳轉回setjump的點,而此時又會執行一遍setjump,并傳回val。

一個簡單的例子:

1.	#include<stdio.h>  
2.	#include<stdlib.h>  
3.	#include<string.h>  
4.	#include<setjmp.h>  
5.	  
6.	static jmp_buf env;  
7.	static int globval;  
8.	  
9.	static void f1(int globval, int autoval, int regival,   
10.	               int volval, int staval);  
11.	  
12.	int main(int argc, char *argv[])  
13.	{  
14.	    int autoval;  
15.	    register int regival;  
16.	    volatile int volval;  
17.	    static int staval;  
18.	  
19.	    globval = 1;  
20.	    autoval = 2;  
21.	    regival = 3;  
22.	    volval  = 4;  
23.	    staval  = 5;  
24.	  
25.	    if (setjmp(env) != 0)  
26.	    {  
27.	        printf("After longjump\n");  
28.	        printf("value: %d %d %d %d %d\n", globval,   
29.	                autoval, regival, volval , staval);  
30.	        getchar();  
31.	    }  
32.	  
33.	    globval = 11;  
34.	    autoval = 12;  
35.	    regival = 13;  
36.	    volval  = 14;  
37.	    staval  = 15;  
38.	  
39.	    f1(globval, autoval, regival, volval, staval);  
40.	    printf("Can program come to here ???\n");  
41.	    exit(0);  
42.	}  
43.	static void f1(int globval, int autoval,   
44.	               int regival, int volval, int staval)  
45.	{  
46.	    printf("Before longjump\n");  
47.	    printf("value: %d %d %d %d %d\n", globval, a  
48.	            utoval, regival, volval, staval);  
49.	    longjmp(env,1);  
50.	    printf("Can program come to f1 end ???\n");  
51.	}  
           

      執行結果如下:

Linux程式環境

(3).查詢和更改資源限制

Linux系統中有資源的概念,比如,CPU,memory,stack等都是系統的資源。而下面的這組函數可以用來擷取或設定相應的資源資訊。

#include<sys/resource.h>
    struct rlimit{
    rlimit _t rlim_cur;
    rlimit _t rlim_maxr;
};
           
int getrlimit(int resource, struct rlimit *rlptr);
擷取對應資源的值
int setrlimit(int resource, const struct rlimit *rlptr);
設定對應資源的值

Linux 中可以操作的主要資源如下:

RLIMIT_AS 程序可用存儲區的最大總長度,會影響sbrk函數和mmap函數
RLIMIT_CORE           core檔案的最大位元組數,若其值為0則阻止建立core檔案
RLIMIT_CPU            CPU時間的最大量值(秒),當超過此軟限制時,向該程序發送SIGXCPU信号
RLIMIT_DATA            資料段的最大位元組長度。這是初始化資料、非初始化資料以及堆的總和
RLIMIT_FSIZE           可以建立的檔案的最大位元組長度。當超過此軟限制時,則向該程序發送SIGXFSZ信号
RLIMIT_LOCKS 一個程序可持有的檔案鎖的最大數(此數也包括Linux特有的檔案租借數)
RLIMIT_MEMLOCK     一個程序使用mlock能夠鎖定在存儲器中的最大位元組長度
RLIMIT_NOFILE          每個程序能打開的最大檔案數。更改此限制将影響到sysconf函數在參數_SC_OPEN_MAX中的傳回值
RLIMIT_NPROC 每個實際使用者ID可擁有的最大子程序數。更改此限制将影響到sysconf函數在參數_SC_CHILD_MAX中傳回的值
RLIMIT_RSS                最大駐記憶體集的位元組長度(resident set size in bytes, RSS)。如果實體存儲器供不應求,則核心将從程序處取回超過RSS的部分
RLIMIT_SBSIZE          使用者在任一給定時刻可以占用的套接字緩沖區的最大長度(位元組)。(Linux 2.4.22不支援)
RLIMIT_STACK           棧的最大位元組長度

簡要說明:

  • 任何一個程序都可以将一個soft limit值更改為小于其hard limit值
  • 任何一個程序都可以将一個hard limit值更改為大于其soft limit 值
  • 隻有超級使用者才能增加hard limit值
  • Linux中可以使用shell内置的ulimit指令更改這些資源
  • 資源限制影響到調用程序并由其子程序繼承

    Linux中也可以使用ulimit指令去擷取和設定資源資訊,關于ulimit用法如下:

ulimit [options] [resource]
           

其中option選項有:

  • -a  顯示目前資源限制的設定;
  • -H  設定資源的硬性限制,也就是管理者所設下的限制
  • -S  設定資源的彈性限制

resource選項有:

  • -c <core檔案上限>  設定core檔案的最大值,機關為區塊
  • -d <資料節區大小>  程式資料節區的最大值,機關為KB
  • -f <檔案大小>  shell所能建立的最大檔案,機關為區塊
  • -m <記憶體大小>  指定可使用記憶體的上限,機關為KB
  • -n <檔案數目>  指定同一時間最多可開啟的檔案數
  • -p <緩沖區大小>  指定管道緩沖區的大小,機關512位元組
  • -s <堆疊大小>  指定堆疊的上限,機關為KB
  • -t <CPU時間>  指定CPU使用時間的上限,機關為秒
  • -u <程式數目>  使用者最多可開啟的程式數目
  • -v <虛拟記憶體大小>  指定可使用的虛拟記憶體上限,機關為KB

1.5. 程序和中斷上下文

1.  程序上下文

程序上文是指:程序由使用者态切到核心态時需要儲存使用者态時寄存器的值,程序狀态,堆棧上的内容,以便傳回使用者态時恢複;程序下文是指:進入核心态後執行的程式。

2. 中斷上下文

中斷上文是指:硬體通過中斷觸發信号,導緻核心調用中斷處理程式,進入核心空間。這個過程中,硬體的一些變量和參數也要傳遞給核心,核心通過這些參數進行中斷處理。中斷上文可以看作就是硬體傳遞過來的這些參數和核心需要儲存的一些其他環境;中斷下文是指:中斷服務程式。

本節内容轉載自https://blog.csdn.net/qq_38500662/article/details/80598486

寫在文末:本文作為個人對APUE的學習筆記,章節安排和内容基本參考APUE。文中有疏漏或者錯誤的地方,還請不吝賜教。

繼續閱讀