【Linux】13. 文件操作

news2025/1/11 22:43:42

1. 重新认识文件

经过之前的linux命令操作、进程相关概念的学习,我们对于文件也并不陌生
首先需要明确以下概念:

  1. 即使是空文件,也要在磁盘当中占据空间
  2. 文件 = 文件内容 + 文件属性
  3. 文件操作 = 对文件内容的操作 或者 对文件属性的操作 或者 二者都有
  4. 标定一个文件:必须使用文件路径+文件名【具有唯一性】
  5. 如果没用指明对应的文件路径默认是在当前路径下对文件进行访问(其中当前路径指的是进程的当前路径,其实就是环境变量中的一个值,是可以被修改的)
  6. 当我们将fopen,fclose,fwrite等接口实现完成,代码编译完成形成二进制可执行程序后,不运行,文件所对应的操作有没有执行呢?
    — 没有

以上概念视为对之前的总结, 对文件的操作其本质是进程对文件进行的操作!
如果一个文件没有被打开,可以直接被访问嘛?— 不能,一个文件要被访问就必须先要打开(怎么打开呢? --用户进程调用文件打开接口+OS实现文件打开功能)
所以,综上所述,文件操作的本质就是描述进程和被打开文件的关系
那么,未被打开的文件呢? – 属于文件系统的部分(后序博客中会介绍到)

2. 重新理解文件操作

在C语言的学习过程中,我们学习到大量的文件操作接口并使用,但我们只是了解其如何使用,并未了解操作系统底层是如何实现的
那么对于其他语言(C++/Java/Python/php…)存不存在文件接口呢?
–我们不得而知,但按照常理而言都应该提供文件操作的接口 但是其接口实现都不一样

文件存储在磁盘当中(磁盘属于硬件,要访问硬件就绕不开OS(软硬件资源管理器),必须调用操作系统提供的接口)
所以不管上层语言如何发生变化,库函数底层都必须调用系统调用接口

那么如何降低学习成本呢? – 学习不变的东西(操作系统底层如何实现文件操作)

小知识点:批量化注释 Ctrl+v+j/k+I +“//” +Esc

在这里插入图片描述

3. 文件操作(C语言)

[hx@hx file_operation]$ cat myfile.c
#include <stdio.h>
#include <unistd.h>

#define FILE_NAME "log.txt"

int main()
{
  FILE *fp  = fopen(FILE_NAME,"w");
  if(NULL == fp)
  {
    perror("fopen");
    return 1;
  }
  fclose(fp);
  return 0;
}

在我们#define FILE_NAME "log.txt"时,并未指明文件的路径,那么文件会创建在哪里呢? – 当前路径下,进程在执行代码,生成可执行程序时是在当前路径下的,文件也就对应的生成在该路径下(若是更改当前路径那么文件对应的位置也就发生改变)

来回顾一下文件使用方式:
" r " — 以只读的方式打开文件,文件不存在报错
" w " — 以只写的方式打开文件,文件不存在创建文件
" a " — 在文件末尾追加数据,文件不存在创建文件
" r+ " — 读写都可以,文件不存在报错
" w+ " — 读写都可以,文件不存在创建文件
" a+ " — 读写都可以,在文件末尾进行读写,文件不存在创建文件
在这里插入图片描述
在这里插入图片描述
在当前centos7下echo会默认在末尾添加\n,在其他系统下不一定(看系统配置)
在这里插入图片描述

  1. 如果我们以"w"的方式打开文件,而不写入数据,C语言会默认将其中的数据清空
  2. 文件创建出来,其权限默认是0666 真实的权限 (0666&~umask = 0664)
    在这里插入图片描述
    我们在文件中将umask设置成为0,此时生成的文件就是666,那为啥shell在执行umask命令时还是0002呢?
[hx@hx file_operation]$ umask
0002

– 因为是子进程在执行创建文件的命令,子进程umask被置为0,但并不影响父进程(shell)

4. 文件操作(操作系统)

4.1 open

在这里插入图片描述
一个int有多个比特位,操作系统采取比特位传递选项,下面来演示一下:
一个比特位对应一个选项 比特位的位置不能重复
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.2 write

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.3 read

在这里插入图片描述

4.4 总结

在这里插入图片描述

5. 深层次理解文件(文件描述符的引出)

