开启内存分页

简介: 开启内存分页

x86系列处理器上的内存分页

  • 硬件层直接支持内存分页机制
  • 默认情况下并没有打开分页机制
  • 分页机制开启后,使用二级页表对内存进行管理

启用分页机制步骤

  1. 创建页目录表及页表
  2. 将页目录表首地址写入控制寄存器 cr3
  3. 寄存器 cr0 的 PG 位置 1

启动分页机制代码实现

  • 找到以前的代码 loader.asm,在这个代码的基础上做更改
  • 回顾代码的框架
boot.asm 跳转到 loader.asm,并打印 “Welcome to KOS.” 
      |
CODE16_START   ; 实模式,打印 “Loader...”
      |
CODE32_START   ; 进入保护模式,打印 “Enter protection”
  • 先上改动后的完整代码 loader.asm
  • 为了程序的美观,我们把开启分页机制的工作都封装成了一个函数 setup_page
  • 先来看一下开启分页机制的第 2 步和第 3 步,这两步比较简单
; 将页目录表首地址写入控制寄存器 cr3
mov eax, PAGE_DIR_BASE
mov cr3, eax
; 寄存器 cr0 的 PG 位置 1
mov eax, cr0
or  eax, 0x80000000
mov cr0, eax
  • 下面我们就实现页目录表及页表的创建
  • 知识点准备
  • 页目录占用 1 内存页(可访问 1024 个子页表)
  • 单个子页表占用 1 内存页(可访问 1024 个页面)
  • 页面起始地址按 4K 字节对齐(4096 的整数倍)
  • 分页后可访问的虚拟地址空间为:4K * (1024 * 1024) = 4G
  • 首先,我们脑子中要有页表的整体样子,如下图(由下往上看)。PDE:页目录项;PTE:页表项

  • 从图中可以看出,为了让页表和更紧凑一些,页目录后面紧跟着页表,其实这不是必须的
  • 页目录表和页表都存在于物理内存之中,它们自身也要有个“安身”的地方,我们就把页目录表放在物理地址 0x100000 处(),页目录本身占 4KB,所以第一个页表的物理地址是 0x101000

  • 第一个页目录项(忽略属性)PDE0 的值为 0x101000,第二个页目录项(忽略属性)PDE1 的值为 0x102000,依次类推...
  • 第一个页表项(忽略属性)PTE0 的值为 0x0(第一个页表我们就让它指向实际物理内存 0x0 处),第二个页表录项(忽略属性)PTE1 的值为 0x1000,依次类推... 不过这在图中没有显示出来
  • 从 PDE0 找到 PTE0,然后再从 PTE0 找到实际内存 0x0;从 PDE1 找到 PTE1,然后再从 PTE1 找到实际内存 0x1000;从 PDE2 找到 PTE2,然后再从 PTE2 找到实际内存 0x2000。依次类推...
  • 在实现创建页目录表和页表代码之前,我们先学习一个汇编小知识
; 传送指令
stosb / stosw / stosd
  • 把 al / ax / eax 中的值存储到 es:edi 指向的内存单元中
  • 同时 edi 的值根据方向标志自增或自减(cld:自增 / std:自减)
  • 举例:
mov ax, 0
mov es, ax  ; es = 0
mov edi, 0  ; edi = 0
mov eax, 0xFF
cld
stosd
; 程序执行到这里后
; 地址 0:0 所指向的内存被赋值 0xFF
; edi = 0x04  ; eax 为 4 字节,顾 edi 自增 4 字节
  • 由于我们是在保护模式下开启分页机制,所以把页目录表和页表也当成一种数据段处理,于是段描述符相关操作来一遍
; 增加页目录表和页表的段描述符和选择子
PAGE_DIR_DESC : Descriptor PAGE_DIR_BASE, 4095,       DA_DRW + DA_32
; 子页表我们把颗粒度 G 设为 1,则内存单位就变为了 4K ,于是子页表内存数据段总大小 = (1023+1)*4K 
PAGE_TAB_DESC : Descriptor PAGE_TAB_BASE, 1023,       DA_DRW + DA_32 + DA_LIMIT_4K
PAGE_DIR_SELECTOR  equ   (0x0005 << 3) + SA_RPL0 + SA_TIG
PAGE_TAB_SELECTOR  equ   (0x0006 << 3) + SA_RPL0 + SA_TIG
  • 就剩下最后的页目录表和页表实现代码实现了,用 C 语言来理解就是循环给页目录表和页表这两个数组填充每一个元素
; 创建页目录表
    mov ax, PAGE_DIR_SELECTOR
    mov es, ax
    mov edi, 0          ; es:edi -> PAGE_DIR_SELECTOR:0
    mov cx, 1024        ; 循环 1024 次,页目录共 1024 项
    mov eax, PAGE_TAB_BASE + PG_P + PG_US_U + PG_RW_W ; eax = 第一个子页表的地址和属性
    cld                 ; edi 自增
