OS - Lab2实验报告

归档于2025年7月8日。

杂记

Pasted image 20250410155839.png

我们需要回顾一下内存空间相关的知识。

  • kuseg:用户态空间,需要通过TLB和Cache
  • kseg0:过Cache,不过TLB;MMU拉低最高位(-0x8000_0000)得物理地址
  • kseg1:Cache和TLB都不过;MMU拉低高三位得物理地址
  • kseg2:过TLB和Cache

还有,MIPS通过MMIO (Memory Mapped IO)进行外设访问。显然外设处在固定的物理地址上,我们肯定想到kseg1

TLB需要软件初始化,故内核最开始自然放在不用TLB的kseg0上。kseg1暂时认为是访问IO设备专用。

思考题

2.1

指针变量存储的一般认为是虚拟地址。

在我们实现了地址翻译功能的硬件上,lwsw访问的地址被视为虚拟内存;而像我们计组课设中直接访问内存的处理器核上,被视为物理地址。

2.2

  • C语言本身没有泛型语法,这样写可以做到创建一个泛型双向链表,提高可重用性。这同时也省掉了重复造轮子的烦恼,不是吗?(笑)

  • 对于单项链表:插入只需更新两个next域(如insert_after,则是更新当前的,与待插入的两个节点),效率优于剩余二者(循环链表,双向链表还需维护prev);但在删除时,单项列表需要遍历,效率较低;

    对于循环列表:可以认为是双向链表的特殊情况;操作次数与双向列表近乎一致,不过由于需要特判头部节点,以及prev指针需要解引用来维护,效率可能较双向列表略有下降。

2.3

  • C。按照queue.h展开即可。

2.4

  • 查询4Kc手册P41,我们可以得知ASID的定义:
    Pasted image 20250401214103.png

    类似于我们使用PID区分不同的进程,我们使用ASID进行不同进程/线程对应内存空间的标识。 不同的进程,其内存空间/上下文一般情况下不同,虚拟地址到物理地址的映射一般也会不同。这就需要ASID,指示TLB中的映射是否有效。

  • ASID有8位,对应即是2^8 = 256个不同的地址空间。

2.5

  • tlb_invalidate()调用tlb_out
  • 无效化ASID与虚拟地址va对应的页表项;若页表项不存在,则什么都不做。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
     3 LEAF(tlb_out)  // LEAF函数,不调用任何其他子例程,不使用栈上的任何内存空间
    4 .set noreorder // 指定下面程序不可交换执行顺序
    5 mfc0 t0, CP0_ENTRYHI // 暂存当前的ENTRYHI
    6 mtc0 a0, CP0_ENTRYHI // 将传入的参数(VPN,ASID)写入ENTRYHI
    7 nop // 消除冲突
    8 /* Step 1: Use 'tlbp' to probe TLB entry */
    9 /* Exercise 2.8: Your code here. (1/2) */
    10 tlbp // 从ENTRYHI指导的VPN和ASID,将TLB表项号读入INDEX
    11 nop
    12 /* Step 2: Fetch the probe result from CP0.Index */
    13 mfc0 t1, CP0_INDEX // 将INDEX内容提取出来
    14 .set reorder // 允许编译器调换执行顺序,因为下面程序对结果没影响
    15 bltz t1, NO_SUCH_ENTRY // 当INDEX最高位为1时,表项无效,比较时认为$t1 < 0
    16 .set noreorder // 不允许编译器调换执行顺序
    17 mtc0 zero, CP0_ENTRYHI // 准备清空内容
    18 mtc0 zero, CP0_ENTRYLO0 // 准备清空内容
    19 mtc0 zero, CP0_ENTRYLO1 // 准备清空内容;由于奇偶页设计,我们需要LO0,LO1都准备上
    20 nop
    21 /* Step 3: Use 'tlbwi' to write CP0.EntryHi/Lo into TLB at CP0.Index */
    22 /* Exercise 2.8: Your code here. (2/2) */
    23 tlbwi // 清空INDEX对应的TLB表项
    24 .set reorder
    25
    26 NO_SUCH_ENTRY: // 无论表项是否有效,下面程序我们都要执行
    27 mtc0 t0, CP0_ENTRYHI // 恢复之前暂存的ENTRYHI
    28 j ra // 函数执行完成,返回原程序
    29 END(tlb_out)

2.6

整体来说,用户程序进行CPU缓存的过程是这样:

  • 执行用户程序前,由内核进行TLB等内存相关硬件的初始化,随后再执行用户程序
  • 用户程序执行到lw/sw指令,检查TLB
  • (我们先不考虑Cache)若TLB表项命中,则将处理器中的虚拟地址转换为物理地址,进行访存
  • 若TLB未命中,则由硬件产生缺页中断,进入内核态,执行对应的异常处理程序;
  • 在异常处理程序中,调用了我们Lab中实现的do_tlb_refill等一系列函数
  • 执行完成后,从内核态退出,回到产生异常的访存指令,继续执行。

