全部博文(183)
分类: LINUX
2010-03-11 22:48:05
1.6 Memory Capacity Detection
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:
Assumptions and Limitations
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);
1.6 Memory Capacity Detection
[] [] [] []
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:
Assumptions and Limitations
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);
}
}