操作系统:保护模式(二)内存模型

news/2024/12/24 3:51:31/文章来源:https://www.cnblogs.com/himu-qaq/p/18442985

平坦内存模型

现代操作系统一般不会使用过于复杂的分段机制,而是采用平坦内存模型 + 分页模型来管理内存。

平坦内存模型(Flat Memory Model),这是现代操作系统(如 Linux 和 Windows)常用的内存模型。在这种模型中,所有段的段基址都为 0,段界限为 4GB,使得整个内存空间看起来像一个连续的内存块. 因此进程可以访问整个虚拟内存空间。这种模型简化了内存管理,使得代码更加接近于没有段式管理的模型。

在 32 位平坦内存模型中,虽然段的地址范围被设置为 0 到 4GB,但这并不意味着系统实际上必须有 4GB 的物理内存。

汇编定义

[SECTION .gdt]
CODE_DESC: dd 0x0000FFFFdd 0x00CF9800
DATA_STACK_DESC: dd 0x0000FFFFdd 0x00CF9200

代码段

数据段

获取物理内存容量

一般而言有以下办法:

  • INT 0x12 仅获取1MB以下的常规内存大小。
  • INT 0x15, AH=0x88 可以获取1MB以上的扩展内存大小,但不包括详细的内存布局。
  • INT 0x15, EAX=0xE820 是获取物理内存布局的最佳选择,适用于现代计算机。
  • 读取CMOS 可以获取扩展内存,但仅限于64MB以下。
  • UEFI 提供了更多现代化的服务来获取内存信息,但需要在EFI环境中运行。

现代一般使用 INT 0x15, EAX=0xE820 方法。

INT 0x15, EAX=0xE820 物理内存布局

一次中断只会返回一个 ARDS 也即一种布局,实际可用物理内存需要遍历 所有 Type == 1 的布局,累加并保存到缓冲区(因为保护模式下中断已不可使用)。

输入

  • EAX = 0xE820: 指定功能号,表示请求物理内存布局信息。
  • EBX: 指定调用的偏移量。第一次调用时设置为 0,后续调用时使用上一次调用返回的 EBX 继续查询,直到 EBX 返回 0 表示查询结束。
  • ES:DI: 指向保存结果的缓冲区的指针,通常为一个 ARDS 结构
struct ARDS /* 20 bits */ {uint64_t base_address;  // 内存区域的起始地址uint64_t length;        // 内存区域的长度(以字节为单位)uint32_t type;          // 内存类型, 操作系统一般只可以使用 type == 1 的内存uint32_t extended;      // 扩展属性,通常为 0
};
  • ECX: 指定 ARDS 结构的大小,通常为 20(字节数),表示缓冲区的大小。
  • EDX: 必须为签名 'SMAP'(ASCII),即 EDX = 0x534D4150

输出

  • EAX: 如果成功,则保持为 0x534D4150,即 'SMAP' 签名。
  • EBX: 如果 EBX = 0,表示已返回所有的内存区块信息,否则应将返回的 EBX 用于下一次调用。
  • CF(Carry Flag): 如果 CF = 0 表示成功,如果 CF = 1 表示错误(此时 EAX 可能包含错误代码)。
  • ES:DI: 包含内存区域描述结构 ARDS 的数据。

示例

    ; -------------------------; Memory; -------------------------xor ebx, ebxmov edx, 0x534D4150 ; "SMAP"mov di, ArdsBuf
.try_e820:mov eax, 0x0000E820mov ecx, 20int 0x15jc .e820_failadd di, cxinc word [ArdsCnt]cmp ebx, 0jnz .try_e820mov cx, word [ArdsCnt]mov ebx, ArdsBufxor edx, edx
.find_usable_memory:mov eax, [ebx]add eax, [ebx + 8]add ebx, 20cmp edx, eaxjge .next_ardmov edx, eax
.next_ard:loop .find_usable_memoryjmp .success_get_memory
.e820_fail:mov ax, Messagemov bx, 0x07mov cx, MessageLengthxor dx, dxcall printstrhalt
.success_get_memory:mov [TotalMemoryBytes], edx

分页机制

