Linux核心——記憶體管理

來源:互聯網
上載者:User

標籤:jsb   structs   關係   高效能   nbsp   ppi   cut   程式   hit   

記憶體管理頁

核心把物理頁作為記憶體管理的基本單位。記憶體管理單元(MMU,管理記憶體並把虛擬位址轉換為物理地址)通常以頁為單位進行處理。MMU以頁大小為單位來管理系統中的頁表。

從虛擬記憶體的角度看,頁就是最小單位。

32位系統:頁大小4KB

64位系統:頁大小8KB

在支援4KB頁大小並有1GB實體記憶體的機器上。實體記憶體會被劃分為262144個頁。

核心用 struct page 結構表示系統中的每一個物理頁。

struct page {

   page_flags_t flags;   /* 表示頁的狀態。每一位表示一種狀態*/

    atomic_t _count;       /* 存放頁的引用計數,0代表沒有被引用 */

    atomic_t _mapcount;

    unsigned long private;

    strcut address_space *mapping;

    pgoff_t index;

    struct list_head lru;

    void *virtual;    /* 頁在虛擬記憶體中的地址,動態映射物理頁 */

}

以下,我們來解釋下當中的重要欄位。

flags:這個欄位用於存放頁的狀態。這些狀態包含頁是不是髒的,是不是被鎖定在記憶體中等。 flag 的每一位單獨表示一種狀態。所以,它至少能夠同一時候表示出32種不同的狀態。

_count:這個欄位存放頁的使用計數,也就是這個頁被引用了多少次。非常奇怪。技術值變為 -1 時,就說明當前核心並沒有引用這一頁。於是,在新的分配中就能夠使用它,注意,這個欄位使用的是 -1 代表未使用,而不是 0 。

virtual:這個欄位是頁的虛擬位址。

mapping:這個域指向和這個頁關聯的address_space 對象。

private:這個依據名字就能夠看得出,它指向私人資料。

核心通過這種資料結構管理系統中全部的頁。由於核心須要知道一個頁是否空暇,誰有擁有這個頁。擁有者可能是:使用者空間進程、動態分配的核心資料、靜態核心代碼、頁快速緩衝等等。系統中每個物理頁都要分配這樣一個結構體,進行記憶體管理。

區

因為硬體的限制,核心並不能對全部的頁一視同仁。Linux必須處理例如以下兩種因為硬體存在缺陷而引起的記憶體定址問題:

    1)一些硬體僅僅能用某些特定的記憶體位址來運行DMA(直接記憶體訪問)。

    2)一些體繫結構其記憶體的物理定址範圍比虛擬定址範圍大得多。

這樣,就有一些記憶體不能永久地映射到核心空間上。

    因為存在這樣的限制,核心把具有相似特性的頁劃分為不同的區(ZONE):

    1)ZONE_DMA——這個區包括的頁能用來運行DMA操作。

2)ZONE_NORMAL——這個區包括的都是能正常地映射網頁。

3)ZONE_DMA32——同上,只是僅僅能被32位裝置訪問

4)ZONE_HIGHMEM——這個區包括“高端記憶體”,當中的頁並能不永久地映射到核心地址空間。

 Linux把系統的頁劃分為區,形成不同的記憶體池,這樣就能夠依據用途進行分配。

注意。區的劃分沒有不論什麼物理意義。這僅僅是核心為了管理頁而採取的一種邏輯上的分組。用於DMA的記憶體必須從ZONE_DMA中進行分配。可是一般用途的記憶體卻既能從ZONE_DMA分配,也能從ZONE_NORMAL分配。

獲得頁

核心提供了一種請求記憶體的底層機制,並提供了對它進行訪問的幾個介面。全部這些介面都以頁為單位分配記憶體。定義於<linux/gfp.h>。

最核心的函數是:

structpage *alloc_pages( unsigned int gfp_mask, unsigned int order );

該函數分配 2order 個連續的物理頁,並返回一個指向第一頁的 page 結構體指標,假設出錯就返回NULL。

void*page_address( struct page *page );

把給定的頁轉換成它的邏輯地址。假設無須用到 struct page。能夠調用:

unsignedlong __get_free_pages( unsigned int gfp_mask, unsigned int order );

這個函數與alloc_pages 作用同樣,只是它直接返回所請求的第一個頁的邏輯地址。由於頁是連續的,因此其它頁也會緊隨其後。

假設僅僅須要一頁,能夠用下面兩個函數:

structpage *alloc_page( unsigned int gfp_mask );

unsignedlong _get_free_page( unsigned int gfp_mask );

假設須要讓返回頁的內容全為0,能夠使用以下這個函數

