介绍
之前详细介绍了FAT12文件系统
其中涉及到过多位运算的时间处理问题,我在另一篇文章中进行了分析。并且附有C语言代码。
DOS的镜像就是FAT12文件系统,在网络上有freedos,操作跟DOS大同小异,可以免费下载
DOS原版的镜像我放在百度云里面了,提取码far8
视频演示
友情提示:做实验可以先把代码copy下来,把代码和DOS的镜像放在同一目录下运行一下,感受DOS的快感。不然几千行代码很劝退的。代码,不要在wsl运行。
实现的功能
虽然说是仿真FAT12文件系统,实际上把很多DOS的功能都实现出来了。说白了也就是DOS的C语言仿真。这篇文章介绍读取FAT12文件系统的内容,不对磁盘进行修改。
仿真,实现了如下命令(只是很基础的命令,不能加参数)
基本结构
首先整个操作系统是用C语言写成,基本的思路是,把一个函数当作一个程序。只是仿真文件系统,打开文件表、活动文件表……等数据结构统统没有。
基本的思路是main()调用boost_system()函数。boost_system()函数再调用cmd()函数。cmd()函数就读取各个命令,然后调用对应指令的函数,比如directory(), change_directory(), type()
下面是一个示意图
仿真的主干过程就是写各种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的命令行放在了前面。
循环遍历每一个文件目录项的过程如下
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);