- AMD RX 570 기반 Linux 데스크톱에서 RAM 사용량이 높은 상태로 절전하면 복귀 후 검은 화면이나 입력 불능 상태가 반복되어, 단순 화면 문제를 넘어 커널 전원 관리 흐름까지 추적해야 했음
- 핵심 원인은 S3 절전 전에 VRAM을 시스템 RAM으로 백업해야 하는 amdgpu가 여유 RAM 부족 상황에서 디스크 swap을 제대로 활용하지 못하고 OOM으로 무너지는 흐름에 있었음
- VRAM eviction을
dpm_suspend()에서dpm_prepare()로 앞당기는 첫 패치는 SSD 전원 차단과의 경합을 줄였지만,pm_restrict_gfp_mask()가 이미 swap을 비활성화해 연속 메모리 부족이 계속 발생할 수 있었음 - 이후
register_pm_notifier()로PM_SUSPEND_PREPARE시점에amdgpu_device_evict_resources()를 호출하도록 바꾸자, swap과 디스크가 살아 있는 동안 VRAM을 백업할 수 있어 테스트 환경에서는 고 RAM·VRAM 사용량에서도 절전이 성공함 - 이 변경은 amdgpu 트리에 병합됐지만 데드락 가능성 때문에 2025-06에 되돌려졌고, 사용자 프로세스를 먼저 freeze하지 않는 절전 경로에서는 3D 앱이 VRAM을 다시 GPU로 끌어오며 eviction을 막을 수 있음
반복된 절전 실패와 초기 오진
- 데스크톱은 Windows와 Linux를 듀얼부팅하는 구성으로, Linux에서 RAM 사용량이 높을 때 절전을 시도하면 시스템이 자주 깨짐
- 복귀 후 검은 화면과 움직이는 커서만 보이거나, 화면 출력 없이 magic SysRq 또는 하드 리셋에만 반응함
- 일부 경우 KDE 잠금 화면 시계는 실시간으로 갱신되지만 로그인이나 상호작용 시 멈춤
- 테스트 환경은 Gigabyte B550M DS3H, AMD RX 570 GPU, Kingston A2000 1TB NVMe SSD, Arch Linux, systemd-boot, Linux 6.4였음
journalctl --system -b -1로 이전 부팅 로그를 확인하자, 일부 절전 시도에서amdgpu_device_suspend아래 커널 코드의 OOM 오류가 나타남- 많은 실패 사례는 다음 로그에서 끝났고, 깨진 시스템이 다시 깨어난 기록은 남지 않음
systemd-sleep:Entering sleep state 'suspend'...- kernel:
PM: suspend entry (deep)
- 처음에는 NVMe APST 절전 모드를 의심해
nvme_core.default_ps_max_latency_us=0,iommu=soft, SSD 펌웨어 업그레이드, 2TB 부팅 SSD 교체를 시도했지만 문제가 해결되지 않음
실패 위치를 좁힌 디버깅 도구들
- systemd가 여러 절전 모드를 연속 시도하는 동작이 로그 노이즈와 커널 상태 악화를 만들 수 있어
/etc/systemd/sleep.conf에SuspendState=mem을 추가함- 디버깅은 단순해졌지만 근본 원인은 그대로 남아 있었음
echo 1 > /sys/power/pm_trace로 절전 실패 위치를 추적함pm_trace는 절전-복귀 진행 상태를 시스템 시간에 저장함- 비동기 절전이 비활성화되는 부작용 덕분에, amdgpu suspend 실패 후 전체 시스템 hang 대신 복구되는 양상도 확인됨
systemd.debug_shell커널 파라미터를 추가해 KDE나 TTY 로그인 없이도 Ctrl-Alt-F9에서 root shell을 띄울 수 있게 함- USB 컨트롤러와 키보드가 깨지는 경우에 대비해 PS/2 키보드를 사용했고, 이후에는 시리얼 콘솔을 구성함
- 커널 파라미터:
no_console_suspend console=tty0 console=ttyS0,115200 loglevel=8 - 노트북에서
sudo minicom --device /dev/ttyUSB0 --baudrate 115200로 로그를 지속 캡처함 - 시리얼 콘솔은 디스플레이와 네트워크가 깨진 뒤에도 명령 실행과 로그 수집을 가능하게 했지만, 전송 속도와 색상·화면 크기 처리에는 제약이 있었음
- 커널 파라미터:
amdgpu VRAM eviction과 OOM의 연결
- 크래시 로그는 대체로 amdgpu의 TTM 버퍼 eviction 경로에서 발생함
amdgpu_device_evict_resources()amdgpu_ttm_evict_resources()
- 관련 버그 리포트에서 “evict”는 VRAM을 시스템 RAM으로 복사하거나, 시스템 RAM을 swap으로 옮기는 동작과 관련된 것으로 확인됨
- 데스크톱이 S3 절전에 들어가면 PCIe GPU 전원이 끊기고 VRAM 칩 데이터가 사라짐
- GPU 드라이버는 절전 전에 사용 중인 VRAM을 시스템 RAM으로 복사해야 함
- 복귀 후에는 RAM에 저장한 데이터를 다시 복원해야 함
- Linux amdgpu 드라이버에는 사용 중인 VRAM 전체를 담을 여유 RAM이 없을 때, 시스템 RAM을 디스크 기반 swap으로 옮기는 대신 메모리 부족으로 충돌하는 문제가 있었음
- Linux가 suspend 중 OOM을 만나면 절전을 취소하고 장치를 다시 시작하려 하지만, 이미 중단된 절전 때문에 일부 드라이버가 깨지거나 suspend/resume 중 다시 OOM이 날 수 있음
- 비동기 절전이 켜져 있으면 위험이 더 커짐
- 절전 자체는 성공했지만 resume 중 장치 시작 과정에서 OOM이 발생할 수도 있음
첫 번째 해결 시도: VRAM 백업 시점 앞당기기
- Mario Limonciello는
/sys/power/pm_print_times와/sys/power/pm_debug_messages를 켜고 절전 로그를 확인하라고 제안함 - 로그에서는 NVMe와 amdgpu 드라이버가 절전 중
pci_pm_suspend에 병렬 진입하는 것으로 나타남 - 처음에는 GPU suspend를 SSD suspend보다 앞당기는 방법을 찾았고, Linux의 device suspend ordering 메커니즘도 확인함
- 이 메커니즘은 모든 GPU를 모든 시스템 디스크보다 먼저 suspend하는 용도보다는 밀접하게 연결된 주변장치 동기화에 더 가까워 보였음
- Mario는 Linux suspend의
prepare단계에서 VRAM을 evict하는 방식을 제안했고, VRAM eviction을dpm_suspend()에서dpm_prepare()로 옮기는 커널 패치를 작성함 - 변경 전후 흐름은 다음과 같음
- 기존:
dpm_suspend()시점에 VRAM 백업이 실행되어 SSD가 꺼지는 흐름과 겹칠 수 있음 - 변경:
dpm_prepare()에서 VRAM 백업을 먼저 시도하고, 실패하면 다른 장치 suspend 전에 절전을 중단함
- 기존:
- 이 변경은 이전보다 나았지만,
pm_restrict_gfp_mask()가dpm_prepare()보다 먼저 swap을 비활성화한다는 점 때문에 고 RAM 사용량에서 여전히 실패함amdgpu_ttm_evict_resources()는 연속 메모리 부족으로 실패할 수 있음- VRAM을 RAM에 모두 넣더라도 이후 드라이버 할당을 처리할 여유가 부족하면 sleep 또는 wake 중 OOM이 날 수 있음
Ghidra로 찾은 별도 amdgpu 크래시
- 테스트 중
BUG: unable to handle page fault for address: fffffffffffffffc오류가 발생했고, 거의 null에 가까운 포인터 역참조처럼 보였음 - 크래시 로그는
dm_resume+0x200위치를 가리켰지만 소스 코드의 줄 번호는 제공하지 않음 amdgpu.ko커널 모듈을 저장·추출한 뒤 Ghidra로 디컴파일해dm_resume안의 크래시 위치를 커널 소스 라인과 매핑함for_each_new_crtc_in_state(dm->cached_state, crtc, new_crtc_state, i)매크로에서 문제가 발생함dm->cached_state가 유효한 포인터가 아니라fffffffffffffff4였음- 이후
[RSI + 0x8]필드를 읽으며fffffffffffffffc주소에서 page fault가 발생함
- 원인은
dm_suspend()가drm_atomic_helper_suspend()반환값을 바로 포인터에 저장한 흐름에 있었음drm_atomic_helper_suspend()는 유효 포인터 또는ERR_PTR(err)를 반환할 수 있음- OOM으로
-ENOMEM(-12)가 반환됐고, amdgpu suspend 코드가 이를 포인터처럼 역참조한 것으로 판단됨
- Mario는 실패 반환값을 검사하고 suspend를 중단하는 코드를 추가해 이 문제를 수정함
포기한 방향: prepare() 중 swap 허용
- 고 RAM 사용량에서 절전 실패가 계속된 이유는 amdgpu가
dpm_prepare()에서 VRAM을 백업하지만, 이 시점에는 이미pm_restrict_gfp_mask()가 디스크 swap을 비활성화했기 때문임 - 한 가지 시도는
dpm_prepare()에서는 swap을 허용하고,dpm_suspend()가 디스크를 끄기 직전에 swap을 비활성화하는 구조였음 - 이를 위해
pm_restrict_gfp_mask()호출을enter_state()에서 더 깊은dpm_suspend_start()내부로 옮기는 실험을 함 - 실무적 제약이 컸음
pm_restrict_gfp_mask()는kernel/power/power.h에 선언되어 있고kernel/power/suspend.c에서 호출됨- 호출하려던 위치는
drivers/base/power/main.c였고, 이 파일은 일반적으로kernel/power/헤더를 포함하지 않음 - 임시 해킹으로
#include <../kernel/power/power.h>를 추가했지만 upstream에는 설득이 필요한 구조였음
- 하이브리드 절전에서도 정확성 문제가 있었음
- 하이브리드 절전은 시스템 이미지를 저장하며
pm_restrict_gfp_mask()를 호출한 뒤 swap이 비활성화된 상태로suspend_devices_and_enter()를 호출함 - 같은 함수를 다시 호출하면 경고가 발생하고
pm_restore_gfp_mask()가 swap을 다시 켜지 못할 수 있음
- 하이브리드 절전은 시스템 이미지를 저장하며
- 테스트에서는 실패 또는 크래시 빈도가 줄었지만 완전히 해결되지는 않았고, core power management를 건드리는 취약한 변경이라 upstream 시도는 하지 않음
사용자 공간 우회책: amdgpu-sleep
- 2024-10에 SuperTuxKart를 종료한 뒤 절전했다가 복귀 시 검은 화면이 발생함
- 로그상 첫 절전 시도는
dpm_prepare()에서 OOM이 났고, 다음 시도는 resume 중 amdgpu의bw_calcs()메모리 할당 크래시로 이어짐
- 로그상 첫 절전 시도는
- NVIDIA의 사용자 공간 VRAM 백업 방식을 참고함
- NVIDIA는 systemd가 kernel suspend를 호출하기 전후로 실행되는 서비스에서
/proc/driver/nvidia/suspend에 기록해 VRAM 백업·복원을 수행함
- NVIDIA는 systemd가 kernel suspend를 호출하기 전후로 실행되는 서비스에서
- 이를 바탕으로 Arch Linux용 amdgpu-sleep 패키지를 만듦
- 절전 전에
/sys/kernel/debug/dri/1/amdgpu_evict_vram을 읽음 - 이 debug endpoint는 amdgpu에 모든 VRAM을 시스템 RAM으로 저장하라고 지시함
- systemd는 GPU VRAM eviction이 끝날 때까지 기다린 뒤 kernel suspend를 시작함
- 절전 전에
- 데스크톱에서 절전할 때는 빠르게 VRAM을 메모리로 복사하고, 필요한 경우 RAM을 swap으로 밀어내며 성공함
- 여러 3D 앱이 실행 중일 때는 앱들이 계속 프레임을 렌더링하며 VRAM을 다시 GPU로 가져와
amdgpu_evict_vram과 줄다리기 라이브락이 발생함- 이 상태는 70초 넘게 지속된 뒤
amdgpu_evict_vram이 포기함 - 이후 systemd가 kernel suspend를 시작해 userspace를 freeze하면 kernel 단계에서는 VRAM eviction이 성공함
- 이 상태는 70초 넘게 지속된 뒤
- 이 스크립트는 livelock이 생기는 빈도보다 스크립트를 끈 상태에서 kernel-level crash가 나는 빈도가 낮지 않아 계속 사용됨
최종 패치: power management notifier
- 2024-11 Mario는 swap이 아직 활성화된 동안 eviction을 허용하는 패치 테스트를 요청함
- 패치는
register_pm_notifier()를 호출하는 구조였고, Linux의 power management notifier API를 사용함 - 콜백은
PM_HIBERNATION_PREPARE와PM_SUSPEND_PREPARE메시지를 받아amdgpu_device_evict_resources()를 호출함 PM_SUSPEND_PREPARE는enter_state() → suspend_prepare()에서pm_notifier_call_chain_robust(PM_SUSPEND_PREPARE, PM_POST_SUSPEND)를 통해 발행됨- notifier 콜백이 있는 드라이버에
PM_SUSPEND_PREPARE가 전달됨 - 실패가 발생하면 이미 준비한 드라이버에
PM_POST_SUSPEND가 전달되고 절전은 중단됨
- notifier 콜백이 있는 드라이버에
- 이 위치에서 VRAM을 evict하면
pm_restrict_gfp_mask()가 swap을 비활성화하기 전이고, 디스크도 아직 freeze되지 않은 상태임 - 수정된 흐름은 다음과 같음
suspend_prepare()PM_SUSPEND_PREPAREamdgpu_device_pm_notifier() → amdgpu_device_evict_resources()pm_restrict_gfp_mask()dpm_prepare()dpm_suspend()
- 수정된 amdgpu 드라이버를 포함한 커스텀 커널을 빌드해 테스트한 결과, 고 RAM·VRAM 사용량에서 여러 차례 절전해도 오류가 발생하지 않음
- 다만 amdgpu가 PipeWire나 kernel이 스피커를 음소거하기 전에 VRAM을 백업하면서 몇 초간 오디오가 반복 재생되는 현상이 나타남
- 몇 차례 코드 리뷰 후 이 변경은 amdgpu tree에 병합됐고, 1년 넘는 시도 끝에 해당 버그를 해결하는 단계로 이어짐
2025-06 업데이트와 되돌림
- 처음에는 변경이 stable Linux kernel 6.14에 포함될 것으로 예상됐지만, 이후 가능한 데드락 때문에 revert됨
- 새로 도입된 문제는 systemd가 모든 사용자 공간 프로세스를 먼저 freeze하지 않고 system suspend를 호출하는 경우에 발생함
- kernel이 자체적으로 프로세스를 freeze하기 전에 VRAM을 시스템 RAM으로 evict하려고 함
- 동시에 3D 프로그램이 VRAM을 다시 GPU로 가져오면 suspend eviction 과정이 deadlock될 수 있음
- 이는 사용자 공간
amdgpu_evict_vram우회책에서 겪은 라이브락과 유사함 - Linux PM maintainer는
pm_restrict_gfp_mask()호출을suspend_devices_and_enter()안쪽으로 옮기는 방안을 제안함- 이는 앞서 실험했던
prepare()중 swap 허용 방향과 맞닿아 있음
- 이는 앞서 실험했던
- 직접 구현·제출할 계획은 없음
- AMD GPU를 더 느린 CPU와 8GB 메모리를 가진 오래된 컴퓨터로 옮겼고, 그 환경에서 커널 빌드를 돌리고 싶지 않기 때문임
- 메인 컴퓨터는 Intel Arc B570으로 업그레이드했지만, 10GB VRAM에서도 같은 유형의 문제가 발생함
- 해당 문제는 drm/xe kernel bug tracker에 보고됨