[커널 19차] 99 주차

2024.03.30 17:56

Min 조회 수:82

kmem_cache_destroy()

슬랩 캐시 자체를 삭제해주는 함수다.

void kmem_cache_destroy(struct kmem_cache *s)
{
	int err = -EBUSY;
	bool rcu_set;

	if (unlikely(!s) || !kasan_check_byte(s))
		return;
  • kmem_cache가 NULL인 경우 리턴한다.
	cpus_read_lock();
	mutex_lock(&slab_mutex);

	rcu_set = s->flags & SLAB_TYPESAFE_BY_RCU;

	s->refcount--;
	if (s->refcount)
		goto out_unlock;

	err = shutdown_cache(s);
	WARN(err, "%s %s: Slab cache still has objects when called from %pS",
	     __func__, s->name, (void *)_RET_IP_);
  • CPU read lock을 획득하는 이유는 모르겠다.
  • 슬랩 캐시들을 리스트로 가지고 있는 slab_caches에서 인자로 넘어온 캐시를 제거해야 됨으로 slab_mutext를 확보한다.
  • 슬랩 캐시 merge와 같이 여러 군데에서 슬랩 캐시를 사용하고 있는 경우가 있으므로 refconunt가 0인 경우만 detroy를 진행한다.
  • shutdown_cache()에서 슬랩 캐시를 삭제한다.
out_unlock:
	mutex_unlock(&slab_mutex);
	cpus_read_unlock();
	if (!err && !rcu_set)
		kmem_cache_release(s);
}
EXPORT_SYMBOL(kmem_cache_destroy);
  • sysfs에 만들었던 파일과 링크들을 kmem_cache_release()통해 삭제한다.

 

shutdown_cache()

static int shutdown_cache(struct kmem_cache *s)
{
	/* free asan quarantined objects */
	kasan_cache_shutdown(s);

	if (__kmem_cache_shutdown(s) != 0)
		return -EBUSY;

	list_del(&s->list);

	if (s->flags & SLAB_TYPESAFE_BY_RCU) {
		list_add_tail(&s->list, &slab_caches_to_rcu_destroy);
		schedule_work(&slab_caches_to_rcu_destroy_work);
	} else {
		kfence_shutdown_cache(s);
		debugfs_slab_release(s);
	}

	return 0;
}
  • __kmem_cache_shutdown()에서 슬랩 캐시를 삭제한다. 만약 아직 할당 해제되지 않은 객체들이 있어서 캐시 삭제에 실패한 경우 EBUSY를 리턴해서 삭제를 중단한다.
  • 캐시 삭제에 성공한 경우 slab_caches 리스트에서 해당 캐시를 삭제한다.
/*
 * Release all resources used by a slab cache.
 */
int __kmem_cache_shutdown(struct kmem_cache *s)
{
	int node;
	struct kmem_cache_node *n;

	flush_all_cpus_locked(s);
	/* Attempt to free all objects */
	for_each_kmem_cache_node(s, node, n) {
		free_partial(s, n);
		if (n->nr_partial || node_nr_slabs(n))
			return 1;
	}
	return 0;
}
  • 모든 CPU에 있는 CPU 슬랩과 CPU partial에 있는 슬랩들을 노드로 보내거나 버디로 삭제한다.
  • 모든 노드에 있는 슬랩을 버디로 돌려보낸다.
  • 만약 삭제되지 않은 슬랩이 있는 경우 캐시 삭제에 실패한다.

 

flush_all_cpus_locked()

static DEFINE_MUTEX(flush_lock);
static DEFINE_PER_CPU(struct slub_flush_work, slub_flush);

static void flush_all_cpus_locked(struct kmem_cache *s)
{
	struct slub_flush_work *sfw;
	unsigned int cpu;

	lockdep_assert_cpus_held();
	mutex_lock(&flush_lock);

	for_each_online_cpu(cpu) {
		sfw = &per_cpu(slub_flush, cpu);
		if (!has_cpu_slab(cpu, s)) {
			sfw->skip = true;
			continue;
		}
		INIT_WORK(&sfw->work, flush_cpu_slab);
		sfw->skip = false;
		sfw->s = s;
		queue_work_on(cpu, flushwq, &sfw->work);
	}
  • 각 CPU에 있는 CPU 슬랩과, partial 슬랩들을 직접 삭제하지 않고 각 CPU의 워크큐를 통해 삭제한다.
  • CPU에 있는 slub_flush를 초기화하고 실행해야하므로 flush_lock을 잡는다.
  • 각 CPU를 순회하면서 slub_flush를 가지고 오고, work 함수를 flush_cpu_slab로 초기화한다.
  • queue_work_on을 통해 워크큐에 작업을 넣는다. 이렇게하면서 해당하는 CPU에서 해당 잡을 알아서 실행한다.
  • 만약 CPU에 슬랩과 partial 슬랩이 아무것도 없는 경우는 스킵한다.
	for_each_online_cpu(cpu) {
		sfw = &per_cpu(slub_flush, cpu);
		if (sfw->skip)
			continue;
		flush_work(&sfw->work);
	}

	mutex_unlock(&flush_lock);
}
  • flush_work()를 통해 워크큐에 보냈던 잡이 다 끝날 때까지 기다린다.

 

