|
前面有提到系统IO和标准IO在缓冲方面的区别,针对缓冲部分,有一些概念和操作函数需要了解。 1.3.2.1 内核缓冲 实际上系统IO在进行文件读写时并不会直接访问磁盘设备,而是仅仅在用户空间缓冲区和内核缓冲区(kernel buffer cache)之间复制数据,而内核会将其缓冲区中的数据写入(刷新)到磁盘设备中。 系统调用与磁盘操作并不是同步的,例如write函数并不会等待数据真正写入到磁盘之后再返回。如果在此期间,其它进程调用read函数读取该文件的这几个字节数据,那么内核将自动从缓冲区中读取这几个字节数据返回给应用程序。 这样的设计,目的是为了提高文件I/O的速度和效率,使得系统调用read、write的操作更为快速,不需要等待磁盘操作(将数据写入到磁盘或从磁盘读取出数据),磁盘操作通常是比较缓慢的。内核根据相应的存储算法自动判断何时写入磁盘,这一设计更为高效,减少了内核操作磁盘的次数。 1.3.2.2 主动刷新内核缓冲 有些场景需要立刻将数据写入磁盘设备,此时就需要使用特定的函数(fsync、fdatasync、sync)去刷新内核缓冲区的方法。 1.3.2.3 标准IO缓冲 标准I/O有自己的缓冲区(stdio缓冲区),因此虽然标准I/O是在系统I/O基础上进行封装而实现,但在效率、性能上标准I/O要优于系统I/O。 标准I/O所维护的stdio缓冲是用户空间的缓冲区,当应用程序中通过标准I/O操作磁盘文件时,为了减少系统调用的次数,标准I/O函数会将用户读取或写入文件的数据缓存在stdio缓冲区,然后再一次性将stdio缓冲区中缓存的数据通过调用系统调用系统I/O写入到系统I/O的内核缓冲区。 这种机制通过减少系统调用的频率,降低了用户空间与内核空间频繁切换的开销,从而提高了文件操作的效率和性能。同时,标准I/O还免除了开发者手动管理缓冲区的复杂性,使得文件操作更加便捷高效。 1.行缓冲 例如printf默认为行缓冲: #include #include int main() { printf("This is test1\n"); printf("This is test2"); while (1) sleep(1); return 0; } 编译运行并查看测试结果: This is test1
printf仅打印了第一个带换行符的“This is test1”,而第二个打印并未出现。while循环的作用是防止进程结束自动刷新缓冲区。 2.无缓冲 例如设置stdio无缓冲: #include #include int main() { if (setvbuf(stdout,NULL,_IONBF,0)) { //设置stdio为无缓冲 printf("setvbuf error\n"); return -1; } printf("This is test1\n"); printf("This is test2"); while (1) sleep(1); return 0; } 编译运行并查看测试结果: This is test1 This is test2 printf的两个打印都成功输出,并且程序并未结束。 3.全缓冲与刷新缓冲 #include #include int main() { char buf[9]; if (setvbuf(stdout,buf,_IOLBF,9) != 0) { printf("setvbuf error\n"); return -1; } fprintf(stdout,"1"); fflush(stdout); while (1) { sleep(1); fprintf(stdout,"12345"); } return 0; } 编译运行并查看测试结果: 1 1234512345 1234512345^C 设置缓冲区长度为9字节、全缓冲,首次打印1并刷新缓冲区,此时按回车等待后面打印,循环每次输出到buf中5个字节数据,第二次10字节时超过缓冲区长度就会打印出来。 在文件关闭、程序退出时都会自动刷新stdio缓冲区。
|