Linux Boot Sequence を読む ( 4 ) ~ GRUB kernel ~

February 18, 2018

startup.S

kern/i386/pc/startup.S から始まる。

start:  
_start:  
__start:  
#ifdef __APPLE__  
LOCAL(start):  
#endif  
        .code32  
  
        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)  
  
        /* copy back the decompressed part (except the modules) */  
#ifdef __APPLE__  
        movl    $EXT_C(_edata), %ecx  
        subl    $LOCAL(start), %ecx  
#else  
        movl    $(_edata - _start), %ecx  
#endif  
        movl    $(_start), %edi  
        rep  
        movsb  
  
        movl    $LOCAL (cont), %esi  
        jmp     *%esi  
LOCAL(cont):  
  
... (中略) ...  
  
         /*                                                                                                                                                                                                                                      
         *  Call the start of main body of C code.                                                                                                                                                                                              
         */  
        call EXT_C(grub_main)  

startup.S は 0x100000 にロードされているが、プログラムのバイナリは 0x090000 で実行されるようにリンクされているたえ、0x100000 から 0x090000 へコピーを行う。
その後、grub_main() を呼び出す処理を行う。

grub_main()

やっと C のコードまでたどり着いた。
grub_main() は grub-core/kern/main.c に書かれているが、メニューを表示してユーザーの入力を受け付ける処理のため省略する。
以降は Linux カーネルをブートする選択肢が取られたときの処理を見る。

grub-core/loader/i386/linux.c を読む。

grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),  
                int argc, char *argv[])  
{  
  grub_file_t file = 0;  
  struct linux_kernel_header lh;  
  grub_uint8_t setup_sects;  
  grub_size_t real_size, prot_size, prot_file_size;  
  
... (中略) ...  
  
  file = grub_file_open (argv[0]);  
  if (! file)  
    goto fail;  
  
  if (grub_file_read (file, &lh, sizeof (lh)) != sizeof (lh))  

grubcmdlinux() の引数には、grub.conf の linux に指定されたオプション文字列が空白文字列で分解された配列形式で渡される。
例えば argv[0] は /vmlinuz-x.x.x… のようなカーネルファイルパスとなる。
ファイルのオープンに成功すると、612 バイトのカーネルヘッダを lh に読み込む。その後、ヘッダ情報の識別番号やバージョン、ファイル構造の種類、サイズなどのチェック処理が行われる。

次は initrd ファイルの読み込み。

grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),  
                 int argc, char *argv[])  
{  
  grub_size_t size = 0, aligned_size = 0;  
  grub_addr_t addr_min, addr_max;  
  grub_addr_t addr;  
  grub_err_t err;  
  struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 };  
  
  if (argc == 0)  
    {  
      grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));  
      goto fail;  
    }  
  
  if (! loaded)  
    {  
      grub_error (GRUB_ERR_BAD_ARGUMENT, N_("you need to load the kernel first"));  
      goto fail;  
    }  
  
  if (grub_initrd_init (argc, argv, &initrd_ctx))  
    goto fail;  
  
... (中略) ...  
  
  linux_params.ramdisk_image = initrd_mem_target;  
  linux_params.ramdisk_size = size;  
  linux_params.root_dev = 0x0100; /* XXX */  
  
 fail:  
  grub_initrd_close (&initrd_ctx);  
  
  return grub_errno;  
}  

上記で initrd ファイルをメモリにロードし、ロードしたアドレスとサイズをカーネルに渡すパラメータに設定してファイルをクローズして処理を終える。

GRUB2 の最後の処理は grub_linux_boot() からカーネルに制御を渡すこと。

grub_linux_boot (void)  
{  
  grub_err_t err = 0;  
  const char *modevar;  
  char *tmp;  
  struct grub_relocator32_state state;  
  void *real_mode_mem;  
  struct grub_linux_boot_ctx ctx = {  
    .real_mode_target = 0  
  };  
  grub_size_t mmap_size;  
  grub_size_t cl_offset;  
  
...  
  

 © 2023, Dealing with Ambiguity