【问题标题】:How to get RAM Size , bootloader如何获取 RAM 大小,引导加载程序
【发布时间】:2016-08-18 18:02:36
【问题描述】:

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

[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 '\0' 
               ;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,以获得低内存。但我已经测试了打印斧头寄存器,但屏幕上没有显示任何内容。而且我也不知道如何在系统中获得总和可用的 ram。非常感谢您的帮助!

【问题讨论】:

  • 使用 INT 15h, EAX = E820h 获取系统的内存映射,以便找到您可以使用的内存区域。
  • 如果ax包含以千字节为单位的低内存量,那么当mov si, axcall print_char_boot这两条语句被执行时会发生什么?

标签: assembly x86 ram bootloader osdev


【解决方案1】:

虽然您帖子正文中所述的问题更多是关于打印寄存器的值,而不是关于检测系统可用的内存,但我将忠于标题问题,并提供一个示例如何检测系统内存映射。

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


检测内存不是一件容易的事,它需要完全了解所安装的硬件1,如果没有它就无法完成(请参阅 OSDev 上的Detecting memory)。
举个简单的例子,想想aliased memory,如果没有任何相关且缓慢的方法,软件无法检测到它。

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

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


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

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

  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。可以使用SMBusI2C 控制器通过SPD 检索有关已安装模块(主要是DIMM 和移动变体)的信息。
然后 BIOS 会考虑启用的内存映射设备和总线拓扑(连同其路由和桥接信息)并通过 SMBios specification 公开所有这些。

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

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

【讨论】:

  • 好的,首先,非常感谢这个很棒的回复!好的,如图所示,总数为 03f9f000 ,对应于 1,073,347,584 。这是 KB 、 Mb 、 毫字节等吗?还有一个问题,我最近得到了 GRUB 的 0xD5C02700 -> 3,586,139,904 。那是在什么单位?这是我对 30 亿的测试:youtube.com/watch?v=bzE5dkmh9Vs。非常感谢!!
  • @Dsafds 3f9f000h 是 66.711.552。内存的单位是最小的可寻址项:字节(对于 x86)。 3.586139.904 字节略大于 3 GiB。
  • 是总内存还是可用内存?
  • @Dsafds 可用内存。
  • 玛格丽特:最后一个问题。我对 RAM 的数量有点怀疑它告诉我它给了我.. 因为正如您在上面的 viedo 上看到的,当我将它打印为小数时,它给了我这些奇怪的字符(即总 RAM)。你知道为什么会这样吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-08-03
  • 2015-01-16
  • 1970-01-01
  • 2011-11-14
  • 2015-03-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多