5.1获取物理内存容量

5.1.1学习Linux获取内存的方法

在 Linux 2.6 内核中, 是用 detect_memory 函数来获取内存容量的 其函数在本质上是通过调用 BIOS 中断 0x15 实现的, 分别是BIOS 中断 0x15 的3个子功能, 子功能号要存放到寄存器EAx或Ax中, 如下:
1.EAx=0xE820 遍历主机上全部内存。
2.Ax=0xE801分别检测低 15MB 和 16MB~4GB 的内存, 最大支持 4GB。
3.AH=0x88 最多检测出 64MB 内存, 实际内存超过此容量也按照 64MB 返回。

利用BIOS中断0x15子功能0xe820获取内存

BIOS 中断 0x15 的子功能 0xE820 能够获取系统的内存布局, 由于系统内存各部分的类型属性不同,BIOS 就按照类型属性来划分这片系统内存, 所以这种查询呈迭代式, 每次 BIOS 只返回一种类型的内存信息 直到将所有内存类型返回完毕。子功能 0xE820 的强大之处是返回的内存信息较丰宫, 包括多个属性字段, 所以需要一种格式结构来组织这些数据。内存信息的内容是用地址范围描述符来描述的, 用于存储这种描述符的结构称之为地址范围描述符。

bios 1MB的空间 bios内存容量总数_寄存器


此结构中的字段大小都是4字节, 共5个字段, 所以此结构大小为20字节。 每次 int0x15 之后, BIOS就返回这样一个结构的数据。 注意, ARDS 结构中用 64位宽度的属性来描述这段内存基地址 (起始地址)及其长度, 所以表中的基地址和长度都分为低 32位和高 32 位两部分。

Type 字段用来描述这段内存的类型, 这里所谓的类型是说明这段内存的用途, 即其是可以被操作系统使用, 还是保留起来不能用。 Type字段的具体意义见下表:

bios 1MB的空间 bios内存容量总数_字段_02


BIOS 中断只是一段函数例程, 调用它就要为其提供参数现在介绍下 BIOS 中断 0x15 的 0xe820子功能需要哪些参数。先介绍下此中断例程的调用方法。 表5-3 所示是使用此中断的方法, 分输入和输出两部分。

bios 1MB的空间 bios内存容量总数_描述符_03


bios 1MB的空间 bios内存容量总数_bios 1MB的空间_04


ECX 寄存器和 ES: DI 寄存器, 是典型的 “值-结果” 型参数, 即调用方提供了两个变量作为被调用函数的参数, 一个变量是缓冲区指针, 另一个变量是缓冲区大小。 被调用函数在缓冲区中写入数据后, 将实际所写入的字节数记录到缓冲区大小变量中。

根据表 5-3 中的说明, 此中断的调用步骤如下。

(1) 填写好 “调用前输入” 中列出的寄存器。

(2)执行中断调用 int 0x15。

(3) 在CF位为0的情况下, “返回后输出″ 中对应的寄存器便会有对应的结果。

利用BIOS中断0x15子功能0xe801获取内存

另一个获取内存容量的方法是BIOS 0xl5 中断的子功能 0xE801。

此方法虽然简单,但功能不强大, 最大只能识别4GB 内存, 不过这对咱们32位地址总线足够了。 稍微有点不便的是该方法检测到的内存是分别放置在两组寄存器中的。

bios 1MB的空间 bios内存容量总数_bios 1MB的空间_05


此中断的调用步骤如下。

(1) 将AX寄存器写入0xE801。

(2) 执行中断调用 int 0x15。

(3) 在CF 位为0的情况下, “返回后输出” 中对应的寄存器便会有对应的结果。

利用BIOS中断的0x15子功能0x88获取内存

最后一个获取内存的方法也同样是 BIOS 0x15 中断, 子功能号是 0x88。 该方法使用最简单, 但功能也最简单, 简单到只能识别最大 64MB 的内存。 即使内存容量大于 64MB, 也只会显示 63MB, 大家可以自己在bochs中试验下。为什么只显示到63M呢? 因为此中断只会显示 1MB之上的内存,不包括这 1MB

bios 1MB的空间 bios内存容量总数_字段_06


中断返回后, Ax寄存器中的值, 其单位是 1KB。 此中断的调用步骤如下。

(1)将 AX 寄存器写入 0x88

(2) 执行中断调用 int0x15

(3)在CF位为0的情况下, “返回后输出” 中对应的寄存器便会有对应的结果。

##实际代码获取物理内存

