【Linux】IO 篇:文件调用原理,文件描述符,FILE的内涵,解析重定向,理解缓冲区,软硬链接

文章目录

  • 一、系统调用接口
  • 二、文件调用
      • 1. 文件描述符 fd
      • 2. 文件调用原理
      • 3. FILE
  • 三、重定向
      • dup2
  • 四、缓冲区
      • 简易 FILE 的代码实现
  • 五、ext2 文件系统
      • 1. inode 和 文件名
      • 2. 重新认识目录
      • 3. 理解文件的增删查改
      • 4. 一些补充
  • 六、文件链接
      • 1. 建立软连接
      • 2. 建立硬连接

文件被加载之前,被存在磁盘上,操作文件,文件的部分内容则会被调度到 内存中。

要分析文件,我们也把文件分成两种:

  • 磁盘上的文件(文件系统)
  • 内存中的文件

这里谈论的是,内存中的文件

文件被打开,OS 会为被打开的文件创建对应的内核数据结构 struct file,将所有这个类型的结构体用某种数据结构链接起来以供 OS 管理。

struct file
{// 各种文件属性(磁盘中读出来的)// 各种链接关系// 缓冲区相关
};

一、系统调用接口


主要介绍一个

open

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>// man 手册查看
man 2 open
  • int open(const char *pathname, int flags)
  • int open(const char *pathname, int flags, mode_t mode);

参数 pathname:

  • 文件名

参数 flags:

  • 标志位,man 2 手册可查

参数 mode:

  • 设置(新建)文件权限

返回值:

  • 为 -1 则说明,open 失败
  • 非负为 文件描述符(见后文)

参数 flags

  • O_CREAT | O_WRONLY
    如果没有文件则创建生成,
    默认不会对原始文件内容做清空,会从从最开始覆盖

  • O_CREAT | O_WRONLY | O_TRUNC
    如果没有文件则创建生成
    清空 写

  • O_CREAT | O_WRONLY | O_APPEND
    如果没有文件则创建生成
    追加写

注意在往文件里写入的时候,strlen(str) 不要 +1,因为 ‘\0’ 是 C 语言的结束规定,不是文件的规定,加进去会乱码。

使用系统接口进行 IO 的时候,一定要注意,\0 问题

我们 C 语言使用的一系列函数

  • fopen、fclose、fwrite/fputs、fread/fgets

都是系统函数的封装

  • open、close、write、read

二、文件调用


1. 文件描述符 fd

任何一个进程,在启动的时候,默认都会打开三个文件:

  • 标准输入 - - 设备文件 -> 键盘文件   0

  • 标准输出 - - 设备文件 -> 显示器文件   1

  • 标准错误 - - 设备文件 -> 显示器文件   2

      其中 标准输出 和 标准错误 都会向 显示器 打印,但他们其实是不一样的。eg:测试中,输入受重定向符的影响,而错误不受重定向符的影响
    

文件描述符,也是 open 对应的返回值。我们创建的文件返回的值是从 3 开始的,而 0 1 2 正是被上面默认打开的三个文件占用了。这个数字本质就是 数组下标

一张简图:
在这里插入图片描述

进程中,文件描述符的分配规则:
在文件描述符表中,最小的,没有被使用的数组元素,分配给新文件
fclose(stdin);
// 等价于
close(0);

2. 文件调用原理

  • 1 个进程 可以调度 n 个文件,每个文件都有 一个缓冲区
  • 调用 read / write / close 这些系统接口时,都需要文件操作符。也就是说,在操作系统层面,我们必须要访问fd,才可以找到文件
  • 我们所谓的 IO 类 read / write 函数,本质上是 拷贝函数
  • 什么时候将缓冲区上的内容刷新到磁盘中指定的位置,由 OS 自主决定
  • 进程 和 文件 并没有深度耦合,便于操作系统的管理
    在这里插入图片描述

如何理解一切皆文件:

  • 每个硬件都有一个 struct file 对象,C语言里面没有成员函数,使用的就是函数指针完成的众多行为。
  • 进程通过 指针数组,访问的其实是这些 struct file 对象,包括里面的缓冲区、函数指针…
  • 而用户的操作,实际上都是进程的操作
  • 所以我们说,Linux 下,一切皆文件
    在这里插入图片描述

3. FILE

#include <stdio.h>
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;FILE *fopen(const char *path, const char *mode);

在这里插入图片描述

这里的 FILE * 是 结构体类型!由 C语言提供的,跟内核的 struct file 没有任何关系。

我们指定在操作系统层面,我们必须要访问 fd,才可以找到文件。也就是说 struct FILE 里面必定封装了 fd。

我们来看 FILE 源码是这样写的:

typedef struct _IO_FILE FILE;/usr/include/stdio.hstruct _IO_FILE {//...int _fileno; //封装的文件描述符,就是我们说的 fd// C语言维护的缓冲区相关内容//...
};

测试如下:

print("%d\n", stdin->_fileno);
print("%d\n", stdout->_fileno);
print("%d\n", stderr->_fileno);
FILE *fp = fopen("test.txt", "w");
print("%d\n", fp->_fileno);--------
输出结果:
0
1
2
3

三、重定向


🌰< 输出重定向举例

close(1);
int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);	// fd = 1printf("hello\n"); // stdout -> 1
printf("hello\n");
printf("hello\n");---------
hello
hello
hello
字样,被保存进了 test.txt 中

在这里插入图片描述

🌰> 输入重定向举例

close(0);
int fd = open("test.txt", O_RDONLY);	// fd = 0int a,b;
scanf("%d %d", &a, &b);	// stdin -> 0
printf("a = %d, b = %d\n", a, b);---------
在 test.txt 文件中写入 123 456
运行程序后输出
a = 123, b = 456

在这里插入图片描述

🌰>>追加重定向举例

