本文深入源码讲述了Contiki文件系统Coffee的格式化,即擦除整个FLASH,并初始化结构体protected_mem
。
1. 格式化
1.1 硬盘格式化
(1)低级格式化
低级格式化就是将空白的磁盘划分出柱面和磁道,再将磁道划分为若干个扇区,每个扇区又划分出标识部分ID、间隔区GAP和数据区DATA等。可见,低级格式化是高级格式化之前的一件工作,低级格式化只能针对一块硬盘而不能支持单独的某一个分区。每块硬盘在出厂时,已由硬盘生产商进行低级格式化,因此通常使用者无需再进行低级格式化操作。其实,我们对一张软盘进行的全面格式化就是一种低级格式化。对于硬盘上出现逻辑坏道或者软性物理坏道,用户可以试试使用低级格式化来达到屏蔽坏道的作用,这样能在一定程度上保证用户数据的可靠性,但坏道却会随着硬盘分区、格式化次数的增长而扩散蔓延[3]。
(2)高级格式化
高级格式化又称逻辑格式化,它是指根据用户选定的文件系统(如FAT12、FAT16、FAT32、NTFS、EXT2、EXT3等),在磁盘的特定区域写入特定数据,以达到初始化磁盘或磁盘分区、清除原磁盘或磁盘分区中所有文件的一个操作。高级格式化包括对主引导记录中分区表相应区域的重写、根据用户选定的文件系统,在分区中划出一片用于存放文件分配表、目录表等用于文件管理的磁盘空间,以便用户使用该分区管理文件[4]。
1.2 Coffee格式化
跟硬盘格式化类似,FLASH低级格式化(可以如此理解,也许根本就没有,由产家弄好了)已经把FLASH按块-页分好了,Coffee格式化类似于硬盘的高级格式化,但不尽相同。第一使用Coffee文件系统,必须进行格式化,将整个FLASH擦除(FLASH物理特性,先擦除后写)。
2. cfs_coffee_format
cfs_coffee_format
主要工作将Coffee管理的FLASH区域全部写入“0”(而不是擦除,极易被COFFEE_ERASE这个名字误导),以及初始化结构体protected_mem
,源码如下:
int cfs_coffee_format(void)
{
unsigned i;
PRINTF("Coffee: Formatting %u sectors", COFFEE_SECTOR_COUNT);
*next_free = 0;
for (i = 0; i < COFFEE_SECTOR_COUNT; i++)
{
COFFEE_ERASE(i);
PRINTF(".");
}
memset(&protected_mem, 0, sizeof(protected_mem)); /* Formatting invalidates the file information.*/
PRINTF(" done!\n");
return 0;
}
2.1 next_free
next_free
是protected_mem_t
结构体的一个成员变量,指向下一个空闲的页,初始化为0
,源码如下:
static struct protected_mem_t
{
struct file coffee_files[COFFEE_MAX_OPEN_FILES];
struct file_desc coffee_fd_set[COFFEE_FD_SET_SIZE];
coffee_page_t next_free;
char gc_wait;
} protected_mem;
static struct file *const coffee_files = protected_mem.coffee_files;
static struct file_desc *const coffee_fd_set = protected_mem.coffee_fd_set;
static coffee_page_t *const next_free = &protected_mem.next_free;
static char *const gc_wait = &protected_mem.gc_wait;
2.2 COFFEE_ERASE
Coffee格式化是按区擦除,可以把COFFEE_SECTOR_SIZ
E设成几倍的块大小(片外FLASH,即NAND FLASH)或者几倍的页大小(片上FLASH,即NOR FLASH,按页擦除或整个FLASH擦除),这将有利于容量大的存储设备。[2]是这么说的,COFFEE_SECTOR_SIZE
用于应付大的存储设备(比如SD卡),在这种情况下,将其设置大一点可加快顺序扫描速度。
COFFEE_ERASE
宏被定义到硬件相关的擦除函数(这也是Coffee文件移植需做的事,在cfs-coffee-arch.h
文件中),如下:
#define COFFEE_ERASE(sector) stm32_flash_erase(sector)
stm32_flash_erase
函数源代码(在cfs-coffee-arch.c
中)如下:
void stm32_flash_erase(u8_t sector)
{
u32_t addr = COFFEE_START + (sector) *COFFEE_SECTOR_SIZE;
u32_t end = addr + COFFEE_SECTOR_SIZE;
/* This prevents from accidental write to CIB. */
if (!(addr >= FLASH_START && end <= (FLASH_START+FLASH_PAGES*COFFEE_SECTOR_SIZE)))
{
return ;
}
FLASH_Unlock();
FLASH_ErasePage(addr);
}
咋一看,上述的代码有只有当COFFEE_SECTOR_SIZE
(逻辑区大小)与FLASH_PAGE_SIZE
(FLASH物理页大小)相等的时候,才不会有问题。若COFFEE_SECTOR_SIZE
大于FLASH_PAGE_SIZE
,但FLASH_ErasePage
只擦除一页,那余下的就没擦除了;倘若COFFEE_SECTOR_SIZE
小于FLASH_PAGE_SIZE
,那还多擦除了部分。在我们的例子,将COFFEE_SECTOR_SIZE
被设成FLASH物理页大小FLASH_PAGE_SIZE
,所以没出现问题。
细心的你会发现,即便如此也还有问题,因为FLASH是页读写,按块擦除,而块通常由若干页组成,而代码却是按页擦除。但你忽略了另一个问题,平台相关性,我们例子的FLASH是片上FLASH,即NOR FLASH,与内存统一编址,可以随机读取,按页擦除或者整块擦除,所以一直没出问题。
注意:这是我之前的理解,后来发现有误。事实上,COFFEE_ERASE
并不是将FLASH擦除,而是将FLASH写入“0”,修改后的源代码如下:
void stm32_flash_erase(u8_t sector)
{
u32_t data = 0;
u32_t addr = COFFEE_START + (sector) * COFFEE_SECTOR_SIZE;
u32_t end = addr + COFFEE_SECTOR_SIZE;
if(!(addr>=COFFEE_START && end<=COFFEE_START+COFFEE_SIZE)) //确保地址在Coffee管理的区间
{
return;
}
FLASH_Unlock();
FLASH_ErasePage(addr); //先擦除 NOTE:assume COFFEE_SECTOR_SIZE=FLASH_PAGE_SIZE
for(; addr < end; addr += 4)
{
if(FLASH_ProgramWord(addr, data) != FLASH_COMPLETE)
{
PRINTF("FLASH_ProgramHalfWord Error.\n");
}
}
FLASH_Lock();
}
2.3 memset
memset
函数功能是将一段内存块填充某个给定的值,通常用于对较大结构体或数组清零操作。memset(void *s, int c, size_t n)
,即将从地址s
开始往后的n
个字节都设成c
[5]。在这里,将protected_mem
结构体(见2.1)的成员变量都设成0
(数组的每个元素也为0)。奇怪了,实现这个函数怎么会在Install_Software\IAR Systems\Embedded Workbench 5.4\arm\INC
呢?源码如下,完全看不懂:-(
#define _DLIB_STRING_SKIP_INLINE_MEMSET
#pragma inline
void *memset(void *_D, int _C, size_t _N)
{
__aeabi_memset(_D, _N, _C);
return _D;
}
至此Coffee文件系统格式化完毕:-)
参考资料:
[1] Enabling Large-Scale Storage in Sensor Networks with the Coffee File System.pdf
[2]
[3] 文章《》
[4] 维基百科词条:
[5] 博文《memset用法详解》