FATFS文件系统,在多个任务都存在文件处理时,可能会因为任务调度导致在f_open函数执行过程中被其他任务抢断,导致文件系统中的 FATFS fs变量的值会与当前任务处理文件的初始值不匹配,导致异常发生。
f_open 打开文件操作
FRESULT f_open (
FIL* fp, /* Pointer to the blank file object */
const TCHAR* path, /* Pointer to the file name */
BYTE mode /* Access mode and file open mode flags */
)
注意:
1、使用的时候最好使用全路径打开,避免使用 f_chdir();改变当前目录,尤其在程序中多处使用f_chdir容易忘记当前文件路径
实际测试到如下问题
两个任务,任务A优先级高于任务B:
任务A:
if(test == 1)
{
PRINTF("6OS.\r\n");
res = f_open(&fil2, "/HISTORY/CO/FIXPTfixpt20110105.msg", FA_READ | FA_WRITE);
PRINTF("6OE.\r\n");
if(res)
{
PRINTF(" f_open /HISTORY/CO/FIXPTfixpt20110105.msg failed. ret=%d\r\n",res);
}
else
{
res = f_lseek(&fil2, f_size(&fil2));
res = f_write(&fil2, "Hello, World66!\r\n", 17, &bw);
PRINTF("6CS.\r\n");
res = f_close(&fil2);
PRINTF("6CE.\r\n");
}
}
OSTimeDly(500);
任务B:
if(test == 1)
{
//测试FATFS 2个文件同时打开,有无错误
PRINTF("8OS.\r\n");
res = f_open(&fil, "/HISTORY/CO/FIXPTfixpt20110104.msg", FA_READ | FA_WRITE);
PRINTF("8OE.\r\n");
if(res)
{
PRINTF(" f_open /HISTORY/CO/FIXPTfixpt20110104.msg failed. ret=%d\r\n",res);
}
else
{
res = f_lseek(&fil, f_size(&fil));
res = f_write(&fil, "Hello, World88!\r\n", 17, &bw);
PRINTF("8CS.\r\n");
res = f_close(&fil);
PRINTF("8CE.\r\n");
}
}
OSTimeDly(1000);
以上两个任务在UcOS系统中运行,因为系统频率比较低25Mhz,导致f_open、f_close函数运行占用时间比较久,出现在任务B文件被打开过程中被任务A打断运行的情况
测试中函数运行流畅
f_open
...
res = follow_path(&dj, path);
for (;;) {
res = create_name(dp, &path); /* Get a segment name of the path */ 根据输入的文件全路径一级一级创建目录
if (res != FR_OK) break;
res = dir_find(dp); //寻找存储在falsh上的文件目录,按扇区查找
res = dir_sdi(dp, 0); /* Rewind directory object */先回滚文件目录到根目录,然后一个扇区一个扇区去匹配 create_name 获取的当前目录,匹配成功返回
...
}
for循环直到全部匹配完毕,任何一个目录匹配错误则f_open失败
测试中发现的问题如下:当上个文件打开成功后, FATFS fs变量中保存的当前扇区数据与其记录的扇区内容不对应,即当前真实flash中winsect扇区的内容与win[]不对应,原因是因为任务调度时,FATFS fs是全局变量,任务调度导致fs中的winsect与win[]内容匹配异常。最后导致无法匹配到目录而f_open失败。
typedef struct {
BYTE fs_type; /* Filesystem type (0:N/A) */
BYTE pdrv; /* Physical drive number */
BYTE n_fats; /* Number of FATs (1 or 2) */
BYTE wflag; /* win[] flag (b0:dirty) */
BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */
WORD id; /* Volume mount ID */
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
WORD csize; /* Cluster size [sectors] */
#if FF_MAX_SS != FF_MIN_SS
WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */
#endif
#if FF_USE_LFN
WCHAR* lfnbuf; /* LFN working buffer */
#endif
#if FF_FS_EXFAT
BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */
#endif
#if FF_FS_REENTRANT
FF_SYNC_t sobj; /* Identifier of sync object */
#endif
#if !FF_FS_READONLY
DWORD last_clst; /* Last allocated cluster */
DWORD free_clst; /* Number of free clusters */
#endif
#if FF_FS_RPATH
DWORD cdir; /* Current directory start cluster (0:root) */
#if FF_FS_EXFAT
DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */
DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */
DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */
#endif
#endif
DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */
DWORD fsize; /* Size of an FAT [sectors] */
DWORD volbase; /* Volume base sector */
DWORD fatbase; /* FAT base sector */
DWORD dirbase; /* Root directory base sector/cluster */
DWORD database; /* Data base sector */
DWORD winsect; /* Current sector appearing in the win[] */
BYTE win[FF_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */
} FATFS;
针对以上问题解决方法
一、避免任务中同时打开多个文件,为f_open、f_close 设置互斥信号,该方式虽然导致系统性能下降,但可保证文件系统稳定可靠运行。
如:
FRESULT f_open (
FIL* fp, /* Pointer to the blank file object */
const TCHAR* path, /* Pointer to the file name */
BYTE mode /* Access mode and file open mode flags */
)
{
FRESULT res;
DIR dj;
FATFS *fs;
#if !FF_FS_READONLY
DWORD dw, cl, bcs, clst, sc;
FSIZE_t ofs;
#endif
DEF_NAMBUF
uint8_t err = 0;
if (!fp) return FR_INVALID_OBJECT;
OSMutexPend(fsopen_mutex, MUTEX_FFTIMEOUT_MS, &err);
if(err != OS_ERR_NONE)
printf("OSMutexPend fsopen_mutex err = %d.\r\n", err);
/* Get logical drive number */
mode &= FF_FS_READONLY ? FA_READ : FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_CREATE_NEW | FA_OPEN_ALWAYS | FA_OPEN_APPEND;
res = find_volume(&path, &fs, mode);
if (res == FR_OK) {
dj.obj.fs = fs;
INIT_NAMBUF(fs);
// printf("lfnb=%08x\r\n",(fs->lfnbuf));
res = follow_path(&dj, path); /* Follow the file path */
#if !FF_FS_READONLY /* Read/Write configuration */
if (res == FR_OK) {
if (dj.fn[NSFLAG] & NS_NONAME) { /* Origin directory itself? */
res = FR_INVALID_NAME;
}
#if FF_FS_LOCK != 0
else {
res = chk_lock(&dj, (mode & ~FA_READ) ? 1 : 0); /* Check if the file can be used */
}
#endif
}
/* Create or Open a file */
if (mode & (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW)) {
if (res != FR_OK) { /* No file, create new */
if (res == FR_NO_FILE) { /* There is no file to open, create a new entry */
#if FF_FS_LOCK != 0
res = enq_lock() ? dir_register(&dj) : FR_TOO_MANY_OPEN_FILES;
#else
res = dir_register(&dj);
#endif
}
mode |= FA_CREATE_ALWAYS; /* File is created */
}
else { /* Any object with the same name is already existing */
if (dj.obj.attr & (AM_RDO | AM_DIR)) { /* Cannot overwrite it (R/O or DIR) */
res = FR_DENIED;
} else {
if (mode & FA_CREATE_NEW) res = FR_EXIST; /* Cannot create as new file */
}
}
if (res == FR_OK && (mode & FA_CREATE_ALWAYS)) { /* Truncate the file if overwrite mode */
#if FF_FS_EXFAT
if (fs->fs_type == FS_EXFAT) {
/* Get current allocation info */
fp->obj.fs = fs;
init_alloc_info(fs, &fp->obj);
/* Set directory entry block initial state */
mem_set(fs->dirbuf + 2, 0, 30); /* Clear 85 entry except for NumSec */
mem_set(fs->dirbuf + 38, 0, 26); /* Clear C0 entry except for NumName and NameHash */
fs->dirbuf[XDIR_Attr] = AM_ARC;
st_dword(fs->dirbuf + XDIR_CrtTime, GET_FATTIME());
fs->dirbuf[XDIR_GenFlags] = 1;
res = store_xdir(&dj);
if (res == FR_OK && fp->obj.sclust != 0) { /* Remove the cluster chain if exist */
res = remove_chain(&fp->obj, fp->obj.sclust, 0);
fs->last_clst = fp->obj.sclust - 1; /* Reuse the cluster hole */
}
} else
#endif
{
/* Set directory entry initial state */
cl = ld_clust(fs, dj.dir); /* Get current cluster chain */
st_dword(dj.dir + DIR_CrtTime, GET_FATTIME()); /* Set created time */
dj.dir[DIR_Attr] = AM_ARC; /* Reset attribute */
st_clust(fs, dj.dir, 0); /* Reset file allocation info */
st_dword(dj.dir + DIR_FileSize, 0);
fs->wflag = 1;
if (cl != 0) { /* Remove the cluster chain if exist */
dw = fs->winsect;
res = remove_chain(&dj.obj, cl, 0);
if (res == FR_OK) {
res = move_window(fs, dw);
fs->last_clst = cl - 1; /* Reuse the cluster hole */
}
}
}
}
}
else { /* Open an existing file */
if (res == FR_OK) { /* Is the object exsiting? */
if (dj.obj.attr & AM_DIR) { /* File open against a directory */
res = FR_NO_FILE;
} else {
if ((mode & FA_WRITE) && (dj.obj.attr & AM_RDO)) { /* Write mode open against R/O file */
res = FR_DENIED;
}
}
}
}
if (res == FR_OK) {
if (mode & FA_CREATE_ALWAYS) mode |= FA_MODIFIED; /* Set file change flag if created or overwritten */
fp->dir_sect = fs->winsect; /* Pointer to the directory entry */
fp->dir_ptr = dj.dir;
#if FF_FS_LOCK != 0
fp->obj.lockid = inc_lock(&dj, (mode & ~FA_READ) ? 1 : 0); /* Lock the file for this session */
if (fp->obj.lockid == 0) res = FR_INT_ERR;
#endif
}
#else /* R/O configuration */
if (res == FR_OK) {
if (dj.fn[NSFLAG] & NS_NONAME) { /* Is it origin directory itself? */
res = FR_INVALID_NAME;
} else {
if (dj.obj.attr & AM_DIR) { /* Is it a directory? */
res = FR_NO_FILE;
}
}
}
#endif
if (res == FR_OK) {
#if FF_FS_EXFAT
if (fs->fs_type == FS_EXFAT) {
fp->obj.c_scl = dj.obj.sclust; /* Get containing directory info */
fp->obj.c_size = ((DWORD)dj.obj.objsize & 0xFFFFFF00) | dj.obj.stat;
fp->obj.c_ofs = dj.blk_ofs;
init_alloc_info(fs, &fp->obj);
} else
#endif
{
fp->obj.sclust = ld_clust(fs, dj.dir); /* Get object allocation info */
fp->obj.objsize = ld_dword(dj.dir + DIR_FileSize);
}
#if FF_USE_FASTSEEK
fp->cltbl = 0; /* Disable fast seek mode */
#endif
fp->obj.fs = fs; /* Validate the file object */
fp->obj.id = fs->id;
fp->flag = mode; /* Set file access mode */
fp->err = 0; /* Clear error flag */
fp->sect = 0; /* Invalidate current data sector */
fp->fptr = 0; /* Set file pointer top of the file */
#if !FF_FS_READONLY
#if !FF_FS_TINY
mem_set(fp->buf, 0, FF_MAX_SS); /* Clear sector buffer */
#endif
if ((mode & FA_SEEKEND) && fp->obj.objsize > 0) { /* Seek to end of file if FA_OPEN_APPEND is specified */
fp->fptr = fp->obj.objsize; /* Offset to seek */
bcs = (DWORD)fs->csize * SS(fs); /* Cluster size in byte */
clst = fp->obj.sclust; /* Follow the cluster chain */
for (ofs = fp->obj.objsize; res == FR_OK && ofs > bcs; ofs -= bcs) {
clst = get_fat(&fp->obj, clst);
if (clst <= 1) res = FR_INT_ERR;
if (clst == 0xFFFFFFFF) res = FR_DISK_ERR;
}
fp->clust = clst;
if (res == FR_OK && ofs % SS(fs)) { /* Fill sector buffer if not on the sector boundary */
if ((sc = clst2sect(fs, clst)) == 0) {
res = FR_INT_ERR;
} else {
fp->sect = sc + (DWORD)(ofs / SS(fs));
#if !FF_FS_TINY
if (disk_read(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) res = FR_DISK_ERR;
#endif
}
}
}
#endif
}
// printf("lfnb=%08x\r\n",(fs->lfnbuf));
FREE_NAMBUF();
}
if (res != FR_OK) fp->obj.fs = 0; /* Invalidate file object on error */
err = OSMutexPost(fsopen_mutex);
if(err != OS_ERR_NONE)
printf("OSMutexPost fsopen_mutex err = %d.\r\n", err);
LEAVE_FF(fs, res);
}
FRESULT f_close (
FIL* fp /* Pointer to the file object to be closed */
)
{
FRESULT res;
FATFS *fs;
uint8_t err = 0;
OSMutexPend(fsopen_mutex, MUTEX_FFTIMEOUT_MS, &err);
if(err != OS_ERR_NONE)
printf("OSMutexPend fsopen_mutex err = %d.\r\n", err);
#if !FF_FS_READONLY
res = f_sync(fp); /* Flush cached data */
if (res == FR_OK)
#endif
{
res = validate(&fp->obj, &fs); /* Lock volume */
if (res == FR_OK) {
#if FF_FS_LOCK != 0
res = dec_lock(fp->obj.lockid); /* Decrement file open counter */
if (res == FR_OK) fp->obj.fs = 0; /* Invalidate file object */
#else
fp->obj.fs = 0; /* Invalidate file object */
#endif
#if FF_FS_REENTRANT
unlock_fs(fs, FR_OK); /* Unlock volume */
#endif
}
}
err = OSMutexPost(fsopen_mutex);
if(err != OS_ERR_NONE)
printf("OSMutexPost fsopen_mutex err = %d.\r\n", err);
return res;
}
二、实现FATFS的可重入功能
#define FF_FS_REENTRANT 0
#define FF_FS_TIMEOUT 1000
#define FF_SYNC_t HANDLE
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
/ module itself. Note that regardless of this option, file access to different
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
/ to the same volume is under control of this function.
/
/ 0: Disable re-entrancy. FF_FS_TIMEOUT and FF_SYNC_t have no effect.
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj()
/ function, must be added to the project. Samples are available in
/ option/syscall.c.
/
/ The FF_FS_TIMEOUT defines timeout period in unit of time tick.
/ The FF_SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*,
/ SemaphoreHandle_t and etc. A header file for O/S definitions needs to be
/ included somewhere in the scope of ff.h. */