Xv6虚拟内存(一)——内核地址空间

阅读材料

Xv6代码:memlayout.h、vm.c

教材:Chapter 3 3.1 - 3.3

内核虚拟地址空间初始化

之前的文章提到了,在main()函数中,内核会指派CPU0来完成绝大部分的初始化工作,其中就包括内核的虚拟地址空间初始化工作,而内核通过调用kvminit()函数来实现内核虚拟地址空间的构建。

 kvminit函数

该函数是一个包装函数,通过调用kvmmake函数来初始化kernel_pagetable。该变量是一个全局变量,记录了内核根页表的基地址。

// Initialize the one kernel_pagetable
void kvminit(void)
{
	kernel_pagetable = kvmmake();
}

kvmmake函数

这个函数才是真正负责构建内核虚拟地址空间的函数。该函数通过调用kvmmap函数来实现虚拟地址到物理地址映射的构建。

需要注意的是:

  1. 内核虚拟地址空间采用直接映射方式。即对于内核来说,虚拟地址等于物理地址,可以参考上图中的虚线
  2. trampoline页表映射了两次。该页表在物理地址空间中只存在一份,紧跟在Kernel text之后。而在虚拟地址空间中,该页第一次通过直接映射,映射到了对应的虚拟地址上;第二次通过手动指定,映射到了宏MAXVA指定的地址下紧挨的一页。

函数调用链:

kvminit()--->kvmmake()--->kvmmap()--->mappages()--->walk()

pagetable_t kvmmake(void)
{
	pagetable_t kpgtbl;

	kpgtbl = (pagetable_t)kalloc();
	memset(kpgtbl, 0, PGSIZE);

	// uart registers
	kvmmap(kpgtbl, UART0, UART0, PGSIZE, PTE_R | PTE_W);

	// virtio mmio disk interface
	kvmmap(kpgtbl, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);

	// PLIC
	kvmmap(kpgtbl, PLIC, PLIC, 0x4000000, PTE_R | PTE_W);

	// map kernel text executable and read-only.
	kvmmap(kpgtbl, KERNBASE, KERNBASE, (uint64)etext - KERNBASE, PTE_R | PTE_X);

	// map kernel data and the physical RAM we'll make use of.
	kvmmap(kpgtbl, (uint64)etext, (uint64)etext, PHYSTOP - (uint64)etext, PTE_R | PTE_W);

	// map the trampoline for trap entry/exit to
	// the highest virtual address in the kernel.
	kvmmap(kpgtbl, TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);

	// allocate and map a kernel stack for each process.
	proc_mapstacks(kpgtbl);

	return kpgtbl;
}

 kvmmap函数

该函数是一个包装函数,用于将物理地址空间映射到虚拟地址空间。通过调用mappages函数来实现其功能。

  • kptbl:根页表的首地址
  • va:要映射的虚拟地址
  • pa:要映射的物理地址
  • sz:要映射的内存区域大小
  • perm:页表项描述符
void kvmmap(pagetable_t kpgtbl, uint64 va, uint64 pa, uint64 sz, int perm)
{
	if (mappages(kpgtbl, va, sz, pa, perm) != 0)
		panic("kvmmap");
}

proc_mapstacks函数

该函数为每一个进程(进程总数量用宏NPROC定义)都在内核地址空间中分配了2个页的空间。第一个页是保护页,用于防止进程栈溢出;第二个页是进程的kstack页。

这些页从trampoline页起始地址开始,从高地址往低地址增长,从kstack0、kstack1、…… kstack63

void proc_mapstacks(pagetable_t kpgtbl)
{
	struct proc *p;

	for (p = proc; p < &proc[NPROC]; p++)
	{
		char *pa = kalloc();
		if (pa == 0)
			panic("kalloc");
		uint64 va = KSTACK((int)(p - proc));
		kvmmap(kpgtbl, va, (uint64)pa, PGSIZE, PTE_R | PTE_W);
	}
}

mappages函数 

该函数通过调用walk函数,为指定的虚拟内存逐级分配页表,从而实现了虚拟内存到物理内存映射的创建

int mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm)
{
	uint64 a, last;
	pte_t *pte;

	if ((va % PGSIZE) != 0)
		panic("mappages: va not aligned");

	if ((size % PGSIZE) != 0)
		panic("mappages: size not aligned");

	if (size == 0)
		panic("mappages: size");

	a = va;
	last = va + size - PGSIZE;
	for (;;)
	{
		if ((pte = walk(pagetable, a, 1)) == 0)
			return -1;
		if (*pte & PTE_V)
			panic("mappages: remap");
		*pte = PA2PTE(pa) | perm | PTE_V;
		if (a == last)
			break;
		a += PGSIZE;
		pa += PGSIZE;
	}
	return 0;
}

walk函数 

该函数用于遍历页表结构,找到最末尾一级的页表项

  • 如果alloc == 0:如果该页表项有效则返回其物理地址,若无效则返回0
  • 如果alloc == 1:如果该页表项有效则返回其物理地址,若无效则调用物理内存分配器kalloc函数分配一个物理页,并返回物理地址;如果kalloc函数返回0,说明物理内存已满,分配失败,返回0
pte_t* walk(pagetable_t pagetable, uint64 va, int alloc)
{
	if (va >= MAXVA)
		panic("walk");

	for (int level = 2; level > 0; level--)
	{
		pte_t *pte = &pagetable[PX(level, va)];
		if (*pte & PTE_V)
		{
			pagetable = (pagetable_t)PTE2PA(*pte);
		}
		else
		{
			if (!alloc || (pagetable = (pde_t *)kalloc()) == 0)
				return 0;
			memset(pagetable, 0, PGSIZE);
			*pte = PA2PTE(pagetable) | PTE_V;
		}
	}
	return &pagetable[PX(0, va)];
}

 开启分页功能

main函数中,所有核心(包括cpu0)都会通过调用kvminithart函数来开启分页功能。也就是说,在调用该函数前,内核访问的都是物理内存;调用该函数后,内核访问的就都是虚拟内存了

kvminithart函数

该函数通过将之前定义的全局变量kernel_pagetable写入到satp寄存器中来开启分页功能

void kvminithart()
{
	// wait for any previous writes to the page table memory to finish.
	sfence_vma();

	w_satp(MAKE_SATP(kernel_pagetable));

	// flush stale entries from the TLB.
	sfence_vma();
}

参考文献

7. 内核态虚拟内存 | XV6 源代码阅读指南 (gitbook.io)

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值