kmalloc函数
#include<linux/slab.h> void *kmalloc(size_t size,int flags); |
1.不会对所申请的内存清零,保留原有数据
2.参数:size:分配大小
flags:kmalloc行为
3.flags:GFP_KERNEL :内核内存通常的分配方法,可能引起休眠
GFP_ATOMIC :用于在中断处理例程或其它运行于进程上下文之外的代码中分配内存,不会休眠
GFP_USER:用于为用户空间分配内存,可能会引起休眠
GFP_HIGHUSER:类似于GFP_USER,不过如果有高端内存的话就从那里分配
GFP_NOIO:在GFP_KERNEL的基础上,禁止任何I/O的初始化
GFG_NOFS:在GFP_KERNEL的基础上,不允许执行任何文件系统的调用
另外有一些分配标志与上述“或”起来使用
__GFP_DMA:
__GFP_HIGHMEM:
__GFP_COLD:
__GFP_NOWARN:
__GFP_HIGH:
__GFP_REPEAT:
__GFP_NOFAIL:
__GFP_NORETRY:
4.内存区段
linux通常把内存分成三个区段:
可用于DMA内存 | 存在于特别的地址范围 |
常规内存 | |
高端内存 | 32位平台为访问(相对)大量内存而存在的一种机制 |
5.size
linux处理内存分配:创建一系列的内存对象池,每个池中的内存块大小是固定一致的。处理分配请求时,就直接在包含有足够大的内存块的池中传递一个整块给请求者。
内核只能分配一些预定义的,固定大小的字节数组。若申请任意数量的内存,则得到的可能会多一些,最多可以得到申请数量的两倍。
下限 | 32或者64,取决于当前体系 |
上限 | 128kb,使用的页面的大小 |
后备高速缓存
驱动程序常常需要反复分配许多相同大小内存块的情况,增加了一些特殊的内存池,称为后备高速缓存(lookaside cache)。 设备驱动程序通常不会涉及后备高速缓存,但是也有例外:在 Linux 2.6 中 USB 和 SCSI 驱动。Linux 内核的高速缓存管理器有时称为“slab 分配器”,相关函数和类型在 <linux/slab.h> 中声明。slab 分配器实现的高速缓存具有 kmem_cache_t 类型。
1.实现过程如下:
|
参数*name: 一个指向 name 的指针,name和这个后备高速缓存相关联,功能是管理信息以便追踪问题;通常设置为被缓存的结构类型的名字,不能包含空格。
参数size:每个内存区域的大小。
参数offset:页内第一个对象的偏移量;用来确保被分配对象的特殊对齐,0 表示缺省值。
参数flags:控制分配方式的位掩码:
-
SLAB_NO_REAP 保护缓存在系统查找内存时不被削减,不推荐。
SLAB_HWCACHE_ALIGN 所有数据对象跟高速缓存行对齐,平台依赖,可能浪费内存。
SLAB_CACHE_DMA 每个数据对象在 DMA 内存区段分配.。
2.一旦某个对象的高速缓存被创建,就可以调用kmem_cache_alloc从中分配内存对象:
viod *kmem_cache_alloc(kmem_cache_t *cache,int flags); cache是之前创建的高速缓存,flags和传递给kmalloc的相同,并且当需要分配更多的内存来满足kmem_cache_alloc时,高速缓存还会利用这个参数 |
3.释放一个内存对象,使用kmem_cache_free:
void kmem_cache_free(kmem_cache_t *cache,const void *obj); |
4.如果驱动程序代码中和高速缓存有关的部分已经处理完了(典型情况:模块被卸载的时候),这时驱动程序应该释放它的高速缓存:
int kmem_cache_destroy(kmem_cache_t *cache); /*只在从这个缓存中分配的所有的对象都已返时才成功。因此,应检查 kmem_cache_destroy 的返回值:失败指示模块存在内存泄漏*/ |
内存池
为了确保在内存分配不允许失败情况下成功分配内存,内核提供了称为内存池( "mempool" )的抽象,它其实是某种后备高速缓存。它为了紧急情况下的使用,尽力一直保持空闲内存。所以使用时必须注意: mempool 会分配一些内存块,使其空闲而不真正使用,所以容易消耗大量内存 。而且不要使用 mempool 处理可能失败的分配。应避免在驱动代码中使用 mempool。
内存池的类型为 mempool_t ,在 <linux/mempool.h> ,使用方法如下:
1.创建mempool
/*min_nr 参数是内存池应当一直保留的最小数量的分配对象*/ /*实际的分配和释放对象由 alloc_fn 和 free_fn 处理,原型:*/ |
你可编写特殊用途的函数来处理 mempool 的内存分配,但通常只需使用 slab 分配器为你处理这个任务:mempool_alloc_slab 和 mempool_free_slab的原型和上述内存池分配原型匹配,并使用 kmem_cache_alloc 和 kmem_cache_free 处理内存的分配和释放。
典型的设置内存池的代码如下:
|
(2)创建内存池后,分配和释放对象:
|
可用一下函数重定义mempool预分配对象的数量:
|
(3)若不再需要内存池,则返回给系统:
|
get_free_page与相关函数
1.如果模块需要分配大块的内存,使用面向页的分配技术会更好一些,就是整页的分配。
|
2.get_order 函数可以用来从一个整数参数 size(必须是 2 的幂) 中提取 order,函数也很简单:
|
3.通过/proc/buddyinfo 可以知道系统中每个内存区段上的每个 order 下可获得的数据块数目。
4.。当程序不需要页面时,它可用下列函数之一来释放它们。
它们的关系是:
|
alloc_pages 接口
1.struct page 是一个描述一个内存页的内部内核结构,定义在<linux/Mm_types.h>
2.
Linux 页分配器的核心是称为 alloc_pages_node 的函数:
|
参数nid 是要分配内存的 NUMA 节点 ID,
参数flags 是 GFP_ 分配标志,
参数order 是分配内存的大小.
返回值是一个指向第一个(可能返回多个页)page结构的指针, 失败时返回NULL。
alloc_pages 通过在当前 NUMA 节点分配内存( 它使用 numa_node_id 的返回值作为 nid 参数调用 alloc_pages_node)简化了alloc_pages_node调用。alloc_pages 省略了 order 参数而只分配单个页面。
释放分配的页:
|
vmalloc 和 ioremap
vmalloc 是一个基本的 Linux 内存分配机制,它在虚拟内存空间分配一块连续的内存区,尽管这些页在物理内存中不连续 (使用一个单独的 alloc_page 调用来获得每个页),但内核认为它们地址是连续的。 应当注意的是:vmalloc 在大部分情况下不推荐使用。因为在某些体系上留给 vmalloc 的地址空间相对小,且效率不高。函数原型如下:
|
kmalloc 和 _get_free_pages 返回的内存地址也是虚拟地址,其实际值仍需 MMU 处理才能转为物理地址。vmalloc和它们在使用硬件上没有不同,不同是在内核如何执行分配任务上:kmalloc 和 __get_free_pages 使用的(虚拟)地址范围和物理内存是一对一映射的, 可能会偏移一个常量 PAGE_OFFSET 值,无需修改页表。
而vmalloc 和 ioremap 使用的地址范围完全是虚拟的,且每次分配都要通过适当地设置页表来建立(虚拟)内存区域。 vmalloc 可获得的地址在从 VMALLOC_START 到 VAMLLOC_END 的范围中,定义在 <asm/patable.h> 中。vmalloc 分配的地址只在处理器的 MMU 之上才有意义。当驱动需要真正的物理地址时,就不能使用 vmalloc。 调用 vmalloc 的正确场合是分配一个大的、只存在于软件中的、用于缓存的内存区域时。注意:vamlloc 比 __get_free_pages 要更多开销,因为它必须即获取内存又建立页表。因此, 调用 vmalloc 来分配仅仅一页是不值得的。vmalloc 的一个小的缺点在于它无法在原子上下文中使用。因为它内部使用 kmalloc(GFP_KERNEL) 来获取页表的存储空间,因此可能休眠。
ioremap 也要建立新页表,但它实际上不分配任何内存,其返回值是一个特殊的虚拟地址可用来访问特定的物理地址区域。
为了保持可移植性,不应当像访问内存指针一样直接访问由 ioremap 返回的地址,而应当始终使用 readb 和 其他 I/O 函数。
ioremap 和 vmalloc 是面向页的(它们会修改页表),重定位的或分配的空间都会被上调到最近的页边界。ioremap 通过将重映射的地址下调到页边界,并返回第一个重映射页内的偏移量来模拟一个非对齐的映射。
per-CPU变量
per-CPU 变量是一个有趣的 2.6 内核特性,定义在 <linux/percpu.h> 中。当创建一个per-CPU变量,系统中每个处理器都会获得该变量的副本。其优点是对per-CPU变量的访问(几乎)不需要加锁,因为每个处理器都使用自己的副本。per-CPU 变量也可存在于它们各自的处理器缓存中,这就在频繁更新时带来了更好性能
在编译时间创建一个per-CPU变量使用如下宏定义:
|
虽然操作per-CPU变量几乎不必使用锁定机制。 但是必须记住 2.6 内核是可抢占的,所以在修改一个per-CPU变量的临界区中可能被抢占。并且还要避免进程在对一个per-CPU变量访问时被移动到另一个处理器上运行。所以必须显式使用 get_cpu_var 宏来访问当前处理器的变量副本, 并在结束后调用 put_cpu_var。 对 get_cpu_var 的调用返回一个当前处理器变量版本的 lvalue ,并且禁止抢占。又因为返回的是lvalue,所以可被直接赋值或操作。例如:
|
当要访问另一个处理器的变量副本时, 使用:
|
|
per-CPU变量可以导出给模块, 但必须使用一个特殊的宏版本:
|
获得大的缓冲区
大量连续内存缓冲的分配是容易失败的。到目前止执行大 I/O 操作的最好方法是通过离散/聚集操作 。
当内核被引导, 它可以访问系统种所有可用物理内存,接着通过调用子系统的初始化函数, 允许初始化代码通过减少留给常规系统操作使用的 RAM 数量来分配私有内存缓冲给自己。
|