# 一、 实验目的

  • 了解 MIPS 4Kc 的访存流程与内存映射布局

  • 掌握与实现物理内存的管理方法(链表法)

  • 掌握与实现虚拟内存的管理方法(两级页表)

  • 掌握 TLB 清除与重填的流程

# 二、 访存流程

image-20250330232433190

# 虚拟地址到物理地址的映射

image-20250330233055099

# Exercise 2.1

补全 mips_detect_memory 函数

void mips_detect_memory(u_int _memsize)
{
	/* Step 1: Initialize memsize. */
	memsize = _memsize;
	/* Step 2: Calculate the corresponding 'npage' value. */
	/* Exercise 2.1: Your code here. */
	npage = memsize / PAGE_SIZE;
	// finished
	printk("Memory size: %lu KiB, number of pages: %lu\n", memsize / 1024, npage);
}

每页的大小为 4KB,这里用到了宏 PAGE_SIZE ,其定义位于 ./include/mmh.h 中:

#define PAGE_SIZE 4096

# alloc 函数

static void *alloc(u_int n, u_int align, int clear);

作用是分配 n 字节的空间并返回初始的虚拟地址,同时将地址按 align 字节对齐。若 clear 为真,则将对应内存空间的值清零,否则不清零。函数中的全局变量 freemem 记录了空闲空间的最低地址位,查阅 mmu.h 可知 0x80400000 是 kseg0 中内核栈空间的栈顶, freemem 是从此处开始。

这部分指导书描述的已经很详细。需要掌握以下几个宏的用法:

  • ROUND(freemem, align) :返回 [ freemem / align ] * align (将 freemem 按 align 向上对齐)
  • ROUNDDOWN(freemem, align) :返回 [ freemem / align ] * align (将 freemem 按 align 向下对齐)
  • PADDR(va) :返回虚拟地址 va 所对应的物理地址
  • KADDR(x) :返回物理地址 x 所对应的虚拟地址

# 三、物理内存管理

# 链表宏

image-20250331001602566

image-20250330235304069

链表宏的定义位于 include/queue.h ,其实现了 双向链表 功能。注意和下面这种双向链表区分,我们结点的 le_next 指向后继结点,而 le_prev 指向前驱结点的 le_next

51a5cdb15161bb9cc6a62795c24bd56

# Exercise 2.2

After 仿照 Before 实现即可,需要注意判断 listelm 的后继节点是否存在

#define LIST_INSERT_BEFORE(listelm, elm, field)              \
	do                                                       \
	{                                                        \
		(elm)->field.le_prev = (listelm)->field.le_prev;  		// 将 elm 的前驱指针赋值为 listelm 的前驱指针    \
		LIST_NEXT ((elm), field) = (listelm);                	// 令 elm 的后继指针指向 listelm 			   \
		*(listelm)->field.le_prev = (elm);                   	// 令 (listelm 原本的前驱结点) 的后继指针指向 elm \
		(listelm)->field.le_prev = &LIST_NEXT ((elm), field); 	// 令 listelm 的前驱指针指向 elm 的后继指针		\
	} while (0)
#define LIST_INSERT_AFTER(listelm, elm, field)                                   						\
	/* Exercise 2.2: Your code here. */                                          						\
	do                                                                           						\
        {                                                                            					\
		LIST_NEXT((elm), field) = (listelm)->field.le_next;     // 将 elm 的后继指针赋值为 listelm 的后继指针    \
		(elm)->field.le_prev = &((listelm)->field.le_next);     // 令 elm 的前驱指针指向 listelm 的后继指针      \
		if ((listelm)->field.le_next != NULL)                                    						\
		{                                      // 令 (listelm 原本的后继结点) 的前驱指针 指向 elm 的后继指针       \
			((listelm)->field.le_next)->field.le_prev = &((elm)->field.le_next); 						\
		}                                                                        						\
		(listelm)->field.le_next = elm;                         // 令 listelm 的后继指针指向 elm			   \
	} while (0)

