【linux】:模拟文件基本操作以及文件在磁盘中如何存储的学习(上)

简介: 【linux】:模拟文件基本操作以及文件在磁盘中如何存储的学习(上)

前言



经过我们上一篇对linux系统文件操作的学习想必我们已经会使用系统文件接口了,今天我们就用系统文件接口来封装一个像C语言库那样的文件操作函数的函数来加深我们对文件操作的学习。


一、模拟C库文件操作



首先我们创建相应的.c   .h 以及main.c头文件,然后我们写一个makefile:

6a651a544910447d9d3aad770e59e6b4.png

接下来我们完善.h里面的代码:

#pragma once
#include <stdio.h>
#define NUM 1024
#define BUFF_NONE 0x1
#define BUFF_LINE 0x2
#define BUFF_ALL  0x4
typedef struct _MY_FILE
{
  int fd;
  char outputbuffer[NUM];
  int flags;       //刷新策略
}MY_FILE;
MY_FILE* my_fopen(const char* path,const char* mode);
size_t my_fwrite(const void* ptr,size_t size,size_t nmemb,MY_FILE* stream);
int my_fclose(MY_FILE* fp);


首先有一个文件结构体,在这个结构体中有文件描述符,有一个1024大小的缓冲区,还有控制刷新的标志,我们为了方便将结构体重命名为MY_FILE,既然有刷新策略那么我们就#define3个刷新策略,分别是无缓冲,行缓冲,全缓冲。然后我们实现三个函数,分别是打开文件,写入文件和关闭文件,这些函数我们都是通过系统接口调用所以参数与系统接口一致,下面我们进行函数主体的编写:

#include "mystdio.h"
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <malloc.h>
#include <assert.h>
#include <unistd.h>
MY_FILE* my_fopen(const char* path,const char* mode)
{
  //.识别标识位
  int flag = 0;
  if (strcmp(mode,"r")==0)
  {
    flag|=O_RDONLY;
  }
  else if(strcmp(mode,"w")==0)
  {
    flag|=(O_CREAT | O_WRONLY | O_TRUNC);
  }
  else if(strcmp(mode,"a")==0)
  {
    flag|=(O_CREAT | O_WRONLY | O_APPEND);
  }
  else 
  {
  }
  //2.尝试打开文件
  mode_t m = 0666;
  int fd = 0;
  if (flag & O_CREAT)
  {
    fd = open(path,flag,m);
  }
  else 
  {
    fd = open(path,flag);
  }
  if (fd<0)
  {
    return NULL;
  }
  //给用户返回MY_FILE对象,需要先进行构建
  MY_FILE* mf = (MY_FILE*)malloc(sizeof(MY_FILE));
  if (mf==NULL)
  {
    close(fd);
    return NULL;
  }
  //4.初始化MY_FILE对象
  mf->fd = fd;
  mf->flags = BUFF_LINE;
  memset(mf->outputbuffer,'\0',sizeof(mf->outputbuffer));
  //my_outputbuffer[0] = 0;  //初始化缓冲区
  //5.返回打开的文件
  return mf;
}
int my_fflush(MY_FILE* fp)
{
  return 0;
}
size_t my_fwrite(const void* ptr,size_t size,size_t nmemb,MY_FILE* stream)
{
  return 0;
}
int my_fclose(MY_FILE* fp)
{
  assert(fp);
  //1.冲刷缓冲区
  if (fp->current >0)
  {
    my_fflush(fp);
  }
  //2.关闭文件
  close(fp->fd);
  //3.释放堆空间
  free(fp);
  //4.指针置NULL --- 可以设置可以不设置
  fp = NULL;
  return 0;
}


下面我们一个函数一个函数的进行讲解,首先是fopen函数

MY_FILE* my_fopen(const char* path,const char* mode)
{
  //.识别标识位
  int flag = 0;
  if (strcmp(mode,"r")==0)
  {
    flag|=O_RDONLY;
  }
  else if(strcmp(mode,"w")==0)
  {
    flag|=(O_CREAT | O_WRONLY | O_TRUNC);
  }
  else if(strcmp(mode,"a")==0)
  {
    flag|=(O_CREAT | O_WRONLY | O_APPEND);
  }
  else 
  {
  }
}


对于打开文件的函数我们要判断用户是什么模式下的打开文件,我们设置标志位为0,如果用户输入的方式是r只读模式,就让标志位或上只读选项,如果是w写模式,这个时候要有三种实现,文件不存在就创建文件,只写文件,写之前先清空文件,搞定w模式后我们完成追加模式,对于a来讲也有三种情况,文件不存在就创建文件,只写文件,追加文件不进行清空,搞定第一步后我们完成第二步打开文件:

 //2.尝试打开文件
  mode_t m = 0666;
  int fd = 0;
  if (flag & O_CREAT)
  {
    fd = open(path,flag,m);
  }
  else 
  {
    fd = open(path,flag);
  }
  if (fd<0)
  {
    return NULL;
  }


要打开文件我们先创建一个默认的文件权限0666,并且创建一个文件标识符变量,判断文件是否存在,如果存在就用不需要权限参数的open函数代开文件,如果不存在则需要用三个参数的open函数打开文件。如果返回值小于0说明打开文件失败返回空指针。

  //3.给用户返回MY_FILE对象,需要先进行构建
  MY_FILE* mf = (MY_FILE*)malloc(sizeof(MY_FILE));
  if (mf==NULL)
  {
    close(fd);
    return NULL;
  }
  //4.初始化MY_FILE对象
  mf->fd = fd;
  mf->flags = BUFF_LINE;
  memset(mf->outputbuffer,'\0',sizeof(mf->outputbuffer));
  //my_outputbuffer[0] = 0;  //初始化缓冲区
  //5.返回打开的文件
  return mf;


第三步给用户返回一个文件对象,先创建一个指针用来开一个MY_FILE的结构体,然后判断是否创建成功如果不成功我们就将文件关闭并且返回NULL,如果成功我们就初始化MY_struct中的参数,将文件标识符传过去,因为C语言是行缓冲所以我们直接设为行缓冲,然后给结构体中的缓冲区初始化。这些过程都结束了就返回打开的文件。

int my_fclose(MY_FILE* fp)
{
  assert(fp);
  //1.冲刷缓冲区
  if (fp->current >0)
  {
    my_fflush(fp);
  }
  //2.关闭文件
  close(fp->fd);
  //3.释放堆空间
  free(fp);
  //4.指针置NULL --- 可以设置可以不设置
  fp = NULL;
  return 0;
}


关闭文件的函数很简单,因为关闭文件前需要先刷新缓冲区所以我们在struct中添加一个变量current来判断是否需要刷新缓冲区,冲刷完缓冲区后将文件关闭,最后将malloc出来的struct结构体空间释放掉,为了安全起见可以将fp指针置为NULL。

#include "mystdio.h"
#define MYFILE "log.txt"
int main()
{
  MY_FILE* fp = my_fopen(MYFILE,"w");
  if (fp==NULL)
  {
    return 1;
  }
  my_fclose(fp);
  return 0;
}


下面我们演示一下我们的文件操作成功与否:

84d88309f1364c0397de3a9e0de94c46.png


通过上图我们可以看到我们写的文件操作函数是成功的,只是有些功能我们还没有去完善,如果都完善了可以替代C语言库用我们自己的库。

下面我们将fwrite接口补充完整:

//我们今天返回的就是一次实际写入的字节数,我们就不返回个数了
size_t my_fwrite(const void* ptr,size_t size,size_t nmemb,MY_FILE* stream)
{
  //1.缓冲区如果已经满了,就直接写入
  if (stream->current==NUM)
  {
    my_fflush(stream);
  }
  //2.根据缓冲区剩余情况,进行数据拷贝即可
  size_t user_size = size*nmemb;
  size_t my_size = NUM-stream->current;
  size_t writen = 0;
  if (my_size>=user_size)
  {
    memcpy(stream->outputbuffer+stream->current,ptr,user_size);
    //3.更新计数器字段
    stream->current+=user_size;
    writen = user_size;
  }
  else 
  {
    memcpy(stream->outputbuffer+stream->current,ptr,my_size);
    stream->current+=my_size;
    writen = my_size;
  }
  //4.开始计划刷新
  if (stream->flags & BUFF_ALL)
  {
    if (stream->current==NUM)
    {
      my_fflush(stream);
    }
  }
  else if(stream->flags & BUFF_LINE)
  {
    if (stream->outputbuffer[stream->current-1]=='\n')
    {
      my_fflush(stream);
    }
  }
  else 
  {
  }
  return writen;
}


