Slub Allocator 동작 방식
ref
OverView
- Page Allocator(Buddy): 페이지 할당을 요청하면 실제 물리 주소의 페이지를 할당해준다.
- Slab Allocator: 할당받은 페이지를 slab 이라는 객체 단위로 쪼개서 사용한다. 커널은 같은 모양의 객체를 반복해서 사용하기 때문에 효율적인 메모리 관리를 위해 사용한다.
- Slub Allocator: 슬랩할당자는 복잡한 큐 기반 캐싱 방법을 사용하는데, 최신 리눅스는 단순화하고 성능을 개선한 슬럽할당자를 사용한다.
커널영역에서 사용하는 대부분의 메모리 할당은 Slub Allocator라는 추가적인 계층 위에서 할당받은 Page 프레임을 더 쪼개서 사용한다.
Slab Allocator
커널이 할당하는 객체는 유형과 크기에 따라 캐시에 아래처럼 저장된다.
고정된 수, 고정된 크기의 객체 모임을 하나의 슬랩이라고 부른다.
각 캐시는 슬랩들을 가리키고 있는데, 전부 꽉찬 슬랩, 일부 객체만 할당된 슬랩, 완전히 빈 슬랩으로 나뉘어 연결리스트로 관리된다.
커널이 객체를 할당하려할 때 부분슬랩이나 빈 슬랩이 없는 경우에만 Page Allocator를 통해 PAGE SIZE 단위로 물리적으로 연속된 페이지를 슬랩으로 할당받게 된다.
Slub Allocator
Slab, Slub, Slob은 커널메모리를 다른 구조로 관리하게되며, 작은 임베디드는 Slob, 최신 리눅스는 Slub Allocator 를 사용하기 때문에 이 이후에는 Slub이라고 생각하면 된다.
커널은 이 슬랩할당자들을 컴파일 시점에 선택할 수 있게 제공하고 있다.
kmem_cache의 구조체부터 모양이 달라지기 때문에 mm/slab.h 에는 공통으로 사용하는 코드를 확인할 수 있고 include/linux/slab_def.h, include/linux/slub_def.h 등의 파일에서 할당자에 맞는 구조체를 볼 수 있다.
#ifdef CONFIG_SLAB
#include <linux/slab_def.h>
#endif
#ifdef CONFIG_SLUB
#include <linux/slub_def.h>
#endif
비교를 위한 Slab Allocator
위에서 Slab을 설명할 때 있던 그림이 Slab Allocator 이다.
slab.c (array_cache), slab_def.h (kmem_cache), slab.h (kmem_cache_node), struct slab
해제된 object는 CPU코어의 L1캐시에 남아있을 수 있기 때문에 재사용하면 효율이 좋다.
kmem_cache별로 크기가 일정하기 때문에 크기는 신경쓸 필요 없이 entry에서 꺼내서 할당해주면 된다.
cpu_cache에 저장된 메모리주소를 this_cpu_ptr 같은 매크로로 접근하면 CPU코어번호 기반 주소로 변경되어 접근할 수 있다.
array_cache → slabs_partial → slabs_free → new page 순으로 할당된다.
[ kmem_cache (ex. kmalloc-64) ]
├─ cpu_cache ──────> [ array_cache (per cpu) ]
│ void *entry[] # 최근 해제된 free object ptr 스택 (CPU별로 관리)
│ uint limit # 캐시 최대 크기. 초과되면 batchcount 단위로 kmem_cache_node에 반납
│
└─ node[] ─────────> [ kmem_cache_node (per node) ]
├─ slabs_partial ────┐
├─ slabs_full ───────┼──> [ struct slab ] # from kernel 5.17
└─ slabs_free ───────┘ slab_cache
slab_list : kmem_cache_node 에 연결하기 위한 구조체
freelist : 첫 freeobj 주소
active : 사용중인 object 수
s_mem : 첫 object 시작주소
│
v *freelist
[obj:use][obj:free][obj:use][obj:free]
└──── next ptr ────┘└─ next ptr ─> NULL
Slub
슬럽은 검증필요 이제 검증해야함.
[ kmem_cache ]
├─ cpu_slab ───────> [ kmem_cache_cpu (per cpu) ] <-- Fast Path (Lock-free)
│ slab : 현재 할당 중인 Active Slab 포인터
│ freelist : 다음으로 할당할 free object 주소
│
└─ node[] ─────────> [ kmem_cache_node (per node) ] <-- Slow Path
└─ partial ──────────┐
(Full/Free 없음) │
v
[ struct slab (과거 struct page) ]
slab_cache : 소속 kmem_cache
freelist : 슬랩 내 첫 free object 주소
inuse : 사용중인 object 수
objects : 슬랩 내 총 object 수
│
v
[obj:use][obj:free][obj:use][obj:free]
└──── next ptr ────┘└─ next ptr ─> NULL
Comments