Memory · Linux 6.18.37 LTS · source + diagram note

vmalloc

최신 LTS 기준: kernel.org longterm 6.18.37 기준으로 파일 위치와 링크를 맞춘 원본 코드 분석 노트입니다. 불연속 physical page를 연속 VA로 제공.

전체 흐름

vmalloc(size)은 커널 가상 주소 공간에 연속 범위를 예약한 뒤 새 물리 페이지들을 할당하고 페이지 테이블에 연결한다. 가상 주소가 연속이어도 물리 페이지 프레임은 서로 떨어질 수 있다. 이 경로는 사용자 공간의 VMA·page fault 처리와 구분한다. vmalloc 진입 · 가상 범위 예약 · 물리 페이지 할당 · 페이지 매핑

vmap(pages[], count, ...)도 연속된 커널 가상 주소를 만들지만, 입력으로 받은 페이지 배열을 매핑한다. 기본 호출에서는 이 페이지들을 새로 할당하지 않으며 소유권도 가져오지 않는다. vmap 계약 · vmap 구현

소스 코드 위치

먼저 확인할 Linux 6.18.37 LTS 소스 파일

원본 코드:
Linux 6.18.37 LTS: mm/vmalloc.c
Linux 6.18.37 LTS: include/linux/vmalloc.h
Linux 6.18.37 LTS: mm/ioremap.c

설명: mm/vmalloc.c에서 VA 예약·페이지 할당·매핑·해제를 추적하고, include/linux/vmalloc.h에서 vm_struct/vmap_area 및 소유권 플래그를 확인한다. mm/ioremap.c는 연관된 I/O 매핑 경로이며 vmalloc()의 물리 페이지 할당 단계는 아니다.

대표 코드

__vmalloc_node_range_noprof()

이 버전에서 범위 예약은 __get_vm_area_node(), 새 페이지 할당과 매핑은 __vmalloc_area_node()가 맡는다.

원본: mm/vmalloc.c:3864–3867
again:
	area = __get_vm_area_node(size, align, shift, VM_ALLOC |
				  VM_UNINITIALIZED | vm_flags, start, end, node,
				  gfp_mask, caller);
원본: mm/vmalloc.c:3904–3943
	/* Allocate physical pages and map them into vmalloc space. */
	ret = __vmalloc_area_node(area, gfp_mask, prot, shift, node);
	if (!ret)
		goto fail;

	/*
	 * Mark the pages as accessible, now that they are mapped.
	 * The condition for setting KASAN_VMALLOC_INIT should complement the
	 * one in post_alloc_hook() with regards to the __GFP_SKIP_ZERO check
	 * to make sure that memory is initialized under the same conditions.
	 * Tag-based KASAN modes only assign tags to normal non-executable
	 * allocations, see __kasan_unpoison_vmalloc().
	 */
	kasan_flags |= KASAN_VMALLOC_VM_ALLOC;
	if (!want_init_on_free() && want_init_on_alloc(gfp_mask) &&
	    (gfp_mask & __GFP_SKIP_ZERO))
		kasan_flags |= KASAN_VMALLOC_INIT;
	/* KASAN_VMALLOC_PROT_NORMAL already set if required. */
	area->addr = kasan_unpoison_vmalloc(area->addr, size, kasan_flags);

	/*
	 * In this function, newly allocated vm_struct has VM_UNINITIALIZED
	 * flag. It means that vm_struct is not fully initialized.
	 * Now, it is fully initialized, so remove this flag here.
	 */
	clear_vm_uninitialized_flag(area);

	if (!(vm_flags & VM_DEFER_KMEMLEAK))
		kmemleak_vmalloc(area, PAGE_ALIGN(size), gfp_mask);

	return area->addr;

fail:
	if (shift > PAGE_SHIFT) {
		shift = PAGE_SHIFT;
		align = original_align;
		goto again;
	}

	return NULL;

매핑 성공 뒤 접근 가능 상태를 설정하고 VM_UNINITIALIZED를 지운다. 큰 페이지 시도가 실패하면 보통 페이지 크기로 다시 시도한다. 완료·재시도 분기

vmap()

