其實這個問題困擾我很久 在 grub 裡會我們會填一個 initrd 的東西, 這個東西就是在 /boot 下面的 initrd.img* 這個檔案 是 ramdisk 但是問題來了 grub 到底是如何跟 kernel 互動的呢?
kernel 怎麼去知道這個 initrd 在什麼地方呢?
所以稍微追了一下 所以在此紀錄一下
P.S : 我這邊所說的 initrd 是包含 initramfs (cpio-gzip format), 和 initrd (filesystem based loop device). 都是經由 bootloader (GRUB/GRUB2) 所 指定的.
< Trace >
[ Based on kernel 2.6.32.7 ]
首先 從 kernel.org 去抓 source tarball, 在解開後會有一個完整的 kernel source tree, 我下面通通就以 KERNEL_SRC, 來代表你的 top directory.
@ ${KERNEL_SRC}/arch/x86/include/asm/bootparam.h
struct boot_params {
... 略 ...
struct setup_header hdr; /* setup header */ /* 0x1f1 */
... 略 ...
}
struct setup_header {
... 略 ...
__u16 setup_move_size;
__u32 code32_start;
__u32 ramdisk_image; <---
__u32 ramdisk_size; <---
__u32 bootsect_kludge;
... 略 ...
}
由上面可以看到 kernel 這兩個 parameter, (ramdisk_image, ramdisk_size) 是由 grub 所給予的 比對一下 grub2 的 header file 你會發現有很多部份是相似的
我是在 ubuntu karmic 9.10 直接 apt-get source grub-pc (grub-version: grub2-1.97~beta4)
@ {GRUB_SRC}/include/grub/i386/linux.h
/* For the Linux/i386 boot protocol version 2.03. */
struct linux_kernel_header
{
grub_uint8_t code1[0x0020];
grub_uint16_t cl_magic; /* Magic number 0xA33F */
grub_uint16_t cl_offset; /* The offset of command line */
grub_uint8_t code2[0x01F1 - 0x0020 - 2 - 2];
... 略 ...
grub_uint8_t loadflags; /* Boot protocol option flags */
grub_uint16_t setup_move_size; /* Move to high memory size */
grub_uint32_t code32_start; /* Boot loader hook */
grub_uint32_t ramdisk_image; /* initrd load address */ <---
grub_uint32_t ramdisk_size; /* initrd size */ <---
grub_uint32_t bootsect_kludge; /* obsolete */
... 略 ...
}
在 grub 裡面你會看到如上的 linux_kernel_header structure, 裡面的 ramdisk_image, ramdisk_size, 就是會 pass 給 kernel 這樣 kernel 就會使用這個 initrd 做為後續的動作. 除了前面 48 個 bytes 是 grub2 所定義的.
在繼續往下看
@ ${KERNEL_SRC}/arch/x86/kernel/setup.c
#ifdef CONFIG_BLK_DEV_INITRD
static void __init reserve_initrd(void)
{
u64 ramdisk_image = boot_params.hdr.ramdisk_image;
u64 ramdisk_size = boot_params.hdr.ramdisk_size;
u64 ramdisk_end = ramdisk_image + ramdisk_size;
u64 end_of_lowmem = max_low_pfn_mapped << PAGE_SHIFT;
if (!boot_params.hdr.type_of_loader ||
!ramdisk_image || !ramdisk_size)
return; /* No initrd provided by bootloader */
initrd_start = 0;
if (ramdisk_size >= (end_of_lowmem>>1)) {
free_early(ramdisk_image, ramdisk_end);
printk(KERN_ERR "initrd too large to handle, "
"disabling initrd\n");
return;
}
printk(KERN_INFO "RAMDISK: %08llx - %08llx\n", ramdisk_image,
ramdisk_end);
if (ramdisk_end <= end_of_lowmem) {
/* All in lowmem, easy case */
/*
* don't need to reserve again, already reserved early
* in i386_start_kernel
*/
initrd_start = ramdisk_image + PAGE_OFFSET; <---
initrd_end = initrd_start + ramdisk_size; <---
return;
}
relocate_initrd();
free_early(ramdisk_image, ramdisk_end);
}
#else
static void __init reserve_initrd(void)
{
}
#endif /* CONFIG_BLK_DEV_INITRD */
上面注意一下箭號部份
initrd_start = ramdisk_image + PAGE_OFFSET; <---
initrd_end = initrd_start + ramdisk_size; <---
kernel 就會從 bootloader 所置放的 ramdisk_image 和 ramdisk_size 轉成自己的變數 initrd_start, initrd_end
在來 ...
@ ${KERNEL_SRC}/init/initramfs.c
static int __init populate_rootfs(void)
{
char *err = unpack_to_rootfs(__initramfs_start,
__initramfs_end - __initramfs_start);
if (err)
panic(err); /* Failed to decompress INTERNAL initramfs */
if (initrd_start) { <---
#ifdef CONFIG_BLK_DEV_RAM
int fd;
printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n");
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start);
if (!err) {
free_initrd();
return 0;
} else {
clean_rootfs();
unpack_to_rootfs(__initramfs_start,
__initramfs_end - __initramfs_start);
}
printk(KERN_INFO "rootfs image is not initramfs (%s)"
"; looks like an initrd\n", err);
fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700);
if (fd >= 0) {
sys_write(fd, (char *)initrd_start,
initrd_end - initrd_start);
sys_close(fd);
free_initrd();
}
#else
printk(KERN_INFO "Unpacking initramfs...\n");
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start);
if (err)
printk(KERN_EMERG "Initramfs unpacking failed: %s\n", err);
free_initrd();
#endif
}
return 0;
}
上面箭號部份的 initrd_start, initrd_end 就是從上面傳下來的 如此 在呼叫 unpack_to_rootfs 就可成功的 把 rootfs mount 起來
在來 ...
@$KERNEL_SRC/init/main.c
static int __init kernel_init(void * unused)
{
... 略 ...
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
... 略 ...
}
因為 default ramdisk_execute_command 是空值 所以 會被設為 /init, 除非你有指定 rdinit, 不然不會影響預設值.
在來 ...
@ $KERNEL_SRC/init/main.c
static noinline int init_post(void)
__releases(kernel_lock)
{
... 略 ...
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
(void) sys_dup(0);
(void) sys_dup(0);
current->signal->flags |= SIGNAL_UNKILLABLE;
if (ramdisk_execute_command) { <---
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
panic("No init found. Try passing init= option to kernel.");
}
在上面可以看到會依據 ramdisk_execute_command 的 /init 呼叫 run_init_process 如此 kernel 到此將主控權 交到 userspace 的 init (不過這個有旦書啦 除非 你使用的是 cpio-based 的 initramfs 不然 init PID 必不為 1).
< to be continued >
留言列表