6.s081 lab3
lab3
页表初始化过程:
物理页是一组由run结构体保存的,每个runmain函数调用kinit,初始化物理页。kinit调用了freerange。把内核对应的物理页全部释放掉,加入到freelist(由run组成的链表)中。(因为内核的虚拟地址和物理地址是直接映射的,所以就是end到PHYSTOP)freerange又调用了kfree(void *p)函数,这个函数的作用就是释放p这一页,从p开始,pgsize大小的空间全被置为1,然后把p转换为*run类型,用头插法把*p加入到freelist中,也是就*p的值是原先freelist的地址。这样从end到PHYSTOP对应的物理页就全被初始化,并被保存到freelist中去了。然后main又调用kvminit。kvminit首先通过kalloc创建内核的root page-table page 。然后调用kvmmap install the translations that the kernel needs(不知道这句话咋翻译)。kvmmap实际是调用mappages实现这个功能。
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;//printf("%p %p \n",pa,PA2PTE(pa));if(a == last)break;a += PGSIZE;pa += PGSIZE;}return 0;
}
是创建一个页表项,映射虚拟地址va开始的size个字节到物理地址pa。walk返回的是最后一级页表的页表项地址。然后将walk返回的这项改为物理地址pa。
然后main调用kvminithart,让系统能够映射。把kernel pagetable存入寄存器。
然后main调用procinit,为每个进程分配kernel stack。
一些点
在内核部分,虚拟地址和物理地址是直接映射的,虚拟地址和物理地址的区别就是多了10个标志位。
Print a page table
这个实验比较简单,参照freewalk的代码就好了。
void
vmp(pagetable_t pagetable, int depth){if(depth>2) return; for(int i=0; i<512;i++){pte_t pte = pagetable[i];if((pte & PTE_V)){for(int j=0;j<depth;j++){printf(".. ");} uint64 child = PTE2PA(pte);printf("..%d: pte %p pa %p\n",i,pte,child); vmp((pagetable_t)child,depth+1);}}
}
void
vmprint(pagetable_t pagetable){printf("page table %p\n",pagetable);vmp(pagetable,0);
}
A kernel page table per process
这个实验和下个实验要做的是使内核能直接解析用户指针。利用的是内核在虚拟地址较高的部分开始,而进程从0开始。这个实验要做的就是为每个进程创建一个kernel pagetable,然后在运行这个进程的时候,把系统的kernel pagetable切换为进程的kernel pagetable。这个实验做的时候挺难的,现在写起复盘好像还好。
struct proc {struct spinlock lock;// p->lock must be held when using these:enum procstate state; // Process statestruct proc *parent; // Parent processvoid *chan; // If non-zero, sleeping on chanint killed; // If non-zero, have been killedint xstate; // Exit status to be returned to parent's waitint pid; // Process ID// these are private to the process, so p->lock need not be held.uint64 kstack; // Virtual address of kernel stackuint64 sz; // Size of process memory (bytes)pagetable_t pagetable; // User page tablepagetable_t kernel_pagetable; //user kernel pagetablestruct trapframe *trapframe; // data page for trampoline.Sstruct context context; // swtch() here to run processstruct file *ofile[NOFILE]; // Open filesstruct inode *cwd; // Current directorychar name[16]; // Process name (debugging)
};
根据提示,首先要做的是往proc 结构体加个 kernel pagetable。
pagetable_t
proc_kernel_pagetable(){pagetable_t kpagetable = (pagetable_t)kalloc();memset(kpagetable, 0 ,PGSIZE);mappages(kpagetable, UART0, PGSIZE, UART0,PTE_R |PTE_W); mappages(kpagetable,VIRTIO0, PGSIZE, VIRTIO0, PTE_R | PTE_W);//mappages(kpagetable, CLINT, 0x10000, CLINT, PTE_R | PTE_W);mappages(kpagetable, PLIC, 0x400000, PLIC, PTE_R | PTE_W);mappages(kpagetable, KERNBASE, (uint64)etext-KERNBASE, KERNBASE, PTE_R | PTE_X );mappages(kpagetable,(uint64)etext, PHYSTOP-(uint64)etext, (uint64)etext, PTE_R | PTE_W);mappages(kpagetable,TRAMPOLINE, PGSIZE, (uint64)trampoline, PTE_R | PTE_X);return kpagetable;
}
然后参照kvminit,写一个初始化进程的kernel pagetable的函数。 注释掉的那行是因为在第三个实验不需要映射。要注意的是注意mappages里面的标志位参数,有两个是PTE_X,全是读写的话,会卡在一个地方,进不了shell。
static struct proc*
allocproc(void)
{struct proc *p;for(p = proc; p < &proc[NPROC]; p++) {acquire(&p->lock);if(p->state == UNUSED) {goto found;} else {release(&p->lock);}}return 0;found:p->pid = allocpid();//lab 3 初始化p->kernel_pagetable=proc_kernel_pagetable();//kstackchar *pa = kalloc();if(pa==0)panic("stack kalloc");uint64 va = (TRAMPOLINE - 2*PGSIZE);mappages(p->kernel_pagetable, va, PGSIZE,(uint64)pa, PTE_R | PTE_W);p->kstack = va; // Allocate a trapframe page.if((p->trapframe = (struct trapframe *)kalloc()) == 0){release(&p->lock);return 0;}// An empty user page table.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->context.sp = p->kstack + PGSIZE;return p;
}
接下来就是改allocproc函数,主要有两个:1.初始化进程的kernel pagetable2.修改kernel stack。因为每个进程的kernel stack是映射在kernel pagetable的,所以这里需要把procinit的部分功能移到这来。
void
scheduler(void)
{struct proc *p;struct cpu *c = mycpu();c->proc = 0;for(;;){// Avoid deadlock by ensuring that devices can interrupt.intr_on();int found = 0;for(p = proc; p < &proc[NPROC]; p++) {acquire(&p->lock);if(p->state == RUNNABLE) {// Switch to chosen process. It is the process's job// to release its lock and then reacquire it// before jumping back to us.p->state = RUNNING;c->proc = p;w_satp(MAKE_SATP(p->kernel_pagetable));sfence_vma();swtch(&c->context, &p->context);kvminithart();// Process is done running for now.// It should have changed its p->state before coming back.c->proc = 0;found = 1;}release(&p->lock);}
#if !defined (LAB_FS)if(found == 0) {intr_on();asm volatile("wfi");}
#else;
#endif}
}
修改scheduler,在进程调度的时候切换kernel pagetable。
void
free_kernel_pagetable(pagetable_t pagetable, uint64 stack,uint64 sz){uvmunmap(pagetable, UART0, 1, 0); uvmunmap(pagetable, VIRTIO0, 1, 0); //uvmunmap(pagetable, CLINT, 0x10000/PGSIZE, 0); uvmunmap(pagetable, PLIC, 0x400000/PGSIZE, 0); uvmunmap(pagetable, KERNBASE, ((uint64)etext-KERNBASE)/PGSIZE, 0); uvmunmap(pagetable, TRAMPOLINE, 1, 0); uvmunmap(pagetable, (uint64)etext,(PHYSTOP-(uint64)etext)/PGSIZE , 0); uvmunmap(pagetable, 0, PGROUNDUP(sz)/PGSIZE, 0);uvmunmap(pagetable, stack, 1, 1);freewalk(pagetable);
}
static void
freeproc(struct proc *p)
{if(p->trapframe)kfree((void*)p->trapframe);p->trapframe = 0;if(p->pagetable)proc_freepagetable(p->pagetable, p->sz);if(p->kernel_pagetable)free_kernel_pagetable(p->kernel_pagetable,p->kstack,p->sz);p->pagetable = 0;p->sz = 0;p->pid = 0;p->parent = 0;p->name[0] = 0;p->chan = 0;p->killed = 0;p->xstate = 0;p->state = UNUSED;
}// Create a user page table for a given process,
// with no user memory, but with trampoline pages.
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;
}
然后就是释放进程的时候也要free 进程的kernel pagetable。因为这里贴的代码是做完了lab3的代码,所以有些内容和下个实验有关系。
Simplify copyin/copyinstr
这个部分就是在之前实验的基础上,把进程的pagetable映射添加到kernel pagetable上,要注意PTE_U只能在用户态使用。
void
userinit(void)
{struct proc *p;pte_t *pte,*kernelpte;p = allocproc();initproc = p;// allocate one user page and copy init's instructions// and data into it.uvminit(p->pagetable, initcode, sizeof(initcode));p->sz = PGSIZE;//lab3pte= walk(p->pagetable,0,0);kernelpte = walk(p->kernel_pagetable,0,1);*kernelpte = (*pte) & (~PTE_U);// prepare for the very first "return" from kernel to user.p->trapframe->epc = 0; // user program counterp->trapframe->sp = PGSIZE; // user stack pointersafestrcpy(p->name, "initcode", sizeof(p->name));p->cwd = namei("/");p->state = RUNNABLE;release(&p->lock);
}
首先修改userinit。
int
fork(void)
{int i, pid;struct proc *np;struct proc *p = myproc();pte_t *pte,*kernelpte;// Allocate process.if((np = allocproc()) == 0){return -1;}// Copy user memory from parent to child.if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){freeproc(np);release(&np->lock);return -1;}np->sz = p->sz;np->parent = p;//lab3 paet3for(int j=0;j< p->sz; j+=PGSIZE){kernelpte = walk(np->kernel_pagetable,j,1);pte = walk(np->pagetable,j,0);*kernelpte = (*pte) & (~PTE_U);}// copy saved user registers.*(np->trapframe) = *(p->trapframe);// Cause fork to return 0 in the child.np->trapframe->a0 = 0;// increment reference counts on open file descriptors.for(i = 0; i < NOFILE; i++)if(p->ofile[i])np->ofile[i] = filedup(p->ofile[i]);np->cwd = idup(p->cwd);safestrcpy(np->name, p->name, sizeof(p->name));pid = np->pid;np->state = RUNNABLE;release(&np->lock);return pid;
}
int
exec(char *path, char **argv)
{char *s, *last;int i, off;uint64 argc, sz = 0, sp, ustack[MAXARG+1], stackbase;struct elfhdr elf;struct inode *ip;struct proghdr ph;pagetable_t pagetable = 0, oldpagetable;struct proc *p = myproc();begin_op();if((ip = namei(path)) == 0){end_op();return -1;}ilock(ip);// Check ELF headerif(readi(ip, 0, (uint64)&elf, 0, sizeof(elf)) != sizeof(elf))goto bad;if(elf.magic != ELF_MAGIC)goto bad;if((pagetable = proc_pagetable(p)) == 0)goto bad;// Load program into memory.for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=sizeof(ph)){if(readi(ip, 0, (uint64)&ph, off, sizeof(ph)) != sizeof(ph))goto bad;if(ph.type != ELF_PROG_LOAD)continue;if(ph.memsz < ph.filesz)goto bad;if(ph.vaddr + ph.memsz < ph.vaddr)goto bad;uint64 sz1;if((sz1 = uvmalloc(pagetable, sz, ph.vaddr + ph.memsz)) == 0)goto bad;if(sz1>=PLIC)goto bad;sz = sz1;if(ph.vaddr % PGSIZE != 0)goto bad;if(loadseg(pagetable, ph.vaddr, ip, ph.off, ph.filesz) < 0)goto bad;}iunlockput(ip);end_op();ip = 0;p = myproc();uint64 oldsz = p->sz;// Allocate two pages at the next page boundary.// Use the second as the user stack.sz = PGROUNDUP(sz);uint64 sz1;if((sz1 = uvmalloc(pagetable, sz, sz + 2*PGSIZE)) == 0)goto bad;sz = sz1;uvmclear(pagetable, sz-2*PGSIZE);sp = sz;stackbase = sp - PGSIZE;//lab3 part3// Push argument strings, prepare rest of stack in ustack.for(argc = 0; argv[argc]; argc++) {if(argc >= MAXARG)goto bad;sp -= strlen(argv[argc]) + 1;sp -= sp % 16; // riscv sp must be 16-byte alignedif(sp < stackbase)goto bad;if(copyout(pagetable, sp, argv[argc], strlen(argv[argc]) + 1) < 0)goto bad;ustack[argc] = sp;}ustack[argc] = 0;// push the array of argv[] pointers.sp -= (argc+1) * sizeof(uint64);sp -= sp % 16;if(sp < stackbase)goto bad;if(copyout(pagetable, sp, (char *)ustack, (argc+1)*sizeof(uint64)) < 0)goto bad;pte_t *pte, *kernel_pte;uvmunmap(p->kernel_pagetable,0,PGROUNDUP(oldsz)/PGSIZE,0);for(int j=0;j<sz;j+=PGSIZE){pte = walk(pagetable, j,0);kernel_pte = walk(p->kernel_pagetable, j, 1);*kernel_pte = ((*pte) & (~PTE_U));}// arguments to user main(argc, argv)// argc is returned via the system call return// value, which goes in a0.p->trapframe->a1 = sp;// Save program name for debugging.for(last=s=path; *s; s++)if(*s == '/')last = s+1;safestrcpy(p->name, last, sizeof(p->name));// Commit to the user image.oldpagetable = p->pagetable;p->pagetable = pagetable;p->sz = sz;p->trapframe->epc = elf.entry; // initial program counter = mainp->trapframe->sp = sp; // initial stack pointerproc_freepagetable(oldpagetable, oldsz);if(p->pid==1)vmprint(p->pagetable); return argc; // this ends up in a0, the first argument to main(argc, argv)bad:if(pagetable)proc_freepagetable(pagetable, sz);if(ip){iunlockput(ip);end_op();}return -1;
}
然后是fork和exec,这两个函数要做的操作本质是一样的。
uint64
sys_sbrk(void)
{int addr;int n;pte_t *pte,*kernel_pte;struct proc *p = myproc();if(argint(0, &n) < 0)return -1;addr = p->sz;if(growproc(n) < 0)return -1;if(n>0){for(int j=addr;j<addr+n;j+=PGSIZE){pte=walk(p->pagetable,j,0);kernel_pte = walk(p->kernel_pagetable,j,1);*kernel_pte = (*pte) & ~PTE_U; }}else{//for(int j=addr-PGSIZE;j>=addr+n;j-=PGSIZE) // uvmunmap(p->kernel_pagetable,j,1,0 );uvmunmap(p->kernel_pagetable,PGROUNDUP(addr+n),-n/PGSIZE,0);}return addr;
}
然后修改 sbrk,n是正数增加映射,负数减少映射。
int
copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len)
{/*uint64 n, va0, pa0;while(len > 0){va0 = PGROUNDDOWN(srcva);pa0 = walkaddr(pagetable, va0);if(pa0 == 0)return -1;n = PGSIZE - (srcva - va0);if(n > len)n = len;memmove(dst, (void *)(pa0 + (srcva - va0)), n);copyin_new(pagetable, dst, srcva, n);len -= n;dst += n;srcva = va0 + PGSIZE;}return 0;*/return copyin_new(pagetable, dst, srcva, len);
}
最后就是修改copyin()和copyinstr()。这里我没太搞懂的是为什么可以直接return copyin_new(pagetable, dst, srcva, len); 不需要每一个PGSIZE调用一次这个函数,像注释掉的memmove(dst, (void *)(pa0 + (srcva - va0)), n);一样。 是因为之前因为内核不能直接识别用户指针,所以每一个PGSIZE就需要翻译一遍吗?
6.s081 lab3相关推荐
- xv6 6.S081 Lab3: alloc
xv6 6.S081 Lab3: alloc 写在前面 实验介绍 开始! 任务再描述 任务一实现 任务二实现 Buddy Allocator Code Thru 任务二的实现 alloc代码在这里.另 ...
- 6.S081 Lab3 page tables
6.S081 Lab3 page tables 未完成 文章目录 6.S081 Lab3 page tables 未完成 1. Print a page table ([easy](https://p ...
- MIT6.S081 Lab3 Page tables
lab1.2不是太难,lab 3太变态了,github上记一下代码,源代码地址 :https://github.com/CodePpoi/mit-lab 参考博客 : https://blog.csd ...
- MIT6.S081 Lab3: page tables
Print a page table 接收一个pagetable_t并把它指向的页表打印. 在kernel/def.h中增加函数声明void vmprint(void)并在kernel/vm.c中定义 ...
- 6.S081 Lab3 page tables 页表
Speed up system calls 加速系统调用 有些操作系统会通过在用户空间和内核空间之间共享一些内存来加速系统调用.该实验题目要求在内核中保存一个映射到用户内存空间的结构体,该结构体中保存 ...
- 吴军三部曲态度(四)人际关系
交友不要怕吃亏 如果你现在在大学中,那么很好,大学或者是更早交的朋友,是很单纯的,不会像社会上那么的功利,因为到了社会上,很多东西都跟利益挂钩了,这样目的就不会那么的单纯了. 俗话说,穷在闹市无人问, ...
- XV6 lab3:Trap
Lab3:Traps 参考文章: 6.S081 & 操作系统内核 操作系统MIT6.S081:Lab4->Trap Xv6操作系统的系统调用通过 trampoline 来实现用户空间和内 ...
- OSPF分解试验部分-LAB3:OSPF各种网络类型试验
LAB3:OSPF各种网络类型试验 1.NBMA试验 试验需求: Frame-relay使用物理接口,OSPF建立后,默认应该是NBMA网络类型.我们验证NBMA中我们应该如何配置OSPF. 配置 ...
- CS144 计算机网络实验 lab3 笔记
CS144 计算机网络实验 lab3 笔记 介绍 本实验中,我们将会在之前实验的基础上,实现一个TCP sender ----将字节流转换成数据报并发送. TCP协议是一个在不可靠的协议上提供可靠的, ...
最新文章
- 图表复现|PRD地下水微生物群落的多样性分析文献
- Salesforce - soql 多字段多值过滤查询思路
- 让软件版本信息自动引用SVN修订版本号
- sublime text3 按F5运行python代码
- 项目中最常用到的颜色
- get_metrology_object_result 获取计量模型的测量结果
- canal —— 阿里巴巴mysql数据库binlog的增量订阅消费组件
- 【OpenCV 例程200篇】99. 修正阿尔法均值滤波器
- java spring源码_spring源码分析-spring中的bean
- 动易Ajax登陆调用
- 夕夕博士的视频观后笔记集合~
- 三阶魔方7步还原法-一共只需记6个公式
- darknet源码理解(二)---图片的读取
- 区别samtools faid产生的.fai文件功能和bwa index 产生的四个文件的功能
- 【线性代数】线性组合,线性相关与生成子空间(linear combination, linear dependency span)
- 图解|网络究竟是如何运作的?
- 这个电脑用显微镜才能看清:却能让假货无处遁形
- 将台式机组成云服务器_四种旧PC台式电脑改造桌面云虚拟化的方案介绍
- RK 7.1 OTA升级提示Not enough free space on /cache to apply patches
- 02 项目立项管理:项目建议书、可行性研究、项目评估与论证、项目招标与投标、立项签订合同