您的位置:首页 > 运维架构 > Linux

Linux Fs-path_walk(从路径dir到目标节点entry)

2010-12-19 01:00 519 查看
文件系统 - 从路径名到目标节点
文件系统

dentry inode file

这里有个重要的图片, 还有几个数据结构列在前面, 会对后面的分析有所帮助…



数据结构1
struct nameidata {
struct dentry *dentry;
struct vfsmount *mnt;
struct qstr last;
unsigned int flags;
int last_type;
};
数据结构 2
struct dentry { // 该结构所代表的是逻辑意义上的文件, 记录的是其逻辑上的属性.
atomic_t d_count;
unsigned int d_flags;
struct inode * d_inode; /* Where the name belongs to - NULL is negative */
struct dentry * d_parent; /* parent directory */
struct list_head d_vfsmnt;
struct list_head d_hash; /* lookup hash list */
struct list_head d_lru; /* d_count = 0 LRU list */
struct list_head d_child; /* child of parent list */
struct list_head d_subdirs; /* our children */
struct list_head d_alias; /* inode alias list */
struct qstr d_name;
unsigned long d_time; /* used by d_revalidate */
struct dentry_operations *d_op;
struct super_block * d_sb; /* The root of the dentry tree */
unsigned long d_reftime; /* last time referenced */
void * d_fsdata; /* fs-specific data */
unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */
};

数据结构 3
struct inode { // 该结构所代表的是物理上的文件, 记录的是其物理上的属性.(比如说文件的存储位置, 被哪些不同名所指向, 所属用户.组, 所在设备号, 创建 修改时间等等)
struct list_head i_hash;
struct list_head i_list;
struct list_head i_dentry;
struct list_head i_dirty_buffers;
unsigned long i_ino;
atomic_t i_count;
kdev_t i_dev;
umode_t i_mode;
nlink_t i_nlink;
uid_t i_uid;
gid_t i_gid;
kdev_t i_rdev;
loff_t i_size;
time_t i_atime;
time_t i_mtime;
time_t i_ctime;
unsigned long i_blksize;
unsigned long i_blocks;
unsigned long i_version;
struct semaphore i_sem;
struct semaphore i_zombie;
struct inode_operations *i_op;
struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct super_block *i_sb;
wait_queue_head_t i_wait;
struct file_lock *i_flock;
struct address_space *i_mapping;
struct address_space i_data;
struct dquot *i_dquot[MAXQUOTAS];
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
unsigned long i_dnotify_mask; /* Directory notify events */
struct dnotify_struct *i_dnotify; /* for directory notifications */
unsigned long i_state;
unsigned int i_flags;
unsigned char i_sock;
atomic_t i_writecount;
unsigned int i_attr_flags;
__u32 i_generation;
union {
struct ext2_inode_info ext2_i;

} u;
};
数据结构 4
struct qstr {
const unsigned char * name;
unsigned int len;
unsigned int hash;
};

先分析这样一个函数.在path_walk 之前要对 path_walk返回的结构体nameidata指针进行必要的初始化.
Fs/namei.c L690-702
int path_init(const char *name, unsigned int flags, struct nameidata *nd)
{
nd->last_type = LAST_ROOT; /* if there are only slashes... */
nd->flags = flags;
if (*name=='/') //如果当前给出路径是以’/’开始, 则说明给出的是绝对路径, 也就是说要从根目录一层一层的找下去, 所以这里nameidata指针中的 dentry 要取当前进程的根目录的 dentry.
return walk_init_root(name,nd); //如是取路径.
read_lock(¤t->fs->lock);
// 否则的话说明当前给出路径是相对路径, 要从当前进程环境中取出当前目录的dentry, 及mnt .
nd->mnt = mntget(current->fs->pwdmnt);
nd->dentry = dget(current->fs->pwd);
read_unlock(¤t->fs->lock);
return 1;
}

Fs/namei.c L672-688
static inline int
walk_init_root(const char *name, struct nameidata *nd)
{
read_lock(¤t->fs->lock);
if (current->fs->altroot && !(nd->flags & LOOKUP _NOALT)) {
// 这里只读出了, 如果当前路径有替换根目录, 并且对应禁止标志位也无效,那么使用替换 目录.
nd->mnt = mntget(current->fs->altrootmnt);
nd->dentry = dget(current->fs->altroot);
read_unlock(¤t->fs->lock);
if (__emul_lookup_dentry(name,nd))
return 0;
read_lock(¤t->fs->lock);
}
//如果没有替换路径那么把当前用户根目录信息赋值到nameidata数据下.
nd->mnt = mntget(current->fs->rootmnt);
nd->dentry = dget(current->fs->root);
read_unlock(¤t->fs->lock);
return 1;
}
到这里, 在path_walk函数中所要用到的 dentry 及 mnt等数据都以初始化完成. 可以进行查找了. (这里要查找的是目标目录或文件的dentry及 mnt )

/*
* Name resolution.
*
* This is the basic name resolution function, turning a pathname
* into the final dentry.
*
* We expect 'base' to be positive and a directory.
*/
int path_walk(const char * name, struct nameidata *nd)
{
struct dentry *dentry;
struct inode *inode;
int err;
unsigned int lookup_flags = nd->flags;

while (*name=='/')
name++;
if (!*name)
goto return_base;
//以上 去掉给出路径最前面所有 ’/’ 字符.如果只有一个 ‘/’ 或全是 ‘/’ 那么 直接返回当传入的nameidata信息.
inode = nd->dentry->d_inode;
if (current->link_count)
lookup_flags = LOOKUP_FOLLOW;

/* At this point we know we have a real path component. */
for(;;) { // 主要的循环, 在这要遍历给出路径中每一个路径或文件.
unsigned long hash;
struct qstr this;
unsigned int c;

err = permission(inode, MAY_EXEC); //当前路径有执行的权限吗?
dentry = ERR_PTR(err);
if (err)
break;

this.name = name;
c = *(const unsigned char *)name;

hash = init_name_hash(); //初始化哈希值.
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); //哈希值计算函数.

/* remove trailing slashes? */
if (!c)
goto last_component; //给定最后一个节点是文件
while (*++name == '/');
if (!*name)
goto last_with_slashes; //给定最后一个节点是目录.

/*
* "." and ".." are special - ".." especially so because it has
* to be able to know about the current root directory and
* parent relationships.
*/
// 如果当前节点是 ‘.’ 或 ‘..’
if (this.name[0] == '.') switch (this.len) {
default:
break;
case 2:
if (this.name[1] != '.')
break;
follow_dotdot(nd);
inode = nd->dentry->d_inode;
/* fallthrough */
case 1:
continue;
}
/*
* See if the low-level filesystem might want
* to use its own hash..
*/
// 如果目标文件系统有自己的哈希函数. 那就用自带的哈希函数计算哈希值.
if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {
err = nd->dentry->d_op->d_hash(nd->dentry, &this);
if (err < 0)
break;
}
/* This does the actual lookups.. */
// 在内存中查找该目录或文件的dentry是否存在(过).
dentry = cached_lookup(nd->dentry, &this, LOOKUP_CONTINUE);
if (!dentry) {
dentry = real_lookup(nd->dentry, &this, LOOKUP_CONTINUE);
// 到磁盘上去查找该目录或文件 在dentry 是否存在(过).
err = PTR_ERR(dentry);
if (IS_ERR(dentry))就用自带的哈希函数.
break;
}
/* Check mountpoints.. */
//如果找到目录是一个设备的挂载点, 那么前进到被挂载设备的根目录上去, 直到设备的根目录下没有挂载其它设备为止.
while (d_mountpoint(dentry) && __follow_down(&nd->mnt, &dentry))
;

err = -ENOENT;
inode = dentry->d_inode;
if (!inode) //如果目录或文件已找到, 无论是目录还是文件, 这个inode 一定不为空. 需要这个数据去操作本节点.
goto out_dput;
err = -ENOTDIR;
if (!inode->i_op) // 节点操作函数不能为空.
goto out_dput;

if (inode->i_op->follow_link) {
err = do_follow_link(dentry, nd); //验证当前目录或文件是否是链接. 并前进到真正的节点.
dput(dentry);
if (err)
goto return_err;
err = -ENOENT;
inode = nd->dentry->d_inode;
if (!inode)
break;
err = -ENOTDIR;
if (!inode->i_op)
break;
} else {
dput(nd->dentry);
nd->dentry = dentry;
}
err = -ENOTDIR;
if (!inode->i_op->lookup)
break;
continue;
/* here ends the main loop */

last_with_slashes: //最后一个节点是目录.
lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
last_component: //最后一个节点是文件.
if (lookup_flags & LOOKUP_PARENT) //是找当前目录的根目录吗?
goto lookup_parent;
if (this.name[0] == '.') switch (this.len) { //最后一级是点或点点.
default:
break;
case 2:
if (this.name[1] != '.')
break;
follow_dotdot(nd);
inode = nd->dentry->d_inode;
/* fallthrough */
case 1:
goto return_base;
}
//最后一个节点自己有哈希函数.
if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {
err = nd->dentry->d_op->d_hash(nd->dentry, &this);
if (err < 0)
break;
}
// 在内存中查找最后一个节点.
dentry = cached_lookup(nd->dentry, &this, 0);
if (!dentry) {
// 到物理介质上去查找当前节点.
dentry = real_lookup(nd->dentry, &this, 0);
err = PTR_ERR(dentry);
if (IS_ERR(dentry))
break;
}
//最后一级目录或文件是否其它设备mount点, 如果是那么一直找到新设备根目录没有被其它设备mount 为止.
while (d_mountpoint(dentry) && __follow_down(&nd->mnt, &dentry))
;
inode = dentry->d_inode;
if ((lookup_flags & LOOKUP_FOLLOW)
&& inode && inode->i_op && inode->i_op->follow_link) {
err = do_follow_link(dentry, nd); //查看最后一级节点是不是其它文件或目录的链接, 如果是则前进到真实目录或文件上去.
dput(dentry);
if (err)
goto return_err;
inode = nd->dentry->d_inode;
} else {
dput(nd->dentry);
nd->dentry = dentry;
}
err = -ENOENT;
if (!inode)
goto no_inode;
if (lookup_flags & LOOKUP_DIRECTORY) {
err = -ENOTDIR;
if (!inode->i_op || !inode->i_op->lookup)
break;
}
goto return_base;
no_inode:
err = -ENOENT;
if (lookup_flags & (LOOKUP_POSITIVE|LOOKUP_DIRECTORY))
break;
goto return_base;
lookup_parent:
nd->last = this;
nd->last_type = LAST_NORM;
if (this.name[0] != '.')
goto return_base;
if (this.len == 1)
nd->last_type = LAST_DOT;
else if (this.len == 2 && this.name[1] == '.')
nd->last_type = LAST_DOTDOT;
return_base:
return 0;
out_dput:
dput(dentry);
break;
}
path_release(nd);
return_err:
return err;
}

