전체 흐름
Linux v6.6의 fair 스케줄러는 실행 시간을 가중치에 따른 vruntime으로 누적하고, 서비스를 덜 받은 후보 중 가상 데드라인이 빠른 엔티티를 고른다. 이 선택은 kernel/sched/fair.c의 EEVDF 경로를 기준으로 읽는다. update_curr()가 호출됐다는 사실만으로 다음 엔티티 선택이나 문맥 교환이 일어난 것은 아니다.
큐의 가중 평균 가상시간 V와 각 엔티티의 vruntime 차이가 eligibility의 기준이다.
기본 EEVDF 탐색은 eligible 후보 중 가장 이른 가상 데드라인을 찾는다. buddy 및 현재 엔티티 유지 분기는 별도 조건을 갖는다.
개념상의 판단 순서다. 실제 코드는 나눗셈한 평균을 직접 비교하지 않고 가중 합으로 eligibility를 검사하며, 이 그림은 함수 호출 순서를 뜻하지 않는다.
소스 코드 위치
Linux v6.6에서 읽을 위치
kernel/sched/fair.c의 avg_vruntime()·entity_eligible()는 서비스 기준을, __pick_eevdf()·pick_next_entity()는 선택 분기를 보여 준다. update_deadline()·update_curr()는 실행량과 데드라인 갱신을 분리해서 읽어야 한다.
Linux v6.6 원본: kernel/sched/fair.c
대표 코드
update_curr(): 서비스 시간 갱신
#endif /* CONFIG_SMP */
/*
* Update the current task's runtime statistics.
*/
static void update_curr(struct cfs_rq *cfs_rq)
{
struct sched_entity *curr = cfs_rq->curr;
u64 now = rq_clock_task(rq_of(cfs_rq));
u64 delta_exec;
if (unlikely(!curr))
return;
delta_exec = now - curr->exec_start;
if (unlikely((s64)delta_exec <= 0))
return;
curr->exec_start = now;
if (schedstat_enabled()) {
struct sched_statistics *stats;
stats = __schedstats_from_se(curr);
__schedstat_set(stats->exec_max,
max(delta_exec, stats->exec_max));
}
curr->sum_exec_runtime += delta_exec;
schedstat_add(cfs_rq->exec_clock, delta_exec);
curr->vruntime += calc_delta_fair(delta_exec, curr);
update_deadline(cfs_rq, curr);
update_min_vruntime(cfs_rq);
if (entity_is_task(curr)) {
struct task_struct *curtask = task_of(curr);
trace_sched_stat_runtime(curtask, delta_exec, curr->vruntime);
cgroup_account_cputime(curtask, delta_exec);delta_exec가 양수일 때 sum_exec_runtime과 vruntime을 늘리고 update_deadline()을 호출한다. 이 호출은 데드라인 경계에 닿지 않으면 바로 돌아온다. 경계에 닿아도 runnable 엔티티가 하나뿐이면 이 함수에서 resched_curr()를 요청하지 않는다.
pick_next_entity(): 조건부 buddy 또는 EEVDF
* 2) pick the "next" process, since someone really wants that to run
* 3) pick the "last" process, for cache locality
* 4) do not run the "skip" process, if something else is available
*/
static struct sched_entity *
pick_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
/*
* Enabling NEXT_BUDDY will affect latency but not fairness.
*/
if (sched_feat(NEXT_BUDDY) &&
cfs_rq->next && entity_eligible(cfs_rq, cfs_rq->next))
return cfs_rq->next;
return pick_eevdf(cfs_rq);
}
static bool check_cfs_rq_runtime(struct cfs_rq *cfs_rq);
static void put_prev_entity(struct cfs_rq *cfs_rq, struct sched_entity *prev)
{
/*
* If still on the runqueue then deactivate_task()
* was not called and update_curr() has to be done:
*/
if (prev->on_rq)
update_curr(cfs_rq);
/* throttle cfs_rqs exceeding runtime */
check_cfs_rq_runtime(cfs_rq);
if (prev->on_rq) {
update_stats_wait_start_fair(cfs_rq, prev);
/* Put 'current' back into the tree. */
__enqueue_entity(cfs_rq, prev);
/* in !on_rq case, update occurred at dequeue */
update_load_avg(cfs_rq, prev, 0);
}
cfs_rq->curr = NULL;
}NEXT_BUDDY 기능이 켜져 있고 next가 존재하며 eligible일 때만 그 후보를 바로 반환한다. 그 외에는 pick_eevdf()로 간다. 이어지는 put_prev_entity() 부분은 이전 엔티티를 큐에 되돌리는 경우의 처리이며, 두 함수가 항상 연속 호출된다는 뜻은 아니다.
함수별 분석
v6.6의 EEVDF 선택에는 eligibility와 deadline 두 조건이 함께 들어간다. tasks_timeline은 서비스 순서로 정렬한 RB-tree이고, 하위 트리의 min_deadline을 보관해 빠른 탐색을 돕는다. 모든 엔티티가 들어 있는 별도의 “eligible tree”가 있는 것은 아니다.
pick_next_entity()와 EEVDF 내부 분기RUN_TO_PARITY 조건이 참이면 내부 탐색 전에 현재 엔티티를 반환할 수 있다. 일반적인 EEVDF 탐색은 오른쪽 하위 트리까지 항상 훑지 않고 min_deadline 보조값을 이용한다.
1. Eligibility
entity_eligible()은 현재 실행 중인 엔티티가 runnable이면 그 가중치도 평균에 포함한다. avg_vruntime() > se->vruntime 같은 나눗셈 결과의 단순 비교는 경계에서 정밀도를 잃으므로, 실제 검사는 가중 합 avg >= entity_key × load로 한다.
2. Deadline 선택
__pick_eevdf()는 eligible 현재 엔티티를 후보에 넣을 수 있고, RB-tree 노드와 하위 트리의 최소 데드라인을 비교한다. 탐색 결과가 없을 때 pick_eevdf()의 가장 왼쪽 엔티티 반환은 오류 진단을 동반한 예외 경로이지 정상적인 선택 규칙이 아니다.
| 함수 | 확인하는 값 | v6.6에서의 결정 |
|---|---|---|
| entity_eligible | avg_vruntime 누적값, avg_load, entity_key | 가중 비교로 서비스 부족 여부 판정 |
| __pick_eevdf | current, tree, min_deadline | 조건부 현재 유지 또는 eligible 최소 deadline 탐색 |
| pick_next_entity | NEXT_BUDDY, next, eligibility | 세 조건이 맞으면 next, 아니면 pick_eevdf |
구조체와 필드
vruntime과 deadline은 모두 가상시간 좌표다. avg_vruntime은 큐에 저장된 가중 상대합이며, 함수 avg_vruntime()이 현재 엔티티를 포함해 가중 평균 V를 계산한다. 같은 이름의 필드와 함수를 구별해야 한다.
세 줄은 별개 큐가 아니라 같은 fair 런큐의 평균 계산, 트리 탐색, 현재 실행 엔티티를 구분한 것이다. 현재 엔티티는 트리 밖에 있어도 후보가 될 수 있다.
| 필드 | 이 페이지에서 읽을 의미 | 근거 함수 |
|---|---|---|
| se.vruntime | 가중 서비스 누적 위치; 실제 경과시간과 다름 | update_curr |
| se.deadline / se.slice | 요청한 slice의 가상시간 끝점 | update_deadline |
| se.vlag | 배치에 보존하는 가상 lag; 실행 중에는 선택 시 deadline을 복사한 RUN_TO_PARITY 표식으로도 사용 | update_entity_lag, place_entity |
| cfs_rq.avg_vruntime / avg_load | 기준점 대비 가중 합과 가중치 합 | avg_vruntime, entity_eligible |
| tasks_timeline / min_deadline | vruntime 순 RB-tree와 하위 트리 최소 deadline | __enqueue_entity, __pick_eevdf |
실행 단계와 상태 변화
런타임 회계, 재스케줄 요청, 다음 엔티티 선택은 서로 다른 사건이다. 아래 분기는 update_curr()와 update_deadline()이 실제로 약속하는 결과만 표시한다.
delta_exec <= 0이면 update_curr()도 일찍 반환한다. 다른 경로의 재스케줄 요청 가능성까지 이 그림이 배제하지는 않는다.
| 조건 | fair.c에서 바뀌는 것 | 아직 단정할 수 없는 것 |
|---|---|---|
| delta_exec <= 0 | update_curr 조기 반환 | 이 호출에서 서비스 시간이 누적됐다고 볼 수 없음 |
| 양의 delta_exec, deadline 전 | 실행량·vruntime 갱신; 기존 deadline 유지 | 다음 엔티티 선택과 문맥 교환 |
| deadline 도달, nr_running = 1 | 새 slice·deadline 갱신 | 이 함수의 resched_curr 요청 |
| deadline 도달, nr_running > 1 | 새 deadline 설정과 resched_curr 요청 | 즉각적인 switch 또는 특정 엔티티 선택 |
해석할 때 지킬 경계
런큐의 가중 평균과 엔티티 key를 비교한다. tasks_timeline 자체를 “eligible 전용 트리”라고 부르면 후보 탐색을 잘못 읽게 된다.
update_deadline()은 se->vruntime에 가중 slice를 더한다. 실제 실행 시간과 동일한 축으로 눈금을 붙이면 안 된다.
resched_curr()는 다음 스케줄 결정을 요청한다. 여기서 어느 태스크로 즉시 전환했다고 결론 내릴 수 없다.
NEXT_BUDDY와 RUN_TO_PARITY는 조건이 맞을 때만 기본 EEVDF 후보 탐색에 앞선다. leftmost fallback은 정상 규칙이 아니다.
계측과 검증
코드에서 관찰점을 먼저 고른 뒤 실험 환경의 trace 설정을 확인해야 한다. 다음 표는 소스가 직접 보여 주는 범위와, 실제 실행에서 별도로 확인해야 할 범위를 나눈다. 이 글은 특정 보드에서 성능·지연시간을 측정한 결과를 제시하지 않는다.
| 관찰점 | 직접 확인되는 사실 | 그것만으로 알 수 없는 것 |
|---|---|---|
| trace_sched_stat_runtime | update_curr가 task 런타임과 vruntime을 기록하는 지점 | 어떤 엔티티가 다음으로 선택됐는지 |
| resched_curr 호출 | deadline 갱신 뒤 경쟁자가 있을 때 재스케줄 요청 | 즉시 문맥 교환됐는지 |
| pick_next_entity 반환 | buddy 조건 또는 EEVDF 결과 엔티티 | 실제 시스템의 지연시간 분포 |
추가 확인 사항
- 현재 엔티티가 트리 밖에 있을 때
__pick_eevdf()는 어떤 조건으로 그 엔티티를 후보에 남기는가? NEXT_BUDDY의 후보가 eligible하지 않으면 어느 경로로 떨어지는가?- 데드라인 경계에 닿았지만
nr_running이 1이면 이 함수에서 무엇이 갱신되고 무엇이 요청되지 않는가?