系列文章目录
文章目录
- 系列文章目录
- xv6 中的内存页是如何分配的
- RISC-V 是多级页表
- 对page table的理解
xv6 中的内存页是如何分配的
在本课中,内存也相关源码路径为:
 kernel/kallo.c
// Physical memory allocator, for user processes,
// kernel stacks, page-table pages,
// and pipe buffers. Allocates whole 4096-byte pages.
#include "types.h"
#include "param.h"
#include "memlayout.h"
#include "spinlock.h"
#include "riscv.h"
#include "defs.h"
void freerange(void *pa_start, void *pa_end);
extern char end[]; // first address after kernel.
                   // defined by kernel.ld.
struct run {
  struct run *next;
};
struct {
  struct spinlock lock;
  struct run *freelist;
} kmem;
void
kinit()
{
  initlock(&kmem.lock, "kmem");
  freerange(end, (void*)PHYSTOP);
}
void
freerange(void *pa_start, void *pa_end)
{
  char *p;
  p = (char*)PGROUNDUP((uint64)pa_start);
  for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE)
    kfree(p);
}
// Free the page of physical memory pointed at by pa,
// which normally should have been returned by a
// call to kalloc().  (The exception is when
// initializing the allocator; see kinit above.)
void
kfree(void *pa)
{
  struct run *r;
  if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
    panic("kfree");
  // Fill with junk to catch dangling refs.
  memset(pa, 1, PGSIZE);
  r = (struct run*)pa;
  acquire(&kmem.lock);
  r->next = kmem.freelist;
  kmem.freelist = r;
  release(&kmem.lock);
}
uint64
kfreemem(void)
{
  struct run *r;
  uint64 count = 0;
  acquire(&kmem.lock);
  r = kmem.freelist;
  while(r) {
    r = r->next;
    count++;
  }
  release(&kmem.lock);
  return count * PGSIZE;
}
// Allocate one 4096-byte page of physical memory.
// Returns a pointer that the kernel can use.
// Returns 0 if the memory cannot be allocated.
void *
kalloc(void)
{
  struct run *r;
  acquire(&kmem.lock);
  r = kmem.freelist;
  if(r)
    kmem.freelist = r->next;
  release(&kmem.lock);
  if(r)
    memset((char*)r, 5, PGSIZE); // fill with junk
  return (void*)r;
}
如何在一个物理内存上,创建不通的地址空间?
 如上所述:
 最常见的就是分配页,页表(pagetables) – 需要硬件支持或者通过内存管理单元实现(MMU – Memory Management Unit)

 上图展示了如下步骤:
- CPU 执行指令,指令中的地址是虚拟地址 0x1000
- 通过 MMU 转换为物理地址 
  - MMU 中维护一张表,用来进行虚拟地址和物理地址的映射关系
- 通常来说,内存地址对应关系这张表单也保存在内存中,所以CPU中需要一些寄存器(SATP)来存放表单在物理内存中的地址,用来告诉 MMU 去哪里找这份表单
- 每个应用程序都有自己的表单,这个表单定义了它的地址空间,当操作系统将CPU从一个应用程序切换到另一个应用程序时,同时也需要切换SATP寄存器中的内容,从而指向新的进程保存在物理内存中的地址对应表单。
 
- 通过真实物理地址进行数据的存取读
页表存的是物理地址
 通过虚拟地址找到对应的表单和偏移。)
RISC-V 是多级页表
(多级页表,某种意义上也代表着会读多次内存)
 
对page table的理解

 
 
在操作系统,对于物理内存来说,每一个地址间隔所代表的的物理存储单位为 1B(一个字节,这里先不要考虑钻进64位本身占多少的错误思维(我自己之前就是));
 所以一个64位操作系统,不考虑其他因素,CPU的可寻址大小最大为 2^64 Byte(32位是4G,下面的例子拿32位距离比较好理解)
 将物理内存按照块或页分配,概念上理解成每 4KB 一页。即 2^12,即32位地址中,低的那12位可以用来表示对 page table 某页的 Offset, 即偏移。这里存在如下计算:
- 一个32位的地址占用 32 / 8 = 4 B的空间,为了方便所以使用了 4B 作为页表项,这样一个页表就可以有 4KB / 4B = 1K 页表项(单位是页标项)
- 其他资料喜欢以1G做例,这里使用最大场景做例子: 一共可以分配出 2^64 / 2^12 = 2^52页
- 这样对最基础的页表来说,就可以通过将 低位12位作为 offset 来根据虚拟地址求得真实物理地址。
- 问题:事实上,页表本身也是需要用物理内存来进行存储的,如果一个应用程序占用内存过大,其所需要生成或使用的页表本身也会占用非常大的内存空间,这样明显过于浪费资源;假如一个应用程序占用4GB内存,那其所需要的的页表就占用 4GB / 4KB * 4B == 4MB, 总内存就是 4G + 4MB;而如果是二级页表:
 4MB的页表还可以继续分配成4MB / 4KB == 1K 页-->1K页 * 4B == 1KB, 占用 4G + 1KB
相关笔记:顶级页表最多只能有一个页面。(这是规定)
一个页面可以存放1K个页表项4KB/4B = 1K。
所以顶级页表虽然只有一个页面但是可以存放1K个页表项,其中每一个页表项对应的是下一级的1K个页表项。所以可以存放的最大空间是1K* 1K *4KB = 4GB内存。



















