内存管理模块分为2部分:物理页面管理和页表管理
。前者强调对机器拥有的物理内存的管理
,包括建立对应的数据结构、处理分配和回收动作等。而后者主要是强调利用Intel x86系列处理器的页式地址管理功能,
完成(虚拟)线性地址到物理地址的转换
,包括建立页目录、页表等。
物理页面管理相关的函数:
- boot_alloc()
- page_init()
- page_alloc()
- page_free()
背景知识
JOS的启动过程是先把bootsector的内容读到0x7c00处,bootsector中的代码开始执行后,会从磁盘上紧接着自己的第2个扇区开始,一直读8个扇区的内容(一共是8×512=4KB,ELF头的大小)到0x10000(64KB)的地方,然后通过对ELF头的解析,得到kernel模块编译出来后所占的大小,并将kernel读到物理内存0x100000(1MB)开始的地方。
然后设置好GDT,并调用i386_init()函数,而i386_init()函数在将自己的BSS区域清零后,调用cons_init()函数设置屏幕显示,为cprintf的运行做 好准备。随后调用i386_detect_memory()函数和i386_vm_init()。后者就是我们内存管理的主要函数了
.bss 节:未初始化的全局变量部分,一部分不会在磁盘有存储空间,因为这些变量并没有被初始化,因此全部默认为0,在将这节装入到内存的时候程序需要为其分配相应大小的初始值为0的内存空间
在调用i386_init()后,系统将重载GDT,新的GDT:
SEG_NULL # null seg
SEG(STA_X|STA_R, -KERNBASE, 0xffffffff) # code seg
SEG(STA_W, -KERNBASE, 0xffffffff) # data seg
新GDT后两项的base,它们是-KERNBASE。如果KERNBASE=0xF0000000,则
GDT的base=0x10000000
页面管理
JOS内核是以页(page)为最小单位,通过链表来管理内存的
。它使用MMU来映射,保护每一块被分配出去的内存
。操作系统必须要追踪记录哪些内存区域是空闲的,哪些是被占用的。
页面管理相关的数据结构
typedef uint32_t pte_t;
typedef uint32_t pde_t;
struct PageInfo {
// Next page on the free list.
// 空闲页面链表next指针
struct PageInfo *pp_link;
// pp_ref is the count of pointers (usually in page table entries)
// to this page, for pages allocated using page_alloc.
// Pages allocated at boot time using pmap.c's
// boot_alloc do not have valid reference count fields.
// 页面状态:空闲/占用
// 多个不同的虚拟地址被同时映射到相同的物理页上,PageInfo结构体的pp_ref
// 被位于虚拟地址UTOP之下的虚拟页所映射的次数
uint16_t pp_ref;
};
下面通过mem_init函数有关页面管理的代码进行简要的分析和流程的梳理
。部分代码如下
void mem_init(void)
{
i386_detect_memory();
// create initial page directory.
// kern_pgdir指向操作系统的页目录表,这个页紧跟着操作系统内核之后
kern_pgdir = (pde_t *) boot_alloc(PGSIZE);
// 分配一个页的大小,并且将这部分内存清0
memset(kern_pgdir, 0, PGSIZE);
// 为页目录表添加第一个页目录表项
// UVPT:0xef400000,从这个虚拟地址开始,存放的是操作系统的页表kern_pgdir
// PADDR(kern_pgdir)计算kern_pgdir所对应的真实物理地址
kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;
// 分配一块内存,用来存放一个struct PageInfo的数组
// 数组中的每一个PageInfo代表内存当中的一页,PageInfo为页面链表的节点
pages = (struct PageInfo *) boot_alloc(sizeof(struct PageInfo) * npages);
// 内存清0
memset(pages, 0, npages * sizeof(struct PageInfo));
page_init();
check_page_free_list(1);
check_page_alloc();
}
首先调用i386_detect_memory函数检测现在系统中有多少可用的内存空间。kern_pgdir是指向操作系统的页目录表的指针
,操作系统之后工作在虚拟内存模式下
时,就需要这个页目录表进行地址转换。
boot_alloc()函数暂时当做页分配器
,之后我们使用的真实页分配器是page_alloc()函数
。而它的核心思想就是维护一个静态变量nextfree,里面存放着
下一个可以使用的空闲内存空间的虚拟地址`,所以每次当我们想要分配n个字节的内存时,我们都需要修改这个变量的值
为页目录表添加第一个页目录表项
后,分配一块内存,分配npages数目的结构体PageInfo空间,由pages指向该块内存
。操作系统内核通过这个数组来追踪所有内存页的使用情况
。然后运行函数page_init(),这个函数初始化两个信息:pages数组和pages_free_list链表
(存放着所有空闲页的信息)
page_init后的内存分布
初始化所有物理内存页的相关数据结构后,check_page_free_list(1)和check_page_alloc()
,这两个检查程序检查页面管理是否正确。前者检查page_free_list链表的空闲页,是否都是合法/空闲
,后者检查page_alloc(),page_free()两个函数是否能够正确运行