系列文章目录
文章目录
- 系列文章目录
- 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内存。