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

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

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

【专题】2024飞行汽车技术全景报告合集PDF分享(附原数据表)

原文链接: https://tecdat.cn/?p=37628 6月16日,小鹏汇天旅航者X2在北京大兴国际机场临空经济区完成首飞,这也是小鹏汇天的产品在京津冀地区进行的首次飞行。小鹏汇天方面还表示,公司准备量产,并计划今年四季度开启预售小鹏汇天分体式飞行汽车,探索分体式飞行汽车城际通勤。阅读原文,获取专题报告合集全文,解锁文末271份飞行汽车相关行业研究报告。 据悉,业内人士对飞行汽车行业

day-51 合并零之间的节点

思路 直接遍历链表即可,遇到val=0跳过,val非零则加在一起,最后返回即可 解题过程 返回链表可以有头结点,方便插入,返回head.next Code /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}*

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟)

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟) 题目描述 给定一个链表,链表中的每个节点代表一个整数。链表中的整数由 0 分隔开,表示不同的区间。链表的开始和结束节点的值都为 0。任务是将每两个相邻的 0 之间的所有节点合并成一个节点,新节点的值为原区间内所有节点值的和。合并后,需要移除所有的 0,并返回修改后的链表头节点。 思路分析 初始化:创建一个虚拟头节点

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显

JS和jQuery获取节点的兄弟,父级,子级元素

原文转自http://blog.csdn.net/duanshuyong/article/details/7562423 先说一下JS的获取方法,其要比JQUERY的方法麻烦很多,后面以JQUERY的方法作对比。 JS的方法会比JQUERY麻烦很多,主要则是因为FF浏览器,FF浏览器会把你的换行也当最DOM元素。 <div id="test"><div></div><div></div

贝壳面试:什么是回表?什么是索引下推?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题: 1.谈谈你对MySQL 索引下推 的认识? 2.在MySQL中,索引下推 是如何实现的?请简述其工作原理。 3、说说什么是 回表,什么是 索引下推 ? 最近有小伙伴在面试 贝壳、soul,又遇到了相关的

专题二_滑动窗口_算法专题详细总结

目录 滑动窗口,引入: 滑动窗口,本质:就是同向双指针; 1.⻓度最⼩的⼦数组(medium) 1.解析:给我们一个数组nums,要我们找出最小子数组的和==target,首先想到的就是暴力解法 1)暴力: 2)优化,滑动窗口: 1.进窗口 2.出窗口 3.更新值 2.⽆重复字符的最⻓⼦串(medium) 1)仍然是暴力解法: 2)优化: 进窗口:hash[s[rig

Mysql高级篇(中)——索引介绍

Mysql高级篇(中)——索引介绍 一、索引本质二、索引优缺点三、索引分类(1)按数据结构分类(2)按功能分类(3) 按存储引擎分类(4) 按存储方式分类(5) 按使用方式分类 四、 索引基本语法(1)创建索引(2)查看索引(3)删除索引(4)ALTER 关键字创建/删除索引 五、适合创建索引的情况思考题 六、不适合创建索引的情况 一、索引本质 索引本质 是 一种数据结构,它用