Memory Allocation API In Linux Kernel && Linux Userspace、kmalloc vmalloc Difference、Kernel Large Section Memory Allocation

Andrew.Hann發表於2014-11-21

目錄

1. 核心態(ring0)記憶體申請和使用者態(ring3)記憶體申請
2. 核心態(ring0)記憶體申請:kmalloc/kfree、vmalloc/vfree
3. 使用者態(ring3)記憶體申請:malloc/free
4. 核心記憶體申請原則
5. 核心中分配實體地址連續的大段記憶體

 

1. 核心態(ring0)記憶體申請和使用者態(ring3)記憶體申請 

0x1: 差異點

在核心中申請記憶體和在使用者空間中申請記憶體不同,有以下因素引起了複雜性,包括

1. 核心的虛擬和實體地址被限制到1GB 
2. 核心的記憶體不能PAGEABLE
3. 核心通常需要連續的實體地址 
4. 通常核心申請記憶體是不能睡眠 
5. 核心中的錯誤比其他地方的錯誤有更多的代價

核心態的記憶體申請API和使用者態的記憶體申請API使用方法很類似,所不同的是,因為核心態常駐執行的特殊性,核心的記憶體申請在可交換型、空間連續性方面會有一些差別,我們接下來深入學習一下它們

Relevant Link:

http://blog.csdn.net/newfeicui/article/details/6437917

 

2. 核心態(ring0)記憶體申請:kmalloc/kfree、vmalloc/vfree

0x1: kmalloc函式原型

\linux-3.15.5\include\linux\slab.h

void *__kmalloc(size_t size, gfp_t flags);

引數說明,linux-3.15.5\include\linux\gfp.h

1. size_t size: 要分配的塊的大小
2. gfp_t flags: 分配標誌(flags),用於指定kmalloc的行為 
    1) ___GFP_DMA: #define ___GFP_DMA 0x01u: 要求分配在能夠 DMA 的記憶體區
    2) ___GFP_HIGHMEM: #define ___GFP_HIGHMEM 0x02u: 指示分配的記憶體可以位於高階記憶體
    3) ___GFP_DMA32: #define ___GFP_DMA32 0x04u: 
    4) ___GFP_MOVABLE: #define ___GFP_MOVABLE 0x08u: 
    5) ___GFP_WAIT: #define ___GFP_WAIT 0x10u: Allocation will not sleep.
    6) ___GFP_HIGH: #define ___GFP_HIGH 0x20u: 標識了一個高優先順序請求, 它被允許來消耗甚至被核心保留給緊急狀況的最後的記憶體頁
    7) ___GFP_IO: #define ___GFP_IO 0x40u: The allocator can start disk I/O.
    8) ___GFP_FS: #define ___GFP_FS 0x80u: The allocator can start filesystem I/O.
    9) ___GFP_COLD: #define ___GFP_COLD 0x100u: The allocator should use cache cold pages.
    10) ___GFP_NOWARN: #define ___GFP_NOWARN 0x200u: 阻止核心來發出警告(使用 printk ),當一個分配無法滿足
    11) ___GFP_REPEAT: #define ___GFP_REPEAT 0x400u: The allocator will repeat the allocation if it fails, but the allocation can potentially fail.
    12) ___GFP_NOFAIL: #define ___GFP_NOFAIL 0x800u: The allocator will indefinitely repeat the allocation. The allocation cannot fail.
    13) ___GFP_NORETRY: #define ___GFP_NORETRY 0x1000u: The allocator will never retry if the allocation fails.
    14) ___GFP_MEMALLOC: #define ___GFP_MEMALLOC 0x2000u: 
    15) ___GFP_COMP: #define ___GFP_COMP 0x4000u: Add compound page metadata. Used internally by the hugetlb code.
    16) ___GFP_ZERO: #define ___GFP_ZERO 0x8000u: 
    17) ___GFP_NOMEMALLOC: #define ___GFP_NOMEMALLOC 0x10000u
    18) ___GFP_HARDWALL #define ___GFP_HARDWALL 0x20000u
    19) ___GFP_THISNODE: #define ___GFP_THISNODE 0x40000u
    20) ___GFP_RECLAIMABLE: #define ___GFP_RECLAIMABLE 0x80000u
    21) ___GFP_KMEMCG: #define ___GFP_KMEMCG 0x100000u
    22) ___GFP_NOTRACK: #define ___GFP_NOTRACK 0x200000u
    23) ___GFP_NO_KSWAPD: #define ___GFP_NO_KSWAPD 0x400000u
    24) ___GFP_OTHER_NODE: #define ___GFP_OTHER_NODE 0x800000u
    25) ___GFP_WRITE: #define ___GFP_WRITE 0x1000000u
    /*
    除了系統預設的標誌位之後,實際程式設計中最常用的是多個巨集定義"異或疊加"的標誌位
    */
    1) GFP_KERNEL: #define GFP_KERNEL (__GFP_WAIT | __GFP_IO | __GFP_FS)
    2) GFP_ATOMIC: #define GFP_ATOMIC (__GFP_HIGH): 用來從中斷處理和程式上下文之外的其他程式碼中分配記憶體. 從不睡眠
    3) GFP_USER: #define GFP_USER    (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL): 用來為使用者空間頁來分配記憶體; 它可能睡眠.

