티스토리 뷰

4.7.6 슬랩 객체 할당 (p449)

4-137 p464 new_slab_objects

  • node 의 partial list 를 cpu 캐시로 옮겨온다, 없는 경우 버디 시스템에서 할당 받는다.
  • slub 에서 node 는 partial list 만 갖고 있고, cpu cache 에서는 freelist, page, partial 로 관리된다.
freelist = get_partial() // cpu 캐시로 노드의 partial list 에서 가져오기
if (freelist) return freelist;

page = new_slab(s, flags, node); // 없는 경우 버디시스템에서 할당
freelist = page->freelist // 첫 객체

return freelist

4-138 p467 get_partial

  • 노드의 partial 리스트에서 page 를 cpu 캐시로 가져온다.
  • 첫 slub 은 cpu_slab 의 page 와 free_list 로 올라가고, 나머지는 cpu_slab 의 partial 에 추가된다.
  • cpu_slab->cpu_partial 의 절반을 초과하는 만큼 가져간다
// node 를 지정하지 않았다면 로컬 node  나 가장 가까운 node 사용

// 내 node 에 present_page 가 없다면 인접 노드에서 획득
node_to_mem_node() to determine the fallback node

static inline struct kmem_cache_node *get_node(struct kmem_cache *s, int node)
{
    return s->node[node];
}
object = get_partial_node(s, get_node(s, searchnode), c, flags);

4-139 p468 get_partial_node

  • node 의 partial 리스트에서 슬랩 페이지 가져오기
for (page = node->partial) { // node 의 partial 리스트를 순회
  t = acquire_partial(...) // 4-141
}

// 처음 획득시에는 바로 kmem_cache_cpu 의 page 에 등록
c->page = page

