1. 页
内核把物理页作为内存管理的基本单位。内存管理单元(MMU)是把虚拟地址转换为物理地址的硬件。大多数32位体系结构支持4KB的页,而64位体系结构一般会支持8KB的页。内核用struct page结构表示系统中的每个物理页,在<linux/mm.h>中定义。
struct page{
page_flags_t flags;
atomic_t _count;
atomic_t _mapcount;
unsigned long private;
struct address_space *mapping;
pgoff_t index;
struct list lru;
void *virtual;
};
flag域用来存放页的状态,包括是不是胀的,是不是锁定在内存等。
_count域存放页的引用计数--当计数值变为0时,就说明当前内核没有用该页,于是在新的分配中就可以使用它。
virual域是页的虚拟地址。
必须理解一点,page结构与物理页相关,而不是与虚拟页相关。内核仅仅用它来描述当前时刻在相关的物理页中存放的东西。它是描述物理内存本身,而不是描述包含在其中的数据。
2. 区
由于硬件的限制,内核把页划分为三种区(<linx/mmzone.h>):
ZONE_DMA--包含的页能用来执行DMA操作
ZONE_NORMAL--包含的页都能正常映射的页
ZONE_HIGHMEM--包含"高端内存",其中的页并不能永久地映射到内核地址空间。
在X86上,ZONE_HIGHMEM为高于896M的所有物理内存;ZONE_DMA为0-16M的物理内存。
每个区都用struct zone表示,详见<linux/mmzone.h>。内核启动期间,会初始化这三个区,代码位于<mm/page_alloc.c>。
原子操作可以保证指令以原子的方式执行,执行过程不被打断。内核提供了两组原子操作接口:一组是针对整数进行操作;另一组是针对单独的位进行操作。
3. 内存分配与释放
3.1. 获得页
内核提供了一种请求内存的底层机制,并提供了对它进行访问的接口。
标识
描述
alloc_page(flags)
只分配一页,返回指向页结构的指针
alloc_pages(flags, order)
分配(1<<order)页,返回指向第一页页结构的指针
__get_free_page(flags)
只分配一页,返回指向其逻辑地址的指针
__get_free_pages(flags, order)
只分配(1<<order)页,返回指向第一页逻辑地址的指针
get_zeroed_page(flags)
只分配一页,让其内容填充0,返回其逻辑地址的指针
Page_address(*page)
把给定的页换成它的虚拟地址
调用__get_free_pages等函数后需要检查错误,因为内核分配内存可能会失败。
3.2. 释放页
释放页时需要谨慎,只能释放属于自己的页。传递了错误的struct page或地址,用了错误的order值,都可能导致系统崩溃。释放页的接口有:
void __free_pages(struct page *page, unsigned int order);
void free_pages(unsigned long addr, unsigned int order);
void ree_page(unsigned long addr);
3.3. kmalloc和kfree函数
kmalloc函数可以获得以字节为单位的一块内核内存(<linux/slab.h>):
void *kmalloc(size_t size, int flags);
void kfree(const void *ptr);
该函数返回一个指向内存块的指针,所分配的内存区在物理上是连续的。这在出错时,它返回NULL。在调用kmalloc函数后,必须检查返回的是不是NULL。
Kfree函数释放由kmalloc分配出来的内存块,注意调用kfree(NULL)是安全的。
如果搜索的范围仅限于一个字,使用__ffs()和__ffz()这两个函数更好。
3.4. flags标志
这些标志分为三类:
1) 行为修饰符,表示内核应该如何分配所需内存。例如,中断程序在分配内存中的过程中不能睡眠。
2) 区修饰符,表示从哪儿(区)分配内存。
3) 类型,是组合了行为修饰符和区修饰符。
内核中常用的标志是GFP_KERNEL,这种分配可能会引起睡眠,使用的是普通优先级。另一个是GFP_ATOMIC,表示不能睡眠的内存分配,即使没有足够的连续内存块可供使用,内核可能无法释放出内存来,内核也不能让调用者睡眠。分配成功的几率比GPF_KERNEL小。GFP_DMA表示分配器必须满足从ZONE_DMA进行分配,满足某些驱动程序的需求。GFP_NOIO和GFP_NOFS,可能会引起阻塞,但它们会避免执行某些操作。用在某些低级块I/O或文件系统的代码中。
什么时候使用哪种标志,见下表:
情形
标志
进程上下文,可以睡眠
GFP_KERNEL
进程上下文,不可以睡眠
GFP_ATOMIC
中断处理程序
GFP_ATOMIC
软中断
GFP_ATOMIC
Tasklet
GFP_ATOMIC
需要用于DMA的内存,可以睡眠
GFP_DMA| GFP_KERNEL
需要用于DMA的内存,不可以睡眠
GFP_DDMA |GFP_ATOMIC
3.5. vmalloc和vfree函数
vmalloc函数分配的内存虚拟地址是连续的,而物理地址则无需连续。这也是用户空间的分配函数的工作方式。而kmalloc函数确保页在物理地址上时连续的,自然虚拟地址也是连续的。
在性能上,vmalloc函数为了把不连续的页转换为虚拟地址空间上连续的页,必须专门建立页表项。所以内核代码大多用kmalloc函数。在<linux/vmalloc.h>中定义:
void *vmalloc(unsigned long size);
void vfree(void * addr);
vmalloc函数返回一个指针,执行逻辑上连续的一块内存区,在发生错误时,函数返回NULL。vmalloc和vfree函数可能睡眠,因此,不能从中断上下文中进行调用,也不能从其他不允许阻塞的情况下进行调用。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/wzhwho/archive/2009/05/21/4207542.aspx