目录

1、理解“文件”

1.1 狭义理解

1.2 广义理解

1.3 文件操作的归类认知

1.4 系统角度

2、回顾C文件接口

2.1 文件的打开与关闭

2.2 文件的读写函数

2.3 stdin & stdout & stderr

3、系统文件I/O

3.1 一种传标志位的方式

3.2 文件的系统调用接口

3.2.1 open()

3.2.2 read() & write() & close()

3.3 库函数和系统调用

3.4 文件描述符fd

3.4.1 0 & 1 & 2

3.4.2 文件描述符的分配规则

3.4.3 重定向

3.4.4 重定向系统调用dup2()

4、理解一切皆文件

5、缓冲区

5.1 缓冲区的定义

5.2 缓冲区的作用

5.3 缓冲区的机制

现象1:

现象2:


1、理解“文件”

1.1 狭义理解

  • 文件在磁盘里
  • 磁盘是永久性存储介质,因此文件在磁盘上永久性存储
  • 磁盘是外设(即是输出设备也是输入设备)。
  • 对磁盘文件的所有操作(如读取、写入)本质上都是对外设的输入/输出,简称I/O(Input/Output)。

1.2 广义理解

  • Linux中,一切皆文件(键盘、显示器、网卡、磁盘……这些都是抽象化的过程)(后面会深入理解)。

1.3 文件操作的归类认知

  • 文件 = 属性(元数据)+ 内容
  • 对于0KB的空文件是占用磁盘空间的,有文件属性。
  • 所有的文件操作本质文件内容操作文件属性操作

1.4 系统角度

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

文件分为“内存级(被打开)”文件“磁盘级(未打开)”文件

本节主讲“内存级(被打开)”文件

2、回顾C文件接口

2.1 文件的打开与关闭

FILE *fopen(const char *path, const char *mode);

mode含义文件不存在时文件存在时写入方式
"r"只读返回 NULL正常打开不可写入
"r+"读写返回 NULL正常打开从当前位置覆盖
"w"只写(新建)新建文件清空原内容从头写入
"w+"读写(新建)新建文件清空原内容从头写入
"a"追加(只写)新建文件保留内容,追加到末尾只能末尾追加
"a+"追加(读写)新建文件保留内容,可读/追加写入可读,但写入仅限末尾

int fclose(FILE *fp);

注意:

ls /proc/[ 进程 id] -l 命令,查看当前正在运行进程的信息。

  • cwd:指向进程的当前工作目录创建文件和打开文件的默认路径
  • exe:指向启动当前进程的可执行文件的路径

2.2 文件的读写函数

