전원 관리 (Power Management)

Linux 커널 PM 서브시스템의 전체 제어 경로를 심층 정리합니다. 시스템 절전(s2idle/S3/S4) 상태 전이와 wakeup 흐름, runtime PM(pm_runtime/autosuspend), dev_pm_ops·genpd(PM 도메인), PM QoS·wakeup source, OPP/DVFS·cpufreq·cpuidle·thermal 연동, Energy Model/EAS 정책, powercap/RAPL·Regulator, 시스템 종료/재부팅, suspend/resume 디버깅(pm_test·trace)까지 운영 관점으로 다룹니다.

관련 표준: ACPI 6.5 (전원 상태 G/S/D/C/P-states), Intel SDM (전력 관리 MSR) — 커널 PM 서브시스템이 구현하는 전원 관리 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.
교차 참조: ACPI G/S/D-state 및 열 관리(Thermal Management)는 ACPI, cpufreq/governors/P-State은 ktime / Clock, 전력 MSR/HWP는 MSR 레지스터(Register), PCI D-state/ASPM은 PCI / PCIe 페이지(Page)를 참조하십시오.
전제 조건: 전원관리Thermal 서브시스템 문서를 먼저 읽으세요. 전력/열 관리 주제는 성능과 안정성의 절충이 핵심이므로, 제어 루프와 임계값 정책을 함께 이해해야 운영 사고를 줄일 수 있습니다.

핵심 요약

  • Suspend(S3) — RAM에 상태를 유지한 채 CPU/디바이스 전원을 끄는 절전 모드(Suspend)입니다.
  • Hibernate(S4) — RAM 내용을 디스크에 저장하고 완전 전원 차단. 복원 시 디스크에서 복구합니다.
  • 런타임 PM — 개별 디바이스를 사용하지 않을 때 자동으로 저전력 상태로 전환합니다.
  • cpuidle — idle CPU를 C-state(C0→C1→C3...)로 전환하여 전력을 절약합니다.
  • dev_pm_ops — 드라이버가 구현하는 PM 콜백(Callback) 구조체(Struct). suspend/resume 등의 동작을 정의합니다.

단계별 이해

  1. 시스템 절전 체험systemctl suspend로 시스템을 S3 절전에 진입시킵니다.

    전원 버튼이나 키보드로 깨우면 모든 상태가 복원됩니다.

  2. 절전 상태 확인cat /sys/power/state로 지원되는 절전 상태를 확인합니다.

    freeze(s2idle), mem(S3), disk(hibernate) 등이 표시됩니다.

  3. 런타임 PM 확인/sys/bus/pci/devices/*/power/runtime_status로 각 PCI 디바이스의 PM 상태를 볼 수 있습니다.

    active, suspended, unsupported 중 하나입니다.

  4. 드라이버 관점 — 드라이버는 dev_pm_ops.suspend/.resume 콜백을 구현하여 절전/복원 동작을 정의합니다.

    하드웨어 레지스터 저장/복원, DMA 중단, 인터럽트(Interrupt) 비활성화 등을 수행합니다.

PM 서브시스템 아키텍처 개요

Linux 커널의 전원 관리(PM) 코드는 여러 디렉토리에 분산되어 있으며, 시스템 레벨과 디바이스 레벨의 두 축으로 구성됩니다.

소스 트리 구조

경로역할
kernel/power/시스템 절전(suspend/hibernate), PM 코어, wakeup
drivers/base/power/디바이스 PM 인프라: dev_pm_ops, 런타임 PM, 클럭 PM
drivers/cpuidle/cpuidle 프레임워크, 거버너, 드라이버
drivers/opp/Operating Performance Points(OPP) 프레임워크
drivers/powercap/powercap 프레임워크, Intel/AMD RAPL
drivers/regulator/Regulator(전압/전류) 프레임워크
drivers/cpufreq/CPU 주파수 스케일링 (→ ktime/Clock)
drivers/thermal/열 관리 (→ ACPI)

핵심 구조체

/* include/linux/pm.h */
struct dev_pm_ops {
    int (*prepare)(struct device *dev);
    void (*complete)(struct device *dev);
    int (*suspend)(struct device *dev);
    int (*resume)(struct device *dev);
    int (*freeze)(struct device *dev);
    int (*thaw)(struct device *dev);
    int (*poweroff)(struct device *dev);
    int (*restore)(struct device *dev);
    int (*suspend_late)(struct device *dev);
    int (*resume_early)(struct device *dev);
    int (*freeze_late)(struct device *dev);
    int (*thaw_early)(struct device *dev);
    int (*poweroff_late)(struct device *dev);
    int (*restore_early)(struct device *dev);
    int (*suspend_noirq)(struct device *dev);
    int (*resume_noirq)(struct device *dev);
    int (*freeze_noirq)(struct device *dev);
    int (*thaw_noirq)(struct device *dev);
    int (*poweroff_noirq)(struct device *dev);
    int (*restore_noirq)(struct device *dev);
    int (*runtime_suspend)(struct device *dev);
    int (*runtime_resume)(struct device *dev);
    int (*runtime_idle)(struct device *dev);
};

/* include/linux/suspend.h */
struct platform_suspend_ops {
    int (*valid)(suspend_state_t state);
    int (*begin)(suspend_state_t state);
    int (*prepare)(void);
    int (*enter)(suspend_state_t state);
    void (*wake)(void);
    void (*finish)(void);
    void (*end)(void);
};

PM 서브시스템 계층 다이어그램

Userspace: /sys/power/ · powertop · turbostat · systemctl suspend System Sleep cpuidle PM QoS Energy Model powercap PM Core (kernel/power/ + drivers/base/power/) dev_pm_ops Runtime PM genpd OPP Regulator Wakeup Sources DPM List Clock PM cpufreq Thermal Platform / Hardware ACPI S-states C-states (MWAIT) P-states (HWP) PMIC / Regulators

시스템 절전 상태 (System Sleep)

Linux 커널은 세 가지 시스템 절전 상태를 지원하며, /sys/power/state를 통해 진입합니다.

절전 상태 매핑(Mapping)

/sys/power/state내부 상태ACPI 매핑설명
freezePM_SUSPEND_TO_IDLE (s2idle)S0 (S0ix)프로세스(Process) 동결 + 디바이스 저전력 + CPU idle. 가장 빠른 진입/복귀
memPM_SUSPEND_MEM (S2RAM)S3RAM 자체 리프레시 유지, 나머지 전원 차단. mem_sleep_default로 s2idle 대체 가능
diskPM_SUSPEND_DISK (hibernate)S4메모리 이미지를 swap에 기록 후 전원 차단. resume= 파라미터로 복원
mem_sleep_default: 최신 플랫폼(Intel Tiger Lake+, AMD Zen 3+)은 S3를 지원하지 않고 s2idle(S0ix)만 지원하는 경우가 많습니다. /sys/power/mem_sleep에서 현재 설정을 확인할 수 있습니다.

Suspend 진입 흐름

echo mem > /sys/power/state pm_suspend(state) suspend_prepare() 프로세스 동결 (freeze_processes) suspend_devices_and_enter() dpm_suspend_start() prepare → suspend 콜백 dpm_suspend_late() suspend_late 콜백 dpm_suspend_noirq() IRQ 비활성 후 suspend_noirq suspend_enter() platform→enter() / CPU offline ← 하드웨어 절전 → 웨이크업 이벤트 →

Resume 경로는 역순으로 진행됩니다: resume_noirq → resume_early → resume → complete. 각 단계에서 에러 발생 시 해당 지점부터 rollback이 이루어집니다.

pm_suspend() 호출 체인 분석

사용자가 echo mem > /sys/power/state를 실행하면, sysfs의 state_store() 핸들러에서 시작하여 최종적으로 플랫폼별 suspend_ops->enter()까지 도달합니다. 아래는 커널 소스(kernel/power/suspend.c 기준)의 핵심 호출 경로입니다.

state_store() → sysfs write 핸들러 kernel/power/main.c pm_suspend(state) kernel/power/suspend.c PM_SUSPEND_MEM / PM_SUSPEND_TO_IDLE enter_state(state) 유효성 검사 + 통계 기록 suspend_prepare(state) PM notifier + freeze_processes() suspend_devices_and_enter(state) 핵심 디바이스 suspend 루프 dpm_suspend(state) 전체 디바이스 suspend 콜백 dpm_suspend_late/noirq() late + noirq 단계 suspend_enter(state, &wakeup) non-boot CPU offline suspend_ops->enter(state) ACPI/PSCI/EFI 플랫폼 진입 ← 하드웨어 절전 (S3/s2idle) → wakeup IRQ →
/* kernel/power/suspend.c — pm_suspend() 핵심 경로 (간략화) */

int pm_suspend(suspend_state_t state)
{
    int error;

    if (state == PM_SUSPEND_TO_IDLE)
        s2idle_begin();                     /* s2idle 전용 초기화 */

    error = enter_state(state);

    if (state == PM_SUSPEND_TO_IDLE)
        s2idle_end();
    return error;
}

static int enter_state(suspend_state_t state)
{
    int error;

    if (state == PM_SUSPEND_MEM) {
        /* suspend_ops 유효성 검사 */
        if (!suspend_ops || !suspend_ops->enter)
            return -ENOSYS;
    }

    trace_suspend_resume(TPS("freeze_processes"), 0, true);
    error = suspend_prepare(state);       /* PM notifier 호출 + 프로세스 동결 */
    if (error)
        goto Unlock;

    error = suspend_devices_and_enter(state); /* 디바이스 suspend + 플랫폼 진입 */

    suspend_finish();                    /* thaw_processes + PM notifier 복원 */
Unlock:
    return error;
}
코드 설명
  • pm_suspend(): 시스템 suspend의 최상위 진입점입니다. s2idle(S0ix) 모드일 경우 별도의 s2idle_begin()/s2idle_end()로 감싸서 idle 기반 절전을 처리합니다.
  • enter_state(): 실제 절전 진입 로직을 수행합니다. 먼저 suspend_ops(플랫폼이 등록한 콜백 구조체)의 유효성을 검사하고, suspend_prepare()로 프로세스를 동결한 뒤, suspend_devices_and_enter()로 디바이스 suspend와 플랫폼 진입을 수행합니다.
  • suspend_prepare(): PM_SUSPEND_PREPARE notifier를 호출하여 사용자 공간 데몬에게 suspend를 알리고, freeze_processes()로 모든 사용자 프로세스와 커널 스레드를 동결합니다.
  • suspend_finish(): resume 후 thaw_processes()로 동결을 해제하고 PM_POST_SUSPEND notifier를 호출합니다.
  • suspend_ops->enter(): x86에서는 ACPI의 acpi_suspend_enter(), ARM에서는 PSCI의 psci_system_suspend()가 호출되어 실제 하드웨어 절전 상태에 진입합니다.

Hibernate 흐름

/* Hibernate 진입 */
echo disk > /sys/power/state
  → hibernate()
    → freeze_processes()
    → dpm_suspend_start(PMSG_FREEZE)       /* 디바이스 freeze */create_image()                        /* 메모리 스냅샷 생성 */dpm_resume_end(PMSG_THAW)             /* 디바이스 복원 (이미지 쓰기용) */swsusp_write()                        /* swap 파티션에 이미지 기록 */dpm_suspend_start(PMSG_POWEROFF)      /* 디바이스 poweroff */hibernation_platform_enter()          /* ACPI S4 진입 */

/* Hibernate 복원 (부팅 시) */
커널 부팅 → resume=/dev/sdaX 파라미터 감지
  → swsusp_read()                          /* swap에서 이미지 로드 */restore_image()                        /* 메모리 복원 + 점프 */

주요 /sys/power/ 인터페이스

파일설명
/sys/power/state절전 상태 진입 (freeze, mem, disk)
/sys/power/mem_sleepmem 상태의 실제 모드 (s2idle, [deep])
/sys/power/diskhibernate 모드 (platform, shutdown, reboot, suspend, test_resume)
/sys/power/image_sizehibernate 이미지 목표 크기 (기본 RAM의 2/5)
/sys/power/wakeup_countwakeup 이벤트 카운터 (spurious wakeup 방지)
/sys/power/pm_testsuspend 테스트 모드 (none, core, processors, platform, devices, freezer)
/sys/power/suspend_statssuspend 성공/실패 통계

Suspend-to-RAM (S3) 상세 흐름

사용자가 echo mem > /sys/power/state 또는 systemctl suspend를 실행하면, 커널은 다단계 절차를 거쳐 시스템을 S3 상태로 진입시킵니다.

Suspend-to-RAM (S3) 단계 요약 사용자 트리거 `echo mem > /sys/power/state` → `pm_suspend(PM_SUSPEND_MEM)` 1단계: 준비 `suspend_prepare()` / PM notifier → `freeze_processes()` + `freeze_kernel_threads()` 콘솔 준비 2단계: 디바이스 suspend `dpm_prepare()` → `dpm_suspend()` (leaf → root) / DMA 정지, 레지스터 저장 `suspend_console()`, `platform_suspend_begin()`, 클럭 게이팅 3단계: noirq 전환 `dpm_suspend_late()` → `dpm_suspend_noirq()` `disable_nonboot_cpus()`, `arch_suspend_disable_irqs()`, `syscore_suspend()` 4단계: 실제 S3 진입 `suspend_ops->enter()` / ACPI `acpi_suspend_enter()` — PM1a/PM1b SLP_EN 설정 ── 시스템 절전 상태 ── 외부 wakeup: 전원 버튼 / RTC / LAN / GPIO 5단계: 저수준 복귀 펌웨어 wakeup 벡터 → `syscore_resume()` / `dpm_resume_noirq()` / IRQ 재활성화 보조 CPU 온라인 재진입 6단계: 디바이스 복원 `dpm_resume_early()` → `dpm_resume()` (root → leaf) 클럭/인터럽트/레지스터 복원, I/O 재시작, `dpm_complete()` Suspend 완료 및 사용자 공간 복귀 `thaw_processes()` / `resume_console()` / PM_POST_SUSPEND notifier SUSPEND RESUME 트리거 → 준비 → 디바이스 정지 → noirq → S3 진입 → wakeup → 복귀 → 복원 → 완료
Wakeup 벡터: S3 진입 전 커널은 FACS(Firmware ACPI Control Structure)의 firmware_waking_vector 필드에 resume 진입점(Entry Point) 주소를 기록합니다. CPU가 리셋될 때 펌웨어(Firmware)는 이 주소로 점프하여 커널 resume 코드를 실행합니다. x86_64에서는 리얼 모드 → 보호 모드 → 롱 모드 전환을 다시 수행합니다.

s2idle (Modern Standby / S0ix)

S3보다 빠른 복귀를 위한 소프트웨어 기반 절전 방식입니다. ACPI S3 없이 커널이 직접 저전력 idle 상태를 관리합니다. Intel 플랫폼에서는 C10 idle state + S0ix 패키지 상태를 활용합니다.

s2idle을 mem의 기본으로 설정하려면 echo s2idle > /sys/power/mem_sleep 명령을 사용합니다.

S3 흐름
suspend_ops->enter() → 펌웨어가 전원을 관리합니다.
s2idle 흐름
freeze_enter() → cpuidle으로 깊은 idle에 진입합니다. 주기적 tick freeze와 디바이스 Runtime PM을 활용합니다.

/sys/power/mem_sleep 출력 해석:

Hibernate (S4) 상세 흐름

Hibernate는 전체 시스템 메모리를 디스크에 저장하고 전원을 완전히 차단합니다. 전원이 복구되면 디스크에서 메모리 이미지를 복원하여 suspend 직전 상태로 되돌립니다.

Hibernate (S4) 전체 흐름 요약 트리거: `echo disk > /sys/power/state` → `hibernate()` kernel/power/hibernate.c + swap/snapshot 경로 1단계: 준비 `PM_HIBERNATION_PREPARE` notifier → `freeze_processes()` / swap 대상 확인 2단계: 스냅샷 생성 `create_image()` + `dpm_suspend_start()` + `syscore_suspend()` `swsusp_arch_suspend()`에서 CPU 레지스터/메모리 페이지 캡처 → 임시 resume 후 디스크 기록 3단계: 디스크 기록 + 전원 차단 `swsusp_write()` → LZO/LZ4 압축 + `S1SUSPEND` 서명 / swap page 0에 메타데이터 `hibernate_ops->enter()`(S4) 또는 poweroff/reboot 전원 완전 차단 → 재부팅 (`resume=` 파라미터) HIBERNATE RESUME R1: Resume 탐지 `software_resume()` → `swsusp_check()`로 시그니처/메타데이터 검증 R2: 이미지 로드 `swsusp_read()` → swap에서 이미지 읽기 → 압축 해제 후 페이지 복원 준비 R3: 아키텍처 복원 핵심 `swsusp_arch_resume()` → 원래 물리 페이지/레지스터 복원 `swsusp_arch_suspend()` 반환값 1 → 복원 경로 진입 → hibernate 직전 문맥으로 회귀 R4: 후처리 `syscore_resume()` / `dpm_resume_end()` / `thaw_processes()` → hibernate 직전 상태 복귀 트리거 → 준비 → 스냅샷 → 디스크 기록 → 전원 차단 → 재부팅 → 탐지 → 로드 → 복원 → 후처리
Hibernate 필수 설정:
  • swap 파티션/파일 크기 ≥ 사용 중인 RAM (free -h의 used 기준)
  • 커널 파라미터: resume=/dev/sdXN 또는 resume=UUID=...
  • initramfs에 resume 모듈 포함: /etc/initramfs-tools/conf.d/resume
  • swap 파일 사용 시: resume_offset= 파라미터 추가 필요 (filefrag -v로 오프셋(Offset) 확인)
Hibernate 이미지 구조 swap page 0: swsusp_info (메타데이터) - "S1SUSPEND" 서명 (8바이트), 커널 버전, 이미지 크기 - CRC32 체크섬, 첫 데이터 페이지 오프셋 - resume 시 유효성 검사에 사용 swap page 1~N: 압축된 메모리 이미지 - LZO/LZ4 압축, 멀티스레드 압축/해제 지원 - PFN(Page Frame Number) 매핑 포함 - 클린 페이지 제외로 실제 저장 크기 축소 운영 시 조정 포인트 - 이미지 상한: /sys/power/image_size (기본: RAM의 2/5) - 압축 선택: CONFIG_HIBERNATION_COMP_LZO/LZ4 - TuxOnIce 사용 시: echo lz4 > /sys/power/tuxonice/compressor - swap 크기는 사용 메모리 이상을 권장

cpuidle 프레임워크

CPU가 실행할 작업이 없을 때, 커널은 cpuidle 프레임워크를 통해 적절한 저전력 상태(C-state)로 진입합니다. 깊은 C-state일수록 전력 절감이 크지만 복귀 지연(exit latency)도 증가합니다.

핵심 구조체

/* include/linux/cpuidle.h */
struct cpuidle_state {
    char  name[16];           /* C-state 이름 (예: "C1", "C6S") */
    char  desc[32];           /* 설명 */
    s64   exit_latency_ns;      /* 복귀 지연 (나노초) */
    s64   target_residency_ns;  /* 최소 체류 시간 */
    unsigned int flags;        /* CPUIDLE_FLAG_* */
    int (*enter)(struct cpuidle_device *dev,
               struct cpuidle_driver *drv, int index);
};

struct cpuidle_driver {
    const char *name;
    struct cpuidle_state states[CPUIDLE_STATE_MAX];
    int state_count;
    int safe_state_index;      /* BM-safe fallback state */
};

거버너 비교

거버너알고리즘적합 환경
menu예상 idle 시간 + 보정 계수 + PM QoS 제약범용 (기본값)
TEO (Timer Events Oriented)최근 타이머(Timer) 이벤트 패턴 분석, 깊은 상태 적극 선택서버, 주기적 워크로드
ladderC-state를 단계적으로 승격/강등틱 기반(주로 레거시)
haltpoll짧은 busy-poll 후 halt, VM 게스트 최적화KVM 게스트

cpuidle 드라이버

드라이버플랫폼C-state 소스
intel_idleIntel 프로세서하드코딩된 MWAIT 힌트 테이블 (CPUID 기반)
acpi_idleACPI 지원 시스템ACPI _CST 메서드 (FADT C-state 정보)
psci_idle (ARM)ARM64 플랫폼PSCI cpu_suspend + DT idle-states

cpuidle 아키텍처 다이어그램

스케줄러 idle thread cpuidle_enter() Governor (menu/TEO) → 최적 C-state 선택 PM QoS 제약 확인 Driver (intel_idle) → HW 진입 (MWAIT) CPU Hardware C-state (C0 → C1 → C6 → C10)

sysfs 인터페이스

CPU cpuidle sysfs 인터페이스 /sys/devices/system/cpu/cpu0/cpuidle/state{N}/ 파일명 설명 name C-state 이름 (예: C1E, C6) desc C-state 설명 문자열 latency 복귀 지연 시간 (μs) — C-state가 깊을수록 큼 residency 이 상태 진입 최소 체류 시간 (μs) usage 진입 횟수 (부팅 이후 누적) time 총 체류 시간 (μs, 부팅 이후 누적) disable 0=활성, 1=비활성화 (echo 1 > disable) above / below 이 상태보다 깊은/얕은 상태가 선택된 횟수 cpupower idle-info / powertop 으로 C-state 사용 현황 모니터링

런타임 PM

런타임 PM은 시스템이 동작 중일 때 개별 디바이스의 전원을 동적으로 관리합니다. 사용하지 않는 디바이스를 자동으로 suspend하여 전력을 절감합니다.

상태 전환 다이어그램

RPM_ACTIVE RPM_SUSPENDING RPM_SUSPENDED RPM_RESUMING runtime_suspend() 콜백 성공 runtime_resume() 콜백 성공 abort

pm_runtime_* API

함수동작
pm_runtime_enable(dev)런타임 PM 활성화 (초기 usage_count=1이므로 필수)
pm_runtime_get_sync(dev)usage_count++ 후 동기 resume (에러 시 put 필요)
pm_runtime_put_sync(dev)usage_count-- 후 즉시 idle 검사 → suspend
pm_runtime_put_autosuspend(dev)usage_count-- 후 autosuspend 타이머 시작
pm_runtime_set_autosuspend_delay(dev, ms)autosuspend 지연 시간 설정
pm_runtime_use_autosuspend(dev)autosuspend 모드 활성화
pm_runtime_mark_last_busy(dev)마지막 사용 시각 갱신 (autosuspend 타이머 리셋)
pm_runtime_get_noresume(dev)usage_count++만 (resume 안 함)
pm_runtime_put_noidle(dev)usage_count--만 (idle 검사 안 함)
pm_runtime_forbid(dev)런타임 suspend 금지
pm_runtime_allow(dev)런타임 suspend 허용

Autosuspend 패턴

/* 디바이스 사용 시 (예: I/O 요청) */
pm_runtime_get_sync(dev);        /* resume + refcount++ */
/* ... 실제 작업 ... */
pm_runtime_mark_last_busy(dev);  /* 타이머 리셋 */
pm_runtime_put_autosuspend(dev); /* refcount--, delay 후 suspend */

Runtime PM 내부 경로 분석

pm_runtime_get_sync()를 호출하면 내부적으로 rpm_resume()이 실행되어 디바이스를 활성화합니다. 반대로 pm_runtime_put()rpm_idle()rpm_suspend() 경로를 거칩니다.

/* drivers/base/power/runtime.c — rpm_resume() 핵심 경로 (간략화) */

static int rpm_resume(struct device *dev, int rpmflags)
{
    int retval = 0;

    spin_lock_irq(&dev->power.lock);

    /* 이미 active이면 즉시 반환 */
    if (dev->power.runtime_status == RPM_ACTIVE) {
        retval = 1;
        goto out;
    }

    /* 부모 디바이스가 있으면 부모를 먼저 resume */
    if (dev->parent) {
        spin_unlock(&dev->power.lock);
        pm_runtime_get_noresume(dev->parent);
        retval = rpm_resume(dev->parent, 0);   /* 재귀: 부모 resume */
        if (retval)
            goto out;
        spin_lock(&dev->power.lock);
    }

    dev->power.runtime_status = RPM_RESUMING;
    spin_unlock_irq(&dev->power.lock);

    /* PM 도메인 또는 드라이버의 runtime_resume 콜백 호출 */
    if (dev->pm_domain)
        retval = pm_generic_runtime_resume(dev);
    else
        retval = callback(dev);                /* dev->driver->pm->runtime_resume */

    spin_lock_irq(&dev->power.lock);

    if (retval == 0) {
        dev->power.runtime_status = RPM_ACTIVE;
        dev->power.runtime_error = 0;
    } else {
        dev->power.runtime_status = RPM_SUSPENDED;
        dev->power.runtime_error = retval;
    }
out:
    spin_unlock_irq(&dev->power.lock);
    return retval;
}
코드 설명
  • 상태 확인: 이미 RPM_ACTIVE이면 콜백을 호출하지 않고 즉시 반환합니다. 이미 진행 중(RPM_RESUMING)이면 완료를 대기합니다.
  • 부모 먼저 resume: 디바이스 트리 구조에서 부모가 suspended 상태이면, 재귀적으로 부모를 먼저 resume합니다. 예를 들어 PCI 디바이스는 PCI 브리지가 먼저 활성화되어야 합니다.
  • 콜백 우선순위: pm_domain(genpd) > dev->type->pm > dev->class->pm > dev->bus->pm > dev->driver->pm 순서로 탐색하여 첫 번째 유효한 runtime_resume 콜백을 호출합니다.
  • spinlock 보호: dev->power.lock으로 상태 전이를 원자적으로 보호합니다. 콜백 호출 시에는 lock을 해제하여 콜백이 슬립 가능한 함수를 호출할 수 있게 합니다.
  • 에러 처리: 콜백이 에러를 반환하면 상태를 RPM_SUSPENDED로 되돌리고 runtime_error에 에러 코드를 기록합니다. sysfs의 runtime_status에서 "error" 상태로 확인됩니다.
/* pm_runtime_get_sync() → rpm_resume() 호출 체인 요약 */

pm_runtime_get_sync(dev)
  → __pm_runtime_resume(dev, RPM_GET_PUT)
    → atomic_inc(&dev->power.usage_count)   /* refcount 증가 */rpm_resume(dev, RPM_GET_PUT)
      → if (dev->parent) rpm_resume(parent)   /* 부모 재귀 resume */
      → dev->power.runtime_status = RPM_RESUMING
      → callback(dev)                        /* runtime_resume() 실행 */
      → dev->power.runtime_status = RPM_ACTIVE

pm_runtime_put_autosuspend(dev)
  → atomic_dec(&dev->power.usage_count)   /* refcount 감소 */rpm_idle(dev)                          /* idle 검사 */callback(dev)                        /* runtime_idle() 실행 */rpm_suspend(dev)                     /* autosuspend 타이머 설정 */mod_timer(&dev->power.suspend_timer, expires)
코드 설명
  • pm_runtime_get_sync(): usage_count를 원자적으로 증가시킨 후 rpm_resume()을 동기적으로 호출합니다. 반환 시점에 디바이스는 반드시 RPM_ACTIVE 상태입니다(에러가 없다면).
  • pm_runtime_put_autosuspend(): usage_count를 감소시킨 후, 0이 되면 rpm_idle()을 거쳐 autosuspend 타이머를 설정합니다. 타이머 만료 시 실제 rpm_suspend()가 실행됩니다.
  • Autosuspend 타이머: mod_timer()suspend_timer를 설정합니다. autosuspend_delay ms 이내에 새로운 pm_runtime_get()이 호출되면 타이머가 취소되어 불필요한 suspend/resume 사이클을 방지합니다.

드라이버 구현 예제

static int my_runtime_suspend(struct device *dev)
{
    struct my_device *priv = dev_get_drvdata(dev);
    clk_disable_unprepare(priv->clk);
    return 0;
}

static int my_runtime_resume(struct device *dev)
{
    struct my_device *priv = dev_get_drvdata(dev);
    return clk_prepare_enable(priv->clk);
}

static int my_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    /* ... 초기화 ... */
    pm_runtime_set_autosuspend_delay(dev, 200);  /* 200ms */
    pm_runtime_use_autosuspend(dev);
    pm_runtime_set_active(dev);
    pm_runtime_enable(dev);
    return 0;
}

static void my_remove(struct platform_device *pdev)
{
    pm_runtime_disable(&pdev->dev);
    pm_runtime_set_suspended(&pdev->dev);
}

static const struct dev_pm_ops my_pm_ops = {
    RUNTIME_PM_OPS(my_runtime_suspend, my_runtime_resume, NULL)
};

런타임 PM sysfs

/sys/devices/.../power/ sysfs 인터페이스 /sys/devices/.../power/ runtime_status 현재 상태: active / suspended / suspending / resuming runtime_usage usage_count — 현재 활성 참조 수 runtime_active_time 총 활성 시간 (ms) runtime_suspended_time 총 suspended 시간 (ms) autosuspend_delay_ms 자동 suspend 대기 시간 (ms) — -1이면 비활성 control auto / on — "on"이면 런타임 PM 강제 비활성화 pm_runtime_get/put() 호출로 usage_count 증감 → 0이 되면 suspend 트리거

디바이스 PM (dev_pm_ops)

struct dev_pm_ops는 디바이스가 시스템 절전과 런타임 PM 이벤트에 응답하기 위한 22개 콜백을 정의합니다. 실제 드라이버에서는 매크로(Macro)를 사용하여 간결하게 작성합니다.

콜백 전체 매핑

단계SuspendHibernate (freeze)Hibernate (restore)
prepareprepareprepareprepare
메인suspendfreezerestore
latesuspend_latefreeze_laterestore_early
noirqsuspend_noirqfreeze_noirqrestore_noirq
← 하드웨어 절전/복원 →
noirqresume_noirqthaw_noirq
earlyresume_earlythaw_early
메인resumethaw
completecompletecompletecomplete

편의 매크로

/* 시스템 PM 전용 (suspend = resume 콜백 재사용) */
DEFINE_SIMPLE_DEV_PM_OPS(name, suspend_fn, resume_fn)

/* 런타임 PM + 시스템 PM (시스템 PM이 런타임 PM 콜백 재사용) */
DEFINE_RUNTIME_DEV_PM_OPS(name, suspend_fn, resume_fn, idle_fn)

/* pm_sleep_ptr() — CONFIG_PM_SLEEP 미설정 시 NULL로 최적화 */
static const struct dev_pm_ops my_pm_ops = {
    SYSTEM_SLEEP_PM_OPS(my_suspend, my_resume)
    RUNTIME_PM_OPS(my_rt_suspend, my_rt_resume, my_rt_idle)
};

static struct platform_driver my_drv = {
    .driver = {
        .name = "my-device",
        .pm   = pm_sleep_ptr(&my_pm_ops),
    },
};

struct dev_pm_ops 필드별 해설

struct dev_pm_opsinclude/linux/pm.h에 정의되어 있으며, 시스템 절전과 런타임 PM의 모든 콜백을 포괄합니다. 각 필드의 호출 시점과 역할을 정리합니다.

/* include/linux/pm.h — struct dev_pm_ops (간략화) */
struct dev_pm_ops {
    /* ── 시스템 Suspend/Resume ── */
    int (*prepare)(struct device *dev);        /* suspend 전 준비: 자식 디바이스 스캔 중단 */
    void (*complete)(struct device *dev);      /* resume 후 정리: 지연된 작업 재개 */
    int (*suspend)(struct device *dev);        /* 메인 suspend: HW 상태 저장, I/O 중단 */
    int (*resume)(struct device *dev);         /* 메인 resume: HW 상태 복원, I/O 재개 */
    int (*suspend_late)(struct device *dev);   /* IRQ 활성 상태의 마지막 suspend */
    int (*resume_early)(struct device *dev);  /* IRQ 활성 직후 첫 resume */
    int (*suspend_noirq)(struct device *dev); /* IRQ 비활성 후 최종 suspend */
    int (*resume_noirq)(struct device *dev);  /* IRQ 활성화 전 최초 resume */

    /* ── Hibernate (Freeze/Thaw/Poweroff/Restore) ── */
    int (*freeze)(struct device *dev);         /* 스냅샷 전: DMA 중단, HW 상태 저장 */
    int (*thaw)(struct device *dev);           /* 스냅샷 후 복원: 이미지 기록용 */
    int (*poweroff)(struct device *dev);       /* 이미지 기록 후 전원 차단 전 */
    int (*restore)(struct device *dev);        /* 부팅 시 이미지 복원 후 */
    /* + freeze_late, thaw_early, poweroff_late, restore_early,
         freeze_noirq, thaw_noirq, poweroff_noirq, restore_noirq */

    /* ── 런타임 PM ── */
    int (*runtime_suspend)(struct device *dev); /* 디바이스 idle 시 저전력 전환 */
    int (*runtime_resume)(struct device *dev);  /* 디바이스 사용 요청 시 복원 */
    int (*runtime_idle)(struct device *dev);    /* idle 판단: 0 반환 시 suspend 진행 */
};
코드 설명
  • prepare: suspend 시 가장 먼저 호출됩니다. 새로운 자식 디바이스 등록을 방지하고, 반환값이 양수이면 해당 디바이스의 suspend/resume 콜백을 건너뜁니다(direct_complete 최적화).
  • suspend / resume: 가장 일반적인 콜백입니다. suspend에서 HW 레지스터를 저장하고 DMA/인터럽트를 중단하며, resume에서 복원합니다.
  • suspend_late / resume_early: IRQ가 아직 활성인 상태에서의 추가 처리입니다. 공유 자원 정리 등에 사용합니다.
  • suspend_noirq / resume_noirq: 모든 IRQ가 비활성화된 상태에서 호출됩니다. PCI pci_save_state(), GPIO 컨트롤러 상태 저장 등 인터럽트 없이 수행해야 하는 작업에 사용합니다.
  • freeze / thaw: hibernate 전용입니다. freeze는 스냅샷 촬영 전 디바이스를 정지시키고, thaw는 이미지를 디스크에 기록하기 위해 디바이스를 재활성화합니다.
  • poweroff / restore: poweroff는 이미지 기록 후 전원 차단 전 호출되며, restore는 부팅 시 이미지 복원 후 디바이스를 재설정합니다.
  • runtime_suspend / runtime_resume / runtime_idle: 시스템 동작 중 개별 디바이스의 전원을 동적으로 관리합니다. runtime_idle이 0을 반환하면 PM 코어가 runtime_suspend를 호출합니다.

struct dev_pm_info 주요 필드

모든 struct device에 내장된 power 필드(struct dev_pm_info)는 PM 코어가 디바이스의 전원 상태를 추적하는 데 사용합니다.

/* include/linux/pm.h — struct dev_pm_info (핵심 필드) */
struct dev_pm_info {
    pm_message_t        power_state;       /* 현재 PM 메시지 (PMSG_*) */
    unsigned int        can_wakeup:1;      /* wakeup 소스 가능 여부 */
    unsigned int        async_suspend:1;   /* 비동기 suspend 허용 */
    unsigned int        is_suspended:1;    /* dpm_suspend 완료 표시 */
    unsigned int        is_noirq_suspended:1;
    unsigned int        is_late_suspended:1;
    unsigned int        no_pm:1;           /* PM 콜백 건너뛰기 */
    unsigned int        direct_complete:1; /* prepare 반환값 > 0 */

    /* ── 런타임 PM 상태 ── */
    atomic_t            usage_count;       /* get/put 참조 카운터 */
    atomic_t            child_count;       /* 활성 자식 수 */
    enum rpm_status     runtime_status;    /* RPM_ACTIVE / RPM_SUSPENDED / ... */
    struct timer_list   suspend_timer;     /* autosuspend 타이머 */
    unsigned long       timer_expires;     /* 타이머 만료 시각 (jiffies) */
    struct work_struct  work;              /* rpm_idle/suspend/resume 작업큐 */
    int                 runtime_error;     /* 마지막 런타임 PM 에러 코드 */
    int                 autosuspend_delay; /* ms 단위 자동 suspend 지연 */

    /* ── Wakeup 관련 ── */
    struct wakeup_source *wakeup;          /* wakeup source 포인터 */
    struct list_head    entry;             /* dpm_list 연결 */
    struct completion   completion;        /* 비동기 suspend 완료 대기 */
};
코드 설명
  • power_state: 현재 디바이스에 전달된 PM 메시지(PMSG_SUSPEND, PMSG_FREEZE 등)를 저장합니다. 드라이버가 현재 suspend 유형을 구분하는 데 사용할 수 있습니다.
  • usage_count: 런타임 PM의 핵심입니다. pm_runtime_get()으로 증가, pm_runtime_put()으로 감소하며, 0이 되면 idle 검사가 트리거됩니다.
  • runtime_status: RPM_ACTIVE, RPM_SUSPENDED, RPM_SUSPENDING, RPM_RESUMING 네 가지 상태입니다. sysfs의 runtime_status로 확인 가능합니다.
  • suspend_timer / timer_expires: autosuspend 모드에서 pm_runtime_put_autosuspend() 호출 시 설정되는 타이머입니다. 만료되면 work를 통해 suspend가 진행됩니다.
  • async_suspend: 이 플래그가 설정된 디바이스는 dpm_suspend()에서 비동기적으로 suspend됩니다. 독립적인 디바이스를 병렬로 처리하여 suspend 시간을 단축합니다.
  • direct_complete: prepare()가 양수를 반환하고 런타임 PM으로 이미 suspended 상태이면, 해당 디바이스의 suspend/resume 콜백을 건너뛰는 최적화입니다.
  • entry: 글로벌 dpm_list에 연결됩니다. 디바이스 등록 순서대로 삽입되며, dpm_suspend()는 이 리스트를 역순으로 순회합니다.

DPM 리스트 순서

디바이스 PM(DPM)은 디바이스 등록 순서를 기반으로 리스트를 관리합니다. Suspend 시에는 역순(자식 → 부모), Resume 시에는 정순(부모 → 자식)으로 콜백을 호출하여 의존성을 보장합니다.

주의: noirq 콜백은 모든 IRQ가 비활성화된 상태에서 실행됩니다. 이 단계에서는 인터럽트를 사용하는 I/O(예: DMA 완료 대기)를 수행할 수 없습니다. PCI 디바이스의 경우 pci_save_state()/pci_restore_state()를 이 단계에서 호출합니다.

suspend_devices_and_enter() 구현 분석

suspend_devices_and_enter()는 디바이스 suspend의 핵심 함수입니다. 플랫폼 begin() 호출부터 시작하여 디바이스를 단계별로 suspend하고, 플랫폼 진입 후 역순으로 resume합니다.

/* kernel/power/suspend.c — suspend_devices_and_enter() 간략화 */

static int suspend_devices_and_enter(suspend_state_t state)
{
    int error;
    bool wakeup = false;

    /* 1단계: 플랫폼 시작 */
    if (suspend_ops->begin) {
        error = suspend_ops->begin(state);    /* ACPI: 전원 상태 유효성 검사 */
        if (error) goto Close;
    }

    /* 2단계: 디바이스 suspend (prepare → suspend) */
    error = dpm_suspend_start(PMSG_SUSPEND);
    if (error) {
        pr_err("Some devices failed to suspend\n");
        goto Recover_platform;
    }

    /* 3단계: 플랫폼 prepare + suspend_enter() */
    if (suspend_ops->prepare)
        suspend_ops->prepare();              /* 플랫폼 저전력 준비 */

    error = suspend_enter(state, &wakeup);   /* late/noirq + CPU off + enter */

    /* ← resume 경로: suspend_enter()에서 반환 → */

    if (suspend_ops->finish)
        suspend_ops->finish();               /* 플랫폼 복원 */

    /* 4단계: 디바이스 resume (resume → complete) */
    dpm_resume_end(PMSG_RESUME);

Recover_platform:
    if (suspend_ops->recover)
        suspend_ops->recover();
Close:
    if (suspend_ops->end)
        suspend_ops->end();
    return error;
}
코드 설명
  • suspend_ops->begin(): 플랫폼 드라이버(ACPI/PSCI)가 요청된 절전 상태를 지원하는지 검증합니다. ACPI에서는 S3 상태 가용 여부를 확인합니다.
  • dpm_suspend_start(): dpm_prepare()dpm_suspend()를 순서대로 호출합니다. prepare에서 새 디바이스 등록을 차단하고, suspend에서 모든 디바이스의 HW 상태를 저장합니다.
  • suspend_enter(): 이 함수 내에서 dpm_suspend_late(), dpm_suspend_noirq()를 호출한 뒤, non-boot CPU를 오프라인으로 전환하고 suspend_ops->enter()로 실제 절전에 진입합니다. wakeup 이벤트로 복귀하면 역순으로 dpm_resume_noirq(), dpm_resume_early()를 호출합니다.
  • dpm_resume_end(): dpm_resume()dpm_complete()를 호출하여 모든 디바이스를 정상 상태로 복원합니다.
  • 에러 처리: 디바이스 suspend 실패 시 recover 콜백으로 플랫폼 상태를 복구한 뒤, 이미 suspend된 디바이스를 resume합니다.

dpm_suspend() 디바이스 순회 분석

dpm_suspend()는 글로벌 dpm_list역순으로 순회하며 각 디바이스의 suspend 콜백을 호출합니다. async_suspend 플래그가 설정된 디바이스는 별도 워크큐에서 병렬 처리됩니다.

dpm_suspend() — 디바이스 리스트 역순 순회 dpm_list (디바이스 등록 순서) dev_A → dev_B → dev_C → dev_D → dev_E → dev_F ← 역순 순회 (자식 → 부모 보장) → dev->power.async_suspend ? async_schedule_dev() 워크큐에서 병렬 실행 __device_suspend(dev) 현재 스레드에서 동기 실행 list_move(&dev->power.entry, &dpm_suspended_list) async 완료 대기: dpm_wait_for_subordinate(dev) Yes No
/* drivers/base/power/main.c — dpm_suspend() 간략화 */

int dpm_suspend(pm_message_t state)
{
    ktime_t starttime = ktime_get();
    int error = 0;

    mutex_lock(&dpm_list_mtx);

    /* dpm_prepared_list를 역순으로 순회 */
    while (!list_empty(&dpm_prepared_list)) {
        struct device *dev = to_device(
            dpm_prepared_list.prev);                /* 마지막 디바이스 (역순) */

        get_device(dev);
        mutex_unlock(&dpm_list_mtx);

        /* 비동기 suspend: 의존 디바이스 완료 대기 */
        dpm_wait_for_subordinate(dev, async);

        if (async_error)
            goto Skip;

        if (dev->power.async_suspend) {
            /* 워크큐에서 병렬 suspend */
            async_schedule_dev(async_suspend, dev);
        } else {
            /* 동기 suspend */
            error = __device_suspend(dev, state, false);
            if (error) {
                dpm_save_failed_dev(dev_name(dev));
                put_device(dev);
                break;                           /* 실패 시 중단 */
            }
        }
Skip:
        mutex_lock(&dpm_list_mtx);
        if (!error)
            list_move(&dev->power.entry, &dpm_suspended_list);
        put_device(dev);

        if (async_error)
            break;
    }

    mutex_unlock(&dpm_list_mtx);
    async_synchronize_full();                  /* 모든 비동기 작업 완료 대기 */

    dpm_show_time(starttime, state, error, NULL);
    return error;
}
코드 설명
  • 역순 순회: dpm_prepared_list.prev로 리스트의 마지막 디바이스부터 처리합니다. 디바이스 등록 순서의 역순이므로, 자식 디바이스가 부모보다 먼저 suspend되어 의존성이 보장됩니다.
  • dpm_wait_for_subordinate(): 현재 디바이스의 하위(자식/공급자) 디바이스가 비동기 suspend를 완료할 때까지 대기합니다. 이를 통해 의존성 순서를 비동기 환경에서도 유지합니다.
  • async_schedule_dev(): dev->power.async_suspend가 설정된 디바이스는 시스템 워크큐의 별도 스레드에서 suspend됩니다. USB, PCI 등 독립적인 디바이스를 병렬로 처리하여 전체 suspend 시간을 단축합니다.
  • __device_suspend(): 실제 콜백 호출 함수입니다. dev->pm_domain, dev->type, dev->class, dev->bus, dev->driver 순서로 PM ops를 탐색하여 첫 번째로 발견된 콜백을 실행합니다.
  • dpm_save_failed_dev(): 실패한 디바이스 이름을 기록합니다. /sys/power/suspend_stats/failed_dev에서 확인할 수 있어 디버깅에 유용합니다.
  • async_synchronize_full(): 루프 종료 후 아직 진행 중인 비동기 suspend 작업이 모두 완료될 때까지 대기합니다.
  • list_move(): suspend 완료된 디바이스를 dpm_suspended_list로 이동합니다. resume 시에는 이 리스트를 정순으로 순회합니다.

PM 도메인 (genpd)

Generic PM Domain(genpd)은 하나의 전력 도메인(power domain)에 속한 디바이스 그룹을 관리합니다. SoC에서 특정 하드웨어 블록의 전원을 독립적으로 켜고 끌 수 있으며, 도메인 간 계층 관계를 지원합니다.

핵심 구조체

/* include/linux/pm_domain.h */
struct generic_pm_domain {
    struct device dev;
    const char *name;
    unsigned int flags;         /* GENPD_FLAG_* */
    int (*power_on)(struct generic_pm_domain *domain);
    int (*power_off)(struct generic_pm_domain *domain);
    struct list_head parent_links;  /* 부모 도메인 링크 */
    struct list_head child_links;   /* 자식 도메인 링크 */
    struct list_head dev_list;      /* 소속 디바이스 리스트 */
};

PM 도메인 계층 다이어그램 (SoC 예시)

SoC Always-On CPU Power Domain GPU Power Domain Peripheral Domain CPU0/1 CPU2/3 GPU Core + Shader USB Domain UART Domain usb0, usb1 uart0..3 gpu0 cpu0, cpu1 cpu2, cpu3 규칙: 자식 도메인 중 하나라도 ON이면 부모도 ON 유지

genpd API

/* Provider 등록 */
pm_genpd_init(genpd, gov, is_off);       /* 도메인 초기화 */
pm_genpd_add_subdomain(parent, child);  /* 계층 관계 설정 */
of_genpd_add_provider_onecell(np, data); /* DT provider 등록 */

/* Consumer 연결 (DT 기반 자동) */
dev_pm_domain_attach(dev, true);        /* 디바이스를 도메인에 연결 */
dev_pm_domain_detach(dev, true);        /* 디바이스를 도메인에서 분리 */

Device Tree 바인딩

/* Provider (SoC DTSI) */
power_domains: power-controller@10000 {
    compatible = "vendor,soc-power-domains";
    #power-domain-cells = <1>;
};

/* Consumer (디바이스 노드) */
usb@20000 {
    compatible = "vendor,usb-controller";
    power-domains = <&power_domains 3>; /* domain index 3 */
};

PM QoS (Quality of Service)

PM QoS는 전원 관리 결정에 성능 제약 조건을 부과하는 프레임워크입니다. 디바이스나 서브시스템이 최소 응답 시간을 요구하면, cpuidle 거버너가 이 제약을 존중하여 깊은 C-state 진입을 제한합니다.

QoS 유형

유형인터페이스영향
CPU latency QoS/dev/cpu_dma_latency시스템 전체 최대 허용 C-state exit latency 제한
디바이스 PM QoS (latency)per-device sysfs개별 디바이스의 resume latency 제한
디바이스 PM QoS (flags)per-device sysfsNO_POWER_OFF 등 디바이스별 PM 정책
Frequency QoScpufreq 내부최소/최대 CPU 주파수 제한

API

/* CPU latency QoS (글로벌) */
struct pm_qos_request req;
cpu_latency_qos_add_request(&req, 50);  /* 최대 50μs latency 요구 */
cpu_latency_qos_update_request(&req, 100);
cpu_latency_qos_remove_request(&req);

/* 디바이스 PM QoS */
dev_pm_qos_add_request(dev, &req, DEV_PM_QOS_RESUME_LATENCY, 100);
dev_pm_qos_update_request(&req, 200);
dev_pm_qos_remove_request(&req);

cpuidle 연동

cpuidle 거버너(menu/TEO)는 매 idle 진입 시 cpuidle_governor_latency_req()를 호출하여 현재 유효한 최소 latency 제약을 확인합니다. exit_latency가 이 제약을 초과하는 C-state는 선택 후보에서 제외됩니다.

# 사용자 공간에서 latency 제약 설정
# /dev/cpu_dma_latency에 4바이트 LE 값 쓰기
# fd를 열고 있는 동안만 제약 유효
exec 3> /dev/cpu_dma_latency
printf '\x32\x00\x00\x00' >&3  # 50μs 제약
# ... 작업 수행 ...
exec 3>&-  # fd 닫기 → 제약 해제

Wakeup Sources

Wakeup source는 시스템을 절전 상태에서 깨울 수 있는 이벤트 소스를 추적합니다. 진행 중인 wakeup 이벤트가 있으면 suspend 진입을 중단(abort)합니다.

wakeup_source API

/* 디바이스에 wakeup capability 설정 */
device_init_wakeup(dev, true);    /* wakeup source 등록 + capable 설정 */
device_set_wakeup_enable(dev, true);

/* Wakeup 이벤트 보고 */
pm_wakeup_event(dev, msec);       /* msec 동안 suspend 방지 */
pm_stay_awake(dev);               /* suspend 방지 시작 */
pm_relax(dev);                    /* suspend 방지 해제 */
__pm_wakeup_event(ws, msec);      /* wakeup_source 직접 사용 */

wakeup_count 메커니즘

Suspend 진입 시 wakeup_count를 사용하면 race condition을 방지할 수 있습니다:

# 1. 현재 wakeup count 읽기
count=$(cat /sys/power/wakeup_count)

# 2. 같은 값을 다시 쓰기 (이 사이에 wakeup이 발생했으면 실패)
echo $count > /sys/power/wakeup_count || exit 1

# 3. suspend 진입 (wakeup 발생 시 즉시 abort)
echo mem > /sys/power/state

sysfs 인터페이스

Wakeup Source sysfs 인터페이스 /sys/devices/.../power/ — 디바이스별 wakeup 설정 wakeup enabled / disabled — wakeup 소스 활성화 여부 wakeup_count 이 디바이스의 wakeup 이벤트 발생 횟수 /sys/class/wakeup/wakeup*/ — 전역 wakeup source 목록 name wakeup source 이름 (드라이버 등록 시 지정) active_count __pm_stay_awake() 호출 횟수 (lock 횟수) event_count 총 이벤트 수 (pm_wakeup_event 호출 횟수) wakeup_count 실제 시스템 wakeup을 유발한 횟수 expire_count 타임아웃 만료 횟수 (autosuspend 관련) cat /sys/kernel/debug/wakeup_sources 로 모든 wakeup source 상태 확인
autosleep: /sys/power/autosleepmem을 쓰면 시스템은 wakeup 이벤트가 없을 때 자동으로 suspend에 진입합니다. Android에서 널리 사용됩니다.

Android 전원 관리

Android의 전원 관리는 커널의 wakeup source/autosleep 메커니즘을 핵심으로 사용합니다. 초기 Android의 wakelocks는 out-of-tree 패치(Patch)였으나, 커널 3.5에서 PM wakeup sources로 통합되었습니다.

Android 용어커널 메커니즘역사
WakeLock (Java API)wakeup_source + autosleepwakelocks → PM wakeup sources (3.5+)
Doze 모드alarm_timer + suspendAndroid 6.0+, 유휴 시 앱 활동 제한
App Standby Bucketscgroup freezer + cpusetAndroid 9+, 앱 사용 빈도별 차등 제한
/* Android WakeLock → 커널 wakeup_source 매핑 */
# /sys/power/wake_lock에 쓰기 → 유저스페이스 wakeup_source 생성
# PowerManager.WakeLock.acquire() → 최종적으로 여기에 도달
echo my_wakelock > /sys/power/wake_lock   # suspend 방지
echo my_wakelock > /sys/power/wake_unlock  # suspend 허용

/* UCLAMP: Android의 Power HAL이 태스크별 util 힌트를 설정 */
# foreground 앱: 높은 uclamp.min으로 성능 보장
# background 앱: 낮은 uclamp.max로 전력 절약
# EAS(Energy Aware Scheduling)가 util 힌트를 기반으로 에너지 효율적 코어 선택

Android의 Power HAL, UCLAMP, cgroup 기반 에너지 관리 등 내용은 Android 커널 — 프로세스 모델을 참고하라.

Energy Model & EAS

Energy Model(EM)은 CPU의 주파수-전력 관계를 모델링하고, EAS(Energy Aware Scheduling)는 이 정보를 활용하여 태스크(Task)를 에너지 효율적인 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[];      /* cpumask */
};

EAS 활성화 조건

EAS 태스크 배치 흐름

find_energy_efficient_cpu(p, prev_cpu, sd_flag)
  → 각 performance domain에 대해:
    → 각 CPU에 태스크를 가상 배치
    → em_cpu_energy()로 에너지 소비 계산
    → compute_energy(): Σ(OPP_power × utilization / capacity)
  → 에너지가 최소인 CPU 선택
  → 이전 CPU 대비 6% 이상 절감 시에만 마이그레이션

EM + EAS 연동 다이어그램

cpufreq driver Energy Model freq → power 테이블 OPP Framework CFS Scheduler EAS (find_eec) schedutil 등록 em_cpu_energy() 주파수 결정

OPP 프레임워크

OPP(Operating Performance Points)는 디바이스가 동작할 수 있는 전압-주파수 조합을 관리합니다. cpufreq, devfreq, Energy Model 등이 OPP 테이블을 참조하여 동적 전압/주파수 스케일링(DVFS)을 수행합니다.

dev_pm_opp API

함수설명
dev_pm_opp_add(dev, freq, u_volt)동적 OPP 추가
dev_pm_opp_remove(dev, freq)OPP 제거
dev_pm_opp_find_freq_ceil(dev, &freq)freq 이상인 최소 OPP
dev_pm_opp_find_freq_floor(dev, &freq)freq 이하인 최대 OPP
dev_pm_opp_get_voltage(opp)OPP의 전압 반환
dev_pm_opp_get_freq(opp)OPP의 주파수 반환
dev_pm_opp_set_rate(dev, freq)주파수 설정 (전압 자동 조정)
dev_pm_opp_of_add_table(dev)DT에서 OPP 테이블 로드

Device Tree OPP 테이블

cpu_opp_table: opp-table {
    compatible = "operating-points-v2";
    opp-shared;

    opp-600000000 {
        opp-hz = /bits/ 64 <600000000>;
        opp-microvolt = <900000>;
        opp-supported-hw = <0x3>;
    };
    opp-1200000000 {
        opp-hz = /bits/ 64 <1200000000>;
        opp-microvolt = <1100000 1050000 1150000>; /* target min max */
    };
    opp-1800000000 {
        opp-hz = /bits/ 64 <1800000000>;
        opp-microvolt = <1250000>;
        turbo-mode;  /* 부스트 전용 */
    };
};

Thermal Throttling 연동

열 관리 서브시스템이 dev_pm_opp_put_clkname()과 cooling device를 통해 최대 OPP를 동적으로 제한합니다. 과열 시 상위 OPP가 비활성화되어 주파수가 자동으로 하향됩니다.

12. Regulator 프레임워크 =============================================================== -->

Regulator 프레임워크

참고: Regulator 프레임워크는 주로 ARM/SoC 임베디드 플랫폼에서 PMIC(Power Management IC)를 제어하는 데 사용됩니다. x86 데스크톱/서버에서는 ACPI 또는 EC(Embedded Controller)가 전압 관리를 대신합니다.

Consumer / Provider 모델

/* Consumer API — 디바이스 드라이버에서 전원 요청 */
struct regulator *reg = devm_regulator_get(dev, "vdd");
regulator_enable(reg);
regulator_set_voltage(reg, 1800000, 1800000);  /* 1.8V */
regulator_disable(reg);

/* Provider — PMIC 드라이버에서 regulator 등록 */
static const struct regulator_ops my_ops = {
    .enable          = my_enable,
    .disable         = my_disable,
    .set_voltage_sel = my_set_voltage,
    .get_voltage_sel = my_get_voltage,
    .list_voltage    = regulator_list_voltage_linear,
};

static const struct regulator_desc my_desc = {
    .name       = "LDO1",
    .ops        = &my_ops,
    .type       = REGULATOR_VOLTAGE,
    .min_uV     = 800000,
    .uV_step    = 50000,
    .n_voltages = 32,
};

Device Tree 바인딩

pmic@48 {
    compatible = "vendor,pmic";
    regulators {
        ldo1: LDO1 {
            regulator-name = "vdd_1v8";
            regulator-min-microvolt = <1800000>;
            regulator-max-microvolt = <1800000>;
            regulator-always-on;
        };
        buck1: BUCK1 {
            regulator-name = "vdd_cpu";
            regulator-min-microvolt = <800000>;
            regulator-max-microvolt = <1400000>;
            regulator-ramp-delay = <12500>; /* μV/μs */
        };
    };
};

/* Consumer 참조 */
cpu@0 {
    cpu-supply = <&buck1>;
};

전원 관리 통합 패턴

디바이스 드라이버에서 Regulator를 Runtime PM 및 System Sleep과 통합하는 대표적인 패턴입니다.

/* Regulator + Runtime PM 통합 패턴 */
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>

struct my_device {
    struct regulator_bulk_data supplies[2];
    struct clk     *clk;
    void __iomem   *base;
};

static int my_device_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct my_device *mydev;
    int ret;

    mydev = devm_kzalloc(dev, sizeof(*mydev), GFP_KERNEL);
    if (!mydev)
        return -ENOMEM;

    /* 벌크 레귤레이터 획득 */
    mydev->supplies[0].supply = "vdd";   /* → DT의 vdd-supply */
    mydev->supplies[1].supply = "vddio"; /* → DT의 vddio-supply */

    ret = devm_regulator_bulk_get(dev, 2, mydev->supplies);
    if (ret)
        return dev_err_probe(dev, ret, "failed to get regulators\\n");

    /* 초기 전원 투입 */
    ret = regulator_bulk_enable(2, mydev->supplies);
    if (ret)
        return ret;

    /* ... 디바이스 초기화 ... */

    /* Runtime PM 활성화 */
    pm_runtime_set_active(dev);
    pm_runtime_enable(dev);
    pm_runtime_set_autosuspend_delay(dev, 2000); /* 2초 유예 */
    pm_runtime_use_autosuspend(dev);

    platform_set_drvdata(pdev, mydev);
    return 0;
}

/* === Runtime PM 콜백 === */

static int my_device_runtime_suspend(struct device *dev)
{
    struct my_device *mydev = dev_get_drvdata(dev);

    /* 1. 하드웨어 유휴 상태로 전환 */
    /* 2. 클럭 비활성화 */
    clk_disable_unprepare(mydev->clk);
    /* 3. 레귤레이터 비활성화 (전력 절감 핵심!) */
    regulator_bulk_disable(2, mydev->supplies);

    return 0;
}

static int my_device_runtime_resume(struct device *dev)
{
    struct my_device *mydev = dev_get_drvdata(dev);
    int ret;

    /* 1. 레귤레이터 활성화 (전원 복구) */
    ret = regulator_bulk_enable(2, mydev->supplies);
    if (ret)
        return ret;

    usleep_range(1000, 1500); /* 전원 안정화 대기 */

    /* 2. 클럭 활성화 */
    ret = clk_prepare_enable(mydev->clk);
    if (ret) {
        regulator_bulk_disable(2, mydev->supplies);
        return ret;
    }

    /* 3. 하드웨어 재초기화 */
    return 0;
}

/* === System Sleep 콜백 === */

static int my_device_suspend(struct device *dev)
{
    struct my_device *mydev = dev_get_drvdata(dev);
    int ret;

    /* Runtime PM으로 이미 suspend됐으면 추가 작업 불필요 */
    ret = pm_runtime_force_suspend(dev);
    if (ret)
        return ret;

    /* suspend 상태에서 전압을 낮출 수 있다면 설정
     * (PMIC가 suspend mode를 지원하는 경우) */
    regulator_set_voltage(mydev->supplies[0].consumer,
                          0, INT_MAX); /* 최소 전압으로 */

    return 0;
}

static int my_device_resume(struct device *dev)
{
    struct my_device *mydev = dev_get_drvdata(dev);

    /* 정상 전압 복원 */
    regulator_set_voltage(mydev->supplies[0].consumer,
                          1800000, 1800000);

    return pm_runtime_force_resume(dev);
}

static const struct dev_pm_ops my_device_pm_ops = {
    SYSTEM_SLEEP_PM_OPS(my_device_suspend, my_device_resume)
    RUNTIME_PM_OPS(my_device_runtime_suspend,
                   my_device_runtime_resume, NULL)
};

CPU DVFS와 Regulator

DVFS(Dynamic Voltage and Frequency Scaling)는 CPU 부하에 따라 동작 주파수와 코어 전압을 동적으로 조절하여 전력 소모를 최적화합니다. cpufreq 서브시스템이 OPP(Operating Performance Point) 테이블에 따라 Regulator Framework를 통해 전압을 변경합니다.

/* CPU DVFS를 위한 Device Tree OPP 테이블 */
cpu0: cpu@0 {
    compatible = "arm,cortex-a53";
    device_type = "cpu";
    reg = <0x0>;
    clocks = <&clk_cpu>;
    cpu-supply = <&buck1>;       /* 코어 전압 레귤레이터 */
    operating-points-v2 = <&cpu_opp_table>;
};

cpu_opp_table: opp-table {
    compatible = "operating-points-v2";

    opp-408000000 {
        opp-hz = /bits/ 64 <408000000>;   /* 408MHz */
        opp-microvolt = <950000>;          /* 0.95V */
    };
    opp-816000000 {
        opp-hz = /bits/ 64 <816000000>;   /* 816MHz */
        opp-microvolt = <1050000>;         /* 1.05V */
    };
    opp-1200000000 {
        opp-hz = /bits/ 64 <1200000000>;  /* 1.2GHz */
        opp-microvolt = <1200000>;         /* 1.2V */
    };
    opp-1800000000 {
        opp-hz = /bits/ 64 <1800000000>;  /* 1.8GHz */
        opp-microvolt = <1350000>;         /* 1.35V */
        turbo-mode;
    };
};
CPU Frequency → Voltage → 408MHz 0.95V 816MHz 1.05V 1.2GHz 1.2V 1.8GHz 1.35V (turbo) P ∝ C × V² × f 전력 = 커패시턴스 × 전압² × 주파수
# DVFS 상태 확인 및 모니터링

# 현재 CPU 주파수와 전압
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
cat /sys/class/regulator/regulator.0/microvolts

# OPP 테이블 확인
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies

# 레귤레이터 상태 요약 (전체 공급 체인)
cat /sys/kernel/debug/regulator/regulator_summary

# 주파수 변경 이벤트 추적
trace-cmd record -e cpu_frequency -e regulator_set_voltage
trace-cmd report
전압 스케일링 순서: 주파수를 올릴 때는 전압을 먼저 올린 후 주파수를 올립니다 (전압 부족 방지). 주파수를 내릴 때는 주파수를 먼저 내린 후 전압을 내립니다 (불필요한 전력 소모 방지). cpufreq 프레임워크가 이 순서를 자동으로 처리합니다.

Power Domain과 레귤레이터

SoC의 Power Domain은 칩 내부의 전력 영역을 독립적으로 ON/OFF할 수 있는 하드웨어 기능입니다. 레귤레이터가 보드 레벨의 외부 전원을 제어한다면, Power Domain은 SoC 내부의 전력 게이팅을 제어합니다. 두 시스템은 함께 작동하여 최대한의 전력 절감을 달성합니다.

구분RegulatorPower Domain
제어 대상보드 레벨 외부 전원 (PMIC Buck/LDO)SoC 내부 전력 게이트
인터페이스I2C/SPI로 PMIC 레지스터 접근MMIO로 SoC PMU 레지스터 접근
프레임워크drivers/regulator/drivers/pmdomain/ (genpd)
DT 프로퍼티*-supply = <&phandle>power-domains = <&phandle index>
전압 제어가능 (set_voltage)불가 (ON/OFF만)
지연 시간수백 µs ~ 수 ms수 µs ~ 수십 µs
사용 예CPU 코어 전압, 센서 전원GPU 코어, DSP, Video codec 블록
/* Power Domain + Regulator 결합 예시 */
power-controller {
    compatible = "vendor,power-domains";
    #power-domain-cells = <1>;
};

/* GPU는 내부 Power Domain + 외부 Regulator 모두 사용 */
gpu@12000000 {
    compatible = "vendor,gpu";
    reg = <0x12000000 0x10000>;
    power-domains = <&power_controller 3>; /* SoC 내부 PD */
    mali-supply = <&buck2>;                  /* 외부 전압 */
    operating-points-v2 = <&gpu_opp_table>;
};
전원 관리 순서 요약:
  • 전원 투입(Power-on): 외부 Regulator enable → 안정화 대기 → Power Domain ON → Clock enable → 디바이스 초기화
  • 전원 차단(Power-off): 디바이스 유휴 확인 → Clock disable → Power Domain OFF → Regulator disable
  • Runtime Suspend: 디바이스 idle → Clock gate → (선택적) Regulator disable → (선택적) Power Domain OFF
  • System Suspend: 모든 디바이스 suspend → 비필수 Regulator off → PMIC suspend mode → SoC deep sleep

시스템 종료/재부팅

Reboot 상세 흐름

커널 재부팅은 reboot 명령 또는 reboot() 시스템 콜(System Call)로 트리거됩니다. 모든 프로세스를 종료하고 디바이스를 정리한 뒤 하드웨어 리셋을 수행합니다.

