手写简易操作系统(六)--内存分页

前情提要

上一节我们讲到了获取物理内存,这节我们将开启内存分页

一、内存分页的作用

内存分页是一种操作系统和硬件协同工作的机制,用于将物理内存分割成固定大小的页面(通常为4KB)并将虚拟内存空间映射到这些页面上。内存分页的主要作用包括:

  1. 虚拟内存管理: 内存分页允许操作系统将进程的虚拟地址空间映射到物理内存中的不同页面上,从而实现了虚拟内存管理。这使得每个进程能够拥有独立的地址空间,提高了内存的利用率和安全性。
  2. 内存保护: 通过页表中的权限位可以对页面进行保护,例如只读、读写、执行等权限设置。这样可以保护操作系统和进程之间的内存隔离,防止非法访问或修改内存数据。
  3. 内存共享: 内存分页也支持不同进程之间的内存共享。多个进程可以将同一个物理页面映射到各自的虚拟地址空间中,从而实现共享内存的目的。
  4. 内存管理: 通过内存分页,操作系统可以更灵活地管理物理内存,如内存的分配、回收、页面置换(换出到磁盘、换入到内存)、内存压缩等操作。
  5. 减少外部碎片: 内存分页可以将物理内存划分为固定大小的页面,减少了外部碎片的产生,提高了内存的利用效率。

二、一级页表

分页机制是在分段机制的基础之上的,分段机制获取的地址就是之前我们用选择子选择到的全局描述符里面的段基址+EIP中的段内偏移地址,这两个地址相加可以获得实际的物理地址,在我们没有进行内存分页之前。

如果打开了分页机制,段部件输出的线性地址就不再等同于物理地址了,我们称之为虚拟地址,它是逻辑上的,是假的,不应该被送上地址总线。CPU必须要拿到物理地址才行,此虚拟地址对应的物理地址需要在页表中查找,这项查找工作是由页部件自动完成的。

我们直接举个例子讲述一级页表的工作方式,结合我们上节讲的GDT,假设选择子选择出来的段基址为0,偏移地址为0x1234

image-20240313160144424

三、二级页表

一级页表我们只是举个例子,用来说明页表的操作,但实际我们用的是二级页表,因为一级页表有些问题

1、一级页表中最多可容纳1M(1048576)个页表项,每个页表项是4字节,如果页表项全满的话,便是4MB大小

2、一级页表中所有页表项必须要提前建好,原因是操作系统要占用4GB虚拟地址空间的高1GB,用户进程要占用低3GB

3、每个进程都有自己的页表,进程一多,光是页表占用的空间就很可观了。

归根结底,我们要解决的是:不要一次性地将全部页表项建好,需要时动态创建页表项。

所以我们多套一层,多一个页目录项

请添加图片描述

每个进程都有自己的页表,这样的话每个进程中相同的虚拟地址可以映射到不同的物理地址中,这样的话就实现了进程与进程之间内存的隔离,顺便也解决了碎片化的问题。

请添加图片描述

四、页表项和也目录项

image-20240313160711856

P,Present,意为存在位。若为1表示该页存在于物理内存中,若为0表示该表不在物理内存中。

RW,Read/Write,意为读写位。若为1表示可读可写,若为0表示可读不可写。

US,User/Supervisor,意为普通用户/超级用户位。若为1时,任意级别都可以访问。为0,只允许特权级别为0、1、2的程序访问。

PWT,Page-level Write-Through,意为页级通写位,也称页级写透位。若为1表示此项采用通写方式,本位用来间接决定是否用此方式改善该页的访问效率。这里直接置为0就可以。

PCD,Page-level Cache Disable,意为页级高速缓存禁止位,置为0。

A,Accessed,意为访问位。若为1表示该页被CPU访问过啦。是用来在内存不足时与将不常用的内存置换到硬盘中。

D,Dirty,意为脏页位。当CPU对一个页面执行写操作时,就会设置对应页表项的D位为1。

PAT,Page Attribute Table,意为页属性表位,置0。

G, Global,意为全局位,为1表示是全局页,为0表示不是全局页。若为全局页,该页将在高速缓存TLB中一直保存,无需繁琐的置换过程。

