linux文件——深度学习文件fd、文件系统调用

news2024/11/15 4:22:08

        前言:从本片开始正式进入linux文件的学习,本片内容主要是文件的fd。 本篇内容博主将要先带友友回忆C语言中的文件操作接口,然后再过渡到操作系统中的系统调用的学习,最后理解操作系统中的文件操作。

         ps:本节内容设计一点linux下的task_struct的知识,友友们最好了解一点进程PCB相关知识后再进行观看

目录

C语言下的各种文件操作接口

fopen

chdir

fwrite

>

strlen是否加一的问题

认识文件系统

open

O_WRONLY

O_CREAT

 mode

write 

O_TRUNC清空写

O_APPEND追加写

访问文件的本质

文件fd——系统级别

FILE——语言级别


C语言下的各种文件操作接口

fopen

        我们先使用man fopen, 查看一下c里面的打开文件操作:

        然后我们从图中就可以看到fopen的返回值是一个FILE的类型, 而这个东西以前我们不知道是什么。 但是现在我们可以知道了, 这个东西其实就是一种叫做文件句柄的东西。 

        我们以前说过, 如果fopen打开一个文件, 这个文件在当前目录下不存在, 又是以写的方式方式打开。 那么这个文件就会自动在当前程序的目录下创建一个文件。 

        这个规则在man手册中也有提到:

        就是如上图的红框框, 如上图就是说将文件清空, 或者创建一个新的文件。

        现在我们写下面这个程序:        

运行后,就会发现, 创建了一个新的文件。 

chdir

        现在, 我们回到代码当中, 看我们给fopen传的第一个参数。(如下图)

这个参数是我们打开文件的路径和文件名。 但是如果只有文件名, 那么就默认在当前路径下创建一个文件。 但是问题来了,  当前路径是什么呢?——我们在讲进程的时候, 程序打开, 就会形成进程, 进程有一个当前工作路径, 这个当前工作路径cwd, 就是我们所说的当前路径。 ——如果我更改了当前进程cwd, 就可以把文件新建到其他目录。 而更改工作路径的系统调用就是chdir

        现在我们来试验一下:

我们先在当前路径下创建一个cydir目录, 一会我们就要将newfile.exe的工作路径改到cydir里面去。

这个时候里面还没有什么东西:

修改一下我们的程序, 让这个程序睡眠上100秒, 方便我们观察proc命令, 查看当前进程的工作路径。 

输入ls/proc/9238 -l指令

就可以查到当前进程的工作路径cwd:

         然后我们修改我们的程序——使用chdir修改工作路径:

        然后我们就会发现我们的工作路径确实发生了改变。 

fwrite

先使用man手册查看一下fwrite

        fwrite有四个参数, 第一个参数是ptr, 代表需要拷贝的数据的起始地址; 第二个参数是要拷贝的大小; 第三个参数是要拷贝的个数;第四个参数是要拷贝到哪里去。 

        下面是我们的程序以及运行结果:

        现在, 我们再写入其他的字符串, 就会发现原本的数据都被清空了, 只剩下新写入的数据:

下面是代码以及运行结果:

这是为什么呢? 我们重新看一下man手册中对w方法的论述, 就如同下图的红框框——对于w方法, 如果没有该文件, 那么就创建一个文件。如果有这个文件, 就让这个文件清空再写入。 

>

>和w类似, 重定向到文件中的时候, 我们的文件会先被清空,再写入。

如果什么都没写, 只是 > 文件名, 那么文件会被清空, 里面没有任何数据。

 

        由此, 我们可以发现, 其实>的底层就是"w"方法!!!两者都是打开文件即是空文件!!

