6. 基础IO

article/2025/6/7 23:18:22

0.背景

a.访问一个文件,都必须先把对应的文件打开(打开文件就是把它从磁盘加载到内存中)

b.如果一个文件,压根就没有被打开,那么它就在磁盘上

c.谁来打开??用户通过bash,启动进程打开这个文件的->fopen(系统调用)->操作系统->进程通过操作系统打开文件

d.OS内,一定同时存在大量的被打开的文件->OS如何管理这些被打开的文件呢(先描述,再组织)->一定存在一种数据结构体,描述被打开的文件!!如同pcb一样!

e.进程有task_struct,未来进程也有打开的文件,我们研究打开文件,本质是研究:进程与文件的关系!!!

1.理解“文件”

1.0 结论:

打开文件,必须先找到文件,要找到文件,就必须知道文件的路径+文件名,这就是为什么进程要有cwd(查找文件的默认路径!)的原因之一。

#include<>从系统默认目录中查找;#include“”优先从当前源文件目录找,找不到再从系统默认目录中查找。

1.1狭义理解:

• ⽂件在磁盘⾥
• 磁盘是永久性存储介质,因此⽂件在磁盘上的存储是永久性的
• 磁盘是外设(即是输出设备也是输⼊设备)
• 磁盘上的⽂件 本质是对⽂件的所有操作,都是对外设的输⼊和输出 简称 IO

1.2广义理解:

Linux下一切皆文件(键盘、显⽰器、⽹卡、磁盘…… 这些都是抽象化的过程

1.3文件操作:

• 对于 0KB 的空⽂件是占⽤磁盘空间的
• ⽂件是⽂件属性(元数据)和⽂件内容的集合(⽂件 = 属性(元数据)+ 内容)
• 所有的⽂件操作本质是⽂件内容操作和⽂件属性操作

1.4系统角度:

• 对⽂件的操作本质是进程对⽂件的操作
• 磁盘的管理者是操作系统
• ⽂件的读写本质不是通过 C 语⾔ / C++ 的库函数来操作的(这些库函数只是为⽤⼾提供⽅便),⽽
是通过⽂件相关的系统调⽤接⼝来实现的

2.回顾C文件接口

2.0背景知识:

fopen是用于打开文件的标准库函数 。

filename : 是一个指向字符串的指针,该字符串包含了要打开文件的路径及文件名 。 

mode :也是一个指向字符串的指针,用于指定打开文件的方式,r读,w写,a追加等等。

 函数调用成功时,返回一个指向 FILE 类型结构体的指针,该指针指向已打开文件的相关信息,后续可通过这个指针进行文件读写等操作;若文件打开失败,返回 NULL ,并且会设置全局变量 errno 来指示错误类型 。

fclose用于关闭一个已经打开的文件

stream 是指向需要关闭的文件的指针 ,该指针是通过 fopen 等函数打开文件时返回的

 2.1 hello.c打开文件

打开的myfile⽂件在哪个路径下?
在程序的当前路径下,那系统怎么知道程序的当前路径在哪⾥呢?
可以使⽤ ls /proc/[ 进程 id] -l 命令查看当前正在运⾏进程的信息:

1.cwd:指向当前进程运行目录的一个符号链接

2.exe:指向启动当前进程的可执行文件(完整路径)的符号链接

打开⽂件,本质是进程打开,所以,进程知道⾃⼰在哪⾥,即便⽂件不带路径,进程也知道。由此OS就能知道要创建的⽂件放在哪⾥。

2.2 hello.c写文件

 fwrite用于向文件写入数据块

 写入位置与文件打开模式有关,比如 w+ 模式从文件指针位置开始写,可能覆盖原有内容;a+ 模式从文件末尾追加内容 。

若成功执行,返回实际写入的数据项个数。若此返回值与 nmemb 不同,表明写入过程出错。比如磁盘空间不足、文件权限问题等会导致写入错误。

注意:fwrite 写入的数据先存于用户空间缓冲区,不会立即同步到文件,若要及时更新文件内容,可调用 fflush 函数。

2.3 hello.c读文件 

 fread用于从文件中读取数据(函数参数与fwrite类似)

fread 函数从 stream 所指向的文件中,按二进制形式读取数据,并将其存储到 ptr 指向的内存区域

 feof用于判断文件指针是否已经到达文件末尾。在循环读取文件内容时,可以帮助我们确定何时停止读取。

 2.4 输出信息到显示器

我今天向显示器写入12345,我并不是写入了一个int 12345,而是向显示器写入了‘1’ ‘2’ ‘3’ ‘4’ ‘5’

同理我今天通过键盘,输入12345,其实是输入了‘1’ ‘2’ ‘3’ ‘4’ ‘5’。所以,显示器和键盘都叫做字符设备,他们都是文本文件。

int x = 12345;
printf("%d", x);12345->'1','2','3','4','5'   --->格式化输出scanf()                 
1,2,3,4,5 -> 12345 -> &a  --->格式化输入    二进制文件和文本文件是由文件本身属性决定的。

2.5 stdin & stdout & stderr

• C默认会打开三个输⼊输出流,分别是stdin, stdout, stderr
• 仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,⽂件指针#include <stdio.h>
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;

3.系统文件I/O

打开⽂件的⽅式不仅仅是fopen,ifstream等流式,语⾔层的⽅案,其实系统才是打开⽂件最底层的⽅案。

3.1 一种传递标志位的方法

3.2 hello.c 写文件:

• 上⾯的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数
(libc)。
• ⽽ open close read write lseek 都属于系统提供的接⼝,称之为系统调⽤接⼝

 系统调用接口和库函数的关系,一目了然。

所以,可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。

1.为什么C语言要封装文件操作接口?2.为什么大部分语言,都要对系统调用做封装?

跨平台性(主要原因,来增加语言竞争力);系统调用麻烦(次要原因)。 

3.3 hello.c 读文件:


*3.4 文件描述符fd(本质是进程的文件描述符表的下标)

通过对open函数的了解(其返回值是文件描述符),文件描述符是一个小整数

Linux进程默认情况下会有3个缺省打开的⽂件描述符,分别是标准输⼊0, 标准输出1, 标准错
误2。
0,1,2对应的物理设备⼀般是:键盘,显⽰器,显⽰器。
 

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

FILE是什么?->C标准库内定义的一个结构体!->推测FILE结构体里面,一定要封装一个整数,一定是fd 

 write(3,“hello world”) ->用户定义的缓冲区内部保存的数据!->所以:write根本就不是写入到文件,write的本质:是拷贝函数!把数据从用户空间拷贝到对应文件的内核缓冲区中!

 3.4.2 文件描述符分配规则

在files_struct数组当中,从文件描述符表数组中找到当前没有被使用的最小的⼀个下标,作为新的文件描述符。

3.4.3 重定向

系统进行重定向必须先打开这个文件!

那么,关闭1会发生什么?

本来应该输出到显⽰器上的内容,输出到了⽂件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。常⻅的重定向有: > , >> , <。
那么,重定向的本质是什么?

*3.4.4 使用 dup2 系统调用(还得写)

 所以我们有了重定向的概念和本质理解,那么我们创建子进程,子进程是如何看待父进程打开的文件的?子进程会继承父进程的PCB,并对文件描述符表(files_struct)进行指针的拷贝(就是浅拷贝,指针指向的仍是父进程的files_struct), 所以,父子printf的时候,会同时向同一个显示器文件打印。

那么对于子进程来讲,它有没有自己打开0,1,2呢?->事实上子进程默认打开了标准输入,标准输出,标准错误。->所以它是通过父进程继承下来的->父进程本来就是打开的。

*3.4.5 在myshell中添加重定向功能

4.理解一切皆文件

一切皆文件!是站在进程的视角,在struct file结构体之上,看待文件的视角!!

⾸先,在windows中是⽂件的东西,它们在linux中也是⽂件;其次⼀些在windows中不是⽂件的东
西,⽐如进程、磁盘、显⽰器、键盘这样硬件设备也被抽象成了⽂件,你可以使⽤访问⽂件的⽅法访问它们获得信息;甚⾄管道,也是⽂件;⽹络编程中的socket(套接字)这样的东西,使⽤的接⼝跟⽂件接⼝也是⼀致的。
这样做最明显的好处是,开发者仅需要使用⼀套 API 和开发工具,即可调取 Linux 系统中绝大部分的资源。比如,Linux 中⼏乎所有读(读⽂件,读系统状态,读PIPE)的操作都可以⽤read 函数来进⾏;⼏乎所有更改(更改⽂件,更改系统参数,写 PIPE)的操作都可以⽤ write 函数来进⾏。
当打开⼀个⽂件时,操作系统为了管理所打开的⽂件,都会为这个⽂件创建⼀个file结构体。
struct file 中的 f_op 指针指向了⼀个 file_operations 结构体,这个结构体中的成员除了struct module* owner 其余都是函数指针。该结构和 struct file 都在fs.h下。
file_operation 就是把系统调⽤和驱动程序关联起来的关键数据结构,这个结构的每⼀个成员都
对应着⼀个系统调⽤。读取 file_operation 中相应的函数指针,接着把控制权转交给函数,从⽽
完成了Linux设备驱动程序的⼯作。
上图中的外设,每个设备都可以有⾃⼰的read、write,但⼀定是对应着不同的操作⽅法!!但通过
struct file file_operation 中的各种函数回调,让我们开发者只⽤file便可调取 Linux 系统中绝⼤部分的资源!!这便是“linux下⼀切皆⽂件”的核⼼理解。
Linux中,打开文件,要为我们创建struct file,三个核心:
1.文件属性
2.文件内核缓冲区
3.底层设备文件的操作表

5.缓冲区

5.1什么是缓冲区

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

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

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

为了减少使⽤系统调⽤的次数,提⾼效率,我们就可以采⽤缓冲机制。⽐如我们从磁盘⾥取信息,可以在磁盘⽂件进⾏操作时,可以⼀次从⽂件中读出⼤量的数据到缓冲区中,以后对这部分的访问就不需要再使⽤系统调⽤了,等缓冲区的数据取完后再去磁盘中读取,这样就可以 减少磁盘的读写次数,再加上计算机对缓冲区的操作大快于对磁盘的操作,故应用缓冲区可大提高计算机的运行速度。
⼜⽐如,我们使⽤打印机打印⽂档,由于打印机的打印速度相对较慢,我们先把⽂档输出到打印机相应的缓冲区,打印机再⾃⾏逐步打印,这时我们的CPU可以处理别的事情。可以看出,缓冲区就是⼀块内存区,它⽤在输⼊输出设备和CPU之间,⽤来缓存数据。它使得低速的输⼊输出设备和⾼速的CPU能够协调⼯作,避免低速的输⼊输出设备占⽤CPU,解放出CPU,使其能够⾼效率⼯作。
总结:缓存最大的意义(可以把他想象成菜鸟驿站)
1.提高使用缓存的进程的效率->允许进程单位时间内,做更多的工作2.允许数据,在缓冲区中积压,以一次,就可以刷新多次数据,变相的减少IO次数

5.3缓冲类型

标准I/O提供了3种类型的缓冲区。
1.全缓冲区:这种缓冲⽅式要求填满整个缓冲区后才进⾏I/O系统调⽤操作。对于磁盘⽂件的操作通
常使⽤全缓冲的⽅式访问。2.⾏缓冲区:在⾏缓冲情况下,当在输⼊和输出中遇到换⾏符时,标准I/O库函数将会执⾏系统调⽤
操作。当所操作的流涉及⼀个终端时(例如标准输⼊和标准输出),使⽤⾏缓冲⽅式。因为标准
I/O库每⾏的缓冲区⻓度是固定的,所以只要填满了缓冲区,即使还没有遇到换⾏符,也会执⾏
I/O系统调⽤操作,默认⾏缓冲区的⼤⼩为1024。3.⽆缓冲区:⽆缓冲区是指标准I/O库不对字符进⾏缓存,直接调⽤系统调⽤。标准出错流stderr通
常是不带缓冲区的,这使得出错信息能够尽快地显⽰出来。除了上述列举的默认刷新⽅式,下列特殊情况也会引发缓冲区的刷新:
1. 缓冲区满时;
2. 执⾏flush语句;

1.我们之前所说的缓冲区,全是语言级缓冲区,和内核没有关系。

2.缓冲区在哪?->FILE内部。

3.为什么要有语言级别的缓冲区?->系统调用有成本(浪费时间)->减少系统调用次数。

4.重新理解一下printf,scanf的格式化过程:printf("%d", a); ->格式化到哪里了?-> 格式化结果写入FILE缓冲区中- > 检测是否要刷新 -> write(3, outbuffer);

也就是说有语言级缓冲区,还有一个系统级缓冲区(一般而言:全缓冲;显示器:行刷新),我们平时写的会先放入语言级缓冲区,满足一定条件后才会转入系统级缓冲区

内核关于文件缓冲区的刷新方式:一般而言全缓冲;显示器,行刷新。-》但有单独的执行流,根据内存的使用情况来动态刷新,即便刷新条件不满足。

*5.4FILE

正常有\n应该是按行刷新(往显示器上写),但是,现在是往文件里重定向,所以他在底层进行了刷新策略的隐式调整-》变为全缓冲!!!

正常fork结束后不应该清空缓冲区吗?为什么还会刷新两次?清空缓冲区就是对数据修改-》写实拷贝666 

我们发现 printf fwrite (库函数)都输出了2次,⽽ write 只输出了⼀次(系统调⽤)。为
什么呢?肯定和fork有关!
• ⼀般C库函数写⼊⽂件时是全缓冲的,⽽写⼊显⽰器是⾏缓冲。
• printf fwrite 库函数+会⾃带缓冲区(进度条例⼦就可以说明),当发⽣重定向到普通⽂
件时,数据的缓冲⽅式由⾏缓冲变成了全缓冲。
• ⽽我们放在缓冲区中的数据,就不会被⽴即刷新,甚⾄fork之后
• 但是进程退出之后,会统⼀刷新,写⼊⽂件当中。
• 但是fork的时候,⽗⼦数据会发⽣写时拷⻉,所以当你⽗进程准备刷新的时候,⼦进程也就有了
同样的⼀份数据,随即产⽣两份数据。
• write 没有变化,说明没有所谓的缓冲。

所以重定向那份结果,是父/子结束一次刷新一次缓冲区,导致有两份重复的代码。

为什么要存在perror,std::cerr? 为了让正常输出和错误输出进行分离

5.5简单设计一下libc库


http://www.hkcw.cn/article/mtgydvssew.shtml

相关文章

碰一碰发视频-源码系统开发技术分享

#碰一碰营销系统# #碰一碰系统# #碰一碰发视频# 架构设计哲学&#xff1a;近场通信的优雅平衡 一、核心通信技术选型 1. 双模协同传输引擎 技术协议栈延迟控制适用场景NFCISO 14443-A<100ms精准触发场景BLE 5.0GATT Profile300-500ms中距传输场景 工程决策依据&…

动态规划之网格图模型(二)

文章目录 动态规划之网格图模型&#xff08;二&#xff09;LeetCode 931. 下降路径最小和思路Golang 代码 LeetCode 2684. 矩阵中移动的最大次数思路Golang 代码 LeetCode 2304. 网格中的最小路径代价思路Golang 代码 LeetCode 1289. 下降路径最小和 II思路Golang 代码 LeetCod…

QUIC——UDP实现可靠性传输

首先我们要知道TCP存在什么样的痛点问题 TCP的升级很困难TCP建立连接的延迟网络迁移需要重新建立连接TCP存在队头阻塞问题 QUIC就是为了解决以上的问题而诞生了, 下面我会介绍QUIC的一些特性和原理 QUIC对比TCP优势: 握手建连更快 QUIC内部包含了TLS, 它在自己的帧会携带TL…

PyTorch——线性层及其他层介绍(6)

线性层 前面1,1,1是你想要的&#xff0c;后面我们不知道这个值是多少&#xff0c;取-1让Python自己计算 import torch import torchvision from torch import nn from torch.nn import Linear from torch.utils.data import DataLoader# 加载CIFAR-10测试数据集并转换为Tensor格…

bilibili批量取消关注

目录 如何使用 ​编辑 代码 如何使用 使用谷歌浏览器&#xff0c;通过F12打开调式面板&#xff0c;找到下面的位置&#xff1a; 代码 /*** 批量取消关注脚本* 自动遍历多页内容并取消所有关注*/// 配置常量 const CONFIG {CLICK_DELAY: 250, // 点击间隔时间&#…

7.RV1126-OPENCV cvtColor 和 putText

一.cvtColor 1.作用 cvtColor 是 OPENCV 里面颜色转换的转换函数。能够实现 RGB 图像转换成灰度图、灰度图转换成 RGB 图像、RGB 转换成 HSV 等等 2.API CV_EXPORTS_W void cvtColor( InputArray src, OutputArray dst, int code, int dstCn 0 ); 第一个参数&#xff1a;…

研发型企业如何面对源代码保密问题

在当今数字化时代&#xff0c;研发团队面临着数据安全和工作效率的双重挑战。技术成果和源代码不仅是企业的核心资产&#xff0c;更是企业竞争力的基石。然而&#xff0c;数据泄露的风险无处不在&#xff0c;从内部员工的无意失误到外部攻击者的恶意窃取&#xff0c;都可能给企…

BeeWorks:私有化即时通讯,筑牢企业信息安全防线

在数字化时代&#xff0c;即时通讯已成为企业日常运营中不可或缺的工具。然而&#xff0c;数据安全问题一直是企业使用即时通讯服务时的重要考量因素。BeeWorks即时通讯系统以其私有化部署模式&#xff0c;为企业提供了一个安全、可靠、自主可控的沟通平台。 私有化部署&#…

akka实践之应用的扩展性问题和actor模型

如何解决应用的扩展性问题 当一个应用需要处理海量并发请求时&#xff0c;传统的开发模式往往显得力不从心&#xff0c;为什么应用需要扩展性&#xff1f; 需求增长: 用户量激增&#xff0c;数据量爆炸式增长。资源限制: 服务器、带宽、存储等资源有限。复杂性增加: 代码逻辑…

Starrocks Full GC日志分析

GC日志样例&#xff1a; [2025-06-03T07:36:06.1770800] GC(227) Pause Full (G1 Evacuation Pause) [2025-06-03T07:36:06.1960800] GC(227) Phase 1: Mark live objects [2025-06-03T07:36:06.9480800] GC(227) Cleaned string and symbol table, strings: 47009 processed,…

mapbox高阶,生成并加载等时图

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️mapboxgl.Map style属性1.3 ☘️Fill面图层样式1.4 ☘️symbol符号图层…

防火墙在OSI模型中的层级工作(2025)

1. 物理层&#xff08;L1&#xff09;& 数据链路层&#xff08;L2&#xff09; 传统防火墙&#xff1a;通常不处理L1/L2&#xff08;由交换机/网卡负责&#xff09;。 现代演进&#xff1a; MAC地址过滤&#xff1a;部分防火墙支持基于MAC地址的粗粒度策略&#xff08;如禁…

帝可得 - 运营管理APP

Android模拟器 本项目的App客户端部分已经由前端团队进行开发完成&#xff0c;并且以apk的方式提供出来&#xff0c;供我们测试使用&#xff0c;如果要运行apk&#xff0c;需要先安装安卓的模拟器。 可以选择国内的安卓模拟器产品&#xff0c;比如&#xff1a;网易mumu、雷电…

关于list集合排序的常见方法

目录 1、list.sort() 2、Collections.sort() 3、Stream.sorted() 4、进阶排序技巧 4.1 空值安全处理 4.2 多字段组合排序 4.3. 逆序 5、性能优化建议 5.1 并行流加速 5.2 原地排序 6、最佳实践 7、注意事项 前言 Java中对于集合的排序操作&#xff0c;分别为list.s…

自然语言处理(NLP)的系统学习路径规划

文章目录 一、基础准备阶段&#xff08;1-2个月&#xff09;1. 数学基础2. 编程基础3. 语言学基础 二、核心技术阶段&#xff08;3-4个月&#xff09;1. 经典NLP技术2. 深度学习模型3. 预训练模型入门 三、进阶实战阶段&#xff08;2-3个月&#xff09;1. 热门任务实战2. 大模型…

CSS3美化页面元素

1. 字体 <span>标签 字体样式⭐ 字体类型&#xff08;font-family&#xff09; 字体大小&#xff08;font-size&#xff09; 字体风格&#xff08;font-style&#xff09; 字体粗细&#xff08;font-weight&#xff09; 字体属性&#xff08;font&#xff09; 2. 文本 文…

便签软件哪个好用,最好用的免费便签软件介绍

在快节奏的工作和生活中&#xff0c;一款好用的便签软件能帮助我们高效记录灵感、管理待办事项&#xff0c;甚至成为个人生产力系统的核心工具。2025年&#xff0c;市面上涌现了许多优秀的免费便签软件&#xff0c;它们各具特色&#xff0c;能满足不同用户的需求。便签软件哪个…

如何轻松删除 Android 上的文件(3 种方法)

Android 手机是非常强大的设备&#xff0c;可让我们存储大量的个人数据&#xff0c;从照片和视频到应用程序和文档。然而&#xff0c;随着时间的推移&#xff0c;您的设备可能会因不再需要的文件而变得混乱。删除这些文件有助于释放空间并提高性能。在本指南中&#xff0c;我们…

鸿蒙简易版影视APP案例实战

目录 1. 案例效果 2. 资源初始化和资源文件 2.1. string.json (en_US) 2.2. string.json (zh_CN) 2.3. constants 3. 视频列表 3.1. 顶部导航 3.1.1. TobBar 组件 3.1.2. TopBar 数据源 3.2. 全部分类内容页面 3.2.1. 全部分类组件 3.2.2. 轮播图组件 3.2.3. 图片列…

对于python中“FileNotFoundError: [Errno 2] No such file or directory”的解决办法

写在前面 最近在使用 vscode 写代码 (python) 时发现使用相对路径读取文件以及写入文件时&#xff0c;想要直接在当前目录下读写一直提示没有该文件&#xff0c;需要返回根目录。并且使用 vscode 自带调试"F5"以及 Code Runner 扩展即右上角三角形都是如此。参考了许…