6.S081——virtual memory part——xv6 source code complete analysis series (2)

0.Briefly Spaeking

Click here to read the previous article of this series of blogs
. In the previous blog, we analyzed the virtual memory part of the xv6 kernel code in detail, mainly analyzing the three global variables and six functions in the vm.c file. this blogFollow the pace of the previous blog. Then analyze and read the following source code, the sameFirst list all the source code to read

1.kernel/memorylayout.h
2.kernel/vm.c(434 rows) <-----------(the code to be read in this blog)
3.kernel/kalloc.c
4.kernel/ exec.c
5.kernel/riscv.h

We have read a small part of the source code of vm.c last time, and this time we will read it. For the convenience of memory,The label here is also followed by the previous article.

1.kenrel/vm.c

1.8 walkaddr function

The walkaddr function isA layer of encapsulation of the walk function, specially used to find the user page tableThe physical address corresponding to a specific virtual address va. So pay attention to two things about this function:

1. It is only used to look up the user page table
2. It returns the physical address, not just the PTE of the final layer like the walk function. Let's take a

look at the code with comments below:)

// Look up a virtual address, return the physical address,
// or 0 if not mapped.
// Can only be used to look up user pages.
uint64
walkaddr(pagetable_t pagetable, uint64 va)
{
    
    
  pte_t *pte;
  uint64 pa;
  
  // 如果虚拟地址大于最大虚拟地址,返回0
  // 物理地址为0的地方是未被使用的地址空间
  // Question: 为什么不像walk函数一样直接陷入panic?
  if(va >= MAXVA)
    return 0;
  
  // 调用walk函数,直接在用户页表中找到最低一级的PTE
  pte = walk(pagetable, va, 0);
  // 如果此PTE不存在,或者无效,或者用户无权访问
  // 都统统返回0(为什么不陷入panic?)
  if(pte == 0)
    return 0;
  if((*pte & PTE_V) == 0)
    return 0;
  if((*pte & PTE_U) == 0)
    return 0;
  
  // 从PTE中截取下来物理地址页号字段,直接返回
  pa = PTE2PA(*pte);
  return pa;
}

Finally, I tried to check who was calling this function and found that it wasThese three functions

1.copyin
2.copyout
3.copyinstr

They are the highlight in vm.c,Responsible for copying data between kernel mode and user modeYes, we'll take a closer look at those three functions and how theyUse the walkaddr function to implement address translation in the user page tableof.

1.9 freewalk function

Another walk function... what this function does isIt is specially used to reclaim the memory of the page table page, because the page table is a multi-level structure, so the implementation of this function uses recursion, as stated in the English comments on the source code, when calling this function, it should be guaranteedleaf level page tableThe mapping relationship is all released and released (this will be responsible for the uvmunmap function later), because this functionSpecially used to reclaim page table pages

The combination of uvmunmap function and freewalk function has successfully realizedFull release of page table pages and physical pages

The following is a schematic diagram:
insert image description here
the following isAnnotated source code

// Recursively free page-table pages.
// All leaf mappings must already have been removed.
// 译:递归地释放页表页,所有的叶级别页表映射关系必须已经被解除
void
freewalk(pagetable_t pagetable)
{
    
    
  // there are 2^9 = 512 PTEs in a page table.
  // 每一个页表都正好有512个页表项PTE,所以要遍历它们并尝试逐个释放
  for(int i = 0; i < 512; i++){
    
    
    // 取得对应的PTE
    pte_t pte = pagetable[i];
    // 注意,这里通过标志位的设置来判断是否到达了叶级页表
    // 如果有效位为1,且读位、写位、可执行位都是0
    // 说明这是一个高级别(非叶级)页表项,且此项未被释放,应该去递归地释放
    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);
      // 释放完毕之后,将原有的PTE全部清空,表示已经完全释放
      pagetable[i] = 0;
    // 如果有效位为1,且读位、写位、可执行位有一位为1
    // 表示这是一个叶级PTE,且未经释放,这不符合本函数调用条件,会陷入一个panic
    } else if(pte & PTE_V){
    
    
      panic("freewalk: leaf");
    }
    // 这里隐藏了一个逻辑,即if(pte & PTE_V == 0)
    // 这说明当前PTE已经被释放,不用再次释放了,直接遍历下一个PTE
  }
  // 最后释放页表本身占用的内存,回收,回到上一层递归
  kfree((void*)pagetable);
}

Draw a dividing line, and next we're going to seeFunctions used to manipulate the user address space, these functions all start with uvm. Let's start one by one from the very beginning!

