linux虚拟文件系统是设备驱动程序的之上的一个抽象层,致力于提供给应用程序一个统一的操作文件的接口。虚拟文件系统的各个数据结构之间的关系比较复杂,画了一张各个数据结构之间的关系图在 http://download.csdn.net/detail/lonewolfxw/4588935,这个清晰的给出了各结构的关系。
1. 目录查找
linux的虚拟文件系统最核心的结构就是dentry缓存,每次查找一个路径时,先在dentry缓存中查找是否有对应的项,例如cd /home/lonewolf则先解析目录结构,查找对应于home的dentry是否在缓存中,若不在则从底层设备中读取,然后查找lonewolf对应的dentry,依次进行,找到了dentry就找到文件对应的inode。因此,dentry缓存和文件目录树相同,按照树的方式组织,并且将已经得到的dentry项加入到dentry_hashtable缓存起来,避免每次都从低速的底层设备中读取,哈希表用来快速的查找。这当然只是一个查找的简略的过程,实际的过程相当复杂,需要处理很多情况,包括:
- 检测符号链接,是否要跟踪符号链接
- 检测挂载点,并且重定向到新的文件系统查找
- 检查路径上每个目录的权限
- 解析各种复杂的用户输入的路径,如/./home///..//.
- /*
- * 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.
- *
- * Returns 0 and nd will have valid dentry and mnt on success.
- * Returns error and drops reference to input namei data on failure.
- */
- static int link_path_walk(const char *name, struct nameidata *nd)<span style="white-space:pre"> </span> /*struct nameidata只是一个查找结果的传送定义的一个结构体,查找成功则nd->path就是找到的结果,在执行这个函数之前,先对@nd进行初始化,表示路径查找的起点,若@name是以'/'开始,怎@nd->path就初始化fs_struct->root表示从跟目录开始查找,若@name不是以'/'开始,则初始化fs_struct->pwd表示从进程的当前目录开始查找*/
- {
- struct path next;
- int err;
- while (*name=='/') //若name是以/开始,则跳过,然后解析真正的路径的各个部分
- name++;
- if (!*name)
- return 0;
- /* At this point we know we have a real path component. */
- for(;;) {
- unsigned long hash;
- struct qstr this;
- unsigned int c;
- int type;
- err = may_lookup(nd); //检查权限,检查@nd对应inode的目录是否有执行权限,访问目录需要目录的执行权限
- if (err)
- break;
- /*下面就是解析目录的各个部分, this表示解析的当前部分的结果*/
- 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);
- /*type表示当前部分的类型,用来处理特殊目录'.'和'..'*/
- type = LAST_NORM;
- if (this.name[0] == '.') switch (this.len) {
- case 2:
- if (this.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)) {
- err = parent->d_op->d_hash(parent, nd->inode,
- &this);
- if (err < 0)
- break;
- }
- }
- /*如果字符串已经结束,或者当前解析之后全是'/',则当前部分是路径的最后一部分,没有进过walk_component处理,因为有可能最后一个部分不是目录,从而跳到last_component处理,由调用link_path_walk出来最后一个部分*/
- if (!c)
- goto last_component;
- while (*++name == '/');
- if (!*name)
- goto last_component;
- err = walk_component(nd, &next, &this, type, LOOKUP_FOLLOW);/*这个函数执行真正的dentry缓存的查找,在dentry_hashtable中查找@this的名字,找不到则使用inode的inode_operations->lookup中的操作从底层设备载入进来,这个哈希表用父目录的dentry和当前目录名字的hash值也就是this->hash作为键值。而且这个函数还处理目录的权限以及目录是装载点的情况,由于一个目录下可以装载多个文件系统,最新装载的文件系统隐藏以前的装载,若是装载点,则顺着装载点一直查找,直到最上层的装载点也就是当前可以看到的文件系统,当这个函数返回1,则表示这个目录是符号链接,下面进行特殊处理。函数调用成功则 @nd->path 表示this.name这个名字所表示的目录,也是就当前解析成功的目录,然后下一次循环解析下一个部分时候,这个目录就当做父目录在dentry缓存中查找,直至所有的部分全部完成*/
- if (err < 0)
- return err;
- if (err) {
- err = nested_symlink(&next, nd);//如果err是1,则处理符号链接
- if (err)
- return err;
- }
- if (can_lookup(nd->inode)) /*检查这个部分是否可以查找,也就是说检查这个部分是否是目录,由于除了最后一部分之外,中间的部分必须是目录,不是目录则出错。是最后一项会跳过此处的检查,直接跳到last_component*/
- continue;
- err = -ENOTDIR;
- break;
- /* here ends the main loop */
- last_component:
- nd->last = this;
- nd->last_type = type;
- return 0;
- }
- terminate_walk(nd);
- return err;
- }
- static inline int walk_component(struct nameidata *nd, struct path *path,
- struct qstr *name, int type, int follow)
- {
- struct inode *inode;
- int err;
- /*
- * "." and ".." are special - ".." especially so because it has
- * to be able to know about the current root directory and
- * parent relationships.
- */
- if (unlikely(type != LAST_NORM))
- return handle_dots(nd, type); /*处理目录是'.'和'..’的情况,'.'很好处理,直接跳过就可以了,'..'稍微麻烦,因为当前目录有可能是一个装载点,跳到上一级目录就要切换文件系统*/
- err = do_lookup(nd, name, path, &inode);/*这个从dentry缓存中查找,找不到就从底层设备中找,并且会处理装载点的情况*/
- if (unlikely(err)) {
- terminate_walk(nd);
- return err;
- }
- if (!inode) {//没有找到dentry,则表示文件不存在
- path_to_nameidata(path, nd);
- terminate_walk(nd);
- return -ENOENT;
- }
- if (should_follow_link(inode, follow)) {//检查是否要跟踪符号链接,若是返回1,有nested_symlink处理
- if (nd->flags & LOOKUP_RCU) {
- if (unlikely(unlazy_walk(nd, path->dentry))) {
- terminate_walk(nd);
- return -ECHILD;
- }
- }
- BUG_ON(inode != path->dentry->d_inode);
- return 1;
- }
- path_to_nameidata(path, nd);//将找到的path放到nd中返回
- nd->inode = inode;
- return 0;
- }
- static inline int handle_dots(struct nameidata *nd, int type)
- {
- if (type == LAST_DOTDOT) {
- if (nd->flags & LOOKUP_RCU) {
- if (follow_dotdot_rcu(nd))
- return -ECHILD;
- } else
- follow_dotdot(nd);
- }
- return 0;
- }
- static int follow_dotdot_rcu(struct nameidata *nd)
- {
- set_root_rcu(nd); //获得当前进程的根文件系统的path
- while (1) {
- if (nd->path.dentry == nd->root.dentry &&
- nd->path.mnt == nd->root.mnt) { /*如果在根路径上执行"..",没有意义直接跳过就可以了*/
- break;
- }
- if (nd->path.dentry != nd->path.mnt->mnt_root) { /*不是装载点的根目录,就直接获得dentry的parent就可以了*/
- struct dentry *old = nd->path.dentry;
- struct dentry *parent = old->d_parent;
- unsigned seq;
- seq = read_seqcount_begin(&parent->d_seq);
- if (read_seqcount_retry(&old->d_seq, nd->seq))
- goto failed;
- nd->path.dentry = parent;
- nd->seq = seq;
- break;
- }
- if (!follow_up_rcu(&nd->path))
- break;
- nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq);
- }
- follow_mount_rcu(nd);
- nd->inode = nd->path.dentry->d_inode;
- return 0;
- failed:
- nd->flags &= ~LOOKUP_RCU;
- if (!(nd->flags & LOOKUP_ROOT))
- nd->root.mnt = NULL;
- rcu_read_unlock();
- br_read_unlock(vfsmount_lock);
- return -ECHILD;
- }
查找操作比较麻烦,处理的情况很多,沿着目录一步一步一直找到最后一个目录,然后dentry缓存起到很好的加速作用,不用每次都从设备中读取,在解析目录各个部分考虑符号链接和装载点就可以了。
2. 文件系统装载
文件系统装载大概的过程就是先查找文件系统要装载的目录的dentry和vfsmount,然后新建一个vfsmount表示新的装载点,调用文件系统的mount操作,将其装载,并且将新的vfsmount加入vfsmount树中,相应的dentry项设置相关的flag。文件系统的装载由do_mount完成
- /*
- * Flags is a 32-bit value that allows up to 31 non-fs dependent flags to
- * be given to the mount() call (ie: read-only, no-dev, no-suid etc).
- *
- * data is a (void *) that can point to any structure up to
- * PAGE_SIZE-1 bytes, which can contain arbitrary fs-dependent
- * information (or be NULL).
- *
- * Pre-0.97 versions of mount() didn't have a flags word.
- * When the flags word was introduced its top half was required
- * to have the magic value 0xC0ED, and this remained so until 2.4.0-test9.
- * Therefore, if this magic number is present, it carries no information
- * and must be discarded.
- */
- long do_mount(char *dev_name, char *dir_name, char *type_page,
- unsigned long flags, void *data_page)
- {
- struct path path;
- int retval = 0;
- int mnt_flags = 0;
- /* Discard magic */
- if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
- flags &= ~MS_MGC_MSK;
- /* Basic sanity checks */
- if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE)) //验证目录的名字
- return -EINVAL;
- if (data_page) //特定文件系统的私有项,大小为一页
- ((char *)data_page)[PAGE_SIZE - 1] = 0;
- /* ... and get the mountpoint */
- retval = kern_path(dir_name, LOOKUP_FOLLOW, &path); //上文讲的查找装载点的路径
- if (retval)
- return retval;
- retval = security_sb_mount(dev_name, &path,
- type_page, flags, data_page); //直接调用security_ops->sb_mount,若成功直接返回
- if (retval)
- goto dput_out;
- /*通过flag配置装载选项,下面是一个多路选择器,根据不同的装载选项调用不同的函数*/
- /* Default to relatime unless overriden */
- if (!(flags & MS_NOATIME))
- mnt_flags |= MNT_RELATIME;
- /* Separate the per-mountpoint flags */
- if (flags & MS_NOSUID)
- mnt_flags |= MNT_NOSUID;
- if (flags & MS_NODEV)
- mnt_flags |= MNT_NODEV;
- if (flags & MS_NOEXEC)
- mnt_flags |= MNT_NOEXEC;
- if (flags & MS_NOATIME)
- mnt_flags |= MNT_NOATIME;
- if (flags & MS_NODIRATIME)
- mnt_flags |= MNT_NODIRATIME;
- if (flags & MS_STRICTATIME)
- mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME);
- if (flags & MS_RDONLY)
- mnt_flags |= MNT_READONLY;
- flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | MS_BORN |
- MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT |
- MS_STRICTATIME);
- if (flags & MS_REMOUNT)
- retval = do_remount(&path, flags & ~MS_REMOUNT, mnt_flags,
- data_page);
- else if (flags & MS_BIND)
- retval = do_loopback(&path, dev_name, flags & MS_REC);
- else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
- retval = do_change_type(&path, flags);
- else if (flags & MS_MOVE)
- retval = do_move_mount(&path, dev_name);
- else
- retval = do_new_mount(&path, type_page, flags, mnt_flags,
- dev_name, data_page);
- dput_out:
- path_put(&path);
- return retval;
- }
- /*
- * create a new mount for userspace and request it to be added into the
- * namespace's tree
- */
- static int do_new_mount(struct path *path, char *type, int flags,
- int mnt_flags, char *name, void *data)
- {
- struct vfsmount *mnt;
- int err;
- if (!type)
- return -EINVAL;
- /* we need capabilities... */
- if (!capable(CAP_SYS_ADMIN)) //权限检查
- return -EPERM;
- mnt = do_kern_mount(type, flags, name, data); /*新建一个vfsmount实例,并通过特定文件系统的操作装载到系统系统中,返回装载点的根目录*/
- if (IS_ERR(mnt))
- return PTR_ERR(mnt);
- err = do_add_mount(mnt, path, mnt_flags); /*将vfsmount加入到vfsmount树中, 设置相关的数据结构的选项*/
- if (err)
- mntput(mnt);
- return err;
- }
- static int attach_recursive_mnt(struct vfsmount *source_mnt,
- struct path *path, struct path *parent_path)
- {
- LIST_HEAD(tree_list);
- struct vfsmount *dest_mnt = path->mnt;
- struct dentry *dest_dentry = path->dentry;
- struct vfsmount *child, *p;
- int err;
- if (IS_MNT_SHARED(dest_mnt)) {
- err = invent_group_ids(source_mnt, true);
- if (err)
- goto out;
- }
- err = propagate_mnt(dest_mnt, dest_dentry, source_mnt, &tree_list); /*处理从属装载和共享装载,将相关的vfsmount通过tree_list返回*/
- if (err)
- goto out_cleanup_ids;
- br_write_lock(vfsmount_lock);
- if (IS_MNT_SHARED(dest_mnt)) {
- for (p = source_mnt; p; p = next_mnt(p, source_mnt))
- set_mnt_shared(p);
- }
- if (parent_path) { /*如果source_mnt之前装载在@parent_path,要迁移到@path上,则先从parent_path中移除,然后增加到@path路径上,移除包括从parent_path的vfsmount的子装载点中移除和从mount_hashtable中移除,因为mount_hashtable是通过父装载点的vfsmount和子装载的dentry来计算哈希值的*/
- detach_mnt(source_mnt, parent_path);
- attach_mnt(source_mnt, path);
- touch_mnt_namespace(parent_path->mnt->mnt_ns);
- } else { /*parent_path为空表示新挂载项,则设置source_mnt的mnt_parent、mnt_root、mnt_mountpoint,然后将其增加到父装载点的子装载点链表中,并将其加入哈希表*/
- mnt_set_mountpoint(dest_mnt, dest_dentry, source_mnt);
- commit_tree(source_mnt);
- }
- /*处理所有的从属装载和共享装载*/
- list_for_each_entry_safe(child, p, &tree_list, mnt_hash) {
- list_del_init(&child->mnt_hash);
- commit_tree(child);
- }
- br_write_unlock(vfsmount_lock);
- return 0;
- out_cleanup_ids:
- if (IS_MNT_SHARED(dest_mnt))
- cleanup_group_ids(source_mnt, NULL);
- out:
- return err;
- }