AV L,意为Available位,即保留位。

页表同描述符表一样,是个内存中的数据结构,处理器要使用它们,必须要知道它们的物理地址,所以页表也有个专门的寄存器来存储其地址。这就是控制寄存器cr3。控制寄存器cr3用于存储页表物理地址,所以cr3寄存器又称为页目录基址寄存器(Page Directory Base Register,PDBR)

请添加图片描述

由于页目录表所在的地址要求在一个自然页内,即页目录的起始地址是4KB的整数倍,低12位地址全是0。所以,只要在cr3寄存器的第31~12位中写入物理地址的高20位就行了。PWT位和PCD位在介绍页表项时说过了,它们用于设置高速缓存相关的特性,在此将其置为0即可。

五、开启内存分页机制

开启内存分页机制分为三步

1、准备好页目录以及页表

2、在cr3寄存器的第31~12位中写入页目录物理地址的高20位

3、寄存器cr0的PG位置1。(其中cr0寄存器的各个位在进入保护模式时有讲)

我们可以看代码了,loader.s添加了一下代码

; os/src/boot/loader.s
; 下面就是保护模式下的程序了
[bits 32]
p_mode_start:mov ax, SELECTOR_DATAmov ds, axmov es, axmov ss, axmov esp,LOADER_STACK_TOPmov ax, SELECTOR_VIDEOmov gs, axmov byte [gs:320], 'M'mov byte [gs:322], 'A'mov byte [gs:324], 'I'mov byte [gs:326], 'N'call setup_page ; 创建页目录及页表并初始化页内存位图;要将描述符表地址及偏移量写入内存gdt_ptr,一会用新地址重新加载sgdt [gdt_ptr]	      ; 存储到原来gdt的位置;将gdt描述符中视频段描述符中的段基址+0xc0000000mov ebx, [gdt_ptr + 2]  or dword [ebx + 0x18 + 4], 0xc0000000      ;视频段是第3个段描述符,每个描述符是8字节,故0x18。;段描述符的高4字节的最高位是段基址的31~24位;将gdt的基址加上0xc0000000使其成为内核所在的高地址add dword [gdt_ptr + 2], 0xc0000000add esp, 0xc0000000        ; 将栈指针同样映射到内核地址; 把页目录地址赋给cr3mov eax, PAGE_DIR_TABLE_POSmov cr3, eax; 打开cr0的pg位(第31位)mov eax, cr0or eax, 0x80000000mov cr0, eax;在开启分页后,用gdt新的地址重新加载lgdt [gdt_ptr]             ; 重新加载mov byte [gs:320], 'V'     ;视频段段基址已经被更新,用字符v表示virtual addrmov byte [gs:322], 'i'     ;视频段段基址已经被更新,用字符v表示virtual addrmov byte [gs:324], 'r'     ;视频段段基址已经被更新,用字符v表示virtual addrmov byte [gs:326], 't'     ;视频段段基址已经被更新,用字符v表示virtual addrmov byte [gs:328], 'u'     ;视频段段基址已经被更新,用字符v表示virtual addrmov byte [gs:330], 'a'     ;视频段段基址已经被更新,用字符v表示virtual addrmov byte [gs:332], 'l'     ;视频段段基址已经被更新,用字符v表示virtual addrjmp $setup_page:                      ; 创建页目录及页表mov ecx, 4096mov esi, 0
.clear_page_dir:                 ; 清理页目录空间mov byte [PAGE_DIR_TABLE_POS + esi], 0inc esiloop .clear_page_dir.create_pde:				         ; 创建页目录mov eax, PAGE_DIR_TABLE_POSadd eax, 0x1000 			     ; 此时eax为第一个页表的位置及属性,属性全为0mov ebx, eax				     ; 此处为ebx赋值,是为.create_pte做准备,ebx为基址。;   下面将页目录项0和0xc00都存为第一个页表的地址,;   一个页表可表示4MB内存,这样0xc03fffff以下的地址和0x003fffff以下的地址都指向相同的页表,;   这是为将地址映射为内核地址做准备or eax, PG_US_U | PG_RW_W | PG_P	      ; 页目录项的属性RW和P位为1,US为1,表示用户属性,所有特权级别都可以访问.mov [PAGE_DIR_TABLE_POS + 0x0], eax       ; 第1个目录项,在页目录表中的第1个目录项写入第一个页表的位置(0x101000)及属性(7)mov [PAGE_DIR_TABLE_POS + 0xc00], eax     ; 一个页表项占用4字节,0xc00表示第768个页表占用的目录项,0xc00以上的目录项用于内核空间,; 也就是页表的0xc0000000~0xffffffff共计1G属于内核,0x0~0xbfffffff共计3G属于用户进程.sub eax, 0x1000mov [PAGE_DIR_TABLE_POS + 4092], eax	  ; 使最后一个目录项指向页目录表自己的地址;下面创建第一个页表PTE,其地址为0x101000,也就是1MB+4KB的位置,需要映射前1MB内存mov ecx, 256				              ; 1M低端内存 / 每页大小4k = 256mov esi, 0mov edx, PG_US_U | PG_RW_W | PG_P	      ; 属性为7,US=1,RW=1,P=1
.create_pte:mov [ebx+esi*4],edx			              ; 此时的ebx已经在上面成为了第一个页表的地址,edx地址为0,属性为7add edx,4096                              ; edx+4KB地址inc esi                                   ; 循环256次loop .create_pte;创建内核其它页表的PDEmov eax, PAGE_DIR_TABLE_POSadd eax, 0x2000 		                     ; 此时eax为第二个页表的位置or eax, PG_US_U | PG_RW_W | PG_P             ; 页目录项的属性为7mov ebx, PAGE_DIR_TABLE_POSmov ecx, 254			                     ; 范围为第769~1022的所有目录项数量mov esi, 769
.create_kernel_pde:mov [ebx+esi*4], eaxinc esiadd eax, 0x1000loop .create_kernel_pderet

boot.inc 添加了如下的宏定义

PAGE_DIR_TABLE_POS equ 0x100000
PG_P  equ   1b
PG_RW_R	 equ  00b 
PG_RW_W	 equ  10b 
PG_US_S	 equ  000b 
PG_US_U	 equ  100b 

我们可以画个图,看一下现在的内存中的页目录和页表是怎么回事

image-20240313173352367

其实有两个页目录项指向了第一个页表,第一个页目录以及第768个页目录,第768个页目录意味着虚拟地址为 1100_0000_00 开头的地址,这一部分指向了第一个PTE,第一个PTE首先包含了1024项,但是只有前256项被用到,这个地址范围是 0000_0000_00 ~ 0100_0000_00 ,是虚拟地址的中10位,最后十二位就是在相应内存块的位置。

这部分作用就是将物理地址 0x00000~0xfffff 映射到虚拟地址 0xc0000000 ~ 0xc00fffff

这样我们的内核代码就放在物理地址1MB以下的位置即可。

最后看一下成果

请添加图片描述

六、修改页目录表与页表

最后一个页目录是指向了自己,这也为修改页目录表埋下了机会,否则内存虚拟化后,无法通过直接访问物理地址来访问内存,页目录表也不在虚拟内存可以访问的空间内,那么这个表相当于直接丢失了,无法访问

可以观察到有三个奇怪的地址映射,这就是最后一个页目录指向自己导致的

0xffc00000-0xffc00fff -> 0x000000101000-0x000000101fff
0xfff00000-0xffffefff -> 0x000000101000-0x0000001fffff
0xfffff000-0xffffffff -> 0x000000100000-0x000000100fff

1、若虚拟地址的高十位为 11_1111_1111 ,那么索引为当前的页目录表,所以,当前的页目录表就被当做了页表

2、若虚拟地址的中十位为 11_1111_1111 ,那么索引为当前的页目录表(被当做页表)的最后一项,指向的还是当前的页目录表,再配合虚拟地址的后12位就可以修改页目录表了,这就是 0xfffff000-0xffffffff -> 0x000000100000-0x000000100fff 这个地址映射的由来。

3、若虚拟地址的中十位为 00_0000_0000, 那么索引为当前的页目录表(被当做页表)的第一项,指向的是第一项的PTE页表,再配合虚拟地址的后12位就可以修改第一个页表了,这就是 ,0xffc00000-0xffc00fff -> 0x000000101000-0x000000101fff 这个地址映射的由来。