举例如:
/////root//////share/c/test1/main.c
第一步, 去年root前的 ‘/’ 通过代码 while (*name=='/') name++;
第二步, 计算root的哈希值, 在内存中查找dentry, 看是否存在, 不存在则到物理设备中去查找对应的dentry, 如是其它设备的挂载点, 则前进到其设备的根节点, 一直打到新设备的根节点不再有其它设备被mount为止. 如该节点是链接, 则前进到真正的节点.
第三步, 去年 当前节点与下一个节点之间多余的 ‘/’ 字符. 重复第二步.

相关函数:
Path_walk() -> follow_dotdot() ./linux/fs/namei.c L380 - 413
static inline void follow_dotdot(struct nameidata *nd)
{
while(1) {
struct vfsmount *parent;
struct dentry *dentry;
read_lock(¤t->fs->lock);
if (nd->dentry == current->fs->root &&
nd->mnt == current->fs->rootmnt) { //如果已经是根设备了. 什么也不做.
read_unlock(¤t->fs->lock);
break;
}
read_unlock(¤t->fs->lock);
spin_lock(&dcache_lock);
if (nd->dentry != nd->mnt->mnt_root) { // 如果当前节点dentry 不等于当前节点monut数据结构中的根设备的dentry, 说明当前节点不是做为根节点被mount到其它设备上去的. Dentry中的d_vfsmnt是记录当前目录下有多少个mount设备的, 不能通过它来判断来当前节点是不是有其它设备被 mount ;
dentry = dget(nd->dentry->d_parent); //取出父节点.
spin_unlock(&dcache_lock);
dput(nd->dentry);
nd->dentry = dentry; //向父目录前进.
break;
}
// 以下情况是 当前节点是另一个设备mount 后的根目录, 再向上, 就要跳向其它设备了.
parent=nd->mnt->mnt_parent;
if (parent == nd->mnt) { // 这种情况是不应该出现的. 既然当前节点已经是其它设备的mount 点, 两个mnt对象就不应该为同一个.
spin_unlock(&dcache_lock);
break;
}
mntget(parent);
dentry=dget(nd->mnt->mnt_mountpoint); // 取 mount 节点的dentry;
spin_unlock(&dcache_lock);
dput(nd->dentry);
nd->dentry = dentry; // 跳回mount点. 这好像有点问题, 现在的dentry 是mount点的, 而现在是要向上级目录跳, 是从新设备的根目录向上跳, 不应该跳到mount点, 而应该跳到 mount 点的上层目录. ???
// 呵呵 , 注意这里是while(1)死循环, 而当前节点是mount点的时候是没有break的, 在下一次循环的时候 会向上跳并 执行break 跳出while(1);
mntput(nd->mnt);
nd->mnt = parent;
}
}

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息