您的位置:首页 > 其它

内存页面管理

2015-11-25 00:46 399 查看
本地找不到对应证书,所以可以通过关掉验证来解决这一问题,就是在git 命令前面加上:

env GIT_SSL_NO_VERIFY=true

所以完整的命令是这样:

env GIT_SSL_NO_VERIFY=true git pull

虽然在很多操作系统书讲到页式地址管理的时候总会说页面大小是可以根据系统的安排而调整的,一般是1KB的整数倍,如1KB,2KB,4KB或者8KB等。但是,在32位的i386上,页面的大小是固定的4KB!当然,在Extended Paging模式下(在Pentium以后的处理器中进行了支持),为了寻址更大的空间,页面的大小甚至可以为4MB,虽然这种模式在本实验以及后续实验中应该不会碰到。

如果一个页面管理机制只管理图4-1中的空闲物理内存空间的话,已经存在在内存中的内核代码就无法通过 页式地址转换来进行 逻辑地址→物理地址的转换了,这种情况下,内核代码的运行就成了问题!所以,在页面管理上,系统必须管理所有的物理内存空间。



需要对图4-1中不同的物理内存区域进行区别对待:对于已经存在在内核中的一些实模式数据(如Real Mode IDT)和系统区域(如BIOS数据,显存区域等),系统不能把它们当作空闲内存分配出去给其他进程使用;而对于内核本身所在的区域,当然也不能把它当做空闲区分配出去给其他进程使用,而且同时,还需要建立起合适的映射关系,达到将逻辑地址转换到物理地址的目标(始终记住:内核程序的逻辑地址是从KERNBASE=0xF0000000开始的!

应该拿物理内存的大小去整除4KB!简单的说,页面的数目可以用以下等式表达: 页面的数目(npage) = 物理内存的大小/4K>>12



1)页面管理链表的结构

现在我们来讨论用于页面管理的双向链表结构。首先,我们来看一下构成这个链表的结点的情况。该结点的结构是在memlayout.h中规定

大概是



该结构存在3个成员:指向链表下一个结点的指针、一个指向指针的指针以及一个short int类型的pp_ref,注意,short int类型在Bochs模拟出来的x86平台上仍然是占32位,所以整个结构一共会占用12字节

2)页面管理链表在内存中的存储和放置

启动页式地址管理一上来就是对于物理页面的管理问题,所以,页面管理链表要在这个启动页式地址管理以前完成。在i386_vm_init(void)函数中,是通boot_alloc函数为页目录(Page directory)分配4KB的空间的,

(pgdir = boot_alloc(PGSIZE, PGSIZE);)所以,我们也可以用这个函数为页面管理链表中的管理结点分配空间

这个空间的大小应该等于物理页面的数目×页面管理链表的结点大小(12B)。同时,为了方便管理,这个空间应该占用整数个4KB的页面。 至于这个空间放置在什么位置,就要看boot_alloc的实现了

static void *
boot_alloc(uint32_t n)
{
static char *nextfree;  // virtual address of next byte of free memory
char *result;
if (!nextfree) {
extern char end[];
nextfree = ROUNDUP((char *) end, PGSIZE);
}
//补充代码
result=nextfree;
nextfree+=ROUNDUP(n,PGSIZE);
return result;

}
//在调用这个函数的时候,传入的参数为:  n = npages* sizeof(struct Page);align = PGSIZE (4KB);align参数在实验中省去,PGSIZE默认4KB


系统中定义了一个static char* 类型的全局变量boot_freemem,我们看到该函数的流程是先让这个全局变量(实际上就是一个地址了)等于内核装入后的最末尾的位置,然后将他向高地址)移到PGSIZE(这里是4KB)对齐的位置,并从这里分配空间,分配完了以后,boot_freemem继续望上移,并将前一个PGSIZE对齐的位置返回回去。所以,我们的页面管理链表实际上存储在紧接着内核以及页目录的内存的“相对高端”的地方,而且该空间的大小是随着系统物理内存的大小而变化的,系统物理内存越大,这个空间占用的地方也就越多。

上面是参考资料里的

注意我们做的稍有不同

首先将end符号向上和4K字节对齐(JOS已经帮我们完成),然后将这个地址(也就是nextfree)加上你要分配的空间并依然4K字节对齐,接着返回原先的nextfree即可。

这个空间的位置如图4-4所示



pages指向的是有很多个Page结构(页面管理链表的结点)的连续内存区域。而这些结构与实际的物理页面是一一对应的,所以可以用下标的方法,唯一地表示一个物理页!例如,一个拥有2MB的计算机,它一共有2MB/4KB=512个物理页面,那么pages[100]就引用的是物理内存0x64000到0x65000的物理页面所对应的页面管理链表结点!系统还会通过之前定义的工具宏来将pages指向的这一组页面管理链表结点组织成前文双向链表。这就意味着,对于页面管理和组织,至少有2种办法:其1是通过pages[下标],另一种是通过双向链表。

对于前一种方法,好处是直观和确定性,而对于后一种方法,其好处是灵活,同时可以通过pp_ref域知道该页面被使用或引用的情况

3)页面管理

在JOS系统中,页面操作主要申请页面的操作page_alloc()以及释放页面的操作page_free()还有进行页面系统初始化的系统调用

page_alloc()函数被调用后,将取页面管理双向链表中的第一个管理结点,并将该结点返回出去,得到页面管理结点后,一定要将它所对应的物理页面(对应的物理页面的首地址可以用page2kva()宏得到)清零。

本实验pmap.c中

boot_freemem和boot_pgdir,前者是当前可用内存的开始地址(也就是说,内核载入以后,系统管理所需要的内容就从end以后开始分配,即从一开始boot_ freemem是等于end,这个在接下来的代码里就能看到);boot_pgdir则是系统页目录所在空间的开始地址

i386vm_init()中

其中bootalloc()为其分配内存空间地址,然后将分配的地址段清空。然后将其物理地址(PADDR)放入boot_cr3准备启动x86的页面地址转换机制

•PGSIZE为一个物理页的大小4KB=4096B,定义在inc/mmu.h中,其中还有我们后面要用的重要常量PTSIZE,为一个页表对应实际物理内存的大1024*4KB=4MB从boot_alloc()得到的页面是不会做相应的初始化工作的

•memset接受的清空地址是pgdir即一个虚拟地址,这个在我们后面的工作中对实际分配到的物理页面进行初始化时提醒,清空时使用memset也一定要使用实际物理页面对应的内核虚拟地址

•bootcr3得到的是一个物理地址

然后在mem_init(void)中首先检查可用内存大小,然后调用boot_alloc()分配了一个页面给kern_pgdir。我们为pages分配空间

接着完成page_init()。

在page_ init() 里系统首初始化了pages数组以及 page_ free _ list,可以看到这个page_free_list指向了所有的Page结构,建立其每个物理页面对应的实际链表节点,我们要把那些被操作系统占用或是系统预留空间从链表里去除掉。其实只需要剔除两块地址,第一块是0地址开始的第一个页面,第二块就是io hole开始的向上的一组连续的页面。

extern char end[];
pages[1].pp_link=0;    //first page
struct Page* pgstart=pa2page((physaddr_t)IOPHYSMEM);
struct Page* pgend=pa2page((physaddr_t)(end-KERNBASE+PGSIZE+npages*sizeof(struct Page)));
pgend=pgend+1;
pgstart=pgstart-1;
cprintf("pgstart %x ,pgend %x \r\n",(int)pgstart,(int)pgend);
pgend->pp_link=pgstart;


page_alloc()。

从page_free_list头剔除一个Page,然后改变page_free_list使其为其pp_link即可

if(page_free_list==NULL)
{
return NULL;
}
struct Page* result=page_free_list;
page_free_list=page_free_list->pp_link;
if(alloc_flags & ALLOC_ZERO)
{
memset(page2kva(result),0,PGSIZE);
}

e19a
return result;


最后释放

void
page_free(struct Page *pp)
{
//cprintf("page_frees\r\n");
pp->pp_link=page_free_list;
page_free_list=pp;
// Fill this function in
}


页表管理

首先我们将地址分为三类:

逻辑地址(Virtual Address)、线性地址(Linear Address)和物理地址(Physical Address)。逻辑地址是指程序在编译连接后,变量名字等的符号地址,在JOS系统中的内核部分,该地址是以KERNBASE(默认等于0xF0000000,实际上可以根据具体的情况加以修改);

线性地址是指经过x86保护模式的段地址变换后的地址,该变换的过程是 逻辑地址+段首地址;

物理地址是指内存存储单元的编址,如1GB的内存,它的物理编址是从0x00000000到0x40000000。需要注意的是,在本文中,这三类地址的长度都是32位

1)线性地址的转换

启用了x86页式内存管理后,当处理器碰到一个线性地址后,它会把这个地址分成3部分:页目录索引(Directory)、页表索引(Table)和页内偏移(Offset),这3个部分把原本32位的线性地址分成了10位、10位和12位的3个片段。既然页内偏移地址占12位,页的大小就自然为4KB了



页目录项在图4-4中我们知道它占用了一个4KB的物理页面,实际上它在内部分成了1024个单元(10位有1024个可能的值),每个单元占4字节(32位,也就是保护模式下一个uint32_t类型所占的内存空间大小),它们称为页目录项(Page Directory Entry),这些单元与页表中包含的单元在格式上是一致的,不同的是页表中的单元称为页表项(Page Table Entry)



其中的高20位存储的是一个地址,但因为只有高20位(使用的时候低位会被全部清零),所以只能寻址4KB对齐的地址空间,同时,由于x86把所有物理内存分成了4KB大小的页,每个页的首地址必然是4KB对齐的!所以这个表项中的高20位地址能够定位到内存中任何一个物理页面的首地址。如果这个表项在页目录表中,那么这个地址寻址的就是页目录的下级,也就是页表所在物理页面的首地址,而如果这个表项在页表中,这个地址寻址的就是真正的数据页(Page Frame)的首地址。

剩下十位表示一些物理页面状态

页目录所在的物理页面的首地址在启动x86的页式地址管理前,需要放到CR3中,这样x86在进行页式地址转换时会自动地从CR3中取得页目录地址,从而找到当前的页目录,如果无法找到该页目录,地址转换就无从谈起了

通过线性地址的高10位,可以得到该线性地址在页目录中对应的表项,通过该页目录项中的地址(页目录表项的高20位)可以得到页表所在物理页的首地址,从而找到页表;然后,在通过线性地址的中间10位取得该地址在页表中的位置,从而得到页表项。最后,从找到的页表项所存储的地址(高20位)定位数据页(Page Frame),并将该数据页首地址加上线性地址中的页内偏移,从而得到物理地址

即4-6过程

具体有如下图



2)页目录、页表和数据页的关联

下面是对一些关于页面表操作函数和宏进行说明。

define PGOFF(la) (((uintptr_t) (la)) & 0xFFF) //这个宏取得的是线性地址的页内偏移部分

define PTE_ADDR(pte) ((physaddr_t) (pte) & ~0xFFF) //取得页表项中的物理地址(指向的物理页面的首地址)

有用的inline函数

static inline ppn_t page2ppn(struct Page *pp) 返回Page结构pp所对应的页面下标;

static inline physaddr_t page2pa(struct Page *pp)

返回Page结构pp所对应的物理页面的物理首地址; static inline struct Page* pa2page(physaddr_t pa)

返回物理地址pa所在的物理页面所对应的页面结构 static inline void* page2kva(struct Page *pp)

与page2pa类似,只不过返回的是Page结构pp所对应的物理页面的内核首地址(逻辑地址)

boot_map_region() 映射一个虚拟地址区间到一个物理地址区间,貌似在本部分没用到。

pte_t * pgdir_walk(pde_t *pgdir, const void *va, int create)

检查虚拟地址(应该是线性地址)va已经能够用页表(页目录+页表的体系)翻译,如果能够,则返回该地址对应的页表项的地址;如果不能,同时create=0的话,则返回空(NULL);但是,如果create=1的话,为该地址创建对应的页表(因为没有实际物理页面相对应,即使创建,返回的页表项中的地址部分也为空!),并返回va所对应的页表项的地址。

注意:该函数返回的页表项地址为内核地址!

struct Page * page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)

在页式地址翻译机制中查找线性地址va所对应的物理页面,如果找到,则返回该物理页面,并将对应的页表项的地址放到pte_store中;如果找不到,或其他原因,则返回空(NULL)。

void page_remove(pde_t *pgdir, void *va)

删除线性地址va所对应的物理页面。

注意:在删除页面的时候,调用的是page_decref(),仅减低该页面的引用度,而不一定要将页面删除。同时,由于页表项发生了修改,删除操作完成后,应该调用tlb_invalidate()更新TLB。

int page_insert(pde_t *pgdir, struct Page *pp, void *va, int perm)

这是JOS在实现页面支持中最重要的一个函数,该函数的功能是将页面管理结构pp所对应的物理页面分配给线性地址va。同时,将对应的页表项的permission设置成PTE_P&perm。

注意:一定要考虑到线性地址va已经指向了另外一个物理页面或者干脆就是这个函数要指向的物理页面的情况。如果线性地址va已经指向了另外一个物理页面,则先要调用page_remove将该物理页从线性地址va处删除,再将va对应的页表项的地址赋值为pp对应的物理页面。如果va指向的本来就是参数pp所对应的物理页面,则将va对应的页表项中的物理地址赋值重新赋值为pp所对应的物理页面的首地址即可。

static void boot_map_segment(pde_t *pgdir, uintptr_t la, size_t size, physaddr_t pa, int perm)

在页表中,将线性地址[la, la+size]映射到物理地址[pa, pa+size]。

注意:size一定是PGSIZE(4KB)的整数倍。

pte_t *
pgdir_walk(pde_t *pgdir, const void *va, int create)
{
//cprintf("pgdir_walk\r\n");
// Fill this function in
pte_t* result=NULL;
if(pgdir[PDX(va)]==(pte_t)NULL)
{
if(create==0)
{
return NULL;
}
else
{
struct Page* page=page_alloc(1);
if(page==NULL)
{
return NULL;
}
page->pp_ref++;
pgdir[PDX(va)]=page2pa(page)|PTE_P|PTE_W|PTE_U;
result=page2kva(page);
}
}
else
{
//cprintf("%u ",PGNUM(PTE_ADDR(pgdir[PDX(va)])));
result=page2kva(pa2page(PTE_ADDR(pgdir[PDX(va)])));
}
return &result[PTX(va)];
// Fill this function in
//return NULL;


查找页目录表,根据宏PDX取得页目录项,如果不为空,取出该项内容的前20位(PTE_ADDR宏),这是物理地址,通过此物理地址查找对应的Page结构(pa2page宏),然后获得此Page的虚拟地址(page2kva宏)。

此时的地址为页表的虚拟地址,根据偏移得到页目录项,在返回此页目录项地址。

如果前20位不为空,检查create,如果为0,返回null。否则新分配一个Page作为页表,然后自增Page 的引用,让该页目录项的前20位为页表物理地址(page2pa得到物理地址),并设置一些权限符号(不加通不过最后的检测函数),在通过此页表的虚拟地址得到相应页表项的虚拟地址并返回。

page_insert(pde_t *pgdir, struct Page *pp, void *va, int perm)
{
//cprintf("page_insert\r\n");
// Fill this function in
pte_t* pte;
struct Page* pg=page_lookup(pgdir,va,NULL);
if(pg==pp)
{
pte=pgdir_walk(pgdir,va,1);
pte[0]=page2pa(pp)|perm|PTE_P;
return 0;
}
else if(pg!=NULL )
{
page_remove(pgdir,va);
}
pte=pgdir_walk(pgdir,va,1);
if(pte==NULL)
{
return -E_NO_MEM;
}
pte[0]=page2pa(pp)|perm|PTE_P;
pp->pp_ref++;
return 0;
// Fill this function in
//return 0;
}
首先查找该va是否已经映射到了某个物理页面,如果映射到了,则解除映射。
使用pgdir_walk查询该地址的pte(若不存在则建立,第三个参数传入1),如果pte为空,说明没有额外的空间分配页表,因此返回-E_NO_MEM。否则将该pte的内容填充成相应物理页面的物理地址,增加这个物理页面的引用次数。


page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)

{

//cprintf(“page_lookup\r\n”);

// Fill this function in

pte_t* pte=pgdir_walk(pgdir,va,0);

if(pte==NULL)

{

return NULL;

}

if(pte_store!=0)

{

*pte_store=pte;

}

if(pte[0] !=(pte_t)NULL)
{
//cprintf("%x \r\n",pte[PTX(va)]);
return pa2page(PTE_ADDR(pte[0]));

}
else
{
return NULL;
}
// Fill this function in
//return NULL;


}

这个函数逻辑很简单,首先查找此va对应的物理页面,如果此页面不为空,说明va已经映射到了物理页面,减少这个物理页面的引用次数(次数为0就释放这个页面了,相关逻辑封装在了page_decref中),然后置相应的页表项为0,并通知tlb失效。tlb是个高速缓存,用来缓存查找记录增加查找速度。


3) JOS的内存组织

kernbase到最高为4g是一块remapped内存,这块很大的内存从低到高要映射整块物理内存。

其次kernbase往下PTSIZE(貌似是4M)是不可用内存,再往下是KERNSTACKTOP也就是内核栈的栈顶,从KERNSTACKTOP往下KSTKSIZE为内核栈区域,大概是8个页面,内核栈不能超过这个区域。从KERNSTACKTOP往下一个PTSIZE这块区域除了内核栈之外还有一块空白区域,防止栈溢出的时候复写用户空间的数据。

接着是ULIM,代表用户空间的最高地址,换句话说此位置往下所有空间为用户空间,用户空间有很多东西,之后的LAB会详细说明,在此只说UPAGES。

UPAGES是JOS用户记录物理页面使用情况的数据结构,为了使用户空间能访问这块数据结构,会将PAGES映射到UPAGES位置

有映射示意图如下:



一共有三个线性地址到物理地址的映射是需要的:

[UPAGES, sizeof(PAGES) ] => [pages, sizeof(PAGES)]

这里PAGES代表页面管理结构所占用的空间;

[KSTACKTOP – KSTKSIZE, 8] => [bootstack, 8]

其中bootstack为内核编译时预先留下的8个页面(用做内核堆栈);

[KERNBASE, pages in the memory] => [0, pages in the memory] 这个地址映射范围比较广,含盖了所有物理内存。

其中,最后一个地址映射最重要,因为JOS其后启动新的段式地址,新的段base=0x0(见struct Segdesc gdt[]),如果没有这个地址映射,以前的内核地址0xf0000000开始的地址是无法变换到实际的物理地址的
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  内存 管理 操作系统