How Do I Determine The Amount Of RAM: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
m (→‎A map of low (conventional) memory: fixed table structure for BDA.)
(Aiming FAQ page at new "Detecting Memory" article)
Line 1: Line 1:
#REDIRECT [[Detecting Memory (x86)]]
Determining how much memory you have is one of the first things that most people implement in their kernel. In the "old" days of operating systems this was very easy as few people had more than 64mb of memory.

== What and why ... ==

Why did I mention 64mb of memory? Because the CMOS can only hold values up to 99mb. So what are you going to do if you encounter machines with 128mb? There are some functions in the BIOS for memory handling. The first set of calls supported by all BIOS only returns what is in the CMOS, thus making it irrelevant. There is some more advanced calls but are not kuse are directly probing memory, and using your motherboard chipset registers to determine memory. The major drawback with the later methods is you must know what kind of chipset the user has on his motherboard....

Something to note, not that you may need to know, is that on old machines (maybe even new ones), it is possible to have less than 640kb base memory and still have extended memory beyond the 1mb mark. But in today's machines where most people have single 64mb DIMM's, you wont come across this.

== A map of low (conventional) memory ==

There are regions that are used for the system and that the BIOS will never report to you because they're assuming well-known standards. Quoting [http://therx.sourceforge.net/osdev/files/ia32_rm_addr.pdf The Workings of: x86-16/32 RealMode Addressing by Perica Senjak] (minor corrections by BrendanTrotter):

{| {{wikitable}}
|-
! start
! end
! size
! region/exception
! description
|-
! colspan=5 | Low Memory (the first MiB)
|-
| 00000000
| 000003FF
| 400
| RAM
| Real-Mode IVT (Interrupt Vector Table)
|-
| 00000400
| 000004FF
| 100
| RAM
| BDA (BIOS data area)
|-
| 00000500
| 0009FBFF
| ? 9F700
| RAM/free for use
| Conventional memory (<= 9F700 Byte)
|-
| 00007C00
| 00007DFF
| 200
| RAM
| Operating System BootSector
|-
| 0009FC00
| 0009FFFF
| 400
| RAM
| EBDA (Extended BIOS data area)
|-
| 000A0000
| 000FFFFF
| 60000
| various
| ROM Area (384 KiB)
|-
! colspan=5 | Standard usage of the ROM Area
|-
| 000A0000
| 000BFFFF
| 20000
| video RAM
| VGA Mem (128 KiB)
|-
| 000A0000
| 000AFFFF
| 10000
| video RAM
| VGA framebuffer (64 KiB)
|-
| 000B0000
| 000B7FFF
| 8000
| video RAM
| text monochrome (32 KiB)
|-
| 000B8000
| 000BFFFF
| 8000
| video RAM
| text color (32 KiB)
|-
| 000C0000
| 000C7FFF
| 8000
| ROM
| Video BIOS* (32 KiB is typical size)
|-
| 000F0000
| 000FFFFF
| 10000
| ROM
| Motherboard BIOS* (64 KiB is typical size)
|-
! colspan=5 | High Memory (everything after the first MiB)
|-
| 00100000
| FEBFFFFF
| FEB00000
| RAM?/free for use?
| Extended memory
|-
| 01000000
| 010FFFFF
| 100000 ?
| ISA 15-16MB (only with ISA bus?)
|-
| FEC00000
| FFFFFFFF
| 1400000
| various
| PnP NVRAM?, LAPIC, ...
|}

== Counting RAM using the BIOS ==

You can determine RAM size with the BIOS via two different calls.

The first call is built in nearly every BIOS, the later call is only contained within newer BIOS's (from [http://www.ctyme.com/rbrown.htm Ralf Browns Interrupt List] :)


--------B-1588-------------------------------
INT 15 - SYSTEM - GET EXTENDED MEMORY SIZE (286+)
AH = 88h
Return: CF clear if successful
AX = number of contiguous KB starting at absolute address 100000h
CF set on error
AH = status
80h invalid command (PC,PCjr)
86h unsupported function (XT,PS30)


*Notes*: DOS TSRs which wish to allocate extended memory for themselves often hook this call, and return a reduced memory size. They are then free to use the memory between the new and old sizes at will. If your OS boots from DOS (e.g. lin-loader for linux), this may become more relevant.

The standard BIOS only returns memory between 1MB and 16MB; use =AH=C7h= for memory beyond 16MB not all BIOSes correctly return the carry flag, making this call unreliable unless one first checks whether it is supported through a mechanism other than calling the function and testing CF

In these times though, these functions are 'poor' to say the least. A newer function that does the same thing is:

