// kern/trap/trap.c
static inline void print_pgfault(struct trapframe *tf) {
cprintf("page falut at 0x%08x: %c/%c\n", tf->badvaddr,
trap_in_kernel(tf) ? 'K' : 'U',
tf->cause == CAUSE_STORE_PAGE_FAULT ? 'W' : 'R');
}
static int pgfault_handler(struct trapframe *tf) {
extern struct mm_struct *check_mm_struct;
print_pgfault(tf);
if (check_mm_struct != NULL) {
return do_pgfault(check_mm_struct, tf->cause, tf->badvaddr);
}
panic("unhandled page fault.\n");
}
void exception_handler(struct trapframe *tf) {
int ret;
switch (tf->cause) {
/* .... other cases */
case CAUSE_FETCH_PAGE_FAULT:// 取指令时发生的Page Fault先不处理
cprintf("Instruction page fault\n");
break;
case CAUSE_LOAD_PAGE_FAULT:
cprintf("Load page fault\n");
if ((ret = pgfault_handler(tf)) != 0) {
print_trapframe(tf);
panic("handle pgfault failed. %e\n", ret);
}
break;
case CAUSE_STORE_PAGE_FAULT:
cprintf("Store/AMO page fault\n");
if ((ret = pgfault_handler(tf)) != 0) { //do_pgfault()页面置换成功时返回0
print_trapframe(tf);
panic("handle pgfault failed. %e\n", ret);
}
break;
default:
print_trapframe(tf);
break;
}
}
之前我们物理页帧管理有个功能没有实现,那就是动态的内存分配。管理虚拟内存的数据结构(页表)需要有空间进行存储,而我们又没有给它预先分配内存(也无法预先分配,因为事先不确定我们的页表需要分配多少内存),就需要有malloc/free
的接口来分配释放内存。我们在这里顺便看看pmm.h
里对物理页面和虚拟地址,物理地址进行转换的一些函数。
// kern/mm/pmm.c
void *kmalloc(size_t n) { //分配至少n个连续的字节,这里实现得不精细,占用的只能是整数个页。
void *ptr = NULL;
struct Page *base = NULL;
assert(n > 0 && n < 1024 * 0124);
int num_pages = (n + PGSIZE - 1) / PGSIZE; //向上取整到整数个页
base = alloc_pages(num_pages);
assert(base != NULL); //如果分配失败就直接panic
ptr = page2kva(base); //分配的内存的起始位置(虚拟地址),
//page2kva, 就是page_to_kernel_virtual_address
return ptr;
}
void kfree(void *ptr, size_t n) { //从某个位置开始释放n个字节
assert(n > 0 && n < 1024 * 0124);
assert(ptr != NULL);
struct Page *base = NULL;
int num_pages = (n + PGSIZE - 1) / PGSIZE;
/*计算num_pages和kmalloc里一样,
但是如果程序员写错了呢?调用kfree的时候传入的n和调用kmalloc传入的n不一样?
就像你平时在windows/linux写C语言一样,会出各种奇奇怪怪的bug。
*/
base = kva2page(ptr);//kernel_virtual_address_to_page
free_pages(base, num_pages);
}
// kern/mm/pmm.h
/*
KADDR, PADDR进行的是物理地址和虚拟地址的互换
由于我们在ucore里实现的页表映射很简单,所有物理地址和虚拟地址的偏移值相同,
所以这两个宏本质上只是做了一步加法/减法,额外还做了一些合法性检查。
*/
/* *
* PADDR - takes a kernel virtual address (an address that points above
* KERNBASE),
* where the machine's maximum 256MB of physical memory is mapped and returns
* the
* corresponding physical address. It panics if you pass it a non-kernel
* virtual address.
* */
#define PADDR(kva) \
({ \
uintptr_t __m_kva = (uintptr_t)(kva); \
if (__m_kva < KERNBASE) { \
panic("PADDR called with invalid kva %08lx", __m_kva); \
} \
__m_kva - va_pa_offset; \
})
/* *
* KADDR - takes a physical address and returns the corresponding kernel virtual
* address. It panics if you pass an invalid physical address.
* */
#define KADDR(pa) \
({ \
uintptr_t __m_pa = (pa); \
size_t __m_ppn = PPN(__m_pa); \
if (__m_ppn >= npage) { \
panic("KADDR called with invalid pa %08lx", __m_pa); \
} \
(void *)(__m_pa + va_pa_offset); \
})
extern struct Page *pages;
extern size_t npage;
extern const size_t nbase;
extern uint_t va_pa_offset;
/*
我们曾经在内存里分配了一堆连续的Page结构体,来管理物理页面。可以把它们看作一个结构体数组。
pages指针是这个数组的起始地址,减一下,加上一个基准值nbase, 就可以得到正确的物理页号。
pages指针和nbase基准值我们都在其他地方做了正确的初始化
*/
static inline ppn_t page2ppn(struct Page *page) { return page - pages + nbase; }
/*
指向某个Page结构体的指针,对应一个物理页面,也对应一个起始的物理地址。
左移若干位就可以从物理页号得到页面的起始物理地址。
*/
static inline uintptr_t page2pa(struct Page *page) {
return page2ppn(page) << PGSHIFT;
}
/*
倒过来,从物理页面的地址得到所在的物理页面。实际上是得到管理这个物理页面的Page结构体。
*/
static inline struct Page *pa2page(uintptr_t pa) {
if (PPN(pa) >= npage) {
panic("pa2page called with invalid pa");
}
return &pages[PPN(pa) - nbase];//把pages指针当作数组使用
}
static inline void *page2kva(struct Page *page) { return KADDR(page2pa(page)); }
static inline struct Page *kva2page(void *kva) { return pa2page(PADDR(kva)); }
//从页表项得到对应的页,这里用到了 PTE_ADDR(pte)宏,对页表项做操作,在mmu.h里 定义
static inline struct Page *pte2page(pte_t pte) {
if (!(pte & PTE_V)) {
panic("pte2page called with invalid pte");
}
return pa2page(PTE_ADDR(pte));
}
//PDE(Page Directory Entry)指的是不在叶节点的页表项(指向低一级页表的页表项)
static inline struct Page *pde2page(pde_t pde) { //PDE_ADDR这个宏和PTE_ADDR是一样的
return pa2page(PDE_ADDR(pde));
}