flush_cpu_slab()

/*
 * Flush cpu slab.
 *
 * Called from CPU work handler with migration disabled.
 */
static void flush_cpu_slab(struct work_struct *w)
{
	struct kmem_cache *s;
	struct kmem_cache_cpu *c;
	struct slub_flush_work *sfw;

	sfw = container_of(w, struct slub_flush_work, work);

	s = sfw->s;
	c = this_cpu_ptr(s->cpu_slab);

	if (c->slab)
		flush_slab(s, c);

	unfreeze_partials(s);
}
  • 워크큐에 의해서 각 CPU에서 실행되는 함수로 CPU 슬랩은 flush_slab() 통해 CPU partial은 unfreeze_partials() 통해 슬랩들을 노드로 보내거나 삭제한다.

flush_slab()

static inline void flush_slab(struct kmem_cache *s, struct kmem_cache_cpu *c)
{
	unsigned long flags;
	struct slab *slab;
	void *freelist;

	local_lock_irqsave(&s->cpu_slab->lock, flags);

	slab = c->slab;
	freelist = c->freelist;

	c->slab = NULL;
	c->freelist = NULL;
	c->tid = next_tid(c->tid);

	local_unlock_irqrestore(&s->cpu_slab->lock, flags);

	if (slab) {
		deactivate_slab(s, slab, freelist);
		stat(s, CPUSLAB_FLUSH);
	}
}
  • CPU 슬랩과 kmem_cache_cpu와의 관계를 끊고 CPU 슬랩을 deactivate_slab()을 통해 노드로 보내거나 삭제한다.

unfreeze_partials()

/*
 * Unfreeze all the cpu partial slabs.
 */
static void unfreeze_partials(struct kmem_cache *s)
{
	struct slab *partial_slab;
	unsigned long flags;

	local_lock_irqsave(&s->cpu_slab->lock, flags);
	partial_slab = this_cpu_read(s->cpu_slab->partial);
	this_cpu_write(s->cpu_slab->partial, NULL);
	local_unlock_irqrestore(&s->cpu_slab->lock, flags);

	if (partial_slab)
		__unfreeze_partials(s, partial_slab);
}
  • kmem_cache_cpu 구조체의 partail을 NULL로 바꿔 partial 리스트와의 관계를 끊는다.
  • __unfreeze_partials()통해 partial 리스트에 있는 슬랩을 삭제하거나 노드로 보낸다.

__unfreeze_partials()

static void __unfreeze_partials(struct kmem_cache *s, struct slab *partial_slab)
{
	struct kmem_cache_node *n = NULL, *n2 = NULL;
	struct slab *slab, *slab_to_discard = NULL;
	unsigned long flags = 0;

	while (partial_slab) {
		struct slab new;
		struct slab old;

		slab = partial_slab;
		partial_slab = slab->next;
  • partial 리스트인 partial_slab을 하나씩 순회한다.
		n2 = get_node(s, slab_nid(slab));
		if (n != n2) {
			if (n)
				spin_unlock_irqrestore(&n->list_lock, flags);

			n = n2;
			spin_lock_irqsave(&n->list_lock, flags);
		}
  • CPU partial 리스트에 있는 slab은 remote 할당 경우와 같이 다른 노드에서 슬랩을 할당받아 왔을 경우가 있는데, 그런 경우는 해당 노드의 lock을 획득한다.
		do {

			old.freelist = slab->freelist;
			old.counters = slab->counters;
			VM_BUG_ON(!old.frozen);

			new.counters = old.counters;
			new.freelist = old.freelist;

			new.frozen = 0;

		} while (!__slab_update_freelist(s, slab,
				old.freelist, old.counters,
				new.freelist, new.counters,
				"unfreezing slab"));
  • 현재 슬랩의 frozen을 0으로 해준다.
  • 중간에 객체를 해제하는 경우 등 cmpxchg가 실패하는 경우가 있으므로 성공할 때까지 시도한다.
		if (unlikely(!new.inuse && n->nr_partial >= s->min_partial)) {
			slab->next = slab_to_discard;
			slab_to_discard = slab;
		} else {
			add_partial(n, slab, DEACTIVATE_TO_TAIL);
			stat(s, FREE_ADD_PARTIAL);
		}
	}
  • 현재 슬랩이 empty 상태이고 노드의 최소 슬랩 유지 개수를 넘었다면 discard 리스트에 삽입한다.
  • 그게 아니라면, 슬랩을 노드의 partial에 추가한다.
	if (n)
		spin_unlock_irqrestore(&n->list_lock, flags);

	while (slab_to_discard) {
		slab = slab_to_discard;
		slab_to_discard = slab_to_discard->next;

		stat(s, DEACTIVATE_EMPTY);
		discard_slab(s, slab);
		stat(s, FREE_SLAB);
	}
}
  • discard 리스트에 있는 슬랩들을 discard_slab()을 통해 버디로 삭제한다.

 

