获取物理内存容量

简介: 获取物理内存容量

引言

  • 前面章节中我们学习的内存有关的概念都建立在物理内存之上,无论理论概念说得多高大上,最终也要在物理内存上落实行动。为了在后期做好内存管理工作,咱们先得知道自己有多少物理内存才行
  • 如何获取系统物理内存的大小呢?

BIOS 提供的中断(int 0x15)

  • 来获取内存物理容量有 3 中方法,它们是 BIOS 中断 0x15 的 3 个子功能,子功能号要存放到寄存器 EAX 或 AX 中,如下:
  • AH=0x88:最多检测出 64MB 内存,实际内存超过此容量也按照 64MB 返回
  • AX=0xE801:分别检测低 15MB 和 16MB~4GB 的内存,最大支持 4GB
  • EAX=0xE820:遍历主机上全部内存
  • 注意:BIOS 中断是实模式下的方法,只能在进入保护模式前调用

方法 1:利用 BIOS 中断 0x15 子功能 0x88 获取物理内存容量

  • 由于最多只能检测 64M 的内存,所以此方法我们不做说明

方法 2:利用 BIOS 中断 0x15 子功能 0xE801 获取物理内存容量

  • 此中断的调用步骤如下:
  • 将 AX 寄存器写入 0xE801
  • 执行中断调用 int 0x15
  • 在 CF 位为 0 的情况下,获取返回结果
  • 返回值具体说明
寄存器或状态位 描述
CF 位 CF 位为 0 表示调用未出错,CF 为 1,表示调用出错
AX 以 1KB 为单位,只显示 15MB 以下的内存容量,故最大值为 0x3c00,即 AX 表示的最大内存为 0x3c00*1024=15MB
BX 以 64KB 为单位,内存空间 16MB~4GB 中连续的单位数量,即内存大小为 BX*64*1024 字节
CX 同 AX
DX 同 BX
  • 关于标志寄存器知识详见 标志寄存器,对于 32位处理器已经扩展成了 eflags,可自行查找学习
  • 就在上一章节实现的代码的基础上进行实验吧 loader.asm
  • 具体实现见 loader.asm ,注意:因为用到 BIOS 中断,所以读内存容量指令必须在是模式下执行
  • 我们先定义 4 字节的空间用于读取内存容量
MEM_SIZE times 4 db 0
  • int 0x15 中断使用如下:
xor eax, eax            ; 用于清 CF 标志位 CF = 0
mov eax, 0xE801
int 0x15
  • 接下来如果 CF标志位 = 0,则物理内存容量 = AX*1024+BX*64*1024
shl eax, 10             ; eax = eax * 1024        
shl ebx, 16             ; ebx = ebx * 64 * 1024
add dword [MEM_SIZE], eax
add dword [MEM_SIZE], ebx
  • 程序到这里就已经得到 MEM_SIZE 了。不过我们没有办法显示出来内存容量,只能反汇编后断点调试了

ndisasm -o 0x900 loader.bin  > loader.txt

  • 找到实模式下最后一条指令 “jmp dword CODE32_SELECTOR:0” 处地址为 0x9D0,在这里打上断点
  • 从反汇编后的文件中我们找到了 [MEM_SIZE] 地址为 [0x969]
  • bochs 调试打印信息如下
<bochs:1> b 0x9d0
<bochs:2> c
...
<bochs:3> xp 0x969
[bochs]:
0x00000969 <bogus+       0>:    0x01f00000
  • 从提示信息中可以得到获取的物理内存大小为:0x01f00000 = 31M
  • 查看 bochs 配置文件 bochsrc.disk 中内存的配置大小配置为 32M

# how much memory the emulated machine will have

# 虚拟机内存大小

megs: 32

  • 为什么少了 1M 呢?
  • 我们再回过头来看看“返回值具体说明”中AX和BX寄存器的描述内容,发现,描述恰巧丢了 15~16 这 1M 空间,这又是为什么?
  • 这个就只能说是历史遗留问题了,凡是不懂得,就归结到历史原因上,早期芯片将 15M~16M 这段内存映射给了 ISA 设备使用,也别管 ISA 是啥,反正就是内存没法用了,这种情况称之为内存黑洞。具体咱就不研究了
  • 现在,我们人为增加 1M 内存容量
add dword [MEM_SIZE], eax
add dword [MEM_SIZE], ebx
mov ecx, 1
shl ecx, 20               ; ecx = 1MB
add dword [MEM_SIZE], ecx ; 人为增加 1M

方法 3:利用 BIOS 中断 0x15 子功能 0xE820 获取物理内存容量

  • BIOS 中断 0x15 的子功能 0xE820 能够获取系统的内存布局,这是最灵活的内存获取方式
  • 操作系统根据需要把一整块物理内存分成多块不同属性的内存块,子功能 0xE820 就可以获得每一块内存的信息,想得到所有内存信息,就需要多次中断,来获得所有内存块的信息
  • 通过子功能 0xE820 获得的内存信息被称作为地址范围描述符(Address Range Descriptor Structure, ARDS)
  • 地址范围描述符结构 ARDS
字节偏移量 属性名称 描 述
0 BaseAddrLow 基地址的低 32 位
4 BaseAddrHigh 基地址的高 32 位
8 LengthLow 内存长度的低 32 位,以字节为单位
12 LengthHigh 内存长度的高 32 位,以字节为单位
16 Type 本段内存的类型
  • 用 C 来描述这个结构
struct ARDS
{
  unsigned int  BaseAddrLow;
  unsigned int  BaseAddrHigh
  unsigned int  LengthLow
  unsigned int  LengthHigh
  unsigned int  Type
}
  • 此结构中的字段大小都是 4 字节,共 5 个字段,所以此结构大小为 20 字节。每次 int 0x15 之后,BIOS 就返回这样一个结构的数据。注意,ARDS 结构中用 64 位宽度的属性来描述这段内存基地址(起始地址)及其长度,所以表中的基地址和长度都分为低 32 位和高 32 位两部分
  • Type 值:1,这段内存可以被操作系统使用;2,内存使用中或者被系统保留,操作系统不可以用此内存;其他,未定义,将来会用到,目前保留
  • 中断调用前输入
寄存器或状态位 参数用途
EAX 子功能号:EAX 寄存器用来指定子功能号,此处输入为 0xE820
EBX ARDS 后续值:内存信息需要按类型分多次返回,由于每次执行一次中断都只返回一种类型内存的 ARDS 结构,所以要记录下一个待返回的内存 ARDS,在下一次中断调用时通过此值告诉 BIOS 该返回哪个 ARDS,这就是后续值的作用。第一次调用时一定要置为 0,EBX 具体值我们不用关注,字取决于具体 BIOS 的实现。每次中断返回后,BIOS 会更新此值
ES:DI ARDS 缓冲区:BIOS 将获取到的内存信息写入此寄存器指向的内存,每次都以 ARDS 格式返回
ECX ARDS 结构的字节大小:用来指示 BIOS 写入的字节数。调用者和 BIOS 都同时支持的大小是 20 字节,将来也许会扩展此结构
EDX 固定为签名标记 0x534d4150,此十六进制数字是字符串 SMAP 的 ASCII 码:BIOS 将调用者正在请求的内存信息写入 ES:DI 寄存器所指向的 ARDS 缓冲区后,再用此签名校验其中的信息
  • 中断调用后输出
寄存器或状态位 参数用途
CF 位 若 CF 位为 0 表示调用未出错,CF 为 1,表示调用出错
EAX 字符串 SMAP 的 ASCII 码 0x534d4150
ES:DI ARDS 缓冲区地址,同输入值是一样的,返回时此结构中已经被BIOS 填充了内存信息
ECX BIOS 写入到 ES:DI 所指向的 ARDS 结构中的字节数,BIOS 最小写入 20 字节
EBX 后续值:下一个 ARDS 的位置。每次中断返回后,BIOS 会更新此值,BIOS 通过此值可以找到下一个待返回的 ARDS 结构,咱们不需要改变 EBX 的值,下一次中断调用时还会用到它。在 CF 位为 0 的情况下,若返回后的 EBX 值为 0,表示这是最后一个 ARDS 结构
  • 说了那么多 ARDS,就算我们得到了 ARDS 内存信息,又怎么知道实际物理内存容量有多大呢?
  • 每次中断后都会得到一组 BaseAddrLow + LengthLow(基地址+内存长度),当所有内存信息得到后,其中 BaseAddrLow + LengtLow 的最大值就是我们要的实际物理内存容量(32 位操作系统只用到低 32 位)
  • 注意:获取到的 ARDS 结构信息中只有 Type 为 1 时才是有效的
  • 直接上代码:loader.asm
  • 先把原先的 detect_memory 函数改名为 detect_memory_0xe801,区分本次新增的函数 detect_memory_0xe820
  • 先来看一下获取 ARDS 的伪代码帮助理解:
