CPUFreq
Linux 커널 CPU 주파수 스케일링(CPUFreq) 서브시스템을 심층 분석합니다. DVFS·P-state 기본 원리, policy/driver/governor 계층 구조, schedutil/ondemand/performance의 선택 기준, Intel P-state·AMD P-state·CPPC/HWP 연동, Turbo Boost와 thermal pressure 상호작용, latency/throughput/power 균형을 위한 실전 튜닝과 관측 지표까지 종합적으로 다룹니다.
핵심 요약
- 핵심 객체 — 이 문서의 중심이 되는 자료구조/API를 먼저 파악합니다.
- 실행 경로 — 요청이 들어와 처리되고 종료되는 흐름을 확인합니다.
- 병목 지점 — 지연이나 처리량 저하가 발생하는 구간을 점검합니다.
- 동기화 지점 — 경합과 경쟁 조건이 생길 수 있는 구간을 구분합니다.
- 운영 포인트 — 관측 지표와 튜닝 항목을 함께 확인합니다.
단계별 이해
- 구성요소 확인
핵심 자료구조와 주요 API를 먼저 식별합니다. - 요청 흐름 추적
입력부터 완료까지의 호출 경로를 순서대로 따라갑니다. - 예외 경로 점검
실패 처리, 재시도, 타임아웃 등 경계 조건을 확인합니다. - 성능/안정성 점검
잠금 경합, 큐 적체, 병목 지점을 측정하고 조정합니다.
개요
CPUFreq는 CPU 주파수와 전압을 동적으로 조정하여 전력 소비를 최적화하는 서브시스템입니다.
커널 소스에서 drivers/cpufreq/ 디렉터리에 위치하며, include/linux/cpufreq.h에 핵심 자료구조가 정의되어 있습니다.
최신 커널(6.x)에서는 schedutil 거버너가 기본값으로 설정되어, 스케줄러 부하 추적(PELT)과 직접 연동하여 에너지 효율과 반응성을 동시에 달성합니다.
DVFS (Dynamic Voltage and Frequency Scaling)
전력 소비 공식
CPU 동적 전력 소비는 주파수와 전압에 비례합니다:
P_dynamic = C × V² × F
P_dynamic : 동적 전력 (Watts)
C : 스위칭 용량 (Capacitance, 게이트 수와 활동률에 비례)
V : 공급 전압 (Voltage)
F : 동작 주파수 (Frequency)
주파수를 절반으로 낮추고 전압도 낮추면:
- 주파수: 3.0GHz → 1.5GHz (50%)
- 전압: 1.2V → 0.9V (75%)
- 전력: 1.2² × 3.0 = 4.32 → 0.9² × 1.5 = 1.22 (72% 감소)
전압의 제곱에 비례하므로, 전압 감소가 전력 절감에 가장 큰 영향을 줍니다. 이것이 DVFS에서 전압과 주파수를 함께 조정하는 이유입니다.
P-states vs C-states
| 항목 | P-states (CPUFreq) | C-states (CPUIdle) |
|---|---|---|
| 목적 | 실행 중 주파수/전압 조정 | 유휴 시 전력 절약 |
| 상태 | CPU 활성 (실행 중) | CPU 유휴 (대기 중) |
| 전력 절감 | 중간 (50-80%) | 큼 (90-99%) |
| 복귀 시간 | 즉시 (0 us) | 수 us ~ ms |
| 제어 | CPUFreq Governor | CPUIdle Governor |
| ACPI 명세 | P0(최고) ~ Pn(최저) | C0(활성) ~ C3(깊은 절전) |
| 레지스터 | MSR_IA32_PERF_CTL | MWAIT 힌트 |
| 스케줄러 연동 | schedutil (실행 중 조정) | menu/TEO (유휴 시 선택) |
Governor (정책)
Governor는 CPU 주파수를 언제, 어떻게 변경할지 결정하는 정책입니다.
커널은 struct cpufreq_governor로 거버너를 추상화하며, 각 거버너는 init(), start(), stop(), limits() 콜백을 구현합니다.
Governor 종류
| Governor | 동작 | 전력 | 성능 | 적합한 경우 |
|---|---|---|---|---|
performance | 항상 최고 주파수 | 높음 | 최고 | 서버, HPC, 게임 |
powersave | 항상 최저 주파수 | 낮음 | 최저 | 극한 절전 (비권장) |
ondemand | 부하 95% 이상 시 최대 | 중간 | 높음 | 데스크톱 (레거시) |
conservative | 단계적 증가/감소 | 낮음 | 중간 | 노트북 |
schedutil | 스케줄러 정보 활용 | 최적 | 높음 | 최신 기본값 (권장) |
userspace | 유저 공간 제어 | 가변 | 가변 | 벤치마크, 디버깅 |
cpufreq_governor 구조체
/* include/linux/cpufreq.h */
struct cpufreq_governor {
char name[CPUFREQ_NAME_LEN];
/* 거버너 생명주기 콜백 */
int (*init)(struct cpufreq_policy *policy);
void (*exit)(struct cpufreq_policy *policy);
int (*start)(struct cpufreq_policy *policy);
void (*stop)(struct cpufreq_policy *policy);
void (*limits)(struct cpufreq_policy *policy);
/* sysfs 속성 */
struct attribute_group *attr_group_gov;
/* 모듈 소유자 */
struct module *owner;
/* 거버너 목록 연결 */
struct list_head governor_list;
};
거버너 생명주기
거버너는 정책(policy)에 연결될 때 다음 순서로 콜백이 호출됩니다:
init()- 데이터 구조 초기화, sysfs 속성 생성start()- 스케줄러 콜백 등록, 주파수 조정 시작limits()- 정책 제한(min/max) 변경 시 호출stop()- 스케줄러 콜백 해제exit()- 데이터 구조 해제, sysfs 속성 제거
schedutil Governor (권장)
Linux 4.7+에서 도입된 스케줄러 기반 Governor입니다. 전통적인 거버너(ondemand, conservative)가 유휴 시간 비율로 부하를 추정하는 것과 달리, schedutil은 PELT(Per-Entity Load Tracking) 신호를 직접 사용합니다.
/* kernel/sched/cpufreq_schedutil.c */
static void sugov_update_single(struct update_util_data *hook, u64 time,
unsigned int flags)
{
struct sugov_cpu *sg_cpu = container_of(hook, struct sugov_cpu, update_util);
unsigned long util, max;
unsigned int next_f;
/* 스케줄러에서 CPU 활용률 가져오기 */
sugov_get_util(&util, &max);
/* 목표 주파수 계산: freq = util / max * max_freq */
next_f = get_next_freq(sg_cpu, util, max);
/* 주파수 변경 */
sugov_update_commit(sg_cpu, time, next_f);
}
Governor 선택 가이드
- 최신 시스템 (2017+):
schedutil(기본값, 최적 균형) - 고성능 서버:
performance(지연 시간 최소화) - 레거시 데스크톱:
ondemand(널리 테스트됨) - 배터리 우선:
conservative(점진적 조정)
아키텍처
CPUFreq는 3계층 구조로 설계되었습니다. Governor가 주파수 결정 정책을, Core가 공통 인터페이스를, Driver가 하드웨어 제어를 담당합니다.
계층 구조
cpufreq_policy 구조체
cpufreq_policy는 동일한 클록/전압 도메인을 공유하는 CPU 집합을 나타냅니다.
하나의 정책 객체에 여러 CPU가 연결될 수 있으며, 이들은 동일한 주파수로 동작합니다.
/* include/linux/cpufreq.h */
struct cpufreq_policy {
cpumask_var_t cpus; /* 온라인 상태인 CPU들 */
cpumask_var_t related_cpus; /* 같은 주파수 도메인의 모든 CPU */
cpumask_var_t real_cpus; /* 이 정책에 실제 연결된 CPU */
unsigned int shared_type; /* CPUFREQ_SHARED_TYPE_* */
unsigned int cpu; /* 정책의 대표 CPU */
struct clk *clk; /* clock framework 연결 */
/* 하드웨어 한계 */
struct cpufreq_cpuinfo cpuinfo; /* min_freq, max_freq, transition_latency */
/* 사용자/정책 한계 */
unsigned int min; /* 최소 주파수 (kHz) */
unsigned int max; /* 최대 주파수 (kHz) */
unsigned int cur; /* 현재 주파수 (kHz) */
/* 거버너 */
struct cpufreq_governor *governor; /* 현재 governor */
void *governor_data; /* 거버너 전용 데이터 */
/* 주파수 테이블 */
struct cpufreq_frequency_table *freq_table;
unsigned int suspend_freq; /* suspend 시 주파수 */
/* 전환 통계 */
struct cpufreq_stats *stats;
struct cpufreq_stats *last_stats;
/* 동기화 */
struct rw_semaphore rwsem; /* 정책 접근 보호 */
/* fast_switch 지원 여부 */
bool fast_switch_possible;
bool fast_switch_enabled;
/* 전환 지연 (나노초) */
unsigned int transition_delay_us;
/* EMD (Energy Model) 통합 */
struct em_perf_domain *em_pd;
};
cpufreq_cpuinfo 구조체
/* include/linux/cpufreq.h */
struct cpufreq_cpuinfo {
unsigned int max_freq; /* 하드웨어 최대 주파수 (kHz) */
unsigned int min_freq; /* 하드웨어 최소 주파수 (kHz) */
unsigned int transition_latency; /* P-state 전환 지연 (ns) */
};
cpufreq_driver 콜백 상세
CPUFreq 드라이버는 struct cpufreq_driver를 통해 하드웨어 인터페이스를 추상화합니다.
드라이버는 cpufreq_register_driver()로 등록되며, 코어는 이 구조체의 콜백을 통해 하드웨어를 제어합니다.
cpufreq_driver 구조체
/* include/linux/cpufreq.h */
struct cpufreq_driver {
char name[CPUFREQ_NAME_LEN];
u16 flags;
/* ---- 필수 콜백 ---- */
int (*init)(struct cpufreq_policy *policy);
int (*verify)(struct cpufreq_policy_data *policy_data);
/* ---- 주파수 설정 (택 1) ---- */
int (*setpolicy)(struct cpufreq_policy *policy);
int (*target)(struct cpufreq_policy *policy,
unsigned int target_freq, unsigned int relation);
int (*target_index)(struct cpufreq_policy *policy,
unsigned int index);
unsigned int (*fast_switch)(struct cpufreq_policy *policy,
unsigned int target_freq);
/* ---- 선택 콜백 ---- */
unsigned int (*get)(unsigned int cpu);
unsigned int (*resolve_freq)(struct cpufreq_policy *policy,
unsigned int target_freq);
int (*exit)(struct cpufreq_policy *policy);
int (*suspend)(struct cpufreq_policy *policy);
int (*resume)(struct cpufreq_policy *policy);
void (*ready)(struct cpufreq_policy *policy);
int (*set_boost)(struct cpufreq_policy *policy, int state);
/* ---- 중간 주파수 전환 (PLL 재설정 시) ---- */
unsigned int (*get_intermediate)(struct cpufreq_policy *policy,
unsigned int index);
int (*target_intermediate)(struct cpufreq_policy *policy,
unsigned int index);
/* ---- sysfs 속성 ---- */
struct freq_attr **attr;
};
콜백 역할 상세
| 콜백 | 필수 | 컨텍스트 | 역할 |
|---|---|---|---|
init | 필수 | 프로세스 | 정책 초기화: cpuinfo.min/max_freq, freq_table, transition_latency 설정 |
verify | 필수 | 프로세스 | 정책 제한값 유효성 검증 (cpufreq_verify_within_limits 헬퍼 사용) |
target | 택1 | 프로세스 | 목표 주파수와 관계(RELATION_L/H/C) 지정하여 설정 (레거시) |
target_index | 택1 | 프로세스 | 주파수 테이블의 인덱스로 직접 설정 (가장 일반적) |
fast_switch | 택1 | 인터럽트 | 스케줄러 컨텍스트에서 직접 호출, 슬립 금지 (schedutil용) |
setpolicy | 택1 | 프로세스 | HWP 모드: min/max 범위만 전달, 하드웨어가 자율 결정 |
get | 선택 | 프로세스 | 현재 실제 동작 주파수 반환 (하드웨어에서 읽기) |
resolve_freq | 선택 | 프로세스 | 목표 주파수를 실제 가능한 주파수로 변환 (실제 설정 없이) |
exit | 선택 | 프로세스 | 정책 해제 시 정리 (CPU 핫플러그 오프라인) |
suspend | 선택 | 프로세스 | 시스템 절전 진입 시 호출 (거버너 정지 후) |
resume | 선택 | 프로세스 | 시스템 절전 복귀 시 호출 (거버너 시작 전) |
set_boost | 선택 | 프로세스 | Turbo/Boost 활성화/비활성화 |
fast_switch vs target 경로 비교
| 비교 항목 | fast_switch | target / target_index |
|---|---|---|
| 호출 컨텍스트 | 인터럽트 (스케줄러 tick) | 프로세스 (kthread) |
| 슬립 가능 | 불가 (IRQ disabled) | 가능 |
| 전환 지연 | 수 us | 수십 ~ 수백 us |
| notifier 호출 | 불가 | PRE/POST 호출 |
| 대표 드라이버 | intel_pstate (HWP), amd-pstate (MSR) | acpi-cpufreq, cppc_cpufreq |
| schedutil 활용 | 직접 호출 (최적 경로) | irq_work → kthread 우회 |
policy->fast_switch_possible이 true면 드라이버가 fast_switch를 지원합니다.
schedutil은 이 플래그를 확인하고, 가능하면 fast_switch 경로를 우선 사용합니다.
최신 Intel(HWP)과 AMD(CPPC MSR) CPU는 모두 fast_switch를 지원합니다.
드라이버 플래그
/* include/linux/cpufreq.h - 주요 드라이버 플래그 */
#define CPUFREQ_STICKY (1 << 0) /* 언로드 불가 */
#define CPUFREQ_CONST_LOOPS (1 << 1) /* loops_per_jiffy 고정 */
#define CPUFREQ_HAVE_GOVERNOR_PER_POLICY (1 << 3) /* 정책별 거버너 */
#define CPUFREQ_NEED_INITIAL_FREQ_CHECK (1 << 5) /* 초기 주파수 검증 */
#define CPUFREQ_IS_COOLING_DEV (1 << 7) /* cooling device 자동 등록 */
#define CPUFREQ_NEED_UPDATE_LIMITS (1 << 8) /* 한계값 변경 시 업데이트 */
schedutil 거버너 심화
schedutil은 스케줄러의 PELT(Per-Entity Load Tracking) 신호를 직접 활용하는 유일한 거버너입니다. 기존 거버너가 주기적으로 CPU 유휴 시간을 샘플링하는 것과 달리, 스케줄러 이벤트(태스크 깨움, 마이그레이션, tick)마다 실시간으로 주파수를 업데이트합니다.
PELT-schedutil 파이프라인
주파수 계산 공식 상세
/* kernel/sched/cpufreq_schedutil.c */
static unsigned int get_next_freq(struct sugov_policy *sg_policy,
unsigned long util, unsigned long max)
{
struct cpufreq_policy *policy = sg_policy->policy;
unsigned int freq;
/* 1.25 배율 적용: 100% 활용률 → 최대 주파수 도달 보장
* C = 1.25 이므로 util이 max의 80%일 때 max_freq에 도달
* 이는 스케줄러 활용률이 실제보다 약간 낮게 추적되는 것을 보상 */
freq = map_util_freq(util, policy->cpuinfo.max_freq, max);
/* resolve_freq가 있으면 실제 가능한 주파수로 변환 */
if (policy->freq_table)
freq = cpufreq_driver_resolve_freq(policy, freq);
return freq;
}
/* map_util_freq: f = C * util / max * f_max */
static inline unsigned long map_util_freq(unsigned long util,
unsigned long freq,
unsigned long cap)
{
return (freq + (freq >> 2)) * util / cap; /* freq * 1.25 * util / cap */
}
IO-wait 부스트 메커니즘
IO 완료로 깨어난 태스크는 보통 짧은 CPU burst를 수행합니다. schedutil은 이 패턴을 인식하여 주파수를 즉시 부스트합니다:
/* IO-wait 부스트 로직 */
static void sugov_iowait_boost(struct sugov_cpu *sg_cpu, u64 time,
unsigned int flags)
{
/* SCHED_CPUFREQ_IOWAIT 플래그 확인 */
if (flags & SCHED_CPUFREQ_IOWAIT) {
if (sg_cpu->iowait_boost_pending)
return;
sg_cpu->iowait_boost_pending = true;
/* 부스트 레벨 두 배로 증가 (최대 max_freq까지) */
if (sg_cpu->iowait_boost) {
sg_cpu->iowait_boost <<= 1;
if (sg_cpu->iowait_boost > sg_cpu->iowait_boost_max)
sg_cpu->iowait_boost = sg_cpu->iowait_boost_max;
} else {
sg_cpu->iowait_boost = sg_cpu->sg_policy->policy->min;
}
}
}
iowait_boost_enable 파라미터를 비활성화하거나, EAS(Energy Aware Scheduling)를 통해 에너지 모델 기반으로 보정할 수 있습니다.
sugov_update_shared vs sugov_update_single
| 비교 항목 | sugov_update_single | sugov_update_shared |
|---|---|---|
| 적용 조건 | 정책에 CPU가 1개 | 정책에 CPU가 2개 이상 |
| 활용률 계산 | 해당 CPU만 확인 | 정책 내 모든 CPU 중 최대값 |
| 동기화 | 불필요 | per-cpu 잠금 필요 |
| 주파수 결정 | 단일 CPU 기반 | 가장 부하 높은 CPU 기준 |
| fast_switch | 직접 호출 | 직접 호출 (최대 활용률 CPU에서) |
/* 공유 정책에서의 업데이트 */
static void sugov_update_shared(struct update_util_data *hook,
u64 time, unsigned int flags)
{
struct sugov_cpu *sg_cpu = container_of(hook, struct sugov_cpu, update_util);
struct sugov_policy *sg_policy = sg_cpu->sg_policy;
unsigned long util, max;
unsigned int next_f;
/* per-cpu 잠금으로 동시 접근 방지 */
raw_spin_lock(&sg_policy->update_lock);
/* 이 CPU의 활용률 저장 */
sg_cpu->util = util;
sg_cpu->max = max;
sg_cpu->last_update = time;
/* 정책 내 모든 CPU 중 최대 활용률 계산 */
sugov_get_util(sg_policy);
/* 최대 활용률 기반으로 주파수 결정 */
next_f = get_next_freq(sg_policy, util, max);
raw_spin_unlock(&sg_policy->update_lock);
}
Intel P-state 드라이버
Intel CPU (Sandy Bridge+)는 전용 intel_pstate 드라이버를 사용합니다.
이 드라이버는 두 가지 동작 모드를 가집니다: active mode(HWP)와 passive mode(SWP).
동작 모드
| 모드 | 커널 파라미터 | 거버너 사용 | 주파수 결정 | 적용 CPU |
|---|---|---|---|---|
| Active (HWP) | intel_pstate=hwp (기본) | 내부 알고리즘 | 하드웨어 자율 | Skylake+ |
| Passive (SWP) | intel_pstate=passive | cpufreq 거버너 | 드라이버 제어 | Sandy Bridge+ |
| 비활성 | intel_pstate=disable | acpi-cpufreq | ACPI P-state | 모든 Intel |
HWP (Hardware P-states)
Skylake+ CPU는 하드웨어가 자동으로 P-state를 선택합니다 (HWP).
소프트웨어는 IA32_HWP_REQUEST MSR을 통해 선호도(min, max, desired, EPP)만 전달하고, 실제 P-state 결정은 CPU 마이크로코드가 수행합니다.
HWP MSR 레지스터 레이아웃
/* drivers/cpufreq/intel_pstate.c - HWP 요청 쓰기 */
static void intel_pstate_hwp_set(unsigned int cpu)
{
struct cpudata *cpu_data = all_cpu_data[cpu];
u64 value;
value = (u64)cpu_data->epp << 24 | /* EPP */
(u64)cpu_data->desired << 16 | /* Desired */
(u64)cpu_data->max_perf << 8 | /* Maximum */
(u64)cpu_data->min_perf; /* Minimum */
wrmsrl_on_cpu(cpu, MSR_HWP_REQUEST, value);
}
EPP (Energy Performance Preference)
HWP 모드에서는 EPP로 성능/전력 균형을 제어합니다.
| EPP 값 | 의미 | 주파수 경향 | 전형적 사용처 |
|---|---|---|---|
| 0 | performance | 항상 높음 | HPC, 게임 서버 |
| 128 | balance_performance | 성능 우선 균형 | 웹 서버, DB |
| 192 | balance_power | 전력 우선 균형 | 데스크톱 일반 |
| 255 | power | 항상 낮음 | 배터리 절약 |
# EPP 설정 확인/변경
cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference
# balance_performance
# 사용 가능한 EPP 목록
cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences
# default performance balance_performance balance_power power
# 모든 CPU에 performance EPP 적용
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference; do
echo performance > "$cpu"
done
Intel P-state 전용 sysfs
# Intel P-state 드라이버 상태 확인
cat /sys/devices/system/cpu/intel_pstate/status
# active (HWP 모드) 또는 passive (SWP 모드)
# 최대/최소 성능 비율 (% 단위)
cat /sys/devices/system/cpu/intel_pstate/max_perf_pct
# 100
cat /sys/devices/system/cpu/intel_pstate/min_perf_pct
# 20
# Turbo 비활성화
echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo
# HWP 동적 부스트 (Alder Lake+)
cat /sys/devices/system/cpu/intel_pstate/hwp_dynamic_boost
# 1 (활성)
scaling_governor를 보면 performance 또는 powersave만 표시되지만, 실제 주파수 결정은 CPU 마이크로코드가 EPP와 min/max 힌트를 기반으로 자율적으로 수행합니다.
AMD P-state 드라이버
AMD Zen2+ CPU는 amd-pstate 드라이버를 사용하여 CPPC(Collaborative Processor Performance Control) 인터페이스를 통해 주파수를 제어합니다. 기존 acpi-cpufreq가 3단계 이산 P-state만 지원하는 것과 달리, CPPC는 연속적인 성능 범위를 제공합니다.
CPPC2 통신 경로
AMD P-state 동작 모드
| 모드 | 커널 파라미터 | 설명 | 드라이버 역할 | 펌웨어 역할 |
|---|---|---|---|---|
| Active | amd_pstate=active | EPP 기반 자율 제어 | min/max/EPP만 설정 | 자율 주파수 결정 |
| Guided | amd_pstate=guided | 범위 기반 자율 제어 | min/max 범위 설정 | 범위 내 자율 결정 |
| Passive | amd_pstate=passive | 명시적 목표 제어 | desired_perf 직접 지정 | 지정값 반영 |
CPPC 성능 수준
/* drivers/cpufreq/amd-pstate.h */
struct amd_cpudata {
u32 highest_perf; /* 부스트 포함 최고 성능 */
u32 nominal_perf; /* 지속 가능한 최고 성능 */
u32 lowest_nonlinear_perf; /* 효율 임계점 */
u32 lowest_perf; /* 절대 최저 성능 */
u32 max_freq; /* nominal_perf에 대응하는 주파수 */
u32 max_limit_freq; /* 허용 최대 주파수 */
u32 min_limit_freq; /* 허용 최소 주파수 */
u32 lowest_nonlinear_freq; /* 비선형 임계 주파수 */
u32 epp_cached; /* 캐시된 EPP 값 */
bool boost_supported; /* 부스트 지원 여부 */
bool hw_prefcore; /* HW Preferred Core 지원 */
};
# AMD P-state 드라이버 상태 확인
cat /sys/devices/system/cpu/amd_pstate/status
# active / guided / passive
# 성능 수준 확인 (정책별)
cat /sys/devices/system/cpu/cpufreq/policy0/amd_pstate_highest_perf
# 166
cat /sys/devices/system/cpu/cpufreq/policy0/amd_pstate_max_freq
# 4500000 (4.5 GHz)
cat /sys/devices/system/cpu/cpufreq/policy0/amd_pstate_lowest_nonlinear_freq
# 1400000 (1.4 GHz)
# Preferred Core 확인
cat /sys/devices/system/cpu/cpufreq/policy0/amd_pstate_hw_prefcore
# supported
cat /sys/devices/system/cpu/cpufreq/policy0/amd_pstate_prefcore_ranking
# 166 (코어 성능 순위)
# 동작 모드 런타임 전환 (6.5+ 커널)
echo guided > /sys/devices/system/cpu/amd_pstate/status
highest_perf 레지스터를 통해 코어별 성능 순위를 스케줄러에 전달하여, 고성능 코어에 중요 태스크를 우선 배치합니다. amd_pstate_prefcore_ranking이 높은 코어일수록 더 높은 클록에 도달할 수 있습니다.
Turbo Boost / Precision Boost
Turbo는 열/전력 여유가 있을 때 일시적으로 최대 주파수를 초과하는 기능입니다.
Turbo 작동 원리
- Intel Turbo Boost 2.0/3.0: 1-2 코어 부하 시 기본 3.0GHz → 5.0GHz+, 활성 코어 수에 반비례하여 부스트 클록 감소
- AMD Precision Boost 2: CPPC highest_perf까지 동적 주파수 증가, 25MHz 단위 세밀 조정
- 제약: TDP (Thermal Design Power), 온도, 전류(EDC/TDC), 전압 제한
Turbo Boost 주의사항
- 비결정적: 온도/전력에 따라 주파수 변동 → 벤치마크 시 비활성화 권장
- 전체 코어: 모든 코어 100% 시 Turbo 미적용 또는 감소된 부스트 (TDP 제한)
- 서버: 예측 가능성 위해 Turbo 비활성화하는 경우 많음
- 열 스로틀링: Turbo 주파수에서 장시간 동작 시 열 압력(thermal pressure)으로 강제 감속
# Intel Turbo 비활성화
echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo
# AMD Boost 비활성화
echo 0 > /sys/devices/system/cpu/cpufreq/boost
# 범용 (acpi-cpufreq 등)
echo 0 > /sys/devices/system/cpu/cpufreq/boost
열 압력 (Thermal Pressure) 연동
CPU 온도가 임계값을 초과하면 하드웨어 또는 펌웨어가 최대 주파수를 제한합니다. 커널은 이 정보를 thermal pressure로 스케줄러에 전달하여 부하 분산 결정에 반영합니다.
열 압력 피드백 루프
/* include/linux/sched/topology.h */
/* 열 압력을 스케줄러에 전달 */
void arch_set_thermal_pressure(const struct cpumask *cpus,
unsigned long th_pressure);
/* 스케줄러가 열 압력 조회 */
static inline unsigned long arch_scale_thermal_pressure(int cpu)
{
return per_cpu(thermal_pressure, cpu);
}
/* cpufreq cooling device에서 호출 */
static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
unsigned int clip_freq = cpufreq_cdev->freq_table[state].frequency;
/* 최대 주파수 제한 */
cpufreq_cdev->clipped_freq = clip_freq;
cpufreq_update_policy(cpufreq_cdev->policy->cpu);
/* thermal pressure 업데이트 */
arch_set_thermal_pressure(cpufreq_cdev->policy->cpus,
max_capacity - clip_capacity);
return 0;
}
dmesg | grep -i throttle로 열 스로틀링 로그를 확인하세요. Intel CPU는 turbostat의 PkgWatt와 CorWatt 컬럼으로 전력 제한 여부를, perf stat -e msr/tsc/,msr/aperf/,msr/mperf/로 실제 vs 요청 주파수 비율을 확인할 수 있습니다.
에너지 모델 (Energy Model)
에너지 모델(EM, Energy Model)은 CPU의 주파수-전력 관계를 모델링하여 EAS(Energy Aware Scheduling)에 제공합니다. 스케줄러는 EM을 참조하여 태스크를 가장 에너지 효율적인 CPU에 배치합니다.
에너지 모델 곡선
/* include/linux/energy_model.h */
struct em_perf_state {
unsigned long frequency; /* kHz */
unsigned long power; /* mW (밀리와트) */
unsigned long cost; /* 에너지 비용 (정규화) */
unsigned long flags;
};
struct em_perf_domain {
struct em_perf_state *table; /* 성능 상태 테이블 */
int nr_perf_states; /* 상태 수 */
unsigned long cpus[]; /* 이 도메인의 CPU들 */
};
/* 에너지 모델 등록 - cpufreq 드라이버가 호출 */
int em_dev_register_perf_domain(struct device *dev,
unsigned int nr_states,
struct em_data_callback *cb,
cpumask_var_t cpus,
bool microwatts);
EAS 연동
EAS(Energy Aware Scheduling)는 에너지 모델을 참조하여 태스크 배치 시 에너지 소비를 최소화합니다:
/* kernel/sched/fair.c - EAS 태스크 배치 */
static int find_energy_efficient_cpu(struct task_struct *p, int prev_cpu)
{
unsigned long best_delta = ULONG_MAX;
int best_energy_cpu = prev_cpu;
/* 각 성능 도메인에 대해 */
for_each_pd(pd, ...) {
/* 현재 에너지 + 태스크 추가 시 에너지 계산 */
unsigned long cur_energy = compute_energy(p, prev_cpu, pd);
unsigned long new_energy = compute_energy(p, cpu, pd);
unsigned long delta = new_energy - cur_energy;
if (delta < best_delta) {
best_delta = delta;
best_energy_cpu = cpu;
}
}
return best_energy_cpu;
}
/proc/sys/kernel/sched_energy_aware가 1이면 EAS가 활성 상태입니다.
OPP (Operating Performance Points) 프레임워크
OPP 프레임워크는 주파수-전압 쌍을 관리하는 공통 인터페이스입니다. ARM SoC에서 주로 사용되며, Device Tree에서 OPP 테이블을 정의하고 cpufreq 드라이버가 이를 읽어 주파수 테이블을 구성합니다.
OPP 테이블에서 cpufreq 연동까지
Device Tree OPP 바인딩
/* Device Tree OPP 테이블 예제 (ARM SoC) */
cpu0_opp_table: opp-table {
compatible = "operating-points-v2";
opp-shared;
opp-600000000 {
opp-hz = /bits/ 64 <600000000>; /* 600 MHz */
opp-microvolt = <900000>; /* 0.9V */
opp-microamp = <300000>; /* 300 mA */
clock-latency-ns = <300000>; /* 300 us */
};
opp-1200000000 {
opp-hz = /bits/ 64 <1200000000>; /* 1.2 GHz */
opp-microvolt = <1000000>; /* 1.0V */
opp-microamp = <500000>; /* 500 mA */
clock-latency-ns = <300000>;
};
opp-1800000000 {
opp-hz = /bits/ 64 <1800000000>; /* 1.8 GHz */
opp-microvolt = <1100000 1150000 1050000>; /* target, max, min */
opp-microamp = <800000>;
clock-latency-ns = <300000>;
opp-suspend; /* suspend 시 이 OPP 사용 */
};
};
/* OPP 프레임워크 API */
#include <linux/pm_opp.h>
/* DT에서 OPP 테이블 로드 */
int dev_pm_opp_of_add_table(struct device *dev);
/* 주어진 주파수 이상의 OPP 찾기 */
struct dev_pm_opp *dev_pm_opp_find_freq_ceil(struct device *dev,
unsigned long *freq);
/* OPP의 전압 조회 */
unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp);
/* OPP 수 조회 */
int dev_pm_opp_get_opp_count(struct device *dev);
/* cpufreq 드라이버에서 OPP → freq_table 변환 */
int dev_pm_opp_init_cpufreq_table(struct device *dev,
struct cpufreq_frequency_table **table);
주파수 불변성 (Frequency Invariance)
스케줄러의 PELT 신호는 현재 CPU 주파수에 영향을 받습니다. 1GHz에서 50% 활용률과 2GHz에서 50% 활용률은 실제 처리량이 다르지만, 보정 없이는 동일하게 보입니다. 주파수 불변성(frequency invariance)은 이 차이를 보정합니다.
주파수 불변성 보정
/* arch/x86/kernel/smpboot.c - x86 주파수 불변성 */
static void x86_arch_scale_freq_tick(void)
{
u64 aperf, mperf;
unsigned long freq_scale;
/* APERF: 실제 동작 사이클 수 */
rdmsrl(MSR_IA32_APERF, aperf);
/* MPERF: 최대 주파수 기준 사이클 수 */
rdmsrl(MSR_IA32_MPERF, mperf);
/* freq_scale = aperf / mperf * SCHED_CAPACITY_SCALE */
freq_scale = div64_u64(aperf * SCHED_CAPACITY_SCALE, mperf);
this_cpu_write(arch_freq_scale, freq_scale);
}
/* 스케줄러가 주파수 스케일 팩터 조회 */
unsigned long arch_scale_freq_capacity(int cpu)
{
return per_cpu(arch_freq_scale, cpu);
}
| 아키텍처 | 주파수 불변성 소스 | 정확도 | 업데이트 주기 |
|---|---|---|---|
| x86 (Intel/AMD) | APERF/MPERF MSR 카운터 | 높음 (HW 측정) | 매 스케줄러 tick |
| ARM64 (AMU) | Activity Monitor Unit 카운터 | 높음 (HW 측정) | 매 스케줄러 tick |
| ARM64 (비AMU) | cpufreq 드라이버 피드백 | 중간 (SW 추적) | 주파수 전환 시 |
| ARM32 | cpufreq 드라이버 피드백 | 중간 (SW 추적) | 주파수 전환 시 |
거버너 비교 분석
각 거버너의 내부 동작 원리와 파라미터를 비교 분석합니다.
거버너 결정 트리
거버너별 파라미터 비교
| 파라미터 | ondemand | conservative | schedutil |
|---|---|---|---|
| 부하 추적 방식 | 유휴 시간 비율 | 유휴 시간 비율 | PELT (스케줄러 직접) |
| 주파수 상승 조건 | 부하 > up_threshold | 부하 > up_threshold | 활용률 비례 계산 |
| 주파수 하강 조건 | 부하 < down_differential | 부하 < down_threshold | 활용률 비례 계산 |
| 상승 패턴 | 즉시 최대 | freq_step% 단계적 | 비례 (1.25x) |
| 하강 패턴 | 비례 감소 | freq_step% 단계적 | 비례 (1.25x) |
| 샘플링 주기 | sampling_rate (us) | sampling_rate (us) | 스케줄러 이벤트 기반 |
| IO-wait 부스트 | io_is_busy | 없음 | iowait_boost |
| fast_switch | 미지원 | 미지원 | 지원 |
ondemand / conservative 파라미터 상세
# ========== ondemand 파라미터 ==========
ls /sys/devices/system/cpu/cpufreq/ondemand/
# 부하 임계치 (기본 95%): 이 이상이면 즉시 최대 주파수
echo 80 > /sys/devices/system/cpu/cpufreq/ondemand/up_threshold
# 샘플링 간격 (마이크로초): 부하 확인 주기
echo 10000 > /sys/devices/system/cpu/cpufreq/ondemand/sampling_rate
# IO 부하 반영: 1이면 IO-wait도 CPU 부하에 포함
echo 1 > /sys/devices/system/cpu/cpufreq/ondemand/io_is_busy
# 주파수 하강 차등: up_threshold - sampling_down_factor
echo 10 > /sys/devices/system/cpu/cpufreq/ondemand/sampling_down_factor
# ========== conservative 파라미터 ==========
ls /sys/devices/system/cpu/cpufreq/conservative/
# 상승 임계치
echo 80 > /sys/devices/system/cpu/cpufreq/conservative/up_threshold
# 하강 임계치
echo 20 > /sys/devices/system/cpu/cpufreq/conservative/down_threshold
# 주파수 변경 단계 (% 단위, 기본 5%)
echo 5 > /sys/devices/system/cpu/cpufreq/conservative/freq_step
cpufreq sysfs 인터페이스 완전 가이드
cpufreq의 모든 제어와 모니터링은 /sys/devices/system/cpu/ 아래의 sysfs 인터페이스를 통해 이루어집니다.
정책별 sysfs 속성
경로: /sys/devices/system/cpu/cpufreq/policyX/ (X는 정책 번호)
| 속성 | 읽기/쓰기 | 설명 |
|---|---|---|
affected_cpus | R | 이 정책의 영향을 받는 온라인 CPU 목록 |
related_cpus | R | 같은 주파수 도메인의 모든 CPU (오프라인 포함) |
cpuinfo_min_freq | R | 하드웨어 지원 최소 주파수 (kHz) |
cpuinfo_max_freq | R | 하드웨어 지원 최대 주파수 (kHz) |
cpuinfo_transition_latency | R | P-state 전환 지연 시간 (ns) |
scaling_driver | R | 사용 중인 드라이버 이름 |
scaling_governor | R/W | 현재 거버너 (변경 가능) |
scaling_available_governors | R | 사용 가능한 거버너 목록 |
scaling_min_freq | R/W | 사용자 설정 최소 주파수 제한 |
scaling_max_freq | R/W | 사용자 설정 최대 주파수 제한 |
scaling_cur_freq | R | 마지막으로 요청된 주파수 |
scaling_available_frequencies | R | 지원 주파수 목록 (이산 드라이버만) |
scaling_setspeed | R/W | userspace 거버너에서 주파수 직접 설정 |
cpuinfo_cur_freq | R | 하드웨어에서 읽은 실제 현재 주파수 |
energy_performance_preference | R/W | EPP 설정 (HWP/CPPC 드라이버) |
energy_performance_available_preferences | R | 사용 가능한 EPP 목록 |
전역 sysfs 속성
# 전역 속성 (모든 정책 공통)
cat /sys/devices/system/cpu/cpufreq/boost
# 1 (Turbo/Boost 활성)
# Intel P-state 전용
ls /sys/devices/system/cpu/intel_pstate/
# max_perf_pct min_perf_pct no_turbo status hwp_dynamic_boost
# AMD P-state 전용
ls /sys/devices/system/cpu/amd_pstate/
# status prefcore
# 전환 통계
cat /sys/devices/system/cpu/cpufreq/policy0/stats/total_trans
# 15432 (총 전환 횟수)
cat /sys/devices/system/cpu/cpufreq/policy0/stats/time_in_state
# 800000 5234 (800MHz에서 5234 jiffies)
# 1200000 3421 (1.2GHz에서 3421 jiffies)
# ...
scaling_cur_freq는 드라이버가 마지막으로 요청한 주파수이고, cpuinfo_cur_freq는 하드웨어에서 실제로 읽은 주파수입니다. HWP 모드에서는 하드웨어가 자율적으로 주파수를 결정하므로 두 값이 다를 수 있습니다. 정확한 주파수를 알려면 cpuinfo_cur_freq를 확인하세요.
cpufreq 드라이버 작성 가이드
새로운 SoC나 플랫폼을 위한 cpufreq 드라이버를 작성하는 단계별 가이드입니다.
최소 드라이버 예제
#include <linux/cpufreq.h>
#include <linux/module.h>
#include <linux/pm_opp.h>
#include <linux/clk.h>
#define DRIVER_NAME "my_soc_cpufreq"
/* ---- 1단계: 정책 초기화 ---- */
static int my_cpufreq_init(struct cpufreq_policy *policy)
{
struct device *cpu_dev;
struct cpufreq_frequency_table *freq_table;
int ret;
cpu_dev = get_cpu_device(policy->cpu);
if (!cpu_dev)
return -ENODEV;
/* OPP 테이블에서 주파수 목록 로드 */
ret = dev_pm_opp_of_add_table(cpu_dev);
if (ret)
return ret;
/* OPP → cpufreq 주파수 테이블 변환 */
ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
if (ret)
goto err_opp;
/* 정책에 주파수 테이블 설정 */
ret = cpufreq_table_validate_and_sort(policy);
policy->freq_table = freq_table;
/* clock framework 연결 */
policy->clk = clk_get(cpu_dev, NULL);
/* 전환 지연 설정 (나노초) */
policy->cpuinfo.transition_latency = 300000; /* 300 us */
/* 현재 주파수 설정 */
policy->cur = clk_get_rate(policy->clk) / 1000;
return 0;
err_opp:
dev_pm_opp_of_remove_table(cpu_dev);
return ret;
}
/* ---- 2단계: 정책 유효성 검증 ---- */
static int my_cpufreq_verify(struct cpufreq_policy_data *policy)
{
return cpufreq_generic_frequency_table_verify(policy);
}
/* ---- 3단계: 주파수 설정 (target_index) ---- */
static int my_cpufreq_target_index(struct cpufreq_policy *policy,
unsigned int index)
{
struct cpufreq_frequency_table *freq_table = policy->freq_table;
unsigned int new_freq = freq_table[index].frequency;
struct device *cpu_dev = get_cpu_device(policy->cpu);
struct dev_pm_opp *opp;
unsigned long volt, freq_hz = new_freq * 1000;
int ret;
/* 목표 주파수의 OPP 찾기 */
opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_hz);
if (IS_ERR(opp))
return PTR_ERR(opp);
volt = dev_pm_opp_get_voltage(opp);
dev_pm_opp_put(opp);
/* 전압 설정 (레귤레이터) → 주파수 설정 (클록) */
ret = regulator_set_voltage(cpu_reg, volt, volt);
if (ret)
return ret;
ret = clk_set_rate(policy->clk, freq_hz);
return ret;
}
/* ---- 4단계: 현재 주파수 조회 ---- */
static unsigned int my_cpufreq_get(unsigned int cpu)
{
struct cpufreq_policy *policy = cpufreq_cpu_get_raw(cpu);
return clk_get_rate(policy->clk) / 1000;
}
/* ---- 5단계: 정리 ---- */
static int my_cpufreq_exit(struct cpufreq_policy *policy)
{
clk_put(policy->clk);
dev_pm_opp_of_remove_table(get_cpu_device(policy->cpu));
return 0;
}
/* ---- 6단계: 드라이버 구조체 정의 ---- */
static struct cpufreq_driver my_cpufreq_driver = {
.name = DRIVER_NAME,
.flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK |
CPUFREQ_IS_COOLING_DEV,
.init = my_cpufreq_init,
.exit = my_cpufreq_exit,
.verify = my_cpufreq_verify,
.target_index = my_cpufreq_target_index,
.get = my_cpufreq_get,
.attr = cpufreq_generic_attr,
.suspend = cpufreq_generic_suspend,
};
/* ---- 7단계: 모듈 등록 ---- */
static int __init my_cpufreq_driver_init(void)
{
return cpufreq_register_driver(&my_cpufreq_driver);
}
static void __exit my_cpufreq_driver_exit(void)
{
cpufreq_unregister_driver(&my_cpufreq_driver);
}
module_init(my_cpufreq_driver_init);
module_exit(my_cpufreq_driver_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Example CPUFreq driver");
드라이버 작성 체크리스트
| 단계 | 필수 항목 | 관련 함수/매크로 |
|---|---|---|
| 1 | 주파수 테이블 구성 (OPP 또는 수동) | dev_pm_opp_of_add_table() |
| 2 | init 콜백에서 policy 초기화 | cpuinfo.min/max_freq, cur, clk |
| 3 | verify 콜백 구현 | cpufreq_generic_frequency_table_verify() |
| 4 | target/target_index/fast_switch 중 택1 | 슬립 가능 여부에 따라 선택 |
| 5 | get 콜백으로 실제 주파수 조회 | clk_get_rate() 또는 MSR 읽기 |
| 6 | exit 콜백으로 정리 | OPP 테이블 해제, clock put |
| 7 | 에너지 모델 등록 (EAS 지원 시) | em_dev_register_perf_domain() |
| 8 | cooling device 등록 (열 관리) | CPUFREQ_IS_COOLING_DEV 플래그 |
fast_switch 콜백은 인터럽트 컨텍스트에서 호출되므로 슬립할 수 없습니다. mutex, kmalloc(GFP_KERNEL), usleep_range 등을 사용할 수 없습니다. MSR 쓰기나 MMIO 같은 비차단 연산만 가능합니다. 지원하려면 init에서 policy->fast_switch_possible = true를 설정하세요.
모니터링
CPU 주파수 상태를 확인하는 다양한 방법입니다.
/sys/devices/system/cpu/cpu*/cpufreq
# 현재 주파수
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
# 2400000 (2.4GHz)
# 지원 주파수 목록
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies
# 800000 1200000 1600000 2000000 2400000 3000000
# Governor
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# schedutil
# 지원 Governor 목록
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors
# conservative ondemand schedutil performance powersave
cpupower 유틸리티
# 주파수 정보 확인
cpupower frequency-info
# CPU 0: 800 MHz - 3.0 GHz
# current CPU frequency: 1.5 GHz (asserted by call to hardware)
# Governor 변경
cpupower frequency-set -g performance
# 주파수 범위 제한
cpupower frequency-set -d 1.2GHz -u 2.4GHz
# 모든 CPU 정보 요약
cpupower -c all frequency-info -m
실시간 모니터링
# watch로 실시간 확인
watch -n1 'grep MHz /proc/cpuinfo'
# turbostat (가장 상세)
turbostat --interval 1
# Core CPU Avg_MHz Busy% Bzy_MHz TSC_MHz IRQ PkgWatt CorWatt
# 0 0 1200 40.00 3000 3000 1234 45.2 32.1
# perf로 주파수 전환 추적
perf stat -e power/energy-cores/,power/energy-pkg/ -a sleep 5
# ftrace로 cpufreq 이벤트 추적
echo 1 > /sys/kernel/debug/tracing/events/power/cpu_frequency/enable
cat /sys/kernel/debug/tracing/trace
# <idle>-0 [001] 123.456: cpu_frequency: state=2400000 cpu_id=1
cpufreq 트레이스포인트
# 사용 가능한 cpufreq 트레이스포인트
ls /sys/kernel/debug/tracing/events/power/
# cpu_frequency (주파수 변경)
# cpu_frequency_limits (주파수 제한 변경)
# pstate_sample (intel_pstate 샘플링)
# 주파수 변경 이벤트 실시간 모니터링
echo 1 > /sys/kernel/debug/tracing/events/power/cpu_frequency/enable
echo 1 > /sys/kernel/debug/tracing/events/power/cpu_frequency_limits/enable
cat /sys/kernel/debug/tracing/trace_pipe
# sugov:0-1234 [000] 456.789: cpu_frequency: state=3600000 cpu_id=0
# sugov:0-1234 [000] 457.012: cpu_frequency: state=800000 cpu_id=0
성능 튜닝
워크로드별 최적 설정입니다.
워크로드별 권장 설정
| 워크로드 | Governor | Turbo | EPP | 이유 |
|---|---|---|---|---|
| 데스크톱 | schedutil | ON | balance_performance | 응답성 + 절전 |
| 노트북 | schedutil | ON | balance_power | 배터리 수명 |
| 웹 서버 | performance | OFF | performance | 예측 가능한 지연 |
| HPC/렌더링 | performance | ON | performance | 최대 성능 |
| 실시간 (PREEMPT_RT) | performance | OFF | performance | 결정적 동작 |
| 배치 처리 | schedutil | ON | balance_performance | 효율성 |
| 데이터베이스 | performance | ON | balance_performance | 지연 시간 최소 |
| 가상화 호스트 | performance | ON | performance | 게스트 성능 보장 |
| 임베디드/IoT | conservative | OFF | power | 전력 절약 극대화 |
서버 최적화 예제
# ========== 웹 서버 - 낮은 지연 우선 ==========
cpupower frequency-set -g performance
echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference; do
echo performance > "$cpu"
done
# ========== DB 서버 - 예측 가능성 + 성능 ==========
cpupower frequency-set -g performance
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference; do
echo balance_performance > "$cpu"
done
# ========== 개발 서버 - 균형 ==========
cpupower frequency-set -g schedutil
# ========== Kubernetes 노드 - 안정적 성능 ==========
cpupower frequency-set -g performance
# cgroup v2 cpu.max로 컨테이너별 CPU 시간 제한
echo "100000 100000" > /sys/fs/cgroup/my-pod/cpu.max
Governor 파라미터 튜닝
# ondemand governor 파라미터
echo 50 > /sys/devices/system/cpu/cpufreq/ondemand/up_threshold
# 50% 부하에서 주파수 상승 (기본값: 95)
echo 10000 > /sys/devices/system/cpu/cpufreq/ondemand/sampling_rate
# 10ms마다 부하 확인 (기본값: 가변)
# conservative governor 파라미터
echo 5 > /sys/devices/system/cpu/cpufreq/conservative/freq_step
# 5% 단계로 주파수 변경 (기본값: 5)
자동화 스크립트
#!/bin/bash
# cpufreq-setup.sh - 서버 최적화 자동 설정 스크립트
set -euo pipefail
PROFILE="${1:-balanced}"
DRIVER=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver)
echo "드라이버: $DRIVER, 프로필: $PROFILE"
case "$PROFILE" in
performance)
cpupower frequency-set -g performance
if [[ "$DRIVER" == "intel_pstate" ]]; then
echo 0 > /sys/devices/system/cpu/intel_pstate/no_turbo
for f in /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference; do
echo performance > "$f"
done
fi
;;
balanced)
cpupower frequency-set -g schedutil
if [[ "$DRIVER" == "intel_pstate" ]]; then
for f in /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference; do
echo balance_performance > "$f"
done
fi
;;
powersave)
cpupower frequency-set -g schedutil
if [[ "$DRIVER" == "intel_pstate" ]]; then
echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo
for f in /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference; do
echo power > "$f"
done
fi
;;
esac
echo "설정 완료. 현재 주파수:"
cpupower -c all frequency-info -f
Intel vs AMD 상세 비교
| 항목 | Intel (intel_pstate) | AMD (amd-pstate) |
|---|---|---|
| 드라이버 | intel_pstate (전용) | amd-pstate (Zen2+) 또는 acpi-cpufreq |
| HW 인터페이스 | MSR (IA32_HWP_REQUEST) | MSR (CPPC) 또는 ACPI PCC |
| 자율 모드 | HWP (active mode) | EPP autonomous (active mode) |
| 소프트웨어 제어 | passive mode (SWP) | passive mode / guided mode |
| 성능 범위 | 이산 P-state (테이블 기반) | 연속 범위 (CPPC 단위 없는 값) |
| Turbo 이름 | Turbo Boost 2.0/3.0 | Precision Boost 2/Overdrive |
| EPP 범위 | 0-255 (4개 프리셋) | 0-255 (4개 프리셋) |
| 코어 우선순위 | Turbo Boost Max 3.0 | Preferred Core (CPPC) |
| fast_switch | HWP: MSR 직접 쓰기 | MSR 지원 시 가능 (Zen3+) |
| 에너지 모델 | 미사용 (HWP 자율) | passive/guided 모드에서 사용 가능 |
| 최소 CPU | Sandy Bridge (2세대) | Zen2 (Ryzen 3000) |
# 드라이버 확인
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver
# intel_pstate (Intel CPU)
# amd-pstate (AMD Zen2+ CPU)
# acpi-cpufreq (레거시)
# Intel: HWP 활성 확인
cat /sys/devices/system/cpu/intel_pstate/status
# active (HWP 사용 중)
# AMD: CPPC 모드 확인
cat /sys/devices/system/cpu/amd_pstate/status
# active / guided / passive
# 두 드라이버 모두: CPPC/HWP 지원 여부 확인
grep -E 'hwp|cppc' /proc/cpuinfo
# flags: ... hwp hwp_notify hwp_act_window hwp_epp ...
문제 해결
일반적인 CPUFreq 관련 문제와 해결 방법입니다.
주파수가 변경되지 않음
# 1. BIOS에서 SpeedStep/Cool'n'Quiet 활성화 확인
# 2. Governor가 userspace가 아닌지 확인
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# performance로 변경
# 3. 드라이버 로드 확인
lsmod | grep -E 'pstate|cpufreq'
# 4. 커널 파라미터 확인
cat /proc/cmdline | grep intel_pstate
# intel_pstate=disable이 없어야 함
# 5. 주파수 제한 확인
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
# min == max이면 주파수 고정 상태
# 6. CPU가 오프라인인지 확인
cat /sys/devices/system/cpu/online
성능 저하
# 현재 주파수 확인
cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq
# Thermal throttling 확인
dmesg | grep -i -E 'throttle|thermal|temperature'
# Intel: PROCHOT 외부 제한 확인
turbostat --interval 1 | grep -E 'PROCHOT|PkgWatt'
# 전력 제한 확인
cat /sys/class/powercap/intel-rapl/intel-rapl:0/constraint_0_power_limit_uw
# Governor를 performance로 강제
cpupower frequency-set -g performance
# 열 압력 확인
cat /sys/devices/system/cpu/cpu*/topology/thermal_pressure
자주 발생하는 문제
| 증상 | 원인 | 해결 |
|---|---|---|
| 주파수가 항상 최저 | powersave 거버너 또는 EPP=power | 거버너/EPP 변경 |
| 주파수가 올라갔다 내려감 | 열 스로틀링 | 냉각 확인, Turbo 비활성화 |
| scaling_driver가 acpi-cpufreq | intel_pstate 비활성화됨 | 커널 파라미터 확인 |
| boost 파일이 없음 | HW 부스트 미지원 또는 드라이버 미지원 | CPU/BIOS 확인 |
| EPP 변경 불가 | HWP 모드가 아님 | intel_pstate status 확인 |
| amd-pstate 로드 안됨 | 커널 파라미터 미설정 (6.3 이전) | amd_pstate=active 추가 |
| 주파수 전환이 느림 | ACPI 기반 드라이버 (fast_switch 미지원) | MSR 기반 드라이버 사용 |
turbostat으로 현재 상태를 기록하고, 변경 후 충분한 모니터링 시간을 확보하세요. 특히 DB 서버에서 주파수 전환 지연이 쿼리 레이턴시에 영향을 줄 수 있습니다.
커널 설정 옵션
CPUFreq 관련 주요 커널 설정(CONFIG_) 옵션입니다.
주요 CONFIG 옵션
| 옵션 | 설명 | 기본값 |
|---|---|---|
CONFIG_CPU_FREQ | CPUFreq 서브시스템 활성화 | Y |
CONFIG_CPU_FREQ_STAT | 주파수 전환 통계 | Y |
CONFIG_CPU_FREQ_DEFAULT_GOV_SCHEDUTIL | 기본 거버너를 schedutil로 설정 | Y (6.x) |
CONFIG_CPU_FREQ_GOV_PERFORMANCE | performance 거버너 | Y |
CONFIG_CPU_FREQ_GOV_POWERSAVE | powersave 거버너 | Y |
CONFIG_CPU_FREQ_GOV_ONDEMAND | ondemand 거버너 | M/Y |
CONFIG_CPU_FREQ_GOV_CONSERVATIVE | conservative 거버너 | M |
CONFIG_CPU_FREQ_GOV_SCHEDUTIL | schedutil 거버너 | Y |
CONFIG_X86_INTEL_PSTATE | Intel P-state 드라이버 | Y |
CONFIG_X86_AMD_PSTATE | AMD P-state 드라이버 | Y |
CONFIG_X86_ACPI_CPUFREQ | ACPI 기반 cpufreq 드라이버 | M |
CONFIG_ENERGY_MODEL | 에너지 모델 프레임워크 | Y |
CONFIG_PM_OPP | OPP 프레임워크 | Y (ARM) |
# 현재 커널의 CPUFreq 관련 설정 확인
zcat /proc/config.gz | grep -i cpu_freq
# 또는
grep -i cpu_freq /boot/config-$(uname -r)
# 로드된 cpufreq 모듈 확인
lsmod | grep -i cpufreq
# cpufreq_conservative 16384 0
# cpufreq_ondemand 16384 0
커널 커맨드라인 파라미터
| 파라미터 | 설명 | 예시 |
|---|---|---|
intel_pstate=disable | intel_pstate 비활성화 (acpi-cpufreq 사용) | 레거시 호환 |
intel_pstate=passive | intel_pstate를 passive 모드로 (cpufreq 거버너 사용) | schedutil 직접 사용 |
intel_pstate=hwp_only | HWP 미지원 시 로드 안함 | HWP 필수 환경 |
intel_pstate=no_hwp | HWP 비활성화 (SWP 모드 강제) | HWP 문제 디버깅 |
amd_pstate=active | AMD P-state active 모드 | EPP 기반 자율 제어 |
amd_pstate=guided | AMD P-state guided 모드 | 범위 기반 자율 제어 |
amd_pstate=passive | AMD P-state passive 모드 | 명시적 목표 제어 |
amd_pstate.shared_mem=1 | ACPI 공유 메모리 방식 사용 | MSR 미지원 CPU |
cpufreq.default_governor= | 기본 거버너 오버라이드 | =performance |
주파수 전환 메커니즘
CPU 주파수가 변경될 때 커널은 notifier chain을 통해 관련 서브시스템에 통보합니다. 이 메커니즘은 loops_per_jiffy 재계산, 타이머 보정 등에 필수적입니다.
전환 Notifier
/* include/linux/cpufreq.h */
#define CPUFREQ_PRECHANGE (0) /* 주파수 변경 전 */
#define CPUFREQ_POSTCHANGE (1) /* 주파수 변경 후 */
struct cpufreq_freqs {
struct cpufreq_policy *policy;
unsigned int old; /* 이전 주파수 (kHz) */
unsigned int new; /* 새 주파수 (kHz) */
u8 flags; /* CPUFREQ_CONST_LOOPS 등 */
};
/* notifier 등록 */
int cpufreq_register_notifier(struct notifier_block *nb,
unsigned int list);
/* notifier 해제 */
int cpufreq_unregister_notifier(struct notifier_block *nb,
unsigned int list);
전환 흐름 상세
/* drivers/cpufreq/cpufreq.c - 주파수 전환 전체 흐름 */
int __cpufreq_driver_target(struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation)
{
unsigned int old_freq = policy->cur;
int retval;
/* 1단계: 주파수 해석 (resolve) */
unsigned int resolved = cpufreq_driver_resolve_freq(policy, target_freq);
/* 2단계: PRECHANGE 통지 */
struct cpufreq_freqs freqs = {
.policy = policy,
.old = old_freq,
.new = resolved,
};
cpufreq_freq_transition_begin(policy, &freqs);
/* 3단계: 실제 하드웨어 주파수 변경 */
retval = cpufreq_driver->target_index(policy, index);
/* 4단계: POSTCHANGE 통지 */
cpufreq_freq_transition_end(policy, &freqs, retval);
/* 5단계: 정책의 현재 주파수 업데이트 */
if (!retval)
policy->cur = resolved;
return retval;
}
Notifier 등록 예제
/* 주파수 변경 이벤트를 감지하는 모듈 예제 */
static int my_cpufreq_callback(struct notifier_block *nb,
unsigned long val, void *data)
{
struct cpufreq_freqs *freq = data;
switch (val) {
case CPUFREQ_PRECHANGE:
pr_info("CPU%d: %u kHz -> %u kHz (시작)\n",
freq->policy->cpu, freq->old, freq->new);
break;
case CPUFREQ_POSTCHANGE:
pr_info("CPU%d: %u kHz -> %u kHz (완료)\n",
freq->policy->cpu, freq->old, freq->new);
break;
}
return NOTIFY_OK;
}
static struct notifier_block my_cpufreq_nb = {
.notifier_call = my_cpufreq_callback,
};
/* 모듈 초기화 시 등록 */
cpufreq_register_notifier(&my_cpufreq_nb, CPUFREQ_TRANSITION_NOTIFIER);
fast_switch 경로에서는 PRECHANGE/POSTCHANGE notifier가 호출되지 않습니다. 이는 fast_switch가 인터럽트 컨텍스트에서 동작하여 notifier chain을 안전하게 순회할 수 없기 때문입니다. 따라서 주파수 변경에 의존하는 서브시스템은 fast_switch 환경에서 대체 메커니즘이 필요합니다.
UCLAMP (Utilization Clamping) 연동
UCLAMP은 태스크별로 활용률의 최소/최대값을 제한하는 메커니즘입니다. schedutil 거버너는 UCLAMP 값을 주파수 결정에 직접 반영합니다.
UCLAMP 개념
/* include/linux/sched.h - 태스크별 UCLAMP */
struct task_struct {
/* ... */
struct uclamp_se uclamp_req[UCLAMP_CNT]; /* 요청값 */
struct uclamp_se uclamp[UCLAMP_CNT]; /* 유효값 */
};
/* UCLAMP 종류 */
enum uclamp_id {
UCLAMP_MIN = 0, /* 최소 활용률 보장 (성능 보장) */
UCLAMP_MAX = 1, /* 최대 활용률 제한 (전력 절약) */
UCLAMP_CNT,
};
UCLAMP이 schedutil에 미치는 영향
| UCLAMP 설정 | schedutil 주파수 결정 | 사용 시나리오 |
|---|---|---|
UCLAMP_MIN=512 | 활용률이 낮아도 최소 50% 주파수 보장 | 지연 시간이 중요한 태스크 |
UCLAMP_MAX=256 | 활용률이 높아도 25% 주파수로 제한 | 백그라운드 배치 작업 |
MIN=0, MAX=1024 | 제한 없음 (기본값) | 일반 태스크 |
MIN=768, MAX=1024 | 최소 75% 주파수 보장, 최대 제한 없음 | 실시간 오디오/비디오 |
# UCLAMP 설정 (cgroup v2)
echo 512 > /sys/fs/cgroup/my-task/cpu.uclamp.min
# 최소 활용률 50% 보장 → 최소 50% 주파수
echo 256 > /sys/fs/cgroup/my-task/cpu.uclamp.max
# 최대 활용률 25% 제한 → 최대 25% 주파수
# sched_attr로 태스크별 설정 (프로그래밍 방식)
# struct sched_attr attr = {
# .sched_util_min = 512, // UCLAMP_MIN
# .sched_util_max = 1024, // UCLAMP_MAX
# };
# sched_setattr(pid, &attr, 0);
UCLAMP_MIN=768을 설정하면, CPU 활용률이 낮은 순간에도 높은 주파수가 유지되어 쿼리 레이턴시가 안정됩니다. 반대로, 로그 압축 같은 배치 태스크에 UCLAMP_MAX=256을 설정하면 전력 소비를 줄이면서 전체 시스템 열 부담을 감소시킬 수 있습니다.
cpufreq와 CPU 핫플러그
CPU가 온라인/오프라인될 때 cpufreq 정책이 어떻게 관리되는지 설명합니다.
핫플러그 시 cpufreq 흐름
/* CPU 온라인 시 */
cpufreq_online(cpu)
→ cpufreq_policy_alloc() /* 정책 객체 할당 (없으면) */
→ cpufreq_driver->init(policy) /* 드라이버 초기화 */
→ cpufreq_init_policy(policy) /* 거버너 연결 */
→ cpufreq_start_governor(policy) /* 거버너 시작 */
/* CPU 오프라인 시 */
cpufreq_offline(cpu)
→ cpufreq_stop_governor(policy) /* 거버너 정지 */
→ cpufreq_exit_governor(policy) /* 거버너 해제 (마지막 CPU) */
→ cpufreq_driver->exit(policy) /* 드라이버 정리 (마지막 CPU) */
→ cpufreq_policy_free(policy) /* 정책 해제 (마지막 CPU) */
| 상황 | 정책 동작 | 거버너 동작 |
|---|---|---|
| 정책의 첫 CPU 온라인 | 정책 생성, freq_table 초기화 | init → start |
| 정책에 CPU 추가 | cpus 마스크에 CPU 추가 | 변경 없음 |
| 정책에서 CPU 제거 (마지막 아님) | cpus 마스크에서 CPU 제거 | 변경 없음 |
| 정책의 마지막 CPU 오프라인 | 정책 해제 | stop → exit |
| 정책의 대표 CPU 오프라인 | 대표 CPU 변경 | stop → start (새 대표 CPU) |
# CPU 오프라인 (cpufreq 정책에서 제거)
echo 0 > /sys/devices/system/cpu/cpu3/online
# CPU 온라인 (cpufreq 정책에 추가)
echo 1 > /sys/devices/system/cpu/cpu3/online
# 정책의 CPU 확인
cat /sys/devices/system/cpu/cpufreq/policy0/affected_cpus
# 0 1 2 3 (CPU3 오프라인 시: 0 1 2)
# 온라인 CPU 확인
cat /sys/devices/system/cpu/online
# 0-7
cpufreq 성능 분석 심화
CPU 주파수 스케일링이 실제 워크로드 성능에 미치는 영향을 분석하는 고급 기법들입니다.
핵심 성능 지표
| 지표 | 측정 방법 | 의미 |
|---|---|---|
| IPC (Instructions Per Cycle) | perf stat | 주파수 대비 실제 처리 효율 |
| 주파수 체류 시간 | time_in_state | 각 주파수에서 보낸 시간 비율 |
| 전환 횟수 | total_trans | 주파수 변경 빈도 (높으면 오버헤드) |
| C0 잔류 비율 | turbostat | CPU가 활성 상태인 시간 비율 |
| APERF/MPERF 비율 | MSR 읽기 | 실제 동작 주파수 / 기준 주파수 |
| 에너지 소비 | RAPL (perf stat) | 총 에너지 소비량 (Joules) |
| EDP (Energy-Delay Product) | 계산 | 에너지 효율의 종합 지표 |
성능 측정 실전
# ========== 1. IPC와 주파수 관계 측정 ==========
perf stat -e cycles,instructions,cache-misses,power/energy-cores/ \
-a --interval-print 1000 -- sleep 10
# 시간별 IPC 변화와 에너지 소비 관찰
# ========== 2. 주파수 체류 시간 분석 ==========
# 변경 전 스냅샷
cat /sys/devices/system/cpu/cpufreq/policy0/stats/time_in_state > /tmp/before.txt
# 워크로드 실행
stress-ng --cpu 4 --timeout 30
# 변경 후 스냅샷
cat /sys/devices/system/cpu/cpufreq/policy0/stats/time_in_state > /tmp/after.txt
# 차이 분석
diff /tmp/before.txt /tmp/after.txt
# ========== 3. turbostat 상세 분석 ==========
turbostat --show Core,CPU,Avg_MHz,Busy%,Bzy_MHz,TSC_MHz,PkgWatt,CorWatt,PkgTmp \
--interval 1
# ========== 4. perf로 cpufreq 이벤트와 성능 연관 분석 ==========
perf record -e power:cpu_frequency -e sched:sched_switch -a -- sleep 10
perf script | head -50
# ========== 5. 에너지 효율 비교 ==========
# performance 거버너에서
cpupower frequency-set -g performance
perf stat -e power/energy-pkg/ -- my_workload
# schedutil 거버너에서
cpupower frequency-set -g schedutil
perf stat -e power/energy-pkg/ -- my_workload
# EDP = Energy(J) * Time(s) → 낮을수록 효율적
BPF를 활용한 cpufreq 분석
/* cpufreq 전환 지연 측정 BPF 프로그램 개념 */
SEC("tracepoint/power/cpu_frequency")
int trace_cpu_freq(struct trace_event_raw_cpu_frequency *ctx)
{
u32 cpu = ctx->cpu_id;
u32 freq = ctx->state;
u64 ts = bpf_ktime_get_ns();
/* 주파수 변경 이벤트를 맵에 기록 */
struct freq_event evt = {
.cpu = cpu,
.freq = freq,
.timestamp = ts,
};
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU,
&evt, sizeof(evt));
return 0;
}
/* bpftrace 원라이너: 주파수 변경 분포 */
/* bpftrace -e 'tracepoint:power:cpu_frequency { @freq = hist(args->state / 1000); }' */
# bpftrace로 CPU 주파수 변경 실시간 모니터링
bpftrace -e 'tracepoint:power:cpu_frequency {
printf("CPU%d: %d MHz\n", args->cpu_id, args->state / 1000);
}'
# 주파수 분포 히스토그램
bpftrace -e 'tracepoint:power:cpu_frequency {
@freq_hist = hist(args->state / 1000);
} END { print(@freq_hist); }'
# 주파수 전환 간격 측정
bpftrace -e 'tracepoint:power:cpu_frequency {
@last[args->cpu_id] = nsecs;
if (@prev[args->cpu_id]) {
@interval_us = hist((nsecs - @prev[args->cpu_id]) / 1000);
}
@prev[args->cpu_id] = nsecs;
}'
cpufreq Cooling Device
cpufreq는 thermal 프레임워크와 연동하여 cooling device로 동작합니다. 온도가 상승하면 thermal zone이 cooling device에 주파수 제한을 요청합니다.
Cooling 아키텍처
/* drivers/thermal/cpufreq_cooling.c */
struct cpufreq_cooling_device {
struct thermal_cooling_device *cdev;
struct cpufreq_policy *policy;
unsigned int max_level; /* 최대 cooling 단계 */
struct freq_table *freq_table; /* 주파수-state 매핑 */
unsigned int clipped_freq; /* 현재 제한 주파수 */
};
/* CPUFREQ_IS_COOLING_DEV 플래그 사용 시 자동 등록 */
/* 또는 수동 등록: */
struct thermal_cooling_device *cpufreq_cooling_register(
struct cpufreq_policy *policy);
# cooling device 확인
ls /sys/class/thermal/cooling_device*/
cat /sys/class/thermal/cooling_device0/type
# cpufreq-0
# 현재 cooling 상태
cat /sys/class/thermal/cooling_device0/cur_state
# 0 (제한 없음)
# 최대 cooling 상태
cat /sys/class/thermal/cooling_device0/max_state
# 5
# 수동 cooling 테스트 (온도 기반이 아닌 강제 제한)
echo 3 > /sys/class/thermal/cooling_device0/cur_state
# → 3단계 주파수 제한 적용
하이브리드 아키텍처 (big.LITTLE / Intel Hybrid)
비대칭 멀티프로세싱(AMP) 환경에서 cpufreq는 코어 유형별로 독립적인 주파수 도메인을 관리합니다.
하이브리드 토폴로지와 cpufreq
| 항목 | ARM big.LITTLE | Intel Hybrid (Alder Lake+) |
|---|---|---|
| 고성능 코어 | big 코어 (Cortex-A78 등) | P-코어 (Golden Cove 등) |
| 효율 코어 | LITTLE 코어 (Cortex-A55 등) | E-코어 (Gracemont 등) |
| 주파수 도메인 | 코어 클러스터별 독립 | P-코어/E-코어 그룹별 독립 |
| cpufreq 정책 | 클러스터당 1개 정책 | 코어 그룹당 1개 정책 |
| EAS 활용 | 에너지 모델 기반 배치 | 에너지 모델 + Thread Director |
| 드라이버 | cpufreq-dt, scmi | intel_pstate (HWP) |
| 주파수 범위 | big: 1.0-2.8GHz, LITTLE: 0.5-1.8GHz | P: 0.8-5.2GHz, E: 0.8-3.9GHz |
# Intel Hybrid 코어 유형 확인
lscpu --extended
# CPU NODE SOCKET CORE L1d L1i L2 L3 ONLINE TYPE
# 0 0 0 0 0 0 0 0 yes Core
# 8 0 0 8 8 8 4 0 yes Atom
# 코어별 cpufreq 정책 확인
for p in /sys/devices/system/cpu/cpufreq/policy*; do
echo "$(basename $p): CPUs=$(cat $p/affected_cpus) max=$(cat $p/cpuinfo_max_freq)kHz"
done
# policy0: CPUs=0 1 2 3 4 5 6 7 max=5200000kHz (P-코어)
# policy8: CPUs=8 9 10 11 max=3900000kHz (E-코어)
# ARM big.LITTLE: 클러스터별 정책
# policy0: CPUs=0 1 2 3 max=1800000kHz (LITTLE)
# policy4: CPUs=4 5 6 7 max=2800000kHz (big)
CONFIG_X86_HFI(Hardware Feedback Interface)로 Thread Director 정보를 활용합니다.
가상화 환경에서의 cpufreq
가상 머신(VM) 환경에서 cpufreq의 동작과 제한 사항을 설명합니다.
가상화 유형별 cpufreq 지원
| 환경 | cpufreq 가용성 | 드라이버 | 비고 |
|---|---|---|---|
| 베어메탈 | 완전 지원 | intel_pstate / amd-pstate | 직접 MSR 접근 |
| KVM (패스스루) | MSR 패스스루 시 지원 | intel_pstate (HWP) | 호스트 설정 필요 |
| KVM (기본) | 제한적 | acpi-cpufreq (에뮬레이션) | 가상 P-state |
| Xen (PV) | dom0에서만 | acpi-cpufreq | domU는 제어 불가 |
| VMware | 미지원 (일반적) | 없음 | 호스트가 관리 |
| 컨테이너 (Docker) | 호스트와 공유 | 호스트 드라이버 | cgroup으로 제한 가능 |
| AWS EC2 | 일부 인스턴스 | acpi-cpufreq | 전용 인스턴스에서 가능 |
# KVM 게스트에서 cpufreq 지원 확인
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver
# acpi-cpufreq (가상 P-state) 또는 없음
# 가상화 환경 감지
systemd-detect-virt
# kvm, vmware, xen, none (베어메탈)
# KVM 호스트: 게스트에 HWP 패스스루 (QEMU 옵션)
# -cpu host,+invtsc,+hwp,+hwp_act_window
# 컨테이너: cgroup으로 CPU 주파수 제한 효과
# (직접 주파수 제어 불가, CPU 시간 할당으로 간접 제어)
echo "50000 100000" > /sys/fs/cgroup/my-container/cpu.max
# 50% CPU 시간 제한 → 실질적 성능 제한
performance 거버너를 설정하고, 게스트에서는 cpufreq를 비활성화하는 것이 좋습니다.
cpufreq 내부 동기화
cpufreq 서브시스템은 여러 컨텍스트에서 동시에 접근되므로 정교한 동기화가 필요합니다.
동기화 메커니즘
| 잠금 | 보호 대상 | 유형 | 사용 위치 |
|---|---|---|---|
policy->rwsem | 정책 전체 접근 | 읽기/쓰기 세마포어 | sysfs 접근, 거버너 전환 |
cpufreq_driver_lock | 드라이버 등록/해제 | 읽기/쓰기 잠금 | cpufreq_register_driver |
sg_policy->update_lock | schedutil 업데이트 | raw_spinlock | sugov_update_shared |
cpufreq_transition_lock | 주파수 전환 직렬화 | mutex | freq_transition_begin/end |
policy->transition_lock | 전환 중 상태 보호 | spinlock | PRE/POST 통지 |
/* sysfs에서 거버너 변경 시 동기화 */
static ssize_t store_scaling_governor(struct cpufreq_policy *policy,
const char *buf, size_t count)
{
int ret;
char str_governor[CPUFREQ_NAME_LEN];
sscanf(buf, "%15s", str_governor);
/* 정책 쓰기 잠금 획득 */
down_write(&policy->rwsem);
/* 기존 거버너 정지 → 새 거버너 시작 */
ret = cpufreq_set_policy(policy, str_governor);
up_write(&policy->rwsem);
return ret ? ret : count;
}
/* fast_switch 경로: IRQ 컨텍스트에서 잠금 최소화 */
static void sugov_fast_switch(struct sugov_policy *sg_policy,
u64 time, unsigned int next_freq)
{
/* rwsem 미획득 - fast_switch는 잠금 없이 호출됨 */
/* 드라이버의 fast_switch 콜백이 MSR에 직접 쓰기 */
cpufreq_driver_fast_switch(sg_policy->policy, next_freq);
}
policy->rwsem을 획득하면 교착 상태가 발생합니다. notifier는 이미 rwsem을 보유한 상태에서 호출되기 때문입니다. notifier 콜백에서는 정책 변경이나 sysfs 접근을 시도하지 마세요. 비동기 작업이 필요하면 work queue를 사용하세요.
주파수 테이블 관리
cpufreq 드라이버는 지원하는 주파수 목록을 cpufreq_frequency_table 형태로 관리합니다.
주파수 테이블 구조
/* include/linux/cpufreq.h */
struct cpufreq_frequency_table {
unsigned int flags; /* CPUFREQ_BOOST_FREQ 등 */
unsigned int driver_data; /* 드라이버 전용 데이터 (인덱스 등) */
unsigned int frequency; /* kHz 단위 주파수 */
};
/* 특수 주파수 값 */
#define CPUFREQ_ENTRY_INVALID ~0u /* 무효 엔트리 (건너뛰기) */
#define CPUFREQ_TABLE_END ~1u /* 테이블 종료 표시 */
/* 테이블 순회 매크로 */
#define cpufreq_for_each_entry(pos, table) \
for (pos = table; \
pos->frequency != CPUFREQ_TABLE_END; \
pos++)
#define cpufreq_for_each_valid_entry(pos, table) \
cpufreq_for_each_entry(pos, table) \
if (pos->frequency == CPUFREQ_ENTRY_INVALID) \
continue; \
else
주파수 테이블 정의 예제
/* 정적 주파수 테이블 정의 */
static struct cpufreq_frequency_table my_freq_table[] = {
{ .driver_data = 0, .frequency = 800000 }, /* 800 MHz */
{ .driver_data = 1, .frequency = 1200000 }, /* 1.2 GHz */
{ .driver_data = 2, .frequency = 1600000 }, /* 1.6 GHz */
{ .driver_data = 3, .frequency = 2000000 }, /* 2.0 GHz */
{ .driver_data = 4, .frequency = 2400000 }, /* 2.4 GHz */
{ .driver_data = 5, .frequency = 3000000 }, /* 3.0 GHz (기본 최대) */
{ .driver_data = 6, .frequency = 3600000,
.flags = CPUFREQ_BOOST_FREQ }, /* 3.6 GHz (부스트) */
{ .frequency = CPUFREQ_TABLE_END }, /* 종료 */
};
/* init에서 테이블 등록 */
static int my_cpufreq_init(struct cpufreq_policy *policy)
{
/* 주파수 테이블 설정 */
policy->freq_table = my_freq_table;
/* 테이블 유효성 검증 및 정렬 */
return cpufreq_table_validate_and_sort(policy);
}
주파수 테이블 헬퍼 함수
| 함수 | 역할 |
|---|---|
cpufreq_table_validate_and_sort() | 테이블 유효성 검증 및 오름차순 정렬 |
cpufreq_generic_frequency_table_verify() | 정책 제한에 대한 테이블 검증 |
cpufreq_frequency_table_target() | 관계(L/H/C)에 따라 목표 인덱스 찾기 |
cpufreq_table_find_index_l() | 목표 이하 가장 가까운 주파수 인덱스 |
cpufreq_table_find_index_h() | 목표 이상 가장 가까운 주파수 인덱스 |
cpufreq_table_find_index_c() | 목표에 가장 가까운 주파수 인덱스 |
실전 디버깅 시나리오
운영 환경에서 자주 마주치는 cpufreq 관련 문제와 체계적인 디버깅 절차를 다룹니다.
시나리오 1: 주파수가 올라가지 않음
# 체계적 진단 절차
# 1단계: 현재 상태 수집
echo "=== 드라이버 ==="
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver
echo "=== 거버너 ==="
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
echo "=== 주파수 범위 ==="
echo "min: $(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq)"
echo "max: $(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq)"
echo "cur: $(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq)"
echo "hw_min: $(cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq)"
echo "hw_max: $(cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq)"
# 2단계: 제한 원인 확인
echo "=== Thermal 제한 ==="
cat /sys/class/thermal/thermal_zone*/temp
echo "=== Cooling 상태 ==="
for cd in /sys/class/thermal/cooling_device*/; do
type=$(cat "${cd}type")
state=$(cat "${cd}cur_state")
echo " $type: state=$state"
done
# 3단계: RAPL 전력 제한 확인
echo "=== RAPL 제한 ==="
cat /sys/class/powercap/intel-rapl/intel-rapl:0/constraint_0_power_limit_uw 2>/dev/null
cat /sys/class/powercap/intel-rapl/intel-rapl:0/constraint_0_max_power_uw 2>/dev/null
# 4단계: 커널 로그 확인
dmesg | grep -iE 'cpufreq|pstate|throttl|thermal|power.limit' | tail -20
시나리오 2: 비정상적 주파수 진동 (frequency oscillation)
# 주파수가 빠르게 오르내리는 문제 진단
# 1. 주파수 전환 횟수 확인 (높으면 진동 의심)
cat /sys/devices/system/cpu/cpufreq/policy0/stats/total_trans
# 10초 간격으로 2회 측정하여 차이 확인
t1=$(cat /sys/devices/system/cpu/cpufreq/policy0/stats/total_trans)
sleep 10
t2=$(cat /sys/devices/system/cpu/cpufreq/policy0/stats/total_trans)
echo "10초 동안 전환: $((t2 - t1))회"
# 100회 이상이면 과도한 진동
# 2. ftrace로 주파수 변경 패턴 분석
echo 1 > /sys/kernel/debug/tracing/events/power/cpu_frequency/enable
sleep 5
echo 0 > /sys/kernel/debug/tracing/events/power/cpu_frequency/enable
cat /sys/kernel/debug/tracing/trace | grep cpu_frequency | tail -30
# 3. 해결: schedutil rate_limit 조정
# rate_limit_us가 너무 낮으면 과도한 전환 발생
cat /sys/devices/system/cpu/cpufreq/policy0/schedutil/rate_limit_us
# 기본값이 낮으면 상향 조정
echo 2000 > /sys/devices/system/cpu/cpufreq/policy0/schedutil/rate_limit_us
# 2ms 미만 간격의 변경 억제
시나리오 3: schedutil이 최대 주파수를 사용하지 않음
# schedutil의 주파수 계산이 최대에 미달하는 경우
# 1. PELT 활용률 확인
cat /proc/schedstat
# 또는 perf sched record로 분석
# 2. thermal pressure 확인
cat /sys/devices/system/cpu/cpu0/topology/thermal_pressure
# 0이 아니면 열 제한으로 capacity 감소
# 3. UCLAMP 제한 확인
cat /proc/1/sched | grep uclamp
# uclamp.max가 1024 미만이면 제한 있음
# 4. 주파수 불변성 스케일 확인
# arch_scale_freq_capacity가 1024 미만이면 보정 적용 중
# 5. 해결: 확실한 최대 주파수가 필요하면
cpupower frequency-set -g performance
# 또는 UCLAMP_MIN=1024 설정
디버깅 도구 요약
| 도구 | 용도 | 명령어 |
|---|---|---|
cpupower | 주파수 정보/설정 | cpupower frequency-info -m |
turbostat | 상세 주파수/전력/온도 | turbostat --interval 1 |
perf stat | IPC, 에너지 측정 | perf stat -e power/energy-pkg/ |
ftrace | 주파수 변경 이벤트 추적 | events/power/cpu_frequency |
bpftrace | 고급 실시간 분석 | tracepoint:power:cpu_frequency |
dmesg | 커널 로그 (throttle) | dmesg | grep -i throttle |
sysfs | 직접 상태 확인/변경 | /sys/devices/system/cpu/ |
stress-ng | 부하 생성 테스트 | stress-ng --cpu 4 -t 30 |
소스 코드 위치 가이드
CPUFreq 관련 커널 소스 코드의 주요 파일과 디렉터리를 안내합니다.
주요 소스 파일
| 경로 | 설명 |
|---|---|
include/linux/cpufreq.h | cpufreq_policy, cpufreq_driver, cpufreq_governor 정의 |
drivers/cpufreq/cpufreq.c | CPUFreq 코어: 정책 관리, sysfs, notifier |
drivers/cpufreq/cpufreq_stats.c | 주파수 전환 통계 |
drivers/cpufreq/freq_table.c | 주파수 테이블 헬퍼 |
kernel/sched/cpufreq_schedutil.c | schedutil 거버너 |
drivers/cpufreq/cpufreq_ondemand.c | ondemand 거버너 |
drivers/cpufreq/cpufreq_conservative.c | conservative 거버너 |
drivers/cpufreq/cpufreq_performance.c | performance 거버너 |
drivers/cpufreq/cpufreq_powersave.c | powersave 거버너 |
drivers/cpufreq/cpufreq_userspace.c | userspace 거버너 |
drivers/cpufreq/intel_pstate.c | Intel P-state 드라이버 (HWP 포함) |
drivers/cpufreq/amd-pstate.c | AMD P-state 드라이버 (CPPC) |
drivers/cpufreq/amd-pstate-ut.c | AMD P-state 유닛 테스트 |
drivers/cpufreq/acpi-cpufreq.c | ACPI 기반 cpufreq 드라이버 |
drivers/cpufreq/cppc_cpufreq.c | CPPC cpufreq 드라이버 (ARM) |
drivers/cpufreq/cpufreq-dt.c | Device Tree 기반 cpufreq (ARM) |
drivers/thermal/cpufreq_cooling.c | cpufreq cooling device |
include/linux/energy_model.h | 에너지 모델 인터페이스 |
kernel/power/energy_model.c | 에너지 모델 구현 |
include/linux/pm_opp.h | OPP 프레임워크 인터페이스 |
drivers/opp/core.c | OPP 프레임워크 핵심 구현 |
drivers/opp/of.c | OPP Device Tree 파싱 |
# 커널 소스에서 cpufreq 관련 파일 탐색
find drivers/cpufreq/ -name "*.c" | wc -l
# 약 80개 이상의 드라이버/거버너 소스 파일
# cpufreq 코어 코드 규모
wc -l drivers/cpufreq/cpufreq.c
# 약 2800줄
# schedutil 거버너 코드 규모
wc -l kernel/sched/cpufreq_schedutil.c
# 약 850줄
# intel_pstate 드라이버 코드 규모
wc -l drivers/cpufreq/intel_pstate.c
# 약 3300줄
# amd-pstate 드라이버 코드 규모
wc -l drivers/cpufreq/amd-pstate.c
# 약 1800줄
# 구조체 정의 빠르게 찾기
grep -n 'struct cpufreq_policy {' include/linux/cpufreq.h
grep -n 'struct cpufreq_driver {' include/linux/cpufreq.h
grep -n 'struct cpufreq_governor {' include/linux/cpufreq.h
include/linux/cpufreq.h에서 핵심 구조체 파악 →
(2) drivers/cpufreq/cpufreq.c의 cpufreq_online()에서 초기화 흐름 추적 →
(3) kernel/sched/cpufreq_schedutil.c의 sugov_update_single()에서 주파수 결정 로직 확인 →
(4) 관심 드라이버(intel_pstate.c 또는 amd-pstate.c)의 init()와 fast_switch() 콜백 분석.
cpufreq 이벤트 흐름 종합
cpufreq 서브시스템의 주요 이벤트 흐름을 종합적으로 정리합니다.
전체 생명주기 흐름
주요 API 요약
| API | 유형 | 호출자 | 역할 |
|---|---|---|---|
cpufreq_register_driver() | 드라이버 | 플랫폼 드라이버 | cpufreq 드라이버 등록 |
cpufreq_unregister_driver() | 드라이버 | 모듈 해제 | cpufreq 드라이버 해제 |
cpufreq_register_governor() | 거버너 | 거버너 모듈 | 새 거버너 등록 |
cpufreq_register_notifier() | 알림 | 관심 서브시스템 | 주파수 변경 알림 등록 |
cpufreq_driver_fast_switch() | 코어 | schedutil | 인터럽트 컨텍스트 주파수 변경 |
cpufreq_driver_target() | 코어 | 거버너 | 프로세스 컨텍스트 주파수 변경 |
cpufreq_update_policy() | 코어 | cooling/sysfs | 정책 제한값 갱신 |
cpufreq_cpu_get() | 코어 | 외부 모듈 | CPU의 정책 참조 획득 |
cpufreq_cpu_put() | 코어 | 외부 모듈 | CPU의 정책 참조 해제 |
cpufreq_verify_within_limits() | 헬퍼 | 드라이버 verify | 주파수 범위 유효성 검증 |
cpufreq_generic_suspend() | 헬퍼 | 드라이버 | 범용 suspend 처리 |
cpufreq_generic_attr | 속성 | 드라이버 | 기본 sysfs 속성 배열 |
이벤트 추적 가이드
# 모든 cpufreq 관련 트레이스포인트 활성화
echo 1 > /sys/kernel/debug/tracing/events/power/cpu_frequency/enable
echo 1 > /sys/kernel/debug/tracing/events/power/cpu_frequency_limits/enable
# schedutil 내부 추적 (디버그 빌드 필요)
echo 1 > /sys/kernel/debug/tracing/events/power/pstate_sample/enable
# 종합 추적 시작
echo > /sys/kernel/debug/tracing/trace
echo 1 > /sys/kernel/debug/tracing/tracing_on
# 워크로드 실행...
echo 0 > /sys/kernel/debug/tracing/tracing_on
# 결과 분석
cat /sys/kernel/debug/tracing/trace | \
grep cpu_frequency | \
awk '{print $4, $5}' | \
sort | uniq -c | sort -rn
# 가장 빈번한 주파수 전환 패턴 확인
turbostat으로 기준선 수집 →
(2) cpupower frequency-set으로 거버너/제한 변경 →
(3) 동일 워크로드 재실행 →
(4) perf stat으로 IPC/에너지 비교 →
(5) EDP(Energy-Delay Product)가 가장 낮은 설정 채택.
반복적인 A/B 테스트가 최적 설정을 찾는 가장 확실한 방법입니다.
빠른 참조 카드
| 작업 | 명령어 |
|---|---|
| 현재 주파수 확인 | cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq |
| 거버너 변경 | cpupower frequency-set -g performance |
| 주파수 고정 | cpupower frequency-set -d 2400MHz -u 2400MHz |
| Turbo 비활성화 (Intel) | echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo |
| Boost 비활성화 (AMD) | echo 0 > /sys/devices/system/cpu/cpufreq/boost |
| EPP 설정 | echo performance > .../energy_performance_preference |
| 드라이버 확인 | cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver |
| 전환 통계 | cat .../cpufreq/policy0/stats/time_in_state |
| 실시간 모니터링 | turbostat --interval 1 |
| 열 스로틀링 확인 | dmesg | grep -i throttle |
| AMD 모드 전환 | echo guided > /sys/devices/system/cpu/amd_pstate/status |
| Intel 모드 확인 | cat /sys/devices/system/cpu/intel_pstate/status |
최신 동향과 발전 방향
cpufreq 서브시스템의 최근 커널 버전별 변경 사항과 향후 발전 방향을 정리합니다.
커널 버전별 주요 변경
| 커널 버전 | 변경 사항 | 영향 |
|---|---|---|
| 4.7 (2016) | schedutil 거버너 도입 | 스케줄러-cpufreq 직접 연동의 시작 |
| 5.1 (2019) | amd-pstate 초기 프로토타입 | AMD CPPC 지원 기반 마련 |
| 5.17 (2022) | amd-pstate passive 모드 도입 | AMD CPU에서 세밀한 주파수 제어 |
| 6.1 (2022) | amd-pstate guided 모드 | 범위 기반 자율 제어 추가 |
| 6.3 (2023) | amd-pstate active 모드 (EPP) | AMD EPP 기반 완전 자율 제어 |
| 6.4 (2023) | amd-pstate Preferred Core | 코어별 성능 순위 스케줄러 반영 |
| 6.5 (2023) | amd-pstate 런타임 모드 전환 | 재부팅 없이 active/guided/passive 전환 |
| 6.7 (2024) | Intel HFI(Hardware Feedback Interface) 개선 | Thread Director 정보 활용 강화 |
| 6.8 (2024) | 에너지 모델 EM2 개선 | 인효율 테이블(inefficient states) 지원 |
| 6.9 (2024) | cpufreq_driver 콜백 현대화 | 레거시 target 콜백 정리 |
향후 발전 방향
- 하드웨어 자율 제어 확대: Intel HWP와 AMD EPP active 모드에서 볼 수 있듯이, 주파수 결정 권한이 점점 하드웨어로 이동하고 있습니다. 소프트웨어는 정책 힌트(EPP, min/max)만 제공하는 방향으로 발전합니다.
- 하이브리드 아키텍처 최적화: Intel Hybrid(P+E 코어), ARM DynamIQ(big.LITTLE+) 등 비대칭 멀티프로세싱 환경에서 코어 유형별 최적 주파수 관리가 더욱 중요해지고 있습니다.
- 에너지 모델 정교화: EAS의 에너지 모델이 런타임 측정 기반으로 동적 업데이트되는 방향으로 진화하여, 더 정확한 에너지 효율 예측이 가능해질 전망입니다.
- CXL/이기종 메모리 고려: CXL 메모리 접근 패턴에 따른 CPU 주파수 최적화가 새로운 연구 주제로 부상하고 있습니다.
- AI 워크로드 최적화: NPU/GPU와 CPU의 협력 실행에서 CPU 주파수 조정이 전체 시스템 효율에 미치는 영향에 대한 연구가 진행되고 있습니다.
운영 모범 사례
- 기본 설정 유지: 대부분의 환경에서 schedutil + HWP/CPPC 기본 설정이 최적입니다. 특별한 이유 없이 변경하지 마세요.
- 벤치마크 시 Turbo 비활성화: 재현 가능한 벤치마크를 위해 Turbo를 비활성화하고, performance 거버너를 사용하세요.
- 프로덕션 변경 전 테스트: 거버너나 EPP 변경은 반드시 스테이징 환경에서 충분히 테스트한 후 적용하세요.
- 모니터링 필수:
turbostat이나 Prometheus + node_exporter를 통해 주파수, 온도, 전력을 지속적으로 모니터링하세요. - 열 관리 우선: 주파수를 높이기 전에 냉각 시스템이 충분한지 확인하세요. 열 스로틀링은 성능 저하의 가장 흔한 원인입니다.
- 커널 업데이트 시 확인: cpufreq 드라이버와 거버너는 커널 버전마다 변경될 수 있으므로, 업데이트 후 설정이 유지되는지 확인하세요.
- systemd-tmpfiles 또는 udev 규칙 활용: 부팅 시 자동으로 cpufreq 설정을 적용하려면 systemd-tmpfiles나 udev 규칙을 사용하세요.
# systemd-tmpfiles로 부팅 시 자동 설정 (권장)
# /etc/tmpfiles.d/cpufreq.conf
w /sys/devices/system/cpu/cpufreq/policy0/scaling_governor - - - - schedutil
w /sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq - - - - 800000
w /sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq - - - - 3600000
# udev 규칙으로 cpufreq 드라이버 로드 후 자동 설정
# /etc/udev/rules.d/99-cpufreq.rules
# SUBSYSTEM=="cpu", ACTION=="add", \
# RUN+="/usr/bin/cpupower frequency-set -g schedutil"
# tuned 프로필 활용 (Red Hat 계열)
tuned-adm profile throughput-performance
# 또는
tuned-adm profile powersave
# power-profiles-daemon 활용 (데스크톱)
powerprofilesctl set performance
# 또는
powerprofilesctl set balanced