c语言file_FAT12模拟-C语言读取

6ef62c9d695e8f380b7c272b9d34219d.png

介绍

之前详细介绍了FAT12文件系统

其中涉及到过多位运算的时间处理问题,我在另一篇文章中进行了分析。并且附有C语言代码。

DOS的镜像就是FAT12文件系统,在网络上有freedos,操作跟DOS大同小异,可以免费下载

DOS原版的镜像我放在百度云里面了,提取码far8

视频演示

144de5652af160ea3a3abe061b56ed1c.png
DOS仿真演示https://www.zhihu.com/video/1228831455298027520

友情提示:做实验可以先把代码copy下来,把代码和DOS的镜像放在同一目录下运行一下,感受DOS的快感。不然几千行代码很劝退的。代码,不要在wsl运行。

实现的功能

虽然说是仿真FAT12文件系统,实际上把很多DOS的功能都实现出来了。说白了也就是DOS的C语言仿真。这篇文章介绍读取FAT12文件系统的内容,不对磁盘进行修改。

仿真,实现了如下命令(只是很基础的命令,不能加参数)

c67b4ff607f55b21b12da77ef4b6530d.png

基本结构

首先整个操作系统是用C语言写成,基本的思路是,把一个函数当作一个程序。只是仿真文件系统,打开文件表、活动文件表……等数据结构统统没有。

基本的思路是main()调用boost_system()函数。boost_system()函数再调用cmd()函数。cmd()函数就读取各个命令,然后调用对应指令的函数,比如directory(), change_directory(), type()

下面是一个示意图

17b37208a96c1eddf06e2e6d6501bb27.png

仿真的主干过程就是写各种cmd命令行中的函数,毕竟DOS系统就是通过命令行执行各种程序。

启动操作系统的相关操作

在方阵程序中启动操作系统就是:

(1)打开FAT软盘

(2)读取MBR区的数据

(3)调用cmd程序

打开FAT软盘

打开FAT软盘就是是用C语言打开二进制文件的函数,赋值给文件指针。

FILE *fat12_file = fopen(disk, "rb");

由于是打开的二进制文件,因此在读写过程中涉及到很多的调整文件指针位置的操作,涉及到函数fseek、fread、fwrite。

int fseek(FILE *fp, long offset, int origin);
unsigned fread (void *buf, unsigned size, unsigned count, FILE* fp);
unsigned fwrite (const void *bufAunsigned size,unsigned count,FILE* fp);

fseek作用是调整文件指针的位置。第一个参数为文件指针,第二个参数是偏移的字节数,第三个参数是开始偏移的地址:SEEK_SET表示从文件头开始(SEEK_END表示从文件末尾开始,SEEK_CUR表示从当前位置开始)。

fread的作用是读取一定的字节数到buf中。第一个参数是存储读取数据的指针,第二个参数指定了一次读取size个字节,第三个参数指定一共读取count次,第四个参数为文件指针。

fwrite的作用是写入一定字节到文件中。参数的含义和fread相似。

更多的文件操作细节见C语言文件操作完全攻略

读取MBR数据

首先要定义结构header,按顺序储存MBR区的各种数据,不论仿真程序是否使用到。我们可以用下面一条指令,将所有的MBR数据存储在对应的变量中。

fread(&fat_head, sizeof(char), BYTE_PER_BLOCK, fat12_file);

fat_head就是按顺序声明好的结构,结构体的定义如下。要使用#pragma(1)取消对齐,这样才能让MBR区的数据对号入座。

typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
#pragma pack (1)
//数据段
struct header
{
    //跳转指令
    char jmp[3];
    // 厂商名 yxros1.0
    char BS_OEMName[8];
    // 每个扇区字节数 512
    u16 BPB_BytesPerSec;
    // 每簇扇区数 1
    u8 BPB_SecPerClus;
    //boot引导占扇区数 1
    u16 BPB_ResvdSecCnt;
    //一共有几个FAT表 2
    u8 BPB_NumFATs;
    //根目录文件最大数  0xe0 = 224
    u16 BPB_RootEntCnt;
    //扇区总数  0xb40 = 2880
    u16 BPB_TotSec16;
    //介质描述  0xf0
    u8 BPB_Media;
    //每个FAT表占扇区数 9
    u16 BPB_FATSz16;
    // 每个磁道占扇区数 0x12
    u16 BPB_SecPerTrk;
    // 磁头数   0x2
    u16 BPB_NumHeads;
    // 隐藏扇区数 0
    u32 BPB_HiddSec;
    // 如果BPB_TotSec16=0,则由这里给出扇区数 0
    u32 BPB_TotSec32;
    // INT 13H的驱动号 0
    u8 BS_DrvNum;
    //保留,未用    0
    u8 BS_Reserved1;
    //扩展引导标记  0x29
    u8 BS_BootSig;
    // 卷序列号 0
    u32 BS_VollD;
    // 卷标 'yxr620'
    u8 BS_VolLab[11];
    // 文件系统类型 'FAT12'
    u8 BS_FileSysType[8];
    //引导代码
    char code[448];
    //结束标志
    char end_point[2];

// 防止各种对齐现象,取消对齐,读写比较方便
}__attribute__((packed)) fat_head;

调用cmd

直接调用cmd函数即可,cmd函数的定义也附在下面:

void cmd(FILE *fat12_file);

cmd程序

cmd程序读取每一行命令,然后根据不同的命令调用不同的函数,来执行相应指令。

分析指令

直接使用下面函数,把每种指令转化成对应的操作码,然后cmd直接根据不同的操作码调用不同的指令。

#define _DIR 1
#define _CD 2
#define _SHUT 3
#define _TYPE 4
#define _COPY 5
#define _DEL 6
#define _MKDIR 7
#define _RMDIR 8

int decode_operation(char *operation)
{
    // 将小写指令转成大写指令
    small2big(operation);
    // printf("%s", operation);
    if(!strcmp(operation, "DIR")) return _DIR;
    else if(!strcmp(operation, "CD")) return _CD;
    else if(!strcmp(operation, "SHUT")) return _SHUT;
    else if(!strcmp(operation, "TYPE")) return _TYPE;
    else if(!strcmp(operation, "COPY")) return _COPY;
    else if(!strcmp(operation, "DEL")) return _DEL;
    else if(!strcmp(operation, "MKDIR")) return _MKDIR;
    else if(!strcmp(operation, "RMDIR")) return _RMDIR;
    return 0;
}

设计指令的基本原则

除了cd指令,其他指令都不能改变当前的路径,这些操作都使用temp_path来表示真正操作的路径。temp_path初始化为current_path,绝对路径、相对路径对路径的更改都发生在temp_path上。

指令后跟的路径情况非常复杂。下面函数专门分离路径名称并且将temp_path调整到对应的路径上去。

第一个参数为当前路径的指针;

第二个参数为储存FAT12文件系统的文件指针;

第三个参数可能是要操作的目录名称,也可能是绝对路径。

// 用来给direcotry等函数分离绝对路径,也可以分离相对路径
void split_path_direcotry(struct PATH *temp_path_ptr, FILE *fat12_file,
    char *file_name);

具体说一下struct PATH。

PATH_Arr:从根目录到当前目录每个目录的名称;

PATH_Length:当前目录的深度,根目录为1;

BASE:当前目录起始地址(在FAT软盘上的地址)

SIZE:该目录一共占有的字节数,为512字节的整倍数

//保存当前路径的结构
struct PATH
{
    // 储存路径的数组
    char PATH_Arr[100][11];
    // 储存当前已经有多少个路径,根目录为A,长度为1
    int PATH_Length;
    // 首扇区的地址
    int BASE;
    // 该目录项大小(字节为单位)
    int SIZE;
};

dir指令

dir指令可以使用绝对路径和相对路径,也可以直接用dir指令输出当前目录的文件信息。下面是该指令对应函数的签名。

第一个参数是存储当前路径信息的结构体指针;

第二个参数为储存FAT12文件系统的文件指针。

// 进行DIR操作
void directory(struct PATH *current_path_ptr, FILE *fat12_file);

首先使用split_path_directory函数将temp_path调整到对应的目录中,接下来就可以循环输出。temp_path指向的目录所有文件以下面的格式输出。可以看到,输出跟DOS基本一样,跟Windows的命令也基本一样,就是日期等信息被Windows的命令行放在了前面。

d536c9c8e2e690c663ac916f38b6f818.png

循环遍历每一个文件目录项的过程如下

int base = temp_path.BASE;


    for(i = 0; i < temp_path.SIZE / 32; ++i)
    {
        struct ROOT_ENTRY temp_entry;
        // 将文件指针,放在根目录开头处
        fseek(fat12_file, base, SEEK_SET);
        //读取一个目录项
        fread(&temp_entry, 1, 32, fat12_file);
        base += 32; 

        // 不用输出空目录项,或者已删除的文件
        if(temp_entry.DIR_Name[0] == '0' || temp_entry.DIR_Name[0] == 0xe5)
            continue;
        // 不用输出隐藏文件
        if(temp_entry.DIR_Attr == 0x27) continue;
        // 输出文件名和扩展名
        int j;
        for (j = 0; j < 12; ++j)
        printf("%c", temp_entry.DIR_Name[j]);

        // 输出文件大小或者文件夹<dir>
        if(temp_entry.DIR_Attr == 0x10) printf("%-20s ", "<DIR>");
        else printf("%20d", temp_entry.DIR_FileSize);
            
        //输出日期和时间
        print_date(temp_entry.DIR_WriDate);
        print_time(temp_entry.DIR_WriTime);
        printf("n");
    }

cd指令

cd指令的全称为change_directory,可以使用绝对路径和相对路径。该操作的函数原型如下:

第一个参数是当前目录的指针,也就是要改变的量;

第二个参数是FAT12软盘的文件指针;

第三个参数是要进入的路径,可以是相对路径也可以是绝对路径。

// 进入目录操作
// 这个函数本身只是改变current_path_ptr的函数
// 很多其他操作都可以使用这个函数
void change_directory(struct PATH *current_path_ptr,
    FILE *fat12_file, char *subdirectory);

type指令

type指令可使用相对路径,也可用绝对路径。type指令的函数签名如下:

第一、二个参数和上面的命令一样;

第三个参数是文件名,或者文件名和绝对路径的组合。

// type指令,输出该文件的所有内容
void type_command(struct PATH *current_path_ptr, FILE *faT12_file,
    char *file_name);
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值