Linux2.6中启动ramdisk分析
一、起因
使用busybox制作了一个cpio.gz的文件系统,然后使用这个文件系统作为qemu的启动盘进行启动,最后发现可以识别出是一个cpio文件系统,但是到最后还是出现了panic,说是找不到文件系统。大致的错误类型为"VFS: Cannot open root device \"
……
panic("VFS: Unable to mount root fs on %s", b);
也就是通过源文件的搜索可以看到是在linux-2.6.37.1\init\do_mounts.c: mount_block_root函数中出现的问题。所以就需要分析一下内核为什么会走到这一步,也就是我的ramdisk哪里出了问题。
二、内核的启动流程
1、对于ramdisk的使用
linux-2.6.37.1\init\main.c::kernel_init()函数中实现
/*
* check if there is an early userspace init. If yes, let it do all
* the work
*/
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";这里设置执行的默认ramdisk命令。这个值可以通过内核启动参数rdinit设置,如果没有设置,使用默认的rd文件系统中根文件夹下的init文件,这个很奇怪,不是在/sbin/init,可能是为了简介吧。
/*
static int __init rdinit_setup(char *str)
{
unsigned int i;
ramdisk_execute_command = str;
/* See "auto" comment in init_setup */
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("rdinit=", rdinit_setup);
*/
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();}
这里的代码是整个ramdisk加载的核心分水岭,如果这个ramdisk_execute_command的值非零,那么就不会有很面的尝试了,就会让ramdisk_execute_command完成整个加载过程。我今天加载失败的时候,发现我的busybox里面就没有这个文件。
2、如果ramdisk中不存在init文件
如果不存在,明显就是需要执行prepare_namespace函数来完成了。这个函数首先判断命令行中指定的根文件系统所在设备类型,注意,这里指定的虽然是文件,但是它很可能是一个设备文件,在Linux中,设备也是文件,只是一种特殊的文件而已。所以,可以让boot指定使用的启动设备是在ramdisk中的那个文件,从而通过该文件确定为一个设备。
static int __init root_dev_setup(char *line)
{
strlcpy(saved_root_name, line, sizeof(saved_root_name));
return 1;
}
__setup("root=", root_dev_setup);
不管如何,此处可能玩出很多花样,但是此时都是最终确定一个跟文件设备,也就是设置好ROOT_DEV的值,从而为最终的启动做好准备。
3、ramdisk中image文件的加载
int __init initrd_load(void)
{
if (mount_initrd) {
create_dev("/dev/ram", Root_RAM0);
/*
* Load the initrd data into /dev/ram0. Execute it as initrd
* unless /dev/ram0 is supposed to be our actual root device,
* in that case the ram disk is just set up here, and gets
* mounted in the normal path.
*/
if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) {
sys_unlink("/initrd.image");
handle_initrd();
return 1;
}
镜像的加载
int __init rd_load_image(char *from)
out_fd = sys_open((const char __user __force *) "/dev/ram", O_RDWR, 0);
if (out_fd < 0)
goto out;
in_fd = sys_open(from, O_RDONLY, 0);
if (in_fd < 0)
goto noclose_input;
nblocks = identify_ramdisk_image(in_fd, rd_image_start, &decompressor);首先判断是否是一个文件系统的镜像文件,如果不是那就不做特殊处理,也就是通过dd if of 创建的一个完整备份。
}
sys_unlink("/initrd.image");
return 0;
}
static void __init handle_initrd(void)
{
int error;
int pid;
…………
/*
* In case that a resume from disk is carried out by linuxrc or one of
* its children, we need to tell the freezer not to wait for us.
*/
current->flags |= PF_FREEZER_SKIP;
pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD);同样是根文件系统下的linuxrc文件,这里创建一个单独的线程来执行该文件,注意,这里的第二个参数是一个文件的绝对路径。
if (pid > 0)
while (pid != sys_wait4(-1, NULL, 0, NULL)) 这里同步等待新创建的linuxrc的完成,所以虽然是创建了一个单独的线程,但是依然是一个同步等待的过程。因为这个linuxrc很多时候就是完成一些特殊的驱动的加载,也就是原始ramdisk中驱动模块的选择。
yield();
4、/initrd.image文件的由来
static int __init populate_rootfs(void)
{
char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size);
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_size);
}
printk(KERN_INFO "rootfs image is not initramfs (%s)"
"; looks like an initrd\n", err);
fd = sys_open((const char __user __force *) "/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;
}
这个函数是通过rootfs_initcall(populate_rootfs);由init_calls调用的,所以还是比较早得。总起来说,就是首先尝试是一个cpio.gz文件,如果不是,那么假设是一个image文件,在rootfs根文件系统下创建一个/initrt.image文件,并通过sys_write将initrd中的所有内容直接写入该文件。
5、initrd_start initrd_end的由来
这个是内核和bootloader之间约定好的方式,不同的体系结构有不同的实现方式,在386是通过参数页设置,而PowerPC下则是通过特定寄存器由loader传递给内核。
三、网络资源
这是一篇很好的文章,可以参考一下