1. VFS层安装普通文件系统分析
之所以称之为普通安装是因为接下来讨论的是将一个文件系统安装在一个已安装文件系统之上的情形。使用 mount() 系统调用来安装一个普通文件系统,将会调用它的服务例程 sys_mount() 函数。
1.1 sys_mount()
asmlinkage long sys_mount(char __user * dev_name, char __user * dir_name,
char __user * type, unsigned long flags, void __user * data)
copy_mount_options(type, &type_page);
getname(dir_name);
copy_mount_options(dev_name, &dev_page);
copy_mount_options(data, &data_page);
do_mount((char *)dev_page, dir_page, (char *)type_page, flags, (void *)data_page);
struct nameidata nd;
int mnt_flags = 0;
path_lookup(dir_name, LOOKUP_FOLLOW, &nd);
if (flags & MS_REMOUNT)
else
do_new_mount(&nd, type_page, flags, mnt_flags, dev_name, data_page);
struct vfsmount *mnt;
mnt = do_kern_mount(type, flags, name, data);
do_add_mount(mnt, nd, mnt_flags, NULL);
path_release(&nd);
- 调用path_lookup()函数搜索安装点路径,将最后一个分量的信息保存在 nameidata 类型的局部变量 nd 中,nd 中包括安装点的目录项和已安装文件描述符等重要信息。
- 根据安装标志可以进行不同的操作,例如更改已安装文件系统的安装点等。我们讨论的是用户要安装一个特殊文件系统或存放在磁盘分区中的普通文件系统的情况所以调用 do_new_mount() 函数。
1.2 do_kern_mount()
struct vfsmount *
do_kern_mount(const char *fstype, int flags, const char *name, void *data)
struct file_system_type *type = get_fs_type(fstype);
struct vfsmount *mnt;
mnt = vfs_kern_mount(type, flags, name, data);
struct vfsmount *mnt;
mnt = alloc_vfsmnt(name);
struct vfsmount *mnt = kmem_cache_zalloc(mnt_cache, GFP_KERNEL);
mnt->mnt_devname = newname;
type->get_sb(type, flags, name, data, mnt);
mnt->mnt_mountpoint = mnt->mnt_root;
mnt->mnt_parent = mnt;
- 在注册的文件系统类型链表中搜索将要挂载的文件系统的名字,结果保存在局部变量中。后续需要调用文件系统对象中的 get_sb() 方法获取并填充超级块对象。
- 这里进行了初始化,后面在 mnt_set_mountpoint() 中会重新指向正确的对象。
1.3 do_add_mount()
int do_add_mount(struct vfsmount *newmnt, struct nameidata *nd,
int mnt_flags, struct list_head *fslist)
while (d_mountpoint(nd->dentry) && follow_down(&nd->mnt, &nd->dentry));
if (!check_mnt(nd->mnt));
return mnt->mnt_ns == current->nsproxy->mnt_ns;
if (nd->mnt->mnt_sb == newmnt->mnt_sb &&
nd->mnt->mnt_root == nd->dentry);
if (S_ISLNK(newmnt->mnt_root->d_inode->i_mode));
newmnt->mnt_flags = mnt_flags;
graft_tree(newmnt, nd);
attach_recursive_mnt(mnt, nd, NULL);
mnt_set_mountpoint(dest_mnt, dest_dentry, source_mnt);
child_mnt->mnt_parent = mntget(mnt);
child_mnt->mnt_mountpoint = dget(dentry);
dentry->d_mounted++;
commit_tree(source_mnt);
struct vfsmount *parent = mnt->mnt_parent;
struct vfsmount *m;
LIST_HEAD(head);
struct mnt_namespace *n = parent->mnt_ns;
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);
list_add_tail(&mnt->mnt_hash, mount_hashtable +
hash(parent, mnt->mnt_mountpoint));
list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
-
进程可能会发生休眠,这期间另一个进程可能在相同的安装点上安装文件系统设置改变了根文件系统。这样前面获取到的nd的信息有可能已经变化了。
d_mountpoint(nd->dentry)
return dentry->d_mounted;
follow_down(&nd->mnt, &nd->dentry)
struct vfsmount *mounted;
mounted = lookup_mnt(*mnt, *dentry);
__lookup_mnt(mnt, dentry, 1)
struct list_head *head = mount_hashtable + hash(mnt, dentry);
for (;;) {
p = list_entry(tmp, struct vfsmount, mnt_hash);
if (p->mnt_parent == mnt && p->mnt_mountpoint == dentry) {
found = p;
break;
}
}
if (mounted) {
*mnt = mounted;
*dentry = dget(mounted->mnt_root);
return 1;
}
return 0;
-
同一个文件系统安装到同一个安装点没有意义。
2. 超级块的分配
在安装过程中调用 do_kern_mount() 函数时,通过函数指针调用了 get_sb() 方法,该方法用于分配和填充超级块对象,并将创建好的超级块添加到相应链表中。接下来已Ext2文件系统为例进行说明。
2.1 文件系统类型注册
static struct file_system_type ext2_fs_type = {
.owner = THIS_MODULE,
.name = "ext2",
.get_sb = ext2_get_sb,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV,
};
static int __init init_ext2_fs(void)
register_filesystem(&ext2_fs_type);
static int ext2_get_sb(struct file_system_type *fs_type, int flags,
const char *dev_name, void *data, struct vfsmount *mnt)
get_sb_bdev(fs_type, flags, dev_name, data, ext2_fill_super, mnt);
2.2 get_sb_bdev()
该函数分配并初始化一个新的适合于磁盘文件系统的超级块。
int get_sb_bdev(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data,
int (*fill_super)(struct super_block *, void *, int),
struct vfsmount *mnt)
struct block_device *bdev;
struct super_block *s;
bdev = open_bdev_excl(dev_name, flags, fs_type);
s = sget(fs_type, test_bdev_super, set_bdev_super, bdev);
if (s->s_root) {
} else {
s->s_flags = flags;
strlcpy(s->s_id, bdevname(bdev, b), sizeof(s->s_id));
b_set_blocksize(s, block_size(bdev));
fill_super(s, data, flags & MS_SILENT ? 1 : 0);
}
simple_set_mnt(mnt, s);
mnt->mnt_sb = sb;
mnt->mnt_root = dget(sb->s_root);
- 如果是一个新分配的超级块对象,此时还没有分配对应的根目录项,所以 s_root 还没有指向。如果是从文件系统对象链表中搜索到的超级块,则添加链表前 s_root 已经有所指向。通过此方法可以判断 sget() 函数调用获取的超级块对象是已有的还是新分配的。
2.2.1 sget()
搜索文件系统链表 fs_supers,如果找到一个与块设备相关的超级块,则返回它的地址。否则,分配并初始化一个新的超级块对象,把它插入到文件系统链表和超级块全局链表中,并返回地址。
struct super_block *sget(struct file_system_type *type,
int (*test)(struct super_block *,void *),
int (*set)(struct super_block *,void *),
void *data)
struct super_block *s = NULL;
struct list_head *p;
retry:
if (test) list_for_each(p, &type->fs_supers) {
struct super_block *old;
old = list_entry(p, struct super_block, s_instances);
if (!test(old, data))
continue;
if (!grab_super(old))
goto retry;
if (s)
destroy_super(s);
return old;
}
if (!s) {
s = alloc_super(type);
struct super_block *s = kzalloc(sizeof(struct super_block), GFP_USER);
s->s_count = S_BIAS;
atomic_set(&s->s_active, 1);
goto retry;
}
set(s, data);
s->s_type = type;
strlcpy(s->s_id, type->name, sizeof(s->s_id));
list_add_tail(&s->s_list, &super_blocks);
list_add(&s->s_instances, &type->fs_supers);
- test 实际指向 test_bdev_super() 函数,比较前面调用 open_bdev_excl() 函数获取的块设备驱动与链表中超级块的 s_bdev 对象是否相同。
static int test_bdev_super(struct super_block *s, void *data)
return (void *)s->s_bdev == data;
- 当从超级块链表中获取到满足上面条件的超级块需要进一步判断该超级块是否活跃有效,调用 grab_super() 函数。
static int grab_super(struct super_block *s)
s->s_count++;
if (s->s_root) {
if (s->s_count > S_BIAS) {
atomic_inc(&s->s_active);
s->s_count--;
return 1;
}
}
put_super(s);
return 0;
- set() 实际指向 set_bdev_super() 函数,对超级块对象进行设置。将前面调用 open_bdev_excl() 函数获取的块设备驱动,以及设备标志符设置到 s_bdev 和 s_dev 字段中。
static int set_bdev_super(struct super_block *s, void *data)
s->s_bdev = data;
s->s_dev = s->s_bdev->bd_dev;
2.2.2 fill_super填充磁盘超级块信息
因为超级块对象有实际的磁盘超级块与其对应,所以需要读取磁盘的信息来填充超级块的一些字段,fill_super 实际指向 ext2_fill_super() 函数。
static int ext2_fill_super(struct super_block *sb, void *data, int silent)
struct ext2_sb_info * sbi;
struct inode *root;
sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
sb->s_fs_info = sbi;
sb->s_magic = le16_to_cpu(es->s_magic);
sb->s_op = &ext2_sops;
sb->s_export_op = &ext2_export_ops;
sb->s_xattr = ext2_xattr_handlers;
root = iget(sb, EXT2_ROOT_INO);
sb->s_root = d_alloc_root(root);
2.2.3 根索引节点
根索引节点号通常为固定值,EXT2文件系统根目录的索引节点号是2(EXT2_ROOT_INO值为2)。
static inline struct inode *iget(struct super_block *sb, unsigned long ino)
struct inode *inode = iget_locked(sb, ino);
struct hlist_head *head = inode_hashtable + hash(sb, ino);
struct inode *inode;
inode = ifind_fast(sb, head, ino);
struct inode *inode;
inode = find_inode_fast(sb, head, ino);
hlist_for_each (node, head) {
inode = hlist_entry(node, struct inode, i_hash);
if (inode->i_ino != ino)
continue;
if (inode->i_sb != sb)
continue;
}
if (inode)
return inode;
get_new_inode_fast(sb, head, ino);
struct inode * inode;
inode = alloc_inode(sb);
inode->i_sb = sb;
inode->i_ino = ino;
list_add(&inode->i_list, &inode_in_use);
list_add(&inode->i_sb_list, &sb->s_inodes);
hlist_add_head(&inode->i_hash, head);
inode->i_state = I_LOCK|I_NEW;
if (inode && (inode->i_state & I_NEW))
sb->s_op->read_inode(inode);
- inode对象和磁盘的索引节点相对应,调用超级块的 read_inode() 方法读取磁盘索引节点,该方法对应 ext2_read_inode() 函数。
void ext2_read_inode (struct inode * inode)
struct ext2_inode * raw_inode = ext2_get_inode(inode->i_sb, ino, &bh);
inode->i_mode = le16_to_cpu(raw_inode->i_mode);
if (S_ISREG(inode->i_mode)) {
} else if (S_ISDIR(inode->i_mode)) {
inode->i_op = &ext2_dir_inode_operations;
inode->i_fop = &ext2_dir_operations;
} else if (S_ISLNK(inode->i_mode)) {
} else {
}
2.2.4 根目录项
struct dentry * d_alloc_root(struct inode * root_inode)
struct dentry *res = NULL;
static const struct qstr name = { .name = "/", .len = 1 };
res = d_alloc(NULL, &name);
struct dentry *dentry;
dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL);
dentry->d_name.name = dname;
memcpy(dname, name->name, name->len);
dentry->d_flags = DCACHE_UNHASHED;
res->d_sb = root_inode->i_sb;
res->d_parent = res;
d_instantiate(res, root_inode);
list_add(&entry->d_alias, &inode->i_dentry);
entry->d_inode = inode;
- 根目录项的名字也是“/”,这个名字不重要,只有路径查找中使用绝对路径时的首个分量会显示的使用“/”,访问其他目录挂载文件系统的情况要使用安装点的名字而不是“/”。
3. 总结:
- 如何判断一个目录项是否为安装点:
- 判断该目录项对象的 d_mounted 字段是否大于0。
- 搜索已安装文件系统描述符哈希表,遍历链表中的元素,判断元素的 mnt_parent 字段是否等于该目录项对象对应的已安装文件系统描述符;并且 mnt_mountpoint 是否等于该目录项。(参考 follow_down()函数的实现)。简单表示为:
p->mnt_parent == nd->mnt && p->mnt_mountpoint == nd->dentry - 如何获取文件系统类型对象:
调用 get_fs_type() 函数传入文件系统类型的名字,该函数会遍历 file_systems 链表,找到名字相同的对象。
|