008 Linux文件系统数据结构详解:目录项dentry

前两篇博客看了超级块和索引节点的数据结构,接下来研究一下目录项dentry的结构。目录项设计的目的是构建完整的目录树,这样可以快速找到文件对应的inode,帮助VFS完成文件操作。具体过程如下:

用户程序在调用open()、stat()、chmod()等函数时,传递文件路径信息到VFS,VFS需要根据文件路径找到对应的inode,所以VFS用文件名和目录项缓存一层一层比对,直到找到对应的目录项,进而获取inode信息。如果在缓存中没有命中,VFS将会新创建文件的inode和对应的目录项,并且持久化到磁盘上。

dentry cache
dentry cache

所以,所有的文件系统对象都有对应的目录项,不论是目录、常规文件,还是符号链接、块设备文件、字符设备文件等,目录项反映的是文件系统对象在文件系统树中的位置,便于VFS快速寻找。

从以上分析可知,目录项不仅内存数据结构,也有持久化的磁盘结构。内核中VFS使用hashtable缓存目录项结构,缓存的目录项信息来自与磁盘。

一、磁盘目录项结构

以ext4文件系统为例,struct ext4_dir_entry_2 是ext4文件系统维护的目录项结构,里面包含以下几个字段:

  • 1)inode:inode编号,指向目录项对应的inode
  • 2)rec_len:目录项长度,由于目录项里包含文件名称,所以长度不固定,在查询目录项时,需要在目录下按顺序查找,通过rec_len就能知道下一个目录项的偏移量。在ext4文件系统中,目录项长度要求是4的倍数。
  • 3)name_len:文件名长度,不超过255个字符
  • 4)file_type:文件类型,常规文件、目录、字符设备、块设备、套接字、符号链接等
  • 5)name[EXT4_NAME_LEN]:文件名
ext2的目录项布局
ext2的目录项布局
/*
 * The new version of the directory entry.  Since EXT4 structures are
 * stored in intel byte order, and the name_len field could never be
 * bigger than 255 chars, it's safe to reclaim the extra byte for the
 * file_type field.
 */
struct ext4_dir_entry_2 {
	__le32	inode;			/* Inode number */
	__le16	rec_len;		/* Directory entry length */
	__u8	name_len;		/* Name length */
	__u8	file_type;		/* See file type macros EXT4_FT_* below */
	char	name[EXT4_NAME_LEN];	/* File name */
};
#define EXT4_NAME_LEN 255

#define EXT4_FT_UNKNOWN		0
#define EXT4_FT_REG_FILE	1
#define EXT4_FT_DIR		2
#define EXT4_FT_CHRDEV		3
#define EXT4_FT_BLKDEV		4
#define EXT4_FT_FIFO		5
#define EXT4_FT_SOCK		6
#define EXT4_FT_SYMLINK		7

二、VFS的目录项对象

VFS的目录项对象结构体是struct dentry,位于dcahe.h中。以下是详细的dentry结构:

struct dentry {
	/* RCU lookup touched fields */
	unsigned int d_flags;		/* protected by d_lock */
	seqcount_spinlock_t d_seq;	/* per dentry seqlock */
	struct hlist_bl_node d_hash;	/* lookup hash list */
	struct dentry *d_parent;	/* parent directory */
	struct qstr d_name;
	struct inode *d_inode;		/* Where the name belongs to - NULL is
					 * negative */
	unsigned char d_iname[DNAME_INLINE_LEN];	/* small names */

	/* Ref lookup also touches following */
	struct lockref d_lockref;	/* per-dentry lock and refcount */
	const struct dentry_operations *d_op;
	struct super_block *d_sb;	/* The root of the dentry tree */
	unsigned long d_time;		/* used by d_revalidate */
	void *d_fsdata;			/* fs-specific data */

