下面是 ext3 文件系统的超级块(super block)函数集结构
static struct super_operations ext3_sops = {
//
// 为了更清晰,在这个结构中只列出我们感兴趣的字段
//
......
//
// 注意:这里的 ext3_read_inode() 是不是很眼熟
//
.read_inode = ext3_read_inode,
......
};
终于走到了最终的读取函数!这个函数非常简单,在判断一些有效性后,直接调用超级块(super block)函数集中的 read_inode 方法,也就是我们前面介绍的 ext3_sops函数集中的 ext3_read_inode() 函数。
static inline struct inode *iget(struct super_block *sb, unsigned long ino)
{
struct inode *inode = iget_locked(sb, ino);
if (inode && (inode->i_state & I_NEW)) {
//
// 这里调用的就是 ext3_read_inode() 函数
//
sb->s_op->read_inode(inode);
unlock_new_inode(inode);
}
return inode;
}
到这里我们可以解释 ext3_read_inode() 函数是何时调用的了,可以说是open_namei() 函数在路径转换时间接的调用了 iget() 函数,而 iget() 函数则是调用了已经注册好的超级块(super block)函数集 ext3_sops 中的 ext3_read_inode() 函数来获取相应的 inode。其实这也就可以解释为什么在 struct inode->i_fop 中(也就是 ext3_file_operations 函数集中) open 操作函数 generic_file_open() 是个空操作。因为其对应的 inode 已经在 open_namei()->iget() 中得到了,得到了一个 inode 其实在实际文件系统中就是一个打开操作,得到了 inode 当然就可以对它进行读/写操作了。只所以提供了一个 generic_file_open() 应该是占位用的,占位的目的应该是为了可以使用用户提供的操作方法。也就是说,如果你自己写了一个 open 操作并赋值给 struct inode->i_fop->open 的话,系统会调用你所提供的这个 open 操作。我们在上面分析 __dentry_open() 函数时已经指出了这个调用点。以上的疑问都得到了解答,但这里又再次引出了一个疑问,那就是这个已经注册好了的 超级块(super block)函数集 ext3_sops是什么时候注册的?要解答这个疑问我们只能从头,也就是 mount 文件系统时进行分析。
在分析 mount 前我们首先来了解下如下结构,这个结构是在注册新的文件系统时被作为参数传递的,注册文件系统的函数为 register_filesyste()。
struct file_system_type {
//
// 文件系统名称,如:ext3
//
const char *name;
int fs_flags;
//
// 实际文件系统的超级块(super block)函数。在 mount 时通
// 过它来得到超级块的信息,包含 inode 等。
//
int (*get_sb) (struct file_system_type *, int,
const char *, void *, struct vfsmount *);
void (*kill_sb) (struct super_block *);
//
// 当前模块
//
struct module *owner;
//
// 指向下一个文件系统地址
//
struct file_system_type * next;
struct list_head fs_supers;
struct lock_class_key s_lock_key;
struct lock_class_key s_umount_key;
};
我们再来看下 ext3 文件系统是如何填充这个结构的。
static struct file_system_type ext3_fs_type = {
.owner = THIS_MODULE,
.name = "ext3",
//
// 注意这里的回调函数指向了 ext3_get_sb()
//
.get_sb = ext3_get_sb,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV,
};
最终使用 register_filesystem( &ext3_fs_type ); 完成文件系统的注册。这里仅是注册了文件系统,我们知道要使用一个文件系统首先要 mount 才可使用。我们清楚了以上结构后,接着来看 vfs_kern_mount() 函数,这个函数是内核最终实现 mount 的函数,这个函数的第一个参数即是上面提到的 file_system_type 结构,在 ext3 文件系统下传递的是 ext3_fs_type。函数中调用的 type->get_sb 即触发了 ext3_get_sb() 函数。
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;
//
// 根据名称分配一个新的 vfsmount 挂接点。
//
mnt = alloc_vfsmnt(name);
if (!mnt)
goto out;
if (data) {
secdata = alloc_secdata();
if (!secdata)
goto out_mnt;
error = security_sb_copy_data(type, data, secdata);
if (error)
goto out_free_secdata;
}
//
// 注意:这里调用了已注册文件系统的超级块(super block)函数
// 对于 ext3 文件系统来说,就是调用了 ext3_get_sb,可参考
// 以上对 file_system_type 的说明。
//
error = type->get_sb(type, flags, name, data, mnt);
if (error < 0)
goto out_free_secdata;
error = security_sb_kern_mount(mnt->mnt_sb, secdata);
if (error)
goto out_sb;
//
// 这里的挂接点是一个 dentry 结构
//
mnt->mnt_mountpoint = mnt->mnt_root;
//
// 把新的 vfsmount 结构赋给自身的 parent 这样可以
// 通过 parent 遍历出所有 mount 的文件系统
//
mnt->mnt_parent = mnt;
up_write(&mnt->mnt_sb->s_umount);
free_secdata(secdata);
return mnt;
//
// 以下流程只有出错时才会走到
//
out_sb:
dput(mnt->mnt_root);
up_write(&mnt->mnt_sb->s_umount);
deactivate_super(mnt->mnt_sb);
out_free_secdata:
free_secdata(secdata);
out_mnt:
free_vfsmnt(mnt);
out:
return ERR_PTR(error);
}
下面的 ext3_get_sb() 函数仅是个简单的封状,直接调用的 get_sb_bdev()函数,但这里要注意 get_sb_bdev() 函数不是严格按照 ext3_get_sb() 函数进行传递的,它本身多出了一个 ext3_fill_super 参数,而这个参数是以一个回调函数形式提供的。
static int ext3_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data, struct vfsmount *mnt)
{
//
// 注意:这里多了一个 ext3_fill_super() 的回调函数。
//
return get_sb_bdev(fs_type, flags, dev_name, data, ext3_fill_super, mnt);
}
了解了以上结构我们再来看 ext3_fill_super() 函数的具体实现,这个函数的第一个参数即是一个超级块(super block)结构。在此函数中将上面提到的 ext3 超级块(super block) 函数集 ext3_sops 赋给了此结构。然后调用 iget() 函数触发超级块(super block) 函数集。
static int ext3_fill_super (struct super_block *sb, void *data, int silent)
{
//
// 篇幅所限,在这个函数中我们只列出相关代码。
//
//
// 设置超级块的函数集
//
sb->s_op = &ext3_sops;
sb->s_export_op = &ext3_export_ops;
sb->s_xattr = ext3_xattr_handlers;
#ifdef CONFIG_QUOTA
sb->s_qcop = &ext3_qctl_operations;
sb->dq_op = &ext3_quota_operations;
#endif
INIT_LIST_HEAD(&sbi->s_orphan); /* unlinked but open files */
sb->s_root = NULL;
//
// 调用 iget() 函数得到相应的 inode。
//
root = iget(sb, EXT3_ROOT_INO);
//
// 根据得到的根 inode 分配超级块(super block)中的
// s_root 此字段是一个 struct dentry 结构。
//
sb->s_root = d_alloc_root(root);
//
// 如果根 dentry 无效则提示错误跳到失败处。
//
if (!sb->s_root) {
printk(KERN_ERR "EXT3-fs: get root inode failed\n");
iput(root);
goto failed_mount4;
}
//
// 如果根 inode 不是目录或者大小与块无效则提示错误
// 跳到失败处。
//
if (!S_ISDIR(root->i_mode) || !root->i_blocks || !root->i_size) {
dput(sb->s_root);
sb->s_root = NULL;
printk(KERN_ERR "EXT3-fs: corrupt root inode, run e2fsck\n");
goto failed_mount4;
}
}
至此所有流程都走到了,疑问也被一个个打破。我们在整体的梳理下流程。在内核sys_open 被调用打开一个文件或者设备驱动时,调用 filp_open()->do_filp_open()函数,在 do_filp_open() 函数中,首先利用 open_namei() 函数得到一个 structnameidata 结构,那么在这个过程中 __path_lookup_intent_open() 函数设置了struct nameidata->intent.open 相关字段,然后调用 do_path_lookup() 函数,在这个函数中设置了 struct nameidata->mnt 与 struct nameidata->dentry 相关字段后调用了 _link_path_walk() 函数开始分解路径,并依次调用 do_lookup() 函数来获得路径中个目录与最终文件的 struct inode。do_lookup() 函数先从 inode 缓存即 hlist 中查找 inode,如果没有找到则调用 real_lookup() 函数,此函数分配了一个 struct dentry 结构,然后使用上层目录的 struct inode->i_op->lookup()方法来继续查找,这样就触发了 ext3_lookup() 函数,而此函数得到 struct dentry与 inode number 后调用 iget() 函数来返回 struct inode。(这里有必要强调一点,那就是不仅目录才有 struct dentry 结构,一个文件也拥有一个 struct dentry 结构,这个从上面具体代码分析中可以看到)而 iget() 函数是使用 struct inode 超级块(super block)中的函数 ext3_read_inode() 来最终完成从磁盘读取 inode 操作,读到一个 in core 类型的 struct inode 后为了提供文件与设备读/写等操作设置了 struct inode->i_op 与 struct inode->i_fop 函数集。其实以上步骤按照提供的系统调用以及内核操作流程来理解等于是打开了一个文件或目录。这也就是为什么在 ext3_file_operations 函数集中只有读/写等操作,而打开是空操作的原因。至于为什么提供一个空操作函数,在上面分析时已经给出了,这里不在阐述。到此struct inode,struct dentry, struct nameidata 结构都已完全填充好。在 open_namei() 调用返回后将得到的 nameidata 结构作为参数调用 nameidata_to_filp() 函数,在此函数当中使用 struct dentry 作参数调用了__dentry_open() 函数,在这个函数中会动态初始化一个 struct file 结构,并使用struct inode->i_fop 函数集来填充 struct file->f_op (别忘了,我们前面的 inode结构中相关域都已经准备好了,这里直接拿来使用即可)。那么不管是文件还是设备驱动,可以看出来是走到具体文件系统这里才开始区分的。如果是目录/文件/连接则直接使用 ext3_file_xxx 或 ext3_dir_xxx 等函数集。如果操作对象是一个设备驱动的话则使用 init_special_inode 来初始化不同的设备驱动,如果是一个字符设备驱动的话则调用 chrdev_open() 函数来对应 struct file 操作集。而上面提到的超级块(super block) 函数是在注册文件系统注册时由 register_filesystem() 函数注册 ,在 mount 时由 vfs_kern_mount() 函数间接调用 ext3_fill_super() 函数时进行关联的。具体可以看上面的代码分析,这里不在详述。所有流程清晰后我们再说一下 structinode 中的几个函数集的区别与作用。我们这里仅以文件/目录为例进行解释,struct inode_operations 操作是对文件(inode)的建立/查找(打开)/删除/重命名操作,struct file_operations 操作是对已经存在的文件的读/写/刷新/列目录(readdir)/发送控制字操作。
参考:linux kernel source 2.6.19.1
/usr/fs/ext3/inode.c
/usr/fs/ext3/namei.c
/usr/fs/ext3/super.c
/usr/fs/ext3/file.c
/usr/fs/ext3/dir.c
/usr/fs/block_dev.c
/usr/fs/open.c
/usr/fs/dcache.h
/usr/fs/inode.c
/usr/fs/namei.c
/usr/fs/super.c
/inlucde/linux/mount.h
感谢 zhuzj 与我探讨。
感谢我的发小齐佳佳,并将本次劳动成果献给齐佳佳。
WSS(Whitecell Security Systems),一个非营利性民间技术组织,致力于各种系统安全技术的研究。坚持传统的hacker精神,追求技术的精纯。
WSS 主页:http://www.whitecell.org/
WSS 论坛:http://www.whitecell.org/forums/
共3页: 上一页 [1] [2] 3 |