CPUIdle

Linux 커널 CPU 유휴 상태 관리(CPUIdle) 서브시스템을 심층 분석합니다. C-state별 특성과 전이 비용, menu/ladder/teo governor의 예측 로직, tick/nohz 및 timer 이벤트 상호작용, exit latency·target residency 기반 선택 기준, cpufreq·scheduler·interrupt 부하와의 연계, 전력 절감과 tail latency 사이의 트레이드오프를 고려한 실전 튜닝까지 종합적으로 다룹니다.

전제 조건: 커널 아키텍처스케줄러 문서를 먼저 읽으세요. CPU가 실행할 태스크가 없을 때 스케줄러가 idle 태스크를 선택하고, idle 태스크가 CPUIdle 서브시스템을 호출하는 흐름을 이해하면 본 문서를 더 빠르게 소화할 수 있습니다. 전원 관리 개요와 CPUFreq(P-states)도 함께 참고하시면 좋습니다.
일상 비유: CPUIdle은 직원의 대기 전략과 비슷합니다. 전화(인터럽트)가 올 때까지 자리에 앉아 대기할 수도 있고(C1, 빠른 응답), 잠시 눈을 감고 쉴 수도 있고(C3, 중간 응답), 아예 퇴근해서 깊이 잠들 수도 있습니다(C6+, 느린 복귀). Governor는 "다음 전화가 얼마나 빨리 올까"를 예측하여 최적의 대기 방식을 결정하는 관리자입니다. 깊이 잠들수록 에너지를 절약하지만, 급한 전화에 늦게 응답하는 트레이드오프가 있습니다.

핵심 요약

  • C-state -- CPU 유휴 전력 상태. C0(활성)부터 C10(최심층 수면)까지 깊어질수록 전력 절감이 크지만 복귀 지연도 증가합니다.
  • cpuidle_driver -- 하드웨어별 C-state 테이블과 진입/복귀 함수를 등록하는 드라이버 인터페이스입니다.
  • cpuidle_governor -- 다음 유휴 시간을 예측하여 최적 C-state를 선택하는 정책 엔진(menu, teo, ladder, haltpoll)입니다.
  • do_idle() -- 스케줄러 idle 루프의 핵심 함수. governor 선택 → driver 진입 → 깨어남 → 통계 갱신 순서로 동작합니다.
  • PM QoS -- 사용자/드라이버가 최대 허용 복귀 지연 시간을 설정하여 깊은 C-state 진입을 제한하는 메커니즘입니다.

단계별 이해

  1. 유휴 진입 경로 이해
    스케줄러가 실행 큐가 비었음을 감지 → idle 태스크 선택 → do_idle() 호출 → CPUIdle 서브시스템으로 진입합니다.
  2. Governor의 C-state 선택
    Governor가 다음 타이머 이벤트, 최근 유휴 패턴, PM QoS 제약을 종합하여 최적 C-state 인덱스를 반환합니다.
  3. Driver의 C-state 진입
    선택된 C-state의 enter() 콜백이 호출되어 MWAIT, HLT 또는 ACPI 방식으로 CPU를 저전력 상태로 전환합니다.
  4. 깨어남과 통계 갱신
    인터럽트나 타이머로 깨어나면 실제 수면 시간을 측정하고, governor에 피드백하여 다음 예측 정확도를 높입니다.

개요

CPUIdle은 CPU가 유휴 상태일 때 저전력 C-states로 전환하여 전력을 절약하는 리눅스 커널 서브시스템입니다. 현대 프로세서는 대부분의 시간을 유휴 상태로 보내기 때문에(데스크톱 시스템 기준 70~95%), CPUIdle의 C-state 선택 품질이 전체 시스템 전력 소비와 응답 지연에 직접적인 영향을 미칩니다.

CPUIdle 서브시스템은 drivers/cpuidle/ 디렉토리에 위치하며, 크게 세 가지 컴포넌트로 구성됩니다:

컴포넌트역할소스 파일
Core드라이버/거버너 등록, sysfs 인터페이스, 통계 수집cpuidle.c, sysfs.c
Driver하드웨어별 C-state 테이블, 진입/복귀 함수driver.c, intel_idle, acpi_idle
GovernorC-state 선택 정책 (예측 알고리즘)governor.c, governors/menu.c, governors/teo.c

C-states vs P-states

CPU 전력 관리에는 두 가지 직교하는 차원이 있습니다. C-states는 유휴 시 전력을 줄이고, P-states는 실행 중 주파수/전압을 조절합니다:

항목C-states (CPUIdle)P-states (CPUFreq)
목적유휴 시 전력 절약실행 중 주파수 조정
상태CPU 유휴 (대기)CPU 활성 (실행)
전력 절감매우 큼 (90-99%)중간 (50-80%)
복귀 지연수 us ~ ms거의 없음 (0 us)
트리거스케줄러 (유휴 감지)부하 변화
제어CPUIdle GovernorCPUFreq Governor
ACPI 표준ACPI Cx statesACPI Px states
커널 소스drivers/cpuidle/drivers/cpufreq/

소스 디렉토리 구조

경로설명
drivers/cpuidle/cpuidle.c코어: cpuidle_idle_call(), 등록/해제, 통계
drivers/cpuidle/driver.c드라이버 등록/해제, per-CPU 드라이버 관리
drivers/cpuidle/governor.c거버너 등록/전환, 현재 거버너 관리
drivers/cpuidle/sysfs.csysfs 인터페이스 (/sys/devices/system/cpu/cpuidle/)
drivers/cpuidle/governors/menu.cMenu governor 구현
drivers/cpuidle/governors/teo.cTEO governor 구현
drivers/cpuidle/governors/ladder.cLadder governor 구현
drivers/cpuidle/governors/haltpoll.cHaltpoll governor 구현 (VM 게스트)
drivers/idle/intel_idle.cIntel 전용 네이티브 C-state 드라이버
drivers/acpi/processor_idle.cACPI 기반 범용 C-state 드라이버
include/linux/cpuidle.h핵심 구조체 및 API 선언
Scheduler CPUIdle Core Governor Driver Hardware do_idle() tick_nohz_idle_enter() cpuidle_idle_call() menu_select() teo_select() ladder_select() PM QoS latency_req + next_timer_event 기반 의사결정 intel_idle (MWAIT) acpi_idle (IOPORT) haltpoll (HLT) CPU Power Domain L1/L2/LLC Cache Package Power

C-states (전력 상태)

C-states는 ACPI 표준에서 정의한 CPU 유휴 전력 상태입니다. 숫자가 클수록 더 많은 하드웨어 컴포넌트를 꺼서 전력을 절약하지만, 다시 활성 상태(C0)로 복귀하는 데 더 오래 걸립니다.

C-state 종류

C-state이름전력복귀 지연동작
C0Active100%0 us정상 실행
C1Auto Halt10-20%0-1 usCPU 클럭 정지 (HLT)
C1EEnhanced Halt5-10%1-10 us클럭 + 전압 낮춤
C2Stop Grant3-5%10-20 usL2 캐시 유지
C3Sleep1-2%20-100 usL2 캐시 플러시
C6Deep Sleep0.1-0.5%100-200 us코어 전원 차단
C7Deeper Sleep0.05%200-300 usLLC 캐시 유지
C8Deepest Sleep0.01%300-500 usLLC 캐시 플러시
C10Package0.001%500+ us패키지 전체 차단

C-state 동작 원리

  • C1: HLT 명령어 실행 → 클럭 게이팅. 코어 로직은 유지되며 인터럽트 즉시 응답
  • C1E: HLT + 코어 전압 강하. C1보다 절전 효과가 크지만 전압 복원 시간 필요
  • C3: L2 캐시 flush → SRAM 전원 차단. 캐시 미스 비용이 복귀 오버헤드에 추가
  • C6: 코어 레지스터를 전용 SRAM에 저장 → 코어 전원 차단 → 복귀 시 레지스터 복원
  • C7/C8: LLC(Last Level Cache) 관리. C7은 LLC 유지, C8은 LLC까지 플러시
  • C10: 패키지 전체 전원 차단 (멀티 소켓 시 패키지 단위). 모든 코어가 깊은 C-state에 진입해야 가능

깊은 C-state일수록 더 많은 하드웨어를 끄므로 절전 효과가 크지만, 복귀 시 하드웨어를 다시 켜야 하므로 지연이 증가합니다.

C-state 하드웨어 진입 메커니즘

x86 아키텍처에서 C-state 진입 방식은 크게 세 가지입니다:

방식명령어장점단점사용 드라이버
MWAITMONITOR/MWAIT힌트 비트로 정밀한 C-state 지정 가능Intel 전용(AMD는 제한적)intel_idle
HLTHLT범용, 단순C1만 가능폴백 드라이버
ACPI I/O Portinb(P_LVLx)BIOS 정의, 범용I/O 포트 접근 오버헤드acpi_idle
/* MWAIT를 이용한 C-state 진입 (intel_idle 드라이버) */
static __cpuidle int intel_idle(struct cpuidle_device *dev,
                                  struct cpuidle_driver *drv,
                                  int index)
{
    struct cpuidle_state *state = &drv->states[index];
    unsigned long eax = flg2MWAIT(state->flags);
    unsigned int cstate;

    cstate = (((eax) >> 4) & 0xf) + 1;  /* MWAIT 힌트에서 C-state 추출 */

    /*
     * MONITOR: 감시할 메모리 주소 설정
     * MWAIT: 해당 주소에 쓰기가 발생하거나
     *        인터럽트가 올 때까지 대기
     */
    __monitor((void *)&current_thread_info()->flags, 0, 0);
    if (!need_resched())
        __mwait(eax, 1);  /* eax: C-state 힌트, ecx: 확장 플래그 */

    return index;
}
/* ACPI I/O 포트를 이용한 C-state 진입 (acpi_idle 드라이버) */
static int acpi_idle_enter_bm(struct cpuidle_device *dev,
                               struct cpuidle_driver *drv,
                               int index)
{
    struct acpi_processor_cx *cx = per_cpu(acpi_cstate[index], dev->cpu);

    /* P_LVL2/P_LVL3 I/O 포트 읽기로 C-state 진입 */
    inb(cx->address);  /* 이 I/O 읽기가 CPU를 유휴 상태로 전환 */

    /* C3의 경우 Bus Master 활동 확인 필요 */
    if (cx->type == ACPI_STATE_C3) {
        u32 bm_sts = acpi_read_bit_register(ACPI_BITREG_BUS_MASTER_STATUS);
        if (bm_sts)
            acpi_write_bit_register(ACPI_BITREG_BUS_MASTER_STATUS, 1);
    }
    return index;
}
C-state 전력-지연 계층 전력 소비 감소 복귀 지연 증가 --> C0 Active (0 us) C1 HLT (1 us) C1E Enhanced (10 us) C3 Sleep (100 us) C6 Deep (200 us) C8 Deepest (500 us) C10 Package (1ms+) 코어 레벨 C-states 패키지 레벨 C-states

C-state 전이 비용

C-state 전이에는 진입 비용(entry cost)과 복귀 비용(exit cost)이 있습니다. 실제 에너지 절약은 유휴 시간이 break-even point를 넘어야만 발생합니다:

에너지 절약 = (C0_power - Cn_power) * idle_duration - entry_cost - exit_cost

Break-even 조건: idle_duration > (entry_cost + exit_cost) / (C0_power - Cn_power)

예시: C6 state (Skylake)
  진입 비용:      ~50 uJ
  복귀 비용:      ~100 uJ
  C0 전력:        4.0 W
  C6 전력:        0.02 W
  Break-even:     (50 + 100) / (4.0 - 0.02) = ~37.7 us
  → 37.7 us 이상 유휴 시에만 C6 진입이 이득

