이 코드는 어떤 문제를 푸나요?
타이머 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 잠금 안팎 어디에 있는지 확인해 보세요. 관찰·계상 기능도 락 범위와 비용을 고려해 배치됩니다.
그림으로 보는 변화

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
해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고
