在前面的博文里,我们讲解了基于80x86体系的Linux内核分段和分页机制,并详细地讨论了Linux的内存布局。有了这些基本概念以后,我们就来详细讨论内核如何动态地管理那些可用的内存空间。
对于80386这种32位的处理器结构,Linux采用4KB页框大小作为标准的内存分配单元。内核必须记录每个页框的当前状态,例如,区分哪些页框包含的是属于进程的页,而哪些页框包含的是内核代码或内核数据。内核还必须能够确定动态内存中的页框是否空闲,如果动态内存中的页框不包含有用的数据,那么这个页框就是空闲的。在以下情况下页框是不空闲的:包含用户态进程的数据、某个软件高速缓存的数据、动态分配的内核数据结构、设备驱动程序缓冲的数据、内核模块的代码等等。
内核用数据结构page描述一个页框的状态信息,所有的页描述符存放在全局mem_map数组中,其数组的下标为页框号(pfn)。因为每个描述符长度为32字节,所以mem_map所需要的空间略小于整个RAM的1%。
那么一个页描述符怎样与一个占据4k的页框相联系(映射)呢?有了mem_map数组,这个问题就很简单了。因为如果知道了page数据的地址pd,用pd去减去mem_map就得到了pd的页框号pfn。那么这个物理页的物理地址是physAddr = pfn << PAGE_SHIFT 。
在得知该物理页的物理地址是physAddr后,就可以视physAddr的大小得到它的虚拟地址:
1.physAddr < 896M 对应虚拟地址是 physAddr + PAGE_OFFSET (PAGE_OFFSET=3G)
2.physAddr >= 896M 对应虚拟地址不是静态映射的,通过内核的高端虚拟地址映射得到一个虚拟地址。
在得到该页的虚拟地址之后,内核就可以正常访问这个物理页了。
内核提供一个virt_to_page(addr)宏来产生线性地址addr对应的页描述符地址。pfn_to_page(pfn)宏产生与页框号pfn对应的页描述符地址。相反,也提供page_to_pfn(pg)宏来产生页描述符对应的页的页框号pfn。注意,针对80x86结构,上述宏并不是直接通过men_map数组来确定页框号的,而是通过内存管理区的zone_mem_map来确定的,不过原理是一样的:
#define page_to_pfn(pg) /
({ /
struct page *__page = pg; /
struct zone *__zone = page_zone(__page); /
(unsigned long)(__page - __zone->zone_mem_map) /
+ __zone->zone_start_pfn; /
})
这里千万要注意!不要混淆一个概念。这里的physAddr虽然表示物理地址,但是并不能说明该地址的数据就一定存在于物理内存中。那么如何判断这个页到底在不在内存中呢?你看,前面的知识就用到了——分页机制。也就是说,如果这个页因为各种各样五花八门的原因被交换出去了,那么它对应的页的Present标志就为0。这里就牵涉到缺页异常了,要深入了解,请关注笔者后面的博文。
在这里我们只需要对数据结构page详细讨论以下两个字段:
1、_count:页的引用计数器。如果该字段为-1,则相应页框空闲,并可被分配给任一进程或内核本身;如果该字段的值大于或等于0,则说明页框被分配给一个或多个进程,或用于存放一些内核数据结构。page_count()函数返回_count加1后的值,也就是该页的使用者的数目。
2、flags:包含多达32个用来描述页框状态的标志。对于每个PG_xyz标志,内核都定义了操纵其值的一些宏。通常,PageXyz宏返回标志的值,而SetPageXyz和ClearPageXyz宏分别设置和清除相应的位。
含义 |
|
PG_locked |
页被锁定,例如,在磁盘I/O操作中涉及的页。 |
PG_error |
在传输页时发生错误 |
PG_referenced |
刚刚访问过的页 |
PG_uptodate |
在完成读操作后置位,除非发生磁盘I/O 错误 |
PG_dirty |
页已经被修改 |
PG_lru |
页在活动或非活动页链表中 |
PG_active |
页在活动页链表中 |
PG_slab |
包含在slab 中的页框 |
PG_highmem |
页框属于ZONE_HIGHMEM 管理区 |
PG_checked |
由一些文件系统(如Ext2 和Ext3)使用的标志 |
PG_arch_1 |
在80x86 体系结构上没有使用 |
PG_reserved |
页框留给内核代码或没有使用 |
PG_private |
页描述符的private字段存放了有意义的数据 |
PG_writeback |
正在使用writepage方法将页写到磁盘上 |