驱动内存管理函数

linux内核里面的内存申请

有哪些函数??

(1) 在内核中申请内存和在用户空间中申请内存不同,
有以下因素引起了复杂性,包括:
内核的虚拟和物理地址被限制到 1GB。
内核的内存不能 pageable。
内核通常需要 连续的物理地址 。
通常内核申请内存是 不能睡眠。
内核中的错误比其他地方的错误有更多的代价。
(2)内核中的内存申请有一些简单的规则:
判断申请内存的时候可否睡眠,也就是调用kmalloc的时候能否被阻塞。
如果在一个中断处理函数中(即在中断处理的下半部分),或者有一个锁的时候,就不能被阻塞。
如果在一个进程上下文,也没有锁,则一般可以睡眠。
如果可以睡眠,指定 GFP_KERNEL。
如果不能睡眠,就指定 GFP_ATOMIC。
如果需要 DMA 可以访问的内存,比如ISA或者有些PCI设备,就需要指定 GFP_DMA。
需要对返回值检查 NULL。
为了没有内存泄漏,需要用完后释放内存,且最好不要在死循环中开辟内存。
在linux用户空间动态申请内存时是用 malloc() 函数,对应的释放函数是 free() 。
而在内核空间中申请内存,
一般会用到 kmalloc()、kzalloc()、vmalloc() 这三个函数
以及 __get_free_pages() 和mempool_create() 函数,
以下是这些函数的使用及它们之间的区别。
①kmalloc()
函数原型:
void *kmalloc(size_t size, gfp_t flags)

(重点☆)kmalloc() 申请的内存位于物理内存映射区域(即虚拟内存地址)。
但是在物理上它是连续的,它们与真实的物理地址只有一个固定的偏移,
因为存在较简单的转换关系,所以对申请的内存大小有限制,不能超过128KB。
size:
待分配的内存大小。
由于Linux内存管理机制的原因,
内存只能按照页面大小(一般32位机为4KB,64位机为8KB)进行分配,
这样就导致了当我们仅需要几个字节内存时,
系统仍会返回一个页面的内存,显然这是极度浪费的。
所以,不同于malloc的是,
kmalloc的处理方式是:内核先为其分配一系列不同大小(32B、64B、128B、… 、128KB)的内存池,当需要分配内存时,系统会分配大于等于所需内存的最小一个内存池给它。
即kmalloc分配的内存,最小为32字节,最大为128KB。如果超过128KB,需要采样其它内存分配函数,例如vmalloc()。
flags:
常见的flags参数(三种分配内存的方法):
①GFP_ATOMIC —— 分配内存的过程是一个原子过程,
分配内存的过程不会被(高优先级进程或中断)打断;
②GFP_KERNEL —— 正常分配内存;
③GFP_DMA —— 给 DMA 控制器分配内存,
需要使用该标志(DMA要求分配虚拟地址和物理地址连续)。

所以使用 GFP_ KERNEL 标志申请内存时,若暂时不能满足,则进程会睡眠等待页,
即会引起阻塞,
因此不能在中断上下文或持有自旋锁的时候使用GFP_KERNE 申请内存。
所以,在中断处理函数、tasklet 和内核定时器等非进程上下文中不能阻塞,此时驱动应当使用 GFP_ATOMIC 标志来申请内存。当使用 GFP_ATOMIC 标志申请内存时,若不存在空闲页,则不等待,直接返回。
[ 更多的flags 的参考用法 ]
 |– 进程上下文,可以睡眠      GFP_KERNEL
 |– 进程上下文,不可以睡眠     GFP_ATOMIC
 |  |– 中断处理程序       GFP_ATOMIC
 |  |– 软中断          GFP_ATOMIC
 |  |– Tasklet          GFP_ATOMIC
 |– 用于DMA的内存,可以睡眠   GFP_DMA | GFP_KERNEL
 |– 用于DMA的内存,不可以睡眠  GFP_DMA | GFP_ATOM

解析用法:
_ _GFP_DMA(要求分配在能够 DMA 的内存区)
_ _GFP_HIGHMEM(指示分配的内存可以位于高端内存)
_ _GFP_COLD(请求一个较长时间不访问的页)
_ _GFP_NOWARN(当一个分配无法满足时,阻止内核发出警告)
_ _GFP_HIGH(高优先级请求,允许获得被内核保留给紧急状况使用的最后的内存页)
_ _GFP_REPEAT(分配失败则尽力重复尝试)
_ _GFP_NOFAIL(标志只许申请成功,不推荐)
_ _GFP_NORETRY(若申请不到,则立即放弃)

对应的内存释放函数:
函数原型:
void kfree(const void *objp)

示例:
static int xxxx_fun_xxx(void)
{
char *buff;
int ret;

buff = kmalloc(1024, GFP_KERNEL);
if (!buff)
	return -ENOMEM;

......    // 业务代码

kfree(buff);
return ret;

}

以kmalloc()为例,
共同点:
都是用于内核空间申请内存。
都是以字节为单位进行分配。
所分配的内存,在虚拟地址上连续。
区别:
1.kzalloc 基于 kmalloc 上强制清零的操作;(以下描述不区分 kmalloc 和 kzalloc)
2.kmalloc 分配的内存大小有限制(128KB),而 vmalloc 没有限制;
3.kmalloc 可以保证分配的内存物理地址是连续的,
但是 vmalloc 不能保证保证分配的内存物理地址是连续的;
4.kmalloc 分配内存的过程可以是原子过程(使用 GFP_ATOMIC),
而 vmalloc 分配内存时则可能产生阻塞;
5.kmalloc 分配内存的开销小,因此 kmalloc 比 vmalloc 要快;
6.一般情况下,内存只有在要被 DMA 访问的时候才需要物理上连续,
但为了性能上的考虑,内核中一般使用 kmalloc(),
而只有在需要获得大块内存时才使用 vmalloc()。
例如,当模块被动态加载到内核当中时,就把模块装载到由 vmalloc() 分配的内存上。
总结:
基本可以说kmalloc因为内存的开销小而备受欢迎,
而vmalloc因为分配的内存大小没有限制被用来获取大块的内存空间,
需要注意的是DMA需要的是连续的空间地址。
②kzalloc()
kzalloc() 函数与 kmalloc() 非常相似,参数及返回值是一样的,可以说是前者是后者的一个变
种,因为 kzalloc() 实际上只是额外附加了 __GFP_ZERO 标志。
所以它除了申请内核内存外,
还会对申请到的内存内容清零。
函数原型如下:
void *kzalloc(size_t size, gfp_t flags)
{
return kmalloc(size, flags | __GFP_ZERO);
}

kzalloc() 对应的内存释放函数也是: kfree()
kzalloc()函数一般用在Linux驱动代码中的 probe 函数中芯片驱动的内存开辟操作。
示例如下:
static int xxx_fun_xxx_probe(xxx, xxx)
{
char *buf;
… // 业务代码

buf= kzalloc(sizeof(struct xxx), GFP_KERNEL);
if (!buf)
	return -ENOMEM;

...... // 业务代码

kfree(buf);
return 0;

}

③vmalloc()
一般用在为只存在于软件中(没有对应的硬件意义)的较大的顺序缓冲区分配内存,
(重点☆)vmalloc() 函数则会在 虚拟内存空间 给出一块连续的内存区,
但这片连续的虚拟内存在物理内存中并不一定连续。
由于 vmalloc() 没有保证申请到的是连续的物理内存,因此对申请的内存大小没有限制,
如果需要申请较大的内存空间就需要用此函数了。
函数原型如下:
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;
}

注意:vmalloc() 和 vfree() 可以睡眠,因此不能从中断上下文调用。
示例如下:
static int xxxx_fun_xxx(void)
{
char *buff;
int ret;

buff = vmalloc(sizeof(struct xxx));
if (!buff)
	return -ENOMEM;

......    // 业务代码

kfree(buff);
return ret;

}

(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);
}

根据返回页面数目分类,分为仅返回单页面的函数和返回多页面的函数。
①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));
}

②__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)关于内存池
当在驱动程序中,遇到反复分配、释放同一大小的内存块时,建议使用内存池技术
在linux中,有一个叫做slab分配器的内存池管理技术,内存池使用的内存区叫做后备高速缓存。
mempool_create()函数用于创建一个内存池。
。。。。。。。。。。。。。。。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值