IO之内核buffer----"buffer cache"

本文深入探讨了操作系统中文件缓存的工作原理,包括buffercache的作用、读写操作的内部流程及优化方法。此外,还讨论了如何精确控制缓存刷新以确保数据安全性和提升性能。

举例

一般情况下,Read,write系统调用并不直接访问磁盘。这两个系统调用仅仅是在用户空间和内核空间的buffer之间传递目标数据。

举个例子,下面的write系统调用仅仅是把3个字节从用户空间拷贝到内核空间的buffer之后就直接返回了
write(fd,”abc”,3);
在以后的某个时间点上,内核把装着“abc”三个字节的buffer写入(flush)磁盘……
如果另外的进程在这个过程中想要读刚才被打开写的那个文件怎么办?答案是:内核会从刚才的buffer提供要读取的数据,而不是从磁盘读。

"buffer cache"

要理解"buffer cache"这个东西,需要澄清一下概念:
一般情况下,进程在io的时候,要依赖于内核中的一个buffer模块来和外存发生数据交换行为。另一个角度来说,数据从应用进程自己的buffer流动到外存,中间要先拷贝到内核的buffer中,然后再由内核决定什么时候把这些载有数据的内核buffer写出到外存。
"buffer cache"仅仅被内核用于常规文件(磁盘文件)的I/O操作。

介绍完“写出”,该介绍“读入”了。

当前系统上第一次读一个文件时,Read系统调用触发内核以block为单位从磁盘读取文件数据,并把数据blocks存入内核buffer,然后read不断地从这个buffer取需要的数据,直到buffer中的数据全部被读完,接下来,内核从磁盘按顺序把当前文件后面的blocks再读入内核buffer,然后read重复之前的动作…
一般的文件访问,都是这种不断的顺序读取的行为,为了加速应用程序读磁盘,unix的设计者们为这种普遍的顺序读取行为,设计了这样的机制----预读,来保证进程在想读后续数据的时候,这些后续数据已经的由内核预先从磁盘读好并且放在buffer里了。这么做的原因是磁盘的io访问比内存的io访问要慢很多,指数级的差别。

read,write从语义和概念上来说,本来是必须要直接和磁盘交互的,调用时间非常长,应用每次在使用这两个系统的时候,从表象上来说都是被卡住。而有了这些buffer,这些系统调用就直接和buffer交互就可以了,大幅的加速了应用执行。

内存上buffer cache多大?

Linux内核并没有规定"buffer cache"的尺寸上线,原则上来说,除了系统正常运行所必需和用户进程自身所必需的之外的内存都可以被"buffer cache"使用。而系统和用户进程需要申请更多的内存的时候,"buffer cache"的内存释放行为会被触发,一些长久未被读取,以及被写过的脏页就会被释放和写入磁盘,腾出内存,以便被需要的行为方使用。

何时内存上的buffer cache上的数据flush到磁盘上?

现在大体上你们已经知道了吧,"buffer cache"有五个flush的触发点:

  1. pdflush(内核线程)定期flush;
  2. 系统和其他进程需要内存的时候触发它flush;
  3. 用户手工sync,外部命令触发它flush;
  4. proc内核接口触发flush,"echo 3 >/proc/sys/vm/drop_caches;
  5. 应用程序内部控制flush。

这个"buffer cache"从概念上的理解就是这些了,实际上,更准确的说,linux从2.4开始就不再维护独立的"buffer cache"模块了,而是把它的功能并入了"page cache"这个内存管理的子系统了,"buffer cache"现在已经是一个unix系统族的普遍的历史概念了。

高性能写文件

写100MB的数据
场景1,1次写1个字节,总共write 100M次;
场景2,1次写1K个字节,总共write 100K次;
场景3,1次写4K个字节,总共write 25K次;
场景4,1次写16k个字节,总共write大约不到7K次。
以上4种写入方式,内核写磁盘的次数基本相同,因为写磁盘的单位是block,而不是字节。现在的系统默认的block都是4k。
第1种性能非常差,user time和system time执行时间都很长,既然写盘次数都差不多,那他慢在哪儿呢?答案是系统调用的次数太多
第2种,user time和system time都显著降低,不过system time降低幅度更大
第2种以后,性能差别就不是很高了,第3种和第4种性能几乎一样
有兴趣的朋友可以试一试,如果你的服务器很好,可以适当放大测试样本。
总而言之,得出的结论是以block的尺寸为write(fd, sizeof(buf),buf)的调用单位就可以了,再大对性能也没什么太大的提高。

题外话:一个衡量涉及IO程序的好坏的粗略标准

题外话:一个衡量涉及IO程序的好坏的粗略标准是“程序运行应该尽量集中在user time,避免大量的system time”以及“IO的时候肯定是需要一些应用层buf的,比如上述4个场景,匹配就可以了(比如场景3,场景1和场景2会导致系统调用次数太多,场景4使用的buf尺寸过于浪费)”
每个系统调用在返回的时候,会有一个从内核态向用户态切换的间隙,每次在这个间隙里面,系统要干两个事情----递送信号和进程调度,其中进程调度会重新计算全部RUN状态进程的优先级。
系统调用太多的话,递送信号和进程调度引起的计算量是不容忽视的。

精确地flush "buffer cache"

在很多业务场景下,我们仅仅调用write()把需要写盘的数据推送至内核的"buffer cache"中,这是很不负责任的。或许我们应该不断地频繁地把"buffer cache"中的数据强制flush到磁盘,尽最大可能保证我们的业务数据尽量不因断电而丢失。
天下没有免费的午餐,既想要效率(写入内核buffer),又想要安全性(数据必须flush到外存介质中才安全),这似乎是很矛盾的。SUSv3(Single UNIX Specification Version 3)给了这种需求一个折中的解决方案,让OS尽量满足我们的苛刻的要求。介绍这个折中方案之前,有两个SUSv3提案的规范很重要,说明如下:
1.数据完整性同步(synchronized I/O data integrity)
一个常规文件所包含的信息有两种:文件元数据和文件内容数据。
文件元数据包括:文件所属用户、组、访问权限,文件尺寸,文件硬连接数目,最后访问时间戳,最后修改时间戳,最后文件元数据修改时间戳,文件数据块指针。
对于文件内容数据,大家应该都很清楚是什么东西。
对于写操作,这个规范规定了,写文件时保证文件内容数据和必要的文件元数据保持完整性即可。粗糙地举个例子来解释这个规范,某次flush内核中的数据到磁盘的时候,仅仅把文件内容数据写入磁盘即可,但是如果这次写文件导致了文件尺寸的变化,那么这个文件尺寸作为文件的元数据也需要被写入磁盘,必要信息保持同步。而其他的文件元数据,例如修改时间,访问时间一概略去,不需要同步。
2.文件完整性同步(synchronized I/O file integrity)
相对于数据完整性同步而言,这个规范规定了,所有内容数据以及元数据都要同步。

下面来介绍linux提供的几种flush内核缓冲数据的几种方案

相信看完之后,大家应该知道上述提及的折中方案是怎样的

  1. int fsync(int fd);
    文件完整性同步;
  2. int fdatasync(int fd);
    数据完整性同步。
    fdatasync相对于fsync的意义在于,fdatasync大致仅需要一次磁盘操作,而fsync需要两次磁盘操作。举例说明一下,假如文件内容改变了,但是文件尺寸并没有发生变化,那调用fdatasync仅仅是把文件内容数据flush到磁盘,而fsync不仅仅把文件内容flush刷入磁盘,还要把文件的last modified time也同步到磁盘文件系统。last modified time属于文件的元数据,一般情况下文件的元数据和文件内容数据在磁盘上不是连续存放的,写完内容数据再写元数据,必然涉及到磁盘的seek,而seek又是机械硬盘速度慢的根源。。。
    在某些业务场景下,fdatasync和fsync的这点微小差别会导致应用程序性能的大幅差异。
  3. sync_file_range()
    这个接口是linux从2.6.17之后实现的,是linux独有的非标准接口。这个接口提供了比fdatasync更为精准的flush数据的能力。详细请参照man。
  4. void sync(void);
    强制"buffer cache"中的数据全部flush到磁盘,并且要遵循文件完整性同步。
    上面4种方式介绍完毕,open()系统调用的打开文件的标志位,比如O_DSYNC诸如此类的标志,对flush数据的影响和上面几个接口作用类似。

