3.7: 系统调用sbrk的实现(Code: sbrk)

sbrk是一个系统调用,用于进程调整(扩展或收缩)其内存大小。该系统调用的实现位于函数growproc中(kernel/proc.c:260)。 growproc 会调用 uvmallocuvmdealloc,取决于参数 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 实现的。