[커널 19차] 96 주차

2024.03.14 07:47

Min 조회 수:32

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;
}

 

 

 

번호 제목 글쓴이 날짜 조회 수
공지 [공지] 스터디 정리 노트 공간입니다. 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
XE Login