커널 아키텍처 (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 등 검증 체인을 흐름으로 이해합니다.
  • 실패 지점 식별 — 부팅 로그에서 단계별 실패 단서를 빠르게 찾습니다.

단계별 이해

  1. 커널/사용자 공간 경계 파악
    코드가 어느 공간에서 실행되는지, 특권 레벨 전환(시스템 콜·인터럽트)이 언제 발생하는지 이해합니다.
  2. 대상 아키텍처의 특권 모델 확인
    x86_64(Ring), ARM64(EL), RISC-V(M/S/U-mode), MIPS(KSU) 중 어느 것인지 파악하고, 커널이 어느 레벨에서 실행되는지 확인합니다.
  3. 부팅 단계 식별
    현재 이슈가 펌웨어·부트로더·커널 초기화 중 어느 단계에서 발생하는지 먼저 고정합니다.
  4. 전환 경계 검증
    단계 간 인자 전달과 상태 인계(부팅 파라미터, 페이지 테이블 활성화 시점 등)를 추적합니다.
  5. 플랫폼별 재검증
    메모리 순서 모델, IOMMU, 인터럽트 컨트롤러 등 플랫폼 의존 요소가 다른 하드웨어 조건에서도 올바르게 동작하는지 확인합니다.

x86_64, ARM64, RISC-V, MIPS 아키텍처별 커널 구조, 부팅 과정, 주소 공간 레이아웃을 상세히 다룹니다.

왜 커널 아키텍처를 알아야 할까요? 같은 C 코드라도 x86_64에서 문제없이 동작하던 코드가 ARM64에서 데이터 경쟁 버그로 이어지거나, RISC-V에서는 존재하지 않는 확장 명령어에 의존해 빌드 오류가 발생할 수 있습니다. 커널 드라이버·서브시스템 코드를 이식하거나, 부팅 실패를 디버깅하거나, 특권 레벨 경계를 넘는 보안 취약점(Vulnerability)을 분석할 때 아키텍처 지식이 없으면 근본 원인에 도달하기 어렵습니다. 이 문서는 네 아키텍처 각각의 특권 모델·메모리 주소 공간·부팅 경로·레지스터 체계를 커널 코드와 연결하여 설명합니다.

관련 표준: Intel SDM (x86/x64 아키텍처), ARM Architecture Reference Manual (AArch64), UEFI Specification, ACPI 6.6 — 커널이 지원하는 주요 프로세서 아키텍처와 펌웨어 인터페이스 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

리눅스 커널 개요 (Linux Kernel Overview)

리눅스 커널은 1991년 Linus Torvalds가 처음 공개한 이래, 현재 세계에서 가장 널리 사용되는 운영체제 커널입니다. 서버, 데스크탑, 임베디드 기기, 스마트폰(Android), 슈퍼컴퓨터에 이르기까지 거의 모든 컴퓨팅 영역에서 동작합니다. 커널은 하드웨어와 사용자 공간(user space) 사이에서 추상화 계층 역할을 하며, 다음과 같은 핵심 기능을 담당합니다:

참고: 리눅스 커널 소스는 매우 큰 규모의 코드베이스이며, 전 세계 수천 명의 개발자가 참여하는 대표적인 오픈소스 프로젝트입니다. 지원 아키텍처도 x86, ARM, ARM64, RISC-V, MIPS, PowerPC, s390 등 매우 다양합니다.

모놀리식 vs 마이크로커널 (Monolithic vs Microkernel)

운영체제 커널 설계에는 크게 두 가지 접근 방식이 있습니다. 모놀리식 커널(Monolithic Kernel)은 모든 핵심 서비스(프로세스 관리, 메모리 관리, 파일시스템, 드라이버 등)가 하나의 커다란 커널 이미지 안에서 동일한 주소 공간에서 실행됩니다. 반면 마이크로커널(Microkernel)은 최소한의 기능만 커널에 포함하고 나머지는 사용자 공간 서버로 분리합니다.

리눅스는 모놀리식 커널입니다. 그러나 순수한 모놀리식이 아닌, 동적으로 적재 가능한 커널 모듈(Loadable Kernel Module, LKM)을 지원하여 모듈화의 유연성을 확보합니다. 이를 "모듈형 모놀리식(Modular Monolithic)" 커널이라고도 합니다. 커널 모듈에 대한 자세한 내용은 커널 모듈 문서를 참고하세요.

커널 아키텍처 비교 모놀리식 커널 (Monolithic) Linux · FreeBSD · OpenBSD 사용자 공간 (User Space) 프로세스 A 프로세스 B 프로세스 C 시스템 콜 커널 공간 (Kernel Space) 프로세스 관리 메모리 관리 VFS · 파일시스템 네트워크 스택 디바이스 드라이버 시그널 · IPC 서브시스템 간 직접 함수 호출 (빠름) 마이크로커널 (Microkernel) Minix · QNX · seL4 · Zircon 사용자 공간 (User Space) 파일시스템 서버 드라이버 서버 네트워크 서버 IPC 마이크로커널 (Kernel Space) IPC 스케줄러 기본 메모리 관리 최소한의 기능만 커널에 포함 파일시스템 · 드라이버 · 네트워크는 사용자 공간 서버로 이동 장점 높은 성능 구조 단순 장점 격리성 우수 드라이버 장애 격리 단점 버그 확산 모듈 교체 어려움 단점 IPC 오버헤드 구조 복잡 리눅스 = 모듈형 모놀리식: 모놀리식의 성능 + 커널 모듈(LKM)로 동적 확장 가능
모놀리식 커널(좌)과 마이크로커널(우) 구조 비교 — 모놀리식은 서브시스템이 커널 공간에 통합되어 직접 함수 호출로 통신하고, 마이크로커널은 최소 기능만 커널에 두고 파일시스템·드라이버·네트워크 서버가 사용자 공간에서 IPC로 통신합니다.
TIP: 모놀리식 커널의 장점은 서브시스템 간 함수 호출이 직접적이어서 성능이 뛰어나다는 것입니다. 마이크로커널은 IPC(프로세스 간 통신)를 통해 서비스 간 통신하므로 오버헤드(Overhead)가 있지만, 격리성과 안정성이 우수합니다. Linus Torvalds와 Andrew Tanenbaum 사이의 유명한 "모놀리식 vs 마이크로커널" 논쟁은 OS 설계 철학에서 중요한 역사적 사건입니다.

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)도 여전히 지원됩니다.

BIOS vs UEFI 이중 경로 부팅 흐름 전원 ON / 리셋 벡터 CPU가 리셋 벡터(0xFFFFFFF0)에서 첫 명령 실행 BIOS (레거시) UEFI (현대) BIOS POST 하드웨어 자가진단, 부트 디바이스 선택 SEC → PEI → DXE 단계 UEFI 펌웨어 초기화, EFI 메모리 맵 생성 1 펌웨어 초기화 MBR 로드 (512바이트) 디스크 첫 섹터, 파티션 테이블 포함 ESP 마운트 EFI System Partition (FAT32) 2 부트 미디어 GRUB Stage 1 → Stage 2 grub.cfg 파싱, 파일시스템 인식 GRUB EFI 앱 / EFI Stub grubx64.efi 또는 EFI Stub 직접 부팅 3 부트로더 실행 커널 + initramfs 로드 Real → Protected → Long Mode 전환 커널 + initramfs 로드 Protected → Long Mode 전환 4 커널 로드 decompress_kernel() → startup_64() 커널 압축 해제 후 64-bit 커널 초기화 진입 arch/x86/boot/compressed/misc.c → arch/x86/kernel/head_64.S
전원 ON 후 CPU가 리셋 벡터(0xFFFFFFF0)에서 첫 명령을 실행하는 공통 시작점에서 BIOS(레거시)와 UEFI(현대) 두 경로로 분기하여 펌웨어 초기화 → 부트 미디어 접근 → 부트로더 실행 → 커널 로드 및 모드 전환의 4단계를 거치고, 마지막에 decompress_kernel() → startup_64()에서 합류합니다. 번호 배지는 단계 순서를 나타내며, 파란색은 BIOS 경로, 초록색은 UEFI 경로를 구분합니다.
CPU 모드 전환 흐름 Real Mode 16-bit · 1MB 주소 공간 CS:IP 세그먼트 기반 주소 지정 인터럽트 벡터 테이블(IVT) 사용 BIOS INT 서비스 호출 가능 최대 20-bit 주소 버스 CR0.PE = 0 startup_32() CR0.PE = 1 Protected Mode 32-bit · 4GB 주소 공간 GDT/IDT 디스크립터 기반 페이징 활성화 가능 PAE 활성화 (CR4.PAE) 커널 압축 해제 단계 CR0.PE = 1 startup_64() EFER.LME = 1 Long Mode 64-bit · 48-bit VA (256TB) RIP 상대 주소 지정 4-Level 페이지 테이블 64-bit 레지스터 (RAX, RBX…) x86_64_start_kernel() 호출 CR4.PAE + EFER.LME = 1
x86_64 부팅 시 CPU 모드 전환 흐름 — Real Mode(16-bit, 1MB)에서 startup_32()가 CR0.PE=1로 Protected Mode(32-bit, 4GB)로 전환하고, 이어 startup_64()가 EFER.LME=1로 Long Mode(64-bit, 48-bit VA)에 진입합니다. 각 박스는 해당 모드의 주소 공간·주소 지정 방식·핵심 제어 비트를 요약하며, 최종 단계에서 x86_64_start_kernel()이 호출됩니다.
x86_64 커널 초기화 시퀀스 1 decompress_kernel() 커널 이미지 압축 해제 (zlib / lzma / lz4 / zstd) arch/x86/boot/compressed/misc.c 2 startup_64() 세그먼트 레지스터 초기화, 초기 페이지 테이블 설정 arch/x86/kernel/head_64.S 3 x86_64_start_kernel() GDT/IDT 설정, CR4/EFER 초기화, 페이지 테이블 최종화 arch/x86/kernel/head64.c 4 start_kernel() CPU/메모리/인터럽트/스케줄러/타이머 초기화 setup_arch() mm_init() sched_init() trap_init() init/main.c 5 rest_init() kernel_init 스레드 생성, idle 루프 진입 (PID 0) init/main.c 6 kernel_init() 드라이버 초기화, rootfs 마운트, init 프로세스 탐색 (PID 1) init/main.c 커널 공간 사용자 공간 7 /sbin/init (systemd) 사용자 공간 PID 1 실행, 서비스 및 타겟 시작 (사용자 공간)
압축된 커널 이미지에서 사용자 공간 init까지의 x86_64 초기화 시퀀스 — decompress_kernel() 압축 해제로 시작해 startup_64() → x86_64_start_kernel()을 거치고, 굵은 테두리의 start_kernel()에서 setup_arch()·mm_init()·sched_init()·trap_init() 등 주요 서브시스템이 초기화됩니다. 이후 rest_init() → kernel_init()을 거쳐 /sbin/init(systemd)이 사용자 공간 PID 1로 실행됩니다. 번호 배지는 단계 순서를, 각 카드 우측의 칩은 해당 단계를 구현하는 소스 파일을 나타내며, 하단 점선은 커널 공간과 사용자 공간의 경계를 표시합니다.
주의: UEFI 부팅에서는 EFI Stub 기능을 통해 bootloader 없이 커널을 직접 부팅할 수 있습니다. 이 경우 커널 이미지가 직접 UEFI 애플리케이션으로 동작하며, 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 기준 주소 공간 레이아웃을 보여줍니다:

x86_64 가상 주소 공간 레이아웃 (4-Level Paging, 48-bit) 높은 주소 낮은 주소 0xFFFFFFFF_FFFFFFFF 0xFFFFFFFF_80000000 0xFFFFC900_00000000 0xFFFF8880_00000000 0xFFFF8000_00000000 0x00007FFF_FFFFFFFF 0x00000000_00000000 커널 텍스트 매핑 (Kernel Text Mapping) __START_KERNEL_map · 커널 이미지 고정 가상 주소 매핑 2 GB 모듈 매핑 공간 (Module Mapping) 1 GB vmalloc / ioremap 영역 비연속 가상 메모리 할당 · 디바이스 I/O 메모리 재매핑 약 16 TB 물리 메모리 직접 매핑 (Direct Map) page_offset_base · 가상 주소 = 물리 주소 + 고정 오프셋 최대 64 TB 가드 홀 (Guard Hole) · 미매핑 예약 영역 비정규 주소 영역 (Non-canonical Hole) · 약 16 EB 비트 [63:48] 부호 확장 불일치 구간 · 참조 시 #GP(Exception) 발생 정규 주소(Canonical) 규칙 비트[63:48] ← 비트 47 복제(부호 확장) 전부 0이면 사용자 · 전부 1이면 커널 스택 (Stack) 함수 호출 프레임 · 지역 변수 낮은 주소로 성장 mmap 영역 · 공유 라이브러리 · 파일/익명 매핑 ASLR 무작위 간격 (힙과 mmap 사이) 힙 (Heap) 높은 주소로 성장 프로그램 세그먼트 (ELF Text · Data · BSS) NULL 포인터 가드 페이지 · 역참조 시 SIGSEGV 커널 공간 상위 128 TB 사용자 공간 하위 128 TB 커널 전용 사용자 전용 접근 불가 (#GP · SIGSEGV) 예약 · 간격 경계 값은 커널 버전과 KASLR 설정에 따라 달라질 수 있음 (기준: Documentation/arch/x86/x86_64/mm)
x86_64 가상 주소 공간 레이아웃 - 상위 128TB 커널 공간(Kernel Space)과 하위 128TB 사용자 공간(User Space), 비정규 주소(Non-canonical) 영역과 부호 확장(Sign Extension) 규칙