unsignedlong get_zeroed_page(unsigned int gfp_mask );

方法

描寫敘述

alloc_page(gfp_mask)

僅僅分配一頁,返回指向頁結構的指標

alloc_pages(gfp_mask, order)

分配 2^order 個頁,返回指向第一頁頁結構的指標

__get_free_page(gfp_mask)

僅僅分配一頁,返回指向其邏輯地址的指標

__get_free_pages(gfp_mask, order)

分配 2^order 個頁,返回指向第一頁邏輯地址的指標

get_zeroed_page(gfp_mask)

僅僅分配一頁,讓其內容填充為0,返回指向其邏輯地址的指標

當不再須要頁時能夠使用下面函數來釋放它。

void__free_pages( struct page *page, unsigned int order );

voidfree_pages( unsigned long addr, unsigned int order );

voidfree_page( unsigned long addr );

釋放頁時要謹慎,僅僅能釋放屬於你的頁。傳遞了錯誤的 struct page 或地址,用了錯誤的 order 值都可能導致系統崩潰。請記住,核心是全然依賴自己的。

kmalloc()

kmalloc 與 malloc 一族函數很類似,僅僅只是它多了一個 flags 參數。kmalloc在<linux/slab.h>中聲明:

void*kmalloc( size_t size, int flags );

     這個函數返回一個指向記憶體塊的指標,其記憶體塊至少要有 size 大小。所分配的記憶體正在物理上是連續的。

在出錯時,它返回 NULL。除非沒有足夠的記憶體可用。否則核心總能分配成功。

在對 kmalloc 調用之後,你必須檢查返回的是不是 NULL,假設是,要適當地處理錯誤。

    在低級頁分配函數還是 kmalloc 中,都用到了gfp_mask(分配器標誌)。這些標誌可分為三類:行為修飾符、區修飾符及類型。

    1)行為修飾符表示核心應當怎樣分配所需的記憶體。在某些特定情況下,僅僅能使用某些特定的方法分配記憶體。

比如。中斷處理常式就要求核心在分配記憶體的過程中不能睡眠(由於中斷處理常式不能被又一次調度)。

    2)區修飾符指明究竟從哪一區中進行分配。

    3)類型標誌組合了行為修飾符和區修飾符。將各種可能用到的組合歸納為不同類型。簡化了修飾符的使用。

 kmalloc 的還有一端就是 kfree,kfree聲明於<linux/slab.h>中

voidkfree( constvoid *ptr );

kfree 函數釋放由 kmalloc分配出來的記憶體塊。

調用 kfree( NULL ) 是安全的。

vmalloc()

vmalloc 的工作方式是類似於 kmalloc。僅僅只是前者分配的記憶體虛擬位址是連續的,而物理地址則無需連續。這也是使用者空間分配函數的工作方式:由malloc()返回的頁在進程的虛擬位址空間內是連續的,可是這並不保證他們在物理RAM中也是連續的。kmalloc()函數確保頁在物理地址上是連續。vmalloc函數值確保在虛擬位址空間內是連續的。它通過分配非連續的實體記憶體塊,在修訂頁表,把記憶體映射到邏輯地址空間的連續地區中,就能做到這點。

大多數情況下,唯獨硬體裝置須要得到物理地址連續的記憶體,由於硬體裝置存在記憶體管理單元以外,它根本不理解什麼是虛擬位址。雖然只在某些情況下才須要物理上連續的記憶體塊,可是非常多核心都有kmalloc()來擷取記憶體。而不是vmalloc()。這主要出於效能方面的考慮。vmalloc()函數為了把物理上不連續的頁轉換成虛擬位址空間上連續的頁。必須專門建立頁表項。糟糕的是,通過vmalloc()獲得的頁必須一個一個地進行映射。由於這些原因,通常是在為了獲得大塊記憶體時。比如當模組被動態插入核心時。就把模組裝載到由vmalloc()分配的記憶體上。

void *vmalloc(unsigned long size)

該函數返回一個指標。指向邏輯上連續的一塊記憶體。其大小至少為size。在錯誤發生時。函數返回NULL。

函數可能睡眠,因此麼不能從中斷上下文中進行調用。也不能從其它不同意堵塞的情況下進行調用。

釋放通過vfree()函數

void vfree(const void *addr)

slab層

為了便於資料的頻繁分配和回收,Linux核心提供了slab層(也就是所謂的slab分配器)。slab分配器扮演了通用資料結構緩衝層的角色。

slab層把不同的對象劃分為快速緩衝。當中每一個快速緩衝組中存放的都是不同類型的資料結構對象。比如,一個快速緩衝用於存放進程描寫敘述符,還有一個快速緩衝用於存放i節點。

