繁体   English   中英

BareMetalOS如何在没有malloc,brk或mmap的Assembly中分配内存?

[英]How is BareMetalOS allocating memory in Assembly without malloc, brk, or mmap?

作为一个学习实验,我对在程序集(OSX上的NASM中的x86-64)中创建哈希表感兴趣。 要求之一是能够动态分配/管理内存。

浏览了许多有关如何在程序集中分配内存的资源之后,大多数人建议使用brkmmap syscall。 我还没有确切了解它们是如何工作的,因为我在BareMetal-OS中发现了另一种不使用任何系统调用的内存分配实现(在下面复制了它们的代码)。

我的问题是,他们如何做到这一点? 您能为那些没有系统编程背景并且是汇编新手的用户解释在汇编中执行内存分配的相关指令吗? 想要了解如何在程序集中实现内存分配的原因是能够在程序集中实现哈希表。

刚接触汇编(我主要使用JavaScript),并且尚未在汇编中找到有关内存分配的详细资源,所以我不知道从哪里开始。 对您来说可能很明显,但是您有背景,而我没有。 在过去的一两周中,我已经进行了一些汇编 ,因此我了解了有关寄存器上mov的基本知识以及跳转命令,但还不了解他们为实现此内存工作正在做的其他工作。 我的想法是,如果他们可以在不使用brkmmap情况下在程序集中实现内存分配,那么我想这样做,因为那样的话,我真的是直接在没有任何系统层的情况下操纵内存,而且看来您可以对它们进行微调。

这是从GitHub复制的代码:

https://github.com/ReturnInfinity/BareMetal-OS/blob/master/os/syscalls/memory.asm

# =============================================================================
# BareMetal -- a 64-bit OS written in Assembly for x86-64 systems
# Copyright (C) 2008-2014 Return Infinity -- see LICENSE.TXT
#
# Memory functions
# =============================================================================

align 16
db 'DEBUG: MEMORY   '
align 16


# -----------------------------------------------------------------------------
# os_mem_allocate -- Allocates the requested number of 2 MiB pages
#  IN:  RCX = Number of pages to allocate
# OUT:  RAX = Starting address (Set to 0 on failure)
# This function will only allocate continuous pages
os_mem_allocate:
  push rsi
  push rdx
  push rbx

  cmp rcx, 0
  je os_mem_allocate_fail   # At least 1 page must be allocated

  # Here, we'll load the last existing page of memory in RSI.
  # RAX and RSI instructions are purposefully interleaved.

  xor rax, rax
  mov rsi, os_MemoryMap   # First available memory block
  mov eax, [os_MemAmount]   # Total memory in MiB from a double-word
  mov rdx, rsi      # Keep os_MemoryMap unmodified for later in RDX         
  shr eax, 1      # Divide actual memory by 2

  sub rsi, 1
  std       # Set direction flag to backward
  add rsi, rax      # RSI now points to the last page

os_mem_allocate_start:      # Find a free page of memory, from the end.
  mov rbx, rcx      # RBX is our temporary counter

os_mem_allocate_nextpage:
  lodsb
  cmp rsi, rdx      # We have hit the start of the memory map, no more free pages
  je os_mem_allocate_fail

  cmp al, 1
  jne os_mem_allocate_start # Page is taken, start counting from scratch

  dec rbx       # We found a page! Any page left to find?
  jnz os_mem_allocate_nextpage

os_mem_allocate_mark:     # We have a suitable free series of pages. Allocate them.
  cld       # Set direction flag to forward

  xor rdi, rsi      # We swap rdi and rsi to keep rdi contents.
  xor rsi, rdi
  xor rdi, rsi

  # Instructions are purposefully swapped at some places here to avoid 
  # direct dependencies line after line.
  push rcx      # Keep RCX as is for the 'rep stosb' to come
  add rdi, 1
  mov al, 2
  mov rbx, rdi      # RBX points to the starting page
  rep stosb
  mov rdi, rsi      # Restoring RDI
  sub rbx, rdx      # RBX now contains the memory page number
  pop rcx       # Restore RCX

  # Only dependency left is between the two next lines.
  shl rbx, 21     # Quick multiply by 2097152 (2 MiB) to get the starting memory address
  mov rax, rbx      # Return the starting address in RAX
  jmp os_mem_allocate_end

os_mem_allocate_fail:
  cld       # Set direction flag to forward
  xor rax, rax      # Failure so set RAX to 0 (No pages allocated)

os_mem_allocate_end:
  pop rbx
  pop rdx
  pop rsi
  ret
# -----------------------------------------------------------------------------


# -----------------------------------------------------------------------------
# os_mem_release -- Frees the requested number of 2 MiB pages
#  IN:  RAX = Starting address
# RCX = Number of pages to free
# OUT:  RCX = Number of pages freed
os_mem_release:
  push rdi
  push rcx
  push rax

  shr rax, 21     # Quick divide by 2097152 (2 MiB) to get the starting page number
  add rax, os_MemoryMap
  mov rdi, rax
  mov al, 1
  rep stosb

  pop rax
  pop rcx
  pop rdi
  ret
# -----------------------------------------------------------------------------


# -----------------------------------------------------------------------------
# os_mem_get_free -- Returns the number of 2 MiB pages that are available
#  IN:  Nothing
# OUT:  RCX = Number of free 2 MiB pages
os_mem_get_free:
  push rsi
  push rbx
  push rax

  mov rsi, os_MemoryMap
  xor rcx, rcx
  xor rbx, rbx

os_mem_get_free_next:
  lodsb
  inc rcx
  cmp rcx, 65536
  je os_mem_get_free_end
  cmp al, 1
  jne os_mem_get_free_next
  inc rbx
  jmp os_mem_get_free_next

os_mem_get_free_end:
  mov rcx, rbx

  pop rax
  pop rbx
  pop rsi
  ret
# -----------------------------------------------------------------------------


# -----------------------------------------------------------------------------
# os_mem_copy -- Copy a number of bytes
#  IN:  RSI = Source address
# RDI = Destination address
# RCX = Number of bytes to copy
# OUT:  Nothing, all registers preserved
os_mem_copy:
  push rdi
  push rsi
  push rcx

  rep movsb     # Optimize this!

  pop rcx
  pop rsi
  pop rdi
  ret
# -----------------------------------------------------------------------------


# =============================================================================
# EOF

还要注意,我已经阅读了很多有关在C中创建哈希表的资源,我在这里复制其中的一个(具有C代码和相应的程序集)。 但是,几乎所有的C示例都使用malloc ,我想避免这种情况。 我正在尝试完全不依赖C来学习汇编。

同样, 来自Quora的资源有助于指出malloc.c源代码中使用brkmmap位置。 但是,由于发现了BareMetal-OS memory.asm代码,所以我还没有进行研究,该代码似乎甚至不使用那些syscall来分配内存。 因此,问题是,他们如何做到这一点? 您能否在它们的程序集中解释执行内存分配的相关指令?

更新资料

这本书有助于解释mmapbrk以下有关内存内部的几乎所有内容,而所有内容都在实现操作系统方面。 http://www.amazon.com/Modern-Operating-Systems-4th-Edition/dp/013359162X

为了管理内存,您的代码需要“拥有”一些内存。 问题在于,在具有操作系统的任何计算机上,操作系统都拥有所有内存。 因此,您的代码必须向操作系统询问一些内存,可以使用brkmmapmalloc

因此,例如,如果您要汇编编写内存管理器,并且有一台具有4GB内存的计算机,那么在程序开始时从malloc请求1GB内存,然后以任何方式管理该内存将是不合理的。你喜欢的方式。

BareMetal-OS的汇编代码确实不适用于您的情况,因为BareMetal 操作系统,因此不需要请求任何人提供内存。 它已经拥有所有内存,并且可以按自己喜欢的方式对其进行管理。

根据其他评论和答案,BareMetal-OS可以以这种方式实现分配的原因是因为它依赖于发布的代码或常规汇编编译器(例如NASM等)中不存在的几个其他函数调用。在发布的代码中依赖的是:

os_MemoryMap
os_MemAmount

它们要么是BareMetal-OS特定的调用,要么是特定于代码发布者所使用的某些内存管理器的调用。 如果没有一些外部库(例如libc或内存管理器lib),则只能使用brk指令。 45 on x86 12 on x86_64 45 on x86 12 on x86_64 45 on x86 12 on x86_64 )希望这会给难题再添一笔。 祝好运。

