Ryotta's Linux 7.0 MM

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

# Virtio Balloon

관련 소스: mm/balloon.c, drivers/virtio/virtio_balloon.c, include/linux/balloon.h, include/uapi/linux/virtio_balloon.h

개요 (Overview)

Virtio Balloon은 가상머신에서 호스트(하이퍼바이저)와 게스트 간에 동적으로 메모리를 주고받는 메커니즘입니다. 게스트 OS는 "balloon"을 inflate하여 사용 중인 페이지를 호스트에게 반환하고, deflate하여 호스트로부터 메모리를 다시 가져올 수 있습니다. 이를 통해 여러 가상머신 간 메모리 오버커밋이 가능해집니다.

커널은 mm/balloon.c에 공용 인터페이스를, drivers/virtio/virtio_balloon.c에 Virtio 드라이버 구현을 제공합니다. 공용 인터페이스는 페이지 할당, enqueue/dequeue, 마이그레이션을 담당하며, 드라이버 측은 virtqueue를 통해 호스트와 통신합니다. Linux 7.0에서는 Free Page Hint, Free Page Reporting, OOM 시 자동 deflate 등 다양한 기능이 추가되어 있습니다.

Virtio Balloon 자료구조 관계도
Virtio Balloon 호출 흐름도

소스 파일 경로

mm/balloon.c                              ← 공용 balloon 인터페이스
drivers/virtio/virtio_balloon.c           ← Virtio balloon 드라이버
include/linux/balloon.h                   ← balloon_dev_info 정의
include/uapi/linux/virtio_balloon.h       ← feature 플래그, 통계 정의

빠른 점검 명령

# balloon 페이지 수 확인
cat /proc/ballooninfo

# Virtio balloon 모듈 상태 확인
lsmod | grep virtio_balloon

# virtio 디바이스 정보 확인
ls /sys/class/misc/balloon*

# 게스트 메모리 통계 (balloon 통계)
dmesg | grep -i balloon

# Free Page Reporting 상태 확인
cat /sys/kernel/mm/page_reporting/reporting

# OOM 알림 설정 확인 (deflate_on_oom)
dmesg | grep "deflate on oom"

# 메모리 압력 확인 (balloon inflate/deflate 트리거)
grep -i balloon /proc/vmstat

# balloon 마이그레이션 활성화 확인
grep BALLOON /proc/vmstat

# virtio 디바이스 드라이버 정보
lspci -v | grep -i virtio

# 게스트에서 호스트 통신 상태 (virtqueue)
dmesg | grep "virtio.*balloon"

핵심 자료구조

struct balloon_dev_info

include/linux/balloon.h:53 — 모든 balloon 디바이스의 공용 페이지 관리 구조체

struct balloon_dev_info {
    unsigned long isolated_pages;    /* 마이그레이션을 위해 격리된 페이지 수 */
    struct list_head pages;           /* 호스트에게 할당된 페이지 목록 (LRU) */
    int (*migratepage)(struct balloon_dev_info *, struct page *newpage,
            struct page *page, enum migrate_mode mode);  /* 페이지 마이그레이션 콜백 */
    bool adjust_managed_page_count;  /* managed_page_count 조정 여부 */
};
  • isolated_pages: Compaction 스레드가 마이그레이션을 위해 격리한 balloon 페이지 수
  • pages: balloon_page_insert()로 추가되는 페이지들의 연결 리스트
  • migratepage: 드라이버별 마이그레이션 콜백 (virtio는 virtballoon_migratepage)
  • adjust_managed_page_count: OOM deflate 시 adjust_managed_page_count 플래그를 사용하여 메모리 관리 카운터를 조정
  • struct virtio_balloon

    drivers/virtio/virtio_balloon.c:55 — Virtio balloon 드라이버의 핵심 구조체

    struct virtio_balloon {
        struct virtio_device *vdev;
        struct virtqueue *inflate_vq, *deflate_vq, *stats_vq, *free_page_vq;
    
        struct workqueue_struct *balloon_wq;        /* Free Page Hint 전용 워크큐 */
        struct work_struct report_free_page_work;    /* Free Page Reporting 작업 */
        struct work_struct update_balloon_stats_work; /* 통계 업데이트 작업 */
        struct work_struct update_balloon_size_work;  /* 크기 조정 작업 */
    
        spinlock_t stop_update_lock;
        bool stop_update;
        unsigned long config_read_bitmap;
    
        struct list_head free_page_list;     /* Free Page Hint용 할당된 페이지 목록 */
        spinlock_t free_page_list_lock;
        unsigned long num_free_page_blocks;  /* free_page_list의 블록 수 */
    
        u32 cmd_id_received_cache;   /* 호스트로부터 받은 cmd_id 캐시 */
        __virtio32 cmd_id_active;    /* 현재 활성화된 cmd_id */
        __virtio32 cmd_id_stop;      /* 정지 신호 cmd_id */
    
        wait_queue_head_t acked;     /* 호스트 ack 대기 큐 */
        unsigned int num_pages;      /* 현재 balloon에 있는 페이지 수 */
        struct balloon_dev_info vb_dev_info;  /* 공용 balloon 디바이스 정보 */
    
        struct mutex balloon_lock;   /* inflate/deflate 동기화 뮤텍스 */
        unsigned int num_pfns;       /* 호스트에 알려줄 PFN 배열 길이 */
        __virtio32 pfns[VIRTIO_BALLOON_ARRAY_PFNS_MAX];  /* PFN 배열 (최대 256개) */
    
        struct virtio_balloon_stat stats[VIRTIO_BALLOON_S_NR]; /* 메모리 통계 */
    
        struct shrinker *shrinker;           /* Free Page Hint용 shrinker */
        struct notifier_block oom_nb;        /* OOM 알림 블록 */
    
        struct virtqueue *reporting_vq;      /* Free Page Reporting virtqueue */
        struct page_reporting_dev_info pr_dev_info;  /* page reporting 디바이스 정보 */
    
        spinlock_t wakeup_lock;
        bool processing_wakeup_event;
        u32 wakeup_signal_mask;
    };

    virtio_balloon_config (호스트 ↔ 게스트 공유)

    include/uapi/linux/virtio_balloon.h:46 — 호스트와 게스트가 공유하는 설정 구조체

    struct virtio_balloon_config {
        __le32 num_pages;  /* 호스트가 게스트에게 반환을 요청한 페이지 수 */
        __le32 actual;     /* 게스트가 실제로 balloon에 넣은 페이지 수 */
        union {
            __le32 free_page_hint_cmd_id;     /* Free Page Hint 명령 ID */
            __le32 free_page_report_cmd_id;   /* deprecated 별칭 */
        };
        __le32 poison_val;  /* PAGE_POISON 값 */
    };

    virtio_balloon_stat (메모리 통계)

    include/uapi/linux/virtio_balloon.h:126 — 호스트에게 전송되는 메모리 통계 구조체

    struct virtio_balloon_stat {
        __virtio16 tag;  /* 통계 타입 (VIRTIO_BALLOON_S_* 상수) */
        __virtio64 val;  /* 통계 값 */
    } __attribute__((packed));

    virtio_balloon_feature 플래그

    include/uapi/linux/virtio_balloon.h:34 — 지원 기능 비트맵

    #define VIRTIO_BALLOON_F_MUST_TELL_HOST  0  /* 페이지 반환 전 호스트에게 알림 필요 */
    #define VIRTIO_BALLOON_F_STATS_VQ        1  /* 메모리 통계 virtqueue 지원 */
    #define VIRTIO_BALLOON_F_DEFLATE_ON_OOM   2  /* OOM 발생 시 자동 balloon 해제 */
    #define VIRTIO_BALLOON_F_FREE_PAGE_HINT   3  /* Free Page Hint virtqueue 지원 */
    #define VIRTIO_BALLOON_F_PAGE_POISON      4  /* 페이지 포이즈닝 사용 */
    #define VIRTIO_BALLOON_F_REPORTING        5  /* Free Page Reporting virtqueue 지원 */

    핵심 함수

    1. balloon_page_alloc

    mm/balloon.c:147 — balloon에 사용할 새 페이지를 할당

    struct page *balloon_page_alloc(void)
    {
        gfp_t gfp_flags = __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN;
    
        if (IS_ENABLED(CONFIG_BALLOON_MIGRATION))
            gfp_flags |= GFP_HIGHUSER_MOVABLE;  /* 마이그레이션 가능한 고유 사용자 영역 */
        else
            gfp_flags |= GFP_HIGHUSER;          /* 일반 고유 사용자 영역 */
    
        return alloc_page(gfp_flags);
    }
  • 역할: Buddy Allocator에서 페이지를 할당하되, balloon이므로 실패해도 재시도하지 않음
  • 분기 로직: CONFIG_BALLOON_MIGRATION이 활성화되면 GFP_HIGHUSER_MOVABLE를 사용하여 페이지 마이그레이션 가능
  • 2. balloon_page_list_enqueue

    mm/balloon.c:75 — 페이지 목록을 balloon에 추가

    size_t balloon_page_list_enqueue(struct balloon_dev_info *b_dev_info,
                     struct list_head *pages)
    {
        struct page *page, *tmp;
        unsigned long flags;
        size_t n_pages = 0;
    
        spin_lock_irqsave(&balloon_pages_lock, flags);
        list_for_each_entry_safe(page, tmp, pages, lru) {
            list_del(&page->lru);
            balloon_page_enqueue_one(b_dev_info, page);  /* 페이지 삽입 + 카운터 증가 */
            n_pages++;
        }
        spin_unlock_irqrestore(&balloon_pages_lock, flags);
        return n_pages;
    }
  • 역할: 할당된 페이지들을 balloon_dev_info->pages 목록에 추가
  • 분기 로직: balloon_page_enqueue_one에서 adjust_managed_page_count 플래그에 따라 managed page count를 조정하고, NR_BALLOON_PAGES 노드 상태를 증가시킴
  • 3. balloon_page_list_dequeue

    mm/balloon.c:111 — balloon에서 페이지를 제거하여 반환

    size_t balloon_page_list_dequeue(struct balloon_dev_info *b_dev_info,
                     struct list_head *pages, size_t n_req_pages)
    {
        struct page *page, *tmp;
        unsigned long flags;
        size_t n_pages = 0;
    
        spin_lock_irqsave(&balloon_pages_lock, flags);
        list_for_each_entry_safe(page, tmp, &b_dev_info->pages, lru) {
            if (n_pages == n_req_pages)
                break;
            list_del(&page->lru);
            if (b_dev_info->adjust_managed_page_count)
                adjust_managed_page_count(page, 1);   /* managed count 복원 */
            balloon_page_finalize(page);               /* 페이지 정리 */
            __count_vm_event(BALLOON_DEFLATE);
            list_add(&page->lru, pages);
            dec_node_page_state(page, NR_BALLOON_PAGES);
            n_pages++;
        }
        spin_unlock_irqrestore(&balloon_pages_lock, flags);
        return n_pages;
    }
  • 역할: balloon 목록에서 요청된 수만큼 페이지를 꺼내 반환
  • 분기 로직: compaction으로 인해 격리된 페이지가 있을 수 있어 요청 수보다 적게 반환될 수 있음
  • 4. fill_balloon (virtio)

    drivers/virtio/virtio_balloon.c:242 — 호스트에게 inflate 요청 (게스트 → 호스트)

    static unsigned int fill_balloon(struct virtio_balloon *vb, size_t num)
    {
        unsigned int num_allocated_pages;
        struct page *page, *next;
        unsigned int num_pfns;
        LIST_HEAD(pages);
    
        num = min(num, ARRAY_SIZE(vb->pfns));  /* 한 번에 최대 256개 */
    
        for (num_pfns = 0; num_pfns < num;
             num_pfns += VIRTIO_BALLOON_PAGES_PER_PAGE) {
            page = balloon_page_alloc();
            if (!page) {
                msleep(200);  /* 할당 실패 시 200ms 대기 후 재시도 */
                break;
            }
            list_add(&page->lru, &pages);
        }
    
        mutex_lock(&vb->balloon_lock);
        vb->num_pfns = 0;
        list_for_each_entry_safe(page, next, &pages, lru) {
            list_del(&page->lru);
            balloon_page_enqueue(&vb->vb_dev_info, page);  /* balloon 목록에 추가 */
            set_page_pfns(vb, vb->pfns + vb->num_pfns, page);
            vb->num_pages += VIRTIO_BALLOON_PAGES_PER_PAGE;
            vb->num_pfns += VIRTIO_BALLOON_PAGES_PER_PAGE;
        }
    
        num_allocated_pages = vb->num_pfns;
        if (vb->num_pfns != 0)
            tell_host(vb, vb->inflate_vq);  /* 호스트에게 PFN 목록 전송 */
        mutex_unlock(&vb->balloon_lock);
        return num_allocated_pages;
    }
  • 역할: 호스트에게 반환할 페이지를 할당하고 PFN 목록을 virtqueue로 전송
  • 분기 로직: 페이지 할당 실패 시 200ms 대기 후 부분 할당으로 진행, balloon_lock 뮤텍스로 동시성 보호
  • 5. leak_balloon (virtio)

    drivers/virtio/virtio_balloon.c:301 — 호스트에서 deflate 요청 (호스트 → 게스트)

    static unsigned int leak_balloon(struct virtio_balloon *vb, size_t num)
    {
        unsigned int num_freed_pages;
        struct page *page;
        struct balloon_dev_info *vb_dev_info = &vb->vb_dev_info;
        LIST_HEAD(pages);
    
        num = min(num, ARRAY_SIZE(vb->pfns));
        mutex_lock(&vb->balloon_lock);
        num = min(num, (size_t)vb->num_pages);  /* 현재 balloon 페이지 수 제한 */
    
        for (vb->num_pfns = 0; vb->num_pfns < num;
             vb->num_pfns += VIRTIO_BALLOON_PAGES_PER_PAGE) {
            page = balloon_page_dequeue(vb_dev_info);
            if (!page)
                break;
            set_page_pfns(vb, vb->pfns + vb->num_pfns, page);
            list_add(&page->lru, &pages);
            vb->num_pages -= VIRTIO_BALLOON_PAGES_PER_PAGE;
        }
    
        num_freed_pages = vb->num_pfns;
        if (vb->num_pfns != 0)
            tell_host(vb, vb->deflate_vq);  /* 호스트에게 해제 알림 */
        release_pages_balloon(vb, &pages);    /* 페이지 해제 (put_page) */
        mutex_unlock(&vb->balloon_lock);
        return num_freed_pages;
    }
  • 역할: balloon에서 페이지를 꺼내 실제로 메모리로 반환
  • 분기 로직: MUST_TELL_HOST 기능이 있으면 반드시 virtqueue를 통해 호스트에게 먼저 알려야 함

  • 호출 흐름

    Inflate 흐름 (게스트 → 호스트 메모리 반환)

    virtballoon_changed
      └→ start_update_balloon_size
           └→ queue_work(update_balloon_size_work)
                └→ update_balloon_size_func
                     └→ towards_target (차이 계산)
                          └→ fill_balloon (diff > 0)
                               ├→ balloon_page_alloc (GFP_HIGHUSER_MOVABLE)
                               ├→ balloon_page_enqueue → balloon_page_insert
                               │    ├→ __SetPageOffline
                               │    ├→ SetPageMovableOps (CONFIG_BALLOON_MIGRATION)
                               │    └→ list_add(&page->lru, &balloon->pages)
                               ├→ set_page_pfns (PFN 변환: page → balloon PFN)
                               └→ tell_host → virtqueue_add_outbuf → virtqueue_kick
                                    └→ wait_event(vb->acked, virtqueue_get_buf)

    Deflate 흐름 (호스트 → 게스트 메모리 반환)

    update_balloon_size_func
      └→ towards_target (차이 계산)
           └→ leak_balloon (diff < 0)
                ├→ balloon_page_dequeue → balloon_page_list_dequeue
                │    ├→ list_del(&page->lru)
                │    ├→ adjust_managed_page_count (카운터 복원)
                │    └→ balloon_page_finalize → set_page_private(0)
                ├→ set_page_pfns
                ├→ tell_host → deflate_vq
                └→ release_pages_balloon → put_page (실제 해제)

    Free Page Hint 흐름 (빈 페이지를 호스트에게 힌트)

    virtballoon_changed
      └→ virtio_balloon_queue_free_page_work
           └→ queue_work(report_free_page_work)
                └→ report_free_page_func
                     ├→ cmd_id_received == DONE?
                     │    └→ return_free_pages_to_mm (모든 free page 반환)
                     └→ cmd_id_received != STOP && 변경됨?
                          └→ virtio_balloon_report_free_page
                               ├→ send_cmd_id_start (시작 명령 전송)
                               ├→ send_free_pages
                               │    └→ loop: get_free_page_and_send
                               │         ├→ alloc_pages (free page block 할당)
                               │         ├→ virtqueue_add_inbuf (host에게 전달)
                               │         └→ list_add(free_page_list)
                               └→ send_cmd_id_stop (정지 명령 전송)

    OOM 시 deflate 흐름

    OOM 발생
      └→ virtio_balloon_oom_notify
           └→ leak_balloon(vb, 256)  /* 최대 256 페이지 해제 */
                └→ update_balloon_size (호스트에게 새 크기 알림)

    페이지 마이그레이션 흐름 (CONFIG_BALLOON_MIGRATION)

    Compaction 스레드
      └→ balloon_page_isolate (격리)
           ├→ balloon_page_device (page_private로 디바이스 확인)
           ├→ list_del (목록에서 제거)
           └→ isolated_pages++
      └→ balloon_page_migrate (마이그레이션)
           ├→ b_dev_info->migratepage → virtballoon_migratepage
           │    ├→ mutex_trylock (락 획득 실패 시 -EAGAIN)
           │    ├→ 1단계: newpage inflate (tell_host via inflate_vq)
           │    └→ 2단계: old page deflate (tell_host via deflate_vq)
           ├→ balloon_page_insert (새 페이지를 balloon 목록에 추가)
           └→ balloon_page_finalize → put_page (이전 페이지 해제)

    조건별 비교

    feature 플래그별 동작

    플래그비트동작드라이버에 미치는 영향
    MUST_TELL_HOST0페이지 반환 전 호스트에게 반드시 알림deflate 시 virtqueue 통신 필수
    STATS_VQ1메모리 통계 전송 virtqueue 지원stats_vq 생성, 통계 수집 워크큐
    DEFLATE_ON_OOM2OOM 시 자동으로 balloon 해제oom_nb 등록, 최대 256 페이지 해제
    FREE_PAGE_HINT3Free Page Hint virtqueue 지원free_page_vq, shrinker, 전용 워크큐
    PAGE_POISON4페이지 포이즈닝 사용poison_val을 호스트에게 설정
    REPORTING5Free Page Reporting virtqueue 지원reporting_vq, page_reporting_dev_info

    inflate vs deflate 비교

    항목fill_balloon (inflate)leak_balloon (deflate)
    방향게스트 → 호스트 (메모리 반환)호스트 → 게스트 (메모리 복원)
    할당/해제`balloon_page_alloc` (할당)`balloon_page_dequeue` (해제)
    virtqueue`inflate_vq``deflate_vq`
    페이지 카운터`num_pages` 증가`num_pages` 감소
    managed count`adjust_managed_page_count(page, -1)``adjust_managed_page_count(page, 1)`
    VM 이벤트`BALLOON_INFLATE``BALLOON_DEFLATE`
    노드 상태`NR_BALLOON_PAGES` 증가`NR_BALLOON_PAGES` 감소
    실패 처리할당 실패 시 200ms 대기 후 부분 진행dequeue 실패 시 BUG() (비정상 상태)

    Free Page Hint vs Free Page Reporting 비교

    항목Free Page HintFree Page Reporting
    virtqueue`free_page_vq``reporting_vq`
    할당 단위`VIRTIO_BALLOON_HINT_BLOCK_ORDER` (MAX_PAGE_ORDER)`page_reporting_dev_info.order`
    동작 방식능동적으로 free page를 할당하여 호스트에게 전달호스트가 요청한 페이지를 직접 보고
    cmd_idSTART → STOP 명령 교환불필요
    shrinker`virtio_balloon_shrinker_scan/count`불필요
    워크큐`balloon_wq` (전용)`page_reporting_dev_info.report` 콜백

    페이지 마이그레이션 상태

    상태page 플래그page_privateisolation
    일반 balloon 페이지`PageOffline=true`, `PageMovableOps=true`balloon_dev_info 포인터`isolated_pages=0`
    격리된 페이지동일동일`list_del` + `isolated_pages++`
    해제된 페이지`PageOffline` 유지 (sticky)0 (clear)없음

    관련 문서

  • 메모리 관리 개요
  • Buddy Allocator
  • Compaction
  • Migration
  • Free Page Reporting
  • Mempool