전체 흐름
vmalloc 계열은 필요한 backing page를 내부에서 확보해 연속된 커널 가상 주소에 매핑한다. vmap은 호출자가 이미 가진 struct page * 배열을 연속 가상 주소에 매핑한다. 둘 다 물리 주소가 연속이라는 보장은 없으며, 한 API가 다른 공개 API를 차례로 부르는 흐름도 아니다.
__vmalloc_node_range → __get_vm_area_node → __vmalloc_area_node와 vmap(pages, count, flags, prot)를 별도 레인으로 읽는다. 전자는 page 확보, 후자는 호출자 page 사용이 핵심 차이다.
기본 vmap은 vunmap으로 매핑을 해제하고 page는 호출자가 관리한다. 성공한 VM_MAP_PUT_PAGES 경로는 배열과 각 page 참조 하나를 넘겨 vfree로 해제한다.
화살표는 코드상 생략된 helper를 포함한 요약 경로이며 공개 vmap()이 vmalloc()의 다음 호출이라는 뜻이 아니다. 근거: vmalloc.c:2879–2933, 3115–3140, 3279–3322.
소스 코드 위치
고정 기준: Linux v6.6
로컬 mm/vmalloc.c 원문과 행별 보기에서 예약·매핑·해제를 대조한다. 기존의 mm/vmalloc.c, include/linux/vmalloc.h, mm/ioremap.c 링크도 유지한다.
이 글은 고정된 v6.6 mm/vmalloc.c의 조건을 설명한다. ioremap의 cache attribute와 아키텍처별 TLB 처리 전체를 이 파일만으로 일반화하지 않는다.
대표 코드
__vmalloc_node_range: VM_ALLOC 영역과 내부 할당 호출
again:
area = __get_vm_area_node(real_size, align, shift, VM_ALLOC |
VM_UNINITIALIZED | vm_flags, start, end, node,
gfp_mask, caller);
if (!area) {
bool nofail = gfp_mask & __GFP_NOFAIL;
warn_alloc(gfp_mask, NULL,
"vmalloc error: size %lu, vm_struct allocation failed%s",
real_size, (nofail) ? ". Retrying." : "");
if (nofail) {
schedule_timeout_uninterruptible(1);
goto again;
}
goto fail;
}
/*
* Prepare arguments for __vmalloc_area_node() and
* kasan_unpoison_vmalloc().
*/
if (pgprot_val(prot) == pgprot_val(PAGE_KERNEL)) {
if (kasan_hw_tags_enabled()) {
/*
* Modify protection bits to allow tagging.
* This must be done before mapping.
*/
prot = arch_vmap_pgprot_tagged(prot);
/*
* Skip page_alloc poisoning and zeroing for physical
* pages backing VM_ALLOC mapping. Memory is instead
* poisoned and zeroed by kasan_unpoison_vmalloc().
*/
gfp_mask |= __GFP_SKIP_KASAN | __GFP_SKIP_ZERO;
}
/* Take note that the mapping is PAGE_KERNEL. */
kasan_flags |= KASAN_VMALLOC_PROT_NORMAL;
}
/* Allocate physical pages and map them into vmalloc space. */
ret = __vmalloc_area_node(area, gfp_mask, prot, shift, node);
if (!ret)
goto fail;__get_vm_area_node()로 VA 영역을 예약하고 VM_ALLOC을 붙인다. 뒤에서 __vmalloc_area_node()를 호출해 물리 페이지 확보와 매핑을 수행한다. 이 발췌에는 vmap() 호출이 없다.
__vmalloc_area_node: 페이지 배열과 backing page 확보
array_size = (unsigned long)nr_small_pages * sizeof(struct page *);
if (!(gfp_mask & (GFP_DMA | GFP_DMA32)))
gfp_mask |= __GFP_HIGHMEM;
/* Please note that the recursion is strictly bounded. */
if (array_size > PAGE_SIZE) {
area->pages = __vmalloc_node(array_size, 1, nested_gfp, node,
area->caller);
} else {
area->pages = kmalloc_node(array_size, nested_gfp, node);
}
if (!area->pages) {
warn_alloc(gfp_mask, NULL,
"vmalloc error: size %lu, failed to allocated page array size %lu",
nr_small_pages * PAGE_SIZE, array_size);
free_vm_area(area);
return NULL;
}
set_vm_area_page_order(area, page_shift - PAGE_SHIFT);
page_order = vm_area_page_order(area);
area->nr_pages = vm_area_alloc_pages(gfp_mask | __GFP_NOWARN,
node, page_order, nr_small_pages, area->pages);내부에서 페이지 포인터 배열을 마련한 뒤 vm_area_alloc_pages()로 backing page를 채운다. 같은 함수의 3181–3184행은 그 배열을 vmap_pages_range()에 넘긴다. 이 매핑 helper와 별도 공개 API vmap()을 혼동하지 않는다.
vmap: 호출자가 준 page 배열의 매핑
/**
* vmap - map an array of pages into virtually contiguous space
* @pages: array of page pointers
* @count: number of pages to map
* @flags: vm_area->flags
* @prot: page protection for the mapping
*
* Maps @count pages from @pages into contiguous kernel virtual space.
* If @flags contains %VM_MAP_PUT_PAGES the ownership of the pages array itself
* (which must be kmalloc or vmalloc memory) and one reference per pages in it
* are transferred from the caller to vmap(), and will be freed / dropped when
* vfree() is called on the return value.
*
* Return: the address of the area or %NULL on failure
*/
void *vmap(struct page **pages, unsigned int count,
unsigned long flags, pgprot_t prot)
{
struct vm_struct *area;
unsigned long addr;
unsigned long size; /* In bytes */
might_sleep();
if (WARN_ON_ONCE(flags & VM_FLUSH_RESET_PERMS))
return NULL;
/*
* Your top guard is someone else's bottom guard. Not having a top
* guard compromises someone else's mappings too.
*/
if (WARN_ON_ONCE(flags & VM_NO_GUARD))
flags &= ~VM_NO_GUARD;
if (count > totalram_pages())
return NULL;
size = (unsigned long)count << PAGE_SHIFT;
area = get_vm_area_caller(size, flags, __builtin_return_address(0));
if (!area)
return NULL;
addr = (unsigned long)area->addr;
if (vmap_pages_range(addr, addr + size, pgprot_nx(prot),
pages, PAGE_SHIFT) < 0) {
vunmap(area->addr);
return NULL;
}
if (flags & VM_MAP_PUT_PAGES) {
area->pages = pages;
area->nr_pages = count;
}
return area->addr;
}pages는 호출자가 전달한다. vmap()은 VA 영역을 확보하고 그 페이지를 매핑하지만 이 함수 안에서 새 backing page를 할당하지 않는다. VM_MAP_PUT_PAGES가 있는 성공 경로에서만 kmalloc/vmalloc 메모리로 만든 배열 자체와 각 page의 참조 하나가 넘어간다.
vfree: vmalloc 소유 페이지 또는 이전된 참조 해제
if (!addr)
return;
vm = remove_vm_area(addr);
if (unlikely(!vm)) {
WARN(1, KERN_ERR "Trying to vfree() nonexistent vm area (%p)\n",
addr);
return;
}
if (unlikely(vm->flags & VM_FLUSH_RESET_PERMS))
vm_reset_perms(vm);
for (i = 0; i < vm->nr_pages; i++) {
struct page *page = vm->pages[i];
BUG_ON(!page);
mod_memcg_page_state(page, MEMCG_VMALLOC, -1);
/*
* High-order allocs for huge vmallocs are split, so
* can be freed as an array of order-0 allocations
*/
__free_page(page);
cond_resched();
}
atomic_long_sub(vm->nr_pages, &nr_vmalloc_pages);
kvfree(vm->pages);
kfree(vm);
}remove_vm_area()로 매핑을 제거한 다음 기록된 page마다 __free_page()를 호출하고 배열을 해제한다. 실제 물리 page의 최종 해제는 참조가 남아 있는지에 따른다. VM_MAP_PUT_PAGES로 성공한 vmap 결과에도 이 해제 API가 지정된다.
vunmap: 보통의 vmap 매핑만 해제
void vunmap(const void *addr)
{
struct vm_struct *vm;
BUG_ON(in_interrupt());
might_sleep();
if (!addr)
return;
vm = remove_vm_area(addr);
if (unlikely(!vm)) {
WARN(1, KERN_ERR "Trying to vunmap() nonexistent vm area (%p)\n",
addr);
return;
}
kfree(vm);vunmap()은 가상 영역을 제거하고 vm_struct를 해제한다. 이 경로에는 page별 __free_page()가 없다. 기본 vmap 호출자의 page 배열·참조는 호출자 책임으로 남는다.
함수별 분석
1. vmalloc 계열의 내부 할당
__vmalloc_node_range()는 __get_vm_area_node()로 VM_ALLOC 영역을 확보한 뒤 __vmalloc_area_node()를 호출한다. 후자는 page 포인터 배열을 만들고 vm_area_alloc_pages()로 page를 확보한 다음 vmap_pages_range()에 전달한다. 중간 helper를 생략한 요약이며 공개 vmap() 호출 체인이 아니다.
2. 호출자 page의 vmap
vmap()은 전달받은 pages를 vmap_pages_range()로 매핑한다. VM_MAP_PUT_PAGES가 설정된 성공 경로만 area->pages와 nr_pages를 기록한다. 실패하면 NULL을 돌려주며 성공한 소유권 이전으로 취급하지 않는다.
3. 나중의 해제
vfree()와 vunmap()은 모두 remove_vm_area()를 거치지만 page 처리 범위는 다르다. 기본 vmap 결과는 vunmap() 뒤에도 호출자 page 소유권이 유지된다. vmalloc 및 성공한 VM_MAP_PUT_PAGES 경로는 vfree()가 기록된 page 참조와 배열을 정리한다.
구조체와 필드
가상 영역 기록과 물리 page 소유권을 한 화살표로 묶지 않는다. 같은 연속 VA라도 누가 page를 마련했는지와 해제 책임이 다르다.
| 객체/필드 | 역할 | 확인 지점 |
|---|---|---|
vmap_area | 충돌하지 않는 VA 구간의 관리 node | remove_vm_area |
vm_struct | 주소·flags·page 배열 등을 기록하는 영역 descriptor | vmap area |
area->pages | vmalloc 내부 확보 page 또는 조건부 이전된 vmap 배열 | vmap flags / vmalloc allocation |
VM_MAP_PUT_PAGES | 성공한 vmap에서 배열 및 각 page 참조 하나를 넘기는 조건 | API 계약 |
실행 단계와 상태 변화
생성·매핑·해제는 API별로 나누어 비교한다. vfree가 모든 vmap 결과의 일반적인 해제 함수는 아니다.
PFN 숫자는 관계를 설명하기 위한 예시이며 실행 결과가 아니다. 기본 page 단위의 개념도이고 huge vmalloc 선택이나 물리 page의 실제 배열을 단정하지 않는다. 근거: vmap():2916–2932, __vmalloc_area_node():3139–3140와 3181–3183.
vmalloc(size)__vmalloc_node_range()에 추가 VM_NO_GUARD 플래그를 넘기지 않는다. 따라서 __get_vm_area_node()가 요청 크기 외에 가드 한 페이지를 예약한다. 예약:2592–2595 · 기본 진입:3382–3386
vmap(pages[], ...)VM_NO_GUARD 요청은 경고 후 제거하므로 성공한 vmap()도 기본 가드 예약을 유지한다. 실제 페이지 매핑은 count로 계산한 사용 크기에만 한다. 플래그:2906–2911 · 사용 크기:2916–2923
위 막대는 조건을 설명하는 개념도다. __vmalloc_node_range()의 다른 호출자가 VM_NO_GUARD를 명시하면 예약 크기가 달라질 수 있다. 분기 근거:2592–2595
| 경로 | backing page 마련 | 연속 VA 매핑 | 정상 해제/소유권 |
|---|---|---|---|
| vmalloc 계열 | 내부 vm_area_alloc_pages | 내부 vmap_pages_range | vfree가 매핑·기록된 page 참조·배열을 정리 |
| 기본 vmap | 호출자가 미리 제공 | vmap_pages_range | vunmap은 매핑만 정리; page/배열은 호출자 책임 |
vmap + VM_MAP_PUT_PAGES | 호출자가 미리 제공 | vmap_pages_range | 성공 시 배열·각 page 참조 하나 이전; vfree로 정리 |
remove_vm_area()는 free_unmap_vmap_area()를 호출한다. 이 경로는 mapping 제거와 lazy VA 처리에 관여하지만, 모든 아키텍처에서 같은 순간에 모든 CPU TLB가 비워진다고 단정하지 않는다.
불변 조건과 실패 사례
vmalloc/vmap이 연속 가상 주소를 반환해도 물리 page 연속성을 보장하지 않는다. DMA 등 물리 연속성이 필요한 사용자는 별도 조건을 확인해야 한다.
기본 vmap 호출자는 매핑이 살아 있는 동안 page를 유효하게 유지해야 한다. VM_MAP_PUT_PAGES는 성공 시 배열과 참조 하나를 이전하므로 그 후 호출자가 중복 해제하지 않는다.
vunmap()은 interrupt context에서 호출하면 안 된다(2864행). vfree()는 NMI가 아닌 interrupt context에서 vfree_atomic()으로 넘겨 deferred 해제를 요청하지만, 두 함수 모두 NMI에서는 호출하면 안 된다(2777행 · 2812–2817행).
vmalloc과 ioremap의 사용 목적 및 protection은 다를 수 있다. 이 글은 구체적 cache attribute나 TLB 완료 시점을 전 아키텍처에 대해 약속하지 않는다.
계측과 검증
영역이 보이는지와 backing page 소유권을 별도로 확인한다. 관찰 가능한 주소 범위만으로 물리 연속성이나 page의 최종 해제 완료를 추론하지 않는다.
| 관찰 수단 | 알 수 있는 것 | 한계 |
|---|---|---|
cat /proc/vmallocinfo | 현재 기록된 vmalloc/vmap 영역과 호출자 정보 | 각 page의 물리 연속성 증거는 아님 |
trace_alloc_vmap_area / trace_free_vmap_area_noflush | VA 예약과 lazy 해제 지점 | trace 설정 의존; page 참조의 최종 해제와 구별 |
| page-table dump | 해당 VA의 mapping 속성 | 아키텍처·debug 설정 의존 |
| TLB 성능 카운터 | 워크로드 중 miss 변화 | 단일 vmalloc 호출의 인과관계를 직접 증명하지 않음 |
추가 확인 사항
- 호출자가 준 page 배열이
VM_MAP_PUT_PAGES조건을 만족하며 성공했는가? - 현재 주소는 vmalloc 내부 page인가, 기본 vmap의 caller-owned page인가?
- 해제 뒤 여전히 남은 page 참조는 누가 보유하며, mapping 제거 시점과 최종 free 시점은 어떻게 구분할 것인가?