前两篇博客看了超级块和索引节点的数据结构,接下来研究一下目录项dentry的结构。目录项设计的目的是构建完整的目录树,这样可以快速找到文件对应的inode,帮助VFS完成文件操作。具体过程如下:
用户程序在调用open()、stat()、chmod()等函数时,传递文件路径信息到VFS,VFS需要根据文件路径找到对应的inode,所以VFS用文件名和目录项缓存一层一层比对,直到找到对应的目录项,进而获取inode信息。如果在缓存中没有命中,VFS将会新创建文件的inode和对应的目录项,并且持久化到磁盘上。
所以,所有的文件系统对象都有对应的目录项,不论是目录、常规文件,还是符号链接、块设备文件、字符设备文件等,目录项反映的是文件系统对象在文件系统树中的位置,便于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]:文件名
/*
* 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
链接:查看原图
官方文档:VFS虚拟文件系统
《008 Linux文件系统数据结构详解:目录项dentry》有2个想法