MSR 레지스터 (Model-Specific Register)
x86 MSR(Model-Specific Register) 가이드입니다. RDMSR/WRMSR 명령과 권한 모델, SYSCALL/APIC/PMU/보안/가상화(Virtualization) 계열 MSR, HWP/EPP·RAPL·APERF/MPERF 기반 전력·성능 제어, Intel/AMD 아키텍처 차이, MTRR/PAT 메모리 속성, Linux 커널 MSR API, Spectre 완화 관련 레지스터, KVM MSR 비트맵(Bitmap)과 msr-tools를 활용한 실전 디버깅(Debugging)까지 폭넓게 다룹니다.
핵심 요약
- MSR — Model-Specific Register. CPU 동작 모드, 성능, 전력, 보안을 제어하는 특수 레지스터입니다.
- RDMSR / WRMSR — MSR을 읽고 쓰는 특권 명령어. ECX에 MSR 번호를 지정합니다.
- 주요 MSR — TSC(타임스탬프), EFER(Long Mode), APIC_BASE, MTRR(메모리 타입), PAT 등이 있습니다.
- rdmsr / wrmsr 도구 — 사용자 공간(User Space)에서
/dev/cpu/N/msr을 통해 MSR을 읽고 쓸 수 있습니다.
단계별 이해
- MSR 개념 잡기 — 일반 레지스터(범용, 세그먼트)와 달리 MSR은 번호(인덱스)로 식별되는 특수 목적 레지스터입니다.
Intel SDM Vol.4에 수천 개의 MSR이 문서화되어 있습니다.
- 읽기 실습 —
sudo rdmsr 0x10으로 TSC(Time Stamp Counter) 값을 읽어봅니다.msr-tools패키지를 설치하고modprobe msr로 MSR 드라이버를 로드합니다. - 커널 API — 드라이버/커널 코드에서는
rdmsrl()/wrmsrl()함수로 MSR에 접근합니다.잘못된 MSR 쓰기는 시스템 크래시를 유발할 수 있으므로 주의가 필요합니다.
MSR 개요
MSR(Model-Specific Register)은 x86 프로세서에 내장된 특수 레지스터 집합으로, CPU의 동작 모드 제어, 성능 모니터링, 전력 관리, 보안 완화 등 다양한 기능을 담당합니다. "Model-Specific"이라는 이름처럼 원래는 CPU 모델마다 다른 레지스터 세트를 가졌으나, 현재는 많은 MSR이 Intel/AMD 간에 공통으로(architectural) 정의되어 있습니다.
MSR의 역사와 발전
- Intel Pentium (1993): RDMSR/WRMSR 명령어 및 TSC(Time Stamp Counter) 최초 도입
- Pentium Pro (P6, 1995): 성능 모니터링 카운터(PMC), MTRR 도입
- AMD K8 (2003): SYSCALL/SYSRET용 MSR, NX bit(IA32_EFER.NXE) 도입
- Intel Core 이후: Architectural Performance Monitoring, SpeedStep/HWP MSR 확대
- Spectre/Meltdown (2018~): IA32_SPEC_CTRL, IA32_PRED_CMD, IA32_ARCH_CAPABILITIES 등 보안 MSR 대거 추가
MSR 주소 공간(Address Space)
MSR은 32비트 주소 공간(0x00000000 ~ 0xFFFFFFFF)을 사용하며, 각 MSR은 64비트(EDX:EAX) 값을 가집니다. 주소 범위는 대략 다음과 같이 구분됩니다:
| 주소 범위 | 용도 | 예시 |
|---|---|---|
0x00000000 ~ 0x00001FFF | Architectural MSR | IA32_TSC, IA32_APIC_BASE |
0x00000100 ~ 0x000001FF | Fixed/PMC 영역 | IA32_MTRR*, PMC |
0x00000174 ~ 0x00000176 | SYSENTER MSR | IA32_SYSENTER_CS/ESP/EIP |
0x00000186 ~ 0x0000018F | 성능 이벤트 선택 | IA32_PERFEVTSEL0~7 |
0x000001A0 | 기능 제어 | IA32_MISC_ENABLE |
0x00000300 ~ 0x000003FF | Fixed Counter / PMC | IA32_FIXED_CTR0~2 |
0x00000400 ~ 0x00000477 | MC(Machine Check) 뱅크 | IA32_MCi_CTL/STATUS |
0x00000480 ~ 0x0000049F | VMX 기능 보고 | IA32_VMX_BASIC 등 |
0x00000800 ~ 0x000008FF | x2APIC | ICR, LVT 등 |
0xC0000000 ~ 0xC0001FFF | AMD/Long mode MSR | MSR_EFER, MSR_STAR, MSR_LSTAR |
0xC0010000 ~ 0xC001FFFF | AMD 전용 | HWCR, SYSCFG, NB_CFG |
접근 권한
RDMSR/WRMSR 명령어는 Ring 0(커널 모드)에서만 실행 가능합니다. 유저 공간에서 실행하면 #GP(General Protection) 예외가 발생합니다. 다만 RDTSC, RDTSCP, RDPMC 등 일부 전용 명령어는 CR4 비트 설정에 따라 Ring 3에서도 실행할 수 있습니다.
/dev/cpu/<n>/msr 장치 파일 또는 msr-tools 패키지의 rdmsr/wrmsr 유틸리티를 사용합니다. 단, CONFIG_X86_MSR=y로 커널이 빌드되어야 하며, CAP_SYS_RAWIO 권한이 필요합니다.RDMSR / WRMSR 명령어
명령어 형식
| 명령어 | 입력 | 출력 | 설명 |
|---|---|---|---|
RDMSR | ECX = MSR 주소 | EDX:EAX = 64비트 값 | MSR 읽기 |
WRMSR | ECX = MSR 주소, EDX:EAX = 값 | 없음 | MSR 쓰기 |
RDTSC | 없음 | EDX:EAX = TSC 값 | IA32_TSC 전용 읽기 |
RDTSCP | 없음 | EDX:EAX = TSC, ECX = TSC_AUX | 직렬화(Serialization)된 TSC 읽기 |
RDPMC | ECX = PMC 인덱스 | EDX:EAX = 카운터 값 | PMC 전용 읽기 |
직렬화와 순서 보장(Ordering)
WRMSR은 직렬화(serializing) 명령어로 분류되어, 이전의 모든 명령어가 완료된 후에 실행됩니다. 반면 RDMSR은 직렬화 명령어가 아니므로, 정확한 순서 보장이 필요하면 앞에 LFENCE 또는 MFENCE를 배치해야 합니다.
; 직렬화된 MSR 읽기 패턴
lfence ; 이전 명령어 완료 보장
mov $0x10, %ecx ; IA32_TSC = 0x10
rdmsr ; EDX:EAX = TSC 값
shl $32, %rdx
or %rax, %rdx ; RDX = 64비트 TSC
CPUID를 통한 MSR 존재 확인
특정 MSR에 접근하기 전에 해당 기능의 존재 여부를 CPUID로 확인해야 합니다. 존재하지 않는 MSR에 접근하면 #GP(0) 예외가 발생합니다.
| CPUID 리프 | 비트 | 확인 대상 |
|---|---|---|
| CPUID.01H:EDX | bit 5 | MSR 명령어 지원 (RDMSR/WRMSR) |
| CPUID.01H:EDX | bit 4 | TSC 지원 |
| CPUID.01H:ECX | bit 15 | PDCM (IA32_PERF_CAPABILITIES) |
| CPUID.80000001H:EDX | bit 20 | NX bit (IA32_EFER.NXE) |
| CPUID.80000001H:EDX | bit 11 | SYSCALL/SYSRET |
| CPUID.07H:EBX | bit 0 | FSGSBASE 명령어 |
| CPUID.07H:EDX | bit 26 | IA32_SPEC_CTRL / IBRS |
| CPUID.07H:EDX | bit 29 | IA32_ARCH_CAPABILITIES |
시스템 제어 MSR
IA32_EFER (0xC0000080) — Extended Feature Enable Register
Long Mode 활성화와 NX bit 제어를 담당하는 핵심 MSR입니다.
| 비트 | 이름 | 설명 |
|---|---|---|
| 0 | SCE | SYSCALL/SYSRET Enable |
| 8 | LME | Long Mode Enable (IA-32e 활성화) |
| 10 | LMA | Long Mode Active (읽기 전용(Read-Only), CR0.PG로 활성화) |
| 11 | NXE | No-Execute Enable (W^X 보안) |
| 12 | SVME | SVM Enable (AMD-V 전용) |
| 13 | LMSLE | Long Mode Segment Limit Enable (AMD) |
| 14 | FFXSR | Fast FXSAVE/FXRSTOR (AMD) |
| 15 | TCE | Translation Cache Extension (AMD) |
/* arch/x86/kernel/cpu/common.c — Long Mode 진입 시 EFER 설정 */
static void setup_efer(void)
{
u64 efer;
rdmsrl(MSR_EFER, efer);
efer |= EFER_SCE; /* SYSCALL enable */
if (boot_cpu_has(X86_FEATURE_NX))
efer |= EFER_NX; /* NX bit enable */
wrmsrl(MSR_EFER, efer);
}
코드 설명
arch/x86/kernel/cpu/common.c의 EFER MSR 초기화 함수입니다. rdmsrl/wrmsrl은 arch/x86/include/asm/msr.h에 정의된 64비트 MSR 접근 매크로로, 내부적으로 RDMSR/WRMSR 명령어를 실행합니다.
- rdmsrl(MSR_EFER, efer)ECX에 MSR 주소 0xC0000080을 넣고
RDMSR을 실행하여 EDX:EAX로 반환된 64비트 값을efer변수에 저장합니다.rdmsrl은rdmsr의 64비트 래퍼로, 상위/하위 32비트를 자동 병합합니다. - efer |= EFER_SCE
SYSCALL/SYSRET명령어를 활성화합니다. Long Mode에서 시스템 콜 진입의 필수 전제 조건입니다. - efer |= EFER_NXNX(No-Execute) 비트를 활성화하여 W^X 보안 정책을 적용합니다. CPU가
X86_FEATURE_NX를 지원할 때만 설정합니다. - wrmsrl(MSR_EFER, efer)수정된 값을
WRMSR명령어로 EFER에 다시 씁니다.WRMSR은 직렬화 명령어이므로 이전 명령어가 모두 완료된 후 실행됩니다.
IA32_MISC_ENABLE (0x1A0)
다양한 CPU 기능을 활성화/비활성화하는 범용 제어 레지스터입니다.
| 비트 | 이름 | 설명 |
|---|---|---|
| 0 | Fast-Strings Enable | REP MOVSB/STOSB 최적화 |
| 7 | Performance Monitoring Available | PMU 사용 가능 여부 |
| 11 | Branch Trace Storage Unavailable | BTS 비활성화 (읽기 전용) |
| 12 | PEBS Unavailable | PEBS 비활성화 (읽기 전용) |
| 16 | Enhanced SpeedStep Enable | EIST/P-state 전환 활성화 |
| 18 | ENABLE_MONITOR_FSM | MONITOR/MWAIT 활성화 |
| 22 | Limit CPUID Maxval | CPUID 리프 제한 (레거시 OS 호환) |
| 23 | xTPR Message Disable | TPR 메시지 비활성화 |
| 34 | XD Bit Disable | NX bit 전역 비활성화 |
IA32_FEATURE_CONTROL (0x3A)
VMX(가상화) 활성화 및 BIOS 잠금(Lock)을 제어합니다. BIOS/UEFI 펌웨어(Firmware)가 부팅 시 설정하며, Lock bit이 설정되면 재부팅 전까지 변경 불가합니다.
| 비트 | 이름 | 설명 |
|---|---|---|
| 0 | Lock | 1로 설정 시 이 MSR은 읽기 전용 (재부팅까지) |
| 1 | Enable VMX in SMX | TXT 환경에서 VMX 활성화 |
| 2 | Enable VMX outside SMX | 일반 환경에서 VMX 활성화 |
| 8~14 | SENTER Local Function Enables | TXT SENTER 리프 활성화 |
| 15 | SENTER Global Enable | TXT SENTER 전역 활성화 |
| 17 | SGX Launch Control Enable | SGX Launch Enclave 제어 |
| 18 | SGX Global Enable | SGX 전역 활성화 |
/* arch/x86/kernel/cpu/feat_ctl.c — VMX 활성화 확인 */
void init_ia32_feat_ctl(struct cpuinfo_x86 *c)
{
u64 msr;
rdmsrl(MSR_IA32_FEAT_CTL, msr);
if (msr & FEAT_CTL_LOCKED)
return; /* BIOS가 이미 잠금 */
/* Lock 비트 없이 열려 있으면 직접 설정 */
msr = FEAT_CTL_LOCKED;
msr |= FEAT_CTL_VMX_ENABLED_OUTSIDE_SMX;
wrmsrl(MSR_IA32_FEAT_CTL, msr);
}
코드 설명
arch/x86/kernel/cpu/feat_ctl.c에서 IA32_FEATURE_CONTROL(0x3A) MSR을 초기화하는 함수입니다. 이 MSR은 VMX(가상화) 활성화와 BIOS 잠금(Lock)을 제어하며, 인덱스 상수는 arch/x86/include/asm/msr-index.h에 정의되어 있습니다.
- rdmsrl(MSR_IA32_FEAT_CTL, msr)현재
IA32_FEATURE_CONTROL값을 읽습니다. BIOS/UEFI가 부팅 시 VMX 비트와 Lock 비트를 설정하는 것이 일반적입니다. - msr & FEAT_CTL_LOCKED비트 0(Lock)이 설정되어 있으면 이 MSR은 재부팅 전까지 읽기 전용이 되므로, 더 이상 수정할 수 없어 즉시 반환합니다.
- msr = FEAT_CTL_LOCKEDLock 비트가 설정되지 않은 경우(BIOS가 초기화하지 않은 경우), 커널이 직접 Lock 비트를 포함한 값을 구성합니다.
- FEAT_CTL_VMX_ENABLED_OUTSIDE_SMX비트 2를 설정하여 일반 환경(SMX 외부)에서 VMX를 활성화합니다. KVM 등 하이퍼바이저가
VMXON을 실행하려면 이 비트가 필수입니다.
SYSCALL / SYSRET MSR
Long Mode에서 SYSCALL/SYSRET 명령어는 시스템 콜(System Call) 진입/복귀를 위해 3개의 MSR을 사용합니다. 이 MSR들은 IA32_EFER.SCE=1일 때만 유효합니다.
| MSR | 주소 | 용도 |
|---|---|---|
MSR_STAR | 0xC0000081 | SYSCALL의 CS/SS 선택자 (비트 47:32 = kernel CS, 비트 63:48 = user CS) |
MSR_LSTAR | 0xC0000082 | SYSCALL 진입점(Entry Point)의 64비트 RIP (Long Mode) |
MSR_CSTAR | 0xC0000083 | SYSCALL 진입점 RIP (Compatibility Mode, Linux 미사용) |
MSR_SYSCALL_MASK | 0xC0000084 | SYSCALL 시 RFLAGS에 적용할 마스크 (마스크된 비트 = 0) |
/* arch/x86/kernel/cpu/common.c — SYSCALL MSR 초기화 */
void syscall_init(void)
{
wrmsr(MSR_STAR, 0,
(__USER32_CS << 16) | __KERNEL_CS); /* STAR 상위 32비트 */
wrmsrl(MSR_LSTAR,
(unsigned long)entry_SYSCALL_64); /* 시스템 콜 진입점 */
#ifdef CONFIG_IA32_EMULATION
wrmsrl(MSR_CSTAR,
(unsigned long)entry_SYSCALL_compat);
#endif
/* IF, TF, DF, AC 플래그 마스킹 — 인터럽트/디버그 비활성화 */
wrmsrl(MSR_SYSCALL_MASK,
X86_EFLAGS_TF | X86_EFLAGS_DF |
X86_EFLAGS_IF | X86_EFLAGS_AC);
}
SYSCALL 실행 시 CPU는 (1) RCX에 리턴 주소(RIP) 저장, (2) R11에 RFLAGS 저장, (3) CS/SS를 STAR에서 로드, (4) RIP를 LSTAR에서 로드, (5) RFLAGS를 SYSCALL_MASK로 마스킹합니다. 스택 전환은 CPU가 하지 않으며, 커널이 직접 per-CPU 영역에서 RSP를 교체합니다.세그먼트 Base MSR (FS/GS)
Long Mode에서 FS/GS 세그먼트의 base 주소는 GDT가 아닌 MSR로 관리됩니다. Linux 커널은 GS_BASE를 per-CPU 데이터 포인터로, FS_BASE를 유저 공간 TLS(Thread-Local Storage)로 사용합니다.
| MSR | 주소 | 용도 |
|---|---|---|
IA32_FS_BASE | 0xC0000100 | FS 세그먼트 base (유저: TLS/glibc) |
IA32_GS_BASE | 0xC0000101 | GS 세그먼트 base (현재 활성 GS) |
IA32_KERNEL_GS_BASE | 0xC0000102 | SWAPGS로 교환할 GS base (대기 값) |
SWAPGS 메커니즘
SWAPGS는 IA32_GS_BASE와 IA32_KERNEL_GS_BASE를 원자적(Atomic)으로 교환합니다. 시스템 콜/인터럽트(Interrupt) 진입 시 커널은 SWAPGS를 실행하여 per-CPU 포인터를 GS에 로드하고, 복귀 시 다시 SWAPGS로 유저의 GS를 복원합니다.
; entry_SYSCALL_64 진입부 (arch/x86/entry/entry_64.S)
SYM_CODE_START(entry_SYSCALL_64)
swapgs ; GS = per-CPU base (커널용)
mov %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp ; KPTI: 커널 페이지 테이블 전환
mov PER_CPU_VAR(cpu_current_top_of_stack), %rsp
; ... 레지스터 저장, 시스템 콜 디스패치
FSGSBASE 명령어 (Linux 5.3+)
FSGSBASE CPU 기능(CPUID.07H:EBX.FSGSBASE[bit 0])이 있으면, RDFSBASE/WRFSBASE/RDGSBASE/WRGSBASE 명령어로 WRMSR 없이 직접 FS/GS base를 읽고 쓸 수 있습니다. 이 명령어들은 비직렬화이며 매우 빠릅니다(~10 사이클 vs WRMSR ~100 사이클).
/* arch/x86/include/asm/fsgsbase.h */
static inline unsigned long rdgsbase(void)
{
unsigned long gsbase;
asm volatile("rdgsbase %0" : "=r"(gsbase) :: "memory");
return gsbase;
}
static inline void wrgsbase(unsigned long gsbase)
{
asm volatile("wrgsbase %0" :: "r"(gsbase) : "memory");
}
APIC MSR
IA32_APIC_BASE (0x1B)
Local APIC의 기본 주소와 활성화 상태를 제어합니다.
| 비트 | 이름 | 설명 |
|---|---|---|
| 8 | BSP | Bootstrap Processor 플래그 (읽기 전용) |
| 10 | EXTD | x2APIC 모드 활성화 |
| 11 | EN | APIC 전역 활성화 |
| 12~35 | APIC Base | APIC MMIO base 주소 (기본: 0xFEE00000) |
x2APIC 모드 MSR (0x800~0x8FF)
x2APIC 모드에서는 MMIO 대신 MSR을 통해 APIC 레지스터에 접근합니다. 이는 MMIO보다 빠르고 32비트 APIC ID를 지원하여 256 CPU 이상의 시스템을 지원할 수 있습니다.
| MSR 주소 | xAPIC MMIO 오프셋(Offset) | 이름 | 접근 |
|---|---|---|---|
0x802 | 0x20 | APIC ID | R |
0x803 | 0x30 | APIC Version | R |
0x808 | 0x80 | Task Priority (TPR) | R/W |
0x80B | 0xB0 | EOI | W |
0x80F | 0xF0 | Spurious Interrupt Vector | R/W |
0x830 | 0x300+0x310 | ICR (64비트 통합) | R/W |
0x832~838 | 0x320~380 | LVT (Timer, Thermal, PMI 등) | R/W |
0x83F | — | Self IPI | W |
/* arch/x86/kernel/apic/x2apic_cluster.c — x2APIC IPI 전송 */
static void x2apic_send_IPI(int cpu, int vector)
{
u32 dest = per_cpu(x86_cpu_to_logical_apicid, cpu);
/* x2APIC ICR: MSR 0x830, 하위 32비트 = vector+flags, 상위 32비트 = dest */
wrmsrl(APIC_BASE_MSR + (APIC_ICR >> 4),
((u64)dest << 32) | APIC_DM_FIXED | vector);
}
성능 모니터링 MSR (PMU)
Performance Monitoring Unit(PMU) MSR은 CPU 이벤트(캐시 미스, 분기 예측(Branch Prediction) 실패, 명령어 은퇴 등)를 하드웨어 수준에서 카운팅합니다. perf 도구가 내부적으로 이 MSR들을 프로그래밍합니다.
Architectural PMU (버전 2+)
| MSR | 주소 | 설명 |
|---|---|---|
IA32_PERFEVTSELx | 0x186 + x | 이벤트 선택 레지스터 (이벤트/umask/usr/os/edge 등) |
IA32_PMCx | 0x0C1 + x | 범용 성능 카운터 (48비트) |
IA32_FIXED_CTR0 | 0x309 | Instruction Retired (고정 카운터) |
IA32_FIXED_CTR1 | 0x30A | Unhalted Core Cycles (고정 카운터) |
IA32_FIXED_CTR2 | 0x30B | Unhalted Reference Cycles (고정 카운터) |
IA32_FIXED_CTR_CTRL | 0x38D | 고정 카운터 제어 (OS/USR/PMI 설정) |
IA32_PERF_GLOBAL_CTRL | 0x38F | 전역 카운터 활성화 비트맵 |
IA32_PERF_GLOBAL_STATUS | 0x38E | 오버플로 상태 (PMI 인터럽트 원인) |
IA32_PERF_GLOBAL_OVF_CTRL | 0x390 | 오버플로 상태 클리어 |
IA32_PERFEVTSELx 비트 필드
/* L3 캐시 미스 이벤트 프로그래밍 예 */
#define PERFEVTSEL_EN (1ULL << 22)
#define PERFEVTSEL_OS (1ULL << 17)
#define PERFEVTSEL_USR (1ULL << 16)
/* Event 0x2E, Umask 0x41 = LLC-misses (Skylake 기준) */
u64 evtsel = 0x2E | (0x41 << 8) |
PERFEVTSEL_EN | PERFEVTSEL_OS | PERFEVTSEL_USR;
wrmsrl(MSR_P6_EVNTSEL0, evtsel);
/* 카운터 읽기 */
u64 count;
rdmsrl(MSR_P6_PERFCTR0, count);
pr_info("LLC misses: %llu\\n", count);
perf stat -e cache-misses,instructions,cycles ./program 명령은 내부적으로 이 PMU MSR들을 프로그래밍합니다. 커널 드라이버 arch/x86/events/intel/core.c가 MSR 접근을 추상화합니다.전력/주파수 MSR
현대 x86 CPU는 MSR을 통해 동적 주파수/전압 조절(DVFS)을 제어합니다. Linux의 cpufreq 드라이버(intel_pstate, amd-pstate)가 이 MSR들을 직접 사용하여 CPU의 성능 수준을 조절합니다. 이 섹션에서는 P-State 제어, HWP(Hardware P-States), RAPL(전력 제한), C-State 레지던시, AMD CPPC 등 전력/주파수 관련 MSR 전체를 상세히 다룹니다.
- P-State: Performance State — CPU 주파수/전압 조합. P0이 최고 성능, 숫자가 클수록 저전력.
- C-State: CPU Idle State — C0(활성), C1(Halt), C3(Sleep), C6(Deep Sleep) 등. 깊을수록 절전.
- FID: Frequency ID — 주파수 배수(ratio). Base Clock × FID = 실제 주파수.
- VID: Voltage ID — 전압 식별자. FID와 함께 P-State를 정의.
- DVFS: Dynamic Voltage and Frequency Scaling — 부하에 따라 주파수와 전압을 동적 조절.
- EPP: Energy Performance Preference — HWP에서 성능 vs 절전 우선순위 (0=성능, 255=절전).
- RAPL: Running Average Power Limit — 패키지/코어/DRAM의 전력 소비를 제한하고 측정.
MSR_PLATFORM_INFO (0xCE) — 플랫폼 주파수 정보
CPU의 기본 주파수 비율(ratio), 최소/최대 비율, TDP 관련 정보를 담고 있는 읽기 전용 MSR입니다. intel_pstate 드라이버가 초기화 시 가장 먼저 읽는 MSR입니다.
| 비트 | 필드 | 설명 |
|---|---|---|
| 15:8 | Maximum Non-Turbo Ratio | 기본 주파수 비율 (Base Frequency = BCLK × 이 값). 예: 0x22 = 34 → 3.4GHz (BCLK 100MHz) |
| 23:16 | — | 예약됨 |
| 28 | Programmable Ratio Limit for Turbo | 1이면 MSR_TURBO_RATIO_LIMIT 프로그래밍 가능 |
| 29 | Programmable TDP Limit for Turbo | 1이면 TDP 제한 변경 가능 |
| 30 | Programmable TJ Offset | 1이면 TjMax 오프셋 변경 가능 |
| 40:32 | Maximum Efficiency Ratio | 최소 효율 주파수 비율 (최저 P-State). 예: 0x04 = 400MHz |
| 47:41 | — | 예약됨 |
| 55:48 | Minimum Operating Ratio | 절대 최소 동작 비율 (C-State 진입 직전 최저 주파수) |
/* drivers/cpufreq/intel_pstate.c — 플랫폼 정보에서 주파수 범위 결정 */
static void intel_pstate_get_cpu_pstates(struct cpudata *cpu)
{
u64 value;
rdmsrl(MSR_PLATFORM_INFO, value);
/* Base Frequency ratio (bits 15:8) */
cpu->pstate.max_pstate = (value >> 8) & 0xFF;
/* Minimum Efficiency ratio (bits 40:32) */
cpu->pstate.min_pstate = (value >> 32) & 0xFF;
/* 예: max_pstate=34 → 3.4GHz, min_pstate=4 → 400MHz (BCLK=100MHz) */
rdmsrl(MSR_TURBO_RATIO_LIMIT, value);
cpu->pstate.turbo_pstate = value & 0xFF; /* 1코어 활성 시 최대 터보 비율 */
pr_debug("cpu %d: min=%d base=%d turbo=%d\\n",
cpu->cpu, cpu->pstate.min_pstate,
cpu->pstate.max_pstate, cpu->pstate.turbo_pstate);
}
IA32_PERF_CTL / IA32_PERF_STATUS — P-State 제어 (레거시 EIST)
Enhanced Intel SpeedStep Technology(EIST)에서 OS가 직접 목표 P-State를 설정하고, 현재 P-State를 읽는 데 사용하는 MSR 쌍입니다. HWP 활성화 시에도 레거시 호환을 위해 존재하지만, 실질적 제어는 HWP MSR이 담당합니다.
IA32_PERF_CTL (0x199) — 목표 P-State 설정
| 비트 | 필드 | 설명 |
|---|---|---|
| 7:0 | Target Ratio | 목표 주파수 비율 (BCLK × ratio = 목표 주파수) |
| 15:8 | — | 예약됨 (일부 모델에서 VID 인코딩) |
| 16 | IDA/Turbo Disengage | 1이면 해당 코어의 터보 부스트 비활성화 |
| 31:17 | — | 예약됨 |
| 32 | IDA/Turbo Disengage (64-bit) | 일부 모델에서 추가 터보 제어 |
IA32_PERF_STATUS (0x198) — 현재 P-State (읽기 전용)
| 비트 | 필드 | 설명 |
|---|---|---|
| 15:0 | Current Performance State Value | 현재 동작 중인 P-State 값 (ratio + VID 인코딩) |
/* 레거시 P-State 전환 — acpi-cpufreq 드라이버 */
/* drivers/cpufreq/acpi-cpufreq.c */
static void do_drv_write(void *_cmd)
{
struct drv_cmd *cmd = _cmd;
/* MSR_IA32_PERF_CTL에 목표 비율 쓰기 */
wrmsrl(MSR_IA32_PERF_CTL,
cmd->val | (rdmsrl(MSR_IA32_PERF_CTL) & ~0xFFFFULL));
/*
* P-State 전환은 즉각적이지 않음:
* - Intel: ~10μs (전압/주파수 안정화)
* - 전환 중 IA32_PERF_STATUS 값이 점진적으로 변경
* - IA32_MISC_ENABLE[16] (EIST Enable)이 1이어야 동작
*/
}
코드 설명
drivers/cpufreq/acpi-cpufreq.c의 레거시 P-State 전환 코드입니다. IA32_PERF_CTL(0x199) MSR에 목표 주파수 비율을 기록하여 CPU의 동작 주파수를 변경합니다.
- wrmsrl(MSR_IA32_PERF_CTL, ...)
IA32_PERF_CTL의 하위 16비트에 목표 비율(Target Ratio)을 기록합니다. 비트 7:0이 주파수 비율이며, BCLK(보통 100MHz) × ratio = 목표 주파수입니다. - rdmsrl(MSR_IA32_PERF_CTL) & ~0xFFFFULL현재 MSR 값의 상위 비트(예: 터보 부스트 비활성화 비트 등)를 보존하기 위해 하위 16비트만 마스킹하고 OR 연산으로 새 비율을 삽입합니다.
- P-State 전환 지연CPU 내부적으로 전압과 주파수를 동시에 조정하므로 약 10마이크로초(us)의 안정화 시간이 필요합니다. 전환 중에는
IA32_PERF_STATUS(0x198)에서 현재 상태를 확인할 수 있습니다.
IA32_MISC_ENABLE(0x1A0)의 비트 16(Enhanced SpeedStep Enable)이 0이면 EIST가 비활성화되어 IA32_PERF_CTL 쓰기가 무시됩니다. BIOS에서 SpeedStep을 활성화해야 합니다.MSR_TURBO_RATIO_LIMIT (0x1AD) — 터보 부스트 비율 제한
활성 코어 수에 따른 최대 터보 부스트 비율을 정의합니다. 활성 코어가 적을수록 더 높은 주파수까지 부스트할 수 있습니다 (열/전력 여유분 활용).
| 비트 | 필드 | 설명 |
|---|---|---|
| 7:0 | 1C Max Turbo Ratio | 1코어 활성 시 최대 비율 (가장 높음) |
| 15:8 | 2C Max Turbo Ratio | 2코어 활성 시 최대 비율 |
| 23:16 | 3C Max Turbo Ratio | 3코어 활성 시 최대 비율 |
| 31:24 | 4C Max Turbo Ratio | 4코어 활성 시 최대 비율 |
| 39:32 | 5C Max Turbo Ratio | 5코어 활성 시 최대 비율 |
| 47:40 | 6C Max Turbo Ratio | 6코어 활성 시 최대 비율 |
| 55:48 | 7C Max Turbo Ratio | 7코어 활성 시 최대 비율 |
| 63:56 | 8C Max Turbo Ratio | 8코어 활성 시 최대 비율 |
/* 터보 비율 파싱 — turbostat 도구 로직과 유사 */
static void dump_turbo_ratios(void)
{
u64 turbo_limit;
int i;
rdmsrl(MSR_TURBO_RATIO_LIMIT, turbo_limit);
for (i = 0; i < 8; i++) {
u8 ratio = (turbo_limit >> (i * 8)) & 0xFF;
pr_info("%dC turbo: %d (%d MHz)\\n",
i + 1, ratio, ratio * 100);
}
/*
* 출력 예시 (i7-12700):
* 1C turbo: 49 (4900 MHz) ← 단일 코어 최대
* 2C turbo: 49 (4900 MHz)
* 3C turbo: 48 (4800 MHz)
* 4C turbo: 47 (4700 MHz)
* ...
* 8C turbo: 46 (4600 MHz) ← 전코어 터보
*/
}
/* 코어 수가 8 이상인 CPU는 추가 MSR 사용:
* MSR_TURBO_RATIO_LIMIT1 (0x1AE): 코어 9~16
* MSR_TURBO_RATIO_LIMIT2 (0x1AF): 코어 17~24
* MSR_TURBO_RATIO_LIMIT3 (0x1AC): 그룹별 비율 (Atom 계열)
*/
turbostat 도구는 이 MSR을 읽어 코어 수별 최대 터보 주파수를 표시합니다: turbostat --show Core,CPU,Bzy_MHz,TSC_MHz,Busy%,PkgWattHWP (Hardware P-States) — Intel Speed Shift
HWP는 Skylake(6세대) 이후 Intel CPU에서 지원하는 하드웨어 자율 P-State 관리 기술입니다. 기존 EIST에서는 OS가 매번 목표 P-State를 결정해야 했으나, HWP에서는 OS가 성능 범위와 선호도만 설정하면 CPU 하드웨어가 부하, 온도, 전력 상태를 종합적으로 판단하여 최적의 P-State를 자율 선택합니다. P-State 전환 응답 시간이 ~10ms에서 ~30μs로 대폭 개선됩니다.
IA32_PM_ENABLE (0x770) — HWP 활성화
| 비트 | 필드 | 설명 |
|---|---|---|
| 0 | HWP_ENABLE | 1로 설정하면 HWP 활성화. 비가역적: 한 번 활성화하면 재부팅 전까지 비활성화 불가. |
| 63:1 | — | 예약됨 (0으로 유지) |
IA32_PM_ENABLE에 1을 쓰면 HWP가 영구(재부팅까지) 활성화됩니다. 다시 0으로 되돌릴 수 없으므로, 커널 드라이버는 부팅 초기에 한 번만 설정합니다.IA32_HWP_CAPABILITIES (0x771) — 하드웨어 성능 범위 (읽기 전용)
CPU가 보고하는 P-State 범위. OS는 이 범위 내에서 HWP_REQUEST를 설정해야 합니다.
| 비트 | 필드 | 설명 |
|---|---|---|
| 7:0 | Highest_Performance | 최대 성능 수준 (터보 포함). 예: 49 → 4.9GHz |
| 15:8 | Guaranteed_Performance | 지속 보장 성능 수준 (Base Freq). 예: 34 → 3.4GHz |
| 23:16 | Most_Efficient_Performance | 최고 에너지 효율 성능 수준. 예: 8 → 800MHz |
| 31:24 | Lowest_Performance | 절대 최저 성능 수준. 예: 4 → 400MHz |
/* IA32_HWP_CAPABILITIES 파싱 */
static void read_hwp_caps(struct cpudata *cpu)
{
u64 cap;
rdmsrl(MSR_IA32_HWP_CAPABILITIES, cap);
cpu->hwp_cap.highest = HWP_HIGHEST_PERF(cap); /* bits 7:0 */
cpu->hwp_cap.guaranteed = HWP_GUARANTEED_PERF(cap); /* bits 15:8 */
cpu->hwp_cap.efficient = HWP_MOSTEFFICIENT_PERF(cap); /* bits 23:16 */
cpu->hwp_cap.lowest = HWP_LOWEST_PERF(cap); /* bits 31:24 */
/*
* 실제 예시 (Intel i9-13900K):
* Highest=49, Guaranteed=34, Efficient=8, Lowest=4
* → 4.9GHz 최대, 3.4GHz 보장, 800MHz 효율, 400MHz 최저
*/
}
IA32_HWP_REQUEST (0x774) — OS → HW 성능 요청 (코어별)
OS가 HWP에 전달하는 성능 힌트. 각 논리 코어별로 독립 설정 가능합니다.
| 비트 | 필드 | 설명 |
|---|---|---|
| 7:0 | Minimum_Performance | 허용 최저 성능. 이 이하로 내려가지 않음. 절전 vs 응답 지연(Latency) 트레이드오프. |
| 15:8 | Maximum_Performance | 허용 최대 성능. 이 이상 올라가지 않음. 전력/발열 제한에 활용. |
| 23:16 | Desired_Performance | 목표 성능 힌트. 0이면 HW가 자율 결정 (자율 모드). |
| 31:24 | Energy_Performance_Preference | EPP 값 (0~255): 0=최대 성능, 128=균형, 255=최대 절전. |
| 41:32 | Activity_Window | 부하 모니터링 창 크기 (10비트: 7비트 값 + 3비트 지수). 0이면 HW 기본값. |
| 42 | Package_Control | 1이면 이 코어의 요청이 패키지 레벨에서 적용 |
| 59:43 | — | 예약됨 |
| 63:60 | — | 예약됨 |
EPP (Energy Performance Preference) 상세
EPP는 HWP에서 성능과 에너지 효율 사이의 균형점을 OS가 CPU에 전달하는 핵심 파라미터입니다. 값이 작을수록 성능 우선, 클수록 절전 우선입니다.
| EPP 값 | Linux 정책 | sysfs 값 | 동작 특성 |
|---|---|---|---|
| 0 | performance | performance | 최대 터보 부스트, 전력 무제한. 고성능 서버/게이밍. |
| 64 | balance_performance | balance_performance | 성능 우선이지만 약간의 절전 고려. 데스크톱 기본값. |
| 128 | default/normal | default | 성능과 절전 균형. 일반 워크로드. |
| 192 | balance_power | balance_power | 절전 우선이지만 합리적 성능 유지. 노트북 배터리 모드. |
| 255 | power | power | 최대 절전, 최저 주파수 유지. 극한 배터리 절약. |
/* drivers/cpufreq/intel_pstate.c — HWP_REQUEST 설정 (전체 필드) */
static void intel_pstate_hwp_set(struct cpudata *cpu)
{
u64 value, prev;
s16 epp;
rdmsrl_on_cpu(cpu->cpu, MSR_IA32_HWP_REQUEST, &prev);
value = HWP_MIN_PERF(cpu->pstate.min_pstate); /* bits 7:0 */
value |= HWP_MAX_PERF(cpu->pstate.max_pstate); /* bits 15:8 */
value |= HWP_DESIRED_PERF(0); /* bits 23:16 — 0 = 자율 모드 */
epp = intel_pstate_get_epp(cpu);
value |= HWP_ENERGY_PERF_PREFERENCE(epp); /* bits 31:24 */
if (value != prev) {
wrmsrl_on_cpu(cpu->cpu, MSR_IA32_HWP_REQUEST, value);
trace_cpu_frequency(cpu->pstate.max_pstate * cpu->pstate.scaling, cpu->cpu);
}
}
echo "balance_performance" > /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preferencecat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences 로 사용 가능한 값 확인.IA32_HWP_REQUEST_PKG (0x772) — 패키지 레벨 HWP 요청
패키지(소켓(Socket)) 전체에 대한 HWP 요청을 설정합니다. 코어별 HWP_REQUEST에서 Package_Control 비트(42)가 0인 경우 이 MSR의 값이 기본 적용됩니다.
| 비트 | 필드 | 설명 |
|---|---|---|
| 7:0 | Minimum_Performance | 패키지 전체 최저 성능 |
| 15:8 | Maximum_Performance | 패키지 전체 최대 성능 |
| 23:16 | Desired_Performance | 패키지 전체 목표 성능 |
| 31:24 | Energy_Performance_Preference | 패키지 전체 EPP |
| 41:32 | Activity_Window | 패키지 전체 활동 창 |
IA32_HWP_INTERRUPT (0x773) — HWP 변경 알림 인터럽트
| 비트 | 필드 | 설명 |
|---|---|---|
| 0 | EN_Guaranteed_Performance_Change | Guaranteed Performance가 변경될 때 인터럽트 발생 |
| 1 | EN_Excursion_Minimum | 성능이 Minimum 이하로 떨어질 때 인터럽트 발생 |
| 2 | EN_Highest_Performance_Change | Highest Performance가 변경될 때 인터럽트 발생 |
IA32_HWP_STATUS (0x777) — HWP 상태 피드백
| 비트 | 필드 | 설명 |
|---|---|---|
| 0 | Guaranteed_Performance_Change | 1이면 보장 성능 수준이 변경됨 (W1C: 1 쓰면 클리어) |
| 2 | Excursion_To_Minimum | 1이면 성능이 요청 최소치 이하로 내려갔음 (W1C) |
| 4 | Highest_Performance_Change | 1이면 최대 성능 수준이 변경됨 (W1C) |
/* HWP 인터럽트 핸들러 — 보장 성능 변경 처리 */
void intel_pstate_irq_handler(void)
{
u64 status;
rdmsrl(MSR_IA32_HWP_STATUS, status);
if (status & 0x1) {
/* Guaranteed Performance 변경 — 열 스로틀링 등으로 발생 */
u64 cap;
rdmsrl(MSR_IA32_HWP_CAPABILITIES, cap);
pr_info("HWP guaranteed perf changed to %llu\\n",
(cap >> 8) & 0xFF);
/* 상태 비트 클리어 (W1C) */
wrmsrl(MSR_IA32_HWP_STATUS, status);
}
}
APERF / MPERF — 실제 주파수 측정
APERF와 MPERF는 CPU의 실제 동작 주파수와 활용률을 측정하는 핵심 카운터 쌍입니다. 두 카운터 모두 C0 상태(활성)에서만 증가하며, C-State(유휴)에서는 멈춥니다.
| MSR | 주소 | 동작 |
|---|---|---|
IA32_MPERF | 0xE7 | Maximum Performance 카운터. 최대 비터보 주파수(TSC rate)에 비례하여 증가. C0 상태에서만 증가. |
IA32_APERF | 0xE8 | Actual Performance 카운터. 실제 동작 클럭에 비례하여 증가. 터보 부스트/스로틀링 반영. |
APERF/MPERF를 이용한 핵심 계산
/*
* 두 시점(t0, t1) 사이의 델타를 이용한 계산:
*
* 1) 실제 평균 주파수:
* actual_freq = base_freq × (ΔAPERF / ΔMPERF)
* - ΔAPERF > ΔMPERF → 터보 부스트 활성 (base_freq 초과)
* - ΔAPERF < ΔMPERF → 스로틀링 발생 (base_freq 미만)
* - ΔAPERF = ΔMPERF → 정확히 base_freq로 동작
*
* 2) C0 레지던시 (CPU 활용률):
* busy% = ΔMPERF / ΔTSC × 100
* - TSC는 항상 일정 비율로 증가 (invariant TSC)
* - MPERF는 C0에서만 증가
* → ΔMPERF/ΔTSC = C0 상태에서 보낸 시간 비율
*
* 3) 평균 주파수 (유휴 포함):
* avg_freq = TSC_freq × (ΔAPERF / ΔTSC)
* - 전체 시간 대비 실제 작업량 반영
*/
struct freq_sample {
u64 aperf, mperf, tsc;
};
static void snapshot(struct freq_sample *s)
{
unsigned long flags;
local_irq_save(flags);
rdmsrl(MSR_IA32_APERF, s->aperf);
rdmsrl(MSR_IA32_MPERF, s->mperf);
s->tsc = rdtsc_ordered();
local_irq_restore(flags);
}
static void calc_freq(struct freq_sample *s0, struct freq_sample *s1)
{
u64 da = s1->aperf - s0->aperf;
u64 dm = s1->mperf - s0->mperf;
u64 dt = s1->tsc - s0->tsc;
u64 base_khz = tsc_khz; /* 커널 전역 변수 */
pr_info("actual_freq = %llu MHz\\n",
div64_u64(base_khz * da, dm) / 1000);
pr_info("C0 busy = %llu.%llu%%\\n",
dm * 100 / dt, (dm * 10000 / dt) % 100);
pr_info("avg_freq (incl idle) = %llu MHz\\n",
div64_u64(base_khz * da, dt) / 1000);
}
turbostat의 Bzy_MHz 컬럼은 base_freq × ΔAPERF/ΔMPERF를, Busy%는 ΔMPERF/ΔTSC × 100을 보여줍니다. Avg_MHz는 base_freq × ΔAPERF/ΔTSC입니다.RAPL (Running Average Power Limit) MSR
RAPL은 Intel Sandy Bridge 이후 도입된 전력 측정 및 제한 메커니즘으로, 패키지/코어/DRAM/GPU의 실시간(Real-time) 전력 소비를 모니터링하고, 전력 상한을 설정할 수 있습니다. 커널의 intel_rapl 드라이버와 powercap 프레임워크가 이 MSR들을 추상화합니다.
RAPL 도메인 (Power Domain)
| 도메인 | 약칭 | 측정/제한 대상 | 주요 MSR 기반 주소 |
|---|---|---|---|
| Package (PKG) | PKG | CPU 패키지 전체 (코어 + Uncore + GPU) | 0x610 ~ 0x614 |
| Power Plane 0 (PP0) | PP0 | CPU 코어 영역만 | 0x638 ~ 0x63A |
| Power Plane 1 (PP1) | PP1 | 내장 GPU (클라이언트 CPU만) | 0x640 ~ 0x642 |
| DRAM | DRAM | 메모리 컨트롤러/DRAM (서버 CPU) | 0x618 ~ 0x61C |
| Platform (PSys) | PSYS | 전체 시스템 (Skylake+, 노트북) | 0x64C ~ 0x650 |
MSR_RAPL_POWER_UNIT (0x606) — 전력 단위
RAPL MSR의 값을 물리 단위로 변환하기 위한 단위 정보입니다. 모든 RAPL 값 해석의 기준이 됩니다.
| 비트 | 필드 | 설명 |
|---|---|---|
| 3:0 | Power Units | 전력 단위 = 1/(2^n) 와트. 보통 n=3 → 0.125W |
| 12:8 | Energy Status Units | 에너지 단위 = 1/(2^n) 줄. 보통 n=14 → ~61μJ |
| 19:16 | Time Units | 시간 단위 = 1/(2^n) 초. 보통 n=10 → ~976μs |
/* RAPL 단위 파싱 */
static void parse_rapl_units(void)
{
u64 units;
double power_unit, energy_unit, time_unit;
rdmsrl(MSR_RAPL_POWER_UNIT, units);
power_unit = 1.0 / (1 << (units & 0xF)); /* 보통 0.125W */
energy_unit = 1.0 / (1 << ((units >> 8) & 0x1F)); /* 보통 ~61μJ */
time_unit = 1.0 / (1 << ((units >> 16) & 0xF)); /* 보통 ~976μs */
/* 예: Power Unit=3 → 1/8=0.125W
* Energy Unit=14 → 1/16384 ≈ 61.04μJ
* Time Unit=10 → 1/1024 ≈ 976.6μs
*/
}
MSR_PKG_POWER_LIMIT (0x610) — 패키지 전력 제한
패키지 레벨의 전력 제한(PL1/PL2)을 설정합니다. PL1은 장기(sustained) TDP, PL2는 단기(burst) TDP입니다.
| 비트 | 필드 | 설명 |
|---|---|---|
| 14:0 | Power Limit 1 (PL1) | 장기 전력 제한 (Power Units 단위). 예: 600 × 0.125W = 75W TDP |
| 15 | PL1 Enable | 1이면 PL1 활성화 |
| 16 | PL1 Clamping Limitation | 1이면 PL1 미만으로도 클램핑 허용 (더 공격적 제한) |
| 23:17 | PL1 Time Window | PL1 시간 창 (Time Units 사용). 이 시간 동안의 평균 전력이 PL1 이하여야 함 |
| 46:32 | Power Limit 2 (PL2) | 단기 전력 제한. 보통 PL1의 1.25배. 예: 93.75W |
| 47 | PL2 Enable | 1이면 PL2 활성화 |
| 48 | PL2 Clamping Limitation | PL2 클램핑 허용 |
| 55:49 | PL2 Time Window | PL2 시간 창 (보통 ~28ms) |
| 63 | Lock | 1이면 이 MSR 잠금 (재부팅까지 변경 불가). BIOS가 설정. |
/* drivers/powercap/intel_rapl_msr.c — RAPL 전력 제한 설정 */
static int set_domain_power_limit(struct rapl_domain *rd,
u64 power_limit_uw, int pl_id)
{
u64 val, mask;
int shift = (pl_id == 1) ? 0 : 32;
rdmsrl(rd->msr_power_limit, val);
/* 와트를 RAPL 단위로 변환 */
u64 raw = div64_u64(power_limit_uw,
rd->rp->power_unit * 1000000ULL);
mask = 0x7FFFULL << shift;
val = (val & ~mask) | ((raw & 0x7FFF) << shift);
val |= (1ULL << (15 + shift)); /* Enable bit */
wrmsrl(rd->msr_power_limit, val);
return 0;
}
MSR_PKG_ENERGY_STATUS (0x611) — 패키지 에너지 소비 (읽기 전용)
패키지의 누적 에너지 소비량입니다. 32비트 카운터로, 오버플로우됩니다.
| 비트 | 필드 | 설명 |
|---|---|---|
| 31:0 | Total Energy Consumed | 누적 에너지 (Energy Units 단위). 두 시점의 차이로 전력 계산. |
/* 패키지 전력 측정 (두 시점 간 에너지 차이) */
static u64 measure_pkg_power_mw(unsigned int interval_ms)
{
u64 e0, e1, units;
u64 energy_unit;
rdmsrl(MSR_RAPL_POWER_UNIT, units);
energy_unit = 1000000ULL >> ((units >> 8) & 0x1F); /* μJ 단위 */
rdmsrl(MSR_PKG_ENERGY_STATUS, e0);
msleep(interval_ms);
rdmsrl(MSR_PKG_ENERGY_STATUS, e1);
/* 32비트 오버플로우 처리 */
u64 delta = (e1 - e0) & 0xFFFFFFFF;
/* mW = (delta × energy_unit_μJ) / interval_ms */
return div64_u64(delta * energy_unit, interval_ms);
}
/*
* 오버플로우 주기 예시:
* Energy Unit=14 → 61μJ/count
* 200W 소비 시: 2^32 × 61μJ / 200W ≈ 1311초 ≈ ~22분
* 커널은 주기적으로 읽어 오버플로우를 추적해야 함
*/
RAPL Lock 메커니즘 상세
RAPL Power Limit MSR의 최상위 비트(bit 63)는 Lock 비트로, 한 번 1로 설정되면 시스템 재부팅 전까지 해당 MSR을 다시 수정할 수 없도록 영구 잠금합니다. 이 메커니즘은 보안 강화(악의적 전력 조작 방지)와 OEM 전력 정책 보호(사용자/OS가 제조사 설정을 덮어쓰지 못하도록)를 목적으로 합니다.
Lock 비트 배치 (도메인별)
| 도메인 | Power Limit MSR | Lock 비트 | 설명 |
|---|---|---|---|
| PKG (패키지) | 0x610 | 63 | 패키지 전체 전력 제한 잠금. 가장 중요한 Lock. |
| PP0 (Core) | 0x638 | 31 | 코어 도메인 전력 제한 잠금 (32비트 구조). |
| PP1 (GPU) | 0x640 | 31 | 내장 GPU 전력 제한 잠금 (클라이언트 CPU). |
| DRAM | 0x618 | 63 | DRAM 전력 제한 잠금 (서버 CPU). |
| PSys (Platform) | 0x64C | 63 | 플랫폼 전체 전력 제한 잠금 (Skylake+ 노트북). |
AC 전원 차단 + 배터리 제거 또는 시스템 재부팅 전까지 절대 0으로 되돌릴 수 없습니다. 잘못된 전력 제한값을 설정 후 Lock하면, 시스템이 의도치 않게 낮은 전력으로 고정될 수 있습니다.Lock이 설정되는 시점
RAPL Lock은 일반적으로 BIOS/UEFI 펌웨어가 POST(Power-On Self-Test) 또는 부팅 초기 단계에서 설정합니다. 다만 실제 기본값은 OEM, BIOS 버전, 마이크로코드, 하이퍼바이저(Hypervisor) 정책에 따라 달라지므로 아래 표는 대표적인 경향으로 읽어야 합니다:
| 시스템 유형 | Lock 정책 | 이유 |
|---|---|---|
| 노트북/태블릿 | 기본 Lock인 경우가 많음 | 열 설계 전력(TDP) 보호. 사용자가 전력 제한을 높여 과열/화재를 유발하지 않도록 제한. |
| 데스크톱 (OEM) | 대부분 Lock | 전원 공급 장치(PSU) 용량 보호. 과다 전력 소비로 인한 시스템 불안정 방지. |
| 서버 (Dell, HPE 등) | 선택적 Lock | 데이터센터 전력 예산 관리. BIOS 옵션으로 제어 가능한 경우 많음. |
| 자작 PC (DIY) | 보통 Unlock | 오버클러킹/언더볼팅 유연성 제공. 매더보드 제조사가 사용자 제어 허용. |
| 클라우드 VM | 호스트 정책 의존 | 게스트가 호스트 전력 정책에 간섭하지 못하게 대개 숨기거나 읽기 전용/합성 값으로 제한. |
CFG Lock, Overclocking Lock, RAPL Lock 유사 옵션을 제공합니다. 하지만 명칭과 실제 동작은 vendor/model마다 다르며, 옵션이 있어도 모든 RAPL 도메인이 동일하게 풀리는 것은 아닙니다.보안 관점: Plundervolt 및 CVE-2019-11157
2019년, 연구자들은 Plundervolt 공격을 발표했습니다. 이 공격은 RAPL MSR을 통해 CPU 전압을 비정상적으로 낮춰, Intel SGX(Software Guard Extensions) enclave의 메모리 무결성(Integrity)을 파괴하고 암호키를 유출하는 취약점(Vulnerability)입니다.
- CVE-2019-11157: Privilege escalation via RAPL interface (CVSS 6.7)
- 공격 원리: 낮은 전압에서 CPU가 연산 오류를 발생시켜, SGX enclave의 암호 연산이 잘못된 결과를 반환하도록 유도.
- Intel/OEM 대응: 마이크로코드와 BIOS 정책으로 undervolting 및 관련 MSR 쓰기를 더 보수적으로 제한하는 플랫폼이 늘었습니다.
- Linux 커널 대응: 펌웨어가 Lock 상태를 노출하면
intel_rapl/powercap경로는 해당 도메인을 읽기 전용으로 취급합니다.
/* 개념적 점검 흐름: 부팅 시 firmware lock 상태 확인 */
static int check_rapl_lock(void)
{
u64 pkg_limit;
rdmsrl(MSR_PKG_POWER_LIMIT, pkg_limit);
if (pkg_limit & (1ULL << 63))
pr_info("RAPL locked by firmware\\n");
else
pr_warn("RAPL writable: verify platform security policy\\n");
return 0;
}
powercap 노출 상태를 직접 확인해야 합니다.Lock 상태 확인 방법
다음 방법으로 현재 시스템의 RAPL Lock 상태를 확인할 수 있습니다:
# 방법 1: rdmsr로 직접 확인 (요구: msr 모듈 로드)
sudo modprobe msr
sudo rdmsr -f 63:63 0x610 # PKG Lock 비트 (1=Locked, 0=Unlocked)
sudo rdmsr -f 31:31 0x638 # PP0 (Core) Lock 비트
sudo rdmsr -f 63:63 0x618 # DRAM Lock 비트 (서버 CPU만)
# 방법 2: x86_energy_perf_policy 도구
sudo x86_energy_perf_policy -r # "MSR 0x610 ... Lock=1" 출력
# 방법 3: turbostat (모든 전력 관련 MSR 덤프)
sudo turbostat --Summary --quiet --show PkgWatt,RAMWatt --interval 1
# 출력에 "RAPL locked" 메시지가 있으면 Lock됨
# 방법 4: 커널 로그 확인
dmesg | grep -i rapl
# 출력 예시: "intel_rapl_common: RAPL package-0 domain package locked by BIOS"
Lock된 MSR에 쓰기 시도 시 동작
RAPL Lock 비트가 설정된 상태에서 해당 MSR에 WRMSR 명령을 실행하면, 다음과 같은 동작이 발생합니다:
- Intel CPU 동작:
#GP(0)(General Protection Fault) 예외 발생 → 커널 모드에서는 커널 패닉(Kernel Panic), 유저 모드에서는SIGILL또는SIGSEGV시그널(Signal). - 쓰기 무시 (일부 모델): 일부 Atom/저전력 CPU는 예외 대신 쓰기를 조용히 무시 (silent fail).
RDMSR로 다시 읽으면 이전 값 유지. - Linux 커널 처리:
drivers/powercap/intel_rapl_msr.c의rapl_write_data_raw()는 쓰기 전에 Lock 비트를 확인하고, Lock되어 있으면-EACCES반환.
/* drivers/powercap/intel_rapl_msr.c — Lock 확인 후 쓰기 */
static int rapl_msr_write_raw(int cpu, struct rapl_domain *rd,
u64 val)
{
u64 current;
int ret;
/* Lock 비트 확인 */
ret = rdmsrl_on_cpu(cpu, rd->msr_power_limit, ¤t);
if (ret)
return ret;
if (current & rd->lock_bit) {
pr_debug("RAPL: Domain %s locked, write denied\\n", rd->name);
return -EACCES; /* Permission denied */
}
/* Lock되지 않았으면 쓰기 실행 */
return wrmsrl_on_cpu(cpu, rd->msr_power_limit, val);
}
/* 사용자 공간에서 powercap sysfs 쓰기 시 */
// # echo 50000000 > /sys/class/powercap/intel-rapl:0/constraint_0_power_limit_uw
// 출력: bash: echo: write error: Permission denied (EACCES)
/dev/cpu/0/msr에 직접 쓰기를 시도해도 Lock 비트가 설정된 플랫폼에서는 보통 #GP, 쓰기 실패, 읽기 전용 노출 중 하나로 귀결됩니다. 일반적인 OS 경로만으로 firmware/hardware lock을 우회할 수 있다고 기대하면 안 됩니다.OEM별 RAPL Lock 정책 경향
아래 표는 현장에서 자주 보이는 경향을 정리한 것입니다. 같은 제조사라도 제품군, BIOS 옵션, 출시 시점에 따라 결과가 달라질 수 있습니다.
| 제조사 | 제품군 | Lock 정책 | BIOS 옵션 |
|---|---|---|---|
| Dell | Latitude, XPS | 대체로 Lock | 모델 의존 |
| Dell | PowerEdge 서버 | 선택적 (BIOS) | System Profile에서 제어 |
| HP | EliteBook, ProBook | 대체로 Lock | 모델 의존 |
| HP | ProLiant 서버 | 선택적 | Power Regulator 옵션 |
| Lenovo | ThinkPad | 대체로 Lock | 모델 의존 |
| Lenovo | ThinkSystem 서버 | 선택적 | Operating Mode 설정 |
| ASUS | ZenBook, VivoBook | 대부분 Lock | 일부 모델에 CFG Lock 옵션 |
| ASUS | ROG (게이밍) | Unlock 가능 | Advanced\Power 메뉴 |
| MSI | 게이밍/워크스테이션 | Unlock 가능 | OC Settings 메뉴 |
| Gigabyte | AORUS, 매더보드 | Unlock 기본 | Tweaker\Advanced CPU Settings |
| Supermicro | 서버 매더보드 | Unlock 기본 | IPMI로 동적 제어 가능 |
| Apple | MacBook (Intel) | 강한 제한 경향 | 사용자 노출 BIOS 없음 |
커널의 Lock 감지 및 처리
Linux 커널의 intel_rapl 드라이버는 초기화 시 각 도메인의 Lock 상태를 검사하고, Lock된 도메인은 읽기 전용으로 표시합니다:
/* drivers/powercap/intel_rapl_common.c — RAPL 도메인 초기화 */
static int rapl_detect_domains(struct rapl_package *rp, int cpu)
{
struct rapl_domain *rd;
u64 locked;
int i;
for (i = 0; i < RAPL_DOMAIN_MAX; i++) {
rd = &rp->domains[i];
if (!rd->msr_power_limit)
continue;
/* Lock 비트 읽기 */
rdmsrl(rd->msr_power_limit, locked);
if (locked & rd->lock_bit) {
rd->locked = true;
pr_info("RAPL: package %d domain %s locked by BIOS\\n",
rp->id, rd->name);
}
}
return 0;
}
/* sysfs 속성을 읽기 전용으로 설정 */
static umode_t power_limit_attr_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
struct rapl_domain *rd = to_rapl_domain(kobj);
if (rd->locked)
return S_IRUGO; /* 읽기 전용 (0444) */
else
return S_IRUGO | S_IWUSR; /* 읽기+쓰기 (0644) */
}
Lock된 도메인의 sysfs 파일은 권한이 -r--r--r--로 설정되어, 쓰기 시도 시 명확히 거부됩니다:
# Lock된 시스템
$ ls -l /sys/class/powercap/intel-rapl:0/constraint_0_power_limit_uw
-r--r--r-- 1 root root 4096 # 읽기 전용!
# Unlock된 시스템
$ ls -l /sys/class/powercap/intel-rapl:0/constraint_0_power_limit_uw
-rw-r--r-- 1 root root 4096 # root 쓰기 가능
가상화 환경에서의 RAPL Lock
가상 머신 내에서 RAPL MSR 접근은 하이퍼바이저 정책, 노출 CPU 모델, 버전에 따라 크게 달라집니다:
| 하이퍼바이저 | RAPL MSR 노출 | Lock 상태 | 주의사항 |
|---|---|---|---|
| KVM/QEMU | 버전/설정 의존 | 정책 의존 | 읽기 전용 에너지 카운터만 노출하거나, 0/합성 값을 반환하거나, 아예 숨길 수 있음 |
| VMware ESXi | 대체로 제한적 | 정책 의존 | 제품 버전과 가상 하드웨어 설정에 따라 다름 |
| Hyper-V | 대체로 제한적 | 정책 의존 | Synthetic 인터페이스 또는 비노출 형태가 일반적 |
| Xen | Dom0 우선 | DomU 제한적 | DomU에 어떤 값을 노출할지는 버전/정책 의존 |
/* 개념적 의사 코드: 하이퍼바이저의 RAPL MSR 노출 정책 */
static int hypervisor_get_msr(struct vcpu *vcpu, struct msr_data *msr_info)
{
switch (msr_info->index) {
case MSR_PKG_POWER_LIMIT:
if (!vcpu->rapl_passthrough)
return inject_gp_or_return_synthetic_value(msr_info);
return emulate_guest_visible_limit(vcpu, msr_info);
case MSR_PKG_ENERGY_STATUS:
return emulate_or_hide_energy_counter(vcpu, msr_info);
}
return -EOPNOTSUPP;
}
Lock 우회 시도 및 불가능성
다음과 같은 시도들은 일반적인 운영체제/하이퍼바이저 경로에서는 성공하기 어렵습니다:
- 소프트웨어 우회: Lock 비트는 CPU 마이크로코드 내부 상태로, OS 권한으로 제어 불가.
- 다른 MSR로 우회:
MSR_PLATFORM_POWER_LIMIT(0x65C) 등 다른 전력 MSR도 개별적으로 Lock됨. - ACPI 우회: ACPI
_PPC,_PSS메서드는 주파수만 제어하며, RAPL 전력 제한과 독립적. - 드라이버 수정: 커널 드라이버를 수정해도 CPU 하드웨어 Lock은 변경 불가.
- Cold Boot Attack: DRAM 냉각 후 재부팅해도 MSR은 CPU 내부 레지스터라 의미 없음.
실질적인 해제 경로는 시스템 재부팅 후 BIOS/UEFI 설정에서 Lock 정책을 변경하는 것입니다. 다만 많은 플랫폼은 해당 옵션을 노출하지 않거나, 마이크로코드 정책 때문에 사용자가 해제할 수 없을 수 있습니다.
RAPL Lock 요약 체크리스트
| 질문 | 답변 |
|---|---|
| Lock 비트 위치는? | PKG/DRAM/PSys: bit 63, PP0/PP1: bit 31 |
| 누가 설정하는가? | BIOS/UEFI 펌웨어 (POST 단계) |
| 재부팅 없이 해제 가능? | 불가능 (Write-Once Sticky) |
| Lock 시 쓰기하면? | #GP(0) 예외 또는 조용히 무시 |
| 보안 목적은? | Plundervolt 공격 방지, 열 설계 보호 |
| 서버에서 동적 제어 가능? | 가능 (IPMI/iLO로 대역외 제어) |
| 가상화 환경에서는? | 하이퍼바이저 정책 의존 (비노출/읽기 전용/합성 값 가능) |
| 확인 명령어는? | rdmsr -f 63:63 0x610 |
RAPL 도메인별 MSR 주소 정리
| 용도 | PKG | PP0 (Core) | PP1 (GPU) | DRAM | PSys |
|---|---|---|---|---|---|
| Power Limit | 0x610 | 0x638 | 0x640 | 0x618 | 0x64C |
| Energy Status | 0x611 | 0x639 | 0x641 | 0x619 | 0x64D |
| Power Info | 0x614 | 0x63A | 0x642 | 0x61C | — |
| Perf Status | — | 0x63B | — | 0x61B | — |
| Policy | — | 0x63C | 0x642 | — | — |
/sys/class/powercap/intel-rapl:0/energy_uj — 패키지 누적 에너지 (μJ)/sys/class/powercap/intel-rapl:0/constraint_0_power_limit_uw — PL1 (μW)/sys/class/powercap/intel-rapl:0/constraint_1_power_limit_uw — PL2 (μW)perf stat -e power/energy-pkg/도 동일한 MSR을 읽습니다.C-State 관련 MSR
C-State MSR은 CPU 유휴 상태(Idle State) 제어와 각 C-State의 레지던시(체류 시간) 측정에 사용됩니다. cpuidle 드라이버(intel_idle)와 turbostat이 이 MSR들을 활용합니다.
MSR_PKG_CST_CONFIG_CONTROL (0xE2) — 패키지 C-State 제어
| 비트 | 필드 | 설명 |
|---|---|---|
| 3:0 | Package C-State Limit | 허용 최대 패키지 C-State. 0=C0, 1=C2, 2=C6, 3=C6N, 7=제한 없음 (모델별 상이) |
| 10 | I/O MWAIT Redirection Enable | 1이면 I/O 명령어를 MWAIT C-State로 리다이렉션 |
| 15 | CFG Lock | 1이면 이 MSR 잠금 (재부팅까지 변경 불가). BIOS가 설정. |
| 25 | C3 State Auto Demotion Enable | 1이면 C3 자동 디모션 활성화 |
| 26 | C1 State Auto Demotion Enable | 1이면 C1 자동 디모션 활성화 |
| 27 | Enable C3 Undemotion | 1이면 C3 언디모션 활성화 |
| 28 | Enable C1 Undemotion | 1이면 C1 언디모션 활성화 |
C-State 레지던시 MSR (코어/패키지)
| MSR | 주소 | 범위 | 설명 |
|---|---|---|---|
MSR_CORE_C1_RES | 0x660 | 코어 | 코어 C1 레지던시 카운터 (TSC 단위) |
MSR_CORE_C3_RESIDENCY | 0x3FC | 코어 | 코어 C3 레지던시 카운터 |
MSR_CORE_C6_RESIDENCY | 0x3FD | 코어 | 코어 C6 레지던시 카운터 |
MSR_CORE_C7_RESIDENCY | 0x3FE | 코어 | 코어 C7 레지던시 카운터 |
MSR_PKG_C2_RESIDENCY | 0x60D | 패키지 | 패키지 C2 레지던시 카운터 |
MSR_PKG_C3_RESIDENCY | 0x3F8 | 패키지 | 패키지 C3 레지던시 카운터 |
MSR_PKG_C6_RESIDENCY | 0x3F9 | 패키지 | 패키지 C6 레지던시 카운터 |
MSR_PKG_C7_RESIDENCY | 0x3FA | 패키지 | 패키지 C7 레지던시 카운터 |
MSR_PKG_C8_RESIDENCY | 0x630 | 패키지 | 패키지 C8 레지던시 (Haswell+) |
MSR_PKG_C9_RESIDENCY | 0x631 | 패키지 | 패키지 C9 레지던시 (Haswell+) |
MSR_PKG_C10_RESIDENCY | 0x632 | 패키지 | 패키지 C10 레지던시 (가장 깊은 유휴) |
/* C-State 레지던시 비율 측정 */
static void measure_cstate_residency(unsigned int interval_ms)
{
u64 tsc0, tsc1, c6_0, c6_1, c7_0, c7_1;
tsc0 = rdtsc_ordered();
rdmsrl(MSR_CORE_C6_RESIDENCY, c6_0);
rdmsrl(MSR_CORE_C7_RESIDENCY, c7_0);
msleep(interval_ms);
tsc1 = rdtsc_ordered();
rdmsrl(MSR_CORE_C6_RESIDENCY, c6_1);
rdmsrl(MSR_CORE_C7_RESIDENCY, c7_1);
u64 dt = tsc1 - tsc0;
u64 dc6 = c6_1 - c6_0;
u64 dc7 = c7_1 - c7_0;
pr_info("C6 residency: %llu.%02llu%%\\n",
dc6 * 100 / dt, (dc6 * 10000 / dt) % 100);
pr_info("C7 residency: %llu.%02llu%%\\n",
dc7 * 100 / dt, (dc7 * 10000 / dt) % 100);
/*
* turbostat 출력과 대응:
* CPU%c1 = C1 residency %, CPU%c6 = C6 residency %
* Pkg%pc2 = Package C2 %, Pkg%pc6 = Package C6 %
* 이상적인 유휴 서버: Pkg%pc6 > 90%
*/
}
IA32_ENERGY_PERF_BIAS (0x1B0) — 에너지 성능 편향
HWP의 EPP보다 이전에 도입된(Sandy Bridge+) 레거시 에너지 성능 힌트입니다. HWP가 활성화되지 않은 시스템에서 CPU의 자율적 전력 관리 결정에 힌트를 제공합니다.
| 비트 | 필드 | 설명 |
|---|---|---|
| 3:0 | Energy Policy Hint | 0=최대 성능, 7=균형, 15=최대 절전 |
| 63:4 | — | 예약됨 |
| 값 | Linux 상수 | 의미 |
|---|---|---|
| 0 | ENERGY_PERF_BIAS_PERFORMANCE | 최대 성능 — 전력 무시 |
| 4 | ENERGY_PERF_BIAS_BALANCE_PERFORMANCE | 성능 우선 균형 |
| 6 | ENERGY_PERF_BIAS_NORMAL | 일반 (커널 기본값) |
| 8 | ENERGY_PERF_BIAS_BALANCE_POWERSAVE | 절전 우선 균형 |
| 15 | ENERGY_PERF_BIAS_POWERSAVE | 최대 절전 |
/sys/devices/system/cpu/cpu0/power/energy_perf_bias로 설정 가능.IA32_CLOCK_MODULATION (0x19A) — 클럭 변조 (레거시 스로틀링)
소프트웨어 제어 클럭 변조(Software Controlled Clock Modulation, aka duty cycling)를 설정합니다. 이는 가장 초기의 전력 관리 기법으로, CPU 클럭을 일정 비율로 on/off 하여 전력을 줄입니다. 현대 CPU에서는 DVFS가 훨씬 효율적이므로 거의 사용되지 않습니다.
| 비트 | 필드 | 설명 |
|---|---|---|
| 0 | Extended On-Demand Clock Modulation Enable | 1이면 fine-grained duty cycle 사용 (3:1 비트 포함 4비트) |
| 3:1 | On-Demand Clock Modulation Duty Cycle | 듀티 사이클: 001=12.5%, 010=25%, ..., 111=87.5% |
| 4 | On-Demand Clock Modulation Enable | 1이면 클럭 변조 활성화 |
AMD P-State MSR (CPPC)
AMD Zen 아키텍처 CPU는 ACPI CPPC(Collaborative Processor Performance Control) 프레임워크를 기반으로 P-State를 관리합니다. Linux의 amd-pstate 드라이버(5.17+)가 이 MSR들을 사용합니다.
MSR_AMD_CPPC_ENABLE (0xC00102B1) — CPPC 활성화
| 비트 | 필드 | 설명 |
|---|---|---|
| 0 | CPPC Enable | 1이면 CPPC(하드웨어 자율 P-State) 활성화 |
MSR_AMD_CPPC_CAP1 (0xC00102B0) — CPPC 성능 범위
| 비트 | 필드 | 설명 |
|---|---|---|
| 7:0 | Highest Performance | 최대 성능 수준 (부스트 포함) |
| 15:8 | Nominal Performance | 공칭 성능 수준 (Base Frequency 상당) |
| 23:16 | Lowest Nonlinear Performance | 에너지 효율이 급격히 떨어지기 시작하는 하한 |
| 31:24 | Lowest Performance | 절대 최저 성능 수준 |
MSR_AMD_CPPC_REQ (0xC00102B3) — CPPC 성능 요청
| 비트 | 필드 | 설명 |
|---|---|---|
| 7:0 | Maximum Performance | 허용 최대 성능 |
| 15:8 | Minimum Performance | 허용 최저 성능 |
| 23:16 | Desired Performance | 목표 성능 (0=자율) |
| 31:24 | Energy Performance Preference | EPP (0=성능, 255=절전) — Intel HWP EPP와 동일 의미 |
MSR_AMD_PSTATE_DEF (0xC0010064 ~ 0xC0010069) — P-State 정의
AMD CPU는 최대 8개의 하드웨어 P-State 정의를 MSR에 보관합니다 (P0~P7). 각 MSR은 FID(주파수), DID(분주비), VID(전압)를 인코딩합니다.
| 비트 | 필드 | 설명 |
|---|---|---|
| 7:0 | CpuFid | CPU Frequency ID |
| 13:8 | CpuDfsId | CPU DFS (Divider) ID |
| 21:14 | CpuVid | CPU Voltage ID |
| 22 | IddDiv | 전류 분배기 |
| 29:23 | IddValue | 전류 값 |
| 63 | PstateEn | 1이면 이 P-State 유효 |
/* AMD P-State 주파수 계산 */
static u32 amd_pstate_freq_mhz(u64 pstate_def)
{
u32 fid = pstate_def & 0xFF;
u32 did = (pstate_def >> 8) & 0x3F;
if (did == 0)
return 0; /* 0으로 나눗셈 방지 */
/* Frequency (MHz) = 200 × FID / DID */
return 200 * fid / did;
/*
* 예: Ryzen 9 7950X P0
* FID=0xB0 (176), DID=0x08 (8)
* Freq = 200 × 176 / 8 = 4400 MHz
*/
}
/* drivers/cpufreq/amd-pstate.c — CPPC 성능 요청 */
static void amd_pstate_update(struct amd_cpudata *cpudata,
u32 min_perf, u32 des_perf,
u32 max_perf, bool fast_switch)
{
u64 value = READ_ONCE(cpudata->cppc_req_cached);
value &= ~(AMD_CPPC_MIN_PERF_MASK | AMD_CPPC_DES_PERF_MASK |
AMD_CPPC_MAX_PERF_MASK);
value |= AMD_CPPC_MIN_PERF(min_perf);
value |= AMD_CPPC_DES_PERF(des_perf);
value |= AMD_CPPC_MAX_PERF(max_perf);
if (fast_switch)
wrmsrl(MSR_AMD_CPPC_REQ, value);
else
wrmsrl_on_cpu(cpudata->cpu, MSR_AMD_CPPC_REQ, value);
}
amd-pstate=passive: OS governor가 목표 주파수 결정 → 드라이버가 CPPC MSR에 변환하여 전달.amd-pstate=active: amd-pstate-epp 드라이버 사용, EPP 기반 하드웨어 자율 관리 (Intel HWP와 유사).amd-pstate=guided: OS가 min/max만 설정, 그 범위 내에서 하드웨어가 자율 결정.cpufreq 드라이버와 MSR 매핑(Mapping)
Linux 커널의 cpufreq 프레임워크는 MSR을 직접 노출하지 않고, sysfs를 통해 추상화합니다. 각 sysfs 파일이 어떤 MSR을 읽고 쓰는지 이해하면 디버깅에 유용합니다.
| sysfs 경로 | Intel MSR | AMD MSR | 설명 |
|---|---|---|---|
scaling_min_freq | HWP_REQUEST[7:0] | CPPC_REQ[15:8] | 최소 주파수 설정 |
scaling_max_freq | HWP_REQUEST[15:8] | CPPC_REQ[7:0] | 최대 주파수 설정 |
scaling_cur_freq | APERF/MPERF 계산 | APERF/MPERF 계산 | 현재 실제 주파수 |
cpuinfo_min_freq | PLATFORM_INFO[40:32] | CPPC_CAP1[31:24] | 하드웨어 최소 주파수 |
cpuinfo_max_freq | TURBO_RATIO_LIMIT[7:0] | CPPC_CAP1[7:0] | 하드웨어 최대 주파수 |
base_frequency | PLATFORM_INFO[15:8] | CPPC_CAP1[15:8] | 기본 주파수 (비터보) |
energy_performance_preference | HWP_REQUEST[31:24] | CPPC_REQ[31:24] | EPP 값 |
# 전력/주파수 관련 주요 확인 명령어
# 현재 주파수 정책 확인
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver # intel_pstate 또는 amd-pstate
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor # performance, powersave 등
# HWP/EPP 확인 (Intel)
cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference
cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences
# RAPL 전력 확인
cat /sys/class/powercap/intel-rapl:0/name # package-0
cat /sys/class/powercap/intel-rapl:0/energy_uj # 누적 에너지 (μJ)
cat /sys/class/powercap/intel-rapl:0/constraint_0_power_limit_uw # PL1 (μW)
# turbostat으로 종합 확인
turbostat --show Core,CPU,Avg_MHz,Busy%,Bzy_MHz,TSC_MHz,PkgWatt,CorWatt,Pkg%pc2,Pkg%pc6
# MSR 직접 읽기 (msr-tools)
rdmsr -p 0 0xCE # MSR_PLATFORM_INFO
rdmsr -p 0 0x1AD # MSR_TURBO_RATIO_LIMIT
rdmsr -p 0 0x774 -f 31:24 # HWP_REQUEST의 EPP 필드만
rdmsr -p 0 0x611 # PKG_ENERGY_STATUS (RAPL)
열 관리(Thermal Management) MSR
CPU 온도 모니터링과 열 스로틀링(thermal throttling) 제어를 위한 MSR입니다.
| MSR | 주소 | 설명 |
|---|---|---|
IA32_THERM_STATUS | 0x19C | 코어별 열 상태 및 온도 읽기 |
IA32_THERM_INTERRUPT | 0x19B | 열 인터럽트 임계값 설정 |
IA32_PACKAGE_THERM_STATUS | 0x1B1 | 패키지 전체 열 상태 |
IA32_PACKAGE_THERM_INTERRUPT | 0x1B2 | 패키지 열 인터럽트 설정 |
IA32_TEMPERATURE_TARGET | 0x1A2 | TjMax (최대 허용 온도, 읽기 전용) |
온도 계산 방법
Intel CPU는 TjMax로부터의 거리(Digital Readout)를 보고합니다:
/* drivers/hwmon/coretemp.c — 코어 온도 읽기 */
static int get_core_temp(int cpu)
{
u32 eax, edx;
int tjmax, temp;
/* TjMax 읽기 (보통 100°C) */
rdmsr_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
tjmax = (eax >> 16) & 0xFF;
/* 현재 온도 = TjMax - Digital Readout */
rdmsr_on_cpu(cpu, MSR_IA32_THERM_STATUS, &eax, &edx);
if (eax & 0x80000000) { /* Reading Valid 비트 */
temp = tjmax - ((eax >> 16) & 0x7F);
return temp; /* 섭씨 온도 */
}
return -1;
}
/sys/class/hwmon/hwmon*/temp*_input으로 유저 공간에서 읽을 수 있으며, 값은 밀리도(m°C) 단위입니다 (예: 65000 = 65°C). coretemp 드라이버가 위 MSR을 추상화합니다.TSC 관련 MSR
TSC(Time Stamp Counter)는 가장 자주 사용되는 MSR 중 하나로, 고해상도 시간 측정의 기반입니다.
| MSR | 주소 | 설명 |
|---|---|---|
IA32_TSC | 0x10 | TSC 카운터 값 (RDMSR 또는 RDTSC로 읽기) |
IA32_TSC_ADJUST | 0x3B | TSC 보정 오프셋 (가상화/핫플러그(Hotplug) 시 동기화용) |
IA32_TSC_DEADLINE | 0x6E0 | APIC TSC-Deadline 모드 타이머(Timer) 목표값 |
IA32_TSC_AUX | 0xC0000103 | RDTSCP로 읽는 보조 값 (보통 CPU ID) |
TSC-Deadline 모드
TSC-Deadline 모드는 APIC 타이머의 최신 모드로, 지정한 TSC 값에 도달하면 인터럽트를 발생시킵니다. 기존 one-shot/periodic 모드보다 정밀합니다.
/* arch/x86/kernel/apic/apic.c — TSC-Deadline 타이머 설정 */
static void setup_APIC_tsc_deadline(u64 deadline)
{
/* LVT Timer에 TSC-Deadline 모드 설정 */
apic_write(APIC_LVTT, APIC_LVT_TIMER_TSCDEADLINE | LOCAL_TIMER_VECTOR);
/* 목표 TSC 값 쓰기 — 이 값에 도달하면 인터럽트 */
wrmsrl(MSR_IA32_TSC_DEADLINE, deadline);
}
코드 설명
arch/x86/kernel/apic/apic.c의 TSC-Deadline 타이머 설정 코드입니다. MSR_IA32_TSC_DEADLINE(0x6E0)은 APIC 타이머의 최신 모드로, TSC 값 기반의 정밀한 인터럽트 스케줄링을 지원합니다.
- apic_write(APIC_LVTT, ...)LVT(Local Vector Table) Timer 레지스터에 TSC-Deadline 모드 플래그와 인터럽트 벡터를 설정합니다. 이 설정이 선행되어야
MSR_IA32_TSC_DEADLINE쓰기가 유효합니다. - wrmsrl(MSR_IA32_TSC_DEADLINE, deadline)목표 TSC 값을 MSR에 기록합니다. CPU의 TSC가 이 값에 도달하면 로컬 APIC 타이머 인터럽트가 발생합니다. one-shot 방식으로 동작하므로, 다음 인터럽트를 위해서는 새로운 deadline을 다시 써야 합니다.
IA32_TSC(0x10)와 연동하여 나노초 단위 정밀도를 제공합니다.
TSC_ADJUST — 가상화 환경 TSC 동기화
KVM은 vCPU 마이그레이션 시 IA32_TSC_ADJUST를 사용하여 게스트가 인식하는 TSC 값을 보정합니다. 게스트가 보는 TSC = 물리 TSC + TSC_OFFSET + TSC_ADJUST입니다.
보안 MSR (Spectre/Meltdown 완화)
2018년 Spectre/Meltdown 취약점 발견 이후, CPU 벤더들은 소프트웨어 기반 완화를 지원하기 위한 MSR을 대거 추가했습니다.
IA32_ARCH_CAPABILITIES (0x10A)
CPU가 특정 취약점에 하드웨어적으로 면역인지 보고합니다(읽기 전용). 커널은 이 MSR을 읽어 불필요한 완화를 건너뜁니다.
| 비트 | 이름 | 의미 |
|---|---|---|
| 0 | RDCL_NO | Meltdown(Rogue Data Cache Load) 면역 |
| 1 | IBRS_ALL | IBRS가 모든 분기에 적용 (Enhanced IBRS) |
| 2 | RSBA | Return Stack Buffer 대안 사용 (retpoline 주의) |
| 3 | SKIP_L1DFL_VMENTRY | VM 진입 시 L1D flush 불필요 |
| 4 | SSB_NO | Speculative Store Bypass 면역 |
| 5 | MDS_NO | Microarchitectural Data Sampling 면역 |
| 6 | IF_PSCHANGE_MC_NO | 페이지 크기 변경 시 MC 면역 |
| 7 | TSX_CTRL | IA32_TSX_CTRL MSR 존재 |
| 8 | TAA_NO | TSX Asynchronous Abort 면역 |
추론 실행 제어 MSR
| MSR | 주소 | 설명 |
|---|---|---|
IA32_SPEC_CTRL | 0x48 | IBRS(bit 0), STIBP(bit 1), SSBD(bit 2) 제어 |
IA32_PRED_CMD | 0x49 | IBPB(bit 0) — 분기 예측기 배리어 (쓰기 전용) |
IA32_FLUSH_CMD | 0x10B | L1D_FLUSH(bit 0) — L1 데이터 캐시 플러시 (쓰기 전용) |
각 완화 기법의 동작
/* arch/x86/kernel/cpu/bugs.c — Spectre v2 완화 */
/* IBRS: 간접 분기가 하위 권한 예측을 사용하지 않음 */
static void spec_ctrl_enable_ibrs(void)
{
u64 msr = this_cpu_read(x86_spec_ctrl_current);
msr |= SPEC_CTRL_IBRS;
native_wrmsrl(MSR_IA32_SPEC_CTRL, msr);
}
/* IBPB: 분기 예측기 전체 무효화 (컨텍스트 스위치 시) */
static void indirect_branch_prediction_barrier(void)
{
native_wrmsrl(MSR_IA32_PRED_CMD, PRED_CMD_IBPB);
}
/* SSBD: 추론적 저장-로드 바이패스 비활성화 (Spectre v4) */
static void ssb_disable(void)
{
u64 msr = this_cpu_read(x86_spec_ctrl_current);
msr |= SPEC_CTRL_SSBD;
native_wrmsrl(MSR_IA32_SPEC_CTRL, msr);
}
/* L1D Flush: VM 진입 전 L1 데이터 캐시 플러시 (L1TF 완화) */
static void l1d_flush_force(void)
{
native_wrmsrl(MSR_IA32_FLUSH_CMD, L1D_FLUSH);
}
코드 설명
arch/x86/kernel/cpu/bugs.c의 Spectre/Meltdown 완화 코드입니다. 추론 실행(Speculative Execution) 취약점을 소프트웨어로 완화하기 위해 전용 MSR을 사용합니다. MSR 인덱스는 arch/x86/include/asm/msr-index.h에 정의되어 있습니다.
- native_wrmsrl(MSR_IA32_SPEC_CTRL, msr)
IA32_SPEC_CTRL(0x48)에 IBRS(비트 0) 또는 SSBD(비트 2) 플래그를 설정합니다.native_wrmsrl은 paravirt 계층을 우회하여 직접WRMSR을 실행하는 변형으로, 보안에 민감한 경로에서 사용됩니다. - native_wrmsrl(MSR_IA32_PRED_CMD, PRED_CMD_IBPB)
IA32_PRED_CMD(0x49)는 쓰기 전용 MSR로, 비트 0에 1을 쓰면 분기 예측기(Branch Predictor)가 전체 무효화됩니다. 컨텍스트 스위치 시 이전 프로세스의 분기 예측 정보가 새 프로세스에 누출되는 것을 방지합니다. - native_wrmsrl(MSR_IA32_FLUSH_CMD, L1D_FLUSH)
IA32_FLUSH_CMD(0x10B)는 쓰기 전용 MSR로, L1 데이터 캐시를 즉시 플러시합니다. VM 진입 전에 호스트 데이터가 L1D에 남아 있으면 게스트가 L1TF(L1 Terminal Fault) 공격으로 읽을 수 있으므로, KVM이 VM entry 직전에 호출합니다.
가상화 MSR
Intel VMX 관련 MSR
VMX(Virtual Machine Extensions) 기능을 보고하고 제어하는 MSR입니다. KVM 모듈이 VMX 초기화 시 이 MSR들을 읽어 지원되는 기능을 확인합니다.
| MSR | 주소 | 설명 |
|---|---|---|
IA32_VMX_BASIC | 0x480 | VMCS 크기, 메모리 타입, 기본 기능 |
IA32_VMX_PINBASED_CTLS | 0x481 | Pin-Based VM-Execution Controls 허용 비트 |
IA32_VMX_PROCBASED_CTLS | 0x482 | Primary Processor-Based Controls 허용 비트 |
IA32_VMX_PROCBASED_CTLS2 | 0x48B | Secondary Processor-Based Controls |
IA32_VMX_EXIT_CTLS | 0x483 | VM-Exit Controls 허용 비트 |
IA32_VMX_ENTRY_CTLS | 0x484 | VM-Entry Controls 허용 비트 |
IA32_VMX_EPT_VPID_CAP | 0x48C | EPT/VPID 기능 보고 |
IA32_VMX_CR0_FIXED0/1 | 0x486/487 | VMX 모드에서 CR0 필수/허용 비트 |
IA32_VMX_CR4_FIXED0/1 | 0x488/489 | VMX 모드에서 CR4 필수/허용 비트 |
MSR 비트맵 (Intel VT-x)
VMCS의 MSR 비트맵은 게스트의 RDMSR/WRMSR이 VM exit를 유발할지 결정하는 4KB 비트맵입니다. 비트가 0이면 게스트가 직접 접근하고, 1이면 VM exit가 발생하여 KVM이 에뮬레이션합니다.
/* arch/x86/kvm/vmx/vmx.c — MSR 비트맵 설정 */
static void vmx_set_msr_bitmap_read(unsigned long *msr_bitmap,
u32 msr, bool intercept)
{
int f;
if (msr <= 0x1FFF)
f = 0; /* 하위 MSR 영역: 오프셋 0 */
else if (msr >= 0xC0000000 && msr <= 0xC0001FFF)
f = 1024; /* 상위 MSR 영역: 오프셋 1024바이트 */
else
return; /* 범위 밖 — 항상 intercept */
msr &= 0x1FFF;
if (intercept)
__set_bit(msr, msr_bitmap + f / sizeof(long));
else
__clear_bit(msr, msr_bitmap + f / sizeof(long));
}
AMD SVM MSRPM (MSR Permission Map)
AMD SVM은 8KB MSRPM을 사용하며, 각 MSR에 대해 2비트(읽기/쓰기 각각)를 할당합니다.
| MSRPM 오프셋 | MSR 범위 | 비트 할당 |
|---|---|---|
| 0x000 ~ 0x7FF | 0x00000000 ~ 0x00001FFF | bit 0 = read intercept, bit 1 = write intercept |
| 0x800 ~ 0xFFF | 0xC0000000 ~ 0xC0001FFF | 위와 동일 |
| 0x1000 ~ 0x17FF | 0xC0010000 ~ 0xC0011FFF | 위와 동일 |
KVM의 MSR 에뮬레이션
/* arch/x86/kvm/x86.c — 게스트 MSR 읽기 에뮬레이션 */
int kvm_get_msr(struct kvm_vcpu *vcpu, u32 index, u64 *data)
{
switch (index) {
case MSR_IA32_TSC:
*data = kvm_read_l1_tsc(vcpu, rdtsc());
break;
case MSR_IA32_SPEC_CTRL:
*data = vcpu->arch.spec_ctrl;
break;
case MSR_IA32_ARCH_CAPABILITIES:
*data = vcpu->arch.arch_capabilities;
break;
/* ... 수십 개의 MSR 에뮬레이션 */
default:
return kvm_x86_ops.get_msr(vcpu, index, data);
}
return 0;
}
MTRR / PAT MSR
MTRR (Memory Type Range Registers)
MTRR은 물리 메모리(Physical Memory) 영역의 캐시 정책(Uncacheable, Write-Combining, Write-Through, Write-Back 등)을 설정합니다. 주로 BIOS가 초기화하며, 그래픽 프레임버퍼 등 MMIO 영역에 Write-Combining을 설정합니다.
| MSR | 주소 | 설명 |
|---|---|---|
IA32_MTRRCAP | 0xFE | MTRR 기능: 가변 범위 수, FIX 지원, WC 지원 |
IA32_MTRR_DEF_TYPE | 0x2FF | 기본 메모리 타입 및 MTRR 활성화 |
IA32_MTRR_FIX64K_00000 | 0x250 | 고정 범위: 0x00000~0x7FFFF (64KB 단위) |
IA32_MTRR_FIX16K_80000 | 0x258 | 고정 범위: 0x80000~0xBFFFF (16KB 단위) |
IA32_MTRR_FIX4K_C0000 | 0x268~26F | 고정 범위: 0xC0000~0xFFFFF (4KB 단위) |
IA32_MTRR_PHYSBASEn | 0x200+2n | 가변 범위 n의 base 주소 및 타입 |
IA32_MTRR_PHYSMASKn | 0x201+2n | 가변 범위 n의 마스크 및 유효 비트 |
메모리 타입 인코딩
| 값 | 약칭 | 설명 |
|---|---|---|
| 0 | UC | Uncacheable — 캐시 사용 안 함 |
| 1 | WC | Write-Combining — 쓰기 결합 (프레임버퍼용) |
| 4 | WT | Write-Through — 쓰기 즉시 반영 |
| 5 | WP | Write-Protect — 읽기는 캐시, 쓰기는 UC처럼 |
| 6 | WB | Write-Back — 가장 빠름 (일반 RAM) |
PAT (Page Attribute Table)
PAT는 MTRR의 페이지 단위 확장으로, 페이지 테이블(Page Table) 엔트리의 PAT/PCD/PWT 비트 조합으로 8개 메모리 타입 중 하나를 선택합니다.
| MSR | 주소 | 설명 |
|---|---|---|
IA32_PAT | 0x277 | 8개 PAT 엔트리 (각 8비트, 총 64비트) |
/* arch/x86/mm/pat/memtype.c — PAT 초기화 */
void pat_init(void)
{
u64 pat;
/* Linux 기본 PAT 설정:
* PAT0=WB PAT1=WC PAT2=UC- PAT3=UC
* PAT4=WB PAT5=WC PAT6=UC- PAT7=UC */
pat = PAT(0, WB) | PAT(1, WC) | PAT(2, UC_MINUS) | PAT(3, UC) |
PAT(4, WB) | PAT(5, WC) | PAT(6, UC_MINUS) | PAT(7, UC);
wrmsrl(MSR_IA32_CR_PAT, pat);
}
코드 설명
arch/x86/mm/pat/memtype.c의 PAT(Page Attribute Table) 초기화 코드입니다. IA32_PAT(0x277) MSR은 64비트를 8개 엔트리(각 8비트)로 나누어, 페이지 테이블 엔트리의 PAT/PCD/PWT 비트 조합에 따라 메모리 캐시 타입을 결정합니다.
- PAT(n, type) 매크로8비트 메모리 타입 코드를 PAT MSR의 해당 엔트리 위치(n×8비트)에 배치합니다. WB=6(Write-Back), WC=1(Write-Combining), UC_MINUS=7, UC=0(Uncacheable)입니다.
- PAT0=WB, PAT1=WC리눅스는 BIOS 기본값 대신 자체 PAT 배치를 사용합니다. PAT0(WB)은 일반 RAM, PAT1(WC)은 프레임버퍼나 GPU 메모리에 사용됩니다. MTRR과 달리 PAT는 페이지 단위로 캐시 정책을 세밀하게 제어할 수 있습니다.
- wrmsrl(MSR_IA32_CR_PAT, pat)조합된 64비트 값을 PAT MSR에 기록합니다. 모든 CPU에서 동일한 PAT 설정을 사용해야 하므로, 부팅 시 각 CPU가 개별적으로 이 함수를 호출합니다.
Linux 커널 MSR API
Linux 커널은 다양한 MSR 접근 래퍼 함수를 제공합니다. 모두 arch/x86/include/asm/msr.h에 정의되어 있습니다.
기본 접근 함수
| 함수 | 서명 | 설명 |
|---|---|---|
rdmsr | rdmsr(msr, low, high) | 32비트 쌍으로 읽기 |
wrmsr | wrmsr(msr, low, high) | 32비트 쌍으로 쓰기 |
rdmsrl | rdmsrl(msr, val) | 64비트 값으로 읽기 |
wrmsrl | wrmsrl(msr, val) | 64비트 값으로 쓰기 |
안전한(safe) 변형
존재하지 않는 MSR에 접근하면 #GP 예외가 발생합니다. safe 변형은 예외를 잡아 에러 코드를 반환합니다.
| 함수 | 반환값 | 설명 |
|---|---|---|
rdmsrl_safe | int (0=성공, -EIO=실패) | 안전한 64비트 읽기 |
wrmsrl_safe | int (0=성공, -EIO=실패) | 안전한 64비트 쓰기 |
rdmsr_safe | int | 안전한 32비트 쌍 읽기 |
wrmsr_safe | int | 안전한 32비트 쌍 쓰기 |
/* safe 변형 사용 예 — MSR 존재 여부 불확실할 때 */
u64 val;
int err;
err = rdmsrl_safe(MSR_IA32_ARCH_CAPABILITIES, &val);
if (err) {
pr_info("IA32_ARCH_CAPABILITIES not supported\\n");
} else {
if (val & ARCH_CAP_RDCL_NO)
pr_info("CPU is not vulnerable to Meltdown\\n");
}
코드 설명
arch/x86/include/asm/msr.h에 정의된 rdmsrl_safe의 사용 예입니다. safe 변형은 존재하지 않는 MSR에 접근할 때 발생하는 #GP(0) 예외를 내부적으로 처리하여, 커널 패닉 없이 에러 코드를 반환합니다.
- rdmsrl_safe(MSR_IA32_ARCH_CAPABILITIES, &val)성공 시 0을 반환하고
val에 MSR 값을 저장합니다. 실패 시-EIO를 반환합니다. 내부적으로arch/x86/lib/msr.c의 예외 테이블(exception table) 메커니즘을 사용하여#GP예외 발생 시 에러 경로로 분기합니다. - if (err)구형 CPU나 일부 가상화 환경에서는
IA32_ARCH_CAPABILITIES(0x10A)가 존재하지 않습니다. safe 변형을 쓰지 않으면#GP로 커널이 중단되므로, MSR 존재 여부가 불확실한 경우 반드시 safe 변형을 사용해야 합니다. - ARCH_CAP_RDCL_NO비트 0을 검사하여 CPU가 Meltdown(Rogue Data Cache Load) 취약점에 하드웨어적으로 면역인지 확인합니다. 면역이면 커널이 KPTI(Kernel Page Table Isolation) 완화를 생략하여 성능 손실을 방지합니다.
Cross-CPU MSR 접근
다른 CPU의 MSR을 읽고 쓰려면 IPI를 통해 해당 CPU에서 실행해야 합니다.
| 함수 | 설명 |
|---|---|
rdmsr_on_cpu(cpu, msr, &lo, &hi) | 특정 CPU에서 MSR 읽기 |
wrmsr_on_cpu(cpu, msr, lo, hi) | 특정 CPU에서 MSR 쓰기 |
rdmsrl_on_cpu(cpu, msr, &val) | 64비트 버전 |
wrmsrl_on_cpu(cpu, msr, val) | 64비트 버전 |
rdmsr_safe_on_cpu | 안전한 Cross-CPU 읽기 |
/dev/cpu/N/msr — 유저 공간 접근
CONFIG_X86_MSR=y일 때 커널은 각 CPU별로 /dev/cpu/<N>/msr 캐릭터 디바이스를 생성합니다. pread()/pwrite()의 오프셋이 MSR 주소, 데이터가 8바이트 MSR 값입니다.
/* 유저 공간 MSR 읽기 예 (root 또는 CAP_SYS_RAWIO 필요) */
#include <fcntl.h>
#include <unistd.h>
int fd = open("/dev/cpu/0/msr", O_RDONLY);
uint64_t tsc;
pread(fd, &tsc, sizeof(tsc), 0x10); /* IA32_TSC */
printf("TSC = %llu\\n", tsc);
close(fd);
코드 설명
유저 공간에서 /dev/cpu/0/msr 장치 파일을 통해 MSR을 읽는 예제입니다. arch/x86/kernel/msr.c의 MSR 캐릭터 디바이스 드라이버가 이 인터페이스를 제공하며, CONFIG_X86_MSR=y로 빌드해야 합니다.
- open("/dev/cpu/0/msr", O_RDONLY)CPU 0의 MSR 장치를 엽니다. 파일 경로의 숫자가 CPU 번호를 나타냅니다.
root권한 또는CAP_SYS_RAWIO케이퍼빌리티가 필요합니다. 커널 5.10 이후msr모듈의 기본 접근이 제한되어allow_writes=on파라미터가 필요할 수 있습니다. - pread(fd, &tsc, sizeof(tsc), 0x10)
pread의 오프셋(0x10)이 MSR 주소(IA32_TSC)에 해당합니다. 드라이버 내부에서 해당 CPU에 IPI를 보내RDMSR을 실행하고, 8바이트(64비트) MSR 값을 유저 버퍼에 복사합니다. 쓰기는pwrite로 수행합니다.
AMD 전용 MSR
AMD 프로세서는 0xC0010000~0xC001FFFF 범위에 고유 MSR을 가지고 있습니다.
| MSR | 주소 | 설명 |
|---|---|---|
MSR_AMD64_HWCR | 0xC0010015 | 하드웨어 구성: SMM Lock, TSC Freq Sel, TLB Flush Filter 등 |
MSR_AMD64_SYSCFG | 0xC0010010 | 시스템 구성: MTRR 확장, Tom2 활성화, IORRs 등 |
MSR_AMD64_NB_CFG | 0xC001001F | Northbridge 구성: 초기 APIC ID 크기 등 |
MSR_AMD64_PATCH_LEVEL | 0x0000008B | 마이크로코드 패치(Patch) 수준 |
MSR_AMD64_PATCH_LOADER | 0xC0010020 | 마이크로코드 로더(Loader) 트리거 |
MSR_AMD64_OSVW_ID_LENGTH | 0xC0010140 | OS Visible Workarounds 길이 |
MSR_AMD64_OSVW_STATUS | 0xC0010141 | OS Visible Workarounds 상태 |
MSR_AMD64_DE_CFG | 0xC0011029 | Decode Config (LFENCE 직렬화 등) |
MSR_AMD64_LS_CFG | 0xC0011020 | Load/Store Config (SSB 비활성화 등) |
MSR_AMD_PPIN | 0xC00102F1 | Protected Processor ID Number |
AMD LFENCE 직렬화
/* arch/x86/kernel/cpu/amd.c — LFENCE를 직렬화 명령어로 설정 */
static void init_amd_lfence(struct cpuinfo_x86 *c)
{
u64 val;
rdmsrl(MSR_AMD64_DE_CFG, val);
if (!(val & BIT(1))) {
val |= BIT(1); /* LFENCE를 dispatch serializing으로 */
wrmsrl(MSR_AMD64_DE_CFG, val);
}
set_cpu_cap(c, X86_FEATURE_LFENCE_RDTSC);
}
AMD SVM 관련 MSR
| MSR | 주소 | 설명 |
|---|---|---|
MSR_VM_CR | 0xC0010114 | SVM 잠금 및 활성화 제어 |
MSR_VM_HSAVE_PA | 0xC0010117 | 호스트 상태 저장 영역 물리 주소(Physical Address) |
MSR_AMD64_SEV | 0xC0010131 | SEV(Secure Encrypted Virtualization) 상태 |
MSR_AMD64_SEV_ES | 0xC0010131 | SEV-ES/SEV-SNP 비트 포함 |
실전 예제
예제 1: 현재 CPU 주파수 읽기 (APERF/MPERF)
#include <linux/module.h>
#include <asm/msr.h>
#include <asm/cpu_device_id.h>
static int __init freq_init(void)
{
u64 aperf0, aperf1, mperf0, mperf1;
u64 tsc_khz_val = tsc_khz;
unsigned long flags;
local_irq_save(flags);
rdmsrl(MSR_IA32_APERF, aperf0);
rdmsrl(MSR_IA32_MPERF, mperf0);
local_irq_restore(flags);
msleep(100); /* 100ms 대기 */
local_irq_save(flags);
rdmsrl(MSR_IA32_APERF, aperf1);
rdmsrl(MSR_IA32_MPERF, mperf1);
local_irq_restore(flags);
/* freq = tsc_freq * delta_aperf / delta_mperf */
u64 freq_khz = div64_u64(tsc_khz_val * (aperf1 - aperf0),
(mperf1 - mperf0));
pr_info("CPU %d actual freq: %llu MHz\\n",
smp_processor_id(), freq_khz / 1000);
return 0;
}
module_init(freq_init);
MODULE_LICENSE("GPL");
예제 2: PMU 카운터로 IPC 측정
/* 고정 카운터로 Instructions Per Cycle 측정 */
static void measure_ipc(void)
{
u64 inst0, inst1, cycles0, cycles1;
/* 고정 카운터 활성화: OS + USR 모드 */
wrmsrl(MSR_CORE_PERF_FIXED_CTR_CTRL, 0x33); /* CTR0, CTR1 활성화 */
wrmsrl(MSR_CORE_PERF_GLOBAL_CTRL,
(1ULL << 32) | (1ULL << 33)); /* Fixed CTR0,1 전역 활성화 */
rdmsrl(MSR_CORE_PERF_FIXED_CTR0, inst0); /* Instructions Retired */
rdmsrl(MSR_CORE_PERF_FIXED_CTR1, cycles0); /* Unhalted Cycles */
/* ... 측정 대상 코드 ... */
rdmsrl(MSR_CORE_PERF_FIXED_CTR0, inst1);
rdmsrl(MSR_CORE_PERF_FIXED_CTR1, cycles1);
pr_info("IPC = %llu.%02llu\\n",
(inst1 - inst0) / (cycles1 - cycles0),
((inst1 - inst0) * 100 / (cycles1 - cycles0)) % 100);
}
예제 3: CPU 코어 온도 읽기
static int read_core_temp(int cpu)
{
u32 eax, edx;
int tjmax, offset;
/* TjMax (보통 100°C) */
rdmsr_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
tjmax = (eax >> 16) & 0xFF;
/* Digital Readout */
rdmsr_on_cpu(cpu, MSR_IA32_THERM_STATUS, &eax, &edx);
if (!(eax & (1 << 31)))
return -1; /* 값이 유효하지 않음 */
offset = (eax >> 16) & 0x7F;
return tjmax - offset; /* °C */
}
예제 4: TSC 기반 정밀 시간 측정
static inline u64 rdtsc_ordered(void)
{
/* LFENCE + RDTSC 로 순서 보장 */
barrier();
return rdtsc();
}
static void benchmark(void)
{
u64 t0, t1;
unsigned long flags;
local_irq_save(flags);
t0 = rdtsc_ordered();
/* 측정 대상 코드 */
some_function();
t1 = rdtsc_ordered();
local_irq_restore(flags);
pr_info("Elapsed: %llu cycles (%llu ns)\\n",
t1 - t0,
div64_u64((t1 - t0) * 1000000, tsc_khz));
}
MSR 디버깅
msr-tools 패키지
유저 공간에서 MSR을 직접 읽고 쓸 수 있는 도구입니다.
# 설치 (Debian/Ubuntu)
sudo apt install msr-tools
# msr 커널 모듈 로드
sudo modprobe msr
# IA32_TSC (0x10) 읽기 — CPU 0
sudo rdmsr -p 0 0x10
# IA32_EFER (0xC0000080) 읽기 — 모든 CPU
sudo rdmsr -a 0xC0000080
# IA32_MISC_ENABLE (0x1A0) 읽기 — 비트별 출력
sudo rdmsr -p 0 -b 0x1A0
# IA32_SPEC_CTRL (0x48) 읽기 — Spectre 완화 상태
sudo rdmsr -p 0 0x48
# MSR 쓰기 (주의: 잘못된 값은 시스템 크래시 유발)
sudo wrmsr -p 0 0x48 0x7 # IBRS + STIBP + SSBD 활성화
perf 도구를 이용한 PMU 확인
# PMU 하드웨어 이벤트 목록
perf list hw cache
# PMU 카운터 직접 지정 (event=0x2E, umask=0x41 = LLC-misses)
perf stat -e r412e ./program
# MSR 기반 이벤트 추적
perf stat -e cycles,instructions,cache-misses,branch-misses ./program
# Architectural MSR 기능 확인
perf stat -e cpu/event=0x00,umask=0x01/ -- sleep 1
커널 로그에서 MSR 관련 정보
# 부팅 시 MSR 관련 메시지
dmesg | grep -i msr
# Spectre/Meltdown 완화 상태
cat /sys/devices/system/cpu/vulnerabilities/*
# MTRR 설정 확인
cat /proc/mtrr
# PAT 설정 확인
dmesg | grep -i pat
일반적 함정과 주의사항
- #GP 예외: 존재하지 않는 MSR에 RDMSR/WRMSR 시 발생. 반드시 CPUID로 확인하거나
*_safe()변형 사용 - Reserved 비트: MSR의 예약 비트에 1을 쓰면 #GP 발생. 항상 Read-Modify-Write 패턴 사용
- per-CPU 특성: 대부분의 MSR은 코어별로 독립적. CPU 0에서 쓴 값이 CPU 1에 적용되지 않음
- Lock 비트: IA32_FEATURE_CONTROL 등의 Lock 비트가 설정되면 재부팅 전까지 변경 불가
- 가상화 환경: VM 안에서 일부 MSR은 하이퍼바이저가 에뮬레이션하므로 물리 하드웨어와 값이 다를 수 있음
- Lockdown LSM: Secure Boot 환경에서
/dev/cpu/*/msr을 통한 쓰기가 차단될 수 있음
MSR 주요 레퍼런스 테이블
자주 사용되는 ~70개 주요 MSR의 종합 레퍼런스입니다. 커널 헤더 arch/x86/include/asm/msr-index.h에서 전체 매크로(Macro)를 확인할 수 있습니다.
| 주소 | 커널 매크로 | 이름 | R/W | 카테고리 |
|---|---|---|---|---|
0x10 | MSR_IA32_TSC | Time Stamp Counter | R/W | TSC |
0x1B | MSR_IA32_APICBASE | APIC Base Address | R/W | APIC |
0x3A | MSR_IA32_FEAT_CTL | Feature Control | R/W* | 제어 |
0x3B | MSR_IA32_TSC_ADJUST | TSC Adjust | R/W | TSC |
0x48 | MSR_IA32_SPEC_CTRL | Speculation Control | R/W | 보안 |
0x49 | MSR_IA32_PRED_CMD | Prediction Command | W | 보안 |
0x8B | MSR_IA32_UCODE_REV | Microcode Revision | R | 시스템 |
0xCE | MSR_PLATFORM_INFO | Platform Info | R | 주파수 |
0xE7 | MSR_IA32_MPERF | Max Performance Counter | R/W | 주파수 |
0xE8 | MSR_IA32_APERF | Actual Performance Counter | R/W | 주파수 |
0xFE | MSR_IA32_MTRRCAP | MTRR Capability | R | MTRR |
0x10A | MSR_IA32_ARCH_CAPABILITIES | Arch Capabilities | R | 보안 |
0x10B | MSR_IA32_FLUSH_CMD | L1D Flush Command | W | 보안 |
0x174 | MSR_IA32_SYSENTER_CS | SYSENTER CS | R/W | 시스콜 |
0x175 | MSR_IA32_SYSENTER_ESP | SYSENTER ESP | R/W | 시스콜 |
0x176 | MSR_IA32_SYSENTER_EIP | SYSENTER EIP | R/W | 시스콜 |
0x186 | MSR_P6_EVNTSEL0 | Perf Event Select 0 | R/W | PMU |
0x198 | MSR_IA32_PERF_STATUS | Perf Status | R | 주파수 |
0x199 | MSR_IA32_PERF_CTL | Perf Control | R/W | 주파수 |
0x19A | MSR_IA32_CLOCK_MODULATION | Clock Modulation | R/W | 열 |
0x19B | MSR_IA32_THERM_INTERRUPT | Thermal Interrupt | R/W | 열 |
0x19C | MSR_IA32_THERM_STATUS | Thermal Status | R/W | 열 |
0x1A0 | MSR_IA32_MISC_ENABLE | Misc Enable | R/W | 제어 |
0x1A2 | MSR_IA32_TEMPERATURE_TARGET | Temperature Target (TjMax) | R | 열 |
0x1AD | MSR_TURBO_RATIO_LIMIT | Turbo Ratio Limit | R | 주파수 |
0x1B1 | MSR_IA32_PACKAGE_THERM_STATUS | Package Thermal Status | R/W | 열 |
0x200 | MSR_MTRR_PHYS_BASE(0) | MTRR Phys Base 0 | R/W | MTRR |
0x250 | MSR_MTRR_FIX64K_00000 | MTRR Fixed 64K | R/W | MTRR |
0x277 | MSR_IA32_CR_PAT | Page Attribute Table | R/W | PAT |
0x2FF | MSR_MTRR_DEF_TYPE | MTRR Default Type | R/W | MTRR |
0x309 | MSR_CORE_PERF_FIXED_CTR0 | Fixed CTR: Inst Retired | R/W | PMU |
0x30A | MSR_CORE_PERF_FIXED_CTR1 | Fixed CTR: Unhalted Cycles | R/W | PMU |
0x30B | MSR_CORE_PERF_FIXED_CTR2 | Fixed CTR: Ref Cycles | R/W | PMU |
0x38D | MSR_CORE_PERF_FIXED_CTR_CTRL | Fixed CTR Control | R/W | PMU |
0x38E | MSR_CORE_PERF_GLOBAL_STATUS | Global PMU Status | R | PMU |
0x38F | MSR_CORE_PERF_GLOBAL_CTRL | Global PMU Control | R/W | PMU |
0x480 | MSR_IA32_VMX_BASIC | VMX Basic | R | VMX |
0x48B | MSR_IA32_VMX_PROCBASED_CTLS2 | VMX Secondary Controls | R | VMX |
0x48C | MSR_IA32_VMX_EPT_VPID_CAP | EPT/VPID Capability | R | VMX |
0x6E0 | MSR_IA32_TSC_DEADLINE | TSC Deadline | R/W | TSC |
0x770 | MSR_IA32_PM_ENABLE | HWP Enable | R/W | HWP |
0x771 | MSR_IA32_HWP_CAPABILITIES | HWP Capabilities | R | HWP |
0x774 | MSR_IA32_HWP_REQUEST | HWP Request | R/W | HWP |
0xC0000080 | MSR_EFER | Extended Feature Enable | R/W | 제어 |
0xC0000081 | MSR_STAR | SYSCALL CS/SS | R/W | 시스콜 |
0xC0000082 | MSR_LSTAR | SYSCALL RIP (64-bit) | R/W | 시스콜 |
0xC0000084 | MSR_SYSCALL_MASK | SYSCALL RFLAGS Mask | R/W | 시스콜 |
0xC0000100 | MSR_FS_BASE | FS Base (64-bit) | R/W | 세그먼트 |
0xC0000101 | MSR_GS_BASE | GS Base (64-bit) | R/W | 세그먼트 |
0xC0000102 | MSR_KERNEL_GS_BASE | Kernel GS Base (SWAPGS) | R/W | 세그먼트 |
0xC0000103 | MSR_TSC_AUX | TSC Aux (RDTSCP) | R/W | TSC |
0x1B0 | MSR_IA32_ENERGY_PERF_BIAS | Energy Perf Bias (EPB) | R/W | 전력 |
0x3F8 | MSR_PKG_C3_RESIDENCY | Package C3 Residency | R | C-State |
0x3F9 | MSR_PKG_C6_RESIDENCY | Package C6 Residency | R | C-State |
0x3FA | MSR_PKG_C7_RESIDENCY | Package C7 Residency | R | C-State |
0x3FC | MSR_CORE_C3_RESIDENCY | Core C3 Residency | R | C-State |
0x3FD | MSR_CORE_C6_RESIDENCY | Core C6 Residency | R | C-State |
0x3FE | MSR_CORE_C7_RESIDENCY | Core C7 Residency | R | C-State |
0x606 | MSR_RAPL_POWER_UNIT | RAPL Power Unit | R | RAPL |
0x610 | MSR_PKG_POWER_LIMIT | Package Power Limit (PL1/PL2) | R/W | RAPL |
0x611 | MSR_PKG_ENERGY_STATUS | Package Energy Status | R | RAPL |
0x614 | MSR_PKG_POWER_INFO | Package Power Info (TDP) | R | RAPL |
0x618 | MSR_DRAM_POWER_LIMIT | DRAM Power Limit | R/W | RAPL |
0x619 | MSR_DRAM_ENERGY_STATUS | DRAM Energy Status | R | RAPL |
0x638 | MSR_PP0_POWER_LIMIT | Core Power Limit (PP0) | R/W | RAPL |
0x639 | MSR_PP0_ENERGY_STATUS | Core Energy Status (PP0) | R | RAPL |
0x640 | MSR_PP1_POWER_LIMIT | GPU Power Limit (PP1) | R/W | RAPL |
0x660 | MSR_CORE_C1_RES | Core C1 Residency | R | C-State |
0x772 | MSR_IA32_HWP_REQUEST_PKG | HWP Package Request | R/W | HWP |
0x773 | MSR_IA32_HWP_INTERRUPT | HWP Interrupt Enable | R/W | HWP |
0x777 | MSR_IA32_HWP_STATUS | HWP Status | R/W | HWP |
0xC0010015 | MSR_AMD64_HWCR | AMD HW Config | R/W | AMD |
0xC0010064 | MSR_AMD_PSTATE_DEF_BASE | AMD P-State Def 0 (P0) | R | AMD 주파수 |
0xC0011029 | MSR_AMD64_DE_CFG | AMD Decode Config | R/W | AMD |
0xC00102B0 | MSR_AMD_CPPC_CAP1 | AMD CPPC Capabilities | R | AMD 주파수 |
0xC00102B1 | MSR_AMD_CPPC_ENABLE | AMD CPPC Enable | R/W | AMD 주파수 |
0xC00102B3 | MSR_AMD_CPPC_REQ | AMD CPPC Request | R/W | AMD 주파수 |
MSR 접근 패턴과 API
MSR 접근은 단순한 RDMSR/WRMSR 명령어 실행 이상의 복잡한 계층 구조를 가집니다. 유저 공간에서의 접근 요청이 커널을 거쳐 하드웨어에 도달하기까지의 전체 경로, 안전한 에러 처리 패턴, 그리고 paravirt 환경에서의 차이를 심층적으로 다룹니다.
rdmsrl_safe / wrmsrl_safe 에러 처리 패턴
프로덕션 코드에서는 반드시 _safe 변형을 사용하여 #GP 예외를 안전하게 처리해야 합니다. 특히 CPU 세대별로 지원 여부가 다른 MSR에 접근할 때 필수입니다.
/* arch/x86/kernel/cpu/common.c — 다중 MSR 프로빙 패턴 */
struct msr_probe {
u32 msr;
const char *name;
};
static const struct msr_probe security_msrs[] = {
{ MSR_IA32_ARCH_CAPABILITIES, "ARCH_CAP" },
{ MSR_IA32_CORE_CAPABILITIES, "CORE_CAP" },
{ MSR_IA32_TSX_CTRL, "TSX_CTRL" },
{ 0, NULL },
};
static void probe_security_msrs(void)
{
const struct msr_probe *p;
u64 val;
int err;
for (p = security_msrs; p->name; p++) {
err = rdmsrl_safe(p->msr, &val);
if (err) {
pr_info("%s: not supported\n", p->name);
continue;
}
pr_info("%s: 0x%016llx\n", p->name, val);
}
}
on_each_cpu를 이용한 전체 CPU MSR 쓰기
MSR은 코어별로 독립적이므로, 모든 CPU에 동일한 값을 설정하려면 IPI를 사용해야 합니다.
/* 모든 CPU에 MSR 값 브로드캐스트 쓰기 */
struct msr_write_info {
u32 msr;
u64 val;
};
static void __msr_write_cpu(void *info)
{
struct msr_write_info *w = info;
wrmsrl(w->msr, w->val);
}
static void msr_write_all_cpus(u32 msr, u64 val)
{
struct msr_write_info info = { .msr = msr, .val = val };
/* preemption 비활성화 상태에서 IPI 전송 */
on_each_cpu(__msr_write_cpu, &info, 1);
pr_info("MSR 0x%x = 0x%llx written to all CPUs\n", msr, val);
}
native_read_msr vs paravirt
Linux 커널은 paravirt ops를 통해 MSR 접근을 가상화할 수 있습니다. 네이티브 환경에서는 직접 RDMSR을 실행하지만, KVM/Xen 게스트에서는 하이퍼바이저가 가로챌 수 있습니다.
/* arch/x86/include/asm/paravirt.h — paravirt MSR 접근 */
static inline u64 paravirt_read_msr(unsigned msr)
{
return PVOP_CALL1(u64, cpu.read_msr, msr);
}
/* arch/x86/kernel/paravirt.c — 네이티브 구현 */
static u64 native_read_msr(unsigned int msr)
{
DECLARE_ARGS(val, low, high);
asm volatile("1: rdmsr\n"
"2:\n"
_ASM_EXTABLE_TYPE(1b, 2b, EX_TYPE_RDMSR)
: EAX_EDX_RET(val, low, high)
: "c" (msr));
return EAX_EDX_VAL(val, low, high);
}
/* Xen PV 환경: MSR 접근이 하이퍼콜로 변환됨 */
/* xen_read_msr_safe() → HYPERVISOR_msr_op() */
CONFIG_PARAVIRT=y 커널에서 MSR 접근은 간접 호출을 통하며, paravirt patching이 부팅 시 네이티브 코드로 대체될 수 있습니다. pv_ops.cpu.read_msr가 native_read_msr로 패치되면 오버헤드가 제거됩니다.Intel TPMI (Topology Aware Register and PM Capsule Interface)
TPMI는 Intel Sapphire Rapids 이후 세대에서 도입된 새로운 레지스터 접근 메커니즘입니다. 기존 MSR이 코어별로 접근해야 하는 한계를 넘어, PCI VSEC(Vendor Specific Extended Capability)를 통해 MMIO 기반으로 다이(die)별·도메인별 전력/성능 레지스터에 접근할 수 있습니다.
TPMI 드라이버 구조
/* drivers/platform/x86/intel/tpmi.c — TPMI 플랫폼 드라이버 */
struct intel_tpmi_info {
struct platform_device *pdev;
struct intel_tpmi_plat_info *plat_info;
void __iomem *base; /* MMIO base from PCI BAR */
u32 feature_count;
struct intel_tpmi_feature *features;
};
/* Feature ID로 MMIO 주소 조회 */
static void __iomem *tpmi_get_feature_base(
struct intel_tpmi_info *tpmi,
int feature_id, int die)
{
struct intel_tpmi_feature *f;
int i;
for (i = 0; i < tpmi->feature_count; i++) {
f = &tpmi->features[i];
if (f->id == feature_id)
return tpmi->base + f->offset +
die * f->die_stride;
}
return NULL;
}
TPMI Feature Discovery
/* TPMI feature enumeration via PCI VSEC */
static int tpmi_probe(struct platform_device *pdev)
{
struct intel_tpmi_info *tpmi;
u32 header, num_entries;
int i;
tpmi = devm_kzalloc(&pdev->dev, sizeof(*tpmi), GFP_KERNEL);
tpmi->base = devm_ioremap_resource(&pdev->dev, ...);
/* TPMI 헤더에서 feature 수 읽기 */
header = readl(tpmi->base);
num_entries = FIELD_GET(TPMI_HDR_NUM_ENTRIES, header);
for (i = 0; i < num_entries; i++) {
u32 entry = readl(tpmi->base + 4 + i * 4);
u16 feature_id = FIELD_GET(TPMI_ENTRY_ID, entry);
switch (feature_id) {
case TPMI_ID_RAPL:
tpmi_create_device("intel_rapl_tpmi", ...);
break;
case TPMI_ID_SST:
tpmi_create_device("isst_tpmi", ...);
break;
case TPMI_ID_UNCORE:
tpmi_create_device("intel_uncore_tpmi", ...);
break;
}
}
return 0;
}
CONFIG_INTEL_TPMI=m으로 지원됩니다.AMD Zen MSR 상세
AMD Zen 아키텍처(Zen 1~Zen 5)는 Intel과 다른 고유 MSR 체계를 가지고 있습니다. 특히 전력 관리에서 PPT(Package Power Tracking), TDP, EDC(Electrical Design Current), THM(Thermal) 제한이 STAPM(Skin Temperature Aware Power Management)과 연동되어 동적으로 부스트 클럭을 제어합니다.
AMD 주파수 제어 MSR
/* AMD P-State MSR (Zen 2+) */
/* MSR_AMD_PSTATE_DEF_BASE (0xC0010064) + n → P-State n 정의 */
struct amd_pstate_def {
u64 CpuFid : 8; /* [7:0] CPU 주파수 ID */
u64 CpuDfsId : 6; /* [13:8] CPU DFS(분주) ID */
u64 CpuVid : 8; /* [21:14] CPU 전압 ID */
u64 IddValue : 8; /* [29:22] 전류 값 */
u64 IddDiv : 2; /* [31:30] 전류 분배 */
u64 Rsvd : 31; /* [62:32] 예약 */
u64 PsEn : 1; /* [63] P-State 활성화 */
};
/* 주파수 계산: Core_freq = 200MHz × CpuFid / CpuDfsId */
static u32 amd_pstate_freq(u64 pstate_def)
{
u32 fid = pstate_def & 0xFF;
u32 dfs = (pstate_def >> 8) & 0x3F;
if (dfs == 0)
return 0;
return 200 * fid / dfs; /* MHz */
}
AMD CPPC MSR (Zen 3+)
/* drivers/cpufreq/amd-pstate.c — CPPC MSR 기반 주파수 제어 */
static int amd_pstate_set_epp(struct cpufreq_policy *policy,
u8 epp)
{
u64 cppc_req;
int ret;
ret = rdmsrl_safe(MSR_AMD_CPPC_REQ, &cppc_req);
if (ret)
return ret;
/* EPP 필드: bits [31:24] */
cppc_req &= ~GENMASK_ULL(31, 24);
cppc_req |= ((u64)epp << 24);
/* Min/Max perf 설정 */
cppc_req &= ~GENMASK_ULL(7, 0); /* min perf */
cppc_req |= policy->min / 25; /* 25 MHz 단위 */
cppc_req &= ~GENMASK_ULL(15, 8); /* max perf */
cppc_req |= ((u64)(policy->max / 25)) << 8;
wrmsrl_on_cpu(smp_processor_id(), MSR_AMD_CPPC_REQ, cppc_req);
return 0;
}
Precision Boost Overdrive MSR
/* AMD PBO/Curve Optimizer 관련 MSR (비공식, SMU 통신) */
#define MSR_AMD_CPPC_CAP1 0xC00102B0
#define MSR_AMD_CPPC_ENABLE 0xC00102B1
#define MSR_AMD_CPPC_CAP2 0xC00102B2
#define MSR_AMD_CPPC_REQ 0xC00102B3
#define MSR_AMD_CPPC_STATUS 0xC00102B4
/* CPPC Capabilities 읽기 (Zen 3+) */
static void read_amd_cppc_caps(void)
{
u64 cap1, cap2;
rdmsrl(MSR_AMD_CPPC_CAP1, cap1);
pr_info("Highest Perf: %llu\n", cap1 & 0xFF);
pr_info("Nominal Perf: %llu\n", (cap1 >> 8) & 0xFF);
pr_info("Lowest NL: %llu\n", (cap1 >> 16) & 0xFF);
pr_info("Lowest Perf: %llu\n", (cap1 >> 24) & 0xFF);
}
- Zen 1/+: 레거시 P-State MSR만 지원, CPPC는 ACPI 메모리 기반만
- Zen 2: MSR 기반 CPPC 일부 지원 시작
- Zen 3: 완전한 MSR 기반 CPPC, amd-pstate 드라이버 사용 가능
- Zen 4/5: CPPC Preferred Core, Guided Autonomous Mode, EPP 지원
PMU MSR과 perf 연동
Performance Monitoring Unit(PMU)는 하드웨어 이벤트 카운터를 제공하며, Linux의 perf_event 서브시스템이 이를 추상화합니다. 이 섹션에서는 PMU MSR의 정밀한 설정, PEBS(Processor Event-Based Sampling), LBR(Last Branch Record), Intel PT(Processor Trace) 등 고급 프로파일링(Profiling) 기능의 MSR 수준 동작을 다룹니다.
IA32_PERF_GLOBAL_CTRL 설정
/* arch/x86/events/intel/core.c — PMU 글로벌 활성화 */
static void intel_pmu_enable_all(int added)
{
struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events);
u64 ctrl;
/* 활성화된 카운터 비트 마스크 구성 */
ctrl = cpuc->intel_ctrl_guest_mask |
cpuc->intel_ctrl_host_mask;
/* IA32_PERF_GLOBAL_CTRL (0x38F):
* Bits [N-1:0] = GP 카운터 N개 활성화
* Bits [34:32] = Fixed 카운터 3개 활성화
* Bit [35] = Perf Metrics 활성화 (ICL+)
*/
wrmsrl(MSR_CORE_PERF_GLOBAL_CTRL, ctrl);
}
PEBS 구성
/* PEBS (Processor Event-Based Sampling) 설정 */
static void intel_pmu_pebs_enable(struct perf_event *event)
{
struct hw_perf_event *hwc = &event->hw;
u64 pebs_enable;
/* IA32_PEBS_ENABLE (0x3F1):
* Bit n = GP 카운터 n에 대해 PEBS 활성화
* Bit 32+n = GP 카운터 n에 대한 PEBS output 선택
*/
rdmsrl(MSR_IA32_PEBS_ENABLE, pebs_enable);
pebs_enable |= 1ULL << hwc->idx;
/* DS Area (Debug Store) 설정 — PEBS 레코드 버퍼 */
/* IA32_DS_AREA (0x600): DS 관리 영역의 선형 주소 */
wrmsrl(MSR_IA32_DS_AREA, (unsigned long)cpuc->ds);
wrmsrl(MSR_IA32_PEBS_ENABLE, pebs_enable);
}
/* PEBS 레코드 구조: 정확한 IP, 레지스터 값, 메모리 주소 등 포함 */
struct pebs_basic {
u64 format_size;
u64 ip; /* 정확한 명령어 주소 (EventingIP) */
u64 applicable_counters;
u64 tsc;
};
LBR MSR 설정
/* LBR (Last Branch Record) MSR 구성 */
static void intel_pmu_lbr_enable_all(bool pmi)
{
struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events);
/* MSR_LBR_SELECT (0x1C8): LBR 필터링
* Bit 0: CPL_EQ_0 — Ring 0 분기 기록
* Bit 1: CPL_NEQ_0 — Ring 3 분기 기록
* Bit 2: JCC — 조건 분기
* Bit 3: NEAR_REL_CALL — 근접 상대 콜
* Bit 4: NEAR_IND_CALL — 근접 간접 콜
* Bit 5: NEAR_RET — 근접 리턴
* Bit 8: NEAR_IND_JMP — 간접 점프
*/
wrmsrl(MSR_LBR_SELECT, cpuc->lbr_sel->config);
/* IA32_DEBUGCTL (0x1D9): LBR/BTS/FREEZE 제어
* Bit 0: LBR — LBR 기록 활성화
* Bit 1: BTF — Branch Trace Flag
* Bit 6: TR — Branch Trace 메시지 활성화
* Bit 11: FREEZE_WHILE_SMM
*/
u64 debugctl;
rdmsrl(MSR_IA32_DEBUGCTLMSR, debugctl);
debugctl |= DEBUGCTLMSR_LBR;
wrmsrl(MSR_IA32_DEBUGCTLMSR, debugctl);
}
Intel PT MSR 구성
/* Intel Processor Trace MSR 설정 */
static void pt_config_start(struct perf_event *event)
{
u64 ctl = 0;
/* MSR_IA32_RTIT_CTL (0x570): PT 제어
* Bit 0: TraceEn — 트레이스 활성화
* Bit 1: CYCEn — 사이클 패킷 활성화
* Bit 2: OS — Ring 0 트레이스
* Bit 3: User — Ring 3 트레이스
* Bit 6: FabricEn — 패브릭 포트 출력
* Bit 8: TSCEn — TSC 패킷 삽입
* Bit 13: BranchEn — 분기 패킷 활성화
*/
ctl |= RTIT_CTL_TRACEEN;
ctl |= RTIT_CTL_OS | RTIT_CTL_USR;
ctl |= RTIT_CTL_TSC_EN | RTIT_CTL_BRANCH_EN;
/* Output: ToPA (Table of Physical Addresses) 모드 */
wrmsrl(MSR_IA32_RTIT_OUTPUT_BASE, ...); /* 0x560 */
wrmsrl(MSR_IA32_RTIT_OUTPUT_MASK, ...); /* 0x561 */
wrmsrl(MSR_IA32_RTIT_CTL, ctl); /* 0x570 */
}
CPUID.0AH에서 GP 카운터 수(EAX[15:8])와 비트 폭(EAX[23:16])을 확인해야 합니다. Skylake 이상은 일반적으로 8개 GP 카운터, Ice Lake+는 Topdown 메트릭용 Fixed 카운터도 추가됩니다.주파수 스케일링(Frequency Scaling) MSR
HWP(Hardware-controlled P-States)는 Intel Skylake부터 도입된 자율 주파수 관리 메커니즘으로, 기존 소프트웨어 기반 EIST/SpeedStep을 대체합니다. 커널의 intel_pstate 드라이버가 HWP MSR을 통해 성능 범위와 에너지 선호도를 설정하면, CPU 하드웨어가 자율적으로 최적 주파수를 결정합니다.
HWP MSR (IA32_HWP_REQUEST)
/* drivers/cpufreq/intel_pstate.c — HWP 요청 설정 */
static void intel_pstate_hwp_set(unsigned int cpu)
{
u64 hwp_req;
struct cpudata *cpu_data = all_cpu_data[cpu];
/* MSR_IA32_HWP_REQUEST (0x774) 비트 레이아웃:
* [7:0] Minimum_Performance
* [15:8] Maximum_Performance
* [23:16] Desired_Performance (0=자율)
* [31:24] Energy_Performance_Preference
* 0x00 = Maximum Performance
* 0x80 = Balanced (기본)
* 0xFF = Maximum Energy Saving
* [41:32] Activity_Window
* [42] Package_Control
*/
hwp_req = cpu_data->hwp_req_cached;
hwp_req &= ~HWP_MIN_PERF(~0L);
hwp_req |= HWP_MIN_PERF(cpu_data->min_perf_ratio);
hwp_req &= ~HWP_MAX_PERF(~0L);
hwp_req |= HWP_MAX_PERF(cpu_data->max_perf_ratio);
hwp_req &= ~HWP_EPP(~0L);
hwp_req |= HWP_EPP(cpu_data->epp_cached);
wrmsrl_on_cpu(cpu, MSR_IA32_HWP_REQUEST, hwp_req);
}
MSR_IA32_PERF_CTL / STATUS (레거시 EIST)
/* 레거시 P-State 제어 (HWP 미지원 CPU 또는 passive 모드) */
static void intel_pstate_set_pstate(struct cpudata *cpu,
int pstate)
{
u64 val;
/* MSR_IA32_PERF_CTL (0x199):
* Bits [15:0]: Target P-State ratio
* Bit [32]: IDA(Turbo) Engage (0=enable)
*/
val = (u64)pstate << 8; /* ratio in [15:8] */
wrmsrl(MSR_IA32_PERF_CTL, val);
/* MSR_IA32_PERF_STATUS (0x198, Read-Only):
* 현재 동작 중인 P-State ratio 반환
*/
rdmsrl(MSR_IA32_PERF_STATUS, val);
cpu->pstate.current_pstate = (val >> 8) & 0xFF;
}
Turbo Ratio MSR
/* 코어 수별 최대 터보 배율 읽기 */
static void read_turbo_ratios(void)
{
u64 val;
int i;
/* MSR_TURBO_RATIO_LIMIT (0x1AD):
* [7:0] = 1-core 활성 시 최대 ratio
* [15:8] = 2-core 활성 시 최대 ratio
* [23:16] = 3-core 활성 시 최대 ratio
* ... 최대 8 코어까지
*/
rdmsrl(MSR_TURBO_RATIO_LIMIT, val);
for (i = 0; i < 8; i++) {
u8 ratio = (val >> (i * 8)) & 0xFF;
pr_info("%d-core turbo: %u MHz\n",
i + 1, ratio * 100);
}
/* MSR_TURBO_RATIO_LIMIT1 (0x1AE): 코어 9~16 */
/* MSR_TURBO_RATIO_LIMIT2 (0x1AF): 코어 17~24 */
/* HWP Capabilities도 확인 */
rdmsrl(MSR_IA32_HWP_CAPABILITIES, val); /* 0x771 */
pr_info("HWP Highest: %llu, Guaranteed: %llu\n",
val & 0xFF, (val >> 8) & 0xFF);
}
intel_pstate 드라이버는 /sys/devices/system/cpu/cpufreq/policy*/energy_performance_preference sysfs를 통해 EPP 값을 설정합니다. performance=0, balance_performance=128, balance_power=192, power=255 등이 일반적인 매핑입니다.가상화 MSR 패스스루
KVM/VMX 환경에서 게스트 VM의 MSR 접근은 MSR 비트맵(MSR bitmap)에 의해 제어됩니다. 비트맵에서 해당 MSR이 "통과(passthrough)"로 설정되면 VM exit 없이 직접 접근하고, "인터셉트"로 설정되면 호스트로 제어가 전환됩니다.
MSR 비트맵 구성
/* arch/x86/kvm/vmx/vmx.c — MSR 비트맵 설정 */
static void vmx_set_msr_bitmap_read(unsigned long *msr_bitmap,
u32 msr)
{
/* MSR 비트맵 레이아웃 (4096 bytes = 4 × 1024):
* Offset 0x000: Read bitmap — MSR 0x00000000~0x00001FFF
* Offset 0x400: Read bitmap — MSR 0xC0000000~0xC0001FFF
* Offset 0x800: Write bitmap — MSR 0x00000000~0x00001FFF
* Offset 0xC00: Write bitmap — MSR 0xC0000000~0xC0001FFF
*/
if (msr <= 0x1FFF) {
/* Low range: bit 설정 = intercept */
__set_bit(msr, msr_bitmap);
} else if (msr >= 0xC0000000 && msr <= 0xC0001FFF) {
__set_bit(msr - 0xC0000000,
msr_bitmap + 0x400 / sizeof(long));
}
}
/* Passthrough 예: TSC는 직접 접근 허용 */
vmx_clear_msr_bitmap_read(vmx->vmcs01.msr_bitmap,
MSR_IA32_TSC);
/* Intercept 예: SPEC_CTRL은 호스트가 관리 */
vmx_set_msr_bitmap_read(vmx->vmcs01.msr_bitmap,
MSR_IA32_SPEC_CTRL);
vmx_set_msr_bitmap_write(vmx->vmcs01.msr_bitmap,
MSR_IA32_SPEC_CTRL);
KVM MSR 인터셉트 처리
/* arch/x86/kvm/x86.c — MSR 에뮬레이션 핸들러 */
int kvm_emulate_rdmsr(struct kvm_vcpu *vcpu)
{
u32 ecx = kvm_rcx_read(vcpu);
u64 data;
int r;
r = kvm_get_msr(vcpu, ecx, &data);
if (r) {
/* MSR 읽기 실패 → 게스트에 #GP 주입 */
kvm_inject_gp(vcpu, 0);
return 1;
}
/* 결과를 EDX:EAX에 설정 */
kvm_rax_write(vcpu, data & 0xFFFFFFFF);
kvm_rdx_write(vcpu, data >> 32);
return kvm_skip_emulated_instruction(vcpu);
}
/* MSR 필터링: 유저 공간 (QEMU)이 접근 가능 MSR 목록 설정 */
/* KVM_X86_SET_MSR_FILTER ioctl로 allow/deny 규칙 지정 */
IA32_TSC, IA32_TSC_AUX, IA32_SPEC_CTRL 등을 passthrough하면 프로파일링 오버헤드가 크게 줄어듭니다. 그러나 보안상 민감한 MSR(예: IA32_APIC_BASE)은 반드시 인터셉트해야 합니다.보안 감사용 MSR 점검
시스템 보안 감사에서 MSR 상태 확인은 CPU 수준의 보안 기능이 올바르게 활성화되었는지 검증하는 핵심 절차입니다. Spectre/Meltdown 완화, SMEP/SMAP, CET(Control-flow Enforcement Technology) 등의 활성화 여부를 MSR 값으로 직접 확인할 수 있습니다.
Spectre 완화 MSR 검증
/* 보안 감사: Spectre/Meltdown 완화 MSR 점검 모듈 */
#include <linux/module.h>
#include <asm/msr.h>
static void audit_spec_ctrl(void)
{
u64 spec_ctrl, arch_caps;
int err;
/* IA32_SPEC_CTRL (0x48) 점검 */
err = rdmsrl_safe(MSR_IA32_SPEC_CTRL, &spec_ctrl);
if (!err) {
pr_info("IBRS: %s\n", spec_ctrl & 1 ? "ON" : "OFF");
pr_info("STIBP: %s\n", spec_ctrl & 2 ? "ON" : "OFF");
pr_info("SSBD: %s\n", spec_ctrl & 4 ? "ON" : "OFF");
}
/* IA32_ARCH_CAPABILITIES (0x10A) — 하드웨어 완화 */
err = rdmsrl_safe(MSR_IA32_ARCH_CAPABILITIES, &arch_caps);
if (!err) {
pr_info("RDCL_NO (Meltdown immune): %s\n",
arch_caps & ARCH_CAP_RDCL_NO ? "YES" : "NO");
pr_info("IBRS_ALL (enhanced IBRS): %s\n",
arch_caps & ARCH_CAP_IBRS_ALL ? "YES" : "NO");
pr_info("MDS_NO: %s\n",
arch_caps & ARCH_CAP_MDS_NO ? "YES" : "NO");
pr_info("TAA_NO: %s\n",
arch_caps & ARCH_CAP_TAA_NO ? "YES" : "NO");
pr_info("SBDR_SSDP_NO: %s\n",
arch_caps & ARCH_CAP_SBDR_SSDP_NO ? "YES" : "NO");
}
}
SMEP/SMAP/CET 확인
/* CR4 기반 보안 기능 확인 (MSR을 통한 간접 검증) */
static void audit_cr4_security(void)
{
unsigned long cr4 = native_read_cr4();
pr_info("SMEP (Supervisor Mode Execution Prevention): %s\n",
cr4 & X86_CR4_SMEP ? "ENABLED" : "DISABLED");
pr_info("SMAP (Supervisor Mode Access Prevention): %s\n",
cr4 & X86_CR4_SMAP ? "ENABLED" : "DISABLED");
pr_info("CET (Control-flow Enforcement Technology): %s\n",
cr4 & X86_CR4_CET ? "ENABLED" : "DISABLED");
pr_info("UMIP (User-Mode Instruction Prevention): %s\n",
cr4 & X86_CR4_UMIP ? "ENABLED" : "DISABLED");
/* CET 세부 MSR 확인 */
if (cr4 & X86_CR4_CET) {
u64 scet, ucet;
rdmsrl(MSR_IA32_S_CET, scet); /* 0x6A2 */
rdmsrl(MSR_IA32_U_CET, ucet); /* 0x6A0 */
pr_info(" S-CET SH_STK_EN: %s, IBT: %s\n",
scet & 1 ? "ON" : "OFF",
scet & 4 ? "ON" : "OFF");
pr_info(" U-CET SH_STK_EN: %s, IBT: %s\n",
ucet & 1 ? "ON" : "OFF",
ucet & 4 ? "ON" : "OFF");
}
}
마이크로코드 리비전 점검
/* 마이크로코드 버전 확인 — 보안 패치 적용 검증 */
static void audit_microcode(void)
{
u64 ucode_rev;
struct cpuinfo_x86 *c = &boot_cpu_data;
/* IA32_BIOS_SIGN_ID (0x8B) — 마이크로코드 리비전 */
rdmsrl(MSR_IA32_UCODE_REV, ucode_rev);
pr_info("Microcode revision: 0x%08llx\n",
ucode_rev >> 32);
pr_info("CPU: %s Family %xh Model %xh Stepping %x\n",
c->x86_vendor == X86_VENDOR_INTEL ?
"Intel" : "AMD",
c->x86, c->x86_model, c->x86_stepping);
/* Spectre v2 필요 마이크로코드 확인 예:
* Intel SKL-SP: 최소 0x02000065 이상 필요
* Spectre BHI: 최소 0x0300000f 이상 필요
*/
if ((ucode_rev >> 32) < 0x02000065 &&
c->x86_model == 0x55) {
pr_warn("WARNING: Microcode too old for full Spectre mitigation!\n");
}
}
/sys/devices/system/cpu/vulnerabilities/ 파일은 커널이 판단한 취약점 상태를 보여주지만, MSR을 직접 읽으면 하드웨어 수준의 실제 완화 상태를 확인할 수 있습니다. 두 정보가 불일치하면 커널 버전과 마이크로코드 업데이트가 필요할 수 있습니다.유저스페이스 MSR 도구
커널 모듈(Kernel Module)을 작성하지 않고도 유저 공간에서 MSR을 읽고 분석할 수 있는 다양한 도구가 있습니다. msr-tools, turbostat, cpupower 등이 내부적으로 /dev/cpu/N/msr을 통해 MSR에 접근합니다.
msr-tools 고급 사용법
# msr-tools: 모든 CPU의 HWP 상태 확인
# IA32_HWP_REQUEST (0x774) 읽기
for cpu in /dev/cpu/*/msr; do
cpunum=$(echo $cpu | grep -o '[0-9]*')
val=$(sudo rdmsr -p $cpunum 0x774 2>/dev/null)
if [ -n "$val" ]; then
min=$((16#${val:14:2}))
max=$((16#${val:12:2}))
epp=$((16#${val:8:2}))
echo "CPU$cpunum: min=$min max=$max EPP=$epp"
fi
done
# RAPL 에너지 카운터 읽기 (MSR_PKG_ENERGY_STATUS)
sudo rdmsr -p 0 0x611
# 단위 변환: MSR_RAPL_POWER_UNIT (0x606)의 [12:8] = Energy Unit
unit=$(sudo rdmsr -p 0 0x606)
energy_unit=$(( (16#$unit >> 8) & 0x1F ))
echo "Energy unit: 2^-${energy_unit} Joules"
# MSR 쓰기 예: EPP를 performance(0)로 설정
# 주의: Read-Modify-Write 필수
val=$(sudo rdmsr -p 0 0x774)
# bits [31:24] = EPP, 0으로 설정
new_val=$(printf "0x%016x" $(( (16#$val & ~0xFF000000) | 0x00000000 )))
sudo wrmsr -p 0 0x774 $new_val
turbostat 내부 동작
# turbostat: CPU 주파수/전력/C-State 모니터링
# 내부적으로 읽는 주요 MSR:
# MSR_IA32_APERF (0xE8) — Actual Performance
# MSR_IA32_MPERF (0xE7) — Maximum Performance
# MSR_PKG_ENERGY_STATUS (0x611) — 패키지 에너지
# MSR_CORE_C3/C6/C7_RESIDENCY — C-State 체류 시간
# MSR_PKG_C2/C3/C6/C7_RESIDENCY — 패키지 C-State
# 기본 실행 (1초 간격)
sudo turbostat --interval 1
# 특정 카운터만 선택
sudo turbostat --show Core,CPU,Avg_MHz,Busy%,Bzy_MHz,PkgWatt
# 프로그램 실행 중 모니터링
sudo turbostat --quiet -- ./benchmark
# CSV 출력 (스크립트 분석용)
sudo turbostat --interval 1 --out turbo.csv -- stress -c 8 -t 30
# Busy%: MPERF/TSC × 100 (실제 CPU 활성 비율)
# Bzy_MHz: TSC_MHz × APERF/MPERF (실제 동작 주파수)
cpupower MSR 접근
# cpupower: cpufreq 드라이버를 통한 간접 MSR 제어
# 현재 드라이버 및 거버너 확인
cpupower frequency-info
# EPP 설정 (HWP MSR을 간접적으로 제어)
sudo cpupower set --perf-bias 0 # 최고 성능
sudo cpupower set --perf-bias 15 # 최대 절전
# 주파수 범위 설정 (MSR_IA32_HWP_REQUEST 간접 변경)
sudo cpupower frequency-set -g performance
sudo cpupower frequency-set -d 2.4GHz -u 4.8GHz
# idle 상태 확인 (C-State 관련)
cpupower idle-info
# 특정 C-State 비활성화
sudo cpupower idle-set -d 3 # C3 이상 비활성화
# 모니터 모드 (MSR 기반 카운터 실시간 표시)
sudo cpupower monitor -m Mperf
/dev/cpu/N/msr 접근에는 CAP_SYS_RAWIO 또는 root 권한이 필요합니다. Lockdown LSM이 활성화된 경우(Secure Boot) 쓰기가 차단될 수 있으며, CONFIG_X86_MSR=y와 modprobe msr이 선행되어야 합니다.Per-CPU MSR 스냅샷 기법
MSR은 코어별로 독립적이므로, 시스템 전체 MSR 상태를 파악하려면 모든 CPU에서 동시에 값을 수집해야 합니다. 이 기법은 성능 분석, 전력 감사, 보안 점검에서 핵심적으로 사용됩니다.
Per-CPU 스냅샷 커널 모듈
/* Per-CPU MSR 스냅샷 수집 모듈 */
#include <linux/module.h>
#include <linux/smp.h>
#include <asm/msr.h>
struct msr_snapshot {
u32 msr_addr;
u64 value;
u64 tsc;
int cpu;
int err;
};
static struct msr_snapshot snapshots[NR_CPUS];
static const u32 audit_msrs[] = {
MSR_IA32_SPEC_CTRL, /* 0x48 */
MSR_IA32_ARCH_CAPABILITIES, /* 0x10A */
MSR_IA32_HWP_REQUEST, /* 0x774 */
MSR_IA32_APERF, /* 0xE8 */
MSR_IA32_MPERF, /* 0xE7 */
};
static void __snapshot_msr(void *info)
{
u32 msr = *(u32 *)info;
int cpu = smp_processor_id();
struct msr_snapshot *s = &snapshots[cpu];
s->msr_addr = msr;
s->cpu = cpu;
s->tsc = rdtsc();
s->err = rdmsrl_safe(msr, &s->value);
}
static void take_msr_snapshot(u32 msr)
{
int cpu;
on_each_cpu(__snapshot_msr, &msr, 1);
for_each_online_cpu(cpu) {
struct msr_snapshot *s = &snapshots[cpu];
if (s->err)
pr_info("CPU%d: MSR 0x%x — not supported\n",
cpu, msr);
else
pr_info("CPU%d: MSR 0x%x = 0x%016llx (TSC=%llu)\n",
cpu, msr, s->value, s->tsc);
}
}
스냅샷 비교 스크립트
#!/bin/bash
# Per-CPU MSR 스냅샷 비교 (msr-tools 기반)
# 사용법: ./msr-diff.sh <MSR_ADDR>
MSR=${1:-0x48} # 기본: IA32_SPEC_CTRL
NCPU=$(nproc)
declare -A values
echo "=== MSR 0x$MSR Per-CPU Snapshot ==="
for ((cpu=0; cpu<NCPU; cpu++)); do
val=$(sudo rdmsr -p $cpu 0x$MSR 2>/dev/null)
if [ $? -eq 0 ]; then
values[$cpu]=$val
echo "CPU$cpu: 0x$val"
else
echo "CPU$cpu: (not supported)"
fi
done
# 일관성 확인
unique=$(printf '%s\n' "${values[@]}" | sort -u | wc -l)
if [ "$unique" -eq 1 ]; then
echo "[OK] 모든 CPU에서 동일한 값"
else
echo "[WARNING] CPU별 MSR 값 불일치 감지!"
printf '%s\n' "${values[@]}" | sort | uniq -c | sort -rn
fi
on_each_cpu()는 IPI를 사용하므로 인터럽트가 활성화된 상태에서만 호출 가능합니다. 또한 CPU 핫플러그가 진행 중일 때는 cpus_read_lock()으로 보호해야 합니다. smp_call_function_single()은 특정 CPU 하나에서만 실행시킬 때 사용합니다.MSR 오류 처리와 예외
MSR 접근 시 발생할 수 있는 #GP(General Protection) 예외는 커널 크래시의 주요 원인 중 하나입니다. Linux 커널은 exception table 메커니즘을 통해 이를 안전하게 처리하며, 이 섹션에서는 그 내부 동작과 올바른 에러 처리 패턴을 상세히 다룹니다.
존재하지 않는 MSR에서의 #GP 예외
/* #GP(0) 발생 시나리오:
* 1. 존재하지 않는 MSR 주소에 RDMSR/WRMSR
* 2. 예약(Reserved) 비트에 1 쓰기
* 3. Lock 비트가 설정된 MSR에 쓰기 시도
* 4. 권한 부족 (CPL > 0에서 RDMSR/WRMSR)
*/
/* 안전하지 않은 접근 — 커널 패닉 가능! */
static void unsafe_msr_access(void)
{
u64 val;
/* 이 MSR이 존재하지 않으면 #GP → oops → 커널 패닉 */
rdmsrl(0xDEADBEEF, val); /* 절대 이렇게 하지 마세요! */
}
/* 안전한 접근 — exception table 활용 */
static int safe_msr_access(void)
{
u64 val;
int err;
err = rdmsrl_safe(0xDEADBEEF, &val);
if (err) {
pr_info("MSR 0xDEADBEEF does not exist (err=%d)\n", err);
return -ENODEV;
}
return 0;
}
ex_handler_msr 복구 메커니즘
/* arch/x86/mm/extable.c — MSR 예외 핸들러 */
/* RDMSR에 대한 exception table 항목 처리 */
bool ex_handler_rdmsr_unsafe(const struct exception_table_entry *fixup,
struct pt_regs *regs)
{
/* #GP 발생 시:
* 1. EAX = 0, EDX = 0 (읽기 결과를 0으로)
* 2. RIP를 fixup 주소로 변경 (정상 흐름 복귀)
*/
WARN_ONCE(1, "unchecked MSR access error: RDMSR from 0x%x at %pS\n",
(unsigned int)regs->cx,
(void *)regs->ip);
regs->ax = 0;
regs->dx = 0;
regs->ip = ex_fixup_addr(fixup);
return true;
}
/* rdmsrl_safe의 인라인 어셈블리에서 exception table 생성 */
/*
* asm volatile("1: rdmsr\n"
* " xor %[err], %[err]\n"
* "2:\n"
* _ASM_EXTABLE_TYPE_REG(1b, 2b, EX_TYPE_RDMSR_SAFE, %[err])
* : [err] "=a" (err), "=d" (high), "=a" (low)
* : "c" (msr));
*
* exception table: 1b → 2b, type=RDMSR_SAFE
* #GP at 1b → jump to 2b, err = -EIO
*/
MSR 범위 검증
/* MSR 접근 전 유효성 검증 패턴 */
static bool msr_range_valid(u32 msr)
{
/* x86 MSR 주소 공간 유효 범위 */
static const struct {
u32 start, end;
const char *desc;
} valid_ranges[] = {
{ 0x00000000, 0x00001FFF, "Architectural" },
{ 0x40000000, 0x400000FF, "Hyper-V" },
{ 0xC0000000, 0xC0001FFF, "AMD/x86-64" },
{ 0xC0010000, 0xC001FFFF, "AMD-specific" },
};
int i;
for (i = 0; i < ARRAY_SIZE(valid_ranges); i++) {
if (msr >= valid_ranges[i].start &&
msr <= valid_ranges[i].end)
return true;
}
return false;
}
/* Read-Modify-Write 안전 패턴 */
static int msr_set_bits_safe(u32 msr, u64 bits_to_set)
{
u64 val;
int err;
err = rdmsrl_safe(msr, &val);
if (err)
return err;
val |= bits_to_set;
err = wrmsrl_safe(msr, val);
if (err) {
pr_warn("Failed to write MSR 0x%x: %d "
"(possible reserved bit violation)\n",
msr, err);
}
return err;
}
- 커널 드라이버에서 MSR 접근 시 반드시
_safe변형을 사용하세요. barerdmsrl()/wrmsrl()은 해당 MSR이 100% 존재한다고 확신할 때만 사용합니다. - MSR 쓰기 전에 항상 Read-Modify-Write 패턴을 사용하세요. 전체 64비트를 덮어쓰면 예약 비트에 잘못된 값이 들어갈 수 있습니다.
- 새로운 CPU 모델이 추가될 때 MSR 주소나 비트 레이아웃이 변경될 수 있으므로,
arch/x86/include/asm/msr-index.h의 매크로를 사용하고 CPUID로 기능 지원 여부를 먼저 확인하세요.
참고 링크
- Intel 64 and IA-32 Architectures Software Developer Manuals (Intel SDM) — MSR 전체 목록과 비트 필드 정의를 포함하는 공식 매뉴얼입니다
- AMD64 Architecture Programmer's Manual — AMD 프로세서 MSR 정의 및 x86 호환성 정보를 제공합니다
- 커널 소스: arch/x86/include/asm/msr-index.h — 리눅스 커널에서 정의하는 MSR 인덱스 상수 전체 목록입니다
- 커널 소스: arch/x86/include/asm/msr.h — rdmsrl(), wrmsrl() 등 MSR 접근 인라인 함수 정의입니다
- 커널 소스: arch/x86/lib/msr.c — MSR 읽기/쓰기 라이브러리 구현입니다
- 커널 소스: drivers/char/msr.c — /dev/cpu/*/msr 캐릭터 디바이스 드라이버 구현입니다
- 커널 문서: x86 MSR (Documentation/arch/x86/msr.rst) — MSR 디바이스 인터페이스 공식 커널 문서입니다
- LWN.net: Restricting access to /dev/msr — MSR 디바이스 접근 제한 보안 논의입니다
- LWN.net: The RAPL power-capping subsystem — RAPL MSR 기반 전력 제한 서브시스템 설명입니다
- LWN.net: Intel Hardware P-States (HWP) — HWP MSR을 통한 하드웨어 P-State 제어 분석입니다
- 커널 소스: arch/x86/kernel/cpu/msr.c — CPU별 MSR 초기화 코드입니다
- 커널 소스: arch/x86/kvm/x86.c — KVM의 MSR 가상화 처리 (비트맵 필터링 포함)입니다
- 커널 문서: intel_pstate CPU Performance Scaling Driver — HWP/EPP MSR 기반 전력 성능 드라이버 문서입니다
관련 문서
MSR 레지스터와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.