아키텍처별 주소 변환(Address Translation) 경로 비교

가상 주소(Virtual Address)를 물리 주소(Physical Address)로 변환하는 핵심 경로는 세 아키텍처 모두 유사하지만, 제어 레지스터(Register)와 페이지 테이블 포맷이 다릅니다. 아래 다이어그램은 세 아키텍처가 공유하는 변환 파이프라인과 아키텍처별 차이점을 한 화면에서 비교합니다.

아키텍처별 가상 주소 변환 경로 1. 공통 변환 파이프라인 — TLB 우선 조회 가상 주소 (VA) CPU가 접근하는 가상 주소 TLB 조회 최근 변환 결과 캐시 (VA→PA) 물리 주소 (PA) 실제 DRAM 위치 TLB 히트 — 페이지 워크 생략 미스 하드웨어 페이지 테이블 워크 (Page Table Walk) 루트 레지스터부터 리프 PTE까지 단계별 메모리 탐색 → PFN 도출 리프 PTE의 PFN + 오프셋 매핑 없음 또는 권한 위반 → 페이지 폴트(Page Fault) 발생, 커널이 원인 처리 후 명령 재시도 2. 아키텍처별 차이 — 제어 레지스터 · 테이블 구성 · TLB 관리 구분 x86_64 ARM64 RISC-V 루트 레지스터 CR3 (+PCID) 현재 루트 테이블 물리 주소 보유 TTBR0_EL1 · TTBR1_EL1 + TCR_EL1 변환 속성 설정 satp MODE · ASID · 루트 PPN 필드 페이지 테이블 단계 PML4 → PDPT → PD → PT 4단계 · 인덱스 각 9비트 L0 → L1 → L2 → L3 4단계 · 인덱스 각 9비트 (48-bit VA) Sv39 3단계 · Sv48 4단계 인덱스 각 9비트 커널/사용자 분리 단일 테이블 세트 사용 KPTI 적용 시 CR3 전환으로 격리 TTBR0 = 사용자 / TTBR1 = 커널 독립된 루트 2개 운영 단일 루트 사용 상위 절반 주소를 커널 영역으로 사용 TLB 태깅 PCID (CR3 하위 12비트) 문맥 전환 시 flush 절약 ASID (TTBR0_EL1 필드) 문맥 전환 시 flush 절약 ASID (satp 필드) 문맥 전환 시 flush 절약 무효화 명령 INVLPG · INVPCID TLBI 계열 명령어 sfence.vma 대형 페이지 2MB (PD) / 1GB (PDPT) 2MB / 1GB (블록 디스크립터) 2MB / 1GB (메가·기가 페이지) 기준: 4KB 페이지 · 인덱스 9비트(테이블당 엔트리 512개)는 세 아키텍처 공통 — ARM64 TCR_EL1과 RISC-V satp.MODE 설정에 따라 단계 수와 VA 폭은 변동
공통 변환 파이프라인(TLB 우선 조회)과 x86_64 · ARM64 · RISC-V의 제어 레지스터 및 페이지 테이블 구성 비교

세그먼테이션과 페이징 (Segmentation & Paging)

x86_64에서 세그먼테이션은 사실상 플랫 모델(flat model)로 사용됩니다. 모든 세그먼트의 베이스가 0이고 리미트가 최대값으로 설정되어, 세그먼테이션은 사실상 비활성화된 상태입니다. 그러나 GDT(Global Descriptor Table)는 여전히 존재하며, 커널/사용자 모드 전환과 TSS(Task State Segment)를 위해 필수적입니다.

x86_64 세그먼테이션 (Flat Model) 모든 세그먼트 Base = 0 · Limit = 최대값 → 논리 주소가 그대로 선형 주소가 됩니다 주소 데이터 경로 제어(조회) 경로 논리 주소 (Logical Address) 셀렉터 (16-bit) 오프셋 (64-bit) 디스크립터 조회 (인덱스 = 셀렉터 ÷ 8) 통과 그대로 offset + Base + GDT (Global Descriptor Table) GDTR이 가리키는 디스크립터 배열 # 세그먼트 Base Limit DPL 0 널 디스크립터 — — — 1 커널 코드 0 4GB 0 2 커널 데이터 0 4GB 0 3 사용자 코드 0 4GB 3 4 사용자 데이터 0 4GB 3 5 TSS 실제 값 실제 값 0 · 셀렉터 = 엔트리 인덱스 × 8 · [0]번 널 디스크립터는 예약 · Long Mode에서는 CPU가 Base/Limit 무시 (FS/GS MSR 베이스만 유효) Base = 0 선형 주소 = 오프셋 그대로 (Base 기여 없음) 페이징으로 페이징 (Page Table) 선형 주소 → 물리 주소 Flat 모델에서 실제로 남는 역할 ① 특권 검사 CPL-DPL 비교로 커널/사용자 분리 유지 ② FS/GS Base MSR로 설정하는 유일한 유효 베이스 (per-CPU, TLS) ③ 시스템 콜 전환 syscall/sysret이 MSR에서 CS/SS 자동 로드
셀렉터가 GDT에서 디스크립터를 조회해도 Base가 모두 0이라 오프셋이 선형 주소로 그대로 통과하며, 보호는 페이징과 DPL 특권 검사가 담당합니다.

페이징은 4단계 페이지 테이블을 사용합니다 (5단계 페이징은 CONFIG_X86_5LEVEL=y로 활성화):

x86_64 4-Level Page Table Walk (48-bit VA) 하위 48비트로 주소 변환 수행 · 비트 63:48은 부호 확장(캐노니컬 주소) 부호 확장 [63:48] PGD 인덱스 [47:39] 9비트 PUD 인덱스 [38:30] 9비트 PMD 인덱스 [29:21] 9비트 PTE 인덱스 [20:12] 9비트 페이지 오프셋 [11:0] 12비트 1 CR3 PGD 물리주소 주소 변환 시작점 2 PGD (PML4) VA[47:39] · 512 엔트리 엔트리 → PUD 물리주소 3 PUD VA[38:30] · 512 엔트리 엔트리 → PMD 물리주소 4 PMD VA[29:21] · 512 엔트리 엔트리 → PTE 물리주소 5 PTE VA[20:12] · 512 엔트리 PFN 기록 · 접근 플래그 6 물리 프레임 4KB 페이지 베이스 = PFN × 4096 오프셋 [11:0] 프레임 베이스 물리 주소 (Physical Address) = 프레임 베이스 + 오프셋[11:0] 프레임 베이스 = PTE가 기록한 PFN × 4096 PFN은 40비트(PA[51:12]) · 물리 주소 최대 52비트 핵심 정리 ① 9비트 인덱스 × 4단계 = 36비트 + 12비트 오프셋 = 48비트 가상 주소 ② 화살표 = 상위 엔트리가 기록한 다음 테이블의 물리 주소 ③ PS=1이면 PUD(1GB)·PMD(2MB) Huge Page → 이후 단계 생략 ④ 비트63:48 = 부호 확장(캐노니컬) · 물리 주소 최대 52비트(4PB)
CPU는 CR3가 가리키는 PGD(리눅스 명명, x86_64 하드웨어의 PML4)부터 시작해 각 단계 테이블에서 9비트 인덱스로 엔트리 하나를 선택하고, 그 엔트리에 기록된 다음 테이블의 물리 주소를 따라 내려갑니다. PTE에서 얻은 4KB 프레임 베이스에 가상 주소 하위 12비트 오프셋을 더해 최종 물리 주소가 완성됩니다.

링 구조 (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 주소이름용도커널 활용
0x10TSCTime Stamp Counter고정밀 타이머
0x1A2IA32_MISC_ENABLE여러 Misc 기능turbo boost, fast string
0x1A4IA32_PERF_CTLPerformance ControlP-상태 제어
0xC0000080EFERExtended Feature EnableLong Mode, NX, SYSCALL
0xC0000100STARSYSCALL Targetsyscall CS/SS
0xC0000101LSTARLong Mode SYSCALL64-bit syscall target
0xC0000102CSTARCompat SYSCALL32-bit compat target
0xC0000103FMASKSYSCALL RFLAGS masksyscall flags mask
0xC0000104FS.baseFS Base Addressper-CPU data
0xC0000105GS.baseGS Base Addressper-CPU kernel
0x00000309~0x0000030BFixed Counter 0~2Fixed Performance Counterperf_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));
MSR 접근 주의: 일부 MSR은 특권 레벨(Ring 0)에서만 접근 가능합니다. 사용자 공간에서 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 Gate0x5하드웨어 태스크 전환 (거의 미사용)
Interrupt Gate0x6예외/인터럽트, IF 자동 Clear
Trap Gate0x7예외만 사용, 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
VMX VM-Exit 처리 흐름 non-root(게스트) ↔ root(호스트/KVM) 하드웨어 자동 전환 · 상태와 사유는 VMCS에 기록 VMX non-root 모드 (게스트) 제한된 권한으로 게스트 코드 실행 VMX root 모드 — 호스트 KVM 완전한 권한으로 KVM 코드 실행 1 게스트 코드 실행 vCPU 스레드 주요 VM-Exit 유발 조건 • 외부 인터럽트 · NMI · 예외 • 민감 명령어: CPUID · RDMSR/WRMSR · CR 접근 • EPT 위반 · HLT · PAUSE · I/O 명령어 2 VM-Exit 하드웨어 자동 전환 (지연 약 수 µs) 게스트 상태를 저장 (VMCS guest-state 영역) host-state 적재 후 HOST_RIP로 분기 3 Exit 사유 판독 VMCS에서 EXIT_REASON 판독 exit qualification 함께 해석 4 핸들러 디스패치 handle_vmexit()에서 사유별 전용 핸들러 호출 5 처리 수행 명령어 에뮬레이션 · 이벤트 인젝션 EPT 갱신 등 후속 작업 6 게스트 재개 (VM-Entry) VM-Exit 시점의 게스트 RIP부터 실행 계속 VMRESUME / VMLAUNCH 명령으로 진입 실행 주기 VM-Entry (빠른 경로) 드문 경로 7 유저스페이스 위임 (QEMU) KVM_RUN 리턴 → QEMU가 처리 처리 완료 후 KVM_RUN 재호출 대표적인 VM-Exit 사유 (EXIT_REASON 일부) 인터럽트 · 예외 EXTERNAL_INTERRUPT EXCEPTION_NMI NMI_WINDOWING 명령어 에뮬레이션 CPUID MSR_READ · MSR_WRITE IO_INSTRUCTION CR_ACCESS 메모리 가상화 EPT_VIOLATION EPT_MISCONFIG 게스트 상태 변화 HLT PAUSE TRIPLE_FAULT INIT · SIPI
VM-Exit이 발생하면 하드웨어가 자동으로 게스트 상태를 VMCS에 저장하고 host-state를 적재한 뒤 HOST_RIP로 분기합니다. 호스트 쪽 KVM은 EXIT_REASON을 판독해 전용 핸들러로 디스패치하고 명령어 에뮬레이션·이벤트 인젝션 등을 수행한 뒤 VM-Entry(VMRESUME/VMLAUNCH)로 게스트를 재개하며, 드물게는 유저스페이스 QEMU에 위임해 처리 후 재진입합니다.
/* 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/SYSRET: x86_64에서 시스템 콜(System Call)은 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 발생 시에만 가능합니다.

ARM64 시스템 레지스터

ARM64는 x86의 MSR과 유사하게 MSR/MRS 명령어로 접근하는 시스템 레지스터를 사용합니다. 시스템 레지스터는SCTLR_ELx, TTBR0_EL1, TCR_EL1 등 EL별로 나뉩니다.

레지스터EL용도
SCTLR_EL1EL1System Control, MMU, 캐시 활성화
TTBR0_EL1EL1Translation Table Base 0 (User)
TTBR1_EL1EL1Translation Table Base 1 (Kernel)
TCR_EL1EL1TLB 제어, ASID 크기, 메모리 속성
MAIR_EL1EL1Memory Attribute Index Register
SPselEL0/1스택 포인터 선택
CurrentELAll현재 Exception Level (읽기 전용)
DAIFEL0/1인터럽트 마스크 (D/I/F)
TPIDR_EL0EL0사용자 스레드(Thread) ID
TPIDR_EL1EL1커널 TLS base
CNTV_CTL_EL0EL0가상 타이머 제어
CPACR_EL1EL1협프로세서 액세스
VBAR_EL1EL1Vector 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/SPIirq-gic.c
GICv3Redistributor, LPI, MSI 지원 (redistributor 최대 255개)irq-gic-v3.c
GICv4가상화 직렬화(Serialization), vPE tableirq-gic-v4.c
GICv4.1Extended LPI range, hierarchical cacheirq-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 등의 옵션이 있습니다.

/* 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 Exception Level 계층 높을수록 권한 큼 · 낮은 EL→높은 EL은 예외(SVC·HVC·SMC)로만 진입, ERET로 복귀 권한 높음 권한 낮음 EL3 Secure Monitor — 시큐어 모니터 ATF/TF-A 펌웨어 · 최고 권한 TrustZone 시큐어 월드/일반 월드 전환 제어 핵심 레지스터 SCR_EL3 · MVBAR_EL3 SMC — 시큐어 모니터 호출 ERET EL2 Hypervisor — 하이퍼바이저 KVM 실행 계층 · 게스트 OS 격리 Stage-2 주소 변환(VTTBR_EL2) 제어 핵심 레지스터 HCR_EL2 · VTTBR_EL2 · VBAR_EL2 HVC — 하이퍼바이저 호출 ERET EL1 OS 커널 — 리눅스 커널이 실행되는 레벨 시스템 콜 · 인터럽트 · 예외 처리 페이지 테이블 관리 · MMU 설정 핵심 레지스터 SCTLR_EL1 · TTBR0_EL1 · TTBR1_EL1 · TCR_EL1 MAIR_EL1 · VBAR_EL1 · CPACR_EL1 · TPIDR_EL1 SVC — 시스템 콜 ERET EL0 User Application — 사용자 애플리케이션 최저 권한 · 일반 애플리케이션 실행 권한이 필요한 작업은 시스템 콜로 커널에 위임 핵심 레지스터 TPIDR_EL0 · CNTV_CTL_EL0 동작 원리 예외 진입 (SVC · HVC · SMC) — VBAR_ELn 벡터 테이블로 분기, PSTATE는 SPSR_ELn에, 복귀 주소는 ELR_ELn에 저장 예외 복귀 (ERET) — ELR_ELn 주소로 복귀, SPSR_ELn에서 PSTATE 복원, 더 낮은 EL로 이동 가능 전환 규칙 — 낮은 EL에서 높은 EL로는 예외로만 진입, 외부 인터럽트(GIC)도 등록된 EL로 예외로 진입 VHE — Linux 커널이 EL2에서 직접 실행되어 KVM 호스트(EL2)·게스트(EL1) 전환 오버헤드 감소
ARM64 Exception Level 계층 — 위로 갈수록 권한이 크며, 낮은 EL에서 높은 EL로는 예외(SVC · HVC · SMC)로만 진입하고 ERET로 복귀합니다. 리눅스 커널은 굵은 테두리의 EL1에서 실행되며, 각 EL의 핵심 시스템 레지스터(SCTLR_EL1 · HCR_EL2 · SCR_EL3 등)도 함께 표시됩니다.

ARM64 부팅 과정

ARM64의 부팅 과정은 x86과 상당히 다릅니다. 대부분의 ARM64 시스템은 Device Tree(DT)를 사용하여 하드웨어 구성 정보를 커널에 전달합니다. 부팅 프로토콜은 커널 이미지의 시작점에 명시된 규약을 따릅니다.

ARM64 부팅 시퀀스와 실행 레벨 부팅 단계마다 예외 레벨 EL3→EL2→EL1→EL0로 하강 · BL31은 EL3 상주로 SMC/PSCI 제공 EL3 보안 펌웨어 EL2 하이퍼바이저 EL1 OS 커널 EL0 사용자 1 BootROM SoC 내장 코드 실행 BL1 로드·검증 2 TF-A BL1 · BL2 보안 초기화·이미지 검증 BL31 · BL33 적재 BL31 (Secure Monitor) · EL3 상주 — SMC/PSCI 서비스 3 U-Boot / UEFI 커널·initramfs 적재 x0 ← DTB 주소 저장 4 Linux 커널 진입 _start (head.S) MMU 활성화 · EL 확인 5 start_kernel() 커널 초기화 수행 init 스레드 생성 6 init / systemd 첫 사용자 프로세스 SMC (PSCI) ERET — EL3 → EL2 정상 월드 전환 EL2 → EL1 강등 (nVHE) VHE면 EL2 유지 첫 사용자 공간 진입 참고 핵심 인자 — x0 = DTB 물리 주소 · x1~x3 = 0(예약), booting.rst 부팅 프로토콜 준수 EL 결정 — 부트로더는 높은 EL(EL2)에서 커널로 점프 · VHE면 EL2 유지 · 미지원 시 EL1 강등(EL2는 KVM 전용) 호출 규칙 — 하위 EL은 SVC(시스템 콜)로만 커널 서비스 요청 · SMC(PSCI)는 EL3 BL31로 요청 실행 흐름 SMC/PSCI 요청 레인 경계
ARM64 부팅 단계(BootROM → TF-A → U-Boot/UEFI → 커널 → 사용자 공간)와 각 단계가 실행되는 특권 레벨(EL3/EL2/EL1/EL0), EL3 상주 BL31과의 SMC 관계

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 */
    };
};
TIP: Device Tree 소스 파일은 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)는 세 가지 차원에서 발생할 수 있습니다:

