Radix Tree (레거시)

Linux 커널의 Radix Tree는 정수 키 기반 매핑을 제공하는 자료구조로, 4.20 커널 이전까지 페이지 캐시의 핵심이었습니다. 현재는 XArray로 대체되었지만, 레거시 코드 이해와 역사적 맥락을 위해 학습이 필요합니다.

⚠️
레거시 API: Radix Tree API는 Linux 4.20부터 XArray로 대체되었습니다. 새로운 코드에서는 XArray를 사용하세요. 이 문서는 기존 코드 이해와 역사적 맥락을 위한 것입니다.
일상 비유: Radix Tree는 전화번호부의 접두사 검색과 비슷합니다. 전화번호의 각 자릿수(키의 비트 그룹)를 따라 트리를 내려가면 해당 번호의 가입자 정보(포인터)에 도달합니다. 같은 지역번호를 공유하는 번호들은 트리의 같은 가지를 공유하여 메모리를 절약합니다.

핵심 요약

  • Trie 구조 — 키의 비트를 트리 경로로 사용합니다.
  • O(log n) 성능 — 삽입/검색/삭제가 트리 높이에 비례합니다.
  • 페이지 캐시 — address_space의 핵심 자료구조였습니다.
  • 태그 기능 — dirty, writeback 등의 상태를 비트맵으로 관리합니다.
  • XArray로 전환 — 더 간단한 API와 더 나은 성능을 제공합니다.

단계별 이해

  1. 트리 구조 파악
    64개 슬롯(6비트씩)을 가진 노드가 키의 비트를 단계별로 분기하는 Trie 구조를 이해합니다. 트리 높이는 키 범위에 따라 자동 조절됩니다.
  2. 태그 비트맵 이해
    각 노드에 PAGECACHE_TAG_DIRTY, PAGECACHE_TAG_WRITEBACK 등 태그 비트맵이 저장되어 O(log n)에 특정 상태의 항목만 검색하는 원리를 파악합니다.
  3. XArray 마이그레이션
    레거시 radix_tree_insert()/radix_tree_lookup()을 XArray의 xa_store()/xa_load()로 전환하는 패턴을 학습합니다.

개요 (Overview)

Radix Tree는 다음과 같은 특징을 가집니다:

ℹ️

역사: Radix Tree는 2001년 커널 2.4.x에 도입되어 페이지 캐시의 핵심 자료구조로 사용되었습니다. 2018년 Linux 4.20에서 XArray로 대체되었으며, API 호환 래퍼만 남아있습니다.

구조 (Structure)

/* include/linux/radix-tree.h (레거시) */
struct radix_tree_root {
    gfp_t gfp_mask;
    struct radix_tree_node __rcu *rnode;
};

struct radix_tree_node {
    unsigned char shift;
    unsigned char offset;
    unsigned char count;
    unsigned char exceptional;
    struct radix_tree_node *parent;
    struct radix_tree_root *root;
    void __rcu *slots[RADIX_TREE_MAP_SIZE];
    unsigned long tags[RADIX_TREE_MAX_TAGS][RADIX_TREE_TAG_LONGS];
};

#define RADIX_TREE_MAP_SHIFT 6  /* 64개 슬롯 */
#define RADIX_TREE_MAP_SIZE (1UL << RADIX_TREE_MAP_SHIFT)

Radix Tree 내부 구조

Radix Tree는 64-way 트리로, 인덱스의 6비트씩을 사용하여 경로를 결정합니다:

Radix Tree 64-way 구조 Radix Tree 64-way 구조 (인덱스 420 검색 예제) 인덱스 420 (0b 0000 0000 0000 0000 0000 0000 0001 1010 0100) 비트 0-5: 000100 (4) 비트 6-11: 101001 (41... 잘림) 비트 12+: 0 (레벨 1) Level 0 radix_tree_root Level 1 radix_tree_node (64 slots) slot[0] NULL ... slot[6] → node ... slot[63] NULL rnode Level 2 radix_tree_node (64 slots) slot[0] NULL ... slot[4] → page ... slot[63] NULL slot[6] struct page* (인덱스 420) 페이지 캐시 항목 검색 경로: root → slot[6] (비트 0-5) → slot[4] (비트 6-11) → struct page* O(log₆₄ n) = 최대 11레벨 (64비트 주소 공간: 64^11 ≈ 2^66)

레거시 API

기본 연산

/* 초기화 */
RADIX_TREE_INIT(my_tree, GFP_KERNEL);
void INIT_RADIX_TREE(struct radix_tree_root *root, gfp_t gfp_mask);

/* 삽입 */
int radix_tree_insert(struct radix_tree_root *root,
                        unsigned long index, void *item);

/* 검색 */
void *radix_tree_lookup(struct radix_tree_root *root,
                           unsigned long index);

/* 삭제 */
void *radix_tree_delete(struct radix_tree_root *root,
                           unsigned long index);

