커널 아키텍처 (Kernel Architecture)
리눅스 커널 아키텍처 심층 분석. x86_64, ARM64, RISC-V, MIPS 아키텍처별 부팅 과정(Boot Process), 주소 공간(Address Space), 특권 레벨, 커널 소스 트리 구조를 다룹니다.
일상 비유 ② 특권 레벨(Privilege Level) 관점: 커널 아키텍처는 층별 출입 카드 시스템이 있는 건물과 같습니다. 일반 직원, 즉 사용자 애플리케이션(Application)은 자기 층(User Space)만 접근할 수 있고, 건물 관리 시스템(커널)만 전체 층(Kernel Space)과 기계실(하드웨어)에 접근할 수 있습니다. x86_64·ARM64·RISC-V·MIPS는 각각 이 출입 권한 체계를 다른 방식으로 구현한 것입니다.
핵심 요약
- 특권 레벨 — 커널(Ring 0 / EL1 / S-mode / KSU Kernel)과 사용자 앱(Ring 3 / EL0 / U-mode / KSU User)의 권한 경계를 이해합니다. 이 경계가 OS 보안과 안정성의 뼈대입니다.
- 주소 공간 분리 — 가상 주소(Virtual Address) 공간이 커널 영역과 사용자 영역으로 나뉘는 구조를 파악합니다. 같은 가상 주소라도 페이지 테이블이 다르면 전혀 다른 물리 주소를 가리킵니다.
- ISA 설계 철학 차이 — x86_64(CISC(복잡명령세트)·강한 메모리 순서), ARM64(RISC(감소명령세트)·약한 메모리 순서·TrustZone), RISC-V(오픈 모듈형 ISA), MIPS(소프트웨어 관리 TLB·세그먼트 주소 공간)의 핵심 차이를 구분합니다.
- 부팅 단계 분리 — 펌웨어(Firmware), 부트로더(Bootloader), 커널 초기화 경계를 구분합니다.
- 하드웨어 기술 정보 — ACPI(Advanced Configuration and Power Interface)/DT(Device Tree) 등 기술 정보가 어디서 소비되는지 확인합니다.
- 신뢰 체인(Chain of Trust) — Secure Boot 등 검증 체인을 흐름으로 이해합니다.
- 실패 지점 식별 — 부팅 로그에서 단계별 실패 단서를 빠르게 찾습니다.
단계별 이해
- 커널/사용자 공간 경계 파악
코드가 어느 공간에서 실행되는지, 특권 레벨 전환(시스템 콜·인터럽트)이 언제 발생하는지 이해합니다. - 대상 아키텍처의 특권 모델 확인
x86_64(Ring), ARM64(EL), RISC-V(M/S/U-mode), MIPS(KSU) 중 어느 것인지 파악하고, 커널이 어느 레벨에서 실행되는지 확인합니다. - 부팅 단계 식별
현재 이슈가 펌웨어·부트로더·커널 초기화 중 어느 단계에서 발생하는지 먼저 고정합니다. - 전환 경계 검증
단계 간 인자 전달과 상태 인계(부팅 파라미터, 페이지 테이블 활성화 시점 등)를 추적합니다. - 플랫폼별 재검증
메모리 순서 모델, IOMMU, 인터럽트 컨트롤러 등 플랫폼 의존 요소가 다른 하드웨어 조건에서도 올바르게 동작하는지 확인합니다.
x86_64, ARM64, RISC-V, MIPS 아키텍처별 커널 구조, 부팅 과정, 주소 공간 레이아웃을 상세히 다룹니다.
왜 커널 아키텍처를 알아야 할까요? 같은 C 코드라도 x86_64에서 문제없이 동작하던 코드가 ARM64에서 데이터 경쟁 버그로 이어지거나, RISC-V에서는 존재하지 않는 확장 명령어에 의존해 빌드 오류가 발생할 수 있습니다. 커널 드라이버·서브시스템 코드를 이식하거나, 부팅 실패를 디버깅하거나, 특권 레벨 경계를 넘는 보안 취약점(Vulnerability)을 분석할 때 아키텍처 지식이 없으면 근본 원인에 도달하기 어렵습니다. 이 문서는 네 아키텍처 각각의 특권 모델·메모리 주소 공간·부팅 경로·레지스터 체계를 커널 코드와 연결하여 설명합니다.
리눅스 커널 개요 (Linux Kernel Overview)
리눅스 커널은 1991년 Linus Torvalds가 처음 공개한 이래, 현재 세계에서 가장 널리 사용되는 운영체제 커널입니다. 서버, 데스크탑, 임베디드 기기, 스마트폰(Android), 슈퍼컴퓨터에 이르기까지 거의 모든 컴퓨팅 영역에서 동작합니다. 커널은 하드웨어와 사용자 공간(user space) 사이에서 추상화 계층 역할을 하며, 다음과 같은 핵심 기능을 담당합니다:
- 프로세스(Process) 관리 (Process Management) - 프로세스 생성, 스케줄링, 종료, 시그널(Signal) 처리
- 메모리 관리 (Memory Management) - 가상 메모리(Virtual Memory), 페이지 테이블(Page Table), 물리 메모리(Physical Memory) 할당
- 파일시스템 (Filesystem) - VFS(Virtual Filesystem)를 통한 다양한 파일시스템 지원
- 디바이스 드라이버 (Device Drivers) - 하드웨어 추상화 및 제어
- 네트워킹 (Networking) - TCP/IP 스택, 소켓(Socket), 패킷(Packet) 필터링
- 보안 (Security) - LSM, SELinux, capabilities, seccomp
모놀리식 vs 마이크로커널 (Monolithic vs Microkernel)
운영체제 커널 설계에는 크게 두 가지 접근 방식이 있습니다. 모놀리식 커널(Monolithic Kernel)은 모든 핵심 서비스(프로세스 관리, 메모리 관리, 파일시스템, 드라이버 등)가 하나의 커다란 커널 이미지 안에서 동일한 주소 공간에서 실행됩니다. 반면 마이크로커널(Microkernel)은 최소한의 기능만 커널에 포함하고 나머지는 사용자 공간 서버로 분리합니다.
리눅스는 모놀리식 커널입니다. 그러나 순수한 모놀리식이 아닌, 동적으로 적재 가능한 커널 모듈(Loadable Kernel Module, LKM)을 지원하여 모듈화의 유연성을 확보합니다. 이를 "모듈형 모놀리식(Modular Monolithic)" 커널이라고도 합니다. 커널 모듈에 대한 자세한 내용은 커널 모듈 문서를 참고하세요.
x86_64 아키텍처 (x86_64 Architecture)
x86_64(또는 AMD64, Intel 64)는 데스크탑과 서버 환경에서 가장 널리 사용되는 아키텍처입니다. x86의 32비트 아키텍처를 64비트로 확장한 것으로, 리눅스 커널에서 가장 오랫동안 지원해 온 아키텍처 중 하나입니다.
부팅 과정
x86_64 시스템의 부팅 과정은 펌웨어에서 시작하여 커널이 완전히 초기화될 때까지 여러 단계를 거칩니다. 현대 시스템에서는 UEFI(Unified Extensible Firmware Interface)가 표준이지만, 레거시 BIOS(Basic Input/Output System)도 여전히 지원됩니다.
CONFIG_EFI_STUB=y 설정이 필요합니다.
UEFI에 대한 자세한 내용은 UEFI 문서, 부팅 과정 전반은
부팅 과정 문서를 참고하세요.
커널의 x86_64 엔트리 포인트는 어셈블리(Assembly)로 작성되어 있습니다. 다음은 핵심 부팅 코드의 간략화된 예시입니다:
/* 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 기준 주소 공간 레이아웃을 보여줍니다:
아키텍처별 주소 변환(Address Translation) 경로 비교
가상 주소(Virtual Address)를 물리 주소(Physical Address)로 변환하는 핵심 경로는 세 아키텍처 모두 유사하지만, 제어 레지스터(Register)와 페이지 테이블 포맷이 다릅니다. 아래 다이어그램은 세 아키텍처가 공유하는 변환 파이프라인과 아키텍처별 차이점을 한 화면에서 비교합니다.
세그먼테이션과 페이징 (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 엔트리, 최종 물리 페이지(Page) 매핑(Mapping)
링 구조 (Protection Rings)
x86_64는 4개의 특권 레벨(Ring 0 ~ Ring 3)을 제공하지만, 리눅스에서는 실제로 Ring 0(커널 모드)와 Ring 3(사용자 모드) 두 레벨만 사용합니다. Ring 1과 Ring 2는 사용되지 않으며, 가상화(Virtualization) 확장(VT-x)에서는 Ring -1(VMX root mode)이라는 개념이 추가됩니다.
각 세그먼트 디스크립터에는 DPL(Descriptor Privilege Level) 필드가, 현재 실행 컨텍스트에는 CPL(Current Privilege Level) 값이 존재하며, CPU는 접근할 때마다 CPL ≤ DPL 조건을 검사합니다. 사용자 코드(CPL=3)가 커널 데이터(DPL=0)에 접근하면 #GP(General Protection) 예외가 발생하며, 이 메커니즘이 링 경계의 하드웨어적 강제력을 제공합니다. 자세한 보안 모델은 커널 보안 문서를 참고하세요.
MSR (Model-Specific Register)
MSR은 프로세서 모델별로 정의되는 특수 레지스터로, 성능 카운터, 전원 관리, 터보 부스트, virtualization
기능 등을 제어합니다. 리눅스 커널은 rdmsr/wrmsr 명령어로 MSR에 접근합니다.
| MSR 주소 | 이름 | 용도 | 커널 활용 |
|---|---|---|---|
0x10 | TSC | Time Stamp Counter | 고정밀 타이머 |
0x1A2 | IA32_MISC_ENABLE | 여러 Misc 기능 | turbo boost, fast string |
0x1A4 | IA32_PERF_CTL | Performance Control | P-상태 제어 |
0xC0000080 | EFER | Extended Feature Enable | Long Mode, NX, SYSCALL |
0xC0000100 | STAR | SYSCALL Target | syscall CS/SS |
0xC0000101 | LSTAR | Long Mode SYSCALL | 64-bit syscall target |
0xC0000102 | CSTAR | Compat SYSCALL | 32-bit compat target |
0xC0000103 | FMASK | SYSCALL RFLAGS mask | syscall flags mask |
0xC0000104 | FS.base | FS Base Address | per-CPU data |
0xC0000105 | GS.base | GS Base Address | per-CPU kernel |
0x00000309~0x0000030B | Fixed Counter 0~2 | Fixed Performance Counter | perf_event |
/* arch/x86/include/asm/msr.h - MSR 접근 매크로 */
/* MSR 읽기 */
static inline u64 rdmsrl(unsigned int msr)
{
u64 val;
asm volatile("rdmsr" : "=A"(val) : "c"(msr));
return val;
}
/* MSR 쓰기 */
static inline void wrmsrl(unsigned int msr, u64 val)
{
asm volatile("wrmsr" : : "c"(msr), "A"(val));
}
/* 시스템 콜 진입 MSR 설정 예시 */
wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);
wrmsrl(MSR_FMASK, 0);
wrmsrl(MSR_STAR, ((u64)__KERNEL_CS << 32) | (__USER_CS << 16));
rdmsr을 시도하면 #GP 예외가 발생합니다. 커널 드라이버에서는 wrmsr()을 사용하기 전에 capable(CAP_SYS_RAWIO)로 권한을 검사해야 합니다.
x86_64 시스템 콜 Internals
x86_64 시스템 콜은 SYSCALL 명령어로 진입하며, MSR에 설정된 핸들러 주소로 점프합니다.
복귀는 SYSRET 명령어로 수행됩니다.
/* arch/x86/entry/entry_64.S - syscall 진입 흐름 (핵심 부분) */
/* 사용자 공간에서 SYSCALL */
syscall /* RAX=syscall 번호, RDI, RSI, RDX, R10, R8, R9 = 인자 */
/* RIP → MSR_LSTAR에 저장된 주소 */
entry_SYSCALL_64:
/* 레지스터 저장 (pt_regs 구조체) */
push %rax /* RAX 저장 (syscall 번호) */
cld
push %r15
push %r14
push %r13
push %r12
push %r11 /* RFLAGS 저장 */
push %r10
push %r9
push %r8
push %rcx /* RIP 저장 (복귀 주소) */
push %r11 /* RFLAGS 재저장 */
push %rdx
push %rsi
push %rdi
push %rbp
push %rax /* syscall 번호 */
/* GS 기반 per-CPU 접근 시작 */
SWAPGS /* GS → 커널 GS로 교체 */
/* 시스템 콜 테이블에서 핸들러 호출 */
mov %gs:cpu_tss.x86_tss.sp0, %rsp /* 커널 스택으로 교체 */
test $0x7, %rcx /* compat/native 판별 (반환 주소 하위 비트) */
jz 1f
swapgs
1:
and $-16, %rsp
mov %rsp, %gs:cpu_tss.x86_tss.sp1
leaq -RED_ZONE_SIZE(%rsp), %rsp
push %rsp
push %gs:cpu_tss.x86_tss.sp0
/* syscall 테이블 호출 */
mov %rax, %rsp
sub $sizeof(struct pt_regs), %rsp
mov %rax, %rsp /* pt_regs 빌드 */
mov %rax, %rsp, %rdi
call do_syscall_64
/* 복귀 */
ret
syscall_return:
SWAPGS
sysretq /*Ring 3 복귀, RIP=RCX, RFLAGS=RDX */
x86_64 인터럽트 처리
x86_64의 인터럽트(Interrupt) 처리는 IDT(Interrupt Descriptor Table)과 LAPIC(Local APIC)가 담당합니다. 예외, IRQ, NMI 모두 IDT 게이트를 통해 처리됩니다.
256개 벡터 중 0~31번은 CPU 예외(#DF, #PF 등)에 예약되어 있고, 32번 이상은 외부 인터럽트에 할당됩니다.
커널은 FIRST_EXTERNAL_VECTOR(0x20)부터 IRQ 벡터를 배정하며, LAPIC 벡터는
FIRST_SYSTEM_VECTOR(0xF0) 이후 구간에서 사용합니다. 중요한 인터럽트는
TSS의 IST(Interrupt Stack Table)를 지정해 전용 스택에서 처리되도록 하여
일반 스택 손상 시에도 안정적으로 복구할 수 있습니다.
| 게이트 유형 | 선택자 | 용도 |
|---|---|---|
| Task Gate | 0x5 | 하드웨어 태스크 전환 (거의 미사용) |
| Interrupt Gate | 0x6 | 예외/인터럽트, IF 자동 Clear |
| Trap Gate | 0x7 | 예외만 사용, IF 유지 |
/* arch/x86/include/asm/desc_defs.h - IDT 게이트 구조 */
struct gate_struct {
u16 offset_low; /* offset [15:0] */
u16 selector; /* 코드 세그먼트 셀렉터 */
u8 ist; /* Interrupt Stack Table */
u8 type_attr; /* Type (0x6=Interrupt, 0x7=Trap) | DPL | P */
u16 offset_mid; /* offset [31:16] */
u32 offset_high; /* offset [63:32] */
u32 reserved; /* 보존 */
} __attribute__((packed));
/* IRQ 벡터 할당 */
#define FIRST_EXTERNAL_VECTOR 0x20 /* IRQ0 */
#define FIRST_SYSTEM_VECTOR 0xF0 /* LAPIC 등 시스템 벡터 */
VMX (Virtualization) Internals
VMX는 Intel의 하드웨어 가상화 확장입니다. VMX root mode(Ring -1)와 VMX non-root mode(게스트)를 지원합니다.
/* arch/x86/include/asm/vmx.h - VMX 주요 MSR */
#define MSR_IA32_VMX_BASIC 0x480
#define MSR_IA32_VMX_PINCTRL 0x481
#define MSR_IA32_VMX_PROCCTRL 0x482
#define MSR_IA32_VMX_EXITCTRL 0x483
#define MSR_IA32_VMX_ENTRYCTRL 0x484
/* VMCS 필드 */
#define VMCS_GUEST_RIP 0x00001602
#define VMCS_GUEST_RSP 0x00001603
#define VMCS_GUEST_CR3 0x00001604
#define VMCS_HOST_CR3 0x00001902
#define VMCS_HOST_RSP 0x00001903
#define VMCS_HOST_RIP 0x00001904
/* VMX 실행 상태 (VMCS field: VMCS_GUEST_ACTIVITY_STATE) */
#define VMX_GUEST_STATE_ACTIVE 0
#define VMX_GUEST_STATE_HLT 1
#define VMX_GUEST_STATE_SHUTDOWN 2
#define VMX_GUEST_STATE_WAIT_SIPI 3
/* arch/x86/include/asm/segment.h - GDT 세그먼트 정의 */
/*
* x86_64 GDT 레이아웃 (간략화):
* Entry 0: NULL 디스크립터 (CPU 요구사항)
* Entry 1: Kernel 32-bit Code Segment (호환 모드용)
* Entry 2: Kernel 64-bit Code Segment
* Entry 3: Kernel Data Segment
* Entry 4: User 32-bit Code Segment (ia32 compat)
* Entry 5: User Data Segment
* Entry 6: User 64-bit Code Segment
* Entry 7+: TSS, TLS 등
*/
#define GDT_ENTRY_KERNEL32_CS 1
#define GDT_ENTRY_KERNEL_CS 2
#define GDT_ENTRY_KERNEL_DS 3
#define GDT_ENTRY_DEFAULT_USER32_CS 4
#define GDT_ENTRY_DEFAULT_USER_DS 5
#define GDT_ENTRY_DEFAULT_USER_CS 6
/* 세그먼트 셀렉터 값 (index << 3 | RPL) */
#define __KERNEL32_CS (GDT_ENTRY_KERNEL32_CS * 8) /* 0x08, Ring 0 */
#define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8) /* 0x10, Ring 0 */
#define __KERNEL_DS (GDT_ENTRY_KERNEL_DS * 8) /* 0x18, Ring 0 */
#define __USER32_CS (GDT_ENTRY_DEFAULT_USER32_CS * 8 + 3) /* 0x23, Ring 3 */
#define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS * 8 + 3) /* 0x2B, Ring 3 */
#define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS * 8 + 3) /* 0x33, Ring 3 */
SYSCALL 명령어를 사용합니다.
이 명령어는 Ring 3에서 Ring 0으로 전환하며, MSR_LSTAR 레지스터에 저장된 시스템 콜 핸들러(Handler) 주소로 점프합니다.
SYSRET으로 사용자 모드로 복귀합니다. 이전의 int 0x80 방식보다 훨씬 빠릅니다.
시스템 콜에 대한 자세한 내용은 시스템 콜 (System Call) 문서를 참고하세요.
ARM64 아키텍처 (ARM64 / AArch64 Architecture)
ARM64(AArch64)는 모바일 기기부터 서버, 슈퍼컴퓨터까지 빠르게 확산되고 있는 아키텍처입니다. Apple Silicon(M1/M2/M3/M4), AWS Graviton4, Ampere Altra/AmpereOne 등 고성능 ARM64 프로세서가 서버 시장에서도 중요한 위치를 차지하고 있습니다.
ARM64는 x86_64와 비교해 세 가지 설계 철학의 차이를 먼저 이해해야 합니다. 첫째, RISC 기반으로 명령어 수가 적고 길이가 고정(4바이트)되어 있어 파이프라인 설계가 단순합니다. 둘째, x86의 Ring 0~3 대신 Exception Level(EL0~EL3)이라는 4단계 모델을 사용하며, TrustZone(EL3)으로 보안 세계와 일반 세계를 하드웨어 수준에서 격리합니다. 셋째, 메모리 순서가 약한 모델(Weak Ordering)이어서 명시적 배리어 없이는 멀티코어 환경에서 쓰기 순서가 보장되지 않습니다. x86에서 포팅한 코드에 배리어가 빠져 있어도 x86 하드웨어의 강한 메모리 순서 덕분에 동작하던 코드가, ARM64에서는 산발적인 데이터 경쟁 버그로 나타나는 것이 가장 흔한 포팅 실수입니다.
Exception Level (EL0 ~ EL3)
ARM64는 x86의 Ring 구조 대신 Exception Level(EL)이라는 4단계 특권 모델을 사용합니다. 각 EL은 명확한 역할을 가지며, 하위 EL에서 상위 EL로의 전환은 exception 발생 시에만 가능합니다.
- EL0 (User/Application) - 사용자 애플리케이션이 실행되는 레벨. 가장 낮은 특권.
- EL1 (OS Kernel) - 운영체제 커널이 실행되는 레벨. 리눅스 커널은 여기서 동작합니다.
- EL2 (Hypervisor) - 하이퍼바이저(Hypervisor)가 실행되는 레벨. KVM이 이 레벨을 사용합니다.
- EL3 (Secure Monitor) - ARM TrustZone의 Secure Monitor. 보안 세계와 일반 세계 전환을 관리합니다.
ARM64 시스템 레지스터
ARM64는 x86의 MSR과 유사하게 MSR/MRS 명령어로 접근하는 시스템 레지스터를 사용합니다.
시스템 레지스터는SCTLR_ELx, TTBR0_EL1, TCR_EL1 등 EL별로 나뉩니다.
| 레지스터 | EL | 용도 |
|---|---|---|
SCTLR_EL1 | EL1 | System Control, MMU, 캐시 활성화 |
TTBR0_EL1 | EL1 | Translation Table Base 0 (User) |
TTBR1_EL1 | EL1 | Translation Table Base 1 (Kernel) |
TCR_EL1 | EL1 | TLB 제어, ASID 크기, 메모리 속성 |
MAIR_EL1 | EL1 | Memory Attribute Index Register |
SPsel | EL0/1 | 스택 포인터 선택 |
CurrentEL | All | 현재 Exception Level (읽기 전용) |
DAIF | EL0/1 | 인터럽트 마스크 (D/I/F) |
TPIDR_EL0 | EL0 | 사용자 스레드(Thread) ID |
TPIDR_EL1 | EL1 | 커널 TLS base |
CNTV_CTL_EL0 | EL0 | 가상 타이머 제어 |
CPACR_EL1 | EL1 | 협프로세서 액세스 |
VBAR_EL1 | EL1 | Vector Base Address |
/* ARM64 시스템 레지스터 접근 예시 */
/* MMU 활성화 (SCTLR_EL1.M = 1) */
mrs x0, sctlr_el1
orr x0, x0, #1 /* M 비트 */
msr sctlr_el1, x0
/* 페이지 테이블 베이스 설정 (TTBR0_EL1) */
msr ttbr0_el1, x0
/* 인터럽트 허용 (DAIF 클리어) */
msr daif, #0
/* 현재 EL 확인 */
mrs x0, currentel
lsr x0, x0, #2 /* 0=EL0, 1=EL1, 2=EL2, 3=EL3 */
GIC (Generic Interrupt Controller)
ARM64 시스템의 인터럽트 컨트롤러는 GIC(Generic Interrupt Controller)입니다. GICv2, GICv3, GICv4 버전이 있으며, 버전별로 프로그래밍 인터페이스가 다릅니다.
| 버전 | 주요 특징 | 커널 드라이버 |
|---|---|---|
| GICv2 | 최대 8 CPUs, legacy PPI/SPI | irq-gic.c |
| GICv3 | Redistributor, LPI, MSI 지원 (redistributor 최대 255개) | irq-gic-v3.c |
| GICv4 | 가상화 직렬화(Serialization), vPE table | irq-gic-v4.c |
| GICv4.1 | Extended LPI range, hierarchical cache | irq-gic-v4.c (동일 파일) |
/* drivers/irqchip/irq-gic-v3.c - GICv3 초기화 핵심 */
/* Redistributor베이스 주소 */
#define GICR_CTLR 0x0
#define GICR_TYPER 0x08
#define GICR_PIDR2 0xFE8
/* Interrupt ID Ranges */
#define GIC_SPI_OFFSET 32 /* SPI: 32~1019 */
#define GIC_PPI_OFFSET 16 /* PPI: 16~31 */
#define GIC_SGI_OFFSET 0 /* SGI: 0~15 */
/* GICD_IROUTERn - Interrupt Routing Register */
/* GICD_IROUTERn 경유로 각 SPI를 대상 redistributor에 라우팅 */
static int gic_set_affinity(struct irq_data *d, const cpumask *mask, bool force)
{
/* GICv3: GICD_IROUTERn(16바이트 스트라이드)로 대상 CPU 라우팅 */
void __iomem *router = gic_dist_base() + GICD_IROUTER_BASE +
(irq(d) * GICD_IROUTER_STRIDE);
u32 target = cpu_logical_map(cpumask_first(mask));
writel_relaxed(target, router); /* Affinity 0-3 필드 */
return IRQ_SET_MASK_OK;
}
ARM64 KVM 가상화
ARM64에서 KVM은 하이퍼바이저로 동작하며, EL2에서 실행됩니다. CONFIG_KVM, CONFIG_ARM64_VHE 등의 옵션이 있습니다.
- VHE (Virtualization Host Extensions): 커널이 EL2에서 직접 실행되어 가상화 오버헤드를 크게 줄임
- 비VHE: 분리된 커널/VMM 필요 (전통적 구성)
/* arch/arm64/kvm/arm.c - KVM 초기화 */
static int kvm_init(void)
{
int ret;
/* Hyp 시스템 레지스터 초기화 */
kvm_linux_set_id_aa64pfr0(0);
kvm_linux_set_id_aa64pfr1(0);
kvm_linux_set_id_aa64zfr0(0);
/* 2단계 페이지 테이블 활성화 */
if (has_vhe()) {
kvm_init_el2_stuff();
}
return 0;
}
/* VM 생성 시 2단계 페이지 테이블 설정 */
static int kvm_init_stage2(struct kvm *kvm)
{
phys_addr_t pgd = __get_free_pages(GFP_KERNEL, VTCR_SIZE_BITS);
/* VTCR_EL2: Stage-2 변환 제어 필드(SL0, IPS 등) 설정 */
u64 vtcr = kvm_vtcr_el2_calc(mmu);
write_sysreg(vtcr, vtcr_el2);
/* VTTBR_EL2: Stage-2 페이지 테이블 베이스 주소 기록 */
write_sysreg(pgd, vttbr_el2);
return 0;
}
ARM64 보안 확장 (PAC, BTI, MTE, GCS)
ARM64는 다양한 하드웨어 기반 보안 확장을 제공합니다.
| 확장 | 설명 | 커널 지원 |
|---|---|---|
| PAC (Pointer Authentication) | 포인터 상위 비트에 인증 코드를 추가해 위변조·부패 탐지 | CONFIG_ARM64_POINTER_AUTH |
| BTI (Branch Target Identification) | 간접 분기 타깃의 무단 도용 방지(랜딩 패드) | CONFIG_ARM64_BTI |
| MTE (Memory Tagging Extension) | 메모리 태그로 버퍼 오버플로(Buffer Overflow) 감지 | CONFIG_ARM64_MTE |
| GCS (Guarded Control Stack) | ROP 공격 방어 | CONFIG_ARM64_GCS |
| CFI (Control-Flow Integrity) | BTI를 활용하는 컴파일러 레벨 제어 흐름 검증 (독립 HW 확장이 아님) | BTI + 컴파일러 옵션 |
/* PAC (Pointer Authentication) 예시 */
/* PACIA 키 생성 (A 키 사용) */
pacia x0, x1, x2 /* x0 = x0 | PAC(x1, x2) */
/* PACIB (B 키 사용) */
pacib x0, x1, x2
/* AUTIA 복호화 검증, 실패 시 BRK */
autia x0, x1, x2
/* BTI (Branch Target Identification) */
/* BTI 테스트 */
bti c /* call-friendly 랜딩 패드 */
bti j /* jump-friendly 랜딩 패드 */
bti cj /* call/jump 모두 허용 */
/* MTE (Memory Tagging) */
irg x0, x1 /* x1을 x0에 복사하고 상위 바이트에 랜덤 태그 생성 */
stg x0, [x1] /* Store tag with data */
ldg x2, [x1] /* Load tag to x2 */
cfinv x0 /* Compare and invalidate if mismatch */
ARM64 부팅 과정
ARM64의 부팅 과정은 x86과 상당히 다릅니다. 대부분의 ARM64 시스템은 Device Tree(DT)를 사용하여 하드웨어 구성 정보를 커널에 전달합니다. 부팅 프로토콜은 커널 이미지의 시작점에 명시된 규약을 따릅니다.
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 33 4>; /* GIC SPI INTID 33, level-triggered */
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로 역컴파일할 수도 있습니다.
Device Tree에 대한 자세한 내용은 Device Tree 문서를 참고하세요.
ARM64 메모리 순서 모델 (Memory Ordering Model)
ARM64는 x86_64와 달리 약한 메모리 순서 모델 (Weak Ordering)을 채택합니다. CPU는 메모리 읽기/쓰기 연산의 실행 순서를 하드웨어 레벨에서 보장하지 않으며, 컴파일러도 메모리 접근의 순서를 변경할 수 있습니다. 이 특성이 x86에서만 개발한 코드를 ARM64에 포팅할 때 산발적인 데이터 경쟁 버그(Data Race)로 나타나는 근본 원인입니다.
ARM64의 메모리 재순서화(Reordering)는 세 가지 차원에서 발생할 수 있습니다:
- 컴파일러 재순서화 — 컴파일러 최적화 과정에서 C 코드의 메모리 접근 순서가 변경됨
- CPU 하드웨어 재순서화 — ARM64 CPU가 동시에 여러 메모리 요청을 발행할 때, 완료 순서가 코드 순서와 다를 수 있음
- 시스템/커널 재순서화 — DMA, IOMMU, 캐시 코어리티(Coherency) 등 시스템 레벨에서 메모리 접근이 재배치(Relocation)될 수 있음
이러한 재순서화를 방지하기 위해 ARM64는 메모리 배리어(Memory Barrier) 명령어를 제공합니다. 배리어는 종류에 따라 범위가 다르며, 각 배리어는 다른 상황에서 사용됩니다:
| 명령어 | 종류 | 의미 | 커널 매크로(Macro) |
|---|---|---|---|
DMB | Data Memory Barrier | 데이터 메모리 접근 순서 보장(Ordering) | smp_wmb(), smp_rmb(), smp_mb() |
DSB | Data Synchronization Barrier | 이전 모든 메모리 접근 완료 후 다음 명령 실행 | mb() (완전 배리어) |
ISB | Instruction Synchronization Barrier | 파이프라인 플러시 후 다음 명령 가져옴 | SCTLR_EL1.C/I 등 시스템 레지스터 수정 후 사용 |
/* ARM64 메모리 배리어 명령어 예시 */
/* DMB ISH — Inner Shareable Domain Data Memory Barrier */
dmb ish /* 공유 메모리 접근 순서 보장 */
/* DMB SY — Full System Barrier (가장 강함) */
dmb sy /* 모든 메모리 접근 순서 보장 */
/* DSB — Data Synchronization Barrier */
dsb sy /* DMB는 접근 순서만, DSB는 이전 모든 접근 완료 보장 */
/* ISB — Instruction Synchronization Barrier */
isb /* 파이프라인 플러시, 명령어 변경 후 사용 */
커널 코드에서 가장 많이 사용되는 배리어는 smp_mb()/smp_rmb()/smp_wmb()입니다.
각각 dmb ish/dmb ishld/dmb ishst로 확장되어 Inner Shareable 도메인 내
메모리 접근 순서를 보장하며, 완전한 시스템 동기화가 필요한 곳에는 dsb sy 계열을 사용합니다.
arch/arm64/include/asm/barrier.h에서 정의됩니다:
/* arch/arm64/include/asm/barrier.h (핵심 발췌) */
/* 기본 배리어 원시 명령어 래퍼 */
#define isb() asm volatile("isb" : : : "memory")
#define dmb(opt) asm volatile("dmb " #opt : : : "memory")
#define dsb(opt) asm volatile("dsb " #opt : : : "memory")
/* 전체 배리어 (컴파일러 + 하드웨어) */
#define __mb() dsb(sy)
#define __rmb() dsb(ld)
#define __wmb() dsb(st)
/* SMP 배리어 — Inner Shareable 도메인 내 접근 순서만 보장 */
#define __smp_mb() dmb(ish)
#define __smp_rmb() dmb(ishld)
#define __smp_wmb() dmb(ishst)
x86_64는 TSO(Totally Store Order) 모델이라 smp_mb()는 하드웨어 펜스 없이
컴파일러 배리어만으로 충분하지만, mb()는 여전히 mfence 명령어를 발행합니다.
반면 ARM64에서는 이 모든 매크로가 실제 배리어 명령어로 확장됩니다.
따라서 x86-only 관습으로 배리어를 생략한 코드는 ARM64에서
데이터 경쟁(Data Race)으로 연결될 수 있습니다. 반드시 아키텍처 독립 배리어 매크로를 사용해야 합니다:
atomic_*(), READ_ONCE(),
WRITE_ONCE() 매크로를 사용하여 컴파일러 재순서화도 함께 방지합니다.
Linux 커널의 include/linux/compiler.h와 include/linux/atomic.h를 참고하세요.
/* include/linux/compiler.h — READ_ONCE / WRITE_ONCE */
/* 컴파일러 재순서화 방지: 단일 로드/스토어를 강제 (단순화된 표현) */
/* 실제 정의는 include/linux/compiler.h — asm_volatile 기반 컴파일러 배리어 + volatile 캐스트 */
#define READ_ONCE(x) \
({ typeof(x) _v_; asm_volatile("" : : "m"(RAW_MEMORY(x))); (_v_ = (x)); _v_; })
#define WRITE_ONCE(x, v) \
({ asm_volatile("" : "m"(RAW_MEMORY(x))); ((x) = (v)); });
#define WRITE_ONCE(x, v) \
__atomic_store_n(&(x), (v), __ATOMIC_SEQ_CST)
/* 예: flags 변수의 safe access */
static void safe_flag_clear(volatile unsigned long *flags)
{
WRITE_ONCE(*flags, 0);
smp_wmb(); /* flags 변경이 다른 메모리 쓰기보다 먼저 완료됨 */
}
static bool safe_flag_test(volatile unsigned long *flags)
{
bool result = READ_ONCE(*flags);
smp_rmb(); /* flags 읽기가 이후 메모리 읽기보다 먼저 완료됨 */
return result;
}
ARM64 메모리 순서 모델은 또한 LoadLoad, LoadStore, StoreLoad, StoreStore 재순서화의 네 가지 조합을 배리어 종류별로 제어할 수 있습니다. DMB ISH는 네 타입 모두를 방지하고, DMB ISHLD는 로드 관련(LoadLoad/LoadStore) 순서를, DMB ISHST는 스토어 관련 (StoreStore/StoreLoad) 순서를 보장합니다. 각 상황에 맞는 배리어를 선택하면 성능 최적화가 가능합니다.
MTE 커널 지원 (Memory Tagging Extension for Kernel)
ARMv8.5-A부터 도입된 MTE(Memory Tagging Extension)는 16바이트 그라눌(Granule) 단위로 4비트 태그를 메모리 메타데이터에 저장하여, 버퍼 오버플로우, use-after-free, heap corrosion 등의 메모리 안전성 오류를 하드웨어 레벨에서 감지하는 기능입니다. Linux 커널 5.16부터 사용자 공간 MTE를, 6.6부터 하드웨어 태그 기반 KASAN(KERNEL MTE)을 지원하기 시작했습니다.
| Kconfig | 종류 | 버전 | 설명 |
|---|---|---|---|
CONFIG_ARM64_MTE | User MTE | 5.16+ | 사용자 공간 MTE (MTE-capable ABI, prctl(PR_SET_TAGGED_ADDR_CTRL)) |
CONFIG_KASAN_HW_TAGS | Kernel MTE | 6.6+ | 하드웨어 태그 기반 KASAN (ARM64_MTE 필요, 커널 힙 태그 검증) |
Kernel MTE가 활성화되면, 커널 힙 할당자(SLAB/SLUB allocator)는 각 객체(Object)에
의사난수 태그를 할당하고, 객체 접근 시 하드웨어가 태그를 검증합니다.
태그 불일치 시 Tag Check Fault가 발생하며, 이를 통해 버퍼 오버플로,
use-after-free, heap corruption 등의 메모리 안전성 위반을 사전에 감지할 수 있습니다.
MTE는 KASAN(Kernel Address Sanitizer)과 상호 보완적인 진단 수단입니다:
KASAN이 더 큰 오버헤드를 가지지만 더 넓은 범위(스택, 전역 변수 포함)를 커버하는 반면,
MTE는 하드웨어 기반이므로 런타임 오버헤드가 훨씬 작지만 힙 객체만 커버합니다.
/* arch/arm64/mm/fault.c — MTE Tag Check Fault 처리 (실제 소스 발췌) */
/* EC 0x15(Tag Check Fault)의 ESR_ELx.ESS 필드([14:12]) 인코딩 */
#define ESR_ESC_OOOVRT 0b000 /* Out-of-range virtual address */
#define ESR_ESC_OOSRLD 0b001 /* Out-of-shareability load */
#define ESR_ESC_GRANULE 0b010 /* Granule size mismatch */
#define ESR_ESC_MISCON 0b011 /* Misconfigured */
/* 사용자 공간 동기 태그 체크 폴트 핸들러 */
static int do_tag_check_fault(unsigned long far, unsigned long esr,
struct pt_regs *regs)
{
/* 태그 체크 폴트는 FAR_EL1 비트 63:60이 UNKNOWN일 수 있음 */
if (!cpus_have_cap(ARM64_MTE_FAR))
far = (__untagged_addr(far) & ~MTE_TAG_MASK) | (far & MTE_TAG_MASK);
do_bad_area(far, esr, regs);
return 0;
}
/* 폴트 디스패치 테이블 등록 (EC/FSC → 핸들러 매핑) */
static const struct fault_info fault_info[] = {
...
{ do_tag_check_fault, SIGSEGV, SEGV_MTESERR, "synchronous tag check fault" },
};
/* EL1 동기 태그 폴트: do_mem_abort() 경유 → TCF 비활성화 후 복구 */
if (is_el1_mte_sync_tag_check_fault(esr)) {
do_tag_recovery(addr, esr, regs);
return;
}
/* ARM64 MTE 관련 명령어 */
/* IRG — Issue Random Tag: x1을 x0에 복사하고 상위 바이트에 랜덤 태그 생성 */
irg x0, x1
/* GMI — Generate and Move Immediate: immediate 값을 태그로 설정 */
gmi x0, #5
/* MSTT — Move and Set Tag: x0의 태그를 [x1] 메모리 태그에 복사 */
mstt x0, [x1]
/* LDGT — Load Granule Tag: [x1]의 128바이트 블록 태그를 x0에 로드 */
ldgt x0, [x1]
/* STGT — Store Granule Tag: x2의 태그를 [x1]의 128바이트 블록에 저장 */
stgt x2, [x1]
/* MTE 태그 검사 활성화: SCTLR_EL1.TCF 필드(bit [1:0]) 설정 */
mrs x0, sctlr_el1
orr x0, x0, #0x3 /* TCF=ASYMM 또는 SYNC 등 모드 선택 */
msr sctlr_el1, x0
isb /* 시스템 레지스터 갱신 동기화 */
/* CFINV — Compare and Invalidate: x0의 태그를 무효화(0으로 설정) */
cfinv x0
MTE를 커널 컴파일에 적용하려면 다음 두 가지 방법이 있습니다:
- Build-time MTE:
CONFIG_KASAN_HW_TAGS=y를 설정하여 커널 이미지를 하드웨어 태그 기반 KASAN으로 빌드. 커널 실행 시mte=on파라미터로 활성화. - Runtime MTE: 커널 부트 파라미터
mte=on|koff|ukon으로 태그 검사(Tag Checking)와 태그 생성(Tag Allocation)을 독립적으로 제어할 수 있습니다.
# MTE 커널 빌드 설정
# .config에 MTE 활성화
make menuconfig
# → Kernel hacking → [*] KASAN Hardware Tags Support
# 또는 커맨드 라인에서 설정
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
CONFIG_ARM64_MTE=y CONFIG_KASAN_HW_TAGS=y
# MTE 활성화 부트 파라미터 추가 (GRUB 또는 Device Tree)
# /etc/default/grub — GRUB_CMDLINE_LINUX_DEFAULT 에 추가
mte=on # 태그 검사+생성 모두 활성화 (기본)
mte=koff # 커널 MTE 비활성화
mte=ukon # 사용자 MTE만 활성화
# MTE 지원 여부 확인 (cpuinfo 기능 문자열)
grep -w mte /proc/cpuinfo
MTE 관련 기능은 릴리스마다 점진적으로 개선되고 있으며, KASAN HW 태그 연동, Hugepage 태그 관리, 태그 저장 오버헤드 절감 등이 최근 릴리스에서 지속적으로 반영되었습니다(구체 항목은 릴리스 노트 참조).
MTE 커널 사용 시 Tag Check Fault가 발생하면 커널은 해당 faults의 종류에 따라
다음 중 하나를 수행합니다:
- Abort (기본) — 커널 패닉 발생. 심각한 메모리 안전성 위반 시 사용.
- Signal — 사용자 공간 MTE fault 시 SIGSEGV 또는 SIGBUS 시그널을 프로세스에 전송.
- Ignore — debug 모드에서 태그 mismatch를 로그만 기록하고 정상으로 계속.
MTE의 태그는 16바이트(Granule) 단위로 할당되며, 4비트 태그이므로 2^4 = 16개의 서로 다른 태그값을 가질 수 있습니다. 커널 MTE는 SLUB 할당자(SLAB allocator)와 연동되어 각 객체에 대해 의사난수 기반의 고유 태그값을 할당합니다.
RISC-V 아키텍처 (RISC-V Architecture)
RISC-V는 UC Berkeley에서 설계한 오픈 소스 ISA(Instruction Set Architecture)로, 라이선스 비용 없이 누구나 자유롭게 구현할 수 있습니다. 리눅스 커널은 RISC-V를 공식적으로 지원하며, SiFive, StarFive, SpacemiT K1 등의 칩에서 이미 리눅스가 동작합니다.
특권 모드 (Privilege Modes)
RISC-V는 3단계 특권 모드를 정의합니다. 각 모드는 CSR(Control and Status Register) 접근 권한이 다릅니다.
- Machine Mode (M-mode) - 가장 높은 특권. 하드웨어에 직접 접근 가능. SBI 구현(OpenSBI)이 여기서 동작합니다.
- Supervisor Mode (S-mode) - 리눅스 커널이 실행되는 모드. 페이지 테이블과 인터럽트(Interrupt)를 관리합니다.
- User Mode (U-mode) - 사용자 애플리케이션 모드. 가장 낮은 특권.
RISC-V CSR (Control and Status Register)
RISC-V는 특권 모드별로 다른 CSR을 사용합니다. M-mode, S-mode, U-mode 각각 접근 가능한 CSR이 정의됩니다.
| CSR | 모드 | 설명 |
|---|---|---|
mstatus | M | Machine Status, MIE, MPIE 등 |
mie | M | Machine Interrupt Enable |
mtvec | M | Machine Trap Vector Base |
mepc | M | Machine Exception PC |
mcause | M | Machine Cause (예외 번호) |
mtval | M | Machine Trap Value (주소 등) |
sstatus | S | Supervisor Status |
sie | S | Supervisor Interrupt Enable |
stvec | S | Supervisor Trap Vector |
sepc | S | Supervisor Exception PC |
scause | S | Supervisor Cause |
stval | S | Supervisor Trap Value |
satp | S | Supervisor Address Translation and Protection |
scounteren | S | Supervisor Counter Enable |
모드 간 전환은 ecall(하위 모드에서 상위 모드로 진입), mret/sret(상위 모드에서 하위로 복귀) 명령어로 이루어지며,
예외·인터럽트의 위임 대상 모드는 medeleg/mideleg CSR로 제어됩니다.
/* RISC-V CSR 접근 예시 */
/* sstatus 읽기 */
csrr t0, sstatus
/* sstatus의 SIE 비트(Set Interrupt Enable) */
csrsi sstatus, 0x2
/* sstatus의 SIE 비트 클리어 */
csrci sstatus, 0x2
/* medeleg 설정: U-mode 예외를 S-mode로 위임 */
csrw medeleg, t0
/* satp (주소 변환 테이블) 설정 - Sv48의 경우 */
/* PPN[43:0] = 페이지 테이블 물리 프레임 번호 */
csrw satp, t0
RISC-V 페이지 테이블 (Sv39/Sv48/Sv57)
RISC-V Privileged Architecture 사양은 Sv39, Sv48, Sv57 세 가지 표준 가상 메모리 스킴을 정식 정의합니다. 리눅스는 Sv48 (4-level)을 주로 사용합니다.
- Sv39: 3-level, 512GB 주소 공간 (페이지 크기 1GB, 2MB, 4KB)
- Sv48: 4-level, 256TB 주소 공간, 가장 일반적
- Sv57: 5-level, 128PB 주소 공간 (v5.18부터 메인라인 지원)
/* arch/riscv/include/asm/pgtable.h - 페이지 테이블 형식 */
#define PTE_SHIFT 10 /* PTE Bits 수 */
#define PTE_PFN_SHIFT 10 /* PFN 시작 비트 */
#define PTE_VALID _(1 << 0)
#define PTE_READ _(1 << 1) /* Read enable */
#define PTE_WRITE _(1 << 2) /* Write */
#define PTE_EXEC _(1 << 3) /* Execute */
#define PTE_USER _(1 << 4) /* User accessible */
#define PTE_GLOBAL _(1 << 5) /* Global */
#define PTE_ACCESS _(1 << 6) /* Accessed */
#define PTE_DIRTY _(1 << 7) /* Dirty (Svadu 확장) */
/* Sv48 4-level 페이지 테이블 */
/* VA를 9비트 인덱스 4개로 분할 → L3→L2→L1→L0 테이블 → 4KB 페이지 */
CLINT와 PLIC (인터럽트 컨트롤러)
RISC-V의 인터럽트는 두 가지 유형으로 나뉩니다:
- CLINT (Core Local Interrupt): 소프트웨어 인터럽트(SWINT), 타이머 인터럽트, IPI (코어당 1세트)
- PLIC (Platform-Level Interrupt Controller): 외부 디바이스 IRQ, 전역
/* drivers/irqchip/irq-sifive-plic.c - PLIC */
/* PLIC 메모리 맵 오프셋 (drivers/irqchip/irq-sifive-plic.c 기준) */
#define PLIC_PRIORITY_BASE 0x000000 /* source priority: source_id × 4 */
#define PLIC_PENDING_BASE 0x001000
#define PLIC_CONTEXT_ENABLE_BASE 0x002000 /* + context × 0x80 */
#define PLIC_CONTEXT_BASE 0x200000 /* + context × 0x1000 */
#define PLIC_CLAIM_OFFSET 0x0 /* 컨텍스트 윈도우 내 claim */
#define PLIC_THRESHOLD_OFFSET 0x4 /* 컨텍스트 윈도우 내 threshold */
/* IRQ 소스 enable 토글 예시 */
static void plic_toggle(struct irq_data *d, unsigned enable)
{
u32 mask = 1UL << (irq(d) % 32);
void __iomem *reg = plic_regs + PLIC_CONTEXT_ENABLE_BASE +
(context * 0x80) + (irq(d) / 32) * 4;
if (enable)
writel(readl(reg) | mask, reg);
else
writel(readl(reg) & ~mask, reg);
}
/* IRQ 클레임 (CLAIM) - 인터럽트 핸들링 */
static void plic_handle_irq(void)
{
u32 irq;
while ((irq = readl(plic_regs + PLIC_CLAIM_OFFSET))) {
generic_handle_domain_irq(irq_domain, irq);
writel(irq, plic_regs + PLIC_CLAIM_OFFSET);
}
}
SBI 확장
SBI(Supervisor Binary Interface)는 커널이 M-mode 펌웨어(OpenSBI 등)에 서비스를 요청하는 표준 ABI입니다. 주요 표준 확장 목록:
| 확장 ID | 이름 | 기능 |
|---|---|---|
0x10 | base | Base 확장 (spec 버전·구현 정보 조회) |
0x54494D45 | time | 타이머 설정 (gettimeofday) |
0x735049 | ipi | Inter-Processor Interrupt |
0x52464E43 | rfence | Remote fence (TLB flush) |
0x48534D | hsm | Hart State Management (부팅/종료) |
0x505343 | psc | Power State Control (시스템 suspend/shutdown) |
0x444D4E | dm | Debug Channel (디버그) |
0x53525354 | srst | System Reset (리셋/파워오프) |
SBI (Supervisor Binary Interface)
SBI는 S-mode(커널)와 M-mode(펌웨어) 사이의 표준 인터페이스입니다. 리눅스 커널은 SBI 호출을 통해 타이머(Timer) 설정, 프로세서 간 인터럽트(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 부팅 과정
RISC-V의 부팅 과정은 ARM64와 유사하게 여러 펌웨어 단계를 거칩니다. 현재 가장 일반적인 조합은 BootROM(FSBL) → OpenSBI → U-Boot → Linux입니다.
MIPS 아키텍처 (MIPS Architecture)
MIPS(Microprocessor without Interlocked Pipeline Stages)는 1981년 스탠포드 대학에서 시작된
초기 RISC(Reduced Instruction Set Computing) 설계로, x86_64 기계어(Machine Code)가 아닌 정수 파이프라인 중심의 단순한
명령어 세트를 표방했습니다. Sony PlayStation 2, Nintendo 64, SGI 워크스테이션, Cisco 라우터,
OpenWRT 기반 공유기 임베디드 SoC(Ingenic, MediaTek, Realtek 등)에서 널리 사용되었으며,
리눅스 커널은 arch/mips에서 대부분의 MIPS 구현을 지원합니다.
오늘날 MIPS는 신규 사용자보다는 기존 임베디드 기기와 레거시 소프트웨어 유지보수 중심으로
커널 트리에 남아 있습니다. RISC-V가 라이선스 제약이 없으면서 MIPS와 유사한 RISC 설계 철학을 계승하면서,
신규 설계는 RISC-V로 이동하는 추세입니다. 커널 소스에서 MIPS는 별도의 arch/mips 트리로 유지되며
x86_64·ARM64·RISC-V와 달리 소프트웨어 관리 TLB와 세그먼트 기반 주소 공간이라는
독특한 구조를 가집니다.
개요 (Overview)
MIPS는 설계 초기부터 빅엔디안(Big-Endian)을 기본으로 채택했으며, 일부 구현은 리틀엔디안
(MIPSel)도 지원합니다. 커널은 엔디안(Endianness)에 따라 CPU_LITTLE_ENDIAN과 CPU_BIG_ENDIAN
Kconfig 옵션으로 나뉘며, 배포판 크로스 툴체인 접두사도 mipsel-linux-gnu-처럼 달라집니다.
커널이 지원하는 MIPS 계열은 다음과 같습니다:
- MIPS I ~ MIPS V — 클래식 32/64비트 ISA (R2000/R4000/R10000 등)
- MIPS32 / MIPS64 Release 1~6 — 현대 MIPS 표준 (Release 6는 지연 슬롯 제거·컴팩트 분기 도입)
- microMIPS — 코드 밀도 최적화 명령어 세트
- nanoMIPS — 최신 임베디드용 16/32비트 혼합 명령어 세트
- MT(Multi-Threading), DSP ASE, MSA(MIPS SIMD), EVA, VZ — 아키텍처 확장
CONFIG_CPU_MIPS32_R6 / CONFIG_CPU_MIPS64_R6로 빌드되며,
기존 R2 이전 코드와 바이너리 호환이 되지 않습니다. 상세는
R6 제거/추가 명령어를 참고하세요.
특권 모드 (Privilege Modes)
MIPS는 CP0(Coprocessor 0)의 Status 레지스터 KSU 필드로 세 가지 특권 모드를 표현합니다.
그러나 리눅스 커널은 슈퍼바이저 모드를 사용하지 않고, 커널 모드(Kernel Mode)와 유저 모드(User Mode)
두 가지만 사용합니다. 예외 발생 시 하드웨어가 EXL(Exception Level) 비트를 세워 KSU와 무관하게
자동으로 커널 모드로 전환합니다.
- 커널 모드 (KSU=00) — 모든 CP0 접근, 모든 메모리 접근, 전체 명령어 실행 가능. 리눅스 커널이 여기서 실행됩니다.
- 슈퍼바이저 모드 (KSU=01) — 커널 매핑 영역 접근 가능. 리눅스 MIPS는 이를 사용하지 않습니다.
- 유저 모드 (KSU=10) — kuseg(사용자 가상 주소)와 TLB 매핑 영역만 접근 가능. 사용자 공간이 여기서 실행됩니다.
CP0 Status 레지스터 ($12)
Status 레지스터는 특권 모드·인터럽트·예외 상태를 한꺼번에 관리합니다.
리눅스 인터럽트 비활성화 루틴(local_irq_disable/arch_local_irq_disable)은
이 레지스터의 IE 비트를 mfc0/mtc0로 직접 조작합니다.
핵심 비트는 다음과 같습니다:
| 비트 | 이름 | 설명 |
|---|---|---|
| 0 | IE | 전역 인터럽트 활성화 (리눅스 local_irq_enable/disable) |
| 1 | EXL | 예외 레벨. 설정 시 자동으로 커널 모드 진입 |
| 2 | ERL | 에러 레벨 (Reset/NMI/Cache Error 처리 중) |
| 4:3 | KSU[1:0] | 특권 모드 선택 (00=커널, 01=슈퍼바이저, 10=유저) |
| 15:8 | IM[7:0] | 개별 인터럽트 마스크 (외부 6개 + 소프트웨어 2개) |
| 22 | BEV | 부트 예외 벡터 선택 (1=0xBFC00000, 0=0x80000000 기준) |
| 28 | CU0 | Coprocessor 0 접근 허용 (커널 모드에서 1로 유지) |
부팅 초기 head.S의 setup_c0_status 매크로는 Status를
특정 비트 조합으로 깨끗이 초기화합니다:
/* arch/mips/kernel/head.S — setup_c0_status 매크로 (개념적 요약) */
or $t0, $t0, ST0_KERNEL_CUMASK /* CU0 등 커널 필수 비트 세트 */
or $t0, $t0, 0x1f /* BEV 등 초기 부팅 비트 */
xor $t0, $t0, 0x1f /* 0x1f 마스크 클리어 (IE/EXL/ERL 제거) */
mtc0 $t0, CP0_STATUS /* Status 반영 */
sll $zero, 3 /* EHb — 파이프라인 해저드 제거 */
CP0 레지스터 (Coprocessor 0)
CP0는 x86의 CR/MSR, ARM64의 시스템 레지스터(System Register)에 해당하는 머신 제어 레지스터 집합입니다.
커널은 mfc0/mtc0 명령어로 접근하며, 예외·TLB·타이머·인터럽트 제어가 모두 CP0를 통해
이루어집니다. 주요 레지스터는 다음과 같습니다:
| 번호 | 이름 | 설명 |
|---|---|---|
| 0 | Index | TLB 인덱스 (TLBWI/TLBP 결과) |
| 1 | Random | TLBWR용 랜덤 인덱스 |
| 2-3 | EntryLo0/1 | TLB 엔트리 하위 비트 (짝수/홀수 페이지 쌍) |
| 4 | Context | TLB 미스 시 PTE 주소 계산 (PTEBase + BadVPN2) |
| 5 | PageMask | TLB 페이지 크기 마스크 |
| 6 | Wired | TLBWR에서 보호할 고정 엔트리 수 |
| 8 | BadVAddr | 가장 최근 주소 오류의 가상 주소 |
| 9 | Count | 프로그램 가능 카운터 (타이머 기준) |
| 10 | EntryHi | TLB 엔트리 상위 비트 (VPN2, ASID) |
| 11 | Compare | Count 비교값 (일치 시 타이머 인터럽트) |
| 12 | Status | 프로세서 상태 (IE, EXL, ERL, KSU, IM 등) |
| 13 | Cause | 예외 원인 (ExcCode, IP, BD 등) |
| 14 | EPC | 예외 발생 PC (복귀 주소) |
| 15 | PRId | 프로세서 ID (구현자/버전) |
| 15.1 | EBase | 예외 벡터 베이스 주소 (R2+, 리눅스가 코어별로 설정) |
| 16 | Config | Cache, TLB, 엔디안(Endianness) 등 하드웨어 구성 |
| 30 | ErrorEPC | 에러 레벨 예외의 PC |
cevt-r4k 드라이버(drivers/clocksource/mips-gic-timer.c 및
arch/mips/kernel/cevt-r4k.c)로 주기적 타이머 인터럽트를 구현합니다.
랜덤 TLB 인덱스는 리눅스 보안 하드닝에서 커널 매핑을 흩뿌리는(Randomization) 용도로도 이용됩니다.
주소 공간 (Address Space)
32비트 MIPS의 주소 공간은 세그먼트 기반입니다. 최상위 비트가 세그먼트를 결정하며, kseg0/kseg1은 TLB를 거치지 않고 물리 메모리에 직접 매핑되는 것이 핵심 특징입니다. 이 때문에 커널은 초기 부팅 단계에서 페이지 테이블(TLB 엔트리) 설정 없이 코드를 실행할 수 있습니다.
PAGE_OFFSET = 0x80000000(kseg0 베이스)로 커널 가상 공간이 시작됩니다.
32비트 유저 공간 끝은 TASK_SIZE = 0x7fff0000 정도이며, o32 ABI가 32비트 커널과 64비트 커널
모두에서 이 레이아웃을 사용합니다. 64비트(XKPHYS, XKSEG 등) 레이아웃은
가상 주소 공간 문서에서 상세히 다룹니다.
TLB (Software-Managed TLB)
MIPS의 가장 독특한 점은 TLB(Translation Lookaside Buffer)를 하드웨어가 아니라 커널이 관리한다는 것입니다.
x86_64/ARM64/RISC-V는 페이지 워크(Page Table Walk)를 하드웨어가 수행하지만, MIPS는 TLB 미스 시 CPU가
TLB Refill 예외를 발생시키고 커널이 직접 TLB 엔트리를 채워 넣습니다. 이를 위해
커널은 빌드 시점에 arch/mips/mm/tlbex.c가 생성한 최적화된 핸들러 코드를 사용합니다.
- 쌍 페이지 매핑 — 하나의 TLB 엔트리가
EntryLo0(짝수 페이지)와EntryLo1(홀수 페이지) 두 페이지를 동시에 매핑합니다. - ASID(Address Space Identifier) —
EntryHi.ASID(8비트)로 프로세스별 주소 공간을 구분, 문맥 전환(Context Switch) 시 TLB를 비우지 않고 재사용합니다. - PageMask — 엔트리별 페이지 크기(4KB~최대 수 MB)를 개별 지정합니다.
- Wired — 하위 N개 엔트리를
TLBWR(랜덤 교체) 대상에서 제외하여 커널 고정 매핑을 보호합니다. - Context — 미스 시 PTE 주소를
PTEBase + BadVPN2로 즉시 계산해 핸들러 지연을 최소화합니다.
/* arch/mips/mm/tlbex.c가 빌드 시 생성하는 TLB Refill 핸들러 (개념적 코드) */
mfc0 k1, C0_CONTEXT /* Context → PTE 주소 */
lw k0, 0(k1) /* 짝수 페이지 PTE → EntryLo0 */
lw k1, 4(k1) /* 홀수 페이지 PTE → EntryLo1 */
mtc0 k0, C0_ENTRYLO0 /* EntryLo0 설정 */
mtc0 k1, C0_ENTRYLO1 /* EntryLo1 설정 */
ehb /* 파이프라인 해저드 배리어 */
tlbwr /* Random 인덱스에 TLB 기록 */
eret /* 예외 복귀 (EPC로 이동) */
TLBWR/TLBWI 순서와 ehb 배치가 중요합니다.
잘못될 경우 커널이 TLB 셧다운(Shutdown) 상태에 빠질 수 있습니다.
예외 벡터 (Exception Vectors)
MIPS는 예외 종류에 따라 고정된 예외 벡터(Exception Vector) 주소로 분기합니다.
Status.BEV가 0이면 kseg0(캐시) 기반, 1이면 0xBFC00000(비캐시 ROM) 기반으로
이동합니다. 부팅 초기 BEV=1에서 커널이 초기화 후 BEV=0으로 전환합니다.
| 벡터 주소 (BEV=0) | 예외 | 설명 |
|---|---|---|
0x80000000 | TLB Refill | 32비트 주소의 TLB 미스 (TLBL/TLBS, EXL=0일 때) |
0x80000080 | XTLB Refill | 64비트 주소의 TLB 미스 (MIPS64) |
0x80000100 | Cache Error | 캐시 패리티/ECC 오류 (ERL=1 상태) |
0x80000180 | General Exception | SYSCALL, TLB Modify, Address Error, 인터럽트 등 모든 일반 예외 |
0x80000200 | Interrupt (IV=1) | 직렬 인터럽트 전용 벡터 (선택적) |
0xBFC00000 | Reset / NMI (BEV=1) | 리셋/하드웨어 오류 — 비캐시 ROM 영역 |
General Exception 벡터에 도달하면 커널은 Cause.ExcCode 필드로 핸들러를 선택합니다.
arch/mips/kernel/genex.S의 except_vec3_generic이 이 디스패치를 수행합니다:
| ExcCode | 의미 | 커널 핸들러 |
|---|---|---|
| 0 | Interrupt (인터럽트) | handle_int |
| 1 | Mod (TLB 수정 시도) | handle_tlbm |
| 2 | TLBL (TLB 로드 미스) | handle_tlbl |
| 3 | TLBS (TLB 저장 미스) | handle_tlbs |
| 4 | AdEL (주소 오류 — 로드) | handle_adel |
| 5 | AdES (주소 오류 — 저장) | handle_ades |
| 8 | Sys (SYSCALL) | handle_sys |
| 9 | Bp (BREAK) | handle_bp |
| 10 | RI (예약 명령어) | handle_ri |
| 11 | CpU (코프로세서 불가) | handle_cpu |
| 13 | Ov (오버플로) | handle_ov |
/* arch/mips/kernel/genex.S — except_vec3_generic (General 벡터 디스패치) */
mfc0 k1, C0_CAUSE /* Cause 레지스터 읽기 */
andi k1, k1, 0x7c /* ExcCode 필드 (비트 6:2) 추출 */
la k0, exception_handlers
addu k0, k0, k1 /* ExcCode × 4 = 테이블 오프셋 */
lw k0, 0(k0) /* 핸들러 주소 로드 */
jr k0 /* 핸들러로 점프 */
nop /* 지연 슬롯 */
부팅 과정 (Boot Process)
MIPS 부팅은 펌웨어(Bootloader)가 커널 엔트리로 직접 분기하는 단순한 구조입니다.
대표적인 펌웨어로 YAMON(Board 템플릿 보드), PMON, U-Boot,
CFE가 있으며, 커널은 kernel_entry()에서 시작됩니다.
/* arch/mips/kernel/head.S — 커널 진입점 kernel_entry() (개념적 요약) */
FEXPORT(kernel_entry)
setup_c0_status /* Status 초기화 (BEV=1 유지, EXL/ERL 클리어) */
sll $zero, 3 /* EHb — 파이프라인 해저드 제거 */
#ifdef CONFIG_RELOCATABLE
jal relocate_kernel /* 실제 적재 주소로 자기 재배치 */
jr.hb v0 /* 재배치된 커널로 점프 (hazard barrier) */
#endif
j start_kernel /* C 언어 초기화 진입 (arch-independent) */
- 펌웨어 인자: U-Boot/YAMON은
a0=인자 개수(argc),a1=인자 배열(argv),a2=환경(envp),a3=추가 정보를 전달하며, 커널은 이를fw_arg0~fw_arg3으로 저장해fw_init_cmdline등에서 사용합니다. - BMIPS DTB 규약: Broadcom(BMIPS)에서는
a0=0, a1=0xffffffff, a2=Device Tree 물리 주소로 DTB를 전달합니다. 장치는 물리 512MB 이내, 64비트 정렬이어야 합니다. (Documentation arch/mips/booting.rst) - CONFIG_RELOCATABLE: 커널이 실제 적재 주소(Load Address)와 컴파일 주소가 다를 때
relocate_kernel이 자기 코드를 옮긴 뒤jr.hb로 재점프합니다. - SMP: 멀티코어 시스템에서 부팅 CPU만
kernel_entry를 수행하고, 나머지 코어는 펌웨어/IPI 메일박스로 보조 부팅 루틴(smp_bootstrap)에 진입합니다.
메모리 순서 모델 (Memory Ordering Model)
MIPS는 전통적으로 비순차적(Weakly Ordered) 메모리 모델을 채택했습니다. 명령어 실행은 순서대로 보이지만, 메모리 접근은 구현에 따라 재배열될 수 있습니다. 원자성은 LL/SC(Load Linked / Store Conditional) 쌍으로 보장하며, 순서 보장은 SYNC 명령어로 수행합니다.
| SYNC stype | 의미 |
|---|---|
0x00 | 완료(Completion) 배리어 — 앞선 모든 load/store가 완료됨을 보장 |
0x10 | 경량(Lightweight) 순서 배리어 — 로컬 외부 순서만 보장 (R2+, 리눅스가 주요 SMP 장벽으로 사용) |
/* arch/mips/include/asm/barrier.h에서 메모리 배리어 매크로 구성 */
/* smp_mb()는 SYNC stype 0, smp_rmb/wmb는 stype 0x10 계열 */
__sync:
sync 0 /* 완전 순서 배리어 */
sync 0x10 /* 경량 순서 배리어 (R2+) */
sync로 acquire/release를 명시해야 하며,
Classic MIPS(v5 이전)에서는 SC 성공 후에도 LLCbit가 일치하지 않을 수 있어 주의가 필요합니다.
리눅스 arch_spin_lock은 LL/SC 루프 뒤 sync 0을 배치합니다.
가상화 (Virtualization)
MIPS의 하드웨어 가상화는 VZ(Virtualization Module) 확장이 표준입니다.
커널은 CONFIG_KVM_MIPS_VZ로 KVM 게스트를 지원합니다. 과거에는
Trap & Emulate(KVM_TE) 방식도 있었지만, 2021년(v5.13 이후 병합 창)에
유지보수 축소로 커널에서 제거되었습니다. 따라서 현대 리눅스 MIPS KVM은
VZ 확장이 있는 하드웨어(옥테온 등)에서만 동작합니다.
Guest Entry/EPC 메커니즘을 제공합니다.
상세 레지스터 구성은 VZ (Virtualization Extension)를 참고하세요.
유지보수 현황 (2025-2026)
| 구분 | 내용 |
|---|---|
| 커널 상태 | 신규 기능 개발보다 유지보수·버그 수정 중심. 주요 배포판의 공식 MIPS 포트는 없습니다. |
| R6 지원 | CONFIG_CPU_MIPS32_R6/CONFIG_CPU_MIPS64_R6로 메인라인 유지 |
| Loongson | Loongson 계열은 2023년 이후 MIPS 호환 모드에서 자체 LoongArch ISA로 주력 이동 |
| 실사용 | OpenWRT 호환 공유기, Cisco/임베디드 네트워크 장비, Ingenic/Lexra SoC 등 레거시 장비 |
| 최근 변경 | v6.13 멀티클러스터 인터럽트 컨트롤러, v6.14 PCI 레거시 주소 변환, v6.18 TLB 유일화 스택 버그 수정 등 (상세) |
arch/mips는 LTS 기반으로 계속 유지되며,
버그 수정·툴체인 호환성이 유지되고 있습니다.
특권 레벨 비교 (Privilege Level Comparison)
운영체제가 여러 프로그램을 동시에 실행할 때, 한 프로그램이 다른 프로그램의 메모리를 덮어쓰거나 하드웨어를 직접 제어하면 시스템 전체가 무너집니다. 이를 막기 위해 CPU는 실행 코드가 "얼마나 많은 권한을 갖느냐"를 하드웨어 수준에서 구분합니다 — 이것이 특권 레벨(Privilege Level)입니다.
특권 레벨이 낮은 코드(사용자 애플리케이션)는 직접 하드웨어에 접근하거나 다른 프로세스의 메모리를 읽으려 하면 CPU가 즉시 예외(Exception)를 발생시켜 커널에 제어권을 넘깁니다. 커널은 특권 레벨이 가장 높은 곳에서 실행되며, 이 경계가 OS의 보안과 안정성의 근본입니다. 세 아키텍처는 이 개념을 각기 다른 이름과 계층 수로 구현합니다:
위 다이어그램에서 주목할 점은 리눅스 커널이 각 아키텍처에서 다른 특권 레벨에서 동작하는 것입니다:
- 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에서 실행되어 가상화 전환 오버헤드가 크게 줄어듭니다.
시스템 콜/예외 진입 경로 비교
사용자 공간에서 커널 공간으로 전환되는 공통 경로는 시스템 콜, 인터럽트, 예외입니다. 아키텍처별 진입 명령과 복귀 명령은 다르지만, 커널이 트랩 프레임을 저장하고 핸들러를 호출한 뒤 사용자 공간으로 복귀하는 큰 흐름은 동일합니다.
- 시스템 콜 진입 명령: x86_64는
SYSCALL, ARM64는SVC, RISC-V는ecall을 사용합니다. - 트랩 벡터: x86_64(IDT), ARM64(VBAR_EL1), RISC-V(stvec)가 첫 진입 지점을 결정합니다.
- 복귀 명령: x86_64(
SYSRET/IRETQ), ARM64(ERET), RISC-V(sret)로 복귀합니다.
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()이 호출될 때까지 대기 상태입니다.
인터럽트에 대한 자세한 내용은 인터럽트 (Interrupt) 문서를,
스케줄러(Scheduler) 초기화는 프로세스 스케줄러 문서를 참고하세요.
커널 소스 트리 구조 (Kernel Source Tree Structure)
리눅스 커널 소스 코드는 기능별로 잘 정리된 디렉토리 구조를 가지고 있습니다. 커널 개발을 시작할 때 이 구조를 이해하는 것이 매우 중요합니다.
drivers/ 디렉터리는 트리 내 최대 규모의 디렉터리로, 하드웨어 지원 코드의 대부분이 여기에 위치합니다.
커널의 핵심 로직은 kernel/, mm/, fs/, net/에 집중되어 있으며,
이 디렉토리들의 코드를 이해하면 커널의 핵심 동작 원리를 파악할 수 있습니다.
소스 코드 탐색에는 Bootlin Elixir Cross-referencer가
매우 유용합니다.
arch/ 디렉토리 상세 (Architecture Directory Detail)
arch/ 디렉토리 아래의 각 아키텍처 디렉토리는 비슷한 하위 구조를 가집니다.
이는 커널의 아키텍처 추상화 설계 원칙을 반영합니다:
arch/<arch>/kernel/- 프로세스 전환, 시스템 콜, SMP, 시그널 처리 등 핵심 아키텍처 코드arch/<arch>/mm/- 페이지 테이블 관리, TLB 관리, 캐시(Cache) 관리 등 메모리 관련 코드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 | MIPS |
|---|---|---|---|---|
| 특권 레벨 | Ring 0-3 (+ VMX) | EL0-EL3 | U/S/M mode | KSU (Kernel/Sup/User) |
| 커널 실행 레벨 | Ring 0 | EL1 (VHE: EL2) | S-mode | KSU=00 (Kernel) |
| 시스템 콜 방식 | SYSCALL/SYSRET | SVC 명령어 | ECALL 명령어 | SYSCALL 명령어 |
| 부팅 펌웨어 | BIOS/UEFI | BootROM + TF-A | BootROM + OpenSBI | YAMON/PMON/U-Boot |
| HW 정보 전달 | ACPI/E820 | Device Tree / ACPI | Device Tree | Device Tree (BMIPS) |
| 주소 공간 | 48/57-bit VA | 48/52-bit VA | Sv39/Sv48/Sv57 | 32-bit kseg0~3 / 64-bit XKPHYS |
| 페이지 크기 | 4KB (기본) | 4KB / 16KB / 64KB | 4KB (기본) | 4KB (기본, PageMask로 4KB~16MB) |
| I/O 방식 | Port I/O + MMIO | MMIO | MMIO | MMIO |
| 인터럽트 컨트롤러(Interrupt Controller) | APIC (LAPIC + I/O APIC) | GIC (v2/v3/v4) | PLIC / APLIC+IMSIC | CP0 Count/Compare + 외부 핀(IP/HW) |
| 가상화 | VT-x / AMD-V | EL2 + VHE | H-extension | VZ (KVM_TE는 제거됨) |
| 하드웨어 보안 | CET Shadow Stack, SMEP/SMAP | MTE (v5.16), PAC/BTI, GCS (userspace v6.13) | PMP (펌웨어 구성), Svade/Svadu (v6.13) | EVA, VZ 게스트 격리 |
아키텍처별 성능 최적화 (Architecture-Specific Performance Optimization)
각 아키텍처는 고유한 성능 특성과 최적화 포인트를 가지고 있습니다. 커널 개발자는 타겟 아키텍처의 특성을 이해하고 이에 맞는 최적화를 적용해야 합니다.
x86_64 최적화 포인트
x86_64는 강력한 OoOE(Out-of-Order Execution, 비순서 실행)와 넓은 SIMD(Single Instruction Multiple Data) 레지스터(AVX-512, AVX2, SSE)를
갖추고 있지만, 그 복잡성만큼 잘못된 사용이 성능을 오히려 깎는 경우도 많습니다.
대표적인 함정은 캐시 라인 경계를 무시한 데이터 배치(거짓 공유(False Sharing))와,
likely()/unlikely() 없이 작성된 분기 코드입니다.
아래 기법들은 "기본값을 지키면 손해 없다"는 관점에서 핵심만 정리한 것입니다.
캐시 아키텍처와 False Sharing 방지
x86_64 CPU는 일반적으로 L1(32KB~64KB), L2(256KB~1MB), L3(Shared, 여러 MB~수십 MB)의 3단 캐시 계층 구조를 갖습니다. 캐시 라인(Cache Line) 기본 크기는 64바이트이며, 메모리 접근은 이 64바이트 단위(그레인, Granule)로 L1/L2/L3 캐시에 로드됩니다.
False Sharing은 두 개 이상의 CPU 코어가 서로 다른 변수를 접근하는데, 그 변수들이 같은 64바이트 캐시 라인 내에 배치될 때 발생하는 성능 저하입니다. 한 코어가 변수 A를 수정하면 MESIF/MOSI 일관성 프로토콜에 의해 다른 코어의 해당 캐시 라인이 무효화(Invalid)되어, 다른 코어가 변수 B에 접근할 때도 캐시 미스가 발생하게 됩니다.
/* ❌ 잘못된 예: False Sharing 발생 가능 */
/* hot과 cold가 같은 캐시 라인에 배치될 수 있음 */
struct bad_example {
atomic_t hot_counter; /* 모든 코어에서 자주 접근 */
spinlock_t hot_lock; /* 모든 코어에서 자주 경쟁 */
char cold_name[64];/* 가끔만 접근하는 이름 */
struct list_head cold_list;/* 드물게 접근하는 연결 리스트 */
};
/* ✅ 올바른 예: hot/cold 데이터 분리 + 캐시 라인 정렬 */
struct hot_data {
atomic_t counter;
spinlock_t lock;
} __cacheline_aligned; /* 64바이트 경계 정렬 */
struct cold_data {
char name[64];
struct list_head list;
} __cacheline_aligned;
/* Per-CPU 변수 사용: 각 코어마다 전용 복사본 */
static struct hot_data percpu_hot
__attribute__((percpu));
/* Per-CPU 변수 접근 (예: network packet counter) */
static void inc_packet_count(void)
{
atomic_inc(&this_cpu_ptr(&percpu_hot)->counter);
}
커널 내부적으로 __cacheline_aligned는 아키텍처 특정 패딩을 추가하여
캐시 라인 크기에 맞춤합니다. x86_64에서는 64바이트, ARM64에서는 128바이트(일부 새 CPU)에
정렬됩니다.
/* include/linux/cache.h — 캐시 라인 정렬 매크로 */
/* L1 Cacheline-aligned */
#define __cacheline_aligned __attribute__ ((aligned (L1_CACHE_BYTES)))
/* x86_64: arch/x86/include/asm/cache.h */
#define L1_CACHE_BYTES 64
/* Per-CPU 데이터의 정렬: cacheline_align() */
#define cacheline_aligned(x) __cacheline_aligned_in_smp x
/* 현대 커널: thread_info는 v4.13부터 task_struct에 통합됨 */
/* 현재 태스크 접근 매크로 (arch/x86/include/asm/current.h) */
#define current (typeof(struct task_struct *)this_cpu_read_stable(current_task))
HW/SW Prefetch 기술
x86_64 CPU는 하드웨어 프리페치(Prefetch)를 기본적으로 제공하며, 간단한 순차 접근 패턴이나
stride-2와 같은 규칙적인 패턴을 자동으로 감지하여 데이터를 캐시에 미리 로드합니다.
그러나 커널 코드에서는 하드웨어가 감지하지 못하는 불규칙한 데이터 접근 패턴이 빈번하게 발생합니다.
이런 경우 명시적인 prefetch() 또는 _mm_prefetch() intrinsic 함수를
사용하여 소프트웨어 단에서 Prefetch 힌트를 줄 수 있습니다.
/* include/linux/prefetch.h — prefetch 매크로 개념 예시 */
/* Prefetch data into cache (읽기용) */
#define prefetch(addr) __builtin_prefetch(addr)
/* Prefetch for write (쓰기 전용 힌트) */
#define prefetchw(addr) __builtin_prefetch(addr, 1)
/* 예: 네트워크 rx ring buffer 처리 */
static int net_rx_poll(struct netdev_rx_queue *rxq, int budget)
{
struct sk_buff *skb;
int i;
for (i = 0; i < budget; i++) {
/* 현재 버퍼 처리 */
skb = rxq_dequeue(rxq);
if (!skb)
break;
/* 다음 2개 버퍼를 Prefetch (HW prefetch가 잡지 못하는 경우) */
prefetch(skb + 1);
prefetch(skb + 2);
netif_receive_skb(skb);
}
return i;
}
분기 예측(Branch Prediction) 최적화
x86_64 CPU는 동적 분기 예측(Dynamic Branch Prediction)을 통해 분기의 실제 결과(진입 or 비진입)를 학습하고 다음 실행에 활용합니다. 분기 예측이 실패하면 파이프라인이 플러시되고 수십 사이클 규모의 지연(Latency)이 발생하며, 그 크기(Penalty)는 구현 세대에 따라 달라집니다. 커널 코드에서 분기 예측 효율을 높이는 방법은 다음과 같습니다:
likely()/unlikely()사용 — 컴파일러가 코드를 배치할 때 분기 예측 힌트를 반영합니다.likely()내부 코드가 연속된 주소에 배치되어, 파이프라인이 효율적으로 Prefetch할 수 있습니다.- Hot/Cold 코드 분리 — 자주 실행되는 경로를 연속 메모리에 배치하고, 드물게 실행되는 오류 경로를 별도 섹션으로 분리합니다.
- 루프 언롤링(Loop Unrolling) — 컴파일러가 작은 루프를 자동으로 Unroll하는 경우가 많지만, 커널 내부에서는 때때로 수동 Unrolling이 특정 성능 패턴에서 더 나은 결과를 만듭니다.
/* include/linux/compiler_types.h — likely/unlikely 내부 */
#if defined(__GNUC__) && __GNUC__ >= 4
# define likely(x) __builtin_expect (!(!(x)), 1)
# define unlikely(x) __builtin_expect (!(!(x)), 0)
#else
# define unlikely(x) (x)
#endif
/* 사용 예: 네트워크 패킷 처리 */
static int process_packet(struct sk_buff *skb)
{
/* 정상 경로: 패킷 유효함 (99.9% 케이스) */
if (likely(skb->len > 0)) {
process_ip_header(skb);
process_tcp_header(skb);
return 0;
}
/* 예외 경로: 빈 패킷 (0.1% 케이스) */
pr_debug("Empty packet received\n");
kfree_skb(skb);
return -EINVAL;
}
NUMA Aware 최적화
NUMA(Non-Uniform Memory Access) 아키텍처에서 메모리 접근 지연 시간은 "어느 NUMA 노드에 속한 메모리를 접근하느냐"에 따라 다릅니다. 같은 노드 내 메모리 접근은 상대적으로 짧은 지연을 보이고, 다른 노드에서 접근하면 링크(PCIe/ QPI/ UPI)를 통과하며 더 긴 지연이 발생할 수 있습니다(정도는 하드웨어·워크로드에 따라 달라짐).
리눅스 커널은 NUMA 인식 기능을 여러 계층에서 제공합니다:
- Per-CPU NUMA 정책: 각 CPU 스레드가 속한 NUMA 노드를 인식하고,
kmalloc_node(),alloc_pages_node()를 통해 로컬 노드에서 메모리를 할당 할 수 있습니다. - NUMA_BALANCING:
/proc/sys/kernel/numa_balancingsysctl로 활성화 시, 커널이 힌트 폴트(hint fault)를 유발해 프로세스가 자주 접근하는 메모리를 실행 중인 CPU에 가까운 NUMA 노드로 마이그레이션합니다. - NUMA Aware Page Allocation: buddy allocator가 NUMA 노드별
free list를 관리하며,
GFP_THISNODE플래그를 사용하면 지정 노드 외 다른 노드로의 폴백(Fallback) 할당을 방지합니다.
/* NUMA-인식 메모리 할당 예제 */
/* 현재 CPU가 속한 NUMA 노드에서 kmalloc */
static struct my_device *alloc_device(int node)
{
struct my_device *dev;
/* 인자 node가 유효하면 해당 노드, 아니면 현재 노드를 사용 */
if (node < 0)
node = numa_node_id();
dev = kmalloc_node(sizeof(*dev), GFP_KERNEL, node);
if (!dev)
return NULL;
/* RX ring buffer용 페이지 32개를 같은 NUMA 노드에서 일괄 할당 */
alloc_pages_bulk_node(node, GFP_KERNEL | __GFP_NOWARN,
32, dev->rx_bufs);
return dev;
}
/* NUMA-aware per-CPU variable */
static DEFINE_PER_CPU(struct cpu_stats, cpu_stats);
static void update_cpu_stats(int packets)
{
struct cpu_stats *stats = this_cpu_ptr(&cpu_stats);
stats->packets += packets;
stats->bytes += skb->len;
}
SIMD 명령어 활용 (SSE/AVX/AVX-512)
x86_64의 SIMD 명령어(SSE, AVX, AVX-512)는 데이터 병렬처리에 매우 강력합니다.
커널 모드에서는 제한적으로 사용되며, kernel_fpu_begin/end()를 사용하여
FPU/MMX/SIMD 레지스터 컨텍스트를 보호해야 합니다.
커널에서 SIMD를 사용하는 대표적인 예:
- 암호화 하위 시스템: AES-NI, SHA extensions(GHASH, PCLMULQDQ) 활용
- CRC32/CRC32c 계산: PCLMULQDQ 기반
crc32_pclmul()/crc32c_pclmul()등 - zstd/lz4: AVX2/ AVX-512 기반 압축/해제
- net filter(BPF/XDP): AVX2 기반 패킷 분류
/* 커널 모드 SIMD 사용 예제 (AES-NI 암호화) */
/* crypto/aesni-intel_glue.c 계열 — AES-NI 기반 ECB 암호화 구조 (개념 단순화) */
static int aesni_ecb_encrypt(struct aesni_ctx *ctx,
u8 *dst, const u8 *src,
unsigned int len)
{
int ret = -EBUSY;
kernel_fpu_begin();
do {
/* aesenc, aesenclast intrinsics (GCC/Clang) */
_mm_store_si128(&out, _mm_aesenc_si128(in, *round_key));
_mm_store_si128(&out, _mm_aesenclast_si128(in, *last_round_key));
src += 16;
dst += 16;
len -= 16;
round_key++;
} while (len >= 16);
kernel_fpu_end();
return 0;
}
/* CRC32c PCLMULQDQ 최적화 */
uint32_t crc32c_pclmul(const u8 *data, unsigned int len, uint32_t crc)
{
return _mm_crc32_u64(crc, *(const uint64_t*)data);
}
kernel_fpu_begin() 호출 시 FPU/SIMD 레지스터가 커널에 의해 context-switch되면
컨텍스트 전환 오버헤드가 커집니다. 데이터 블록이 작으면 FPU/SIMD 상태 저장 비용이
순차 처리 대비 불리해질 수 있으므로, 데이터 블록이 충분히 큰 경우에 한해 SIMD 루틴을 사용하는 것이
성능적으로 유리합니다(임계값은 워크로드·구현에 따라 다름).
TLB 최적화
TLB(Translation Lookaside Buffer)는 가상 주소 → 물리 주소 변환 결과를 캐시하는 고속 메모리입니다. TLB miss가 발생하면 Page Table Walk(4-level이면 L1 → L2 → L3 → L4 → Page)가 필요한데, 캐시 히트 여부에 따라 수십~수백 사이클의 추가 지연이 발생할 수 있습니다.
주요 TLB 최적화 기법:
- Huge Pages: 4KB → 2MB 페이지로 전환하면 TLB entry 1개로 512개의
4KB page를 대체할 수 있습니다.
CONFIG_HUGETLBFS또는CONFIG_TRANSPARENT_HUGEPAGE를 통해 활성화합니다. - ASID (Address Space ID): x86_64는 CR3에 ASID를 내장하여 컨텍스트 스위치 시 TLB flush를 최소화합니다.
- TLB Shootdown 최소화: 페이지 테이블 변경 후
flush_tlb_one()를 사용하는 것이flush_tlb_all()보다 효율적입니다.
# Transparent Huge Pages 설정 확인
cat /sys/kernel/mm/transparent_hugepage/enabled
# [always] madvise never
# HugePages 상태 확인
cat /proc/meminfo | grep -i huge
# HugePages_Total: 128
# HugePages_Free: 96
# TLB miss 측정
perf stat -e dTLB-load-misses,dTLB-store-misses -- ./benchmark
FRED과 GCS (Intel CET)
Intel CET(Control-flow Enforcement Technology)는 두 가지 서브 기술로 구성됩니다. GCS(Guarded Control Stack)는 Shadow Stack을 사용하여 Return Address를 보호하며, FRED(Flexible Return and Event Delivery)는 커널 호출/인터럽트 진입-종료를 가속합니다.
FRED은 Linux v6.12(2024 Q3)에 메인라인 머지되었으며, Granite Rapids(Xeon 6) 세대부터 하드웨어 지원을 시작했습니다. 기존 IDT 기반 예외/인터럽트 전달 경로를 전용 하드웨어 메커니즘으로 대체하여 이벤트 전달 오버헤드를 줄입니다(정도는 하드웨어·워크로드에 따라 달라짐).
# FRED 지원 확인
grep -i fred /proc/cpuinfo
# GCS 상태 확인
dmesg | grep -i gcs
# 예: CET/FRED 관련 부팅 메시지 확인 (실제 텍스트는 커널 버전에 따라 다름)
# FRED 활성화 부트 파라미터
fred=on # forced enable
fred=off # explicit disable
CONFIG_X86_FRED=y 빌드 시
CPUID.(EAX=0x14).ECX[0] 비트로 지원 여부를 런타임 확인 후
사용 여부가 결정됩니다. 커널 파라미터 fred=on/fred=off로 활성화 여부를
명시적으로 제어할 수 있으며, CPU가 지원하지 않으면 boot-time warning이 발생합니다.
하드웨어 성능 모니터링 (PMC / IntelPT)
x86_64 CPU는 PMU (Performance Monitoring Unit)를 통해 다양한 Hardware Counter를 제공하는데,
이를 통해 명령어 수, Cache Miss, Branch Miss, Cycle 수 등을 정량적으로 측정할 수 있습니다.
perf은 이 PMC에 접근하는 최상위 도구이며, IntelPT는 Branch-level trace를
제공합니다.
# CPU cycle, instruction, cache/branch miss를 함께 측정
perf stat -e cycles,instructions,cache-references,cache-misses, \
branch-instructions,branch-misses -- ./my_benchmark
# 결과를 flame graph로 가시화
perf record -F 99 -- ./my_benchmark
perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg
# Intel PT trace (branch-level) record
perf record -e intel_pt// -- ./my_benchmark
perf inject -j --input perf.data --output perf.pt
# MSR 직접 읽기 (root 필요)
rdmsr -a 0x198 # IA32_PERFCTR0 (core cycles)
rdmsr -a 0x199 # IA32_PERFCTR1 (instructions)
MSR(Model-specific Register)에는 각 CPU 세대마다 다양한 성능 카운터가 위치합니다.
perf stat은 PMU의 Counter multiplexing을 자동으로 처리하므로,
일반적으로 raw MSR 접근 없이 perf를 사용하는 것을 권장합니다.
rdmsr/wrmsr는 raw MSR 값을 읽고 적는
user-space 도구입니다. Kernel module에서는 rdmsrl(msr_id, &val),
wrmsrl(msr_id, val) 함수를 사용합니다. 하지만 MSR ID는 CPU 세대별로
다르므로, boot_cpu_has()/cpu_feature_enabled() (cpufeature bit)로 확인한 뒤 접근해야 합니다.
ARM64 최적화 포인트
ARM64(RISC 계열)는 x86와 달리 약한 메모리 순서(Weak Memory Ordering) 모델을 사용합니다.
코드가 작성된 순서와 실제 메모리 접근 순서가 다를 수 있기 때문에,
공유 데이터를 다룰 때는 dmb/dsb 배리어나
smp_wmb()/smp_rmb() 같은 커널 추상화를 명시적으로 사용해야 합니다.
이 점이 x86 경험자가 ARM64 커널 코드를 처음 작성할 때 가장 자주 실수하는 부분입니다.
ARM64 메모리 순서 모델
ARMv8-A 메모리 순서 모델은 ACQUIRE-RELEASE 계열의 약한 순서(Weakly Ordered) 모델입니다.
Load-Load, Load-Store, Store-Load, Store-Store 재배치 모두 허용되므로,
DMB와 DSB 명령의 종류에 따라 재배치 허용 범위 제어합니다.
| 명령 | Domain | Scope | 커널 매크로 |
|---|---|---|---|
DMB ISH | Inner Shareable | Cache coherent domain | smp_mb() |
DMB ISHLD | Inner Shareable | LoadLoad barrier | smp_rmb() |
DMB ISHST | Inner Shareable | Store→Load 재배치만 차단 (Store-Load barrier) | smp_wmb() |
DMB SY | Full System | Load/Store 모두 | mb() (strongest) |
DSB ISH | Inner Shareable | Memory access completion barrier (DSB ISH) | cache/maintains flush |
DSB SY | Full System | All accesses complete | Context switch, KASLR |
ISB | - | Pipeline flush after instruction change | isb() |
ARM64에서 분기 예측과 명령 Pipeline과 같은 하드웨어 특징은 다음과 같습니다:
- Out-of-Order vs In-order: Apple M-series는 와이드 이슈 OoO 코어인 반면, Ampere(Graviton)의 Neoverse N1/N2 코어는 인오더(In-order) 설계입니다
- Branch Predictor: ARM64에서도 동적 분기 예측(Dynamic Branch Prediction)을 지원합니다.
AArch64 ISA에는 x86처럼 명시적인 분기 힌트 명령어가 없으므로,
GCC의
__builtin_expect()가 코드 배치 최적화에 사용됩니다. - Load-Store architecture: 모든 메모리 접근이 명시적인 Load/Store 명령어이고, Register-toRegister 연산만 메모리 접근이 아닙니다. 이 특징으로 인해 x86과 달리 Memory Barrier의 역할이 명확히 분리됩니다.
/* ARM64 Memory Ordering 예제 */
static void producer(struct shared *d)
{
WRITE_ONCE(d->data, 42);
smp_wmb(); /* DMB ISHST: store → load 재배치 차단 */
WRITE_ONCE(d->ready, 1);
}
static int consumer(struct shared *d)
{
int val;
if (!READ_ONCE(d->ready))
return 0;
smp_rmb(); /* DMB ISHLD: load → load 순서 보장 */
val = READ_ONCE(d->data);
return val;
}
LSE (Large System Extensions)
ARMv8.1-A부터 도입된 LSE(Large System Extensions)는 원자적 읽기-수정-쓰기(RMW) 연산을 위한 단일 명령어 집합(Instruction Set)을 추가하는 확장입니다.
LSE 이전에는 Atomic 연산이 LDXR/STXR 루프 + DMB ISH로
구현되었는데, 이는 멀티코어 환경에서 spin-like 재시도를 유발하여
동시성(Spin Count)이 높은 경우 성능 저하를 초래했습니다.
참고로 LDAR/STLR(Load-Acquire / Store-Release)는 LSE 이전의 ARMv8.0 기본 ISA에 이미 존재하며,
각각 acquire 로드와 release 스토어의 순서 보장을 제공합니다. LSE가 새로 추가한 것은
LDADD/SWP/CAS/LDXP/STXP 같은 원자 RMW 명령어로,
LDXR/STXR 재시도 루프를 단일 명령으로 대체해 경쟁 환경의 스피너(Spin) 횟수를 줄여줍니다.
/* LSE Atomic Operations (ARM64 Assembly) */
/* LSE 이전: LDXR/STXR 루프 + DMB */
loop:
ldxr w0, [x1]
add w0, w0, #1
stxr w2, w0, [x1]
cbnz w2, loop /* store failed → retry */
dmb ish
/* LSE 이후: LDADD 단일 명령 (재시도 루프 없음) */
ldadd w0, wzr, [x1] /* *x1 += 1 (원자적) */
/* Linux kernel: arch/arm64/include/asm/atomic.h */
/* atomic_add() → LSE 빌드에서는 LDADD 계열 명령으로 대체 (런타임 alternatives 패칭) */
grep -w atomics /proc/cpuinfo 명령으로 LSE Atomics
지원 여부를 확인할 수 있습니다(cpuinfo 기능 문자열은 "atomics"로 노출됨).
CPU가 LSE를 지원하면 커널은 부팅 시 alternatives 패칭으로 Atomic 연산 코드를
LSE 명령어로 교체합니다(컴파일 타임 선택이 아닌 런타임 패칭).
SVE (Scalable Vector Extension)
SVE는 ARMv8.2-A부터 도입된 확장 명령어 집합으로, SIMD 작업에 사용되는 SIMD 레지스터의 길이를 고정 길이(AVX-512 = 512-bit)가 아니라 하드웨어에 따라 가변 길이로 정의합니다. ARM64 Server SoC(Graviton2, Fujitsu A64FX)에서 사용되며, 벡터 연산의 처리 단위(LEN)는 하드웨어 설계에 따라 결정됩니다.
SVE의 주요 특징:
- 가변 길이 벡터 — Graviton2(P128 = 128-bit) vs A64FX(P2048 = 2048-bit)
동일 코드가 다양한 하드웨어에서 동작하며,
svlen명령어로 벡터 길이 런타임 확인. - Predicate — Loop Unrolling 없이 조건부 Mask를 이용한 데이터 병렬 처리.
- Kernel SVE 지원 — 커널 모드를 위해 SVE 레지스터 컨텍스트를
kernel_neon_begin/end(),kernel_sve_begin/end()로 감싸야 합니다.
/* SVE 명령어 예시 (Scalable Vector Add) */
/* z0, z1, z2 = SVE vector registers */
ld1 {z0.s}, p0/all, [x0] /* load vector z0 from address x0. pred p0 */
ld1 {z1.s}, p0/all, [x1] /* load vector z1 from address x1 */
add z2.s, p0, z0.s, z1.s /* z2 = z0 + z1, mask p0 */
st1 {z2.s}, p0/all, [x2] /* store z2 to x2 */
/* whilelo: predicate 기반 루프 (loop unroll 없이) */
top:
ld1 {z0.s}, p0/z, [x0] /* predicated load (SVE는 포스트 증식 주소화 미지원) */
whilelo p0.s, w2, w3 /* p0 = (w2 < w3)? 1:0 per lane */
b.cond p0.any, top /* any bit set? → continue */
/* SVE vector length 확인 */
mrs x0, SVL /* SVE 벡터 길이(바이트)를 x0에 로드 */
/* Kernel SVE 사용 예: crypto/sha256-glue.c */
static void sha256_transform_sve(struct sha256_state *s,
const u8 *data)
{
kernel_sve_begin();
/* SVE intrinsics (GCC/Clang) */
sve_sha256_process(s, data);
kernel_sve_end();
}
SME (Scalable Matrix Extension)
SME는 SVE를 기반으로 확장된 매트릭스 지향(matrix-oriented) SIMD 확장으로,
ARMv9.2-A부터 지원됩니다. Tensor-like 연산에 특화된 ZA 레지스터 공간을 제공하며,
ZA 타일 크기(ZAL × ZAW)는 구현의 SVE 벡터 길이(SVL)에 따라 결정됩니다.
SME의 핵심 특징:
- Streaming Mode — 캐시를 우회(Bypass)하고 메모리에 직접 읽고 쓰는 모드로, PSTATE.SM 비트로 진입/탈출합니다. 메모리 순서는 일반 ARMv8-A 약한 순서 모델을 따릅니다.
- ZA register — SVE의 z0~z31 벡터 레지스터와 별도로 할당되는 매트릭스 연산 특화 레지스터 공간입니다. 행렬 곱(FMLA 등 MMA 명령)을 단일 명령으로 수행합니다.
- Fractured Loads/Store — ZA의 부분(row/column)을 메모리에 직접 로드/스토.
/* SME 매트로릭스 연산 개념 예시 (ZA register) */
/* 스트리밍 모드: PSTATE.SM = 1로 진입, SMSTOP 명령으로 종료 */
smstop /* streaming mode 종료 (PSTATE.SM = 0) */
/* ZA fractured load/store (프래디케이트 기반) */
ldr za0.d, p0, [x0] /* ZA의 일부 요소를 p0 마스크로 로드 */
str za0.d, p0, [x2] /* ZA의 일부 요소를 p0 마스크로 저장 */
/* MMA: Floating-point Matrix Multiply-Accumulate */
fmla za0.s, x1.s, za1.s /* za0 += x1 × za1 (행렬 곱-누적) */
grep -w sme /proc/cpuinfo 명령으로 SME 지원을
확인할 수 있습니다. Linux 커널은 task_struct의 fpsimd_state 안에
별도의 sme_state 영역을 두어 SME 컨텍스트를 관리합니다.
NEON (128-bit SIMD)
NEON이 ARM64에서 가장 널리 사용되는 SIMD 명령어입니다. x86의 AES-NI에 대응하는 ARMv8 Cryptographic Extensions(AES, AESEMC, AESIMC, SHA1/SHA2, PMULL 등)이 전용 암호화 명령어를 제공하며, 커널 crypto 프레임워크는 이 명령어들을 활용합니다. PMULL은 PCLMULQDQ의 ARM 대응으로, GHASH 등 Message Authentication Code(MAC) 연산을 가속합니다.
/* NEON + ARM Crypto Extension 예시 */
/* PMULL: Polynomial Multiply (GHASH 핵심 연산) */
pmull q0, d0, d8 /* 하위 64비트 쌍 → 128비트 결과(q0) */
pmull2 q1, d1, d9 /* 상위 64비트 쌍 → 128비트 결과(q1) */
/* AES 라운드 연산 (ARM Crypto Extensions) */
aes q0, q1 /* SubBytes + ShiftRows (암호화 라운드) */
aesimc q0, q1 /* InvMixColumns (복호화 라운드) */
aesmc q0, q1 /* MixColumns (암호화 라운드) */
/* VMULL: Integer Multiply-Lengthen (포화 연산 아님) */
vmull q0, d0, d1 /* 32 × 32 → 64 (부호 있는 곱 확장) */
/* Kernel NEON usage example: crypto/ghash-neon-arm64.c */
/* GHASH using NEON + ARM Crypto Extensions */
static void ghash_neon_update(struct ghash_ctx *ctx,
const u8 *src, unsigned int len)
{
uint64_t x[2], h[2];
kernel_neon_begin();
/* Load H key into NEON registers */
vld1q_u64(h, (const uint64_t *)ctx->hash);
while (len >= 16) {
/* Load input block */
vld1q_u64(x, (const uint64_t *)src);
/* EORB with current state */
x[0] = veorq_u64(x[0], ctx->x[0]);
x[1] = veorq_u64(x[1], ctx->x[1]);
/* Polynomial multiply (PMULL) — core of GHASH */
/* ... NEON intrinsics for PMULL, EOR, shift ... */
src += 16;
len -= 16;
}
kernel_neon_end();
}
MTE (Memory Tagging Extension)
MTE가 활성화된 ARM64 시스템에서는 Tag Check Fault의 오버헤드가 발생합니다. 보안 검사와 성능 간의 Trade-off에서, Tag Allocation + Tag Checking을 모두 켰을 때 전반적으로 일부 성능 저하가 관찰되지만(정도는 하드웨어·워크로드에 따라 달라짐), 이는 사용 패턴(Heap 객체 수, Allocation 빈도, Cache Hit Rate)에 따라 달라집니다.
MTE가 성능에 미치는 영향:
- Tag Allocation: 16바이트 데이터마다 1바이트 태그를 저장하므로, 4KB 페이지당 256바이트가 Tag Storage로 사용됩니다(유효 용량 ≈ 3840바이트).
- Tag Compare: 각 Load/Store마다 태그 비교가 추가되어 소폭의 지연이 발생합니다(구현·캐시 상태에 따라 다름).
- Tag Check Fault: 불일치 시 Exception 발생 → Context Switch → 성능 대폭 저하.
MTE 설정이 성능에 미치는 영향을 최소화하려면:
- Tag Allocation만 활성(Tag Checking 비활성): 디버깅/프로파일링(Profiling) 모드에서 활용. Tag를 할당하지만 Violation을 Exception이 아닌 Ignore로 처리합니다.
- Huge Page + MTE: 2MB Huge Page를 사용하면 Tag Storage Overhead가 4KB Page 대비 1/512로 감소됩니다.
- SME Streaming Mode: Stream Mode는 Tag Storage를 참조하지 않으므로, MTE 오버헤드를 완전히 피할 수 있습니다.
# MTE Tag Storage 확인
cat /proc/cpuinfo | grep -i mte
# Features: ... fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp
# jpcre32 lrnd lse2 ssbs mte
# MTE 지원 여부 (cpuinfo 기능 문자열)
grep -w mte /proc/cpuinfo
# user-space MTE 제어 비트 확인
prctl PR_GET_TAGGED_ADDR_CTRL # TCF bit 상태 반환
ARM64 NUMA 최적화
ARM64 서버(Graviton, Ampere, Apple Silicon)는 NUMA Aware 메모리 할당과 CPU Affinity
정책이 x86_64와 유사하게 동작합니다. 다만 ARM64는 ACPI 대신 Device Tree로 NUMA 토폴로지를
정의하므로, DTB(Device Tree Blob)에서 numa-node, memory, cpus 노드의
numa-node-id 속성으로 NUMA 노드-메모리-CPU 매핑을 정의합니다.
/* ARM64 Device Tree: NUMA 토폴로지 정의 예시 */
/ {
#address-cells = <2>;
#size-cells = <2>;
/* NUMA 노드 0: 물리 메모리 1GB @ 0x8_0000_0000 */
memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x40000000>;
numa-node-id = <0>;
};
/* NUMA 노드 1: 물리 메모리 1GB @ 0xC_0000_0000 */
memory@c0000000 {
device_type = "memory";
reg = <0x0 0xc0000000 0x0 0x40000000>;
numa-node-id = <1>;
};
cpus {
cpu0 {
device_type = "cpu";
reg = <0>;
numa-node-id = <0>;
};
cpu4 {
device_type = "cpu";
reg = <4>;
numa-node-id = <1>;
};
};
};
ARM64 캐시 및 TLB 최적화
ARM64는 캐시 라인 크기가 64바이트(Neoverse 기반 Graviton 등) 또는 128바이트(Apple M-series)로 x86_64와 다를 수 있습니다. TLB Entry 수는 아키텍처마다 다르며, TLB 최적화 측면에서 Huge Page, ASID, DSB ISH 명령의 효율적 활용이 중요합니다.
- Huge Pages: ARM64도 2MB/1GB Huge Page를 지원합니다.
CONFIG_TRANSPARENT_HUGEPAGE와CONFIG_HUGETLBFS를 활성화합니다. - ASID 관리: ARM64에서 ASID는 TTBR0/TTBR1_EL1의 ASID 필드로 제어됩니다. ASID가 다르면 전체 TLB flush 없이 TTBR만 교체하여 기존 TLB 엔트리를 보존할 수 있습니다.
- DSB ISH 활용: 캐시 유지(Cache Maintenance) 작업 후 DSB ISH를 반드시 사용하여, 플러시가 완료되기 전에 새로운 접근이 발생하지 않도록 합니다.
/* ARM64 TLB flush 패턴 (arch/arm64/mm/tlbflush.c) */
/* Single address TLB flush (context-aware) */
void flush_tlb_page(struct vm_area_struct *vma, unsigned long address)
{
/* ASID가 동일하면 단일 entry만 flush */
__asm__ __volatile__(
"dsb ish"> /* Ensure page table update visible */
"tlbi vale1is, %0" /* VA-based TLBI, EL1, Inner Shareable */ /* TLB Invalidate Virtual Address, EL1 */
"dsb ish"> /* Ensure TLBI complete */
"isb"> /* Pipeline flush */
: : "r"(address) : "memory"
);
}
/* ASID가 다르면(다른 process) — global TLB flush */
void flush_tlb_all(void)
{
__asm__ __volatile__(
"dsb ish">
"tlbi vmalle1is" /* All-addresses TLBI, EL1, Inner Shareable */ /* Global TLB invalidate, all ASIDs */
"dsb ish">
"isb">
: : : "memory"
);
}
ARM64 성능 모니터링 (PMU / CoreSight)
ARM64의 PMU(Performance Monitoring Unit)는 architected register로 PMCR_EL0,
PMCNTENSET_EL0, PMCCNTR_EL0(사이클 카운터) 등으로 접근합니다.
또한 ARM CoreSight는 실행 시점의 Hardware trace를 제공하는데,
Intel PT의 ARM64 대응 도구라고 볼 수 있습니다.
| 도구/기술 | 용도 | 사용 예 |
|---|---|---|
| ARM PMU | architected Performance Counter | perf stat -e cycles,instructions,cache-misses |
| CoreSight ETM | Execution Trace Macrocell (Instruction-level trace) | perf record -e cs_etm// |
| CoreSight STM | Software Trace Macrocell (custom event trace) | stm_write_event() in kernel driver code |
| PMU Registers | Raw Performance Counter 접근 | mrs x0, pmcr_el0 (EL1 access) |
# 시스템의 PMU 목록 확인 (cpu, etm4x 등)
ls /sys/bus/event_source/devices/
# perf with ARM PMU
perf stat -e cycles,instructions,cache-misses -- ./benchmark
# CoreSight ETM trace (CONFIG_CORESIGHT_ETM* 필요)
perf record -e cs_etm// -- ./benchmark
perf script
# CPU 토폴로지 확인 (예시 sysfs)
cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list
/* ARM64 PMU Register 접근 (kernel code) */
/* PMCR_EL0: Performance Monitor Control Register */
static void pmu_enable_counters(void)
{
u64 val;
/* C bit: 이벤트 카운터 리셋, E bit: PMU 인에이블 */
asm volatile("msr pmcr_el0, %0" : : "r"((u64)0x00000005) : "memory");
/* Enable all counters */
asm volatile("msr pmcntenset_el0, %0" : : "r"((u64)0xFFFFFFFF) : "memory");
/* Enable PMU */
asm volatile("mrs %0, pmcr_el0" : "=r"(val));
val |= (1 << 0); /* E bit: Enable */
asm volatile("msr pmcr_el0, %0" : : "r"(val) : "memory");
}
perf 도구를 사용하세요.
perf stat -e cycles,instructions,cache-misses,branch-misses로 주요 메트릭을 측정할 수 있습니다.
자세한 내용은 개발 도구 (Development Tools) 문서를 참고하세요.
실전 디버깅(Debugging) 팁 (Debugging Tips)
아키텍처별 커널 디버깅은 각 플랫폼의 특성을 이해하고 적절한 도구를 사용해야 효과적입니다.
공통 디버깅 기법
디버깅 도구에는 계층이 있습니다. 가장 가벼운 커널 로그(pr_*/WARN_ON)로 현장을 포착하고, 재현이 어려운 타이밍 문제는 ftrace/tracing으로 이벤트 순서를 기록하며, 패닉 발생 시에는 kdump/kexec로 메모리 덤프를 확보해 post-mortem 분석하는 것이 표준 절차입니다. 하드웨어 디버거(JTAG 등)는 부팅 초기 단계나 시뮬레이터 환경에서 마지막 수단으로 사용합니다.
아키텍처에 상관없이 가장 먼저 활용해야 할 도구는 커널 로그(pr_debug/pr_err)와
WARN_ON() 계열 매크로입니다. 이 두 가지는 별도 장비 없이도 즉시 사용할 수 있고,
문제 발생 위치와 콜스택을 빠르게 좁혀 주기 때문에 항상 첫 번째 수단으로 삼아야 합니다.
아키텍처 특화 도구(JTAG, CoreSight 등)는 그다음 단계입니다.
/* 커널 디버그 출력 */
pr_debug("Variable x = %d\\n", x); /* 동적 디버그(dyndbg)로 활성화 시 출력 */
pr_info("Info message\\n"); /* 정보성 메시지 */
pr_warn("Warning: %s\\n", msg); /* 경고 */
pr_err("Error: %d\\n", errno); /* 에러 */
/* WARN 및 BUG 매크로 */
WARN_ON(condition); /* 조건 만족 시 경고 + 백트레이스 */
WARN_ONCE(condition, "message"); /* 최초 1회만 경고 */
BUG_ON(critical_error); /* 치명적 에러 시 패닉 (사용 자제) */
/* 동적 디버그 */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
pr_debug("Entry: func=%s\\n", __func__);
x86_64 특화 디버깅
x86_64는 Intel PT(Processor Trace)와 MSR 직접 접근이라는 강력한 하드웨어 도구를 제공합니다. Intel PT는 분기 명령 실행 이력을 하드웨어가 기록하므로, 이미 크래시가 발생한 후에도 어떤 경로로 실행되었는지 재구성할 수 있습니다. 일반적인 printk 디버깅으로는 타이밍 문제를 재현하기 어려운 경쟁 조건(Race Condition) 디버깅에 특히 유용합니다.
| 도구/기법 | 용도 | 사용 예 |
|---|---|---|
| KGDB | GDB를 통한 원격 디버깅 | kgdboc=ttyS0,115200 kgdbwait 커널 파라미터 |
| Intel PT | 프로세서 트레이스 | perf record -e intel_pt// |
| MSR 읽기 | 모델 특정 레지스터 확인 | rdmsr 0x1a0 (IA32_MISC_ENABLE) |
| APIC 디버그 | 인터럽트 문제 추적 | /proc/interrupts, ftrace irq_vectors_entry 이벤트 |
ARM64 특화 디버깅
ARM64 플랫폼은 대부분 임베디드·서버 보드이기 때문에 UART 시리얼 콘솔이 핵심 디버깅 수단입니다.
부팅 초기에 출력이 없다면 earlycon 파라미터로 직렬 포트(Serial Port)를 직접 지정해야 합니다.
칩 내부 트레이스가 필요한 경우에는 CoreSight ETM(Embedded Trace Macrocell)을 활용하며,
이는 Intel PT의 ARM 대응 기능입니다. Device Tree 구성 오류는 /proc/device-tree/와
dtc로 덤프해서 비교하는 것이 가장 빠릅니다.
| 도구/기법 | 용도 | 사용 예 |
|---|---|---|
| JTAG | 하드웨어 디버거 | Arm Development Studio(DS-5), JTAG 디버거, OpenOCD |
| Device Tree 확인 | HW 구성 검증 | /proc/device-tree/, dtc -I fs /proc/device-tree |
| CoreSight | 온칩 디버그/트레이스 | ETM (Embedded Trace Macrocell) 활용 |
| earlycon | 초기 부팅 로그 | earlycon=pl011,0x09000000 |
RISC-V 특화 디버깅
실제 RISC-V 하드웨어 없이 시작하기 가장 좋은 방법은 Spike ISA 시뮬레이터 또는 QEMU RISC-V입니다. 두 환경 모두 커널 부팅 로그를 터미널로 받아볼 수 있으며, 실리콘이 없는 상태에서 SBI·CSR 동작을 검증하는 데 유용합니다. 실제 보드를 사용할 경우, RISC-V JTAG 디버그 스펙은 x86/ARM보다 최근에 정의되었으므로 보드마다 지원 품질이 다릅니다. 보드 문서에서 지원 디버그 Transport(JTAG/cJTAG)를 먼저 확인하세요.
| 도구/기법 | 용도 | 사용 예 |
|---|---|---|
| OpenOCD | JTAG 디버깅 | RISC-V 보드에 연결하여 GDB 원격 디버깅 |
| Spike 시뮬레이터 | ISA 시뮬레이터 | 하드웨어 없이 RISC-V 커널 테스트 |
| SBI 디버깅 | SBI 호출 추적(Call Trace) | SBI 호출 경로(sbi_ecall) 트레이스 확인 |
CONFIG_DEBUG_* 옵션들을 비활성화하세요.
디버그 옵션은 성능에 상당한 영향을 미칩니다. 개발 및 테스트 환경에서만 활성화하는 것이 좋습니다.
자세한 내용은 디버깅 문서를 참고하세요.
MIPS 특화 디버깅
MIPS는 하드웨어 없이 시작하기에 가장 쉬운 대상 중 하나입니다.
QEMU가 Malta 보드(qemu-system-mips/qemu-system-mipsel)와
MIPS virt 머신(-M virt)을 지원하며, TLB 소프트웨어 관리 특성상
TLB 관련 예외가 부팅 초기에 자주 발생하므로 이를 관찰하는 것이 디버깅의 핵심입니다.
| 도구/기법 | 용도 | 사용 예 |
|---|---|---|
| QEMU 주소 매핑 | 가상 주소·물리 주소 확인 | -monitor에서 info mtree로 MMIO 주소 검증 |
| KASLR / vmap 디버깅 | TLB 엔트리 추적 | CONFIG_MIPS_VA_BITS와 dmesg의 TLB 셧다운 로그 확인 |
| GDB 원격 디버깅 | 커널 심볼(Kernel Symbol) 디버깅 | gdb-multiarch + QEMU -s -S로 kernel_entry 중단점 |
| 직렬 콘솔(Serial Console) | 부팅 로그 확인 | QEMU -nographic 또는 보드 console=ttyS0,115200 |
# MIPS32 (little-endian, Malta) QEMU 실행 예
qemu-system-mipsel -M malta \
-kernel vmlinux \
-append "console=ttyS0" \
-nographic
CONFIG_DEBUG_* 옵션들을 비활성화하세요.
디버그 옵션은 성능에 상당한 영향을 미칩니다. 개발 및 테스트 환경에서만 활성화하는 것이 좋습니다.
자세한 내용은 디버깅 문서를 참고하세요.
자주 묻는 질문 (FAQ)
Q1. 어떤 아키텍처를 선택해야 하나요?
A: 사용 목적에 따라 다릅니다:
- x86_64: 데스크탑, 서버, 클라우드 환경. 가장 성숙한 생태계와 도구 지원
- ARM64: 모바일, 임베디드, 저전력 서버 (AWS Graviton, Apple Silicon). 전력 효율 우수
- RISC-V: 연구, 교육, 새로운 HW 프로젝트. 오픈 ISA로 라이선스 비용 없음
- MIPS: 레거시 라우터·임베디드 기기 유지보수 또는 소프트웨어 관리 TLB 등 RISC 설계 학습 목적. 신규 HW는 RISC-V 권장
Q2. 크로스 컴파일(Cross Compilation)은 어떻게 하나요?
A: ARCH와 CROSS_COMPILE 변수를 설정합니다:
# ARM64 크로스 컴파일 (x86_64 호스트)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)
# RISC-V 크로스 컴파일
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfig
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- -j$(nproc)
자세한 내용은 빌드 시스템 문서를 참고하세요.
Q3. 32비트 커널도 여전히 사용되나요?
A: 예, 특정 임베디드 환경에서는 여전히 사용됩니다. ARM32 (AArch32), x86 (i386), MIPS32 등은 레거시 시스템이나 자원 제약 환경에서 사용되지만, 새로운 프로젝트는 대부분 64비트 아키텍처를 선택합니다. 주요 배포판들은 x86 32비트 지원을 점차 종료하고 있습니다.
Q4. ARM64의 big.LITTLE은 무엇인가요?
A: 고성능 코어(big)와 저전력 코어(LITTLE)를 조합한 이기종 멀티프로세싱(HMP) 아키텍처입니다.
리눅스 커널은 Energy Aware Scheduling (EAS)를 통해 워크로드에 따라 적절한 코어에 태스크(Task)를 할당합니다.
최신 ARM 시스템은 DynamIQ 기술을 사용하여 더 유연한 코어 조합을 지원합니다.
자세한 내용은 프로세스 스케줄러 문서를 참고하세요.
Q5. 페이지 테이블 레벨은 어떻게 결정되나요?
A: 가상 주소 공간 크기에 따라 결정됩니다:
- x86_64: 4-level (48-bit VA), 5-level (57-bit VA,
CONFIG_X86_5LEVEL) - ARM64: 3-level(SV39)/4-level(SV48)/5-level(SV57, 56-bit VA) — VA 폭과 페이지 크기에 따라 결정
- RISC-V: Sv39 (3-level), Sv48 (4-level), Sv57 (5-level)
레벨이 많을수록 주소 공간은 넓어지지만 페이지 테이블 워킹 오버헤드가 증가합니다. 자세한 내용은 메모리 관리 문서를 참고하세요.
Q6. 아키텍처별 시스템 콜 차이는?
A: 시스템 콜 번호와 진입 메커니즘이 다릅니다:
- x86_64:
syscall명령어, 번호는arch/x86/entry/syscalls/syscall_64.tbl - ARM64:
svc #0명령어, 번호는include/uapi/asm-generic/unistd.h - RISC-V:
ecall명령어, 번호는 주로asm-generic/unistd.h를 사용해 ARM64와 많은 항목을 공유
모든 아키텍처는 공통 시스템 콜 인터페이스를 제공하지만, 일부 아키텍처별 시스템 콜이 존재할 수 있습니다. 자세한 내용은 시스템 콜 (System Call) 문서를 참고하세요.
Q7. 가상화 오버헤드는 얼마나 되나요?
A: 하드웨어 가상화 지원 여부에 따라 크게 다릅니다:
- Hardware-assisted (VT-x, AMD-V, ARM VHE, RISC-V H-ext): 다소의 오버헤드 (정도는 워크로드 의존)
- Paravirtualization (Xen PV): 하드웨어 지원 없을 때 사용, 오버헤드 더 높음
- I/O 가상화: 네트워크, 디스크 I/O에서 오버헤드 큼 → virtio, SR-IOV로 완화
현대 시스템에서는 하드웨어 가상화 지원이 표준이므로 오버헤드가 크지 않습니다. 자세한 내용은 가상화 (KVM) 문서를 참고하세요.
Q8. PREEMPT_RT와 PREEMPT의 차이는 무엇인가요?
A: 두 옵션은 선점(preemption) 범위가 다릅니다:
CONFIG_PREEMPT— 커널 코드 대부분을 선점할 수 있지만, 스핀락(spinlock) 내부와 같은 임계 구간은 선점 불가능합니다. 일반 데스크탑/서버 환경에 적합합니다.CONFIG_PREEMPT_RT(v6.12 메인라인 병합) — 스핀락을 RT-mutex로 변환하고, 인터럽트 핸들러를 스레드화하여 거의 모든 커널 경로를 선점할 수 있습니다. 일반 커널 대비 훨씬 낮은 지연 분산을 제공해 산업 제어, 로봇 공학, 프로 오디오 등에 사용됩니다(실측 지연은 워크로드·하드웨어에 따라 다름).
v6.12 이전에는 별도의 외부 패치(Patch)(rt-patches)를 적용해야 했으나, 이제 메인라인 커널만으로 실시간(Real-time) 커널을 빌드할 수 있습니다. 자세한 내용은 RT-Mutex 문서를 참고하세요.
Q9. FRED가 기존 IDT를 완전히 대체하나요?
A: FRED(Flexible Return and Event Delivery)는 Intel이 도입한 새로운 이벤트 전달 메커니즘으로, v6.12에서 커널 지원이 추가되었습니다.
- 대체 범위 — FRED는 예외(Exception)와 인터럽트 전달 경로를 IDT(Interrupt Descriptor Table) 기반에서 전용 하드웨어 메커니즘으로 대체합니다. 스택 전환, 레지스터 저장, 이벤트 레벨 분류를 하드웨어가 자동으로 처리합니다. SYSCALL/SYSRET 시스템 콜 경로는 그대로 유지됩니다.
- 호환성 — FRED를 지원하지 않는 프로세서에서는 기존 IDT 방식이 그대로 사용됩니다. 커널은 부팅 시 CPU 기능을 감지하여 자동으로 적절한 경로를 선택합니다.
- 이점 — 커널 진입/복귀 오버헤드 감소, 중첩 이벤트 처리 효율화, 레거시 진입 방식의 특수한 동작(quirk) 제거 등의 장점이 있습니다.
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)와 AMD-SME(Secure Memory Encryption)는 AMD 고유 기능입니다(본 문서 앞부분의 ARM SME(Scalable Matrix Extension)와는 다른 개념입니다).
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 주소 변환, 디바이스 격리(Isolation) — drivers/iommu/arm/arm-smmu-* |
| AMBA/AXI/ACE Spec | 버스(Bus) 프로토콜 | 캐시 일관성(coherency), 배리어 동작, DMA 전송 특성 |
RISC-V Specifications
RISC-V는 단일 문서가 아니라 용도별 볼륨으로 나뉘어 있는 스펙 체계입니다. Unprivileged ISA는 사용자 레벨 명령어를, Privileged ISA는 특권 모드·CSR·메모리 관리·인터럽트를 정의하며, SBI/PLIC/AIA 같은 플랫폼 인터페이스 스펙은 펌웨어(OpenSBI)와 인터럽트 컨트롤러 연동을 규정합니다. 아래 표는 각 문서가 커널 개발에서 어디에 쓰이는지를 정리한 것입니다.
| 문서 | 내용 | 커널 개발 활용 |
|---|---|---|
| Unprivileged ISA (Volume I) | 기본 정수 ISA + 표준 확장 | RV64I, M/A/F/D/C 확장, 원자적(Atomic) 명령어(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 | 외부 인터럽트 라우팅(Routing), 우선순위(Priority) — drivers/irqchip/irq-sifive-plic.c |
| AIA Specification | Advanced Interrupt Architecture | APLIC + IMSIC, MSI 기반 인터럽트 — 차세대 RISC-V 인터럽트 체계 |
arch/riscv/ 하위의 구현이 스펙의 각 챕터와 직접 대응됩니다.
특히 Privileged ISA Vol. II의 페이지 테이블과 예외 처리 챕터는 커널 개발의 핵심 참조 문서입니다.
MIPS Architecture Reference Manuals
MIPS 사양은 아키텍처 버전(Release)별로 문서 유형이 나뉩니다. CP0 레지스터·예외·TLB 동작은 Privileged Resource Architecture (MD00090)에 수록되어 있으며, 명령어 집합은 MIPS32/MIPS64 Architecture Reference Manual에서 다룹니다. 정식 문서 배포는 Wave Computing(구 Imagination)의 MIPS 지원 페이지에서 과거 PDF(MD00086/MD00087 등)로 제공되었으며, 현재는 검증된 사본이 커널 커뮤니티·교육 자료로 유통됩니다.
| 문서 | 내용 | 커널 개발 활용 |
|---|---|---|
| Privileged Resource Architecture (MD00090) | CP0, 예외, TLB | arch/mips/include/asm/mipsregs.h의 CP0 번호, tlbl/tlbs 미스 흐름 |
| MIPS32 Instruction Set Architecture Reference (MD00086) | MIPS32 명령어 집합 (Volume II) | SYSCALL/TLBWR/ERET 등 어셈블리 핸들러 명령어 |
| MIPS64 Instruction Set Architecture Reference (MD00087) | MIPS64 명령어 집합 (Volume II) | XTLB Refill 벡터, n32/n64 ABI, 64비트 주소 공간 |
| Release 6 (R6) 명령어셋 개정 | R6 명령어 집합 개정 | CONFIG_CPU_MIPS32_R6 컴팩트 분기(Compact Branch)와 지연 슬롯(Delay Slot) 제거 |
arch/mips/와 MIPS 명령어셋 (ISA) 문서를 함께 참조하는 것이 좋습니다.
특히 커널 개발 중 CP0 비트 필드를 확인할 때는 mipsregs.h의 매크로 정의가
아키텍처 매뉴얼 비트 위치와 일치하는지 대조하면 오류를 줄일 수 있습니다.
매뉴얼 효과적 활용법
아키텍처 매뉴얼은 전부를 읽을 필요가 없습니다. 어떤 현상을 설명하는지 먼저 파악하고, 아래 표처럼 상황별로 필요한 챕터만 찾아보는 방식이 가장 효율적입니다. 레지스터 필드나 비트 위치를 확인할 때는 매뉴얼의 레지스터 요약표(Register Summary)를 먼저 보고, 상세 동작은 해당 챕터 본문으로 이동하세요.
| 상황 | 참조 문서 | 참조 섹션 |
|---|---|---|
| 페이지 테이블 워크 디버깅 | Intel SDM Vol. 3 Ch. 4 / ARM ARM Part D / RISC-V Privileged Spec(MMU 챕터) | 페이지 테이블 엔트리 형식, 비트 필드 의미 |
| 인터럽트 핸들러 작성 | Intel SDM Vol. 3 Ch. 6, 10 / GIC Spec / PLIC Spec | IDT 구조, APIC 프로그래밍, EOI 처리 |
| 메모리 배리어 선택 | Intel SDM Vol. 3 Ch. 8 / ARM ARM Part B / RISC-V Unprivileged Spec(fence 명령어 섹션) | 메모리 순서 모델, 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 / 각 코어 TRM(전력 관리 섹션) | C-state, P-state, MWAIT, WFI |
| 보안 기능 구현 | 각 제조사 보안 가이드 | Intel CET, ARM MTE/PAC/BTI, AMD SEV/SME |
- ACPI Specification — 전원 관리(Power Management), 디바이스 열거, 테이블(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 등 벤더별 구현 상세
AMD vs Intel 마이크로아키텍처 비교 (Microarchitecture Comparison)
x86_64 프로세서를 제조하는 두 벤더 — AMD와 Intel — 는 동일한 ISA(명령어 셋 아키텍처)를 구현하지만, 내부 마이크로아키텍처 설계 철학이 근본적으로 다릅니다. 이 차이는 커널의 스케줄러, NUMA 정책, 캐시 관리, 전력 제어 코드에 직접적인 영향을 미칩니다.
칩렛(Chiplet) vs 모놀리식/타일(Tile) 설계
| 항목 | AMD (Zen4/Zen5) | Intel 서버 (SPR/EMR/GNR) | Intel 클라이언트 (RPL/MTL/LNL/ARL/PTL) |
|---|---|---|---|
| 다이(Die) 구조 | CCD(Core Complex Die) + IOD(I/O Die) 분리형 칩렛 | 고코어수 모놀리식 다이 또는 EMIB 멀티 타일 패키지 | Meteor Lake: Foveros 3D 타일 / Arrow Lake: 칩렛 타일 / Panther Lake: 3D Foveros 통합 타일 |
| 코어 구성 | CCD당 8코어(Zen4) / 16코어(Zen5c Dense), 동종 코어(Homogeneous) | 128코어 이상(Xeon 6 AP), 동종 Xeon 또는 P-core/E-core 혼합(GNR) | P-core 6~8 + E-core 8~16 + LP E-core 2 (3-tier 이종 Heterogeneous) |
| 인터커넥트(Interconnect) | Infinity Fabric (IF) — 포인트-투-포인트, CCD↔IOD 스타 토폴로지(Topology) | UPI(Ultra Path Interconnect) 소켓간, 내부 메시(Mesh) 인터커넥트 | Foveros 인터커넥트(die-to-die), 내부 링 버스 |
| 메모리 컨트롤러 | IOD에 집중 (UMC — Unified Memory Controller), NUMA 노드 = 소켓 또는 NPS 분할 | 다이 내 분산, SNC(Sub-NUMA Clustering)로 NUMA 분할 가능 | SoC 타일에 통합, 단일 NUMA 노드 |
| L3 캐시 공유 | CCD 내 전체 코어 공유 (Zen3+: 32MB/CCD, V-Cache: 96MB) | 메시 분산 슬라이스(Slice), 전체 코어 논리적 공유 | P-core와 E-core 별도 L3 또는 공유 (세대별 상이) |
| 확장 방식 | CCD 수 증가 (EPYC: 최대 12 CCD = 96코어, Zen4c dense 버전은 192코어까지) | 코어 수 증가 또는 타일 추가 | 타일 조합 변경 |
| 커널 영향 | CCD간 NUMA 지연 차이 → NPS BIOS 설정에 따라 NUMA 토폴로지 변동 | SNC 활성화 시 NUMA 노드 2배 → 메모리 할당 정책 영향 | ITMT(Intel Thread Director) 스케줄러 연동 필요, HFI(Hardware Feedback Interface) v6.17 확장 |
인터커넥트 토폴로지 비교
파이프라인(Pipeline) 및 실행 엔진 비교
마이크로아키텍처 세대별 파이프라인 폭과 내부 자원의 차이는 IPC(클록당 명령어 수)와 분기 예측 정확도에 직접 영향을 미치며, 커널의 성능 최적화 결정에도 관련됩니다. 아래 표의 디코드 폭·ROB 크기 등은 제조사 공식 수치와 커뮤니티 리버스 엔지니어링 값을 함께 정리한 것입니다.
| 파라미터 | AMD Zen4 (Raphael/Genoa) | AMD Zen5 (Granite Ridge/Turin) | Intel Golden Cove (ADL P-core) | Intel Lion Cove (LNL P-core) |
|---|---|---|---|---|
| 디코드 폭(Decode Width) | 4-wide | 4-wide (2×4 이중 파이프) | 6-wide | 8-wide |
| ROB(Re-Order Buffer) 크기 | 320 엔트리 | 448 엔트리 | 512 엔트리 | 576 엔트리 |
| 정수 실행 포트 | 6개 ALU + 3개 AGU | 6개 ALU + 4개 AGU | 5개 ALU + 2개 AGU | 6개 ALU + 3개 AGU |
| 분기 예측기 | TAGE + 퍼셉트론(Perceptron), ~13K 엔트리 | TAGE + 퍼셉트론, ~24K 엔트리 | TAGE-like, ~12K 엔트리 | TAGE-like, ~16K 엔트리 |
| L1 D-캐시 | 32 KB, 8-way | 48 KB, 12-way | 48 KB, 12-way | 48 KB, 12-way |
| L2 캐시 | 1 MB/코어 | 1 MB/코어 | 1.25 MB/코어 (P-core) | 2.5 MB/코어 (P-core) |
| L3 캐시 | 32 MB/CCD (공유) | 32 MB/CCD (공유) | 30 MB (전체 공유, 메시 슬라이스) | 36 MB (전체 공유) |
| AVX-512 지원 | 512-bit 네이티브 | 512-bit 네이티브 | 512-bit 네이티브(P-core) | P-core만 512-bit 네이티브 |
| 코어 유형 | 동종(Homogeneous) | 동종 / Dense(Compact) 분리 | 이종: P-core + E-core | 이종: P-core + E-core + LP E-core |
| 커널 스케줄러 영향 | CCD간 NUMA 지연만 고려 | Dense 코어 식별 필요 | ITMT 우선순위 스케줄링 필수 | HFI(Hardware Feedback Interface) + ITMT |
- AMD 토폴로지 탐지: CPU 토폴로지 — AMD Chiplet 아키텍처
- Intel 이종 코어 스케줄링: CPU 토폴로지 — Intel 아키텍처
- 캐시 코히런시 프로토콜 비교: CPU 캐시 — 코히런시 프로토콜
- MSR 주소 대응표: MSR — Intel vs AMD MSR 비교
x86 CPU 실행 모드 (Operating Modes)
x86 프로세서는 리셋 후 리얼 모드(Real Mode)에서 시작하여 여러 모드를 거쳐 롱 모드(Long Mode)에 도달합니다. 리눅스 커널 부팅 과정은 이 모드 전환을 순차적으로 수행하며, 각 모드의 특성을 이해하는 것은 부트로더 코드와 커널 초기화 코드를 분석하는 데 필수적입니다.
모드 전환 흐름
Real Mode (리얼 모드)
Real Mode는 1978년 Intel 8086 설계 당시 유일한 실행 모드였습니다. 당시 메모리는 1MB가 최대였고, 메모리 보호 개념 자체가 없었기 때문에 모든 코드가 하드웨어에 직접 접근할 수 있었습니다. 현대 x86_64 시스템에서 Real Mode는 부팅 직후 펌웨어(BIOS/UEFI) 환경에서만 사용됩니다. CPU가 전원을 켜는 순간에는 하위 호환성을 위해 항상 Real Mode로 시작하고, 이후 Protected Mode → Long Mode 순으로 전환합니다. 즉, 커널 개발자가 Real Mode 코드를 직접 작성할 일은 거의 없지만, 부팅 초기 실패를 디버깅할 때 이 모드의 제약(16비트, 1MB 주소)을 이해하고 있어야 합니다.
| 특성 | 설명 |
|---|---|
| 비트 폭 | 16비트 레지스터, 20비트 주소 (세그먼트:오프셋(Offset) = 세그먼트×16 + 오프셋) |
| 주소 공간 | 1MB (0x00000 ~ 0xFFFFF), A20 게이트로 확장 가능 |
| 보호 기능 | 없음 — 모든 코드가 전체 메모리/I/O 포트 접근 가능 |
| 인터럽트 | IVT(Interrupt Vector Table) at 0x0000 (256 × 4바이트) |
| 커널 사용 | 부트로더 초기 단계, BIOS 서비스 호출, A20 활성화 |
; Real Mode 초기 부팅 코드 개념 예시 (실제 코드: arch/x86/boot/compressed/head_32.S 등)
; 부트로더가 커널 이미지를 로우 메모리에 로드한 뒤 진입점부터 실행됨
; 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 (보호 모드)
Protected Mode는 Intel 80286에서 도입되고 80386에서 완성된 32비트 실행 모드입니다. Real Mode의 한계(1MB 주소, 보호 없음)를 극복하여 4GB 주소 공간, 하드웨어 메모리 보호, 특권 레벨(Ring)을 제공합니다. 리눅스 32비트 커널(i386)은 이 모드에서 동작하며, 64비트 부팅에서도 Long Mode 진입 전 필수 경유 단계입니다.
| 특성 | 설명 |
|---|---|
| 비트 폭 | 32비트 레지스터 (EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP, EIP, EFLAGS) |
| 주소 공간 | 4GB 선형 주소 (PAE 시 물리 64GB), 세그먼테이션 + 페이징 2단계 변환 |
| 보호 기능 | Ring 0~3 특권 레벨, 세그먼트별 접근 권한, 페이지별 R/W + U/S 보호 |
| 핵심 자료구조 | GDT, LDT, IDT, TSS, 페이지 디렉토리/테이블 |
| 진입 조건 | GDT 설정 + CR0.PE=1 + far jump (CS 갱신) |
| 커널 사용 | 32비트 리눅스 커널 전체, 64비트 부팅의 중간 단계, UEFI CSM 호환 |
Protected Mode 진입 상세
Real Mode에서 Protected Mode로 전환하려면 다음 단계를 정확한 순서로 수행해야 합니다. 하나라도 빠지면 트리플 폴트(Triple Fault)로 시스템이 리셋됩니다.
; Protected Mode 진입 전체 흐름 (arch/x86/boot/pmjump.S 기반)
; ① 인터럽트 비활성화 — 모드 전환 중 인터럽트 금지
cli
; ② A20 게이트 활성화 — 1MB 이상 메모리 접근 가능하게
in al, 0x92 ; Fast A20 (System Port)
or al, 2
out 0x92, al
; ③ GDT 로드 — 세그먼트 디스크립터 테이블 설정
lgdt [gdt_ptr] ; GDTR에 GDT base + limit 적재
; ④ CR0.PE = 1 — Protection Enable 비트 설정
mov eax, cr0
or eax, 1 ; PE 비트 (bit 0)
mov cr0, eax ; → 이 순간 Protected Mode 진입!
; ⑤ far jump — CS를 GDT 코드 세그먼트 셀렉터로 갱신
; 이것이 없으면 명령어 프리페치 큐에 Real Mode 코드가 남아 있음
jmp 0x08:pm_entry ; GDT[1] = 코드 세그먼트 (셀렉터 0x08)
; ⑥ 데이터 세그먼트 레지스터 재적재
pm_entry:
.code32
mov ax, 0x10 ; GDT[2] = 데이터 세그먼트 (셀렉터 0x10)
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, stack_top ; 32비트 스택 설정
세그먼테이션 (Segmentation)
Protected Mode의 핵심 메커니즘은 세그먼테이션입니다. 모든 메모리 접근은 세그먼트 셀렉터를 통해 GDT/LDT의 디스크립터를 참조하여 선형 주소(Linear Address)로 변환됩니다.
GDT 엔트리 구조
/* GDT 엔트리 구조 (Intel SDM Vol.3 Section 3.4.5) */
struct gdt_entry {
u16 limit_low; /* 세그먼트 크기 [15:0] (바이트 0-1) */
u16 base_low; /* 베이스 주소 [15:0] (바이트 2-3) */
u8 base_mid; /* 베이스 주소 [23:16] (바이트 4) */
u8 access; /* P | DPL(2) | S | Type(4) (바이트 5) */
u8 granularity; /* G | D/B | L | AVL | Limit[19:16] (바이트 6) */
u8 base_high; /* 베이스 주소 [31:24] (바이트 7) */
} __attribute__((packed));
/* Access 바이트 비트 분해:
* bit 7 : P (Present) — 1이면 세그먼트 유효, 0이면 #NP 예외
* bit 6-5 : DPL (Descriptor Privilege Level) — 00=Ring 0 ~ 11=Ring 3
* bit 4 : S (System) — 1=코드/데이터, 0=시스템(TSS, Call Gate 등)
* bit 3-0 : Type — S=1일 때:
* 코드: bit3=1, bit2=Conforming, bit1=Readable, bit0=Accessed
* 데이터: bit3=0, bit2=Expand-down, bit1=Writable, bit0=Accessed
*/
/* Granularity 바이트 비트 분해:
* bit 7 : G (Granularity) — 0=바이트, 1=4KB 단위 (Limit × 4KB)
* bit 6 : D/B (Default size) — 코드: 0=16bit, 1=32bit / 스택: 0=SP, 1=ESP
* bit 5 : L (Long mode) — 1=64bit 코드 (Long Mode 전용, D=0 필수)
* bit 4 : AVL (Available) — OS가 자유롭게 사용
* bit 3-0 : Limit [19:16] — Limit 상위 4비트
*/
Linux GDT 레이아웃
/* arch/x86/include/asm/segment.h — Linux GDT 레이아웃 */
/* 셀렉터 값 = (인덱스 << 3) | TI | RPL */
#define GDT_ENTRY_KERNEL32_CS 1 /* 0x08: Ring 0 32-bit 코드 */
#define GDT_ENTRY_KERNEL_CS 2 /* 0x10: Ring 0 64-bit 코드 */
#define GDT_ENTRY_KERNEL_DS 3 /* 0x18: Ring 0 데이터 */
#define GDT_ENTRY_DEFAULT_USER32_CS 4 /* 0x23: Ring 3 32-bit 코드 (RPL=3) */
#define GDT_ENTRY_DEFAULT_USER_DS 5 /* 0x2B: Ring 3 데이터 */
#define GDT_ENTRY_DEFAULT_USER_CS 6 /* 0x33: Ring 3 64-bit 코드 */
#define GDT_ENTRY_TSS 8 /* 0x40: TSS 디스크립터 슬롯 (인덱스 9는 미사용) */
#define GDT_ENTRY_LDT 10 /* 0x50: LDT 디스크립터 슬롯 (인덱스 11은 미사용) */
#define GDT_ENTRY_TLS_MIN 12 /* 0x60: TLS 엔트리 시작 */
#define GDT_ENTRY_PER_CPU 15 /* 0x78: per-CPU 데이터 */
/* Linux의 Flat 세그먼트 설정:
* 코드/데이터 세그먼트 모두 Base=0, Limit=0xFFFFF, G=1
* → 선형 주소 0x00000000 ~ 0xFFFFFFFF (4GB) 전체 커버
* → 세그먼테이션을 사실상 무력화하고 페이징에 보호를 위임 */
Protected Mode 페이징 (2-Level)
페이징이 활성화되면(CR0.PG=1) 선형 주소는 페이지 디렉토리(PD)와 페이지 테이블(PT) 2단계를 거쳐 물리 주소로 변환됩니다.
| PTE/PDE 비트 | 의미 | 커널 활용 |
|---|---|---|
| P (bit 0) | Present — 1이면 유효, 0이면 Page Fault (#PF) | 요구 페이징(demand paging), swap 감지 |
| R/W (bit 1) | Read/Write — 0이면 읽기 전용(Read-Only) | CoW(Copy-on-Write), 코드 페이지 보호 |
| U/S (bit 2) | User/Supervisor — 0이면 Ring 0만 접근 | 커널/사용자 공간 분리 |
| A (bit 5) | Accessed — CPU가 접근 시 자동 설정 | LRU 페이지 교체 알고리즘 |
| D (bit 6) | Dirty — 쓰기 발생 시 자동 설정 | 페이지 writeback 결정 |
| PS (bit 7, PDE) | Page Size — 1이면 4MB 대형 페이지 (2MB 대형 페이지는 PAE 모드에서만 존재) | 커널 직접 매핑, 대용량 메모리 |
| G (bit 8) | Global — TLB flush 시 유지 | 커널 매핑 TLB 보존 (CR4.PGE 필요) |
Ring 전환 메커니즘
Protected Mode의 Ring 전환(유저 → 커널, 커널 → 유저)은 인터럽트/예외, SYSENTER/SYSEXIT, Call Gate를 통해 수행됩니다. Ring 전환 시 CPU는 자동으로 TSS에서 새 Ring의 스택(SS:ESP)을 로드합니다.
/* Ring 전환 시 CPU 자동 동작:
*
* Ring 3 → Ring 0 (인터럽트/예외):
* 1. TSS에서 Ring 0의 SS0:ESP0 로드
* 2. 이전 SS:ESP를 새 스택에 push
* 3. EFLAGS push
* 4. CS:EIP push (복귀 주소)
* 5. 에러 코드 push (해당 시)
* 6. IDT에서 새 CS:EIP 로드 → 핸들러 진입
*
* Ring 0 → Ring 3 (IRET):
* 1. 스택에서 EIP, CS, EFLAGS pop
* 2. CS의 RPL이 현재 CPL보다 높으면 (유저 복귀)
* 3. 스택에서 ESP, SS 추가 pop
* 4. Ring 3 코드 실행 재개
*/
/* SYSENTER/SYSEXIT (Pentium II+, 32비트):
* SYSENTER: Ring 3 → Ring 0 (MSR에서 CS/EIP/ESP 로드)
* IA32_SYSENTER_CS (0x174) → 커널 CS
* IA32_SYSENTER_ESP (0x175) → 커널 ESP
* IA32_SYSENTER_EIP (0x176) → 커널 진입점
* SYSEXIT: Ring 0 → Ring 3 (ECX=유저 ESP, EDX=유저 EIP)
*/
wrmsrl(MSR_IA32_SYSENTER_CS, __KERNEL_CS);
wrmsrl(MSR_IA32_SYSENTER_ESP, tss->x86_tss.sp0);
wrmsrl(MSR_IA32_SYSENTER_EIP,
(unsigned long)entry_SYSENTER_32);
Protected Mode의 TSS 역할: Protected Mode에서 TSS는 주로 Ring 전환 시 스택 포인터(SS0:ESP0, SS1:ESP1, SS2:ESP2)를 제공하고, I/O Permission Bitmap으로 사용자 공간 I/O 포트 접근을 제어합니다. x86 하드웨어 태스크 스위칭(TR 셀렉터 변경)은 리눅스에서 사용하지 않으며, 소프트웨어 컨텍스트 스위칭(Context Switching)만 사용합니다.
PAE (Physical Address Extension)
- PAE 모드: 32비트 CPU에서도 4GB 이상의 물리 메모리에 접근할 수 있게 합니다
- CR4.PAE=1로 활성화
- 페이지 테이블 엔트리가 32비트 → 64비트로 확장
- 물리 주소: 36비트 → 최대 64GB
- 최상위 테이블로 PDPT(Page Directory Pointer Table) 추가
- 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 활성화 필수 (전제 조건)
PAE 모드에서는 물리 주소가 36비트로 확장되어 기존 32비트 PDE/PTE로는 전체 프레임 주소를 담을 수 없게 됩니다. 그래서 커널은 Page Directory(PD) 위에 PDPT(Page Directory Pointer Table)라는 한 단계를 더 두며, PDPT의 각 엔트리는 64비트 폭으로 52비트 물리 프레임 주소와 제어 비트를 함께 가집니다. 이때 CR3에는 PD의 주소 대신 PDPT의 물리 주소가 적재됩니다. PDE/PTE 자체가 64비트가 되면서 bit 63을 NX(No-Execute) 비트로 활용할 수 있게 되는데, 이것이 DEP(Data Execution Prevention)/W^X 정책의 하드웨어 기반입니다.
리눅스에서 PAE가 사용되는 곳은 크게 두 곳입니다. 첫째, 32비트(x86) 커널은 CONFIG_X86_PAE=y 빌드에서 4GB 초과 RAM 시스템을 지원하기 위해 PAE를 사용합니다. 둘째, 64비트(x86_64) 부팅 경로에서는 EFER.LME 기록이 CR4.PAE=1을 요구하므로, Real Mode에서 Long Mode로 넘어가는 중간 단계로 PAE를 일시적으로 활성화한 뒤 64비트 PTE 기반 4-level 페이징으로 전환합니다.
Long Mode (IA-32e Mode / 64-bit Mode)
Long Mode는 x86-64 확장에 의해 정의된 64비트 실행 모드로, ISA의 마케팅 명칭으로는 AMD64·Intel EM64T가 있으며 Intel SDM 문서에서는 IA-32e mode라고 표기합니다. 64비트 범용 레지스터, 최대 256TB(48비트) 또는 128PB(57비트) 가상 주소 공간, 4-/5-level 페이징, SYSCALL/SYSRET 고속 시스템 콜, NX(No-Execute) 비트 등을 제공합니다. 현대 리눅스 커널(x86_64)은 이 모드에서 동작합니다.
| 특성 | 설명 |
|---|---|
| 비트 폭 | 64비트 GPR (RAX~R15), 64비트 RIP, 64비트 RFLAGS |
| 가상 주소 | 48비트 (4-level) = 256TB, 57비트 (5-level, LA57) = 128PB |
| 물리 주소 | 최대 52비트 = 4PB (실제 CPU에 따라 40~52비트) |
| 페이징 | 4-level (PML4→PDPT→PD→PT) 필수, 5-level (PML5) 선택 |
| 세그먼테이션 | 사실상 비활성 — CS/SS DPL만 유효, 나머지 base/limit 무시, FS/GS base만 MSR로 설정 |
| 시스템 콜 | SYSCALL/SYSRET (고속, MSR 기반), INT 0x80 호환 가능 |
| 보안 기능 | NX 비트, SMEP, SMAP, PKU, CET, PCID |
| 진입 전제 | Protected Mode + PAE + PML4 + EFER.LME=1 + CR0.PG=1 |
Long Mode 진입 상세
Protected Mode에서 Long Mode로의 전환은 정확한 순서가 필수입니다. 순서가 틀리면 #GP(General Protection Fault) 또는 Triple Fault가 발생합니다.
; arch/x86/boot/compressed/head_64.S — Long Mode 전환 (간략화)
.code32
startup_32:
; ① 페이징이 켜져 있으면 끔 (UEFI에서 올 때)
movl %cr0, %eax
andl $~X86_CR0_PG, %eax
movl %eax, %cr0
; ② PAE 활성화
movl %cr4, %eax
orl $X86_CR4_PAE, %eax
movl %eax, %cr4
; ③ Identity-mapped PML4 페이지 테이블 구성
; 가상주소 = 물리주소 (전환 직후 코드가 같은 주소에서 실행되도록)
leal pgtable(%ebx), %edi
xorl %eax, %eax
movl $(BOOT_INIT_PGT_SIZE/4), %ecx
rep stosl ; 0으로 초기화
; PML4[0] → PDPT
leal pgtable + 0x1007(%ebx), %eax
movl %eax, pgtable + 0(%ebx)
; PDPT[0] → PD (PD 안의 PDE들이 PS=1로 2MB 대형 페이지 매핑)
leal pgtable + 0x2007(%ebx), %eax
movl %eax, pgtable + 0x1000(%ebx)
; ④ CR3에 PML4 물리 주소 적재
leal pgtable(%ebx), %eax
movl %eax, %cr3
; ⑤ EFER.LME = 1 (Long Mode Enable)
movl $MSR_EFER, %ecx
rdmsr
btsl $_EFER_LME, %eax ; bit 8
wrmsr
; ⑥ CR0.PG = 1 → Long Mode 활성화!
; 이 순간 EFER.LMA=1이 자동으로 설정됨
movl %cr0, %eax
orl $X86_CR0_PG, %eax
movl %eax, %cr0
; ⑦ far JMP → 64-bit 코드 세그먼트 (CS.L=1)
; 이 JMP는 CPU의 명령어 디코더를 64-bit 모드로 전환
ljmpl $__KERNEL_CS, $startup_64
; === 여기서부터 64-bit 코드 ===
.code64
startup_64:
; 64-bit 레지스터 사용 가능
xorq %rax, %rax ; 64-bit zero
movq %rax, %ds ; Long Mode에서 DS/ES/SS base=0 강제
movq %rax, %es
movq %rax, %ss
movq %rax, %fs
movq %rax, %gs
4-Level 페이징 (PML4)
Long Mode에서 페이징은 필수이며, 최소 4단계 페이지 테이블을 사용합니다. 각 테이블은 512개 엔트리(각 8바이트)로 구성되어 4KB를 차지합니다.
Protected Mode vs Long Mode 페이징 비교
| 항목 | Protected Mode (non-PAE) | Protected Mode (PAE) | Long Mode (4-level) | Long Mode (5-level) |
|---|---|---|---|---|
| 가상 주소 비트 | 32 | 32 | 48 | 57 |
| 물리 주소 비트 | 32 (4GB) | 36 (64GB) | 최대 52 (4PB) | 최대 52 (4PB) |
| PTE 크기 | 4바이트 | 8바이트 | 8바이트 | 8바이트 |
| 테이블 레벨 | 2 (PD→PT) | 3 (PDPT→PD→PT) | 4 (PML4→PDPT→PD→PT) | 5 (PML5→PML4→...) |
| 엔트리/테이블 | 1024 | 512 (PDPT: 4) | 512 | 512 |
| 기본 페이지 | 4KB | 4KB | 4KB | 4KB |
| 대형 페이지 | 4MB (PSE) | 2MB | 2MB, 1GB | 2MB, 1GB |
| NX 비트 | 없음 | 있음 (bit 63) | 있음 (bit 63) | 있음 (bit 63) |
| CR3 가리킴 | PD | PDPT | PML4 | PML5 |
레지스터 확장
Long Mode에서는 기존 8개 범용 레지스터가 64비트로 확장되고, R8~R15 8개 레지스터가 추가됩니다. REX 프리픽스가 이 확장 레지스터 접근을 가능하게 합니다.
| 32-bit (Protected) | 64-bit (Long) | 추가 레지스터 | 용도 (System V ABI) |
|---|---|---|---|
| EAX → RAX | RAX (64-bit) | R8 | 5번째 함수 인자 |
| EBX → RBX | RBX (callee-saved) | R9 | 6번째 함수 인자 |
| ECX → RCX | RCX (4번째 인자) | R10 | static chain (caller-saved) |
| EDX → RDX | RDX (3번째 인자) | R11 | SYSCALL RFLAGS 저장 |
| ESI → RSI | RSI (2번째 인자) | R12 | callee-saved |
| EDI → RDI | RDI (1번째 인자) | R13 | callee-saved |
| EBP → RBP | RBP (frame pointer) | R14 | callee-saved |
| ESP → RSP | RSP (stack pointer) | R15 | callee-saved |
| EIP → RIP | RIP (64-bit) | RIP-relative 주소 지정 모드 추가 | |
Long Mode에서의 세그먼테이션 변화
Long Mode는 세그먼테이션을 대부분 비활성화합니다. Protected Mode에서 핵심이었던 세그먼트 Base/Limit이 무시됩니다.
| 세그먼트 | Protected Mode | Long Mode (64-bit) |
|---|---|---|
| CS | Base + Limit + DPL + L/D 비트 모두 유효 | DPL, L, D 비트만 유효. Base/Limit 무시. L=1, D=0이어야 64-bit 모드 |
| SS | Base + Limit + DPL 유효 | DPL만 유효 (Ring 전환 시). Base=0, Limit 무시 |
| DS, ES | Base + Limit + 접근 권한 유효 | 완전 무시 — Base=0 강제, Limit 체크 없음 |
| FS, GS | Base + Limit + 접근 권한 유효 | Base만 유효 (MSR로 설정). FS: TLS, GS: per-CPU 데이터 |
/* Long Mode에서 FS/GS Base 설정 (MSR 사용) */
/* 커널 per-CPU 데이터 접근: GS Base */
wrmsrq(MSR_GS_BASE, per_cpu_offset(cpu));
/* 이후 %gs:offset 으로 per-CPU 변수 접근 */
/* 예: movq %gs:current_task, %rax → 현재 task_struct */
/* 유저 TLS(Thread-Local Storage): FS Base */
wrmsrq(MSR_FS_BASE, thread->fsbase);
/* glibc의 __thread 변수는 FS Base 기준으로 접근 */
/* SWAPGS: 시스템 콜 진입 시 GS 교체 */
/* SYSCALL → GS = 유저 값 → SWAPGS → GS = 커널 per-CPU */
/* SYSRET 전 → SWAPGS → GS = 유저 값 복원 */
Identity Mapping의 필요성: 모드 전환(Protected → Long) 직후, CPU는 다음 명령어를 기존 물리 주소에서 실행합니다. 그런데 페이징이 활성화되면 주소 해석이 달라지므로, 전환 코드가 위치한 물리 주소에 대해 가상주소 = 물리주소(identity mapping)를 설정해야 합니다. 이 매핑이 없으면 전환 직후 첫 명령어에서 Page Fault가 발생합니다. 리눅스 커널은 부팅 초기에 임시 identity mapping을 만들고, start_kernel() 이후에 제거합니다.
Long Mode 서브모드
| 서브모드 | CS.L | CS.D | 주소 크기 | 오퍼랜드 크기 | 용도 |
|---|---|---|---|---|---|
| 64-bit Mode | 1 | 0 | 64비트 (기본) | 32비트 (기본), REX.W로 64비트 | 커널 전체, 64비트 유저 프로세스 |
| 호환성 모드(Compatibility Mode) | 0 | 1 | 32비트 | 32비트 | 32비트 유저 프로세스 (ia32 compat) |
| Compatibility Mode (16-bit) | 0 | 0 | 16비트 | 16비트 | 16비트 유저 코드 (vm86 대안, 매우 드묾) |
arch/x86/entry/entry_64_compat.S의
entry_SYSENTER_compat와 entry_SYSCALL_compat가 이 전환을 처리합니다.
Protected Mode vs Long Mode 종합 비교
| 항목 | Protected Mode (32-bit) | Long Mode (64-bit) |
|---|---|---|
| 범용 레지스터 | 8개 × 32비트 (EAX~ESP) | 16개 × 64비트 (RAX~R15) |
| 명령어 포인터 | EIP (32비트) | RIP (64비트), RIP-relative 주소 지정 |
| 가상 주소 공간 | 4GB | 256TB (48-bit) / 128PB (57-bit) |
| 세그먼테이션 | 완전 활성 (Base+Limit+Access) | 사실상 비활성 (FS/GS base만 MSR) |
| 페이징 | 선택 (2-level 또는 PAE 3-level) | 필수 (4-level 또는 5-level) |
| NX 비트 | PAE에서만 가능 | 기본 지원 (EFER.NXE) |
| 시스템 콜 | INT 0x80, SYSENTER/SYSEXIT | SYSCALL/SYSRET (고속, MSR 기반) |
| TSS | 스택 포인터 + I/O bitmap | IST(Interrupt Stack Table) 7개 + I/O bitmap |
| IDT 게이트 | 8바이트 | 16바이트 (64-bit 오프셋, IST 인덱스 추가) |
| GDT 시스템 디스크립터 | 8바이트 | 16바이트 (TSS/LDT는 64-bit base 필요) |
| 함수 호출 규약(Calling Convention) | cdecl (스택 기반 인자 전달) | System V AMD64 ABI (레지스터 6개 → 스택) |
| Red Zone | 없음 | RSP 아래 128바이트 (리프 함수 최적화) |
CPU 모드 전환 주의사항
- GDT/IDT 준비 — Protected/Long Mode 전환 전에 반드시 유효한 GDT를 설정. 잘못된 GDT는 Triple Fault → 리셋
- A20 게이트 — Real→Protected 전환 전 A20 활성화 필수. 비활성 시 1MB 이상 모든 주소가 하위 1MB 영역으로 에일리어싱(Aliasing)됨
- Identity Mapping — 모드 전환 직후 코드가 실행되는 주소에 대해 가상=물리 매핑이 있어야 함. 없으면 즉시 페이지 폴트(Page Fault)
- 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), 쓰기 보호(Write Protection)(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 — 인터럽트 우선순위 마스킹 (TPR, Protected Mode 이후 사용 가능) |
| EFER (MSR) | LME, LMA, SCE, NXE | Long Mode 활성화(LME/LMA), SYSCALL(SCE), NX 비트(NXE) |
Descriptor Table 레지스터 구조
GDTR/IDTR 레지스터는 GDT와 IDT의 위치를 CPU에 알려주는 특수 레지스터입니다.
LGDT/LIDT 명령어로 메모리의 의사 디스크립터(Pseudo-Descriptor)를 읽어 적재하며,
Protected Mode에서는 48비트(6바이트), Long Mode에서는 80비트(10바이트) 구조를 사용합니다.
| 레지스터 | 유형 | 내용 | 적재 명령어 | 저장 명령어 |
|---|---|---|---|---|
GDTR |
시스템 레지스터 | GDT Base + Limit (6/10바이트) | LGDT |
SGDT |
IDTR |
시스템 레지스터 | IDT Base + Limit (6/10바이트) | LIDT |
SIDT |
LDTR |
세그먼트 레지스터 | LDT를 가리키는 GDT 셀렉터 + 캐시된 디스크립터 | LLDT |
SLDT |
TR |
세그먼트 레지스터 | TSS를 가리키는 GDT 셀렉터 + 캐시된 디스크립터 | LTR |
STR |
리눅스 커널은 arch/x86/include/asm/desc.h에서 GDT/IDT 적재를 위한
인라인 함수(Inline Function)를 제공합니다:
/* arch/x86/include/asm/desc.h */
/* LGDT/LIDT 명령어가 받는 6/10바이트 메모리 구조 */
struct desc_ptr {
u16 size; /* 테이블 바이트 크기 - 1 (Limit) */
u64 address; /* 테이블 선형 주소 (Base) */
} __attribute__((packed));
static __always_inline void native_load_gdt(const struct desc_ptr *dtr)
{
asm volatile("lgdt %0" : : "m"(*dtr));
}
static __always_inline void native_load_idt(const struct desc_ptr *dtr)
{
asm volatile("lidt %0" : : "m"(*dtr));
}
/* 사용 예시: 부팅 시 GDT 설정 */
/* arch/x86/kernel/head64.c */
struct desc_ptr gdt_descr = {
.size = GDT_SIZE - 1,
.address = (unsigned long)get_cpu_gdt_rw(0),
};
load_gdt(&gdt_descr);
LGDT/LIDT는 메모리 피연산자 하나만 받으며,
Protected Mode에서는 6바이트(16+32비트), Long Mode에서는 10바이트(16+64비트) 구조를 읽습니다.
리눅스의 struct desc_ptr는 __attribute__((packed))으로 패딩(Padding)을 제거하여
이 요구사항을 충족합니다.
GDTR 적재 자체는 이미 캐시된 세그먼트 디스크립터를 무효화하지 않지만, 새로운 GDT 내용이 다르면 CS를 제외한 세그먼트 레지스터를 명시적으로 재적재해야 합니다.
세그먼트 디스크립터 비트 필드 상세
GDT/LDT의 각 엔트리는 8바이트(64비트) 세그먼트 디스크립터입니다. 비트 배치가 연속적이지 않아 소프트웨어에서 직접 조립할 때 주의가 필요합니다. 인텔 SDM Vol.3 Section 3.4.5의 "Segment Descriptor"를 기준으로 설명합니다. 아래 다이어그램의 필드 폭은 가독성 확보를 위한 개략치이며 실제 비트 비례가 아닙니다.
| Type 값 | 세그먼트 종류 | 접근 속성 |
|---|---|---|
0000 | 데이터 | 읽기 전용 |
0010 | 데이터 | 읽기/쓰기 |
0100 | 데이터 | 읽기 전용, Expand-Down |
0110 | 데이터 | 읽기/쓰기, Expand-Down |
1000 | 코드 | 실행 전용 |
1010 | 코드 | 실행/읽기 |
1100 | 코드 | 실행 전용, Conforming |
1110 | 코드 | 실행/읽기, Conforming |
/* GDT_ENTRY() 매크로 — arch/x86/include/asm/desc_defs.h */
/* base: 32비트 세그먼트 베이스, limit: 20비트 크기, flags: 접근·세분성 바이트 */
#define GDT_ENTRY(flags, base, limit) \
((((base) & _AC(0xff000000,ULL)) << (56-24)) | \
(((flags) & _AC(0x0000f0ff,ULL)) << 40) | \
(((limit) & _AC(0x000f0000,ULL)) << (48-16)) | \
(((base) & _AC(0x00ffffff,ULL)) << 16) | \
(((limit) & _AC(0x0000ffff,ULL))))
/* 사용 예: Ring 0 64-bit 코드 세그먼트 (CS=0x10, Long Mode) */
/* flags=0xa09b: G=1, L=1, P=1, DPL=0, S=1, Type=0xb (코드/실행/읽기) */
GDT_ENTRY(0xa09b, 0, 0xfffff)
L=1, D/B=0 조합이어야 합니다.
또한 Long Mode에서는 CS를 제외한 세그먼트(DS/ES/SS)의 Base와 Limit이 CPU에 의해 무시됩니다(플랫 메모리 모델).
단, FS/GS의 Base는 MSR(0xC0000100/0xC0000101)로 별도 설정하여 per-CPU 포인터나 TLS에 활용합니다.
시스템 세그먼트 디스크립터 (S=0)
GDT 디스크립터의 S(Descriptor Type) 비트가 0이면 시스템 디스크립터입니다.
코드/데이터 세그먼트(S=1)와 달리 TSS, LDT, 각종 Gate를 기술합니다.
Long Mode에서 TSS와 LDT 디스크립터는 128비트(16바이트)로 확장되어
GDT에서 연속된 두 슬롯을 차지합니다. 다이어그램의 필드 폭은 실제 비트 비례가 아닌 개략치입니다.
| Type 값 (4비트, S=0) | 디스크립터 종류 | 설명 |
|---|---|---|
0x1 |
16비트 TSS (Available) | Protected Mode 16비트 태스크 상태 세그먼트 (사용 가능) |
0x2 |
LDT | 지역 디스크립터 테이블 (Long Mode: 128비트 확장) |
0x3 |
16비트 TSS (Busy) | Protected Mode 16비트 태스크 상태 세그먼트 (실행 중) |
0x9 |
64비트 TSS (Available) | Long Mode TSS (사용 가능) — GDT 2슬롯(128비트) |
0xB |
64비트 TSS (Busy) | Long Mode TSS (실행 중) — TR 레지스터가 가리키는 TSS |
0xC |
Call Gate | 특권 레벨 변경을 위한 원거리 호출 게이트 |
0xE |
Interrupt Gate | 인터럽트/예외 핸들러 진입점 (IF 자동 클리어) |
0xF |
Trap Gate | 트랩 핸들러 진입점 (IF 유지) |
리눅스 커널은 arch/x86/include/asm/desc_defs.h에서
시스템 디스크립터를 위한 구조체(Struct)를 정의합니다:
/* arch/x86/include/asm/desc_defs.h */
/* Long Mode TSS/LDT 디스크립터: GDT에서 연속된 2슬롯(16바이트) 차지 */
struct ldttss_desc {
u16 limit0; /* Limit[15:0] */
u16 base0; /* Base[15:0] */
unsigned base1:8, /* Base[23:16] */
type:5, /* Type (S=0 포함된 5비트) */
dpl:2, /* 디스크립터 권한 레벨 */
p:1; /* Present 비트 */
unsigned limit1:4, /* Limit[19:16] */
zero0:3, /* Reserved = 0 */
g:1, /* Granularity (1=4KB 단위) */
base2:8; /* Base[31:24] */
u32 base3; /* Base[63:32] — 두 번째 QWord */
u32 zero1; /* Reserved = 0 */
} __attribute__((packed));
/* TSS 디스크립터 설정 — arch/x86/kernel/cpu/common.c의 cpu_init() */
/* TSS 디스크립터는 cpu_entry_area 내부가 아니라 GDT 슬롯에 기록됩니다 */
set_tss_desc(cpu, &get_cpu_entry_area(cpu)->tss);
IDT 게이트 디스크립터 구조 상세
64비트 Long Mode의 IDT 엔트리는 16바이트(128비트)로, Protected Mode의 8바이트에서 확장되었습니다. 핸들러 오프셋이 64비트로 늘어났으며, IST(Interrupt Stack Table) 필드가 추가되어 중첩 예외 상황에서도 안전한 전용 스택을 사용할 수 있습니다. 다이어그램의 필드 폭은 실제 비트 비례가 아닌 개략치입니다.
| Gate Type | Type 필드 | IF 플래그 | 용도 |
|---|---|---|---|
| Interrupt Gate | 0xE (1110) | 자동 클리어 | 하드웨어 인터럽트, 재진입 방지 |
| Trap Gate | 0xF (1111) | 유지 | 소프트웨어 예외, 디버그 트랩 |
| IST 값 | 할당 스택 | 사용 벡터 (리눅스) |
|---|---|---|
0 | 일반 스택 (RSP0) | 일반 인터럽트/예외 |
1 | IST1 스택 | #DF Double Fault (벡터 8) |
2 | IST2 스택 | NMI (벡터 2) |
3 | IST3 스택 | #DB Debug (벡터 1) |
4 | IST4 스택 | #MC Machine Check (벡터 18) |
/* struct gate_struct — arch/x86/include/asm/desc_defs.h */
struct gate_struct {
u16 offset_low; /* 핸들러 주소 [15:0] */
u16 segment; /* 코드 세그먼트 셀렉터 (예: __KERNEL_CS = 0x10) */
struct idt_bits bits; /* IST[2:0] | 0[4:0] | Type[3:0] | 0 | DPL[1:0] | P */
u16 offset_middle; /* 핸들러 주소 [31:16] */
u32 offset_high; /* 핸들러 주소 [63:32] */
u32 reserved; /* 반드시 0 */
} __attribute__((packed));
/* 인터럽트 게이트 설정 예 (arch/x86/kernel/idt.c) */
set_intr_gate(X86_TRAP_DE, asm_exc_divide_error); /* #DE: DPL=0, IF 클리어 */
SYSG(X86_TRAP_BP, asm_exc_int3), /* early_idts[] 항목: #BP DPL=3 게이트는 SYSG 매크로 테이블로 설치 */
int 0x80(시스템 콜)이나 int3(디버그)처럼 사용자 공간에서 소프트웨어 인터럽트를 발생시키려면
해당 IDT 게이트의 DPL=3이어야 합니다. DPL=0인 게이트에 사용자 공간이 접근하면
#GP(General Protection Fault)가 발생합니다.
현대 리눅스는 SYSCALL 명령어를 선호하므로 int 0x80은 호환성 모드 경로(asm_int80_emulation)에서만 사용됩니다.
CPL/DPL/RPL 특권 레벨 메커니즘
x86은 4개의 특권 레벨(Ring 0~3)을 지원하며, 세그먼트 접근 시 CPU가 자동으로 세 가지 레벨을 비교합니다. 리눅스는 Ring 0(커널)과 Ring 3(사용자)만 사용하며, Ring 1/2는 활용하지 않습니다.
| 레벨 | 위치 | 의미 | 리눅스 값 |
|---|---|---|---|
| CPL | CS 셀렉터 [1:0] | 현재 실행 코드의 특권 수준 | 0 (커널) 또는 3 (유저) |
| DPL | GDT 디스크립터 [46:45] | 세그먼트 접근에 필요한 최소 특권 | 0 (커널 세그먼트), 3 (유저 세그먼트) |
| RPL | 세그먼트 셀렉터 [1:0] | 호출자가 명시한 접근 권한 | 셀렉터 값에 포함 (0x08→0, 0x2B→3) |
; Ring 3 → Ring 0 전환 시 CPU가 자동으로 수행하는 동작
; (예외/인터럽트 발생 시, CALL 게이트 사용 시)
;
; 1. CPL 변경 감지: 새 DPL < 현재 CPL → 스택 전환 수행
; 2. TSS.RSP0에서 Ring 0 커널 스택 포인터 로드
; 3. 현재 컨텍스트를 새 스택에 자동 푸시 (5개 항목):
; [+40]: SS (원래 Ring 3 스택 세그먼트)
; [+32]: RSP (원래 Ring 3 스택 포인터)
; [+24]: RFLAGS
; [+16]: CS (원래 Ring 3 코드 세그먼트)
; [ +8]: RIP (원래 복귀 주소)
; [ 0]: Error Code (예외인 경우에만 푸시)
;
; 복귀: IRET → 저장된 SS/RSP/RFLAGS/CS/RIP 복원
; CPL이 높아지면 자동으로 Ring 3 스택으로 복귀
SYSCALL 명령어는 세그먼트 기반 특권 검사 없이 직접 Ring 0으로 전환하며,
스택 교체를 자동으로 수행하지 않습니다.
따라서 커널 진입 직후 SWAPGS로 GS.base를 교체하여 current_task 등
per-CPU 데이터에 접근합니다.
IRET과 달리 SYSRET은 RFLAGS의 RF(Restartable Flag) 비트를 복원할 수 없고,
TF(Trap Flag)를 그대로 복원하면 SYSRET 직후 다시 트랩이 발생하므로,
커널은 복귀 전 R11의 해당 비트들을 정제한 뒤 SYSRETQ를 실행합니다.
SYSCALL/SYSRET 메커니즘 상세
현대 리눅스 커널은 시스템 콜에 INT 0x80/IRET 대신
SYSCALL/SYSRET 명령어를 사용합니다.
이 명령어들은 세그먼트 디스크립터 검사를 생략하고 MSR에서 직접 CS/SS 셀렉터와
진입점 주소를 읽어 Ring 3 ↔ Ring 0 전환을 수행합니다.
| MSR 주소 | 이름 | 내용 |
|---|---|---|
0xC0000081 |
STAR |
[63:48]: SYSRET CS/SS 셀렉터, [47:32]: SYSCALL CS/SS 셀렉터 기준값 |
0xC0000082 |
LSTAR |
64비트 모드 Ring 0 진입점 주소 (entry_SYSCALL_64) |
0xC0000083 |
CSTAR |
Compatibility Mode 진입점 |
0xC0000084 |
SFMASK |
SYSCALL 실행 시 RFLAGS에서 클리어할 비트 마스크 |
리눅스 커널의 syscall_init()에서 부팅 시 MSR을 설정합니다:
/* arch/x86/kernel/cpu/common.c — 단순화 발췌 */ void syscall_init(void) { /* FRED 활성화 시 SYSCALL/SYSRET MSR 경로는 사용하지 않음 (#UD 발생) */ if (!cpu_feature_enabled(X86_FEATURE_FRED)) idt_syscall_init(); } static inline void idt_syscall_init(void) { /* STAR: [63:48]=SYSRET용 CS 기준(__USER32_CS), [47:32]=SYSCALL용 CS(__KERNEL_CS) */ wrmsrl(MSR_STAR, ((u64)__USER32_CS) << 48 | ((u64)__KERNEL_CS) << 32); /* LSTAR: 64비트 SYSCALL 진입점 */ wrmsrl(MSR_LSTAR, (u64)entry_SYSCALL_64); /* CSTAR: 32비트 Compatibility Mode 진입점 (Intel CPU에서 직접 쓰기는 #UD이므로 wrmsrl_cstar() 경유) */ wrmsrl_cstar((u64)(ia32_enabled() ? entry_SYSCALL_compat : entry_SYSCALL32_ignore)); /* SFMASK: SYSCALL 진입 시 RFLAGS에서 클리어할 비트 (14개 플래그 전체) */ wrmsrl(MSR_SYSCALL_MASK, X86_EFLAGS_CF|X86_EFLAGS_PF|X86_EFLAGS_AF| X86_EFLAGS_ZF|X86_EFLAGS_SF|X86_EFLAGS_TF| X86_EFLAGS_IF|X86_EFLAGS_DF|X86_EFLAGS_OF| X86_EFLAGS_IOPL|X86_EFLAGS_NT|X86_EFLAGS_RF| X86_EFLAGS_AC|X86_EFLAGS_ID); }
RCX에 non-canonical 주소(비트 [63:48]이 모두 동일하지 않은 주소)를 설정하고
SYSCALL을 호출한 뒤, 커널이 그대로 SYSRET을 실행하면
Intel CPU에서 #GP(General Protection Fault)가 커널 권한(Ring 0)으로 발생합니다.
이는 심각한 특권 상승 취약점으로 이어집니다.
리눅스는 entry_64.S에서 SYSRETQ 직전에 RCX canonical 여부를 확인하고,
이상 시 IRET 경로로 우회합니다.
자세한 내용은 커널 취약점을 참조하세요.
TSS (Task State Segment) 구조
Long Mode의 TSS는 하드웨어 멀티태스킹 지원 대신 스택 포인터 저장소로 사용됩니다. CPU는 예외/인터럽트 발생 시 TSS에서 스택 포인터를 가져옵니다. 리눅스는 CPU마다 하나의 TSS를 유지하며, 태스크 전환(context switch)마다 RSP0를 갱신합니다.
/* struct x86_hw_tss — arch/x86/include/asm/processor.h (단순화) */
struct x86_hw_tss {
u32 reserved1;
u64 sp0; /* RSP0: Ring 0 진입 시 커널 스택 최상단 */
u64 sp1; /* RSP1: Ring 1 (리눅스 미사용, 항상 0) */
u64 sp2; /* RSP2: Ring 2 (리눅스 미사용 — SYSCALL 진입 시 사용자 RSP 임시 저장용 스크래치) */
u64 reserved2;
u64 ist[7]; /* IST1~7: NMI, #DF, #MC 등 전용 스택 포인터 */
u32 reserved3;
u32 reserved4;
u16 reserved5;
u16 io_bitmap_base; /* IOPB 오프셋 (0x68 = IOPB 없음) */
} __attribute__((packed));
/* RSP0 갱신 — arch/x86/kernel/process_64.c */
/* __switch_to() 호출 시마다 새 태스크의 커널 스택 최상단을 RSP0에 기록 */
/* this_cpu_write(cpu_tss_rw.x86_tss.sp0, task_top_of_stack(next_p)); */
RSP1은 0이며, RSP2는 SYSCALL 진입 시 사용자 RSP를 임시 저장하는 스크래치 공간으로 재사용됩니다.
RSP0만 실제로 활용되며, 태스크 전환마다 새 태스크의
커널 스택 주소로 갱신됩니다.
IST 스택은 cpu_entry_area에 고정 할당되어, 커널 스택 오버플로(Stack Overflow)우 상황에서도
안전하게 예외를 처리할 수 있습니다.
Long Mode 특화 기능 상세
Long Mode는 단순히 64비트 레지스터와 넓은 주소 공간만 제공하는 것이 아닙니다. 캐노니컬(Canonical) 주소 규칙, REX 프리픽스, FS/GS Base MSR, RIP-relative 주소 지정 등 시스템 소프트웨어가 반드시 이해해야 할 고유 메커니즘을 포함합니다.
| MSR 주소 | 이름 | 용도 | 커널 접근 |
|---|---|---|---|
0xC0000100 | FS.Base | FS 세그먼트 베이스 (사용자 TLS) | arch_prctl(ARCH_SET_FS) |
0xC0000101 | GS.Base | GS 세그먼트 베이스 (커널 per-CPU) | wrmsr(MSR_GS_BASE, ...) |
0xC0000102 | KernelGS.Base | SWAPGS로 교체될 GS.Base 저장소 | SWAPGS 명령어 |
; arch/x86/entry/entry_64.S — 시스템 콜 진입 (Ring 3 → Ring 0, 단순화 발췌)
; SYSCALL 실행 후: GS.base = 사용자 GS 값 (그대로 유지됨)
; per-CPU 데이터 접근을 위해 SWAPGS로 커널 GS.base를 복원해야 함
SYM_CODE_START(entry_SYSCALL_64)
swapgs ; GS.base ↔ KernelGS.base 교체 (진입 시 첫 번째 명령어)
movq %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2) ; 사용자 RSP를 scratch 공간에 임시 저장
; ... 커널 스택 설정, pt_regs 저장 ...
; Ring 0 → Ring 3 복귀 (단순화 발췌 — 실제 코드는 POP_REGS·트램폴린 스택 전환·
; STACKLEAK_ERASE_NOCLOBBER·SWITCH_TO_USER_CR3_STACK 등을 먼저 수행)
SYM_CODE_START(syscall_return_via_sysret)
; ... 위 단계들 완료 후 ...
swapgs ; sysretq 직전의 마지막 단계에서 사용자 GS.base 복원
sysretq ; Ring 3 복귀 (RCX→RIP, R11→RFLAGS)
; RIP-relative 주소 지정 — PIE/KASLR 호환
; Long Mode에서 [rip + disp32] 형식으로 현재 명령어 기준 ±2GB 내 접근
; 어셈블러가 자동으로 다음 명령어 주소와의 차이를 계산
lea rax, [rip + my_symbol] ; RIP-relative LEA: 심볼 주소를 위치 독립적으로 로드
mov rbx, [rip + global_var] ; 전역 변수 접근: 재배치 없이 동작
; 컴파일러: -fPIC 또는 -fPIE 플래그 → 자동으로 RIP-relative 코드 생성
; 커널 KASLR: 임의 주소에 로드되어도 내부 참조는 상대 오프셋으로 유효
| REX 비트 | 효과 | 예시 |
|---|---|---|
W (bit 3) | 오퍼랜드 크기를 64비트로 강제 | REX.W mov → 64비트 이동 |
R (bit 2) | ModRM.reg 필드에 4번째 비트 추가 | r8~r15 (reg 필드 인코딩) |
X (bit 1) | SIB.index 필드에 4번째 비트 추가 | SIB 인덱스 레지스터 r8~r15 |
B (bit 0) | ModRM.rm, SIB.base, opcode reg 확장 | r8~r15 (rm/base 인코딩) |
0x0000800000000000)에 접근하면 즉시 #GP(0)가 발생합니다.
이를 이용해 사용자 공간에서 의도적으로 #GP를 유발하면, 잘못된 SWAPGS
타이밍과 결합하여 커널 GS.base 노출 취약점(Spectre-variant)으로 이어질 수 있습니다.
관련 방어 기법(SWAPGS 배리어)은 커널 취약점 및 보안 패치를 참조하세요.
v6.12~v6.14 최신 변경사항 (Recent Kernel Changes)
커널 v6.12~v6.14(2024년 말~2025년 초)에서 아키텍처 관련 주요 변경사항을 정리합니다. 이 기간에는 PREEMPT_RT 완전 병합, sched_ext, 각 아키텍처의 하드웨어 보안 기능 확장 등 대규모 변경이 이루어졌습니다.
크로스 아키텍처 변경
PREEMPT_RT 완전 메인라인 병합, sched_ext BPF 스케줄러 도입 등 스케줄링 서브시스템의
오랜 숙제가 해결된 시기입니다. NUMA 메모리 코드 범용화처럼 특정 아키텍처에 묶여 있던 코드를
공통 계층으로 올리는 작업도 병행되었습니다.
| 버전 | 변경사항 | 설명 |
|---|---|---|
| v6.12 | PREEMPT_RT 메인라인 병합 |
20년간 out-of-tree로 유지되던 실시간 선점 패치가 메인라인에 완전 병합되었습니다. CONFIG_PREEMPT_RT로 활성화합니다. |
| v6.12 | sched_ext (BPF 확장형 스케줄러) |
BPF 프로그램으로 스케줄러 정책을 동적으로 변경할 수 있는 프레임워크가 EEVDF와 함께 병합되었습니다. |
| v6.12 | numa_memblks 범용화 |
x86 전용이었던 NUMA 메모리 블록 코드가 아키텍처 독립적 코드로 이동했습니다. |
| v6.13 | Lazy preemption 스케줄링 모델 | 선점 가능 시점을 지연하여 불필요한 컨텍스트 스위치를 줄이는 새로운 스케줄링 모델이 도입되었습니다. |
x86_64 변경
Intel FRED(Flexible Return and Event Delivery)과 AMD Bus Lock Detect가 핵심 변경입니다. 인터럽트·예외 전달 경로를 현대화하고, 잠금(Lock) 경합(Contention)으로 인한 성능 저하를 하드웨어 수준에서 감지·제어하는 방향으로 발전하고 있습니다.
| 버전 | 변경사항 | 설명 |
|---|---|---|
| v6.12 | FRED (Flexible Return and Event Delivery) | 기존 IDT 기반 인터럽트/예외 전달을 대체하는 Intel의 새로운 이벤트 전달 메커니즘입니다. FRED 이벤트는 TSS.RSP0 경로의 자동 스택 전환 대신 커널이 각 태스크의 커널 스택 포인터(sp0)를 MSR IA32_FRED_RSP0에 유지하도록 합니다. |
| v6.12 | Shadow Stack 가드 갭(Guard Gap) | CET(Control-flow Enforcement Technology) Shadow Stack의 가드 갭 처리가 범용 미매핑 영역 할당 코드에 통합되었습니다. |
| v6.13 | AMD Bus Lock Detect | 분할 잠금과 유사하게, 버스 전체를 잠그는 명령어를 감지·처벌하는 AMD 하드웨어 기능 지원이 추가되었습니다. |
| v6.14 | TLB 플러싱 확장성 최적화 | 컨텍스트 스위치 중 지연 데이터 구조 업데이트로 TLB 플러싱 오버헤드가 대폭 감소했습니다. 대규모 코어 시스템에서 성능 향상이 두드러집니다. |
| v6.14 | AMD XDNA NPU 드라이버 (amdxdna) |
AMD XDNA 아키텍처 기반 NPU(Neural Processing Unit)를 지원하는 amdxdna 드라이버가 메인라인에 병합되었습니다. CNN·LLM 등 AI 워크로드를 NPU에서 가속할 수 있으며, AMD Ryzen AI 탑재 노트북에서 활용됩니다. |
ARM64 변경
메모리 보호 키(POE), ARM CCA 기밀 컴퓨팅, GCS 제어 흐름 보호 등 하드웨어 보안 기능 확장이 집중되었습니다. 서버·엣지 시장에서 ARM64의 역할이 커지면서 가상화·신뢰 실행 환경(TEE) 기반 강화도 함께 진행됩니다.
| 버전 | 변경사항 | 설명 |
|---|---|---|
| v6.12 | Permission Overlay Extension (POE) | 시스템 콜이나 TLB 무효화(Invalidation) 없이 메모리 보호 키(Memory Protection Keys)를 변경할 수 있는 ARM 확장을 지원합니다. |
| v6.12 | Arm CCA(Confidential Compute Architecture) 게스트 | pKVM(Protected KVM) 기반 기밀 컴퓨팅(Confidential Computing) 게스트 지원이 초기 병합되었습니다. Realm VM 실행을 위한 기반입니다. |
| v6.12 | vDSO getrandom() |
ARM64에서 getrandom()을 커널 진입 없이 vDSO를 통해 호출할 수 있게 되었습니다. |
| v6.13 | GCS (Guarded Control Stack) | 하드웨어 기반 리턴 주소 스택 보호 기능입니다. ROP(Return-Oriented Programming) 공격을 하드웨어 수준에서 차단합니다. 사용자 공간 지원이 추가되었습니다. |
| v6.13 | MTE(Memory Tagging Extension) hugetlb 지원 | MTE 태그 검사가 대형 페이지(hugetlb)에서도 동작하도록 확장되었습니다. |
| v6.13 | SMMUv3 중첩 변환(Nested Translation) | 2단계 중첩 주소 변환을 지원하여, 가상화 환경에서 IOMMU 패스스루(Passthrough) 성능이 개선됩니다. |
RISC-V 변경
IOMMU 지원, 하드웨어 자동 A/D 비트 갱신(Svade/Svadu), 상위 비트 태그 ABI 등 생태계 성숙을 보여주는 변경들입니다. "확장 집합을 조합해 ISA를 구성한다"는 RISC-V 설계 철학에 따라 매 릴리스마다 새로운 표준 확장이 커널 지원 목록에 추가되고 있습니다.
| 버전 | 변경사항 | 설명 |
|---|---|---|
| v6.13 | RISC-V IOMMU 지원 | RISC-V 아키텍처 전용 IOMMU 드라이버가 추가되었습니다. 디바이스 메모리 격리 기반을 마련합니다. |
| v6.13 | Svade/Svadu 확장 | 하드웨어가 페이지 테이블의 Accessed/Dirty 비트를 자동으로 갱신하는 확장입니다. 소프트웨어 A/D 비트 관리 오버헤드를 제거합니다. |
| v6.13 | 사용자 공간 포인터 마스킹(Tagged Address ABI) | ARM64의 TBI(Top Byte Ignore)와 유사하게, 상위 비트를 태그로 사용할 수 있는 ABI가 추가되었습니다. |
| v6.14 | T-Head xtheadvector 확장 | T-Head(RISC-V 벤더)의 벡터 확장 지원이 추가되어, 해당 SoC에서 벡터 연산을 활용할 수 있습니다. |
| v6.14 | SpacemiT K1 SoC 초기 지원 | RVA22 프로파일과 벡터 확장을 지원하는 SpacemiT K1 SoC의 초기 지원이 병합되었습니다. |
| v6.14 | KVM: Svvptc/Zabha/Ziccrse 게스트 확장 | KVM 게스트에서 다양한 RISC-V 확장을 활용할 수 있도록 지원이 확대되었습니다. |
CONFIG_PREEMPT_RT만으로
실시간 커널을 빌드할 수 있게 되었으며, 산업 제어, 로봇 공학, 오디오 등 저지연이 필수적인 영역에서
리눅스의 활용 범위가 크게 넓어졌습니다.
v6.15~v7.2 최신 변경사항 (2025-2026)
2025년 5월(v6.15)부터 2026년 4월(v7.0)에 이르는 기간은 6.x 시리즈에서 7.x 시리즈로 넘어가는 전환기를 포함합니다. 특히 v7.0에서 커널은 7.x 시리즈에 진입했으며, Rust 생태계 확장과 ACPI PRM(Platform Runtime Mechanism) 기반 CXL 주소 변환, 대형 시스템 확장성 개선 같은 대규모 변경이 이어졌습니다.
크로스 아키텍처 변경
이 시기에는 Rust 드라이버·서브시스템 도입 범위가 크게 확대된 것이 크로스 아키텍처 변경 중 가장 두드러진 흐름이었습니다. v7.0에서 커널이 7.x 시리즈에 진입하며, 단일 헤드라인 기능보다 장기 프로젝트들의 수렴이 특징인 국면으로 접어들었습니다.
| 버전 | 변경사항 | 설명 |
|---|---|---|
| v6.16 (2025-07) | CONFIG_X86_NATIVE_CPU |
커널을 -march=native로 빌드하는 옵션이 추가되었습니다. 빌드 호스트와 동일 계열 CPU에서 실행 시 컴파일러 최적화(Compiler Optimization)로 성능을 높일 수 있습니다. |
| v6.18 LTS (2025-11) | Rust Binder 도입 | Android의 IPC 관리자인 Binder의 Rust 버전이 2년간 작업 끝에 병합되었습니다. 현재 kernel.org 공개 기준으로 6.18 LTS의 projected EOL은 2028년 12월입니다. |
| v6.18 LTS | Tyr 드라이버 (Rust) | Arm Mali CSF 기반 GPU를 지원하는 Rust 드라이버가 병합되었습니다. Panthor 드라이버의 Rust 포팅이며, Collabora·Arm·Google 공동 개발입니다. |
| v6.19 (2026-02) | PCIe 링크 암호화 | Confidential VM과의 통신을 가능하게 하는 PCIe 링크 암호화 지원이 메인라인에 병합되었습니다. |
| v7.0 (2026-04-12) | Rust 생태계 확장 | Rust로 작성되는 신규 드라이버·서브시스템이 계속 증가하며, 커널 소스 트리 내 Rust 코드 비중이 한층 커졌습니다. |
| v7.0 | 7.x 시리즈 진입 | v7.0 출시로 커널이 6.x 시리즈에서 7.x 시리즈로 넘어갔습니다. 단일 헤드라인 기능보다 Rust·sched_ext 등 장기 프로젝트의 수렴이 특징입니다. |
x86_64 변경
AMD INVLPGB 브로드캐스트 TLB 무효화와 Intel Diamond Rapids/Nova Lake S 지원처럼, 코어 수가 수백 개에 이르는 대형 서버·CXL 확장 메모리 시스템에서 발생하는 확장성(Scalability) 문제를 해결하는 변경들이 주를 이룹니다.
| 버전 | 변경사항 | 설명 |
|---|---|---|
| v6.15 | AMD INVLPGB — 브로드캐스트 TLB 무효화 | AMD Zen 3 이상 프로세서에서 IPI(Inter-Processor Interrupt) 없이 원격 TLB 항목을 일괄 무효화하는 INVLPGB 명령어 지원이 추가되었습니다. 대규모 NUMA 시스템에서 TLB 샷다운(shootdown) 오버헤드를 크게 줄여 성능을 향상시킵니다. |
| v6.15 | PMU 드라이버 개선 | Intel·AMD 양측 PMU(Performance Monitoring Unit) 드라이버에 다양한 개선이 이루어져 성능 분석 정확도와 커버리지가 높아졌습니다. |
| v6.17 | AMD HFI (Hardware Feedback Interface) | AMD의 Hardware Feedback Interface 지원이 추가되어, CPU가 스케줄러에 성능 관련 피드백 정보를 제공할 수 있게 되었습니다. |
| v6.18 | AMD ABMC (Assignable Bandwidth Monitoring Counters) | AMD EPYC에서 QOS 대역폭(Bandwidth) 카운터를 리소스에 할당할 수 있는 ABMC 지원이 추가되어 리소스 제어(resctrl) 정밀도가 향상되었습니다. |
| v6.18 | AMD CPU 토폴로지 탐지 정리 | x86/cpu 계열 풀 리퀘스트로 AMD CPU 토폴로지 탐지 경로가 대규모 정리·버그 수정되었습니다. |
| v7.0 | CXL + ACPI PRM 주소 변환 (AMD Zen 5) | AMD EPYC 9005(Turin, Zen 5)에서 데뷔한 ACPI Platform Runtime Mechanism(PRM) 기반 CXL 주소 변환 지원이 메인라인에 병합되었습니다. 후속 플랫폼 확장 범위는 실제 플랫폼 지원 문서 기준으로 확인하는 편이 안전합니다. |
| v7.0 | Intel Diamond Rapids / Nova Lake S / Panther Lake | Diamond Rapids, Nova Lake S, Panther Lake 등 차세대 Intel 서버·클라이언트 플랫폼에 대한 지원이 추가되었습니다. |
| v7.1 (2026-06-14) | Intel FRED 기본 활성화 | Intel Panther Lake를 포함한 FRED 지원 CPU에서 확장된 이벤트/리턴 메커니즘(FRED)이 기본적으로 활성화되었습니다. sys_enter/sys_exit 호출 경로 성능 향상이 기대됩니다. 기존 compat 모드는 여전히 syscall/set_fs 경로를 사용하므로, x86_64 네이티브 바이너리에서 효과를 확인해야 합니다. |
ARM64 변경
Lazy Preemption의 ARM64 정식 도입, SME(행렬 연산 가속), MPAM(메모리 QoS(Quality of Service) 분할) 등 클라우드·HPC 서버에서 ARM64의 성능과 자원 격리 능력을 끌어올리는 방향으로 집중되고 있습니다.
| 버전 | 변경사항 | 설명 |
|---|---|---|
| v7.0 | 선점 모델 통합 (full + lazy만 남김) | 최신 아키텍처들(arm64, loongarch, powerpc, riscv, s390, x86)의 선점 모델을 full preemption과 lazy preemption 두 가지로 정리하여 커널 코드를 단순화했습니다. |
| v6.19 | SME (Scalable Matrix Extension) 개선 | SME 전용 시스템에서 ptrace로 스트리밍 모드를 비활성화하는 기능 등이 추가되며 SME 지원이 개선되었습니다. |
| v6.19 | MPAM 드라이버 (Memory Partitioning and Monitoring) | 멀티 사용자 VM이 돌아가는 서버에서 공유 메모리 리소스 분할·모니터링을 위한 MPAM 드라이버가 병합되었습니다. x86 resctrl에 해당하는 ARM64 고급 QoS 기능입니다. |
RISC-V 변경
Zicfiss(Shadow Stack)·Zicfilp(Landing Pad)로 제어 흐름 무결성(Integrity)을 위한 CFI(Control-Flow Integrity) 하드웨어 지원을 완성하고, Tenstorrent AI 가속기 지원을 시작하는 등 RISC-V 생태계가 보안과 AI 영역으로 확장되고 있습니다.
| 버전 | 변경사항 | 설명 |
|---|---|---|
| v6.15 | BFloat16 명령어 지원 | BFloat16(BF16) 부동소수점 명령어 지원이 추가되어 RISC-V의 수치 계산 능력이 확장되었습니다. |
| v6.19 | Zicbop 유저스페이스 노출 + kselftest | 비준된 Zicbop(프리페치 힌트) 확장이 유저스페이스에 노출되고, 관련 kselftest가 추가되었습니다. getauxval(AT_HWCAP)/hwprobe()로 탐지 가능합니다. |
| v6.19 | Tenstorrent Blackhole 초기 지원 | Tenstorrent AI 가속기 Blackhole의 기초 메인라인 지원이 시작되었습니다. |
| v6.13~v7.0 | 사용자 모드 제어 흐름 무결성(CFI) | Shadow Stack/Landing Pad 방식의 사용자 모드 제어 흐름 무결성(CFI) 지원이 v6.13에서 도입되어 v7.0까지 지속적으로 확장되었습니다. x86 CET / ARM64 GCS에 대응하는 RISC-V의 하드웨어 ROP/JOP(Jump-Oriented Programming) 방어 기능입니다. |
LoongArch / 기타 아키텍처
LoongArch(루온아크)은 중국 로손(Loongson)이 개발한 독자 명령어셋으로, x86/ARM/RISC-V와 독립적인 리눅스 메인라인 아키텍처입니다. 이 외에도 SPARC, DEC Alpha 같은 레거시 아키텍처는 신규 기능보다 유지보수·정리 성격의 변경이 주로 병합됩니다.
| 버전 | 변경사항 | 설명 |
|---|---|---|
| v7.0 | Loongson 128비트 원자 cmpxchg | Loongson 프로세서에 128비트 cmpxchg 원자 연산 지원이 추가되어 락프리 자료구조 구현이 용이해졌습니다. |
| v7.0 | SPARC / DEC Alpha 새 코드 | 레거시로 여겨지던 SPARC와 DEC Alpha 아키텍처에 새 코드가 추가되었습니다. 레거시 아키텍처 커뮤니티의 재활성화를 보여줍니다. |
2026년 하드웨어 지형
| 벤더/제품 | 시기 | 주요 특징 |
|---|---|---|
| Intel Panther Lake (Core Ultra 300) | 2026년 상반기 | Intel 18A 공정, Xe3 통합 GPU, P/E코어 하이브리드, FRED 지원(v7.1 커널부터 기본 활성) |
| Intel Arrow Lake Refresh | 2026년 상반기 | Arrow Lake-S 재브랜드, 부분 클럭 상향. Zen 6 대비 중간기 대응 |
| Intel Nova Lake (Core Ultra 400) | 2026년 말 ~ 2027년 | 차세대 통합 P/E 코어 계열로 거론되지만, 정확한 구성과 커널 지원 범위는 공식 공개 자료 기준으로 재확인 필요 |
| AMD Zen 6 | 2026년 하반기 | TSMC 2nm/3nm 공정 예상. 구체적인 구성과 시점은 공식 발표 기준으로 확인 필요 |
| AMD EPYC Zen 5 (9005 시리즈) | 출시 완료 | CXL 주소 변환이 ACPI PRMT로 구현, 커널 v7.0 대응 |
v7.2 최신 변경사항 (2026-08)
v7.2는 2026년 8월 16일 배포된 메이저 릴리스입니다(kernel.org 기준 최신 안정은 v7.2.6, 2026-09-14).
v7.0~v7.1이 Rust 정식 승격과 FRED 기본 활성이라는 개별 테마에 집중했다면,
v7.2는 캐시 인지 스케줄링(Cache-Aware Scheduling), 더 공정한 GPU 스케줄러,
MGLRU 페이지 회수 개선, 스왑 테이블 4단계(Swap Table Phase IV) 등
스케줄러(Scheduler)·메모리 관리(Memory Management)·블록 계층 전반을
다지는 흐름이 특징입니다. 커널은 "단일 헤드라인 기능"보다
여러 장기 프로젝트가 동시에 수렴하는 안정화 국면으로 접어들었습니다.
| 영역 | 변경사항 | 설명 |
|---|---|---|
| 스케줄러 | 캐시 인지 태스크 스케줄링 | 데이터를 공유하는 태스크(예: 같은 프로세스의 스레드들)를 같은 LLC(Last Level Cache) 도메인에 배치해 캐시 바운싱·미스(Miss)를 줄입니다. 캐시 지역성(Cache Locality)을 높여 데이터 접근 효율을 개선합니다. |
| GPU 스케줄러 | Fair(er) DRM GPU 스케줄러 | 기존 FIFO 기반 GPU 잡 스케줄링을 CFS 태스크 스케줄러의 아이디어로 모델링해 개선했습니다. 무거운 GPU 로드와 병행 실행되는 인터랙티브 클라이언트의 공정성(Fairness)·지연 시간이 개선됩니다. |
| 메모리 회수(Reclaim) | MGLRU 회수 루프 개선 | MGLRU의 회수 루프와 더티 쓰기백(Dirty Writeback) 처리를 정리·개선했습니다. 특정 워크로드(MongoDB + YCSB)에서 최대 약 30% 향상과 파일 refault 감소가 확인되었으며, LOC 감소와 예기치 않은 OOM(Out of Memory) 감소가 함께 나타났습니다. 회수 성능은 측정 환경에 따라 달라질 수 있습니다. |
| 스왑(swap) | Swap Table Phase IV | 익명(Anonymous)과 shmem 스왑의 할당·충전을 folio 단위로 통합하고 정적 메타데이터(스태틱 배열·맵)를 제거했습니다. 정적 메타데이터 오버헤드가 크게 줄어들어 스왑 크기 대비 메모리 사용량이 감소합니다. |
| 블록 계층 | dm-inlinecrypt 타겟 | 인라인 블록 디바이스 암호화(Encryption)를 위한 새 dm 타겟이 추가되었습니다. Android의 dm-default-key 기반 작업을 계승하되 패스스루 지원은 제외하고, dm-crypt의 실용적 대체재로 설계되었습니다. |
| 파일시스템 | Btrfs 대형 folio 기본 활성 | v6.17 이후 실험적이던 대형 folio가 기본 활성화되고 2MB까지의 초대형(huge) folio 실험 지원, 순차 쓰기·Direct I/O 성능 개선, GET_CSUMS ioctl이 추가되었습니다. |
| 시스템 콜 | openat(2) 확장 | OPENAT2_REGULAR 플래그(일반 파일만 허용 — FIFO·디바이스 노드 등 리다이렉션 방지)와 O_EMPTYPATH(빈 경로 인식 → LOOKUP_EMPTY로 fd 뒤의 파일 직접 재오픈)가 추가되었습니다. |
| procfs | /proc/filesystems·/proc/interrupts 생성 가속 | libselinux 등이 빈번히 읽는 /proc/filesystems와 /proc/interrupts의 생성 경로가 최적화되어 극단적 사용자(Extreme Users)의 읽기 성능이 개선되었습니다. |
| sched_ext | 서브 스케줄러(Sub-Scheduler) | cgroup별로 서로 다른 sched_ext 스케줄러를 적용하기 위한 서브 스케줄러 인프라가 v7.1부터 병합되기 시작했고, v7.2에는 토폴로지 순서대로 밀집된 CPU ID(cid, Topological CPU IDs) 매핑이 추가되었습니다. |
| 툴체인 | clang 23 요구·strncpy 제거 | 커널 내 strncpy() 사용이 제거되었습니다. Rust 쪽에는 소프트웨어 태그 기반 KASAN과 AutoFDO 지원이 추가되었습니다. |
v7.2 아키텍처별 변경
v7.2는 각 아키텍처에서도 보안·가상화·페이징 기능을 꾸준히 확장했습니다. 이식성이 좋은 RISC-V에선 MMU 무효화 성능을 다듬었고, ARM64에서는 서버급 메모리 QoS·선형 매핑 보안을, x86_64에서는 TLB 제어와 가상화 권한 세분화를 강화했습니다.
| 아키텍처 | 변경사항 | 설명 |
|---|---|---|
| x86_64 | TSC/CX8 미지원 CPU 제거 | TSC(Time Stamp Counter) 없거나 CX8(CMPXCHG8B) 없는 오래된 CPU 지원이 제거되었습니다. 현대 x86 커널이 요구하는 최소 CPU 기준이 올라갔습니다. |
| x86_64 | tlbi= 커맨드 라인 스위치 |
TLB 무효화(TLBI) 동작을 선택하는 tlbi= 부트 파라미터가 추가되어 특정 하드웨어에서 TLB 샷다운(Shootdown) 정책을 조정할 수 있습니다. |
| x86_64 | KVM MBEC/GMET 지원 | 슈퍼바이저/유저 모드 실행 권한 비트를 별도로 제공하여 execute-permission을 세분화하는 KVM MBEC/GMET 지원을 병합했습니다. |
| x86_64 | 런타임 TDX 모듈 업데이트 | 기밀 컴퓨팅(Confidential Computing)용 TDX(TD-Trust Domain Extensions) 모듈을 런타임에 갱신하는 지원이 추가되었습니다. |
| ARM64 | 커널 데이터/BSS 선형 별칭 해제 | 커널 데이터·BSS 영역의 선형 매핑(Linear Alias)을 해제해, 해당 영역을 매핑되지 않은 가상 주소 없이 접근하려는 공격 경로를 줄이는 하드닝(Hardening) 작업이 이루어졌습니다. |
| ARM64 | MPAM v0.1 지원 | 메모리 파티셔닝·모니터링(MPAM, Memory Partitioning And Monitoring) v0.1 아키텍처 버전 지원이 추가되어 서버급 메모리 QoS 분할이 정비되었습니다. |
| ARM64 | Azure Cobalt 100 TLBI 에라타 완화 | Microsoft Azure Cobalt 100 CPU의 TLBI(Translation Lookaside Buffer Invalidate) 에라타를 완화했습니다. 멀티 소켓 서버에서 TLB 일관성 오류 위험을 줄입니다. |
| ARM64 | Apple M3 (t8122) 초기 지원 | 2023년 세대 Apple 노트북 SoC(M3, t8122)가 리버스 엔지니어링으로 초기 커널 지원을 얻었습니다. 5개 랩톱 모델이 대상입니다. |
| RISC-V | 비-리프·범위 무효화(Range Invalidation) | RISC-V MMU에 비-리프(Non-leaf)와 범위(Range) TLB 무효화 기능 지원이 추가되어, 대량 주소 공간을 다루는 시스템에서 TLB 무효화 비용이 줄어듭니다. |
| RISC-V | KVM 스테이지-2 TLB 일괄 플러시(Flush) | KVM 게스트(stage-2) 페이지 테이블 변경 시 TLB 플러시를 일괄(batch) 처리하는 최적화가 도입되었습니다. |
| RISC-V | ARCH_HAS_CC_CAN_LINK | RISC-V에서 커널이 조건부 컴파일·링크를 판단하는 ARCH_HAS_CC_CAN_LINK가 구현되어 빌드 툴체인 지원 판정이 개선되었습니다. |
아키텍처 분석 플레이북
Kexec 시스템
kexec는 실행 중인 커널에서 다른 커널로 재부팅 없이 전환하는 메커니즘입니다.
재부팅 시간을 획기적으로 단축하며, kdump 기반 크래시 덤프(Dump) 수집에도 사용됩니다.
/* include/uapi/linux/kexec.h - kexec 시스템 콜 인터페이스 */
/* kexec_load() 플래그 */
#define KEXEC_ON_CRASH 0x00000001 /* 크래시 덤프 커널 로드 */
#define KEXEC_PRESERVE_CONTEXT 0x00000002 /* 레지스터 컨텍스트 보존 */
#define KEXEC_UPDATE_ELFCOREHDR 0x00000004 /* ELF core 헤더 갱신 */
#define KEXEC_ARCH_MASK 0xffff0000 /* 아키텍처 마스크 */
/* kexec_file_load() 플래그 */
#define KEXEC_FILE_UNLOAD 0x00000001 /* 로드된 이미지 언로드 */
#define KEXEC_FILE_ON_CRASH 0x00000002 /* kdump 이미지에 속함 */
#define KEXEC_FILE_NO_INITRAMFS 0x00000004 /* initramfs 미로드 */
#define KEXEC_FILE_FORCE_DTB 0x00000020 /* 현재 DTB 인계 강제 */
struct kexec_segment {
const void *buf; /* 사용자 버퍼 주소 */
size_t bufsz; /* 버퍼 크기 */
const void *mem; /* 대상 물리 주소 */
size_t memsz; /* 대상 메모리 영역 크기 */
};
/* 한 번에 전달 가능한 세그먼트 최대 개수 */
#define KEXEC_SEGMENT_MAX 16
kexec -l vmlinuz --initrd=initrd.img --append="..."로 새 커널 로드 후
kexec -e로 실행합니다. 이 기능은 펌웨어 재초기화 없이 커널 전환하므로
서버 재부팅 시 수십 초를 절약할 수 있습니다.
크래시 덤프 (kdump)
커널 패닉(Kernel Panic) 시 메모리 덤프를 수집하는 메커니즘입니다. 두 번째 커널(kdump 커널)이 패닉을 트리거하고 첫 번째 커널의 메모리를 덤프합니다.
| 컴포넌트 | 역할 |
|---|---|
kdump 커널 | 패닉 발생 시 실행되는 크래시 커널 |
kexec | 패닉 시 크래시 커널로 전환 |
makedumpfile | 메모리 덤프에서 불필요 페이지 필터링 |
crash | 크래시 덤프 분석 도구 |
# /etc/kdump.conf (kdump 설정)
path /var/crash
core_collector makedumpfile --compressed
# 크래시 커널용 메모리는 커널 부트 파라미터로 예약 (예: GRUB_CMDLINE_LINUX)
# crashkernel=256M
# 수집된 vmcore 분석
crash vmlinuz-$(uname -r) /var/crash/$(date +%Y%m%d%H%M)/vmcore
ACPI (Advanced Configuration and Power Interface)
ACPI는 x86/ARM64 서버와 데스크탑에서 하드웨어 구성과 전원 관리를 담당하는 표준 인터페이스입니다. DSDT(Differentiated System Description Table)와 SSDT(Secondary System Description Table)로 테이블이 제공됩니다.
커널 초기화 단계에서 drivers/acpi/core.c의 acpi_core_init()이 다음 순서로 동작합니다:
acpi_table_init()이 펌웨어에서 RSDP(Root Service Description Pointer)를 찾아 XSDT/RSDT 루트 테이블 목록을 읽고,
acns_init()이 DSDT/SSDT로부터 네임스페이스(Namespace)를 파싱하며,
acpi_hw_initialize()가 ACPI 하드웨어 인터페이스를 초기화하고,
마지막으로 acpi_enable()가 각 장치의 _INI 메서드를 실행해 서브시스템을 활성화합니다.
# 펌웨어에서 ACPI 테이블 덤프
sudo acpidump -o acpi.dat
# 개별 테이블(dsdt.dat, apic.dat 등) 추출
sudo acpixtract -a acpi.dat
# DSDT를 ASL 소스로 디컴파일하여 점검
iasl -d dsdt.dat
Device Tree Binding
Device Tree는 하드웨어 구성 정보를 구조화된 DTS로 기술합니다.
각 디바이스 드라이버는 자신의 compatible 문자열 매칭 테이블(of_device_id)을 선언하고,
커널 초기화 시 DT 노드 순회 과정에서 해당 테이블과 일치하는 노드에 바인딩됩니다.
/* 플랫폼 드라이버의 OF 매칭 테이블 선언 예시 */
static const struct of_device_id uart_of_match[] = {
{ .compatible = "arm,pl011" },
{ }
};
MODULE_DEVICE_TABLE(of, uart_of_match);
/* IRQ 칩 드라이버는 IRQCHIP_DECLARE 매크로로 직접 등록 (drivers/irqchip/irq-gic.c) */
IRQCHIP_DECLARE(gic_500, "arm,gic-500", gic_of_init);
IRQCHIP_DECLARE(gic_400, "arm,cortex-a15-gic", gic_of_init);
IRQCHIP_DECLARE(gic_300, "arm,cortex-a7-gic", gic_of_init);
IRQCHIP_DECLARE(gic_200, "arm,cortex-a9-gic", gic_of_init);
아키텍처 문서를 읽을 때는 개념 암기보다 "실제 커널 코드에서 어디서 소비되는지"를 함께 확인해야 이해가 빠르게 고정됩니다. 아래 절차는 x86_64, ARM64, RISC-V를 공통 틀로 비교할 때 유용합니다.
| 분석 축 | 핵심 질문 | 확인 위치 |
|---|---|---|
| 권한 전환 | 사용자→커널 진입 경로가 무엇인가? | arch/*/entry/, syscall entry 코드 |
| 메모리 모델 | 페이지 테이블 구성과 주소 변환 흐름은? | arch/*/mm/, page table 매크로 |
| 인터럽트 구조 | 예외/IRQ 디스패치(Dispatch) 체인이 어떻게 구성되는가? | arch/*/kernel/irq*, trap/exception 핸들러 |
| 부팅 초기화 | early init에서 공통 init로 넘어가는 경계는? | head*.S, start_kernel() 이전 코드 |
# 아키텍처별 엔트리 코드 빠른 점검
git grep -n "SYSCALL\|el0_svc\|do_trap" -- arch/x86 arch/arm64 arch/riscv
# 페이지 테이블 핵심 경로 확인
git grep -n "pgd\|pud\|pmd\|pte" -- arch/*/include/asm arch/*/mm
# 부팅 초기 코드 진입점 확인
git grep -n "start_kernel\|head_.*\.S" -- arch/* init/main.c
참고자료
공식 커널 문서 (Official Kernel Documentation)
- 커널 개발 프로세스 개요 — How the development process works (kernel.org)
- x86 아키텍처 공식 문서 — x86-specific Documentation (kernel.org)
- ARM64 아키텍처 공식 문서 — ARM64 Architecture (kernel.org)
- RISC-V 아키텍처 공식 문서 — RISC-V Architecture (kernel.org)
- 커널 코어 API 문서 — Core API Documentation (kernel.org)
- 커널 부트 파라미터 목록 — The kernel's command-line parameters (kernel.org)
- 커널 빌드 시스템 문서 — Kernel Build System (kernel.org)
- 실시간 선점 문서 — Real-time preemption / PREEMPT_RT 내부 구조 (docs.kernel.org)
- ARM64 GCS 사용자 공간 인터페이스 — Guarded Control Stack support for AArch64 Linux (kernel.org)
- 메모리 관리 관리자 가이드 — Memory Management admin guide (kernel.org)
- 스케줄러 문서 — Scheduler documentation (CFS/EEVDF/sched_ext) (kernel.org)
CPU 아키텍처 사양서 (Architecture Specifications)
- Intel® 64 and IA-32 아키텍처 SDM — Software Developer's Manual (Vol. 1-4, Optimization Manual) (intel.com)
- AMD64 APM — Architecture Programmer's Manual (Vol. 1-5) 및 기술 문서 포털 (docs.amd.com)
- ARM ARM — Architecture Reference Manual for A-profile (DDI 0487, ARMv8-A/ARMv9-A) (developer.arm.com)
- RISC-V 사양서 — Unprivileged ISA (Vol. I) / Privileged ISA (Vol. II) / 표준 확장 (riscv.org)
- RISC-V SBI 사양서 — Supervisor Binary Interface Specification (riscv-non-isa)
- Devicetree 사양서 — Device Tree Specification 및 바인딩 규칙 (devicetree.org)
- UEFI / ACPI 사양서 — UEFI Forum 공식 스펙 (ACPI 6.6, UEFI 2.x 포함) (uefi.org)
- PCI Express Base Specification — PCIe 구성 공간, MSI/MSI-X, AER, SR-IOV (pcisig.com)
- IOMMU 문서 — Intel VT-d / AMD IOMMU / ARM SMMU 커널 인터페이스 (kernel.org)
커널 소스 및 탐색 (Source Browsing)
- 공식 커널 Git 저장소 — Linus Torvalds' linux.git (git.kernel.org)
- 아키텍처별 커널 소스 — arch/ 디렉터리 (Bootlin Elixir)
- 커널 초기화 진입점 소스 — init/main.c (Bootlin Elixir)
- x86_64 진입 어셈블리 — arch/x86/kernel/head_64.S (Bootlin Elixir)
- ARM64 진입 어셈블리 — arch/arm64/kernel/head.S (Bootlin Elixir)
- RISC-V 진입 어셈블리 — arch/riscv/kernel/head.S (Bootlin Elixir)
- 커널 릴리스 목록 — The Linux Kernel Archives Releases (kernel.org)
릴리스 노트 및 최신 변경사항 (Release Notes)
- Linux 7.0 커널 릴리스 해설 — The 7.0 kernel has been released (LWN.net, 2026-04)
- Linux 6.17 커널 릴리스 해설 — The 6.17 kernel has been released (LWN.net, 2025-09)
- Linux 6.12 릴리스 요약 — KernelNewbies (PREEMPT_RT 메인라인 병합, sched_ext, FRED)
- Linux 6.13 릴리스 요약 — KernelNewbies (GCS, Lazy preemption, RISC-V IOMMU)
- Linux 6.14 릴리스 요약 — KernelNewbies (TLB 최적화, SpacemiT K1, amdxdna NPU)
- Linux 6.15 릴리스 요약 — KernelNewbies (AMD INVLPGB 브로드캐스트 TLB 무효화, VFS 개선)
- Linux 6.16 릴리스 요약 — KernelNewbies (CONFIG_X86_NATIVE_CPU, RISC-V Zicbop 초기 지원)
- Linux 6.17 릴리스 요약 — KernelNewbies (Intel Xe3, CPU 버그 완화 선택 인터페이스)
심층 해설 및 분석 기사 (In-depth Articles)
- 커널 초기화 순서 해설 — An introduction to the kernel initialization (LWN.net)
- 커널 소스 구조 개요 — A guide to the Kernel Development Process (LWN.net)
- LWN 커널 인덱스 — LWN.net Kernel Index (주제별 커널 기사 분류)
- Linux 6.12-rc1 분석 — PREEMPT_RT 메인라인·sched_ext 병합 (LWN.net)
- Linux 6.13 ARM64 기능 분석 — GCS·Arm CCA 보호 VM (Phoronix)
- Linux 7.0 출시 분석 — 7.x 시리즈 진입 (Phoronix)
서적 (Books)
- Robert Love — Linux Kernel Development, 3rd Edition, Addison-Wesley (커널 전반 구조와 서브시스템 해설)
- Daniel P. Bovet, Marco Cesati — Understanding the Linux Kernel, 3rd Edition, O'Reilly (x86 중심 커널 내부 구조 상세)
- Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman — Linux Device Drivers, 3rd Edition, O'Reilly — 무료 온라인 전문 (LWN.net)
- Wolfgang Mauerer — Professional Linux Kernel Architecture, Wrox Press (커널 2.6 구조 심층 분석, 개념 참조용)
- Greg Kroah-Hartman — Linux Kernel in a Nutshell, O'Reilly — 무료 온라인 전문 (kroah.com)
- Mel Gorman — Understanding the Linux Virtual Memory Manager, Bruce Perens Open Source Series (메모리 관리 서브시스템 심층)
- Andrea Di Vitto — Linux Kernel Programming / Linux Kernel Programming Part 2, Packt (최신 커널 기반 실습 및 모듈·드라이버 개발)