驱动基础之分配内存

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了驱动基础之分配内存相关的知识,希望对你有一定的参考价值。

1 kmalloc()

函数原型如下所示:

void *kmalloc(size_t size,int gfp_mask);

该函数是一个简单的接口,用它可以获得以字节为单位的内存。该函数返回一个指向内存块的指针,其内存块至少有size大小。所分配的内存区在物理地址上是连续的。出错时,它返回NULL。除非没有足够的内存可用,否则内核总能分配成功。

通过kmalloc()获得的物理内存应该被kfree()释放,函数原型如下所示:

void kfree(const void *addr);

如果想要释放的内存不是由kmalloc()分配的,或者想要释放的内存早就释放了,此时调用kfree()会导致严重的后果。与用户空间类似,分配和回收要配对使用,以避免内存泄露和其他BUG,注意调用kfree(NULL)是安全的。

gfp_mask 表示分配标志符,有如下常用标志:

GFP_KERNEL 内核内存的常用分配方法,可能引起休眠。使用

GFP_KERNEL允许kmalloc()在空闲内存较少时把当前进程转入休眠以等待一个页面。在当前进程休眠时,内核会采取适当的行动,或者是把缓冲区的内容刷新到硬盘上,或者是从一个用户进程换出内存,以获取一个内存页面。

GFP_ATOMIC 用于中断处理例程、tasklet、持有自旋锁以及其他不能休眠的地方。内核通常会为原子性的分配预留一些空闲页面。使用GFP_ATOMIC标志时,kmalloc()甚至可以用掉最后一个空闲页面。

2 __get_free_pages()

函数原型如下所示:

unsigned long __get_free_pages(unsigned int gfp_mask,

unsigned long order)

该函数分配(1<<order)个连续的物理页,并且返回分配内存的首地址,分配的内存页不清零。内存页释放函数如下所示:

void free_pages(unsigned long addr,unsigned long order)

释放页时要谨慎,只能释放属于你的页,传递了错误的地址或者错误的order值,都可能导致系统崩溃。

order 表示分配的页数是(1<<order),order可允许最大的值是10或者11(对应于1024或者2048个页),这依赖于体系结构。

kmalloc()和__get_free_pages()的异同:

kmalloc()和__get_free_pages()申请的内存位于DMA映射区和常规区域映射区,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系。

基于页的分配策略在于更有效地使用了内存。按页分配不会浪费内存空间,而用kmalloc()则会因为分配粒度的原因浪费一定数量的内存。

3 vmalloc()

函数原型如下所示:

void *vmalloc(unsigned long size);

大多数情况下,只有硬件设备需要获得物理地址连续的内存。在很多体系结构上,硬件设备存在于内存管理单元MMU之外,它根本不理解什么是虚拟地址。因此,硬件设备用到的任何内存区都必须是物理地址连续的内存,而不仅仅是虚拟地址连续的内存。

很多内核代码都使用kmalloc()来获得内存,而不是vmalloc()。这主要是出于性能的考虑,vmalloc()为了把物理上不连续的页转换为虚拟地址空间连续的页,必须专门建立页表项。因为这些原因,vmalloc()仅仅在不得已时才会使用,一般是为了获得大块内存时,比如模块被动态插入到内核中时,就把模块装载到由vmalloc()分配的内存上。

vmalloc()在<linux/vmalloc.h>中声明,在<mm/vmalloc.c>中定义。

vmalloc()返回一个指针,指向虚拟地址连续的内存,其大小至少为size。在发生错误时,函数返回NULL。vmalloc()可能睡眠,因此不能在中断上下文中进行调用。通过vmalloc()获得的虚拟内存应该被vfree()释放,函数原型如下所示:

void vfree(void *addr);

vfree()可能睡眠,因此不能在中断上下文中进行调用。

4 ioremap()

函数原型如下所示:

void *ioremap(unsigned long offset, unsigned long size)

ioremap()与vmalloc()类似,也需要建立新的页表,但是它并不进行vmalloc()中所执行的内存分配行为。ioremap()返回一个特殊的虚拟地址,该虚拟地址位于vmalloc映射区域。通过ioremap()获得的虚拟地址应该被iounmap()释放,函数原型如下所示:

void iounmap(void *addr);

ioremap()有个变体是devm_ioremap(),类似于其他以devm_开头的函数,通过devm_ioremap()进行的映射通常不需要在驱动退出和出错时进行iounmap()。devm_ioremap()函数原型如下所示:

void __iomem *devm_ioremap(struct device *dev, unsigned long offset,

unsigned long size);

以上是关于驱动基础之分配内存的主要内容,如果未能解决你的问题,请参考以下文章

C#基础之内存分配

Linux内存从0到1学习笔记(6.7,物理内存初始化之CMA初始化)

数据结构与算法基础之malloc()动态分配内存概述

MySQL系列:innodb源代码分析之内存管理

Go 语言基础 之 进阶

小林coding阅读笔记:操作系统篇之内存分配与回收