这篇文章主要介绍“linux内存管理相关的函数有哪些”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“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; inent; i++){ vcpuid->arch.cpuid_entries[i].eax = cpuid_entries[i].eax; ... vcpuid->arch.cpuid_entries[i].index = 0; } ... out_free: vfree(cpuid_entries); out: 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