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

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

☘写在前面☘

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


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


📝 c语言指针的使用

📝 linux的系统调用

📝 磁盘的组织方式?

📝 超级块存储哪些信息?

📝 inode存储哪些内容?

📝 位示图有啥用?

全文大约阅读时间: 30min


🧑🏻作者简介:一个从工业设计改行学嵌入式的年轻人

🔒资源下载:gitee仓库

✨联系方式:2201891280(QQ)


📔全文目录📔

一、基本要求介绍

🧭目标

🥙形式要求

🥪实现约束

二、解决方法思路

✍参考文章

🧇主体实现思路

三、linux系统调用简介

📃open & close

📃write & read

📃lseek

四、磁盘组织方式简介

文件访问的过程

五、主函数实现

提示信息的输出

类终端功能实现

六、文件功能定义

主要数据结构

创建文件系统

格式化

低级格式化

创建文件

删除文件

七、写在最后

一、基本要求介绍

🧭目标

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


🥙形式要求

基于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位示图和数据块的位示图。

文件访问的过程

当我们尝试访问一个文件\123\456.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 found\n",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

海报我都做好了-.-

相关文章
|
存储 C语言
|
存储 C语言
|
程序员 编译器 数据库
|
1月前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
62 23
|
1月前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
66 15
|
1月前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
60 24
|
1月前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
63 16
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
36 3
|
1月前
|
存储 算法 安全
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
19 2
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】回文数判定(头歌实践教学平台习题)【合集】
算术运算于 C 语言仿若精密 “齿轮组”,驱动着数值处理流程。编写函数求区间[100,500]中所有的回文数,要求每行打印10个数。根据提示在右侧编辑器Begin--End之间的区域内补充必要的代码。如果操作数是浮点数,在 C 语言中是不允许直接进行。的结果是 -1,因为 -7 除以 3 商为 -2,余数为 -1;注意:每一个数据输出格式为 printf("%4d", i);的结果是 1,因为 7 除以 -3 商为 -2,余数为 1。取余运算要求两个操作数必须是整数类型,包括。开始你的任务吧,祝你成功!
51 1

热门文章

最新文章