Linux文件操作

news2024/9/23 23:34:24

文章目录

    • 前言
    • 什么是文件
    • 文件操作
      • C语言文件操作接口
      • 文件操作系统调用接口
    • 文件描述符
      • 如何理解FILE和文件描述符的关系

前言

本文主要介绍的是Linux系统下的文件操作和Linux的文件系统的机制,希望能够通过本文来增加读者对于文件操作的基本使用和理解。以及了解Linux系统对文件管理的一套独有的优秀的机制

什么是文件

接下来,我们就要来进入我们的主题。在介绍相关操作和Linux文件 系统之前,我们要先弄明白什么是文件 或许我们对于文件既陌生又熟悉。熟悉是因为我们几乎每天都在读文件和写文件,陌生是因为我们没法能够准确描述出这个文件这个概念。 接下来我们就来理解理解什么是文件。

文件=文件内容+文件属性!
在Linux系统下,即使创建一个空内容的文件!这个文件也是要占据磁盘空间的!因为操作系统需要记录这个文件的属性!比如大小,创建时间,问价类型等等属性,而这些东西同样是数据,既然是数据,需要存储就要存储空间!

文件操作

既然文件=文件内容+文件属性。那么毫无疑问地,对文件的操作=对文件内容得操作+对文件属性的操作! 最典型的就是,假如我往一个文件里面写入东西,改变的就是文件的内容。同时由于内容增多,对应的文件的体积变大,也就是文件的大小也变大,所以此时文件的属性也发生了改变! 既然谈到了文件操作,许多高级语言也都为我们提供了一系列的和文件相关操作的接口函数,下面我们以C语言为例,来使用一下C语言的文件接口。

C语言文件操作接口

我们来回顾一下C语言的相关的文件接口:C语言有很多文件的读写操作,这里我们只用fwrite进行演示

FILE* fopen(const char* name,const char* model);
打开文件的接口,如果成功返回对应的文件指针,失败则返回NULL,model参数有如下的几种:
“r” ---->只读方式 ,“w”---->写入方式
r+ /w±–>读写方式
fclose(FILE* pf);---->关闭文件
fwirte---->向指定文件写入

下面我们就来简单得使用一下这几个接口

#include<stdio.h>
#include<string.h>
/*
 * 使用C语言相关的文件接口
 * fopen()--->打开文件
 * fclose()---->关闭文件
 * fwrie()---->写 fprintf--->格式话写 
 * fread()--->读 fscanf---->格式化读取
 *
 * */ 
int main()
{ 
  
  FILE* pf=fopen("test.txt","w+");
  //打开失败
  if(!pf)
  {
    perror("打开失败了,不玩了\n");
    return 1;
  }
  //打开成功,使用fwrite写入I like Linux!
  const char* msg="I like Linux!\n";
  fwrite(msg,sizeof(char),strlen(msg),pf);
  return 0;
}

程序运行完以后,我们确实看到了在当前的目录下多了一个test.txt的文件,接下来我们打开对应的文件查看是否是否把对应得内容写入到了对应得文件里面。
在这里插入图片描述
在这里插入图片描述
可以看到,对应内容确实已经被写入到文件里面了。接下来我们在来多在原来的基础上再写入一条信息到文件里。

#include<stdio.h>
#include<string.h>
int main()
{

 const char* msg="I love Linux";
 FILE* pf=fopen("test.txt","w");
 if(!pf)
 {
   perror("哥们打开文件失败了,跑路把\n");
   return 1;
 }
 //写入文件
 fwrite(msg,sizeof(char),strlen(msg),pf);

 fclose(pf);

  return 0;
}

程序一运行,打开对应的文件查看内容:
在这里插入图片描述
完蛋!我之前写入的内容咋就不见了呢?问题就出现在这个w模式上!w模式打开文件进行写入的时候,都会将原来的文件的内容清空!而如果我们要追加式写入,那么我们就要使用“a”选项!具体的相关细节操作和w模式一模一样,读者可以自行下来尝试使用。

文件操作系统调用接口

但是,这里有一个问题。我们知道文件是存放在磁盘的。磁盘是何物?磁盘是一个没有感情,冷冰冰的硬件! 换言之,我们对文件进行写入操作,本质就是对磁盘进行了写入操作!就是对硬件进行了操作! 但问题是,你只是一个编程语言,你哪里来的权力能够操作硬件?归根结底,写入磁盘的工作必须是操作系统完成的!毕竟操作系统是硬件的管理者。 但是操作系统这货又不相信用户,不直接暴露自己给用户,所以对应的操作系统必然会提供一系列的系统调用接口来给我们提供文件操作! 那么接下来来在深入理解这些系统接口之前,我们先来看一看这些接口。

首先,系统接口的命名和前面讲的fopen,fwrite,fread,fclose非常接近。 系统接口的名字就是把这些C语言接口的名字去掉一个f就可以了。

先来看第一个函数:open() ->顾名思义,就是打开一个文件,具体的函数声明如下:

int open(const char pathname, int flags);
int open(const char pathname, int flags, mode_t mode)

第一个参数是对应你要打开的文件名,第二个参数则是你要以什么模式打开。而文件打开又有如下的选项参数:

O_RONLY:只读模式打开
O_WRONLY:只写方式打开
O_RDONLY;读写方式打开
还有对应的一些附加选项;
O_CREAT:文件不存在就创建
O_TRUNC:每次打开时清空文件内容
O_APPEND:不清空文件,往文件末尾继续追加内容

介绍完了打开,我们在介绍一下往文件写入的接口函数

ssize_t write(int fd, const void *buf, size_t count);
第一个参数是open的返回值(后面我们会详谈这个fd具体是什么)
第二个参数就是指向我们要写入文件的内容
第三个参数就是我们要写多少字节的内容
write函数返回我们实际写入文件的内容的字节大小,出错就返回-1

//使用系统调用对文件进行操作
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{ 
//屏蔽系统默认掩码的影响
  umask(0);
  //如果不存在要创建,就要用三个参数版本的
  int fd=open("test.txt",O_WRONLY | O_CREAT,0666);
  //中间进行读写操作,这里先以写入操作为例
  const char* msg="I like Linux!\n";
  //不需要带上'\0',文件不认识这个东西
  write(fd,msg,strlen(msg));
  close(fd);
  return 0;
}

在这里插入图片描述
可以看到,这里确实生成了一个文件,并且这个文件被写入了I like Linux! 这就是write系统调用,而相对应的读取就使用的是read操作。

//使用系统调用对文件进行操作
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
   //之前文件已经创建好了,接下来读取文件内容
   char message[128];
   memset(message,'\0',sizeof(message));
   int fd=open("test.txt",O_RDONLY);
   if(fd<0)
   {
     perror("打开失败了,直接溜吧\n");
     return 1;
   }
   //当作字符串处理
  ssize_t s=read(fd,message,sizeof(message)-1);
  if(s<0)
  {
    perror("兄弟读取失误了啊\n");
    return 2;
  }
  printf("读取到的内容是%s",message);
  return 0;
}

在这里插入图片描述
这就是Linux系统里面文件的相关读写操作。接下来,我们对这些接口中一些参数进行解读,并结合一些内核的数据结构来理解这些操作的含义。

文件描述符

首先,在对于文件读写操作之前,我们做的第一件事情就是打开文件。对应的系统调用就是open,而这个open的第一个参数就是打开的文件名,而耐人寻味的就是这个文件的返回值。我们先来看看手册里面对于这个返回值的解读

open 和 creat 都 返回 一个 新的 文件描述符 (若是 有 错误 发生 返回 -1 ,并在 errno 设置 错误 信息).

这里提到了一个叫做文件描述符的东西。接下来我们就来深入探究一下这个文件描述符究竟是何物,但在谈及文件描述符之前,我们必须要理解Linux操作系统的一个设计的理念---->Linux下一切皆文件!
那对应诸如键盘,显示器,网卡等等这些硬件,Linux也是把它们看成一个个文件吗?---->是的!Linux依旧把这些设备看成文件!那么这是怎么做到的呢?
不知道读者是否还记得一个东西叫做进程地址空间,我们知道内存是无法保护自己不被非法访问。而进程地址空间不仅提供给了进程统一的视角看待内存,更是给物理内存提供了一层访问保护(软件层的保护),同样,类似于进程地址空间,只要我能够提供一套特定的软件层,负责做这些硬件相关的读写方法,这样从内核的角度,我就可以使用统一的视角来看待这些硬件设备! 而实际上,Linux内核也是这么去完成。内核中的文件的结构被称为struct file,对应的一部分的源代码如下