# 页控制块

MOSMOS 中维护了 npage 个页控制块,也就是 Page 结构体。每一个页控制块对应一页的物理内存,MOSMOS 用这个结构体来按页管理物理内存的分配。 npage 个 Page 和 npage 个物理页面一一顺序对应。具体来说,用一个数组存放这些 Page 结构体,首个 Page 的地址为 P ,则 P[i] 对应从 0 开始计数的第 i 个物理页面。

需要掌握 ./include/pamp.h 中一些函数的用法

  • page2ppn(struct Page *pp) : 计算地址为 pp 的物理页在全局 pages 数组中的页号
  • page2pa(struct Page *pp) : 根据 Page 结构体指针得到对应的物理地址
  • Page *pa2page(u_long pa) : 根据物理地址得到对应的 Page 结构体指针
  • page2kva(struct Page *pp) : 根据 Page 结构体指针得到对应的虚拟地址
  • va2pa(Pde *pgdir, u_long va) : 给定页目录 pgdir ,将 虚拟地址 (VA) 转换为 物理地址 (PA)

# Exercise 2.3

补全 page_init 函数。其功能是初始化所有物理页 Page 指针,将使用过的页的引用次数置 1,未使用的页加入 page_free_list

void page_init(void)
{
	/* Step 1: Initialize page_free_list. */
	/* Hint: Use macro `LIST_INIT` defined in include/queue.h. */
	/* Exercise 2.3: Your code here. (1/4) */
	LIST_INIT(&page_free_list);				// 注意对 page_free_list 取地址
	// 	finished
	/* Step 2: Align `freemem` up to multiple of PAGE_SIZE. */
	/* Exercise 2.3: Your code here. (2/4) */
	freemem = ROUND(freemem, PAGE_SIZE);
	//	finished
	/* Step 3: Mark all memory below `freemem` as used (set `pp_ref` to 1) */
	/* Exercise 2.3: Your code here. (3/4) */
	struct Page *p;
	for (p = pages; page2kva(p) < freemem; p++)
	{
		p->pp_ref = 1;
	}
	//	finished
	/* Step 4: Mark the other memory as free. */
	/* Exercise 2.3: Your code here. (4/4) */
	for (; page2ppn(p) < npage; p++)
	{
		p->pp_ref = 0;
		LIST_INSERT_HEAD(&page_free_list, p, pp_link);
	}
	//	finished
}

# Exercise 2.4

补全 page_alloc 函数。其功能是向空闲链表 page_free_list 申请页面

int page_alloc(struct Page **new)
{
	/* Step 1: Get a page from free memory. If fails, return the error code.*/
	struct Page *pp;
	/* Exercise 2.4: Your code here. (1/2) */
	if (LIST_EMPTY(&page_free_list))
	{
		return -E_NO_MEM;
	}
	pp = LIST_FIRST(&page_free_list);
	//	finished
	LIST_REMOVE(pp, pp_link);
	/* Step 2: Initialize this page with zero.
	 * Hint: use `memset`. */
	/* Exercise 2.4: Your code here. (2/2) */
	memset(page2kva(pp), 0, PAGE_SIZE);
	//	finished
	*new = pp;
	return 0;
}

# Exercise 2.5

补全 page_free 函数。它的作用是将 pp 指向的页控制块重新插入到 page_free_list 中。此外需要先确保 pp 指向的页控制块对应的物理页面引用次数为 0

void page_free(struct Page *pp)
{
	assert(pp->pp_ref == 0);
	/* Just insert it into 'page_free_list'. */
	/* Exercise 2.5: Your code here. */
	LIST_INSERT_HEAD(&page_free_list, pp, pp_link);
	return;
}

# 四、虚拟内存管理

前面已经提到过,MOS 中用 PADDR 与 KADDR 这两个宏可以对位于 kseg0 的虚拟地址和对应的物理地址进行转换。

