x86从实际模式到保护模式(3):强大的分页机制

分页机制,强大如斯,理论和物理,分离让你脑子停熄

虚拟内存和用户程序结构

虚拟内存在理论上可以看作是一个用户程序拥有自己的一块4G内存,即该用户程序认为自己独占所有内存,而物理上则是高速缓存存储器(cache)根据局部性原理进行数据交换,加上流水线技术等等的一种假象,虚拟内存的地址是一种逻辑地址,是根据段式存储而分配的一种逻辑上的线性内存空间,如果不进行分页,则就是物理地址,否则还是要通过页部件转换成物理地址,在此不做过多叙述。

用户程序分为:全局部分(GDT,PCB,MBR,HMA,Stack segment,Kernel…)+ 任务的私有部分(LDT,TSS,Code segment,Data segment,Stack segment)。所有的用户程序的全局部分几乎是一样的。

物理内存的分页原因

物理内存分页的原因:

把虚拟内存对应于物理内存进行分页,页的大小为4K,物理内存一共有1024 * 1024个页(内存为4G),由物理地址00000000开始,每次加上4K即00001000,以此类推。

在这里插入图片描述
段地址转化成页地址的过程:取段地址的前20位*4找到页表中对应的索引的值(该值是物理地址的前20位)+ 线性地址的后12位 = 32位物理地址,将内容写入页中即可

为了让用户程序的两部分在页中也泾渭分明,因此在内存中必须先定义全局部分的页,因此产生了页目录表和页表

页表有1024个页表的物理地址,每个页表有1024个物理页,因此可以表示1024 * 1024个页

CR3(页目录基址寄存器)存放着当前任务的页目录地址,因此相应的页部件也得调整:

线性地址的前10位用来指明页目录的索引(CR3指向页目录),根据索引的值取得页表的地址,中间10位取得页表的索引的值,加上最后的12位合成32位物理页。

         ;创建系统内核的页目录表PDT
         ;页目录表清零 
         mov ecx,1024                       ;1024个目录项
         mov ebx,0x00020000                 ;页目录的物理地址
         xor esi,esi
  .b1:
         mov dword [es:ebx+esi],0x00000000  ;页目录表项清零 
         add esi,4
         loop .b1

此处要搞清楚,是先有内核才有页表的,但是内核后期也要页表,因此内核的页表在映射的过程中是直接把线性地址转化成物理地址即可。

页表和页目录表结构

  • 21~31:页表物理基地址
  • 0:P,页表和页目录表是否在内存中
  • 1:RW,页表和页目录表是否可读写
  • 2:US,用户或管理位,特权级位
  • 3:PWT,页级通写位,是否写入高速缓存
  • 4:PCD,页级高速缓存位
  • 5:A,是否被访问
  • 6:D,是否写过数据
  • 7:O(页目录表);PAT(页表项):页属性表支持位
  • 8:G,全局位,该表是否为全局位
  • 9~11:程序是否可使用
         ;在页目录内创建指向页目录自己的目录项
         mov dword [es:ebx+4092],0x00020003 

         ;在页目录内创建与线性地址0x00000000对应的目录项
         mov dword [es:ebx+0],0x00021003    ;写入目录项(页表的物理地址和属性)      

         ;创建与上面那个目录项相对应的页表,初始化页表项 
         mov ebx,0x00021000                 ;页表的物理地址
         xor eax,eax                        ;起始页的物理地址 
         xor esi,esi
  .b2:       
         mov edx,eax
         or edx,0x00000003                                                      
         mov [es:ebx+esi*4],edx             ;登记页的物理地址
         add eax,0x1000                     ;下一个相邻页的物理地址 
         inc esi
         cmp esi,256                        ;仅低端1MB内存对应的页才是有效的 
         jl .b2
         
  .b3:                                      ;其余的页表项置为无效
         mov dword [es:ebx+esi*4],0x00000000  
         inc esi
         cmp esi,1024
         jl .b3 
开启分页机制

CR3:指向页目录,CR0的PG位表示开启分页机制

         ;令CR3寄存器指向页目录,并正式开启页功能 
         mov eax,0x00020000                 ;PCD=PWT=0
         mov cr3,eax

         mov eax,cr0
         or eax,0x80000000
         mov cr0,eax                        ;开启分页机制

页表建立的先后是,先把用户程序放入虚拟内存,然后搜寻物理内存的空闲页将其地址放入建立的页表

用户程序的页表是怎么来的?是先复制内核的页目录,将高端映射为内核部分,低端映射为用户任务部分

空闲页的搜索
页映射位串:操作系统会在获得内存信息的时候会获得所有页的相关信息,当有程序要分配内存时,就在位串中来指定每个页的分配情况,共1024 * 1024个b

搜索位串是否空闲

bts r, r

刷新TLB:传统情况下,程序要使用内存,必须经过页目录表和页表转化成物理地址,因此把页表项预先装在处理器中,可以加快速度,这个叫做TLB(转换速查缓存器,快表)

mov cr3, ebx
mov ebx, cr3

在这里插入图片描述

tlb较小,当tlb满的时候,替换掉那些用的少的行

自我综述

一篇文章想讲全分页机制肯定是不可能的,想完全弄懂只能去看源码和理论和物理上的实现,这点很绕,但是不可避免。
虚拟内存说白了其实就是个虚拟的玩意,是程序自己想的,你可以想象程序的所有段其实都在硬盘上。打个比方,一个.c文件生成.out文件里面的地址其实都是虚拟地址,而这个地址是他自封的,他认为他在运行的时候也是这个地址
当程序变成一个进程的时候他会将线性地址(虚拟地址)通过页目录表和页表映射到内存中,当发生切换进程的时候,旧的进程会将页目录表的信息存放在CR3寄存器,这个新的进程也会通过页目录表和页表得到物理页,如果物理页都被占用了,则将暂时不用的物理页先换出内存(cache或者其他技术)。

验证
// 01.c
#include <stdio.h>

int main() {
    
    
	printf("Hello World! \n");
	return 0;
}
gcc 01.c	# 编译源代码
readelf -a a.out
# 查看该用户程序头
lh@lh-virtual-machine:~$ readelf -a a.out
ELF 头:
  # 魔数,其中45 4c 46对应的是ASCII表值是ELF,表明这个文件是一个elf文件
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  ....
  入口点地址:               0x1060
  程序头起点:          64 (bytes into file)
  Start of section headers:          14712 (bytes into file)
  标志:             0x0
  Size of this header:               64 (bytes)
  
  Size of program headers:           56 (bytes)
  # 标号的数量
  Number of program headers:         13
  
  Size of section headers:           64 (bytes)
  # 段的数量
  Number of section headers:         31

好了,x86从实模式到保护模式到此结束啦,下一阶段我要走业务了,所以先学一学protobuf和libevent空间吧!

Supongo que te gusta

Origin blog.csdn.net/qq_48322523/article/details/121056132
Recomendado
Clasificación