Lab3实验报告

Lab3实验报告

思考题

Q1

左边:PDX()的作用是获得地址对应的页目录索引,因此e->env_pgdir[PDX(UVPT)]的含义为e的对应UVPT(用户页表的起始处的内核虚拟地址)页目录号的进程页目录的值。

右边:PADDR()用于将内核虚拟地址转成对应的物理地址,e->env_pgdir代表进程e的页目录的内核虚拟地址。PTE_V是有效权限位。因此右边的意思就是得到e页目录对应的物理地址,并使其有效。

经过这个操作,用户程序能够通过UVPT来读到它的页表。


Q2

  1. data是传入的进程控制块指针,共有两处被调用:load_icode_mapperload_icode

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    static int load_icode_mapper(void *data, u_long va, size_t offset, u_int perm,const void *src, size_t len)
    {
    //......
    }

    static void load_icode(struct Env *e, const void *binary, size_t size) {
    //......
    panic_on(elf_load_seg(ph, binary + ph->p_offset, load_icode_mapper, e));
    //......
    }

    作用为在增加虚拟地址到物理地址映射时提供env_pgdirenv_asid两个参数。

  2. 没有data的话,load_icode_mapper无法知道页目录及地址和asid,那么后续的page_insert就不知道要将页面插入到哪里。


Q3

由指导书的图:

image-20230531152348375
  1. 首先判断offset是否为0。如果不为0则代表地址未对齐,将offset所在的剩下的BY2PGbinary数据写入对应页的对应地址。
  2. 若已经对齐,则直接依此将段内的页映射到物理空间。
  3. 当文件大小小于内存大小时,其余空间用0填充,直到填满内存空间。

Q4

指导书中说:这里的 env_tf.cp0_epc 字段指示了进程恢复运行时 PC 应恢复到的位置。而PC是CPU所处的指令地址。Lab2中我们知道了对CPU来说,所见都为虚拟地址。因env_tf.cp0_epc 存储的是虚拟地址。


Q5

kern/genex.S中,其中handle_int定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
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
j schedule
END(handle_int)

handle_mod, handle_tlb, handle_sys通过BUILD_HANDLER构造。


Q6

1
2
3
4
5
6
7
8
9
# enable_irq 函数
LEAF(enable_irq)
li t0, (STATUS_CU0 | STATUS_IM4 | STATUS_IEc)
# TATUS_IM4代表第4个中断使能位——时钟中断,STATUS_IEc代表中断使能。
mtc0 t0, CP0_STATUS
# 将t0的值赋给CP0_STATUS寄存器,这样它就能够控制中断
jr ra
# 跳转返回
END(enable_irq)
1
2
3
4
5
6
7
8
9
10
11
12
# timer_irq 函数
timer_irq:
sw zero, (KSEG1 | DEV_RTC_ADDRESS | DEV_RTC_INTERRUPT_ACK)
# 将寄存器zero的值(零)存储到内存地址 (KSEG1 | DEV_RTC_ADDRESS | DEV_RTC_INTERRUPT_ACK)中。
# KSEG1 | DEV_RTC_ADDRESS是时钟的位置。
# DEV_RTC_INTERRUPT_ACK是设备寄存器的偏移量,代表实时时钟(RTC)的中断应答寄存器。
# 通过将零存储到该寄存器,可以清除实时时钟中断。
li a0, 0
# 将a0设置为0
j schedule
# 跳转到调度函数
END(handle_int)

Q7

  1. 调用kclock_init完成时钟初始化,并设置中断频率。

  2. 调用enable_irq打开中断

  3. 若产生中断异常,PC指向0x800000080,跳转到.text.exc_gen_entry代码段进行异常分发。

    因为是中断异常,因此属于0号异常,跳转到handle_init

    继续判断IM4是否为时钟中断,如果是进而跳转到timer_irq

    timer_irq调用schedule实现进程调度

  4. schedule首先取出进程控制块

    时间片减一(静态变量count--)

    如果:(未调度进程 || 时间片已用完 || 程序不是可运行状态 || yield指定发生切换)

    ​ 则进行进程切换

  5. 切换过程:

    如果进程快依旧为可运行状态,就将其插入调度队列队尾

    从队头取一个进程

    剩余时间片数量count重新设置为新进城的优先级

  6. 运行当前新的选中进程



实验难点

  1. 在调用map_segment()时,要求size必须是页面大小的整数倍。因此在env_init中用到了ROUND函数:

    1
    2
    3
    4
    5
    6
    void env_init(void) {
    //......
    map_segment(base_pgdir, 0, PADDR(pages), UPAGES, ROUND(npage * sizeof(struct Page), BY2PG),PTE_G);
    map_segment(base_pgdir, 0, PADDR(envs), UENVS, ROUND(NENV * sizeof(struct Env), BY2PG),PTE_G);
    //......
    }
  2. 初始化时注意链表插入顺序。

  3. 暴露 UTOP 往上到 UVPT 之间所有进程共享的只读空间。重要的一句代码为e->env_pgdir[PDX(UVPT)] = PADDR(e->env_pgdir) | PTE_V;,能够使得用户程序直接通过UVPT读取页表。具体解释在思考题中涉及。

  4. SR的理解。如e->env_tf.cp0_status = STATUS_IM4 | STATUS_KUp | STATUS_IEp;中后三个参数,分别代表四号中断可以被响应、用户状态以及开启中断。

  5. elf_load_seg中,load_icode_mapper函数是作为参数传入的,辅助ELF的解析。

  6. env_run中保存上下文的核心为保存寄存器状态。因为具体信息都在进程结构体中有所记录,因此只需保存当前相关寄存器值在后续即可进行现场还原。具体来说保存上下文是((struct Trapframe *)KSTACKTOP - 1)结构体中的内容。

  7. 对进程调度函数的理解:函数中用到了静态变量,而静态变量的特点为在下次调用函数时,这个变量保存的值不会初始化,依旧是之前的值。因此用来记录进程的时间片剩余情况。当时间片都结束后,如果程序依旧未运行完,也仍要将进程挂起——放在队尾,执行新的进程。



心得体会

因为进程相关代码与lab2中的页表处理具有一定的相似性,因此整体来说实验难度要小于lab2。从知识密度来讲,我认为其要比lab2多且难理解。此次试验涉及到了更多底层汇编以及寄存器的相关知识,对理解产生了一定的障碍。

本次实验主要完成了进程的创建、切换、调度等功能,并且引入中断、异常处理等机制,再次丰富了操作系统的完整性。

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
Runtime Display
  • Copyrights © 2023-2024 Lucas
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信