Grub2.00研究

    話說看linux內核源碼真是一件辛苦的事情啊,爲了弄清楚操做系統,我從linux源碼看到grub源碼,再看到BIOS,真心是傷不起啊。我研究的linux內核版本是2.6.34.13,它不支持從直接從內核啓動,須要一個bootloader。目前linux中使用最普遍的的bootloader是grub,本文就是對grub2.00(下文中簡寫爲grub)在x86下的分析研究。
    grub的源碼主要分爲四個部分,分別爲grub-core/boot/i386/pc/boot.S, grub-core/boot/i386/pc/ diskboot .S,grub-core/boot/i386/pc/ startup_raw .S,以及grub的核心代碼
  • boot.S是MBR中的512個字節,主要工做是加載diskboot.S。
  • diskboot.S也是512字節的代碼,它的做用是加載startup_raw.Sgrub的核心代碼。
  • startup_raw.S的做用是解壓grub的核心代碼。
  • grub的核心代碼是grub真正功能實現的地方。
    通常 boot.S是放在第一個扇區(即MBR), diskboot.S是放在第二個扇區,startup_raw.S和grub核心代碼放在接下來的幾十個扇區(可是限於0-63扇區)。下面按照grub的執行流程來研究grub的工做原理。

1. grub-core/boot/i386/pc/boot.S

    計算機啓動後(略過BIOS部分)會將硬盤的第一個扇區加載到0000:7C00處,這個扇區的佈局以下圖所示。


    此時IP寄存器的值爲7C00,第一條指令爲跳轉指令,跳到BPB(BIOS Parameter Block)後面的代碼處執行。這一部分比較簡單,首先初始化段寄存器,而後從啓動盤讀取第二個扇區到0000:8000處。
/* boot kernel */
jmp *(kernel_address) /* kernel_address = 0000:8000 */

    隨這這一條指令的執行,boot.S完成了它的使命,將執行權交給了diskboot.S。

2. grub-core/boot/i386/pc/diskboot.S

    diskboot.S的工做跟boot.S相似,它會根據配置信息從啓動盤中讀取很少於62個扇區的數據。它會將這些數據放在0000:8200處的一段內存中,其中開始部分是start_raw.S的代碼,後一部分是壓縮的grub核心代碼。
ljmp $0, $(GRUB_BOOT_MACHINE_KERNEL_ADDR + 0x200)
    這條跳轉指令是diskboot的最後一條指令(不考慮意外狀況),實際上就是跳轉到0000:8200處。

3. grub-core/kern/i386/pc/startup_raw.S

    startup_raw.S的部分關鍵代碼以下,首先須要 設置數據段、堆棧段和擴展段寄存器,以及棧指針。
/* set up %ds, %ss, and %es */
xorw %ax, %ax
movw %ax, %ds
movw %ax, %ss
movw %ax, %es
/* set up the real mode/BIOS stack */
movl $GRUB_MEMORY_MACHINE_REAL_STACK, %ebp
movl %ebp, %esp
    進行一些準備工做後開始進入保護模式, real_to_prot 這個函數的代碼在 grub-core/kern/i386/realmode .S中
/* transition to protected mode */
DATA32 call real_to_prot
    進入保護模式後就調用 _LzmaDecodeA解壓壓縮的核心代碼, _LzmaDecodeA這個函數在 lzma_decode.S中定義 。緊接着的幾條指令的做用是設置參數,而後跳轉到核心代碼。
movl $GRUB_MEMORY_MACHINE_DECOMPRESSION_ADDR, %edi
        ...
        popl %esi
movl LOCAL(boot_dev), %edx
movl $prot_to_real, %edi
movl $real_to_prot, %ecx
movl $LOCAL(realidt), %eax
jmp *%esi
    解壓後的核心最開始放在0x00100000處,在上面的指令中,esi寄存器的值就是核心代碼的地址,edx、edi、ecx、eax分別是啓動設備、從保護模式進入實模式函數的地址、從實模式進入保護模式函數的地址、實模式中斷描述符表的地址。