C-state 전이의 숨겨진 비용

하드웨어 복귀 지연(exit_latency)만이 비용의 전부가 아닙니다. 깊은 C-state에서 복귀 시 추가 비용이 발생합니다:

  • 캐시 콜드 스타트: C3+에서 L2 캐시 플러시 후 캐시 미스 급증 (수십~수백 us 추가)
  • TLB 무효화: C6에서 코어 전원 차단 시 TLB 전체 플러시
  • 분기 예측기 리셋: 코어 전원 차단 시 분기 예측 히스토리 소실
  • 주파수 램프업: 깊은 C-state 복귀 시 P-state가 최저로 시작 → 부스트까지 수십 us

이러한 "소프트" 비용까지 합하면 실제 target_residency는 하드웨어 exit_latency의 2~5배에 달합니다.

아키텍처

CPUIdle은 Driver-Governor 분리 아키텍처를 채택합니다. Driver가 "어떤 C-state가 존재하며 어떻게 진입하는가"를 정의하고, Governor가 "지금 어떤 C-state에 진입할 것인가"를 결정합니다.

cpuidle_state 구조체

/* include/linux/cpuidle.h */
struct cpuidle_state {
    char             name[CPUIDLE_NAME_LEN];    /* "C1", "C6" 등 */
    char             desc[CPUIDLE_DESC_LEN];    /* 설명 문자열 */

    s64              exit_latency_ns;           /* 복귀 지연 (ns) */
    s64              target_residency_ns;       /* Break-even 시간 (ns) */
    unsigned int     exit_latency;              /* 복귀 지연 (us, 호환) */
    int              power_usage;               /* 전력 소비 (상대값, mW) */
    unsigned int     target_residency;          /* Break-even 시간 (us) */

    unsigned int     flags;
    /*
     * 주요 플래그:
     *   CPUIDLE_FLAG_NONE           - 특별한 속성 없음
     *   CPUIDLE_FLAG_POLLING        - Polling 상태 (실제 C-state 아님)
     *   CPUIDLE_FLAG_COUPLED        - 다중 CPU 결합 idle
     *   CPUIDLE_FLAG_TIMER_STOP     - 로컬 타이머 정지됨
     *   CPUIDLE_FLAG_UNUSABLE       - 이 상태 사용 불가
     *   CPUIDLE_FLAG_OFF            - sysfs에서 비활성화됨
     *   CPUIDLE_FLAG_TLB_FLUSHED    - TLB가 플러시됨
     *   CPUIDLE_FLAG_RCU_IDLE       - RCU idle 상태 진입
     */

    int (*enter)(struct cpuidle_device *dev,
                  struct cpuidle_driver *drv,
                  int index);
    int (*enter_dead)(struct cpuidle_device *dev,
                       int index);
    int (*enter_s2idle)(struct cpuidle_device *dev,
                         struct cpuidle_driver *drv,
                         int index);
};

cpuidle_driver 구조체

/* include/linux/cpuidle.h */
struct cpuidle_driver {
    const char          *name;          /* "intel_idle", "acpi_idle" 등 */
    struct module       *owner;

    /* C-state 배열과 개수 */
    struct cpuidle_state states[CPUIDLE_STATE_MAX]; /* 최대 10개 */
    int                 state_count;

    /* 이 드라이버가 적용되는 CPU 마스크 */
    struct cpumask      *cpumask;

    /* Governor에게 전달할 BM 감지 여부 */
    unsigned int        bctimer:1;
};

cpuidle_device 구조체

/* include/linux/cpuidle.h */
struct cpuidle_device {
    unsigned int        registered:1;
    unsigned int        enabled:1;
    unsigned int        poll_time_limit:1;
    unsigned int        cpu;                /* CPU 번호 */

    ktime_t             next_hrtimer;       /* 다음 hrtimer 이벤트 */
    int                 last_state_idx;     /* 마지막 선택된 C-state */
    u64                 last_residency_ns;  /* 마지막 실제 수면 시간 */

    /* per-state 사용 통계 */
    struct cpuidle_state_usage states_usage[CPUIDLE_STATE_MAX];
};

target_residency (Break-even Time)

Break-even Time 계산

target_residency는 C-state 진입/복귀 비용을 회수하기 위한 최소 수면 시간입니다.

예시: C3 state

  • 진입 비용: 10 us
  • 복귀 비용: 20 us
  • 유휴 전력: 5W, C3 전력: 0.5W (90% 절감)
  • Break-even: (10 + 20) / 0.9 = 33 us

즉, 33us 이상 유휴 상태가 지속되어야 C3가 이득입니다. 이보다 짧은 유휴에서 C3에 진입하면 오히려 에너지를 낭비합니다.

드라이버 등록 흐름

/* intel_idle 드라이버 등록 예시 */
static struct cpuidle_driver intel_idle_driver = {
    .name = "intel_idle",
    .owner = THIS_MODULE,
};

static int __init intel_idle_init(void)
{
    struct cpuidle_driver *drv = &intel_idle_driver;
    int retval;

    /* 1. CPU 모델 확인 및 C-state 테이블 선택 */
    const struct idle_cpu *icpu = intel_idle_find_cpu();
    if (!icpu)
        return -ENODEV;

    /* 2. C-state 테이블을 드라이버에 복사 */
    for (cstate = 0; icpu->state_table[cstate].name; cstate++) {
        drv->states[drv->state_count] = icpu->state_table[cstate];
        drv->state_count++;
    }

    /* 3. 드라이버 등록 */
    retval = cpuidle_register_driver(drv);
    if (retval)
        return retval;

    /* 4. 각 CPU에 대해 디바이스 등록 */
    cpuidle_register_device(&per_cpu(cpuidle_dev, cpu));

    return 0;
}
module_init(intel_idle_init);

do_idle() 루프

do_idle()은 CPU가 유휴 상태일 때 반복 실행되는 핵심 루프입니다. 스케줄러의 idle 태스크(swapper 프로세스, PID 0)에서 호출됩니다.

/* kernel/sched/idle.c */
static void do_idle(void)
{
    int cpu = smp_processor_id();

    while (!need_resched()) {
        rmb();  /* need_resched 읽기 배리어 */

        local_irq_disable();

        if (cpu_is_offline(cpu)) {
            tick_nohz_idle_stop_tick();
            cpuidle_play_dead();  /* enter_dead 콜백 */
        }

        arch_cpu_idle_enter();

        /*
         * 핵심: cpuidle_idle_call()이 governor와 driver를 호출
         * 1. governor->select()로 C-state 선택
         * 2. tick_nohz_idle_stop_tick()으로 nohz 진입
         * 3. driver->states[index].enter()로 C-state 진입
         * 4. 깨어난 후 governor->reflect()로 결과 피드백
         */
        cpuidle_idle_call();

        arch_cpu_idle_exit();
    }

    /* 스케줄 필요: 타이머, IRQ, 새 태스크 등 */
    schedule_idle();
}
do_idle() / cpuidle_idle_call() 실행 흐름 need_resched() == false governor->select(drv, dev) latency_req = PM QoS 확인 tick_nohz_idle_stop_tick() stop_tick? default_idle_call() [HLT] drv->states[idx].enter() 인터럽트/타이머로 깨어남 governor->reflect(dev, idx) need_resched() 재확인 No Yes 루프

cpuidle_idle_call() 상세

/* drivers/cpuidle/cpuidle.c */
static void cpuidle_idle_call(void)
{
    struct cpuidle_device *dev = cpuidle_get_device();
    struct cpuidle_driver *drv = cpuidle_get_cpu_driver(dev);
    int next_state, entered_state;
    bool stop_tick = true;

    /* 1. 드라이버/디바이스 검증 */
    if (!dev || !dev->enabled || !drv || !drv->state_count) {
        default_idle_call();
        return;
    }

    /* 2. 강제 idle=poll 모드 확인 */
    if (cpuidle_not_available(drv, dev)) {
        default_idle_call();
        return;
    }

    /* 3. Governor가 최적 C-state 선택 */
    next_state = cpuidle_select(drv, dev, &stop_tick);

    /* 4. nohz tick 중단 (필요 시) */
    if (stop_tick)
        tick_nohz_idle_stop_tick();

    /* 5. RCU idle 알림 */
    rcu_idle_enter();

    /* 6. C-state 진입 */
    entered_state = cpuidle_enter(drv, dev, next_state);

    /* 7. RCU idle 복귀 */
    rcu_idle_exit();

    /* 8. nohz tick 재시작 */
    tick_nohz_idle_restart_tick();

    /* 9. Governor에 결과 피드백 */
    cpuidle_reflect(dev, entered_state);
}

POLL 상태와 default_idle_call()

CPUIdle 드라이버의 states[0]은 보통 POLL 상태입니다. 이 상태는 HLT도 실행하지 않고 need_resched()를 빠르게 반복 확인합니다. 매우 짧은 유휴 시간(수 us 미만)에서 C-state 진입 비용 없이 즉시 응답할 수 있지만, CPU가 100% 전력을 소비합니다. default_idle_call()은 CPUIdle이 비활성화되었을 때의 폴백으로, 단순히 HLT를 실행합니다.

CPUIdle Governor

Governor는 어떤 C-state로 들어갈지 결정하는 정책 엔진입니다. 리눅스 커널은 4가지 governor를 제공하며, 런타임에 전환할 수 있습니다.

Governor알고리즘복잡도적합한 경우기본 선택 조건
menu예측 기반 (통계)중간일반 시스템 (기본값)NO_HZ_IDLE 활성 시
teoTimer Events Oriented낮음타이머 중심 워크로드NO_HZ_IDLE + 커널 옵션
ladder단계적 승격/강등낮음Tickless 비활성화 시고정 tick 시스템
haltpollPolling + HLT매우 낮음VM 게스트 (KVM)VM 환경 감지 시
# 현재 governor 확인
cat /sys/devices/system/cpu/cpuidle/current_governor
# menu

# 사용 가능한 governor 목록
cat /sys/devices/system/cpu/cpuidle/available_governors
# menu teo ladder haltpoll

# governor 전환 (런타임)
echo teo > /sys/devices/system/cpu/cpuidle/current_governor

Menu governor는 가장 널리 사용되는 기본 governor입니다. 통계 기반 예측으로 다음 유휴 시간을 추정하고, 가장 깊은 적절한 C-state를 선택합니다.

/* drivers/cpuidle/governors/menu.c - menu_select() 핵심 로직 */
static int menu_select(struct cpuidle_driver *drv, struct cpuidle_device *dev,
                        bool *stop_tick)
{
    struct menu_device *data = this_cpu_ptr(&menu_devices);
    s64 latency_req = cpuidle_governor_latency_req(dev->cpu);
    u64 predicted_ns;
    u64 interactivity_req;
    int i, idx;

    /* 1. 다음 타이머 이벤트까지 남은 시간 */
    u64 delta_next = tick_nohz_get_sleep_length(&dev->next_hrtimer);

    /* 2. 이전 유휴 패턴 기반 예측 */
    predicted_ns = get_typical_interval(data, delta_next);

    /* 3. 상호작용성 보정 계수 적용 */
    interactivity_req = predicted_ns / data->correction_factor[data->bucket];

    /* 4. 가장 깊은 적절한 C-state 선택 */
    idx = 0;
    for (i = 1; i < drv->state_count; i++) {
        struct cpuidle_state *s = &drv->states[i];

        if (dev->states_usage[i].disable)
            continue;

        /* exit_latency가 PM QoS 요구를 초과하면 건너뜀 */
        if (s->exit_latency_ns > latency_req)
            break;

        /* target_residency가 예측 시간을 초과하면 건너뜀 */
        if (s->target_residency_ns > predicted_ns)
            break;

        idx = i;
    }

    /* 5. 매우 짧은 유휴면 tick 유지 */
    if (idx == 0 && drv->states[0].flags & CPUIDLE_FLAG_POLLING)
        *stop_tick = false;

    return idx;
}

Menu governor는 최근 유휴 시간의 이동 평균분산을 추적하여 다음 유휴 시간을 예측합니다:

/* get_typical_interval: 최근 유휴 패턴 분석 */
static u64 get_typical_interval(struct menu_device *data, u64 expected_ns)
{
    /*
     * intervals[] 배열: 최근 INTERVALS(=8) 회의 유휴 시간 기록
     *
     * 알고리즘:
     * 1. 최근 8회 유휴 시간에서 표준편차 계산
     * 2. 표준편차의 1.5배를 초과하는 이상치 제거
     * 3. 남은 값의 평균을 예측값으로 사용
     * 4. 다음 타이머 이벤트 시간으로 클램핑
     */

    u64 avg, stddev, max, thresh;
    int i, divisor;

    /* 평균 및 표준편차 계산 */
    avg = 0;
    for (i = 0; i < INTERVALS; i++)
        avg += data->intervals[i];
    avg >>= INTERVAL_SHIFT;  /* /8 */

    /* 이상치 제거 후 재계산 */
    thresh = avg + (stddev << 1);  /* avg + 2*stddev */
    max = min(thresh, expected_ns);

    return max;
}

Menu Governor의 보정 계수 (Correction Factor)

Menu governor는 워크로드 유형별로 4개의 보정 버킷을 유지합니다:

버킷예측 유휴 시간전형적 워크로드
01 ms 미만고빈도 인터럽트, 네트워킹
11~10 ms데스크톱 상호작용
210~100 ms배치 처리, 컴파일
3100 ms 이상유휴 서버, 대기

각 버킷에 독립적인 보정 계수를 적용하여, 워크로드 변화에 빠르게 적응합니다.

TEO Governor (Timer Events Oriented)

TEO governor는 Linux 5.1에서 Rafael J. Wysocki가 도입한 governor입니다. Menu governor의 통계 기반 예측 대신, 직전 타이머 이벤트 분포를 분석하여 C-state를 선택합니다.

/* drivers/cpuidle/governors/teo.c - teo_select() 핵심 로직 */
static int teo_select(struct cpuidle_driver *drv,
                       struct cpuidle_device *dev,
                       bool *stop_tick)
{
    struct teo_cpu *cpu_data = per_cpu_ptr(&teo_cpus, dev->cpu);
    s64 latency_req = cpuidle_governor_latency_req(dev->cpu);
    u64 duration_ns;
    int i, idx;

    duration_ns = tick_nohz_get_sleep_length(&dev->next_hrtimer);

    /*
     * TEO 핵심 아이디어:
     * 각 C-state 구간별로 "일찍 깨어남(early wakeup)" 확률을 추적
     *
     * 만약 특정 C-state의 target_residency 이전에 자주 깨어난다면,
     * 그 C-state는 비효율적이므로 한 단계 얕은 상태를 선택
     */

    idx = 0;
    for (i = 1; i < drv->state_count; i++) {
        struct cpuidle_state *s = &drv->states[i];
        struct teo_bin *bin = &cpu_data->state_bins[i];

        if (dev->states_usage[i].disable)
            continue;

        if (s->exit_latency_ns > latency_req)
            break;

        if (s->target_residency_ns > duration_ns)
            break;

        /* early wakeup 비율이 높으면 건너뜀 */
        if (bin->early_hits > bin->hits + bin->misses)
            continue;

        idx = i;
    }

    return idx;
}
비교 항목Menu GovernorTEO Governor
예측 기반최근 8회 유휴 시간 통계C-state 구간별 깨어남 분포
보정 계수4개 버킷 보정 팩터없음 (직접적 분포 추적)
상호작용성iowait/IO 부스트 반영반영하지 않음
복잡도O(states * intervals)O(states)
장점범용적, 다양한 워크로드 적응단순, 타이머 기반 워크로드에 정확
단점급격한 패턴 변화에 느린 적응비정형 워크로드에 부정확
Governor C-state 선택 의사결정 트리 PM QoS latency_req? POLL (C0) = 0 us 예측 유휴 시간? (predicted_ns) > 0 us C1 / C1E < 100 us C3 / C6 100 us ~ 1 ms C7+ / C10 > 1 ms 추가 필터: state.disabled, CPUIDLE_FLAG_UNUSABLE, exit_latency > latency_req reflect(): 실제 수면 시간 vs 예측 → 다음 선택에 피드백

Ladder Governor

Ladder governor는 가장 단순한 governor로, 단계적으로 깊은/얕은 C-state로 이동합니다. 고정 tick(HZ 기반) 시스템에서 사용됩니다:

/* drivers/cpuidle/governors/ladder.c */
static int ladder_select_state(struct cpuidle_driver *drv,
                               struct cpuidle_device *dev,
                               bool *stop_tick)
{
    struct ladder_device *ldev = this_cpu_ptr(&ladder_devices);
    struct ladder_device_state *last = &ldev->states[ldev->last_state_idx];

    /*
     * 승격 조건: 현재 상태에서 promotion_count 초과
     * → 한 단계 더 깊은 C-state로 이동
     */
    if (last->stats.below_count >= last->threshold.promotion_count) {
        last->stats.below_count = 0;
        if (ldev->last_state_idx < drv->state_count - 1)
            ldev->last_state_idx++;
    }

    /*
     * 강등 조건: 실제 수면 시간 < target_residency
     * → 한 단계 얕은 C-state로 이동
     */
    if (last->stats.above_count >= last->threshold.demotion_count) {
        last->stats.above_count = 0;
        if (ldev->last_state_idx > 0)
            ldev->last_state_idx--;
    }

    return ldev->last_state_idx;
}

Haltpoll Governor (VM 게스트)

Haltpoll governor는 KVM 가상 머신 게스트에 특화된 governor입니다. VM 환경에서 HLT 실행은 VM Exit를 유발하여 호스트 커널로 전환되는 비용이 큽니다. Haltpoll은 먼저 polling으로 대기하다가, 일정 시간이 지나면 HLT를 실행합니다:

/* drivers/cpuidle/governors/haltpoll.c */
static int haltpoll_select(struct cpuidle_driver *drv,
                           struct cpuidle_device *dev,
                           bool *stop_tick)
{
    s64 latency_req = cpuidle_governor_latency_req(dev->cpu);

    /*
     * 전략:
     * 1. 짧은 유휴 → POLL (VM Exit 회피)
     * 2. 긴 유휴 → HLT (CPU 양보)
     *
     * guest_halt_poll_ns: polling 상한 시간 (기본 200 us)
     * 적응적: 이전 유휴가 짧았으면 polling 시간 증가,
     *         길었으면 polling 시간 감소
     */

    if (dev->poll_time_limit)
        return 0;  /* POLL */

    return 1;  /* HLT */
}

VM에서의 C-state 이슈

가상 머신 게스트에서 깊은 C-state를 사용하면 성능 문제가 발생할 수 있습니다:

  • VM Exit 비용: HLT/MWAIT 실행 시 VM Exit → 호스트 커널 진입 → vCPU 디스케줄 → 수 us~수십 us 오버헤드
  • 파라가상화: KVM의 kvm_steal_time으로 스틸 타임을 감지하여 governor가 보상
  • 권장 설정: VM 게스트에서는 haltpoll governor와 poll_state/halt 2-state 드라이버 사용

Intel C-states

Intel 프로세서는 intel_idle 드라이버(drivers/idle/intel_idle.c)를 통해 네이티브 MWAIT 기반 C-state를 제공합니다. 각 프로세서 세대마다 지원하는 C-state와 특성이 다릅니다.

Intel 프로세서별 C-states

프로세서코드네임지원 C-states최대 코어 C-state최대 패키지 C-state
NehalemNHMC1, C3, C6C6C6
Sandy BridgeSNBC1, C1E, C3, C6, C7C7C7
HaswellHSWC1, C1E, C3, C6, C7, C8C8C8
SkylakeSKLC1, C1E, C3, C6, C7, C8, C10C8C10
Alder LakeADLC1, C1E, C6 (P-core) / C1, C1E, C6 (E-core)C6C10
Sapphire RapidsSPRC1, C1E, C6C6C6

intel_idle C-state 테이블 예시 (Skylake)

/* drivers/idle/intel_idle.c - Skylake 데스크톱 C-state 테이블 */
static struct cpuidle_state skl_cstates[] = {
    {
        .name = "C1",
        .desc = "MWAIT 0x00",
        .flags = MWAIT2flg(0x00),
        .exit_latency = 2,            /* 2 us */
        .target_residency = 2,       /* 2 us */
        .enter = &intel_idle,
        .enter_s2idle = intel_idle_s2idle,
    },
    {
        .name = "C1E",
        .desc = "MWAIT 0x01",
        .flags = MWAIT2flg(0x01) | CPUIDLE_FLAG_ALWAYS_ENABLE,
        .exit_latency = 10,           /* 10 us */
        .target_residency = 20,      /* 20 us */
        .enter = &intel_idle,
        .enter_s2idle = intel_idle_s2idle,
    },
    {
        .name = "C3",
        .desc = "MWAIT 0x10",
        .flags = MWAIT2flg(0x10) | CPUIDLE_FLAG_TLB_FLUSHED,
        .exit_latency = 70,           /* 70 us */
        .target_residency = 211,     /* 211 us */
        .enter = &intel_idle,
        .enter_s2idle = intel_idle_s2idle,
    },
    {
        .name = "C6",
        .desc = "MWAIT 0x20",
        .flags = MWAIT2flg(0x20) | CPUIDLE_FLAG_TLB_FLUSHED,
        .exit_latency = 85,           /* 85 us */
        .target_residency = 325,     /* 325 us */
        .enter = &intel_idle,
        .enter_s2idle = intel_idle_s2idle,
    },
    {
        .name = "C7s",
        .desc = "MWAIT 0x33",
        .flags = MWAIT2flg(0x33) | CPUIDLE_FLAG_TLB_FLUSHED,
        .exit_latency = 124,          /* 124 us */
        .target_residency = 800,     /* 800 us */
        .enter = &intel_idle,
        .enter_s2idle = intel_idle_s2idle,
    },
    {
        .name = "C8",
        .desc = "MWAIT 0x40",
        .flags = MWAIT2flg(0x40) | CPUIDLE_FLAG_TLB_FLUSHED,
        .exit_latency = 200,          /* 200 us */
        .target_residency = 800,     /* 800 us */
        .enter = &intel_idle,
        .enter_s2idle = intel_idle_s2idle,
    },
    {
        .name = "C10",
        .desc = "MWAIT 0x60",
        .flags = MWAIT2flg(0x60) | CPUIDLE_FLAG_TLB_FLUSHED,
        .exit_latency = 890,          /* 890 us */
        .target_residency = 5000,    /* 5000 us (5 ms) */
        .enter = &intel_idle,
        .enter_s2idle = intel_idle_s2idle,
    },
    { .name = NULL },  /* 종료 표지 */
};

MWAIT 힌트 비트 형식

비트필드설명
[3:0]Sub C-state같은 C-state 내 세부 변종 (예: C7 vs C7s)
[7:4]C-stateMWAIT C-state 번호 (0=C1, 1=C2, ...)
[31:8]Reserved예약
# MWAIT 힌트 예시
# 0x00 = C-state 0 (C1), Sub-state 0
# 0x01 = C-state 0 (C1), Sub-state 1 (C1E)
# 0x10 = C-state 1 (C2/C3), Sub-state 0
# 0x20 = C-state 2 (C6), Sub-state 0
# 0x33 = C-state 3 (C7), Sub-state 3 (C7s)
# 0x40 = C-state 4 (C8), Sub-state 0
# 0x60 = C-state 6 (C10), Sub-state 0

# CPUID로 MWAIT 지원 확인
cpuid -l 5  # Leaf 5: MONITOR/MWAIT
# MONITOR/MWAIT:
#    smallest monitor-line size (bytes) = 0x40 (64)
#    largest  monitor-line size (bytes) = 0x40 (64)
#    number of C0 sub C-states using MWAIT = 0x0 (0)
#    number of C1 sub C-states using MWAIT = 0x2 (2)
#    number of C2 sub C-states using MWAIT = 0x1 (1)
#    number of C3 sub C-states using MWAIT = 0x2 (2)

AMD C-states

AMD 프로세서는 주로 acpi_idle 드라이버를 통해 ACPI 기반 C-state를 제공합니다. AMD의 C-state 구현은 Intel과 몇 가지 중요한 차이가 있습니다.

프로세서아키텍처지원 C-states드라이버특이사항
Zen/Zen+Zen 1세대C1, C2(C1E), C6acpi_idleMWAIT 미사용
Zen 2Matisse/RomeC1, C2, C6acpi_idleBIOS에서 C-state 제어
Zen 3Vermeer/MilanC1, C2, C6acpi_idle향상된 C6 전력 관리
Zen 4Raphael/GenoaC1, C2, C6amd_idle네이티브 드라이버 도입 시작

AMD MWAIT 제한 사항

AMD 프로세서는 MWAIT를 지원하지만, 깊은 C-state에는 제한적입니다:

  • MWAIT C1만 안정: AMD Zen 시리즈에서 MWAIT는 C1에만 안정적으로 동작
  • C6 진입 경로: AMD C6는 MWAIT가 아닌 ACPI I/O 포트 또는 전용 레지스터 방식
  • BIOS 의존성: AMD 시스템에서 C-state 깊이는 BIOS/UEFI 설정에 크게 의존
  • 커널 파라미터: amd_pstate=active 또는 amd_pstate=passive와 함께 사용
# AMD 시스템에서 C-state 확인
cat /sys/devices/system/cpu/cpuidle/current_driver
# acpi_idle

cat /sys/devices/system/cpu/cpu0/cpuidle/state*/name
# POLL
# C1
# C2

# AMD에서 C6 활성화 확인
dmesg | grep -i "cpuidle\|c-state\|acpi_idle"
# [    0.391204] ACPI: processor limited to max C-state 2
# BIOS에서 C6 활성화 필요

Package C-states

Package C-state(PC-state)는 전체 CPU 패키지 수준의 전력 상태입니다. 패키지 내 모든 코어가 특정 깊이 이상의 코어 C-state에 진입해야만 패키지 C-state로 전이됩니다.

코어 C-state와 Package C-state 관계 CPU Package Core 0 C-state: C6 L1/L2 OFF Core 1 C-state: C6 L1/L2 OFF Core N C-state: C6 L1/L2 OFF Uncore LLC (L3) Memory Ctrl PCIe Root Package C-state (PC6/PC8/PC10) 모든 코어 C6+ → Uncore 클럭 게이팅 → LLC 플러시 → 패키지 전원 차단 PC-state 전이 조건: ALL cores in C6+ AND no pending interrupts AND no DMA PC2: Ring/Uncore 클럭 감소 PC6: Uncore 전원 차단, LLC 유지 PC8: LLC 플러시, 대부분 전원 차단 PC10: 패키지 완전 차단 (S0ix 연계)
# 패키지 C-state 사용률 확인
turbostat --interval 1 --show Pkg%pc2,Pkg%pc3,Pkg%pc6,Pkg%pc7,Pkg%pc8,Pkg%pc10
# Pkg%pc2  Pkg%pc3  Pkg%pc6  Pkg%pc7  Pkg%pc8  Pkg%pc10
#    5.12     0.00    72.30     0.00     0.00      0.00

# MSR로 패키지 C-state residency 확인 (Intel)
rdmsr 0x3F8  # MSR_PKG_C2_RESIDENCY
rdmsr 0x3F9  # MSR_PKG_C3_RESIDENCY
rdmsr 0x3FA  # MSR_PKG_C6_RESIDENCY
rdmsr 0x3FB  # MSR_PKG_C7_RESIDENCY
rdmsr 0x630  # MSR_PKG_C8_RESIDENCY
rdmsr 0x631  # MSR_PKG_C9_RESIDENCY
rdmsr 0x632  # MSR_PKG_C10_RESIDENCY
패키지 C-state코어 조건Uncore 동작LLC메모리 컨트롤러전력 절감
PC0하나 이상 C0활성활성활성기준
PC2모든 코어 C1+클럭 감소활성활성~20%
PC3모든 코어 C3+클럭 게이팅유지셀프리프레시~50%
PC6모든 코어 C6+전원 차단유지셀프리프레시~80%
PC8모든 코어 C8+전원 차단플러시셀프리프레시~90%
PC10모든 코어 C10전원 차단플러시최소 전력~99%

s2idle (Suspend-to-Idle)

s2idle(Suspend-to-Idle)은 S0ix 저전력 유휴 상태로, 전통적인 S3 슬립 대신 소프트웨어 기반 절전을 구현합니다. CPUIdle의 가장 깊은 C-state를 활용하여 S3에 가까운 전력 절감을 달성합니다.

/* kernel/power/suspend.c - s2idle 진입 경로 */
static int suspend_enter(suspend_state_t state)
{
    if (state == PM_SUSPEND_TO_IDLE) {
        /*
         * s2idle 경로:
         * 1. 디바이스 suspend (IRQ 비활성화)
         * 2. 각 CPU가 deepest C-state 진입
         * 3. enter_s2idle() 콜백 사용
         * 4. 웨이크업 이벤트 시 복귀
         */
        cpuidle_enter_s2idle();
        return 0;
    }
    /* S3 경로: ACPI/firmware 기반 */
    ...
}
비교 항목S3 (Suspend-to-RAM)s2idle (S0ix)
구현펌웨어/ACPI 기반소프트웨어(커널) 기반
CPU 상태전원 완전 차단가장 깊은 C-state (C10)
메모리셀프리프레시 (내용 유지)셀프리프레시 (내용 유지)
복귀 시간1~3초100~500 ms
전력 소비매우 낮음 (~0.5W)낮음 (~1~3W)
웨이크업 소스PME, 전원 버튼, RTC모든 인터럽트 (IRQ 기반)
커널 파라미터mem_sleep_default=deepmem_sleep_default=s2idle
# s2idle 지원 확인
cat /sys/power/mem_sleep
# s2idle [deep]   ← deep이 기본이면 s2idle은 지원만 되는 상태
# [s2idle] deep   ← s2idle이 기본 선택

# s2idle을 기본으로 변경
echo s2idle > /sys/power/mem_sleep

# s2idle 진입
echo mem > /sys/power/state

# s2idle 디버깅
echo 1 > /sys/power/pm_debug_messages
dmesg | grep -i "s2idle\|suspend\|freeze"

s2idle 전력 최적화

s2idle에서 최적의 전력 절감을 달성하려면:

  • PC10 도달: 모든 코어가 C10에 진입해야 PC10 패키지 C-state 달성
  • 디바이스 D3: PCIe 디바이스가 D3 상태로 전환되어야 패키지 C-state 허용
  • ASPM L1.2: PCIe Active State Power Management이 L1.2까지 허용
  • USB: USB 디바이스가 autosuspend되어야 함

지연시간 vs 전력 트레이드오프

깊은 C-state는 전력은 절약하지만 응답성을 희생합니다. 이 트레이드오프를 이해하는 것이 CPUIdle 튜닝의 핵심입니다.

C-state: 복귀 지연 vs 전력 소비 복귀 지연 (Exit Latency) 전력 소비 (W) C0 0 us C1 2 us C1E 10 us C3 70 us C6 85 us C7s 124 us C10 890 us 실시간/HPC 웹서버/DB 데스크톱/노트북

워크로드별 권장 설정

워크로드최대 C-statelatency_req이유절전 효과
데스크톱C7+제한 없음절전 우선, 응답 지연 허용매우 높음
웹 서버C1E10 ustail latency 제어 필요중간
데이터베이스C1E~C350 us쿼리 지연 변동 최소화중간
HPCC11 us최대 처리량, 균일한 지연낮음
실시간C0 (비활성화)0 us결정적 지연 필요없음
배치 처리C8+제한 없음전력 효율 최우선매우 높음
VM 호스트C1E20 usVM Exit 오버헤드 고려낮음~중간

Deep C-state 문제

C6 이상의 깊은 C-state는 다음 문제를 일으킬 수 있습니다:

  • 지연 시간 증가: 100us+ → 고빈도 트레이딩, 실시간 시스템에 부적합
  • TSC (Time Stamp Counter) 정지: C3+ (일부 CPU) → 타이밍 측정 오류
  • PCIe ASPM 문제: 일부 하드웨어와 호환 문제
  • 성능 변동: Wake-up 지연으로 일시적 성능 저하 (tail latency 증가)
  • 캐시 트래싱: C3+에서 캐시 flush 후 캐시 워밍업 비용

고성능 서버는 보통 C1E 이하로 제한합니다.

PM QoS (Power Management Quality of Service)

PM QoS는 커널 드라이버와 사용자 공간 프로그램이 최대 허용 CPU 복귀 지연 시간을 설정하여 깊은 C-state 진입을 제한하는 프레임워크입니다. CPUIdle governor는 PM QoS 제약을 반드시 준수합니다.

PM QoS latency 제약 흐름 /dev/cpu_dma_latency 사용자 공간 요청 dev_pm_qos_request() 커널 드라이버 요청 cpu_latency_qos per-CPU QoS 요청 PM QoS 집계 min(모든 요청) = latency_req CPUIdle Governor exit_latency <= latency_req C-state 범위 제한 예: latency_req = 10 us → C1E까지만 허용 (exit_latency 10 us) 예: latency_req = 0 us → POLL만 허용 (C0, exit_latency 0 us)

사용자 공간 PM QoS

/* 사용자 공간에서 PM QoS 설정 (/dev/cpu_dma_latency) */
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>

int main(void)
{
    /* CPU DMA 지연시간을 10us로 제한 → C1E 이하만 허용 */
    int fd = open("/dev/cpu_dma_latency", O_WRONLY);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    int32_t latency_us = 10;  /* 10 us */
    write(fd, &latency_us, sizeof(latency_us));

    /*
     * fd를 열어둔 동안 지연시간 제한 유지
     * close(fd) 호출 시 제한 해제
     *
     * 주의: fd가 닫히면 즉시 해제되므로,
     * 백그라운드에서 유지하려면 프로세스가 살아있어야 함
     */

    /* 작업 수행... */
    sleep(3600);

    close(fd);  /* 제한 해제 */
    return 0;
}

커널 드라이버 PM QoS

/* 커널 드라이버에서 CPU 지연 제약 설정 */
#include <linux/pm_qos.h>

static struct pm_qos_request my_qos_req;

static int my_driver_probe(struct platform_device *pdev)
{
    /* DMA 전송 중 깊은 C-state 방지 */
    cpu_latency_qos_add_request(&my_qos_req, 50); /* 50 us */
    return 0;
}

static void my_dma_start(void)
{
    /* DMA 시작 시 제약 강화 */
    cpu_latency_qos_update_request(&my_qos_req, 0);  /* 0 us: POLL만 */
}

static void my_dma_complete(void)
{
    /* DMA 완료 후 제약 완화 */
    cpu_latency_qos_update_request(&my_qos_req, PM_QOS_DEFAULT_VALUE);
}