INT 15h
AX = E801h
Return:
CF clear if successful
AX = extended memory between 1M and 16M, in K (max 3C00h = 15MB)
BX = extended memory above 16M, in 64K blocks
CX = configured memory 1M to 16M, in K
DX = configured memory above 16M, in 64K blocks CF set on error

This function has been around since about 1994, so all systems from after then up to now should have this function.

*Note*: For optimum compatibility with all systems you should use the functions in the order: E820h, E881h, E801h and resort to 88h/C7h if everything else fails.


SeeAlso:AH=87h,AH=8Ah"Phoenix",AH=C7h,AX=DA88h,AX=E801h,AX=E820h
--------b-15E820-----------------------------
INT 15 - newer BIOSes - GET SYSTEM MEMORY MAP
AX = E820h
EAX = 0000E820h
EDX = 534D4150h ('SMAP')
EBX = continuation value or 00000000h to start at beginning of map
ECX = size of buffer for result, in bytes (should be <= 20 bytes)
ES:DI -> buffer for result (see #00560)
Return: CF clear if successful
EAX = 534D4150h ('SMAP')
ES:DI buffer filled
EBX = next offset from which to copy or 00000000h if all done
ECX = actual length returned in bytes
CF set on error
AH = error code (86h) (see #00475 at INT 15/AH=80h)

'''Notes'''
* this function is now supported by most newer BIOSes
* a maximum of 20 bytes will be transferred at one time, even if ECX is higher; some BIOSes ignore the value of ECX on entry, and always copy 20 bytes
* some BIOSes expect the high word of EAX to be clear on entry, I.e. EAX=0000E820h

If this function is not supported, an application should fall back to AX=E802h, AX=E801h, and then AH= 88h the BIOS is permitted to return a nonzero continuation value in EBX and indicate that the end of the list has already been reached by returning with CF set on the next iteration this function will return base memory and ISA/PCI memory contiguous with base memory as normal memory ranges; it will indicate chipset-defined address holes which are not in use and motherboard memory-mapped devices, and all occurrences of the system BIOS as reserved standard PC address ranges will not be reported

SeeAlso:AH=C7h,AX=E801h"Phoenix",AX=E881h,MEM xxxxh:xxx0h"ACPI"
Format of Phoenix BIOS system memory map address range descriptor:
Offset Size Description (Table 00559)
00h QWORD base address
08h QWORD length in bytes
10h DWORD type of address range (see #00560)
(Table 00560)
Values for System Memory Map address type:
01h memory, available to OS
02h reserved, not available (e.g. system ROM, memory-mapped device)
03h ACPI Reclaim Memory (useable by OS after reading ACPI tables)
04h ACPI NVS Memory (OS is required to save this memory between NVS
sessions)
other not defined yet -- treat as Reserved
SeeAlso: #00559

''' ACPI 3.0 Notes '''
* Version 3.0 of the ACPI standard extends "int 15h, AX=E820"
* A type of 05h has been defined for "memory in which errors have been detected"
* A 32 bit set of flags ("Extended Attibutes") has been appended to the end of the 20 byte structure, making it 24 bytes.
* Bit 0 of the Extended Attributes indicates if the entire entry should be ignored (if the bit is clear). This is going to be a huge compatibility problem because most current OSs won't read this bit and won't ignore the entry.
* Bit 1 of the Extended Attributes indicates if the entry is non-volatile (if the bit is set) or not. The standard states that "Memory reported as non-volatile may require characterization to determine its suitability for use as conventional RAM.".
* The remaining 30 bits of the Extended Attributes are undefined.


== BIOS reports xxx, is this normal ? ==

The 'standard' gap between 0xA0000 and ~0xFFFFF will never be reported by BIOS.

'''Output by a call to INT 15h, AX=E820 in Bochs:'''

Memory Table:
Base | Memory | Type
0x0000000000000000 | 0x000000000009FC00 | Free Memory
0x000000000009FC00 | 0x0000000000000400 | Reserved Memory
(Standard gap between 0xA0000 to ~0xFFFFF)
0x00000000000E8000 | 0x0000000000018000 | Reserved Memory
0x0000000000100000 | 0x0000000001F00000 | Free Memory
0x00000000FFFC0000 | 0x0000000000040000 | Reserved Memory

== Asking GRUB the amount of RAM ==

[[GRUB]], or any bootloader implementing [http://www.gnu.org/software/grub/manual/multiboot/multiboot.html The Multiboot Specification] provides a convenient way of detecting the amount of RAM your machine has. Rather than re-invent the wheel, you can ride on the hard work that others have done by utilizing the multiboot_info structure. When GRUB runs, it loads this structure into memory and leaves the address of this structure in the EBX register.

To utilize this structure, first include the file [http://www.gnu.org/software/grub/manual/multiboot/html_node/multiboot.h.html multiboot.h] in your kernel's main file. Then, make sure that when you load your _main function from your assembly loader, you push EBX onto the stack. BareBones has this already done for you.

The key for memory detection lies in the multiboot_info struct. To get access to it, you've already pushed it onto the stack...define your start function as such:

_main (multiboot_info_t* mbd, unsigned int magic) {...}

Now you may just check <tt>mbd->flags</tt> to see that bit 0 is set, and then you can safely refer to <tt>mbd->mem_lower</tt> for conventional memory (e.g. physical addresses ranging between 0 and 640KB) and <tt>mbd->mem_upper</tt> for high memory (e.g. from 1MB). Both are given in "real" kilobytes, i.e. blocks of 1024 bytes each.

If this is still not yet enough for you, you may check bit 6 of <tt>mbd->flags</tt> and use <tt>mbd->mmap_addr</tt> to access the BIOS-provided memory map. Quoting [http://www.gnu.org/software/grub/manual/multiboot/html_node/Boot-information-format.html#Boot%20information%20format specifications],

{{Quotation
| If bit 6 in the flags word is set, then the mmap_* fields are valid, and indicate the address and length of a buffer containing a memory map of the machine provided by the BIOS. mmap_addr is the address, and mmap_length is the total size of the buffer. The buffer consists of one or more of the following size/structure pairs (size is really used for skipping to the next pair):''
}}

:{| {{wikitable}}
|-
! -4
| size
|-
! 0
| base_addr_low
|-
! 4
| base_addr_high
|-
! 8
| length_low
|-
! 12
| length_high
|-
! 16
| type
|-
|}

where size is the size of the associated structure in bytes, which can be greater than the minimum of 20 bytes. base_addr_low is the lower 32 bits of the starting address, and base_addr_high is the upper 32 bits, for a total of a 64-bit starting address. length_low is the lower 32 bits of the size of the memory region in bytes, and length_high is the upper 32 bits, for a total of a 64-bit length. type is the variety of address range represented, where a value of 1 indicates available RAM, and all other values currently indicated a reserved area.

So in order to use the GRUB memory map you declare an appropriate structure, get the pointer to the first instance, grab whatever address and length information you want, and finally skip to the next memory map instance by adding size+4 to the pointer, tacking on the 4 to account for GRUB treating base_addr_low as offset 0 in the structure. You must also use mmap_length to make sure we don't overshoot the entire buffer.

typedef struct multiboot_memory_map {
unsigned int size;
unsigned int base_addr_low,base_addr_high;
//You can also use: unsigned long long int base_addr; if supported.
unsigned int length_low,length_high;
//You can also use: unsigned long long int length; if supported.
unsigned int type;
} multiboot_memory_map_t;
int main(multiboot_info* mbt,unsigned int magic) {
...
multiboot_memory_map_t* mmap = mbt->mmap_addr;
while(mmap < mbt->mmap_addr + mbt->mmap_length) {
...
mmap = (multiboot_memory_map_t*) ( (unsigned int)mmap + mmap->size + sizeof(unsigned int) );
};
...
}

== Counting RAM by direct probing ==

'''WE DISCOURAGE YOU FROM DIRECTLY PROBING MEMORY'''

Use BIOS to get a memory map, or use [[GRUB]].

When perfectly implemented, directly probing memory may allow you to detect the amount of memory even on systems where the BIOS fails to provide the appropriate support (or without even worrying about whether your BIOS can do it or not). Depending on how its coded, may or may not take into account holes in system memory (15/16mb mark ala OS/2) or memory mapped devices like frame buffering SVGA cards, etc.

However, your BIOS knows things you ignore about your motherboard and PCI devices. Probing memory-mapped PCI devices may have *unpredictable results* and possibly *damage your system*, so once again we '''discourage''' its use.

That being said, here comes an example of how it should work (note the interrupt disable and the cache invalidation using InlineAssembly to keep memory consistent :)

/*
* void count_memory (void)
*
* probes memory above 1mb
*
* last mod : 05sep98 - stuart george
* 08dec98 - "" ""
* 21feb99 - removed dummy calls
*
*/
void 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");
mem_end=memkb<<20;
mem=(ULONG*)0x413;
bse_end=((*mem)&0xFFFF)<<6;
outb(0x21, irq1);
outb(0xA1, irq2);
}

----
Related Threads:
* [[Topic:11391|Grubs multiboot memory map]], featuring real examples of GRUB/BIOS reported memory map.

[[Category:FAQ]]
[[Category:FAQ]]
[[Category:Common Devices]]

Revision as of 20:33, 17 May 2008