1. 程式人生 > >Linux核心學習筆記九——核心記憶體管理方式

Linux核心學習筆記九——核心記憶體管理方式

一 頁

       核心把物理頁作為記憶體管理的基本單位;記憶體管理單元(MMU)把虛擬地址轉換為物理

地址,通常以頁為單位進行處理。MMU以頁大小為單位來管理系統中的也表。

       32位系統:頁大小4KB

       64位系統:頁大小8KB

核心用相應的資料結構表示系統中的每個物理頁:

  <linux/mm_types.h>

  struct page {}

核心通過這樣的資料結構管理系統中所有的頁,因此核心判斷一個頁是否空閒,誰有擁有這個頁

,擁有者可能是:使用者空間程序、動態分配的核心資料、靜態核心程式碼、頁快取記憶體……

系統中每一個物理頁都要分配這樣一個結構體,進行記憶體管理。

二 區

       Linux記憶體定址存在問題:

一些硬體只能用某些特定的記憶體來執行DMA(直接記憶體訪問)

一些體系結構其記憶體的物理定址範圍必須你定址範圍大得多。這樣導致一些記憶體不能永久對映到核心空間上。

       通常32位Linux核心地址空間劃分0~3G為使用者空間,3~4G為核心空間。當核心模組程式碼或執行緒訪問記憶體時,

程式碼中的記憶體地址都為邏輯地址,而對應到真正的實體記憶體地址,需要地址一對一的對映。因此核心空間地址為3~4G,

最多隻能對映到1G空間的記憶體,超出1G大小的記憶體將如何去問呢!

       由於存在上述條件的限制。Linux將核心空間地址劃分為三個區:

ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM。

       ZONE_HIGHMEM即為高階記憶體,這就是記憶體高階記憶體概念的由來。

在x86結構中,三種類型的區域如下:

  ZONE_DMA        記憶體開始的16MB

  ZONE_NORMAL       16MB~896MB

  ZONE_HIGHMEM       896MB ~ 結束

同樣每個區包含眾多頁,形成不同記憶體池,按照用途進行記憶體分配。

用相應的資料結構來表示區:

  <linux/mmzone.h>

  struct zone {}

三 獲取頁/記憶體

static inline struct page *alloc_pages(gfp_t gfp_mask, unsigned int order)

該函式分配2的order次方個連續的物理頁,返回指向第一個頁的page結構體指標。

void *page_address(const struct page *page)

返回指向給定物理頁當前所在的邏輯地址

extern unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);

extern unsigned long get_zeroed_page(gfp_t gfp_mask);

釋放:

extern void __free_pages(struct page *page, unsigned int order);

extern void free_pages(unsigned long addr, unsigned int order);

記憶體的分配可能失敗,記憶體的釋放要準確!

1 kmalloc

kmalloc()函式與使用者空間malloc一組函式類似,獲得以位元組為單位的一塊核心記憶體。

void *kmalloc(size_t size, gfp_t flags)

void kfree(const void *objp)

分配記憶體物理上連續。

gfp_t標誌:表明分配記憶體的方式。如:

GFP_ATOMIC:分配記憶體優先順序高,不會睡眠

GFP_KERNEL:常用的方式,可能會阻塞。

2 vmalloc    

void *vmalloc(unsigned long size)

void vfree(const void *addr)

vmalloc()與kmalloc方式類似,vmalloc分配的記憶體虛擬地址是連續的,而實體地址則無需連續,與使用者空間分配函式一致。

vmalloc通過分配非連續的實體記憶體塊,在修正頁表,把記憶體對映到邏輯地址空間的連續區域中,虛擬地址是連續的。

       是否必須要連續的實體地址和具體使用場景有關。在不理解虛擬地址的硬體裝置中,記憶體區都必須是連續的。

       通過建立頁錶轉換成虛擬地址空間上連續,肯定存在一些消耗,帶來效能上影響。

所以通常核心使用kmalloc來申請記憶體,在需要大塊記憶體時使用vmalloc來分配。

四 slab層

       核心中經常進行記憶體的分配和釋放。為了便於資料的頻繁分配和回收,通常建立一個空

閒連結串列——記憶體池。當不使用的已分配的記憶體時,將其放入記憶體池中,而不是直接釋放掉。

       Linux核心提供了slab層來管理記憶體的分配和釋放。

頻繁分配和回收必然導致記憶體碎片,快取他們.

slab層得設計實現

       slab層把不同的物件劃分為所謂的快取記憶體組。每個快取記憶體組存放不同型別的物件。快取記憶體劃分為slab,

