티스토리 뷰

오늘 생각한것

그러니까 이게 무슨 개념이냐면 어떤 유닛에 static & reserve 영역에 진입할꺼면 전역 schunk 를 참조하고, dynamic 영역에 진입할꺼면 dchunk 를 참조하는 개념인 것 같다! 노드 하나당 chunk 를 하나씩 가지는데, enbedding 방식을 사용하면 연속된 공간에 잡는 것 같다. 그래서 이걸 그룹이라고 묶으면 해당 그룹안에는 cpu 가 하나당 하나의 유닛이 구성되고 first chunk 는 (static + (reserve) + dynamic) * unit 이 된다. schunk dchunk 는 이 first chunk 에 잇는 유닛의 정보를 사용할 때 쓰는듯 (공용 구조체같은 느낌! base addr 에 각 cpu 를 더해서 쓸것만 같아)

4.9.3 first chunk 구성

  • reserved 영역이 지정되지 않는 경우 schunk 에 static+dynamic 만 구성
  • reserved 영역이 지정되는 경우 schunk 는 static + dynamic 으로, dchunk 는 dynamic 으로 구성

4-170 4-171 mm/percpu.c pcpu_setup_first_chunk

  • smap, dmap 을 PERCPU_DYNAMIC_EARLY_SLOT 만큼 만들었는데, 이 인덱스가 뭔지는 아직 모름
  • pcpu_slot : 요청온 사이즈에 맞는 청크를 찾기 위함. 그래서 유닛 하나당 사이즈를 가지고 만들 수 있는 인덱스의 리스트만 초기화 한 듯 하다. -> p551 을 참고하자
PCPU_SLOT_BASE_SHIFT = 5 (32 bytes 단위).
요청 크기 = 128 bytes, 512 bytes

static int __pcpu_size_to_slot(int size)
{
    int highbit = fls(size);    /* size is in bytes */
    return max(highbit - PCPU_SLOT_BASE_SHIFT + 2, 1);
}

128
fls(128) = 8
slot = max(8 - 5 + 2, 1) = 5
slot[5] 에 해당하는 chunk 에서 처리

512
fls(128) = 10
slot = max(10 - 5 + 2, 1) = 7
slot[7] 에 해당하는 chunk 에서 처리
struct pcpu_group_info {
    int            nr_units;    /* aligned # of units */
    unsigned long        base_offset;    /* base address offset */
    unsigned int        *cpu_map;    /* unit->cpu map, empty
                         * entries contain NR_CPUS */
};

struct pcpu_alloc_info {
    size_t            static_size;
    size_t            reserved_size;
    size_t            dyn_size;
    size_t            unit_size;
    size_t            atom_size;
    size_t            alloc_size;
    size_t            __ai_size;    /* internal, don't use */
    int            nr_groups;    /* 0 if grouping unnecessary */
    struct pcpu_group_info    groups[];
};
    // group (node 의 pcpu 정보) 를 순회
    for (group = 0, unit = 0; group < ai->nr_groups; group++, unit += i) {
        const struct pcpu_group_info *gi = &ai->groups[group];

        group_offsets[group] = gi->base_offset;
        group_sizes[group] = gi->nr_units * ai->unit_size;

        for (i = 0; i < gi->nr_units; i++) {
            cpu = gi->cpu_map[i];
            if (cpu == NR_CPUS)
                continue;

            // cpu 를 가지고 unit 과 unit offset 을 알수 있도록 매핑
            unit_map[cpu] = unit + i;
            unit_off[cpu] = gi->base_offset + i * ai->unit_size;

            /* determine low/high unit_cpu */
            // 가잔 offset 이 높/낮은 cpu 설정
            if (pcpu_low_unit_cpu == NR_CPUS ||
                unit_off[cpu] < unit_off[pcpu_low_unit_cpu])
                pcpu_low_unit_cpu = cpu;
            if (pcpu_high_unit_cpu == NR_CPUS ||
                unit_off[cpu] > unit_off[pcpu_high_unit_cpu])
                pcpu_high_unit_cpu = cpu;
        }
    }

    // 유닛 카운트
    pcpu_nr_units = unit;

     // 각 CPU가 사용할 사이즈 (alloc_size / upa) >> PAGE_SIFT = 유닛 하나가 사용할 페이지 수
    pcpu_unit_pages = ai->unit_size >> PAGE_SHIFT;
    pcpu_unit_size = pcpu_unit_pages << PAGE_SHIFT;
    // per-CPU 메모리 할당의 최소 단위 크기(atomic allocation size).
    pcpu_atom_size = ai->atom_size;

