1.6 Memory Capacity Detection

 

[Home]  [Top]  [Previous]  [Next]

 


1.6.1 Overview

 

OS必须知道系统物理内存的数量,才能够有效的使用和管理这些物理内存。所以在booting阶段,我们必须通过某种手段来检测和获取物理内存的总量。

 

由于在booting阶段的绝大部分时间里,主机处于Real Mode下,而在Real Mode下,我们通过正常手段能够访问的物理内存最大只能达到1M+64K(在A20 Gate被打开的情况下,否则,最大只能访问1M),所以我们无法直接通过内存访问来获取内存总量。因此,剩下的唯一手段就是通过BIOS中断。

 

但不幸的是,通过BIOS中断获取内存容量并非在所有情况都可以完全工作。

 

获取机器内存容量的方法一般来讲有三种方法,这三种方法都是基于BIOS INT 15h中断,它们的名称依此为88h,E801h,E820h。

 

其中,88h方法是在Intel 80286出现的那天起就存在的,后续的PC及其兼容机为了保持向下兼容,都继续保留了这种方法。因此,这种方法在所有的IBM PC及其兼容机上都存在,所以看起来,booting只需要这种方法就可以获取物理内存总量,其它两种方法似乎没有存在的必要。但事实上,这种方法存在一个重要的缺陷,由于这种方法是在16-bit时代就存在的,所以,它通过16-bit寄存器来保存内存容量作为返回值。但16-bit所能够表示的最大值是64 KB。由于这种方法的返回值是以KB为单位,所以它能够表示的系统最大物理内存容量为64 MB。而对于今天的PC机来说,64 MB已经是很低的内存配置了,大多数PC机的实际物理内存配置都大于64 MB。

 

另外,需要注意的是,由于这种方法出现于Intel 80286时代,而80286的24-bit地址总线能够访问的最大地址为16MB,所以,尽管这种方法使用16-bit寄存器能够表示的最大内存数量为64 MB,但标准的BIOS只允许通过这种方法获取最大16 MB的物理内存数量。

 

为了能够获取大于64 MB的内存,就必须通过另外两种方法的一种。但并非每一台PC机都实现了这两种方法或这两种方法之一。

 

当今流行的桌面操作系统在Booting阶段进行内存检测的方法都是采用这三种方法,比如Microsoft Windows NT,Linux等。所以在某些不支持E820h,E801h的PC上,它们也最多只能检测到64 MB或16 MB物理内存(依赖于88h的最大返回值限制)。

 

但请不必过分担心,对于绝大多数现代PC机来说,至少提供了这两种方法的一种;而对于过去的PC机,又很少有超过64 MB的配置。从这个意义上来说,这已经足够了


1.6.2 INT 15h, AX=E820h - Query System Address Map


E820h只能在Real Mode下使用。


这个中断调用返回所有被安装在主机上的RAM,以及被BIOS所保留的物理内存范围的内存映象。

输入:

Register Meaning Description
EAX 功能码 E820h
EBX 后续值 放置着"后续值",这个值是为了得到下一块物理内存段,它应该指定上一次调用此程序所返回的值,如果这是第一次调用,EBX必须被指定为0。
ES:DI 缓冲指针 指向一个地址范围描述符结构,BIOS将会填充此结构。
ECX 缓冲大小 缓冲指针所指向的地址范围描述符结构的大小,以Byte为单位,无论ES:DI所指向的结构如何设置,BIOS最多将会填充ECX个字节,必需被BIOS以及调用者所支持的最小尺寸是20个字节,未来的实现将会扩展此限制。
EDX 标志 'SMAP' - BIOS将会使用此标志,对调用者将要请求的系统映象信息进行校验,这些信息会被BIOS放置到ES:DI所指向的结构中。



输出:

Register Meaning Description
CF 进位标志 不进位表示没有错误,否则则存在错误。
EAX 标志 'SMAP'
ES:DI 缓冲指针 返回的地址范围描述符结构指针,和输入值相同。
ECX 缓冲大小 BIOS所填充在地址范围描述符中的字节数量,被BIOS所返回的最小值是20个字节。
EBX 后续