0x2: kmalloc使用方法

1. kmalloc()分配的記憶體處於3GB~high_memory之間的一段連續記憶體,這段核心空間與實體記憶體的對映一一對應
2. Linux處理記憶體分配通過建立一套固定大小的記憶體物件池。分配請求被這樣來處理,進入一個持有足夠大的物件的池子並且將整個記憶體塊遞交給請求者。驅動開發者應當記住的一件事情是,核心只能分配某些預定義的,固定大小的位元組陣列
3. kmalloc 能夠處理的最小分配是 32 或者 64 位元組(或者是其整數倍),依賴系統的體系所使用的頁大小,所以使用kmalloc申請一個任意數量記憶體,我們可能得到稍微多於請求的,至多是 2 倍數量
4. kmalloc 能夠分配的記憶體塊的大小有一個上限。這個限制隨著體系和核心配置選項而變化。為了提高我們的LKM程式碼的相容性和可移植性,我們可以申請分配的記憶體最大隻能 128 KB
5. kmalloc特殊之處在於它分配的記憶體是"物理上連續"的,這對於要進行DMA的裝置十分重要 
6. kmalloc最大隻能開闢(128k-16)KB,16個位元組是被頁描述符結構佔用了 
7. 很多硬體需要一塊比較大的連續記憶體用作DMA傳送。這塊記憶體需要一直駐留在記憶體,不能被交換到檔案中去。但是kmalloc最多隻能開闢大小為32xPAGE_SIZE的記憶體,一般的PAGE_SIZE = 4kB,也就是kmalloc最多隻能申請128kB的大小的記憶體 

Relevant Link:

http://oss.org.cn/kernel-book/ldd3/ch08.html
http://people.netfilter.org/rusty/unreliable-guides/kernel-hacking/routines-kmalloc.html
http://www.makelinux.net/books/lkd2/ch11lev1sec4
https://www.kernel.org/doc/htmldocs/kernel-api/API-kmalloc.html

0x3: vmalloc函式原型

\linux-3.15.5\include\linux\vmalloc.h

void * vmalloc(unsigned long size)

source/mm/vmalloc.c

*__vmalloc(unsigned long size, gfp_t gfp_mask, pgprot_t prot)
{
    return __vmalloc_node(size, 1, gfp_mask, prot, NUMA_NO_NODE, __builtin_return_address(0));
}

__vmalloc_node

static void *__vmalloc_node(unsigned long size, unsigned long align, gfp_t gfp_mask, pgprot_t prot, int node, const void *caller)
{
    return __vmalloc_node_range(size, align, VMALLOC_START, VMALLOC_END, gfp_mask, prot, node, caller);
}