这篇文章解释了os_mem_allocate函数的汇编代码。 基本思想是以2MB的块分配内存。 有一个65536个字节的数组( os_MemoryMap ),用于跟踪哪些块是空闲的以及哪些块已使用。 值1是空闲块,值2是已用块。 可以管理的内存总量为64K * 2MB = 128GB。 由于大多数计算机没有那么多内存,因此还有另一个变量( os_MemAmount )指示计算机的内存大小(以MB为单位)。

os_mem_allocate函数的输入是一个计数,即要分配多少2MB块。 该函数旨在仅分配连续的块。 例如,如果输入请求为3,则该函数尝试分配6MB内存,并通过在数组中连续搜索三个1来进行分配。 该函数的返回值是指向分配的内存的指针,如果无法满足请求,则返回0。

输入计数在rcx传递。 该代码验证请求是否为非零数量的块。 输入0会导致返回值为0。

os_mem_allocate:
    push rsi                  # save some registers 
    push rdx
    push rbx

    cmp rcx, 0                # Is the count 0?
    je os_mem_allocate_fail   # If YES, then return 0

该代码执行回旋计算以将rsi指向65536字节数组中的最后一个可用字节。 以下代码段的最后两行是最有趣的。 设置方向标志意味着后续的lodsb指令将递减rsi 当然,将rsi指向数组中的最后一个可用字节是计算的重点。

    xor rax, rax
    mov rsi, os_MemoryMap   # Get the address of the 65536 byte array into RSI
    mov eax, [os_MemAmount] # Get the memory size in MB into EAX
    mov rdx, rsi            # Keep os_MemoryMap in RDX for later use        
    shr eax, 1              # Divide by 2 because os_MemAmount is in MB, but chunks are 2MB

    sub rsi, 1              # in C syntax, we're calculating &array[amount/2-1], which is the address of the last usable byte in the array
    std                     # Set direction flag to backward
    add rsi, rax            # RSI now points to the last byte

接下来,代码具有一个循环,该循环搜索N个连续的空闲块,其中N是在rcx传递给函数的rcx 循环向后扫描阵列,连续查找N 1个。 如果rbx达到0,则循环成功。 rbx循环在数组中找到2时,它将rbx重置回N。

os_mem_allocate_start:       
    mov rbx, rcx                 # RBX is the number of contiguous free chunks we need to find

os_mem_allocate_nextpage:
    lodsb                        # read a byte into AL, and decrement RSI
    cmp rsi, rdx                 # if RSI has reached the beginning of the array
    je os_mem_allocate_fail      # then the loop has failed

    cmp al, 1                    # Is the chunk free?
    jne os_mem_allocate_start    # If NO, we need to restart the count

    dec rbx                      # If YES, decrement the count 
    jnz os_mem_allocate_nextpage # If the count reaches zero we've succeeded, otherwise continue looping

此时,代码已找到足够的连续块来满足请求,因此现在通过将数组中的字节设置为2,将所有块标记为“已使用”。方向标志设置为转发,以便后续的stosb指令将增量rdi

os_mem_allocate_mark:      # We have a suitable free series of chunks, mark them as used
    cld                    # Set direction flag to forward

    xor rdi, rsi           # We swap RDI and RSI to keep RDI contents, but
    xor rsi, rdi           # more importantly we want RDI to point to the     
    xor rdi, rsi           # location in the array where we want to write 2's

    push rcx               # Save RCX since 'rep stosb' will modify it
    add rdi, 1             # the previous loop decremented RSI too many times
    mov al, 2              # the value 2 indicates a "used" chunk
    mov rbx, rdi           # RBX is going to be used to calculate the return value
    rep stosb              # store some 2's in the array, using the count in RCX
    mov rdi, rsi           # Restoring RDI

最后,该函数需要提供一个指针以返回到调用方。

    sub rbx, rdx           # RBX is now an index into the 65536 byte array
    pop rcx                # Restore RCX
    shl rbx, 21            # Multiply by 2MB to convert the index to a pointer
    mov rax, rbx           # Return the pointer in RAX
    jmp os_mem_allocate_end

下一个代码段通过将返回值设置为0处理错误。清除方向标志很重要,因为按照惯例,方向是向前的。

os_mem_allocate_fail:
    cld               # Set direction flag to forward
    xor rax, rax      # Failure so set RAX to 0 (No pages allocated)

最后,还原寄存器并返回指针。

os_mem_allocate_end:
   pop rbx
   pop rdx
   pop rsi
   ret

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM