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 *);
- };