1. 关于路径名查找
1.1 处理过程
VFS路径名查找,也就是如何从文件路径名导出相应的索引节点。这个处理过程可以简述为:检查与第一个名字匹配的目录项,获得相应的索引节点。然后,从磁盘读出包含那个索引节点的目录文件,并检查与第二个名字匹配的目录项,以获得相应的索引节点。对于包含在路径中的每个名字,这个过程反复执行。
1.2 绝对路径与相对路径
如果路径名的第一个字符是“/”,那么这个路径是绝对路径,因此从 current->fs->root
(进程根目录)所标识的目录开始搜索。否则路径是相对路径,因此从current->fs->pwd
(进程的当前目录)所标识的目录开始搜索。
1.3 高速缓存
目录项高速缓存极大地加速了查找过程,因为它把最常使用地目录项对象保留在内存中。很多情况下,路径名的分析可以避免从磁盘读取中间目录。
1.4 特殊处理
根据VFS文件系统地特点,查找中我们必须考虑:
- 检查每个目录的访问权限,验证是否允许进程读取这一目录的内容。
- 文件名是否是其他路径的符号链接。
- 符号链接可能导致循环使用
- 文件名可能是一个已安装文件系统的安装点。查找操作要延申到新的文件系统。
- 路径名查找应该在发出系统调用的进程的命名空间中完成。具有不同命名空间的两个进程使用的相同路径名,可能指定了不同的文件。
1.5 本次分析的要点
整个路径查找过程需要判断和分析处理的内容相对比较多,我们这里只分析路径查找过程中,某个分量作为安装点已经安装了文件系统,内核如何知道该目录是安装点,并且交换文件系统,进入并访问子文件系统内的目录或文件。
2. path_lookup()
路径名查找由 path_lookup() 执行。
/**
* @name: 文件路径名
* @flags: 查找标志
* @nd: 存放查找结果
* /
int fastcall path_lookup(const char *name, unsigned int flags, struct nameidata *nd)
do_path_lookup(AT_FDCWD, name, flags, nd);
/* 初始化nd的某些字段 */
nd->last_type = LAST_ROOT;
nd->flags = flags;
nd->depth = 0;
if (*name=='/') {
/* 使用绝对路径,查找从根目录开始 */
/* 获取相应的根文件系统已安装文件系统描述符和根目录的目录项 */
nd->mnt = mntget(fs->rootmnt);
nd->dentry = dget(fs->root);
} else if (dfd == AT_FDCWD) {
/* 使用相对路径,查找操作从当前工作目录开始 */
/* 获取当前工作目录对应的已安装文件系统描述符和目录项 */
nd->mnt = mntget(fs->pwdmnt);
nd->dentry = dget(fs->pwd);
} else {
}
path_walk(name, nd);
current->total_link_count = 0;
/* 查找操作的核心,参考3 */
link_path_walk(name, nd);
- nd 变量保存查找的结果,dentry 和 mnt 字段分别指向所解析的最后一个路径分量的目录项对象和已安装文件系统描述符。
- 由于 path_lookup() 函数返回的 nameidata 结构中的目录项对象和已安装文件系统对象代表了查找操作的结果,因此在调用者完成使用查找结果之前,这两个对象不能被释放。因此,path_lookup() 增加了两个对象引用计数器的值。如果要释放这些对象,则调用 path_release() 函数,传递给它 nameidata 结构的地址。
3. link_path_walk()
路径名查找操作的核心。
int fastcall link_path_walk(const char *name, struct nameidata *nd)
__link_path_walk(name, nd);
struct inode *inode;
/* 跳过路径名第一个分量前的斜杠 */
while (*name=='/')
name++;
/* 获取根目录或当前工作目录目录项的索引节点,这是开始查找的索引节点 */
inode = nd->dentry->d_inode;
/* 把路径名分解为分量,对每个分量执行执行for循环内的操作 */
for(;;) {
unsigned long hash;
struct qstr this;
unsigned int c;
/* 1. 访问权限检查 */
err = exec_permission_lite(inode, nd);
if (inode->i_op && inode->i_op->permission)
return -EAGAIN;
if (err == -EAGAIN)
err = vfs_permission(nd, MAY_EXEC);
/* 考虑要解析的下一个分量,从名字计算出hash值 */
this.name = name;
c = *(const unsigned char *)name;
hash = init_name_hash(); //hash = 0;
do {
name++;
hash = partial_name_hash(c, hash);
c = *(const unsigned char *)name;
} while (c && (c != '/'));
this.len = name - (const char *) this.name;
this.hash = end_name_hash(hash); //return hash;
/* 如果解析的分量是“.”和“..”,这种情况前面解析的目录项应该可以在缓存中找到 */
if (this.name[0] == '.') switch (this.len) {
default:
break;
case 2:
if (this.name[1] != '.')
break;
/* 参考3.1 */
follow_dotdot(nd);
/* inode重新指向跟踪后的索引节点值 */
inode = nd->dentry->d_inode;
/* 注意这里没有break */
case 1:
continue;
}
/* 如果解析的分量不是.和..则需要从目录项缓存中查找 */
/* 如果有自定义的散列值方法则调用它修改前面计算的散列值 */
if (nd->dentry->d_op && nd->dentry->d_op->d_hash)
nd->dentry->d_op->d_hash(nd->dentry, &this);
/* 参考3.3 */
do_lookup(nd, &this, &next);
inode = next.dentry->d_inode;
path_to_nameidata(&next, nd);
nd->mnt = path->mnt;
nd->dentry = path->dentry;
continue;
}
- exec_permission_lite() 执行权限检查,如果访问的是一个目录需要有可执行权限。如果索引节点有自定义的 permission 方法,则执行它。
3.1 follow_dotdot()
当解析到分量为..
的时候,调用 follow_dotdot() 函数进行处理
static __always_inline void follow_dotdot(struct nameidata *nd)
while(1) {
/* while处理几个文件系统安装在同一安装点 */
struct vfsmount *parent;
struct dentry *old = nd->dentry;
/* 如果最近解析的目录是进程的根目录,那就不能再向上追踪了 */
if (nd->dentry == fs->root && nd->mnt == fs->rootmnt)
break;
/* 如果解析的目录不是文件系统的根目录 */
if (nd->dentry != nd->mnt->mnt_root) {
nd->dentry = dget(nd->dentry->d_parent);
break;
}
parent = nd->mnt->mnt_parent;
/* 这个文件系统没有安装在其他文件系统之上,这种情况通常表示为命名空间的根文件系统 */
if (parent == nd->mnt)
break;
/* 这里需要交换文件系统 */
nd->dentry = dget(nd->mnt->mnt_mountpoint);
nd->mnt = parent;
/* 注意这里还在while循环里,dentry和mnt的赋值可能还会改变,最终会找到挂载点的父目录项 */
}
/* 判断目录是否为安装点,更新mnt和dentry的值,参考3.2 */
follow_mount(&nd->mnt, &nd->dentry);
3.2 follow_mount()
该函数是判断并切换文件系统的重要函数。检查分量名是否是某一文件系统的安装点,并交换文件系统,将目录项切换到已安装文件系统的根目录。__follow_mount() 函数与该函数类似。
static void follow_mount(struct vfsmount **mnt, struct dentry **dentry)
{
/* 该目录是否安装了其他文件系统,while处理几个文件系统安装在同一安装点 */
while (d_mountpoint(*dentry)) {
/* 哈希表中搜索在该目录项上安装的文件系统,参考3.3.3 */
struct vfsmount *mounted = lookup_mnt(*mnt, *dentry);
/* 如果没发生安装则直接退出,否则需要交换文件系统 */
if (!mounted)
break;
dput(*dentry);
mntput(*mnt);
*mnt = mounted;
/* 目录项切换为安装点根目录 */
*dentry = dget(mounted->mnt_root);
}
}
3.3 do_lookup()
static int do_lookup(struct nameidata *nd, struct qstr *name, struct path *path)
struct vfsmount *mnt = nd->mnt;
/* 在目录项高速缓存中搜索目录项对象,参考3.3.1 */
struct dentry *dentry = __d_lookup(nd->dentry, name);
if (!dentry)
goto need_lookup;
done:
path->mnt = mnt;
path->dentry = dentry;
/* 可以参考3.2 follow_mount,使用了不同的参数封装 */
__follow_mount(path);
need_lookup:
/* 如果没有找到这样的目录项对象,参考3.3.2 */
dentry = real_lookup(nd->dentry, name, nd);
goto done;
3.3.1 __d_lookup()
在目录项高速缓存中搜索分量的目录项对象。注意这里参数中的目录项传入的是父目录项的地址。
struct dentry * __d_lookup(struct dentry * parent, struct qstr * name)
unsigned int len = name->len;
unsigned int hash = name->hash;
const unsigned char *str = name->name;
/* 根据父目录项地址和计算出的hash值得到哈希表中的数组项 */
struct hlist_head *head = d_hash(parent,hash);
dentry_hashtable + (hash & D_HASHMASK);
struct dentry *found = NULL;
struct hlist_node *node;
struct dentry *dentry;
hlist_for_each_entry_rcu(dentry, node, head, d_hash) {
if (dentry->d_name.hash != hash)
continue;
if (dentry->d_parent != parent)
continue;
qstr = &dentry->d_name;
if (parent->d_op && parent->d_op->d_compare) {
if (parent->d_op->d_compare(parent, qstr, name))
goto next;
} else {
if (qstr->len != len)
goto next;
if (memcmp(qstr->name, str, len))
goto next;
if (!d_unhashed(dentry))
found = dentry;
}
3.3.2 real_lookup()
real_lookup() 函数执行索引节点的 lookup 方法从磁盘读取目录,创建一个新的目录项对象并把它插入到目录项高速缓存中,然后创建一个新的索引节点对象并把它插入到索引节点高速缓存中。
static struct dentry * real_lookup(struct dentry * parent, struct qstr * name, struct nameidata *nd)
struct dentry * result;
struct inode *dir = parent->d_inode;
/* 再次查找防止在等信号的过程中目录项被创建了 */
result = d_lookup(parent, name);
/* 本质上也是调用__d_lookup,加入了顺序锁的保护 */
dentry = __d_lookup(parent, name);
if (!result) {
struct dentry * dentry = d_alloc(parent, name);
result = dir->i_op->lookup(dir, dentry, nd);
result = dentry;
}
3.3.3 lookup_mnt()
根据安装点和父文件系统在哈希表中搜索子文件系统并返回已安装文件系统描述符。
struct vfsmount *lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)
struct vfsmount *child_mnt;
child_mnt = __lookup_mnt(mnt, dentry, 1)
struct list_head *head = mount_hashtable + hash(mnt, dentry);
struct list_head *tmp = head;
struct vfsmount *p, *found = NULL;
for (;;) {
tmp = dir ? tmp->next : tmp->prev;
p = NULL;
if (tmp == head)
break;
p = list_entry(tmp, struct vfsmount, mnt_hash);
if (p->mnt_parent == mnt && p->mnt_mountpoint == dentry) {
found = p;
break;
}
}
return found;