文件系统专题之 “索引节点高速缓存”

2024-04-22 14:38

本文主要是介绍文件系统专题之 “索引节点高速缓存”,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

VFS也用了一个高速缓存来加快对索引节点的访问,和块高速缓存不同的一点是每个缓冲区不用再分为两个部分了,因为inode结构中已经有了类似于块高速缓存中缓冲区首部的域。索引节点高速缓存的实现代码全部在fs/inode.c,这部分代码并没有随着内核版本的变化做很多的修改。


1.索引节点链表


每个索引节点可能处于哈希表中,也可能同时处于下列“类型”链表的一种中:

·      "in_use" – 有效的索引节点,即 i_count > 0且i_nlink > 0(参看前面的inode结构)

·      "dirty" - 类似于 "in_use" ,但还“脏”

·      "unused" – 有效的索引节点但还没使用,即 i_count = 0。

这几个链表定义如下:

static LIST_HEAD(inode_in_use);

static LIST_HEAD(inode_unused);

static struct list_head *inode_hashtable;

    static LIST_HEAD(anon_hash_chain); /* for inodes with NULL i_sb */

因此,索引节点高速缓存的结构概述如下:

·      全局哈希表inode_hashtable,其中哈希值是根据每个超级块指针的值和32位索引节点号而得。对没有超级块的索引节点(inode->i_sb == NULL),则将其加入到anon_hash_chain链表的首部。例如,net/socket.c中sock_alloc()函数, 通过调用fs/inode.c中get_empty_inode()创建的套接字是一个匿名索引节点,这个节点就加入到了anon_hash_chain链表。

·      正在使用的索引节点链表。全局变量inode_in_use指向该链表中的首元素和尾元素。函数get_empty_inode()获得一个空节点,get_new_inode()获得一个新节点,通过这两个函数新分配的索引节点就加入到这个链表中。

·      未用索引节点链表。全局变量inode_unused的next域 和prev域分别指向该链表中的首元素和尾元素。

·      脏索引节点链表。由相应超级块的s_dirty域指向该链表中的首元素和尾元素。

·      对inode对象的缓存,定义如下:

static kmem_cache_t * inode_cachep

这是一个Slab缓存,用于分配和释放索引节点对象。

索引节点的i_hash域指向哈希表,i_list指向in_use、unused 或 dirty某个链表。所有这些链表都受单个自旋锁inode_lock的保护,其定义如下:

/*

* A simple spinlock to protect the list manipulations.

*

* NOTE! You also have to own the lock if you change

* the i_state of an inode while it is in use..

*/

static spinlock_t inode_lock = SPIN_LOCK_UNLOCKED;

   索引节点高速缓存的初始化是由inode_init()实现的,而这个函数是在系统启动时由init/main.c中的start_kernel()函数调用的。inode_init()只有一个参数,表示索引节点高速缓存所使用的物理页面数。因此,索引节点高速缓存可以根据可用物理内存的大小来进行配置,例如,如果物理内存足够大的话,就可以创建一个大的哈希表。

   索引节点状态的信息存放在数据结构inodes_stat_t中,在fs/fs.h中定义如下:

   struct inodes_stat_t {

        int nr_inodes;

        int nr_unused;

        int dummy[5];

    };

   extern struct inodes_stat_t inodes_stat

    用户程序可以通过/proc/sys/fs/inode-nr 和 /proc/sys/fs/inode-state获得索引节点高速缓存中索引节点总数及未用索引节点数。


2.索引节点高速缓存的工作过程


    为了帮助大家理解索引节点高速缓存如何工作,我们来跟踪一下在打开Ext2文件系统的一个常规文件时,相应索引节点的作用。

fd = open("file", O_RDONLY);

close(fd);

   open()系统调用是由fs/open.c中的sys_open函数实现的,而真正的工作是由fs/open.c中的filp_open()函数完成的,filp_open()函数如下:

    struct file *filp_open(const char * filename, int flags, int mode)

{

        int namei_flags, error;

         struct nameidata nd;

        namei_flags = flags;

         if ((namei_flags+1) & O_ACCMODE)

                 namei_flags++;

         if (namei_flags & O_TRUNC)

                namei_flags |= 2;

        error = open_namei(filename, namei_flags, mode, &nd);

         if (!error)

                 return dentry_open(nd.dentry, nd.mnt, flags);

         return ERR_PTR(error);

}

其中nameidata结构在fs.h中定义如下:

struct nameidata {

         struct dentry *dentry;

         struct vfsmount *mnt;

         struct qstr last;

         unsigned int flags;

        int last_type;

};

   这个数据结构是临时性的,其中,我们主要关注dentry和mnt域。Dentry结构我们已经在前面介绍过,而vfsmount结构记录着所属文件系统的安装信息,例如文件系统的安装点、文件系统的根节点等。

   filp_open()主要调用以下两个函数

(1)     open_namei():填充目标文件所在目录的dentry结构 和 所在文件系统的vfsmount结构。在dentry结构中dentry->d_inode就指向目标文件的索引节点。这个函数比较复杂和庞大,在此为了突出主题,后面我们只介绍与主题相关的内容。

(2)     dentry_open():建立目标文件的一个“上下文”,即file数据结构,并让它与当前进程的task_strrct结构挂上钩。同时,在这个函数中,调用了具体文件系统的打开函数,即f_op->open()。该函数返回指向新建立的file结构的指针。

     open_namei()函数通过path_walk()与目录项高速缓存(即目录项哈希表)打交道,而path_walk()又调用具体文件系统的inode_operations->lookup()方法;该方法从磁盘找到并读入当前节点的目录项,然后通过iget(sb, ino),根据索引节点号从磁盘读入相应索引节点并在内存建立起相应的inode结构,这就到了我们讨论的索引节点高速缓存。

当索引节点读入内存后,通过调用d_add(dentry, inode),就将dentry结构和inode结构之间的链接关系建立起来。两个数据结构之间的联系是双向的。一方面,dentry结构中的指针d_inode指向inode结构,这是一对一的关系,因为一个目录项只对应着一个文件。反之则不然,同一个文件可以有多个不同的文件名或路径(通过系统调用link()建立,注意与符号连接的区别,那是由symlink()建立的),所以从inode结构到dentry结构的方向是一对多的关系。因此, inode结构的i_ dentry是个队列,dentry结构通过其队列头部d_alias挂入相应inode结构的队列中。

  

    为了进一步说明索引节点高速缓存,我们来进一步考察iget()。当我们打开一个文件时,就调用了iget()函数,而iget真正调用的是iget4(sb, ino, NULL, NULL)函数,该函数代码如下:

     struct inode *iget4(struct super_block *sb, unsigned long ino, find_inode_t find_actor, void *opaque)

{

        struct list_head * head = inode_hashtable + hash(sb,ino);

         struct inode * inode;

         spin_lock(&inode_lock);

         inode = find_inode(sb, ino, head, find_actor, opaque);

         if (inode) {

                __iget(inode);

                 spin_unlock(&inode_lock);

                wait_on_inode(inode);

                return inode;

         }

         spin_unlock(&inode_lock);

         /*

          * get_new_inode() will do the right thing, re-trying the search

          * in case it had to block at any point.

         */

         return get_new_inode(sb, ino, head, find_actor, opaque);

}

下面对以上代码给出进一步的解释:

·      inode结构中有个哈希表inode_hashtable,首先在inode_lock锁的保护下,通过find_ inode函数在哈希表中查找目标节点的inode结构,由于索引节点号只有在同一设备上时才是唯一的,因此,在哈希计算时要把索引节点所在设备的super_block结构的地址也结合进去。如果在哈希表中找到该节点,则其引用计数(i_count)加1;如果i_count在增加之前为0,说明该节点不“脏”,则该节点当前肯定处于inode_unused list队列中,于是,就把该节点从这个队列删除而插入inode_in_use队列;最后,把inodes_stat.nr_unused减1。

·      如果该节点当前被加锁,则必须等待,直到解锁,以便确保iget4()返回一个未加锁的节点。

·      如果在哈希表中没有找到该节点,说明目标节点的inode结构还不在内存,因此,调用get_new_inode()从磁盘上读入相应的索引节点并建立起一个inode结构,并把该结构插入到哈希表中。

·      对get_new_inode()给出进一步的说明,该函数从Slab缓存区中分配一个新的inode结构,但是这个分配操作有可能出现阻塞,于是,就应当解除保护哈希表的inode_lock自旋锁,以便在哈希表中再次进行搜索。如果这次在哈希表中找到这个索引节点,就通过__iget把该节点的引用计数加1,并撤销新分配的节点。如果在哈希表中还没有找到,就使用新分配的索引节点;因此,把该索引节点的一些域先初始化为必须的值,然后调用具体文件系统的 sb->s_op->read_inode()域填充该节点的其他域。这就把我们从索引节点高速缓存带到了某个具体文件系统的代码中。当s_op->read_inode()方法正在从磁盘读索引节点时,该节点被加锁(i_state = I_LOCK);当read_inode()返回时,该节点的锁被解除,并且唤醒所有等待者。


这篇关于文件系统专题之 “索引节点高速缓存”的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/926052

相关文章

Linux中的缓冲区和文件系统详解

《Linux中的缓冲区和文件系统详解》:本文主要介绍Linux中的缓冲区和文件系统方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、FILE结构1、fd2、缓冲区二、文件系统1、固态硬盘2、逻辑地址LBA(一)数据块 Data blocks(二)inode表

Mysql中InnoDB与MyISAM索引差异详解(最新整理)

《Mysql中InnoDB与MyISAM索引差异详解(最新整理)》InnoDB和MyISAM在索引实现和特性上有差异,包括聚集索引、非聚集索引、事务支持、并发控制、覆盖索引、主键约束、外键支持和物理存... 目录1. 索引类型与数据存储方式InnoDBMyISAM2. 事务与并发控制InnoDBMyISAM

StarRocks索引详解(最新整理)

《StarRocks索引详解(最新整理)》StarRocks支持多种索引类型,包括主键索引、前缀索引、Bitmap索引和Bloomfilter索引,这些索引类型适用于不同场景,如唯一性约束、减少索引空... 目录1. 主键索引(Primary Key Index)2. 前缀索引(Prefix Index /

MySQL进阶之路索引失效的11种情况详析

《MySQL进阶之路索引失效的11种情况详析》:本文主要介绍MySQL查询优化中的11种常见情况,包括索引的使用和优化策略,通过这些策略,开发者可以显著提升查询性能,需要的朋友可以参考下... 目录前言图示1. 使用不等式操作符(!=, <, >)2. 使用 OR 连接多个条件3. 对索引字段进行计算操作4

Java实现Elasticsearch查询当前索引全部数据的完整代码

《Java实现Elasticsearch查询当前索引全部数据的完整代码》:本文主要介绍如何在Java中实现查询Elasticsearch索引中指定条件下的全部数据,通过设置滚动查询参数(scrol... 目录需求背景通常情况Java 实现查询 Elasticsearch 全部数据写在最后需求背景通常情况下

Pandas中多重索引技巧的实现

《Pandas中多重索引技巧的实现》Pandas中的多重索引功能强大,适用于处理多维数据,本文就来介绍一下多重索引技巧,具有一定的参考价值,感兴趣的可以了解一下... 目录1.多重索引概述2.多重索引的基本操作2.1 选择和切片多重索引2.2 交换层级与重设索引3.多重索引的高级操作3.1 多重索引的分组聚

oracle数据库索引失效的问题及解决

《oracle数据库索引失效的问题及解决》本文总结了在Oracle数据库中索引失效的一些常见场景,包括使用isnull、isnotnull、!=、、、函数处理、like前置%查询以及范围索引和等值索引... 目录oracle数据库索引失效问题场景环境索引失效情况及验证结论一结论二结论三结论四结论五总结ora

Golang使用minio替代文件系统的实战教程

《Golang使用minio替代文件系统的实战教程》本文讨论项目开发中直接文件系统的限制或不足,接着介绍Minio对象存储的优势,同时给出Golang的实际示例代码,包括初始化客户端、读取minio对... 目录文件系统 vs Minio文件系统不足:对象存储:miniogolang连接Minio配置Min

Python中列表的高级索引技巧分享

《Python中列表的高级索引技巧分享》列表是Python中最常用的数据结构之一,它允许你存储多个元素,并且可以通过索引来访问这些元素,本文将带你深入了解Python列表的高级索引技巧,希望对... 目录1.基本索引2.切片3.负数索引切片4.步长5.多维列表6.列表解析7.切片赋值8.删除元素9.反转列表

MySQL的索引失效的原因实例及解决方案

《MySQL的索引失效的原因实例及解决方案》这篇文章主要讨论了MySQL索引失效的常见原因及其解决方案,它涵盖了数据类型不匹配、隐式转换、函数或表达式、范围查询、LIKE查询、OR条件、全表扫描、索引... 目录1. 数据类型不匹配2. 隐式转换3. 函数或表达式4. 范围查询之后的列5. like 查询6