[커널 19차] 97 주차

2024.03.16 22:22

Min 조회 수:50

create_cache()

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;

useroffset과 usersize를 값이 유효한지 확인한다.

	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;
#ifdef CONFIG_HARDENED_USERCOPY
	s->useroffset = useroffset;
	s->usersize = usersize;
#endif

kmem_cache구조체를 할당하기 위해 kmem_cache라는 캐시에서 객체를 하나 할당해오고, 인자로 넘어온 값들로 초기화한다.

	err = __kmem_cache_create(s, flags);
	if (err)
		goto out_free_cache;

	s->refcount = 1;
	list_add(&s->list, &slab_caches);
	return s;

out_free_cache:
	kmem_cache_free(kmem_cache, s);
out:
	return ERR_PTR(err);
}

__kmem_cache_create()를 통해 캐시를 생성한다. 초기 refcount는 1이고, 나중에 merge에 의해서 refcount가 증가될 수 있다. 생성된 캐시는 slab_caches 리스트에 추가한다.

__kmem_cache_create

int __kmem_cache_create(struct kmem_cache *s, slab_flags_t flags)
{
	int err;

	err = kmem_cache_open(s, flags);
	if (err)
		return err;

kmem_cache_open()을 통해 slab 캐시를 생성한다.

	/* Mutex is not taken during early boot */
	if (slab_state <= UP)
		return 0;

부팅 과정 중에 slab의 상태를 나타내는 slab_state가 변하게 되는데, UP보다 같거나 낮은 상태일 때는 sysfs를 사용할 수 없으므로 바로 리턴한다.

	err = sysfs_slab_add(s);
	if (err) {
		__kmem_cache_release(s);
		return err;
	}

	if (s->flags & SLAB_STORE_USER)
		debugfs_slab_add(s);

	return 0;
}

/sys/kernel/slab 경로에 현재 생성된 kmem_cache들의 디렉토리가 있고, 각 디렉토리 안에 캐시에 대한 정보를 알 수 있는 파일들이 존재한다. sysfs_slab_add()를 통해 이 디렉토리와 정보를 생성한다.

 

 

kmem_cache_open()

static int kmem_cache_open(struct kmem_cache *s, slab_flags_t flags)
{
	s->flags = kmem_cache_flags(s->size, flags, s->name);

flag와 부트 파라미터에 따라 flag를 세팅한다.

#ifdef CONFIG_SLAB_FREELIST_HARDENED
	s->random = get_random_long();
#endif

freepoint를 random값으로 인코딩, 디코딩하는 경우 필요한 random값을 생성한다.

	if (!calculate_sizes(s))
		goto error;
	if (disable_higher_order_debug) {
		/*
		 * Disable debugging flags that store metadata if the min slab
		 * order increased.
		 */
		if (get_order(s->size) > get_order(s->object_size)) {
			s->flags &= ~DEBUG_METADATA_FLAGS;
			s->offset = 0;
			if (!calculate_sizes(s))
				goto error;
		}
	}

calculate_size()를 통해 Align, Freepointer의 위치, 메타데이터, 레드존 유무에 따른 사이즈를 구한다.

#ifdef system_has_freelist_aba
	if (system_has_freelist_aba() && !(s->flags & SLAB_NO_CMPXCHG)) {
		/* Enable fast mode */
		s->flags |= __CMPXCHG_DOUBLE;
	}
#endif

CMPXCHG를 쓸 수 있는 경우 해당 플래그를 세팅한다.

/*
	 * The larger the object size is, the more slabs we want on the partial
	 * list to avoid pounding the page allocator excessively.
	 */
	s->min_partial = min_t(unsigned long, MAX_PARTIAL, ilog2(s->size) / 2);
	s->min_partial = max_t(unsigned long, MIN_PARTIAL, s->min_partial);

	set_cpu_partial(s);

min_partial은 node에서 최소로 유지해야 하는 slab 페이지의 개수이다. 해당 값은 ilog2(s->size) / 2로 MAX_PARTIAL과 MIN_PARTIAL 사이의 값을 갖는다.

set_cpu_partial()은 CPU partial의 최대 slab 페이지 개수를 세팅한다.

#ifdef CONFIG_NUMA
	s->remote_node_defrag_ratio = 1000;
#endif

CPU가 속한 노드에 남은 slab 페이지가 없는 경우 리모트 노드에서 slab 페이지를 가져올 수 있다. 이때 버디를 통해 새로운 페이지를 가지고 오는 대신 리모트 노드에서만 페이지를 가지고 온다면, 노드 간 페이지 파편화가 심해질 수 있으므로 remote_node_defrag_ratio에 따른 일정 확률로 실패나 성공을 하게 된다.

	/* Initialize the pre-computed randomized freelist if slab is up */
	if (slab_state >= UP) {
		if (init_cache_random_seq(s))
			goto error;
	}

초기 slab의 freelist에 들어가는 객체의 순서를 랜덤으로 하는 경우, 객체의 랜덤한 순서를 미리 정한다.

	if (!init_kmem_cache_nodes(s))
		goto error;

	if (alloc_kmem_cache_cpus(s))
		return 0;

error:
	__kmem_cache_release(s);
	return -EINVAL;
}

