Start_Kernel (Week 14)
Start_Kernel (Week 14)

Start_Kernel (Week 14)

카테고리
⚙️ Start Kernel
작성자
박용성박용성
작성일
2024년 08월 08일
태그
C
Linux
Slug
start-kernel-14
floatFirstTOC: right
 

함수 선언

extern void init_numa_balancing(unsigned long clone_flags, struct task_struct *p);

함수 정의

void init_numa_balancing(unsigned long clone_flags, struct task_struct *p) { int mm_users = 0; struct mm_struct *mm = p->mm; if (mm) { mm_users = atomic_read(&mm->mm_users); if (mm_users == 1) { mm->numa_next_scan = jiffies + msecs_to_jiffies(sysctl_numa_balancing_scan_delay); mm->numa_scan_seq = 0; } } p->node_stamp = 0; p->numa_scan_seq = mm ? mm->numa_scan_seq : 0; p->numa_scan_period = sysctl_numa_balancing_scan_delay; p->numa_migrate_retry = 0; /* Protect against double add, see task_tick_numa and task_numa_work */ p->numa_work.next = &p->numa_work; p->numa_faults = NULL; p->numa_pages_migrated = 0; p->total_numa_faults = 0; RCU_INIT_POINTER(p->numa_group, NULL); p->last_task_numa_placement = 0; p->last_sum_exec_runtime = 0; init_task_work(&p->numa_work, task_numa_work); /* New address space, reset the preferred nid */ if (!(clone_flags & CLONE_VM)) { p->numa_preferred_nid = NUMA_NO_NODE; return; } /* * New thread, keep existing numa_preferred_nid which should be copied * already by arch_dup_task_struct but stagger when scans start. */ if (mm) { unsigned int delay; delay = min_t(unsigned int, task_scan_max(current), current->numa_scan_period * mm_users * NSEC_PER_MSEC); delay += 2 * TICK_NSEC; p->node_stamp = delay; } }
 

함수 역할

  • numa scan과 관련 파라미터를 초기화한다.
    • NUMA scan은 새 태스크가 생성될 때 마다 실행됨
  • kernel/sched/fair.c에 위치
    • NUMA Balancing과 관련된 코드들이 모여있는 곳
 

언제 호출되는가?

새 프로세스를 만들 때, 즉 fork가 일어날 때 일어난다.
callstack
  • copy_process() -> sched_fork() ->  __sched_fork() -> init_numa_balancing()
 

파라미터

init_numa_balancing(unsigned long clone_flags, struct task_struct *p)
unsigned long clone_flags : _do_fork() 호출 시 전달되는 인자1가 여기에도 전달
struct task_struct *p : copy_process() 함수가 호출되었을 때
 
  • 복습, task_struct란?
리눅스의 PCB, 즉 프로세스와 관련된 데이터를 저장하는 자료구조. 프로세스(스레드)별로 하나 씩 만들어진다.
 
  • 복습, mm_struct란?
각 프로세스별로 할당된 주소공간과 메모리에 대한 정보를 관리

함수 코드 살펴보기

int mm_users = 0; struct mm_struct *mm = p->mm; if (mm) { mm_users = atomic_read(&mm->mm_users); if (mm_users == 1) { mm->numa_next_scan = jiffies + msecs_to_jiffies(sysctl_numa_balancing_scan_delay); mm->numa_scan_seq = 0; } }
 
if (mm)
mm이 null이 아닌경우, 실행. task_struct에서 mm이 null인 경우 → 태스크가 커널 스레드일 경우 mm이 null이라고 함. (자신만의 가상 유저 메모리 스페이스를 가지고 있지 않기 때문에)
 
if (mm_users == 1)
이 mm을 사용중인 태스크가 1개일 경우: 즉, 기존 mm을 활용한 것이 아니라, 새롭게 fork하여 만들어진 mm_struct인 경우
 
mm->numa_next_scan = jiffies + msecs_to_jiffies(sysctl_numa_balancing_scan_delay); numa_next_scan을 jiffies + scan_delay만큼으로 세팅, 즉 현재시간에서 scan_delay만큼의 시간 이후에 scan을 하겠다는 것.
 
