[單刷APUE系列]第一章——Unix基礎知識[1]
[單刷APUE系列]第一章——Unix基礎知識[2]
[單刷APUE系列]第二章——Unix標準及實現
[單刷APUE系列]第三章——文件I/O
[單刷APUE系列]第四章——文件和目錄[1]
[單刷APUE系列]第四章——文件和目錄[2]
[單刷APUE系列]第五章——標準I/O庫
[單刷APUE系列]第六章——系統數據文件和信息
[單刷APUE系列]第七章——進程環境
[單刷APUE系列]第八章——進程控制[1]
[單刷APUE系列]第八章——進程控制[2]
[單刷APUE系列]第九章——進程關係
[單刷APUE系列]第十章——信號[1]shell
咱們知道,不管是彙編仍是C語言仍是其餘的語言,在編譯成實際二進制代碼的時候,都是存在着一個入口點,通常來講,這個入口點就是main函數,C語言都是從main函數處開始執行,在Unix開發中,main函數都是長這樣的小程序
int main(int argc, char *argv[]);
通常狀況下,是shell啓動程序,shell都是經過內核的exec函數調用程序,可是在啓動以前,須要有一個特殊程序將環境變量和啓動參數傳給main函數。這個特殊程序能夠以下表示exit(main(argc, argv));
啓動進程之後,就須要考慮進程退出了,Unix系統有8種進程終止行爲。segmentfault
從main返回數組
exit函數ide
_exit或者_Exit函數
最後一個線程退出佈局
最後一個線程調用pthread_exit函數性能
下面的就是異常退出學習
調用abortui
接收到一個信號
最後一個線程對取消請求做出相應
系統提供了三個函數用於退出進程
void exit(int status); void _Exit(int status); void _exit(int status);
前兩個是標準C庫中的函數,後一個是BSD系統調用庫。
Before termination, exit() performs the following functions in the order listed:
Call the functions registered with the atexit(3) function, in the reverse order of their registration.
Flush all open output streams.
Close all open streams.
Unlink all files created with the tmpfile(3) function.
exit函數在退出進程以前,老是會進行以上4個步驟。另外兩個則是基本同樣。
這三個函數都接受一個status參數,也叫做退出狀態。若是調用這三個函數不帶參數,或者main函數執行return,或者main函數沒有返回值,那麼就不會定義返回狀態。可是若是main返回值是int類型,而且有顯式返回行爲,那麼進程返回狀態就是0.
#include <stdio.h> main(int argc, char *argv[]) { printf("Hello, world\n"); }
上面的代碼缺乏了返回值的聲明,咱們知道,若是沒有返回值的聲明,進程的返回狀態就是未定義的,原著中就有了兩個標準定義的對比,可是通過試驗,如今的gcc和clang編譯器基本都是默認支持c99標準,也就是說,會自動幫你補全返回值,因此這個也只須要了解就行。
在前面標準C庫exit函數的說明中,能夠了解到,exit函數在退出進程時候,會進行4個步驟,其中一個就是調用atexit函數註冊的函數,而且是以註冊順序的反向來調用。
int atexit(void (*func)(void));
顯而易見,就是傳入一個函數指針,並且這個函數基本不用關心返回值,由於它根本就沒作什麼工做。
咱們知道,內核想要執行一個程序,只有經過exec函數族,並且當啓動一個進程的時候,main函數都會要求命令行參數和環境變量。前面也提到過,啓動進程的時候其實是啓動了一個小程序,而後由它來傳遞各類參數給main函數,而開發者在實際開發的時候只須要關心main函數做爲入口點的代碼就好了。這裏實際上很簡單就只講解一下就爲止了。
每一個程序均可以獲得環境變量,其實是進程接收環境表,和參數表同樣,環境表實際上也是一個字符指針數組,實際上Unix系統提供了environ
全局變量指向該指針數組。
extern char **environ;
數組其實是一個指針,這個應該很容易理解,因此在這裏使用了一個全局二級指針做爲數組,數組的每一個元素是一個字符指針,用於指向各自的字符串,而後最後就是一個null做爲整個數組的結尾,因此在判斷的時候只須要if (i = 0; environ[i] != NULL; ++i)
便可很簡單的使用。
實際上,因爲環境變量的重要性,Unix系統規定main函數能夠有三個參數,最後一個參數就是環境表參數,可是因爲後來ISO C規定main函數只有兩個參數,並且第三個參數因爲environ
全局變量的存在也變得毫無心義,因此POSIX規定也廢棄了main的第三個參數,可是通常狀況下,咱們都不直接存取environ全局變量,都是使用系統提供的getenv和putenv函數來訪問,可是若是想要總體查看,就須要訪問environ全局變量。
在前面講解其餘東西的時候筆者就略微的提到了C語言的存儲佈局,一直以來,C程序的佈局都是沿襲了彙編的習慣,由如下部分組成
正文段。這是主要用於執行的部分,並且因爲正文段的頻繁讀取執行,因此正文段通常都是隻讀的,防止誤操做致使破壞或者被惡意程序竊取。前面講解的保存正文位保存的就是這一段
初始化數據段。這一段其實是在C語言中以明確的語句初始化的變量。例如:int i = 0;
未初始化數據段。也叫做bss段,也就是語句中沒有明確賦予初值的變量。
棧。自動變量以及函數調用的場景信息都被放在這裏,棧具備先進後出的特性,很是適合函數調用和變量分配空間,回收空間的行爲。而且,這些都是由系統自動管理的。
堆。堆一般用於用戶自行分配空間,也就是開發者一般的malloc分配。
除了這些意外,根據各個系統的不一樣,還存在着不一樣的段,可是這些都和學習Unix開發關係不大。爲了查看分段信息,可使用size
命令
~/Development/Unix $ size echoarg __TEXT __DATA __OBJC others dec hex 4096 4096 0 4294971392 4294979584 100003000
這就是蘋果系統下的狀況,實際上其餘Unix實現也是差很少的。
共享庫是很重要的東西,基本上稍微和系統開發有關的都會接觸到靜態庫和動態庫。咱們知道一個二進制程序其實是由各個段組成,不少狀況下,程序開發都會共享不少代碼,因此就有了這兩個連接庫方式,在學習C語言編譯過程的時候,就知道了編譯實際上分預處理、代碼生成、彙編和連接,預處理、代碼生成和彙編都是隻針對本身編寫的代碼,咱們在編寫代碼的時候用到其餘的庫,當時只導入了頭文件,讓編譯經過,至於實際代碼就是在連接階段加載,靜態庫和動態庫區別就在因而否將實際代碼連接到二進制文件。
不一樣的編譯系統可能會有不一樣的參數用於說明是否要使用動態庫,因此用戶實際上應當參考用戶手冊的說明,這裏就不講述了。
ISO C有三個分配存儲空間的函數
void *malloc(size_t size); void *calloc(size_t count, size_t size); void *realloc(void *ptr, size_t size); void free(void *ptr);
這四個函數應該每一個人都用過了,不須要過多講解。不過這三個內存分配管理函數其實是歸屬標準C庫的,也就是說,這並非不可替代的東西,在底層調用上,實際都是調用sbrk系統調用,前面也講到過,sbrk系統調用實質上是更改進程內存區域段的大小,可是無論內存的具體分配,而malloc函數族則是在邏輯層面上分配內存,當咱們使用free回收內存時,其實是依舊保留在進程的堆空間中的,並非歸還給系統,直到進程退出,全部內存被釋放,內存管理問題實際上一直是老大難問題,也是追蹤bug的重點,可是筆者我的很是推崇引用計數,這在C++乃至其餘語言中都是很好用的方法,雖然簡單,可是隻要當心,通常都不會出錯。
做爲malloc和free的替代,實際上有很多系統和類庫都提供了相關的函數,如今有libmalloc、vmalloc、quick-fit、jemalloc、tcmalloc和alloca等,可是根據性能對比,jemalloc和tcmalloc其實是最好的兩個,在實際開發中都能有效的提高內存管理的效率。
環境變量主要的形式就是name=value
鍵值對,對於Unix自己來講,這些東西沒有任何意義,可是環境變量可讓應用程序以最快捷方便的形式進行讀取信息,因此ISO C標準也規定了相關的函數
char *getenv(const char *name); int putenv(char *string); int setenv(const char *name, const char *value, int overwrite); int unsetenv(const char *name);
getenv函數接收一個name指針,返回name=value中對應的value字符串指針,putenv取得形式爲name=value的字符串,將其放到環境表中,若是name存在則刪除原先的定義,setenv將name設置爲value,overwrite參數控制是否重寫,unsetenv則刪除name對應的環境變量。
你們可能會奇怪putenv和setenv有什麼區別,下面就是官方解釋
The string pointed to by string becomes part of the environment. A program should not alter or free the string, and should not use stack or other transient string variables as arguments to putenv(). The setenv() function is strongly preferred to putenv().
putenv函數的字符串指針直接被放到了環境表中,因此不能修改或者釋放這個字符串,因此不該該使用棧或者堆分配的字符串做爲參數,而setenv函數則沒有這個問題。
原著中還寫了有關環境表修改時的操做,可是筆者我的認爲並無必要在這裏繼續講,因此各位能夠看看原著,裏面寫的已經足夠了。
goto語句應該都知道,在C語言中,goto語句是不能超過函數的,因此係統也提供了兩個函數用於執行跳轉
int setjmp(jmp_buf env); void longjmp(jmp_buf env, int val);
在彙編語言中,曾經有過這麼一個概念,地址分爲段地址和偏移地址,8086芯片是20位地址編碼,而後它將其分爲這兩種地址,而後能夠尋址1m地址空間,這裏也有個長跳轉和短跳轉,固然,這裏就講講C語言中兩種跳轉,在實際開發中,咱們常常遇到函數持續嵌套調用,而後棧中有一堆的保存狀態,結果在棧頂的函數發現一個錯誤,那麼可能須要打印一個出錯信息,而後返回main函數,可是若是嵌套過多,咱們就不得不不斷的一層層檢查返回值,最終回到main函數,這個時候,就是非局部跳轉起做用的時候了。
setjmp在調用的位置保存調用環境到env參數中,而後會返回0。longjmp函數須要兩個參數,一個就是先前調用的時候保存的env環境,第二個是具非0的val,這將成爲從新開始點的返回值,根據返回值就能簡單的判斷出錯了,並且還不須要通過層層的棧返回。
All accessible objects have values as of the time longjmp() routine was called, except that the values of objects of automatic storage invocation duration that do not have the volatile type and have been changed between the setjmp() invocation and longjmp() call are indeterminate.
在蘋果系統下的聲明就是如上所示,只有volatile類型的自動變量會被不回滾,其餘自動變量都會被不肯定的值回滾,並且因爲不一樣的系統的實現不一樣,因此實際使用也極爲困難。
資源是有限的,因此每一個進程實際上都有固定的資源限制,系統提供了兩個函數用於查詢一些限制
int getrlimit(int resource, struct rlimit *rlp); int setrlimit(int resource, const struct rlimit *rlp); The resource parameter is one of the following: RLIMIT_CORE The largest size (in bytes) core file that may be created. RLIMIT_CPU The maximum amount of cpu time (in seconds) to be used by each process. RLIMIT_DATA The maximum size (in bytes) of the data segment for a process; this defines how far a program may extend its break with the sbrk(2) system call. RLIMIT_FSIZE The largest size (in bytes) file that may be created. RLIMIT_MEMLOCK The maximum size (in bytes) which a process may lock into memory using the mlock(2) function. RLIMIT_NOFILE The maximum number of open files for this process. RLIMIT_NPROC The maximum number of simultaneous processes for this user id. RLIMIT_RSS The maximum size (in bytes) to which a process's resident set size may grow. This imposes a limit on the amount of physical memory to be given to a process; if memory is tight, the system will prefer to take memory from processes that are exceeding their declared resident set size. RLIMIT_STACK The maximum size (in bytes) of the stack segment for a process; this defines how far a program's stack segment may be extended. Stack exten- sion is performed automatically by the system.
rlimit的結構體長這樣
struct rlimit { rlim_t rlim_cur; /* current (soft) limit */ rlim_t rlim_max; /* hard limit */ };
而上面說明中的就是能獲取或者修改的資源的名稱,實際上進程只能調小自身限額,而不能提升限額,只有root權限才能提高限額,因此通常不多用setrlimit設置,並且資源限制影響和其餘的進程屬性同樣是隨着進程派生繼承的。