系統調用與內存管理(sbrk、brk、mmap、munmap)

一、系統調用(System Call):

在Linux中,4G內存可分爲兩部分——內核空間1G(3 ~ 4G)與用戶空間3G(0 ~ 3G),我們通常寫的C代碼都是在對用戶空間即0 ~ 3G的內存進行操作。而且,用戶空間的代碼不能直接訪問內核空間,因此內核空間提供了一系列的函數,實現用戶空間進入內核空間的接口,這一系列的函數稱爲系統調用(System Call)。比如我們經常使用的open、close、read、write等函數都是系統級別的函數(man 2 function_name),而像fopen、fclose、fread、fwrite等都是用戶級別的函數(man 3 function_name)。不同級別的函數能夠操作的內存區域自然也就不同。

我們用一幅圖來描述函數的調用過程:

對於C++中new與delete的底層則是用malloc和free實現。而我們所用的malloc()、free()與內核之間的接口(橋樑)就是sbrk()等系統函數;當然我們也可以直接調用系統調用(系統函數),達到同樣的作用。我們可以用下面這幅圖來描述基本內存相關操作之間的關係:

雖然使用系統調用會帶來一定的好處,但是物極必反,系統調用並非能頻繁使用。由於程序由用戶進入內核層時,會將用戶層的狀態先封存起來,然後到內核層運行代碼,運行結束以後,從內核層出來到用戶層時,再把數據加載回來。因此,頻繁的系統調用效率很低。今天我們就係統調用層面來對內存操作做進一步的瞭解。

二、內存管理(Memory Management)系統調用:

1、brk()與sbrk():

(1)、函數原型與實現:

//函數原型:
#include<unistd.h>
int brk(void * addr); 
void * sbrk(intptr_t increment);

由於sbrk()與brk()這兩個系統函數有點所謂怪異,我們先來看看man手冊對於sbrk()與brk()的描述:

DESCRIPTION

brk() and sbrk() change the location of the program break, which
defines the end of the process's data segment (i.e., the program
break is the first location after the end of the uninitialized data
segment). Increasing the program break has the effect of allocating
memory to the process; decreasing the break deallocates memory.

brk() sets the end of the data segment to the value specified by
addr, when that value is reasonable, the system has enough memory,
and the process does not exceed its maximum data size (see
setrlimit(2)).

sbrk() increments the program's data space by increment bytes.
Calling sbrk() with an increment of 0 can be used to find the current
location of the program break.

RETURN VALUE

On success, brk() returns zero. On error, -1 is returned, and errno
is set to ENOMEM.

On success, sbrk() returns the previous program break. (If the break
was increased, then this value is a pointer to the start of the newly
allocated memory). On error, (void *) -1 is returned, and errno is
set to ENOMEM.

描述:
brk()和sbrk()改變程序間斷點的位置。程序間斷點就是程序數據段的結尾。(程序間斷點是爲初始化數據段的起始位置).通過增加程序間斷點進程可以更有效的申請內存 。當addr參數合理、系統有足夠的內存並且不超過最大值時brk()函數將數據段結尾設置爲addr,即間斷點設置爲addr。sbrk()將程序數據空間增加increment字節。當increment爲0時則返回程序間斷點的當前位置。

返回值:
brk()成功返回0,失敗返回-1並且設置errno值爲ENOMEM(注:在mmap中會提到)。
sbrk()成功返回之前的程序間斷點地址。如果間斷點值增加,那麼這個指針(指的是返回的之前的間斷點地址)是指向分配的新的內存的首地址。如果出錯失敗,就返回一個指針並設置errno全局變量的值爲ENOMEM。

總結:
這兩個函數都用來改變 “program break” (程序間斷點)的位置,改變數據段長度(Change data segment size),實現虛擬內存到物理內存的映射。
brk()函數直接修改有效訪問範圍的末尾地址實現分配與回收。sbrk()參數函數中:當increment爲正值時,間斷點位置向後移動increment字節。同時返回移動之前的位置,相當於分配內存。當increment爲負值時,位置向前移動increment字節,相當與於釋放內存,其返回值沒有實際意義。當increment爲0時,不移動位置只返回當前位置。參數increment的符號決定了是分配還是回收內存。而關於program break的位置如圖所示:

