2.7 内存分配
一些设备驱动程序必须意识到内存区的存在,另外,许多驱动程序需要内存分配函数的服务。本节我们将简要地讨论这两点。
内核会以分页形式组织物理内存,而页大小则取决于具体的体系架构。在基于x86的机器上,其大小为4096B。物理内存中的每一页都有一个与之对应的struct page(定义在include/linux/ mm_types.h文件中):
在32位x86系统上,默认的内核配置会将4 GB的地址空间分成给用户空间的3 GB的虚拟内存空间和给内核空间的1 GB的空间(如图2-5所示)。这导致内核能处理的处理内存有1 GB的限制。现实情况是,限制为896 MB,因为地址空间的128 MB已经被内核数据结构占据。通过改变3 GB/1 GB的分割线,可以放宽这个限制,但是由于减少了用户进程虚拟地址空间的大小,在内存密集型的应用程序中可能会出现一些问题。
图2-5 32位PC系统上默认的地址空间分布
内核中用于映射低于896 MB物理内存的地址与物理地址之间存在线性偏移;这种内核地址被称作逻辑地址。在支持“高端内存”的情况下,在通过特定的方式映射这些区域产生对应的虚拟地址后,内核将能访问超过896 MB的内存。所有的逻辑地址都是内核虚拟地址,而所有的虚拟地址并非一定是逻辑地址。
因此,存在如下的内存区。
(1) ZONE_DMA(小于16 MB),该区用于直接内存访问(DMA)。由于传统的ISA设备有24条地址线,只能访问开始的16 MB,因此,内核将该区献给了这些设备。
(2) ZONE_NORMAL(16~896 MB),常规地址区域,也被称作低端内存。用于低端内存页的struct page结构中的“虚拟”字段包含了对应的逻辑地址。
(3) ZONE_HIGH(大于896 MB),仅仅在通过kmap()映射页为虚拟地址后才能访问。(通过kunmap()可去除映射。)相应的内核地址为虚拟地址而非逻辑地址。如果相应的页未被映射,用于高端内存页的struct page结构体的“虚拟”字段将指向NULL。
kmalloc()是一个用于从ZONE_NORMAL区域返回连续内存的内存分配函数,其原型如下:
void *kmalloc(int count, int flags);
count是要分配的字节数,flags是一个模式说明符。支持的所有标志列在include/linux./gfp.h文件中(gfp是get free page的缩写),如下为常用标志。
(1) GFP_KERNEL,被进程上下文用来分配内存。如果指定了该标志,kmalloc()将被允许睡眠,以等待其他页被释放。
(2) GFP_ATOMIC,被中断上下文用来获取内存。在这种模式下,kmalloc()不允许进行睡眠等待,以获得空闲页,因此GFP_ATOMIC分配成功的可能性比用GFP_KERNEL低。
由于kmalloc()返回的内存保留了以前的内容,将它暴露给用户空间可到会导致安全问题,因此我们可以使用kzalloc()获得被填充为0的内存。
如果需要分配大的内存缓冲区,而且也不要求内存在物理上有联系,可以用vmalloc()代替kmalloc():
void *vmalloc(unsigned long count);
count是要请求分配的内存大小。该函数返回内核虚拟地址。
vmalloc()需要比kmalloc()更大的分配空间,但是它更慢,而且不能从中断上下文调用。另外,不能用vmalloc()返回的物理上不连续的内存执行DMA。在设备打开时,高性能的网络驱动程序通常会使用vmalloc()来分配较大的描述符环行缓冲区。
内核还提供了一些更复杂的内存分配技术,包括后备缓冲区(look aside buffer)、slab和mempool;这些概念超出了本章的讨论范围,不再细述。