第一次写博客,水平难免有限,请各位见谅。
最近在看linux内核mount文件系统的相关内容,由此产生了一些疑惑,即在mount一个文件系统后,原有的目录项与被挂载的文件系统的根目录dentry之间是如何联系的,vfs进行路径搜索的时候,是怎么知道该目录项被挂载了一个新的文件系统,新的文件系统又如何与挂载位置的目录项联系起来?通过程序验证后略知一二,特此记录。
本文分析基于linux内核4.4.198。
假设条件
- /tmp是一个挂载点
- /tmp上只挂载了一个文件系统
相关数据结构
// 查找dentry使用的函数
/**
* vfs_path_lookup - lookup a file path relative to a dentry-vfsmount pair
* @dentry: pointer to dentry of the base directory
* @mnt: pointer to vfs mount of the base directory
* @name: pointer to file name
* @flags: lookup flags
* @path: pointer to struct path to fill
*/
int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
const char *name, unsigned int flags,
struct path *path)
//该结构体是struct mount的一部分,一个struct mount实例代表一个挂载点
struct vfsmount {
struct dentry *mnt_root; /* root of the mounted tree */
struct super_block *mnt_sb; /* pointer to superblock */
int mnt_flags;
};
//表示路径
struct path {
struct vfsmount *mnt;
struct dentry *dentry;
};
// 目录项
struct dentry {
/* RCU lookup touched fields */
unsigned int d_flags; /* protected by d_lock */
seqcount_t d_seq; /* per dentry seqlock */
struct hlist_bl_node d_hash; /* lookup hash list */
struct dentry *d_parent; /* parent directory */
struct qstr d_name;
struct inode *d_inode; /* Where the name belongs to - NULL is
* negative */
unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */
/* Ref lookup also touches following */
struct lockref d_lockref; /* per-dentry lock and refcount */
const struct dentry_operations *d_op;
struct super_block *d_sb; /* The root of the dentry tree */
unsigned long d_time; /* used by d_revalidate */
void *d_fsdata; /* fs-specific data */
struct list_head d_lru; /* LRU list */
struct list_head d_child; /* child of parent list */
struct list_head d_subdirs; /* our children */
/*
* d_alias and d_rcu can share memory
*/
union {
struct hlist_node d_alias; /* inode alias list */
struct rcu_head d_rcu;
} d_u;
};
分析
通过遍历根目录"/“对应dentry(可以通过current->fs->root获得)的d_subdirs链表,能够找到一个名为"tmp"的dentry实例,注意这里的dentry名没有”/",通过该实例的d_flags成员变量可以知道该dentry被标记为"已mounted",但是该dentry不是一个root dentry,即该dentry的d_parent成员变量指向的是根目录"/"对应dentry而不是自身???
不是每个文件系统根目录的dentry的d_parent成员变量都指向自身的吗?继续往下分析–》
通过函数vfs_path_lookup,查找路径"/tmp/kaka",最后一个参数struct path *path被填充,可以通过path->mnt->mnt_root获得目标dentry所在文件系统的根目录,该目录名称为"/",经分析,这个dentry实例与vfs的根目录"/“不是一个东西,每个挂载在vfs上的文件系统,都有一个名为”/"对应的dentry,作为该文件系统的根目录。继续分析,该dentry的d_flags成员变量显示该dentry未被mounted,并且,该dentry是一个root dentry,即该dentry的d_parent成员变量指向的是自身。
原来,上面这个名为"tmp"的dentry和由path得到的名为"/“的dentry其实都代表的是一个路径即”/tmp",vfs查找路径的时候,通过根目录找到的名为"tmp"的dentry,会检查他的d_flags成员变量,发现该dentry为mmounted状态的话,会继续查找究竟是谁挂载在"tmp"这个目录项上了,找到后再继续往下找。
那么,究竟内核是怎么查找到名为"tmp"的dentry实例和由path得到的名为"/"的dentry实例直接存在关系的呢?通过查看struct dentry的定义,好像并没有找到一个成员变量能过将这两个实例联系在一起???还有,内核查找路径不是通过dentry结构的d_subdirs成员变量链表进行查找的吗?
继续分析发现,linux内核是通过二元组{dentry,mount}找到当前路径下的相关内容,或者通过look_up回调函数进行硬件读取查找;其中对于第一种方法,通过二元组{dentry,mount}获取相关hash结果,在对应散列表下搜索。对于上面的问题,内核代码vfs_path_lookup->filename_lookup->path_lookupat->link_path_walk->walk_component->lookup_fast->__follow_mount_rcu函数中有如下代码:
if (!d_mountpoint(path->dentry))
return !(path->dentry->d_flags & DCACHE_NEED_AUTOMOUNT);
mounted = __lookup_mnt(path->mnt, path->dentry);
/*
* find the first mount at @dentry on vfsmount @mnt.
* call under rcu_read_lock()
*/
struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)
{
struct hlist_head *head = m_hash(mnt, dentry);
struct mount *p;
hlist_for_each_entry_rcu(p, head, mnt_hash)
if (&p->mnt_parent->mnt == mnt && p->mnt_mountpoint == dentry)
return p;
return NULL;
}
通过__lookup_mnt函数使得上述问题的两个dentry关联了起来。
值得一提的是,在函数vfs_path_lookup()中path变量作为一个buffer般的存在存储着中间变量,并被多次修改。