/* 사용 예 */
struct radix_tree_root page_tree;
INIT_RADIX_TREE(&page_tree, GFP_KERNEL);

struct page *page = alloc_page(GFP_KERNEL);
radix_tree_insert(&page_tree, 42, page);

struct page *found = radix_tree_lookup(&page_tree, 42);

radix_tree_delete(&page_tree, 42);

태그 연산

/* 태그 설정 */
void *radix_tree_tag_set(struct radix_tree_root *root,
                           unsigned long index, unsigned int tag);

/* 태그 제거 */
void *radix_tree_tag_clear(struct radix_tree_root *root,
                             unsigned long index, unsigned int tag);

/* 태그 확인 */
int radix_tree_tag_get(struct radix_tree_root *root,
                         unsigned long index, unsigned int tag);

/* 페이지 캐시 예: dirty 태그 */
#define PAGECACHE_TAG_DIRTY 0
#define PAGECACHE_TAG_WRITEBACK 1

radix_tree_tag_set(&mapping->i_pages, index, PAGECACHE_TAG_DIRTY);

태그 비트맵 구조

Radix Tree의 태그 시스템은 각 항목에 여러 플래그를 비트맵으로 관리합니다:

Radix Tree 태그 비트맵 구조 Radix Tree 태그 비트맵 구조 (3개 태그 × 64개 슬롯) struct radix_tree_node slots[64]: page0 page1 NULL ... (총 64개 슬롯) tags[3][1]: (3개 태그 × 64비트) 태그[0] DIRTY: 1 0 0 ... (비트 0=dirty, 1=clean, 2=clean, ...) 태그[1] WRITEBACK: 0 1 0 ... (비트 1이 writeback 중) 태그[2] TOWRITE: 1 0 0 ... (비트 0이 쓰기 대기 중) 태그 연산 동작 radix_tree_tag_set(&tree, 0, DIRTY) → tags[0][0] |= (1 << 0) // 비트 0을 1로 설정 radix_tree_tag_clear(&tree, 0, DIRTY) → tags[0][0] &= ~(1 << 0) // 비트 0을 0으로 설정 페이지 캐시 활용: dirty 페이지만 찾아 writeback 수행, writeback 중인 페이지 추적 O(1) 태그 설정/해제, O(log n) 태그된 항목 검색

페이지 캐시에서의 사용

Radix Tree는 페이지 캐시의 핵심이었습니다:

/* include/linux/fs.h (4.20 이전) */
struct address_space {
    struct radix_tree_root i_pages;  /* 페이지 캐시 */
    /* ... */
};

/* mm/filemap.c */
struct page *find_get_page(struct address_space *mapping,
                           pgoff_t offset)
{
    struct page *page;

    rcu_read_lock();
    page = radix_tree_lookup(&mapping->i_pages, offset);
    if (page && !get_page_unless_zero(page))
        page = NULL;
    rcu_read_unlock();

    return page;
}

XArray로 전환

Linux 4.20부터 Radix Tree는 XArray로 대체되었습니다:

Radix Tree XArray 개선 사항
radix_tree_insert xa_store 더 직관적인 이름
radix_tree_lookup xa_load 짧은 이름
radix_tree_delete xa_erase 명확한 의미
radix_tree_tag_set xa_set_mark 일관된 네이밍
복잡한 API 단순한 API 학습 곡선 감소

변환 예제

/* 기존 Radix Tree 코드 */
struct radix_tree_root my_tree;
INIT_RADIX_TREE(&my_tree, GFP_KERNEL);
radix_tree_insert(&my_tree, 42, ptr);
void *result = radix_tree_lookup(&my_tree, 42);
radix_tree_delete(&my_tree, 42);

/* XArray로 변환 */
struct xarray my_xarray;
xa_init(&my_xarray);
xa_store(&my_xarray, 42, ptr, GFP_KERNEL);
void *result = xa_load(&my_xarray, 42);
xa_erase(&my_xarray, 42);

XArray 마이그레이션 체크리스트

⚠️

마이그레이션 가이드: Radix Tree 코드를 XArray로 변환할 때 다음 체크리스트를 따르세요.

1단계: 헤더 및 타입 변경

  • #include <linux/radix-tree.h>#include <linux/xarray.h>
  • struct radix_tree_rootstruct xarray
  • RADIX_TREE_INIT()DEFINE_XARRAY() 또는 xa_init()

2단계: API 함수 변환

Radix Tree API XArray API 주의사항
radix_tree_insert() xa_store() 반환 값 변경 (음수 → 이전 값 포인터)
radix_tree_lookup() xa_load() 동일한 동작
radix_tree_delete() xa_erase() 반환 값이 삭제된 포인터
radix_tree_tag_set() xa_set_mark() 마크(mark)로 용어 변경
radix_tree_tag_clear() xa_clear_mark() 마크(mark)로 용어 변경
radix_tree_tag_get() xa_get_mark() 마크(mark)로 용어 변경
radix_tree_gang_lookup() xa_for_each() 매크로로 간소화됨
radix_tree_gang_lookup_tag() xa_for_each_marked() 매크로로 간소화됨

