内存池的类型为 mempool_t ,在 <linux/mempool.h> ,使用方法如下:
(1)创建 mempool :
mempool_t *mempool_create(int min_nr,
mempool_alloc_t *alloc_fn,
mempool_free_t *free_fn,
void *pool_data);
/*min_nr 参数是内存池应当一直保留的最小数量的分配对象*/
/*实际的分配和释放对象由 alloc_fn 和 free_fn 处理,原型:*/
typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data);
typedef void (mempool_free_t)(void *element, void *pool_data);
/*给 mempool_create 最后的参数 *pool_data 被传递给 alloc_fn 和 free_fn */
你可编写特殊用途的函数来处理 mempool 的内存分配,但通常只需使用 slab 分配器为你处理这个任务:mempool_alloc_slab 和 mempool_free_slab的原型和上述内存池分配原型匹配,并使用 kmem_cache_alloc 和 kmem_cache_free 处理内存的分配和释放。
典型的设置内存池的代码如下:
cache = kmem_cache_create(. . .);
pool = mempool_create(MY_POOL_MINIMUM,mempool_alloc_slab, mempool_free_slab, cache);
(2)创建内存池后,分配和释放对象:
void *mempool_alloc(mempool_t *pool, int gfp_mask);
void mempool_free(void *element, mempool_t *pool);
在创建mempool时,分配函数将被调用多次来创建预先分配的对象。因此,对 mempool_alloc 的调用是试图用分配函数请求额外的对象,如果失败,则返回预先分配的对象(如果存在)。用 mempool_free 释放对象时,若预分配的对象数目小于最小量,就将它保留在池中,否则将它返回给系统。
可用一下函数重定义mempool预分配对象的数量:
int mempool_resize(mempool_t *pool, int new_min_nr, int gfp_mask);
/*若成功,内存池至少有 new_min_nr 个对象*/
(3)若不再需要内存池,则返回给系统:
void mempool_destroy(mempool_t *pool);
/*在销毁 mempool 之前,必须返回所有分配的对象,否则会产生 oops*/
get_free_page 和相关函数
如果一个模块需要分配大块的内存,最好使用面向页的分配技术。
__get_free_page(unsigned int flags);
/*返回一个指向新页的指针, 未清零该页*/
get_zeroed_page(unsigned int flags);
/*类似于__get_free_page,但用零填充该页*/
__get_free_pages(unsigned int flags, unsigned int order);
/*分配是若干(物理连续的)页面并返回指向该内存区域的第一个字节的指针,该内存区域未清零*/
/*参数flags 与 kmalloc 的用法相同;
参数order 是请求或释放的页数以 2 为底的对数。若其值过大(没有这么大的连续区可用), 则分配失败*/
get_order 函数可以用来从一个整数参数 size(必须是 2 的幂) 中提取 order,函数也很简单:
/* Pure 2^n version of get_order */
static __inline__ __attribute_const__ int get_order(unsigned long size)
{
int order;
size = (size - 1) >> (PAGE_SHIFT - 1);
order = -1;
do {
size >>= 1;
order++;
} while (size);
return order;
}
使用方法举例在Linux设备驱动程序学习(7)-内核的数据类型 中有。
通过/proc/buddyinfo 可以知道系统中每个内存区段上的每个 order 下可获得的数据块数目。
当程序不需要页面时,它可用下列函数之一来释放它们。
void free_page(unsigned long addr);
void free_pages(unsigned long addr, unsigned long order);
它们的关系是:
#define __get_free_page(gfp_mask) \
__get_free_pages((gfp_mask),0)
若试图释放和你分配的数目不等的页面,会破坏内存映射关系,系统会出错。
注意:只要符合和 kmalloc 的相同规则, __get_free_pages 和其他的函数可以在任何时候调用。这些函数可能失败(特别当使用 GFP_ATOMIC 时),因此调用这些函数的程序必须提供分配失败的处理。
从用户的角度,可感觉到的区别主要是速度提高和更好的内存利用率(因为没有内部的内存碎片)。但主要优势实际不是速度, 而是更有效的内存利用。 __get_free_page 函数的最大优势是获得的页完全属于调用者, 且理论上可以适当的设置页表将起合并成一个线性的区域。
alloc_pages 接口
struct page 是一个描述一个内存页的内部内核结构,定义在<linux/Mm_types.h> 。
Linux 页分配器的核心是称为 alloc_pages_node 的函数:
struct page *alloc_pages_node(int nid, unsigned int flags,
unsigned int order);
/*以下是这个函数的 2 个变体(是简单的宏):*/
struct page *alloc_pages(unsigned int flags, unsigned int order);
struct page *alloc_page(unsigned int flags);
/*他们的关系是:*/
#define alloc_pages(gfp_mask, order) \
alloc_pages_node(numa_node_id(), gfp_mask, order)
#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)
参数nid 是要分配内存的 NUMA 节点 ID,
参数flags 是 GFP_ 分配标志,
参数order 是分配内存的大小.
返回值是一个指向第一个(可能返回多个页)page结构的指针, 失败时返回NULL。
alloc_pages 通过在当前 NUMA 节点分配内存( 它使用 numa_node_id 的返回值作为 nid 参数调用 alloc_pages_node)简化了alloc_pages_node调用。alloc_pages 省略了 order 参数而只分配单个页面。
释放分配的页:
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(对于没有驻留在缓存中的页) 通知内核,帮助分配器优化内存使用*/
vmalloc 和 ioremap
vmalloc 是一个基本的 Linux 内存分配机制,它在虚拟内存空间分配一块连续的内存区,尽管这些页在物理内存中不连续 (使用一个单独的 alloc_page 调用来获得每个页),但内核认为它们地址是连续的。 应当注意的是: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 处理才能转为物理地址。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变量使用如下宏定义:
DEFINE_PER_CPU(type, name);
/*若变量( name)是一个数组,则必须包含类型的维数信息,例如一个有 3 个整数的per-CPU 数组创建如下: */
DEFINE_PER_CPU(int[3], my_percpu_array);
虽然操作per-CPU变量几乎不必使用锁定机制。 但是必须记住 2.6 内核是可抢占的,所以在修改一个per-CPU变量的临界区中可能被抢占。并且还要避免进程在对一个per-CPU变量访问时被移动到另一个处理器上运行。所以必须显式使用 get_cpu_var 宏来访问当前处理器的变量副本, 并在结束后调用 put_cpu_var。 对 get_cpu_var 的调用返回一个当前处理器变量版本的 lvalue ,并且禁止抢占。又因为返回的是lvalue,所以可被直接赋值或操作。例如:
get_cpu_var(sockets_in_use)++;
put_cpu_var(sockets_in_use);
当要访问另一个处理器的变量副本时, 使用:
per_cpu(variable, int cpu_id);
当代码涉及到多处理器的per-CPU变量,就必须实现一个加锁机制来保证访问安全。
动态分配per-CPU变量方法如下:
void *alloc_percpu(type);
void *__alloc_percpu(size_t size, size_t align);/*需要一个特定对齐的情况下调用*/
void free_percpu(void *per_cpu_var); /* 将per-CPU 变量返回给系统*/
/*访问动态分配的per-CPU变量通过 per_cpu_ptr 来完成,这个宏返回一个指向给定 cpu_id 版本的per_cpu_var变量的指针。若操作当前处理器版本的per-CPU变量,必须保证不能被切换出那个处理器:*/
per_cpu_ptr(void *per_cpu_var, int cpu_id);
/*通常使用 get_cpu 来阻止在使用per-CPU变量时被抢占,典型代码如下:*/
int cpu;
cpu = get_cpu()
ptr = per_cpu_ptr(per_cpu_var, cpu);
/* work with ptr */
put_cpu();
/*当使用编译时的per-CPU 变量, get_cpu_var 和 put_cpu_var 宏将处理这些细节。动态per-CPU变量需要更明确的保护*/
per-CPU变量可以导出给模块, 但必须使用一个特殊的宏版本:
EXPORT_PER_CPU_SYMBOL(per_cpu_var);
EXPORT_PER_CPU_SYMBOL_GPL(per_cpu_var);
/*要在模块中访问这样一个变量,声明如下:*/
DECLARE_PER_CPU(type, name);
注意:在某些体系架构上,per-CPU变量的使用是受地址空间有限的。若在代码中创建per-CPU变量, 应当尽量保持变量较小.
获得大的缓冲区
大量连续内存缓冲的分配是容易失败的。到目前止执行大 I/O 操作的最好方法是通过离散/聚集操作 。
在引导时获得专用缓冲区
若真的需要大块连续的内存作缓冲区,最好的方法是在引导时来请求内存来分配。在引导时分配是获得大量连续内存页(避开 __get_free_pages 对缓冲大小和固定颗粒双重限制)的唯一方法。一个模块无法在引导时分配内存,只有直接连接到内核的驱动才可以。而且这对普通用户不是一个灵活的选择,因为这个机制只对连接到内核映象中的代码才可用。要安装或替换使用这种分配方法的设备驱动,只能通过重新编译内核并且重启计算机。
当内核被引导, 它可以访问系统种所有可用物理内存,接着通过调用子系统的初始化函数, 允许初始化代码通过减少留给常规系统操作使用的 RAM 数量来分配私有内存缓冲给自己。
在引导时获得专用缓冲区要通过调用下面函数进行:
#include <linux/bootmem.h>
/*分配不在页面边界上对齐的内存区*/
void *alloc_bootmem(unsigned long size);
void *alloc_bootmem_low(unsigned long size); /*分配非高端内存。希望分配到用于DMA操作的内存可能需要,因为高端内存不总是支持DMA*/
/*分配整个页*/
void *alloc_bootmem_pages(unsigned long size);
void *alloc_bootmem_low_pages(unsigned long size);/*分配非高端内存*/
/*很少在启动时释放分配的内存,但肯定不能在之后取回它。注意:以这个方式释放的部分页不返回给系统*/
void free_bootmem(unsigned long addr, unsigned long size);
更多细节请看内核源码中 Documentation/kbuild 下的文件。
ARM9开发板实验
这几个实验我都没有用LDD3原配的代码,而是用以前的ioctl_and_lseek的代码改的。
(1)scullc 模块试验
模块源码:scullc
测试代码:scullc_test
(2)scullp 模块试验
模块源码:scullp
测试代码:scullp_test
(3)scullv 模块试验
模块源码:scullv
实验现象:
[Tekkaman2440@SBC2440V4]#insmod /lib/modules/scullc.ko
[Tekkaman2440@SBC2440V4]#cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
14 sound
81 video4linux
89 i2c
90 mtd
116 alsa
128 ptm
136 pts
153 spi
180 usb
189 usb_device
204 s3c2410_serial
252 scullc
253 usb_endpoint
254 rtc
Block devices:
1 ramdisk
256 rfd
7 loop
31 mtdblock
93 nftl
96 inftl
179 mmc
[Tekkaman2440@SBC2440V4]#mknod -m 666 scullc c 252 0
[Tekkaman2440@SBC2440V4]#ls -l /tmp/test (注:test是普通的2070B的文本文件)
-rw-r--r-- 1 root root 2070 Nov 24 2007 /
[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullc
[Tekkaman2440@SBC2440V4]#cat /proc/slabinfo
slabinfo - version: 2.1 (statistics)
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail> : globalstat <listallocs> <maxobjs> <grown> <reaped> <error> <maxfreeable> <nodeallocs> <remotefrees> <alienoverflow> : cpustat <allochit> <allocmiss> <freehit> <freemiss>
scullc 1 1 4020 1 1 : tunables 24 12 0 : slabdata 1 1 0 : globalstat 1 1 1 0 0 0 0 0 0 : cpustat 0 1 0 0
......
[Tekkaman2440@SBC2440V4]#cat /proc/scullcseq
Device 0: qset 1000, q 4000, sz 2070
item at c3edca00, qset at c3efe000
0: c3f56028
[Tekkaman2440@SBC2440V4]#/tmp/scullc_test
open scull !
scull_quantum=10 scull_qset=4
close scull !
[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullc
[Tekkaman2440@SBC2440V4]#cat /proc/slabinfo
slabinfo - version: 2.1 (statistics)
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail> : globalstat <listallocs> <maxobjs> <grown> <reaped> <error> <maxfreeable> <nodeallocs> <remotefrees> <alienoverflow> : cpustat <allochit> <allocmiss> <freehit> <freemiss>
scullc 207 207 4020 1 1 : tunables 24 12 0 : slabdata 207 207 0 : globalstat 208 207 208 1 0 0 0 0 0 : cpustat 0 208 1 0
......
[Tekkaman2440@SBC2440V4]#cat /proc/scullcmem
Device 0: qset 4, q 10, sz 2070
item at c3edc930, qset at c3edc8c8
item at c3edc894, qset at c3edc860
item at c3edc82c, qset at c3edc7f8
item at c3edc7c4, qset at c3edc790
item at c3edc75c, qset at c3edc178
item at c3edc624, qset at c3edc4b8
item at c3edc9cc, qset at c3edc998
item at c3edc4ec, qset at c3edc964
item at c3edc484, qset at c3edccd8
item at c3edcca4, qset at c3edcc70
item at c3edcc3c, qset at c3edcc08
item at c3edcbd4, qset at c3edcba0
item at c3edcb6c, qset at c3edcb38
item at c3edcb04, qset at c3edcad0
item at c3edca9c, qset at c3edca68
item at c3edca34, qset at c3edca00
item at c3edc8fc, qset at c3edcfb0
item at c3edcf7c, qset at c3edcf48
item at c3edcf14, qset at c3edcee0
item at c3edceac, qset at c3edce78
item at c3edce44, qset at c3edce10
item at c3edcddc, qset at c3edcda8
item at c3edcd74, qset at c3edcd40
item at c3edcd0c, qset at c388a450
item at c388a41c, qset at c388a3e8
item at c388a3b4, qset at c388a380
item at c388a34c, qset at c388a318
item at c388a2e4, qset at c388a2b0
item at c388a27c, qset at c388a248
item at c388a214, qset at c388a1e0
item at c388a1ac, qset at c388a178
item at c388a144, qset at c388a790
item at c388a75c, qset at c388a728
item at c388a6f4, qset at c388a6c0
item at c388a68c, qset at c388a658
item at c388a624, qset at c388a5f0
item at c388a5bc, qset at c388a588
item at c388a554, qset at c388a520
item at c388a4ec, qset at c388a4b8
item at c388a484, qset at c388aad0
item at c388aa9c, qset at c388aa68
item at c388aa34, qset at c388aa00
item at c388a9cc, qset at c388a998
item at c388a964, qset at c388a930
item at c388a8fc, qset at c388a8c8
item at c388a894, qset at c388a860
item at c388a82c, qset at c388a7f8
item at c388a7c4, qset at c388ae10
item at c388addc, qset at c388ada8
item at c388ad74, qset at c388ad40
item at c388ad0c, qset at c388acd8
item at c388aca4, qset at c388ac70
0: c38fb028
1: c38fc028
2: c38fd028
[Tekkaman2440@SBC2440V4]#insmod /lib/modules/scullp.ko
[Tekkaman2440@SBC2440V4]#cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
14 sound
81 video4linux
89 i2c
90 mtd
116 alsa
128 ptm
136 pts
153 spi
180 usb
189 usb_device
204 s3c2410_serial
251 scullp
252 scullc
253 usb_endpoint
254 rtc
Block devices:
1 ramdisk
256 rfd
7 loop
31 mtdblock
93 nftl
96 inftl
179 mmc
[Tekkaman2440@SBC2440V4]#mknod -m 666 /dev/scullp c 251 0
[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullp
[Tekkaman2440@SBC2440V4]#cat /proc/scullpseq
Device 0: qset 1000, q 4000, sz 2070
item at c3edc964, qset at c3eff000
0: c3f21000
[Tekkaman2440@SBC2440V4]#/tmp/scullp_test
open scull !
scull_quantum=1000 scull_qset=2
close scull !
[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullp
[Tekkaman2440@SBC2440V4]#cat /proc/scullpseq
Device 0: qset 2, q 1000, sz 2070
item at c3edcc70, qset at c3edcca4
item at c3edc484, qset at c3edcc3c
0: c383f000
[Tekkaman2440@SBC2440V4]#insmod /lib/modules/scullv.ko
[Tekkaman2440@SBC2440V4]#cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
14 sound
81 video4linux
89 i2c
90 mtd
116 alsa
128 ptm
136 pts
153 spi
180 usb
189 usb_device
204 s3c2410_serial
250 scullv
251 scullp
252 scullc
253 usb_endpoint
254 rtc
Block devices:
1 ramdisk
256 rfd
7 loop
31 mtdblock
93 nftl
96 inftl
179 mmc
[Tekkaman2440@SBC2440V4]#mknod -m 666 /dev/scullv c 250 0
[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullv
[Tekkaman2440@SBC2440V4]#cat /proc/scullvseq
Device 0: qset 1000, q 4000, sz 2070
item at c3edc7c4, qset at c389d000
0: c485e000
[Tekkaman2440@SBC2440V4]#cat /proc/scullpseq
Device 0: qset 2, q 1000, sz 2070
item at c3edcc70, qset at c3edcca4
item at c3edc484, qset at c3edcc3c
0: c383f000
从上面的数据可以看出: 在s3c2440 (ARM9)体系上, vmalloc返回的虚拟地址就在用于映射物理内存的地址上。