内核将物理页作为内存管理的基本单位,而处理器最小的处理单位可能是一个字节或一个字。对于内存管理来说,页是最小的管理单元。MMU维护着以页为最小粒度的页表。不同的平台页的大小也不同。许多平台支持多种页大小。大部分32位平台拥有4KB个页,而64位平台拥有8KB个页。
区域(Zone)
由于硬件的限制,内核不能将所有的内存物理页一视同仁。有些物理页的地址只能用于特定的任务。正是由于这个原因,内核将物理页划分成区域。内核用区域来对具有类似属性的页进行分组。内核有4个主要的内存区域:
ZONE_DMA——可以执行DMA操作的区域。
ZONE_DMA32——与ZONE_DMA类似,该区域包含可以执行DMA操作的页。这些页只能由32位设备访问。
ZONE_NORMAL——该区域包含了普通的、正常映射了的页。
ZONE_HIGHMEM——该区域包含了“高内存”,这些物理页不是永久性地映射到了内核的地址空间中。X86平台上的映射情况如下:
底层页操作
内核提供了一种底层机制来请求内存,以及几个访问内存的接口。所有这些接口分配的内存的粒度为页大小,声明在<linux/gfp.h>,核心函数为:
struct page * alloc_pages(gfp_t gfp_mask, unsigned int order)
该函数分配了2order个连续的物理页,并返回指向第一个页结构的指针。
void * page_address(struct page *page)
该函数返回指向给定物理页当前对应的逻辑地址的指针。
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
与alloc_pages类似,只不过返回的是请求页的逻辑地址。
如果只请求一个页,可使用如下相应的函数:
struct page * alloc_page(gfp_t gfp_mask)
unsigned long __get_free_page(gfp_t gfp_mask)
如果需要返回零填充的页,使用函数:
unsigned long get_zeroed_page(unsigned int gfp_mask)
与函数__get_free_page()类似,只不过返回的是零填充后的页。
释放页
下面的一些函数用于释放不再需要的页:
void __free_pages(struct page *page, unsigned int order)
void free_pages(unsigned long addr, unsigned int order)
void free_page(unsigned long addr)
kmalloc()
kmalloc()函数的操作类似于用户空间的malloc(),只是多了一个flags参数。kmalloc()函数是一个获取以字节为单位的内核内存的简单接口。
void * kmalloc(size_t size, gfp_t flags)
该函数返回指向某个内存区域的指针,该内存区域在物理上是连续的。
kfree()
kfree()释放由kmalloc()分配的内核内存空间,其函数原型如下:
void kfree(const void *ptr)
vmalloc()
vmalloc()分配一段在物理上不连续,但在虚拟地址空间上是连续的内存。它一般用于分配较大的内存空间时。其函数原型如下:
void * vmalloc(unsigned long size)
该函数可以会休眠,所以不能在中断上下文中使用或其他不能休眠的情形。
出于性能上的考虑,在内核代码中,分配内存时一般使用kmalloc()函数。
vfree()
该函数用于释放由vmalloc()函数分配的内存空间。其函数原型如下所示:
void vfree(const void *addr)
内核栈
内核栈占据一个或二个物理页,取决于编译时的配置选项。这些栈的大小从4KB到16KB大小不等。历史上,中断处理程序与进程共享一个栈。当启用了单页栈后,中断处理函数拥有了自己的栈。
由于内核栈的大小限制,所以在内核函数中,尽量控制栈变量的大小和数量,避免在一个函数内部定义很大数组。
高内存映射
在X86平台上,物理地址超过896M的都慎于高内存。这些地址并不永久地或自动地映射到内核地址空间。这些物理面必须映射到内核的逻辑地址空间。在X86平台上,高内存地址通常映射到了处于3G和4G之间的内存地址。
为了将一个指定的page结构映射到内核的地址空间,使用如下函数:
void *kmap(struct page *page)
该函数可用于低内存地址或高内存地址的物理页映射。当物理页处于低内存,该函数返回该页的虚拟地址。当物理页处于高内存时,将创建一个永久映射,并返回返回映射后的地址。
void kunmap(struct page *page)
该函数解除映射。kmap()和kunmap()函数均不能用于中断上下文中。
临时映射
有时,映射建立必须发生在中断上下文中,这时可以使用另外一对函数。
void *kmap_atomic(struct page *page, enum km_type type)
和
void kunmap_atomic(void *kvaddr, enum km_type type)