genalloc/genpool 子系统

内核中存在许多内存分配子系统,每个都旨在满足特定的需求。然而,有时内核开发者需要为特定范围的专用内存实现一个新的分配器;这些内存通常位于某个设备上。该设备驱动的作者当然可以编写一个小的分配器来完成这项工作,但这会导致内核中充斥着数十个测试不充分的分配器。早在2005年,Jes Sorensen 从 sym53c8xx_2 驱动程序中提取了其中一个分配器,并将其作为创建专用内存分配器的通用模块发布。这段代码在 2.6.13 版本中被合并;此后它被大大修改了。

使用此分配器的代码应包含 <linux/genalloc.h>。操作从使用以下函数之一创建内存池开始:

struct gen_pool *gen_pool_create(int min_alloc_order, int nid)

创建一个新的专用内存池

参数

int min_alloc_order

每个位图位代表的字节数的 2 的对数

int nid

内存池结构应在其上分配的节点 ID,或 -1

描述

创建一个新的专用内存池,可用于管理非由常规 kmalloc/kfree 接口管理的特殊用途内存。

struct gen_pool *devm_gen_pool_create(struct device *dev, int min_alloc_order, int nid, const char *name)

受管理的 gen_pool_create

参数

struct device *dev

提供 gen_pool 的设备

int min_alloc_order

每个位图位代表的字节数的 2 的对数

int nid

用于已分配 gen_pool 的节点选择器,对于所有节点使用 NUMA_NO_NODE

const char *name

gen_pool 的名称或 NULL,用于标识设备上的特定 gen_pool

描述

创建一个新的专用内存池,可用于管理非由常规 kmalloc/kfree 接口管理的特殊用途内存。该内存池将由设备管理代码自动销毁。

调用 gen_pool_create() 将创建一个内存池。分配的粒度由 min_alloc_order 设置;它是一个类似于页分配器使用的 2 的对数基数(log-base-2)值,但它指的是字节而非页。因此,如果 min_alloc_order 传入 3,则所有分配都将是 8 字节的倍数。增加 min_alloc_order 会减少跟踪内存池中内存所需的内存量。nid 参数指定应使用哪个 NUMA 节点来分配管理结构;如果调用者不关心,则可以为 -1。

“托管”接口 devm_gen_pool_create() 将内存池绑定到特定设备。此外,当给定设备被销毁时,它会自动清理内存池。

内存池通过以下函数关闭:

void gen_pool_destroy(struct gen_pool *pool)

销毁专用内存池

参数

struct gen_pool *pool

要销毁的内存池

描述

销毁指定的专用内存池。验证没有未完成的分配。

值得注意的是,如果给定内存池中仍有未完成的分配,此函数将采取相当极端的措施,即调用 BUG(),导致整个系统崩溃。特此警告。

一个新创建的内存池没有可供分配的内存。在这种状态下它几乎无用,因此首要任务之一通常是向内存池添加内存。这可以通过以下函数之一完成:

int gen_pool_add(struct gen_pool *pool, unsigned long addr, size_t size, int nid)

向内存池添加一个新的专用内存块

参数

struct gen_pool *pool

要添加新内存块的内存池

unsigned long addr

要添加到内存池的内存块的起始地址

size_t size

要添加到内存池的内存块的字节大小

int nid

内存块结构和位图应在其上分配的节点 ID,或 -1

描述

向指定的内存池添加一个新的专用内存块。

成功返回 0,失败返回负数 errno。

int gen_pool_add_owner(struct gen_pool *pool, unsigned long virt, phys_addr_t phys, size_t size, int nid, void *owner)

向内存池添加一个新的专用内存块

参数

struct gen_pool *pool

要添加新内存块的内存池

unsigned long virt

要添加到内存池的内存块的虚拟起始地址

phys_addr_t phys

要添加到内存池的内存块的物理起始地址

size_t size

要添加到内存池的内存块的字节大小

int nid

内存块结构和位图应在其上分配的节点 ID,或 -1

void *owner

发布者希望在分配时回溯的私有数据

描述

向指定的内存池添加一个新的专用内存块。

成功返回 0,失败返回负数 errno。

调用 gen_pool_add() 会将从 addr 开始(在内核的虚拟地址空间中)的 size 字节内存放入给定内存池中,再次使用 nid 作为辅助内存分配的节点 ID。gen_pool_add_virt() 变体将一个显式物理地址与内存关联起来;只有当内存池用于 DMA 分配时才需要这样做。

从内存池分配内存(并将其放回)的函数是:

unsigned long gen_pool_alloc(struct gen_pool *pool, size_t size)

从内存池分配专用内存

参数

struct gen_pool *pool

要从中分配的内存池

size_t size

要从内存池分配的字节数

描述

从指定的内存池中分配请求的字节数。使用内存池分配函数(默认为首次适应算法)。在没有 NMI 安全的 cmpxchg 实现的架构上,不能在 NMI 处理程序中使用此函数。

void *gen_pool_dma_alloc(struct gen_pool *pool, size_t size, dma_addr_t *dma)

从内存池为 DMA 用途分配专用内存

参数

struct gen_pool *pool

要从中分配的内存池

size_t size

要从内存池分配的字节数

dma_addr_t *dma

DMA 视图的物理地址返回值。如果不需要,请使用 NULL

描述

从指定的内存池中分配请求的字节数。使用内存池分配函数(默认为首次适应算法)。在没有 NMI 安全的 cmpxchg 实现的架构上,不能在 NMI 处理程序中使用此函数。

返回值

分配内存的虚拟地址,失败时为 NULL

void gen_pool_free_owner(struct gen_pool *pool, unsigned long addr, size_t size, void **owner)

