Linux v6.18.37 · 개념과 코드 읽기

scheduler tick: 주기적으로 실행 시간을 계상하고 재선택을 요청하기

이 코드는 어떤 문제를 푸나요?

타이머 tick이 왔다고 매번 다른 task로 교체하는 것은 아닙니다. 먼저 경과 시간과 CPU 상태를 갱신하고 현재 스케줄링 정책에 이번 tick을 알려 줍니다. Linux 6.18.37에서 이 공통 함수의 실제 이름은 sched_tick이며, donor를 통해 계상 대상을 구분합니다.

읽을 범위: v6.18.37 · kernel/sched/core.c · sched_tick 5579–5628행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.

먼저 알아둘 개념

계상과 전환

얼마나 실행했는지 기록하는 일, 재스케줄이 필요하다고 표시하는 일, 실제 context switch는 서로 다른 단계입니다.

스케줄링 클래스 콜백

fair, RT 등 각 정책이 자신의 시간 규칙을 적용하도록 task_tick 함수를 호출합니다. 공통 tick 코드가 모든 정책의 세부 조건을 직접 작성하지 않습니다.

donor

이 버전에서 스케줄링 자원 계상의 대상이 되는 task입니다. 현재 실행 포인터와 항상 같은 것으로 생략하지 말고 rq->donor 사용을 그대로 읽으셔야 합니다.

처음 읽을 때

tick 전후에 실행한 누적 시간이 늘어났지만 다음 task는 그대로일 수 있는 예를 생각해 보세요. tick은 교체 명령이 아니라 정책이 판단할 정기 기회입니다.

더 깊이 살펴볼 때

하드웨어 pressure, PSI, sched_ext와 core scheduling 갱신이 rq 잠금 안팎 어디에 있는지 확인해 보세요. 관찰·계상 기능도 락 범위와 비용을 고려해 배치됩니다.

그림으로 보는 변화

scheduler tick: 주기적으로 실행 시간을 계상하고 재선택을 요청하기의 단계별 개념 그림
각 단계에 화살표 의미와 생략 범위를 표시했습니다. 주소·숫자 예제는 실제 장치 값을 뜻하지 않습니다.
1단계 설명

1단계 고정

GIF 원본 열기

1. 현재 CPU 상태 갱신

rq_clock, 하드웨어 pressure, 계상 기준

화살표는 tick 한 번의 처리 순서입니다. CPU 간 작업 이동을 표시하는 화살표가 아닙니다.

2. 정책별 tick 처리

donor->sched_class->task_tick

해당 정책이 요청 소진 등 자신의 조건을 확인합니다. 필요하면 재선택을 요청합니다.

3. 주변 기능과 균형 조정

잠금 해제 후 perf, workqueue, balancing

실제 context switch 여부는 이후 실행 문맥과 재스케줄 조건에 달려 있습니다.

sched_tick를 한 줄씩 읽기

줄 번호는 v6.18.37 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.

void sched_tick(void)
{
	int cpu = smp_processor_id();
	struct rq *rq = cpu_rq(cpu);
	/* accounting goes to the donor task */
	struct task_struct *donor;
	struct rq_flags rf;
	unsigned long hw_pressure;
	u64 resched_latency;

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

	sched_clock_tick();

	rq_lock(rq, &rf);
	donor = rq->donor;

	psi_account_irqtime(rq, donor, NULL);

	update_rq_clock(rq);
	hw_pressure = arch_scale_hw_pressure(cpu_of(rq));
	update_hw_load_avg(rq_clock_task(rq), rq, hw_pressure);

	if (dynamic_preempt_lazy() && tif_test_bit(TIF_NEED_RESCHED_LAZY))
		resched_curr(rq);

	donor->sched_class->task_tick(rq, donor, 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, donor);
	scx_tick(rq);

	rq_unlock(rq, &rf);

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

	perf_event_task_tick();

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

	if (!scx_switched_all()) {
		rq->idle_balance = idle_cpu(cpu);
		sched_balance_trigger(rq);
	}
}
void sched_tick(void)

이 버전의 공통 스케줄러 tick 함수입니다. 문서에서 개념적으로 scheduler tick이라고 부르더라도 원문 함수명은 sched_tick입니다.

	int cpu = smp_processor_id();

지금 실행하는 CPU 번호를 얻습니다. 다른 CPU의 rq를 임의로 갱신하는 경로가 아닙니다.

	struct rq *rq = cpu_rq(cpu);

해당 CPU의 runqueue를 찾습니다.

	struct task_struct *donor;

시간 계상 대상 donor를 담을 포인터를 준비합니다.

	struct rq_flags rf;

rq 잠금과 관련된 상태를 보관할 자료구조입니다.

	unsigned long hw_pressure;

하드웨어 때문에 줄어든 CPU 처리 용량 정보를 저장합니다.

	u64 resched_latency;

재스케줄 지연 경고를 위한 시간을 담습니다. 기능 검사에 따라 사용됩니다.

	if (housekeeping_cpu(cpu, HK_TYPE_KERNEL_NOISE))

