Files
P4-USB-transfer/docs/extflash_concurrency_analysis.md
T
zhengxinandCursor 851e347cf5 初始提交卡包 USB 工程
纳入固件源码、屏幕组件、nand_xfer 工具和文档。忽略本机构建产物、sdkconfig,以及含本机路径的依赖锁。

Co-authored-by: Cursor <[email protected]>
2026-10-13 21:54:50 +08:00

5.8 KiB
Raw Blame History

外部 Flash (NAND) 访问时序与并发分析报告

1. 概述

本报告分析 /extflash 挂载的 NAND Flash 在动画切换、UI 操作、下载解压等场景下的访问时序与潜在并发问题。


2. extflash 访问点汇总

2.1 读访问(Lottie JSON、stat、opendir)

调用位置 任务/上下文 操作 触发时机
switch_lottie_animation_cross_path_internal change_task (core 0) stat() + lv_lottie_set_src_file() (fopen/fread) BLE 指令、动画完成回调、定时器、下载成功
main_task main_task (core 1) stat(path_buf) 处理 DOWNLOAD_SUCCESS_BIT 时
extract_task extract_task (core 1) list_files_in_directory() (opendir/readdir), stat() 解压完成后
emoji_storage_sync_downloaded_packs_from_fs 调用者上下文 opendir/readdir/stat emoji_storage 初始化
zip_extractor extract_task 内 stat(zip_file_path) 解压前检查 ZIP 存在
change_task 路径解析 change_task 多处 stat() 解析表情包路径时

2.2 写访问

调用位置 任务/上下文 操作 触发时机
download_file MQTT 回调上下文 fopen/fwrite (ZIP 下载) MQTT 收到下载 URL 后
extract_zip_file extract_task (core 1) fopen/fwrite (解压 JSON) 下载完成后
check_flash_integrity flash_main 初始化 fopen/fwrite 启动时
OTA extract_running_app_to_storage OTA 流程 写入 /extflash/flash.bin OTA 升级时

3. 并发场景分析

3.1 典型并发组合

场景 任务 A 任务 B 是否可能并发
1 change_task 读 Lottie extract_task 写解压文件 是
2 change_task 读 Lottie download_file 写 ZIP 是
3 main_task stat extract_task 写 是
4 动画切换 用户手势/UI 操作 是(逻辑上并发)

3.2 底层串行化

  • VFS/FAT 层:ESP-IDF 的 fatfs 挂载使用 ff_mutex,同一时刻只允许一个文件操作。
  • NAND 硬件:单 SPI 总线,单设备,物理上只能串行传输。

因此,不会出现真正的硬件级并发读写,但会出现多个任务排队等待同一 NAND 访问。


4. 关键问题

4.1 持锁期间进行 NAND 读(UI 卡顿)

lvgl_init.c 中:

// 约 1185-1188 行
_lock_acquire(&lvgl_api_lock);
lv_lottie_set_src_file(current_lottie, file_path);  // 内部会 fopen/fread 从 NAND 读
// ...
_lock_release(&lvgl_api_lock);
  • lv_lottie_set_src_file 会从 NAND 读取整个 Lottie JSON,耗时较长。
  • 整个读取过程在持有 lvgl_api_lock 期间执行。
  • LVGL 任务需要该锁才能刷新界面,因此动画切换期间 UI 会冻结。
  • 若用户在切换过程中滑动手势,UI 回调会卡在等待锁释放。

4.2 写操作阻塞读

  • extract_task 解压大量 JSON 时,会长时间占用 FAT 锁。
  • 此时 change_task 若执行 stat() 或 lv_lottie_set_src_file(),会阻塞在 FAT 层。
  • 导致动画切换延迟或卡顿。

4.3 读操作阻塞写

  • change_task 正在读取 Lottie 时,若 extract_task 或 download_file 开始写,会阻塞在 FAT 层。
  • 下载/解压时间被拉长。

4.4 UI 手势与 extflash 的关系

  • ui_event_gesture_cb 不直接访问 extflash,只做 ui_change_page(lv_screen_load_anim)。
  • 但若此时 change_task 正在执行 switch_lottie_animation_cross_path_internal 并持有 lvgl_api_lock,LVGL 任务会阻塞,手势处理会延迟。

5. 任务与核心分布

任务 核心 优先级 主要 extflash 操作
change_task 0 CHANGE_TASK_PRIORITY 读 Lottie、stat
主 LVGL 任务 0 EXAMPLE_LVGL_TASK_PRIORITY 无直接访问
main_task 1 - stat
extract_task 1 configMAX_PRIORITIES - 1 写解压、list_files、stat
download_file 调用者上下文 (MQTT) - 写 ZIP

6. 建议措施

6.1 短期(缓解 UI 卡顿)

  1. 分离锁与文件读取:

    • 先 stat() 检查文件是否存在(不持 lvgl 锁)。
    • 在持锁前,将文件内容读入内存缓冲区(或使用 LVGL 支持的内存源)。
    • 持锁期间只做 lv_lottie_set_src_buffer 或等效 API,不进行 NAND 读。
  2. 缩短持锁时间:

    • 若无法改为内存源,至少将 lv_lottie_set_src_file 移出锁外,仅把 LVGL 对象创建/修改等必须持锁的部分放在锁内。

6.2 中期(统一访问顺序)

  1. 增加 extflash 访问互斥锁:

    • 所有对 /extflash 的 fopen/fread/fwrite/stat/opendir 等操作统一通过一个 extflash_mutex 保护。
    • 避免长时间写阻塞短时读,可考虑读写锁(如支持)或优先级策略。
  2. 解压时暂停动画切换:

    • 解压开始前设置标志,change_task 收到切换请求时若发现解压中则排队或延后。
    • 解压完成后再处理排队请求。

6.3 长期(架构优化)

  1. 预加载/缓存:

    • 常用 Lottie 预先读入内存或临时文件,切换时减少 NAND 访问。
  2. 异步加载:

    • 先显示占位或上一帧,后台异步读 Lottie,完成后切换。
  3. NAND 性能调优:

    • 检查 SPI 时钟、DMA、队列等配置,在稳定前提下提升吞吐。

7. 结论

  • 并发访问:不会出现硬件级并发,但多任务会排队访问 NAND,存在相互阻塞。
  • 动画切换:在持 lvgl_api_lock 期间进行 NAND 读,会导致 UI 卡顿。
  • UI 操作:手势本身不访问 extflash,但会因等待 lvgl_api_lock 而受动画切换影响。
  • 建议:优先将持锁与 NAND 读分离,并考虑引入 extflash 访问互斥与解压/切换协调逻辑。