Linux用數據結構dentry來描述fs中與某個文件索引節點相鏈接的一個目錄項(可以是文件,也可以是目錄)。 每個dentry對象都屬於下列幾種狀態之一: (1)未使用(unused)狀態:該dentry對象的引用計數d_count的值為0,但其d_inode指針仍然指向相關的的索引節點。該目錄項仍然包含有效的信息,只是當前沒有人引用他。這種dentry對象在回收內存時可能會被釋放。 (2)正在使用(inuse)狀態:處於該狀態下的dentry對象的引用計數d_count大於0,且其d_inode指向相關的inode對象。這種dentry對象不能被釋放。 (3)負(negative)狀態:與目錄項相關的inode對象不復存在(相應的磁盤索引節點可能已經被刪除),dentry對象的d_inode指針為NULL。但這種dentry對象仍然保存在dcache中,以便後續對同一文件名的查找能夠快速完成。這種dentry對象在回收內存時將首先被釋放。 Linux為了提高目錄項對象的處理效率,設計與實現了目錄項高速緩存(dentry cache,簡稱dcache),它主要由兩個數據結構組成: 1. 哈希鏈表dentry_hashtable:dcache中的所有dentry對象都通過d_hash指針域鏈到相應的dentry哈希鏈表中。 2. 未使用的dentry對象鏈表dentry_unused:dcache中所有處於“unused”狀態和“negative”狀態的dentry對象都通過其d_lru指針域鏈入dentry_unused鏈表中。該鏈表也稱為LRU鏈表。 目錄項高速緩存dcache是索引節點緩存icache的主控器(master),也即dcache中的dentry對象控制著icache中的inode對象的生命期轉換。無論何時,只要一個目錄項對象存在於dcache中(非negative狀態),則相應的inode就將總是存在,因為inode的引用計數i_count總是大於0。當dcache中的一個dentry被釋放時,針對相應inode對象的iput()方法就會被調用。
1 目錄項對象的SLAB分配器緩存dentry_cache dcache是建立在dentry對象的slab分配器緩存dentry_cache(按照Linux的命名規則,似乎應該是dentry_cachep,^_^)之上的。因此,目錄項對象的創建和銷毀都應該通過kmem_cache_alloc()函數和kmem_cache_free()函數來進行。 dentry_cache是一個kmem_cache_t類型的指針。它定義在dcache.c文件中: static kmem_cache_t *dentry_cache; 這個slab分配器緩存是在dcache機制的初始化例程dcache_init()中通過調用函數kmem_cache_create()來創建的。 1.1 分配接口 dcache在kmem_cache_alloc()的基礎上定義兩個高層分配接口:d_alloc()函數和d_alloc_root()函數,用來從dentry_cache slab分配器緩存中為一般的目錄項和根目錄分配一個dentry對象。 其中,d_alloc()的實現如下: #define NAME_ALLOC_LEN(len) ((len+16) & ~15) strUCt dentry * d_alloc(struct dentry * parent, const struct qstr *name) { char * str; struct dentry *dentry; dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL); if (!dentry) return NULL; if (name->len > DNAME_INLINE_LEN-1) { str = kmalloc(NAME_ALLOC_LEN(name->len), GFP_KERNEL); if (!str) { kmem_cache_free(dentry_cache, dentry); return NULL; } } else str = dentry->d_iname; memcpy(str, name->name, name->len); str[name->len] = 0; atomic_set(&dentry->d_count, 1); dentry->d_flags = 0; dentry->d_inode = NULL; dentry->d_parent = NULL; dentry->d_sb = NULL; dentry->d_name.name = str; dentry->d_name.len = name->len; dentry->d_name.hash = name->hash; dentry->d_op = NULL; dentry->d_fsdata = NULL; INIT_LIST_HEAD(&dentry->d_vfsmnt); INIT_LIST_HEAD(&dentry->d_hash); INIT_LIST_HEAD(&dentry->d_lru); INIT_LIST_HEAD(&dentry->d_subdirs); INIT_LIST_HEAD(&dentry->d_alias); if (parent) { dentry->d_parent = dget(parent); dentry->d_sb = parent->d_sb; spin_lock(&dcache_lock); list_add(&dentry->d_child, &parent->d_subdirs); spin_unlock(&dcache_lock); } else INIT_LIST_HEAD(&dentry->d_child); dentry_stat.nr_dentry++; return dentry; } NOTE: (1)如果文件名的長度大於15,則調用kmalloc()函數從slab分配器中為文件名分配內存;否則將文件名拷貝到d_iname數組中,並讓b_name.name指針指向d_iname。 (2)引用計數d_count將被初始化為1,其余成員都被初始化為NULL。 (3)如果父目錄的dentry被給定,則設置d_parent指針指向父目錄的dentry對象(因此必須通過dget函數來增加父目錄dentry對象的引用計數)。並通過d_child指針域將這個dentry對象鏈入父目錄dentry對象的d_subdirs鏈表。否則,將d_child初始化為指向自身。 (4)將dcache統計信息中的dentry對象總數nr_dentry值加1。 函數d_alloc_root()用來為fs的根目錄(並不一定是系統全局文件系統的根“/”)分配dentry對象。它以根目錄的inode對象指針為參數,如下所示: struct dentry * d_alloc_root(struct inode * root_inode) { struct dentry *res = NULL; if (root_inode) { res = d_alloc(NULL, &(const struct qstr) { "/", 1, 0 }); if (res) { res->d_sb = root_inode->i_sb; res->d_parent = res; d_instantiate(res, root_inode); } } return res; } (1)可以看出,首先還是必須調用d_alloc()函數來從dentry_cache slab分配器緩存中分配一個dentry對象。注意!特別之處在於d_alloc()函數的調用參數。 (2)然後,將所分配的dentry對象的d_parent指針設置為指向自身。NOTE!這一點是判斷一個dentry對象是否是一個fs的根目錄的唯一准則(include/linux/dcache.h): #define IS_ROOT(x) ((x)==(x)->d_parent) (3)最後,通過調用d_instantiate()函數來實例化這個dentry對象。 函數d_instantiate用於向dentry結構中填寫inode信息,因此該函數會將一個dentry對象從negative狀態轉變為inuse狀態。如下所示: void d_instantiate(struct dentry *entry, struct inode * inode) { spin_lock(&dcache_lock); if (inode) list_add(&entry->d_alias, &inode->i_dentry); entry->d_inode = inode; spin_unlock(&dcache_lock); } NOTE! 函數d_instantiate()假定在被調用之前,調用者已經增加了inode的引用計數。 1.2 釋放接口 目錄項緩存dcache定義了兩個高層釋放接口:d_free()函數和dentry_iput()函數。其中,d_free函數用來將dcache中不使用的dentry對象釋放回dentry_cache slab分配器緩存;而dentry_iput()函數則用來釋放一個dentry對象對一個inode對象的引用關聯。 函數d_free()首先調用dentry對象操作方法中的d_release()函數(如果定義了的話),通常在d_release()函數中釋放dentry->fsdata數據。然後,用dname_external()函數判斷是否已經為目錄項名字d_name分配了內存,如果是,則調用kfree()函數釋放d_name所占用的內存。接下來,調用kmem_cache_free()函數釋放這個dentry對象。最後,修改dcache統計信息中的dentry對象總數(減1)。其源碼如下: /* no dcache_lock, please */ static inline void d_free(struct dentry *dentry) { if (dentry->d_op && dentry->d_op->d_release) dentry->d_op->d_release(dentry); if (dname_external(dentry)) kfree(dentry->d_name.name); kmem_cache_free(dentry_cache, dentry); dentry_stat.nr_dentry--; } 其中,dname_external()是定義在dcache.h頭文件中的內聯函數,如下: static __inline__ int dname_external(struct dentry *d) { return d->d_name.name != d->d_iname; } 而dentry_iput()函數則主要用於在調用d_free()函數釋放一個dentry對象之前,釋放該dentry對象與相應inode對象的關聯,從而將一個dentry對象轉變為negative狀態。主要包括如下幾項任務:(1)將這個dentry對象從相應inode對象的別名鏈表i_dentry中摘除;(2)解除自旋鎖dcache_lock;(3)調用dentry的操作方法d_iput()函數(如果有的話),或者iput()方法。 該函數與d_instantiate()函數是相反的,如下: static inline void dentry_iput(struct dentry * dentry) { struct inode *inode = dentry->d_inode; if (inode) { dentry->d_inode = NULL; list_del_init(&dentry->d_alias); spin_unlock(&dcache_lock); if (dentry->d_op && dentry->d_op->d_iput) dentry->d_op->d_iput(dentry, inode); else iput(inode); } else spin_unlock(&dcache_lock); } NOTE: (1)如果定義了dentry方法d_iput(),則dentry_iput()通過調用d_iput()方法來釋放inode對象,否則就通過iput()來釋放inode對象。 (2)dent