CPUID 명령어
x86 CPUID 기반 런타임 기능 탐지를 부트 초기화와 동적 최적화 경로 관점에서 심층 분석합니다. Leaf/Subleaf 구조와 vendor 확장 해석, Intel·AMD 기능 비트 차이, microcode 업데이트로 달라지는 기능 가시성, Linux 커널의 boot_cpu_data·x86_capability·alternative patching 연계, 취약점(Vulnerability) 완화 기능(IBRS/SSBD 등) 감지, 가상화(Virtualization) 환경에서의 CPUID masking 영향, 사용자 공간(User Space) 노출과 커널 내부 정책 차이까지 안전한 기능 게이팅 설계에 필요한 핵심을 다룹니다.
핵심 요약
- Leaf 구조 — EAX에 leaf 번호, ECX에 sub-leaf를 넣고 CPUID 실행하면 EAX/EBX/ECX/EDX에 정보 반환.
- 주요 Leaf — Leaf 0x01(기본 피처), 0x07(확장 피처), 0x0D(XSAVE), 0x80000001(확장 피처 AMD).
- 커널 추상화 — x86_capability[] 배열에 피처 비트 통합, cpu_has() 매크로(Macro)로 런타임 확인.
- Intel vs AMD — 동일 기능도 다른 Leaf/비트 위치 사용, 커널이 벤더별로 추상화.
- 부팅 초기화 — early_cpu_detect()에서 boot_cpu_data 구조체(Struct) 초기화.
단계별 이해
- 기본 CPUID 동작
EAX 레지스터에 leaf 번호를 넣고 CPUID를 실행하면 4개 레지스터로 CPU 정보가 반환됩니다. - 주요 Leaf 파악
Leaf 0x01과 0x07이 가장 중요합니다. 각 비트 의미를 Intel/AMD SDM에서 확인하세요. - 커널 추상화 이해
boot_cpu_data, x86_capability[], cpu_has() 매크로를 통해 커널이 CPUID를 어떻게 추상화하는지 파악합니다. - 벤더 차이 인식
Intel과 AMD가 동일 기능에 다른 Leaf를 쓰는 경우를 파악하고 커널의 통합 방식을 이해합니다.
CPUID는 x86 프로세서의 기능, 모델 정보, 캐시(Cache) 토폴로지(Topology), 확장 기능 등을 쿼리하는 핵심 명령어입니다. 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 + 최대 기본 leaf | EBX:EDX:ECX = 벤더 문자열 |
0x01 | - | 프로세서 식별 + 기능 플래그 | EAX=버전, ECX/EDX=피처 비트 |
0x02 | - | 캐시/TLB 디스크립터 (Intel) | 바이트별 디스크립터 코드 |
0x04 | 0,1,2,... | 캐시 파라미터 (Intel) | 캐시 타입, 크기, 연관도, 라인 크기 |
0x06 | - | Thermal / Power Management | Turbo Boost, HWP 지원 여부 |
0x07 | 0 | 확장 기능 플래그 | AVX2, BMI1/2, AVX-512, SHA, UMIP 등 |
0x07 | 1 | 확장 기능 플래그 2 | AVX-VNNI, HRESET, LAM 등 |
0x0A | - | Performance Monitoring | PMC 버전, 카운터 수/비트 폭 → PMU 탐지 상세 |
0x0B | 0,1,2 | Extended Topology | SMT/Core/Package 토폴로지 |
0x0D | 0,1,2,... | XSAVE 상태 영역 | XSAVE 크기, 지원 컴포넌트 (SSE, AVX, MPX, AVX-512, PKRU 등) → XSAVE 상세 |
0x0F | 0,1 | QoS Monitoring (Intel RDT) | L3 캐시 모니터링 지원 |
0x10 | 0,1,2,3 | QoS Enforcement (Intel RDT) | L3 CAT, L2 CAT, MBA 지원 |
0x12 | 0,1,2,... | SGX Capability (Intel) | SGX 지원 여부, EPC 섹션 정보 |
0x14 | 0,1 | Intel Processor Trace | PT 기능, 필터링 범위 |
0x15 | - | TSC / Core Crystal Clock | TSC:Core 비율 (TSC 주파수 계산) → 주파수 탐지 상세 |
0x16 | - | Processor Frequency | 기본/최대/버스(Bus) 주파수 (MHz) → 주파수 탐지 상세 |
0x1F | 0,1,2,... | V2 Extended Topology | Module/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-..." 등 |
0x80000005 | L1 캐시/TLB 정보 (AMD) | L1 Data/Instruction 캐시 크기/연관도 |
0x80000006 | L2/L3 캐시 정보 | 캐시 크기, 연관도, 라인 크기 |
0x80000007 | 고급 전력 관리 | EDX bit 8: Invariant TSC 지원 |
0x80000008 | 주소 크기 + 코어 수 | 물리/가상 주소(Virtual Address) 비트 수, 코어 수 (AMD) |
0x8000000A | SVM 기능 (AMD) | SVM 리비전, ASID 수, Nested Paging 등 |
0x8000001E | AMD 토폴로지 | 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;
*ecx = 0; /* leaf-only 쿼리는 sub-leaf=0 */
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)
| 비트 | 이름 | 커널 상수 | 설명 |
|---|---|---|---|
| 0 | SSE3 | X86_FEATURE_SSE3 | Streaming SIMD Extensions 3 |
| 1 | PCLMULQDQ | X86_FEATURE_PCLMULQDQ | Carry-Less Multiplication (CRC, GCM) |
| 5 | VMX | X86_FEATURE_VMX | Virtual Machine Extensions (Intel VT-x) |
| 6 | SMX | X86_FEATURE_SMX | Safer Mode Extensions (Intel TXT) |
| 9 | SSSE3 | X86_FEATURE_SSSE3 | Supplemental SSE3 |
| 12 | FMA | X86_FEATURE_FMA | Fused Multiply-Add (FMA3) |
| 13 | CX16 | X86_FEATURE_CX16 | CMPXCHG16B 지원 |
| 19 | SSE4.1 | X86_FEATURE_SSE4_1 | SSE4.1 명령어 |
| 20 | SSE4.2 | X86_FEATURE_SSE4_2 | SSE4.2 (PCMPESTRI, CRC32) |
| 21 | x2APIC | X86_FEATURE_X2APIC | Extended APIC (MSR 기반) |
| 25 | AES-NI | X86_FEATURE_AES | AES 명령어 (AESENC/AESDEC) |
| 26 | XSAVE | X86_FEATURE_XSAVE | XSAVE/XRSTOR/XSETBV/XGETBV |
| 27 | OSXSAVE | X86_FEATURE_OSXSAVE | OS가 XSAVE를 활성화함 |
| 28 | AVX | X86_FEATURE_AVX | Advanced Vector Extensions |
| 30 | RDRAND | X86_FEATURE_RDRAND | 하드웨어 난수 생성 |
| 31 | Hypervisor | X86_FEATURE_HYPERVISOR | 하이퍼바이저(Hypervisor) 존재 (VM에서만 set) |
EDX 피처 플래그 (Leaf 1)
| 비트 | 이름 | 커널 상수 | 설명 |
|---|---|---|---|
| 0 | FPU | X86_FEATURE_FPU | x87 FPU 내장 |
| 3 | PSE | X86_FEATURE_PSE | Page Size Extension (4MB 페이지(Page)) |
| 4 | TSC | X86_FEATURE_TSC | Time Stamp Counter (RDTSC) |
| 5 | MSR | X86_FEATURE_MSR | RDMSR/WRMSR 지원 |
| 6 | PAE | X86_FEATURE_PAE | Physical Address Extension (36+ bit) |
| 9 | APIC | X86_FEATURE_APIC | 온칩 APIC |
| 11 | SEP | X86_FEATURE_SEP | SYSENTER/SYSEXIT |
| 12 | MTRR | X86_FEATURE_MTRR | Memory Type Range Registers |
| 13 | PGE | X86_FEATURE_PGE | Page Global Enable |
| 16 | PAT | X86_FEATURE_PAT | Page Attribute Table |
| 19 | CLFLUSH | X86_FEATURE_CLFLUSH | 캐시 라인(Cache Line) 플러시(Flush) |
| 25 | SSE | X86_FEATURE_SSE | Streaming SIMD Extensions |
| 26 | SSE2 | X86_FEATURE_SSE2 | SSE2 (x86-64 필수) |
| 28 | HTT | X86_FEATURE_HTT | Hyper-Threading Technology |
참고: NX는 Leaf 1 EDX가 아니라 확장 Leaf 0x80000001의 EDX bit 20에서 확인합니다.
Leaf 7 Sub-leaf 1 EAX 피처 플래그 (ECX=1)
Leaf 7 Sub-leaf 1(EAX=0x07, ECX=1)은 Ice Lake 이후 추가된 신규 확장 기능을 보고합니다. 커널은 arch/x86/kernel/cpu/common.c에서 cpuid_count(7, 1, ...)으로 이 값을 읽어 x86_capability 배열에 저장합니다.
| 비트 | 이름 | 커널 상수 | 설명 |
|---|---|---|---|
| 4 | AVX-VNNI | X86_FEATURE_AVX_VNNI | VEX-인코딩 벡터 뉴럴 네트워크 INT8 곱셈누산 (Alder Lake+) |
| 5 | AVX512_BF16 | X86_FEATURE_AVX512_BF16 | BFloat16 변환 명령어 (AI/ML 추론 최적화) |
| 7 | CMPCCXADD | X86_FEATURE_CMPCCXADD | 비교 조건부 원자 추가 (락 없는 연산, Sapphire Rapids+) |
| 10 | FZRM | X86_FEATURE_FZRM | Fast Zero-length REP MOVSB |
| 11 | FSRS | X86_FEATURE_FSRS | Fast Short REP STOSB (짧은 memset 최적화) |
| 12 | FSRCS | X86_FEATURE_FSRC | Fast Short REP CMPSB/SCASB |
| 17 | FRED | X86_FEATURE_FRED | Flexible Return and Event Delivery (인터럽트(Interrupt) 지연(Latency) 단축) |
| 18 | LKGS | X86_FEATURE_LKGS | Load Kernel GS Base (SWAPGS 대체, 보안 강화) |
| 19 | WRMSRNS | X86_FEATURE_WRMSRNS | Non-serializing WRMSR (성능 개선) |
| 21 | AMX-FP16 | X86_FEATURE_AMX_FP16 | AMX FP16 행렬 곱셈 (4세대 Xeon Scalable+) |
| 22 | HRESET | X86_FEATURE_HRESET | History Reset — 분기 예측(Branch Prediction)기 히스토리 선택적 초기화 |
| 26 | LAM | X86_FEATURE_LAM | Linear Address Masking — 포인터 상위 비트에 태그 저장 (사용자 공간) |
Leaf 7 확장 기능 (EBX/ECX/EDX)
커널 부팅 시 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 명령어 패치(Patch) 메커니즘을 사용합니다. 부팅 시 CPUID 결과에 따라 실제 기계어(Machine Code) 코드를 NOP 또는 JMP으로 바이너리 패치하여, 런타임에 조건 분기 오버헤드(Overhead)를 완전히 제거합니다. .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;
}
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=0x3C004163 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 차이점
| 항목 | Intel | AMD |
|---|---|---|
| 벤더 문자열 | GenuineIntel | AuthenticAMD |
| 캐시 정보 (상세) | Leaf 0x04 (Deterministic) | Leaf 0x8000001D (유사 형식) |
| L1 캐시 (간략) | Leaf 0x02 (디스크립터) | Leaf 0x80000005 (직접 크기) |
| 토폴로지 | Leaf 0x0B / 0x1F | Leaf 0x8000001E (CU/Node) |
| 가상화 기능 | VMX (Leaf 1 ECX bit 5) | SVM (Leaf 0x8000000A) |
| 전력 관리 | Leaf 0x06 (HWP) | Leaf 0x80000007 (CPB, APM) |
| 주소 크기 | Leaf 0x80000008 | Leaf 0x80000008 (동일) |
| Family 인코딩 | Family 6 (대부분) | Family 0xF+Ext (예: 0x17=Zen, 0x19=Zen3/4, 0x1A=Zen5) |
| SGX | Leaf 0x12 (지원) | 해당 없음 (SEV 사용) |
| Encrypted VM | TME/MKTME (MSR 기반) | SEV/SEV-ES/SEV-SNP (Leaf 0x8000001F) |
커널의 벤더 추상화: 커널은 struct cpu_dev 구조체를 통해 Intel/AMD/Hygon/Centaur/Zhaoxin 등 벤더별 CPUID 해석 차이를 추상화합니다. arch/x86/kernel/cpu/intel.c와 arch/x86/kernel/cpu/amd.c에 각 벤더의 초기화 로직이 분리되어 있으며, 공통 피처 플래그는 x86_capability 배열에 통합 저장됩니다.
CPUID 직렬화(Serialization) 효과와 성능 영향
CPUID는 Intel SDM이 정의한 완전한 직렬화 명령어(Serializing Instruction)입니다. 직렬화 명령어는 실행 전 이전 모든 명령어가 완전히 완료될 때까지 파이프라인(Pipeline)을 멈추며, 이후 명령어의 프리페치·비순차 실행도 막습니다. 이 특성 때문에 CPUID는 성능 측정 경계 설정과 정밀 타이밍에 전통적으로 활용됩니다.
| 명령어 | 직렬화 수준 | 주요 용도 | 비고 |
|---|---|---|---|
CPUID | 완전 직렬화 (Fully Serializing) | 정밀 타이밍 경계, 부팅 시 CPU 탐지 | 권한 무관 실행 가능, 런타임 반복 비권장 |
MFENCE | 메모리 직렬화 (Store+Load) | 메모리 순서 보장(Ordering), lock-free 알고리즘 | 파이프라인 비움 없음 |
LFENCE | 로드 직렬화 (Load) | Spectre 완화, 로드 순서 강제 | AMD: 스펙실행 방지 효과 있음 |
IRET | 완전 직렬화 | 인터럽트 핸들러(Handler) 복귀 | 컨텍스트 전환 포함 |
WRMSR | 완전 직렬화 (일부 MSR) | MSR 설정 후 즉시 효과 반영 | MSR 종류에 따라 다름 |
/* RDTSC 정밀 측정 — CPUID로 파이프라인 직렬화 후 타이밍 시작
* 출처: Intel SDM Vol. 2B "RDTSC" 사용 지침
*/
/* 측정 시작: cpuid로 이전 명령 완전 완료 보장 */
xor %eax, %eax
cpuid /* 파이프라인 flush — 이전 명령 모두 완료 */
rdtsc /* EAX=TSC[31:0], EDX=TSC[63:32] */
shl $32, %rdx
or %rax, %rdx
mov %rdx, %r8 /* 시작 TSC 저장 */
/* ... 측정할 코드 실행 ... */
/* 측정 종료: rdtscp + lfence (아웃-오브-오더 방지) */
rdtscp /* ECX=IA32_TSC_AUX (core ID), EAX/EDX=TSC */
shl $32, %rdx
or %rax, %rdx
sub %r8, %rdx /* 경과 사이클 수 */
lfence /* 이후 명령이 TSC 읽기 전 실행되지 않도록 */
/* 커널이 CPUID를 부팅 시에만 호출하는 이유:
* 직렬화로 인한 성능 페널티 — 현대 CPU에서 수백 클럭 사이클 소요
* 런타임 성능 경로에서는 static_cpu_has() 또는 alternative() 사용 */
/* 잘못된 패턴: 런타임에 CPUID 반복 호출 (성능 저하) */
bool has_avx_bad(void) {
unsigned int eax, ebx, ecx, edx;
cpuid_count(7, 0, &eax, &ebx, &ecx, &edx);
return ebx & (1 << 5); /* 매번 CPUID 실행 → 수백 사이클 낭비 */
}
/* 올바른 패턴: 부팅 시 저장된 피처 비트 참조 (인라인 비트 검사) */
bool has_avx_good(void) {
return cpu_feature_enabled(X86_FEATURE_AVX2);
}
RDTSCP vs RDTSC: 현대 커널 측정에서는 RDTSCP를 선호합니다. RDTSCP는 이전 로드 명령을 직렬화(load-serializing)하고 IA32_TSC_AUX에서 코어 ID도 함께 반환합니다. 단, RDTSCP는 완전한 직렬화가 아니므로 이후 명령 직렬화를 위해 LFENCE를 추가합니다. CPUID는 현대 측정에서는 오버헤드가 커 RDTSCP+LFENCE 조합으로 대체됩니다.
Leaf 0x0D: XSAVE 상태 컴포넌트 관리
Leaf 0x0D는 프로세서가 지원하는 XSAVE 상태 컴포넌트의 크기, 오프셋(Offset), 비트맵(Bitmap)을 질의합니다. 커널은 이 정보를 바탕으로 컨텍스트 전환 시 저장/복원할 FPU 상태 영역을 정확하게 할당합니다. CR4.OSXSAVE를 설정해 OS가 XSAVE를 지원함을 프로세서에 알려야만 AVX·AVX-512·AMX 등 확장 상태를 사용할 수 있습니다.
/* arch/x86/kernel/fpu/xstate.c — XSAVE 상태 초기화 */
void fpu__init_system_xstate(unsigned int legacy_size)
{
u32 eax, ebx, ecx, edx;
/* Leaf 0x0D Sub-leaf 0: 전체 지원 컴포넌트 비트맵 */
cpuid_count(0xd, 0, &eax, &ebx, &ecx, &edx);
xfeatures_mask_all = eax + ((u64)edx << 32);
/* EBX: 현재 XCR0 기준 크기, ECX: 최대 크기 */
fpu_kernel_cfg.max_size = ecx;
fpu_kernel_cfg.used_size = ebx;
/* Sub-leaf 1: XSAVE 기능 플래그 (XSAVEOPT, XSAVEC 등) */
cpuid_count(0xd, 1, &eax, &ebx, &ecx, &edx);
if (eax & (1 << 1))
setup_force_cpu_cap(X86_FEATURE_XSAVEC);
if (eax & (1 << 3))
setup_force_cpu_cap(X86_FEATURE_XSAVES);
if (eax & (1 << 4))
setup_force_cpu_cap(X86_FEATURE_XFD); /* AMX 지연 저장 */
/* Sub-leaf N (N≥2): 각 컴포넌트의 크기와 XSAVE 영역 내 오프셋 */
for (int i = 2; i < XFEATURE_MAX; i++) {
cpuid_count(0xd, i, &eax, &ebx, &ecx, &edx);
xstate_sizes[i] = eax; /* 컴포넌트 크기 (바이트) */
xstate_offsets[i] = ebx; /* XSAVE 영역 내 오프셋 */
}
}
Leaf 0x0A: 성능 모니터링 유닛(PMU) 탐지
Leaf 0x0A는 Intel Performance Monitoring Unit(PMU)의 버전과 카운터 구성을 보고합니다. perf 서브시스템은 이 정보를 사용해 사용 가능한 하드웨어 카운터 수와 비트 폭을 결정합니다. AMD는 다른 메커니즘(Leaf 0x80000022 EPMC)을 사용하므로 Leaf 0x0A는 사실상 Intel 전용입니다.
/* arch/x86/kernel/cpu/perf_event_intel.c — Intel PMU 탐지 */
void x86_pmu_show_pmu_cap(struct x86_pmu *pmu)
{
unsigned int eax, ebx, ecx, edx;
cpuid(0x0a, &eax, &ebx, &ecx, &edx);
pmu->version = eax & 0xff; /* EAX[7:0]: PMU 버전 (0이면 미지원) */
pmu->num_counters = (eax >> 8) & 0xff; /* EAX[15:8]: 범용 카운터 수 */
pmu->counter_bits = (eax >> 16) & 0xff; /* EAX[23:16]: 카운터 비트 폭 */
/* EBX: 사용 불가 이벤트 비트맵 (bit=1이면 해당 이벤트 미지원) */
/* bit 0=명령 실행, 1=비중단 사이클, 2=참조 사이클, 3=LLC참조,
* 4=LLC미스, 5=분기 실행, 6=분기 미스 예측 */
pmu->unavailable = ebx & 0xff;
/* EDX: 고정 카운터 정보 */
pmu->num_counters_fixed = edx & 0x1f; /* EDX[4:0]: 고정 카운터 수 */
pmu->fixed_counter_bits = (edx >> 5) & 0xff; /* EDX[12:5]: 고정 카운터 비트 폭 */
pr_info("PMU: version %u, %u counters (%u bits), %u fixed\n",
pmu->version, pmu->num_counters,
pmu->counter_bits, pmu->num_counters_fixed);
}
| PMU 버전 | 도입 마이크로아키텍처 | 주요 기능 |
|---|---|---|
| v1 | Pentium M, Core Solo/Duo | 범용 카운터 2개, 기본 이벤트 (cycles, instructions, cache-misses, branch-misses) |
| v2 | Core 2 (Merom, 2006) | 3개 고정 카운터 추가 (INST_RETIRED, CPU_CLK_UNHALTED.CORE, REF), PEBS 지원 |
| v3 | Nehalem (2008) | 범용 카운터 4개, Off-core Response 이벤트, 멀티스레드 카운터 지원 |
| v4 | Skylake (2015) | 인터럽트 억제 개선, LBR(Last Branch Record) 통합, 필터링 기능 강화 |
| v5 | Sapphire Rapids (2023) | Architectural LBR (XSAVE 기반, 최대 32개), PEBS 지연 개선, 정밀 이벤트 |
Leaf 0x15/0x16: TSC·코어 주파수 탐지
현대 Linux 커널은 Leaf 0x15와 Leaf 0x16을 통해 HPET나 PIT를 사용하지 않고 TSC 주파수를 정확하게 계산합니다. Leaf 0x15는 Core Crystal Clock과 TSC의 비율을, Leaf 0x16은 코어·최대·버스 주파수를 MHz 단위로 직접 반환합니다. 두 Leaf가 모두 사용 불가한 경우 커널은 PIT 기반 보정으로 폴백합니다.
/* arch/x86/kernel/tsc.c — TSC 주파수 탐지 (두 Leaf 조합) */
unsigned int native_calibrate_tsc(void)
{
unsigned int eax_denominator, ebx_numerator, ecx_crystal_hz, edx;
/* Leaf 0x15: Crystal Clock ↔ TSC 비율
* TSC_Hz = Crystal_Hz × EBX / EAX */
cpuid(0x15, &eax_denominator, &ebx_numerator, &ecx_crystal_hz, &edx);
if (eax_denominator && ebx_numerator && ecx_crystal_hz) {
return (ecx_crystal_hz / 1000) *
ebx_numerator / eax_denominator; /* kHz 단위 반환 */
}
/* ECX=0인 플랫폼: Leaf 0x16으로 코어 주파수 직접 참조 */
unsigned int eax_base_mhz, ebx_max_mhz, ecx_bus_mhz;
cpuid(0x16, &eax_base_mhz, &ebx_max_mhz, &ecx_bus_mhz, &edx);
if (eax_base_mhz)
return eax_base_mhz * 1000; /* MHz → kHz 변환 */
return 0; /* 실패 시 PIT/HPET 기반 보정 폴백 */
}
| 마이크로아키텍처 | 코드명 | Crystal Clock (ECX) | 비고 |
|---|---|---|---|
| Skylake, Kaby Lake | SKL, KBL | 24 MHz | ECX=24,000,000 |
| Ice Lake, Tiger Lake | ICL, TGL | 19.2 MHz | ECX=19,200,000 |
| Alder Lake, Raptor Lake | ADL, RPL | 38.4 MHz | ECX=38,400,000 |
| Atom Goldmont, Tremont | GLM, TRM | 19.2 / 25 MHz | 모델에 따라 다름 |
| Sapphire Rapids | SPR | 25 MHz | ECX=25,000,000 |
| AMD (대부분) | - | ECX=0 (미지원) | Leaf 0x16 EAX 사용 |
Invariant TSC: Leaf 0x80000007 EDX bit 8이 설정된 CPU는 전력 절약 상태(C-state)와 무관하게 TSC가 일정한 속도로 동작합니다. Linux는 이 비트를 확인해 TSC를 클럭소스로 사용할지 결정합니다. /proc/cpuinfo의 constant_tsc·nonstop_tsc·tsc_reliable 플래그로 확인할 수 있습니다.
마이크로코드 업데이트와 CPUID 변화
마이크로코드 업데이트는 CPU의 내부 동작을 수정하며, 특히 보안 취약점 완화를 위한 새로운 CPUID 피처 비트를 활성화합니다. Spectre·Meltdown 공개(2018) 이후, 많은 시스템에서 마이크로코드 로드 전후로 CPUID 결과가 달라집니다. Linux 커널은 early microcode 로드 후 CPUID를 재질의해 새 피처를 반영합니다.
/* arch/x86/kernel/cpu/microcode/core.c — 마이크로코드 로드 흐름 */
/* 1단계: early_initcall에서 microcode 로드 */
static int __init microcode_init(void)
{
microcode_ops = init_intel_microcode(); /* 또는 init_amd_microcode() */
return microcode_ops->apply_microcode(0);
}
/* 2단계: 마이크로코드 적용 후 피처 비트 재스캔 및 변경 사항 보고 */
void microcode_check(struct cpuinfo_x86 *prev_info)
{
struct cpuinfo_x86 info;
get_cpu_cap(&info); /* CPUID 재질의 */
for (int i = 0; i < NCAPINTS; i++) {
u32 changed = info.x86_capability[i] ^ prev_info->x86_capability[i];
if (changed)
pr_info("x86/CPU: Word %d: bits %#x changed\n", i, changed);
}
}
/* 3단계: 보안 완화 재적용 (마이크로코드로 활성화된 MSR 비트 사용) */
void spectre_v2_select_mitigation(void)
{
/* IBRS: Leaf 7 EDX bit 26 — 마이크로코드 업데이트 후 나타남 */
if (boot_cpu_has(X86_FEATURE_IBRS))
wrmsrl(MSR_IA32_SPEC_CTRL, SPEC_CTRL_IBRS);
/* STIBP: Leaf 7 EDX bit 27 — 형제 스레드 간 분기 예측 격리 */
if (boot_cpu_has(X86_FEATURE_STIBP))
wrmsrl(MSR_IA32_SPEC_CTRL, SPEC_CTRL_STIBP);
}
Early vs Late 마이크로코드 로드: initramfs의 마이크로코드 이미지(/boot/intel-ucode.img)는 GRUB가 커널보다 먼저 메모리에 올려 early boot 단계에서 적용됩니다. 늦은 마이크로코드 로드(/dev/cpu/microcode 쓰기)는 이미 수행된 CPUID 기반 초기화를 재실행하지 않으므로, 보안 완화 픽스에는 반드시 early 로드가 필요합니다. AMD는 /lib/firmware/amd-ucode/ 경로를 사용합니다.
CPU 기능 마스킹과 cmdline 제어
Linux 커널은 CPUID로 탐지된 피처라도 커맨드라인 옵션이나 에라타 워크어라운드로 강제로 비활성화하거나 활성화할 수 있습니다. setup_clear_cpu_cap()은 이미 설정된 피처 비트를 제거하며, setup_force_cpu_cap()은 CPUID에 없어도 비트를 강제 설정합니다. 이 메커니즘은 x86_capability와 cpu_caps_cleared/set 비트맵 배열을 통해 동작합니다.
/* arch/x86/kernel/cpu/common.c — 피처 비트 마스킹 메커니즘 */
/* 피처 강제 제거: boot_cpu_data와 이후 초기화되는 모든 CPU에 적용 */
void setup_clear_cpu_cap(unsigned int bit)
{
clear_cpu_cap(&boot_cpu_data, bit);
set_bit(bit, cpu_caps_cleared); /* SMP: 이후 부팅하는 CPU도 동일하게 클리어 */
}
/* cmdline 파싱 예: "noibrs" 옵션 처리 */
static int __init noibrs_setup(char *str)
{
setup_clear_cpu_cap(X86_FEATURE_IBRS);
setup_clear_cpu_cap(X86_FEATURE_IBRS_ENHANCED);
return 1;
}
__setup("noibrs", noibrs_setup);
/* 에라타 마스킹: 특정 Family/Model에서 버그있는 피처 비트 강제 제거 */
static void intel_workarounds(struct cpuinfo_x86 *c)
{
/* Kaby Lake/Coffee Lake 일부 스테핑: TSX 버그 → 강제 비활성화 */
if (c->x86 == 6 && c->x86_model == 0x8e &&
c->x86_stepping < 0xb) {
setup_clear_cpu_cap(X86_FEATURE_HLE);
setup_clear_cpu_cap(X86_FEATURE_RTM);
}
}
| cmdline 옵션 | 효과 | 관련 X86_FEATURE_* |
|---|---|---|
noibrs | IBRS 비활성화 (Spectre-v2 완화 비적용) | X86_FEATURE_IBRS |
noibpb | 간접 분기 예측기 배리어 비활성화 | X86_FEATURE_IBPB |
nox2apic | x2APIC 비활성화 (레거시 xAPIC 강제) | X86_FEATURE_X2APIC |
nopti | Kernel Page Table Isolation 비활성화 (Meltdown 완화 해제) | X86_FEATURE_PTI |
mitigations=off | 모든 CPU 취약점 완화 비활성화 (성능 우선) | IBRS, STIBP, SSBD, L1TF 등 다수 |
tsx=off | TSX (HLE/RTM) 강제 비활성화 (TAA/TSX-async-abort) | X86_FEATURE_HLE, X86_FEATURE_RTM |
nosmt | SMT(하이퍼스레딩) 비활성화 | 논리적 CPU 오프라인 처리 |
spectre_v2=off | Spectre-v2 완화 전체 비활성화 | X86_FEATURE_IBRS, X86_FEATURE_IBPB 등 |
CPUID Faulting: 유저 공간 CPUID 제한
CPUID Faulting은 Intel이 Ivy Bridge(3세대 Core)에서 도입한 보안 기능으로, 비특권 유저 공간에서 CPUID 실행 시 #GP(0) 예외를 발생시킵니다. 커널이 이 예외를 처리해 CPUID 결과를 에뮬레이션하거나 제한할 수 있으며, 이를 통해 컨테이너(Container)나 가상 환경에서 CPU 마이크로아키텍처 정보 노출을 방지합니다.
/* arch/x86/kernel/process.c — CPUID Faulting 설정 */
/* MSR_MISC_FEATURES_ENABLES(0x140) bit 0 = CPUID_FAULT */
void enable_cpuid_fault(void)
{
if (!boot_cpu_has(X86_FEATURE_CPUID_FAULT))
return;
wrmsrl(MSR_MISC_FEATURES_ENABLES, MISC_FEATURES_CPUID_FAULT);
pr_info("x86/CPU: CPUID Faulting enabled\n");
}
/* do_general_protection() 핸들러에서 CPUID 에뮬레이션 처리 */
static bool emulate_cpuid(struct pt_regs *regs)
{
unsigned int eax = regs->ax, ecx = regs->cx;
unsigned int r_eax, r_ebx, r_ecx, r_edx;
/* 커널이 CPUID 결과를 직접 계산해 유저 레지스터에 삽입 */
cpuid_count(eax, ecx, &r_eax, &r_ebx, &r_ecx, &r_edx);
/* 민감한 정보 마스킹: 하이퍼바이저 leaf는 숨김 */
if (eax >= 0x40000000)
r_eax = r_ebx = r_ecx = r_edx = 0;
regs->ax = r_eax; regs->bx = r_ebx;
regs->cx = r_ecx; regs->dx = r_edx;
regs->ip += 2; /* CPUID = 2바이트 명령어 (0x0F 0xA2) */
return true;
}
| 항목 | CPUID Faulting | CR4.UMIP (User-Mode Instruction Prevention) |
|---|---|---|
| 대상 명령어 | CPUID | SGDT, SLDT, SIDT, STR, SMSW |
| 활성화 방법 | MSR_MISC_FEATURES_ENABLES bit 0 | CR4 bit 11 |
| 예외 발생 | #GP(0) → 커널 에뮬레이션 가능 | #GP(0) → SIGSEGV 전달 |
| 보안 목적 | CPU 마이크로아키텍처 정보 노출 방지 | GDT/LDT/IDT 주소 노출 방지 (KASLR 보완) |
| 커널 상수 | X86_FEATURE_CPUID_FAULT | X86_FEATURE_UMIP |
| 도입 | Ivy Bridge (3세대 Core, 2012) | Goldmont (Atom), Cannon Lake+ (2018) |
컨테이너 보안 활용: CPUID Faulting은 컨테이너가 호스트 CPU의 정확한 모델·스테핑 정보를 획득하지 못하게 합니다. KVM은 게스트에게 가상화된 CPUID 결과를 에뮬레이션하는 방식으로 유사한 효과를 달성합니다. seccomp과 prctl(PR_SET_TSC, PR_TSC_SIGSEGV)를 조합하면 RDTSC도 제한할 수 있습니다.
AMD 전용 확장 Leaf (SME/SEV/QoS)
AMD는 0x8000001F를 통해 Secure Memory Encryption(SME)과 Secure Encrypted Virtualization(SEV) 지원 여부를 보고합니다. 이 Leaf는 메모리 암호화(Encryption) C-bit 위치와 물리 주소(Physical Address) 크기 감소량도 반환합니다. Linux 커널의 arch/x86/mm/mem_encrypt_amd.c는 이를 사용해 암호화 마스크를 초기화합니다.
/* arch/x86/mm/mem_encrypt_amd.c — SME/SEV 탐지 및 초기화 */
void amd_get_encrypted_memory_parameters(void)
{
unsigned int eax, ebx, ecx, edx;
/* Leaf 0x8000001F: AMD 메모리 암호화 기능 */
cpuid(0x8000001f, &eax, &ebx, &ecx, &edx);
if (!(eax & 1)) /* EAX bit 0: SME 지원 여부 */
return;
/* EBX[5:0]: C-bit 위치 (물리 주소에서 암호화 비트가 위치하는 비트 번호) */
sme_me_mask = 1ULL << (ebx & 0x3f);
/* EBX[11:6]: C-bit 포함으로 인한 물리 주소 비트 수 감소량 */
unsigned int phys_bits_reduced = (ebx >> 6) & 0x3f;
pr_info("AMD SME: C-bit at physical address bit %llu "
"(phys_bits reduced by %u)\n",
__ffs(sme_me_mask), phys_bits_reduced);
if (eax & (1 << 1)) /* EAX bit 1: SEV 지원 */
setup_force_cpu_cap(X86_FEATURE_SEV);
/* ECX: 동시 실행 가능한 암호화 게스트(ASID) 수 */
/* EDX: 최소 ASID (최대 동시 SEV 게스트 = ASID_MAX - EDX + 1) */
}
| 비트 | 이름 | 설명 |
|---|---|---|
| 0 | SME | Secure Memory Encryption — DRAM 전체 투명 암호화 (AES-128, C-bit 설정 시 활성) |
| 1 | SEV | Secure Encrypted Virtualization — 게스트 메모리 독립 암호화 |
| 2 | PAGE_FLUSH_MSR | MSR_AMD64_SME_PA로 물리 페이지 TLB 플러시 |
| 3 | SEV-ES | SEV Encrypted State — 게스트 레지스터 상태도 암호화 (VMSA 보호) |
| 4 | SEV-SNP | SEV Secure Nested Paging — 메모리 소유권 검증으로 무결성(Integrity) 보호 추가 |
| 5 | VMPL | VM Permission Levels — 게스트 내 권한 계층 4단계 (VMPL0~3) |
| 10 | SEV-TIO | Trusted I/O Virtualization 지원 |
| 28 | VMSA_REGPROT | VMSA 레지스터 보호 강화 (SEV-ES 보완) |
AMD Leaf 0x80000020은 Platform QoS(L3 캐시 할당, 메모리 대역폭(Bandwidth) 할당)를, 0x80000022는 Extended Performance Monitoring(EPMC)을 보고합니다. EPMC는 AMD Zen 4부터 도입되어 Linux perf가 Intel 수준의 하드웨어 카운터 접근을 AMD에서도 활용할 수 있게 합니다.
ARM64 ID 레지스터와 비교
ARM64(AArch64) 아키텍처는 x86의 CPUID 명령어 대신 시스템 레지스터를 사용해 CPU 기능을 보고합니다. MRS 명령어로 읽는 ID_AA64*_EL1 레지스터들이 x86의 CPUID Leaf에 해당하며, 커널의 arch/arm64/kernel/cpufeature.c가 이를 추상화합니다. ARM64는 각 레지스터의 4비트 필드 값이 클수록 더 새로운 기능 버전을 의미합니다.
/* arch/arm64/kernel/cpufeature.c — ARM64 CPU 기능 탐지 패턴 */
/* x86: boot_cpu_has(X86_FEATURE_AES) 에 대응 */
static bool has_aes(const struct arm64_cpu_capabilities *cap, int scope)
{
/* ARM64: MRS x0, ID_AA64ISAR0_EL1 로 읽는 값 */
u64 val = read_sanitised_ftr_reg(SYS_ID_AA64ISAR0_EL1);
return cpuid_feature_extract_unsigned_field(
val, ID_AA64ISAR0_EL1_AES_SHIFT) >= 1;
}
/* x86: boot_cpu_has(X86_FEATURE_LSE_ATOMICS) 에 대응 */
static bool has_lse_atomics(const struct arm64_cpu_capabilities *cap, int scope)
{
u64 val = read_sanitised_ftr_reg(SYS_ID_AA64ISAR0_EL1);
return cpuid_feature_extract_unsigned_field(
val, ID_AA64ISAR0_EL1_ATOMIC_SHIFT) >= 2;
}
/* x86: boot_cpu_has(X86_FEATURE_HYPERVISOR) 에 대응 */
static bool has_el2_virtualization(const struct arm64_cpu_capabilities *cap, int scope)
{
/* EL2(하이퍼바이저) 지원: ID_AA64PFR0_EL1 EL2 필드 */
u64 val = read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1);
return cpuid_feature_extract_unsigned_field(
val, ID_AA64PFR0_EL1_EL2_SHIFT) > 0;
}
| 기능 범주 | x86 CPUID Leaf | ARM64 ID 레지스터 | 읽기 명령 |
|---|---|---|---|
| 암호화 가속 | Leaf 1 ECX (AES-NI), Leaf 7 EBX (SHA) | ID_AA64ISAR0_EL1 | MRS x0, ID_AA64ISAR0_EL1 |
| 메모리 모델·페이지 | Leaf 1 EDX (PSE), 0x80000001 EDX (1GB) | ID_AA64MMFR0_EL1 | MRS x0, ID_AA64MMFR0_EL1 |
| 프로세서 기능·EL | Leaf 1 ECX (VMX), Leaf 7 ECX (UMIP) | ID_AA64PFR0_EL1 | MRS x0, ID_AA64PFR0_EL1 |
| 디버그·추적 | Leaf 0x14 (Intel PT) | ID_AA64DFR0_EL1 | MRS x0, ID_AA64DFR0_EL1 |
| SIMD/벡터 | Leaf 1 ECX (AVX), Leaf 7 (AVX-512) | ID_AA64PFR0_EL1, SVE: ID_AA64ZFR0_EL1 | MRS x0, ID_AA64ZFR0_EL1 |
| 원자 연산 | Leaf 1 ECX (CX16) | ID_AA64ISAR0_EL1 ATOMIC 필드 | MRS x0, ID_AA64ISAR0_EL1 |
| 물리 주소 크기 | Leaf 0x80000008 EAX[7:0] | ID_AA64MMFR0_EL1 PARange 필드 | MRS x0, ID_AA64MMFR0_EL1 |
| 가상화 | Leaf 1 ECX bit 5 (VMX) | ID_AA64PFR0_EL1 EL2 필드 | MRS x0, ID_AA64PFR0_EL1 |
ARM64의 피처 탐지 철학: ARM64는 CPU ID 레지스터를 4비트 필드(field) 단위로 정의합니다. 값이 클수록 더 새로운 기능 버전을 의미합니다 (예: ATOMIC≥2 → LSE 지원). x86의 단일 피처 비트와 달리 "이 기능의 버전 N 이상"처럼 질의합니다. 또한 ARM64는 모든 코어가 동일한 ID 레지스터 값을 가져야 하므로 x86의 코어별 CPUID 불일치 문제가 없습니다.
ARM64 CPU 식별
ARM64(AArch64) 아키텍처는 x86의 CPUID 명령어 대신 시스템 레지스터(System Registers)를 통해 CPU 기능을 식별합니다. Linux 커널은 부팅 시 이 레지스터들을 읽어 boot_cpu_data에 해당하는 boot_cpu_feature 구조체를 초기화하고, arm64_cpu_capabilities 배열을 통해 기능 탐지와 커널 최적화 경로를 결정합니다.
MIDR_EL1 Implementer 코드 및 PartNum 예시
| Implementer 코드 | 제조사 | PartNum 예시 | 코어 예시 |
|---|---|---|---|
0x41 | ARM Ltd. | 0xD0C, 0xD40, 0xD44, 0xD4F | Cortex-A76, Neoverse V1, V2, N2 |
0x51 | Qualcomm | 0x801, 0x802, 0x803, 0x804 | Kryo 2xx Gold/Silver, Kryo 3xx |
0x61 | Apple | 0x022, 0x023, 0x024, 0x030 | Firestorm (M1), Icestorm, Everest (M2) |
0x4E | NVIDIA | 0x004, 0x003 | Carmel (Tegra Xavier), Denver |
0xC0 | Ampere | 0xAC3 | AmpereOne (eMAG 계열) |
0x48 | HiSilicon | 0xD01, 0xD02 | TaiShan v110, TaiShan v120 |
0x46 | Fujitsu | 0x001 | A64FX (Post-K) |
0x56 | Marvell | 0x581 | ThunderX2 |
ID_AA64* 레지스터 전체 목록과 커버 기능
| 레지스터 | 주요 커버 기능 | 주요 필드 예시 |
|---|---|---|
ID_AA64ISAR0_EL1 | 암호화·원자성 ISA | AES, SHA1, SHA2, CRC32, Atomic (LSE), RDM, SHA3, SM3, SM4, DP, FHM |
ID_AA64ISAR1_EL1 | 포인터 인증·분기·벡터 | DPB, JSCVT, FCMA, LRCPC, GPA/GPI (PAC), FRINTTS, SB, SPECRES, BF16, I8MM |
ID_AA64ISAR2_EL1 | 최신 확장 ISA | WFXT, RPRES, PAC3, BC (HBC), MOPS (memcpy/memset ISA), CSSC, RPRFM |
ID_AA64MMFR0_EL1 | 물리 주소 범위·TG | PARange (32~52bit), TGran4/16/64, SNSMem, BigEnd, ASIDBits, ExS |
ID_AA64MMFR1_EL1 | MMU 고급 기능 | HAFDBS, VMIDBits, VHE, HPDS, LO, PAN, SpecSEI, XNX, TWED, ETS, HCX, AFP |
ID_AA64MMFR2_EL1 | ARMv8.2+ MMU 확장 | CnP, UAO, LSM, IESB, VARange (LVA), CCIDX, NV, ST, AT, FWB, IDS, TTL, BBM, EVT, E0PD |
ID_AA64MMFR3_EL1 | ARMv8.9+ MMU | TCRX, SCTLRX, S1PIE, S1POE, S2POE, AIE, MEC, D128, D128_2, SNERR, ANERR |
ID_AA64MMFR4_EL1 | 최신 MMU 확장 | EIESB, ASID2, HACDBS, FGWTE3, NV_frac, E2H0 |
ID_AA64PFR0_EL1 | EL 지원 수준·SVE | EL0~EL3 (AArch32/64), FP, AdvSIMD, GIC, RAS, SEL2, SVE, DIT, MPAM, AMU, CSV2, CSV3 |
ID_AA64PFR1_EL1 | MTE·SME·RAS 확장 | BT (BTI), SSBS, MTE, RAS_frac, MPAM_frac, SME, RNDR_trap, CSV2_frac, NMI, MTE_frac, GCS, THE, DF2, PFAR |
ID_AA64PFR2_EL1 | ARMv9.4+ 피처 | MTEFAR, MTESTOREONLY, MTEPERM, UINJ |
ID_AA64DFR0_EL1 | 디버그·PMU | DebugVer, TraceVer, PMUVer, BRPs, WRPs, CTX_CMPs, PMSVer, DoubleLock, TraceFilt |
ID_AA64DFR1_EL1 | 디버그 확장 | EBEP, ITE, ABLE, PMICNTR, SPMU, CTX_CMPs_2, BRPs_2, WRPs_2 |
ID_AA64ZFR0_EL1 | SVE 기능 (PFR0.SVE=1 시 유효) | SVEver, AES, BitPerm, BF16, B16B16, SHA3, SM4, I8MM, F32MM, F64MM |
ID_AA64SMFR0_EL1 | SME 기능 (PFR1.SME=1 시 유효) | F32F32, BI32I32, B16F32, F16F32, I8I32, F16F16, I16I32, FA64, LUTv2, SMEver, I16I64, SF8FMA, SF8DP4, SF8DP2 |
커널 구현: arm64_ftr_reg와 arm64_cpu_capabilities
/* arch/arm64/kernel/cpufeature.c
* arm64_ftr_reg: ID 레지스터의 필드별 안전 값(최솟값) 관리
*/
struct arm64_ftr_reg {
const char *name;
u64 strict_mask; /* 이 비트는 CPU 간 반드시 일치해야 함 */
u64 user_mask; /* userspace에 노출할 비트 마스크 */
u64 sys_val; /* 시스템 안전 값(최솟값으로 수렴) */
u64 user_val; /* userspace 노출 값(새니타이즈) */
const struct arm64_ftr_bits *ftr_bits; /* 필드별 정의 배열 */
};
/* arm64_cpu_capabilities: 기능 탐지 + enable 콜백 */
struct arm64_cpu_capabilities {
const char *desc;
u16 capability; /* ARM64_HAS_* 상수 */
u16 type; /* SCOPE_SYSTEM / LOCAL_CPU / BOOT_CPU */
bool (*matches)(const struct arm64_cpu_capabilities *,
int scope);
void (*cpu_enable)(const struct arm64_cpu_capabilities *);
union {
struct { /* ID 레지스터 기반 탐지 */
u32 sys_reg;
u8 field_pos;
u8 field_width;
u8 min_field_value;
u8 hwcap_type;
unsigned long hwcap;
};
const struct midr_range *midr_range_list; /* MIDR 범위 기반 */
};
};
/* 사용 예: LSE(Large System Extension) 원자성 명령어 탐지 */
static const struct arm64_cpu_capabilities arm64_features[] = {
{
.desc = "LSE atomic instructions",
.capability = ARM64_HAS_LSE_ATOMICS,
.type = ARM64_CPUCAP_SYSTEM_FEATURE,
.matches = has_cpuid_feature,
.sys_reg = SYS_ID_AA64ISAR0_EL1,
.field_pos = ID_AA64ISAR0_EL1_ATOMIC_SHIFT,
.min_field_value = 2,
.hwcap = HWCAP_ATOMICS,
},
/* ... BTI, MTE, SVE, PAC, SSBS, ... */
};
cpu_errata와 MIDR 범위 매칭
/* arch/arm64/kernel/cpu-errata.c
* is_affected_midr_range(): MIDR_EL1 + REVIDR_EL1 로 에라타 해당 CPU 판별
*/
static bool is_affected_midr_range(const struct arm64_cpu_capabilities *entry,
int scope)
{
const struct arm64_midr_revidr *fix;
const struct midr_range *range;
u32 midr = read_cpuid_id(); /* MIDR_EL1 읽기 */
u64 revidr;
if (!is_midr_in_range_list(midr, entry->midr_range_list))
return false;
/* REVIDR_EL1: 특정 리비전은 이미 수정됨 — 제외 처리 */
revidr = read_sysreg(revidr_el1);
for (fix = entry->fixed_revs; fix && fix->revidr_mask; fix++) {
if (is_midr_in_range(midr, &fix->range) &&
(revidr & fix->revidr_mask))
return false; /* 수정된 리비전 → 에라타 불필요 */
}
return true;
}
/* HWCAP_CPUID: EL0에서 ID 레지스터 MRS 시 EL1 트랩 처리
* arch/arm64/kernel/traps.c — do_sysreg_emulation()
* 커널이 user_val(새니타이즈된 값) 반환하여 userspace에 안전하게 노출 */
static int emulate_id_reg(struct pt_regs *regs, u32 sys_reg)
{
const struct arm64_ftr_reg *regp = get_arm64_ftr_reg(sys_reg);
if (!regp)
return -EINVAL;
pt_regs_write_reg(regs, sys_reg_get_rt(sys_reg), regp->user_val);
return 0;
}
CTR_EL0)는 ARM64에서 예외적으로 EL0(사용자 공간)에서 MRS로 직접 읽을 수 있는 시스템 레지스터입니다. I-캐시 정책(VIPT/PIPT), DIC(D-side inner cacheability), IDC(instruction cache DMA coherency), 캐시 라인 크기 등을 담고 있어 malloc·memcpy 구현에서 자주 활용합니다. glibc는 이를 통해 캐시 라인 정렬 최적화를 수행합니다.
CPUID 명령어로 모든 기능을 쿼리하지만, ARM64는 수십 개의 개별 시스템 레지스터를 읽어야 합니다. 또한 ARM64는 멀티 CPU 환경에서 CPU마다 다른 마이크로아키텍처가 혼재할 수 있으므로(big.LITTLE, DynamIQ) arm64_ftr_reg.sys_val이 모든 CPU의 최솟값(안전 값)으로 수렴합니다. 따라서 system_supports_lse_atomics()처럼 시스템 전체 지원 여부를 묻는 헬퍼를 항상 사용해야 합니다.
/proc/cpuinfo의 Features 필드와 AT_HWCAP·AT_HWCAP2를 통해 EL0에 노출되는 ARM64 기능 비트는 커널이 새니타이즈한 값입니다. 가상화 환경에서 하이퍼바이저가 ID 레지스터를 마스킹할 수 있으므로, 사용자 프로그램은 getauxval(AT_HWCAP)을 사용해야 하며 MRS로 직접 읽은 값이 더 많은 기능을 보여주더라도 신뢰해서는 안 됩니다.
RISC-V CPU 식별
RISC-V 아키텍처는 CPU 식별 방식이 x86, ARM64와 근본적으로 다릅니다. CPU 식별 정보는 M-mode(Machine mode) CSR(Control and Status Register)에 있지만, Linux는 S-mode(Supervisor mode)에서 동작하므로 이 레지스터에 직접 접근할 수 없습니다. 대신 Device Tree, SBI(Supervisor Binary Interface), 그리고 런타임 프로빙을 조합하여 CPU 기능을 탐지합니다.
M-mode CSR 주요 레지스터
| CSR 이름 | CSR 주소 | 설명 | 비고 |
|---|---|---|---|
mvendorid | 0xF11 | JEDEC 제조사 코드 | 0=비상업적/미구현, SiFive=0x489, T-Head=0x5B7 |
marchid | 0xF12 | 마이크로아키텍처 ID | 제조사 정의, 오픈소스는 MSB=0 |
mimpid | 0xF13 | 구현 버전 ID | 펌웨어(Firmware)·RTL 리비전 식별 |
misa | 0x301 | ISA 지원 비트맵 | 구현체가 0 반환 허용, 신뢰도 낮음 |
mhartid | 0xF14 | 하드웨어 스레드(Thread) ID | 부트 HART=0, SMP는 0부터 순차 또는 임의 |
AT_HWCAP 비트 맵 (RISC-V)
| HWCAP 매크로 | 비트 | ISA 확장 | 의미 |
|---|---|---|---|
HWCAP_ISA_I | bit 8 | I | 정수 기반 명령어 세트 (필수) |
HWCAP_ISA_M | bit 12 | M | 정수 곱셈/나눗셈 |
HWCAP_ISA_A | bit 0 | A | 원자성 명령어 (LR/SC, AMO) |
HWCAP_ISA_F | bit 5 | F | 단정밀도 부동소수점 |
HWCAP_ISA_D | bit 3 | D | 배정밀도 부동소수점 |
HWCAP_ISA_C | bit 2 | C | 압축 명령어(16비트 인코딩) |
HWCAP_ISA_V | bit 21 | V | 벡터 확장 (RISC-V V 1.0) |
HWCAP_ISA_H | bit 7 | H | 하이퍼바이저 확장 |
커널 구현: riscv_fill_hwcap()과 ALTERNATIVE()
/* arch/riscv/kernel/cpufeature.c
* riscv_isa_ext_data: 확장 이름 문자열 ↔ hwcap 비트 매핑
*/
struct riscv_isa_ext_data {
const unsigned int id; /* RISCV_ISA_EXT_* 상수 */
const char *name; /* DT riscv,isa-extensions의 문자열 */
const char *property; /* DT 속성명 (소문자, 예: "zba") */
const unsigned int *subset_ext_ids; /* 의존 확장 ID 목록 */
const unsigned int subset_ext_size;
};
/* riscv_fill_hwcap(): 부팅 시 DT + misa 파싱하여 elf_hwcap 구성 */
void riscv_fill_hwcap(void)
{
struct device_node *node;
const char *isa;
for_each_of_cpu_node(node) {
/* 1단계: riscv,isa 문자열 파싱 (레거시) */
if (!of_property_read_string(node, "riscv,isa", &isa))
riscv_parse_isa_string(isa, &this_cpu_ptr(&riscv_cpuinfo)->isa);
/* 2단계: riscv,isa-extensions 배열 파싱 (최신 DT 바인딩) */
riscv_parse_isa_extensions(node);
}
/* 3단계: SBI 확장 프로빙 */
if (sbi_probe_extension(SBI_EXT_PMU) > 0)
static_branch_enable(&riscv_isa_ext_keys[RISCV_ISA_EXT_ZICNTR]);
/* 4단계: elf_hwcap 최종 세트 */
elf_hwcap = riscv_isa_extension_base(hart_isa);
}
/* ALTERNATIVE() 매크로: 런타임 기능 기반 코드 패치
* 기능 있으면 NOP를 실제 명령어로 교체, 없으면 fallback 유지
* arch/riscv/include/asm/alternative.h */
ALTERNATIVE("nop", /* fallback: 기능 없을 때 */
"amoadd.w zero,zero,(a0)", /* 패치: Zaamo 있을 때 */
0, /* vendor ID (0=any) */
RISCV_ISA_EXT_ZAAMO, /* 요구 확장 */
CONFIG_RISCV_ISA_ZAAMO); /* 컴파일 조건 */
/* SBI 확장 프로빙 — arch/riscv/kernel/sbi.c */
long sbi_probe_extension(int ext)
{
struct sbiret ret;
/* ecall: a7=SBI_EXT_BASE, a6=SBI_BASE_PROBE_EXT, a0=ext_id */
ret = sbi_ecall(SBI_EXT_BASE, SBI_BASE_PROBE_EXT, ext,
0, 0, 0, 0, 0);
if (!ret.error)
return ret.value; /* 0=미지원, 양수=지원(버전/기능 플래그) */
return -EOPNOTSUPP;
}
/* /proc/cpuinfo 출력 예 (RISC-V)
* processor : 0
* hart : 0
* isa : rv64imafdc_zba_zbb_zbc_zbs
* mmu : sv48
* uarch : sifive,bullet0
*/
riscv,isa 문자열보다 riscv,isa-extensions phandle 배열이 권위 소스로 사용됩니다. 이 배열에는 zba, zbb, zbs, zicbom(캐시 관리 명령), zifencei, svnapot(NAPOT 페이지) 등 세부 확장이 명시됩니다. 벤더 SoC DTB가 구식 형식을 사용하는 경우 riscv,isa 레거시 파싱도 병행합니다.
misa를 0으로 반환하는 구현체를 허용합니다. 실제로 일부 임베디드 코어(Ibex, CVA6 일부)는 misa=0을 반환합니다. 따라서 리눅스 커널은 misa를 보조 수단으로만 활용하고, Device Tree의 riscv,isa-extensions를 우선합니다. 사용자 공간 프로그램은 getauxval(AT_HWCAP)으로 기능을 확인해야 하며, misa를 직접 읽을 방법도 없습니다(S-mode 접근 불가).
VLEN(벡터 레지스터 길이)이 구현체마다 다릅니다(128~65536비트). Linux는 vlenb CSR(0xC22, S-mode 읽기 가능)을 통해 실제 VLEN을 탐지하고, /proc/cpuinfo의 isa 필드와 HWCAP_ISA_V로 노출합니다. glibc 2.39+는 이를 통해 RVV 최적화 memcpy/memset을 자동 선택합니다.
PowerPC CPU 식별
PowerPC 아키텍처는 PVR(Processor Version Register)이라는 단일 특수 목적 레지스터로 CPU 버전을 식별합니다. mfspr 명령어로 읽으며, 상위 16비트는 버전(세대/마이크로아키텍처), 하위 16비트는 리비전(스테핑/수정 수준)을 나타냅니다. Linux 커널은 arch/powerpc/kernel/cputable.c의 cpu_specs[] 테이블로 PVR 값을 CPU 피처 집합에 매핑(Mapping)합니다.
PVR 값과 주요 POWER 프로세서
| 프로세서 | PVR 버전 (상위 16비트) | PVR 전체 예시 | 주요 특징 |
|---|---|---|---|
| POWER7 | 0x003F | 0x003F0101 | ISA 2.06, VSX, TM(트랜잭셔널 메모리), 8-way SMT |
| POWER7+ | 0x004A | 0x004A0100 | POWER7 개선판, 향상된 CAPI |
| POWER8E | 0x004B | 0x004B0100 | ISA 2.07, HTM, CAPI, 12-way SMT, ECC enhanced |
| POWER8NVL | 0x004C | 0x004C0100 | NVLink 지원 POWER8 (Summit 전 세대) |
| POWER8 | 0x004D | 0x004D0200 | 표준 POWER8, ISA 2.07B |
| POWER9 | 0x004E | 0x004E1200 | ISA 3.0, NVLink 2.0, PCIe Gen4, 4-way SMT |
| POWER10 | 0x0080 | 0x00801100 | ISA 3.1, MMA(행렬곱셈 가속), PCIe Gen5, OpenCAPI 5.0 |
| e5500 (Freescale) | 0x8024 | 0x80240010 | 임베디드 Book3E, e5500 코어 |
| e6500 (Freescale) | 0x8040 | 0x80400010 | AltiVec 지원 임베디드 코어 |
/* arch/powerpc/kernel/cputable.c
* struct cpu_spec: PVR 범위 → 기능 집합 매핑 테이블의 핵심 구조체
*/
struct cpu_spec {
unsigned int pvr_mask; /* PVR 비교 시 마스크 (버전부만 또는 전체) */
unsigned int pvr_value; /* 마스크된 PVR 기댓값 */
char *cpu_name; /* /proc/cpuinfo의 "cpu" 필드 */
unsigned long cpu_features; /* CPU_FTR_* 비트마스크 */
unsigned long cpu_user_features; /* AT_HWCAP 비트 (PPC_FEATURE_*) */
unsigned long cpu_user_features2; /* AT_HWCAP2 비트 (PPC_FEATURE2_*) */
unsigned int mmu_features; /* MMU_FTR_* 비트마스크 */
void (*cpu_setup)(struct seq_file *m,
struct cpu_spec *spec); /* 추가 초기화 콜백 */
void (*cpu_restore)(void); /* 슬립 복구 후 MSR/HID 복원 */
};
/* cpu_specs[] 테이블 항목 예시 (POWER10) */
{
.pvr_mask = 0xffff0000,
.pvr_value = 0x00800000,
.cpu_name = "POWER10",
.cpu_features = CPU_FTRS_POWER10,
.cpu_user_features = COMMON_USER_POWER10,
.cpu_user_features2 = COMMON_USER2_POWER10,
.mmu_features = MMU_FTRS_POWER10,
.cpu_setup = __setup_cpu_power10,
},
/* identify_cpu(): 부팅 초기 PVR 읽어 cpu_specs[] 선형 검색
* arch/powerpc/kernel/cputable.c */
struct cpu_spec * identify_cpu(unsigned long offset, unsigned int pvr)
{
struct cpu_spec *s = cpu_specs;
struct cpu_spec **cur = &cur_cpu_spec;
for (; s->pvr_mask; s++) {
if ((pvr & s->pvr_mask) == s->pvr_value) {
*cur = apply_feature_fixups(s);
return *cur;
}
}
return NULL;
}
/* PowerPC: PVR 및 PIR 읽기
* PVR (Processor Version Register) = SPR 287 (0x11F)
* PIR (Processor ID Register) = SPR 1023 (0x3FF) — POWER9+
*/
/* PVR 읽기: mfspr rD, 287 */
mfspr r3, 287 /* r3 = PVR */
rlwinm r4, r3, 16, 16, 31 /* r4 = Version (상위 16비트 → 하위) */
andi. r5, r3, 0xFFFF /* r5 = Revision (하위 16비트) */
/* PIR 읽기: 논리 CPU ID (POWER9 이후 SMT 스레드 포함)
* PIR[31:8] = core ID, PIR[7:0] = thread ID within core */
mfspr r3, 1023
rlwinm r4, r3, 24, 8, 31 /* r4 = core ID */
andi. r5, r3, 0xFF /* r5 = thread ID */
/* Device Tree의 cpu@0 노드:
* reg = <0>; — HART/PIR에 대응하는 논리 CPU ID
* cpu-version = <0x004E1200>; — PVR 값 직접 명시 (일부 플랫폼)
* ibm,pa-features = <...>; — PA(PowerPC Architecture) 확장 비트 배열
* d-cache-size = <0x8000>;
* i-cache-size = <0x8000>;
*/
PPC_FEATURE_HAS_ALTIVEC(bit 28): AltiVec/VMX 벡터 유닛 (POWER6+)PPC_FEATURE_HAS_VSX(bit 7): VSX(Vector-Scalar Extension, POWER7+)PPC_FEATURE2_HAS_HTM: 하드웨어 트랜잭셔널 메모리 (POWER8, 이후 POWER10에서 제거)PPC_FEATURE2_ARCH_3_1: ISA 3.1 (POWER10) — MMA, prefixed instructionsPPC_FEATURE2_MMA: 행렬 곱셈 가속기 (POWER10 전용)PPC_FEATURE_TRUE_LE: 진정한 Little-Endian 모드 지원 (POWER8+, ppc64le)
cur_cpu_spec 전역 포인터가 현재 CPU의 cpu_spec을 가리키며, cpu_has_feature(CPU_FTR_*) 매크로로 런타임 기능 확인이 가능합니다.
MIPS CPU 식별
MIPS 아키텍처는 CP0(Coprocessor 0)의 레지스터를 통해 CPU 식별과 기능 탐지를 수행합니다. 핵심은 PRId(Processor ID, CP0 Register 15)와 Config0~Config5 레지스터 체인입니다. Config 레지스터는 M 비트(bit 31)로 다음 Config 레지스터의 존재를 표시하며, 체인 방식으로 연결됩니다.
PRId 비트 레이아웃 및 Company ID 코드
| 비트 범위 | 필드명 | 설명 | 예시 |
|---|---|---|---|
| [31:24] | Company Options | 회사 정의 옵션 (구현체별 의미 다름) | 0x00 (보통 미사용) |
| [23:16] | Company ID | CPU 제조사 코드 | 0x01=MIPS, 0x0D=Cavium, 0x0E=Loongson 레거시 |
| [15:8] | Processor ID | CPU 코어 종류 | 0x90=MIPS74K, 0x96=MIPS I6400, 0x29=Cavium CN78xx |
| [7:0] | Revision | 패치/스테핑 리비전 | 0x01~0x0F |
| Company ID | 제조사 | 주요 코어 |
|---|---|---|
0x01 | MIPS Technologies (Wave Computing) | MIPS 24K/34K/74K/M5150, I6400, I6500, P5600 |
0x02 | Broadcom | BCM3302, BCM4710 (MIPS 기반 홈 라우터) |
0x0D | Cavium Networks | Octeon CN3xxx/CN5xxx/CN7xxx/CN8xxx (네트워크 프로세서) |
0x0E | Loongson (레거시) | Loongson 1/2/3 (초기 버전, MIPS64 호환) |
0x10 | NEC | VR4100, VR4181 (PDA/임베디드) |
0x23 | RMI / NetLogic | XLR/XLS (다중코어 네트워크 SoC) |
0x30 | Alchemy (AMD) | Au1000/Au1100/Au1200 (저전력 임베디드) |
Config 레지스터 체인 구조
/* PRId 읽기: CP0 Register 15, Select 0 */
mfc0 $t0, $15, 0 /* $t0 = PRId */
srl $t1, $t0, 16 /* $t1 = Company ID (상위) + Processor ID */
andi $t2, $t0, 0xFF /* $t2 = Revision [7:0] */
/* Config0: CP0 Register 16, Select 0
* [31] M=1 → Config1 존재
* [15:13] AT: 아키텍처 타입 (0=MIPS32, 1=MIPS64/32addr, 2=MIPS64)
* [12:10] AR: 아키텍처 리비전 (0=R1, 1=R2, 2=R6)
* [8:7] MT: MMU 타입 (0=없음, 1=TLB, 3=FMT/FixedMapping, 4=VTLB+FTLB)
* [2:0] K0: kseg0 캐시 속성 (0=Uncached, 3=WriteBack Cached)
*/
mfc0 $t0, $16, 0 /* Config0 */
srl $t1, $t0, 31 /* M 비트: 1이면 Config1 존재 */
beqz $t1, no_config1
mfc0 $t0, $16, 1 /* Config1: 캐시 지오메트리, FP, MIPS16, Watch */
/* Config1[30:25] MMUSize: TLB 엔트리 수 - 1
* Config1[24:22] IS: I-캐시 집합 수 로그2 (0=64, 1=128, ...)
* Config1[21:19] IL: I-캐시 라인 크기 (1=4B, 2=8B, 3=16B, 4=32B)
* Config1[18:16] IA: I-캐시 연관도 (0=직접, 1=2-way, ...)
* Config1[15:13] DS: D-캐시 집합 수, Config1[12:10] DL, Config1[9:7] DA
* Config1[0] FP: FPU 지원 여부
*/
mfc0 $t0, $16, 3 /* Config3: ULRI, VInt, MSA, MCU, MtChecker */
/* Config3[28] ULRI: UserLocal 레지스터 구현 여부
* Config3[5] VINT: 벡터 인터럽트 지원
* Config3[4] VEIC: 외부 인터럽트 컨트롤러 지원
* Config3[2] MT: 멀티스레딩(MIPS MT ASE) 지원
* Config3[1] SM: SmartMIPS ASE 지원
*/
ext $t1, $t0, 28, 1 /* ULRI 비트 추출 */
mfc0 $t0, $16, 5 /* Config5: VP(가상 프로세서), FRE, MSAEn */
/* Config5[7] FRE: FR=1 모드에서 레거시 FPU 에뮬레이션
* Config5[2] VP: 가상 프로세서(MIPS MT 관련)
* Config5[27] MSAEn: MSA(MIPS SIMD Architecture) 활성화
*/
no_config1:
/* arch/mips/kernel/cpu-probe.c
* cpu_probe(): 부팅 초기 PRId + Config 체인 읽어 cpuinfo_mips 초기화
*/
void cpu_probe(void)
{
struct cpuinfo_mips *c = ¤t_cpu_data();
unsigned int cpu = smp_processor_id();
/* PRId 읽기 */
c->processor_id = read_c0_prid(); /* mfc0 $t, $15, 0 */
c->fpu_id = cpu_has_fpu() ? read_32bit_cp1_register(CP1_REVISION) : 0;
c->cputype = CPU_UNKNOWN;
/* Company ID로 분기 */
switch (c->processor_id & PRID_COMP_MASK) {
case PRID_COMP_MIPS: /* 0x010000 */
cpu_probe_mips(c, cpu);
break;
case PRID_COMP_CAVIUM: /* 0x0D0000 */
cpu_probe_cavium(c, cpu);
break;
case PRID_COMP_LOONGSON: /* 0x0E0000 — 레거시 */
cpu_probe_loongson(c, cpu);
break;
/* ... */
}
/* Config 레지스터 체인 읽기 */
cpu_probe_config(c);
/* cpuinfo_mips.ases: CPU_ASE_MIPS16, CPU_ASE_MSA, CPU_ASE_DSP 등 */
/* cpuinfo_mips.options: MIPS_CPU_FPU, TLB, 4KEX, LLSC 등 */
}
/* cpuinfo_mips 주요 필드 */
struct cpuinfo_mips {
unsigned long udelay_val;
unsigned long asid_cache;
unsigned int processor_id; /* PRId 원본 값 */
unsigned int fpu_id; /* FPU PRId */
unsigned int cputype; /* CPU_24K, CPU_74K, CPU_CAVIUM_OCTEON 등 */
unsigned long options; /* MIPS_CPU_FPU, MIPS_CPU_LLSC, ... */
unsigned long ases; /* MIPS_ASE_MIPS16, MIPS_ASE_MSA, ... */
unsigned int tlbsize; /* TLB 엔트리 수 (Config1에서 읽음) */
unsigned int tlbsizevtlb; /* VTLB 크기 */
unsigned int tlbsizeftlbsets; /* FTLB 세트 수 */
int icache_size, dcache_size; /* 바이트 단위 */
int icache_ways, dcache_ways;
int icache_linesz, dcache_linesz;
};
cpu_probe_config()는 M 비트를 확인하며 순차적으로 체인을 따라갑니다.
S390x CPU 식별
IBM Z 아키텍처(s390x)는 x86의 CPUID와 개념적으로 가장 유사한 비트맵 기반 기능 열거 방식을 사용합니다.
세 가지 핵심 명령어 — STIDP, STFLE, STSI — 가 CPU 식별, 기능 비트맵, 토폴로지 정보를 각각 담당합니다.
STIDP — CPU 식별 더블워드
STIDP(Store CPU ID) 명령어는 64비트 더블워드를 메모리에 저장합니다.
상위 16비트가 CPU 유형 코드이며, 이 값으로 IBM Z 세대(z13/z14/z15/z16 등)를 구분합니다.
| 비트 범위 | 필드명 | 크기 | 설명 |
|---|---|---|---|
| 63–48 | CPU Type | 16-bit | IBM Z 세대 식별자 (예: 0x3906 = z14, 0x8561 = z15) |
| 47–32 | Version | 16-bit | 마이크로코드/하드웨어 버전 |
| 31–8 | Serial | 24-bit | 시스템 시리얼 번호 |
| 7–0 | (예약) | 8-bit | 현재 미사용, 0으로 유지 |
/* arch/s390/kernel/setup.c — STIDP로 CPU 유형 읽기 */
static void __init setup_cpu_type(void)
{
struct cpuid cpu_id;
get_cpu_id(&cpu_id); /* inline STIDP 래퍼 */
/* cpu_id.machine = 상위 16비트 (CPU 유형) */
S390_lowcore.cpu_type = cpu_id.machine;
pr_info("CPU: Type %04X, Version %04X\n",
cpu_id.machine, cpu_id.version);
}
/* <asm/processor.h> — cpuid 구조체 */
struct cpuid {
unsigned int version : 8;
unsigned int ident : 24;
unsigned int machine : 16;
unsigned int unused : 16;
} __packed;
STFLE — 기능 비트맵 열거
STFLE(Store Facility List Extended)는 x86 CPUID의 ECX/EDX 플래그와 가장 유사한 개념입니다.
각 비트 하나가 정확히 하나의 하드웨어/펌웨어 기능을 나타내며, 비트맵은 64비트 더블워드(블록) 단위로 확장됩니다.
r0에 요청할 블록 수를 지정하고 STFLE를 실행하면 비트맵 전체가 메모리에 기록됩니다.
| 비트 번호 | 기능 이름 | 의미 |
|---|---|---|
| 2 | z/Architecture 모드 | 64비트 z/Architecture 동작 모드 지원 |
| 17 | MSA (Message Security Assist) | AES/SHA 등 암호화 하드웨어 명령어 |
| 42 | DFP (Decimal Floating Point) | IEEE 754-2008 10진 부동소수점 지원 |
| 49 | Miscellaneous Insn Extensions | RISBG 등 비트 조작 확장 명령어 |
| 76 | MSA 확장 3 | CMAC, GHASH 하드웨어 가속 |
| 77 | MSA 확장 4 | RSA 보조 연산 |
| 129 | 벡터 명령어 (VXF) | 128비트 SIMD 벡터 연산 (z13+) |
| 134 | 벡터 확장 1 (VXE) | 벡터 BFP/정수 확장 (z14+) |
| 146 | MSA 확장 8 | EdDSA, 512비트 해시(Hash) 가속 |
| 155 | 벡터 Packed Decimal | 10진 데이터 벡터 처리 |
| 165 | NNP 지원 | 신경망 처리 보조 (z16+) |
/* arch/s390/kernel/processor.c — STFLE 비트맵 읽기 및 hwcap 설정 */
static void __init setup_hwcaps(void)
{
/* STFLE 결과는 부팅 초기에 S390_lowcore.stfle_fac_list[]에 저장됨 */
static const int stfl_bits[6] = { 0, 2, 17, 42, 49, 76 };
int i;
for (i = 0; i < ARRAY_SIZE(stfl_bits); i++) {
if (test_facility(stfl_bits[i]))
pr_debug("Facility bit %d set\n", stfl_bits[i]);
}
/* 벡터 지원 확인 (비트 129) */
if (test_facility(129))
elf_hwcap |= HWCAP_S390_VX;
/* 벡터 확장 확인 (비트 134) */
if (test_facility(134))
elf_hwcap |= HWCAP_S390_VXD | HWCAP_S390_VXE;
}
/* test_facility() 매크로: 비트 N이 설정되었는지 확인 */
/* stfle_fac_list[N/64] & (1UL << (63 - N%64)) */
STSI — 시스템 정보 및 토폴로지
STSI(Store System Information)는 LPAR 구성, CPU 토폴로지, 가상화 레이어 정보를 제공합니다.
Function Code(FC)와 Selector 1/2 조합으로 다양한 정보 블록을 선택합니다.
test_facility(N) 매크로로 개별 비트를 검사합니다.
두 방식 모두 "비트 하나 = 기능 하나"라는 동일한 의미론을 따릅니다.
arch/s390/kernel/processor.c—setup_hwcaps(),cpuinfo_S390proc 출력arch/s390/kernel/setup.c— 부팅 초기 STIDP/STFLE 호출,S390_lowcore초기화arch/s390/include/asm/facility.h—test_facility(),stfle_fac_list정의arch/s390/include/asm/processor.h—struct cpuid,get_cpu_id()인라인 어셈블리
/proc/cpuinfo의 features: 행이 STFLE 비트맵을 인간이 읽기 쉬운 문자열로 변환한 결과입니다.
LoongArch CPU 식별
LoongArch는 중국 룽신(Loongson)이 설계한 RISC ISA로, 2021년 리눅스 5.19에 메인라인 병합되었습니다.
CPU 기능 열거는 CPUCFG 명령어 하나로 통일되어 있으며, x86 CPUID의 leaf 인덱스 방식과 구조적으로 가장 닮아 있습니다.
CPUCFG 명령어 동작 원리
cpucfg rd, rj 명령어는 rj 레지스터의 값을 인덱스(워드 번호)로 사용하여 해당 구성 워드를 rd에 복사합니다.
인덱스 0부터 시작하며, 정의되지 않은 인덱스를 읽으면 0이 반환됩니다.
| 인덱스 | 워드 이름 | 주요 필드 / 비트 |
|---|---|---|
| 0x0 | PRID | Company[31:16], ProcType[15:8], Rev[7:0] — 프로세서 ID |
| 0x1 | 아키텍처 기능 | IOCSR[0], IOCSR32[1], LAMO[2], FP[3], FPD[4], LSX[6], LASX[7], Crypto[8], LVZ[9], LBT-X86[10], LBT-ARM[11], LBT-MIPS[12] |
| 0x2 | 구현 기능 | FP64[1], UnalignMem[3], PageSizeSupported[5:0] 등 마이크로아키텍처 옵션 |
| 0x3 | 캐시 정보 0 | I-캐시 레벨 수, 라인 크기, 연관도 |
| 0x4 | 캐시 정보 1 | D-캐시 레벨 수, 라인 크기, 연관도 |
| 0x5 | 캐시 정보 2 | L2 캐시 파라미터 |
| 0x6 | 캐시 정보 3 | L3 캐시 파라미터 |
| 0x10 | 성능 특성 0 | 디코드 폭, 실행 유닛 수 |
| 0x11–0x14 | 성능 특성 1–4 | TLB 엔트리 수, 분기 예측 버퍼(Buffer) 크기 등 |
/* arch/loongarch/kernel/cpu-probe.c — CPUCFG 기반 기능 탐지 */
static void cpu_probe_loongarch(struct cpuinfo_loongarch *c)
{
uint32_t config;
/* 워드 0: PRID 읽기 */
config = read_cpucfg(LOONGARCH_CPUCFG0);
c->processor_id = config;
/* 워드 1: 아키텍처 기능 비트맵 */
config = read_cpucfg(LOONGARCH_CPUCFG1);
if (config & CPUCFG1_FP)
set_isa(c, LOONGARCH_ISA_FP32);
if (config & CPUCFG1_LSX)
set_isa(c, LOONGARCH_ISA_LSX); /* 128비트 SIMD */
if (config & CPUCFG1_LASX)
set_isa(c, LOONGARCH_ISA_LASX); /* 256비트 SIMD */
if (config & CPUCFG1_CRYPTO)
set_isa(c, LOONGARCH_ISA_CRYPTO);
/* elf_hwcap 설정 */
if (cpu_has_fpu())
cpu_set_fpu_caps(c);
if (cpu_has_lsx())
elf_hwcap |= HWCAP_LOONGARCH_LSX;
if (cpu_has_lasx())
elf_hwcap |= HWCAP_LOONGARCH_LASX;
}
/* read_cpucfg() 인라인 어셈블리 래퍼 */
static inline uint32_t read_cpucfg(uint32_t reg)
{
uint32_t __res;
__asm__ volatile(
"cpucfg %0, %1\n\t"
: "=r"(__res) : "r"(reg)
);
return __res;
}
HWCAP 및 커널 소스
LoongArch의 elf_hwcap은 CPUCFG 워드 1 비트에서 직접 파생됩니다.
/proc/cpuinfo의 Features: 행이 이를 반영하며, 사용자 공간은 getauxval(AT_HWCAP)으로 동일 정보에 접근합니다.
rj에 인덱스를 넣는 cpucfg rd, rj로 대응됩니다.
반환 값도 32비트 워드 단위라는 점에서 x86 EAX/EBX/ECX/EDX 각각과 동일한 크기입니다.
커널 코드는 arch/loongarch/kernel/cpu-probe.c의 cpu_probe()에 집중되어 있으며,
CPU 정보 구조체는 struct cpuinfo_loongarch(arch/loongarch/include/asm/cpu-info.h)입니다.
SPARC CPU 식별 (개요)
SPARC(sun4v/sun4u) 리눅스는 현재 유지보수 모드로, 새로운 하드웨어 지원이 추가되지 않습니다.
CPU 식별은 특권 레지스터 %ver와 sun4v 하이퍼바이저 제공 Machine Description을 통해 이루어집니다.
VER 레지스터 구조
rdpr %ver, %reg 명령어로 64비트 버전 레지스터를 읽습니다.
이 레지스터는 특권 레벨(SPARC V9 기준 트랩 레벨 0 이상)에서만 접근 가능하므로,
부팅 초기 커널 초기화 단계에서 한 번 읽어 저장해 둡니다.
| 비트 범위 | 필드명 | 설명 |
|---|---|---|
| 63–48 | Manuf | 제조사 코드 (예: 0x003E = Sun/Oracle) |
| 47–32 | Impl | 구현(칩) 번호 (예: UltraSPARC T4 = 0x0091) |
| 31–24 | Mask | 마스크(스테핑) 번호 |
| 15–8 | MaxTL | 최대 트랩 레벨 (일반적으로 6) |
| 4–0 | MaxWin | 레지스터 윈도우 수 − 1 (일반적으로 7) |
/* arch/sparc/kernel/cpu.c — sun4v CPU 탐지 */
void __init sun4v_cpu_probe(void)
{
switch (sun4v_chip_type) {
case SUN4V_CHIP_NIAGARA1:
sparc_pmu_type = "niagara";
break;
case SUN4V_CHIP_NIAGARA2:
sparc_pmu_type = "niagara2";
break;
default:
printk(KERN_WARNING "Unknown sun4v chip type %d\n",
sun4v_chip_type);
break;
}
}
/* VER 레지스터 직접 읽기 (어셈블리) */
register unsigned long ver asm("g0");
__asm__("rdpr %%ver, %0" : "=r"(ver));
manuf = (ver >> 48) & 0xffff;
impl = (ver >> 32) & 0xffff;
mask = (ver >> 24) & 0xff;
sun4v Machine Description
UltraSPARC T 시리즈(Niagara) 이후의 sun4v 플랫폼은 하이퍼바이저가 제공하는
Machine Description(MD) 구조체를 통해 CPU 토폴로지, 캐시 크기, 지원 기능을 전달합니다.
MD는 DAG(방향성 비순환 그래프) 형태의 노드-속성 구조로, 커널은
arch/sparc/kernel/mdesc.c의 mdesc_scan()으로 파싱합니다.
arch/sparc/)는 리눅스 커널에서 유지보수 모드(maintenance mode)로 분류됩니다.
새로운 기능 추가나 새 하드웨어 지원은 사실상 중단되었으며, 보안 패치와 빌드 수정만 반영됩니다.
Oracle SPARC M8 이후 세대는 리눅스 메인라인에 공식 지원되지 않습니다.
프로덕션 환경에서는 Solaris 또는 OpenBSD/sparc64를 권장합니다.
Linux 커널 통합 CPU 추상화
Linux 커널은 x86의 CPUID, ARM64의 MRS, RISC-V의 CSR 등 아키텍처마다 서로 다른 CPU 식별 메커니즘을 단일한 추상화 레이어로 통합합니다.
이 레이어를 통해 커널 상위 계층과 사용자 공간은 아키텍처를 의식하지 않고 CPU 기능을 조회하고 활용할 수 있습니다.
/proc/cpuinfo 생성 메커니즘
/proc/cpuinfo는 각 아키텍처가 독립적으로 구현한 show_cpuinfo() 콜백(Callback)을 통해 생성됩니다.
이 함수는 struct seq_operations 인터페이스를 통해 /proc 파일시스템(Filesystem)에 등록되며,
커널 부팅 시 수집된 CPU 정보를 아키텍처별 형식으로 출력합니다.
| 아키텍처 | 구현 파일 | 핵심 함수 | 기능 플래그 필드명 | 정보 수집 시점 |
|---|---|---|---|---|
| x86 | arch/x86/kernel/cpu/proc.c |
show_cpuinfo() |
flags:, bugs: |
identify_cpu() (부팅 초기) |
| ARM64 | arch/arm64/kernel/cpuinfo.c |
c_show() |
Features: |
cpuinfo_store_cpu() |
| RISC-V | arch/riscv/kernel/proc.c |
c_show() |
isa: |
DT 파싱 + SBI 호출 |
| PowerPC | arch/powerpc/kernel/setup-common.c |
show_cpuinfo() |
cpu:, clock: |
PVR 레지스터 + DT |
| S390x | arch/s390/kernel/processor.c |
show_cpuinfo() |
features: |
stfle() (STFLE 명령) |
| LoongArch | arch/loongarch/kernel/proc.c |
c_show() |
Features: |
cpu_probe() |
x86의 경우 flags: 필드에는 하드웨어가 실제로 지원하는 기능과 커널이 소프트웨어적으로 에뮬레이션하는 기능이 혼합되어 출력됩니다.
bugs: 필드는 커널이 알고 있는 CPU 버그(예: Spectre, Meltdown 관련)를 나타냅니다.
/* arch/x86/kernel/cpu/proc.c */
static int show_cpuinfo(struct seq_file *m, void *v)
{
struct cpuinfo_x86 *c = v;
unsigned int i;
seq_printf(m, "processor\t: %u\n", c->cpu_index);
seq_printf(m, "vendor_id\t: %s\n", c->x86_vendor_id[0]
? c->x86_vendor_id : "unknown");
/* flags: 필드 — 커널 비트 배열에서 문자열로 변환 */
seq_puts(m, "flags\t\t:");
for (i = 0; i < 32 * NCAPINTS; i++) {
if (cpu_has(c, i) && x86_cap_flags[i] != NULL)
seq_printf(m, " %s", x86_cap_flags[i]);
}
seq_putc(m, '\n');
/* bugs: 필드 — 알려진 취약점/버그 목록 */
seq_puts(m, "bugs\t\t:");
for (i = 0; i < NBUGINTS * 32; i++) {
if (cpu_bug(c, i) && x86_bug_flags[i])
seq_printf(m, " %s", x86_bug_flags[i]);
}
seq_putc(m, '\n');
return 0;
}
| 정보 항목 | x86 | ARM64 | RISC-V | PowerPC | S390x |
|---|---|---|---|---|---|
| CPU 모델/이름 | model name |
CPU implementer, CPU part |
uarch |
cpu |
machine |
| 기능 플래그 | flags |
Features |
isa |
(없음, DT 참조) | features |
| 동작 주파수 | cpu MHz |
CPU max MHz |
hart + 별도 |
clock |
bogomips per cpu |
| 캐시 크기 | cache size |
(없음) | (없음) | L1 d cache |
(없음) |
| 버그/취약점 | bugs |
(없음) | (없음) | (없음) | (없음) |
| 프로세서 번호 | processor |
processor |
processor |
processor |
processor |
AT_HWCAP / AT_HWCAP2: ELF 보조 벡터
AT_HWCAP은 커널이 사용자 공간 프로그램을 실행할 때 ELF 보조 벡터(auxiliary vector)를 통해 전달하는 하드웨어 기능 비트마스크입니다.
이는 /proc/cpuinfo보다 훨씬 빠르고 안정적인 기능 탐지 방법으로, 동적 링커(Linker)와 런타임 라이브러리가 주로 활용합니다.
| 아키텍처 | HWCAP 비트 예시 | 정의 위치 | AT_HWCAP2 지원 |
|---|---|---|---|
| x86_64 | HWCAP_FPU, HWCAP_MMX, HWCAP_SSE |
arch/x86/include/asm/elf.h |
없음 (cpuid 직접 사용) |
| ARM64 | HWCAP_FP, HWCAP_ASIMD, HWCAP_SVE, HWCAP_SHA3 |
arch/arm64/include/uapi/asm/hwcap.h |
O (HWCAP2_SVE2 등) |
| RISC-V | COMPAT_HWCAP_ISA_I, HWCAP_ISA_M, HWCAP_ISA_A |
arch/riscv/include/uapi/asm/hwcap.h |
O (확장 ISA) |
| PowerPC | PPC_FEATURE_HAS_ALTIVEC, PPC_FEATURE_HAS_VSX |
arch/powerpc/include/uapi/asm/cputable.h |
O (PPC_FEATURE2_*) |
| S390x | HWCAP_S390_ESAN3, HWCAP_S390_VXRS |
arch/s390/include/uapi/asm/hwcap.h |
없음 |
/* 사용자 공간: getauxval()을 이용한 HWCAP 조회 */
#include <sys/auxv.h>
#include <asm/hwcap.h>
void detect_cpu_features(void)
{
unsigned long hwcap = getauxval(AT_HWCAP);
unsigned long hwcap2 = getauxval(AT_HWCAP2);
#if defined(__aarch64__)
/* ARM64: SVE 지원 여부 확인 */
if (hwcap & HWCAP_SVE)
printf("SVE 지원: 벡터 길이 %lu bytes\n",
getauxval(AT_VECTOR_SIZE_SVE));
if (hwcap2 & HWCAP2_SVE2)
printf("SVE2 지원\n");
if (hwcap & HWCAP_AES)
printf("AES 하드웨어 가속 사용 가능\n");
#elif defined(__riscv)
/* RISC-V: 기본 ISA 확장 확인 */
if (hwcap & COMPAT_HWCAP_ISA_V)
printf("RISC-V Vector 확장 지원\n");
if (hwcap & COMPAT_HWCAP_ISA_F)
printf("단정밀도 부동소수점(F) 지원\n");
#endif
}
ALTERNATIVE() 매크로: 런타임 코드 패칭
ALTERNATIVE() 매크로는 커널이 부팅 시 CPU 기능을 확인한 후 최적화된 코드로 교체하는 런타임 패칭 메커니즘입니다.
이를 통해 단일 커널 이미지가 기능이 다른 여러 CPU에서 최적 성능을 발휘할 수 있습니다.
/* arch/x86/include/asm/alternative.h */
/*
* ALTERNATIVE(oldinstr, newinstr, feature)
* - oldinstr: 기본 명령어 (기능 없을 때 사용)
* - newinstr: 대체 명령어 (기능 있을 때 패칭)
* - feature : X86_FEATURE_* 비트
*/
#define ALTERNATIVE(oldinstr, newinstr, feature) \
ALTERNATIVE_2(oldinstr, newinstr, feature, \
newinstr, feature)
/* 실사용 예: mfence vs lfence 선택 */
static inline void memory_barrier_before_unstuff(void)
{
asm volatile(
ALTERNATIVE("mfence", "lfence", X86_FEATURE_LFENCE_RDTSC)
: : : "memory"
);
}
/* arch/arm64/include/asm/alternative.h */
/*
* ARM64 ALTERNATIVE: 시스템 레지스터 기반 기능 비트 참조
* ARM64_HAS_* 상수는 idreg 비교 결과를 나타냄
*/
SYM_FUNC_START(__memcpy)
alternative_if ARM64_HAS_NO_HW_PREFETCH
prfm pldl1keep, [x1, #448]
alternative_else_nop_endif
/* ... 실제 memcpy 구현 ... */
SYM_FUNC_END(__memcpy)
커널은
apply_alternatives()를 start_kernel() 초기에 호출하여 모든 __alt_instructions 섹션의 패치를 일괄 적용합니다. 패칭은 단방향이며, 이후 CPU 핫플러그(Hotplug)로 추가된 CPU가 더 적은 기능을 가지면 문제가 발생할 수 있어 커널은 이를 cpu_die_early()로 처리합니다. RISC-V는 riscv_cpufeature_patch_func()를 통해 동일한 패턴을 구현합니다.
x86은 CPUID 비트를 직접 참조하는 단순한 비트마스크 방식인 반면, ARM64는 시스템 레지스터(
ID_AA64ISAR0_EL1 등)의 특정 필드 값을 비교하는 방식을 사용합니다. 이로 인해 ARM64의 ARM64_HAS_* 상수는 단순 비트 번호가 아니라 레지스터, 필드, 최솟값을 인코딩한 복합 값입니다(arm64_ftr_bits[] 테이블 참조).
아키텍처 간 CPU 식별 비교
각 아키텍처는 하드웨어 설계 철학과 역사적 배경에 따라 근본적으로 다른 CPU 식별 메커니즘을 채택했습니다. 이 차이를 이해하는 것은 이식 가능한 커널 코드 작성과 크로스 아키텍처 성능 최적화에 필수적입니다.
종합 비교 테이블
| 아키텍처 | 명령어 | 레지스터/소스 | 권한 수준 | 커널 파일 | 핵심 구조체 | HWCAP 변수 |
|---|---|---|---|---|---|---|
| x86/x86_64 | CPUID |
EAX(leaf)/ECX(subleaf) → EAX,EBX,ECX,EDX | 비권한 (Ring 0~3) | arch/x86/kernel/cpu/common.c |
struct cpuinfo_x86 |
ELF_HWCAP (매크로, 제한적) |
| ARM64 | MRS Xt, sysreg |
ID_AA64ISAR0_EL1, ID_AA64MMFR0_EL1 등 30여 개 | EL1 이상 (커널) | arch/arm64/kernel/cpufeature.c |
struct cpuinfo_arm64 |
elf_hwcap, elf_hwcap2 |
| RISC-V | CSR(misa) + DT + SBI |
mvendorid, marchid, mimpid, misa | M-mode(CSR), S-mode(SBI) | arch/riscv/kernel/cpufeature.c |
struct riscv_cpuinfo |
elf_hwcap |
| LoongArch | CPUCFG rd, rj |
레지스터 번호 rj → 결과 rd (32비트) | 비권한 (PLV3) | arch/loongarch/kernel/cpu-probe.c |
struct cpuinfo_loongarch |
elf_hwcap |
| PowerPC | mfspr PVR + DT |
PVR SPR + ibm,pa-features DT 속성 |
권한 필요 (SPR) | arch/powerpc/kernel/cputable.c |
struct cpu_spec |
elf_hwcap, elf_hwcap2 |
| S390x | STFLE |
512비트 비트맵 배열 (비트 번호 = 기능) | 권한 필요 | arch/s390/kernel/processor.c |
S390_lowcore.stfle_fac_list |
elf_hwcap (제한적) |
| MIPS | MFC0 Config |
CP0 Config0~Config5 레지스터 | 권한 필요 (CP0) | arch/mips/kernel/cpu-probe.c |
struct cpuinfo_mips |
elf_hwcap |
설계 철학 심층 비교
각 아키텍처의 CPU 식별 방식은 그 아키텍처가 탄생한 시대와 대상 시장을 반영합니다. 단순함을 추구한 x86, 엄격한 버전 관리를 선택한 ARM64, 개방성을 위해 3중 소스를 도입한 RISC-V, 그리고 비트 번호 자체가 기능 ID인 S390x까지 각기 다른 철학을 지닙니다.
| 아키텍처 | 핵심 설계 원칙 | 장점 | 단점/주의점 | 유사 아키텍처 |
|---|---|---|---|---|
| x86 | 단일 명령, 리프 인덱스, 비권한 | 사용자 공간에서 직접 호출 가능, 수십 년간 하위 호환 유지 | 리프가 700개 이상으로 파편화, subleaf 중첩 복잡 | LoongArch (CPUCFG) |
| ARM64 | 시스템 레지스터 필드 버전 관리 | 4비트 필드로 기능 레벨 표현 가능, idreg 위조 감지 가능 | EL1 전용, 사용자 공간 직접 조회 불가, 레지스터 수가 많음 | 없음 (독자 방식) |
| RISC-V | 3중 소스(CSR+DT+SBI) 협력 | 확장성 최고, 비표준 확장도 DT로 기술 가능 | 구현 복잡도 높음, 소스 간 불일치 가능성 | 없음 (오픈 ISA 특유) |
| LoongArch | CPUID와 동일한 비권한 단일 명령 | x86 개발자에게 친숙, 빠른 학습 곡선 | 생태계 역사가 짧아 기능 수가 적음 | x86 (CPUID) |
| PowerPC | PVR 모델 번호 + 펌웨어(DT) 협력 | IBM 파워 서버의 동적 기능 광고 지원 | 커널에 CPU 모델별 매핑 테이블 필요, 유지 비용 | 일부 MIPS |
| S390x | 비트 번호 = 기능 ID (STFLE 비트맵) | 새 기능 추가 시 비트 하나만 할당, 극도로 단순한 API | 비트맵 크기 한계(현재 512비트), 메인프레임 전용 | 없음 (IBM 메인프레임 전용) |
/* 아키텍처별 CPU 식별 핵심 코드 비교 */
/* 1. x86: CPUID 명령어 직접 호출 (비권한) */
static void cpuid(u32 leaf, u32 *eax, u32 *ebx, u32 *ecx, u32 *edx)
{
asm volatile("cpuid"
: "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
: "a"(leaf), "c"(0));
}
/* 2. ARM64: MRS로 시스템 레지스터 읽기 (EL1 전용) */
static inline u64 read_id_aa64isar0_el1(void)
{
u64 val;
asm volatile("mrs %0, id_aa64isar0_el1" : "=r"(val));
return val; /* 4비트 필드 여러 개로 구성 */
}
/* AES 기능: bits[7:4] != 0 이면 지원 */
#define ID_AA64ISAR0_AES_SHIFT 4
#define ID_AA64ISAR0_AES_MASK 0xFULL
/* 3. RISC-V: misa CSR (M-mode) + DT 조합 */
static unsigned long riscv_isa_read(void)
{
unsigned long misa;
/* M-mode에서만 접근 가능 */
asm volatile("csrr %0, misa" : "=r"(misa));
return misa; /* 비트 A=Atomic, M=Mul/Div, F=Float... */
}
/* 4. S390x: STFLE (비트 번호가 기능 ID) */
static int __stfle(u64 *list, int doublewords)
{
register unsigned long __nr asm("0") = doublewords - 1;
asm volatile(".insn s,0xb2b00000,%0" /* STFLE opcode */
: "=Q"(*list), "+d"(__nr) : : "cc");
return __nr + 1; /* 기능 비트 45 = "vector" 지원 여부 */
}
LoongArch의
CPUCFG 명령어는 x86 CPUID와 거의 동일한 인터페이스를 제공합니다. 레지스터 번호(rj)로 정보 종류를 선택하고, 비권한 상태에서 실행 가능하며, 결과가 32비트 레지스터(rd)에 반환됩니다. 설계진이 x86 생태계 개발자들의 빠른 적응을 의도했음을 알 수 있습니다. 다만 현재(v1.0) 정의된 레지스터 수가 x86보다 훨씬 적어 향후 확장이 예상됩니다.
ARM64 시스템 레지스터의 각 4비트 필드는 단순히 "있다/없다"가 아니라 기능 레벨(0=없음, 1=기본, 2=강화...)을 나타냅니다. 따라서
cpu_have_feature()는 내부적으로 arm64_ftr_bits[] 테이블을 조회하여 필드 값과 최솟값을 비교합니다. 단순히 비트가 0이 아님을 확인하는 것만으로는 충분하지 않습니다. 또한 커널은 SMP 시스템에서 모든 CPU의 idreg 값 중 "안전한 최솟값"만을 system_cpucaps[]에 기록합니다.
크로스 아키텍처 기능 탐지 실전
실제 코드에서 CPU 기능을 탐지할 때는 커널 내부 API, 사용자 공간 HWCAP, ifunc 리졸버, 그리고 /proc/cpuinfo 파싱이라는 네 가지 방법 중 맥락에 따라 적절한 것을 선택해야 합니다.
이식성과 성능을 동시에 확보하려면 각 방법의 특성과 한계를 명확히 이해해야 합니다.
커널 내부 기능 탐지 패턴
커널 드라이버와 서브시스템 코드에서는 런타임 기능 탐지와 컴파일 타임 가드를 조합하여 사용합니다. 불필요한 런타임 오버헤드를 피하면서 다중 아키텍처를 지원하는 것이 목표입니다.
/*
* 커널 내부 크로스 아키텍처 기능 탐지 패턴
* arch/x86, arch/arm64, arch/riscv 모두 동일한 상위 API 지향
*/
/* ── 패턴 1: 컴파일 타임 아키텍처 가드 ─────────────────── */
#ifdef CONFIG_X86
if (boot_cpu_has(X86_FEATURE_AVX2)) {
/* x86 AVX2 최적화 경로 */
aes_encrypt_avx2(ctx, dst, src);
} else {
aes_encrypt_generic(ctx, dst, src);
}
#elif defined(CONFIG_ARM64)
if (cpu_have_feature(cpu_feature(AES))) {
/* ARM64 AES 하드웨어 가속 */
ce_aes_encrypt(ctx, dst, src);
} else {
aes_encrypt_generic(ctx, dst, src);
}
#elif defined(CONFIG_RISCV)
if (riscv_isa_extension_available(NULL, ZKN)) {
/* RISC-V Zkn 암호화 확장 */
zkn_aes_encrypt(ctx, dst, src);
} else {
aes_encrypt_generic(ctx, dst, src);
}
#else
aes_encrypt_generic(ctx, dst, src);
#endif
/* ── 패턴 2: IS_ENABLED()로 설정 확인 ─────────────────── */
static void init_crypto_engine(void)
{
/* IS_ENABLED()는 컴파일 타임 상수 → dead code 제거됨 */
if (IS_ENABLED(CONFIG_CRYPTO_AES_ARM64_CE))
arm64_ce_aes_register();
if (IS_ENABLED(CONFIG_CRYPTO_AES_NI_INTEL))
aesni_intel_register();
}
/* ── 패턴 3: static_cpu_has() — 가장 빠른 런타임 경로 ─── */
static inline void clflush_cache_range(void *addr, unsigned int size)
{
/*
* static_cpu_has()는 ALTERNATIVE() 기반:
* 부팅 후 해당 분기가 무조건 점프 또는 NOP으로 패칭됨
* → 런타임 조건 분기 비용 0
*/
if (static_cpu_has(X86_FEATURE_CLFLUSHOPT))
clflushopt_cache_range(addr, size);
else
clflush_cache_range_opt(addr, size);
}
커널 내부에서는 상황에 따라 세 가지 변형을 구분하여 사용합니다.
boot_cpu_has()는 BSP(부트 CPU)의 기능만 참조하며 초기화 코드에서 안전하게 쓸 수 있습니다. cpu_has()는 per-CPU 정보를 참조하여 CPU마다 다를 수 있는 이기종 시스템에서 정확합니다. static_cpu_has()는 ALTERNATIVE()로 구현되어 런타임 분기 비용이 없지만, 코드 섹션이 읽기 전용(Read-Only)이 된 이후에만 정확합니다. 핫패스(hot path) 코드에서는 반드시 static_cpu_has()를 사용하십시오.
사용자 공간 이식성 탐지: getauxval 패턴
/*
* 이식 가능한 사용자 공간 CPU 기능 탐지
* - /proc/cpuinfo 파싱보다 훨씬 빠름 (파일 I/O 없음)
* - 커널 버전과 무관하게 동작 (ABI 안정)
* - getauxval(AT_HWCAP)은 O(1) — 보조 벡터 선형 탐색
*/
#include <sys/auxv.h>
#include <stdint.h>
#include <stdbool.h>
/* 아키텍처별 헤더 조건부 포함 */
#if defined(__aarch64__)
#include <asm/hwcap.h>
#elif defined(__riscv)
#include <asm/hwcap.h>
#endif
typedef struct {
bool has_aes;
bool has_sha2;
bool has_simd; /* NEON/SSE/VMX/V */
bool has_crc32;
} cpu_caps_t;
static cpu_caps_t detect_cpu_caps(void)
{
cpu_caps_t caps = {0};
unsigned long hwcap = getauxval(AT_HWCAP);
unsigned long hwcap2 = getauxval(AT_HWCAP2);
(void)hwcap2; /* 아키텍처에 따라 미사용 경고 억제 */
#if defined(__aarch64__)
caps.has_aes = !!(hwcap & HWCAP_AES);
caps.has_sha2 = !!(hwcap & HWCAP_SHA2);
caps.has_simd = !!(hwcap & HWCAP_ASIMD);
caps.has_crc32 = !!(hwcap & HWCAP_CRC32);
#elif defined(__x86_64__)
/*
* x86: AT_HWCAP이 제한적 → CPUID 직접 사용 또는
* __builtin_cpu_supports() (GCC/Clang 내장)
*/
caps.has_aes = (int)__builtin_cpu_supports("aes");
caps.has_sha2 = (int)__builtin_cpu_supports("sha");
caps.has_simd = (int)__builtin_cpu_supports("avx2");
caps.has_crc32 = (int)__builtin_cpu_supports("sse4.2");
#elif defined(__riscv)
/* RISC-V: ISA 확장 비트 */
caps.has_simd = !!(hwcap & COMPAT_HWCAP_ISA_V);
caps.has_aes = false; /* Zkn은 hwcap2로 이동 예정 */
#elif defined(__powerpc64__)
caps.has_aes = !!(hwcap2 & PPC_FEATURE2_VEC_CRYPTO);
caps.has_simd = !!(hwcap & PPC_FEATURE_HAS_ALTIVEC);
#endif
return caps;
}
/* ── glibc ifunc 리졸버: 로드 시 최적 구현 선택 ────────── */
static void (*resolve_memcpy(void))(void *, const void *, size_t)
{
unsigned long hwcap = getauxval(AT_HWCAP);
#if defined(__aarch64__)
if (hwcap & HWCAP_SVE)
return __memcpy_aarch64_sve;
if (hwcap & HWCAP_ASIMD)
return __memcpy_aarch64_simd;
#elif defined(__x86_64__)
if (__builtin_cpu_supports("avx512f"))
return __memcpy_avx512;
if (__builtin_cpu_supports("avx2"))
return __memcpy_avx2;
#endif
return __memcpy_generic;
}
/* ifunc 어트리뷰트로 로드 시 리졸버 자동 호출 */
void *memcpy(void *, const void *, size_t)
__attribute__((ifunc("resolve_memcpy")));
/proc/cpuinfo 파싱의 함정과 권장 대안
| 방법 | 속도 | 이식성 | 정확성 | 권장 사용처 | 주요 문제점 |
|---|---|---|---|---|---|
getauxval(AT_HWCAP) |
O(1), 파일 I/O 없음 | Linux 전체 (glibc/musl) | 커널이 보증 | 사용자 공간 기능 탐지의 기본 | x86는 HWCAP이 제한적 |
__builtin_cpu_supports() |
O(1), 내부 캐시 | GCC/Clang, x86/ARM64 | 컴파일러 구현 의존 | x86 상세 기능 (AVX512 등) | 컴파일러마다 지원 범위 다름 |
| CPUID 직접 호출 | O(1), 직접 명령 | x86/LoongArch 전용 | 하드웨어 직접 보증 | x86 저수준 라이브러리 | 아키텍처 비이식, 가상화 환경 주의 |
/proc/cpuinfo 파싱 |
느림 (파일 I/O + 문자열 파싱) | Linux 전체 | 필드명이 아키텍처마다 다름 | 디버깅(Debugging), 사람이 읽는 도구 | 아키텍처마다 필드명 다름, 느림 |
| ifunc 리졸버 | 런타임 0 오버헤드 | glibc 지원 플랫폼 | 로드 시 한 번만 결정 | 라이브러리 내부 최적 구현 선택 | 가상 함수 포인터 간접 호출 불가 |
/proc/self/auxv 읽기 |
파일 I/O 필요 | Linux 전체 | 커널이 보증 | getauxval 없는 환경 (Go 등) | 바이너리 파싱 필요, 번거로움 |
1. 필드명이 아키텍처마다 다릅니다. x86의 기능 필드는
flags:이지만 ARM64는 Features:, RISC-V는 isa:입니다. 대소문자도 다르므로 단순 문자열 검색은 반드시 아키텍처별로 분기해야 합니다.2. 기능 이름이 아키텍처마다 다릅니다. ARM64
aes와 x86 aes는 같은 이름이지만, ARM64 sha1과 x86 sha_ni는 다른 이름입니다.3. x86의
flags:는 소프트웨어 에뮬레이션 기능도 포함합니다. 예를 들어 rep_good은 하드웨어 기능이 아니라 커널이 설정한 힌트입니다.4. 가상화 환경에서
flags:는 하이퍼바이저가 조작할 수 있습니다. KVM이 특정 기능을 숨기거나 추가할 수 있으므로, 보안에 민감한 기능 탐지에는 AT_HWCAP이 더 신뢰할 수 있습니다.5. 멀티라인 파싱이 필요합니다.
/proc/cpuinfo는 CPU 코어마다 동일한 정보를 반복하므로 첫 번째 블록만 파싱하거나 중복 제거 로직이 필요합니다.
커널 코드라면
cpu_have_feature() / static_cpu_has() / riscv_isa_extension_available() 등 아키텍처별 API를 #ifdef CONFIG_*로 감싸 사용합니다. 사용자 공간 라이브러리라면 ARM64/RISC-V/PowerPC는 getauxval(AT_HWCAP)를 우선 사용하고, x86은 __builtin_cpu_supports() 또는 CPUID를 직접 호출합니다. /proc/cpuinfo는 디버깅 도구나 사람이 읽는 출력에만 사용하고, 성능에 민감한 경로에서는 절대 피해야 합니다. 다중 아키텍처 지원이 필요한 프로젝트라면 google/cpu_features와 같은 이식성 라이브러리 사용을 검토하십시오.
하이퍼바이저 Leaf (0x40000000+)
x86 CPU는 0x40000000~0x4FFFFFFF 범위를 하이퍼바이저 전용 CPUID 공간으로 예약합니다. 하이퍼바이저는 이 범위를 사용해 자신의 존재와 기능을 게스트 OS에 알립니다. Leaf 1 ECX bit 31이 설정되면 하이퍼바이저가 존재하며, Leaf 0x40000000에서 하이퍼바이저 벤더 이름을 확인할 수 있습니다.
/* arch/x86/kvm/cpuid.c — KVM CPUID 에뮬레이션 */
static void kvm_set_cpuid_entry_hypervisor(struct kvm_cpuid_entry2 *entry)
{
switch (entry->function) {
case 0x40000000:
/* KVM 최대 leaf + 벤더 문자열 "KVMKVMKVM\0\0\0" */
entry->eax = KVM_CPUID_FEATURES; /* 최대 하이퍼바이저 leaf = 0x40000001 */
memcpy(&entry->ebx, "KVMK", 4);
memcpy(&entry->ecx, "VMKV", 4);
memcpy(&entry->edx, "M\0\0\0", 4);
break;
case 0x40000001:
/* KVM 파라가상화 기능 플래그 */
entry->eax = 0;
if (kvm_clock_supported())
entry->eax |= (1 << KVM_FEATURE_CLOCKSOURCE2);
if (kvm_steal_time_supported())
entry->eax |= (1 << KVM_FEATURE_STEAL_TIME);
if (kvm_async_pf_supported())
entry->eax |= (1 << KVM_FEATURE_ASYNC_PF);
break;
}
}
/* VMCS/VMCB에서 게스트 CPUID 결과 제어 */
int kvm_emulate_cpuid(struct kvm_vcpu *vcpu)
{
u32 eax, ebx, ecx, edx;
kvm_cpuid(vcpu, &eax, &ebx, &ecx, &edx, false);
/* 게스트 레지스터에 결과 저장 후 RIP += 2 */
kvm_rax_write(vcpu, eax); kvm_rbx_write(vcpu, ebx);
kvm_rcx_write(vcpu, ecx); kvm_rdx_write(vcpu, edx);
return kvm_skip_emulated_instruction(vcpu);
}
| 하이퍼바이저 | 벤더 문자열 | 기능 Leaf |
|---|---|---|
| KVM (Linux) | KVMKVMKVM\0\0\0 | 0x40000001 (KVM 기능 플래그) |
| Microsoft Hyper-V | Microsoft Hv | 0x40000002~0x40000006 |
| VMware | VMwareVMware | 0x40000010 (포트 I/O 기반) |
| Xen | XenVMMXenVMM | 0x40000001 (Xen 인터페이스 버전) |
| QEMU TCG | TCGTCGTCGTCG | 0x40000001 |
| 비트 | 상수 이름 | 설명 |
|---|---|---|
| 0 | KVM_FEATURE_CLOCKSOURCE | KVM 클럭소스 v1 (MSR 기반) |
| 1 | KVM_FEATURE_NOP_IO_DELAY | I/O 딜레이 NOP 처리 (성능 개선) |
| 3 | KVM_FEATURE_CLOCKSOURCE2 | KVM 클럭소스 v2 (shared memory 기반, 권장) |
| 4 | KVM_FEATURE_ASYNC_PF | 비동기 페이지 폴트 (게스트 블로킹 감소) |
| 5 | KVM_FEATURE_STEAL_TIME | Steal Time 통계 (스케줄러(Scheduler) 공정성(Fairness) 개선) |
| 9 | KVM_FEATURE_PV_TLB_FLUSH | 파라가상화 TLB 플러시 (VMEXIT 횟수 감소) |
| 11 | KVM_FEATURE_PV_SEND_IPI | 파라가상화 IPI 전송 |
| 13 | KVM_FEATURE_POLL_CONTROL | 게스트가 하이퍼바이저 폴링(Polling) 제어 가능 |
| 14 | KVM_FEATURE_PV_SCHED_YIELD | vCPU yield 힌트 전달 (오버커밋 환경 최적화) |
하이퍼바이저 탐지 방법: grep hypervisor /proc/cpuinfo로 Leaf 1 ECX bit 31을 확인합니다. cpuid -l 0x40000000 명령으로 벤더 문자열을 직접 읽을 수 있습니다. KVM은 /dev/kvm 존재 여부로도 탐지 가능하며, systemd-detect-virt 명령은 다수의 탐지 방법을 통합합니다.
참고 링크
- Intel 64 and IA-32 Architectures Software Developer Manuals (Intel SDM) — Vol. 2A의 CPUID 명령어 Leaf/Sub-leaf 전체 정의를 포함합니다
- AMD64 Architecture Programmer's Manual Vol. 3 — AMD 확장 CPUID Leaf (0x80000000+) 및 AMD 고유 기능 비트 정의입니다
- 커널 소스: arch/x86/include/asm/cpufeatures.h — 리눅스 커널의 CPU 피처 비트 정의 (X86_FEATURE_*) 전체 목록입니다
- 커널 소스: arch/x86/kernel/cpu/common.c — 부팅 시 CPUID 기반 CPU 초기화 및 피처 탐지 코드입니다
- 커널 소스: arch/x86/include/asm/cpufeature.h — boot_cpu_has(), static_cpu_has() 등 피처 검사 매크로입니다
- 커널 소스: arch/arm64/kernel/cpufeature.c — ARM64 시스템 레지스터 기반 CPU 피처 탐지 구현입니다
- 커널 소스: arch/arm64/include/asm/cpufeature.h — ARM64 cpus_have_const_cap() 등 피처 검사 인터페이스입니다
- 커널 문서: x86 CPU Information (Documentation/arch/x86/cpuinfo.rst) — /proc/cpuinfo 필드와 CPU 피처 플래그 설명입니다
- LWN.net: CPUID faulting and the CPUID count limit — CPUID faulting 메커니즘과 KVM 가상화 관련 논의입니다
- LWN.net: Reworking the x86 CPU capabilities — 커널 CPU 기능 탐지 리팩토링 히스토리입니다
- Arm Architecture Reference Manual for A-profile architecture (ARM ARM) — ARM64 ID 레지스터 (ID_AA64*) 필드 정의를 포함합니다
- 커널 소스: arch/x86/kernel/cpu/intel.c — Intel CPU 전용 초기화 및 마이크로코드 피처 탐지입니다
- 커널 소스: arch/x86/kernel/cpu/amd.c — AMD CPU 전용 초기화 및 확장 피처 탐지입니다
- 커널 문서: ARM64 CPU Feature Registers — ARM64 유저 공간 CPUID 에뮬레이션 (MRS 트래핑) 문서입니다
관련 문서
CPUID와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.