https://blog.csdn.net/yang2011079080010/article/details/53759759
https://blog.csdn.net/carolzhang8406/article/details/7227761
对于操作系统而言,I/O操作可以分为两类,一类是带缓存的IO,又称为标准IO(C标准库中提供了标准IO库,即stdio),它实现了跨平台的用户缓存解决方案。另一类是Unix/Linux下的文件IO,又称直接IO,即文件访问机制不经过操作系统内核的缓存,数据直接在磁盘和应用程序地址空间进行传输。相对而言,直接IO效率更高。本篇文章将从文件的打开、读写等方面来介绍这两类IO操作。
一.标准IO
标准IO在系统调用的上一层多加了一个缓冲区,也因此引入了流的概念。在UNIX/Linux下表示为FILE*,
FILE实际上包含了为管理流所需要的所有信息:实际I/O的文件描述符,指向流缓存的指针(标准I/O缓存,由malloc分配,又称为用户态进程空间的缓存,区别于内核所设的缓存),缓存长度,当前在缓存中的字节数,出错标志等。标准I/O对每个I/O流自动进行缓存管理,它提供了三种类型的缓存:
1) 全缓存。当填满标准I/O缓存后才执行I/O操作。磁盘上的文件通常是全缓存的。
2) 行缓存。当输入输出遇到新行符或缓存满时,才由标准I/O库执行实际I/O操作。stdin、stdout通常是行缓存的。
3) 无缓存。相当于read、write了。stderr通常是无缓存的,因为它必须尽快输出。
在linux的缓存IO机制中,操作系统会将IO的数据缓存在文件系统的页缓存(page cache)中,也就是说,数据会先被拷贝到操作系统内核的缓存区中,然后才会从操作系统内核的缓存区拷贝到应用程序的地址空间。标准I/O库在关闭流的时候自动释放缓存。另外,也可以使用函数fflush()将流所有未写的数据送入(刷新)到内核(内核缓冲区),fsync()将所有内核缓冲区的数据写到文件(磁盘)。第一次调用带缓存的文件操作函数时,标准库会自动分配内存并且读出一段固定大小的内容存储在缓存中。所以以后每次的读写操作并不是针对硬盘上的文件直接进行的,而是针对内存中的缓存的。何时从硬盘中读取文件或者向硬盘中写入文件有标准库的机制控制。实际上,标准IO最终还是通过调用系统提供的不带缓存的IO实现的(每次read/write都进行一次系统调用),标准IO的引入,避免了频繁的系统调用,减少了系统资源消耗,提高了IO效率。
eg:我怀疑讲的不对????
以fgetc/fputc 为例,当用户程序第一次调用fgetc 读一个字节时,fgetc 函数可能通过系统调用进入内核读1K字节到I/O缓冲区中,然后返回I/O缓冲区中的第一个字节给用户,把读写位置指 向I/O缓冲区中的第二个字符,以后用户再调fgetc ,就直接从I/O缓冲区中读取,而不需要进内核了,当用户把这1K字节都读完之后,再次调用fgetc时,fgetc函数会再次进入内核读1K字节到I/O缓冲区中。在这个场景中用户程序、C标准库和内核之间的关系就像在“Memory Hierarchy”中CPU、Cache和内存之间的关系一样,C标准库之所以会从内核预读一些数据放在I/O缓冲区中,是希望用户程序随后要用到这些数据,C标准库的I/O缓冲区也在用户空间,直接从用户空间读取数据比进内核读数据要快得多。另一方面,用户程序调用fputc通常只是写到I/O缓冲区中,这样fputc函数可以很快地返回,如果I/O缓冲区写满了,fputc 就通过系统调用把I/O缓冲区中的数据传给内核,内核最终把数据写回磁盘或设备。有时候用户程序希望把I/O缓冲区中的数据立刻传给内核,让内核写回设备或磁盘,这称为Flush操作,对应的库函数是fflush,fclose函数在关闭文件之前也会做Flush操作。
二.文件IO
1.read读文件
(1)当采用默认方式打开文件时(阻塞读)
(2)采用非阻塞方式读
2.write写文件
此外,写文件的方式还与open()调用有关,可以有追加写(Append)模式、同步写模式等
标准I/O的缓存:
1)当STDOUT连接到终端设备时,那么它就是行缓存的,也就是标准IO库没看到一个新行符 \n时就刷新一次缓存(即执行一次实际的输出操作)。这一特性可以通过如下测试代码来验证
int main()
{
printf("This Line Should be Cached...");
sleep(3); //这时候在终端上是看不到任何输出
printf("\nThis Line Should be Cached Again"); //这时候可以看到第一个printf的输出,因为被换行符刷新了
sleep(3); //这时候也只能看到一行输出,而看不到第二个printf输出的
printf("This Line Should Not be Cached Again\n"); //这时候可以看到第二个和第三个printf的输出,因为被结尾的\n刷新
sleep(3);
getchar();
}
2)当STDOUT被重定向到一个具体文件时,那么标准输出是全缓存的,也就是说只有当输出缓存被塞满或者调用fflush或fclose时才会执行实际的写入操作,这里就不给出具体例子,可以通过freopen将STDOUT重定向到一个具体文件来进行测试。
不带缓存的I/O对文件描述符操作,带缓存的标准I/O是针对流的。
带缓存的文件操作是标准C 库的实现,第一次调用带缓存的文件操作函数时标准库会自动分配内存并且读出一段固定大小的内容存储在缓存中。所以以后每次的读写操作并不是针对硬盘上的文件直接进行的,而是针对内存中的缓存的。何时从硬盘中读取文件或者向硬盘中写入文件有标准库的机制控制。不带缓存的文件操作通常都是系统提供的系统调用,更加低级,直接从硬盘中读取和写入文件,由于IO瓶颈的原因,速度并不如意,而且原子操作需要程序员自己保证,但使用得当的话效率并不差。另外标准库中的带缓存文件IO 是调用系统提供的不带缓存IO实现的。