커널 아키텍처 (Kernel Architecture)
x86_64, ARM64, RISC-V 아키텍처별 커널 구조, 부팅 과정, 주소 공간 레이아웃을 상세히 다룹니다.
리눅스 커널 개요 (Linux Kernel Overview)
리눅스 커널은 1991년 Linus Torvalds가 처음 공개한 이래, 현재 세계에서 가장 널리 사용되는 운영체제 커널입니다. 서버, 데스크탑, 임베디드 기기, 스마트폰(Android), 슈퍼컴퓨터에 이르기까지 거의 모든 컴퓨팅 영역에서 동작합니다. 커널은 하드웨어와 사용자 공간(user space) 사이에서 추상화 계층 역할을 하며, 다음과 같은 핵심 기능을 담당합니다:
- 프로세스 관리 (Process Management) - 프로세스 생성, 스케줄링, 종료, 시그널 처리
- 메모리 관리 (Memory Management) - 가상 메모리, 페이지 테이블, 물리 메모리 할당
- 파일시스템 (Filesystem) - VFS를 통한 다양한 파일시스템 지원
- 디바이스 드라이버 (Device Drivers) - 하드웨어 추상화 및 제어
- 네트워킹 (Networking) - TCP/IP 스택, 소켓, 패킷 필터링
- 보안 (Security) - LSM, SELinux, capabilities, seccomp
모놀리식 vs 마이크로커널 (Monolithic vs Microkernel)
운영체제 커널 설계에는 크게 두 가지 접근 방식이 있습니다. 모놀리식 커널(Monolithic Kernel)은 모든 핵심 서비스(프로세스 관리, 메모리 관리, 파일시스템, 드라이버 등)가 하나의 커다란 커널 이미지 안에서 동일한 주소 공간(커널 공간)에서 실행됩니다. 반면 마이크로커널(Microkernel)은 최소한의 기능만 커널에 포함하고 나머지는 사용자 공간 서버로 분리합니다.
리눅스는 모놀리식 커널입니다. 그러나 순수한 모놀리식이 아닌, 동적으로 적재 가능한 커널 모듈(Loadable Kernel Module, LKM)을 지원하여 모듈화의 유연성을 확보합니다. 이를 "모듈형 모놀리식(Modular Monolithic)" 커널이라고도 합니다.
┌─────────────────────────────────────────────────────────────┐
│ 모놀리식 커널 (Linux) │
│ ┌──────────┬──────────┬──────────┬──────────┬──────────┐ │
│ │ Process │ Memory │ VFS │ Network │ Device │ │
│ │ Mgmt │ Mgmt │ │ Stack │ Drivers │ │
│ └──────────┴──────────┴──────────┴──────────┴──────────┘ │
│ 모두 Ring 0 (커널 공간)에서 실행 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 마이크로커널 (Minix, QNX) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ User Space: FS 서버 │ 드라이버 서버 │ 네트워크 서버 │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 커널: IPC, 스케줄링, 기본 메모리 관리 │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
x86_64 아키텍처 (x86_64 Architecture)
x86_64(또는 AMD64, Intel 64)는 데스크탑과 서버 환경에서 가장 널리 사용되는 아키텍처입니다. x86의 32비트 아키텍처를 64비트로 확장한 것으로, 리눅스 커널에서 가장 오랫동안 지원해 온 아키텍처 중 하나입니다.
부팅 과정 (Boot Process)
x86_64 시스템의 부팅 과정은 펌웨어에서 시작하여 커널이 완전히 초기화될 때까지 여러 단계를 거칩니다. 현대 시스템에서는 UEFI가 표준이지만, 레거시 BIOS도 여전히 지원됩니다.
x86_64 부팅 과정 (Boot Sequence)
═══════════════════════════════════════════════════════════
┌───────────┐ ┌───────────────┐ ┌──────────────────┐
│ BIOS / │───→│ Bootloader │───→│ Linux Kernel │
│ UEFI │ │ (GRUB2/sysli) │ │ (vmlinuz) │
└───────────┘ └───────────────┘ └──────────────────┘
│ │ │
POST & HW 초기화 Stage1: MBR/ESP 로드 decompress_kernel()
CPU Real Mode Stage2: 커널 이미지 로드 start_kernel()
메모리 맵 감지 initrd/initramfs 로드 rest_init()
커널 커맨드라인 전달 → init 프로세스 실행
─── 상세 흐름 ───────────────────────────────────────────
1. 전원 ON → CPU가 리셋 벡터(0xFFFFFFF0)에서 실행 시작
2. BIOS/UEFI 펌웨어: POST, 하드웨어 초기화, 부트 디바이스 선택
3. Bootloader 1단계: MBR(512바이트) 또는 UEFI ESP에서 로드
4. Bootloader 2단계: 파일시스템 인식, 커널+initramfs 로드
5. Real Mode → Protected Mode → Long Mode (64-bit) 전환
6. startup_64() → x86_64_start_kernel() → start_kernel()
7. 아키텍처 초기화, 메모리 설정, 스케줄러 초기화
8. rest_init() → kernel_init() → /sbin/init 실행
CONFIG_EFI_STUB=y 설정이 필요합니다.
커널의 x86_64 엔트리 포인트는 어셈블리로 작성되어 있습니다. 다음은 핵심 부팅 코드의 간략화된 예시입니다:
/* arch/x86/kernel/head_64.S - x86_64 커널 엔트리 포인트 (간략화) */
SYM_CODE_START_NOALIGN(startup_64)
/* 세그먼트 레지스터 초기화 */
xorl %eax, %eax
movl %eax, %ds
movl %eax, %es
movl %eax, %ss
movl %eax, %fs
movl %eax, %gs
/* 초기 페이지 테이블 설정 (Identity mapping) */
leaq early_top_pgt(%rip), %rax
movq %rax, %cr3
/* 스택 포인터 설정 */
leaq init_thread_union+THREAD_SIZE(%rip), %rsp
/* C 코드로 점프 */
call x86_64_start_kernel
SYM_CODE_END(startup_64)
주소 공간 레이아웃 (Address Space Layout)
x86_64에서는 48비트 가상 주소 공간(256TB)을 사용합니다. 이 공간은 사용자 영역(하위)과 커널 영역(상위)으로 명확하게 분리됩니다. 최신 커널에서는 5-level paging을 통해 57비트(128PB) 주소 공간도 지원합니다. 다음 다이어그램은 4-level paging 기준 주소 공간 레이아웃을 보여줍니다:
세그먼테이션과 페이징 (Segmentation & Paging)
x86_64에서 세그먼테이션은 사실상 flat model로 사용됩니다. 모든 세그먼트의 베이스가 0이고
리미트가 최대값으로 설정되어, 세그먼테이션은 사실상 비활성화된 상태입니다. 그러나 GDT(Global Descriptor Table)는
여전히 존재하며, 커널/사용자 모드 전환과 TSS(Task State Segment)를 위해 필수적입니다.
페이징은 4단계 페이지 테이블을 사용합니다 (5단계 페이징은 CONFIG_X86_5LEVEL=y로 활성화):
- PGD (Page Global Directory) - 512 엔트리, CR3가 가리킴
- PUD (Page Upper Directory) - 512 엔트리
- PMD (Page Middle Directory) - 512 엔트리
- PTE (Page Table Entry) - 512 엔트리, 최종 물리 페이지 매핑
x86_64 4-Level Page Table Walk (48-bit VA)
═══════════════════════════════════════════════════════════
가상 주소 (48-bit):
┌──────┬──────┬──────┬──────┬──────────┐
│ PGD │ PUD │ PMD │ PTE │ Offset │
│[47:39│[38:30│[29:21│[20:12│ [11:0] │
│ 9bit │ 9bit │ 9bit │ 9bit │ 12bit │
└──┬───┴──┬───┴──┬───┴──┬───┴────┬─────┘
│ │ │ │ │
▼ ▼ ▼ ▼ │
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ PGD │→ │ PUD │→ │ PMD │→ │ PTE │──┘→ 물리 주소
│ table│ │ table│ │ table│ │ table│ (최대 52-bit)
└──────┘ └──────┘ └──────┘ └──────┘
↑
CR3 레지스터
링 구조 (Protection Rings)
x86_64는 4개의 특권 레벨(Ring 0 ~ Ring 3)을 제공하지만, 리눅스에서는 실제로 Ring 0(커널 모드)과 Ring 3(사용자 모드) 두 레벨만 사용합니다. Ring 1과 Ring 2는 사용되지 않으며, 가상화 확장(VT-x)에서는 Ring -1(VMX root mode)이라는 개념이 추가됩니다.
/* arch/x86/include/asm/segment.h - GDT 세그먼트 정의 */
/*
* GDT 레이아웃 (간략화):
* Entry 0: NULL 디스크립터 (CPU 요구사항)
* Entry 1: Kernel Code Segment (CS) - DPL=0
* Entry 2: Kernel Data Segment (DS) - DPL=0
* Entry 3: User Code Segment (CS) - DPL=3
* Entry 4: User Data Segment (DS) - DPL=3
* Entry 5: TSS (Task State Segment)
*/
#define GDT_ENTRY_KERNEL_CS 1
#define GDT_ENTRY_KERNEL_DS 2
#define GDT_ENTRY_DEFAULT_USER_CS 3
#define GDT_ENTRY_DEFAULT_USER_DS 4
/* 세그먼트 셀렉터 값 (index << 3 | RPL) */
#define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8) /* 0x08 - Ring 0 */
#define __KERNEL_DS (GDT_ENTRY_KERNEL_DS * 8) /* 0x10 - Ring 0 */
#define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS * 8 + 3) /* 0x1B - Ring 3 */
#define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS * 8 + 3) /* 0x23 - Ring 3 */
SYSCALL 명령어를 사용합니다.
이 명령어는 Ring 3에서 Ring 0으로 전환하며, MSR_LSTAR 레지스터에 저장된 시스템 콜 핸들러 주소로 점프합니다.
SYSRET으로 사용자 모드로 복귀합니다. 이전의 int 0x80 방식보다 훨씬 빠릅니다.
ARM64 아키텍처 (ARM64 / AArch64 Architecture)
ARM64(AArch64)는 모바일 기기부터 서버, 슈퍼컴퓨터까지 빠르게 확산되고 있는 아키텍처입니다. Apple Silicon(M1/M2/M3), AWS Graviton, Ampere Altra 등 고성능 ARM64 프로세서가 서버 시장에서도 중요한 위치를 차지하고 있습니다.
Exception Level (EL0 ~ EL3)
ARM64는 x86의 Ring 구조 대신 Exception Level(EL)이라는 4단계 특권 모델을 사용합니다. 각 EL은 명확한 역할을 가지며, 하위 EL에서 상위 EL로의 전환은 exception 발생 시에만 가능합니다.
- EL0 (User/Application) - 사용자 애플리케이션이 실행되는 레벨. 가장 낮은 특권.
- EL1 (OS Kernel) - 운영체제 커널이 실행되는 레벨. 리눅스 커널은 여기서 동작합니다.
- EL2 (Hypervisor) - 하이퍼바이저가 실행되는 레벨. KVM이 이 레벨을 사용합니다.
- EL3 (Secure Monitor) - ARM TrustZone의 Secure Monitor. 보안 세계와 일반 세계 전환을 관리합니다.
ARM64 Exception Level 구조
═══════════════════════════════════════════════
특권 높음 ↑
│ ┌─────────────────────────────┐
EL3 │ │ Secure Monitor (ATF/TF-A) │ ← ARM Trusted Firmware
│ └─────────────────────────────┘
│ ┌─────────────────────────────┐
EL2 │ │ Hypervisor (KVM) │ ← 가상화 지원
│ └─────────────────────────────┘
│ ┌─────────────────────────────┐
EL1 │ │ OS Kernel (Linux) │ ← 커널 모드
│ └─────────────────────────────┘
│ ┌─────────────────────────────┐
EL0 │ │ User Applications │ ← 사용자 모드
│ └─────────────────────────────┘
특권 낮음 ↓
전환 방식:
EL0 → EL1 : SVC (Supervisor Call) 명령어
EL1 → EL2 : HVC (Hypervisor Call) 명령어
EL2 → EL3 : SMC (Secure Monitor Call) 명령어
상위 → 하위 : ERET (Exception Return) 명령어
ARM64 부팅 과정 (Boot Process)
ARM64의 부팅 과정은 x86과 상당히 다릅니다. 대부분의 ARM64 시스템은 Device Tree를 사용하여 하드웨어 구성 정보를 커널에 전달합니다. 부팅 프로토콜은 커널 이미지의 시작점에 명시된 규약을 따릅니다.
ARM64 부팅 시퀀스
═══════════════════════════════════════════════════════════
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ BootROM │──→│ BL1/BL2 │──→│ U-Boot / │──→│ Linux │
│ (SoC) │ │ (TF-A) │ │ UEFI │ │ Kernel │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │
EL3에서 시작 EL2로 전환 EL2/EL1에서 실행
보안 초기화 DTB 주소 전달 head.S → start_kernel()
커널 로드 MMU 활성화
── 상세 ──
1. BootROM: SoC 내장 ROM에서 실행, 첫 부트로더(BL1) 로드
2. BL1 (Trusted Firmware): EL3에서 실행, 보안 초기화
3. BL2: 추가 펌웨어 로드 (BL31=EL3 런타임, BL33=Normal World)
4. BL33 (U-Boot/UEFI): 커널 이미지와 DTB 로드
5. 커널은 x0에 DTB 물리 주소를 받아 시작
6. primary_entry() → __primary_switch() → start_kernel()
MMIO와 Device Tree
ARM 시스템에서 하드웨어 레지스터에 접근하는 기본 방식은 MMIO(Memory-Mapped I/O)입니다.
x86의 Port I/O(in/out 명령어)와 달리, ARM은 메모리 주소에 하드웨어 레지스터를 매핑하고
일반 메모리 접근 명령어(LDR/STR)로 제어합니다.
Device Tree(DT)는 하드웨어 구성을 기술하는 데이터 구조입니다. DTS(Device Tree Source) 파일로 작성하고, DTC(Device Tree Compiler)로 DTB(Device Tree Blob) 바이너리로 컴파일합니다. 커널은 부팅 시 DTB를 파싱하여 하드웨어 정보를 인식합니다.
/* 간단한 Device Tree 예시 (arch/arm64/boot/dts/example.dts) */
/dts-v1/;
/ {
model = "Example ARM64 Board";
compatible = "vendor,example-board";
#address-cells = <2>;
#size-cells = <2>;
memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x40000000>; /* 1GB @ 0x80000000 */
};
uart0: serial@9000000 {
compatible = "arm,pl011", "arm,primecell";
reg = <0x0 0x09000000 0x0 0x1000>; /* MMIO 영역 */
interrupts = <0 1 4>; /* GIC SPI #1, level */
clock-names = "uartclk", "apb_pclk";
};
gic: interrupt-controller@8000000 {
compatible = "arm,gic-v3";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x0 0x08000000 0x0 0x10000>, /* GICD */
<0x0 0x080A0000 0x0 0xF60000>; /* GICR */
};
};
arch/arm64/boot/dts/ 디렉토리에 위치하며,
제조사별로 하위 디렉토리가 구성됩니다. make dtbs 명령으로 모든 DTB 파일을 빌드할 수 있습니다.
dtc -I dtb -O dts 명령으로 DTB를 다시 DTS로 역컴파일할 수도 있습니다.
RISC-V 아키텍처 (RISC-V Architecture)
RISC-V는 UC Berkeley에서 설계한 오픈 소스 ISA(Instruction Set Architecture)로, 라이센스 비용 없이 누구나 자유롭게 구현할 수 있습니다. 리눅스 커널은 RISC-V를 공식적으로 지원하며, SiFive, StarFive 등의 칩에서 이미 리눅스가 동작합니다.
특권 모드 (Privilege Modes)
RISC-V는 3단계 특권 모드를 정의합니다. 각 모드는 CSR(Control and Status Register) 접근 권한이 다릅니다.
- Machine Mode (M-mode) - 가장 높은 특권. 하드웨어에 직접 접근 가능. SBI 구현(OpenSBI)이 여기서 동작합니다.
- Supervisor Mode (S-mode) - 리눅스 커널이 실행되는 모드. 페이지 테이블과 인터럽트를 관리합니다.
- User Mode (U-mode) - 사용자 애플리케이션 모드. 가장 낮은 특권.
RISC-V 특권 모드 계층
═══════════════════════════════════════════════
특권 높음 ↑
│ ┌───────────────────────────────┐
M-mode │ │ Machine Mode (OpenSBI/BBL) │
(최고특권) │ │ - 하드웨어 직접 접근 │
│ │ - 타이머, IPI 관리 │
│ │ - SBI 콜 처리 │
│ └───────────────────────────────┘
│ ↕ ecall
│ ┌───────────────────────────────┐
S-mode │ │ Supervisor Mode (Linux) │
(커널) │ │ - 페이지 테이블 관리 (satp) │
│ │ - 인터럽트/예외 처리 │
│ │ - 커널 코드 실행 │
│ └───────────────────────────────┘
│ ↕ ecall
│ ┌───────────────────────────────┐
U-mode │ │ User Mode (Applications) │
(사용자) │ │ - 일반 애플리케이션 │
│ │ - 시스템 콜로 S-mode 진입 │
│ └───────────────────────────────┘
특권 낮음 ↓
SBI (Supervisor Binary Interface)
SBI는 S-mode(커널)와 M-mode(펌웨어) 사이의 표준 인터페이스입니다. 리눅스 커널은 SBI 호출을 통해 타이머 설정, 프로세서 간 인터럽트(IPI) 전송, 콘솔 출력 등의 기계 수준 작업을 수행합니다. OpenSBI가 가장 널리 사용되는 SBI 구현체입니다.
/* arch/riscv/include/asm/sbi.h - SBI 호출 인터페이스 (간략화) */
/* SBI Extension IDs */
#define SBI_EXT_TIME 0x54494D45 /* "TIME" */
#define SBI_EXT_IPI 0x735049 /* "sPI" */
#define SBI_EXT_RFENCE 0x52464E43 /* "RFNC" */
#define SBI_EXT_HSM 0x48534D /* "HSM" */
/* SBI 호출 수행 (ecall 명령어 사용) */
struct sbiret {
long error;
long value;
};
static inline struct sbiret sbi_ecall(int ext, int fid,
unsigned long arg0, unsigned long arg1,
unsigned long arg2, unsigned long arg3)
{
struct sbiret ret;
register unsigned long a0 asm("a0") = arg0;
register unsigned long a1 asm("a1") = arg1;
register unsigned long a6 asm("a6") = fid;
register unsigned long a7 asm("a7") = ext;
asm volatile("ecall"
: "+r"(a0), "+r"(a1)
: "r"(a6), "r"(a7)
: "memory");
ret.error = a0;
ret.value = a1;
return ret;
}
RISC-V 부팅 과정 (Boot Process)
RISC-V의 부팅 과정은 ARM64와 유사하게 여러 펌웨어 단계를 거칩니다. 현재 가장 일반적인 조합은 ZSBL → OpenSBI → U-Boot → Linux입니다.
RISC-V 부팅 시퀀스
═══════════════════════════════════════════════════════════
┌───────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ ZSBL │──→│ OpenSBI │──→│ U-Boot │──→│ Linux │
│ (BootROM) │ │ (M-mode) │ │ (S-mode) │ │ Kernel │
└───────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │
하드웨어 초기화 SBI 인터페이스 DTB 전달 head.S 진입
M-mode 실행 타이머/IPI 설정 커널 로드 setup_vm()
S-mode로 전환 initramfs 로드 start_kernel()
── RISC-V 커널 엔트리 흐름 ──
1. OpenSBI가 a0=hartid, a1=dtb_addr을 설정하고 커널로 점프
2. _start (arch/riscv/kernel/head.S) 진입
3. setup_vm() - 초기 페이지 테이블 설정
4. relocate_enable_mmu() - MMU 활성화
5. start_kernel() → C 코드 시작
특권 레벨 비교 (Privilege Level Comparison)
세 아키텍처의 특권 레벨 구조를 비교하면 설계 철학의 차이를 명확히 알 수 있습니다. 다음 다이어그램은 x86_64, ARM64, RISC-V의 특권 레벨을 나란히 비교합니다:
위 다이어그램에서 주목할 점은 리눅스 커널이 각 아키텍처에서 다른 특권 레벨에서 동작한다는 것입니다:
- x86_64: Ring 0에서 동작. Ring -1(VMX)은 하이퍼바이저 전용.
- ARM64: EL1에서 동작. EL2는 하이퍼바이저, EL3는 보안 모니터.
- RISC-V: S-mode에서 동작. M-mode는 SBI 펌웨어가 담당.
CONFIG_ARM64_VHE=y가 활성화되면,
리눅스 커널이 EL2에서 직접 실행될 수 있습니다. 이를 통해 KVM 호스트 커널이 EL2에서 동작하고,
게스트 OS가 EL1에서 실행되어 가상화 전환 오버헤드가 크게 줄어듭니다.
start_kernel() - 커널 초기화 진입점 (Kernel Init Entry)
모든 아키텍처에서 아키텍처별 초기화가 완료되면 start_kernel() 함수가 호출됩니다.
이 함수는 init/main.c에 정의되어 있으며, 커널의 공통 초기화를 수행하는 핵심 함수입니다.
/* init/main.c - start_kernel() 함수 (핵심 흐름 간략화) */
asmlinkage __visible void __init start_kernel(void)
{
/* 아키텍처 의존적 초기화 (이전 단계에서 일부 수행) */
setup_arch(&command_line); /* 아키텍처별 설정 */
/* 부팅 초기 메모리 할당자 (memblock) */
setup_per_cpu_areas(); /* Per-CPU 영역 설정 */
/* 핵심 서브시스템 초기화 */
trap_init(); /* 예외/인터럽트 벡터 설정 */
mm_core_init(); /* 메모리 관리 초기화 */
sched_init(); /* 스케줄러 초기화 */
init_IRQ(); /* 인터럽트 컨트롤러 설정 */
time_init(); /* 타이머 초기화 */
console_init(); /* 콘솔 초기화 */
/* VFS 및 기타 서브시스템 */
vfs_caches_init(); /* VFS 캐시 초기화 */
signals_init(); /* 시그널 초기화 */
proc_root_init(); /* procfs 초기화 */
/* 나머지 초기화 - kernel_init 스레드 생성 */
arch_call_rest_init(); /* rest_init() → kernel_init() */
/*
* rest_init()에서:
* 1. kernel_init 커널 스레드 생성 (PID 1의 전신)
* 2. kthreadd 커널 스레드 생성 (PID 2)
* 3. 현재 스레드는 idle 스레드(PID 0)가 됨
*
* kernel_init()에서:
* 1. initcall 실행 (드라이버 초기화 등)
* 2. /sbin/init 또는 /init 실행 → PID 1 프로세스
*/
}
start_kernel()은 인터럽트가 비활성화된 상태에서 실행됩니다.
이 함수가 실행되는 동안에는 단일 CPU만 활성화되어 있으며, 나머지 CPU(Secondary CPU)는
smp_init()이 호출될 때까지 대기 상태입니다.
커널 소스 트리 구조 (Kernel Source Tree Structure)
리눅스 커널 소스 코드는 기능별로 잘 정리된 디렉토리 구조를 가지고 있습니다. 커널 개발을 시작할 때 이 구조를 이해하는 것이 매우 중요합니다.
linux/ ← 커널 소스 루트
├── arch/ ← 아키텍처 의존적 코드
│ ├── x86/ ← x86/x86_64 아키텍처
│ │ ├── boot/ ← 부트 코드 (bzImage 생성)
│ │ ├── kernel/ ← 프로세스, 시스템 콜, SMP
│ │ ├── mm/ ← x86 페이지 테이블, TLB
│ │ ├── include/ ← x86 전용 헤더
│ │ └── entry/ ← 시스템 콜/인터럽트 진입점
│ ├── arm64/ ← ARM64 (AArch64)
│ │ ├── boot/dts/ ← Device Tree 소스
│ │ ├── kernel/ ← ARM64 커널 코드
│ │ └── mm/ ← ARM64 메모리 관리
│ └── riscv/ ← RISC-V
│ ├── kernel/ ← RISC-V 커널 코드
│ └── mm/ ← RISC-V 메모리 관리
│
├── kernel/ ← 핵심 커널 코드 (아키텍처 독립)
│ ├── sched/ ← 스케줄러 (CFS, RT, DL, EEVDF)
│ ├── locking/ ← 잠금 프리미티브
│ ├── irq/ ← 인터럽트 프레임워크
│ ├── time/ ← 타이머, clocksource
│ ├── bpf/ ← eBPF 서브시스템
│ └── rcu/ ← RCU (Read-Copy-Update)
│
├── mm/ ← 메모리 관리 (아키텍처 독립)
│ ├── page_alloc.c ← Buddy allocator
│ ├── slub.c ← SLUB 할당자
│ ├── vmalloc.c ← vmalloc 영역 관리
│ ├── mmap.c ← 메모리 매핑
│ └── oom_kill.c ← OOM Killer
│
├── fs/ ← 파일시스템
│ ├── ext4/ ← ext4 파일시스템
│ ├── btrfs/ ← Btrfs 파일시스템
│ ├── proc/ ← procfs (/proc)
│ ├── sysfs/ ← sysfs (/sys)
│ └── namei.c ← 경로 탐색 (pathname lookup)
│
├── drivers/ ← 디바이스 드라이버 (가장 큰 디렉토리)
│ ├── char/ ← 문자 디바이스
│ ├── block/ ← 블록 디바이스
│ ├── net/ ← 네트워크 디바이스 드라이버
│ ├── gpu/ ← GPU 드라이버 (DRM)
│ ├── pci/ ← PCI 버스 드라이버
│ ├── usb/ ← USB 드라이버
│ └── of/ ← Open Firmware / Device Tree
│
├── net/ ← 네트워킹 스택
│ ├── core/ ← 소켓, sk_buff 등 핵심
│ ├── ipv4/ ← IPv4 프로토콜
│ ├── ipv6/ ← IPv6 프로토콜
│ ├── netfilter/ ← Netfilter (iptables/nftables)
│ └── xdp/ ← XDP (eXpress Data Path)
│
├── include/ ← 커널 공통 헤더 파일
│ ├── linux/ ← 핵심 커널 API 헤더
│ ├── asm-generic/ ← 아키텍처 공통 ASM 헤더
│ └── uapi/ ← 사용자 공간 API 헤더
│
├── init/ ← 커널 초기화 (start_kernel() 등)
│ └── main.c ← start_kernel() 정의
│
├── ipc/ ← IPC (공유 메모리, 세마포어, 메시지 큐)
├── security/ ← 보안 모듈 (SELinux, AppArmor)
├── crypto/ ← 암호화 API
├── lib/ ← 커널 내부 라이브러리 함수
├── scripts/ ← 빌드 스크립트, 도구
├── tools/ ← 사용자 공간 도구 (perf 등)
├── Documentation/ ← 커널 문서
├── Kconfig ← 최상위 설정 파일
└── Makefile ← 최상위 빌드 파일
drivers/ 디렉토리가 전체 소스의 약 60% 이상을 차지합니다.
커널의 핵심 로직은 kernel/, mm/, fs/, net/에 집중되어 있으며,
이 디렉토리들의 코드를 이해하면 커널의 핵심 동작 원리를 파악할 수 있습니다.
소스 코드 탐색에는 Bootlin Elixir Cross-referencer가
매우 유용합니다.
arch/ 디렉토리 상세 (Architecture Directory Detail)
arch/ 디렉토리 아래의 각 아키텍처 디렉토리는 비슷한 하위 구조를 가집니다.
이는 커널의 아키텍처 추상화 설계 원칙을 반영합니다:
arch/<arch>/kernel/- 프로세스 전환, 시스템 콜, SMP, 시그널 처리 등 핵심 아키텍처 코드arch/<arch>/mm/- 페이지 테이블 관리, TLB 관리, 캐시 관리 등 메모리 관련 코드arch/<arch>/include/asm/- 아키텍처별 헤더 파일 (인라인 어셈블리, 레지스터 정의 등)arch/<arch>/boot/- 부트 코드, 부트 이미지 생성 스크립트arch/<arch>/configs/- 기본 커널 설정 파일 (defconfig)arch/<arch>/Kconfig- 아키텍처별 Kconfig 옵션 정의
커널은 이러한 구조를 통해 아키텍처 독립적인 코드(kernel/, mm/ 등)와
아키텍처 의존적인 코드(arch/)를 깔끔하게 분리합니다. 새로운 아키텍처를 지원하려면
주로 arch/ 아래에 해당 아키텍처 디렉토리를 추가하면 됩니다.
빌드 설정 예시 (Build Configuration)
각 아키텍처의 기본 설정 파일(defconfig)로 빠르게 커널을 빌드할 수 있습니다:
# x86_64 기본 설정으로 커널 빌드
make x86_64_defconfig
make -j$(nproc)
# ARM64 크로스 컴파일
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
make defconfig
make -j$(nproc) Image dtbs
# RISC-V 크로스 컴파일
export ARCH=riscv
export CROSS_COMPILE=riscv64-linux-gnu-
make defconfig
make -j$(nproc)
# QEMU로 빌드된 커널 테스트 (x86_64)
qemu-system-x86_64 \
-kernel arch/x86/boot/bzImage \
-initrd /path/to/initramfs.cpio.gz \
-append "console=ttyS0" \
-nographic
요약 (Summary)
이 문서에서는 리눅스 커널이 지원하는 주요 세 아키텍처의 핵심 개념을 살펴보았습니다. 각 아키텍처의 특성을 다음 표로 정리합니다:
| 항목 | x86_64 | ARM64 | RISC-V |
|---|---|---|---|
| 특권 레벨 | Ring 0-3 (+ VMX) | EL0-EL3 | U/S/M mode |
| 커널 실행 레벨 | Ring 0 | EL1 (VHE: EL2) | S-mode |
| 시스템 콜 방식 | SYSCALL/SYSRET | SVC 명령어 | ECALL 명령어 |
| 부팅 펌웨어 | BIOS/UEFI | BootROM + TF-A | ZSBL + OpenSBI |
| HW 정보 전달 | ACPI/E820 | Device Tree / ACPI | Device Tree |
| 주소 공간 | 48/57-bit VA | 48/52-bit VA | Sv39/Sv48/Sv57 |
| 페이지 크기 | 4KB (기본) | 4KB / 16KB / 64KB | 4KB (기본) |
| I/O 방식 | Port I/O + MMIO | MMIO | MMIO |
| 인터럽트 컨트롤러 | APIC (LAPIC + I/O APIC) | GIC (v2/v3/v4) | PLIC / APLIC+IMSIC |
| 가상화 | VT-x / AMD-V | EL2 + VHE | H-extension |
CPU 제조사 아키텍처 매뉴얼 (Architecture Software Developer's Manuals)
리눅스 커널 개발에서 각 CPU 제조사의 공식 아키텍처 매뉴얼은 가장 권위 있고 정확한 참조 문서입니다. 커널 코드의 아키텍처 의존 부분(arch/ 디렉토리)을 이해하거나 수정할 때 반드시 해당 매뉴얼을 참조해야 합니다.
Intel® 64 and IA-32 Architectures Software Developer's Manual (Intel SDM)
| 볼륨 | 내용 | 커널 개발 활용 |
|---|---|---|
| Vol. 1 | 기본 아키텍처 | 데이터 타입, 실행 환경, 명령어 개요, x87 FPU, SSE/AVX |
| Vol. 2 (A-Z) | 명령어 세트 레퍼런스 | 개별 명령어의 opcode, 동작, 예외 조건 — 인라인 어셈블리 작성 시 필수 |
| Vol. 3 (A-D) | 시스템 프로그래밍 가이드 | 보호 모드, 페이징, 인터럽트/예외, MSR, APIC, VT-x — 커널 개발 핵심 볼륨 |
| Vol. 4 | MSR (Model-Specific Register) | CPU 모델별 MSR 목록, 성능 카운터, 전력 관리 레지스터 |
| Optimization Manual | 최적화 레퍼런스 | 마이크로아키텍처 세부사항, 분기 예측, 캐시 동작, SIMD 최적화 가이드 |
arch/x86/ 코드를 분석할 때 Vol. 3이 가장 자주 참조됩니다.
특히 페이지 테이블 구조(Chapter 4), 인터럽트/예외 처리(Chapter 6), APIC(Chapter 10),
VT-x(Chapter 23-33)는 커널 개발자의 필독 장입니다.
Intel SDM은 5,000페이지 이상이므로 전체를 읽기보다 필요한 챕터를 색인으로 찾아 참조하는 방식이 효율적입니다.
AMD64 Architecture Programmer's Manual (AMD APM)
| 볼륨 | 내용 | 커널 개발 활용 |
|---|---|---|
| Vol. 1 | 애플리케이션 프로그래밍 | 레지스터, 데이터 타입, 명령어 개요 |
| Vol. 2 | 시스템 프로그래밍 | Long Mode, 페이징, 시스템 콜(SYSCALL/SYSRET), SMM, AMD-V(SVM) |
| Vol. 3 | 범용/SIMD 명령어 | x86-64 명령어 인코딩, SSE/AVX 상세 |
| Vol. 4 | 128/256-bit 미디어 명령어 | XOP, FMA4 등 AMD 전용 확장 |
| Vol. 5 | 64-bit 미디어/x87 명령어 | 레거시 FPU, 3DNow! 명령어 |
arch/x86/kvm/svm/ 디렉토리는
AMD APM Vol. 2의 SVM 챕터를 직접 구현한 것입니다.
또한 AMD의 SEV(Secure Encrypted Virtualization)와 SME(Secure Memory Encryption)는 AMD 고유 기능입니다.
ARM Architecture Reference Manual (ARM ARM)
| 문서 | 내용 | 커널 개발 활용 |
|---|---|---|
| ARMv8-A ARM (DDI 0487) | AArch64/AArch32 ISA 레퍼런스 | A64 명령어, 시스템 레지스터, 예외 모델, MMU, GIC 인터페이스 |
| ARMv9-A ARM | ARMv9 확장 포함 | SVE2, MTE(Memory Tagging), RME(Realm Management), CCA |
| ARM Cortex-A TRM | 코어별 Technical Reference Manual | 캐시 구조, TLB, 분기 예측기, 구현 정의(IMPLEMENTATION DEFINED) 동작 |
| GIC Architecture Spec | Generic Interrupt Controller | GICv2/v3/v4 인터럽트 분배, LPI, ITS — drivers/irqchip/irq-gic-* |
| SMMU Architecture Spec | System MMU (IOMMU) | DMA 주소 변환, 디바이스 격리 — drivers/iommu/arm/arm-smmu-* |
| AMBA/AXI/ACE Spec | 버스 프로토콜 | 캐시 일관성(coherency), 배리어 동작, DMA 전송 특성 |
RISC-V Specifications
| 문서 | 내용 | 커널 개발 활용 |
|---|---|---|
| Unprivileged ISA (Volume I) | 기본 정수 ISA + 표준 확장 | RV64I, M/A/F/D/C 확장, 원자적 명령어(AMO), 벡터(V) 확장 |
| Privileged ISA (Volume II) | 특권 아키텍처 | M/S/U 모드, CSR, 페이지 테이블(Sv39/48/57), 인터럽트/예외, H 확장(가상화) |
| SBI Specification | Supervisor Binary Interface | OpenSBI와의 인터페이스, 타이머/IPI/리모트 fence 호출 |
| PLIC Specification | Platform-Level Interrupt Controller | 외부 인터럽트 라우팅, 우선순위 — drivers/irqchip/irq-sifive-plic.c |
| AIA Specification | Advanced Interrupt Architecture | APLIC + IMSIC, MSI 기반 인터럽트 — 차세대 RISC-V 인터럽트 체계 |
arch/riscv/ 하위의 구현이 스펙의 각 챕터와 직접 대응됩니다.
특히 Privileged ISA Vol. II의 페이지 테이블과 예외 처리 챕터는 커널 개발의 핵심 참조 문서입니다.
매뉴얼 효과적 활용법
| 상황 | 참조 문서 | 참조 섹션 |
|---|---|---|
| 페이지 테이블 워크 디버깅 | Intel SDM Vol. 3 Ch. 4 / ARM ARM D5 / RISC-V Priv. Ch. 4 | 페이지 테이블 엔트리 형식, 비트 필드 의미 |
| 인터럽트 핸들러 작성 | Intel SDM Vol. 3 Ch. 6, 10 / GIC Spec / PLIC Spec | IDT 구조, APIC 프로그래밍, EOI 처리 |
| 메모리 배리어 선택 | Intel SDM Vol. 3 Ch. 8 / ARM ARM B2 / RISC-V Unpriv. Ch. 14 | 메모리 순서 모델, fence/barrier 명령어 |
| KVM 가상화 구현 | Intel SDM Vol. 3 Ch. 23-33 / AMD APM Vol. 2 / ARM ARM D1 | VMCS/VMCB 구조, VM entry/exit, EPT/NPT, EL2 |
| 전력 관리 (cpuidle/cpufreq) | Intel SDM Vol. 3 Ch. 14 / ACPI Spec / ARM DEN0024A | C-state, P-state, MWAIT, WFI |
| 보안 기능 구현 | 각 제조사 보안 가이드 | Intel CET, ARM MTE/PAC/BTI, AMD SEV/SME |
- ACPI Specification — 전원 관리, 디바이스 열거, 테이블(DSDT/SSDT) — x86과 ARM64 서버 모두 사용
- UEFI Specification — EFI stub, Boot Services, Runtime Services
- PCI Express Base Specification — PCIe 구성 공간, MSI/MSI-X, AER, SR-IOV
- IOMMU Specification — Intel VT-d Spec / AMD IOMMU Spec / ARM SMMU Spec
- Devicetree Specification — ARM/RISC-V 하드웨어 기술, 바인딩 규칙
- 각 SoC 벤더 데이터시트 — Qualcomm, Samsung, Broadcom, SiFive 등 벤더별 구현 상세
x86 CPU 실행 모드 심화 (Operating Modes)
x86 프로세서는 리셋 후 Real Mode에서 시작하여 여러 모드를 거쳐 Long Mode에 도달합니다. 리눅스 커널 부팅 과정은 이 모드 전환을 순차적으로 수행하며, 각 모드의 특성을 이해하는 것은 부트로더 코드와 커널 초기화 코드를 분석하는 데 필수적입니다.
모드 전환 흐름
Real Mode (리얼 모드)
| 특성 | 설명 |
|---|---|
| 비트 폭 | 16비트 레지스터, 20비트 주소 (세그먼트:오프셋 = 세그먼트×16 + 오프셋) |
| 주소 공간 | 1MB (0x00000 ~ 0xFFFFF), A20 게이트로 확장 가능 |
| 보호 기능 | 없음 — 모든 코드가 전체 메모리/I/O 포트 접근 가능 |
| 인터럽트 | IVT(Interrupt Vector Table) at 0x0000 (256 × 4바이트) |
| 커널 사용 | 부트로더 초기 단계, BIOS 서비스 호출, A20 활성화 |
; arch/x86/boot/header.S — 리눅스 부팅 초기 (Real Mode)
; BIOS가 이 코드를 0x7C00에 로드하여 실행
; A20 게이트 활성화 (20번째 주소 라인)
; A20이 비활성이면 1MB 이상 주소에 접근 불가
in al, 0x92 ; Fast A20 (System Port)
or al, 2
out 0x92, al
; Protected Mode로 전환 준비
lgdt [gdt_ptr] ; GDT 로드
mov eax, cr0
or eax, 1 ; PE (Protection Enable) 비트 설정
mov cr0, eax ; → Protected Mode 진입!
jmp 0x08:pm_entry ; far jump로 CS 갱신 (GDT 셀렉터 0x08)
Protected Mode (보호 모드)
| 특성 | 설명 |
|---|---|
| 비트 폭 | 32비트 레지스터, 32비트 주소 |
| 주소 공간 | 4GB (PAE 미사용 시), 세그먼테이션 + 페이징 |
| 보호 기능 | Ring 0~3, 세그먼트 기반 접근 제어, 페이지 기반 보호 |
| 핵심 자료구조 | GDT(Global Descriptor Table), IDT(Interrupt Descriptor Table), TSS(Task State Segment) |
| 커널 사용 | 32비트 리눅스 커널 (i386), 64비트 부팅 과정의 중간 단계 |
/* GDT 엔트리 구조 (Intel SDM Vol.3 Section 3.4.5) */
struct gdt_entry {
u16 limit_low; /* 세그먼트 크기 [15:0] */
u16 base_low; /* 베이스 주소 [15:0] */
u8 base_mid; /* 베이스 주소 [23:16] */
u8 access; /* P | DPL(2) | S | Type(4) */
u8 granularity; /* G | D/B | L | AVL | Limit[19:16] */
u8 base_high; /* 베이스 주소 [31:24] */
} __attribute__((packed));
/* Linux의 GDT 레이아웃 (arch/x86/include/asm/segment.h) */
/* 인덱스 0: NULL 디스크립터 (필수) */
/* __KERNEL_CS (0x10): Ring 0 코드 세그먼트, DPL=0 */
/* __KERNEL_DS (0x18): Ring 0 데이터 세그먼트, DPL=0 */
/* __USER_CS (0x23): Ring 3 코드 세그먼트, DPL=3 */
/* __USER_DS (0x2B): Ring 3 데이터 세그먼트, DPL=3 */
/* Per-CPU GDT: 각 CPU마다 별도의 TSS, TLS 엔트리 */
/* 주의: Long Mode에서는 세그먼테이션이 거의 비활성화 */
/* CS, SS만 의미 있고 나머지 세그먼트의 base/limit은 무시됨 */
/* 단, FS/GS base는 MSR(FS_BASE, GS_BASE)로 설정하여 */
/* per-CPU 데이터 및 TLS 접근에 사용 */
PAE (Physical Address Extension)
/* PAE 모드: 32비트 CPU에서 4GB 이상 물리 메모리 접근 */
/* CR4.PAE=1로 활성화 */
/* 페이지 테이블 엔트리가 32비트 → 64비트로 확장 */
/* 물리 주소: 36비트 → 최대 64GB */
/* PAE 페이지 테이블 구조:
* PDPT (4 엔트리) → PD (512 엔트리) → PT (512 엔트리) → 4KB 페이지
* CR3 → PDPT (32바이트, 4 × 8바이트 엔트리)
*/
/* PAE는 NX(No-Execute) 비트의 전제 조건 */
/* PTE bit 63 = NX: 해당 페이지의 코드 실행 금지 */
/* → 스택/힙 실행 방지 (DEP/W^X) */
/* Long Mode 전환에도 PAE 활성화 필수 (전제 조건) */
Long Mode (IA-32e Mode / 64-bit Mode)
/* Long Mode 전환 순서 (Intel SDM Vol.3 Section 9.8.5) */
/*
* 1. Protected Mode에서 시작 (PE=1)
* 2. PAE 활성화: CR4.PAE = 1
* 3. 4-level 페이지 테이블 설정: CR3 = PML4 물리 주소
* 4. Long Mode 활성화: IA32_EFER.LME = 1 (MSR 0xC0000080)
* 5. 페이징 활성화: CR0.PG = 1
* → 이 순간 Long Mode 진입 (IA32_EFER.LMA=1 자동 설정)
* 6. far jump로 64비트 코드 세그먼트로 점프 (CS.L=1)
*/
/* arch/x86/boot/compressed/head_64.S — 실제 커널 모드 전환 */
/* Long Mode 전환 코드 (간략화) */
movl $MSR_EFER, %ecx
rdmsr
btsl $_EFER_LME, %eax /* LME 비트 설정 */
wrmsr /* EFER에 기록 */
movl %cr0, %eax
orl $X86_CR0_PG, %eax /* PG 비트 설정 */
movl %eax, %cr0 /* → Long Mode 활성화! */
ljmp $__KERNEL_CS, $startup_64 /* 64-bit 코드로 점프 */
Long Mode 서브모드
| 서브모드 | CS.L | CS.D | 동작 | 용도 |
|---|---|---|---|---|
| 64-bit Mode | 1 | 0 | 64비트 주소/오퍼랜드, RIP-relative, 확장 레지스터(R8~R15) | 커널, 64비트 유저 프로세스 |
| Compatibility Mode | 0 | 1 | 32비트 주소/오퍼랜드 (Protected Mode와 동일한 명령어 인코딩) | 32비트 유저 프로세스 실행 (ia32 compat) |
arch/x86/entry/entry_64_compat.S의
entry_SYSENTER_compat와 entry_SYSCALL_compat가 이 전환을 처리합니다.
CPU 모드 전환 주의사항
- GDT/IDT 준비 — Protected/Long Mode 전환 전에 반드시 유효한 GDT를 설정. 잘못된 GDT는 Triple Fault → 리셋
- A20 게이트 — Real→Protected 전환 전 A20 활성화 필수. 비활성 시 홀수 MB 주소에 접근 불가
- Identity Mapping — 모드 전환 직후 코드가 실행되는 주소에 대해 가상=물리 매핑(identity mapping)이 있어야 함. 없으면 즉시 페이지 폴트
- PAE 선행 — Long Mode 진입에 PAE 필수. PAE 없이 LME 설정 후 PG 활성화하면 #GP
- CR3 유효성 — Long Mode에서 CR3는 PML4/PML5 테이블의 물리 주소. 잘못된 값은 즉시 크래시
- 5-level 페이징 (LA57) — Intel Ice Lake+에서 CR4.LA57=1로 PML5 활성화 시 57비트 가상 주소 (128PB). 커널
CONFIG_X86_5LEVEL필요 - UEFI 부팅 — UEFI는 이미 Protected/Long Mode로 진입한 상태에서 커널을 호출. EFI stub은 모드 전환 없이 직접 커널 초기화 진행
제어 레지스터(CR) 요약
| 레지스터 | 주요 비트 | 기능 |
|---|---|---|
| CR0 | PE, PG, WP, NE, MP, TS | 보호모드(PE), 페이징(PG), 쓰기 보호(WP), FPU 상태(TS/MP) |
| CR2 | (전체) | Page Fault 발생 시 폴트 주소 저장 |
| CR3 | PCD, PWT, PCID | 페이지 테이블 베이스(PML4/PML5), PCID로 TLB 태깅 |
| CR4 | PAE, PSE, PGE, OSFXSR, OSXSAVE, LA57, PCIDE, SMEP, SMAP, PKE | PAE, 큰 페이지(PSE), 전역 페이지(PGE), SIMD(OSFXSR), 보안(SMEP/SMAP) |
| CR8 (TPR) | [3:0] | Task Priority Register — 인터럽트 우선순위 마스킹 (Long Mode 전용) |
| EFER (MSR) | LME, LMA, SCE, NXE | Long Mode 활성화(LME/LMA), SYSCALL(SCE), NX 비트(NXE) |