内存管理
内存管理有两个部分,
1)物理内存管理:主要是内核要有一个物理内存分配器,内核可以分配内存并且在后面不用了释放它。
2)虚拟内存管理:它负责内核和用户软件使用的虚拟地址映射到物理内存。X86硬件内存管理单元(MMU)通过咨询一系列页表来执行这个映射when指令使用内存时。
物理内存管理
boot_alloc()
这个函数仅仅只使用在初始化期间,在page_free_list设置之前。
static void *
boot_alloc(uint32_t n)
{
static char *nextfree; // virtual address of next byte of free memory
char *result;
// 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, 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.
result=nextfree;
nextfree=ROUNDUP((char*)result+n,PGSIZE);
cprintf("boot_alloc memory at%x,next alloc at%x\n",result,nextfree);
return result;
}
在下面这行代码运行之前我们使用的是pgdir是在计算机启动那篇文章最后那部分提到的entry_pgdir;那个是我们静态初始化了一个页目录,那个目录我们只映射了4MB内存;
kern_pgdir = (pde_t *) boot_alloc(PGSIZE);
分配4096个字节的内存,有1024条目录项,用来作为一个完整的内核页目录,这个页目录作为一个页表插入对于的页目录slot位置,
kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;
用boot_alloc继续分配 64*8=512byte ,用来抽象物理页,进行管理。(因为我们物理内存大小这里模拟的是256MB,所以有64个页)
pages=(struct PageInfo*)boot_alloc(sizeof(struct PageInfo)*npages) ;
void
page_init(void)
我们使用page_init()初始化我们上面分配的pageInfo数组。
因为每个pageInfo对应一个物理页,在前一篇文章中我们页提到过 物理地址空间存在一个hole,所以
1)初始化的时候要将hole对应的PageInfo标注为已用,
2)最开始的一页我们已经在用了,也要标记为已用,
其他的页我们通过循环加入到空闲链表中
最后我们还会提供两个函数,`
struct PageInfo *
page_alloc(int alloc_flags)
void
page_free(struct PageInfo *pp)
void
page_decref(struct PageInfo* pp)
前两个函数用来从空闲链表中申请,释放PageInfo*;后面一个就是用来减少对应物理页的引用数。
这就是整个物理内存管理了
虚拟内存管理
虚拟内存管理就是对页表进行管理,硬件MMU通过页表实现了虚拟内存到物理内存的映射。
pte_t *
pgdir_walk(pde_t *pgdir, const void *va, int create)
给一个页目录指针,返回一个指针指向页表entry的虚拟地址
pte_t *
pgdir_walk(pde_t *pgdir, const void *va, int create)
{
// Fill this function in
pde_t*pde_ptr=pgdir+PDX(va); //*pde_ptr一个页表的起始地址
if(!(*pde_ptr&PTE_P)){
if(create){
struct PageInfo*pp=page_alloc(ALLOC_ZERO);
if(pp==NULL){
return NULL;
}
pp->pp_ref++;
*pde_ptr=(page2pa(pp))|PTE_P|PTE_U|PTE_W;
}
else{
return NULL;
}
}
return (pte_t*)KADDR(PTE_ADDR(*pde_ptr))+PTX(va);
}
1)先在页目录中找到对应的entry,取出页目录中的值,看二级页表是否存在,
2)如果存在,(注意,页目录和页表中存储的都是物理地址)
提出去出物理地址后,将其转换为虚拟地址,将虚拟地址转换成pte_t*类型,加上在页表上的偏移量,得到了va所在的pte_t的虚拟地址。
3)不存在,根据creat 标志确定释放为其创建二级页表
static void
boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
映射虚拟地址[va,va+size),到物理地址[pa,pa+size)
static void
boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
{
// Fill this function in
size_t pgs=size/PGSIZE;
if(size%PGSIZE!=0){
pgs++;
}
for(size_t i=0;i<pgs;i++){
pte_t *pte=pgdir_walk(pgdir,(void*)va,1);
if(pte==NULL){
panic("boot_map_region():out of memory");
}
*pte=pa|PTE_P|perm;
va+=PGSIZE;
pa+=PGSIZE;
}
}
简单来说就是把物理地址填充进入对应一级页目录和二级页表。
page_lookup()
page_remove()
page_insert()
根据pgdir和va返回对应物理页的PageInfo*;
struct PageInfo *
page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
{
// Fill this function in
pde_t *pte=pgdir_walk(pgdir,(void*)va,0);
if(pte==NULL)
{
return NULL;
}
if(!(*pte)&PTE_P){
return NULL;
}
if(pte_store){
*pte_store=pte;
}
return pa2page(PTE_ADDR(*pte));
}
将一个物理页pp映射到va处,va处原来有直接移除
int
page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm)
{
// Fill this function in
pte_t *pte=pgdir_walk(pgdir,(void*)va,1);//如果va对应页表还没分配,则创建一个页表,并且将对应页目录项设置为页表地址|权限
if(!pte){
return -E_NO_MEM;
}
pp->pp_ref++;
if((*pte)&PTE_P){
page_remove(pgdir,va);
}
physaddr_t pa=page2pa(pp);
*pte=pa|perm|PTE_P;
pgdir[PDX(va)] |=perm;
return 0;
}
移除va处的物理页,并且将页表条目设置为0;
void
page_remove(pde_t *pgdir, void *va)
{
// Fill this function in
pde_t*pte_store;
struct PageInfo*pp=page_lookup(pgdir,va,&pte_store);
if(pp==NULL){
return;
}
page_decref(pp);
*pte_store=0;
tlb_invalidate(pgdir,va);
}