VFS虚拟文件系统
虚拟文件系统 (VFS) 组件可为一些驱动提供一个统一接口。有了该接口,用户可像操作普通文件一样操作虚拟文件。这类驱动程序可以是 FAT、SPIFFS 等真实文件系统,也可以是有文件类接口的设备驱动程序——官方文档
说人话就是ESP32可以支持运行嵌入式文件系统
目前ESP-IDF实现的功能如下:
- 按名读取/写入文件
- 兼容POSIX和C库函数文件操作
- 不会对路径中的点
.
或..
进行特殊处理(不会将其视为对当前目录或上一级目录的引用)
已注册的VFS驱动程序均有一个路径前缀与之关联,此路径前缀即为分区的挂载点。如果挂载点中嵌套了其他挂载点,则在打开文件时使用具有最长匹配路径前缀的挂载点。挂载点名称必须以路径分隔符/
开头,且分隔符后至少包含一个字符
执行打开文件操作时,FS驱动程序仅得到文件的相对路径
标准IO流
如果在menuconfig中没有将UART for console output
设置为None
,则stdin
、stdout
和stderr
将默认从UART读取或写入。UART0或UART1都可以用作标准IO。因此可以直接调用stdio.h中的相关库函数。默认情况下,UART0使用115200波特率,TX管脚为 GPIO1,RX管脚为GPIO3;VFS使用简单的函数对UART进行读写操作,也可以在menuconfig中更改参数。
所有IO数据放进UART的FIFO前,写操作将处于忙等待busy-wait(阻塞)状态,读操作处于非阻塞,仅返回FIFO中已有数据。由于读操作为非阻塞,高层级的C库函数调用(如 fscanf(“%d\n”, &var)等)可能获取不到所需结果。如果应用程序使用UART驱动,则可以调用 esp_vfs_dev_uart_use_driver
函数来让VFS使用驱动中断、读写阻塞功能等。也可以调用esp_vfs_dev_uart_use_nonblocking
来使用非阻塞函数
此外,VFS还为输入和输出提供可选的换行符转换功能,可以通过menuconfig来设置输出结尾的换行符
标准流IO与移植的FreeRTOS
stdin、stdout和stderr的FILE指针(对象句柄)在所有FreeRTOS任务之间共享,指向这些对象的指针分别存储在每个任务的struct _reent
中
为了处理各个任务之间的文件指针临界区,预处理器把如下代码:
1 | fprintf(stderr, "42\n"); |
解释为:
1 | fprintf(__getreent()->_stderr, "42\n"); |
__getreent函数返回一个指向struct _reent的指针
每个任务的TCB都拥有一个struct _reent结构体,用于在本任务内处理文件而不影响其他任务
FATFS文件系统
ESP-IDF使用FatFs库来实现FAT文件系统
在文件中调用#include "esp_vfs_fat.h"
和#include "esp_vfs.h"
后可以在FLASH中通过C标准库和POSIX的API经过VFS使用FatFs库的大多数功能
经由这一功能可以实现SD卡的读取
使用步骤
- 调用esp_vfs_fat_register()指定挂载文件系统的路径前缀、FatFs驱动编号和一个用于接收指向FATFS结构指针的变量
- 调用ff_diskio_register()为上述步骤中的驱动编号注册磁盘I/O驱动
- 调用 FatFs 函数f_mount,或f_fdisk,f_mkfs,并使用与传递到esp_vfs_fat_register()相同的驱动编号挂载文件系统
- 调用 C 标准库和 POSIX API 对路径中带有步骤 1 中所述前缀的文件执行打开、读取、写入、擦除、复制等操作
- 关闭所有打开的文件
- 调用f_mount并使用NULL FATFS参数为与上述编号相同的驱动*卸载文件系统
- 调用FatFs函数 ff_diskio_register()并使用NULL ff_diskio_impl_t参数和相同的驱动编号来*释放注册的磁盘I/O驱动
- 调用esp_vfs_fat_unregister_path()并使用文件系统挂载的路径将 FatFs 从 NVS 中移除,并释放步骤 1 中分配的 FatFs 结构
除了需要提前注册、挂载文件系统外,其他操作和正常的FATFS使用没有区别
磨损均衡
ESP32使用的FLASH具备扇区结构,每个扇区仅允许有限次数的擦除/修改操作,ESP-IDF提供磨损均衡组件用于平衡各个扇区之间的损耗。提供两种模式:1. 性能模式(先将数据保存在RAM中,擦除扇区,然后将数据存储回FLASH);2. 安全模式(数据先保存在FLASH中空余扇区,擦除扇区后,数据即存储回去)
设备默认使用性能模式且将扇区大小定为512字节。磨损均衡组件不会将数据缓存在RAM中。写入和擦除函数直接修改FLASH,函数返回后,FLASH即完成修改。
常用API如下:
wl_mount(const esp_partition_t *partition, wl_handle_t *out_handle)
为指定分区挂载并初始化磨损均衡模块,通过out_handle传出句柄
wl_unmount(wl_handle_t handle)
卸载分区并释放磨损均衡模块
wl_erase_range(wl_handle_t handle, size_t start_addr, size_t size)
擦除FLASH中从start_addr开始大小为size的地址范围
wl_write(wl_handle_t handle, size_t dest_addr, const void *src, size_t size)
将数据用指针src引用后写入分区从dest_addr开始大小为size的区域
wl_read(wl_handle_t handle, size_t src_addr, void *dest, size_t size)
从分区读取从src_addr开始大小为size的数据
wl_size(wl_handle_t handle)
返回可用内存的大小(以字节为单位)
wl_sector_size(wl_handle_t handle)
返回一个扇区的大小
上面的wl_handle_t为WL句柄,可通过wl_mount的output参数传出
结合使用磨损均衡与FATFS示例如下:
1 | static wl_handle_t s_wl_handle = WL_INVALID_HANDLE; |
使用外部FLASH挂载FATFS示例如下:
1 |
|
SPIFFS文件系统
SPIFFS 是一个用于 SPI NOR flash 设备的嵌入式文件系统,支持磨损均衡、文件系统一致性检查等功能——官方文档。
目前位置SPIFFS还是一个不完全的文件系统:不支持目录,只能生成扁平结构;不是实时栈,每次写操作耗时不等;不支持检测或处理已损坏的块等
但是这玩意确实很好用
使用spiffsgen.py工具就可以配置SPIFFS,shell中使用格式如下
1 | python spiffsgen.py <image_size> <base_dir> <output_file> |
其中image_size为分区大小,base_dir为SPIFFS映像所在目录,output_file为SPIFFS映像输出文件
也可以使用CMake工具进行配置:
1 | spiffs_create_partition_image(<partition> <base_dir> [FLASH_IN_PROJECT] [DEPENDS dep dep dep...]) |
CMake配置自动传递给spiffsgen.py工具生成映像。单独调用 spiffsgen.py
时需要用到 image_size 参数,但在CMake中调用spiffs_create_partition_image
时仅需要 partition 参数,映像大小将直接从当前工程的分区表中获取。
注意:在CMake中使用spiffs_create_partition_image
,需从组件CMakeLists.txt文件调用
下面是一个标准的SPIFFS分区表示例
1 | # Name, Type, SubType, Offset, Size, Flags |
需要使用SubType=spiffs标识出某一分区是SPIFFS存储分区
SPIFFS配置与API参考
在文件中调用#include "esp_spiffs.h"
就可以使用相关API
使用以下API初始化SPIFFS到虚拟文件系统
1 | esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t *conf) |
esp_vfs_spiffs_conf_t是SPIFFS文件系统初始化结构体,应如下配置:
1 | esp_vfs_spiffs_conf_t conf = { |
更多API如下所示:
1 | esp_err_t esp_vfs_spiffs_unregister(const char *partition_label);//取消VFS上的SPIFFS初始化 |
用C库函数进行SPIFFS文件读写
可以使用POSIX和C库函数在SPIFFS写入和读取数据
下面是官方给出的使用例
1 | void app_main(void) |
FLASH加密
FLASH加密功能用于加密与ESP32搭载使用的SPI Flash中的内容。启用FLASH加密功能后,物理读取SPI FLASH便无法恢复大部分FLASH内容。通过明文数据烧录ESP32可应用加密功能,若已启用加密功能,引导加载程序会在首次启动时对数据进行加密。
FLASH加密功能与密钥同样稳固,但并非所有数据都是加密存储且无法防止攻击者获取FLASH的高层次布局信息
为了防止攻击者直接恶意修改固件,推荐搭配使用FLASH加密与安全启动,但启用安全启动时,OTA
启用FLASH加密后,系统将默认加密下列类型的FLASH数据:
- BootLoader
- 分区表
- 所有app类型的分区
其他类型的FLASH数据将视情况进行加密:
- 如果已启用安全启动,则会加密安全启动引导加载程序摘要
- 分区表中标有加密标记的分区
注意:启用 Flash 加密将限制后续 ESP32 更新
FLASH加密分为两种模式:开发模式和生产模式
使用FLASH加密
一般地,只要在menuconfig中设置使用加密并使用加密方式烧录即可使用FLASH加密
开发模式:可使用 ESP32 内部生成的密钥或外部主机生成的密钥在开发中运行FLASH加密
生产模式:使用ESP32生成的FLASH加密密钥
在程序中使用#include "esp_flash_encrypt.h"
、#include "esp_efuse_table.h"
、#include "esp_efuse.h"
、#include "soc/efuse_reg.h"
后可以使用相关API
分区表如下:
1 | # Name, Type, SubType, Offset, Size, Flags |
如果所有分区都需以加密格式更新,则可使用以下命令:
1 | idf.py encrypted-flash monitor |
只要 FLASH_CRYPT_CNT
eFuse 设置为奇数位的值,所有通过MMU的FLASH缓存访问的FLASH内容都将被透明解密:MMU FLASH缓存将无条件解密所有数据
- 释放模式下,UART引导加载程序无法执行FLASH加密操作,只能使用OTA下载已经加密过的映像
可通过调用函数esp_flash_encryption_enabled()来确认当前是否已启用FLASH加密
可通过调用函数esp_get_flash_encryption_mode()来识别使用的FLASH加密模式
使用分区读取函数esp_partition_read()来读取加密分区的数据
下面是使用FLASH加密的示例
1 |
|
sdkconfig中的相关条目如下:
1 | CONFIG_SECURE_FLASH_ENC_ENABLED=y |