Xv6虚拟内存(二):虚拟内存API

阅读材料

  • Xv6代码:kernel/vm.c

Xv6提供了一系列虚拟内存API,用于内核的其他模块去更方便的操作虚拟内存。

为了避免重复,这里只分析其他章节没有分析到的函数。

// vm.c
void            kvminit(void);
void            kvminithart(void);
void            kvmmap(pagetable_t, uint64, uint64, uint64, int);
int             mappages(pagetable_t, uint64, uint64, uint64, int);
pagetable_t     uvmcreate(void);
void            uvmfirst(pagetable_t, uchar *, uint);
uint64          uvmalloc(pagetable_t, uint64, uint64, int);
uint64          uvmdealloc(pagetable_t, uint64, uint64);
int             uvmcopy(pagetable_t, pagetable_t, uint64);
void            uvmfree(pagetable_t, uint64);
void            uvmunmap(pagetable_t, uint64, uint64, int);
void            uvmclear(pagetable_t, uint64);
pte_t *         walk(pagetable_t, uint64, int);
uint64          walkaddr(pagetable_t, uint64);
int             copyout(pagetable_t, uint64, char *, uint64);
int             copyin(pagetable_t, char *, uint64, uint64);
int             copyinstr(pagetable_t, char *, uint64, uint64);

walkaddr函数

该函数用于从虚拟地址查找对应的物理地址

uint64 walkaddr(pagetable_t pagetable, uint64 va)
{
	pte_t *pte;
	uint64 pa;

	if (va >= MAXVA)
		return 0;

	pte = walk(pagetable, va, 0);
	if (pte == 0)
		return 0;
	if ((*pte & PTE_V) == 0)
		return 0;
	if ((*pte & PTE_U) == 0)
		return 0;
	pa = PTE2PA(*pte);
	return pa;
}

uvm系列函数

uvm可以理解成用户进程虚拟地址,即这些函数是提供给内核中的进程模块,用来操纵用户进程虚拟地址空间的。

uvmcreate函数

该函数通过调用kalloc函数,分配一个空的根页表。如果kalloc函数返回0,说明物理内存分配器崩溃,函数返回0代表失败;否则返回根页表的基地址。

pagetable_t uvmcreate()
{
	pagetable_t pagetable;
	pagetable = (pagetable_t)kalloc();
	if (pagetable == 0)
		return 0;
	memset(pagetable, 0, PGSIZE);
	return pagetable;
}

uvmfirst函数

该函数仅用于第一个用户进程——init进程的页表初始化

void uvmfirst(pagetable_t pagetable, uchar *src, uint sz)
{
	char *mem;

	if (sz >= PGSIZE)
		panic("uvmfirst: more than a page");
	mem = kalloc();
	memset(mem, 0, PGSIZE);
	mappages(pagetable, 0, PGSIZE, (uint64)mem, PTE_W | PTE_R | PTE_X | PTE_U);
	memmove(mem, src, sz);
}

uvmalloc函数

该函数用于虚拟内存的扩展。

  • 如果newsz小于oldsz则直接返回oldsz
  • 否则,循环分配新页面知道达到newsz
uint64 uvmalloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz, int xperm)
{
	char *mem;
	uint64 a;

	if (newsz < oldsz)
		return oldsz;

	oldsz = PGROUNDUP(oldsz);
	for (a = oldsz; a < newsz; a += PGSIZE)
	{
		mem = kalloc();
		if (mem == 0)
		{
			uvmdealloc(pagetable, a, oldsz);
			return 0;
		}
		memset(mem, 0, PGSIZE);
		if (mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_R | PTE_U | xperm) != 0)
		{
			kfree(mem);
			uvmdealloc(pagetable, a, oldsz);
			return 0;
		}
	}
	return newsz;
}

uvmdealloc函数 

该函数用于调整已分配虚拟内存区域的大小,通过调用uvmunmap函数来释放不需要的页表及对应的物理页

uint64 uvmdealloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz)
{
	if (newsz >= oldsz)
		return oldsz;

	if (PGROUNDUP(newsz) < PGROUNDUP(oldsz))
	{
		int npages = (PGROUNDUP(oldsz) - PGROUNDUP(newsz)) / PGSIZE;
		uvmunmap(pagetable, PGROUNDUP(newsz), npages, 1);
	}

	return newsz;
}

uvmcopy函数 

该函数为fork系统调用提供虚拟内存模块的底层支持,将父进程的页复制到子进程的虚拟地址空间中。

  • old:父进程的根页表基地址
  • new:子进程的跟根页表基地址
  • sz:父进程内存大小
int uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
	pte_t *pte;
	uint64 pa, i;
	uint flags;
	char *mem;

	for (i = 0; i < sz; i += PGSIZE)
	{
		if ((pte = walk(old, i, 0)) == 0)
			panic("uvmcopy: pte should exist");
		if ((*pte & PTE_V) == 0)
			panic("uvmcopy: page not present");
		pa = PTE2PA(*pte);
		flags = PTE_FLAGS(*pte);
		if ((mem = kalloc()) == 0)
			goto err;
		memmove(mem, (char *)pa, PGSIZE);
		if (mappages(new, i, PGSIZE, (uint64)mem, flags) != 0)
		{
			kfree(mem);
			goto err;
		}
	}
	return 0;

