어셈블리 종합 (Assembly)

Linux 커널 GCC 인라인 어셈블리, GNU Assembler 지시자, AT&T/Intel 문법 비교, x86_64/ARM64 호출 규약, 메모리 배리어, 커널 진입/탈출 코드 종합 가이드.

관련 표준: System V AMD64 ABI (x86-64 호출 규약), ARM AAPCS (AArch64 호출 규약), Intel SDM (명령어 레퍼런스) — 커널 어셈블리 코드가 따르는 ABI 및 ISA 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

인라인 어셈블리 개요

Linux 커널은 성능 최적화, 하드웨어 직접 제어, 원자적 연산 등을 위해 인라인 어셈블리를 사용합니다. GCC의 asm (또는 __asm__) 키워드를 사용합니다.

GCC 인라인 어셈블리 문법

asm volatile (
    "assembly template"            /* 어셈블리 코드 */
    : output operands              /* 출력 (optional) */
    : input operands               /* 입력 (optional) */
    : clobbers                     /* 변경되는 레지스터/메모리 */
);

/* 예: x86 rdtsc (TSC 읽기) */
static inline u64 rdtsc(void)
{
    u32 lo, hi;
    asm volatile ("rdtsc" : "=a"(lo), "=d"(hi));
    return ((u64)hi << 32) | lo;
}

/* 예: CR3 레지스터 읽기 (현재 페이지 테이블) */
static inline unsigned long read_cr3(void)
{
    unsigned long val;
    asm volatile ("mov %%cr3, %0" : "=r"(val));
    return val;
}

오퍼랜드 제약조건

제약x86 의미설명
"r"범용 레지스터컴파일러가 선택
"a"eax/raxaccumulator
"b"ebx/rbxbase register
"c"ecx/rcxcounter register
"d"edx/rdxdata register
"m"메모리 참조메모리 피연산자
"i"즉시값상수
"="출력 전용쓰기만
"+"입출력읽기+쓰기

시스템 콜 ABI

x86_64 시스템 콜 규약

/* x86_64 System V ABI - 시스템 콜 */
/*   syscall number: rax               */
/*   arguments:      rdi, rsi, rdx, r10, r8, r9 */
/*   return value:   rax                */
/*   clobbered:      rcx, r11           */

/* 시스템 콜 진입점 (entry_SYSCALL_64) */
SYM_CODE_START(entry_SYSCALL_64)
    swapgs                          /* GS base를 커널용으로 교체 */
    movq    %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
    movq    PER_CPU_VAR(cpu_current_top_of_stack), %rsp
    pushq   $__USER_DS              /* user SS */
    pushq   PER_CPU_VAR(cpu_tss_rw + TSS_sp2) /* user RSP */
    pushq   %r11                    /* user RFLAGS */
    pushq   $__USER_CS              /* user CS */
    pushq   %rcx                    /* user RIP */
    /* ... */
SYM_CODE_END(entry_SYSCALL_64)

커널에서 자주 사용하는 어셈블리 패턴

/* cmpxchg: Compare and exchange (atomic) */
static inline unsigned long cmpxchg_local(
    volatile void *ptr, unsigned long old, unsigned long new)
{
    unsigned long prev;
    asm volatile ("cmpxchgq %2, %1"
        : "=a"(prev), "+m"(*(unsigned long *)ptr)
        : "r"(new), "0"(old)
        : "memory");
    return prev;
}

/* CLI/STI: 인터럽트 비활성화/활성화 */
static inline void native_irq_disable(void)
{
    asm volatile ("cli" ::: "memory");
}

static inline void native_irq_enable(void)
{
    asm volatile ("sti" ::: "memory");
}

인라인 어셈블리에서 "memory" clobber를 빠뜨리면 컴파일러가 메모리 접근을 재정렬하여 미묘한 버그가 발생할 수 있습니다. 메모리를 수정하는 어셈블리에는 반드시 포함하세요.

AT&T vs Intel 문법 비교

x86 어셈블리에는 두 가지 주요 문법이 있습니다. Linux 커널과 GCC는 기본적으로 AT&T 문법을 사용하지만, Intel 문법도 지원합니다. 두 문법의 핵심 차이를 이해하는 것이 중요합니다.

특성AT&T 문법 (GAS 기본)Intel 문법 (NASM 등)
오퍼랜드 순서src, dst (소스 먼저)dst, src (목적지 먼저)
레지스터 접두사%rax, %eaxrax, eax
즉시값 접두사$42, $0xff42, 0ffh
크기 접미사movl, movq, movbmov DWORD, mov QWORD
메모리 참조disp(%base,%index,scale)[base + index*scale + disp]
간접 점프*%raxjmp rax
/* AT&T 문법 (Linux 커널 기본) */
movq    %rsi, %rdi          /* rsi → rdi 복사 */
addl    $1, %eax            /* eax += 1 */
movl    8(%rbp), %eax       /* [rbp+8] → eax */
leaq    (%rdi,%rsi,4), %rax /* rax = rdi + rsi*4 */
cmpq    $0, (%rdi)           /* [rdi]와 0 비교 */

/* 동일한 코드의 Intel 문법 */
mov     rdi, rsi            /* rsi → rdi 복사 */
add     eax, 1              /* eax += 1 */
mov     eax, [rbp+8]        /* [rbp+8] → eax */
lea     rax, [rdi+rsi*4]    /* rax = rdi + rsi*4 */
cmp     QWORD PTR [rdi], 0  /* [rdi]와 0 비교 */

GCC에서 Intel 문법을 사용하려면 -masm=intel 옵션을 지정하거나, 인라인 어셈블리 내에서 .intel_syntax noprefix 지시자를 사용합니다. 그러나 커널 코드는 AT&T 문법이 표준이므로 AT&T에 익숙해지는 것이 중요합니다.

GNU Assembler (GAS) 지시자

GNU Assembler는 Linux 커널의 어셈블리 파일(.S)을 처리합니다. 자주 사용되는 GAS 지시자를 이해해야 커널의 어셈블리 코드를 읽을 수 있습니다.

섹션 지시자

.section .text            /* 코드 섹션 (실행 가능) */
.section .data            /* 초기화된 데이터 */
.section .bss             /* 초기화되지 않은 데이터 */
.section .rodata          /* 읽기 전용 데이터 */
.section .init.text, "ax" /* 초기화 코드 (부팅 후 해제) */

/* 커널 전용 섹션 예시 */
.pushsection .altinstructions, "a"   /* 대안 명령어 섹션 */
.popsection                          /* 이전 섹션으로 복귀 */

심볼과 정렬

.global  my_function      /* 전역 심볼 선언 */
.local   helper_func      /* 로컬 심볼 (외부 비공개) */
.type    my_function, @function  /* 함수 타입 정보 */
.size    my_function, . - my_function  /* 함수 크기 */

.align   16               /* 16바이트 정렬 */
.balign  4096             /* 4096바이트 경계 정렬 */
.p2align 4                /* 2^4 = 16바이트 정렬 */

매크로와 조건부 어셈블리

/* 매크로 정의 */
.macro SAVE_REGS
    pushq   %rbp
    pushq   %rbx
    pushq   %r12
    pushq   %r13
    pushq   %r14
    pushq   %r15
.endm

.macro RESTORE_REGS
    popq    %r15
    popq    %r14
    popq    %r13
    popq    %r12
    popq    %rbx
    popq    %rbp
.endm

/* 매크로 매개변수 */
.macro PUSH_AND_CLEAR reg:req
    pushq   \reg
    xorq    \reg, \reg
.endm

/* 조건부 어셈블리 */
.if CONFIG_X86_64
    movq    %rsp, %rdi
.else
    movl    %esp, %eax
.endif

/* 반복 */
.rept 8
    nop                      /* 8번 반복 */
.endr

데이터 지시자

.byte    0x90             /* 1바이트 */
.word    0x1234           /* 2바이트 */
.long    0x12345678       /* 4바이트 */
.quad    0x123456789abcdef0 /* 8바이트 */
.ascii   "Hello"          /* 문자열 (NUL 없음) */
.asciz   "Hello"          /* NUL 종료 문자열 */
.fill    256, 1, 0        /* 256바이트를 0으로 채움 */
.space   4096             /* 4096바이트 공간 확보 */

/* 커널에서 자주 보는 SYM_* 매크로 (include/linux/linkage.h) */
SYM_FUNC_START(my_asm_func)     /* 함수 시작 선언 */
    /* ... 코드 ... */
    retq
SYM_FUNC_END(my_asm_func)       /* 함수 종료 + 크기 기록 */

SYM_CODE_START(entry_point)      /* 비표준 호출 규약 코드 */
SYM_CODE_END(entry_point)

SYM_DATA_START(my_data)          /* 데이터 심볼 */
    .long 42
SYM_DATA_END(my_data)

x86_64 호출 규약 심화

Linux 커널은 x86_64에서 System V AMD64 ABI를 따릅니다. 함수 호출 시 레지스터 사용 규칙을 정확히 알아야 합니다.

레지스터 역할

레지스터용도호출 후 보존
rdi, rsi, rdx, rcx, r8, r9함수 인자 1~6비보존 (caller-saved)
rax반환값비보존
rdx반환값 (128비트 시 상위)비보존
r10, r11임시비보존
rbx, rbp, r12~r15범용보존 (callee-saved)
rsp스택 포인터보존
xmm0~xmm7부동소수점 인자/반환비보존

스택 프레임과 Red Zone

/*
 * 표준 함수 프롤로그/에필로그
 *
 * 스택 레이아웃:
 *   [rbp+16]  두 번째 스택 인자 (있는 경우)
 *   [rbp+8]   리턴 주소
 *   [rbp]     이전 rbp (프레임 포인터)
 *   [rbp-8]   로컬 변수 시작
 */
SYM_FUNC_START(example_func)
    pushq   %rbp                /* 프레임 포인터 저장 */
    movq    %rsp, %rbp          /* 새 프레임 설정 */
    subq    $32, %rsp           /* 로컬 변수 공간 */
    pushq   %rbx                /* callee-saved 보존 */
    pushq   %r12

    /* ... 함수 본문 ... */

    popq    %r12                /* callee-saved 복원 */
    popq    %rbx
    leave                       /* = movq %rbp,%rsp; popq %rbp */
    retq
SYM_FUNC_END(example_func)

Red Zone 주의: System V ABI에서 유저 공간 코드는 rsp 아래 128바이트를 임시 저장소로 사용할 수 있습니다 (Red Zone). 그러나 커널 코드에서는 Red Zone이 비활성화됩니다 (-mno-red-zone). 인터럽트가 언제든 발생하여 스택을 덮어쓸 수 있기 때문입니다.

시스템 콜 vs 일반 함수 호출

특성일반 함수 호출 (call)시스템 콜 (syscall)
4번째 인자rcxr10 (rcx는 syscall이 덮어씀)
호출 방식call 명령어syscall 명령어
리턴retsysret (또는 iretq)
변경 레지스터caller-savedrcx, r11 추가 변경
컨텍스트동일 권한 수준유저→커널 권한 전환

ARM64 어셈블리

Linux 커널은 ARM64 (AArch64)도 광범위하게 지원합니다. ARM64 어셈블리의 기본을 알면 멀티 아키텍처 코드를 이해하는 데 도움이 됩니다.

ARM64 레지스터

레지스터용도호출 규약
x0~x7함수 인자/반환값비보존
x8간접 결과 레지스터비보존
x9~x15임시 레지스터비보존
x16~x17PLT/veneer (IP0, IP1)비보존
x18플랫폼 레지스터커널에서 current_task
x19~x28callee-saved보존
x29 (FP)프레임 포인터보존
x30 (LR)링크 레지스터 (리턴 주소)비보존
SP스택 포인터보존

ARM64 주요 명령어

/* 산술 연산 */
add     x0, x1, x2              /* x0 = x1 + x2 */
sub     x0, x1, #16             /* x0 = x1 - 16 */
madd    x0, x1, x2, x3          /* x0 = x1*x2 + x3 */

/* 메모리 접근: LDP/STP (쌍 로드/저장) */
stp     x29, x30, [sp, #-16]!  /* FP, LR 스택에 저장 (pre-index) */
ldp     x29, x30, [sp], #16    /* FP, LR 복원 (post-index) */

/* 조건부 실행 */
cmp     x0, #0
csel    x1, x2, x3, eq          /* if (x0==0) x1=x2 else x1=x3 */
cset    x0, ne                   /* x0 = (조건 NE이면 1, 아니면 0) */

/* PC 상대 주소 계산 */
adrp    x0, my_symbol            /* x0 = 페이지 주소 (4KB 정렬) */
add     x0, x0, :lo12:my_symbol  /* 하위 12비트 오프셋 추가 */

/* 비트 조작 */
ubfx    x0, x1, #4, #8         /* x1의 비트[11:4] 추출 → x0 */
bfi     x0, x1, #8, #4         /* x1의 하위 4비트를 x0[11:8]에 삽입 */

ARM64 커널 어셈블리 예시

/* ARM64 인라인 어셈블리: 현재 Exception Level 읽기 */
static inline u64 read_currentel(void)
{
    u64 val;
    asm volatile ("mrs %0, CurrentEL" : "=r"(val));
    return (val >> 2) & 3;  /* EL0=0, EL1=1, EL2=2 */
}

/* ARM64 인라인 어셈블리: 데이터 캐시 클린 + 무효화 */
static inline void dc_civac(unsigned long addr)
{
    asm volatile ("dc civac, %0" :: "r"(addr) : "memory");
}

/* ARM64 인라인 어셈블리: 명령어 배리어 */
static inline void isb(void)
{
    asm volatile ("isb" ::: "memory");
}

커널 진입/탈출 코드

커널의 가장 중요한 어셈블리 코드는 유저 공간↔커널 공간 전환을 담당하는 진입/탈출 코드입니다. 시스템 콜, 인터럽트, 예외 처리 시 정확한 컨텍스트 저장/복원이 필수입니다.

x86_64 시스템 콜 진입 (상세)

/* arch/x86/entry/entry_64.S - 시스템 콜 진입 흐름 */
SYM_CODE_START(entry_SYSCALL_64)
    /* 1. GS base 교체: 유저 → 커널 per-CPU */
    swapgs

    /* 2. 유저 RSP를 per-CPU에 임시 저장 */
    movq    %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)

    /* 3. 커널 스택으로 전환 */
    movq    PER_CPU_VAR(cpu_current_top_of_stack), %rsp

    /* 4. pt_regs 구조체 구성 (스택에 푸시) */
    pushq   $__USER_DS              /* SS */
    pushq   PER_CPU_VAR(cpu_tss_rw + TSS_sp2)  /* RSP */
    pushq   %r11                    /* RFLAGS (syscall이 r11에 저장) */
    pushq   $__USER_CS              /* CS */
    pushq   %rcx                    /* RIP (syscall이 rcx에 저장) */

    /* 5. 에러 코드 + 범용 레지스터 저장 */
    pushq   $-1                     /* 에러 코드 자리 (syscall은 없음) */
    PUSH_AND_CLEAR_REGS rax=$__NR_syscall_max

    /* 6. C 함수 호출: do_syscall_64(regs, nr) */
    movq    %rsp, %rdi              /* 첫 번째 인자: pt_regs 포인터 */
    movslq  %eax, %rsi              /* 두 번째 인자: syscall 번호 */
    call    do_syscall_64
    /* ... 복귀 경로 ... */
SYM_CODE_END(entry_SYSCALL_64)

컨텍스트 저장/복원 (pt_regs)

/* arch/x86/include/asm/ptrace.h */
struct pt_regs {
    unsigned long r15, r14, r13, r12;
    unsigned long bp;    /* rbp */
    unsigned long bx;    /* rbx */
    unsigned long r11, r10, r9, r8;
    unsigned long ax;    /* rax - syscall 반환값 */
    unsigned long cx, dx, si, di;
    unsigned long orig_ax;  /* syscall 번호 원본 */
    unsigned long ip;    /* RIP */
    unsigned long cs;
    unsigned long flags; /* RFLAGS */
    unsigned long sp;    /* RSP */
    unsigned long ss;
};

인터럽트 진입 경로

인터럽트는 시스템 콜과 달리 어디서든 발생할 수 있으므로 더 신중한 컨텍스트 저장이 필요합니다. x86_64에서 CPU는 자동으로 SS, RSP, RFLAGS, CS, RIP를 IST(Interrupt Stack Table) 또는 커널 스택에 푸시합니다.

/* 인터럽트 진입 매크로 (개념적) */
.macro idtentry_irq vector cfunc
SYM_CODE_START(asm_\cfunc)
    /* CPU가 자동 저장: SS, RSP, RFLAGS, CS, RIP */
    pushq   $~(\vector)          /* 에러 코드 위치에 벡터 저장 */
    call    error_entry           /* 범용 레지스터 저장, swapgs */
    movq    %rsp, %rdi           /* pt_regs 포인터 */
    movl    \vector, %esi        /* IRQ 번호 */
    call    \cfunc               /* C 핸들러 호출 */
    jmp     error_return         /* 레지스터 복원, iretq */
SYM_CODE_END(asm_\cfunc)
.endm

어셈블리 메모리 배리어

현대 CPU는 성능을 위해 메모리 연산 순서를 재배치합니다. 멀티프로세서 환경에서 올바른 동작을 보장하려면 메모리 배리어가 필수입니다.

x86 메모리 배리어

x86은 비교적 강한 메모리 순서를 보장하는 TSO (Total Store Ordering) 모델을 사용합니다. 그러나 일부 상황에서는 명시적 배리어가 필요합니다.

/* x86 하드웨어 배리어 명령어 */
mfence                          /* 전체 배리어: 모든 load/store 완료 보장 */
lfence                          /* 로드 배리어: 이전 load 완료 후 다음 실행 */
sfence                          /* 스토어 배리어: 이전 store 완료 보장 */

/* lock 접두사: 원자적 RMW + 전체 배리어 효과 */
lock incl (%rdi)                /* 원자적 증가 + 암묵적 mfence */
lock cmpxchgl %eax, (%rdi)      /* 원자적 CAS */
lock xaddl %eax, (%rdi)         /* 원자적 교환 후 덧셈 */
/* 커널 배리어 매크로 (arch/x86/include/asm/barrier.h) */
#define mb()     asm volatile("mfence" ::: "memory")
#define rmb()    asm volatile("lfence" ::: "memory")
#define wmb()    asm volatile("sfence" ::: "memory")

/* SMP 배리어: UP에서는 컴파일러 배리어만 */
#define smp_mb()   asm volatile("lock; addl $0,-4(%%rsp)" ::: "memory", "cc")
#define smp_rmb()  barrier()  /* x86 TSO: load는 순서 보장 */
#define smp_wmb()  barrier()  /* x86 TSO: store는 순서 보장 */

/* 컴파일러 배리어 (하드웨어 배리어 아님!) */
#define barrier() asm volatile("" ::: "memory")

ARM64 메모리 배리어

ARM64는 x86보다 약한 메모리 순서 모델을 사용하므로 배리어가 더 중요합니다.

/* ARM64 배리어 명령어 */
dmb     sy                      /* Data Memory Barrier (전체) */
dmb     ish                     /* Inner Shareable 도메인 */
dmb     ishld                   /* Inner Shareable, Load만 */
dmb     ishst                   /* Inner Shareable, Store만 */

dsb     sy                      /* Data Synchronization Barrier */
dsb     ish                     /* 모든 이전 메모리 접근 완료 보장 */

isb                             /* Instruction Synchronization Barrier */
                                /* 파이프라인 플러시, 시스템 레지스터 변경 후 필요 */
/* ARM64 커널 배리어 (arch/arm64/include/asm/barrier.h) */
#define mb()     asm volatile("dmb sy" ::: "memory")
#define rmb()    asm volatile("dmb ld" ::: "memory")
#define wmb()    asm volatile("dmb st" ::: "memory")

#define smp_mb()   asm volatile("dmb ish" ::: "memory")
#define smp_rmb()  asm volatile("dmb ishld" ::: "memory")
#define smp_wmb()  asm volatile("dmb ishst" ::: "memory")

/* ARM64 Acquire/Release 의미론 (LDAR/STLR) */
/* smp_load_acquire → LDAR: 이후 load/store가 앞으로 재배치 안 됨 */
/* smp_store_release → STLR: 이전 load/store가 뒤로 재배치 안 됨 */
💡

x86의 TSO 모델에서는 smp_rmb()smp_wmb()가 컴파일러 배리어만으로 충분하지만, ARM64에서는 실제 하드웨어 배리어 명령어가 필요합니다. 이것이 커널이 아키텍처별 배리어 매크로를 제공하는 이유입니다.

어셈블리 디버깅

커널 어셈블리 코드를 디버깅하는 것은 C 코드보다 어렵지만, 적절한 도구를 활용하면 효율적으로 문제를 찾을 수 있습니다.

objdump로 디스어셈블

# 커널 이미지 디스어셈블 (특정 함수)
objdump -d vmlinux | grep -A 30 '<do_syscall_64>:'

# 인라인 어셈블리 확인: C 소스와 어셈블리 혼합 출력
objdump -d -S -l vmlinux | less

# 특정 섹션만 디스어셈블
objdump -d -j .text vmlinux

# 모듈 디스어셈블
objdump -d my_module.ko

# 릴로케이션 정보 확인
objdump -r my_module.ko

GDB 디스어셈블

# QEMU + GDB로 커널 디버깅
qemu-system-x86_64 -kernel bzImage -s -S ...

# 다른 터미널에서
gdb vmlinux
(gdb) target remote :1234
(gdb) disassemble do_syscall_64       # 함수 디스어셈블
(gdb) x/20i $rip                      # 현재 위치부터 20개 명령어
(gdb) display/i $pc                   # 매 스텝마다 현재 명령어 표시
(gdb) stepi                           # 명령어 단위 스텝 (si) */
(gdb) nexti                           # call 건너뛰기 (ni) */
(gdb) info registers                  # 레지스터 확인 */
(gdb) p/x $rax                        # 특정 레지스터 값 */

# Intel 문법으로 전환
(gdb) set disassembly-flavor intel

ftrace function_graph로 흐름 추적

# function_graph tracer로 함수 호출 흐름 시각화
cd /sys/kernel/debug/tracing
echo function_graph > current_tracer
echo do_syscall_64 > set_graph_function   # 특정 함수 하위만
echo 1 > tracing_on
# ... 작업 수행 ...
cat trace

# 출력 예시:
#  0)               |  do_syscall_64() {
#  0)   0.150 us    |    syscall_enter_from_user_mode();
#  0)               |    __x64_sys_write() {
#  0)               |      ksys_write() {
#  0)   0.087 us    |        __fdget_pos();
#  0)               |        vfs_write() {
#  0)   ...

참고자료

공식 매뉴얼 및 문서

추천 서적

커널 소스 내 어셈블리 참고 위치

경로설명
arch/x86/entry/entry_64.Sx86_64 시스템 콜, 인터럽트 진입점
arch/x86/lib/memcpy, memset 등 최적화된 어셈블리 구현
arch/x86/include/asm/인라인 어셈블리 헤더 (atomic, bitops, barrier)
arch/arm64/kernel/entry.SARM64 예외/인터럽트 진입점
arch/arm64/lib/ARM64 문자열/메모리 함수 어셈블리
include/linux/compiler.hREAD_ONCE, WRITE_ONCE, barrier 매크로
include/asm-generic/barrier.h아키텍처 공통 배리어 정의

커널 어셈블리 코드를 읽을 때는 arch/ 디렉토리 아래의 아키텍처별 코드부터 시작하세요. include/asm/ 헤더에 있는 인라인 어셈블리는 C 코드와 자연스럽게 통합되어 있어 이해하기 더 쉽습니다. Bootlin Elixir 크로스 레퍼런스를 활용하면 심볼 정의를 빠르게 찾을 수 있습니다.

CPU 매뉴얼과 어셈블리 개발 실무

Intel SDM 읽는 법 — 명령어 레퍼런스 예시

Intel SDM Vol. 2의 각 명령어 항목은 다음과 같은 구조로 되어 있으며, 인라인 어셈블리 작성 시 반드시 확인해야 할 핵심 정보를 담고 있습니다:

/* Intel SDM Vol.2 명령어 항목 구조 예시: CMPXCHG */

/* Opcode    | Instruction          | Description */
/* 0F B0 /r  | CMPXCHG r/m8, r8     | Compare AL with r/m8 */
/* 0F B1 /r  | CMPXCHG r/m32, r32   | Compare EAX with r/m32 */
/* REX.W + 0F B1 /r | CMPXCHG r/m64, r64 | Compare RAX with r/m64 */

/* 핵심 확인 사항:
 * 1. Flags Affected: ZF (성공 시 set), CF/PF/AF/SF/OF
 * 2. #UD: LOCK prefix 없이 메모리 오퍼랜드에 사용 시 동작 정의
 * 3. LOCK prefix: 메모리 오퍼랜드에 LOCK 사용 가능 (atomic)
 * 4. 64-bit mode: REX.W prefix로 64비트 확장
 */

/* 커널에서의 실제 사용 — arch/x86/include/asm/cmpxchg.h */
static inline unsigned long __cmpxchg(volatile void *ptr,
        unsigned long old, unsigned long new, int size)
{
    unsigned long prev;
    switch (size) {
    case 8:
        asm volatile("lock; cmpxchgq %1, %2"
            : "=a"(prev)
            : "r"(new), "m"(*(u64 *)ptr), "0"(old)
            : "memory");
        return prev;
    }
}

ARM ARM 읽는 법 — 시스템 레지스터