/* === Reboot 시스템 콜 ===
 *
 * 소스: kernel/reboot.c
 *
 * #include <linux/reboot.h>
 * #include <sys/reboot.h>   (사용자 공간)
 *
 * reboot() 시스템 콜:
 *   reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd, arg)
 *   - MAGIC1 = 0xfee1dead  ("feel dead")
 *   - MAGIC2 = 0x28121969 | 0x05121996 | 0x16041998 | 0x20112000
 *     (Linus 및 자녀들의 생년월일)
 *   - cmd: LINUX_REBOOT_CMD_RESTART   = 0x01234567  (재부팅)
 *          LINUX_REBOOT_CMD_HALT      = 0xCDEF0123  (정지)
 *          LINUX_REBOOT_CMD_POWER_OFF = 0x4321FEDC  (전원 끄기)
 *          LINUX_REBOOT_CMD_RESTART2  = 0xA1B2C3D4  (문자열 인자 재부팅)
 *          LINUX_REBOOT_CMD_KEXEC     = 0x45584543  (kexec)
 */

/* === kernel_restart() 내부 흐름 ===
 *
 * SYSCALL_DEFINE4(reboot, ...) [kernel/reboot.c]
 *   → cmd == LINUX_REBOOT_CMD_RESTART:
 *     → kernel_restart(NULL)
 *
 * kernel_restart(char *cmd):
 *   ├── kernel_restart_prepare(cmd)
 *   │     ├── blocking_notifier_call_chain(&reboot_notifier_list, ...)
 *   │     │     → 등록된 reboot notifier들 순차 호출
 *   │     │       (예: watchdog 정지, 하드웨어 LED 끄기, IPMI 알림 등)
 *   │     ├── system_state = SYSTEM_RESTART
 *   │     └── device_shutdown()
 *   │           → 모든 디바이스의 .shutdown() 콜백 호출 (역순)
 *   │           → 디스크 캐시 플러시, DMA 정지, NIC 링 해제 등
 *   │
 *   ├── kmsg_dump(KMSG_DUMP_SHUTDOWN) → 로그 덤프 (pstore 등)
 *   ├── migrate_to_reboot_cpu()       → CPU 0(BSP)으로 마이그레이션
 *   ├── syscore_shutdown()            → syscore_ops .shutdown() 체인
 *   │
 *   └── machine_restart(cmd)          ★ 아키텍처별 하드웨어 리셋 ★
 *         → arch/x86/kernel/reboot.c: native_machine_restart()
 */

/* === x86 리셋 메서드 (native_machine_restart) ===
 *
 * 소스: arch/x86/kernel/reboot.c
 *
 * 커널은 여러 리셋 메서드를 순차적으로 시도합니다:
 * (reboot= 커널 파라미터로 순서/방법 지정 가능)
 */
enum reboot_type {
    BOOT_TRIPLE    = 't',  /* Triple Fault — IDT를 0으로 → #GP → CPU 리셋 */
    BOOT_KBD       = 'k',  /* 키보드 컨트롤러 (i8042) — port 0x64에 0xFE 기록 */
    BOOT_BIOS      = 'b',  /* BIOS 리셋 — 리얼 모드 전환 → BIOS warm reboot */
    BOOT_ACPI      = 'a',  /* ACPI RESET_REG — FADT의 리셋 레지스터 사용 */
    BOOT_EFI       = 'e',  /* EFI ResetSystem(EfiResetCold, ...) */
    BOOT_CF9_FORCE = 'p',  /* PCI CF9 리셋 — I/O 포트 0xCF9에 0x06 기록 */
};

/* 기본 시도 순서 (ACPI 리셋 레지스터가 있는 경우):
 *   1. ACPI RESET_REG (FADT Generic Address)
 *   2. 키보드 컨트롤러 (i8042 0xFE)
 *   3. EFI ResetSystem()
 *   4. PCI CF9
 *   5. Triple Fault (최후의 수단)
 *
 * 커널 파라미터 예시:
 *   reboot=acpi       → ACPI 리셋 우선
 *   reboot=efi        → EFI ResetSystem() 우선
 *   reboot=kbd        → 키보드 컨트롤러 우선
 *   reboot=pci        → PCI CF9 리셋 우선
 *   reboot=triple     → Triple Fault
 *   reboot=bios       → BIOS warm reboot
 *   reboot=cold       → 콜드 리셋 (메모리 초기화)
 *   reboot=warm       → 웜 리셋 (BIOS POST 스킵)
 *   reboot=force      → emergency_restart() 사용 (notifier 호출 안 함)
 */

/* 각 리셋 메서드 상세 */

/* 1. ACPI RESET_REG
 * FADT(Fixed ACPI Description Table)에 정의된 리셋 레지스터:
 *   - reset_reg: Generic Address Structure (I/O, MMIO, 또는 PCI Config)
 *   - reset_value: 레지스터에 기록할 값
 *
 * acpi_reboot():
 *   acpi_reset() → FADT.reset_reg에 FADT.reset_value 기록
 *   → 칩셋이 리셋 신호 생성 */

/* 2. 키보드 컨트롤러 (i8042)
 *   outb(0xFE, 0x64)  → CPU 리셋 라인(A20 컨트롤러) 활성화
 *   → 레거시 시스템의 전통적 리셋 방법
 *   → PS/2 컨트롤러 없는 시스템에서는 동작 안 함 */

/* 3. EFI ResetSystem()
 *   efi.reset_system(EFI_RESET_COLD, EFI_SUCCESS, 0, NULL)
 *   → UEFI Runtime Service를 통한 리셋
 *   → 가장 신뢰성 높은 방법 (현대 시스템) */

/* 4. PCI CF9
 *   outb(0x02, 0xCF9)  → CF9 초기화
 *   outb(0x06, 0xCF9)  → 풀 리셋 (bit1=시스템리셋, bit2=콜드리셋)
 *   → Intel PCH/ICH 칩셋의 리셋 컨트롤 레지스터
 *   → 0x0E = 풀 리셋 + CPU 리셋 */

/* 5. Triple Fault
 *   load_idt(&no_idt)  → IDT를 0으로 설정
 *   __asm__ __volatile__("int3")  → #BP → IDT 없음 → #DF → #TF
 *   → CPU가 Triple Fault → 무조건적 리셋
 *   → 다른 모든 방법이 실패했을 때의 최후의 수단 */
Reboot Notifier: 드라이버는 register_reboot_notifier()로 재부팅 알림을 등록합니다. 이 notifier에서 워치독 정지, RAID 배터리 상태 저장, IPMI 시스템 이벤트 로그 기록, 원격 관리 카드(BMC) 알림 등을 수행합니다. notifier에서의 지연은 재부팅 시간에 직접 영향을 줍니다.
/* === Reboot Notifier 등록 예시 === */
#include <linux/reboot.h>

static int my_reboot_handler(struct notifier_block *nb,
                             unsigned long action, void *data)
{
    switch (action) {
    case SYS_RESTART:     /* reboot */
    case SYS_HALT:        /* halt */
    case SYS_POWER_OFF:   /* poweroff */
        my_hardware_shutdown();
        break;
    }
    return NOTIFY_DONE;
}

static struct notifier_block my_reboot_nb = {
    .notifier_call = my_reboot_handler,
    .priority = 0,  /* 높을수록 먼저 호출 */
};

/* 모듈 init에서 등록 */
register_reboot_notifier(&my_reboot_nb);

/* 모듈 exit에서 해제 */
unregister_reboot_notifier(&my_reboot_nb);

Poweroff (S5) 상세 흐름

전원 차단은 ACPI S5 상태 또는 EFI ResetSystem(Shutdown)으로 수행됩니다. poweroff, shutdown -h now, systemctl poweroff 명령이 이 경로를 트리거합니다.

Poweroff 전체 흐름

소스: kernel/reboot.c, kernel/power/poweroff.c

사용자 공간에서 커널로의 진입 경로는 다음과 같습니다:

SYSCALL_DEFINE4(reboot, ...)에서 cmd == LINUX_REBOOT_CMD_POWER_OFF이면 kernel_power_off()를 호출합니다.

kernel_power_off():
  ├── kernel_shutdown_prepare(SYSTEM_POWER_OFF)
  │     ├── blocking_notifier_call_chain(&reboot_notifier_list,
  │     │                                 SYS_POWER_OFF, ...)
  │     │     → reboot notifier들 호출 (재부팅과 동일)
  │     ├── system_state = SYSTEM_POWER_OFF
  │     └── device_shutdown()
  │           → 모든 디바이스 .shutdown() 콜백
  │           → 디스크 캐시 플러시 ★ (데이터 무결성 핵심)
  │           → USB 컨트롤러 정지, NIC 링 해제 등
  │
  ├── kmsg_dump(KMSG_DUMP_SHUTDOWN)  → 로그 덤프
  ├── migrate_to_reboot_cpu()        → CPU 0으로 마이그레이션
  ├── syscore_shutdown()
  │
  └── pm_power_off()               ★ 플랫폼별 전원 차단 ★
        → 함수 포인터, 플랫폼 초기화 시 등록됨

pm_power_off가 NULL이거나 실패하면 kernel_halt()로 폴백합니다 (CPU 정지, 전원은 계속 공급).

pm_power_off 등록 메커니즘

아키텍처/플랫폼별로 pm_power_off 함수 포인터를 설정합니다:

x86 ACPI
acpi_power_off() [drivers/acpi/sleep.c] → acpi_enter_sleep_state(ACPI_STATE_S5) → SLP_TYPa/SLP_TYPb에 S5 값 기록 → PM1a_CNT에 SLP_EN 비트 설정 → 전원 차단
x86 EFI
efi_power_off() [drivers/firmware/efi/reboot.c] → efi.reset_system(EFI_RESET_SHUTDOWN, ...) → UEFI Runtime Service로 전원을 차단합니다.
ARM (Device Tree 기반)
gpio-poweroff, syscon-poweroff 등 드라이버가 등록합니다. GPIO 핀 토글 또는 PMIC 레지스터 기록으로 전원을 차단합니다.
PSCI (ARM64 가상화)
psci_sys_poweroff() → PSCI SYSTEM_OFF SMC 호출 → 펌웨어가 전원을 차단합니다.

Halt vs Poweroff

항목kernel_halt()kernel_power_off()
동작device_shutdown() + CPU 정지 (hlt 루프)device_shutdown() + pm_power_off()
전원계속 공급됨 (ATX PSU ON 상태)ACPI S5 또는 EFI로 ATX PSU를 OFF
결과"System halted." 출력 후 무한 대기, 수동 전원 버튼 필요전원이 자동으로 차단됨

역사적 배경: AT 파워서플라이(1990년대)는 소프트웨어 전원 차단이 불가능하여 halt만 가능했습니다. ATX 파워서플라이(1996~)부터 ACPI를 통한 소프트웨어 전원 차단이 가능해졌습니다.

Emergency 경로: 시스템이 정상 종료할 수 없는 비상 상황에서는 다른 경로를 사용합니다:
  • SysRq+Oemergency_power_off(): notifier/device_shutdown 없이 즉시 전원 차단
  • SysRq+Bemergency_restart(): notifier/device_shutdown 없이 즉시 재부팅
  • SysRq+S,U,B — 안전한 비상 재부팅: Sync(디스크 동기화) → Umount(파일시스템(Filesystem) 읽기전용) → reBoot
  • panic() — 커널 패닉(Kernel Panic) 시: panic_timeout초 후 자동 재부팅 (kernel.panic=10)
  • 하드웨어 워치독/dev/watchdog: 일정 시간 내 리프레시 없으면 칩셋이 강제 리셋

systemd의 종료 과정 (사용자 공간)

systemctl poweroff 또는 reboot 실행 시 다음 순서로 진행됩니다:

  1. systemd가 shutdown.target 활성화 — 모든 서비스 유닛을 역순으로 정지합니다. ExecStop= 또는 SIGTERM을 전송하며, TimeoutStopSec 후 SIGKILL로 강제 종료합니다.
  2. 파일시스템 언마운트 — systemd-shutdown이 모든 마운트를 해제합니다. 루트 파일시스템은 읽기 전용으로 리마운트합니다.
  3. reboot() 시스템 콜 호출 — poweroff 시 LINUX_REBOOT_CMD_POWER_OFF, reboot 시 LINUX_REBOOT_CMD_RESTART를 사용합니다.

타임아웃 설정 (/etc/systemd/system.conf):

종료 지연 디버깅: systemd-analyze blame으로 서비스별 시작/정지 시간을, journalctl -b -1 -e로 마지막 종료 로그를 확인할 수 있습니다.

kexec — 부트로더 없는 빠른 재부팅

소스: kernel/kexec.c, kernel/kexec_core.c

기존 커널에서 새 커널을 직접 로드하여 BIOS/UEFI POST 단계를 건너뜁니다. 서버 환경에서 재부팅 시간을 수십 초에서 수 초로 단축할 수 있습니다.

  1. kexec -l /boot/vmlinuz --initrd=/boot/initrd.img --command-line="root=/dev/sda1 ..." — 새 커널을 메모리에 미리 로드합니다 (segments 준비).
  2. kexec -e 또는 reboot(LINUX_REBOOT_CMD_KEXEC)machine_kexec()를 호출하여 모든 디바이스를 shutdown하고, disable_nonboot_cpus() 실행 후, relocate_kernel이 커널 이미지를 최종 위치에 복사하고 새 커널의 startup_64로 점프합니다.
kexec
정상 재부팅을 대체하는 빠른 커널 교체 방식입니다.
kdump
커널 패닉 시 크래시 덤프를 수집합니다. 예약된 메모리에 미리 로드된 "capture 커널"로 전환하고, /proc/vmcore로 크래시 메모리 덤프에 접근하여 makedumpfile로 저장한 뒤 crash 도구로 분석합니다.
# === Reboot / Poweroff / Suspend 실전 명령어 모음 ===

# 재부팅
reboot                           # systemd → reboot(RESTART)
systemctl reboot                 # 동일
echo b > /proc/sysrq-trigger    # SysRq 즉시 재부팅 (비상용)

# 전원 차단
poweroff                         # systemd → reboot(POWER_OFF)
systemctl poweroff               # 동일
shutdown -h now                  # halt 후 전원 차단
echo o > /proc/sysrq-trigger    # SysRq 즉시 전원 차단 (비상용)

# Suspend-to-RAM
systemctl suspend                # S3 또는 s2idle
echo mem > /sys/power/state      # 직접 제어
pm-suspend                       # pm-utils (레거시)

# Hibernate
systemctl hibernate              # S4
echo disk > /sys/power/state     # 직접 제어

# 절전 상태 확인
cat /sys/power/state             # 지원되는 상태 목록
cat /sys/power/mem_sleep         # s2idle vs deep(S3) 선택
cat /sys/power/disk              # hibernate 모드: platform/shutdown/reboot

# 리셋 메서드 확인/변경
cat /sys/kernel/reboot/mode      # cold / warm
cat /sys/kernel/reboot/type      # kbd / acpi / efi / pci / triple

# kexec 빠른 재부팅
kexec -l /boot/vmlinuz-$(uname -r) \
    --initrd=/boot/initrd.img-$(uname -r) \
    --command-line="$(cat /proc/cmdline)"
kexec -e                         # POST 건너뛰고 새 커널로 직접 점프

# 종료 디버깅
dmesg | grep -i "reboot\|shutdown\|power"   # 커널 종료 메시지
journalctl -b -1 --no-pager | tail -50      # 이전 부팅의 마지막 로그
cat /sys/power/pm_debug_messages             # PM 디버그 메시지 활성화
echo 1 > /sys/power/pm_debug_messages       # suspend 상세 로그 켜기

전원 관리 주의사항

전원 관리 핵심 고려사항:
  • Suspend/Resume 순서 — suspend: 유저 프로세스 동결 → 디바이스 late_suspend → noirq_suspend. resume은 역순. 의존성 있는 디바이스는 device_link로 순서 보장(Ordering)
  • Runtime PM 균형pm_runtime_get/put 쌍이 불균형이면 영원히 잠들거나 깨어나지 못함. pm_runtime_get_sync 실패 시 put 호출 누락 주의
  • IRQ 안전성 — noirq 단계에서는 인터럽트가 비활성. 이 단계의 suspend/resume 콜백에서 인터럽트 의존 코드 사용 금지
  • 클럭/레귤레이터 — suspend 시 클럭 비활성화 후 resume에서 복원 안 하면 디바이스 동작 불능. clk_prepare_enable/clk_disable_unprepare 쌍 필수
  • wakeup 소스device_init_wakeup()으로 등록한 디바이스만 suspend 상태에서 시스템을 깨울 수 있음. cat /sys/power/wakeup_count
  • C-state 지연 트레이드오프 — 깊은 C-state는 전력 절감이 크지만 복귀 지연(exit latency)도 큼. HFT/실시간(Real-time) 환경에서는 C1 이하로 제한
  • Hibernate 메모리 — 전체 RAM 내용을 디스크에 기록. swap 파티션이 RAM보다 작으면 hibernate 실패. resume= 커널 파라미터 필수
  • ACPI 의존성 — x86 전원 관리는 ACPI에 크게 의존. DSDT/SSDT 테이블 버그가 suspend 실패의 주요 원인. acpidumpiasl로 디버깅

PM 디버깅

pm_test 모드

Suspend를 단계별로 테스트하여 문제 구간을 좁힐 수 있습니다:

# 테스트 모드 설정 (해당 단계까지만 진행 후 resume)
echo freezer > /sys/power/pm_test    # 프로세스 동결만 테스트
echo devices > /sys/power/pm_test    # 디바이스 suspend까지
echo platform > /sys/power/pm_test   # 플랫폼 콜백까지
echo processors > /sys/power/pm_test # CPU 비활성화까지
echo core > /sys/power/pm_test       # 코어까지 (실제 진입 직전)

# 테스트 실행
echo mem > /sys/power/state

# 테스트 후 복원
echo none > /sys/power/pm_test

suspend_stats

# Suspend 통계 확인
cat /sys/power/suspend_stats/success     # 성공 횟수
cat /sys/power/suspend_stats/fail        # 실패 횟수
cat /sys/power/suspend_stats/last_failed_dev   # 마지막 실패 디바이스
cat /sys/power/suspend_stats/last_failed_step  # 마지막 실패 단계

pm_trace & PM 디버그 메시지

# pm_trace 활성화 (RTC에 해시 저장, 실패 디바이스 추적)
echo 1 > /sys/power/pm_trace
echo mem > /sys/power/state
# resume 후 dmesg에서 "hash matches" 메시지 확인

# 상세 PM 로그 활성화
echo 1 > /sys/power/pm_debug_messages

# initcall 디버깅 (부팅 시 느린 PM 콜백 찾기)
# 커널 cmdline: initcall_debug pm_debug_messages

ftrace PM 이벤트

# PM 관련 tracepoint
echo 1 > /sys/kernel/debug/tracing/events/power/suspend_resume/enable
echo 1 > /sys/kernel/debug/tracing/events/power/cpu_idle/enable
echo 1 > /sys/kernel/debug/tracing/events/power/device_pm_callback_start/enable
echo 1 > /sys/kernel/debug/tracing/events/power/device_pm_callback_end/enable

# suspend/resume 시간 프로파일링
echo mem > /sys/power/state
cat /sys/kernel/debug/tracing/trace

흔한 문제 & 해결

증상원인진단/해결
Suspend 시 시스템 정지디바이스 드라이버 콜백 무한 대기pm_test=devices로 범위 좁히기, pm_debug_messages=1
즉시 Wake-upSpurious wakeup source/proc/acpi/wakeup에서 소스 비활성화, wakeup_count 사용
Resume 후 디바이스 동작 안 함resume 콜백에서 레지스터 복원 누락드라이버의 resume/restore 콜백 확인
s2idle에서 전력 소비 높음디바이스가 D0 상태 유지turbostat/powertop으로 C-state 확인, PCI ASPM 점검
Hibernate 복원 실패resume= 파라미터 오류, swap 부족resume=/dev/sdX 확인, image_size 조정
런타임 PM 동작 안 함pm_runtime_enable() 미호출runtime_status sysfs 확인, control=auto 설정

PM 도구 요약

도구용도패키지
powertop전력 소비 분석, 튜닝 제안powertop
turbostatC-state/P-state/전력 실시간 모니터링linux-tools
cpupowercpufreq/cpuidle 설정 조회/변경linux-tools
sleepgraphsuspend/resume 타임라인 HTML 보고서sleepgraph (pm-graph)
intel_gpu_topIntel GPU 전력/활동 모니터링intel-gpu-tools
rapl-readRAPL 에너지 카운터 직접 읽기커스텀/perf

커널 설정 종합

Kconfig 옵션설명기본값
CONFIG_PMPM 프레임워크 전체Y
CONFIG_PM_SLEEP시스템 절전 (suspend/hibernate) 지원Y
CONFIG_SUSPENDsuspend-to-RAM (S3) / s2idleY
CONFIG_HIBERNATIONHibernate (S4, swap 이미지)Y (데스크톱)
CONFIG_PM_AUTOSLEEPautosleep (/sys/power/autosleep)N
CONFIG_PM_WAKELOCKS사용자 공간(User Space) wakelock (Android)N
CONFIG_PM_DEBUGPM 디버그 기능 (pm_test, suspend_stats)N
CONFIG_PM_TRACEpm_trace (RTC 기반 디바이스 추적)N
CONFIG_CPU_IDLEcpuidle 프레임워크Y
CONFIG_CPU_IDLE_GOV_MENUmenu 거버너Y
CONFIG_CPU_IDLE_GOV_TEOTEO 거버너Y
CONFIG_CPU_IDLE_GOV_HALTPOLLhaltpoll 거버너 (KVM)M
CONFIG_INTEL_IDLEintel_idle 드라이버Y (x86)
CONFIG_PM_GENERIC_DOMAINSgenpd (PM 도메인)Y
CONFIG_PM_OPPOPP 프레임워크Y (ARM)
CONFIG_POWERCAPpowercap 프레임워크Y
CONFIG_INTEL_RAPLIntel RAPL powercap 드라이버M
CONFIG_REGULATORRegulator 프레임워크Y (ARM)
CONFIG_ENERGY_MODELEnergy ModelY (ARM)
CONFIG_ENERGY_EFFICIENT_SCHEDEAS 활성화 (sched 내부)Y (ARM)

참고 자료

커널 공식 문서

LWN.net 기사

커널 소스 (drivers/base/power/)

전원 관리와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.