自映射啊啊啊啊啊啊啊啊啊啊啊

Connor

变量定义

pa -> 物理地址
va -> 虚拟地址
pdx(va) -> 虚拟地址va高十位
ptx(va) -> 虚拟地址va中间十位
offset(va) -> va的低十二位
base 为 实现自映射的页表的首地址 。后22位为0(与1024个页大小对齐)


普通的二级页表

对于一个32位的系统,总空间大小为4GB (1 << 32) , 现在规定一个页面的大小为 4kB (1 << 12),很显然对于整个虚拟空间,可以一共分为 1024 * 1024(1<<20) 个页,也就是1024个 1024个页,如下图所示。而对于我们需要查找的页表,实际上就只需要其中一个1024个页面即可。
image1.png

下面先考虑普通的二级页表 :

对于一个普通的二级页表而言, 虚拟页面 不需要 在 虚拟地址连续存储。当我要通过一个va查找他的pa时,基本流程是这样的:

  1. 通过va 得到 pdx(va) , ptx(va) , offset(va)
  2. 使用当前进程对应的pgdir,也就是页目录基地址,既Pte * 类型的变量,偏移 pdx ,(这里是指针偏移):Pde *pde = pgdir + pdx
  3. 得到的pde应该是一个指向一级页表项的指针,对其解引用可以得到,其对应的二级页表的物理地址,由于页表存储在kseg0段,可以通过对地址直接加上0x80000000转换成对应的二级页表的虚拟地址 : Pte *pte = KADDR(PTE_ADDR(*(pde))),这个地方得到的地址是通过一级页表项找到的二级页表的基地址
  4. 随后要通过 ptx 读取二级页表项的虚拟地址 Pte *pte = pte + ptx
  5. 如果要获得最终的物理地址 : u_long pa = PTE_ADDR(*(pte)) | offset(va)

自映射的二级页表

对于一个自映射的二级页表来说,本质与普通的二级页表相同,不同的点在于 :

  1. 这1024个虚拟页面连续排列在内存中
  2. 页目录位于这1024个虚拟页表中,页目录中有一项会映射到自己

image2.png

接下来有几个问题 :

  1. 页目录的位置
  2. 自映射如何通过一个虚拟地址va,来找到对应的物理地址pa
  3. 上机的问题 :如何建立和消除自映射

Q1 : 页目录的位置 :

前面提到,对于整个虚拟空间,一共有1024 个 1024 个 页面 ,(这里的意思是每1024个页面当成一个整体,因为自映射需要1024个页面),那么用来是实现自映射的这1024个页面相对于起始地址的偏移量,也就是 页目录 在这1024个页面中的相对于起始地址base的偏移量。这里总的起始地址是0x00000000

也就是说,如果给你自映射的页表的首地址base :

则一个页面大小为 1 << 12 , 1024个页大小为 1 << 22 , 那么base 的偏移量也就是 x = base / (PAGE_SIZE * 1024) , 既 x = (base >> 22) , 也就是说,页目录的位置就是在base的基础上加上这个x * PAGE_SIZE ,也就是 x << 12 既: pgdir_va = base | x << 12 既 : pgdir_va = base | (base >> 22) << 12 也就是 pgdir_va = base | (base >> 10)

这里 base 要求和 1024 * PAGE_SIZE 对齐,也就是 base 的低22位为0

Q2 :自映射如何通过一个虚拟地址va,来找到对应的物理地址pa

通过Q1可知,页目录的位置 为 base | (base >> 10) ,但是自映射实现地址翻译的时候真的需要通过这个页目录吗?这里是最让我感到疑惑的地方 , 这里给出个人理解 :