	union {
		struct list_head d_lru;		/* LRU list */
		wait_queue_head_t *d_wait;	/* in-lookup ones only */
	};
	struct list_head d_child;	/* child of parent list */
	struct list_head d_subdirs;	/* our children */
	/*
	 * d_alias and d_rcu can share memory
	 */
	union {
		struct hlist_node d_alias;	/* inode alias list */
		struct hlist_bl_node d_in_lookup_hash;	/* only for in-lookup ones */
	 	struct rcu_head d_rcu;
	} d_u;
} __randomize_layout;
字段涵义使用说明
d_flags目录项的标志位,用于标识目录项的状态和属性/* d_flags entries */
#define DCACHE_OP_HASH          0x00000001
#define DCACHE_OP_COMPARE       0x00000002
#define DCACHE_OP_REVALIDATE        0x00000004
#define DCACHE_OP_DELETE        0x00000008
#define DCACHE_OP_PRUNE         0x00000010
d_seq用于实现读-复制更新(Read-Copy-Update)机制的序列计数器,用于保护目录项的访问
d_hash用于在哈希表中进行目录项的快速查找总的哈希表位于dcache.c中:
所有的目录项都要加入struct hlist_bl_head *dentry_hashtable
d_parent父目录的目录项
d_name目录项名称类型是qstr,快速字符串,一个结构体,包含了长度和const字符指针两个域
d_inode目录项对应文件的inode一个目录项对象对应一个索引节点对象。但一个索引节点对象可以对象多个目录项对象
因为inode反映的是文件系统对象的元数据,而dentry则表示文件系统对象在文件系统树中的位置
d_iname短名称用于存储短名称(长度小于等于DNAME_INLINE_LEN)的内部缓冲区,避免动态分配内存
d_lockref结合了锁和引用计数用于对目录项进行同步和引用计数
d_op链接到目录项操作表
d_sb指向目录项对象所属文件系统超级块的实例
d_time最后一次验证时间这个字段用于缓存目的。它存储了目录项最后一次验证或访问的时间戳,用于dentry_operations中的d_revalidate操作
d_fsdata该字段是void *类型,用于存储特定文件系统特殊的数据
d_lru目录项LRU链表
d_child链接到父目录下的孩子目录项列表
d_subdirs当前目录项子目录项列表
d_alias链接到相同inode的不同目录项链表链接表示相同索引节点(inode)的目录项别名。d_alias用作链表元素,以连接表示相同文件的各个dentry对象。
在利用硬链接两个不同名称表示同一文件时,会发生这种情况。对应于文件的inode的i_dentry成员用作该链表的表头。

三、dentry操作表

VFS目录项对象操作表dentry_operations

struct dentry_operations {
	int (*d_revalidate)(struct dentry *, unsigned int);
	int (*d_weak_revalidate)(struct dentry *, unsigned int);
	int (*d_hash)(const struct dentry *, struct qstr *);
	int (*d_compare)(const struct dentry *,
			unsigned int, const char *, const struct qstr *);
	int (*d_delete)(const struct dentry *);
	int (*d_init)(struct dentry *);
	void (*d_release)(struct dentry *);
	void (*d_prune)(struct dentry *);
	void (*d_iput)(struct dentry *, struct inode *);
	char *(*d_dname)(struct dentry *, char *, int);
	struct vfsmount *(*d_automount)(struct path *);
	int (*d_manage)(const struct path *, bool);
	struct dentry *(*d_real)(struct dentry *, const struct inode *);
} ____cacheline_aligned;
函数使用说明注意事项
d_revalidate()校验目录项的可用性和版本当VFS需要重新验证dentry时调用。每当名称查找在dcache中发现一个dentry时,就会调用此函数。大多数本地文件系统将其保留为NULL,因为它们在dcache中的所有拒绝都是有效的。网络文件系统是不同的,因为服务器上的事情可能会发生变化,而客户端不一定知道。
d_weak_revalidate当VFS需要重新验证Jump的dentry时调用。当路径遍历在dentry处结束时,会调用此函数,而dentry不是通过在父目录中进行查找获得的,路径中包括“/”、“.”和“..”,以及procfs样式的符号链接和挂载点遍历。在这种情况下,不太关心dentry是否仍然完全正确,而是关心inode是否仍然有效。与d_revalidate一样,大多数本地文件系统都会将其设置为NULL,因为它们的dcache条目始终有效。
d_hash当VFS向哈希表添加dentry时调用第一个参数dentry父目录项
d_compare比较dentry名称和指定的名字第一个参数dentry父目录项
d_delete当删除对dentry的最后一个引用并且dcache正在决定是否缓存它时调用。返回1表示立即删除,或返回0表示缓存dentry
d_init当分配一个新的目录项时,调用此函数
d_release当释放了一个目录项时,调用此函数
d_prune
d_iput当目录项丢失其inode时调用当此值为NULL时,默认情况下VFS调用iput()。如果你定义了这个方法,你必须自己调用iput()
d_dname当应生成dentry的路径名时调用(伪文件系统)对于某些伪文件系统(sockfs、pipefs等)延迟路径名生成很有用
d_automount
d_manage
d_real覆盖/联合类型的文件系统实现了此方法,以返回被覆盖隐藏的常规文件的底层节点之一。

四、目录项结构关系视图

以下以一个简单目录树结构,来说明它在内存中的关联关系

/
----home
--------/docs
-------------abc.txt
-------------file-system.pdf
-------------project.pdf
--------/photoes
------------body.jpg
------------greatwall.png
----usr
--------local
----var
目录项关联关系图
目录项结构关联关系图

链接:查看原图

上一篇:Linux文件系统数据结构详解:索引节点inode

官方文档:VFS虚拟文件系统

《008 Linux文件系统数据结构详解:目录项dentry》有2个想法

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注