struct ARDS ARDS[64] = {0};   // ARDS 缓冲区
int count = 0;                // 内存块个数
es:di = ARDS;
ebx = 0;
do
{
  // 固定参数,使用中断前需重新设置
  eax = 0xE820
  edx = 0x534d4150;
  ecx = 20;
  int 0x15;                   // 触发 0x15 中断
  // int 0x15 中断执行完成后,返回一个 ARDS 信息填充到 es:di 执行的内存中 
  if(ARDS[i].Type == 1)       // Type 为 1 时表示系统可以使用
  {
    // 比较大小,把较大的赋值给 MEM_SIZE
    if(MEM_SIZE < ARDS[i].BaseAddrLow +  ARDS[i].LengthLow)
      MEM_SIZE = ARDS[i].BaseAddrLow +  ARDS[i].LengthLow
  }
  if(cf == 1)                 // 失败处理
  {
    error();
    break;
  }
  di += 20;
  count++;
}
while(ebx != 0)               // 如果 ebx = 0,说明是最后一个 ARDS 结构
  • 有了上面的伪代码,我们再来看汇编实现的 detect_memory_0xe820 函数就比较容易了
  • 我们依旧需要使用断点调试的方法来查看获得的物理内存容量,反汇编后找到 MEM_SIZE 地址为 0x96a,MEM_ARDS 地址为 0x96e 在 “jmp dword CODE32_SELECTOR:0” 这条跳转指令处(0xed1)打断点,查看内存 MEM_SIZE 和 MEM_ARDS 中的值
  • 在调试过程中发现获得的物理内存容量始终少了 0x10000,不知道啥原因,参考 detect_memory_0xe801 人为给它加上 0x10000 吧
<bochs:1> b 0xed1
<bochs:2> c
...
<bochs:3> xp 0x96a
[bochs]:
0x0000096a <bogus+       0>:    0x02000000
<bochs:4> xp/5 0x96e
[bochs]:
0x0000096e <bogus+       0>:    0x00000000      0x00000000      0x0009f000      0x00000000
0x0000097e <bogus+      16>:    0x00000001
<bochs:5> xp/5 0x96e+20
[bochs]:
0x00000982 <bogus+       0>:    0x0009f000      0x00000000      0x00001000      0x00000000
0x00000992 <bogus+      16>:    0x00000002
<bochs:6> xp/5 0x96e+40
[bochs]:
0x00000996 <bogus+       0>:    0x000e8000      0x00000000      0x00018000      0x00000000
0x000009a6 <bogus+      16>:    0x00000002
<bochs:7> xp/5 0x96e+60
[bochs]:
0x000009aa <bogus+       0>:    0x00100000      0x00000000      0x01ef0000      0x00000000
0x000009ba <bogus+      16>:    0x00000001
<bochs:8> xp/5 0x96e+80
[bochs]:
0x000009be <bogus+       0>:    0x01ff0000      0x00000000      0x00010000      0x00000000
0x000009ce <bogus+      16>:    0x00000003
<bochs:9> xp/5 0x96e+100
[bochs]:
0x000009d2 <bogus+       0>:    0xfffc0000      0x00000000      0x00040000      0x00000000
0x000009e2 <bogus+      16>:    0x00000002
<bochs:10> xp/5 0x96e+120
[bochs]:
0x000009e6 <bogus+       0>:    0x00000000      0x00000000      0x00000000      0x00000000
0x000009f6 <bogus+      16>:    0x00000000

获取物理内存容量逻辑

  • 当前我们有两种方式都可以实现物理内存容量的检测,那么怎么处理这两者之间的关系呢?
  • 先使用 0xe820 方式检测内存容量,如果失败了再使用 0xe801 方式检测内存容量,如果都失败了,死机,没有内存操作系统也没必要启动了
  • 让我们用 C 语言的方式实现一下这个逻辑吧
void detect_memory(void)
{
  int err = 0;
  err = detect_memory_0xe820();
  if(err)
  {
    detect_memory_0xe801();
  }
  else
  {
    printf("Get Memory Err");
    for(;;) // 死机
  }
}

