Ryotta's Linux 7.0 MM

메모리 관리 서브시스템 완전 분석

유휴 페이지 (Page Idle)

개요 (Overview)

Linux 커널의 Page Idle 기능은 메모리 페이지의 유휴(idle) 상태를 추적하여 DAMON(DAmonitor)과 같은 메모리 접근 분석 시스템과 연동하는 메커니즘입니다. 각 페이지에 "유휴" 플래그를 설정하고, 해당 페이지가 사용자 공간에서 실제로 접근되었는지 여부를 판단합니다.

이 기능은 /sys/kernel/mm/page_idle/bitmap 인터페이스를 통해 사용자 공간에서 제어할 수 있으며, 1비트가 1페이지(4KB)에 대응하는 비트맵 구조로 동작합니다. 페이지가 유휴 상태인지 확인하려면 해당 비트를 읽고, 유휴 상태로 표시하려면 비트를 씁니다.

Page Idle은 도서관 책 배치와 비슷합니다. 각 페이지는 책 한 권이고, 유휴 플래그는 "이 책은 아직 읽히지 않았다"는 스티커입니다. DAMON이 스티커를 붙여놓고 나중에 다시 확인할 때, 스티커가 그대로 남아 있으면 책이 움직이지 않은 것입니다. 커널은 PTE의 Accessed 비트와 비교하여 스티커를 떼거나 남기고, 실제로 접근된 책은 유휴 상태에서 해제됩니다.

이 기능은 64비트 시스템에서는 page-flags.hPG_idle 플래그를 사용하고, 32비트 시스템에서는 page_extPAGE_EXT_IDLE 플래그를 사용합니다. DAMON, KSM, 메모리 마이그레이션 등에서 페이지 접근 패턴을 분석할 때 활용됩니다.

소스 파일 경로:
├── mm/page_idle.c                    ← 유휴 페이지 코어 로직
├── include/linux/page_idle.h         ← 유휴 플래그 인라인 함수
├── include/linux/page-flags.h        ← PG_idle 플래그 정의 (64비트)
├── include/linux/page_ext.h          ← PAGE_EXT_IDLE 플래그 (32비트)
├── mm/page_ext.c                     ← page_idle_ops 등록
├── mm/damon/ops-common.c             ← DAMON 유휴 플래그 설정
└── mm/damon/vaddr.c                  ← DAMON 유휴 상태 확인
유휴 페이지 구조와 흐름

빠른 점검 명령

# page_idle 비트맵 파일 존재 확인
ls -la /sys/kernel/mm/page_idle/bitmap 2>/dev/null || echo "CONFIG_IDLE_PAGE_TRACKING 미활성화"

# 커널 설정 확인
grep CONFIG_IDLE_PAGE_TRACKING /boot/config-$(uname -r)

# 페이지 유휴 상태 읽기 (PFN 0 ~ 1024, 128바이트 = 1024비트)
dd if=/sys/kernel/mm/page_idle/bitmap bs=1 count=128 2>/dev/null | xxd | head -8

