如何获取 RAM 大小,引导加载程序

How to get RAM Size , bootloader

我想问一下如何在引导加载程序中获取 Total RAM Size 和 Usable RAM Size。截至目前,我知道如何获得较低的内存。但出于某种原因我无法将其打印到屏幕上,因为它保存在 ax 寄存器中。这是我目前所拥有的:

[BITS 16] ; BootLoader always starts 16 BIT Moded

    jmp main_bootloader ; Jump to Main Bootloader

     ;************** INITALIZED VARIABLES *********************;
      string db 'BoneOS Loading . . .', 0x0
      string2 db 'Starting of 16Bit Bootloader' , 0x0
      press_to_cont db 'Press any key to continue . . .' , 0x0
      carry_flag_err db ' CARRY FLAG HAS BEEN SET! ERROR ' , 0x0
      magic_number equ 0x534D4150
      limit dw 0
      base  dw 0
      low_memory dd 0
      answer resb 64
     ;*********************************************************;  

     ;******************** GDTs *****************************;


         null_descriptor :
            dd 0                ; null descriptor--just fill 8 bytes with zero
            dd 0 

        ; Notice that each descriptor is exactally 8 bytes in size. THIS IS IMPORTANT.
        ; Because of this, the code descriptor has offset 0x8.

         code_descriptor:           ; code descriptor. Right after null descriptor
            dw 0FFFFh           ; limit low
            dw 0                ; base low
            db 0                ; base middle
            db 10011010b            ; access
            db 11001111b            ; granularity
            db 0                ; base high

        ; Because each descriptor is 8 bytes in size, the Data descritpor is at offset 0x10 from
        ; the beginning of the GDT, or 16 (decimal) bytes from start.

         data_descriptor:           ; data descriptor
            dw 0FFFFh           ; limit low (Same as code)
            dw 0                ; base low
            db 0                ; base middle
            db 10010010b            ; access
            db 11001111b            ; granularity
            db 0                ; base high

        end_of_gdt: 
            toc: 
                dw end_of_gdt - null_descriptor - 1     ; limit (Size of GDT)
                dd null_descriptor          ; base of GDT   

        load_gdt:
            lgdt [toc]

        .done:
            ret


     ;*********************************************************;

     ;*************** LABELS FOR MAIN **************************;
        print_char_boot:
            mov ah, 0Eh ; Code For BIOS To Print Char 0Eh

        .repeat:
            lodsb ; Load Byte From SI Register
            cmp al, 0 ; Compare AL With 0 If so Done
            je .done 
            int 10h ; Call Interupt. Checks AH Register for code 0EH = Print char
            jmp .repeat ; Loop Back

        .done:
            ret ; Return to previous code


        print_new_line:
               mov al, 0    ; null terminator '[=10=]' 
               ;Adds a newline break '\n'
               mov ah, 0x0E
               mov al, 0x0D
               int 0x10
               mov al, 0x0A 
               int 0x10
               ret

        get_pressed_key:
            mov ah, 0
            int 0x16  ;BIOS Call. Key goes to al register
            ret 
        GET_RAM_SIZE:

        reboot:
            mov si, press_to_cont
            call print_char_boot
            call get_pressed_key ; Gets Pressed Key

            int 19h ;Reboot
            ret

        enable_A20: ; Enabling A20 Line For Full Memory
            cli ; Stop Interupts before doing so

            call    a20wait ; a20wait call
            mov     al,0xAD ; Send 0xAD Command to al register
            out     0x64,al ; Send command 0xad (disable keyboard).

            call    a20wait ; When controller ready for command
            mov     al,0xD0 ; Send 0xD0 Command to al register
            out     0x64,al ; Send command 0xd0 (read from input)

            call    a20wait2 ; When controller ready for command
            in      al,0x60 ; Read input from keyboard
            push    eax ; Save Input by pushing to stack

            call    a20wait ; When controller ready for command
            mov     al,0xD1 ; mov 0xD1 Command to al register
            out     0x64,al ; Set command 0xd1 (write to output)

            call    a20wait ; When controller ready for command
            pop     eax ; Pop Input from Keyboard
            or      al,2 ; Mov 0xD3 to al register
            out     0x60,al ; Set Command 0xD3

            call    a20wait ; When controller ready for command
            mov     al,0xAE ; Mov Command 0xAE To al register
            out     0x64,al ; Write command 0xae (enable keyboard)

            call    a20wait ; When controller ready for command
            sti ; Enable Interrupts after enabling A20 Line
            ret

                a20wait:
                    in      al,0x64 ; input from 0x64 port, goes to al register
                    test    al,2 ; compares al register with 2
                    jnz     a20wait ; If it is zero loop again
                    ret


                a20wait2:
                    in      al,0x64 ; input from 0x64 port, goes to al register
                    test    al,1 ; compares al register with 2
                    jz      a20wait2 ; If it is zero loop again
                    ret 


            get_lower_memory:
                clc ; Clears Carry Flag
                int 0x12 ; BIOS Call Request Lower Memory Size in KB
                jc .err ; If Carry Flag Has Been Set , the system its running on dosent support this
                jmp .done ; If Sucessfull ax register contains contiguous low memory in KB

            .err:
                times 5 call print_new_line ; Prints New Line
                mov si, carry_flag_err
                call print_char_boot 
                jmp .repeat

            .done:
            ret

        .repeat:
            jmp .repeat





;**************************************************************;


;*******************'MAIN' BOOTLOADER FUNCTION ****************;
main_bootloader:
    xor ax, ax
    mov ss, ax
    mov sp, 4096

    mov ax, 07C0h       ; Set data segment to where we're loaded
    mov ds, ax

    mov si, string ; si register usefull for lodsb command
    call print_char_boot ; Call print_char_boot label below
    call print_new_line ; Prints New Line
    mov si, string2
    call print_char_boot 
    times 2 call print_new_line
    ; Enable A20 Line
    call enable_A20

    call get_lower_memory ; Get Low Memory


    mov si,ax
    call print_char_boot 
    times 5 call print_new_line

    call reboot ; Reboot

    ;call null_descriptor


    jmp $ ; Infinite Loop 
    ;Bootloader gets infinite loop 
    ;Incase No Infinite Loop in Kernel

;****************************************************************;  


;************************* BOOTLOADER REQUIREMENTS **************;  
times 510 - ($ - $$) db 0 ; Has to be 512 bytes .. Repeats 510 byes to make it so
dw 0xAA55 ; BootLoader Sig. To Validate this is a bootloader
;

****************************************************************;

正如你在我的主页上看到的那样,我是 call get_lower_memory ; Get Low Memory ,以获得低内存。但我已经测试过打印 ax 寄存器,但屏幕上没有显示任何内容。而且我也不知道如何在系统中获得总的和可用的内存。帮助将不胜感激!

虽然你的 post 的 body 中提出的问题更多是关于打印寄存器的值而不是关于检测系统可用的内存,但我会很忠诚针对标题问题,并举例说明如何检测系统内存映射。

作为奖励,提供了将 32 位无符号整数显示为十六进制数字的函数,以及支持占位符的非常原始的 print


检测内存不是一件容易的事,它需要对安装的硬件有全面的了解1,没有它就无法完成(参见 OSDev 上的 Detecting memory)。
作为一个简单的例子,想想一个 aliased memory,如果没有任何涉及和缓慢的方法,软件无法检测到它。

承认与 BIOS 的合作是强制性的,我们可以看到 16 位实模式引导加载程序可以使用哪些服务。
上面提到的 OSDev 关于 detecting memory 的页面已经有一个专用于标题目的的服务列表,请参考。

我们将专注于 Int 15/AX=E820h 服务。
它的用途是 return 内存范围列表及其描述。
每次调用 return 下一个描述符,使用 ebx 来跟踪进度。寄存器 ebx 应被视为不透明值。
尽管 Ralf's Brown Interrupt List 中有描述,但描述符可以是 24 字节长,因此最好使用该长度并最终检查 returned in ecx 中的值] 区分 20/24 字节的描述符。


一旦我们有了描述符列表,它们就可以被分配内存的例程使用2.
拖东西不值钱:

  1. 描述符排序。一些有问题的 BIOS 可能 return 重叠 区域(在这种情况下做出最保守的选择)。

  2. 即使对描述符进行排序,也可能存在差距。不报告没有内存映射的范围,这是标准孔的情况(范围从 0a0000h 到 0fffffh)。

  3. 虽然报告了 BIOS 明确保留的区域(例如从 0f0000h 到 0fffffh 的阴影区域)。

在下面的示例中,描述符与非保留内存总量一起打印在屏幕上3

顺便说一句,你可以使用itoa16函数在EAX中打印一个32位值,假设你改变了字符在屏幕上的打印方式。

BITS 16

;Set CS to a known value
;This makes the offsets in memory and in source match 
;(e.g. __START__ is at offset 5h in the binary image and at addres 7c0h:0005h)

jmp 7c0h:__START__

__START__:
 ;Set all the segments to CS 
 mov ax, cs
 mov ds, ax
 mov es, ax
 mov ss, ax
 xor sp, sp

 ;Clear the screen
 mov ax, 03h
 int 10h

 ;FS will be used to write into the text buffer
 push 0b800h
 pop fs

 ;SI is the pointer in the text buffer 
 xor si, si 

 ;These are for the INT 15 service
 mov di, baseAddress                    ;Offset in ES where to save the result
 xor ebx, ebx                           ;Start from beginning
 mov ecx, 18h                           ;Length of the output buffer (One descriptor at a time)

 ;EBP will count the available memory 
 xor ebp, ebp 

_get_memory_range:
 ;Set up the rest of the registers for INT 15 
 mov eax, 0e820h 
 mov edx, 534D4150h
 int 15h
 jc _error 

 ;Has somethig been returned actually?
 test ecx, ecx
 jz _next_memory_range

 ;Add length (just the lower 32 bits) to EBP if type = 1 or 3 
 mov eax, DWORD [length]

 ;Avoid a branch (just for the sake of less typing)

 mov edx, DWORD [type]         ;EDX = 1        | 2        | 3        | 4   (1 and 3 are available memory)
 and dx, 01h                   ;EDX = 1        | 0        | 1        | 0 
 dec edx                       ;EDX = 0        | ffffffff | 0        | ffffffff 
 not edx                       ;EDX = ffffffff | 0        | ffffffff | 0 
 and eax, edx                  ;EAX = length   | 0        | length   | 0 

 add ebp, eax

 ;Show current memory descriptor 
 call show_memory_range

_next_memory_range:
 test ebx, ebx 
 jnz _get_memory_range

 ;Print empty line
 push WORD strNL 
 call print 

 ;Print total memory available 
 push ebp 
 push WORD strTotal
 call print 

 cli
 hlt

_error:
 ;Print error
 push WORD strError
 call print

 cli 
 hlt


 ;Memory descriptor returned by INT 15 
 baseAddress dq 0
 length      dq 0
 type        dd 0
 extAttr     dd 0

 ;This function just show the string strFormat with the appropriate values 
 ;taken from the mem descriptor 
 show_memory_range:
  push bp
  mov bp, sp

  ;Extend SP into ESP so we can use ESP in memory operanda (SP is not valid in any addressing mode)
  movzx esp, sp 

  ;Last percent
  push DWORD [type]

  ;Last percents pair
  push DWORD [length]
  push DWORD [length + 04h]

  ;Add baseAddress and length (64 bit addition)
  push DWORD [baseAddress]
  mov eax, DWORD [length]
  add DWORD [esp], eax               ;Add (lower DWORD)
  push DWORD [baseAddress + 04h]
  mov eax, DWORD [length + 04h]
  adc DWORD [esp], 0                 ;Add with carry (higher DWORD)

  ;First percents pair
  push DWORD [baseAddress]
  push DWORD [baseAddress + 04h]

  push WORD strFormat
  call print

  mov sp, bp                         ;print is a mixed stdcall/cdecl, remove the arguments

  pop bp
  ret

 ;Strings, here % denote a 32 bit argument printed as hex 
 strFormat db "%% - %% (%%) - %", 0
 strError  db "Som'thing is wrong :(", 0
 strTotal  db "Total amount of memory: %", 0 
 ;This is tricky, see below 
 strNL     db 0

 ;Show a 32 bit hex number
 itoa16:
  push cx
  push ebx

  mov cl, 28d

 .digits:
   mov ebx, eax
   shr ebx, cl
   and bx, 0fh                     ;Get current nibble

   ;Translate nibble (digit to digital)
   mov bl, BYTE [bx + hexDigits]

   ;Show it 
   mov bh, 0ch
   mov WORD [fs:si], bx
   add si, 02h   

   sub cl, 04h
  jnc .digits

  pop ebx
  pop cx
  ret

  hexDigits db "0123456789abcdef"

  ;This function is a primitive printf, where the only format is % to show a 32 bit 
  ;hex number 
  ;The "cursor" is kept by SI.
  ;SI is always aligned to lines, so 1) never print anything bigger than 80 chars
  ;2) successive calls automatically print into their own lines 
  ;3) SI is assumed at the beginning of a line 

  ;Args
  ;Format
  print:
   push bp
   mov bp, sp

   push di
   push cx

   mov di, WORD [bp+04h]      ;String 
   mov cx, 80*2               ;How much to add to SI to reach the next line 

   add bp, 06h                ;Pointer to var arg 

  .scan:

    ;Read cur char 
    mov al, [di]
    inc di

    ;Format?
    cmp al, '%'
    jne .print

    ;Get current arg and advance index 
    mov eax, DWORD [bp]
    add bp, 04h
    ;Show the number 
    call itoa16

    ;We printed 8 chars (16 bytes) 
    sub cx, 10h

   jmp .scan    

  .print:
    ;End of string?
    test al, al
    je .end

    ;Normal char, print it 
    mov ah, 0ch
    mov WORD [fs:si], ax
    add si, 02h
    sub cx, 02h

   jmp .scan   


  .end:
   add si, cx

   pop cx
   pop di

   pop bp
   ret 02h

   ;Signature
   TIMES 510 - ($-$$) db 0 
   dw 0aa55h

在64MiB的Bochs仿真机中,结果为

格式为开始-结束(大小)-类型

使用图片我们得到

计算出的内存总量为 66.711.552 字节或 64 MiB - 1 KiB (EBDA) - 96 KiB(阴影区域) - 288 KiB(标准孔)。
ACPI 表被认为是可用的,因为它们是可回收的。


1 特别是北桥的部分,现在是iMC,专用于处理DRAM。可以通过 SPD using either a SMBus or I2C 控制器检索有关已安装模块(主要是 DIMM 和移动变体)的信息。
BIOS 然后考虑启用的内存映射设备和总线拓扑(连同其路由和桥接信息)并通过 SMBios specification.

公开所有这些

2 因为它无论如何都会使用某种范围描述符,所以最终会执行格式转换。

3 此计数包括新类型 5(坏内存)范围。