目录
前言
LAB2主要实现的是JOS内存管理的功能,内存管理包括两个部分:(1)内核的物理内存分配器,以便内核可以分配物理内存以及释放,分配器的大小为1页,4K。主要功能:记录哪些页面空闲、哪些已经分配、多少个进程在共享分配的页面、以及分配与释放内存页。(2)虚拟内存:将内核与软件使用的虚拟地址映射到物理内存中的地址。X86的内存管理单元mmu在指令使用内存时执行映射。
LAB2包括一些新文件:
- inc/memlayout.h
- kern/pmap.c
- kern/pmap.h
- kern/kclock.h
- kern/kclock.c
memlayout.h 描述了必须通过修改 pmap.c 来实现的虚拟地址空间的布局。 memlayout.h 和 pmap.h 定义了 PageInfo 结构,您将使用它来跟踪哪些物理内存页是空闲的。 kclock.c 和 kclock.h 操作 PC 的电池供电时钟和 CMOS RAM 硬件,其中 BIOS 记录 PC 包含的物理内存量等。 pmap.c 中的代码需要读取这个设备硬件,以便计算出有多少物理内存,但不需要填补 CMOS 硬件如何工作的细节。 注意 memlayout.h 和 pmap.h,因为LAB2使用到包含的许多定义。查看 inc/mmu.h,因为它也包含许多有用的定义。
一、XV6内存管理
1. PageTables(页表)是操作系统控制内存地址含义的机制。它们允许xv6将不同进程的地址空间多路复用到单个物理内存中,并保护不同进程的内存。页表提供的间接级别允许使用许多巧妙的技巧。Xv6主要使用页表来多路复用地址空间和保护内存。它还使用一些简单的页表技巧:映射相同的内存(内核)在几个地址空间,映射相同的内存不止一次在一个地址空间(每个用户页面也映射到内存的内核的物理视图),并与一个未映射的页面保护用户堆栈。
2. 分页的硬件
x86指令(用户指令和内核指令)操作虚拟地址。但是机器的RAM或物理内存是用物理地址建立索引的。因此x86页表硬件通过将每个虚拟地址映射到一个物理地址来连接这两种地址。
一个x86页表是由2^20(1,048,576)页表项(PTEs)组成的数组。每个PTE包含一个20位的物理页码(PPN)和一些标志。分页硬件将虚拟地址通过其前20位索引页表找到PTE,并更换地址的前20位的PPN。分页硬件复制低12位不变从虚拟到物理地址翻译。因此,页表让操作系统4096(2^12)字节的对齐块粒度控制虚拟到物理的地址转换。这样的块称为页面。
在实际转换过程中,页表作为两级树存储在物理内存中。树根是一个4096字节的页目录(pgdir),包含1024个类似页表项的页表页的引用。每个页表是由1024个页表项组成的数组。 分页硬件从虚拟地址的前10位选择页目录项,如果页目录项存在,则从中间10位选择来自页目录项指向的页表页中选择PTE,若PDE或者PTE不存在,则引发故障。(两级结构:页目录表-->页目录项--->页表页-->页表项)
每个PTE包含标志位,告诉分页硬件如何允许使用关联的虚拟地址。
3. 进程地址空间
每个进程都有一个单独的页表,xv6告诉页表硬件在xv6在进程之间切换时切换页表。
包括Kernel每个进程的页表运行所需要的所有映射,所有映射都出现在KERNBASE之上。
二、一些JOS中的定义
1. PageInfo
struct PageInfo {
// 下一个空闲页面
struct PageInfo *pp_link;
// 到这个页面,用于使用 page_alloc 分配的页面。
// 在启动时使用 pmap.c 分配的页面
// boot_alloc 没有有效的引用计数字段。
uint16_t pp_ref;//虚拟地址到物理地址多对一,因此pp_ref表示引用计数
};
2. 线性地址
3. 地址转换
// 地址的页码字段
#define PGNUM(la) (((uintptr_t) (la)) >> PTXSHIFT)
// 页目录索引
#define PDX(la) ((((uintptr_t) (la)) >> PDXSHIFT) & 0x3FF)
// 页表索引
#define PTX(la) ((((uintptr_t) (la)) >> PTXSHIFT) & 0x3FF)
// 页面中的偏移
#define PGOFF(la) (((uintptr_t) (la)) & 0xFFF)
//在页表或页目录项中的地址 *pte/*pde
#define PTE_ADDR(pte) ((physaddr_t) (pte) & ~0xFFF)
// Page table/directory entry flags.
//pde/pte标志
#define PTE_P 0x001 // Present,没有使用的pte
#define PTE_W 0x002 // Writeable可写
#define PTE_U 0x004 // User用户可用
#define PTE_PWT 0x008 // Write-Through
#define PTE_PCD 0x010 // Cache-Disable
#define PTE_A 0x020 // Accessed
#define PTE_D 0x040 // Dirty
#define PTE_PS 0x080 // Page Size
#define PTE_G 0x100 // Global
//把KERNELBASE之上的虚拟地址转化为物理地址
#define PADDR(kva) _paddr(__FILE__, __LINE__, kva)
static inline physaddr_t
_paddr(const char *file, int line, void *kva)
{
if ((uint32_t)kva < KERNBASE)
_panic(file, line, "PADDR called with invalid kva %08lx", kva);
return (physaddr_t)kva - KERNBASE;
}
//将物理地址转化为对应内核的虚拟地址
#define KADDR(pa) _kaddr(__FILE__, __LINE__, pa)
static inline void*
_kaddr(const char *file, int line, physaddr_t pa)
{
if (PGNUM(pa) >= npages)
_panic(file, line, "KADDR called with invalid pa %08lx", pa);
return (void *)(pa + KERNBASE);
}
//将物理页面转为对应的物理地址
static inline physaddr_t
page2pa(struct PageInfo *pp)//page to physic adress
{
return (pp - pages) << PGSHIFT;
}
//将物理地址转化为物理页面
static inline struct PageInfo*
pa2page(physaddr_t pa)//physic adress to page
{
if (PGNUM(pa) >= npages)
panic("pa2page called with invalid pa");
return &pages[PGNUM(pa)];
}
//物理页面转为虚拟地址
static inline void*
page2kva(struct PageInfo *pp)//page tp kenerl virtual adress
{
return KADDR(page2pa(pp));
}
4. 与内存管理相关的函数及其功能
void mem_init(void);//内存管理初始化
void page_init(void);//初始化物理页面内存结构与内存空闲链表free_page_list
struct PageInfo *page_alloc(int alloc_flags);//分配一个物理页面但不增加引用计数
void page_free(struct PageInfo *pp);//释放一个空闲物理页并将其添加到空闲链表中
int page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm);//将物理页面pp映射到虚拟地址va处
void page_remove(pde_t *pgdir, void *va);//取消虚拟地址va的映射
struct PageInfo *page_lookup(pde_t *pgdir, void *va, pte_t **pte_store);//查找映射虚拟地址va的物理页
void page_decref(struct PageInfo *pp);//减少物理页pp上的引用计数
void tlb_invalidate(pde_t *pgdir, void *va);//使TLB条目无效,前提是页表被其当前使用的进程编辑
pte_t *pgdir_walk(pde_t *pgdir, const void *va, int create);//从给出的页目录返回一个映射va的页表项,没有找到则建立一个新的页表项
总结
简单对LAB2需要的前置知识做了一些梳理,且虚拟内存映射用了XV6的版本来理解,JOS的虚拟内存映射图可以在memlayout.h的注释中找到。