initrd、rootfs及内核root=参数

一、0号和1号进程

通俗的讲0号进程就是以start_kernel为入口的一个任务,也就是内核本身,这个任务的task_struct结构就是我们在编译的时候静态初始化的init_task结构,这个结构的位置和地址在可执行文件生成的时候就已经确定,其中的大部分成员都按照所需进行了初始化。

1号任务就是以init函数为入口的一个任务,这个任务对内核来说,就是一个一个一般的线程,通过kernel_thread创建一个线程,为它分配一个task_struct结构,只是这个线程执行的代码是在内核中写入的,而不是一个用户态代码。这个任务在执行的最后会通过exec函数执行用户态约定好的可执行文件,从而派生出系统的第一个任务。所有的内核,如果没有定制,那么他们到执行init之前,所有的代码都是相同的。不同的系统如何配置,就是需要在这个init可执行文件中进行设置了。

二、rootfs和initrd

对于rootfs,是整个内核中真正的所有文件系统的根节点,它同样是内核中静态创建的一个结构。这个结构的特殊之处就在于它不要来任何外部设备而只以来内存。用内核文档的话来说,它是一个特殊的ramfs。它的定义就是位于\linux-2.6.21\fs\ramfs\inode.c中,其中定义了rootfs,从这个名字就可以看出,这是系统中的根文件系统,并且它只存在于内存中,不依赖于外设。这个优点就是可以在外设没有初始化的时候创建根文件系统。从另一方面来看,这也是必须的:因为根文件系统都是挂载在外设中的,而外设一般又是通过/dev来访问的,所以必须首先有一个不依赖于外设的文件,这样才能创建设备文件,进而在这个设备文件上创建根文件系统

这个根文件系统的注册位于init_rootfs,这个其实比较简单,只是简单的注册了这个文件系统,这个文件系统的名称就是rootfs文件系统。而这个真正的系统文件系统的创建则是由init_mount_tree函数来执行的,执行的调用连关系为

start_kernel--->>>vfs_caches_init--->>mnt_init--->>>init_mount_tree

static void __init init_mount_tree(void)
{
 struct vfsmount *mnt;
 struct mnt_namespace *ns;

 mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
 if (IS_ERR(mnt))
  panic("Can't create rootfs");
 ns = kmalloc(sizeof(*ns), GFP_KERNEL);
 if (!ns)
  panic("Can't allocate initial namespace");
 atomic_set(&ns->count, 1);
 INIT_LIST_HEAD(&ns->list);
 init_waitqueue_head(&ns->poll);
 ns->event = 0;
 list_add(&mnt->mnt_list, &ns->list);
 ns->root = mnt;
 mnt->mnt_ns = ns;

 init_task.nsproxy->mnt_ns = ns;
 get_mnt_ns(ns);

 set_fs_pwd(current->fs, ns->root, ns->root->mnt_root);
 set_fs_root(current->fs, ns->root, ns->root->mnt_root
);通过这一步,已经 设置了0号进程的根文件系统,这样就可以通过相对路径和绝对路径来访问一个文件系统。同时更为重要也是最为重要的就是,我们已经可以使用文件系统的所有接口,包括设备文件的创建,文件夹的创建,文件的创建,文件系统的挂在等,这就为initrd指定的文件系统的挂在创建的基本条件
}
这里只是创建了一个挂接树的根文件夹,此时文件系统已经可以使用,可以调用sys_open sys_mknod之类的接口了

initrd是一个ramdisk文件,也就是它本身也是一个文件系统,一般是通过cpio + gzip生成的一个文件,或者大家可以按照tar来理解,就是一个基本的包含了一个文件系统结构的压缩文件,当然里面还可以包含更多的东西,例如包含大量的可执行文件,但是一般里面只是包含了根文件系统大致的框架,例如proc文件夹、dev文件夹等我们常见的文件夹,还可以包含系统启动时的一些配置文件等。

这个initrd的其实地址和结束地址同样需要由bootloader传递给内核,但是我们通过 cat /proc/cmdline 中并不能看到initrd的开始和结束,甚至不能看到内核的命令行中有任何的内容,这一点在我们确定使用了initrd的qemu中也看不到。事实上,initrd的起始地址和结束地址同样不能依赖于文件系统,因为rootfs是空的,正式试图通过这个initrd来创建最为简单原始的根文件系统,所以只能传递地址而不是字符串或者绝对路径

对于这个initrd的起始地址和结束地址的传递方法,不同的体系结构有不同的实现约定。对于i386的实现,它位于启动页面之后的。在内核的setupS中,其中列出了这些约定的位置:

start:
 jmp trampoline

…………

ramdisk_image: .long 0  # address of loaded ramdisk image
     # Here the loader puts the 32-bit
     # address where it loaded the image.
     # This only will be read by the kernel.

ramdisk_size: .long 0  # its size in bytes

在boot.txt中有相应的文字说明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 0230/4 2.05+ kernel_alignment Physical addr alignment required for kernel 0234/1 2.05+ relocatable_kernel Whether kernel is relocatable or not

对于PowerPC,这个地址则是通过寄存器来传递的,因为RISC系列的寄存器还是比较多的。

此时我们知道了一个ramdisk在内存中的位置,此时就需要把它解压缩到另一个文件系统中,从而让这个文件系统作为根文件系统。linux-2.6.21\init\initramfs.c这里就开始进行initrd的解压缩工作,这是平台无关的,但是initrd如何从bootloader传过来却是平台相关的,也就是386和powerPC传递initrd的起始和结束地址的方法是体系相关的。那么解压缩到哪里呢?其实就是解压缩到我们刚才说到的最为原始的那个不依赖于任何外设的rootfs文件系统的根文件夹下,整个视线在下面的代码中完成

static int __init populate_rootfs(void) 从这个名字也可以看到,这个populate就是污染rootfs的意思,也就是在空白的rootfs上创建新的文件系统,来自initrd。这也是rootfs第一次大批量的接受文件系统的创建和链接等
{
 char *err = unpack_to_rootfs(__initramfs_start,
    __initramfs_end - __initramfs_start, 0);
 if (err)
  panic(err);
#ifdef CONFIG_BLK_DEV_INITRD
 if (initrd_start) {
#ifdef CONFIG_BLK_DEV_RAM
  int fd;
  printk(KERN_INFO "checking if image is initramfs...");
  err = unpack_to_rootfs((char *)initrd_start,
   initrd_end - initrd_start, 1);
  if (!err) {
   printk(" it is\n");
   unpack_to_rootfs((char *)initrd_start,
    initrd_end - initrd_start, 0
);
   free_initrd();
   return 0;
  }
  printk("it isn't (%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...");
  err = unpack_to_rootfs((char *)initrd_start,
   initrd_end - initrd_start, 0);
  if (err)
   panic(err);
  printk(" done\n");
  free_initrd();
#endif
 }
#endif
 return 0;
}

具体的文件系统的创建 unpack_to_rootfs--->>>write_buffer--->>actions--->>do_symlink等完成cpio中文件系统向rootfs文件系统的创建。此时根文件系统就由大量的文件夹了,可能还包括copyfile传递过来的文件

root=命令的解析和使用linux-2.6.21\init\do_mounts.c

__setup("root=", root_dev_setup);

static int __init root_dev_setup(char *line)
{
 strlcpy(saved_root_name, line, sizeof(saved_root_name));
 return 1;
}

这里就将root设备保存到了saved_root_name中,从而可以在之后使用,注意:此时rootfs中已经保存了initrd中的内容,所以整个root是一个文件系统的路径。同样是在这个文件中,完成了对于真正的initrd中文件系统的加载和根文件系统的切换,从而让用户看到的是root=设置的路径

/*
 * Prepare the namespace - decide what/where to mount, load ramdisks, etc.
 */
void __init prepare_namespace(void)
{
 int is_floppy;

 if (root_delay) {
  printk(KERN_INFO "Waiting %dsec before mounting root device...\n",
         root_delay);
  ssleep(root_delay);
 }

 /* wait for the known devices to complete their probing */
 while (driver_probe_done() != 0)
  msleep(100);

 md_run_setup();

 if (saved_root_name[0]) {
  root_device_name = saved_root_name;
  if (!strncmp(root_device_name, "mtd", 3)) {
   mount_block_root(root_device_name, root_mountflags);
   goto out;
  }
  ROOT_DEV = name_to_dev_t(root_device_name);
  if (strncmp(root_device_name, "/dev/", 5) == 0)
   root_device_name += 5;
 }

 is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;

 if (initrd_load())
  goto out;

 if (is_floppy && rd_doload && rd_load_disk(0))
  ROOT_DEV = Root_RAM0;

 mount_root();
out:
 sys_mount(".", "/", NULL, MS_MOVE, NULL);
 sys_chroot(".");
 security_sb_post_mountroot();
}

最后的sys_chroot将会修改根文件

 set_fs_root(current->fs, nd.mnt, nd.dentry);
 set_fs_altroot();


然后通过name_to_dev_t将一个字符串形式的路径转换为一个设备,例如可能是/dev/ram0,表示整个根文件系统在一个randisk中,二这个ramdisk很可能就是一个flash。注意:这个接口比较妙,它可以将一个字符串路径转换为一个设备号,而ROOT_DEV是早期版本中内核启动的依据。这样加上这个函数,就可以使之后的代码依然只依赖和使用ROOT_DEV这种简单的《Major,Minor》方式找到一个设备,例如一块硬盘,一个flash,或者VMware使用mapper等。当然,其中也使用了一些技巧,但是还是要强调,根文件是一个设备,这一点可以通过

ls /dev/root -l

看到根文件系统对应的设备文件。

四、总结

在Vmware上的一些测试

者说明内核使用的跟设备的设备号为
[tsecer@Harry BuildDir]$ ls /dev/mapper/vg_harry-lv_root  -l
brw-rw----. 1 root disk 253, 0 2011-03-27 11:54 /dev/mapper/vg_harry-lv_root
240-254 block LOCAL/EXPERIMENTAL USE
  Allocated for local/experimental use.  For devices not
  assigned official numbers, these ranges should be
  used in order to avoid conflicting with future assignments.

[root@Harry BuildDir]# mkdir /dev/MyRoot
[root@Harry BuildDir]# mount -t ext4 /dev/mapper/vg_harry-lv_root /dev/MyRoot/
[root@Harry BuildDir]# ls /dev/MyRoot/
bin   dev  home  lost+found  mnt  proc  sbin     srv  tmp  var
boot  etc  lib   media       opt  root  selinux  sys  usr
[root@Harry BuildDir]# cd /dev/MyRoot/
[root@Harry MyRoot]# ls
bin   dev  home  lost+found  mnt  proc  sbin     srv  tmp  var
boot  etc  lib   media       opt  root  selinux  sys  usr
[root@Harry MyRoot]# ls root/
anaconda-ks.cfg  install.log  install.log.syslog
[root@Harry MyRoot]# ls /root/
anaconda-ks.cfg  install.log  install.log.syslog
[root@Harry MyRoot]# ls /home/

但是对于动态挂接的文件,这里并没有创建这么多的设备文件
[root@Harry MyRoot]# pwd
/dev/MyRoot
[root@Harry MyRoot]# ls /dev/
agpgart          floppy-fd0          parport0  snapshot  tty31  tty61
block            full                port      snd       tty32  tty62
bsg              fuse                ppp       sr0       tty33  tty63
bus              gpmctl              ptmx      stderr    tty34  tty7
cdrom            hpet                pts       stdin     tty35  tty8

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

导航