Ryotta's Linux 7.0 MM

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

19. KSM (Kernel Same-page Merging)

개요 (Overview)

KSM(Kernel Same-page Merging)은 커널이 실행 중인 프로세스들의 동일한 내용의 익명 페이지를 탐지하여 하나의 물리 페이지로 병합(merging)하는 메모리 중복 제거 메커니즘입니다. 가상화 환경(KVM/QEMU)에서 여러 가상 머신이 동일한 커널이나 라이브러리를 실행할 때, 또는 같은 프로세스를 fork()한 후 COW로 분리되지 않은 페이지를 다시 병합할 때 메모리 사용량을 크게 절감할 수 있습니다.

KSM은 백그라운드 커널 스레드인 ksmd가 주기적으로 모든 VM_MERGEABLE 플래그가 설정된 VMA를 스캔하고, 페이지 내용을 비교하여 stable tree(쓰기 보호된 KSM 페이지)와 unstable tree(일시적으로 보관되는 후보 페이지)에 정렬합니다. 동일한 페이지를 찾으면 write-protect 후 PTE를 교체하여 물리 페이지를 공유합니다.

애플리케이션은 madvise(..., MADV_MERGEABLE)로 병합 후보를 명시하고, MADV_UNMERGEABLE로 제외합니다. 운영자는 sysfs로 ksmd의 실행 여부와 스캔 속도를 조절합니다.

같은 사본 여러 부를 한 권으로 정리해 나머지를 공유하는 도서관 사본 관리와 비슷합니다. 스캔과 비교 비용이 있으므로 CPU 오버헤드가 생기며, 필요한 VMA만 골라 켜는 것이 중요합니다.

소스 파일 경로

mm/ksm.c                    ← KSM 핵심 구현 (스캔, 비교, 병합, stable/unstable tree)
mm/madvise.c                ← MADV_MERGEABLE/MADV_UNMERGEABLE 처리
include/linux/ksm.h         ← KSM 외부 API 및 인라인 함수
mm/mm_slot.h                ← mm_slot 해시 테이블 관리
include/linux/mm.h          ← struct mm_struct 내 ksm 관련 필드

KSM 호출 흐름

빠른 점검 명령

# KSM 스레드 상태 확인
ps aux | grep ksmd

# KSM 병합 통계 (전체 시스템)
cat /sys/kernel/mm/ksm/pages_shared     # stable tree의 고유 KSM 페이지 수
cat /sys/kernel/mm/ksm/pages_sharing    # KSM 페이지를 공유 중인 추가 매핑 수
cat /sys/kernel/mm/ksm/pages_unshared   # unstable tree의 미병합 후보 수
cat /sys/kernel/mm/ksm/pages_volatile   # rmap_item 중 어느 트리에도 없는 수
cat /sys/kernel/mm/ksm/pages_scanned    # 총 스캔된 페이지 수
cat /sys/kernel/mm/ksm/full_scans       # 전체 스캔 완료 횟수
cat /sys/kernel/mm/ksm/pages_skipped    # smart scan으로 건너뛴 페이지 수
cat /sys/kernel/mm/ksm/ksm_zero_pages   # zero page 병합 수

# KSM 수익 계산 (절약 메모리 - 오버헤드)
cat /sys/kernel/mm/ksm/general_profit

# KSM 스캔 파라미터 확인/조정
cat /sys/kernel/mm/ksm/pages_to_scan     # 한 번에 스캔할 페이지 수 (기본 100)
cat /sys/kernel/mm/ksm/sleep_millisecs   # 스캔 간 대기 시간 (기본 20ms)
cat /sys/kernel/mm/ksm/run               # 0:정지, 1:병합, 2:병합해제
cat /sys/kernel/mm/ksm/merge_across_nodes  # NUMA 노드 간 병합 허용 여부
cat /sys/kernel/mm/ksm/use_zero_pages    # zero page 병합 활성화 여부
cat /sys/kernel/mm/ksm/smart_scan        # 스마트 스캔 활성화 여부
cat /sys/kernel/mm/ksm/max_page_sharing  # 하나의 KSM 페이지 최대 공유 수 (기본 256)

