深入理解Linux系统内存中文件结构以及缓冲区,模拟实现c语言库文件接口

ops/2025/1/21 11:04:05/

目录

一、文件的理解

二、文件操作

1.Linux系统中文件接口:

1.1.open

1.2.write 

1.3.read

三、文件描述符

四、重定向的理解

五、缓冲区

1.语言层缓冲区

2.系统层缓冲区

3.缓冲区刷新策略(语言层)

六、c文件接口的模拟实现

1.mystdio.h

2.mystdio.c


一、文件的理解

        在Linux系统中,文件被存储在磁盘上。磁盘是计算机重要的存储工具,属于外设,它既是输入设备也是输出设备,而且是永久性储存的机械设备

        从广义上来讲,Linux系统中⼀切皆⽂件(键盘、显⽰器、⽹卡、磁盘……这些都是抽象化的过程),也就是对于这些硬件的管理信息,都被抽象并储存在文件中。

        因为这些设备都有相同的属性,比如读,写等,所以可以屏蔽底层的差异把它们都抽象成文件就让系统方便管理的多,这样就实现了c++中多态的效果

注意文件=内容+属性,所以一个0KB的空文件也占磁盘空间!

二、文件操作

        对文件进行操作时第一步必然就是打开文件,因为显示器,键盘等等都是文件,所以我们要把数据打印到显示器上,或者把从键盘输入数据都需要打开文件。

        但是在我们在写程序的时候自己从没打开过这些文件,依然能够实现对应的输入输出操作。这其实是因为在程序启动时系统就默认打开三个输⼊输出流,分别是stdin(标准输入流),stdout(标准输出流),stderr(标准错误流)

        关于文件操作在c语言中我们学过fopen打开文件,fread,fwrite读写文件。而我们也知道c库中这些函数都是官方写好的一下比较常用的接口来提高我们代码编写效率。而对于涉及硬件的这些接口它底层必然用到了系统接口,比如文件操作相关接口。

1.Linux系统中文件接口:

常用接口:

1.1.open

以上图片是截取了man手册中关于open使用的部分信息。

注意使用它需要包含的三个头文件。 

  • 返回值:open失败返回-1,成功则返回文件描述符通常记作fd,关于fd先错误的想象成 “区分文件的标志”,到下文会详解。
  • 第一个参数pathname需要传入要被打开的文件路径。
  • 第二个参数flags需要传入标志位,从而让系统知道文件的打开方式。
  • 第三个参数mode需要传入这个文件需要被设置的起始权限,注意:需要以0开头表示八进制。当然这个参数也很少用到。
  • 返回值:

标记位:巧用了位图的思想,比如我们把W = 1(0000 0001)来表示写文件,C = 2(0000 0010)表示没有此文件就创建该文件,T = 3(0000 0011)表示把先该文件清空再写入 ,如果传入参数flags = C | W | T,到了函数内部如果C | flags为真则如果文件不存在就创建,如果W | flags为真就以写的形式打开文件文... ... 这样传入一个参数就达到了传入多个参数的效果。W,C,T只是为方便临时举的例子,下面我们来看Linux系统提供的标志位参数。

 Linux文件系统提供的标志位常用参数有:

  • O_RDONLY: 以只读方式打开
  • O_WRONLY: 以只写方式打开
  • O_CREAT : 若⽂件不存在,则创建它。需要使⽤mode选项,来指明新⽂件的访问权限O_TRUNC:先清空再写入
  • O_APPEND: 追加写入

1.2.write 

  • 返回值:操作错误返回-1,否则返回写入成功的字节数。
  • 第一个参数fd,传入文件描述符。
  • 第二个参数buf,传入需要写的数据区域的指针。
  • 第三个参数count,传入需要写入的字节个数。

1.3.read

  • 返回值:操作错误返回-1,否则返回读取成功的字节数。
  • 第一个参数fd,传入文件描述符。
  • 第二个参数buf,传入需要读取到的数据区域的指针。
  • 第三个参数count,传入需要读取的字节个数。

三、文件描述符

        文件描述符fd简单一点来说就是数组下标,是一个什么样的数组,数组元素是什么我们来具体来看。

        首先在系统中所有进程用到的所有文件都会被加载到内存中,这些文件通常会以链表或其他结构组织起来。而每个进程pcb中会存在一个files_struct结构体来储存该进程用到的文件信息,而在files_struct中存在一个数组,数组元素是该进程用到的文件信息,通过该信息可以找到对应的文件。如下:

所以我们用一个fd就可以锁定确定的文件。 

        注意:在程序运行时系统默认给我们打开的文件stdin,stdout,strerr分别对应文件描述符表中的0,1,2下标。

        进程中新打开的文件会从文件描述符标的0下标位置依次往后找到被关闭的文件并把新文件的信息储存到其中,如果没有就插入到数组尾部。