init_kmem_cache_nodes()를 통해 노드별로 KMEM_CACHE_NODE를 생성하고 CPU별로 KMEM_CACHE_CPU를 생성한다.

 

 

 

calculate_sizes()

/*
 * calculate_sizes() determines the order and the distribution of data within
 * a slab object.
 */
static int calculate_sizes(struct kmem_cache *s)
{
	slab_flags_t flags = s->flags;
	unsigned int size = s->object_size;
	unsigned int order;

	/*
	 * Round up object size to the next word boundary. We can only
	 * place the free pointer at word boundaries and this determines
	 * the possible location of the free pointer.
	 */
	size = ALIGN(size, sizeof(void *));

#ifdef CONFIG_SLUB_DEBUG
	/*
	 * Determine if we can poison the object itself. If the user of
	 * the slab may touch the object after free or before allocation
	 * then we should never poison the object itself.
	 */
	if ((flags & SLAB_POISON) && !(flags & SLAB_TYPESAFE_BY_RCU) &&
			!s->ctor)
		s->flags |= __OBJECT_POISON;
	else
		s->flags &= ~__OBJECT_POISON;

SLAB_POISON flag가 있을 경우, 객체를 초기화할 때나 객체를 free할 때 특정 값으로 객체를 채운다. Use after free가 발생해서 포이즌 값이 변경된다면 객체를 할당할 때 체크해서 경고를 띄운다. Typesafe by RCU나 constructor가 세팅되어 있는 경우 객체가 해제됐다고 해서 포이즌 값으로 채우면 안됨으로 포이즌 플래그를 지운다.

	/*
	 * If we are Redzoning then check if there is some space between the
	 * end of the object and the free pointer. If not then add an
	 * additional word to have some bytes to store Redzone information.
	 */
	if ((flags & SLAB_RED_ZONE) && size == s->object_size)
		size += sizeof(void *);
#endif

객체의 사이즈가 워드 사이즈에 딱 맞으면 레드존으로 사용할 공간이 없으므로 워드만큼 공간을 추가해 준다.

	/*
	 * With that we have determined the number of bytes in actual use
	 * by the object and redzoning.
	 */
	s->inuse = size;

레드존과 객체의 크기를 inuse에 넣어 메타데이터의 offset 값으로 사용한다.

	if (slub_debug_orig_size(s) ||
	    (flags & (SLAB_TYPESAFE_BY_RCU | SLAB_POISON)) ||
	    ((flags & SLAB_RED_ZONE) && s->object_size < sizeof(void *)) ||
	    s->ctor) {
		/*
		 * Relocate free pointer after the object if it is not
		 * permitted to overwrite the first word of the object on
		 * kmem_cache_free.
		 *
		 * This is the case if we do RCU, have a constructor or
		 * destructor, are poisoning the objects, or are
		 * redzoning an object smaller than sizeof(void *).
		 *
		 * The assumption that s->offset >= s->inuse means free
		 * pointer is outside of the object is used in the
		 * freeptr_outside_object() function. If that is no
		 * longer true, the function needs to be modified.
		 */
		s->offset = size;
		size += sizeof(void *);
	} else {
		/*
		 * Store freelist pointer near middle of object to keep
		 * it away from the edges of the object to avoid small
		 * sized over/underflows from neighboring allocations.
		 */
		s->offset = ALIGN_DOWN(s->object_size / 2, sizeof(void *));
	}

freepointer가 객체 내의 존재할 수도 있고 객체 외부에 존재할 수 있다.

다음 경우에 freepointer가 외부에 위치하게 된다.

  • kmalloc인 경우 요청 사이즈보다 큰 사이즈의 캐시에서 객체를 할당할 수 있고 이런 경우 남는 부분은 레드존으로 사용될 수 있는데 freepointer가 해당 레드존을 침하는 경우가 있어서 freepointer를 객체 외부에 두어야 한다.
  • Typesafe by RCU나 slab poison이 켜진 경우, constructor가 있는 경우, free 후 객체에 값이 세팅될 수 있다.
  • 레드존을 사용하는데 객체 사이즈가 워드보다 작은 경우 freepointer가 레드존을 침범한다.

객체 내부에 존재하는 한 두 바이트 오버플로우로 freepointer가 손상되는 걸 방지하기 위하여 객체 중간 freepointer 위치에 저장이 된다.

#ifdef CONFIG_SLUB_DEBUG
	if (flags & SLAB_STORE_USER) {
		/*
		 * Need to store information about allocs and frees after
		 * the object.
		 */
		size += 2 * sizeof(struct track);

		/* Save the original kmalloc request size */
		if (flags & SLAB_KMALLOC)
			size += sizeof(unsigned int);
	}
#endif

SLAB_STORE_USER가 플래그에 세팅되어 있을 경우, 어느 함수에서 해당 객체가 할당되고 해제되었는지 저장해 두는 메타데이터인 Allocation info과 Free info를 추가한다. (track 구조체)

kamlloc인 경우 객체 사이즈보다 작은 사이즈로 할당 될 수도 있으므로 요청된 사이즈를 저장하기 위해서 공간을 추가로 확보한다.

	kasan_cache_create(s, &size, &s->flags);
#ifdef CONFIG_SLUB_DEBUG
	if (flags & SLAB_RED_ZONE) {
		/*
		 * Add some empty padding so that we can catch
		 * overwrites from earlier objects rather than let
		 * tracking information or the free pointer be
		 * corrupted if a user writes before the start
		 * of the object.
		 */
		size += sizeof(void *);

		s->red_left_pad = sizeof(void *);
		s->red_left_pad = ALIGN(s->red_left_pad, s->align);
		size += s->red_left_pad;
	}
#endif

왼쪽 레드존 사이즈를 추가한다.

	/*
	 * SLUB stores one object immediately after another beginning from
	 * offset 0. In order to align the objects we have to simply size
	 * each object to conform to the alignment.
	 */
	size = ALIGN(size, s->align);
	s->size = size;
	s->reciprocal_size = reciprocal_value(size);

size값을 align으로 맞춘다. reciprocal_size는 슬랩 페이지 내에 오브젝트의 인덱스 계산을 빠르게 하기 위한 어떤 값이다.

	order = calculate_order(size);

	if ((int)order < 0)
		return 0;

	s->allocflags = 0;
	if (order)
		s->allocflags |= __GFP_COMP;

	if (s->flags & SLAB_CACHE_DMA)
		s->allocflags |= GFP_DMA;

	if (s->flags & SLAB_CACHE_DMA32)
		s->allocflags |= GFP_DMA32;

	if (s->flags & SLAB_RECLAIM_ACCOUNT)
		s->allocflags |= __GFP_RECLAIMABLE;

	/*
	 * Determine the number of objects per slab
	 */
	s->oo = oo_make(order, size);
	s->min = oo_make(get_order(size), size);

	return !!oo_objects(s->oo);
}