4、若虚拟地址的中十位为 11_0000_0000 ~ 11_1111_1111, 那么索引为当前的页目录表(被当做页表)的第768项到1024项,指向的是第768项到1024项的PTE页表,再配合虚拟地址的后12位就可以修改这些页表了,这就是 ,0xfff00000-0xffffefff -> 0x000000101000-0x0000001fffff 这个地址映射的由来。

拿到了这个虚拟地址,我们就可以直接访问这块内存对PDE与PTE进行修改。

结束语

这节讲述了内存的分页机制,以及如何利用这种分页机制,如何在已经开启分页机制的基础上对PDE与PTE进行修改。

下节我们将开启内核,以及用C语言编程

我将代码放在了github上,大家可以自行下载 https://github.com/lyajpunov/os.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/744101.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

填涂颜色(洛谷)

题目 原题 由数字 0 0 0 组成的方阵中,有一任意形状的由数字 1 1 1 构成的闭合圈。现要求把闭合圈内的所有空间都填写成 2 2 2。例如: 6 6 6\times 6 66 的方阵( n 6 n6 n6),涂色前和涂色后的方阵如下&#xff1…

【更新】上市公司“宽带中国”战略数据集(2000-2022年)

参照李万利(2022)、薛成(2020)等人的做法,根据企业所在城市入选“宽带中国”试点战略的批次构建DID。如果样本期间内企业所在城市被评选为“宽带中国” 试点城市,则该地区企业样本在入选当年及以后年份取1&…

video标签的src和srcObject属性的区别

在HTML的<video>标签中&#xff0c;src和srcObject是两个不同的属性。 src属性&#xff1a;用于指定视频资源的URL。可以通过设置该属性来指定一个视频文件的路径或者网络地址&#xff0c;例如 <video src"video.mp4"></video> 通过设置src属性&a…

打卡学习kubernetes——了解k8s基本概念

目录 1 Container 2 Pod 3 Node 4 Namespace 5 Service 6 Label 7 Annotations 8 Volume 1 Container Container(容器)是一种便携式、轻量级的操作系统级虚拟化技术。它使用namespace隔离不同的软件运行环境&#xff0c;并通过镜像自包含软件的运行环境&#xff0c;从而…

计算机二级真题讲解每日一题:《高校分类》

描述‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‮‬ 修改编程模板&#xff0c;用…

Focal and Global Knowledge Distillation forDetectors

摘要 文章指出&#xff0c;在目标检测中&#xff0c;教师和学生在不同领域的特征差异很大&#xff0c;尤其是在前景和背景中。如果我们 平等地蒸馏它们&#xff0c;特征图之间的不均匀差异将对蒸馏产生负面影响。因此&#xff0c;我们提出了局部和全局蒸馏。局部蒸馏分离前景和…

【Spring Boot系列】快速上手 Spring Boot

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

C语言(数组)单元练习二

单项选择题 1. A. float f[3,4]&#xff1b; B. int a[ ][4]&#xff1b; C. char c(3)&#xff1b; D. double d[32][4]&#xff1b; 2. 一维数组初始化时,若对部分数组元素赋初值,则下面正确的说法是______。 A. 可以只对数组的前几个元素赋初值 B. 可以只对数…

macOS - 获取硬件设备信息

文章目录 1、CPU获取方式 一&#xff1a; system_profiler获取方式二&#xff1a;sysctl&#xff0c; machdepmachdep 2、内存3、硬盘4、显卡5、声卡6、光驱7、系统序列号8、型号标识符9、UUID 等信息 10. 计算机名称 1、CPU 获取方式 一&#xff1a; system_profiler % syst…

深信服技术认证“SCCA-C”划重点:深信服应用交付AD

为帮助大家更加系统化地学习云计算知识&#xff0c;高效通过云计算工程师认证&#xff0c;深信服特推出“SCCA-C认证备考秘笈”&#xff0c;共十期内容。“考试重点”内容框架&#xff0c;帮助大家快速get重点知识 划重点来啦 *点击图片放大展示 深信服云计算认证&#xff08;S…

中国工程精英智创数字工厂——2023纵览基础设施大会暨光辉大奖赛观察 (下)

中国工程精英智创数字工厂 ——2023纵览基础设施大会暨光辉大奖赛观察 &#xff08;下&#xff09; 吴付标 中国制造的尽头是智能化、智慧化&#xff0c;这一趋势正在加速前进。2022年&#xff0c;中国以50座达沃斯论坛盖章认证的“灯塔工厂”数量冠绝全球&#xff0c;而“数…

活动预告:如何培养高质量应用型医学人才?

在大数据时代与“新医科”建设的背景下&#xff0c;掌握先进的医学数据处理技术成为了医学研究与应用的重要技能。 为了更好地培养社会所需要的高质量应用型医学人才&#xff0c;许多高校已经在广泛地开展面向医学生的医学数据分析教学工作。 在“课-训-赛”育人才系列活动的…

详解Python中%r和%s的区别及用法

首先看下面的定义&#xff1a; %r用rper()方法处理对象 %s用str()方法处理对象 函数str() 用于将值转化为适于人阅读的形式&#xff0c;而repr() 转化为供解释器读取的形式&#xff08;如果没有等价的语法&#xff0c;则会发生SyntaxError 异常&#xff09; 某对象没有适于人…

Spring Data访问 MongoDB(十五)----MongoDB特有的数据操作方法

【Spring连载】使用Spring Data访问 MongoDB&#xff08;十五&#xff09;----MongoDB特有的数据操作方法 一、更新方法二、删除方法 除了 查询方法之外&#xff0c;还可以使用专门的方法更新数据。 一、更新方法 你还可以使用上表中的关键字创建查询&#xff0c;以标识匹配的…

rust 阿里云oss操作

在rust中如何操作阿里云oss文件&#xff0c;阿里云官方并没有提供这样的sdk&#xff0c;我们可以使用社区的aliyun-oss-rust-sdk库crate来实现。 功能列表 文件下载签名下载签名上传获取上传对象的policy上传本地文件上传内存文件文件删除 添加依赖 [dependencies] # 异步 a…

面试常问:为什么 Vite 速度比 Webpack 快?

前言 最近作者在学习 webpack 相关的知识&#xff0c;之前一直对这个问题不是特别了解&#xff0c;甚至讲不出个123....&#xff0c;这个问题在面试中也是常见的&#xff0c;作者在学习的过程当中总结了以下几点&#xff0c;在这里分享给大家看一下&#xff0c;当然最重要的是…

面试六--TCP粘包问题

1.流式传输协议 流式传输协议&#xff08;Streaming Protocol&#xff09;是一种用于在网络上传输数据的通信协议&#xff0c;它允许数据以连续的流的形式进行传输&#xff0c;而不是一次性发送完整的数据包。流式传输协议即协议的内容是像流水一样的字节流&#xff0c;内容与内…

如何使用vue定义组件之——全局or局部

在Vue中定义一个组件&#xff0c;您需要使用Vue.component()方法来全局注册组件&#xff0c;或者在Vue实例的选项中局部注册组件&#xff0c;以下是一个具体的步骤&#xff1f; 1.全局注册组件&#xff1a; 使用Vue.component()方法来注册一个全局组件&#xff0c;这意味着这个…

Flutter第三弹:常用的Widget

目标&#xff1a; 1&#xff09;常用的Widget有哪些&#xff1f;有什么特征&#xff1f; 2&#xff09;开发一个简单的登录页面。 一、Flutter常用Widget 对于Flutter来说&#xff0c;一切皆Widget. 常用的Widget&#xff0c;包括一些基础功能的Widget. 控件名称功能备注…

代码随想录day19(2)二叉树:二叉树的最大深度(leetcode104)

题目要求&#xff1a;求出二叉树的最大深度 思路&#xff1a;首先要区分二叉树的高度与深度。二叉树的高度是任一结点到叶子结点的距离&#xff0c;而二叉树的深度指的是任一节点到根节点的距离&#xff08;从1开始&#xff09;。所以求高度使用后序遍历&#xff08;从下往上&…