err:
	uvmunmap(new, 0, i / PGSIZE, 1);
	return -1;
}

uvmfree函数

该函数首先释放释放用户内存页,然后通过调用freewalk释放页表页

void uvmfree(pagetable_t pagetable, uint64 sz)
{
	if (sz > 0)
		uvmunmap(pagetable, 0, PGROUNDUP(sz) / PGSIZE, 1);
	freewalk(pagetable);
}

该函数遍历整个页表。对于每个页表项,若有效且为内层页表,则递归调用freewalk处理子页表,并清空当前页表项

void freewalk(pagetable_t pagetable)
{
	// there are 2^9 = 512 PTEs in a page table.
	for (int i = 0; i < 512; i++)
	{
		pte_t pte = pagetable[i];
		if ((pte & PTE_V) && (pte & (PTE_R | PTE_W | PTE_X)) == 0)
		{
			// this PTE points to a lower-level page table.
			uint64 child = PTE2PA(pte);
			freewalk((pagetable_t)child);
			pagetable[i] = 0;
		}
		else if (pte & PTE_V)
		{
			panic("freewalk: leaf");
		}
	}
	kfree((void *)pagetable);
}

 uvmunmap函数

该函数用于从指定的根页表基地址中移除指定个页,作用与mappages函数相反

  • pagetable:指定根页表基地址
  • va:指定要移除地址的:虚拟地址(必须按页对齐)
  • npages:指定从va开始要移除多少个页
  • do_free:指定是否要释放对应的物理页
void uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
	uint64 a;
	pte_t *pte;

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

	for (a = va; a < va + npages * PGSIZE; a += PGSIZE)
	{
		if ((pte = walk(pagetable, a, 0)) == 0)
			panic("uvmunmap: walk");
		if ((*pte & PTE_V) == 0)
			panic("uvmunmap: not mapped");
		if (PTE_FLAGS(*pte) == PTE_V)
			panic("uvmunmap: not a leaf");
		if (do_free)
		{
			uint64 pa = PTE2PA(*pte);
			kfree((void *)pa);
		}
		*pte = 0;
	}
}

uvmclear函数

该函数供exec系统调用为用户栈设置保护页,原理是清除该页的页表项中U bit,让该页在U-mode没有访问权限

void uvmclear(pagetable_t pagetable, uint64 va)
{
	pte_t *pte;

	pte = walk(pagetable, va, 0);
	if (pte == 0)
		panic("uvmclear");
	*pte &= ~PTE_U;
}

copy系列函数 

copyout函数

该函数将数据从内核空间复制到用户空间。该函数每次复制一页(若不足一页有多少复制多少,不跨页)

int copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{
	uint64 n, va0, pa0;
	pte_t *pte;

	while (len > 0)
	{
		va0 = PGROUNDDOWN(dstva);
		if (va0 >= MAXVA)
			return -1;
		pte = walk(pagetable, va0, 0);
		if (pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0 || (*pte & PTE_W) == 0)
			return -1;
		pa0 = PTE2PA(*pte);
		n = PGSIZE - (dstva - va0);
		if (n > len)
			n = len;
		memmove((void *)(pa0 + (dstva - va0)), src, n);

		len -= n;
		src += n;
		dstva = va0 + PGSIZE;
	}
	return 0;
}

copyin函数 

该函数与copyout函数相反,是将数据从内核空间复制到用户空间

int copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len)
{
	uint64 n, va0, pa0;

	while (len > 0)
	{
		va0 = PGROUNDDOWN(srcva);
		pa0 = walkaddr(pagetable, va0);
		if (pa0 == 0)
			return -1;
		n = PGSIZE - (srcva - va0);
		if (n > len)
			n = len;
		memmove(dst, (void *)(pa0 + (srcva - va0)), n);

		len -= n;
		dst += n;
		srcva = va0 + PGSIZE;
	}
	return 0;
}

 copyinstr函数

该函数专门用于拷贝字符串到内核空间,相比copyin函数多了'\0'字符的处理

int copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max)
{
	uint64 n, va0, pa0;
	int got_null = 0;

	while (got_null == 0 && max > 0)
	{
		va0 = PGROUNDDOWN(srcva);
		pa0 = walkaddr(pagetable, va0);
		if (pa0 == 0)
			return -1;
		n = PGSIZE - (srcva - va0);
		if (n > max)
			n = max;

		char *p = (char *)(pa0 + (srcva - va0));
		while (n > 0)
		{
			if (*p == '\0')
			{
				*dst = '\0';
				got_null = 1;
				break;
			}
			else
			{
				*dst = *p;
			}
			--n;
			--max;
			p++;
			dst++;
		}

		srcva = va0 + PGSIZE;
	}
	if (got_null)
	{
		return 0;
	}
	else
	{
		return -1;
	}
}

参考文献

8. 用户态虚拟内存 | XV6 源代码阅读指南 (gitbook.io)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值