但是,对于位于 kuseg 的虚拟地址,MOS 中采用两级页表结构对其进行地址转换。

第一级表称为页目录 (Page Directory),第二级表称为页表 (Page Table)。为避免歧义,下面用 一级页表指代 Page Directory,二级页表指代 Page Table。

image-20250331101619928

由于一个页表项可以恰好由一个 32 位整型来表示,因此可以使用 Pde 来表示一级页表项类型,用 Pte 来表示二级页表项类型,这两者的本质都是 u_long 类型,它们的类型别名定义位于 include/mmu.h 。

例如,设 pgdir 是一个 Pde * 类型的指针,表示一个一级页表的基地址,那么使用 pgdir\+ i 即可得到偏移量为 i 的一级页表项(页目录项)地址

需要掌握以下几个宏的用法,其定义位于 ./include/mmu.h 中:

  • PDX(va) : 获取虚拟地址 va 的 31-22 位 页目录索引(一级页表项偏移量)
  • PTX(va) : 获取虚拟地址 va 的 21-12 位 页表索引(二级页表项偏移量)
  • PTE_ADDR(pte) : 从页表项 pte 中提取 高 20 位 物理页号
  • PTE_FLAGS(pte) : 从页表项 pte 中提取 低 12 位 权限位
  • PPN(pa) : 从物理地址 pa 中提取 物理页号
  • VPN(va) : 从虚拟地址 pa 中提取 虚拟页号

# 二级页表检索函数

static int pgdir_walk(Pde *pgdir, u_long va, int create, Pte **ppte);

该函数的作用是:给定一级页表的基址,给定目标虚拟地址,将对应的 指向二级页表项的 指针赋值给 *ppte 。(没有则可创建)

注意,这里可能会在页目录项无效且 create 为真时,使用 page_alloc 创建一个页表,此时应维护申请得到的物理页的 pp_ref 字段。

image-20250331110203650

# Exercise 2.6

static int pgdir_walk(Pde *pgdir, u_long va, int create, Pte **ppte)
{
	Pde *pgdir_entryp;
	struct Page *pp;
	/* Step 1: 计算页目录项(一级页表项)地址 */
	/* Exercise 2.6: Your code here. (1/3) */
	pgdir_entryp = pgdir + PDX(va);
	//	finished
	/* Step 2: If the corresponding page table is not existent (valid) then:
	 *   * If parameter `create` is set, create one. Set the permission bits 'PTE_C_CACHEABLE |
	 *     PTE_V' for this new page in the page directory. If failed to allocate a new page (out
	 *     of memory), return the error.
	 *   * Otherwise, assign NULL to '*ppte' and return 0.
	 */
	/* Exercise 2.6: Your code here. (2/3) */
	if ((*pgdir_entryp & PTE_V) == 0)		// 也可以直接写 if (PTE_V)。PTE_V 的定义位于,/include/mmh.h 中。
	{
		if (create)
		{
			if (page_alloc(&pp) != 0)		// 给物理页分配空间。这个代码块也可以直接用 try (page_alloc (&pp)) 替代
			{
				return -E_NO_MEM;
			}								
			*pgdir_entryp = page2pa(pp);	// 获取物理页指针对应的物理地址
			*pgdir_entryp = *pgdir_entryp | PTE_C_CACHEABLE | PTE_V;	// 权限赋值
			pp->pp_ref++;					//	物理页的引用 +1
		}
		else
		{
			*ppte = NULL;
			return 0;
		}
	}
	//	fininshed
	/* Step 3: Assign the kernel virtual address of the page table entry to '*ppte'. */
	/* Exercise 2.6: Your code here. (3/3) */
    // 下面这条语句很复杂,用于获取二级页表项的地址。其整体逻辑如下:
    // 从页目录项 (*pgdir_entryp) 中提取二级页表的物理页号 (PTE_ADDR)
    // 将物理页号转换为内核虚拟地址 (KADDR),即二级页表的虚拟基地址
    // 加上二级页表项偏移量,找到 va 对应的二级页表项地址
	*ppte = (Pte *)KADDR(PTE_ADDR(*pgdir_entryp)) + PTX(va);
	return 0;
}

