【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.cgcc -o $@ $^
.PHONY:clean
clean:rm myFILE 

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

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

相关文章

rust学习(字节数组转string)

最新在写数据传输相关的操作&#xff0c;发现string一个有趣的现象&#xff0c;代码如下&#xff1a; fn main() {let mut data:[u8;32] [0;32];data[0] a as u8;let my_str1 String::from_utf8_lossy(&data);let my_str my_str1.trim();println!("my_str len is…

STM32实验之USART串口发送+接受数据(二进制/HEX/文本)

涉及三个实验&#xff1a; 1.USART串口发送和接收数据 我们使用的是将串口封装成为一个Serial.c模块.其中包含了 void Serial_Init(void);//串口初始化 void Serial_SendByte(uint8_t Byte);//串口发送一个字节 void Serial_SendArray(uint8_t *Array,uint16_t Length);//…

【C语言】详解函数(上)(庖丁解牛版)

文章目录 1. 前言2. 函数的概念3.库函数3.1 标准库和头文件3.2 库函数的使用3.2.1 头文件的包含3.2.2 实践 4. 自定义函数4.1 自定义函数的语法形式4.2 函数的举例 5. 形参和实参5.1 实参5.2 形参5.3 实参和形参的关系 6. return 语句6. 总结 1. 前言 一讲到函数这块&#xff…

栈排序00

题目链接 栈排序 题目描述 注意点 对栈进行排序使最小元素位于栈顶最多只能使用一个其他的临时栈存放数据不得将元素复制到别的数据结构&#xff08;如数组&#xff09;中栈中的元素数目在[0, 5000]范围内 解答思路 本题是要实现一个小顶堆&#xff0c;可以直接使用Priori…

上位机图像处理和嵌入式模块部署(f407 mcu中的udp server开发)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 既然lwip已经port到407上面了&#xff0c;接下来其实就可以做一些测试了。本身lwip支持tcp、udp&#xff0c;也支持client和server&#xff0c;既然…

【数据分享】中国第三产业统计年鉴(1991-2022)

大家好&#xff01;今天我要向大家介绍一份重要的中国第三产业统计数据资源——《中国第三产业统计年鉴》。这份年鉴涵盖了从1991年到2022年中国第三产业统计全面数据&#xff0c;并提供限时免费下载。&#xff08;无需分享朋友圈即可获取&#xff09; 数据介绍 每年的《中国…

2004NOIP普及组真题 3. FBI树

线上OJ 地址&#xff1a; [04NOIP普及组] FBI树 本题的意思是&#xff1a;给定一个 01字符串 &#xff08;对应一棵完全二叉树的最后一层叶子节点&#xff09;&#xff0c;将树的每一个节点的值用字母“F、B、I”表示。规则&#xff08;如下图所示&#xff09;为&#xff1a; 1…

Spring AI 第二讲 之 Chat Model API 第二节Ollama Chat

通过 Ollama&#xff0c;您可以在本地运行各种大型语言模型 (LLM)&#xff0c;并从中生成文本。Spring AI 通过 OllamaChatModel 支持 Ollama 文本生成。 先决条件 首先需要在本地计算机上运行 Ollama。请参阅官方 Ollama 项目 README&#xff0c;开始在本地计算机上运行模型…

curl 92 HTTP/2 stream 5 was not closed cleanly: CANCEL

source ~/.bash_profile flutter clean Command exited with code 128: git fetch --tags Standard error: 错误&#xff1a;RPC 失败。curl 92 HTTP/2 stream 5 was not closed cleanly: CANCEL (err 8) 错误&#xff1a;预期仍然需要 2737 个字节的正文 fetch-pack: unexpec…

GPT革命:AI如何重塑我们的未来!

GPT革命&#xff1a;AI如何重塑我们的未来&#xff01; &#x1f604;生命不息&#xff0c;写作不止 &#x1f525; 继续踏上学习之路&#xff0c;学之分享笔记 &#x1f44a; 总有一天我也能像各位大佬一样 &#x1f3c6; 博客首页 怒放吧德德 To记录领地 &#x1f31d;分享…

