[커널 19차] 95 주차
2024.03.03 10:24
kmem_cache_free()
void kmem_cache_free(struct kmem_cache *s, void *x)
{
s = cache_from_obj(s, x);
if (!s)
return;
trace_kmem_cache_free(_RET_IP_, x, s);
slab_free(s, virt_to_slab(x), x, NULL, &x, 1, _RET_IP_);
}
슬랩 오브젝트를 free하기 위한 함수이다.
cache_from_obj()를 통해 인자로 넘어온 kmem_cache인 s와 free할 오브젝트인 x가 속한 kmem_cache가 맞는지 확인한다. 오브젝트의 kmem_cache를 가져오기 위해서는 x가 속한 page의 헤더에 있는 정보를 참조한다. 이 과정은 몇 가지 처리가 더 필요함으로 설정에 따라 비교를 하지 않을 수 있다.
static __fastpath_inline void slab_free(struct kmem_cache *s, struct slab *slab,
void *head, void *tail, void **p, int cnt,
unsigned long addr)
{
memcg_slab_free_hook(s, slab, p, cnt);
cgroup의 memory controller 관련 hook 함수를 호출한다.
/*
* With KASAN enabled slab_free_freelist_hook modifies the freelist
* to remove objects, whose reuse must be delayed.
*/
if (slab_free_freelist_hook(s, &head, &tail, &cnt))
do_slab_free(s, slab, head, tail, cnt, addr);
}
do_slab_free()가 실질적으로 해제를 하는 함수인데, KASAN이 활성화된 경우, freelist (head ~ tail)를 수정할 수 있다.
do_slab_free() (!SLUB_TINY)
/*
* Fastpath with forced inlining to produce a kfree and kmem_cache_free that
* can perform fastpath freeing without additional function calls.
*
* The fastpath is only possible if we are freeing to the current cpu slab
* of this processor. This typically the case if we have just allocated
* the item before.
*
* If fastpath is not possible then fall back to __slab_free where we deal
* with all sorts of special processing.
*
* Bulk free of a freelist with several objects (all pointing to the
* same slab) possible by specifying head and tail ptr, plus objects
* count (cnt). Bulk free indicated by tail pointer being set.
*/
static __always_inline void do_slab_free(struct kmem_cache *s,
struct slab *slab, void *head, void *tail,
int cnt, unsigned long addr)
{
void *tail_obj = tail ? : head;
struct kmem_cache_cpu *c;
unsigned long tid;
void **freelist;
redo:
/*
* Determine the currently cpus per cpu slab.
* The cpu may change afterward. However that does not matter since
* data is retrieved via this pointer. If we are on the same cpu
* during the cmpxchg then the free will succeed.
*/
c = raw_cpu_ptr(s->cpu_slab);
tid = READ_ONCE(c->tid);
/* Same with comment on barrier() in slab_alloc_node() */
barrier();
현재 CPU의 kmem_cache_cpu (c)를 읽어오고, 다른 필드들보다 가장 먼저 transaction id (tid) 필드를 읽어온다. tid 이외의 다른 필드를 먼저 읽지 않도록 컴파일러 배리어를 사용한다. 컴파일러 배리어는 읽기/쓰기 배리어처럼 실제 CPU instruction 이 삽입되지는 않고, 컴파일러가 순서를 바꾸지 못하록 한다.
만약 kmem_cache_cpu의 다른 필드를 tid보다 먼저 읽어온다면 무결성이 깨질 수 있다. (cmpxchg_double을 할때 tid가 바뀌지 않았더라도, c→freelist or c→slab를 읽고 나서 c→tid를 읽는 그 사이에 누군가 객체를 해제하거나 할당했을 수 있다.)
if (unlikely(slab != c->slab)) {
__slab_free(s, slab, head, tail_obj, cnt, addr);
return;
}
현재 CPU slab에 속하지 않는 객체를 해제하는 경우, slowpath를 탄다.
fastpath에서는 보통 lock을 사용하지 않지만, PREEMPT_RT config 옵션이 켜진 경우에는 (cmpxchg_double을 사용한) lockless fastpath를 사용하지 않고, local_lock을 사용한다.
if (USE_LOCKLESS_FAST_PATH()) {
freelist = READ_ONCE(c->freelist);
set_freepointer(s, tail_obj, freelist);
if (unlikely(!__update_cpu_freelist_fast(s, freelist, head, tid))) {
note_cmpxchg_failure("slab_free", s, tid);
goto redo;
}
}
!PREEMPT_RT인 경우에는 free list에 객체를 삽입한다. (tail_obj → fp)를 freelist로 바꾸고, c→freelist를 head로 갱신한다.
else {
/* Update the free list under the local lock */
local_lock(&s->cpu_slab->lock);
c = this_cpu_ptr(s->cpu_slab);
if (unlikely(slab != c->slab)) {
local_unlock(&s->cpu_slab->lock);
goto redo;
}
tid = c->tid;
freelist = c->freelist;
set_freepointer(s, tail_obj, freelist);
c->freelist = head;
c->tid = next_tid(tid);
local_unlock(&s->cpu_slab->lock);
}
stat(s, FREE_FASTPATH)
PREEMPT_RT에서도 똑같이 객체를 freelist에 삽입하지만 local_lock을 사용한다.
__slab_free()
slowpath에서는, 위 다이어그램과 같이 slab→freelist와 counter(inuse, frozen)들을 갱신한다. SLUB은 free path에서 최대한 n→list_lock을 사용하지 않고, slab_update_freelist()로 객체를 해제하려고 한다. 단, 객체의 해제 후 node의 partial slab list를 수정해야 하는 경우 (1. full → partial이 되어서 slab을 node partial slab list에 삽입하거나, 2. partial or full → empty가 되어서 slab을 node partial slab list에서 제거하거나)에는 slab_update_freelist()로 freelist를 갱신하기 전에 n→list_lock을 먼저 획득한다.
/*
* Slow path handling. This may still be called frequently since objects
* have a longer lifetime than the cpu slabs in most processing loads.
*
* So we still attempt to reduce cache line usage. Just take the slab
* lock and free the item. If there is no additional partial slab
* handling required then we can return immediately.
*/
static void __slab_free(struct kmem_cache *s, struct slab *slab,
void *head, void *tail, int cnt,
unsigned long addr)
{
void *prior;
int was_frozen;
struct slab new;
unsigned long counters;
struct kmem_cache_node *n = NULL;
unsigned long flags;
stat(s, FREE_SLOWPATH);
if (kfence_free(head))
return;
kfence에서 할당한 객체인 경우, SLUB이 아닌 kfence에서 해제되므로, 이 경우는 곧바로 리턴한다.
if (IS_ENABLED(CONFIG_SLUB_TINY) || kmem_cache_debug(s)) {
free_to_partial_list(s, slab, head, tail, cnt, addr);
return;
}
CPU slab과 CPU partial을 사용하지 않는 경우 (SLUB_TINY 옵션이 켜져있거나, 디버그 캐시인 경우) free_to_partial_list()로 node의 partial slab list에 있는 slab에 곧바로 해제한다.
아래 do ~ while문은 slab_update_freelist()이 성공할 때까지 반복하여 해제를 시도한다. slab_update_freelist()는 slab→freelist와 slab→counters를 atomic하게 갱신한다.
do {
if (unlikely(n)) {
spin_unlock_irqrestore(&n->list_lock, flags);
n = NULL;
}
n ≠ NULL인 경우, n→list_lock을 획득한 상태로 slab_update_freelist()를 실패한 경우이므로 n→list_lock을 반환한다.
prior = slab->freelist;
counters = slab->counters;
set_freepointer(s, tail, prior);
new.counters = counters;
was_frozen = new.frozen;
new.inuse -= cnt;
prior는 기존의 slab→freelist의 값을 저장한다. set_freepointer()로 tail의 free pointer를 slab→freelist 변경한다.
counters에는 objects, inuse, frozen 세 값이 포함되어있다. (union과 struct를 사용하여). 기존의 counters 값을 복사한 다음, inuse를 해제할 객체의 수만큼 빼주고, was_frozen 변수에 이 슬랩이 어떤 CPU에 의해서 freeze 되었는지를 저장한다.
if ((!new.inuse || !prior) && !was_frozen) {
일반적으로 SLUB은 __slab_free()에서 n→list_lock을 최대한 획득하지 않으려고 하지만, 객체를 해제한 다음 node의 partial slab list를 수정해야 하는 경우에는 n→list_lock을 먼저 획득한다.
-
이번에 객체를 해제함으로써 슬랩이 empty slab이 되거나
-
slab→freelist (= prior)가 NULL이었던 경우, full slab → partial slab으로 바뀐 경우
위 두 가지 경우에는, 슬랩이 frozen slab이 아니라면 (!was_frozen) n→partial을 수정해야 하므로 n→list_lock을 획득한다. 1번 케이스는 partial slab을 n→partial에서 제거하는 경우이고, 2번 케이스는 full이었던 slab이 partial slab으로 바뀌어서 n→partial에 삽입되는 경우이다.
if (kmem_cache_has_cpu_partial(s) && !prior) {
/*
* Slab was on no list before and will be
* partially empty
* We can defer the list move and instead
* freeze it.
*/
new.frozen = 1;
}
단, full slab → partial slab이 되는 경우에, 현재 캐시가 CPU partial을 사용한다면 n→partial에 삽입하는 대신 현재 CPU에서 freeze를 한 다음 CPU partial list에 삽입한다.
else { /* Needs to be taken off a list */
n = get_node(s, slab_nid(slab));
/*
* Speculatively acquire the list_lock.
* If the cmpxchg does not succeed then we may
* drop the list_lock without any processing.
*
* Otherwise the list_lock will synchronize with
* other processors updating the list of slabs.
*/
spin_lock_irqsave(&n->list_lock, flags);
}
}
} while (!slab_update_freelist(s, slab,
prior, counters,
head, new.counters,
"__slab_free"));
만약 위에서 freeze를 하지 않았다면, n→list_lock을 획득한 다음 slab_update_freelist()를 시도한다. 단, slab_update_freelist()가 실패하면 다시 처음부터 시도한다.
if (likely(!n)) {
if (likely(was_frozen)) {
/*
* The list lock was not taken therefore no list
* activity can be necessary.
*/
stat(s, FREE_FROZEN);
}
slab_update_freelist()가 성공했는데 n == NULL인 경우에는 n→partial을 전혀 수정하지 않는 경우다. was_frozen == true였다면 stat을 갱신한다.
else if (new.frozen) {
/*
* If we just froze the slab then put it onto the
* per cpu partial list.
*/
put_cpu_partial(s, slab, 1);
stat(s, CPU_PARTIAL_FREE);
}
return;
}
full → partial이 되면서 현재 CPU가 freeze한 경우에는 여기서 put_cpu_partial()로 slab을 CPU partial list에 삽입하고 stat을 갱신한다.
if (unlikely(!new.inuse && n->nr_partial >= s->min_partial))
goto slab_empty;
slab이 empty가 된 경우에는, 현재 노드의 partial slab 수가 최소 threshold (s→min_partial) 이상인 경우에만 버디에게 반환한다. (goto slab_empty)
/*
* Objects left in the slab. If it was not on the partial list before
* then add it.
*/
if (!kmem_cache_has_cpu_partial(s) && unlikely(!prior)) {
remove_full(s, n, slab);
add_partial(n, slab, DEACTIVATE_TO_TAIL);
stat(s, FREE_ADD_PARTIAL);
}
spin_unlock_irqrestore(&n->list_lock, flags);
return;
실행 흐름이 여기까지 온 경우는 1) empty slab이 되었지만 threshold 때문에 해제하지 않은 경우거나, 2) full slab → partial slab이 되었지만 freeze를 하지 않은 경우이다.
1번의 경우, 이미 n→partial에 존재하기 때문에, 2번의 경우에만 슬랩을 n→partial에 삽입한다. 일반적으로 SLUB은 full slab list를 관리하지 않지만, SLAB_STORE_USER 디버깅 플래그를 사용하는 경우에는 full slab의 linked list를 관리하기 때문에 full slab → partial slab이 되었을때, n→full에서 슬랩을 제거해주어야 한다.
n→partial 리스트를 수정한 다음에는 n→list_lock을 반환한다.
slab_empty:
if (prior) {
/*
* Slab on the partial list.
*/
remove_partial(n, slab);
stat(s, FREE_REMOVE_PARTIAL);
} else {
/* Slab must be on the full list */
remove_full(s, n, slab);
}
spin_unlock_irqrestore(&n->list_lock, flags);
stat(s, FREE_SLAB);
discard_slab(s, slab);
}
empty slab을 해제하는 경우는 두 가지로 나뉜다.
1) partial slab → empty slab이 된 경우에는 n→partial 리스트에서 슬랩을 제거한 다음 버디에게 반환한다.
2) full slab → empty slab이 된 경우에는 n→full 리스트에서 슬랩을 제거한 다음 버디에게 반환한다.
버디에게 empty slab을 반환할 때는 n→list_lock을 반환한 다음 discard_slab()을 호출한다.
번호 | 제목 | 글쓴이 | 날짜 | 조회 수 |
---|---|---|---|---|
공지 | [공지] 스터디 정리 노트 공간입니다. | woos | 2016.05.14 | 626 |
248 | [커널 19차] 103 주차 | Min | 2024.04.28 | 1 |
247 | [커널 20차] 48주차 | 무한질주 | 2024.04.25 | 21 |
246 | [커널 19차] 102 주차 | Min | 2024.04.20 | 36 |
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 |
240 | [커널 19차] 96 주차 | Min | 2024.03.14 | 32 |
» | [커널 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 | 737 |
231 | [커널 20차] 23주차 | 이민찬 | 2023.10.14 | 81 |
230 | [커널 20차] 22주차 | 이민찬 | 2023.10.08 | 76 |
229 | [커널 20차] 21주차 | 이민찬 | 2023.09.23 | 116 |
.