一、進程終止
進程正常終止:
1. 從main()函數返回,即retrun 0
2. 調用exit(),即在main()函數內或其它被main()函數調用的函數內調用exit()
3. 調用_exit()或_Exit(),即在main()函數內或其它會被main()調用的函數體內調用_exit()或_Exit()
4. 最后一個線程從其所在進程返回
5. 最后一個線程在其所在進程調用pthread_exit()函數
進程異常終止:
6.調用abort()
7.進程接收到信號
8.進程中最后一個線程最取消做出響應
可以使用exit()、_exit()和_Exit()函數來正常終止程序。
第一個函數和后面兩個函數區別是:exit()會進行一些資源清理工作,然后返回內核,而_exit()和_Exit()則是立即返回內核。其頭文件及函數原型如下:
#include <stdlib.h> void exit(int status); void _Exit(int status); #include <unistd.h> void _exit(int status);
三個函數的參數都是用於返回給操作系統,值為程序的退出狀態。
當我們使用return n;語句或exit()函數退出程序時,可以通過ISO C標准提供的庫函數來實現清理工作,該庫函數會讓return n;或exit()函數在程序退出時自動調用我們登記的清理函數。其函數聲明如下:
#include <stdlib.h> int atexit(void (*func) (void));
該函數負責登記程序退出時要執行的函數,若注冊成功則返回0,否則返回非0。
參數是一個函數指針。atexit()函數可以登記多個清理函數,清理函數先后順序是以棧的形式壓入,即先入后出。如果函數被注冊多次,那么也會被調用多次。
示例代碼如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 static void exit_func(void) 5 { 6 static int cnt = 0; 7 cnt++; 8 printf("%d: %s\n", cnt, __FUNCTION__); 9 } 10 11 int main() 12 { 13 atexit(exit_func); 14 atexit(exit_func); 15 16 // exit(0); /* 調用兩次exit_func */ 17 // _Exit(0); /* 直接退出 */ 18 19 return 0; /* 調用兩次exit_func */ 20 }
二、命令行參數
命令行參數是程序調用main()函數時傳遞的參數,如下代碼可以打印輸入參數:
#include <stdio.h> int main(int argc, char **argv) { int i; for (i = 0; i < argc; ++i) printf("argv[%d]: %s\n", i, argv[i]); return 0; }
在Linux命令行執行:
$ gcc test.c
$ ./a.out argv1 argv2 argv3
argv[0]: ./a.out
argv[1]: argv1
argv[2]: argv2
argv[3]: argv3
三、環境表
環境表是個指針數組,用於存儲環境變量,我們可以在Linux命令行中使用命令env查看環境變量
環境表使用全局變量environ存儲的,使用方式如下:
extern char **environ;
示例代碼如下:
#include <stdio.h> extern char **environ; int main() { int i; for (i = 0; environ[i] != NULL; ++i) printf("%d: %s\n", i, environ[i]); return 0; }
四、C語言的存儲空間布局
1. 代碼區:用於存放代碼(函數)的區域,是只讀區。函數指針就是指向代碼區的地址
2. 全局變量區:用於存放全局變量(定義在函數外的變量)和static的局部變量
3. BSS段:用於存放未初始化的全局變量的區域。BSS段在main()函數執行之前,會清0
4. 棧區(堆棧區stack):用於存放局部變量的區域,包括函數的參數和非static的局部變量
5. 堆區(heap):是程序員唯一可以控制的區域。通常內存分配、回收都是在堆區。malloc()、free()都是在堆區。堆區的內存系統完全不管,程序員全權管理堆區內存
6. 只讀常量區:存放字符串字面值(""括起來的字符串)和const修飾的全局變量。這個區域也是只讀區環境變量
需要注意的幾點有:
1. const修飾的局部變量(如const int i = 1)在棧區
2. 如char a[] = "abc","abc"在只讀常量區,賦值操作會把"abc"復制到棧區
3. 如char *a = "abc",char *a指向只讀常量區
五、共享庫
共享庫,即動態鏈接庫,在Linux系統中以.so(share object)為后綴。程序開始啟動運行時,加載所需的函數。這樣減少了每個可執行文件的大小。
共享庫的另一個優點是可以用庫函數新版本代替老版本而無需對使用該庫的程序重新連接編輯。
共享庫的創建命令為:
$ gcc -fpic -shared 源文件1, 源文件2, 源文件n -o lib庫文件名.so(-fpic可以省略)
如:$ gcc -fpic -shared add.c -o libadd.so
$ gcc test.o -l libadd.so
需要注意的是,如果我們想要使用自己創建的共享庫,需要需要配置環境變量LD_LIBRARY_PATH到共享庫所在文件夾才能找到。否則會導致程序鏈接成功,運行失敗。
六、存儲空間分配
malloc()系列函數聲明如下:
#include <stdlib.h> void *malloc(size_t size); /* 分配size字節的內存 */ void free(void *ptr); /* 釋放malloc()、calloc()和realloc()返回指針的內存 */ void *calloc(size_t nmemb, size_t size); /* 分配size字節的內存並清0 */ void *realloc(void *ptr, size_t size); /* 重新指定分配的內存大小 */ /* 例子 */ char *str = NULL; str = (char *)calloc(10, sizeof(char)); /* 分配內存並清0 */ free(str); /* 釋放內存 */
malloc()系列函數需要注意的有以下兩點:
1. 申請小塊內存時,一次映射33個內存頁,用完后繼續映射。申請大塊內存時(超過31個內存頁)會映射比申請稍多一點的內存頁;
2. 除了分配正常的內存空間,它還需要額外開辟一些空間,用於存儲一些附加信息,比如分配的大小。存在前面4個字節(32位系統4個字節,64位系統8個字節)。
但是對於我們來說,正常使用即可。
sbrk()和brk():
#include <unistd.h> int brk(void *addr); /* 申請/釋放內存,一般用於釋放內存 */ void *sbrk(intptr_t increment); /* 申請/釋放內存,一般用於申請內存 */ /* 例子 */ char *str = NULL; str = (char *)sbrk(4); /* 分配4個字節 */ brk(str); /* 釋放指針 */
mmap()和munmap():
#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); /* 分配內存 */ int munmap(void *addr, size_t length); /* 釋放內存 */ /* 例子 */ char *str = NULL; str = (char *)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, NULL, NULL); munmap(str, 4);
函數參數以及返回值:
addr:指定映射存儲區的起始地址,通常設置為0
length:映射的字節數
prot:映射的權限。有PROT_READ(可讀)、PROT_WRITE(可寫)、PROT_EXEC(可執行)、PROT_NONE(不可訪問)
flags:映射的屬性。有MAP_SHARED、MAP_PRIVATE和MAP_ANONYMOUS。前兩個二選一,指明映射的內存是否共享,MAP_SHARED只對映射文件有效。MAP_ANONYMOUS用於映射物理內存,默認映射文件
fd:要被映射文件的描述符
offset:映射字節在文件中的起始偏移量
返回值:成功返回映射區的起始地址;出錯返回MAP_FAILED。
關於上述三個分配內存的函數,其實我們只需要使用malloc()系列函數即可。
malloc小於128k的內存,使用brk()分配內存,緊接着堆區;
malloc大於128k的內存,使用mmap()分配內存,在堆和棧之間找一塊空閑內存分配。
具體分配規則如下圖:

七、環境變量
環境表包含環境變量名有環境變量,ISO C提供了一個庫函數用於獲取環境變量的值。其函數聲明如下:
#include <stdlib.h> char *getenv(const char *name);
該函數成功返回對應指針,失敗返回NULL。
除了獲取環境變量值之外,UNIX系統也提供了三個用於增加、更新和刪除的庫函數。 其頭文件及函數原型如下:
#include <stdlib.h> int putenv(char *string); /* string格式為name=value,如果name已經定義,那么覆蓋之前的定義 */ int setenv(const char *name, const char *value, int replace); /* 如果replace非0,那么覆蓋之前的定義;如果repalce為0,則不刪除現有的定義 */ int unsetenv(const char *name); /* 刪除定義,不存在定義不算出錯 */
這三個函數成功時返回0,出錯返回-1。
每個進程都在一個環境下運行,當我們使用上面的函數更改了進程的運行環境之后,該環境會被進程的子進程繼承,但不會影響到進程的父進程。
下一章 第八章:進程控制
