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
Lab3文件树(已折叠)

 在新增的代码中,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_
./include/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_prioritypriority=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…)

posted @ 2020-05-29 22:36  LittleNyima  阅读(618)  评论(0编辑  收藏  举报