__vmalloc_node_range

void *__vmalloc_node_range(unsigned long size, unsigned long align,
            unsigned long start, unsigned long end, gfp_t gfp_mask,
            pgprot_t prot, int node, const void *caller)
{
    struct vm_struct *area;
    void *addr;
    unsigned long real_size = size;

    size = PAGE_ALIGN(size);
    if (!size || (size >> PAGE_SHIFT) > totalram_pages)
        goto fail;

    area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNINITIALIZED,
                  start, end, node, gfp_mask, caller);
    if (!area)
        goto fail;

    addr = __vmalloc_area_node(area, gfp_mask, prot, node);
    if (!addr)
        return NULL;

    /*
     * In this function, newly allocated vm_struct has VM_UNINITIALIZED
     * flag. It means that vm_struct is not fully initialized.
     * Now, it is fully initialized, so remove this flag here.
     */
    clear_vm_uninitialized_flag(area);

    /*
     * A ref_count = 2 is needed because vm_struct allocated in
     * __get_vm_area_node() contains a reference to the virtual address of
     * the vmalloc'ed block.
     */
    kmemleak_alloc(addr, real_size, 2, gfp_mask);

    return addr;

fail:
    warn_alloc_failed(gfp_mask, 0,
              "vmalloc: allocation failure: %lu bytes\n",
              real_size);
    return NULL;
}

__vmalloc_area_node

static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
                 pgprot_t prot, int node)
{
    const int order = 0;
    struct page **pages;
    unsigned int nr_pages, array_size, i;
    gfp_t nested_gfp = (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO;

    nr_pages = get_vm_area_size(area) >> PAGE_SHIFT;
    array_size = (nr_pages * sizeof(struct page *));

    area->nr_pages = nr_pages;
    /* Please note that the recursion is strictly bounded. */
    if (array_size > PAGE_SIZE) {
        pages = __vmalloc_node(array_size, 1, nested_gfp|__GFP_HIGHMEM,
                PAGE_KERNEL, node, area->caller);
        area->flags |= VM_VPAGES;
    } else {
        pages = kmalloc_node(array_size, nested_gfp, node);
    }
    area->pages = pages;
    if (!area->pages) {
        remove_vm_area(area->addr);
        kfree(area);
        return NULL;
    }

    for (i = 0; i < area->nr_pages; i++) {
        struct page *page;
        gfp_t tmp_mask = gfp_mask | __GFP_NOWARN;

        if (node == NUMA_NO_NODE)
            page = alloc_page(tmp_mask);
        else
            page = alloc_pages_node(node, tmp_mask, order);

        if (unlikely(!page)) {
            /* Successfully allocated i pages, free them in __vunmap() */
            area->nr_pages = i;
            goto fail;
        }
        area->pages[i] = page;
    }

    if (map_vm_area(area, prot, &pages))
        goto fail;
    return area->addr;

fail:
    warn_alloc_failed(gfp_mask, order,
              "vmalloc: allocation failure, allocated %ld of %ld bytes\n",
              (area->nr_pages*PAGE_SIZE), area->size);
    vfree(area->addr);
    return NULL;
}

從核心原始碼中可以看出,vmalloc複用了kmalloc的記憶體申請的程式碼

0x4: vmalloc函式使用方法

char *buf;

buf = vmalloc(16 * PAGE_SIZE); /* get 16 pages */
if (!buf)
        /* error! failed to allocate memory */

/*
 * buf now points to at least a 16*PAGE_SIZE bytes
 * of virtually contiguous block of memory
 */

Relevant Link:

http://www.makelinux.net/books/lkd2/ch11lev1sec5
http://www.kerneltravel.net/journal/v/mem.htm

 

3. 使用者態(ring3)記憶體申請:malloc/free

Relevant Link:

http://www.cnblogs.com/hanyonglu/archive/2011/04/28/2031271.html

 

4. 核心記憶體申請原則

0x1: kmalloc

1. 要特別注意根據當前"CPU中斷上下文",使用正確的標誌位申請記憶體
    1) 判斷申請記憶體的時候可否睡眠,也就是呼叫kmalloc的時候能否被阻塞
    2) 如果在一箇中斷處理,在中斷處理的下半部分,或者有一個鎖的時候,就不能被阻塞
    3) 如果在一個程式上下文,也沒有鎖,則一般可以睡眠
    4) 在kprobe的回撥handle中,當前CPU處於"關中斷"狀態,這個時候就不能使用"GFP_KERNEL"標誌位進行kmalloc記憶體申請,否則可能會因為發生kmalloc暫時申請不到記憶體而產生睡眠等待,繼而繼續產生CPU中斷,然而在CPU關中斷情況下,CPU是無法響應新的中斷的,這個時候就會引起核心panic

2. 如果可以睡眠(CPU可以響應新的中斷),指定GFP_KERNEL 

3. 如果不能睡眠(CPU當前無法響應新的中斷),就指定GFP_ATOMIC 
GFP_KERNEL是linux記憶體分配器的標誌,標識著記憶體分配器將要採取的行為。分配器標誌分為行為修飾符,區修飾符及型別。行為修飾符表示核心應當如何分配所需的記憶體。區修飾符表示記憶體區應當從何處分配。型別就是行為修飾符和區修飾符的合體
/*
#define GFP_KERNEL(__GFP_WAIT | __GFP_IO | __GFP_FS)
__GFP_WAIT: 缺記憶體頁的時候可以睡眠
__GFP_IO: 允許啟動磁碟IO
__GFP_FS: 允許啟動檔案系統IO
*/
其中,缺頁中斷的處理涉及到硬碟外設的硬體中斷的響應,但是在關中斷情況下,CPU是無法響應硬碟外設的中斷請求的,這時候有可能導致發生缺頁中斷的記憶體永遠不可用

4. 如果需要DMA可以訪問的記憶體,比如ISA或者有些PCI裝置,就需要指定GFP_DMA 

5. 需要對kmalloc返回的值檢查NULL 

6. 為了沒有記憶體洩漏,需要用kfree()來釋放記憶體

0x2: vmalloc

1. vmalloc()分配的記憶體在 VMALLOC_START~4GB之間的一段非連續記憶體區域,這段非連續記憶體區對映到實體記憶體也可能是非連續的
2. 在核心空間中呼叫kmalloc()分配連續物理空間,而呼叫vmalloc()分配非物理連續空間 
3. 把kmalloc()所分配核心空間中的地址稱為"核心邏輯地址"
4. 把vmalloc()分配的核心空間中的地址稱"核心虛擬地址"
5. vmalloc()在分配過程中須更新核心頁表

0x3: kmalloc和vmalloc的區別

1. kmalloc保證分配的記憶體在物理上是連續的,kmalloc()分配的記憶體在0xBFFFFFFF-0xFFFFFFFF以上的記憶體中
    1) kmalloc分配的是一段"非分頁記憶體(not pageable memory)"
    2) vmalloc分配的是一段"可分頁記憶體(pageable memory)"

2. vmalloc保證的是
    1) 在虛擬地址空間上的連續
    2) 實體地址非連續
起始位置由VMALLOL_START來決定,一般作為交換區(可被交換到磁碟swap中)、模組的分配

3. kmalloc能分配的大小有限,vmalloc和malloc能分配的大小相對較大(因為vmalloc還可以處理交換空間)

4. vmalloc使用的正確場合是分配一大塊,連續的,只在軟體中存在的,用於緩衝的記憶體區域。不能在微處理器之外使用 

5. vmalloc 中呼叫了 kmalloc(GFP—KERNEL),因此也不能應用於原子上下文 

6. kmalloc分配記憶體是基於slab,因此slab的一些特性包括著色,對齊等都具備,效能較好。實體地址和邏輯地址都是連續的