# KSM advisor 확인/조정
cat /sys/kernel/mm/ksm/advisor_mode                  # none / scan-time
cat /sys/kernel/mm/ksm/advisor_max_cpu               # advisor 최대 CPU%
cat /sys/kernel/mm/ksm/advisor_target_scan_time      # 목표 스캔 시간(초)
echo scan-time | sudo tee /sys/kernel/mm/ksm/advisor_mode
echo none | sudo tee /sys/kernel/mm/ksm/advisor_mode

# KSM 활성화 (sysfs를 통한 제어)
echo 1 | sudo tee /sys/kernel/mm/ksm/run   # ksmd 시작
echo 0 | sudo tee /sys/kernel/mm/ksm/run   # ksmd 중지

# 프로세스별 KSM 사용량 확인
cat /proc/<pid>/status | grep KSM
awk '/VmFlags/ && /mg/ {print}' /proc/<pid>/smaps  # mergeable VMA 확인

# per-mm KSM 정보 (/proc/<pid>/ksm_stat - 해당 커널에서 지원 시)
cat /proc/<pid>/ksm_stat 2>/dev/null

# KSM 관련 커널 설정 확인
zgrep KSM /proc/config.gz 2>/dev/null || grep KSM /boot/config-$(uname -r)

핵심 자료구조

KSM 자료구조 관계도

struct ksm_stable_node

stable tree의 노드로, write-protect된 KSM 페이지를 나타냅니다. 동일한 내용의 여러 매핑이 있을 때 chain/dup 구조로 관리됩니다.

// mm/ksm.c:159-185
struct ksm_stable_node {
    union {
        struct rb_node node;    /* stable tree 내 rb-tree 노드 */
        struct {                /* migration list에 있을 때 */
            struct list_head *head;
            struct {
                struct hlist_node hlist_dup;  /* chain 내 dup 연결 */
                struct list_head list;         /* migrate_nodes 연결 */
            };
        };
    };
    struct hlist_head hlist;    /* 이 stable node를 사용하는 rmap_item 목록 */
    union {
        unsigned long kpfn;     /* KSM 페이지의 물리 프레임 번호 */
        unsigned long chain_prune_time;  /* chain pruning 마지막 시간 */
    };
#define STABLE_NODE_CHAIN -1024  /* chain 노드 판별 마커 */
    int rmap_hlist_len;         /* hlist의 rmap_item 수 또는 STABLE_NODE_CHAIN */
#ifdef CONFIG_NUMA
    int nid;                    /* NUMA 노드 ID */
#endif
};

struct ksm_rmap_item

가상 주소→물리 페이지의 역방향 매핑(rmap) 항목입니다. unstable tree의 rb-tree 노드이거나 stable tree의 hlist 요소로 사용됩니다.

// mm/ksm.c:201-221
struct ksm_rmap_item {
    struct ksm_rmap_item *rmap_list;  /* mm_slot의 단일 연결 리스트 */
    union {
        struct anon_vma *anon_vma;    /* stable tree에 있을 때 anon_vma */
#ifdef CONFIG_NUMA
        int nid;                      /* unstable tree에 있을 때 NUMA node */
#endif
    };
    struct mm_struct *mm;             /* 이 rmap_item이 속한 mm */
    unsigned long address;            /* 가상 주소 + 플래그 비트 */
    unsigned int oldchecksum;         /* unstable tree에서 이전 체크섬 */
    rmap_age_t age;                   /* 스캔 반복 횟수 (스마트 스캔용) */
    rmap_age_t remaining_skips;       /* 남은 건너뜀 횟수 */
    union {
        struct rb_node node;          /* unstable tree 내 rb-tree 노드 */
        struct {                      /* stable tree에서 사용 시 */
            struct ksm_stable_node *head;
            struct hlist_node hlist;
        };
    };
};

// 주소 하위 비트 플래그 (mm/ksm.c:223-225)
#define SEQNR_MASK  0x0ff   /* 불안정 트리 seqnr */
#define UNSTABLE_FLAG 0x100 /* unstable tree 노드 */
#define STABLE_FLAG   0x200 /* stable tree의 rmap_item */

struct ksm_mm_slot

스캔 커서가 현재 위치한 mm 정보를 담습니다. 해시 테이블로 mm→mm_slot 빠른 조회를 지원합니다.

// mm/ksm.c:126-129
struct ksm_mm_slot {
    struct mm_slot slot;               /* 해시 + 연결 리스트 관리 */
    struct ksm_rmap_item *rmap_list;   /* 이 mm의 rmap_item 목록 머리 */
};

// mm/mm_slot.h:15-19
struct mm_slot {
    struct hlist_node hash;     /* 해시 테이블 연결 */
    struct list_head mm_node;   /* mm 목록 연결 */
    struct mm_struct *mm;       /* 대상 mm_struct */
};

struct ksm_scan (스캔 커서)

전역 스캔 커서로, 현재 어떤 mm, 어떤 주소, 어떤 rmap_list를 스캔 중인지 추적합니다.

// mm/ksm.c:140-145
struct ksm_scan {
    struct ksm_mm_slot *mm_slot;   /* 현재 스캔 중인 mm_slot */
    unsigned long address;          /* 다음 스캔할 가상 주소 */
    struct ksm_rmap_item **rmap_list; /* 다음 rmap_item을 가리키는 포인터 */
    unsigned long seqnr;            /* 완료된 전체 스캔 횟수 */
};

struct advisor_ctx (KSM Advisor)

scan-time advisor가 스캔 속도를 적응형으로 조절하기 위한 컨텍스트입니다.

// mm/ksm.c:325-331
struct advisor_ctx {
    ktime_t start_scan;         /* 현재 스캔 시작 시간 */
    unsigned long scan_time;    /* 이전 스캔 소요 시간 */
    unsigned long change;       /* pages_to_scan 변경 비율 (EWMA) */
    unsigned long long cpu_time; /* 이전 스캔의 ksmd CPU 사용 시간 */
};

전역 트리 및 주요 변수

// mm/ksm.c:228-248 — stable/unstable tree 루트
static struct rb_root one_stable_tree[1] = { RB_ROOT };
static struct rb_root one_unstable_tree[1] = { RB_ROOT };
static struct rb_root *root_stable_tree = one_stable_tree;
static struct rb_root *root_unstable_tree = one_unstable_tree;

// mm/ksm.c:252-301 — 주요 통계 변수
static unsigned long ksm_pages_scanned;     // 스캔된 페이지 수
static unsigned long ksm_pages_shared;      // stable tree 고유 KSM 페이지 수
static unsigned long ksm_pages_sharing;     // KSM 페이지 공유 매핑 수
static unsigned long ksm_pages_unshared;    // unstable tree 미병합 수
static unsigned long ksm_rmap_items;        // 사용 중 rmap_item 수
static unsigned long ksm_pages_skipped;     // smart scan으로 건너뛴 수

핵심 함수

1. ksm_scan_thread() — ksmd 메인 스레드

// mm/ksm.c:2801-2826
static int ksm_scan_thread(void *nothing)

역할: 백그라운드에서 ksm_do_scan()을 반복 호출하는 커널 스레드입니다. set_user_nice(current, 5)로 낮은 우선순위로 동작하며, KSM_RUN_MERGE 상태일 때만 스캔을 수행합니다.