free_partial()

/*
 * Attempt to free all partial slabs on a node.
 * This is called from __kmem_cache_shutdown(). We must take list_lock
 * because sysfs file might still access partial list after the shutdowning.
 */
static void free_partial(struct kmem_cache *s, struct kmem_cache_node *n)
{
	LIST_HEAD(discard);
	struct slab *slab, *h;

	BUG_ON(irqs_disabled());
	spin_lock_irq(&n->list_lock);
	list_for_each_entry_safe(slab, h, &n->partial, slab_list) {
		if (!slab->inuse) {
			remove_partial(n, slab);
			list_add(&slab->slab_list, &discard);
		} else {
			list_slab_objects(s, slab,
			  "Objects remaining in %s on __kmem_cache_shutdown()");
		}
	}
	spin_unlock_irq(&n->list_lock);

	list_for_each_entry_safe(slab, h, &discard, slab_list)
		discard_slab(s, slab);
}
  • 노드의 partial 리스트를 순회하면서
    • 슬랩이 empty 상태일 경우 슬랩을 discard 리스트에 추가한다.
    • 슬랩에 아직 할당된 객체가 남아있는 경우 list_slab_objects()을 통해 할당된 객체의 정보를 출력한다.
  • discard 리스트에 등록된 슬랩들을 discard_slab()을 통해 버디로 삭제한다.

list_slab_objects()

static void list_slab_objects(struct kmem_cache *s, struct slab *slab,
			      const char *text)
{
#ifdef CONFIG_SLUB_DEBUG
	void *addr = slab_address(slab);
	void *p;

	slab_err(s, slab, text, s->name);

	spin_lock(&object_map_lock);
	__fill_map(object_map, s, slab);

	for_each_object(p, s, addr, slab->objects) {

		if (!test_bit(__obj_to_index(s, addr, p), object_map)) {
			pr_err("Object 0x%p @offset=%tu\\n", p, p - addr);
			print_tracking(s, p);
		}
	}
	spin_unlock(&object_map_lock);
#endif
}
  • __fill_map을 통해 현재 슬랩의 freelist에 있는 객체의 인덱스를 알아내서 map에 있는 해당 인덱스를 1로 세팅한다.
  • 슬랩에 속한 모든 객체를 순회하면서 map에 1로 체크되어 있지 않는 객체에 대해서는 에러 메세지를 출력해준다.

 

번호 제목 글쓴이 날짜 조회 수
공지 [공지] 스터디 정리 노트 공간입니다. woos 2016.05.14 627
248 [커널 19차] 103 주차 Min 2024.04.28 10
247 [커널 20차] 48주차 무한질주 2024.04.25 25
246 [커널 19차] 102 주차 Min 2024.04.20 40
245 [커널 19차] 101 주차 Min 2024.04.13 63
244 [커널 19차] 100 주차 Min 2024.04.13 16
» [커널 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
239 [커널 19차] 95 주차 [2] Min 2024.03.03 111
238 [커널 20차] 32주차 brw 2023.12.16 387
237 [커널 20차] 29주차 brw 2023.11.27 161
236 [커널 20차] 27주차 brw 2023.11.21 86
235 [커널 20차] 26주차 brw 2023.11.21 49
234 [커널 20차] 28주차 이민찬 2023.11.19 64
233 [커널 20차] 25주차 이민찬 2023.10.30 120
232 [커널 20차] 24주차 이민찬 2023.10.22 752
231 [커널 20차] 23주차 이민찬 2023.10.14 81
230 [커널 20차] 22주차 이민찬 2023.10.08 76
229 [커널 20차] 21주차 이민찬 2023.09.23 116
XE Login