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文件系统(四)文件缓存相关推荐

  1. linux 更改ctime_Linux 的文件系统及文件缓存知识点整理

    Linux的文件系统特点 文件系统要有严格的组织形式,使得文件能够以块为单位进行存储. 文件系统中也要有索引区,用来方便查找一个文件分成的多个块都存放在了什么位置. 如果文件系统中有的文件是热点文件, ...

  2. Linux的文件系统及文件缓存知识点整理

    Table of Contents Linux的文件系统 文件系统的特点 ext系列的文件系统的格式 inode与块的存储 inode位图和块位图 文件系统的格式 目录的存储格式 Linux中的文件缓 ...

  3. _Linux 的文件系统及文件缓存知识点整理

    Linux的文件系统特点 文件系统要有严格的组织形式,使得文件能够以块为单位进行存储. 文件系统中也要有索引区,用来方便查找一个文件分成的多个块都存放在了什么位置. 如果文件系统中有的文件是热点文件, ...

  4. Linux文件系统及文件储存方式

    前言 Linux文件系统构成 文件式的文件结构 Linux的一个具体文件 系统对文件的访问方式 Linux系统的删除方式 shred与rm的区别 rm删除文件的恢复 前言 闲来无事复习了下Linux文 ...

  5. 嵌入式 Linux 入门(二、Linux 文件系统、文件类型及权限管理)

    嵌入式 Linux入 门第二课, linux 文件系统.文件类型及权限管理. ...... 矜辰所致 目录 前言 一.Linux 文件属性 1.1 Linux 文件类型 1.2 Linux 文件权限及 ...

  6. Linux文件系统及文件储存方式【转】

    本文转载自:https://blog.csdn.net/qyp199312/article/details/54927843 前言 Linux文件系统构成 文件式的文件结构 Linux的一个具体文件 ...

  7. Spark修炼之道(基础篇)——Linux大数据开发基础:第二节:Linux文件系统、文件夹(一)...

    本节主要内容 怎样获取帮助文档 Linux文件系统简单介绍 文件夹操作 訪问权限 1. 怎样获取帮助文档 在实际工作过程其中,常常会忘记命令的使用方式.比如ls命令后面能够跟哪些參数,此时能够使用ma ...

  8. Linux 文件系统类型 文件系统结构 与Windows文件系统的比较

    Linux 文件系统类型 磁盘文件系统. 包括硬盘.CD-ROM.DVD.USB存储器.磁盘阵列等.常见文件系统格式有:autofs.coda.Ext(Extended File sytem,扩展文件 ...

  9. linux文件系统、文件系统结构、虚拟文件系统

    参考:linux文件系统及其目录结构.虚拟文件系统 作者:丶PURSUING 发布时间: 2021-02-15 09:33:29 网址:https://blog.csdn.net/weixin_447 ...

  10. linux文件系统中文件基本权限,Linux文件权限基本属性图文详解

    [概述] 在 Linux 系统中,不同的用户处于不同的地位,拥有不同的权限,为保护系统安全性,Linux 系统对不同的用户访问同一文件(包括目录文件)的权限做了不同的规定. Linux 系统的每一个文 ...

最新文章

  1. 苹果开发之Cocoa编程(原书第4版)
  2. 构建单层单向RNN网络对MNIST数据集分类
  3. 网络基础---IP编址
  4. php 新浪ip接口,php利用新浪接口查询ip获取地理位置示例
  5. deepin允许root登录_王者荣耀安卓免ROOT不用电脑修改战区2020最新版教程
  6. Java创建多线程的方法总结
  7. IOUtils快速进行内容复制与常用方法
  8. 苹果手机android解锁,解锁教程:Android和iOS手机锁屏密码忘了怎么办?
  9. 微信文章数据 API数据接口
  10. CyanogenMod
  11. element-plus的el-date-picker中value-format属性失效以及只选择到时分值
  12. 计算机实验室主要工作业绩范文,实验室工作总结
  13. VS2012 msvcr110d.dll xxxxxx处有未经处理的异常:0xC0000005:写入位置xxxxxx时发生访问冲突(scanf_s引起)-已解决
  14. 华为路由交换堆叠(通过堆叠卡)
  15. java.lang.IllegalArgumentException异常
  16. 红黑树调整(漫画版)
  17. CASIA-OLHWDB2.0-2.2数据集wptt文件解析
  18. 总结十三:外在认知比实际行动更重要
  19. Markdown使用方法、常用技巧汇总
  20. 自学考试英语二(第二讲)

热门文章

  1. 音频合并无缝衔接怎么弄?这篇文章教会你
  2. 呼声很高的这门语言真的难学吗?
  3. 《定时执行专家》更新日志 - 5.5.0.0(300.16052021)版 - boom 2021-5-16
  4. sd卡数据恢复:sd卡损坏这样修复数据
  5. 历年计算机一级考试原题,全国计算机等级考试一级历年上机真题.doc
  6. 7-6 日K蜡烛图 (15 分)
  7. 从数据库从取出密码和前台输入的密码用equals作比较总是返回FALSE
  8. 股票量化分析工具QTYX使用攻略——形态选股叠加业绩报告
  9. C/C++实习面试(一)
  10. 微服务领域是不是要变天了?Spring Cloud Alibaba正式入驻Spring Cloud官方孵化器!