Xen向GuestOS伸出了魔爪
之所以這麼寫這個標題,是因爲寫到這裏,我發現,這一部分的解析纔剛剛露出冰山一角,這一部分是指Xen作爲Hypervisor對操作GuestOS的支撐部分,我在想是不是要在這個文檔裏寫這個問題;因爲這。。。不屬於調度了吧(還是屬於?)?這已經屬於GuestOS的調度基礎支撐了。
RT調度的do_schedule()
Xen的RT調度入口函數是位於文件”./xen/common/sched_rt.c”中的static struct task_slice rt_schedule(conststruct scheduler *ops, s_time_t now,bool_t tasklet_work_scheduled)。主要處理:任務選擇,預算結算,返回所選擇的任務、將運行時間以及是否遷移自其他pcpu。本章將會分析rt_schedule()函數的實現。
1. cpumask_clear_cpu(cpu,&prv->tickled);
從tickled中清楚當前pcpu位圖,tickled置位是因爲執行了runq_tickle(),runq_tickle()有點發現優先級排序不對了,會整理,然後就會產生一次軟中斷調度(查找到3處使用runq_tickle()的位置,分別是vcpu剛醒、上下文切換時剛保存完當前vcpu現場,調度完)。
2. burn_budget(ops,scurr, now);
將當前vcpu的預算結算掉(idle_vcpu除外),對於RT調度,系統定義了私有的數據結構struct rt_vcpu[63],其中的last_start元素(snext->last_start = now; 在下一個vcpu投入運行前更新了此元素。)記錄上次投入運行的時間,可以計算出當前vcpu已經執行的時長[65],在計算完後也要再次將last_start=now;cur_budget記錄vcpu運行時預算(在此處RT調度的預算只可能被減少,另外有一個timer會調用repl_timer_handler()來爲失去預算的vcpu補充預算。詳見章節RT調度的預算補充),當cur_budget<=0,如果flags元素(flags同樣是struct rt_vcpu的元素,定義有詳盡的標誌位)中存在RTDS_exteratime標誌(根據註釋,RTDS_extratime標誌是想讓進程有多x輪的預算,以至於在沒有優先級比他高的vcpu,他就可以一直跑?因爲默認sched_init_vcpu()創建vcpu時都會alloc_vdata(),alloc_vdata()是scheduler的接口,在RT的實現都給加了此標誌。只有在hypercall的do_domctl() XEN_DOMCTL_SCHEDOP_putvcpuinfo操作是沒有XEN_DOMCTL_SCHEDRT_extra標誌時,纔會被清楚此標誌位。),則提升其優先級、補充其預算(我很懷疑這種操作的合理性),否則置位flags元素的RTDS_depleted標誌(表示vcpu預算耗盡,在之後的處理中將會被加入到預算耗盡的vcpu鏈表,等待補充預算。);
3. tasklet_work_scheduled(tasklet需要處理標誌)被置位時:snext = rt_vcpu(idle_vcpu[cpu]);
有tasklet需要正式調度過去執行(此種情況發生在tasklet_list的任務在tasklet觸發後,發現需要此pcpu執行,即當前pcpu的tasklet_work_to_do被置位了,於是在調度時遇到,則需要執行。),就不會發生runq_pick()(算法選擇可投入執行的vcpu)操作,即將要投入運行的任務將會是idle_vcpu,只需做一些記錄即可,作爲空閒vcpu會不停地執行tasklet。
4. snext= runq_pick(ops, cpumask_of(cpu));
遍歷可執行vcpu鏈表,綜合vcpu所處Domain的有效pcpu位圖(vcpu所在struct domain中有struct cpupool,cpupool內cpu_valid用位圖表示可用pcpu們。)、vcpu的硬親和pcpu位圖(vcpu中有cpu_hard_affinity位圖,表示vcpu僅可在哪幾個pcpu上執行。)和實際pcpu位圖(smp_processor_id()可獲得。),得到下一個運行的vcpu(找到的第一個vcpu即爲投入運行的vcpu,於是此鏈表順序很重要,包含調度原則。)。需要注意的是這裏的選擇vcpu並不考慮剩餘預算的問題,如果沒有預算,系統將會失敗,也就是說,RunQ中vcpu必有預算。
5. if (snext == NULL ) snext = rt_vcpu(idle_vcpu[cpu]);
如果沒有合適的vcpu被選到,則執行idle_vcpu。
6. 查看是否需要執行之前的vcpu
if( !is_idle_vcpu(current) &&
vcpu_runnable(current) &&
scurr->cur_budget > 0&&
( is_idle_vcpu(snext->vcpu) ||compare_vcpu_priority(scurr, snext) > 0 ) )
snext = scurr
如果之前的vcpu,不是idle_vcpu、可執行(沒有暫停標誌和暫停計數,並且其所在域也沒有)、預算存在並且新選的vcpu是idle_vcpu或比之前vcpu優先級低,則不會切換。
總結成人話就是:如果選完以後發現是空閒vcpu並且原先的vcpu不空閒,那麼繼續執行之前的vcpu;如果選出的vcpu優先級不如之前的高,並且之前的vcpu還有預算,那麼還是執行之前的vcpu。這是RT調度的重要原則之一。
7. if ( snext != scurr &&
!is_idle_vcpu(current) &&
vcpu_runnable(current) )
__set_bit(__RTDS_delayed_runq_add, &scurr->flags);
如果下一個要投入運行的vcpu不是當前vcpu,並且當前運行的vcpu也不是idle_vcpu,並且當前vcpu還是可執行;置位struct rt_vcpu->flags的__RTDS_delayed_runq_add(在rt_context_saved()這個vcpu會被加入到RunQ,rt_context_saved()是在啓動投運vcpu前執行的context_saved()裏調用。)。
更新下一個投入執行的vcpu的投入時間
9. if ( snext->vcpu->processor != cpu ){
snext->vcpu->processor = cpu;
ret.migrated = 1;
}
檢查此vcpu是否遷移自非當前pcpu(跨pcpu的任務遷移必然會引起大量cache miss,降低效率,因此原則上跨pcpu的調度應該有一定阻尼,很明顯Xen的RT沒有做。),對於產生遷移的需要更新vcpu->processor;ret是是函數返回值task_slice數據結構,置位其migrated元素,提示遷移發生(在schedule()中R next_slice = sched->do_schedule(sched, now,tasklet_work_scheduled); 也有介紹, if ( next_slice.migrated ) sched_move_irqs(next); 同樣根據此處判斷結果。)。
10. ret.time= snext->cur_budget;
cur_budget存有當前vcpu將會被投入運行的時長,放入在返回值struct task_slice中time元素(在schedule()中設置投運任務的運行時間時使用此元素。)。
11. ret.task= snext->vcpu;
將選定的vcpu設置到返回值中。
RT調度的預算補充
當vcpu的預算消耗完,需要補充預算的vcpu們會被放到ReplQ鏈表,由軟定時器repl_timer[83]觸發的處理函數repl_timer_handler()將會爲之補充預算。下面將分析repl_timer_handler()功能。
1. 遍歷ReplQ鏈表,在遍歷過程中,會不斷給ReplQ中的vcpu元素補充預算、按週期延展deadline、將優先級置0,並加入到臨時鏈表等待進一步處理;預算補充完後,檢測如果vcpu還在RunQ/DeplQ,則會拔、插(先從RunQ/DeplQ鏈表取出,再插入,可以重新決定vcpu進RunQ/DeplQ,如果進入RunQ還可以重新排列其位置。)回RunQ/DeplQ(此處預算剛補充完畢,應該會插回RunQ。)。再次遍歷過程中,如果遇到deadline未到的vcpu應截止遍歷,僅處理已補充預算的vcpu。
2. 遍歷上一步補充過預算的vcpu,如果發現vcpu正在運行,但優先級並不比RunQ上的下一個vcpu高(因爲Xen的RT調度所有pcpu共用待RunQ鏈表,經過重新充能先後時可能出現低優先級被先執行的情況的。),這是不合理的,觸發runq_tickle()(runq_tickle()會檢查指定scheduler的新加vcpu是否有機會調度,如果有,則觸發調度軟中斷。);如果vcpu不在執行,判斷其RTDS_depleted標誌位(判斷完清除之)並確認其在RunQ/DeplQ,同樣觸發runq_tickle()。
RT調度的喚醒
vcpu任務的喚醒由rt_vcpu_wake()執行,對於以下vcpu狀態:
1. vcpu正在指定的pcpu執行,更改per-cpu狀態vcpu_wake_running,喚醒結束。
2. vcpu處於RunQ/DeplQ,更改per-cpu狀態vcpu_wake_onrunq,喚醒結束。
喚醒vcpu可直接執行完畢。
對於普通情況,檢測vcpu處於可執行狀態/不可執行狀態,相應的更改per-cpu狀態標誌。如果vcpu的deadline已超時,更新其deadline、預算、last_start=now、優先級置0(rt_update_deadline()操作。);否則無需操作。如果vcpu RTDS_scheduled(標誌vcpu在pcpu上執行。)置位,則置位RTDS_delayed_runq_add,以便於在此vcpu經過rt_context_saved()時會將之加入RunQ/DeplQ;如果之前判斷deadline超時,則此處需要將其在ReplQ上插拔一次,以防止其在ReplQ鏈表不存在或順序問題,完成後喚醒即結束。對於vcpu RTDS_scheduled未置位,即vcpu未在pcpu上執行,則需要將其插入ReplQ、RunQ/DeplQ、然後用runq_tickle()檢查一下是否該觸發軟中斷(如果被喚醒vcpu優先級高的話。),喚醒結束。
RT調度的優先級
RT調度的優先級首先由struct rt_vcpu->priority_level決定,priority_level越小的vcpu優先級越高;在同優先級的情況下,會比較structrt_vcpu->cur_deadline,deadline越近的vcpu優先級越高。具體可查看函數compare_vcpu_priority()。
RT調度的數據結構
RT調度有3個鏈表(每個RT調度的scheduler實體有3個鏈表,多個scheduler(RT/Credit)共存是可能的。),由所有的pcpu(源文件中存在註釋表述的是CPU pool,用pcpu是爲了不用解釋CPU pool。)共享一個是有序(排列順序是優先級高在前,同優先級的deadline緊急的靠前。)的RunQ鏈表,鏈接有預算且可執行的vcpu;一個是無序DeplQ鏈表,鏈接沒有預算等待補充預算之後就能跑的vcpu;最後是一個有序(排列順序同樣是優先級高在前,同優先級的deadline緊急的靠前與RunQ用同樣的插入函數和優先級比較方式。)的ReplQ鏈表,鏈接等待補充預算的vcpu。
RunQ和DeplQ是互斥的兩個鏈表,即一個vcpu不能同時在RunQ和DeplQ上;但一個vcpu應該同時在RunQ和ReplQ上,或同時在DeplQ和ReplQ上。
ReplQ只能在vcpu喚醒、初創的時候,插入RunQ/DeplQ(或is_running態)纔會被插入ReplQ;當vcpu不再被調度時,需要離開ReplQ(也會離開RunQ/DeplQ)。
如果vcpu是由於進入睡眠(rt_vcpu_sleep()是調度器接口sleep的RT實現)、被銷燬(rt_vcpu_remove()是調度器接口remove_vcpu的RT實現,在銷燬vcpu(sched_destroy_vcpu())、遷移domain(sched_move_domain())被使用。)、遷移離開調度(rt_context_saved()是調度器context_saved的RT實現,),則離開3大鏈表。
RT調度的deadline
RT調度的deadline位於rt_vcpu->cur_deadline,唯一可以補充cur_deadline的位置是rt_update_deadline(),而在普通運行階段(vcpu初始化、vcpu喚醒也會執行rt_update_deadline()操作。),唯有軟定時器repl_tmer的處理函數repl_timer_handler()會調用rt_update_deadline()爲vcpu更新deadline,軟定時器repl_timer的觸發時間總是按照RunQ即將投入運行的任務的cur_deadline設定。
進一步
經過上述分析可以全面的瞭解Xen調度的前因後果,RT調度的實現考量,之後還需要進一步分析以及目前Xen的調度在ARM平臺存在的一些問題如下。
1. 總結分析可能觸發調度的點。
2. Xen對vcpu的切換過程,如寄存器保存、新寄存器值設入。
3. Xen在已啓動的pcpu中如何啓動其他pcpu。
在vcpu_initialise()時,會有
vcpu->arch.saved_context.sp = (register_t)v->arch.cpu_info;
vcpu->arch.saved_context.pc = (register_t)continue_new_vcpu;
是如何工作的。
4. 目前的RT調度,各CPU均採用同一任務鏈表,調度需要獲取鎖,是存在存在競爭、浪費的。
5. Xen跨CPU調度僅根據一些固定設置,不是阻尼的方式防止,這很明顯會引起不必要的cachemiss降低效率。
6. Xen不能感知GuestOS的空閒、於是當vcpu執行idle線程時,Xen也不會有動作,是否可以修改GuestOS的idle線程。
7. Xen對大小核並未考慮。