커널 잡음을 담당하는 housekeeping CPU인지 확인합니다. 격리된 CPU의 부담을 구분하는 조건입니다.

		arch_scale_freq_tick();

해당 CPU라면 주파수 기반 용량 계상 상태를 갱신합니다.

	sched_clock_tick();

스케줄러 시계의 tick 처리를 수행합니다. 뒤의 실행 시간 계산에 일관된 시각 기준이 필요합니다.

	rq_lock(rq, &rf);

rq 상태를 일관되게 갱신하기 위해 잠금을 잡고 관련 상태를 rf에 보관합니다.

	donor = rq->donor;

잠금 안에서 이번 계상의 대상 task를 읽습니다.

	psi_account_irqtime(rq, donor, NULL);

donor의 PSI 관련 IRQ 시간 계상을 반영합니다. 작업이 CPU를 얻지 못하는 압력 관찰과 연결됩니다.

	update_rq_clock(rq);

실행 큐의 현재 시계를 갱신합니다. 오래된 시각으로 정책의 경과 시간을 계산하지 않도록 합니다.

	hw_pressure = arch_scale_hw_pressure(cpu_of(rq));

CPU가 실제 제공하지 못하는 처리 용량에 대한 하드웨어 pressure 값을 읽습니다.

	update_hw_load_avg(rq_clock_task(rq), rq, hw_pressure);

갱신한 rq task 시각과 pressure로 하드웨어 부하 평균을 갱신합니다.

	if (dynamic_preempt_lazy() && tif_test_bit(TIF_NEED_RESCHED_LAZY))

lazy preemption이 동작하고 지연된 재스케줄 요청이 있는지 확인합니다.

		resched_curr(rq);

그렇다면 현재 rq에 재스케줄이 필요함을 반영합니다. 이 호출을 곧바로 context switch라고 해석하지 않습니다.

	donor->sched_class->task_tick(rq, donor, 0);

donor가 속한 스케줄링 정책의 task_tick을 실행합니다. 마지막 0은 이 호출에서 queued 인자로 전달되는 값입니다.

	if (sched_feat(LATENCY_WARN))

재스케줄 지연 경고 기능이 켜져 있는지 확인합니다.

		resched_latency = cpu_resched_latency(rq);

켜져 있으면 요청 이후의 지연 시간을 계산하여 잠금 밖에서 경고할 수 있게 합니다.

	calc_global_load_tick(rq);

시스템 load 계상에 이번 tick을 반영합니다.

	sched_core_tick(rq);

core scheduling의 tick 관련 상태를 처리합니다. 설정에 따라 실체가 달라질 수 있습니다.

	task_tick_mm_cid(rq, donor);

메모리 문맥의 concurrency ID 관리에 tick을 반영합니다.

	scx_tick(rq);

sched_ext 쪽의 tick 처리도 수행합니다. 전체 task가 해당 클래스로 전환되었는지는 아래에서 별도 확인합니다.

	rq_unlock(rq, &rf);

공통 rq 상태 갱신을 끝내고 잠금을 풉니다.

	if (sched_feat(LATENCY_WARN) && resched_latency)

경고 기능이 켜져 있고 실제 지연이 감지되었는지 확인합니다. 앞의 조건과 맞물려 resched_latency를 읽습니다.

		resched_latency_warn(cpu, resched_latency);

잠금 밖에서 지연 경고를 출력합니다.

	perf_event_task_tick();

성능 계측 이벤트의 task tick 처리를 수행합니다.

	if (donor->flags & PF_WQ_WORKER)

계상 대상이 workqueue worker인지 flags 비트로 확인합니다.

		wq_worker_tick(donor);

worker라면 workqueue의 tick 관련 관리도 진행합니다.

	if (!scx_switched_all()) {

모든 task를 sched_ext가 맡는 상황이 아닌지 확인합니다. 기존 균형 조정과의 역할을 구분합니다.

		rq->idle_balance = idle_cpu(cpu);

이 CPU가 idle인지 기록하여 balancing 판단에 제공합니다.

		sched_balance_trigger(rq);

필요한 스케줄러 부하 균형 조정 절차를 촉발합니다. 이 줄 하나가 모든 이동을 완료한다는 뜻은 아닙니다.

함께 생각해 볼 질문

tick 한 번마다 task가 반드시 바뀌나요?

아닙니다. 실행 시간이 기록되고 정책 판단이 이루어지지만 계속 현재 task가 적절할 수 있습니다.

task_tick 콜백을 쓰는 이유는 무엇인가요?

공통 시간 갱신과 각 정책의 시간 규칙을 나누기 위해서입니다. RT와 fair가 같은 소진 규칙을 쓰지 않습니다.

재스케줄 플래그를 세우면 그 줄에서 즉시 전환하나요?

그렇지 않습니다. 적절한 선점 지점이나 복귀 경로에서 실제 스케줄러 전환으로 이어질 수 있습니다.

출처와 읽은 범위

Linux stable v6.18.37 · kernel/sched/core.c

해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고

맨 위로 ↑