[커널 19차] 96 주차
2024.03.14 07:47
cache creation & destroy
/**
* kmem_cache_create_usercopy - Create a cache with a region suitable
* for copying to userspace
* @name: A string which is used in /proc/slabinfo to identify this cache.
* @size: The size of objects to be created in this cache.
* @align: The required alignment for the objects.
* @flags: SLAB flags
* @useroffset: Usercopy region offset
* @usersize: Usercopy region size
* @ctor: A constructor for the objects.
*
* Cannot be called within a interrupt, but can be interrupted.
* The @ctor is run when new pages are allocated by the cache.
*
* The flags are
*
* %SLAB_POISON - Poison the slab with a known test pattern (a5a5a5a5)
* to catch references to uninitialised memory.
*
* %SLAB_RED_ZONE - Insert `Red` zones around the allocated memory to check
* for buffer overruns.
*
* %SLAB_HWCACHE_ALIGN - Align the objects in this cache to a hardware
* cacheline. This can be beneficial if you're counting cycles as closely
* as davem.
*
* Return: a pointer to the cache on success, NULL on failure.
*/
kmem_cache_create_usercopy()는 슬랩 캐시를 생성하는 API다. name, size, align, flags, useroffset, usersize, ctor을 인자로 받는다.
useroffset, usersize는 슬랩 객체 내에서 userspace로 카피가 가능한 영역을 제한하는 데에 사용된다. (LWN.net 글 참고: https://lwn.net/Articles/727322/)
ctor은 슬랩 페이지가 할당될때 실행될 생성자 함수이다. 이 함수는 슬랩 페이지가 처음 할당될 때 한 번만 호출되고, 객체가 할당되고 해제될 때는 호출되지 않는다.
struct kmem_cache *
kmem_cache_create_usercopy(const char *name,
unsigned int size, unsigned int align,
slab_flags_t flags,
unsigned int useroffset, unsigned int usersize,
void (*ctor)(void *))
{
struct kmem_cache *s = NULL;
const char *cache_name;
int err;
#ifdef CONFIG_SLUB_DEBUG
/*
* If no slub_debug was enabled globally, the static key is not yet
* enabled by setup_slub_debug(). Enable it if the cache is being
* created with any of the debugging flags passed explicitly.
* It's also possible that this is the first cache created with
* SLAB_STORE_USER and we should init stack_depot for it.
*/
if (flags & SLAB_DEBUG_FLAGS)
static_branch_enable(&slub_debug_enabled);
if (flags & SLAB_STORE_USER)
stack_depot_init();
#endif
SLAB_DEBUG_FLAGS 중 하나의 플래그라도 사용하면 slub_debug_enabled라는 static key를 활성화 상태로 바꾼다.
SLAB_STORE_USER 플래그를 사용하는 경우 stack backtrace의 저장을 위해 stack depot (stack backtrace de-duplication)을 초기화한다.
mutex_lock(&slab_mutex);
err = kmem_cache_sanity_check(name, size);
if (err) {
goto out_unlock;
}
slab_mutex는 캐시의 리스트, 캐시 디스크립터의 속성 등을 보호하는 데에 사용된다.
kmem_cache_sanity_check()은 name과 size 파라미터에 문제가 있는지 확인한다. (Only if DEBUG_VM=y)
/* Refuse requests with allocator specific flags */
if (flags & ~SLAB_FLAGS_PERMITTED) {
err = -EINVAL;
goto out_unlock;
}
kmem_cache_create()에 허용되지 않는 플래그가 켜진 경우 캐시 생성을 실패한다.
/*
* Some allocators will constraint the set of valid flags to a subset
* of all flags. We expect them to define CACHE_CREATE_MASK in this
* case, and we'll just provide them with a sanitized version of the
* passed flags.
*/
flags &= CACHE_CREATE_MASK;
현재 config에서 지원하는 플래그를 제외한 플래그들을 모두 무시한다. (i.e. SLUB_DEBUG가 꺼져있는데 디버깅 관련 플래그가 설정된 경우)
/* Fail closed on bad usersize of useroffset values. */
if (!IS_ENABLED(CONFIG_HARDENED_USERCOPY) ||
WARN_ON(!usersize && useroffset) ||
WARN_ON(size < usersize || size - usersize < useroffset))
usersize = useroffset = 0;
usersize, useroffset 값을 검증하고, 만약 정상적인 값이 아니라면 무시한다.
if (!usersize)
s = __kmem_cache_alias(name, size, align, flags, ctor);
if (s)
goto out_unlock;
__kmem_cache_alias()는 slab merging 기능을 사용하는 경우, 새로운 슬랩 캐시를 만드는 대신 이미 생성한 캐시를 여러 유저가 공유하여 사용하도록 한다.
cache_name = kstrdup_const(name, GFP_KERNEL);
if (!cache_name) {
err = -ENOMEM;
goto out_unlock;
}
새로운 버퍼를 할당받은 다음 캐시의 이름을 복사한다 (name이 문자열 상수가 아니라면)
s = create_cache(cache_name, size,
calculate_alignment(flags, align, size),
flags, useroffset, usersize, ctor, NULL);
calculate_alignment() 함수로 alignment 값을 계산한 다음 create_cache()로 캐시를 생성한다.
if (IS_ERR(s)) {
err = PTR_ERR(s);
kfree_const(cache_name);
}
out_unlock:
mutex_unlock(&slab_mutex);
if (err) {
if (flags & SLAB_PANIC)
panic("%s: Failed to create slab '%s'. Error %d\\n",
__func__, name, err);
else {
pr_warn("%s(%s) failed with error %d\\n",
__func__, name, err);
dump_stack();
}
return NULL;
}
return s;
}
EXPORT_SYMBOL(kmem_cache_create_usercopy);
캐시 생성에서 오류가 났는지 확인하고, 오류가 발생했다면 SLAB_PANIC 플래그의 여부에 따라서 warning을 띄우거나 커널을 panic 시킨다.
__kmem_cache_alias()
struct kmem_cache *
__kmem_cache_alias(const char *name, unsigned int size, unsigned int align,
slab_flags_t flags, void (*ctor)(void *))
{
struct kmem_cache *s;
s = find_mergeable(size, align, flags, name, ctor);
find_mergeable()은 이미 생성된 캐시 중, 새로 만들 캐시와 병합할 수 있는 캐시를 찾아서 반환한다.
if (s) {
if (sysfs_slab_alias(s, name))
return NULL;
s->refcount++;
/*
* Adjust the object sizes so that we clear
* the complete object on kzalloc.
*/
s->object_size = max(s->object_size, size);
s->inuse = max(s->inuse, ALIGN(size, sizeof(void *)));
}
return s;
}
병합할 수 있는 캐시를 찾은 경우, sysfs에 노드를 추가하고 (/sys/kernel/slab) 레퍼런스 카운트를 증가시킨다.
캐시를 병합할 때는 object_size와 inuse를 더 큰쪽에 맞춰준다.
예를 들어 만약 두 캐시의 object_size가 5, 6바이트고 align이 둘다 8이라면 size는 둘다 8일 것이다. object_size는 __GFP_ZERO로 할당된 객체를 0으로 zeroing하는 크기이므로, 캐시를 병합할 때는 object_size를 더 큰쪽으로 맞춰준다.
비슷하게 객체의 오른쪽 redzone이 끝나는 부분 (s→inuse)가 서로 다를 수 있기 때문에, 병합할 때는 더 큰쪽에 맞춰준다.
find_mergeable()
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;
slab_nomerge 부트 파라미터를 사용한 경우 머지를 하지 않는다.
if (ctor)
return NULL;
생성자가 있는 캐시는 객체가 할당되거나 해제된 직후의 상태에 대한 가정을 하는 경우가 있으므로 병합하지 않는다. 예를 들어서 생성자에서 spin_lock_init()처럼 lock을 초기화하는 경우, 다른 캐시와 병합한다면, “객체를 할당했을때 스핀락이 이미 초기화되었을 것이라는 가정”이 깨지게 된다. 따라서 생성자를 사용하는 경우 병합하지 않는다.
size = ALIGN(size, sizeof(void *));
align = calculate_alignment(flags, align, size);
size = ALIGN(size, align);
calculate_alignment()에서 align을 계산하고, 계산된 alignment 값을 size에 align한다.
flags = kmem_cache_flags(size, flags, name);
예를 들어 부팅 파라미터에 “slub_debug=U,kmalloc-*” 를 추가하면 SLAB_STORE_USER 플래그가 kmem_cache_flags()에 의해서 추가된다.
if (flags & SLAB_NEVER_MERGE)
return NULL;
슬랩 캐시 플래그가 머지를 하면 안되는 플래그인 경우 (i.e. SLAB_TYPESAFE_BY_RCU) 머지하지 않는다.
list_for_each_entry_reverse(s, &slab_caches, list) {
if (slab_unmergeable(s))
continue;
리스트를 거꾸로 순회하면서 머지할 수 있는 캐시를 찾는다. 캐시가 머지를 하면 안되는 캐시인 경우 스킵한다.
if (size > s->size)
continue;
머지할 수 있는 캐시라도 생성하려는 캐시의 size가 작거나 같은 경우에만 머지한다.
if ((flags & SLAB_MERGE_SAME) != (s->flags & SLAB_MERGE_SAME))
continue;
어떤 슬랩 캐시 플래그는, 두 캐시가 플래그가 모두 켜져있거나 꺼져있는 경우만 머지할 수 있다. 예를 들어서 SLAB_CACHE_DMA 플래그가 켜진 캐시와 꺼진 캐시는 머지할 수 없다.
/*
* Check if alignment is compatible.
* Courtesy of Adrian Drzewiecki
*/
if ((s->size & ~(align - 1)) != s->size)
continue;
두 캐시의 alignment가 호환되지 않는다면 머지하지 않는다.
if (s->size - size >= sizeof(void *))
continue;
size의 차가 포인터 크기 이상인 경우에도 머지하지 않는다.
if (IS_ENABLED(CONFIG_SLAB) && align &&
(align > s->align || s->align % align))
continue;
SLAB의 경우 cache coloring에 의해서 페이지의 시작에서 객체가 시작하지 않을 수 있는데, 이로 인한 크래시를 해결하기 위한 부분이다. 자세한 것은 commit 참조
return s;
}
return NULL;
}
댓글 0
번호 | 제목 | 글쓴이 | 날짜 | 조회 수 |
---|---|---|---|---|
공지 | [공지] 스터디 정리 노트 공간입니다. | woos | 2016.05.14 | 626 |
247 | [커널 20차] 48주차 | 무한질주 | 2024.04.25 | 19 |
246 | [커널 19차] 102 주차 | Min | 2024.04.20 | 35 |
245 | [커널 19차] 101 주차 | Min | 2024.04.13 | 63 |
244 | [커널 19차] 100 주차 | Min | 2024.04.13 | 16 |
243 | [커널 19차] 99 주차 | Min | 2024.03.30 | 82 |
242 | [커널 19차] 98 주차 | Min | 2024.03.23 | 55 |
241 | [커널 19차] 97 주차 | Min | 2024.03.16 | 50 |
» | [커널 19차] 96 주차 | Min | 2024.03.14 | 32 |
239 | [커널 19차] 95 주차 [2] | Min | 2024.03.03 | 111 |
238 | [커널 20차] 32주차 | brw | 2023.12.16 | 386 |
237 | [커널 20차] 29주차 | brw | 2023.11.27 | 161 |
236 | [커널 20차] 27주차 | brw | 2023.11.21 | 86 |
235 | [커널 20차] 26주차 | brw | 2023.11.21 | 48 |
234 | [커널 20차] 28주차 | 이민찬 | 2023.11.19 | 64 |
233 | [커널 20차] 25주차 | 이민찬 | 2023.10.30 | 120 |
232 | [커널 20차] 24주차 | 이민찬 | 2023.10.22 | 733 |
231 | [커널 20차] 23주차 | 이민찬 | 2023.10.14 | 81 |
230 | [커널 20차] 22주차 | 이민찬 | 2023.10.08 | 76 |
229 | [커널 20차] 21주차 | 이민찬 | 2023.09.23 | 116 |
228 | [커널 20차] 20주차 [2] | 이민찬 | 2023.09.17 | 182 |
.