欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
內存觀(guān)點(diǎn)

Linux內存管理

摘要本章首先以應用程序開(kāi)發(fā)者的角度審視Linux的進(jìn)程內存管理,在此基礎上逐步深入到內核中討論系統物理內存管理和內核內存地使用方法。力求從外自?xún)?、水到渠成地引導網(wǎng)友分析Linux地內存管理與使用。在本章最后我們給出一個(gè)內存映射地實(shí)例,幫助網(wǎng)友們理解內核內存管理與用戶(hù)內存管理之間地關(guān)系,希望大家最終能駕馭Linux內存管理。

 

前言

內存管理一向是所有操作系統書(shū)籍不惜筆墨重點(diǎn)討論的內容,無(wú)論市面上或是網(wǎng)上都充斥著(zhù)大量涉及內存管理的教材和資料。因此我們這里所要寫(xiě)的Linux內存管理采取必重就輕的策略,從理論層面就不去板門(mén)弄斧,貽笑大方了。我們最想做的和可能做到的是以開(kāi)發(fā)者的角度談?wù)剬却婀芾淼睦斫?,最終目的是把我們在內核開(kāi)發(fā)中使用內存的經(jīng)驗和對Linux內存管理的認識與大家共享。

當然這其中我們也會(huì )設計一些諸如段頁(yè)等內存管理的基本理論,但我們目的不是為了強調理論,而是為了指導理解開(kāi)發(fā)中的實(shí)踐,所以?xún)H僅點(diǎn)到為止,不做深究。