预读

上面介绍了写buffer以及如何控制buffer的flush,下面来讲一讲如何控制读cache的行为。
读cache这一块,基本上,我们可以控制的就是文件的预读。
我们从POSIX规定的一个接口来论述一下如何控制文件的预读以及控制它的意义。接口原型如下:

#include
int posix_fadvise(int fd, off_t offset, off_t len, int advice);

fd:打开文件的描述符其实;
offset和len:指明文件区域;
advice:预读的方式。预读方式及其意义如下:

  1. POSIX_FADV_NORMAL:内核默认的预读方式;
  2. POSIX_FADV_RANDOM:内核禁用预读。适合随机读文件的业务,每次按业务要求的量读取数据,不多读;
  3. POSIX_FADV_SEQUENTIALP:内核把默认的预读量(POSIX_FADV_NORMAL)扩大一倍;
  4. POSIX_FADV_WILLNEED:读取出来的内容会被应用程序多次访问(就是应用程序会不断的调用read()对这些内容不断的读);
  5. POSIX_FADV_NOREUSE:读取出来的内容只会被应用程序访问一次,访问一次之后就清理掉并且释放内存。cache服务器,比如memcache或者redis启动时,把文件内容加载到应用层cache,就是这个参数存在的典型场景;
  6. POSIX_FADV_DONTNEED:应用程序后续不打算访问指定范围中的文件内容,内核从"page cache(buffer cache)"中删除指定范围的文件内容,释放内存。

对于POSIX_FADV_WILLNEED这种方式,linux自己有一个特定接口,原型如下:
ssize_t readahead(int fd, off64_t offset, size_t count);

linux的"buffer cache"默认预读128k。

实际上,OS全局控制"buffer cache"的操作接口不仅仅是上面提及的几种,/proc/sys/vm/目录下还有几个参数可以从其他一些方面来控制"buffer cache"的行为,这部分内容在之后我整理笔记之后会介绍。

转载于:https://www.cnblogs.com/muahao/p/5954372.html

<think>好的,用户想了解buffer I/O和direct I/O的区别。我需要先回忆这两种I/O模式的基本概念,然后整理它们的区别点。 首先,buffer I/O,也就是缓冲I/O,应该是利用操作系统的页缓存来进行的读写操作。当应用程序进行文件读写时,数据会先被复制到内核空间的缓冲区,然后再由操作系统决定何时写入磁盘。这样做的好处是可以减少实际的磁盘访问次数,提升性能,尤其是对于频繁读写的小数据量操作。不过,缺点就是数据的一致性问题,比如突然断电可能导致数据丢失,因为缓存中的数据还没写入磁盘。 然后是direct I/O,即直接I/O,绕过操作系统的缓存,直接和磁盘交互。数据直接从用户空间传输到磁盘,或者从磁盘读取到用户空间,不需要经过内核缓冲区。这样做的好处是避免了用户空间和内核空间之间的数据复制,适合处理大文件或者需要自己管理缓存的场景。但缺点就是可能会增加磁盘的I/O次数,影响性能,尤其是小数据量的读写。 接下来要对比它们的区别,可以从数据流向、缓存机制、性能特点、使用场景等方面展开。比如,数据流方面,buffer I/O需要经过内核缓冲区,而direct I/O直接到磁盘;缓存机制上,buffer I/O依赖系统缓存,direct I/O不适用;性能方面,buffer适合小文件高频,direct适合大文件或自管理缓存;使用场景比如数据库有时候会用direct I/O来避免双重缓存。 还要注意系统调用和内存使用方面的区别。buffer I/O可能会利用到系统的预读和延迟写策略,而direct I/O需要应用自己处理这些优化。内存方面,buffer I/O会增加内核内存的使用,而direct I/O则减少这部分开销,但可能增加用户空间的内存管理负担。 需要确保提到的例子准确,比如数据库系统如MySQL在特定配置下使用direct I/O。另外,应用选择哪种方式需要考虑具体需求,比如对数据一致性的要求高,可能倾向direct I/O,而追求高吞吐和小文件操作则用buffer I/O。 最后,检查是否有遗漏的关键点,比如数据一致性和持久性,以及API使用的不同。确保解释清晰,结构合理,用用户容易理解的语言,避免过于技术化的术语,但必要的术语需要保留并适当解释。</think>Buffer I/O(缓冲I/O)与Direct I/O(直接I/O)是两种不同的文件读写机制,心区别在于是否使用操作系统的**页缓存(Page Cache)**。以下是逐步对比分析: --- ### 1. **数据流向差异** - **Buffer I/O** 数据流:`应用程序 ↔ 用户空间缓冲区 ↔ 内核页缓存 ↔ 磁盘` 特点:所有读写操作**必须经过内核页缓存**。写入时先缓存到内存,后续由操作系统异步刷盘;读取时优先从缓存中获取数据。 - **Direct I/O** 数据流:`应用程序 ↔ 用户空间缓冲区 ↔ 磁盘` 特点:**绕过内核页缓存**,数据直接在用户空间和磁盘之间传输。需要应用程序自行管理缓存策略。 --- ### 2. **缓存机制** | 特性 | Buffer I/O | Direct I/O | |---------------------|-------------------------------|--------------------------------| | 依赖操作系统缓存 | ✅ 是 | ❌ 否 | | 数据一致性 | 延迟写入(可能丢失未刷盘数据)| 直接写入(数据持久性更强) | | 内存占用 | 较高(需维护内核缓存) | 较低(但需用户自行管理缓冲区) | --- ### 3. **性能特点** - **Buffer I/O优势场景** - 频繁访问小文件(缓存命中率高) - 需要利用预读(Read-ahead)或延迟写(Write-behind)优化 - 示例:文本编辑器、日志文件追加写入 - **Direct I/O优势场景** - 大文件读写(避免缓存占用过多内存) - 需要**避免双重缓存**(如数据库系统自行管理缓存) - 示例:MySQL的InnoDB引擎、视频处理软件 --- ### 4. **系统调用差异** - **Buffer I/O** 使用标准文件操作接口(如`read()`, `write()`),依赖内核自动管理缓存。 ```c // 示例:标准读取(经过页缓存) read(fd, buf, size); ``` - **Direct I/O** 需在打开文件时指定`O_DIRECT`标志(Linux),且要求内存对齐(对齐到磁盘块大小)。 ```c // 示例:直接I/O模式打开文件(Linux) int fd = open("file.txt", O_RDWR | O_DIRECT); ``` --- ### 5. **典型应用场景** | Buffer I/O | Direct I/O | |-------------------------------------|-------------------------------------| | 通用文件操作 | 数据库系统(如MySQL、Oracle) | | 需要高吞吐的小文件读写 | 需要确保数据持久性的关键任务 | | 依赖系统缓存优化的场景 | 自行实现缓存策略的高性能应用 | --- ### 6. **选择依据** - 选择 **Buffer I/O** 若: - 需要利用内核缓存提升性能 - 容忍潜在的数据延迟写入风险 - 选择 **Direct I/O** 若: - 要求数据写入的强一致性(如金融交易) - 需避免内核与用户空间的双重缓存(如数据库--- ### 总结 Buffer I/O通过牺牲一定数据一致性换取性能,适合通用场景; Direct I/O以更高复杂度和性能波动为代价,换取对I/O过程的精准控制。实际选择需权衡业务需求与系统特性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值