RTOS 存储简介
FreeRTOS 设计的完整的 IO 栈包括 iobox、vfs、fs、blkpart、flash driver。考虑到 RTOS 比 Linux 更精小高效和 Flash 不需要考虑寻址的特点,并没参考 Linux 设计 Page Cache 和 IO调度器,但在 Flash driver 添加了 Cache 以加速性能。
常见的系统中,都会为 Flash 设备专门设计 MTD 层,起到对 flash driver 进一步封装。在 RTOS 的 IO 栈中,blkpart 就实现了MTD层的功能,除此之外,还实现了分区解析和操作。为了兼容和符合规范,在将来的迭代中可以考虑把 blkpart 拆分出 MTD 层。
iobox简介
iobox 是在 RTOS 实现的一套简单的 IO 命令的集合。其在 menuconfig
中的路径为:
System components
|-> aw components
|-> iobox
目前支持以下命令:
命令 | 功能 | 示例 |
---|---|---|
ls |
列出文件 | ls /data |
ll |
等效于ls -lk | ll /data |
cat |
读取并打印文件 | cat /data/demo |
hexdump |
十六进制显示文件内容 | hexdump /data/demo |
hd |
等效于hexdump -C | hd /data/demo |
cp |
复制文件 | cp /data/demo /data/demo1 |
mv |
重命名文件 | mv /data/demo /data/demo1 |
rm |
删除文件 | rm /data/demo |
unlink |
取消硬链接(等效于删除文件) | unlink /data/demo |
link |
创建硬链接 | link /data/demo |
rwcheck |
读写测试 | rwcheck -d /data -s 256k -s 1000000 |
rwspeed |
顺序读写性能测试 | rwspeed -d /data -s 128k -t 3 |
vi |
vi文本编辑器 | vi /data/demo |
df |
显示fs信息 | df /data |
-h参数可以获取详细的使用说明
VFS简介
VFS 类似与 Linux VFS,是为所有的 FS 提供统一的标准,向上注册和提供系统调用。正因为有 VFS 的存在,用户才可以忽略文件系统的差异,只需要调用规范的 IO 函数即可实现 IO 操作,例如 read()
,ioctl()
,rename()
。
RTOS 的 VFS 移植自第三方的开源 VFS,在 menuconfig
的选择路径为:
System components
|-> thirdparty components
|-> Virtual Filesystem
FS简介
spiffs 和 littlefs
当前,RTOS 支持 spiffs 和 littlefs 两种文件系统
总的来说,推荐使用littlefs。
spiffs
在 menuconfig
的路径为:
System components
|-> thirdparty components
|-> SPIFFS Filesystem
littlefs
在 menuconfig
的路径为:
System components
|-> thirdparty components
|-> LittleFS Filesystem
|-> LFS Codes Version (v2.2.1)
RTOS 中暂不支持 Linux 上常见的 flash 文件系统,例如 yaffs,jffs2,主要是因为 开源协议 和 体量臃肿 的考虑。
与 Linux 相似,文件系统需要使用必须先挂载。littlefs 和 spiffs提供的挂载/卸载接口如下:
#include <spiffs.h>
int spiffs_mount(const char *source, const char *target, bool format);
int spiffs_umount(const char *target);
#incldue <littlefs.h>
int littlefs_mount(const char *dev, const char *mnt, bool format);
int littlefs_umount(const char *mnt);
打包FS资源文件
在项目中,如果需要在编译时创建基于文件系统的资源镜像包,在烧录的时候直接写入分区,可以使用以下方法:
- 修改分区文件,添加
downloadfile
配置 - 在资源目录创建与分区名同名的文件夹,并把资源文件放入此文件夹
由于 RTOS 系统默认使用 littlefs,因此创建的资源文件包也是基于 littlefs 。
例如,需要在UDISK分区创建资源包,可以这么改:
第一步,在 sys_partition{_NOR}.fex
中,在 UDISK
分区项中修改 downloadfile
配置为 data_
开头的文件:
[partition]
name = UDISK
downloadfile = "data_udisk.fex"
user_type = 0x8100
需要注意,如果使能了 CONFIG_XIP
,则该配置文件名称为sys_partition_xip_{_NOR}.fex
。
资源文件名必须以data_开头,这是触发脚本打包资源文件的标识。其余的名字不限制。
第二步,在tina-rt/tools/data
中创建与分区名相同名字的文件夹,例如
$ mkdir -p board/<chip_name>/<project_name>/data/UDISK
并把资源文件拷贝进去即可。
devfs
在 RTOS 里,有实现 devfs 的设备文件系统。正因为 devfs
的存在,在 /dev
目录下,就可以看到设备文件。
OTA更新可以直接操作设备节点,以实现分区级别的更新。
由于历史原因,驱动可能在 /dev
下注册了 设备节点和分区名节点,也可能在 /dev
下注册了设备节点,在 /dev/by-name
下注册别名节点。例如:
/dev/UDISK
/dev/by-name/UDISK
/dev/nand0p5
/dev/NOR
开发者可以通过 ls /dev
命令确认驱动把设备注册在哪里。
文件操作方式
普通文件读写接口
可以使用标准的 Posix 操作接口,如:
ssize_t read (int __fd, void *__buf, size_t __nbytes);
ssize_t write (int __fd, const void *__buf, size_t __n);
int close (int __fd);
int open (char *name, int flag);
也可以使用 C 库的文件操作接口,如:
size_t fread (void *__restrict __ptr, size_t __size, size_t __n, FILE *__restrict __stream);
size_t fwrite (const void *__restrict __ptr, size_t __size, size_t __n, FILE *__restrict __s);
FILE *fopen (const char *__restrict __filename, const char *__restrict __modes);
int fclose (FILE *__stream);
设备分区文件操作
存储分区设备节点可以被当前一个普通文件来读写,如以下例子:
// 该例子可用于读取 env 分区中的内容
char buffer[32];
int fd = open("/dev/env", O_RDONLY);
read(fd, buffer, sizeof(buffer));
close(fd);
blkpart简介
如上所属,RTOS 设计的 blkpart 类似与 MTD,起到对 flash driver 进一步封装的功能,此外还实现了分区解析和分区操作。
blkpart与MTD设计的出发点有些不同,blkpart如其名,主要是把存储空间按块设备分区的逻辑管理,而MTD更多只是规范flash驱动的注册和使用方法。期待将来的迭代中能对blkpart进一步规范,拆离出MTD层,让开发者更好理解。
blkpart在 menuconfig
的路径为:
System components
|-> aw components
|-> Block Partition
|-> Partition Table Size (KB) # 配置GPT分区表的存储空间大小
|-> Logical Partition Start Address (KB) # 分区表起作用的开始偏移
驱动通过调用以下接口注册和注销设备:
int add_blkpart(struct blkpart *blk);
void del_blkpart(struct blkpart *blk);
注册设备后,blkpart会自行解析分区,并向上注册。上层可以通过以下接口访问设备:
ssize_t blkpart_read(struct part *, uint32_t, uint32_t, void *);
ssize_t blkpart_write(struct part *, uint32_t, uint32_t, const void *);
int blkpart_erase(struct part *, uint32_t, uint32_t);
这几个接口就类似于MTD中的 mtd_read/mtd_write/mtd_erase
。
Flash 驱动
RTOS 主要支持 SPINOR 和 SPINAND 两类存储介质。
SPI NOR
NOR 驱动相对简单,只是按照协议通过 spi 发送操作指令。其中为了加快性能,在 NOR 驱动上添加了简单的Cache层。部分 Flash 物料在测试过程中发现了一些掉电场景下会掉码问题,因此若该物料支持写保护功能,则可以使能写保护来避免掉电场景下出现掉码问题。
Cache
Cache 层的代码,是为了尽可能合并连续写入的数据,再一次性写入。
NOR 写之前必须先擦除,普遍支持 Sector Erase
,32KB Block Erase
,64KB Block Erase
和 Chip Erase
的四种擦除方式。查看 NOR 的规格书发现,除了擦除大小不一致外,擦除性能也有很大差别:
厂家 | 写(ms) | 4K擦除(ms) | 32K擦除(ms) | 64K擦除(ms) | 全盘擦除(s) |
---|---|---|---|---|---|
MXIC | 0.33~1.2 | 25~120 | 140~650 | 250~650 | 26~60 |
Winbond | 0.7~3 | 45~400 | 120~1600 | 150~2000 | 40~200 |
GD | 0.5~2.4 | 50~400 | 160~800 | 300~1200 | 50~120 |
ESMT | 0.5~3 | 40~300 | 200~1000 | 300~2000 | 60~200 |
总的来说,擦除性能 Chip Erase > 64KB Block Erase > 32KB Block Erase > Sector Erase
。而一次写操作的耗时主要是擦除,因此,如果需要优化写性能,需要尽可能使用更大的擦除。
考虑到文件系统的空间浪费情况,文件系统需要采用 4K 的块大小,进一步导致驱动必须 Sector Erase
, 也就是4K擦除。这样的矛盾导致 NOR 的写性能偏低。
因此,创建了 64K Block 大小的 Cache, 每一次文件系统的写和擦除操作都缓存起来,在合适时机一次写擦除和写入。为了防止 Cache 的存在导致文件系统丢数据,合适时机的选择非常重要。
在实现中充分考虑的 littlefs 的机制,在以下时机写入保证了不会丢失数据:
- FS主动调用 sync 要求回刷数据时
- Cache缓存的数据满足64K Block或者32K Block了
- FS 写入的地址跨了另外一个Block
其他文件系统的支持,需要结合文件系统的实现,以评估是否会丢失数据。
写保护
在测试中发现,掉电时如果主控依然在发送数据,NOR很大概率会造成误写,把数据写入其他地址,导致数据被损坏。为了解决这个问题,除了硬件实现掉电时马上复位的方法外,软件上也可以使能写保护,让NOR在误写时无法写入。
目前发现的写保护机制有两种,一个是区间保护,一个是独立块保护,两者的差别主要是保护和解保护的最小颗粒不同。
区间保护(Status Register Memory Protection, 或描述为bp写保护,Block Protect,Block Lock protection mode,protect area等)按从低或高地址开始的一段连续空间保护。以 16M 容量的NOR为例,配置为从低地址开始的8M空间,可实现0-8M的空间无法写入,但无法保护 8-16M 的空间。不同厂家划分的保护区间可能有稍许不同,但都无法做到精细保护。例如如果需要写第7M地址的数据,此时只能把保护范围缩减为 0-4M,使得 4-16M 可写,此时依然会有大概率会误写数据。
独立块保护(Individual Block Memory Protect,或描述为Individual Sector Protection mode等)是以块为单位单独控制是否保护。一般而言,块大小是64K,即一次可以做到只解保护64K的小范围。
从理论分析,独立块保护的掉电误写风险远比区间保护小。
启用写保护功能,还需要考虑频繁保护与解保护会带来多大的性能损失和寿命损失。
区间保护修改的状态寄存器跟NOR一样,只有10W次的擦写寿命,且写入时间在ms级别。如果使用区间保护,频繁的修改保护区间,会导致性能下降,且会加速状态寄存器的磨损。
独立块保护修改的是SRAM状态位,无明显擦写寿命限制,且写入时间在ns级别,即使频繁修改保护状态,对性能影响非常微弱。
因此,如果NOR支持,推荐使用独立块保护。区间保护更适用于不需要频繁解保护的场景。
总的来说,
- 如果NOR支持独立块保护,则采用独立块保护,再根据预算决定是否采用硬件复位。
- 如果NOR不支持独立块保护,建议采用硬件复位,取消软件写保护以提高性能和减少磨损。
新物料的支持和测试
因为写保护的存在,导致新物料适配更加复杂。
uboot
: 烧录所用的 uboot
需要适配解锁操作。主要是在 drivers/mtd/spi/spi-NOR-ids.c
中给对应物料配置上 SPI_NOR_HAS_LOCK
(支持写保护) 和 SPI_NOR_INDIVIDUAL_LOCK
(块保护) 等标志,并实现对应的锁操作函数。
rtos
: 添加新物料的支持,主要是添加 struct NOR_info NOR_ids[]
:
struct NOR_info {
char *name;
unsigned char id[MAX_ID_LEN];
unsigned int blk_size;
unsigned int blk_cnt;
struct NOR_protection *pt;
unsigned int pt_len;
unsigned int pt_def;
int flag;
#define EN_IO_PROG_X4 BIT(1)
#define EN_IO_READ_X2 BIT(2)
#define EN_IO_READ_X4 BIT(3)
#define EN_INDIVIDUAL_PROTECT_MODE BIT(4)
};
如果不考虑写保护,只需要把前4项根据 SPEC 信息填写即可。其中blk_size对应文件系统的块大小,因此建议设置为4096,因此 blk_cnt
就是NOR总大小除以4096。
pt,pt_len和pt_def适用于区间保护。由于不再推荐使用区间保护,因此不展开介绍。
flag通过设置 EN_INDIVIDUAL_PROTECT_MODE BIT
以支持独立块保护。
在新物料适配后,需要进行大量压测,以确保足够稳定
SPI NAND
SPINAND的物理特性比 SPINOR 复杂,对驱动要求更高。RTOS 通过适配全志自研的NFTL,实现在 RTOS 上支持SPINAND。
NFTL实现只提供静态库,结合已经开源的物理层代码,即可在 RTOS 上支持SPINAND。
eMMC / SD Card / SD Nand 驱动
eMMC / SD Card / SD Nand 使用的是标准的 SDIO 接口,BROM 支持从支持的 MMC 储存器启动。支持 TF 卡启动,eMMC 启动,SD Nand 启动,也支持通过 SDIO 读取相关存储设备的数据。