VFS数据结构之(dentry)
作者:互联网
1. 目录项对象(dentry object)
VFS把每个目录看作一个文件,由若干子目录和文件组成。对于进程查找路径名中的每个分量,内核都为其创建一个目录项对象;目录项对象将每个分量与其对应的索引节点相联系。例如/tmp/test,内核会分别为“/”,“tmp”,“test”创建目录项。注意,目录项对象在磁盘上没有对应的映像,所以dentry中不包含判断dirty的标志。
引入目录项结构是很有必要的,因为同一个文件有且仅有一个inode对象表示,而由于硬链接的存在,对同一文件的访问可以通过不同的文件名,所以中间需要引入目录项。
1.1 目录项数据结构
struct dentry {
atomic_t d_count; /* 目录项对象引用计数器 */
unsigned int d_flags; /* 目录项高速缓存标志 */
spinlock_t d_lock; /* 保护目录项对象的自旋锁 */
struct inode *d_inode; /* 与文件名关联的索引节点 */
struct hlist_node d_hash; /* 指向散列表表项链表的指针 */
struct dentry *d_parent; /* 父目录的目录项对象 */
struct qstr d_name; /* 文件名 */
struct list_head d_lru; /* 用于未使用目录项链表的指针 */
union {
struct list_head d_child; /* 对目录而言,用于同一父目录中的目录项链表的指针 */
struct rcu_head d_rcu; /* 回收目录项对象时,由RCU描述符使用 */
} d_u;
struct list_head d_subdirs; /* 对目录而言,子目录项链表的头 */
struct list_head d_alias; /* 用于与同一索引节点(别名)相关的目录项链表的指针 */
unsigned long d_time; /* 由d_revalidate方法使用 */
struct dentry_operations *d_op; /* 目录项方法 */
struct super_block *d_sb; /* 文件的超级块对象 */
void *d_fsdata; /* 依赖于文件系统的数据 */
struct dcookie_struct *d_cookie;/* 指向内核配置文件使用的数据结构的指针 */
int d_mounted; /* 对目录项而言,用于记录安装该目录项的文件系统数的计数器 */
unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* 存放短文件名的空间 */
};
1.2 目录项的状态
- 正在使用状态:
一个被使用的目录项对应一个有效的索引节点(d_inode指向相应的索引节点),并且该对象存在一个或多个使用者(d_count),该目录项包含有效信息,正在被使用,不能丢弃。 - 未被使用状态:
一个未被使用的目录项对应一个有效的索引节点,但当前VFS并未使用它(d_count值为0),该目录项对象仍然指向一个有效对象,而且被保留在缓存中,以后使用它时,不必重新创建,可以加速查找。但在必要时回收内存,该内容可能被丢弃。 - 负状态:
一个负状态的目录项对象没有对应的有效索引节点(d_inode为NULL),因为索引节点被删除或路径不正确,但是目录项仍然保留在高速缓存中,以便后续对同一文件名的查找操作能够快速完成。 - 状态切换:
当指向相应文件的最后一个硬链接被删除后,一个“正在使用”的目录项对象可能变成“负”状态,这种情况下,该目录项对象被移到“未使用”目录项对象组成的LRU链表中。每当内核缩减目录项高速缓存时,“负”状态目录项对象就朝着LRU链表的尾部移动,这样一来,这些对象就逐渐被释放。
1.3 关键字段说明
-
d_lru
:
所有“未使用状态”和“负状态”目录项对象都存放在一个“最近最少使用(Least Recently used,LRU)”的双向链表中,该链表按照插入的时间排序。最近最少使用的目录项对象总是靠近链表的尾部,一旦目录项高速缓存空间变小,内核就从链表尾部删除元素,使得最近最常使用的对象得以保留。该链表是由 dentry_unused 变量表示的全局双向链表,d_lru 字段指向该链表中的相邻元素。 -
d_alias
:
每个“正在使用状态”的目录项对象都被插入一个双向链表中,该链表由索引节点对象的 i_dentry 字段指向(注意:由于每个索引节点可能与若干硬链接关联,所以需要一个链表),目录项对象的 d_alias 字段指向相邻元素的地址。 -
d_hash
:
散列表由 dentry_hashtable 数组实现的。数组中每个元素是一个指向链表的指针。由于散列技术可能引发冲突,所以目录项包含 d_hash 字段指向具有相同散列值的链表中的相邻元素。散列函数产生的值是由目录项对象及文件名计算出来的。简单图示如下,行表示数组,列表示链表,其他列与第一列类似::
那么如何根据数组项和文件名得到哈希表的数组项,首先需要根据名字计算出hash值,计算方法参考下面 d_name 字段的说明,然后使用 d_hash 函数根据父目录的目录和hash值来得到相应的数组项,进而对链表实施插入遍历等操作/** * @entry: 父目录项地址 * @inode: 根据名字结算出的hash值 * @return:返回相应哈希表的数组项用于后面对链表的遍历插入等 */ 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_inode
:
每个目录项都有关联的索引节点。由于硬链接的存在,多个目录项可能指向同一索引节点。 -
d_sb
:
与目录项相应的超级块对象。 -
d_parent d_child d_subdirs
:- d_parent 字段表示父目录项对象。
- d_subdirs 子目录项链表的头。
- d_child 字段表示同一父目录中的目录项链表的指针。所以d_child 字段指向父目录项中 d_subdirs 链表的相邻元素。
例如tmp目录下有三个子目录,名字分别为test1,test2,test3。
-
d_count
:
表明该目录项对象有一个或多个使用者。目录项最后一个引用被删除时 d_count 值为0。 -
d_name
:
索引节点对象中并没有保存文件的名字,真正的名字保存在目录项的 d_name 字段中。d_name 也是一个结构体:struct qstr { unsigned int hash; /* 根据名字计算出的hash值 */ unsigned int len; /* 名字的长度 */ const unsigned char *name; /* 路径分量的名字 */ };
hash
:
根据路径分量的名字计算出的hash值,具体的文件系统可能会实现自己的生成hash值得相关方法,存放在d_op 字段的d_hash字段中。相关代码如下:int fastcall path_lookup(const char *name, unsigned int flags, struct nameidata *nd) do_path_lookup(AT_FDCWD, name, flags, nd); path_walk(name, nd); link_path_walk(name, nd); __link_path_walk(name, nd); unsigned long hash; struct qstr this; unsigned int c; /* 根据路径分量的名字生成hash */ this.name = name; c = *(const unsigned char *)name; /* #define init_name_hash() 0 */ hash = init_name_hash(); do { name++; hash = partial_name_hash(c, hash); return (prevhash + (c << 4) + (c >> 4)) * 11; c = *(const unsigned char *)name; } while (c && (c != '/')); this.len = name - (const char *) this.name; this.hash = end_name_hash(hash); return (unsigned int) hash; /* 可以使用具体文件系统定义的方法生成hash的方法 */ if (nd->dentry->d_op && nd->dentry->d_op->d_hash) { err = nd->dentry->d_op->d_hash(nd->dentry, &this); if (err < 0) break; }
-
d_mounted
:
分配目录项对象时该字段设为0,在该目录上每安装一个文件系统计数值加1,后续可以通过目录项对象中的 d_mounted 字段判断是否进入了挂载点。
1.4 目录项操作
与目录项对象关联的方法由 dentry_operations 结构描述,由 d_op 字段指向。
struct dentry_operations {
int (*d_revalidate)(struct dentry *, struct nameidata *);
int (*d_hash) (struct dentry *, struct qstr *);
int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
int (*d_delete)(struct dentry *);
void (*d_release)(struct dentry *);
void (*d_iput)(struct dentry *, struct inode *);
char *(*d_dname)(struct dentry *, char *, int);
};
d_revalidate(dentry, nameidata)
:
该函数判断目录项对象是否有效,VFS准备从dcache中使用一个目录项时,会调用该函数。大部分文件系统将该方法置为NULL,因为他们认为dcache中的目录项对象总是有效的。d_hash(dentry, name)
:
生成一个散列值;这是用于目录项散列表的,特定于我具体文件系统的散列函数,参数dentry标识包含路径分量的目录。参数name指向一个结构,该结构包含要查找的路径名分量以及由散列函数生成的散列值。d_compare(dir, name1, name2)
:
比较两个文件名。name1应该属于dir所指的目录.缺省的VFS函数是常用的字符匹配函数。对于有些文件系统,比如FAT,简单的字符串比较不能满足其需求,因为FAT文件系统不区分大小写,所以需要实现一种不区分大小写的字符串比较函数。d_delete(dentry)
:
当目录项对象的最后一个引用被删除时,调用该方法。缺省的VFS函数什么也不做。d_release(dentry)
:
当要释放一个目录项对象时,调用该方法。缺省的VFS函数什么也不做。d_iput(dentry, ino)
:
当一个目录项对象变为“负”状态时,调用该方法。缺省的VFS函数调用 iput() 释放索引节点对象。
1.5 分配根目录项
/**
* @root_inode: 用于根目录项的索引节点
*/
struct dentry * d_alloc_root(struct inode * root_inode)
struct dentry *res = NULL;
/* 根目录项的名字“/” */
static const struct qstr name = { .name = "/", .len = 1 };
res = d_alloc(NULL, &name);
struct dentry *dentry;
char *dname;
dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL);
dentry->d_name.name = dname;
dentry->d_name.len = name->len;
dentry->d_name.hash = name->hash;
memcpy(dname, name->name, name->len);
atomic_set(&dentry->d_count, 1);
dentry->d_flags = DCACHE_UNHASHED;
res->d_sb = root_inode->i_sb;
/* 根目录项的父目录项是其本身 */
res->d_parent = res;
/**
* d_instantiate - 为目录项填充索引节点信息
* @entry: 目录项
* @inode: 与该目录项关联的索引节点
*/
d_instantiate(res, root_inode);
list_add(&entry->d_alias, &inode->i_dentry);
entry->d_inode = inode;
标签:hash,struct,dentry,对象,数据结构,VFS,目录,name 来源: https://blog.csdn.net/weixin_50497980/article/details/118853272