# 取消地址映射的函数

void page_remove(Pde *pgdir, u_int asid, u_long va)

作用是删除一级页表基地址 pgdir 对应的两级页表结构中虚拟地址 va 对物理地址的映射。如果存在这样的映射,那么对应物理页面的引用次数会减少一次

# 增加地址映射函数

int page_insert(Pde *pgdir, u_int asid, struct Page *pp, u_long va, u_int perm);

作用是将一级页表基地址 pgdir 对应的两级页表结构中虚拟地址 va 映射到页控制块 pp 对应的物理页面,并将页表项权限为设置为 perm 。其中参数 asid 是进程的地址空间标识符,用于区分不同进程的 TLB 条目(防止不同进程的相同虚拟地址冲突),在本次实验中无需理会。

需要了解以下函数的用法:

  • tlb_invalidate(asid, va) : 用于 显式失效 TLB 中特定虚拟地址 va 的缓存项,保证下次访问相应虚拟地址 va 时一定触发 TLB 重填,进而保证访存的正确性。

# Exercise 2.7

int page_insert(Pde *pgdir, u_int asid, struct Page *pp, u_long va, u_int perm)
{
	Pte *pte;
	// 检查 va 是否已经存在了映射(只检查不创建) 
	pgdir_walk(pgdir, va, 0, &pte);			
	
    // 如果已经存在了映射,检查映射是否有效
	if (pte && (*pte & PTE_V))
	{
        // 如果映射有效,检查其映射的物理页和将插入的物理页是否一致
        if (pa2page(*pte) != pp)
		{
            // 如果不一致,则删除原映射,后续再插入新页
			page_remove(pgdir, asid, va);
		}
		else
		{
            // 如果一致,则只需更新权限映射位
            // 但先要将 TLB 中缓存的页表项删掉,然后更新内存中的页表项。
            // 这样下次加载 va 所在页时,TLB 会重新从页表中加载这一页表项。
            // 插入完成后函数立即返回。
			tlb_invalidate(asid, va);
			*pte = page2pa(pp) | perm | PTE_C_CACHEABLE | PTE_V;
			return 0;
		}
	}
	/* Step 2: Flush TLB with 'tlb_invalidate'. */
	/* Exercise 2.7: Your code here. (1/3) */
    // 将 TLB 中缓存的页表项删掉
	tlb_invalidate(asid, va);
	/* Step 3: Re-get or create the page table entry. */
	/* If failed to create, return the error. */
	/* Exercise 2.7: Your code here. (2/3) */
    // 检索二级页表项,没有则创建
	try(pgdir_walk(pgdir, va, 1, &pte));
	/* Step 4: Insert the page to the page table entry with 'perm | PTE_C_CACHEABLE | PTE_V'
	 * and increase its 'pp_ref'. */
	/* Exercise 2.7: Your code here. (3/3) */
    // 获得物理地址并设置权限位
	*pte = page2pa(pp) | perm | PTE_C_CACHEABLE | PTE_V;
	pp->pp_ref++;
	return 0;
}

# 寻找映射的物理地址函数

struct Page * page_lookup(Pde *pgdir, u_long va, Pte **ppte)

作用是返回一级页表基地址 pgdir 对应的两级页表结构中虚拟地址 va 映射的物理页面的页控制块,同时将 ppte 指向的空间设为对应的二级页表项地址,如果未找到则设为 NULL

# 五、访问内存与 TLB 重填

# TLB 组成与 CP0 寄存器

与内存管理相关的 CP0 寄存器:

寄存器序号寄存器名用途
8BadVaddr保存引发地址异常的虚拟地址
10、2EntryHi、EntryLoHi 记录了 TLB 的高 32 位,Lo 记录了 TLB 的低 32 位,所有读写 TLB 的操作都要通过这两个寄存器
0Index决定索引号为某个值的 TLB 表项被读或者写
1Random提供一个随机的索引号用于 TLB 的读写