遵循“理論來(lái)源于實(shí)踐”的“教條”,我們先不必一下子就鉆入內核里去看系統內存到底是如何管理,那樣往往會(huì )讓你陷入似懂非懂的窘境(我當年就犯了這個(gè)錯誤?。?。所以最好的方式是先從外部(用戶(hù)編程范疇)來(lái)觀(guān)察進(jìn)程如何使用內存,等到對大家內存使用有了較直觀(guān)的認識后,再深入到內核中去學(xué)習內存如何被管理等理論知識。最后再通過(guò)一個(gè)實(shí)例編程將所講內容融會(huì )貫通。

進(jìn)程與內存

進(jìn)程如何使用內存?

毫無(wú)疑問(wèn)所有進(jìn)程(執行的程序)都必須占用一定數量的內存,它或是用來(lái)存放從磁盤(pán)載入的程序代碼,或是存放取自用戶(hù)輸入的數據等等。不過(guò)進(jìn)程對這些內存的管理方式因內存用途不一而不盡相同,有些內存是事先靜態(tài)分配和統一回收的,而有些卻是按需要動(dòng)態(tài)分配和回收的。

對任何一個(gè)普通進(jìn)程來(lái)講,它都會(huì )涉及到5種不同的數據段。稍有編程知識的朋友都該能想到這幾個(gè)數據段種包含有“程序代碼段”、“程序數據段”、“程序堆棧段”等。不錯,這幾種數據段都在其中,但除了以上幾種數據段之外,進(jìn)程還另外包含兩種數據段。下面我們來(lái)簡(jiǎn)單歸納一下進(jìn)程對應的內存空間中所包含的5種不同的數據區。

代碼段:代碼段是用來(lái)存放可執行文件的操作指令,也就是說(shuō)是它是可執行程序在內存種的鏡像。代碼段需要防止在運行時(shí)被非法修改,所以只準許讀取操作,而不允許寫(xiě)入(修改)操作——它是不可寫(xiě)的。

數據段:數據段用來(lái)存放可執行文件中已初始化全局變量,換句話(huà)說(shuō)就是存放程序靜態(tài)分配[1]的變量和全局變量。

BSS[2]BSS段包含了程序中未初始化全局變量,在內存中 bss段全部置零。

堆(heap:堆是用于存放進(jìn)程運行中被動(dòng)態(tài)分配的內存段,它大小并不固定,可動(dòng)態(tài)擴張或縮減。當進(jìn)程調用malloc等函數分配內存時(shí),新分配的內存就被動(dòng)態(tài)添加到堆上(堆被擴張);當利用free等函數釋放內存時(shí),被釋放的內存從堆中被剔除(堆被縮減)

:棧是用戶(hù)存放程序臨時(shí)創(chuàng )建的局部變量,也就是說(shuō)我們函數括弧“{}”中定義的變量(但不包括static聲明的變量,static意味這在數據段中存放變量)。除此以外在函數被調用時(shí),其參數也會(huì )被壓入發(fā)起調用的進(jìn)程棧中,并且待到調用結束后,函數的返回值也回被存放回棧中。由于棧的先進(jìn)先出特點(diǎn),所以棧特別方便用來(lái)保存/恢復調用現場(chǎng)。從這個(gè)意義上將我們可以把堆??闯梢粋€(gè)臨時(shí)數據寄存、交換的內存區。

進(jìn)程如何組織這些區域?

上述幾種內存區域中數據段、BSS和堆通常是被連續存儲的——內存位置上是連續的,而代碼段和棧往往會(huì )被獨立存放。有趣的是堆和棧兩個(gè)區域關(guān)系很“曖昧”,他們一個(gè)向下“長(cháng)”(i386體系結構中棧向下、堆向上),一個(gè)向上“長(cháng)”,相對而生。但你不必擔心他們會(huì )碰頭,因為他們之間間隔很大(到底大到多少,你可以從下面的例子程序計算一下),絕少有機會(huì )能碰到一起。

下圖簡(jiǎn)要描述了進(jìn)程內存區域的分布:

數據段

BSS

代碼段

“事實(shí)勝于雄辯”,我們用一個(gè)小例子(原形取自《User-Level Memory Management)來(lái)展示上面所講的各種內存區的差別與位置。

#include<stdio.h>

#include<malloc.h>

#include<unistd.h>

int bss_var;

int data_var0=1;

int main(int argc,char **argv)

{

  printf("below are addresses of types of process‘s mem\n");

  printf("Text location:\n");

  printf("\tAddress of main(Code Segment):%p\n",main);

  printf("____________________________\n");

  int stack_var0=2;

  printf("Stack Location:\n");

  printf("\tInitial end of stack:%p\n",&stack_var0);

  int stack_var1=3;

  printf("\tnew end of stack:%p\n",&stack_var1);

  printf("____________________________\n");

  printf("Data Location:\n");

  printf("\tAddress of data_var(Data Segment):%p\n",&data_var0);

  static int data_var1=4;

  printf("\tNew end of data_var(Data Segment):%p\n",&data_var1);

  printf("____________________________\n");

  printf("BSS Location:\n");

  printf("\tAddress of bss_var:%p\n",&bss_var);

  printf("____________________________\n");

  char *b = sbrk((ptrdiff_t)0);

  printf("Heap Location:\n");

  printf("\tInitial end of heap:%p\n",b);

  brk(b+4);

  b=sbrk((ptrdiff_t)0);

  printf("\tNew end of heap:%p\n",b);

return 0;

 }

它的結果如下

below are addresses of types of process‘s mem

Text location:

   Address of main(Code Segment):0x8048388

____________________________

Stack Location:

   Initial end of stack:0xbffffab4

   new end of stack:0xbffffab0

____________________________

Data Location:

   Address of data_var(Data Segment):0x8049758

   New end of data_var(Data Segment):0x804975c

____________________________

BSS Location:

   Address of bss_var:0x8049864

____________________________

Heap Location:

   Initial end of heap:0x8049868

   New end of heap:0x804986c

利用size命令也可以看到程序的各段大小,比如執行size example會(huì )得到

text data bss dec hex filename

1654 280   8 1942 796 example

但這些數據是程序編譯的靜態(tài)統計,而上面顯示的是進(jìn)程運行時(shí)動(dòng)態(tài)值,但兩者是對應的。

 

從前面的例子,我們對進(jìn)程使用的邏輯內存分布已經(jīng)先睹為快。這部分我們就繼續進(jìn)入操作系統內核看看進(jìn)程對內存具體是如何進(jìn)行分配和管理的。

從用戶(hù)向內核看,所使用的內存表象形式會(huì )依次經(jīng)歷“邏輯地址”——“線(xiàn)形地址”——“物理地址”幾種形式(關(guān)于幾種地址的解釋在前面已經(jīng)講述了)。邏輯地址經(jīng)段機制轉化成線(xiàn)性地址;線(xiàn)性地址又經(jīng)過(guò)頁(yè)機制轉化為物理地址。(但是我們要知道Linux系統雖然保留了段機制,但是將所有程序的段地址都定死為0-4G,所以雖然邏輯地址和線(xiàn)性地址是兩種不同的地址空間,但在Linux中邏輯地址就等于線(xiàn)性地址,它們的值是一樣的)。沿著(zhù)這條線(xiàn)索,我們所研究的主要問(wèn)題也就集中在下面幾個(gè)問(wèn)題。

1.         進(jìn)程空間地址如何管理?

2.         進(jìn)程地址如何映射到物理內存?

3.         物理內存如何被管理?

以及由上述問(wèn)題引發(fā)的一些子問(wèn)題。如系統虛擬地址分布;內存分配接口;連續內存分配與非連續內存分配等。

 

進(jìn)程內存空間

Linux操作系統采用虛擬內存管理技術(shù),使得每個(gè)進(jìn)程都有各自互不干涉的進(jìn)程地址空間。該空間是塊大小為4G的線(xiàn)性虛擬空間,用戶(hù)所看到和接觸的都是該虛擬地址,無(wú)法看到實(shí)際的物理內存地址。利用這種虛擬地址不但能起到保護操作系統的效果(用戶(hù)不能直接訪(fǎng)問(wèn)物理內存),而且更重要的是用戶(hù)程序可使用比實(shí)際物理內存更大的地址空間(具體的原因請看硬件基礎部分)。

在討論進(jìn)程空間細節前,請大家這里先要澄清下面幾個(gè)問(wèn)題。

l         第一、4G的進(jìn)程地址空間被人為的分為兩個(gè)部分——用戶(hù)空間與內核空間。用戶(hù)空間從03G0xC0000000),內核空間占據3G4G。用戶(hù)進(jìn)程通常情況下只能訪(fǎng)問(wèn)用戶(hù)空間的虛擬地址,不能訪(fǎng)問(wèn)內核空間虛擬地址。例外情況只有用戶(hù)進(jìn)程進(jìn)行系統調用(代表用戶(hù)進(jìn)程在內核態(tài)執行)等時(shí)刻可以訪(fǎng)問(wèn)到內核空間。

l         第二、用戶(hù)空間對應進(jìn)程,所以每當進(jìn)程切換,用戶(hù)空間就會(huì )跟著(zhù)變化;而內核空間是由內核負責映射,它并不會(huì )跟著(zhù)進(jìn)程改變,是固定的。內核空間地址有自己對應的頁(yè)表(init_mm.pgd),用戶(hù)進(jìn)程各自有不同的頁(yè)表(。

l         第三、每個(gè)進(jìn)程的用戶(hù)空間都是完全獨立、互不相干的。不信的話(huà),你可以把上面的程序同時(shí)運行10次(當然為了同時(shí)運行,讓它們在返回前一同睡眠100秒吧),你會(huì )看到10個(gè)進(jìn)程占用的線(xiàn)性地址一模一樣。

 

進(jìn)程內存管理

進(jìn)程內存管理的對象是進(jìn)程線(xiàn)性地址空間上的內存鏡像,這些內存鏡像其實(shí)就是進(jìn)程使用的虛擬內存區域(memory region)。進(jìn)程虛擬空間是個(gè)3264位的“平坦”(獨立的連續區間)地址空間(空間的具體大小取決于體系結構)。要統一管理這么大的平坦空間可絕非易事,為了方便管理,虛擬空間被化分為許多大小可變的(但必須是4096的倍數)內存區域,這些區域在進(jìn)程線(xiàn)性地址中像停車(chē)位一樣有序排列。這些區域的劃分原則是“將訪(fǎng)問(wèn)屬性一致的地址空間存放在一起”,所謂訪(fǎng)問(wèn)屬性在這里無(wú)非指的是“可讀、可寫(xiě)、可執行等”。

如果你要查看某個(gè)進(jìn)程占用的內存區域,可以使用命令cat /proc/<pid>/maps獲得(pid是進(jìn)程號,你可以運行上面我們給出的例子——./example &;pid便會(huì )打印到屏幕),你可以發(fā)現很多類(lèi)似于下面的數字信息。

由于程序example使用了動(dòng)態(tài)庫,所以除了example本身使用的的內存區域外,還會(huì )包含那些動(dòng)態(tài)庫使用的內存區域(區域順序是:代碼段、數據段、bss段)。

我們下面只抽出和example有關(guān)的信息,除了前兩行代表的代碼段和數據段外,最后一行是進(jìn)程使用的??臻g。

-------------------------------------------------------------------------------

08048000 - 08049000 r-xp 00000000 03:03 439029                               /home/mm/src/example

08049000 - 0804a000 rw-p 00000000 03:03 439029                               /home/mm/src/example

……………

bfffe000 - c0000000 rwxp ffff000 00:00 0

----------------------------------------------------------------------------------------------------------------------

每行數據格式如下:

(內存區域)開(kāi)始-結束訪(fǎng)問(wèn)權限  偏移  主設備號:次設備號 i節點(diǎn)  文件。

注意,你一定會(huì )發(fā)現進(jìn)程空間只包含三個(gè)內存區域,似乎沒(méi)有上面所提到的堆、bss等,其實(shí)并非如此,程序內存段和進(jìn)程地址空間中的內存區域是種模糊對應,也就是說(shuō),堆、bss、數據段(初始化過(guò)的)都在進(jìn)程空間種由數據段內存區域表示。

 

Linux內核中對應進(jìn)程內存區域的數據結構是: vm_area_struct, 內核將每個(gè)內存區域作為一個(gè)單獨的內存對象管理,相應的操作也都一致。采用面向對象方法使VMA結構體可以代表多種類(lèi)型的內存區域--比如內存映射文件或進(jìn)程的用戶(hù)空間棧等,對這些區域的操作也都不盡相同。

vm_area_strcut結構比較復雜,關(guān)于它的詳細結構請參閱相關(guān)資料。我們這里只對它的組織方法做一點(diǎn)補充說(shuō)明。vm_area_struct是描述進(jìn)程地址空間的基本管理單元,對于一個(gè)進(jìn)程來(lái)說(shuō)往往需要多個(gè)內存區域來(lái)描述它的虛擬空間,如何關(guān)聯(lián)這些不同的內存區域呢?大家可能都會(huì )想到使用鏈表,的確vm_area_struct結構確實(shí)是已鏈表形式鏈接,不過(guò)位了方便查找,內核又以紅黑樹(shù)(以前的內核使用平衡樹(shù))的形式組織內存區域,以便降低搜索耗時(shí)。并存兩種組織形式,并非冗余:鏈表用于需要遍歷全部節點(diǎn)的時(shí)候用,而紅黑樹(shù)適用于在地址空間中定位特定內存區域的時(shí)候。內核為了內存區域上的各種不同操作都能獲得高性能,所以同時(shí)使用了這兩種數據結構。

下圖反映了進(jìn)程地址空間的管理模型:

mmap

進(jìn)程內存描述符

 

Vm_area_struct

進(jìn)程虛擬地址

進(jìn)程的地址空間對應的描述結構是“內存描述符結構,它表示進(jìn)程的全部地址空間,——包含了和進(jìn)程地址空間有關(guān)的全部信息,其中當然包含進(jìn)程的內存區域。

進(jìn)程內存的分配與回收

創(chuàng )建進(jìn)程fork()、程序載入execve()、映射文件mmap()、動(dòng)態(tài)內存分配malloc()/brk()等進(jìn)程相關(guān)操作都需要分配內存給進(jìn)程。不過(guò)這時(shí)進(jìn)程申請和獲得的還不是實(shí)際內存,而是虛擬內存,準確的說(shuō)是“內存區域”。進(jìn)程對內存區域的分配最終多會(huì )歸結到do_mmap()函數上來(lái)(brk調用被單獨以系統調用實(shí)現,不用do_mmap()),

內核使用do_mmap()函數創(chuàng )建一個(gè)新的線(xiàn)性地址區間。但是說(shuō)該函數創(chuàng )建了一個(gè)新VMA并不非常準確,因為如果創(chuàng )建的地址區間和一個(gè)已經(jīng)存在的地址區間相鄰,并且它們具有相同的訪(fǎng)問(wèn)權限的話(huà),那么兩個(gè)區間將合并為一個(gè)。如果不能合并,那么就確實(shí)需要創(chuàng )建一個(gè)新的VMA了。但無(wú)論哪種情況, do_mmap()函數都會(huì )將一個(gè)地址區間加入到進(jìn)程的地址空間中--無(wú)論是擴展已存在的內存區域還是創(chuàng )建一個(gè)新的區域。

同樣釋放一個(gè)內存區域使用函數do_ummap(),它會(huì )銷(xiāo)毀對應的內存區域。

如何由虛變實(shí)!

    從上面已經(jīng)看到進(jìn)程所能直接操作的地址都為虛擬地址。當進(jìn)程需要內存時(shí),從內核獲得的僅僅時(shí)虛擬的內存區域,而不是實(shí)際的物理地址,進(jìn)程并沒(méi)有獲得物理內存(物理頁(yè)框——頁(yè)的概念請大家參與硬件基礎一章),獲得的僅僅是對一個(gè)新的線(xiàn)性地址區間的使用權。實(shí)際的物理內存只有當進(jìn)程真的去訪(fǎng)問(wèn)新獲取的虛擬地址時(shí),才會(huì )由“請頁(yè)機制”產(chǎn)生“缺頁(yè)”異常,從而進(jìn)入分配實(shí)際頁(yè)框的例程。

該異常是虛擬內存機制賴(lài)以存在的基本保證——它會(huì )告訴內核去真正為進(jìn)程分配物理頁(yè),并建立對應的頁(yè)表,這之后虛擬地址才實(shí)實(shí)在在映射到了系統物理內存上。(當然如果頁(yè)被換出到磁盤(pán),也會(huì )產(chǎn)生缺頁(yè)異常,不過(guò)這時(shí)不用再建立頁(yè)表了)

這種請頁(yè)機制把頁(yè)框的分配推遲到不能再推遲為止,并不急于把所有的事情都一次做完(這中思想由點(diǎn)想涉及模式中的代理模式(proxy))。之所以能這么做是利用了內存訪(fǎng)問(wèn)的“局部性原理”,請頁(yè)帶來(lái)的好處是節約了空閑內存,提高了系統吞吐。要想更清楚的了解請頁(yè),可以看看《深入理解linux內核》一書(shū)。

這里我們需要說(shuō)明在內存區域結構上的nopage操作,該操作是當發(fā)生訪(fǎng)問(wèn)的進(jìn)程虛擬內存而發(fā)現并未真正分配頁(yè)框時(shí),該方法變被調用來(lái)分配實(shí)際的物理頁(yè),并為該頁(yè)建立頁(yè)表項。在最后的例子中我們會(huì )演示如何使用該方法。

 

 

系統物理內存管理 

雖然應用程序操作的對象是映射到物理內存之上的虛擬內存,但是處理器直接操作的卻是物理內存。所以當用程序訪(fǎng)問(wèn)一個(gè)虛擬地址時(shí),首先必須將虛擬地址轉化成物理地址,然后處理器才能解析地址訪(fǎng)問(wèn)請求。地址的轉換工作需要通過(guò)查詢(xún)頁(yè)表才能完成,概括的講,地址轉換需要將虛擬地址分段,使每段虛地址都作為一個(gè)索引指向頁(yè)表,而頁(yè)表項則指向下一級別的頁(yè)表或者指向最終的物理頁(yè)面。

每個(gè)進(jìn)程都有自己的頁(yè)表。進(jìn)程描述符號的pgd域指向的就是進(jìn)程的頁(yè)全局目錄。席面我們借用《linux設備驅動(dòng)程序》中的一幅圖大致看看進(jìn)程地址空間到物理頁(yè)之間的轉換關(guān)系。

 

 

     上面的過(guò)程說(shuō)起簡(jiǎn)單,做起難呀。因為在虛擬地址映射到頁(yè)之前必須先分配物理頁(yè)——也就是說(shuō)必須先從內核獲取空閑頁(yè),并建立頁(yè)表。下面我們介紹一下內核管理物理內存的機制。

 

物理內存管理(頁(yè)管理)

Linux內核管理物理內存是通過(guò)分頁(yè)機制實(shí)現的,它將整個(gè)內存劃分成無(wú)數4k(i386體系結構中)大小頁(yè),從而分配和回收內存的基本單位便是內存頁(yè)了。利用分頁(yè)管理有助于靈活分配內存地址,因為分配時(shí)不必要求必須有大塊的連續內存[3],系統可以東一頁(yè)、西一頁(yè)的湊出所需要的內存供進(jìn)程使用。雖然如此,但是實(shí)際上系統使用內存還是傾向于分配連續的內存塊,因為分配連續內存時(shí),頁(yè)表不需要更改,因此能降低TLB的刷新率(頻繁刷新會(huì )很大增加訪(fǎng)問(wèn)速度)。

鑒于上述需求,內核分配物理頁(yè)為了盡量減少不連續情況,采用了“伙伴”關(guān)系來(lái)管理空閑頁(yè)框?;锇殛P(guān)系分配算法大家不應陌生——幾乎所有操作系統書(shū)都會(huì )提到,我們不去詳細說(shuō)它了,如果不明白可以參看有關(guān)資料。這里只需要大家明白Linux中空閑頁(yè)面的組織和管理利用了伙伴關(guān)系,因此空閑頁(yè)面分配時(shí)也需要遵循伙伴關(guān)系,最小單位只能是2的冪倍頁(yè)面大小。內核中分配空閑頁(yè)框的基本函數是get_free_page/get_free_pages,它們或是分配單頁(yè)或是分配指定的頁(yè)框(2、4、8…512頁(yè))。

 注意:get_free_page是在內核中分配內存,不同于malloc在用戶(hù)空間中分配,malloc利用堆動(dòng)態(tài)分配,實(shí)際上是調用brk()系統調用,該調用的作用是擴大或縮小進(jìn)程堆空間(它會(huì )修改進(jìn)程的brk域)。如果現有的內存區域不夠容納堆空間,則會(huì )以頁(yè)面大小的倍數位單位,擴張或收縮對應的內存區域,但brk值并非以頁(yè)面大小為倍數修改,而是按實(shí)際請求修改。因此Malloc在用戶(hù)空間分配內存可以以字節為單位分配,但內核在內部仍然會(huì )是以頁(yè)為單位分配的。

   另外需要提及的是,物理頁(yè)在系統中由頁(yè)框結構struct paga描述,系統中所有的頁(yè)框存儲在數組mem_map[]中,可以通過(guò)該數組找到系統中的每一頁(yè)(空閑或非空閑)。而其中的空閑頁(yè)框則可由上述提到的以伙伴關(guān)系組織的空閑頁(yè)鏈表(free_area[MAX_ORDER]索引。

 

空閑頁(yè)框

APP

內存區域 vm_area_structs

malloc、fork、excute、mmap

brk/do_map

get_free_page(s)

用戶(hù)空間

內核空間

進(jìn)程虛擬地址空間

 

系統調用

進(jìn)程頁(yè)表

 

請頁(yè)異常

內核內存使用

Slab

    所謂尺有所長(cháng),寸有所短。以頁(yè)為最小單位分配內存對于內核管理系統物理內存來(lái)說(shuō)的確比較方便,但內核自身最常使用的內存卻往往是很?。ㄟh遠小于一頁(yè))的內存塊——比如存放文件描述符、進(jìn)程描述符、虛擬內存區域描述符等行為所需的內存都不足一頁(yè)。這些用來(lái)存放描述符的內存相比頁(yè)面而言,就好比是面包屑與面包。一個(gè)整頁(yè)中可以聚集多個(gè)這種這些小塊內存;而且這些小塊內存塊也和面包屑一樣頻繁地生成/銷(xiāo)毀。

  為了滿(mǎn)足內核對這種小內存塊的需要,Linux系統采用了一種被稱(chēng)為slab分配器的技術(shù)。Slab分配器的實(shí)現相當復雜,但原理不難,其核心思想就是“存儲池[4]的運用。內存片段(小塊內存)被看作對象,當被使用完后,并不直接釋放而是被緩存到“存儲池”里,留做下次使用,這無(wú)疑避免了頻繁創(chuàng )建與銷(xiāo)毀對象所帶來(lái)的額外負載。

Slab技術(shù)不但避免了內存內部分片(下文將解釋?zhuān)?lái)的不便(引入Slab分配器的主要目的是為了減少對伙伴系統分配算法的調用次數——頻繁分配和回收必然會(huì )導致內存碎片——難以找到大塊連續的可用內存,而且可以很好利用硬件緩存提高訪(fǎng)問(wèn)速度。

    Slab并非是脫離伙伴關(guān)系而獨立存在的一種內存分配方式,slab仍然是建立在頁(yè)面基礎之上,換句話(huà)說(shuō),Slab將頁(yè)面(來(lái)自于伙伴關(guān)系管理的空閑頁(yè)框鏈)撕碎成眾多小內存塊以供分配,slab中的對象分配和銷(xiāo)毀使用kmem_cache_allockmem_cache_free。

 

Kmalloc

Slab分配器不僅僅只用來(lái)存放內核專(zhuān)用的結構體,它還被用來(lái)處理內核對小塊內存的請求。當然鑒于Slab分配器的特點(diǎn),一般來(lái)說(shuō)內核程序中對小于一頁(yè)的小塊內存的求情才通過(guò)Slab分配器提供的接口Kmalloc來(lái)完成(雖然它可分配32 131072字節的內存)。從內核內存分配角度講kmalloc可被看成是get_free_pages)的一個(gè)有效補充,內存分配粒度更靈活了。

有興趣的話(huà)可以到/proc/slabinfo中找到內核執行現場(chǎng)使用的各種slab信息統計,其中你會(huì )看到系統中所有slab的使用信息。從信息中可以看到系統中除了專(zhuān)用結構體使用的slab外,還存在大量為Kmalloc而準備的Slab(其中有些為dma準備的)。

 

 

內核非連續內存分配(Vmalloc

 

伙伴關(guān)系也好、slab技術(shù)也好,從內存管理理論角度而言目的基本是一致的,它們都是為了防止“分片”,不過(guò)分片又分為外部分片和內部分片之說(shuō),所謂內部分片是說(shuō)系統為了滿(mǎn)足一小段內存區(連續)的需要,不得不分配了一大區域連續內存給它,從而造成了空間浪費;外部分片是指系統雖有足夠的內存,但卻是分散的碎片,無(wú)法滿(mǎn)足對大塊“連續內存”的需求。無(wú)論何種分片都是系統有效利用內存的障礙。slab分配器使得含與一個(gè)頁(yè)面內眾多小塊內存可獨立被分配使用,避免了內部分片,節約了空閑內存?;锇殛P(guān)系把內存塊按大小分組管理,一定程度上減輕了外部分片的危害,因為頁(yè)框分配不在盲目,而是按照大小依次有序進(jìn)行,不過(guò)伙伴關(guān)系只是減輕了外部分片,但并未徹底消除。你自己筆畫(huà)一下多次分配頁(yè)框后,空閑內存的剩余情況吧。

所以避免外部分片的最終思路還是落到了如何利用不連續的內存塊組合成“看起來(lái)很大的內存塊”——這里的情況很類(lèi)似于用戶(hù)空間分配虛擬內存,內存邏輯上連續,其實(shí)影射到并不一定連續的物理內存上。Linux內核借用了這個(gè)技術(shù),允許內核程序在內核地址空間中分配虛擬地址,同樣也利用頁(yè)表(內核頁(yè)表)將虛擬地址影射到分散的內存頁(yè)上。以此完美地解決了內核內存使用中的外部分片問(wèn)題。內核提供vmalloc函數分配內核虛擬內存,該函數不同于kmalloc,它可以分配較Kmalloc大得多的內存空間(可遠大于128K,但必須是頁(yè)大小的倍數),但相比Kmalloc來(lái)說(shuō)Vmalloc需要對內核虛擬地址進(jìn)行重影射,必須更新內核頁(yè)表,因此分配效率上要低一些(用空間換時(shí)間)

與用戶(hù)進(jìn)程相似內核也有一個(gè)名為init_mmmm_strcut結構來(lái)描述內核地址空間,其中頁(yè)表項pdg=swapper_pg_dir包含了系統內核空間(3G-4G)的映射關(guān)系。因此vmalloc分配內核虛擬地址必須更新內核頁(yè)表,而kmallocget_free_page由于分配的連續內存,所以不需要更新內核頁(yè)表。

 

空閑頁(yè)框

APP

內存區域 vm_area_structs

malloc、fork、excute、mmap

brk/do_map

get_free_page(s)

用戶(hù)空間

內核空間

進(jìn)程虛擬地址空間

 

系統調用

進(jìn)程頁(yè)表

 

請頁(yè)異常

內核程序

物理內存影射區

Vmalloc分配區

slab

get_free_page(s)

內核頁(yè)表

get_free_page(s)

請頁(yè)異常

 

 

vmalloc分配的內核虛擬內存與kmalloc/get_free_page分配的內核虛擬內存位于不同的區間,不會(huì )重疊。因為內核虛擬空間被分區管理,各司其職。進(jìn)程空間地址分布從0到G(其實(shí)是到PAGE_OFFSET,0x86中它等于0xC0000000),從3Gvmalloc_start這段地址是物理內存映射區域(該區域中包含了內核鏡像、物理頁(yè)框表mem_map等等)比如我使用的系統內存是64M(可以用free看到),那么(3G——3G+64M)這片內存就應該映射物理內存,而vmalloc_start位置應在3G+64M附近(說(shuō)附近因為是在物理內存映射區與vmalloc_start期間還回存在一個(gè)8M大小的gap來(lái)防止躍界),vmalloc_end的位置接近4G(說(shuō)接近是因為最后位置系統會(huì )保留一片128k大小的區域用于專(zhuān)用頁(yè)面映射,還由可能會(huì )由高端內存映射區,這些都是細節,這里我們不做糾纏)。

 

 

 

進(jìn)程地址空間

物理內存映射區

 

3G

內核虛擬空間

Vmalloc_start

Vmalloc_end

上圖是內存分布的模糊輪廓

 

   get_free_pageKmalloc函數所分配的連續內存都陷于物理映射區域,所以它們返回的內核虛擬地址和實(shí)際物理地址僅僅是相差一個(gè)偏移量(PAGE_OFFSET),你可以很方便的將其轉化為物理內存地址,同時(shí)內核也提供了virt_to_phys()函數將內核虛擬空間中的物理影射區地址轉化為物理地址。要知道,物理內存映射區中的地址與內核頁(yè)表是有序對應,系統中的每個(gè)物理頁(yè)框都可以找到它對應的內核虛擬地址(在物理內存映射區中的)。

vmalloc分配的地址則限于vmalloc_startvmalloc_end之間。每一塊vmalloc分配的內核虛擬內存都對應一個(gè)vm_struct結構體(可別和vm_area_struct搞混,那可是進(jìn)程虛擬內存區域的結構),不同的內核虛擬地址被4k打大小空閑區的間隔,以防止越界——見(jiàn)下圖)。與進(jìn)程虛擬地址的特性一樣,這些虛擬地址可與物理內存沒(méi)有簡(jiǎn)單的位移關(guān)系,必須通過(guò)內核頁(yè)表才可轉換為物理地址或物理頁(yè)。它們有可能尚未被映射,在發(fā)生缺頁(yè)時(shí)才真正分配物理頁(yè)框。

 

這里給出一個(gè)小程序幫助大家認請上面幾種分配函數所對應的區域。

#include<linux/module.h>

#include<linux/slab.h>

#include<linux/vmalloc.h>

unsigned char *pagemem;

unsigned char *kmallocmem;

unsigned char *vmallocmem;

int init_module(void)

{

 pagemem = get_free_page(0);

 printk("<1>pagemem=%s",pagemem);

 kmallocmem = kmalloc(100,0);

 printk("<1>kmallocmem=%s",kmallocmem);

 vmallocmem = vmalloc(1000000);

 printk("<1>vmallocmem=%s",vmallocmem);

}

void cleanup_module(void)

{

 free_page(pagemem);

 kfree(kmallocmem);

 vfree(vmallocmem);

}

 

內存管理實(shí)例

代碼功能介紹

我們希望能通過(guò)訪(fǎng)問(wèn)用戶(hù)空間的內存達到讀取內核數據的目的,這樣便可進(jìn)行內核空間到用戶(hù)空間的大規模信息傳輸。

具體的講,我們要利用內存映射功能,將系統內核中的一部分虛擬內存映射到用戶(hù)空間,從而使得用戶(hù)空間地址等同與被映射的內核內存地址。

代碼結構體系介紹

內核空間內存分配介紹

因此我們將試圖寫(xiě)一個(gè)虛擬字符設備驅動(dòng)程序,通過(guò)它將系統內核空間映射到用戶(hù)空間——將內核虛擬內存映射到用戶(hù)虛擬地址。當然映射地址時(shí)少不了定位內核空間對應的物理地址,并且還要建立新的用戶(hù)頁(yè)表項,以便用戶(hù)進(jìn)程尋址時(shí)能找到對應的物理內存。

從中應該看出,需要我完成既定目標,我們需要獲得:被映射內核空間物理地址 建立對應的用戶(hù)進(jìn)程頁(yè)表。

在內核空間中主要存在kmalloc分配的物理連續空間和vmalloc分配的非物理連續空間。kmalloc分配的空間往往被稱(chēng)為內核邏輯地址,由于它是連續分配(直接處理物理頁(yè)框),而且分配首地址一定,所以其分配的內核虛擬地址對應的實(shí)際物理地址很容易獲得:內核虛擬地址—PAGE_OFFSET0xC0000000)(內核有對應例程virt_to_phys)即等于物理地址,而且其對應的頁(yè)表屬于內核頁(yè)表(swapper_pg_dir——在系統初始化時(shí)就以建立,因此省去了建立頁(yè)表的工作。

vmalloc分配的空間被稱(chēng)為內核虛擬地址,它的問(wèn)題相對要復雜些,這是因為其分配的內核虛擬內存空間并非直接操作頁(yè)框,而是分配的是vm_struct結構。該結構邏輯上連續但對應的物理內存并非連續,也就是說(shuō)它vamlloc分配的內核空間地址所對應的物理地址并非可通過(guò)簡(jiǎn)單線(xiàn)性運算獲得。從這個(gè)意義上講,它的物理地址在分配前是不確定的,因此雖然vmalloc分配的空間與kmalloc一樣都是由內核頁(yè)表來(lái)映射的,但vmalloc分配內核虛擬地址時(shí)必須更新內核頁(yè)表。

 

注釋?zhuān)?/span>vmalloc分配的內核虛擬內存與kmalloc/get_free_page分配的內核邏輯內存位于不同的區間,不會(huì )重疊。因為內核空間被分區管理,各司其職。進(jìn)程空間地址分布從0到G(其實(shí)是到PAGE_OFFSET,0x86中它等于0xC0000000),從3Gvmalloc_start這段地址是物理內存映射區域(該區域中包含了內核鏡像、物理頁(yè)框表mem_map等等)比如我使用的系統內存是64M(可以用free看到),那么(3G——3G+64M)這片內存就應該映射物理內存,而vmalloc_start位置應在3G+64M附近(說(shuō)附近因為是在物理內存映射區與vmalloc_start期間還回存在一個(gè)8M大小的gap來(lái)防止躍界),vmalloc_end的位置接近4G(說(shuō)接近是因為最后位置系統會(huì )保留一片128k大小的區域用于專(zhuān)用頁(yè)面映射,還由可能會(huì )由高端內存映射區,這些都是細節,這里我們不做糾纏)。

       另一個(gè)需要澄清的是,vmalloc分配的內核空間,其結構是vm_area,可千萬(wàn)別與用戶(hù)空間malloc分配的vm_area_struct結構混淆。前者由內核頁(yè)表映射,而后者則由用戶(hù)頁(yè)表映射。

 

 

進(jìn)程地址空間

 

物理內存映射區kmalloc分配

 

 Vmalloc 分配區

 

 

3Gpage_offset

 

內核虛擬空間

 

Vmalloc_start

 

Vmalloc_end

 

上圖是內存分布的模糊輪廓

 

實(shí)例藍圖

為了近可能豐富我們的例子程序的場(chǎng)景,我們選擇映射vmalloc分配的內核虛擬空間(下面我們簡(jiǎn)稱(chēng)為vk地址)到用戶(hù)空間。

要知道用戶(hù)進(jìn)程操作的是虛擬內存區域vm_area_struct,我們此刻需要將用戶(hù)vma區間利用用戶(hù)頁(yè)表映射到vk對應的物理內存上去(如下圖所示)。這里主要工作便是建立用戶(hù)也表項完成映射工作,而這個(gè)工作完全落在了vma->nopage[5]操作上,該方法會(huì )幫助我們在發(fā)生“缺頁(yè)”時(shí),動(dòng)態(tài)構造映射所需物理內存的頁(yè)表項。

 

 

用戶(hù)虛擬空間Vm_area_struct

Vk空間vm_struct

物理內存

Vma->nopage

 

 

 

 

 

 

 

 

 

 

 

 

 

 


我們需要實(shí)現nopage方法,動(dòng)態(tài)建立對應頁(yè)表,而在該方法中核心任務(wù)是尋找到vk地址對應的內核邏輯地址[6]。這必然需要我們做以下工作:

