內核中物理內存的管理


內存頁

MMU :內存管理單元,將虛擬內存轉化為物理地址的硬件。

  • 因為 MMU 通常以頁為單位處理內存,所以從虛擬內存的角度來說,內核中頁是最小的單位。
  • 一般 32 位系統使用4k頁,64 位系統使用8k頁。
  • 內核使用 struct page 結構體存放物理頁。page 與物理頁相關,而非虛擬頁,當內存頁被swap后,可能不再和同一個page相關聯。
  • 可以通過page_address(page) 函數獲取物理頁page 對應的邏輯地址。

page 的內核代碼

defined in <linux/mm_types.h>

struct page { 
    unsigned long flags;    //內存頁狀態,定義在<linux/page-flags.h>.
    atomic_t _count;        //引用計數
    atomic_t _mapcount; 
    unsigned long private; 
    struct address_space *mapping; 
    pgoff_t index; 
    struct list_head lru; 
    void *virtual;          //在虛擬內存中的地址
};

ZONE

由於硬件的原因,內核對於內存中不同物理地址的內存並不一視同仁。由於這種限制,內存將內存頁划分了區(zone)。ZONE的划分是為了管理頁的一種邏輯分組。內存分配不能同時在兩個zone 分配。

有些硬件存在下面兩種缺陷引起的內存尋址問題,所以需要將內存分區。

  1. 一些硬件只能用特定的內存地址來執行DMA(直接內存訪問)。
  2. 一些體系結構的內存物理尋址范圍大於虛擬尋址范圍,導致一些內存不能映射到內核空間。

linux 主要有下面 4 種 ZONE

  1. ZONE_DMA—This zone contains pages that can undergo DMA.
  2. ZONE_DMA32—Like ZOME_DMA, this zone contains pages that can undergo DMA. Unlike ZONE_DMA, these pages are accessible only by 32-bit devices. On some architectures, this zone is a larger subset of memory.
  3. ZONE_NORMAL—This zone contains normal, regularly mapped, pages.
  4. ZONE_HIGHMEM—This zone contains “high memory,” which are pages not perma￾nently mapped into the kernel’s address space.

例如 X86-32 架構,ISA 設備只能訪問物理內存的前16M,高於896M的內存不能直接映射。剩下的就是NORMAL區。如果體系結構沒有限制,那么全部都是NORMAL區。

Zone Description Physical Memory
ZONE_DMA DMA-able pages < 16MB
ZONE_NORMAL Normally addressable pages 16–896MB
ZONE_HIGHMEM Dynamically mapped pages > 896MB

zone 的水線:每一個zone 都有自己的最小值,最低值,最高值三個水線,使用水線設置合適的內存消耗基准,水線隨着空暇內存變化

zone 的內核代碼

defined in <linux/mmzone.h>

struct zone { 
    unsigned long watermark[NR_WMARK];          //持有該區的最小最低最高的水位值。
    unsigned long lowmem_reserve[MAX_NR_ZONES]; 
    struct per_cpu_pageset pageset[NR_CPUS]; 
    spinlock_t lock;                            //自旋鎖
    struct free_area free_area[MAX_ORDER] 
    spinlock_t lru_lock; 
    struct zone_lru {
        struct list_head list; 
        unsigned long nr_saved_scan;
    }lru[NR_LRU_LISTS]; 
    struct zone_reclaim_stat reclaim_stat; 
    unsigned long pages_scanned; 
    unsigned long flags; 
    atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS]; 
    int prev_priority; 
    unsigned int inactive_ratio; 
    wait_queue_head_t *wait_table; 
    unsigned long wait_table_hash_nr_entries; 
    unsigned long wait_table_bits; 
    struct pglist_data *zone_pgdat; 
    unsigned long zone_start_pfn; 
    unsigned long spanned_pages; 
    unsigned long present_pages; 
    const char *name;
};

獲取頁

內核提供了一些請求內存和釋放內存的底層接口,

請求內存函數 描述
alloc_page(gfp_mask) Allocates a single page and returns a pointer to its
alloc_pages(gfp_mask, order) Allocates 2order pages and returns a pointer to the first page’s page structure
__get_free_page(gfp_mask) Allocates a single page and returns a pointer to its logical address
__get_free_pages(gfp_mask, order) Allocates 2order pages and returns a pointer to the first page’s logical address
get_zeroed_page(gfp_mask) Allocates a single page, zero its contents and returns a pointer to its logical address

釋放內存接口:

void __free_pages(struct page *page, unsigned int order) 
void free_pages(unsigned long addr, unsigned int order) 
void free_page(unsigned long addr)

kmalloc()

和上面獲取頁的接口不同,kmalloc() 主要用於申請字節為單位的內存。kmalloc() 返回一個指向內存塊的指針,至少是 size 大小,分配的內存區在物理上是連續的。

void * kmalloc(size_t size, gfp_t flags)
gfp_mask 標志
  1. 行為修飾符:分配內存時的動作
  2. 區修飾符:從哪個 zone 分配內存
  3. 類型:組合上面兩個
    • GFP_ATOMIC 分配內存是不能睡眠,在內存緊缺時容易失敗。
    • GFP_KERNEL 可以睡眠,用於安全調度的進程上下文中,成功率高。
