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

前情提要

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

一、内存分页的作用

内存分页是一种操作系统和硬件协同工作的机制,用于将物理内存分割成固定大小的页面(通常为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_DATA
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov esp,LOADER_STACK_TOP
    mov ax, SELECTOR_VIDEO
    mov gs, ax

    mov 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描述符中视频段描述符中的段基址+0xc0000000
    mov ebx, [gdt_ptr + 2]  
    or dword [ebx + 0x18 + 4], 0xc0000000      ;视频段是第3个段描述符,每个描述符是8字节,故0x18。
					                           ;段描述符的高4字节的最高位是段基址的31~24位

    ;将gdt的基址加上0xc0000000使其成为内核所在的高地址
    add dword [gdt_ptr + 2], 0xc0000000

    add esp, 0xc0000000        ; 将栈指针同样映射到内核地址

    ; 把页目录地址赋给cr3
    mov eax, PAGE_DIR_TABLE_POS
    mov cr3, eax

    ; 打开cr0的pg位(第31位)
    mov eax, cr0
    or eax, 0x80000000
    mov cr0, eax

    ;在开启分页后,用gdt新的地址重新加载
    lgdt [gdt_ptr]             ; 重新加载

    mov byte [gs:320], 'V'     ;视频段段基址已经被更新,用字符v表示virtual addr
    mov byte [gs:322], 'i'     ;视频段段基址已经被更新,用字符v表示virtual addr
    mov byte [gs:324], 'r'     ;视频段段基址已经被更新,用字符v表示virtual addr
    mov byte [gs:326], 't'     ;视频段段基址已经被更新,用字符v表示virtual addr
    mov byte [gs:328], 'u'     ;视频段段基址已经被更新,用字符v表示virtual addr
    mov byte [gs:330], 'a'     ;视频段段基址已经被更新,用字符v表示virtual addr
    mov byte [gs:332], 'l'     ;视频段段基址已经被更新,用字符v表示virtual addr

    jmp $

setup_page:                      ; 创建页目录及页表
    mov ecx, 4096
    mov esi, 0
.clear_page_dir:                 ; 清理页目录空间
    mov byte [PAGE_DIR_TABLE_POS + esi], 0
    inc esi
    loop .clear_page_dir

.create_pde:				         ; 创建页目录
    mov eax, PAGE_DIR_TABLE_POS
    add eax, 0x1000 			     ; 此时eax为第一个页表的位置及属性,属性全为0
    mov 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, 0x1000
    mov [PAGE_DIR_TABLE_POS + 4092], eax	  ; 使最后一个目录项指向页目录表自己的地址

;下面创建第一个页表PTE,其地址为0x101000,也就是1MB+4KB的位置,需要映射前1MB内存
    mov ecx, 256				              ; 1M低端内存 / 每页大小4k = 256
    mov esi, 0
    mov 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,属性为7
    add edx,4096                              ; edx+4KB地址
    inc esi                                   ; 循环256次
    loop .create_pte

;创建内核其它页表的PDE
    mov eax, PAGE_DIR_TABLE_POS
    add eax, 0x2000 		                     ; 此时eax为第二个页表的位置
    or eax, PG_US_U | PG_RW_W | PG_P             ; 页目录项的属性为7
    mov ebx, PAGE_DIR_TABLE_POS
    mov ecx, 254			                     ; 范围为第769~1022的所有目录项数量
    mov esi, 769
.create_kernel_pde:
    mov [ebx+esi*4], eax
    inc esi
    add eax, 0x1000
    loop .create_kernel_pde
    ret

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Django官网项目 五

Writing your first Django app, part 5 | Django documentation | Django 自动测试介绍 何为自动测试 测试有系统自动完成。你只需要一次性的编写测试代码,当程序代码变更后,不需要对原来的测试人工再重新测试一遍。系统可以自动运行原来编写的测试代…

【unity资源加载与优化章】Profiler优化工具详解

👨‍💻个人主页:元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 收录于专栏:Uni…

今日AI:GPT-4.5意外曝光可能6月发布、UP主借AI识别情绪播放量186万、全球首个AI程序员诞生

欢迎来到【今日AI】栏目!这里是你每天探索人工智能世界的指南,每天我们为你呈现AI领域的热点内容,聚焦开发者,助你洞悉技术趋势、了解创新AI产品应用。 新鲜AI产品点击了解:AIbase - 智能匹配最适合您的AI产品和网站 📢一分钟速…

简述类与对象

一、两者关系 类是Java语言中最重要的数据类型,用于创建具体实例(对象) 抽象出一类事物共有的属性和行为,即数据以及数据上的操作 类是对现实事物的模拟,包含属性(成员变量)和行为&#xff0…

《如何使用C语言去下三子棋?》

目录 一、环境配置 二、功能模块 1.打印菜单 2.初始化并打印棋盘 3、行棋 3.1玩家行棋 3.2电脑行棋 4、判断是否和棋 5.判赢 三、代码实现 1、test.c文件 2、game.c文件 3、game.h文件 一、环境配置 本游戏用到三个文件,分别是两个源文件test.c game.c 和…

排序算法之快速排序算法介绍

目录 快速排序介绍 时间复杂度和稳定性 代码实现 C语言实现 c实现 java实现 快速排序介绍 快速排序(Quick Sort)使用分治法策略。 它的基本思想是:选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分;其中一部分的所有数据…

动态规划——传球问题

题目链接:1.传球游戏 - 蓝桥云课 (lanqiao.cn) 本题关键在于动态规划的数组设计,以及围坐一圈时索引的变化。 首先是动态规划,由于是求球传递m次回到第一位同学,那么就可以设计成一个二维数组,每个位置代表的是&#x…

【LeetCode热题100】240. 搜索二维矩阵 II

一.题目要求 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性: 每行的元素从左到右升序排列。 ‘每列的元素从上到下升序排列。 二.题目难度 中等 三.输入样例 示例 1: 输入:matrix [[1,4,7…

蓝桥杯 填空 卡片

蓝桥杯 填空题 卡片 解题思路&#xff1a; 我们只需要消耗完卡片的个数即可。 代码示例&#xff1a; #include<bits/stdc.h> using namespace std; int a[10]; bool isEnd(){for(int i0;i<10;i){if(a[i]-1)return false;}return true; } bool getN(int x){while(x){i…

ARM 汇编指令:(五)CMP指令

目录 1.CMP比较指令 2.指令条件码 cond 1.CMP比较指令 CMP指令是计算机指令集中的一种比较指令&#xff0c;用于比较两个操作数的大小关系或相等性&#xff0c;并根据比较结果设置或更新条件码寄存器&#xff08;或程序状态字&#xff09;的标志位。 指令格式&#xff1a;C…

jenkins + gitea 自动化部署Docker项目(vue + .NET Core)

废话不多说&#xff0c;服务先安装好Jenkins 和 gitea 理论上 gitlab 一样的实现流程 Jenkins 配置&#xff1a; 第一步装插件 安装 Generic Event 安装 gitea 相关插件 创建一个任务 设置 git 根据自己git 的认证填写对应的认证方式 构建环境记得勾选这个&#xff0c;会清…

【关注】国内外经典大模型(ChatGPT、LLaMA、Gemini、DALL·E、Midjourney、文心一言、千问等

以ChatGPT、LLaMA、Gemini、DALLE、Midjourney、Stable Diffusion、星火大模型、文心一言、千问为代表AI大语言模型带来了新一波人工智能浪潮&#xff0c;可以面向科研选题、思维导图、数据清洗、统计分析、高级编程、代码调试、算法学习、论文检索、写作、翻译、润色、文献辅助…

Python XML数据处理库之xmltodict使用详解

概要 在 Python 的开发中,处理 XML 数据是一项常见的任务。然而,Python 标准库中的 XML 解析器使用起来可能较为繁琐,需要编写大量的代码来处理 XML 数据。幸运的是,有一个名为 xmltodict 的第三方库可以帮助我们简化这个过程。本文将深入探讨 xmltodict 库的各个方面,包…

16、设计模式之观察者模式(Observer)

一、什么是观察者模式 观察者模式属于行为型模式。在程序设计中&#xff0c;观察者模式通常由两个对象组成&#xff1a;观察者和被观察者。当被观察者状态发生改变时&#xff0c;它会通知所有的观察者对象&#xff0c;使他们能够及时做出响应&#xff0c;所以也被称作“发布-订…

【git】GitHub仓库没有 Contribution activity

解决方案 检查并更改本地的 git 绑定的邮箱和名字 git config --global user.name "Your New Name" git config --global user.email "yournewemailexample.com"查询方式 git config --global user.name git config --global user.email成功显示

opencv dnn模块 示例(25) 目标检测 object_detection 之 yolov9

文章目录 1、YOLOv9 介绍2、测试2.1、官方Python测试2.1.1、正确的脚本2.2、Opencv dnn测试2.2.1、导出onnx模型2.2.2、c测试代码 2.3、测试统计 3、自定义数据及训练3.1、准备工作3.2、训练3.3、模型重参数化 1、YOLOv9 介绍 YOLOv9 是 YOLOv7 研究团队推出的最新目标检测网络…

Spring Cloud Alibaba微服务从入门到进阶(三)

Spring Cloud Alibaba是spring Cloud的子项目 Spring Cloud Alibaba的主要组件&#xff08;红框内是开源的&#xff09; Spring Cloud是快速构建分布式系统的工具集&#xff0c; Spring Cloud提供了很多分布式功能 Spring Cloud常用子项目 项目整合 Spring Cloud Alibaba …

开源导出html表格项目-easyHtml

开源导出html表格项目-easyHtml 背景介绍 背景 项目的由来&#xff0c;在面试的过程中&#xff0c;发现这个需求&#xff08;导出html表格&#xff09;比较常见&#xff0c;同时也引起我的兴趣&#xff0c;所以就有了开源项目easyHtml第一个版本 介绍 功能 支持自定义表格标…

Linux 配置ssh、scp、sftp免密登录

SSH&#xff08;Secure Shell&#xff09;是一种安全的远程登录协议&#xff0c;它使用客户端-服务器架构促进2个系统之间的安全通信&#xff0c;并允许用户远程登录服务器。在某些高可用环境下&#xff0c;服务器之间可能还需要配置免密互信&#xff0c;即基于密钥验证登录。 …

vue3 + antd二次封装a-table组件

前置条件 vue版本 v3.3.11 ant-design-vue版本 v4.1.1 内容梗概 二次封装a-table组件&#xff0c;大大提高工作效率和降低项目维护成本&#xff1b; 先看效果图 代码区域 utils.js文件 // 用于模拟接口请求 export const getRemoteTableData (data [], time 1000) >…