calculate_order()를 통해 size에 대해서 슬랩 페이지를 구성할 페이지의 오더를 구해온다.

order와 flag 값에 따라 allocflag를 세팅한다.

s→oo은 오더와 사이즈를 저장하고 있는 멤버변수로, 상위 16비트에 order를 저장하고 하위 16비트에 size를 저장한다. s→oo의 order는 이상적인 order 수이고 s→min의 order는 최소로 필요한 order가 저장된다.

슬랩 페이지의 오브젝트 개수를 리턴하며 함수가 종료된다.

 

calculate_order()

static inline int calculate_order(unsigned int size)
{
	unsigned int order;
	unsigned int min_objects;
	unsigned int max_objects;
	unsigned int min_order;

	min_objects = slub_min_objects;
	if (!min_objects) {
		/*
		 * Some architectures will only update present cpus when
		 * onlining them, so don't trust the number if it's just 1. But
		 * we also don't want to use nr_cpu_ids always, as on some other
		 * architectures, there can be many possible cpus, but never
		 * onlined. Here we compromise between trying to avoid too high
		 * order on systems that appear larger than they are, and too
		 * low order on systems that appear smaller than they are.
		 */
		unsigned int nr_cpus = num_present_cpus();
		if (nr_cpus <= 1)
			nr_cpus = nr_cpu_ids;
		min_objects = 4 * (fls(nr_cpus) + 1);
	}
	/* min_objects can't be 0 because get_order(0) is undefined */
	max_objects = max(order_objects(slub_max_order, size), 1U);
	min_objects = min(min_objects, max_objects);

	min_order = max_t(unsigned int, slub_min_order,
			  get_order(min_objects * size));
	if (order_objects(min_order, size) > MAX_OBJS_PER_PAGE)
		return get_order(size * MAX_OBJS_PER_PAGE) - 1;

슬랩 할당할 때 order를 정한다. CPU가 개수에 비례해서 최소 필요한 객체의 개수도 증가한다. 부트 파라미터인 slub_max_orde와 slub_min_order, slub_min_ojbects도 반영하여 개수를 조절한다.

여기서 설정한 order가 한 페이지 내의 최대 객체 개수인 MAX_OBJS_PER_PAGE를 넘는다면 최대 객체 개수의 오더 - 1개로 오더를 설정해 리턴한다.

/*
	 * Attempt to find best configuration for a slab. This works by first
	 * attempting to generate a layout with the best possible configuration
	 * and backing off gradually.
	 *
	 * We start with accepting at most 1/16 waste and try to find the
	 * smallest order from min_objects-derived/slub_min_order up to
	 * slub_max_order that will satisfy the constraint. Note that increasing
	 * the order can only result in same or less fractional waste, not more.
	 *
	 * If that fails, we increase the acceptable fraction of waste and try
	 * again. The last iteration with fraction of 1/2 would effectively
	 * accept any waste and give us the order determined by min_objects, as
	 * long as at least single object fits within slub_max_order.
	 */
	for (unsigned int fraction = 16; fraction > 1; fraction /= 2) {
		order = calc_slab_order(size, min_order, slub_max_order,
					fraction);
		if (order <= slub_max_order)
			return order;
	}

페이지의 남는 부분이 적으면서 min_order와 제일 가까운 오더를 찾는다. 찾는 오더가 slub_max_order보다 적으면 해당 order를 리턴한다.

	/*
	 * Doh this slab cannot be placed using slub_max_order.
	 */
	order = get_order(size);
	if (order <= MAX_ORDER)
		return order;
	return -ENOSYS;
}

