Scheduler · Linux v6.6 · source + diagram note

load balancing

주기적·NOHZ·new-idle 진입을 구분하고, source 선택부터 조건부 task 이동까지 v6.6 코드로 확인합니다.

전체 흐름

Linux v6.6의 CPU load balancing은 하나의 연속 호출 사슬이 아니다. 주기적 tick과 NOHZ 요청은 SCHED_SOFTIRQ에서 처리하고, 새로 idle이 된 CPU는 별도 newidle_balance 경로에서 task를 당길 수 있다. 어느 경로든 실제 이동 전에 domain, source group·rq, affinity와 비용 조건을 통과해야 한다.

판단 단위

sched_domain은 탐색 범위를, lb_env는 목적지·후보 CPU·imbalance·이동 제약을 담는다. 후보를 찾았다는 뜻과 이동했다는 뜻을 분리한다.

0개 이동도 결과

balance 시도는 담당 CPU 아님, 후보 group/rq 없음, affinity 제한, 이동 비용 등의 이유로 task를 옮기지 않고 끝날 수 있다.

그림 1. v6.6에서 balance를 시도하는 세 진입 경로
주기적 ticktrigger_load_balance가 기한을 확인↓ SCHED_SOFTIRQrun_rebalance_domains → 일반 domain 순회
NOHZ idle 대리 처리정지된 idle CPU tick을 대신할 kick↓ SCHED_SOFTIRQnohz_idle_balance가 먼저 처리; true면 일반 경로는 실행하지 않음
새로 idle이 된 CPUpending wakeup·overload·예상 비용 확인↓ 별도 pullSD_BALANCE_NEWIDLE domain에서 load_balance(..., CPU_NEWLY_IDLE, ...)

fair.c:12068–12138, 12194–12232. 각 칸은 가능한 진입 분류이며, 모든 실행에서 세 경로가 차례로 발생한다는 뜻이 아니다.

소스 코드 위치

Linux v6.6에서 대조할 파일

함께 읽을 파일:
kernel/sched/fair.c — 진입, source 선택, migration 분기
kernel/sched/topology.c — sched domain 구성 확인
kernel/sched/sched.h — sched domain·rq 관련 정의 확인

설명: 아래 세 발췌와 행 번호는 Torvalds Linux v6.6 공식 Raw fair.c의 지정 연속 행을 기준으로 한다. 인용 행은 이 고정 버전 원문의 실제 줄바꿈을 기준으로 대조했으며, 다른 사본이나 웹 뷰어가 표시하는 행 번호를 섞지 않는다. topology와 구조체 정의는 v6.6 topology.c, v6.6 sched.h에서 함께 확인한다.

공식 tag: Linux v6.6 kernel/sched/fair.c Raw. 함수명이 비슷한 다른 LTS 버전의 행 번호나 흐름을 이 글에 대입하지 않는다.

후기 버전에서 대응 함수의 이름과 분기가 어떻게 달라졌는지는 v6.18.37 비교 표를 참고한다.

대표 코드

기존 카드의 함수 제목과 첨부 코드 위치가 서로 달랐다. 아래는 실제 v6.6 fair.c의 연속 발췌 세 개이며, 전체 함수나 가능한 분기 전부를 대체하지 않는다.

softirq 진입과 주기적 요청

원본 코드: kernel/sched/fair.c, 12194–12232행
static __latent_entropy void run_rebalance_domains(struct softirq_action *h)
{
	struct rq *this_rq = this_rq();
	enum cpu_idle_type idle = this_rq->idle_balance ?
						CPU_IDLE : CPU_NOT_IDLE;

	/*
	 * If this CPU has a pending nohz_balance_kick, then do the
	 * balancing on behalf of the other idle CPUs whose ticks are
	 * stopped. Do nohz_idle_balance *before* rebalance_domains to
	 * give the idle CPUs a chance to load balance. Else we may
	 * load balance only within the local sched_domain hierarchy
	 * and abort nohz_idle_balance altogether if we pull some load.
	 */
	if (nohz_idle_balance(this_rq, idle))
		return;

	/* normal load balance */
	update_blocked_averages(this_rq->cpu);
	rebalance_domains(this_rq, idle);
}

/*
 * Trigger the SCHED_SOFTIRQ if it is time to do periodic load balancing.
 */
void trigger_load_balance(struct rq *rq)
{
	/*
	 * Don't need to rebalance while attached to NULL domain or
	 * runqueue CPU is not active
	 */
	if (unlikely(on_null_domain(rq) || !cpu_active(cpu_of(rq))))
		return;

	if (time_after_eq(jiffies, rq->next_balance))
		raise_softirq(SCHED_SOFTIRQ);

	nohz_balancer_kick(rq);
}

읽는 법: nohz_idle_balance가 true이면 여기서 반환한다. 그 밖에는 blocked average를 갱신하고 일반 domain 순회로 간다. trigger_load_balance는 기한이 된 경우에만 SCHED_SOFTIRQ를 올린다.

source 후보를 찾거나 balanced로 종료

