Linux文件系统(四)文件缓存
Linux文件系统
Linux文件系统(一)文件系统概述
Linux文件系统(二)磁盘文件系统
Linux文件系统(三)虚拟文件系统
Linux文件系统(四)文件缓存
Linux文件系统(四)文件缓存
文章目录
- Linux文件系统(四)文件缓存
- 一、系统调用层和虚拟文件系统层
- 二、ext4 文件系统层
- 2.1 直接读写
- 2.2 带缓存的读写
- 三、总结
前几篇文章讲解了文件系统的挂载和文件的打开,这篇文章讲解文件的读写
一、系统调用层和虚拟文件系统层
文件的读写就是通过调用 read、write 系统调用来实现的,由于读写的实现过程很多逻辑都是相似的,所以两部分放在一起讨论
read、write 系统调用的定义如下
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{struct fd f = fdget_pos(fd);
......loff_t pos = file_pos_read(f.file);ret = vfs_read(f.file, buf, count, &pos);
......
}SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,size_t, count)
{struct fd f = fdget_pos(fd);
......loff_t pos = file_pos_read(f.file);ret = vfs_write(f.file, buf, count, &pos);
......
}
对于 read 来讲,调用了 vfs_read -> __vfs_read,对于 write 来讲,调用了 vfs_write -> __vfs_write
下面看以下 __vfs_read 和 __vfs_write 的代码
ssize_t __vfs_read(struct file *file, char __user *buf, size_t count,loff_t *pos)
{if (file->f_op->read)return file->f_op->read(file, buf, count, pos);else if (file->f_op->read_iter)return new_sync_read(file, buf, count, pos);elsereturn -EINVAL;
}ssize_t __vfs_write(struct file *file, const char __user *p, size_t count,loff_t *pos)
{if (file->f_op->write)return file->f_op->write(file, p, count, pos);else if (file->f_op->write_iter)return new_sync_write(file, p, count, pos);elsereturn -EINVAL;
}
上一篇文章讲了,进程打开的文件都对应有一个 struct file,里面有相应的文件操作集 file_operation,具体的文件操作集有文件系统决定
对于 read,最终会调用 file->f_op->read 或者 file->f_op->read_iter,对于 write,最终会调用 file->f_op->write 或者 file->f_op->write_iter
下面来讲解具体文件系统的实现,以 ext4 文件系统为例
二、ext4 文件系统层
2.1 直接读写
对于 ext4 文件系统,定义了一个文件操作集 file_operations,如下
const struct file_operations ext4_file_operations = {.......read_iter = ext4_file_read_iter,.write_iter = ext4_file_write_iter,
......
}
对于 ext4 文件系统,没有定义 read 和 write,所以会调用 ext4_file_read_iter 和 ext4_file_write_iter
ext4_file_read_iter 会调用 generic_file_read_iter,ext4_file_write_iter 会调用 __generic_file_write_iter
ssize_t
generic_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{......if (iocb->ki_flags & IOCB_DIRECT) {......struct address_space *mapping = file->f_mapping;
......retval = mapping->a_ops->direct_IO(iocb, iter);}
......retval = generic_file_buffered_read(iocb, iter, retval);
}ssize_t __generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
{......if (iocb->ki_flags & IOCB_DIRECT) {......written = generic_file_direct_write(iocb, from);
......} else {......written = generic_perform_write(file, from, iocb->ki_pos);
......}
}
generic_file_read_iter 和 __generic_file_write_iter 有相似的逻辑,就是是否使用缓存
缓存其实就是一块内存空间,因为内存的访问速度比硬盘快许多,所以Linux为了提高读写性能,会准备一块缓存,不直接操作硬盘,而是直接操作内存,然后批量读取或者写入磁盘
因此根据是否使用缓存,可以把文件的 I/O 操作分为两类
第一种类型是缓存 I/O。大多数文件系统都是缓存I/O。对于读操作来讲,操作系统会先检测,内存的缓存区是否已存在需要的数据。如果存在,那么直接从缓存中返回;如果不存在,则从硬盘中读取数据,然后缓存到操作系统的缓存中。对于写操作,操作系统会将用户空间的数组拷贝到内核的缓存中,对于用户进程,这已经算是写完成了,至于什么时候将数据写到硬盘中,由操作系统决定,除非显示调用 sync 同步命令马上将缓存写入硬盘
第二种类型是直接I/O,就是应用直接访问磁盘,而不使用缓存
从上面的程序中可以看到,如果设置了 IOCB_DIRECT 标志,那么就使用直接I/O,否则使用缓存I/O
对于读操作,如果是直接I/O,会调用 address_space 的 direct_IO 函数,直接从磁盘读取数据,address_space 的作用主要是内存映射的时候,将文件和内存页产生关联
address_space 的相关操作根据文件系统的不同而不同,对于 ext4 文件系统,定义如下
static const struct address_space_operations ext4_aops = {.......direct_IO = ext4_direct_IO,
......
};
对于写操作,如果是直接I/O,会调用 generic_file_direct_write,同样也会调用 address_space 的 direct_IO 函数
ext4_direct_IO 最终会调用 __blockdev_direct_IO -> do__blockdev_direct_IO,这就跨过了缓存层,直接到了文件系统的设备驱动层
下面我们再来看带缓存的读写操作
2.2 带缓存的读写
带缓存的写
首先看带缓存的写函数 generic_perform_write
ssize_t generic_perform_write(struct file *file,struct iov_iter *i, loff_t pos)
{struct address_space *mapping = file->f_mapping;const struct address_space_operations *a_ops = mapping->a_ops;do {struct page *page;unsigned long offset; /* Offset into pagecache page */unsigned long bytes; /* Bytes to write to page */status = a_ops->write_begin(file, mapping, pos, bytes, flags,&page, &fsdata);copied = iov_iter_copy_from_user_atomic(page, i, offset, bytes);flush_dcache_page(page);status = a_ops->write_end(file, mapping, pos, bytes, copied,page, fsdata);pos += copied;written += copied;balance_dirty_pages_ratelimited(mapping);} while (iov_iter_count(i));
}
这个函数中是一个 while 循环,我们需要找到这次写入影响的所有页,然后依次写入,对于每一个循环,主要做下面几件事
- 对每一页,先调用 address_space 的 write_begin 准备
- 调用 iov_iter_copy_from_user_atomic,将写入的内容从用户空间拷贝到内核空间
- 调用 address_space 的 write_end 完成写操作
- 调用 balance_dirty_pages_ratelimited,看脏页是否太多,需要写入硬盘中。脏页的意思是文件内容已写入缓存中,但还没写入硬盘中的页面
下面我们依次来看这四个步骤
static const struct address_space_operations ext4_aops = {.......write_begin = ext4_write_begin,.write_end = ext4_write_end,
......
}
第一步,对于 ext4 文件系统来讲,调用的是 ext4_write_begin
ext4 是一个日志系统,为了防止突然掉电的时候的数据丢失,引入了日志(journal)模式。日志文件系统比非日志文件系统多了一个 journal 区域。文件在 ext4 文件系统中分两部分存储,一部分是文件的元数据,另一部是数据。元数据和数据的操作日志 journal 也是分开管理。可以在挂载 ext4 文件系统的时候,使用 journal 模式。这种模式必须在将数据写入文件系统中,必须等待元数据和数据的日志已经落盘才能发挥作用。这样的性能比较差,但是是最安全的
另一种模式是 order模式。这种模式不记录数据的日志,只记录元数据的日志,但是在写元数据日志前,必须先确保数据已经落盘。这个是折中的方式,也是默认的模式
还有一种模式是 writeback模式,不记录数据的日志,只记录元数据的日志,但是不确保数据比元数据的日志先落盘。这个性能是最好的,但是也是最不安全的
在 ext4_write_begin 中,可以看到对于 ext4_journal_strat 的调用,就是做日志相关的工作
在 ext4_write_begin 中还做了另一件重要的事,就是调用 grab_cache_page_write_begin,来得到应该写入的缓存页,其定义如下
struct page *grab_cache_page_write_begin(struct address_space *mapping,pgoff_t index, unsigned flags)
{struct page *page;int fgp_flags = FGP_LOCK|FGP_WRITE|FGP_CREAT;page = pagecache_get_page(mapping, index, fgp_flags,mapping_gfp_mask(mapping));if (page)wait_for_stable_page(page);return page;
}
在内核中,缓存以页为单位存放在内存中,那我们如何知道,一个文件哪些数据被存放在缓存中呢?每一个打开的文件都有一个 struct file 结构,每一个 struct file 结构中都有一个 struct address_space 用于关联文件和内存,就是在这个结构中,有一棵树,用于保存所有于这个文件相关的缓存页
我们查找的时候,往往根据文件中的偏移值找到相应的缓存页,而基数树 radix tree 这种数据结构能快速根据长整型找到其相应的对象,因而这里缓存页就放在 radix 基数树里面
struct address_space {struct inode *host; /* owner: inode, block_device */struct radix_tree_root page_tree; /* radix tree of all pages */spinlock_t tree_lock; /* and lock protecting it */
......
}
pagecache_get_page 根据 pgoff_t index 这个长整型,在这棵树里面找到缓存页,如果找不到就创建一个缓存页
第二步,调用 iov_iter_copy_from_user_atomic,其定义如下
size_t iov_iter_copy_from_user_atomic(struct page *page,struct iov_iter *i, unsigned long offset, size_t bytes)
{char *kaddr = kmap_atomic(page), *p = kaddr + offset;iterate_all_kinds(i, bytes, v,copyin((p += v.iov_len) - v.iov_len, v.iov_base, v.iov_len),memcpy_from_page((p += v.bv_len) - v.bv_len, v.bv_page,v.bv_offset, v.bv_len),memcpy((p += v.iov_len) - v.iov_len, v.iov_base, v.iov_len))kunmap_atomic(kaddr);return bytes;
}
先将分配好的物理页面通过 kmap_atomic 临时映射到内核虚拟地址空间,然后将用户空间的数据拷贝到内核空间,之后再使用 kunmap_atomic 取消临时映射
第三步,调用 ext4_write_end 完成写入。这里会调用 ext4_journal_stop 完成日志的写入,会调用 block_write_end -> __block_commit_write -> mark_buffer_dirty,将修改过的缓存标记为脏页。可见,这里的写入并不是真正的写入硬盘,而是写入缓存中,然后标记相应的缓存页为脏页
第四步,调用 balance_dirty_pages_ratelimited,是回写脏页的一个时机
/*** balance_dirty_pages_ratelimited - balance dirty memory state* @mapping: address_space which was dirtied** Processes which are dirtying memory should call in here once for each page* which was newly dirtied. The function will periodically check the system's* dirty state and will initiate writeback if needed.*/
void balance_dirty_pages_ratelimited(struct address_space *mapping)
{struct inode *inode = mapping->host;struct backing_dev_info *bdi = inode_to_bdi(inode);struct bdi_writeback *wb = NULL;int ratelimit;
......if (unlikely(current->nr_dirtied >= ratelimit))balance_dirty_pages(mapping, wb, current->nr_dirtied);
......
}
balance_dirty_pages_ratelimited 发现脏页数量大于限制值,就调用 balance_dirty_pages -> wb_start_background_writeback,启动一个背后线程开始回写,这只是回写的一种场景,下面几种场景也会触发回写
- 用户主动调用 sync,将缓存刷到硬盘中,最终会调用 wakeup_flusher_threads,同步脏页
- 当内存十分紧张时,以至于无法分配页面的时候,会调用 free_more_memory,最终会调用 wakeup_flusher_threads,释放脏页
- 脏页已经更新一定时间了,时间超过了 timer,需要及时回写脏页,保持缓存于硬盘上的内容一致
带缓存的读操作
带缓存的读相对来说简单一些,其对应的函数为 generic_file_buffered_read,定义如下
static ssize_t generic_file_buffered_read(struct kiocb *iocb,struct iov_iter *iter, ssize_t written)
{struct file *filp = iocb->ki_filp;struct address_space *mapping = filp->f_mapping;struct inode *inode = mapping->host;for (;;) {struct page *page;pgoff_t end_index;loff_t isize;page = find_get_page(mapping, index);if (!page) {if (iocb->ki_flags & IOCB_NOWAIT)goto would_block;page_cache_sync_readahead(mapping,ra, filp,index, last_index - index);page = find_get_page(mapping, index);if (unlikely(page == NULL))goto no_cached_page;}if (PageReadahead(page)) {page_cache_async_readahead(mapping,ra, filp, page,index, last_index - index);}/** Ok, we have the page, and it's up-to-date, so* now we can copy it to user space...*/ret = copy_page_to_iter(page, offset, nr, iter);}
}
在 generic_file_buffered_read 中,首先查找缓存中是否存在相应的缓存页,如果没有找到,不但需要读取这一页的内容,还需要进行预读,这些在 page_cache_sync_readahead 中实现。然后再查找一篇相应的缓存页,这是应该可以找到了
如果缓存中可以找到相应的缓存页,这时候还需要判断是否需要进行预读
最后调用 copy_page_to_iter,将缓存页的内容拷贝回用户内存空间
三、总结
read 和 write 系统调用分别调用 vfs_read 和 vfs_write,然后再调用 f_op 中 的 read 和 write,对于 ext4 文件系统,调用的就是 ext4_file_read_iter 和 ext4_file_write_iter
读和写都分为直接I/O和缓存I/O
对于直接I/O,读和写都是调用 address_apace 中的 direct_IO,对于 ext4 文件系统,对应的就是 ext4_direct_IO,然后直接访问块设备驱动
对于缓存I/O,如果是读,那么就在缓存中查找相应的页,然后拷贝回用户空间。如果是写,那么就将数据写到相应的缓存页,然后标记页为脏,等待适当的时机内核线程进行回写
Linux文件系统(四)文件缓存相关推荐
- linux 更改ctime_Linux 的文件系统及文件缓存知识点整理
Linux的文件系统特点 文件系统要有严格的组织形式,使得文件能够以块为单位进行存储. 文件系统中也要有索引区,用来方便查找一个文件分成的多个块都存放在了什么位置. 如果文件系统中有的文件是热点文件, ...
- Linux的文件系统及文件缓存知识点整理
Table of Contents Linux的文件系统 文件系统的特点 ext系列的文件系统的格式 inode与块的存储 inode位图和块位图 文件系统的格式 目录的存储格式 Linux中的文件缓 ...
- _Linux 的文件系统及文件缓存知识点整理
Linux的文件系统特点 文件系统要有严格的组织形式,使得文件能够以块为单位进行存储. 文件系统中也要有索引区,用来方便查找一个文件分成的多个块都存放在了什么位置. 如果文件系统中有的文件是热点文件, ...
- Linux文件系统及文件储存方式
前言 Linux文件系统构成 文件式的文件结构 Linux的一个具体文件 系统对文件的访问方式 Linux系统的删除方式 shred与rm的区别 rm删除文件的恢复 前言 闲来无事复习了下Linux文 ...
- 嵌入式 Linux 入门(二、Linux 文件系统、文件类型及权限管理)
嵌入式 Linux入 门第二课, linux 文件系统.文件类型及权限管理. ...... 矜辰所致 目录 前言 一.Linux 文件属性 1.1 Linux 文件类型 1.2 Linux 文件权限及 ...
- Linux文件系统及文件储存方式【转】
本文转载自:https://blog.csdn.net/qyp199312/article/details/54927843 前言 Linux文件系统构成 文件式的文件结构 Linux的一个具体文件 ...
- Spark修炼之道(基础篇)——Linux大数据开发基础:第二节:Linux文件系统、文件夹(一)...
本节主要内容 怎样获取帮助文档 Linux文件系统简单介绍 文件夹操作 訪问权限 1. 怎样获取帮助文档 在实际工作过程其中,常常会忘记命令的使用方式.比如ls命令后面能够跟哪些參数,此时能够使用ma ...
- Linux 文件系统类型 文件系统结构 与Windows文件系统的比较
Linux 文件系统类型 磁盘文件系统. 包括硬盘.CD-ROM.DVD.USB存储器.磁盘阵列等.常见文件系统格式有:autofs.coda.Ext(Extended File sytem,扩展文件 ...
- linux文件系统、文件系统结构、虚拟文件系统
参考:linux文件系统及其目录结构.虚拟文件系统 作者:丶PURSUING 发布时间: 2021-02-15 09:33:29 网址:https://blog.csdn.net/weixin_447 ...
- linux文件系统中文件基本权限,Linux文件权限基本属性图文详解
[概述] 在 Linux 系统中,不同的用户处于不同的地位,拥有不同的权限,为保护系统安全性,Linux 系统对不同的用户访问同一文件(包括目录文件)的权限做了不同的规定. Linux 系统的每一个文 ...
最新文章
- 苹果开发之Cocoa编程(原书第4版)
- 构建单层单向RNN网络对MNIST数据集分类
- 网络基础---IP编址
- php 新浪ip接口,php利用新浪接口查询ip获取地理位置示例
- deepin允许root登录_王者荣耀安卓免ROOT不用电脑修改战区2020最新版教程
- Java创建多线程的方法总结
- IOUtils快速进行内容复制与常用方法
- 苹果手机android解锁,解锁教程:Android和iOS手机锁屏密码忘了怎么办?
- 微信文章数据 API数据接口
- CyanogenMod
- element-plus的el-date-picker中value-format属性失效以及只选择到时分值
- 计算机实验室主要工作业绩范文,实验室工作总结
- VS2012 msvcr110d.dll xxxxxx处有未经处理的异常:0xC0000005:写入位置xxxxxx时发生访问冲突(scanf_s引起)-已解决
- 华为路由交换堆叠(通过堆叠卡)
- java.lang.IllegalArgumentException异常
- 红黑树调整(漫画版)
- CASIA-OLHWDB2.0-2.2数据集wptt文件解析
- 总结十三:外在认知比实际行动更重要
- Markdown使用方法、常用技巧汇总
- 自学考试英语二(第二讲)
热门文章
- 音频合并无缝衔接怎么弄?这篇文章教会你
- 呼声很高的这门语言真的难学吗?
- 《定时执行专家》更新日志 - 5.5.0.0(300.16052021)版 - boom 2021-5-16
- sd卡数据恢复:sd卡损坏这样修复数据
- 历年计算机一级考试原题,全国计算机等级考试一级历年上机真题.doc
- 7-6 日K蜡烛图 (15 分)
- 从数据库从取出密码和前台输入的密码用equals作比较总是返回FALSE
- 股票量化分析工具QTYX使用攻略——形态选股叠加业绩报告
- C/C++实习面试(一)
- 微服务领域是不是要变天了?Spring Cloud Alibaba正式入驻Spring Cloud官方孵化器!