strlen是否加一的问题

        我们这里思考一个问题, 我们在c语言中, 每一个字符串都有一个\0, 这个\0在strlen的时候不会计算, 并且不会显示。 那么, 我们看下面这个代码:

        这个strlen(src)处要不要加1? 我们目前虽然不知道要不要加一, 但是我们可以试验一下, 如果加1会打印什么, 这里我们实验, 下面是运行结果:

        我们可以发现, 在换行符后面又多打印了一串乱码。 ——而博主可以告诉友友们, 这个乱码其实就是\0。 strlen + 1就是将最后一个\0也包括进来了, 所以会打印\0

        那么问题来了, 我们平时打印的时候, 要不要加这个1呢? ——答案是不加, 因为我们的字符串后面默认补\0是c语言中的规则, c语言规定字符串的长度默认加1, 但是这是c语言规定, 而不是操作系统规定。 所以, 对于操作系统来说, 我们不加1。

认识文件系统

认识文件系统之前,我们需要理解几个知识点:

  •         首先, 文件是不是在磁盘上的?——是的。 那么磁盘是不是硬件设备呢?——是的。 那么访问文件的本质上其实就是访问硬件。 ——无论是什么文件, 比如显示器, 向显示器上面打印数据的本质上就是在访问显示器文件。 又比如键盘, 我们从键盘输入本质上就是在访问键盘文件。 而且, 要知道, 磁盘文件就是硬件, 而文件保存在磁盘里。 我们平时在IO的时候访问文件, 那么是不是就是在访问磁盘?——结论就是, 访问文件, 本质上就是访问硬件。
  •         我们知道, 整个计算机结构的最上层是用户, 下面是程序, 再下面是操作系统, 然后是驱动器, 最后是硬件。 如下图:

        并且, 对于用户来说, 不存在用户越过中间的程序、操作系统、驱动器而直接去访问硬件。用户做不到, 因为操作系统不相信任何人。 用户要访问硬件必须从上向下贯穿整个操作系统。 又因为操作系统不相信任何人, 所以操作系统和程序又有一层系统调用。——几乎所有的库只要是访问硬件,必定要封装系统调用!!!比如fprintf/printf/fscanf/fread/gets/fgetss………都是库函数!!!都能访问硬件, 所以一定封装系统调用!!!

open

        现在我们来看一个系统调用——open, 下面是man手册:

        使用man 2 open

        这里有两个函数, 两个的区别就是第三个参数:mode_t mode。

        这两个参数, 第一个参数是我们要打开的文件名——即路径。 第二个是我们要打开的文件的模式——即权限。

        然后第二个open里面的特殊的第三个参数, 这个参数就是指定我们创建一个新文件的时候创建这个文件的默认权限。

        那么就是说, 第一个open一般用于我们已经存在这个文件时候的打开。 第二个open一般用于这个文件不存在的时候再打开。

        那么第一个参数就是要开的文件的所在路径, 这个路径相对和绝对可以, 但是如果不带路径, 只有文件名那么就以进程的当前工作目录来决定了。

        第二个参数叫做flags, 这个flags有几个常见的选项O_RDONLY、O_WRONLY、O_RDWR、O_ERCAT、O_APPEND——这些选项是宏定义。

        我们如果学过位图, 我们就知道, 对于一个整形来说, 有32个比特位, 那么就是有32个标志位。 而这种标志位就是flags, flags利用了这种比特位级别的标志方式。

        接下来我们做一些实验。

        我们想要看ONE处的标志位, 那么我们就可以show(ONE), 想看TWO处的标志位, 那么就show(TWO)。 如果想要看一下ONE以及TWO处的标记位, 我们就show(ONE|TWO)。

        然后结果就是如下:

        接下来我们测试一下具有两个参数的open函数:

O_WRONLY

我们使用下面的代码测试, 注意, 这个时候我们只以的方式打开:

我们会发现, 没有文件被创建出来, 就如同下图:

O_CREAT

而我们想要创建出这个文件, 就需要使用O_WRONLY和O_CREAT两种方式组合。

 这个时候再运行就能看到新创建出来的文件里。 但是这个时候新创建出来的文件权限是---s-wx---, 这个很显然不是文件被创建出来的时候的默认权限。

 mode

        想要获得默认权限, 两个参数的open不能解决这个问题。 必须使用三个参数的open。 第三个参数就是规定新创建的文件的默认权限。——第三个参数是默认权限, 我们在传参的时候必须传八进制。

        创建出来后, 我们就会发现我们创建出来的是664权限而不是666权限。 这是为什么呢? ——因为我们传的是默认权限, 而真正的权限等于默认权限 & (~umask)博主此时的umask是002, 所以最终权限是664.

        而如果想要不考虑umask, 就在对应的进程中运行函数umask(0)。

        这个时候, 我们创建的文件权限就是666了!!!

write 

write函数是写函数, 下面是man手册:

       

  •         第一个参数是打开文件的时候的返回值。——open的返回值。  
  •         第二个参数是要拷贝的数据来源的地址。
  •         第三个参数是要拷贝的字节数。

如下图, write的实际应用:

运行后, 运行的结果如下图:

但是我们修改写入的内容, 会发现文件的内容没有被清空。 下面是实验过程:
先写入一行bbbbbbbb后又写入了一行aaaa

下面是运行结果:

O_TRUNC清空写

        想要能够清空文件, 就要另外加一个选项:O_TRUNC, 这个选项就是先清空文件。

         加了选项之后我们再运行程序:

        首先打印一行bbbbbbbbbbbbbb

然后再写入一行aaaa, 下面是代码和运行结果:

我们可以发现, 已经清空了文件!!!

O_APPEND追加写

追加写需要用到O_APPEND选项:

现在我们回头看一线fopen、fpeintf这些函数的底层一定是封装了open, write这些函数。 另外, 像python, java这类语言, 他们也一定有类似于fprintf, fopen类似的函数。 这些函数的底层一定会封装open, write, read这些系统调用。 

访问文件的本质

文件fd——系统级别

首先, 我们知道, 文件一定是保存在磁盘中, 我们的一个进程启动时, 会加载一个PCB。 而当进程访问一个文件的时候, 首先会打开这个文件。 然后就会内存中加载一个叫做struct_file的结构体。 这个结构体里面描述了一个被打开的文件的信息。 

        对于每一个进程, 都可能打开多个文件, 这些文件都会有对应的struct file结构体, 也就是上图的连成串的链表的一个节点。 那么进程如何找到这些struct file结构体呢?

        其实, 每一个进程里面都有一个struct file_struct* files, 指向一个名叫struct file_struct的结构体, 这个结构体里面保存了一个指针数组。 这个数组的名字叫做struct file* fd_array[]。 这些文件指针数组指向一个个描述文件的结构体。

        当我们打开一个文件的时候, 就会生成一个描述文件的结构体。 同时进程会在文件指针数组里面找一个空位置保存刚刚创建的描述文件的结构体的地址。 然后再讲这个数组的下标返回给用户。 这个返回值就是open的返回值!!!最终, 进程就可以根据这一张文件描述符表, 就可以把我们打开的文件找到了。

        那么open的返回值具体是如何返回的呢? ——当我们使用open打开一个文件, 操作系统就会创建一个文件描述对象。 然后文件描述符表就会在指针数组中找空位置指向这个文件描述对象。 然后将这个位置的下标返回给用户, 返回值就是一个整型类型的, 我们称为fd
 

        在上面那张图中, 我们如果划分区域, 可以划分如下:

那么左侧的就是进程管理, 右边的就是文件管理。 而两者的联系点就是struct file_struct。

多个文件创建的时候, 因为进程练习文件的本质就是使用指针数组。——是一个数组, 所以返回值fd是按照从小到大的顺序返回的。 如下图实验:

        运行结果3, 4, 5按照顺序创建的fd。

那么我们知道了, 文件的返回值fd时按照文件的打开顺序返回, 那么请问, 文件的返回值, 上图中是从3开始的。 那么0, 1, 2去哪里了呢? 或者说有0号下标吗?

