티스토리 뷰
4.9.5 per-cpu 동적 할당
4-180 mm/percpu.c pcpu_alloc_area
- chunk 의 free entry 를 순회
- 정렬들의 이유로 빈 공간이 생기는데 이를 head 라고 함 (실제로 사용하는지는 아직 모름)
- entry 에서 사용하고 남은 공간을 tail 이라고 함
struct pcpu_chunk {
int map_used; /* # of map entries used before the sentry */
int *map; /* allocation map */
int first_free; /* no free below this */
};
// map 에는 entry 의 주소들이 담겨있음
for (i = chunk->first_free, p = chunk->map + i; i < chunk->map_used; i++, p++) {
if head < sizeof (int) || !(p[-1] && 1) // head 가 int 보다 작거나 이전 entry 가 사용중이지 않을때
*p = off += head // p 가 원래는 map 에 있는 값을 가리켰는데 head 만큼 더해짐
if (p[-1] & 1) chunk->free_size -= head; // 이전 chunk 가 사용중이라면 내 청크에서 head 만큼 뺌
else max_contig = max(*p - p[-1], max_contig) // 이전 chunk 가 사용중이지 않았다면, 이전 chunk 가 head 만큼 사용할 수 있다고 생각하는듯?
tail = this_size - head - size // head 는 어차피 0임. entry 의 size 에서 내가 사용할 size 를 남은 공간이 되고 이를 tail 이라 하는듯
if (tail < sizeof((int)) {
tail = 0;
size = this_size - head; // tail 이 너무 작으면 entry 를 안쪼개나 보다. 그래서 size 를 그냥 크게 하는듯
}
4-181 mm/percpu.c pcpu_alloc_area 2
- head 나 tail 남아있으면 쪼개기
- head : 앞에 entry 가 사용중이고 head 가 충분히 클때만 남아있음
- tail : 남은 공간이 충분히 클 때만 남아 있음
- occ : occupied
// p 가 map 을 가리키고 있었으니, map 을 nr_extra 만큼 뒤로 미룸
memmove(p + nr_extra + 1, p + 1, sizeof(chunk->map[0]) * (chunk->map_used - i));
chunk->map_used += nr_extra;
// 순회 후 힌트값 설정
if (i + 1 == chunk->map_used) chunk->contig_hint = max_contig; /* fully scanned */
else chunk->contig_hint = max(chunk->contig_hint, max_contig);
chunk->free_size -= size;
*p |= 1; // entry 를 사용중으로 변경
*occ_pages_p = pcpu_count_occupied_pages(chunk, i); // 방금 지정한 영역의 페이지 수 반환
사이즈가 변경됐으니 chunk slot 을 변경
4-182 mm/percpu.c pcpu_alloc_area
- offset 에 align 을 맞춘값을 head 로 설정하고 head + size 가 area 에 들어가는지 확인
- pop_only 가 false인경우 head 크기를 return
- pop_only 가 true 인 경우 활성화된 페이지 내에서 적합한 공간을 계속 루프를 돌며 알아냄
struct pcpu_chunk {
unsigned long populated[]; /* populated bitmap */
};
static void __maybe_unused pcpu_next_unpop(struct pcpu_chunk *chunk, int *rs, int *re, int end)
{
// rs = rs ~ end 까지 populated 배열에서 0인걸 찾음
// re = rs + 1 ~ end 까지 1인걸 찾음 // -> 그러니까 [rs, re) 는 populate 되지 않음
*rs = find_next_zero_bit(chunk->populated, end, *rs);
*re = find_next_bit(chunk->populated, end, *rs + 1);
}
static int pcpu_fit_in_area {
while (true) {
page_start = PFN_DOWN(head + off); // 시작위치 + head
page_end = PFN_UP(head + off + size); // 시작위치 + head + 요청 사이즈
rs = page_start;
pcpu_next_unpop(chunk, &rs, &re, PFN_UP(off + this_size));
// -> 그러니까 [rs, re) 는 populate(활성화) 되지 않음
if (rs >= page_end)
return head; // 이미 모두 활성화 된 상태
// 비활성화된 페이지의 끝으로 off 를 변경, 왜냐면 re 는 활성화 페이지임 [rs,re) 가 비활성화이기 때문
// 활성화된 페이지에서만 head 를 구하고 싶구나
cand_off = re * PAGE_SIZE;
}
}
4-183 mm/percpu-vm.c pcpu_populate_chunk
- chunk 내에 요청된 page 를 활성화
// cpu 당 chunk page 에 접근
static int __maybe_unused pcpu_page_idx(unsigned int cpu, int page_idx)
{
return pcpu_unit_map[cpu] * pcpu_unit_pages + page_idx;
}
static struct page **pcpu_get_pages(struct pcpu_chunk *chunk_alloc) {
size_t pages_size = pcpu_nr_units * pcpu_unit_pages * sizeof(pages[0]); // 모든 유닛 페이지 의 구조체(struct page)를 한번에 할당.
}
static int pcpu_alloc_pages(struct pcpu_chunk *chunk,
struct page **pages, int page_start, int page_end)
{
for_each_possible_cpu(cpu) {
for (i = page_start; i < page_end; i++) {
struct page **pagep = &pages[pcpu_page_idx(cpu, i)];
*pagep = alloc_pages_node(cpu_to_node(cpu), gfp, 0); // node 에서 페이지 할당
}
}
}
4-184 mm/percpu-vm.c pcpu_map_pages
- 할당받은 페이지를 vmalloc 영역에 매핑
- page->index 가 pcpu_chunk 를 가리키도록 설정
for_each_possible_cpu(cpu) {
err = __pcpu_map_pages(pcpu_chunk_addr(chunk, cpu, page_start),
&pages[pcpu_page_idx(cpu, page_start)],
page_end - page_start);
if (err < 0)
goto err;
for (i = page_start; i < page_end; i++)
pcpu_set_page_chunk(pages[pcpu_page_idx(cpu, i)], chunk);
}
5 태스크 관리
5.1 태스크 표현
5.1.1 프로세스와 쓰레드의 차이 그리고 태스크
프로그램 : 컴파일된 객체
프로세스 : 실행중이거나 실행 대기중인 프로그램의 인스턴스. 하나 이상의 쓰레드로 이루어짐
커널관점에서는 프로세스와 쓰레드를 구분하지 않고 태스크라는 개념으로 처리한다.
5.1.2 태스크를 표현하는 자료구조
task struct 는 architecture 에 독립적인 구조로 include 에 위치
thread_struct, thread_info 는 architecture 의존적으로 arch 디렉토리에 위치
5.1.3 초기화 과정
thread_info_cache_init
thread_info 구조체의 슬랩 초기화. THREAD_SIZE 가 PAGE_SIZE 보다 작을때만 캐시를 생성
ARM 64 는 16KB 로 일반적인 경우 초기화 하지 않음 (페이지 크기가 64KB 이상일때만 필요해서 함수 구현이 깡통임)
fork_init
task_struct 구조체 용 슬랩캐시 생성
architecture 와 관련된 task cache 를 초기화
시스템의 최대 쓰레드 개수를 제한하는 max_threads 초기화
swapper 의 resource limit (rlimit) 초기화
5-2 kernel/fork.c set_max_threads
시스템에서 생성할 수 있는 최대 태스크 개수 설정
if (fls64(totalram_pages) + fls64(PAGE_SIZE) > 64)
threads = MAX_THREADS;
else
hreads = div64_u64((u64) totalram_pages * (u64) PAGE_SIZE, (u64) THREAD_SIZE * 8UL);
https://www.kernel.org/doc/html/v6.0/x86/kernel-stacks.html
THREAD_SIZE 각 쓰레드가 사용하는 stack 의 크기
$ cat /proc/sys/kernel/threads-max
63150
5-3 kernel/fork.c proc_caches_init
task 용 슬랩 캐시 초기화
5.2 PID 관리하기
5.2.1 PID 를 표현하는 자료구조
- 태스크를 식별하는 PID (0: swapper, 1: init, 2: kthreadd) 이후 1씩 증가
- pidmap 구조체는 pid namespace 마다 유지 관리 됨
// include/linux/pid.h
struct pid
{
atomic_t count; // 참조 카운트
unsigned int level; // pid 할당받은 namespace 의 레벨
/* lists of tasks that use this pid */
struct hlist_head tasks[PIDTYPE_MAX]; // pid 를 사용하는 task list
struct rcu_head rcu;
struct upid numbers[1]; // 구조체가 표현하는 정수형 pid, pid ns 계층 구조를 사용하는 경우 2개 이상 사용
// 리눅스 6버전에서는 numbers[] 임을 확인
};
// include/linux/pid_namespace.h
struct pidmap {
atomic_t nr_free; // 할당 가능한 pid 의 개수
void *page; // 비트맵으로 사용할 페이지
};
pid_namespace
- 가상화를 지원하는 시스템에서 호스트간 테스크 집합의 마이그레이션을 위해 도입. 같은 pid 더라도 ns 가 다름.
- namespace 는 계층 구조를 이룸
// include/linux/pid_namespace.h
struct pid_namespace {
struct kref kref;
struct pidmap pidmap[PIDMAP_ENTRIES]; // namespace 별로 pid 를 관리
..
}
upid
struct upid {
/* Try to keep pid_chain in the same cacheline as nr for find_vpid */
int nr; // 정수 pid
struct pid_namespace *ns; // pid namespace
struct hlist_node pid_chain; // pid 해시테이블에 연결될때 사용하는 필드
};
pid 해시 테이블
task 가 많아지면 pid로 task 를 찾기 힘드니 해시 테이블로 검색
{pid, &pid_namespace} -> upid. chain 방식으로 사용
5.2.2 PID 할당하기
5-4 kernel/pid.c alloc_pid
- child namespace 에서 만들어지더라도 struct pid 는 부모 ns 부터 만들어진다
pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL); // 슬랩 캐시에서 pid 구조체 획득
pid->level = ns->level; // pid 레벨을 namespace level 로 설정
tmp = ns
// 0으로 갈수록 root 의 namespace 저장
for (i = ns->level; i >= 0; i--) {
nr = alloc_pidmap(tmp); // 정수 pid
pid->numbers[i].nr = nr;
pid->numbers[i].ns = tmp;
tmp = tmp->parent;
}
// init process 라면 초기화 됐는지 검사
if (unlikely(is_child_reaper(pid))) {
if (pid_ns_prepare_proc(ns))
goto out_free;
}
get_pid_ns(ns); // 얘도 내부적으로 counter
atomic_set(&pid->count, 1); // counter 1 증가
for (type = 0; type < PIDTYPE_MAX; ++type)
INIT_HLIST_HEAD(&pid->tasks[type]); // pid 구조체를 사용하는 task 들 초기화 (PID, PGID, SID 로 사용)
upid = pid->numbers + ns->level; // numbers 가 배열에다가 level 을 더했으니 가장 child upid
for ( ; upid >= pid->numbers; --upid) {
hlist_add_head_rcu(&upid->pid_chain, &pid_hash[pid_hashfn(upid->nr, upid->ns)]); // hash table 에 연결
upid->ns->nr_hashed++;
}
return pid;
5-5 kernel/pid.c alloc_pidmap
ns 에서 관리하는 pidmap 에서 pid 를 할당하기. pid 값에 따라 pidmap 페이지가 달라짐. 필요한 경우 별도 페이지 할당이 필요.
#define PAGE_SHIFT 12 // 4KB 일때
#define PAGE_SIZE (_AC(1,UL) << PAGE_SHIFT) = 4096
#define BITS_PER_PAGE (PAGE_SIZE * 8) = 4096 * 8 (32768 개)
#define BITS_PER_PAGE_MASK (BITS_PER_PAGE - 1)
struct pid_namespace {
struct kref kref;
struct pidmap pidmap[PIDMAP_ENTRIES]; // namespace 별로 pid 를 관리
..
}
// namespace 에서 pid 할당
static int alloc_pidmap(struct pid_namespace *pid_ns) {
// 기본적으로는 last + 1 을 사용하고, max (32768) 를 넘어가는 경우RESERVED_PID 로 설정
pid = last + 1;
if (pid >= pid_max) pid = RESERVED_PIDS;
offset = pid & BITS_PER_PAGE_MASK;
pidmap = &pid_ns->pidmap[pid/BITS_PER_PAGE] // pidmap 구함
// DIV_ROUND_UP(pid_max, BIT_PER_PAGE) 라고하면 하나의 namespace 에 총 pidmap 의 페이지 수가 나오겠지?
// !offset 은 0인경우 1이 되고, 1인경우 0이됨 그래서 offset 이 있던 경우에는 페이지를 하나 빼는걸로 보임
// 1의 보수는 ~임 !는 not
max_scan = DIV_ROUND_UP(pid_max, BITS_PER_PAGE) - !offset;
for (i = 0; i <= max_scan; ++i) {
// pidmap 에 page 가 없는 경우 페이지 할당해서 설정
if (unlikely(!map->page)) {
'개발 > 코드로 알아보는 ARM 리눅스 커널 TIL' 카테고리의 다른 글
20250111 5.5 테스크 생성하기 ~ 5.7 idle 쓰레드 (swapper) (0) | 2025.01.18 |
---|---|
20250104 5.2.2 PID 할당 ~ 5.5 테스크 생성하기 (0) | 2025.01.11 |
20241214 4.9.3 per-cpu first_chunk 구성 & 초기화 & 동적할당 (1) | 2024.12.21 |
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 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- shared_from_this
- ad skip
- boost
- RVO
- set value
- it's called a vrpit
- 봄날에 스케치
- 코어 남기기
- Golang
- Obstacle Avoidance
- 클래스 맴버 변수 출력하기
- mysql
- vr핏
- 영상 픽셀화 하기
- hole-punching
- SuffixArray
- red underline
- Visual Studio
- print shared_ptr class member variable
- 카카오
- vrpit
- 우리는 vr핏이라고 부릅니다
- 잘못된 빨간줄
- cockroach db
- 면접
- Quest2
- chrome-extension
- C++
- 에러 위치 찾기
- Reciprocal n-body Collision Avoidance
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
글 보관함