a)         找到vmalloc虛擬內存對應的內核頁(yè)表,并尋找到對應的內核頁(yè)表項。

b)        獲取內核頁(yè)表項對應的物理頁(yè)框指針。

c)        通過(guò)頁(yè)框得到對應的內核邏輯地址。

基本函數

我們實(shí)例將利用一個(gè)虛擬字符驅動(dòng)程序,驅動(dòng)負責將一定長(cháng)的內核虛擬地址(vmalloc分配的)映射到設備文件上,以便可以通過(guò)訪(fǎng)問(wèn)文件內容來(lái)達到訪(fǎng)問(wèn)內存的目的。這樣做的最大好處是提高了內存訪(fǎng)問(wèn)速度,并且可以利用文件系統的接口編程(設備在Linux中作為特殊文件處理)訪(fǎng)問(wèn)內存,降低了開(kāi)發(fā)難度。

 

 Map_driver.c就是我們的虛擬字符驅動(dòng)程序,不用說(shuō)它要實(shí)現文件操作表(file_operations——字符驅動(dòng)程序主要做的工作便是實(shí)現該結構中的,為了要完成內存映射,除了常規的open/release操作外,必須自己實(shí)現mmap操作,該函數將給定的文件映射到指定的地址空間上,也就是說(shuō)它將負責把vmalloc分配的內核地址映射到我們的設備文件上。

我們下面就談?wù)?/span>mmap操作的實(shí)現細節:

