Lab3
页表实验。
这里我觉得主要关注的就是页表的初始化以及页表如何工作,涉及的函数较多,也比较复杂。
初始化流程
下面是 kernel/main.c 的 main() 代码。
初始化的流程是:
kree() 将所有物理空间初始化为一页一页,并将底部地址加入 freelist ->
kvminit() 初始化kernel_pgtbl ->
kvminithart() 初始化 satp寄存器,在riscv中satp存储一级页表的位置 ->
procinit() 初始化每一个进程的内核栈
void
main()
{
if(cpuid() == 0){
consoleinit();
#if defined(LAB_PGTBL) || defined(LAB_LOCK)
statsinit();
#endif
printfinit();
printf("\n");
printf("xv6 kernel is booting\n");
printf("\n");
kinit(); // physical page allocator
kvminit(); // create kernel page table
kvminithart(); // turn on paging
procinit(); // process table
trapinit(); // trap vectors
trapinithart(); // install kernel trap vector
plicinit(); // set up interrupt controller
plicinithart(); // ask PLIC for device interrupts
binit(); // buffer cache
iinit(); // inode cache
fileinit(); // file table
virtio_disk_init(); // emulated hard disk
#ifdef LAB_NET
pci_init();
sockinit();
#endif
userinit(); // first user process
__sync_synchronize();
started = 1;
} else {
while(started == 0)
;
__sync_synchronize();
printf("hart %d starting\n", cpuid());
kvminithart(); // turn on paging
trapinithart(); // install kernel trap vector
plicinithart(); // ask PLIC for device interrupts
}
scheduler();
}
kvminit() 内核页表
kvminit() 首先通过kalloc() 为 内核页表分配一页的空间 并初始化为 *kernel_pagetable = 0.
kvmmap() 其实就是调用了一个 mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm).
mappages()的功能就是将 va虚拟地址 映射到 pa物理地址。需要注意的是,此时xv6还没开启地址翻译,因此这几个 kvmmap() 传入的参数映射都是直接映射,也就是说 va是多少,pa就是多少。
比较有趣的就是
KERNBASE:0x80000000
Trampoline: Va位于 MaxVa - PGSIZE
void
kvminit()
{
kernel_pagetable = (pagetable_t) kalloc();
memset(kernel_pagetable, 0, PGSIZE);
// uart registers
kvmmap(UART0, UART0, PGSIZE, PTE_R | PTE_W);
// virtio mmio disk interface
kvmmap(VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);
// CLINT
kvmmap(CLINT, CLINT, 0x10000, PTE_R | PTE_W);
// PLIC
kvmmap(PLIC, PLIC, 0x400000, PTE_R | PTE_W);
// map kernel text executable and read-only.
kvmmap(KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);
// map kernel data and the physical RAM we'll make use of.
kvmmap((uint64)etext, (uint64)etext, PHYSTOP-(uint64)etext, PTE_R | PTE_W);
// map the trampoline for trap entry/exit to
// the highest virtual address in the kernel.
kvmmap(TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);
}
void
kvmmap(uint64 va, uint64 pa, uint64 sz, int perm)
{
if(mappages(kernel_pagetable, va, sz, pa, perm) != 0)
panic("kvmmap");
}
procinit() 进程内核栈
值得注意的是 KSTACK()返回值每次间隔两页,因为有一页作为 kstack 的空间,有一页用在Guard Page(利用页错误防止溢出导致其他页被污染,该页不会映射到物理空间,因此不会造成浪费)
每一个用户进程都有一个对应的kernel stack,在xv6里是最多 64个进程,procinit() 为每一个进程都初始化好了 kstack 。
初始化完要重新将新的内核页表存入satp寄存器。
这里就有一个疑问,就是 kernel_pagetable 里面存着每个进程的 kstack 有什么用呢?
kstack是进程的内核栈
进程栈分为用户栈和进程栈
在 user mode 下运行时使用用户栈,在 superior mode 下运行时使用内核栈
那么既然设置了satp寄存器,就意味着地址翻译开始了,那每个进程的首级页表存在哪里呢?
在proc 结构体中:
struct proc { ... // these are private to the process, so p->lock need not be held. uint64 kstack; // Virtual address of kernel stack uint64 sz; // Size of process memory (bytes) pagetable_t pagetable; // User page table ... };
void
procinit(void)
{
struct proc *p;
initlock(&pid_lock, "nextpid");
for(p = proc; p < &proc[NPROC]; p++) {
initlock(&p->lock, "proc");
// Allocate a page for the process's kernel stack.
// Map it high in memory, followed by an invalid
// guard page.
char *pa = kalloc();
if(pa == 0)
panic("kalloc");
uint64 va = KSTACK((int) (p - proc));
kvmmap(va, (uint64)pa, PGSIZE, PTE_R | PTE_W);
p->kstack = va;
}
kvminithart();
}
用户进程创建
用户进程的页表是如何创建的?
static struct proc*
allocproc(void)
{
struct proc *p;
for(p = proc; p < &proc[NPROC]; p++) {
acquire(&p->lock);
if(p->state == UNUSED) {
// 找到一个 UNUSED的进程分配给它
goto found;
} else {
release(&p->lock);
}
}
return 0;
found:
p->pid = allocpid();
// Allocate a trapframe page.
if((p->trapframe = (struct trapframe *)kalloc()) == 0){
release(&p->lock);
return 0;
}
// An empty user page table.创建一个用户页表,赋值给 p->pagetable
p->pagetable = proc_pagetable(p);
if(p->pagetable == 0){
freeproc(p);
release(&p->lock);
return 0;
}
// Set up new context to start executing at forkret,
// which returns to user space.
memset(&p->context, 0, sizeof(p->context));
p->context.ra = (uint64)forkret;
// p->kstack 已经在 procinit() 里面初始化了
p->context.sp = p->kstack + PGSIZE;
return p;
}
通过调用 proc_pagetable() 函数,创建一个用户页表
uvmcreate() 给用户页表分配了物理空间
然后在va顶部映射 trampoline,在trampoline底下映射trapfame
pagetable_t
proc_pagetable(struct proc *p)
{
pagetable_t pagetable;
// An empty page table.
pagetable = uvmcreate();
if(pagetable == 0)
return 0;
// map the trampoline code (for system call return)
// at the highest user virtual address.
// only the supervisor uses it, on the way
// to/from user space, so not PTE_U.
if(mappages(pagetable, TRAMPOLINE, PGSIZE,
(uint64)trampoline, PTE_R | PTE_X) < 0){
uvmfree(pagetable, 0);
return 0;
}
// map the trapframe just below TRAMPOLINE, for trampoline.S.
if(mappages(pagetable, TRAPFRAME, PGSIZE,
(uint64)(p->trapframe), PTE_R | PTE_W) < 0){
uvmunmap(pagetable, TRAMPOLINE, 1, 0);
uvmfree(pagetable, 0);
return 0;
}
return pagetable;
}
Last updated