Linux文件系统(上)

  • 目录

    前言

    1.文件接口——用户与文件的“桥梁”

    2.C语言中FILE结构与Linux系统调用中fd的关系

    3.fd字段如何在文件读写操作中发挥作用

    4.fd的分配规则与文件重定向

     5.文件缓冲区

    6.如何理解Linux中一切皆文件的管理方案

    7.涉及代码一览

    总结


前言

在Linux中存在“两列”文件,一种是加载到内存中的文件,另一种是保存在磁盘等设备中的文件。本文要讲的就是加载到内存中的文件是以何种形态存在,是如何进行表征的。


1.文件接口——用户与文件的“桥梁”

在C语言中我们接触到过一些与文件有关的接口:诸如fopen、fwrite、fread、fclose等,这些接口都一定与一个名为FILE*的类型有关,这个所谓的FILE*就是C语言中封装的一个描述文件属性的结构体的指针。

图1        C语言中的文件操作接口

在C语言中我们通过接口最直观的能感受到C语言接口给我们返回了一个描述文件的结构体指针,我们对这个指针进行相应的读写调用就可以实现对文件的读写。在Linux平台上,Linux也给我们提供了一些访问文件的接口:

图2        Linux平台中的文件接口

我们写一个代码示例观察一下两者的行为差异:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>// int main()
// {
//     //向一个名为test.txt的文件中写入hello world,
//     //并将写入的文件内容在读取出来
//     FILE * f1=fopen("./test.txt","r+"); //r+表示以读写方式打开文件
//     const char * message="hello world";
//     char  output[64];//     //向f1指向的文件中写入信息
//     fwrite(message,strlen(message),1,f1);//     //r+状态表示可读可写,但是在写状态完成后,
//     //此时的文件指针不一定为0,所以要手动置零,从文件开头开始读文件
//     rewind(f1);//     //读取文件
//     fread(output,strlen(message),1,f1);//     //关闭文件流
//     fclose(f1); //     //打印结果 
//     printf("%s\n",output);
//     return 0;
// }#include <fcntl.h>
#include <unistd.h>
int main()
{//向一个名为test.txt的文件中写入hello world,//并将写入的文件内容在读取出来int fd=open("./test.txt",O_RDWR|O_TRUNC|O_CREAT); //O_RDWR表示以读写方式打开文件,O_TRUNC表示每次写入前清空文件内容,O_CREAT表示写入文件不存在就创建const char * message="hello world";char  output[64];//向fd指向的文件中写入信息write(fd,message,strlen(message));//O_RDWR状态表示可读可写,但是在写状态完成后,//此时的文件指针不一定为0,所以要手动置零,从文件开头开始读文件lseek(fd,0,SEEK_SET);//读取文件read(fd,output,strlen(message));//关闭文件流close(fd); //打印结果 printf("%s\n",output);return 0;
}
图3        对比C语言文件接口与Linux系统调用的接口差异

 我们从上述代码中不难看到,无论是C语言的文件接口还是Linux平台的系统调用接口,都可以实现对文件的读写操作,只不过,C语言中的文件读写接口与一个名为FILE的结构有关,Linux平台的读写文件系统调用与一个名为fd的整形有关。

2.C语言中FILE结构与Linux系统调用中fd的关系

我们在第一点中的讨论与代码实践中,隐隐约约感觉到,C语言的文件接口与Linux系统调用接口一定存在着某种必然联系。

先说结论:

FILE结构中一定包含fd这一字段,因为C语言的接口都是对各平台语言的系统调用的封装。 

 理由如下:

在博主的【浅谈冯诺依曼体系与操作系统】一文中讨论了,操作系统在整个计算机体系中的定位如下。

图4        操作系统在软件与硬件中的定位

我们使用的Linux内核其实就是一种操作系统,这一点是毋庸置疑的,操作系统的内核不允许用户直接访问,如果用户想要访问内核就必须使用Linux规定的系统调用接口。在第一部分中我们讨论的诸如open、close、read、write接口就属于系统调用接口,所以如果有任何的一种语言想在Linux平台下,访问Linux下的文件管理资源,那么这种语言就必须使用系统调用接口或者再系统调用接口上进行进一步的封装。C语言使用FILE结构进行文件的操作,但是对文件读写操作就一定绕不开系统调用接口,也就一定绕不开fd这一字段。所以FILE这一结构中一定包含fd这一字段。

3.fd字段如何在文件读写操作中发挥作用

在讲解这一部分之前,我想请读者想这样一个问题:

读写文件是否要以进程的形式进行?

答案是需要以进程的形式进行。

如果我们要对一个文件进行读写操作就一定需要将这个文件加载到内存中,而一旦文件被加载到内存中就需要对“文件”这一数据进行管理,要对数据做管理就必须要对数据作描述的结构,有了对数据描述的结构,下一步就需要交给操作系统集中调度处理,而操作系统只会对进程进行调度和管理,那么就意味着对数据的描述就必须要与进程相关联。那么我们可以查看一下:Linux内核中有关描述文件的结构体的字段、Linux内核中管理文件结构的字段。

图5        linux-2.6.32.19文件结构体中的字段   
图6        进程中有关管理文件结构的字段 
图7        进程与文件结构管理与文件结构的关系

我们来整理一下三者的逻辑:

首先,在Linux操作系统管理存在着描述进程的结构体,在这个结构体中有一个指针,这个指针指向管理文件的结构体,管理文件的结构体中又有一个指向文件结构体的指针数组。而fd就是这个数组的下标。如果我们想对相应文件进行读写操作只需要先为文件开辟一个空间,,然后令指针数组中一个为空的指针指向该文件结构即可,当我们想对该文件进行访问是,只需要对应的数组下标即可。

4.fd的分配规则与文件重定向

当一个进程启动时,会有三个文件为我们自动打开。它们分别是:标准输入文件、标准输出文件、标准错误文件。它们是对应的fd按照顺序分别是:0、1、2。在C语言中我们使用的输出接口本质上就是向标准输出文件中写入数据,在C语言中我们使用的输入接口本质上就是从标准输入进行数据的读取,这些接口与fd相绑定,这些接口默认从fd为0的文件中进行数据的读取,向fd为1的文件进行写入。我们可以使用相应接口测试一下:

图8        使用系统调用接口测试结果

那么如果我们呢关闭对应的读写文件后在创建新的文件后,对新创建的文件进行读写发生什么呢?

图9        测试代码
图10        测试代码结果

我们不难看到。之前对标准输出输出的内容,现在输出到了log.txt文件中,之前需要从标准输入中阻塞读取的数据,现在可以从文件中非阻塞读取数据。

这其实是因为fd的分配规则所导致的:

当文件读写被关闭时,除了要对描述文件的结构体作释放外,还需要将对应fd的文件指针置为空,当再有新的文件被打开时,操作系统会优先令文件指针数组中下标最小的、未被使用文件指针指向新创建的文件,这就导致了C语言中向屏幕输出,从键盘读取数据的接口,从新创建的文件中读取数据,向新创建的文件写入数据。这种将输入输出定向的其它文件的操作就叫做输入输出重定向。

这是我们无意间发现的文件重定向,我们来看一下Linux操作系统中的文件重定向接口:

int dup2(int sourcefd , int copyfd);

 说明:

dup2的行为是将文件指针数组中sourcefd下标对应的内容拷贝到copyfd下标对应的内容,本质上就是令两个文件指针指向同一个文件。

图11        dup2使用示例

不同于我们关闭一个读写流后,创建一个新的读写流顶替旧的读写流的行为,这种方式实现文件的重定向只能保持一个文件描述符指向一个文件,但是使用dup2令文件重定向,可以令多个文件描述符指向同一个文件,这也就是我们图11结果的成因。

 5.文件缓冲区

我们在讲解文件缓冲区之前看看看这段代码,请你推测一下这段代码执行后的结果是什么:


int main()
{printf("hello world!");while(1);
}
图12        代码执行后的结果

从图12中不难发现,该程序进入了死循环,但是在进入死循环之前,应该打印一条“hello world!”的信息,但是我们从终端中并没有发现这条消息。这个现象的成因实际上就与缓冲区有关系,C语言有这样的考量:

如果对一个文件进行多次少量的写入,会明显增加接口调用的时间,但是若将需要读写的内容存储到一块大小合适的空间中,当缓冲区中的内容达到一定大小时,进行一次大量的读写,就可以相对减少调用的时间。 

以上就是C语言缓冲区存在意义的理解,那么缓冲区刷新的策略有哪些呢:

①无缓冲:这通常是对于一些需要即时输出的文件,如stderr(标准错误流),不对其进行缓冲是为了尽快获取错误信息。

②行缓冲:当C语言识别到“\n”换行符是会进行缓存区的刷新。

③全缓冲:当C语言层缓冲区被填满时,才会进行刷新操作。

④进程退出时,自动刷新缓冲区。

图13        行缓冲示例​​​​

我们介绍的C语言缓冲区实际上是:C语言与内核交互数据的缓冲区。

以C语言的FILE结构为例,其底层一定封装了Linux内核相关的系统调用,而我们的缓冲区实际上就包含在FILE结构中,C语言在进行读写操作的时候,将数据拷贝到相应的缓冲区位置中,而后可利用缓冲区中数据进行其它调用或返回。

那么,Linux内核与硬件之间有没有缓冲呢?如何证明该缓冲区与C语言中的缓冲区不是一个?

图14        输出的奇怪现象

当我们使用C语言的调用接口(fwrite)时,向屏幕写一次但是不令其刷新缓冲区,当创建一个新的进程的时候,子进程继承父进程的所有数据包括缓冲区,因为没有对数据进行修改所以父子进程的缓冲区在页表映射中指向同一块区域,但当父子进程退出时,要对缓冲区作清空,对缓冲区作清空是一种修改操作,此时发生写时拷贝,子进程拷贝父进程的缓冲区中的内容,父子进程将缓冲区中的内容向Linux内核传递,调用系统调用接口,写入到硬件输出缓冲区,而后输出到显示器,所以打印两次。但对于直接调用系统调用接口(write)时,由于其不将内容写入到进程缓冲区,而是直接写入到硬件输出缓冲区,所以只打印一次。

6.如何理解Linux中一切皆文件的管理方案

在Linux操作系统中,Linux将所有硬件设备都视作文件,让我们带入操作系统的视角,所以现在无论是键盘、鼠标还是网卡、显示。它们都是文件,即然是文件那么一定就有读写方法,对于磁盘这类可读可写的存储介质还好说,我们如何对键盘、鼠标这种只读设备进行写入呢?我们需要对只读设备进行写入吗?当然不需要,对于只读文件,我们可以将它的写方法设置为空,同理,对于只写设备,我们可以将它们的读方法设置为空。将所有硬件设备视作文件的一个重要因素就是函数指针,当读写方法存在是,将对应的函数指针指向对应的读写方法即可,对于不存在的方法,函数指针设置成空。对文件的操作在Linux内核中struct file结构中的struct file_operations字段中存储。我们来看看struct file_operations的字段。

图15        文件操作结构体

7.涉及代码一览

#include <stdio.h>
#include <stdlib.h>
#include <string.h>// int main()
// {
//     //向一个名为test.txt的文件中写入hello world,
//     //并将写入的文件内容在读取出来
//     FILE * f1=fopen("./test.txt","r+"); //r+表示以读写方式打开文件
//     const char * message="hello world";
//     char  output[64];//     //向f1指向的文件中写入信息
//     fwrite(message,strlen(message),1,f1);//     //r+状态表示可读可写,但是在写状态完成后,
//     //此时的文件指针不一定为0,所以要手动置零,从文件开头开始读文件
//     rewind(f1);//     //读取文件
//     fread(output,strlen(message),1,f1);//     //关闭文件流
//     fclose(f1); //     //打印结果 
//     printf("%s\n",output);
//     return 0;
// }#include <fcntl.h>
#include <unistd.h>
// int main()
// {
//     //向一个名为test.txt的文件中写入hello world,
//     //并将写入的文件内容在读取出来
//     int fd=open("./test.txt",O_RDWR|O_TRUNC|O_CREAT); //O_RDWR表示以读写方式打开文件,O_TRUNC表示每次写入前清空文件内容,O_CREAT表示写入文件不存在就创建
//     const char * message="hello world";
//     char  output[64];//     //向fd指向的文件中写入信息
//     write(fd,message,strlen(message));//     //O_RDWR状态表示可读可写,但是在写状态完成后,
//     //此时的文件指针不一定为0,所以要手动置零,从文件开头开始读文件
//     lseek(fd,0,SEEK_SET);//     //读取文件
//     read(fd,output,strlen(message));//     //关闭文件流
//     close(fd); //     //打印结果 
//     printf("%s\n",output);
//     return 0;
// }// int main()
// {
//     close(1);
//     int fd1=open("./log.txt",O_WRONLY|O_CREAT|O_TRUNC);
//     const char * message="hello linunx\n";
//     write(fd1,message,strlen(message));//     close(0);
//     int fd2=open("./test.txt",O_RDONLY);
//     char  buffer[64];//     int end=read(fd2,buffer,63);
//     buffer[end]='\0';
//     printf("buffer contens:%s",buffer);
//     return 0;
// }// int main()
// {
//     //开启一个写文件描述符
//     FILE* f1=fopen("./log.txt0","w");
//     dup2(f1->_fileno,1);//     char message[64]="hello linux!!\n";
//     fwrite(message,strlen(message),1,f1);
//     //向屏幕进行打印
//     printf("hello world!!\n");//     return 0;
// }// int main()
// {//     printf("hello world!\n");
//     while(1);// }int main()
{write(1,"hello ",6);fwrite(" hello world!! ",16,1,stdout);int id=fork();if(id==0){}return 0;
}

总结

本文通过C语言中的读写接口作为引入介绍了Linux中的文件读写接口和Linux读写接口的参数,以及如何在Linux内核中fd如何实现定位要读写的文件,并由fd的分配规则引出了文件重定向和文件缓冲区,最后介绍了Linux内核对文件的管理视角是怎么样的。在下一篇文章中将解释文件如何在磁盘上的存储。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Python数据分析 Pandas库-初步认识

Python数据分析 Pandas库-初步认识 认识Pandas ​ pandas是一个非常实用的Python工具&#xff0c;我们可以把它想象成一个超级强大的表格处理工具&#xff0c;它比Excel更智能&#xff0c;操作更为简单。pands可以从各种文件格式&#xff08;CSV、JSON、SQL、Excel&#xff0…

电商数据采集分析全流程分享||电商数据API接口

电商数据监测&#xff0c;能为品牌发展提供参考依据&#xff0c;已经成为了业内共识。依托智能系统&#xff0c;将电商数据转换为有价值的营销情报&#xff0c;只需三步&#xff1a; 数据采集 可采集30多个电商平台数据&#xff0c;采集字段高达40多个&#xff0c;包含标题、价…

Axure设计之表格列冻结(动态面板+中继器)

在Web端产品设计中&#xff0c;复杂的表格展示是常见需求&#xff0c;尤其当表格包含大量列时&#xff0c;如何在有限的屏幕空间内优雅地展示所有信息成为了一个挑战。用户通常需要滚动查看隐藏列&#xff0c;但关键信息列&#xff08;如ID、操作按钮等&#xff09;在滚动时保持…

免费AI播客生成:notebooklm可以生成播客的两个发言人谈论的内容,从各种来源如研究论文、文章

参考&#xff1a; https://notebooklm.google.com/ 可以上传文章链接&#xff0c;ai自动生成播客两人对话&#xff1a; 另外notebooklm他本身也是个rag知识库对话&#xff0c;可以直接聊天框对话

vscode 中 python 代码跳转不生效 ctrl加单击不跳转

目录 网友的解决方法&#xff1a; 我的解决方法 vscode 中 python 代码跳转不生效 ctrl加单击不跳转 网友的解决方法&#xff1a; vscode 中 python 代码跳转不生效_vscode python 代码无法跳转-CSDN博客 解决方法 后来发现vs code初次远程连接服务器时&#xff0c;需要…

Redis的AOF持久化、重写机制、RDB持久化、混合持久化

1、AOF持久化 1.1.AOF持久化大致过程 概括&#xff1a;命令追加&#xff08;append&#xff09;、文件写入、文件同步&#xff08;sync&#xff09; Redis 每执行一条写操作命令&#xff0c;就把该命令以追加的方式写入到一个文件里&#xff0c;然后重启 Redis 的时候&#…

基于51单片机的电饭锅控制系统proteus仿真

地址&#xff1a; https://pan.baidu.com/s/1CGyg6uPhFI0MeaBWwe_HAg 提取码&#xff1a;1234 仿真图&#xff1a; 芯片/模块的特点&#xff1a; AT89C52/AT89C51简介&#xff1a; AT89C52/AT89C51是一款经典的8位单片机&#xff0c;是意法半导体&#xff08;STMicroelectro…

basectf2024 pwn week2-[Week2] format_string_level0

知识点: 格式化字符串漏洞 因为是用的read,其实应该也可以构造shellcode,但是好像全开了 但是因为这道题,自己读取了flag 所以我们找到flag和buf差多少 发现ptr(存flag)和buf差2 我们去找到buf from pwn import * ioprocess("./pwn") #ioremote("challenge…

本地部署轻量级web开发框架Flask结合内网穿透公网环境访问管理界面

文章目录 1. 安装部署Flask2. 安装Cpolar内网穿透3. 配置Flask的web界面公网访问地址4. 公网远程访问Flask的web界面 本篇文章主要讲解如何在本地安装Flask&#xff0c;以及如何将其web界面发布到公网进行远程访问。 Flask是目前十分流行的web框架&#xff0c;采用Python编程语…

破解信息架构实施的密码:常见挑战与最佳解决方案全指南

信息架构的成功实施是企业数字化转型的关键步骤&#xff0c;但在实际操作中&#xff0c;企业往往会遇到各种复杂的挑战。这些挑战包括 技术整合的难度、数据管理的复杂性、合规性要求的变化 以及 资源限制 等。《信息架构&#xff1a;商业智能&分析与元数据管理参考模型》为…

重生奇迹MU 浅析那些极具魔幻色彩的职业装备

沉稳厚重的剑士职业装备 剑士是所有喜欢近战作战方式的玩家首选的职业。作为来自勇者大陆的最强战士&#xff0c;剑士所穿戴的职业装备都偏向沉稳和厚重&#xff0c;通常全身覆盖重甲。这是因为剑士需要冲向敌人战斗&#xff0c;没有过硬的装备护身&#xff0c;他们很难承受住…

【油猴脚本】00006 案例 Tampermonkey油猴脚本自定义表格列名称,自定义表格表头,自定义表格的thead里的td

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【油…

多目标优化算法求解LSMOP(Large-Scale Multi-Objective Optimization Problem)测试集,MATLAB代码

LSMOP&#xff08;Large-Scale Multi-Objective Optimization Problem&#xff09;测试集是用于评估大规模多目标优化算法性能的一组标准测试问题。这些测试问题通常具有大量的决策变量和目标函数&#xff0c;旨在模拟现实世界中的复杂优化问题。 LSMOP测试集包含多个子问题&am…

C++基础面试题 | C++中的构造函数可以是虚函数吗? C++中的析构函数一定要是虚函数吗?

文章目录 问题一&#xff1a;在C中&#xff0c;构造函数不能是虚函数。问题二&#xff1a;析构函数不一定需要声明为虚函数&#xff0c;但在多态环境下&#xff0c;建议一定将其声明为虚函数。示例虚函数总结 问题一&#xff1a;在C中&#xff0c;构造函数不能是虚函数。 这是…

先框架后历元还是先历元后框架?

最近测绘同行高总发现了一个问题&#xff0c;《CH/T 2014-2016 大地测量控制点坐标转换技术规范》中”5.1.4.1 a) 不同ITRF间框架转换参数的历元归算“中历元不明确&#xff0c;不知是观测历元还是目标历元。他和一些同行用一些数据测试验证后认为观测历元更为贴合实际。 ​编…

不同博弈情景下策略选择的最优化探索

一&#xff1a;鹰鸽博弈 鹰鸽博弈是博弈论中的一个经典模型&#xff0c;以下是对鹰鸽博弈的具体介绍&#xff1a; 基本策略和行为模式&#xff1a; 鹰策略&#xff1a;代表着激进、好斗、具有攻击性的行为方式。在博弈中&#xff0c;选择鹰策略的个体在面对竞争或冲突时会全力…

“树”据结构:并查集从入门到AC

“树”据结构&#xff1a;并查集 前言算法设计代码示例优化相关文章 前言 在一组数据中&#xff0c;数据被分为了不同的集合&#xff0c;那么其中的集合往往可以用树形来表示。而区分集合&#xff0c;与查找集合的元素&#xff0c;就会成为核心的问题。并查集主要就是解决这类…

2024_中秋国庆双节来临 祝CSDN所有开发者与网站节日快乐

亲爱的CSDN朋友们&#xff1a; 在这个金风送爽、丹桂飘香的美好时节&#xff0c;我们迎来了一年一度的中秋佳节。明月高悬&#xff0c;洒下银辉&#xff0c;照亮了我们心中的思念与祝福。 中秋&#xff0c;是团圆的象征。无论你此刻身在何处&#xff0c;心中那份对家的眷恋、对…

0基础带你入门Linux之简介

1.Linux和Windows对比 Window很明显的特征就是有C盘、D盘登各种磁盘 我们通过点击不同的盘符&#xff0c;点击里面存储的文件进行查阅的操作 而Linux则很简单&#xff0c;只有一个根目录&#xff0c;也可以说只有一个盘&#xff0c;整个系统所有的东西都是在根目录下的 我们可…

redis基本数据结构-set

文章目录 1. set的基本介绍1.1. set底层结构之hash表的简单介绍1.2. 常用命令 2. 常见的业务场景2.1. 标签系统2.2. 社交网络好友关系 1. set的基本介绍 参考链接&#xff1a;https://mp.weixin.qq.com/s/srkd73bS2n3mjIADLVg72A redis 的 set 数据结构是一个无序的集合&#…