• 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…)

  • 相关阅读:
    Java 程序流程语句
    Java 基本语法
    Java 环境搭建
    Spring事务管理
    AOP 与 Spring中AOP使用(下)
    python爬虫笔记之爬取足球比赛赛程
    python爬虫笔记之re.match匹配,与search、findall区别
    python爬虫笔记之re.compile.findall()
    python爬虫笔记之re.IGNORECASE
    跨站脚本攻击(selfxss)笔记(三)
  • 原文地址:https://www.cnblogs.com/littlenyima/p/12989489.html
Copyright © 2020-2023  润新知