typedef struct FILE_DESC
{
LIST_NODE node;
FILE_ENTRY fe;
U32 secIdx; // 文件数据链表节点偏移
U32 offset; // 用于记录文件读写位置
U08 changed; // 数据是否被修改
U08 catch[SEC_SIZE]; // 数据缓冲区
} FILE_DESC;
static U32 FindIndex(U32 secBegin, U32 idx)
{
U32 ret = secBegin;
U32 i = 0;
while(i < idx && ret != SEC_END_FLAG)
{
ret = NextSector(ret);
i++;
}
return ret;
}
static BOOL IsOpened(const U08* name)
{
BOOL ret = FALSE;
LIST_NODE* pListNode = NULL;
FILE_DESC* fd = NULL;
LIST_FOR_EACH(&fdList, pListNode)
{
fd = (FILE_DESC *)LIST_NODE(pListNode, FILE_DESC, node);
if(TRUE == StrCmp(fd->fe.name, name, -1))
{
ret = TRUE;
break;
}
}
return ret;
}
FILE_DESC* FileOpen(const U08* name)
{
FILE_DESC* fd = NULL;
FILE_ENTRY* fe = NULL;
// 检查参数合法性
if(NULL == name)
goto error;
// 文件不可以重复打开
if(TRUE == IsOpened(name))
goto error;
// 找到 fe
fe = FindInRoot(name);
if(NULL == fe)
goto error;
// 申请一块内存用于存放文件描述符数据结构
fd = (FILE_DESC *)FS_MALLOC(sizeof(FILE_DESC));
if(NULL == fd)
goto error;
// 初始化文件描述符 fd,然后将其添加到链表 fdList 中
fd->fe = *fe;
fd->secIdx = 0;
fd->offset = 0;
fd->changed =0;
ListAddHead(&fdList, &(fd->node));
error:
FS_FREE(fe);
return fd;
}
E_RET FileClose(FILE_DESC* fd)
{
// 检查文件描述符 fd 是否有效
if(FALSE == IsFdValid(fd))
return E_ERR;
Flush(fd); // 将缓冲区中文件数据写到硬盘中
ListDelNode(&(fd->node)); // 删除链表节点
FS_FREE(fd);
return E_OK;
}
static E_RET Flush(FILE_DESC* fd)
{
U32 secNum = SEC_END_FLAG;
U08* buf = NULL;
E_RET ret = E_ERR;
// 当参数 fd 为空或者缓冲区没有变动时直接退出
if(NULL == fd || 0 == fd->changed)
return E_ERR;
// 得到当前变动缓冲区对应的物理扇区号,然后将改动后的 catch[SEC_SIZE] 数据该扇区中
secNum = FindIndex(fd->fe.fileBegin, fd->secIdx);
if(SEC_END_FLAG == secNum)
return E_ERR;
if(E_ERR == FS_WRITE(secNum, fd->catch))
return E_ERR;
fd->changed = 0; // 缓冲区数据已成功写入硬盘,清变动标志
// 当文件数据更新后,该文件对应的 FILE_ENTRY 也有可能变化
// 比如文件追加写入数据后 lastBytes 会增加,此时需要重新将 FILE_ENTRY 写回硬盘
// 这里为了简单就不判断 FILE_ENTRY 是否改动了,直接重新写回硬盘
buf = (U08 *)FS_MALLOC(SEC_SIZE);
if(NULL == buf)
return E_ERR;
if(E_ERR == FS_READ(fd->fe.inSecIdx, buf))
goto error;
*((FILE_ENTRY *)(buf + fd->fe.inSecOff)) = fd->fe;
if(E_ERR == FS_WRITE(fd->fe.inSecIdx, buf))
goto error;
ret = E_OK;
error:
FS_FREE(buf);
return ret;
}
写个代码代码简单测试一下文件的打开和关闭功能
void FsTest(void)
{
printk("creat:%d\n", CreatFileInRoot("1.txt"));
FILE_DESC* fd = FileOpen("1.txt");
printk("%d\n", IsOpened("1.txt"));
FileClose(fd);
printk("%d\n", IsOpened("1.txt"));
}
测试结果:
完整代码见:fs.c
在解决了文件的打开关闭函数后,接下来我们来研究一个文件的写操作
自然的,我们需要思考下面的几个问题
开始写代码,首先遍历 fdList, 确保传入的参数 fd 是个已经打开的文件,然后将 buf 中数据分批次写入文件数据区,因为一次最多只能写 SEC_SIZE 个字节的数据
U32 FileWrite(FILE_DESC* fd, U08* buf, U32 len)
{
U32 ret = 0;
U32 i = 0;
U32 n = 0;
U32 secNum = SEC_END_FLAG;
U08* feBuf = NULL;
FILE_ENTRY* fe = NULL;
U32 remainLen = 0;
// 检查参数合法性
if(NULL == fd || NULL == buf || 0 == len)
goto error;
// 检查文件描述符 fd 是否有效
if(FALSE == IsFdValid(fd))
goto error;
if(0 == fd->fe.fileNum) // 如果还没有分配文件数据区,则分配一个扇区
{
if(E_ERR == FileAddNewSector(&fd->fe))
goto error;
// FileAddNewSector 函数有可能改变硬盘中 fe 数据,所以我们还得把硬盘中 fe 数据复制到 fd->fe 中
feBuf = (U08 *)FS_MALLOC(SEC_SIZE);
if(NULL == feBuf)
goto error;
if(E_ERR == FS_READ(fd->fe.inSecIdx, (U08 *)feBuf)) // 读出 fe 所在的整个扇区数据
goto error;
fe = (FILE_ENTRY *)(feBuf + fd->fe.inSecOff); // 找到 fe 起始位置
fd->fe = *fe;
}
// 将 buf 中数据分批次写入文件数据区,一次最多只能写 SEC_SIZE 个字节的数据
while(i < len)
{
// 检查一下文件数据链表的最后一个扇区是否已经满了
// 如果满了,则申请一个空闲扇区,添加到 fe 对应的文件数据区链表末尾
if(SEC_SIZE == fd->offset)
{
if(E_ERR == FileAddNewSector(&fd->fe))
goto error;
// FileAddNewSector 函数有可能改变硬盘中 fe 数据,所以我们还得把硬盘中 fe 数据复制到 fd->fe 中
feBuf = (U08 *)FS_MALLOC(SEC_SIZE);
if(NULL == feBuf)
goto error;
if(E_ERR == FS_READ(fd->fe.inSecIdx, (U08 *)feBuf)) // 读出 fe 所在的整个扇区数据
goto error;
fe = (FILE_ENTRY *)(feBuf + fd->fe.inSecOff); // 找到 fe 起始位置
fd->fe = *fe;
fd->offset = 0;
fd->secIdx++;
}
secNum = FindIndex(fd->fe.fileBegin, fd->secIdx); // 获得当前 catch 对应的物理扇区号
if(E_ERR == FS_READ(secNum, fd->catch)) // 将扇区号 secNum 中的数据读到 catch 缓冲区中
goto error;
n = SEC_SIZE - fd->offset; // catch 缓冲区中剩余大小
remainLen = len - i; // 剩余长度
n = (n < remainLen ) ? n : remainLen; // 取 n 与 remainLen 较小值作为内存拷贝长度
MemCpy((U08 *)(fd->catch + fd->offset), (U08 *)(buf + i), n);
i += n;
fd->changed = 1;
fd->offset += n;
// 如果是最后一包的话,则更新 lastBytes
if((fd->secIdx == fd->fe.fileNum - 1) && (fd->fe.lastBytes < fd->offset))
fd->fe.lastBytes = fd->offset;
Flush(fd);
ret = i;
}
error:
FS_FREE(feBuf);
return ret;
}
在写数据之前,要判断一下文件的数据区是否足够,不够的话当然是要继续申请空闲扇区然后添加到文件数据链表的末尾,函数 FileAddNewSector() 就是实现该功能的
static E_RET FileAddNewSector(FILE_ENTRY* fe)
{
E_RET ret = E_ERR;
U32 sn = SEC_END_FLAG;
U08* buf = NULL;
if(0 == fe->fileNum) // 如果尚未分配扇区,则分配一个空闲扇区
{
sn = AllocSector(); // 申请一个空闲扇区
if(SEC_END_FLAG == sn)
goto error;
buf = (U08 *)FS_MALLOC(SEC_SIZE);
if(NULL == buf)
goto error;
if(E_ERR == FS_READ(fe->inSecIdx, (U08 *)buf)) // 读出 fe 所在的整个扇区数据
goto error;
fe = (FILE_ENTRY *)(buf + fe->inSecOff); // 找到 fe 起始位置
// 更新 fe
fe->fileBegin = sn;
fe->fileNum++;
if(E_ERR == FS_WRITE(fe->inSecIdx, (U08 *)buf))
goto error;
}
else
{
sn = AllocSector();
if(SEC_END_FLAG == sn)
goto error;
buf = (U08 *)FS_MALLOC(SEC_SIZE);
if(NULL == buf)
goto error;
if(E_ERR == FS_READ(fe->inSecIdx, (U08 *)buf)) // 读出 fe 所在的整个扇区数据
goto error;
fe = (FILE_ENTRY *)(buf + fe->inSecOff); // 找到 fe 起始位置
if(E_ERR == AddToLast(fe->fileBegin, sn)) // 将新申请的扇区号 sn 添加到根目录扇区链表末尾
goto error;
// 更新 fe
fe->fileNum++;
fe->lastBytes = 0;
if(E_ERR == FS_WRITE(fe->inSecIdx, (U08 *)buf)) // 将更新后的数据写回硬盘
goto error;
}
ret = E_OK;
error:
FS_FREE(buf);
return ret;
}
写个代码测试一下文件写入函数是否正确。因为还没有实现文件读操作函数,所以测试时候直接根据扇区号从硬盘中读取数据
U32 testBuf[400];
void FsTest(void)
{
U32 sn = SEC_END_FLAG;
U32 next = SEC_END_FLAG;
// 当做文件数据
for(U32 i = 0; i < sizeof(testBuf) / sizeof(testBuf[0]); i++)
testBuf[i] = i;
printk("creat:%d\n", CreatFileInRoot("1.txt"));
FILE_DESC* fd = FileOpen("1.txt");
printk("-------------- Write -------------\n");
// 将 testBuf 缓冲区中数据写入文件
printk("write:%d\n", FileWrite(fd, (U08 *)testBuf, sizeof(testBuf)));
FileClose(fd);
printk("-------------- Read -------------\n");
// 清 testBuf 缓冲区
for(U32 i = 0; i < sizeof(testBuf) / sizeof(testBuf[0]); i++)
testBuf[i] = 0;
// 重新打开文件
fd = FileOpen("1.txt");
printk("fileNum = %d\n", fd->fe.fileNum);
printk("lastBytes = %d\n", fd->fe.lastBytes);
printk("fileBegin = %d\n", fd->fe.fileBegin);
next = fd->fe.fileBegin;
for(U32 i = 0; i < fd->fe.fileNum; i++)
{
FS_READ(next, (U08 *)testBuf);
printk("testBuf[0] = %d testBuf[1] = %d\n", testBuf[0], testBuf[1]);
next = NextSector(next);
printk("next = %d\n", next);
}
FileClose(fd);
}
完整代码见:fd.c
从下图运行结果可以看出文件成功写入 1600 字节,文件占 4 个扇区,最后一个扇区 64 字节,3*512+64 正好等于 1600,并且打印出的每个扇区前两个字节数据也是正确的
实现了文件写入后,文件读取也就比较容易实现了
U32 FileRead(FILE_DESC* fd, U08* buf, U32 len)
{
U32 ret = 0;
U32 i = 0;
U32 n = 0;
U32 readLen = 0;
U32 secNum = SEC_END_FLAG;
U32 remainLen = 0;
// 检查参数合法性
if(NULL == fd || NULL == buf || 0 == len)
goto error;
// 检查文件描述符 fd 是否有效
if(FALSE == IsFdValid(fd))
goto error;
n = GetFileLen(fd) - FileTell(fd); // 计算文件可读大小
readLen = (n < len) ? n : len; // 取文件大小与要读取长度 len 两者较小值
// 分批次读取,因为每次最多读取 SEC_SIZE 个字节的数据
while (i < readLen)
{
if(SEC_SIZE == fd->offset)
{
fd->offset = 0;
fd->secIdx++;
}
secNum = FindIndex(fd->fe.fileBegin, fd->secIdx); // 获得当前 catch 对应的物理扇区号
if(E_ERR == FS_READ(secNum, fd->catch)) // 将扇区号 secNum 中的数据读到 catch 缓冲区中
goto error;
n = SEC_SIZE - fd->offset; // catch 缓冲区中剩余大小
if(remainLen < SEC_SIZE) // 当剩余字节数小于 SEC_SIZE 时,n = remainLen
n = remainLen;
MemCpy((U08 *)(buf + i), (U08 *)(fd->catch + fd->offset), n);
i += n;
fd->offset += n;
remainLen = readLen -i;
ret = i;
}
error:
return ret;
}
需要注意的是,在文件读操作函数中,我们需要计算出文件可读大小,对应的语句为:n = GetFileLen(fd) - FileTell(fd);,可读大小等于文件总大小减去文件当前读写指针的位置
// 获取文件数据大小
U32 GetFileLen(FILE_DESC* fd)
{
U32 ret = 0;
if(SEC_END_FLAG != fd->fe.fileBegin)
ret = (fd->fe.fileNum - 1) * SEC_SIZE + fd->fe.lastBytes;
return ret;
}
// 得到文件当前读写位置指针相对于文件首的偏移字节数
U32 FileTell(FILE_DESC* fd)
{
U32 ret = 0;
if(SEC_END_FLAG != fd->fe.fileBegin)
ret = fd->secIdx * SEC_SIZE + fd->offset;
return ret;
}
文件读写都已经实现好了,那么我们就可以来进行一个完整的读写测试了
U32 testBuf[300];
void FsTest(void)
{
U32 sn = SEC_END_FLAG;
U32 next = SEC_END_FLAG;
for(U32 i = 0; i < sizeof(testBuf) / sizeof(testBuf[0]); i++)
testBuf[i] = i;
printk("creat:%d\n", CreatFileInRoot("1.txt"));
FILE_DESC* fd = FileOpen("1.txt");
// 将 testBuf 缓冲区中数据写入文件
printk("write:%d\n", FileWrite(fd, (U08 *)testBuf, sizeof(testBuf)));
FileClose(fd);
// 清 testBuf 缓冲区
for(U32 i = 0; i < sizeof(testBuf) / sizeof(testBuf[0]); i++)
testBuf[i] = 0;
// 重新打开文件
fd = FileOpen("1.txt");
// printk("Read:%d\n", FileRead(fd, (U08 *)testBuf, 2000));
// printk("Read:%d\n", FileRead(fd, (U08 *)testBuf, 800));
printk("Read:%d\n", FileRead(fd, (U08 *)testBuf, sizeof(testBuf)));
for(U32 i = 0; i < sizeof(testBuf) / sizeof(testBuf[0]); i++)
printk("%d ", testBuf[i]);
FileClose(fd);
}
测试结果如下,文件写入的数据与读取的数据相同,功能实现 OK
完整代码见:fs.c
文件读写我们已经实现完成了,基本上文件系统的基本功能也差不多完成了,不过还有一个功能还需要实现,那就是设置文件读写指针位置,我们总不能每次读写文件都是从头开始吧
设置文件读写指针位置的代码其实非常简单,代码如下:
E_RET FileSeek(FILE_DESC* fd, U32 pos)
{
// 检查文件描述符 fd 是否有效
if(FALSE == IsFdValid(fd))
return E_ERR;
pos = (pos < GetFileLen(fd)) ? pos : GetFileLen(fd);
fd->secIdx = pos / SEC_SIZE;
fd->offset = pos % SEC_SIZE;
return E_OK;
}
测试一下吧,测试代码跟文件读写的测试代码几乎一样,仅在读数据前重新设置一下读写指针位置
U32 testBuf[300];
void FsTest(void)
{
U32 sn = SEC_END_FLAG;
U32 next = SEC_END_FLAG;
for(U32 i = 0; i < sizeof(testBuf) / sizeof(testBuf[0]); i++)
testBuf[i] = i;
printk("creat:%d\n", CreatFileInRoot("1.txt"));
FILE_DESC* fd = FileOpen("1.txt");
// 将 testBuf 缓冲区中数据写入文件
printk("write:%d\n", FileWrite(fd, (U08 *)testBuf, sizeof(testBuf)));
FileClose(fd);
// 清 testBuf 缓冲区
for(U32 i = 0; i < sizeof(testBuf) / sizeof(testBuf[0]); i++)
testBuf[i] = 0;
// 重新打开文件
fd = FileOpen("1.txt");
// 设置文件读写指针位置
FileSeek(fd, 100);
// 打印当前文件读写指针位置
printk("FileTell:%d\n", FileTell(fd));
// printk("Read:%d\n", FileRead(fd, (U08 *)testBuf, 2000));
// printk("Read:%d\n", FileRead(fd, (U08 *)testBuf, 800));
printk("Read:%d\n", FileRead(fd, (U08 *)testBuf, sizeof(testBuf)));
for(U32 i = 0; i < sizeof(testBuf) / sizeof(testBuf[0]); i++)
printk("%d ", testBuf[i]);
FileClose(fd);
}
从结果上看,文件读写指针设置成功:
代码见:fs.c
我们实现文件系统的目的不光是内核需要,应用层也需要,于是我们需要把相关函数设计成系统调用
关于系统调用如何实现这里就不再重复细说了,目前文件系统需要实现的系统调用接口函数有:
E_RET SYS_CreatFileInRoot(const U08* name); // 在根目录 root 下创建一个文件
E_RET SYS_DeleteFileInRoot(const U08* name); // 在根目录 root 下删除一个文件
E_RET SYS_RenameFileInRoot(const U08* old, const U08* new); // 文件重命名
FILE_DESC* SYS_FileOpen(const U08* name); // 打开文件
E_RET SYS_FileClose(FILE_DESC* fd); // 关闭文件
U32 SYS_FileWrite(FILE_DESC* fd, U08* buf, U32 len); // 向文件中写入数据
U32 SYS_FileRead(FILE_DESC* fd, U08* buf, U32 len); // 从文件中读取数据
U32 SYS_GetFileLen(FILE_DESC* fd); // 获取文件数据大小
U32 SYS_FileTell(FILE_DESC* fd); // 得到文件当前读写指针位置
E_RET SYS_FileSeek(FILE_DESC* fd, U32 pos); // 设置文件当前读写指针位置
系统调用实现涉及到的文件有:fs.c、fs.h、u_syscall.c、u_syscall.h
注意在实现系统调用时有个细节,那就是 user 层的文件描述符 fd 是 U32 类型的,而内核中是 FILE_DESC* 类型,为什么 user 层不使用 FILE_DESC* 类型定义呢?
因为 FILE_DESC 这个数据结构对于上层来说是不可见的,fd 值具体是多少对应用层来说并没有多大意义,而且应用层如果知道 FILE_DESC 具体定义,那么它就有可能做一些非法操作,user 层描述符 fd 定义为 U32 类型也是一种安全机制
在 “app.c” 中创建一个任务测试一下系统调用实现的是否有问题,FsTest() 函数其实就是我们上面做实验用的代码,原样复制过来而已,当然了打印 printk 替换成 print,代码见:app.c
static void TaskA(void)
{
SetCursorPos(0, 9);
FsTest();
}
测试结果如下(由于打印超限,所以把之前 testBuf[300] 改为 testBuf[200])
文件系统暂时就写到这里吧
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。