7.1 引言shell
本章將學習:當執行程序時,其main函數是如何被調用的;命令行參數是如何傳送給執行程序的;典型的存儲器佈局是什麼樣式;如何分配另外的存儲空間;進程如何使用環境變量;各類不一樣的進程終止方式等;另外還將說明longjmp和setjmp函數以及它們與棧的交互做用;還將介紹研究進程的資源限制數組
7.2 main函數編輯器
C程序老是從main函數開始執行。當內核執行C程序時,在調用main前先調用一個特殊的啓動例程。可執行程序文件將此啓動例程指定爲程序的起始地址——這是由鏈接編輯器設置的,而鏈接編輯器則由C編譯器調用。啓動例程從內核取得命令行參數和環境變量值,而後爲按上述方式調用main函數作好安排函數
7.3 進程終止佈局
有8種方式使進程終止,其中5中爲正常終止,它們是:學習
(1)從main返回fetch
(2)調用exitthis
(3)調用_exit或_Exitspa
(4)最後一個線程從其啓動例程返回命令行
(5)最後一個線程調用pthread_exit
異常終止有3中方式,它們是:
(6)調用abort
(7)接到一個信號並終止
(8)最後一個線程對取消請求作出響應
上一節的啓動例程是這樣編寫的,使得從main返回後當即調用exit函數:exit(main(argc,argv));
1.exit函數
有三個函數用於正常終止一個程序:_exit和_Exit當即進入內核,exit則先執行一些清理處理(包括調用執行各終止處理程序,關閉全部標準I/O流等),而後進入內核
#include<stdlib.h> //ISO void exit(int status); void _Exit(int status); #include<unistd.h> //POSIX.1 void _exit(int status);
因爲歷史緣由,exit函數老是執行一個標準I/O庫的清理關閉操做:爲全部打開流調用fclose函數。這回形成全部緩衝的輸出數據都被沖洗(寫到文件上)
三個函數都帶一個整型參數,稱之爲終止狀態。大多數UNIX shell都提供檢查進程終止狀態的方法。若是(a)若調用這些函數時不帶終止狀態,或(b)main執行了一個無返回值的return語句,或(c)main沒有聲明返回類型爲整型,則該進程的終止狀態是未定的。
2.atexit函數
按照ISO C的規定,一個進程能夠登記多達32個函數,這些函數將由exit自動調用。咱們稱這些函數爲終止處理程序,並調用atexit函數來登記這些函數
#include<stdlib.h> int atexit(void (*func)(void)); //返回值:若成功則返回0,若出錯則返回非0值
其中,atexit的參數是一個函數地址,當調用此函數時無需向它傳送任何參數,也不指望它返回一個值。exit調用這些函數的順序與它們登記時候的順序相反。同一函數如若登記屢次,則也會被調用屢次。
內核使程序執行的惟一方法是調用一個exec函數
實例:7_2 終止處理程序實例
1 #include"apue.h" 2 3 static void my_exit1(void); 4 static void my_exit2(void); 5 6 int main() 7 { 8 if(atexit(my_exit2)!=0) 9 err_sys("can't register my_exit2"); 10 if(atexit(my_exit1)!=0) 11 err_sys("can't register my_exit1"); 12 if(atexit(my_exit1)!=0) 13 err_sys("can't register my_exit1"); 14 15 printf("main is done\n"); 16 return(0); 17 } 18 static void my_exit1(void) 19 { 20 printf("first exit handler\n"); 21 } 22 static void my_exit2(void) 23 { 24 printf("second exit handler\n"); 25 }
執行後能夠看出:終止處理程序每登記一次,就會被調用一次,且exit調用這些函數的順序與它們登記時候的順序相反。
7.4 命令行參數
當執行一個程序時,調用exec的進程可將命令行參數傳遞給該新進程。
實例:7_3 將全部命令行參數回送到標準輸出
#include"apue.h" int main(int argc,char *argv[]) { int i; for(i=0;i<argv;i++) printf("argv[%d]:%s\n",i,argv[i]); exit(0); }
7.5 環境表
每一個程序都會接收到一張環境表。與參數表同樣,環境表也是一個字符指針數組,其中每一個指針包含一個以null結束的C字符串的地址。全局變量environ則包含了該指針數組的地址:extern char **environ;
7.6 C程序的存儲空間佈局
從歷史上講,C程序一直由下面幾部分組成:
-正文段。這是由cpu執行的機器指令部分。一般,正文段是可共享的。因此即便頻繁執行的程序(如文本編輯器,C編譯器和shell等)在存儲器中也只需有一個副本,另外,正文段經常是隻讀的,以防止程序因爲意外而修改其自身指令
-初始化數據段。一般將此段稱爲數據段,它包含了程序中需明確地賦初值的變量。例如,C程序中出如今任何函數以外的聲明:int maxcount=99,使此變量帶有其初值存放在初始化數據段中
-非初始化數據段。一般將此段稱爲bss段,這一名稱來源於一個早期的彙編運算符,意思是「block started by symblo」(由符號開始的塊),在程序開始執行以前,內核將此段中的數據初始化爲0或空指針。出如今任何函數外的C聲明:long sum[1000];使此變量存放在非初始化數據段中
-棧。自動變量以及每次函數調用時所需保存的信息都存放在此段中。每次調用函數時,其返回地址以及調用者的環境信息(例如某些機器寄存器的值)都存放在棧中。而後,最近被調用的函數在棧上爲其自動和臨時變量分配存儲空間。經過以這種方式使用棧,能夠遞歸調用C函數。遞歸函數每次調用自身時,就使用一個新的棧幀,所以一個函數調用實例中的變量集不會影響另外一個函數調用實例中的變量
-堆。一般在隊中進行動態存儲分配。因爲歷史造成的慣例,堆位於非初始化數據段和棧之間
size(1)命令報告正文段、數據段和bss段的長度
7.7 共享庫
共享庫使得可執行文件不在須要包含公用的庫例程,而只需在全部進程均可引用的存儲區中維護這種庫例程的一個副本。程序第一次執行或者第一次調用某個庫函數時,用動態連接方法將程序與共享庫函數相連接。這減小了每一個可執行文件的長度,但增長了一些運行時間的開銷。這種時間開銷發生在該程序第一次被執行時或者每一個共享庫函數第一次被調用時。共享庫的另外一個有點是能夠用庫函數的新版本代替老版本,而無需對使用該庫的程序從新連接編輯
7.8 存儲器分配
ISO C說明了三個用於存儲空間動態分配的函數
(1)malloc :分配指定字節數的存儲區。此存儲區中的初始值不肯定。
(2)calloc :爲指定數量具指定長度的對象分配存儲空間。該控件中的每一位都初始化爲0
(3)realloc:更改之前分配區的長度(增長或減小)。當增長長度時,可能需將之前分配區的內容移到另外一個足夠大的區域,以便在尾端提供增長的存儲區,而新增區域內的初始值 則不肯定
#include<stdlib.h> void *malloc(size_t size); void *calloc(size_t nobj,size_t size); void *realloc(void *ptr,size_t newsize); //三個函數返回值:若成功則返回非空指針,若出錯則返回NULL void free(void *ptr);
這三個分配函數多返回的指針必定是適當對齊的,使其可用於任何數據對象。
函數free釋放ptr指向的存儲空降。被釋放的空間一般被送入可用存儲區池,之後,可在調用上述三個分配函數再分配。
7.9 環境變量
ISO C定義了一個函數getenv,能夠用其取環境變量值,可是該標準又稱環境的內容是由實現定義的。
#include<stdlib.h> char *getenv(const char *name); //返回值:指向與name關聯的value的指針,若未找到則返回NULL
注意,此函數返回一個指針,指向name=value字符串中的value。咱們應當使用getenv從環境中取一個指定環境變量的值,而不是直接訪問environ
#include<stdlib,h> int putenv(char *str); int setenv(const char *name,const char *value,int rewrite); int unsetenv(const char *name); //三個函數返回值:若成功則返回0,若出錯則返回非0值
這三個函數的操做是:
-putenv取形式爲name=value的字符串,將其放到環境表中。若是name已經存在,則先刪除其原理的定義。
-setenv將name設置爲value。若是在環境中name已經存在,那麼(a)若rewrite非0,則首先刪除其現有定義;(b)若rewrite爲0,則不刪除其現有定義
-unsetenv刪除name的定義。即便不存在這種定義也不出錯
若是修改一個現有的name:
a.若是新value的長度少於或等於現有value的長度,則只要在原字符串所用空間中寫入新字符串
b.若是新value的長度大於原長度,則必須調用malloc爲新字符串分配空間,而後將新字符串複製到該空間中,接着使環境表中針對name的指針指向新分配區
若是要增長一個新的name:
a.若是這是第一次增長一個新name,則必須調用malloc爲新的指針表分配空間。接着,將原來的環境表複製到新分配區,並將指向新name=value字符串的指針存放在該指針表的表尾,而後又將一個空指針存放在其後。最後使environ指向新指針表。
b.若是這不是第一次增長一個新name,則可知之前調用malloc在堆中爲環境表分配了空間,因此只要調用realloc,以分配比原空間多存放一個指針的空間。而後將指向新name=value字符串的指針存放在該表表尾,後面跟着一個空指針
7.10 setjmp和longjmp函數
在C中,goto語句是不能跨越函數的,而執行這類跳轉功能的是函數setjmp和longjmp。這兩個函數對於處理髮生在深層嵌套函數調用那個中的出錯狀況是很是有用的
實例:7_4 進行命令處理的典型程序骨架
1 #include"apue.h" 2 3 #define TOK_ADD 5 4 5 void do_line(char *); 6 void cmd_add(void); 7 int get_token(void); 8 9 int main() 10 { 11 char line[MAXLINE]; 12 while(fgets(line,MAXLINE,stdin)!=null) 13 do_line(line); 14 exit(0); 15 } 16 char *tok_ptr; //global pointer for get_token() 17 18 void do_line(char *ptr) //process one line of input 19 { 20 int cmd; 21 tok_ptr=ptr; 22 while((cmd=get_token())>0){ 23 switch(cmd){//one case for each command 24 case TOK_ADD: 25 cmd_add(); 26 break; 27 } 28 } 29 } 30 void cmd_add(void) 31 { 32 int token; 33 token=get_token(); 34 //rest of processing for this command 35 } 36 int get_token(void) 37 { 38 //fetch next token from line pointed to by tok_ptr 39 }
其主循環是從標準輸入讀一行,而後調用do_line處理該輸入行。do_line函數調用get_token從該輸入行中取下一個標記。一行中的第一個標記假定是一條某種形式的命令,因而switch語句就實現命令選擇。
#include<setjmp.h> int setjmp(jmp_buf env); //返回值:若直接調用則返回0,若從longjmp調用則返回非0 void longjmp(jmp_buf env,int val);
在但願返回到的位置調用setjmp.setjmp參數env的類型是一個特殊類型jmp_buf。這一數據類型是某種形式的數組,其中存放在調用longjmp時能用來恢復棧狀態的全部信息。由於須要另外一個函數中引用env變量,因此規範的處理方式是將env變量定義爲全局變量
實例:7_5 setjmp和longjmp實例
http://blog.163.com/muren20062094@yeah/blog/static/1618444162011529103634600/
可直接參考上述連接,總的來講,就是setjmp設置跳轉的位置,longjmp跳到那個位置,但跳轉只是回來原來的位置,並不實現回滾自動變量和寄存器的值(在上面代碼中加上一個靜態全局變量,不斷自增輸出,便可看出)
7.11 getrlimit 和setrlimit函數
每一個進程都有一組資源限制,其中一些能夠用getrlimit和setrlimit函數查詢和更改。
#include<sys/resource.h> int getrlimit(int resource,struct rlimit *rlptr); int setrlimit(int resource,const struct rlimit *rlptr);
對這兩個函數的每一次調用都會指定一個資源以及一個指向下列結構的指針
struct rlimit{ rlim_t rlim_cur;//soft limit:current limit rlim_t rlim_max;//hard limit:maximum value for rlim_cur;
在更改資源限制時,須遵循下列三條規則:
(1)任何一個進程均可將一個軟限制更改成小於或等於其硬限制值
(2)任何一個進程均可下降其硬限制值,但它必須大於或等於其軟限制值。這種下降對普通用戶而言是不可逆的
(3)只有超級用戶進程能夠提升硬限制值
常量RLIM_INFINITY指定了一個無限量的限制
實例:7_8 打印當前資源限制
1 #include"apue.h" 2 #if defined(BSD)||defined(MACOS) 3 #include<sys/time.h> 4 #define FMT "%10lld " 5 #else 6 #define FMT "%10ld " 7 #endif 8 #include<sys/resource.h> 9 10 #define doit(name) pr_limits(#name,name) 11 static void pr_limits(char *,int); 12 int main(void) 13 { 14 #ifdef RLIMIT_AS 15 doit(RLIMIT_AS); 16 #endif 17 doit(RLIMIT_CORE); 18 doit(RLIMIT_CPU); 19 doit(RLIMIT_DATA); 20 doit(RLIMIT_FSIZE); 21 #ifdef RLIMIT_LOCK 22 doit(RLIMIT_LOCKS); 23 #endif 24 #ifdef RLIMIT_MEMLOCK 25 doit(RLIMIT_MEMLOCK); 26 #endif 27 doit(RLIMIT_NOFILE); 28 #ifdef RLIMIT_NPROC 29 doit(RLIMIT_NPROC); 30 #endif 31 #ifdef RLIMIT_RSS 32 doit(RLIMIT_RSS); 33 #endif 34 #ifdef RLIMIT_SBSIZE 35 doit(RLIMIT_SBSIZE); 36 #endif 37 doit(RLIMIT_STACK); 38 #ifdef RLIMIT_VMEM 39 doit(RLIMIT_VMEM); 40 #endif 41 exit(0); 42 } 43 static void pr_limits(char *name,int resource) 44 { 45 struct rlimit limit; 46 if(getrlimit(resource,&limit)<0) 47 err_sys("getrlimit error for %s",name); 48 printf("%-14s ", name); 49 if(limit.rlim_cur==RLIM_INFINITY) 50 printf("(infinite) "); 51 else 52 printf(FMT,limit.rlim_cur); 53 if(limit.rlim_max==RLIM_INFINITY) 54 printf("(infinite)"); 55 else 56 printf(FMT,limit.rlim_max); 57 putchar((int)'\n'); 58 }