本文基于linux3.10某一嵌入式系统,该文件系统的配置选项设置如下:
图1.1 根文件系统配置选项设置
两行配置如下:
[*] Initial RAMfilesystem and RAM disk (initramfs/initrd) support
(usr/rootfs.cpio.gz)Initramfs source file(s)
这两行的意义是启用initramfs文件系统,并且将源码目录下的usr/roorfs.cpio.gz作为根文件系统。内核会将该文件系统加载到内存并且挂载该文件系统做为根文件系统。
在windos下通常有C盘、D盘之类的分区,可以把linux下的根文件系统“/”理解成C盘这样的设备,对于这里所说的嵌入式情景,这个设备就是一整块NAND FLASH,看起来有点类似图1.1所示的情况,在挂载根文件系统时会调用rootfs_mount读取flash上的超级块信息,并依据该信息构建VFS层的超级块信息。超级块信息获取是通过MTD层接口实现的,实际上底层是通过CPU 的NAND FLASH控制器来实现对NAND FLASH读写的。
图1.2 只有一个根节点的NAND设备
在《 Linux系统启动那些事—基于Linux 3.10内核》和《虚拟文件系统 (VFS)-基于linux3.10》分别对启动加载流程和文件系统的挂载有过叙述,这里一些切入点源于这两篇文章。
首先是start_kernel()函数,该函数在启动那些事中有过说明,该函数调用vfs_caches_init()函数,接下来调用inode_init()初始化索引节点,然后调用mnt_init()函数挂载根文件系统,mount_init()函数调用init_rootfs函数。
图1.3 根文件系统挂载函数调用流程
调用init_rootfs函数并未传递参数,所以分析起来容易很多。bdi是backing_dev_info的缩写,bdi_init用于初始化后备存储器的一些字段,这些字段包括回写链表、回写锁等,关系到读写策略,和挂载关系并不大。register_filesystem在《虚拟文件系统 (VFS)-基于linux3.10》明确指出一个文件系统想要使用必须先加载该文件系统,表示该文件系统的参数rootfs_fs_type包括了该文件文件系统的mount方法。
<fs/ramfs/inode.c>
277 int __init init_rootfs(void)
278 {
279 int err;
280 err = bdi_init(&ramfs_backing_dev_info);
281 if (err)
282 return err;
283
284 err = register_filesystem(&rootfs_fs_type);
285 if (err)
286 bdi_destroy(&ramfs_backing_dev_info);
287
288 return err;
289 }
对应这里的根文件系统的结构定义如下:
<fs/ramfs/inode.c>
static struct file_system_type rootfs_fs_type = {
.name = "rootfs",
.mount = rootfs_mount,
.kill_sb = kill_litter_super,
};
init_mount_tree()也没有需要传递的参数,get_fs_type函数在VFS那篇文章提过,这里就是获得参数“rootfs”对应的文件系统类型,如果发现当前文件系统并未注册该文件系统则会尝试以module的方式挂载该文件系统。一种文件系统只能注册一次,但是多个设备可以挂载为同一种文件系统类型的设备。
<fs/namespace.c>
2686 static void __init init_mount_tree(void)
2687 {
2688 struct vfsmount *mnt;
2689 struct mnt_namespace *ns;
2690 struct path root;
2691 struct file_system_type *type;
2692
2693 type = get_fs_type("rootfs");
2694 if (!type)
2695 panic("Can't find rootfs type");
2696 mnt = vfs_kern_mount(type, 0, "rootfs", NULL);
2697 put_filesystem(type);
2698 if (IS_ERR(mnt))
2699 panic("Can't create rootfs");
2700
2701 ns = create_mnt_ns(mnt);
2702 if (IS_ERR(ns))
2703 panic("Can't allocate initial namespace");
2704
2705 init_task.nsproxy->mnt_ns = ns;
2706 get_mnt_ns(ns);
2707
2708 root.mnt = mnt;
2709 root.dentry = mnt->mnt_root;
2710
2711 set_fs_pwd(current->fs, &root);
2712 set_fs_root(current->fs, &root);
2713 }
2694行判断是否找到了该文件系统,因为后面mount根文件系统时是需要其mount方法的,而mount方法存在于文件系统类型中,所以2693行必须要先查找一下,如果没找到则2695行就显示panic,这是内核非常严重的错误,这种错误预示着设备将宕机。2696行是实际意义上的挂载。
struct vfsmount *
vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
{
struct mount *mnt;
struct dentry *root;
if (!type)
return ERR_PTR(-ENODEV);
/*分配并初始化一个mount结构体,该结构体存储了若干和mount相关的信息,这些信息包括,挂载点(目录项)、父挂载点、若干挂载链表等*/
mnt = alloc_vfsmnt(name);
if (!mnt)
return ERR_PTR(-ENOMEM);
if (flags & MS_KERNMOUNT)
mnt->mnt.mnt_flags = MNT_INTERNAL;
root = mount_fs(type, flags, name, data); // 真正的挂载函数,如果挂载成功,则会返回目录项
if (IS_ERR(root)) {
free_vfsmnt(mnt);
return ERR_CAST(root);
}
mnt->mnt.mnt_root = root; //挂载点目录项
printk(KERN_EMERG "mnt->mnt.mnt_root:%s", root->d_iname);
mnt->mnt.mnt_sb = root->d_sb;//挂载点超级块
mnt->mnt_mountpoint = mnt->mnt.mnt_root;
mnt->mnt_parent = mnt;//父目录信息
br_write_lock(&vfsmount_lock);
//将新挂载的根文件系统添加到超级块的挂载链表上
list_add_tail(&mnt->mnt_instance, &root->d_sb->s_mounts);
br_write_unlock(&vfsmount_lock);
return &mnt->mnt;
}
mount_fs的工作就是完成根文件系统的挂载,实际上就是读取FLASH上超级块并填充VFS层自己的超级块。
<fs/super.c>
1086 struct dentry *
1087 mount_fs(struct file_system_type *type, int flags, const char *name, void *data)
1088 {
1089 struct dentry *root;
1090 struct super_block *sb;
1091 char *secdata = NULL;
1092 int error = -ENOMEM;
…
1103 //这里就是调用根文件系统的mount方法。
1104 root = type->mount(type, flags, name, data);
1105 if (IS_ERR(root)) {
1106 error = PTR_ERR(root);
1107 goto out_free_secdata;
1108 }
1109 sb = root->d_sb;
…
1137 return ERR_PTR(error);
1138 }
rootfs_mount 函数实际上就是对mount_nodev的封装,很容易理解这里命名方法,因为该文件系统是没有实际的物理设备对应的,其实际上只存在于内存中。最后一个参数ramfs_fill_super指定了超级块的填充方法。
static struct dentry *rootfs_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
return mount_nodev(fs_type, flags|MS_NOUSER, data, ramfs_fill_super);
}
mount_nodev首先调用sget获得超级块,然后调用指针函数参数fill_super对其进行填充。
struct dentry *mount_nodev(struct file_system_type *fs_type,
int flags, void *data,
int (*fill_super)(struct super_block *, void *, int))
{
int error;
struct super_block *s = sget(fs_type, NULL, set_anon_super, flags, NULL);
if (IS_ERR(s))
return ERR_CAST(s);
error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);
if (error) {
deactivate_locked_super(s);
return ERR_PTR(error);
}
s->s_flags |= MS_ACTIVE;
return dget(s->s_root);
}