ARM 커널 코드에서는 시스템 레지스터 접근이 빈번합니다. ARM ARM의 시스템 레지스터 장은 각 레지스터의 비트 필드와 접근 조건을 정의합니다:

/* ARM ARM에서 SCTLR_EL1 레지스터 비트 필드 확인 후 커널 코드 이해 */
/* arch/arm64/include/asm/sysreg.h */
#define SCTLR_EL1_MMU_EN    (1 << 0)   /* M bit: MMU enable */
#define SCTLR_EL1_ALIGN     (1 << 1)   /* A bit: Alignment check */
#define SCTLR_EL1_DCACHE    (1 << 2)   /* C bit: Data cache enable */
#define SCTLR_EL1_WXN       (1 << 19)  /* WXN: Write implies XN */
#define SCTLR_EL1_ICACHE    (1 << 12)  /* I bit: Instruction cache */

/* ARM64 어셈블리에서 시스템 레지스터 접근 */
asm volatile("mrs %0, sctlr_el1" : "=r"(val));   /* 읽기 */
asm volatile("msr sctlr_el1, %0" :: "r"(val));    /* 쓰기 */
asm volatile("isb");  /* Instruction Synchronization Barrier 필수 */

RISC-V Privileged ISA — CSR 접근

/* RISC-V Privileged ISA에서 CSR 정의 확인 후 커널 코드 이해 */
/* arch/riscv/include/asm/csr.h */
#define CSR_SSTATUS     0x100   /* Supervisor Status */
#define CSR_SIE         0x104   /* Supervisor Interrupt Enable */
#define CSR_STVEC       0x105   /* Supervisor Trap Vector */
#define CSR_SATP        0x180   /* Supervisor Address Translation */

/* RISC-V CSR 접근 인라인 어셈블리 */
#define csr_read(csr)  ({                 unsigned long __v;                      asm volatile("csrr %0, " __stringify(csr)         : "=r"(__v) :: "memory");          __v; })

/* satp 레지스터: 페이지 테이블 모드(Sv39/48/57) + ASID + PPN */
/* Privileged ISA Vol.II Section 4.1.11 참조 */
#define SATP_MODE_SV39  (8UL << 60)
#define SATP_MODE_SV48  (9UL << 60)
#define SATP_MODE_SV57  (10UL << 60)
매뉴얼-코드 매핑 요령:
  • 커널 헤더의 #define 상수는 거의 항상 해당 아키텍처 매뉴얼의 비트 필드 정의와 1:1 대응합니다
  • 커널 소스의 주석에 "See Intel SDM Vol. 3, Section X.Y" 같은 참조가 있으면 반드시 확인하세요
  • arch/x86/include/asm/msr-index.h의 MSR 정의는 Intel SDM Vol. 4에서 직접 가져온 것입니다
  • AMD 전용 MSR은 같은 파일에 MSR_AMD64_* 접두어로 정의되어 있으며, AMD APM을 참조합니다

SIMD 명령과 커널 개발

x86 SIMD 확장 역사

확장레지스터비트 폭도입주요 기능
MMXMM0-MM764-bitPentium MMX (1997)정수 SIMD, FPU 레지스터 공유
SSEXMM0-XMM7128-bitPentium III (1999)단정밀도 부동소수점 4개 병렬
SSE2XMM0-XMM15 (64-bit)128-bitPentium 4 (2001)배정밀도, 정수 128-bit. x86-64 기본 지원
SSE3/SSSE3/SSE4XMM128-bit2004-2008수평 연산, 문자열 비교, CRC32
AVXYMM0-YMM15256-bitSandy Bridge (2011)256-bit 부동소수점, VEX 인코딩
AVX2YMM0-YMM15256-bitHaswell (2013)256-bit 정수, Gather, FMA3
AVX-512ZMM0-ZMM31, k0-k7512-bitXeon Phi / Skylake-X (2016)512-bit, 마스크 레지스터, scatter/gather
AMXTMM0-TMM7타일 (최대 1KB)Sapphire Rapids (2023)행렬 곱셈 가속 (INT8/BF16)
AVX10ZMM/YMM/XMM128~512-bitGranite Rapids (2024+)AVX-512 통합 후속, 벡터 길이 프로필

x86 SIMD 레지스터 레이아웃

/*
 * x86 SIMD 레지스터의 계층 구조 (하위 호환)
 *
 * ZMM0 (512-bit) ─ AVX-512
 * ├── YMM0 (256-bit, 하위 256) ─ AVX/AVX2
 * │   ├── XMM0 (128-bit, 하위 128) ─ SSE/SSE2
 * │   │   ├── [127:64]  상위 64-bit
 * │   │   └── [63:0]    하위 64-bit (스칼라 float/double)
 * │   └── [255:128] AVX 상위 128-bit
 * └── [511:256] AVX-512 상위 256-bit
 *
 * x86-64: XMM0-XMM15 (SSE), YMM0-YMM15 (AVX), ZMM0-ZMM31 (AVX-512)
 * AVX-512는 XMM16-XMM31, YMM16-YMM31도 추가
 *
 * 마스크 레지스터: k0-k7 (AVX-512)
 *   k0은 항상 "all ones" (마스크 없음)
 *   k1-k7: 조건부 연산에 사용
 */

/* 레지스터 내 데이터 해석 (XMM 128-bit 기준) */
/*
 * packed single (4 × float32):  [f3 | f2 | f1 | f0]
 * packed double (2 × float64):  [d1      | d0     ]
 * packed byte   (16 × int8):    [b15|...|b1|b0    ]
 * packed word   (8 × int16):    [w7|w6|...|w1|w0  ]
 * packed dword  (4 × int32):    [d3 | d2 | d1 | d0]
 * packed qword  (2 × int64):    [q1      | q0     ]
 */

VEX vs Legacy 인코딩: SSE 명령어(legacy)로 XMM 레지스터를 수정하면 YMM/ZMM의 상위 비트가 보존됩니다. 반면 VEX 인코딩(AVX) 명령어는 YMM 상위 128비트를 자동으로 0으로 클리어합니다. legacy SSE와 VEX/EVEX 명령어를 혼용하면 SSE-AVX 전환 페널티가 발생하므로, 커널 SIMD 코드에서는 한 가지 인코딩만 일관되게 사용해야 합니다.

x86 SIMD 핵심 명령어 패턴

로드/스토어 명령어

/* === SSE/AVX 로드/스토어 명령어 === */

/* 정렬된 로드/스토어 (16/32/64바이트 정렬 필수) */
movaps  (%rdi), %xmm0           /* 128-bit aligned load (packed single) */
movapd  (%rdi), %xmm0           /* 128-bit aligned load (packed double) */
movdqa  (%rdi), %xmm0           /* 128-bit aligned load (integer) */
vmovaps (%rdi), %ymm0           /* 256-bit aligned load (VEX) */
vmovaps (%rdi), %zmm0           /* 512-bit aligned load (EVEX) */

/* 비정렬 로드/스토어 (정렬 불필요, 약간 느릴 수 있음) */
movups  (%rdi), %xmm0           /* 128-bit unaligned (packed single) */
movdqu  (%rdi), %xmm0           /* 128-bit unaligned (integer) */
vmovdqu (%rdi), %ymm0           /* 256-bit unaligned */
vmovdqu32 (%rdi), %zmm0         /* 512-bit unaligned (EVEX, 32-bit elem) */

/* Non-Temporal 스토어 (캐시 오염 방지, Write-Combining) */
movntps %xmm0, (%rdi)           /* 캐시를 거치지 않고 메모리에 직접 기록 */
movntdq %xmm0, (%rdi)           /* 정수 non-temporal store */
vmovntps %ymm0, (%rdi)          /* 256-bit non-temporal */
sfence                           /* non-temporal store 후 반드시 sfence */

/* 마스크 로드/스토어 (AVX-512) */
vmovdqu32 (%rdi), %zmm0{%k1}    /* k1 마스크 비트가 1인 요소만 로드 */
vmovdqu32 (%rdi), %zmm0{%k1}{z} /* zero-masking: 마스크 0인 요소는 0 */

산술/논리 연산

/* === SSE2 정수 연산 (128-bit) === */
paddb   %xmm1, %xmm0           /* 바이트 단위 덧셈 (16개 병렬) */
paddw   %xmm1, %xmm0           /* 워드 단위 덧셈 (8개 병렬) */
paddd   %xmm1, %xmm0           /* 더블워드 단위 덧셈 (4개 병렬) */
paddq   %xmm1, %xmm0           /* 쿼드워드 단위 덧셈 (2개 병렬) */
psubb   %xmm1, %xmm0           /* 바이트 단위 뺄셈 */

/* 논리 연산 (비트 단위, 데이터 타입 무관) */
pxor    %xmm1, %xmm0           /* 128-bit XOR (RAID, 암호에 핵심) */
pand    %xmm1, %xmm0           /* 128-bit AND */
por     %xmm1, %xmm0           /* 128-bit OR */
pandn   %xmm1, %xmm0           /* ~xmm0 AND xmm1 */

/* === AVX2 정수 연산 (256-bit, VEX 3-operand) === */
vpaddd  %ymm2, %ymm1, %ymm0    /* ymm0 = ymm1 + ymm2 (비파괴적) */
vpxor   %ymm2, %ymm1, %ymm0    /* ymm0 = ymm1 XOR ymm2 */

/* === AVX-512 연산 (512-bit, EVEX) === */
vpaddd  %zmm2, %zmm1, %zmm0            /* 16개 int32 병렬 덧셈 */
vpaddd  %zmm2, %zmm1, %zmm0{%k1}       /* 마스크 적용: k1=0인 요소 유지 */
vpaddd  %zmm2, %zmm1, %zmm0{%k1}{z}    /* 마스크 적용: k1=0인 요소 = 0 */

/* === 부동소수점 연산 === */
addps   %xmm1, %xmm0           /* 4 × float 병렬 덧셈 */
mulpd   %xmm1, %xmm0           /* 2 × double 병렬 곱셈 */
vfmadd231ps %ymm2, %ymm1, %ymm0 /* FMA: ymm0 = ymm1*ymm2 + ymm0 */

셔플/퍼뮤트/비트 조작

/* 데이터 재배열 명령어 — SIMD 프로그래밍의 핵심 */

/* PSHUFB (SSSE3): 바이트 단위 임의 셔플 (look-up table 패턴) */
/* xmm0의 각 바이트를 xmm1의 인덱스에 따라 재배열 */
pshufb  %xmm1, %xmm0           /* xmm0[i] = xmm0[xmm1[i] & 0xF] */
                                /* xmm1[i] bit7=1이면 xmm0[i]=0 */

/* PSHUFD: 32-bit 요소 셔플 (즉시값으로 순서 지정) */
pshufd  $0x39, %xmm1, %xmm0   /* xmm0 = {xmm1[0],xmm1[3],xmm1[2],xmm1[1]} */

/* PUNPCKLBW/PUNPCKHBW: 인터리브 (바이트 단위) */
punpcklbw %xmm1, %xmm0         /* 하위 8바이트를 인터리브 */

/* AVX2 퍼뮤트: 128-bit 레인 간 교차 가능 */
vperm2i128 $0x31, %ymm1, %ymm0, %ymm2  /* 128-bit 레인 교환 */
vpermd  %ymm1, %ymm0, %ymm2    /* 32-bit 단위 임의 퍼뮤트 */

/* AVX-512 퍼뮤트 */
vpermb  %zmm1, %zmm0, %zmm2    /* 바이트 단위 64-way 퍼뮤트 */
vpermt2d %zmm2, %zmm1, %zmm0   /* 2-source 퍼뮤트 (merge) */

/* 비트 시프트 (요소별) */
pslld   $4, %xmm0              /* 각 32-bit 요소를 4비트 좌측 시프트 */
psrld   $4, %xmm0              /* 각 32-bit 요소를 4비트 우측 시프트 */
vpsllvd %ymm1, %ymm0, %ymm2    /* AVX2: 요소별 가변 시프트 */

비교/블렌드/변환

/* === 비교 명령어 === */
pcmpeqb %xmm1, %xmm0           /* 바이트 비교: 같으면 0xFF, 다르면 0x00 */
pcmpgtd %xmm1, %xmm0           /* 부호있는 32-bit 비교: xmm0>xmm1이면 0xFFFFFFFF */

/* SSE4.2 문자열 비교 (커널에서 문자열 처리에 활용) */
pcmpistri $0x08, %xmm1, %xmm0  /* Equal Each: 바이트 일치 인덱스 → ECX */
pcmpestri $0x00, %xmm1, %xmm0  /* Equal Any: 문자 집합 검색 */

/* AVX-512 비교 → 마스크 레지스터 */
vpcmpeqd %zmm1, %zmm0, %k1     /* 16개 int32 비교 → k1 마스크 (16-bit) */
vpcmpud $1, %zmm1, %zmm0, %k2  /* 부호없는 less-than 비교 */
kmovw   %k1, %eax               /* 마스크 → 범용 레지스터 전송 */
popcnt  %eax, %eax              /* 일치 개수 세기 */

/* === 블렌드: 조건부 선택 === */
blendvps %xmm0, %xmm1, %xmm2   /* SSE4.1: xmm0 MSB로 xmm1/xmm2 선택 */
vpblendvb %ymm3, %ymm2, %ymm1, %ymm0 /* AVX2: 바이트별 조건부 선택 */
vpblendmd %zmm2, %zmm1, %zmm0{%k1}   /* AVX-512: 마스크 기반 블렌드 */

/* === 데이터 타입 변환 === */
cvtdq2ps  %xmm0, %xmm1         /* 4 × int32 → 4 × float */
cvtps2dq  %xmm0, %xmm1         /* 4 × float → 4 × int32 (반올림) */
cvttps2dq %xmm0, %xmm1         /* 4 × float → 4 × int32 (truncate) */
vpmovzxbw %xmm0, %ymm1         /* 16 × uint8 → 16 × uint16 (zero-extend) */
vpmovsxwd %xmm0, %ymm1         /* 8 × int16 → 8 × int32 (sign-extend) */

암호화 전용 SIMD 명령어

/* === AES-NI 명령어 (커널 crypto 핵심) === */
aesenc    %xmm1, %xmm0         /* AES 한 라운드 암호화 */
aesenclast %xmm1, %xmm0        /* AES 마지막 라운드 암호화 */
aesdec    %xmm1, %xmm0         /* AES 한 라운드 복호화 */
aesdeclast %xmm1, %xmm0        /* AES 마지막 라운드 복호화 */
aeskeygenassist $1, %xmm0, %xmm1 /* AES 라운드 키 생성 보조 */
aesimc    %xmm0, %xmm1         /* InvMixColumns (복호화 키 변환) */

/* VAES: 256/512-bit AES (AVX-512 + VAES) */
vaesenc   %ymm2, %ymm1, %ymm0  /* 2블록 병렬 AES 암호화 */
vaesenc   %zmm2, %zmm1, %zmm0  /* 4블록 병렬 AES 암호화 */

/* === PCLMULQDQ: 갈루아 필드 곱셈 (GCM, CRC) === */
pclmulqdq $0x00, %xmm1, %xmm0  /* Carry-less multiply: xmm0[63:0] × xmm1[63:0] */
pclmulqdq $0x11, %xmm1, %xmm0  /* xmm0[127:64] × xmm1[127:64] */
vpclmulqdq $0x00, %zmm1, %zmm0, %zmm2 /* 512-bit VPCLMULQDQ (4 병렬) */

/* === SHA-NI 명령어 === */
sha256rnds2 %xmm0, %xmm1       /* SHA-256 2라운드 처리 */
sha256msg1  %xmm1, %xmm0       /* SHA-256 메시지 스케줄 1 */
sha256msg2  %xmm1, %xmm0       /* SHA-256 메시지 스케줄 2 */
sha1rnds4   $0, %xmm1, %xmm0   /* SHA-1 4라운드 처리 */
sha1nexte   %xmm1, %xmm0       /* SHA-1 다음 E값 계산 */

/* === CRC32 (SSE4.2) === */
crc32b  (%rdi), %eax            /* CRC32C: 1바이트 누적 */
crc32q  (%rdi), %rax            /* CRC32C: 8바이트 누적 */

Gather/Scatter (AVX2/AVX-512)

/* Gather: 불연속 메모리 주소에서 벡터로 모아 읽기 */
/* 기존 스칼라 코드:
 *   for (i = 0; i < 8; i++) result[i] = base[index[i]];
 * → 단일 Gather 명령어로 대체 */

/* AVX2 Gather */
vgatherdps %ymm2, (%rdi,%ymm1,4), %ymm0
/* ymm0[i] = MEM[rdi + ymm1[i]*4] (ymm2 마스크 기반)
 * ymm2는 마스크 겸 오류 추적, 실행 후 0으로 클리어됨 */

vgatherdpd %xmm2, (%rdi,%xmm1,8), %ymm0
/* 4개 인덱스(xmm1, 32-bit) → 4개 double 로드 → ymm0 */

/* AVX-512 Gather (마스크 레지스터 사용, 더 효율적) */
vgatherdps (%rdi,%zmm1,4), %zmm0{%k1}
/* k1 마스크가 1인 요소만 로드, 완료된 요소의 k1 비트 클리어 */

/* AVX-512 Scatter: 벡터에서 불연속 메모리 주소로 흩뿌려 쓰기 */
vscatterdps %zmm0, (%rdi,%zmm1,4){%k1}
/* MEM[rdi + zmm1[i]*4] = zmm0[i] (k1 마스크 적용)
 * Gather의 역연산. AVX-512 이전에는 없었음 */
💡

Gather 성능 주의: Gather 명령어는 편리하지만, 내부적으로 여러 개의 스칼라 로드를 수행합니다. 캐시 라인이 연속적이지 않으면 성능 이점이 제한됩니다. Intel Skylake-X 이후부터 성능이 크게 개선되었으며, 커널에서는 주로 테이블 룩업이 빈번한 암호 알고리즘에서 활용됩니다.

ARM SIMD 확장

확장레지스터비트 폭도입주요 기능
NEON (Advanced SIMD)V0-V31 (AArch64)128-bitARMv7 / ARMv8정수+부동소수점 SIMD, 암호 확장(CE)
SVE (Scalable Vector)Z0-Z31, P0-P15128~2048-bitARMv8.2-A가변 벡터 길이, 프레디케이트 레지스터
SVE2Z0-Z31128~2048-bitARMv9-ANEON 명령어 세트 포괄, 암호 확장
SMEZA 타일SVE VL × SVE VLARMv9.2-A행렬 연산, Streaming SVE 모드

ARM NEON 핵심 명령어

/* ARM NEON (Advanced SIMD) 레지스터 구조
 *
 * V0-V31: 128-bit 벡터 레지스터
 * 접근 방식:
 *   Vn.16B  = 16 × byte    Vn.8B  = 하위 8 × byte (64-bit)
 *   Vn.8H   = 8 × halfword Vn.4H  = 하위 4 × halfword
 *   Vn.4S   = 4 × single   Vn.2S  = 하위 2 × single
 *   Vn.2D   = 2 × double   Vn.1D  = 하위 1 × double
 *   Bn/Hn/Sn/Dn = 스칼라 접근 (byte/half/single/double)
 */

/* === 로드/스토어 === */
ld1     {v0.16b}, [x0]          /* 128-bit 연속 로드 */
ld1     {v0.16b, v1.16b}, [x0]  /* 256-bit 연속 로드 (2 레지스터) */
ld1     {v0.4s-v3.4s}, [x0]     /* 512-bit 연속 로드 (4 레지스터) */
st1     {v0.16b}, [x0]          /* 128-bit 스토어 */

/* Structure 로드: 인터리브 해제 (SoA 변환) */
ld2     {v0.4s, v1.4s}, [x0]    /* 2-way 디인터리브: ABABAB → A,B */
ld3     {v0.4s, v1.4s, v2.4s}, [x0] /* RGB → R,G,B 분리 */
ld4     {v0.4s-v3.4s}, [x0]     /* 4-way 디인터리브 */
st2     {v0.4s, v1.4s}, [x0]    /* 2-way 인터리브 저장 */

/* === 산술 연산 === */
add     v0.4s, v1.4s, v2.4s     /* 4 × int32 덧셈 */
sub     v0.8h, v1.8h, v2.8h     /* 8 × int16 뺄셈 */
mul     v0.4s, v1.4s, v2.4s     /* 4 × int32 곱셈 */
mla     v0.4s, v1.4s, v2.4s     /* v0 += v1 * v2 (multiply-accumulate) */
fmul    v0.4s, v1.4s, v2.4s     /* 4 × float 곱셈 */
fmla    v0.4s, v1.4s, v2.4s     /* fused multiply-add (FP) */

/* === 논리/비트 연산 === */
eor     v0.16b, v1.16b, v2.16b  /* 128-bit XOR */
and     v0.16b, v1.16b, v2.16b  /* 128-bit AND */
bsl     v0.16b, v1.16b, v2.16b  /* Bitwise Select: bit=1 → v1, bit=0 → v2 */

/* === 비교 === */
cmeq    v0.4s, v1.4s, v2.4s     /* 같으면 0xFFFFFFFF, 다르면 0 */
cmgt    v0.4s, v1.4s, v2.4s     /* 부호있는 greater-than */
cmhi    v0.4s, v1.4s, v2.4s     /* 부호없는 greater-than (higher) */

/* === 셔플/퍼뮤트 === */
tbl     v0.16b, {v1.16b}, v2.16b /* 테이블 룩업 (x86 PSHUFB와 유사) */
tbl     v0.16b, {v1.16b, v2.16b}, v3.16b /* 32바이트 테이블 룩업 */
trn1    v0.4s, v1.4s, v2.4s     /* Transpose (짝수 요소 인터리브) */
trn2    v0.4s, v1.4s, v2.4s     /* Transpose (홀수 요소 인터리브) */
zip1    v0.4s, v1.4s, v2.4s     /* 하위 절반 인터리브 */
uzp1    v0.4s, v1.4s, v2.4s     /* 짝수 요소 추출 */
rev64   v0.16b, v1.16b          /* 64-bit 단위 내 바이트 역순 */
ext     v0.16b, v1.16b, v2.16b, #4 /* 연결 후 4바이트 시프트 추출 */

/* === 암호 확장 (ARMv8 Crypto Extension) === */
aese    v0.16b, v1.16b          /* AES 단일 라운드 암호화 */
aesd    v0.16b, v1.16b          /* AES 단일 라운드 복호화 */
aesmc   v0.16b, v1.16b          /* AES MixColumns */
aesimc  v0.16b, v1.16b          /* AES InvMixColumns */
pmull   v0.1q, v1.1d, v2.1d     /* 다항식 곱셈 (GHASH) */
sha256h  q0, q1, v2.4s          /* SHA-256 해시 업데이트 */
sha256su0 v0.4s, v1.4s          /* SHA-256 스케줄 업데이트 */

ARM SVE 프로그래밍 모델

SVE(Scalable Vector Extension)는 벡터 길이를 하드웨어 구현에 의존하는 벡터 길이 비종속 (Vector Length Agnostic, VLA) 프로그래밍 모델입니다. 동일한 바이너리가 128비트~2048비트 구현에서 동작합니다.

/* SVE 레지스터 구조
 *
 * Z0-Z31: 스케일러블 벡터 (128~2048-bit, 하드웨어 정의)
 *   Zn의 하위 128비트 = NEON Vn과 공유 (아키텍처 보장)
 * P0-P15: 프레디케이트 레지스터 (VL/8 비트)
 *   각 비트가 벡터의 한 바이트에 대응
 *   P0-P7: governing predicate로 사용 가능
 * FFR: First Fault Register (투기적 로드용)
 * VL: Vector Length (cntb로 바이트 단위 조회)
 */

/* === 벡터 길이 조회 === */
cntb    x0                      /* x0 = VL (바이트 단위). 예: 512-bit → 64 */
cnth    x0                      /* x0 = VL / 2 (halfword 개수) */
cntw    x0                      /* x0 = VL / 4 (word 개수) */
cntd    x0                      /* x0 = VL / 8 (doubleword 개수) */

/* === 프레디케이트 생성 === */
ptrue   p0.b                    /* 모든 바이트 활성 (all-true) */
ptrue   p0.s                    /* 모든 word 활성 */
whilelt p0.s, x0, x1            /* x0 < x1인 요소만 활성 (루프 테일 처리) */
pfalse  p0.b                    /* 모든 비트 비활성 (all-false) */

/* === VLA 루프 패턴 (커널에서 가장 중요) === */
/* memcpy를 SVE로 구현하는 예시 */
/*   x0 = dst, x1 = src, x2 = len (바이트) */
    mov     x3, #0              /* 오프셋 초기화 */
    whilelt p0.b, x3, x2        /* 프레디케이트 설정 */
.loop:
    ld1b    z0.b, p0/z, [x1, x3] /* 프레디케이트 기반 로드 */
    st1b    z0.b, p0, [x0, x3]   /* 프레디케이트 기반 스토어 */
    incb    x3                    /* x3 += VL (바이트 수만큼 증가) */
    whilelt p0.b, x3, x2         /* 잔여 요소 프레디케이트 갱신 */
    b.first .loop                 /* 활성 요소 있으면 계속 */

