Linuxカーネルのコマンドラインはブートローダーからどう渡されるのか?

先日参加した自作OSもくもく会で「Linuxカーネルコマンドラインブートローダーからどう渡されるのか?」のような話が聞こえたので、調べようと思い調べてみました。確認はLinux kernel v4.5とsystemd-bootの2016/05/02 23:00 JSTのコードです。 uefiじゃない環境も確認しようかなと思ってgrubのコードをgit cloneはしました。が、うちのメイン環境で使っているのはsystemd-bootだしってことで確認してません。

で、カーネルコマンドラインは↓のようなやつですね。

masami@saga:~/codes$ cat /proc/cmdline
initrd=\initramfs-4.6.0-rc5-ktest+.img root=/dev/sda2 rw crashkernel=256M

コマンドラインカーネルに渡すとしたら、どこかしらのアドレスに置くんだろうというのは想像できますが、ブートローダーの好きな場所に置くとも考えにくいので、何かしらのプロトコルは決まっているはずです。というわけで、カーネルのドキュメントを確認します。確認するのはDocumentation/x86/boot.txtです。 これを見ると、カーネルコマンドラインはヒープの終わりから0xA0000までの間の好きなところに置けると書かれています。

532   Set this field to the linear address of the kernel command line.
533   The kernel command line can be located anywhere between the end of
534   the setup heap and 0xA0000; it does not have to be located in the
535   same 64K segment as the real-mode code itself.

そして、systemd-bootのsrc/boot/efi/linux.cのコードを見ると、0xA0000がありますね。systemd-bootはアドレス0xA0000にカーネルコマンドラインを置くようです。

        if (cmdline) {
                addr = 0xA0000;
                err = uefi_call_wrapper(BS->AllocatePages, 4, AllocateMaxAddress, EfiLoaderData,
                                        EFI_SIZE_TO_PAGES(cmdline_len + 1), &addr);
                if (EFI_ERROR(err))
                        return err;
                CopyMem((VOID *)(UINTN)addr, cmdline, cmdline_len);
                ((CHAR8 *)addr)[cmdline_len] = 0;
                boot_setup->cmd_line_ptr = (UINT32)addr;
        }

そして、if文を抜けた後は、linux_efi_handover()を呼び、この関数からLinuxカーネルに制御が移ります。

#ifdef __x86_64__
typedef VOID(*handover_f)(VOID *image, EFI_SYSTEM_TABLE *table, struct SetupHeader *setup);
static inline VOID linux_efi_handover(EFI_HANDLE image, struct SetupHeader *setup) {
        handover_f handover;

        asm volatile ("cli");
        handover = (handover_f)((UINTN)setup->code32_start + 512 + setup->handover_offset);
        handover(image, ST, setup);
}
#else

ここから呼ばれるのはarch/x86/kernel/head_32.Sのstartup_32だと思います。そして、この辺でブートパラメータのコピー処理があります。

127 /*
128  * Copy bootup parameters out of the way.
129  * Note: %esi still has the pointer to the real-mode data.
130  * With the kexec as boot loader, parameter segment might be loaded beyond
131  * kernel image and might not even be addressable by early boot page tables.
132  * (kexec on panic case). Hence copy out the parameters before initializing
133  * page tables.
134  */
135         movl $pa(boot_params),%edi
136         movl $(PARAM_SIZE/4),%ecx
137         cld
138         rep
139         movsl
140         movl pa(boot_params) + NEW_CL_POINTER,%esi
141         andl %esi,%esi
142         jz 1f                   # No command line
143         movl $pa(boot_command_line),%edi
144         movl $(COMMAND_LINE_SIZE/4),%ecx
145         rep
146         movsl

boot_command_lineはinit/main.cにあります。linux/include/init.hでextern宣言しているのでhead_32.Sからもアクセスできます。

122 /* Untouched command line saved by arch-specific code. */
123 char __initdata boot_command_line[COMMAND_LINE_SIZE];

今回はこんなところで( ´ー`)フゥー...

Go言語によるWebアプリケーション開発

Go言語によるWebアプリケーション開発