美文网首页
2018-12-21

2018-12-21

作者: tropping | 来源:发表于2018-12-21 15:19 被阅读0次

    操作系统lab4学习笔记


    1.看程序

    kern/mpconfig.c

        unsigned char percpu_kstacks[NCPU][KSTKSIZE]
        __attribute__ ((aligned(PGSIZE)));
    

    这里attribute aligned是对齐的意思。那么aligned(PGSIZE)就是对齐到页了。

    Part A: Multiprocessor Support and Cooperative Multitasking

    Multiprocessor Support

    Exercise1

    推测函数作用:

    • 返回值void* 输入物理地址pa 输入未对齐的 size
    • 作用:将[pa,pa+size)映射到从base开始的空间上,连续的。返回当前用到了哪里(虚拟地址)
    • 检查越界(8 9 10)
    • 对齐页面(6 7)
    • 使用boot_map_region进行映射
    • base类似分配页面的next,持续更改上一次用到了哪里,同时也是函数返回想要的值(12 13)
    • 要把va和size对齐到页面,因为boot_map_region没有作round,默认是对齐了的

    看一下boot_map_region(pgdir,va,size,pa,perm),可知对应参数为kern_pgdir,base,size,pa,PTE_PCD|PTE_PWT|PTE_W).

    void *
    mmio_map_region(physaddr_t pa, size_t size)
    {
        static uintptr_t base = MMIOBASE;
        //panic("mmio_map_region not implemented");
        size_t roundSize=ROUNDUP(size,PGSIZE);
        physaddr_t roundPa=ROUNDDOWN(pa,PGSIZE);
        if(base+size>MMIOLIM) {
            panic("mmio_map_region: size overflows MMIOLIM!");
        }
        boot_map_region(kern_pgdir,base,roundSize,roundPa,PTE_PCD|PTE_PWT|PTE_W);
        base+=roundSize;
        return (void*)base;
    }
    

    之后发现一个bug,是对于返回值return的理解的要求错了。

    Return the base of the reserved region.

    其实应该是base+=roundSize前的base!
    所以第13行应该为:

        return (void*)(base-roundSize);
    

    Application Processor Bootstrap

    Exercise2

    在kern\mapentry.S中

    # Specification says that the AP will start in real mode with CS:IP
    # set to XY00:0000, where XY is an 8-bit value sent with the
    # STARTUP. Thus this code must start at a 4096-byte boundary.
    #
    # Because this code sets DS to zero, it must run from an address in
    # the low 2^16 bytes of physical memory.
    

    大概又看了一下汇编,大概就是CS<<4|IP组成当前指令,比如CS=2AE3H,IP=0003H,CPU将在2AE33H处读取指令。(事实上保护模式下应该是查表后的值<<4得到的值相加)那么XY00:0000就是XY000H可以寻址2^12=4096byte的一段了。同时DS(data segment)SS等段选择寄存器,是段寻址模式。<a href="https://www.cnblogs.com/rixiang/p/5451130.html">GDT,LDT,GDTR,LDTR 详解</a>这里解释的比较清楚。至于DS为0但是CS不为零啊,为什么会有这个影响还不清楚。

    啊,理解了。分为real mode 和protected mode,两者由于寄存器位数的不同,造成的寻址模式也不同。16位(real mode):cs:ip=cs<<4 | ip,32位(protected mode):cs为了向下兼容还是当成16bit用,为了匹配32位模式使用了GDT所以变成了查GDT了。<a href="https://blog.csdn.net/yeruby/article/details/39718119">GDT、GDTR、LDT、LDTR的学习</a>

    # Call mp_main().  (Exercise for the reader: why the indirect call?)
    movl    $mp_main, %eax
    call    *%eax
    

    这是为什么呢?

    构造GDT:

    gdt:
        SEG_NULL                # null seg
        SEG(STA_X|STA_R, 0x0, 0xffffffff)   # code seg
        SEG(STA_W, 0x0, 0xffffffff)     # data seg
    
    gdtdesc:
        .word   0x17                # sizeof(gdt) - 1
        .long   MPBOOTPHYS(gdt)         # address gdt
    
    .globl mpentry_end
    

    不是很明白。
    看到SEG的定义

    #define SEG_NULL                        \
        .word 0, 0;                     \
        .byte 0, 0, 0, 0
    #define SEG(type,base,lim)                  \
        .word (((lim) >> 12) & 0xffff), ((base) & 0xffff);  \
        .byte (((base) >> 16) & 0xff), (0x90 | (type)),     \
            (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff)
    
    

    GDT表项的结构:
    <img src="http://ww1.sinaimg.cn/mw690/bff4f9baly1fybb1emm0cj20n10673z9.jpg"/>
    明白了!

    由于mpentry.S的代码被映射到MPENTRY_PADDR上,对应的地址为0x7000,属于basemem中,所以加一个判断即可。

        if(i==MPENTRY_PADDR/PGSIZE)
        {
            pages[i].pp_ref=1;
            pages[i].pp_link=NULL;
            continue;
        }
    

    checkpagefreelist通过。

    Question

    为什么在mpentry.S中需要用到直接的偏移量计算呢?查看.S后发现这个宏只用在.code16段中。
    去掉宏,尝试运行,报错:

    relocation truncated to fit: R_386_16 against `.text'
    查stackoverflow,大概是
    using a label as a 16bit immediate, but linking as if it was 32bit code.

    也就是受到了16bit的限制。考虑报错,看到报错位置是kernel.o,也就是和kernel被加载到一起了,是KERNBASE以上的,如果直接寻址必然超过2^20=1MB的限制,所以通过宏进行计算相对位置跳转!而且,是使用目标-函数开始位置+物理内存位置,计算出来的是物理内存的代码段是相对于0x7000处的,小于1MB可寻址。但是为什么boot.S可以直接用呢?大概是因为boot中还没有开启分页机制且代码在低地址上运行。

    Per-CPU State and Initialization

    Exercise3

    就是KSTKSIZE和KSTCGAP组成一个CPU的堆栈区,循环赋值就可以了。

    static void
    mem_init_mp(void)
    {
        int i=0;
        uintptr_t KSTACKTOPi;
        for (i=0;i<NCPU;i++)
        {
            KSTACKTOPi=KSTACKTOP-i*(KSTKSIZE+KSTKGAP);
            boot_map_region(kern_pgdir,KSTACKTOPi-KSTKSIZE,KSTKSIZE,PADDR(&percpu_kstacks[i]),PTE_W);
        }
    }
    

    就是不知道percpu_kstacks是在哪里被赋值的,全局查找也没有找到被赋值的语句,觉得很神奇。

    Exercise4

    替换变量,加偏移量。

    void
    trap_init_percpu(void)
    {
        int i = thiscpu->cpu_id;
        thiscpu->cpu_ts.ts_esp0 = KSTACKTOP - i*(KSTKSIZE+KSTKGAP);
        thiscpu->cpu_ts.ts_ss0 = GD_KD;
        gdt[(GD_TSS0 >> 3) + i] = SEG16(STS_T32A, (uint32_t) (&(thiscpu->cpu_ts)),
                        sizeof(struct Taskstate) - 1, 0);
        gdt[(GD_TSS0 >> 3) + i].sd_s = 0;
        // Load the TSS selector (like other segment selectors, the
        // bottom three bits are special; we leave them 0)
        ltr(GD_TSS0+8*i);
        lidt(&idt_pd);
    }
    

    上面都是一路替换,难点在于第12行,重新看了一下GDT的构造。

    struct Segdesc gdt[NCPU + 5] =
    {
        SEG_NULL,
        [GD_KT >> 3] = SEG(STA_X | STA_R, 0x0, 0xffffffff, 0),
        [GD_KD >> 3] = SEG(STA_W, 0x0, 0xffffffff, 0),
        [GD_UT >> 3] = SEG(STA_X | STA_R, 0x0, 0xffffffff, 3),
        [GD_UD >> 3] = SEG(STA_W, 0x0, 0xffffffff, 3),
        [GD_TSS0 >> 3] = SEG_NULL
    };
    

    为什么会>>3呢?根据定义,

    #define GD_KT     0x08     // kernel text
    #define GD_KD     0x10     // kernel data
    #define GD_UT     0x18     // user text
    #define GD_UD     0x20     // user data
    #define GD_TSS0   0x28     // Task segment selector for CPU 0
    

    把它转化成二进制:

    001000
    010000
    011000
    100000
    101000
    .....
    

    就很明显的看出来,实际上每次在第四位上+1,即十进制+8!为什么是+8呢?考虑到GDT表项的结构,每个表项是64bit,就是8个字节!所以其实是因为GDT表项的大小是8字节,对应的二进制第三位为零,恰好作为索引了!
    所以在这里第12行要每次+8。

    Locking

    Exercise5

    就是在各个地方加锁。

    Round-Robin Scheduling

    Exercise6

    先看看这个函数怎么用的。
    1.僵尸环境,调用的时候curenv=NULL;

        if (curenv->env_status == ENV_DYING) {
            env_free(curenv);
            curenv = NULL;
            sched_yield();
        }
    

    2.trap处理完之后,CURENV可能为NULL或此时status!=running;

        if (curenv && curenv->env_status == ENV_RUNNING)
            env_run(curenv);
        else
            sched_yield();
    

    3.用户主动调用,此时应该是curenv存在,status==running;

    static void
    sys_yield(void)
    {
        sched_yield();
    }
    

    4.mp_main()中,即初始化完AP后调用,此时找事情做(此时的curenv是什么?status呢?)
    5.i386_init()中,初始化完后运行第一个用户环境。(此时的curenv是什么?status呢?)
    6.env_destroy()中,此时curenv==NULL;

        if (curenv == e) {
            curenv = NULL;
            sched_yield();
        }
    

    于是,这里的思路就是找到一个read的env运行,
    A.当curenv为NULL时从头开始遍历env
    B.当curenv不为NULL时从下一个开始遍历env
    当没有找到一个runnable的环境时
    A.如果自身是正在运行的,那么运行自身
    B.halt

    void
    sched_yield(void)
    {
        struct Env *idle;
        idle = thiscpu->cpu_env;
        int i;
        if (idle) {
            i = ENVX(idle->env_id);
        }
        else {
            i = 0;
        }
        int start = i;
        if (envs[i].env_status == ENV_RUNNABLE) {
            env_run(&envs[i]);
        }
        for (i++;start!=i;i=(i+1)%NENV) {
            if (envs[i].env_status == ENV_RUNNABLE) {
                env_run(&envs[i]);
            }
        }
        if (idle && (idle->env_status)) {
            env_run(idle);
        }
    
        // sched_halt never returns
        sched_halt();
    }
    

    运行时发现总是触发General protection,但是多进程便可以。而且多进程总是从10001开始执行,且第一个进程无法被执行,即总是跳过第一个进程。
    其实从当前进程的下一个进程开始是没错的,缺少的判断是当idle==NULL的时候的第0个进程!在原来的代码中没有判断,是start的取值的问题。即应该从0而不是i++开始。因为刚开始的时候cpuenv必然是空的,即应该从第0号开始。恰好漏了这种情况,通过start和i的赋值恰好处理了。所以正确的是这样的,而且这样的还简洁!

    void
    sched_yield(void)
    {
        struct Env *idle;
        idle = thiscpu->cpu_env;
        int i;
        int start;
        if (idle) {
            i = ENVX(idle->env_id);
            start=i+1;
            if (idle->env_status==ENV_RUNNING) {
            idle->env_status=ENV_RUNNABLE;
        }
        }
        else {
            i = 0;
            start=i;
        }
    
        int count=0;
        for (;count++<NENV;i=(i+1)%NENV) {
            if (envs[i].env_status == ENV_RUNNABLE) {
                env_run(&envs[i]);
            }
        }
        sched_halt();
    }
    

    然后改syscall()

                case (SYS_yield):
                        cprintf("yielding\n");
                        sys_yield();
                        return 0;
    

    加进程

    #if defined(TEST)
        // Don't touch -- used by grading script!
        ENV_CREATE(TEST, ENV_TYPE_USER);
    #else
        // Touch all you want.
        //ENV_CREATE(user_hello, ENV_TYPE_USER);
        ENV_CREATE(user_yield,ENV_TYPE_USER);
        ENV_CREATE(user_yield,ENV_TYPE_USER);
        ENV_CREATE(user_yield,ENV_TYPE_USER);
        ENV_CREATE(user_yield,ENV_TYPE_USER);
        ENV_CREATE(user_yield,ENV_TYPE_USER);
    #endif // TEST*
    

    尝试几次后,发现总会触发General Protection,原来是正确的问题,在没有进程的时候触发的。
    部分输出:

    Back in environment 00001003, iteration 0.
    T_SYSCALL
    Back in environment 00001004, iteration 1.
    T_SYSCALL
    yielding
    T_SYSCALL
    yielding
    T_SYSCALL
    Back in environment 00001000, iteration 0.

    Question3

        curenv=e;
        e->env_status=ENV_RUNNING;
        e->env_runs++;
        lcr3(PADDR(e->env_pgdir));
        //step2
        unlock_kernel();
        env_pop_tf(&e->env_tf);
    

    由于各个pgdir的UTOP上面都一样(除了UVPT是自己的pgdir地址),所以envinfo那一部分也一样,所以可以正常解引用。

    Question4

    保存在tf里,在发生中断的时候trap函数中保存了trapframe,在陷入内核的时候保存了trapframe的位置。用于恢复。

    System Calls for Environment Creation

    Exercise7

    static envid_t
    sys_exofork(void)
    {
        // Create the new environment with env_alloc(), from kern/env.c.
        // It should be left as env_alloc created it, except that
        // status is set to ENV_NOT_RUNNABLE, and the register set is copied
        // from the current environment -- but tweaked so sys_exofork
        // will appear to return 0.
    
        // LAB 4: Your code here.
        //panic("sys_exofork not implemented");
        struct Env* newe;
        int ret = env_alloc(&newe,curenv->env_id);
        if (ret<0) return ret;
        newe->env_status=ENV_NOT_RUNNABLE;
        newe->env_tf=curenv->env_tf;
        newe->env_tf.tf_regs.reg_eax=0;
        return newe->env_id;
    }
    
    static int
    sys_env_set_status(envid_t envid, int status)
    {
        // Hint: Use the 'envid2env' function from kern/env.c to translate an
        // envid to a struct Env.
        // You should set envid2env's third argument to 1, which will
        // check whether the current environment has permission to set
        // envid's status.
    
        // LAB 4: Your code here.
        //panic("sys_env_set_status not implemented");
        if ((status!=ENV_RUNNABLE)&&(status!=ENV_NOT_RUNNABLE)) {
            return -E_INVAL;
        }
        struct Env* theenv;
        int ret = envid2env(envid,&theenv,1);
        if (ret!=0) return ret;
        theenv->env_status=status;
        return 0;
    }
    
    static int
    sys_page_alloc(envid_t envid, void *va, int perm)
    {
        // Hint: This function is a wrapper around page_alloc() and
        //   page_insert() from kern/pmap.c.
        //   Most of the new code you write should be to check the
        //   parameters for correctness.
        //   If page_insert() fails, remember to free the page you
        //   allocated!
    
        // LAB 4: Your code here.
        //panic("sys_page_alloc not implemented");
        struct Env* the_env;
        int ret = envid2env(envid,&the_env,1);
        if (ret<0) return -E_BAD_ENV;
        if (va>=UTOP||((va%PGSIZE)!=0)) return -E_INVAL;
        if (((perm&(PTE_P|PTE_U))!=(PTE_P|PTE_U))||((perm|PTE_SYSCALL)!=PTE_SYSCALL)) return -E_INVAL;
        struct PageInfo *p=page_alloc(ALLOC_ZERO);
        if (!p) return -E_NO_MEM;
        ret = page_insert(the_env->env_pgdir,p,va,perm);
        if (ret<0) {
            page_free(p);
            return -E_NO_MEM;
        }
        return 0;
    }
    
    static int
    sys_page_map(envid_t srcenvid, void *srcva,
             envid_t dstenvid, void *dstva, int perm)
    {
        // Hint: This function is a wrapper around page_lookup() and
        //   page_insert() from kern/pmap.c.
        //   Again, most of the new code you write should be to check the
        //   parameters for correctness.
        //   Use the third argument to page_lookup() to
        //   check the current permissions on the page.
    
        // LAB 4: Your code here.
        //panic("sys_page_map not implemented");
        struct Env *s_env,*d_env;
        int ret = envid2env(srcenvid,&s_env,1);
        if (ret<0) return -E_BAD_ENV;
        ret = envid2env(dstenvid,&d_env,1);
        if (ret<0) return -E_BAD_ENV;
        if (srcva>=UTOP||((srcva%PGSIZE)!=0)) return -E_INVAL;
        if (dstva>=UTOP||((dstva%PGSIZE)!=0)) return -E_INVAL;
        pte_t pte;
        struct PageInfo* p = page_lookup(s_env->env_pgdir,srcva,&pte);
        if (!p) return -E_INVAL;
        if (((perm&(PTE_P|PTE_U))!=(PTE_P|PTE_U))||((perm|PTE_SYSCALL)!=PTE_SYSCALL)) return -E_INVAL;
        if ((((*pte)&PTE_W)==0)&&(perm&PTE_W)) return -E_INVAL;
        ret = page_insert(d_env->env_pgdir,p,dstva,perm);
        if (ret<0) return ret;
        return 0;
    }
    

    都是按照注释写的。
    最后增加sys_call(),补全。
    运行时发现总是触发General protection,但是多进程便可以。而且多进程总是从10001开始执行,且第一个进程无法被执行,即总是跳过第一个进程。
    其实从当前进程的下一个进程开始是没错的,缺少的判断是当idle==NULL的时候的第0个进程!在原来的代码中没有判断,是start的取值的问题。即应该从0而不是i++开始。因为刚开始的时候cpuenv必然是空的,即应该从第0号开始。恰好漏了这种情况,通过start和i的赋值恰好处理了。
    运行make run-dumbfork(),成功。

    Part B: Copy-on-Write Fork

    Setting the Page Fault Handler

    Exercise8

    static int
    sys_env_set_pgfault_upcall(envid_t envid, void *func)
    {
        // LAB 4: Your code here.
        //panic("sys_env_set_pgfault_upcall not implemented");
        struct Env* the_env;
        int ret = envid2env(envid,&the_env,1);
        if (ret<0) return ret;
        the_env->env_pgfault_upcall=func;
        return 0;
    }
    

    Invoking the User Page Fault Handler

    Exercise9

    有一个神图来解释发生了什么:
    <img src="http://ww1.sinaimg.cn/mw690/bff4f9baly1fydh8lv73nj20fo0emtfs.jpg"/>

    简而言之,发生缺页中断的时候,依然会陷入内核态,只不过在dispatch之后会额外的操作,进行handle之后转移到用户异常堆栈中。

    • A.发生在用户态:用户态(用户堆栈)->内核态(内核堆栈)->用户异常处理(用户异常堆栈)
    • B.发生在用户异常态:用户异常态(用户异常堆栈)->内核态(内核堆栈)->用户异常处理(用户异常态)

    其中有一些不同,即用户异常态需要在上一个utf下垫一个空32位,返回的时候用,具体为什么不太明白。
    你可以通过检测它的值是不是在 UXSTACKTOP-PGSIZE 和 UXSTACKTOP-1 (包含边界)之间,来判断 tf->tf_esp 是否已经在用户异常堆栈上。esp是当前调用的栈底,检查esp就可以了。(8-14行)为什么不包括UXSTACKTOP呢?
    然后根据UTrapframe赋值就好了。最后调整eip到函数入口,运行即可。
    trap.c/

    void
    page_fault_handler(struct Trapframe *tf)
    {
        ...
        if (curenv->env_pgfault_upcall) {
            struct UTrapframe* u_tf;
            size_t gap=0;
            if ((tf->tf_esp>=(UXSTACKTOP-PGSIZE))&&(tf->tf_esp<UXSTACKTOP)) {
                gap = 4;
            }
            else {
                gap = 0;
            }
            u_tf = tf->tf_esp - sizeof(struct UTrapframe) - gap;
            user_mem_assert(curenv,u_tf,sizeof(struct UTrapframe),(PTE_W|PTE_U));
            u_tf->utf_esp=tf->tf_esp;
            u_tf->utf_eflags=tf->tf_eflags;
            u_tf->utf_eip=tf->tf_eip;
            u_tf->utf_regs=tf->tf_regs;
            u_tf->utf_err=tf->tf_err;
            u_tf->utf_fault_va=fault_va;
            curenv->env_tf.eip=(uint32_t)curenv->env_pgfault_upcall;
            curenv->env_tf.esp=(uint32_t)u_tf;
            env_run(curenv);
        }
        ...
    }
    

    User-mode Page Fault Entrypoint

    Exercise10

    _pgfault_upcall:
        // Call the C page fault handler.
        pushl %esp          // function argument: pointer to UTF
        movl _pgfault_handler, %eax
        call *%eax
        addl $4, %esp
        movl 0x30(%esp),%eax
        subl $0x4,%eax
        movl %eax,0x30(%esp)
        movl 0x28(%esp),%ebx
        movl %ebx,(%eax)
    
        addl $0x8,%esp
        popal
    
        addl $0x4,%esp
        popfl
    
        pop %esp
        ret
    

    实在是妙啊,从一开始就是为了最后的ret。

    Exercise11

    void
    set_pgfault_handler(void (*handler)(struct UTrapframe *utf))
    {
        int r;
    
        if (_pgfault_handler == 0) {
            // First time through!
            // LAB 4: Your code here.
            //panic("set_pgfault_handler not implemented");
            r = sys_page_alloc(0,(void*)(UXSTACKTOP-PGSIZE),PTE_W|PTE_U|PTE_P);
            if (r<0) 
                panic("set_pgfault_handler: sys_page_alloc failed! %e\n",r);
            sys_env_set_pgfault_upcall(0,_pgfault_upcall);
        }
    
        // Save handler pointer for assembly to call.
        _pgfault_handler = handler;
    }
    

    Implementing Copy-on-Write Fork

    Exercise12

    Part C: Preemptive Multitasking and Inter-Process communication (IPC)

    Clock Interrupts and Preemption

    Exercise13

    Exercise14

    Inter-Process communication (IPC)

    Exercise15

    相关文章

      网友评论

          本文标题:2018-12-21

          本文链接:https://www.haomeiwen.com/subject/ruomkqtx.html