ramdisk与 initrd、initramfs的关系

1、ramdisk、initrd是什么?

ramdisk是一种基于内存的虚拟文件系统,通常用于放置内核的中间数据。
而initrd全称为”boot loader initialized RAM disk”,也就是由启动加载器所初始化的RamDisk设备,它的作用是完善内核的模块机制,让内核的初始化流程更具弹性;内核以及initrd,都由bootloader在机子启动后被加载至内存的指定位置,主要功能为按需加载模块以及按需改变根文件系统。更详细的内容,请参阅initrd的man手册,里面阐述了内核开发者对initrd制订的功能标准。命令:man initrd

2、/dev/ram0 这个设备是哪来的?

答: 拷贝initrd内容到/dev/ram0 上时文件系统已经被加载,只是没有设置根文件系统,内核的初始化流程还没有进入用户层而已,我们当然不能知道是否有这个设备,但的确是存在的。
      所谓文件系统,就是VFS,而根文件系统是其中的一个属性项而已;而每个设备本身是内核数据区里的一个数据结构,经过VFS的映射,才被用户层视为一个文件。所以,内核要创建继而访问一个设备,是可以直接传递”/dev/ram”这样的参数给create_dev函数来实现的。
       至于设置根文件系统,只是C语言里对一个数据结构的成员赋值而已。而所谓目录项,也是内核数据区里的一个数据结构。要创建/dev/ram0,只需赋值根文件系统的值,创建dev目录项,并且创建这个ram设备即可。
      要区分内核层以及用户层的区别,可以用一个很简单的逻辑来解释,试想一个,你在用户层删除了/dev里的ram0,那是不是内核就没有了这个设备了呢?再想,如果你删除了你正在使用的硬盘的设备节点/dev/hda,那么是不是内核就不能操作硬盘了呢?答案是很明显的,没有了设备节点,只意味着用户不能操作该设备,并不代表内核不能访问该设备。在最新的设备管理体系中,设备管理的操作逻辑是放在用户层的,那就是udev,默认的规则中没有/dev/eth0设备节点,但你可以照样上网。

3、在 grub 中使用到的 initrd ,是用来做什么的?如何做到?

答:GRUB中只是简单地把initrd拷贝在内存中指定的位置而已,没有对它做进一步的操作,只是将initrd的大小放在内核setup.S的头部数据区域,由内核来初始化这段内存区域。

请看内核初始化代码init中的initramfs.c代码片断:

代码:

#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start) {
int fd;
printk(KERN_INFO “checking if image is initramfs…”);
                /*这里的initrd_start和initrd_end就是GRUB放置initrd的内存区域的范围*/
err = unpack_to_rootfs((char *)initrd_start,
initrd_end – initrd_start, 1);
if (!err) {
printk(” it is “);
unpack_to_rootfs((char *)initrd_start,
initrd_end – initrd_start, 0);
free_initrd_mem(initrd_start, initrd_end);
return;
}

printk(“it isn’t (%s); looks like an initrd “, err);
fd = sys_open(“/initrd.image”, O_WRONLY|O_CREAT, 700);
if (fd >= 0) {
sys_write(fd, (char *)initrd_start,
initrd_end – initrd_start);
sys_close(fd);
free_initrd_mem(initrd_start, initrd_end);
}

}

#endif

GRUB不参与Linux内核的初始化!它是与内核独立的。

记住,GRUB与Linux内核交互的方式遵循一种协议,目前的协议版本为2.02;GRUB通过”setup head”区域与内核交换数据。
      假设内核映像为kernel.img,那么这个img文件里就包含着三截代码段(在编译内核的最后阶段生成),第一段是bootsect.S,就是旧的内核内置启动器,它的地位已经被GRUB替代,所以被丢弃不用;第二段是setup.S,由GRUB负责将其放置到物理地址0x90200上,它的头部就是前面提及的所谓head区,GRUB会把需要告诉内核的信息例如initrd的物理起始地址以及大小,放在这个head区;第三段就是真正的压缩内核映像,视乎其体积类型,如果是大内核,就将这段内核映像放至0x100000。
      那么,在初始化页表的时候,内核会搬动head区的数据到合适的位置,里面的数据就会被内核初始化所利用,所以内核就得知了initrd的所有信息。
      也就是说,setup.S 前面一段是用来在grub和内核之间传递参数的,如果在grub 中设置了 initrd的话,grub会读出该文件的地址以及文件的大小,然后将其写入setup.S的head区的特定地方,待内核启动时使用.grub引导结束后,应该跳转至setup.S执行.

这样的initrd.img文件是由grub载入内存,然后再将内存中的initrd信息告诉内核,让内核进行初始化。

内核文件头部是这样一个数据结构:

/* For the Linux/i386 boot protocol version 2.03. */
struct linux_kernel_header
{
char code1[0x0020];
unsigned short cl_magic; /* Magic number 0xA33F */
unsigned short cl_offset; /* The offset of command line */
char code2[0x01F1 – 0x0020 – 2 – 2];
unsigned char setup_sects; /* The size of the setup in sectors */
unsigned short root_flags; /* If the root is mounted readonly */
unsigned short syssize; /* obsolete */
unsigned short swap_dev; /* obsolete */
unsigned short ram_size; /* obsolete */
unsigned short vid_mode; /* Video mode control */
unsigned short root_dev; /* Default root device number */
unsigned short boot_flag; /* 0xAA55 magic number */
unsigned short jump; /* Jump instruction */
unsigned long header; /* Magic signature “HdrS” */
unsigned short version; /* Boot protocol version supported */
unsigned long realmode_swtch; /* Boot loader hook */
unsigned long start_sys; /* Points to kernel version string */
unsigned char type_of_loader; /* Boot loader identifier */
unsigned char loadflags; /* Boot protocol option flags */
unsigned short setup_move_size; /* Move to high memory size */
unsigned long code32_start; /* Boot loader hook */
unsigned long ramdisk_image; /* initrd load address */
unsigned long ramdisk_size; /* initrd size */
unsigned long bootsect_kludge; /* obsolete */
unsigned short heap_end_ptr; /* Free memory after setup end */
unsigned short pad1; /* Unused */
char *cmd_line_ptr; /* Points to the kernel command line */
unsigned long initrd_addr_max; /* The highest address of initrd */
} __attribute__ ((packed));

grub 首先把vmlinuxz装载入内存,然后把initrd装载入内存,并initrd的装载地址
填入vmlinuxz的头部相应位置

4、grub能知道该把intrd.img这个文件放在内存的什么地方才安全吗?

答:Linux为它的启动制订了一个标准协议,GRUB或者LILO要启动Linux,就必须遵循这个协议。该协议放在内核源码目录的”Documentation/i386/boot.txt”。
      根据这个协议,initrd的位置尽可能地放在内存的高地址,这个高地址的值取决于机器的架构,但为了达到最大的兼容性,一般放在16MB靠后的地方,因为16MB是i286能识别的最大内存。另外,不同的bootloader,对initrd的加载地址可能有点差异,但只要不覆盖内核以及内核初始化可能要用的数据区即可。

5、struct linux_kernel_header在哪个文件中定义的阿?

答:grub-0.97源码 /stage2/share.h文件中
      linux 内核 linux-2.6.18/Documentation/i386/boot.txt 也说明了头部结构
The header looks like:

Offset Proto Name Meaning
/Size

01F1/1 ALL(1 setup_sects The size of the setup in sectors
01F2/2 ALL root_flags If set, the root is mounted readonly
01F4/4 2.04+(2 syssize The size of the 32-bit code in 16-byte paras
01F8/2 ALL ram_size DO NOT USE – for bootsect.S use only
01FA/2 ALL vid_mode Video mode control
01FC/2 ALL root_dev Default root device number
01FE/2 ALL boot_flag 0xAA55 magic number
0200/2 2.00+ jump Jump instruction
0202/4 2.00+ header Magic signature “HdrS”
0206/2 2.00+ version Boot protocol version supported
0208/4 2.00+ realmode_swtch Boot loader hook (see below)
020C/2 2.00+ start_sys The load-low segment (0x1000) (obsolete)
020E/2 2.00+ kernel_version Pointer to kernel version string
0210/1 2.00+ type_of_loader Boot loader identifier
0211/1 2.00+ loadflags Boot protocol option flags
0212/2 2.00+ setup_move_size Move to high memory size (used with hooks)
0214/4 2.00+ code32_start Boot loader hook (see below)
0218/4 2.00+ ramdisk_image initrd load address (set by boot loader)
021C/4 2.00+ ramdisk_size initrd size (set by boot loader)
0220/4 2.00+ bootsect_kludge DO NOT USE – for bootsect.S use only
0224/2 2.01+ heap_end_ptr Free memory after setup end
0226/2 N/A pad1 Unused
0228/4 2.02+ cmd_line_ptr 32-bit pointer to the kernel command line
022C/4 2.03+ initrd_addr_max Highest legal initrd address

在文件arch/i386/boot/bootsect.S可以看到这个结构的影子

.org 497
setup_sects: .byte SETUPSECTS
root_flags: .word ROOT_RDONLY
syssize: .word SYSSIZE
swap_dev: .word SWAP_DEV
ram_size: .word RAMDISK
vid_mode: .word SVGA_MODE
root_dev: .word ROOT_DEV
boot_flag: .word 0xAA55

发表评论

− four = three