Situation Solution
Process context, can sleep Use GFP_KERNEL.
Process context, cannot sleep Use GFP_ATOMIC, or perform your allocations with GFP_KERNEL at an earlier or later point when you can sleep
Interrupt handler Use GFP_ATOMIC.
Softirq Use GFP_ATOMIC.
Tasklet Use GFP_ATOMIC.
Need DMA-able memory, can Use (GFP_DMA
Need DMA-able memory, cannot Use (GFP_DMA
kfree()

kfree() 釋放由kmalloc()申請的內存。

void kfree(const void *ptr)

//例子
char *buf;
buf = kmalloc(BUF_SIZE, GFP_ATOMIC); if (!buf)
/* error allocating memory ! */
kfree(buf);

vmalloc()

類似kmalloc,但是物理內存地址可以不連續,虛擬內存地址是連續的。可以睡眠。

一般只有硬件要求得到物理地址連續的內存。軟件可以使用只有虛擬地址連續的內存。很多內核代碼雖然不需要連續的物理內存,但還是使用kmalloc,因為性能好,不需要做邏輯映射。

//declared in <linux/vmalloc.h>

void * vmalloc(unsigned long size)
void vfree(const void *addr)

//例子
char *buf;
buf = vmalloc(16 * PAGE_SIZE); /* get 16 pages */ if (!buf)
vfree(buf);

Slab 層

有很多對象存放在鏈表結構中,在不用的時候空閑鏈表也已經占用內存。內核不能不能控制這些空閑鏈表的回收,尤其是在內存緊缺時。所以引入了 slab 分配器。slab 扮演了一個通用的數據結構緩存角色。

slab 把不同類型的對象放到不同的 caches 中。 一個 slab 由一個或者多個物理上的連續頁組成,一般情況只有一個頁,每一個cache 有多個 slab。

image

一個 slab 有三個狀態:full, partial, or empty. 先從 partial 開始填充。

cache 用 kmem_cache 結構表示 包含三個鏈表 slabs_full,slabs_partial,slabs_empty,鏈表中包含所有的 slab。

struct slab {
    struct list_head    list; /* full, partial, or empty list */
    unsigned long       colouroff; /* offset for the slab coloring */ 
    void                *s_mem; /* first object in the slab */  
    unsigned int        inuse; /* allocated objects in the slab */
    kmem_bufctl_t       free; /* first free object, if any */
};

slab  的創建:
通過 *kmem_getpages()中調用的 _get_free_pages()函數分配內存頁。

slab 是在 cache 的基礎之上,提供給內核一個簡單的接口,通過接口來對 cache 進行分配和撤銷。slab 起一個分配器的作用,可以為具體的 object 分配內存。

//cache 的創建(slab 分配器的接口):

//返回一個指向 cache 的指針。align 是 slab 第一個對象的偏移量,用於內存對齊
struct kmem_cache * kmem_cache_create(const char *name, size_t size,size_t align, unsigned long flags, void (*ctor)(void *));

//撤銷 cache
int kmem_cache_destroy(struct kmem_cache *cachep)

//創建 cache 后,獲取對象,沒有空閑 slab 的話,通過上面的*kmem_getpages()獲取新的頁。
void * kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)

task_struct 對象的 slab 和 cache 創建例子

//1.首先創建一個全局變量存放 task_struct 的 cache
struct kmem_cache *task_struct_cachep;

task_struct_cachep = kmem_cache_create(“task_struct”, sizeof(struct task_struct),ARCH_MIN_TASKALIGN, SLAB_PANIC | SLAB_NOTRACK, NULL);

//2.進程調用 fork()時,會創建新的process descriptor:

struct task_struct *tsk;
tsk = kmem_cache_alloc(task_struct_cachep, GFP_KERNEL); 
if (!tsk)
    return NULL;

//3.process descriptor 被撤銷
kmem_cache_free(task_struct_cachep, tsk);

//4.task_struct_cachep cache 是不會被撤銷的,因為內核經常要用,非要撤銷的話:

int err;
err = kmem_cache_destroy(task_struct_cachep); if (err)
/* error destroying cache */

其他

Stack 上內存的靜態分配

32位 和 64位 頁的大小為4K 和 8K,一般進程有兩頁的內核棧,也可以設置單頁內核棧。

隨着運行時間的增加,物理內存碎片增加,分配連續的頁越來越難。當單頁棧設置后,中斷程序不再和進程放在同一個棧內,有自己的中斷棧。

棧的溢出會覆蓋緊鄰堆棧末端的內容,溢出后 down 機還好,否則會破壞數據。

高端內存的映射

高端內存的永久映射數量是有限的,不需要時需要解除。通過函數kmap進行映射,可以睡眠。kmap_atomic提供了原子性的臨時映射。不會被阻塞,禁止內核搶斷。

per CPU 新接口

對於 smp 系統,多個 cpu 可以有自己才能訪問的數據,這樣不需要鎖,只需要注意內核搶占的問題即可。

void *percpu_ptr; 
unsigned long *foo;

percpu_ptr = alloc_percpu(unsigned long);    //為 cpu 動態分配內存,類似 kmalloc()
if (!ptr)
/* error allocating memory .. */

foo = get_cpu_var(percpu_ptr);  //獲取當前 CPU 上的指定數據,會禁止內核搶斷
/* manipulate foo .. */  
put_cpu_var(percpu_ptr);    //開啟內核搶斷

分配內存函數的選擇

需求 函數 特點
連續的物理頁 kmalloc() 可以通過 flag 決定是否可以睡眠,
高端內存 alloc_pages() 返回一個 page 的指針,而不是邏輯地址,因為可能沒有映射
獲取真正的指針 kmap() 會把高端內存映射到邏輯地址
不需要內存連續的地址 vmalloc() 需要映射,有性能損失
創建和撤銷數據結構 slab 使用 cache 動態分配


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM