【Linux】深入理解Linux文件系统:从C接口到内核设计哲学


文章目录

  • 前言
  • 一、C语言中的文件接口
    • 1. 文件指针(句柄)FILE*
      • 以写方式打开文件,若文件不存在会新建一个文件
      • W写入方式,在==打开文件之前==都会将文件内容全部清空
      • 追加写方式,其用法与写方法一致,不同在于a方法可以在文件结尾写入
  • 二、认识文件系统调用
    • Linux下的系统调用open()
      • 第一个参数为文件路径
      • 第二个参数为操作文件的方式
      • 第三个可选参数是更改创建文件的默认权限:
  • 三、访问文件的本质
  • 四、重定向与缓冲区
    • 自定义重定向系统调用接口dup2
  • 再谈“一切皆文件”
    • 1. 外设设备与文件系统的关系
    • 2. 扩展思想:
  • 总结


前言

在计算机系统中,文件由内容数据和元数据属性共同构成。文件的完整生命周期分为两个阶段:

文件状态存储位置管理方式
未打开文件磁盘存储介质文件系统通过inode管理
已打开文件内存内核通过file结构体管理
  • 所有文件操作本质上都是进程与文件系统的交互
  • 打开文件需要将文件属性加载到内存
  • 文件内容采用按需加载策略(延迟加载)

研究文件系统本质是研究进程和文件之间的关系(文件是由进程打开的);未打开的文件存在磁盘上(存储介质),文件要被打开(属性)必须先要加载到内存;


一、C语言中的文件接口

基本输入输出 stdio.h
访问磁盘的过程称之为IO的过程,

1. 文件指针(句柄)FILE*

//C标准库通过FILE结构体封装文件描述符FILE *fopen(const char *path, const char *mode)
// mode参数决定了你的访问权限
mode说明特性
“w”写模式(清空文件)文件不存在时创建
“a”追加模式保留原内容,末尾写入
“r”读写模式文件必须存在

以写方式打开文件,若文件不存在会新建一个文件

若没有指定路径,程序会在默认当前路径下创建,当前路径指的是进程的当前路径(使用ls /proc/[pid] 查看到当前进程的cwd)。
在这里插入图片描述
同样的,修改当前进程的工作目录就可以改变创建文件的默认路径。

chdir("home/ys") //修改进程工作路径为home/ys

W写入方式,在打开文件之前都会将文件内容全部清空

在这里插入图片描述

上一个程序疑问:strlen要不要+1?

我们知道写入字符串时需要将\0也写入,我们试验之后发现文本中多了@^这样的乱码,推测这就是\0,只不过vim文本编辑器将其解释成了乱码符号。结论是strlen不需要+1,文件系统没有规定字符串必须以\0结尾

追加写方式,其用法与写方法一致,不同在于a方法可以在文件结尾写入

二、认识文件系统调用

c语言程序在启动时,会默认打开三个标准输入输出流文件:

stdin:键盘设备
stdout:显示器文件
stderr:显示器文件

文件其实是在磁盘上的,由于磁盘是外部设备,访问文件实际上是访问磁盘这样的硬件。不同的语言有不同的文件操作方式,但在底层用的是都是一样的实现方式——都需要调用系统接口open、read、write。

库函数(fopen,printf,fscanf等)访问硬件设备一定会通过系统调用来访问。

Linux下的系统调用open()

在这里插入图片描述

第一个参数为文件路径

  • 若pathname以路径的方式给出,则当需要创建该文件时,就在pathname路径下进行创建。
  • 若pathname以文件名的方式给出,则当需要创建该文件时,默认在当前路径下进行创建。(注意当前路径的含义)

第二个参数为操作文件的方式

方式含义
O_RDONLY以只读的方式打开文件
O_WRNOLY以只写的方式打开文件
O_APPEND以追加的方式打开文件
O_RDWR以读写的方式打开文件
O_CREAT当目标文件不存在时,创建文件

1. O_WRONLY是写方式,但是它并不会新建文件
2. O_CREAT打开文件时清空文件

3. O_APPEND 追加写选项
写入:

const char* message = "hello";
write(fd,message,strlen(message));
//write并不会对文件进行清空式写入。
int fd = open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666); //追加
write(fd,message,strlen(message),);

第三个可选参数是更改创建文件的默认权限:

//eg:
int fd = open("log.txt",O_WRONLY|O_CREAT); 

创建权限错误,所以新建文件时需要告诉接口权限是什么。
在这里插入图片描述

int fd = open("log.txt",O_WRONLY|O_CREAT,0666); 

在这里插入图片描述
这里创建出来的并不是666而是664,应该要想到之前学到的权限掩码(0002)的知识!

比特位级别的传参方式原理:
使用位图的方式,一次向操作系统传递多个标志位

三、访问文件的本质

可以将其类比系统管理进程(struct_task),Linux系统中一切皆文件,因此管理进程势必要通过先描述再组织的方法进行。要描述一个被打开的文件(struct_file),往往需要包含文件路径、文件基本属性(权限、大小、读写位置、访问用户的信息等)、文件的内核缓冲区信息、下一个struct_file的指针

一个进程可能会打开多个文件,那么进程与文件之间又是如何关联的?(1:n)

进程PCB中会存在一个结构体指针struct files_struct *files指向了一个结构体,该结构体存放了一个存放各种文件PCB指针的数组;因为是数组,所以这也解释了为什么open接口返回的是int类型的值了,进程根据这个下标就可以访问对应文件。

如果尝试打印一下返回值,发现文件描述符默认是从3开始的,那么0,1,2是什么文件呢?那就是标准输入输出错误流了!(stdin \ stdout \stderr

int fd = open("demo.txt",O_WRONLY |O_CREAT,0666);
cout << fd << endl; //3cout << stdin->_fileno << endl;//0
cout << stdout->_fileno << endl;//1
cout << stderr->_fileno << endl;//2

在这里插入图片描述
既然一切皆文件,那么输出流也是文件,因此我们可以使用以下代码向标准输出流文件中写入message信息:

const char* message = "hello";
write(1,message,strlen(message));// 1 就是标准输出流stdout

从标准输入流文件中读取buffer大小的字符放在buffer[1024]数组中 :

char buffer[1024];
read(0,buffer,sizeof(buffer));
printf("echo: %s\n",buffer);

四、重定向与缓冲区

文件描述符对应的分配规则是什么?

从0下标开始,寻找没有被使用的数组位置,它的下标就是新文件的文件描述符值。

假设我们有一个空文件log.txt,有如下代码,含义是将msg中的strlen长度的数据输出到显示器。

const char* msg = "hello linux\n";
write(1,msg,strlen(msg));

但如果先关闭了1描述符(即关闭标准输出流),除了显示器无法显示外

close(1);
int fd = open("log.txt", O_RDONLY | O_CREAT, 0666);//1
const char* msg = "hello linux\n";
write(1,msg,strlen(msg));//此时写入的就是1号文件描述符,即log.txt 文件

log.txt中居然存有数据。
这一工作,称为输出重定向。根据上面的知识可以意识到关闭了1描述符后,那么这里就是空着的,当使用open接口新建log.txt时,根据文件描述符分配规则,自然1号位就成为了log.txt的fd描述符,所以将本来要写入stdout的数据写入到了log.txt中。

自定义重定向系统调用接口dup2

int dup2(int oldfd,int newfd)
把oldfd复制到newfd
//oldfd 相当于 原本的 3 描述符
//newfd 相当于 原本的 1 描述符int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);
dup2(fd, 1); 

这里要注意的是,重定向中的拷贝,不是将文件描述符表中的下标进行拷贝,而是对下标处的内容(文件结构体指针)进行拷贝!

使用dup2在打开文件log.txt后,进行了输出重定向,将原本输出到显示器的内容写入到了log.txt文件中。再次更改代码open的宏参数(O_TRUNC -> O_APPEND),就成为了追加重定向操作。结果如下所示:
在这里插入图片描述
同样的,可以修改代码让其重定向标准输入流至文件(默认read从stdin文件读数据,重定向后,从log.txt文件中读)。这一过程称为输入重定向

在这里插入图片描述

以上是使用dup2重定向系统调用函数write、read,前面提到c语言printf、fprintf底层也是这样的文件描述符表的结构,那是否可以控制c语言中的输入输出呢?

dup2(fd,1);
printf("hello printf\n");
fprintf(stdout,"hello printf\n");

回想之前的章节介绍到echo指令,可以进行输出重定向,cat指令可以进行输入重定向

echo "hello" > log.txt   //输出重定向
cat < log.txt            //输入重定向
echo "hello" >> log.txt  //追加重定向

进程的替换不会影响文件的访问(包括重定向操作)——复习进程替换

stdout与stderr都是可以向显示器打印,为什么要有2?他们俩的区别是什么?

有如下代码,表示将字符串分别输出到1(标准输出流)和2(标准错误流)中。
在这里插入图片描述

$ ./mytest 1>normal.log 2>err.log
//将stdout的数据重定向至normal.log
//将stderr的数据重定向至err.log

在这里插入图片描述

实际上,1和2是相同的实现方式,只不过在使用中,相较于正常结果而言,更关注的是它的错误信息,而正常运行的信息往往很多,不便错误的筛查与纠正。因此,为了将错误信息分离出来,才有了标准错误流。

一个衍生用法:

$ ./mytest >normal.log 2>&1

再谈“一切皆文件”

1. 外设设备与文件系统的关系

在这之前我们知道:所有操作计算机的动作都是由进程执行的,包括文件的访问,每一种外设都要有描述他们的结构体对象(struct_dev)

此外,每一种外设都有其相独特的读写方法,纵然每个外设对应的访问实现方式不同(各家外设设备驱动的不同),而对于操作系统来看,这些外设无非都是一些需要进行读写的文件,而能够直接进行文件访问读写的就是进程(open接口),打开新的文件就会创建一个新的struct_file,这个结构体是不是很熟悉?在这个结构体中,就存在着能够指向该文件具体实现自身读写行为的指针(struct fils_operations*),例如(指向了不同磁盘的读写方法,不同键盘的读写方法)。

  • 在Linux中,将struct_file这一层的逻辑关系称为虚拟文件系统(VFS)

外设差异化被封装在驱动中:不同厂商的驱动实现自己的读写逻辑(如razer_keyboard_readlogitech_keyboard_read),但必须遵循操作系统定义的接口。
操作系统通过抽象层统一接口:上层应用只需调用 read()、write() 等标准接口,无需关心底层是罗技还是雷蛇设备。

2. 扩展思想:

这种设计模式与 面向对象编程中的多态性高度相似

​基类(抽象接口) ​:操作系统定义的设备驱动接口(如file_operations)。
​派生类(具体实现) ​:厂商驱动的读写函数(如雷蛇、罗技的实现)。
运行时多态 :通过函数指针动态绑定到具体实现。

通过这种机制,操作系统实现了外设的 ​​“高内聚、低耦合”​,使得硬件厂商可以自由创新,同时保持软件生态的兼容性。


总结

👍 ​感谢各位大佬观看。如果本文有帮助,请点赞收藏支持~

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

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

相关文章

国产品牌芯洲科技100V降压芯片系列

SCT2A25采用带集成环路补偿的恒导通时间(COT)模式控制&#xff0c;大大简化了转换器的片外配置。SCT2A25具有典型的140uA低静态电流&#xff0c;采用脉冲频率调制(PFM)模式&#xff0c;它使转换器在轻载或空载条件下实现高转换效率。 芯洲科技100V降压芯片系列提供丰富的48V系…

ctfshow-大赛原题-web702

因为该题没有理解到位&#xff0c;导致看wp也一直出错&#xff0c;特此反思一下。 参考yu22x师傅的文章 &#xff1a;CTFSHOW大赛原题篇(web696-web710)_ctfshow 大赛原题-CSDN博客 首先拿到题目&#xff1a; // www.zip 下载源码 我们的思路就是包含一个css文件&#xff0c;…

LabVIEW技巧——获取文件版本信息

获取可执行文件&#xff08;exe&#xff09;版本信息的几种方法 方法1. LabVIEW自带函数 labview自带了获取文件版本号的VI&#xff0c;但是没有开放到程序框图的函数选板中&#xff0c;在该目录下可以找到&#xff1a;...\LabVIEW 20xx\vi.lib\Platform\fileVersionInfo.llb…

三格电子——CAN 转光纤(点对点)布线常见问题

1、CAN 布线 &#xff08;1&#xff09;H 接 H ,L 接 L &#xff08;2&#xff09;两端设备挂 120 欧姆电阻 2、假如用点对点的 CAN 转光纤现实远程传输 &#xff08;1&#xff09;H 接 H ,L 接 L &#xff08;2&#xff09;光端机都挂 120 欧姆电阻 每个光端机挂的设备有一个加…

python进阶: 深入了解调试利器 Pdb

Python是一种广泛使用的编程语言&#xff0c;以其简洁和可读性著称。在开发和调试过程中&#xff0c;遇到错误和问题是不可避免的。Python为此提供了一个强大的调试工具——Pdb&#xff08;Python Debugger&#xff09;。 Pdb是Python标准库中自带的调试器&#xff0c;可以帮助…

React 设计艺术:如何精确拆分组件接口,实现接口隔离原则

接口隔离原则 接口隔离原则&#xff08;Interface Segregation Principle&#xff0c;简称 ISP&#xff09;也是面向对象设计中的重要原则之一。它的核心思想是&#xff0c;一个类不应该依赖它不需要的接口。在 React 开发中&#xff0c;遵循接口隔离原则可以提高代码的可维护性…

内部聊天软件,BeeWorks-安全的企业内部通讯软件

企业在享受数据便利的同时&#xff0c;如何保障企业数据安全已经成为无法回避的重要课题。BeeWorks作为一款专为企业设计的内部通讯软件&#xff0c;通过全链路的安全能力升维&#xff0c;为企业提供了一个安全、高效、便捷的沟通协作平台&#xff0c;全面保障企业数据安全。 …

【零基础】基于 MATLAB + Gurobi + YALMIP 的优化建模与求解全流程指南

MATLAB Gurobi YALMIP 综合优化教程&#xff08;进阶&#xff09; 本教程系统介绍如何在 MATLAB 环境中使用 YALMIP 建模&#xff0c;并通过 Gurobi 求解器高效求解线性、整数及非线性优化问题。适用于工程、运营研究、能源系统等领域的高级优化建模需求。 一、工具概览 1.…

Freertos----互斥量

一、为什么要使用互斥量&#xff1f; 我们想让任务A、B都执行add_a函数&#xff0c;a的最终结果是18817。 假设任务A运行完代码①&#xff0c;在执行代码②之前被任务B抢占了&#xff1a;现在任务A的R0等于1。 任务B执行完add_a函数&#xff0c;a等于9。 任务A继续运行&#…

高级java每日一道面试题-2025年4月11日-微服务篇[Nacos篇]-Nacos使用的数据库及其数据同步机制是什么?

如果有遗漏,评论区告诉我进行补充 面试官: Nacos使用的数据库及其数据同步机制是什么&#xff1f; 我回答: Nacos 使用的数据库及其数据同步机制详解 在微服务架构中&#xff0c;Nacos 作为服务注册与配置管理的核心组件&#xff0c;其数据存储和同步机制对系统的高可用性和…

揭秘大数据 | 22、软件定义存储

揭秘大数据 | 19、软件定义的世界-CSDN博客 揭秘大数据 | 20、软件定义数据中心-CSDN博客 揭秘大数据 | 21、软件定义计算-CSDN博客 老规矩&#xff0c;先把这个小系列的前三篇奉上。今天书接上文&#xff0c;接着叙软件定义存储的那些事儿。 软件定义存储源于VMware公司于…

git常用修改命令

1. 代码回退与历史修改 git reset 模式命令示例作用范围适用场景--softgit reset --soft HEAD~1仅移动 HEAD 指针重新提交之前的修改--mixedgit reset HEAD~1 (默认)重置暂存区取消已 add 但未提交的文件--hardgit reset --hard a1b2c3d彻底丢弃工作区和暂存区彻底回退到某个…

【ubuntu】linux开机自启动

目录 开机自启动&#xff1a; /etc/rc.loacl system V 使用/etc/rc*.d/系统运行优先级 遇到的问题&#xff1a; 1. Linux 系统启动阶段概述 方法1&#xff1a;/etc/rc5.d/ 脚本延时日志 方法二&#xff1a;使用 udev 规则来触发脚本执行 开机自启动&#xff1a; /etc/…

Python深度学习基础——深度神经网络(DNN)(PyTorch)

张量 数组与张量 PyTorch 作为当前首屈一指的深度学习库&#xff0c;其将 NumPy 数组的语法尽数吸收&#xff0c;作为自己处理张量的基本语法&#xff0c;且运算速度从使用 CPU 的数组进步到使用 GPU 的张量。 NumPy 和 PyTorch 的基础语法几乎一致&#xff0c;具体表现为&am…

光伏产品研发项目如何降本增效?8Manage 项目管理软件在复合材料制造的应用

在复合材料制造领域&#xff0c;特别是光伏PECVD石墨舟和燃料电池石墨双极板等高精尖产品的研发过程中&#xff0c;高效的项目管理直接决定了产品开发周期、质量和市场竞争力。然而&#xff0c;许多企业在项目立项、进度跟踪、资源分配和质量控制等环节面临挑战。 针对这些痛点…

linux的glib库使用

glib常用接口使用 1. glib介绍2. glib命令安装3. 获取glib的版本信息和兼容信息4. glib使用例子4.1 链表例子4.2 哈希表例子4.3 使用面向对象例子 1. glib介绍 广泛应用于桌面环境、嵌入式系统、GNOME等项目中。它提供了完整的面向对象编程模型&#xff08;GObject&#xff09…

vs2022使用git方法

1、创建git 2、在cmd下执行 git push -f origin master &#xff0c;会把本地代码全部推送到远程&#xff0c;同时会覆盖远程代码。 3、需要设置【Git全局设置】&#xff0c;修改的代码才会显示可以提交&#xff0c;否则是灰色的不能提交。 4、创建的分支&#xff0c;只要点击…

SAP ECCS 标准报表 切换为EXCEL电子表格模式

在解决《SAP ECCS标准报表在报表中不存在特征CG细分期间 消息号 GK715报错分析》问题过程中通过DEBUG方式参照测试环境补录数据后&#xff0c;不再报GK715错误&#xff0c;此时用户要的很急&#xff0c;要出季报。要求先把数据导出供其分析出季报。 采用导出列表方式&#xff…

基于 Python 和 OpenCV 技术的疲劳驾驶检测系统(2.0 全新升级,附源码)

大家好&#xff0c;我是徐师兄&#xff0c;一个有着7年大厂经验的程序员&#xff0c;也是一名热衷于分享干货的技术爱好者。平时我在 CSDN、掘金、华为云、阿里云和 InfoQ 等平台分享我的心得体会。 &#x1f345;文末获取源码联系&#x1f345; 2025年最全的计算机软件毕业设计…

MATLAB项目实战(一)

题目&#xff1a; 某公司有6个建筑工地要开工&#xff0c;每个工地的位置&#xff08;用平面坐标系a&#xff0c;b表示&#xff0c;距离单位&#xff1a;km&#xff09;及水泥日用量d(t)由下表给出&#xff0e;目前有两个临时料场位于A(5,1)&#xff0c;B(2,7)&#xff0c;日储…