Last updated on 6 months ago
前言 在文件描述符文章中已经介绍 fd是如何生成的;原本想接下来 说下 fd 和 文件是怎么关联的,但是粗略的看了来整个过程,实在是太复杂,有很多前置的知识点,很难一下子展开讲,要讲明白的话不是一两篇文章能解决的;于是 打算围绕这个问题,再细分下来,逐个击破。
本章的内容 就先简单的介绍 文件路径再linux 中怎么解析的
在文件描述符介绍的文章中 也有讲过 open 函数,这里就不重复叙述怎么进入 do_sys_open 函数
先介绍几个关键的结构体
nameidata
nameidata结构体 会一直伴随着 路径解析的过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 struct nameidata { struct path path ; struct qstr last ; struct path root ; struct inode *inode ; unsigned int flags; unsigned seq, m_seq; int last_type; unsigned depth; int total_link_count; struct saved { struct path link ; struct delayed_call done ; const char *name; unsigned seq; } *stack , internal[EMBEDDED_LEVELS]; struct filename *name ; struct nameidata *saved ; struct inode *link_inode ; } __randomize_layout;
file
一个file代表 一个打开的文件对象,通过 fd 可以找到这个 file ,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 struct file { union { struct llist_node fu_llist ; struct rcu_head fu_rcuhead ; } f_u; struct path f_path ; struct inode *f_inode ; const struct file_operations *f_op ; fmode_t f_mode; struct mutex f_pos_lock ; loff_t f_pos; struct fown_struct f_owner ; const struct cred *f_cred ; struct file_ra_state f_ra ; #ifdef CONFIG_EPOLL struct list_head f_ep_links ; struct list_head f_tfile_llink ; } __randomize_layout;
dentry
dentry (directory entry)记录了 父目录以及子目录项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 struct dentry { unsigned int d_flags; seqcount_t d_seq; struct hlist_bl_node d_hash ; struct dentry *d_parent ; struct qstr d_name ; struct inode *d_inode ; unsigned char d_iname[DNAME_INLINE_LEN]; union { struct list_head d_lru ; wait_queue_head_t *d_wait; }; struct list_head d_child ; struct list_head d_subdirs ; union { struct hlist_node d_alias ; struct hlist_bl_node d_in_lookup_hash ; struct rcu_head d_rcu ; } d_u; } __randomize_layout;
主要关注do_sys_open 中的 do_filp_open 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 long do_sys_open (int dfd, const char __user *filename, int flags, umode_t mode) { struct open_flags op ; int fd = build_open_flags(flags, mode, &op); struct filename *tmp ; if (fd) return fd; tmp = getname(filename); if (IS_ERR(tmp)) return PTR_ERR(tmp); fd = get_unused_fd_flags(flags); if (fd >= 0 ) { struct file *f = do_filp_open(dfd, tmp, &op); if (IS_ERR(f)) { put_unused_fd(fd); fd = PTR_ERR(f); } else { fsnotify_open(f); fd_install(fd, f); } } putname(tmp); return fd; }
do_filp_open fd申请成功的情况下, 才会开始路径解析, 该函数先 nameidata 初始化了 路径名字(先假设 文件路径绝对路径,为 /home/hrp/test) ,主要关注 path_openat 的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct file *do_filp_open (int dfd, struct filename *pathname, const struct open_flags *op) { struct nameidata nd ; int flags = op->lookup_flags; struct file *filp ; set_nameidata(&nd, dfd, pathname); filp = path_openat(&nd, op, flags | LOOKUP_RCU); if (unlikely(filp == ERR_PTR(-ECHILD))) filp = path_openat(&nd, op, flags); if (unlikely(filp == ERR_PTR(-ESTALE))) filp = path_openat(&nd, op, flags | LOOKUP_REVAL); restore_nameidata(); return filp; }
path_openat 主要工作是,根据open 带的flag来分配一个 file ,接下来根据 这些flag 来判断进入不同 流程 这里有O_PATH 的标志位,这个标志位只是申请了文件描述符,没有开打文件; 我们关注 以下这三个函数
**path_init **link_path_walk do_last (放在下篇文章讲)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static struct file *path_openat (struct nameidata *nd, const struct open_flags *op, unsigned flags) { struct file *file ; int error; file = alloc_empty_file(op->open_flag, current_cred()); if (IS_ERR(file)) return file; if (unlikely(file->f_flags & __O_TMPFILE)) { error = do_tmpfile(nd, flags, op, file); } else if (unlikely(file->f_flags & O_PATH)) { error = do_o_path(nd, flags, file); } else { const char *s = path_init(nd, flags); while (!(error = link_path_walk(s, nd)) && (error = do_last(nd, file, op)) > 0 ) { nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL); s = trailing_symlink(nd); } terminate_walk(nd); } if (likely(!error)) { if (likely(file->f_mode & FMODE_OPENED)) return file; WARN_ON(1 ); error = -EINVAL; } fput(file); }
path_init 前面说到的 nameidata 也只是初始化 路径名,而在path_init 中会进一步初始化 nameidata
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 static const char *path_init (struct nameidata *nd, unsigned flags) { const char *s = nd->name->name; .... nd->last_type = LAST_ROOT; nd->flags = flags | LOOKUP_JUMPED | LOOKUP_PARENT; nd->depth = 0 ; nd->root.mnt = NULL ; nd->path.mnt = NULL ; nd->path.dentry = NULL ; if (flags & LOOKUP_ROOT) { struct dentry *root = nd->root.dentry; struct inode *inode = root->d_inode; nd->path = nd->root; nd->inode = inode; return s; } if (*s == '/' ) { set_root(nd); if (likely(!nd_jump_root(nd))) return s; return ERR_PTR(-ECHILD); } else if (nd->dfd == AT_FDCWD) { if (flags & LOOKUP_RCU) { struct fs_struct *fs = current->fs; unsigned seq; do { seq = read_seqcount_begin(&fs->seq); nd->path = fs->pwd; nd->inode = nd->path.dentry->d_inode; nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq); } while (read_seqcount_retry(&fs->seq, seq)); } else { get_fs_pwd(current->fs, &nd->path); nd->inode = nd->path.dentry->d_inode; } return s; } else { .... return s; } }
link_path_walk nameidata 初始化了 root ,root 里面包含了 目录” / “ 的dentry, 接下来看 link_path_walk 的实现,该函数也是 路径解析的关键函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 假设路径为 /home/hrp/abcstatic int link_path_walk (const char *name, struct nameidata *nd) { while (*name == '/' ) name++; if (!*name) return 0 ; for (;;) { u64 hash_len; int type; err = may_lookup(nd); hash_len = hash_name(nd->path.dentry, name); type = LAST_NORM; if (name[0 ] == '.' ) switch (hashlen_len(hash_len)) { case 2 : if (name[1 ] == '.' ) { type = LAST_DOTDOT; nd->flags |= LOOKUP_JUMPED; } break ; case 1 : type = LAST_DOT; } if (likely(type == LAST_NORM)) { struct dentry *parent = nd->path.dentry; nd->flags &= ~LOOKUP_JUMPED; if (unlikely(parent->d_flags & DCACHE_OP_HASH)) { struct qstr this = { { .hash_len = hash_len }, .name = name }; err = parent->d_op->d_hash(parent, &this); if (err < 0 ) return err; hash_len = this.hash_len; name = this.name; } } nd->last.hash_len = hash_len; nd->last.name = name; nd->last_type = type; name += hashlen_len(hash_len); if (!*name) goto OK; do { name++; } while (unlikely(*name == '/' )); if (unlikely(!*name)) { OK: if (!nd->depth) return 0 ; name = nd->stack [nd->depth - 1 ].name; if (!name) return 0 ; err = walk_component(nd, WALK_FOLLOW); } else { err = walk_component(nd, WALK_FOLLOW | WALK_MORE); } if (err < 0 ) return err; if (unlikely(!d_can_lookup(nd->path.dentry))) { if (nd->flags & LOOKUP_RCU) { if (unlazy_walk(nd)) return -ECHILD; } return -ENOTDIR; } } }
walk_component 这里再分析下 walk_component , walk_component
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static int walk_component (struct nameidata *nd, int flags) { struct path path ; struct inode *inode ; unsigned seq; int err; if (unlikely(nd->last_type != LAST_NORM)) { err = handle_dots(nd, nd->last_type); if (!(flags & WALK_MORE) && nd->depth) put_link(nd); return err; } err = lookup_fast(nd, &path, &inode, &seq); return step_into(nd, &path, flags, inode, seq); }
lookup_fast 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 static int lookup_fast (struct nameidata *nd, struct path *path, struct inode **inode, unsigned *seqp) { struct vfsmount *mnt = nd->path.mnt; struct dentry *dentry , *parent = nd->path.dentry; int status = 1 ; int err; if (nd->flags & LOOKUP_RCU) { unsigned seq; bool negative; dentry = __d_lookup_rcu(parent, &nd->last, &seq); *inode = d_backing_inode(dentry); } path->mnt = mnt; path->dentry = dentry; err = follow_managed(path, nd); if (likely(err > 0 )) *inode = d_backing_inode(path->dentry); return err; }
总结 从 home/hrp/abc 得到hash 值, 在 **’/‘ ** 中的dentry找到 **’ home ‘**的 dentry ,更新 nameidata,
以此类推 直到找到 目录hrp所在的dentry 为止
下一篇文章介绍 解析到 hrp目录后,是怎么找到abc文件的
TODO:
是怎么通过 drenty hash到的
软连接,特殊字符(./ ../)怎么处理的
drentry 不在内存中,这种情况怎么处理?