0%

linux 内核内存分配函数

linux 内核内存分配函数

1 伙伴系统

1.1 alloc_page/alloc_pages/free_pages

1.1.1 alloc_pages_node

1
struct page *alloc_pages_node(int nid, unsigned int flags, unsigned int order);
参数 含义
nid NUMA node ID
flags Usual GFP_ allocation flags
order he size of the allocation
  • 现在的机器上都是有多个CPU和多个内存块的,以前我们都是将内存块看成是一大块内存,所有CPU到这个共享内存访问消息是一样的,这就是之前普遍使用的SMP模型

  • 但是随着处理器的增加,共享内存可能会导致内存访问冲突越来越厉害,NUMA(Non-Uniform Memory Access)就是在这样的环境下引入的一个模型,比如一台机器有2个处理器、4个内存块,我们将1个处理器和两个内存块关联起来,称为一个 NUMA node,这样这个机器就会有两个 NUMA node,在物理分布上,NUMA node 的处理器和内存块的物理距离更小,因此访问也更快。比如这台机器会分左右两个处理器(cpu1、cpu2),在每个处理器两边放两个内存块(memory1.1, memory1.2, memory2.1,memory2.2),这样 NUMA node1 的 cpu1 访问memory1.1 和 memory1.2 就比访问 memory2.1 和 memory2.2 更快。所以使用 NUMA 的模式如果能尽量保证本 node 内的 CPU 只访问本 node 内的内存块,那这样的效率就是最高的

  • Get Free Page (GFP) Flags 的取值参考:Physical Page Allocation

  • 需要分配的物理页个数为:2 的 order 次方

  • alloc_pages_node 的返回值:如果分配成功,是第一个页的指针;如果分配失败,是空指针 NULL

1.1.2 alloc_pages

1
2
#define alloc_pages(gfp_mask, order) \
alloc_pages_node(numa_node_id(), gfp_mask, order)
  • alloc_pages 是宏定义,逻辑是调用 alloc_pages_node 函数,传递给 nid 参数的值是调用 numa_node_id() 函数获取到的当前 CPU 所处的 NUMA node 的 ID,需要用户传递参数 GFP flags 和需要分配的页个数

1.1.3 alloc_page

1
#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)
  • alloc_page 是宏定义,逻辑是调用 alloc_pages,传递给 order 参数的值为 0,表示需要分配的物理页个数为 2 的 0 次方,即 1 个物理页,需要用户传递参数 GFP flags

1.1.4 free_pages

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// include/linux/gfp.h
void free_pages(unsigned long addr, unsigned int order)
{
if (addr != 0) {
VM_BUG_ON(!virt_addr_valid((void *)addr));
__free_pages(virt_to_page((void *)addr), order);
}
}

// mm/page_alloc.c
void __free_pages(struct page *page, unsigned int order)
{
if (put_page_testzero(page)) {
if (order == 0)
free_hot_cold_page(page, false);
else
__free_pages_ok(page, order);
}
}
参数 含义
addr 要释放的连续物理页中第一个的地址
order 被释放的物理页个数为 2 的 order 次方

2 Slab

2.1 kmem_cache

2.1.1 kmem_cache_create

1
2
3
4
5
6
7
kmem_cache_t *kmem_cache_create(const char *name, size_t size,
size_t offset,
unsigned long flags,
void (*constructor)(void *, kmem_cache_t *,
unsigned long flags),
void (*destructor)(void *, kmem_cache_t *,
unsigned long flags));
参数 含义
name 被缓存对象的类名
size 每个对象的大小
offset 第一个对象在物理页中的偏移量,通常置为 0
flags 决定内存如何被分配、管理
constructor 构造函数
destructor 析构函数
  • flags 参数是 bitmap,决定了内存如何被分配、管理,
flag 作用
SLAB_NO_REAP 保护缓存在系统查找内存时不被削减,不推荐
SLAB_HWCACHE_ALIGN 所有数据对象跟高速缓存行对齐,平台依赖,可能浪费内存
SLAB_CACHE_DMA 每个数据对象在 DMA 内存区段分配
  • 参数 constructor 和 destructor 是可选函数(不能只有destructor,而没有constructor ),用来初始化新分配的对象和在内存被作为整体释放给系统之前“清理”对象

  • constructor 函数不保证在为对象被分配内存后立即被调用,同理,destructor 函数不是立刻在一个对象被释放后调用

  • 将 flags 按位与 SLAB_CTOR_ATOMIC,可以确保 constructor 函数 和 destructor 函数是原子的,不允许在执行中线程被置于睡眠状态

2.1.2 kmem_cache_alloc

  • 通过调用 kmem_cache_alloc 从已创建的后备高速缓存中分配对象,
1
void *kmem_cache_alloc(kmem_cache_t *cache, int flags);
参数 含义
cache 调用 kmem_cache_create 函数创建的缓存
flags GFP(Get Free Page)flags

2.1.3 kmem_cache_free

  • 使用 kmem_cache_free 释放一个对象,
1
void kmem_cache_free(kmem_cache_t *cache, const void *obj);

2.1.4 kmem_cache_destroy

  • 当用完这个后备高速缓存(通常在当模块被卸载时),释放缓存,
1
int kmem_cache_destroy(kmem_cache_t *cache);

2.2 kmalloc

  • 使用 kmalloc 函数动态分配内存空间,
1
void *kmalloc(size_t size, int flags);
参数 含义
size 要分配内存空间的字节数
flags GFP(Get Free Page)flags
  • 分配到的内存空间可能会略大于 size 参数的值

  • flags 参数可取以下值,

flags 取值 含义
GFP_KERNEL 在分配内存空间时,当前进程可被置于睡眠状态
GFP_ATOMIC 在分配内存空间时,当前进程不可被打断
GFP_USER 用于在用户空间分配内存,当前进程可被置于睡眠状态
GFP_HIGHUSER 和 GFP_USER 相似,但是在 high memory 分配内存
GFP_NOIO 和 GFP_KERNEL 相似,但是在内存分配过程中限制 I/O 操作
GFP_NOFS 和 GFP_KERNEL 相似,但是在内存分配过程中限制文件系统调用

更多 flags 的取值可参考 <linux/gfp.h> 头文件

2.3 kzalloc

  • kzalloc 等价于先调用 kmalloc 分配一块内存空间,然后初始化为0,
1
2
3
4
5
6
7
8
9
/**
* kzalloc - allocate memory. The memory is set to zero.
* @size: how many bytes of memory are required.
* @flags: the type of memory to allocate (see kmalloc).
*/
static inline void *kzalloc(size_t size, gfp_t flags)
{
return kmalloc(size, flags | __GFP_ZERO);
}

2.4 kfree

  • 分配内存后如果不释放会造成内存泄漏,在内核中可能导致系统崩溃,
1
void kfree(const void *objp);

可以调用 kfree 函数释放动态分配的内存

3 vmalloc/vfree

3.1 vmalloc

1
void *vmalloc(unsigned long size);
  • 使用 vmalloc 函数在虚拟地址空间分配连续的内存空间,这些内存的物理地址并不是连续的

3.2 vfree

1
void vfree(void * addr);
  • 使用 vfree 函数释放由 vmalloc 函数分配的内存空间