lguest上的guest os啟動的過程
根據(jù)linux啟動流程的分析,在執(zhí)行到j(luò)mp *0xc0100000時,系統(tǒng)將會根據(jù)是壓縮內(nèi)核還是未壓縮的內(nèi)核來決定跳轉(zhuǎn)的方向:
(1)如果是未壓縮的內(nèi)核,就直接跳到/kernel/head_32.S的入口開始執(zhí)行
(2)如果是壓縮的內(nèi)核,就要先解壓,整個解壓的過程在/boot/compressed/head_32.S中,解壓完成后跳到解壓內(nèi)核的起始地址開始執(zhí)行其實解壓后的起始地址,也是/kernel/head_32.S的入口。
因此不管是壓縮的內(nèi)核還是未壓縮的內(nèi)核,都會執(zhí)行/kernel/head_32.S中的代碼。這是可以確定0xc0100000處的第一條代碼就是startup_32.
現(xiàn)在我們就從/kernel/head_32.S開始分析。
(1)重新加載boot_gdt_desc和段寄存器的值
(2)清空bss段
(3)把實模式下的boot_params,拷貝到保護模式下的boot_params結(jié)構(gòu)體中
(4)movl pa(boot_params) + NEW_CL_POINTER,%esi // %esi指向了 setup_header.cmd_line_ptr
判斷一下命令行參數(shù)指針是否為空,如果不為空,就要把命令行的參數(shù)拷貝過來,拷貝到boot_command_line數(shù)組中。
下面就開始判斷,如果定義了CONFIG_PARAVIRT的話,即支持虛擬化對的話,就要選擇到底執(zhí)行哪條內(nèi)核路徑,這里有三條路徑:
1 default_entry:這是默認的系統(tǒng)啟動路徑
2 bad_subarch: 當前內(nèi)核不支持的啟動路徑
3當前內(nèi)核支持的兩種虛擬化啟動路徑:lguest_entry 和 xen_entry
相當于定義了一個內(nèi)核路徑數(shù)組,數(shù)組名是subarch_entries,元素個數(shù)是num_subarch_entries,然后在尋址的時候是這樣的:
eax = 0 + eax*4 + pa(subarch_entries)。因為我們分析的是lguest , 因此這里的跳到lguest_entry。
有個很重要的數(shù)據(jù)結(jié)構(gòu),在這里必須要介紹一下: boot_params 結(jié)構(gòu)體,它就是傳說中的"zero page".
這個數(shù)據(jù)結(jié)構(gòu)幾乎保存了啟動過程中所需要的所有的信息,比如屏幕顯示信息,hdr,e820返回的內(nèi)存信息等等,在后面的啟動程序中,很多地方都用到了這個數(shù)據(jù)結(jié)構(gòu)中的參數(shù),
很明顯這是個循環(huán)復制的一組指令,目的地址就是boot_params, 那么源地址是哪里呢?其實,當跳到startup_32時,%esi還是執(zhí)行實模式下的數(shù)據(jù)boot_params。應該豁然開朗了,
實模式下的數(shù)據(jù)在保護模式下是不可用的,因此要拷貝過來。為了驗證esi到底是不是指向boot_params,看下面的代碼:
但是,boot_params到底是什么時候被初始化的呢?這也是我們比較關(guān)心的一個問題。
大部分的初始化的代碼包含在arch/x86/boot/Main.c中
(1)copy_boot_params(); 初始化的boot_params.hdr
(2)detect_memory(void);
初始化了boot_params.e820_map 和boot_params.e820_entries
(3)query_apm_bios(); 初始化了apm_bios_info
(4)query_apm_bios(); 初始化了screen_info
到此,boot_params這個數(shù)據(jù)結(jié)構(gòu)介紹完了,后面很多代碼都會從這個結(jié)構(gòu)體里面取數(shù)據(jù)。繼續(xù)分析lguest執(zhí)行流程lguest_entry.
在arch/x86/lguest/i386_head.S中找到了ENTRY(lguest_entry)
我們把這段代碼總整體上來看,就是實現(xiàn)了這么一個操作,%eax = $LHCALL_LGUEST_INIT %ebx=lguest_data 數(shù)據(jù)結(jié)構(gòu)的物理地址
后面又創(chuàng)建了一個堆棧,然后又調(diào)用了一個c函數(shù)lguest_init .把以上的信息串聯(lián)起來,豈不就相當于在匯編里調(diào)用c函數(shù)的整個準備過程,
先設(shè)置好參數(shù)(%eax,%ebx),然后設(shè)置好堆棧。
LHCALL_LGUEST_INIT是 lguest實現(xiàn)的hypercall 調(diào)用號,就想我們熟悉的linux的其他的系統(tǒng)調(diào)用,只不過是lguest的系統(tǒng)調(diào)用而已。在調(diào)用
系統(tǒng)調(diào)用前,要把系統(tǒng)調(diào)用號保存在eax中,把參數(shù)保存在ebx等其他的幾個寄存器中。然后.byte 0×0f ,0×01,0xc1就是執(zhí)行系統(tǒng)調(diào)用,相當于
int $0×80,這么做的目的無非就是通知host os,當前運行的是一個guest os.
這里也涉及到一個非常重要的數(shù)據(jù)結(jié)構(gòu)lguest_data, 分析下這個結(jié)構(gòu)體。
這個數(shù)據(jù)結(jié)構(gòu)實現(xiàn)了Host和Guest之間進行交流的一種方法
/*G:032 The second method of communicating with the Host is to via "struct
* lguest_data". Once the Guest's initialization hypercall tells the Host where
* this is, the Guest and Host both publish information in it. :*/
詳細分析一下每一個數(shù)據(jù)成員的含義:
(1)irq_enabled
/* 512 == enabled (same as eflags in normal hardware). The Guest
* changes interrupts so often that a hypercall is too slow. */
相當于Host里面的eflags, 512=2^9,即第10位置1,就表示開中斷。這么做的原因是:如果通過hypercall 來實現(xiàn)中斷的使能的話,太慢了!
(2)DECLARE_BITMAP(blocked_interrupts, LGUEST_IRQS);
定義了一個bitmap,用來做中斷屏蔽的???
(3)CR2 :Guest缺頁中斷時會在CR2中保存一個hypercall,Host在這里寫上一次page fault的虛擬地址
(4)time : Host設(shè)置的時間
(5)hcall_status[LHCALL_RING_SIZE] LHCALL_RING_SIZE=64
/* Async hypercall ring. Instead of directly making hypercalls, we can
* place them in here for processing the next time the Host wants.
* This batching can be quite efficient. */
/* 0xFF == done (set by Host), 0 == pending (set by Guest). */
并不是每產(chǎn)生一個hypercall,Host就對它進行處理,可以先讓這些hypercall 排隊,主機在某一個時間來對他們進行處理。0xFF表示Host處理完了所有的pending的hypercall, 0表示有Guest的hypercall在pending.
(6)reserve_mem: 指明給switcher保留的空間的大小(主機初始化)
(7)設(shè)置TSC的 頻率(Host初始化)
(以下變量guest 在初始化時設(shè)置)
(8)noirq_start,noirq_end: 不允許中斷的一段指令的范圍,即使此時是開中斷的;
(9)kernel_address = PAGE_OFFSET
(10)syscall_vec: 系統(tǒng)調(diào)用號0×80
下面我們就跳到arch/x86/lguest/boot.c 中的lguest_init()函數(shù)來執(zhí)行。
這個函數(shù)主要是對內(nèi)核的一些敏感操作進行的封裝或這說是替換,主要有一下幾個方面的封裝:
(1)中斷相關(guān)的操作
(2)cpu指令的封裝
(3)頁表管理
(4)apic的讀寫操作
(5)時間相關(guān)操作
對以上部分封裝之后,接著又執(zhí)行了下面一系列的操作:
(1)reserve_top_address(lguest_data.reserve_mem);
為Host<->Guest Switcher 保留一段內(nèi)存空間,這段內(nèi)存空間的大小由lguest_data.reserve_mem 指定。
(2)lockdep_init()
這個函數(shù)分別申請4096個classhash_table和8192個chainhash_table.這兩個hashtable到底是用來he干什么的,現(xiàn)在還不清楚?
(3)para_virt_disable_iospace()
禁止所有的非虛擬驅(qū)動程序去掃描它所支持的硬件設(shè)備,減少啟動時間
(4)cpu_detect(&new_cpu_data)
(5)add_preferred_console(“hvc”,0,NULL);
注冊hvc這個虛擬終端的驅(qū)動
(6)virtio_cons_early_init(early_put_chars);
(7)pm_power_off = lguest_power_off
machine_ops.restart=lguest_restart
(9)i386_start_kernel
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
