전체 흐름
RT class는 SCHED_FIFO와 SCHED_RR runnable task를 CPU별 rt_rq의 우선순위 큐에 둔다. 큐 선택·SMP 이동·runtime 제한은 서로 다른 판단이다. 이 글은 v6.6의 rt.c 경로를 기준으로 읽는다.
active.queue[prio]와 bitmap, rt_nr_running을 함께 읽는다.
affinity가 CPU 이동을 제한하고 RT bandwidth가 실행 가능 여부를 제한한다. 높은 RT priority만으로 두 제약을 넘지 않는다.
task_struct.rt의 sched_rt_entity; SCHED_FIFO/RRrq->rt.active의 priority별 list와 bitmappick_next_task_rt 경로로 task를 선택pick_next_rt_entity가 첫 설정 bit의 queue 앞 entity를 읽음rt_se_prio()가 돌려주는 내부 우선순위 인덱스(p->prio 또는 그룹의 최고 우선순위)가 작을수록 먼저 선택된다. 이 인덱스를 사용자 설정 sched_priority 값과 혼동하지 않는다. throttling된 RT runqueue는 별도 runnable 검사에서 제외될 수 있다.v6.6 kernel/sched/rt.c의 enqueue_task_rt·pick_next_rt_entity·pick_next_task_rt에 맞춘 관계도다. CFS의 sched_entity나 cfs_rq 경로가 아니다.
소스 코드 위치
고정 버전 원본 파일
kernel/sched/rt.c — RT queue, 선택, migration, bandwidth
kernel/sched/core.c — scheduler class의 선택 결과 소비kernel/sched/rt.c의 실제 함수 이름과 kernel/sched/core.c의 class 선택 경로를 v6.6 tag에서 대조한다.
버전 경계: v6.6은 pick_next_task_rt를 .pick_next_task에 연결한다. CONFIG_SMP에서는 .pick_task = pick_task_rt도 등록하므로, 이 hook 자체가 v6.18에 처음 생긴 것은 아니다. v6.18.37의 blocked-task·proxy execution 경로를 이 코드에 소급하지 않는다.
대표 코드
enqueue_task_rt
kernel/sched/rt.c, 1534–1550행
static void
enqueue_task_rt(struct rq *rq, struct task_struct *p, int flags)
{
struct sched_rt_entity *rt_se = &p->rt;
if (flags & ENQUEUE_WAKEUP)
rt_se->timeout = 0;
check_schedstat_required();
update_stats_wait_start_rt(rt_rq_of_se(rt_se), rt_se);
enqueue_rt_entity(rt_se, flags);
if (!task_current(rq, p) && p->nr_cpus_allowed > 1)
enqueue_pushable_task(rq, p);
}
읽는 법: enqueue_rt_entity가 priority queue를 갱신한다. pushable list는 현재 task가 아니고 둘 이상의 CPU가 허용되는 경우에만 대상이다.
pick_next_rt_entity에서 pick_next_task_rt까지
kernel/sched/rt.c, 1769–1822행
static struct sched_rt_entity *pick_next_rt_entity(struct rt_rq *rt_rq)
{
struct rt_prio_array *array = &rt_rq->active;
struct sched_rt_entity *next = NULL;
struct list_head *queue;
int idx;
idx = sched_find_first_bit(array->bitmap);
BUG_ON(idx >= MAX_RT_PRIO);
queue = array->queue + idx;
if (SCHED_WARN_ON(list_empty(queue)))
return NULL;
next = list_entry(queue->next, struct sched_rt_entity, run_list);
return next;
}
static struct task_struct *_pick_next_task_rt(struct rq *rq)
{
struct sched_rt_entity *rt_se;
struct rt_rq *rt_rq = &rq->rt;
do {
rt_se = pick_next_rt_entity(rt_rq);
if (unlikely(!rt_se))
return NULL;
rt_rq = group_rt_rq(rt_se);
} while (rt_rq);
return rt_task_of(rt_se);
}
static struct task_struct *pick_task_rt(struct rq *rq)
{
struct task_struct *p;
if (!sched_rt_runnable(rq))
return NULL;
p = _pick_next_task_rt(rq);
return p;
}
static struct task_struct *pick_next_task_rt(struct rq *rq)
{
struct task_struct *p = pick_task_rt(rq);
if (p)
set_next_task_rt(rq, p, true);
return p;
}
읽는 법: bitmap → priority list → (group RT가 켜진 경우) 하위 runqueue → task 순으로 선택하며 pick_next_task_rt가 다음 RT task 상태를 설정한다.
함수별 분석
큐 삽입과 task 선택을 하나의 원자적 단계로 읽지 않는다. 같은 CPU의 RT queue라도 preemption, migration, throttling 여부가 선택 가능성을 바꾼다.
p->rt 확인·wake 시 timeout 초기화rt.c의 enqueue_task_rt, pick_next_rt_entity, pick_next_task_rt 기준. pushable 후보는 모든 RT task가 아니다.
1. enqueue_task_rt
현재 task가 아니고 허용 CPU가 둘 이상이면 pushable 후보 등록. p->nr_cpus_allowed와 CPU affinity를 확인해야 push/pull 가능성을 설명할 수 있다.
2. pick_next_task_rt
pick_next_rt_entity는 bitmap과 queue에서 고른다. pick_next_task_rt가 set_next_task_rt를 호출. RT runtime 소진은 priority가 낮아서 밀린 상태와 다르다.
구조체와 필드
rt_rq.active는 priority별 runnable entity를, rt_bandwidth는 기간별 실행 예산을, pushable_tasks는 SMP 이동 후보를 나타낸다.
rq->rtrt_rq: runnable 수, highest priority, runtime 사용량rt_prio_arrayactive.queue[prio]와 bitmap이 선택 후보를 표현rt_bandwidthperiod와 runtime 예산; 소진 시 RT queue throttlingpushable_tasksSMP에서 옮길 수 있는 RT task 후보의 plist이 네 항목은 시간 순서가 아니라 서로 다른 자료구조/역할이다. rt_bandwidth는 queue의 다음 노드가 아니며 pushable list도 runnable RT task 전부가 아니다.
| 항목 | 읽는 법 |
|---|---|
rt_rq.active | priority list + bitmap; pick_next_rt_entity가 읽는다. |
rt_rq.rt_time·rt_throttled | runtime 사용량과 제한 상태. priority 순위와 구분한다. |
rt_bandwidth | period/runtime 정책 및 재충전 timer. |
pushable_tasks | 현재 실행 중이 아니면서 CPU 이동 조건을 만족한 task 후보. |
실행 단계와 상태 변화
RT task의 정상 실행은 priority queue에서 task를 고르는 과정이다. CPU 간 push/pull과 runtime throttle은 각각 SMP 조건과 예산 조건에서만 일어난다. update_curr_rt()는 rq->curr의 실행 시간을 계산하고, bandwidth가 켜져 있으며 runtime이 무제한이 아닌 RT runqueue에 사용량을 누적한다.
enqueue_rt_entity로 CPU별 RT queue에 반영pick_next_task_rt가 runnable 후보 중 선택실선 주 경로와 두 선택적 가지를 분리했다. push/pull 뒤에 반드시 throttle되는 순서가 아니며 enqueue/pick은 실패 자원 해제 단계도 아니다.
| 분기 | 조건 | 코드 관찰점 |
|---|---|---|
| priority 선택 | RT queue가 runnable | pick_next_rt_entity의 bitmap 및 list |
| push/pull | push의 출발 CPU overload / pull의 다른 CPU overload, 후보 우선순위, affinity와 migration 제한 | pushable_tasks·find_lowest_rq |
| throttle | RT runtime 제한이 켜져 있고 예산 소진 | rt_time·rt_throttled·period timer |
불변 조건과 실패 사례
RT 우선순위는 허용 CPU 집합을 무시하지 않는다. CPU 선택과 push/pull에서 cpuset·affinity 조건을 함께 본다.
rt_rq.active에 있는 runnable task와 pushable_tasks에 든 이동 후보는 같은 집합이 아니다.
높은 priority task가 선택되지 않았을 때 priority 순서와 runtime 소진을 구분한다. group RT 경로의 rt_rq_throttled()는 rt_throttled && !rt_nr_boosted를 검사한다. 우선순위 상속 등으로 boost된 entity가 있으면 flag 하나만으로 queue가 차단되었다고 단정하지 않는다.
계측과 검증
서로 다른 원인을 한 tracepoint로 단정하지 않는다. RT task의 wakeup·switch, priority 변경, runtime 설정을 나눠 확인한다.
| 관찰점 | 해석할 것 |
|---|---|
sched_wakeup / sched_switch | RT task가 runnable이 된 시점과 실제 CPU 실행 시점을 구분한다. |
sched_pi_setprio | priority inheritance에 따른 유효 우선순위 변화를 본다. 모든 RT enqueue의 증거는 아니다. |
/proc/sys/kernel/sched_rt_period_us · sched_rt_runtime_us | RT bandwidth 정책의 현재 설정을 읽는다. 값만으로 특정 task의 throttling 발생을 확정하지 않는다. |
cyclictest | 전체 wakeup latency의 관찰 도구이며, 큐·migration·throttle 중 어느 원인인지 추가 trace와 대조한다. |
v6.6 코드 대조: rt.c의 update_curr_rt와 period timer, push/pull 분기를 함께 확인한다.
추가 확인 사항
active.bitmap의 첫 priority bit와 해당 list의 첫 entity가 어떤 조건에서 갱신되는가?pushable_tasks에 등록되지 않는 RT task는 어느 경로에서 CPU 이동이 제한되는가?- RT task가 runnable인데 선택되지 않을 때 priority 순서, CPU affinity,
rt_throttled중 무엇이 원인인가? - v6.6의
.pick_next_taskhook이 선택 후 상태 설정을 어디에서 수행하는가?