這些快速緩衝又被劃分為slab。slab由一個或多個物理上連續的頁組成。普通情況下,slab也就只由一頁組成。每一個快速緩衝能夠由多個slab組成。

每一個slab都包括一些對象成員。這裡的對象指的是被緩衝的資料結構。每一個slab處於三種狀態之中的一個:滿、部分滿或空。當核心的某一部分須要一個對象時。就要由slab分配了,首先考慮的是部分滿的slab。假設不存在部分滿的slab則去空的slab分配,假設也不存在空的slab。則核心須要申請頁又一次分配快速緩衝。描寫敘述了快速緩衝、slab及對象之間的關係。來自http://www.cnblogs.com/wang_yb/archive/2013/05/23/3095907.html



整個slab層的原理例如以下:

1.能夠在記憶體中建立各種對象的快速緩衝(比方進程描寫敘述相關的結構 task_struct的快速緩衝)

2.除了針對特定對象的快速緩衝以外,也有通用對象的快速緩衝

3.每一個快速緩衝中包括多個 slab。slab用於管理緩衝的對象

4.slab中包括多個緩衝的對象,物理上由一頁或多個連續的頁組成

每一個快速緩衝都是用kmem_cache_s 結構來表示。這個結構包括三個鏈表 slabs_full。slabs_partial和 slabs_empty。均存放在 kmem_lists 結構內。這些鏈表包括快速緩衝中的全部slab。slab描寫敘述符 structslab 用來描寫敘述每一個slab:

struct slab {

    struct list_head list;         /* 滿、部分滿或空鏈表 */

    unsigned long colouroff;  /* slab 著色的位移量   */

    void *s_mem;                  /* 在 slab 中的第一個對象 */

    unsigned int inuse;          /* 已指派的對象數        */

    kmem_bufctl_t tree;         /* 第一個空間對象(假設有的話) */

};

slab分配器的介面

主要有四個

1.  快速緩衝的建立
struct kmem_cache * kmem_cache_create (const char *name, size_t size, size_t align, unsigned long flags, void (*ctor)(void *))

 

2.      從快速緩衝中指派至

void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)

3.      釋放對象。返回給原先的slab

void kmem_cache_free(struct kmem_cache *cachep, void *objp)

4.快速緩衝的銷毀

void kmem_cache_destroy(struct kmem_cache *cachep)
slab解決記憶體片段

記憶體片段存在的方式有兩種:a.內部片段 b.外部片段

內部片段的產生:由於全部的記憶體配置必須起始於可被 4、8或 16 整除(視處理器體繫結構而定)的地址或者由於MMU的分頁機制的限制,決定記憶體配置演算法僅能把預定大小的記憶體塊分配給客戶。如果當某個客戶請求一個 43 位元組的記憶體塊時,由於沒有適合大小的記憶體,所以它可能會獲得 44位元組、48位元組等稍大一點的位元組,因此由所需大小四捨五入而產生的多餘空間就叫內部片段。


      外部片段的產生: 頻繁的分配與回收物理頁面會導致大量的、連續且小的頁面塊夾雜在已指派的頁面中間。就會產生外部片段。

假設有一塊一共同擁有100個單位的連續空暇記憶體空間。範圍是0~99。假設你從中申請一塊記憶體,如10個單位,那麼申請出來的記憶體塊就為0~9區間。

這時候你繼續申請一塊記憶體。比方說5個單位大。第二塊得到的記憶體塊就應該為10~14區間。假設你把第一塊記憶體塊釋放。然後再申請一塊大於10個單位的記憶體塊。比方說20個單位。由於剛被釋放的記憶體塊不能滿足新的請求,所以僅僅能從15開始分配出20個單位的記憶體塊。如今整個記憶體空間的狀態是0~9空暇。10~14被佔用。15~24被佔用,25~99空暇。當中0~9就是一個記憶體片段了。

假設10~14一直被佔用,而以後申請的空間都大於10個單位,那麼0~9就永遠用不上了。變成外部片段。

解決方案:

slab機制,由於slab預先分配了特定資料結構大小的記憶體,所以沒有內部片段或者外部片段。

slab與傳統記憶體管理員模式比較:

與傳統的記憶體管理員模式相比。 slab 緩衝分配器提供了非常多長處。

首先。核心通常依賴於對小對象的分配,它們會在系統生命週期內進行無數次分配。slab 緩衝分配器通過對類似大小的對象進行緩衝而提供這樣的功能。從而避免了常見的片段問題。slab 分配器還支援通用對象的初始化,從而避免了為同一目而對一個對象反覆進行初始化。

