우선, virt board의 메모리 구조를 참조하기 위해서 QEMU의 source code에서 memory map을 직접 찾아 확인하였다.

소스코드는 다음과 같았다.

Virt Board Memory Map

hw/arm/virt.c

/* Addresses and sizes of our components.
 * 0..128MB is space for a flash device so we can run bootrom code such as UEFI.
 * 128MB..256MB is used for miscellaneous device I/O.
 * 256MB..1GB is reserved for possible future PCI support (ie where the
 * PCI memory window will go if we add a PCI host controller).
 * 1GB and up is RAM (which may happily spill over into the
 * high memory region beyond 4GB).
 * This represents a compromise between how much RAM can be given to
 * a 32 bit VM and leaving space for expansion and in particular for PCI.
 * Note that devices should generally be placed at multiples of 0x10000,
 * to accommodate guests using 64K pages.
 */
static const MemMapEntry base_memmap[] = {
    /* Space up to 0x8000000 is reserved for a boot ROM */
    [VIRT_FLASH] =              {          0, 0x08000000 },
    [VIRT_CPUPERIPHS] =         { 0x08000000, 0x00020000 },
    /* GIC distributor and CPU interfaces sit inside the CPU peripheral space */
    [VIRT_GIC_DIST] =           { 0x08000000, 0x00010000 },
    [VIRT_GIC_CPU] =            { 0x08010000, 0x00010000 },
    [VIRT_GIC_V2M] =            { 0x08020000, 0x00001000 },
    [VIRT_GIC_HYP] =            { 0x08030000, 0x00010000 },
    [VIRT_GIC_VCPU] =           { 0x08040000, 0x00010000 },
    /* The space in between here is reserved for GICv3 CPU/vCPU/HYP */
    [VIRT_GIC_ITS] =            { 0x08080000, 0x00020000 },
    /* This redistributor space allows up to 2*64kB*123 CPUs */
    [VIRT_GIC_REDIST] =         { 0x080A0000, 0x00F60000 },
    [VIRT_UART] =               { 0x09000000, 0x00001000 },
    [VIRT_RTC] =                { 0x09010000, 0x00001000 },
    [VIRT_FW_CFG] =             { 0x09020000, 0x00000018 },
    [VIRT_GPIO] =               { 0x09030000, 0x00001000 },
    [VIRT_SECURE_UART] =        { 0x09040000, 0x00001000 },
    [VIRT_SMMU] =               { 0x09050000, 0x00020000 },
    [VIRT_PCDIMM_ACPI] =        { 0x09070000, MEMORY_HOTPLUG_IO_LEN },
    [VIRT_ACPI_GED] =           { 0x09080000, ACPI_GED_EVT_SEL_LEN },
    [VIRT_NVDIMM_ACPI] =        { 0x09090000, NVDIMM_ACPI_IO_LEN},
    [VIRT_PVTIME] =             { 0x090a0000, 0x00010000 },
    [VIRT_SECURE_GPIO] =        { 0x090b0000, 0x00001000 },
    [VIRT_MMIO] =               { 0x0a000000, 0x00000200 },
    /* ...repeating for a total of NUM_VIRTIO_TRANSPORTS, each of that size */
    [VIRT_PLATFORM_BUS] =       { 0x0c000000, 0x02000000 },
    [VIRT_SECURE_MEM] =         { 0x0e000000, 0x01000000 },
    [VIRT_PCIE_MMIO] =          { 0x10000000, 0x2eff0000 },
    [VIRT_PCIE_PIO] =           { 0x3eff0000, 0x00010000 },
    [VIRT_PCIE_ECAM] =          { 0x3f000000, 0x01000000 },
    /* Actual RAM size depends on initial RAM and device memory settings */
    [VIRT_MEM] =                { GiB, LEGACY_RAMLIMIT_BYTES },
};
Link to original

즉, 이를 정리하면 다음과 같은 메모리 구조를 가지고 있다.