普通人也能弄的 16 个AI搞钱副业,门槛低,易上手!

大家好&#xff0c;我是灵魂画师向阳 本期给大家分享的是利用AI 做副业的一些方法&#xff0c;大家可以挑选适合自己的赛道去搞钱 现在是人工智能时代&#xff0c;利用好AI 工具&#xff0c;可以降低普通人做副业的门槛&#xff0c;同时也能提高工作效率&#xff0c; 因此AI …

【微机原理与汇编语言】循环程序设计

一、实验目的 1.熟练掌握8086/8088常用汇编指令的使用方法 2.熟练掌握循环结构程序编程技巧 3.熟练掌握汇编语言程序运行调试方法 二、实验要求 认真分析实验题目&#xff0c;设计程序流程图&#xff0c;独立完成代码编写及运行调试。 三、实验题目 给出不大于255的十个…

图片裁剪与上传处理方案 —— 基于阿里云 OSS 处理用户资料

目录 01: 通用组件&#xff1a;input 构建方案分析 02: 通用组件&#xff1a;input 构建方案 03: 构建用户资料基础样式 04: 用户基本资料修改方案 05: 处理不保存时的同步问题 06: 头像修改方案流程分析 07: 通用组件&#xff1a;Dialog 构建方案分析 08: 通用组件&…

计算机组成原理·考点知识点整理

根据往年考试题&#xff0c;对考点和知识点的一个整理。 校验编码 码距 一种编码的最小码距&#xff0c;其实就是指这种编码的码距。码距有两种定义&#xff1a; 码距所描述的对象含义 2 2 2 个特定的码其二进制表示中不同位的个数一种编码这种编码中任意 2 2 2 个合法编码的…

【linux进程控制(三)】进程程序替换--如何自己实现一个bash解释器?

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; 进程程序替换 1. 前言2. exec…

【JMeter接口自动化】第8讲 Fiddler抓包Jmeter

1&#xff09;配置好Fiddler 设置Fiddler-Tools-Options-HTTPS 设置Fiddler-Tools-Options-Connections&#xff0c;设置端口为8888 2&#xff09;查看IP 在CMD中输入ipconfig 查看IP地址 3&#xff09;配置Jmeter Http请求——基本&#xff0c;设置Http请求&#xff0c;使用…

北航数据结构与程序设计第四次作业选填题复习

首先都是线性的&#xff0c;线性包括顺序和链式&#xff0c;栈和队都可以用两种方式实现。栈只能存于栈顶取于栈顶&#xff0c;队列先进先出&#xff0c;因此存取点是固定的。 函数栈帧创建原理 画图即可。 A.显然不行&#xff0c;5如果第一个出来说明5是最后一个进的&#xf…

Lambda表达式与函数式工具在Python中的应用详解

目录 一、引言 二、Lambda表达式 Lambda表达式的定义 Lambda表达式的使用场景 Lambda表达式的示例 三、函数式工具 map()函数 filter()函数 reduce()函数 itertools模块 functools模块 四、Lambda表达式与函数式工具的结合使用 五、Lambda表达式与函数式工具的注意…

【云岚家政】-day00-开发环境配置

文章目录 1 开发工具版本2 IDEA环境配置2.1 编码配置2.2 自动导包设置2.3 提示忽略大小写2.4 设置 Java 编译级别 3 Maven环境3.1 安装Maven3.2 配置仓库3.3 IDEA中配置maven 4 配置虚拟机4.1 导入虚拟机4.2 问题 5 配置数据库环境5.1 启动mysql容器5.2 使用MySQL客户端连接数据…

Java Socket 网络编程实例(阻塞IO、非阻塞IO、多路复用Selector、AIO)

文章目录 1. 概述2. TCP 阻塞式IO 网络编程实例2.1 TCP网络编程服务端2.2 ByteBufferUtil2.3 客户端代码2.4 运行截图 3. TCP 非阻塞式IO 网络编程实例3.1 服务端3.2 客户端3.3 运行截图 4. 多路复用4.1 服务器端4.2 客户端4.3 运行截图 5. AIO5.1 AIO 服务端5.2 客户端5.3 运行…