Linux内核启动过程分析(4)

(1)mount_initrd表示是否使用了image-initrd。可以通过kernel的参数“noinitrd“来配置mount_initrd的值,默认为1。很少看到有项目区配置该值,所以一般情况下,mount_initrd的值应该为1。
    (2)创建一个Root_RAM0的设备节点/dev/ram,调用rd_load_image将image-initrd的数据加载到/dev/ram0。rd_load_image会打开/dev/ram0,先是用identify_ramdisk_image()识别image-initrd的文件系统类型,确定是romfs、squashfs、minix,还是ext2。然后用crd_load()为image-initrd分配空间、计算循环冗余校验码(CRC)、解压,并将其加载到内存中。
    (3)判断ROOT_DEV!=Root_RAM0的含义是,如果你在grub或者lilo里配置的root=不指定为/dev/ram0,则转向handle_initrd(),由它来挂载实际的文件系统。例如我电脑上的Fedora启动指定root=/dev/mapper/VolGroup-lv_root,肯定就不是Root_RAM0了。如果没有指定根设备(或指定为默认的/dev/ram0),则会跳过handle_initrd(),直接返回到prepare_namespace()。
    下面是handle_initrd()的代码:

static void __init handle_initrd(void)
{
 int error;
 int pid;

real_root_dev = new_encode_dev(ROOT_DEV);
 create_dev("/dev/root.old", Root_RAM0);
 /* mount initrd on rootfs' /root */
 mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY);
 sys_mkdir("/old", 0700);
 root_fd = sys_open("/", 0, 0);
 old_fd = sys_open("/old", 0, 0);
 /* move initrd over / and chdir/chroot in initrd root */
 sys_chdir("/root");
 sys_mount(".", "/", NULL, MS_MOVE, NULL);
 sys_chroot(".");

/*
  * 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);
 if (pid > 0)
  while (pid != sys_wait4(-1, NULL, 0, NULL))
  yield();

current->flags &= ~PF_FREEZER_SKIP;

/* move initrd to rootfs' /old */
 sys_fchdir(old_fd);
 sys_mount("/", ".", NULL, MS_MOVE, NULL);
 /* switch root and cwd back to / of rootfs */
 sys_fchdir(root_fd);
 sys_chroot(".");
 sys_close(old_fd);
 sys_close(root_fd);

if (new_decode_dev(real_root_dev) == Root_RAM0) {
  sys_chdir("/old");
  return;
 }

ROOT_DEV = new_decode_dev(real_root_dev);
 mount_root();

printk(KERN_NOTICE "Trying to move old root to /initrd ... ");
 error = sys_mount("/old", "/root/initrd", NULL, MS_MOVE, NULL);
 if (!error)
  printk("okay\n");
 else {
  int fd = sys_open("/dev/root.old", O_RDWR, 0);
  if (error == -ENOENT)
  printk("/initrd does not exist. Ignored.\n");
  else
  printk("failed\n");
  printk(KERN_NOTICE "Unmounting old root\n");
  sys_umount("/old", MNT_DETACH);
  printk(KERN_NOTICE "Trying to free ramdisk memory ... ");
  if (fd < 0) {
  error = fd;
  } else {
  error = sys_ioctl(fd, BLKFLSBUF, 0);
  sys_close(fd);
  }
  printk(!error ? "okay\n" : "failed\n");
 }
}

(1)real_root_dev为一个全局变量,用来保存放用户指定的根设备号。
    (2)调用mount_block_root将initrd挂载到rootfs的/root下,设备节点为/dev/root.old。提取rootfs的根目录描述符并将其保存到root_fd。它的作用就是为了在进入到initrd文件系统并处理完initrd之后,还能够返回rootfs。
    (3)进入到/root中的initrd文件系统,调用kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD)启动一个内核线程来运行/linuxrc文件,等待它完成的后续的初始化工作。
    (4)把initrd文件系统移动到rootfs的/old下。然后通过root_fd重新进入到rootfs,如果real_root_dev在linuxrc中重新设成Root_RAM0,说明直接把image-initrd直接作为真正的根文件系统,initrd_load()返回1,而后prepare_namespace()直接goto到out,改变当前目录到initrd中,不作后续处理直接返回。
    (5)如果使用用户指定的根设备,则调用mount_root将真正的文件系统挂载到VFS的/root目录下。通过调用链mount_root()--->mount_block_root()--->do_mount_root()--->sys_mount(name,"/root")可知,指定的根设备用设备节点/dev/root表示,挂载点为VFS的/root,并将当前目录切换到了这个挂载点下。
    (6)如果真实文件系统中有/initrd目录,那么会把/old中的initrd移动到真实文件系统的/initrd下。如果没有/initrd目录,则用sys_umount()卸载initrd,并释放它的内存。
    prepare_namspace执行完后,真正的文件系统就挂载成功。转入init_post(),它用来运行用户空间的第一个进程,即众所周知的init进程。代码如下:

static noinline int init_post(void)
 __releases(kernel_lock)
{
 /* ...... */

if (ramdisk_execute_command) {
  run_init_process(ramdisk_execute_command);
  printk(KERN_WARNING "Failed to execute %s\n",
    ramdisk_execute_command);
 }

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. "
      "See Linux Documentation/init.txt for guidance.");
}

注意run_init_process在调用相应程序运行的时候,用的是kernel_execve。也就是说调用进程会替换当前进程。只要上述任意一个文件调用成功,就不会返回到这个函数。如果上面几个文件都无法执行。打印出没有找到init文件的错误。运行用户空间中的init进程可能是以下几种情况:
    (1)noinitrd方式,则直接运行用户空间中的/sbin/init(或/etc/init,/bin/init),作为第一个用户进程。
    (2)传统的image-initrd方式。运行的第一个程序是/linuxrc脚本,由它来启动用户空间中的init进程。
    (3)cpio-initrd和initramfs方式。运行的第一个程序是/init脚本,由它来启动用户空间中的init进程。
    我电脑上Fedora的/boot目录下有initramfs-2.6.35.10-74.fc14.i686.img,它就是启动Fedora时指定的cpio-initrd(经过了压缩,可以用file命令查看其文件类型)。先加上.gz后缀,用gunzip解压,然后用cpio -i --make-directories < initramfs-2.6.35.10-74.fc14.i686.img命令导出它的文件。我们可以看到根目录下有/init脚本,./bin目录中有一组很少但却非常必要的应用程序,包括dash(一个脚本解释器,比bash体积小速度快,兼容性高,以前的initrd用的是nash)、plymouth、sed等。./sbin下有dmraid、kpartx、loginit脚本、lvm(逻辑卷管理器)、modprobe、switch_root、udevd等核心程序。
    /init设置$PATH环境变量,挂载procfs和sysfs、启动udev(动态设备管理进程,通过监视sysfs按照规则动态创建/dev目录中的设备,已经逐渐取代了hotplug和coldplug)、挂载真正的根文件系统、用switch_root切换到根分区并运行/sbin/init。 

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/17407.html