// struct pcpu_chunk {
//   ..
//   unsigned long        populated[];    /* populated bitmap */
// };

    // 페이지마다 populated 라는걸 사용하는 듯
    pcpu_chunk_struct_size = sizeof(struct pcpu_chunk) +
        BITS_TO_LONGS(pcpu_unit_pages) * sizeof(unsigned long);

    /*
     * Allocate chunk slots.  The additional last slot is for
     * empty chunks.
     */
    // cpu 하나의 유닛 사이즈를 가지고 chunk slot 의 갯수를 구한다음에, 그만큼 pcpu_slot 을 만든다.
    pcpu_nr_slots = __pcpu_size_to_slot(pcpu_unit_size) + 2;
    pcpu_slot = memblock_virt_alloc(
            pcpu_nr_slots * sizeof(pcpu_slot[0]), 0);
    for (i = 0; i < pcpu_nr_slots; i++)
        INIT_LIST_HEAD(&pcpu_slot[i]);

4-172 mm/percpu.c pcpu_setup_first_chunk

그림 551 을 보면 알겠지만 schunk, dchunk 는 (static 영역 & reserve 영역 & dynamic 영역) 을 구분하는게 아니라, 저 영역은 유닛이 first chunk 일 때 갖고 있는거. 이거랑 일단 헷갈리지만 말아보자.

앞으로 여러 chunk 들이 생길텐데, first chunk & 그 외의 일반 chunk & empty chunk 가 존재한다.

일반 chunk 들은 struct pcpu_chunk 하나로 관리하지만, first chunk 의 경우 reserve 영역에 따라 아래같이 사용한다.

  • reserve 있을 때 : pcpu_first_chunk (dcunk) & pcpu_reserved_chunk (schunk)
  • 1을 or 하는 것은 사용중이라는 표시, 1번인덱스는 사용하고 있지 않기때문에 | 1 을 안했음
schunk->map = {1, reserve 시작 지점, reserve 끝 지점 | 1  }
dchunk->map = {1, dynamic 시작 지점, dynamic 끝 지점 | 1  }
  • reserve 없을 때 : pcpu_first_chunk (schunk)
schunk->map = {1, dynamic 시작 지점, dynamic 끝 지점 | 1  }

그러니까 이게 무슨 개념이냐면 어떤 유닛에 static & reserve 영역에 진입할꺼면 전역 schunk 를 참조하고, dynamic 영역에 진입할꺼면 dchunk 를 참조하는 개념인 것 같다!

bitmap_fill(schunk->populated, pcpu_unit_pages); // 모든 페이지가 활성화 (왜냐면 first chunk 에 있는 유닛 하나는 모든 페이지를 활성화)

4-173 mm/percpu.c pcpu_count_occupied_pages

  • map 의 인덱스를 넣어서 사용하는 공간의 페이지 수를 구한다.
  • 아래 코드는 인자로 인덱스 1을 넣었기 때문에, dynamic 영역의 페이지 수를 구한다. (pcpu_first_chunk 는 결국 1~2 에 dynamic 영역)
pcpu_count_occupied_pages(pcpu_first_chunk, 1);

// 위 아래로 사용 가능한 영역이었다면 (free 영역인 경우 &1이 아닌거) 그쪽껄 가져와서 사용하는데,
// 못가져오는 경우(in-use 영역인 경우 &1 인거)면 좁혀서 페이지를 센다
return max_t(int, PFN_DOWN(end) - PFN_UP(off), 0);

4-174 mm/percpu.c pcpu_chunk_relocate

  • chunk 를 적절한 slot 의 리스트로 변경해준다. freesize 로 slot 을 가져오고 현재 위치랑 바꿈. 예를들어 freesize 가 작아졌으면 작은 인덱스로 옮기는 식
  • pcpu_reserved_chunk 일때는 이미 예약된 것이니 넣지 않는다. (static + reserve 는 별도로 쓸 수 없으니까, reserve 가 있을때만 reserve chunk 가 요거로 지정됨) -> pcpu_alloc 에서도 reserved bool flag 를 받아서 이걸 사용
  • 슬롯이 사이즈로 chunk 를 가리키게 되는데, 그러면 만약에 slot 으로 chunk 를 찾으면 내 cpu 의 유닛이 없을수도 있을듯. 뒤에 아마 cpu 가 사용하는 코드 보면 알겠다. --> 그게 아니라 어차피 모든 청크에 똑같이 생겨야 하는 거 같다
struct pcpu_chunk {
    struct list_head    list;        /* linked to pcpu_slot lists */
}

// Note that the reserved chunk is never put on chunk slots.
static void pcpu_chunk_relocate(struct pcpu_chunk *chunk, int oslot)
{
    // o : old, n : new
    int nslot = pcpu_chunk_slot(chunk);

    if (chunk != pcpu_reserved_chunk && oslot != nslot) {
        if (oslot < nslot) // 더 커졌으므로 사용하기 좋은 상태라 list 앞에넣고
            list_move(&chunk->list, &pcpu_slot[nslot]);
        else // 더 작아졌으므로 사용하기 어려우니까 tail 에다가 붙이나? 잘 모르겠네
            list_move_tail(&chunk->list, &pcpu_slot[nslot]);
    }
}

pcpu_free_alloc_info -> 설정시 사용했던 구조체 해제
pcpu_schedule_balance_work -> free chunk 페이지 및 페이지 할당(populate) 수를 관리

4-174-2 mm/percpu.c percpu_init_late(void)

  • pcpu_first_chunk 와 pcpu_reserved_chunk 할당에 사용했던 map 이 static int 배열이었는데, 이를 새로 할당받은 메모리로 복사한다.

4.9.5 per-cpu 동적 할당

pcpu 를 동적으로 할당하기

4-175 4-176 mm/percpu.c pcpu_alloc 1 2

할당받았던 map 확장이 필요하면 확장하고, pcpu 에서 area 할당하고 필요한 페이지 받는 정도

reserve 일때 pcpu reserve chunk 에서 받음 / 아닌경우는 size slot 부터 키워가면서 chunk 찾음

4-177 mm/percpu.c pcpu_alloc 3

chunk 를 만들 때 모든 cpu unit 에 같은 offset 을 가질 수 있도록 페이지를 할당하는게 맞는건지 좀 어려움

static struct pcpu_chunk *pcpu_create_chunk(void)
{
    const int nr_pages = pcpu_group_sizes[0] >> PAGE_SHIFT;
    pages = alloc_pages(GFP_KERNEL, order_base_2(nr_pages));
atomic 이 아니고 slot 에 chunk 가 없을때 chunk 를 새로 생성

pcpu_populate_chunk : 실제 페이지를 생성후 vmap 에 mapping

pcpu_chunk_populated : 할당받은 시작 pfn ~ 끝 pfn 까지 populate 플래그 설정

4-178 mm/percpu.c pcpu_alloc 4

populate 된 빈 페이지가 너무 작아지면, atomic 요청이 올때를 대비해 populate 해둔다

// cpu offset 마다 clear 해둔다
/* clear the areas and return address relative to base address */
for_each_possible_cpu(cpu)
    memset((void *)pcpu_chunk_addr(chunk, cpu, 0) + off, 0, size);

// atomic 요청이 실패하면 rebalancing 을 한번 돌린다
if (is_atomic) {
    /* see the flag handling in pcpu_blance_workfn() */
    pcpu_atomic_alloc_failed = true;
    pcpu_schedule_balance_work();
}

4-179 mm/percpu.c pcpu_need_to_extend

pcpu_map_extend_workfn : map 이 부족해지면 2배씩 늘리기

is_atomic 인 경우 3개의 map entry 가 필요.

  • 마지막 주소
  • 맵 엔트리에 size 추가시 align 만큼 공간이 남는 경우, size 영역과 남는 공간에 대한 엔트리

is_atomic 인 경우 사용 + ATOMIC_MARGIN_LOW < 할당 인 경우 미리 맵 확장 함수를 스케줄링

is_atomic 아닌 경우는 더 큰 배열을 확보하기 위해 margin 64 로 설정

그리고 모자라면 2배 늘린값을 리턴

댓글