四、重定向的理解

        重定向本质就是文件描述符表中文件信息的相互覆盖,打个比方:文件描述符表下标 a储存f1的文件信息,下标b储存文件f2的文件信息,把b下标的信息覆盖到a下标位置,那么原来对f1文件操作就会变成对f2文件操作。注意:b下标储存的还是f2。

以上操作我们通常使用dup2接口来实现:

它需要传入两个参数,作用是把oldfd位置的内容覆盖到newfd位置。 

        因为操作系统对文件操作只认fd(下标),无论是c库封装的FILE本质还是通过fd来确定文件。比如printf只认fd = 1(对应的是标准输出),那么我们可以实现这样的重定向操作:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{int fd = open("log",O_CREAT | O_WRONLY, 0664);if(fd<0) return -1;                                                                                                                                                                 dup2(fd,1);                                                                                                               printf("hello linux!\n");  close(fd);                                                                                               return 0;                                                                                                                 
}

        注意:标准输出流stdout(fd=1),标准错误流stderr(fd=2),它们虽然下标不一样,但对应文件描述符表的文件信息是一样的都是显示器。 

正如我上面所说的措辞:fd并不是区分文件的标志。        

        那为什么要把同一个文件写在一个进程的两个文件描述符中呢?这是为了方便把错误信息单独分离出来。

五、缓冲区

        缓冲区分为语言层的和系统层的,像c语言就有自己的缓冲区。通常我们所说的缓冲区指的是语言层的缓冲区。

1.语言层缓冲区

        在认识缓冲区之前我们先想象一个场景,假设一辆公交车在司机在候车区看到1个人需要乘车就马上拉走一个,拉完再回来拉,每次几乎只拉一个人而路途又很长,那样的话可以想象效率是多么的低,解决方法就是公交车会在候车区停留20分钟到30分钟,等乘客足够多的时候一次性拉走,这样一来效率就高了很多。

        像printf,sacnf......这些函数是调用了操作系统指令才得以实现的,如果每读写1个数据都做一次系统调用,操作系统在执行的时候会被频繁的打断,效率变得很低。所以有了缓冲区的出现,让需要调用操作系统的数据先放在缓冲区,到最后一次性的执行。

数据:乘客

缓冲区:候车区

操作系统:公交车

注意:关闭文件起到了刷新缓冲区的作用,这也是打开的文件一定要关闭的原因之一。

2.系统层缓冲区

        当语言层缓冲区刷新之后,数据并不会马上写入磁盘,而是放到了系统的缓冲区,系统缓冲区的作用是减少磁盘的随机读写,增加顺序读写从而提高读写效率。因为读写到一起的都是相关性强的数据,等再次被读的时候就可以一起被读出来。

3.缓冲区刷新策略(语言层)

        在c语言中被写到显示器文件的数据是按行刷新,也就是遇到换行符“\n”就进行刷新,因为我们通常在阅读时候都是一行一行读的。而被写到其他文件的数据都是等缓冲区满的时候才刷新,也就是全刷新。最后在程序结束后缓冲区也会刷新。

我们可以做以下验证:

#include <stdio.h>
#include <unistd.h>
int main()
{printf("hello linux");                                                                                                                                                            sleep(3);//休眠3秒                return 0;                                                    
}

        在执行以上代码我们会明显感觉到先执行sleep(3)才打印出hello linux,这是因为printf的信息一直没有刷新到缓冲区,直到程序退出后才刷新。 

注意:语言层缓冲区刷新到系统后就当做已经完成读写了,不要纠结系统的缓冲区是否刷新。

我们再看以下示例:

#include <stdio.h>
#include <string.h>
int main()
{const char *msg0="hello printf\n";const char *msg1="hello fwrite\n";const char *msg2="hello write\n";printf("%s", msg0);fwrite(msg1, strlen(msg1), 1, stdout);write(1, msg2, strlen(msg2));fork();return 0;
}

输出结果(写到显示器): 

 当我们重定向到其他文件时(写到其他文件):

如果你能解释以上输出结果,那么恭喜你,已经掌握了缓冲区的刷新策略。

        首先printf,fwrite是c语言库接口,执行这两条语句后数据被加载到缓冲区,而write是系统调用,数据被直接加载到系统。

        写到显示器是行刷新策略,所以都被刷新到系统了。尽管后面fork创建了子进程,子进程什么也不做也会刷新缓冲区,但缓冲区已经空了,所以结果如上。

        写到其他文件是全部刷新策略,write写的内容直接到系统了,所以只需要刷新printf,fwrite写的内容,父进程刷新一次,子进程刷新一次,所以打印了两份内容。

非常感谢您能耐心读完这篇文章。倘若您从中有所收获,还望多多支持呀!74c0781738354c71be3d62e05688fecc.png 

六、c库文件接口的模拟实现

1.mystdio.h

