From dc862a0c8e0a1fb8a70dc71c084887bf3f920a10 Mon Sep 17 00:00:00 2001 From: Zhiyuan Shao Date: Sat, 24 Oct 2020 13:56:24 +0800 Subject: [PATCH] add chapt5 and chapt6 --- README.md | 4 +- chapter4.md | 55 ++-- chapter5.md | 246 +++++++++++++++++ chapter6.md | 640 ++++++++++++++++++++++++++++++++++++++++++++ pictures/fig5_1.png | Bin 0 -> 3691 bytes pictures/fig5_2.png | Bin 0 -> 2883 bytes pictures/fig5_4.png | Bin 0 -> 17213 bytes 7 files changed, 908 insertions(+), 37 deletions(-) create mode 100644 chapter5.md create mode 100644 chapter6.md create mode 100644 pictures/fig5_1.png create mode 100644 pictures/fig5_2.png create mode 100644 pictures/fig5_4.png diff --git a/README.md b/README.md index 27db94f..df8573e 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ [第四章. (实验3)物理内存管理](chapter4.md) +[第五章. (实验4)缺页异常的处理](chapter5.md) - - +[第六章. (实验5)进程的封装](chapter6.md) diff --git a/chapter4.md b/chapter4.md index 87c23e5..0e2c5fe 100644 --- a/chapter4.md +++ b/chapter4.md @@ -126,50 +126,35 @@ Score: 20/20 其次,我们现在管理的可用内存是那一部分? 代理内核的本质也是一段程序,他本身是需要内存空间的,而这一段空间自然不能再被分配。除去内核本身占的空间,内核可支配的物理空间从0x80016000开始,大小在PKE设定为8M,2048个页面,故而供内核支配的的内存的范围为(first_free_page~first_free_paddr)。如下图所示。 ``` -KERNTOP------->+---------------------------------+ 0x80816000 - -(first_free_paddr) | | - -​ | Kern Physical Memory | - -​ | | 8M 2048pages - - (first_free_page) | | - - KERNBSE -----> +---------------------------------+ 0x80016000 - -​ | Kern Text/Data/BBS | - - KERN ------>+---------------------------------+ 0x80000000 + KERNTOP------->+---------------------------------+ 0x80816000 +(first_free_paddr)| | + | Kern Physical Memory | + | | 8M 2048pages + (first_free_page)| | + KERNBSE -----> +---------------------------------+ 0x80016000 + | Kern Text/Data/BBS | + KERN------>+---------------------------------+ 0x80000000 +``` 再往上便是我们可以支配的空间了(KERNTOP~Top Memory): +``` kernel/user - 4G ------------> +-------------------------------------------------+ - -​ | | - -​ | Empty Memory (*) | - -​ | | - - Top Memmory ---> +----------------------------------------------+ 随物理内存大小移动 - -​ | | - -​ | User Remapped Memory | - -​ | | - -​ | | - -first_free_paddr->+------------------------------------------------------+ 0x80816000 + | | + | Empty Memory (*) | + | | + Top Memmory ---> +-------------------------------------------------+ 随物理内存大小移动 + | | + | User Remapped Memory | + | | + | | +first_free_paddr->+-------------------------------------------------+ 0x80816000 ``` - + 最后,我们来看物理内存分配的单位:操作系统中,物理页是物理内存分配的基本单位。一个物理页的大小是4KB,我们使用结构体Page来表示,其结构如图: diff --git a/chapter5.md b/chapter5.md new file mode 100644 index 0000000..577dc73 --- /dev/null +++ b/chapter5.md @@ -0,0 +1,246 @@ +## 第五章.(实验4)缺页异常的处理 + +### 5.1 实验内容 + +实验要求:在APP里写递归程序,其执行过程导致栈的不断增长。在代理内核中实现缺页中断的处理例程(trap),使其能够支撑递归程序的正确执行。 + + + +**练习一:缺页中断实例的完善(需要编程)** + + **在**"pk/mmap.c"内有__handle_page_fault()函数,完善该函数,实现缺页中的处理。 + +``` +202 static int __handle_page_fault(uintptr_t vaddr, int prot) +203 { +204 printk("page fault vaddr:0x%lx\n", vaddr); +205 //your code here +206 //start------------> +207 pte_t* pte =0; +208 +209 //<-----------end +210 if (pte == 0 || *pte == 0 || !__valid_user_range(vaddr, 1)) +211 return -1; +212 else if (!(*pte & PTE_V)) +213 { +214 +215 //your code here +216 //start---------> +217 +218 uintptr_t ppn =0; +219 vmr_t* v = NULL; +220 +221 //<----------end +``` + +当你完成__handle_page_fault()函数后,可进行如下测试: + +编译app目录下,实验四相关文件: + +`$ riscv64-unknown-elf-gcc app/app4_1.c -o app/elf/app4_1` + +`$ riscv64-unknown-elf-gcc app/app4_2.c -o app/elf/app4_2` + +​ 使用spike运行,预期输出如下: + +``` +spike obj/pke app/elf/app4_1 + +PKE IS RUNNING +page fault vaddr:0x0000000000011000 +page fault vaddr:0x000000007f7ecc00 +page fault vaddr:0x00000000000100c0 +page fault vaddr:0x000000007f000000 +page fault vaddr:0x000000007f001000 +``` + + + +`$ spike obj/pke app/elf/app4_1` + +//递归程序可正常运行 + +如果你的两个测试app都可以正确输出的话,那么运行检查的python脚本: + +`$ ./pke-lab4` + +若得到如下输出,那么恭喜你,你已经成功完成了实验四!!! + +``` +build pk : OK +running app4_1 : OK + test4_1 : OK +running app4_2 : OK + test4_2 : OK +``` + +### 5.2 基础知识 + +**5.2.1 虚拟地址空间** + + 物理地址唯一,且有限的,但现在的操作系统上往往有不止一个的程序在运行。如果只有物理地址,那对于程序员来说无疑是相当繁琐的。程序不知道那些内存可用,那些内存已经被其他程序占有,这就意味着操作系统必须管理所有的物理地址,并且所有所有代码都是共用的。 + + 为了解决上述问题,操作系统引入了虚拟地址的概念。每个进程拥有着独立的虚拟地址空间,这个空间是连续的,一个虚拟页可以映射到不同或者相同的物理页。这就是我们所说的虚拟地址。在程序中,我们所使用的变量的地址均为虚拟地址。 + + + +**5.2.2 虚拟地址同物理地址之间的转换** + +​ 虚拟地址只是一个逻辑上的概念,在计算机中,最后正真被访问的地址仍是物理地址。所以,我们需要在一个虚拟地址访问内存之前将它翻译成物理地址,这个过程称为地址翻译。CPU上的内存管理单元(MMU)会利用存放在主存的页表完成这一过程。 + +​ RISCV的S模式下提供了基于页面的虚拟内存管理机制,内存被划分为固定大小的页。我们使用物理地址对这些页进行描述,我们在此回顾上一章所讲到的RISCV物理地址的定义: + +fig5_1 + +图5.1 RISCV64 物理地址 + +​ 可以看到,物理地址由PPN(物理页号)与Offset(偏移量)组成。这里的PPN就对应着上述的物理页。 + +​ 现在,我们来看RISCV虚拟地址的定义: + +fig5_2 + +图5.2 RISCV64 虚拟地址 + +​ 可以看到虚拟地址同样由页号和偏移量组成。而这二者之间是如何转换的呢?RV64支持多种分页方案,如Sv32、Sv39、Sv48,它们的原理相似,这里我们对pke中所使用的Sv39进行讲述。Sv39中维护着一个三级的页表,其页表项定义如下: + + fig1_7 + +图5.3 Sv39页表项 + +​ 当启动分页后,MMU会对每个虚拟地址进行页表查询,页表的地址由satp寄存器保存。在"pk/mmap.c"中的pk_vm_init函数中有如下一行代码其中,sptbr即为satp的曾用名,在这行代码中,我们将页表地址写入satp寄存器。 + +``` +458 write_csr(sptbr, ((uintptr_t)root_page_table >> RISCV_PGSHIFT) | SATP_MODE_CHOICE); +``` + + + + fig5_4 + + 图5.4 地址转换 + +​ 于是,当需要进行页表转换时,我们变从satp所存储的页表地址开始,逐级的转换。 + +在pke中,位于"pk/mmap.c"中的转换代码如下: + +``` +112 static size_t pt_idx(uintptr_t addr, int level) +113 { +114 size_t idx = addr >> (RISCV_PGLEVEL_BITS*level + RISCV_PGSHIFT); +115 return idx & ((1 << RISCV_PGLEVEL_BITS) - 1); +116 } +``` + + + +​ 首先,我们来看pt_idx函数,函数中将虚拟地址addr右移RISCV_PGLEVEL_BITS*level + RISCV_PGSHIFT位,其中RISCV_PGSHIFT对应着VPN中的Offset,而level则对应着各级VPN,pt_idx通过level取出指定的VPN。当level = 2, 得到vpn[2],即页目录项在一级页表的序号,,当level = 1, 得到vpn[1],即页目录项在二级页表的序号,同理,当level = 0, 则得到vpn[0],即页表项在三级页表的序号。 + +``` +125 static pte_t* __walk_internal(uintptr_t addr, int create) +126 { +127 pte_t* t = root_page_table; +128 for (int i = (VA_BITS - RISCV_PGSHIFT) / RISCV_PGLEVEL_BITS - 1; i > 0; i--) { +129 size_t idx = pt_idx(addr, i); +130 if (unlikely(!(t[idx] & PTE_V))) +131 return create ? __continue_walk_create(addr, &t[idx]) : 0; +132 t = (pte_t*)(pte_ppn(t[idx]) << RISCV_PGSHIFT); +133 } +134 return &t[pt_idx(addr, 0)]; +135 } +``` + +接着,我们进一步分析__walk_internal函数,首先VA_BITS即虚拟地址的位数为39,RISCV_PGSHIFT即代表虚拟地址中Offset的位数,二者相减,剩下的就是VPN0、VPN1……VPNX的位数,在除以VPN的位数,得到就是VPN的数量。由于pke中式Sv39,故而VPN的数量为3,即VPN0、VPN1、VPN2。 + +接着我们使用pt_idx函数得到各级VPN的值,依据图5.2所示逐级查询,一直找到该虚拟地址对应的页表项,而该页表项中存着该虚拟地址所对应的物理页号,再加上虚拟地址中的偏离量,我们就可以找到最终的物理地址了!! + + + +**5.2.3** **缺页异常处理** + +``` + 1 #include + 2 + 3 int main() + 4 { + 5 + 6 uintptr_t addr = 0x7f000000; + 7 *(int *)(addr)=1; + 8 + 9 uintptr_t addr1_same_page = 0x7f000010; + 10 uintptr_t addr2_same_page = 0x7f000fff; + 11 *(int *)(addr1_same_page)=2; + 12 *(int *)(addr2_same_page)=3; + 13 + 14 uintptr_t addr1_another_page = 0x7f001000; + 15 uintptr_t addr2_another_page = 0x7f001ff0; + 16 *(int *)(addr1_another_page)=4; + 17 *(int *)(addr2_another_page)=5; + 18 + 19 + 20 return 0; + 21 } +``` + +以上程序中,我们人为的访问虚拟地址0x7f000000与虚拟地址0x7f001000所对应的物理页,由于操作系统并没有事先加载这些页面,于是会出发缺页中断异常。进入pk/mmap.c文件下的__handle_page_fault函数中,其代码如下: + +``` +203 static int __handle_page_fault(uintptr_t vaddr, int prot) +204 { +205 printk("page fault vaddr:0x%lx\n", vaddr); +206 //your code here +207 //start------------> +208 pte_t* pte =0; +209 +210 //<-----------end +211 if (pte == 0 || *pte == 0 || !__valid_user_range(vaddr, 1)) +212 return -1; +213 else if (!(*pte & PTE_V)) +214 { +215 +216 //your code here +217 //start---------> +218 +219 uintptr_t ppn =0; +220 vmr_t* v = NULL; +221 +222 //<----------end +223 +224 if (v->file) +225 { +226 size_t flen = MIN(RISCV_PGSIZE, v->length - (vaddr - v->addr)); +227 // ssize_t ret = file_pread(v->file, (void*)vaddr, flen, vaddr - v->addr + v->offset); +228 ssize_t ret = file_pread_pnn(v->file, (void*)vaddr, flen, ppn, vaddr - v->addr + v->offset); +229 kassert(ret > 0); +230 if (ret < RISCV_PGSIZE) +231 memset((void*)vaddr + ret, 0, RISCV_PGSIZE - ret); +232 } +233 else +234 memset((void*)vaddr, 0, RISCV_PGSIZE); +235 __vmr_decref(v, 1); +236 *pte = pte_create(ppn, prot_to_type(v->prot, 1)); +237 } +238 +239 pte_t perms = pte_create(0, prot_to_type(prot, 1)); +240 if ((*pte & perms) != perms) +241 return -1; +242 +243 flush_tlb(); +244 return 0; +245 } +``` + + + +​ 对于一个没有对应物理地址的虚拟地址,我们需要进行如下的处理。首先,找到该物理地址所对应的pte,这里你可能会使用到__walk函数,__walk中调用了上文中我们讨论过的__walk_internal函数,对于一个给定的虚拟地址,返回其对于的pte,其定义如下; + +``` +138 pte_t* __walk(uintptr_t addr) +139 { +140 return __walk_internal(addr, 0); +141 } +``` + +其次,使用操作系统为该虚拟地址分配一个相对应的物理页,还记得物理内存管理中的内存分配嘛?现在它有用武之地了;最后将该物理地址写入第一步的得到的pte中,这里你会用到page2ppn和pte_create函数。 + +以上,就是本次实验需要大家完成的部分了! \ No newline at end of file diff --git a/chapter6.md b/chapter6.md new file mode 100644 index 0000000..ad63f57 --- /dev/null +++ b/chapter6.md @@ -0,0 +1,640 @@ +## 第六章.(实验5)进程的封装 + +### 6.1 实验内容 + +实验要求:在APP里写fork调用,其执行过程将fork出一个子进程。在代理内核中实现fork的处理例程(trap),使其能够支撑APP程序的正确执行。 + +在本次实验的app4.c文件中,将会测试fork()函数。代码中170及172系统调用分别对应着sys_fork()和sys_getpid()系统调用。调用fork函数后,将会有两个返回。在父进程中,fork返回新创建子进程的进程ID;而在子进程中,fork返回0。你需要阅读proc.c文件,完善相关代码,是的app4.c可以正常运行。 + + + +**6.1.1 练习一:alloc_proc(需要编程)** + +完善"pk/proc.c"中的alloc_proc(),你需要对以下属性进行初始化: + +l enum proc_state state; + +l int pid; + +l int runs; + +l uintptr_t kstack; + +l volatile bool need_resched; + +l struct proc_struct *parent; + +l struct mm_struct *mm; + +l struct context context; + +l struct trapframe *tf; + +l uintptr_t cr3; + +l uint32_t flags; + +l char name[PROC_NAME_LEN + 1]; + + + +**6.1.2 练习二:do_fork(需要编程)** + +完善"pk/proc.c"中的do_fork函数,你需要进行以下工作: + +l 调用alloc_proc()来为子进程创建进程控制块 + +l 调用setup_kstack来设置栈空间 + +l 用copy_mm来拷贝页表 + +l 调用copy_thread来拷贝进程 + +l 为子进程设置pid + +l 设置子进程状态为就绪 + +l 将子进程加入到链表中 + + + +完成以上代码后,你可以进行如下测试,然后输入如下命令: + +`$ riscv64-unknown-elf-gcc ../app/app5.c -o ../app/elf/app5` + +`$ spike ./obj/pke app/elf/app5` + +预期的输出如下: + +``` +PKE IS RUNNING +page fault vaddr:0x00000000000100c2 +page fault vaddr:0x000000000001e17f +page fault vaddr:0x0000000000018d5a +page fault vaddr:0x000000000001a8ba +page fault vaddr:0x000000000001d218 +page fault vaddr:0x000000007f7e8bf0 +page fault vaddr:0x0000000000014a68 +page fault vaddr:0x00000000000162ce +page fault vaddr:0x000000000001c6e0 +page fault vaddr:0x0000000000012572 +page fault vaddr:0x0000000000011fa6 +page fault vaddr:0x0000000000019064 +page fault vaddr:0x0000000000015304 +page fault vaddr:0x0000000000017fd4 +this is farther process;my pid = 1 +sys_exit pid=1 +page fault vaddr:0x0000000000010166 +page fault vaddr:0x000000000001e160 +page fault vaddr:0x000000000001d030 +page fault vaddr:0x0000000000014a68 +page fault vaddr:0x00000000000162ce +page fault vaddr:0x000000000001c6e0 +page fault vaddr:0x0000000000012572 +page fault vaddr:0x0000000000011fa6 +page fault vaddr:0x0000000000019064 +page fault vaddr:0x000000000001abb6 +page fault vaddr:0x0000000000015304 +page fault vaddr:0x0000000000017fd4 +page fault vaddr:0x0000000000018cd4 +this is child process;my pid = 2 +sys_exit pid=2 +``` + +如果你的app可以正确输出的话,那么运行检查的python脚本: + +./pke-lab5 + +若得到如下输出,那么恭喜你,你已经成功完成了实验六!!! + + + +build pk : OK + +running app5 : OK + + test fork : OK + +Score: 20/20 + +### 6.2 基础知识 + +**6.2.1 进程结构** + + 在pk/proc.h中,我们定义进程的结构如下: + + 42 struct proc_struct { + + 43 enum proc_state state; + + 44 int pid; + + 45 int runs; + + 46 uintptr_t kstack; + + 47 volatile bool need_resched; + + 48 struct proc_struct *parent; + + 50 struct context context; + + 51 trapframe_t *tf; + + 52 uintptr_t cr3; + + 53 uint32_t flags; + + 54 char name[PROC_NAME_LEN + 1]; + + 55 list_entry_t list_link; + + 56 list_entry_t hash_link; + + 57 }; + +​ 可以看到在41行的枚举中,我们定义了进程的四种状态,其定义如下: + + 11 enum proc_state { + + 12 PROC_UNINIT = 0, + + 13 PROC_SLEEPING, + + 14 PROC_RUNNABLE, + + 15 PROC_ZOMBIE, + + 16 }; + +​ 四种状态分别为未初始化(PROC_UNINIT)、睡眠(PROC_SLEEPING)、可运行(PROC_RUNNABLE)以及僵死(PROC_ZOMBIE)状态。 + +​ 除却状态,进程还有以下重要属性: + +l pid:进程id,是进程的标识符 + +l runs:进程已经运行的时间 + +l kstack:进程的内核栈空间 + +l need_resched:是否需要释放CPU + +l parent:进程的父进程 + +l context:进程的上下文 + +l tf:当前中断的栈帧 + +l cr3:进程的页表地址 + +l name:进程名 + +除了上述属性,可以看到在55、56行还维护了两个进程的链表,这是操作系统内进程的组织方式,系统维护一个进程链表,以组织要管理的进程。 + + + +**6.2.2 设置第一个内核进程idleproc** + + 在"pk/pk.c"的rest_of_boot_loader函数中调用了proc_init来设置第一个内核进程: + +317 void + +318 proc_init() { + +319 int i; + +320 extern uintptr_t kernel_stack_top; + +321 + +322 list_init(&proc_list); + +323 for (i = 0; i < HASH_LIST_SIZE; i ++) { + +324 list_init(hash_list + i); + +325 } + +326 + +327 if ((idleproc = alloc_proc()) == NULL) { + +328 panic("cannot alloc idleproc.\n"); + +329 } + +330 + +331 idleproc->pid = 0; + +332 idleproc->state = PROC_RUNNABLE; + +333 idleproc->kstack = kernel_stack_top; + +334 idleproc->need_resched = 1; + +335 set_proc_name(idleproc, "idle"); + +336 nr_process ++; + +337 + +338 currentproc = idleproc; + +339 + +340 } + +​ 322行的proc_list是系统所维护的进程链表,324行的hash_list是一个大小为1024的list_entry_t的hash数组。在对系统所维护的两个list都初始化完成后,系统为idleproc分配进程结构体。然后对idleproc的各个属性进行设置,最终将currentproc改为idleproc。 + +​ 在上述代码中,我们只是为idleproc分配了进程控制块,但并没有切换到idleproc,真正的切换代码在proc_init函数后面的run_loaded_program以及cpu_idle函数中进行。 + + + +**6.2.3 do_fork** + +​ 在run_loaded_program中有如下代码: + +140 trapframe_t tf; + +141 init_tf(&tf, current.entry, stack_top); + +142 __clear_cache(0, 0); + +143 do_fork(0,stack_top,&tf); + +144 write_csr(sscratch, kstack_top); + + + + 在这里,声明了一个trapframe,并且将它的gpr[2](sp)设置为内核栈指针,将它的epc设置为current.entry,其中current.entry是elf文件的入口地址也就是app的起始执行位置,随即,我们调用了do_frok函数,其中传入参数stack为0表示我们正在fork一个内核进程。 + +​ 在do_frok函数中,你会调用alloc_proc()来为子进程创建进程控制块、调用setup_kstack来设置栈空间,调用copy_mm来拷贝页表,调用copy_thread来拷贝进程。现在,我们来对以上函数进行分析。 + +​ setup_kstack函数代码如下,在函数中,我们为进程分配栈空间,然后返回: + +210 static int + +211 setup_kstack(struct proc_struct *proc) { + +212 proc->kstack = (uintptr_t)__page_alloc(); + +213 return 0; + +214 } + +copy_mm k函数代码如下,在函数中,我们对页表进行拷贝。 + +228 static int + +229 copy_mm(uint32_t clone_flags, struct proc_struct *proc) { + +230 //assert(currentproc->mm == NULL); + +231 /* do nothing in this project */ + +232 uintptr_t cr3=(uintptr_t)__page_alloc(); + +233 memcpy((void *)cr3,(void *)proc->cr3,RISCV_PGSIZE); + +234 proc->cr3=cr3; + +235 return 0; + +236 } + +​ 最后是copy_thread函数: + +240 static void + +241 copy_thread(struct proc_struct *proc, uintptr_t esp, trapframe_t *tf) { + +242 proc->tf = (trapframe_t *)(proc->kstack + KSTACKSIZE - sizeof(trapframe_t)); + +243 *(proc->tf) = *tf; + +244 + +245 proc->tf->gpr[10] = 0; + +246 proc->tf->gpr[2] = (esp == 0) ? (uintptr_t)proc->tf -4 : esp; + +247 + +248 proc->context.ra = (uintptr_t)forkret; + +249 proc->context.sp = (uintptr_t)(proc->tf); + +250 } + +​ 在函数中,首先对传入的栈帧进行拷贝,并且将上下文中的ra设置为地址forkret,将sp设置为该栈帧。 + +​ 完成以上几步后,我们为子进程设置pid,将其加入到进程链表当中,并且设置其状态为就绪。 + +​ + +**6.2.3 上下文切换** + +​ 每个进程都有着自己的上下文,在进程间切换时,需要对上下文一并切换。 + +​ 在pk/proc.c的cpu_idle中有以下代码: + +374 void + +375 cpu_idle(void) { + +376 while (1) { + +377 if (currentproc->need_resched) { + +378 schedule(); + +379 } + +380 } + +381 } + +​ 在当前进程处于need_resched状态时,会执行调度算法schedule,其代码如下: + + 16 void + + 17 schedule(void) { + + 18 list_entry_t *le, *last; + + 19 struct proc_struct *next = NULL; + + 20 { + + 21 currentproc->need_resched = 0; + + 22 last = (currentproc == idleproc) ? &proc_list : &(currentproc->list_link); + + 23 le = last; + + 24 do { + + 25 if ((le = list_next(le)) != &proc_list) { + + 26 next = le2proc(le, list_link); + + 27 if (next->state == PROC_RUNNABLE) { + + 28 break; + + 29 } + + 30 } + + 31 } while (le != last); + + 32 if (next == NULL || next->state != PROC_RUNNABLE) { + + 33 next = idleproc; + + 34 } + + 35 next->runs ++; + + 36 if (next != currentproc) { + + 37 proc_run(next); + + 38 } + + 39 } + + 40 } + + + +​ 在schedule函数中找到下一个需要执行的进程,并执行,执行代码proc_run如下: + +145 void + +146 proc_run(struct proc_struct *proc) { + +147 if (proc != currentproc) { + +148 bool intr_flag; + +149 struct proc_struct *prev = currentproc, *next = proc; + +150 currentproc = proc; + +151 write_csr(sptbr, ((uintptr_t)next->cr3 >> RISCV_PGSHIFT) | SATP_MODE_CHOICE); + +152 switch_to(&(prev->context), &(next->context)); + +153 + +154 } + +155 } + +​ 当传入的proc不为当前进程时,执行切换操作: + +7 switch_to: + + 8 # save from's registers + + 9 STORE ra, 0*REGBYTES(a0) + + 10 STORE sp, 1*REGBYTES(a0) + + 11 STORE s0, 2*REGBYTES(a0) + + 12 STORE s1, 3*REGBYTES(a0) + + 13 STORE s2, 4*REGBYTES(a0) + + 14 STORE s3, 5*REGBYTES(a0) + + 15 STORE s4, 6*REGBYTES(a0) + + 16 STORE s5, 7*REGBYTES(a0) + + 17 STORE s6, 8*REGBYTES(a0) + + 18 STORE s7, 9*REGBYTES(a0) + + 19 STORE s8, 10*REGBYTES(a0) + + 20 STORE s9, 11*REGBYTES(a0) + + 21 STORE s10, 12*REGBYTES(a0) + + 22 STORE s11, 13*REGBYTES(a0) + + 23 + + 24 # restore to's registers + + 25 LOAD ra, 0*REGBYTES(a1) + + 26 LOAD sp, 1*REGBYTES(a1) + + 27 LOAD s0, 2*REGBYTES(a1) + + 28 LOAD s1, 3*REGBYTES(a1) + + 29 LOAD s2, 4*REGBYTES(a1) + + 30 LOAD s3, 5*REGBYTES(a1) + + 31 LOAD s4, 6*REGBYTES(a1) + + 32 LOAD s5, 7*REGBYTES(a1) + + 33 LOAD s6, 8*REGBYTES(a1) + + 34 LOAD s7, 9*REGBYTES(a1) + + 35 LOAD s8, 10*REGBYTES(a1) + + 36 LOAD s9, 11*REGBYTES(a1) + + 37 LOAD s10, 12*REGBYTES(a1) + + 38 LOAD s11, 13*REGBYTES(a1) + + 39 + + 40 ret + +​ 可以看到,在switch_to中,我们正真执行了上一个进程的上下文保存,以及下一个进程的上下文加载。在switch_to的最后一行,我们执行ret指令,该指令是一条从子过程返回的伪指令,会将pc设置为x1(ra)寄存器的值,还记得我们在copy_thread中层将ra设置为forkret嘛?现在程序将从forkret继续执行: + +160 static void + +161 forkret(void) { + +162 extern elf_info current; + +163 load_elf(current.file_name,¤t); + +164 + +165 int pid=currentproc->pid; + +166 struct proc_struct * proc=find_proc(pid); + +167 write_csr(sscratch, proc->tf); + +168 set_csr(sstatus, SSTATUS_SUM | SSTATUS_FS); + +169 currentproc->tf->status = (read_csr(sstatus) &~ SSTATUS_SPP &~ SSTATUS_SIE) | SSTATUS_SPIE; + +170 forkrets(currentproc->tf); + +171 } + + + +​ 我们进入forkrets: + +121 forkrets: + +122 # set stack to this new process's trapframe + +123 move sp, a0 + +124 addi sp,sp,320 + +125 csrw sscratch,sp + +126 j start_user + + + + + + 76 .globl start_user + + 77 start_user: + + 78 LOAD t0, 32*REGBYTES(a0) + + 79 LOAD t1, 33*REGBYTES(a0) + + 80 csrw sstatus, t0 + + 81 csrw sepc, t1 + + 82 + + 83 # restore x registers + + 84 LOAD x1,1*REGBYTES(a0) + + 85 LOAD x2,2*REGBYTES(a0) + + 86 LOAD x3,3*REGBYTES(a0) + + 87 LOAD x4,4*REGBYTES(a0) + + 88 LOAD x5,5*REGBYTES(a0) + + 89 LOAD x6,6*REGBYTES(a0) + + 90 LOAD x7,7*REGBYTES(a0) + + 91 LOAD x8,8*REGBYTES(a0) + + 92 LOAD x9,9*REGBYTES(a0) + + 93 LOAD x11,11*REGBYTES(a0) + + 94 LOAD x12,12*REGBYTES(a0) + + 95 LOAD x13,13*REGBYTES(a0) + + 96 LOAD x14,14*REGBYTES(a0) + + 97 LOAD x15,15*REGBYTES(a0) + + 98 LOAD x16,16*REGBYTES(a0) + + 99 LOAD x17,17*REGBYTES(a0) + +100 LOAD x18,18*REGBYTES(a0) + +101 LOAD x19,19*REGBYTES(a0) + +102 LOAD x20,20*REGBYTES(a0) + +103 LOAD x21,21*REGBYTES(a0) + +104 LOAD x22,22*REGBYTES(a0) + +105 LOAD x23,23*REGBYTES(a0) + +106 LOAD x24,24*REGBYTES(a0) + +107 LOAD x25,25*REGBYTES(a0) + +108 LOAD x26,26*REGBYTES(a0) + +109 LOAD x27,27*REGBYTES(a0) + +110 LOAD x28,28*REGBYTES(a0) + +111 LOAD x29,29*REGBYTES(a0) + +112 LOAD x30,30*REGBYTES(a0) + +113 LOAD x31,31*REGBYTES(a0) + +114 # restore a0 last + +115 LOAD x10,10*REGBYTES(a0) + +116 + +117 # gtfo + +118 sret + +​ 可以看到在forkrets最后执行了sret,程序就此由内核切换至用户程序执行!! + +​ \ No newline at end of file diff --git a/pictures/fig5_1.png b/pictures/fig5_1.png new file mode 100644 index 0000000000000000000000000000000000000000..7bd272eb1c17cffcc91ab0b8505b0e606d0665b6 GIT binary patch literal 3691 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 b#p|~C#&$i%iYCxTzg1S|s7rJ+kNE!rXXO-l literal 0 HcmV?d00001 diff --git a/pictures/fig5_2.png b/pictures/fig5_2.png new file mode 100644 index 0000000000000000000000000000000000000000..40774887b616ab2a62fe84a298c1c635ad35908d GIT binary patch literal 2883 zcmb_e`9Bl>AD?@KGKnan#;`ex97B$kV{N$#laV4LSNSl62yM9{=E!|y%sC{vmLpA+ zg=!c@Q_7Lt5#RZKzkkB#hxhySem&ps$K(BaydJOD^YtWJqs>Kxq=f(gfQY39!WIDF z;pd!Hf!v%O6$xDD7_K|E=B9v}x5wr<4)0A9lnDS(pK@U58Xw0O46tyz0{|QW?=LP( z+f%;)0HIHo2orleb|Du|9jODw3p(|PycGfx{KRZZZ{2&OQmLqTLm+lQ6v%v88X_-V zci68PnzH8QM6Ft9yxx-xy_GbQGL^G8Fw?dr!1)3Y5VnpfE;ttz$!nbSuHfAGNe!Vr z?N0)qnfQ%GR#fn>-M`^*F+la!?%&oX#isctgu1$V)b7rf96-MF?Egj;|c@f8p0dm|0Rb=i$b`%D8R2|yXRXfSsr4HW${8Z3R#v3Qsp$!pJn z?(-nEWa|*+d?X;51Mv%xybRok=mStOYYrqV3T5DFpu8iF3WY*YF{xvY#mo1=`mj2L zaYj9i@i(~rZsB1OIkk`DENqr04@pc;jX@}%ja+GHm3{2;Y<}|4LY)`!FA3s6k4cBK zBS$Otjx6{l%RfVGu|?Cbx(20gViI(G?Y5ydS8$kS9_!MIE*vP_9@$AE_&4_Xh+j@h z2yQ_g6lp$?5fHkT6MuK8?IgbUs8Tw6mF?Xn{3XJjct>^`+>rZBFW?V0_=5KC9j(_S z)~wHhQg5&6hXVY57OgclDji8XGcq*lQ}&SCyPKkrBfAU=>HTN+0`I_w_0nwR!lO-ZEP!a#$vW zqBS@kU8l^POe;%$LhC-l_gX>u=^WXd2}JtqrZCPra@DTCGIKM#ocgR|HU?=tt!-ji(mWVZxeh}U_H)_B{1 zuu=TolSI!ntf_lsdB$s&Me-_3Bj)sndZv2%REtQ;e!wg#W{HeZ2=Y{f&ztOSFD?{a z;zWj-hxm7eW%~qFtWSGFs6O&kh>2C&m3L~`)1K!JPjFG@&1+yG_1G_~SN~f=kQii> zeSi96+3+<3MBp;(vv2%+mMQ+`px>s&HT^!Qdic(jfGnWoXM9DtM<&}FWb-ib!}x|v z@tRftVC7p#KH5a)fd0czPum|#?JR*jNHg6C`dB%3fGcTHX*noC$KURqCozqk+-i1{ znvj{uq_4K*udAR4KCkEPbO*gsyhBBiH;f1=9Yjx~+;8e+R0ueQ_=6JNTrbvm^hIi1 zrbuK#IcvzfYp!FJs&f)eLE5jUV?2Cyq7^5%)_wq+(}@O5AVYYZ8b+No(| z`866I6Ff^x&T+dgomqNRMX0=dtOWC-8FA-HUV*mm#ZE$wZq3SWwPa*~jIYqT52kj3=TJ`)EY;Ogc{NmkLMMEKUzFK(_uiB^KqkmvC$_P%CbvB{ zL!0-5*{-NDHFGc*y-u7~Z5heV(46pJY7U=>ay2z@uQ$1svRNPT-5O*mK!)Vzdvmgs z972qp@;Nl0H7MB1((05;3mBelDz2qwqqhwv=~QTI(Q#kCM^uE?ck+lq7nMT5AJ2JS z27aijr%l^36r`w3raaWMm084KdL&jV2-gNdac{Dvfh($3HHX+2LelZLgziZ}gKV&s zb~NFSu@W_*Hn9kK&7nQ)Hlz!H>qxWEKXG}2Om!~Y&q{s|=;(9%6rl@3+1U1j@4G-& zjYrXW*d=m<42JLGV2#;iz(6KJ8NM_u`0H90**M64dg#@Eavt<=S z(~-_H!{-e{(=S&k@H0VR5*&NG45IU*pl1b!URCpKTLKvt7BPDr`k->bSvrT?uX*Za zjWUOZK9alfULAY%Z4?DV%-}AYKWlWwzbr9GSwjx}eku34_yj+iEptZcmKDF`1}j~_ z>w0{x*K0%9`nZQurpU&Q+zO|F_QGWI!tw8ZAXgtvzQ*^hgz^Ri(K60xh&De>*R<_j zY+G%6MVhcunB(S)8$N!6!=z&%zPP{yuxhONF))9!@w|)4r5V=H+YR8Z;;tE2PFTcr z-ij(!a@UrY4seF?nJxx~@ijf#C1rBm?5~Iwx|!i9&|f2j`@xv~7p*+Q$}&5omSC!1 zRm<6Be^8Pw%N3TUueC@xRrJfV)ERQmG^zGUB5HG(0XwlHRx8A$ieGu13IS2VfZm*O&hO~b^QIs|w6I=qfFknt6*=mkB7gxQnEOm^< zDSGIm3>L%!m&kBt)A03+C86IUuCfcZ!%9$*ld}eUU-e^^p7b%uBhJAtZXo~i|Bb_H zu)@VR%ID2?QY!HGTK1r`aNK&o*iaIPCHb`T%yhDsUoDZmkzT9EP84*S@_6zH*6M(t zTgfF2f#5J3?(($Udc&W7dhns)coQq1DfI6`$8fki5NZ?S)-rRdneU-%1EWV(F&`Hg zxOB{$QwfPNT-$-J-d$($fAbH$@UDgO`gXj(g4E3hs(bBDwkbUDFi%)x%Ah3Bk3W@Q z!k!@j^Nol?^VnL4`*-TT-9H1M{)-|=!cjp(w+=DGg$%;D5$~z|i|UKL24nQ4$SDH> z>G-!MW!p(Xt%b_#tP6`VqvnuR4VRP_cdce?#kx+7rOlF~yWv=T7g#mgmuk J8dJBJ{{XB}P5S@< literal 0 HcmV?d00001 diff --git a/pictures/fig5_4.png b/pictures/fig5_4.png new file mode 100644 index 0000000000000000000000000000000000000000..02481a632073f7325af3acce4bf23fe25b8045e0 GIT binary patch literal 17213 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