BUAA-OS-lab3-进程与调度
发表于:2023-06-10 | 分类: BUAA操作系统
字数统计: 2.4k | 阅读时长: 8分钟 | 阅读量:

BUAA-OS lab3实验报告,关于mos系统的进程模式的实现以及进程时间片调度的实现。

lab3_log

实验要求

  • 创建一个进程并成功运行
  • 实现时钟中断
  • 实现两个进程之间的调度

相关文件解读

  • include/trap.h: 定义了Trapframe结构体,用于在进程调度时保存当前进程上下文环境
  • include/env.h: 定义了实现PCB的Env结构体(这个很重要,包含了一个进程所需要的所有的信息),与一系列进程相关的函数

思考题

Thinking 3.1

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

  • 其中UVPT的起始地址是页表项的起始虚拟地址,PDX获得的是这个虚拟地址的页目录号。
    • 这个页目录项就是指向页目录自身的页目录项,我们通过PADDR(e->env_pgdir)来获取页目录(这个页目录就是页表中的一页)的物理地址。
  • 自映射的意义即是我只需要知道二级页表的初始地址,我就能构造出页目录的初始地址以及指向页目录自身的PDE,这样,方便我们访问管理页表与页目录。

Thinking 3.2

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

  • elf_load_seg函数再load_icode函数中被调用,我们可以看到注释中写道它在lib/elfloader.c中被定义,我们可以看到它的最后一个参数data被传入load_icode的形参e,然后最后在load_icode_mapper里又被转换为了Env*,并将src写的页面插入了env的页表中。
  • data参数实现了重定位,实现了解耦合,可以实现函数的复用与拓展

Thinking 3.3

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

  • elf_load_seg的作用是将elf文件的段加载到内存,我们看它传入了一个文件头的参数,与二进制文件(数组)的参数,以及一个回调方法和参数。通过这个文件头读取这个文件的信息,然后通过回调函数把二进制文件写入data的页中。

Thinking 3.4

Thinking 3.4 思考上面这一段话,并根据自己在 Lab2 中的理解,回答:
• 你认为这里的 env_tf.cp0_epc 存储的是物理地址还是虚拟地址?

  • mips是冯诺依曼体系结构,指令和数据不分别存储,所以指令也在内存里,也遵循内存管理的虚拟地址结构,所以cp0中应该是虚拟地址。

Thinking 3.5

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

  • 通过查找功能找到
  • handle_int在kern/genex.S中有个显式的函数中在处理
  • 而其他三个都是通过宏定义重复调用实现的。在kern/genex.S找到了下面语句:
1
2
3
4
5
6
#if !defined(LAB) || LAB >= 4
BUILD_HANDLER mod do_tlb_mod
BUILD_HANDLER sys do_syscall
#endif

BUILD_HANDLER reserved do_reserved

Thinking 3.6

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

1
2
3
4
5
LEAF(enable_irq)
li t0, (STATUS_CU0 | STATUS_IM4 | STATUS_IEc)
mtc0 t0, CP0_STATUS
jr ra
END(enable_irq)
  • 上面是enable_irq,作用是开启中断。接下来是由kern/entry.S中的函数获得中断函数的入口。
    • 开启中断的方式就是写CP0寄存器
1
2
3
4
timer_irq:
sw zero, (KSEG1 | DEV_RTC_ADDRESS | DEV_RTC_INTERRUPT_ACK)
li a0, 0
j schedule
  • timer_irq,作用是跳转到schedule中,执行调度。
  • 具体的实现是给一个地址写入了一个0,大致的意思应该是立即执行
    • KSEG1 | DEV_RTC_ADDRESS 是模拟器(GXemul)映射实时钟的位置
    • INTERRUPT_ACK 意思是中断执行。
  • 然后给传入了一个参数,a0=0
    • 看到这个参数对应schedule函数中的yield,也就是说不使用yield指定强制切换。

Thinking 3.7

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

  • mos操作系统发生时钟中断的时候就会最终调用一次schedule函数。
  • 而这个函数会把env_sched_list中的进程循环执行。
    • 每次调用减少当前进程的一个时间片。
    • 减到0,就把这个进程放到队尾,取出队首的进程执行。