保护模式下的数据保护

  • 比如前面我们在实模式下得到的实际物理内存相关数据,需要将其传递到保护模式下以供使用,如果我们不对这些数据做任何保护措施,那么该数据就有可能被破坏。既然是保护模式,那对数据做保护措施不是轻轻松松的事吗
  • 我们把需要保护的数据当成一个只读数据段不就可以了嘛
SYS_DATA_SEGMENT:
  MEM_SIZE        times   4           db  0      ; 物理内存容量
  MEM_SIZE_OFFSET equ     MEM_SIZE - SYS_DATA_SEGMENT
  MEM_ARDS        times   64 * 20     db  0      ; ARDS 缓冲区
  MEM_ARDS_OFFSET equ     MEM_ARDS - SYS_DATA_SEGMENT
SYS_DATA_SEG_LEN    equ     $ - SYS_DATA_SEGMENT
  • 对应的段描述法和段选择符
; 段描述符,只读属性
SYS_DATA_DESC : Descriptor 0, SYS_DATA_SEG_LEN - 1, DA_DR + DA_32
; 段选择符
SYS_DATA_SELECTOR     equ   (0x000B << 3) + SA_RPL0 + SA_TIG
  • 段描述符中段基址别忘记初始化
mov esi, SYS_DATA_SEGMENT
mov edi, SYS_DATA_DESC
call InitDescItem
目录
相关文章
|
4月前
|
Linux
内存学习(五):物理内存组织
内存学习(五):物理内存组织
178 0
|
4月前
|
缓存
内存学习(三):物理地址空间
内存学习(三):物理地址空间
68 0
|
4月前
|
缓存 Linux C语言
Linux内存管理宏观篇(四)物理内存:物理内存管理区
Linux内存管理宏观篇(四)物理内存:物理内存管理区
58 1
|
4月前
|
存储 缓存 Unix
内存学习(一):物理地址空间内存概述
内存学习(一):物理地址空间内存概述
41 0
|
8天前
|
存储 算法 内存技术
深入理解操作系统内存管理:从虚拟内存到物理内存的映射
【4月更文挑战第30天】 在现代操作系统中,内存管理是一个复杂而关键的功能。它不仅确保了系统资源的有效利用,还为每个运行的程序提供了独立的地址空间,保障了程序之间的隔离性和安全性。本文将探讨操作系统如何通过分页机制和虚拟内存技术实现内存的抽象化,以及这些技术是如何影响应用程序性能的。我们将详细解析虚拟地址到物理地址的转换过程,并讨论操作系统在此过程中扮演的角色。文章的目的是为读者提供一个清晰的框架,以便更好地理解内存管理的工作原理及其对系统稳定性和效率的影响。
|
1月前
|
人工智能 缓存 算法
深入理解操作系统内存管理:从虚拟内存到物理内存的映射
【4月更文挑战第8天】 在现代操作系统中,内存管理是核心功能之一,它负责协调和管理计算机的内存资源,确保系统稳定高效地运行。本文深入探讨了操作系统内存管理的关键概念——虚拟内存和物理内存的映射机制。通过剖析分页系统、分段机制和虚拟内存地址转换过程,文章旨在为读者提供一个清晰的理解框架,同时讨论了内存管理的优化技术及其对系统性能的影响。此外,还简要介绍了内存碎片问题以及垃圾回收机制的重要性,并展望了未来内存管理技术的发展趋势。
|
2月前
|
存储 算法 内存技术
深入理解操作系统内存管理:从虚拟内存到物理内存
【2月更文挑战第30天】 在现代计算机系统中,操作系统的内存管理是确保系统高效稳定运行的关键组成部分。本文将深入探讨操作系统内存管理的复杂世界,特别是虚拟内存和物理内存之间的关联与转换机制。通过分析分页系统的工作原理、虚拟地址空间的结构以及页面置换算法,文章旨在为读者提供一个清晰的框架,以理解内存管理在操作系统中的重要性和实现细节。
|
6月前
|
存储 算法 程序员
【OSTEP】超越物理内存:机制 | 请求分页 | 交换位与存在位 | 页错误
【OSTEP】超越物理内存:机制 | 请求分页 | 交换位与存在位 | 页错误
31 0
|
2月前
|
缓存
|
2月前
|
存储 缓存 Linux