当前位置: 首页 > news >正文

10.【Linux系统编程】缓冲区详解——库缓冲区 VS 内核缓冲区

目录

  • 1. 引言
    • 1.1 情况1,printf完不使用close
    • 1.2 情况2,printf完使用close
    • 1.3 用户IO操作在系统层调用方式
  • 2. 缓冲区
    • 2.1 什么是缓冲区
    • 2.2 为什么要引入缓冲区机制
    • 2.3 缓冲区工作流程
    • 2.4 刷新方式&缓冲类型
      • 2.4.0 未刷新缓冲区
      • 2.4.1 fflush(强制刷新缓冲区)
      • 2.4.2 stderr(刷新条件满足→立即刷新)
    • 2.5 库函数IO和系统调用IO(printf等&write等)
    • 2.6 FILE结构体
    • 2.7 简单设计一下libc库

1. 引言

1.1 情况1,printf完不使用close

// stream.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstdlib>int main()
{close(1);// fd == 1int fd = open("log.txt",O_CREAT | O_WRONLY | O_APPEND, 0666);// 默认stdout输出,但是将1关闭了,所以此时log.txt的文件描述符fd为1,stdout = 1// 库函数printf("fd:%d\n",fd);printf("hello world\n");printf("hello world\n");printf("hello world\n");//系统调用const char *msg = "hello write\n";write(fd, msg, strlen(msg));// 没敢写close// close(fd);return 0;
}

运行查看

$ cat log.txt
hello write
fd:1
hello world
hello world
hello world

1.2 情况2,printf完使用close

// stream.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstdlib>int main()
{close(1);// fd == 1int fd = open("log.txt",O_CREAT | O_WRONLY | O_APPEND, 0666);// 默认stdout输出,但是将1关闭了,所以此时log.txt的文件描述符fd为1,stdout = 1// 库函数printf("fd:%d\n",fd);printf("hello world\n");printf("hello world\n");printf("hello world\n");//系统调用const char *msg = "hello write\n";write(fd, msg, strlen(msg));// 没敢写closeclose(fd);return 0;
}

运行查看

$ cat log.txt
hello write

1.3 用户IO操作在系统层调用方式

为什么使用close之后,printf的内容就不能输出到log.txt了,而write始终都可以输出到log.txt呢?

  • 因为C语言标准库的缓冲区的存在

缓冲区的工作流程:

  • 程序写入数据 → 暂存到缓冲区 → 满足刷新条件(1.显式操作 / 2.缓冲类型 / 3.程序退出)→ 刷新到硬件设备

在这里插入图片描述

2. 缓冲区

2.1 什么是缓冲区

缓冲区是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。

缓冲区在文件部分分为:用户级缓冲区(库缓冲区)、内核级缓冲区

2.2 为什么要引入缓冲区机制

读写文件时,如果不会开辟对文件操作的缓冲区,直接通过系统调用对磁盘进行操作(读、写等),那么每次对文件进行一次读写操作时,都需要使用读写系统调用来处理此操作,即需要执行一次系统调用,执行一次系统调用将涉及到CPU状态的切换,即从用户空间切换到内核空间,实现进程上下文的切换,这将损耗一定的CPU时间,频繁的磁盘访问对程序的执行效率造成很大的影响。

为了减少使用系统调用的次数,提高效率,我们就可以采用缓冲机制。比如我们从磁盘里取信息,可以在磁盘文件进行操作时,可以一次从文件中读出大量的数据到缓冲区中,以后对这部分的访问就不需要再使用系统调用了,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。又比如,我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的CPU可以处理别的事情。可以看出,缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。

2.3 缓冲区工作流程

  • 程序写入数据 → 暂存到缓冲区 → 满足刷新条件(缓冲类型 / 显式操作 / 程序退出) → 刷新到硬件设备
    在这里插入图片描述

2.4 刷新方式&缓冲类型

程序一般有三种刷新方式

  1. 强制刷新(执行flush语句)
  2. 刷新条件满足(即缓冲类型,包括下述三种)
  3. 进程结束

标准I/O提供了3种类型的缓冲区。(加粗即为刷新条件)

  • 全缓冲区:这种缓冲方式要求填满整个缓冲区后才进行I/O系统调用操作。对于磁盘文件的操作通常使用全缓冲的方式访问。
  • 行缓冲区:在行缓冲情况下,当在输入和输出中遇到换行符时(只对显示器适用),标准I/O库函数将会执行系统调用操作。当所操作的流涉及一个终端时(例如标准输入和标准输出),使用行缓冲方式。因为标准I/O库每行的缓冲区长度是固定的,所以只要填满了缓冲区,即使还没有遇到换行符,也会执行I/O系统调用操作,默认行缓冲区的大小为1024
  • 无缓冲区:无缓冲输出的核心是 “绕过标准 I/O 库的缓存,直接调用系统调用”。
    • 利用默认无缓冲的stderr流(通过fprintf(stderr, ...)等函数);
    • setvbuf将指定流(如stdout)设置为_IONBF模式;
    • 直接使用系统调用(如write),完全不依赖标准 I/O 库。

2.4.0 未刷新缓冲区

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {close(1);int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0) { perror("open"); return 0; }printf("hello world: %d\n", fd);close(fd);return 0;
}

我们本来想使用重定向思维,让本应该打印在显示器上的内容写到“log.txt”文件中,但我们发现,程序运行结束后,文件中并没有被写入内容:

# cat log.txt