//文件的内核数据结构,struct file
struct file {
	struct list_head	f_list;
	struct dentry		*f_dentry;
	struct vfsmount         *f_vfsmnt;
	struct file_operations	*f_op;
	//....
}
//而这个file_opreations的定义如下
struct file_operations {
	ssize_t (*read) (struct file *, char *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
	//...	
};

那么对应的,在Linux内核里面,对于硬件的处理方式可以用如下的方式解决
在这里插入图片描述
到这里,我们就可以得到一个重要的结论:绝大多数硬件问题,都可以通过添加一层软件驱动层的方式,使得上层能够以统一的视角看待下层,屏蔽底层硬件的差异!
理解了Linux能够把一切东西抽象成文件以后,我们再来进一步认识文件。我们必须明确一个概念,我们当前所有的文件相关操作都是内存级别的!也就是说打开文件都是由进程打开的。一个进程可以打开多个文件,那么就意味着这个进程必须要把对应的打开的文件给维护起来,那么在内核数据结构层面如何体现这种维护关系呢?

struct task_struct{
//...其他属性
struct files_struct *files;
//..其他属性
};
//接下来我们跳到task_struct的对应定义
struct files_struct {
        atomic_t count;
        spinlock_t file_lock;     /* Protects all the below members.  Nests inside tsk->alloc_lock */
        //...
        //这个才是最重要的结构
        struct file * fd_array[NR_OPEN_DEFAULT];
};

到这里,我们可以得到一个结论了:这个文件描述符fd—>其实就是用来维护一个进程和对应打开的文件的数量的关系。更准确的讲,这个fd其实就是一个文件指针数组的下标!
那么对应一个进程和它打开文件的对应关系就是下面这个样子

在这里插入图片描述

当我们调用open函数打开文件的时候,内核创建了对应的数据结构,对应的在进程的file_struct找到一个尚未被分配的fd也就是对应的数组下标 建立对应的映射维护关系,并且把这个下标返回给用户。

那么我们来看一看这个系统给我们创建文件以后,给我们分配的文件fd是多少

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
  //打开文件test1.txt文件,查看文件
  int fd1=open("test1.txt",O_WRONLY | O_CREAT,0666);
  int fd2=open("test2.txt",O_WRONLY | O_CREAT,0666);
  printf("test1文件的fd是%d\n",fd1);
  printf("test2文件的fd是%d\n",fd2);
  return 0;
}

运行结果如下:
在这里插入图片描述
奇怪,为什么打开的文件是从三开始的?0,1,2去了哪里了呢?难道,已经被别人给占了吗? 是的!0,1,2这三个确实是已经被占。而这三个就是我们C语言阶段接触过的三个文件流.

stdin:标准输入
stdout:标准输出
stderr:标准错误

但是这里有一个很大的问题:这三个玩意是C语言提供的,类型是FILE*,但是为什么它会把文件fd这样一个下标给占据了呢?

如何理解FILE和文件描述符的关系

结合我们前面的理解,我们知道,C语言内部所有的文件操作函数。最终都会被转化成朴实无华的操作系统对于文件的一系列的操作。那么对应的,因为操作系统只认识fd,所以对应的FILE结构体必然是封装了对应的fd文件描述,我们可以简单瞅一瞅对应的FILE类型的源代码

struct _IO_FILE {
 //...很多属性
 //_fileno--->就是对应封装的文件fd
  int _fileno;

为了进一步验证我们的观点,我们可以尝试把C语言给我提供的三个文件流的_fileno打印出来看一看是不是0,1,2

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
 printf("stdin->fd=%d\n",stdin->_fileno);
 printf("stdout->fd=%d\n",stdout->_fileno);
 printf("stderr->fd=%d\n",stderr->_fileno);
  return 0;
}