函数名功能描述适用流类型参数说明返回值备注
fgetc从流中读取单个字符所有输入流
(如stdin、文件)
FILE *stream(文件指针)读取的字符(int
失败返回EOF
通常用于逐字符处理
fputc向流写入单个字符所有输出流
(如stdout、文件)
int char(字符)
FILE *stream
写入的字符(int
失败返回EOF
fgets从流中读取一行文本所有输入流char *str(缓冲区)
int n(最大长度)
FILE *stream
成功返回str
失败返回NULL
保留换行符\n
fputs向流写入一行文本所有输出流const char *str(字符串)
FILE *stream
成功返回非负值
失败返回EOF
不自动添加换行符
fscanf格式化输入(类似scanf所有输入流FILE *stream
const char *format(格式字符串)
...(变量地址)
成功匹配的参数数量
失败返回EOF
需注意缓冲区溢出风险
fprintf格式化输出(类似printf所有输出流FILE *stream
const char *format
...(变量值)
成功返回写入字符数
失败返回负值
fread二进制输入(块读取)文件流void *ptr(缓冲区)
size_t size(每块大小)
size_t nmemb(块数)
FILE *stream
实际读取的块数用于结构体等二进制数据
fwrite二进制输出(块写入)文件流const void *ptr(数据地址)
size_t size
size_t nmemb
FILE *stream
实际写入的块数

注意:

写字符串,不用写\0,因为这是C语言的规定,不是文件的规定,写进去会乱码。 

2.3 stdin & stdout & stderr

C程序启动默认打开三个输入输出流,分别是stdin,stdout,stderr

#include <stdio.h>extern FILE *stdin;  // 标准输入,键盘文件
extern FILE *stdout; // 标准输出,显示器文件
extern FILE *stderr; // 标准错误,显示器文件

3、系统文件I/O

3.1 一种传标志位的方式

使用位图,用比特位作为标志位。

#include <stdio.h>#define ONE     (1 << 0)  // 0000 0001 (二进制)
#define TWO     (1 << 1)  // 0000 0010 (二进制)
#define THREE   (1 << 2)  // 0000 0100 (二进制)void 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);                // 输出: flags has ONE!func(THREE);              // 输出: flags has THREE!func(ONE | TWO);          // 输出: flags has ONE! flags has TWO!func(ONE | TWO | THREE);  // 输出: flags has ONE! flags has TWO! flags has THREE!return 0;
}

3.2 文件的系统调用接口

man 2 系统调用,有具体说明。

3.2.1 open()
#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);

pathname:要打开或创建的目标文件路径。

flags:打开文件时的选项标志,可以使用以下常量通过"或"运算(|)组合:

必须指定且只能指定一个的选项:

  • O_RDONLY只读打开。

  • O_WRONLY只写打开。

  • O_RDWR读写打开。

可选标志:

  • O_CREAT:若文件不存在则创建它(需要mode参数,设置新文件的访问权限)。

  • O_APPEND追加写模式。

  • O_TRUNC:如果文件已存在且为普通文件,打开时会将其长度截断为0逻辑上的清空(类似与vector的size)

return value:

  • 成功:返回新打开的文件描述符fd非负整数

  • 失败:返回-1

注意:

那么C语言的fopen的flag就是:

“r” = O_RDONLY;

“w” = O_CREAT | O_WRONLY | O_TRUNC;

“a” = O_CREAT | O_WRONLY | O_APPEND;

“r+” = O_RDWR;

“w+” = O_CREAT | O_RDWR | O_TRUNC;

“a+” = O_CREAT | O_RDWR | O_APPEND。

3.2.2 read() & write() & close()

类比C文件相关接口。

#include <unistd.h>ssize_t read(int fd, void *buf, size_t count);
fd:文件描述符
buf:存储读取数据的缓冲区
count:请求读取的字节数
#include <unistd.h>ssize_t write(int fd, const void *buf, size_t count);
fd:文件描述符
buf:包含待写入数据的缓冲区
count:请求写入的字节数
#include <unistd.h>int close(int fd);
fd:要关闭的文件描述符

注意:

read()和write()的buf都是void*,不关心数据格式,以二进制流输入输出。

那么为什么语言层,有字符流的输入输出?

  • 首先,底层都是二进制流的输入输出。
  • 字符按ASCII输入(读出),按ASCII输出(写入)。对于字符设备,字符通过ASCII转化成二进制写到里面,然后通过ASCII解释,以字符的形式显示。

字符流的输入输出,是因为,我们输入输出的是字符串

3.3 库函数和系统调用

类型示例函数所属层级特点
库函数fopenfclosefreadfwriteC标准库(libc)1. 提供更高级的抽象
2. 带缓冲区
3. 可移植性更好
4. 最终会调用系统调用
系统调用openclosereadwritelseek操作系统接口1. 直接与内核交互
2. 无缓冲区
3. 效率更高但更底层
4. 与具体操作系统相关

3.4 文件描述符fd

3.4.1 0 & 1 & 2

Linux 进程默认情况下会有 3 个缺省打开的文件描述符,分别是

标准输入 0标准输出 1标准错误 2

0,1,2 对应的物理设备一般是:键盘显示器显示器

所以输入输出还可以采用如下方式:

0,1,2是自动打开的

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>  // 添加read/write所需的头文件int main()
{char buf[1024];// 从标准输入(文件描述符0)读取数据ssize_t s = read(0, buf, sizeof(buf) - 1); // 保留1字节给结尾的\0if(s > 0) {buf[s] = '\0';  // 添加字符串结束符// 将输入内容同时输出到标准输出(1)和标准错误(2)write(1, buf, s);write(2, buf, s);}return 0;
}

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

注意:

C语言的stdin(fd = 0),stdout(fd = 1),stderr(fd = 2),是一个FILE结构体的指针,FILE结构体里面封装了文件描述符fd,其他语言也一样。

3.4.2 文件描述符的分配规则

直接看代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>  // 添加 close 函数所需的头文件int main()
{int fd = open("myfile", O_RDONLY);if (fd < 0) {perror("open");return 1;}printf("fd: %d\n", fd);close(fd);  // 正确的关闭位置return 0;
}

输出: fd: 3

关闭 fd = 0 或者 fd = 2,再看

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>  // 添加 close() 所需的头文件int main()
{close(0);  // 关闭标准输入(文件描述符 0)// close(2);  // 注释掉的关闭标准错误(文件描述符 2)int fd = open("myfile", O_RDONLY);if (fd < 0) {perror("open");return 1;}printf("fd: %d\n", fd);close(fd);               // 关闭文件描述符return 0;
}

 输出: fd: 0或 fd: 2

结论:

在 Linux 系统中,文件描述符的分配原则最小的没有被使用下标,作为fd,给新打开的文件

3.4.3 重定向

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

#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_CREAT | O_WRONLY | O_TRUNC, 0644);if(fd < 0) {perror("open");return 1;}printf("fd: %d\n", fd);fflush(stdout);close(fd);exit(0);
}

因为语言层只认stdout中的fd = 1,此时下标为1的指针指向myfile,所以

本来应该输出到显示器上的内容,输出到了myfile文件中。

这种现象叫做输出重定向

常见的重定向有: >,>>,<。

输出重定向的本质:

注意:

cat log.txt > myfile,实际上是cat log.txt 1>myfile只重定向了标准输出

cat log.txt 1>myfile 2>&1重定向了标准输出和标准错误

3.4.4 重定向系统调用dup2()
#include <unistd.h>int dup2(int oldfd, int newfd);

oldfd的指针 覆盖  newfd的指针 。

如:dup2(fd,0),实现输入重定向,dup2(fd,1),实现输出重定向。

所以,重定向 = 文件打开方式 + dup2()

4、理解一切皆文件

首先,在 Windows 中是文件的东西,它们在 Linux 中也是文件;其次一些在 Windows 中不是文件的东西,比如进程、磁盘、显示器、键盘这样的硬件设备也被抽象成了文件,你可以使用访问文件的方法访问它们获得信息;甚至管道,也是文件;将来我们要学习网络编程中的 socket(套接字)这样的东西,使用的接口跟文件接口也是一致的。

这样做最明显的好处是,开发者仅需要使用一套 API ,即可调取 Linux 系统中绝大部分的资源。举个简单的例子,Linux 中几乎所有读(读文件,读系统状态,读 PIPE)的操作都可以用 read 函数来进行;几乎所有更改(更改文件,更改系统参数,写 PIPE)的操作都可以用 write 函数来进行。

上图中的外设,每个设备都可以有自己的 read、write,但一定是对应着不同的操作方法!!但通过 struct file 下的 struct file_operations 中的各种函数回调,让我们开发者只用 file 便可调取 Linux 系统中绝大部分的资源!!这便是 "Linux 下一切皆文件" 的核心理解。

封装+多态的体现。 

5、缓冲区

5.1 缓冲区的定义

临时存储数据的内存区域。

5.2 缓冲区的作用

提高使用者的效率。

5.3 缓冲区的机制

  • 用户级语言层缓冲区,避免频繁调用系统调用(成本高),提高C语言接口的效率。
  • 文件内核缓冲区,提高系统调用的效率。
  • 可以通过fsync(),将文件内核缓冲区的数据刷新到硬件。
  • 一般认为数据交给OS,就相当于交给硬件。

基于上面的机制,可以理解下面的现象:

现象1:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main() {// 关闭标准输出(文件描述符1)close(1);int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0664);if (fd < 0) {perror("open");return 1;}printf("hello world: %d\n", fd);  // 注意:这里打印的fd值应该是1close(fd);return 0;
}

这个时候,对于普通文件,应该是满了刷新,可是没满,也没有强制刷新,然后关闭了fd,在程序退出时,刷新,但fd已经关闭了,刷新不了,所以log.txt中不会有数据。

可以使用 fflush() 强制刷新下缓冲区。

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main() {// 关闭标准输出(文件描述符1)close(1);int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0664);if (fd < 0) {perror("open");return 1;}printf("hello world: %d\n", fd);  // 注意:这里打印的fd值应该是1fflush(stdout); // 强制刷新close(fd);return 0;
}

注意:stderr是不带缓冲区,即立即刷新

现象2:
#include <stdio.h>
#include <string.h>
#include <unistd.h>  // 添加 write() 和 fork() 所需的头文件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, 1, strlen(msg1), stdout);write(1, msg2, strlen(msg2));fork();return 0;
}

结果:

hello printf
hello fwrite
hello write

显示器,行刷新;

系统调用write(),直接写入内核。

但是重定向一下 ./hello > file,结果:

hello write
hello printf
hello fwrite
hello printf
hello fwrite

系统调用write(),直接写入内核;

重定向,改变了刷新方式,普通文件,满了刷新,可是没慢,也没有强制刷新,程序退出时,刷新,父子进程各刷新一份。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/88546.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/88546.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/88546.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

广告匹配策略的智能化之路:人工智能大模型的方法和步骤

摘要 广告匹配策略是指根据用户的需求和偏好&#xff0c;向用户推荐最合适的广告的方法。广告匹配策略的优化是数字化营销的核心问题之一&#xff0c;也是提升广告效果和收益的关键因素。本文介绍了如何利用人工智能大模型&#xff0c;从数据分析、广告推荐、策略优化、效果评…

飞算JavaAI:重塑Java开发的“人机协同“新模式

引言 在Java开发领域&#xff0c;“效率"与"质量"的平衡始终是开发者面临的核心挑战——重复编码消耗精力、复杂业务易出漏洞、老系统重构举步维艰。飞算JavaAI的出现&#xff0c;并非简单地用AI替代人工&#xff0c;而是构建了一套"AI处理机械劳动&#…

运行ssh -T git@github.com报错

运行ssh -T gitgithub.com报错 no such identity: /root/.ssh/id_rsa: No such file or directory gitssh.github.com: Permission denied (publickey). 如果我用的是ed25519而非rsa&#xff0c;有id_ed25519 则需要打开~/.ssh/config检查一下是否写错了 vim ~/.ssh/config 然后…

20250710-2-Kubernetes 集群部署、配置和验证-网络组件存在的意义?_笔记

一、网络组件的作用&#xfeff;1. 部署网络组件的目的&#xfeff;核心功能&#xff1a;执行kubectl apply -f calico.yaml命令的主要目的是为Kubernetes集群部署网络组件必要性&#xff1a;解决Pod间的跨节点通信问题建立集群范围的网络平面&#xff0c;使所有Pod处于同一网络…

【牛客刷题】dd爱科学1.0

文章目录 一、题目介绍1.1 题目描述1.2 输入描述:1.3 输出描述:1.4 示例1二、解题思路2.1 核心策略2.2 算法流程2.3 正确性证明三、算法实现四、关键步骤解析五、复杂度分析六、正确性验证七、算法对比7.1 暴力搜索法7.2 动态规划7.3 三种解法对比分析一、题目介绍 1.1 题目描…

跑步-Java刷题 蓝桥云课

目录 题目链接 题目 解题思路 代码 题目链接 竞赛中心 - 蓝桥云课 题目 解题思路 用数组记录每个月有多少天,再使用一个int型变量记录是星期几,遍历即可 代码 import java.util.Scanner; // 1:无需package // 2: 类名必须Main, 不可修改public class Main {public stat…

Qt常用控件之QWidget(二)

Qt常用控件&#xff08;二&#xff09;1.window frame2.windowTitle3.windowIcon&#x1f31f;&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f;&#x1f31f; &#x1f680;&#x1f680;系列专栏&#xff1a;【Qt的学习】 &#x1f4dd;&#x1f4dd;本篇…

飞算Java AI:专为 Java 开发者打造的智能开发引擎

目录 一&#xff0c;核心功能 1&#xff0c;智能编码&#xff08;AI Coding&#xff09; 2&#xff0c;AI 驱动测试&#xff08;AI Testing&#xff09; 3&#xff0c;智能运维&#xff08;AIOps&#xff09; 4&#xff0c;工程化支持 二、注册与上手&#xff1a;3 分钟快…

基于开源AI大模型AI智能名片S2B2C商城小程序源码的私域流量新生态构建

摘要&#xff1a;私域流量并非新生概念&#xff0c;企业持续构建和经营“企业 - 客户”关系是其持续存在的关键&#xff0c;且会随时代发展自我完善迭代。本文探讨了开源AI大模型AI智能名片S2B2C商城小程序源码在私域流量领域的应用价值。通过分析私域流量发展现状与挑战&#…

用 ELK+Filebeat 提高50%问题排查效率,这套方案实测有效!

摘要 在中大型系统中&#xff0c;日志的分布常常让问题排查变得异常痛苦&#xff1a;每次出错都要登录一堆服务器、翻一堆文本&#xff0c;还不一定能找到关键线索。为了解决这个问题&#xff0c;ELK&#xff08;Elasticsearch、Logstash、Kibana&#xff09;日志聚合平台应运而…

数据治理到底是什么?搞清这四件事,你就彻底明白了!

目录 第一件事&#xff1a;数据治理不是做“数据”&#xff0c;是做“管” 第二件事&#xff1a;治理的核心&#xff0c;是“数、责、权”的三角绑定 一是“数”&#xff1a;你到底有哪些数据&#xff1f; 二是“责”&#xff1a;每张表、每个字段是谁负责&#xff1f; 三…

Spring的事务控制——学习历程

思考&#xff1a;1. 事务是干什么的&#xff1f;2. 事务的特性&#xff1f;3. 事务控制的传播方式&#xff08;传播行为&#xff09;4. 事务的隔离级别5. 事务是如何实现的&#xff1f;6. 事务的回滚方式7. 事务失效场景回答&#xff1a;1. 事务和锁&#xff0c;还有版本控制 …

鸿蒙 Secure Boot 全流程解析:从 BootROM 到内核签名验证的实战指南

摘要 随着智能设备应用的深入&#xff0c;操作系统安全成为设备可信运行的基础。在物联网和多终端场景中&#xff0c;一旦系统被恶意篡改&#xff0c;将带来数据泄露、设备被控等严重后果。鸿蒙系统在安全启动方面设计了完整的机制&#xff0c;从最底层的 Boot ROM 开始逐级校验…

tailwindCSS === 使用插件自动类名排序

目录 类如何排序 自定义 实战应用 .prettierrc package .eslintrc 人们一直在讨论在 Tailwind 项目中对实用程序类进行排序的最佳方法。今天&#xff0c;我们很高兴地宣布&#xff0c;随着我们官方 prettier-plugin-tailwindcss 的发布&#xff0c;您终于可以不用为此担…

数据结构 —— 键值对 map

目录 map的若干操作 1、emplace() 2、find(key) 3、count(key) 4、lower_bound 和 upper_bound 5、erase() 6、empty() 7、降序的map 计蒜客T3603 叫号系统 题意&#xff1a; 解题思路&#xff1a; Code: Leetcode1309 解码字母到整数映射 题意&#xff1a; 解题…

C++ 性能优化指南

C 性能优化指南&#xff08;针对 GCC 编译器&#xff0c;面向高级工程师面试&#xff09; 代码优化面试常问点&#xff1a; 如何避免不必要的对象拷贝&#xff1f;为什么要用引用或 std::move&#xff1f;虚函数调用有什么性能开销&#xff1f;原理解释&#xff1a; 传递对象时…

拼数(字符串排序)

题目描述设有 n 个正整数 a1​…an​&#xff0c;将它们联接成一排&#xff0c;相邻数字首尾相接&#xff0c;组成一个最大的整数。输入格式第一行有一个整数&#xff0c;表示数字个数 n。第二行有 n 个整数&#xff0c;表示给出的 n 个整数 ai​。输出格式一个正整数&#xff…

【MySQL】函数学习-字符串函数

一、MySQL字符串函数基础回顾 在MySQL中&#xff0c;字符串函数用于处理文本数据&#xff0c;常见场景包括数据拼接、格式转换、清洗等。以下是核心函数速览&#xff1a;函数名作用说明基础示例&#xff08;独立运行&#xff09;CONCAT(s1,s2)拼接多个字符串SELECT CONCAT(heel…

AI不是“心智的蒸汽机“:重新理解人工智能的本质

当我们谈论人工智能时&#xff0c;最常听到的比喻是"心智的蒸汽机"——一个能够自动化认知任务的强大工具。但这个比喻可能从根本上误导了我们对AI真正潜力的理解。 最近&#xff0c;来自科罗拉多大学丹佛分校和肯尼索州立大学的研究团队发表了一篇论文[1]&#xff0…

免费的AI Logo工具生成的Logo质量怎么样?我对比了7个AI Logo生成器,设计必备

你尝试过用 AI 生成 Logo 吗&#xff1f;在 AI 巨火的今天&#xff0c;什么事情都可以尝试用 AI 去做。在品牌设计上也是如此&#xff0c;用 AI 做品牌设计、用 AI 做电商海报、用 AI 做包装设计等等。不知道你用过哪些 AI 工具&#xff0c;哪些是你觉得好用的。今天我们就来研…