Linux(11)——基础IO(上)
目录
一、理解文件
二、回顾C文件的接口
📄 C语言文件操作函数表
编辑📄 三个文件流
三、系统文件I/O
1️⃣open
2️⃣close
3️⃣write
4️⃣read
四、文件描述符
💡用户操作文件的底层逻辑是什么?
💡什么是进程创建的时候会默认打开0、1、2?
五、文件描述符的分配规则
一、理解文件
我们来以三个问题来切入
💡文件大小为0时,要不要占用空间呢?
答案毋庸置疑是要的,因为除了内容之外,还有文件的相关属性,这里我们要明确一点就是文件=内容+属性(也叫元数据)。
💡访问文件需要打开文件,谁来打开呢?
实际上是进程来打开文件,对文件的操作本质上还是进程对文件的操作,而根本上还是系统对于文件的一系列的调用。
💡操作系统要不要管理被打开的文件,怎么管理?
答案是要管理,还是我们之前说的先描述,再组织。
二、回顾C文件的接口
📄 C语言文件操作函数表
函数名 | 功能说明 | 返回值说明 |
---|---|---|
fopen() | 打开一个文件 | 成功返回 FILE* ,失败 NULL |
fclose() | 关闭一个已打开的文件 | 成功返回 0 ,失败 EOF |
fread() | 从文件中读取数据 | 返回实际读取的元素数量 |
fwrite() | 向文件写入数据 | 返回实际写入的元素数量 |
fgetc() | 从文件读取一个字符 | 成功返回字符,失败 EOF |
fputc() | 向文件写入一个字符 | 成功返回字符,失败 EOF |
fgets() | 从文件读取一行字符串 | 成功返回字符串,失败 NULL |
fputs() | 向文件写入一个字符串 | 成功返回非负值,失败 EOF |
fprintf() | 向文件格式化输出 | 返回写入的字符数 |
fscanf() | 从文件格式化输入 | 成功读取项数 |
ftell() | 获取文件当前位置(偏移量) | 成功返回位置,失败 -1L |
fseek() | 设置文件位置指针 | 成功返回 0 ,失败非零 |
rewind() | 将文件位置指针重置到文件开头 | 无返回值 |
feof() | 检查文件是否到达 EOF | 是返回非零,否返回 0 |
ferror() | 检查文件操作是否发生错误 | 有错返回非零,无错返回 0 |
clearerr() | 清除文件错误和 EOF 标志 | 无返回值 |
remove() | 删除一个文件 | 成功返回 0 ,失败非零 |
rename() | 重命名一个文件 | 成功返回 0 ,失败非零 |
tmpfile() | 创建一个临时二进制文件 | 成功返回 FILE* ,失败 NULL |
setbuf() | 设置文件缓冲区 | 无返回值 |
setvbuf() | 设置缓冲方式和缓冲区大小 | 成功返回 0 ,失败非零 |
这里不过多介绍了,感兴趣的友友可以移步至:文件读写https://blog.csdn.net/2301_80065652/article/details/141182826
下面我们来写几个读写方面的例子:
写操作:
读操作:
下面这个函数一定要注意了,函数原型是:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数说明:
参数名 | 含义 |
---|---|
ptr | 指向存储读取数据的内存地址(缓冲区) |
size | 单个数据项的字节数 |
nmemb | 要读取的数据项个数 |
stream | 文件指针 |
返回值说明:返回成功读取的数据项数量(即不是字节数,是数据项个数),如果出错或到达文件末尾,返回值可能小于nmemb
。
所以下面这个代码是在每次读stelen(msg)(包含了回车)个字节,并在末尾置0以输出字符串。
输出到屏幕的操作:
其实这里的代码和上面的写操作是很一样的,这是因为输出到文件就是对屏幕文件进行写操作。

📄 三个文件流
我们现在要明确一个概念那就是“一切皆文件”,也就是说在Linux下一切都是可以用文件来表示的,包括但不限于键盘文件和屏幕文件,电脑通过从键盘文件中获取数据然后再向屏幕文件中写数据来显示我们输入的内容。
但是我们在实际操作的时候并没有说是去打开某个键盘文件或是屏幕文件,实际上是操作系统默认帮我们打开了三个文件流,他们分别是标准输入流(stdin),标准输出流(stdout)和标准错误流(stderr),同时呢他们又分别是键盘文件,显示器文件和显示器文件。
#include <stdio.h>extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;
我们发现这里的返回值类型和我们fwrite函数和fprintf函数中的参数类型是一样的,都是FILE*类型。
敲黑板:
其实我们见过的绝大多数语言是有这三个文件流的,比如Java和Python。
三、系统文件I/O
其实我们在C语言和C++中用用到的fopen(C语言)和ifstream(C++)都是语言层上面的调用,实际上呢在系统层面都是有自己行对应的接口的,而语言层只是对系统层的一个封装罢了,这样的封装更加易用且有自己的缓冲机制。接下来我们谈谈四大底层文件的接口:
1️⃣open
函数原型:
int open(const char *pathname, int flags, mode_t mode);
参数说明:
第一个参数pathname是要打开的文件名或是要创建的目标文件。
第二个参数是flags表示的是打开文件的方式,如图:
参数 | 含义 |
---|---|
O_RDONLY | 只读打开 |
O_WRONLY | 只写打开 |
O_RDWR | 读写打开 |
O_CREAT | 文件不存在则创建(需配合 mode 参数) |
O_APPEND | 写入时追加到文件尾 |
O_TRUNC | 如果文件存在则清空 |
O_EXCL | 与 O_CREAT 一起使用时,文件存在就报错 |
O_NONBLOCK | 非阻塞打开 |
这里就不得不提一下传递多个像这要的标志位的方法了:
给出一个例子来说明
#include <stdio.h>#define ONE 0001 //0000 0001#define TWO 0002 //0000 0010#define THREE 0004 //0000 0100void func(int flags) {if (flags & ONE) printf("flags has ONE! ");if (flags & TWO) printf("flags has TWO! ");if (flags & THREE) printf("flags has THREE! ");printf("\n");
}int main() {func(ONE);func(THREE);func(ONE | TWO);func(ONE | THREE | TWO);return 0;
}
就是说实际上这里的flags的传递也是这样的方式,我们可以在bits/fcntl-linux.h文件中见一见:
可以见到:
/* From /usr/include/asm-generic/fcntl.h */
#define O_RDONLY 00
#define O_WRONLY 01
#define O_RDWR 02
#define O_CREAT 0100 /* not fcntl */
#define O_EXCL 0200 /* not fcntl */
#define O_TRUNC 01000 /* not fcntl */
#define O_APPEND 02000
#define O_NONBLOCK 04000
第三个参数是mode,表示文件创建出来的权限,这是一个可选项(因为也可以只是打开文件):
我们在之前谈文件权限时提到了权限源码的概念,也就是说这里设置的权限需要进行计算(mode&(~umask))才是最终的权限,如果我们想让这个权限就是最终的权限可以将unmask设置为0。
返回值:
这个函数的返回值就是一个文件描述符(file descriptor),类型为int。
我么可以写个代码来看看:
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h> int main()
{ umask(0); int fd1 = open("test1.txt", O_RDONLY | O_CREAT, 0666); int fd2 = open("test2.txt", O_RDONLY | O_CREAT, 0666); int fd3 = open("test3.txt", O_RDONLY | O_CREAT, 0666); int fd4 = open("test4.txt", O_RDONLY | O_CREAT, 0666); int fd5 = open("test5.txt", O_RDONLY | O_CREAT, 0666); printf("fd1:%d\n", fd1); printf("fd2:%d\n", fd2); printf("fd3:%d\n", fd3); printf("fd4:%d\n", fd4); printf("fd5:%d\n", fd5); return 0;
}
我们也可以尝试打开不存在的文件试试:
我们发现他的文件描述符是-1。
这里的文件描述符实际上就是一个指针数组的下标,而指针数组中的指针就指向了被打开了的文件的文件的信息。这也就说明了我们打开文件其实就是依次增加被打开文件的个数相应的就增加了数组指针的下标了,而如果没有访问到这个文件那么就返回-1。但是我们会发现我们在上面的打印信息中的下标并不是从1开始的,这是因为我们之前讲的进程会默认打开三个文件,也就是标准输入0,标准输出1,标准错误2。
2️⃣close
这个接口没啥好说的了,就是关闭打开的文件:
#include <unistd.h>int close(int fd);
传入要关闭的文件描述符,如果关闭成功就返回0,否则返回-1。
示例:
3️⃣write
用于将数据从内存写入一个文件描述符所代表的目标:
#include <unistd.h>ssize_t write(int fd, const void *buf, size_t count);
参数说明:
fd:文件描述符
buf:指向要写入的数据的内存地址
count:要写入的字节数
返回值:
成功:返回写入的字节数(可能小于count)
失败:返回 -1
我们来写个代码来验证:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> int main()
{ int fd = open("test2.txt", O_WRONLY | O_CREAT, 0666); if(fd < 0){ perror("open errror"); return 1; } const char* msg = "hello xywl\n"; for(int i = 0; i < 5; i++){ write(fd, msg, strlen(msg)); } close(fd); return 0;
}
输出结果:
4️⃣read
用于从文件描述符中读取数据到内存:
#include <unistd.h>ssize_t read(int fd, void *buf, size_t count);
参数说明:
fd:文件描述符
buf:接收数据的内存缓冲区(通常是一个数组)
count:希望读取的最大字节数
返回值:
情况 | 返回值 |
---|---|
读取成功 | 实际读取的字节数(可能 < count) |
读到文件结尾 | 0 (EOF) |
出错 |
|
写个代码验证一下:
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h> int main() { int fd = open("example.txt", O_RDONLY); if (fd == -1) { perror("open"); return 1; } char buffer[128]; ssize_t bytes_read; while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) { write(1, buffer, bytes_read); } if (bytes_read == -1) { perror("read"); } close(fd); return 0;
}
输出结果:
四、文件描述符
其实文件描述符就是操作系统内核用于表示打开的文件的一个非整数,这个我们在之前的内容中已经说明了,那么接下来我们来回答几个问题:
💡用户操作文件的底层逻辑是什么?
我们都知道程序在运行起来的时候,操作系统会将代码和数据加载到内存之中,然后会创建对应的task_struct、mm_struct、页表等相关数据结构。我们管理文件就需要一个files_struct的结构体,我们创建的task_struct中有一个指针指向这个结构体,而这个结构体中就会有一个fd_arrray数组,这个数组中存的就是我们之前说的文件描述符了。
举个例子,我们的进程要打开一个log.txt的文件,我们首先要把文件加载到内存之中,然后形成struct file结构体并把它添加到双链表中,同时将该结构体的首地址填写到对应3下标的指针数组中,然后用户就可以获取了。
💡什么是进程创建的时候会默认打开0、1、2?
进程创建的时候后默认生成这三个的struct file并连接到了双链表中,因为他们是最先创建的,所以我们也不难知道他们分别对应了文件描述符的0,1,2的下标了,这样就默认打开了标准输入流,标准输出流和标准错误流。
五、文件描述符的分配规则
我们可以以一个之前写的代码来引入:
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h> int main()
{ umask(0); int fd1 = open("test1.txt", O_RDONLY | O_CREAT, 0666); int fd2 = open("test2.txt", O_RDONLY | O_CREAT, 0666); int fd3 = open("test3.txt", O_RDONLY | O_CREAT, 0666); int fd4 = open("test4.txt", O_RDONLY | O_CREAT, 0666); int fd5 = open("test5.txt", O_RDONLY | O_CREAT, 0666); printf("fd1:%d\n", fd1); printf("fd2:%d\n", fd2); printf("fd3:%d\n", fd3); printf("fd4:%d\n", fd4); printf("fd5:%d\n", fd5); return 0;
}
我们运行上面的发现实际上的fd是从3开始的,其实这个也不难理解,因为0,1,2是默认打开的,就相当于是已经被占用了。
所以我们这里可以试试关闭一些文件会怎么样,我们先还是关闭0来试试:
我们可以看到,代码变成了从0开始,然后再是3的递增。
接下来我们可以来试着将0和1都关闭来看看结果,这里之所以不管是因为他是标准输出流,没有他屏幕上将没有内容。
我们发现输出的fd变成了从0开始然后再是2的递增的数字。
结论:文件描述符是从最小但是没有被使用的fd_array数组下标开始进行分配的。
相关文章:

Linux(11)——基础IO(上)
目录 一、理解文件 二、回顾C文件的接口 📄 C语言文件操作函数表 编辑📄 三个文件流 三、系统文件I/O 1️⃣open 2️⃣close 3️⃣write 4️⃣read 四、文件描述符 💡用户操作文件的底层逻辑是什么? Ǵ…...

ABP-Book Store Application中文讲解 - Part 9: Authors: User Interface
ABP-Book Store Application中文讲解 - Part 9: Authors: User Interface TBD 1. 汇总 ABP-Book Store Application中文讲解-汇总-CSDN博客 2. 前一章 ABP-Book Store Application中文讲解 - Part 8: Authors: Application Layer-CSDN博客 项目之间的引用关系。 目…...
鸿蒙开发修改版本几个步骤
鸿蒙开发修改版本几个步骤 比如:5.0.4(16)版本改为5.0.2(14)版本 一、项目下的build-profile.json5 "products": [{"name": "default","signingConfig": "default&qu…...

Hive自定义函数案例(UDF、UDAF、UDTF)
目录 前提条件 背景 概念及适用场景 UDF(User-Defined Function) 概念 适用场景 UDAF(User-Defined Aggregate Function) 概念 适用场景 UDTF(User-Defined Table-Generating Function) 概念 适…...

【学习笔记】Circuit Tracing: Revealing Computational Graphs in Language Models
Circuit Tracing: Revealing Computational Graphs in Language Models 替代模型(Replacement Model):用更多的可解释的特征来替代transformer模型的神经元。 归因图(Attribution Graph):展示特征之间的相互影响,能够追踪模型生成输出时所采用…...
3D视觉重构工业智造:解码迁移科技如何用“硬核之眼“重塑生产节拍
一、工业视觉的进化论:从CCD到3D相机的范式革命 在汽车冲压车间里,传统CCD相机正面临四大检测困局: 平面感知局限:二维视觉无法捕捉曲面工件形变环境适应性差:反光板件导致误检率超12%动态捕捉延迟:传送带…...
Elasticsearch中的刷新(Refresh)和刷新间隔介绍
在 Elasticsearch 中,刷新(Refresh) 是控制索引数据何时对搜索可见的机制,而 刷新间隔(Refresh Interval) 则是配置该机制执行频率的参数。理解这两个概念对于平衡搜索实时性与写入性能至关重要。 一、刷新(Refresh)的本质 Lucene 索引结构与搜索可见性Elasticsearch …...

STM32标准库-TIM定时器
文章目录 一、TIM定时器1.1定时器1.2定时器类型1.1.1 高级定时器1.1.2通用定时器1.1.3基本定时器 二、定时中断基本结构预分频器时器计时器时序计数器无预装时序计数器有预装时序RCC时钟树 三、定时器定时中断3.1 接线图3.2代码3.3效果: 四、定时器外部中断4.1接线图…...
【算法训练营Day05】哈希表part1
文章目录 哈希表理论基础有效的字母异位词两个数组的交集快乐数两数之和 哈希表理论基础 几个值得关注的知识点: hash表用于快速的判断元素是否存在(空间换时间)其原理就是将数据通过散列函数映射到bucket中,如果发生hash碰撞&a…...
CMap应用场景和例子
CMap 详解 CMap 是 MFC (Microsoft Foundation Classes) 库中的一个模板类,用于实现键值对的映射关系(类似哈希表或字典)。它提供了高效的数据存储和检索功能,适用于需要通过键快速查找值的场景。 基本模板参数 cpp 运行 tem…...

Kafka 如何保证顺序消费
在消息队列的应用场景中,保证消息的顺序消费对于一些业务至关重要,例如金融交易中的订单处理、电商系统的库存变更等。Kafka 作为高性能的分布式消息队列系统,通过巧妙的设计和配置,能够实现消息的顺序消费。接下来,我…...

【算法题】算法一本通
每周更新至完结,建议关注收藏点赞。 目录 待整理文章已整理的文章方法论思想总结模版工具总结排序 数组与哈希表栈双指针(滑动窗口、二分查找、链表)树前缀树堆 优先队列(区间/间隔问题、贪心 )回溯图一维DP位操作数学…...

Modbus转Ethernet IP赋能挤出吹塑机智能监控
在现代工业自动化领域,小疆智控Modbus转Ethernet IP网关GW-EIP-001与挤出吹塑机的应用越来越广泛。这篇文章将为您详细解读这两者的结合是如何提高生产效率,降低维护成本的。首先了解什么是Modbus和Ethernet IP。Modbus是一种串行通信协议,它…...
C++中如何遍历map?
文章目录 1. 使用范围for循环(C11及以上)2. 使用迭代器3. 使用反向迭代器注意事项 在C中, std::map 是一种关联容器,它存储的是键值对(key-value pairs),并且按键的顺序进行排序。遍历 std::m…...

什么是终端安全管理系统(终端安全管理软件2024科普)
在当今数字化迅速发展的时代,企业面临着越来越多的信息安全威胁。为了应对这些挑战,保障公司数据的安全性和完整性,终端安全管理系统(Endpoint Security Management System)应运而生。 本文将为您深入浅出地科普2024年…...
书籍转圈打印矩阵(8)0604
题目 给定一个整型矩阵matrix,请按照转圈的方式打印它。 例如: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 打印结果为:1,2,3ÿ…...

【JVM】Java类加载机制
【JVM】Java类加载机制 什么是类加载? 在 Java 的世界里,每一个类或接口在经过编译后,都会生成对应的 .class 字节码文件。 所谓类加载机制,就是 JVM 将这些 .class 文件中的二进制数据加载到内存中,并对其进行校验…...
Elasticsearch中的自定义分析器(Custom Analyzer)介绍
在 Elasticsearch 中,自定义分析器(Custom Analyzer) 是一种可配置的文本处理组件,允许用户通过组合分词器(Tokenizer)、过滤器(Token Filter)和字符过滤器(Character Filter)来定义特定的文本分析逻辑。这使得 Elasticsearch 能够针对不同语言、业务场景或特殊需求,…...

《C++初阶之入门基础》【C++的前世今生】
【C的前世今生】目录 前言:---------------起源---------------一、历史背景二、横空出世---------------发展---------------三、标准立世C98:首个国际标准版本C03:小修订版本 四、现代进化C11:现代C的开端C14:对C11的…...

Apache APISIX
目录 Apache APISIX是什么? Lua Lua 的主要特点: Lua 的常见应用: CVE-2020-13945(Apache APISIX默认API Token导致远程Lua代码执行) 编辑Lua脚本解析 CVE-2021-45232(Apache APISIX Dashboard API权限绕过导致RCE) Apache …...

如何在 git dev 中创建合并请求
先将 自己的代码 推到 自己的远程的 分支上 在 创建 合并请求 根据提示 将 自己的远程的 源码 合并到 对应的分支上 然后 创建 合并请求 等待 对应的 人 来 进行合并就行...

基于nlohmann/json 实现 从C++对象转换成JSON数据格式
C对象的JSON序列化与反序列化 基于JsonCpp库实现C对象序列化与反序列化 JSON 介绍 JSON作为一种轻量级的数据交换格式,在Web服务和应用程序中广泛使用。 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读…...
Java枚举类映射MySQL的深度解析与实践指南
Java枚举类映射MySQL的深度解析与实践指南 一、枚举类型映射的四大核心策略 1. 序数映射法(ordinal映射) 实现原理:存储枚举值的下标顺序 public enum OrderStatus {PENDING, // 存储为0PROCESSING, // 存储为1SHIPPED, //…...
代码训练LeetCode(21)跳跃游戏2
代码训练(21)LeetCode之跳跃游戏2 Author: Once Day Date: 2025年6月4日 漫漫长路,才刚刚开始… 全系列文章可参考专栏: 十年代码训练_Once-Day的博客-CSDN博客 参考文章: 45. 跳跃游戏 II - 力扣(LeetCode)力扣 (LeetCode) 全球极客挚爱…...
【HarmonyOS 5】鸿蒙APP使用【团结引擎Unity】开发的案例教程
以下是基于团结引擎开发鸿蒙Unity应用的详细案例教程,整合环境配置、工程适配、跨语言通信等核心环节 一、环境配置(关键前置步骤) 1. 工具安装 工具版本要求作用团结引擎Hub≥1.2.3Unity鸿蒙项目构建管理DevEco Studio≥…...

《T/CI 404-2024 医疗大数据智能采集及管理技术规范》全面解读与实施分析
规范背景与详细信息 《T/CI 404-2024 医疗大数据智能采集及管理技术规范》是由中国国际科技促进会联合河南科技大学、河南科技大学第一附属医院、深圳市人民医院等十余家医疗机构与企业共同制定的团体标准,于2024年5月正式发布实施。该规范是我国医疗大数据领域的重要技术标准…...

国产三维CAD皇冠CAD在「金属压力容器制造」建模教程:蒸汽锅炉
面对蒸汽锅炉设计中复杂的曲面封头、密集的管板开孔、多变的支撑结构以及严格的强度与安全规范(如GB150、ASME等),传统二维设计手段往往捉襟见肘,易出错、效率低、协同难。国产三维CAD皇冠CAD(CrownCAD)凭借…...
Mysql避免索引失效
1. 在索引列上使用函数或表达式 问题描述 SELECT * FROM users WHERE YEAR(create_time) 2023; 如果create_time列上有索引,上述查询会导致索引失效,因为MySQL无法直接利用索引的B树结构。 解决方法 将函数应用于条件值,而不是列&#…...
python爬虫:Ruia的详细使用(一个基于asyncio和aiohttp的异步爬虫框架)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Ruia概述1.1 Ruia介绍1.2 Ruia特点1.3 安装Ruia1.4 使用案例二、基本使用2.1 Request 请求2.2 Response - 响应2.3 Item - 数据提取2.4 Field 提取数据2.5 Spider - 爬虫类2.6 Middleware - 中间件三、高级功能3.1 …...

C++中单例模式详解
在C中,单例模式 (Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。这在需要一个全局对象来协调整个系统行为的场景中非常有用。 为什么要有单例模式? 在许多项目中,某些类从逻辑上讲只需要一个实…...