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

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

相关文章

浅谈mysql的not exists走不走索引

《浅谈mysql的notexists走不走索引》在MySQL中,​NOTEXISTS子句是否使用索引取决于子查询中关联字段是否建立了合适的索引,下面就来介绍一下mysql的notexists走不走索... 在mysql中,​NOT EXISTS子句是否使用索引取决于子查询中关联字段是否建立了合适的索引。以下

MySQL之InnoDB存储引擎中的索引用法及说明

《MySQL之InnoDB存储引擎中的索引用法及说明》:本文主要介绍MySQL之InnoDB存储引擎中的索引用法及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录1、背景2、准备3、正篇【1】存储用户记录的数据页【2】存储目录项记录的数据页【3】聚簇索引【4】二

全面解析MySQL索引长度限制问题与解决方案

《全面解析MySQL索引长度限制问题与解决方案》MySQL对索引长度设限是为了保持高效的数据检索性能,这个限制不是MySQL的缺陷,而是数据库设计中的权衡结果,下面我们就来看看如何解决这一问题吧... 目录引言:为什么会有索引键长度问题?一、问题根源深度解析mysql索引长度限制原理实际场景示例二、五大解决

C++链表的虚拟头节点实现细节及注意事项

《C++链表的虚拟头节点实现细节及注意事项》虚拟头节点是链表操作中极为实用的设计技巧,它通过在链表真实头部前添加一个特殊节点,有效简化边界条件处理,:本文主要介绍C++链表的虚拟头节点实现细节及注... 目录C++链表虚拟头节点(Dummy Head)一、虚拟头节点的本质与核心作用1. 定义2. 核心价值二

MySQL中的索引结构和分类实战案例详解

《MySQL中的索引结构和分类实战案例详解》本文详解MySQL索引结构与分类,涵盖B树、B+树、哈希及全文索引,分析其原理与优劣势,并结合实战案例探讨创建、管理及优化技巧,助力提升查询性能,感兴趣的朋... 目录一、索引概述1.1 索引的定义与作用1.2 索引的基本原理二、索引结构详解2.1 B树索引2.2

python3如何找到字典的下标index、获取list中指定元素的位置索引

《python3如何找到字典的下标index、获取list中指定元素的位置索引》:本文主要介绍python3如何找到字典的下标index、获取list中指定元素的位置索引问题,具有很好的参考价值,... 目录enumerate()找到字典的下标 index获取list中指定元素的位置索引总结enumerat

从入门到精通MySQL 数据库索引(实战案例)

《从入门到精通MySQL数据库索引(实战案例)》索引是数据库的目录,提升查询速度,主要类型包括BTree、Hash、全文、空间索引,需根据场景选择,建议用于高频查询、关联字段、排序等,避免重复率高或... 目录一、索引是什么?能干嘛?核心作用:二、索引的 4 种主要类型(附通俗例子)1. BTree 索引(

MySQL 添加索引5种方式示例详解(实用sql代码)

《MySQL添加索引5种方式示例详解(实用sql代码)》在MySQL数据库中添加索引可以帮助提高查询性能,尤其是在数据量大的表中,下面给大家分享MySQL添加索引5种方式示例详解(实用sql代码),... 在mysql数据库中添加索引可以帮助提高查询性能,尤其是在数据量大的表中。索引可以在创建表时定义,也可

MySQL索引失效问题及解决方案

《MySQL索引失效问题及解决方案》:本文主要介绍MySQL索引失效问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录mysql索引失效一、概要二、常见的导致MpythonySQL索引失效的原因三、如何诊断MySQL索引失效四、如何解决MySQL索引失

什么是ReFS 文件系统? ntfs和refs的优缺点区别介绍

《什么是ReFS文件系统?ntfs和refs的优缺点区别介绍》最近有用户在Win11Insider的安装界面中发现,可以使用ReFS来格式化硬盘,这是不是意味着,ReFS有望在未来成为W... 数十年以来,Windows 系统一直将 NTFS 作为「内置硬盘」的默认文件系统。不过近些年来,微软还在研发一款名