文件I/O的内核缓冲
本文转载自文件 I/O 的内核缓冲
导语
从最粗略的角度理解 Linux 文件 I/O 内核缓冲(buffer cache),啰嗦且不严谨。只为了直观理解。
当我们说一个程序读写磁盘上的文件时,通常指的是把磁盘设备上的数据块存储到用户空间内存中(或把用户空间内存的数据存储到磁盘设备上)。
然而,程序与硬件的交互是交由操作系统内核来处理的,这样做的好处是,一方面可以为应用程序提供简单统一的接口,降低用户与硬件交互的复杂度;另一方面也提高了与硬件交互的安全性。
因此,上图的模型在用户空间与磁盘之间多了一层:内核空间。
于是,一个读文件操作变成了:
操作系统(内核)先从磁盘读取数据到内核空间的内存(read①),再把数据从内核空间内存拷贝到用户空间内存(read②)。此后,用户应用程序才可以操作此数据。
在这个过程中有两次数据读取操作:
read① 从磁盘上读取;
read② 从内存中读取。
我们知道,访问磁盘的速度要远远低于访问内存的速度,这是不同存储介质的物理特性和访问方式决定的,两者是毫秒和纳秒的区别,所以理论上 read① 的速度要远远慢于 read②。那么整个文件读取过程的时间瓶颈就出现在了对磁盘的读取上。要解决这个问题,就用上了 缓冲(buffer)。
什么是缓冲
由于中文翻译的问题,我们有时候会把字面上相似,但实际差别较大的两个东西混为一谈,比如缓冲和缓存,再比如伪类和伪元素。
缓冲(buffer),简单来说是为了解决速度不均匀的问题,而在生产和消费者之间设立的一个缓和区、平衡区。
比如我们经常在看在线视频时,开始会提示一小段时间的「缓冲」,这是因为视频播放要求均匀、持续的数据,而网络传输是时快时慢的,此时缓冲的作用就是等待生产者(网络)积累一定的数据后才给消费者去消费。这是生产者速度追不上消费者的情况。
再比如汽车安全气囊,当汽车速度骤降时,人体会受到方向盘冲击,而安全气囊就是一个对人体向前速度的缓冲,以减少身体接触方向盘时的速度。这是生产者速度大于消费者的情况。
由于我们将要关注的「内核缓冲区告诉缓存」既是一种缓冲机制,又发挥缓存的作用,所以这里特别容易搞糊涂 T T
内核缓冲
回到刚才的读文件场景。因为内核从磁盘读取数据的速度太慢,跟不上程序从内存中读数据的速度,所以它也设立了一个缓冲区,用来中和两者的速度差异。这就是文件 I/O 的内核缓冲,原名叫 Kernel Buffer Cache,一般翻译成「缓冲区高速缓存」(又是缓冲又是缓存的……我们先按缓冲去理解它的作用)。
它本质上就是图二中内核空间的一块内存,是读取过程中绕不开的中转站,只不过为了读写效率做了特别的工作,所以起了个特殊的名字。
那么内核缓冲做了什么事情呢?
- 读:数据预读
- 写:延时回写
数据预读(read_ahead)
数据预读指的是,当程序发起 read() 系统调用时,内核会比请求更多地读取磁盘上的数据,保存在缓冲区,以备程序后续使用。这种数据的预取基于一种预设:程序会重复地访问最近访问过的数据,且这种访问往往是顺序访问(比如对文件从前到后的顺序访问)。
因此当我们向内核请求读取数据时,内核会先从自己的 buffer cache 去寻找,如果命中数据,则不需要进行真正的磁盘 I/O,直接从内存中返回数据;如果缓存未命中,则内核会从磁盘中读取请求的 page,并同时读取紧随其后的几个 page(比如三个),如果文件是顺序访问的,那么下一个读取请求就会命中之前预读的缓存。
当然,预读算法非常复杂,这里只是一个简化的逻辑。当内核判断文件并非顺序读取时,也可能会放弃预读。
因此,预读提供了以下好处:
- 减少了 I/O 时间对进程的影响。 因为进程的读取操作和真正的 I/O 可能发生在不同的时空,数据是预取的,当进程需要它的时候早已经在内存中准备好了,对于这个进程来说,I/O 时间是不存在的,但是对于整个系统来说,I/O 时间是一个必要成本,因为总要从磁盘读数据,只是发生的时间早晚罢了;
- 提供了缓存。 当进程对文件重复访问时,buffer cache 提供了缓存,把本来应该发生的 I/O 省掉了,这个和第一点不同,是结结实实得省掉了一次 I/O 时间;
- 减少了磁盘处理器的命令数,因为每个命令多读了几个相邻扇区,或者说,把小块的 I/O 变成了大块的 I/O,提升了磁盘性能;
回写
回写指的是,当程序发起 write() 系统调用时,内核并不会直接把数据写入到磁盘文件中,而仅仅是写入到缓冲区中,几秒后才会真正将数据刷新到磁盘中。对于系统调用来说,数据写入缓冲区后,就返回了,因此一个 read() / write() 并非真正执行 I/O 操作,它只代表数据在用户空间 / 内核空间传递的完成。
延迟往磁盘写入数据的一个最大的好处就是,可以合并更多的数据一次性写入磁盘。也就是上面说的,把小块的 I/O 变成大块 I/O,减少磁盘处理命令次数,提高提盘性能。
另一个好处是,当其它进程紧接着访问该文件时,内核可以从直接从缓冲区中提供更新的文件数据。
因此,Linux 内核以 buffer cache 为介质,通过预读和回写的机制,提高了文件 I/O 速度,和磁盘访问效率。
内核缓冲区有多大?
Linux 内核对 buffer cache 并没有设定固定的大小上限,内核会分配尽可能多的内存给 buffer cache,它只受到可用内存总量限制。当可用内存不足,内核会将脏页(修改过的缓存)回写入磁盘,预读算法也有相应的策略去回收不必要的缓存。
示例
我们通过一个简单程序的过程图来对内核缓冲做一个粗略的演示:
下图是一个复制程序,把文件 A 的内容拷贝到文件 B,过程简化了很多,只列出了关注点。
用户的缓冲:C 库 I/O 缓冲
由于缓冲可以将小块 I/O 合并成大块 I/O 操作这一特性,C 库的 I/O 函数(比如 stdio 库中的 fpringf()、fgets()、fputs()等)也提供了缓冲机制。
我们知道,这些库函数是用户程序和 read()、write() 这些 system call 之间的桥梁,它的 buffer 并不会对磁盘读写产生什么影响,而在于可以减少系统调用的开销。
系统调用的开销大吗?可以看下面一张图:
这是《The Linux Programming Interface》展示的一个系统调用 execve() 的执行过程,它还是需要蛮多的步骤,内核必须捕获调用、检查调用参数的有效性、在内核态和用户态之间传输数据。
虽然单个系统调用开销看起来并不巨大,但是试想一个场景:向磁盘写入 1000 字节,是写入 1000 次,每次写入 1 字节呢,还是一次性写入 1000 字节比较好呢。这两者的一个最大差别就是前者需要调用 write() 系统调用 1000 次,而后者只需要调用一次。那么累积起来,系统调用的消耗也是可观的。
上面书中有一个对比,复制 100MB 的数据,设立一个缓冲区,缓冲区大小为 1 字节的用时为 107.43 秒(total cpu 107.32 / user cpu 8.20 / sys cpu 99.12),而当缓冲区大小为 4096 字节时,这个成绩是 2.05 秒(total cpu 0.38 / user cpu 0.01 / sys cpu 0.38)(缓冲区再大性能提升就不显著了,因为 anyway 读写 I/O 的时间和用户空间、内核空间拷贝数据的时间开销是大头,少量的系统调用的开销对比起来就微乎其微了)。
因此,在用户这边,对 I/O 操作也设置一个缓冲区以减少系统调用,也是一个不错的选择。
当然,缓冲是一种策略,一种解决问题的方式,只是 Linux kernel、glibc 为解决特定的问题实现了它。我们用户在编写程序解决问题的时候,也可以将这种思想作为一种选择。
文件I/O的内核缓冲的更多相关文章
- java 文件字节和字符流 缓冲流
流的原理 1) 在 Java 程序中,对于数据的输入/输出操作以“流”(stream) 方式进行:2) J2SDK 提供了各种各样的“流”类,用以获取不同种类的数据:程序中通过标准的方法输入或输出数据 ...
- UNIX环境编程学习笔记(3)——文件I/O之内核 I/O 数据结构
lienhua342014-08-27 内核使用三种数据结构表示打开的文件,分别是文件描述符表.文件表和 V 节点表. (1) 每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述符表,每 ...
- linux文件IO操作篇 (二) 缓冲文件
2. 缓冲文件操作 //规模较大 实时性低的文件 //当数据长度快要超过缓冲区的范围时,或者时间周期达到时,数据才被送往指定位置 //需要使用FILE * 作为文件标识符 //stdin 标准输入 / ...
- JS调用activeX实现浏览本地文件夹功能 wekit内核只需要<input type="file" id="files" name="files[]" webkitdirectory/>即可,IE内核比较麻烦
研究了一天,js访问本地文件本身是不可能的,只能借助于插件.植入正题,IE仅支持ActiveX插件. function openDialog() { try { var Message = " ...
- mmap - 内存映射文件 - 减少一次内核空间内数据向用户空间数据拷贝的操作
关于mmap 网上有很多有用的文章,我这里主要记录,日常使用到mmap时的理解: https://www.cnblogs.com/huxiao-tee/p/4660352.html 测试代码: htt ...
- Nginx 文件传输效率、实时、压缩配置指令
# sendfile 开启文件高效传输模式 # 默认值:off # 位置:http.servcer.location-- # 开启和不开启worker访问的文件发送到浏览器的过程不同. # 不开启的时 ...
- 文件I/O(不带缓冲)概述
一.引言 UNIX系统中大多数文件I/O只需用到5个函数:open.read.write.lseek以及close.这些函数经常被称为不带缓冲的I/O(unbuffered I/O).术语不带缓冲指的 ...
- /var/log目录下的20个Linux日志文件功能详解 分类: 服务器搭建 linux内核 Raspberry Pi 2015-03-27 19:15 80人阅读 评论(0) 收藏
如果愿意在Linux环境方面花费些时间,首先就应该知道日志文件的所在位置以及它们包含的内容.在系统运行正常的情况下学习了解这些不同的日志文件有助于你在遇到紧急情况时从容找出问题并加以解决. 以下介绍的 ...
- 文件I/0缓冲
设置stdio流缓冲模式 #include<stdio.h> int setvbuf(FILE *stream,char *buf,int mode,size_t size) int se ...
随机推荐
- Prometheus—告警altermanger
Prometheus-告警altermanger 1.告警altermanger装配 2.告警Mysql 3.Prometheus针对nodes告警规则配置 相关内容原文地址链接: 51CTO:wfw ...
- Java——I/O,字节流与字符流,BufferedOutputStream,InputStream等(附相关练习代码)
I/O: I/O是什么? 在程序中,所有的数据都是以流的形式进行传输或者保存. 程序需要数据的时候,就要使用输入流读取数据. 程序需要保存数据的时候,就要使用输出流来完成. 程序的输入以及输出都是以流 ...
- mapreduce编程练习(一)简单的练习 WordCount
入门训练:WordCount 问题描述:对一个或多个输入文件中的单词进行计数统计,比如一个文件的输入文件如下 输出格式: 运行代码实例: package hadoopLearn; import jav ...
- 软件测试漫谈(web测试,自动化测试,Jmeter)
软件测试,就是一个过程或一系列过程,用来确定计算机代码完成了其应该完成的功能不执行其不该有的操作. 简单说就是找bug的过程. 测试分类 (1)按测试方式分类:静态测试.动态测试 (2) 按测试方法分 ...
- I - I(Highways)
N个点,给你N个点的坐标,现在还有Q条边已经连接好了.问把N个点怎么连接起来的花费的距离最短? The island nation of Flatopia is perfectly flat. Unf ...
- 2020Nowcode多校 Round5 C. Easy
C. Easy 构造两个序列分别要满足 \(\sum_{i=1}^{k} a_{i} = N\) \(\sum_{i=1}^{k} b_{i} = M\) 一种方案能贡献\(\prod_{i=1}^{ ...
- Java 窗口 绘制图形 #1
写在前面: editplus换成eclipse了 Sketchpad要钱,买不起 自己搞(rua) by emeralddarkness 建立了一个平面直角坐标系 两个变元x,y,参数i 实现了以下功 ...
- 郁闷的出纳员 HYSBZ - 1503
OIER公司是一家大型专业化软件公司,有着数以万计的员工.作为一名出纳员,我的任务之一便是统计每位员工的 工资.这本来是一份不错的工作,但是令人郁闷的是,我们的老板反复无常,经常调整员工的工资.如果他 ...
- 02、Scrapy 安装、目录结构及启动
1.从豆瓣源去快速安装Scrapy开发环境 C:\Users\licl11092>pip install -i https://pypi.douban.com/simple/ scrapy 2. ...
- MongoDB 部署 & 基础命令
MongoDB 官方文档 MongoDB 介绍 Mongodb 由 C++ 语言编写的,是一个基于分布式文件存储的开源数据库系统. 是专为可扩展性,高性能和高可用性而设计的数据库, 是非关系型数据库中 ...