이러한 재순서화를 방지하기 위해 ARM64는 메모리 배리어(Memory Barrier) 명령어를 제공합니다. 배리어는 종류에 따라 범위가 다르며, 각 배리어는 다른 상황에서 사용됩니다:

명령어종류의미커널 매크로(Macro)
DMBData Memory Barrier데이터 메모리 접근 순서 보장(Ordering)smp_wmb(), smp_rmb(), smp_mb()
DSBData Synchronization Barrier이전 모든 메모리 접근 완료 후 다음 명령 실행mb() (완전 배리어)
ISBInstruction 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)으로 연결될 수 있습니다. 반드시 아키텍처 독립 배리어 매크로를 사용해야 합니다:

x86 → ARM64 포팅 시 주의: x86에서 배리어 없이 동작하던 코드는 ARM64에서 재순서화로 인해 버그가 발생할 수 있습니다. 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_MTEUser MTE5.16+사용자 공간 MTE (MTE-capable ABI, prctl(PR_SET_TAGGED_ADDR_CTRL))
CONFIG_KASAN_HW_TAGSKernel MTE6.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를 커널 컴파일에 적용하려면 다음 두 가지 방법이 있습니다:

# 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 vs KASAN 비교: MTE는 하드웨어 기반이므로 KASAN 대비 오버헤드가 훨씬 작지만 (정도는 하드웨어·워크로드에 따라 달라짐), KASAN은 스택 버퍼 오버플로, 전역 변수, shadow memory 기반 디버깅을 포함해 훨씬 넓은 범위의 메모리 오류를 감지합니다. 둘을 함께 사용하면 가장 포괄적인 메모리 안전성 검사가 가능합니다.

MTE 커널 사용 시 Tag Check Fault가 발생하면 커널은 해당 faults의 종류에 따라 다음 중 하나를 수행합니다:

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) 접근 권한이 다릅니다.

RISC-V CSR (Control and Status Register)

RISC-V는 특권 모드별로 다른 CSR을 사용합니다. M-mode, S-mode, U-mode 각각 접근 가능한 CSR이 정의됩니다.

CSR모드설명
mstatusMMachine Status, MIE, MPIE 등
mieMMachine Interrupt Enable
mtvecMMachine Trap Vector Base
mepcMMachine Exception PC
mcauseMMachine Cause (예외 번호)
mtvalMMachine Trap Value (주소 등)
sstatusSSupervisor Status
sieSSupervisor Interrupt Enable
stvecSSupervisor Trap Vector
sepcSSupervisor Exception PC
scauseSSupervisor Cause
stvalSSupervisor Trap Value
satpSSupervisor Address Translation and Protection
scounterenSSupervisor 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)을 주로 사용합니다.

/* 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의 인터럽트는 두 가지 유형으로 나뉩니다:

/* 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이름기능
0x10baseBase 확장 (spec 버전·구현 정보 조회)
0x54494D45time타이머 설정 (gettimeofday)
0x735049ipiInter-Processor Interrupt
0x52464E43rfenceRemote fence (TLB flush)
0x48534DhsmHart State Management (부팅/종료)
0x505343pscPower State Control (시스템 suspend/shutdown)
0x444D4EdmDebug Channel (디버그)
0x53525354srstSystem Reset (리셋/파워오프)
RISC-V 특권 모드 계층 높을수록 특권 큼 · ecall로 상위 모드 진입, mret/sret로 복귀 · 리눅스 커널은 S-mode에서 실행 특권 높음 특권 낮음 M-mode — Machine 레벨 3 (0b11) OpenSBI/펌웨어 실행 — 모든 CSR·하드웨어 직접 접근 SBI 서비스 제공: 타이머·IPI·콘솔 등 ecall (SBI 호출) mret S-mode — Supervisor 레벨 1 (0b01) Linux 커널이 실행되는 모드 시스템 콜·예외·인터럽트 처리와 가상 메모리 관리 ecall (시스템 콜) sret U-mode — User 레벨 0 (0b00) 일반 애플리케이션 실행 — 특권 명령·시스템 CSR 접근 제한 시스템 콜 시 ecall 트랩으로 S-mode 커널에 위임 CSR 접근 범위 모든 CSR 접근 가능 mtvec · mepc · mstatus mcause · mscratch S/U 레벨 CSR 접근 stvec · sepc · sstatus satp · sscratch · scause 사용자 CSR만 접근 time · cycle · instret (읽기 전용 성능 카운터) 동작 방식 트랩 진입 (ecall) — 목표 모드의 벡터(mtvec/stvec)로 분기 — mepc/sepc에 복귀 주소, mstatus/sstatus(MPP/SPP)에 이전 모드 저장 트랩 복귀 (mret/sret) — mepc/sepc 주소로 복귀하고 MPP/SPP 값으로 이전 특권 모드를 복원 예외 위임 (medeleg) — OpenSBI가 U-mode 예외를 S-mode로 위임 — 일반 시스템 콜은 M-mode를 거치지 않고 직접 트랩
RISC-V 특권 모드 계층 — 위로 갈수록 특권이 높아지고, ecall로 상위 모드에 진입한 뒤 sret/mret로 복귀합니다. 리눅스 커널은 굵은 테두리의 S-mode에서 실행됩니다.

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입니다.

RISC-V 부팅 시퀀스와 SBI 경계 부팅 제어는 mret으로 M-mode 펌웨어에서 S-mode로 이전 · 이후 머신 기능은 ecall(SBI)로만 요청 M-mode (Machine) 펌웨어 — 하드웨어 직접 접근 · SBI 서비스 제공 1 BootROM (FSBL) ROM의 1차 부트로더 (BootROM) DRAM 초기화 후 OpenSBI로 점프 2 OpenSBI (M-mode 펌웨어) 하드웨어 직접 접근 — 타이머·IPI·콘솔·HSM 서비스 제공 다음 단계를 S-mode로 진입(mret)시킨 뒤 트랩 대기 SBI 호출 경계 — ecall / sret (M ↔ S) S-mode (Supervisor) 부트로더·OS — 머신 기능은 SBI(ecall)로만 요청 3 U-Boot 커널·DTB·initramfs를 메모리에 적재 _start로 점프 (S-mode 유지) 4 Linux Kernel (S-mode) a0=hartid · a1=DTB 물리 주소로 _start 진입 setup_vm() → start_kernel() 초기화 mret — S-mode 전환 ecall (요청) sret (복귀) 참고 커널 엔트리 인자 — a0 = 부팅 HART 번호 · a1 = DTB 물리 주소 (RISC-V 부팅 프로토콜) 대표 SBI 호출 — sbi_set_timer(타이머) · sbi_send_ipi(IPI) · sbi_hart_start(HSM) · sbi_srst_system_reset(리셋) 서비스 경로 — S-mode 커널은 ecall로 요청 → OpenSBI가 M-mode에서 처리 → sret 복귀 · 시스템 콜은 U→S로 직접 처리 (M-mode 미경유) 실행 흐름 SBI 요청/복귀 SBI 경계
부팅 제어는 M-mode 펌웨어(BootROM→OpenSBI)에서 mret으로 S-mode(U-Boot→커널)에 넘어가며, 이후 머신 기능은 ecall로만 OpenSBI에 요청합니다.
HART: RISC-V에서 CPU 코어에 해당하는 개념을 HART(Hardware Abstraction for RISC-Threading)라고 합니다. 멀티코어 시스템에서는 여러 HART가 존재하며, 부팅 시 하나의 HART만 부팅 코드를 실행하고 나머지는 WFI(Wait For Interrupt) 상태에서 대기합니다.

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와 세그먼트 기반 주소 공간이라는 독특한 구조를 가집니다.

참고: 이 문서에서는 커널 아키텍처 수준(특권 모드, CP0, 주소 공간, TLB, 예외 벡터, 부팅, 메모리 순서)만 다룹니다. 명령어 세트, o32/n32/n64 ABI, 지연 슬롯(Delay Slot), EVA(Enhanced Virtual Addressing), VZ(Virtualization) 확장 등 명령어 수준 상세는 MIPS 명령어셋 (ISA) 문서를 참고하세요.

개요 (Overview)

MIPS는 설계 초기부터 빅엔디안(Big-Endian)을 기본으로 채택했으며, 일부 구현은 리틀엔디안 (MIPSel)도 지원합니다. 커널은 엔디안(Endianness)에 따라 CPU_LITTLE_ENDIAN과 CPU_BIG_ENDIAN Kconfig 옵션으로 나뉘며, 배포판 크로스 툴체인 접두사도 mipsel-linux-gnu-처럼 달라집니다. 커널이 지원하는 MIPS 계열은 다음과 같습니다:

Release 6 (R6): MIPS Release 6는 컴팩트 분기(Compact Branch) 도입으로 지연 슬롯을 제거하고 명령어 필드를 재구성했습니다. 커널은 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와 무관하게 자동으로 커널 모드로 전환합니다.

CP0 Status 레지스터 ($12)

Status 레지스터는 특권 모드·인터럽트·예외 상태를 한꺼번에 관리합니다. 리눅스 인터럽트 비활성화 루틴(local_irq_disable/arch_local_irq_disable)은 이 레지스터의 IE 비트를 mfc0/mtc0로 직접 조작합니다. 핵심 비트는 다음과 같습니다:

비트이름설명
0IE전역 인터럽트 활성화 (리눅스 local_irq_enable/disable)
1EXL예외 레벨. 설정 시 자동으로 커널 모드 진입
2ERL에러 레벨 (Reset/NMI/Cache Error 처리 중)
4:3KSU[1:0]특권 모드 선택 (00=커널, 01=슈퍼바이저, 10=유저)
15:8IM[7:0]개별 인터럽트 마스크 (외부 6개 + 소프트웨어 2개)
22BEV부트 예외 벡터 선택 (1=0xBFC00000, 0=0x80000000 기준)
28CU0Coprocessor 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를 통해 이루어집니다. 주요 레지스터는 다음과 같습니다:

번호이름설명
0IndexTLB 인덱스 (TLBWI/TLBP 결과)
1RandomTLBWR용 랜덤 인덱스
2-3EntryLo0/1TLB 엔트리 하위 비트 (짝수/홀수 페이지 쌍)
4ContextTLB 미스 시 PTE 주소 계산 (PTEBase + BadVPN2)
5PageMaskTLB 페이지 크기 마스크
6WiredTLBWR에서 보호할 고정 엔트리 수
8BadVAddr가장 최근 주소 오류의 가상 주소
9Count프로그램 가능 카운터 (타이머 기준)
10EntryHiTLB 엔트리 상위 비트 (VPN2, ASID)
11CompareCount 비교값 (일치 시 타이머 인터럽트)
12Status프로세서 상태 (IE, EXL, ERL, KSU, IM 등)
13Cause예외 원인 (ExcCode, IP, BD 등)
14EPC예외 발생 PC (복귀 주소)
15PRId프로세서 ID (구현자/버전)
15.1EBase예외 벡터 베이스 주소 (R2+, 리눅스가 코어별로 설정)
16ConfigCache, TLB, 엔디안(Endianness) 등 하드웨어 구성
30ErrorEPC에러 레벨 예외의 PC
타이머: Count/Compare가 리눅스 클록소스(Clock Source)의 기준입니다. 리눅스는 cevt-r4k 드라이버(drivers/clocksource/mips-gic-timer.c 및 arch/mips/kernel/cevt-r4k.c)로 주기적 타이머 인터럽트를 구현합니다. 랜덤 TLB 인덱스는 리눅스 보안 하드닝에서 커널 매핑을 흩뿌리는(Randomization) 용도로도 이용됩니다.

주소 공간 (Address Space)

32비트 MIPS의 주소 공간은 세그먼트 기반입니다. 최상위 비트가 세그먼트를 결정하며, kseg0/kseg1은 TLB를 거치지 않고 물리 메모리에 직접 매핑되는 것이 핵심 특징입니다. 이 때문에 커널은 초기 부팅 단계에서 페이지 테이블(TLB 엔트리) 설정 없이 코드를 실행할 수 있습니다.

MIPS 32-bit 가상 주소 공간 레이아웃 전체 4GB를 5개 세그먼트로 분할 — kseg0/1은 TLB 없이 물리 메모리 직접 매핑, kuseg/kseg2/3은 소프트웨어 TLB 관리 · 박스 높이는 실제 크기 비례 주소 범위 세그먼트 크기 속성 kseg3 0xE0000000 0xFFFFFFFF 512MB 커널 매핑 — TLB 사용 vmalloc, ioremap, 모듈 영역 kseg2 0xC0000000 0xDFFFFFFF 512MB 커널/슈퍼바이저 매핑 — TLB 사용 결과적으로 리눅스가 가상 매핑에 광범위 활용 kseg1 0xA0000000 0xBFFFFFFF 512MB 커널 비매핑·비캐시 — TLB 미사용 MMIO, 부트 코드 (PA = VA − 0xA0000000) kseg0 0x80000000 0x9FFFFFFF 512MB 커널 비매핑·캐시 — TLB 미사용 커널 코드/데이터 (PA = VA − 0x80000000) kuseg 0x00000000 0x7FFFFFFF 2GB 유저 매핑 — TLB 사용 유저 프로세스 코드/데이터/스택/힙 직접 매핑 — kseg0/kseg1은 물리 하위 512MB 직접 매핑 (PA = VA & 0x1FFFFFFF) — TLB 초기화 이전 부팅 단계에서도 커널 코드 실행 가능
MIPS 32-bit 가상 주소 공간 — 4GB를 5개 세그먼트(kuseg 2GB, kseg0~3 각 512MB)로 나누며 박스 높이가 실제 크기에 비례합니다. kseg0/1은 TLB 없이 물리 메모리를 직접 매핑하고, kuseg/kseg2/3은 소프트웨어 TLB로 관리됩니다.
PAGE_OFFSET: 리눅스 MIPS에서는 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가 생성한 최적화된 핸들러 코드를 사용합니다.

MIPS 소프트웨어 관리 TLB — TLB 미스 처리 흐름 CPU는 페이지 워크를 하지 않음 — TLB 미스 시 커널 핸들러가 PTE 주소를 계산해 EntryLo 쌍을 로드하고 TLB에 기록 후 복귀 1 TLB 미스 발생 (TLBL/TLBS) 하드웨어 페이지 워크 없음 — CPU가 예외만 발생 2 예외 벡터 진입 EXL=0 → 0x80000000 (전용 Refill 벡터) / EXL=1 → 0x80000180 (General) 3 PTE 주소 계산 (Context 레지스터) PTEBase[31:23] | BadVPN2[22:4] → 미스 PTE 위치 직접 산출 4 PTE 쌍 → EntryLo0/EntryLo1 로드 짝수/홀수 페이지 PTE를 lw로 두 번 읽어 채움 5 TLBWR/EHb → ERET 복귀 Random 인덱스에 기록 · EHb로 해저드 제거 · EPC로 복귀
MIPS 소프트웨어 관리 TLB 미스 처리 흐름 — TLB 미스가 발생하면 CPU는 페이지 워크 없이 예외만 발생시키고, 커널 핸들러가 전용 Refill 벡터로 진입해 Context 레지스터로 PTE 주소를 계산하고 PTE 쌍을 EntryLo0/1에 로드한 뒤 TLBWR로 TLB에 기록하고 EPC로 복귀합니다.
/* 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로 이동) */
TLB 셧다운 위험: MIPS의 TLB는 소프트웨어 관리 방식이라 예외 벡터 코드 실행 중에도 해당 영역을 매핑한 TLB 엔트리가 필요합니다. 부팅 초기 매핑을 재구성할 때는 코드가 실행 중인 엔트리를 순간적으로 덮어쓰지 않도록 TLBWR/TLBWI 순서와 ehb 배치가 중요합니다. 잘못될 경우 커널이 TLB 셧다운(Shutdown) 상태에 빠질 수 있습니다.

예외 벡터 (Exception Vectors)

MIPS는 예외 종류에 따라 고정된 예외 벡터(Exception Vector) 주소로 분기합니다. Status.BEV가 0이면 kseg0(캐시) 기반, 1이면 0xBFC00000(비캐시 ROM) 기반으로 이동합니다. 부팅 초기 BEV=1에서 커널이 초기화 후 BEV=0으로 전환합니다.

벡터 주소 (BEV=0)예외설명
0x80000000TLB Refill32비트 주소의 TLB 미스 (TLBL/TLBS, EXL=0일 때)
0x80000080XTLB Refill64비트 주소의 TLB 미스 (MIPS64)
0x80000100Cache Error캐시 패리티/ECC 오류 (ERL=1 상태)
0x80000180General ExceptionSYSCALL, TLB Modify, Address Error, 인터럽트 등 모든 일반 예외
0x80000200Interrupt (IV=1)직렬 인터럽트 전용 벡터 (선택적)
0xBFC00000Reset / NMI (BEV=1)리셋/하드웨어 오류 — 비캐시 ROM 영역

General Exception 벡터에 도달하면 커널은 Cause.ExcCode 필드로 핸들러를 선택합니다. arch/mips/kernel/genex.S의 except_vec3_generic이 이 디스패치를 수행합니다:

ExcCode의미커널 핸들러
0Interrupt (인터럽트)handle_int
1Mod (TLB 수정 시도)handle_tlbm
2TLBL (TLB 로드 미스)handle_tlbl
3TLBS (TLB 저장 미스)handle_tlbs
4AdEL (주소 오류 — 로드)handle_adel
5AdES (주소 오류 — 저장)handle_ades
8Sys (SYSCALL)handle_sys
9Bp (BREAK)handle_bp
10RI (예약 명령어)handle_ri
11CpU (코프로세서 불가)handle_cpu
13Ov (오버플로)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) */
RISC-V와의 유사점: MIPS 부팅도 인자 레지스터(a0~a3)로 정보를 전달하고, 펌웨어가 커널 엔트리로 직접 분기한다는 점에서 RISC-V와 개념적으로 유사합니다. 다만 RISC-V는 SBI라는 공식 펌웨어 인터페이스가 있고, MIPS는 U-Boot/YAMON/PMON 등 보드 의존적인 각기 다른 규약이 공존합니다.

메모리 순서 모델 (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+) */
LL/SC 비배리어: LL/SC는 그 자체로 메모리 순서 배리어가 아닙니다. 스핀락 등 락 구현에서는 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 확장이 있는 하드웨어(옥테온 등)에서만 동작합니다.

VZ와 RISC-V 비교: RISC-V의 H 확장(Hypervisor Extension)이 게스트 특권 모드(GS/VS)를 도입한 것처럼, MIPS VZ는 하드웨어가 게스트 모드(Guest Mode)는 하기 위해 GuestCtl/GuestCtl1/GuestCtl2CP0 레지스터 군과 Guest Entry/EPC 메커니즘을 제공합니다. 상세 레지스터 구성은 VZ (Virtualization Extension)를 참고하세요.

유지보수 현황 (2025-2026)

구분내용
커널 상태신규 기능 개발보다 유지보수·버그 수정 중심. 주요 배포판의 공식 MIPS 포트는 없습니다.
R6 지원CONFIG_CPU_MIPS32_R6/CONFIG_CPU_MIPS64_R6로 메인라인 유지
LoongsonLoongson 계열은 2023년 이후 MIPS 호환 모드에서 자체 LoongArch ISA로 주력 이동
실사용OpenWRT 호환 공유기, Cisco/임베디드 네트워크 장비, Ingenic/Lexra SoC 등 레거시 장비
최근 변경v6.13 멀티클러스터 인터럽트 컨트롤러, v6.14 PCI 레거시 주소 변환, v6.18 TLB 유일화 스택 버그 수정 등 (상세)
향후 전망: 신규 프로젝트에는 RISC-V를 선택하는 것이 권장됩니다. 다만 대량의 기존 MIPS 기기(Loongson, 보급형 라우터, 광섬유 장비)의 장기 지원을 위해 리눅스 커널의 arch/mips는 LTS 기반으로 계속 유지되며, 버그 수정·툴체인 호환성이 유지되고 있습니다.

특권 레벨 비교 (Privilege Level Comparison)

운영체제가 여러 프로그램을 동시에 실행할 때, 한 프로그램이 다른 프로그램의 메모리를 덮어쓰거나 하드웨어를 직접 제어하면 시스템 전체가 무너집니다. 이를 막기 위해 CPU는 실행 코드가 "얼마나 많은 권한을 갖느냐"를 하드웨어 수준에서 구분합니다 — 이것이 특권 레벨(Privilege Level)입니다.

특권 레벨이 낮은 코드(사용자 애플리케이션)는 직접 하드웨어에 접근하거나 다른 프로세스의 메모리를 읽으려 하면 CPU가 즉시 예외(Exception)를 발생시켜 커널에 제어권을 넘깁니다. 커널은 특권 레벨이 가장 높은 곳에서 실행되며, 이 경계가 OS의 보안과 안정성의 근본입니다. 세 아키텍처는 이 개념을 각기 다른 이름과 계층 수로 구현합니다:

아키텍처별 특권 레벨 비교 (Privilege Level Comparison) 같은 행(밴드) = 유사한 역할의 레벨 · 굵은 테두리 = 리눅스 커널 실행 레벨 · 점선 테두리 = 선택적 가상화 확장 x86_64 Ring · VMX 모델 Linux 커널 : Ring 0 ARM64 (AArch64) Exception Level (EL) Linux 커널 : EL1 RISC-V 특권 모드 (M · HS · S · U) Linux 커널 : S-mode 높음 낮음 펌웨어 · 보안 하이퍼바이저 커널 사용자 Ring -2 (SMM) 시스템 관리 모드 (SMI 펌웨어) EL3 Secure Monitor (ATF BL31) M-mode 머신 모드 (OpenSBI) Ring -1 (VMX Root) 하이퍼바이저 (KVM) EL2 하이퍼바이저 (KVM) HS-mode (H 확장) 가상화 확장 · 하이퍼바이저 (KVM) VHE 활성 시 커널이 EL2에서 직접 실행 Ring 0 Linux 커널 (커널 모드) EL1 Linux 커널 (OS Kernel) S-mode Linux 커널 (슈퍼바이저) Ring 1, 2 : 미사용 (x86) 커널 · 사용자 경계 Ring 3 사용자 모드 (애플리케이션) EL0 사용자 애플리케이션 U-mode 사용자 애플리케이션 커널 실행 레벨 (굵은 테두리) 일반 특권 레벨 선택적 확장 (가상화 · H 확장) 커널 · 사용자 경계
x86_64, ARM64, RISC-V의 특권 레벨 비교 - 같은 행(밴드)은 유사한 역할의 레벨이며, 세로축은 위로 갈수록 특권이 높습니다. 굵은 테두리는 각 아키텍처에서 리눅스 커널이 실행되는 레벨(Ring 0 / EL1 / S-mode), 점선 테두리는 선택적 확장 레벨(가상화)입니다.

위 다이어그램에서 주목할 점은 리눅스 커널이 각 아키텍처에서 다른 특권 레벨에서 동작하는 것입니다:

VHE (Virtualization Host Extensions): ARM64에서 CONFIG_ARM64_VHE=y가 활성화되면, 리눅스 커널이 EL2에서 직접 실행될 수 있습니다. 이를 통해 KVM 호스트 커널이 EL2에서 동작하고, 게스트 OS가 EL1에서 실행되어 가상화 전환 오버헤드가 크게 줄어듭니다.

시스템 콜/예외 진입 경로 비교

사용자 공간에서 커널 공간으로 전환되는 공통 경로는 시스템 콜, 인터럽트, 예외입니다. 아키텍처별 진입 명령과 복귀 명령은 다르지만, 커널이 트랩 프레임을 저장하고 핸들러를 호출한 뒤 사용자 공간으로 복귀하는 큰 흐름은 동일합니다.

아키텍처별 사용자→커널 진입 경로 세 아키텍처가 동일한 5단계 흐름을 따름 — 진입 명령 · 트랩 벡터 · 복귀 명령만 아키텍처마다 다름 x86_64 커널 실행 레벨 : Ring 0 ARM64 커널 실행 레벨 : EL1 RISC-V 커널 실행 레벨 : S-mode 사용자 모드 1. 사용자 실행 애플리케이션 (Ring 3) 1. 사용자 실행 애플리케이션 (EL0) 1. 사용자 실행 애플리케이션 (U-mode) 2. 진입 트리거 SYSCALL / INT n / #PF 2. 진입 트리거 SVC #0 / IRQ / Data Abort 2. 진입 트리거 ecall / interrupt / exception 사용자→커널 전환 커널 모드 3. 트랩 벡터 진입 entry_SYSCALL_64 / IDT 핸들러 3. 트랩 벡터 진입 EL1 벡터 (VBAR_EL1) 진입 3. 트랩 벡터 진입 stvec 트랩 엔트리 진입 4. 커널 핸들러 do_syscall_64() do_page_fault() 트랩 프레임 저장 4. 커널 핸들러 el0_svc do_mem_abort() 트랩 프레임 저장 4. 커널 핸들러 do_trap_ecall_u() do_page_fault() 트랩 프레임 저장 커널→사용자 복귀 사용자 모드 5. 사용자 복귀 SYSRETQ / IRETQ 로 Ring 3 복귀 5. 사용자 복귀 ERET 로 EL0 복귀 5. 사용자 복귀 SRET 로 U-mode 복귀 사용자 모드 (띠) 커널 모드 (띠) 특권 전환 지점 (두꺼운 점선)
시스템 콜/예외 진입 경로 비교 - 세 아키텍처가 동일한 5단계(사용자 실행 → 진입 트리거 → 트랩 벡터 → 커널 핸들러 → 사용자 복귀)를 따르고, 진입 명령·트랩 벡터·복귀 명령만 아키텍처마다 다릅니다. 왼쪽 띠는 각 단계의 특권 상태(사용자 모드/커널 모드)를, 굵은 점선은 특권 전환 지점을 나타냅니다.

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)

리눅스 커널 소스 코드는 기능별로 잘 정리된 디렉토리 구조를 가지고 있습니다. 커널 개발을 시작할 때 이 구조를 이해하는 것이 매우 중요합니다.

리눅스 커널 소스 트리(linux/) 구조 linux/ (커널 소스 트리 루트) arch/ — 아키텍처(Architecture) 의존 코드 x86/ arm64/ riscv/ mips/ arch/<arch>/kernel/ 시스템 콜·인터럽트 처리·SMP arch/<arch>/mm/ 페이지 테이블·TLB 조작 · boot/ 부트 코드 · configs/ defconfig kernel/ · mm/ · ipc/ — 커널 핵심 코어 kernel/ mm/ ipc/ kernel/ 스케줄링(sched/)·동기화(locking/)·시간·RCU mm/ 버디 할당자(page_alloc.c)와 SLUB(slub.c) · ipc/ System V IPC와 메시지 큐 fs/ · net/ · block/ — 파일 시스템과 네트워크 fs/ net/ block/ fs/ VFS와 파일시스템 구현(ext4/·btrfs/·proc/·sysfs/) net/ 소켓 계층과 프로토콜 스택(ipv4/·ipv6/) · block/ 블록 I/O 요청 처리 drivers/ — 디바이스 드라이버 (가장 큰 디렉터리) net/ pci/ usb/ gpu/ scsi/ input/ 하드웨어 지원 코드의 대부분이 집중된 소스 트리 내 최대 규모 디렉터리 버스 추상 위에 base/ driver model(platform/ 등)이 동작 include/ · init/ · virt/ — 공용 헤더와 부팅 진입 include/ init/ virt/ include/ 공용 헤더 — linux/ 아키텍처 독립, uapi/ 사용자 공간(Userspace) ABI 헤더 init/main.c start_kernel()가 부팅 첫 단계 · virt/ KVM 하이퍼바이저 lib/ · crypto/ · security/ — 지원 영역과 빌드 lib/ crypto/ security/ scripts/ lib/ 공용 유틸리티 · crypto/ 암호화 API와 드라이버 · security/ LSM 훅 등록 루트 Makefile과 Kconfig가 전체 빌드를 제어(defconfig, menuconfig) 요약 — 핵심 로직은 kernel/ · mm/ · fs/ · net/, 하드웨어 지원은 drivers/, 플랫폼 종속은 arch/, 공용 헤더는 include/
linux/ 루트에서 왼쪽 수직 분배선을 거처 6개 역할 그룹으로 연결되는 구조 — 플랫폼 종속 arch/, 핵심 코어 kernel/·mm/·ipc/, 파일 시스템·네트워크 fs/·net/·block/, 하드웨어 지원 대부분 drivers/(최대 디렉터리), 공용 헤더·부팅 진입 include/·init/·virt/, 지원 영역·빌드 lib/·crypto/·security/ — 색상이 역할을, 칩이 대표 서브디렉토리를 나타냅니다
TIP: drivers/ 디렉터리는 트리 내 최대 규모의 디렉터리로, 하드웨어 지원 코드의 대부분이 여기에 위치합니다. 커널의 핵심 로직은 kernel/, mm/, fs/, net/에 집중되어 있으며, 이 디렉토리들의 코드를 이해하면 커널의 핵심 동작 원리를 파악할 수 있습니다. 소스 코드 탐색에는 Bootlin Elixir Cross-referencer가 매우 유용합니다.

arch/ 디렉토리 상세 (Architecture Directory Detail)

arch/ 디렉토리 아래의 각 아키텍처 디렉토리는 비슷한 하위 구조를 가집니다. 이는 커널의 아키텍처 추상화 설계 원칙을 반영합니다:

커널은 이러한 구조를 통해 아키텍처 독립적인 코드(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 게스트 격리
다음 단계: 커널 아키텍처의 기본 개념을 이해했다면, 다음으로 빌드 시스템 (Build System) 문서에서 실제로 커널을 빌드하는 방법을 학습하거나, 메모리 관리 문서에서 각 아키텍처의 페이지 테이블과 메모리 할당 메커니즘을 심층적으로 살펴볼 수 있습니다. 또한 부팅 과정 문서에서 각 아키텍처별 부팅 흐름을 더 자세히 확인할 수 있습니다.

아키텍처별 성능 최적화 (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;
}
SW Prefetch 주의사항: prefetch 명령은 "Cache-Line을 캐시에 로드한다"는 힌트일 뿐, 메모리 접근이 아닙니다. TLB 미스(TLB Miss)는 prefetch로 해결되지 않습니다. 또한, prefetch 호출 시점과 실제 접근 사이의 간격이 너무 짧으면 오히려 Prefetch 유닛을 점유해 성능을 악화할 수 있습니다. 일반적으로 prefetch된 데이터가 실제 접근되기까지 상당한 양의 중간 작업이 있을 때 SW prefetch가 효과를 냅니다(적정 간격은 구현 세대·워크로드마다 다름).

분기 예측(Branch Prediction) 최적화

x86_64 CPU는 동적 분기 예측(Dynamic Branch Prediction)을 통해 분기의 실제 결과(진입 or 비진입)를 학습하고 다음 실행에 활용합니다. 분기 예측이 실패하면 파이프라인이 플러시되고 수십 사이클 규모의 지연(Latency)이 발생하며, 그 크기(Penalty)는 구현 세대에 따라 달라집니다. 커널 코드에서 분기 예측 효율을 높이는 방법은 다음과 같습니다:

/* 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 인식 기능을 여러 계층에서 제공합니다:

/* 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를 사용하는 대표적인 예:

/* 커널 모드 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);
}
SIMD 커널 사용 주의: 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 최적화 기법:

# 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
FRED/GCS 설정: FRED은 CPU가 지원해야 활성화되며, 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를 사용하는 것을 권장합니다.

MSR 접근: 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 명령의 종류에 따라 재배치 허용 범위 제어합니다.

명령DomainScope커널 매크로
DMB ISHInner ShareableCache coherent domainsmp_mb()
DMB ISHLDInner ShareableLoadLoad barriersmp_rmb()
DMB ISHSTInner ShareableStore→Load 재배치만 차단 (Store-Load barrier)smp_wmb()
DMB SYFull SystemLoad/Store 모두mb() (strongest)
DSB ISHInner ShareableMemory access completion barrier (DSB ISH)cache/maintains flush
DSB SYFull SystemAll accesses completeContext switch, KASLR
ISB-Pipeline flush after instruction changeisb()

ARM64에서 분기 예측과 명령 Pipeline과 같은 하드웨어 특징은 다음과 같습니다:

/* 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 패칭) */
ARM64 LSE 확인: 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의 주요 특징:

/* 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의 핵심 특징:

/* 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 (행렬 곱-누적) */
SME 지원 확인: 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가 성능에 미치는 영향:

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 명령의 효율적 활용이 중요합니다.

/* 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: 사용 목적에 따라 다릅니다:

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: 가상 주소 공간 크기에 따라 결정됩니다:

레벨이 많을수록 주소 공간은 넓어지지만 페이지 테이블 워킹 오버헤드가 증가합니다. 자세한 내용은 메모리 관리 문서를 참고하세요.

Q6. 아키텍처별 시스템 콜 차이는?

A: 시스템 콜 번호와 진입 메커니즘이 다릅니다:

모든 아키텍처는 공통 시스템 콜 인터페이스를 제공하지만, 일부 아키텍처별 시스템 콜이 존재할 수 있습니다. 자세한 내용은 시스템 콜 (System Call) 문서를 참고하세요.

Q7. 가상화 오버헤드는 얼마나 되나요?

A: 하드웨어 가상화 지원 여부에 따라 크게 다릅니다:

현대 시스템에서는 하드웨어 가상화 지원이 표준이므로 오버헤드가 크지 않습니다. 자세한 내용은 가상화 (KVM) 문서를 참고하세요.

Q8. PREEMPT_RT와 PREEMPT의 차이는 무엇인가요?

A: 두 옵션은 선점(preemption) 범위가 다릅니다:

v6.12 이전에는 별도의 외부 패치(Patch)(rt-patches)를 적용해야 했으나, 이제 메인라인 커널만으로 실시간(Real-time) 커널을 빌드할 수 있습니다. 자세한 내용은 RT-Mutex 문서를 참고하세요.

Q9. FRED가 기존 IDT를 완전히 대체하나요?

A: FRED(Flexible Return and Event Delivery)는 Intel이 도입한 새로운 이벤트 전달 메커니즘으로, v6.12에서 커널 지원이 추가되었습니다.

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! 명령어
Intel SDM과의 차이점: AMD APM Vol. 2는 AMD-V(SVM) 가상화를 상세히 다루며, 이는 Intel VT-x와 구조적으로 다릅니다. KVM 코드에서 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 전송 특성
주의 — IMPLEMENTATION DEFINED: ARM 아키텍처 매뉴얼에는 "IMPLEMENTATION DEFINED"로 표기된 동작이 많습니다. 이는 각 SoC/코어 구현에 따라 달라지므로, 실제 하드웨어에서의 동작은 해당 코어의 TRM(Technical Reference Manual)과 SoC 벤더의 데이터시트를 추가로 확인해야 합니다.

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 인터럽트 체계
RISC-V의 특징: 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) 제거
커널 코드 대응: MIPS 문서는 RISC-V와 달리 폐쇄형이므로 최신 명령어 세부는 커널 소스 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 확장

인터커넥트 토폴로지 비교

AMD: Infinity Fabric 스타(Star) 토폴로지 CCD당 전용 xGMI 링크 (양방향) 1홉 1홉 1홉 1홉 CCD 0 8코어 · L3 32MB CCD 1 8코어 · L3 32MB CCD 2 8코어 · L3 32MB CCD 3 8코어 · L3 32MB IOD (입출력 다이) IF 스위치 · UMC(메모리 컨트롤러) · PCIe 5.0 메모리·I/O: DDR5 (UMC) · PCIe 5.0 — IOD 경유 모든 CCD ↔ IOD: 항상 1홉 · 균일 지연 CCD ↔ CCD: IOD 경유 2홉 CCD 추가 시에도 홉 수 불변 Intel: 메시(Mesh) 토폴로지 경로 예시: Core 0 → Core 1 → Core 5 → IMC = 3홉 Core 0 + L3 슬라이스 Core 1 + L3 슬라이스 Core 2 + L3 슬라이스 Core 3 + L3 슬라이스 Core 4 + L3 슬라이스 Core 5 + L3 슬라이스 Core 6 + L3 슬라이스 Core 7 + L3 슬라이스 Core 8 + L3 슬라이스 IMC 메모리 컨트롤러 Core 9 + L3 슬라이스 Core 10 + L3 슬라이스 PCIe 컨트롤러 Core 11 + L3 슬라이스 Core 12 + L3 슬라이스 Core 13 + L3 슬라이스 1 2 3 거리 비례 가변 홉 — 위치에 따라 상이 예시 경로 3홉 4×4 메시 모서리↔모서리 최대 6홉 xGMI (AMD) 메시 링크 (Intel) 홉 경로 예시 (번호) 메모리·I/O 경로 코어 + L3 타일
AMD는 모든 CCD가 중앙 IOD에 xGMI로 직접 연결되는 스타 토폴로지입니다 — CCD↔IOD는 항상 1홉(CCD↔CCD는 IOD 경유 2홉)이라 코어 위치와 무관하게 균일한 지연을 제공합니다. Intel은 코어·L3 슬라이스·IMC·PCIe 타일이 4×4 메시로 연결되어 거리에 따라 홉 수와 지연이 가변합니다(예시: Core 0 → IMC 3홉, 모서리↔모서리 최대 6홉). 본 다이어그램은 개념적 단순화이며, 실제 서버 메시의 타일 구성은 세대별로 다릅니다.

파이프라인(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-wide4-wide (2×4 이중 파이프)6-wide8-wide
ROB(Re-Order Buffer) 크기320 엔트리448 엔트리512 엔트리576 엔트리
정수 실행 포트6개 ALU + 3개 AGU6개 ALU + 4개 AGU5개 ALU + 2개 AGU6개 ALU + 3개 AGU
분기 예측기TAGE + 퍼셉트론(Perceptron), ~13K 엔트리TAGE + 퍼셉트론, ~24K 엔트리TAGE-like, ~12K 엔트리TAGE-like, ~16K 엔트리
L1 D-캐시32 KB, 8-way48 KB, 12-way48 KB, 12-way48 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
커널 코드 참조:

x86 CPU 실행 모드 (Operating Modes)

x86 프로세서는 리셋 후 리얼 모드(Real Mode)에서 시작하여 여러 모드를 거쳐 롱 모드(Long Mode)에 도달합니다. 리눅스 커널 부팅 과정은 이 모드 전환을 순차적으로 수행하며, 각 모드의 특성을 이해하는 것은 부트로더 코드와 커널 초기화 코드를 분석하는 데 필수적입니다.

모드 전환 흐름

CPU 모드 전환 흐름 ① CR0.PE=1 ② CR4.PAE=1 ③ EFER.LME=1 + CR0.PG=1 Real Mode 16-bit · 1MB 주소 BIOS/UEFI 부팅 시작점 Protected Mode 32-bit · 4GB 선형 주소 Ring 0~3 · GDT/IDT/TSS PAE 32-bit · 물리 64GB 페이징 확장 (보조 단계) Long Mode 64-bit · 256TB 가상 주소 현대 리눅스 실행 환경 Compatibility Mode 32-bit (Long Mode 하위) 레거시 32비트 실행 CS.L=0 far jump CS.L=1 부팅 경로 참고 BIOS 부팅 — Real → Protected → Long 순서를 그대로 거침 UEFI 부팅 — 펌웨어가 이미 Long Mode 상태에서 커널에 제어를 넘김 (EFI stub은 모드 전환 생략) Long Mode 활성화는 CR0.PG=1(페이징)로 완료 — 진입 직후 CS.L=0, far jump로 CS.L=1 전환 실선 박스 = CPU 실행 모드 점선 박스 = 보조 단계 · 하위 모드 (독립 모드 아님)
x86 CPU는 리셋 직후 Real Mode에서 시작하여 ① CR0.PE=1로 보호 모드(Protected Mode), ② CR4.PAE=1로 PAE(페이징 확장), ③ EFER.LME=1 + CR0.PG=1로 Long Mode에 순차 진입합니다. Long Mode 안에서는 CS.L 비트로 64비트 모드와 Compatibility(32비트) 모드를 far jump로 전환합니다.

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비트 스택 설정
Real Mode → Protected Mode 전환 단계 준비 단계 ①~③ — 모드 전환 사전 준비 ① CLI 인터럽트 금지 전환 도중 NMI·IRQ 방해 차단 cli ② A20 활성화 A20 게이트 오픈 1MB 초과 메모리 접근 허용 in al, 0x92 → out 0x92, al ③ LGDT GDTR ← GDT base + limit 보호 모드 세그먼트 디스크립터 준비 lgdt [gdt_ptr] 전환 & 정리 ④~⑥ — 스위치 · CS/세그먼트 갱신 ④ CR0.PE = 1 보호 모드 스위치 — 이 순간 전환! PE 비트(bit 0)를 1로 설정 mov cr0, eax ⑤ far JMP CS 셀렉터 0x08 적재 프리페치 큐 플러시 jmp 0x08:pm_entry ⑥ 세그먼트 재적재 DS·ES·FS·GS·SS 갱신 데이터 셀렉터 0x10 로드 mov ds, ax CR0 레지스터 비트 배치 (전체 32비트 · PE = bit 0) PG 31 CD 30 NW 29 예약 28-19 AM 18 예약 17 WP 16 PG·CD·NW·AM — 이 전환 단계에서는 모두 0 (후속 초기화에서 설정) · WP(16) — 커널 페이지 쓰기 보호 예약 15-6 NE 5 ET 4 TS 3 EM 2 MP 1 PE 0 PE(bit 0) — 0→1이면 보호 모드 전환 트리거 NE·ET·TS·EM·MP — FPU 제어 비트 중요 — CR0.PE = 1 직후에는 반드시 far JMP로 CS를 재적재해야 합니다 far JMP가 없으면 명령어 프리페치 큐에 Real Mode로 디코딩된 결과가 남아 예측 불가능한 동작이 발생합니다.
Real Mode → Protected Mode 전환은 CLI → A20 → LGDT → CR0.PE=1 → far JMP → 세그먼트 재적재 순서로 진행됩니다. CR0.PE(bit 0)가 전환 스위치이며, PE=1 직후에는 반드시 far JMP로 CS를 갱신해야 합니다.

세그먼테이션 (Segmentation)

Protected Mode의 핵심 메커니즘은 세그먼테이션입니다. 모든 메모리 접근은 세그먼트 셀렉터를 통해 GDT/LDT의 디스크립터를 참조하여 선형 주소(Linear Address)로 변환됩니다.

Protected Mode: 논리 주소 → 선형 주소 → 물리 주소 논리 주소 (Logical) CS : EIP = 0x08 : 0x00401000 셀렉터(16비트) : 오프셋(32비트) ① 세그먼테이션 GDT 조회 선형 주소 (Linear) 0x00401000 = Base + Offset (Base=0) ② 페이징 CR0.PG=1 물리 주소 (Physical) 프레임 주소 + Offset PG=0이면 선형 = 물리 ① 세그먼테이션 — 셀렉터로 디스크립터를 찾아 Base를 더함 세그먼트 셀렉터 0x08 (16비트) Index [15:3] TI RPL Index = 0x08 >> 3 = 1 TI=0 · RPL=0 예제 임시 GDT — Index×8 위치 [0] NULL (필수) [1] 커널 코드 (0x08) ← Index=1 [2] 커널 데이터 (0x10) 디스크립터 [1] 필드 Base = 0x00000000 Limit = 0xFFFFF · G=1 DPL = 0 (Ring 0) 한계 검사 — Offset > Limit이면 #GP 예외 발생 선형 주소 = Base + Offset = 0x00401000 ② 페이징 — 2단계 테이블로 물리 프레임 결정 32비트 선형 주소 분해 Directory [31:22] Table [21:12] Offset [11:0] PD 항목 선택 PT 항목 선택 프레임 내 위치 CR3 레지스터 페이지 디렉토리 (PD) PDE 페이지 테이블 (PT) PTE 물리 프레임 (Frame) CR3 상위 20비트 = PD 물리 베이스 Directory 인덱스 → PT 선택 Table 인덱스 → 프레임 번호 지정 하위 12비트 Offset 그대로 물리 주소 = 프레임 주소 + Offset Linux의 Flat 세그먼트 모델 — 논리 주소 = 선형 주소인 이유 · 모든 세그먼트 디스크립터에 Base=0, Limit=0xFFFFF(G=1, 4GiB 커버)를 설정 → Base 가산이 항상 0이라 Offset이 그대로 통과 · 셀렉터는 Ring 구분(DPL/RPL 특권 검사) 용도로만 존재하고 주소를 이동시키지 않음 · 메모리 보호는 페이징이 전담 — 세그먼테이션은 x86 전용이라 이식성 확보, Long Mode에서는 강제 비활성화
논리 주소(Segment:Offset)를 세그먼트 디스크립터의 Base와 합산하여 선형 주소를 만들고, 페이징이 활성화되어 있으면 물리 주소로 최종 변환합니다. Linux는 Flat 모델로 세그먼테이션을 사실상 우회합니다.

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단계를 거쳐 물리 주소로 변환됩니다.

Protected Mode 2-Level 페이징 (CR0.PG=1, CR4.PAE=0) 32비트 선형 주소 31 22 12 0 Directory [31:22] 10비트 → PD 엔트리 선택 Table [21:12] 10비트 → PT 엔트리 선택 Offset [11:0] 12비트 → 페이지 내 위치 CR3 PD 기준 주소 페이지 디렉토리 (PD) 1024 × 4B 엔트리 = 4KB PDE[0] PDE[1] ... PDE[1023] PT 기준 주소 페이지 테이블 (PT) 1024 × 4B 엔트리 = 4KB PTE[0] PTE[1] ... PTE[1023] 프레임 주소 물리 페이지 프레임 4KB (4096 바이트) 물리 주소 = 프레임 기준 + Offset 하위 12비트가 그대로 더해짐 PDE / PTE 엔트리 형식 (32비트) Page Frame 기준 주소 [31:12] (20비트 = 프레임 번호) AVL [11:9] G PS/PAT D A PCD PWT U/S R/W P 31 12 9 8 7 6 5 4 3 2 1 0 P=Present(유효 · 0이면 #PF) · R/W=쓰기 허용 · U/S=유저 접근 허용 · A=접근 기록 · D=쓰기 기록 G=TLB 보존(Global) · PS=PDE bit7, 1이면 4MB 페이지로 PT 생략 · PAT=PTE bit7, 메모리 타입 선택 · PCD·PWT=캐시 정책 · AVL=OS 정의
32비트 선형 주소를 Directory(10비트) + Table(10비트) + Offset(12비트)로 분해하여 2단계 페이지 테이블을 거쳐 물리 주소로 변환합니다. 각 주소 필드(파랑·주황·초록)는 같은 색의 구조물에 대응하며, PDE.PS=1이면 4MB 대형 페이지로 PT 단계를 건너뜁니다.
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가 발생합니다.

Protected Mode → Long Mode 전환 상태 머신 상태 (CPU 모드) 전이 동작 실패 시 결과 Protected Mode (32-bit) PE = 1 · PG = 0 또는 1 (non-PAE 페이징 허용) EFER.LME = 0 — IA-32e 미활성 ①~④ 준비 단계 — 순서 엄수 ① CR0.PG = 0 — 페이징 비활성화 ② CR4.PAE = 1 — 물리 주소 확장 켬 ③ CR3 ← PML4 물리 주소 (identity map) ④ EFER.LME = 1 — MSR 0xC0000080 쓰기 ✕ 실패 시 · PG = 1 상태에서 LME 기록 → #GP · PAE = 0인 채 PG = 1 → #GP · identity map 누락 → Triple Fault 전환 준비 완료 PAE = 1 · LME = 1 · PG = 0 · CR3 = PML4 여전히 32비트 코드 실행 중 페이징 OFF — 물리 주소 직접 접근 ⑤ CR0.PG = 1 — 핵심 스위치 LME · PAE 조건 충족을 CPU가 확인 EFER.LMA = 1 자동 설정 ✕ 실패 시 · 초기 부팅은 IDT 미설정 상태 · #PF · #GP 발생 시 곧장 Triple Fault · Triple Fault → CPU 리셋 무한 반복 Long Mode 활성 (EFER.LMA = 1) CS.L = 0이면 Compatibility Mode 유지 인터럽트·예외는 이미 64비트 방식 처리 IDT 엔트리도 16바이트(64비트 게이트) 해석 ⑥ far JMP — 64비트 코드 세그먼트 로드 ljmp $__KERNEL_CS, $startup_64 CS.L = 1 · CS.D/B = 0 디스크립터 로드 ✕ 실패 시 · 점프 생략 — LMA = 1인 채 32비트 실행 지속 · CS.L = 1 + D/B = 1 → #GP (예약 조합) 64-bit Mode 진입 완료 CS.L = 1 · CS.D/B = 0 64비트 RIP — 명령어 스트림 64비트 디코딩 DS/ES/SS base = 0 강제 (플랫 메모리 모델) ※ UEFI 부팅은 예외: 펌웨어가 이미 Long Mode — EFI 스텁은 이 전환 없이 startup_64로 직접 진입합니다
Protected Mode에서 Long Mode로의 전환은 위→아래 시간순으로 진행됩니다: ①페이징 비활성화 → ②PAE → ③PML4 → ④EFER.LME 준비 후, ⑤CR0.PG=1로 Long Mode 활성(LMA 자동 설정), ⑥far JMP로 64비트 코드에 진입합니다. 각 단계의 순서 오류는 #GP 또는 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를 차지합니다.

Long Mode 4-Level 페이징 (48비트 가상 주소) 63 47 38 29 20 11 0 부호 확장 [63:48] · 16비트 PML4 인덱스 [47:39] · 9비트 PDPT 인덱스 [38:30] · 9비트 PD 인덱스 [29:21] · 9비트 PT 인덱스 [20:12] · 9비트 페이지 오프셋 [11:0] · 12비트 비트 47과 동일 (Canonical 주소) ① 페이지 테이블 워크 CR3 PML4 기준 주소 PML4 · L4 512 엔트리 = 4KB PML4 기준: CR3 PDPT · L3 512 엔트리 = 4KB PML4 엔트리 선택 PD · L2 512 엔트리 = 4KB PDPT 엔트리 선택 PT · L1 512 엔트리 = 4KB PD 엔트리 선택 물리 페이지 4KB 물리 프레임 오프셋 = 바이트 위치 화살표 = 선택된 엔트리가 담고 있는 다음 레벨 테이블의 물리 기준 주소 PDE.PS=1 → 2MB 페이지 (PT 생략) PDPTE.PS=1 → 1GB 페이지 (PD·PT 생략) 참고: 5-Level 페이징(LA57) 지원 CPU는 PML5를 추가해 가상 주소 공간이 57비트로 확장됩니다 ② PTE(Page Table Entry) 비트 배치 — 64비트 NX bit 63 예약·소프트웨어용 [62:52] 물리 프레임 번호 (PFN) [51:12] AVL [11:9] G 8 PAT 7 D 6 A 5 PCD 4 PWT 3 U/S 2 R/W 1 P 0 P=0 → 유효하지 않은 매핑 · 접근 시 Page Fault(#PF) 발생 R/W=0 읽기 전용 · U/S=0 커널 전용 · NX=1 실행 금지 (W^X 보호) A/D 비트는 접근/쓰기 발생 시 하드웨어가 자동 설정 NX 비트는 EFER.NXE=1일 때만 활성화
48비트 가상 주소를 4단계(PML4→PDPT→PD→PT)로 분해해 물리 프레임을 찾아갑니다. 각 테이블은 512 엔트리 × 8바이트 = 4KB이며, PS 비트 설정으로 2MB/1GB 대형 페이지를, PML5 추가(LA57)로 57비트 확장도 지원합니다.

Protected Mode vs Long Mode 페이징 비교

항목Protected Mode (non-PAE)Protected Mode (PAE)Long Mode (4-level)Long Mode (5-level)
가상 주소 비트32324857
물리 주소 비트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→...)
엔트리/테이블1024512 (PDPT: 4)512512
기본 페이지4KB4KB4KB4KB
대형 페이지4MB (PSE)2MB2MB, 1GB2MB, 1GB
NX 비트없음있음 (bit 63)있음 (bit 63)있음 (bit 63)
CR3 가리킴PDPDPTPML4PML5

레지스터 확장

Long Mode에서는 기존 8개 범용 레지스터가 64비트로 확장되고, R8~R15 8개 레지스터가 추가됩니다. REX 프리픽스가 이 확장 레지스터 접근을 가능하게 합니다.

32-bit (Protected)64-bit (Long)추가 레지스터용도 (System V ABI)
EAX → RAXRAX (64-bit)R85번째 함수 인자
EBX → RBXRBX (callee-saved)R96번째 함수 인자
ECX → RCXRCX (4번째 인자)R10static chain (caller-saved)
EDX → RDXRDX (3번째 인자)R11SYSCALL RFLAGS 저장
ESI → RSIRSI (2번째 인자)R12callee-saved
EDI → RDIRDI (1번째 인자)R13callee-saved
EBP → RBPRBP (frame pointer)R14callee-saved
ESP → RSPRSP (stack pointer)R15callee-saved
EIP → RIPRIP (64-bit)RIP-relative 주소 지정 모드 추가

Long Mode에서의 세그먼테이션 변화

Long Mode는 세그먼테이션을 대부분 비활성화합니다. Protected Mode에서 핵심이었던 세그먼트 Base/Limit이 무시됩니다.

세그먼트Protected ModeLong Mode (64-bit)
CSBase + Limit + DPL + L/D 비트 모두 유효DPL, L, D 비트만 유효. Base/Limit 무시. L=1, D=0이어야 64-bit 모드
SSBase + Limit + DPL 유효DPL만 유효 (Ring 전환 시). Base=0, Limit 무시
DS, ESBase + Limit + 접근 권한 유효완전 무시 — Base=0 강제, Limit 체크 없음
FS, GSBase + 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.LCS.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 대안, 매우 드묾)
Compatibility Mode와 커널: 64비트 리눅스에서 32비트 바이너리를 실행하면 사용자 공간은 호환성 모드(Compatibility Mode, CS.L=0)로 동작하지만, 시스템 콜로 커널에 진입하면 항상 64-bit Mode로 전환됩니다. 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 주소 지정
가상 주소 공간4GB256TB (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/SYSEXITSYSCALL/SYSRET (고속, MSR 기반)
TSS스택 포인터 + I/O bitmapIST(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/IDTR 의사 디스크립터(Pseudo-Descriptor) 구조 메모리상의 의사 디스크립터 LGDT/LIDT 피연산자 (m16&32 / m16&64) LGDT / LIDT (적재) SGDT / SIDT (저장) GDTR / IDTR 레지스터 테이블 선형 주소(Base)와 크기(Limit) 보관 Protected Mode — 총 48비트(6바이트) Limit 비트 [15:0] · 2바이트 Base (선형 주소) 비트 [47:16] · 4바이트 확장 영역 (Long Mode) 비트 [63:32] · 4바이트 바이트 오프셋 0-1 바이트 오프셋 2-5 바이트 오프셋 6-9 · LM에서 추가 Long Mode — 총 80비트(10바이트) Limit 비트 [15:0] · 2바이트 Base 하위 32비트 비트 [47:16] · 4바이트 Base 상위 32비트 비트 [63:32] · 4바이트 바이트 오프셋 0-1 바이트 오프셋 2-5 바이트 오프셋 6-9 Limit = 테이블 전체 바이트 크기 - 1 Base = 테이블 시작 선형 주소 Base 상위 [63:32] — Long Mode 확장
LGDT/LIDT는 메모리의 의사 디스크립터를 GDTR/IDTR에 적재하고 SGDT/SIDT는 반대로 저장합니다. Protected Mode 구조는 48비트(Limit 16비트 + Base 32비트, 6바이트)이며, Long Mode에서는 Base가 64비트로 확장된 80비트(10바이트) 구조를 사용합니다 — Protected Mode 행의 점선 영역(바이트 6-9)이 Long Mode에서 실제 필드가 됩니다.
레지스터 유형 내용 적재 명령어 저장 명령어
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 피연산자 형식: 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"를 기준으로 설명합니다. 아래 다이어그램의 필드 폭은 가독성 확보를 위한 개략치이며 실제 비트 비례가 아닙니다.

GDT 세그먼트 디스크립터 64비트 비트 필드 왼쪽 → 오른쪽 : 최상위 비트(MSB, 63) → 최하위 비트(LSB, 0) 상위 DWORD — 비트 [63:32] Base[31:24] [63:56] G 55 D/B 54 L 53 AVL 52 Lmt[19:16] [51:48] P 47 DPL [46:45] S 44 Type[3:0] [43:40] Base[23:16] [39:32] 하위 DWORD — 비트 [31:0] Base[15:0] [31:16] Limit[15:0] [15:0] 범례 Base 주소 필드 (세그먼트 기준 주소) 속성 플래그 : G · D/B · L Segment Limit 필드 (세그먼트 한계) 접근 바이트(Access Byte) : P · DPL · S · Type AVL : 시스템 소프트웨어용 예약 비트 주요 약어 P — Present : 세그먼트가 메모리에 존재함 DPL — Descriptor Privilege Level : 권한 수준(0~3) S — System : 1=코드/데이터 세그먼트, 0=시스템 세그먼트 AVL — Available : 시스템 소프트웨어가 임의 사용 가능 G — Granularity : Limit 단위(0=1바이트, 1=4KiB) D/B — Default/Big : 기본 연산 크기 플래그 L — Long : 64비트 코드 세그먼트 표시 Type — 세그먼트 종류와 접근 권한(4비트)
GDT 세그먼트 디스크립터는 64비트(8바이트) 구조로, 상위 DWORD에 Base[31:24]·속성 플래그(G·D/B·L)·AVL·Limit[19:16]·접근 바이트(P·DPL·S·Type)·Base[23:16]이, 하위 DWORD에 Base[15:0]과 Limit[15:0]이 배치됩니다. 색상은 필드 역할(Base/속성 플래그/Limit/접근 바이트/AVL)을 구분하며, 필드 폭은 가독성 위주의 개략치입니다.
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)
Long Mode 코드 세그먼트 주의사항: 64비트 Long Mode에서 코드 세그먼트는 반드시 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에서 연속된 두 슬롯을 차지합니다. 다이어그램의 필드 폭은 실제 비트 비례가 아닌 개략치입니다.

시스템 세그먼트 디스크립터 (TSS/LDT) — Long Mode 128비트 구조 첫 번째 GDT 슬롯 · 오프셋 0x00–0x07 (하위 64비트) 63 56 55 52 51 48 47 44 43 40 39 16 15 0 Base[31:24] [63:56] G 55 000 54:52 Lmt[19:16] [51:48] P 47 DPL 46:45 S=0 44 Type 43:40 Base[23:0] [39:16] Limit[15:0] [15:0] 바이트 7 바이트 6 바이트 5 바이트 4·3·2 바이트 1·0 두 번째 GDT 슬롯 · 오프셋 0x08–0x0F (상위 64비트) 127 96 95 64 Reserved (0으로 설정) [127:96] Base[63:32] [95:64] — 64비트 Base 주소의 상위 32비트 TSS / LDT 디스크립터 = 연속된 2슬롯 · 총 128비트 (16바이트) Base (기본 주소) Limit (세그먼트 크기) Type / 제어 비트 예약 (반드시 0) ※ Call Gate · Interrupt Gate · Trap Gate는 Long Mode에서도 64비트(8바이트)를 유지합니다.
Long Mode의 TSS/LDT 디스크립터는 128비트로 확장되어 GDT에서 연속된 두 슬롯을 차지합니다. 첫 번째 슬롯에 Limit·Base 하위 비트·Type/DPL/P 제어 비트가 배치되고, 두 번째 슬롯은 Base[63:32]와 예약 영역만 포함합니다.
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);
Long Mode TSS/LDT 디스크립터 확장: Protected Mode에서는 TSS/LDT 디스크립터가 일반 세그먼트처럼 64비트(8바이트)였지만, Long Mode에서는 64비트 Base 주소를 수용하기 위해 128비트(16바이트)로 확장됩니다. 따라서 GDT에서 두 개의 연속 슬롯을 사용하며, 두 번째 슬롯은 상위 32비트 Base와 예약 영역만 포함합니다. Call Gate, Interrupt Gate, Trap Gate는 Long Mode에서도 여전히 64비트입니다.

IDT 게이트 디스크립터 구조 상세

64비트 Long Mode의 IDT 엔트리는 16바이트(128비트)로, Protected Mode의 8바이트에서 확장되었습니다. 핸들러 오프셋이 64비트로 늘어났으며, IST(Interrupt Stack Table) 필드가 추가되어 중첩 예외 상황에서도 안전한 전용 스택을 사용할 수 있습니다. 다이어그램의 필드 폭은 실제 비트 비례가 아닌 개략치입니다.

상위 QWORD (바이트 15-8) · 비트 127 → 64 127 95 64 Reserved 127:96 Offset[63:32] 95:64 3 하위 QWORD (바이트 7-0) · 비트 63 → 0 63 47 46 44 43 39 34 31 15 0 Offset[31:16] 63:48 2 P 47 DPL 46:45 0 44 Type 43:40 Reserved 39:35 IST 34:32 Selector 31:16 Offset[15:0] 15:0 1 파란색 조각 1 · 2 · 3을 차례로 이어 붙이면 64비트 핸들러 오프셋이 완성됩니다 핸들러 Offset 조각 제어 필드 (P · DPL · Type) IST (스택 선택) 코드 세그먼트 Selector 예약 · 고정 0
Gate TypeType 필드IF 플래그용도
Interrupt Gate0xE (1110)자동 클리어하드웨어 인터럽트, 재진입 방지
Trap Gate0xF (1111)유지소프트웨어 예외, 디버그 트랩
IST 값할당 스택사용 벡터 (리눅스)
0일반 스택 (RSP0)일반 인터럽트/예외
1IST1 스택#DF Double Fault (벡터 8)
2IST2 스택NMI (벡터 2)
3IST3 스택#DB Debug (벡터 1)
4IST4 스택#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 매크로 테이블로 설치 */
DPL 설정 주의: 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/DPL/RPL 특권 레벨 메커니즘 ① 세그먼트 셀렉터(Segment Selector, 16비트) 구조 Index [15:3] GDT/LDT 디스크립터 번호 TI [2] RPL [1:0] 요청 권한 15 3 2 0 13비트 → 최대 8192개의 디스크립터 중 하나 선택 0=GDT · 1=LDT 호출자가 지정 예시 : __USER_CS = 0x2B → Index 5 · GDT(TI=0) · RPL=3 · __KERNEL_CS = 0x10 → Index 2 · GDT(TI=0) · RPL=0 ② 세 가지 특권 레벨 — 어디에 저장되고 무엇을 의미하는가 CPLCurrent Privilege Level 위치 : CS 셀렉터 비트 [1:0] 역할 : 현재 실행 코드의 특권 수준 “지금 나는 누구인가?” 리눅스 : Ring 0(커널) 또는 Ring 3(사용자) DPLDescriptor Privilege Level 위치 : 디스크립터 비트 [46:45] 역할 : 세그먼트가 요구하는 최소 특권 “어떤 권한을 요구하는가?” 리눅스 : 커널 0 / 사용자 3 RPLRequested Privilege Level 위치 : 셀렉터 비트 [1:0] 역할 : 이번 접근에 부여된 요청 권한 “어떤 권한으로 요청하는가?” 보통 요청자의 CPL과 동일하게 설정 접근 허용 조건 : max(CPL, RPL) ≤ DPL CPL과 RPL 중 낮은 권한(더 큰 숫자)도 대상이 요구하는 최소 특권을 충족해야 통과 ✗ 거부사용자 코드가 커널 세그먼트에 직접 접근 CPL = 3 (사용자 코드 실행 중) RPL = 3 (셀렉터 하위 2비트) DPL = 0 (커널 코드 세그먼트) max(3, 3) = 3 > 0 → 자격 미달 #GP(General Protection Fault) 발생 ✓ 허용커널(CPL=0)이 사용자 데이터 세그먼트에 접근 CPL = 0 (커널 코드 실행 중) RPL = 3 (사용자 데이터 셀렉터) DPL = 3 (사용자 데이터 세그먼트) max(0, 3) = 3 ≤ 3 → 자격 충족 데이터 접근 허용 참고 : RPL은 커널이 사용자를 대신해 접근할 때, 사용자보다 높은 권한으로의 대리 접근을 차단하는 장치입니다. x86-64 리눅스는 평탄(flat) 세그먼트 모델이라 대부분의 검사가 생략되지만, SYSCALL·게이트 전환 등에서 CPL/DPL 개념은 계속 사용됩니다.
세그먼트 셀렉터는 13비트 Index와 TI, RPL 필드로 구성되며, CPU는 데이터 세그먼트 접근 시 max(CPL, RPL) ≤ DPL 검사로 허용 여부를 판정합니다. CPL은 현재 실행 코드의 권한(CS의 하위 2비트), DPL은 세그먼트가 요구하는 최소 특권(디스크립터 [46:45]), RPL은 셀렉터에 명시한 요청 권한입니다 — RPL은 커널의 대리 접근이 사용자 권한을 넘보지 못하게 하는 장치이며, 리눅스는 Ring 0/3만 사용합니다.
레벨위치의미리눅스 값
CPLCS 셀렉터 [1:0]현재 실행 코드의 특권 수준0 (커널) 또는 3 (유저)
DPLGDT 디스크립터 [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/SYSRET과 IRET의 차이: 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 전환을 수행합니다.

SYSCALL/SYSRET 메커니즘 — Ring 3 ↔ Ring 0 전환 상세 진입 경로 (SYSCALL) 복귀 경로 (SYSRET) Ring 3 — 사용자 공간 (CPL 3) ① 시스템 콜 호출 준비 RAX ← 시스템 콜 번호 RDI·RSI·RDX·R10·R8·R9 ← 함수 인자 6개 ② syscall 명령어 실행 — 이후 동작은 하드웨어가 자동 수행 ⑦ 사용자 코드 재개 RIP ← RCX (syscall 다음 명령) RAX ← 시스템 콜 반환값 RCX·R11은 SYSCALL이 임의로 덮어쓴(clobbered) 레지스터 SYSCALL — 특권 전환 (CPL 3 → 0) 스택 교체는 이루어지지 않음 CPU — SYSCALL·SYSRET 명령어의 하드웨어 동작 ② SYSCALL 진입 동작 (하드웨어) RCX ← RIP (복귀 주소 보존) R11 ← RFLAGS (마스크 전 원본 플래그 보존) CS ← STAR[47:32], SS ← +8 (커널 셀렉터) (STAR) RIP ← LSTAR (entry_SYSCALL_64) (LSTAR) RFLAGS ← RFLAGS & ~SFMASK (IF=0 → 인터럽트 차단) (SFMASK) ⑥ SYSRET 복귀 동작 (하드웨어) RIP ← RCX (사용자 복귀 주소 — 반드시 canonical 검증) RFLAGS ← R11 (사용자가 저장한 플래그 그대로 복원) CS ← STAR[63:48], SS ← +8 (사용자 셀렉터) (STAR) CPL ← 3 (사용자 특권 복귀) 스택 전환 없음 — 사용자 RSP 복원은 커널이 미리 수행 커널 진입점 도착 LSTAR가 가리킨 entry_SYSCALL_64부터 실행 ⑤의 sysretq 실행 CPU가 SYSRET 복귀 동작 수행 ⑥ SYSRET — 복귀 (CPL 0 → 3) RCX·R11로 RIP·RFLAGS 복원 Ring 0 — 커널 공간 (CPL 0) ③ 커널 진입 처리 SWAPGS — GS.base를 커널 값으로 교체 커널 스택으로 RSP 전환 사용자 문맥을 pt_regs에 저장 ④ 디스패치 · 핸들러 실행 RAX 번호로 sys_call_table 조회 핸들러 실행 — 레지스터 인자 전달 반환값을 pt_regs의 ax에 기록 ⑤ 복귀 준비 pt_regs 복원 — RCX·R11 포함 RSP를 사용자 스택 값으로 복원 sysretq로 CPU에 복귀 지시 MSR — 부팅 시 syscall_init()에서 설정 (arch/x86/kernel/cpu/common.c) LSTAR (0xC0000082) 64비트 SYSCALL 진입점 — entry_SYSCALL_64 주소 STAR (0xC0000081) SYSCALL용 CS·SS 기준값 = [47:32], SYSRET용 기준값 = [63:48] SFMASK (0xC0000084) SYSCALL 시 RFLAGS에서 강제 클리어할 비트 — CF·PF·AF·ZF·SF·TF·IF·DF·OF·IOPL·NT·RF·AC·ID CSTAR (0xC0000083) 32비트 호환 모드 진입점 — 64비트 프로그램 경로에는 미사용 ※ 위 CPU 동작 옆 (STAR)·(LSTAR)·(SFMASK) 태그는 해당 MSR에서 값을 읽어 사용함을 표시 ⚠ Non-canonical RCX로 SYSRET을 실행하면 #GP 예외가 CPL 0(커널)에서 발생 — 리눅스는 canonical 검사 후 비정상 시 IRET 경로로 복귀 RFLAGS 복원에 사용되는 R11도 사용자가 조작할 수 있는 값이므로, 커널은 복귀 전 NT 등 이상 플래그 비트를 정제한 뒤 sysretq를 실행합니다.
SYSCALL은 레지스터 보존(RCX·R11)과 특권·분기 전환만 하드웨어가 처리하며, 스택 교체·문맥 저장·복귀 검증은 모두 커널 소프트웨어의 몫입니다. 순서는 ①② 진입 → ③④⑤ 커널 처리 → ⑥ 복귀 전환 → ⑦ 재개이며, 보라색 (태그)는 대응 MSR을 나타냅니다.
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);
}
SYSRET 보안 주의사항 (Non-canonical RIP): 사용자 공간이 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를 갱신합니다.

Long Mode TSS 구조 (최소 104 bytes) 오프셋 크기 필드 설명 0x00 4B Reserved 예약 (0으로 초기화) 0x04 8B RSP0 Ring 0 커널 스택 최상단 (태스크 전환마다 갱신) 0x0C 8B RSP1 Ring 1 스택 (리눅스 미사용, 항상 0) 0x14 8B RSP2 Ring 2 스택 (리눅스 미사용 — SYSCALL 진입 시 사용자 RSP 임시 저장) 0x1C 8B Reserved 예약 0x24 ~ 0x5B 56B (7×8B) IST1 ~ IST7 (각 8바이트) IST1: #DF Double Fault (벡터 8) IST2: NMI Non-Maskable Interrupt (벡터 2) IST3: #DB Debug (벡터 1) IST4: #MC Machine Check (벡터 18) IST5~7: 기본 미할당 (SEV-ES 빌드: IST5 = #VC) 0x5C 10B Reserved 예약 (reserved3 + reserved4 + reserved5) 0x66 2B IOPB Offset I/O 권한 비트맵 오프셋 (0x68 = 비트맵 미사용) RSP — 권한별 스택 포인터 IST — 예외 전용 스택 IOPB — I/O 비트맵 오프셋 Reserved — 예약 총 크기: 최소 104 (0x68) 바이트 (io_bitmap_base 필드까지 포함) struct x86_hw_tss — arch/x86/include/asm/processor.h
Long Mode TSS(struct x86_hw_tss, arch/x86/include/asm/processor.h)의 메모리 맵입니다 — 오프셋 0x00 예약, 0x04~0x14 RSP0~RSP2(Ring 0/1/2 스택 포인터, RSP0는 태스크 전환마다 갱신), 0x1C 예약, 0x24~0x5B IST1~IST7(각 8바이트, #DF·NMI·#DB·#MC 전용 스택), 0x5C 예약, 0x66 io_bitmap_base(I/O 비트맵 오프셋). 총 크기는 최소 104(0x68) 바이트이며, 리눅스는 CPU마다 하나의 TSS를 유지하고 태스크 전환마다 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)); */
리눅스 TSS 사용 방식: 리눅스는 Ring 1/2를 사용하지 않아 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 주소 지정 등 시스템 소프트웨어가 반드시 이해해야 할 고유 메커니즘을 포함합니다.

Canonical 주소 구조 (4-레벨 페이징, 48비트 VA) 63 48 47 0 Sign Extension [63:48] = bit[47] 값 복사 유효 가상 주소 (Effective Address) 48비트 — 상위 36비트는 페이지 테이블 인덱스, 하위 12비트는 페이지 오프셋 [47:0] 필드 분해 — 총 48비트 = 인덱스 9비트 × 4 + 오프셋 12비트 PML4 [47:39] L4 테이블 선택 PDPT [38:30] L3 테이블 선택 PD [29:21] L2 테이블 선택 PT [20:12] L1 테이블 선택 Offset [11:0] 4KB 내 바이트 위치 47 0 CR3 레지스터가 L4(PML4) 테이블의 물리 기준 주소를 지정 — TLB 미스 시 최대 4회의 메모리 참조로 변환 완료 64비트 가상 주소 공간에서의 Canonical / Non-Canonical 영역 사용자 공간 (하위 Canonical) 0x0000_0000_0000_0000 ~ 0x0000_7FFF_FFFF_FFFF Non-Canonical 영역 0x0000_8000_0000_0000 ~ 0xFFFF_7FFF_FFFF_FFFF #GP(0) 예외 — 모든 접근 차단 커널 공간 (상위 Canonical) 0xFFFF_8000_0000_0000 ~ 0xFFFF_FFFF_FFFF_FFFF 최대 128 TB 약 16 EB (도식상 폭 축약) 최대 128 TB bit[47] = 0 → 상위 16비트 전체가 0 (유저) · bit[47] = 1 → 상위 16비트 전체가 1 (커널) — 이 규칙을 어기면 Non-Canonical
64비트 가상 주소는 비트 [47]을 상위 16비트 [63:48]에 복사한 값만 Canonical 주소로 인정됩니다 — 하위 48비트 중 36비트는 PML4·PDPT·PD·PT 인덱스(각 9비트), 12비트는 페이지 오프셋이며, CR3가 PML4 기준 주소를 지정합니다. bit[47]=0이면 하위 Canonical(사용자, 최대 128TB), 1이면 상위 Canonical(커널, 최대 128TB)이고 그 사이는 Non-Canonical 영역(#GP)입니다.
REX 프리픽스 (1바이트: 0x40~0x4F) 0100 고정 (bits 7:4) W bit 3: 64bit 오퍼랜드 R bit 2: ModRM.reg 확장 X bit 1: SIB.index 확장 B bit 0: rm/SIB.base 확장 7 4 3 2 1 0 예: REX.W (0x48) + 0x89 0xC8 = mov rax, rcx | REX.RXB (0x4F) = r8~r15 전체 접근 가능
MSR 주소이름용도커널 접근
0xC0000100FS.BaseFS 세그먼트 베이스 (사용자 TLS)arch_prctl(ARCH_SET_FS)
0xC0000101GS.BaseGS 세그먼트 베이스 (커널 per-CPU)wrmsr(MSR_GS_BASE, ...)
0xC0000102KernelGS.BaseSWAPGS로 교체될 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 인코딩)
Non-canonical 주소와 SWAPGS 보안 이슈: Non-canonical 주소(예: 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 확장을 활용할 수 있도록 지원이 확대되었습니다.
PREEMPT_RT 역사적 의의: v6.12에서 PREEMPT_RT가 메인라인에 병합된 것은 리눅스 커널 역사상 가장 오래 걸린 기능 통합(약 20년) 중 하나입니다. 이를 통해 별도의 패치 없이 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 아키텍처에 새 코드가 추가되었습니다. 레거시 아키텍처 커뮤니티의 재활성화를 보여줍니다.
ACPI 6.6 스펙 릴리스: 2025년에 UEFI Forum에서 ACPI 6.6 스펙이 공개되었습니다. Platform Profile 정의와 CXL 환경 지원 등에 대한 업데이트가 포함되어 있으며, 커널 측 흡수는 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 공정 예상. 구체적인 구성과 시점은 공식 발표 기준으로 확인 필요
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가 구현되어 빌드 툴체인 지원 판정이 개선되었습니다.
v7.2 관전 포인트: 캐시 인지 스케줄링은 프로세스 스케줄러·CFS 스케줄러 상세와, dm-inlinecrypt는 dm-crypt·dm-verity와 연결하여 보시면 맥락이 이어집니다. sched_ext 서브 스케줄러 준비 항목은 sched_ext 문서에서 후속 내용을 확인할 수 있습니다.

아키텍처 분석 플레이북

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 활용: 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
실무 팁: 아키텍처 차이를 비교할 때는 기능 단위(예: syscall, page fault, interrupt)로 파일을 나란히 열어 공통점/차이점을 표로 정리하면 코드 리뷰와 포팅 작업 속도가 크게 올라갑니다.

참고자료

공식 커널 문서 (Official Kernel Documentation)

CPU 아키텍처 사양서 (Architecture Specifications)

커널 소스 및 탐색 (Source Browsing)

릴리스 노트 및 최신 변경사항 (Release Notes)

심층 해설 및 분석 기사 (In-depth Articles)

서적 (Books)

필수 관련 문서: