很早以前有一個問題:搞單片機的時候,在不做響應任何中斷的前提下,任何一個子產品代碼跑一個死循環,機器一定會卡死。但是到了接觸linux之後,發現不論是在核心中還是使用者層代碼中,while(1)把機器搞當機這樣的操作完全失效了,這是為什麼?
原來,這是周期性排程器的功勞,前提核心是搶占式核心
在系統時鐘中斷來之後,在中斷處理函數中會通過tick_periodic最終調到scheduler_tick,也就是一個"滴答"會調用一次這個函數,看下實作:
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;
sched_clock_tick();
rq_lock(rq, &rf);
walt_set_window_start(rq, &rf);
walt_update_task_ravg(rq->curr, rq, TASK_UPDATE,
walt_ktime_clock(), 0);
update_rq_clock(rq);
curr->sched_class->task_tick(rq, curr, 0);
rq_unlock(rq, &rf);
}
這個函數主要就是更新一些統計量例如rq的時鐘計數、cpu負載等,重點在curr->sched_class->task_tick(rq, curr, 0)會去調用目前程序所在排程類的task_tick方法來決定目前程序是不是應該被排程(真正的排程不在這裡是以隻是決定)。
例如rt:
static void task_tick_rt(struct rq *rq, struct task_struct *p, int queued)
{
......
if (--p->rt.time_slice) //如果時間片用完了,就該排程了
return;
......
//加到隊尾,設定TIF_NEED_RESCHED
for_each_sched_rt_entity(rt_se) {
if (rt_se->run_list.prev != rt_se->run_list.next) {
requeue_task_rt(rq, p, 0);
resched_curr(rq);
return;
}
}
}
又比如cfs:
task_tick_fair--->entity_tick--->check_preempt_tick:
check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
unsigned long ideal_runtime, delta_exec;
struct sched_entity *se;
s64 delta;
ideal_runtime = sched_slice(cfs_rq, curr);
//是不是運作足夠長時間了
delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;
if (delta_exec > ideal_runtime) {
resched_curr(rq_of(cfs_rq));
/*
* The current task ran long enough, ensure it doesn't get
* re-elected due to buddy favours.
*/
clear_buddies(cfs_rq, curr);
return;
}
......
//是不是有更可憐(遭受更多不公平)的程序需要被執行
se = __pick_first_entity(cfs_rq);
delta = curr->vruntime - se->vruntime;
if (delta < 0)
return;
if (delta > ideal_runtime)
resched_curr(rq_of(cfs_rq));
}
如上兩個排程類對task_tick的實作,當達到一定的條件時,都會為目前程序調用resched_curr設定TIF_NEED_RESCHED标志,一旦從中斷傳回核心空間或者從中斷傳回使用者空間時,都會檢查該标志進而隐式地調用schedule搶占目前程序。在不關中斷和進制搶占的時候,while(1);這種操作會先是被中斷打斷,在中斷傳回時可能就會被其他程序搶占。