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

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-13 21:54:50 +08:00

143 lines
5.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 外部 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` 中:
```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 访问互斥与解压/切换协调逻辑。