前序
文件系统的最终目的是为了进行文件的管理,文件的管理就是读写、删除等操作,文件打开后,本篇继续分析读操作。
分析假设
(1)假设一个磁盘就一个分区。
(2)只分析FAT32文件系统相关的代码。
(3)函数的大部分分析,都写入代码注释中。
(4)重要的注释都回加入很多星号以及数学标号。例如,
/****************** 1.把字符存入lfn的buffer中 *******************/
(5)在f_open()分析时,发现太多的代码,占用了不少的位置,从此篇文章开始,删除错误判定和不重要的代码,减少文章的长度。
f_read函数分析
下面是f_read()函数的源码:
FRESULT f_read (
FIL* fp, /* Pointer to the file object */
void* buff, /* Pointer to data buffer */
UINT btr, /* Number of bytes to read */
UINT* br /* Pointer to number of bytes read */
)
{
FRESULT res;
FATFS *fs;
DWORD clst, sect;
FSIZE_t remain;
UINT rcnt, cc, csect;
BYTE *rbuff = (BYTE*)buff;
*br = 0; /* Clear read byte counter */
/********* 1.如果文件可读数据不足,调整要读取的数据字节数 **********/
remain = fp->obj.objsize - fp->fptr;
if (btr > remain) btr = (UINT)remain; /* Truncate btr by remaining bytes */
for ( ; btr; /* Repeat until all data read */
rbuff += rcnt, fp->fptr += rcnt, *br += rcnt, btr -= rcnt) {
if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */
csect = (UINT)(fp->fptr / SS(fs) & (fs->csize - 1)); /* Sector offset in the cluster */
/* 够了一个正簇,需要找到文件的下一个簇的位置 */
if (csect == 0) { /* On the cluster boundary? */
if (fp->fptr == 0) { /* On the top of the file? */
clst = fp->obj.sclust; /* Follow cluster chain from the origin */
} else { /* Middle or end of the file */
if (fp->cltbl) {
clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */
} else
{
clst = get_fat(&fp->obj, fp->clust); /* Follow cluster chain on the FAT */
}
}
fp->clust = clst; /* Update current cluster */
}
sect = clust2sect(fs, fp->clust); /* Get current sector */
sect += csect;
cc = btr / SS(fs); /* When remaining bytes >= sector size, */
/************ 2.如果要读出的数据大于1个扇区,就先把整数扇区直接读入到用户的缓冲区 ****************/
if (cc) { /* Read maximum contiguous sectors directly */
if (csect + cc > fs->csize) { /* Clip at cluster boundary */
cc = fs->csize - csect;
}
if (disk_read(fs->drv, rbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR);
if ((fp->flag & FA_DIRTY) && fp->sect - sect < cc) {
mem_cpy(rbuff + ((fp->sect - sect) * SS(fs)), fp->buf, SS(fs));
}
rcnt = SS(fs) * cc; /* Number of bytes transferred */
continue;
}
/* fp缓冲区对应的sect值与要读的扇区不相同的话,先要把脏扇区回写到磁盘,再从磁盘把数据读入到fp对应的缓冲 */
if (fp->sect != sect) { /* Load data sector if not in cache */
if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */
if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
fp->flag &= (BYTE)~FA_DIRTY;
}
if (disk_read(fs->drv, fp->buf, sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Fill sector cache */
}
fp->sect = sect;
}
rcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes left in the sector */
if (rcnt > btr) rcnt = btr; /* Clip it by btr if needed */
/******* 3.把不足一个扇区的内容,通过当前扇区的缓冲区,直接读入到用户缓冲区 ********/
mem_cpy(rbuff, fp->buf + fp->fptr % SS(fs), rcnt); /* Extract partial sector */
}
LEAVE_FF(fs, FR_OK);
}
读文件相对来说简单一些:
(1)找出文件所在的簇的起始位置,这个簇在f_open()函数的时候已经被确认了。
(2)然后在把簇中的内容读取到用户的缓冲区。
核心思想总结
(1)在f_open()函数的时候,fp->obj.objsize中记录的文件的大小,fp->clust记录了文件的起始簇号。
(2)每次读文件的时候都会更改fp->fptr的值,如果fp->fptr的值够了一个簇,但是要读取的大小还不足,此时就要找到文件对应的下一个簇的位置,然后更新fp->clust。
(3)在文件描述符的fp对应的结构体中,sect指向当前的读扇区,buf[]缓冲当前的扇区内容。