APUE第七章主要分享了進程的運行環境。主要內容包括:
1、main函數
在這節裏面主要說明了在我們平常利用的main函數是如何被調用的。其實從程序開發人員的角度會考慮這樣一個問題,編譯後 的程序爲什麼會運行?爲什麼要有main函數等等。main函數是不是程序最開始運行的地方,等等。
其實當一個C程序在運行前,系統會做很多的初始化工作,然後再調用exec函數來運行C程序。
2、進程的終止
- 進程的終止方式有8種:
- 其中正常終止有5種:
- return from main
- 調用exit函數
- 調用_exit或者_Exit函數
- return of the last thread from its start routine
- call pthread_exit from the last thread
異常終止有3種:
- 調用abort
- receipt of a signal(收到信號)
- response of the last thread to a cancellation request
接下來有三個exit函數:
#include<stdlib.h>
void exit(int status);
void _Exit(int status);
#include<unistd.h>
void _exit(int status);
其中_Exit和_exit函數會理解返回到kernel;而exit函數會做一些清理工作,比如調用fclose關閉文件等,將未寫入的文件flush掉等,然後會返回kernel。
這三個函數都有一個int的參數,稱爲exit status。大多數的shell可以檢測進程退出時的狀態。如果在main函數中沒有調用return、exit或者return了但沒有指定return的值,那麼進程的exit status是不確定的。但在C99裏面,進程的退出狀態爲0.
eg:
#include<stdio.h>
main()
{
printf("hello, world/n");
}
編譯,鏈接,運行程序。
$ ./a.out
hello, world
$echo $? //用於輸出進程的退出狀態
13//可以看到退出狀態是13,其實是printf函數的返回值
而如果指定是C99方式的話,即
$ cc -std=c99 hello.c //指明是C99
hello.c:4 warning: return type defaults to int
$ ./a.out
hello, world
$echo $?
0
可以看到C99標準下退出狀態是0
曾經看到一道筆試題,說main函數在調用結束的時候會不會有什麼動作,大概是這個意思吧。其實就是考的如下函數
#include<stdlib.h>
int atexit(void (*func) (void));
在ISO C中一個進程可以註冊至多32個函數,這些函數是在進程exit的時候調用的。同時這些函數調用的順序和他們註冊的順序相反,有點類似C++中構造函數一樣。
下面這張圖展示了一個C程序的如何開始和結束的:
從上圖可知,kernel調用exec函數族來運行C程序,在C程序運行過程中會調用其他的函數,然後C程序可能會調用exit系列或者return(其實是間接調用exit)來結束自己的運行。可以看到,在調用exit函數時,程序不會理解返回到kernel中,而是做了一系列的清理工作,然後調用_exit或者_Exit函數返回kernel的。
3、命令行參數
在調用exec函數族的時候可以傳遞給進程一系列的參數。
4、環境列表
環境列表主要是在全局變量char **environ中存儲,形式是name=value,可以從下圖看出:
一般來說,獲取環境變量不是通過直接操作environ變量來進行的,而是通過調用getenv和setenv函數來進行的。
5、C程序在內存中的佈局
6、shared library
庫文件的使用大大減少了可執行文件的大小,同時當庫內部函數的具體實現改變時,只要接口不改變,不會影響庫的使用。庫文件一個缺點是當系統第一次調用庫文件時開銷會大一點。
7、內存分配
這裏說的內存主要是指的是堆空間的分配。堆空間在使用的時候需要特別注意,需要開發人員自己申請,同時也需要自己釋放不再需要的空間,如果不正確釋放堆空間,那麼會照成memory leak,即內存泄漏。
堆空間分配的函數主要有:
#include<stdlib.h>
void *malloc(size_t size);
//malloc用來分配size大小的堆空間,同時分配空間的初始值是不確定的!!
void *calloc(size_t nobj, size_t size);
//calloc用來分配nobj個大小爲size的空間,即分配nobj*size大小的空間,但不同於malloc的是calloc分配的空間是初始化過的!
void *realloc(void *ptr, size_t newsize);
//realloc用來增長或者減少之前分配的空間。需要注意的是ptr指向的空間必須是通過malloc或者calloc分配的空間!!如果ptr爲NULL,那麼realloc與malloc類似,用來分配newsize大小的空間。
//return NULL on error. non-null is OK
void free(void *ptr);
//釋放堆空間。其中ptr必須是malloc、calloc或者realloc調用後的返回值,即必須是動態內存的分配纔可以使用free釋放。
可以利用lint和valgrind等檢測工具來檢測程序中的隱蔽bug,將這兩個工具結合起來很是給力~
8、環境變量
從前面的介紹中可以看到,環境變量的形式是name=value。一般來說不是直接操作environ來進行獲取或設置,而是通過getenv或者setenv函數來獲取和操作的。
#include<stdlib.h>
char *getenv(const char *name);
//通過name來獲得對應的value。
我們可以改變當前進程和他子進程的運行環境,但是不能改變當前進程父進程的運行環境。
9、setjmp和longjmp函數
在C語言中,goto語句一般是不提倡使用的,但如果研究一些源代碼會發現,其實很多代碼裏面都使用的goto。但是goto語句有一個不足的地方就是他不能跳出函數的範圍,只能在一個函數內部進行。標準庫提供了setjmp和longjmp函數來結合使用,可以實現長跳轉。
一般來說setjmp和longjmp函數用於一些出錯處理,其他方面用的不多,很多地方都建議儘量少使用。
可參考《APUE》和《c專家編程》
10、getrlimit和setrlimit函數
每一個進程都有一系列資源的限制,例如進程最多可以打開的文件數、進程堆棧的大小等。這些limit可以通過getrlimit和setrlimit函數來進行獲取和設置。
#include<sys/resource.h>
int getrlimit(int resource, struct rlimit *rlptr);
int setrlimit(int resource, const struct rlimit *rlptr);
//return 0 if ok; nonzero on error
其中rlimit是這樣一個結構:
struct rlimit
{
rlim_t rlim_cur;//soft limit: 表示當前的limit值
rlim_t rlim_max;//hard limit:表示rlim_cur的最大值
};
一個進程有如下操作:
- 可以改變soft limit,soft limit必須要小於等於hard limit
- 可以改變hard limit,但必須要保證hard limit大於等於soft limit
- 只有超級用戶可以改變hard limit
如果某個limit是沒有限制的話,那麼會被指定爲常量:RLIM_INFINITY
下圖是一些常見的limit常量和他們的具體含義:
其中在調試程序時用到的一個是core的大小,一般來說可能系統設置core的大小是0,即當程序異常退出時不產生core,這對於調試來說不太實用,因此一般需要調整該值。在bash中,有這樣一個命令:ulimit可以改變core文件的大小。
一般是通過:
ulimit -c unlimited來使得core文件的大小不受限制。當然也有其他的選項。
上面大概就是APUE第七章進程運行環境的一個總結,有很多細節需要在設計中深入考慮。