我们写入的第一件事就是判断缓冲区是否有空间,如果我们的缓冲区已经满了我们就直接把数据刷新出去。如果我们期望写入4096字节,但是缓冲区只有1024字节,所以最后缓冲区只能输出1024字节,所以下一步我们要根据缓冲区剩余情况进行数据拷贝即可。current是当前缓冲区有多少字符,并且current也是下一次缓冲区继续写入的下标。接下来我们计算用户传的缓冲区有多大,再计算我们自己的缓冲区还剩下多少,如果我们的剩余空间是大于用户的数据的,说明我们的空间是足够的,直接把用户的缓冲区的数据拷贝到我们自己的缓冲区即可,在这里要注意不能直接拷贝到stream->outbuffer,因为当前缓冲区很可能是有数据的我们不能覆盖,只能从current的位置继续拷贝,拷贝完成后记得更新我们缓冲区的位置,由于fwrite函数写入成功会返回当前写入多少字节,所以我们用一个变量记录当前写入了多少字节,如果我们当前的空间不足以容纳用户传来的空间,我们就只能拷贝我们剩余大小的空间,然后接下来我们计划开始刷新缓冲区,如果是全缓冲就等下标等于NUM的时候刷新,如果是行刷新我们直接判断current-1的位置是不是\n,如果是\n我们就进行行刷新。最后返回写入了多少字节就完成了,接下来我们完成刷新函数:

int my_fflush(MY_FILE* fp)
{
  assert(fp);
  write(fp->fd,fp->outputbuffer,fp->current);
  return 0;
}


首先判断fp是否为空指针,如果不是我们就进行刷新,刷新我们直接用write函数,第一个参数是文件描述符,第二个参数是缓冲区,第三个参数是写入多少的大小,总体就是向文件描述符中写入特定的缓冲区中的特定大小,接下来我们在main函数中试试我们的fwrite函数。

#include "mystdio.h"
#define MYFILE "log.txt"
#include <string.h>
#include <unistd.h>
int main()
{
  MY_FILE* fp = my_fopen(MYFILE,"w");
  if (fp==NULL)
  {
    return 1;
  }
  const char* str = "hello my fwrite";
  int cnt = 5;
  while (cnt)
  {
    char buffer[1024];
    snprintf(buffer,sizeof(buffer),"%s:%d\n",str,cnt--);
    size_t size = my_fwrite(buffer,strlen(buffer),1,fp);
    sleep(1);
    printf("当前成功写入:%lu个字节\n",size);
  }
  my_fclose(fp);
  return 0;
}


接下来我们运行一下看看效果:

acb6e45bc19c4891a92e1093bac87ccc.png


当我们运行后发现,其他的都没问题但是为什么多打印了那么多?因为我们每次打印后都要重新刷新一下缓冲区再进行打印,否则每次都带有之前旧的数据被打印出来,下面我们将缓冲区函数改一下:

int my_fflush(MY_FILE* fp)
{
  assert(fp);
  write(fp->fd,fp->outputbuffer,fp->current);
  //刷新后清理缓冲区
  fp->current = 0;
  return 0;
}

ee3ebac5fb3f4118b70e9a604e0f0070.png


通过上图我们可以看到这次的程序是成功完成。这就是我们模拟的文件操作,通过这样的实现我们更深刻的理解了系统文件操作。


区又有什么块的知识,下一篇文章我们将详细介绍文件的软硬链接。

目录
相关文章
|
5月前
|
存储 数据管理 Linux
区分Linux中.tar文件与.tar.gz文件的不同。
总之,".tar"文件提供了一种方便的文件整理方式,其归档但不压缩的特点适用于快速打包和解压,而".tar.gz"文件通过额外的压缩步骤,尽管处理时间更长,但可以减小文件尺寸,更适合于需要节约存储空间或进行文件传输的场景。用户在选择时应根据具体需求,考虑两种格式各自的优劣。
824 13
|
6月前
|
安全 Linux
Linux赋予文件000权限的恢复技巧
以上这些步骤就像是打开一扇锁住的门,步骤看似简单,但是背后却有着严格的逻辑和规则。切记,在任何时候,变更文件权限都要考虑安全性,不要无谓地放宽权限,那样可能
204 16
|
6月前
|
存储 Linux 数据处理
深入剖析Linux中一切即文件的哲学和重定向的机制
在计算机的奇妙世界中,Linux的这套哲学和机制减少了不同类型资源的处理方式,简化了抽象的概念,并蕴藏着强大的灵活性。就像变戏法一样,轻轻松松地在文件、程序与设备之间转换数据流,标准输入、输出、错误流就在指尖舞动,程序的交互和数据处理因此变得既高效又富有乐趣。
111 4
|
7月前
|
Ubuntu Linux
"unzip"命令解析:Linux下如何处理压缩文件。
总的来说,`unzip`命令是Linux系统下一款实用而方便的ZIP格式文件处理工具。本文通过简明扼要的方式,详细介绍了在各类Linux发行版上安装 `unzip`的方法,以及如何使用 `unzip`命令进行解压、查看和测试ZIP文件。希望本文章能为用户带来实际帮助,提高日常操作的效率。
1055 12
|
6月前
|
Linux
linux文件重命名命令
本指南介绍Linux文件重命名方法,包括单文件操作的`mv`命令和批量处理的`rename`命令。`mv`可简单更改文件名并保留扩展名,如`mv old_file.txt new_name.txt`;`rename`支持正则表达式,适用于复杂批量操作,如`rename &#39;s/2023/2024/&#39; *.log`。提供实用技巧如大小写转换、数字序列处理等,并提醒覆盖风险与版本差异,建议使用`-n`参数预览效果。
|
3月前
|
Linux 应用服务中间件 Shell
二、Linux文本处理与文件操作核心命令
熟悉了Linux的基本“行走”后,就该拿起真正的“工具”干活了。用grep这个“放大镜”在文件里搜索内容,用find这个“探测器”在系统中寻找文件,再用tar把东西打包带走。最关键的是要学会使用管道符|,它像一条流水线,能把这些命令串联起来,让简单工具组合出强大的功能,比如 ps -ef | grep 'nginx' 就能快速找出nginx进程。
476 1
二、Linux文本处理与文件操作核心命令
|
3月前
|
Linux
linux命令—stat
`stat` 是 Linux 系统中用于查看文件或文件系统详细状态信息的命令。相比 `ls -l`,它提供更全面的信息,包括文件大小、权限、所有者、时间戳(最后访问、修改、状态变更时间)、inode 号、设备信息等。其常用选项包括 `-f` 查看文件系统状态、`-t` 以简洁格式输出、`-L` 跟踪符号链接,以及 `-c` 或 `--format` 自定义输出格式。通过这些选项,用户可以灵活获取所需信息,适用于系统调试、权限检查、磁盘管理等场景。
328 137
|
3月前
|
安全 Ubuntu Unix
一、初识 Linux 与基本命令
玩转Linux命令行,就像探索一座新城市。首先要熟悉它的“地图”,也就是/根目录下/etc(放配置)、/home(住家)这些核心区域。然后掌握几个“生存口令”:用ls看周围,cd去别处,mkdir建新房,cp/mv搬东西,再用cat或tail看文件内容。最后,别忘了随时按Tab键,它能帮你自动补全命令和路径,是提高效率的第一神器。
766 57
|
2月前
|
存储 安全 Linux
Linux卡在emergency mode怎么办?xfs_repair 命令轻松解决
Linux虚拟机遇紧急模式?别慌!多因磁盘挂载失败。本文教你通过日志定位问题,用`xfs_repair`等工具修复文件系统,三步快速恢复。掌握查日志、修磁盘、验重启,轻松应对紧急模式,保障系统稳定运行。
553 2