文件操作的mmap操作是在用戶(hù)進(jìn)行系統調用mmap[7]時(shí)被執行的,而且在調用前內核已經(jīng)給用戶(hù)找到并分配了合適的虛擬內存區域vm_area_struct,這個(gè)區域將代表文件內容,所以剩下要做的便是如何把虛擬區域和物理內存掛接到一起了,即構造頁(yè)表。由于我門(mén)前面所說(shuō)的原因,我們系統中頁(yè)表需要動(dòng)態(tài)分配,因此不可使用remap_page_range函數一次分配完成,而必須使用虛擬內存區域自帶的nopage方法,在現場(chǎng)構造頁(yè)表。這樣以來(lái),文件操作的mmap的方法只要完成“為它得到的虛擬內存區域綁定對應的操作表vm_operations”即可。于是主要的構造工作就落在了vm_operations中的nopage方法上了。

Nopage方法中核心內容上面已經(jīng)提到了是“尋找到vk地址對應的內核邏輯地址”,這個(gè)解析內核頁(yè)表的工作是需要自己編寫(xiě)輔助函數vaddr_to_kaddr來(lái)完成的,它所作的工作概括來(lái)講就是上文提到的a\b\c三條。

有關(guān)整個(gè)任務(wù)執行路徑請看下圖。

