【Linux】内存级文件

目录

C语言关于文件操作的函数

Linux关于文件操作的系统调用

完善myshell

C语言缓冲区


其实我们在C语言就学过文件操作,但是从语言的角度,我们只是说会用了关于文件的一些操作和函数,但其实它究竟是怎么回事我们其实并不明白,那么当我们学习到Linux操作系统的时候,我们才能更加深入的去了解这文件究竟是怎么回事

那么我们需要首先明确一些概念:文件=内容+属性,不能说我这个文件是空,那么就不占空间;访问文件都得先打开,然后通过执行代码的方式去修改文件内容,也就是文件要被加载到内存中;是进程打开的文件并且一个进程可以打开多个文件;一个时间段内,可能有多个进程,操作系统要管理这些进程,同时也可能有多个被打开的文件,操作系统也要管理这些文件,那么如何管理呢?先描述,再组织,就是说,操作系统要给每个文件创建一个结构体对象,对文件的管理就变成了对于结构体的管理。

C语言关于文件操作的函数

下面只是介绍了一小部分,如果想详细了解,可以去我的另一篇博客:

C语言文件操作详谈

那么下面先回忆一下之前C语言用的一些函数:

首先就是fopen,并且我记得当时选项w最为奇怪,因为每一个用,那么之前的数据都会不见了,这不是跟我们的输出重定向(>)很像吗,因为它们都是每次使用之前的内容就会被清空

这段话的意思是:截断文件到长度0或者如果没有那么创建新文件

我们原来都是这么用的:

我们说如果w选项,当前路径下没有这个文件,那么就会在当前路径下去创建,那么进程怎么知道当前在那个路径下呢?我们说过进程启动时,会记录下自己的路径

所以进程就会在这个路径下创建新文件

并且我们还有一个选项a,叫做append(附加),这不是跟追加重定向(>>)很像吗

下面我们再看一下fwrite这个函数

基本的使用就是这样

我们可以确定的是,输入或输出一些东西必须要指定文件,就连键盘和显示器也不例外,因为Linux下一切皆文件,那么平时用printf/scanf的时候也没打开键盘和显示器文件并且也没有指定文件啊(我们这么想是因为把键盘和显示器看成和普通文件一样了),那是因为首先stdout,stdin和stderror是进程默认打开的,不用手动打开,可以直接使用

其次为什么printf不用指定文件呢?因为它为了方便使用,是不用指定文件的,但是底层实现是封装了fprintf

fprintf是要加stdout的,所以printf就是这么实现的

Linux关于文件操作的系统调用

我们知道,对硬件进行修改只能通过操作系统,所以操作系统就必须提供系统调用接口,像fopen这样的库函数是语言层面的概念,为了实现语言的跨平台性和可移植性,所以它要封装系统调用,并且在不同的操作系统要封装各自的系统调用。所以我们下面就介绍一下Linux操作系统关于文件操作的系统调用open和close

open的第一个参数就是文件所处的路径,第二个参数就是之前说的类似于“w“、“a”的一些选项,常见的打开标志有:

O_RDONLY:以只读方式打开文件

O_WRONLY:以只写方式打开文件

O_CREAT:没有这个文件就创建

O_TRUNC:打开文件前会清空文件

O_APPEND:在文件尾追加数据

第三个参数就是我刚创建好一个文件,此时要给文件设置的权限,返回值就是文件描述符,其实就是一个整数,通过这个整数,就可以确定这个文件,具体是怎么确定的,这就跟底层实现有关了,我们后面会介绍一下

当我们用第一个open时,并且路径中没有,它需要创建,这时我们可以看到文件的权限是乱码

所以我们一般使用第二个,权限计算还是给定的权限减去权限掩码,我们把它们当成八进制数,比如:

666-002=664

并且通过umask()函数我们还可以在当前程序中设置权限掩码并且这个权限掩码只在当前程序下生效

666-666=000

我们上面介绍了第二个参数,第二个参数要给一个整数,其实下面的一些“选项”就是一些,这些宏可以通过位运算决定整数的比特位,其实就是位图,从而达到不同的下面的选项,知道了这些宏的意义,我们就可以和之前fopen的不同选项的功能对应上了,其实“w”选项不就是这三个选项的叠加吗

其实如果只有前两个的话原始文件中的内容并不会清空,而新内容就从头开始进行覆盖,就是这样一种现象

选项“a”不就是这三个选项的叠加吗

并且追加内容时会在下一行追加

所以我们说fopen底层就是封装了open,并且不同的选项底层就对应了不同的宏,我们上面说stdin、stdout、stderr每个进程都会默认打开,并且它们的类型是FILE*,这是C语言层面的类型,本质就是一个结构体,既然Linux要通过文件描述符来确定一个文件C语言要通过FILE*的对象,所以FILE结构体中肯定有文件描述符

为了探究文件描述符到底是什么,我们可以连续创建一些文件看看它们的文件描述符之间有什么规律

文件描述符是从3开始依次增长,那0,1,2呢?其实0,1,2就分别是程序默认打开的stdin,stdout和stderr,并且这个数字其实就是数组下标,什么意思呢?

我们知道,操作系统不仅要进行进程管理,还要进行文件管理,对于进程的管理我们知道是通过PCB,就是一个结构体对象;同样,对于打开的文件(不管以什么样的方式打开),操作系统也是要创建一个结构体对象(比如我们叫做struct file)进行管理的,我们可以把它们之间的关系大致理解为这样:

就是说:一个进程的PCB中存着此进程打开的文件列表的指针,通过这个指针可以找到打开的文件列表,而这个文件列表中也是存着每个文件管理的指针,通过这个指针就可以找到操作系统对文件进行管理的结构体了

这就是为什么我们可以通过文件描述符(一个整数),来确定唯一一个文件了

并且要注意,我们图中的缓冲区是操作系统给每个打开的文件提供的,和下面说的C语言库函数提供的缓冲区不是一个概念

既然stdout等是自动打开的,我们可以关闭stdout试一试,还不能关这个,因为一旦关了就打印不出东西来了,我们可以关掉stdin,于是新打开的文件的文件描述符就会从最小的没用到的0开始

这个_fileno就是FILE*结构体中文件描述符变量的名字

所以我们就可以利用上面的规则实现重定向,比如printf默认向stdout中打印,其实它认识的是文件描述符为1,所以我们就可以这样,将想打印的内容打印到一个文件中而不是显示器

这样也确实比较麻烦,并且还要了解文件描述符的分配规则,其实也不知可以这么做,系统还提供了系统调用来供我们使用,这个就是通过拷贝指针来实现目的,就是上面图中中间那个图里边的指针

于是我们就可以这么用:

就是让数组下标为1的里边的内容变成数组下标为fd的里边的内容,这样printf打印肯定还是向数组下标为1的里边的指针指向的文件打印,这时文件就变成log.txt

完善myshell

既然已经明白了重定向的原理:就是通过文件描述符实现向任意一个文件中写入,本质上就是改变文件指针数组中的指针就可以实现向指定的文件中写入,于是我们就可以完善一下我们之前写的myshell

可以移步到下面这篇博客中:自定义bash进程

C语言缓冲区

我们这里说的缓冲区其实就是C语言库中提供的一个缓冲区,这么说可能有点抽象,其实就是在我们调用文件相关函数的时候,需要用到FILE这个类型,这个类型其实就是一个结构体,里边就存着缓冲区。

也就是说:不管是log.txt这样的普通文件的文件指针,又或者是stdout这样的进程自动打开的文件指针,它们都是一个结构体对象,这个对象中就存着缓冲区,那么这样好处是什么呢?

1.首先我们要知道,调用系统调用是有成本的,所以我们需要尽可能的少的调用,那么我先暂时把数据写到C语言的缓冲区中,最后统一的调用系统调用把数据给操作系统,这样系统调用的次数就减少了