在这里插入图片描述
如果这个现象还不够明显,我们不妨试一试用把stdout的file_no作为参数传递给write函数,看一看效果是什么样的。

//使用系统调用对文件进行操作
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{ 
 const char* msg="hello world\n";
  write(stdout->_fileno,msg,strlen(msg));
  return 0;
}

运行结果如下:
在这里插入图片描述
结果应证了我们先前的结论。也就是FILE封装了fd,而stdin,stdout,stderr对应封装的fd是0,1,2。
以上就是本篇文章的主要内容,如果有不足的地方,希望可以指出。

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

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

相关文章

【大数据入门核心技术-Ambari】(一)Ambari介绍

一、什么是Ambari Apache Ambari是一种基于Web的工具&#xff0c;支持Apache Hadoop集群的供应、管理和监控。Ambari已支持大多数Hadoop组件&#xff0c;包括HDFS、MapReduce、Hive、Pig、 Hbase、Zookeeper、Sqoop和Hcatalog等。 Apache Ambari 支持HDFS、MapReduce、Hive、Pi…

C语言宏和预处理

C语言宏和预处理【1】include【2】【#pragma】【3】C #include【4】# 和 ## 操作符【5】宏替换【6】#line【7】#error【8】#undef【9】源码【10】结果【1】include #include本命令包含一个文件并在当前位置插入.一般尖括号意味着在标准库目录中搜索, #include “filename” 引…

【C语言与汇编】简单学学C到汇编代码

C语言与汇编 部分过程可参考C primer plus 本书余下的篇幅讨论源代码文件中的内容&#xff1b;本节讨论创建源代码文 件的技巧。有些C实现&#xff08;如Microsoft Visual C、Embarcadero C Builder、Apple Xcode、Open Watcom C、Digital Mars C和Freescale CodeWarrior&…

精品基于SSM的民宿预订管理系统

《民宿预订管理系统》该项目含有源码、论文等资料、配套开发软件、软件安装教程、项目发布教程等 使用技术&#xff1a; 开发语言&#xff1a;Java 框架&#xff1a;ssm 技术&#xff1a;JSP JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;my…

云原生事件驱动框架(KnativeEventing)

事件驱动是指事件在持续事务管理过程中&#xff0c;进行决策的一种策略。可以通过调动可用资源执行相关任务&#xff0c;从而解决不断出现的问题。通俗地说是当用户触发使用行为时对用户行为的响应。在 Serverless 场景下&#xff0c;事件驱动完美符合其设计初衷之一&#xff1…

基于xv6的Copy-On-Write

为什么需要写时拷贝呢&#xff1f; 当 shell执行指令的时候会 fork()&#xff0c;而这个 fork()出来的进程首先会调用的就是 exec来执行对应的命令&#xff0c;如果我们将 fork()创建的进程对地址空间进行了完整的拷贝,那将是一个巨大的消耗 因为在实际应用中&#xff0c;for…

SSM项目整合

文章目录1. 导入依赖2. 配置web.xml文件3. 编写springmvc.xml配置文件4. 编写spring.xml配置文件5. 编写项目整体业务逻辑6. 开发过程中常见状态码错误1. 导入依赖 在整合SSM项目时&#xff0c;需要导入以下依赖 Spring 相关依赖 &#xff08;springmvc相关、事务相关、spring…

凤姐从国外回来会再次爆红吗?她最近的一段话发人深省

随着互联网的发展&#xff0c;也诞生了很多的网红&#xff0c;其实现如今的众多网红&#xff0c;和凤姐比起来都是小巫见大巫。凤姐原名叫罗玉凤&#xff0c;当年因为参加各种选秀节目&#xff0c;再加上鬼使神差语无伦次的言谈&#xff0c;让她一夜之间爆红了网络。 其实当我们…

Pandas统计计数value_counts()的使用

value_counts()方法返回一个序列Series&#xff0c;该序列包含每个值的数量(对于数据框中的任何列&#xff0c;value_counts()方法会返回该列每个项的计数) value_counts()是Series拥有的方法&#xff0c;一般在DataFrame中使用时&#xff0c;需要指定对哪一列进行使用 语法 …

