이 글은 독자가 슬랩에 대한 약간의 이해가 있다고 가정한다. 슬랩을 왜 쓰는지, cache와 slab이 어떤 관계가 있는지, kmalloc/kfree가 무엇인지 정도는 알고 있어야 한다. 아래 두 글을 먼저 읽는 것도 이해에 도움이 될것같다.
슬랩 서브시스템은 원래 SLAB만 존재하다가 SLOB, SLUB이 만들어지면서 아예 독립적인 세 개의 슬랩 할당자가 만들어졌다. 하지만 각 슬랩 할당자는 비슷한 부분이 많기 때문에 이 부분을 2012년에 공통되는 코드를 slab_common으로 분리했다. 따라서 SLAB/SLUB/SLOB 할당자를 분석하기 전에 slab_common에 무슨 코드가 있는지 분석하겠다. 다만 memcg, kasan, kfence, tracing 같은 경우에는 다른 서브시스템에서 슬랩 코드를 수정한 것이라 이해에 방해가 되므로 이 글에선 설명을 생략하겠다. 앞으로 kasan*, kfence*, *memcg*, *trace 등등 이상한 이름의 함수가 나오면 일단은 무시하자. 슬랩을 이해하는 데에는 전혀 도움이 되지 않는다.
이제 분석을 시작해보자. slab_common은 mm/slab_common.c에 구현되어있다.
State of slab subsystem
슬랩 서브시스템은 항상 사용이 가능한 것은 아니다. 부팅 초반에 슬랩을 위한 자료구조들을 초기화하기 전까지는 사용이 불가능하다. 이처럼 슬랩은 다양한 상태로 나뉘는데, 이는 mm/slab.h에 enum으로 정의되어있다.
/*
* State of the slab allocator.
*
* This is used to describe the states of the allocator during bootup.
* Allocators use this to gradually bootstrap themselves. Most allocators
* have the problem that the structures used for managing slab caches are
* allocated from slab caches themselves.
*/
enum slab_state {
DOWN, /* No slab functionality yet */
PARTIAL, /* SLUB: kmem_cache_node available */
PARTIAL_NODE, /* SLAB: kmalloc size for node struct available */
UP, /* Slab caches usable but not all extras yet */
FULL /* Everything is working */
};
extern enum slab_state slab_state;
아직 모든 slab_state를 이해할 필요는 없다. 대강 DOWN은 슬랩 서브시스템의 초기화 이전이고 UP이 초기화 이후라고만 알아두자.
Data structures
슬랩은 struct kmem_cache, struct kmem_cache_node, struct kmem_cache_cpu, struct page 등의 구조체에 접근하는데, 이 부분은 Christoph의 PPT에 잘 나와있으니 아래 글을 참고하자. 이 글에선 이런 구조체를 구체적으로 다루지 않을 것인데, 왜냐하면 구조체들이 SL[AUO]B에 의존적이기 때문이다.
Global variables
slab_common에서 선언하는 전역변수는 대략 아래와 같다.
enum slab_state slab_state;
LIST_HEAD(slab_caches);
DEFINE_MUTEX(slab_mutex);
struct kmem_cache *kmem_cache;
/*
* Merge control. If this is set then no merging of slab caches will occur.
*/
static bool slab_nomerge = !IS_ENABLED(CONFIG_SLAB_MERGE_DEFAULT);
slab_state: 현재 슬랩의 상태
slab_caches: 시스템 내에 존재하는 모든 캐시를 링크드 리스트로 연결한것
slab_mutex: 슬랩에서 사용하는 매우 광범위한 락(뮤텍스)
kmem_cache: 아래에서 설명할 kmem_cache를 할당하기 위한 캐시
slab_nomerge: 뒤에서 설명하겠지만 슬랩에는 비슷한 캐시를 병합해서 하나로 쓰는 기능이 있는데 그걸 사용할지 말지
Slab's interface
슬랩에는 캐시의 생성, 소멸, 오브젝트의 할당, 해제를 위한 인터페이스가 존재한다. kmalloc/kfree를 제외하고는 SL[AUO]B에 의존적인 함수들이기 때문에, 작동 원리는 각각의 할당자를 분석할 때 정리하겠다.
kmem_cache_create, kmem_cache_destroy
슬랩 캐시를 생성하고 소멸하는 함수이다. 캐시를 생성해야 아래의 인터페이스로 할당과 해제를 할 수 있다.
kmem_cache_{alloc,free}, kmem_cache_alloc{_node,node_trace,trace}
kmem_cache_alloc과 kmem_cache_free는 슬랩에서 할당과 해제를 할 때 사용하는 인터페이스이다. SL[AUO]B마다 내부 구조가 다르므로 각각 구현되어있다.
alloc 뒤에 _node, _trace가 붙은 함수도 있는데, 커널 config에 따라 NUMA나 tracing이 켜져있을 때 사용하기 위함이다.
_node 관련 함수는 메모리의 접근 성능이 균일하지 않은 시스템에 대하여 특정 노드에서 할당받기 위해 존재한다. NUMA가 무엇인지는 아래 글에서 설명한다. tracing은 할당/해제를 추적하기 위해 사용되는데 이것도 다른 서브시스템이라 생략하겠다.
kmalloc{,_array}, kfree{,_bulk}, kcalloc{,_node}, krealloc_array, ... etc
kmalloc과 kfree도 변종 함수들이 되게 많다. 위에 있는 함수가 어떻게 다 다른지 설명하는 건 시간낭비이므로 생략한다. 다만 핵심은 특정 크기(sizeof(struct bio), sizeof(struct inode), ... etc)에 대한 할당을 할 때는 kmem_cache_{alloc,free}를, 임의의 크기에 대해 할당/해제할 때는 kmalloc, kfree를 사용한다는 특성만 알아두자.
Initialization on boot process
위에서 말했듯 슬랩이 사용 가능해지려면 부팅시에 초기화를 해야하는데, 이는 크게 kmem_cache_init과 kmem_cache_init_late 함수를 통해 이루어진다. 둘 모두 커널의 시작점인 start_kernel에서 직간접적으로 호출한다.
이름에서 유추할 수 있듯이 kmem_cache_init에서 필수적인 초기화를 우선 하고, kmem_cache_init_late는 부팅 프로세스의 나중에 필요한 부분을 더 초기화한다. kmem_cache_init, kmem_cache_init_late는 SL[AUO]B마다 각각 구현되어있으므로 이 글에서는 공통적인 부분만 설명하겠다. kmem_cache_init_late는 주로 SLAB에서 중요하므로 이것도 생략한다.
kmem_cache_init
초기화 과정에서는 크게 두 가지 작업을 한다. 하나는 kmem_cache를 할당하기 위한 캐시를 생성하는 것이다. 나중에 슬랩에서 캐시를 만들 때 캐시 디스크립터인 kmem_cache 자체도 할당을 해야하는데, 캐시를 만드는데 캐시가 필요하면 닭과 달걀 문제가 생기므로 kmem_cache를 위한 캐시를 부팅시에 만들어주는 것이다. 그리고 두번째 작업은 kmalloc에서 사용할 kmalloc_caches를 할당하는 것이다.
void __init kmem_cache_init(void)
{
/* ... 생략 ... */
create_boot_cache(kmem_cache, "kmem_cache",
offsetof(struct kmem_cache, node) +
nr_node_ids * sizeof(struct kmem_cache_node *),
SLAB_HWCACHE_ALIGN, 0, 0);
/* ... 생략 ... */
/* Now we can use the kmem_cache to allocate kmalloc slabs */
setup_kmalloc_cache_index_table();
create_kmalloc_caches(0);
}
kmem_cache_init은 create_boot_cache로 kmem_cache를 위한 캐시를 만들고, setup_kmalloc_cache_index_table로 할당 사이즈별 캐시 인덱스를 정한 뒤, create_kmalloc_caches로 kmalloc_caches를 초기화한다.
create_boot_cache
부팅시에 만드는 캐시는 create_boot_cache로 만들어진다. 위에서 말한 두 종류의 캐시 모두 부팅시에 만들어지므로 create_boot_cache를 분석해보자. create_boot_cache함수가 하는 일은 별로 없다. 적절한 align값을 계산하고 name, size, useroffset, usersize, align 등 kmem_cache의 공통적인 필드를 초기화해주고 나머지는 SL[AUO]B별 함수인 __kmem_cache_create에서 처리한다.
#ifndef CONFIG_SLOB
/* Create a cache during boot when no slab services are available yet */
void __init create_boot_cache(struct kmem_cache *s, const char *name,
unsigned int size, slab_flags_t flags,
unsigned int useroffset, unsigned int usersize)
{
int err;
unsigned int align = ARCH_KMALLOC_MINALIGN;
s->name = name;
s->size = s->object_size = size;
/*
* For power of two sizes, guarantee natural alignment for kmalloc
* caches, regardless of SL*B debugging options.
*/
if (is_power_of_2(size))
align = max(align, size);
s->align = calculate_alignment(flags, align, size);
s->useroffset = useroffset;
s->usersize = usersize;
err = __kmem_cache_create(s, flags);
if (err)
panic("Creation of kmalloc slab %s size=%u failed. Reason %d\n",
name, size, err);
s->refcount = -1; /* Exempt from merging for now */
}
create_boot_cache 코드의 대부분은 직관적이지만, 직관적이지 않은 두 가지 부분이 있다. 바로 usercopy를 위한 필드와 부트 캐시에 대해서 슬랩 머징을 피하기 위해서 refcount를 음수로 초기화하는 것인데, 이 부분은 글의 마지막에 있는 usercopy와 슬랩 머징의 설명을 보면 이해가 될 것이다.
kmalloc_caches
보통 슬랩은 특정 크기(sizeof(struct bio), sizeof(struct inode), ...)의 오브젝트를 할당할때 사용한다. 이에 비해 kmalloc은 임의의 크기를 갖는 오브젝트를 할당하는 데에 사용된다. 임의의 크기가 정확히 몇인지는 알 수 없으므로, 8, 16, 32, 64, 128, ..., 2^n 등 다양한 크기의 캐시를 만들어둔 후에 적절한 캐시를 할당한다. 아, 참고로 SLOB은 kmalloc을 구현하지 않는다.
이때 kmalloc에서 생성할 가장 작은 캐시의 크기와 가장 큰 캐시의 크기는 SL[AOU]B, 아키텍처마다 다르다. 아래는 kmalloc의 할당 크기와 관련된 매크로이다.
/* Maximum allocatable size */
#define KMALLOC_MAX_SIZE (1UL << KMALLOC_SHIFT_MAX)
/* Maximum size for which we actually use a slab cache */
#define KMALLOC_MAX_CACHE_SIZE (1UL << KMALLOC_SHIFT_HIGH)
/* Maximum order allocatable via the slab allocator */
#define KMALLOC_MAX_ORDER (KMALLOC_SHIFT_MAX - PAGE_SHIFT)
/*
* Kmalloc subsystem.
*/
#ifndef KMALLOC_MIN_SIZE
#define KMALLOC_MIN_SIZE (1 << KMALLOC_SHIFT_LOW)
#endif
그리고 kmalloc은 다양한 용도로 사용될 수 있기 때문에 특정 크기에 대해서 용도별로 캐시를 따로 생성해주어야 한다. 이때 용도는 NORMAL (일반적인 경우), DMA (Direct Memory Access), CGROUP/MEMCG (cgroup 관련), RECLAIM (메모리 부족시 reclaimable한 kmalloc캐시) 등등 다양한 경우가 존재한다.
enum kmalloc_cache_type {
KMALLOC_NORMAL = 0,
#ifndef CONFIG_ZONE_DMA
KMALLOC_DMA = KMALLOC_NORMAL,
#endif
#ifndef CONFIG_MEMCG_KMEM
KMALLOC_CGROUP = KMALLOC_NORMAL,
#else
KMALLOC_CGROUP,
#endif
KMALLOC_RECLAIM,
#ifdef CONFIG_ZONE_DMA
KMALLOC_DMA,
#endif
NR_KMALLOC_TYPES
};
따라서 kmalloc_caches는 타입별로, 크기별로 서로 다른 캐시를 2차원 배열의 형태로 저장한다.
extern struct kmem_cache *
kmalloc_caches[NR_KMALLOC_TYPES][KMALLOC_SHIFT_HIGH + 1];
create_kmalloc_caches
이 함수도 직관적이다. KMALLOC_NORMAL부터 KMALLOC_RECLAIM까지 타입별로, KMALLOC_SHIFT_LOW부터 KMALLOC_SHIFT_HIGH까지 크기별로 new_kmalloc_cache를 통해 캐시를 생성한다. 그 후 kmalloc까지 준비가 끝났으므로 slab_state를 UP로 바꾼다.
/*
* Create the kmalloc array. Some of the regular kmalloc arrays
* may already have been created because they were needed to
* enable allocations for slab creation.
*/
void __init create_kmalloc_caches(slab_flags_t flags)
{
int i;
enum kmalloc_cache_type type;
/*
* Including KMALLOC_CGROUP if CONFIG_MEMCG_KMEM defined
*/
for (type = KMALLOC_NORMAL; type <= KMALLOC_RECLAIM; type++) {
for (i = KMALLOC_SHIFT_LOW; i <= KMALLOC_SHIFT_HIGH; i++) {
if (!kmalloc_caches[type][i])
new_kmalloc_cache(i, type, flags);
/*
* Caches that are not of the two-to-the-power-of size.
* These have to be created immediately after the
* earlier power of two caches
*/
if (KMALLOC_MIN_SIZE <= 32 && i == 6 &&
!kmalloc_caches[type][1])
new_kmalloc_cache(1, type, flags);
if (KMALLOC_MIN_SIZE <= 64 && i == 7 &&
!kmalloc_caches[type][2])
new_kmalloc_cache(2, type, flags);
}
}
/* Kmalloc array is now usable */
slab_state = UP;
#ifdef CONFIG_ZONE_DMA
for (i = 0; i <= KMALLOC_SHIFT_HIGH; i++) {
struct kmem_cache *s = kmalloc_caches[KMALLOC_NORMAL][i];
if (s) {
kmalloc_caches[KMALLOC_DMA][i] = create_kmalloc_cache(
kmalloc_info[i].name[KMALLOC_DMA],
kmalloc_info[i].size,
SLAB_CACHE_DMA | flags, 0,
kmalloc_info[i].size);
}
}
#endif
}
그런데 create_kmalloc_caches는 DMA에 대해서는 create_kmalloc_cache를, 그 외에 대해서는 create_kmalloc_cache를 호출한다. 왤까?
new_kmalloc_cache
new_kmalloc_cache에서도 캐시 자체는 create_kmalloc_cache에서 생성하는데, KMALLOC_{RECLAIM,CGROUP}에 대해서는 별도의 처리를 해주어야 하기 때문에 별도의 wrapper 함수를 만든 것이다.
static void __init
new_kmalloc_cache(int idx, enum kmalloc_cache_type type, slab_flags_t flags)
{
if (type == KMALLOC_RECLAIM) {
flags |= SLAB_RECLAIM_ACCOUNT;
} else if (IS_ENABLED(CONFIG_MEMCG_KMEM) && (type == KMALLOC_CGROUP)) {
if (cgroup_memory_nokmem) {
kmalloc_caches[type][idx] = kmalloc_caches[KMALLOC_NORMAL][idx];
return;
}
flags |= SLAB_ACCOUNT;
}
kmalloc_caches[type][idx] = create_kmalloc_cache(
kmalloc_info[idx].name[type],
kmalloc_info[idx].size, flags, 0,
kmalloc_info[idx].size);
/*
* If CONFIG_MEMCG_KMEM is enabled, disable cache merging for
* KMALLOC_NORMAL caches.
*/
if (IS_ENABLED(CONFIG_MEMCG_KMEM) && (type == KMALLOC_NORMAL))
kmalloc_caches[type][idx]->refcount = -1;
}
create_kmalloc_cache
struct kmem_cache *__init create_kmalloc_cache(const char *name,
unsigned int size, slab_flags_t flags,
unsigned int useroffset, unsigned int usersize)
{
struct kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT);
if (!s)
panic("Out of memory when creating slab %s\n", name);
create_boot_cache(s, name, size, flags, useroffset, usersize);
kasan_cache_create_kmalloc(s);
list_add(&s->list, &slab_caches);
s->refcount = 1;
return s;
}
create_kmalloc_cache도 로직이 매우 간단하다. kmem_cache를 할당받고, create_boot_cache로 캐시를 생성한 후에, slab_caches라는 슬랩 리스트에 삽입한 후에 refcount를 초기화한다.
kmalloc
이제 위에서 초기화한 kmalloc_caches로 kmalloc이 어떻게 동작하는지 살펴보자.
/**
* kmalloc - allocate memory
* @size: how many bytes of memory are required.
* @flags: the type of memory to allocate.
*
* kmalloc is the normal method of allocating memory
* for objects smaller than page size in the kernel.
*
* The allocated object address is aligned to at least ARCH_KMALLOC_MINALIGN
* bytes. For @size of power of two bytes, the alignment is also guaranteed
* to be at least to the size.
* ... 생략 ...
*/
static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
if (__builtin_constant_p(size)) {
#ifndef CONFIG_SLOB
unsigned int index;
#endif
if (size > KMALLOC_MAX_CACHE_SIZE)
return kmalloc_large(size, flags);
#ifndef CONFIG_SLOB
index = kmalloc_index(size);
if (!index)
return ZERO_SIZE_PTR;
return kmem_cache_alloc_trace(
kmalloc_caches[kmalloc_type(flags)][index],
flags, size);
#endif
}
return __kmalloc(size, flags);
}
kmalloc은 두 단계로 이루어진다. (1) 사이즈에 맞는 kmem_cache의 인덱스를 찾는다 (2) 해당 kmem_cache에서 할당한다.
그런데 size가 상수인 경우에는 kmem_cache의 인덱스를 런타임에 계산할 필요가 없으므로 약간의 최적화가 가능하다. 따라서 kmalloc에서는 __builtin_constant_p라는 GCC의 내장 함수로 size가 상수인지 확인한다. 상수라면, kmalloc_index라는 인라인 함수로 인덱스를 구한다. 상수가 아니라면 __kmalloc을 호출해서 런타임에 인덱스를 계산한다.
그리고 할당 사이즈가 KMALLOC_MAX_CACHE_SIZE보다 크면 kmalloc_large로 할당하는데, 이 함수는 페이지 크기보다 오브젝트의 크기가 클때, 슬랩 관련 구조체를 관리하는 오버헤드를 줄이기 위해 버디할당자로부터 메모리를 할당받는다.
create_cache
create_cache는 초기화 과정 이후에 생성되는 캐시를 만들 때 호출된다. create_boot_cache와의 차이점은 create_cache에서는 kmem_cache를 할당하는 캐시를 사용할 수 있다는 점이다. 그리고 create_cache로 생성되는 캐시는 slab_caches 링크드 리스트에 추가된다.
static struct kmem_cache *create_cache(const char *name,
unsigned int object_size, unsigned int align,
slab_flags_t flags, unsigned int useroffset,
unsigned int usersize, void (*ctor)(void *),
struct kmem_cache *root_cache)
{
struct kmem_cache *s;
int err;
if (WARN_ON(useroffset + usersize > object_size))
useroffset = usersize = 0;
err = -ENOMEM;
s = kmem_cache_zalloc(kmem_cache, GFP_KERNEL);
if (!s)
goto out;
s->name = name;
s->size = s->object_size = object_size;
s->align = align;
s->ctor = ctor;
s->useroffset = useroffset;
s->usersize = usersize;
err = __kmem_cache_create(s, flags);
if (err)
goto out_free_cache;
s->refcount = 1;
list_add(&s->list, &slab_caches);
out:
if (err)
return ERR_PTR(err);
return s;
out_free_cache:
kmem_cache_free(kmem_cache, s);
goto out;
}
shutdown_cache
shutdown_cache는 slab_caches 리스트 상에서 캐시를 제거하고, debugfs와 sysfs의 등록을 해제한다. (등록은 __kmem_cache_create에서 한다.) SL[AUO]B에 의존적인 코드는 __kmem_cache_shutdown에서 처리한다.
static int shutdown_cache(struct kmem_cache *s)
{
/* free asan quarantined objects */
kasan_cache_shutdown(s);
if (__kmem_cache_shutdown(s) != 0)
return -EBUSY;
list_del(&s->list);
if (s->flags & SLAB_TYPESAFE_BY_RCU) {
#ifdef SLAB_SUPPORTS_SYSFS
sysfs_slab_unlink(s);
#endif
list_add_tail(&s->list, &slab_caches_to_rcu_destroy);
schedule_work(&slab_caches_to_rcu_destroy_work);
} else {
kfence_shutdown_cache(s);
debugfs_slab_release(s);
#ifdef SLAB_SUPPORTS_SYSFS
sysfs_slab_unlink(s);
sysfs_slab_release(s);
#else
slab_kmem_cache_release(s);
#endif
}
return 0;
}
What is slab merging?
슬랩 머징은 크기가 비슷한 캐시를 합치는 것이다. 사실 합친다기 보단, kmem_cache_create로 캐시를 만들 때 이미 비슷한 캐시가 존재하면 새로운 캐시를 만들지 않고 기존에 존재하는 캐시를 리턴한다. 부트 파라미터 slab_nomerge로 런타임에 활성화/비활성화할 수 있다.
슬랩 머징을 사용하면 다양한 서브시스템에 걸쳐서 캐시를 공유하기 때문에 하드웨어 캐시를 더 효율적으로 사용할 수 있다. 그리고 메타데이터를 덜 사용하기 때문에 메모리도 효율적으로 쓸 수 있다.
근데 모든 캐시를 합칠 수 있는 건 아니라, 합칠 수 있는 캐시를 찾아서 합쳐야한다.
slab_unmergable
/*
* Set of flags that will prevent slab merging
*/
#define SLAB_NEVER_MERGE (SLAB_RED_ZONE | SLAB_POISON | SLAB_STORE_USER | \
SLAB_TRACE | SLAB_TYPESAFE_BY_RCU | SLAB_NOLEAKTRACE | \
SLAB_FAILSLAB | kasan_never_merge())
SLAB_NEVER_MERGE는 슬랩 캐시의 플래그가 머지가 가능한 종류인지 아닌지를 나타낸다.
/*
* Find a mergeable slab cache
*/
int slab_unmergeable(struct kmem_cache *s)
{
if (slab_nomerge || (s->flags & SLAB_NEVER_MERGE))
return 1;
if (s->ctor)
return 1;
if (s->usersize)
return 1;
/*
* We may have set a slab to be unmergeable during bootstrap.
*/
if (s->refcount < 0)
return 1;
return 0;
}
조건을 보면 아래의 조건 중 하나가 충족될 때 머지를 하지 않는다.
(1) no_merge로 시스템 전체에서 비활성화한경우
(2) 캐시 생성 플래그의 특성상 머지가 불가능한 경우
(3) 생성자가 존재하는 경우
(4) usersize가 설정된 경우
(5) 부트 캐시처럼 의도적으로 refcount를 조정해서 합치지 못하게 한 경우
find_mergable
이 함수는 slab_caches를 순회하면서 머지가 가능한 캐시가 있는지 찾는다.
#define SLAB_MERGE_SAME (SLAB_RECLAIM_ACCOUNT | SLAB_CACHE_DMA | \
SLAB_CACHE_DMA32 | SLAB_ACCOUNT)
SLAB_MERGE_SAME은 두 슬랩 캐시의 성질이 같은지 확인할 때 사용된다. 예를 들어 DMA 캐시는 DMA 캐시끼리, RECLAIMABLE한 캐시는 RECLAIMABLE한 캐시끼리 머지해야 하므로, 두 캐시에 대해서 s->flags & SLAB_MERGE_SAME이 같아야 한다.
struct kmem_cache *find_mergeable(unsigned int size, unsigned int align,
slab_flags_t flags, const char *name, void (*ctor)(void *))
{
struct kmem_cache *s;
if (slab_nomerge)
return NULL;
if (ctor)
return NULL;
size = ALIGN(size, sizeof(void *));
align = calculate_alignment(flags, align, size);
size = ALIGN(size, align);
flags = kmem_cache_flags(size, flags, name);
if (flags & SLAB_NEVER_MERGE)
return NULL;
list_for_each_entry_reverse(s, &slab_caches, list) {
if (slab_unmergeable(s))
continue;
if (size > s->size)
continue;
if ((flags & SLAB_MERGE_SAME) != (s->flags & SLAB_MERGE_SAME))
continue;
/*
* Check if alignment is compatible.
* Courtesy of Adrian Drzewiecki
*/
if ((s->size & ~(align - 1)) != s->size)
continue;
if (s->size - size >= sizeof(void *))
continue;
if (IS_ENABLED(CONFIG_SLAB) && align &&
(align > s->align || s->align % align))
continue;
return s;
}
return NULL;
}
find_mergeable도 크게 다르지 않은데, 다음의 조건이 모두 맞는 캐시를 찾아서 리턴한다.
(1) 생성하려는 오브젝트의 크기(정렬 포함)가 기존 캐시의 오브젝트의 크기보다 작거나 같다.
(2) slab_unmergeable가 0을 리턴해야 한다.
(3) 두 캐시의 align이 호환되어야 한다.
(4) 두 캐시의 오브젝트 크기 차이가 sizeof(void *) 작다.
(5) 두 캐시가 SLAB_MERGE_SAME을 &한 결과가 같다
(6) SLAB인 경우, align이 존재해야하며 기존에 존재하는 캐시의 align이 생성하려는 캐시의 align에 대한 배수여야한다.
What is Usercopy in slab?
슬랩에서 usercopy는 2017-8년 즈음에 보안 기능이 강화되면서 추가된 것이다. 아래 LWN.net 글에서 잘 설명해준다. 요약하자면 시스템 호출을 통해서 슬랩으로 할당한 객체의 일부 필드를 커널 공간에서 사용자 공간으로 copy_to_user 함수를 통해 복사할 때가 있다. 보통 객체 전체를 복사해서 주는 게 아니라 일부 필드만을 복사한다.
그런데 취약점으로 인해 원래 복사하려는 것보다 더 많은 양을 복사하면 사용자 공간으로 보안에 민감한 데이터를 유출할 가능성이 있다. 따라서 usercopy가 가능한 영역을 whitelist로 지정해서 지정한 영역만 복사가 가능하게 만드는 것이다. 이를 위해서 kmem_cache에 useroffset과 usersize로 오브젝트 내에서 사용자 공간으로 복사가 가능한 영역을 명시한다.
What is RCU in slab?
슬랩 코드에 RCU를 사용하는 부분(SLAB_TYPESAFE_BY_RCU, slab_caches_rcu_destroy{,work,work_fn})이 조금 있는데, 아쉽게도 내가 아직 RCU(Read Copy Update)가 뭔지 모른다. RCU를 모르는데 슬랩에서 RCU가 어떻게 쓰이는지 알 수는 없으므로 RCU를 먼저 정리한 후에 글을 업데이트해야겠다.
The End
이번 글에선 slab_common에 무엇이 있는지 알아보았다. 모든 함수를 다루지는 않았지만 중요한 건 다 다뤘다. 다음에는 SLUB, SLAB, SLOB 순서대로 분석을 해보려고 한다. (RCU도...)
'Kernel > Slab Allocators' 카테고리의 다른 글
[Linux Kernel] SLUB 오브젝트 할당/해제 분석 (22) | 2021.10.24 |
---|---|
[Linux Kernel] SL[AUO]B: Kernel memory allocator design and philosophy (0) | 2021.10.05 |
The Slab Allocator: An Object-Caching Kernel Memory Allocator (0) | 2021.10.04 |
댓글