实际根文件系统的挂载是在prepare_namespace()中实现的。
init/do_mounts.c
void __init prepare_namespace(void)
{
...
mount_root();
out:
...
sys_mount(".", "/", NULL, MS_MOVE, NULL);
sys_chroot(".");
...
}
首先调用mount_root()将实际的根文件系统挂载在rootfs文件系统的"/root"目录下,并将当前目录切换
到实际根文件系统的根目录下;接着调用sys_mount(".", "/", NULL, MS_MOVE, NULL)将实际文件系统
的挂载点移到(MS_MOVE)rootfs的"/"根目录下。最后将实际文件系统的根目录作为初始进程的根目录。
下面分析下挂载的流程,里面涉及到的MTD、jffs2知识,只简单提下,以后再具体分析。
void __init mount_root(void)
{
...
/*创建设备文件*/
create_dev("/dev/root", ROOT_DEV);
mount_block_root("/dev/root", root_mountflags);
}
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
/*从命令行获取"rootfstype=根文件系统的类型,本例是jffs2*/
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;
}
...
out:
putname(fs_names);
}
static int __init do_mount_root(char *name, char *fs, int flags, void *data)
{
/*将"/dev/root"设备挂载到"/root"目录下*/
int err = sys_mount(name, "/root", fs, flags, data);
if (err)
return err;
/*将当前目录切换到"/root"目录下,因为"/root"挂载有实际的根文件系统,
* 所以当前目录被切换到实际的根文件系统的根目录下
*/
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;
}
SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
char __user *, type, unsigned long, flags, void __user *, data)
{
int ret;
char *kernel_type;
char *kernel_dir;
char *kernel_dev;
unsigned long data_page;
/*获取根文件系统类型*/
ret = copy_mount_string(type, &kernel_type);
if (ret < 0)
goto out_type;
/*获取挂载点路径名*/
kernel_dir = getname(dir_name);
if (IS_ERR(kernel_dir)) {
ret = PTR_ERR(kernel_dir);
goto out_dir;
}
/*获取要挂载的设备名*/
ret = copy_mount_string(dev_name, &kernel_dev);
if (ret < 0)
goto out_dev;
/*获取附件数据*/
ret = copy_mount_options(data, &data_page);
if (ret < 0)
goto out_data;
/*执行挂载*/
ret = do_mount(kernel_dev, kernel_dir, kernel_type, flags,
(void *) data_page);
free_page(data_page);
out_data:
kfree(kernel_dev);
out_dev:
putname(kernel_dir);
out_dir:
kfree(kernel_type);
out_type:
return ret;
}
long do_mount(char *dev_name, char *dir_name, char *type_page,
unsigned long flags, void *data_page)
{
struct path path;
int retval = 0;
int mnt_flags = 0;
/* Discard magic */
if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
flags &= ~MS_MGC_MSK;
/* Basic sanity checks */
if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE))
return -EINVAL;
if (data_page)
((char *)data_page)[PAGE_SIZE - 1] = 0;
/* Default to relatime unless overriden */
if (!(flags & MS_NOATIME))
mnt_flags |= MNT_RELATIME;
/* Separate the per-mountpoint flags */
if (flags & MS_NOSUID)
mnt_flags |= MNT_NOSUID;
if (flags & MS_NODEV)
mnt_flags |= MNT_NODEV;
if (flags & MS_NOEXEC)
mnt_flags |= MNT_NOEXEC;
if (flags & MS_NOATIME)
mnt_flags |= MNT_NOATIME;
if (flags & MS_NODIRATIME)
mnt_flags |= MNT_NODIRATIME;
if (flags & MS_STRICTATIME)
mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME);
if (flags & MS_RDONLY)
mnt_flags |= MNT_READONLY;
flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE |
MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT |
MS_STRICTATIME);
/* ... and get the mountpoint */
/*获取挂载点的信息即mnt结构和dentry结构*/
retval = kern_path(dir_name, LOOKUP_FOLLOW, &path);
if (retval)
return retval;
retval = security_sb_mount(dev_name, &path,
type_page, flags, data_page);
if (retval)
goto dput_out;
if (flags & MS_REMOUNT)
retval = do_remount(&path, flags & ~MS_REMOUNT, mnt_flags,
data_page);
else if (flags & MS_BIND)
retval = do_loopback(&path, dev_name, flags & MS_REC);
else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
retval = do_change_type(&path, flags);
else if (flags & MS_MOVE)
retval = do_move_mount(&path, dev_name);
else
/*执行挂载*/
retval = do_new_mount(&path, type_page, flags, mnt_flags,
dev_name, data_page);
dput_out:
path_put(&path);
return retval;
}
static int do_new_mount(struct path *path, char *type, int flags,
int mnt_flags, char *name, void *data)
{
struct vfsmount *mnt;
if (!type)
return -EINVAL;
/* we need capabilities... */
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
lock_kernel();
/*创建新文件系统的vfsmnt,superblock,dentry,inode等结构*/
mnt = do_kern_mount(type, flags, name, data);
unlock_kernel();
if (IS_ERR(mnt))
return PTR_ERR(mnt);
/*将上述创建的结构,添加到"/root"目录结构中,从而实现挂载*/
return do_add_mount(mnt, path, mnt_flags, NULL);
}
struct vfsmount *
do_kern_mount(const char *fstype, int flags, const char *name, void *data)
{
/*根据文件系统类型jffs2,查找对应的file_system_type结构
*jffs2文件系统之前在fs/jffs2/super.c中的init_jffs2_fs()函数
*完成注册,所以此时可以找到。
*/
struct file_system_type *type = get_fs_type(fstype);
struct vfsmount *mnt;
if (!type)
return ERR_PTR(-ENODEV);
mnt = vfs_kern_mount(type, flags, name, data);
if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) &&
!mnt->mnt_sb->s_subtype)
mnt = fs_set_subtype(mnt, fstype);
put_filesystem(type);
return mnt;
}
这个函数之前介绍过,首先分配一个vfsmount结构,然后调用具体文件系统的get_sb()函数。
get_sb()函数会创建super block,根目录dentry,根索引节点inode等结构。
此处需要注意的是,之前的get_sb()函数,是ram类型的,不会去操作flash等存储IC,而jffs2
的get_sb()函数,会从flash中读取实际文件系统的内容,用来初始化super block,根目录
dentry,根索引节点inode等结构。所以挂载完成后,flash里的相关内容(不是全部)就会在内存
中,这也是挂载的实质用意。
struct vfsmount *
vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
{
struct vfsmount *mnt;
char *secdata = NULL;
int error;
if (!type)
return ERR_PTR(-ENODEV);
error = -ENOMEM;
mnt = alloc_vfsmnt(name);
if (!mnt)
goto out;
printk(KERN_WARNING "get_sb0\n");
if (data && !(type->fs_flags & FS_BINARY_MOUNTDATA)) {
secdata = alloc_secdata();
if (!secdata)
goto out_mnt;
error = security_sb_copy_data(data, secdata);
if (error)
goto out_free_secdata;
}
printk(KERN_WARNING "get_sb1\n");
error = type->get_sb(type, flags, name, data, mnt);
printk(KERN_WARNING "get_sb2\n");
if (error < 0)
goto out_free_secdata;
BUG_ON(!mnt->mnt_sb);
error = security_sb_kern_mount(mnt->mnt_sb, flags, secdata);
if (error)
goto out_sb;
/*
* filesystems should never set s_maxbytes larger than MAX_LFS_FILESIZE
* but s_maxbytes was an unsigned long long for many releases. Throw
* this warning for a little while to try and catch filesystems that
* violate this rule. This warning should be either removed or
* converted to a BUG() in 2.6.34.
*/
WARN((mnt->mnt_sb->s_maxbytes < 0), "%s set sb->s_maxbytes to "
"negative value (%lld)\n", type->name, mnt->mnt_sb->s_maxbytes);
mnt->mnt_mountpoint = mnt->mnt_root;
mnt->mnt_parent = mnt;
up_write(&mnt->mnt_sb->s_umount);
free_secdata(secdata);
return mnt;
out_sb:
dput(mnt->mnt_root);
deactivate_locked_super(mnt->mnt_sb);
out_free_secdata:
free_secdata(secdata);
out_mnt:
free_vfsmnt(mnt);
out:
return ERR_PTR(error);
}
具体的get_sb()函数涉及jffs2的知识,先省略。
程序运行到此处,可以认为代表实际根文件系统的vfsmount,super block,根目录dentry,根索引节点inode
等结构都已初始化完毕。现在要做的就是将这些结构关联到挂载点上。关联过程是由do_add_mount()函数实现。
int do_add_mount(struct vfsmount *newmnt, struct path *path,
int mnt_flags, struct list_head *fslist)
{
int err;
down_write(&namespace_sem);
/* Something was mounted here while we slept */
/*判断挂载点出是否已经挂载有其他的文件系统,是则,覆盖之。此处当然没有*/
while (d_mountpoint(path->dentry) &&
follow_down(path))
;
err = -EINVAL;
if (!(mnt_flags & MNT_SHRINKABLE) && !check_mnt(path->mnt))
goto unlock;
/* Refuse the same filesystem on the same mount point */
err = -EBUSY;
if (path->mnt->mnt_sb == newmnt->mnt_sb &&
path->mnt->mnt_root == path->dentry)
goto unlock;
err = -EINVAL;
if (S_ISLNK(newmnt->mnt_root->d_inode->i_mode))
goto unlock;
newmnt->mnt_flags = mnt_flags;
if ((err = graft_tree(newmnt, path)))
goto unlock;
if (fslist) /* add to the specified expiration list */
list_add_tail(&newmnt->mnt_expire, fslist);
up_write(&namespace_sem);
return 0;
unlock:
up_write(&namespace_sem);
mntput(newmnt);
return err;
}
实际操作由graft_tree()中attach_recursive_mnt()的完成
static int graft_tree(struct vfsmount *mnt, struct path *path)
{
int err;
if (mnt->mnt_sb->s_flags & MS_NOUSER)
return -EINVAL;
if (S_ISDIR(path->dentry->d_inode->i_mode) !=
S_ISDIR(mnt->mnt_root->d_inode->i_mode))
return -ENOTDIR;
err = -ENOENT;
mutex_lock(&path->dentry->d_inode->i_mutex);
if (IS_DEADDIR(path->dentry->d_inode))
goto out_unlock;
err = security_sb_check_sb(mnt, path);
if (err)
goto out_unlock;
err = -ENOENT;
if (!d_unlinked(path->dentry))
err = attach_recursive_mnt(mnt, path, NULL);
out_unlock:
mutex_unlock(&path->dentry->d_inode->i_mutex);
if (!err)
security_sb_post_addmount(mnt, path);
return err;
}
static int attach_recursive_mnt(struct vfsmount *source_mnt,
struct path *path, struct path *parent_path)
{
LIST_HEAD(tree_list);
struct vfsmount *dest_mnt = path->mnt;
struct dentry *dest_dentry = path->dentry;
struct vfsmount *child, *p;
int err;
if (IS_MNT_SHARED(dest_mnt)) {
err = invent_group_ids(source_mnt, true);
if (err)
goto out;
}
/*此例中propagation_next为NULL,所以下面的函数时空函数。
*该函数不知道做什么作用,先不管了
*/
err = propagate_mnt(dest_mnt, dest_dentry, source_mnt, &tree_list);
if (err)
goto out_cleanup_ids;
if (IS_MNT_SHARED(dest_mnt)) {
for (p = source_mnt; p; p = next_mnt(p, source_mnt))
set_mnt_shared(p);
}
spin_lock(&vfsmount_lock);
/*parent_path为NULL*/
if (parent_path) {
detach_mnt(source_mnt, parent_path);
attach_mnt(source_mnt, path);
touch_mnt_namespace(parent_path->mnt->mnt_ns);
} else {
mnt_set_mountpoint(dest_mnt, dest_dentry, source_mnt);
commit_tree(source_mnt);
}
/*tree_list为NULL*/
list_for_each_entry_safe(child, p, &tree_list, mnt_hash) {
list_del_init(&child->mnt_hash);
commit_tree(child);
}
spin_unlock(&vfsmount_lock);
return 0;
out_cleanup_ids:
if (IS_MNT_SHARED(dest_mnt))
cleanup_group_ids(source_mnt, NULL);
out:
return err;
}
就剩下mnt_set_mountpoint(dest_mnt, dest_dentry, source_mnt);和commit_tree(source_mnt);
void mnt_set_mountpoint(struct vfsmount *mnt, struct dentry *dentry,
struct vfsmount *child_mnt)
{
/*此处就是建立新文件系统和挂载点之间关联的地方
*主要设置mnt_parent、mnt_mountpoint以及dentry->d_mounted
*dentry->d_mounted就表示dentry目录是否挂载有文件系统
*/
child_mnt->mnt_parent = mntget(mnt);
/**/
child_mnt->mnt_mountpoint = dget(dentry);
dentry->d_mounted++;
}
static void commit_tree(struct vfsmount *mnt)
{
struct vfsmount *parent = mnt->mnt_parent;
struct vfsmount *m;
LIST_HEAD(head);
struct mnt_namespace *n = parent->mnt_ns;
BUG_ON(parent == mnt);
list_add_tail(&head, &mnt->mnt_list);
list_for_each_entry(m, &head, mnt_list)
{
/*设置新文件系统的命名空间*/
m->mnt_ns = n;
}
/*合并链表*/
list_splice(&head, n->list.prev);
/*将mnt->mnt_hash添加到mount_hashtable哈希表中*/
list_add_tail(&mnt->mnt_hash, mount_hashtable +
hash(parent, mnt->mnt_mountpoint));
/*将mnt_child添加到mnt_mounts*/
list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
touch_mnt_namespace(n);
}
挂载后的主要数据结构图如下:
另外,挂载后,会调用sys_mount(".", "/", NULL, MS_MOVE, NULL)函数,将挂载点移到(MS_MOVE)"/"目录下.
因为设置了MS_MOVE,所以sys_mount()会调用do_move_mount()函数。do_move_mount()仍会调用attach_recursive_mnt();
只不过参数parent_path不再会NULL,所以会执行以下语句
if (parent_path) {
detach_mnt(source_mnt, parent_path);
attach_mnt(source_mnt, path);
touch_mnt_namespace(parent_path->mnt->mnt_ns);
}
static void detach_mnt(struct vfsmount *mnt, struct path *old_path)
{
/*保存原始挂载点"/root"的信息*/
old_path->dentry = mnt->mnt_mountpoint;
old_path->mnt = mnt->mnt_parent;
/*重新初始化新文件系统的挂载信息*/
mnt->mnt_parent = mnt;
mnt->mnt_mountpoint = mnt->mnt_root;
list_del_init(&mnt->mnt_child);
list_del_init(&mnt->mnt_hash);
/*原始挂载点的d_mounted减一,表示去掉了一个文件系统*/
old_path->dentry->d_mounted--;
}
static void attach_mnt(struct vfsmount *mnt, struct path *path)
{
/*设置新的挂载点"/",上面分析过*/
mnt_set_mountpoint(path->mnt, path->dentry, mnt);
/*将mnt->mnt_hash添加到mount_hashtable哈希表中*/
list_add_tail(&mnt->mnt_hash, mount_hashtable +
hash(path->mnt, path->dentry));
/*将mnt_child添加到mnt_mounts*/
list_add_tail(&mnt->mnt_child, &path->mnt->mnt_mounts);
}
还有一点,因为先后执行了sys_chdir("/root")和sys_chroot("."),所以当前目录和根目录都变成了实际文件系统的根目录。