热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

x86从实模式到保护模式虚拟内存和用户程序结构物理内存的分页原因开启分页机制自我综述验证分页机制,强大如斯,理论和物理,分离让你脑子停熄虚

x86从实模式到保护模式

          • 虚拟内存和用户程序结构
          • 物理内存的分页原因
          • 开启分页机制
          • 自我综述
          • 验证


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


虚拟内存和用户程序结构

虚拟内存在理论上可以看作是一个用户程序拥有自己的一块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,4loop .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,eaxor edx,0x00000003 mov [es:ebx+esi*4],edx ;登记页的物理地址add eax,0x1000 ;下一个相邻页的物理地址 inc esicmp esi,256 ;仅低端1MB内存对应的页才是有效的 jl .b2.b3: ;其余的页表项置为无效mov dword [es:ebx+esi*4],0x00000000 inc esicmp esi,1024jl .b3

开启分页机制

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

;令CR3寄存器指向页目录,并正式开启页功能 mov eax,0x00020000 ;PCD=PWT=0mov cr3,eaxmov eax,cr0or eax,0x80000000mov 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 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)标志: 0x0Size of this header: 64 (bytes)Size of program headers: 56 (bytes)# 标号的数量Number of program headers: 13Size of section headers: 64 (bytes)# 段的数量Number of section headers: 31

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


推荐阅读
  • Python如何调用类里面的方法
    本文介绍了在Python中调用同一个类中的方法需要加上self参数,并且规范写法要求每个函数的第一个参数都为self。同时还介绍了如何调用另一个类中的方法。详细内容请阅读剩余部分。 ... [详细]
  • 本文介绍了C++中省略号类型和参数个数不确定函数参数的使用方法,并提供了一个范例。通过宏定义的方式,可以方便地处理不定参数的情况。文章中给出了具体的代码实现,并对代码进行了解释和说明。这对于需要处理不定参数的情况的程序员来说,是一个很有用的参考资料。 ... [详细]
  • 本文介绍了一个程序,可以输出1000内能被3整除且个位数为6的所有整数。程序使用了循环和条件判断语句来筛选符合条件的整数,并将其输出。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文介绍了一个题目的解法,通过二分答案来解决问题,但困难在于如何进行检查。文章提供了一种逃逸方式,通过移动最慢的宿管来锁门时跑到更居中的位置,从而使所有合格的寝室都居中。文章还提到可以分开判断两边的情况,并使用前缀和的方式来求出在任意时刻能够到达宿管即将锁门的寝室的人数。最后,文章提到可以改成O(n)的直接枚举来解决问题。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 本文介绍了求a和b的最大公约数的计算方法,即使用gcd(a, b) = gcd(b, a%b)的公式进行计算。同时给出了一个具体的例子gcd(36, 24) = gcd(24, 12) = gcd(12, 0)。文章还给出了一个使用C语言编写的求最大公约数的程序,并解释了程序的实现原理。 ... [详细]
author-avatar
0519bobo_724
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有