jiffies란?
리눅스 내부의 시간 타이머 변수로, 간단하게 현재 시각을 나타낸다고 보면 된다.
(구체적으로로는 타이머 인터럽트가 일어난 횟수)
jiffies를 이용해서 커널 소스 내부에서 타이머 인터럽트를 활용한 기법을 사용할 수 있음
 
mm->numa_scan_seq = 0;
p->node_stamp = 0; p->numa_scan_seq = mm ? mm->numa_scan_seq : 0; p->numa_scan_period = sysctl_numa_balancing_scan_delay; p->numa_migrate_retry = 0;
node_stamp는 migration stamp, 즉 NUMA 시스템에서 프로세스 또는 스레드의 메모리 접근 시간을 추적하는 변수
 
/* Protect against double add, see task_tick_numa and task_numa_work */ p->numa_work.next = &p->numa_work;
이 부분은 numa_work의 next 포인터를 자기 자신을 가리키도록 하는 부분인데, 이렇게 하면 중복으로 등록되는 것을 막을 수 있다고 함
p->numa_faults = NULL; p->numa_pages_migrated = 0; p->total_numa_faults = 0; RCU_INIT_POINTER(p->numa_group, NULL); p->last_task_numa_placement = 0; p->last_sum_exec_runtime = 0;
  • numa_faults
https://elixir.bootlin.com/linux/v6.10.3/source/include/linux/sched.h#L748 /* * numa_faults is an array split into four regions: * faults_memory, faults_cpu, faults_memory_buffer, faults_cpu_buffer * in this precise order. * * faults_memory: Exponential decaying average of faults on a per-node * basis. Scheduling placement decisions are made based on these * counts. The values remain static for the duration of a PTE scan. * faults_cpu: Track the nodes the process was running on when a NUMA * hinting fault was incurred. * faults_memory_buffer and faults_cpu_buffer: Record faults per node * during the current scan window. When the scan completes, the counts * in faults_memory and faults_cpu decay and these values are copied. */ unsigned long *numa_faults; unsigned long total_numa_faults;
numa fault는 4개의 영역으로 나뉜 배열임 (faults_memory, faults_cpu, faults_memory_buffer, faults_cpu_buffer)
faults_memory는 메모리 fault 발생 평균
faults_cpu는 NUMA hinting fault가 일어났을 때 어느 CPu인지
faults_memory_buffer & faults_cpu_buffer 는 현재 scan중에 node별로 fault를 기록
 
  • numa_group
RCU 포인터로 구현되어있기 때문에 RCU_INIT_POINTER로 초기화를 해줌
→ RCU는 특히 연결리스트 형태의 자료구조에 적용이 가능한, 읽기 속도에 큰 이점을 가지는 보호기법
 
init_task_work(&p->numa_work, task_numa_work);
init_task_work : 코드 자체는 간단한 함수
static inline void init_task_work(struct callback_head *twork, task_work_func_t func) { twork->func = func; }
 
init_task_work(&p->numa_work, task_numa_work);
numa_work에 task_numa_work 함수를 등록시킴.
task_numa_work함수는 추후에 실행
 
task_numa_work 주석
/* * The expensive part of numa migration is done from task_work context. * Triggered from task_tick_numa(). */ static void task_numa_work(struct callback_head *work)
  1. numa migration에서 부담이 많이 가는 작업은 task_work 컨텍스트에서 끝난다
task_work?? → 검색해보니 특정 작업을 지연해서 실행시킬 때 사용한다고 합니다.
 
  1. task_tick_numa()에서 동작한다.
https://elixir.bootlin.com/linux/v6.10/source/kernel/sched/fair.c#L3200 task_tick_numa 는 스케줄러에서 호출됨
 
/* New address space, reset the preferred nid */ if (!(clone_flags & CLONE_VM)) { p->numa_preferred_nid = NUMA_NO_NODE; return; }
새로운 주소 공간이 생성된 경우, 선호 numa node를 NUMA_NO_NODE로 설정되게 하고, 동적으로 선호 NUMA NODE가 선택되게 하는 코드 같습니다
 
if (mm) { unsigned int delay; delay = min_t(unsigned int, task_scan_max(current), current->numa_scan_period * mm_users * NSEC_PER_MSEC); delay += 2 * TICK_NSEC; p->node_stamp = delay; }
mm이 존재하는 경우 (= 즉 유저 스레드인 경우)
node_stamp를 delay 만큼 지연시킴
 
 
 

댓글

guest