u-boot 없이 진행해도 되지만, 본인은 u-boot를 활용해볼 예정이다.
나는 u-boot가 귀찮아요... 하면 u-boot 없이도 부팅 충분히 가능하다. 패스할것
wget https://ftp.denx.de/pub/u-boot/u-boot-2025.10.tar.bz2
tar -xvf u-boot-2025.10.tar.bz2
make qemu_arm64_defconfig
make CROSS_COMPILE=aarch64-linux-gnu -j$(nproc)
kernel/MakefileTARGET = kernel
CROSS_COMPILE ?= aarch64-none-elf-
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
SRC_DIR := src
INC_DIR := include
BUILD_DIR := bin
CFLAGS = -std=c11 -ffreestanding -nostdlib -nostdinc \
-Wall -Wextra -g -O0 \
-mcpu=cortex-a53 -march=armv8-a
CFLAGS += -I$(INC_DIR)
LDFLAGS = -T linker.ld -nostdlib
SRC_C = $(wildcard $(SRC_DIR)/*.c)
SRC_S = $(wildcard $(SRC_DIR)/*.S)
OBJ_C = $(SRC_C:.c=.o)
OBJ_S = $(SRC_S:.S=.o)
OBJS = $(OBJ_S) $(OBJ_C)
.PHONY: all clean dump
all: $(TARGET).elf $(TARGET).bin
$(TARGET).elf: $(OBJS) linker.ld
$(LD) $(LDFLAGS) -o $@ $(OBJS)
$(TARGET).bin: $(TARGET).elf
$(OBJCOPY) -O binary $< $@
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
%.o: %.S
$(CC) $(CFLAGS) -c $< -o $@
dump: $(TARGET).elf
$(OBJDUMP) -d $(TARGET).elf > $(TARGET).dump
clean:
rm -f $(OBJS) $(TARGET).elf $(TARGET).bin $(TARGET).dump
.PHONY: all clean dump
kernel/src/start.S// src/start.S
// AArch64 bare metal 부트 엔트리
.section .text.boot
.global _start
.extern kmain
.extern __bss_start
.extern __bss_end
_start:
// Disable Interrupt (DAIF 세트)
msr DAIFSet, #0b1111
// 스택 포인터 설정 (임시로 DRAM 상단 근처로 가정)
// KERNEL_LOAD_ADDR + 16MB 정도 예시
ldr x0, =0x41000000
mov sp, x0
// .bss 영역 0으로 클리어
ldr x1, =__bss_start
ldr x2, =__bss_end
bss_clear_loop:
cmp x1, x2
b.ge bss_clear_done
str xzr, [x1], #8 // 8바이트씩 0으로 채우기
b bss_clear_loop
bss_clear_done:
// C 엔트리로 진입
bl kmain
1: // kmain 반환 시 그냥 멈춰있기
wfe
b 1b
kernel/src/kmain.c// src/kmain.c
#include <stdint.h>
#define UART0_BASE 0x09000000
#define UART0_DR (*((volatile uint32_t *)(UART0_BASE + 0x00)))
#define UART0_FR (*((volatile uint32_t *)(UART0_BASE + 0x18)))
#define UART0_FR_TXFF (1 << 5)
void uart_putc(char c) {
while (UART0_FR & UART0_FR_TXFF)
;
UART0_DR = (uint32_t)c;
}
void uart_puts(const char *s) {
while (*s) {
uart_putc(*s++);
}
}
void uart_puthex(uint64_t n) {
const char *hexdigits = "0123456789ABCDEF";
uart_puts("0x");
for (int i = 60; i >= 0; i -= 4) {
uart_putc(hexdigits[(n >> i) & 0xF]);
}
uart_puts("\r\n");
}
uint64_t get_current_el(void) {
uint64_t el;
/* CurrentEL 시스템 레지스터 읽기 */
__asm__ volatile("mrs %0, CurrentEL" : "=r"(el));
return el >> 2; /* 하위 2비트는 reserved라 버림 */
}
void kmain(void) {
uart_puts("Hello from my kernel!\n");
uart_puts("\n\r");
uart_puts("--------------------------------\n\r");
uart_puts(" Boot Successful!\n\r");
uint64_t el = get_current_el();
uart_puts(" Current Exception Level: ");
uart_puthex(el);
if (el == 1) {
uart_puts(" -> We are in EL1 (Kernel Mode)\n\r");
} else if (el == 2) {
uart_puts(" -> We are in EL2 (Hypervisor Mode)\n\r");
} else {
uart_puts(" -> Unknown Level\n\r");
}
uart_puts("--------------------------------\n\r");
while (1) {
__asm__ volatile("wfe");
}
}
kernel/src/linker.ldENTRY(_start)
/* 커널이 올라갈 물리 주소 (QEMU virt 기준 DRAM 0x4000_0000 근처 예시) */
KERNEL_LOAD_ADDR = 0x40200000;
SECTIONS
{
. = KERNEL_LOAD_ADDR;
.text : ALIGN(4K) {
KEEP(*(.text.boot)) /* 부트코드용 섹션 */
*(.text .text.*)
}
.rodata : ALIGN(4K) {
*(.rodata .rodata.*)
}
.data : ALIGN(4K) {
*(.data .data.*)
}
.bss : ALIGN(4K) {
__bss_start = .;
*(.bss .bss.* COMMON)
__bss_end = .;
}
/* 심볼용 섹션 (디버깅용) */
/DISCARD/ : {
*(.comment)
}
}
다음 명령어로 qemu로 커널을 부팅해주자
qemu-system-aarch64 -M virt,virtualization=false \
-cpu cortex-a76 -m 1G -nographic \
-bios u-boot/u-boot.bin \
-device loader,file=kernel/kernel.bin,addr=0x40200000
virtualization=false: QEMU가 EL1 레벨에서 부팅진행 (true하면 EL2, secure=true는 EL3에서 부팅)cortex-a76: ARMv8.2qemu 부팅이 완료되었다면, 다음 u-boot 커맨드로 커널이 로드된 곳으로 점프
go 0x40200000
매번 치는게 귀찮다면,
u-boot/.config파일에서CONFIG_BOOTCOMMAND="go 0x40200000"로 바꿔줄것..
(2025.10 버전 기준 462라인)
솔직히,,, uboot가 필요없긴 하다.
qemu-system-aarch64 -M virt,virtualization=false \ -cpu cortex-a76 -m 1G -nographic \ -kernel kernel-hello/kernel.bin
$ qemu-system-aarch64 \
-M virt,virtualization=false -cpu cortex-a76 -nographic -m 1G \
-bios uboot/u-boot.bin \
-device loader,file=kernel/kernel.bin,addr=0x40200000
Bloblist at 0 not found (err=-2)
alloc space exhausted ptr 400 limit 0
Bloblist at 0 not found (err=-2)
U-Boot 2025.10 (Nov 27 2025 - 15:31:11 +0900)
DRAM: 1 GiB
using memory 0x7e65b000-0x7f69b000 for malloc()
Core: 51 devices, 14 uclasses, devicetree: board
Flash: 64 MiB
Loading Environment from Flash... *** Warning - bad CRC, using default environment
In: serial,usbkbd
Out: serial,vidconsole
Err: serial,vidconsole
No USB controllers found
Net: eth0: virtio-net#32
Hit any key to stop autoboot: 0
## Starting application at 0x40200000 ...
Hello from my kernel!
--------------------------------
Boot Successful!
Current Exception Level: 0x0000000000000001
-> We are in EL1 (Kernel Mode)
--------------------------------