/* === Scatter/Gather (SVE 기본 지원) === */
ld1w    z0.s, p0/z, [x0, z1.s, uxtw #2] /* Gather: x0 + z1[i]*4 */
st1w    z0.s, p0, [x0, z1.s, uxtw #2]   /* Scatter */

/* === First Fault 로드 (투기적 로드, 커널 문자열 처리) === */
ldff1b  z0.b, p0/z, [x0, x1]    /* First Fault 로드 */
rdffr   p1.b                     /* FFR 읽기: 성공한 요소만 p1=1 */

SVE의 벡터 길이 비종속 설계: SVE 코드는 벡터 길이를 상수로 가정하지 않습니다. cntb로 런타임에 VL을 조회하고, whilelt 프레디케이트로 루프 테일을 자동 처리합니다. 이 덕분에 Fujitsu A64FX(512-bit)와 AWS Graviton3(256-bit) 등 다른 VL 구현에서 동일 바이너리가 동작합니다. 커널에서 SVE를 사용하는 코드는 arch/arm64/lib/의 문자열/메모리 함수에서 확인할 수 있습니다.

커널에서의 SIMD 사용 — 핵심 제약

/* ⚠ 커널 코드에서 SIMD 레지스터를 직접 사용할 수 없다! */
/*
 * 이유:
 * 1. 커널은 유저 프로세스의 FPU/SIMD 상태를 보존해야 함
 * 2. 인터럽트/softirq 컨텍스트에서 FPU 상태가 정의되지 않음
 * 3. preempt 시 FPU 상태가 손상될 수 있음
 *
 * 해결: kernel_fpu_begin() / kernel_fpu_end()
 * 이 API가 유저 FPU 상태를 저장/복원하고 preempt를 비활성화
 */

#include <asm/fpu/api.h>

void my_aes_encrypt(u8 *dst, const u8 *src, const u8 *key)
{
    /* FPU/SIMD 사용 전에 반드시 호출 */
    kernel_fpu_begin();

    /* 이 구간에서만 SSE/AVX/AES-NI 명령어 사용 가능 */
    asm volatile(
        "movdqu (%[key]), %%xmm0\n"
        "movdqu (%[src]), %%xmm1\n"
        "pxor   %%xmm0, %%xmm1\n"
        "aesenc %%xmm0, %%xmm1\n"
        "movdqu %%xmm1, (%[dst])\n"
        :: [dst] "r"(dst), [src] "r"(src), [key] "r"(key)
        : "xmm0", "xmm1", "memory"
    );

    /* FPU/SIMD 사용 후 반드시 호출 */
    kernel_fpu_end();
}

/* ARM64에서의 SIMD 사용 */
#include <asm/neon.h>
kernel_neon_begin();
/* NEON/SVE 명령어 사용 가능 */
kernel_neon_end();
커널 SIMD 사용의 엄격한 규칙:
  1. kernel_fpu_begin/end 필수 — 이 쌍 없이 SIMD 레지스터를 사용하면 유저 프로세스의 FPU 상태가 손상됨. 결과: 프로세스의 부동소수점 연산이 오염되어 무작위 연산 오류
  2. 컨텍스트 제한kernel_fpu_begin()preempt_disable()을 포함. sleep 불가. IRQ 핸들러, softirq에서도 사용 가능하지만 중첩 비용 큼
  3. 최소 구간 사용 — FPU begin/end 구간이 길면 preempt 지연이 증가하여 시스템 응답성 저하. 가능한 짧게 유지
  4. 대안: SIMD 전용 함수 — crypto API는 별도 SIMD 최적화 함수를 *-aesni, *-neon 등으로 등록하여 자동 관리
  5. CONFIG_CRYPTO_SIMD — 비동기 crypto에서 SIMD를 안전하게 사용하기 위한 래퍼. softirq에서 자동으로 generic fallback

XSAVE/XRSTOR 메커니즘 상세

XSAVE는 FPU/SIMD 상태를 확장 가능한 방식으로 저장/복원하는 x86 메커니즘입니다. kernel_fpu_begin()의 핵심 구현이며, 새로운 SIMD 확장이 추가될 때마다 자동으로 대응합니다.

/* XSAVE 상태 컴포넌트 (CPUID Leaf 0x0D로 확인) */
/*
 * 비트  컴포넌트              크기       오프셋
 * ─────────────────────────────────────────────────
 *  0   x87 FPU              512 bytes  0 (고정)
 *  1   SSE (XMM0-XMM15)     (위에 포함)  0 (고정)
 *  2   AVX (YMM 상위 128)   256 bytes  576
 *  3   MPX BNDREGS          64 bytes   (가변)
 *  4   MPX BNDCFG           64 bytes   (가변)
 *  5   AVX-512 opmask (k)   64 bytes   (가변)
 *  6   AVX-512 ZMM_Hi256    512 bytes  (가변)
 *  7   AVX-512 Hi16_ZMM     1024 bytes (가변)
 *  9   PKRU                 8 bytes    (가변)
 * 11   CET_U                16 bytes   (가변)
 * 12   CET_S                24 bytes   (가변)
 * 17   TILECFG (AMX)        64 bytes   (가변)
 * 18   TILEDATA (AMX)       8192 bytes (가변)
 */

/* 커널의 XSAVE 영역 크기 결정 — arch/x86/kernel/fpu/xstate.c */
void fpu__init_system_xstate(unsigned int legacy_size)
{
    u64 xfeatures_mask;
    unsigned int eax, ebx, ecx, edx;

    /* CPUID Leaf 0x0D, Sub-leaf 0: 지원 컴포넌트 마스크 */
    cpuid_count(0x0D, 0, &eax, &ebx, &ecx, &edx);
    xfeatures_mask = eax | ((u64)edx << 32);
    /* ebx = 현재 XCR0 기준 XSAVE 크기 */
    /* ecx = 지원되는 모든 컴포넌트의 최대 XSAVE 크기 */

    /* 각 컴포넌트의 크기/오프셋 조회 */
    for (int i = 2; i < 64; i++) {
        if (!(xfeatures_mask & (1ULL << i))) continue;
        cpuid_count(0x0D, i, &eax, &ebx, &ecx, &edx);
        /* eax = 컴포넌트 크기, ebx = 오프셋 */
        /* ecx bit 1: 이 컴포넌트가 compacted XSAVE에서 정렬 필요 */
    }
}

/* XSAVE 변종 명령어 */
/* XSAVE:    모든 컴포넌트 저장 (느림, 초기 변종) */
/* XSAVEOPT: 변경된 컴포넌트만 저장 (최적화) */
/* XSAVEC:   Compacted 형식으로 저장 (커널 기본) */
/* XSAVES:   Supervisor 컴포넌트 포함 + compacted */
/* XRSTOR:   컴포넌트 복원 */
/* XRSTORS:  Supervisor 컴포넌트 포함 복원 */

/* kernel_fpu_begin() 내부 흐름 */
void kernel_fpu_begin_mask(unsigned int kfpu_mask)
{
    preempt_disable();

    if (!test_thread_flag(TIF_NEED_FPU_LOAD)) {
        /* 유저 FPU 상태가 레지스터에 live → 메모리에 저장 */
        fpu_save_regs(fpu);
        /* 내부적으로 XSAVES 또는 XSAVEC 실행 */
    }
    /* 이제 커널 코드가 FPU/SIMD 레지스터를 자유롭게 사용 가능 */
}

void kernel_fpu_end(void)
{
    /* TIF_NEED_FPU_LOAD 플래그 설정 → 다음 유저 복귀 시 lazy restore */
    set_thread_flag(TIF_NEED_FPU_LOAD);
    preempt_enable();
}
💡

XSAVE 크기의 실무적 영향: SSE만 사용하면 XSAVE 영역이 ~576바이트이지만, AVX-512 전체를 사용하면 ~2.5KB 이상으로 증가합니다. AMX(TILEDATA)까지 포함하면 ~10KB에 달합니다. 이는 태스크당 thread.fpu 메모리 사용량과 컨텍스트 스위치 시간에 직접 영향을 미칩니다. 커널은 init_fpstate에서 각 프로세스가 실제로 사용하는 컴포넌트만 추적하여 불필요한 저장/복원을 최소화합니다.

커널 내 SIMD 활용 사례

용도x86 구현ARM64 구현커널 코드
AES 암호화 AES-NI (AESENC/AESDEC) ARMv8 Crypto Extension arch/x86/crypto/aesni-intel_glue.c
SHA 해싱 SHA-NI (Goldmont+) CE SHA instructions arch/x86/crypto/sha256_ssse3_glue.c
CRC32 SSE4.2 CRC32 명령, PCLMULQDQ CRC32 명령 arch/x86/crypto/crc32c-intel_glue.c
ChaCha20 AVX2/AVX-512 (4/8블록 병렬) NEON (4블록 병렬) arch/x86/crypto/chacha_glue.c
Poly1305 AVX2 (radix 2^26 표현) NEON arch/x86/crypto/poly1305_glue.c
RAID5/6 XOR AVX2/AVX-512 병렬 XOR NEON XOR lib/raid6/sse*.c, lib/raid6/neon*.c
memcpy/memset REP MOVSB (ERMS) / AVX NEON/SVE 최적화 copy arch/x86/lib/memcpy_64.S
체크섬 (IP/TCP) ADC 체인 + SIMD NEON 체크섬 arch/x86/lib/csum-partial_64.c
SM3/SM4 (중국 표준) AVX2 + AES-NI 활용 CE SM3/SM4 명령 arch/x86/crypto/sm4_aesni_avx2_glue.c
Zstd/LZ4 압축 SIMD 직접 사용 안 함 (스칼라) 스칼라 lib/zstd/ (유저 라이브러리 포팅)

crypto API와 SIMD 통합 구조

/* 커널 crypto API는 SIMD 가속을 투명하게 관리하는 구조를 갖추고 있음 */
/* arch/x86/crypto/aesni-intel_glue.c — 실제 등록 예시 */

static struct skcipher_alg aesni_skciphers[] = {
    {
        .base.cra_name        = "cbc(aes)",
        .base.cra_driver_name = "cbc-aes-aesni",  /* AES-NI 가속 */
        .base.cra_priority    = 400,             /* generic(100)보다 높은 우선순위 */
        .setkey               = aesni_skcipher_setkey,
        .encrypt              = cbc_encrypt,      /* SIMD 사용 */
        .decrypt              = cbc_decrypt,
    },
};

/* 우선순위 기반 자동 선택:
 *   cbc(aes) 요청 시:
 *     1. cbc-aes-aesni   (priority=400, AES-NI 필요)
 *     2. cbc-aes-generic (priority=100, 항상 사용 가능)
 *   → CPU가 AES-NI를 지원하면 자동으로 #1 선택 */

/* simd_skcipher_create_compat(): softirq에서의 SIMD 안전 처리 */
/*
 * 문제: softirq 컨텍스트에서 kernel_fpu_begin()이 실패할 수 있음
 *       (이미 process context에서 FPU를 사용 중일 때)
 *
 * 해결: simd 래퍼가 softirq를 감지하면 자동으로 generic fallback
 *       → cryptd (crypto daemon) 워크큐로 지연 처리
 */
struct simd_skcipher_alg *simd_skcipher_create_compat(
    const char *algname,      /* "cbc(aes)" */
    const char *drvname,      /* "cbc-aes-aesni" (내부 SIMD 알고리즘) */
    const char *basename);    /* "cbc(aes)" (fallback) */

/* 실제 호출 경로:
 *   유저 요청 → crypto_alloc_skcipher("cbc(aes)")
 *   → 커널이 "cbc-aes-aesni"의 simd 래퍼 선택
 *   → process context → kernel_fpu_begin() + AES-NI 직접 사용
 *   → softirq context → cryptd 워크큐 → process context에서 실행
 */

RAID6 SIMD 최적화 심화

/* RAID6는 GF(2^8) 갈루아 필드 연산을 대량으로 수행 → SIMD 핵심 활용처 */
/* lib/raid6/algos.c — 런타임 벤치마크로 최적 알고리즘 자동 선택 */

const struct raid6_calls *const raid6_algos[] = {
    /* 속도 순 (느림 → 빠름) */
    &raid6_intx1,     /* 순수 정수 (fallback) */
    &raid6_intx2,     /* 정수, 2-way unroll */
    &raid6_sse1x1,    /* SSE 128-bit */
    &raid6_sse1x2,    /* SSE 128-bit, 2-way unroll */
    &raid6_sse2x1,    /* SSE2 128-bit */
    &raid6_sse2x2,    /* SSE2 128-bit, 2-way unroll */
    &raid6_sse2x4,    /* SSE2 128-bit, 4-way unroll */
    &raid6_avx2x1,    /* AVX2 256-bit */
    &raid6_avx2x2,    /* AVX2 256-bit, 2-way unroll */
    &raid6_avx2x4,    /* AVX2 256-bit, 4-way unroll */
    &raid6_avx512x1,  /* AVX-512 512-bit */
    &raid6_avx512x2,  /* AVX-512 512-bit, 2-way unroll */
    &raid6_avx512x4,  /* AVX-512 512-bit, 4-way unroll */
    NULL,
};

/* 부팅 시 각 알고리즘을 벤치마크하여 최적 선택 */
/* dmesg 출력 예시:
 * raid6: avx512x4  gen() 29430 MB/s
 * raid6: avx512x2  gen() 27891 MB/s
 * raid6: avx2x4    gen() 18560 MB/s
 * raid6: using algorithm avx512x4 gen() 29430 MB/s
 */

/* AVX2 RAID6 P+Q 신드롬 계산 핵심 루프 */
/* lib/raid6/avx2.c */
static void raid6_avx21_gen_syndrome(int disks, size_t bytes, void **ptrs)
{
    kernel_fpu_begin();

    asm volatile(
        "vmovdqa %[x0f], %%ymm3\n"   /* 마스크 0x0F */
        "vpxor   %%ymm0, %%ymm0, %%ymm0\n" /* P = 0 */
        "vpxor   %%ymm1, %%ymm1, %%ymm1\n" /* Q = 0 */
        /* 각 디스크 데이터를 순회하며:
         *   P ^= data[i]          (단순 XOR)
         *   Q = GF_MUL(Q) ^ data[i]  (GF(2^8) 곱셈 후 XOR)
         * GF 곱셈은 VPSHUFB로 4-bit 룩업테이블 사용 */
        ::: "ymm0","ymm1","ymm2","ymm3","ymm4","ymm5","memory"
    );

    kernel_fpu_end();
}

AVX-512 커널 사용 시 특별 주의

AVX-512의 양면성:
  • 주파수 다운클럭 — AVX-512 명령 사용 시 CPU가 자동으로 터보 주파수를 낮춤 (Intel "AVX offset"). 512비트 실행 유닛 전력 소비 때문
  • 컨텍스트 스위치 비용 — ZMM0-ZMM31 + 마스크 레지스터를 저장/복원해야 하므로 XSAVE 크기 증가 (SSE: 512B, AVX: 1KB, AVX-512: 2.5KB+)
  • 커널 정책 — 리눅스 커널에서는 AVX-512를 crypto와 RAID에만 제한적으로 사용. 일반 커널 코드에서 AVX-512 사용은 권장되지 않음
  • clearcpuid 부트 옵션clearcpuid=avx512f로 AVX-512 비활성화 가능. 서버 환경에서 주파수 안정성을 위해 사용되기도 함
  • Intel P-core / E-core 비대칭 — Alder Lake 이후 하이브리드 아키텍처에서 E-core는 AVX-512를 지원하지 않아, OS 스케줄러가 AVX-512 사용 태스크를 P-core에만 배치해야 함. 커널의 intel_thread_director가 이를 관리

SIMD 대안: 컴파일러 자동 벡터화

/* 커널 코드에서 직접 SIMD 인라인 어셈블리 대신 */
/* 컴파일러 자동 벡터화를 활용하는 방법 */

/* GCC/Clang __attribute__((target)) 으로 특정 ISA 확장 활성화 */
__attribute__((target("avx2")))
void xor_block_avx2(u8 *dst, const u8 *src, size_t len)
{
    /* 컴파일러가 루프를 자동으로 AVX2 벡터화 */
    for (size_t i = 0; i < len; i++)
        dst[i] ^= src[i];
}

/* 주의: 커널 빌드 시 기본적으로 -mno-sse, -mno-mmx 플래그 적용 */
/* SIMD를 사용하는 .c 파일은 Makefile에서 명시적으로 활성화: */
/* CFLAGS_myfile.o += -msse2 -mavx2 */

SIMD 코드 디버깅과 성능 분석

# SIMD 레지스터 확인 (GDB)
(gdb) info vector                   # SSE/AVX 레지스터 전체 출력
(gdb) p $xmm0.v4_float              # XMM0을 4개 float로 해석
(gdb) p $xmm0.v16_int8              # XMM0을 16개 int8로 해석
(gdb) p $ymm0.v8_int32              # YMM0을 8개 int32로 해석
(gdb) p $zmm0.v64_int8              # ZMM0을 64개 int8로 해석

# perf로 SIMD 명령어 사용 분석
perf stat -e fp_arith_inst_retired.128b_packed_single \
          -e fp_arith_inst_retired.256b_packed_single \
          -e fp_arith_inst_retired.512b_packed_single \
          -- ./my_program
# 128/256/512비트 packed FP 명령어 사용 횟수 측정

# AVX-512 다운클럭 모니터링
perf stat -e core_power.lvl0_turbo_license \
          -e core_power.lvl1_turbo_license \
          -e core_power.lvl2_turbo_license \
          -- ./my_program
# Level 0=기본, Level 1=AVX2 offset, Level 2=AVX-512 offset

# 커널 RAID6 벤치마크 결과 확인
dmesg | grep raid6
# raid6: avx512x4  gen() 29430 MB/s
# raid6: using algorithm avx512x4 gen() 29430 MB/s

# 현재 CPU의 SIMD 지원 확인
grep -o 'sse\|sse2\|ssse3\|sse4_1\|sse4_2\|avx\|avx2\|avx512' /proc/cpuinfo | sort -u

# XSAVE 상태 크기 확인
cat /proc/cpuinfo | grep -o 'xsave[^ ]*'
/* objdump으로 SIMD 명령어 확인 */
/* objdump -d vmlinux | grep -E 'vmov|vpxor|vaes|vpshufb|vpadd' */

/* 커널 SIMD 코드의 일반적인 실수와 디버깅 */
/*
 * 1. kernel_fpu_begin/end 누락
 *    증상: 유저 프로세스의 부동소수점 결과가 무작위로 틀림
 *    진단: KASAN/UBSAN으로는 감지 불가. CONFIG_X86_DEBUG_FPU 활성화
 *
 * 2. clobber 리스트 누락
 *    증상: 최적화 수준에 따라 간헐적 오류
 *    진단: -O0에서 정상, -O2에서 비정상 → clobber 확인
 *
 * 3. 정렬 문제
 *    증상: movaps에서 #GP (General Protection) 예외
 *    해결: movdqu/vmovdqu (비정렬 변종) 사용 또는
 *          __attribute__((aligned(32))) 으로 정렬 보장
 *
 * 4. SSE/AVX 전환 페널티
 *    증상: 예상보다 성능이 낮음
 *    진단: perf stat -e assists.sse_avx_mix
 *    해결: VZEROUPPER로 상위 YMM 클리어 또는 VEX 인코딩 통일
 */

VZEROUPPER와 SSE/AVX 전환

/* AVX→SSE 전환 시 발생하는 성능 페널티 방지 */
/*
 * 문제: AVX 코드(ymm 사용)에서 SSE 코드(xmm만 사용)로 전환 시,
 *       CPU가 상위 128비트 상태를 추적하느라 성능 저하
 *
 * Intel Sandy Bridge~Haswell: 큰 전환 페널티
 * Intel Skylake+: "dirty upper state" 추적 비용 (더 작지만 존재)
 * AMD Zen: 전환 페널티 없음 (다른 물리 레지스터 파일)
 */

/* AVX 코드 종료 시 반드시 실행 */
vzeroupper                       /* 모든 YMM/ZMM의 상위 비트를 0으로 */

/* 커널에서의 적용: kernel_fpu_end() 전에 호출 */
/* 또는 AVX 함수 끝에 __attribute__((target("avx"))) + vzeroupper */

/* 커널 crypto 코드에서의 전형적 패턴 */
asm volatile(
    /* ... AVX2 연산 ... */
    "vzeroupper"                 /* SSE 코드로 돌아가기 전 필수 */
    ::: "ymm0", "ymm1", ..., "memory"
);
💡

커널 빌드와 SIMD: 커널은 -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx 플래그로 빌드됩니다. 이는 일반 C 코드에서 컴파일러가 SIMD를 자동 생성하는 것을 방지합니다. SIMD를 사용하는 특정 파일만 Makefile에서 CFLAGS_<file>.o += -msse2로 개별 활성화합니다. 어셈블리 파일(.S)에서는 이 제한이 없으므로 직접 SIMD 명령어를 사용할 수 있습니다.

CPUID 명령어 심화

CPUID는 x86 프로세서의 기능, 모델 정보, 캐시 토폴로지, 확장 기능 등을 쿼리하는 핵심 명령어입니다. Linux 커널은 부팅 초기에 CPUID를 광범위하게 사용하여 CPU 기능을 탐지하고, 런타임에 최적의 코드 경로를 선택합니다.

CPUID 기본 동작

/* CPUID 명령어 기본 사용법
 *   입력: EAX = leaf (기능 번호), ECX = sub-leaf (일부 leaf)
 *   출력: EAX, EBX, ECX, EDX (leaf에 따라 의미 다름)
 *
 *   Intel SDM Vol. 2A, "CPUID — CPU Identification" 참조
 */

/* Leaf 0: 최대 기본 leaf 번호 + 벤더 문자열 */
mov     $0, %eax
cpuid
/* EAX = 최대 지원 leaf 번호 (예: 0x20)
 * EBX:EDX:ECX = 벤더 ID 문자열
 *   Intel: "GenuineIntel" (EBX=0x756E6547 EDX=0x49656E69 ECX=0x6C65746E)
 *   AMD:   "AuthenticAMD" (EBX=0x68747541 EDX=0x69746E65 ECX=0x444D4163)
 */

/* Leaf 1: 프로세서 식별 + 기능 플래그 */
mov     $1, %eax
cpuid
/* EAX = Version Information (Stepping, Model, Family)
 *   [3:0]   Stepping ID
 *   [7:4]   Model
 *   [11:8]  Family
 *   [13:12] Processor Type
 *   [19:16] Extended Model
 *   [27:20] Extended Family
 * EBX = [7:0] Brand Index, [15:8] CLFLUSH size, [23:16] Max APIC IDs, [31:24] Local APIC ID
 * ECX = Feature flags (SSE3, PCLMULQDQ, VMX, SMX, SSE4.1, SSE4.2, x2APIC, AES-NI, AVX, ...)
 * EDX = Feature flags (FPU, VME, PSE, TSC, MSR, PAE, APIC, SEP, MTRR, SSE, SSE2, HTT, ...)
 */

주요 CPUID Leaf 요약

EAX (Leaf)ECX (Sub-leaf)설명핵심 반환값
0x00-벤더 ID + 최대 기본 leafEBX:EDX:ECX = 벤더 문자열
0x01-프로세서 식별 + 기능 플래그EAX=버전, ECX/EDX=피처 비트
0x02-캐시/TLB 디스크립터 (Intel)바이트별 디스크립터 코드
0x040,1,2,...캐시 파라미터 (Intel)캐시 타입, 크기, 연관도, 라인 크기
0x06-Thermal / Power ManagementTurbo Boost, HWP 지원 여부
0x070확장 기능 플래그AVX2, BMI1/2, AVX-512, SHA, UMIP 등
0x071확장 기능 플래그 2AVX-VNNI, HRESET, LAM 등
0x0A-Performance MonitoringPMC 버전, 카운터 수/비트 폭
0x0B0,1,2Extended TopologySMT/Core/Package 토폴로지
0x0D0,1,2,...XSAVE 상태 영역XSAVE 크기, 지원 컴포넌트 (SSE, AVX, MPX, AVX-512, PKRU 등)
0x0F0,1QoS Monitoring (Intel RDT)L3 캐시 모니터링 지원
0x100,1,2,3QoS Enforcement (Intel RDT)L3 CAT, L2 CAT, MBA 지원
0x120,1,2,...SGX Capability (Intel)SGX 지원 여부, EPC 섹션 정보
0x140,1Intel Processor TracePT 기능, 필터링 범위
0x15-TSC / Core Crystal ClockTSC:Core 비율 (TSC 주파수 계산)
0x16-Processor Frequency기본/최대/버스 주파수 (MHz)
0x1F0,1,2,...V2 Extended TopologyModule/Tile/Die 레벨 포함

확장 CPUID (0x80000000+)

EAX (Leaf)설명핵심 반환값
0x80000000최대 확장 leaf 번호EAX = 최대 확장 leaf (예: 0x80000008)
0x80000001확장 기능 플래그ECX/EDX: LAHF, SVM(AMD), NX, 1GB Pages, RDTSCP, Long Mode 등
0x80000002~4프로세서 브랜드 문자열48바이트 ASCII: "Intel(R) Core(TM) i9-..." 등
0x80000005L1 캐시/TLB 정보 (AMD)L1 Data/Instruction 캐시 크기/연관도
0x80000006L2/L3 캐시 정보캐시 크기, 연관도, 라인 크기
0x80000007고급 전력 관리EDX bit 8: Invariant TSC 지원
0x80000008주소 크기 + 코어 수물리/가상 주소 비트 수, 코어 수 (AMD)
0x8000000ASVM 기능 (AMD)SVM 리비전, ASID 수, Nested Paging 등
0x8000001EAMD 토폴로지Compute Unit ID, Node ID

인라인 어셈블리로 CPUID 사용

/* 커널의 CPUID 래퍼 — arch/x86/include/asm/processor.h */
static inline void cpuid(unsigned int leaf,
    unsigned int *eax, unsigned int *ebx,
    unsigned int *ecx, unsigned int *edx)
{
    *eax = leaf;
    asm volatile ("cpuid"
        : "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
        : "0"(*eax), "2"(*ecx));
}

/* sub-leaf 지정 버전 */
static inline void cpuid_count(unsigned int leaf,
    unsigned int subleaf,
    unsigned int *eax, unsigned int *ebx,
    unsigned int *ecx, unsigned int *edx)
{
    *eax = leaf;
    *ecx = subleaf;
    asm volatile ("cpuid"
        : "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
        : "0"(*eax), "2"(*ecx));
}

/* 단일 레지스터만 필요한 경우의 편의 함수 */
static inline unsigned int cpuid_eax(unsigned int leaf)
{
    unsigned int eax, ebx, ecx, edx;
    cpuid(leaf, &eax, &ebx, &ecx, &edx);
    return eax;
}
/* cpuid_ebx(), cpuid_ecx(), cpuid_edx()도 동일 패턴 */

Leaf 1 피처 비트 상세 (ECX/EDX)

ECX 피처 플래그 (Leaf 1)

비트이름커널 상수설명
0SSE3X86_FEATURE_SSE3Streaming SIMD Extensions 3
1PCLMULQDQX86_FEATURE_PCLMULQDQCarry-Less Multiplication (CRC, GCM)
5VMXX86_FEATURE_VMXVirtual Machine Extensions (Intel VT-x)
6SMXX86_FEATURE_SMXSafer Mode Extensions (Intel TXT)
9SSSE3X86_FEATURE_SSSE3Supplemental SSE3
12FMAX86_FEATURE_FMAFused Multiply-Add (FMA3)
13CX16X86_FEATURE_CX16CMPXCHG16B 지원
19SSE4.1X86_FEATURE_SSE4_1SSE4.1 명령어
20SSE4.2X86_FEATURE_SSE4_2SSE4.2 (PCMPESTRI, CRC32)
21x2APICX86_FEATURE_X2APICExtended APIC (MSR 기반)
25AES-NIX86_FEATURE_AESAES 명령어 (AESENC/AESDEC)
26XSAVEX86_FEATURE_XSAVEXSAVE/XRSTOR/XSETBV/XGETBV
27OSXSAVEX86_FEATURE_OSXSAVEOS가 XSAVE를 활성화함
28AVXX86_FEATURE_AVXAdvanced Vector Extensions
30RDRANDX86_FEATURE_RDRAND하드웨어 난수 생성
31HypervisorX86_FEATURE_HYPERVISOR하이퍼바이저 존재 (VM에서만 set)

EDX 피처 플래그 (Leaf 1)

비트이름커널 상수설명
0FPUX86_FEATURE_FPUx87 FPU 내장
3PSEX86_FEATURE_PSEPage Size Extension (4MB 페이지)
4TSCX86_FEATURE_TSCTime Stamp Counter (RDTSC)
5MSRX86_FEATURE_MSRRDMSR/WRMSR 지원
6PAEX86_FEATURE_PAEPhysical Address Extension (36+ bit)
9APICX86_FEATURE_APIC온칩 APIC
11SEPX86_FEATURE_SEPSYSENTER/SYSEXIT
12MTRRX86_FEATURE_MTRRMemory Type Range Registers
13PGEX86_FEATURE_PGEPage Global Enable
16PATX86_FEATURE_PATPage Attribute Table
19CLFLUSHX86_FEATURE_CLFLUSH캐시 라인 플러시
20NX(EDX bit 20 in leaf 0x80000001)No-Execute 비트 (확장 leaf)
25SSEX86_FEATURE_SSEStreaming SIMD Extensions
26SSE2X86_FEATURE_SSE2SSE2 (x86-64 필수)
28HTTX86_FEATURE_HTTHyper-Threading Technology

Leaf 7 확장 기능 (EBX/ECX/EDX)

/* Leaf 7, Sub-leaf 0: 확장 기능 플래그 */
/* EBX 피처 비트 */
#define X86_FEATURE_FSGSBASE     (7*32+ 0)  /* FSGSBASE 명령어 (커널 gs 최적화) */
#define X86_FEATURE_BMI1         (7*32+ 3)  /* Bit Manipulation Instructions 1 */
#define X86_FEATURE_AVX2         (7*32+ 5)  /* AVX2 (256-bit 정수 SIMD) */
#define X86_FEATURE_SMEP         (7*32+ 7)  /* Supervisor Mode Exec Prevention */
#define X86_FEATURE_BMI2         (7*32+ 8)  /* Bit Manipulation Instructions 2 */
#define X86_FEATURE_INVPCID      (7*32+10)  /* Invalidate Process-Context ID */
#define X86_FEATURE_AVX512F      (7*32+16)  /* AVX-512 Foundation */
#define X86_FEATURE_AVX512DQ     (7*32+17)  /* AVX-512 Doubleword/Quadword */
#define X86_FEATURE_RDSEED       (7*32+18)  /* RDSEED 난수 시드 */
#define X86_FEATURE_SMAP         (7*32+20)  /* Supervisor Mode Access Prevention */
#define X86_FEATURE_CLFLUSHOPT   (7*32+23)  /* 최적화된 캐시 라인 플러시 */
#define X86_FEATURE_CLWB         (7*32+24)  /* Cache Line Write Back */
#define X86_FEATURE_SHA_NI       (7*32+29)  /* SHA 명령어 */
#define X86_FEATURE_AVX512BW     (7*32+30)  /* AVX-512 Byte/Word */
#define X86_FEATURE_AVX512VL     (7*32+31)  /* AVX-512 Vector Length */

/* ECX 피처 비트 (Leaf 7, Sub-leaf 0) */
#define X86_FEATURE_AVX512VBMI   (16*32+ 1) /* Vector Byte Manipulation */
#define X86_FEATURE_UMIP         (16*32+ 2) /* User-Mode Instruction Prevention */
#define X86_FEATURE_PKU          (16*32+ 3) /* Protection Keys for Userspace */
#define X86_FEATURE_WAITPKG      (16*32+ 5) /* UMONITOR/UMWAIT/TPAUSE */
#define X86_FEATURE_GFNI         (16*32+ 8) /* Galois Field New Instructions */
#define X86_FEATURE_VAES         (16*32+ 9) /* 256/512-bit AES */

/* EDX 피처 비트 (Leaf 7, Sub-leaf 0) */
#define X86_FEATURE_AVX512_4VNNIW   /* Vector Neural Network (Xeon Phi) */
#define X86_FEATURE_SPEC_CTRL       /* IBRS/IBPB: Spectre v2 완화 */
#define X86_FEATURE_STIBP           /* Single Thread Indirect Branch Predictor */
#define X86_FEATURE_FLUSH_L1D       /* L1D 캐시 플러시 (L1TF 완화) */
#define X86_FEATURE_ARCH_CAPABILITIES /* IA32_ARCH_CAPABILITIES MSR */
#define X86_FEATURE_SSBD            /* Speculative Store Bypass Disable */

커널 부팅 시 CPUID 처리 흐름

/* 커널의 CPU 식별 흐름 — arch/x86/kernel/cpu/common.c */

/* 1단계: 부팅 초기 CPU 탐지 (early_cpu_init) */
void early_cpu_init(void)
{
    /* CPU 벤더 테이블에서 일치하는 벤더 찾기 */
    const struct cpu_dev *cdev;
    for (cdev = __x86_cpu_dev_start; cdev < __x86_cpu_dev_end; cdev++) {
        if (cdev->c_detect) cdev->c_detect(&boot_cpu_data);
    }
    early_identify_cpu(&boot_cpu_data);
}

/* 2단계: CPU 식별 상세 */
static void early_identify_cpu(struct cpuinfo_x86 *c)
{
    /* Leaf 0: 벤더 확인 */
    cpuid(0x00, &c->cpuid_level, ...);

    /* Leaf 1: Family/Model/Stepping + 기본 피처 */
    cpuid(0x01, &tfms, &misc, &cap[4], &cap[0]);
    c->x86          = (tfms >> 8) & 0xf;     /* Family */
    c->x86_model    = (tfms >> 4) & 0xf;     /* Model */
    c->x86_stepping = tfms & 0xf;            /* Stepping */

    /* Extended Family/Model (Family ≥ 6 또는 15인 경우) */
    if (c->x86 == 0xf)
        c->x86 += (tfms >> 20) & 0xff;       /* + Extended Family */
    if (c->x86 >= 0x6)
        c->x86_model += ((tfms >> 16) & 0xf) << 4; /* + Extended Model */

    /* Leaf 7: 확장 기능 (AVX2, SMEP, SMAP, ...) */
    cpuid_count(0x07, 0, &eax, &cap[9], &cap[16], &cap[18]);

    /* 확장 leaf: Long Mode, NX, GB Pages, ... */
    cpuid(0x80000001, &eax, &ebx, &cap[6], &cap[1]);

    /* 물리/가상 주소 비트 수 */
    cpuid(0x80000008, &eax, ...);
    c->x86_phys_bits = eax & 0xff;           /* 예: 46 (Intel), 48 (AMD) */
    c->x86_virt_bits = (eax >> 8) & 0xff;    /* 예: 48 또는 57 (LA57) */
}

/* 3단계: 벤더별 초기화 (Intel/AMD 고유 처리) */
/* intel_init(), amd_init() 에서 각 벤더 고유 CPUID leaf 추가 해석 */

x86_capability 배열과 피처 테스트

/* arch/x86/include/asm/cpufeature.h */

/* CPU 피처 비트를 저장하는 구조체 (커널 전역) */
struct cpuinfo_x86 {
    __u8    x86;              /* CPU family */
    __u8    x86_vendor;       /* X86_VENDOR_INTEL, _AMD, ... */
    __u8    x86_model;
    __u8    x86_stepping;
    __u32   x86_capability[NCAPINTS]; /* 피처 비트 배열 */
    char    x86_model_id[64]; /* 브랜드 문자열 */
    __u8    x86_phys_bits;    /* 물리 주소 비트 */
    __u8    x86_virt_bits;    /* 가상 주소 비트 */
    /* ... */
};

/* boot_cpu_data: 부팅 CPU의 정보 (전역 변수) */
extern struct cpuinfo_x86 boot_cpu_data;

/* 피처 존재 여부 확인 */
if (boot_cpu_has(X86_FEATURE_AVX2)) {
    /* AVX2 사용 가능한 코드 경로 */
}

/* static_cpu_has(): 부팅 후 패치되는 최적화 버전 */
/* Alternative 메커니즘으로 NOP ↔ JMP으로 패치됨 */
if (static_cpu_has(X86_FEATURE_XSAVE)) {
    /* 조건 분기 오버헤드 없이 코드 경로 결정 */
}

static_cpu_has()Alternative 명령어 패치 메커니즘을 사용합니다. 부팅 시 CPUID 결과에 따라 실제 기계어 코드를 NOP 또는 JMP으로 바이너리 패치하여, 런타임에 조건 분기 오버헤드를 완전히 제거합니다. .altinstructions 섹션에 패치 정보가 저장됩니다.

Family / Model / Stepping 해석

/* CPUID Leaf 1 EAX에서 Family/Model/Stepping 추출 */
/*
 * EAX 비트 레이아웃:
 *   [3:0]   Stepping ID
 *   [7:4]   Base Model
 *   [11:8]  Base Family
 *   [13:12] Processor Type (00=OEM, 01=OverDrive, 10=Dual, 11=Reserved)
 *   [19:16] Extended Model
 *   [27:20] Extended Family
 *
 * Display Family = Base Family + Extended Family  (if Base Family == 0xF)
 *                = Base Family                     (otherwise)
 * Display Model  = (Extended Model << 4) + Base Model  (if Base Family >= 0x6 또는 0xF)
 *                = Base Model                           (otherwise)
 */

/* 예시: Intel Core i9-14900K (Raptor Lake-S Refresh) */
/* CPUID 1 EAX = 0x000B0671
 *   Base Family = 0x6, Extended Family = 0x0  → Display Family = 6
 *   Base Model  = 0x7, Extended Model  = 0xB  → Display Model  = 0xB7
 *   Stepping    = 0x1
 *   → "Family 6, Model 183, Stepping 1"
 */

/* 예시: AMD Ryzen 9 7950X (Zen 4, Raphael) */
/* CPUID 1 EAX = 0x00A60F12
 *   Base Family = 0xF, Extended Family = 0x19 → Display Family = 25 (0x19)
 *   Base Model  = 0x1, Extended Model  = 0x6  → Display Model  = 0x61
 *   Stepping    = 0x2
 *   → "Family 25, Model 97, Stepping 2"
 */

/* 커널에서 CPU 모델별 분기 예시 */
#define INTEL_FAM6_RAPTORLAKE_S   0xB7
#define INTEL_FAM6_ALDERLAKE      0x97
#define INTEL_FAM6_SAPPHIRERAPIDS 0x8F

switch (c->x86_model) {
case INTEL_FAM6_RAPTORLAKE_S:
    /* Raptor Lake 고유 workaround/최적화 */
    break;
case INTEL_FAM6_SAPPHIRERAPIDS:
    /* Sapphire Rapids 서버 최적화 */
    break;
}
CPU 토폴로지 종합: CPUID 기반 탐지뿐 아니라 AMD CCX/CCD/IOD, Intel Tile/Hybrid, ARM DynamIQ 아키텍처와 커널 sched_domain 계층까지 종합적으로 다루는 CPU 토폴로지 페이지를 참조하세요.

CPUID 기반 토폴로지 탐지

/* Leaf 0x0B (Extended Topology Enumeration)
 * 또는 Leaf 0x1F (V2 Extended Topology — 최신 CPU)
 *
 * Sub-leaf ECX 입력에 따라 토폴로지 레벨 반환:
 *   sub-leaf 0: SMT 레벨
 *   sub-leaf 1: Core 레벨
 *   sub-leaf 2: Module/Die 레벨 (Leaf 0x1F만)
 *
 * 반환값:
 *   EAX[4:0]  = 다음 레벨 APIC ID 시프트 비트 수
 *   EBX[15:0] = 이 레벨의 논리 프로세서 수
 *   ECX[15:8] = 레벨 타입 (1=SMT, 2=Core, 3=Module, 4=Tile, 5=Die)
 *   EDX       = x2APIC ID
 */

/* arch/x86/kernel/cpu/topology.c — 커널의 토폴로지 파싱 */
void detect_extended_topology(struct cpuinfo_x86 *c)
{
    unsigned int eax, ebx, ecx, edx;
    int leaf = 0x1f;  /* V2 먼저 시도 */

    cpuid_count(leaf, 0, &eax, &ebx, &ecx, &edx);
    if (ebx == 0) leaf = 0x0b;  /* V2 미지원이면 V1 사용 */

    /* sub-leaf 0: SMT 레벨 */
    cpuid_count(leaf, 0, &eax, &ebx, &ecx, &edx);
    c->x86_max_cores = ebx & 0xffff;   /* 코어당 SMT 스레드 수 */
    c->topo_smt_shift = eax & 0x1f;

    /* sub-leaf 1: Core 레벨 */
    cpuid_count(leaf, 1, &eax, &ebx, &ecx, &edx);
    c->topo_core_shift = eax & 0x1f;

    /* APIC ID 기반 패키지/코어/스레드 매핑 */
    c->topo.apicid = edx;
    c->topo.pkg_id = edx >> c->topo_core_shift;
    c->topo.core_id = (edx >> c->topo_smt_shift) &
                      ((1 << (c->topo_core_shift - c->topo_smt_shift)) - 1);
}

CPUID 캐시 정보 (Leaf 4)

/* Leaf 0x04: Deterministic Cache Parameters (Intel)
 * Sub-leaf를 0부터 증가시키며 각 캐시 레벨 정보를 열거
 *
 * 반환값:
 *   EAX[4:0]  = Cache Type (1=Data, 2=Instruction, 3=Unified, 0=No more)
 *   EAX[7:5]  = Cache Level (1=L1, 2=L2, 3=L3)
 *   EAX[25:14]= 이 캐시를 공유하는 최대 APIC ID 수
 *   EBX[11:0] = Line Size - 1
 *   EBX[21:12]= Partitions - 1
 *   EBX[31:22]= Associativity (Ways) - 1
 *   ECX        = Number of Sets - 1
 *
 * 캐시 크기 = (Ways+1) × (Partitions+1) × (LineSize+1) × (Sets+1)
 */

/* 예: Intel i9-14900K의 L3 캐시 (sub-leaf 3) */
/* EAX=0x03C004163  EBX=0x03C0003F  ECX=0x00003FFF  EDX=0x00000006
 *   Type: Unified (3)   Level: L3 (3)
 *   Line Size:  64 bytes  (0x3F + 1)
 *   Partitions: 1         (0 + 1)
 *   Ways:       16        (0xF + 1)
 *   Sets:       16384     (0x3FFF + 1)
 *   → Size = 16 × 1 × 64 × 16384 = 16 MB (per tile)
 */

/* arch/x86/kernel/cpu/cacheinfo.c */
static void ci_leaf_init(struct cacheinfo *ci,
    unsigned int eax, unsigned int ebx, unsigned int ecx)
{
    ci->level          = (eax >> 5) & 0x7;
    ci->type           = eax & 0x1f;
    ci->coherency_line_size = (ebx & 0xfff) + 1;
    ci->ways_of_associativity = ((ebx >> 22) & 0x3ff) + 1;
    ci->number_of_sets = ecx + 1;
    ci->size = ci->ways_of_associativity *
               ci->coherency_line_size *
               ci->number_of_sets;    /* 바이트 단위 캐시 크기 */
}

가상화와 CPUID

/* 하이퍼바이저 탐지: Leaf 1 ECX bit 31 (Hypervisor Present) */
static bool detect_hypervisor(void)
{
    unsigned int eax, ebx, ecx, edx;
    cpuid(1, &eax, &ebx, &ecx, &edx);
    return !!(ecx & (1 << 31));  /* Hypervisor bit */
}

/* 하이퍼바이저 전용 CPUID leaf (0x40000000+) */
/* Leaf 0x40000000: 하이퍼바이저 벤더 문자열 */
cpuid(0x40000000, &eax, &ebx, &ecx, &edx);
/* KVM:    EBX:ECX:EDX = "KVMKVMKVM\0\0\0" */
/* Hyper-V: EBX:ECX:EDX = "Microsoft Hv" */
/* VMware: EBX:ECX:EDX = "VMwareVMware" */
/* Xen:    EBX:ECX:EDX = "XenVMMXenVMM" */

/* KVM에서 CPUID 에뮬레이션 — arch/x86/kvm/cpuid.c */
int kvm_emulate_cpuid(struct kvm_vcpu *vcpu)
{
    u32 eax = kvm_rax_read(vcpu);
    u32 ecx = kvm_rcx_read(vcpu);
    struct kvm_cpuid_entry2 *entry;

    /* 게스트에 노출할 CPUID 값 조회 (호스트와 다를 수 있음) */
    entry = kvm_find_cpuid_entry(vcpu, eax, ecx);
    if (entry) {
        kvm_rax_write(vcpu, entry->eax);
        kvm_rbx_write(vcpu, entry->ebx);
        kvm_rcx_write(vcpu, entry->ecx);
        kvm_rdx_write(vcpu, entry->edx);
    }
    return kvm_skip_emulated_instruction(vcpu);
}

/* QEMU/KVM에서 게스트 CPUID 커스터마이징:
 *   -cpu host          → 호스트 CPUID 그대로 전달
 *   -cpu host,-avx512f → AVX-512 기능 숨김
 *   -cpu EPYC          → AMD EPYC 프로필
 */

CPUID faulting: 최신 Intel CPU는 MSR_MISC_FEATURES_ENABLES (0x140) bit 0으로 CPUID faulting을 지원합니다. 활성화하면 Ring 3(유저 모드)에서 CPUID 실행 시 #GP 예외가 발생하여, 커널이 가상화된 CPUID 값을 반환할 수 있습니다. 하이퍼바이저 없이도 CPUID 가상화가 가능해집니다.

CPUID와 보안 (Spectre/Meltdown 완화)

/* CPU 취약점 탐지에 CPUID가 핵심적으로 사용됨 */
/* arch/x86/kernel/cpu/bugs.c */

/* 1. CPUID Leaf 7 EDX: 하드웨어 완화 기능 확인 */
/* bit 26: IBRS/IBPB  — Indirect Branch Restricted Speculation */
/* bit 27: STIBP      — Single Thread Indirect Branch Predictor */
/* bit 29: IA32_ARCH_CAPABILITIES MSR 존재 */
/* bit 31: SSBD       — Speculative Store Bypass Disable */

/* 2. IA32_ARCH_CAPABILITIES MSR (CPUID로 존재 확인 후 읽기) */
if (boot_cpu_has(X86_FEATURE_ARCH_CAPABILITIES)) {
    u64 ia32_cap = x86_read_arch_cap_msr();
    /* bit 0: RDCL_NO     → Meltdown에 취약하지 않음 */
    /* bit 1: IBRS_ALL    → IBRS가 모든 코드에서 작동 */
    /* bit 2: RSBA        → 대안 예측기 사용 (RetBleed 관련) */
    /* bit 3: SKIP_L1DFL  → L1D flush 불필요 */
    /* bit 4: SSB_NO      → SSB 취약점 없음 */
    /* bit 5: MDS_NO      → MDS 취약점 없음 */
    /* bit 6: IF_PSCHANGE_MC_NO → PSCHANGE MC 영향 없음 */
    /* bit 7: TSX_CTRL    → TSX 비활성화 가능 */
}

/* 3. /proc/cpuinfo의 "bugs" 필드는 이 탐지 결과에 기반 */
/* 예: bugs: cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass ... */

유저 공간에서 CPUID 활용

# /proc/cpuinfo — 커널이 파싱한 CPUID 결과
cat /proc/cpuinfo | head -30
# processor    : 0
# vendor_id    : GenuineIntel
# cpu family   : 6
# model        : 183
# model name   : 13th Gen Intel(R) Core(TM) i9-13900K
# stepping     : 1
# flags        : fpu vme de pse tsc msr pae mce cx8 apic ... avx2 ...
# bugs         : spectre_v1 spectre_v2 spec_store_bypass ...

# cpuid 명령어 도구 (cpuid 패키지)
cpuid -1 -l 0    # Leaf 0 (벤더 문자열)
cpuid -1 -l 1    # Leaf 1 (피처 플래그)
cpuid -1 -l 7    # Leaf 7 (확장 기능)
cpuid -1 -l 4    # Leaf 4 (캐시 정보)

# /dev/cpu/N/cpuid 디바이스 파일 (CONFIG_X86_CPUID)
# 특정 CPU에 직접 CPUID 실행
# lseek offset = leaf | (subleaf << 32)
dd if=/dev/cpu/0/cpuid bs=16 count=1 skip=0 2>/dev/null | xxd

# C에서 직접 CPUID 호출 (유저 공간)
/* 유저 공간 CPUID 직접 호출 */
#include <cpuid.h>   /* GCC 내장 헤더 */

void check_features(void)
{
    unsigned int eax, ebx, ecx, edx;

    /* GCC __get_cpuid() 내장 함수 */
    __get_cpuid(1, &eax, &ebx, &ecx, &edx);

    if (ecx & (1 << 25)) printf("AES-NI supported\n");
    if (ecx & (1 << 28)) printf("AVX supported\n");

    /* Leaf 7 확인 */
    __get_cpuid_count(7, 0, &eax, &ebx, &ecx, &edx);
    if (ebx & (1 <<  5)) printf("AVX2 supported\n");
    if (ebx & (1 << 16)) printf("AVX-512F supported\n");

    /* 브랜드 문자열 (Leaf 0x80000002~4) */
    char brand[49] = {};
    __get_cpuid(0x80000002, (unsigned int*)&brand[0],
        (unsigned int*)&brand[4], (unsigned int*)&brand[8],
        (unsigned int*)&brand[12]);
    __get_cpuid(0x80000003, (unsigned int*)&brand[16],
        (unsigned int*)&brand[20], (unsigned int*)&brand[24],
        (unsigned int*)&brand[28]);
    __get_cpuid(0x80000004, (unsigned int*)&brand[32],
        (unsigned int*)&brand[36], (unsigned int*)&brand[40],
        (unsigned int*)&brand[44]);
    printf("CPU: %s\n", brand);
}

Intel vs AMD CPUID 차이점

항목IntelAMD
벤더 문자열GenuineIntelAuthenticAMD
캐시 정보 (상세)Leaf 0x04 (Deterministic)Leaf 0x8000001D (유사 형식)
L1 캐시 (간략)Leaf 0x02 (디스크립터)Leaf 0x80000005 (직접 크기)
토폴로지Leaf 0x0B / 0x1FLeaf 0x8000001E (CU/Node)
가상화 기능VMX (Leaf 1 ECX bit 5)SVM (Leaf 0x8000000A)
전력 관리Leaf 0x06 (HWP)Leaf 0x80000007 (CPB, APM)
주소 크기Leaf 0x80000008Leaf 0x80000008 (동일)
Family 인코딩Family 6 (대부분)Family 0xF+Ext (예: 0x17=Zen, 0x19=Zen3/4, 0x1A=Zen5)
SGXLeaf 0x12 (지원)해당 없음 (SEV 사용)
Encrypted VMTME/MKTME (MSR 기반)SEV/SEV-ES/SEV-SNP (Leaf 0x8000001F)
💡

커널의 벤더 추상화: 커널은 struct cpu_dev 구조체를 통해 Intel/AMD/Hygon/Centaur/Zhaoxin 등 벤더별 CPUID 해석 차이를 추상화합니다. arch/x86/kernel/cpu/intel.carch/x86/kernel/cpu/amd.c에 각 벤더의 초기화 로직이 분리되어 있으며, 공통 피처 플래그는 x86_capability 배열에 통합 저장됩니다.