분기 로직:

  • ksmd_should_run()ksm_run & KSM_RUN_MERGE && mm_list 비어있지 않음이면 스캔 실행
  • 스캔 후 ksm_thread_sleep_millisecs 동안 대기
  • 대기 중 ksm_thread_sleep_millisecs 값이 변경되면 즉시 깨어남 (ksm_iter_wait)
  • 2. ksm_do_scan() — 스캔 워커

    // mm/ksm.c:2780-2794
    static void ksm_do_scan(unsigned int scan_npages)

    역할: scan_npages만큼 페이지를 스캔합니다. 각 반복에서 scan_get_next_rmap_item()으로 다음 후보 페이지를 얻고, cmp_and_merge_page()로 비교/병합을 시도합니다.

    분기 로직:

  • freezing(current)이면 즉시 종료 (동결 상태)
  • rmap_item이 NULL이면 더 이상 스캔할 것이 없음 → 종료
  • 스캔 후 put_page(page)로 참조 해제, ksm_pages_scanned++
  • 3. cmp_and_merge_page() — 핵심 비교/병합

    // mm/ksm.c:2248-2386
    static void cmp_and_merge_page(struct page *page, struct ksm_rmap_item *rmap_item)

    역할: 단일 페이지의 stable tree 검색, checksum 비교, unstable tree 검색/삽입, 병합을 수행하는 핵심 함수입니다.

    분기 로직:

    1. 이미 stable tree에 있으면 → is_page_sharing_candidate() 확인 후 조기 반환

    2. checksum이 이전과 다르면 → 페이지가 빈번히 변경됨 → unstable tree에도 넣지 않음

    3. try_to_merge_with_zero_page() → zero page 병합 가능하면 병합 후 반환

    4. stable_tree_search(page) → stable tree에서 동일 페이지 찾기

    - 찾으면 → try_to_merge_with_ksm_page()로 병합 시도

    5. unstable tree에서 unstable_tree_search_insert() → 동일 페이지 찾으면

    - try_to_merge_two_pages()로 두 페이지를 병합 후 stable tree에 삽입

    4. stable_tree_search() — stable tree 탐색

    // mm/ksm.c:1825-2030
    static struct folio *stable_tree_search(struct page *page)

    역할: stable tree에서 스캔 중인 페이지와 동일한 내용의 KSM 페이지를 찾습니다. chain/dup 구조를 순회하며 memcmp_pages()로 내용을 비교합니다.

    분기 로직:

  • 이미 KSM fork된 페이지면 → 즉시 반환
  • tree traversal 중 stale node 발견 → ksm_get_folio()가 제거 → 검색 재시작
  • memcmp_pages() == 0 → 동일 페이지 발견
  • - is_page_sharing_candidate() → 공유 가능하면 folio 반환

    - 공유 불가 → chain 내 다른 dup 탐색 또는 NULL 반환

    5. unstable_tree_search_insert() — unstable tree 검색/삽입

    // mm/ksm.c:2133-2198
    static struct ksm_rmap_item *unstable_tree_search_insert(
        struct ksm_rmap_item *rmap_item,
        struct page *page,
        struct page **tree_pagep)

    역할: unstable tree에서 동일 페이지를 찾거나, 없으면 현재 rmap_item을 삽입합니다. unstable tree는 매 전체 스캔 완료 시 리셋되므로 안전합니다.

    분기 로직:

  • tree traversal에서 memcmp_pages() == 0 → 동일 페이지 발견
  • - NUMA 노드 불일치 확인 (!ksm_merge_across_nodes && 다른 노드)

    - *tree_pagep에 tree_page 설정 후 tree_rmap_item 반환

  • 삽입: rmap_item->address |= UNSTABLE_FLAG | seqnr, ksm_pages_unshared++
  • 6. write_protect_page() — 페이지 쓰기 보호

    // mm/ksm.c:1272-1361
    static int write_protect_page(struct vm_area_struct *vma, struct folio *folio,
                                  pte_t *orig_pte)

    역할: 병합 전에 페이지를 쓰기 보호합니다. dirty/clean 상태가 섞여 있으면 PTE를 다시 맞춰야 하며 folio lock이 필요합니다.

    분기 로직:

  • large folio면 → error 반환 (KSM은 일반 페이지만 처리)
  • pte_write || pte_dirty || anon_exclusive || mm_tlb_flush_pending → PTE 클리어 후 재설정
  • folio_mapcount + 1 + swapped != folio_ref_count → O_DIRECT 진행 중 → 실패
  • anon_exclusivefolio_try_share_anon_rmap_pte() 실패 시 → 실패
  • 7. ksm_madvise() — VMA 병합 허용/해제

    // mm/ksm.c:2975-3013
    int ksm_madvise(struct vm_area_struct *vma, unsigned long start,
                    unsigned long end, int advice, vm_flags_t *vm_flags)

    역할: MADV_MERGEABLEMADV_UNMERGEABLE 요청을 처리합니다. MADV_MERGEABLE__ksm_enter(mm)로 해당 mm_struct를 KSM 관리 대상에 넣고 VM_MERGEABLE을 켜며, MADV_UNMERGEABLE은 이미 병합된 페이지가 있으면 break_ksm()으로 분리한 뒤 플래그를 끕니다.

    분기 로직:

  • MADV_MERGEABLEvma_ksm_compatible() 확인, __ksm_enter(mm) 필요 시 호출, *vm_flags |= VM_MERGEABLE
  • MADV_UNMERGEABLEVM_MERGEABLE가 없으면 무시, 익명 VMA면 break_ksm(vma, start, end, true) 후 플래그 해제
  • // mm/madvise.c:1416-1422
    case MADV_MERGEABLE:
    case MADV_UNMERGEABLE:
        error = ksm_madvise(vma, range->start, range->end,
                behavior, &new_flags);
        if (error)
            goto out;
        break;

    호출 흐름

    ksm_scan_thread()                          ← ksmd 메인 스레드
      └─ ksm_do_scan(pages_to_scan)            ← scan_npages만큼 반복
           └─ scan_get_next_rmap_item(&page)   ← 다음 후보 페이지/ rmap_item 획득
                └─ walk_page_range_vma()       ← VMA 순회하여 익명 페이지 탐색
                └─ get_next_rmap_item()        ← rmap_item 할당/조회
                └─ should_skip_rmap_item()     ← smart scan: 건너뛸지 결정
           └─ cmp_and_merge_page(page, rmap_item)  ← 핵심 비교/병합
                ├─ calc_checksum()             ← xxhash 기반 체크섬 계산
                ├─ stable_tree_search(page)    ← stable tree에서 동일 페이지 탐색
                │    └─ memcmp_pages()         ← 페이지 내용 바이트 비교
                │    └─ chain_prune()          ← stale chain/dup 정리
                ├─ unstable_tree_search_insert() ← unstable tree 검색/삽입
                │    └─ memcmp_pages()         ← 페이지 내용 비교
                ├─ try_to_merge_with_ksm_page() ← 기존 KSM 페이지와 병합
                │    └─ try_to_merge_one_page()
                │         └─ write_protect_page() ← PTE 쓰기 보호
                │         └─ replace_page()    ← PTE를 KSM 페이지로 교체
                └─ try_to_merge_two_pages()    ← 두 미병합 페이지 병합
                     └─ stable_tree_insert()   ← stable tree에 새 KSM 노드 삽입
                     └─ stable_tree_append()   ← rmap_item을 stable node에 연결

    KSM Stable/Unstable Tree 비교

    조건별 비교

    KSM 스캔 모드 비교

    항목KSM_RUN_STOP (0)KSM_RUN_MERGE (1)KSM_RUN_UNMERGE (2)
    ksmd 동작중지스캔/병합 수행병합 해제만 수행
    트리 동작불변stable/unstable tree 갱신모든 rmap_item을 unstable에서 제거
    COW 발동없음병합 시 write-protect 발생break_ksm()으로 모든 KSM 페이지 병합 해제
    트리거`echo 0 > run``echo 1 > run``echo 2 > run`

    Smart Scan vs 일반 스캔

    항목Smart Scan (기본 켜짐)일반 스캔 (끄면)
    건너뜀 메커니즘age/remaining_skips로 빈번 변경 페이지 스킵모든 페이지 스캔
    건너뜀 기준age ≥ 3부터 점진적 스킵 (1→2→4→8회)없음
    pages_skipped증가0 유지
    대상`folio_test_ksm()`이 아닌 일반 익명 페이지모든 anonymous 페이지
    장점불필요한 스캔 최소화, CPU 절약완전한 스캔 보장

    NUMA 병합 정책 비교

    항목merge_across_nodes=1 (기본)merge_across_nodes=0
    stable/unstable tree전역 1개씩NUMA node별 별도 tree
    NUMA 원격 병합허용불허 (같은 노드 내에서만 병합)
    메모리 절감최대 (원격 포함)locality 우선
    tree 구조`root_stable_tree[0]`만 사용`root_stable_tree[nr_node_ids]` 배열

    Advisor 모드 비교

    항목KSM_ADVISOR_NONE (기본)KSM_ADVISOR_SCAN_TIME
    pages_to_scan 제어수동 (sysfs)자동 적응형
    목표없음`ksm_advisor_target_scan_time` (기본 200초)
    CPU 제한없음`KSM_ADVISOR_MIN_CPU` (10%) ~ `ksm_advisor_max_cpu` (70%)
    EWMA 적용없음스캔 시간 변화량을 EWMA로 평활

    VMA 지정 경로 비교

    항목애플리케이션 `madvise`운영자 sysfs
    대상개별 VMA전체 ksmd 정책
    핵심 동작`MADV_MERGEABLE`로 `VM_MERGEABLE` 설정, `MADV_UNMERGEABLE`로 해제`run`, `pages_to_scan`, `sleep_millisecs`, `advisor_mode` 조정
    커널 진입점`mm/madvise.c` → `ksm_madvise()``mm/ksm.c` sysfs store 함수
    효과병합 후보 지정/해제스캔 실행, 속도, CPU 상한 제어

    Stable Tree 노드 유형 비교

    항목일반 stable_nodechain stable_nodedup stable_node
    역할KSM 페이지 1개를 나타냄동일 내용의 여러 KSM 페이지 그룹chain 내 개별 KSM 페이지
    rmap_hlist_len0 ~ ksm_max_page_sharingSTABLE_NODE_CHAIN (-1024)0 ~ ksm_max_page_sharing
    hlistrmap_item 연결dup stable_node 연결rmap_item 연결
    rb_tree(rb_node 사용)(rb_node 사용)(rb_node 미사용, hlist_dup로 chain에 연결)

    KSM 수익 계산

    KSM의 메모리 절감 효과는 general_profit으로 측정됩니다:

    general_profit = (pages_sharing + ksm_zero_pages) × PAGE_SIZE
                     - rmap_items × sizeof(struct ksm_rmap_item)
  • 양수: KSM이 메모리를 절약하고 있음 (병합된 페이지의 공유 메모리 > rmap_item 오버헤드)
  • 음수: rmap_item 오버헤드가 병합 이익보다 큼 → KSM 비활성 고려

  • 관련 문서

  • 00-overview.html — 메모리 관리 개요 (KSM 섹션)
  • 03-vma_mmap.html — VMA/mmap (VM_MERGEABLE 플래그)
  • 05-page_reclaim.html — 페이지 회수 (write-protect와 관련)
  • 10-hugepage.html — Huge Pages (THP와 KSM 상호작용)
  • 13-numa.html — NUMA (merge_across_nodes 정책)

  • 참고: KSM Sysfs 인터페이스 요약

    파일읽기/쓰기설명기본값
    `/sys/kernel/mm/ksm/run`RW0:중지, 1:병합, 2:병합해제0
    `/sys/kernel/mm/ksm/pages_to_scan`RW한 번에 스캔할 페이지 수100
    `/sys/kernel/mm/ksm/sleep_millisecs`RW스캔 간 대기 (ms)20
    `/sys/kernel/mm/ksm/merge_across_nodes`RWNUMA 간 병합 허용1
    `/sys/kernel/mm/ksm/use_zero_pages`RWzero page 병합0
    `/sys/kernel/mm/ksm/smart_scan`RW스마트 스캔1
    `/sys/kernel/mm/ksm/max_page_sharing`RW최대 공유 매핑 수256
    `/sys/kernel/mm/ksm/advisor_mode`RWnone / scan-timenone
    `/sys/kernel/mm/ksm/advisor_max_cpu`RW최대 CPU%70
    `/sys/kernel/mm/ksm/advisor_min_pages_to_scan`RW최소 스캔 페이지 수500
    `/sys/kernel/mm/ksm/advisor_max_pages_to_scan`RW최대 스캔 페이지 수30000
    `/sys/kernel/mm/ksm/advisor_target_scan_time`RW목표 스캔 시간 (초)200
    `/sys/kernel/mm/ksm/pages_shared`RO고유 KSM 페이지 수-
    `/sys/kernel/mm/ksm/pages_sharing`RO공유 매핑 수-
    `/sys/kernel/mm/ksm/pages_unshared`RO미병합 후보 수-
    `/sys/kernel/mm/ksm/pages_volatile`RO어느 트리에도 없는 rmap_item-
    `/sys/kernel/mm/ksm/pages_scanned`RO총 스캔 페이지 수-
    `/sys/kernel/mm/ksm/pages_skipped`RO건너뛴 페이지 수-
    `/sys/kernel/mm/ksm/ksm_zero_pages`ROzero page 병합 수-
    `/sys/kernel/mm/ksm/full_scans`RO전체 스캔 완료 횟수-
    `/sys/kernel/mm/ksm/general_profit`RO메모리 수익 (bytes)-
    `/sys/kernel/mm/ksm/advisor_mode`RWnone / scan-timenone
    `/sys/kernel/mm/ksm/advisor_max_cpu`RW최대 CPU%70
    `/sys/kernel/mm/ksm/advisor_target_scan_time`RW목표 스캔 시간 (초)200