OS

OS lab3

谨记第一次exam爆零(课下不能有bug)

Posted by Steel Shadow on April 18, 2023

思考题

  1. Thinking 3.1 请结合 MOS 中的页目录自映射应用解释代码中 e->env_pgdir[PDX(UVPT)] = PADDR(e->env_pgdir) | PTE_V 的含义。

    UVPT 是该用户进程 e 的页表虚拟基地址,采用自映射方法,(进程 e 的)页表的第 PDX(UVPT) 个页目录项映射到进程 e 自身页目录的物理地址,并且对应的页目录项应当是有效的 PTE_V。

  2. Thinking 3.2 elf_load_seg 以函数指针的形式,接受外部自定义的回调函数 map_page。请你找到与之相关的 data 这一参数在此处的来源,并思考它的作用。没有这个参数可不可以?为什么?

    1
    2
    3
    
     int elf_load_seg(Elf32_Phdr *ph, const void *bin, elf_mapper_t map_page, void *data);
    
     struct Env* env = (struct Env*)data; //load_icode_mapper中
    

    map_page (调用 load_icode_mapper)中,data 是指定的一个进程空间, load_icode_mapper 为 data 对应的进程空间分配一个物理页面,并在页表建立映射。
    必须要有data,否则无法为指定进程分配物理页面

  3. Thinking 3.3 结合 elf_load_seg 的参数和实现,考虑该函数需要处理哪些页面加载的情况。

    1
    
     u_long offset = va - ROUNDDOWN(va, BY2PG);
    

    offset 为 va 的距离对齐页面的偏移量。

    1. 若页面不对齐,则需要先将不对齐的页加载到指定进程。
    2. 将二进制文件的对齐部分加载到内存。
    3. 当 段内存大小 大于 二进制文件的大小 时,为多于的部分(如.bss)填充0。
  4. Thinking 3.4 思考上面这一段话,并根据自己在 Lab2 中的理解,回答:
    • 你认为这里的 env_tf.cp0_epc 存储的是物理地址还是虚拟地址?

    是虚拟地址,CPU在会使用MMU将虚拟地址转化为相应的物理地址。此外PC计数器顺序执行指令,物理地址是不连续的,虚拟地址是连续的。

  5. Thinking 3.5 试找出 0、1、2、3 号异常处理函数的具体实现位置。8 号异常(系统调用)涉及的 do_syscall() 函数将在 Lab4 中实现。

    kern/genex.S 0号异常处理函数 handle_int 表示中断,由时钟中断、控制台中断等中断造成 1 2 3号找不到

  6. Thinking 3.6 阅读 init.c、kclock.S、env_asm.S 和 genex.S 这几个文件,并尝试说出enable_irq 和 timer_irq 中每行汇编代码的作用。

     // enable_irq
     LEAF(enable_irq) //定义叶函数
     li      t0, (STATUS_CU0 | STATUS_IM4 | STATUS_IEc)
     // IM4 表示 4 号中断可否被响应
     // IEc IE 代表CPU 中断是否开启,c代表SR寄存器的二重栈底部
     // CU0 SR寄存器的第 28 位,代表可以在用户态使用一些特权指令
     mtc0    t0, CP0_STATUS //CP0寄存器赋值
     jr      ra  //返回调用位置
     END(enable_irq) //定义结束
    
     NESTED(handle_int, TF_SIZE, zero)
         mfc0    t0, CP0_CAUSE
         mfc0    t2, CP0_STATUS
         and     t0, t2
         andi    t1, t0, STATUS_IM4
         bnez    t1, timer_irq
         // TODO: handle other irqs
     timer_irq:
         sw      zero, (KSEG1 | DEV_RTC_ADDRESS | DEV_RTC_INTERRUPT_ACK) //写此地址响应时钟中断
         li      a0, 0 //调度schedule的yield参数为0,也就是正常调度,不强制切换当前进程
         j       schedule //度过了一个时间片,重新进行进程调度
     END(handle_int)
    
  7. 阅读相关代码,思考操作系统是怎么根据时钟中断切换进程的。

    env_sched_list 是需要调度的进程队列(全部是ENV_RUNNABLE),每个进程都有自己的优先级 env_pri(执行的时间片长度)。
    我们设定的时钟频率为 200HZ ,每个时间片为 5ms。若启用时钟中断,则每 5ms 产生一次时钟中断。每次时钟中断时,调用 handle_int 异常处理函数,其中调用 schedule(0) 调度当前进程运行1次。若当前进程的 count(剩余时间片数量) 为 0 或当前进程不为 ENV_RUNNABLE, 则从 env_sched_list 选取一个新进程调度运行。

难点分析

本次实验的难点在于 env_setup_vm 初始化进程虚拟地址空间,要结合 mmu.h 理解用户进程页表分布于 UVPT~ULIM,所有进程共享 UTOP~UVPT 内核只读空间 pages envs(现在env_init内赋模板初值,再在env_setup_vm多次使用)。

我在 exercise 3.4 env_alloc 出错,尽管通过课下测试,但是 exam 失败

1
2
3
4
//正确写法
panic_on(asid_alloc(&(e->env_asid)));
//原错误如下,不清楚 int r 有何作用
e->env_asid = asid_alloc(&r);

env_create 进程在创建时,需要为其加载二进制任务程序,设定优先级,并放入带调度进程队列。

进程加载二进制内存程序 load_icode 步骤:

  • 在 load_icode 中,分别对程序的每个段进行 elf_load_seg,再设置该进程的入口 env_tf.cp0_epc 为程序的入口。
  • 在 elf_load_seg 中,将段使用 load_icode_mapper 加载到进程中,考虑段是否对齐、段内存大小多于程序部分填充0。
  • load_icode_mapper 调用 memcpy 加载程序,调用 page_insert 分配页面映射。

env_run 进程运行时需要保存上下文环境,再切换 curenv 运行进程,并切换 cur_pgdir 页表,最后恢复上下文环境。

后续在链接器部分添加了异常处理程序段和 TLB 缺失处理程序段。

并开启 GXemul-RTC 时钟中断 200HZ。

实验体会

本次实验完成了进程的初始化,并模拟了中断和异常处理,其中着重实现了时钟中断引起的时间片进程切换调度。

由于课下测试强度较弱,未能发现 bug,导致课上测试失败。

在之后的学习中,课下部分需要精心考虑,保证正确性。

指导书&测评网站纠错

1
2
3
# 指导书p88和测评网站 结果显示不对(测评结果是对的)
# 个人猜测指导书和测评网站显示的是旧版本,但是实际测试的是新版本
make test lab=3_2 && make run