전체 흐름
tick은 실행 시간을 계상하고 정책별 task_tick에 판단 기회를 줍니다. need_resched 요청과 실제 context switch는 다른 사건입니다. fair 클래스가 아니면 task_tick_fair()도 호출되지 않습니다.
로컬 timer tick: 조건을 만족한 경우
NOHZ full: 로컬 주기 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 처리와 같다고 일반화하지 않습니다.
함수별 분석
이 그림은 소스의 호출 경계입니다. 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가 이동한다고 가정 |
구조체와 필드
이전 그림은 객체 관계와 실행 순서를 한 줄의 화살표로 섞었습니다. 실제 대상과 읽는 경계를 나눕니다.
| 대상 | 뜻 | 확인할 경계 |
|---|---|---|
rq | CPU별 runqueue·clock | rq 잠금 안에서 갱신 |
rq->curr | 클래스 콜백 계상 대상 | scheduler_tick()에서 선택 |
sched_class->task_tick | 정책별 함수 포인터 | fair/RT/deadline/idle 분기 |
curr->se·cfs_rq | fair entity와 큐 | fair 콜백에서만 사용 |
tick_sched stopped 상태 | 로컬 주기 tick의 중단·재시작 | tick-sched.c에서 관리 |
실행 단계와 상태 변화
다음 행은 매번 순서대로 통과하는 state machine이 아닙니다. 각 경로의 조건과 가능한 후속 결과입니다.
| 경계 | 조건 | 후속 결과 |
|---|---|---|
| 로컬 timer | tick 활성·유효한 문맥 | scheduler_tick() 진입 가능 |
| runqueue | 공통 tick 함수 실행 | rq clock·curr의 클래스별 통계 갱신 |
| 재스케줄 | 정책/선점 조건 충족 | 요청 플래그 설정 가능, 즉시 switch 아님 |
| 균형 조정 | trigger_load_balance()의 조건 충족 | SCHED_SOFTIRQ 요청 가능, 즉시 migration 아님 |
| NOHZ full 보조 | 로컬 tick 중단과 원격 work 조건 | 원격 rq clock·클래스 콜백 갱신 가능 |
불변 조건과 실패 사례
scheduler_tick()는 rq를 잠근 구간에서 clock과 클래스 콜백을 갱신합니다. 원격 tick도 대상 rq 잠금과 CPU 온라인 상태를 확인합니다.
task_tick_fair()를 모든 정책의 tick 경로로 그리면 RT·deadline·idle의 처리를 놓칩니다.
로컬 주기 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 중단 판단 | 이벤트 가용성은 빌드별 확인 |
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 사이에 어떤 조건이 남는가?