自学内容网 自学内容网

解析缓冲区

1.抛问题,看现象

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>



int main()
{
  /* c接口打印置屏幕 */
  fprintf(stdout, "%s", "hello fprintf");
  printf("hello perntf");
  fwrite((void*)"hello fwrite", sizeof(char), sizeof("hello fwrite"), stdout);

  /* 系统接口打印置屏幕 */
  write(1, (void*)"helloc write", strlen("helloc write"));
  close(1);
  //fork();
}

打印结果:

为什么只有系统接口打印了,而我们的C接口并没有?接着看

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>



int main()
{
  /* c接口打印置屏幕 */
  fprintf(stdout, "%s", "hello fprintf\n");
  printf("hello perntf\n");
  fwrite((void*)"hello fwrite\n", sizeof(char), sizeof("hello fwrite\n"), stdout);

  /* 系统接口打印置屏幕 */
  write(1, (void*)"helloc write\n", strlen("helloc write\n"));
  close(1);
  //fork();
}

打印接口:

加了‘\n’我们的打印就可以出现,为什么?

别急,在看一个

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>



int main()
{
  /* c接口打印置屏幕 */
  fprintf(stdout, "%s", "hello fprintf");
  printf("hello perntf");
  fwrite((void*)"hello fwrite", sizeof(char), sizeof("hello fwrite"), stdout);

  /* 系统接口打印置屏幕 */
  write(1, (void*)"helloc write", strlen("helloc write"));
  //close(1);
  fork();
}

 如果你们执行过这段代码,会发现C接口打印了两次,而OS接口只执行了一次,为什么?

OK,看本篇文章,会得到答案的。

2.什么是缓冲区

缓冲区(Buffer)实际上是一段临时存储数据的内存区域,用于在不同设备或系统组件之间传输数据时,平衡数据处理速度的不匹配。它通常用于临时存储数据,以便更有效地进行数据读取或写入操作。

简单来说,就是临时存储数据的一块内存。那么这块内存,是由谁来维护的呢?

两种情况,1.操作系统级别的缓冲区,一般是由操作系统本身来维护,在特定情况下,硬件和应用程序也会参与缓冲区的分配与维护。
2.用户级缓冲区,主要是由应用程序和其依赖的库来完成,操作系统提供底层的内存管理支持。

3.为什么会有缓冲区

首先,CPU的资源是有限的,每个进程都要去竞争。如果一个进程想要去读写文件,那么有缓冲区和无缓冲区有什么区别

总结:

1.避免CPU空转:I/O操作速度远不及CPU处理数据速度,无论外设,磁盘,网络都比CPU慢得多
2.提高I/O性能:每次进行I/O操作都会涉及与磁盘,网络等外设的交互,开销十分大,而且频繁访问会使大幅度降低性能,缓冲区可暂存数据,减少该操作
3.平衡资源竞争:每个进程都会竞争CPU,无缓冲区,CPU会在等待I/O操作的同时停滞,无法有效的调度和使用CPU资源
4.提高数据传输效率:每次都进行与外设的读写交互,一个字符也同步写入外设的话,会增加大量开销,通过缓冲区,带缓冲区满后,一次将数据写入。
5.提升用户体验:当用户打开视频等应用时,缓冲区可以预加载一定数据,避免卡顿。

4.缓冲区刷新策略

1. 立即刷新(无缓冲)

  • 描述:每次更新后,数据会立即写入目标设备或存储,不进行任何缓冲或延迟操作。这意味着每次输出操作都需要等待设备完成写入或显示。
  • 特点
    • 数据立即同步:写入或显示操作会立即反映在目标设备或存储上,确保数据的即时一致性。
    • 无缓冲:没有中间缓冲区,不会积累任何未写入的数据。
  • 缺点
    • 性能较差:每次操作都需要等待外部设备完成写入或刷新,导致性能瓶颈,尤其是在高频率的输出操作时。
  • 应用场景:通常用于对数据一致性要求非常高的应用,如数据库事务日志的实时写入、实时控制系统等。

2. 行刷新(行缓冲)

  • 描述:在显示设备(如显示器)上,数据按行缓存刷新。行缓冲意味着数据在显示之前会先存储在内存中,通常按行逐渐显示,这符合人类视觉的阅读习惯。
  • 特点
    • 逐行刷新:数据按行缓存并刷新,可以减少屏幕的闪烁,提高显示效率,特别适合文本显示和图形界面的内容。
    • 优化用户体验:由于人眼习惯于按行扫描内容,行刷新策略有助于减少视觉干扰(如闪烁),使显示更为流畅。
    • 较高的效率:相比全屏刷新的方式,逐行刷新通常可以更有效地利用显示设备的刷新频率,减少不必要的刷新操作。
  • 缺点
    • 不适合动态视频显示:行刷新策略在处理复杂、动态变化的图像(如视频)时,可能表现得不如全屏刷新策略流畅。
  • 应用场景:主要用于显示设备,如液晶显示器、电视屏幕、计算机显示器等。行缓冲在图形界面、网页浏览和大多数显示场景中都较为常见。