Exercise3.1-3.2

  • 两个队列:env_free_list, env_sched_list;一个用到lab2的LIST结构,另一个用到TAILQ的双端队列结构。相关宏都定义在include/queque.h中
  • 实现kern/env.c中的env_init函数:
    • 首先我们看到了一个东西__attribute__((aligned(BY2PG)))
    • 然后就是两步走
      • 初始化上述两个队列,注意LIST_INIT,TAILQ_INIT是传地址进去的。
      • 然后反着来,将序号从大到小插到env_free_list的表头。
  • 然后实现map_segment函数,实现段地址映射的函数,将一段连续的物理页面,映射到一段连续的虚拟地址。
    • 只需要补一句,就是把传入的参数中的pa与va每次同时增加BY2PG的大小的地址通过page_insert函数建立起联系。
    • 但是要注意page_insert函数需要传入page*,所以要用到pa2page。
  • 模板页目录base_pgdir的作用:各个进程的页目录都从这里取项,就不需要占用多个页的孔吉纳来构造多个进程。

Exercises3.3-3.4

  • 设置进程控制块PCB:从env_free_list中取出一块,并完成设置。(在env_alloc中完成)
    • 其中设置过程需要为进程初始化配置用户地址空间,需要调用env_setup_vm来完成。
    • 理解进程地址空间的分布:4G的虚拟地址空间,ULIM(0x8000_0000)往上是内核,往下是kuseg。然后ULIM~UVPT(user virtual page table)是用户进程的页表,UVPT~UTOP(user top)是所有进程共享的只读空间。再往下就是用户的可读写空间了。
  • 再来完成env_alloc函数
    • 每个进程需要有自己的Trapframe(env_tf)来存放当时的进程的上下文,而我们看Trapframe结构体(include/trap.h中)就能够知道,内容就是我们看到的mipsR3000处理器中的一些寄存器,包括32个通用寄存器,与cp0中的几个寄存器(其中SR寄存器(status register)代表了CPU的运行状态)。
    • asid_alloc函数的用法,阅读源代码,是要传入一个指针,asid写在指针指向的地址里。
    • alloc中没有页面了要返回-E_NO_FREE_ENV(教程不讲!!!!)
    • asid分配不上了要返回错误,所以需要用r来存下返回值。

Exercise3.5-3.8

  • 这几步主要是加载可执行文件,创建进程并执行
  • 首先加载二进制镜像:
    • 给出了四个函数,四个函数的调用关系是:load_icode调用elf_from与elf_load_seg两个函数,elf_load_seg调用load_icode_mapper。
    • 要完成load_icode_mapper(这个函数完成单个页面的加载过程,由elf_load_seg函数调用。
      • 注意page转换成虚拟地址。
  • 然后就是env_creat函数创建进程:
    • 这是内核初始化直接创建,不是通过fork
    • 创建需要先分配一个新的Env结构体,然后设置控制块,再将程序装载即可
  • 最后可以进行进程的创建与切换了。
    • 通过env_run函数,从现在的进程切换至传入的e进程。(进程上下文就是进程执行时所有寄存器的状态。)
    • 根据切换运行进程的流程编写相应的代码。
      • 保存进程上下文
      • 切换PCB(curenv)
      • 切换页表
      • tf出栈,恢复现场

这里的实现过程其实也体现了调用关系,先实现的被后实现的调用

Exercise3.9-3.12

  • kern/entry.S函数的理解。
    • 保存当前上下文。
    • 以异常码为索引index从数组中,取得函数的入口地址,然后跳转。
  • 通过kernel.lds来链接CPU异常与TLBmiss异常的处理程序入口。
  • kclock_init初始化时钟
    • 在一个地址处写入时钟周期。
    • 这里主要弄清楚在时钟中断的过程中发生了一些什么调用
      • 教程是以首次时钟中断为例,mips_init函数中,在用kclock_init完成时钟初始化之后,调用enable_irq开启中断。然后就是由上面提到的kern/entry.S中的函数获得中断函数的入口。
      • 中断处理函数是handle_int,在判断完能够中断后,调用timer_irq执行中断处理。
      • 而这个处理函数的作用就是跳转到schedule中,执行调度。
  • 实现schedule函数,该函数的作用是:
    • 关于static int count;
      • 一次声明重复使用。
    • schedule中使用时间片轮转的方式,但是优先级高的进程的时间片长。

实验体会

  • 这一章里实现了创建进程到运行到调度。
  • 让我更加理解了进程的本质,与调度中需要做的一些事情。
  • 感觉确实难度比lab2稍低一些,因为各个函数的调用关系比较的紧密且明朗。前后需要做的工作也比较的有条理。
  • 理解了整体每个函数的作用与关系,了解了整个工程的层次结构,其实具体的实现还是一件比较次要的事情。
上一篇:
BUAA-OS-lab4-系统调用与ipc与fork
下一篇:
【BUAA-OO】ElevatorDispatcher3.0