2.其次在写代码的人看来,执行完这句代码,数据就好像已经写入到了文件中,但实际上是写入到了缓冲区中,这种在内存中的拷贝可比把数据写入到磁盘当中快多了,这样表现出来的就是C语言的速度很快,可以提高写代码的人的体验

我刚才说缓冲区就在FILE的结构体对象中,那么它跟我们之前说的进程地址空间是什么关系呢?

其实你得看FILE对象在哪,FILE对象可以在调用fopen函数时在函数内部去堆上申请,所以缓冲区就在堆上,我们下边也会模拟实现一下缓冲区的工作原理。而堆不就是进程地址空间的一部分嘛。

我们在语言层面上有这么几种处理不同文件的缓冲区的策略

1.无缓冲区,不用刷新

2.行刷新,比如要写入到显示器中

3.全缓冲,全部刷新,就是一般文件只有当缓冲区写满时才刷新

当然我们也可以主动刷新,比如调用fflush函数,或者进程结束时,缓冲区会自动刷新

那么,上面说的缓冲区的位置,处理不同文件的缓冲区的策略你怎么证明呢?下面我们就来写一个代码验证一下

我们运行这个代码,无论是直接运行,或者是打印到一个文件中,都是正常的:

但是当我们仅在代码尾部加了一个fork后,结果就变了

可以看到,打印到显示器上是正常的,但是打印到文件中,系统调用打印了一回,库函数打印了两回。这是因为系统调用没有缓冲区,直接写到操作系统中;而库函数因为是写入到文件中,所以不是行刷新,程序结束才会刷新,而在程序没结束时,子进程被创建,父子进程共享进程地址空间,而进程地址空间中就存着缓冲区,缓冲区中有内容,程序结束时,父子进程缓冲区中的内容都要被刷新出来刷新缓冲区也是一种修改数据,所以发生写时拷贝

知道了上面的理论,我们可以自己创建一个源代码文件来实现FILE结构体并且实现相关函数,我们实现的大体思路是首先用法要和库中的保持一致,我们内部实现要加上缓冲区,并且对于不同种类的文件要有不同的刷新方式,我们只要是通过自己实现知道C语言的缓冲区具体在哪里就可以

//myFILE.h
#pragma once
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/types.h>
enum MODEBUFF
{
  NO_BUFF,
  ROW_BUFF,
  FULL_BUFF,
};
typedef struct MYFILE
{
  int fileno;
  char buffer[64];
  int pos;
  int mode;
}MYFILE;

MYFILE* fopen(const char*path,const char*option);

int mywrite(const char*str,int size,int n,MYFILE*fp);

int fclose(MYFILE*fp);
//myFILE.c
#include"myFILE.h"


MYFILE* fopen(const char*path,const char*option)
{
  MYFILE* tmp=(MYFILE*)malloc(sizeof(MYFILE));
  tmp->pos=0;
  tmp->mode=FULL_BUFF;
  if(option[0]=='w')
  {
    tmp->fileno=open(path,O_WRONLY|O_CREAT|O_TRUNC,0666);
  }
  else if(option[0]=='a')
  {
    tmp->fileno=open(path,O_WRONLY|O_CREAT|O_APPEND|0666);
  }
  else if(option[0]=='r')
  {
    tmp->fileno=open(path,O_RDONLY);
  }
  else return NULL;
  return tmp;
}


int mywrite(const char*str,int size,int n,MYFILE*fp)
{
  while(n--)
  {  
    if(fp->mode==FULL_BUFF)
    {
      strncpy(fp->buffer+fp->pos,str,size);
      fp->pos=strlen(fp->buffer);
    }
    else
    {
         write(fp->fileno,str,size);
    }
  }
return size;
}
int fclose(MYFILE*fp)
{
  if(fp->pos!=0)write(fp->fileno,fp->buffer,fp->pos);
  close(fp->fileno);
  free(fp);
  fp=NULL;
  return 0;
}
//test.c
#include"myFILE.h"

int main()
{
  MYFILE* fp=fopen("./tmp1","w");
  mywrite("abcbcbbc",5,1,fp);
  fork();
  fclose(fp);


  return 0;
}
//makefile
myFILE:myFILE.c test.c
	gcc -o $@ $^
.PHONY:clean
clean:
	rm myFILE 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

异地公司如何文件共享?

很多企业面临着异地办公的挑战。随着公司业务的扩展和发展&#xff0c;分布在不同地区的办公室需要频繁地共享文件和数据。由于网络环境的限制&#xff0c;异地公司文件共享变得困难且耗时。在这篇文章中&#xff0c;我们将介绍一种能够解决异地公司文件共享问题的解决方案。 科…

Socket网络通讯入门(一)

提示&#xff1a;能力有限&#xff0c;不足以及错误之处还请指出&#xff01; 文章目录 前言一、 计算机网络 OSI、TCP/IP、五层协议 体系结构1.OSI七层模型每层的作用2.TCP/IP协议分成3.五层协议体系结构 二、Socket服务端和客户端 简单通信1.服务端代码2.客户端 总结 前言 简…

【讯为Linux驱动开发】2.注册一个字符设备

【问】如何描述一个字符设备&#xff1f; dev结构体 其中需要关心三个成员变量&#xff1a; 所属模块 &#xff1a;struct module *owner; 文件操作结构体&#xff1a; const struct file_operations *ops 设备号 &#xff1a; dev_t 当应用层使用指令open("/dev/hello&…

京东JD商品详情接口接入文档说明参数说明

京东获得JD商品详情 API 返回值说明 item_get-获得JD商品详情 API注册测试 jd.item_get 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&am…

LabVIEW电路板性能与稳定性测试系统

LabVIEW电路板性能与稳定性测试系统 概述&#xff1a; 开发基于LabVIEW的电路板性能与稳定性测试系统&#xff0c;通过集成多种测试仪器&#xff0c;实现对电路板的电气性能和长期稳定性的全面评估。系统涵盖了电压、电流、温度等多项参数的监测&#xff0c;并具备自动化测试…

微处理器体系结构

1.冯诺依曼结构 传统计算机采用冯●诺依曼(Von Neumann)结构&#xff0c;也称普林斯顿结构&#xff0c;是一种将程序指令存储器和数据存储器合并在一起的存储器结构。 特征&#xff1a; 冯●诺依曼结构的计算机程序和数据共用一个存储空间&#xff0c;程序指令存储地址和数据…

基于YOLOv8深度学习的茶叶病害检测含数据集

在当前全球竞争的时代&#xff0c;农业的重要性不容小觑。作为我国重要的经济作物&#xff0c;茶叶在农村振兴政策框架内对茶农的经济稳定起着至关重要的作用。作为云南省的特色产业和地区农业中著名的“金字品牌”&#xff0c;茶叶行业的高质量发展可以大大增强地区特色产业的…

水泵选型指南

在现代暖通空调&#xff08;HVAC&#xff09;系统中&#xff0c;冷冻水泵是关键组件之一&#xff0c;它在提供冷却和空调效果方面起着至关重要的作用。选择合适的冷冻水泵不仅可以提高系统效率&#xff0c;还能节省能源和维护成本。本文将介绍冷冻水泵选型的关键因素和步骤。 …

不装了,我是知识星球的星主,我摊牌了~

作者&#xff1a;哈哥撩编程 &#xff08;视频号同名&#xff09; 图书作者&#xff1a;程序员职场效能宝典 博客专家&#xff1a;全国博客之星第四名 超级个体&#xff1a;COC上海社区主理人 特约讲师&#xff1a;谷歌亚马逊分享嘉宾 科技博主&#xff1a;极星会首批签约…

量化交易:Miniqmt获取可转债数据和交易python代码

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 低风险资产除了国债外&#xff0c;还有可转债&#xff0c;兼容有高收益的股性和低风险的债性&#xff0c;号称“下有保底&#xff0c;上不封顶”。 &#x1f50d; 可转债&#xff1a;金融市场的双面娇娃 可转债&am…

Go微服务: 基于rocketmq:server和rocketmq:broker搭建RocketMQ环境,以及生产消息和延迟消费消息的实现