// 이후는 kmem_cache_cpu 의 partial 리스트에 등록
put_cpu_partial(...) // 4-147
list_for_each_entry_safe(page, page2, &n->partial, lru) {
n->partial 이 가리키고 있는 리스트가 lru 를 가리키고 있어서 이 주소를 역으로 page 로 돌리는 코드로 보임

4-140 get_any_partial

리모트 노드에서 슬랩 페이지 획득

// 다른 노드에서 slab 페이지를 획득할 확률 지정. 
// 0 인경우 무조건 로컬, 100인경우 대부분 리모트에서 획득 가능
remote_node_defrag_ratio 

mempolicy_slab_node // NUMA policy 에 따라 사용 가능한 node 선택
node_zonelist // node 의 0번 zonelist 나 1번 zonelist 선택

zone 을 순회하면서 가능한 node 에 대해 get_partial_node 호출
cpuset 이 변경됐는지 확인

static inline int read_seqcount_retry(const seqcount_t *s, unsigned start)
{
    smp_rmb();
    return __read_seqcount_retry(s, start);
}

static inline int __read_seqcount_retry(const seqcount_t *s, unsigned start)
{
    return unlikely(s->sequence != start);
}

4-141 p471 acquire_slab

  • get_partial_node 에서 호출했던 코드
  • node 의 partial list 에서 슬랩 페이지를 제거하고 freelist 를 리턴
// page = node->partialist 를 순회하며 가져옴
t = acquire_slab(s, n, page, object == NULL, &objects);

// 처음 객체인 경우 모든 객체를 사용중으로 변경, 그렇지 않다면 freelist 만 설정
// 그리고 슬랩 페이지로 사용하게 되니 frozen 켜주기

freelist = page->freelist;
counters = page->counters;
new.counters = counters;
*objects = new.objects - new.inuse;
if (mode) {
    new.inuse = page->objects;
    new.freelist = NULL;
} else {
    new.freelist = freelist;
}

VM_BUG_ON(new.frozen);
new.frozen = 1;

4.7.7 슬랩 객체 해제

4-142 p473 kmem_cache_free
4-143 p473 cache_from_obj

  • kmem_cache_free 로 슬랩 객체 해제 요청
  • pcpu 캐시의 page 로 돌리기 > pcpu 캐시의 partial list 에 추가 > node 의 partial 에 추가 > 버디 시스템에 추가 순으로 동작
cache_from_obj(struct kmem_cache *s, void *x){
  page = virt_to_head_page -> 객체가 들어있던 첫 페이지

  // page->slab_cache 가 s 와 같거나 루트페이지라면 리턴
  cachep = page->slab_cache;
  if (slab_equal_or_root(cachep, s))
    return cachep;

  // 그렇지 않다면 경고 출력후 인자로 받았던 s 리턴
  return s;
}

// 슬랩 객체인 x 를 page 로 바꾸고 compound page 인경우 헤드 페이지를 리턴
// compound page 는 두개이상의 페이지를 하나의 페이지처럼 사용할 때 사용했었음
virt_to_head_page

4-144 p474 slab_free

slab_free(s, virt_to_head_page(x), x, NULL, 1, _RET_IP_);

static __always_inline void slab_free(
struct kmem_cache *s, struct page *page, void *head, void *tail, int cnt, unsigned long addr) {
  slab_free_freelist_hook(s, head, tail); // 디버깅 옵션에 따라 후킹 함수를 호출 (slab 에 소멸자가 없긴할거같음, 소멸자와 다른걸로 보임)

if (likely(page == c->page)) { // fastpath 로  바로 cpucache->page 에 등록
  freelist -> head ~ tail -> prev freelist

// 그렇지 않은 경우 slow path
__slab_free(s, page, head, tail_obj, cnt, addr);
}

4-145 p474 __slab_free 1/2

  • slowpath 로 슬랩객체 해제
// page 가 cpu cache 의 page 가 아닌 상태에서 이 함수에 들어왔음
static void __slab_free(struct kmem_cache *s, struct page *page,
            void *head, void *tail, int cnt,
            unsigned long addr)

// 결국 인자로 들어온 페이지로 들어가게 된다.
// frozen 되지 않은 페이지는 kmem_cache 에서 관리하지 않는 슬랩 페이지 이거나 노드에서 관리하는 슬랩 페이지 이다.

prior : 이전의

// 다 쓴 페이지나, 하나도 안쓰는 페이지일때 옮겨갈 수 있는걸로 보임.
// 이미 frozen 이었던애는 신경 안써도 됨
if ((!new.inuse || !prior) && !was_frozen) {
  // 1. s 가 percpu 캐시이고
  // 2. freelist 에 객체가 없다면(?) -> 빈 객체가 없다 -> 그럼 사용중이라는것..?!
  // pcpu cache 로 사용하면 되니까 frozen 시키자
  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;
// 만약 pcpu 캐시가 아니거나, -> 얘는 frozen 안되는게 명확
// 빈객체가 있다면 -> 사용중인 객체가 있는 partial 상태..?
} else { /* Needs to be taken off a list */
  n = get_node(s, page_to_nid(page));
  /*
  * 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);
  }
}
PageSlab

#define TESTPAGEFLAG(uname, lname, policy)                \
static __always_inline int Page##uname(struct page *page)        \
    { return test_bit(PG_##lname, &policy(page, 0)->flags); }

4-146 p477 __slab_free 2/2

  • p479 그림을 잘 보자
if 옮겨가야할 노드가 없던 경우 {
  !was_frozen && frozen -> cpu 캐시의 partial list 에 추가
  put_cpu_partial(s, page, 1);
} 

// 사용중인게 없는데, 노드의 partial list 로 못들어가는 경우
if (unlikely(!new.inuse && n->nr_partial >= s->min_partial))
    goto slab_empty;

// cpu partial list 를 못쓰고, 빈 객체가 없던 경우. -> node 의 partial 에서 아예 빠져있었단 얘기인것 같다
if (!kmem_cache_has_cpu_partial(s) && unlikely(!prior)) {
// node 의 partial list 에 추가


slab_empty:
// 버디로 보내자
node 의 partial 리스트에 들어있었다면 제거
    if (prior) {
        /*
         * Slab on the partial list.
         */
        remove_partial(n, page);
        stat(s, FREE_REMOVE_PARTIAL);
그게 아니었다면 full list 에 있었을거다?
    } else {
        /* Slab must be on the full list */
        remove_full(s, n, page);
    }

// 버디행
discard_slab(s, page);
static inline void remove_partial(struct kmem_cache_node *n,
                    struct page *page)
{
    lockdep_assert_held(&n->list_lock);
    list_del(&page->lru);
    n->nr_partial--;
}

static void remove_full(struct kmem_cache *s, struct kmem_cache_node *n, struct page *page)
{
    if (!(s->flags & SLAB_STORE_USER))
        return;

    lockdep_assert_held(&n->list_lock);
    list_del(&page->lru);
}

4-147 p480 put_cpu_partial

put_cpu_partial(s, page, 1);

static void put_cpu_partial(struct kmem_cache *s, struct page *page, int drain)

struct page {
  int pages;    /* Nr of partial slabs left */
  int pobjects;    /* Approximate # of objects */
}

struct kmem_cache {
  int cpu_partial;    /* Number of per cpu partial objects to keep around */
}

// partial list 의 첫 페이지를 가져온다
oldpage = this_cpu_read(s->cpu_slab->partial);
if (oldpage) {
  pobjects = oldpage->pobjects;
  pages = oldpage->pages;

drain 옵션이 켜져있을 때, 빈객체의 수가 cpu_cache 에서 관리할 수 있는 객체의 수보다 커지면 ->cpu_cache 의 partial 을 모두 node 로
if (drain && pobjects > s->cpu_partial) {
  unfreeze_partials(s, this_cpu_ptr(s->cpu_slab));

// partial list 의 다음 페이지는 next 에서 관리한다
page->next = oldpage;

add_partial

lockdep_assert_held -> dep: dependency : 이미 락이 잡혀있는지 디버깅때 확인

node partial 리스트의 page->lru head 또는 tail 에 추가

4-148 p483 remove_partial

node partial 리스트에서 제거

4-149, 4-150 p483 unfreeze_partials 1,2

  • cpu 캐시의 partial list 에 있는 모든 page 를 node 의 partial list tail 에 추가. 만약 노드에서도 초과되면 버디로 돌려줌
c->partial 리스트를 순회 {
  page 의 node 를 획득하고 node 의 list 에 lock

  frozen 만 0으로 변경 -> 노드로 들어가기때문

  // 만약 inuse 가 0인데 n->nr_partial >= s->min_partial 이면 discard_page 로 리스트를 관리
  // 그게 아니라면 노드로 들어감
  if (unlikely(!new.inuse && n->nr_partial >= s->min_partial)) {
    page->next = discard_page;
    discard_page = page;
  } else {
    add_partial(n, page, DEACTIVATE_TO_TAIL);
    stat(s, FREE_ADD_PARTIAL);
  }
}

여기서 discard 된 애들이 있다면 버디로 돌아감
댓글