# 특정 페이지(PFN 100) 유휴 상태 확인
python3 -c "
import struct
with open('/sys/kernel/mm/page_idle/bitmap', 'rb') as f:
    f.seek(100 // 8)
    data = f.read(8)
    val = struct.unpack('Q', data)[0]
    print(f'PFN 100 idle: {bool(val & (1 << (100 % 8)))}')"

# 페이지를 유휴 상태로 표시 (PFN 100)
python3 -c "
import struct
with open('/sys/kernel/mm/page_idle/bitmap', 'r+b') as f:
    f.seek(100 // 8)
    data = f.read(8)
    val = struct.unpack('Q', data)[0]
    val |= (1 << (100 % 8))
    f.seek(100 // 8)
    f.write(struct.pack('Q', val))"

# DAMON과 연동 확인
ls /sys/kernel/mm/damon/ 2>/dev/null || echo "DAMON 미활성화"

# 메모리 핫플러그 관련 확인
cat /sys/devices/system/memory/online_blocks 2>/dev/null | head -1

# rmap 관련 페이지 접근 추적 확인
cat /proc/vmstat | grep -E "pgactivate|pgfault|pgmajfault"

# 시스템 전체 유휴 페이지 통계 (DAMON 사용 시)
cat /sys/kernel/mm/damon/admin/kdamonds/nr_kdamonds 2>/dev/null

# page_ext 플래그 확인 (32비트 시스템)
cat /proc/kallsyms | grep -E "page_idle_ops|PAGE_EXT_IDLE" | head -5

핵심 자료구조

1. `struct page_ext` (include/linux/page_ext.h:52)

struct page_ext {
    unsigned long flags;  // 페이지 확장 플래그 (PAGE_EXT_IDLE, PAGE_EXT_YOUNG 등)
};
  • 역할: 각 페이지에 대한 확장 정보를 저장하는 구조체
  • 플래그: PAGE_EXT_IDLE — 페이지가 유휴 상태임을 표시
  • 할당: 부팅 시 또는 메모리 핫플러그 시 page_ext_init()으로 할당
  • 참조: page_ext_get() / page_ext_put()으로 참조 카운터 관리
  • 2. `page_ext_operations` (include/linux/page_ext.h:25)

    struct page_ext_operations {
        size_t offset;                    // page_ext 내 오프셋
        size_t size;                      // 클라이언트 데이터 크기
        bool (*need)(void);               // page_ext 필요 여부 확인
        void (*init)(void);               // 초기화 콜백 (선택)
        bool need_shared_flags;           // 공유 플래그 사용 여부
    };
  • page_idle_ops: need_page_idle() 함수가 true를 반환하여 page_idle 기능이 활성화됨
  • 등록: page_ext_ops[] 배열에 포함되어 page_ext_init() 시 자동 초기화
  • 3. `page_idle_bitmap_attr` (mm/page_idle.c:200)

    static const struct bin_attribute page_idle_bitmap_attr =
            __BIN_ATTR(bitmap, 0600,
                       page_idle_bitmap_read, page_idle_bitmap_write, 0);
  • 역할: sysfs 바이너리 속성으로 /sys/kernel/mm/page_idle/bitmap 파일 생성
  • 권한: 0600 — 소유자만 읽기/쓰기 가능
  • 함수: page_idle_bitmap_read() — 유휴 페이지 비트맵 읽기
  • 함수: page_idle_bitmap_write() — 페이지를 유휴 상태로 표시
  • 4. `rmap_walk_control` (mm/page_idle.c:103)

    static struct rmap_walk_control rwc = {
        .rmap_one = page_idle_clear_pte_refs_one,
        .anon_lock = folio_lock_anon_vma_read,
    };
  • 역할: rmap walk 시 사용되는 제어 구조체
  • rmap_one: 각 매핑된 PTE에 대해 호출되는 콜백
  • anon_lock: 익명 VMA 잠금 함수
  • 5. `folio` 플래그 관계 (include/linux/page-flags.h:668)

    #ifdef CONFIG_PAGE_IDLE_FLAG
    #ifdef CONFIG_64BIT
    FOLIO_TEST_FLAG(young, FOLIO_HEAD_PAGE)    // 젊은(접근된) 페이지 테스트
    FOLIO_SET_FLAG(young, FOLIO_HEAD_PAGE)     // 젊은 플래그 설정
    FOLIO_TEST_CLEAR_FLAG(young, FOLIO_HEAD_PAGE) // 젊은 플래그 테스트 후 클리어
    FOLIO_FLAG(idle, FOLIO_HEAD_PAGE)          // 유휴 플래그 관리
    #endif
    #endif
  • 64비트: PG_idle 비트가 struct page의 플래그에 직접 저장됨
  • 32비트: page_ext->flagsPAGE_EXT_IDLE 비트 사용

  • 핵심 함수

    1. `page_idle_get_folio()` (mm/page_idle.c:34)

    static struct folio *page_idle_get_folio(unsigned long pfn)
    {
        struct page *page = pfn_to_online_page(pfn);
        struct folio *folio;
    
        if (!page || PageTail(page))
            return NULL;
    
        folio = page_folio(page);
        if (!folio_test_lru(folio) || !folio_try_get(folio))
            return NULL;
        if (unlikely(page_folio(page) != folio || !folio_test_lru(folio))) {
            folio_put(folio);
            folio = NULL;
        }
        return folio;
    }
  • 역할: PFN으로 온라인 사용자 메모리 페이지의 folio 획득
  • 조건: LRU 목록에 있는 페이지만 대상 (isolated 페이지 제외)
  • 반환: folio 획득 성공 시 folio 포인터, 실패 시 NULL
  • 2. `page_idle_clear_pte_refs_one()` (mm/page_idle.c:52)

    static bool page_idle_clear_pte_refs_one(struct folio *folio,
                        struct vm_area_struct *vma,
                        unsigned long addr, void *arg)
    {
        DEFINE_FOLIO_VMA_WALK(pvmw, folio, vma, addr, 0);
        bool referenced = false;
    
        while (page_vma_mapped_walk(&pvmw)) {
            addr = pvmw.address;
            if (pvmw.pte) {
                if (likely(pte_present(ptep_get(pvmw.pte))))
                    referenced |= ptep_test_and_clear_young(vma, addr, pvmw.pte);
                referenced |= mmu_notifier_clear_young(vma->vm_mm, addr, addr + PAGE_SIZE);
            } else if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE)) {
                pmd_t pmdval = pmdp_get(pvmw.pmd);
                if (likely(pmd_present(pmdval)))
                    referenced |= pmdp_clear_young_notify(vma, addr, pvmw.pmd);
                referenced |= mmu_notifier_clear_young(vma->vm_mm, addr, addr + PMD_SIZE);
            }
        }
    
        if (referenced) {
            folio_clear_idle(folio);
            folio_set_young(folio);
        }
        return true;
    }
  • 역할: 특정 folio의 모든 PTE 매핑에서 Accessed 비트 클리어
  • 동작: ptep_test_and_clear_young()으로 PTE Accessed 비트 확인 후 클리어
  • 결과: 페이지가 접근되었으면 유휴 플래그 해제, young 플래그 설정
  • 3. `page_idle_bitmap_read()` (mm/page_idle.c:118)

    static ssize_t page_idle_bitmap_read(struct file *file, struct kobject *kobj,
                         const struct bin_attribute *attr, char *buf,
                         loff_t pos, size_t count)
    {
        u64 *out = (u64 *)buf;
        struct folio *folio;
        unsigned long pfn, end_pfn;
        int bit;
    
        if (pos % BITMAP_CHUNK_SIZE || count % BITMAP_CHUNK_SIZE)
            return -EINVAL;
    
        pfn = pos * BITS_PER_BYTE;
        if (pfn >= max_pfn)
            return 0;
    
        end_pfn = pfn + count * BITS_PER_BYTE;
        if (end_pfn > max_pfn)
            end_pfn = max_pfn;
    
        for (; pfn < end_pfn; pfn++) {
            bit = pfn % BITMAP_CHUNK_BITS;
            if (!bit)
                *out = 0ULL;
            folio = page_idle_get_folio(pfn);
            if (folio) {
                if (folio_test_idle(folio)) {
                    page_idle_clear_pte_refs(folio);
                    if (folio_test_idle(folio))
                        *out |= 1ULL << bit;
                }
                folio_put(folio);
            }
            if (bit == BITMAP_CHUNK_BITS - 1)
                out++;
            cond_resched();
        }
        return (char *)out - buf;
    }
  • 역할: 유휴 페이지 비트맵 읽기 (sysfs read 핸들러)
  • 동작: 각 PFN에 대해 folio 획득 → 유휴 테스트 → PTE 참조 클리어 → 재검증
  • 반환: 실제 읽은 바이트 수
  • 4. `page_idle_bitmap_write()` (mm/page_idle.c:163)

    static ssize_t page_idle_bitmap_write(struct file *file, struct kobject *kobj,
                          const struct bin_attribute *attr, char *buf,
                          loff_t pos, size_t count)
    {
        const u64 *in = (u64 *)buf;
        struct folio *folio;
        unsigned long pfn, end_pfn;
        int bit;
    
        if (pos % BITMAP_CHUNK_SIZE || count % BITMAP_CHUNK_SIZE)
            return -EINVAL;
    
        pfn = pos * BITS_PER_BYTE;
        if (pfn >= max_pfn)
            return -ENXIO;
    
        end_pfn = pfn + count * BITS_PER_BYTE;
        if (end_pfn > max_pfn)
            end_pfn = max_pfn;
    
        for (; pfn < end_pfn; pfn++) {
            bit = pfn % BITMAP_CHUNK_BITS;
            if ((*in >> bit) & 1) {
                folio = page_idle_get_folio(pfn);
                if (folio) {
                    page_idle_clear_pte_refs(folio);
                    folio_set_idle(folio);
                    folio_put(folio);
                }
            }
            if (bit == BITMAP_CHUNK_BITS - 1)
                in++;
            cond_resched();
        }
        return (char *)in - buf;
    }
  • 역할: 페이지를 유휴 상태로 표시 (sysfs write 핸들러)
  • 동작: 비트가 설정된 PFN에 대해 PTE 참조 클리어 후 유휴 플래그 설정
  • 반환: 실제 쓴 바이트 수
  • 5. `page_idle_init()` (mm/page_idle.c:214)

    static int __init page_idle_init(void)
    {
        int err;
    
        err = sysfs_create_group(mm_kobj, &page_idle_attr_group);
        if (err) {
            pr_err("page_idle: register sysfs failed\n");
            return err;
        }
        return 0;
    }
    subsys_initcall(page_idle_init);
  • 역할: sysfs에 page_idle 디렉토리 생성
  • 시점: subsys_initcall — 커널 초기화 단계에서 자동 실행
  • 결과: /sys/kernel/mm/page_idle/bitmap 파일 생성

  • 호출 흐름

    사용자 space (DAMON 등)
        │
        ▼
    /sys/kernel/mm/page_idle/bitmap read/write
        │
        ├── page_idle_bitmap_read()
        │   ├── for each pfn:
        │   │   ├── page_idle_get_folio(pfn)  ← folio 획득
        │   │   ├── folio_test_idle(folio)    ← 유휴 상태 확인
        │   │   ├── page_idle_clear_pte_refs(folio)
        │   │   │   ├── rmap_walk(folio, &rwc)
        │   │   │   │   └── page_idle_clear_pte_refs_one()
        │   │   │   │       ├── ptep_test_and_clear_young()  ← PTE Accessed 비트 클리어
        │   │   │   │       └── folio_clear_idle() / folio_set_young()
        │   │   │   └── folio_unlock(folio)
        │   │   └── folio_test_idle(folio)    ← 재검증
        │   └── 반환: 유휴 페이지 비트맵
        │
        └── page_idle_bitmap_write()
            ├── for each pfn:
            │   ├── page_idle_get_folio(pfn)
            │   ├── page_idle_clear_pte_refs(folio)
            │   ├── folio_set_idle(folio)     ← 유휴 플래그 설정
            │   └── folio_put(folio)
            └── 반환: 쓴 바이트 수

    조건별 비교

    페이지 유휴 플래그 저장 위치 비교

    아키텍처플래그 위치파일비고
    64비트 (x86_64, ARM64)`struct page`의 flags 비트`page-flags.h``PG_idle` 직접 사용
    32비트 (x86, ARM)`page_ext->flags`의 비트`page_idle.h``PAGE_EXT_IDLE` 사용

    유휴 상태 확인 vs 설정 비교

    동작함수PTE 처리결과
    유휴 확인 (read)`page_idle_bitmap_read()``ptep_test_and_clear_young()`유휴 상태 유지 여부 반환
    유휴 설정 (write)`page_idle_bitmap_write()``page_idle_clear_pte_refs()`항상 `folio_set_idle()`

    DAMON vs Page Idle 역할 비교

    구성 요소역할동작
    DAMON메모리 접근 패턴 모니터링주기적으로 페이지 Accessed 비트 확인
    Page Idle유휴 페이지 상태 저장DAMON이 설정한 유휴 플래그 관리
    rmap역방향 매핑 검색PTE를 통해 실제 페이지 접근 여부 확인

    관련 문서

  • 00-overview.html — 메모리 관리 개요
  • 05-page_reclaim.html — 페이지 회수 (LRU, Accessed 비트 활용)
  • 21-damon.html — DAMON (page_idle과 연동)
  • 19-ksm.html — KSM (페이지 접근 추적)
  • 20-migrate.html — 메모리 마이그레이션 (page_idle 활용)
  • 28-rmap.html — 역방향 매핑 (PTE Accessed 비트 관리)