1. 程式人生 > >Xen 程式碼分析分析(1.start_xen)

Xen 程式碼分析分析(1.start_xen)

前言

RT      RealTime實時排程

CPU 單處理器晶片

pcpu    單處理器晶片中的一個核。

vcpu    Xen的基本排程單位,可理解為程序。

預算    Xen的RT排程中預算指的是任務的剩餘執行時間

文件所分析程式碼為Xen4.11版本。

經過編寫過程中的反覆檢視,發現本文易讀性較差,因此引入彩色字型,

淡灰色字型表示與主題相關性不大;

紅色字型表示重要資訊;

紫色字型是為了提神;

各色字型交織使用表示同色字型的連續性

認真講,我並不十分看好Xen on arm的發展(程式碼量不小、前有seL4,後有Ziron),但是不得不說他是目前我們唯一可獲得、可用的arm端支援虛擬化的微核心,所有微核心都是有其共同點的,即保留最核心核心功能(其實我認為應該做出某種權衡,在微核心、巨集核心之間,是否可以進行更細的分割,以平衡生態、程式碼量、可移植性)。於是其他基於微核心的虛擬化實現,必然會走與Xen相似、甚至相同的路,如果能有其他更優秀的實現路線:比如不再依賴Domain0、更靈活的硬體外設驅動方案,也是會與Xen的思想有著千絲萬縷的聯絡(就像Xen與Linux很多思想是一致的。於是我認為對Xen的解析,對HYP微核心的深刻理解,是可以為在微核心上的進一步發展提供靈感的,Xen當然是並不完善的,但是其優秀的成分必然也不少(不然誰會為止貢獻程式碼呢),你可以肆意的優化甚至大規模改變流程,但是對於一個微核心虛擬化方案,所有Xen需要支援的東西,我們肯定是無法錯過的;於是作為一個切入點,Xen是值得的。

目錄

前言

總結

小結

小結

進一步

排程時核心最核心功能之一,從核心啟動開始就會對啟動排程做準備,於是分析Xen核心啟動過程中對排程的預初始化和初始化,是分析排程的初始步驟。Xen核心啟動,會執行start_xen()過程,start_xen()是Xen微核心C程式碼的入口。

1.  setup_cache();

微核心會檢查cache屬性(略);

2.  percpu_init_areas();

為每個pcpu分配空間,因為很多資料各pcpu都會有,比如cpu的頻率、id等[1]。此空間用於存放各pcpu自有資料,避免各個pcpu競爭使用造成飢餓,一般稱這種變數為per-cpu

變數( Xen應該是繼承自Linux的各pcpu變數定義:DECLARE_PER_CPU(unsignedint, cpu_id)是獲得已被定義的per-cpu變數。一次引用宣告,各pcpu分別有了cpuid變數,當代碼所處pcpu執行this_cpu(cpuid),即可獲得此pcpu的cpuid)

3.  set_processor_id(0);

Xen微核心啟動首先會在第一個pcpu上執行,並設定所在的pcpu的id。(具體程式碼為this_cpu(cpuid)=0,第一個percpu變數的使用例項)

4.  set_current((structvcpu *)0xfffff000); idle_vcpu[0]= current

;(idle_vcpu是一個全域性陣列,每一個物理pcpu都會有一個idle_vcpu,當這個物理pcpu沒有任務時,就會把它取出來執行。current是一個巨集變數,通過this_vcpu(curr_vcpu)獲得,即為本物理pcpu中執行的vcpu。)

現在程式碼處於Xen微核心啟動的第一個執行vcpu (Xen想要將所有的執行程序都作為vcpu來表述,可以理解為vcpu即為Xen的程序,vcpu是Xen微核心的關鍵概念,後續還會提到並解釋。),其他的pcpu並未啟動,最終,本vcpu會退化成為一個idle_vcpu (idle_vcpu是Xen中空閒程序。)。

5.  setup_virtual_regions(NULL,NULL);

略。

6.  init_traps();

執行在Xen上的GuestOS(包含特權域)都會認為自己擁有整個硬體.想象沒有CPU對虛擬化的硬支援,Xen如何實現對GuestOS的管控:對於GuestOS下發的每一條計算機指令,Xen都需要將這條指令檢查過之後再投入計算機執行,如同Java的虛擬機器(效率更低),效率就像是笑話(CPU50%以上的時間在檢查指令)。但如果不這樣做,一旦被GuestOS獲得CPU的使用權,GuestOS將會獲得完整的硬體使用權,如果GuestOS將硬體排程定時器的處理函式指向自己的排程函式,Xen就會像BootLoader一樣被扔到一邊,之後的GuestOS也都成為廢紙,所謂域間隔離也都只能是空談。於是所有的GuestOS都會嘗試執行所有計算指令(包括特權指令),而這就是CPU對虛擬化支援的意義所在(指令被分為EL0-EL3(aarch64)特權等級),每一個特權指令都不會被隨意執行,當執行在Xen之上的GuestOS在硬體執行了EL2級特權指令(GuestOS核心才被允許在EL1執行),EL2特權指令將會產生異常,被Xen捕獲,init_traps()就是在初始化捕獲器,包含關鍵的CP15 CR12中HVBAR(CP15 CR12下HVBAR是Hypervisor的Vector Base Address Register儲存異常向量地址;注意aarch64,與aarch32一些特權暫存器使用方式有變化。)等。這也是ARM架構規定特權指令等級的實施方式。

7.  smp_clear_cpu_maps();

Xen要根據裝置樹進行初始化設定,清除CPU點陣圖後,將當前CPU0設入點陣圖。

8.  device_tree_flattened=early_fdt_map(fdt_paddr);

fdt_paddr在啟動start_xen()時傳入,是裝置樹的起始地址;FDT(flatted device tree)的DTB存放有板級裝置資訊,大小不超過2M,最少8byte對齊,early_fdt_map()會對DTB合法性進行檢查,並建立對記憶體的對映(early_fdt_map()呼叫的create_mappings()函式透露出Xen對記憶體管理的資訊,但由於本文件重點分析Xen的排程機制,在此不予詳述。)device_tree_flattened應該是指向DTB所在記憶體起始地址。

9.  其他初始化

Xen在隨後根據DTB資訊進行了大量初始化,設定啟動地址、設定頁表、根據平臺初始化ACPI(Advanced Configuration and Power Management Interface)的AP、完成記憶體初始化,這些操作與Xen排程分析關係不大,此處均不詳述(主要是有些東西弄清楚耗時太長)。

10. system_state=SYS_STATE_boot

system_state全域性變數標誌的Xen當前的狀態(Xen的狀態可能為:SYS_STATE_early_boot、SYS_STATE_boot、SYS_STATE_smp_boot、SYS_STATE_active、SYS_STATE_suspend、SYS_STATE_resume等)當前,Xen完成了記憶體子系統的初始化,從early_boot進入boot狀態。

11. vm_init();

虛擬記憶體管理的初始化。

各個中斷TYPE的初始化為IRQ_TYPE_INVALID(即IRQ正在初始化)(其他還有IRQ_TYPE_NONE(預設的普通IRQ_TYPE)、IRQ_TYPE_EDGE_RISING(上升沿觸發的中斷)、IRQ_TYPE_EDGE_FALLING(下降沿觸發的中斷)、IRQ_TYPE_EDGE_BOTH(上升、下降沿均會觸發的中斷),其他還有IRQ_TYPE_LEVEL_HIGH、IRQ_TYPE_LEVEL_LOW、IRQ_TYPE_LEVEL_MASK、IRQ_TYPE_SENSE_MASK等型別或組合型別的中斷。)

13. platform_init();

根據硬體平臺的初始化,有廠商編寫並根據Xen的介面封裝,Xen會根據DTB資訊進行匹配,得到可用的硬體平臺介面,並呼叫介面初始化函式。(此處Xen需要的初始化的介面少的可憐,init()估計是留給廠商將硬體啟動,init_time()獲得硬體提供的一個定時器來作為系統的時鐘滴答(核心基於此感知時間,如觸發排程,超時操作);specific_mapping()讓廠商把外設的記憶體地址送到struct domain,後期啟動Dom0時,Dom0能看到外設。另外Xen還需要廠商提供的reset()、poweroff(),估計是重啟、關機操作。另外還可以有給出一個禁止pass-through的裝置表告訴Xen。quirks()函式未知。arm32還會有smp_init()、cpu_up()函式。)

14. 對定時器、中斷控制器GIC、串列埠輸出、各pcpu預初始化。

preinit_xen_time();  初始化啟動所在CPU的定時器(對於啟動了ACPI的裝置,ACPI會接管初始化操作),查詢定時器在DTB的節點,設定裝置節點的使用者為DOMID_XEN(這可能是Xen作為一個域的編號7FF2U,Dom0由此編號訪問Hypervisor(如xl對Xen的操作?)。);呼叫平臺介面初始化此定時器,記錄啟動時間()

gic_preinit();       初始化中斷控制器節點。(啟動時間的記錄在boot_count,從P15 CR14讀出。)串列埠初始化       初始化UART裝置、串列埠輸出的中斷、申請串列埠環形緩衝區。CPU初始化       檢測各pcpu可用性、一般屬性,初始化CPU點陣圖,更新nr_cpu_ids(儲存有pcpu數)。

15. init_xen_time();

獲得定時器中斷資源,放入timer_irq陣列

16. gic_init();

GIC私有structgic_hw_operations * gic_hw_ops是GIC裝置介面入口,此處呼叫其init()介面對GIC裝置初始化。GIC是中斷控制器,對於無ACPI平臺,系統直接讀DTB完成初始化,對於有ACPI節點,由ACPI提供的介面完成。

對tasklet機制(Xen中tasklet.c原始碼中顯示是由1992年Linus Torvalds的程式碼基礎上修改而來,總體機制變化不大。)進行初始化,對各pcpu建立tasklet_listsoftirq_tasklet_list;註冊一個CPU的Notifiter(Notifiter是核心的常用通訊機制,主體為一個按優先順序排列的連結串列,被插入當前pcpu下。);開啟TASKLET_SOFTIRQ,並設定tasklet_softirq_action()為軟中斷處理函式。tasklet_softirq_action()呼叫do_tasklet_work()將struct tasklet從原有連結串列摘下並執行其內部提供的處理函式;執行完成後,若struct tasklet->scheduled_on>=0則呼叫tasklet_enqueue()(scheduled_on>=0表示此tasklet應該放在某pcpu上執行,而scheduled_on的值即為pcpu的id,因此應被放到對應pcpu的softirq_tasklet_list或tasklet_list,如果為-1則不會特別處理。)。tasklet_enqueue()會根據struct tasklet->is_softirq(is_softirq將會只能在軟中斷處理。)決定將tasklet加入指定pcpu的softirq_tasklet_list(加入softirq_tasklet_list的同時,指定pcpu的softirq_tasklet_list無內容,則為此CPU軟產生一個TASKLET_SOFTIRQ軟中斷。)或tasklet_list(加入tasklet_list時若指定pcpu的tasklet_list無內容則為此CPU軟產生一個SCHEDULE_SOFTIRQ。)softirq_tasklet_list將會在軟中斷執行tasklet_list則會置位對應pcpu的tasklet_work_to_do變數的_TASKLET_enqueued位(在一次排程的處理next_slice = sched->do_schedule(sched, now,tasklet_work_scheduled); 會用到此變數。)

18. xsm_dt_init();

Xen Security Modules。Xen限制Domain的安全訪問控制機制,可以定義域間、域與HYP以及相關記憶體、裝置資源的通訊、使用。類SELinux。

19. init_timer_interrupt();

通過request_irq(),將timer_irq定時器中斷資源與處理函式timer_interrupt()連線(timer_irq陣列有四個成員TIMER_PHYS_SECURE_PPI、TIMER_PHYS_NONSECURE_PPI、TIMER_VIRT_PPI、TIMER_HYP_PPI,詳見章節REF _Ref517685421 \h  \* MERGEFORMAT 軟中斷softirq執行路線)

20. timer_init();

執行open_softirq(TIMER_SOFTIRQ,timer_softirq_action),將TIMER_SOFTIRQ(TIMER_SOFTIRQ的處理函式)是所有軟中斷啟動之源)開啟,並設定處理函式為timer_softirq_action()。timer_softirq_action()主要將當前pcpu的structtimer中的function執行。

21. init_idle_domain();

執行open_softirq(SCHEDULE_SOFTIRQ,schedule),將SCHEDULE_SOFTIRQ軟中斷開啟並將schedule()排程函式給入;將所用排程器給入全域性struct scheduler ops,然後執行cpu_schedule_up(),其中init_timer()將s_timer_fn()設入當前pcpu的struct schedule_data –> struct timer –>function中,s_timer_fn()會raise_softirq(SCHEDULE_SOFTIRQ)啟動排程。最後init_idle_domain建立了一個空閒域。

22. 其他初始化操作

start_xen()隨後對RCU進行了初始化、建立了記憶體管理相關的虛擬Domain、使能中斷、對SMP的預初始化、呼叫uart的驅動介面初始化postirq、嘗試啟動其餘pcpu。最終start_xen()建立了特權域Domain0,並退化成為idle_vcpu。