리눅스 커널 심층 분석 개정 3판 : 12장. 메모리 관리
Linux Kernel Development Third Edition : Chapter 12. Memory management
Notion에서 보기
1. Page
- page: 가상 메모리 관점에서 메모리 관리의 기본/최소 단위
- 커널은 struct page 구조체로 모든 물리적 페이지를 표현한다
struct page{
unsigned long flags; /* 페이지 상태 저장 */
atomic_t_count; /* 페이지 참조/사용 횟수, -1이면 아무도 사용하고 있지 않음 */
atomic_t_mapcount;
unsigned long private;
struct address_space *mapping;
pgoff_t index;
struct list_head lru;
void *virtual; /* 페이지의 virtual address */
};
- 커널은 페이지 구초제를 통해 시스템에 있는 모든 페이지의 할당 여부와 할당되어 있다면 페이지의 소유자를 파악한다.
2. Zone (구역)
- 커널은 하드웨어적 한계로 인해 비슷한 특성을 가진 페이지를 한 구역에 모아 둔다.
- 커널이 페이지를 관리하기 위해 사용하는 단순한 논리적 묶음으로 물리적 연관성은 없다.
- ZONE_DMA : DMA (direct memory access) 를 수행할 수 있는 페이지
- ZONE_DMA32 : 32비트 장치로만 접근이 가능하고 DMA를 수행할 수 있는 페이지
- ZONE_NORMAL : 정상적인, 통상적으로 할당되는 페이지
- ZONE_HIGHMEM : 커널 주소 공간에 상주하지 않는 페이지 (가상 메모리)
struct zone {
...
unsigned long watermark[NR_WMARK]; /* 해당 구역의 메모리 사용량 수위 정보 */
spinlock_t lock; /* 구조체에 동시 접근을 막는 스핀락 / 페이지 보호X */
...
};
3. 페이지 가져오기
- 2order개수만큼 contiguous한 물리적 메모리를 할당하고 첫번째 페이지의 page 포인터 구조체를 반환한다
- 특정 페이지의 논리적 주소를 얻을 수 있다.
struct page * alloc_pages(gfp_t gfp_maks, unsigned int order)
- 물리적 페이지에 현재 담겨 있는 논리적 주소 포인터를 반환한다.
void * page_address(struct_page *page)
- 첫 번째 페이지의 논리적 주소를 반환한다.
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
4. zero-filled page 가져오기
- content가 0으로 초기화된 페이지가 필요한 경우 사용하는 함수는 다음과 같다. page를 0으로 채워 초기화한 다음 해당 페이지의 논리적 주소 포인터를 반환한다.
unsigned long get_zeroed_page(unsigend int gfp_mask)
→ 시스템 보안을 유지할 수 있다.
- 즉, order 값을 파라미터로 주지 않으면 페이지 하나를 반환하고 order 값이 있다면 2^order 만큼 페이지를 할당하고 반환한다.
5. 페이지 반환
- 할당받지 않은 페이지를 반환할 경우 발생하는 오류를 처리해야 한다.
void free_page (unsigned long addr)
kmalloc()
- 커널 메모리를 바이트 단위로 할당할 때 사용하는 인터페이스이다.
void * kmalloc(size_t size, gfp_t flags)
- 최소한 size 바이트 길이를 가진 연속된 물리적 메모리 영역의 포인터를 반환한다. 사용 가능한 메모리가 부족한 경우 NULL을 반환한다.
- 물리적으로, 가상적으로 연속된 메모리 공간 보장한다.
gfp_mask flag
- <linux/types.h> 파일에 unsigned int 형으로 정의되어 있는 gfp_t 형을 사용한다.
- gfp = __get_free_pages()
- 동작 지정자 : 요청한 메모리를 커널이 어떻게 할당할지 지정한다.
- 여러 개의 지정자를 조합하여 사용할 수 있다.
- 구역 지정자 : 어떤 ZONE에 할당할지 지정한다.** alloc_pages() 함수만 virtual memory를 할당할 수 있다.
- ** __get_free_pages(), kmalloc()은 virtual address를 반환하기 때문에 __GFP_HIGHMEM을 지정할 수 없다.
- 형식 : 특정 메모리 할당 형식에 필요한 동작 지정자 + 구역 지정자** GFP_KERNEL : 디폴트 / 우선순위를 가지는 프로세스 중간에 휴면할 수 있는 할당 방식, 함수가 처리 중 중단될 수 있어서 안전하게 재스케줄링이 가능한 프로세스 context에서만 사용해야 한다.** GFP_NOIO : 요청을 처리하는 동안 디스크 입출력 작업을 할 수 없다.
- ** GFP_NOFS : 요청을 처리하는 동안 파일시스템 입출력 작업을 할 수 없다.
- 즉, 연속적인 메모리 공간이 충분하지 않은 상황에서 메모리 요청이 들어오면 프로세스를 중단시키고 비활성화된 페이지를 디스트 스왑공간으로 옮기고 페이지 변경 내용을 디스크에 저장하는 등의 작업을 수행할 수 있어 할당 작업 성공률이 높다.
- 커널의 대다수 할당 작업은 GFP_KERNEL 플래그를 사용한다.
kfree()
- kmalloc을 통해 할당한 메모리를 해제한다.
void kfree(const void *ptr)
vmalloc()
- 물리적으로 연속될 필요 없이 가상적으로 연속된 메모리 영역을 할당한다.
- 사용자 공간 메모리 할당 함수가 작동하는 방식이다.
- virtual address space에서만 연속성을 보장한다.
- 소프트웨어에서만 사용하는 메모리를 가상적으로만 연속되어 있어도 된다.
void * vmalloc(unsigned long size)
void vfree(const void *addr)
6. slab layer
free list
- 이미 할당된 사용하지 않는 자료구조들이 들어 있다.
- 새로운 메모리를 할당하고 그 메모리에 자료구조를 준비하는 과정 없이 바로 사용할 수 있다.
- 사용이 빈번한 객체를 free list에 캐시하여 사용한다.
- compaction 등의 제어가 불가능하다.
slab layer (slab allocator)
- 제어가 안되는 free list의 한계를 보완하기 위해 제공하는 슬랩 할당자로 범용 자료구조 캐시 계층이다.
- slab allocator의 기본 원칙
- 자주 사용하는 자료구조는 할당 및 해제가 빈번하므로 캐시한다.
- 빈번한 할당 및 해제는 메모리 단편화를 유발하기 때문에 free list는 연속된 순서대로 정리되어 있어야 한다.
- free list를 사용하면 free된 객체를 바로 다음 할당 작업에 사용할 수 있어서 빈번한 할당 및 해제 작업의 성능을 향상시킨다.
- 할당자가 객체의크기, 페이지의 크기, 전체 캐시 크기 등의 정보를 알고 있다면 보다 정교한 처리가 가능하다.
- 시스템의 각 프로세서별로 별도의 캐시를 가지고 있다면 SMP 락을 사용하지 않고도 할당 및 해제가 가능하다.
- 할당자가 NUMA를 지원하는 경우에는 메모리를 요청한 노드에 있는 메모리를 할당해줄 수 있다.
- 여러 객체가 같은 캐시에 섞여 들어가지 않도록 저장되는 개체에 표시할 수 있어야 한다.
Design
- 각 객체를 유형별 캐시 (kernel memory cache)에 분류한다.객체 ex) process descriptor, inode 등...
- 객체 유형 : 캐시 = 1:1
- 슬랩은 하나 이상의 물리적으로 연속된 페이지로 구성된다. (보통 1개의 페이지)
- → 캐시는 여러 개의 슬랩을 가진다.
- 슬랩의 상태ii) 부분 사용 : 슬랩 안에 할당된 객체와 해제된 객체가 모두 들어 있다.
- iii) 미사용 : 할당된 객체가 없다. 모두 해제된 상태이다.
- i) 모두 사용 : 슬랩 안의 모든 객체가 할당되어 있다.
- 커널에서 객체를 요청하면 부분 사용 상태인 슬랩 → 미사용 상태인 슬랩을 이용하여 처리한다. 미사용 슬랩도 없는 경우 슬랩을 새로 만들어 처리한다.
- → 단편화를 완화할 수 있다.
slab allocator interface
- 생성한 캐시 포인터를 반환하고 실패시 NULL을 반환한다.
struct kmem_cache * kmem_cache_create(const char *name, /*캐시 이름*/
size_t size, /*캐시에 들어갈 항목의 크기*/
size_t align, /*첫번째 객체 오프셋*/
unsigned long flags,
/*페이지 내부 특별한 정렬 방식*/
void (*ctor) (void*));
/*캐시에 새로운 페이지 추가시 호출*/
- 지정한 캐시를 제거한다.이때 캐시의 모든 슬랩이 비어 있어야 하고, 캐시를 제거한 뒤 접근하면 안된다. 성공한 경우 0 을 반환하고 실패한 경우 0이 아닌 값을 반환한다.
int kmem_cache_destroy(struct kmem_Cache *cachep)
- cachep가 지정한 캐시에 들어 있는 객체의 포인터를 반환하여 캐시 내부 객체를 할당한다. 슬랩에 free 객체가 없는 경우 kmem_getpages()함수를 이용해서 새로운 페이지를 생성해야 한다.
void * kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)
- 사용한 객체를 해제하고 슬랩에 반환한다.
void kmem_cache_free(struct kmem_cache *cachep, void *objp)
7. 가상 메모리 연결 (mapping)
- 가상 메모리에 있는 페이지에는 고정된 커널 주소 공간 값이 없을 수도 있다. 따라서 alloc_pages() 함수를 호출하여 얻은 페이지에 virtual address가 없을 수도 있다.
고정 연결
- 해당 페이지가 물리적 메모리에 속해 있다면 그냥 페이지의 가상 주소를 반환하고 가상 메모리에 있으면 고정 연결을 만들고 그 주소를 반환한다.
void *kmap(struct page *page)
- 고정 연결의 개수가 제한되므로 더 이상 필요하지 않은 가상 메모리의 연결은 해제한다.
임시 연결
- atomic mapping
- 커널은 가상 메모리를 사전에 정의된 연결 정보에 원자적으로 저장할 수 있다.
- 작업이 중단되는 일 없이 mapping을 할 수 있어서 휴면 상태로 전환할 수 없는 곳에서도 사용할 수 있다.
- 다음 임시 연결이 이전 연결 정보를 덮어쓰기 때문에 따로 비활성화시키는 작업이 필요없다.