Lab3 Report
Lab3 Report
思考题
Thinking 3.1
- 请结合 MOS 中的页目录自映射应用解释代码中
e->env_pgdir[PDX(UVPT)]= PADDR(e->env_pgdir) | PTE_V 的含义
- e->env_pgdir[PDX(UVPT)] = PADDR(e->env_pgdir) | PTE_V
- Map its own page table at 'UVPT' with readonly permission.
- 在 env_pgdir 基地址之上,加上 PDX(UVPT) 的 UVPT 的页目录项偏移
- 找到指向自己页表的页目录项后,赋上用 PADDR(e->env_pgdir) 取得页目录的物理地址,和 PTE_V 标志位表示只读
- e->env_pgdir[PDX(UVPT)] = PADDR(e->env_pgdir) | PTE_V
Thinking 3.2
- 请结合 MOS 中的页目录自映射应用解释代码中
e->env_pgdir[PDX(UVPT)]= PADDR(e->env_pgdir) | PTE_V 的含义
- static void load_icode(struct Env e, const voidbinary, size_t size) 中,调用了 elf_load_seg(ph, binary + ph->p_offset, load_icode_mapper, e)
- 其中 'e' 为待 program segments 从 'binary' 加载到的用户空间进程的环境,如果没有这个参数,则无法加载到用户空间进程的环境中
Thinking 3.3 (?)
- 结合 elf_load_seg
的参数和实现,考虑该函数需要处理哪些页面加载的情况。
- elf_load_seg 函数会从 ph 中获取
va(该段需要被加载到的虚地址)、sgsize(该段在内
存中的大小)、bin_size(该段在文件中的大小)和
perm(该段被加载时的页面权限)
- 内存中的大小和文件中的大小不一定相同,因为文件中的大小可能不足以填满整个页面,余下的部分用 0 来填充。
- 剩余空闲页面不够分配
- 内存不够加载该段的所有数据
- elf_load_seg 函数会从 ph 中获取
va(该段需要被加载到的虚地址)、sgsize(该段在内
存中的大小)、bin_size(该段在文件中的大小)和
perm(该段被加载时的页面权限)
Thinking 3.4
- 你认为这里的 env_tf.cp0_epc 存储的是物理地址还是虚拟地址?
- 是虚拟地址,因为程序入口为 e_entry (Entry point virtual address),当我们运行进程时 CPU 将自动从 PC 所指的位置开始执行二进制码,所以 PC 这个位置为虚拟地址
Thinking 3.5
- 试找出上述 5 个异常处理函数的具体实现位置。
handle_int 在 genex.S 文件中
handle_mod、handle_tlb 和 handle_sys 都是通过 genex.S 文件中的宏函数 BUILD_HANDLER 实现的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15.macro BUILD_HANDLER exception handler
NESTED(handle_\exception, TF_SIZE, zero)
move a0, sp
jal \handler
j ret_from_exception
END(handle_\exception)
.endm
# ...
BUILD_HANDLER tlb do_tlb_refill
#if !defined(LAB) || LAB >= 4
BUILD_HANDLER mod do_tlb_mod
BUILD_HANDLER sys do_syscall
Thinking 3.6
- 阅读 init.c、kclock.S、env_asm.S 和 genex.S
这几个文件,并尝试说出 enable_irq 和 timer_irq 中每行汇编代码的作用。
enable_irq
1
2
3
4
5LEAF(enable_irq)
li t0, (STATUS_CU0 | STATUS_IM4 | STATUS_IEc) # Timer interrupt (STATUS_IM4) will be enabled. 给 t0 为可以打开时钟中断的值
mtc0 t0, CP0_STATUS #设置给 CP0 中断状态
jr ra
END(enable_irq)timer_irq
1
2
3
4
5
6
7
8
9
10
11
12NESTED(handle_int, TF_SIZE, zero)
mfc0 t0, CP0_CAUSE # 中断原因
mfc0 t2, CP0_STATUS # 中断开关状态
and t0, t2 # 中断置 1 的位上且有中断 enable
andi t1, t0, STATUS_IM4 # 再要求时钟中断 enable
bnez t1, timer_irq # 如果 t1 为 1,表示确实有中断,则跳到 timer_irq
// TODO: handle other irqs
timer_irq:
sw zero, (KSEG1 | DEV_RTC_ADDRESS | DEV_RTC_INTERRUPT_ACK) #关闭时钟中断,进行中断处理
li a0, 0 # 传入参数 0?
j schedule # 走到调度函数,进行进程调度
END(handle_int)
Thinking 3.7
- 阅读相关代码,思考操作系统是怎么根据时钟中断切换进程的。
- 首先设置实时中断的频率,每次发生实时中断时在 handle_int 中的 timer_irq 里,调用 schedule 函数,进行进程调度
- 调度中每次首先判断当前要运行的进程地址是否需要让步,是否还有剩余时间片,进程地址是否存在,以及运行状态是否是可运行,即:(yield || !count || !e || e->env_status != ENV_RUNNABLE)
- 如果都满足,则将其的时间片数量 count-- ,再运行这一个时间片。
难点分析
Exercise 3.1
- 要保证 LIST_INSERT_HEAD 之后的顺序和在数组中顺序相同,需要反向遍历插入
Exercise 3.3
- 每一个进程刚被分配的时候最开始要分配一个页面,作为页目录,并且被分配表示页面的引用次数要 +1
- env_pgdir 是 Kernel virtual address of page dir,要将 page 转换为虚拟地址
Exercise 3.4
- 注意 env_setup_vm 成功的返回值为 0,失败为 -4
- 要分配 env_user_tlb_mod_entry'(lab4),'env_runs'(lab6),'env_id'(lab3),'env_asid'(lab3),'env_parent_id' (lab3)
- asid_alloc(&(e->env_asid)) 也可能因为最多 32 个 ASID 满了而分配失败
Exercise 3.6
- e->env_tf.cp0_epc = ehdr->e_entry; 给出出错时返回 EPC 的地方,即程序的第一条指令的虚拟地址
Exercise 3.7
- 创建进程的时候需要赋予其初始优先级,设置为 RUNNABLE 状态,加载某一个需要运行的 ELF 文件到进程地址中,即 load_icode(e,binary,size),最后需要将进程插入到进程调度队列中
Exercise 3.8
- 进程运行时如果当前有进程在运行,则需要先保存存在于 KSTACKTOP 以下的一个 sizeof(TrapFrame) 区域中的寄存器栈帧。即 curenv->env_tf = ((struct Trapframe)KSTACKTOP - 1);
- 运行完毕之后需要回到用户态,要把寄存器栈帧里的东西还原,即 env_pop_tf(&(curenv->env_tf), curenv->env_asid);
Exercise 3.9 && 3.10
- 异常处理函数入口分配
1 | .section .text.tlb_miss_entry # tlb_miss_entry 异常 地址在. = 0x80000000; |
- 入口分配之后在 kernal.lds 中定义异常处理程序的入口地址
实验体会
- 用 DEBUGK 或者 printk 可以看整个程序走到了哪一步卡死,方便调试
- 进程调度的方式,以及背后涉及到的中断控制需要理解
- 注意控制进程的属性,对应到的可执行文件关联,以及每个进程建立一个页表