将已分配的专用内存释放回内存池

参数

struct gen_pool *pool

要释放到的内存池

unsigned long addr

要释放回内存池的内存起始地址

size_t size

要释放内存的字节大小

void **owner

gen_pool_add() 时存储的私有数据

描述

将先前分配的专用内存释放回指定的内存池。在没有 NMI 安全的 cmpxchg 实现的架构上,不能在 NMI 处理程序中使用此函数。

正如预期,gen_pool_alloc() 将从给定内存池分配 `size` 字节的内存。gen_pool_dma_alloc() 变体为 DMA 操作分配内存,并在 dma 指向的空间中返回关联的物理地址。这仅在内存通过 gen_pool_add_virt() 添加时才有效。请注意,此函数与 genpool 通常使用 unsigned long 值表示内核地址的模式不同;它返回一个 void *。

这一切看起来相对简单;事实上,一些开发者显然发现它过于简单。毕竟,上述接口无法控制分配函数如何选择返回哪个特定的内存块。如果需要这种控制,以下函数将很有用:

unsigned long gen_pool_alloc_algo_owner(struct gen_pool *pool, size_t size, genpool_algo_t algo, void *data, void **owner)

从内存池分配专用内存

参数

struct gen_pool *pool

要从中分配的内存池

size_t size

要从内存池分配的字节数

genpool_algo_t algo

调用者传入的算法

void *data

传递给算法的数据

void **owner

可选地检索块所有者

描述

从指定的内存池中分配请求的字节数。使用内存池分配函数(默认为首次适应算法)。在没有 NMI 安全的 cmpxchg 实现的架构上,不能在 NMI 处理程序中使用此函数。

void gen_pool_set_algo(struct gen_pool *pool, genpool_algo_t algo, void *data)

设置分配算法

参数

struct gen_pool *pool

要更改分配算法的内存池

genpool_algo_t algo

自定义算法函数

void *data

algo 使用的额外数据

描述

对内存池中的每个内存分配调用 algo。如果 algo 为 NULL,则使用 gen_pool_first_fit 作为默认内存分配函数。

使用 gen_pool_alloc_algo() 的分配指定用于选择要分配内存的算法;默认算法可以通过 gen_pool_set_algo() 设置。数据值会传递给算法;大多数算法会忽略它,但有时会需要。当然,可以编写一个专用算法,但目前已经有一套相当不错的可用算法:

  • gen_pool_first_fit 是一个简单的首次适应分配器;如果没有指定其他算法,这就是默认算法。

  • gen_pool_first_fit_align 强制分配具有特定对齐方式(通过 genpool_data_align 结构中的数据传递)。

  • gen_pool_first_fit_order_align 将分配对齐到大小的阶数。例如,一个 60 字节的分配将对齐到 64 字节。

  • gen_pool_best_fit,正如人们所料,是一个简单的最佳适应分配器。

  • gen_pool_fixed_alloc 在内存池内的一个特定偏移量处(通过数据参数在 genpool_data_fixed 结构中传递)分配内存。如果指示的内存不可用,则分配失败。

还有一些其他函数,主要用于查询内存池中的可用空间或遍历内存块等目的。然而,大多数用户应该不需要超出上述描述的功能。希望对这个模块的更广泛认知将有助于在未来避免编写专用内存分配器。

phys_addr_t gen_pool_virt_to_phys(struct gen_pool *pool, unsigned long addr)

返回内存的物理地址

参数

struct gen_pool *pool

要从中分配的内存池

unsigned long addr

内存起始地址

描述

成功返回物理地址,失败返回 -1。

void gen_pool_for_each_chunk(struct gen_pool *pool, void (*func)(struct gen_pool *pool, struct gen_pool_chunk *chunk, void *data), void *data)

对通用内存池的每个内存块调用 func

参数

struct gen_pool *pool

通用内存池

void (*func)(struct gen_pool *pool, struct gen_pool_chunk *chunk, void *data)

要调用的函数 func

void *data

func 使用的额外数据

描述

对通用内存池的每个内存块调用 funcfunc 在持有 rcu_read_lock 的情况下被调用。

bool gen_pool_has_addr(struct gen_pool *pool, unsigned long start, size_t size)

检查地址是否在内存池范围内

参数

struct gen_pool *pool

通用内存池

unsigned long start

起始地址

size_t size

区域大小

描述

检查地址范围是否落在指定的内存池内。如果整个范围都包含在内存池中则返回 true,否则返回 false。

size_t gen_pool_avail(struct gen_pool *pool)

获取内存池的可用空闲空间

参数

struct gen_pool *pool

要获取可用空闲空间的内存池

描述

返回指定内存池的可用空闲空间。

size_t gen_pool_size(struct gen_pool *pool)

获取内存池管理的内存字节大小

参数

struct gen_pool *pool

要获取大小的内存池

描述

返回内存池管理的内存字节大小。

struct gen_pool *gen_pool_get(struct device *dev, const char *name)

获取设备的 gen_pool(如果有)

参数

struct device *dev

要从中检索 gen_pool 的设备

const char *name

gen_pool 的名称或 NULL,用于标识设备上的特定 gen_pool

描述

如果设备存在 gen_pool 则返回它,否则返回 NULL。

struct gen_pool *of_gen_pool_get(struct device_node *np, const char *propname, int index)

通过 phandle 属性查找内存池

参数

struct device_node *np

设备节点

const char *propname

包含 phandle(s) 的属性名称

int index

phandle 数组中的索引

描述

返回包含从 phandle 属性指向的设备树节点的物理地址开始的内存块的内存池,如果未找到则返回 NULL。