引 言
文件在现代电子设备中非常重要。处理器通过操作系统对不同类型的文件进行访问,但是在嵌入式领域和低功耗定制 Linux解决方案中,相关的方案比较少见。很多嵌入式开发者习惯把数据放在代码中,导致每次改动都需要升级固件代码。JesFS嵌入式文件系统就是要解决这类问题——专门为小型低功耗设备设计。 电影《终结者2-审判日》中,机器人T 800(施瓦辛格)说过“我有详细的文件”,以此来表明他是一个强大的战斗机器人。这里说到了关键:文件无疑是高科技产品的关键元素! 这一点,同样适用于所有的嵌入式系统。这篇文章将为您介绍一个合适的嵌入式文件系统。
文件与代码
文件本质是一个基本的容器,用来组织和传输各种类型的信息。大到服务器,小到单片机,都可以理解这些信息,操作系统针对不同的文件提供了接口。 在嵌入式系统领域和低功耗定制 Linux解决方案中,相关方案很少。在很多重要项目中,代码和数据没有分离,常常导致项目失败。举例来说,系统参数放在代码里很容易,但是如果以后想修改,每次都必须升级固件。然而,现在通常的做法是在线更新。 笔者从业于高海拔地区的科学测量和相关仪器领域,几个月前有一次相关的经历:在海拔3000米的山上,为一个老旧设备更改配置。经过精疲力尽的挖掘后,笔者认为通过互联网更新,比在-20 ℃的雪地中挖洞更新要容易得多。最终,笔者决定使用文件系统。 虽然有很多嵌入式文件系统,但这些文件系统大多只适配它们所在的领域。所以笔者决定自己开发一个。最终,JesFS(Jo’sembeddedFileSystem 的缩写)诞生了,面向微小系统和极低功耗的应用。
JesFS
设计JesFS最重要的标准就是可靠性和小封装,同样重要的还有数据完整性和远程固件升级。配合JesFS,还需要开发JesFS的bootloader,帮助处理器读取 AES加密固件。bootloader 和JesFS完全独立,这里不做讨论。Jurgen Wickenhauser 目前全部精力都在 JesFS 上。JesFS 已经是一个重要不可或缺的工具。JesFS在GPL v3[1]协议下开源。
参考实例
JesFS使用标准C编译器编译。接下来将会在TI的CC1310 开发板上展示一些功能。CC1310 基于32位ARM Cortex - M3架构,具有868/915 MHz无线传输功能。
CC13xx/26xx系列处理器的Launchpad开发平台物美价廉,配备TI的CCSTUDIO[3]IDE,可以进行源代码调试。CC13xx/26xx开发平台配备了一片1MB的串行Flash存储器,大小只有2mmx3mm! 免费的TI-RTOS可以提供一个工业级的实时操作系统以及TI大量的技术支持。JesFS 的源代码既可以在Launchpad开发平台上编译,也可以使用 Windows或Linux平台的标准C编译器进行编译,但是需要具有模拟Flash存储器驱动的支持。
小型 Flash存储芯片
嵌入式系统通常不需要太多的存储空间,1~16 MB 就足够了。Flash存储芯片现在可以制造得非常小:1 MB Flash可以小到1.24 mmx1.29 mm(见图2)。
JesFS文件系统本身占用的空间非常小,仅仅占用RAM的200字节就可以运行得很好。还有一方面是经济原因:许多大型文件系统使用RAM作为缓存,不仅很复杂,占用很多的存储空间,而且切换操作模式(比如休眠或关机)会很慢。Jes-FS设计之初就考虑到低功耗的问题,可以在几毫秒内实现休眠和唤醒。而且,串行存储器在睡眠模式下消耗电流小于1μA,非常适合电池供电设备。
针对 loT 的独有功能
JesFS的一个基本特性是:可以把嵌入式系统的文件通过互联网自动地镜像存储到服务器,服务器因此具有嵌入式文件系统的一个实时更新的副本。这个特性也可以用在分钟级获取数据的场景,但是为了降低功耗,一般不会频繁通过互联网对比更新服务器副本(通常几小时一次)。 即使传输了新文件、远程升级了固件,只要有需要,JesFS就可以通过特殊标志位(时间戳和CRC32 哈希校验)很容易地从服务器上找回文件。如果展开这些特殊的用法将有无数种可能的应用场景。所有的特性和脚本,作者都经过仔细测试。关于FesFS文件系统功能的详细描述,可以参考相关链接[1]。
解决目标:未关闭的文件
对大多数文件系统来说,如果没有打开文件的写入权限,就只能读文件。那么问题来了:一个异常可能导致未关闭文件,甚至最坏的情况下导致数据丢失。而JesFS不会出现这种情况,文件在任何时候都可以打开读写,而且不会导致数据丢失。这个特性非常适合日志文件,例如,嵌入式系统存储新数据,同时服务器更新到最新数据。
JesFS 的限制
JesFS甩掉沉重的负担后,访问次数还没有调整到最优。未来还将对未关闭文件做一些小的调整和限制。因为是针对资源有限的小型嵌入式系统设计的简单文件系统,所以对文件名长度进行了限制,最多21个字符。为了减轻CPU负担,也没有实现索引数据。总体来说,就是一个扁平化的文件系统。针对小型系统的实际应用,这些限制不应引起任何相关的限制。JesFS文件系统的简洁和易用,可以大大提高效率。作者本人就是最严格的测试者。
串行 Flash存储器
设计软件细节之前,先介绍几个关于Flash存储器的基本知识。Flash存储器有许多种类型,在嵌入式系统领域主要会用到串行NOR Flash芯片。为了能让JesFS更好地适配硬件,有些需要详细说明,同时也涉及到一些底层驱动的开发。 总体来说,几乎所有相关的Flash存储器都有类似的结构,只是在容量、静态电流、最大时钟速率、数据位数方面有所不同。所有串行 NOR Flash芯片都有6个共同特性: ①空存储器,默认存储位为‘1’; ②写入数据时,根据需要把存储位‘1’设置成‘0’; ③相反,擦除操作把数据位从‘0’恢复成‘1’; ④每一个写操作可以写入1~256字节数据,大概需要1 ms; ⑤数据以块或扇区或全部擦除,扇区通常4KB,擦除一个扇区需要20~200 ms(扇区擦除通常很少用到); ⑥擦除操作是有次数限制的,通常不超过100 000次。 访问串行 Flash 芯片很容易,只需要进行4步操作(见图): ①SELECT引脚电平从1到0后的几纳秒内,芯片激活; ②通过CLK引脚(时钟)和DIN引脚(数据)写入数据和命令; ③通过CLK(时钟)引脚和DOUT(数据/状态)引脚读取数据和状态; ④SELECT 引脚电平从0到1,结束操作。
图 8脚串行 Flash 引脚分配
串行 Flash 芯片最大时钟速率通常很高(>50MHz),多数微处理器的硬件驱动不能充分利用。这里参考设计平台使用的CC1310处理器的12 MHz时钟速率,可以达到很不错的1MB/s的数据吞吐量。 下面推荐适用于小型系统的通用存储芯片,虽然有些有兼容的替代芯片,但替代芯片功耗可能会比较高: ◆MX25R8035F (1 MB) to MX25R1635F (16 MB) ◆W25Q80DL (1 MB) to W25Q128JV (16 MB) ◆GD25Q16 (2 MB)
黑盒Demo:家用飞行记录仪
许多嵌入式设备在几年的生命周期内都可以无错误运行,但偶尔也会产生错误。产生错误时,我们会问:“发生了什么?”要回答这个问题,我们需要有足够的历史日志数据分析和诊断。举例来说,内部参数(如温度、电压、湿度)是否发生了改变?是否有人进行了调整?之前是否有过类似的问题?这些问题与飞机上的黑盒子相似。 然而,嵌入式系统通常没有足够的空间做这些。不过,JesFS文件系统的未关闭文件可以稳定、高效地记录诊断数据。JesFS可以避免产生僵尸数据,即使是在复位或断电的极端情况下,也可以写入数据。 依靠两个文件来实现这一目的: ◆ 主文件Data. pri 可以写入最大数据长度为HIS-TORY定义的长度; ◆ 改名为辅助名Data. sec; ◆ 新创建的辅助文件替换原有的辅助文件。 通过定义HISTORY,可以得到特定长度的历史数据,从而确保历史数据的历史记录数量是一个常量,且不会超过2倍的历史记录。通过检索数据来获得正确的顺序,首先是Data. sec,然后是 Data. pri。 可以在相关链接[1]中找到针对各种编译器(PC和嵌入式)的BlackBox演示的完整的文档化源代码,包括Jes-FS的源代码文件。这里打印的清单显示了代码的摘录:
***************************************
*log_blackbox(char * logtext, uint16_t len)
*Thisfunktion logs one line to the the history
*****************************************
int16_tlog_blackbox(char* logtext, uint16_tlen){
FS_DESCfs_desc,fs_desc_sec; //2 JesFs file descriptors int16_t res;
res=fs_start(FS_START_RESTART);
//(fast) WAKE JesFs (might be sleeping)if(res) return res;
//Flags (seedocu) : CREATE File if not exists and open in
//RAW mode,(RAW needed because in RAW -Mode file is not truncated if existing)
res=fs_open( &fs_desc,"Data, pri",SF_OPEN_CREATE SF_OPEN_RAW);
if(res)return res;
//Place (internal) file pointer to the end of the file to allow write fs_read(&fs_desc,NULL,0xFFFFFFFF);
//(dummy) read as much as possible
//write the new data (ASCII, from function rguments) to
//the file
res=fs_write(&fs_desc,logtext,tlen);
if(res) return res;
//Show what was written
uart_printf("Pos:%u Log: %s",fs_desc. file_len, "logtext);
//Now make a file shift if more data than defined in HISTORY if(fs_desc. file_len>= HISTORY){
uart_printf("Shift 'Data. pri' ->'Data. sec' ");
//Optionally delete and (butCREATE in any case) backup file
res= fs_open (&fs_desc_sec,"Data. sec", SF_OPEN if(res)return res;CREATE);
if(res) return res;
//rename (full) data file to secondary file
res=fs_rename( &fs_desc, &fs_desc_sec);
if(res) return res;
}
fs_deepsleep(); //Set Filesystem to UltraLowPowerMode
return0; //OK
驱动程序基础
JesFS使用上层和底层驱动程序。底层驱动程序针对串行Flash,再次被划分为通用命令 JesFS_ml.c和串行SPI的硬件控制部分(这里是jesfs_tirto. c)。上层驱动程序在相关链接[1]中有详细描述。 闪存的处理非常简单(大多数闪存芯片使用的特殊命令在这里不使用),但是很少有命令是相关的,幸运的是,对于几乎所有可用的串行 Flash 芯片都是相同的。对于更大的容量(≥32MB),也只是把地址从3字节扩展到了4字节。 源代码文件更精确地记录了命令。以下是十六进制记数法的命令概述。 9Fh:读取一个3字节的ID(十六进制),其中对制造商、类型和容量(十六进制2的乘方)进行了编码。对于在CC1310中的MX25R8035芯片,这个ID是“C22814”,其中C2h=Macronix,28h=Type, 14h=20=220=1 MB。 B9h:让闪存芯片进入深度睡眠。 ABh:在5~50 μs内唤醒闪存芯片。 03h:读操作。首先,准备读取3字节地址(对32 MB 的容量来说是4字节),读取完数据后,cs#变高。 06h:必须在每次写/删除操作之前发送。 02h:写操作。首先发送3或4个地址字节,然后是1~256字节数据,不超过256字节。 20h:删除扇区。首先发送3或4个地址字节,然后删除相关的4 KB扇区。 05h:测试之前的写/删除命令是否已经完成。
Flash中的组织结构
实现的细节和图形可以在JesFS[1]的文档和源代码中找到,这里只画了结构。JesFS将闪存分为3种不同的扇区类型。每个逻辑扇区与闪存芯片中的一个物理4KB扇区对齐。因此,一个容量为1 MB的闪存芯片最多可以存储255个文件。 扇区0具有特殊的意义,因为它用作文件索引。每个索引条目占用4个字节。因此,可以将索引视为一个包含1024个"无符号长整数"的数组。对于0扇区,必须遵守以下规定: ◆ 索引扇区0仅在格式化期间删除; ◆ 前3个条目包含格式化信息 Magic value、Flash ID (参见命令9Fh)和格式化日期(从 1970.1.1开始的Unix秒数),FFFF FFFFh值被计算为无效/未格式化; ◆ 其余的1021项是文件heads的起始地址,所有这些项必须能被212=4096(扇区大小)整除(JesFS 会检查)。理论上,最多可以有1021个文件。 对于1至n扇区有5种可能: ◆ 扇区为空(包含FFh值的完整字节); ◆ 扇区是活跃文件的头; ◆ 扇区是指向已删除文件的头; ◆ 扇区是活跃文件的一部分; ◆ 扇区是活跃文件中已删除的部分。 head是索引指向的扇区。因此,对heads进行特殊处理是很重要的,因为只有在格式化时才会删除索引中的值。每个扇区总是以3个(或12个 heads)"unsigned long"类型的值开始: ◆ 使用magic value(头,文件或空); ◆ 此区块的所有者(引用头部,如果该块本身是头部,则引用FFFF FFFFh值); ◆指向该文件的下一个区块的指针,或者FFFF FFFFh(如果这是链的最后一个块); 然后要么是文件数据,要么是一个head: ◆文件长度,以字节或FFFF FFFFh表示; ◆ 可选的CRC32 哈希校验(如果在CRC32 模式下写入并关闭的); ◆ 文件的创建日期(自1970.1.1起,以Unix秒为单位); ◆ 文件名称(最多21个字符,不包括最终字符0h); ◆文件的打开标志(1字节); ◆ 一个被保留的空字节。 一个 head总是占用48个字节,一个普通的数据扇区占用12个字节,起始部分是管理信息。其结构如图所示。
图 索引总是指向文件头的扇区0
删除扇区仅仅做个标记。所使用的Magic values是这样选择的:通过使用“0”进行额外的覆盖(发生得非常快)来主动删除状态。只有当一个扇区确实需要,并且没有空闲扇区的时候,才能通过命令20h删除,这需要更多的时间。
磨损平衡和未关闭文件
使用Flash时,所有扇区的使用频率都应该相等,这一点非常重要,因为通常只能擦写约10万次。所以你可以在很短的时间内在一个循环中“杀死”一个扇区。这种避免策略称为“磨损平衡”。对于 JesFS,这意味着一个文件(由于索引中的固定条目)最多可以被删除100 000次。乍一看,擦写次数似乎很多,但对于嵌入式系统来说,这个限制在某些应用程序中肯定是有问题的。另一方面,应该清楚,即使每小时每个扇区擦写一次,也会经过11年。对大多数项目来说,还是很充裕的。 为了尽可能少地删除扇区,让活跃或使用过的文件保持打开也是一个办法;被删除的字节的值总是FFh。如果有问题的文件从未使用这个值(例如,只保存 ASCII数据或用转义序列替换FFh),也可以通过简单读取(最多一个FFh字符)来确定文件的结尾。JesFS有一个非常快的子程序,可以在几毫秒内找到一个16 MB文件的结尾。
CRC-32哈希和数据完整性
只有很少的文件经常更改。因此,JesFS在写(和读)一个文件时可以加CRC-32校验,与“文件关闭”中头部的长度一起记录下来。通过这种方式,可以在任何时候通过简单地读取文件来检查文件的完整性。所使用的CRC-32算法是行业标准(ISO 3309等),甚至得到了PHP(一种常见的服务器脚本语言)的支持。 在JesFS开始时(或者在系统每次唤醒之后)执行一个全面的测试来检查JesFS的基本完整性。JesFS非常注意 bug。演示的源代码中还包含一项功能,可以检查完整的JesFS“心脏和肾脏”(即所有扇区和文件)。
引导程序和固件升级
由于JesFS的结构非常简单,即使是一个简单的引导程序也可以轻松地读取文件并测试完整性。但是,引导程序通常只绑定到特定的CPU架构或控制器类型。
测试 JesFS
测试JesFS的最简单方法是使用CC13xx/26xx系列的launchpad开发平台,这些板卡大约30欧元。在Github 上的JesFS文档中,还可以找到一个简单的控制台应用程序的手册。 除此之外,还可以在运行 Windows或Linux的PC上编译JesFS,在RAM中模拟Flash芯片。生成的image文件完全兼容Flash芯片。如果微控制器的数据格式是lit-tle-endian的,可以很容易地通过编程器进行传输或读取,大多数 ARM SoC(如 MSP430、MSP432等)都支持。
下一步做什么?
根据读者的反馈,这个报告仅仅是一个开始。文件系统不是一件简单的事情,但是如果JesFS能够成为一种通用的工具,作者将会非常高兴,并深信它具有惊人的潜力。 当然,JesFS不仅限于CC13xx/26XX系列的控制器,而且在原则上几乎适用于 16位以上的所有处理器。Jurgen Wickenhauser 目前正在为MSP430系列移植Jes-FS,还有许多可以想到的与JesFS结合的项目,特别是无处不在的物联网提供了无限的可能性。如果你真的感兴趣,可以通过作者的电子邮件(joembedded@gmail. com)联系。
相关链接
[1] JesFS V1.1 auf Github: https://github. com/joem-bedded/JesFS
[2] TI-Launchpad: www,ti,com/tool/LAUNCHXL-CC1310
[3] CCSTUDIO:www.ti. com/tool/CCSTUDIO
[4] Macronix Flash: www. macronix. com/en - us/prod-ucts/NOR - Flash/Serial-NOR - Flash
Von Wickenhauser:从小就对电子产品充满热情。在学习了电气工程之后,于 1992 年把爱好变成了职业,在德国南部从事科学环境测量仪器方面的开发工作。
作者:Von Wickenhäuser
译者:透镜
审核编辑:黄飞
评论
查看更多