目录
X86保护模式
????????段翻译:逻辑地址翻译为线性地址
????????保护机制
虚拟地址、物理地址、线性地址
JOS中的虚拟地址与物理地址?
引用计数
页表管理
????????pgdir_walk()
????????boot_map_region()
????????page_lookup()
????????page_remove()
?????????page_insert()
X86保护模式
????????段翻译:逻辑地址翻译为线性地址
????????逻辑地址转化为线性地址需要描述符、描述符表、选择器、段寄存器。
????????描述符:段描述符为处理器提供将逻辑地址映射到线性地址所需的数据,由编译器、链接器、加载器或操作系统创建。
?? ??? ??? ? ????????描述符表包括全局描述符表 (GDT)与本地描述符表 (LDT)。描述符表只是一个包含描述符的 8 字节条目的存储器阵列,最多有2^13个描述符,且GDT中index=0的描述符并不使用。处理器通过GDTR与LDTR访问定位。 ?? ?????选择器通过指定描述符表,并在其中索引描述符来标识描述符。 ?? ?????段寄存器:描述符的信息存储在段寄存器中,从而避免每次访问内存时都需要查阅描述符表。这些段地址寄存器的可见部分由程序操作,就好像16 位寄存器一样。不可见部分由处理器操纵。 ??
????????保护机制
????????x86中的保护有五个方面:类型检查,限制检查,可寻址域的限制,程序入口点的限制,指令集限制。保护适用于段翻译和页面翻译。 ? ? ? ? 1.?段级保护:段级保护与五种保护都相关,段是保护单元,段描述符存储保护参数。当段描述符的选择器加载到段寄存器中并且每次访问段时,保护检查由 CPU 自动执行。段寄存器保存当前可寻址段的保护参数。 ????????段级保护中,主要涉及到一个特权的概念。
????????特权的概念是通过为处理器识别的关键对象分配一个从 0 到 3 的值来实现的。该值称为权限级别。值零代表最大特权,值三代表最小特权。以下处理器识别的对象包含特权级别: ????????描述符包含一个称为描述符特权级别 (DPL)?的字段。 ????????选择器包含一个称为请求者权限级别 (RPL)的字段。RPL 旨在表示发起选择器的过程的特权级别。 ????????内部处理器寄存器记录当前特权级别 (CPL)。通常,CPL 等于处理器当前正在执行的段的 DPL。随着控制权转移到具有不同 DPL 的段,CPL 会发生变化。处理器通过将 CPL 与一个或多个其他特权级别进行比较来自动评估过程访问另一个段的权利。评估是在描述符的选择器加载到段寄存器时执行的。 ?????(1)数据访问限制:DPL>=max(CPL,RPL),一个过程只能访问处于相同或较低特权级别的数据。这一特性可以防止操作系统的表被修改与读取。
?
???????(2)可执行段控制转移的限制: ????????控制传输由指令 JMP、 CALL、 RET、 INT和IRET以及异常和中断机制完成。 ????????仅当满足以下特权规则之一时, 处理器才允许 JMP或 CALL直接到另一个段。目标的 DPL 等于 CPL,或者目标代码段描述符的符合位被设置,目标的DPL小于或等于CPL。
? ? ??? 2. 页面级别保护:包括可寻址域的限制,类型检查两个方面。
????????PDE、PTE控制页面访问。
????????可寻址域的限制:
????????类型检查:
虚拟地址、物理地址、线性地址
????????虚拟地址由段选择器和段内的偏移量组成。线性地址是在段转换之后,页转换之前。物理地址是在段转换与页面转换都完成后得到的RAM地址。
????????相关更详细的介绍可以看物理地址、虚拟地址(线性地址)、逻辑地址以及MMU的知识(物理地址、虚拟地址(线性地址)、逻辑地址以及MMU的知识_Macross的专栏-CSDN博客)。 一旦我们处于保护模式,就无法直接使用线性或物理地址。 所有内存引用都被解释为虚拟地址并由 MMU 翻译,这意味着 C 中的所有指针都是虚拟地址。
exercise 3:
GDB查看0xf0100000处虚拟地址的内容:
按照va--->pa的转化,0xf0000000被映射到物理地址0x00100000处。
通过qemu查看该物理地址处的内容:
qemu-system-i386 -hda obj/kern/kernel.img -monitor stdio -gdb tcp::26000 -D qemu.log
?两个地址的内容相同,说明映射成功。
?info pg:展示当前页表的详细表示,包括映射范围,权限以及相应的物理页。
?info mem: 当前映射的虚拟地址范围以及权限。
JOS中的虚拟地址与物理地址
?????????(1) JOS 内核可以通过首先将其转换为指针类型来取消引用uintptr_t ?。相比之下,内核无法合理地取消引用物理地址,因为 MMU 会转换所有内存引用。如果将 physaddr_t转换为指针并取消引用它,可能能够加载并存储到结果地址(硬件会将其解释为虚拟地址),但可能无法获得想要的内存位置。
????????(2) 一个小question: ?? ?x是uintptr_t,因为中间有解引用*操作,而物理地址不能解引用,所以x是虚拟地址。 ?? ? ????????JOS中物理地址与虚拟地址的转换 ????????物理地址--->虚拟地址:+0xf0000000。KADDR(pa) ????????虚拟地址--->物理地址:- 0xf0000000。PADDR(va)
引用计数
?????????将相同的物理页面同时映射到多个虚拟地址(或多个环境的地址空间中)。您将在物理页面对应的**pp_ref字段中记录对每个物理页面的引用次数**struct PageInfo。当一个物理页面的这个**计数变为零时**,该**页面可以被释放**,因为它不再被使用。 ????????一般来说,这个计数应该等于物理页在所有页表中出现在下面 UTOP(用户可以接触的虚拟内存0xeec00000)的次数。还将使用pp_ref来跟踪我们保留的指向页目录页的指针的数量,进而跟踪页目录对页表页的引用数量。 ????????使用page_alloc时要小心。它返回的页面将始终具有 0 的引用计数,因为它只是分配了一个空闲页,因此,只要您对返回的页面进行了某些操作(例如将其插入到页表中),就应该增加pp_ref。有时这是由其他函数(例如page_insert)处理的,有时调用page_alloc的函数必须直接执行此操作。
页表管理
????????pgdir_walk()
????????pgdir_walk()的功能:给定'pgdir',一个指向页目录的指针,返回 指向线性地址 'va' 的页表条目 (PTE) 的指针。这需要走两级页表结构。(页目录表--->页目录项--->页表--->页表项)
pte_t *
pgdir_walk(pde_t *pgdir, const void *va, int create)
{ uint32_t pdx=PDX(va);//线性地址的高十位表示页目录项的索引
uint32_t ptx=PTX(va);//线性地址的中间十位表示页表项的索引
pte_t* ptxa;//页表指针
pde_t* pdxa=&pgdir[pdx];//页目录项指针
if((*pdxa&PTE_P)){// 如果页目录项存在并且可以使用,PTE_P的功能在上一节注释中有介绍
ptxa=(pte_t*) KADDR(PTE_ADDR(*pdxa));//在页目录项存下的地址中找到页表地址
}
else{
if(create==false)//如果没有找到且不创建
return NULL;
else{
struct PageInfo* result=page_alloc(ALLOC_ZERO);//创建新的页表来存放页表项
if(!result)
return NULL;//内存不足
result->pp_ref++;//增加引用计数
// ptxa=(pte_t*)KADDR(page2pa(result));
ptxa=(pte_t*)(page2kva(result));//将新创建的物理页面转化为虚拟地址
*pdxa=(page2pa(result))| PTE_P | PTE_W | PTE_U;//将页表地址放入页目录项中
}
}
return &ptxa[ptx];//返回页表项地址
}
????????boot_map_region()
??????? 函数功能:将[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)//[va,va+size]-->[pa,pa+size]
{
int count=size/PGSIZE;
for(int i=0;i<count;i++){
pte_t* pte=pgdir_walk(pgdir,(void*)va+i*PGSIZE,1);//创建页表
// *pte=(pte_t)(pa+i*PGSIZE);
if(!pte)
panic("boot_map_region():out of memory!");
*pte=(pa+i*PGSIZE)|perm|PTE_P;//将页表项中的地址设置为页面的物理地址
}
}
????????page_lookup()
??????? 函数功能:查找虚拟地址va被映射的物理页面。
struct PageInfo *
page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
{
pte_t* pte=pgdir_walk(pgdir,va,0);//不创建新的页表
if(!pte)
return NULL;
else{
if(pte_store)
pte_store=&pte;
}
// struct PageInfo* result=pa2page(PTE_ADDR(*pte));
if(*pte&PTE_P)
return pa2page(PTE_ADDR(*pte));//如果页表项中存在地址并且可以使用
return NULL;//PTE中的地址转化为物理地址
}
????????page_remove()
????????函数功能:取消物理页到虚拟地址va的映射。
void
page_remove(pde_t *pgdir, void *va)//va-X--pa
{ pte_t* pte;
pte_t** pte_store=&pte;
struct PageInfo* pp=page_lookup(pgdir,va,pte_store);
if(!pp)
return;
pp->pp_ref--;//物理页上的引用计数应该减少
if(pp->pp_ref==0)
page_free(pp);//释放物理页
// page_decref(pp);
**pte_store=0;// 对应于 'va' 的 pg 表条目应设置为 0,PTE中的地址=0;
tlb_invalidate(pgdir,va);//使 TLB 无效
}
??? ????? 第一次完成的代码如下,但是make qemu会出现错误,检查发现是page_remove()出现了问题,更具体的是page_lookup()中:
if(pte_store)
pte_store=&pte;
????????当pte为NULL时,上述引用无效。修改为:
if(pte_store)
*pte_store=pte;
????????或者修改page_remove(),直接使用pte修改条目。
void
page_remove(pde_t *pgdir, void *va)//va-X--pa
{
struct PageInfo* pp=page_lookup(pgdir,va,NULL);
if(!pp)
return;
pp->pp_ref--;//物理页上的引用计数应该减少
if(pp->pp_ref==0)
page_free(pp);//释放物理页
// page_decref(pp);
pte_t* pde=&pgdir[PDX(va)];//页目录项
pte_t* pt=KADDR(PTE_ADDR((*pde)));//页表
pte_t* pte=&pt[PTX(va)];//页表项
*pte=0;// 对应于 'va' 的 pg 表条目应设置为 0,PTE中的地址=0;
tlb_invalidate(pgdir,va);//使 TLB 无效
}
?????????page_insert()
??????? 函数功能:将物理页面pp映射到虚拟地址va处。
int
page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm)//pp->va
{
pte_t* pte=pgdir_walk(pgdir,va,1);//创建新的页表
if(!pte)
return -E_NO_MEM;//无内存
else if(*pte&PTE_P){//如果页表项中有物理页面的地址并且可以使用
if (PTE_ADDR((*pte))==page2pa(pp))//如果这个物理页面与需要映射的页面相同
pp->pp_ref--;//那么先减少它的引用计数
else
page_remove(pgdir,va);//不相同则取消映射
}
*pte=page2pa(pp)|perm|PTE_P;//将页表项中的地址设为pp的物理地址并设置权限
pp->pp_ref++;//增加引用计数
return 0;
}
|