# Virtio Balloon
관련 소스:mm/balloon.c,drivers/virtio/virtio_balloon.c,include/linux/balloon.h,include/uapi/linux/virtio_balloon.h
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 등 다양한 기능이 추가되어 있습니다.
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"
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 플래그를 사용하여 메모리 관리 카운터를 조정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;
};
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 값 */
};
include/uapi/linux/virtio_balloon.h:126 — 호스트에게 전송되는 메모리 통계 구조체
struct virtio_balloon_stat {
__virtio16 tag; /* 통계 타입 (VIRTIO_BALLOON_S_* 상수) */
__virtio64 val; /* 통계 값 */
} __attribute__((packed));
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 지원 */
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);
}
CONFIG_BALLOON_MIGRATION이 활성화되면 GFP_HIGHUSER_MOVABLE를 사용하여 페이지 마이그레이션 가능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 노드 상태를 증가시킴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;
}
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;
}
balloon_lock 뮤텍스로 동시성 보호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;
}
MUST_TELL_HOST 기능이 있으면 반드시 virtqueue를 통해 호스트에게 먼저 알려야 함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)
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 (실제 해제)
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 발생
└→ virtio_balloon_oom_notify
└→ leak_balloon(vb, 256) /* 최대 256 페이지 해제 */
└→ update_balloon_size (호스트에게 새 크기 알림)
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 (이전 페이지 해제)
| 플래그 | 비트 | 동작 | 드라이버에 미치는 영향 |
|---|---|---|---|
| MUST_TELL_HOST | 0 | 페이지 반환 전 호스트에게 반드시 알림 | deflate 시 virtqueue 통신 필수 |
| STATS_VQ | 1 | 메모리 통계 전송 virtqueue 지원 | stats_vq 생성, 통계 수집 워크큐 |
| DEFLATE_ON_OOM | 2 | OOM 시 자동으로 balloon 해제 | oom_nb 등록, 최대 256 페이지 해제 |
| FREE_PAGE_HINT | 3 | Free Page Hint virtqueue 지원 | free_page_vq, shrinker, 전용 워크큐 |
| PAGE_POISON | 4 | 페이지 포이즈닝 사용 | poison_val을 호스트에게 설정 |
| REPORTING | 5 | Free Page Reporting virtqueue 지원 | reporting_vq, page_reporting_dev_info |
| 항목 | 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 | Free Page Reporting |
|---|---|---|
| virtqueue | `free_page_vq` | `reporting_vq` |
| 할당 단위 | `VIRTIO_BALLOON_HINT_BLOCK_ORDER` (MAX_PAGE_ORDER) | `page_reporting_dev_info.order` |
| 동작 방식 | 능동적으로 free page를 할당하여 호스트에게 전달 | 호스트가 요청한 페이지를 직접 보고 |
| cmd_id | START → STOP 명령 교환 | 불필요 |
| shrinker | `virtio_balloon_shrinker_scan/count` | 불필요 |
| 워크큐 | `balloon_wq` (전용) | `page_reporting_dev_info.report` 콜백 |
| 상태 | page 플래그 | page_private | isolation |
|---|---|---|---|
| 일반 balloon 페이지 | `PageOffline=true`, `PageMovableOps=true` | balloon_dev_info 포인터 | `isolated_pages=0` |
| 격리된 페이지 | 동일 | 동일 | `list_del` + `isolated_pages++` |
| 해제된 페이지 | `PageOffline` 유지 (sticky) | 0 (clear) | 없음 |