일반적으로 메모리를 할당 받을 때는 buddy 시스템을 통해 페이지를 할당 받는다. 다양한 anti-fragmentation 기법이 적용된 buddy 시스템이라도 부팅 후에는 지속으로 단편화가 발생하고, 따라서 연속된 큰 메모리를 할당하기 어려워지게 된다. 이러한 문제를 Contiguous Memory Allocator(CMA)가 해결하여 시스템 부팅 후에도 연속된 큰 메모리를 할당할 수 있도록 한다.
좀 더 구체적인 동기는 contiguous.c 파일 상단부에 기술되어 있다. 간단히 요약하면, 임베디드 시스템에서, camera나 video coder들은 동작을 위해 큰 크기의 버퍼가 필요할 때가 있다. 예를 들어, full HD 프레임이라면 6MB 이상의 버퍼가 필요한데 이 버퍼를 할당하기 위해 slab, buddy 시스템을 이용하는 것은 비효율적이다.
아래의 디바이스 트리처럼, 메모리 공간을 분리하여 디바이스만 사용하도록 reserve 할 수도 있다. 하지만 디바이스가 버퍼가 필요하지 않은 상태에서도 시스템에서는 이 공간을 활용하지 못하기 때문에 비효율적일 수 있다.
따라서, CMA는 연속된 메모리를 할당하게 하면서 동시에 해당 페이지들을 시스템에서 활용할 수 있도록 하는 2가지의 기능을 수행한다.
이것을 구현하기 위해 사용되는 아이디어는 다음과 같다.
CMA는 영역은 movable page로 구성되어 있다.
CMA에서 할당이 필요하다면 할당된 페이지들을 migration시키고, 연속된 큰 페이지를 만든다.
movable page이란 page의 mobility의 속성 중 하나를 뜻하며, 이에 대한 내용은 "mobility에 따라 Grouping을 통한 외부 단편화 해소" 포스트에서 다룬다.
위 그림 왼편에서는 아직 buddy system이 활성화되지 않고 early 할당자 memblock이 사용되는 상태이다. 이 때 CMA로 잡을 영역을 reserve한다. 그런 다음 buddy system이 활성화 되면, memblock에서 사용 가능한 메모리는 movable 타입의 freelist에 등록되고 reserved된 cma 영역은 CMA 타입 freelist에 등록된다.
CMA 영역은 buddy system에 등록되어 있으므로, 시스템에서 사용할 수 있는 페이지가 된다. 만약 movable 페이지에 대한 요청이 실패한다면, 그 fallback으로 CMA 영역이 할당될 수 있다. 실제로 fallback 되는 로직은 아래와 같이 구성되어 있다.
movable 페이지 요청에 대해서, 8번 라인에서 할당이 실패한다면, 11번 라인에서 cma 영역에서 할당을 시도한다.
그렇다면, CMA 영역 내의 페이지가 buddy system에 의해 할당되었을 있을 경우, CMA 할당 요청은 어떻게 처리 되는 것일까? 우선 CMA의 주요 자료 구조에 대해 알아본 다음 할당 요청에 대해 논의하도록 한다.
cma 영역은 cma 구조체로 관리되며, 할당 여부를 기록하기 위해 bitmap을 이용한다. 아래와 같이 시작 페이지 프레임 번호와 page의 갯수도 저장한다.
cma 영역을 할당/해제하기 위해 사용되는 api로 cma_alloc과 cma_release가 있다. cma_alloc은 bitmap 이용하여 할당 가능한 영역이 있는지 확인한다. 만약 존재한다면 bitmap을 갱신하고 alloc_contig_range 함수를 호출한다. cma_release는 free_contig_range를 호출하고, bitmap을 업데이트한다.
핵심적인 함수는 mm/page_alloc.c에 구현되어 있는 alloc_contig_range와 free_contig_range 함수이다.
alloc_contig_range 함수에서는 buddy system으로 인해 할당된 페이지들을 compaction을 이용하여 아래 그림과 같이 다른 곳으로 migration한다. compation이 성공하면, 요청한 range에 할당되어 있는 페이지가 없기 때문에 연속된 메모리를 할당할 수 있게 된다.
free_contig_range의 경우 단순히 page를 순회하면서 버디 시스템에 반환한다.
여담
cma로 연속된 큰 메모리에 할당에 반드시 성공할 것처럼 보이지만, 반드시 할당 성공을 보장하지는 않는다. compaction은 내부적으로는 page migration을 이용하는데, migration의 기본적인 원리는 빠르게 swap-out, swap-in하는 방식이다. 따라서 movable 페이지라도 swap out 될 수 없는 상태(ex.mlock된 상태라면)라면 cma 할당 요청은 실패하게 된다.
최신 Linux에서는 도입 배경과 다른 케이스로 hugetlb에서도 cma를 사용하고 있다. hugetlb의 새로운 feature로 런타임에 huge page를 생성하는 기능이 있는데, 이것 역시 단편화로 할당하기가 쉽지 않다. 따라서 runtime에 생성할 huge page를 위한 cma 영역을 도입하는 패치("mm: hugetlb: optionally allocate gigantic hugepages using cma")가 도입되었다.
안녕하세요 게시해주신 글들 정말 재밌게 보고 있습니다!! 😁
앞으로도 좋은 글 많이 올려주세요~