티스토리 뷰
20241214 4.9.3 per-cpu first_chunk 구성 & 초기화 & 동적할당
clucle 2024. 12. 21. 15:00오늘 생각한것
그러니까 이게 무슨 개념이냐면 어떤 유닛에 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배 늘린값을 리턴
'개발 > 코드로 알아보는 ARM 리눅스 커널 TIL' 카테고리의 다른 글
20250104 5.2.2 PID 할당 ~ 5.5 테스크 생성하기 (0) | 2025.01.11 |
---|---|
20241221 4.9.5 per-cpu 동적 할당 (1) | 2025.01.04 |
20241207 4.9.2 per-cpu (p516) first_chunk 할당 과정 (0) | 2024.12.07 |
20241130 4.8 kmalloc 과 vmalloc (p487) ~ 4.9 per-cpu 할당자 (0) | 2024.12.07 |
20241123 4.7.6 슬랩 객체 할당 (p449) & 4.7.7 슬랩 객체 해제 (0) | 2024.11.30 |
- Total
- Today
- Yesterday
- 카카오
- 면접
- mysql
- Visual Studio
- Reciprocal n-body Collision Avoidance
- C++
- boost
- 클래스 맴버 변수 출력하기
- RVO
- 우리는 vr핏이라고 부릅니다
- 잘못된 빨간줄
- shared_from_this
- print shared_ptr class member variable
- cockroach db
- Golang
- 코어 남기기
- red underline
- 에러 위치 찾기
- vrpit
- chrome-extension
- 봄날에 스케치
- Quest2
- set value
- hole-punching
- ad skip
- Obstacle Avoidance
- vr핏
- it's called a vrpit
- 영상 픽셀화 하기
- SuffixArray
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |