【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. 进程结束。
相关推荐
A小辣椒9 小时前
TShark:Wireshark CLI 功能
linux
A小辣椒13 小时前
TShark:基础知识
linux
AlfredZhao15 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao1 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334661 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
大树882 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质2 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式