;-------  int 15h eax = 0000E820h ,edx = 534D4150h ('SMAP') 获取内存布局  -------

   xor ebx, ebx		      ;第一次调用时,ebx值要为0
   mov edx, 0x534d4150	      ;edx只赋值一次,循环体中不会改变
   mov di, ards_buf	      ;ards结构缓冲区
.e820_mem_get_loop:	      ;循环获取每个ARDS内存范围描述结构
   mov eax, 0x0000e820	      ;执行int 0x15后,eax值变为0x534d4150,所以每次执行int前都要更新为子功能号。
   mov ecx, 20		      ;ARDS地址范围描述符结构大小是20字节
   int 0x15
   jc .e820_failed_so_try_e801   ;若cf位为1则有错误发生,尝试0xe801子功能  汇编语言jc指令,当flags寄存器中的CF位为1时触发
   add di, cx		      ;使di增加20字节指向缓冲区中新的ARDS结构位置
   inc word [ards_nr]	      ;记录ARDS数量  对地址中的内容+1再送回给地址
   cmp ebx, 0		      ;若ebx为0且cf不为1,这说明ards全部返回,当前已是最后一个   cmp比较ebx与0是否相等,相等ZF=1,不相等ZF=0
   jnz .e820_mem_get_loop ;flagsZF位不为0/不等于时跳转执行

;在所有ards结构中,找出(base_add_low + length_low)的最大值,即内存的容量。
   mov cx, [ards_nr]	      ;遍历每一个ARDS结构体,循环次数是ARDS的数量
   mov ebx, ards_buf 
   xor edx, edx		      ;edx为最大的内存容量,在此先清0
.find_max_mem_area:	      ;无须判断type是否为1,最大的内存块一定是可被使用
   mov eax, [ebx]	      ;base_add_low
   add eax, [ebx+8]	      ;length_low
   add ebx, 20		      ;指向缓冲区中下一个ARDS结构
   cmp edx, eax		      ;冒泡排序,找出最大,edx寄存器始终是最大的内存容量
   jge .next_ards          ;jge大于或者等于转移指令
   mov edx, eax		      ;edx为总内存大小
.next_ards:
   loop .find_max_mem_area
   jmp .mem_get_ok

;------  int 15h ax = E801h 获取内存大小,最大支持4G  ------
; 返回后, ax cx 值一样,以KB为单位,bx dx值一样,以64KB为单位
; 在ax和cx寄存器中为低16M,在bx和dx寄存器中为16MB到4G。
.e820_failed_so_try_e801:
   mov ax,0xe801
   int 0x15
   jc .e801_failed_so_try88   ;若当前e801方法失败,就尝试0x88方法

;1 先算出低15M的内存,ax和cx中是以KB为单位的内存数量,将其转换为以byte为单位
   mov cx,0x400	     ;cx和ax值一样,cx用做乘数
   mul cx 
   shl edx,16
   and eax,0x0000FFFF
   or edx,eax
   add edx, 0x100000 ;ax只是15MB,故要加1MB
   mov esi,edx	     ;先把低15MB的内存容量存入esi寄存器备份

;2 再将16MB以上的内存转换为byte为单位,寄存器bx和dx中是以64KB为单位的内存数量
   xor eax,eax
   mov ax,bx		
   mov ecx, 0x10000	;0x10000十进制为64KB
   mul ecx		;32位乘法,默认的被乘数是eax,积为64位,高32位存入edx,低32位存入eax.
   add esi,eax		;由于此方法只能测出4G以内的内存,故32位eax足够了,edx肯定为0,只加eax便可
   mov edx,esi		;edx为总内存大小
   jmp .mem_get_ok

;-----------------  int 15h ah = 0x88 获取内存大小,只能获取64M之内  ----------
.e801_failed_so_try88: 
   ;int 15后,ax存入的是以kb为单位的内存容量
   mov  ah, 0x88
   int  0x15
   jc .error_hlt
   and eax,0x0000FFFF
      
   ;16位乘法,被乘数是ax,积为32位.积的高16位在dx中,积的低16位在ax中
   mov cx, 0x400     ;0x400等于1024,将ax中的内存容量换为以byte为单位
   mul cx
   shl edx, 16	     ;把dx移到高16位
   or edx, eax	     ;把积的低16位组合到edx,为32位的积
   add edx,0x100000  ;0x88子功能只会返回1MB以上的内存,故实际内存大小要加上1MB

.mem_get_ok:
   mov [total_mem_bytes], edx	 ;将内存换为byte单位后存入total_mem_bytes处。