【C语言有什么用?①】从零开始撸一个用户态模拟文件系统

在这里插入图片描述

☘写在前面☘
学习一个语言最好的方法是做一个小项目,这个项目不需要多么复杂,但是一定能激发你的学习兴趣。让我们话不多说,开始吧

本文将带你手撸一个磁盘组织方式的模拟,你将学到

  • ? c语言指针的使用
  • ? linux的系统调用
  • ? 磁盘的组织方式?
  • ? 超级块存储哪些信息?
  • ? inode存储哪些内容?
  • ? 位示图有啥用?

全文大约阅读时间: 30min

??作者简介:一个从工业设计改行学嵌入式的年轻人
?资源下载:gitee仓库
✨联系方式:2201891280(QQ)


一、基本要求介绍

?目标

完成用户态环境下的磁盘模拟功能,提供磁盘基本信息查询与格式化功能。

?形式要求

基于C/C++,完成上述完整功能

?实现约束

利用一个大文件(128MB)来模拟磁盘块设备,基于固定分片大小实现文件系统的超级块区、inode节点区、数据分片区的管理,具备磁盘格式化、文件系统查询(如:fdisk -l)功能,支持文件的inode节点和对应数据分片的分配、回收。

注:此为楼主的一次linux作业,鉴于还没到提交作业的截至日期,所以如果你 借鉴 此篇文章,请修改一定量以防大家都没分数0.0


二、解决方法思路

拿到一道要求时,最重要的是学会找资源,找到一些思路,然后自己去做实现。
所以我平时很喜欢问思路的同学,但是知道思路还不知道代码怎么写呢,我就觉得这是态度问题了,希望大家还是只借鉴思路,千万别直接复制粘贴,这对自己一点好处都没有

✍参考文章

  1. 文件系统简单模拟
    这篇文章实现的功能非常复杂,所以我只是借鉴了主程序的写法,主要是给自己做一个主程序的基础架构。

  2. ext2文件系统详解
    这篇文件非常详细的介绍了ext2文件系统的实现方式,这是已有的真实文件系统的结构,我们可以对其进行适当的精简,做出我们的想要的功能。

?主体实现思路

  • 模块框架设计
  • 分片大小:4KB 则一共有32K(128M/4K)个分片
  • 保留区:三个分片,分别为超级块,inode位示图,数据区位示图
  • inode节点:128B,每个分片有32个inode节点,32K个分片需要1K个分片保存inode节点信息,分配inode节点区为4MB。

对应的分配方式如下图
在这里插入图片描述

  • 主程序设计

做一个类似于终端的执行方式,输入相应的命令执行相应的操作。
基本的效果如下
在这里插入图片描述


三、linux系统调用简介

为了细化上面提到一些细节实现,我们需要了解linux的对文件的系统调用。这部分有点枯燥,毕竟都是系统调用函数,但是也不多,熟悉这部分的同学直接跳过啦0.0

?open & close

int open (const char *name, int flags, mode_t mode);
int close(int fd);

上面对应的就是打开和open和close系统函数定义

其中open有三个传入参数

  • 第一个就很好理解就是文件名称
  • 第二个是flags表示打开方式,这次用到的只有三个 O_CREAT、O_WRONLY、
    O_RDONLY分别表示如果不存在就创建,只读和只写。其中如果我想获取读写权限可以用 O_WRONLY|O_RDONLY 来表示。
  • 第三个参数我是再第二个参数有O_CREAT时候使用,是定义文件的权限的。因为是为了学习,我就简单的定义的所有人可读可写可执行S_IRWXO|S_IRWXG|S_IRWXU
  • 返回值是文件描述符,表示在文件打卡表项的位置(在内存中,保存着打开文件的位置信息),是系统为我们维护的方便使用的一个符号。如果返回-1表示打开失败

相对来说close就简单很多

  • 只有一个传入参数 就是open的返回值文件描述符,是一个大于等于0的整数可以看下图
    在这里插入图片描述

?write & read

size_t read(int fd, void *buf, size_t count);
size_t write(int filedes,const char* buf,size_t nbytes);

这两个函数很类似,只不过功能一个是读一个是写,我就放在一起讲了。都有三个参数

  • 第一个参数是一个文件打开表项,其实就是我们刚才说的open返回值。
  • 第二个是一个指针,如果忽略他的类型可以理解为一片内存空间,其中read就是往这片空间写入数据,wirte就是将对应的空间的数据写入文件。
  • 第三个数据是读或者写的数据大小,单位是字节。千万别超过第二个参数中指针指向的数据大小,不然会写入未知内存,造成程序未知错误,建议直接就sizeof(第二个元素)

这两个函数可以说对于c语言指针的使用极度舒适,直接操作内存。
需要注意一个点就是每次读写系统会将我们的文件偏移量往后推移读到元素个数

?lseek

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

上面说到每次read和wirte都会推进文件偏移量。所以为了改变文件偏移量,我们就有了这个函数。

  • 第一个参数还是文件打开表项,就是open返回那个
  • 第二个参数就是偏移量。单位是字节,可前可后,所以就可以正也可以负值。
  • 第三个参数是相哪里的位置 有三个可选项SEEK_SET、SEEK_CUR、SEEK_END,分别表示相对文件头、相对当前位置以及相对文件末尾。我们只需要前两个就好了。

上面就是所有需要的基本系统调用函数,接下来介绍一些烧脑的文件结构。


四、磁盘组织方式简介

文件组织方式
图片是一个常见的文件系统的图片,我们需要实现的只有单分区就好了,不那么复杂。
简化的文件系统信息
这张图就是一个最简单的文件系统,但是作业要求我们有inode和数据块的分配回收,所以我们得加上inode位示图和数据块的位示图。

文件访问的过程

当我们尝试访问一个文件123456.txt的时候基本过程是
在这里插入图片描述
从这个过程中我们知道

  • 超级块(一般是第一块)
    主要保存文件系统的基本信息,数据分片的大小、inode节点大小和多少,根目录的位置等
  • inode节点
    **文件的基本信息,**文件大小、文件类型、数据块索引
  • 目录项
    保存目录信息 主要内容是 文件名、inode节点号

五、主函数实现

主函数主要是为了打开文件系统以及给一定的提示信息。

提示信息的输出

void help(){
    printf("****************************************************n");
    printf("*欢迎来到xinglei 的文件系统!!!                   n");
    printf("*主要的功能介绍                                     n");
    printf("*    0.创建系统  :create_filesystem                 n");
    printf("*    1.格式化    :init                              n");
    printf("*    2.低级格式化:low_init                          n");    
    printf("*    3.查看信息  :info                              n");   
    printf("*    4.创建文件  :mkfile                            n");
    printf("*    5.查看位示图:weishitu                          n");
    printf("*    6.删除文件  :delfile                           n");
    printf("*    8.帮助信息  :help                              n");
    printf("*    9.显示文件  :ls                                n");   
    printf("*    10.退出     :quit                              n"); 
    printf("****************************************************n");
}

主要就是打印一些提示信息,方便用户使用这个系统。

类终端功能实现