之前提到文件操作的本质是进程和被打开文件之间的关系

  1. 进程可以打开多个文件嘛?
    当然可以,在编写程序时可以调用多个open函数,那么操作系统中肯定存在大量被打开的文件
  2. 这些被打开的文件是不是需要被操作系统中管理起来呢?
    肯定是需要管理的(操作系统管理各种软硬件资源)
  3. 操作系统如何管理这些文件呢?
    在之前的学习过程中,我们知道管理的本质就是先描述再组织
    操作系统为了管理对应的打开文件,必定会为文件创建对应的内核数据结构表示文件
    struct file{} 这其中包含着文件的大部分属性
    这里的file和C语言当中的FILE不存在联系

每一个被打开的文件都有一个struct file ,struct file可以通过链式结构链接起来,只要找到起始地址,操作系统对打开文件的管理就变成对链表的增删查改

在这里插入图片描述
open函数的返回值是整数,-1表示文件打开失败,那为啥是从3开始,0,1,2这三个整数去哪了?
在这里插入图片描述
现在我们就知道C语言的库函数封装了系统调用接口,C语言的FILE类型的指针也封装了操作系统的文件描述符
(种种迹象表明库函数就是系统调用接口的封装)

我们理解了为啥是从数字开始的,那为啥是0,1,2,3…连续的数组下标呢?
在这里插入图片描述
当我们再打开一个文件时,数组从上往下查找没有被占用的描述符,就被分配给刚创建好的struct file对象
将struct file对象的地址填入对应的3号描述符当中,此时3号描述符就执行新打开的文件了
然后我们再将3号描述符通过系统调用给用户返回,用户就得到了数字3

所以,进程在访问文件时,需要传入3号描述符,进程根据传入的值找到进程描述符表对应的地址 文件找到即可对文件进行操作

操作系统是通过数组(文件描述符表)将进程和被打开的文件关联起来
总结:文件描述符的本质就是数组的下标
在这里插入图片描述
上图是说明进程与文件的关系

6. 文件描述符的分配规则

在这里插入图片描述
通过上图的演示,我们可以初步得到以下结论:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7. 重定向

在这里插入图片描述
重定向的本质是:上层的fd不变,在内核中更改fd对应的struct file* 的地址,也就是说不是描述符之间的改变,而是改变描述符所对应的内容
上述这种输出重定向的方式是采取关闭stdout标准输出的方式,让1号描述符重新指向fd,如果我们同时将0,1,2都关闭是无法实现输出重定向的,也就是说这种方式是不满足需求的,操作系统提供了专门的重定向接口dup2()

7.1 dup2

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.2 myshell当中实现各种重定向

这里可以结合之前自己模拟实现的myshell来操作
在这里插入图片描述

7.2.1 将重定向类型置为NULL

   12 #define NONE_REDIR   0
   13 #define INPUT_REDIR  1
   14 #define OUTPUT_REDIR 2
   15 #define APPEND_REDIR 3
   16 
   17 int RedirType = NONE_REDIR;
   18 char *redirFile = NULL;

将输出/输入/追加重定向先定义出来,一开始将重定向类型置为0,重定向文件为无 后面对重定向类型进行判断

7.2.2 重定向类型判断框架的构建

大致思路就是获取命令行,判断命令行当中是否存在重定向符号

   25 void commandCheck(char* commands)
   26 {                      
   27   assert(commands);                                                                                                                                                                                        
   28   char *start = commands;
   29   char *end = commands+strlen(commands);
   30 
   31   while(start<end)
   32   {
   33     // 追加/输出
   34     if(*start == '>')
   35     {
   36 
   37     }
   38     // 输入
   39     else if(*start == '<') 
   40     {
   41 
   42     }
   43     else 
   44     {
   45       start++;
   46     }
   47   }                                                                                                                                                    
   48 }                                                                                                                                                      

7.2.3 输入重定向的编写

   38     // 输入                                                                                                                                                        
   39     else if(*start == '<')                                                                                                                                         
   40     {                                                                                                                                                              
   41       // " cat <      file.txt"                                                                                                                                    
   42       *start = '\0';                                                                                                                                               
   43       start++;                                                                                                                                                     
   44       trimSpace(start);                                                                                                                                            
   45       // 填写重定向信息                                                                                                                                            
   46       RedirType = INPUT_REDIR;                                                                                                                                     
   47       redirFile = start;
   48       break;
   49     }

如果当前start为<,说明是输入重定向,将当前start置为\0(将命令行分割成两字符串),将重定向信息改为输出重定向,文件名就是start所指向的位置
但是此时又面临一个问题,如何获取文件名的首字符,需要将文件名之前的空格都过滤掉

7.2.4 过滤空格(重点)

又因为不仅仅是在输入重定向这一种情况需要过滤空格,而且在追加和输出重定向中也需要过滤空格,所以我们可以将其以宏函数的形式编写

   17 #define trimSpace(start)  do{\
   18                           while(isspace(*start))\
   19                               ++start;\
   20                           }while(0)

这里的代码看上去很复杂,一步步分析
首先,'\'表示的是续航符

为啥#define定义需要续航符?
因为宏替换是发生在程序的预处理阶段,进行直接替换,取到的是#define后面一行的内容,如果不存在\那么在替换时,不会将上下两行内容合并,所以需要加上续航符将函数当做一个整体进行宏替换

其次用到isspace判断空格++start找到文件名的开始位置,来获取文件名

7.2.5 输出重定向/追加重定向

先判断start是不是>
如果是再判断后一位(start++) 若
start等于>,则是追加重定向,若*start不等于>,则是输出重定向

   42     // 追加/输出
   43     if(*start == '>')
   44     {
   45       *start = '\0';
   46       start++;
   47       if(*start == '>')
   48       {
   49         // "ls -a >> file.txt"
   50         RedirType = APPEND_REDIR;
   51         start++;
   52       }
   53       else 
   54       {
   55         // "ls -a > file.txt"                                                                                                                                                                              
   56         redirType = OUTPUT_REDIR;     
   57       }                               
   58       trimSpace(start);               
   59       redirFile = start;     
   60       break;                                                                                                                                                       
   61     }       

判断完重定向类型后需要将对应的重定向类型修改,同样的需要把空格过滤找到文件名对应位置

8. 进程与重定向文件之间的关系

上面完成了对重定向类型的判断,那么到底如何执行重定向是需要进程来完成的
因为父进程是用来接收子进程退出信息,命令是由子进程进行完成的(shell的原则:王婆派遣实习生的案例)
所以真正的重定向工作是由子进程来完成的

8.1 子进程接收重定向类型基本框架

  144     // 子进程                                                                                                                                  
  145     if(id == 0)                                                                                                                                
  146     {                                                                                                                                          
  147       switch(RedirType)                                                                                                                        
  148       {                                                                                                                                        
  149         case NONE_REDIR:                                                                                                                       
  150           break;                                                                                                                               
  151         case INPUT_REDIR:                                                                                                                      
  152           break;                                                                                                                               
  153         case OUTPUT_REDIR:                                                                                                                     
  154           break;                                                                                                                               
  155         case APPEND_REDIR:                                                                                                                     
  156           break;                                                                                                                               
  157         default:                                                                                                                               
  158           printf("bug?\n");                                                                                                                    
  159           break;
  160       }

8.2 输入重定向

  149         case NONE_REDIR:
  150           // 什么都不做
  151           break;
  152         case INPUT_REDIR:
  153           {
  154             int fd = open(redirFile,O_RDONLY);
  155             if(fd<0)
  156             {
  157               perror("open");
  158               exit(errno);
  159             }
  160             //重定向的文件成功打开
  161             // dup2进行重定向
  162             dup2(fd,0);
  163           }
  164           break;      

调用dup2接口进行重定向,将fd文件指向0(标准输入/stdin)

8.3 输出/追加重定向

  165         case OUTPUT_REDIR:                                                                                                                     
  166         case APPEND_REDIR:                                                                                                                     
  167           {                                                                                                                                    
  168             //使得创建的文件按照自己的权限创建                                                                                                 
  169             umask(0);                                                                                                                          
  170             int flags = O_WRONLY | O_CREAT;                                                                                                    
  171             if(RedirType == APPEND_REDIR) flags |= O_APPEND;
  172             else flags |= O_TRUNC;
  173                 
  174             int fd = open(redirFile,flags,0666);
  175             if(fd < 0)    
  176             {
  177               perror("open");
  178               exit(errno);
  179             }
  180             dup2(fd,1);
  181           }
  182           break;
  183         default:
  184           printf("bug?\n");
  185           break;
  186       }       

追加重定向和输出重定向的差别就是打开文件的方式不一样,一个是以"a"的方式,一个是以"w"的方式
所以RedirType == APPEND_REDIR flags就或等上O_APPEND 否则就或等上O_TRUNC

8.4 理解重定向和父进程之间的关系

既然重定向文件的工作是由子进程来完成的,那么是不是就跟父进程无关呢?
当然不是,子进程是通过父进程创建(fork)出来的,子进程需要获取父进程对应的代码和数据,所以子进程也就从父进程当中获取到如何重定向文件的信息(到底是需要进行哪种重定向)
总而言之,父进程就是给子进程提供重定向信息的
子进程进行重定向会影响父进程嘛?
在这里插入图片描述
执行程序替换时,是否会影响到曾经进程打开的重定向文件呢?
不会,因为以上的结构(fork创建子进程)都是属于内核数据结构当中的模块,而程序替换是将磁盘上新的代码和数据替换到内存当中,完全是两个维度,最直观的表现就是程序替换不会影响内核数据结构当中的PCB(task_struct)