NAMESTART_ADDREND_ADDRLEN
Boot Rom RESERVED (0 to 0x08000000)<<<
VIRT_FLASH0x000000000x080000000x08000000
VIRT_CPUPERIPHS0x080000000x080200000x00020000
GICD & GICC sits inside CPU Peripheral space<<<
VIRT_GIC_DIST0x080000000x080100000x00010000
VIRT_GIC_CPU0x080100000x080200000x00010000
VIRT_GIC_V2M0x080200000x080210000x00001000
VIRT_GIC_HYP0x080300000x080400000x00010000
VIRT_GIC_VCPU0x080400000x080500000x00010000
The space in between here is reserved for GICv3 CPU/vCPU/HYP<<<
VIRT_GIC_ITS0x080800000x080A00000x00020000
This redistributor space allows up to 264kB123 CPUs<<<
VIRT_GIC_REDIST0x080A00000x090000000x00F60000
VIRT_UART0x090000000x090010000x00001000
VIRT_RTC0x090100000x090110000x00001000
VIRT_FW_CFG0x090200000x090200180x00000018
VIRT_GPIO0x090300000x090310000x00001000
VIRT_SECURE_UART0x090400000x090410000x00001000
VIRT_SMMU0x090500000x090700000x00020000
VIRT_PCDIMM_ACPI0x090700000x090700180x00000018
VIRT_ACPI_GED0x090800000x090800040x00000004
VIRT_NVDIMM_ACPI0x090900000x090900040x00000004
VIRT_PVTIME0x090A00000x090B00000x00010000
VIRT_SECURE_GPIO0x090B00000x090B10000x00001000
VIRT_MMIO0x0A0000000x0A0002000x00000200
VIRT_PLATFORM_BUS0x0C0000000x0E0000000x02000000
VIRT_SECURE_MEM0x0E0000000x010000000x01000000
VIRT_PCIE_MMIO0x100000000x3EFF00000x2eff0000
VIRT_PCIE_PIO0x3EFF00000x3F0000000x00010000
VIRT_PCIE_ECAM0x3F0000000x400000000x01000000
VIRT_MEM0x400000000x40400000000x4000000000
Customizable Area<<<
QEMU_DTB0x400000000x402000000x00200000
COSMOS_RAM_START0x40200000

이제 커널의 memory map을 linker script를 이용하여 구성하도록 한다.

크게 text, rodata, data, bss 섹션이 있으며, stack을 위한 공간도 다음의 코드와 같이 충분히 할당해 주었다.

/* Parts of this linker script are directly taken from Andre Richters Project:
 * https://github.com/rust-embedded/rust-raspberrypi-OS-tutorials/blob/master/16_virtual_mem_part4_higher_half_kernel/src/bsp/raspberrypi/link.ld 
*/
 
OUTPUT_FORMAT("elf64-littleaarch64")
OUTPUT_ARCH("aarch64")
ENTRY(_start)
kernel_start = 0x40200000;
 
PHDRS
{
  segment_ro PT_LOAD FLAGS(4); /* 4 == RO */
  segment_rx PT_LOAD FLAGS(5); /* 5 == RX */
  segment_rw PT_LOAD FLAGS(6); /* 6 == RW */
}
 
SECTIONS
{
  . = kernel_start;
  .text : {
    __text_start = .;
    KEEP(*(.text._start))
    *(.text)
    *(.text.*)
    __text_end = .;
  } :segment_rx
 
  .rodata : ALIGN(8) {
    __rodata_start = .;
    *(.rodata)
    *(.rodata.*)
    __rodata_end = .;
  } :segment_ro
 
  .got    : ALIGN(8) {
    /* Global offset table Todo */
    *(.got)
  } :segment_ro
  ASSERT(SIZEOF(.got) == 0, "Relocation Not Supported")
 
  .data   : ALIGN(8) {
    *(.data)
    *(.data.*)
  } :segment_rw
  .bss (NOLOAD)    : ALIGN(16) {
    __bss_start = .;
    *(.bss)
    *(.bss.*)
    . = ALIGN(16);
    __bss_end_exclusive = .;
  } :segment_rw
  . = ALIGN(4K); /* Align to page boundary */
  /***********************************************************************************************
   * Boot Core Stack
   ***********************************************************************************************/
  __boot_core_stack_start = .;         /*   ^             */ 
                                       /*   | stack       */
  . += 16K;                            /*   | growth      */
                                       /*   | direction   */
  __boot_core_stack_end_exclusive = .; /*   |             */
  kernel_end = .;
}

Linker Script - Align

. = ALIGN(8);

equals to..

while ((current_location & 7) != 0)
  *current_location++ = padding_value;

Linux Memory Layout

Text Segment (=Code Segment)

  • 프로그램에서 실행할 코드가 위치할 메모리 영역
  • 쓰기 권한 없음, 실행 권한 있음

Data Segment

  • 컴파일 시에 값이 정해지는 전역 변수
  • 쓰기 권한 존재

ROData Segment

  • 컴파일 시에 값이 정해지고 변경되지 않는 전역 상수
  • 쓰기 권한 없음

BSS Segment

  • 컴파일 시에 초기값이 할당되지 않은 전역변수
  • 초기값은 0으로 할당되며, 쓰기 권한 존재

Heap Segment

  • 동적으로 할당받는 메모리의 영역

Stack Segment

  • 프로세스의 스택이 위치하는 메모리 영역

Reference