전체 흐름
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()의 물리 페이지 할당 단계는 아니다.
GitHub 원본:
대표 코드
__vmalloc_node_range_noprof()
이 버전에서 범위 예약은 __get_vm_area_node(), 새 페이지 할당과 매핑은 __vmalloc_area_node()가 맡는다.
again:
area = __get_vm_area_node(size, align, shift, VM_ALLOC |
VM_UNINITIALIZED | vm_flags, start, end, node,
gfp_mask, caller); /* 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()
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() 쪽으로 넘긴다. 소유권 계약 · 성공 뒤 기록
함수별 분석
- 일반 할당 진입:
vmalloc_noprof()이__vmalloc_node_noprof()를 거쳐__vmalloc_node_range_noprof()에 도달한다. mm/vmalloc.c:3965–3970 · mm/vmalloc.c:3999–4003 - 가상 범위:
__get_vm_area_node()가vm_struct를 만들고alloc_vmap_area()로 주소 범위를 예약한다. 기본적으로 가드 페이지 하나를 더 잡는다. mm/vmalloc.c:3172–3205 - 물리 페이지와 페이지 테이블:
__vmalloc_area_node()가 페이지 포인터 배열을 준비하고vm_area_alloc_pages()로 새 페이지를 받은 뒤vmap_pages_range()로 매핑한다. 페이지 부족 또는 매핑 실패는vfree()경로로 정리한다. mm/vmalloc.c:3686–3719 · mm/vmalloc.c:3732–3796 - 별도 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_struct | addr, 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 선택 경로에서는 달라진다. 가드·범위 예약
vmalloc()과 vmap()의 페이지 출처, VA→PFN 관계, 해제 소유권근거: vmalloc VA 예약 · 새 페이지 할당·매핑 · vmap의 입력 페이지 매핑 · 해제 경로. 도식은 개념도이며 실제 주소나 호출 순서 기록이 아니다.
| 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 설명
불변 조건과 실패 사례
실제 매핑 크기와 예약 크기를 구분한다. 기본 경로는 끝에 가드 페이지를 더 예약하며, 페이지 매핑에는 get_vm_area_size()로 얻은 사용 범위만 넘긴다. 예약 · 매핑 크기
연속된 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 목록