ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux 内核 genalloc/genpool 通用内存池子系统深度解析

Linux 内核 genalloc/genpool 通用内存池子系统深度解析 Linux 内核 genalloc/genpool 通用内存池子系统深度解析【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux导读genalloc/genpool 是 Linux 内核中一套轻量级的通用内存分配子系统专为管理那些不受常规kmalloc/kfree支配的特殊用途内存而设计——典型场景包括设备片上 SRAM、非缓存内存uncached memory、DMA 缓冲区等。本文以 Documentation/core-api/genalloc.rst 为主线结合 lib/genalloc.c 与 include/linux/genalloc.h 的源码实现完整讲解池的创建、内存注入、分配/释放、分配算法选择与查询辅助接口并给出驱动开发中可直接套用的实战示例帮助读者掌握为任意地址区间定制内存分配器的标准方法。为什么内核需要 genpool从特殊内存到通用模块内核中存在大量内存分配子系统各有明确分工页分配器、slab 分配器、CMA、vmalloc……但有时驱动作者需要为一段特殊用途内存编写自己的分配器——例如某块设备上带有的片上 SRAM、一段不可缓存的内存区域。如果每个驱动都各自实现一个微型分配器内核中就会堆积大量测试不充分、行为各异的分配代码。genalloc 正是为解决这一痛点而生的通用模块它把在任意一段地址区间上做按位图分配的能力抽象出来供所有需要管理特殊内存的代码复用。从源码注释lib/genalloc.c可以看到其定位Basic general purpose allocator for managing special purpose memory, for example, memory that is not managed by the regular kmalloc/kfree interface. Uses for this includes on-device special memory, uncached memory etc.这一子系统源自 2005 年 Jes Sorensen 从sym53c8xx_2驱动中提炼出的分配器于内核 2.6.13 版本合入主线此后经过大量演进。如今任何使用该分配器的代码只需包含头文件linux/genalloc.h。核心设计特性从 lib/genalloc.c 的头部注释与实现中可以提炼出该子系统的几项关键设计按位图管理内存每个内存块chunk配一张位图每一位代表min_alloc_order字节的粒度可在 NMI 等不可阻塞上下文中安全使用分配/释放路径基于cmpxchg原子操作与重试实现不依赖自旋锁因此不会在特殊上下文中因锁竞争而死锁代价是极端情况下可能出现活锁livelock无锁路径的前提是内存充足只有当池中已有足够内存时分配/释放才是完全无锁的向池中新增内存块时仍需要拿锁因此依赖无锁特性的使用者必须确保内存预先注入完毕见 lib/genalloc.c架构依赖其基本原子操作是对long的cmpxchg。在不具备 NMI 安全cmpxchg实现的架构上即未配置CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG分配/释放函数内部会执行BUG_ON(in_nmi())明确禁止在 NMI 处理器中使用见 lib/genalloc.c 与 lib/genalloc.c。核心数据结构pool 与 chunk在深入 API 之前先理解两个基础数据结构定义于 include/linux/genalloc.hstruct gen_pool { spinlock_t lock; /* 保护 chunks 链表与池配置 */ struct list_head chunks; /* 该池管理的所有内存块链表 */ int min_alloc_order; /* 最小分配阶log2 字节数 */ genpool_algo_t algo; /* 分配算法回调 */ void *data; /* 传给算法的附加数据 */ const char *name; /* 池名称managed 接口使用 */ }; struct gen_pool_chunk { struct list_head next_chunk; /* 链表节点 */ atomic_long_t avail; /* 本块剩余可用字节数原子读 */ phys_addr_t phys_addr; /* 内存块物理起始地址 */ void *owner; /* 注入时可携带的私有数据 */ unsigned long start_addr; /* 内存块虚拟起始地址 */ unsigned long end_addr; /* 内存块虚拟结束地址含 */ unsigned long bits[]; /* 柔性数组跟踪分配状态的位图 */ };可以看到一个gen_pool由一串gen_pool_chunk组成每个 chunk 记录一段连续内存的起止地址、物理地址、剩余量并用bits[]位图标记每个分配粒度单元的占用情况。分配算法的本质就是在位图上寻找足够长的连续0位区间。池的生命周期创建、注入内存、销毁创建池gen_pool_create 与 devm_gen_pool_create池的创建有两个入口原型声明见 include/linux/genalloc.h 与 include/linux/genalloc.hstruct gen_pool *gen_pool_create(int min_alloc_order, int nid); struct gen_pool *devm_gen_pool_create(struct device *dev, int min_alloc_order, int nid, const char *name);gen_pool_create()的实现lib/genalloc.c通过kmalloc_node分配池结构、初始化自旋锁与 chunk 链表并把默认分配算法设为gen_pool_first_fit。两个关键参数min_alloc_order分配粒度取以 2 为底的对数但单位是字节而非页与页分配器的 order 语义不同。例如传入3则所有分配都将是 8 字节的整数倍。该值越大每块内存所需的跟踪位图越小位图总位数 chunk 字节数 order管理开销越低代价是内部碎片granularity 浪费越大。nid池的家务结构pool 描述符、chunk 描述符与位图分配所在的 NUMA 节点-1表示不关心使用NUMA_NO_NODE语义。devm_gen_pool_create()是受管managed版本lib/genalloc.c它通过 devres 机制把池绑定到指定设备上当设备被销毁时由devm_gen_pool_release()自动调用gen_pool_destroy()清理池lib/genalloc.c。它还要求同一设备上池名称唯一——如果gen_pool_get(dev, name)已能找到同名池则返回-EINVALlib/genalloc.c。name参数用于按名查找池可为NULL。注入内存gen_pool_add / gen_pool_add_owner新建的池是空的没有任何内存可分。第一步通常是向池中添加内存块static inline int gen_pool_add(struct gen_pool *pool, unsigned long addr, size_t size, int nid); int gen_pool_add_owner(struct gen_pool *pool, unsigned long virt, phys_addr_t phys, size_t size, int nid, void *owner);gen_pool_add()把从addr内核虚拟地址空间开始的size字节放入池中nid用于辅助结构的内存节点。它其实是gen_pool_add_owner()的简化包装include/linux/genalloc.h传入phys -1与owner NULLstatic inline int gen_pool_add(struct gen_pool *pool, unsigned long addr, size_t size, int nid) { return gen_pool_add_virt(pool, addr, -1, size, nid); }gen_pool_add_virt()则显式关联物理地址仅当池将用于 DMA 分配时才必须使用include/linux/genalloc.h。gen_pool_add_owner()的实现lib/genalloc.c会计算位图位数nbits size min_alloc_order用vzalloc_node分配 chunk 描述符 位图零初始化保证全部空闲;填充phys_addr、start_addr、end_addr virt size - 1、owner并将chunk-avail初始化为size在pool-lock保护下把 chunk 挂入pool-chunks链表list_add_rcu。owner是注入时携带的私有数据可在分配时通过owner出参取回见下文分配接口适合传递这块内存属于哪个客户/子模块之类的信息。销毁池gen_pool_destroyvoid gen_pool_destroy(struct gen_pool *pool);销毁逻辑lib/genalloc.c遍历每个 chunk通过find_first_bit()检查位图中第一个被占用位只要还有未归还的分配就执行BUG_ON(bit end_bit)直接崩溃整个系统。这是文档中明确警告的极端手段——销毁前必须确保所有分配都已gen_pool_free()归还。分配与释放gen_pool_alloc / gen_pool_dma_alloc / gen_pool_free常规分配 gen_pool_allocstatic inline unsigned long gen_pool_alloc(struct gen_pool *pool, size_t size);从池中分配size字节返回内核虚拟地址unsigned long类型失败返回 0。它使用池当前的默认算法pool-algo与附加数据pool-data即gen_pool_alloc_algo_owner()的薄封装include/linux/genalloc.h。核心路径gen_pool_alloc_algo_owner()lib/genalloc.c的工作流程非 NMI 安全架构上先BUG_ON(in_nmi())防护把请求的size向上取整为粒度整数倍得到所需位数nbits在rcu_read_lock()保护下遍历各 chunk跳过size chunk-avail的块调用分配算法algo()在块位图上定位候选位置start_bit用无锁的bitmap_set_ll()尝试置位若与其他并发分配者冲突则清位重试goto retry成功后计算返回地址chunk-start_addr (start_bit order)并原子地atomic_long_sub(size, chunk-avail)扣减剩余量。DMA 分配系列void *gen_pool_dma_alloc(struct gen_pool *pool, size_t size, dma_addr_t *dma); void *gen_pool_dma_alloc_algo(struct gen_pool *pool, size_t size, dma_addr_t *dma, genpool_algo_t algo, void *data); void *gen_pool_dma_alloc_align(struct gen_pool *pool, size_t size, dma_addr_t *dma, int align); void *gen_pool_dma_zalloc(struct gen_pool *pool, size_t size, dma_addr_t *dma); void *gen_pool_dma_zalloc_algo(struct gen_pool *pool, size_t size, dma_addr_t *dma, genpool_algo_t algo, void *data); void *gen_pool_dma_zalloc_align(struct gen_pool *pool, size_t size, dma_addr_t *dma, int align);gen_pool_dma_alloc()lib/genalloc.c用于分配供 DMA 操作使用的内存它内部调用常规分配拿到虚拟地址再通过gen_pool_virt_to_phys()把对应的物理地址写入dma出参lib/genalloc.c。前提是这些内存必须以gen_pool_add_virt()方式注入即带上了物理地址否则物理地址换算无意义。与常规 API 的一个明显差异它返回void *而非unsigned long符合返回虚拟地址指针的语义。配套变体还包括gen_pool_dma_alloc_align()要求起始地址按align字节对齐内部构造genpool_data_align并改用gen_pool_first_fit_align算法lib/genalloc.cgen_pool_dma_zalloc()系列分配后用memset将内存清零lib/genalloc.c。归还内存 gen_pool_freestatic inline void gen_pool_free(struct gen_pool *pool, unsigned long addr, size_t size); void gen_pool_free_owner(struct gen_pool *pool, unsigned long addr, size_t size, void **owner);gen_pool_free()归还之前分配的size字节必须与分配时传入的size匹配。实现lib/genalloc.c先在rcu_read_lock()下定位包含addr的 chunk然后若addr size - 1超出该 chunk 范围触发BUG_ON释放越界属于编程错误用无锁的bitmap_clear_ll()清位冲突时同样BUG_ON原子地把释放量加回chunk-avail若遍历完所有 chunk 都没找到该地址直接BUG()。gen_pool_free_owner()额外通过owner出参返回注入时记录的 chunk 私有数据便于调用方做资源归属统计。分配算法从 first-fit 到 fixed-alloc上述接口只解决从池里拿内存却不控制具体拿哪一段。如果需要精细控制就要用到算法接口unsigned long gen_pool_alloc_algo_owner(struct gen_pool *pool, size_t size, genpool_algo_t algo, void *data, void **owner); void gen_pool_set_algo(struct gen_pool *pool, genpool_algo_t algo, void *data);gen_pool_alloc_algo()系列在单次分配时显式指定算法gen_pool_set_algo()则设置池的默认算法传NULL会回退为gen_pool_first_fit见 lib/genalloc.c。data参数会原样传给算法多数算法忽略它但对齐与定点分配算法会用到。算法回调的类型定义include/linux/genalloc.htypedef unsigned long (*genpool_algo_t)(unsigned long *map, unsigned long size, unsigned long start, unsigned int nr, void *data, struct gen_pool *pool, unsigned long start_addr);内核预置了五套算法全部实现于 lib/genalloc.c算法行为源码位置gen_pool_first_fit简单首次适配无对齐约束默认算法lib/genalloc.cgen_pool_first_fit_align强制按genpool_data_align.align字节对齐lib/genalloc.cgen_pool_first_fit_order_align按请求大小的 2 的幂阶对齐如 60 字节分配按 64 字节对齐lib/genalloc.cgen_pool_best_fit最佳适配选出最小的足以容纳请求的空闲区lib/genalloc.cgen_pool_fixed_alloc在池内指定偏移处定点分配该处不可用则分配失败lib/genalloc.c配套的数据描述符include/linux/genalloc.hstruct genpool_data_align { int align; /* 起始地址按字节对齐 */ }; struct genpool_data_fixed { unsigned long offset; /* 定点分配的区域偏移 */ };几个值得注意的实现细节first_fit_align先把align换算成位图粒度的align_mask再考虑 chunk 起始地址本身的偏移align_off (start_addr (align - 1)) order最终调用bitmap_find_next_zero_area_off()完成带对齐的搜索lib/genalloc.cfirst_fit_order_alignalign_mask roundup_pow_of_two(nr) - 1即按请求位数向上取整为 2 的幂后减一作为掩码lib/genalloc.c。文档示例60 字节请求在 order0 时nr60对齐到 64 字节fixed_alloc若offset不是粒度整数倍会WARN_ON并返回失败随后只在start offset_bit处尝试若bitmap_find_next_zero_area()的结果不是offset_bit本身说明该区域已被占用则直接返回size表示找不到lib/genalloc.cbest_fit遍历所有候选空洞跟踪最小空洞长度len找到与nr完全匹配的区间即提前返回lib/genalloc.c。查询与辅助接口文档还列出了一组查询/遍历辅助函数全部实现于 lib/genalloc.cgen_pool_virt_to_phys把池内虚拟地址换算为物理地址未命中返回-1。实现为遍历各 chunk若addr落在[start_addr, end_addr]区间内则paddr chunk-phys_addr (addr - chunk-start_addr)lib/genalloc.cgen_pool_for_each_chunk对池内每个 chunk 调用回调函数回调在rcu_read_lock持有时执行lib/genalloc.cgen_pool_has_addr判断[start, startsize)区间是否完整落在池的某个 chunk 内lib/genalloc.cgen_pool_avail累加所有 chunk 的avail返回当前空闲总字节数lib/genalloc.cgen_pool_size累加所有 chunk 的chunk_size返回池管理的总字节数lib/genalloc.cgen_pool_get按设备与名称查找与设备关联的受管池lib/genalloc.cof_gen_pool_get按设备树 phandle 属性查找池仅在CONFIG_OF下可用lib/genalloc.c。实战驱动中的典型用法场景一设备树 SRAM 池remoteproc 实例genpool 最典型的现实应用之一是把设备树描述的片上 SRAM 纳入池管理。以 drivers/remoteproc/meson_mx_ao_arc.c 为例该驱动在probe中通过of_gen_pool_get()按设备树属性sram获取 SRAM 池priv-sram_pool of_gen_pool_get(dev-of_node, sram, 0); /* L163 */ if (!priv-sram_pool) { ... } priv-sram_size gen_pool_avail(priv-sram_pool); /* L169 */ priv-sram_va gen_pool_alloc(priv-sram_pool, priv-sram_size); /* L171 */ priv-sram_pa gen_pool_virt_to_phys(priv-sram_pool, priv-sram_va); /* L177 */在移除路径中则对称地归还gen_pool_free(priv-sram_pool, priv-sram_va, priv-sram_size); /* L227 / L237 */这段代码几乎完整覆盖了本文章介绍的核心 API按设备树取池 → 查询可用量 → 分配 → 虚拟地址转物理地址 → 释放。SRAM 池本身由mm/shmem之外的 SRAM 驱动如drivers/misc/sram.c体系的of_gen_pool_get链路预先创建并注入内存genpool 只负责位图化分配。场景二驱动内自建受管池模板当驱动需要管理设备自带的特殊内存如片上 SRAM 区、专用缓冲区时标准流程如下#include linux/genalloc.h #include linux/of.h struct my_dev { struct device *dev; struct gen_pool *pool; unsigned long buf_va; dma_addr_t buf_pa; }; static int my_dev_probe(struct platform_device *pdev) { struct my_dev *mdev devm_kzalloc(pdev-dev, sizeof(*mdev), GFP_KERNEL); /* 1. 创建受管池粒度 4 字节order2节点不关心 */ mdev-pool devm_gen_pool_create(pdev-dev, 2, -1, mydev-pool); if (IS_ERR(mdev-pool)) return PTR_ERR(mdev-pool); /* 2. 注入内存设备寄存器/平台代码提供的 SRAM 区间 */ /* 若后续要做 DMA必须用 gen_pool_add_virt() 带上物理地址 */ if (gen_pool_add_virt(mdev-pool, (unsigned long)sram_va, (phys_addr_t)sram_pa, sram_size, -1)) return -ENOMEM; /* 3. 分配并取得 DMA 物理地址 */ mdev-buf_va (unsigned long) gen_pool_dma_alloc(mdev-pool, buf_size, mdev-buf_pa); if (!mdev-buf_va) return -ENOMEM; /* 4. 设备销毁时 devm 自动销毁池此处只需显式归还内存 */ /* gen_pool_free(mdev-pool, mdev-buf_va, buf_size); */ return 0; }要点归纳池在设备生命周期内有效devm_gen_pool_create()保证设备移除时自动销毁无需手工调用gen_pool_destroy()需要 DMA 时务必用gen_pool_add_virt()而非gen_pool_add()注入带物理地址的内存否则gen_pool_virt_to_phys()无法得到正确结果销毁池之前必须归还所有分配否则gen_pool_destroy()会BUG()崩掉系统若对起始地址有对齐要求改用gen_pool_dma_alloc_align()或显式gen_pool_set_algo(pool, gen_pool_first_fit_align, align_data)。总结与设计建议genpool 为管理任意一段特殊用途内存提供了标准答案位图跟踪 原子位操作的无锁分配 可插拔分配算法 设备受管生命周期。理解它的关键在于把握几条主线粒度 trade-offmin_alloc_order越大位图越小、管理开销越低但分配粒度越粗无锁的代价NMI 安全的分配依赖CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG与预注入的充足内存两者缺一不可算法即插即用默认 first-fit 适合大多数场景对齐/定点/最佳适配算法通过data描述符扩展也可自行实现genpool_algo_t回调严格的对称性gen_pool_free()必须与分配的size严格一致越界与未归还都是致命编程错误物理地址只服务于 DMAgen_pool_add_virt()注入的物理地址是gen_pool_dma_alloc()与gen_pool_virt_to_phys()的前提。正如文档结语所言更广泛地了解该模块有助于避免未来继续出现大量特制的专用内存分配器——需要管理特殊内存时先看看 genpool 是否已经够用。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表