【Linux】文件:基础IO

目录

[一 理解文件](#一 理解文件)

[1 狭义理解](#1 狭义理解)

[2 广义理解](#2 广义理解)

[3 文件操作的归类认知](#3 文件操作的归类认知)

[4 系统角度](#4 系统角度)

[5 文件分类](#5 文件分类)

[二 回顾C文件接口](#二 回顾C文件接口)

[三 过渡--文件操作,系统级别](#三 过渡--文件操作,系统级别)

[1 学习系统调用](#1 学习系统调用)

[2 粗粒度引入fd](#2 粗粒度引入fd)

[1 定义](#1 定义)

[2 默认fd](#2 默认fd)

[3 文件描述符分配规则](#3 文件描述符分配规则)

[4 语言封装与跨平台性](#4 语言封装与跨平台性)

[5 两个问题](#5 两个问题)

[四 重定向](#四 重定向)

[1 定义](#1 定义)

[2 使用示例](#2 使用示例)

[3 使用dup2系统调用](#3 使用dup2系统调用)

[五 理解一切皆"文件"](#五 理解一切皆“文件“)

[六 缓冲区](#六 缓冲区)

[1 定义](#1 定义)

[2 为什么要引入缓冲区机制](#2 为什么要引入缓冲区机制)

[3 内核文件缓冲区](#3 内核文件缓冲区)

[4 语言缓冲区](#4 语言缓冲区)

[1 两个事实](#1 两个事实)

[5 缓冲类型](#5 缓冲类型)


一 理解文件

1 狭义理解

文件在磁盘里
磁盘是永久性存储介质,因此文件在磁盘上的存储是永久性的
磁盘是外设(既是输出设备也是输入设备)
磁盘上的文件本质是:对文件的所有操作,都是对外设的输入和输出,简称 IO(input output)

我们永远站在内存角度去说IO,不站在设备角度

2 广义理解

Linux 下一切皆文件(键盘、显示器、网卡、磁盘...... 这些都是抽象化的过程)(后面会讲如何去理解)

3 文件操作的归类认知

对于 0KB 的空文件是占用磁盘空间的

文件是文件属性(元数据)和文件内容的集合(文件 = 属性(元数据)+ 内容)

所有的文件操作本质是文件内容操作文件属性操作

文件=文件内容+属性 (属性本质也是文件),所有文件操作本质是内容操作和属性操作,文件操作本质是进程对文件的操作,底层依赖操作系统的系统调用,而非直接通过语言库函数。

操作一个文件之前,必须先打开文件(fopen)

那么:打开的本质是什么? 把文件(内容和属性)加载到内存

为什么要打开? 体系结构决定

谁打开?进程(把一个二进制文件加载到内存)

把代码写完了(fwrite fopen.....),编译->exe->可执行,相当于一份计划交给cpu

当执行到这个代码,才被打开,被写-->所以malloc叫做动态开辟,因为被执行到才开辟

所以学习文件操作,本质就是学习进程和文件的关系

我们之前学到的,用命令访问文件,示例如下:

at 命令运行时,会被 Bash 解释为一个子进程。该子进程会接收 cat main.c 这一命令行参数,在其内部打开 main.c 文件,并读取文件内容。

因此,通过命令访问文件的过程,本质上体现了进程与文件之间的关系。

4 系统角度

对文件的操作本质是进程对文件的操作
磁盘的管理者是操作系统

文件的读写本质不是通过 C 语言 / C++ 的库函数来操作的(这些库函数只是为用户提供方便),而是通过文件相关的系统调用接口来实现的

5 文件分类

磁盘文件在宏观上被分为两类:

a.被进程打开的文件:加载到内存中,成为「内存中的打开文件」,核心体现进程和文件的关系
b.未被进程打开的文件:保留在磁盘上,由文件系统直接管理,属于「文件系统层面」的静态文件

二者共同构成「文件系统和进程」

磁盘是硬件,访问文件本质是访问磁盘硬件

直接操作硬件不可行,必须经由「操作系统 + 磁盘驱动」完成,操作系统对外提供文件访问系统调用(本质就是访问硬盘),这是用户层访问磁盘的唯一合法入口

C 语言等高级语言对底层系统调用进行了封装,让开发者无需直接编写底层硬件交互代码,即可完成文件读写操作

我们本次要学习的基础IO解决a情况的问题,b情况在后面解决


二 回顾C文件接口

cs 复制代码
FILE *fopen(const char *path, const char *mode);

返回值:成功时返回 FILE* 类型的文件句柄(文件流指针),失败时返回 NULL

参数 path:文件路径,用于在系统中定位文件

绝对路径:明确指定文件位置,如 "/home/whb/XXX.txt"

相对路径:省略路径时默认在 ** 当前工作目录(CWD)** 查找,如 "log.txt" 等价于 CWD/"log.txt"

参数 mode:文件打开模式,决定读写行为与文件创建逻辑

模式 核心行为 关键说明
r 只读打开 文件必须存在,流指针置于文件开头
r+ 读写打开 文件必须存在,支持读与写,流指针置于开头
w 只写创建 / 截断 文件不存在则创建,存在则清空内容(截断为 0),流指针置于开头
w+ 读写创建 / 截断 文件不存在则创建,存在则清空,支持读与写,流指针置于开头
a 追加写入 文件不存在则创建,写入内容始终追加到文件末尾,流指针初始在开头
a+ 读 + 追加写入 支持读取与追加写入,文件不存在则创建,读取时指针在开头,写入始终在末尾

路径是定位文件的核心:Linux 系统中必须通过路径找到文件,无路径默认使用 CWD

写模式的隐含行为:w/w+ 会清空已有文件内容,a/a+ 会保留内容并在末尾追加

错误处理:必须检查 fopen 返回值是否为 NULL,避免后续操作空指针

一个文件的当前工作路径是可以修改的:chdir

cs 复制代码
echo "hello world, hello bit" > log.txt

> 重定向符等价于 w 模式,会覆盖原有文件内容

cs 复制代码
echo "hello world, hello bit" >> log.txt

>> 重定向符等价于 a 模式,会在原有内容后追加新内容

OS 视角:文件内容的抽象理解
可以把文件内容想象成一维字符数组 char content[],比如 abcd\n1234 在系统中就是连续的字节序列
每次读写操作都依赖一个位置指针(类似数组下标 int index),标记当前读写到的位置
例如用 cat 或 vim 查看 abcd\n1234 时,本质是遍历这个 "数组" 并输出内容


三 过渡--文件操作,系统级别

我们的系统提供了操作文件的系统调用 例如:open(打开指定路径下的文件) close(关闭文件) read(读) write(写)

1 学习系统调用

我们通过对open这个系统调用的学习,引入后面的知识:

cs 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

返回值:成功时返回文件描述符(fd)(一个非负整数,用于标识打开的文件,也叫 "句柄"---可以用来表示文件唯一性的,都叫做句柄);失败时返回 -1,并设置 errno 错误码

核心参数:flags(位掩码传参)

fiag是打开文件的模式(常见模式见下面表格)

flags位掩码 ,由多个宏定义通过按位或 | 组合而成:

每个宏都是只有一个比特位是 1 的整数值,通过位运算高效组合 / 检查多个选项。

分类 宏定义 作用
访问模式(必选其一) O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读写打开
存在性控制 O_CREAT 文件不存在则创建,必须搭配 mode 参数指定权限
O_TRUNC 若文件存在,截断为 0 长度(清空内容)
O_APPEND 写操作总是追加到文件末尾

权限参数:mode

  • 仅在 flags 包含 O_CREAT 时需要传入,用于指定新文件的访问权限 (如 0644)。
  • 权限会受系统 umask 掩码影响,实际权限为 mode & ~umask

umask:0002--->默认把other的w权限去掉

但是我们也可以自己设置一个umask

存在系统设置的umask,和我们设置的umask,我们听谁的?

就近原则,谁离open近就听谁的

只是改变了当前进程的umask,不会影响系统的

我们再来看一下write这个系统调用:

cs 复制代码
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

返回值:

成功:返回实际写入的字节数(类型 ssize_t,带符号整数)。

失败:返回 -1,并设置 errno 错误码

参数含义:

参数 含义 注意事项
int fd 文件描述符 open()/socket() 等函数返回,标识要写入的目标(文件、管道、套接字等)。
const void *buf 数据缓冲区指针 指向待写入数据的起始地址,const 表示函数不会修改缓冲区内容。
size_t count 期望写入的字节数 无符号整数,指定要从 buf 中写入的最大字节数。

但是当我们写入内容的时候,系统是直接覆盖在上一次的内容上面,如果这次写的内容数量比上一次小,那么没有被覆盖的部分不会被清空,这样老数据可能就会对我们造成干扰

那我们如何清空数据?

2 粗粒度引入fd

不管我们以什么方式打开文件,最后都会返回一个fd,fd到底是什么呢?

1 定义

fd是文件描述符,文件描述符是操作系统(尤其是 Unix/Linux 系统)为了管理打开的文件 / IO 资源,给每个打开的文件分配的一个非负整数标识符。你可以把它理解成:操作系统给每个 "正在使用" 的文件(广义的文件,包括普通文件、管道、网络套接字、设备文件等)分配的一个 "编号",程序通过这个编号就能找到对应的文件,而不用直接操作复杂的文件底层结构

2 默认fd

如果打开一个文件fd是3,那么进程能同时打开多个文件吗?

可以!

当我们同时打开三个文件之后,发现fd的值是3,4,5 如下:

为什么文件描述符是从3开始?0,1,2呢?

大家还记得以前在学习c语言的时候听过一句话吗?

任何一个进程/文件,在运行的时候,都会默认打开三个标准输入输出流,0,1,2文件描述符对应这三个标准输入输出流

0:标准输入(stdin),比如键盘输入;
1:标准输出(stdout),比如终端打印;
2:标准错误(stderr),比如程序报错信息。
后续打开的文件会从 3 开始按顺序分配编号

stdin stdout stderr他们的类型都是FILE*,那什么是FILE?

我们打开c语言的时候,打开文件都会返回一个FILE,FILE 是 C 标准库中定义的一个结构体类型(也常被称为 "文件流")

每个 FILE 结构体实例对应一个打开的文件,里面至少包含:

对应的文件描述符(fd);

输入 / 输出缓冲区(减少系统调用次数,提升效率);

文件当前读写位置;

错误标志、结束标志等状态信息。

在系统层面上,操作系统只认fd,为什么?

因为系统调用进行打开文件,返回文件描述符,系统调用只认文件描述符,所以操作系统只认文件描述符

所以系统在系统调用层面,必须提供文件描述符 但是我们以前用stdin,stdout,stderr,从来没有见过fd啊?

是因为标准输入,输出,错误对应的stdin,stdout,stderr的结构体内部封装了fd

一定要封装fd 例如:fwrite的参数要写入文件,必须通过结构体内封装的fd

怎么确定封装了fd?

结论1:c语言文件操作函数和系统调用函数是封装关系,(fwrite封装了write,fopen封装了open,fread封装了read)

不能让c语言用户看到文件描述符 ,因为文件描述符属于系统概念,所以可以封装一个FILE类型,但是在系统层面,它叫做fd

3 文件描述符分配规则

为什么前面打开的三个文件,fd是3,4,5是连续的?

文件描述符就是文件打开的数组的下标

每个进程有一张打开文件表,Linux 里本质就是数组:

下标 = 文件描述符(0、1、2、3......)

内容 = 指向真正文件结构的指针

核心规则: 在fd_array数组中,分配「当前未被使用的最小下标」作为新的文件描述符

文件=内容+属性,CPU读内存之前,必须先把文件的内容加载到内存。

例如:修改文件:文件内有abcd,想把它改成大写-->先加载到内存,在内存中改写成大写之后刷新回去

修改文件必须遵守规则:先读到内存,后改,后写

结论:数据读写,必须先加载到内存

一个进程可以打开多个文件吗?

可以!打开文件的本质是预加载文件到内存

进程要不要管理被打开的文件?操作系统要不要管理?

要!怎么管理--->先描述,再组织

在内核中,被打开的文件的内核数据结构叫做struct file

当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了 file 结构体,表示一个已经打开的文件对象。而进程执行 open 系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针 * files,指向一张表 files_struct,该表最重要的部分就是包含一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。

有多少个文件,就有多少个struct file

操作系统管理被打开的文件,就是管理struct file

操作系统怎么让用户知道,哪些文件是一号进程打开的,哪些是二号进程打开的?进程和文件的关系怎么维护?

  • 操作系统通过 struct files_struct 结构体维护进程与文件的关联,每个进程的 PCB(进程控制块)中会有一个指向该结构体的指针。
  • struct files_struct 内部包含一个数组 fd_array[],数组的每个元素是 struct file* 类型,用来指向被打开的文件对象。
  • 当进程打开文件(比如标准输入、标准输出)时,会把对应的文件对象地址写入数组的某个下标位置,这个下标就是文件描述符(fd)
  • 常见的文件描述符:0(标准输入)、1(标准输出)、2(标准错误)。

文件描述符的本质

文件描述符的本质是数组下标,操作系统通过这个下标可以快速定位到进程打开的文件对象。

open/read/write/close 的理解

  • open:把外部路径的文件加载到内存中,同时将文件对象的地址写入 fd_array 的某个下标(分配文件描述符)。
  • read/write:通过文件描述符找到对应的文件对象,对文件内容进行读写操作。
  • close:释放文件描述符,将对应的数组位置标记为可用。

4 语言封装与跨平台性

  • C 语言的文件操作函数(如 fopen/fread/fwrite/fclose)是对系统调用的封装。
  • 封装的目的是跨平台性 :不同操作系统的系统调用接口可能不同,通过语言封装,开发者只需学习统一的 C 接口,无需关心底层平台差异。

5 两个问题

(1)每一个进程都会打开标准输入,标准输出,标准错误,0,1,2 为什么?

程序的本质是为了给用户进行数据计算和处理的

程序的本质是接收数据、处理数据、输出结果,而键盘和显示器是最基础的输入输出设备。
操作系统默认打开这三个文件描述符,是为了让程序能直接和用户交互,不需要额外手动打开设备文件

程序要有办法获取数据,最简单的方法:从键盘获取和显示器打印--->支持bug,0,1 ,stdin stdout

(2)一个进程打开了若干文件,该进程创建了子进程,文件需不需要拷贝一份给子进程?文件描述符表需不需要拷贝?

要!

父进程创建子进程时,子进程会继承父进程的文件描述符表(浅拷贝),即父子进程的文件描述符指向同一个文件对象。

子进程不需要重新打开文件,也不需要复制文件本身,只需要复用父进程的文件描述符指向关系。

父进程默认打开的 0、1、2,子进程也会继承,所以子进程同样默认能使用键盘输入和显示器输出


四 重定向

1 定义

重定向:改变文件描述符对应的"实际文件/设备",使原本输出到A设备的内容,输出到B文件/设备 (常见:> 输出重定向、>> 追加重定向、< 输入重定向)。

本质:修改进程fd_array数组中,对应fd下标指向的file指针,使其指向目标文件(而非默认设备)。

2 使用示例

那如果关闭1呢?看代码:

cs 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
 close(1);
 int fd = open("myfile", O_WRONLY|O_CREAT, 00644);
 if(fd < 0){
 perror("open");
 return 1;
 }
 printf("fd: %d\n", fd);
 fflush(stdout);
 close(fd);
 exit(0);
}

此时我们发现,原本应该输出到显示器的内容,最终输出到了文件 myfile 中,且该文件对应的文件描述符 fd=1。这种将标准输出(或输入 / 错误)的目标从默认设备(终端)改为文件的现象,称为输出重定向。常见的重定向符号有:>(覆盖重定向)、>>(追加重定向)、<(输入重定向)

那重定向的本质是什么呢?

3 使用dup2系统调用

函数原型如下:

cs 复制代码
#include <unistd.h>
int dup2(int oldfd, int newfd);
  • 将newfd重定向到oldfd,使newfd指向oldfd对应的文件

不过这个newfd和oldfd的名字取得不好,如果掉个个儿会好理解不少

示例代码:

cs 复制代码
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
 int fd = open("./log", O_CREAT | O_RDWR);
 if (fd < 0) {
 perror("open");
 return 1;
 }
 close(1);
 dup2(fd, 1);
 for (;;) {
 char buf[1024] = {0};
 ssize_t read_size = read(0, buf, sizeof(buf) - 1);
 if (read_size < 0) {
 perror("read");
 break;
 }
 printf("%s", buf);
 fflush(stdout);
 }
 return 0;
}

五 理解一切皆"文件"

我们认识世界事物的方式,是通过事物的属性,但是事物的属性是没有办法被穷举的

聊不了解的区别就是知道事物的属性的多少。每个人的属性类别是一样的,但是值都不同(内容不同)

例如:每个人都有身高,体重,但是每个人具体的数值不同

首先,在 Windows 中是文件的内容,在 Linux 中同样也是文件;其次,一些在 Windows 中不属于文件的东西,比如进程、磁盘、显示器、键盘这类硬件设备,在 Linux 中也都被抽象成了文件,你可以用访问文件的方式去访问它们并获取信息;甚至管道,也是文件;之后我们要学习网络编程中的 socket(套接字),它所使用的接口也和文件接口保持一致。

这样设计最直观的好处是:开发者只需要使用一套 API 和开发工具,就能调用 Linux 系统中绝大多数的资源。举个简单的例子:Linux 中几乎所有的读取操作(读取文件、读取系统状态、读取管道)都可以用 read 函数完成;几乎所有的修改操作(修改文件、修改系统参数、写入管道)都可以用 write 函数完成。

键盘,网卡,显示器这些外设,操作系统要对这些设备做管理--->先描述,再组织

怎样描述设备--->struct device

设备和设备最大的区别是属性值不同

cs 复制代码
struct device
{
  //name
  //vender
  //id
};

但是每个设备的读写方式不一样,有些只能读,有些只能写,有些既能读也能写

不同外设(键盘、网卡、显示器)的硬件操作逻辑不同,但可以通过定义统一的函数指针结构(比如struct device里的操作函数指针),让上层进程用相同的函数调用方式来操作不同设备。

进程角度不在关注底层角度的不同,用同一函数名访问所有外设---->只关系struct file所处软件层,叫做VFS虚拟文件系统
底层不同硬件的差异被函数指针 "屏蔽" 了,上层只需要调用统一的接口,不需要关心具体硬件的实现细节,这也是操作系统设备管理和虚拟文件系统(VFS)的核心思想之一。

底层原理图如下:

用同样的函数指针,指向底层的不同差异,这种技术叫什么?

**多态!**大部分语言都支持


六 缓冲区

1 定义

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

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

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

为了减少使用系统调用的次数,提高效率,我们就可以采用缓冲机制。比如我们从磁盘里取信息,可以在磁盘文件进行操作时,一次从文件中读出大量的数据到缓冲区中,以后对这部分的访问就不需要再使用系统调用了,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数。再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。

又比如,我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的CPU可以处理别的事情。可以看出,缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。

3 内核文件缓冲区

open、write这类系统调用是给用户层使用的抽象接口,和底层硬件的具体操作逻辑是分离的。比如往普通文件bg.txt写入和往磁盘写入的write,底层实现不同,但用户调用的接口是统一的。

write 的本质:write函数只是把数据从用户缓冲区 拷贝 到内核的文件缓冲区,这个操作完成后,write的功能就结束了,此时数据还没有真正写入磁盘

数据刷入磁盘的机制
数据要真正写入磁盘,需要额外的刷新动作:
刷新的策略(比如定时刷新、缓冲区满了再刷新)
刷新的执行者(由操作系统内核决定)

如果我想自己强制刷新呢·?

cs 复制代码
#include <unistd.h>

// 参数:打开文件的文件描述符 fd
// 返回值:成功返回 0,失败返回 -1(并设置 errno)
int fsync(int fd);

fsync是Linux系统调用
read 的工作流程

用户需要先定义自己的用户缓冲区。

调用read时,内核会先检查对应的文件缓冲区是否有目标数据:

如果没有,内核会先把数据从磁盘加载到文件缓冲区。

再把数据从文件缓冲区拷贝到用户自己的缓冲区。

write 本质是拷贝操作:数据先到内核缓冲区,不直接写磁盘。

read 依赖内核缓冲区:先检查内核缓冲区,没有再从磁盘加载,再拷贝到用户缓冲区。
缓冲区的作用:减少磁盘 IO 次数,提升读写效率,同时实现用户层和硬件层的解耦。

操作系统怎么知道,用户要读的数据还在不在内存?

根据文件的读写位置和已加载的数据量

页表里每一项,都标记了三种关键信息,对每一块数据(一页,一般 4KB),OS 都记着:

这页数据在物理内存的哪个位置

是否在内存里(有效位 / 存在位)

权限(读、写、执行)
只要看那个"存在位",OS 立刻知道:
✅ 1 = 在内存里
❌ 0 = 不在内存(在磁盘 / 交换区)

文件内核缓冲区不是线性结构的----以多叉树的形式存在

4 语言缓冲区

1 两个事实

a 调用系统调用也是有成本的---例如:空间配置器/内存池/vector扩容

申请内存必须通过系统调用完成,Linux 下常见的系统调用是:brk/sbrk,mmap

但是申请了内存,就一定会有内存给你吗?如果内存不足呢?

当用户申请内存时,OS 先标记虚拟地址空间,并不立刻分配物理内存。

真正使用内存时,才会分配物理页。

若物理内存不足:

OS 会自主触发内存回收:将不活跃进程的内存页换出到swap分区(交换分区)。

腾出新的物理内存后,再返回给申请的进程使用。

b 代码现象

cs 复制代码
int main() {
    // C库函数(带语言缓冲区)
    printf("hello printf\n");
    fprintf(stdout, "hello printf\n");
    const char *msg1 = "hello fputs\n";
    fputs(msg1, stdout);

    // 系统调用(无语言缓冲区,直接写入内核)
    const char *msg2 = "hello write\n";
    write(1, msg2, strlen(msg2));

    fork(); // 在所有IO之后创建子进程
    return 0;
}

运行结果:

stdout 连接终端 时是行缓冲,遇到\n就刷新缓冲区,fork()时缓冲区已空,无重复输出。

stdout 连接文件 时是全缓冲,缓冲区未填满且程序未结束时不会刷新,fork()会复制父进程的缓冲 区内容,父子进程退出时各自刷新,导致 C 库函数输出重复。

write 是系统调用,直接写入内核缓冲区,无语言缓冲,因此不会重复

层级 维护者 特点
语言缓冲区 C 标准库(FILE结构体) 用户态缓冲区,printf/fputs/fwrite等函数使用,缓冲策略由FILE决定(行缓冲 / 全缓冲 / 无缓冲)
OS 缓冲区 操作系统内核 内核态页缓存,write系统调用写入此处,由 OS 决定何时刷盘,对用户透明

5 缓冲类型

标准I/O提供了3种类型的缓冲区。

  • 全缓冲区:这种缓冲方式要求填满整个缓冲区后才进行I/O系统调用操作。对于磁盘文件的操作通常使用全缓冲的方式访问。

  • 行缓冲区:在行缓冲情况下,当在输入和输出中遇到换行符时,标准I/O库函数将会执行系统调用操作。当所操作的流涉及一个终端时(例如标准输入和标准输出),使用行缓冲方式。因为标准I/O库每行的缓冲区长度是固定的,所以只要填满了缓冲区,即使还没有遇到换行符,也会执行I/O系统调用操作,默认行缓冲区的大小为1024。

  • 无缓冲区:无缓冲区是指标准I/O库不对字符进行缓存,直接调用系统调用。标准出错流stderr通常是不带缓冲区的,这使得出错信息能够尽快地显示出来。

除了上述列举的默认刷新方式,下列特殊情况也会引发缓冲区的刷新:

  1. 缓冲区满时; 2. 执行flush语句; 3. 进程结束。
相关推荐
qq_254674411 小时前
Docker Docker Compose
运维·docker·容器
Aurora(^*_*^)2 小时前
docker 部署openclaw踩坑记录
运维·docker·容器
wanhengidc2 小时前
服务器托管对企业的作用
大数据·运维·服务器·分布式·智能手机
基于底层的菜鸟2 小时前
VsCode GitHub Copilot Chat 节省request
服务器·copilot·ai编程
Yupureki2 小时前
《Linux系统编程》15.进程间通信-管道
linux·运维·服务器·c语言·c++
Yupureki2 小时前
《Linux系统编程》14.库的制作与原理
linux·运维·服务器·c语言·开发语言·c++
正点原子2 小时前
瑞芯微工业级芯加持,正点原子RK3562J开发板/核心板解锁嵌入式开发新可能!
linux·ubuntu·嵌入式
路溪非溪2 小时前
Linux下wifi子系统的数据流
linux·arm开发·驱动开发
feng68_2 小时前
MySQL集群主从复制
linux·运维·数据库·mysql·adb