- 作者: 雪山肥鱼
- 时间:20210301 22:47
- 目的:理解页表与TLB
# 1. Paging Introduction
## 1.1 简单举例
## 1.2 页表在哪里
## 1.3 页表里有什么
## 1.4 伪代码流程
# 2. Paging: Faster Translation
## 2.1 TLB基础算法
## 2.2 TLB 为什么这么快
## 2.3 谁来处理TLB Miss
## 2.4 TLB里有什么
## 2.5 TLB遇到Context Switch
## 2.6 替换策略
## 2.7 真实的TLB Entry
# Paging: Smaller Tables
## 3.1 简单的解决方式:增加Page Size
## 3.2 页与Segment的混合方式
## 3.3 多级页表 multi-level page table
暂时替代虚拟内存分段的方式,我们将虚拟内存切割成固定的单元大小,将这些单元称为page。对应的在物理内存中相同大小的槽 - page frame 页帧。
1. Paging: Introduction
1.1 简单例子
假设有一个64bytes的地址空间,每一页为16bytes, 那么总共有4页,以及对应映射在物理内存中。
上图可以看出:
- 看似连续的地址空间,映射在物理空间中并不连续
- OS 也会在物理空间占有一席
在一个完善的分页内存管理系统中
- 更高效的支持抽象的地址空间,可以不必在意堆栈的增长方向与他们是如何使用的
- 因为物理内存被分配成了固定大小,则减少外部碎片化,外部碎片化是因为分段式管理在物理内存层面上没有same units 的概念。
为了记录虚拟地址与物理地址的映射关系,所以才有了以per-process, 每个进程的为单位的 page table,也就是说一个进程,一个page table.(除了 inverted page table)
一个虚拟地址被分解成两部分
- the virtual page number(VPN)
- the offset 偏移
以上图为例,地址空间大小64字节。一个字节一个地址,2^6 = 64,所以地址式6位。page size = 16(2^4)byte,所以偏移量 有 4位:
在这个例子中page table 暂时理解为 简单存储了
VPN 0 -> PFN 3; VPN 1 -> PFN 7; VPN 2 -> PFN 5;VPN 3 -> PFN 2
假设举例,地址21(010101)
- 前两位01 位 VPN,那么就是01 page.偏移量位 0101
- 查page table 得01 page,对应物理内存页帧的位置(Physical frame number PFN)是07(11)
-
07(11)与偏移地址组合 得到翻译后的物理地址
如下图所示:
注:每一页都管理着若干内存。(此例为16个字节)
1.2 页表存在哪里
- 页表大小计算
以32位地址空间为里,4KB(2^12) 大小的 page size, 则有20位VPN.也就是说 页表一共要管理2^20 个 VPN,也就是1M个VPN,如果每个PTE(Page Table Entry) 有4个字节的大小,那么就是4M。
如果OS中运行了100个程序,那么就要管理400M页表的大小。这是不行的。
现在我们知道物理硬件MMU不可能做到这么大,那么把这400M大小的100个页表,放到内存中,又OS来管理。MMU 只保存 页表的地址。
1.3 页表里有什么
我们假设页表是线性页表,理解成一个数组,元素类型是physical address, 索引是VPN。比如虚拟地址的VPN 是 10,那么我们就在pagetable里面寻找第10个元素。那么就会寻找到这个虚拟地址的物理PFN。3
32位系统里,每一项PTE(page table entry)大小有4个字节。即32位,内容如下:
一些重要的位:
- valid bit: 翻译后的地址 对于 这个程序是否是有效地址,比如进程访问的地址,翻译后发现是代码段,或者kernal的代码,那valid项必定是 0,必定会产生段错误
- protection bits: read write execute 权限
- present bit: 这个页是否存在内存,还是已经置换到了硬盘上,不在内存则会产生page fault 缺页,然后产生page 的 swapped in.
- reference bit:一个页是否已经被访问过,which page are popular an d thus should be kept in memory. 对于页交换有用
1.4 伪代码流程
VPN = (VirtualAddress & VPN_MASK) >> SHIFT
PTEAddr = PTBR + (VPN * sizeof(PTE))
// 1st access memory to fetch PTE
PTE = AccessMemory(PTEAddr)
if(PTE.Valid == False)
RaiseException(SEGEMENTATION_FAULT)
else if(CanAccess(PTE.ProtectBits) == False)
RaiseException(PROTECTION_FAULT)
else
Offset = VirtualAddress & OFFSET_MASK
PhysAddr = (PTE.PFT << PFN_SHIFT) | Offset
// 2end access memory, fetch the specific content in PhysAddr
Regsiter = AccessMemory(PhysAddr)
此时,引出两个问题
- 页表太大
- 两次访问内存,耗时。
2. Paging:Faster Translations(TLBs)
这里我们暂时解决在线性的内存表中访问两次内存,从而能造成耗时的问题。也就是说加速地址的翻译。
- 借助硬件的帮忙
TLB 是硬件MMU 的一部分,是一种cache,当然也可以叫做 address - translation cache.
2.1 TLB的基础算法
VPN = (VirtualAddress & VPN_MASK) >> SHIFT
(Success, TlbEntry) = TLB_Lookup(VPN)
if (Success == True) // TLB Hit
if (CanAccess(TlbEntry.ProtectBits) == True)
Offset = VirtualAddress & OFFSET_MASK
PhysAddr = (TlbEntry.PFN << SHIFT) | Offset
Register = AccessMemory(PhysAddr)
else
RaiseException(PROTECTION_FAULT)
else // TLB Miss
PTEAddr = PTBR + (VPN * sizeof(PTE))
PTE = AccessMemory(PTEAddr)
if (PTE.Valid == False)
RaiseException(SEGMENTATION_FAULT)
else if (CanAccess(PTE.ProtectBits) == False)
RaiseException(PROTECTION_FAULT)
else
TLB_Insert(VPN, PTE.PFN, PTE.ProtectBits)
RetryInstruction()
2.2 TLB 为什么这么快
int sum = 0;
for (i = 0; i < 10; i++) {
sum += a[i];
}
- 8 位的地址空间 外带16byte 的 页,也就是说8位现在被分成了4+4模式,4位vpn,4位页大小即offset.数组的类型位int型,即4个字节。假设a[0]处于上图位置,那么数组排布如上图所示。
那么一组循环下来,TLB结果如下:
miss->hit->hit->miss->hit->hit->hit->mis->hit->hit
所以如果地址空间位数更多,页大小更大,那么只有第一次是miss,其他全是hit.大大提升访问速率。
2.3 谁来处理TLB Miss
硬件和OS一起接手管理TLB MISS.
Hardware处理方式:
- 硬件需要知道page table 具体在哪里,通过MMU中的page table base register 即可。
- 找到pagetable后,找到对应的PFN,计算出想要的physical address(intel x86 架构)
VPN = (VirtualAddress & VPN_MASK) >> SHIFT
(Success, TibEntry) = TLB_LookUp(VPN)
if (Success == True)
if (CanAccess(TlbEntry.ProtectionBits) == True)
Offset = VirtualAddress & OFFSET_MASK
PhysAddr = (TlbEntry.PFN<<SHIFT) | Offset
Register = AccessMemory(PhyAddr)
else
RaiseException(PROTECTION_FAULT)
else //TLB MISS
RaiseException(TLB_MISS)
- TLB MISS, 指令流暂停,进入内核态,跳到trap handler.
- 针对 TLB MISS 的trap handler.寻找物理地址
- 更新 TLB,返回陷入trap的地方
- 从 trap 返回后,再次执行相同的指令,这里是和系统调用不同之处
- 同时OS要避免 TLB MISS 的无限递归
- 处理trap handler 在物理内存中,这一块物理内存没有内存映射,目的也不是解决地址翻译。
- reserver some entries in the TLB for permanently-valid translations, and use some of those permanent translation slots for handler code itself.TLB中的一些翻译槽就是用来hanlder codes的
- CISC:Complex-instruction set computers
- RISC:Reduced-instruction set computers
2.4 TLB里面有什么
TLB 的寻找机制是 parallel 的。
- TLB 的 Valid BIT 和 Page table 的 Valide BIT 区别
- Page Table 中 Entry 的 valid bit 代表当前的进程没有权限访问这一项,也就是说,这一项压根就没有分配给你。此时会产生段错误,进程被中止。
- TLB:valid bit 表示:翻译地址是否有效。比如系统启动时,TLB里的每一项都是invalid,因为还并不存在寻找地址的动作。一旦虚拟内存启用,一个程序开始运行和开始访问虚拟空间时,TLB 才会 慢慢被填满。
- TLB 的 valid位是很有用的,尤其是遇到上下文切换的。我们用这些来区分两个进程在TLB中的地址互补干涉。By setting all TLB entries to invalid, the system can ensure that the about-to-be-run process does not accidentally use a virtual-to-physical translation from a previous process.
- 其他bit 大致相同
2.5 TLB遇到Context Switch
问题: TLB的Entry并不只属于当前正在跑的进程,当然包括其他进程。那么遇到上下文切换,应该如何处理呢才能保证进程隔离呢?
两个进程有相同的VPN 应该如何应对?
- 每次切换进程都要flush TLB 的方式效率太低,开销太大
-
在TLB中 增加 Address Space identifier(ASID) 项,类似每个进程的PID
当然也会有两个进程VPN不同,PFN相同的情况,就是两个进程共享同一页的情况,比如共享库。这种情况会降低内存开销。
2.6 替换策略
LRU 策略 least-recently-used.最近最少使用的
2.7 真实的TLB Entry
- 32位 4KB pagesize
- 19 位虚拟地址,非20位原因,用户用到的地址,和 kernal用到的地址各占一半
- G: global share ,公用的。此时ASID会失效
- page mask:给多级页表用的。
- MIPS 架构的TLB 有32项或者64项,但是其中会给OS保留一些,会有一个寄存器用来监控TLB,告诉OS,TLB现在保留了多少slots 给OS。
- OS use these reserved mapping for code and data that it wants to access during critical times, where a TLB miss would be problematic.
3 Paging: Smaller Tables
TLB的线性表模式会导致TLB表太大了。每一个大概有4M,那么如果开了100个进程,那就会有400M。
3.1 简单的解决方式:增加Page Size
这种解决方式很明显会造成内存的内部碎片化,比如我扩展到了1M,宏观上看确实降低了页表的大小,但是,如果我只用其中的100个字节。那么内部碎片化的问题就会出现。
3.2 页与Segment的混合方式
假设有地址空间大小16KB, 页大小1KB
这个思路是有三张页表,code heap stack各一张page table.每张表依旧有base/bounds寄存器限制每个段的大小。
base寄存器指向的是页表的物理地址
- 但是这种混动方式依旧有缺陷,段的管理方式并不灵活,尤其对于松散的堆管理。
- 也会造成外部碎片,寻找free space 变得更加复杂
3.3 多级页表 multi-level page table
基本原理:
- 讲页表砍碎,每一块和page size 相同。
- 如果页表(be chopped up)中的一页中所管理的,每一项entry(VPN<->PFN)都是invalid,则这一页不会被分配内存。
-
页目录(Page directory)的引入
页目录告诉你了,页表的每一页在哪里。
举例:page size: 16个字节,地址空间16位。则页表总共有16项:
- 左:线性表,浪费
-
右:参照左,每四组是一个页,其中第2页,第3页的valid选项全部位 0 ,所以不用分配,如果按照页目录来看,只用分配第一项和第四项,即对应physical address 里 第201page frame,和204 page frame。PDE(Page Directoryt Entry)
a). 带有页目录的地址分解:
举例:16KB地址空间(14 bits),page size:64bytes(6 bits), VPNS(8 bits)
所以对于页表来说共有2^8entrys需要管理。
此时 页表的大小位2^8 * 4(每个entry 4个字节),那么此时讲page table 切碎,每个碎片式page size 大小,那么就要除6:
2^8 * 4 / 2^6 = 2 ^ 4.
所以页目录共管理2^4 page,如图所示:
- 这就非常明显了,虚拟地址出来后,先找计算属于页目录中的哪一项(即对应的 physical address 的 slot,即这个页表的PFN)
PDEAddr = PageDirBase + (PDIndex + sizeof(PDE)) - 找到在对应page table 中的第几项,
-
在talbe 的 <VPN, PFN>中,找到PFN,得到最后的地址
例:11 1111 1000 000
页目录:11 11 :15,那么就是页目录管理的第15个页表
页表Index:1110: 14,那么就是这张页表的 第14项
PFN:得到PFN是55,最终地址:00 1101 1100 0000 = 0下DC0.
超过两级的多级列表,实际上就是页目录上面还有一层管理页目录的页目录。原理是一致的。
代码:
VPN = (VirtualAddress & VPN_MASK) >> SHIFT
(Success, TlbEntry) = TLB_Lookup(VPN)
if (Success == True) // TLB Hit
if(CanAccess(TlbEntry.ProtectionBits == True)
Offset = VirtualAddress & OFFSET_MASK
PhyAddr = (TlbEntry.PFN<<SHIFT) | Offset
Regsiter = AccessMemoryt(PhyAddr) //PhyAddr
else
RaiseException(Protection_Fault)
else
// first get page directory entry
PDIndex = (VPN & PD_MASK) >> PD_SHIFT
PDEAddr = PDBR + (PDIndex * sizeof(PDE))
PDE = AccessMemory(PDEAddr) //PDEAddr
if(PDE.Valid == False)
RaiseException(Segmentation_Fault)
else
// PDE is valid:now fetch pte from page table
PTIndex = (VPN & PT_MASK) >> PT_SHIFT
PTEAddr = ((PDE.PFN) << SHIFT) + (PTEIndex * sizeof(PTE))
PTE = AccessMemeory(PTEAddr) //PTEAddr
if(PTE.Valid == False)
RaiseException(Segmentation_Fault)
else if (CanAccess(PTE.ProtectionBits)== False)
RaiseException(Protection_Fault)
else
TLB_INSERT(VPN, PTE.PFN, PTE.ProtectionBits)
RetryInstruction()