kernel内存分配中的vmalloc

Linux内核使用二进制伙伴算法管理物理内存,vmalloc接口用于分配虚拟内存中连续但物理上不连续的内存。文章讨论了伙伴系统的工作原理、内存碎片问题、vmalloc的使用场景及其实现细节,包括数据结构和内存管理策略。

在内核初始化完成之后, 内存管理的责任就由伙伴系统来承担. 伙伴系统基于一种相对简单然而令人吃惊的强大算法.

Linux内核使用二进制伙伴算法来管理和分配物理内存页面, 该算法由Knowlton设计, 后来Knuth又进行了更深刻的描述.

伙伴系统是一个结合了2的方幂个分配器和空闲缓冲区合并计技术的内存分配方案, 其基本思想很简单. 内存被分成含有很多页面的大块, 每一块都是2个页面大小的方幂. 如果找不到想要的块, 一个大块会被分成两部分, 这两部分彼此就成为伙伴. 其中一半被用来分配, 而另一半则空闲. 这些块在以后分配的过程中会继续被二分直至产生一个所需大小的块. 当一个块被最终释放时, 其伙伴将被检测出来, 如果伙伴也空闲则合并两者.

  • 内核如何记住哪些内存块是空闲的
  • 分配空闲页面的方法
  • 影响分配器行为的众多标识位
  • 内存碎片的问题和分配器如何处理碎片

    当buddy系统还有大量的连续物理内存时,我们可以通过__pages_alloc成功分配很大的一块连续物理内存空间,随着系统运行时间加长,buddy系统内很难中找到一块大的连续物理内存空间,因此__pages_alloc可能会失败,即便通过kswapd进行页面的回收和交换,buddy仍然不可避免的碎片化

首先我们要明确的是,连续物理内存的分配并不是必要的。对于大部分DMA操作,我们的确需要连续的物理内存;但是对于某些分配内存情况:比如,模块加载,设备和声音驱动程序中,可以在内核源码中关键字vmalloc查找,对vmalloc的使用有个感性认识。

vmalloc把buddy系统内的不连续物理内存,映射到内核中一段连续的地址空间内,因此对于那些无法直接映射的高端物理内存Highmem来说,vmalloc是主要用途之一。因此vmalloc理应优先使用廉价的Highmem内存,而把宝贵的低端内存,留给其他的内核操作。事实上也是如此,vmalloc实现函数的分配标志,指明了从Highmem分配
vmalloc是一个接口函数, 内核代码使用它来分配在虚拟内存中连续但在物理内存中不一定连续的内存


/**
 *  vmalloc  -  allocate virtually contiguous memory
 *  @size:      allocation size
 *  Allocate enough pages to cover @size from the page level
 *  allocator and map them into contiguous kernel virtual space.
 *
 *  For tight control over page level allocator and protection flags
 *  use __vmalloc() instead.
 */
void *vmalloc(unsigned long size)
{
    return __vmalloc_node_flags(size, NUMA_NO_NODE,
                    GFP_KERNEL | __GFP_HIGHMEM);
}

对于vmalloc来说是需要预留一定的地址空间的。而DMA和Normal内存zone 又需要占用数百M的地址空间,参见下面这个经典的kernel地址空间划分图
这里写图片描述

Persistent mappings和Fixmaps地址空间都比较小,这里我们忽略它们,这样只剩下直接地址映射和VMALLOC区,这个划分应该是平衡两个需求的结果

  1. 尽量增加DMA和Normal区大小,也就是直接映射地址空间大小,当前主流平台的内存,基本上都超过了512MB,很多都是标配1GB内存,因此注定有一部分内存无法进行线性映射。

  2. 保留一定数量的VMALLOC大小,这个值是应用平台特定的,如果应用平台某个驱动需要用vmalloc分配很大的地址空间,那么最好通过在kernel参数中指定vmalloc大小的方法,预留较多的vmalloc地址空间。

  3. 并不是Highmem没有或者越少越好,这个是我的个人理解,理由如下:高端内存就像个垃圾桶和缓冲区,防止来自用户空间或者vmalloc的映射破坏Normal zone和DMA zone的连续性,使得它们碎片化。当这个垃圾桶较大时,那么污染Normal 和DMA的机会自然就小了。

