Scheduler · Linux v6.6 · source + diagram note

RT scheduler

RT queue와 priority 선택, push/pull, runtime 제한을 Linux v6.6의 pick_next_task_rt 경로에서 분리해 봅니다.

전체 흐름

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만으로 두 제약을 넘지 않는다.

그림 1. v6.6 RT task의 CPU별 priority queue와 선택 경계
RT tasktask_struct.rt의 sched_rt_entity; SCHED_FIFO/RR
CPU별 RT runqueuerq->rt.active의 priority별 list와 bitmap
.pick_next_task hookpick_next_task_rt 경로로 task를 선택
priority 탐색pick_next_rt_entity가 첫 설정 bit의 queue 앞 entity를 읽음
CPU affinity/cpuset 및 CPU 선택은 이 CPU별 큐에 도달하기 전의 제약이다. 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 여부가 선택 가능성을 바꾼다.

그림 2. v6.6 enqueue와 pick의 분리된 관찰 경로
큐에 넣기
enqueue_task_rtp->rt 확인·wake 시 timeout 초기화
enqueue_rt_entitypriority list와 bitmap 갱신
pushable 후보현재 task가 아니고 허용 CPU가 둘 이상이면 pushable 후보 등록
다음 task 고르기
runnable 검사RT runqueue가 선택 가능한지 확인
priority bitmap첫 bit의 list에서 RT entity 선택
pick_next_task_rt.pick_next_task 경로에서 task 선택
enqueue와 pick은 하나의 함수 호출 체인이 아니다. 사이에 다른 task의 enqueue/dequeue, preemption, migration, RT runtime 제한이 끼어들 수 있다.

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 이동 후보를 나타낸다.

그림 3. v6.6 RT queue·bandwidth·migration 자료구조
per-CPU rq->rtrt_rq: runnable 수, highest priority, runtime 사용량
rt_prio_arrayactive.queue[prio]와 bitmap이 선택 후보를 표현
rt_bandwidthperiod와 runtime 예산; 소진 시 RT queue throttling
pushable_tasksSMP에서 옮길 수 있는 RT task 후보의 plist
CPU별 큐우선순위 인덱스예산 제약SMP 후보

이 네 항목은 시간 순서가 아니라 서로 다른 자료구조/역할이다. rt_bandwidth는 queue의 다음 노드가 아니며 pushable list도 runnable RT task 전부가 아니다.

항목읽는 법
rt_rq.activepriority list + bitmap; pick_next_rt_entity가 읽는다.
rt_rq.rt_time·rt_throttledruntime 사용량과 제한 상태. priority 순위와 구분한다.
rt_bandwidthperiod/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에 사용량을 누적한다.

그림 4. v6.6 RT 선택과 조건부 migration·throttling
runnable RTenqueue_rt_entity로 CPU별 RT queue에 반영
priority pickpick_next_task_rt가 runnable 후보 중 선택
CPU 실행RT task runtime 누적
SMP 선택적 경로push는 현재 CPU의 overload와 이동 후보를, pull은 다른 overloaded CPU의 후보를 확인한다. 우선순위·affinity·migration 제한을 만족해야 하며 모든 pick의 필수 후속 단계가 아니다.
예산 선택적 경로RT runtime을 소진하면 throttle될 수 있다. period timer의 보충·조건을 확인해 다시 runnable이 된다.

실선 주 경로와 두 선택적 가지를 분리했다. push/pull 뒤에 반드시 throttle되는 순서가 아니며 enqueue/pick은 실패 자원 해제 단계도 아니다.

분기조건코드 관찰점
priority 선택RT queue가 runnablepick_next_rt_entity의 bitmap 및 list
push/pullpush의 출발 CPU overload / pull의 다른 CPU overload, 후보 우선순위, affinity와 migration 제한pushable_tasks·find_lowest_rq
throttleRT runtime 제한이 켜져 있고 예산 소진rt_time·rt_throttled·period timer

불변 조건과 실패 사례

priority와 affinity

RT 우선순위는 허용 CPU 집합을 무시하지 않는다. CPU 선택과 push/pull에서 cpuset·affinity 조건을 함께 본다.

queue와 candidate

rt_rq.active에 있는 runnable task와 pushable_tasks에 든 이동 후보는 같은 집합이 아니다.

throttle 해석

높은 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_switchRT task가 runnable이 된 시점과 실제 CPU 실행 시점을 구분한다.
sched_pi_setpriopriority inheritance에 따른 유효 우선순위 변화를 본다. 모든 RT enqueue의 증거는 아니다.
/proc/sys/kernel/sched_rt_period_us · sched_rt_runtime_usRT 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_task hook이 선택 후 상태 설정을 어디에서 수행하는가?