static int my_driver_remove(struct platform_device *pdev)
{
    cpu_latency_qos_remove_request(&my_qos_req);
    return 0;
}

Tickless Kernel (NOHZ)

Tickless Kernel은 유휴 시 타이머 인터럽트를 중단하여 깊은 C-state를 허용합니다. CPUIdle과 밀접하게 연동되며, C-state 효율에 직접적인 영향을 미칩니다.

고정 Tick (HZ=1000) C1 C1 1ms마다 인터럽트 → C3+ 진입 불가 NO_HZ_IDLE (Tickless) idle 진입 타이머 이벤트 C6 (수백 ms 지속 가능) tick 중단 → 다음 이벤트까지 깊은 C-state 유지 전력 절감 효과: NO_HZ_IDLE 시 C6 residency 10배+ 향상 노트북 배터리: 4시간 (고정 tick) → 6시간+ (tickless, 50% 향상)

동적 틱 (Dynamic Tick)

/* 기존 (HZ=1000): 1ms마다 타이머 인터럽트 발생 */
/* → C-state가 1ms 이상 유지 불가 */

/* Tickless: 다음 이벤트까지 타이머 중단 */
/* → 수백 ms 동안 깊은 C-state 유지 가능 */

# 커널 설정 확인
zcat /proc/config.gz | grep NO_HZ
# CONFIG_NO_HZ_IDLE=y      (유휴 CPU만, 기본 권장)
# CONFIG_NO_HZ_FULL=y      (모든 CPU, 실시간/HPC용)

Tickless 효과

항목HZ=1000 (고정 틱)NO_HZ_IDLE (Tickless)
유휴 타이머 인터럽트1000/sec거의 없음
최대 C-state 지속1ms제한 없음
전력 절감중간높음
노트북 배터리4시간6시간+ (50% 향상)
패키지 C-state도달 어려움PC6/PC10 도달 용이
Governor 정확도1ms 제한실제 이벤트 기반 정확

NOHZ와 CPUIdle 상호작용

/* CPUIdle이 tick을 제어하는 핵심 코드 */
static void cpuidle_idle_call(void)
{
    bool stop_tick = true;

    /* Governor가 stop_tick 결정 */
    next_state = cpuidle_select(drv, dev, &stop_tick);

    /*
     * stop_tick = true → 깊은 C-state (C3+)
     *   tick 중단 → 다음 타이머까지 연속 수면
     *
     * stop_tick = false → 얕은 C-state (POLL/C1)
     *   tick 유지 → 1ms마다 깨어남 (스케줄러 체크)
     *
     * 판단 기준: 예측 유휴 시간 vs tick 주기
     *   예측 < 2*tick_period → tick 유지 (stop_tick=false)
     *   예측 > 2*tick_period → tick 중단 (stop_tick=true)
     */
    if (stop_tick || tick_nohz_tick_stopped())
        tick_nohz_idle_stop_tick();
    else
        tick_nohz_idle_retain_tick();

    /* C-state 진입 */
    entered_state = cpuidle_enter(drv, dev, next_state);

    /* tick 재시작 */
    tick_nohz_idle_restart_tick();
}

sysfs 인터페이스

CPUIdle 서브시스템은 /sys/devices/system/cpu/ 아래에 풍부한 sysfs 인터페이스를 제공합니다.

전역 설정

# 현재/사용가능 governor
cat /sys/devices/system/cpu/cpuidle/current_governor
cat /sys/devices/system/cpu/cpuidle/available_governors

# 현재/사용가능 드라이버
cat /sys/devices/system/cpu/cpuidle/current_driver
# intel_idle

per-CPU C-state 정보

# CPU별 C-state 정보 경로
# /sys/devices/system/cpu/cpu/cpuidle/state/

# C-state 이름과 설명
cat /sys/devices/system/cpu/cpu0/cpuidle/state*/name
# POLL
# C1
# C1E
# C6
# C7s

cat /sys/devices/system/cpu/cpu0/cpuidle/state*/desc
# CPUIDLE CORE POLL IDLE
# MWAIT 0x00
# MWAIT 0x01
# MWAIT 0x20
# MWAIT 0x33

# exit_latency (us)
cat /sys/devices/system/cpu/cpu0/cpuidle/state*/latency
# 0    (POLL)
# 2    (C1)
# 10   (C1E)
# 85   (C6)
# 124  (C7s)

# target_residency (us)
cat /sys/devices/system/cpu/cpu0/cpuidle/state*/residency
# 0    (POLL)
# 2    (C1)
# 20   (C1E)
# 325  (C6)
# 800  (C7s)

# 사용 시간 (us) 및 진입 횟수
cat /sys/devices/system/cpu/cpu0/cpuidle/state*/time
cat /sys/devices/system/cpu/cpu0/cpuidle/state*/usage

# 거부 횟수 (exit_latency > latency_req)
cat /sys/devices/system/cpu/cpu0/cpuidle/state*/rejected

# C-state 비활성화/활성화
echo 1 > /sys/devices/system/cpu/cpu0/cpuidle/state3/disable  # 비활성화
echo 0 > /sys/devices/system/cpu/cpu0/cpuidle/state3/disable  # 활성화
sysfs 파일읽기/쓰기설명
nameRC-state 이름 (C1, C6, ...)
descR설명 (MWAIT 힌트 등)
latencyRexit_latency (us)
residencyRtarget_residency (us)
powerR전력 소비 (상대값)
timeR누적 사용 시간 (us)
usageR누적 진입 횟수
rejectedR거부된 횟수
disableR/W0: 활성, 1: 비활성화
aboveR이 상태보다 얕게 유지된 횟수
belowR이 상태보다 깊게 유지된 횟수
s2idle/timeRs2idle 모드 누적 시간
s2idle/usageRs2idle 모드 진입 횟수

모니터링

C-state 사용 현황을 확인하는 다양한 도구를 활용할 수 있습니다.

turbostat - 실시간 C-state 모니터링

turbostat --interval 1
# Core CPU  Avg_MHz  Busy%  Bzy_MHz  C1%   C1E%  C6%   C7s%
#    0   0     800    20.0    4000    10.0  20.0  40.0  10.0
#    0   4     750    18.0    4166    12.0  25.0  35.0   9.0

# 패키지 C-state도 확인 가능
# Pkg%pc2  Pkg%pc3  Pkg%pc6  Pkg%pc7  Pkg%pc8  Pkg%pc10
#    5.0     10.0     20.0      5.0      2.0       0.5

# 특정 컬럼만 표시
turbostat --show Core,CPU,C1%,C1E%,C6%,Pkg%pc6 --interval 2

powertop - 전력 분석

powertop
# C-state 사용률 표시
#   C0 (active):   5.0%
#   C1:           10.0%
#   C1E:          15.0%
#   C6:           60.0%
#   C7s:          10.0%

# CSV 보고서 생성
powertop --csv=powertop_report.csv --time=60

ftrace로 CPUIdle 추적

# CPUIdle 이벤트 추적 활성화
echo 1 > /sys/kernel/debug/tracing/events/power/cpu_idle/enable
cat /sys/kernel/debug/tracing/trace_pipe

# 출력 예시:
# <idle>-0  [000]  123.456789: cpu_idle: state=4 cpu_id=0
# <idle>-0  [000]  123.457234: cpu_idle: state=4294967295 cpu_id=0  ← 복귀

# perf로 C-state 이벤트 기록
perf record -e power:cpu_idle -a -- sleep 10
perf script

# BPF로 C-state 지연 히스토그램
bpftrace -e '
tracepoint:power:cpu_idle /args->state < 100/ {
    @start[cpu] = nsecs;
}
tracepoint:power:cpu_idle /args->state == 4294967295/ {
    if (@start[cpu]) {
        @us = hist((nsecs - @start[cpu]) / 1000);
        delete(@start[cpu]);
    }
}'

cpupower idle-info

cpupower idle-info
# CPUidle driver: intel_idle
# CPUidle governor: menu
# analyzing CPU 0:
# 
# Number of idle states: 5
# Available idle states: POLL C1 C1E C6 C7s
# POLL:
#     Flags/Description: CPUIDLE CORE POLL IDLE
#     Latency: 0
#     Usage: 12345
#     Duration: 67890
# C1:
#     Flags/Description: MWAIT 0x00
#     Latency: 2
#     Usage: 234567
#     Duration: 890123
# ...

# 모든 CPU의 C-state 사용률 요약
cpupower idle-set -D 3  # state3(C6) 이상 비활성화
cpupower idle-set -E 3  # state3(C6) 이상 재활성화
CPUIdle 모니터링 도구 체계 turbostat powertop cpupower perf / bpftrace sysfs (cpuidle/state*/) MSR (RAPL, Residency) tracepoints (power:*) CPUIdle Core + Governor + Driver

성능 튜닝

C-state를 조정하여 응답성 또는 절전을 최적화합니다. 튜닝 방법은 크게 세 가지 수준으로 나뉩니다.

커널 부팅 파라미터

파라미터효과
intel_idle.max_cstate=N0~9intel_idle 드라이버의 최대 C-state 제한
processor.max_cstate=N0~9ACPI 프로세서 드라이버의 최대 C-state 제한
idle=poll-모든 C-state 비활성화, busy loop만 사용
idle=halt-HLT만 사용 (C1)
idle=nomwait-MWAIT 사용 금지, ACPI 폴백
cpuidle.governor=NAME문자열부팅 시 governor 선택
nohz=off-NO_HZ 비활성화 (고정 tick)
# GRUB에서 C-state 제한 설정
# /etc/default/grub
GRUB_CMDLINE_LINUX="intel_idle.max_cstate=1"  # C1까지만 허용
update-grub

# 또는 동시에 ACPI 드라이버도 제한
GRUB_CMDLINE_LINUX="intel_idle.max_cstate=1 processor.max_cstate=1"

런타임 C-state 비활성화

# 특정 C-state 비활성화 (예: C6, state index 3)
echo 1 > /sys/devices/system/cpu/cpu*/cpuidle/state3/disable

# C1E 이상 모두 비활성화 (고성능 모드)
for cpu in /sys/devices/system/cpu/cpu*/cpuidle; do
    for state in $cpu/state*; do
        name=$(cat $state/name)
        if [ "$name" != "POLL" ] && [ "$name" != "C1" ]; then
            echo 1 > $state/disable
        fi
    done
done

# cpupower로 일괄 비활성화
cpupower idle-set -d 3  # state index 3 이상 비활성화

# 전체 C-state 활성화 복원
for state in /sys/devices/system/cpu/cpu*/cpuidle/state*/disable; do
    echo 0 > $state
done

PM QoS 기반 튜닝

# 저지연 웹 서버: C-state를 C1E 이하로 제한
# 방법 1: pm-qa 도구
echo 10 > /dev/cpu_dma_latency  # fd를 열어두어야 유효

# 방법 2: 간단한 데몬 스크립트
exec 3> /dev/cpu_dma_latency
echo -ne '\x0a\x00\x00\x00' >&3  # 10 us (리틀엔디안)
# fd 3이 열린 동안 유효
# exec 3>&- 로 해제

# 방법 3: tuned 프로파일 사용 (RHEL/CentOS)
tuned-adm profile latency-performance
# 이 프로파일은 자동으로 cpu_dma_latency=1 설정

서버 시나리오별 최적화 예제

# === 시나리오 1: 저지연 트레이딩 서버 ===
# 목표: 최소 지연, 전력 비고려
echo performance > /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
GRUB_CMDLINE_LINUX="idle=poll isolcpus=2-7 nohz_full=2-7"
# idle=poll: 모든 C-state 비활성화
# isolcpus: 전용 코어 격리
# nohz_full: 격리 코어에서 tick 완전 제거