最後。slab 分配器還能夠支援硬體緩衝對齊和著色,這防止錯誤的共用(兩個或兩個對象雖然位於不同的記憶體位址,但映射到同樣的告訴緩衝行),這能夠提高效能。但以添加記憶體浪費為代價。

在棧上的靜態分配

核心棧大小固定。我們在進程時要注意節省棧資源,要控制函數內的局部變數。盡量不要出現大型數組或大型結構體。

尤其對於核心棧,一旦造成溢出,就會影響到核心資料(如thread_info)。所以應當優先考慮動態分配。另外一個進程的核心棧和中斷棧是分開的,這樣能夠減輕核心棧的負擔(一個核心棧僅僅佔1頁或2頁)。

高端記憶體的映射

由於32位的處理器可以定址達到4GB。一旦這些頁被分配。就必須映射到核心的虛擬記憶體空間上。  

高於896MB的全部實體記憶體的範圍大都是高端記憶體,它不會永久或自己主動的映射到核心虛擬位址空間。

核心地址的虛擬記憶體大小為1G。當中0-896M的記憶體與實體記憶體一一映射,即線性映射。而896MB~1024MB的虛擬記憶體假設也與實體記憶體線性映射。那麼核心態僅僅能使用1G的實體記憶體。即使實體記憶體大於1G(比方4G),這種話就沒有充分利用實體記憶體了。所以核心虛擬記憶體中的896MB~1024MB與高端記憶體不會一一映射。詳細的映射方式例如以下:

當核心態須要訪問高端實體記憶體時。在核心虛擬記憶體空間中的896-1024MB找一段對應大小空暇的邏輯地址空間。借用一會。借用這段邏輯地址空間,建立映射到想要訪問的那段實體記憶體,暫時用一會,用完後歸還。

這樣當進程後面又須要訪問其它的高端實體記憶體時。仍然能夠用這段邏輯地址空間。

高端記憶體的最基本思想:在核心虛擬空間896MB~1024MB的記憶體中借一段地址空間,建立與高端實體記憶體的暫時地址映射,用完後釋放虛擬空間。達到這段虛擬位址空間能夠迴圈使用,訪問全部實體記憶體。

高端記憶體映射有三種方式:

1、映射到“核心動態映射空間”

這樣的方式非常easy。由於通過 vmalloc() ,在”核心動態映射空間“申請記憶體的時候,就可能從高端記憶體獲得頁面(參看 vmalloc 的實現),因此說高端記憶體有可能映射到”核心動態映射空間“ 中。
        2、永久核心映射
        假設是通過alloc_page() 獲得了高端記憶體相應的 page,怎樣給它找個線性空間?
核心專門為此留出一塊線性空間。從 PKMAP_BASE 到 FIXADDR_START ,用於映射高端記憶體。在 2.4 核心上,這個位址範圍是 4G-8M 到 4G-4M 之間。

這個空間起叫“核心永久映射空間”或者“永久核心映射空間”。

這個空間和其他空間使用相同的頁檔案夾表,對於核心來說,就是 swapper_pg_dir,對普通進程來說。通過 CR3 寄存器指向。

通常情況下,這個空間是 4M 大小,因此只須要一個頁表就可以,核心通過來 pkmap_page_table 尋找這個頁表。
        3、暫時映射

當必須建立一個映射而當前的上下文又不能睡眠時。核心提供了暫時映射(也就是原子映射)。有一組保留的映射。他們能夠存放新建立的暫時映射。核心能夠原子地把高端記憶體中的一個頁映射到某個保留的映射中。因此,暫時映射能夠用在不能睡眠的地方,比方中斷處理常式中,由於擷取映射時絕不會堵塞。

每一個CPU資料

SMP環境下加鎖過多的話,會嚴重影響並行的效率,假設是自旋鎖的話。還會浪費其它CPU的已耗用時間。

所以核心中才有了按CPU分配資料的介面。按CPU分配資料之後。每一個CPU自己的資料不會被其它CPU訪問。儘管浪費了一點記憶體,可是會使系統更加的簡潔高效。

 按CPU來分配資料主要有2個長處:

1.最直接的效果就是降低了對資料的鎖,提高了系統的效能

2.由於每一個CPU有自己的資料,所以處理器切換時能夠大大降低緩衝失效的幾率。由於假設一個處理器操作某個資料。而這個資料在還有一個處理器的緩衝中時,那麼存放這個資料的那個處理器必須清理或重新整理自己的緩衝。持續的緩衝失效成為緩衝抖動。對系統效能影響非常大。

 

 

 

 

Linux核心——記憶體管理

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.