9. shell结果演示

在这里插入图片描述
在这里插入图片描述

10. 如何理解Linux下一切皆文件

在这里插入图片描述这层struct file是操作系统虚拟出来的文件对象(在Linux当中称之为vfs – 虚拟文件系统),摒弃掉底层设备的差别,统一使用文件接口的方式来进行文件操作
struct file当中包含方法指针,会进行初始化指向对应的方法,还会指向内核中的缓冲区。

拓展一下:文件的引用计数

假设我们在堆上申请了一块空间,有指针指向这块堆空间,当我们不需要再使用时就释放空间
在使用过程中,可能有多个指针指向这块堆空间,于是我们在区间加上count 用来计数(记录当前有多少指针指向我)
这个被称之为引用计数
当父进程fork创建子进程时,会将PCB和文件描述符表都给子进程创建一份,但是文件并未新建一份,所以此时子进程文件描述符表中的数据还是指向父进程所打开的文件,那么在进程在关闭文件时,是否就直接将文件关闭了?
不是,只是将文件所对应的引用计数–罢了,当文件的引用计数为0时,操作系统才会释放该打开文件
所以我们close文件时,只是告知操作系统我们不再使用该文件了·

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

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

相关文章

软件测试人到30岁+,还有出路吗???

最近一个学生也可以说是朋友&#xff0c;他遇到了一个让他困扰的职场难题&#xff0c;背景如下&#xff1a; 1&#xff09;他们公司准备搞安全测试了&#xff0c;现在有人员培训的计划&#xff0c;所以全组有学习安全测试课程的安排。 2&#xff09;他自己目前专职性能测试1年了…

基于SSM的物流仓库管理系统

摘 要 随着社会的发展&#xff0c;社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是采用java语言技术和mysql数据库来完成对系统的设计。整个…

“专精特新”发展概况

专精特新概念自2011年开始萌芽&#xff0c;2021年上升为国家战略&#xff0c;2022年写进二十大报告&#xff0c;顶层设计持续推进&#xff0c;旨在聚焦产业链关键环节&#xff0c;加强技术创新&#xff0c;破解核心技术“卡脖子”问题。全国各地政府从企业调研、整体设计、培育…

AI崛起:哪些职位将首当其冲?

随着人工智能的迅猛发展&#xff0c;人们普遍担心自己的工作是否会被AI取代。虽然是否能毁灭人类尚难以确定&#xff0c;但可以肯定的是&#xff0c;AI绝对会抢走某些职位。以下是十个职业&#xff0c;它们很可能是被AI取代的首批。 制造业&#xff1a;随着自动化机器人的进步…

docker-compose 搭建 zipkin 服务端

目录 基于docker-compose搭建服务端 数据库 服务器 docker-compose.yaml 问题 测试 基于docker-compose搭建服务端 数据库 我这边存储选择了Mysql存储&#xff0c;新建了 zipkin库&#xff0c;数据库脚本如下 -- -- Copyright 2015-2019 The OpenZipkin Authors -- -- Li…

Java单元测试学习(二)

Java单元测试学习&#xff08;二&#xff09; 使用测试框架JUnitMockito和单元测试覆盖率框架JaCoCo 目录结构 依赖—很好&#xff0c;这里又有个小插曲 打开页面查看覆盖率时一直显示0/0---->最后的解决方式是①添加了maven-surefire-plugin插件 <?xml version&quo…

C++中流的分类

前言 关于流本质的问题&#xff0c;其实从我刚开始学习C的时候&#xff0c;就已经存在了。当时找了不少的资料&#xff0c;不过一直处于那种知其然而不知其所以然的状态&#xff0c;关于流的本质问题我还是一直没有搞通&#xff0c;始终就是懵懵懂懂的。 不过在今天&#xff0…

智能电能表采集失败的原因和解决方法

智能电能表采集失败的原因和解决方法 智能电能表作为现代电力系统中的重要组成部分&#xff0c;在电能计量、电费结算等方面发挥着关键作用。然而&#xff0c;在实际应用过程中&#xff0c;有时会出现电能表采集失败的情况&#xff0c;这可能源于网络连接故障、数据传输错误等…

数据结构基础-堆

