티스토리 뷰

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)) {
댓글