📄 (ldd) ch07-获取内存(转载).txt
字号:
注意在2.1版内核中vremap已经被重命名为ioremap。而且,Linux 2.1引入了一
个新的头文件,<linux/vmalloc.h>,使用vmalloc时应将它包含进来。
与其他内存分配函数不同的是,vmalloc返回很“高”的地址值-这些地址要高
于物理内存的顶部。由于vmalloc对页表调整后允许用连续的“高”地址访问分配得到的
页面,因此处理器是可以访问返回得到的内存区域的。内核能和其他地址一样地使用vma
lloc返回的地址,但程序中用到的这个地址与地址总线上的地址并不相同。
用vmalloc分配得到的地址是不能在微处理器之外使用的,因为它们只有在处理
器的分页单元之上才有意义。但驱动程序需要真正的物理地址时(象外设用以驱动系统总
线的DMA地址),你就不能使用vmalloc了。正确使用vmalloc函数的场合是为软件分配一
大块连续的用于缓冲的内存区域。注意vmalloc的开销要比__get_free_pages大,因为它
处理获取内存还要建立页表。因此,不值得用vmalloc函数只分配一页的内存空间。
使用vmalloc函数的一个例子函数是create_module系统调用,它利用vmalloc函
数来获取被创建模块需要的内存空间。而在insmod调用重定位模块代码后,将会调用mem
cpy_fromfs函数把模块本身拷贝进分配而得的空间内。
用vmalloc分配得到的内存空间用vfree函数来释放,这就象要用kfree函数来释
放kmalloc函数分配得到的内存空间。
和vmalloc一样,vremap(或ioremap)也建立新的页表,但和vmalloc不同的是,v
remap实际上并不分配内存。vremap的返回值是个虚拟地址,可以用来访问指定的物理内
存区域;得到的这个虚拟地址最后要调用vfree来释放掉。
vremap用于将高内存空间的PCI缓冲区映射到用户空间。例如,如果VGA设备的帧缓冲区
被映射到地址0xf0000000(典型的一个值)后,vremap就可以建立正确的页表让处理机可
以访问。而系统初始化时建立的页表只是用于访问低于物理地址空间的内存区域。系统
的初始化过程并不检测PCI缓冲区,而是由各个驱动程序自己负责管理自己的缓冲区;PC
I的细节将在第15章“外设总线概貌”的“PCI接口”一节中讨论。另外,你不必重映射
I的细节将在第15章“外设总线概貌”的“PCI接口”一节中讨论。另外,你不必重映射
低于1MB的ISA内存区域,因为这段内存空间可用其他方法访问,参见第8章“硬件管理”
的“访问设备卡上的内存”一节。
如果你希望驱动程序能在不同的平台间移植,那么使用vremap时就要小心。在一
些平台上是不能直接将PCI内存区域映射到处理机的地址空间的,例如Alpha上就不行。
此时你就不能象普通内存区域那样地对重映射区域进行访问,你要用readb函数或者其他
一些I/O函数(可参见第8章的“1M内存空间之上的ISA内存”一节)。这套函数可以在不同
平台间移植。
对vmalloc和vremap函数可分配的内存空间大小并没有什么限制,但为了能检测
到程序员的犯下的一些错误,vmalloc不允许分配超过物理内存大小的内存空间。但是记
着,vmalloc函数请求过多的内存空间会产生一些和调用kmalloc函数时相同的问题。
vremap和vmalloc函数都是面向页的(它们都会修改页表);因此分配或释放的内
存空间实际上都会上调为最近的一个页边界。而且,vremap函数并不考虑如何重映射不
是页边界的物理地址。
是页边界的物理地址。
vmalloc函数的一个小缺点是它不能在中断时间内使用,因为它的内部实现调用
了kmalloc(GFP_KERNEL)来获取页表的存储空间。但这不是什么问题-如果__get_free_p
age函数都还不能满足你的中断处理程序的话,那你还是先修改一下你的软件设计吧。
使用虚拟地址的scull: scullv
使用了vmalloc的示例程序是scullv模块。正如scullp,这个模块也是scull的一
个变种,只是使用了不同的分配函数来获取设备用以储存数据的内存空间。
该模块每次分配16页的内存(在Alpha上是128KB,x86上是64KB)。这里内存分配
用了较大的数据块,目的是获取比scullp更好的性能,并且表明此时使用其他可行的分
配技术相对来说会更耗时。用__get_free_pages函数来分配一页以上的内存空间容易出
错,而且即使成功了,也相对较慢。前面我们已经看到,用vmalloc分配若干页比其他函
数要快一些,但由于存在建立页表的开销,只分配一页时却会慢一些。scullv设计得和s
cullp很相似。order参数指定分配的内存空间的“幂”,缺省为4。scullv和scullp的唯
一差别在下面一段代码:
/* 此处用虚拟地址来分配一个单位内存 */
if (!dptr->data[s_pos]) {
dptr->data[s_pos] = (void *)vmalloc(PAGE_SIZE <<order);
if (!dptr->data[s_pos])
return -ENOMEM;
/* 这段代码释放所有分配单元 */
for (i = 0; i < qset; i++)
if (dptr->data[i])
vfree (dptr->data[i]);
如果你在编译这两个模块时都打开了调试开关,就可以通过读它们在/proc下创建的文件
来查看它们进行的数据分配。下面的快照取自我的计算机,我机器的物理地址是从0到0x
1800000(共24MB):
morgana.root# cp /bin/cp /dev/scullp0
morgana.root# cat /proc/scullpmem
Device 0: qset 500, order 0, sz 19652
Item at 0063e598, qset at 006eb018
0: 150e000
1: de6000
2: 10ca000
3: e19000
3: e19000
4: bd1000
morgana.root# cp /zImage.last /dev/scullv0
morgana.root# cat /proc/scullvmem
Device 0: qset 500, order 4, sz 289840
Item at 0063ec98, qset at 00b3e810
0: 2034000
1: 2045000
2: 2056000
3: 2067000
4: 2078000
4: 2078000
从这些值可以看到,scullp分配物理地址(小于0x1800000),而scullv分配虚拟
地址(但注意实际数值与Linux 2.1会不同,因为虚拟地址空间的组织形式变了-见第17
章“近期发展”的“虚拟内存”一节)。
“脏”的处理方法(Playing Dirty)
如果你确实需要大量的连续的内存用作缓冲区,最简单的(也是最不灵活的,但
也最容易出错的)方法是在系统启动时分配。显然,模块不能在启动时分配内存;只有直
接连到内核的设备驱动程序才能运行这种“脏”的处理方式,在启动时分配内存。
尽管在启动时就进行分配似乎是获得大量内存缓冲区的唯一方法,但我还会在第
13章的“分配DMA缓冲区”一节中介绍到另一种分配技术(虽然可能更不好)。在启动时分
配缓冲区有点“脏”,因为它跳过了内核内存管理机制。而且,这种技术普通用户无法
使用,因为它要修改内核。绝大多数用户还是愿意装载模块,而并不愿意对内核打补定
或重新编译内核。尽管我不推荐你使用这种“分配技术”,但它还是值得在此提及的,
因为在GFP_DMA被引入之前,这种技术曾是Linux的早期版本里分配可用于DAM传输的缓冲
区的唯一方法。
让我们先看看启动是是如何进行分配的。内核启动时,它可以访问系统所有的内
存空间。然后以空闲内存区域的边界作为参数,调用内核的各个子系统的初始化函数进
行初始化。每个初始化函数都可以“偷取”一部分空闲区域,并返回新的空闲内存下界
。由于驱动程序是在系统启动时进行内存分配的,所以可以从空闲RAM的线性数组获取连
续的内存空间。
除了不能释放得到的缓冲区,这种内存分配技术还有些缺点。驱动程序得到这些
内存页后,就无法将它们再放到空闲页面池中了;页面池是在已经物理内存的分配结束
后才建立起来的,而且我也不推荐象这样“黑客”内存管理的内部数据结构。但另一方
面,这种技术的优势是,它可以获取用于DMA传输等用途的一段连续区域。目前这也是分
配超过32页的连续内存缓冲区的唯一的“安全”的方式,32页这个值是源于get_free_pa
ges函数参数order可取的最大值为5。但是如果你需要的多个内存页可以是物理上不连续
的,最好还是用vmalloc函数。
如果你真要在启动时获取内存的话,你必须修改内核代码中的init/main.c文件
。关于main.c文件的更多细节可参见第16章和第8章的“1M内存空间之上的ISA内存”一
。关于main.c文件的更多细节可参见第16章和第8章的“1M内存空间之上的ISA内存”一
节。
注意,这种“分配”只能是按页面大小的倍数进行,而页面数不必是2的某个幂
次。
快速参考
与内存分配有关的函数和符号列在下面:
#include <linux/malloc.h>
void *kmalloc(unsigned int size, int priority);
void kfree(void *obj);
这两个函数是最常用的内存分配函数。
#include <linux/mm.h>
GFP_KERNEL
GFP_ATOMIC
GFP_DMA
kmalloc函数的优先权。GFP_DMA是个标志位,可以与GFP_KERNEL和/或GFP_ATOMIC相或。
unsigned long get_free_page(int priority);
unsigned long __get_free_page(int priority);
unsigned long __get_dma_pages(int priority, unsigned long order);
unsigned long __get_free_pages(int priority, unsigned long order,int dma);
这些都是面向页的内存分配函数。以下划线开头的函数不清零分配而得的页。只有前两
这些都是面向页的内存分配函数。以下划线开头的函数不清零分配而得的页。只有前两
个函数是在1.2版和2.0版的Linux间可移植的,而后两者在1.2版与2.0版中的行为并不同
。
void free_page(unsigned long addr);
void free_pages(unsigned long addr, unsigned long order);
这些函数用于释放面向页的分配得到的内存空间。
void* vmalloc(unsigned long size);
void* vremap(unsigned long offset, unsigned long size);
void vfree(void* addr);
这些函数分配或释放连续的虚拟地址空间。vremap用虚拟地址访问物理内存(在2.1版的L
inux中被称为ioremap),而vmalloc是用来分配空闲页面。两种情况下,都是用vfree来
释放分配的内存页。2.1版的Linux引入了头文件<linux/vmalloc.h>,使用这些函数时必
void* vremap(unsigned long offset, unsigned long size);
void vfree(void* addr);
这些函数分配或释放连续的虚拟地址空间。vremap用虚拟地址访问物理内存(在2.1版的L
inux中被称为ioremap),而vmalloc是用来分配空闲页面。两种情况下,都是用vfree来
释放分配的内存页。2.1版的Linux引入了头文件<linux/vmalloc.h>,使用这些函数时必
须先包含(#include)这个头文件。
--
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -