티스토리 뷰
20241207 4.9.2 per-cpu (p516) first_chunk 할당 과정
clucle 2024. 12. 7. 21:58- first chunk 의 맵 엔트리 관리
map 으로 관리하는데, 코드를 봐야 명확한 개념을 알 수 있을 것 같다.
struct pcpu_chunk {
int* map
}
dchunk->map[0] = 1;
dchunk->map[1] = pcpu_reserved_chunk_limit;
dchunk->map[2] = (pcpu_reserved_chunk_limit + dchunk->free_size) | 1;
per-cpu chunk 할당 방식
embed 방식
large page 를 사용하여 tlb 캐시 효율을 올리는 방식. NUMA 는 각 노드 메모리 별로 할당
lowmem 영역을 사용 (ZONE_NORMAL)page 방식
vmalloc 영역에 최소 페이지 단위로 페이지 할당하여 매핑. chunk 를 노드별로 나누지 않음
per-cpu 정적 선언 및 저장 섹션
커널 이미지에 .data..percpu 에 들어간다
디멘드 페이징이란?
first chunk 는 커널이 초기화 될 때, 메모리를 할당받아 구성하고 매핑된다.
추가되는 chunk 들은 슬럽 메모리 할당자가 동작한 후 디맨드 페이징을 이용한다.
pcpu_chunk 구조체에, chunk 영역만 지정하고, 실제 물리 메모리에서 페이지를 할당받지 않는다.
-> 실제로 필요할 때 할당?
embed & paging 방식에 관계 없이 추가되는 chunk 는 vmalloc 영역에 설정된다.
- embed 방식은 vmalloc 영역의 위에서 아래 방향으로 (first chunk 는 lowmem 영역)
- paging 방식은 vmalloc 영역의 아래에서 위 방향으로 (first chunk 는 vmalloc 의 아래)
또한 chunk 는 모든 possible cpu 에 대해 하나의 chunk 로 구성한다.
per-cpu 의 NUMA 지원
static const int *pcpu_unit_map __read_mostly; /* cpu -> unit */
근데 unit 이 뭐지?
4.9.2 per-cpu 초기화
.data..percpu 를 first chunk 로 복사하자.
4-162 mm/percpu.c setup_per_cpu_areas
first chunk 구성하기
프로세서 구조 : UP(Uniprocessor) / SMP(Symmetric Multiprocessing) 에 따라 구현이 달라짐. (아예 별도도 구성 가능)
UP 버전
- 시스템의 per-cpu. 1개의 그룹과 1개의 유닛만 구성
pcpu_alloc_alloc_info(int nr_groups, int nr_units) -> 1, 1 로 호출
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[];
};
// 초기화시 unit 을 NR_CPUS 로 지정
for (unit = 0; unit < nr_units; unit++)
ai->groups[0].cpu_map[unit] = NR_CPUS;
// fc = first chunk
fc = memblock_virt_alloc_from_nopanic(unit_size,
PAGE_SIZE,
__pa(MAX_DMA_ADDRESS));
// size, align, limit 으로 unit_size 를 align 한 메모리 공간 할당 받기
if (limit && bdata->node_min_pfn >= PFN_DOWN(limit))
break;
pcpu_setup_first_chunk(ai, fc) // 코드 4-170
SMP 버전
PERCPU_MODULE_RESERVE : DEFINE_PER_CPU 를 커버할 수 있는 크기.
PERCPU_DYNAMIC_RESERVE : first chunk 에 dynamic per-cpu 할당 할 수 있는 크기.
unit 은 chunk 하나에 cpu 별로 나눠지는 영역을 뜻하는 듯 하다.
pcpu_embed_first_chunk 에서 first chunk 를 구성 // 코드 4-164
delta = (unsigned long)pcpu_base_addr - (unsigned long)__per_cpu_start;
for_each_possible_cpu(cpu)
__per_cpu_offset[cpu] = delta + pcpu_unit_offsets[cpu];
4-164 mm/percpu.c pcpu_embed_first_chunk
embed 방식으로 large 페이지 구성
int __init pcpu_embed_first_chunk(
size_t reserved_size, // 모듈에서 사용하는 reserved 영역의 크기
size_t dyn_size, // dynamic 영역의 크기. ARM64 -> 28K
size_t atom_size, // 할당 최소 크기
pcpu_fc_cpu_distance_fn_t cpu_distance_fn, // cpu 간 distance 를 구하는 함수 포인터. NUMA 의 노드 그룹에 따라 사용. ARM64 -> null
pcpu_fc_alloc_fn_t alloc_fn, // per-cpu 페이지 할당 함수
pcpu_fc_free_fn_t free_fn // per-cpu 페이지 free 함수
)
pcpu_build_alloc_info // pcpu 할당 정보 구성. 코드 4-166
size_sum = ai->static_size + ai->reserved_size + ai->dyn_size; // chunk 의 사이즈
areas_size = PFN_ALIGN(ai->nr_groups * sizeof(void *)); // node 그룹별로 pcpu 의 주소를 저장하기 위함. areas[] 의 사이즈
for group {
// group 당 한번 할당
ptr = alloc_fn(cpu, gi->nr_units * ai->unit_size, atom_size);
areas[group] = ptr;
base = min(ptr, base); // -> 그룹중 가장 낮은 곳
}
// alloc_fn 기본값
static void * __init pcpu_dfl_fc_alloc(unsigned int cpu, size_t size,
size_t align)
{
return memblock_virt_alloc_from_nopanic(
size, align, __pa(MAX_DMA_ADDRESS));
}
for group ; {
안쓰는 cpu 영역 해제
쓰는 cpu 라면 unit_size 에서 안쓰는 부분만큼 해제
}
for group ; {
max distance = group 별 base
}
max distance += ai -> unit_size(first chunk 를 위함) // 75% 넘는 경우 경고. 옵션에 따라 사용 못함 -> rc 가 null 로 리턴되면 page 방식 사용
*여기가 unit_size 만큼 더하는게 아니라 unit 을 곱했어야함. 리눅스 6에선 패치되어있음을 확인*
embed 방식이 위에서 아래로 할당하는 이유도, 전에 할당 취소를 많이 했었기 때문에 빈공간 검색이 용이
이후 free 에서 임시로 사용했던 구조체들을 free 시킨다
4-166 mm/percpu.c pcpu_build_alloc_info
- pcpu chunk 할당 정보 구성
- upa : unit per allocation
- 4K page 로는 하나의 유닛을 만들 수 없음. 보통 수십 KB가 필요
- 2M huge page 등을 처리할 때 효과적인 유닛의 수를 정함
atom_size: 각 CPU에 할당될 최소 단위의 메모리 크기
size_sum : static + reserve + dyn
min unit = min(size_sum, PCPU_MIN_UNIT_SIZE);
alloc_size = min unit 사이즈를 atom_size 배수로 올림 처리
upa = alloc_size / min_unit_size
atom_size 가 4K 라면 upa = 1, max_upa =1 이 되겠지만
예를들어 2M, 16M 이 된다면, upa 가 커짐.
offset_in_page(alloc_size / upa) 유닛 하나가 페이지 단위로 떨어지는 경우
max_upa = upa 를 하나씩 줄여가며, 유닛크기가 4K 페이지의 2 의 n 승이 되도록 조절
4-167
cpu_distance_fn
group_map[tcpu] == group
if (group_map[tcpu] == group && cpu_distance_fn && // target cpu 가 마지막으로 검사한 그룹과 같으면 체크
(cpu_distance_fn(cpu, tcpu) > LOCAL_DISTANCE || // cpu_distance_fn 없으면 cpu 갯수가 group 이 됨
cpu_distance_fn(tcpu, cpu) > LOCAL_DISTANCE)) {
group++; // -> 다음 그룹인 애는 같은 group 이 없으니 여기서 다음에 새로 등록됨
groupmap 에는 group 이, group_cnt 에는 그룹의 카운트를 등록
// best upa 를 max upa 에서 1씩 깍으며 찾음
1. max_upa 찾듯이, page offset 체크
2. 위에서 설정한 group_cnt 를 upa 로 나눈 뒤 round up 하면 할당 요청한 페이지 수 (this_alloc)
3. 이 페이지에서 사용하지 않는 유닛은 wasted 에 더한뒤 너무 많으면 스킵한다
nr_cpu = 8 일때, wasted 의 합이 2를 넘어가면 안됨
alloc_size = 2M
upa | group_cnt | this_allocs | wasted |
---|---|---|---|
32 | (4,4) | (1,1) | (28,28) 56 > 2(x) |
16 | (4,4) | (1,1) | (12,12) 24 >2 (x) |
8 | (4,4) | (1,1) | (4,4) 8 >2 (x) |
4 | (4,4) | (1,1) | (0,0) 0 =< 2 (o) |
2 | (4,4) | (2,2) x 여기서 루프 종료. 4가 best | (0,0) |
4-167
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[];
};
ai 구조
|------필드들------|-group[0]-|-group[1]-|-..group[nr_groups -1]-|-cpu_map[0]-|-cpu_map[1]-|-...cpu_map[nr_unit - 1]-|
cpu_map[0] 이 group[0] 의 cpu_map 포인터
for (group = 0; group < nr_groups; group++) {
ai->groups[group].cpu_map = cpu_map;
cpu_map += roundup(group_cnt[group], upa);
}
그래서 결국 cpu_map 에 각 그룹별로 점프뛰면서 unit -> cpu 를 가리키게 됐음
for (group = 0, unit = 0; group_cnt[group]; group++) {
struct pcpu_group_info *gi = &ai->groups[group];
gi->base_offset = unit * ai->unit_size;
for_each_possible_cpu(cpu)
if (group_map[cpu] == group)
gi->cpu_map[gi->nr_units++] = cpu;
gi->nr_units = roundup(gi->nr_units, upa);
unit += gi->nr_units;
}
이 정보를 가지고 가서, 결국 노드 그룹별로 unit 묶음인 chunk 들을 만들테고, 이중 사용하지 않는건 코드 4-165 의 pcpu_embed_first_chunk 에서 free 하나보다..! 노드별로 locality 를 위해 큰 덩어리로 할당하는듯. 그래서 gi->nr_units 를 지정하고 그만큼씩 할당한다.
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 만큼 만들었는데, 이 인덱스가 뭔지는 아직 모름
group_offsets[group] = gi->base_offset
group_sizes[group] = gi->nr_unit * ai->unit_size
for (group 의 nr_unit) {
cpu = gi->cpu_map[i];
unit_map[cpu] = unit + i; // cpu 에 unit 을 매핑
unit_off[cpu] = gi->base_offset + i * ai->unit_size; // cpu 가 사용중인 unit 의 offset 지정
/* determine low/high unit_cpu */
// set min, max offset
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; // upa 에 따라 cpu 갯수와 다를 수 있음
// 각종 전역 변수들 설정
pcpu_chunk_struct_size = sizeof(struct pcpu_chunk) +
BITS_TO_LONGS(pcpu_unit_pages) * sizeof(unsigned long); // populated 배열 까지 합쳐 배열 합산
struct pcpu_chunk {
..
unsigned long populated[]; /* populated bitmap */
};
//
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]);
// 이게 무슨뜻이지? chunk 리스트를 관리하는 최대 슬롯수 + 2
pcpu_nr_slots = __pcpu_size_to_slot(pcpu_unit_size) + 2;
// ai->unit_size = alloc_size / upa; -> 유닛 하나당 사이즈
pcpu_unit_pages = ai->unit_size >> PAGE_SHIFT; // 유닛 하나에 필요한 page
pcpu_unit_size = pcpu_unit_pages << PAGE_SHIFT; // 유닛 하나에 필요한 메모리양
__pcpu_size_to_slot = chunk 리스트를 관리하는 최대 슬롯수 ?
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); // +2 를 하는이유는 좀더 넉넉한 슬롯을 위해서
}
--> 다음주에 다시보자 이뇨속!
'개발 > 코드로 알아보는 ARM 리눅스 커널 TIL' 카테고리의 다른 글
20241221 4.9.5 per-cpu 동적 할당 (1) | 2025.01.04 |
---|---|
20241214 4.9.3 per-cpu first_chunk 구성 & 초기화 & 동적할당 (1) | 2024.12.21 |
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 |
20241116 4.7.5슬랩 페이지와 슬랩 객체 할당 (p442) (1) | 2024.11.21 |
- Total
- Today
- Yesterday
- 봄날에 스케치
- 우리는 vr핏이라고 부릅니다
- 영상 픽셀화 하기
- cockroach db
- Quest2
- vrpit
- it's called a vrpit
- 잘못된 빨간줄
- 카카오
- mysql
- C++
- boost
- chrome-extension
- Reciprocal n-body Collision Avoidance
- set value
- print shared_ptr class member variable
- Obstacle Avoidance
- 클래스 맴버 변수 출력하기
- SuffixArray
- 면접
- ad skip
- shared_from_this
- RVO
- 에러 위치 찾기
- vr핏
- Golang
- Visual Studio
- hole-punching
- 코어 남기기
- red underline
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |