VFS把目录当作文件对待,所以在路径/bin/vi中,bin和vi都属于文件---bin是特殊的目录文件而vi是一个普通文件,路径中的每个组成部分都由一个索引节点对象表示。虽然它们可以统一由索引节点表示,但是VFS经常需要执行目录相关的操作。比如路径名查找等。路径名查找需要解析路径中的每一个组成部分,不但要确保它有效,还要进一步寻址路径中的下一个部分。为了方便查找操作,VFS引入了目录项的概念。而每个dentry代表路径中的一个特定部分。必须明确一点:在路径中,包括普通文件在内,每一个部分都是目录项对象。目录项也可包括安装点。在路径/mnt/cdrom/foo中,/、mnt、cdrom和foo都属于目录项对象。
目录项对象由dentry结构体表示:
- 在<Dcache.h(include/linux)>中
- struct dentry {
- atomic_t d_count;
- unsigned int d_flags; /* protected by d_lock */
- spinlock_t d_lock; /* per dentry lock */
- struct inode *d_inode; /* Where the name belongs to - NULL is
- * negative */
- /*
- * The next three fields are touched by __d_lookup. Place them here
- * so they all fit in a cache line.
- */
- struct hlist_node d_hash; /* lookup hash list */
- struct dentry *d_parent; /* parent directory */
- struct qstr d_name;
- struct list_head d_lru; /* LRU list */
- /*
- * d_child and d_rcu can share memory
- */
- union {
- struct list_head d_child; /* child of parent list */
- struct rcu_head d_rcu;
- } d_u;
- struct list_head d_subdirs; /* our children */
- struct list_head d_alias; /* inode alias list */
- unsigned long d_time; /* used by d_revalidate */
- struct dentry_operations *d_op;
- struct super_block *d_sb; /* The root of the dentry tree */
- void *d_fsdata; /* fs-specific data */
- #ifdef CONFIG_PROFILING
- struct dcookie_struct *d_cookie; /* cookie, if any */
- #endif
- int d_mounted;
- unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */
- };
不同于super_block和inode两个对象,目录项对象没有对应的磁盘数据结构,VFS根据字符串形式的路径名现场创建它。而且由于目录项对象并非真正保存在磁盘上,所以目录项结构体没有是否被修改的标志。
- 目录项状态
目录项对象有三种有效状态:被使用,未被使用和负状态。
一个被使用的目录项对应一个有效的索引节点并且表明该对象存在一个或多个使用者。一个目录项处于被使用状态,意味着它正被VFS使用并且指向有效的索引节点,因此不能被丢弃。
一个未被使用的目录项对应一个有效的索引节点,但是应该指明VFS当前并未使用它。该目录项对象仍然指向一个有效对象,而且被保存到缓存中以便需要时再使用它。由于该目录项不会过早的被销毁,所以在以后再需要用到它时,不必重新创建,从而使路径查找更迅速。如果要收回内存,可以销毁未使用的目录项。
一个负状态的目录项没有对应的有效索引节点,因为索引节点已被删除,或路径不再正确了,但是目录项仍然保留,以便快速解析以后的路径查询。虽然负状态的目录项有些用处,但是如果需要的话,可以销毁它。目录项对象释放后也可以保存到slab对象缓存中去。
- 目录项缓存
如果VFS层遍历路径名中所有的元素并将它们逐个地解析成目录项对象,效率很低,所以内核将目录项对象缓存在目录项缓存(简称dcache)中。
目录项缓存包括三个部分:
1. “被使用的”目录项链表。该链表通过索引节点对象折哦你哦个的i_dentry项连接相关的索引节点,因为一个给定的索引节点可能有多个链接,所以就可能有多个目录项对象,因此用一个链表来连接它们。
2. “最近被使用的”双向链表。该链表包含有未被使用的和负状态的目录项对象。由于该链以时间顺序插入,所以链头的的节点是最新数据。当内核必须通过删除节点项回收内存时,会从链表删除节点项,因为尾部的节点最旧,它们在近期内再次被使用的可能性最小。
3. 散列表和相应的散列函数用来快速地将给定路径解析为相关目录项对象。
散列表由dentry_hashtable表示,其中每一个元素都是一个指向具有相同键值的目录项对象链表的指针。数组的大小取决于系统中物理内存的大小。
- 在<Dcache.c(fs)>中
- static struct hlist_head *dentry_hashtable __read_mostly;
实际的散列值有d_hash()函数技术,它是内核提供给文件系统的唯一的一个散列函数。
- 在<Dcache.c(fs)>中
- static inline struct hlist_head *d_hash(struct dentry *parent,
- unsigned long hash)
- {
- hash += ((unsigned long) parent ^ GOLDEN_RATIO_PRIME) / L1_CACHE_BYTES;
- hash = hash ^ ((hash ^ GOLDEN_RATIO_PRIME) >> D_HASHBITS);
- return dentry_hashtable + (hash & D_HASHMASK);
- }
查找散列表要通过d_lookup()函数计算,如果该函数在dcache中发现了与其相匹配的目录项对象,则匹配的对象被返回;否则返回NULL。
- /**
- * d_lookup - search for a dentry
- * @parent: parent dentry
- * @name: qstr of name we wish to find
- *
- * Searches the children of the parent dentry for the name in question. If
- * the dentry is found its reference count is incremented and the dentry
- * is returned. The caller must use d_put to free the entry when it has
- * finished using it. %NULL is returned on failure.
- *
- * __d_lookup is dcache_lock free. The hash list is protected using RCU.
- * Memory barriers are used while updating and doing lockless traversal.
- * To avoid races with d_move while rename is happening, d_lock is used.
- *
- * Overflows in memcmp(), while d_move, are avoided by keeping the length
- * and name pointer in one structure pointed by d_qstr.
- *
- * rcu_read_lock() and rcu_read_unlock() are used to disable preemption while
- * lookup is going on.
- *
- * dentry_unused list is not updated even if lookup finds the required dentry
- * in there. It is updated in places such as prune_dcache, shrink_dcache_sb,
- * select_parent and __dget_locked. This laziness saves lookup from dcache_lock
- * acquisition.
- *
- * d_lookup() is protected against the concurrent renames in some unrelated
- * directory using the seqlockt_t rename_lock.
- */
- struct dentry * d_lookup(struct dentry * parent, struct qstr * name)
- {
- struct dentry * dentry = NULL;
- unsigned long seq;
- do {
- seq = read_seqbegin(&rename_lock);
- dentry = __d_lookup(parent, name);
- if (dentry)
- break;
- } while (read_seqretry(&rename_lock, seq));
- return dentry;
- }
- 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;
- struct hlist_head *head = d_hash(parent,hash);
- struct dentry *found = NULL;
- struct hlist_node *node;
- struct dentry *dentry;
- rcu_read_lock();
- hlist_for_each_entry_rcu(dentry, node, head, d_hash) {
- struct qstr *qstr;
- if (dentry->d_name.hash != hash)
- continue;
- if (dentry->d_parent != parent)
- continue;
- spin_lock(&dentry->d_lock);
- /*
- * Recheck the dentry after taking the lock - d_move may have
- * changed things. Don't bother checking the hash because we're
- * about to compare the whole name anyway.
- */
- if (dentry->d_parent != parent)
- goto next;
- /*
- * It is safe to compare names since d_move() cannot
- * change the qstr (protected by d_lock).
- */
- 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)) {
- atomic_inc(&dentry->d_count);
- found = dentry;
- }
- spin_unlock(&dentry->d_lock);
- break;
- next:
- spin_unlock(&dentry->d_lock);
- }
- rcu_read_unlock();
- return found;
- }
而dcache在一定意义上也提供对索引节点的缓存。和目录项对象相关的索引节点对象不会被释放,因为目录项会让相关索引节点的使用计数为正,这样就可以确保索引节点留在内存中。主要目录项被缓存,其相应的索引节点也就被缓存了。
目录项操作
dentry_operation结构体指明了VFS操作目录项的所有方法:
- 在<Dcache.h(include/linux)>中
- struct dentry_operations {
- /*该函数判断目录对象是否邮箱。VFS准备从dcache中使用一个目录项时,会调用该函数。大部分文件系统将该方法设置为NULL,因为它们认为dcache中的目录项对象总是有效的*/
- int (*d_revalidate)(struct dentry *, struct nameidata *);
- /*该函数为目录项生成散列值,当目录项需要加入到散列表中时,VFS调用该函数*/
- int (*d_hash) (struct dentry *, struct qstr *);
- /*VFS调用该函数来比较两个文件名。注意,使用该函数时要加dcache_lock锁*/
- int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
- /*当目录项对象的d_count计数值等于零时,VFS调用该函数。注意,使用该函数时要加dcache_lock锁*/
- int (*d_delete)(struct dentry *);
- /* 当目录项对象将要被释放时,VFS调用该函数,默认情况下,它什么也不做 */
- void (*d_release)(struct dentry *);
- /* 当一个目录项对象丢失了其相关的索引节点时(也就是说磁盘索引节点被删除了),VFS调用该函数。默认情况下VFS会调用iput()函数释放索引节点。如果文件系统重载了该函数,那么除了执行文件系统特殊的工作外,还必须调用iput()函数 */
- void (*d_iput)(struct dentry *, struct inode *);
- };