共计 6720 个字符,预计需要花费 17 分钟才能阅读完成。
这篇文章主要介绍“linux 内存管理相关的函数有哪些”的相关知识,丸趣 TV 小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“linux 内存管理相关的函数有哪些”文章能帮助大家解决问题。
linux 内存管理相关的函数:1、kmalloc(),用于内核态的内存分配;2、vmalloc(),一般用在为只存在于软件中(没有对应的硬件意义)的较大的顺序缓冲区分配内存;3、alloc_page() 和 alloc_pages() 函数,可以在内核空间分配;4、__get_free_pages() 系列函数,返回一个或多个页面的虚拟地址;5、kmem_cache_alloc() 等。
1、kmalloc()
kmalloc() 函数类似与我们常见的 malloc() 函数,前者用于内核态的内存分配,后者用于用户态。
kmalloc() 函数在物理内存中分配一块连续的存储空间,且和 malloc() 函数一样,不会清除里面的原始数据,如果内存充足,它的分配速度很快。其原型如下:
static inline void *kmalloc(size_t size, gfp_t flags); /* 返回的是虚拟地址 */
size: 待分配的内存大小。由于 Linux 内存管理机制的原因,内存只能按照页面大小(一般 32 位机为 4KB,64 位机为 8KB)进行分配,这样就导致了当我们仅需要几个字节内存时,系统仍会返回一个页面的内存,显然这是极度浪费的。所以,不同于 malloc 的是,kmalloc 的处理方式是:内核先为其分配一系列不同大小(32B、64B、128B、…、128KB)的内存池,当需要分配内存时,系统会分配大于等于所需内存的最小一个内存池给它。即 kmalloc 分配的内存,最小为 32 字节,最大为 128KB。如果超过 128KB,需要采样其它内存分配函数,例如 vmalloc()。
flag:该参数用于控制函数的行为,最常用的是 GFP_KERNEL,表示当当前没有足够内存分配时,进程进入睡眠,待系统将缓冲区中的内容 SWAP 到硬盘中后,获得足够内存后再唤醒进程,为其分配。更多标志见下图:
使用 GFP_ KERNEL 标志申请内存时,若暂时不能满足,则进程会睡眠等待页,即会引起阻塞,因此不能在中断上下文或持有自旋锁的时候使用 GFP_KERNE 申请内存。所以,在中断处理函数、tasklet 和内核定时器等非进程上下文中不能阻塞,此时驱动应当使用 GFP_ATOMIC 标志来申请内存。当使用 GFP_ATOMIC 标志申请内存时,若不存在空闲页,则不等待,直接返回。
除了上述表格所列标志外,还包括如下:
_ _GFP_DMA(要求分配在能够 DMA 的内存区)
_ _GFP_HIGHMEM(指示分配的内存可以位于高端内存)
_ _GFP_COLD(请求一个较长时间不访问的页)
_ _GFP_NOWARN(当一个分配无法满足时,阻止内核发出警告)
_ _GFP_HIGH(高优先级请求,允许获得被内核保留给紧急状况使用的最后的内存页)
_ _GFP_REPEAT(分配失败则尽力重复尝试)
_ _GFP_NOFAIL(标志只许申请成功,不推荐)
_ _GFP_NORETRY(若申请不到,则立即放弃)
使用 kmalloc() 申请的内存应使用 kfree() 释放,这个函数的用法和用户空间的 free() 类似。
2、vmalloc()
vmalloc() 一般用在为只存在于软件中(没有对应的硬件意义)的较大的顺序缓冲区分配内存,当内存没有足够大的连续物理空间可以分配时,可以用该函数来分配虚拟地址连续但物理地址不连续的内存。由于需要建立新的页表,所以它的开销要远远大于 kmalloc 及后面将要讲到的__get_free_pages() 函数。且 vmalloc() 不能用在原子上下文中,因为它的内部实现使用了标志为 GFP_KERNEL 的 kmalloc()。其函数原型如下:
void *vmalloc(unsigned long size);
void vfree(const void *addr);
使用 vmalloc 函数的一个例子函数是 create_module() 系统调用,它利用 vmalloc() 函数来获取被创建模块需要的内存空间。
内存分配是一项要求严格的任务,无论什么时候,都应该对返回值进行检测。
在驱动编程中可以使用 copy_from_user() 对内存进行使用。下面举一个使用 vmalloc 函数的示例:
static int xxx(...)
cpuid_entries = vmalloc(sizeof(struct kvm_cpuid_entry) * cpuid- nent);
if(!cpuid_entries)
goto out;
if(copy_from_user(cpuid_entries, entries, cpuid- nent * sizeof(struct kvm_cpuid_entry)))
goto out_free;
for(i=0; i cpuid- nent; i++){vcpuid- arch.cpuid_entries[i].eax = cpuid_entries[i].eax;
vcpuid- arch.cpuid_entries[i].index = 0;
out_free:
vfree(cpuid_entries);
return r;
}
3、页分配函数
在 linux 中,内存分配是以页为单位的,32 位机中一页为 4KB,64 位机中,一页为 8KB,但具体还有根据平台而定。
根据返回值类型的不同,页分配函数分为两类,一是返回物理页地址,二是返回虚拟地址。虚拟地址和物理地址起始相差一个固定的偏移量。
#define __pa(x) ((x) - PAGE_OFFSET)
static inline unsigned long virt_to_phys(volatile void *address)
return __pa((void *)address);
#define __va(x) ((x) + PAGE_OFFSET)
static inline void* phys_to_virt(unsigned long address)
return __va(address);
}
根据返回页面数目分类,分为仅返回单页面的函数和返回多页面的函数。
3.1 alloc_page() 和 alloc_pages() 函数
该函数定义在头文件 /include/linux/gfp.h 中,它既可以在内核空间分配,也可以在用户空间分配,它返回分配的第一个页的描述符而非首地址,其原型为:
#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)
#define alloc_pages(gfp_mask, order) alloc_pages_node(numa_node_id(), gfp_mask, order) // 分配连续 2^order 个页面
static inline struct page *alloc_pages_node(int nid, gfp_t gfp_mask, unsigned int order)
if(unlikely(order = MAX_ORDER))
return NULL;
if(nid 0)
nid = numa_node_id();
return __alloc_pages(gfp_mask, order, noed_zonelist(nid, gfp_mask));
}
3.2 __get_free_pages() 系列函数
它是 kmalloc 函数实现的基础,返回一个或多个页面的虚拟地址。该系列函数 / 宏包括 get_zeroed_page()、_ _get_free_page() 和_ _get_free_pages()。在使用时,其申请标志的值及含义与 kmalloc() 完全一样,最常用的是 GFP_KERNEL 和 GFP_ATOMIC。
/* 分配多个页并返回分配内存的首地址,分配的页数为 2^order,分配的页不清零。order 允许的最大值是 10(即 1024 页)或者 11(即 2048 页),依赖于具体
的硬件平台。*/
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
struct page *page;
page = alloc_pages(gfp_mask, order);
if(!page)
return 0;
return (unsigned long)page_address(page);
#define __get_free_page(gfp_mask) __get_free_pages(gfp_mask, 0)
/* 该函数返回一个指向新页的指针并且将该页清零 */
unsigned long get_zeroed_page(unsigned int flags);
使用_ _get_free_pages() 系列函数 / 宏申请的内存应使用 free_page(addr) 或 free_pages(addr, order) 函数释放:
#define __free_page(page) __free_pages((page), 0)
#define free_page(addr) free_pages((addr), 0)
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);
void __free_pages(struct page *page, unsigned int order)
if(put_page_testzero(page)){if(order == 0)
free_hot_page(page);
else
__free_pages_ok(page, order);
}
free_pages() 函数是调用__free_pages() 函数完成内存释放的。
4、slab 缓存
当在驱动程序中,遇到反复分配、释放同一大小的内存块时(例如,inode、task_struct 等),建议使用内存池技术(对象在前后两次被使用时均分配在同一块内存或同一类内存空间,且保留了基本的数据结构,这大大提高了效率)。在 linux 中,有一个叫做 slab 分配器的内存池管理技术,内存池使用的内存区叫做后备高速缓存。
salb 相关头文件在 linux/slab.h 中,在使用后备高速缓存前,需要创建一个 kmem_cache 的结构体。
4.1 创建 slab 缓存区
该函数创建一个 slab 缓存(后备高速缓冲区),它是一个可以驻留任意数目全部同样大小的后备缓存。其原型如下:
struct kmem_cache *kmem_cache_create(const char *name, size_t size, \
size_t align, unsigned long flags,\
void (*ctor)(void *, struct kmem_cache *, unsigned long),\
void (*dtor)(void *, struct kmem_cache *, unsigned ong)));
其中:
name:创建的缓存名;
size:可容纳的缓存块个数;
align:后备高速缓冲区中第一个内存块的偏移量(一般置为 0);
flags:控制如何进行分配的位掩码,包括 SLAB_NO_REAP(即使内存紧缺也不自动收缩这块缓存)、SLAB_HWCACHE_ALIGN(每 个 数 据 对 象 被 对 齐 到 一 个 缓 存 行)、SLAB_CACHE_DMA(要求数据对象在 DMA 内存区分配)等);
ctor:是可选的内存块对象构造函数(初始化函数);
dtor:是可选的内存对象块析构函数(释放函数)。
4.2 分配 slab 缓存函数
一旦创建完后备高速缓冲区后,就可以调用 kmem_cache_alloc() 在缓存区分配一个内存块对象了,其原型如下:
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);
cachep 指向开始分配的后备高速缓存,flags 与传给 kmalloc 函数的参数相同,一般为 GFP_KERNEL。
4.3 释放 slab 缓存
该函数释放一个内存块对象:
void *kmem_cache_free(struct kmem_cache *cachep, void *objp);
4.4 销毁 slab 缓存
与 kmem_cache_create 对应的是销毁函数,释放一个后备高速缓存:
int kmem_cache_destroy(struct kmem_cache *cachep);
它必须等待所有已经分配的内存块对象被释放后才能释放后备高速缓存区。
4.5 slab 缓存使用举例
创建一个存放线程结构体(struct thread_info)的后备高速缓存,因为在 linux 中涉及频繁的线程创建与释放,如果使用__get_free_page() 函数会造成内存的大量浪费,效率也不高。所以在 linux 内核的初始化阶段就创建了一个名为 thread_info 的后备高速缓存,代码如下:
/* 创建 slab 缓存 */
static struct kmem_cache *thread_info_cache;
thread_info_cache = kmem_cache_create(thread_info , sizeof(struct thread_info), \
SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);
/* 分配 slab 缓存 */
struct thread_info *ti;
ti = kmem_cache_alloc(thread_info_cache, GFP_KERNEL);
/* 使用 slab 缓存 */
/* 释放 slab 缓存 */
kmem_cache_free(thread_info_cache, ti);
kmem_cache_destroy(thread_info_cache);
5、内存池
在 Linux 内核中还包含对内存池的支持,内存池技术也是一种非常经典的用于分配大量小对象的后备缓存技术。
5.1 创建内存池
mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn, \
mempool_free_t *free_fn, void *pool_data);
mempool_create() 函数用于创建一个内存池,min_nr 参数是需要预分配对象的数目,alloc_fn 和 free_fn 是指向内存池机制提供的标准对象分配和回收函数的指针,其原型分别为:
typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data);
typedef void (mempool_free_t)(void *element, void *pool_data);
pool_data 是分配和回收函数用到的指针,gfp_mask 是分配标记。只有当_ _GFP_WAIT 标记被指定时,分配函数才会休眠。
5.2 分配和回收对象
在内存池中分配和回收对象需由以下函数来完成:
void *mempool_alloc(mempool_t *pool, int gfp_mask);
void mempool_free(void *element, mempool_t *pool);
mempool_alloc() 用来分配对象,如果内存池分配器无法提供内存,那么就可以用预分配的池。
5.3 销毁内存池
void mempool_destroy(mempool_t *pool);
mempool_create() 函数创建的内存池需由 mempool_destroy() 来回收。
关于“linux 内存管理相关的函数有哪些”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注丸趣 TV 行业资讯频道,丸趣 TV 小编每天都会为大家更新不同的知识点。