堆实现 计算机科学中&#xff0c;堆是一种基于树的数据结构&#xff0c;通常用完全二叉树实现。堆的特性如下 在大顶堆中&#xff0c;任意节点 C 与它的父节点 P 符合 P.value \geq C.value而小顶堆中&#xff0c;任意节点 C 与它的父节点 P 符合 P.value \leq C.value最顶层…

维护嵌入式 Linux 内核——So Easy

导读Pengutronix 内核黑客 Jan Lbbe 总结了嵌入式 Linux 中正在不断增长的安全威胁&#xff0c;并在这次欧洲嵌入式 Linux 会议上概述了一个计划&#xff0c;以保持长期设备的安全和功能完整。 安全漏洞只发生在 Windows 上的好日子正在快速过去。恶意软件黑客和拒绝服务老手们…

【InsCode AI 创作助手】关于编程人员的未来发展趋势,看看AI们怎么说

一、你平时会使用这类AI工具吗&#xff1f;你对这类型的工具有什么看法&#xff1f; 1&#xff09;会经常使用AI工具吗&#xff1f; 是的&#xff0c;我在生活和工作中经常会使用AI工具&#xff0c;尤其是chatGPT&#xff08;3.5&#xff09;和文心一言&#xff0c;关于midjour…

【微信小程序开发】第 5 节 - 小程序代码的构成

欢迎来到博主 Apeiron 的博客&#xff0c;祝您旅程愉快 &#xff01; 时止则止&#xff0c;时行则行。动静不失其时&#xff0c;其道光明。 目录 1、缘起 2、了解项目的基本组成结构 3、小程序页面的组成部分 4、JSON 配置文件 4.1、json 配置文件的作用 4.2、app.json 文…

http首部(下)

开始头大&#xff0c;哈哈&#xff0c;这个东西真的很无聊且枯燥&#xff0c;奈何最近的学习中经常用到这些知识&#xff0c;还是过一遍比较放心。上一篇博客中我们讨论了http报文首部&#xff0c;其划分为请求头和响应头。请求头主要由请求行、请求字段、通用字段、实体字段组…

ChatGPT请不要和打工人争辩今天星期几

目录 1 今天星期几2 聊聊ChatGPT与工具的结合 1 今天星期几 周五了&#xff0c;一个星期快结束了&#xff0c;闲来问问chatgpt (gpt-3.5) 今天 ( 2023.06.03星期五&#xff09;星期几&#x1f601;&#xff0c;chatgpt给出的回答如下&#xff1a; 今天是2023年6月2号没错&…

Linux系统下安装配置 Nginx 详细教程介绍

Linux系统下安装配置 Nginx 详细教程介绍 一、下载 Nginx 安装包 打开Nginx官网 &#xff1a;nginx: download 然后我们找到一个版本&#xff0c;把鼠标移动到上面&#xff0c;右键 - 复制链接地址 我们使用 wget 命令把Nginx安装包下载到/usr/local/目录中 安装wget yum…

基于html+css的图展示110

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

sid,eld,sidd dataset介绍,dng图像处理

文章目录 SID dataset1. SID dataset 概述2. SID 读取和显示代码3. 一些示例 SIDD datasetELD datasetDNG camera pipeline SID dataset 1. SID dataset 概述 SID 是Learning to See in the Dark 论文中提出的暗光raw数据集 其中包括两个相机的拍摄数据 Sony alpha7S II 和 …

实战演练 | 在 Navicat 16 中创建视图

为了规范化数据库表&#xff0c;常常会将高级别表中的冗余列抽取到单独的子表中。这通常是由于某些字段与父实体具有一对多关系而发生的。例如&#xff0c;请参考以下使用 Navicat Data Modeler 生成的模型&#xff1a; 评估最初是 ups 表的一部分&#xff0c;但这导致了数据冗…

没素材也可以剪辑,根据画面描述搜索影片素材!

随着社交媒体的普及&#xff0c;越来越多的人开始喜欢用短视频分享自己的生活。但是&#xff0c;在剪辑过程中&#xff0c;素材的质量和多样性是很重要的。如果你缺乏素材&#xff0c;可以考虑根据画面描述去搜索一些适合的影片素材。 首先&#xff0c;你需要确定自己需要什么…

自学黑客(网络安全),学习集锦奉上!

想学网络安全&#xff0c;不知道学习方向&#xff1f;我整理了一份渗透测试学习方法&#xff0c;话不多说&#xff0c;上干货。 web安全知识学习&#xff08;理论期&#xff09; 学习web安全基础知识、html语言、python、java、数据库等等。另外端口也可以学习一下3306、3389…