전체 흐름
높은 order의 물리 페이지 할당이 조각화로 막힐 때 compaction은 이동 가능한 folio를 free page로 옮겨 큰 연속 free 공간을 만들려고 시도한다. 이동 횟수와 실제 할당 성공은 다르다.
- 고차 할당 요구요청 order 크기의 연속 free block 필요
- 적합성 판단zone watermark와 이동 목적지 여유 검사
- 양쪽 PFN 검사낮은 쪽은 이동 후보, 높은 쪽은 free 목적지
- 종료 검사적합한 block·capture·중단·스캔 완료 구분
직접 요청의 설명 순서다. 다른 compaction 호출자는 별도 경로를 갖는다. mm/compaction.c:2618; mm/compaction.c:2381
| 작업 | 하는 일 | 보장하지 않는 것 |
|---|---|---|
| compaction | 이동 가능한 folio를 옮겨 연속 free 공간을 모으려 함 | 사용 중인 메모리를 즉시 회수 |
| reclaim | page를 되찾아 전체 free page 수를 늘림 | 고차 연속 free block 형성 |
| COMPACT_SUCCESS | 이번 compaction의 성공 조건 충족 | 후속 할당의 성공 |
근거: mm/compaction.c:2381; mm/compaction.c:2169. 일반 order-0 할당과 직접 고차 compaction을 구분한다.
소스 코드 위치
먼저 확인할 Linux v6.6 소스 파일
Linux v6.6: mm/compaction.c
Linux v6.6: mm/migrate.c
Linux v6.6: include/linux/compaction.h
설명: 진입 함수가 있는 파일과 구조체 정의, 아키텍처별 연결 코드, 콜백 구현을 함께 확인한다. 여러 파일을 대조해야 호출 관계와 각 코드의 역할을 알 수 있다.
대표 코드
isolate_migratepages · migrate scanner
(cc->mode != MIGRATE_SYNC ? ISOLATE_ASYNC_MIGRATE : 0);
bool fast_find_block;
/*
* Start at where we last stopped, or beginning of the zone as
* initialized by compact_zone(). The first failure will use
* the lowest PFN as the starting point for linear scanning.
*/
low_pfn = fast_find_migrateblock(cc);
block_start_pfn = pageblock_start_pfn(low_pfn);
if (block_start_pfn < cc->zone->zone_start_pfn)
block_start_pfn = cc->zone->zone_start_pfn;
/*
* fast_find_migrateblock() has already ensured the pageblock is not
* set with a skipped flag, so to avoid the isolation_suitable check
* below again, check whether the fast search was successful.
*/
fast_find_block = low_pfn != cc->migrate_pfn && !cc->fast_search_fail;
/* Only scan within a pageblock boundary */
block_end_pfn = pageblock_end_pfn(low_pfn);
/*
* Iterate over whole pageblocks until we find the first suitable.
* Do not cross the free scanner.
*/
for (; block_end_pfn <= cc->free_pfn;
fast_find_block = false,
cc->migrate_pfn = low_pfn = block_end_pfn,
block_start_pfn = block_end_pfn,
block_end_pfn += pageblock_nr_pages) {
/*
* This can potentially iterate a massively long zone with
* many pageblocks unsuitable, so periodically check if we
* need to schedule.
*/
if (!(low_pfn % (COMPACT_CLUSTER_MAX * pageblock_nr_pages)))
cond_resched();
확인 사항: 이 코드에서는 반환값보다 상태 변경을 먼저 확인한다. 어느 잠금을 획득한 뒤 어떤 필드를 바꾸는지, 실패 시 어느 레이블로 분기하는지, 변경된 상태를 다음 호출자가 어떤 전제로 사용하는지 추적한다.
compact_zone_order
VM_BUG_ON(!list_empty(&cc->migratepages));
return ret;
}
static enum compact_result compact_zone_order(struct zone *zone, int order,
gfp_t gfp_mask, enum compact_priority prio,
unsigned int alloc_flags, int highest_zoneidx,
struct page **capture)
{
enum compact_result ret;
struct compact_control cc = {
.order = order,
.search_order = order,
.gfp_mask = gfp_mask,
.zone = zone,
.mode = (prio == COMPACT_PRIO_ASYNC) ?
MIGRATE_ASYNC : MIGRATE_SYNC_LIGHT,
.alloc_flags = alloc_flags,
.highest_zoneidx = highest_zoneidx,
.direct_compaction = true,
.whole_zone = (prio == MIN_COMPACT_PRIORITY),
.ignore_skip_hint = (prio == MIN_COMPACT_PRIORITY),
.ignore_block_suitable = (prio == MIN_COMPACT_PRIORITY)
};
struct capture_control capc = {
.cc = &cc,
.page = NULL,
};
/*
* Make sure the structs are really initialized before we expose the
* capture control, in case we are interrupted and the interrupt handler
* frees a page.
*/
barrier();
WRITE_ONCE(current->capture_control, &capc);
ret = compact_zone(&cc, &capc);
확인 사항: 이 코드에서는 반환값보다 상태 변경을 먼저 확인한다. 어느 잠금을 획득한 뒤 어떤 필드를 바꾸는지, 실패 시 어느 레이블로 분기하는지, 변경된 상태를 다음 호출자가 어떤 전제로 사용하는지 추적한다.
함수별 분석
compact_zone_order()가 compact_zone()을 호출한다. 후자는 격리 후보를 찾고 성공한 경우에만 migration을 호출한다.
- try_to_compact_pageszone별 직접 요청
- compact_zone_ordercompact_control·capture 설정
- compact_zonescanner와 반복 종료 관리
- isolate → migrateSUCCESS면 migrate_pages 호출
둘째 함수가 셋째 함수를 호출한다. compact_zone_order()가 folio를 직접 격리하지 않는다. mm/compaction.c:2618; mm/compaction.c:2381
| 함수 | 책임 | 분기·결과 |
|---|---|---|
| compact_zone_order | order·mode·zone을 compact_control에 설정 | compact_zone 호출 뒤 capture 여부 검사 |
| compact_zone | 두 scanner 시작 위치와 루프 제어 | compact_finished가 CONTINUE일 동안 반복 |
| isolate_migratepages | 낮은 PFN에서 이동 후보 격리 | NONE / SUCCESS / ABORT |
| migrate_pages | compaction_alloc로 목적지 folio를 찾아 이동 시도 | 실패 후보 putback 또는 재검사 |
v6.6 고정 소스: mm/compaction.c:2618, mm/compaction.c:2381, mm/compaction.c:1967.
구조체와 필드
compact_control은 zone·order·mode와 두 스캐너의 PFN, 격리된 후보와 목적지 목록을 함께 담는다.
cached PFN·free-list 빠른 탐색·skip hint가 있으므로 매번 인접 pageblock을 한 칸씩 움직이지 않는다. mm/compaction.c:2381; mm/compaction.c:1763
| 필드 | 뜻 | 읽을 때 주의 |
|---|---|---|
| order / mode | 요청 크기와 migration 방식 | ASYNC와 SYNC_LIGHT 등 결과가 다를 수 있음 |
| migrate_pfn | 낮은 쪽 이동 후보 탐색 위치 | cached 시작점·skip hint 반영 |
| free_pfn | 높은 쪽 free 대상 탐색 위치 | free-list 빠른 탐색도 가능 |
| migratepages / freepages | 격리된 후보와 목적지 목록 | 격리 자체는 migration 완료가 아님 |
실행 단계와 상태 변화
격리, 실제 이동, 적합한 고차 free block의 발견은 서로 다른 사건이다. 격리 실패와 중단도 같은 결과가 아니다.
v6.6 compact_zone()의 분기: mm/compaction.c:2490; 결과 검사: mm/compaction.c:2169.
| 결과 | 조건 | 해석 |
|---|---|---|
| COMPACT_SUCCESS | 직접 요청에서 적합한 free block 또는 captured page | 후속 allocation 성공 보장은 아님 |
| COMPACT_COMPLETE | scanner 만남, whole_zone 검사 | 스캔 완료 |
| COMPACT_PARTIAL_SKIPPED | scanner 만남 후 일부 범위 생략 또는 proactive 조기 종료 | 부분 검사 종료 |
| COMPACT_CONTENDED | 경합·fatal signal·격리 중단 | 이번 시도 중단 |
| COMPACT_NO_SUITABLE_PAGE → COMPACT_CONTINUE | 아직 적합한 free block이 없음 | 계속 검사 |
불변 조건과 실패 사례
compaction의 핵심 객체가 외부에 공개된 뒤에는 마지막 참조가 사라질 때까지 해제 경로가 callback, timer, IRQ, worker와 경합하지 않아야 한다.
다른 CPU나 하위 계층이 변경된 필드를 읽는다면 잠금과 메모리 배리어가 접근 순서를 어떻게 보장하는지 확인한다. 참조 횟수와 RCU로 관리하는 객체 수명도 별도로 확인한다.
중간 단계에서 실패하면 이미 확보한 자원을 의존 관계에 맞춰 해제해야 한다. goto 레이블이 많다면 각 분기에 도달했을 때 확보한 자원을 표로 정리한다.
임베디드 보드를 처음 구동할 때는 패닉 대신 로그 중단, 시간 초과, 장치 초기화 지연, 과도한 인터럽트 같은 증상으로 문제가 드러날 수 있다.
compaction을 reclaim과 같은 free 작업으로 봄
unmovable page 하나가 pageblock을 막는 효과를 과소평가함
CMA/THP 요구를 일반 order-0 allocation과 섞음
계측과 검증
로그를 무작정 추가하지 말고 확인할 가설을 먼저 세운다. 상태표에서 처리가 멈춘 단계를 찾은 뒤, 상태를 바꾸는 함수와 그 결과를 사용하는 함수를 함께 살펴본다.
| 관찰 지점 | 확인할 사실 | 한계 |
|---|---|---|
| trace_mm_compaction_begin / migratepages / finished | 스캔 시작, 이동 시도·성공 수, 결과 상태 | 이동 수가 allocation 성공을 뜻하지 않음 |
| /proc/pagetypeinfo | order·migratetype별 free block 분포 | 단일 시점의 분포로 원인 확정 불가 |
| vm.compact_memory | 명시적 compaction 실험 | 직접 고차 요청의 mode·목표와 다름 |
고정 소스: mm/compaction.c:2381; mm/compaction.c:2169. 계측 기능과 파일 가용성은 커널 설정에 따른다.
compaction.o는 빌트인 항목입니다. available_filter_functions에서 실제 추적 가능한 이름을 확인하고, 함수 이름 패턴으로 범위를 좁힙니다. 아래 명령은 추적 설정을 바꾸므로 별도 실험 환경에서만 실행합니다.
# 예시: tracefs가 켜진 실험 환경에서 compaction 함수명으로 좁힌다.
mount -t tracefs nodev /sys/kernel/tracing
echo function_graph > /sys/kernel/tracing/current_tracer
echo 'compact_zone*' > /sys/kernel/tracing/set_ftrace_filter
cat /sys/kernel/tracing/trace_pipe
추가 확인 사항
- compaction 의 state machine을 네 단계로 줄였을 때, 실제 코드에서 빠지는 intermediate state는 무엇인가?
- compaction 의 fast path가 생략한 검사는 어느 init path 또는 slow path에서 보증되는가?
- 실험으로 확인한다면 'compaction을 reclaim과 같은 free 작업으로 봄' 문제를 어떤 tracepoint와 counter로 분리할 수 있는가?
- 실험으로 확인한다면 'unmovable page 하나가 pageblock을 막는 효과를 과소평가함' 문제를 어떤 tracepoint와 counter로 분리할 수 있는가?