版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
注:本文分析基于3.10.0-693.el7内核版本,即CentOS 7.4
上回说到".“和”.."两个目录项,如果打开的文件目录是一个正常绝对路径呢,如,/home/test.txt,那这个时候就是普通目录的处理了。
static inline int walk_component(struct nameidata *nd, struct path *path,
int follow)
{
struct inode *inode;
int err;
//.和..的处理
if (unlikely(nd->last_type != LAST_NORM))
return handle_dots(nd, nd->last_type);
//这里就是普通目录的处理,快通道,其实也就是内存里有该dentry缓存的情况
err = lookup_fast(nd, path, &inode);
if (unlikely(err)) {
if (err < 0)
goto out_err;
//如果内存中没找到,就只能老老实实调用对应文件系统的查找函数找了
err = lookup_slow(nd, path);
if (err < 0)
goto out_err;
inode = path->dentry->d_inode;
}
...
}
快通道的查询实际上就是在内存中查找是否有缓存,有的话检查目标dentry信息合法性,确认在查找期间相关信息未发生改变。
static int lookup_fast(struct nameidata *nd,
struct path *path, struct inode **inode)
{
struct vfsmount *mnt = nd->path.mnt;
struct dentry *dentry, *parent = nd->path.dentry;
int need_reval = 1;
int status = 1;
int err;
//使用RCU方式查找dentry
if (nd->flags & LOOKUP_RCU) {
unsigned seq;
//在内存中的散列表中根据字符串查找目标dentry
dentry = __d_lookup_rcu(parent, &nd->last, &seq);
if (!dentry)
//内存里没找到,那就只能通过较慢的方式去查找
goto unlazy;
//找到该目录,进行一些合法性检查,
*inode = dentry->d_inode;
if (read_seqcount_retry(&dentry->d_seq, seq))
return -ECHILD;
//d_seq的值用于保证在上面的查询过程中,父目录没有变化
if (__read_seqcount_retry(&parent->d_seq, nd->seq))
return -ECHILD;
nd->seq = seq;
//如果需要重新检查dentry合法性
if (unlikely(dentry->d_flags & DCACHE_OP_REVALIDATE)) {
status = d_revalidate(dentry, nd->flags);
if (unlikely(status <= 0)) {
if (status != -ECHILD)
need_reval = 0;
goto unlazy;
}
}
//更新临时变量path
path->mnt = mnt;
path->dentry = dentry;
//和之前类似,有可能找到的目录是个挂载点,因此需要找到真正的目录
if (likely(__follow_mount_rcu(nd, path, inode)))
return 0;
unlazy:
//unlazy_walk主要负责由rcu-walk转为ref-walk模式
if (unlazy_walk(nd, dentry))
return -ECHILD;
} else {
//非RCU方式查找
dentry = __d_lookup(parent, &nd->last);
}
//没找到dentry,那就是fast方式失败,返回通过慢方式查找
if (unlikely(!dentry))
goto need_lookup;//非RCU方式也没找到
//以下和上面通过RCU方式查找类似,如果找到了目标dentry就进行合法性检查
//以及挂载点跨越等操作
if (unlikely(dentry->d_flags & DCACHE_OP_REVALIDATE) && need_reval)
status = d_revalidate(dentry, nd->flags);
if (unlikely(status <= 0)) {
if (status < 0) {
dput(dentry);
return status;
}
if (!d_invalidate(dentry)) {
dput(dentry);
goto need_lookup;
}
}
path->mnt = mnt;
path->dentry = dentry;
err = follow_managed(path, nd->flags);
if (unlikely(err < 0)) {
path_put_conditional(path, nd);
return err;
}
if (err)
nd->flags |= LOOKUP_JUMPED;
*inode = path->dentry->d_inode;
return 0;
need_lookup:
//fast方式失败,返回用慢方式处理
return 1;
}
如果内存中没找到,那么就进入慢通道了,调用lookup_slow()函数。
static int lookup_slow(struct nameidata *nd, struct path *path)
{
struct dentry *dentry, *parent;
int err;
parent = nd->path.dentry;
BUG_ON(nd->inode != parent->d_inode);
//使用可进入睡眠状态的互斥锁,这就是为啥称之为slow
mutex_lock(&parent->d_inode->i_mutex);
dentry = __lookup_hash(&nd->last, parent, nd->flags);
mutex_unlock(&parent->d_inode->i_mutex);
if (IS_ERR(dentry))
return PTR_ERR(dentry);
//更新path临时变量
path->mnt = nd->path.mnt;
path->dentry = dentry;
//同样要考虑跨越挂载点的问题
err = follow_managed(path, nd->flags);
if (unlikely(err < 0)) {
path_put_conditional(path, nd);
return err;
}
if (err)
nd->flags |= LOOKUP_JUMPED;
return 0;
}
在慢通道中使用互斥锁保护临界区,因此有可能出现睡眠情况,这样就是为什么称之为慢通道的原因。也正是因为存在睡眠的情况,因此有可能在睡眠的时候有其他进程将目标dentry加载到内存,因此需要在内存中查找一次,万一就中奖了呢,反正内存中的操作耗时很小。
static struct dentry *__lookup_hash(struct qstr *name,
struct dentry *base, unsigned int flags)
{
bool need_lookup;
struct dentry *dentry;
//由于使用了mutex,有可能在睡眠的时候其他进程将该目录加载到内存
//因此再到内存里找一次,反正内存里耗时小
dentry = lookup_dcache(name, base, flags, &need_lookup);
if (!need_lookup)
return dentry;
//内存里没找到,调用文件系统的查找函数
return lookup_real(base->d_inode, dentry, flags);
}
如果睡眠的时候,nothing happend,那就乖乖的按程序走。调用lookup_real()函数,该函数最终会调用对应文件系统的查找函数。
static struct dentry *lookup_real(struct inode *dir, struct dentry *dentry,
unsigned int flags)
{
struct dentry *old;
/* Don't create child dentry for a dead directory. */
if (unlikely(IS_DEADDIR(dir))) {
dput(dentry);
return ERR_PTR(-ENOENT);
}
//调用对应文件系统的查找函数,有可能会调用到底层驱动,读取硬盘数据
old = dir->i_op->lookup(dir, dentry, flags);
if (unlikely(old)) {
dput(dentry);
dentry = old;
}
return dentry;
}
由上可见,慢的原因不仅在于互斥锁的睡眠,还有可能是查找函数调用底层驱动,获取硬盘数据。
到这,普通目录的情况也走完了,此时就剩最后一种情况——符号链接,下次继续。