여기까지 코드가 진행된 경우는 slub_max_order에 맞는 효율적인 오더를 찾지 못한 경우이므로 size에 맞는 오더를 찾아 리턴한다.

 

 

set_cpu_partial()

#ifdef CONFIG_SLUB_CPU_PARTIAL
	unsigned int nr_objects;

	/*
	 * cpu_partial determined the maximum number of objects kept in the
	 * per cpu partial lists of a processor.
	 *
	 * Per cpu partial lists mainly contain slabs that just have one
	 * object freed. If they are used for allocation then they can be
	 * filled up again with minimal effort. The slab will never hit the
	 * per node partial lists and therefore no locking will be required.
	 *
	 * For backwards compatibility reasons, this is determined as number
	 * of objects, even though we now limit maximum number of pages, see
	 * slub_set_cpu_partial()
	 */
	if (!kmem_cache_has_cpu_partial(s))
		nr_objects = 0;
	else if (s->size >= PAGE_SIZE)
		nr_objects = 6;
	else if (s->size >= 1024)
		nr_objects = 24;
	else if (s->size >= 256)
		nr_objects = 52;
	else
		nr_objects = 120;

	slub_set_cpu_partial(s, nr_objects);
#endif
}

slab 사이즈에 따라 CPU partial에 최대 유지하려는 객체의 개수를 정한다. 객체의 개수를 기준으로 partial의 slab 페이지를 가지고 올 경우, 상당히 많은 페이지를 가지고 오는 경우가 있으므로 slub_set_cpu_partial() 함수를 통해 nr_objects를 slab 페이지의 개수로 변환한다.

slub_set_cpu_partial()

static void slub_set_cpu_partial(struct kmem_cache *s, unsigned int nr_objects)
{
	unsigned int nr_slabs;

	s->cpu_partial = nr_objects;

	/*
	 * We take the number of objects but actually limit the number of
	 * slabs on the per cpu partial list, in order to limit excessive
	 * growth of the list. For simplicity we assume that the slabs will
	 * be half-full.
	 */
	nr_slabs = DIV_ROUND_UP(nr_objects * 2, oo_objects(s->oo));
	s->cpu_partial_slabs = nr_slabs;
}

slab 페이지가 가지고 있는 객체의 반만 가지고 있다는 가정을하고 nr_slabs를 계산한다.

 

번호 제목 글쓴이 날짜 조회 수
공지 [공지] 스터디 정리 노트 공간입니다. woos 2016.05.14 626
248 [커널 19차] 103 주차 new Min 2024.04.28 3
247 [커널 20차] 48주차 무한질주 2024.04.25 22
246 [커널 19차] 102 주차 Min 2024.04.20 37
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
» [커널 19차] 97 주차 Min 2024.03.16 50
240 [커널 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 738
231 [커널 20차] 23주차 이민찬 2023.10.14 81
230 [커널 20차] 22주차 이민찬 2023.10.08 76
229 [커널 20차] 21주차 이민찬 2023.09.23 116
XE Login