linux设备驱动程序(第三版)阅读笔记(八)

来源:互联网 发布:天津知味餐厅 编辑:程序博客网 时间:2024/05/22 15:04

 

说明:版权所有归作者,只供学习交流,若有其它用途请联系作者,转载请遵守IT人职业规范,请注明转载地址

 

 

第八章:分配内存

1,(kmalloc函数)kmalloc内存分配引擎是一个功能强大的工具,由于和malloc相似,所以学习它也很容易。除非阻塞,否则这个函数可运行得很快,而且不对所获取的内存空间清零,也就是说,分配给它的区域任然保持着原有数据。它分配的区域在物理内存中也是连续的

Kmalloc 函数原型:

#include <linux/slab.h>

Void *kmalloc(size_t size, int flags);

第一个参数是要分配的块的大小第二个参数是分配标志(flags),更有意思的是,它能够以多种方式控制kmalloc的行为。

常用的标志(flags):

GFP_ATOMIC

用于在中断处理例程或其他运行于进程上下文之外的代码中分配内存,不会休眠。

GFP_KERNEL:

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

GFP_HIGHUSER:

类似于GFP_USER,不过如果有高端内存的话就从那里分配。

GFP_NOIO:

GFP_NOFS:

这两个标志的功能类似于GFP_KERNEL,但是为内核分配内存的工作方式添加了一些限制。具有GFP_NOFS标志的分配不充许执行任何文件系统调用,而GFP_NOIO禁止任何I/O的初始化。这两个标志主要在文件系统和虚拟内存代码中使用,这些代码中的内存分配可休眠,但不应该递归的文件系统调用。

 

Linux内核把内存分为三个区段:可用于DMA的内存,常规内存以及高端内存。通常的内存分配都发生在常规内存区,但通过设置上面介绍过的标志也可以请求在其他区段中分配。

 

内核负责管理系统物理内存,物理内存只能按页面进行分配。其结果是kmalloc和典型的用户空间的malloc在实现上有很大的差别。简单的基于堆的内存分配技术会遇到麻烦,因为页面边界的处理是一个很棘手的问题。因此内核使用了特殊的基于页的分配技术,以最佳的利用系统RAM。Linux处理内存分配的方法是,创建一系列的内存对象池,每个池中的内存块大小是固定一致的。处理分配请求时,就直接在包含有足够大的内存块的池中传递一个整块给请求者。

 

内存管理机制相当复杂,其细节对设备驱动开发人员并不重要,驱动程序开发人员应该记住一点,就是内核只能分配一些预定义的。固定大小的字节数组。如果申请任意数量的内存,那么得到的很可能会多一些,最多会申请到数量的两倍。另外,程序员应该记住,kmalloc能处理的最小的内存块是32或者64,到底是哪个则取决于当前体系结构使用的页面大小。

kmalloc能够分配的内存块大小,存在一个上限。这个上限随着体系结构的不同以及内核配置选项的不同而变化。如果我们希望代码具有完整的可移植性,则不应该分配大于128KB的内存。但是,如果我们希望得到多余几千字节的内存,则最好使用除kmalloc之外的内存获取方法。

 

2,(get_free_page和相关的函数)如果模块需要分配大块的内存,使用面向页的分配技术会更好些。

分配页面可使用下面函数:

get_zeroed_page(unsigned int flags);

返回指向新页面的指针并将页面清零。

__get_free_page(unsigned int flags);

类似于get_zeroed_page,但不清零页面。

__get_free_pages(unsigned int flags, unsigned int order)

分配若干(物理连续的)页面,并返回指向该内存区域第一个字节的指针,但不清零页面

参数flags的作用和kmalloc中的一样;通常使用GFP_KERNELGFP_ATOMIC,也许还会加上__GFP_DMA标志(申请可用于ISA直接内存访问操作的内存)或者_GFP_HIGHMEM标志(使用高端内存)。参数order是要申请或释放的页面数的以2为底的对数(即log2(N))。例如,order(阶数)为0表示一个页面,order3表示8个页面。如果order太大,而又没有那么大的连续区域可以分配,就会返回失败。

 

当程序不再需要使用页面时,可以使用下面函数之一来释放它们。第一个函数是一个宏,展开后就是对第二个函数的调用:

Void free_page(unsigned long addr);

Void free_pages(unsigned long addr, unsigned long order)

如果试图释放和先前分配数目不等的页面,内存映射关系就会破坏,随后系统就会出错。

值得强调的是,只要符合和kmalloc同样的规则,get_free_pages和其他函数可以在任何时间调用。某些情况下函数分配内存会失败,特别是在使用了GFP_ATOMIC的时候。因此,调用了这些函数的程序在分配出错时都应提供相应的处理。

