XV6学习笔记(2) :内存管理
XV6学习笔记(2) :内存管理
在学习笔记1中,完成了对于pc启动和加载的过程。目前已经可以开始在c语言代码中运行了,而当前已经开启了分页模式,不过是两个4mb的大的内存页,而没有开启小的内存页。接下来就可以从main.c
的init函数开始
这里会和JOS做一个对比
首先看一下在执行main.c之前的物理内存分布
0x0000-0x7c00 引导程序的栈 0x7c00-0x7d00 引导程序的代码(512字节) 0x10000-0x11000 内核ELF文件头(4096字节) 0xA0000-0x100000 设备区 0x100000-0x400000 Xv6操作系统(未用满)
1. Kinit1函数
1.1 xv6中的kinit1函数
int
main(void)
{
kinit1(end, P2V(4*1024*1024)); // phys page allocator
kvmalloc(); // kernel page table
//....
}
这是main函数的开始。所以我们先从kinit1
开始
这里的end地址就是kernel从0x80100000
开始。然后是内核的代码段 + 只读数据段+ stab段+ stabstr + 数据段 + .bss段这些之后的起始地址。如下图所示。
void
kinit()
{
initlock(&kmem.lock, "kmem");
freerange(end, (void*)PHYSTOP);
}
- 这里的vstart就是end的地址而vend是
KERNBASE + 128MB
=0x86400000
- 这里就是把[vstart, 0x86400000]的内存按页(4kb大小)进行free
kree
这里会把他插入到freelist中
void
freerange(void *vstart, void *vend)
{
char *p;
p = (char*)PGROUNDUP((uint)vstart);
for(; p + PGSIZE <= (char*)vend; p += PGSIZE)
kfree(p);
}
//PAGEBREAK: 21
// Free the page of physical memory pointed at by v,
// 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);
}
好了这里就可以完成整个free操作了
1.2 jos的boot_alloc函数
和上面的是非常类似的
- 当第一次执行的时候nextfree是空这里会进行第一次分配。这里的起始地址也是
end
的地址 - 然后返回这一段分配的地址,并更新nextfree
static void *
boot_alloc(uint32_t n)
{
static char *nextfree; // virtual address of next byte of free memory
char *result = NULL;
// Initialize nextfree if this is the first time.
// 'end' is a magic symbol automatically generated by the linker,
// which points to the end of the kernel's bss segment:
// the first virtual address that the linker did *not* assign
// to any kernel code or global variables.
if (!nextfree) {
extern char end[];
nextfree = ROUNDUP((char *) end + 1, PGSIZE);
}
// Allocate a chunk large enough to hold 'n' bytes, then update
// nextfree. Make sure nextfree is kept aligned
// to a multiple of PGSIZE.
//
// LAB 2: Your code here.
if (n > 0) {
result = nextfree;
nextfree = ROUNDUP(nextfree + n, PGSIZE);
} else if (n == 0) {
result = ROUNDUP(nextfree, PGSIZE);
} else {
panic("boot_alloc(n): n < 0\n");
}
cprintf("boot_alloc(): nextfree=%08x\n", nextfree);
if ((uintptr_t) nextfree >= KERNBASE + PTSIZE) {
panic("boot_alloc(): out of memory\n");
}
return result;
}
2. kvminit
// Initialize the one kernel_pagetable
void
kvminit(void)
{
kernel_pagetable = kvmmake();
}
这里我们先看一下kvmmake()
/ Make a direct-map page table for the kernel.
pagetable_t
kvmmake(void)
{
pagetable_t kpgtbl;
kpgtbl = (pagetable_t) kalloc();
memset(kpgtbl, 0, PGSIZE);
// uart registers
kvmmap(kpgtbl, UART0, UART0, PGSIZE, PTE_R | PTE_W);
// virtio mmio disk interface
kvmmap(kpgtbl, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);
// PLIC
kvmmap(kpgtbl, PLIC, PLIC, 0x400000, PTE_R | PTE_W);
// map kernel text executable and read-only.
kvmmap(kpgtbl, KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);
// map kernel data and the physical RAM we'll make use of.
kvmmap(kpgtbl, (uint64)etext, (uint64)etext, PHYSTOP-(uint64)etext, PTE_R | PTE_W);
// map the trampoline for trap entry/exit to
// the highest virtual address in the kernel.
kvmmap(kpgtbl, TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);
// map kernel stacks
proc_mapstacks(kpgtbl);
return kpgtbl;
}
当然这里引出了很多函数
1. kalloc函数
从我们的空闲队列中获得一个指针。返回
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;
}
2. kvmmap:
- add a mapping to the kernel page table.
- only used when booting
- does not flush TLB or enable paging.
void
kvmmap(pagetable_t kpgtbl, uint64 va, uint64 pa, uint64 sz, int perm)
{
if(mappages(kpgtbl, va, sz, pa, perm) != 0)
panic("kvmmap");
}
3. mappages函数
- 给定虚拟地址va和物理地址pa
- 把[va , va + size]和 [pa, pa + size]进行映射。并且以perm位
static int
mappages(pde_t *pgdir, void *va, uint size, uint pa, int perm)
{
char *a, *last;
pte_t *pte;
a = (char*)PGROUNDDOWN((uint)va);
last = (char*)PGROUNDDOWN(((uint)va) + size - 1);
for(;;){
if((pte = walkpgdir(pgdir, a, 1)) == 0)
return -1;
if(*pte & PTE_P)
panic("remap");
*pte = pa | perm | PTE_P;
if(a == last)
break;
a += PGSIZE;
pa += PGSIZE;
}
return 0;
}
4.walk函数
这里关于页的操作要补充一些关于页表操作的知识
在risc-v的xv6中利用39位的虚拟地址。整个原则如下
// The risc-v Sv39 scheme has three levels of page-table
// pages. A page-table page contains 512 64-bit PTEs.
// A 64-bit virtual address is split into five fields:
// 39..63 -- must be zero.
// 30..38 -- 9 bits of level-2 index.
// 21..29 -- 9 bits of level-1 index.
// 12..20 -- 9 bits of level-0 index.
// 0..11 -- 12 bits of byte offset within the page.
// extract the three 9-bit page table indices from a virtual address.
#define PGSHIFT 12 // bits of offset within a page
#define PXMASK 0x1FF // 9 bits
#define PXSHIFT(level) (PGSHIFT+(9*(level)))
#define PX(level, va) ((((uint64) (va)) >> PXSHIFT(level)) & PXMASK)
#define PTE2PA(pte) (((pte) >> 10) << 12)
-
这里的操作用到了上面的
PX
函数#假设 level = 2 va >> (12 + 9 * 2) & 0x1FF (va >> 30) & 0x1FF |EXT|L2| & 0x0001|1111|1111| # 得到的就是L2的地址
-
这里就是获取指定虚拟地址的pte也就是最后一层的
PPN
pte_t *
walk(pagetable_t pagetable, uint64 va, int alloc)
{
if(va >= MAXVA)
panic("walk");
for(int level = 2; level > 0; level--) {
pte_t *pte = &pagetable[PX(level, va)];
if(*pte & PTE_V) {
pagetable = (pagetable_t)PTE2PA(*pte);
} else {
if(!alloc || (pagetable = (pde_t*)kalloc()) == 0)
return 0;
memset(pagetable, 0, PGSIZE);
*pte = PA2PTE(pagetable) | PTE_V;
}
}
return &pagetable[PX(0, va)];
}
整个映射完的图如下
3. kvminithart()函数
- 第一行设置了
satp
这样硬件就可以找到pgdir的地址了
// Switch h/w page table register to the kernel's page table,
// and enable paging.
void
kvminithart()
{
w_satp(MAKE_SATP(kernel_pagetable));
sfence_vma();
}
刷新当前cpu的tlb
// flush the TLB.
static inline void
sfence_vma()
{
// the zero, zero means flush all TLB entries.
asm volatile("sfence.vma zero, zero");
}