Update chapter6.md

pull/1/head
Zhiyuan Shao 4 years ago
parent dc862a0c8e
commit 2a9860ba49

@ -103,19 +103,18 @@ sys_exit pid=2
如果你的app可以正确输出的话那么运行检查的python脚本
./pke-lab5
`./pke-lab5`
若得到如下输出,那么恭喜你,你已经成功完成了实验六!!!
```
build pk : OK
running app5 : OK
test fork : OK
Score: 20/20
```
### 6.2 基础知识
@ -123,49 +122,34 @@ Score: 20/20
在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状态。
@ -197,53 +181,32 @@ l name进程名
在"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。
@ -255,77 +218,57 @@ l name进程名
在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一个内核进程。
在这里声明了一个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设置为该栈帧。
@ -339,301 +282,181 @@ copy_mm k函数代码如下在函数中我们对页表进行拷贝。
在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如下
在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设置为x1ra寄存器的值还记得我们在copy_thread中层将ra设置为forkret嘛现在程序将从forkret继续执行
```
160 static void
161 forkret(void) {
162 extern elf_info current;
163 load_elf(current.file_name,&current);
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程序就此由内核切换至用户程序执行

Loading…
Cancel
Save