vm_area對應的內核邏輯地址

(page_address

獲得vm_area對應內核頁(yè)表項指針

(pte_offset)

得到內核邏輯地址對應的進(jìn)程頁(yè)框

(virt_to_page)

獲得vm_area對應的內核邏輯地址(vaddr_to_kaddr)

File->mma (mapdrv_mmap)

mmap系統調用

Vma->nopage(map_nopage)

獲得vm_area對應的內核頁(yè)表項

(pgd_offset_k

pmd_offset

pte_offset)

STEP BY STEP

編譯map_driver.cmap_driver.o模塊,具體參數見(jiàn)Makefile

加載模塊 insmod map_driver.o

生成對應的設備文件

1 /proc/devices下找到map_driver對應的設備命和設備號:grep mapdrv /proc/devices

2 建立設備文件mknod  mapfile c 254 0  (在我系統里設備號為254

    利用maptest讀取mapfile文件,將取自?xún)群说男畔ⅲ?/span>”ok”——我們在內核中在vmalloc分配的空間中填放的信息)打印到用戶(hù)屏幕。

 

全部程序下載 mmap.tar (感謝Martin Frey,該程序主體出自他的靈感)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
Linux內核--內核地址空間分布和進(jìn)程地址空間
Linux內存管理 下
Linux內存管理
Windows/Linux內核地址空間管理的異同
別再說(shuō)你不懂 Linux 內存管理了,10 張圖給你安排的明明白白
Linux高端內存映射
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久