.creat_pde:             ; 创建页目录项
    stosd               ; 把 eax 中的值写入 [es:edi] 指向的内存中
    add eax, 4096       ; eax = eax + 4K
    loop .creat_pde
; 创建页表
    mov ax, PAGE_TAB_SELECTOR
    mov es, ax
    mov edi, 0          ; es:edi -> PAGE_TAB_SELECTOR:0
    mov ecx, 1024*1024  ;  循环 1024*1024 次,共 1024 个页表,每个页表有 1024 个页表项
    mov eax, PG_P + PG_US_U + PG_RW_W ; 只有属性,没有基地址,因为第一个页表项指向的就是物理地址 0x0 处
    cld
.creat_pte:             ; 创建页表项
    stosd               ; 把 eax 中的值写入 [es:edi] 指向的内存中
    add eax, 4096       ; eax = eax + 4K
    loop .creat_pte

发现

  • 我们来改动一下代码
; 原代码 call setup_page 开启分页机制后直接就死循环了
mov ebp, msg2Offset
mov bl, 0x0F        ; 打印属性,黑底白字
; 坐标 (0, 2)
mov dl, 0x00
mov dh, 0x02
call print_str_32
call setup_page
jmp $
  • 改动后
; 改动后,先 call setup_page 开启分页机制,再打印"Enter protection"
call setup_page
mov ebp, msg2Offset
mov bl, 0x0F        ; 打印属性,黑底白字
; 坐标 (0, 2)
mov dl, 0x00
mov dh, 0x02
call print_str_32
jmp $
  • 编译执行,发现程序居然跟改动前一样,按道理开启分页机制后,映射后的地址不应该改变,导致开启分页机制后的程序无法执行吗?
  • 猜测:我们的分页机制没有开启成功?
  • 验证一下,把 add eax, 4096 注释掉,破坏页表的创建
.creat_pte:         ; 创建页表项
stosd               ; 把 eax 中的值写入 [es:edi] 指向的内存中
; add eax, 4096       ; eax = eax + 4K
loop .creat_pte
  • 编译运行一下,发现 "Enter protection" 未打印出来,程序崩溃了,感觉分页机制好像是开启了的
  • 这到底是什么原因呢?
  • 以一个具体地址 0x00804ABC,分析其映射后的地址变成了多少。我们把 0x00804ABC 转成二进制 100000000100101010111100,其中低 12 位的值为:0xABC,中 10 位的值为:0x4,高 10 位的值为:0x2。我们的第一个子页表对应的实际物理地址 0x0 处,0x00804ABC 映射后的实际物理地址为:4096 * (1024 * 0x02 + 0x04) + 0xABC = 0x00804ABC。映射前后的地址居然是一样的,自己可以再随便换个地址映射一下看看是不是一样。
  • 让我们来反汇编后断点调试证明
  • make 之后反汇编一下
ndisasm -o 0x900 loader.bin  > loader.txt
  • 找到程序最后死循环处(jmp $):0xA14,使用 “b 0xA14”在 0xA14 处打断点,执行 “c” 命令后程序运行到断点处,根据页目录表首地址 0x100000 和 高 10 位的值 0x2,于是我们找到 0x00804ABC 地址对应的页目录项实际对应的物理地址为 0x100000 + 2*4 = 0x100008,用 “xp 0x100008”命令 查看 0x100008 地址处的值为 0x00103007,其中低 12 位是属性,高 20 位为对应的子页表首地址,于是我们再根据中 10 位的值 0x4 计算出对应的子页表中的页表项地址为 0x103000 + 4*4 = 0x103010,然后我们使用 “xp 0x103010” 命令查看 0x103010 地址处的值,发现其值为 0x00804007,去掉低 12 位的属性,高 20 位的地址为 0x00804000,最后我们再加上偏移量 0xABC 得到最终映射的物理地址为 0x00804ABC
<bochs:3> xp 0x100008
0x00100008 <bogus+       0>:    0x00103007
<bochs:4> xp 0x103010
0x00103010 <bogus+       0>:    0x00804007
  • 当前的分页方式使得:任意虚拟地址都被直接映射为物理地址,因此,setup_page 调用的时间并不影响程序的执行结果
  • 由此可以看出,我们目前构建的页目录和页表其实是一种非常简单的方式,简单到直接将虚拟地址映射成物理地址
目录
相关文章
|
6月前
|
算法 程序员
深入理解操作系统内存管理:分页系统的优势与挑战
【4月更文挑战第7天】 在现代操作系统中,内存管理是一项至关重要的任务,它确保了计算机能够高效、安全地运行各种程序。分页系统作为内存管理的一种技术,通过将物理内存分割成固定大小的单元——页面,为每个运行的程序提供了一种独立且连续的内存地址空间。该技术不仅简化了内存分配,还允许更高效的内存使用和保护。本文探讨了分页系统的核心原理,优势以及面临的挑战,旨在为读者揭示其在操作系统设计中的重要性。
|
12月前
|
存储 网络虚拟化 索引
【OSTEP】分页(Paging) | 页表中究竟有什么 | 页表存在哪 | 内存追踪
【OSTEP】分页(Paging) | 页表中究竟有什么 | 页表存在哪 | 内存追踪
277 0
|
6月前
|
缓存 算法 安全
深入理解操作系统内存管理:分页系统的优势与挑战
【5月更文挑战第31天】 在现代操作系统中,内存管理是核心功能之一。分页系统作为内存管理的一种流行技术,其设计哲学基于时间和空间的局部性原理,旨在提高内存利用率和系统性能。本文将探讨分页系统的关键优势及其面临的挑战,包括页面置换算法、内存碎片问题以及虚拟到物理地址转换的复杂性。通过对分页机制的深入分析,我们揭示了它在多任务处理环境中如何允许多个进程共享主存资源,并保证了操作系统的稳定性与高效性。
|
6月前
|
缓存 算法
深入理解操作系统内存管理:分页系统的优势与挑战
【5月更文挑战第28天】 在现代操作系统中,内存管理是一项至关重要的功能,它不仅确保了系统的稳定运行,还提升了资源的利用效率。本文将探讨分页系统这一核心概念,并分析其在内存管理中的优势和面临的挑战。通过剖析分页机制的工作原理及其对虚拟内存实现的重要性,我们进一步阐述了它在多任务处理和内存保护方面的作用。同时,文章也将讨论分页带来的性能开销、页面置换算法的设计以及它们如何影响系统的整体性能。
|
6月前
|
存储 缓存 算法
深入理解操作系统内存管理:分页系统的优势与挑战构建高效Android应用:探究Kotlin协程的优势与实践
【5月更文挑战第27天】 在现代计算机系统中,内存管理是操作系统的核心功能之一。分页系统作为一种内存管理技术,通过将物理内存划分为固定大小的单元——页面,为每个运行的程序提供独立的虚拟地址空间。这种机制不仅提高了内存的使用效率,还为多任务环境提供了必要的隔离性。然而,分页系统的实现也带来了一系列的挑战,包括页面置换算法的选择、内存抖动问题以及TLB(Translation Lookaside Buffer)的管理等。本文旨在探讨分页系统的原理、优势及其面临的挑战,并通过分析现有解决方案,提出可能的改进措施。
|
12月前
|
存储 算法 程序员
【OSTEP】超越物理内存:机制 | 请求分页 | 交换位与存在位 | 页错误
【OSTEP】超越物理内存:机制 | 请求分页 | 交换位与存在位 | 页错误
65 0
|
6月前
|
安全 Linux Windows
深入理解操作系统内存管理:分页与分段的融合
【4月更文挑战第30天】 在现代操作系统中,内存管理是确保多任务环境稳定运行的关键。本文将深入探讨分页和分段两种内存管理技术,并分析它们如何相互融合以提供更为高效、安全的内存使用策略。通过对比这两种技术的优缺点,我们将探索现代操作系统中它们的综合应用,以及这种融合对操作系统设计和性能的影响。
|
6月前
|
存储
CPU的内存分页
CPU的内存分页是一种内存管理机制,旨在优化内存的使用效率和程序的运行效率。在现代计算机系统中,整个虚拟和物理内存空间被切割成固定大小的块,称为页(Page)和帧(Frame)。页用于虚拟地址空间,而帧用于物理内存空间。这些页和帧的大小通常是固定的,比如常见的4KB。 CPU通过内存管理单元(MMU)来实现虚拟地址到物理地址的转换。这个转换过程是通过页表来完成的,页表存储在内存中,并保存了页号与页帧号的映射关系。当CPU需要访问某个虚拟地址时,它会查阅页表,找到对应的物理地址,然后完成内存访问。 内存分页的主要好处有以下几点: 1. **减小换入换出的粒度**:内存分页允许操作系统以更小
42 0
|
6月前
|
缓存 算法 安全
深入理解操作系统内存管理:分页系统的优势与挑战
【2月更文挑战第30天】 在现代操作系统中,内存管理是核心功能之一,它负责将有限的物理内存资源分配给多个并发运行的进程。分页系统作为内存管理的一种流行技术,其通过虚拟到物理地址的映射提供了程序的逻辑地址空间,并允许更高效的内存分配和保护。本文旨在探讨分页系统的关键优势,包括其如何提升内存利用率、实现内存保护以及支持多任务处理。同时,我们也将分析分页机制带来的挑战,诸如页面置换算法的效率问题、页表管理和TLB(Translation Lookaside Buffer)的维护等。
|
6月前
|
存储 算法
深入理解操作系统内存管理:分页系统的优势与挑战
【2月更文挑战第29天】 在现代操作系统中,内存管理是核心功能之一,它负责有效地分配、跟踪和回收内存资源。分页系统作为一种内存管理技术,已经成为大多数操作系统的标准配置。本文将探讨分页系统的原理、优势以及面临的挑战。通过对分页机制的深入分析,我们旨在提供一个全面的视角,以帮助读者更好地理解这一关键技术如何影响操作系统的性能和稳定性。