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 (Some output on the gap between 0xA0000 and 0xFFFFF)
(Categorisation, Removed PFR, fixed some markup)
Line 1: Line 1:
{{Convert}}

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.
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.


Line 11: Line 9:
== A map of low (conventional) memory ==
== 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 [The Workings of: x86-16/32 RealMode Addressing by Perica Senjak|http://therx.sourceforge.net/osdev/files/ia32_rm_addr.pdf] (minor corrections by BrendanTrotter):
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}}
{| {{wikitable}}
Line 122: Line 120:
You can determine RAM size with the BIOS via two different calls.
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 [Ralf Browns Interrupt List | http://www.ctyme.com/rbrown.htm]:)
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] :)




Line 264: Line 262:
|}
|}


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.

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.
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.
Line 292: Line 289:
'''WE DISCOURAGE YOU FROM DIRECTLY PROBING MEMORY'''
'''WE DISCOURAGE YOU FROM DIRECTLY PROBING MEMORY'''


Use BIOS to get a memory map, or use [GRUB].
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.
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.
Line 381: Line 378:
----
----
Related Threads:
Related Threads:
* [Grub memory map|Forum:9087], featuring real examples of GRUB/BIOS reported memory map.
* [[Topic:11391|Grubs multiboot memory map]], featuring real examples of GRUB/BIOS reported memory map.

Categories: HowTo, HardWareMemory, UsingBios
[[Category:FAQ]]
[[Category:Common Devices]]

Revision as of 10:35, 23 October 2007

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 The Workings of: x86-16/32 RealMode Addressing by Perica Senjak (minor corrections by BrendanTrotter):

start end size region/exception description
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)
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)
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 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 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 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 mbd->flags to see that bit 0 is set, and then you can safely refer to mbd->mem_lower for conventional memory (e.g. physical addresses ranging between 0 and 640KB) and mbd->mem_upper 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 mbd->flags and use mbd->mmap_addr to access the BIOS-provided memory map. Quoting specifications,

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):
-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: