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

v6.18.37 / mm/vmalloc.c

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

## vmap

```c

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;
}

```

### 3501행

```c

void *vmap(struct page **pages, unsigned int count,

```

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

### 3502행

```c

	   unsigned long flags, pgprot_t prot)

```

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

### 3504행

```c

	struct vm_struct *area;

```

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

### 3505행

```c

	unsigned long addr;

```

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

### 3506행

```c

	unsigned long size;		/* In bytes */

```

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

### 3508행

```c

	might_sleep();

```

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

### 3510행

```c

	if (WARN_ON_ONCE(flags & VM_FLUSH_RESET_PERMS))

```

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

### 3511행

```c

		return NULL;

```

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

### 3517행

```c

	if (WARN_ON_ONCE(flags & VM_NO_GUARD))

```

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

### 3518행

```c

		flags &= ~VM_NO_GUARD;

```

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

### 3520행

```c

	if (count > totalram_pages())

```

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

### 3521행

```c

		return NULL;

```

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

### 3523행

```c

	size = (unsigned long)count << PAGE_SHIFT;

```

페이지 개수에 페이지 크기를 곱해 바이트 수를 구합니다. PAGE_SHIFT만큼 왼쪽 이동하는 것이 그 곱셈입니다.

### 3524행

```c

	area = get_vm_area_caller(size, flags, __builtin_return_address(0));

```

요청 크기의 가상 영역을 확보하고 호출 위치도 기록합니다. 아직 각 물리 페이지와의 연결은 아래에서 수행합니다.

### 3525행

```c

	if (!area)

```

가상 주소 범위 확보가 실패했는지 확인합니다.

### 3526행

```c

		return NULL;

```

페이지 테이블을 만들기 전에 실패를 반환합니다.

### 3528행

```c

	addr = (unsigned long)area->addr;

```

확보한 영역의 시작 주소를 페이지 테이블 작성용 정수로 꺼냅니다.

### 3529행

```c

	if (vmap_pages_range(addr, addr + size, pgprot_nx(prot),

```

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

### 3530행

```c

				pages, PAGE_SHIFT) < 0) {

```

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

### 3531행

```c

		vunmap(area->addr);

```

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

### 3532행

```c

		return NULL;

```

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

### 3535행

```c

	if (flags & VM_MAP_PUT_PAGES) {

```

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

### 3536행

```c

		area->pages = pages;

```

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

### 3537행

```c

		area->nr_pages = count;

```

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

### 3539행

```c

	return area->addr;

```

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

