FileX + LevelX中异常错误处理:
移植FileX+LevelX实现基于NorFlash的文件系统,访问文件系统时,出现错误返回FX_IO_ERROR 时,寻找隐藏在背后的真实原因。问题描述:
在STM32上移植FileX及LevelX后,对使用的NorFlash文件系统进行测试,通过使用反复写入大量数据与文件的方法,考察文件系统稳定性。测试中发现,当空闲扇区耗尽时,不仅创建新文件时会报FX_INVALID_PATH错误,读取文件时也会报FX_IO_ERROR 错误,误导调试方向。原因分析:
测试中,在写入大量文件后,访问文件系统时出现奇怪的错误返回,读取文件、写入文件、创建文件、遍历目录都异常出错。跟踪源码运行后分析,是由于空闲扇区耗尽所致。在文件系统内部实现的各类访问操作时,会频繁修改文件分区中的BootSector内容,引起扇区的更新写入操作。当访问调用写扇区的操作时,由于无足够空闲扇区引发访问错误.
刚开始出现问题时,是发生在打开文件并读取时,调用文件读操作时报错,没想到读取背后还隐藏了写操作,因而误导了调试精力,一直在排查Flash的读取驱动程序上。
原始的错误是发生在内部的写入扇区操作时,调用了驱动层的 _lx_nor_flash_sector_write 函数,其中在调用 lx_nor_flash_physical_sector_allocate 函数获取空闲扇区失败,此时设置了错误码为 LX_NO_SECTORS,反映的是真实的错误原因:
else
{
/* Indicate the write was unsuccessful. */
status = LX_NO_SECTORS;
}
/* Return the completion status. */
return(status);
但是在其上一级的驱动框架中,驱动函数是没有返回值的空返回函数。在判断了返回值不为 LX_SUCCESS 的话,设置了系统控制数据结构中的驱动状态数据后,直接返回:
/* Determine if the write was successful. */
if (status != LX_SUCCESS)
{
/* Return an I/O error to FileX. */
media_ptr -> fx_media_driver_status = FX_IO_ERROR;
return;
}
因此更上一级的访问接口 _fx_utility_logical_sector_read 在调用驱动层函数时,无法感知真实错误原因,只能通过引用上述控制结构中的状态变量来判断结果:
/* Invoke the driver to read the sector. */
(media_ptr -> fx_media_driver_entry) (media_ptr); //调用底层驱动框架中的接口,该接口无直接返回值
/* Clear the system write flag. */
media_ptr -> fx_media_driver_system_write = FX_FALSE;
/* Check for successful completion. */
if (media_ptr -> fx_media_driver_status) //而是判断系统控制数据中的状态变量
{
/* Error writing a cached sector out. Return the
error status. */
return(media_ptr -> fx_media_driver_status); //错误时,直接返回了该状态变量
}
这样,访问的错误码就从原始的 LX_NO_SECTORS 转而变成了 FX_IO_ERROR。
如果以上流程发生在读取文件时,就会获得一个 FX_IO_ERROR 错误码,掩盖了真实的“空间不足”错误原因(LX_NO_SECTORS)。这导致在不熟悉FileX+LevelX系统时,调试中很难推测到,打开文件读取数据失败的原因,是因为系统中没有空闲扇区造成的。
如果该流程发生在创建文件时的 fx_file_create 调用中,在上述流程调用返回 FX_IO_ERROR 后,还会继续向下运行。在创建文件时,上面的流程发生在查询文件是否已存在的操作中,如果返回为 FX_SUCCESS 表明在文件系统中查询到了该文件,返回 FX_ALREADY_CREATED。但如果在查询过程中遇到上述错误,没有完成查询就在半路上返回时了FX_IO_ERROR ,后续的判断认为是没有查找到该文件存在,可以继续创建该文件。但在接下来创建文件时,由于之前调用查询函数时是非正常返回,查询结果异常,而在后续的操作中又用到该查询结果,进而出现错误返回:
/* Search the system for the supplied file name. */
status = _fx_directory_search(media_ptr, file_name, &dir_entry, &search_directory, &name_ptr);
/* Determine if the search was successful. */
if (status == FX_SUCCESS) //返回成功,表明找到了要创建的文件名
{
#ifdef FX_ENABLE_FAULT_TOLERANT
FX_FAULT_TOLERANT_TRANSACTION_FAIL(media_ptr);
#endif /* FX_ENABLE_FAULT_TOLERANT */
/* Release media protection. */
FX_UNPROTECT
/* File found - Return the error code. */
return(FX_ALREADY_CREATED);
}
//返回非 FX_SUCCESS 时,表明没找到要创建的文件名
/* Determine if there is anything left after the name. */
if (_fx_directory_name_extract(name_ptr, &dir_entry.fx_dir_entry_name[0]))
{
//此时由于上一步调用异常返回,导致dir_entry.fx_dir_entry_name[0]中的内容不确定,函数_fx_directory_name_extract可能执行错误
/* Release media protection. */
FX_UNPROTECT
/* Extra information after the file name, return an invalid path
error. */
return(FX_INVALID_PATH);
}
此时返回的错误码转变为 FX_INVALID_PATH,进一步掩盖了真相,一度以为是文件名及目录路径不合法,反复修改无果。
实际导致应用层面对的错误过程是:创建一个新文件时,原本由于空间不足而返回的 LX_NO_SECTORS 错误,最终被返回为 FX_INVALID_PATH,使调用者以为是创建的文件的路径或命名不合法,花时间在文件名本身的内容上反复修改调试,误导调试方向。
解决方案:
当系统中的空闲扇区数为0时,基本无法对文件系统进行任何操作,包括文件读取与删除,只能重新格式化,造成数据丢失。因此创建新文件、或添加文件内容时,先获取并判断当前文件系统的空闲扇区是否满足本次存储的空间要求,再进行下一步操作,避免在超空间的写入时耗尽所有有效扇区,导致文件系统异常。以下是测试输出结果,可以看出,在读取目录信息、读取文件过程中,如果遇到Block中的失效扇区数量高时,会发生扇区移动及Block擦除操作,释放已失效的扇区,增加有效空余空间。
Now start fill FS.
Loop 1 -> free space: 58880, free sector: 110
Loop 2 -> free space: 58368, free sector: 106
Loop 3 -> free space: 57856, free sector: 102
Loop 4 -> free space: 57344, free sector: 98
Loop 5 -> free space: 56832, free sector: 94
Loop 6 -> free space: 56320, free sector: 90
Loop 7 -> free space: 55808, free sector: 86
Loop 8 -> free space: 54784, free sector: 81
Loop 9 -> free space: 54272, free sector: 77
Loop 10 -> free space: 53760, free sector: 73
Loop 11 -> free space: 53248, free sector: 69
Loop 12 -> free space: 52224, free sector: 62
Loop 13 -> free space: 51200, free sector: 54
Loop 14 -> free space: 50176, free sector: 47
Loop 15 -> free space: 49152, free sector: 40
Loop 16 -> free space: 47616, free sector: 32
Loop 17 -> free space: 46592, free sector: 24
Loop 18 -> free space: 45568, free sector: 17
Start erease block 0.
Loop 19 -> free space: 44544, free sector: 21
Start erease block 1.
Loop 20 -> free space: 43520, free sector: 25
Loop 21 -> free space: 42496, free sector: 17
Start erease block 2.
Loop 22 -> free space: 41984, free sector: 23
Loop 23 -> free space: 41472, free sector: 18
Start erease block 3.
Loop 24 -> free space: 40448, free sector: 21
Start erease block 4.
Loop 25 -> free space: 39424, free sector: 23
Loop 26 -> free space: 38400, free sector: 15
End fill FS, loop cnt: 26
Start test FS.
List content in dir /:
--------------------------------------------------------
Name Size Attr Time
Start erease block 6.
Data 0 00000010 2017-1-1 0:0:0
--------------------------------------------------------
Total dirs: 1 total files: 0
Used bytes: 0 free bytes: 38400 free sector: 25
List content in dir /Data:
--------------------------------------------------------
Name Size Attr Time
. 0 00000010 2017-1-1 0:0:0
.. 0 00000010 2017-1-1 0:0:0
file-1.txt 968 00000020 2017-1-1 0:0:0
file-2.txt 1012 00000020 2017-1-1 0:0:0
file-3.txt 1056 00000020 2017-1-1 0:0:0
file-4.txt 1100 00000020 2017-1-1 0:0:0
file-5.txt 1144 00000020 2017-1-1 0:0:0
file-6.txt 264 00000020 2017-1-1 0:0:0
file-7.txt 308 00000020 2017-1-1 0:0:0
file-8.txt 352 00000020 2017-1-1 0:0:0
file-9.txt 396 00000020 2017-1-1 0:0:0
file-10.txt 450 00000020 2017-1-1 0:0:0
file-11.txt 495 00000020 2017-1-1 0:0:0
file-12.txt 540 00000020 2017-1-1 0:0:0
file-13.txt 585 00000020 2017-1-1 0:0:0
file-14.txt 630 00000020 2017-1-1 0:0:0
file-15.txt 675 00000020 2017-1-1 0:0:0
file-16.txt 720 00000020 2017-1-1 0:0:0
file-17.txt 765 00000020 2017-1-1 0:0:0
file-18.txt 810 00000020 2017-1-1 0:0:0
file-19.txt 855 00000020 2017-1-1 0:0:0
file-20.txt 900 00000020 2017-1-1 0:0:0
file-0.txt 924 00000020 2017-1-1 0:0:0
--------------------------------------------------------
Total dirs: 2 total files: 21
Used bytes: 14949 free bytes: 38400 free sector: 25