在主程序中,有FatFS操作的流程,我的流程分析就以这个为基础。
一、读文件流程。
1、程序结构
res = f_mount(0, &fs);
res = f_open(&file, "data.txt", FA_OPEN_EXISTING | FA_READ);
while(1)
prints(data);
}
f_close(&file);
总共四个与文件系统相关的函数,下面就沿着函数执行路径去探索一下。
2、f_mount(0, &fs)的执行
参数0是卷号,就像电脑上的CDE盘等。fs是一个未初始化的一个文件系统对象,其定义在此:FATFS fs。这个函数好像就做了两个个事,使全局文件系统指针FatFS指向fs对象,并使fs.fstype=0。
3、f_open(&file, "data.txt", FA_OPEN_EXISTING | FA_READ)的执行
参数&file是提供一个文件对象指针,打开文件过程中获得的一些信息都填入这个结构体。
FA_OPEN_EXISTING | FA_READ表示要打开和读取相应的文件。源程序如下:
FRESULT f_open (
FIL *fp, /* fp就指向传入的对象file */
const char *path, /*path指向data.txt的地址 */
BYTE mode /* Access mode and file open mode flags */
)
这个函数的主要操作是:将磁盘的0扇区读入fs->win【】扇区缓冲。并对其中的特殊位置进行检验。如果读入错误或者末尾不是55AA,返回2。如果确实是FAT文件系统返回0。如果返回1可能是主引导扇区,继续从分区引导扇区读取。
auto_mount继续往下执行:
fatsize = LD_DWORD(&fs->win[BPB_FATSz32]);
fs->sects_fat = (CLUST)fatsize; //每个FAT表的扇区数目
fs->n_fats = fs->win[BPB_NumFATs]; /* FAT表的个数 */
fatsize *= fs->n_fats; /* (Number of sectors in FAT area) */
fs->fatbase = bootsect + LD_WORD(&fs->win[BPB_RsvdSecCnt]); /* FAT start sector (lba)
*/FAT表开始于开始扇区+保留扇区。
fs->csize = fs->win[BPB_SecPerClus]; /* 每个簇的扇区数目 */
fs->n_rootdir = LD_WORD(&fs->win[BPB_RootEntCnt]); /* Nmuber of root directory entries */
totalsect = LD_DWORD(&fs->win[BPB_TotSec32]);
fs->max_clust = maxclust;
fmt = FS_FAT32;
if (fmt == FS_FAT32)
fs->dirbase = LD_DWORD(&fs->win[BPB_RootClus]);
fs->database = fs->fatbase + fatsize + fs->n_rootdir / 16; /* Data start sector (lba)
fs->free_clust = (CLUST)0xFFFFFFFF;
//以上代码主要是根据引导扇区里的数据,填充文件系统对象结构体的信息:包括FAT表大小、数目、根目录区起始地址、数据区起始扇区、文件系统类型等等。后面还有一些代码,也是做同样工作的,这里就不再叙述了。
函数下面回到f_open继续执行。
Auto_mount函数早期有个转折,如果文件系统类型已经定义,则直接返回,后续代码不会执行。
5、f_open()执行完auto_mount()后
下一个函数:
trace_path(&dj, fn, path, &dir); /* Trace the file path */
这个函数里面只有path指向文件路径,其它三个都是未经初始化的指针,应该都是在这个函数里面进行设置。Dj是目录结构体,fn是8.3文件名数组,dir是指向fs扇区缓冲里当前文件目录项的指针。
从函数名称和参数可以推断出它的主要功能是将文件名从路径中提取出来,并转变成从标准8.3格式,同时找到文件所在目录,并将目录扇区读入文件系统缓冲区,依据文件目录项的信息填充dj结构体。
新的007c代码中已经改成了follow_path();
以下是源代码:
FRESULT trace_path ( DIR *dj, char *fn,const char *path, BYTE **dir )
//从簇号得到相应的扇区。
dj->index = 0; //目录索引项先初始化为0
for (;;)
主要是调用f_read函数,一个字节一个字节的读进来,如果遇到
,则读入结束,正确返回buf缓冲区
指针;不正确返回0.
所以读文件的重点在f_read()函数。
7、f_read(fil, p, 1, &rc)函数的分析
读函数中第一个关键语句: remain = fp->fsize - fp->fptr; //文件大小减去当前位置,得到剩下的字
节数。if (btr > remain) btr = (UINT)remain;//如果要读的字节数大于剩余字节数,则调整。
举个例子,从文件头开始读5128个字节,共占据11个扇区。一簇以8个扇区计算。
for ( ; btr; /* Repeat until all data transferred */
rbuff += rcnt, fp->fptr += rcnt, *br += rcnt, btr -= rcnt)
第二次读时额外执行的语句。
}
fp->csect++; /* 这个在第三次读时也会执行 */
}//往下语句也会在第三次读时执行。
sect = clust2sect(fp->curr_clust) + fp->csect - 1; /* 如果文件读写指针不在扇区边界,为了读到数据,必须往前走一个扇区 */
if (!move_window(sect)) goto fr_error; /* Move sector window */
rcnt = 512U - (fp->fptr % 512U); * Get partial sector from sector window */
if (rcnt > btr) rcnt = btr;
memcpy(rbuff, &fp->fs->win[fp->fptr % 512U], rcnt);
}
8、f_close()
主要做了两个工作:(1)res = f_sync(fp);将文件的改动写入,同时调用函数sync()将文件系统
变化写入
二、写文件流程分析
1、写文件程序结构。
res = f_open(&file, "331.txt", FA_CREATE_ALWAYS | FA_WRITE);
res = f_write(&file, data, 512, &br);
f_close(&file);
2、函数f_open(&file, "331.txt", FA_CREATE_ALWAYS | FA_WRITE)的执行
仍然是打开文件,但是以创建和写的方式打开,其执行与读方式的打开就稍有不同。以下是写部分的操作源代码:
先执行函数reserve_direntry(&dj, &dir); 这个函数的作用是通过dj目录对象在相应目录的簇里查找一个空闲目录项,并用dir指向它。并设置该文件目录项的属性等。
3、f_write(&file, data, 512, &br);函数的执行
这个函数执行文件写入操作,file是文件信息结构体:包括它对应的文件系统结构体指针、目录项扇区号、文件对用的起始簇号、当前簇号、当前扇区号、文件对象写指针。有了这些信息,就好操作文件。
Data是写入数据的缓冲区,512是要写的数据个数,br是写入成功的个数(这个是返回值)。部分源代码
如下:
cc = btw / 512U; /* When remaining bytes >= sector size, */
if (cc) { * Write maximum contiguous sectors directly */
if (fp->csect + cc > fp->fs->csize) /* Clip at cluster boundary */
cc = fp->fs->csize - fp->csect;
if (disk_write(0, wbuff, sect, (BYTE)cc) != RES_OK)
实际操作与读文件差不多,只是在写入数目操作本簇的时候,有这个操作,clust = create_chain(fp->curr_clust)文件系统会分配一个新的簇。
有些源代码就没有深入去读。具体的含义等文件系统移植成功时,在调试过程中去分析。
文件系统相应信息区域。(2)fp->fs=0,将文件关闭。