(2)、簡單測試:

對於分配好的內存,我們只要有其首地址old與長度MAX*MAX即可不越界的準確使用(如下圖所示),其效果與malloc相同,只不過sbrk()與brk()是C標準函數的底層實現而已,其機制較爲複雜(測試中,死循環是爲了查看maps文件,不至於進程消亡文件隨之消失)。

雖然,sbrk()與brk()均可分配回收兼職,但是我們一般用sbrk()分配內存,而用brk()回收內存,上例中回收內存可以這樣寫:

int err = brk(old);
// 或者brk(p);效果與sbrk(-MAX*MAX);是一樣的,但brk()更方便與清晰明瞭。
if(-1 == err){
    perror("brk");
    exit(EXIT_FAILURE);
}

2、mmap()與munmap():

mmap函數(地址映射):mmap將一個文件或者其它對象映射進內存。文件被映射到多個頁上,如果文件的大小不是所有頁的大小之和,最後一個頁不被使用的空間將會清零(Linux堆空間未使用內存均清零)。這裏我們只研究mmap的內存映射,而暫時不討論文件方面的問題。關於mmap的文件映射的更詳細的內容可參考:認真分析mmap:是什麼 爲什麼 怎麼用

//函數原型:
#incldue<sys/mman.h>
void * mmap(void * addr, size_t length,int prot,int flags,int fd,off_t offset);

參數:

(1)、addr:
起始地址,置零讓系統自行選擇並返回即可。
(2)、length:
長度,不夠一頁會自動湊夠一頁的整數倍,我們可以宏定義#define MIN_LENGTH_MMAP 4096爲一頁大小。
(3)、prot:
讀寫操作權限,PROT_READ可讀、PROT_WRITE可寫、PROT_EXEC可執行、PROT_NONE映射區域不能讀取。(注意PROT_XXXXX與文件本身的權限不衝突,如果在程序中不設定任何權限,即使本身存在讀寫權限,該進程也不能對其操作)。
(4)、flags常用標誌:
MAP_SHARED【share this mapping】、MAP_PRIVATE【Create a private copy-on-write mapping】
MAP_SHARED只能設置文件共享,不能地址共享,即使設置了共享,對於兩個進程來說,也不會生效。而MAP_PRIVATE則對於文件與內存都可以設置爲私有。
MAP_ANON【Deprecated】、MAP_ANONYMOUS:匿名映射,如果映射地址需要加該參數,如果不加默認映射文件。MAP_ANON已經過時,只需使用MAP_ANONYMOUS即可。
(5)、fd:文件描述符。
(6)、offset:文件描述符偏移量
(fd和offset對於一般性內存分配來說設置爲0即可)

返回值:

失敗返回MAP_FAILED,即(void * (-1))並設置errno全局變量。
成功返回指向mmap area的指針pointer。

常見errno錯誤:

ENOMEM:內存不足;
EAGAIN:文件被鎖住或有太多內存被鎖住;
EBADF:參數fd不是有效的文件描述符;
EACCES:存在權限錯誤,。如果是MAP_PRIVATE情況下文件必須可讀;使用MAP_SHARED則文件必須能寫入,且設置prot權限必須爲PROT_WRITE。
EINVAL:參數addr、length或者offset中有不合法參數存在。

munmap函數:解除映射關係

// addr爲mmap函數返回接收的地址,length爲請求分配的長度。
int munmap(void * addr, size_t length);

這張圖描述了mmap內存地址映射的位置關係(棧區以上爲內核空間)。關於這一點我們可以作以簡單的測試(我採用MIN_LENGTH_MMAP宏,當然你也可以用多少申請多少,系統總是以最小1頁來映射的,關於內存分頁與虛擬地址映射可參考:Linux系統內存管理與內存分頁機制

mmap映射的地址處於堆區與棧區中間,malloc映射的堆區內存爲33頁(最小映射大小),而mmap映射的內存爲3頁,也是4096的整數倍。

作者:Apollon_krj
鏈接:https://blog.csdn.net/Apollon_krj/article/details/54565768
來源:CSDN
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章