BUAA_OS_2020_Lab3_Code_Review
(今天终于弄完了lab6的课下测试和实验报告,可以和OSLAB wave goodbye了,耶耶耶)
Lab 3文件树如下,新增文件用*标出,本lab主要与进程管理相关。
.
├── boot
│ ├── Makefile
│ └── start.S
├── drivers
│ ├── gxconsole
│ │ ├── console.c
│ │ ├── dev_cons.h
│ │ └── Makefile
│ └── Makefile
├── fs
│ └── fsformat
├── gxemul
│ ├── elfinfo
│ ├── fsformat *
│ ├── r3000
│ ├── r3000_test
│ ├── test
│ └── view
├── include
│ ├── args.h
│ ├── asm
│ │ ├── asm.h
│ │ ├── cp0regdef.h
│ │ └── regdef.h
│ ├── asm-mips3k
│ │ ├── asm.h
│ │ ├── cp0regdef.h
│ │ └── regdef.h
│ ├── env.h
│ ├── error.h
│ ├── fs.h *
│ ├── kclock.h
│ ├── kerelf.h
│ ├── mmu.h
│ ├── pmap.h
│ ├── printf.h
│ ├── print.h
│ ├── queue.h
│ ├── sched.h
│ ├── stackframe.h
│ ├── trap.h
│ ├── types.h
│ └── unistd.h
├── include.mk
├── init
│ ├── code_a.c *
│ ├── code_b.c *
│ ├── init.c
│ ├── main.c
│ └── Makefile
├── lib
│ ├── env_asm.S *
│ ├── env.c
│ ├── genex.S
│ ├── getc.S *
│ ├── kclock_asm.S *
│ ├── kclock.c *
│ ├── kernel_elfloader.c *
│ ├── Makefile
│ ├── printBackUp
│ ├── print.c
│ ├── printf.c
│ ├── sched.c *
│ ├── syscall_all.c *
│ ├── syscall.S *
│ └── traps.c *
├── Makefile
├── mm
│ ├── Makefile
│ ├── pmap.c
│ └── tlb_asm.S
├── readelf
│ ├── kerelf.h
│ ├── main.c
│ ├── Makefile
│ ├── readelf.c
│ ├── testELF
│ └── types.h
└── tools
└── scse0_3.lds
在新增的代码中,fs开头的文件用来支持文件系统(lab5),init中的两个code.c文件是模拟要载入的二进制文件,env开头的文件与进程管理相关(也就是本lab的主要内容),kclock是控制时钟中断用的文件(在进程调度的时间片轮转法中会用来产生时钟中断),sched.c是进程调度管理文件。syscall用来实现系统调用(主要在lab4开始关注),trap.c用来管理陷入内核相关内容。
进程相关
指导书已经指明,与进程相关的信息都保存在进程控制块中,进程控制块的空间在pmap.c中分配。
1 /* 2 * ./include/env.h 3 */ 4 5 /* See COPYRIGHT for copyright information. */ 6 7 #ifndef _ENV_H_ 8 #define _ENV_H_ 9 10 #include "types.h" 11 #include "queue.h" 12 #include "trap.h" 13 #include "mmu.h" 14 15 #define LOG2NENV 10 16 #define NENV (1<<LOG2NENV) 17 #define ENVX(envid) ((envid) & (NENV - 1)) 18 #define GET_ENV_ASID(envid) (((envid)>> 11)<<6) 19 20 // Values of env_status in struct Env 21 #define ENV_FREE 0 22 #define ENV_RUNNABLE 1 23 #define ENV_NOT_RUNNABLE 2 24 25 struct Env { 26 struct Trapframe env_tf; // Saved registers 27 LIST_ENTRY(Env) env_link; // Free list 28 u_int env_id; // Unique environment identifier 29 u_int env_parent_id; // env_id of this env's parent 30 u_int env_status; // Status of the environment 31 Pde *env_pgdir; // Kernel virtual address of page dir 32 u_int env_cr3; 33 LIST_ENTRY(Env) env_sched_link; 34 u_int env_pri; 35 // Lab 4 IPC 36 u_int env_ipc_value; // data value sent to us 37 u_int env_ipc_from; // envid of the sender 38 u_int env_ipc_recving; // env is blocked receiving 39 u_int env_ipc_dstva; // va at which to map received page 40 u_int env_ipc_perm; // perm of page mapping received 41 42 // Lab 4 fault handling 43 u_int env_pgfault_handler; // page fault state 44 u_int env_xstacktop; // top of exception stack 45 46 // Lab 6 scheduler counts 47 u_int env_runs; // number of times been env_run'ed 48 u_int env_nop; // align to avoid mul instruction 49 }; 50 51 LIST_HEAD(Env_list, Env); 52 extern struct Env *envs; // All environments 53 extern struct Env *curenv; // the current env 54 extern struct Env_list env_sched_list[2]; // runnable env list 55 56 void env_init(void); 57 int env_alloc(struct Env **e, u_int parent_id); 58 void env_free(struct Env *); 59 void env_create_priority(u_char *binary, int size, int priority); 60 void env_create(u_char *binary, int size); 61 void env_destroy(struct Env *e); 62 63 int envid2env(u_int envid, struct Env **penv, int checkperm); 64 void env_run(struct Env *e); 65 66 67 // for the grading script 68 #define ENV_CREATE2(x, y) \ 69 { \ 70 extern u_char x[], y[]; \ 71 env_create(x, (int)y); \ 72 } 73 #define ENV_CREATE_PRIORITY(x, y) \ 74 {\ 75 extern u_char binary_##x##_start[]; \ 76 extern u_int binary_##x##_size;\ 77 env_create_priority(binary_##x##_start, \ 78 (u_int)binary_##x##_size, y);\ 79 } 80 #define ENV_CREATE(x) \ 81 { \ 82 extern u_char binary_##x##_start[];\ 83 extern u_int binary_##x##_size; \ 84 env_create(binary_##x##_start, \ 85 (u_int)binary_##x##_size); \ 86 } 87 88 #endif // !_ENV_H_
在这个头文件中,首先定义了进程控制块的个数为1024,也就是说操作系统最多支持同时1024个进程。一个整数的低10位用来编码进程控制块的编号,ENVX通过取出envid的低10位,得到进程控制块的编号。
1 #define LOG2NENV 10 2 #define NENV (1<<LOG2NENV) 3 #define ENVX(envid) ((envid) & (NENV - 1)) 4 #define GET_ENV_ASID(envid) (((envid)>> 11)<<6)
然后定义了进程控制块的三种状态:空闲、挂起、可调度。
1 // Values of env_status in struct Env 2 #define ENV_FREE 0 3 #define ENV_RUNNABLE 1 4 #define ENV_NOT_RUNNABLE 2
定义了进程控制块的结构,各个域的含义在注释中给出。
1 struct Env { 2 struct Trapframe env_tf; // 用来保存上下文寄存器的结构体,在trap.h中定义。 3 LIST_ENTRY(Env) env_link; // 空闲链表 4 u_int env_id; // 每个进程独一无二的编号 5 u_int env_parent_id; // 父进程的id 6 u_int env_status; // 进程控制块的状态 7 Pde *env_pgdir; // 进程对应页目录的内核虚拟地址 8 u_int env_cr3; // 进程对应页目录首地址寄存器的值,cr3寄存器不同的值对应不同地址空间 9 LIST_ENTRY(Env) env_sched_link; // 进程调度队列中用来链接的域 10 u_int env_pri; // 进程优先级,体现在时间片轮转法中是进程可以连续占有的时间片个数 11 12 // Lab 4 IPC 13 u_int env_ipc_value; // 进程间通信发送的内容 14 u_int env_ipc_from; // 信息发送者进程的id 15 u_int env_ipc_recving; // 进程是否处于接收信息的状态 16 u_int env_ipc_dstva; // 发送信息的虚拟地址(是某一页内存的首地址) 17 u_int env_ipc_perm; // 发送的内存页进行映射时的权限 18 19 // Lab 4 fault handling 20 u_int env_pgfault_handler; // 缺页中断的handler 21 u_int env_xstacktop; // 异常处理栈的栈顶位置 22 23 // Lab 6 scheduler counts 24 u_int env_runs; // 进程被调度的次数 25 u_int env_nop; // align to avoid mul instruction(似乎是为了对齐额外定义的量) 26 };
其中定义在trap.h中的struct Trapframe如下,其实相当于一套MIPS寄存器。
1 struct Trapframe { //lr:need to be modified(reference to linux pt_regs) TODO 2 /* Saved main processor registers. */ 3 unsigned long regs[32]; 4 5 /* Saved special registers. */ 6 unsigned long cp0_status; 7 unsigned long hi; 8 unsigned long lo; 9 unsigned long cp0_badvaddr; 10 unsigned long cp0_cause; 11 unsigned long cp0_epc; 12 unsigned long pc; 13 };
最后定义了三个宏,用来创建进程。第一个宏是评测时用到的,本地运行使用后两个,第二个宏用来创建带优先级的进程,第三个宏用来创建不带优先级的进程。使用方法是:如果想要加载user/文件夹中的proc.c对应的进程,就在init.c中调用ENV_CREATE(user_proc);即可。在编译时需要先编译出.b与.x文件才能创建。
1 // for the grading script 2 #define ENV_CREATE2(x, y) \ 3 { \ 4 extern u_char x[], y[]; \ 5 env_create(x, (int)y); \ 6 } 7 #define ENV_CREATE_PRIORITY(x, y) \ 8 {\ 9 extern u_char binary_##x##_start[]; \ 10 extern u_int binary_##x##_size;\ 11 env_create_priority(binary_##x##_start, \ 12 (u_int)binary_##x##_size, y);\ 13 } 14 #define ENV_CREATE(x) \ 15 { \ 16 extern u_char binary_##x##_start[];\ 17 extern u_int binary_##x##_size; \ 18 env_create(binary_##x##_start, \ 19 (u_int)binary_##x##_size); \ 20 }
了解了以上内容便可以根据指导书补全env.c中的各个函数代码,env.c中各个函数的用途为:
u_int mkenvid(struct Env *e):产生一个进程控制块号,由于函数内有一个static值,这个值被放在envid的高位,所以保证了进程控制块号的绝对不重复。(除非整数溢出了并且溢出之后的调用顺序正好和原来一样,不过这种情况…emm…应该不会发生吧)int envid2env(u_int envid, struct Env **penv, int checkperm):根据envid查找env,地址赋给penv指针,需要注意的是当envid=0返回的是curenv而非null,void env_init(void):用来初始化进程控制模块,行为是初始化了调度队列、空闲队列,以及各个进程控制块。static int env_setup_vm(struct Env *e):初始化进程e对应的内核态内存映射。int env_alloc(struct Env **new, u_int parent_id):创建新的进程。static int load_icode_mapper(u_long va, u_int32_t sgsize, u_char *bin, u_int32_t bin_size, void *user_data):用来加载二进制文件,这个有巨多参数的函数是本lab最麻烦的一个函数。static void load_icode(struct Env *e, u_char *binary, u_int size):一个封装的函数,其调用load_elf并将load_icode_mapper作为参数传入,进行调用。void env_create_priority(u_char *binary, int size, int priority):根据二进制文件创建一个带有优先级的进程。void env_create(u_char *binary, int size):相当于env_create_priority中priority=1的情况。这两个创建进程的函数与头文件中定义的宏相对应。void env_free(struct Env *e):释放进程与其使用的内存。void env_destroy(struct Env *e):封装了env_free,并且考虑到当前进程被free时调度新进程的情况。extern void env_pop_tf(struct Trapframe *tf, int id);extern void lcontext(u_int contxt);:这两个函数是在env_asm.S中定义的汇编函数,分别用来弹出栈中保存的上下文,以及切换上下文。void env_run(struct Env *e):调度一个新的进程。
最后梳理一下load_icode_mapper的思路,这个函数在lab6仍然要用到,因此最好一步到位,考虑到各种情况,不要写错,否则后患无穷。
|offset|
|-----------|---BY2PG---|---......----|---BY2PG---|-----------|00000000000|000....000|00000000000|
^ ^
va va+bin_size
如上所示,例如加载时的首地址为va,当va不与一整页对齐时,offset为va%页的大小,load时先load一部分,让va与一页对齐,再以一整页为单位进行加载,最后如果剩余的部分仍不对齐,需要以byte为单位加载到对齐。除此之外需要注意的一点是,如果bin_size小于sg_size,需要将不足的部分填充0,这一部分为bss段,也就是未初始化的全局变量段,需要保证这一部分初值为0。
中断和异常
在计组课设的P7中已经接触到了中断和异常,当时十分强调的一点是“软硬件协同 ”,此处在操作系统课程设计中,关注的即是软件部分。在切换进程、进行IO等操作时均要使用系统调用,系统调用便是用户态进程使用内核态函数的方法,当系统陷入内核时需要依靠中断机制,CPU在硬件层面上实现了对中断机制的支持,操作系统则需要在软件层面上实现。
在发生中断和异常时,首先会跳转到一个固定的物理地址,这个地址是exception handler的首地址(这个跳转是由硬件保证的),在exception handler中对中断类型进行判断,并根据中断类型的不同跳转到不同的handler中进行中断处理。异常分发的代码为:
1 .section .text.exc_vec3 2 NESTED(except_vec3, 0, sp) 3 .set noat 4 .set noreorder 5 /* 6 * Register saving is delayed as long as we don't know 7 * which registers really need to be saved. 8 */ 9 1: 10 mfc0 k1,CP0_CAUSE 11 la k0,exception_handlers 12 /* 13 * Next lines assumes that the used CPU type has max. 14 * 32 different types of exceptions. We might use this 15 * to implement software exceptions in the future. 16 */ 17 18 andi k1,0x7c 19 addu k0,k1 20 lw k0,(k0) 21 NOP 22 jr k0 23 nop 24 END(except_vec3) 25 .set at
原理是取出异常描述码,并跳转到响应的handler处。为了绑定异常描述码与handler,需要异常向量组来实现。在traps.c中的以下部分代码便是为了这个目的:
1 void trap_init(){ 2 int i; 3 for(i=0;i<32;i++) 4 set_except_vector(i, handle_reserved); 5 set_except_vector(0, handle_int); 6 set_except_vector(1, handle_mod); 7 set_except_vector(2, handle_tlb); 8 set_except_vector(3, handle_tlb); 9 set_except_vector(8, handle_sys); 10 } 11 void *set_except_vector(int n, void * addr){ 12 unsigned long handler=(unsigned long)addr; 13 unsigned long old_handler=exception_handlers[n]; 14 exception_handlers[n]=handler; 15 return (void *)old_handler; 16 }
实现了将相应的handler的地址存入exception_handlers数组中的相应位置,在处理时直接跳转即可。
为了产生时钟中断,还需要开启时钟。开启时钟的代码是用汇编实现的,在调用时将其封装为了C函数进行调用。
1 #include <asm/regdef.h> 2 #include <asm/cp0regdef.h> 3 #include <asm/asm.h> 4 #include <kclock.h> 5 6 .macro setup_c0_status set clr 7 .set push 8 mfc0 t0, CP0_STATUS 9 or t0, \set|\clr 10 xor t0, \clr 11 mtc0 t0, CP0_STATUS 12 .set pop 13 .endm 14 15 .text 16 LEAF(set_timer) 17 18 li t0, 0x01 19 sb t0, 0xb5000100 20 sw sp, KERNEL_SP 21 setup_c0_status STATUS_CU0|0x1001 0 22 jr ra 23 24 nop 25 END(set_timer)
具体是将CP0的status寄存器的相应中断位置位,即可开启时钟。有了时钟中断,便可以实现时间片轮转算法。(不过在之后的lab里我的调度算法出现了一些问题,我直接改成了遍历整个内存控制块数组寻找可以调度的进程,直接将其调度的暴力法,比较摸鱼地摸过了后边几个lab…)

浙公网安备 33010602011771号