Relevant Link:

http://blog.csdn.net/newfeicui/article/details/6437917
http://blog.csdn.net/tigerjibo/article/details/6412881

 

5. 核心中分配實體地址連續的大段記憶體

在核心程式設計中,我們常常需要臨時分配一塊任意大小的實體地址連續的記憶體空間,下面介紹可以使用到的方法

0x1: kmalloc

由於採用了SLUB作為預設記憶體分配器, 所以 kmalloc 工作於 SLUB 分配器之上。核心初始化時,建立一組共 13 個通用物件的緩衝區。值得注意的是,kmalloc() 的底層實現也是基於 __get_free_pages() 來進行的,也正因為如此,kmalloc申請的是一段和實體記憶體一一對應的連續記憶體地址

0x2: __get_free_pages

#include <linux/gfp.h>
__get_free_pages (unsigned int gfp_mask, unsigned int order);

引數說明

1. gfp_mask 
可以直接使用 kmalloc() 函式中使用的引數

2. order 
第二個變數不是指定大小,而表示 2^order 次方個頁,如是 0 就分配一個頁,是 3 就分配 8 個頁

如果想為分配一塊記憶體空間,但嫌計算所需多少頁比較麻煩,那可以使用 get_order() 函式來獲取 order 值

char *buff;
int order;

order = get_order (8192);
buff = __get_free_pages (GFP_KERNEL, order);
if (buff != NULL) 
{ 
    ... 
    free_pages (buff, order);
}

使用該函式時,一定要注意 order 最大值,該最大值定義為 MAX_ORDER ,通常為 11 ,也可能是 10 ,這根據平臺的不同而不同。如果 order 的值國大,則分配失敗的機率就較高,通常使用小於 5 的值,即只分配 32 x PAGE_SIZE 大小的記憶體

0x3: static/全域性變數陣列

使用static或全域性變數陣列, 直接定義變數大小為所需資料大小

static char buffer[ 512 * 1024 * 1024 ]; 

定義512M大小陣列. 不過此方法應用到LKM模組中話,會導致載入模組速度奇慢

0x4: alloc_bootmem

alloc_bootmem()是一種核心記憶體預留的方式,使用alloc_bootmem系列API在start_kernel呼叫mem_init()之前申請所需的連續大記憶體。此段記憶體也就永久保留,除非直接引用所分配的記憶體地址

code example

unsigned long long pf_buf_len = 0x0;
EXPORT_SYMBOL( pf_buf_len );

void *pf_buf_addr = NULL;
EXPORT_SYMBOL( pf_buf_addr );

static int __init pf_buf_len_setup(char *str)
{
    unsigned long long size;
    unsigned int       nid = 0;
    void              *pbuff = NULL;
 // 分析引數
    size = memparse( str, &str );
    if ( *str == '@' ){
        str ++;
        get_option( &str, &nid );
    }
    //printk( KERN_INFO "pf_buf_len: Allocating %llu bytes/n", size );
 // 分配記憶體
    pbuff = alloc_bootmem( size );
    if ( likely( NULL != pbuff ) ) {
        printk( KERN_INFO "pf_buf_len: Allocated %llu bytes at 0x%p(0x%p) on node %u/n",
            size, pbuff, (void *)virt_to_phys(pbuff), nid);
        pf_buf_addr = pbuff;
        pf_buf_len  = size;
        goto out;
    }
    printk( KERN_ERR "pf_buf_len: Allocated %llu bytes fail./n", size );
out:
    return 1;

}
__setup( "pf_buf_len=", pf_buf_len_setup);

Relevant Link:

http://oss.org.cn/kernel-book/ldd3/ch08s03.html
http://www.groad.net/bbs/thread-1113-1-1.html
http://blog.csdn.net/force_eagle/article/details/5275572
http://www.linuxidc.com/Linux/2011-10/45459.htm

 

Copyright (c) 2014 LittleHann All rights reserved

 

相關文章