1.10 uvmcreate function

The function of this function is very simple, it is for the user processAllocate a page table page and return a pointer to this page

// create an empty user page table.
// returns 0 if out of memory.
// 译:创建一个空的用户页表,当内存耗尽时返回空指针
pagetable_t
uvmcreate()
{
    
    
  pagetable_t pagetable;
  // 分配一个内存页
  pagetable = (pagetable_t) kalloc();
  if(pagetable == 0)
    return 0;
  // 将此页表的每一个PTE完全清空
  memset(pagetable, 0, PGSIZE);
  return pagetable;
}

1.10 uvminit function

The function of this function is to load the initcode intoAddress 0 of the user page tableAbove, initcode is some code needed to start the first process. The function of this function is to map the initcode into the user address space. First give the design of the user address space in xv6, you can seefrom virtual address 0The code segment of the process is initially stored. For the first process started by the operating system, this locationPlaced is the initcode code
insert image description here

// Load the user initcode into address 0 of pagetable,
// for the very first process.
// sz must be less than a page.
// 译:将用户的initcode加载到页表的0地址
// 仅为第一个进程而服务
// 代码的尺寸必须小于一个页(4096 bytes)
void
uvminit(pagetable_t pagetable, uchar *src, uint sz)
{
    
    
  // mem虽然是一个指针,但是因为内核地址空间中虚拟地址和物理地址
  // 在RAM上是直接映射的,所以它其实也就等于物理地址
  char *mem;
  
  // 如果要求分配的大小大于一个页面,则陷入panic
  if(sz >= PGSIZE)
    panic("inituvm: more than a page");
  // 分配一页物理内存作为initcode的存放处,memset用来将当前页清空
  mem = kalloc();
  memset(mem, 0, PGSIZE);
  
  // 在页表中加入一条虚拟地址0 <-> mem的映射,相当于将initcode成功映射到了虚拟地址0
  mappages(pagetable, 0, PGSIZE, (uint64)mem, PTE_W|PTE_R|PTE_X|PTE_U);
  // 将initcode的代码一个字节一个字节地搬运到mem地址
  memmove(mem, src, sz);
}

In this functionThe memmove function is called, it is defined in kernel/string.c, and the function implemented isCopy n bytes from src address to dst address, and returnspointer to destination address. After reading it carefully, I found that this code is really wonderful. It implements the entire process of copying strings concisely and elegantly. Let’s take a quick look at this code.

void*
memmove(void *dst, const void *src, uint n)
{
    
    
 // 声明两个活动的指针,用来实时更新拷贝的过程
  const char *s;
  char *d;
  
  // 如果n等于0,说明不需要拷贝字符,直接返回dst
  if(n == 0)
    return dst;
  
  s = src;
  d = dst;
  // case1:src与dst字符串部分重叠时,倒序对字节进行复制,这样可以避免覆盖问题
  if(s < d && s + n > d){
    
    
    // 调整指针到字符串尾部,准备开始倒序复制
    s += n;	
    d += n;
    while(n-- > 0)
      *--d = *--s;
  } else
    // 否则直接正序复制即可
    while(n-- > 0)
      *d++ = *s++;

  return dst;
}

The most important thing is to classify and discuss, just to analyzePotential Src and Dst address overlap issues, when overlapping, reverse copy (copy from the end point to the start point), otherwise forward copy (copy from the start point to the end point), this is the implementation of the memmove function, which is very rigorous and ingenious.
insert image description here

1.11 uvmunmap function

What this function does isCancel the mapping relationship of the specified range in the user process page table, release npages pages starting from the virtual address va, but pay attentionva must be page-aligned. (Here is an extended thinking about a question, why in the memory management code, some codes require the incoming memory address to be aligned, while some do not?)

In fact,The uvmunmap function and the freewalk function are used in combination, we still remember when we looked at the freewalk function earlierIt is responsible for releasing the page table page, then the uvmunmap here is responsible for releasing the PTE records in the leaf-level page tableMapping relations, in particular, if the flag do_free is set, this function alsoAt the same time, the allocated physical pages are also reclaimed

