os-lab3实验报告


Lab3实验报告

思考题

Thinking 3.1

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

0x7fc00000-0x80000000 这 4MB 空间的起始位置(也就是第一个二级页表的基地址)对应着页目录的第一个页目录项。同时由于 1M 个页表项和 4GB 地址空间是线性映射的,不难算出 0x7fc00000 这一个地址对应的应该是第 0x7fc00000 >> 12 个页表项。

每个页表项是4B,一共有1M个页表项,占据4MB的空间,正好是UVPTULIM这个User VPT这4MB的空间。页表自映射使得页目录的某一项正好映射到这个页表。由于线性性质,那么正好就是UVPT >> 22这个页目录中的一项就是页目录所在页表的物理地址。

后面就是页表的标志位了。

Thinking 3.2

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

map_page()中的参数data就是传入elf_load_seg()中传入的参数data,也就是在load_icode()中传入的参数struct Env *e,一个进程控制块。

map_page()data的作用为最后的env

return page_insert(env->env_pgdir, env->env_asid, p, va, perm);

也就是指定当前进程的页目录的内核虚拟地址,和该进程的asid

不可以去掉这个参数,这是因为每个进程有自己的tlb,这个tlb是由asid确定的。

Thinking 3.3

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

  1. 当前页面不可写的情况:调整传入map_page()中断权限位
  2. 段头和页面不对齐的情况:注意开始的位置
  3. 创建页面不成功的情况:return
  4. 二进制文件大小小于段在内存的大小的情况:继续创建填充0的页面

Thinking 3.4

思考上面这一段话,并根据自己在 Lab2 中的理解,回答:

  • 你认为这里的 env_tf.cp0_epc 存储的是物理地址还是虚拟地址?

虚拟地址。

(lab2啥理解呀)应该是说在CPU看来都是虚拟地址,MIPS是有一个MMU的作为地址变换的。

Thinking 3.5

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

kern/genex.S中。

0号异常处理handle_int是处理时钟中断,其中主要是调用了schedule这个函数,但是这里还特判一下$t1是否为0这个我不太理解,理论上前面刚和STATUS_IM7这个做完与运算应该就不会是0才对。

1号异常,存储异常会执行do_tlb_mod,这个在kern/tlbex.c中实现

2、3号异常,tlb异常会执行do_tlb_refill,也就是tlb重填,这个在kern/tlb_asm.S中实现

Thinking 3.6

阅读 entry.S、 genex.S 和 env_asm.S 这几个文件,并尝试说出时钟中断在哪些时候开启,在哪些时候关闭。

entry.S中的handle_int里面开启,时钟中断过程进入schedule函数,然后其中调用env_run,再调用env_pop_tf,,env_pop_tf的具体实现在env_asm.S中,其中调用RESET_KCLOCK,也就是在include/kclock.h中的实现。

Thinking 3.7

阅读相关代码,思考操作系统是怎么根据时钟中断切换进程的。

根据3.6的分析,时钟中断后会调用schedule在这里面切换进程,具体而言包括保存上下文和进入新的进程的上下文环境,同时时钟初始化。

难点分析

本次lab其实主要包括两个部分,一个是进程方面,包括进程的创建和调度;一个是时钟中断方面。

实际上进程的创建和调度应该分开说,但是我认为这两个其实都是在软件层面上的实现(似乎这么说也不准确),反正就这么说了罢。

本次lab的难点我认为主要在于对这几个的流程的理解。

进程创建与调度

进程的创建是在内核态中执行的。

创建进程包括

  1. 初始化Env 这个进程控制块,包括其中的一些变量的取值
  2. struct Env中还有一个关键的env_pgdir,这个就是确定虚拟地址
  3. 进程是运行中的程序,那这个程序从哪里来呢?这个在load_icode中加以实现,由于一个程序可以有多个进程,所以肯定不能用程序的那块内存空间直接操作,同时还有一些动态的空间需要进程创建,那么这块地址空间在哪呢,对于进程来说,他看到的都是自己的虚拟地址,就是这个了~

进程调度实际上就是去掉这个进程然后把那个进程拉过来

  • 去掉这个进程:这一步需要保存CPU上下文信息,同时需要在两个队列中进行处理,包括空闲env区,和调度队列区
  • 把新的进程拉过来:从调度队列拿一个过来,然后恢复他的上下文信息

时钟中断

我觉得这里主要是函数之间的调用挺复杂的,其实工作流程还是很好理解的

  • handle_int
  • schedule
  • env_run
  • env_pop_tf

害,真是中中又断断啊~

其中对于kern/genex.S中的timer_irq不甚了解,但是当我看到guide_book中的描述时(顺便吐槽一下,咋这还不相同的),我好像懂了,或许是更加不同情况会进入不同的异常处理,只需要将bnez稍微调整为beq为某个特定的数就可以了,由于目前只实现了是时钟中断,因此一律调用这个就好啦~

image-20240424011924754

实验体会

在Lab2的时候说这个lab好难啊,在Lab3的时候也还是这么说。Lab2对于List的使用确实是我遇到的很大的一个障碍,这次使用起来更加得手了。

同时自己实现了一次进程后,对于比如“每个结构都有一个数据结构”、“linux一切皆文件”,这些有了更深的认识。

当需要实现某个具有特定功能的结构时,首要就是分析其数据结构,课程组似乎没有特别强调env的数据结构,其实处处都是优雅。基于上面这一点,进程其实就是一段地址空间,与“一切皆文件”的观点不谋而合。当然需要清楚到底是哪一段内存,是什么样子的内存。

这次还用到了更多debug的功能,下次可以多多研究objdump


文章作者: hugo
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hugo !
  目录