每个 TLB 表项都有两个组成部分,包括一组 Key 和两组 Data 。 TLB 采用奇偶页的设计,即使用 VPN 中的高 19 位与 ASID 作为

Key ,一次查找到两个 Data (一对相邻页面的两个页表项),并用 VPN 中的最低 1 位在两个 Data 中选择命中的结果。因此在对 TLB 进行维护(无效化、重填)时,除了维护目标页面,同时还需要维护目标页面的邻居页面

EntryHi 、 EntryLo 都是 CP0 中的寄存器,他们只是分别对应到 TLB 的 Key 与两组 Data,并不是 TLB 本身。

其中 EntryLo0 、 EntryLo1 拥有完全相同的位结构, EntryLo0 存储 Key 对应的偶页,而 EntryLo1 存储 Key 对应的奇页。

image-20250331162410381

软件必须经过 CP0 与 TLB 交互,因此软件操作 TLB 的流程总是分为两步:

  • 填写 CP0 寄存器
  • 使用 TLB 相关指令

# TLB 相关指令

指令作用
tlbr读 TLB 。以 Index 寄存器中的值为索引,读出 TLB 中对应的表项到 EntryHi 与 EntryLo 。
tlbwi写 TLB 。以 Index 寄存器中的值为索引,将此时 EntryHi 与 EntryLo 的值写到索引指定的 TLB 表项中。
tlbwr随机写 TLB 。将 EntryHi 与 EntryLo 的数据随机写到一个 TLB 表项中。
tlbp查 TLB 。用根据 EntryHi 中的 Key (包含 VPN 与 ASID ),查找 TLB 中与之对应的表项。如果命中,并将表项的索引存入 Index 寄存器。若未找到匹配项,则 Index 最高位被置 1

以上指令直接使用即可,不需要操作数

# TLB 维护流程

访问需要经过转换的虚拟内存地址时,首先要使用 Key (虚拟页号和当前进程的 ASID ) 在 TLB 中查询该地址对应的物理页号

  • 如果在 TLB 中存在对应的 TLB 表项时,则可取得物理地址;
  • 如果不能查询到,则产生 TLB Miss 异常,系统跳转到异常处理程序,在内核的两级页表结构中找到对应的物理地址,对 TLB 进行重填。

如果 TLB 中已暂存了页表中某一虚拟地址对应的页表项内容,之后操作系统更新了该页表项,但没有更新 TLB ,则访问该虚拟地址时实际可能会访问到错误的物理页面。所以我们需要维护 TLB 的表项,使得当 TLB 能够查询到虚拟地址相应的页号时,取得的物理页号和权限信息与实际在内核页表中对应的数据一致。

具体来说,维护 TLB 的流程如下:

  1. 更新页表中虚拟地址对应的页表项的同时,将 TLB 中对应的旧表项无效化
  2. 在下一次访问该虚拟地址时,硬件会触发 TLB 重填异常,此时操作系统对 TLB 进行重填

# Exercise 2.8

LEAF(tlb_out)
.set noreorder
    // 保存 EntryHi 的原值
	mfc0    t0, CP0_ENTRYHI
	// 将 Key 写入 EntryHi
	mtc0    a0, CP0_ENTRYHI
	nop
	
	// 使用 tlbp 查找对应旧表项,并将索引存入 Index
	/* Exercise 2.8: Your code here. (1/2) */
	tlbp
	nop
    
	/* Step 2: Fetch the probe result from CP0.Index */
	mfc0    t1, CP0_INDEX
    
.set reorder
	bltz    t1, NO_SUCH_ENTRY
    
.set noreorder
    // 将三个 CP0 寄存器清零
	mtc0    zero, CP0_ENTRYHI
	mtc0    zero, CP0_ENTRYLO0
	mtc0    zero, CP0_ENTRYLO1
	nop
	// 使用 tlbwi 将旧表项清零,使其无效化
	/* Exercise 2.8: Your code here. (2/2) */
	tlbwi
