根文件系统安装操作的第二阶段是由内核在系统初始化即将结束时进行的。根据内核被编译时所选择的选项,和内核装入程序所传递的启动选项,可以有几种方法安装实际根文件系统。为了简单起见,我们只考虑磁盘文件系统的情况,它的设备文件名已通过“root”启动参数传递给内核。同时我们假定除了rootfs文件系统外,没有使用其他初始特殊文件系统。
这里最后调用create_dev("/dev/root", ROOT_DEV),在rootfs中新建了一个/dev/root设备文件,这个设备文件一般就是指内核启动参数指定的包含根文件系统的设备,在rootfs中,这个设备文件被命名为/dev/root.再来看一下mount_block_root:
sys_mount的挂载流程参考 http://blog.csdn.net/new_abc/article/details/7711756
从代码中可以看中,会依次执行指定的init文件,如果失败,就会执行/sbin/init, /etc/init,, /bin/init,/bin/sh
注意的是,run_init_process在调用相应程序运行的时候,用的是kernel_execve。也就是说调用进程会替换当前进程。只要上述任意一个文件调用成功,就不会返回到这个函数。如果上面几个文件都无法执行。打印出没有找到init文件的错误。
内核主要是通过kernel_init调用prepare_namespace()函数执行安装实际根文件系统的操作:
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
*
* Note: this is a potential source of long boot delays.
* For example, it is not atypical to wait 5 seconds here
* for the touchpad of a laptop to initialize.
*/
wait_for_device_probe();
md_run_setup();
/* 把root_device_name变量置为从启动参数“root”中获取的设备文件名。
* 同样,把ROOT_DEV变量置为同一设备文件的主设备号和次设备号。*/
if (saved_root_name[0]) {
root_device_name = saved_root_name;
if (!strncmp(root_device_name, "mtd", 3) ||
!strncmp(root_device_name, "ubi", 3)) {
mount_block_root(root_device_name, root_mountflags);
goto out;
}
ROOT_DEV = name_to_dev_t(root_device_name);//转换为设备号/dev/mtdblock2.
if (strncmp(root_device_name, "/dev/", 5) == 0)
root_device_name += 5;
}
if (initrd_load())
goto out;
/* wait for any asynchronous scanning to complete */
if ((ROOT_DEV == 0) && root_wait) {
printk(KERN_INFO "Waiting for root device %s...\n",
saved_root_name);
while (driver_probe_done() != 0 ||
(ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)
msleep(100);
async_synchronize_full();
}
is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;
if (is_floppy && rd_doload && rd_load_disk(0))
ROOT_DEV = Root_RAM0;
mount_root();
out:
devtmpfs_mount("dev");//挂载devtmpfs文件系统
sys_mount(".", "/", NULL, MS_MOVE, NULL); /* 移动rootfs文件系统根目录上的已安装文件系统的安装点。 */
sys_chroot(".");
}
一般情况下如果根文件系统是磁盘文件系统,为/dev/mtdblock2.所以这里最后会进入mount_root函数:
void __init mount_root(void)
{
printk("mount_root.\n");
#ifdef CONFIG_ROOT_NFS
if (MAJOR(ROOT_DEV) == UNNAMED_MAJOR) {
if (mount_nfs_root())
return;
printk(KERN_ERR "VFS: Unable to mount root fs via NFS, trying floppy.\n");
ROOT_DEV = Root_FD0;
}
#endif
#ifdef CONFIG_BLK_DEV_FD
if (MAJOR(ROOT_DEV) == FLOPPY_MAJOR) {
/* rd_doload is 2 for a dual initrd/ramload setup */
if (rd_doload==2) {
if (rd_load_disk(1)) {
ROOT_DEV = Root_RAM1;
root_device_name = NULL;
}
} else
change_floppy("root floppy");
}
#endif
#ifdef CONFIG_BLOCK
printk("CONFIG_BLOCK.\n");
create_dev("/dev/root", ROOT_DEV);
mount_block_root("/dev/root", root_mountflags);
#endif
}
这里最后调用create_dev("/dev/root", ROOT_DEV),在rootfs中新建了一个/dev/root设备文件,这个设备文件一般就是指内核启动参数指定的包含根文件系统的设备,在rootfs中,这个设备文件被命名为/dev/root.再来看一下mount_block_root:
void __init mount_block_root(char *name, int flags)
{
char *fs_names = __getname_gfp(GFP_KERNEL//分配空间
| __GFP_NOTRACK_FALSE_POSITIVE);
char *p;
#ifdef CONFIG_BLOCK
char b[BDEVNAME_SIZE];
#else
const char *b = name;
#endif
get_fs_names(fs_names);//获取已注册的文件系统
retry:
for (p = fs_names; *p; p += strlen(p)+1) {
int err = do_mount_root(name, p, flags, root_mount_data);//将获取到的文件系统类型逐个去匹配挂载根设备,只有合适的文件系统类型才能匹配到根设备文件系统的类型,进而挂载成功
switch (err) {
case 0:
goto out;
case -EACCES:
flags |= MS_RDONLY;
goto retry;
case -EINVAL:
continue;
}
/*
* Allow the user to distinguish between failed sys_open
* and bad superblock on root device.
* and give them a list of the available devices
*/
#ifdef CONFIG_BLOCK
__bdevname(ROOT_DEV, b);
#endif
printk("VFS: Cannot open root device \"%s\" or %s\n",
root_device_name, b);
printk("Please append a correct \"root=\" boot option; here are the available partitions:\n");
printk_all_partitions();
#ifdef CONFIG_DEBUG_BLOCK_EXT_DEVT
printk("DEBUG_BLOCK_EXT_DEVT is enabled, you need to specify "
"explicit textual name for \"root=\" boot option.\n");
#endif
panic("VFS: Unable to mount root fs on %s", b);
}
printk("List of all partitions:\n");
printk_all_partitions();
printk("No filesystem could mount root, tried: ");
for (p = fs_names; *p; p += strlen(p)+1)
printk(" %s", p);
printk("\n");
#ifdef CONFIG_BLOCK
__bdevname(ROOT_DEV, b);
#endif
panic("VFS: Unable to mount root fs on %s", b);
out:
putname(fs_names);
}
do。再看一下do_mount_root
static int __init do_mount_root(char *name, char *fs, int flags, void *data)
{
int err = sys_mount(name, "/root", fs, flags, data);
if (err)
return err;
sys_chdir("/root");
ROOT_DEV = current->fs->pwd.mnt->mnt_sb->s_dev;
printk("VFS: Mounted root (%s filesystem)%s on device %u:%u.\n",
current->fs->pwd.mnt->mnt_sb->s_type->name,
current->fs->pwd.mnt->mnt_sb->s_flags & MS_RDONLY ?
" readonly" : "", MAJOR(ROOT_DEV), MINOR(ROOT_DEV));
return 0;
}
sys_mount的挂载流程参考 http://blog.csdn.net/new_abc/article/details/7711756
回到prepare_namespace中,在顺利mount_root之后,然后还挂载了devtmpfs,挂载了dev目录。由于之前已经切换到了新的根文件系统的根目录中去了,所以这两步的作用是用新的根文件系统目录替换rootfs,使其成为Linux VFS的根目录。
到这里实际的根文件系统就已经安装上去了。
在kernel_init调用prepare_namespace完后,紧接着执行init_post
/* This is a non __init function. Force it to be noinline otherwise gcc
* makes it inline to init() and it becomes part of init.text section
*/
static noinline int init_post(void)
__releases(kernel_lock)
{
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
unlock_kernel();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
printk("init_post \n.");
current->signal->flags |= SIGNAL_UNKILLABLE;
if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
printk("init_post---------------1 \n.");
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
printk("init_post---------------2 \n.");
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
printk("init_post---------------3 \n.");
run_init_process("/init");
printk("init_post---------------4 \n.");
panic("No init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
从代码中可以看中,会依次执行指定的init文件,如果失败,就会执行/sbin/init, /etc/init,, /bin/init,/bin/sh
注意的是,run_init_process在调用相应程序运行的时候,用的是kernel_execve。也就是说调用进程会替换当前进程。只要上述任意一个文件调用成功,就不会返回到这个函数。如果上面几个文件都无法执行。打印出没有找到init文件的错误。
到这里,根文件系统就已经挂载完毕,并已经启动了第一个init进程。