Linux v6.18.37 · 개념과 코드 읽기

vmalloc과 vmap: 떨어진 페이지를 연속 주소로 보기

이 코드는 어떤 문제를 푸나요?

연속된 포인터 범위가 반드시 연속된 물리 RAM을 뜻하지는 않습니다. vmap은 이미 확보한 페이지 배열을 받아 커널의 연속 가상 주소 범위에 연결합니다. vmalloc이 페이지 확보까지 포함하는 것과 구분하면 가상 주소 관리와 물리 페이지 공급의 역할이 보입니다.

읽을 범위: v6.18.37 · mm/vmalloc.c · vmap 3501–3540행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.

먼저 알아둘 개념

가상 연속성

addr, addr+PAGE_SIZE처럼 가상 주소가 이어진다는 뜻입니다. 각 가상 페이지가 가리키는 물리 페이지는 서로 멀리 떨어져 있을 수 있습니다.

페이지 배열

pages[i]는 i번째 가상 페이지에 연결할 물리 페이지의 메타데이터를 가리킵니다. 배열 자체를 연속 데이터 버퍼로 사용하는 것은 아닙니다.

guard page

매핑 끝 경계를 넘어간 접근을 발견하도록 비워 두는 보호 영역입니다. 인접한 다른 매핑의 경계 보호와도 관계가 있습니다.

처음 읽을 때

세 페이지를 물리 순서 9번·2번·15번으로 확보했다고 가정해 보세요. 가상 주소 A, A+P, A+2P에 배열 순서대로 연결하면 호출자는 하나의 연속 버퍼처럼 접근할 수 있습니다.

더 깊이 살펴볼 때

오류 경로에서 가상 영역 해제와 페이지 소유권을 구분해 보세요. VM_MAP_PUT_PAGES의 유무에 따라 vmap 이후 누가 페이지 참조를 정리하는지가 달라집니다.

그림으로 보는 변화

vmalloc과 vmap: 떨어진 페이지를 연속 주소로 보기의 단계별 개념 그림
각 단계에 화살표 의미와 생략 범위를 표시했습니다. 주소·숫자 예제는 실제 장치 값을 뜻하지 않습니다.
1단계 설명

1단계 고정

GIF 원본 열기

1. 페이지 배열 준비

예시 pages = [물리 9번, 2번, 15번]

숫자는 설명용 페이지 번호입니다. 화살표는 배열의 순서가 매핑 순서가 됨을 뜻합니다.

2. 연속 가상 범위 확보

가상 A, A+P, A+2P

P는 페이지 크기입니다. 이 단계는 물리 페이지를 연속 배치하는 작업이 아닙니다.

3. 페이지 테이블로 연결

A→9번, A+P→2번, A+2P→15번

이 화살표는 주소 변환 관계입니다. 실행 순서나 데이터 복사를 뜻하지 않습니다.

vmap를 한 줄씩 읽기

줄 번호는 v6.18.37 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.

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;
}
void *vmap(struct page **pages, unsigned int count,

페이지 포인터 배열과 페이지 개수를 받아 가상 주소를 만드는 함수입니다. 반환값은 만들어진 범위의 시작 주소 또는 실패 시 NULL입니다.

	   unsigned long flags, pgprot_t prot)

flags는 영역 관리 옵션이고 prot는 매핑 권한입니다. 어느 페이지를 연결할지와 어떤 권한으로 연결할지를 별도 인자로 받습니다.

	struct vm_struct *area;

확보한 가상 영역의 주소와 메타데이터를 가리킬 변수를 준비합니다.

	unsigned long addr;

페이지 테이블 작성에 전달할 가상 시작 주소를 정수로 보관합니다.

	unsigned long size;		/* In bytes */

페이지 개수를 바이트 크기로 바꾼 결과를 담습니다. count와 size의 단위를 구분하셔야 합니다.

	might_sleep();

이 경로가 잠들 수 있음을 검사합니다. 원자적 실행이 필요한 문맥에서 잘못 호출하면 디버깅에 도움이 됩니다.

	if (WARN_ON_ONCE(flags & VM_FLUSH_RESET_PERMS))

이 vmap 인터페이스에서 허용하지 않는 권한 재설정 플래그가 들어왔는지 확인하고 한 번 경고합니다.

		return NULL;

허용하지 않는 요청이므로 매핑을 시작하지 않고 실패를 반환합니다.

	if (WARN_ON_ONCE(flags & VM_NO_GUARD))

guard를 없애 달라는 플래그가 들어오면 한 번 경고합니다. 인접 매핑의 경계 보호까지 약해질 수 있기 때문입니다.

		flags &= ~VM_NO_GUARD;

VM_NO_GUARD 비트를 지워 보호 영역을 유지하는 요청으로 바꿉니다. 이 경우에는 전체 요청을 실패시키지 않습니다.

	if (count > totalram_pages())

요청 페이지 수가 전체 RAM 페이지 수보다 큰 비정상 요청인지 빠르게 확인합니다.

		return NULL;

명백히 과도한 요청이면 NULL을 반환합니다.

	size = (unsigned long)count << PAGE_SHIFT;

페이지 개수에 페이지 크기를 곱해 바이트 수를 구합니다. 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),

[addr, addr+size) 범위에 페이지를 연결하며 실행 불가 권한을 적용합니다. prot 요청을 그대로 코드 실행 가능 매핑으로 두지 않습니다.

				pages, PAGE_SHIFT) < 0) {

pages 배열과 기본 페이지 단위 PAGE_SHIFT를 전달합니다. 하위 함수의 음수 반환은 매핑 실패를 의미합니다.

		vunmap(area->addr);

부분적으로 만든 매핑과 가상 영역을 정리합니다. 자원 확보 뒤 실패한 경우의 되돌리기 단계입니다.

		return NULL;

매핑 실패를 호출자에게 알립니다.

	if (flags & VM_MAP_PUT_PAGES) {

페이지 참조와 배열의 정리를 vmap 영역에 맡기는 옵션인지 확인합니다.

		area->pages = pages;

후속 해제 때 사용할 페이지 배열을 영역에 보관합니다.

		area->nr_pages = count;

함께 보관한 배열의 페이지 개수를 기록합니다.

	return area->addr;

호출자가 연속 버퍼로 사용할 가상 시작 주소를 돌려줍니다.

함께 생각해 볼 질문

vmap은 RAM을 새로 할당하나요?

이 함수는 호출자가 제공한 pages를 매핑합니다. vmalloc 계열은 필요한 페이지를 확보하는 과정까지 포함합니다.

가상 주소가 연속이면 DMA에 그대로 넘길 수 있나요?

그 사실만으로는 안 됩니다. 장치의 DMA 주소 공간과 매핑 규칙은 별도로 따라야 합니다.

매핑 실패 시 바로 NULL만 반환하면 왜 안 되나요?

이미 확보한 가상 영역이 남을 수 있습니다. 실패 경로에서 vunmap으로 부분 매핑과 영역을 정리한 뒤 반환합니다.

출처와 읽은 범위

Linux stable v6.18.37 · mm/vmalloc.c

해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고

맨 위로 ↑