06_实验六_读文件和写文件
读文件和写文件
实验目的
- 了解在EOS应用程序中读文件和写文件的基本方法。
- 通过为FAT12文件系统添加写文件功能,加深对FAT12文件系统和磁盘存储器管理原理的理解。
文件系统驱动程序的作用
用户对文件的读写请求转换为 对磁盘扇区的读写请求,并负责对磁盘扇区进行管理。
实验内容
编写代码调用 EOS API 函数读取文件中的数据
按照下面的步骤查看 EOS 应用程序读取文件中数据的执行结果:
-
使用 OS Lab 打开在本实验 3.1 中创建的 EOS 应用程序项目。
-
在“项目管理器”窗口中双击 Floppy.img 文件,使用 FloppyImageEditor 工具打开此软盘镜像。
-
将本实验文件夹中的 a.txt 文件添加到软盘镜像的根目录中。打开 a.txt 文件查看其中的数据。
-
点击 FloppyImageEditor 工具栏上的保存按钮,关闭该工具。
-
使用 FileApp.c 文件中的源代码替换 EOS 应用程序项目中 EOSApp.c 文件内的源代码。
-
按 F7 生成修改后的 EOS 应用程序项目。
-
按 F5 启动调试。自动运行 EOS 应用程序 EOSApp.exe 时,会由于输入的命令行参数无效而失败。
-
在 EOS 控制台中输入命令“A:\EOSApp.exe A:\a.txt”后按回车,EOSApp.exe 会读取 a.txt 文
件中的内容并显示在屏幕上,如图
- 结束此次调试。
// 启动调试 EOS 应用程序前要特别注意下面的问题:
//
// 1、如果要在调试应用程序时能够调试进入内核并显示对应的源码,
// 必须使用 EOS 核心项目编译生成完全版本的 SDK 文件夹,然
// 后使用此文件夹覆盖应用程序项目中的 SDK 文件夹,并且 EOS
// 核心项目在磁盘上的位置不能改变。
//
调试 FAT12 文件系统的读文件功能
为FAT12文件系统添加写文件功能
由于写文件功能会涉及到为文件分配新的簇、修改文件大小等问题,所以这里首先完成一个最简单的情况:向一个空文件中写入数个字节的数据。
- 使用 OS Lab 打开本实验 3.1 中创建的 EOS 内核项目。
- 从“项目管理器”窗口中打开源文件 io/driver/fat12.c,目前 fat12.c 中的函数 FatWriteFile(第 824 行)为空。
- 将本实验文件夹中的FatWriteFile.c文件拖动到OS Lab窗口中打开,使用该文件中FatWriteFile函数的函数体替换 fat12.c 文件中 FatWriteFile 函数的函数体。
- 在“项目管理器”窗口中双击 Floppy.img 文件,使用 FloppyImageEditor 工具打开此软盘镜像。
- 打开本实验 3.1 中创建的 EOS 应用程序项目文件夹,将 Release 文件夹中的 EOSApp.exe(没有调试信息)添加到软盘镜像中。
- 将本实验文件夹中的 a.txt、b.txt、c.txt 和 d.txt 文件添加到软盘镜像中。
- 点击 FloppyImageEditor 工具栏上的保存按钮,关闭该工具。
- 按 F7 生成修改后的 EOS 内核项目。注意,要使用 Debug 配置。
- 按 F5 启动调试。
实验步骤
EOS中FAT12文件系统相关源代码分析
分析EOS中FAT12文件系统的相关源代码,简要说明EOS实现FAT12文件系统的方法,包括主要数据结构与文件基本操作的实现等。
文件系统是建立在磁盘等块设备之上的一个逻辑层,读写文件时,文件系统驱动会将文件读写请求转换为对磁盘扇区的读写请求,最终由磁盘驱动程序完成对磁盘扇区的读写。
EOS为应用程序提供了一组可以操作文件的API函数,包括打开文件(CreateFile)、关闭文件(CloseHandle)、读文件(ReadFile)和写文件(WriteFile)
打开文件时使用的CreateFile定义如下
EOSAPI HANDLE CreateFile(
IN PCSTR FileName,
IN ULONG DesiredAccess,
IN ULONG ShareMode,
IN ULONG CreationDisposition,
IN ULONG FlagsAndAttributes
)
该函数被调用后会依次调用FatCreate、FatOpenExistingFile、FatOpenFile和FatOpenFileDirectory。
CloseHandle函数定义如下
EOSAPI BOOL CloseHandle(
IN HANDLE Handle)
该函数被调用后会依次调用FatClose、FatCloseFile。
读文件函数ReadFile定义如下
EOSAPI BOOL ReadFile(
IN HANDLE Handle,
OUT PVOID Buffer,
IN ULONG NumberOfBytesToRead,
OUT PULONG NumberOfBytesRead)
该函数被调用后会依次调用ObRead、IopReadFileObject、FatRead和FatReadFile。
写文件函数ReadFile定义如下
EOSAPI BOOL WriteFile(
IN HANDLE Handle,
IN PVOID Buffer,
IN ULONG NumberOfBytesToWrite,
OUT PULONG NumberOfBytesWritten)
该函数被调用后会依次调用ObWrite、IopWriteFileObject、FatWrite和FatWriteFile。
EOS中FAT12文件系统读文件过程的跟踪
简要说明在本部分实验过程中完成的主要工作,包括对读文件的跟踪等,总结EOS中读文件的实现方法
替换EOSApp.c为学生包中的FileApp.c的内容,随后将a.txt复制到img镜像中,生成项目并调试输入A:\eosapp.exe A:\a.txt,得到结果如下
结束调试,将fat12.c拖入IDE窗口,在EOSApp.c文件中的ReadFile函数代码行添加断点,开始调试,输入A:\EOSApp.exe A:\a.txt,在fat12.c文件中FatReadFile函数的开始处添加一个断点。随后继续运行调试,停下后对Vcb和File进行监视,得到以下结果
同时观察到offset值为0,BytesToRead为256。
打开调用堆栈监视流程
继续单步调试观察相关变量变化,最后观察到虚拟机正常输出
为EOS的FAT12文件系统添加写文件功能
给出实现方法的简要描述、源代码、测试及结果等
首先导入学生包中的代码并测试
观察到能正常实现文件读写
修改代码使其支持跨扇区写文件,实现思路为将写入数的数据分为三部分:头扇区、中间扇区和尾扇区,在写入头扇区和尾扇区的中间,插入一个循环,用于写入中间跨越的多个中间扇区,其特点为均是写入满扇区,写入的数据均为512个字节,在此之前计算出跨越的扇区个数即可。
代码及测试如下
STATUS
FatWriteFile(
IN PVCB Vcb,
IN PFCB File,
IN ULONG Offset,
IN ULONG BytesToWrite,
IN PVOID Buffer,
OUT PULONG BytesWriten
)
{
STATUS Status;
// 由于在将新分配的簇插入簇链尾部时,必须知道前一个簇的簇号,
// 所以定义了“前一个簇号”和“当前簇号”两个变量。
USHORT PrevClusterNum, CurrentClusterNum;
USHORT NewClusterNum;
ULONG ClusterIndex;
ULONG FirstSectorOfCluster;
ULONG OffsetInSector;
ULONG i;
// 写入的起始位置不能超出文件大小(并不影响增加文件大小或增加簇,想想原因?)
if (Offset > File->FileSize)
return STATUS_SUCCESS;
// 根据簇的大小,计算写入的起始位置在簇链的第几个簇中(从 0 开始计数)
ClusterIndex = Offset / FatBytesPerCluster(&Vcb->Bpb);
//
// 计算起始地址在簇中的第几个字节
ULONG BytesNumOfCluster = Offset - ClusterIndex * FatBytesPerCluster(&Vcb->Bpb);
ULONG NeedSplit; // 分割标志
ULONG ByteFommer = BytesToWrite; // 前一半需要写入的字节
ULONGByteLatter = 0; // 后一半需要写入的字节
ULONG SpanOfSector = 0; // 跨过的扇区数
PCHAR FrontBuffer; // 前
PCHAR MidBuffer; // 缓冲区 中
PCHAR LatterBuffer; // 后
// 如果写入的内容没有跨扇区,则无需切分
if((512 - BytesNumOfCluster)>= BytesToWrite){
NeedSplit = 0;
}
//否则要分成两半
else{
NeedSplit = 1;
ByteFommer = 512 - BytesNumOfCluster;
NumOfLatterBuffer = (BytesToWrite - ByteFommer) % 512;
SpanOfSector = (BytesToWrite - ByteFommer)/512;
}
if(NeedSplit == 0){
FrontBuffer=Buffer;
}
else{
//划分缓存区
FrontBuffer = Buffer;
MidBuffer = &Buffer[ByteFommer];
LatterBuffer = &Buffer[ByteFommer + SpanOfSector * 512];
}
// 分为三个部分写入,先写入头一个扇区,然后循环写入中间满扇区(均是512字节),最后写入尾扇区
// 写入头扇区
// 顺着簇链向后查找写入的起始位置所在簇的簇号。
PrevClusterNum = 0;
CurrentClusterNum = File->FirstCluster;
for (i = ClusterIndex; i > 0; i--) {
PrevClusterNum = CurrentClusterNum;
CurrentClusterNum = FatGetFatEntryValue(Vcb, PrevClusterNum);
}
// 如果写入的起始位置还没有对应的簇,就增加簇
if (0 == CurrentClusterNum || CurrentClusterNum >= 0xFF8) {
// 为文件分配一个空闲簇
FatAllocateOneCluster(Vcb, &NewClusterNum);
// 将新分配的簇安装到簇链中
if (0 == File->FirstCluster)
File->FirstCluster = NewClusterNum;
else
FatSetFatEntryValue(Vcb, PrevClusterNum, NewClusterNum);
CurrentClusterNum = NewClusterNum;
}
// 计算当前簇的第一个扇区的扇区号。簇从 2 开始计数。
FirstSectorOfCluster = Vcb->FirstDataSector + (CurrentClusterNum - 2) * Vcb->Bpb.SectorsPerCluster;
// 计算写位置在扇区内的字节偏移。
OffsetInSector = Offset % Vcb->Bpb.BytesPerSector;
// 为了简单,暂时只处理一个簇包含一个扇区的情况。
// 并且只处理写入的数据在一个扇区范围内的情况。
Status = IopReadWriteSector( Vcb->DiskDevice,
FirstSectorOfCluster,
OffsetInSector,
(PCHAR)FrontBuffer,
ByteFommer,
FALSE );
if (!EOS_SUCCESS(Status))
return Status;
// 如果文件长度增加了则必须修改文件的长度。
if (Offset + BytesToWrite > File->FileSize) {
File->FileSize = Offset + BytesToWrite;
// 如果是数据文件则需要同步修改文件在磁盘上对应的 DIRENT 结构
// 体。目录文件的 DIRENT 结构体中的 FileSize 永远为 0,无需修改。
if (!File->AttrDirectory)
FatWriteDirEntry(Vcb, File);
}
// 循环写入中间满扇区
Offset += ByteFommer;
ULONG k = SpanOfSector;
while(k > 0){
// 顺着簇链向后查找写入的起始位置所在簇的簇号。
PrevClusterNum = 0;
CurrentClusterNum = File->FirstCluster;
// 根据簇的大小,计算写入的起始位置在簇链的第几个簇中(从 0 开始计数)
ClusterIndex = (Offset) / FatBytesPerCluster(&Vcb->Bpb);
for (i = ClusterIndex; i > 0; i--) {
PrevClusterNum = CurrentClusterNum;
CurrentClusterNum = FatGetFatEntryValue(Vcb, PrevClusterNum);
//CurrentClusterNum = FatGetFatEntryValue(Vcb,CurrentClusterNum);
}
// 如果写入的起始位置还没有对应的簇,就增加簇
if (0 == CurrentClusterNum || CurrentClusterNum >= 0xFF8) {
// 为文件分配一个空闲簇
FatAllocateOneCluster(Vcb, &NewClusterNum);
// 将新分配的簇安装到簇链中
if (0 == File->FirstCluster)
File->FirstCluster = NewClusterNum;
else
FatSetFatEntryValue(Vcb, PrevClusterNum, NewClusterNum);
CurrentClusterNum = NewClusterNum;
}
// 计算当前簇的第一个扇区的扇区号。簇从 2 开始计数。
FirstSectorOfCluster = Vcb->FirstDataSector + (CurrentClusterNum - 2) * Vcb->Bpb.SectorsPerCluster;
// 计算写位置在扇区内的字节偏移。
OffsetInSector = (Offset) % Vcb->Bpb.BytesPerSector;
// 并且只处理写入的数据在一个扇区范围内的情况。
Status = IopReadWriteSector( Vcb->DiskDevice,
FirstSectorOfCluster,//当前簇的第一个扇区的扇区号
OffsetInSector,//写位置在扇区内的字节偏移
(PCHAR)MidBuffer,//缓存区
512,//要写入多少字节
FALSE );
if (!EOS_SUCCESS(Status))
return Status;
// 如果文件长度增加了则必须修改文件的长度。
if (Offset + 512 > File->FileSize) {
File->FileSize = Offset + BytesToWrite;
// 如果是数据文件则需要同步修改文件在磁盘上对应的 DIRENT 结构
// 体。目录文件的 DIRENT 结构体中的 FileSize 永远为 0,无需修改。
if (!File->AttrDirectory)
FatWriteDirEntry(Vcb, File);
}
MidBuffer = &MidBuffer[512];
Offset += 512;
k--;
}
// 写入尾扇区
if(NeedSplit == 1 &&ByteLatter > 0){
// 顺着簇链向后查找写入的起始位置所在簇的簇号。
PrevClusterNum = 0;
CurrentClusterNum = File->FirstCluster;
// 根据簇的大小,计算写入的起始位置在簇链的第几个簇中(从 0 开始计数)
ClusterIndex = (Offset) / FatBytesPerCluster(&Vcb->Bpb);
for (i = ClusterIndex; i > 0; i--) {
PrevClusterNum = CurrentClusterNum;
CurrentClusterNum = FatGetFatEntryValue(Vcb, PrevClusterNum);
//CurrentClusterNum = FatGetFatEntryValue(Vcb,CurrentClusterNum);
}
// 如果写入的起始位置还没有对应的簇,就增加簇
if (0 == CurrentClusterNum || CurrentClusterNum >= 0xFF8) {
// 为文件分配一个空闲簇
FatAllocateOneCluster(Vcb, &NewClusterNum);
// 将新分配的簇安装到簇链中
if (0 == File->FirstCluster)
File->FirstCluster = NewClusterNum;
else
FatSetFatEntryValue(Vcb, PrevClusterNum, NewClusterNum);
CurrentClusterNum = NewClusterNum;
}
// 计算当前簇的第一个扇区的扇区号。簇从 2 开始计数。
FirstSectorOfCluster = Vcb->FirstDataSector + (CurrentClusterNum - 2) * Vcb->Bpb.SectorsPerCluster;
// 计算写位置在扇区内的字节偏移。
OffsetInSector = (Offset) % Vcb->Bpb.BytesPerSector;
// 为了简单,暂时只处理一个簇包含一个扇区的情况。
// 并且只处理写入的数据在一个扇区范围内的情况。
Status = IopReadWriteSector( Vcb->DiskDevice,
FirstSectorOfCluster,//当前簇的第一个扇区的扇区号
OffsetInSector,//写位置在扇区内的字节偏移
(PCHAR)LatterBuffer,//缓存区
NumOfLatterBuffer,//要写入多少字节
FALSE );
if (!EOS_SUCCESS(Status))
return Status;
// 如果文件长度增加了则必须修改文件的长度。
if (Offset +ByteLatter > File->FileSize) {
File->FileSize = Offset + BytesToWrite;
// 如果是数据文件则需要同步修改文件在磁盘上对应的 DIRENT 结构
// 体。目录文件的 DIRENT 结构体中的 FileSize 永远为 0,无需修改。
if (!File->AttrDirectory)
FatWriteDirEntry(Vcb, File);
}
}
// 返回实际写入的字节数量
*BytesWriten = BytesToWrite;
return STATUS_SUCCESS;
}
思考与练习
1、结合 FAT12 文件系统,说明“文件大小”和“文件占用磁盘空间大小”的区别,并举例说明文件的这两个属性值变化的方式有什么不同
文件占用磁盘空间是以簇为单位的,而文件大小是以字节为单位的,所以文件大小小于等于文件占用的磁盘空间(因为一个簇就包含512个字节,每占用一个簇占用的磁盘空间就增加512个字节)。
当文件增大时,并一定需要增加磁盘空间,比如一个文件大小只有1个字节,此时所占磁盘空间为1个簇,当他再增加1个字节的时候,大小变为了2个字节,所占磁盘空间仍然为1个簇,只有当他增加到超过1个簇的大小时,才会增加新的磁盘占用空间。
2、EOS 应用程序在读写文件时,缓冲区大小设置为512的倍数比较合适,说明原因。
在内核中读取文件都是通过IopReadWriteSector函数完成的,该函数每次最多只能读取512个字节,在读取多个扇区时效率会降低。如果使用小于512的缓冲区大小,如100或者200或者256,在读取两个扇区时可能就会调用4次或以上的函数,而设置为512只需调用两次。因此大小设置不合适的话会增加对该函数的调用次数。
3、分析使用链式分配方式管理磁盘空间的优缺点。
优点:存储不连续,解决了连续存储带来的碎片化问题,可以显著提高对磁盘空间的利用率,并且不用事先知道文件的长度,只需要根据文件的当前需要为他分配当前的磁盘块,动态增长时可以动态增加分配。同时“增删改”也十分方便。
缺点:“查”很不方便。因为链式存储随机访问效率低,要访问某一磁盘时必须从头遍历。并且只要其中某一块的任一个指针出现问题,整个链就会全盘崩塌。
本文来自彬彬zhidao的博客,作者:彬彬zhidao,转载请注明原文链接:https://www.cnblogs.com/binbinzhidao/p/17884253.html