본문 바로가기

기술/Linux

리눅스 커널 심층 분석 개정 3판 : 12장. 메모리 관리 Linux Kernel Development Third Edition : Chapter 12. Memory management

리눅스 커널 심층 분석 개정 3판 : 12장. 메모리 관리 

Linux Kernel Development Third Edition : Chapter 12. Memory management

 

리눅스 커널 심층분석

이 책은 리눅스 커널의 핵심을 간결하면서도 심도있게 다루고 있다. 일반적인 운영체제에 대한 이해를 넘어, 여타 유닉스 시스템과 다른 리눅스만의 특징적인 부분에 대한 설계, 구현, 인터페이

book.naver.com

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 (구역)

  • 커널은 하드웨어적 한계로 인해 비슷한 특성을 가진 페이지를 한 구역에 모아 둔다.
  • 커널이 페이지를 관리하기 위해 사용하는 단순한 논리적 묶음으로 물리적 연관성은 없다.
  1. ZONE_DMA : DMA (direct memory access) 를 수행할 수 있는 페이지
  2. ZONE_DMA32 : 32비트 장치로만 접근이 가능하고 DMA를 수행할 수 있는 페이지
  3. ZONE_NORMAL : 정상적인, 통상적으로 할당되는 페이지
  4. 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()
    1. 동작 지정자 : 요청한 메모리를 커널이 어떻게 할당할지 지정한다.
    2. 여러 개의 지정자를 조합하여 사용할 수 있다.
    3. 구역 지정자 : 어떤 ZONE에 할당할지 지정한다.** alloc_pages() 함수만 virtual memory를 할당할 수 있다.
    4. ** __get_free_pages(), kmalloc()은 virtual address를 반환하기 때문에 __GFP_HIGHMEM을 지정할 수 없다.
    5. 형식 : 특정 메모리 할당 형식에 필요한 동작 지정자 + 구역 지정자** GFP_KERNEL : 디폴트 / 우선순위를 가지는 프로세스 중간에 휴면할 수 있는 할당 방식, 함수가 처리 중 중단될 수 있어서 안전하게 재스케줄링이 가능한 프로세스 context에서만 사용해야 한다.** GFP_NOIO : 요청을 처리하는 동안 디스크 입출력 작업을 할 수 없다.
    6. ** GFP_NOFS : 요청을 처리하는 동안 파일시스템 입출력 작업을 할 수 없다.
    7. 즉, 연속적인 메모리 공간이 충분하지 않은 상황에서 메모리 요청이 들어오면 프로세스를 중단시키고 비활성화된 페이지를 디스트 스왑공간으로 옮기고 페이지 변경 내용을 디스크에 저장하는 등의 작업을 수행할 수 있어 할당 작업 성공률이 높다.
    8. 커널의 대다수 할당 작업은 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의 기본 원칙
    1. 자주 사용하는 자료구조는 할당 및 해제가 빈번하므로 캐시한다.
    2. 빈번한 할당 및 해제는 메모리 단편화를 유발하기 때문에 free list는 연속된 순서대로 정리되어 있어야 한다.
    3. free list를 사용하면 free된 객체를 바로 다음 할당 작업에 사용할 수 있어서 빈번한 할당 및 해제 작업의 성능을 향상시킨다.
    4. 할당자가 객체의크기, 페이지의 크기, 전체 캐시 크기 등의 정보를 알고 있다면 보다 정교한 처리가 가능하다.
    5. 시스템의 각 프로세서별로 별도의 캐시를 가지고 있다면 SMP 락을 사용하지 않고도 할당 및 해제가 가능하다.
    6. 할당자가 NUMA를 지원하는 경우에는 메모리를 요청한 노드에 있는 메모리를 할당해줄 수 있다.
    7. 여러 객체가 같은 캐시에 섞여 들어가지 않도록 저장되는 개체에 표시할 수 있어야 한다.

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을 할 수 있어서 휴면 상태로 전환할 수 없는 곳에서도 사용할 수 있다.
  • 다음 임시 연결이 이전 연결 정보를 덮어쓰기 때문에 따로 비활성화시키는 작업이 필요없다.