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传递给内核。

三、网络资源

这是一篇很好的文章,可以参考一下

http://www.ibm.com/developerworks/cn/linux/l-k26initrd/

posted on 2019-03-06 20:26  tsecer  阅读(650)  评论(0编辑  收藏  举报

导航