fatfs源码阅读
使用fatfs文件的第一步,就是调用F_mount函数注册一个工作空间。
F_mount函数的原型如下:
第一个参数根据网上大神的答复,是外设类型,如果是sd卡就是0,flash等等其他的外设就是其他得数,据说有定义,不过我没找到。
第二个参数FATFS指针就是工作空间的指针,个人感觉有点lwip网卡数据结构的感觉。
FATFS数据结构及解释如下,个人感觉了解FATFS这个工作空间数据结构是什么东西就行:
typedef struct { BYTE fs_type; /* FAT sub-type (0:Not mounted) */ BYTE drv; /* Physical drive number */ BYTE csize; /*每簇里有多少个扇区 (一般一簇是4KB 一扇区是512B)*/ BYTE n_fats; /* FAT表的数目 一般是两个 */ BYTE wflag; /* win[ ] dirty flag */ BYTE fsi_flag; /* fsinfo 区域脏数据标志 */ WORD id; /* File system mount ID */ WORD n_rootdir; /* Number of root directory entries (FAT12/16) */#if _MAX_SS != 512 WORD ssize; /* 扇区大小 对应着sd卡中的block 一般是512B*/
#endif
#if _FS_REENTRANT _SYNC_t sobj; /* Identifier of sync object */#endif
#if !_FS_READONLY DWORD last_clust; /* Last allocated cluster */ DWORD free_clust; /* 空闲簇的数目*/ DWORD fsi_sector; /* fsinfo sector (FAT32) */
#endif#if _FS_RPATH DWORD cdir; /* Current directory cluster (0:root) */#endif DWORD n_fatent; /* Number of FAT entries (== Number of clusters + 2) */ DWORD fsize; /*FAT表对应着多少个扇区*/ DWORD fatbase; /*FAT表的起始扇区 */ DWORD dirbase; /* 根目录的起始扇区*/ DWORD database; /* 数据区的起始扇区 */ DWORD winsect; /* Current sector appearing in the win[ ] */ BYTE win[_MAX_SS]; /* Disk access window for Directory, FAT (and file data on tiny cfg) */} FATFS;
注册的FATFS并不在f_mount函数中完成对其属性的初始化,而是第一次调用f_open发现还没有初始化的时候在完成初始化。f_mount函数中做得事情很简单,如下:
FRESULT f_mount (
BYTE vol, /* Logical drive number to be mounted/unmounted */
FATFS *fs /* Pointer to new file system object (NULL for unmount)*/
)
{
FATFS *rfs;
if (vol >= _VOLUMES) /* Check if the drive number is valid */
return FR_INVALID_DRIVE;
rfs = FatFs[vol]; /* Get current fs object */
if (rfs) {
rfs->fs_type = 0; /* Clear old fs object */
}
if (fs) {
fs->fs_type = 0; /* Clear new fs object */
}
FatFs[vol] = fs; /* Register new fs object */
return FR_OK;
}
我删去了一些没用的条件编译,发现最后的代码是上面的代码,其实就是将fatfs文件系统已经定义好的FatFs[vol]指针数组的0号指向我们传进来的工作空间。
------------------------------------------------------------------------------------------------------------
这就是一个文件对象的属性:
typedef struct { FATFS* fs; /* 指向属于自己的文件系统 */ WORD id; /* Owner file system mount ID */ BYTE flag; /*文件状态标识*/ BYTE pad1; DWORD fptr; /* 文件读写指针 (Byte offset origin from top of the file) */ DWORD fsize; /*文件大小*/ DWORD sclust; /*文档开始扇区 */ DWORD clust; /* 当前扇区 */ DWORD dsect; /* Current data sector */
#if !_FS_READONLY DWORD dir_sect; /* Sector containing the directory entry */ BYTE* dir_ptr; /* Ponter to the directory entry in the window */
#endif
#if !_FS_TINY BYTE buf[_MAX_SS]; /* Data read/write buffer */
#endif} FIL;
上面的是一个文件对象的属性,f_open函数就是用来填充它,而f_close函数则是主要用来将其fs属性置为空,以此让此文件对象失效。
F_open:
打开一个文件时,有五个选项:
FA_READ:以读方式打开文件
FA_WRITE:以写方式打开文件,和上一个组合就变成了以读写的方式打开文件
FA_OPEN_EXISTING:打开的文件必须已经存在,如不存在函数失败
FA_OPEN_ALWAYS:如果不存在这个文件,则新建,如果存在这个文件,则在后面添加。用这种方法打开文件要之后调用 f_lseek方法。
FA_CREATE_NEW:创建一个新文件,如果已经存在,则函数失败。
FA_CREATE_ALWAYS:创建一个新文件,如果文件存在,则覆盖。
在f_open函数中,调用了下面这个函数:
这个函数会进行检查,如果文件系统对象还不是有效的还不是有效的,则会初始化它。
F_close:
用来将文件对象的fs属性置为空,以此让此文件对象失效。其中调用f_sync函数,来将缓冲区的数据写入sd卡。
----------------------------------------------------------------------------------------------------------
比较正常的两个函数F_READ和F_WRITE。
F_READ:
第一个参数:文件对象
第二个参数:读取所用的的缓冲区
第三个参数:想要读多少字节的数据
第四个参数:这个参数实际的功能是作为一个返回值,告诉使用者函数完成后实际上读了多少数值,那么如果函数完成后*byteread<bytetoread,那就说明这个文件本身没有bytetoread那么大。
F_WRITE:
这个函数只有在_FS_READONLY == 0时才可以用。
第一个参数:文件对象
第二个参数:针对写入所用的的缓冲区
第三个参数:想要写入少字节的数据
第四个参数:这个参数也是函数的一个返回值,当函数成功执行之后,如果第四个指针所指向的值小于第三个传进去的参数,那就说明,磁盘满了。
详细步骤如下:
准备开始写,先查看一下当前的文件的字节数是不是512的整数倍,如果是的话再查看flag标志中,通过查看可以得出缓冲区是不是又一个完整扇区的数据,如果有的话先将缓冲区中一个扇区的数据写入sd卡,写入512*最大整数倍的数据。剩下的存入缓冲区中。
准备开始写,先查看一下当前的文件的字节数是不是512的整数倍,如果不是,说明缓冲区中还有部分数据,那么就用要写入的数据中的一部分填满缓冲区,更新flag标志,然后回到上一段文字。
所谓的缓冲区就是文件对象的buf[]属性。
--------------------------------------------------------------------------------------------------------------
f_read和f_write函数都只能从文件开头开始读写,那么这个时候就需要一个函数来移动当前指针,配合f_read和f_write函数,这个函数就是f_lseek。
函数原型如下:
第一个参数:文件对象的指针
第二个参数:从文件开始处向后移动多少
Offset是指相对于文件起始处的字节数。
在写模式写了一个超过文件大小的offset,文件将被拓展,并且拓展区域的数据是未定义的。这可以用来迅速的创建一个大文件。
F_lseek函数成功后,为确保指针已经以已经成功移动,必须检查文件对象中的fptr的值,如果其不为所期望的值则说明:1.文件以只读打开,指针只能移动到文件结束处。2.磁盘满了。
示例:
------------------------------------------------------------------------------------------------------------
F_truncate:
缩短文件大小,将文件尾缩短到当前指针处,如果当前指针已经在文件末尾,则不会发生任何改变。
f_sync:
介绍这个函数之前先详细的说一下fatfs的f_write的流程,此流程为读文件系统代码得出:
准备开始写,先查看一下当前的文件的字节数是不是512的整数倍,如果是的话再查看flag标志中,通过查看可以得出缓冲区是不是又一个完整扇区的数据,如果有的话先将缓冲区中一个扇区的数据写入sd卡,写入512*最大整数倍的数据。剩下的存入缓冲区中。
准备开始写,先查看一下当前的文件的字节数是不是512的整数倍,如果不是,说明缓冲区中还有部分数据,那么就用要写入的数据中的一部分填满缓冲区,更新flag标志,然后回到上一段文字。
那么也就说我们不断操作的过程中,其实有可能有一部分文本信息并未真正的写入sd卡中,而是存储在文件的buf数组也就是缓冲区里。那么f_sync的作用就是把缓冲区的数据强制写入sd卡中,这样的话,如果写入文件的持续时间长,周期性的的f_sync一次,可以尽量的保证断电等意外突发情况数据损失减小。
对,你没猜错,f_close函数中会调用这个函数以保证关闭文件之后所有数据均以写入sd卡。所以这个函数利f_close唯一的区别就在于函数调用之后文件不失效。
因为sd卡每次最少写入512字节,所以这次写入不足512字节,那么就用无用的数据填补够512字节,而这部分数据其实还留在缓冲区,下次再写的时候直接用新的数据将这次不足的还有一部分无用信息的数据覆盖掉。(本段为猜测)
F_opendir:
可以打开一个目录,也可以创建一个目录。该目录对象可以不经过任意函数直接丢弃。
f_readdir:
读取一个目录中的项目(这个项目可能是个文件,也可能是个目录),将返回的这一个项目的信息存在FILINFO中,一次返回一个,不停的重复调用这个函数,就能得到这个目录下所有项目的信息了。当读完之后,再次调用这个函数,FILINFO中的f_name属性为空,可以此来判断是不是读取完了所有的该目录下的项目。
注:fatfs支持长文件名,需_USE_LFN宏为真,FILINFO数据结构中有相应的属性处理长文件名。FILINFO数据结构如下:
使用示例:
f_getfree:
这个函数使用来得到空闲的簇的的数目并把文件系统的信息通过第三个参数返回。通过文件系统数据结构的属性同样能计算出空闲的簇数。
f_stat:
获取一个文件或者目录的相关信息。返回到FILINFO结构体中。
f_mkdir:
就是新建一个目录。
用法如下:
f_unlink:
删除一个文件或者目录,但要注意以下几点:
1.文件或者目录不能使只读的。
2.目录的话一定要是空的并且不能是当前目录。
3.文件的话不能是打开的。
f_chmod:
第一个参数:文件名
第二个参数:待被设置的属性标志
第三个参数:要被修改的属性标志
首先文件的属性有以下几种:
那么这个函数的第二个参数和第三个参数是什么意思呢?第二个参数是指函数要把文件要设置成那种属性,第三个参数是指函数要修改那些属性,也就是说出现在第三个参数而没有出现在第二个参数就是要清除的标志,比如:
就是代表设置只读标志,清除读档标志。
f_utime:
修改一个目录的时间戳,把想要修改成的数据通过第二个参数传入。
使用例子:
f_rename:
可以用来重命名一个文件,也可用来移动一个文件,如下:
注意,移动文件不能跨设备移动,比如sd卡的文件只能在sd卡内移动,也不能移动一个已经打开的文件。
f_chdir:
修改某个设备的当前目录。当设备初始打开的时候,当前目录自动设置为根目录。用例如下:
f_chdrive:
修改当前设备。初始的当前设备为0号设备。
f_getcwd:
恢复当前设备上的当前目录。
f_forward:(看的很粗)
从文件中读取数据并直接将数据转发到输出流,而不使用数据缓冲区,适用于小存储系统。
f_mkfs:
新建一个文件系统,其实感觉主要作用是格式化。
f_fdisk:(没好好看)
分区,最多可以分四个,创建分区表到设备的MBR。
以下函数大概介绍一下:
这四个输入输出函数,前三个封装的是f_read和f_write,最后一个封装的是f_putc和f_puts。
f_tell:
得到当前的读写指针,其实是一个宏,如下:
f_eof:
检查当前的读写指针是否打到了文件结尾。
f_size:
又是一个宏,返回的是文件大小。
f_error:
测试文件是否出错。同样是个宏。
</div>
<div class="postDesc">posted @ <span id="post-date">2016-03-08 20:30</span> <a href="https://www.cnblogs.com/dchipnau/">DChipNau</a> 阅读(<span id="post_view_count">189</span>) 评论(<span id="post_comment_count">0</span>) <a href="https://i.cnblogs.com/EditPosts.aspx?postid=5255483" rel="nofollow">编辑</a> <a href="#" onclick="AddToWz(5255483);return false;">收藏</a></div>
</div>
<script type="text/javascript">var allowComments=true,cb_blogId=255187,cb_entryId=5255483,cb_blogApp=currentBlogApp,cb_blogUserGuid='5862811b-e990-e511-9fc1-ac853d9f53cc',cb_entryCreatedDate='2016/3/8 20:30:00';loadViewCount(cb_entryId);var cb_postType=1;</script>