[커널 19차] 97 주차
2024.03.16 22:22
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를 계산한다.
댓글 0
번호 | 제목 | 글쓴이 | 날짜 | 조회 수 |
---|---|---|---|---|
공지 | [공지] 스터디 정리 노트 공간입니다. | woos | 2016.05.14 | 627 |
248 | [커널 19차] 103 주차 | Min | 2024.04.28 | 5 |
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 | 745 |
231 | [커널 20차] 23주차 | 이민찬 | 2023.10.14 | 81 |
230 | [커널 20차] 22주차 | 이민찬 | 2023.10.08 | 76 |
229 | [커널 20차] 21주차 | 이민찬 | 2023.09.23 | 116 |
.