전원 관리 (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/디바이스 전원을 끄는 절전 모드입니다.
- Hibernate(S4) — RAM 내용을 디스크에 저장하고 완전 전원 차단. 복원 시 디스크에서 복구합니다.
- 런타임 PM — 개별 디바이스를 사용하지 않을 때 자동으로 저전력 상태로 전환합니다.
- cpuidle — idle CPU를 C-state(C0→C1→C3...)로 전환하여 전력을 절약합니다.
- dev_pm_ops — 드라이버가 구현하는 PM 콜백 구조체. 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 중단, 인터럽트 비활성화 등을 수행합니다.
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를 통해 진입합니다.
절전 상태 매핑
/sys/power/state | 내부 상태 | ACPI 매핑 | 설명 |
|---|---|---|---|
freeze | PM_SUSPEND_TO_IDLE (s2idle) | S0 (S0ix) | 프로세스 동결 + 디바이스 저전력 + 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이 이루어집니다.
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 진입점 주소를 기록합니다. 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 직전 상태로 되돌립니다.
- swap 파티션/파일 크기 ≥ 사용 중인 RAM (
free -h의 used 기준) - 커널 파라미터:
resume=/dev/sdXN또는resume=UUID=... - initramfs에 resume 모듈 포함:
/etc/initramfs-tools/conf.d/resume - swap 파일 사용 시:
resume_offset=파라미터 추가 필요 (filefrag -v로 오프셋 확인)
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) | 최근 타이머 이벤트 패턴 분석, 깊은 상태 적극 선택 | 서버, 주기적 워크로드 |
| 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 */
드라이버 구현 예제
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개 콜백을 정의합니다. 실제 드라이버에서는 매크로를 사용하여 간결하게 작성합니다.
콜백 전체 매핑
| 단계 | 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),
},
};
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 예시)
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 패치였으나, 커널 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)는 이 정보를 활용하여 태스크를 에너지 효율적인 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 토폴로지: 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() 시스템 콜로 트리거됩니다. 모든 프로세스를 종료하고 디바이스를 정리한 뒤 하드웨어 리셋을 수행합니다.
/* === 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, ...) [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로 소프트웨어 전원 차단 가능
*/
- SysRq+O —
emergency_power_off(): notifier/device_shutdown 없이 즉시 전원 차단 - SysRq+B —
emergency_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 실패의 주요 원인.
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 | 사용자 공간 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) |
관련 문서
전원 관리와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.