1、main函數數組
C程序老是從main函數開始執行。main函數的原型是:編輯器
int main(int argv, char *argv[]);
當內核執行C程序時,在調用main前先調用一個特殊的啓動例程。可執行程序文件將此啓動例程指定爲程序的起始地址---這是由鏈接編輯器設置的,而鏈接編輯器則由C編譯器調用。函數
2、進程終止佈局
一共有8種方式進程終止,其中五種爲正常終止,他們是:一、從main返回;二、調用exit;三、調用_exit或_Exit;四、最後一個線程從其啓動例程返回;五、從最後一個線程調用pthread_exit。異常終止有三種:一、調用abort;二、接到一個信號;三、最後一個線程對取消請求作出響應。優化
一、退出函數spa
3個函數用於正常終止一個程序,_exit和_Exit當即進入內核,exit則限制性一些清理處理,而後返回內核。線程
#include <stdlib.h> void exit(int status); void _Exit(int status); #include <unistd.h>] void _exit(int status);
因爲歷史緣由,exit函數老是執行一個標準I/O庫的清理關閉操做:對於全部打開流調用fclose函數,這形成輸出緩衝中的全部數據都被沖洗。3個退出函數都帶一個整型參數,稱之爲終止狀態。若是main沒有聲明返回類型爲整型,則該進程的終止狀態是未定義的。可是若main的返回類型是整型,而且main執行到最後一條語句時返回,那麼該進程的終止狀態是0。unix
2.函數atexit指針
按照ISO C的規定,一個進程能夠登記多至32個函數,這些函數將有exit自動調用。咱們稱這些函數爲終止 處理程序(exit handler),並調用atexit函數來登記這些函數。code
#include <stdlb.h> int atexit(void (*func)(void));
atexit的參數是一個函數地址,當調用次函數時無需向他傳遞任何參數,也不指望它返回一個值。exit調用這些函數的順序與他們登記時候的順序相反。同一函數如登記屢次,也會被調用屢次。exit首先調用個終止處理程序,然會關閉全部打開流。注意,內核是程序執行的惟一方法是調用一個exec函數,進程自願終止的惟一方法是顯式或隱式調用_exit或_Exit。進程也可非自願地由一個信號使其終止。
7-2 一個C程序如何啓動和終止的
3、環境表
每一個程序都接收到一張環境表。與參數表同樣,環境表也是一個字符指針數組,其中每一個指針包含一個以null結束的C字符串的地址。全局變量environ則包含了該指針數組的地址: extern char **environ。
4、C程序的存儲空間佈局
歷史沿襲至今,C程序一直由下列及部分組成:
一、正文段:CPU執行的極其指令部分。一般正文段是可共享的,因此即便是頻繁執行的程序在存儲器中也只需有一個副本,另外正文段經常是隻讀的,以防止程序因爲意外而修改其指令。
二、初始化數據段:程序中需明確地賦初值的變量,一般稱之爲數據段。
三、未初始化數據段:一般將此段稱爲bbs段(block started by symbol),在程序開始執行以前,內核將此段中的數據初始化爲0或空指針。未初始化的全局變量就放在這個段。
四、棧:自動變以及每次函數調用時所需保存的信息都存放在此段中。每次函數調用時,其返回地址以及調用者的環境信息都存放在棧中。而後,最近被調用的函數在棧上爲其自動和臨時變量分配存儲空間。經過以這種方式使用棧,C遞歸函數能夠工做。遞歸函數每次調用時自身時,就用一個新的幀棧,所以一次函數嗲用實力中的變量集不會影響另外一次函數調用實例中的變量。
五、堆、一般在堆中進行動態存儲分配,因爲歷史緣由,堆位於未初始化數據段和棧之間
7-6 典型的存儲空間安排
未初始化數據段的內容並不存放在磁盤程序文件中。其緣由是內核在程序開始運行前將他們都設置爲0,須要放在磁盤程序文件中的段只有正文段和初始化數據段。
5、共享庫
共享庫是的可執行文件中再也不須要包含公用的庫函數,而只須要在全部進程均可引用的存儲區中保存這種庫例程的一個副本,程序第一次執行或第一次調用某個庫函數時,用動態鏈接方法與共享庫函數相鏈接,者減小了每一個可執行文件的長度,但增長了一些運行時間。共享庫還有一個優勢,就是能夠用新版本庫函數替換老版本庫函數,而無需對程序進行從新編譯。
6、存儲空間分配
ISOC說明了3個用於儲存空間動態分配的函數:
#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 *tpr);
malloc、分配指定字節數的存儲區。此存儲區中的初始值不肯定;calloc,爲指定數量長度的對象分配存儲空間。該空間中的每一位都初始化爲0;realloc,增長或減小之前分配區的長度,當增長長度時,可能需將之前分配區的內容移到另外一個足夠大的區域,一邊在尾端提供增長的存儲去,而新增區域內的初始值不肯定。這3個函數所返回的指針必定是適當對齊的,使其可用於任何數據對象。
這些分配例程一般用sbrk系統調用實現。該系統調用擴充進程的堆。大多數實現分配的存儲空間比所要求的要稍大一些,額外的空間用來記錄慣例信息----分配快的長度、指向下一個分配快的指針等。這就意味着,若是超過一個已分配的尾端或者在已分配區起起始位置以前進行寫操做,則會該寫另外一塊的管理信息。
7、環境變量
環境字符串的形式是: name=value,unix內核並不查看這些字符串,他們的解釋徹底取決於各個應用程序。ISO C定義了一個函數getenv,能夠用其取環境變量值,可是該標準又稱環境的內容是由實現定義的。ISO C沒有定義任何環境變量
#include <stdlib.h> char *getchar(const char *name);
putenv取形式爲name=value的字符串,將其防盜環境表中,若是name已經存在,則先刪除其原來的定義;setenv將name設置爲value。若是name已經存在,若是rewrite非0,則首先刪除其現有的定義,不然不刪除其現有定義。unsetenv刪除name的定義。即便不存在這種定義也不算出錯。
#include <stdlib.h> int putenv(char *str); int setenv(const char *name, const char *value, int rewrite); int unsetenve(const char *name);
環境表和環境字符串一般存放在進程存儲空間的頂部(棧之上)。刪除一個字符串很簡單---只要先在環境表中找到該指針,而後將全部後續指針都向環境表順不順序移動一個位置就能夠了。若是修改一個現有的name:若是新value的長度少於或等於現有value的長度,則只要將新字符串複製到元字符串所用的空間中便可;若是新value的長度大於元長度,則必須調用malloc爲新字符串分配空間,而後將新字符串複製到空間中,接着使黃競標中針對name的指針指向新分配區。
8、函數setjmp和longjmp
在C中,goto語句是不能跨越函數的,而執行這種類型跳轉功能的函數是setjmp和longjmp。這兩個函數對於處理髮生很深層嵌套函數調用中的出錯狀況是很是有用的。
#include "apue.h" #define TOK_ADD 5 void do_line(char *); void cmd_add(void); int get_token(void); int main(void) { char line[MAXLINE]; while(fgets(line, MAXLINE, stdin) != NULL) { do_line(line); } exit(0); } char *tok_ptr; void do_line(char * ptr) { int cmd; tok_ptr = ptr; while((cmd = get_token()) > 0) { switch(cmd) { case TOK_ADD: cmdd_add(); break; } } } void cmd_add(void) { int token; token = get_token(); } int get_token(void) { }
7-9 進行命令處理程序的典型骨架部分
自動變量的存儲單元在每一個函數的幀棧中。數組line在main的幀棧中,整型cmd在do_line的幀棧中,整型token在cmd_add的幀棧中。多層次嵌套調用返回錯誤可使用非局部goto---setjmp和longjmp函數。非局部指的是,這不是由普通的C語言goto語句在一個函數內實施的跳轉,二是在棧上跳過若干調用幀,返回到當前函數調用路徑上的某一個函數中。
#include <setjmp.h> int setjmp(jmp_buf env); void longjmp(jmp_buf env, int val);
#include "apue.h" #include <setjmp.h> #define TOK_ADD 5 jmp_buf jmpbuffer; int main(void) { char line[MAXLINE]; if(setjmp(jmpbuffer) != 0) { printf("error"); } while(fgets(line, MAXLINE, stdin) != NULL) { do_line(line); } exit(0); } ... void cmd_add(void) { int token; token = get_token(); if(token < 0) { longjmp(jmpbuffer, 1); } }
7-11 setjmp和longjmp實例
#include "apue.h" #incldue <setjmp.h> static void f1(int, int, int, int); static void f2(void); static jmp_buf jmpbuffer; static int globval; int main(void) { int autoval; register int regival; volatile int volaval; static int statval; globval = 1; autoval = 2; regival = 3; volaval = 4; statval = 5; if(setjmp(jmpbuffer) != 0) { printf("after longjmp:\n"); printf("globval = %d, autoval = %d, regival = %d, volaval = %d, statval = %d\n", globval, autoval, regival, volaval, statval); exit(0); } globval = 95; autoval = 96; regival = 97; volaval = 98; statval = 99; f1(globval, autoval, regival, volaval, statval); exit(0); } static void f1(int i, int j, int k, int l) { printf("globval = %d, autoval = %d, regival = %d, volaval = %d, statval = %d\n", globval, i, j, k, l); f2(); } static void f2(void) { longjmp(jmpbuffer, 1); }
7-13 longjmp對各種變量的影響
全局變量、靜態變量和易失變量不受優化的影響,在longjmp以後,他們的值是最近所呈現的值。經過這一實例咱們能夠理解到,若是要編寫一個使用非局部跳轉的可移植程序,則必須使用volatile屬性。
9、函數getrlimit和setrlimit
每一個進程都有一組資源限制,其中一些能夠用getrlimit和setrlimit函數查詢和更改。
#include <sys/resource.h> int getrlimit(int resource, struct rlimit *rlptr); int setrlimit(int resource, const struc rlimit *rlptr);
在更改資源限制時,遵循如下條件:一、任何一個進程可將一個軟限制值更改成小於或等於其硬件限制值;二、任何一個進程均可下降其硬限制值,但必須大於或等於其軟限制值;三、只有超級用戶進程能夠提升硬限制值;四、資源限制影響到調用進程並由子進程繼承。