Linux 커널의 Page Idle 기능은 메모리 페이지의 유휴(idle) 상태를 추적하여 DAMON(DAmonitor)과 같은 메모리 접근 분석 시스템과 연동하는 메커니즘입니다. 각 페이지에 "유휴" 플래그를 설정하고, 해당 페이지가 사용자 공간에서 실제로 접근되었는지 여부를 판단합니다.
이 기능은 /sys/kernel/mm/page_idle/bitmap 인터페이스를 통해 사용자 공간에서 제어할 수 있으며, 1비트가 1페이지(4KB)에 대응하는 비트맵 구조로 동작합니다. 페이지가 유휴 상태인지 확인하려면 해당 비트를 읽고, 유휴 상태로 표시하려면 비트를 씁니다.
Page Idle은 도서관 책 배치와 비슷합니다. 각 페이지는 책 한 권이고, 유휴 플래그는 "이 책은 아직 읽히지 않았다"는 스티커입니다. DAMON이 스티커를 붙여놓고 나중에 다시 확인할 때, 스티커가 그대로 남아 있으면 책이 움직이지 않은 것입니다. 커널은 PTE의 Accessed 비트와 비교하여 스티커를 떼거나 남기고, 실제로 접근된 책은 유휴 상태에서 해제됩니다.
이 기능은 64비트 시스템에서는 page-flags.h의 PG_idle 플래그를 사용하고, 32비트 시스템에서는 page_ext의 PAGE_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
struct page_ext {
unsigned long flags; // 페이지 확장 플래그 (PAGE_EXT_IDLE, PAGE_EXT_YOUNG 등)
};
PAGE_EXT_IDLE — 페이지가 유휴 상태임을 표시page_ext_init()으로 할당page_ext_get() / page_ext_put()으로 참조 카운터 관리struct page_ext_operations {
size_t offset; // page_ext 내 오프셋
size_t size; // 클라이언트 데이터 크기
bool (*need)(void); // page_ext 필요 여부 확인
void (*init)(void); // 초기화 콜백 (선택)
bool need_shared_flags; // 공유 플래그 사용 여부
};
need_page_idle() 함수가 true를 반환하여 page_idle 기능이 활성화됨page_ext_ops[] 배열에 포함되어 page_ext_init() 시 자동 초기화static const struct bin_attribute page_idle_bitmap_attr =
__BIN_ATTR(bitmap, 0600,
page_idle_bitmap_read, page_idle_bitmap_write, 0);
/sys/kernel/mm/page_idle/bitmap 파일 생성0600 — 소유자만 읽기/쓰기 가능page_idle_bitmap_read() — 유휴 페이지 비트맵 읽기page_idle_bitmap_write() — 페이지를 유휴 상태로 표시static struct rmap_walk_control rwc = {
.rmap_one = page_idle_clear_pte_refs_one,
.anon_lock = folio_lock_anon_vma_read,
};
#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
PG_idle 비트가 struct page의 플래그에 직접 저장됨page_ext->flags의 PAGE_EXT_IDLE 비트 사용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;
}
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;
}
ptep_test_and_clear_young()으로 PTE Accessed 비트 확인 후 클리어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;
}
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;
}
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);
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` 사용 |
| 동작 | 함수 | 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 | 메모리 접근 패턴 모니터링 | 주기적으로 페이지 Accessed 비트 확인 |
| Page Idle | 유휴 페이지 상태 저장 | DAMON이 설정한 유휴 플래그 관리 |
| rmap | 역방향 매핑 검색 | PTE를 통해 실제 페이지 접근 여부 확인 |