4. grub-core/kern/i386/pc/startup.S

    startup.S首先保存傳遞過來的參數,具體實現是下面的三條指令。
movl %ecx, (LOCAL(real_to_prot_addr) - _start) (%esi)
movl %edi, (LOCAL(prot_to_real_addr) - _start) (%esi)
movl %eax, (EXT_C(grub_realidt) - _start) (%esi)
    爲何不直接使用這幾個變量的地址呢?這是由於startup.S當前所在地址爲0x00100000,而代碼的目標地址爲0x00008200,因此須要轉換一下變量的地址。
    如今須要將核心代碼移動到目標地址,下面的代碼完成了移動的工做。
/* copy back the decompressed part (except the modules) */
movl $(_edata - _start), %ecx
movl $(_start), %edi
rep
movsb
movl $LOCAL (cont), %esi
jmp *%esi
LOCAL(cont):
    ecx的值是核心代碼的大小,由_edata減_start得來。edi的值爲_start,也就是目標地址。esi是核心代碼如今所在的地址,在startup_raw.S中設置,到如今一直未有變更。
    移動完成之後,jmp指令完成了到目標地址的跳轉,這個跳轉很巧妙,雖然整個代碼移動了位置,但看起來就像沒有移動同樣。
/*
 *  Call the start of main body of C code.
 */
call EXT_C(grub_main)
    接下來清空BSS(Block Started by Symbol ),調用 grub_main進入grub的主函數,這個函數在 grub-core/kern/main.c中 。到這來咱們已經來到了grub的核心代碼部分,這部分主要是grub的模塊化框架的初始化,各類命令的註冊,各類模塊的加載等等。
    我比較關心的是grub在用戶選擇指定的系統後怎麼加載linux的,精力有限,其它代碼暫時就不去閱讀了。當用戶選擇指定的內核條目後, grub-core/commands/boot.c中的命令函數 g rub_cmd_boot開始執行
    加載linux的代碼在grub-core/loader/i386目錄下,grub支持16位、32位、64位三種模式啓動linux內核。16位模式下會從新回到實模式,再啓動linux內核。32位和64位兩種模式下不須要再進入實模式,直接在保護模式啓動。
    linux.c文件中的 grub_cmd_linux函數就是處理linux內核加載的 。函數 grub_cmd_linux的做用是根據命令行參數生成相關配置數據,而後設置一個回調函數,由 g rub_cmd_boot調用。這樣作的好處是減少了代碼的耦合度。
     g rub_cmd_boot和 grub_linux_boot兩個函數 將linux內核從文件系統中讀取到內存中,內核代碼分爲兩部分,一部分是實模式代碼,一部分是保護模式代碼。實模式代碼通常放在0x00009000處,保護模式代碼通常放在0x00100000處,在 32位和64位兩種模式啓動實際上不須要實模式部分代碼 。此外, g rub爲linux內核設置了 linux_kernel_params 參數,這些參數在linux內核中會用到。

5.  grub_relocatorXX_boot
   
    離進入linux內核就差最後一步了, grub_linux_boot最後調用 grub_relocatorXX_boot(.../ lib/.../ relocator.c 進入內核。顧名思義,最後所要作的事情是重定位。 grub_cmd_linux只是將加載了內核的代碼 ,這部分代碼要能使用還必須對代碼的每一個chunk進行重定位。
    重定義這部分代碼不是很複雜,須要結合ELF文件格式閱讀,這裏就很少說了。調用 grub_relocator_prepare_relocs重定位好以後,以下面代碼所示,先關閉中斷,而後調用 relst() 就進入到了linux內核, relst()後面的代碼是永遠不會執行的
asm volatile ("cli");
      ((void (*) (void)) relst) ();
      /* Not reached.  */
      return GRUB_ERR_NONE;
    到這裏爲止,本文就算是把grub啓動linux簡要敘述了一遍,其中還有不少細節這裏沒有講,之後有時間再好好看看。
相關文章
相關標籤/搜索