원본: mm/vmalloc.c:3501–3540
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[]를 매핑한다. VM_MAP_PUT_PAGES를 지정한 경우에만 성공 후 배열과 각 페이지 참조를 vfree() 쪽으로 넘긴다. 소유권 계약 · 성공 뒤 기록

함수별 분석

  1. 일반 할당 진입: vmalloc_noprof()이 __vmalloc_node_noprof()를 거쳐 __vmalloc_node_range_noprof()에 도달한다. mm/vmalloc.c:3965–3970 · mm/vmalloc.c:3999–4003
  2. 가상 범위: __get_vm_area_node()가 vm_struct를 만들고 alloc_vmap_area()로 주소 범위를 예약한다. 기본적으로 가드 페이지 하나를 더 잡는다. mm/vmalloc.c:3172–3205
  3. 물리 페이지와 페이지 테이블: __vmalloc_area_node()가 페이지 포인터 배열을 준비하고 vm_area_alloc_pages()로 새 페이지를 받은 뒤 vmap_pages_range()로 매핑한다. 페이지 부족 또는 매핑 실패는 vfree() 경로로 정리한다. mm/vmalloc.c:3686–3719 · mm/vmalloc.c:3732–3796
  4. 별도 API vmap(): get_vm_area_caller()로 가상 범위를 잡지만 물리 페이지는 호출자가 전달한다. 매핑 실패 시 vunmap()으로 가상 범위만 해제한다. mm/vmalloc.c:3501–3539

vmap()이 vmalloc()의 물리 페이지 할당 함수인 것은 아니다. 두 경로는 가상 범위 예약과 페이지 테이블 매핑이라는 공통 작업을 사용한다.

구조체와 소유권

vmap_area는 주소 범위와 busy/free/lazy 관리 노드를 담고, vm_struct는 주소·플래그·페이지 배열·페이지 수를 기록한다. 페이지 배열의 소유권은 어떤 API와 플래그로 만들었는지에 따라 달라진다. include/linux/vmalloc.h:52–84

객체실제 역할수명·해제 경계
vmap_area예약된 커널 가상 주소 범위를 busy 트리에서 관리해제 시 busy 트리에서 분리되고, 일반 경로는 PTE 제거 뒤 lazy 목록에 들어가 TLB purge 뒤 재사용된다. mm/vmalloc.c:2495–2518 · mm/vmalloc.c:2448–2456
vm_structaddr, flags, pages, nr_pages 등 매핑 기록remove_vm_area()가 매핑을 제거하고 기록을 돌려준다. mm/vmalloc.c:3288–3310
물리 페이지vmalloc()은 새 페이지를 확보, 기본 vmap()은 호출자 페이지를 사용vfree()는 보유한 페이지 참조를 반환하고, vunmap()은 매핑 기록만 해제한다. mm/vmalloc.c:3435–3455 · mm/vmalloc.c:3467–3483

연속 VA와 API별 소유권

아래 그림의 PFN 값은 비연속성을 보여 주기 위한 임의 예시다. 실제 주소 배치나 커널 실행 추적이 아니다. 기본 가드 영역은 예약하지만 매핑하지 않으며 VM_NO_GUARD 선택 경로에서는 달라진다. 가드·범위 예약

그림 1. vmalloc()과 vmap()의 페이지 출처, VA→PFN 관계, 해제 소유권
vmalloc은 새 물리 페이지를, vmap은 호출자가 준 페이지를 연속 커널 가상 주소에 매핑한다. 인접한 가상 페이지가 비연속 물리 프레임을 가리키고 해제 API별 페이지 소유권이 다르다.

근거: vmalloc VA 예약 · 새 페이지 할당·매핑 · vmap의 입력 페이지 매핑 · 해제 경로. 도식은 개념도이며 실제 주소나 호출 순서 기록이 아니다.

그림 크게 열기

API별 물리 페이지 출처와 해제 책임
API물리 페이지 출처성공 뒤 소유권적절한 해제
vmalloc(size)vm_area_alloc_pages()가 새 페이지를 확보vm_struct.pages 배열과 페이지 참조를 할당 경로가 보유vfree(): VA 매핑을 제거하고 페이지 참조·배열·기록을 정리. mm/vmalloc.c:3732–3734 · mm/vmalloc.c:3428–3455
vmap(pages[], …) 기본호출자가 전달한 페이지페이지 참조와 입력 배열의 소유권은 호출자에게 유지vunmap(): VA 매핑·기록만 정리; 호출자가 페이지를 별도 관리. mm/vmalloc.c:3486–3499 · mm/vmalloc.c:3467–3483
vmap(..., VM_MAP_PUT_PAGES)호출자가 전달한 페이지매핑 성공 후 배열과 각 페이지 참조를 매핑 쪽으로 양도vfree(): 양도한 참조와 배열까지 정리. mm/vmalloc.c:3493–3497 · mm/vmalloc.c:3535–3539 · mm/vmalloc.c:3438–3455

일반 해제는 PTE를 제거한 뒤 가상 영역을 lazy 목록에 넣는다. TLB flush 및 VA 재사용은 purge 단계에서 이어질 수 있으므로 “vfree() 직후 모든 CPU의 이전 TLB 항목이 즉시 소멸”로 읽지 않는다. mm/vmalloc.c:2448–2456 · 지연 alias 설명

불변 조건과 실패 사례

VA 예약과 가드

실제 매핑 크기와 예약 크기를 구분한다. 기본 경로는 끝에 가드 페이지를 더 예약하며, 페이지 매핑에는 get_vm_area_size()로 얻은 사용 범위만 넘긴다. 예약 · 매핑 크기

공개 시점

VM_ALLOC 경로는 페이지 매핑과 접근 가능 처리 뒤 VM_UNINITIALIZED를 지운다. /proc/vmallocinfo도 이 플래그가 남은 기록은 건너뛴다. 매핑·완료 · 조회 조건

페이지 소유권

vmap()의 기본 입력 페이지는 호출자 소유다. VM_MAP_PUT_PAGES로 성공한 경우에만 페이지 참조와 배열을 양도하므로 vunmap()과 vfree() 선택이 달라진다. 소유권 계약 · 해제 차이

주의

연속된 vmalloc 가상 주소를 연속 물리 주소로 취급하지 않는다.

주의

vfree()의 PTE 제거와 lazy TLB purge를 같은 순간으로 합치지 않는다. unmap·lazy 반납

주의

I/O 영역을 매핑하는 ioremap()과 새 일반 메모리를 할당하는 vmalloc()의 입력·보호 속성을 혼동하지 않는다.

계측과 검증

/proc/vmallocinfo는 현재 busy 가상 영역을 보여 주며 VM_UNINITIALIZED 항목은 건너뛴다. 할당의 중간 단계가 모두 기록된 추적 로그는 아니다. mm/vmalloc.c:5055–5103

관찰 지점알 수 있는 것한계
/proc/vmallocinfo남아 있는 가상 영역, 크기, 호출자, 페이지 수해제된 영역과 실패 중간 상태는 표시하지 않는다.
trace_alloc_vmap_area()VA 범위 예약 시도와 실패 여부. mm/vmalloc.c:2068–2068물리 페이지 할당 완료를 뜻하지 않는다.
trace_free_vmap_area_noflush() / trace_purge_vmap_area_lazy()lazy 반납과 뒤따른 purge를 구분. mm/vmalloc.c:2438–2438 · mm/vmalloc.c:2385–2385해제 API 호출과 TLB purge 시점이 같다고 가정하면 안 된다.

추가 확인 사항

  • vmalloc()의 예약 범위에는 언제 가드 페이지가 추가되고, 실제 페이지를 매핑하는 크기는 어떻게 산정되는가? 예약 · 사용 크기
  • 큰 페이지를 허용한 요청의 할당·매핑이 실패하면 어느 분기에서 일반 페이지 크기로 다시 시도하는가? 큰 페이지 시도 · 재시도
  • vmap(..., VM_MAP_PUT_PAGES)가 실패하거나 성공했을 때 호출자 페이지 배열의 소유권은 각각 누구에게 남는가? 매핑·소유권 기록
  • /proc/vmallocinfo에 나타나지 않는 영역이 모두 할당 실패를 뜻하는가? VM_UNINITIALIZED 필터와 lazy 목록을 함께 확인한다. 표시 필터 · lazy 목록