微服务框架 SpringCloud微服务架构 29 ES 集群 29.5 故障转移

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构29 ES 集群29.5 故障转移29.5.1 ES集群的故障转移29.5.2 总结29 ES 集群 …

【图像分割】粒子群优化T熵图像分割【含Matlab源码 286期】

⛄一、简介 本文所采用的基于熵的切割点和最小描述长度原则(MDLP)。 A.特征选择 特征选择是一个组合优化问题&#xff0c;因为在具有N个特征的数据集上有2N个可能的不同特征子集。FS方法通常有两个重要的部分组成&#xff0c;即搜索技术和特征评估方法。 在特征评估方面&am…

Python项目之文化和旅游数据可视化

文章目录关键词一、做什么二、怎么做1.爬取数据和处理2.数据库设计&#xff0c;并将数据写入数据库3.开发后端接口4.使用Echarts官方模板三、效果展示关键词 Python后端开发Python网络爬虫Echarts可视化面向对象(见源码) 一、做什么 国家5A级旅游景区数据可视化 国家级旅游休…

状态设计模式

一、状态模式 1、定义 状态模式&#xff08;State Pattern&#xff09;又称作状态机模式&#xff08;State Machine Pattern&#xff09;&#xff0c;允许对象在内部状态发生改变时改变它的行为&#xff0c;对象看起来好像修改了它的类。属于行为型设计模式。 状态模式的核心是…

Python:面向对象

目录 一、类的定义 基本语法 python与Java关于类的封装的区别 自定义构造方法 Java与Python的区别 源码 同一个类多种构造函数的构建 源码 二、魔法方法 基础部分 比较部分 与Java的区别 容器类型 三、属性管理 四、封装 基础部分 方法拓展 五、继承&多态 继…

基础IO——系统调用文件

文章目录1. 知识补充和回顾1.1 回顾C文件接口1.2 理论理解2. 系统调用文件接口2.1 open2.2 怎么使用2.3 close和write2.4 read1. 知识补充和回顾 1. 文件文件内容文件属性。即使创建一个空文件&#xff0c;也会占据磁盘数据。 2. 文件操作文件内容操作文件属性操作。在操作文件…

域名+七牛云+PicGo+pypora

域名七牛云PicGopypora 前提准备&#xff1a; 域名&#xff08;自己的域名&#xff09;七牛云 免费注册申请10G空间够用picGo 地址pypora &#xff08;自行下载&#xff09; GO&#xff01;&#xff01;&#xff01; 七牛云 注册--->登录--->控制台找到对象存储新建…

如何在AndroidStudio中使用GitHub

文章目录1.确认是否安装git2.添加GitHub账户3.创建库4.创建分支5. push内容在项目中肯定要集成版本管理工具&#xff0c;不过有时候更换电脑或者升级AndroidStudio时原来集成的内容就不在了&#xff0c;还在再次集成git。时间长了就容易忘记如何集成Git&#xff0c;因此整理总结…

工作10年我面试过上百个程序员,真想对他们说…

V-xin&#xff1a;ruyuanhadeng获得600页原创精品文章汇总PDF 一、写在前面 最近收到不少读者反馈&#xff0c;说自己在应聘一些中大型互联网公司的Java工程师岗位时遇到了不少困惑。 这些同学说自己也做了精心准备&#xff0c;网上搜集了不少Java面试题&#xff0c;然而实际…

详解c++---内存管理

这里写目录标题c语言在堆上申请空间malloccallocreallocfreec中向堆中申请空间的形式new的介绍delete的介绍new与自定义类型new与malloc的不同定位newoperator new与operator delete函数c语言在堆上申请空间 在之前的学习中我们知道c语言主要是通过malloc free calloc&#xf…

Java基础-常用API的使用方法(Math,System,Runtime,Object,BigInteger,BigDecimal)(1)

1 Math类 1.1 概述 tips&#xff1a;了解内容 查看API文档&#xff0c;我们可以看到API文档中关于Math类的定义如下&#xff1a; Math类所在包为java.lang包&#xff0c;因此在使用的时候不需要进行导包。并且Math类被final修饰了&#xff0c;因此该类是不能被继承的。 Math类…