最近做lazy lab做的有点头大,突然发现是自己遗漏了一些内容,即lec5,6,因为刚开始看的时候很多看不懂,就跑去看书了,后来就一直没有回来看lecture
所以为了提高效率,应该快速的把书看一遍,然后回来看lecture,再带着问题去看书看代码
code walk through
kvminit
void kvminit()
{
// 确定64bit的一级目录地址
kernel_pagetable = (pagetable_t) kalloc();
memset(kernel_pagetable, 0, PGSIZE);
// 一对一的IO映射 1 page
kvmmap(UART0, UART0, PGSIZE, PTE_R | PTE_W);
kvmmap(VIRTION(0), VIRTION(0), PGSIZE, PTE_R | PTE_W);
kvmmap(VIRTION(1), VIRTION(1), PGSIZE, PTE_R | PTE_W);
// CLINT 16 pages
kvmmap(CLINT, CLINT, 0x10000, PTE_R | PTE_W);
// PLIC 1024 pages
kvmmap(PLIC, PLIC, 0x400000, PTE_R | PTE_W);
// memlayout.h
// #define KERNBASE 0x80000000L
// #define PHYSTOP (KERNBASE + 128*1024*1024) 所以内核共映射了128M的内存
// #define TRAMPOLINE (MAXVA - PGSIZE)
// etext 在kernel.ld中,编译器根据代码量确定的对齐地址,ALGIN(0x1000),不懂意思
kvmmap(KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);
// 映射数据段和空闲内存
kvmmap((uint64)etext, (uint64)etext, PHYSTOP-(uint64)etext, PTE_R | PTE_W);
// 只有trampoline不是直接映射,实际的位置已经出现在代码段了,KERNBASE~etext。该段代码二次映射
kvmmap(TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);
}
kvmmap
直接调用了mappages(pagetable,va,size,pa,permission) , 而mappages是建立va~va+sz的pte:通过kalloc得到物理地址,然后设置pte
int
mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm)
{
uint64 a, last;
pte_t *pte;
a = PGROUNDDOWN(va);
last = PGROUNDDOWN(va + size - 1);
for(;;){
if((pte = walk(pagetable, a, 1)) == 0)
return -1;
if(*pte & PTE_V)
panic("remap");
*pte = PA2PTE(pa) | perm | PTE_V;
if(a == last)
break;
a += PGSIZE;
pa += PGSIZE;
}
return 0;
}
walk
其中walk通过虚拟地址,找到对应pte:
static pte_t * walk(pagetable_t pagetable, uint64 va, int alloc)
{
if(va >= MAXVA)
panic("walk");
for(int level = 2; level > 0; level--) {
pte_t *pte = &pagetable[PX(level, va)];
if(*pte & PTE_V) {
pagetable = (pagetable_t)PTE2PA(*pte);
} else {
// 分配新的内存给目录,并且初始化目录
if(!alloc || (pagetable = (pde_t*)kalloc()) == 0)
return 0;
memset(pagetable, 0, PGSIZE);
*pte = PA2PTE(pagetable) | PTE_V;
}
}
return &pagetable[PX(0, va)];
}
模拟下图:
依次取出l2,l2,l0,最终拿到作为leaf(指向的不是页表页)的pte
其中如果某个pte不存在,那么根据alloc是否为0,如果为0,也就是不直接分配一个新页表页,那么直接返回0(代表没有映射),如果为1,那么新建一个页表页(分配物理内存),并初始化它(memset为0),以及修改指向它的pte的内容
procinit
在main.c中调用,为所有的进程分配内核栈以及相应guard page
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;
}
其中的KSTACK是一个macro,对于第一个进程,得到的va是trampoline-2*pgsize,第二个是trampoline-4*pgsize,以此类推,如图
然后再依次将内核栈页建立pte(guard page不建立)
设置用户内存空间
allocproc
首先是找到一个state为unused的进程,然后设置pid,分配trapframe,设置页表,建立trampoline,trapframe的pte
fork
首先通过allocproc分配一个初始进程,然后调用uvmcopy(old pagetable,new pagetable,sz),从parent的用户内存复制到child
剩下的就是设置一些相关状态:
np->sz=p->sz
np->parent=p
另外,fork是怎么实现子进程和父进程返回值不同的呢,xv6的实现是父进程返回子进程的pid,设置子进程np->tf->a0 = 0,使其返回0
因为子进程和父进程有同样的fd,所以也要复制一份(要增加对应文件的ref count),设置子进程状态为runnable
uvmcopy
从0~sz,首先找到该地址对应的pte,然后判断是或valid,如果是,那么拿到对应的物理地址,分配物理内存,用memmove复制对应的物理内存,然后把新申请的物理内存建立pte
for(i = 0; i < sz; i += PGSIZE){
if((pte = walk(old, i, 0)) == 0)
// panic("uvmcopy: pte should exist");
continue;
if((*pte & PTE_V) == 0)
continue;
// panic("uvmcopy: page not present");
pa = PTE2PA(*pte);
flags = PTE_FLAGS(*pte);
if((mem = kalloc()) == 0)
goto err;
memmove(mem, (char*)pa, PGSIZE);
if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){
kfree(mem);
goto err;
}
}
exec
注意,exec一般是在fork之后运行的,并且是在子进程中运行的,所以myproc()实际上拿到的是子进程
fork之后,子进程的页表内容和父进程是相同的,现在要根据可执行文件的内容,建立一个新的页表,来代替老页表(和父进程相同的)
建立新页表,首先通过proc_pagetable获取一个只映射了trampoline和trapframe的页表,然后对于elf文件,一个个program header的加载进去
加载主要使用了两个函数:uvmmalloc(pagetable,oldsz,newsz),loadseg(pagetable,va,inode *,offset,sz)
uvmmalloc
为oldsz~newsz之间虚拟内存分配物理内存,并且建立pte,newsz不需要对齐(不过每次增长一个页,对不对齐好像也没区别?)
loadseg
static int loadseg(pagetable_t pagetable, uint64 va, struct inode *ip, uint offset, uint sz) {
// ..省略声明和 va 对齐检查
for(i = 0; i < sz; i += PGSIZE){
pa = walkaddr(pagetable, va + i);
if(sz - i < PGSIZE)
n = sz - i;
else
n = PGSIZE;
// 一批4096 bytes地读取,最后一批可能不足4096
if(readi(ip, 0, (uint64)pa, offset+i, n) != n)
return -1;
}
return 0;
}