不浮躁
分类: LINUX
2014-11-14 21:13:24
挂载根文件系统
挂载根文件系统是一个相当复杂的过程,因为Linux内核允许根文件系统存放在很多不同的地方,比如硬盘分区、软盘、通过NFS共享的远程文件系统,甚至保存在ramdisk中(RAM中的虚拟块设备)。
为了使叙述变得简单,在此假定根文件系统存放在硬盘分区(毕竟这是最常见的情行)。当系统启动时,内核就要在变量ROOT_DEV中寻找包含根文件系统的磁盘主设备号:
---------------------------------------------------------------------
init/do_mounts.c
dev_t ROOT_DEV;
---------------------------------------------------------------------
当编译内核时,或者向最初的启动装入程序传递一个合适的“root”选项时,根文件系统可以被指定为/dev目录下的一个设备文件。类似地,根文件系统的安装标志存放在root_mountflags变量中:
---------------------------------------------------------------------
init/do_mounts.c
int root_mountflags = MS_RDONLY | MS_SILENT;
---------------------------------------------------------------------
用户可以指定这些标志,或者通过对已编译的内核映像使用rdev外部程序,或者向最初的启动装入程序传递一个合适的rootflags选项来达到。
挂载根文件系统分两个阶段:
(1)内核挂载特殊的rootfs文件系统,该文件系统仅提供一个作为初始挂载点的空目录。
(2)内核在空目录上挂载实际根文件系统。
为什么内核不怕麻烦,要在挂载实际根文件系统之前挂载rootfs文件系统呢?这是因为,rootfs文件系统允许内核容易地改变实际根文件系统。实际上,在大多数情况下,系统初始化是内核会逐个地挂载和卸载几个根文件系统。例如,一个发行版的初始启动光盘可能把具有一组最小驱动程序的内核装人 RAM中,内核把存放在ramdisk中的一个最小的文件系统作为根挂载。接下来,在这个初始根文件系统中的程序探测系统的硬件(例如,它们判断硬盘是否
是EIDE、SCSI等等),装入所有必需的内核模块,并从物理块设备重新安装根文件系统。
阶段1:挂载rootfs文件系统
第一阶段是由init_rootfs()和init_mount_tree()函数完成的,它们在系统初始化过程中执行。
init_rootfs()函数注册rootfs特殊文件系统类型(start_kernel()-> vfs_caches_init()-> mnt_init()-> init_rootfs()),其定义如下:
---------------------------------------------------------------------
fs/ramfs/inode.c
static struct = {
. = "rootfs",
. = ,
.kill_sb = ,
};
int (void)
{
int ;
= (&ramfs_backing_dev_info);
if ()
return ;
= (&);
if ()
(&ramfs_backing_dev_info);
return ;
}
---------------------------------------------------------------------
init_mount_tree()函数主要完成根文件系统的初始化(start_kernel()->
vfs_caches_init()-> mnt_init()->init_mount_tree()),其定义如下:
---------------------------------------------------------------------
fs/namespace.c
static void __init init_mount_tree(void)
{
struct vfsmount *mnt;
struct
mnt_namespace *ns;
struct path
root;
mnt =
do_kern_mount("rootfs", 0, "rootfs", NULL);
if (IS_ERR(mnt))
panic("Can't
create rootfs");
ns = create_mnt_ns(mnt);
if (IS_ERR(ns))
panic("Can't
allocate initial namespace");
init_task.nsproxy->mnt_ns = ns;
get_mnt_ns(ns);
root.mnt =
ns->root;
root.dentry =
ns->root->mnt_root;
set_fs_pwd(current->fs, &root);
set_fs_root(current->fs, &root);
}
---------------------------------------------------------------------
init_mount_tree()执行如下操作:
1、调用do_kern_mount()函数(第2313行),把字符串"rootfs"作为文件系统类型和设备名参数传递给它,而标志flags及data参数分别为0和NULL。并把该函数返回的vfsmount描述符的地址保存在mnt局部变量中。
如前面所说,do_kern_mount()调用get_fs_type(),获得文件系统类型的地址,对于init_mount_tree ()来说,也就是get_fs_type()返回rootfs_fs_type的地址。
然后,do_kern_mount()调用vfs_kern_mount(),将rootfs_fs_type地址及"rootfs"分别作为文件系统类型和设备名参数传递给他,flags及data参数则依然分别为0和NULL。
在函数vfs_kern_mount()中,首先,调用alloc_vfsmnt()函数分配vfsmount对象,将其地址存放在局部变量mnt中。接着,以rootfs_fs_type地址及mnt为参数调用rootfs文件系统的get_sb方法,也即是rootfs_get_sb(),flags及data参数则依然分别为0和NULL。rootfs_get_sb()定义如下:
---------------------------------------------------------------------
fs/ramfs/inode.c
static int (struct *,
int , const char *dev_name, void *, struct *mnt)
{
return get_sb_nodev(, |, ,
, mnt);
}
---------------------------------------------------------------------
rootfs_get_sb()调用get_sb_nodev(),直接将传递进来的参数都传给它,也就是rootfs_fs_type地址及"rootfs"分别作为文件系统类型和名字作为参数传递给它,
data参数则依然为NULLflags,设置了标志flags的位,同时,也将()函数地址传递给了它。前面已经提到了get_sb_nodev(),对于rootfs文件系统,则执行操作:
a、调用sget()函数分配新的超级块,传递set_anon_super()函数的地址作为set参数,rootfs_fs_type地址及两个NULL分别作为fs_type、data及mnt参数。
在sget()函数中,对于挂载rootfs来说,则是调用alloc_super(type)分配super_block对象,初始化各个字段s_op字段被设置为default_op。然后调用set函数,也就是set_anon_super()函数,由alloc_super(type)分配的super_block对象地址及NULL为参数。set_anon_super()函数定义如下:
---------------------------------------------------------------------
fs/super.c
int (struct *, void *)
{
int dev;
int ;
retry:
if ((&unnamed_dev_ida, ) == 0)
return -;
(&unnamed_dev_lock);
= (&unnamed_dev_ida, unnamed_dev_start, &dev);
if (!)
unnamed_dev_start = dev + 1;
(&unnamed_dev_lock);
if ( == -)
/* We raced and lost with another CPU. */
goto retry;
else if ()
return -;
if ((dev & ) == (1 << )) {
(&unnamed_dev_lock);
(&unnamed_dev_ida, dev);
if (unnamed_dev_start > dev)
unnamed_dev_start = dev;
(&unnamed_dev_lock);
return -;
}
->s_dev = (0, dev & );
->s_bdi = &noop_backing_dev_info;
return 0;
}
---------------------------------------------------------------------
set_anon_super()函数主要是用合适的方式设置超级快的s_dev字段:主设备号为O,次设备号不同于其他已安装的特殊文件系统的次设备号。
接下来sget()函数将rootfs_fs_type地址赋给超级块的s_type字段,将文件类型的name字段拷贝给超级块的s_id字段,将超级快添加进超级块链表的的尾部,将超级快添加进文件系统的fs_supers链表中,增加文件系统的引用计数,并返回超级块的地址。
b、将flags参数的值拷贝到超级块的s_flags字段中。
c、调用ramfs_fill_super()函数分配索引节点对象和对应的目录项对象并填充超级块一些字段值,sget获得的超级块地址为参数,data参数为NULL。由于rootfs是一种特殊文件系统,没有磁盘超级块,ramfs_fill_super()函数定义如下:
---------------------------------------------------------------------
fs/ramfs/inode.c
static int (struct * , void * , int silent)
{
struct *fsi;
struct * = ;
struct *;
int ;
(, );
fsi = (sizeof(struct ), );
-> = fsi;
if (!fsi) {
= -;
goto fail;
}
= (, &fsi->mount_opts);
if ()
goto fail;
->s_maxbytes = ;
->s_blocksize = ;
->s_blocksize_bits = ;
->s_magic = ;
->s_op = &;
->s_time_gran = 1;
= (, | fsi->mount_opts., 0);
if (!) {
= -;
goto fail;
}
= ();
->s_root = ;
if (!) {
= -;
goto fail;
}
return 0;
fail:
(fsi);
-> = ;
();
return ;
}
---------------------------------------------------------------------
d.设置超级快对象的s_flags标志字段的MS_ACTIVE,调用simple_set_mnt()将传递进来的vfsmount对象和超级快对象连接起来。
rootfs文件系统的get_sb方法返回后,则vfs_kern_mount()继续将mnt->mnt_mountpoint =
mnt->mnt_root; 用mnt中的值初始化mnt->mnt_parent字段。并返回vfsmount对象地址。
vfs_kern_mount()函数返回后do_kern_mount()函数减少文件系统的引用计数,并返回vfsmount对象地址
2、init_mount_tree(void)函数调用create_mnt_ns(mnt)函数为进程0的已挂载文件系统命名空间分配一个mnt_namespace对象,并将它插入到由do_kern_mount()函数返回的已安装文件系统描述符中,create_mnt_ns(mnt)函数定义为:
---------------------------------------------------------------------
fs/namespace.c
struct *(struct *mnt)
{
struct *new_ns;
new_ns = ();
if (!(new_ns)) {
mnt->mnt_ns = new_ns;
new_ns-> = mnt;
(&new_ns->, new_ns->->mnt_list);
}
return new_ns;
}
();
---------------------------------------------------------------------
(struct
*mnt)返回后,增加mnt_namespace对象其引用计数
3、将进程0的根目录和当前工作目录设置为根文件系统。
阶段2:安装实际根文件系统
根文件系统安装操作的第二阶段是由内核在系统初始化即将结束时进行的。根据内核被编译时所选择的选项,和内核装入程序所传递的启动选项,可以有几种方法安装实际根文件系统。为了简单起见,我们只考虑磁盘文件系统的情况,它的设备文件名已通过“root”启动参数传递给内核。同时我们假定除了 rootfs文件系统外,没有使用其他初始特殊文件系统。
内核主要是调用prepare_namespace()函数执行安装实际根文件系统的操作( start_kernel(void)-> rest_init(void)-> kernel_thread(kernel_init,
NULL, CLONE_FS | CLONE_SIGHAND)-> kernel_init()-> prepare_namespace()), prepare_namespace()函数定义如下:
---------------------------------------------------------------------
init/do_mounts.c
/*
* Prepare the namespace - decide what/where to mount, load ramdisks, etc.
*/
void (void)
{
int is_floppy;
if () {
( "Waiting %dsec before mounting root device...\n",
);
();
}
/*
* 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();
();
if ([0]) {
root_device_name = ;
if (!(root_device_name, "mtd", 3) ||
!(root_device_name, "ubi", 3)) {
(root_device_name, );
goto ;
}
= name_to_dev_t(root_device_name);
if ((root_device_name, "/dev/", 5) == 0)
root_device_name += 5;
}
if (())
goto ;
/* wait for any asynchronous scanning to complete */
if (( == 0) && ) {
( "Waiting for root device %s...\n",
);
while (() != 0 ||
( = name_to_dev_t()) == 0)
(100);
();
}
is_floppy = () == ;
if (is_floppy && && (0))
= Root_RAM0;
();
:
devtmpfs_mount("dev");
(".", "/", , , );
(".");
}
---------------------------------------------------------------------
(void)主要执行如下操作:
1、根据静态变量root_delay的值来进行一定的延迟。这个变量根据内核的启动选项进行设置。root_delay变量定义如下:
---------------------------------------------------------------------
init/do_mounts.c
static unsigned int ;
static int (char *)
{
= (, , 0);
return 1;
}
("rootflags=", );
("rootfstype=", );
("rootdelay=", );
---------------------------------------------------------------------
在此,对__setup宏来做些说明。内核组件用__setup宏来注册关键字及与关键字相关联的处理函数,__setup宏在中定义如下:
---------------------------------------------------------------------
include/linux/init.h
/*
* Only for really core code. See moduleparam.h for the normal way.
*
* Force the alignment so the compiler doesn't space elements of the
* obs_kernel_param "array" too far apart in .init.setup.
*/
#define (, unique_id, fn, early) \
static const char __setup_str_##unique_id[] \
(1) = ; \
static struct __setup_##unique_id \
(..) \
((aligned((sizeof(long))))) \
= { __setup_str_##unique_id, fn, early }
#define (, fn) \
(, fn, fn, 0)
---------------------------------------------------------------------
其中:str是关键字,fn是关联处理函数。__setup只是告诉内核在启动时输入串中含有string时,内核要去执行fn。str必须以“=”符结束以使parse_args更方便解析。紧随“=”后的任何文本都会作为输入传给fn。不同的关键字可以注册相同的处理函数。当代码作为模块被编译时,__setup宏被忽略,可以在include/linux/init.h中看到 __setup宏是怎样变化的,不管后续包含它的文件是否是模块,include/linux/init.h都是独立的。
start_kernel两次调用parse_args解析启动选项字符串的原因是启动选项事实上分为两类,且每次调用只能够兼顾到其中一类:
a.缺省选项:绝大多数选项归于此类,这些选项由__setup宏定义并在第二次调用parse_args时处理。
b.先期(处理)选项:在内核启动阶段,有些选项要在其它选项之前被处理,内核提供了early_param宏以代替__setup宏申明此类选项。这些选项由 parse_early_params函数解析。early_param宏和__setup宏仅有的不同就是前者设置了一个特殊标志让内核能够区分两种不同的状况。
由此可知,当向内核提供"rootflags="选项时,会将“=”符之后的字符串作为参数调用(),而后者则是仅仅将字符串转换为整型值并赋给静态变量root_delay。
2、将root_device_name变量置为从启动参数“root”中获取的设备文件名。用户可以用“root=”来指定根文件系统所在设备。它的值保存在saved_root_name中。如果用户指定的根文件系统所在设备设备路径名以mtd或ubi开始。就会直接调用mount_block_root(root_device_name,
root_mountflags)去挂载。这两个都是FLASH文件系统。
否则,调用name_to_dev_t(root_device_name)将设备名称转换为设备的设备号,并赋给ROOT_DEV变量
3、转向initrd_load()执行initrd预处理。initrd_load()定义如下:
---------------------------------------------------------------------
init/do_mounts_initrd.c
int (void)
{
if () {
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_RAM0) {
("/initrd.image");
();
return 1;
}
}
("/initrd.image");
return 0;
}
---------------------------------------------------------------------
4、调用mount_root()函数,它定义如下:
---------------------------------------------------------------------
init/do_mounts.c
void (void)
{
#ifdef CONFIG_ROOT_NFS
if (() == ) {
if (())
return;
( "VFS: Unable to mount root fs via NFS, trying floppy.\n");
= Root_FD0;
}
#endif
#ifdef CONFIG_BLK_DEV_FD
if (() == ) {
/* rd_doload is 2 for a dual initrd/ramload setup */
if (==2) {
if ((1)) {
= Root_RAM1;
root_device_name = ;
}
} else
("root floppy");
}
#endif
#ifdef CONFIG_BLOCK
create_dev("/dev/root", );
("/dev/root", );
#endif
}
---------------------------------------------------------------------
首先根据内核的配置,这个函数挂载不同的跟文件系统:
a.挂载NFS为跟文件系统。
b.挂载软配设备为跟文件系统。
c.挂载块设备为跟文件系统。
1、调用devtmpfs_mount("dev")挂载devfs文件系统。
6、移动rootfs文件系统根目录上的已挂载文件系统的挂载点。
注意,rootfs特殊文件系统没有被卸载:它只是隐藏在基于磁盘的根文件系统下了。