尽管kmalloc(GFP_KERNEL)在没有空闲内存时有时会失败,但内核总会尽可能满足这个内存分配请求。因此,如果分配太多内存,系统的响应性能就很容易降下来。

使用__get_free_page函数的最大优点是这些分配的页面完全属于我们自己,而且在理论上可以通过适当的调整页表将它们合并成一个线性区域。

 

alloc_pages接口:

struct page是内核用来描述单个内存页的数据结构。内核中有许多地方需要使用page结构,尤其在需要使用高端内存(高端内存在内核空间没有对应不变得地址)的地方。

Linux页分配器的核心代码是称为alloc_pages_node的函数:

struct page*alloc_pages_node(int nid, unsigned int flags, unsigned int order);

这个函数具有两种变种(它们只是简单的宏),大多数情况下我们使用这两个宏:

struct page*alloc_pages(unsigned int flags, unsigned int order);

struct page*alloc_page(unsigned int flags);(分配单个页面)

核心函数alloc_pages_node要求传入三个参数。nidNUMA节点的ID号,表示要在其中分配内存,flags是通常的GFP_分配标志,而order是要分配的内存大小。该函数的返回值是指向第一个page结构(可能返回多个页)的指针,它描述了已分配的内存;或者失败时返回NULL

为了释放通过上述途径分配的页面,我们应该使用下面的函数:

void __free_page(struct page *page);

void __free_pages(struct page *page, unsigned int order);

void free_hot_page(struct page *page);

void free_cold_page(struct page *page);

如果你知道某个页面中的内容是否驻留在处理器高速缓存中,则应该使用free_hot_page(用于驻留在高速缓存中的页)或者free_cold_page和内核通信。这个信息可帮助内存分配器优化内存使用。

 

3,(内存池的简单介绍):内核中有些地方的内存分配是不充许失败的。为了确保这种情况下的成功分配,内核开发者建立了一种称为内存池(或者“mempool”)的抽象。内存池其实就是某种形式的后备高速缓存,它试图始终保持空闲内存,以便紧急状态下使用。

如果你想在自己的驱动程序中使用mempool,则应记住下面这点:mempool会分配一些内存块,空闲且不会真正得到使用。因此,使用mempool很容易浪费大量的内存。几乎在所有情况下,最好不使用mempool而是处理可能的分配失败。如果驱动程序存在某种方式可以响应分配的失败,而不会导致对系统一致性的破坏,则应该使用这种方式,也就是说,应尽量避免在驱动程序代码中使用mempool

 

4,(vmalloc及其辅助函数)内存分配函数vmalloc,它分配虚拟地址空间的连续区域。尽管这段区域在物理上可能是不连续的(要访问其中的每个页面都必须独立地调用函数alloc_page),内核却认为它们在地址上是连续的。vmalloc在发生错误时返回0NULL地址),成功时返回一个指针,该指针指向一个线性的、大小最少为size的线性内存区域。

这里描述vmalloc的原因是,它是Linux内存分配机制的基础。但是,我们要注意在大多数情况下不鼓励使用vmalloc。通过vmalloc获得的内存使用起来效率不高,而且在某些体系架构上,用于vmalloc的地址空间总量相对较小。如果希望将使用vmalloc的代码提交给内核主线程代码,则可能会受到冷遇。如果可能,应该直接和单个的页面打交道,而不是使用vmalloc。

Vmalloc函数原型:

#include<linux/vmalloc.h>

void *vmalloc(unsigned long size);

void vfree(void *addr);

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

void iounmap(void *addr);

要强调的是,由kmalloc__get_free_pages返回的内存地址任然也是虚拟地址,其实际值任然要由MMU(内存管理单元,通常是CPU的组成部分)处理才能转为物理内存地址。Vmalloc在如何使用硬件上没有区别,区别在于内核如何执行分配任务上。

 

vmalloc分配到的地址是不能在微处理器之外使用的,因为它们只在处理器的内存管理单元上才有意义。当驱动程序需要真正的物理地址时(像外设用以驱动系统总线的DMA地址),就不能使用vmalloc了。使用vmalloc函数的正确场合是在分配一大块连续的,只在软件中存在的、用于缓冲的内存区域的时候。注意vmalloc的开销要比__get_free_pages大,因为它不但获取内存,还要建立页表。因此,用vmalloc函数分配仅仅一页的空间是不值得的。

vmalloc分配得到的内存空间要用vfree函数来释放,这就像要用kfree函数来释放kmalloc函数分配得到的内存空间一样

 

vmalloc一样,ioremap也建立新的页表,但和vmalloc不同的是,ioremap并不实际分配内存。ioremap的返回值是一个特殊的虚拟地址,可以用来访问指定的物理内存区域,这个虚拟地址最后要调用iounmap来释放掉。要注意,为了保持可移植性,不应把ioremap返回的地址当作指向内存的指针而直接访问。