3.7: 系统调用sbrk的实现(Code: sbrk)
sbrk
是一个系统调用,用于进程调整(扩展或收缩)其内存大小。该系统调用的实现位于函数growproc
中(kernel/proc.c:260)。
growproc
会调用 uvmalloc
或 uvmdealloc
,取决于参数 n 是正数还是负数:
// Grow or shrink user memory by n bytes.
// Return 0 on success, -1 on failure.
int
growproc(int n)
{
uint64 sz;
struct proc *p = myproc();
sz = p->sz;
if(n > 0){
if((sz = uvmalloc(p->pagetable, sz, sz + n, PTE_W)) == 0) {
return -1;
}
} else if(n < 0){
sz = uvmdealloc(p->pagetable, sz, sz + n);
}
p->sz = sz;
return 0;
}
如果 n 为正数:调用 uvmalloc(kernel/vm.c:233),分配物理内存。
// Allocate PTEs and physical memory to grow process from oldsz to
// newsz, which need not be page aligned. Returns new size or 0 on error.
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;
}
uvmalloc 的步骤包括:
使用 kalloc 分配物理内存。
将分配的内存清零。
使用 mappages 将新的物理内存页面添加到用户页表中。
如果 n 为负数:调用 uvmdealloc 来释放内存。
uvmdealloc 的步骤包括:
调用 uvmunmap(kernel/vm.c:178)。
// Remove npages of mappings starting from va. va must be
// page-aligned. The mappings must exist.
// Optionally free the physical memory.
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;
}
}
uvmunmap 使用 walk 函数查找页表项(PTEs)。
使用 kfree 释放 PTE 所指向的物理内存。
页表在内存管理中的作用
xv6 不仅仅将进程的页表用于指导硬件如何映射用户虚拟地址,还将其作为记录哪些物理内存页已分配给该进程的唯一来源。这也是为什么释放用户内存(在 uvmunmap 中)需要检查用户页表的原因。
总结
sbrk 系统调用通过调用 growproc 来完成内存的增长或收缩,而 growproc 进一步依赖 uvmalloc 或 uvmdealloc 来实现实际的物理内存分配和释放。xv6 的设计选择将页表作为分配和管理物理内存的核心数据结构,既简化了设计,又确保了物理内存与虚拟内存的映射一致性。sbrk 的本质是通过调整进程的 "break"(堆的顶部边界) 来扩展或收缩数据段。数据段是进程的一个区域,通常用于动态内存分配,例如 malloc 等库函数就是基于 sbrk 实现的。