# === 시나리오 2: 웹 서버 (nginx/Apache) ===
# 목표: tail latency 억제, 적절한 절전
intel_idle.max_cstate=2        # C1E까지
echo 10 > /dev/cpu_dma_latency  # 10 us PM QoS

# === 시나리오 3: 배치/ML 학습 서버 ===
# 목표: GPU 대기 시 CPU 전력 절약
# C-state 제한 없음 (기본값 유지)
echo teo > /sys/devices/system/cpu/cpuidle/current_governor

# === 시나리오 4: 데이터센터 공유 호스트 (KVM) ===
# 목표: VM density 최대화, 적절한 응답성
intel_idle.max_cstate=2         # 호스트: C1E까지
# 게스트: haltpoll governor 자동 선택

idle=poll 경고

idle=poll은 모든 유휴 CPU가 100% 전력을 소비하며, CPU 온도가 급등하여 써멀 스로틀링을 유발할 수 있습니다. 대부분의 저지연 워크로드에서는 intel_idle.max_cstate=1idle=poll보다 나은 선택입니다. C1의 복귀 지연은 2us에 불과하며, polling 대비 90%+ 전력을 절약합니다.

커널 설정 (Kconfig)

CPUIdle 관련 주요 커널 설정 옵션입니다.

# CPU_IDLE: CPUIdle 서브시스템 활성화
CONFIG_CPU_IDLE=y

# Governor 선택
CONFIG_CPU_IDLE_GOV_LADDER=y
CONFIG_CPU_IDLE_GOV_MENU=y
CONFIG_CPU_IDLE_GOV_TEO=y
CONFIG_CPU_IDLE_GOV_HALTPOLL=y

# 기본 Governor
# (NO_HZ_IDLE=y면 menu, 아니면 ladder)
CONFIG_CPU_IDLE_GOV_MENU=y  # 기본값

# Intel idle 드라이버
CONFIG_INTEL_IDLE=y

# ACPI 프로세서 idle
CONFIG_ACPI_PROCESSOR_IDLE=y

# Tickless 관련
CONFIG_NO_HZ_IDLE=y      # 유휴 CPU만 tickless (권장)
CONFIG_NO_HZ_FULL=y      # 모든 CPU tickless (실시간/HPC)
CONFIG_HZ_1000=y          # 기본 tick 빈도

# PM QoS
CONFIG_PM_QOS=y
CONFIG_CPU_FREQ=y         # CPUFreq (P-states와 연동)
설정의존성설명
CPU_IDLE없음CPUIdle 프레임워크 전체 활성화
INTEL_IDLEX86, CPU_IDLEIntel MWAIT 기반 드라이버
CPU_IDLE_GOV_MENUCPU_IDLEMenu governor (일반 시스템 기본)
CPU_IDLE_GOV_TEOCPU_IDLETEO governor (타이머 기반 워크로드)
CPU_IDLE_GOV_LADDERCPU_IDLELadder governor (고정 tick 시스템)
CPU_IDLE_GOV_HALTPOLLCPU_IDLE, KVM_GUESTHaltpoll governor (VM 게스트)
NO_HZ_IDLE없음유휴 시 tick 중단 (C-state 효율 향상)

커널 트레이싱과 디버깅

CPUIdle 문제를 진단하기 위한 고급 디버깅 기법입니다.

CPUIdle Tracepoints

# 사용 가능한 CPUIdle tracepoint 목록
ls /sys/kernel/debug/tracing/events/power/
# cpu_idle          ← C-state 진입/복귀
# cpu_frequency     ← P-state 변경
# pm_qos_update     ← PM QoS 값 변경
# wakeup_source_activate    ← 웨이크업 소스
# wakeup_source_deactivate

# cpu_idle 트레이스포인트 활성화
echo 1 > /sys/kernel/debug/tracing/events/power/cpu_idle/enable

# PM QoS 변경 추적
echo 1 > /sys/kernel/debug/tracing/events/power/pm_qos_update/enable

# 트레이스 읽기
cat /sys/kernel/debug/tracing/trace
# <idle>-0  [000] d.. 12345.678901: cpu_idle: state=3 cpu_id=0
# <idle>-0  [000] d.. 12345.679123: cpu_idle: state=4294967295 cpu_id=0
# state=4294967295 (0xFFFFFFFF) = PWR_EVENT_EXIT = 복귀

BPF 기반 C-state 분석

# C-state 체류 시간 분포 (히스토그램)
bpftrace -e '
tracepoint:power:cpu_idle
{
    if (args->state < 10) {
        @entry[cpu, args->state] = nsecs;
    } else {
        /* 복귀 이벤트 (state == PWR_EVENT_EXIT) */
        $key = cpu;
        if (@entry[$key, 0]) {
            @residency_ns = hist(nsecs - @entry[$key, 0]);
            delete(@entry[$key, 0]);
        }
    }
}'

# 어떤 인터럽트가 C-state를 깨우는지 분석
bpftrace -e '
tracepoint:irq:irq_handler_entry
{
    @wakeup_irq[args->name] = count();
}'

일반적인 문제와 해결

# 문제 1: C-state가 작동하지 않음

# 1. BIOS에서 C-states 활성화 확인

# 2. intel_idle 드라이버 확인
lsmod | grep intel_idle
cat /sys/devices/system/cpu/cpuidle/current_driver

# 3. C-state 목록 확인
cpupower idle-info

# 4. 커널 파라미터 확인
cat /proc/cmdline | grep idle
# idle=poll이나 intel_idle.max_cstate=0이 없어야 함

# 5. PM QoS 확인
cat /sys/kernel/debug/pm_qos/cpu_dma_latency/requested_value
# 문제 2: 성능 저하 (과도한 C-state)

# 증상: tail latency 급증, 간헐적 응답 지연
# 원인: 깊은 C-state 복귀 지연

# 진단: C-state 사용률 확인
turbostat --interval 1 --show Core,CPU,Avg_MHz,Busy%,C1%,C1E%,C6%
# C6%가 높으면 깊은 C-state가 자주 사용되는 것

# 해결: exit_latency 100us 초과 C-state 비활성화
for state in /sys/devices/system/cpu/cpu*/cpuidle/state*; do
    latency=$(cat $state/latency)
    if [ $latency -gt 100 ]; then
        echo 1 > $state/disable
        echo "Disabled $state (latency=${latency}us)"
    fi
done
# 문제 3: 패키지 C-state에 도달하지 못함

# 진단: turbostat으로 Pkg%pc6 확인
turbostat --interval 5 --show Pkg%pc2,Pkg%pc6,Pkg%pc10
# Pkg%pc6 = 0%이면 패키지 C-state 미진입

# 원인 찾기:
# 1. 하나의 코어라도 깊은 C-state에 있지 않으면 PC6 불가
turbostat --interval 1 --show Core,CPU,C6%  # 모든 코어 C6% 확인

# 2. PCIe 디바이스가 ASPM L1 미진입
lspci -vvv | grep -i "LnkCtl:\|ASPM"

# 3. USB 디바이스 autosuspend 미설정
cat /sys/bus/usb/devices/*/power/control
# "auto"가 아닌 "on"이 있으면 패키지 C-state 방해

디버깅 체크리스트

  1. cpupower idle-info로 드라이버, governor, C-state 목록 확인
  2. turbostat으로 실시간 C-state 분포 확인
  3. /proc/cmdline으로 부팅 파라미터 확인
  4. /dev/cpu_dma_latency PM QoS 확인
  5. dmesg | grep -i "cpuidle\|intel_idle\|acpi_idle"로 초기화 로그 확인
  6. ftrace power:cpu_idle 트레이스포인트로 진입/복귀 패턴 분석

CPUIdle과 다른 서브시스템 연계

CPUIdle은 독립적으로 동작하지 않고, 커널의 여러 서브시스템과 밀접하게 상호작용합니다.

CPUIdle Core + Governor + Driver Scheduler (do_idle) 유휴 감지 CPUFreq (P-states) 주파수 협조 NOHZ / Timer Interrupt (IRQ) 깨움 PM QoS latency 제약 RCU idle 알림 Thermal 온도 제한 ACPI / Firmware C-state 정보

Scheduler와의 연계

/* 스케줄러의 idle 태스크 (PID 0) */
static void cpu_idle_loop(void)
{
    while (1) {
        /*
         * 스케줄러 → CPUIdle 호출 경로:
         * schedule() → pick_next_task() → idle 태스크 선택
         * → cpu_idle_loop() → do_idle() → cpuidle_idle_call()
         *
         * 깨어남 후:
         * cpuidle_idle_call() 복귀 → schedule_idle()
         * → pick_next_task()로 새 태스크 선택
         */

        tick_nohz_idle_enter();

        while (!need_resched()) {
            local_irq_disable();
            cpuidle_idle_call();
            local_irq_enable();
        }

        tick_nohz_idle_exit();
        schedule_idle();
    }
}

CPUFreq와의 연계

CPUIdle(C-states)과 CPUFreq(P-states)는 독립적이지만 상호 보완적입니다:

RCU와의 연계

/*
 * CPUIdle과 RCU의 상호작용:
 *
 * C-state 진입 전 rcu_idle_enter() 호출
 * → RCU에게 "이 CPU는 유휴 상태"임을 알림
 * → RCU grace period 종료 판단에 사용
 *
 * 모든 CPU가 유휴(rcu_idle) 상태이면
 * → RCU grace period가 빠르게 완료
 * → 메모리 해제, 콜백 실행 가속
 */

/* cpuidle_enter() 내부 */
rcu_idle_enter();
entered_state = target_state->enter(dev, drv, index);
rcu_idle_exit();

RCU nocb와 CPUIdle

rcu_nocbs=CPU_LIST 커널 파라미터를 사용하면 RCU 콜백 처리를 전용 커널 스레드로 오프로드하여, 격리된 CPU가 더 깊은 C-state에 오래 머물 수 있습니다. 실시간 시스템과 HPC 환경에서 유용합니다.

문제 해결

일반적인 C-state 문제와 체계적인 해결 방법입니다.

C-state가 작동하지 않음

# 단계별 진단

# 1. BIOS에서 C-states 활성화 확인
# → BIOS Setup > Advanced > CPU Configuration > C-States: Enabled

# 2. 현재 드라이버 확인
cat /sys/devices/system/cpu/cpuidle/current_driver
# "none"이면 CPUIdle 비활성화 상태

# 3. intel_idle 모듈 로드 확인
lsmod | grep intel_idle
dmesg | grep intel_idle

# 4. C-state 목록 확인
cpupower idle-info

# 5. 커널 파라미터 확인 (idle=poll 등이 없어야 함)
cat /proc/cmdline | grep idle

# 6. PM QoS 확인 (0이면 POLL만 허용)
cat /sys/devices/system/cpu/cpu0/power/pm_qos_resume_latency_us

성능 저하 (과도한 C-state)

# 증상: p99 latency 급증, 간헐적 지연

# 1. C-state 분포 확인
turbostat --interval 2 --show Core,CPU,Busy%,C1%,C1E%,C6%,C7s%

# 2. C6%가 높으면 → C6 비활성화
for state in /sys/devices/system/cpu/cpu*/cpuidle/state*; do
    latency=$(cat $state/latency)
    if [ $latency -gt 100 ]; then
        echo 1 > $state/disable
    fi
done

# 3. PM QoS로 미세 조정
exec 3> /dev/cpu_dma_latency
echo -ne '\x32\x00\x00\x00' >&3  # 50 us

# 4. 효과 확인
turbostat --interval 2 --show Core,CPU,Busy%,C1%,C1E%

전력 소비 과다

