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:

测试文件是否出错。同样是个宏。

来自为知笔记(Wiz)

时间: 2024-10-25 17:08:13

fatfs源码阅读的相关文章

CI框架源码阅读笔记3 全局函数Common.php

从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap引导文件都会最先引入全局函数,以便于之后的处理工作). 打开Common.php中,第一行代码就非常诡异: if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 上一篇(CI框架源码阅读笔记2 一切的入口 index

淘宝数据库OceanBase SQL编译器部分 源码阅读--生成逻辑计划

body, td { font-family: tahoma; font-size: 10pt; } 淘宝数据库OceanBase SQL编译器部分 源码阅读--生成逻辑计划 SQL编译解析三部曲分为:构建语法树,生成逻辑计划,指定物理执行计划.第一步骤,在我的上一篇博客淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树里做了介绍,这篇博客主要研究第二步,生成逻辑计划. 一. 什么是逻辑计划?我们已经知道,语法树就是一个树状的结构组织,每个节点代表一种类型的语法含义.如

JDK部分源码阅读与理解

本文为博主原创,允许转载,但请声明原文地址:http://www.coselding.cn/article/2016/05/31/JDK部分源码阅读与理解/ 不喜欢重复造轮子,不喜欢贴各种东西.JDK代码什么的,让整篇文章很乱...JDK源码谁都有,没什么好贴的...如果你没看过JDK源码,建议打开Eclipse边看源码边看这篇文章,看过的可以把这篇文章当成是知识点备忘录... JDK容器类中有大量的空指针.数组越界.状态异常等异常处理,这些不是重点,我们关注的应该是它的一些底层的具体实现,这篇

如何阅读Java源码 阅读java的真实体会

刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比方吧,如果你从来没有学过Java,或是任何一门编程语言如C++,一开始去啃<Core Java>,你是很难从中吸收到营养的,特别是<深入Java虚拟机>这类书,别人觉得好,未必适合现在的你. 虽然Tomcat的源码很漂亮,但我绝不建议你一开始就读它.我文中会专门谈到这个,暂时不展开. 强烈

Memcache-Java-Client-Release源码阅读(之七)

一.主要内容 本章节的主要内容是介绍Memcache Client的Native,Old_Compat,New_Compat三个Hash算法的应用及实现. 二.准备工作 1.服务器启动192.168.0.106:11211,192.168.0.106:11212两个服务端实例. 2.示例代码: String[] servers = { "192.168.0.106:11211", "192.168.0.106:11212" }; SockIOPool pool =

源码阅读笔记 - 1 MSVC2015中的std::sort

大约寒假开始的时候我就已经把std::sort的源码阅读完毕并理解其中的做法了,到了寒假结尾,姑且把它写出来 这是我的第一篇源码阅读笔记,以后会发更多的,包括算法和库实现,源码会按照我自己的代码风格格式化,去掉或者展开用于条件编译或者debug检查的宏,依重要程度重新排序函数,但是不会改变命名方式(虽然MSVC的STL命名实在是我不能接受的那种),对于代码块的解释会在代码块前(上面)用注释标明. template<class _RanIt, class _Diff, class _Pr> in

JDK 源码 阅读 - 2 - 设计模式 - 创建型模式

A.创建型模式 抽象工厂(Abstract Factory) javax.xml.parsers.DocumentBuilderFactory DocumentBuilderFactory通过FactoryFinder实例化具体的Factory. 使用例子: DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = docBuilder

CI源码阅读

CodeIgniter源码分析 http://calixwu.com/2014/11/codeigniter-yuanmafenxi.html CI框架源码阅读笔记 http://www.cnblogs.com/ohmygirl/p/4052686.html

《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分

这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecut