阅读材料
- 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;
}
}