Lab03

思考题

thinking 3.1

MOS中目前使用的envid是由三部分构成:

  • 第0-9位:当前进程在envs数组中偏移量,因为共有1024个env,故这部分占据10位
  • 第10位:1
  • 第11-16位:6位的ASID

低10位是不会出错的,这一步需要判断高7位是否符合envid的规则,以及ASID是否和低10位偏移量对应。

thinking 3.2

  • UTOP以下的区域是可读写区域,UTOP-ULIM间的区域是只读区域

  • 自映射:

    • UVPT以上到ULIM之间,是一个4MB的空间,正好满足对整个4GB进程空间的自映射。
    • 在根据页目录,以这4MB空间内的虚拟地址访问页表时,得到的env_cr3正是
  • 物理地址和虚拟地址:

    • 在MOS中,物理内存为64MB,这是所有进程共享的
    • 对于kuseg, kseg0部分虚拟地址的访问,最终都是落到了访问对应的物理地址。物理地址是真正存储数据的地方
    • 32位操作系统,虚拟内存共4GB,分为用户空间和内核空间。其中kuseg是用户空间,共2GB;kseg0是内核空间的一部分,也是内核空间中我们目前接触最多的部分,其大小为512MB
    • kuseg空间中,通过二级页表机制将虚拟地址映射到不同的物理页;在kseg0空间中,通过将最高位抹去,直接映射到固定的物理页
    • 所有进程共享内核所在的2GB空间

thinking 3.3

user_data为调用load_icode函数时传入的进程参数e

thinking 3.4

根据vava + bin_size是否页对齐,可以分为四种情况:

  • va为页对齐,这种情况较为简单

  • va不是页对齐,其中offset = va - ROUNDDOWN(va, BY2PG),则从va--ROUND(va, BY2PG)这段区间需要单独加载,首先需要考虑va是否和物理页面有映射关系,如果有,则需要找到对应的物理页面;否则需要分配一个新的物理页

4.1

  • va + bin_size为页对齐

  • va + bin_size非页对齐,此时offset = va + i - ROUNDDOWN(va + i, BY2PG),则从ROUNDDOWN(va + i, BY2PG)--va + i这段区间需要单独加载,即用offset代替BY2PG作为加载的size

4.2

thinking 3.5

  • 虚拟地址。

  • 每个进程不一样。

  • e_entry:此字段指明程序入口的虚拟地址。即当文件被加载到进程空间里后,入口程序在进程地址空间里的地址。对于可执行程序文件来说,当 ELF 文件完成加载之 后,程序将从这里开始运行;而对于其它文件来说,这个值应该是 0。

thinking 3.6

epcCP0寄存器组中记录异常发生地址的寄存器。在发生中断时,CPU会将发生中断的地址存入该寄存器。

env_pop_tf()函数中,最后会跳转到进程的pc寄存器,但是在进程切换中,实际跳转的地方应该是epc寄存器,故要将pc寄存器中的值设置为epc

thinking 3.7

  • 在宏SAVE_ALL中,将当前进程的上下文存到TIMESTACK
  • 是内核区域中不同的栈区域,当出现时钟中断时,存储的区域为TIMESTACK,对于其它异常,存储区域为KERNEL_SP

thinking 3.8

  • handle_int: lib/genex.S
  • handle_mod, handle_tlb: lib/genex.S,抽象成了函数handle_\exception
  • handle_sys: lib/syscall.S

thinking 3.9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.macro  setup_c0_status set clr
//定义了一个宏,将状态寄存器修改为设定值,若clr置1则清零
.set push
mfc0 t0, CP0_STATUS
or t0, \set|\clr
xor t0, \clr
mtc0 t0, CP0_STATUS
.set pop
.endm

LEAF(set_timer)
li t0, 0xc8 //t0 <- 200
sb t0, 0xb5000100 //将时钟中断设置为200次
sw sp, KERNEL_SP //KERNEL_SP <- sp, 将当前栈内容存入内核栈指针处
setup_c0_status STATUS_CU0|0x1001 0 //CP0_STATUS <- STATUS_CU0|0x1001=0x10001001
jr ra
nop
END(set_timer)
1
2
3
4
5
6
7
8
9
10
11
12
timer_irq:
sb zero, 0xb5000110 //关闭时钟中断
1: j sched_yield //调用sched_yield函数
nop
/*li t1, 0xff
lw t0, delay
addu t0, 1
sw t0, delay
beq t0,t1,1f
nop*/
j ret_from_exception //调用ret_from_exception函数
nop