# 증상: 노트북 배터리 수명 짧음, 패키지 C-state 미진입

# 1. 패키지 C-state 확인
turbostat --interval 5 --show Pkg%pc2,Pkg%pc6
# Pkg%pc6 = 0%이면 문제

# 2. 어떤 코어가 깊은 C-state에 들어가지 못하는지 확인
turbostat --interval 1 --show Core,CPU,C6%
# C6% = 0인 코어 찾기

# 3. 깨우는 원인 분석
perf record -e power:cpu_idle,irq:irq_handler_entry -a -- sleep 10
perf script | head -50

# 4. 주요 방해 요소 확인
powertop  # "Tunables" 탭에서 "Bad" 항목 확인

# 5. PCIe ASPM 활성화
echo powersave > /sys/module/pcie_aspm/parameters/policy

# 6. USB autosuspend 활성화
for dev in /sys/bus/usb/devices/*/power/control; do
    echo auto > $dev
done
CPUIdle 문제 해결 흐름 증상은? (성능 or 전력) 성능: tail latency 증가 전력: 배터리 수명 짧음 turbostat: C6%/C7s% 높은가? 1. intel_idle.max_cstate=1~2 2. PM QoS latency 제한 turbostat: Pkg%pc6 = 0%인가? 1. PCIe ASPM 활성화 2. USB autosuspend 활성화 3. PM QoS 제약 해제 확인 4. BIOS C-state 설정 확인 도구: turbostat, powertop, cpupower, ftrace, bpftrace

ARM/RISC-V C-states

ARM과 RISC-V 아키텍처도 CPUIdle 프레임워크를 사용하지만, x86과는 진입 메커니즘과 C-state 구조가 다릅니다.

ARM: WFI/WFE 기반 idle

ARM 프로세서는 WFI(Wait For Interrupt)와 WFE(Wait For Event) 명령어를 사용합니다:

/* arch/arm64/kernel/cpuidle.c */
static int arm_cpuidle_simple_enter(struct cpuidle_device *dev,
                                    struct cpuidle_driver *drv,
                                    int index)
{
    /*
     * ARM WFI (Wait For Interrupt):
     * CPU를 저전력 대기 상태로 전환
     * 인터럽트 발생 시 즉시 복귀
     * x86의 HLT와 유사
     */
    cpu_do_idle();  /* WFI 명령어 실행 */
    return index;
}
ARM C-state명칭메커니즘exit_latency설명
WFIClock gatingWFI1~5 us코어 클럭 게이팅, 캐시 유지
Core retentionRetentionPSCI10~50 us코어 전원 감소, 상태 보존
Core power downPower offPSCI100~500 us코어 전원 차단, 캐시 플러시
Cluster power downCluster offPSCI500~1000 us클러스터 전체 전원 차단
System idleS2idlePSCI1~10 ms시스템 전체 저전력

ARM PSCI (Power State Coordination Interface)

/* ARM PSCI를 통한 깊은 C-state 진입 */
/* drivers/cpuidle/cpuidle-psci.c */

static int psci_enter_domain_idle_state(struct cpuidle_device *dev,
                                        struct cpuidle_driver *drv,
                                        int index)
{
    u32 state;

    /* DT에서 정의된 idle-state에 대응하는 PSCI 파워 상태 */
    state = state_nodes[index]->psci_state;

    /*
     * PSCI CPU_SUSPEND 호출:
     * → Secure Monitor (EL3)로 전환
     * → ATF(ARM Trusted Firmware)가 실제 전원 관리
     * → 코어 전원 차단 / 클러스터 전원 차단
     */
    return psci_cpu_suspend_enter(state);
}
/* Device Tree에서 ARM idle states 정의 */
/* arch/arm64/boot/dts/vendor/board.dtsi */

idle-states {
    entry-method = "psci";

    CPU_SLEEP_0: cpu-sleep-0 {
        compatible = "arm,idle-state";
        arm,psci-suspend-param = <0x0010000>;
        local-timer-stop;
        entry-latency-us = <40>;
        exit-latency-us = <100>;
        min-residency-us = <150>;
    };

    CLUSTER_SLEEP_0: cluster-sleep-0 {
        compatible = "arm,idle-state";
        arm,psci-suspend-param = <0x1010000>;
        local-timer-stop;
        entry-latency-us = <500>;
        exit-latency-us = <1000>;
        min-residency-us = <2500>;
    };
};

ARM big.LITTLE과 CPUIdle

ARM big.LITTLE 아키텍처(예: Cortex-A76 + A55)에서는 각 코어 클러스터마다 별도의 idle state가 정의됩니다. big 코어는 성능 우선으로 얕은 C-state를 선호하고, LITTLE 코어는 에너지 효율을 위해 깊은 C-state를 적극 사용합니다. 이는 Intel Alder Lake의 P-core/E-core 구조와 유사한 개념입니다.

RISC-V idle

/* RISC-V idle 진입: WFI 명령어 사용 */
/* arch/riscv/kernel/cpuidle.c */

static int riscv_cpuidle_enter(struct cpuidle_device *dev,
                               struct cpuidle_driver *drv,
                               int index)
{
    if (index == 0) {
        /* WFI: 인터럽트 대기 */
        cpu_do_idle();  /* asm volatile ("wfi") */
    } else {
        /* SBI (Supervisor Binary Interface)를 통한 깊은 idle */
        sbi_cpu_suspend(state);
    }
    return index;
}
비교x86 (MWAIT)ARM (PSCI)RISC-V (SBI)
C1 진입MWAIT 0x00WFIwfi
깊은 idleMWAIT 힌트 비트PSCI CPU_SUSPENDSBI cpu_suspend
C-state 정의커널 C 코드 테이블Device TreeDevice Tree / 코드
펌웨어 역할없음 (네이티브)ATF (EL3)SBI 구현체 (M-mode)
패키지 idle자동 (HW)OS-initiated (PSCI)플랫폼 의존

벤치마크와 측정

CPUIdle 설정의 효과를 정량적으로 측정하는 방법입니다.

C-state 복귀 지연 측정

# 방법 1: idle_latency 벤치마크 (커널 도구)
# IPI(Inter-Processor Interrupt)로 idle CPU를 깨우고 응답 시간 측정

# 방법 2: cyclictest로 wakeup latency 측정
cyclictest --mlockall --priority=99 --interval=1000 \
           --loops=100000 --histogram=400 --histfile=hist.txt
# C-state 설정에 따른 max latency 비교:
# idle=poll:              Max: 3 us
# max_cstate=1 (C1):     Max: 8 us
# max_cstate=2 (C1E):    Max: 25 us
# 기본값 (C6 허용):      Max: 180 us
# 기본값 (C10 허용):     Max: 950 us

# 방법 3: schbench로 tail latency 측정
schbench -m 4 -t 16 -r 30
# 50th percentile:    5 us
# 75th percentile:   12 us
# 90th percentile:   35 us
# 99th percentile:  180 us  ← C-state 영향
# 99.9th percentile: 450 us

전력 측정

# RAPL (Running Average Power Limit)로 CPU 전력 측정
perf stat -e power/energy-pkg/,power/energy-cores/ -- sleep 10
# Performance counter stats for 'sleep 10':
#
#     2.34 Joules  power/energy-pkg/    (패키지 전체)
#     0.89 Joules  power/energy-cores/  (코어만)

# C-state 설정별 전력 비교:
# idle=poll:              Pkg: 45.2W  Cores: 40.1W
# max_cstate=1 (C1):     Pkg:  4.8W  Cores:  2.1W
# 기본값 (C6 허용):      Pkg:  0.9W  Cores:  0.1W
# 기본값 (C10 허용):     Pkg:  0.3W  Cores:  0.02W

# turbostat으로 watt 단위 실시간 측정
turbostat --interval 5 --show PkgWatt,CorWatt,RAMWatt
# PkgWatt  CorWatt  RAMWatt
#    0.92     0.08     0.45

# 비교 스크립트: C-state 설정별 전력/지연 트레이드오프 측정
for max_cs in 0 1 2 3 4; do
    echo "=== max_cstate=$max_cs ==="
    # C-state 설정
    for s in /sys/devices/system/cpu/cpu*/cpuidle/state*/disable; do
        echo 0 > $s
    done
    for s in /sys/devices/system/cpu/cpu*/cpuidle/state*/disable; do
        idx=$(basename $(dirname $s) | sed 's/state//')
        if [ $idx -gt $max_cs ]; then
            echo 1 > $s
        fi
    done
    # 전력 측정 (10초)
    perf stat -e power/energy-pkg/ -- sleep 10 2>&1 | grep Joules
    # 지연 측정
    cyclictest -m -p 99 -i 1000 -l 10000 2>&1 | grep "Max Latencies"
done

C-state Residency 분석

# 각 C-state의 평균 체류 시간 계산
for cpu in /sys/devices/system/cpu/cpu0/cpuidle/state*; do
    name=$(cat $cpu/name)
    usage=$(cat $cpu/usage)
    time=$(cat $cpu/time)
    if [ $usage -gt 0 ]; then
        avg=$((time / usage))
        echo "$name: usage=$usage, total_time=${time}us, avg_residency=${avg}us"
    fi
done
# POLL: usage=12345, total_time=5678us, avg_residency=0us
# C1:   usage=234567, total_time=890123us, avg_residency=3us
# C1E:  usage=456789, total_time=12345678us, avg_residency=27us
# C6:   usage=78901, total_time=987654321us, avg_residency=12518us

# above/below 분석: governor 예측 정확도
for cpu in /sys/devices/system/cpu/cpu0/cpuidle/state*; do
    name=$(cat $cpu/name)
    above=$(cat $cpu/above)
    below=$(cat $cpu/below)
    usage=$(cat $cpu/usage)
    echo "$name: usage=$usage, above=$above (너무 깊었음), below=$below (너무 얕았음)"
done
# above가 높으면: governor가 너무 깊은 C-state를 자주 선택
# below가 높으면: governor가 너무 얕은 C-state를 자주 선택

벤치마크 시 주의사항

  • 워밍업: C-state 변경 후 최소 30초 이상 대기 후 측정 시작 (통계 안정화)
  • 반복 측정: 최소 3회 이상 반복하여 중앙값 사용
  • 격리: 측정 중 불필요한 프로세스/인터럽트 최소화 (isolcpus, irqaffinity)
  • 써멀 영향: idle=poll 측정 시 써멀 스로틀링이 결과를 왜곡할 수 있음
  • RAPL 정확도: RAPL은 평균 전력을 보고하므로, 순간 전력 변동은 반영 안 됨

CPUIdle 드라이버 작성 가이드

커스텀 하드웨어용 CPUIdle 드라이버를 작성하는 방법입니다.

최소 CPUIdle 드라이버

/* 최소 CPUIdle 드라이버 예제 */
#include <linux/cpuidle.h>
#include <linux/module.h>

/* C-state 진입 콜백 */
static int my_idle_enter(struct cpuidle_device *dev,
                         struct cpuidle_driver *drv,
                         int index)
{
    /* C1: 단순 HLT */
    if (index == 1)
        cpu_do_idle();  /* HLT 또는 WFI */

    /* C2: 하드웨어 특정 저전력 진입 */
    if (index == 2)
        my_hw_enter_deep_idle();

    return index;
}