3. 缓冲区刷新(全缓冲,磁盘文件)

  • 描述:在这种策略下,数据首先存储在缓冲区中,直到缓冲区达到一定条件(如达到一定大小或定时刷新)才会将数据写入外部存储(如磁盘文件)。这种策略通常在文件系统和磁盘操作中使用。
  • 特点
    • 批量处理:数据会被积累在缓冲区中,减少I/O操作次数,从而提高系统的整体性能。只有当缓冲区满或满足刷新条件时,数据才会写入磁盘。
    • 高效:定期刷新或按批次刷新数据,可以显著提高磁盘写入效率,尤其适用于高频率的文件操作。
    • 较少I/O操作:通过减少磁盘的读写次数,系统能够保持较高的性能,避免频繁的磁盘操作带来的性能损失。
  • 缺点
    • 数据丢失风险:如果在缓冲区数据刷新到磁盘之前发生崩溃或断电,未刷新到磁盘的数据可能会丢失。
  • 应用场景:常见于磁盘文件的写入操作,比如日志文件、数据库写入、缓存系统等。全缓冲模式在高性能磁盘操作和网络数据传输中也有广泛应用。

总结

  • 立即刷新(无缓冲):适用于对数据一致性要求极高的场景,但会牺牲性能,适用于需要实时反应的设备和应用。
  • 行刷新(行缓冲):适用于显示器、文本处理和图形界面,能够根据人类的阅读习惯逐行刷新,优化显示性能和用户体验。
  • 缓冲区刷新(全缓冲):适用于需要批量写入数据的磁盘操作,能够最大化提高效率,减少I/O操作的次数,适合高性能的文件写入和日志系统。

这些策略在不同的应用环境中各有优势,合理选择和调整刷新策略能够有效提高系统的性能,同时确保数据的可靠性和一致性。

5.用户级缓冲区

以C语言缓冲区为例。

为什么C语言打开文件返回的是FILE*,但是操作系统打开文件返回int,难道操作C语言不调用系统接口进行文件操作?

答案肯定是要调用,不论是linux还是Windows。

通过源码,我们可以看到,在FILE中不仅仅只有文件描述符_fileno还有维护文件缓冲区、文件流状态等字段。

为什么要有用户级缓冲区?

1.提高程序的I/O性能:虽然操作系统已经通过缓冲区减少了与磁盘的交互,但并不意味着文件的读写足够高效。用户级缓冲区的存在进一步提高了性能,尤其是大数据处理时
2.优化数据访问模式:操作的缓冲区通常为了优化磁盘访问而设计,但它并不一定能够充分满足应用程序的特定需求,用户级缓冲区允许根据自己的数据访问模式进行优化。
3.自定义缓冲区策略:操作系统的缓存区是通用的,皆在适应广泛的应用场景,而用户级缓冲区允许程序根据特定的需求定制缓冲区策略。
4.增强跨平台性和可移植性:C语言的文件I/O函数(如fopen(), fwrite(),fread())通过引入用户级缓冲区,实现了跨平台的一致接口
5. 支持高级功能:用户级缓冲区还可以支持一些操作系统缓冲区所不能直接提供的高级功能
6. 同步与并发控制:对于多线程或多进程应用,用户级缓冲区可以帮助管理并发的文件访问,确保数据一致性。

6.回答问题

通过对缓冲区的介绍,刚才的现象也就能很好地解释了

为什么只有系统接口打印了,而我们的C接口并没有?

在 C 语言中,标准库的 fprintf()printf()fwrite() 等函数会使用缓冲区来优化 I/O 操作。这个缓冲区存储着待写入的内容,直到某些条件满足时,才会将数据实际写入屏幕或文件。常见的缓冲策略有:

  • 全缓冲:当缓冲区满时,数据会被写入。
  • 行缓冲:遇到换行符 \n 或输出时,数据会被刷新。
  • 无缓冲:每次写操作都直接进行。

write() 是一个系统调用,它直接与操作系统的文件描述符交互,不经过 C 标准库的缓冲机制,因此它会立即将数据写入到指定的文件描述符(在这个例子中是标准输出,即 stdout)。

  • 标准库缓冲fprintf(stdout, ...)printf(...) 都通过 C 标准库进行缓冲处理。由于默认的标准输出是行缓冲的,这意味着只有遇到换行符或者缓冲区满时,数据才会被写入。然而,因为没有换行符 \n,并且没有调用 fflush(stdout) 来强制刷新缓冲区,所以数据会暂时停留在内存中的缓冲区里,直到程序退出。

  • write() 系统调用write() 直接通过文件描述符 1(标准输出)进行操作,它不依赖于 C 标准库的缓冲机制,因此数据会立即被写入到屏幕上。

  • 关闭文件描述符:调用 close(1) 关闭标准输出文件描述符后,C 标准库的输出缓冲区也不能再被写入数据。当你后续再调用 fprintf()printf()fwrite() 时,它们试图使用已关闭的标准输出流进行写操作,结果会失败或者什么都不输出。

当你调用 close(1) 时,标准输出的文件描述符已经被关闭了。所以之后你尝试使用 fprintf(stdout, ...)printf(...)fwrite(...) 进行输出时,虽然这些函数试图将数据写入 stdout,但是标准输出已经被关闭,导致这些操作无法完成。

加了‘\n’我们的打印就可以出现,为什么?

‘\n’是行刷新,数据会被立即刷新置操作系统。

C接口打印了两次,而OS接口只执行了一次,为什么?


原文地址:https://blog.csdn.net/HH_KZ1314/article/details/144281796

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!