2.7

我选择LoongArch;此处仅对32位的LA32R进行讨论。

由于一些历史渊源,LoongArch有不少地方跟MIPS十分相似,比如,页表都采用了奇偶页的设计:
Pasted image 20250401224910.png

Pasted image 20250401224918.png

在我们本单元介绍的4Kc核中,MMU必须通过TLB进行地址翻译,而LA32R可以通过CSR(控制状态寄存器)配置地址翻译模式,在MMU处实现更灵活的地址翻译:
![[Pasted image 20250401224956.png]]

直接地址翻译模式

在地址为32位时,最高位拉低。类似于我们在MIPS中处理kseg0的虚拟地址转换。

直接映射地址翻译模式

将虚拟地址的[31:29]位,由CSR中存储的映射,进行变换。仅在虚拟地址的[31:29]位命中时,且权限允许,才允许翻译。

Pasted image 20250401225902.png

Pasted image 20250401225913.png

这跟我们在MIPS中kseg1拉低高三位地址,得到实际地址有一点像,不过这里允许的映射更灵活。

页表映射翻译模式

整体思路与MIPS实现基本一致,不过LA32R中由于权限等级不止内核态/用户态,还会对权限进行检查。同时也正因此在内的一些其它原因,LA32R中TLB表项结构与MIPS略有差距。

Pasted image 20250401224910.png

  • 在Tag项中,LA32R多放了一个E,指示TLB表项是否为空;Data项的V仍然执行指示TLB项有效的功能。

  • PS仅在MTLB(数据存储对应的TLB)中出现,指示页的大小(LA32R支持大页)

  • PLV0/1:指示特权等级限制。

  • MAT:指示存储访问类型;

    具体有哪些访问模式见下,与MIPS下的基本一致。

    LA32R了采取两种地址翻译模式;在映射地址翻译模式下,访问模式存在TLB表项中(这一点跟MIPS TLB项中的C0/C1域相似);而在直接地址翻译模式下,由于不通过TLB,我们必须想办法存这一情况的访存模式,这就是CSR.CRMD中存直接翻译情况下访存模式的目的。

    其中,CSR.CRMD指示了当前所处的特权等级,中断使能与(直接地址翻译模式时的)地址翻译模式:
    Pasted image 20250401231114.png

Pasted image 20250401230647.png

其余过程与MIPS是基本一致的,不过:

  • TLB访问/维护过程直接被打包成了一条指令
  • CSR中内容的划分与MIPS的CP0不同,比如LA32R中的ASID单独使用一寄存器管理。

限于篇幅,且并不紧要,这就不再展开了。

Appendix A.1

整体布局应该是这样的:

1
2
3
4
5
6
[PD] - [PD] - [PD] - [PD] - [PD] - [PD] - [UPD] - [PD]
| |
[PT] - [PT] - [UPT] - [PT] - [PT] [PDp] - [PDp] - [PDp]
| |
| [PTp] - [PTp]
[PTE] - [PTE]

其中,

1
2
3
4
U:代指Unique
PD:Page Directory
PT: Page Table
PDp/PTp:页目录/页表地址
  1. 由于题意语焉不详,我们认为所求的是三级页表的一级页表。
    1
    addr_UPD = PT_base + PT_base >> 30 << 21 
  2. 1
    result = PT_base + PT_base >> 30 << 21 + PT_base >> 30 << 12 + PT_base >> 30 << 3

难点分析

双向链表的实现

主要是双向链表的实现并不直观,需要花时间进行理解。

整理下来,双向链表的结构整体长这样:

Pasted image 20250402150655.png

  • 头节点特殊,只有next
  • 对于一个节点,有prevnext两个域
  • next是指向下一节点的指针
  • prev是 指向上一节点的next指针 的指针
  • 由此设计,可以避免对头节点的特判。因为我们维护头节点的next,只需解下一个节点的prev指针,就可维护。

页的管理

我们在整个Lab里做的,都是页控制块

关键在于mips_vm_init()这个函数:
Pasted image 20250402152006.png

这里面涉及的都是物理页面/物理页号

pages的作用,不仅是记录页的基地址,以通过&pages[i]的方式访问第i页的地址,还有以pages[i]的形式,访问与维护第i个控制块。

转换到物理地址,就需要指导书中提到的两个宏了:
Pasted image 20250402152222.png

而虚拟地址到物理地址的映射,是由page_insert完成的。

Pasted image 20250402152423.png

实验体会

本周完成耗时远高于Lab1:

  • 一是内存管理具有一定的复杂度;
  • 二是第一次接触一些实现,如本实验中的双向列表,理解需要一定的时间;
  • 思考题深度很深。

要顺利完成的话,必须对内存管理机制足够熟悉才行……

原创说明

除引用的手册内容以外,本报告为本人原创。

作者

LajiPZ

发布于

2025-07-08

更新于

2025-07-09

许可协议

评论