我们需要知道, c程序在启动的时候, 会默认打开三个文件——stdin、stdout、stderr。 这三个文件会默认占用0, 1, 2三个fd所以我们在新建文件的时候, 是从3开始的。 

//现在我们使用fd == 1, 也就是显示器文件。 在这个文件里打印数据, 就能将数据在终端中打印出来:

//接下来测试fd == 0, 事实stdin——我们要使用read函数读取数据:

        由上面两个测试我们可以发现, 当进程打开的时候, 就会默认将0, 1, 2打开!!!

        那么我们再思考一下, 请问我们默认打开的三个标准输入输出, 是c语言的特性吗???不是, 是操作系统的特性!操作系统会默认打开进程的三个标准输入输出!!!

        那么, 操作系统为什么要默认打开呢?

        因为计算机开机的时候, 我们是不是就能看到显示器画面里, 而且键盘也已经被识别了? 所以, 我们计算机开机的时候, 操作系统已经将我们的显示器和键盘打开了。 而且我们在进行编程的时候, 是必须要用显示器和键盘的。 所以, 当我们打开一个进程, 那么只需要将已经打开的显示器, 键盘文件的地址填到自己进程PCB里。——而之所以要这样, 是因为程序员天然就需要用键盘输入, 用显示器看结果。

FILE——语言级别

        FILE是什么东西? ——对于FILE* fopen来说, 这个是c库自己封装的一个结构体。 而因为linux访问文件的时候只认文件描述符, 所以我们就可以确定, 这个FILE结构体里面必须封装文件描述符!!!

        就比如我们看一下C语言中的stdin, stdout, stderr:

        三个默认文件流底层一定封装了:stdin, 对应fd == 0; stdout, 对应fd == 1; stderr, 对应fd == 2;——也就是说, FILE类型里面一定给封装有fd这样的文件描述符字段。

        而且, 我们也知道, 我们的C库里面有各种接口, 这里我们用fwrite举例:

        fwrite一定要拿到FILE类型的结构体才能执行写入操作。 而fwrite底层的系统调用是write, write写入需要fd。 那么换句话说, FILE里面一定有fd字段。 也就是文件描述符!!!那么我们现在测试一下到底是不是这样的呢? 下面是代码和测试结果:

现在我们关闭1号文件, 看看是否能够关闭:

很显然, 不能打印了, 说明1号文件, 也就是显示器文件被关闭了。

但是这里有一个问题, 就是我们在stderr文件中打印, 我们仍旧可以看到打印结果:

这是为什么呢? ——这是因为虽然1号和二号都是指向显示器文件。 但是显示器文件有引用计数并不仅仅只有显示器文件有引用计数, struct file结构体里面都有引用计数), 关闭1号文件不会彻底关闭显示器文件!!! 也就是说, 1号和2号是根据文件描述符分开打印的!!! 关闭文件的本质就是让struct file里面的引用计数减减和自己这个进程里面指向这个struct file的下标置为空就可以了

        那么我们重新捋一下这个过程——也就是说一开始1, 2号fd都是指向显示器文件, 此时显示器文件的引用计数是2. 当1关闭, 显示器文件的引用计数减减, 变成1, 1号fd指向空, 但是2号fd不变。 所以2号fd依旧可以向显示器打印。

        那么我们就可以确定, 我们C++里面使用的iostream, 虽然可能包含了继承体系或者各种基类, 但是最底层, 一定包含了fd!——因为什么都可以改变, 但是操作系统的系统调用不会变, 变的只是上面语言级别的各种外壳!

----------------------------------------------以上, 就是本节全部内容, 下面是本节笔记:

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

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

相关文章

Android 10.0 DocumentsUI文件管理器首次进入默认显示内部存储文件功能实现

1.前言 在10.0的系统rom定制化开发中,在关于文件管理器的某些功能中,在首次进入文件管理器的时候默认进入下载 文件夹,点击菜单选择内部存储的时候,会显示内部存储的内容,客户开发需要要求默认显示内部存储的文件 接下来分析下功能的实现 如图: 2.DocumentsUI文件管理器首…

入营测评题解

第一题&#xff1a;first 第二题&#xff1a;chengji 打擂台&#xff0c;每个数跟当前最大、最小值比较&#xff0c;维护当前最值即可。 #include<bits/stdc.h> using lllong long; using namespace std;const int N1e610;int n; int x;//1e9, ll最大9e18 ll maxn0,minn…

C++ 新特性 | C++17 常用新特性介绍

目录 1、结构化绑定 2、constexpr扩展 2.1、constexpr lambda 2.2、constexpr if 2.3、constexpr string 4、if with initializer 5、std::optional 6、使用inline定义内联变量 7、std::filesystem库 8、折叠表达式 9、模板的模板参数推导 9.1、从构造函数参数推导…

景联文科技:破解数据标注行业痛点,引领高质量AI数据服务

数据标注行业是人工智能和机器学习领域中一个非常重要的组成部分。随着AI技术的发展&#xff0c;对高质量标注数据的需求也在不断增长。 数据标注市场的痛点 1. 团队管理 在众包和转包模式下&#xff0c;管理大量的标注人员是一项挑战。 需要确保标注人员的专业性、稳定性和…

【ConcurrentHashMap】JDK1.7版本源码解读与分析

如果对文章中提到的与 HashMap 相关的部分有任何疑问, 请移步HashMap源码详解 简介 底层是一个 Segment[] 数组, 每个 Segment对象 内部又有一个 Entry[ ] 数组, 一个 Entry[] 数组就相当于一个HashMap Entry[ ]采用拉链法解决冲突, 但是没有红黑树, 红黑树是1.8才引入的; 一…

音频进阶学习一——模拟信号和数字信号

文章目录 前言|版本声明&#xff1a;山河君&#xff0c;未经博主允许&#xff0c;禁止转载 一、什么是模拟信号和数字信号信号模拟信号数字信号数字和模拟信号的区别一览 二、信号处理系统总结 前言 所有软件的运行都得益于硬件上的突破&#xff0c;数字信号是从40年前就开始高…

达梦数据库 物理备份还原

达梦的物理备份还原 1.背景2.要求3.实验步骤3.1 相关术语3.2 准备工作3.3 联机备份还原3.3.1 数据备份3.3.1.1 手动备份3.3.1.2 定时备份 3.3.2 管理备份3.3.2.1 备份目录管理3.3.2.2 备份集校验与删除 3.3.3 数据还原 3.4 脱机备份还原3.4.1 DMRMAN工具3.4.2 数据备份3.4.2.1 …

https://ffmpeg.org/

https://ffmpeg.org/ https://www.gyan.dev/ffmpeg/builds/ https://github.com/BtbN/FFmpeg-Builds/releases F:\Document_ffmpeg F:\Document_ffmpeg\ffmpeg-master-latest-win64-gpl-shared\bin

python模式设计代码之观察者模式

1、观察者模式 话题订阅模式。观察者模式有两个角色&#xff0c;分别是话题发布者和话题订阅者&#xff08;即观察者&#xff09;。发布者就是把消息发送给话题&#xff0c;观察者就是订阅这个话题从而得到最新的资讯。这个模式的作用就拿手机的消息推送来说&#xff0c;app身…

深入C# .NET核心:委托与事件机制全解析

摘要&#xff1a; 在C# .NET编程中&#xff0c;委托和事件是实现异步编程和对象间通信的关键机制。理解它们的工作原理对于编写高效、响应式的应用程序至关重要。本文将深入探讨C# .NET中的委托与事件&#xff0c;从基础概念到高级应用&#xff0c;为读者提供全面的指导。 正文…

如何提高游戏的可玩性和趣味性?

提高游戏的可玩性和趣味性是吸引玩家并保持他们长期参与的关键。以下是一些策略和建议&#xff0c;可以帮助您增强游戏的吸引力和娱乐价值&#xff1a; 1. 独特的游戏机制 创新玩法&#xff1a;开发新颖、独特的游戏机制&#xff0c;让玩家在体验中感受到前所未有的乐趣。避免…

【网络编程】字节序,IP地址、点分十进制、TCP与UDP的异同

记录学习&#xff0c;思维导图绘制 目录 1、字节序​编辑 2、IP地址 3、点分十进制 4、TCP与UDP的异同 1、字节序 2、IP地址 3、点分十进制 4、TCP与UDP的异同

STL源码刨析:红黑树(RB-tree)

目录 1.前言 2.RB-tree的简单介绍 3.RB-tree的插入节点操作 4.RB-tree的删除节点操作 5.RB-tree的节点设计 6.RB-tree的迭代器设计 7.RB-tree的数据结构 8.RB-tree的构造与内存管理 9.RB-treed的元素操作 前言 在文章《STL源码刨析&#xff1a;树的导览》中&#xff0c;曾简单的…

使用 MongoDB 构建 AI:Flagler Health 的 AI 旅程如何彻底改变患者护理

Flagler Health 致力于为慢性病患者提供支持&#xff0c;为其匹配合适的医生以提供合适的护理。 通常&#xff0c;身患严重病痛的患者面临的选择有限&#xff0c;他们往往需要长期服用阿片类药物&#xff0c;或寻求成本高昂的侵入性外科手术干预。遗憾的是&#xff0c;后一种方…

linux小组件:git

git是什么&#xff1f; git是版本控制器&#xff08;去中心化的分布式系统&#xff09;可以快速高效地处理从小型到大型的各种项目。易于学习&#xff0c;占地面积小&#xff0c;性能极快。它具有廉价的本地库&#xff0c;方便的暂存区域和多个工作流分支等特性。 什么叫版本…

【数据结构七夕专属版】单链表及单链表的实现【附源码和源码讲解】

本篇是博主在学习数据结构时的心得&#xff0c;希望能够帮助到大家&#xff0c;也许有些许遗漏&#xff0c;但博主已经尽了最大努力打破信息差&#xff0c;如果有遗漏还请见谅&#xff0c;嘻嘻&#xff0c;前路漫漫&#xff0c;我们一起前进&#xff01;&#xff01;&#xff0…

微信小程序--19(.wxml 模板文件简单归纳)

类似HTML用来描述当前页面的结构 一、普通样式 1.<view> 内容 </view> 二、滚波样式 1.<swiper> 内容 </swiper> 2.<swiper-item>滚波内容 </swiper-item> 3.常用属性 纵向&#xff1a;scroll-y横向&#xff1a;scroll-x圆点颜色&am…

LinuxC高级day03(Shell脚本)

【1】Shell脚本 1》Shell脚本基础概念 1> 概念 Shell使用方式&#xff1a;手动在命令行下命令或用Shell脚本 Shell脚本本质&#xff1a;Shell命令的有序集合 扩展名最好以 .sh 结尾&#xff0c;见名知义 也可以没有 Shell既是应用程序&#xff0c;又是一种脚本语言 解…

迁移学习之基本概念

迁移学习 1、通俗定义 迁移学习是一种学习的思想和模式 迁移学习作为机器学习的一个重要分支&#xff0c;侧重于将已经学习过的知识迁移应用于新的问题中 迁移学习的核心问题是&#xff0c;找到新问题和原问题之间的相似性&#xff0c;才可以顺利地实现知识地迁移 定义&…

运行pytorch报异常处理

一、问题现象及初步定位&#xff1a; 找不到指定的模块。 Error loading "D:\software\python3\Lib\site-packages\torch\lib\fbgemm.dll 此处缺少.dll文件&#xff0c;首先下载文件依赖分析工具 Dependencies https://github.com/lucasg/Dependencies/tree/v1.11.1 之后下…