APUE 第七章 進程的運行環境

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第七章進程運行環境的一個總結,有很多細節需要在設計中深入考慮。

發佈了71 篇原創文章 · 獲贊 11 · 訪問量 40萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章