1、進程的終止方式
通常情況下,進程有八種終止方式(5種正常終止 + 3種異常終止)
正常終止:
(1) main 的 正常 return
(2)調用 exit 退出
(3)調用 _exit 和 _Exit
(4)最後一個線程從啓動歷程返回
(5)最後一個線程調用pthread_exit退出
異常終止:
(1)調用 abort()
(2)接到一個信號
(3)最後一個線程 對 取消請求做出響應
1、有三種退出函數如下:
這三個函數都屬於進程正常退出的函數,_exit 和 _Exit 會直接進入內核,exit會先執行一些清理處理,然後進入到內核。
exit 函數總會執行一個標準IO的清理關閉操作,對所有打開的文件流都會調用fclose().這三個函數都有一個整型參數,代表終止狀態。
2、atexit 函數
可以登記一個在退出時執行的函數,有點析構函數的感覺。在ISO C裏面規定了,一個進程最多隻能登記32個函數。登記的函數無需參數,也沒有返回值。atexit 也沒有傳參和處理返回值的方式。注意:exit 之後調用他們的順序與登記時候的順序相反,並且同一個函數被登記多次,也會被調用多次。下面是函數原型以及源碼例子:
#include "apue.h"
static void my_exit1(void);
static void my_exit2(void);int
main(void)
{
if (atexit(my_exit2) != 0)
err_sys("can't register my_exit2");if (atexit(my_exit1) != 0)
err_sys("can't register my_exit1");
if (atexit(my_exit1) != 0)
err_sys("can't register my_exit1");printf("main is done\n");
return(0);
}static void
my_exit1(void)
{
printf("first exit handler\n");
}static void
my_exit2(void)
{
printf("second exit handler\n");
}
運行結果:
main is done
first exit handler
first exit handler
second exit handler
3、環境表
每一個程序都有一個環境表,它是以字符串數組形式呈現的,每一項都以 '\0' 結尾。其格式也是name=value, 如書中下表:
主函數中的參數形式如下:
4、C程序的存儲空間佈局
C語言的存儲空間佈局主要有:正文段、初始化數據段、未初始化數據段、棧、堆
(1) 正文段:CPU執行的機器指令部分。通常是可共享的,頻繁執行的程序在存儲器中也只需要一個副本。並且他是隻讀的,以防止被修改。
(2) 初始化數據段:通常稱爲數據段。主要存放一些已初始化的全局變量。
(3) 未初始化數據段:通常稱爲 bss 段(block started by symbol 有符號開始的塊)。在程序開始時,內核會將這些數據初始化爲0,或者是NULL指針。
(4) 棧。程序在運行時,編譯器自動分配的一塊連續存儲區域,存放函數中的參數、局部變量等。不能動態申請、一切都靠編譯器控制。如果超出了存儲大小會出現棧溢出錯誤。入棧彈棧方式:後進先出
(5) 堆。可以動態申請的存儲區。通常是大塊存儲。該區域需要手動申請手動釋放,若不釋放會造成內存泄漏。或是在程序結束後系統回收。
注意:
A 、static 局部變量,初始化的放在數據段,未初始化的放在 bss 段。數據段和bss段的區別就在於,是否初始化。在網上會有靜態存儲區一說,我覺得就是整個數據段 + bss段 + 代碼段,數據段和bss段主要包含的是全局變量、靜態局部變量、常量。
B、linux 中 可以使用size命令,查看各段長度:
書中有張圖:
5、對存儲空間進行動態分配
主要有三個函數:
malloc 和 free 不解釋;
calloc :爲指定的對象,分配一塊相應個數的空間,並且將這段空間清零。
realloc:在一個存儲空間的位置上,申請一塊空間,可能小,可能大。如果大,並且影響到了其他內存,則會申請一塊新區域將該區域的數據拷貝到這塊新區域並返回指針。老區域會釋放。
alloca 棧上的分配:
6、可跨函數跳躍的 setjmp 和 longjmp
C語言的 goto 只能實現函數內的跳躍,要跨函數的跳躍需要用到 setjmp 和 longjmp。書上舉了一段代碼的例子,他存儲的地址如下:
在上述的案例中,假設 cmd_add 出現了錯誤,函數會打印錯誤信息後返回,一直到main函數。該例子cmd_add比main函數低兩個層級,如果低五個層級的話,一層一層的返回就顯得特別麻煩。爲了解決這一問題,就是用了setjmp 和 longjmp。
goto是在函數內跳轉;setjmp 和 longjmp 是在棧上跳轉。
#include <setjmp.h>
int setjmp(jmp_buf env);
返回值:若直接調用,返回0;若從longjmp返回,則爲非0
void longjmp(jmp_buf env, int val);
#include "apue.h"
#include <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);
}/*
* Change variables after setjmp, but before longjmp.
*/
globval = 95; autoval = 96; regival = 97; volaval = 98;
statval = 99;f1(autoval, regival, volaval, statval); /* never returns */
exit(0);
}static void f1(int i, int j, int k, int l)
{
printf("in f1():\n");
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);
}
運行結果:
in f1():
globval = 95, autoval = 96, regival = 97, volaval = 98, statval = 99
after longjmp:
globval = 95, autoval = 96, regival = 97, volaval = 98, statval = 99
將希望返回的位置調用一次 setjmp,然後通過 longjmp 可以跳躍回 setjmp 的地方。
在設置這個棧跳躍點的時候setjmp 返回的是0,
在調用 longjmp 跳躍到棧點時,setjmp 返回的是longjmp的二參,這樣可以方便判斷這個跳躍是從哪裏來的。
setjmp 的參數是一個特殊類型的 jmp_buf 的變量,在調用 longjmp 時需要一個保存了棧信息的變量,就用 jmp_buf 數據類型保存棧的信息。
自動變量、寄存器變量、易變變量是否會出現值得回滾(也就是上例中的1、2、3、4、5)?
根據上例的結果看是不會回滾的,但是還要看情況,大多數的實現不會回滾自動變量和寄存器變量中的值,但這都不一定。如果有一個自動變量,我們不想讓他回滾,那麼我們可以加一個volatile 屬性。聲明爲static 或者是 全局變量,在執行longjmp時保持不變。
注意:
1、如果將上述程序進行1級優化,得到的結果如下:
gcc -O testjmp.c
in f1():
globval = 95, autoval = 96, regival = 97, volaval = 98, statval = 99
after longjmp:
globval = 95, autoval = 2, regival = 3, volaval = 98, statval = 99
-O、-O1:優化編譯需要更多時間,並且大型函數需要更多內存。使用-O選項,編譯器會嘗試減小代碼尺寸減少執行時間,不執行任何需要大量編譯時間的優化。
經過優化之後,自動變量 和 寄存器變量現在在寄存器中,在調用longjmp之後,恢復爲之前setjmp的棧環境,這時如果一直在內存中的數據就會改變,在寄存器中的數據不改變。
2、書中舉了一個局部變量作爲緩衝區的錯誤例子:
7、getrlimit 函數 和 setrlimit 函數
注意:
進程資源的限制,會影響到子進程的繼承。爲了影響一個用戶的所有後續進程,需將資源限制的設置構造在shell中,可以使用ulimit 或者 limit
#include "apue.h"
#include <sys/resource.h>#define doit(name) pr_limits(#name, name)
static void pr_limits(char *, int);
int
main(void)
{
#ifdef RLIMIT_AS
doit(RLIMIT_AS);
#endifdoit(RLIMIT_CORE);
doit(RLIMIT_CPU);
doit(RLIMIT_DATA);
doit(RLIMIT_FSIZE);#ifdef RLIMIT_MEMLOCK
doit(RLIMIT_MEMLOCK);
#endif#ifdef RLIMIT_MSGQUEUE
doit(RLIMIT_MSGQUEUE);
#endif#ifdef RLIMIT_NICE
doit(RLIMIT_NICE);
#endifdoit(RLIMIT_NOFILE);
#ifdef RLIMIT_NPROC
doit(RLIMIT_NPROC);
#endif#ifdef RLIMIT_NPTS
doit(RLIMIT_NPTS);
#endif#ifdef RLIMIT_RSS
doit(RLIMIT_RSS);
#endif#ifdef RLIMIT_SBSIZE
doit(RLIMIT_SBSIZE);
#endif#ifdef RLIMIT_SIGPENDING
doit(RLIMIT_SIGPENDING);
#endifdoit(RLIMIT_STACK);
#ifdef RLIMIT_SWAP
doit(RLIMIT_SWAP);
#endif#ifdef RLIMIT_VMEM
doit(RLIMIT_VMEM);
#endifexit(0);
}static void
pr_limits(char *name, int resource)
{
struct rlimit limit;
unsigned long long lim;if (getrlimit(resource, &limit) < 0)
err_sys("getrlimit error for %s", name);
printf("%-14s ", name);
if (limit.rlim_cur == RLIM_INFINITY) {
printf("(infinite) ");
} else {
lim = limit.rlim_cur;
printf("%10lld ", lim);
}
if (limit.rlim_max == RLIM_INFINITY) {
printf("(infinite)");
} else {
lim = limit.rlim_max;
printf("%10lld", lim);
}
putchar((int)'\n');
}
運行結果:
RLIMIT_AS (infinite) (infinite)
RLIMIT_CORE 0 (infinite)
RLIMIT_CPU (infinite) (infinite)
RLIMIT_DATA (infinite) (infinite)
RLIMIT_FSIZE (infinite) (infinite)
RLIMIT_MEMLOCK 65536 65536
RLIMIT_MSGQUEUE 819200 819200
RLIMIT_NICE 0 0
RLIMIT_NOFILE 1024 1048576
RLIMIT_NPROC 3611 3611
RLIMIT_RSS (infinite) (infinite)
RLIMIT_SIGPENDING 3611 3611
RLIMIT_STACK 8388608 (infinite)