slab由一個或多個物理上連續的頁組成。每個slab處於三種狀態之一:滿,部分滿,空。

快取記憶體,slab,物件之間的關係:

     圖  1. slab 分配器的主要結構

       與傳統的記憶體管理模式相比, slab 快取分配器提供了很多優點。首先,核心通常依賴於對小物件的分配,

它們會在系統生命週期內進行無數次分配。slab 快取分配器通過對類似大小的物件進行快取而提供這種功能,

從而避免了常見的碎片問題。slab 分配器還支援通用物件的初始化,從而避免了為同一目而對一個物件重複

進行初始化。最後,slab 分配器還可以支援硬體快取對齊和著色,這允許不同快取中的物件佔用相同的快取行,

從而提高快取的利用率並獲得更好的效能。

slab資料結構和介面:

每個快取記憶體用kmem_cache結構來表示:

       struct kmem_cache {

              struct kmem_list3 **nodelists;

              ……

       }

快取區包含三種slab:滿,未滿,空閒

struct kmem_list3 {

       struct list_head slabs_partial; /* partial list first, better asm code */

       struct list_head slabs_full;

       struct list_head slabs_free;

       ……

};

每一個slab包含多個物件:

struct slab {

              struct list_head list;

              unsigned long colouroff;

              void *s_mem;            /* including colour offset */

              unsigned int inuse;     /* num of objs active in slab */

              kmem_bufctl_t free;

              unsigned short nodeid;

};

相關介面:mm/slab.c

              核心函式 kmem_cache_create 用來建立一個新快取。這通常是在核心初始化時執行的,或者在首次載入核心模組時執行。

struct kmem_cache *kmem_cache_create (

  const char *name,

  size_t size,

  size_t align,

  unsigned long flags,

  void (*ctor)(void *))

name 引數定義了快取名稱,proc 檔案系統(在 /proc/slabinfo 中)使用它標識這個快取。

size 引數指定了為這個快取建立的物件的大小,

align 引數定義了每個物件必需的對齊。

flags 引數指定了為快取啟用的選項:

  kmem_cache_create 的部分選項(在 flags 引數中指定)

  SLAB_RED_ZONE    在物件頭、尾插入標誌,用來支援對緩衝區溢位的檢查。

  SLAB_POISON  使用一種己知模式填充 slab,允許對快取中的物件進行監視(物件屬物件所有,不過可以在外部進行修改)。

  SLAB_HWCACHE_ALIGN      指定快取物件必須與硬體快取行對齊。

ctor 和 dtor 引數定義了一個可選的物件構造器和析構器。構造器和析構器是使用者提供的回撥函式。當從快取中分配新物件時,可以通過構造器進行初始化。

    要從一個命名的快取中分配一個物件,可以使用 kmem_cache_alloc 函式。

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

這個函式從快取中返回一個物件。注意如果快取目前為空,那麼這個函式就會呼叫 cache_alloc_refill 向快取中增加記憶體。

kmem_cache_alloc 的 flags 選項與 kmalloc 的

cachep:所建立的快取區

flags引數:

  GFP_USER 為使用者分配記憶體(這個呼叫可能會睡眠)。

  GFP_KERNEL    從核心 RAM 中分配記憶體(這個呼叫可能會睡眠)。

  GFP_ATOMIC   使該呼叫強制處於非睡眠狀態(對中斷處理程式非常有用)。

  GFP_HIGHUSER      從高階記憶體中分配記憶體。

五 高階記憶體的對映

永久對映:可能會阻塞

  對映一個給定的page結構到核心地址空間:

  void *kmap(struct page *page)

  解除對映:

  void kunmap(struct page *page)

臨時對映:不會阻塞     

void *kmap_atomic(struct page *page)

六 分配函式的選擇

  l  連續的物理頁:kmalloc或者低階頁分配器

  l  高階記憶體分配:alloc_pages 指向page結構指標,不是邏輯地址指標。再通過kmap()把高階地址記憶體對映到核心的邏輯地址空間。

  l  無需連續實體地址:vmalloc 虛擬地址連續實體地址可能不連續,相對存在效能損失

  l  頻繁建立和銷燬很多較大資料結構:建立slab快取區,提高物件分配和回收效能。

Linux高階記憶體:
  http://ilinuxkernel.com/?p=1013
Linux slab 分配器剖析:
  https://www.ibm.com/developerworks/cn/linux/l-linux-slab-allocator/