.set reorder
NO_SUCH_ENTRY:
	// 恢复 EntryHi 的值
	mtc0    t0, CP0_ENTRYHI
	j       ra
END(tlb_out)

# TLB 重填流程

  1. 从 BadVAddr 中取出引发 TLB 缺失的虚拟地址。

  2. 从 EntryHi 的 0 – 7 位取出当前进程的 ASID 。

  3. 先在栈上为返回地址、待填入 TLB 的页表项以及函数参数传递预留空间,并存入返回地址。以存储奇偶页表项的地址、触发异常的虚拟地址和 ASID 为参数,调用 _do_tlb_refill 函数。该函数是 TLB 重填过程的核心,其功能是根据虚拟地址和 ASID 查找页表,将对应的奇偶页表项写回其第一个参数所指定的地址。

  4. 将页表项存入 EntryLo0 、 EntryLo1 ,并执行 tlbwr 将此时的 EntryHi 与 EntryLo0 、 EntryLo1 写入到 TLB 中(在发生 TLB 缺失时, EntryHi 已经由硬件写入了虚拟页号等信息,无需修改)

# Exercise 2.9

void _do_tlb_refill(u_long *pentrylo, u_int va, u_int asid)
{
	tlb_invalidate(asid, va);
	Pte *ppte;
	/* 提示:
	尝试在循环中调用 'page_lookup' 以查找虚拟地址 va
 	在当前进程页表中对应的页表项 '*ppte'
 	如果 'page_lookup' 返回 'NULL',表明 '*ppte' 找不到,使用 'passive_alloc'
 	为 va 所在的虚拟页面分配物理页面,
 	直至 'page_lookup' 返回不为 'NULL' 则退出循环。
 	你可以在调用函数时,使用全局变量 cur_pgdir 作为其中一个实参。其中存储了当前进程一级页表基地址位于 kseg0 的虚拟地址
 	*/
	/* Exercise 2.9: Your code here. */
	while (page_lookup(cur_pgdir, va, &ppte) == NULL)
	{
		passive_alloc(va, cur_pgdir, asid);
	}
	ppte = (Pte *)((u_long)ppte & ~0x7);
	pentrylo[0] = ppte[0] >> 6;
	pentrylo[1] = ppte[1] >> 6;
}

这里为什么使用 while 语句而非 if 呢?主要是出于对操作系统内存管理可靠性和并发安全性的严格考量

# Exercise 2.10

do_tlb_refill_call:
	addi    sp, sp, -24 /* Allocate stack for arguments(3), return value(2), and return address(1) */
	sw      ra, 20(sp) /* [sp + 20] - [sp + 23] store the return address */
	addi    a0, sp, 12 /* [sp + 12] - [sp + 19] store the return value */
	jal     _do_tlb_refill /* (Pte *, u_int, u_int) [sp + 0] - [sp + 11] reserved for 3 args */
	lw      a0, 12(sp) /* Return value 0 - Even page table entry */
	lw      a1, 16(sp) /* Return value 1 - Odd page table entry */
	lw      ra, 20(sp) /* Return address */
	addi    sp, sp, 24 /* Deallocate stack */
	mtc0    a0, CP0_ENTRYLO0 /* Even page table entry */
	mtc0    a1, CP0_ENTRYLO1 /* Odd page table entry */
	nop
	/* Hint: use 'tlbwr' to write CP0.EntryHi/Lo into a random tlb entry. */
	/* Exercise 2.10: Your code here. */
	tlbwr
	jr      ra
END(do_tlb_refill)

# 六、思考题

# Thinking 2.1

在编写的 C 程序中,指针变量中存储的地址被视为虚拟地址,还是物理地址?MIPS 汇编程序中 lw 和 sw 指令使用的地址被视为虚

