커널 아키텍처 (Kernel Architecture)
리눅스 커널 아키텍처 심층 분석. x86_64, ARM64, RISC-V 아키텍처별 부팅 과정(Boot Process), 주소 공간(Address Space), 특권 레벨, 커널 소스 트리 구조를 다룹니다.
일상 비유 ② 특권 레벨 관점: 커널 아키텍처는 층별 출입 카드 시스템이 있는 건물과 같습니다. 일반 직원(사용자 애플리케이션)은 자기 층(User Space)만 접근할 수 있고, 건물 관리 시스템(커널)만 전체 층(Kernel Space)과 기계실(하드웨어)에 접근할 수 있습니다. x86_64·ARM64·RISC-V는 각각 이 출입 권한 체계를 다른 방식으로 구현한 것입니다.
핵심 요약
- 특권 레벨 — 커널(Ring 0 / EL1 / S-mode)과 사용자 앱(Ring 3 / EL0 / U-mode)의 권한 경계를 이해합니다. 이 경계가 OS 보안과 안정성의 뼈대입니다.
- 주소 공간 분리 — 가상 주소(Virtual Address) 공간이 커널 영역과 사용자 영역으로 나뉘는 구조를 파악합니다. 같은 가상 주소라도 페이지 테이블이 다르면 전혀 다른 물리 주소를 가리킵니다.
- ISA 설계 철학 차이 — x86_64(CISC·강한 메모리 순서), ARM64(RISC·약한 메모리 순서·TrustZone), RISC-V(오픈 모듈형 ISA)의 핵심 차이를 구분합니다.
- 부팅 단계 분리 — 펌웨어(Firmware), 부트로더(Bootloader), 커널 초기화 경계를 구분합니다.
- 하드웨어 기술 정보 — ACPI/DT 등 기술 정보가 어디서 소비되는지 확인합니다.
- 신뢰 체인(Chain of Trust) — Secure Boot 등 검증 체인을 흐름으로 이해합니다.
- 실패 지점 식별 — 부팅 로그에서 단계별 실패 단서를 빠르게 찾습니다.
단계별 이해
- 커널/사용자 공간 경계 파악
코드가 어느 공간에서 실행되는지, 특권 레벨 전환(시스템 콜·인터럽트)이 언제 발생하는지 이해합니다. - 대상 아키텍처의 특권 모델 확인
x86_64(Ring), ARM64(EL), RISC-V(M/S/U-mode) 중 어느 것인지 파악하고, 커널이 어느 레벨에서 실행되는지 확인합니다. - 부팅 단계 식별
현재 이슈가 펌웨어·부트로더·커널 초기화 중 어느 단계에서 발생하는지 먼저 고정합니다. - 전환 경계 검증
단계 간 인자 전달과 상태 인계(부팅 파라미터, 페이지 테이블 활성화 시점 등)를 추적합니다. - 플랫폼별 재검증
메모리 순서 모델, IOMMU, 인터럽트 컨트롤러 등 플랫폼 의존 요소가 다른 하드웨어 조건에서도 올바르게 동작하는지 확인합니다.
x86_64, ARM64, RISC-V 아키텍처별 커널 구조, 부팅 과정, 주소 공간 레이아웃을 상세히 다룹니다.
왜 커널 아키텍처를 알아야 할까요? 같은 C 코드라도 x86_64에서 문제없이 동작하던 코드가 ARM64에서 데이터 경쟁 버그로 이어지거나, RISC-V에서는 존재하지 않는 확장 명령어에 의존해 빌드 오류가 발생할 수 있습니다. 커널 드라이버·서브시스템 코드를 이식하거나, 부팅 실패를 디버깅하거나, 특권 레벨 경계를 넘는 보안 취약점을 분석할 때 아키텍처 지식이 없으면 근본 원인에 도달하기 어렵습니다. 이 문서는 세 아키텍처 각각의 특권 모델·메모리 주소 공간·부팅 경로·레지스터 체계를 커널 코드와 연결하여 설명합니다.
리눅스 커널 개요 (Linux Kernel Overview)
리눅스 커널은 1991년 Linus Torvalds가 처음 공개한 이래, 현재 세계에서 가장 널리 사용되는 운영체제 커널입니다. 서버, 데스크탑, 임베디드 기기, 스마트폰(Android), 슈퍼컴퓨터에 이르기까지 거의 모든 컴퓨팅 영역에서 동작합니다. 커널은 하드웨어와 사용자 공간(user space) 사이에서 추상화 계층 역할을 하며, 다음과 같은 핵심 기능을 담당합니다:
- 프로세스(Process) 관리 (Process Management) - 프로세스 생성, 스케줄링, 종료, 시그널(Signal) 처리
- 메모리 관리 (Memory Management) - 가상 메모리(Virtual Memory), 페이지 테이블(Page Table), 물리 메모리(Physical Memory) 할당
- 파일시스템 (Filesystem) - VFS를 통한 다양한 파일시스템 지원
- 디바이스 드라이버 (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가 표준이지만, 레거시 BIOS도 여전히 지원됩니다.
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)와 페이지 테이블 포맷이 다릅니다. 아래 다이어그램은 x86_64, ARM64, RISC-V의 변환 경로를 한 화면에서 비교합니다.
세그먼테이션과 페이징 (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)이라는 개념이 추가됩니다.
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 |
0x0000013B~0x0000013D | PMC0~2 | 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, RDI, RDX, RSI, 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 %rsp, %gs:thread_struct.sp0 /* 커널 스택 설정 */
mov %rsp, %rsp
test $3, %rsp /* Ring 3에서 호출? */
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(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의 인터럽트 처리는 IDT(Interrupt Descriptor Table)과 LAPIC(Local APIC)가 담당합니다. 예외, IRQ, NMI 모두 IDT 게이트를 통해 처리됩니다.
| 게이트 유형 | 선택자 | 용도 |
|---|---|---|
| Task Gate | 0x5 | 하드웨어 태스크 전환 (거의 미사용) |
| Interrupt Gate | 0x6 | 예외/인터럽트, IF 자동 Clear |
| Trap Gate | 0x7 | 예외만 사용, IF 유지 |
/* arch/x86/include/asm/desc.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 0xE0 /* Local APIC */
#define GLOBAL_INTR_VECTORS 224 /* IRQ0~15 + IOAPIC */
VMX (Virtualization) Internals
VMX는 Intel의 하드웨어 가상화 확장입니다. VMX root mode(Ring -1)와 VMX non-root mode(게스트)를 지원합니다.
/* arch/x86/kvm/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 0x00001602
#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-v2.c |
| GICv3 | Redistributor, LPI, MSI 지원, 480+ CPUs | irq-gic-v3.c |
| GICv4 | 가상화 직렬화(Serialization), vPE table | irq-gic-v4.c |
| GICv4.1 | Extended LPI range, hierarchical cache | irq-gic-v4.1.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 */
/* Each SPI routing to target redistributors */
static int gic_set_affinity(struct irq_data *d, const cpumask *mask, bool force)
{
void __iomem *reg = gic_dist_base(d) + GIC_DIST_TARGET + irq(d);
u32 target = cpu_logical_map(cpumask_first(mask));
writel_relaxed(target, reg);
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);
/* Hyp 레지스터에 2단계 테이블 베이스 설정 */
write_sysreg(pgd, vtcr_el2);
write_sysreg(pgd, vttbr_el2);
return 0;
}
ARM64 보안 확장 (PAC, BTI, MTE, GCS)
ARM64는 다양한 하드웨어 기반 보안 확장을 제공합니다.
| 확장 | 설명 | 커널 지원 |
|---|---|---|
| PAC (Pointer Authentication) | 포인터 인증 코드 (PAC), 반환 주소 무결성(Integrity) | CONFIG_ARM64_POINTER_AUTH |
| BTI (Branch Target Identification) | indirect branch 보호 | 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 기반 간접 호출 검증 | 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 target */
bti "j" /* Jump-friendly target */
bti "i" /* Indirect branch target */
bti "c,j" /* Both */
/* MTE (Memory Tagging) */
irg x0, x1 /* Allocate random tag in x0, base in x1 */
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를 사용하여 하드웨어 구성 정보를 커널에 전달합니다. 부팅 프로토콜은 커널 이미지의 시작점에 명시된 규약을 따릅니다.
MMIO와 Device Tree
ARM 시스템에서 하드웨어 레지스터에 접근하는 기본 방식은 MMIO(Memory-Mapped I/O)입니다.
x86의 Port I/O(in/out 명령어)와 달리, ARM은 메모리 주소에 하드웨어 레지스터를 매핑하고
일반 메모리 접근 명령어(LDR/STR)로 제어합니다.
Device Tree(DT)는 하드웨어 구성을 기술하는 데이터 구조입니다. DTS(Device Tree Source) 파일로 작성하고, DTC(Device Tree Compiler)로 DTB(Device Tree Blob) 바이너리로 컴파일합니다. 커널은 부팅 시 DTB를 파싱하여 하드웨어 정보를 인식합니다.
/* 간단한 Device Tree 예시 (arch/arm64/boot/dts/example.dts) */
/dts-v1/;
/ {
model = "Example ARM64 Board";
compatible = "vendor,example-board";
#address-cells = <2>;
#size-cells = <2>;
memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x40000000>; /* 1GB @ 0x80000000 */
};
uart0: serial@9000000 {
compatible = "arm,pl011", "arm,primecell";
reg = <0x0 0x09000000 0x0 0x1000>; /* MMIO 영역 */
interrupts = <0 1 4>; /* GIC SPI #1, level */
clock-names = "uartclk", "apb_pclk";
};
gic: interrupt-controller@8000000 {
compatible = "arm,gic-v3";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x0 0x08000000 0x0 0x10000>, /* GICD */
<0x0 0x080A0000 0x0 0xF60000>; /* GICR */
};
};
arch/arm64/boot/dts/ 디렉토리에 위치하며,
제조사별로 하위 디렉토리가 구성됩니다. make dtbs 명령으로 모든 DTB 파일을 빌드할 수 있습니다.
dtc -I dtb -O dts 명령으로 DTB를 다시 DTS로 역컴파일할 수도 있습니다.
Device Tree에 대한 자세한 내용은 Device Tree 문서를 참고하세요.
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 |
/* RISC-V CSR 접근 예시 */
/* sstatus 읽기 */
csrr t0, sstatus
/* sstatus의 SIE 비트(Set Interrupt Enable) */
csrs sstatus, 0x2
/* sstatus의 SIE 비트 클리어 */
csrc sstatus, 0x2
/* sbadir 설정 (Supervisor Binary Interface) */
csrw sbadir, t0
/* satp (주소 변환 테이블) 설정 - Sv48의 경우 */
/* PPN[43:0] = 페이지 테이블 물리 프레임 번호 */
csrw satp, t0
RISC-V 페이지 테이블 (Sv39/Sv48/Sv57)
RISC-V는 페이지 테이블 체계를 ISA에서 명시하지 않고 Sv39, Sv48, Sv57 등의 "명명 규약"으로 정의합니다. 리눅스는 Sv48 (4-level)을 주로 사용합니다.
- Sv39: 3-level, 512GB 주소 공간 ( Rendem 1GB, 2MB, 4KB)
- Sv48: 4-level, 256TB 주소 공간, 가장 일반적
- Sv57: 5-level, 128PB 주소 공간 ( v6.13, 일부 SoC)
/* 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) /* U=0: Supervisor, U=1: User */
#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 [47:39] → PML4[511:0] → PDPT[511:0] → PD[511:0] → PT[511:0] → 4KB page */
CLINT와 PLIC (인터럽트 컨트롤러)
RISC-V의 인터럽트는 두 가지 유형으로 나뉩니다:
- CLINT (Core Local Interrupt): Software interrupt (시계열Timer,IPI), 각 코어별
- PLIC (Platform-Level Interrupt Controller): 외부 디바이스 IRQ, 전역
/* drivers/irqchip/irq-sifive-plic.c - PLIC */
/* PLIC IRQ 소스 */
#define PLIC_ENABLE_OFFSET 0x1000
#define PLIC_THRESHOLD_OFFSET 0x200000
#define PLIC_CLAIM_OFFSET 0x200004
/* IRQ 소스 설정 예시 */
static void plic_toggle(struct irq_data *d, unsigned enable)
{
u32 mask = 1UL << (irq(d) % 32);
void __iomem *reg = plic_regs + PLIC_ENABLE_OFFSET +
(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는 다양한 확장을 정의합니다. 표준 확장 목록:
| 확장 ID | 이름 | 기능 |
|---|---|---|
0x10 | base | 기본 SBI (getchar, putchar) |
0x54494D45 | time | 타이머 설정 (gettimeofday) |
0x735049 | ipi | Inter-Processor Interrupt |
0x52464E43 | rfence | Remote fence (TLB flush) |
0x48534D | hsm | Hart State Management (부팅/종료) |
0x505343 | psc | Performance Monitoring Counter |
0x444D4E | dm | Debug Channel (디버그) |
0x534353 | scs | Guest Interrupt Controller |
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와 유사하게 여러 펌웨어 단계를 거칩니다. 현재 가장 일반적인 조합은 ZSBL → OpenSBI → U-Boot → Linux입니다.
특권 레벨 비교 (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/ 디렉토리가 전체 소스의 약 60% 이상을 차지합니다.
커널의 핵심 로직은 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 |
|---|---|---|---|
| 특권 레벨 | Ring 0-3 (+ VMX) | EL0-EL3 | U/S/M mode |
| 커널 실행 레벨 | Ring 0 | EL1 (VHE: EL2) | S-mode |
| 시스템 콜 방식 | SYSCALL/SYSRET | SVC 명령어 | ECALL 명령어 |
| 부팅 펌웨어 | BIOS/UEFI | BootROM + TF-A | ZSBL + OpenSBI |
| HW 정보 전달 | ACPI/E820 | Device Tree / ACPI | Device Tree |
| 주소 공간 | 48/57-bit VA | 48/52-bit VA | Sv39/Sv48/Sv57 |
| 페이지 크기 | 4KB (기본) | 4KB / 16KB / 64KB | 4KB (기본) |
| I/O 방식 | Port I/O + MMIO | MMIO | MMIO |
| 인터럽트 컨트롤러(Interrupt Controller) | APIC (LAPIC + I/O APIC) | GIC (v2/v3/v4) | PLIC / APLIC+IMSIC |
| 가상화 | VT-x / AMD-V | EL2 + VHE | H-extension |
| 하드웨어 보안 | CET Shadow Stack, SMEP/SMAP | MTE, PAC, BTI, GCS (v6.13) | PMP, Svade/Svadu (v6.13) |
아키텍처별 성능 최적화 (Architecture-Specific Performance Optimization)
각 아키텍처는 고유한 성능 특성과 최적화 포인트를 가지고 있습니다. 커널 개발자는 타겟 아키텍처의 특성을 이해하고 이에 맞는 최적화를 적용해야 합니다.
x86_64 최적화 포인트
x86_64는 강력한 OoOE(비순서 실행)와 넓은 SIMD 레지스터를 갖추고 있지만, 그 복잡성만큼
잘못된 사용이 성능을 오히려 깎는 경우도 많습니다. 대표적인 함정은 캐시 라인 경계를 무시한
데이터 배치(False Sharing)와, likely()/unlikely()
없이 작성된 분기 코드입니다. 아래 기법들은 "기본값을 지키면 손해 없다"는 관점에서
핵심만 정리한 것입니다.
| 최적화 영역 | 기법 | 설명 |
|---|---|---|
| 캐시 라인(Cache Line) | 64바이트 정렬 | 핫 패스 데이터는 __cacheline_aligned 사용하여 false sharing 방지 |
| 분기 예측(Branch Prediction) | likely/unlikely | likely(), unlikely() 매크로(Macro)로 힌트 제공 |
| TLB | Huge Pages | 2MB/1GB huge pages 사용으로 TLB miss 감소 (CONFIG_HUGETLBFS) |
| NUMA | 로컬 메모리 할당 | numa_node_id()로 현재 노드 확인 후 로컬 메모리 할당 |
| SIMD | kernel_fpu_begin() | 커널에서 SSE/AVX 사용 시 kernel_fpu_begin/end()로 FPU 상태 보호 |
| 시스템 콜 | vDSO | gettimeofday() 등은 vDSO로 커널 진입 없이 처리 |
/* x86_64 최적화 예제: 캐시 라인 정렬 */
struct hot_data {
spinlock_t lock;
u64 counter;
void *ptr;
} __cacheline_aligned; /* 64바이트 경계에 정렬 */
/* 분기 예측 힌트 */
if (unlikely(error_condition)) {
handle_error();
return -EINVAL;
}
/* likely는 정상 경로에 사용 */
if (likely(ptr != NULL)) {
fast_path(ptr);
}
ARM64 최적화 포인트
ARM64(RISC 계열)는 x86와 달리 약한 메모리 순서(Weak Memory Ordering) 모델을 사용합니다.
코드가 작성된 순서와 실제 메모리 접근 순서가 다를 수 있기 때문에,
공유 데이터를 다룰 때는 dmb/dsb 배리어나
smp_wmb()/smp_rmb() 같은 커널 추상화를 명시적으로 사용해야 합니다.
이 점이 x86 경험자가 ARM64 커널 코드를 처음 작성할 때 가장 자주 실수하는 부분입니다.
| 최적화 영역 | 기법 | 설명 |
|---|---|---|
| 메모리 순서 | 약한 순서 모델 | 명시적 메모리 배리어(Memory Barrier) 필요 (dmb, dsb, isb) |
| Exclusive 연산 | LDXR/STXR | Atomic 연산 시 Load-Exclusive/Store-Exclusive 페어 사용 |
| TLB | ASID | Address Space ID로 컨텍스트 스위치 시 TLB flush 최소화 |
| 캐시 | 64바이트 라인 | 대부분의 ARM64는 64바이트 캐시 라인 사용 |
| NEON | SIMD 최적화 | 암호화(Encryption), 압축 등에서 NEON 명령어 활용 (커널 모드 사용 제한적) |
/* ARM64 메모리 배리어 예제 */
/* 데이터 동기화 배리어 */
WRITE_ONCE(data->value, new_val);
smp_wmb(); /* Write Memory Barrier */
WRITE_ONCE(data->ready, 1);
/* 읽기 측 */
if (READ_ONCE(data->ready)) {
smp_rmb(); /* Read Memory Barrier */
val = READ_ONCE(data->value);
}
RISC-V 최적화 포인트
RISC-V는 기본 ISA가 단순한 대신 실제 SoC마다 지원하는 확장 집합이 다릅니다.
따라서 최적화 코드를 작성할 때는 런타임에 확장 유무를 확인(riscv_has_extension_likely())한 뒤
분기하는 패턴이 필수입니다. 아직 생태계가 성숙 중인 만큼, 특정 SoC에서만 동작하는
코드가 다른 보드에서 폴백(Fallback)으로 느리게 동작하는 사례를 사전에 방지해야 합니다.
| 최적화 영역 | 기법 | 설명 |
|---|---|---|
| 확장 기능 | ISA 확장 감지 | 런타임에 사용 가능한 확장 감지 후 최적화 경로 선택 |
| Atomic 연산 | AMO 명령어 | A 확장의 LR/SC, AMO 명령어로 잠금(Lock) 프리 자료구조 구현 |
| 메모리 순서 | FENCE 명령 | 약한 메모리 모델, 명시적 FENCE 필요 |
| 벡터 연산 | RVV (Vector) | V 확장 사용 시 대량 데이터 처리 성능 향상 |
perf 도구를 사용하세요.
perf stat -e cycles,instructions,cache-misses,branch-misses로 주요 메트릭을 측정할 수 있습니다.
자세한 내용은 개발 도구 (Development Tools) 문서를 참고하세요.
실전 디버깅(Debugging) 팁 (Debugging Tips)
아키텍처별 커널 디버깅은 각 플랫폼의 특성을 이해하고 적절한 도구를 사용해야 효과적입니다.
공통 디버깅 기법
아키텍처에 상관없이 가장 먼저 활용해야 할 도구는 커널 로그(pr_debug/pr_err)와
WARN_ON() 계열 매크로입니다. 이 두 가지는 별도 장비 없이도 즉시 사용할 수 있고,
문제 발생 위치와 콜스택을 빠르게 좁혀 주기 때문에 항상 첫 번째 수단으로 삼아야 합니다.
아키텍처 특화 도구(JTAG, CoreSight 등)는 그다음 단계입니다.
/* 커널 디버그 출력 */
pr_debug("Variable x = %d\\n", x); /* DEBUG 빌드에만 출력 */
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, apic=debug |
ARM64 특화 디버깅
ARM64 플랫폼은 대부분 임베디드·서버 보드이기 때문에 UART 시리얼 콘솔이 핵심 디버깅 수단입니다.
부팅 초기에 출력이 없다면 earlycon 파라미터로 직렬 포트(Serial Port)를 직접 지정해야 합니다.
칩 내부 트레이스가 필요한 경우에는 CoreSight ETM(Embedded Trace Macrocell)을 활용하며,
이는 Intel PT의 ARM 대응 기능입니다. Device Tree 구성 오류는 /proc/device-tree/와
dtc로 덤프해서 비교하는 것이 가장 빠릅니다.
| 도구/기법 | 용도 | 사용 예 |
|---|---|---|
| JTAG | 하드웨어 디버거 | ARM DS-5, DSTREAM, 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) | CONFIG_RISCV_SBI_V01 로그 확인 |
CONFIG_DEBUG_* 옵션들을 비활성화하세요.
디버그 옵션은 성능에 상당한 영향을 미칩니다. 개발 및 테스트 환경에서만 활성화하는 것이 좋습니다.
자세한 내용은 디버깅 문서를 참고하세요.
자주 묻는 질문 (FAQ)
Q1. 어떤 아키텍처를 선택해야 하나요?
A: 사용 목적에 따라 다릅니다:
- x86_64: 데스크탑, 서버, 클라우드 환경. 가장 성숙한 생태계와 도구 지원
- ARM64: 모바일, 임베디드, 저전력 서버 (AWS Graviton, Apple Silicon). 전력 효율 우수
- RISC-V: 연구, 교육, 새로운 HW 프로젝트. 오픈 ISA로 라이선스 비용 없음
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/4-level (VA/페이지 크기 설정에 따라 다름, 일반적으로 48-bit 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): 2~10% 오버헤드 (워크로드 의존)
- 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로 변환하고, 인터럽트 핸들러를 스레드화하여 거의 모든 커널 경로를 선점할 수 있습니다. 수십 마이크로초 수준의 결정적 지연(deterministic latency)을 보장하여 산업 제어, 로봇 공학, 프로 오디오 등에 사용됩니다.
v6.12 이전에는 별도의 외부 패치(Patch)(rt-patches)를 적용해야 했으나, 이제 메인라인 커널만으로 실시간(Real-time) 커널을 빌드할 수 있습니다. 자세한 내용은 RT-Mutex 문서를 참고하세요.
Q9. FRED가 기존 IDT를 완전히 대체하나요?
A: FRED(Flexible Return and Event Delivery)는 Intel이 도입한 새로운 이벤트 전달 메커니즘으로, v6.12에서 커널 지원이 추가되었습니다.
- 대체 범위 — FRED는 인터럽트, 예외, 시스템 콜의 진입/복귀 경로를 IDT(Interrupt Descriptor Table) 기반에서 전용 하드웨어 메커니즘으로 대체합니다. 스택 전환, 레지스터 저장, 이벤트 레벨 분류를 하드웨어가 자동으로 처리합니다.
- 호환성 — FRED를 지원하지 않는 프로세서에서는 기존 IDT 방식이 그대로 사용됩니다. 커널은 부팅 시 CPU 기능을 감지하여 자동으로 적절한 경로를 선택합니다.
- 이점 — 커널 진입/복귀 오버헤드 감소, 중첩 이벤트 처리 효율화, SYSRET 관련 보안 취약점(Vulnerability) 경로 제거 등의 장점이 있습니다.
CPU 제조사 아키텍처 매뉴얼 (Architecture Software Developer's Manuals)
리눅스 커널 개발에서 각 CPU 제조사의 공식 아키텍처 매뉴얼은 가장 권위 있고 정확한 참조 문서입니다. 커널 코드의 아키텍처 의존 부분(arch/ 디렉토리)을 이해하거나 수정할 때 반드시 해당 매뉴얼을 참조해야 합니다.
Intel® 64 and IA-32 Architectures Software Developer's Manual (Intel SDM)
| 볼륨 | 내용 | 커널 개발 활용 |
|---|---|---|
| Vol. 1 | 기본 아키텍처 | 데이터 타입, 실행 환경, 명령어 개요, x87 FPU, SSE/AVX |
| Vol. 2 (A-Z) | 명령어 세트 레퍼런스 | 개별 명령어의 opcode, 동작, 예외 조건 — 인라인 어셈블리 작성 시 필수 |
| Vol. 3 (A-D) | 시스템 프로그래밍 가이드 | 보호 모드, 페이징, 인터럽트/예외, MSR, APIC, VT-x — 커널 개발 핵심 볼륨 |
| Vol. 4 | MSR (Model-Specific Register) | CPU 모델별 MSR 목록, 성능 카운터, 전력 관리 레지스터 |
| Optimization Manual | 최적화 레퍼런스 | 마이크로아키텍처 세부사항, 분기 예측, 캐시 동작, SIMD 최적화 가이드 |
arch/x86/ 코드를 분석할 때 Vol. 3이 가장 자주 참조됩니다.
특히 페이지 테이블 구조(Chapter 4), 인터럽트/예외 처리(Chapter 6), APIC(Chapter 10),
VT-x(Chapter 23-33)는 커널 개발자의 필독 장입니다.
Intel SDM은 5,000페이지 이상이므로 전체를 읽기보다 필요한 챕터를 색인으로 찾아 참조하는 방식이 효율적입니다.
AMD64 Architecture Programmer's Manual (AMD APM)
| 볼륨 | 내용 | 커널 개발 활용 |
|---|---|---|
| Vol. 1 | 애플리케이션 프로그래밍 | 레지스터, 데이터 타입, 명령어 개요 |
| Vol. 2 | 시스템 프로그래밍 | Long Mode, 페이징, 시스템 콜(SYSCALL/SYSRET), SMM, AMD-V(SVM) |
| Vol. 3 | 범용/SIMD 명령어 | x86-64 명령어 인코딩, SSE/AVX 상세 |
| Vol. 4 | 128/256-bit 미디어 명령어 | XOP, FMA4 등 AMD 전용 확장 |
| Vol. 5 | 64-bit 미디어/x87 명령어 | 레거시 FPU, 3DNow! 명령어 |
arch/x86/kvm/svm/ 디렉토리는
AMD APM Vol. 2의 SVM 챕터를 직접 구현한 것입니다.
또한 AMD의 SEV(Secure Encrypted Virtualization)와 SME(Secure Memory Encryption)는 AMD 고유 기능입니다.
ARM Architecture Reference Manual (ARM ARM)
| 문서 | 내용 | 커널 개발 활용 |
|---|---|---|
| ARMv8-A ARM (DDI 0487) | AArch64/AArch32 ISA 레퍼런스 | A64 명령어, 시스템 레지스터, 예외 모델, MMU, GIC 인터페이스 |
| ARMv9-A ARM | ARMv9 확장 포함 | SVE2, MTE(Memory Tagging), RME(Realm Management), CCA |
| ARM Cortex-A TRM | 코어별 Technical Reference Manual | 캐시 구조, TLB, 분기 예측기, 구현 정의(IMPLEMENTATION DEFINED) 동작 |
| GIC Architecture Spec | Generic Interrupt Controller | GICv2/v3/v4 인터럽트 분배, LPI, ITS — drivers/irqchip/irq-gic-* |
| SMMU Architecture Spec | System MMU (IOMMU) | DMA 주소 변환, 디바이스 격리(Isolation) — drivers/iommu/arm/arm-smmu-* |
| AMBA/AXI/ACE Spec | 버스(Bus) 프로토콜 | 캐시 일관성(coherency), 배리어 동작, DMA 전송 특성 |
RISC-V Specifications
| 문서 | 내용 | 커널 개발 활용 |
|---|---|---|
| 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의 페이지 테이블과 예외 처리 챕터는 커널 개발의 핵심 참조 문서입니다.
매뉴얼 효과적 활용법
| 상황 | 참조 문서 | 참조 섹션 |
|---|---|---|
| 페이지 테이블 워크 디버깅 | Intel SDM Vol. 3 Ch. 4 / ARM ARM D5 / RISC-V Priv. Ch. 4 | 페이지 테이블 엔트리 형식, 비트 필드 의미 |
| 인터럽트 핸들러 작성 | Intel SDM Vol. 3 Ch. 6, 10 / GIC Spec / PLIC Spec | IDT 구조, APIC 프로그래밍, EOI 처리 |
| 메모리 배리어 선택 | Intel SDM Vol. 3 Ch. 8 / ARM ARM B2 / RISC-V Unpriv. Ch. 14 | 메모리 순서 모델, fence/barrier 명령어 |
| KVM 가상화 구현 | Intel SDM Vol. 3 Ch. 23-33 / AMD APM Vol. 2 / ARM ARM D1 | VMCS/VMCB 구조, VM entry/exit, EPT/NPT, EL2 |
| 전력 관리 (cpuidle/cpufreq) | Intel SDM Vol. 3 Ch. 14 / ACPI Spec / ARM DEN0024A | C-state, P-state, MWAIT, WFI |
| 보안 기능 구현 | 각 제조사 보안 가이드 | Intel CET, ARM MTE/PAC/BTI, AMD SEV/SME |
- ACPI Specification — 전원 관리(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) 분리형 칩렛 | XCC(Extreme Core Count) 모놀리식 또는 EMIB 타일 | Meteor Lake: Foveros 3D 타일 / Arrow Lake: 칩렛 타일 / Panther Lake: 3D Foveros 통합 타일 |
| 코어 구성 | CCD당 8코어(Zen4) / 16코어(Zen5 Dense), 동종 코어(Homogeneous) | 최대 60+ 코어, 동종 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/128코어) | 코어 수 증가 또는 타일 추가 | 타일 조합 변경 |
| 커널 영향 | CCD간 NUMA 지연 차이 → NPS BIOS 설정에 따라 NUMA 토폴로지 변동 | SNC 활성화 시 NUMA 노드 2배 → 메모리 할당 정책 영향 | ITMT(Intel Thread Director) 스케줄러 연동 필요, HFI(Hardware Feedback Interface) v6.13 확장 |
인터커넥트 토폴로지 비교
파이프라인(Pipeline) 및 실행 엔진 비교
마이크로아키텍처 세대별 파이프라인 폭과 내부 자원의 차이는 IPC(클록당 명령어 수)와 분기 예측 정확도에 직접 영향을 미치며, 커널의 성능 최적화 결정에도 관련됩니다.
| 파라미터 | 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 지원 | 256-bit 실행 (2사이클/512-bit) | 512-bit 네이티브 | 256-bit 실행 (E-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 활성화 |
; arch/x86/boot/header.S — 리눅스 부팅 초기 (Real Mode)
; BIOS가 이 코드를 0x7C00에 로드하여 실행
; A20 게이트 활성화 (20번째 주소 라인)
; A20이 비활성이면 1MB 이상 주소에 접근 불가
in al, 0x92 ; Fast A20 (System Port)
or al, 2
out 0x92, al
; Protected Mode로 전환 준비
lgdt [gdt_ptr] ; GDT 로드
mov eax, cr0
or eax, 1 ; PE (Protection Enable) 비트 설정
mov cr0, eax ; → Protected Mode 진입!
jmp 0x08:pm_entry ; far jump로 CS 갱신 (GDT 셀렉터 0x08)
Protected Mode (보호 모드)
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 (16바이트, 2슬롯) */
#define GDT_ENTRY_LDT 10 /* 0x50: LDT (16바이트, 2슬롯) */
#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) 대형 페이지 | 커널 직접 매핑, 대용량 메모리 |
| 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 */
- PAE 페이지 테이블 구조:
- PDPT (4 엔트리) → PD (512 엔트리) → PT (512 엔트리) → 4KB 페이지
- CR3 → PDPT (32바이트, 4 × 8바이트 엔트리)
- PAE는 NX(No-Execute) 비트의 전제 조건 */
- PTE bit 63 = NX: 해당 페이지의 코드 실행 금지 */
- → 스택/힙 실행 방지 (DEP/W^X) */
- Long Mode 전환에도 PAE 활성화 필수 (전제 조건) */
Long Mode (IA-32e Mode / 64-bit Mode)
Long Mode는 AMD64(Intel에서는 IA-32e Mode)로 명명된 64비트 실행 모드입니다. 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 (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 */
wrmsrl(MSR_GS_BASE, per_cpu_offset(cpu));
/* 이후 %gs:offset 으로 per-CPU 변수 접근 */
/* 예: movq %gs:current_task, %rax → 현재 task_struct */
/* 유저 TLS(Thread-Local Storage): FS Base */
wrmsrl(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 활성화 필수. 비활성 시 홀수 MB 주소에 접근 불가
- 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 — 인터럽트 우선순위 마스킹 (Long 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)을 제거하여
이 요구사항을 충족합니다.
LGDT 실행 직후 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) */
set_tssldt_descriptor(&get_cpu_entry_area(cpu)->tss_desc,
(unsigned long)&get_cpu_entry_area(cpu)->tss.x86_tss,
DESC_TSS,
__KERNEL_TSS_LIMIT);
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 스택 | #MC Machine Check (벡터 18) |
4 | IST4 스택 | #DB, #BP 디버그 관련 |
/* 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 클리어 */
set_system_intr_gate(X86_TRAP_BP, asm_exc_int3); /* #BP: DPL=3, 사용자→int3 허용 */
int 0x80(시스템 콜)이나 int3(디버그)처럼 사용자 공간에서 소프트웨어 인터럽트를 발생시키려면
해당 IDT 게이트의 DPL=3이어야 합니다. DPL=0인 게이트에 사용자 공간이 접근하면
#GP(General Protection Fault)가 발생합니다.
현대 리눅스는 SYSCALL 명령어를 선호하므로 int 0x80은 호환성 모드(ia32_syscall)에서만 사용됩니다.
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를 완전히 복원하지 않으므로
보안상 주의가 필요합니다.
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)
{
/*
* STAR: [63:48] = SYSRET용 CS 기준 셀렉터
* [47:32] = SYSCALL용 CS 셀렉터 (__KERNEL_CS)
* SYSRET 시 CPU는 STAR[63:48]을 CS로, +8을 SS로 사용
*/
wrmsrl(MSR_STAR, ((u64)__USER32_CS) << 48 |
((u64)__KERNEL_CS) << 32);
/* LSTAR: 64비트 SYSCALL 진입점 */
wrmsrl(MSR_LSTAR, (u64)entry_SYSCALL_64);
#ifdef CONFIG_IA32_EMULATION
/* CSTAR: 32비트 Compatibility Mode SYSCALL 진입점 */
wrmsrl(MSR_CSTAR, (u64)entry_SYSCALL_compat);
#endif
/*
* SFMASK: SYSCALL 진입 시 RFLAGS에서 클리어할 비트
* TF(트레이스), DF(방향), IF(인터럽트 금지), NT(중첩 태스크),
* AC(정렬 확인), IOPL 등을 클리어하여 보안 강화
*/
wrmsrl(MSR_SYSCALL_MASK,
X86_EFLAGS_TF | X86_EFLAGS_DF | X86_EFLAGS_IF |
X86_EFLAGS_IOPL | X86_EFLAGS_AC | X86_EFLAGS_NT);
}
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 (리눅스 미사용, 항상 0) */
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/RSP2는 항상 0입니다.
RSP0만 실제로 활용되며, 태스크 전환(context switch)마다 새 태스크의
커널 스택 주소로 갱신됩니다.
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 교체
; 이후 %gs:offset = current_task 등 per-CPU 변수 접근 가능
; ... 커널 스택 설정, 레지스터 저장 ...
; Ring 0 → Ring 3 복귀
SYM_CODE_START(syscall_return_via_sysret):
swapgs ; 사용자 GS.base 복원 (KernelGS.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 스케줄링 모델 | 선점 가능 시점을 지연하여 불필요한 컨텍스트 스위치를 줄이는 새로운 스케줄링 모델이 도입되었습니다. |
| v6.14 | dmem cgroup |
GPU 메모리를 cgroup으로 제어할 수 있는 디바이스 메모리 컨트롤러가 추가되었습니다. |
x86_64 변경
Intel FRED(유연한 이벤트 전달)와 AMD Bus Lock Detect가 핵심 변경입니다. 인터럽트·예외 전달 경로를 현대화하고, 잠금 경합(Lock Contention)으로 인한 성능 저하를 하드웨어 수준에서 감지·제어하는 방향으로 발전하고 있습니다.
| 버전 | 변경사항 | 설명 |
|---|---|---|
| v6.12 | FRED (Flexible Return and Event Delivery) | 기존 IDT 기반 인터럽트/예외 전달을 대체하는 Intel의 새로운 이벤트 전달 메커니즘입니다. 사용자 공간 복귀 시 FRED RSP0을 기록하여 스택 전환 효율을 높입니다. |
| v6.12 | Shadow Stack 가드 갭(Guard Gap) | CET(Control-flow Enforcement Technology) Shadow Stack의 가드 갭 처리가 범용 미매핑 영역 할당 코드에 통합되었습니다. |
| v6.13 | AMD Bus Lock Detect | 분할 잠금과 유사하게, 버스 전체를 잠그는 명령어를 감지·처벌하는 AMD 하드웨어 기능 지원이 추가되었습니다. |
| v6.13 | 이종 코어(Hybrid) 식별 확장 | Intel 이종 설계(P-core/E-core) 코어 타입 식별 인프라가 확장되어 HFI(Hardware Feedback Interface) 연동이 강화되었습니다. |
| 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.0 최신 변경사항 (2025-2026)
2025년 5월(v6.15)부터 2026년 4월(v7.0)에 이르는 기간은 리눅스 커널이 메이저 버전을 19→7.0으로 변경한 전환점이었습니다. Rust 정식 지원 승격, FRED 확장 준비, CXL+ACPI PRMT 지원 같은 대규모 변경이 이어졌으며, Android Common Kernel도 2026년부터 Trunk Stable(Q2/Q4 릴리스) 모델로 전환합니다.
크로스 아키텍처 변경
Rust가 실험적 지위를 벗어나 C와 대등한 공식 커널 언어로 승격된 것이 이 시기의 가장 큰 변화입니다. 동시에 Linus Torvalds가 메이저 버전 번호를 6에서 7로 올리며 단일 헤드라인 기능보다 장기 프로젝트들의 수렴이 특징인 전환점이 되었습니다.
| 버전 | 변경사항 | 설명 |
|---|---|---|
| v6.15 (2025-05) | 32비트 x86 대형 시스템 지원 제거 | 8 CPU 초과 또는 4GB 초과 메모리를 탑재한 32비트 x86 하드웨어 지원이 제거되었습니다. 해당 하드웨어는 장기간 시장에서 사라졌고, 워크로드는 64비트로 이전되었습니다. |
| v6.16 (2025-07) | CONFIG_X86_NATIVE_CPU |
커널을 -march=native로 빌드하는 옵션이 추가되었습니다. 빌드 호스트와 동일 계열 CPU에서 실행 시 컴파일러 최적화(Compiler Optimization)로 성능을 높일 수 있습니다. |
| v6.18 LTS (2025-12) | 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 링크 암호화 및 보안 디바이스 인증 | PCIe CMA/SPDM 기반 디바이스 인증과 링크 암호화 프레임워크가 메인라인에 병합되었습니다. |
| v7.0 (2026-04-12) | Rust 정식 지원 승격 | Rust가 실험적 상태를 벗어나 C와 함께 커널의 공식 언어로 승격되었습니다. 새 드라이버/서브시스템을 Rust로 작성하는 것이 완전히 허용됩니다. |
| v7.0 | AI 코딩 도구 기여 가이드라인 | AI 생성 코드의 기여를 허용하되, 인간 제출자가 리뷰/컴플라이언스/책임을 진다는 공식 문서가 추가되었습니다. |
| v7.0 | 버전 번호 규칙 변경 | Linus Torvalds는 19 도달 시 메이저 번호를 올리는 규칙에 따라 6.20 대신 7.0으로 명명했습니다. 단일 헤드라인 기능보다 Rust·sched_ext·XFS self-healing 등 장기 프로젝트 수렴이 특징입니다. |
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 | Intel/AMD PMU 개선 | Intel·AMD 양측 PMU(Performance Monitoring Unit) 드라이버가 개선되었고, ARM에서는 VGICv3 중첩 가상화, Apple Silicon에서는 FEAT_PMUv3 에뮬레이션이 도입되었습니다. |
| 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 PRMT 주소 변환 (AMD Zen 5) | AMD EPYC 9005(Zen 5)에서 데뷔한 ACPI Platform Runtime Mechanism(PRMT) 기반 CXL 주소 변환 지원이 메인라인에 병합되었습니다. 후속 플랫폼 확장 범위는 실제 플랫폼 지원 문서 기준으로 확인하는 편이 안전합니다. |
| v7.0 | Intel Diamond Rapids / Nova Lake S / Panther Lake | Diamond Rapids NTB 드라이버, Nova Lake S용 LPSS 드라이버, Panther Lake/Crescent Island 플랫폼 개선이 다수 병합되었습니다. |
| v7.x 이후 | FRED 활성화 범위 확대 논의 | FRED(Flexible Return and Event Delivery)는 지원 CPU와 커널 설정 조합에 따라 적용 범위가 넓어지는 흐름입니다. 기본 활성화 시점과 플랫폼별 자동 사용 범위는 이후 메인라인 태그와 배포판 설정으로 재확인해야 합니다. |
ARM64 변경
Lazy Preemption의 ARM64 정식 도입, SME(행렬 연산 가속), MPAM(메모리 QoS 분할) 등 클라우드·HPC 서버에서 ARM64의 성능과 자원 격리 능력을 끌어올리는 방향으로 집중되고 있습니다.
| 버전 | 변경사항 | 설명 |
|---|---|---|
| v6.16 | ARM64 Lazy Preemption 병합 | 지연 선점 스케줄링 모델의 ARM64 지원이 본격 병합되었습니다. 불필요한 선점 포인트를 제거해 처리량(Throughput)을 높이면서도 응답성을 보존합니다. |
| v6.16 | SME (Scalable Matrix Extension) 지원 확대 | SME 지원이 병합되어 ARM64 서버/임베디드에서 행렬 연산 가속을 활용할 수 있습니다. ZT0 레지스터를 추가한 SME2/SME2.1도 커널 트리에서 성숙 중입니다. |
| v6.19 | MPAM 드라이버 (Memory Partitioning and Monitoring) | 멀티 사용자 VM이 돌아가는 서버에서 공유 메모리 리소스 분할·모니터링을 위한 MPAM 드라이버가 병합되었습니다. x86 resctrl에 해당하는 ARM64 고급 QoS 기능입니다. |
RISC-V 변경
Zicfiss(Shadow Stack)·Zicfilp(Landing Pad)로 제어 흐름 무결성(CFI) 하드웨어 지원을 완성하고, Tenstorrent AI 가속기 지원을 시작하는 등 RISC-V 생태계가 보안과 AI 영역으로 확장되고 있습니다.
| 버전 | 변경사항 | 설명 |
|---|---|---|
| v6.15 | BFloat16 / Zaamo·Zalrsc / Zicbom·Zicntr·Zihpm | BFloat16 부동소수점, 원자 메모리 연산 분리 확장(Zaamo=AMO, Zalrsc=LR/SC), 캐시 관리·카운터·성능 모니터 확장 지원이 추가되었습니다. |
| v6.19 | 보조 CPU 병렬 핫플러그(Hotplug) | RISC-V에서 보조 CPU 코어를 병렬로 핫플러그할 수 있게 되어, 대규모 SMP 시스템의 부팅·온라인 시간이 단축됩니다. |
| v6.19 | Zicbop 유저스페이스 노출 + kselftest | 비준된 Zicbop(프리페치 힌트) 확장이 유저스페이스에 노출되고, 관련 kselftest가 추가되었습니다. getauxval(AT_HWCAP)/hwprobe()로 탐지 가능합니다. |
| v6.19 | Tenstorrent Blackhole 초기 지원 | Tenstorrent AI 가속기 Blackhole의 기초 메인라인 지원이 시작되었습니다. |
| v7.0 | Zicfiss / Zicfilp (제어 흐름 무결성) | Shadow Stack(Zicfiss)과 Landing Pad(Zicfilp) 확장이 메인라인에 병합되었습니다. x86 CET / ARM64 GCS에 대응하는 RISC-V의 하드웨어 ROP/JOP 대응 기능입니다. |
LoongArch / 기타 아키텍처
| 버전 | 변경사항 | 설명 |
|---|---|---|
| v7.0 | Loongson 128비트 원자 cmpxchg | Loongson 프로세서에 128비트 cmpxchg 원자 연산 지원이 추가되어 락프리 자료구조 구현이 용이해졌습니다. |
| v7.0 | SPARC / DEC Alpha 새 코드 | 레거시로 여겨지던 SPARC와 DEC Alpha 아키텍처에 새 코드가 추가되었습니다. 레거시 아키텍처 커뮤니티의 재활성화를 보여줍니다. |
CCEL 등)이
정리되었습니다. 커널 측 흡수는 v6.17~v7.0 동안 점진적으로 이루어지고 있습니다.
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, Intel FRED 채택(문서 공개), x86 Ecosystem Advisory Group 합의 반영 |
| AMD EPYC Zen 5 (9005 시리즈) | 출시 완료 | CXL 주소 변환이 ACPI PRMT로 구현, 커널 v7.0 대응 |
아키텍처 분석 플레이북
Kexec 시스템
kexec는 실행 중인 커널에서 다른 커널로 재부팅 없이 전환하는 메커니즘입니다.
재부팅 시간을 획기적으로 단축하며, kdump 기반 크래시 덤프(Dump) 수집에도 사용됩니다.
/* kernel/kexec.c - kexec 핵심 구조체 */
struct kexec_segment {
void *buf; /* 버퍼 가상 주소 */
size_t bufsz; /* 버퍼 크기 */
void *mem; /* 대상 물리 주소 */
size_t memsz; /* 대상 메모리 크기 */
};
struct kexec_image {
unsigned long start; /* 엔트리 포인트 */
unsigned long type; /* 커널 유형 */
int nr_segments;
struct kexec_segment segment[];
};
/* kexec 시스템 콜 */
#define KEXEC_LOAD 0x2000
#define KEXEC_EXEC 0x2001
#define KEXEC_UNLOAD 0x2002
#define KEXEC_ARCH 0x2003
#define KEXEC_MODE 0x2004
kexec -l vmlinuz --initrd=initrd.img --append="..."로 새 커널 로드 후
kexec -e로 실행합니다. 이 기능은 펌웨어 재초기화 없이 커널 전환하므로
서버 재부팅 시 수십 초를 절약할 수 있습니다.
크래시 덤프 (kdump)
커널 패닉(Kernel Panic) 시 메모리 덤프를 수집하는 메커니즘입니다. 두 번째 커널(kdump 커널)이 패닉을 트리거하고 첫 번째 커널의 메모리를 덤프합니다.
| 컴포넌트 | 역할 |
|---|---|
kdump 커널 | acrondown 시 실행되는 크래시 커널 |
kexec | 패닉 시 크래시 커널로 전환 |
makedumpfile | 메모리 덤프에서 불필요 페이지 필터링 |
crash | 크래시 덤프 분석 도구 |
# kdump 설정 예시 (RHEL/CentOS)
# /etc/kdump.conf 설정
path /var/crash
core_collector makedumpfile -c --page-size 4096
# crash 커널 파라미터
crashkernel=256M
# 크래시 덤프 분석
crash vmlinuz-$(uname -r) vmcore
ACPI (Advanced Configuration and Power Interface)
ACPI는 x86/ARM64 서버와 데스크탑에서 하드웨어 구성과 전원 관리를 담당하는 표준 인터페이스입니다. DSDT(ACPI Machine Language)와 SSDT(Secondary System Description Table)로 테이블이 제공됩니다.
/* drivers/acpi/bus.c - ACPI 버스 드라이버 */
static int acpi_bus_init(void)
{
acpi_status status;
/* ACPI 테이블 루트 얻기 */
status = acpi_initialize_tables(initial_table_entry, 4, 0);
if (ACPI_FAILURE(status))
return -ENODEV;
/* ACPI 네임스페이스 로드 (DSDT/SSDT 파싱) */
status = acpi_load_tables();
if (ACPI_FAILURE(status))
return -ENODEV;
/* ACPI 하드웨어 초기화 */
status = acpi_enable_subsystem(0);
return 0;
}
Device Tree Binding
Device Tree는 하드웨어 구성 정보를 구조화된 DTS로 기술합니다.
각 디바이스 드라이버는OF_MATCH_TABLE으로 DT 노드에 바인딩됩니다.
/* drivers/of/device.c - OF 디바이스 매칭 */
static const struct of_device_id irqchip_of_match[] = {
{ .compatible = "arm,gic-v3", .data = gic_of_init },
{ .compatible = "arm,gic-v2", .data = gic_of_init },
{ .compatible = "arm,pl011", .data = pl011_of_init },
{ },
};
/* 드라이버 등록 */
static int __init irqchip_init(void)
{
return of_platform_populate(NULL, DEFAULT_OF_ROOT,
irqchip_of_match, NULL);
}
아키텍처 문서를 읽을 때는 개념 암기보다 "실제 커널 코드에서 어디서 소비되는지"를 함께 확인해야 이해가 빠르게 고정됩니다. 아래 절차는 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
참고자료
- 커널 개발 프로세스 개요 — 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)
- 커널 초기화 순서 해설 — An introduction to the kernel initialization (LWN.net)
- 커널 소스 구조 개요 — A guide to the Kernel Development Process (LWN.net)
- LWN 커널 인덱스 — LWN.net Kernel Index
- 아키텍처별 커널 소스 — arch/ 디렉터리 (Bootlin Elixir)
- 커널 초기화 진입점 소스 — init/main.c (Bootlin Elixir)
- 커널 부트 파라미터 목록 — The kernel's command-line parameters (kernel.org)
- 커널 빌드 시스템 문서 — Kernel Build System (kernel.org)
- 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)
- Robert Love — Linux Kernel Development, 3rd Edition, Addison-Wesley (커널 전반 구조와 서브시스템 해설)
- Daniel P. Bovet, Marco Cesati — Understanding the Linux Kernel, 3rd Edition, O'Reilly (x86 중심 커널 내부 구조 상세)
관련 문서
커널 아키텍처와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.