这里放置着为了等到下一个地址描述符所需要的"后续值",这个值的实际形势倚赖于具体的BIOS的实现,调用者不必要关心它的具体形式,只需要在下次迭代的时候,将其原封不动的放置到EBX中,就可以通过它获取下一个地址范围描述符。如果它的值为0,则表示它是最后一个地址范围描述符。

一定注意,只有这个后续值为零,并且CF没有进位的时候,才表示这是最后一个地址范围描述符。


 


 

Address Range Descriptor Structure

 

地址范围描述符结构

Offset Name Description
0 BaseAddrLow 基地址的低32位
4 BaseAddrHigh 基地址的高32位
8 LengthLow 长度(字节)的低32位
12 LengthHigh 长度(字节)的高32位
16 Type 这个地址范围的地址类型

 

其中Type的取值及其意义如下:
Value Name Description
1 AddressRangeMemory 这个内存段是一段可以被OS使用的RAM
2 AddressRangeReserved 这个地址段正在被使用,或者被系统保留,所以一定不要被OS使用
Other Undefined 保留为未来使用,任何其它值都必需被OS认为是AddressRangeReserved

 

如下一些原因造成BIOS将某个内存段标记为AddressRangeReserved:

  • 这个地址范围包含着系统ROM;
  • 这个地址范围包含着被ROM使用的RAM;
  • 这个地址范围被用作系统设备内存映射;
  • 这个地址范围由于某种原因,不适合被标准设备用作设备内存空间。

Assumptions and Limitations

  • BIOS返回那些描述大块内存的地址范围,随后是ISA/PCI内存;
  • BIOS不返回被用作PCI设备,ISA可选ROM,以及ISA plus&play卡的内存映象,这是因为OS有相应的机制可以检测到它们;
  • BIOS返回芯片定义的地址空洞,这些地址作为保留不会被设备使用;
  • 被定义的大块内存被映射到IO设备的地址范围作为保留地址将会被返回;
  • 系统BIOS的所有occurrences将会被作为保留内存返回,这包括低于1M的内存,在16M(如果存在)处的内存,以及在地址空间(4G)结尾处的内存;
  • 标准的PC地址范围不会被报告,例如在地址A0000到BFFF的被用作video memory的内存;从E0000到EFFFF的内存是主板指定的,将会被报告;
  • 所有的低位内存作为正常的内存将会被报告,处理为规范保留的标准RAM是OS的责任,例如,中断向量表(0:0)以及BIOS数据区(40:0)。

Example address map

这个地址映象样例描述了一个具有128MB RAM的计算机,其中包括640KB的基本内存,以及127M的扩展内存。在640KB的基本内存中,639KB归用户使用,1K作为扩展BIOS数据区。以12MB位置为起始,存在一个LFB(Liner Frame Buffer),被芯片创建的内存空洞是从8MB到16MB,这个内存空洞是APIC设备的内存映象。IO单元处于FEC00000, 本地单元处于FEE00000,系统的BIOS被重映射到(4G-64K)的位置上。
注意,第一块内存,也就是基本内存的639K的终点位置,被报告在BIOS数据段40:13。

"ARM"表示AddressRangeMemory,"ARR"是AddressRangeReserved。

Base Address Length Type Description
0000:0000 639K ARM 可以使用的基本内存(也就是通过INT 12获取的内存容量)
0009:FC00 1K ARR 为BIOS保留的内存;这个区域包括扩展BIOS数据区.
000F:0000 64K ARR 系统BIOS
0010:0000 7M ARM 扩展内存,它没有64M的地址范围限制
0080:0000 8M ARR 芯片内存空洞,用于支持在12MB位置的LFB映射
0100:0000 120M ARM 在芯片内存空洞之上的大块内存RAM
FEC0:0000 4K ARR 被映射到FEC00000的IO APIC内存,注意不同的厂商的APIC所需要的地址范围可能不一样
FEE0:0000 4K ARR 本地APIC内存映射
FFFF:0000 64K ARR 重映射的系统BIOS

 


1.6.3 INT 15h, AX=E801h - Get Memory Size for Large Configurations

E801h只能在Real Mode下使用。

 

最初,这种方式是为EISA服务定义的,这个接口能够报告多达4G的RAM。然而它不象E820h方式那么通用,E820h在更多的系统上可以使用。

 

输入:
Register Meaning Description
AX 功能码 E801h

 

输出:

Register Meaning Description
CF 进位标志 不进位表示没有错误
AX 扩展1 1到16M内存的容量,以KB为单位,最大数量0x3C00 = 15M
BX 扩展2 16MB到4GB之间的内存容量,以64K为单位
CX 配置1 1到16M内存的容量,以KB为单位,最大数量0x3C00 = 15M
DX 配置2 16MB到4GB之间的内存容量,以64K为单位


无法确定"扩展"和"配置"之间的不同到底在哪里,事实上它们的值是相同的。

注意:一个机器可能使用这个接口来报告16M以下内存空洞(count1小于15M,但count2却为非0)。


1.6.4 INT 15h, AH=88h - Get Extended Memory Size


E88h只能在实模式下使用。

这个接口是相当原语性的,它返回1M地址以上的后续内存容量。最大的限止是它的返回值是16-bit的,以KB为单位,所以它最多能够返回64M。在某些系统上,它仅仅能够返回16M以内的内存。

 

和前两者相比,它的唯一好处是它在所有的PC上都工作。

 

输入:

Register Meaning Description
AH 功能码 88h


输出:

Register Meaning Description
CF 进位标志 不进位表示没有错误
AX 内存容量 以KB为单位,1MB以上的内存容量

 


 

1.6.5 Directly Probing Memory

 

我们在前面已经提到,使用BIOS的方法在某些情况下会无法工作。在这些情况下,可以使用直接探测内存方法。直接探测内存,是一种不依赖于BIOS的内存检测方法,这使得它是一种更加轻便的方法。当使用这种方法的时候,你或许需要考虑系统内存空洞,以及被映射到外部设备上的内存(比如frame buffering SVGA cards)。

 

使用这种方法的时候,必须进入Protected Mode。

 

另外,由于Intel的386以后的CPU为了提高内存访问速度,使用了memory cache,来缓冲内存中的内容。但对于我们想通过直接探测内存来确定内存容量的方法来讲, memory cache反而会造成负面的影响,因此我们必须禁止memory cache。

 

/*
* ULONG count_memory (void)
*
* probes memory above 1mb
*
* last mod : 05sep98 - stuart george
*            08dec98 - ""     ""
*            21feb99 - removed dummy calls
*
*/
ULONG count_memory(void)
{
register ULONG *mem;
ULONG mem_count, a;
USHORT memkb;
UCHAR irq1, irq2;
ULONG cr0;

/* save IRQ's */
irq1=inb(0x21);
irq2=inb(0xA1);

/* kill all irq's */
outb(0x21, 0xFF);
outb(0xA1, 0xFF);

mem_count=0;
memkb=0;

// store a copy of CR0
__asm__ __volatile__("movl %%cr0, %%eax":"=a"(cr0))::"eax");

// invalidate the cache
// write-back and invalidate the cache
__asm__ __volatile__ ("wbinvd");

// plug cr0 with just PE/CD/NW
// cache disable(486+), no-writeback(486+), 32bit mode(386+)
__asm__ __volatile__("movl %%eax, %%cr0", :: "a" (cr0 | 0x00000001 | 0x40000000 | 0x20000000) : "eax");

do
{
memkb++;
mem_count+=1024*1024;
mem=(ULONG*)mem_count;

a=*mem;

*mem=0x55AA55AA;

// the empty asm calls tell gcc not to rely on whats in its registers
// as saved variables (this gets us around GCC optimisations)
asm(" ": : :"memory");
if(*mem!=0x55AA55AA)
mem_count=0;
else
{
*mem=0xAA55AA55;
asm(" ": : :"memory");
if(*mem!=0xAA55AA55)
mem_count=0;
}

asm("":::"memory");
*mem=a;
}while(memkb<4096 && mem_count!=0);

__asm__ __volatile__("movl %%eax, %%cr0", :: "a" (cr0) : "eax");

outb(0x21, irq1);
outb(0xA1, irq2);

	return (memkb<<20);
}