Idle-task Scheduler
어떠한 스케줄러보다 우선 순위가 낮아 다른 스케줄러에서 태스크가 동작해야 하는 상황이되면 언제나 preemption 된다. idle 태스크는 core 마다 1개씩 지정되어 있어 다른 cpu로의 migration이 필요 없다.
- 각 core의 부트업 처리가 완료되면 cpu_startup_entry() 함수를 호출하여 idle 태스크로의 역할을 수행한다.
- 부트업 프로세스가 사용하는 메모리 환경을 그대로 사용한다. (init_mm 공유)
- 참고: cpu_startup_entry() – cpuidle | 문c
cpuidle 드라이버
Linux cpuidle 드라이버는 시스템의 에너지 효율성을 향상시키기 위해 CPU를 자동으로 적극적으로 사용하지 않는 상태로 유지하는 방법을 제공한다. 이 드라이버는 CPU가 idle 상태에 도달하면 시스템에 허용된 대로 CPU의 속도와 전력 소비를 조정하여 에너지 효율성을 최적화한다.
cpuidle 드라이버는 다양한 모드를 제공한다. 가장 일반적인 것은 “C0” 모드와 “C1” 모드이다. C0 모드는 CPU가 활성화되어있는 상태이며, C1 모드는 CPU가 유휴 상태에 도달한 상태이다. 더 높은 수준의 C 모드는 더 많은 전력 절약을 제공한다.
cpuidle 드라이버는 시스템의 CPU 사용량에 따라 CPU를 적극적으로 사용하지 않는 상태로 유지하므로 시스템 성능에 큰 영향을 미칠 수 있다. 따라서 적절한 설정이 필요하다. 일반적으로, 사용자는 드라이버의 설정을 조정하여 시스템 성능과 전력 효율성을 최적화할 수 있다.
c mode
인텔이 정의한 “C 모드”는 CPU의 전력 소비를 최적화하기 위한 여러 가지 상태 중에서 가장 저전력 상태를 의미한다. 일반적으로 CPU가 전력을 소모하는 가장 큰 원인은 전기적으로 활성화된 논리 회로들이 전류를 흐르게 하여 발생하는 누설 전류이다. C 모드는 이러한 누설 전류를 최소화하기 위해 CPU를 깊은 수면 상태로 전환시키는 방식으로 전력을 절약한다.
C 모드에는 여러 가지 서브 모드가 있으며, 이들 모드는 CPU에서 발생하는 전력 소모의 수준에 따라 나뉜다. 대표적인 C 모드로는 C1, C2, C3 등이 있고, 이들 모드는 숫자가 증가할수록 CPU가 더 깊은 수면 상태로 전환되어 전력 소모가 더욱 감소한다.
C 모드를 사용하면 전력 소모를 크게 감소시킬 수 있으므로, 모바일 기기나 랩톱과 같은 이동성이 있는 장치, 서버와 같은 대규모 컴퓨팅 시스템 등에서 효과적으로 사용된다. C 모드는 CPU의 성능을 저하시키므로, 시스템의 성능 요구사항과 전력 효율성 요구사항을 적절히 고려하여 사용해야 한다.
다음 디바이스 트리 예에서는 cpu의 deep-sleep 전환 시에 소요되는 시간 정보를 보여준다.
arch/arm64/boot/dts/freescale/fsl-ls1046a.dtsi
idle-states { /* * PSCI node is not added default, U-boot will add missing * parts if it determines to use PSCI. */ entry-method = "psci"; CPU_PH20: cpu-ph20 { compatible = "arm,idle-state"; idle-state-name = "PH20"; arm,psci-suspend-param = <0x0>; entry-latency-us = <1000>; exit-latency-us = <1000>; min-residency-us = <3000>; }; };
select_task_rq_idle()
kernel/sched/idle_task.c
select_task_rq_idle(struct task_struct *p, int cpu, int sd_flag, int flags) { return task_cpu(p); /* IDLE tasks as never migrated */ }
migration을 허용하지 않는다.
check_preempt_curr_idle()
kernel/sched/idle_task.c
/* * Idle tasks are unconditionally rescheduled: */ static void check_preempt_curr_idle(struct rq *rq, struct task_struct *p, int flags) { resched_curr(rq); }
항상 preemption 한다.
dequeue_task_idle()
kernel/sched/idle_task.c
/* * It is not legal to sleep in the idle task - print a warning * message if some code attempts to do it: */ static void dequeue_task_idle(struct rq *rq, struct task_struct *p, int flags) { raw_spin_unlock_irq(&rq->lock); printk(KERN_ERR "bad: scheduling from the idle thread!\n"); dump_stack(); raw_spin_lock_irq(&rq->lock); }
idle 태스크는 엔큐 및 디큐 개념이 없다. 따라서 호출되는 경우 경고 메시지를 출력한다.
task_tick_idle()
kernel/sched/idle_task.c
static void task_tick_idle(struct rq *rq, struct task_struct *curr, int queued) { }
task 틱마다 아무런 동작도 하지 않는다.
update_curr_idle()
kernel/sched/idle_task.c
static void update_curr_idle(struct rq *rq) { }
아무런 동작도 하지 않는다.
switched_to_idle()
kernel/sched/idle_task.c
static void switched_to_idle(struct rq *rq, struct task_struct *p) { BUG(); }
설계상 이 idle 태스크로의 스위칭은 불가능하다.
prio_changed_idle()
kernel/sched/idle_task.c
static void prio_changed_idle(struct rq *rq, struct task_struct *p, int oldprio) { BUG(); }
idle 태스크 자체가 가장 낮은 순위의 태스크로 별도로 우선 순위를 변경할 수 없다.
get_rr_interval_idle()
kernel/sched/idle_task.c
static unsigned int get_rr_interval_idle(struct rq *rq, struct task_struct *task) { return 0; }
idle 태스크는 preemption되지 않는 경우 계속 수행되어야 하므로 idle 스케줄러는 인터벌을 사용하지 않는다.
다음 idle 태스크 선택
pick_next_task_idle()
kernel/sched/idle_task.c
static struct task_struct * pick_next_task_idle(struct rq *rq, struct task_struct *prev) { put_prev_task(rq, prev); schedstat_inc(rq, sched_goidle); return rq->idle; }
기존 태스크에 대한 런타임 계산 등을 끝내고 idle 태스크를 반환한다.
put_prev_task_idle()
kernel/sched/idle_task.c
static void put_prev_task_idle(struct rq *rq, struct task_struct *prev) { idle_exit_fair(rq); rq_last_tick_reset(rq); }
idle 태스크의 실행 시각이 끝난 경우 이에 대한 처리를 수행한다.
- 코드 라인 3에서 런큐의 러너블 로드 평균을 갱신한다.
- 코드 라인 4에서 nohz full 을 허용한 시스템인 경우 last_sched_tick에 현재 시각(jiffies)를 기록한다.
Idle-task 스케줄러 OPS
kernel/sched/idle_task.c
/* * Simple, special scheduling class for the per-CPU idle tasks: */ const struct sched_class idle_sched_class = { /* .next is NULL */ /* no enqueue/yield_task for idle tasks */ /* dequeue is not valid, we print a debug message there: */ .dequeue_task = dequeue_task_idle, .check_preempt_curr = check_preempt_curr_idle, .pick_next_task = pick_next_task_idle, .put_prev_task = put_prev_task_idle, #ifdef CONFIG_SMP .select_task_rq = select_task_rq_idle, #endif .set_curr_task = set_curr_task_idle, .task_tick = task_tick_idle, .get_rr_interval = get_rr_interval_idle, .prio_changed = prio_changed_idle, .switched_to = switched_to_idle, .update_curr = update_curr_idle, };
참고
- Scheduler -1- (Basic) | 문c
- Scheduler -2- (Global Cpu Load) | 문c
- Scheduler -3- (PELT) | 문c
- Scheduler -4- (Group Scheduling) | 문c
- Scheduler -5- (Scheduler Core) | 문c
- Scheduler -6- (CFS Scheduler) | 문c
- Scheduler -7- (Preemption & Context Switch) | 문c
- Scheduler -8- (CFS Bandwidth) | 문c
- Scheduler -9- (RT Scheduler) | 문c
- Scheduler -10- (Deadline Scheduler) | 문c
- Scheduler -11- (Stop Scheduler) | 문c
- Scheduler -12- (Idle Scheduler) | 문c – 현재 글
- Scheduler -13- (Scheduling Domain 1) | 문c
- Scheduler -14- (Scheduling Domain 2) | 문c
- Scheduler -15- (Load Balance 1) | 문c
- Scheduler -16- (Load Balance 2) | 문c
- Scheduler -17- (Load Balance 3 NUMA) | 문c
- Scheduler -18- (Load Balance 4 EAS) | 문c
- Scheduler -19- (초기화) | 문c
- PID 관리 | 문c
- do_fork() | 문c
- cpu_startup_entry() – cpuidle | 문c
- 런큐 로드 평균(cpu_load[]) – v4.0 | 문c
- PELT(Per-Entity Load Tracking) – v4.0 | 문c