RocketMQ 的搭建 1 ) 配置 docker-compose.yaml 文件 version: 3.5 services:rmqnamesrv:image: foxiswho/rocketmq:servercontainer_name: rmqnamesrvports:- 9876:9876volumes:- ./logs:/opt/logs- ./store:/opt/storenetworks:rmq:aliases:- rmqnamesrvrmqbroker:image: fo…

Vue3实战笔记(58)—从零开始掌握Vue3插槽机制,基础入门

文章目录 前言插槽基础入门总结 前言 不论是组件封装还是分析源码&#xff0c;实际开发中经常接触插槽&#xff0c;插槽是干什么用的呢&#xff1f;组件之间能够接收任意类型的 JavaScript 值作为 props&#xff0c;但组件要如何接收模板内容呢&#xff1f;在某些场景中&#…

伏图(Simdroid)5.0 电子散热模块 | 可试用

伏图-电子散热模块&#xff08;Simdroid-EC&#xff09;是云道智造基于通用多物理场仿真PaaS平台伏图开发的针对电子元器件、设备等散热的专用热仿真模块&#xff0c;内置电子产品专用零部件模型库&#xff0c;支持用户通过“搭积木”的方式快速建立电子产品的热分析模型&#…

【13】Camunda7-第一个BPMN流程

在前一篇博文里&#xff0c;我们介绍了Camunda Modeler&#xff0c;那么接下来&#xff0c;我们就使用这个设计器工具绘制我们的第一个BPMN流程。 1. 场景概述 关于第一个流程&#xff0c;这里计划以我们最常见的请假审批流程为例&#xff0c;它涉及到表单设计、人工审批任务等…

从0开始用TCN预测股价

源码地址 由于用的是tensflow&#xff0c;其实和pytorch差不多看懂就行&#xff08;pytorch版本我还在改&#xff0c;有些难度&#xff09;因为这里面的tcn结构是用的 别人的代码https://github.com/philipperemy/keras-tcn 首先说一下什么是TCN 首先我想说 我们预测股票价格…

Science项目文章 | 中国农科院作科所研究团队解析“复粒稻”多粒簇生的机制

2024年3月8日&#xff0c;由中国农业科学院作物科学研究所童红宁研究员领衔的研究团队在Science发表题为“Enhancing rice panicle branching and grain yield through tissue-specific brassinosteroid inhibition”的研究论文。该研究报道了复粒稻多粒簇形成的机制&#xff0…

python-flask项目的服务器线上部署

在部署这部分我首先尝试了宝塔面板&#xff0c;始终连接失败 换了一种思路选择了Xshell成功连接 首先我们需要下载个免费版本的Xshell 免费的&#xff1a;家庭/学校免费 - NetSarang Website 下载完毕打开 1新建-> 输入服务器的账号密码&#xff1a; 在所有会话中点击自…

WalleWeb简化你的DevOps部署流程

walle-web&#xff1a;简化部署流程&#xff0c;提升开发效率&#xff0c;Walle Web让DevOps触手可及 - 精选真开源&#xff0c;释放新价值。 概览 Walle Web是一个功能强大且免费开源的DevOps平台&#xff0c;旨在简化和自动化代码部署流程。它支持多种编程语言&#xff0c;包…

学习axios拦截器

axios拦截器的作用&#xff1a;用于在请求发送前和响应返回后对请求和响应进行统一处理&#xff0c;例如添加公共请求头、处理请求参数、统一处理错误信息等。拦截器提供了一种灵活、高效的方式来管理HTTP请求和响应&#xff0c;帮助在前端开发中更好地处理数据交互。 这是一个…

Commons-Collections篇-CC3链

前言 我们分析前两条链CC1和CC6时&#xff0c;都是利用invoke反射调用的Runtime().getRuntime().exec()来执行命令。而很多时候服务器的代码当中的黑名单会选择禁用Runtime CC3链主要通过动态加载类加载机制来实现自动执行恶意类代码 1.环境安装 可以接着使用我们之前分析C…