thinking 3.10

异常处理

当发生时钟中断,先通过except_vec3异常分发进入对应处理异常函数,在本次实验中为中断处理函数handle_int。然后通过一些处理,比如保存现场等,之后通过判断中断码进入相应的中断服务函数,本次实验中为timer_irq。在该函数中主要工作为调用sched_yield函数调度进程,在该函数中会通过进程的优先级切换进程并运行,这便是根据时钟周期切换进程。

实验难点

进程创建流程及函数调用图

env_create

进程创建相关函数

env_create

位置lib/env.c

说明:创建进程

env_create_priority

位置lib/env.c

说明:创建进程

env_alloc

位置lib/env.c

说明

  • 调用env_setup_vm函数,为进程分配页目录并完成部分映射
  • 初始化对应PCB,主要包括env_id, env_parent_id, env_status, env_runs, env_tf.cp0_status, env_tf.regs[29]
  • 将进程从空闲链表中移出,加入调度队列(这和物理内存管理的页链表管理体系很类似)

env_setup_vm

位置lib/env.c

说明

  • 为进程的页目录分配物理页

  • UTOP为界,将用户空间分别映射,映射的模板是之前虚拟内存初始化时得到的boot_pgdir

    • 0--UTOP间的用户空间是可读写的,且各个进程在这部分的内容是各不一样的,因此在该函数中我们将其全部清零

    • UTOP--UTPV间的用户空间是存储pages, envs结构体数组的,这也是在之前虚拟内存初始化时完成映射的,这样在用户空间就可以访问到这两个数组。这部分对于所有进程都是相同的,因此我们以boot_pgdir为模板完成映射

    • UVPT--UTOP间的用户空间是用来自映射的,这部分4MB空间正好可以映射整个4GB虚拟内存空间,我们用以下代码来初始化这块空间:

      1
      2
      3
      e->env_pgdir = pgdir;
      e->env_cr3 = PADDR(e->env_pgdir);
      e->env_pgdir[PDX(UVPT)] = e->env_cr3 | PTE_V;
    • UTOP以上的空间不属于用户空间,也是用户态无法访问的内存空间

进程加载相关函数

load_icode

位置lib/env.c

说明

  • 分配物理页,并完成从用户栈空间到该物理页的映射
  • 通过load_elf函数完成文件二进制数据到用户空间的映射
  • 设置进程的入口,该入口地址通过上面的load_elf函数获得

load_elf

位置lib/kernel_elfloader.c

说明:分段调用load_icode_mapper函数完成映射

is_elf_format

位置lib/kernel_elfloader.c

说明:判断文件是否符合ELF文件格式

load_icode_mapper

位置lib/env.c

说明:将文件二进制数据映射到用户空间

时钟中断处理图

异常处理

异常处理相关函数

except_vec3

位置boot/start.S

说明:负责异常分发,根据状态寄存器的ExCode跳转到对应的异常处理函数

handle_\exception

位置lib/genex.S

说明:异常处理函数

timer_irq

位置lib/genex.S

说明:时钟中断的处理函数

sched_yield

位置lib/sched.c

说明:调度函数

env_run

位置lib/env.c

说明:运行进程

ret_from_exception

位置lib/genex.S

说明:恢复现场

异常处理相关宏定义

SAVE_ALL

位置include/stackframe.h

位置boot/start.S

说明:保存现场

RESTORE_SOME/ALL

位置include/stackframe.h

说明:恢复现场

get_sp

位置include/stackframe.h

说明:获取保存现场的栈指针

STI

位置include/stackframe.h

说明:开启全局中断使能

CLI

位置include/stackframe.h

说明:禁用全局中断

总结与反思

本次实验重点就是进程的创建和调度,以及简单异常的处理。

相关函数相比之前增加了很多,导致理清函数的调用关系成为了一个重点和前提,否则难以理清逻辑,更别谈去完成所有的函数了。

因此在反复学习指导书和阅读代码之后,在自己理解的基础上画了两个思维导图,以理清逻辑关系。