zoukankan      html  css  js  c++  java
  • Memory Allocation API In Linux Kernel && Linux Userspace、kmalloc vmalloc Difference、Kernel Large Section Memory Allocation

    目录

    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.5includelinuxslab.h

    void *__kmalloc(size_t size, gfp_t flags);

    参数说明,linux-3.15.5includelinuxgfp.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.5includelinuxvmalloc.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
    ",
                  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
    ",
                  (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

  • 相关阅读:
    会话管理?
    为什么要用 Dubbo?
    abstract class和interface有什么区别?
    接口是否可继承接口?抽象类是否可实现(implements)接口?抽象类是否可继承具体类(concrete class)?抽象类中是否可以有静态的main方法?
    用最有效率的方法算出2乘以8等於几?
    如何把一段逗号分割的字符串转换成一个数组?
    查看文件内容有哪些命令可以使用?
    使用哪一个命令可以查看自己文件系统的磁盘空间配额 呢?
    Spring框架中的单例bean是线程安全的吗?
    你更倾向用那种事务管理类型?
  • 原文地址:https://www.cnblogs.com/LittleHann/p/4113830.html
Copyright © 2011-2022 走看看