这是由于我们将1号描述符重定向到磁盘文件后,缓冲区的刷新方式成为了全缓冲。而我们写入的内容并没有填满整个缓冲区,导致并不会将缓冲区的内容刷新到磁盘文件中。怎么办呢?可以使用fflush强制刷新下缓冲区。

2.4.1 fflush(强制刷新缓冲区)

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {close(1);int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0) { perror("open"); return 0; }printf("hello world: %d\n", fd);fflush(stdout);close(fd);return 0;
}

还有一种解决方法,刚好可以验证一下stderr是不带缓冲区的,代码如下:

2.4.2 stderr(刷新条件满足→立即刷新)

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {close(2);int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0) { perror("open"); return 0; }perror("hello world");close(fd);return 0;
}

这种方式便可以将2号文件描述符重定向至文件,由于stderr没有缓冲区,“hello world”不用fflash就可以写入文件:

2.5 库函数IO和系统调用IO(printf等&write等)

  • 因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。
  • 所以C库当中的FILE结构体内部,必定封装了fd

来段代码在研究一下:

#include <stdio.h>
#include <string.h>
int main()
{const char* msg0 = "hello printf\n";const char* msg1 = "hello fwrite\n";const char* msg2 = "hello write\n";printf("%s", msg0);fwrite(msg1, strlen(msg0), 1, stdout);write(1, msg2, strlen(msg2));fork();return 0;
}

运行出结果:

hello printf
hello fwrite
hello write

但如果对进程实现输出重定向呢? ./hello > file , 我们发现结果变成了:

hello write
hello printf
hello fwrite
hello printf
hello fwrite

我们发现printffwrite(库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork有关!

  • 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲
  • printffwrite库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
  • 而我们放在缓冲区中的数据,就不会被立即刷新。
  • 但是进程退出之后,会统一刷新,写入文件当中。
  • 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
  • write 没有变化,说明没有所谓的缓冲。

综上:printffwrite库函数会自带缓冲区,而write系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。

那这个缓冲区谁提供呢?printffwrite 是库函数,write是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是write没有缓冲区,而printffwrite有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。

2.6 FILE结构体

如果有兴趣,可以看看FILE结构体:

typedef struct _IO_FILE FILE; 在/usr/include/stdio.h

// 在/usr/include/libio.h
struct _IO_FILE {int _flags; /* High-order word is _IO_MAGIC; rest is flags.*/#define _IO_file_flags _flags//缓冲区相关/* The following pointers correspond to the C++ streambuf protocol. *//* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */char* _IO_read_ptr; /* Current read pointer */char* _IO_read_end; /* End of get area. */char* _IO_read_base; /* Start of putback+get area. */char* _IO_write_base; /* Start of put area. */char* _IO_write_ptr; /* Current put pointer. */char* _IO_write_end; /* End of put area. */char* _IO_buf_base; /* Start of reserve area. */char* _IO_buf_end; /* End of reserve area. *//* The following fields are used to support backing up and undo. */char* _IO_save_base; /* Pointer to start of non-current get area. */char* _IO_backup_base; /* Pointer to first valid character of backup area*/char* _IO_save_end; /* Pointer to end of non-current get area. */struct _IO_marker* _markers;struct _IO_FILE* _chain;int _fileno; //封装的文件描述符#if 0int _blksize;#elseint _flags2;#endif_IO_off_t _old_offset; /* This used to be _offset but it's too small. */#define __HAVE_COLUMN /* temporary *//* 1+column number of pbase(); 0 is unknown. */unsigned short _cur_column;signed char _vtable_offset;char _shortbuf[1];/* char* _save_gptr; char* _save_egptr; */_IO_lock_t* _lock;#ifdef _IO_USE_OLD_IO_FILE
};

2.7 简单设计一下libc库

mystdio的实现

http://www.dtcms.com/a/475637.html

相关文章:

  • 做网站模版与定制的区别信通网站开发中心
  • ppt网站模板佟年做网站给KK
  • 女人做绿叶网站相亲拉人深圳网站设计x
  • 凡科网怎么做网站网络域名后缀
  • 企业网站建站 优帮云做网站其实不贵
  • 个人或主题网站建设服装效果图网站
  • 像美团这种网站怎么做的深圳外包软件开发
  • 公司网站是否做地方分站智能展厅设计公司
  • 极速网站建设广州网站优化推荐
  • 网站建设公司的会计分录蒙古文网站建设情况
  • 家具flash网站模板下载wordpress用php版本
  • QML学习笔记(三十三)QML的CheckBox
  • 吸引企业做网站网页设计分类
  • 做装饰画的行业网站reactjs wordpress
  • 《XOR》与《再次跳跃吧,俊潇!》题解
  • 四川省建设厅申报网站关键词推广排名
  • 哪些网站可以做平面设计排名优化百度
  • VMamba学习笔记
  • 词根学习笔记 | Alter系列
  • 如何学习VBA_3.3.10 自制VBA工具,充分享受工作的乐趣
  • 专业移动网站建设北京网络职业技术学院官网
  • 广州增城做网站网站建设 中企动力嘉兴0573
  • 对称加密详解
  • 8.5JavaScript函数 arguments
  • 免费网站建设c3sales给帅哥做奴视频网站地址
  • 中英文外贸网站源码代做效果图网站哪家好
  • 【Swift】LeetCode 15. 三数之和
  • 做网站表格单边框标记南通建设局网站查询
  • 百度正版下载恢复百度莱阳seo排名
  • 利用AI工具生成毕业论文,并智能管理相关文献资源。