索引节点高速缓存

8.3.2  索引节点高速缓存

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()方法;

inode_operations->lookup()该方法从磁盘找到并读入当前节点的目录项,然后通过

iget(sb, ino),根据索引节点号从磁盘读入相应索引节点并在内存建立起相应的inode结构,这就到了我们讨论的索引节点高速缓存,这里的ino是通过dentry得到的

当索引节点读入内存后,通过调用

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结构的地址也结合进去。

      1、  如果在哈希表中找到该节点,则其引用计数(i_count)加1;如果i_count在增加之前为0,说明该节点不“脏”,则该节点当前肯定处于inode_unused list队列中,于是,就把该节点从这个队列删除而插入inode_in_use队列;最后,把inodes_stat.nr_unused减1。

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

·      2、如果在哈希表中没有找到该节点,说明目标节点的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()返回时,该节点的锁被解除,并且唤醒所有等待者。

   

 

目录 1 虚拟文件系统概述 5 1.1 通用文件模型 7 1.2 VFS所处理的系统调用 9 2 虚拟文件系统架构 11 2.1 VFS对象数据结构 11 2.1.1 超级块对象 11 2.1.2 索引节点对象 15 2.1.3 文件对象 18 2.1.4 目录项对象 22 2.2 把Linux中的VFS对象串联起来 24 2.2.1 与进程相关的文件 25 2.2.2 索引节点高速缓存 29 2.2.3 目录项高速缓存 30 2.2.4 VFS对象的具体实现 32 2.3 文件系统的注册与安装 38 2.3.1 文件系统类型注册 38 2.3.2 文件系统安装数据结构 41 2.3.3 安装普通文件系统 52 2.3.4 分配超级块对象 58 2.3.5 安装根文件系统 60 2.3.6 卸载文件系统 65 2.4 路径名的查找 66 2.4.1 查找路径名的一般流程 67 2.4.2 父路径名查找 82 2.4.3 符号链接的查找 84 2.5 VFS系统调用的实现 88 2.5.1 open()系统调用 88 2.5.2 read()和write()系统调用 96 2.5.3 close()系统调用 97 3 第二扩展文件系统 99 3.1 Ext2磁盘数据结构 101 3.1.1 磁盘超级块 102 3.1.2 组描述符和位图 105 3.1.3 磁盘索引节点表 105 3.2 VFS接口数据结构 110 3.2.1 Ext2 超级块对象 110 3.2.2 Ext2 的索引节点对象 121 3.2.3 创建Ext2文件系统 124 3.2.4 Ext2的方法总结 126 3.3 Ext2索引节点分配 129 3.3.1 创建索引节点 130 3.3.2 删除索引节点 143 3.4 Ext2数据块分配 144 3.4.1 数据块寻址 145 3.4.2 文件的洞 147 3.4.3 分配数据块 148 4 页面高速缓存 160 4.1 页高速缓存数据结构 160 4.1.1 address_space对象 161 4.1.2 基树 164 4.2 高速缓存底层处理函数 166 4.2.1 查找页 166 4.2.2 增加页 168 4.2.3 删除页 173 4.3 文件系统与高速缓存 175 4.3.1 缓冲头数据结构 175 4.3.2 分配块设备缓冲区页 178 4.3.3 释放块设备缓冲区页 184 4.4 在页高速缓存中搜索块 185 4.4.1 __find_get_block()函数 185 4.4.2 __getblk()函数 188 4.4.3 __bread()函数 190 4.5 把脏页写入磁盘 191 4.5.1 pdflush内核线程 192 4.5.2 搜索要刷新的脏页 193 4.5.3 回写陈旧的脏页 196 5 文件读写 199 5.1 系统调用VFS层的处理 200 5.2 第二扩展文件系统Ext2层的处理 201 5.2.1 Ext2的磁盘布局 202 5.2.2 Ext2的超级块对象 206 5.2.3 Ext2索引节点对象的创建 210 5.2.4 Ext2索引节点对象的读取 218 5.2.5 Ext2层读文件入口函数 225 5.3 页高速缓存层的处理 237 5.3.1 创建一个bio请求 238 5.3.2 得到文件的逻辑块号 244 5.3.3 普通文件的readpage方法 251 5.3.4 块设备文件的readpage方法 252 5.3.5 文件的预读 260 5.4 通用块层的处理 264 5.4.1 块设备的基础知识 265 5.4.2 通用块层相关数据结构 269 5.4.3 提交I/O传输请求 271 5.4.4 请求队列描述符 273 5.5 块设备I/O调度层的处理 281 5.5.1 块设备的初始化 284 5.5.2 建立块设备驱动环境 288 5.5.3 关联block_device结构 295 5.5.4 为设备建立请求队列 306 5.5.5 块设备I/O调度程序 311 5.5.6 真实的I/O调度层处理 321 5.6 块设备驱动层的处理 330 5.6.1 scsi总线驱动的初始化 330 5.6.2 scsi设备驱动体系架构 342 5.6.3 scsi块设备驱动层处理 347 5.6.4 scsi命令的执行 369 5.6.5 scsi命令的第一次转变 372 5.6.6 scsi命令的第二次转变 380 5.7 写文件 384 5.7.1 generic file_write函数 384 5.7.2 普通文件的prepare_write方法 386 5.7.3 块设备文件的prepare_write方法 387 5.7.4 将脏页写到磁盘 388 6 直接I/O与异步I/O 391 6.1 直接I/O 391 6.2 异步I/O 393 6.2.1 Linux 2.6中的异步I/O 394 6.2.2 异步I/O环境 394 6.2.3 提交异步I/O操作 395
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值