close(1);
int fd = open("test.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);	// fd = 1printf("hello\n"); // stdout -> 1
printf("hello\n");
printf("hello\n");---------
运行两次则有
hello
hello
hello
hello
hello
hello
字样,被保存进了 test.txt 中

回头看之前的问题

其中 标准输出 和 标准错误 都会向 显示器 打印,但他们其实是不一样的。
eg:测试中,输入受重定向符的影响,而错误不受重定向符的影响

原因如下:

stdout、cout -> 他们都是向 1 号文件描述符对应的文件打印;
stderr、cerr -> 他们都是向 2 号文件描述符对应的文件打印。
输出重定向时,更改的只是 1 号对应的指向,2 号未被影响。

当我们需要手动分离一个程序正确和错误信息的时候:

./a.out 1>log.txt 2>err.txt

当然也有直接的函数可以使用

dup2

头文件:

#include <unistd.h>

int dup2(int oldfd, int newfd);

参数 oldfd:

  • 最后需要的 fd

参数 newfd:

  • 需要被覆盖的 fd

相当于,把本应该到 newfd 上的,重定向到 oldfd,最后剩下的只有 oldfd

四、缓冲区

C 语言维护的 FILE 结构体 和 OS 维护的 struct file 结构体,都有自己的缓冲区(每个对象都有自己的缓冲区),这两个缓冲区是不相同的。

在这里插入图片描述

C库提供的刷新策略,一般有三种:

  1. 无缓冲
  2. 行缓冲(遇到 \n 刷新)
  3. 全缓存(缓冲区满了刷新)
  • 显示器采用的刷新策略:行缓冲
    普通文件采用的刷新策略:全缓冲

缓冲区的作用:节省调用者的时间

这会产生一些奇怪的现象:

// c 库
fprintf(stdout, "hello fprintf\n");
// os 系统调用
const char *msg = "hello write\n";
write(1, msg, strlen(msg));fork();

这个程序我们在 linux 下,重定向到文件,会出现如下情况

[xxx@hostname file]$ ./a.out
hello write
hello fprintf
[xxx@hostname file]$ ./a.out > test.txt
[xxx@hostname file]$ cat test.txt
hello write
hello fprintf
hello fprintf
[xxx@hostname file]$

第一个运行容易理解,分析第二次 cat 文件内容出现的结果
原因如下:

  • 首先,write 正常调用输出到显示器
  • fprintf 的缓冲区,对于重定向到普通文件,使用全缓冲,这里的内容显然不能将缓冲区填满,所以进程结束时刷新
  • 一直到 fork 被调用,程序还没结束,此时父子进程的缓冲区里都有一份 hello fprintf。谁先结束谁就先写诗拷贝,刷新到屏幕上,于是被打了两次

如果我们想要 强制,手动 刷新 os 上的缓冲区,可以使用函数 fsync

#include <unistd.h>
int fsync(int fd);

简易 FILE 的代码实现

👉🔗链接如下


五、ext2 文件系统


在这里插入图片描述

Block Group:文件系统会根据分区的大小划分为数个 Block Group。而每个Block Group都有着相同的结构组成。

Boot Block 开机块:

  • 每个分区的最开始,有一小块开机相关的。重点包括与操作系统启动相关的内容,如分区表、操作系统定向的地址。

剩下的就是数据相关的:

SB - - Super Block(超级块):存放文件系的所有属性信息

    1. 文件系统的类型
    1. 整个分组的情况
  • SB 在各个分组里面可能都会存在,而且是统一更新的,而是为了防止SB区域坏掉,如果故障,整个分区不可以被使用。(多副本保护数据安全)

  • 记录的信息主要有:bolck 和 inode的总量,未使用的 block 和 inode 的数量,一个 block 和 inode 的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block 的信息被破坏,可以说整个文件系统结构就被破坏了

GDT - - Group Descriptor Table(块组描述符):描述块组内的详细统计等属性信息。

Block Bitmap(块位图):Block Bitmap 中记录着 Data Block 中哪个数据块已经被占用,哪个数据块没有被占用

inode Bitmap(inode 位图):每个 bit 表示一个 inode 是否空闲可用。

i 节点表:存放文件属性 如 文件大小,所有者,最近修改时间等

  • 一般而言每个文件内部所有属性的集合,都会放在一个 inode 节点中(128字节)。一个文件,一个 inode 节点。所以,一个 group 需要有一个区域来专门保存该 group 内所有文件的 inode 节点,即 inode table ,i 节点表。
  • 每个 inode 都会有自己的 inode 编号。inode 编号也属于对应文件的属性 id。
  • 一个 inode 对应一个文件,而文件 inode 属性和该文件对应的数据块是有映射关系的

Data Blocks(数据区):存放文件内容

1. inode 和 文件名

Linux 系统只认 inode 号,文件的 inode 属性中,并不存在文件名。文件名只是给用户用的。

2. 重新认识目录

目录是一个文件,目录也有 inode,有内容。

任何一个文件,一定在目录内部。

目录的数据块里保存的就是,该目录下,文件名和文件 inode 标号对应的映射关系,而且,在目录内,文件名和 inode 互为 key 值。

当我们访问一个文件的时候,我们是在特定目录下访问的,cat test.txt

  1. 先要在当前目录下,找到 test.txt 的 inode 编号
  2. 一个目录也是一个文件,也一定隶属于一个分区,结合 inode,在该分区中找到分组, 在改分组中 inode table 中,找到文件的 inode
  3. 通过 inode 和对应的 datablock 的映射关系,找到改文件的数据块,并加载到 OS,并完成显示到显示器!

3. 理解文件的增删查改

删除文件,只需要修改位图即可

  • 首先根据文件名,找到对应的 inode number
  • 删除内容:根据 inode number 结合 inode属性中的映射关系,设置 block bitmap 对应的比特位,置0即可
  • 删除属性:inode number 设置 inode bitmap 对应的比特位设置为 0

新增文件

  • 在新增文件的目录所处的分组内,os 会在 inode Bitmap 里找一个空位置为 1
  • 对应 inode Bitmap 是第几个比特位,就有了新的 inode 和 inode number,文件创建出来的默认属性,填充到 inode table 的 inode 节点中
  • 数据块里面,内容中,追加一条新的文件名和 inode 的映射关系。

对文件写入

  • 拿着文件的文件名找 inode,比如要添加5个块,把 5 个块的位置在 BlockBitmap 里设为 1,这 5 个编号同样填入块和 inode 映射的数组
  • 向 Datablocks 对应位置里刷新数据

4. 一些补充

  1. 文件被误删了,怎么恢复?

使用工具(从Linux日志中)拿到(删除文件的) inode!!!在 inode bitmap 的位置置 1。
在 inode 里面找到当前文件占用的数据块,把对应数据块的 block bitmap 里的位置置 1。
使用 indoe 访问属性,再通过属性访问数据块,就可以都出来了。

  1. inode 可以确定分组。inode number 是在一个分区内有效的,不能跨分区

  2. 分区、分组、填写系统属性是 OS 完成的。那 OS 在什么时候呢做的呢?
    比如我们重装系统,分区完成之后,要让分区能够被正常使用,还需要对分区做格式化。格式化的过程,其实就是 OS 向分区写入文件系统的管理属性信息。


六、文件链接


查看当前目录下所有文件的 inode number 和详细信息:

ls -li

1. 建立软连接

ln -s

ln -s [文件名] [链接名]

软链接是一个独立的链接文件,有自己的 inode number,必有自己的 inode 属性和内容。

软链接内部放的是自己所指向的文件的路径。

  • 作用就是,将一个路径很深的程序、库、头文件…建立到当前目录下或者其他很好访问的路径下,方便我们访问。
  • 类似 windows 下的快捷方式。

2. 建立硬连接

ln

ln [文件名] [链接名]

硬链接和目标文件共用一个 inode number,意味着,硬链接一定是和目标文件使用同一个 inode 的。

硬链接,建立了文件名和老的 inode 的映射关系。

其中还有一个引用计数,计数的是该 inode 的硬链接数

. 就是当级目录的硬链接
.. 是上级目录的硬链接ls -ali 可以查看 . 和 .. 文件(inode number + 详细信息)
ls -di 可以看本级目录(inode number)

但是!我们不能给目录建立硬链接哦。因为有可能会造成环路路径问题。


🥰如果本文对你有些帮助,请给个赞或收藏,你的支持是对作者大大莫大的鼓励!!(✿◡‿◡) 欢迎评论留言~~


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

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

相关文章

java.lang.IllegalStateException: Failed to load ApplicationContext

问题描述 原因是我在进行微服务远程调用的时候报的错误。 解决方案 原来是jdk版本不一样导致的问题&#xff0c;改成1.8就行了

关于游戏的笔记

关于搭建秦时明月2一键端&#xff0c;并且开启秘境神秘商人东海寻仙幻化 1.该游戏下主要的目录 gm端 服务框架 服务端 2.修改对应的文件 C:\qs\Q2Server\server\conf_common\ManagerAddress.xmlC:\qs\Q2Server\server\conf_manager\GateServer.xml修改ip 3.启动gm startup…

阿里云官方关于数据安全保护的声明

“阿里云监控用户的数据流量&#xff1f;”“真的假的&#xff1f;”随着近日早晨 朱峰肥鹅旅行 对阿里云的一条朋友圈截图传遍了整个IT圈。 对于网络上的各种传播&#xff0c;以下是阿里云的官方答复&#xff0c;原文如下&#xff1a; 关于数据安全保护的声明 今天有客户反映…

opencv35-形态学操作-腐蚀cv2.erode()

形态学&#xff0c;即数学形态学&#xff08;Mathematical Morphology&#xff09;&#xff0c;是图像处理过程中一个非常重要的研 究方向。形态学主要从图像内提取分量信息&#xff0c;该分量信息通常对于表达和描绘图像的形状具有 重要意义&#xff0c;通常是图像理解时所使用…

最佳路径优先搜索算法

本来想直接写A* 的&#xff0c;不过看完最佳路径优先搜索算法后觉得还是要先理解一下这个算法后才能更好的理解A* 算法&#xff0c;所以把这篇文章放到A* 前面。 基本概念 最佳优先搜索算法&#xff08;Best-first-searching&#xff09;是一种启发式搜索算法&#xff08;Heu…

Python实现GA遗传算法优化BP神经网络回归模型(BP神经网络回归算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 遗传算法&#xff08;Genetic Algorithm&#xff0c;GA&#xff09;最早是由美国的 John holland于20世…

Docker从零到掌握(详解)

目录 1.初识Docker 1.1 为什么使用docker 1.2 Docker技术 1.3.安装Docker 1.4.Docker架构 1.5.配置Docker镜像加速器 2.Docker常用命令 2.1.Docker服务相关的命令 2.2.Docker镜像相关的命令 2.3.Docker容器相关的命令 3. 容器的数据卷 3.1.数据卷的概念和作用 3.2…

Idea添加mybatis的mapper文件模版

针对Java开发人员&#xff0c;各种框架的配置模版的确是需要随时保留一份&#xff0c;在使用的时候&#xff0c;方便复制粘贴&#xff0c;但是也依然不方便&#xff0c;我们可以给开发工具&#xff08;IDE&#xff09;中添加配置模版&#xff0c;这里我介绍下使用idea开发工具&…

Python 中的机器学习简介:多项式回归

一、说明 多项式回归可以识别自变量和因变量之间的非线性关系。本文是关于回归、梯度下降和 MSE 系列文章的第三篇。前面的文章介绍了简单线性回归、回归的正态方程和多元线性回归。 二、多项式回归 多项式回归用于最适合曲线拟合的复杂数据。它可以被视为多元线性回归的子集。…

uniapp返回

// 监听返回事件onNavigationBarButtonTap() {uni.showModal({title: 提示,content: 确定要返回吗&#xff1f;,success: (res) > {if (res.confirm) {uni.navigateBack({delta: 2})}}})},

Opencv-C++笔记 (16) : 几何变换 (图像的翻转(镜像),平移,旋转,仿射,透视变换)

文章目录 一、图像平移二、图像旋转2.1 求旋转矩阵2.2 求旋转后图像的尺寸2.3手工实现图像旋转2.4 opencv函数实现图像旋转 三、图像翻转3.1左右翻转3.2、上下翻转3.3 上下颠倒&#xff0c;左右相反 4、错切变换4.1 实现错切变换 5、仿射变换5.1 求解仿射变换5.2 OpenCV实现仿射…

力扣 -- 139. 单词拆分

一、题目 题目链接&#xff1a;139. 单词拆分 - 力扣&#xff08;LeetCode&#xff09; 二、解题步骤 下面是用动态规划的思想解决这道题的过程&#xff0c;相信各位小伙伴都能看懂并且掌握这道经典的动规题目滴。 三、参考代码 class Solution { public:bool wordBreak(str…

基于Java的新闻全文搜索引擎的设计与实现

中文摘要 本文以学术研究为目的&#xff0c;针对新闻行业迫切需求和全文搜索引擎技术的优越性&#xff0c;设计并实现了一个针对新闻领域的全文搜索引擎。该搜索引擎通过Scrapy网络爬虫工具获取新闻页面&#xff0c;将新闻内容存储在分布式存储系统HBase中&#xff0c;并利用倒…

建筑行业如果应用了数字孪生技术能有什么改变?

数字孪生是一种将现实世界与数字世界相结合的先进技术&#xff0c;它在建筑行业中正发挥着越来越重要的作用。通过数字孪生技术&#xff0c;建筑行业可以实现从设计、施工到运营的全生命周期数字化管理&#xff0c;带来了许多优势和机遇。 ① 建筑设计阶段的应用 数字孪生能够…

Unity Shader:闪烁

还是一样的分为UI闪烁和物体闪烁&#xff0c;其中具体可分为&#xff1a;UI闪烁、物体闪烁与半透明闪烁 1&#xff0c;UI闪烁 对于UI 还是一样的&#xff0c;改写UI本身的shader&#xff1a; Shader "UI/YydUIShanShder" {Properties{[PerRendererData] _MainTex(…

Spring Boot如何整合mybatis

文章目录 1. 相关配置和代码2. 整合原理2.1 springboot自动配置2.2 MybatisAutoConfiguration2.3 debug过程2.3.1 AutoConfiguredMapperScannerRegistrar2.3.2 MapperScannerConfigurer2.3.4 创建MapperFactoryBean2.3.5 创建MybatisAutoConfiguration2.3.6 创建sqlSessionFact…

怎么合并多个视频?简单视频合并方法分享

合并多个视频可以将它们组合成一个更长的视频&#xff0c;这对于需要播放多个短视频的情况非常有用。此外&#xff0c;合并视频还可以使视频编辑过程更加高效&#xff0c;因为不必将多个独立的视频文件分别处理。最后&#xff0c;合并视频可以减少文件数量&#xff0c;从而使整…

用html+javascript打造公文一键排版系统13:增加半角字符和全角字符的相互转换功能

一、实践发现了bug和不足 今天用了公文一键排版系统对几个PDF文件格式的材料进行文字识别后再重新排版&#xff0c;处理效果还是相当不错的&#xff0c;节约了不少的时间。 但是也发现了三个需要改进的地方&#xff1a; &#xff08;一&#xff09;发现了两个bug&#xff1a;…

【JVM】 垃圾回收篇——自问自答(1)

Q什么是垃圾&#xff1a; 运行程序中&#xff0c;没用任何指针指向的对象。 Q为什么需要垃圾回收&#xff1f; 内存只分配&#xff0c;不整理回收&#xff0c;迟早会被消耗完。 内存碎片的整理&#xff0c;为新对象腾出空间 没有GC程序无法正常进行。 Q 哪些区域有GC&#…

Java电子招投标采购系统源码-适合于招标代理、政府采购、企业采购、等业务的企业tbms

​ 功能描述 1、门户管理&#xff1a;所有用户可在门户页面查看所有的公告信息及相关的通知信息。主要板块包含&#xff1a;招标公告、非招标公告、系统通知、政策法规。 2、立项管理&#xff1a;企业用户可对需要采购的项目进行立项申请&#xff0c;并提交审批&#xff0c;查…