Scheduler · Linux v6.6 · source + diagram note

scheduler tick

tick 진입, 클래스별 계상, 재스케줄 요청, NOHZ 경로를 소스 코드로 나누어 읽습니다.

전체 흐름

tick은 실행 시간을 계상하고 정책별 task_tick에 판단 기회를 줍니다. need_resched 요청과 실제 context switch는 다른 사건입니다. fair 클래스가 아니면 task_tick_fair()도 호출되지 않습니다.

그림 1. Linux v6.6의 대표 로컬 tick과 NOHZ 원격 보조 경로

로컬 timer tick: 조건을 만족한 경우

tick_sched_handle()update_process_times() 호출
scheduler_tick()rq clock 갱신과 rq 잠금
sched_class->task_tick현재 대상의 클래스 콜백
후속 판단재스케줄·균형 조정은 조건부

NOHZ full: 로컬 주기 tick을 멈춘 경우의 보조 작업

tick 중단full dynticks 조건 확인
sched_tick_remote()원격 work가 rq를 잠금
rq clock원격 시점으로 갱신
class callback필요할 때 task_tick 호출

두 행은 서로 다른 경로입니다. 원격 보조 작업은 로컬 scheduler_tick() 전체를 실행하지 않습니다. 플랫폼·설정·상태에 따라 세부 진입 경로가 다릅니다.

v6.6에서는 rq->curr의 클래스 콜백을 호출합니다. 아래 첫 인용은 함수 끝이 생략되어 있으며, 원본의 CONFIG_SMP 아래에 trigger_load_balance()가 있습니다.

소스 코드 위치

먼저 확인할 Linux v6.6 소스 파일

원본 코드:
Linux v6.6: kernel/sched/core.c
Linux v6.6: kernel/time/tick-sched.c
Linux v6.6: kernel/sched/fair.c

설명: tick-sched.c의 timer 진입과 NOHZ 중단·재시작, core.c의 공통 tick과 원격 보조 작업, fair.c의 fair 클래스 콜백을 서로 다른 경로로 읽습니다. update_process_times()의 호출 연결은 kernel/time/timer.c에서 확인합니다.

upstream kernel/time/timer.c · update_process_times() 참조

대표 코드

원래 코드 인용은 그대로 둡니다. 발췌 범위가 함수 전체와 일치하지 않는 곳은 제목에서 구분합니다.

void scheduler_tick(void)

원본 코드: Linux v6.6 core.c 5633행 부근 — 끝부분 생략

/*
 * This function gets called by the timer code, with HZ frequency.
 * We call it with interrupts disabled.
 */
void scheduler_tick(void)
{
	int cpu = smp_processor_id();
	struct rq *rq = cpu_rq(cpu);
	struct task_struct *curr = rq->curr;
	struct rq_flags rf;
	unsigned long thermal_pressure;
	u64 resched_latency;

	if (housekeeping_cpu(cpu, HK_TYPE_TICK))
		arch_scale_freq_tick();

	sched_clock_tick();

	rq_lock(rq, &rf);

	update_rq_clock(rq);
	thermal_pressure = arch_scale_thermal_pressure(cpu_of(rq));
	update_thermal_load_avg(rq_clock_thermal(rq), rq, thermal_pressure);
	curr->sched_class->task_tick(rq, curr, 0);
	if (sched_feat(LATENCY_WARN))
		resched_latency = cpu_resched_latency(rq);
	calc_global_load_tick(rq);
	sched_core_tick(rq);
	task_tick_mm_cid(rq, curr);

	rq_unlock(rq, &rf);

	if (sched_feat(LATENCY_WARN) && resched_latency)
		resched_latency_warn(cpu, resched_latency);

	perf_event_task_tick();

	if (curr->flags & PF_WQ_WORKER)
		wq_worker_tick(curr);

읽기: rq 잠금 안의 clock 갱신과 클래스 콜백, 잠금 해제 뒤의 계측, 조건부 균형 조정을 구분합니다.

task_tick_fair()

원본 코드: Linux v6.6 fair.c 12381행 부근 — 뒤에 task_fork_fair 일부 포함
 * NOTE: This function can be called remotely by the tick offload that
 * goes along full dynticks. Therefore no local assumption can be made
 * and everything must be accessed through the @rq and @curr passed in
 * parameters.
 */
static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
{
	struct cfs_rq *cfs_rq;
	struct sched_entity *se = &curr->se;

	for_each_sched_entity(se) {
		cfs_rq = cfs_rq_of(se);
		entity_tick(cfs_rq, se, queued);
	}

	if (static_branch_unlikely(&sched_numa_balancing))
		task_tick_numa(rq, curr);

	update_misfit_status(curr, rq);
	update_overutilized_status(task_rq(curr));

	task_tick_core(rq, curr);
}

/*
 * called on fork with the child task as argument from the parent's context
 *  - child not yet on the tasklist
 *  - preemption disabled
 */
static void task_fork_fair(struct task_struct *p)
{
	struct sched_entity *se = &p->se, *curr;
	struct cfs_rq *cfs_rq;
	struct rq *rq = this_rq();
	struct rq_flags rf;

	rq_lock(rq, &rf);
	update_rq_clock(rq);

	cfs_rq = task_cfs_rq(current);

읽기: fair entity별 entity_tick()을 호출합니다. 다른 클래스의 tick 처리와 같다고 일반화하지 않습니다.

함수별 분석

그림 2. scheduler_tick() 안팎의 잠금과 조건부 분기
rq_lockrunqueue 갱신 보호
update_rq_clockCPU별 시간 기준 갱신
curr->sched_class->task_tick선택된 정책의 callback
rq_unlock 이후trigger_load_balance() 조건 검사

이 그림은 소스의 호출 경계입니다. fair 콜백, resched 요청, softirq, context switch는 동일한 한 단계가 아닙니다.

v6.6에서는 rq->curr의 클래스 콜백을 호출합니다. 아래 첫 인용은 함수 끝이 생략되어 있으며, 원본의 CONFIG_SMP 아래에 trigger_load_balance()가 있습니다.

task_tick_fair()는 curr->se부터 부모 entity를 따라 entity_tick()을 호출합니다. entity_tick() 안의 update_curr()가 실행 시간을 갱신합니다. 선점 요청은 hrtick·스케줄링 정책의 조건에 달려 있습니다.

함수직접 하는 일성급한 결론
scheduler_tick()rq clock 갱신과 클래스 콜백 호출항상 fair 콜백이라고 가정
task_tick_fair()fair entity의 시간·평균값 갱신매번 context switch한다고 가정
trigger_load_balance()조건부 SCHED_SOFTIRQ 요청그 자리에서 task가 이동한다고 가정

구조체와 필드

이전 그림은 객체 관계와 실행 순서를 한 줄의 화살표로 섞었습니다. 실제 대상과 읽는 경계를 나눕니다.

대상뜻확인할 경계
rqCPU별 runqueue·clockrq 잠금 안에서 갱신
rq->curr클래스 콜백 계상 대상scheduler_tick()에서 선택
sched_class->task_tick정책별 함수 포인터fair/RT/deadline/idle 분기
curr->se·cfs_rqfair entity와 큐fair 콜백에서만 사용
tick_sched stopped 상태로컬 주기 tick의 중단·재시작tick-sched.c에서 관리

실행 단계와 상태 변화

다음 행은 매번 순서대로 통과하는 state machine이 아닙니다. 각 경로의 조건과 가능한 후속 결과입니다.

경계조건후속 결과
로컬 timertick 활성·유효한 문맥scheduler_tick() 진입 가능
runqueue공통 tick 함수 실행rq clock·curr의 클래스별 통계 갱신
재스케줄정책/선점 조건 충족요청 플래그 설정 가능, 즉시 switch 아님
균형 조정trigger_load_balance()의 조건 충족SCHED_SOFTIRQ 요청 가능, 즉시 migration 아님
NOHZ full 보조로컬 tick 중단과 원격 work 조건원격 rq clock·클래스 콜백 갱신 가능
tick 도착 ≠ 매번 재스케줄 요청 ≠ 즉시 문맥 전환

불변 조건과 실패 사례

rq 잠금

scheduler_tick()는 rq를 잠근 구간에서 clock과 클래스 콜백을 갱신합니다. 원격 tick도 대상 rq 잠금과 CPU 온라인 상태를 확인합니다.

정책 분리

task_tick_fair()를 모든 정책의 tick 경로로 그리면 RT·deadline·idle의 처리를 놓칩니다.

NOHZ 경계

로컬 주기 tick 중단은 모든 scheduler 계상의 영구 중단이 아닙니다. 원격 보조 작업과 재시작 조건을 따로 읽습니다.

요청과 실행

resched 요청, SCHED_SOFTIRQ, 실제 task 이동과 context switch는 서로 다른 사건입니다.

계측과 검증

빌드 설정과 tracefs의 이벤트 목록을 먼저 확인하고 tick 함수, 실제 전환, NOHZ 중단을 각각 관찰합니다.

관찰알 수 있는 것한계
function: scheduler_tick로컬 공통 tick 함수 호출NOHZ 원격 보조 콜백은 별도
sched:sched_switch실제 task 전환tick마다 발생하지 않음
timer:hrtimer_expire_entry고해상도 timer callback모든 플랫폼 tick과 1:1 아님
tick_stop 계열NOHZ tick 중단 판단이벤트 가용성은 빌드별 확인
tracefs 확인 예시:
mount -t tracefs nodev /sys/kernel/tracing
cat /sys/kernel/tracing/available_filter_functions | grep -w scheduler_tick
cat /sys/kernel/tracing/available_events | grep -E 'sched_switch|tick_stop|hrtimer_expire_entry'

추가 확인 사항

  • scheduler_tick()의 rq 잠금 안팎에 각각 어떤 작업이 있는가?
  • fair가 아닌 태스크는 어느 클래스의 task_tick을 실행하는가?
  • NOHZ full에서 원격 work가 갱신하는 값과 건드리지 않는 값은 무엇인가?
  • trigger_load_balance() 호출과 실제 load balance·context switch 사이에 어떤 조건이 남는가?