一次調度的處理
Xen調度入口函數位於文件”./xen/common/schedule.c”中的static voidschedule(void)。調度的觸發方式會有多種,在Xen中確定已知的有定時觸發、中斷返回觸發、任務阻塞觸發(分析能觸發調度的時機是非常重要的,但由於能力所限,短時間內不能枚舉,此處僅列舉分析過程中確定可以觸發調度的時機。)。所有觸發的調度最終都會傳導到schedule。
在schedule()
tasklet_work(tasklet_work=&this_cpu(tasklet_work_to_do)是當前pcpu變量,由DECLARE_PER_CPU定義。)是當前pcputasklet work的狀態標誌,可以爲TASKLET_enqueued、TASKLET_scheduled以及TASKLET_enqueued|TASKLET_scheduled,如果僅有TASKLET_scheduled置位,表示不需要處理,否則調度器會執行idle_vcpu,idle_vcpu會執行do_tasklet(),並調整標誌位。do_tasklet()會調用do_tasklet_work(),在do_tasklet_work()中,tasklet的func會被執行;只有在tasklet鏈表中不再有元素時,do_tasklet()會清除TASKLET_enqueued標誌位。在TASKLET_enqueued標誌位被清除時schedule()會清除TASKLET_scheduled。
sd是當前pcpu的schedule_data,其中包含鎖、struct vcpu *curr(當前正在運行的任務vcpu數據結構指針。)、void *sched_priv(這是調度器的私有數據結構入口。)、struct timer s_timer(調度定時器,軟中斷將會處理其內function函數。)、urgent的vcpu計數。各pcpu的struct timers數據(struct timers也是per-cpu變量,依舊是繼承自Linux的各pcpu變量定義,struct timers並未被DECLARE_PER_CPU引用聲明,由宏DEFINE_PER_CPU定義,DEFINE_PER_CPU是對per-cpu變量的真實定義,而DECLARE_PER_CPU是對per-cpu變量的引用聲明。&this_cpu(a)訪問本pcpu的per-cpu變量&per_cpu(a,cpu0)訪問cpu0核的per-cpu變量。)中有struct timer,分爲struct timer **heap(根據add_to_heap()和remove_from_heap()的分析,struct timer **heap是一個timer堆,大概按照超時順序排列。如果發現所插入timer爲堆內首個timer,則會軟件產生一個TIMER_SOFTIRQ,堆滿才用鏈表。),struct *list(struct timers中的struct timer list是一個timer鏈表,按照超時時間大小排列,在struct timer ** heap空間不夠時,纔會用鏈表。發現所插入timer是鏈表第一個元素時會軟件產生TIMER_SOFTIRQ。),struct timer *running(struct timers中的struct timer running在timer的function被執行時(見20.中timer_softirq_action()),會從timer堆或timer鏈表中移除,然後實際執行時(execute_timer())被加入到running鏈表。),struct list_head inactive(struct timers中的struct timer inactive是timer被deactivate之後的存放之處。),此處將struct timer->status設置爲TIMER_STATUS_inactive,並加入timer所屬pcpu下struct timers數據的inactive鏈表。(timers和timer有着完整的機制描述,可在軟定時器機制struct timers和struct timer查看。)
3. next_slice= sched->do_schedule(sched, now, tasklet_work_scheduled);
調用當前pcpu的調度器計算下一個要投入運行的任務,其中next_slice包含3個元素:structvcpu *task( vcpu是Xen調度的基本單位。)、s_time_t time(指示當前task_slice將會運行多久,主要來自調度器自定數據結構中xx_vcpu->cur_budget。)、bool_t migrated(指示這個任務是否是從其他pcpu遷移到這裏的。);sched爲調度器結構體,指向當前pcpu調度器指針;do_schedule()爲調度器調度計算函數入口[49];now=NOW()是CPU時間;tasklet_work_scheduled是tasklet需要調度投入執行的標誌(tasklet_work_scheduled置位會使調度器在選擇任務使選中idle_vcpu,idle_vcpu內有tasklet處理函數入口do_tasklet()。)。
4. next =next_slice.task;
next是將會被投入運行的任務。
5. sd->curr= next;
至此,schedule函數選好了投入運行的任務,記錄到sd的curr(可執行狀態RUNSTATErunnable遇到tasklet佔用、高優先級的vcpu等是會被調度出去的。)。
if (next_slice.time >= 0 ) set_timer(&sd->s_timer, now +next_slice.time);
next_slice.time只有在下一個任務使idle_vcpu的時候纔會小於0;set_timer()將會給當前pcpu的調度定時器續時,時長決定於next_slice.time。
7. 省略
TRACE_3D()、TRACE_4D()會記錄調度的切換,不予分析;當計算完發現即將投入運行的任務還是之前的任務,則會直接投入運行。
8. vcpu_runstate_change();
修改被調度出局任務的runstate,runstate記錄有vcpu在各狀態停留時間,根據被調度出局的原因:阻塞、離線、可執行[52],是一個struct vcpu_runstate_info數據結構,包含int state(state元素記錄vcpu當前所處狀態:RUNSTATE_running、RUNSTATE_runnable、RUNSTATE_blocked、RUNSTATE_offline)、uint64_t state_entry_time(vcpu進入當前狀態的時間。)、uint64_t time[4](state元素的4個狀態,分別對應數組中的四個元素,記錄有當前vcpu在四個狀態的累計時間。)。
9. prev->last_run_time= now;
記錄被調度出局的vcpu的出局時間。
10. vcpu_runstate_change(next,RUNSTATE_running, now);
記錄即將投運任務的runstate。
11. next->is_running= 1;
標誌着vcpu正在運行中。
12. stop_timer(&prev->periodic_timer);
關閉調度出局的vcpu的periodic_timer,其處理函數爲vcpu_periodic_timer_fn()( init_timer(&v->periodic_timer,vcpu_periodic_timer_fn, v, v->processor);),periodic_timer的作用是向vcpu定時發出虛擬中斷信號(通過evtchn_port_set_pending()向vcpu發送了一箇中斷信號,);此處將之關閉即不再發出此虛擬中斷。
13. if (next_slice.migrated ) sched_move_irqs(next);
對於即將投入運行的vcpu,如果是從別的cpu遷移過來的,則需要調整他的IRQ到當前的cpu上(這又是一個大活兒啊,另外還涉及到Xen對中斷機制的操作,)。
14. vcpu_periodic_timer_work(next);
即將投入運行的vcpu的periodc_timer的啓動:首先檢測當前是否到vcpu的週期,決定是否發出virq;然後檢查並遷移periodic_timer到在當前pcpu名下(migrate_timer()完成);最後設置vcpu下一個virq發生點。
15. context_switch(prev,next);
進行了任務切換( context_switch()需要做很多很多事,不過功能卻只有一個,任務切換,於是先不分析細節。)。
描述Xen的調度單位vcpu
structvcpu 分析
vcpu是Xen的基本調度單位,其數據結構structvcpu複雜(還好我已經搞明白了不少。),下面將分析其關鍵元素。
int |
vcpu_id; |
vcpu識別號 |
int |
processor; |
vcpu運行的pcpu號 |
vcpu_info_t |
*vcpu_info; |
NC |
struct domain |
*domain; |
指向vcpu所在的域 (即描述虛擬機的主數據結構) |
s_time_t |
periodic_period; |
時間計數,週期時長 |
s_time_t |
periodic_last_event; |
時間計數,上次週期開始時間 |
struct timer |
periodic_timer; |
週期定時器,給vcpu提供虛擬中斷的 |
struct timer |
poll_timer; |
/* timeout for SCHEDOP_poll */ |
void |
*sched_priv; |
指向vcpu所處調度器的私有數據結構,與調度算法密切相關,由算法實現者定義,對於RT調度,此指針指向struc rt_vcpu |
struct vcpu_runstate_info |
runstate; |
記錄vcpu運行狀態、進入此運行狀態的時間,在各個運行狀態累積時間。 |
uint64_t |
last_run_time; |
記錄調度出去的時間 |
bool |
is_initialised; |
/* Initialization completed for this VCPU? |
bool |
is_running; |
正在pcpu上運行的標誌 |
unsigned long |
pause_flags; |
vcpu被暫停標誌 |
atomic_t |
pause_count; |
被暫停計數 |
cpumask_var_t |
cpu_hard_affinity; |
允許vcpu執行的pcpu位圖 |
cpumask_var_t |
cpu_hard_affinity_tmp; |
/* Used to change affinity temporarily. */ |
cpumask_var_t |
cpu_hard_affinity_saved; |
/* Used to restore affinity across S3. */ |
cpumask_var_t |
cpu_soft_affinity; |
/* Bitmask of CPUs on which this VCPU prefers to run. */ |
struct arch_vcpu |
arch;
|
記錄有ARM的各個寄存器值(含pc、sp)以及其他不認識的,在任務切換時大顯身手 |
cpumask_var_t |
vcpu_dirty_cpumask; |
/* Bitmask of CPUs which are holding onto this VCPU's state. */ |
struct vcpu還有很多很多元素,以上元素佔比<50%,其他元素與調度關係不大,省略。 |
structrt_vcpu分析
struct rt_vcpu是RT調度專有數據結構,如下爲全部元素:
struct list_head |
q_elem |
作爲一個鏈表元素,可能被放在RunQ鏈表、DeplQ鏈表或爲空。 |
struct list_head |
replq_elem |
作爲鏈表元素可能被放在ReplQ或爲空 |
s_time_t |
period |
設定參數,vcpu的虛擬中斷觸發週期; 默認週期RTDS_DEFAULT_PERIOD爲10毫秒 |
s_time_t |
budget |
設定參數,vcpu作爲Xen中的任務,被調度時要設定的預算值,即允許持續執行的時間; 默認預算RTDS_DEFAULT_BUDGET爲4毫秒 |
s_time_t |
cur_budget |
運行時參數,vcpu剩餘預算值 |
s_time_t |
last_start |
運行時參數,vcpu開始執行時間 |
s_time_t |
cur_deadline |
運行時參數,vcpu的deadline |
struct rt_dom |
*rt_dom |
NC |
struct vcpu |
*vcpu |
指向所描述vcpu主體 |
unsigned |
priority_level |
vcpu的調度優先級 |
unsigned |
flag |
標誌位 RTDS_scheduled表示此vcpu是否正在pcpu上執行; RTDS_delayed_runq_add表示此vcpu被調度暫停執行時,將會被加入Runq還是DeplQ; RTDS_depleted表示此vcpu是否還有預算; RTDS_extratime置位時,預算耗盡將會自動補充 |