static struct cpuidle_driver my_idle_driver = {
    .name  = "my_idle",
    .owner = THIS_MODULE,
    .states = {
        {  /* State 0: POLL (필수) */
            .name            = "POLL",
            .desc            = "CPUIDLE CORE POLL IDLE",
            .exit_latency    = 0,
            .target_residency = 0,
            .flags           = CPUIDLE_FLAG_POLLING,
            .enter           = NULL,  /* 코어가 처리 */
        },
        {  /* State 1: C1 */
            .name            = "C1",
            .desc            = "HLT",
            .exit_latency    = 5,
            .target_residency = 10,
            .enter           = my_idle_enter,
        },
        {  /* State 2: C2 (깊은 idle) */
            .name            = "C2",
            .desc            = "Deep power down",
            .exit_latency    = 100,
            .target_residency = 300,
            .flags           = CPUIDLE_FLAG_TIMER_STOP,
            .enter           = my_idle_enter,
        },
    },
    .state_count = 3,
    .safe_state_index = 1,
};

static int __init my_idle_init(void)
{
    return cpuidle_register(&my_idle_driver, NULL);
}

static void __exit my_idle_exit(void)
{
    cpuidle_unregister(&my_idle_driver);
}

module_init(my_idle_init);
module_exit(my_idle_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Example CPUIdle driver");

드라이버 작성 체크리스트

항목필수설명
POLL state (index 0)권장polling 폴백 상태, exit_latency=0
exit_latency 정확성필수하드웨어 측정 기반 값 사용. 부정확하면 governor 오판
target_residency필수exit_latency의 2~5배가 일반적 (break-even)
TIMER_STOP 플래그조건부로컬 타이머가 정지되는 C-state에 설정
TLB_FLUSHED 플래그조건부TLB가 무효화되는 C-state에 설정
enter_s2idle 콜백선택s2idle 지원 시 구현 (가장 깊은 상태)
enter_dead 콜백선택CPU offline 시 호출되는 복귀 불가 진입
cpumask 설정선택per-CPU 드라이버 시 CPU 마스크 지정

exit_latency 측정 팁

정확한 exit_latency 값은 드라이버 품질의 핵심입니다. 측정 방법:

  • IPI 기반: 타겟 CPU를 idle에 진입시킨 후, 다른 CPU에서 IPI를 보내 응답 시간 측정
  • GPIO 토글: idle 진입 직전 GPIO 핀을 낮추고, 인터럽트 핸들러에서 높여 오실로스코프로 측정
  • 하드웨어 타이머: CPU 외부 타이머(예: SoC 타이머)로 idle 기간 측정
  • 측정 값에 20~50% 마진을 추가하여 안전 값으로 설정하는 것이 관례
CPUIdle 드라이버 등록 시퀀스 Driver (module) CPUIdle Core Governor cpuidle_register_driver() C-state 테이블 검증 cpuidle_register_device() x N sysfs 노드 생성 cpuidle_enable_device() select()/reflect() 시작 select() → index 반환 states[index].enter() return entered_state reflect(entered_state)

s2idle 내부 동작 심화

s2idle(Suspend-to-Idle)이 실제로 진입하는 과정은 cpuidle_enter_s2idle() 경로를 통해 진행됩니다. 일반적인 CPUIdle 경로와 달리, s2idle은 모든 디바이스 드라이버를 먼저 suspend한 뒤 각 CPU가 가장 깊은 C-state로 진입하며, 이때 드라이버의 enter_s2idle 콜백이 별도로 호출됩니다.

/* drivers/cpuidle/cpuidle.c - s2idle 전용 진입 경로 */
int cpuidle_enter_s2idle(struct cpuidle_driver *drv,
                         struct cpuidle_device *dev)
{
    int index;

    /*
     * s2idle에서는 governor 선택을 우회하고
     * 드라이버가 제공하는 가장 깊은 상태를 직접 사용.
     * enter_s2idle 콜백이 정의된 가장 깊은 state 선택
     */
    for (index = drv->state_count - 1; index >= 0; index--) {
        if (drv->states[index].enter_s2idle)
            break;
    }

    if (index < 0)
        return -ENODEV;

    /* 일반 enter() 대신 enter_s2idle() 콜백 호출 */
    drv->states[index].enter_s2idle(dev, drv, index);
    return 0;
}

Intel 플랫폼에서 intel_idle 드라이버는 enter_s2idle 콜백에서 MWAIT에 특수 힌트를 전달하여 패키지 전체를 S0ix 상태로 진입시킵니다. 이 과정에서 플랫폼 펌웨어(PMC)와 협력하여 전력 게이팅을 수행합니다.

/* drivers/idle/intel_idle.c - s2idle용 MWAIT 힌트 */
static void intel_idle_s2idle(struct cpuidle_device *dev,
                               struct cpuidle_driver *drv, int index)
{
    unsigned long ecx = 1;  /* MWAIT 인터럽트 break 플래그 */
    unsigned long eax = flg2MWAIT(drv->states[index].flags);

    /*
     * s2idle 경로에서의 MWAIT:
     * - 인터럽트 비활성화 상태에서 호출
     * - PMC가 모든 코어의 MWAIT 상태를 확인
     * - 모든 코어가 deep C-state에 있으면 S0ix 진입
     */
    mwait_idle_with_hints(eax, ecx);
}

s2idle 진입 실패 원인

s2idle이 예상 전력 수준에 도달하지 못하는 일반적인 원인:

  • EC (Embedded Controller): EC가 SCI를 지속적으로 발생시켜 CPU가 깨어남
  • ACPI GPE: General Purpose Event가 주기적으로 트리거됨 — /sys/firmware/acpi/interrupts/에서 확인
  • 네트워크 디바이스: WoL(Wake-on-LAN) 설정이 D3 진입을 방해
  • 오디오 코덱: HD Audio 컨트롤러가 D0 상태에 고정되면 패키지 C-state 진입 불가
# s2idle 전력 상태 검증 (Intel 플랫폼)
# SLP_S0 카운터: PMC가 S0ix에 실제 진입했는지 확인
cat /sys/kernel/debug/pmc_core/slp_s0_residency_usec

# s2idle 전후 비교
echo s2idle > /sys/power/mem_sleep
BEFORE=$(cat /sys/kernel/debug/pmc_core/slp_s0_residency_usec)
rtcwake -m freeze -s 10   # 10초 후 자동 웨이크업
AFTER=$(cat /sys/kernel/debug/pmc_core/slp_s0_residency_usec)
echo "S0ix residency: $((AFTER - BEFORE)) us"

# 0이면 S0ix에 전혀 진입하지 못한 것
# PMC 서브시스템 블록 원인 확인
cat /sys/kernel/debug/pmc_core/substate_requirements

PM QoS 고급 제약 패턴

PM QoS는 전역(global) 제약 외에도 per-deviceper-CPU 수준의 세밀한 제어를 지원합니다. 커널 6.x에서는 dev_pm_qos 프레임워크를 통해 디바이스별 resume latency 제약을 설정할 수 있습니다.

/* per-device PM QoS: 특정 디바이스의 resume latency 제한 */
#include <linux/pm_qos.h>

static void set_device_latency(struct device *dev)
{
    /*
     * 디바이스의 resume latency를 100us로 제한
     * 이 제약은 해당 디바이스의 부모 도메인(power domain)에도 전파됨
     * → genpd(Generic Power Domain) 레벨에서 C-state 선택에 영향
     */
    dev_pm_qos_add_request(dev, &dev->power.qos->resume_latency_req,
                           DEV_PM_QOS_RESUME_LATENCY, 100);
}

/* sysfs를 통한 per-device 제약 (사용자 공간) */
/* echo 100 > /sys/devices/.../power/pm_qos_resume_latency_us */
PM QoS 유형인터페이스영향 범위사용 예
CPU Latency (전역)/dev/cpu_dma_latency시스템 전체 C-state실시간 오디오, HFT 트레이딩
Per-CPU Latencycpu_latency_qos_*특정 CPU의 C-stateIRQ affinity 코어 보호
Device Resume Latencydev_pm_qos_*디바이스 power domainNVMe SSD 응답 보장
Device Latency Tolerancedev_pm_qos_*디바이스 자체 지연 허용네트워크 카드 패킷 지연
# PM QoS 현재 상태 전체 확인
# 전역 CPU latency 요청 목록
cat /sys/kernel/debug/pm_qos/cpu_dma_latency/requested_value

# per-device resume latency 확인 (NVMe 예시)
find /sys/devices -name pm_qos_resume_latency_us -exec sh -c \
    'echo "$1: $(cat $1)"' _ {} \;

# PM QoS 제약이 C-state에 미치는 영향 실시간 관찰
bpftrace -e '
tracepoint:power:pm_qos_update {
    printf("PM QoS update: action=%d value=%d\n",
           args->action, args->value);
}'

PM QoS와 실시간 워크로드

실시간 워크로드에서는 /dev/cpu_dma_latency에 0을 기록하여 모든 C-state를 비활성화(POLL 모드 강제)하는 것이 일반적입니다. 그러나 이 방법은 전력 소비가 급격히 증가하므로, 가능하면 적절한 지연값(예: 10~50us)을 설정하여 C1/C1E 수준의 절전은 허용하는 것이 권장됩니다.

커널 빌드 고급 설정

CPUIdle 관련 커널 빌드 시 일반적인 Kconfig 외에도 디버그 옵션과 플랫폼별 최적화 옵션이 있습니다.

# 디버그용 추가 설정
CONFIG_PM_DEBUG=y              # 전원 관리 디버그 메시지
CONFIG_PM_ADVANCED_DEBUG=y     # 고급 PM 디버그 sysfs
CONFIG_PM_SLEEP_DEBUG=y        # 슬립/웨이크업 디버깅
CONFIG_PM_TRACE=y              # 전원 상태 트레이싱
CONFIG_FTRACE=y                # 함수 트레이싱 (power 이벤트)
CONFIG_TRACING=y               # 트레이싱 프레임워크

# ARM/ARM64 플랫폼 CPUIdle 드라이버
CONFIG_ARM_CPUIDLE=y           # ARM 일반 CPUIdle 드라이버
CONFIG_ARM_PSCI_CPUIDLE=y      # PSCI 기반 (ARMv8+)
CONFIG_ARM_BIG_LITTLE_CPUIDLE=y # big.LITTLE 유휴 관리

# RISC-V 플랫폼
CONFIG_RISCV_SBI_CPUIDLE=y     # SBI 기반 CPUIdle

# 가상화 환경
CONFIG_HALTPOLL_CPUIDLE=y      # KVM 게스트 haltpoll 드라이버
CONFIG_KVM_GUEST=y             # haltpoll 의존성
# 현재 커널의 CPUIdle 관련 설정 확인
zcat /proc/config.gz 2>/dev/null | grep -i "cpu_idle\|cpuidle\|intel_idle\|no_hz\|pm_qos" \
    || grep -i "cpu_idle\|cpuidle\|intel_idle\|no_hz\|pm_qos" /boot/config-$(uname -r)

# 특정 옵션 포함 여부 확인
scripts/config --file .config --state CPU_IDLE_GOV_TEO
# y, m, n, 또는 undef 중 하나 출력

# 디버그 빌드 일괄 활성화
scripts/config --file .config \
    --enable PM_DEBUG \
    --enable PM_ADVANCED_DEBUG \
    --enable PM_SLEEP_DEBUG \
    --enable FTRACE

빌드 시 주의사항

  • CONFIG_INTEL_IDLE=yCONFIG_ACPI_PROCESSOR_IDLE=y가 모두 활성화된 경우, intel_idle이 우선 로드됩니다. intel_idle.max_cstate=0 부팅 파라미터로 비활성화하면 acpi_idle로 폴백합니다.
  • CONFIG_NO_HZ_FULL=y를 사용하면 CPUIdle governor는 menu가 강제 선택됩니다. ladder는 tick 기반이므로 tickless 환경에서 정상 동작하지 않습니다.
  • 디버그 옵션 활성화 시 성능 오버헤드가 있으므로, 프로덕션 커널에서는 비활성화하는 것이 좋습니다.