首先看dentry数据结构。位于include/linux/dcache.h中 struct dentry
ps:dentry虽然是目录的意思,但是在vfs中,目录和文件都有自己的dentry。(dentry中存了文件名,同一文件存在别名就是这个结构实现的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
再看inode数据结构。include/linux/fs.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
|
"文件"即按一定形式存储在介质上的信息,该信息包含两方面,其一是存储的数据本身,其二是文件的元信息以及记录数据占用的block的id的索引数据(这些信息都组织在文件对应的inode中)。在内存中,每个文件都有一个inode和dentry结构。dentry记录文件名,上级目录,子目录等信息,正是我们看到的(目录树、文件树)树状结构;inode记录着文件在存储介质上的位置和分布,dentry->d_inode指向对应的inode结构。inode代表物理意义上的文件,通过inode可以得到一个数组,这个数组记录文件内容的位置,若数组为(4,5,9),则对应数据位于硬盘的4,5,9块(这里只是一种简化表示,实际上记录文件内容所在的block的id,并不是一个简单的数据搞定的)。其索引节点号(就是inode号)为inode->ino,根据ino就可以计算出对应硬盘中inode的具体位置。
dentry所描述的是逻辑意义上的文件,所描述的是文件逻辑上的属性,所以dentry在磁盘上没有对应的映像;而inode结构代表的是物理意义上的文件,故磁盘上也有inode结构。
inode结构中有一个队列i_dentry,凡是代表同一个文件的所有目录项都通过d_alias域挂入相应的inode结构中的i_dentry队列。或者说,由于硬链接的存在,同一个文件可能有多种表示形式,可以成为别名,但它们都对应同一个文件,也就对应同一个inode,所以inode中有一个字段来将这些别名串起来,采用的是链表的形式。
进程打开一个文件,就会有一个file结构与之对应,同一个进程可以多次打开同一个文件而得到多个不同的file结构,file结构描述了被打开文件的属性,读写偏移指针等
两个不同的file可以对应同一个dentry结构,进程多次打开一个文件,对应只有一个dentry结构,dentry存的是目录项和对应文件的inode的信息。
在介质中,每个文件对应一个inode结点,每个文件可有多个文件名,即可以通过不同的文件名访问同一个文件,多个文件名对应一个文件的关系就是数据结构中dentry和inode多对一的关系。inode中不存储文件名字,只有节点号,通过节点号(ino),可以找到数据在介质中的具体位置,即通过内存inode结构中的ino可以定位到磁盘上的inode结构;而dentry则保存文件名和对应的节点号(inode号),这样就可以实现不同文件名访问一个inode。不同的dentry是通过ln指令实现的。
dentry树描绘了文件系统目录结构,但整个目录结构不能长驻内存,因为非常大。内存装不下。
初始状态下,系统只有代表根目录的dentry和所指向的inode(在根文件系统中挂载生成)。此时要打开一个文件,文件路径对应的结点不存在,根目录的dentry无法找到需要的子节点。这时需要通过inode->i_op中lookup方法找到inode的子节点,找到后,再创建一个dentry与之关联。
由这个过程可以看出。先有inode,再有dentry。
当生成的dentry无人使用时被释放,d_count字段记录了dentry的引用计数,引用为0时,dentry被释放。这里的释放不是直接销毁,而是将dentry放入一个“最近最少使用”队列。当队列过大,内存紧缺时,dentry才被释放。这个LRU队列就像是一个缓存池, 加速了对重复的路径的访问. 而当dentry被真正释放时, 它所对应的inode将被减引用. 如果引用为0, inode也被释放.
故需找一个文件路径时,有三种情况:
1.dentry引用大于0,直接在dentry树中找
2.dentry不在树种,在lru队列中找,LRU队列中的dentry被散列到一个散列表中,便于查找,若找到,则重新添加到dentry树中。
3.若2也未找到,则去找inode,找到后,再创建对应的dentry
这里补充每个目录项对象可以出于以下四种状态之一:
1:空闲状态 即该目录项不包含有效的信息,且没被VFS使用。
2.未使用状态 该对象的d_count计数为0,但d_inode指向关联的索引节点(inode)
3.正在使用状态 该对象的d_count计数不为0,但d_inode指向关联的索引节点
4.负状态 与目录项关联的索引节点不存在了
mount过程:
linux首先找到磁盘分区的super block,然后通过解析磁盘的inode table与file data。构建出自己的dentry列表和inode列表。VFS是按照Ext的方法进行构建的,两者非常相似。如inode结点。ext_inode结点中的一些成员变量其实是没有用的,如引用计数等,保留目的是为了和vfs-inode保持一致,这样在用ext_inode构建vfs_inode时,就不需要一个个赋值,只需要一次拷贝。
故非ext格式的文件系统,mount的过程会慢一些
根目录有一个dentry结构,而根目录里的文件和目录都链接到这个根dentry;同样的道理,二级目录里的文件和目录链接到二级目录,这样一层层,就形成了一颗dentry树。从树顶可以遍历整个文件系统。
为了加快对dentry的查找,内核使用hash表来缓存dentry,称为dentry cache,dentry一般现在dentry cache中查找(与之对应的,inode也会在内核空间使用hash表进行缓存,文件的内容,则采用页高速缓存page cache来缓存,而使用address_space和基树进行管理page cache中的页)。
dentry结构里有d_subdirs成员和d_child成员。d_subdirs是子项的链表头,所有的子项都要链接在这个链表上,d_child是自身链表头,需要连接到父dentry的d_subdirs成员。
d_parent是指针,指向父dentry结构。
d_hash是连接到dentry cache的hash链表。
d_name保存目录或文件的名字。打开一个文件的时候,根据这个成员和用户输入的名字来搜寻目标。
d_mounted用来指示dentry是否是一个挂载点。如果是,则成员不为零。
dentry的hash定位,通过d_hash()函数将父目录dentry的地址和所要查找的文件名的hash值结合起来,重新计算一个hash值,并根据其定位到dentry_hashtable哈希表中(即定位某个表头,缩小查找范围),该表是dentry缓存一部分,接下来扫描该链表,从中找到目标dentry,若没有找到,则通过real_lookup()函数从磁盘中查找。
目录项对象存放在dentry_cache的slab高速缓存中。故目录项的创建和删除是通过kmem_cache_alloc()和kmem_cache_free()实现的。目录项高速缓存由两种类型数据结构组成:
1.处于正在使用、未使用或负状态的目录项对象的集合。
2.一个散列表。用于hash快速查找给定文件名和目录名对应的目录项对象。散列表是由dentry_hashtable数组实现。数组中每个元素是一个指向链表的指针。