char* command[] = {"create_filesystem","init","low_init","info","mkfile","weishitu","delfile","deldir","help","ls","quit"};
int commandnum = sizeof(command)/sizeof(char *);
char path[100] = "file_system";//文件系统
while(1){
        printf("%s$ ",path);
        scanf("%s",com);
        choice = i;
        for(i=0; i<commandnum; ++i)
                    if(strcmp(com,command[i])==0)
                        break;
        choice = i;
        if(Disk == -1 && (!(choice == 0|| choice == 10))){//未创建文件只能退出或者创建文件
            printf("文件系统未创建,请创建文件系统。");
            continue;
        }
        int a;
        read(Disk,&a,sizeof(int));
        if(Disk != -1 &&a == 0 && !(choice == 1 || choice == 2)){
            printf("未格式化,请格式化.");
            lseek(Disk,-sizeof(int),SEEK_CUR);
            continue;
        }
        lseek(Disk,-sizeof(int),SEEK_CUR);
        switch(choice){
            case 0://创建系统
                create_filesystem();
                break;
            case 1://格式化
                init(1);
                break;
            case 2://低级格式化
                low_init();
                break;
            case 3://查看信息
                info();
                break;
            case 4://创建文件
                printf("输入你要创建的文件名: ");
                char filename[20];
                scanf("%s",filename);
                create_file(filename);
                break;
            case 5://查看位示图
                weishi();
                break;
            case 6://删除文件
                printf("请输入你要删除的文件:");
                char delname[20];
                scanf("%s",delname);
                delfile(delname);
                break;
            case 8://帮助信息
                help();
                break;
            case 9://显示文件
                ls();
                break;
            case 10:    //退出系统
                quit = 1;
                break;
            default:
                printf("%s command not foundn",com);
        }

很显然,我定义了一个while1,然后根据输入命令的比较结果来选择执行哪个分支,默认就是重新回到开头。在程序运行的时候我已经做了打开文件的操作,因为我第超级块的前4个字节的数据是固定的不为0,所以我可以用它来判断是否已经格式化,然后其它的就是跳转到相应的功能中就好了0.0

这只是基础程序框架,我们其它功能使用另外一个文件来实现


六、文件功能定义

上面我们已经实现了基础的程序功能,这部分我们要深入细节,完成每一个小功能的实现。

主要数据结构

在这里插入图片描述
✨超级块
就是像刚才说的保存基础信息

typedef struct{
    int inodes_count,   blocks_count;               //超级块和indoe总数
    int free_inodes_count,  free_blocks_count;          //未使用block和inode量
    short block_size,   inode_size;             //block和indoe大小 B
    int first_data_block,   first_inode_block;          //第一块数据块第一块inode节点位置
    int inode_wei,      block_wei;              //位示图所在块号
    short inodes_per_block;                     //每片中的inode数目
    short frag_size;                        //每片大小
    int root_inode;                         //根节点inode号
    int size;                           //总大小B
}chao;      //超级块定义

✨inode节点
这应该是最简陋的inode节点了把?

typedef struct{
    int i_mode;     //文件类型,1表示文件,0文件表示目录项
    int i_size;     //文件的大小单位为B
    int i_blocks;       //文件所占块数 
    int i_block[15];    //索引节点 12个直接索引 1个一级索引 2个二级索引
}inode;     //inode节点定义

✨目录项
目录项不用特别复杂

typedef struct{
    char name[28];
    int inode;
}mulu;      //目录项定义

✨位示图
就简单的一个位就好了。

unsigned int weishitu[1024];//位示图定义

创建文件系统

在这里插入图片描述
这个功能就很简单,就创建一个128MB的文件就好了。注意判断是否已经存在文件避免覆盖。
我用了一个4K的数组循环32K次写入来达到128MB0.0

void create_filesystem(){
    if(Disk != -1){
        printf("已经存在文件系统n");
        return;
    }
    Disk = open(DISK,O_CREAT|O_WRONLY,S_IRWXO|S_IRWXG|S_IRWXU);
    int a[1024] = {0};
    for(int i = 0;i < 1<<15;i++) write(Disk,(void *)a,sizeof(a));//创建128MB文件
    printf("创建文件系统成功n");
}

格式化

在这里插入图片描述
这部分就亿点点复杂需要做的事情有一些多。
就是重写超级块、重写位示图、创建根节点信息。
代码就不放了 因为太长了-.-
可以在仓库中找到,我相信你们看得懂

低级格式化

在这里插入图片描述
其实我们平时也有用到低级格式化,其实就将所有数据置为0。然后再执行高级格式化就好了。

void low_init(){
    int temp;
    read(Disk,&temp,sizeof(int));
    if(temp){
        printf("此操作会清空磁盘,且不可恢复,确定?Y/N  ");
        char s[10];
        while(scanf("%s",s) != EOF && !(!strcmp(s,"Y") || !strcmp(s,"N")))
            printf("请输入正确的字符");
        if(strcmp(s,"N") == 0){
            printf("用户取消n");
            return ;
        }
    }
    close(Disk);
    Disk = open(DISK,O_WRONLY);
    int a[1024] = {0};
    for(int i = 0;i < 1<<15;i++) write(Disk,a,sizeof(a));//全部位置置0
    close(Disk);
    Disk = open(DISK,O_RDONLY);
    printf("低级格式化完成n");
    init(0);
}

这里其实和创建文件很类似,但是要注意判断是否已有系统给与对应提示。

创建文件

在这里插入图片描述
这里已经简化了很多了,只做了创建文件,并不写入数据,所以就是找空间分配空间,写入就好了。
但是要实现找inode节点、找数据节点等多个方法,所以请看源码。。

删除文件

在这里插入图片描述
相比较而言删除文件就简单很多直接将inode和数据区进行回收就好了。
好了,由于很多细节放代码过于复杂今天就写到这里了。


七、写在最后

这只是这次作业的的一部分,有点难懂,而且做成的也只能在电脑上跑跑看而已,第二部分是一个多线程词频统计工具,可以对某个文件目录所有英文单词的出现频率进行统计打印,就有用很多。
如果点赞破百的话,我交作业前(11.30)肯定能更新出来0.0
海报我都做好了-.-
请添加图片描述

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>