下面的图是VMALLOC地址空间内部划分情况
这里写图片描述

在直接地址映射和VMALLOC区之间有一个8MiB的隔离带,隔离带是做什么的呢? 隔离带是用来针对内核故障的保护措施,当访问虚拟地址越界时,则会产生一个page fault异常,也就是说这个内核地址空间没有对应相应的物理地址,这在内核地址空间是不允许的。如果不存在隔离带,那么越界访问不知不觉的跨越直接映射和VMALLOC区,内核却没注意到这个错误。
在VMALLOC内部,会划分为多个vmalloc_area,每个vmalloc_area直间有一个4KB的地址空隙,通过这个小的隔离,可以防止不同映射区直接的越界访问。

数据结构

内核在管理虚拟内存中的vmalloc区域时, 内核必须跟踪哪些子区域被使用、哪些是空闲的. 为此定义了一个数据结构vm_struct, 将所有使用的部分保存在一个链表中. 该结构提的定义在include/linux/vmalloc.h


struct vm_struct {
    struct vm_struct    *next;//所有的vm_struct通过next 组成一个单链表,表头为全局变量vmlist
    void            *addr;//定义了这个虚拟地址空间子区域的起始地址
    unsigned long       size; //定义了这个虚拟地址空间子区域的大小
    unsigned long       flags; //存储了与该内存区关联的标志
    struct page     **pages; //是一个指针,指向page指针的数组,每个数组成员都表示一个映射到这个地址空间的物理页面的实例
    unsigned int        nr_pages; //page指针数据的长度
    phys_addr_t     phys_addr;  //仅当用ioremap映射了由物理地址描述的物理内存区域才有效。
    const void      *caller;
};

其中flags只用于指定内存区类型, 所有可能的flag标识以宏的形式定义在include/linux/vmalloc.h

/* bits in flags of vmalloc's vm_struct below */
#define VM_IOREMAP      0x00000001  /* ioremap() and friends *///表示将几乎随机的物理内存区域映射到vmalloc区域中. 这是一个特定于体系结构的操作
#define VM_ALLOC        0x00000002  /* vmalloc() *///指定由vmalloc产生的子区域
#define VM_MAP          0x00000004  /* vmap()ed pages *///用于表示将现存pages集合映射到连续的虚拟地址空间中
#define VM_USERMAP      0x00000008</
<think>好的,用户想了解Linux内核中常用的内存分配函数。我需要先回忆一下这方面的知识,然后整理出主要的接口和它们的特点。 首先,想到的是kmalloc,这是最常用的动态内存分配函数,用于分配连续物理内存。然后是vmalloc,它分配虚拟地址连续但物理地址不一定连续的内存。这两个的区别很重要,应该要分清楚应用场景。 还有kzalloc,它类似于kmalloc,但会自动清零内存,这对安全敏感的操作很有用。接下来是页分配器,比如alloc_pages,用于分配大块内存,通常以页为单位。 slab分配器也是关键,比如kmem_cache_create,用于高效管理频繁分配和释放的小对象。此外,用户可能还关心GFP标志,比如GFP_KERNEL和GFP_ATOMIC,这些标志控制内存分配的行为,比如是否允许休眠。 需要提到内存释放的函数,如kfree和vfree,以及释放页面的函数。可能还要区分不同上下文中的使用限制,比如在中断上下文中必须使用GFP_ATOMIC。 还要注意内存分配的注意事项,比如避免内存泄漏和碎片化问题。可能需要引用一些内核文档或资料来支持说明,比如提到的CONFIG_RELAY可能涉及到内存分配的使用场景[^1]。 用户的问题可能涉及驱动开发或内核模块编写,所以实际例子会有帮助。比如在驱动中如何使用这些函数,或者像引用中的dm9161驱动修改的例子,虽然不直接相关,但说明内核开发中的实际调整[^2]。 还要检查是否有遗漏的函数,比如devm_系列的资源管理分配函数,它们自动释放内存,防止泄漏。这也是内核驱动中常用的方法。 最后,生成的相关问题需要围绕这些函数的区别、使用场景、最佳实践和常见问题,帮助用户深入理解。比如不同GFP标志的影响,或者如何选择kmalloc和vmalloc,以及如何处理内存不足的情况。</think>### Linux内核常用内存分配接口 在Linux内核开发中,动态内存管理需要依赖特定的接口,以下是核心的内存分配函数及其特点: #### 1. **`kmalloc`/`kfree`** - **用途**:分配**物理地址连续**的小块内存(通常小于一页)。 - **语法**:`void *kmalloc(size_t size, gfp_t flags);` - **特点**: - 返回的地址可直接用于DMA操作[^3]。 - 常用`GFP_KERNEL`(允许休眠)或`GFP_ATOMIC`(原子上下文,如中断)作为标志。 - **示例**: ```c char *buf = kmalloc(1024, GFP_KERNEL); if (!buf) return -ENOMEM; kfree(buf); // 必须显式释放 ``` #### 2. **`vmalloc`/`vfree`** - **用途**:分配**虚拟地址连续但物理地址可能不连续**的大内存块。 - **语法**:`void *vmalloc(unsigned long size);` - **特点**: - 适用于需要大内存但无需物理连续性的场景(如模块加载)。 - 不适用于DMA操作。 - **示例**: ```c void *mem = vmalloc(4096 * 16); if (!mem) return -ENOMEM; vfree(mem); ``` #### 3. **`kzalloc`/`kzfree`** - **用途**:分配并**初始化为零**的内存(等价于`kmalloc + memset`)。 - **语法**:`void *kzalloc(size_t size, gfp_t flags);` - **适用场景**:安全敏感操作(如密码缓冲区)。 #### 4. **页分配器(`alloc_pages`/`__get_free_pages`)** - **用途**:以**页(4KB)为单位**分配物理连续内存。 - **语法**: ```c struct page *alloc_pages(gfp_t flags, unsigned int order); // 返回页描述符 unsigned long __get_free_pages(gfp_t flags, unsigned int order); // 返回虚拟地址 ``` - **特点**:`order`参数表示分配$2^{\text{order}}$页,例如`order=3`分配8页。 #### 5. **Slab分配器(`kmem_cache`)** - **用途**:高效管理**频繁分配/释放的小对象**(如结构体)。 - **流程**: ```c // 创建缓存池 struct kmem_cache *cache = kmem_cache_create("my_cache", sizeof(struct my_struct), 0, 0, NULL); // 分配对象 struct my_struct *obj = kmem_cache_alloc(cache, GFP_KERNEL); // 释放对象 kmem_cache_free(cache, obj); // 销毁缓存池 kmem_cache_destroy(cache); ``` #### 6. **资源管理分配(`devm_kmalloc`)** - **用途**:自动释放内存(绑定到设备生命周期),防止内存泄漏。 - **示例**: ```c char *buf = devm_kmalloc(dev, size, GFP_KERNEL); // 设备卸载时自动释放 ``` --- ### 选择依据与注意事项 1. **物理连续性要求**: - DMA或硬件交互 → `kmalloc`或页分配器。 - 软件逻辑使用 → `vmalloc`。 2. **内存大小**: - 小块内存(<1页) → `kmalloc`。 - 大块内存(多页) → `vmalloc`或页分配器。 3. **上下文限制**: - 中断上下文 → 必须使用`GFP_ATOMIC`。 4. **性能优化**: - 高频小对象 → Slab分配器。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值