전원 관리 (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)까지 운영 관점으로 다룹니다.
핵심 요약
- 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 등의 동작을 정의합니다.
단계별 이해
- 시스템 절전 체험 —
systemctl suspend로 시스템을 S3 절전에 진입시킵니다.전원 버튼이나 키보드로 깨우면 모든 상태가 복원됩니다.
- 절전 상태 확인 —
cat /sys/power/state로 지원되는 절전 상태를 확인합니다.freeze(s2idle),mem(S3),disk(hibernate) 등이 표시됩니다. - 런타임 PM 확인 —
/sys/bus/pci/devices/*/power/runtime_status로 각 PCI 디바이스의 PM 상태를 볼 수 있습니다.active,suspended,unsupported중 하나입니다. - 드라이버 관점 — 드라이버는
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 서브시스템 계층 다이어그램
시스템 절전 상태 (System Sleep)
Linux 커널은 세 가지 시스템 절전 상태를 지원하며, /sys/power/state를 통해 진입합니다.
절전 상태 매핑(Mapping)
/sys/power/state | 내부 상태 | ACPI 매핑 | 설명 |
|---|---|---|---|
freeze | PM_SUSPEND_TO_IDLE (s2idle) | S0 (S0ix) | 프로세스(Process) 동결 + 디바이스 저전력 + CPU idle. 가장 빠른 진입/복귀 |
mem | PM_SUSPEND_MEM (S2RAM) | S3 | RAM 자체 리프레시 유지, 나머지 전원 차단. mem_sleep_default로 s2idle 대체 가능 |
disk | PM_SUSPEND_DISK (hibernate) | S4 | 메모리 이미지를 swap에 기록 후 전원 차단. resume= 파라미터로 복원 |
/sys/power/mem_sleep에서 현재 설정을 확인할 수 있습니다.Suspend 진입 흐름
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 기준)의 핵심 호출 경로입니다.
/* 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_PREPAREnotifier를 호출하여 사용자 공간 데몬에게 suspend를 알리고,freeze_processes()로 모든 사용자 프로세스와 커널 스레드를 동결합니다.suspend_finish(): resume 후thaw_processes()로 동결을 해제하고PM_POST_SUSPENDnotifier를 호출합니다.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_sleep | mem 상태의 실제 모드 (s2idle, [deep]) |
/sys/power/disk | hibernate 모드 (platform, shutdown, reboot, suspend, test_resume) |
/sys/power/image_size | hibernate 이미지 목표 크기 (기본 RAM의 2/5) |
/sys/power/wakeup_count | wakeup 이벤트 카운터 (spurious wakeup 방지) |
/sys/power/pm_test | suspend 테스트 모드 (none, core, processors, platform, devices, freezer) |
/sys/power/suspend_stats | suspend 성공/실패 통계 |
Suspend-to-RAM (S3) 상세 흐름
사용자가 echo mem > /sys/power/state 또는 systemctl suspend를 실행하면, 커널은 다단계 절차를 거쳐 시스템을 S3 상태로 진입시킵니다.
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 출력 해석:
[s2idle] deep— 현재 "mem"이 s2idle로 매핑되어 있습니다.s2idle [deep]— 현재 "mem"이 S3(deep)으로 매핑되어 있습니다.
Hibernate (S4) 상세 흐름
Hibernate는 전체 시스템 메모리를 디스크에 저장하고 전원을 완전히 차단합니다. 전원이 복구되면 디스크에서 메모리 이미지를 복원하여 suspend 직전 상태로 되돌립니다.
- swap 파티션/파일 크기 ≥ 사용 중인 RAM (
free -h의 used 기준) - 커널 파라미터:
resume=/dev/sdXN또는resume=UUID=... - initramfs에 resume 모듈 포함:
/etc/initramfs-tools/conf.d/resume - swap 파일 사용 시:
resume_offset=파라미터 추가 필요 (filefrag -v로 오프셋(Offset) 확인)
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) 이벤트 패턴 분석, 깊은 상태 적극 선택 | 서버, 주기적 워크로드 |
| ladder | C-state를 단계적으로 승격/강등 | 틱 기반(주로 레거시) |
| haltpoll | 짧은 busy-poll 후 halt, VM 게스트 최적화 | KVM 게스트 |
cpuidle 드라이버
| 드라이버 | 플랫폼 | C-state 소스 |
|---|---|---|
intel_idle | Intel 프로세서 | 하드코딩된 MWAIT 힌트 테이블 (CPUID 기반) |
acpi_idle | ACPI 지원 시스템 | ACPI _CST 메서드 (FADT C-state 정보) |
psci_idle (ARM) | ARM64 플랫폼 | PSCI cpu_suspend + DT idle-states |
cpuidle 아키텍처 다이어그램
sysfs 인터페이스
런타임 PM
런타임 PM은 시스템이 동작 중일 때 개별 디바이스의 전원을 동적으로 관리합니다. 사용하지 않는 디바이스를 자동으로 suspend하여 전력을 절감합니다.
상태 전환 다이어그램
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_delayms 이내에 새로운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
디바이스 PM (dev_pm_ops)
struct dev_pm_ops는 디바이스가 시스템 절전과 런타임 PM 이벤트에 응답하기 위한 22개 콜백을 정의합니다. 실제 드라이버에서는 매크로(Macro)를 사용하여 간결하게 작성합니다.
콜백 전체 매핑
| 단계 | Suspend | Hibernate (freeze) | Hibernate (restore) |
|---|---|---|---|
| prepare | prepare | prepare | prepare |
| 메인 | suspend | freeze | restore |
| late | suspend_late | freeze_late | restore_early |
| noirq | suspend_noirq | freeze_noirq | restore_noirq |
| ← 하드웨어 절전/복원 → | |||
| noirq | resume_noirq | thaw_noirq | — |
| early | resume_early | thaw_early | — |
| 메인 | resume | thaw | — |
| complete | complete | complete | complete |
편의 매크로
/* 시스템 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_ops는 include/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가 비활성화된 상태에서 호출됩니다. PCIpci_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 플래그가 설정된 디바이스는 별도 워크큐에서 병렬 처리됩니다.
/* 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 예시)
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 sysfs | NO_POWER_OFF 등 디바이스별 PM 정책 |
| Frequency QoS | cpufreq 내부 | 최소/최대 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 인터페이스
/sys/power/autosleep에 mem을 쓰면 시스템은 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 + autosleep | wakelocks → PM wakeup sources (3.5+) |
| Doze 모드 | alarm_timer + suspend | Android 6.0+, 유휴 시 앱 활동 제한 |
| App Standby Buckets | cgroup freezer + cpuset | Android 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 활성화 조건
- 비대칭 CPU 토폴로지(Topology): big.LITTLE 또는 Intel Hybrid(P-core/E-core) (
SD_ASYM_CPUCAPACITY) - Energy Model 등록: cpufreq 드라이버가
em_dev_register_perf_domain()호출 - schedutil 거버너: cpufreq 거버너가 schedutil이어야 함
- 오버유틸 아님: root domain의 총 utilization이 capacity의 80% 미만
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 연동 다이어그램
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가 비활성화되어 주파수가 자동으로 하향됩니다.
Regulator 프레임워크
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;
};
};
# 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
Power Domain과 레귤레이터
SoC의 Power Domain은 칩 내부의 전력 영역을 독립적으로 ON/OFF할 수 있는 하드웨어 기능입니다. 레귤레이터가 보드 레벨의 외부 전원을 제어한다면, Power Domain은 SoC 내부의 전력 게이팅을 제어합니다. 두 시스템은 함께 작동하여 최대한의 전력 절감을 달성합니다.
| 구분 | Regulator | Power 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 → 무조건적 리셋
* → 다른 모든 방법이 실패했을 때의 최후의 수단 */
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, ...)에서 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를 통한 소프트웨어 전원 차단이 가능해졌습니다.
- SysRq+O —
emergency_power_off(): notifier/device_shutdown 없이 즉시 전원 차단 - SysRq+B —
emergency_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 실행 시 다음 순서로 진행됩니다:
- systemd가 shutdown.target 활성화 — 모든 서비스 유닛을 역순으로 정지합니다.
ExecStop=또는 SIGTERM을 전송하며,TimeoutStopSec후 SIGKILL로 강제 종료합니다. - 파일시스템 언마운트 — systemd-shutdown이 모든 마운트를 해제합니다. 루트 파일시스템은 읽기 전용으로 리마운트합니다.
- reboot() 시스템 콜 호출 — poweroff 시
LINUX_REBOOT_CMD_POWER_OFF, reboot 시LINUX_REBOOT_CMD_RESTART를 사용합니다.
타임아웃 설정 (/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 단계를 건너뜁니다. 서버 환경에서 재부팅 시간을 수십 초에서 수 초로 단축할 수 있습니다.
kexec -l /boot/vmlinuz --initrd=/boot/initrd.img --command-line="root=/dev/sda1 ..."— 새 커널을 메모리에 미리 로드합니다 (segments 준비).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 실패의 주요 원인.
acpidump와iasl로 디버깅
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-up | Spurious 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 |
turbostat | C-state/P-state/전력 실시간 모니터링 | linux-tools |
cpupower | cpufreq/cpuidle 설정 조회/변경 | linux-tools |
sleepgraph | suspend/resume 타임라인 HTML 보고서 | sleepgraph (pm-graph) |
intel_gpu_top | Intel GPU 전력/활동 모니터링 | intel-gpu-tools |
rapl-read | RAPL 에너지 카운터 직접 읽기 | 커스텀/perf |
커널 설정 종합
| Kconfig 옵션 | 설명 | 기본값 |
|---|---|---|
CONFIG_PM | PM 프레임워크 전체 | Y |
CONFIG_PM_SLEEP | 시스템 절전 (suspend/hibernate) 지원 | Y |
CONFIG_SUSPEND | suspend-to-RAM (S3) / s2idle | Y |
CONFIG_HIBERNATION | Hibernate (S4, swap 이미지) | Y (데스크톱) |
CONFIG_PM_AUTOSLEEP | autosleep (/sys/power/autosleep) | N |
CONFIG_PM_WAKELOCKS | 사용자 공간(User Space) wakelock (Android) | N |
CONFIG_PM_DEBUG | PM 디버그 기능 (pm_test, suspend_stats) | N |
CONFIG_PM_TRACE | pm_trace (RTC 기반 디바이스 추적) | N |
CONFIG_CPU_IDLE | cpuidle 프레임워크 | Y |
CONFIG_CPU_IDLE_GOV_MENU | menu 거버너 | Y |
CONFIG_CPU_IDLE_GOV_TEO | TEO 거버너 | Y |
CONFIG_CPU_IDLE_GOV_HALTPOLL | haltpoll 거버너 (KVM) | M |
CONFIG_INTEL_IDLE | intel_idle 드라이버 | Y (x86) |
CONFIG_PM_GENERIC_DOMAINS | genpd (PM 도메인) | Y |
CONFIG_PM_OPP | OPP 프레임워크 | Y (ARM) |
CONFIG_POWERCAP | powercap 프레임워크 | Y |
CONFIG_INTEL_RAPL | Intel RAPL powercap 드라이버 | M |
CONFIG_REGULATOR | Regulator 프레임워크 | Y (ARM) |
CONFIG_ENERGY_MODEL | Energy Model | Y (ARM) |
CONFIG_ENERGY_EFFICIENT_SCHED | EAS 활성화 (sched 내부) | Y (ARM) |
참고 자료
커널 공식 문서
- Power Management — The Linux Kernel documentation
- Power Management (admin-guide) — 관리자 관점의 전원 관리 가이드
- System Sleep States — Suspend-to-Idle, Standby, Suspend-to-RAM, Hibernate
- Runtime Power Management Framework — 런타임 PM 콜백, 자동 서스펜드
- PCI Power Management — PCI D-state, ASPM, PME 웨이크업
- Device Power Management Basics — 드라이버 PM 콜백 구현 가이드
- Energy Model — EAS가 사용하는 에너지 모델 프레임워크
- Operating Performance Points (OPP) — 전압/주파수 쌍 관리
- Regulator Framework Overview — PMIC 전압/전류 제어
- PM Notifiers — 서스펜드/레쥼 알림 체인
LWN.net 기사
- The runtime power management framework — 런타임 PM 프레임워크 심층 해설
- Autosleep and wake locks — 안드로이드 wakelocks와 커널 autosleep
- Power domains and performance states — genpd 전원 도메인 설계
- Energy-aware scheduling — EAS 스케줄러 전력 최적화
- The managed device resource API — devm 리소스 관리와 PM 통합
커널 소스 (drivers/base/power/)
kernel/power/suspend.c— Suspend-to-RAM/Hibernate 핵심 경로kernel/power/qos.c— PM QoS 지연시간/처리량 제약drivers/base/power/runtime.c— Runtime PM 코어 구현drivers/base/power/domain.c— Generic Power Domain (genpd)drivers/base/power/wakeup.c— 웨이크업 소스 관리
관련 문서
전원 관리와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.