3단계: 잠금 처리

  • ☐ XArray는 내장 spinlock(xa_lock) 제공
  • ☐ 외부 잠금 필요 시 XA_FLAGS_LOCK_EXTERNAL 사용
  • ☐ RCU 읽기는 xa_load()가 자동 지원

4단계: 테스트 및 검증

  • ☐ 컴파일 경고 확인 (API 서명 변경)
  • ☐ 동작 테스트 (특히 반환 값 처리)
  • ☐ 성능 벤치마크 (메모리 사용량, 속도)
  • ☐ 동시성 시나리오 테스트 (RCU, 잠금)

5단계: 문서 및 주석 업데이트

  • ☐ 주석에서 "radix tree" → "XArray"로 변경
  • ☐ 커밋 메시지에 전환 이유 명시
  • ☐ 관련 문서 링크 업데이트

왜 대체되었나

XArray가 Radix Tree를 대체한 이유:

  1. API 복잡성 — Radix Tree API는 배우기 어려웠습니다.
  2. 메모리 오버헤드 — 내부 노드 구조가 비효율적이었습니다.
  3. 잠금 복잡성 — RCU 동기화가 복잡했습니다.
  4. 확장성 — 대규모 데이터셋에서 성능 문제가 있었습니다.
💡

성능 개선: XArray는 Radix Tree 대비 메모리 사용량 40% 감소, 캐시 미스 20% 감소, 코드 크기 30% 감소를 달성했습니다.

Radix Tree vs XArray 메모리 레이아웃

Radix Tree와 XArray의 메모리 구조 차이를 비교합니다:

Radix Tree vs XArray 메모리 레이아웃 비교 메모리 레이아웃 비교: Radix Tree (레거시) vs XArray (현대) Radix Tree Node struct radix_tree_node { unsigned char shift; 1 byte unsigned char offset; 1 byte unsigned char count; 1 byte unsigned char exceptional; 1 byte struct .. *parent; 8 bytes struct .. *root; 8 bytes void* slots[64]; 512 bytes tags[3][1]; 24 bytes } 총 크기: ~576 bytes (+ 패딩) 문제점: • parent/root 포인터 불필요 • 메타데이터 오버헤드 큼 XArray Node struct xa_node { unsigned char shift; 1 byte unsigned char offset; 1 byte unsigned char count; 1 byte unsigned char nr_values; 1 byte struct xa_node *parent; 8 bytes struct xarray *array; 8 bytes void* slots[64]; 512 bytes marks[3]; 24 bytes } 총 크기: ~560 bytes (최적화됨) 개선점: • 구조 간소화 • 캐시 효율 향상 전환 XArray 성능 개선 통계 • 메모리 사용량: -40% (노드 구조 최적화 + 압축) • 캐시 미스: -20% (메모리 레이아웃 개선) • 코드 크기: -30% (API 단순화) • 잠금 복잡성: 대폭 감소 (내장 잠금 지원)

레거시 코드 이해

4.20 이전 커널 코드를 분석할 때 알아야 할 패턴:

/* 태그 기반 순회 (dirty page 찾기) */
struct page *page;
unsigned long index = 0;

while ((page = radix_tree_gang_lookup_tag(
            &mapping->i_pages, (void **)&page, index,
            1, PAGECACHE_TAG_DIRTY)) != NULL) {
    /* process dirty page */
    index = page->index + 1;
}

/* XArray 등가 코드 */
unsigned long index;
struct page *page;

xa_for_each_marked(&mapping->i_pages, index, page,
                   XA_MARK_DIRTY) {
    /* process dirty page */
}

성능 특성

연산 Radix Tree XArray
삽입 O(log₆₄ n) O(log₆₄ n)
검색 O(log₆₄ n) O(log₆₄ n)
삭제 O(log₆₄ n) O(log₆₄ n)
메모리 기준선 -40%
캐시 미스 기준선 -20%

Legacy Radix Tree 유지보수 노트

Radix Tree는 XArray로 대체되었지만 레거시 코드 유지보수에서는 여전히 만납니다. 신규 구현은 XArray를 우선하고, 기존 경로 수정 시에는 RCU/예외 엔트리 규칙을 깨지 않도록 주의해야 합니다.

  1. 신규 코드 여부 판단: 신규면 XArray 사용, 레거시면 최소 수정 원칙
  2. 예외 엔트리 처리: NULL/exceptional entry 구분 경로 확인
  3. 동시성 규칙: reader의 RCU 보호 여부 확인
  4. 마이그레이션 가능성: API 매핑표로 XArray 전환 계획 수립
# 레거시 사용 지점 검색
git grep -n "radix_tree_" -- "*.c" "*.h"

# XArray 전환 후보 탐색
git grep -n "radix_tree_lookup\\|radix_tree_insert\\|radix_tree_delete" -- "*.c"