전원 관리 (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 및 열 관리는 ACPI 심화, cpufreq/governors/P-State은 ktime / Clock 심화, 전력 MSR/HWP는 MSR 레지스터, PCI D-state/ASPM은 PCI / PCIe 페이지를 참조하십시오.
전제 조건: 전원관리Thermal 서브시스템 문서를 먼저 읽으세요. 전력/열 관리 주제는 성능과 안정성의 절충이 핵심이므로, 제어 루프와 임계값 정책을 함께 이해해야 운영 사고를 줄일 수 있습니다.

핵심 요약

  • Suspend(S3) — RAM에 상태를 유지한 채 CPU/디바이스 전원을 끄는 절전 모드입니다.
  • Hibernate(S4) — RAM 내용을 디스크에 저장하고 완전 전원 차단. 복원 시 디스크에서 복구합니다.
  • 런타임 PM — 개별 디바이스를 사용하지 않을 때 자동으로 저전력 상태로 전환합니다.
  • cpuidle — idle CPU를 C-state(C0→C1→C3...)로 전환하여 전력을 절약합니다.
  • dev_pm_ops — 드라이버가 구현하는 PM 콜백 구조체. 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 중단, 인터럽트 비활성화 등을 수행합니다.

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를 통해 진입합니다.

절전 상태 매핑

/sys/power/state내부 상태ACPI 매핑설명
freezePM_SUSPEND_TO_IDLE (s2idle)S0 (S0ix)프로세스 동결 + 디바이스 저전력 + 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이 이루어집니다.

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()` `thaw_processes()` / `resume_console()` / PM_POST_SUSPEND notifier Suspend 완료 및 사용자 공간 복귀
Wakeup 벡터: S3 진입 전 커널은 FACS(Firmware ACPI Control Structure)의 firmware_waking_vector 필드에 resume 진입점 주소를 기록합니다. CPU가 리셋될 때 펌웨어는 이 주소로 점프하여 커널 resume 코드를 실행합니다. x86_64에서는 리얼 모드 → 보호 모드 → 롱 모드 전환을 다시 수행합니다.
/* === s2idle (Modern Standby / S0ix) ===
 *
 * S3보다 빠른 복귀를 위한 소프트웨어 기반 절전.
 * ACPI S3 없이 커널이 직접 저전력 idle 상태를 관리.
 * Intel: C10 idle state + S0ix 패키지 상태
 *
 * echo s2idle > /sys/power/mem_sleep  → s2idle을 mem의 기본으로 설정
 *
 * 흐름 차이:
 *   S3:     suspend_ops->enter() → 펌웨어가 전원 관리
 *   s2idle: freeze_enter() → cpuidle으로 깊은 idle 진입
 *           → 주기적 tick freeze, 디바이스 Runtime PM 활용
 *
 * /sys/power/mem_sleep:
 *   [s2idle] deep  → 현재 "mem"이 s2idle로 매핑됨
 *   s2idle [deep]  → 현재 "mem"이 S3(deep)으로 매핑됨
 */

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/resume 대상 확인 2단계: 스냅샷 생성 `create_image()` + `dpm_suspend_start()` + `syscore_suspend()` `swsusp_arch_suspend()`에서 CPU 레지스터/메모리 페이지 캡처 이후 임시 resume로 디스크 기록 경로 진입 3단계: 디스크 기록 + 전원 차단 `swsusp_write()` → LZO/LZ4 압축 + `S1SUSPEND` 서명 저장 swap 첫 페이지에 `swsusp_info` 메타데이터 기록 `hibernate_ops->enter()`(S4) 또는 poweroff/reboot 전원 완전 차단 후 재부팅 커널 커맨드라인 `resume=` 기준으로 복원 시도 R1: Resume 탐지 `hibernate_resume_setup()` / `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로 오프셋 확인)
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)최근 타이머 이벤트 패턴 분석, 깊은 상태 적극 선택서버, 주기적 워크로드
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 */

드라이버 구현 예제

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개 콜백을 정의합니다. 실제 드라이버에서는 매크로를 사용하여 간결하게 작성합니다.

콜백 전체 매핑

단계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),
    },
};

DPM 리스트 순서

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

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

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 패치였으나, 커널 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)는 이 정보를 활용하여 태스크를 에너지 효율적인 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() 시스템 콜로 트리거됩니다. 모든 프로세스를 종료하고 디바이스를 정리한 뒤 하드웨어 리셋을 수행합니다.

/* === 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
 *
 * 사용자 공간 → 커널:
 *   systemctl poweroff → systemd가 SIGTERM → SIGKILL → reboot(POWER_OFF)
 *   shutdown -h now    → init 프로세스가 runlevel 0 진입
 *   reboot(LINUX_REBOOT_CMD_POWER_OFF)
 *
 * SYSCALL_DEFINE4(reboot, ...) [kernel/reboot.c]
 *   → 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():
 *   → device_shutdown() + CPU 정지 (hlt 루프)
 *   → 전원은 계속 공급됨 (ATX PSU ON 상태)
 *   → "System halted." 메시지 출력 후 무한 대기
 *   → 수동으로 전원 버튼을 눌러야 함
 *
 * kernel_power_off():
 *   → device_shutdown() + pm_power_off()
 *   → ACPI S5 또는 EFI로 ATX PSU를 OFF
 *   → 전원이 자동으로 차단됨
 *
 * 역사적 배경:
 *   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(파일시스템 읽기전용) → reBoot
  • 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() 시스템 콜 호출
 *    → LINUX_REBOOT_CMD_POWER_OFF (poweroff)
 *    → LINUX_REBOOT_CMD_RESTART  (reboot)
 *
 * 타임아웃 설정 (/etc/systemd/system.conf):
 *   DefaultTimeoutStopSec=90s   → 서비스 정지 타임아웃
 *   FinalKillSignal=SIGKILL     → 타임아웃 후 강제 종료 시그널
 *
 * 종료 지연 디버깅:
 *   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()
 *        → machine_kexec(kexec_image)
 *          → relocate_kernel: 커널 이미지를 최종 위치에 복사
 *          → 새 커널의 startup_64로 점프
 *          → 기존 커널 메모리 해제 → 새 커널 시작
 *
 * kexec와 kdump의 차이:
 *   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로 순서 보장
  • 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/실시간 환경에서는 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사용자 공간 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)

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