#include<stdio.h>
#define N 128
typedef struct myFILE
{int fd;char buffers[N];//模拟缓冲区int buffSize;  //缓冲区有效字符个数int flag;   
}myFILE;
myFILE* myfopen(char* ,char* );
void myfwrite(char* ,size_t ,myFILE* );
void myfclose(myFILE* );
void myfflush(myFILE* );

2.mystdio.c

#include "mystdio.h"
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
myFILE* setFILE(int _fg, int _fd)
{myFILE* fp = (myFILE*)malloc(sizeof(myFILE));if(fp == NULL) return NULL;fp->fd = _fd;fp->flag = _fg;fp->buffSize=0;memset(fp->buffers, 0, N);return fp;
}
myFILE* myfopen(char* str,char* way)
{int fd = -1,flag = 0;if(strcmp(way,"w")==0){flag = O_CREAT | O_WRONLY | O_TRUNC;fd = open(str, flag, 0664);}else if(strcmp(way,"r")==0){flag = O_RDWR;fd = open(str, flag, 0664);}else if(strcmp(way,"a")==0){flag = O_CREAT | O_WRONLY | O_APPEND;fd = open(str, flag, 0664);}else{//more}if(fd < 0){printf("myfopen error\n");return NULL;}return setFILE(flag, fd);
}
void myfwrite(char* str,size_t sz,myFILE* fp)
{memcpy(fp->buffers+fp->buffSize,str,sz);fp->buffSize += sz;                                                                                                                                                                                                                                        if(str[sz-1]=='\n')myfflush(fp);
}
void myfclose(myFILE* fp)
{myfflush(fp);close(fp->fd);free(fp);fp = NULL;
}
void myfflush(myFILE* fp)
{write(fp->fd,fp->buffers,fp->buffSize);fp->buffSize = 0;
}


http://www.ppmy.cn/ops/151891.html

相关文章

【Elasticsearch】搜索类型介绍,以及使用SpringBoot实现,并展现给前端

Elasticsearch 提供了多种查询类型&#xff0c;每种查询类型适用于不同的搜索场景。以下是八种常见的 Elasticsearch 查询类型及其详细说明和示例。 1. Match Query 用途&#xff1a;用于全文搜索&#xff0c;会对输入的文本进行分词&#xff0c;并在索引中的字段中查找这些分…

Azure Synapse Dedicated SQL Pool实用命令语句

一、数据管理相关命令 1. 数据加载 COPY 命令&#xff1a;用于从外部存储&#xff08;如 Azure Blob 存储&#xff09;加载数据到 Dedicated SQL Pool 中。 COPY INTO [dbo].[target_table] FROM https://<storage_account>.blob.core.windows.net/<container>/…

计算机毕业设计Python+卷积神经网络租房推荐系统 租房大屏可视化 租房爬虫 hadoop spark 58同城租房爬虫 房源推荐系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

利用rsync备份全网服务器数据

一、项目描述 某公司里有一台Web服务器&#xff0c;里面的数据很重要&#xff0c;但是如果硬盘坏了数据就会丢失&#xff0c;现在领导要求把数据做备份&#xff0c;这样Web服务器数据丢失在可以进行恢复&#xff0c;要求如下&#xff1a; 1、备份要求 每天晚上00点整在Web服…

什么是SSL及SSL的工作流程

什么是 SSL SSL(Secure Sockets Layer,安全套接层)是一种保护互联网通信安全的加密协议,用于确保数据在客户端和服务器之间传输时的保密性、完整性和身份验证。它已被TLS(Transport Layer Security,传输层安全协议)取代,但很多场景仍习惯称其为SSL。 SSL/TLS 的主要目…

前端如何实现分页

前言 虽然在实际开发中&#xff0c;大多数分页都是由后端处理&#xff0c;但还是有小部分场景需要前端来实现分页。 实现并不难&#xff0c;仅作为记录&#xff0c;方便下次拿来直接使用。 准备数据源 数据源可以是从后端获取的数据列表&#xff0c;也可以是前端模拟的数据集…

电子科大2024秋《大数据分析与智能计算》真题回忆

考试日期&#xff1a;2025-01-08 课程&#xff1a;成电信软学院-大数据分析与智能计算 形式&#xff1a;开卷 考试回忆版 简答题&#xff08;4*15&#xff09; 1. 简述大数据的四个特征。分析每个特征所带来的问题和可能的解决方案 2. HDFS的架构的主要组件有哪些&#xff0…

C++和OpenGL实现3D游戏编程【连载21】——父物体和子物体模式实现

欢迎来到zhooyu的专栏。 &#x1f525;C和OpenGL实现3D游戏编程【专题总览】 1、本节要实现的内容 上节课我们已经创建了一个基础Object类&#xff0c;以后所有的游戏元素都可以从这个基类中派生出来。同时为了操作方便&#xff0c;我们可以为任意两个Object类&#xff08;及其…