一 main函數
當內核使用一個exec函數執行C程序時,在調用main函數之前先調用一個特殊的啟動例程,可執行程序將此例程指定為程序的起始地址。啟動例程從內核獲取命令行參數和環境變量,然后為調用main函數做好準備。
二 進程終止
進程終止的方式有8種,前5種為正常終止,后三種為異常終止:
1 從main函數返回;
2 調用exit函數;
3 調用_exit或_Exit;
4 最后一個線程從啟動例程返回;
5 最后一個線程調用pthread_exit;
6 調用abort函數;
7 接到一個信號并終止;
8 最后一個線程對取消請求做出響應。
(1)exit函數
#include <stdlib.h>
void exit( int status );
void _Exit( int status );
#include <unistd.h>
void _exit( int status );
這三個函數用于正常終止一個程序,_exit和_Exit立即進入內核,而exit則要先做一些清理工作(調用執行各終止處理程序,關閉所有標準I/O流),再進入內核。三個函數所帶的整型參數稱為終止狀態或退出狀態,如果(a)調用這些函數不帶參數,(b)main函數中的return語句無返回值,(c)main函數沒有聲明返回類型為整型,則進程的終止狀態是未定義的。main函數返回一個整型值與用該值調用exit是等價的。
(2)atexit函數
#include <stdlib.h>
int atexit( void (*fun)( void ) );
一個進程可以登記32個函數,這些函數由exit自動調用,這些函數被稱為終止處理函數,atexit函數可以登記這些函數。exit調用終止處理函數的順序和atexit登記的順序相反,如果一個函數被多次登記,也會被多次調用。
三 環境表
每個程序都會收到一張環境表,環境表是一個字符指針數組,每個指針指向一個以NULL結尾的環境字符串,環境指針environ是一個全局變量,指向指針數組的地址。通常用getenv和putenv函數來訪問特定的環境變量,而不是environ全局變量。如果要查看整個環境,則必須用environ全局變量。
四 C程序的存儲空間布局
1 正文段
CUP執行的機器指令部分,是共享和只讀的。
2 初始化數據段
又稱作數據段,包含了程序中明確需要賦初值的變量。
3 非初始化數據段
在程序開始執行前,內核將此段中的數據初始化為0或空指針。
4 棧
自動變量以及每次函數調用時所需保存的數據都存放在此段中。
5 堆
用于動態存儲分配。堆位于棧和非初始化數據段之間。
五 存儲器分配
#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 );
malloc函數分配指定字節數的存儲區,該存儲區中的初始值不確定;calloc函數為指定數量且指定長度的對象分配存儲空間,該空間中的每一位都初始化為0;realloc函數更改存儲區的長度(增加或減少),新增區域內的初始值不確定,如果ptr為空,realloc和malloc的功能相同。
以上函數的大多數實現所分配的存儲空間都比所要求的大一些,額外的空間用來存儲管理信息。如果在一個超過已分配區的尾端進行寫操作,就會重寫下一個分配區的管理記錄;同樣,在一個已分配區的起始位置之前寫入,會重寫本分配區的管理記錄。這種錯誤是災難性的,但因為不會很快暴露出來,所以很難發現。
六 環境變量
環境字符串的形式如:name=value,它們的解釋完全取決于各個應用程序,而與內核無關。
#include <stdlib.h>
char *getenv( const char *name );
int putenv( char *str );
int setenv( const char *name, const char *value, int rewrite );
int unsetenv( const char *name );
getenv函數返回指向name=value中的value的指針;putenv函數把字符串name=value放入環境表中,如果name已經存在,則先刪除原來的定義;setenv函數將name設置為value,如果name存在且rewrite非0,則刪除其現有定義,若rewrite為0,則不刪除其現有定義;unsetenv函數刪除name的定義,即使不存在也不會出錯。
七 setjmp和longjmp
#include <setjmp.h>
int setjmp( jmp_buf env );
void longjmp( jmp_buf env, int val );
setjmp和longjmp函數用于處理發生在深層次函數調用中的出錯情況,longjmp函數可以在棧上跳過若干個調用幀,返回到當前函數調用路徑上的某個函數中。在希望返回到的位置調用setjmp,數據類型jmp_buf是某種形式的數組,存放在調用longjmp時能用來恢復棧狀態的所有信息。因為需要在另一函數中引用env變量,所以將env定義為全局變量。當檢查到一個錯誤時,調用longjmp函數,第一個參數env就是在調用setjmp時所用的env,第二個參數val非0,它將成為從setjmp處返回的值。使用第二個參數的原因是一個setjmp可以對應多個longjmp,這樣就可以根據返回值來判斷造成返回的longjmp函數在那個函數中,從而確定出錯的位置。
八 getrlimit和setrlimit函數
#include <sys/resource.h>
int getrlimit( int resource, struct rlimit *rlptr );
int setrlimit( int resource, const struct rlimit *rlptr );
getrlimit和setrlimit函數用于獲取或設置進程的資源限制。資源限制通常是由進程0建立的,由每個后續進程繼承。更改資源限制時,注意以下三條規則:
1 進程的軟限制值只能小于或等于硬限制值;
2 任意進程都可以降低其硬限制值,但它必須大于或等于其軟限制值,這種操作對普通用戶是不可逆的;
3 只有超級用戶進程可以提高硬限制值。
資源限制影響到調用進程并由其子進程繼承,這意味著為了影響一個用戶的所有進程,需要將資源限制構造在shell中。