// Remove npages of mappings starting from va. va must be
// page-aligned. The mappings must exist.
// Optionally free the physical memory.
// 译:从虚拟地址va开始移除npages个页面的映射关系
// va必须是页对齐的,映射必须存在
// 释放物理内存是可选的
void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
    
    
  uint64 a;
  pte_t *pte;
  
  // va不是页对齐的,陷入panic  
  if((va % PGSIZE) != 0)
    panic("uvmunmap: not aligned");
   // 通过遍历释放npages * PGSIZE大小的内存
  for(a = va; a < va + npages*PGSIZE; a += PGSIZE){
    
    
    // 如果虚拟地址在索引过程中对应的中间页表页不存在,陷入panic
    // 回顾一下,walk函数返回0,只有一种情况,那就是某一级页表页在查询时发现不存在
    if((pte = walk(pagetable, a, 0)) == 0)
      panic("uvmunmap: walk");
    
    // 查找成功,但发现此PTE不存在,陷入panic
    if((*pte & PTE_V) == 0)
      panic("uvmunmap: not mapped");
    
    // 查找成功,但发现此PTE除了valid位有效外,其他位均为0
    // 这暗示这个PTE原本不应该出现在叶级页表(奇怪的错误),陷入panic
    if(PTE_FLAGS(*pte) == PTE_V)
      panic("uvmunmap: not a leaf");
    
    // 否则这是一个合法的,应该被释放的PTE
    // 如果do_free被置位,那么还要释放掉PTE对应的物理内存
    if(do_free){
    
    
      uint64 pa = PTE2PA(*pte);
      kfree((void*)pa);
    }
    // 最后将PTE本身全部清空,成功解除了映射关系
    *pte = 0;
  }
}

A macro PTE_FLAGS is used in this code, which is directly used forExtract all flags of a PTE, defined as follows:

// 提取出PTE的所有标记位
// 0x3ff相当于保留低10位
#define PTE_FLAGS(pte) ((pte) & 0x3FF)

1.12 uvmdealloc function

This function is used toRecycle pages in the user page table, change the size of the allocated space in the user process fromoldsz modified to newsz,andReturns the size of the new address space, it is worth noting that oldsz is not necessarily greater than newsz, that is to say, this functionDoes not necessarily lead to shrinking of the user address space

In fact, this function is directly related to the sbrk system call, which is used to reclaim redundant process heap memory space, see growproc function (kernel/proc.c) for details.

// Deallocate user pages to bring the process size from oldsz to
// newsz.  oldsz and newsz need not be page-aligned, nor does newsz
// need to be less than oldsz.  oldsz can be larger than the actual
// process size.  Returns the new process size.
// 译:回收用户页,使得进程的内存大小从oldsz变为newsz。oldsz和newsz不一定要是
// 页对齐的,newsz也不一定要大于oldsz。oldsz可以比当前实际所占用的内存大小更大。
// 函数返回进程新占用的内存大小
uint64
uvmdealloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz)
{
    
    
  // 如果新的内存大小比原先内存还要大,那么什么也不用做,直接返回oldsz即可
  if(newsz >= oldsz)
    return oldsz;
  
  // 如果newsz经过圆整后占据的页面数小于oldsz
  // PGROUNDUP宏定义的讲解见上一篇博客
  if(PGROUNDUP(newsz) < PGROUNDUP(oldsz)){
    
    
    // 计算出来要释放的页面数量
    int npages = (PGROUNDUP(oldsz) - PGROUNDUP(newsz)) / PGSIZE;
    // 调用uvmunmap,清空叶级页表的PTE并释放物理内存
    // 因为我们使用了PGROUNDUP来取整页面数量,所以这里可以保证va是页对齐的
    // 因为用户地址空间是从地址0开始紧密排布的, 所以PGROUNDUP(newsz)对应着新内存大小的结束位置
    // 注意do_free置为1,表示一并回收物理内存
    uvmunmap(pagetable, PGROUNDUP(newsz), npages, 1);
  }

  return newsz;
}

1.13 uvmalloc function

There is uvmdealloc function toReclaim process memory, correspondingly, there will be a function for the user processRequest more memory from the kernel, this is the role of the uvmalloc function, which is the same as the uvmdealloc functionCorresponding sister function. Let's take a look directly at its source code implementation:

// 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.
// 译:分配PTE和物理内存来将分配给用户的内存大小从oldsz提升到newsz
// oldsz和newsz不必是页对齐的
// 成功时返回新的内存大小,出错时返回0
uint64
uvmalloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz)
{
    
    
  
  char *mem;
  uint64 a;
  
  // 如果新的内存大小更小,不用分配,直接返回旧内存大小
  if(newsz < oldsz)
    return oldsz;
  
  // 计算原先内存大小需要至少多少页,因为进程地址空间紧密排列
  // 所以这里oldsz指向的其实是原先已经使用内存的下一页,崭新的一页
  oldsz = PGROUNDUP(oldsz);
  // 开始进行新内存的分配
  for(a = oldsz; a < newsz; a += PGSIZE){
    
    
    // 获取一页新的内存
    mem = kalloc();
    // 如果mem为空指针,表示内存耗尽
    // 释放之前分配的所有内存,返回0表示出错
    if(mem == 0){
    
    
      uvmdealloc(pagetable, a, oldsz);
      return 0;
    }
    // 如果分配成功,则将新分配的页面全部清空
    memset(mem, 0, PGSIZE);
	// 并在当前页表项中建立起来到新分配页表的映射
	// mappages函数的讲解见完全解析系列博客(1)
    if(mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0){
    
    
      // 如果mappages函数调用返回值不为0,表明在调用walk函数时索引到的PTE无效
      // 释放之前分配的所有内存,返回0表示出错
      kfree(mem);
      uvmdealloc(pagetable, a, oldsz);
      return 0;
    }
  }
  // 如果成功跳出循环,表示执行成功,返回新的内存空间大小
  return newsz;
}

1.14 uvmcopy function

The uvmcopy function is forfork system callservice, it will be the entire address space of the parent processcopy all to child process, which includespage table itselfandData in physical memory pointed to by the page table
insert image description here

Here is the full source code:

// Given a parent process's page table, copy
// its memory into a child's page table.
// Copies both the page table and the
// physical memory.
// returns 0 on success, -1 on failure.
// frees any allocated pages on failure.
// 译:给定一个父进程页表,将其内存拷贝到子进程页表中
// 同时拷贝页表和对应的物理内存
// 返回0表示成功,-1表示失败
// 失败时会释放所有已经分配的内存
int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
    
    
  pte_t *pte;
  uint64 pa, i;
  uint flags;
  char *mem;
  
  // sz指要复制的地址空间大小,被调用时传入p->sz,表示整个地址空间
  // 对整个地址空间逐页复制
  for(i = 0; i < sz; i += PGSIZE){
    
    
    // 如果寻找过程中发现有PTE不存在,陷入panic
    if((pte = walk(old, i, 0)) == 0)
      panic("uvmcopy: pte should exist");
    // PTE存在但是对应页未被使用,陷入panic
    // 再次强调,用户空间的内存使用是严格紧密的,中间不会有未使用的页存在
    // 自下而上:text、data、guard page、stack、heap
    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;
    }
  }
  // 成功时返回0
  return 0;
 // 错误处理程序,解除所有已经分配的映射关系,并释放对应的物理内存,返回-1
 err:
  uvmunmap(new, 0, i / PGSIZE, 1);
  return -1;
}

1.15 uvmclear function

The uvmclear function is specially used to clear a PTEUser access rights, for the exec function to set the guard page. The implementation is very simple, as follows:

// mark a PTE invalid for user access.
// used by exec for the user stack guard page.
// 译:将一个PTE标记为用户不可访问的
// 用在exec函数中来进行用户栈守护页的设置
void
uvmclear(pagetable_t pagetable, uint64 va)
{
    
    
  pte_t *pte;
  // 使用walk函数找到对应的PTE
  pte = walk(pagetable, va, 0);
  // 如果找不到va对应的PTE就陷入panic
  if(pte == 0)
    panic("uvmclear");
  // 否则将对应PTE的User位清空
  *pte &= ~PTE_U;
}

1.16 uvmfree function

Remember what we said before that uvmunmap is used forCancel the mapping relationship of the leaf-level page table,freewalkUsed to free page table pages, can the combination of the two completely release the memory space. The uvmfree function is a simple combination and encapsulation of the two, used forFree up the user's address space completely

// Free user memory pages,
// then free page-table pages.
// 译:释放用户内存页
// 然后释放页表页
void
uvmfree(pagetable_t pagetable, uint64 sz)
{
    
    
  // 如果用户内存空间大小大于0,首先调用uvmunmap完全释放所有的叶级页表映射关系和物理页
  if(sz > 0)
    uvmunmap(pagetable, 0, PGROUNDUP(sz)/PGSIZE, 1);
  // 然后再释放页表页
  freewalk(pagetable);
}

At this point, we will completely use theAll the functions (uvm*) that manipulate user mode have been thoroughly studied, you can breathe a sigh of relief. Then there are three functions copyin, copyout and copyinstr left, these three functions are dedicated toData transfer between kernel mode and user mode

This article is not short, let’s write it in the next one...

Click here to jump to the next blog

Guess you like

Origin blog.csdn.net/zzy980511/article/details/129912497