floatFirstTOC: right
함수 선언
함수 정의
‣
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)
- numa migration에서 부담이 많이 가는 작업은 task_work 컨텍스트에서 끝난다
task_work?? → 검색해보니 특정 작업을 지연해서 실행시킬 때 사용한다고 합니다.
- 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 만큼 지연시킴
댓글