Linux篇: 文件IO缓冲(上)
Linux篇: 文件IO缓冲(上)
我们在项目开发的时候, 经常会使用到类似 Printf 的方法, 但是有时候会出现调用了该函数之后, 终端上并没有任何的输出, 然后我们需要在需要打印的字符串后面加上换行符 \n 才能正常的输出字符串, 这里就涉及到本文将要介绍的文件IO缓冲的概念.文件I/O的内核缓冲:缓冲区缓存
在对磁盘文件进行操作时,read()和write()系统调用并不会直接发起磁盘访问。相反,它们仅仅是在用户空间缓冲区与内核缓冲区缓存中的缓冲区之间复制数据。例如,write(fd, "abc", 3) 该调用将3字节数据从用户空间内存中的缓冲区传输到内核空间的缓冲区, 此时,write()调用立即返回。稍后的某个时刻,内核会将其缓冲区中的数据写入(刷新到)磁盘。(因此,我们说系统调用与磁盘操作是不同步的。)在此期间,如果有另一个进程试图读取文件的这些字节,内核会自动从缓冲区缓存中提供这些数据,而不是从磁盘上(可能已过时的)文件内容中读取。 相应地,对于输入操作,内核从磁盘读取数据并将其存储在内核缓冲区中。read()调用会从该缓冲区中获取数据,直到缓冲区数据被读完;此时,内核会将文件的下一段内容读入缓冲区缓存。这种设计的目标是使read()和write()调用能够快速执行,因为它们无需等待(缓慢的)磁盘操作。同时,这种设计也很高效,因为它减少了内核必须执行的磁盘传输次数。 Linux内核并未对缓冲区缓存的大小施加固定的上限。内核会根据需要分配尽可能多的缓冲区缓存页,其限制仅取决于可用的物理内存总量以及物理内存在其他用途上的需求(例如,容纳运行中进程的文本段和数据段页)。当可用内存紧张时,内核会将一些已修改的缓冲区缓存页刷新到磁盘,以便释放这些页供重新使用。 这里还有一个更有意思的情况, 在于内核从磁盘读取文件的时候, 并不是系统调用要求读取3个字节, 内核就从磁盘只读取3个字节, 而是采用了预读技术, 这是一种提升顺序读性能的关键机制。 无论是操作系统的文件系统还是磁盘硬件本身,数据读写的最小单位通常是一个扇区或文件系统块。在 MySQL 的 InnoDB 存储引擎中,B+ 树索引的节点大小被设计为与页面(Page) 大小相同. 如果节点大小小于页,那么一个节点可能分散在多个物理 I/O 中;如果节点大于页,则读取一个节点需要多次 I/O。将节点大小设为一个页,保证每个节点恰好通过一次磁盘 I/O 就能完全加载到内存,这非常符合 B+ 树逐节点查找的特性——每次访问节点都触发一次 I/O,而一次 I/O 正好取回整个节点。多次调用写入 VS 一次写入较大数据
无论我们是执行1000次单字节写入,还是一次性写入1000字节,内核执行的磁盘访问次数是相同的。然而,后者更优,因为它只需要一次系统调用,而前者需要1000次。尽管系统调用比磁盘操作快得多,但它们仍然需要相当可观的时间,因为内核必须捕获调用、检查系统调用参数的有效性,并在用户空间和内核空间之间传输数据. 总而言之,如果我们要向文件传输大量数据,那么通过使用大块缓冲区来缓冲数据,从而执行更少的系统调用,可以极大地提高I/O性能。系统调用
在C语言中, 设置IO缓冲的模式setvbuf() 函数用于控制 stdio 库所采用的缓冲形式。本文一般不用刻意的去介绍函数的具体使用方式, 通常情况下自己去查一下就知道如何使用了, 但是这个函数的其中一个参数mode意义很大, 涉及到本文的核心, 因此介绍一下该参数的意义:1. _IONBF:不缓冲 I/O。每次 stdio 库函数调用都会直接触发 write() 或 read() 系统调用。此时,buf 和 size 参数将被忽略,可分别指定为 NULL 和 0。这是stderr的默认模式,确保错误输出能立即显现。这也就是为什么在写入数据到stderr的时候, 没有换行符也马上能在终端看到的原因.2. _IOLBF:行缓冲 I/O。对于指向终端设备的流,此模式为默认。对于输出流,数据将被缓冲,直到遇到换行符才进行输出(除非缓冲区先被填满)。对于输入流,则每次读取一行数据。3. _IOFBF:全缓冲 I/O。数据的读或写(通过调用 read() 或 write())以缓冲区大小为单位进行。对于指向磁盘文件的流,此模式为默认, 因此我们调用函数写入文件的时候, 默认就是全缓冲的, 只有在超过一定的大小之后, 才会真正写入到底层文件中。 至此, 文件的写入为什么需要一定的大小之后才会输出到文件, 以及终端以行缓冲的输入输出的原因已经找到了, 当然了这也是可以修改的, 就是这里的 setvbuf() 方法.场景
1. 之前在项目开发的过程中, 对数据的写入实时性要求非常高, 否则得等待较长的时间才能感知到文件内容的变更(这里所说的时间较长, 是另一个进程感知), 因此就必须调用 write() 函数之后, 立马把数据写入到文件中, 这里可以调用 fflush() 函数, 它就可以强制做数据的写入.
本文来自网友投稿或网络内容,如有侵犯您的权益请联系我们删除,联系邮箱:wyl860211@qq.com 。