给定虚拟地址va , 现在要找到这个 va 的物理地址。首先 va 被分为 pdx , ptx , offset 。

  1. 首先要对查找页目录页表项 :Pde *pde = pgdir + pdx , 这是按照 Pde * 指针偏移 , 如果和上机一样是u_long 类型则要按照字节偏移,因为一个页表项32位,4个字节,所以乘4,也就是左移两位: u_long pde = (u_long)pgdir + (pdx << 2)
  2. 之后呢,就是和普通页表不一样的地方,因为自映射页表连续排布,同时页目录也是对应进行映射,也就是说:页目录中第i个页表项就指向第i个二级页表,也就是说,这个地方其实不需要和普通的二级页表一样对页目录页表项进行解引用而得到二级页表的起始地址,而是可以直接通过刚才的偏移量 pdx ,从这1024个页表的起始地址向后偏移 pdx 个页,得到的就是二级页表的基地址!我的意思是,这个地方要获得二级页表的基地址可以直接通过pdx偏移得到,而不需要通过对页目录页表项解引用得到
  3. 由2可知: 二级页表的基地址: u_long pte_pdgir = base | (pdx << 12) , 那么具体的二级页表项的虚拟地址为
    u_long pte_va = base | (pdx << 12) | (ptx << 2)
  4. 要获得最终的va , 就是对这个二级页表项进行解引用在加上offset得到!!!

回到上机的两个地址的题目 :

pte_va() 函数的功能为:给定自映射虚拟地址空间的首地址 base ,计算使用自映射访问第 pdeno 张页表的第 pteno 个页表项时,需要使用的虚拟地址

1
2
3
u_long pte_va(u_long base, u_long pdeno, u_long pteno) {
return base | (pdeno << 12) | (pteno << 2) ;
}

pde_va() 函数的功能为:给定自映射虚拟地址空间的首地址 base ,计算使用自映射访问页目录中的第 pdeno 个页目录项时,需要使用的虚拟地址

1
2
3
4
u_long pde_va(u_long base, u_long pdeno) {
//先找页目录 : base | (base >> 10) , 再找第pdeno个页表项
return base | (base >> 10) | (pdeno << 2) ;
}

create_self_map() 函数的功能为:给定页目录首地址 pgdir 和 asid ,将所有页表自映射至 [base, base + 1024 * PAGE_SIZE) 的连续 4MB 虚拟地址空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int create_self_map(Pde *pgdir, u_int asid, u_long base)
{
int count = 0;
Pte *ppte;
struct Page *pp;
for (u_long va = base; va < base + 1024 * PAGE_SIZE; va += PAGE_SIZE)
{
if ((pp = page_lookup(pgdir, va, &ppte)) != NULL) //检查其是否已经与物理页面建立了映射
{
page_remove(pgdir, asid, va);
count++;
}
}
pgdir[(base >> 22)] = (PADDR(pgdir) | PTE_V); //将页目录的要映射到自己的页表项的值写为自己的虚拟地址再加上权限位
return count;
}

remove_all_self_map() 函数的功能为:给定页目录首地址 pgdir 和 asid ,解除当前已经建立的所有自映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int remove_all_self_map(Pde *pgdir, u_int asid)
{
int count = 0;
for (int i = 0; i < 1024; i++)
{
if (pgdir[i] == (PADDR(pgdir) | PTE_V)) //查找页表项中映射到自己的页表项
{
pgdir[i] = 0;
count++;
u_long base = i << 22; //通过i反推base
for (u_long va = base; va < base + 1024 * PAGE_SIZE; va += PAGE_SIZE)
{
tlb_invalidate(asid, va); //解除所有的tlb缓存
}
}
}
return count;
}
  • Title: 自映射啊啊啊啊啊啊啊啊啊啊啊
  • Author: Connor
  • Created at : 2026-04-28 12:43:06
  • Updated at : 2026-04-28 13:02:23
  • Link: https://redefine.ohevan.com/2026/04/28/self-mapping/
  • License: This work is licensed under CC BY-NC-SA 4.0.
On this page
自映射啊啊啊啊啊啊啊啊啊啊啊