close

其實這個問題困擾我很久 在 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 >

 

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 丘猴子 的頭像
    丘猴子

    轉貼部落格

    丘猴子 發表在 痞客邦 留言(0) 人氣()