对于编译器而言,地址本身是连续的,被称作线性地址。在只分段的情况下,CPU 认为线性地址等同于物理地址,这种传统模型有以下缺陷:

  • 分段模式要求每个段的内存是连续的,因此当需要分配大块内存时,可能会因为内存碎片的问题导致无法找到足够大的连续空间。这在程序运行过程中,特别是随着内存分配和释放的频繁进行,会造成内存的碎片化。
  • 分段模式无法有效实现进程间的完全隔离。不同的进程共享相同的内存空间模型,如果段描述符配置错误,可能导致进程之间的内存冲突或数据泄露。

分页机制是通过将内存分为固定大小的页来进行管理,每个页在物理内存中的位置可以不连续。分页机制是 CPU 从硬件层面就支持的功能。因此一旦启用分页机制,汇编代码的线性的地址都会被CPU根据页表自动转化为物理地址。

一级页表

CPU 规定一页的长度为 4KB, 于是 4GB 空间被分为 1 M 页,32位的线性地址被分为两部分:高20位为页表索引,低12位为页内偏移。

二级页表

一个页表项为 4个字节,完整的物理内存映射至少需要 4MB 空间以建立一级页表映射。每个进程都需要自己独立的地址空间,因此一级页表方案的内存消耗过大。

x86 默认使用二级页表分页,将1M个页平均放置到1K个页表中,每个页表包含1K个页表项,每个页表项4字节,即二级页表这个大小恰好是4KB大小,即一个页。与一级页表不同的是一级页表必须提前建立,每个进程都需要 4MB 空间进行映射,但是二级页表除了页目录表以外,其二级页表是动态建立的。极大的节约了空间。

传统x86二级分页,将线性空间分为:

  • 高10位:用来定位页目录表中的一个页目录项 (PDE)(页目录项中包含页表的物理地址
  • 中间10位:用于在某个页表中定位页表项 (PTE)
  • 低12位:页内偏移量

由于 PDE,PTE 的均为 4 字节长,在访问一个线性地址时:

  • 用虚拟地址的高10位乘以4,再加上页目录表的物理地址,便是页目录项的物理地址,读取该物理地址处的内容,获得页表的物理地址
  • 用虚拟地址的中间10位乘以4,再加上一步获得的页表的物理地址,便是页表项的的物理地址,读取页表项的内容,便可从页表项的数据结构中获取我们需要访问的物理地址
  • 将该物理地址再加上虚拟地址的低12位,便是最终我们要访问的物理地址

页表项与页目录项一致:

位位置 属性位名称 含义 常见取值
0 P (Present) 页是否存在 0: 不存在,1: 存在
1 R/W (Read/Write) 页面读写权限 0: 只读,1: 可读写
2 U/S (User/Supervisor) 用户模式和内核模式的访问权限 0: 内核模式,1: 用户模式
3 PWT (Page Write-Through) 写策略 0: 回写(Write-back),1: 写通(Write-through)
4 PCD (Page Cache Disable) 缓存策略 0: 允许缓存,1: 禁止缓存
5 A (Accessed) 该页是否被访问过 0: 未访问,1: 已访问
6 D (Dirty) 该页是否被修改过(仅页表项有效) 0: 未修改,1: 已修改
7 PS (Page Size) 页的大小 0: 4KB 页,1: 4MB 页
8 G (Global) 是否为全局页面(TLB切换时不刷新) 0: 非全局,1: 全局
9-11 AVL (Available) 保留位,操作系统可用 未定义,操作系统自定义使用
12-31 Base Address 页表或页的物理地址(对齐到4KB,低12位为0) 页表或物理页的基地址

特别的,唯一的页目录物理地址需要提前存放到 CR3 寄存器(页目录基址寄存器):

PCD, PWT 位一般都取0. 因此低12位均为0.

多进程与分页机制

  • 页表是多进程操作系统实现虚拟内存的基础,每个进程有独立的页表,保证了内存隔离。
  • 操作系统通过按需分配内存、共享内存、分页换页等机制,动态管理进程的虚拟地址空间。
  • 虚拟地址空间的划分通常包括用户空间和内核空间,进程通过页表实现虚拟地址到物理地址的映射。

用户进程通常依赖操作系统的系统调用。也即内核空间的代码,操作系统在划分地址空间时,通常会将内核映射到高位地址。所有进程的内核空间都实际上对应同一片物理地址。

示例: 类 Linux 的地址空间映射

startup_page:mov ecx, 4096mov esi, 0
.clear_page_dir:mov byte [PAGE_DIR_TABLE_BASE + esi], 0inc esiloop .clear_page_dir
.create_pde:mov eax, PAGE_DIR_TABLE_BASEadd eax, 0x1000mov ebx, eaxor eax, PG_US_U | PG_RW_W | PG_P; 第 0 PDE 和 第 768 PDE 都指向同一个页表(第0PTE); 第 0 PDE 是为了将 0x00000000 - 0x003FFFFF 映射到 0x00000000 - 0x003FFFFF.; 第 768 PDE 是为了将 0x00000000 - 0x003FFFFF 映射到 0xC0000000 - 0xC03FFFFF. ; 因为我们内核和 loader 位于 低端 4 MB 之内, 而我们规定内核将会映射到虚拟地址的高 3GB 以上 (0xC0000000 - 0xFFFFFFFF); 至于 0 PDE 是为了保证,对于 loader 代码 (0 - 0xfffff) ,线性地址和物理地址是一样的。 mov dword [PAGE_DIR_TABLE_BASE + 0x0], eaxmov dword [PAGE_DIR_TABLE_BASE + 0xc00], eax; 将最后 PDE 设为页目录表的物理地址,这是为了动态操作页表sub eax, 0x1000mov dword [PAGE_DIR_TABLE_BASE + 4092], eax; 创建第 0 PTEmov ecx, 1024 ; map 4MBmov esi, 0mov edx, PG_US_U | PG_RW_W | PG_P
.create_pte:mov dword [ebx + esi * 4], edxadd edx, 4096inc esiloop .create_pte; 创建内核其它 PDEmov eax, PAGE_DIR_TABLE_BASEadd eax, 0x2000or eax, PG_RW_W | PG_US_U | PG_Pmov ebx, PAGE_DIR_TABLE_BASEmov ecx, 254 ; 769 - 1022 PDEmov esi, 769
.create_kernel_pde:mov [ebx + esi * 4], eaxinc esiadd eax, 0x1000loop .create_kernel_pderet

在 Bochs 下的映射

0x00000000-0x003fffff -> 0x000000000000-0x0000003fffff
0xc0000000-0xc03fffff -> 0x000000000000-0x0000003fffff
# 后面三项是由于最后 PDE 设为页目录表的物理地址,bochs 将 PDE 表本身当作了 256 项 PTE 表导致的。
0xffc00000-0xffc00fff -> 0x000000101000-0x000000101fff
0xfff00000-0xffffefff -> 0x000000101000-0x0000001fffff
0xfffff000-0xffffffff -> 0x000000100000-0x000000100fff
  • 访问页目录项:0xfffffxxx, 其中 xxx 是页目录项的索引 * 4

内核加载

用 GNU C 套件开发生成的内核本质上是一个 ELF 文件头可执行文件。这意味着:

  • 生成内核时,需要手动将可重定向文件链接时指定代码段所在线性地址 (高位 0xc0000000-0xc03fffff)
  • Loader 将原始 kernel 映像从磁盘读出,存放到内核可用内存的 (较高处) 内核本身不会触及的位置。
  • Loader 根据 ELF 文件格式,将 ELF 中的各个段展开到内核空间。
  • Loader 将控制权转移到内核。

内核加载地址的策略

  • 内核加载地址可以任意在内核空间选定,但不能破坏 loader (0x900 - 0x1500) 所在区域,扩展 BIOS 数据区 (0x9FC00-0x9FFFF), 原始 kernel 映像所在区域。
  • 内核加载完毕后可以选择覆盖原始kernel映像,但还是不能破坏 loader (0x900 - 0x1500) 所在区域,扩展 BIOS 数据区 (0x9FC00-0x9FFFF)
; ---------------------------------------------------
; KERNEL
; ---------------------------------------------------
PAGE_DIR_TABLE_BASE equ     0x500000
KERNEL_ENTRY_POINT equ    0xc0100000
KERNEL_BIN_SECTOR equ            0x9
KERNEL_BIN_ADDR equ         0x300000
KERNEL_STACK_BOTTOM equ   0xc0400000

注意:此处策略与书中不同

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

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

相关文章

操作系统:保护模式(五)特权

特权机制 特权特权级数值越大,级别越小。通常,因为操作系统是为所有程序服务的,可靠性最高,而且必须对软硬件有完全的控制权,所以它的主体部分必须拥有特权级0,并处于整个环形结构的中心。也正是因为这样,操作系统的主体部分通常又被称做内核(Kernel、 Core)。 特权级1和…

操作系统:保护模式(一)GDT 与分段机制

GDT 与分段机制 CPU开机时运行于实模式,寻址方式是段寄存器 \(\times\) 10+偏移寄存器=物理地址,主要原因是因为 8086 地址线和数据线不匹配导致的。但是这种寻址方式既不安全也不支持现代操作系统所需的、多任务支持、cpu 特权模式等。 在实模式下,对于基址,变址寻址的寄存…

CSP2024-30

A 题意:将一个圆等分为 \(K\) 分,给出其中 \(n\) 个等分点的编号,\(x_i < x_{i + 1}\)。 有向边 \(i \to j\) 存在,当且仅当 \(j\) 是距离 \(i\) 最大的点(不唯一),且与图中其他边无交点(端点不算)。 求图中最多有多少条边。\(3 \le K \le 10^9, 3 \le n \le \min(…

小白上手Arcgis—用于结合Netlogo、matlab等进行复杂网络操作

小白上手Arcgis(Netlogo复杂网络数据预处理) 1.前言废话:昨天突然想到可以写一下博客,用来记录一下自己的工作,主要是涉及复杂网络方面。情况简介:本人Arcgis小白,之前只是略微知道有这么个软件,以及知道怎么打开软件。学渣一个,而且不是学gis方向的,但由于工作需要,要…

windows10如何安装jdk8,并且配置java home环境?超详细!

前言 大家好,我是小徐啊。记得我刚学习Java的时候,我的老师第一步就是教我们如何安装jdk并且配置java环境。这应该算是学习Java的第一步吧。虽然这个安装过程对我来说已经不是非常难了,但是我知道,对于一些刚入门的小伙伴还是经常容易搞错的,所以,今天小徐就写一篇详细的…

安装小雅问题

如何卸载重装小雅、apt remove xiaoya docker stop 01ec8396b2c529819bb7c95091a88a9af6999c042bcb7ab57662837c97dca5cd docker rm 01ec8396b2c529819bb7c95091a88a9af6999c042bcb7ab57662837c97dca5cdsystemctl start cpolar 开启cplpr systemctl status cpolar

leetcode24 两两交换链表中的节点(swap-nodes-in-pairs)

题目描述: 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。 示例 1:输入:head = [1,2,3,4] 输出:[2,1,4,3]示例 2: 输入:head = [] 输出:[]示例 3: 输入:head = [1] 输出:[…

第一章:Borel测度

第1章 Borel测度 在正式讨论我们的内容之前我们先做几点说明 1.我们只讨论\(\mathbb{R}^n\) 上的测度,因此如果不作特别说明,我们均认为测度和集合为于\(\mathbb{R}^n\) 中: 2.我们不特别区分外测度和测度,因为将外测度限制在可测集上就是可测集上的测度: 3.我们默认读者已…

TypeScript在vue中的使用-----事件类型的获取

当我们要对事件定义类型。一种是通过console.log(e)来看事件的类型。另外一种是@事件名的时候,将$event写好,鼠标放上去看事件类型。再讲$event删除。 如下: 然后我们定义函数的时候就可以指定事件类型了const clickMi = (e:MouseEvent)=>{console.log(e.pageX, e.pageY…

信息学奥赛复赛复习08-CSP-J2020-03表达式前置知识点-后缀表达式、栈、字符读取

PDF文档公众号回复关键字:202410011 P1449 后缀表达式 [题目描述] 所谓后缀表达式是指这样的一个表达式:式中不再引用括号,运算符号放在两个运算对象之后,所有计算按运算符号出现的顺序,严格地由左而右新进行(不用考虑运算符的优先级) 本题中运算符仅包含 + - * / 。保证…

IDEA如何查看已经安装的插件并删除

前言 我们在使用IDEA开发时,经常需要安装一些插件来帮助我们高效快速的处理问题,可以说很实用。 不过有时候,我们不想使用某个插件了,或者某个插件突然不好用了,想要先删除下再安装,那么我们应该怎么删除我们已经安装的插件呢? 如何删除插件 首先,我们点击【File】->…

python中列表和字符串的相互转换

001、列表转换为字符串>>> list1 [xxx, yyy, zzz] >>> "".join(list1) ## 使用字符串内置函数join + 可迭代对象 xxxyyyzzz >>> "_".join(list1) xxx_yyy_zzz 002、字符串转换为列表>>> str1 = &qu…