원본 코드: kernel/sched/fair.c, 11077–11093행
redo:
	if (!should_we_balance(&env)) {
		*continue_balancing = 0;
		goto out_balanced;
	}

	group = find_busiest_group(&env);
	if (!group) {
		schedstat_inc(sd->lb_nobusyg[idle]);
		goto out_balanced;
	}

	busiest = find_busiest_queue(&env, group);
	if (!busiest) {
		schedstat_inc(sd->lb_nobusyq[idle]);
		goto out_balanced;
	}

읽는 법: should_we_balance, find_busiest_group, find_busiest_queue 가운데 어느 단계에서든 이동 없이 out_balanced로 갈 수 있다. group 후보와 rq 후보를 같은 것으로 세지 않는다.

detach 뒤 조건부 attach

원본 코드: kernel/sched/fair.c, 11114–11139행
more_balance:
		rq_lock_irqsave(busiest, &rf);
		update_rq_clock(busiest);

		/*
		 * cur_ld_moved - load moved in current iteration
		 * ld_moved     - cumulative load moved across iterations
		 */
		cur_ld_moved = detach_tasks(&env);

		/*
		 * We've detached some tasks from busiest_rq. Every
		 * task is masked "TASK_ON_RQ_MIGRATING", so we can safely
		 * unlock busiest->lock, and we are able to be sure
		 * that nobody can manipulate the tasks in parallel.
		 * See task_rq_lock() family for the details.
		 */

		rq_unlock(busiest, &rf);

		if (cur_ld_moved) {
			attach_tasks(&env);
			ld_moved += cur_ld_moved;
		}

		local_irq_restore(rf.flags);

읽는 법: source rq 잠금 아래에서 detach_tasks를 시도한다. 잠금 해제 뒤 이번 반복에서 실제로 떼어낸 task가 있을 때에만 attach_tasks를 호출한다. 이것은 한 반복의 일부이며 pinned 재시도와 active balancing 등은 뒤쪽 분기에서 다룬다.

함수별 분석

rebalance_domains는 domain별 간격을 확인해 load_balance를 시도한다. newidle_balance는 이 주기적 경로와 별도로 pending wakeup, CPU 활성화, root domain overload, idle 시간 대비 예상 비용과 SD_BALANCE_NEWIDLE을 확인한다. 분기는 아래처럼 서로 다른 종료점을 갖는다.

그림 2. load_balance의 후보 선택과 조기 종료
담당 CPU인가?should_we_balance(&env)
아니면 continue_balancing=0, balanced 출구
source group이 있는가?find_busiest_group(&env)
없으면 lb_nobusyg, balanced 출구
source rq가 있는가?find_busiest_queue(&env, group)
없으면 lb_nobusyq, balanced 출구
옮길 task가 있는가?affinity·실행 중 여부·cache hotness를 검사하며 detach 시도
이번 반복에 0개면 attach하지 않음; 후속 재시도 조건은 코드에 따름

fair.c:10629–10765, 10770–10910, 11051–11139. capacity는 앞선 group·rq 후보 선택에 반영된다. detach의 task 검사와 구분한 주요 관문만 요약하며 group type, NUMA, active balancing과 모든 반환 경로를 열거하지 않는다.

1. domain 방문

주기적 경로의 rebalance_domains는 domain별 interval을 확인한 뒤 기한이 된 곳에서 load_balance를 호출한다 (fair.c:11479–11532). 새로 idle 경로는 비용과 flag를 따로 확인한다 (fair.c:12068–12138).

2. source 선택

find_busiest_group은 통계를 모아 source group을 고른다. v6.6은 misfit, asym packing, imbalanced group을 평균 load 비교와 별도로 다루며, 상황에 따라 균형 상태로 종료한다 (fair.c:10629–10669).

3. 실제 이동

source rq까지 골라도 affinity 등 때문에 이동 가능한 task가 0개일 수 있다. detach_tasks의 이번 반복 반환값이 양수일 때만 attach_tasks가 실행된다 (fair.c:11114–11139).

관문v6.6에서 확인할 조건결과
softirqnohz_idle_balance가 true일반 rebalance_domains 실행 전 반환
new-idle pullSD_BALANCE_NEWIDLE과 비용 조건통과한 domain에서 CPU_NEWLY_IDLE balance 시도
source 선택담당 CPU, group, rq 후보없으면 task 이동 없이 balanced 출구
migrationcur_ld_moved > 0이번 반복의 detach 목록을 목적지 rq에 attach

구조체와 필드

아래 객체는 순서대로 생성되는 상태가 아니다. 한 domain 안의 local·source group을 비교하고, lb_env가 목적지 rq와 후보 CPU, 이동량을 담은 채 source 선택과 detach에 전달된다.

그림 3. domain 범위 안에서 비교하는 두 그룹과 이동 환경
sched_domain · 탐색 범위/flag/interval
local group목적지 CPU가 속한 그룹의 load·capacity·idle 상태
busiest 후보 groupgroup type과 load·capacity로 비교; 실제 source rq는 다음 단계에서 선택
lb_env · 이번 balance 시도의 지역 문맥dst_rq, CPU mask, imbalance, LBF_* 제약; src_rq는 source rq 선택 뒤 채움

group을 찾은 뒤에도 source rq가 없거나 task affinity 때문에 이동이 0개일 수 있다. fair.c:8559–8584, 8694–8780, 10629–10765, 11051–11139.

객체·필드이번 판단에서 맡는 역할혼동하지 말 것
sched_domain방문할 CPU 범위, balance flag와 intervaldomain 방문만으로 task 이동 확정 아님
sg_lb_stats / capacitylocal과 busiest group 비교, misfit 등 group type 판단평균 load 한 수치만으로 source를 정하지 않음
lb_env목적지 rq·CPU mask·imbalance·이동 flag; source rq는 선택 뒤 채움LBF_ALL_PINNED 등은 영구 task 상태가 아님
rq / taskCPU별 runnable 후보와 detach·attach 대상source rq 선택과 실제 migration은 별개

실행 단계와 결과

이동 경로는 source rq와 destination rq를 동시에 한 잠금 아래에서 바꾸는 단일 단계가 아니다. v6.6 코드는 source 쪽에서 task를 detach한 뒤 잠금을 풀고, 이번 반복에서 떼어낸 task가 있는 경우 destination에 attach한다.

그림 4. 한 번의 detach 반복과 두 갈래 결과
source rq 잠금rq_lock_irqsave
detach 시도cur_ld_moved = detach_tasks
source 잠금 해제detach된 task는 migrating 상태
조건부 attach양수일 때 목적지 rq로 이동
이번 반복 0개attach_tasks 호출 없음; 이후 재시도는 별도 조건
이번 반복 양수목적지 rq에 attach하고 누적 이동량 갱신

fair.c:11114–11139. task가 이동하지 않는 출구는 모두 오류 복구가 아니다. pinned task 재탐색과 active balancing은 이 축약 그림 밖에 있다.

이번 시도 결과조건 예시관찰할 것
일반 경로 생략NOHZ 선행 처리에서 true 반환해당 softirq의 일반 domain 순회가 실행되지 않음
후보 없음담당 CPU·group·rq 관문 중단lb_nobusyg / lb_nobusyq 등 조건별 통계
0개 detachaffinity, hotness 또는 대상 제약attach 없음; pinned·재시도 분기 별도 확인
양수 detach옮길 수 있는 task 발견목적지 rq attach와 누적 이동량
source group 발견 ≠ source rq 발견 ≠ task 이동 완료

불변 조건과 놓치기 쉬운 분기

진입 경로 분리

주기적·NOHZ softirq 처리와 newidle_balance를 매번 순서대로 실행되는 하나의 사슬로 그리지 않는다.

group 비교

misfit task, asym packing, imbalanced group, energy-aware 조건 때문에 단순 평균 load 비교만으로 source를 설명할 수 없다.

affinity와 pinned

source rq를 찾은 뒤에도 이동 가능한 task가 전부 제한될 수 있다. LBF_ALL_PINNED 처리와 다른 destination 재시도는 원문 분기를 따른다.

attach 조건

source rq 잠금 해제 뒤 cur_ld_moved가 양수인 반복에만 attach_tasks가 호출된다. 후보를 세는 통계와 실제 migration을 혼동하지 않는다.

계측과 검증

실제 이동 사건과 balance 내부 판단은 다른 관측값이다. 사용 가능한 tracepoint·schedstat·디버그 인터페이스는 커널 설정에 따라 확인해야 한다.

관측 수단알 수 있는 것한계
sched:sched_migrate_tasktask가 CPU를 옮긴 사건후보 없음·0개 detach 분기 자체는 보여주지 않음
perf sched mapCPU별 실행·스위치 이력group/imbalance 내부 판단을 직접 표시하지 않음
schedstat의 lb_count, lb_nobusyg, lb_nobusyq, lb_failed시도·후보 없음·이동 실패를 구분CONFIG_SCHEDSTATS와 노출 방식 확인 필요
/proc/sched_debug구성에서 제공할 때 rq/domain 상태CONFIG_SCHED_DEBUG 등 설정에 좌우됨
tracefs에서 event 제공 여부 확인:
cd /sys/kernel/tracing
grep '^sched:sched_migrate_task$' available_events
cat events/sched/sched_migrate_task/format

tracefs가 마운트되고 해당 event가 제공될 때의 읽기 전용 확인이다. event가 없어도 balance 자체가 없었다는 증거는 아니다.

추가 확인 사항

  • 이 CPU의 sched_domain interval이 도래했는가, 아니면 new-idle 경로가 별도로 시작했는가?
  • find_busiest_group이 후보를 고르거나 버린 이유는 load, capacity, misfit, energy 조건 중 무엇인가?
  • source rq를 골랐어도 detach_tasks가 0개를 반환했다면 affinity와 pinned 재시도 분기는 어디로 갔는가?