本文涉及到众多知识点。如内核态与用户态。
首先,什么是缓冲区?缓冲和缓存这是两个十分相似且容易混淆的概念。
简单来看,缓冲区是为了解决通信的两个组件之间速度不匹配的问题。比如说,主存和磁盘。主存速度快,磁盘慢,那么进程要将主存中的数据写入到磁盘的时候,主存就不得不等待磁盘。如果不想等怎么办,所以就有缓冲区的概念。缓冲区就是在主存中专门开辟出一块空间,放置要写入磁盘的数据,当进程要把主存中的数据写入磁盘的时候,不真的等待操作系统把数据写入磁盘,而是直接把数据搬到缓冲区中(还在内存中),然后操作系统告诉进程,我已经写好了,你可以继续了,进程就继续向前运行。之后,操作系统再默默把缓冲区中的数据写入到磁盘。
内核缓冲区和进程缓冲区
除了在进程中设计缓冲区,内核也有自己的缓冲区。当一个用户进程要从磁盘读取数据时,内核一般不直接读磁盘,而是将内核缓冲区中的数据复制到进程缓冲区中。但是如果内核缓冲区中没有数据,内核会把对数据块的请求,加入到请求队列,然后把进程挂起,调度其他进程运行。等到数据已经读取到内核缓冲区时,把内核缓冲区中的数据读取到用户进程中,才会通知进程。可以认为,read系统调用是把数据从内核缓冲区复制到进程缓冲区,write系统调用把数据从进程缓冲区复制到内核缓冲区。
带缓冲和不带缓冲的IO
所谓不带缓冲,不是指内核不提供缓冲,而是指系统调用(read和write),不是函数库的调用。系统内核对磁盘的读写都会提供一个块缓冲(即内核缓冲区)。当调用write时,直接将数据写入到内核缓冲区,当内核缓冲区的数据到达一定量时,才会把数据写入到磁盘。因此,不带缓冲的IO是指进程不提供缓冲功能,内核缓冲区仍然存在。write是系统调用,每调用一次write都直接将数据写入到内核缓冲区中。write系统调用把数据从进程缓冲区复制到内核缓冲区,此时并不一定会发生内核缓冲区和磁盘之间的数据交换。只有当满足一定条件时,内核缓冲区中的数据才会写入磁盘。
1 | graph LR |
而带缓冲的IO是指进程对输入输出流进行了改进,提供了一个流缓冲,当用fwrite函数(不是系统调用,是标准库提供的IO函数)往磁盘写数据时,先把数据写入流磁盘缓冲区中。当满足一定条件时,如流缓冲区满了,或刷新流缓冲区,才把流缓冲区中的数据一次写入到内核缓冲区中(这个过程还是调用write系统调用),再经内核缓冲区写入磁盘(双重缓冲)。
1 | graph LR |
因此,带缓冲的IO在往磁盘写入相同的数据量时,会比不带缓冲的IO调用系统调用的次数要少。
总结一下,不带缓冲的IO对文件描述符进行操作,带缓冲的IO操作的则是流。标准IO就是带缓冲的IO。标准IO提供缓冲的目的是为了调用write和read的次数(这两个函数是系统调用,有开销),它对每个IO流自动进行缓冲管理。
参考文献
[1]带缓冲的IO与不带缓冲的IO:https://blog.csdn.net/u011402017/article/details/53747232
[2]用户进程缓冲区和内核缓冲区:https://blog.csdn.net/huaiqu6460/article/details/89677420
[3]内核缓冲区:https://blog.csdn.net/xiaofei0859/article/details/51145717
[4]缓冲和缓存:https://www.cnblogs.com/tonycloud/articles/6568159.html