拟地址,还是物理地址?

均为虚拟地址

# Thinking 2.2

  • 从可重用性的角度,阐述用宏来实现链表的好处。

    宏将一段代码封装成一条语句,具有可重用性。其相比函数更具有类型无关性,而且没有额外的栈的开销。

  • 查看实验环境中的 /usr/include/sys/queue.h,了解其中单向链表与循环链表的实现,比较它们与本实验中使用的双向链表,分析三者在插入与删除操作上的性能差异。

    链表头部插入尾部插入指定结点插入头部删除尾部删除指定结点删除
    单向链表O(1)O(n)O(n)O(1)O(n)O(n)
    双向链表O(1)O(n)O(1)O(1)O(n)O(1)
    循环链表O(1)O(1)O(n)O(1)O(1)O(n)

# Thinking 2.3

请阅读 include/queue.h 以及 include/pmap.h, 将 Page_list 的结构梳理清楚,选择正确的展开结构。

struct Page_list{
 	struct {
 		struct {
 			struct Page *le_next;
 			struct Page **le_prev;
 		} pp_link;
 		u_short pp_ref;
 	}* lh_first;
}

# Thinking 2.4

  • 请阅读上面有关 TLB 的描述,从虚拟内存和多进程操作系统的实现角度,阐述 ASID 的必要性。

在多进程系统中,每个进程拥有独立的地址空间。同一虚拟地址在不同地址空间中通常映射到不同物理地址,通过 ASID 可以判断是在哪个地址空间。

  • 请阅读 MIPS 4Kc 文档《MIPS32® 4K™ Processor Core Family Software User’sManual》的 Section 3.3.1 与 Section 3.4,结合 ASID 段的位数,说明 4Kc 中可容纳不同的地址空间的最大数量。

64 个。

Since the ASID is only 6 bits long, OS software does have to lend a hand if there are ever more than 64 address spaces in concurrent use; but it probably won’t happen too often.

# Thinking 2.5

  • tlb_invalidate 和 tlb_out 的调用关系?

    tlb_invalidate 调用 tlb_out

  • 请用一句话概括 tlb_invalidate 的作用。

    用于 显式失效 TLB 中特定虚拟地址 va 的缓存项,保证下次访问相应虚拟地址 va 时一定触发 TLB 重填,进而保证访存的正确性

  • 逐行解释 tlb_out 中的汇编代码。

    见上文 Exercise 2.8

# Thinking 2.6

请结合 Lab2 开始的 CPU 访存流程与下图中的 Lab2 用户函数部分,尝试将函数调用与 CPU 访存流程对应起来,思考函数调用与 CPU 访存流程的关系

函数调用与 CPU 访存流程紧密相关:当调用函数时,CPU 通过虚拟地址访问指令和数据,若 TLB 未缓存该地址映射则触发异常,由操作系统查询页表完成地址转换;函数内的栈操作、全局变量访问同样依赖这一机制,而返回地址的跳转再次引发指令获取流程

# Thinking 2.6

简单了解并叙述 X86 体系结构中的内存管理机制,比较 X86 和 MIPS 在内存管理上的区别。

特性x86MIPS
地址转换机制分段(可选) + 分页(主流)纯分页
页表管理硬件自动遍历多级页表软件处理 TLB 缺失(通过异常)
TLB 管理硬件自动更新,支持 PCID软件显式控制( tlbwi / tlbwr )
进程隔离标识PCID(12 位,现代 x86)ASID(8 位)
缺页处理硬件触发 #PF ,内核处理类似,但需软件查询页表
典型应用场景通用计算(PC / 服务器)嵌入式 / 实时系统

# 七、参考资料

南风北辰 - 博客园

重结晶 - 博客园

NothingToSay - 知乎

更新于 阅读次数 次

请我喝[茶]~( ̄▽ ̄)~*

CircleCoder 微信支付

微信支付

CircleCoder 支付宝

支付宝

CircleCoder 贝宝

贝宝