设备管理与文件系统
设备管理与文件系统是 AC792N SDK 中负责存储设备(SD 卡、U 盘、NOR Flash)的识别、挂载、卸载与格式化,并将设备抽象为可读写的文件系统路径(如 storage/sd0)供上层应用(播放器、录音、升级等)统一访问的基础能力模块。核心实现位于 sdk/apps/common/system/device_mount.c,它桥接了底层的设备框架(device/device.h)与文件系统框架(fs/fs.h)。
Purpose and Scope
本页面向「设备管理与文件系统」这一能力,完整覆盖:
- 设备框架与文件系统框架的分层关系(
dev_open/dev_ioctl/mount/unmount等 API 的配合方式); device_mount.c中 SD 卡(SD0/SD1)的挂载、卸载、格式化全流程实现,包括挂载状态机、互斥保护、多分区(DMSDX)支持;- 文件系统类型选择(
jlfat/fat)以及 SDK 中并存的其他文件系统(littlefs、NOR Flash 文件系统)的定位; - 对外公共 API(
storage_device_ready、sdcard_storage_device_ready、sdcard_storage_device_format等)及其语义; - 配置项、失败模式、并发与性能注意事项、扩展点。
不在本页范围:蓝牙设备的管理(如 ext_spi_bt/bt_device_manager.c 属于蓝牙协议栈的配对/连接管理,详见蓝牙相关页面);USB U 盘(MSD)与 USB 主机协议栈的细节;Flash 驱动的底层擦写算法。本页只讨论存储设备与文件系统的挂载、访问边界。
Overview
在 AC792N 这类嵌入式 SoC 平台上,存储介质种类繁多(SD 卡、TF 卡、NOR Flash、U 盘等),且文件系统也可能不同(FAT、jlfat、littlefs)。为了让上层业务代码(例如音乐播放器遍历歌曲、录音机写文件、OTA 升级读固件)不关心具体介质,SDK 采用了两层抽象:
- 设备框架(Device Framework):以
dev_open/dev_close/dev_ioctl/dev_online等接口屏蔽具体硬件(SD/MMC 控制器、SPI NOR、USB MSD),设备通过名字(如sd0、sd1、udisk0)寻址; - 文件系统框架(FS Framework):以
mount/unmount/f_format/fmount_exist/fs_open等接口屏蔽文件系统差异,把设备挂载成路径(如storage/sd0),并支持缓存、分区、多文件系统并存。
device_mount.c 处于两者之间:它监听设备的在线状态,负责「设备 → 文件系统路径」的挂载生命周期管理,并对外暴露 storage_device_ready() 这类"存储就绪"查询 API,供系统初始化、播放器、录音等模块在访问存储前确认文件系统可用。
关键设计意图:
- 惰性挂载(Lazy Mount):不强制在开机时挂载所有存储设备,而是由业务方通过
storage_device_ready()触发;设备未插入时返回失败,插入后下次调用即可挂载成功; - 状态记忆:用
sd_mount[]数组记录每个 SD 槽位的挂载状态(未挂载/成功/失败),避免重复挂载,也避免对已失败的设备反复发起耗时操作; - 互斥保护:挂载/卸载可能被多个任务(事件回调、业务线程)同时触发,因此用
OS_MUTEX(sd_mutex)串行化,防止 FAT 表被并发操作破坏; - 失败自愈:挂载失败时打印错误日志;对外提供格式化 API,业务可在设备"未格式化"场景下先格式化再挂载。
Architecture
下图展示了设备管理与文件系统的整体架构以及 device_mount.c 在其中的位置:
flowchart TD
subgraph sg_App["应用层 (apps/common)"]
App["应用代码 / 播放器 / 录音 / OTA"]
Ready["storage_device_ready()"]
SdReady["sdcard_storage_device_ready()"]
Fmt["sdcard_storage_device_format()"]
SubFmt["sdcard_storage_subdevice_format()"]
end
subgraph sg_Mount["设备挂载模块 device_mount.c"]
MountSd["mount_sd_to_fs()"]
UnmountSd["unmount_sd_to_fs()"]
Mutex["sd_mutex (OS_MUTEX)"]
State["sd_mount[2] 状态数组"]
end
subgraph sg_FS["文件系统框架 (fs/fs.h)"]
MountAPI["mount(dev, path, fstype, cache, NULL)"]
UnmountAPI["unmount(path)"]
FmtAPI["f_format(dev, fstype, cluster)"]
ExistAPI["fmount_exist(path)"]
JLFAT["jlfat / fat (FAT 文件系统)"]
end
subgraph sg_Dev["设备框架 (device/device.h)"]
DevOpen["dev_open() / dev_close()"]
DevIoctl["dev_ioctl(IOCTL_GET_CLASS)"]
DevOnline["dev_online()"]
SD0["SD0 设备 (sd0)"]
SD1["SD1 设备 (sd1)"]
end
App --> Ready
App --> SdReady
App --> Fmt
Ready --> MountSd
SdReady --> MountSd
Fmt --> UnmountSd
Fmt --> FmtAPI
SubFmt --> FmtAPI
MountSd --> Mutex
MountSd --> State
UnmountSd --> Mutex
UnmountSd --> State
MountSd --> MountAPI
MountSd --> DevOnline
MountSd --> DevOpen
MountSd --> DevIoctl
UnmountSd --> UnmountAPI
UnmountSd --> ExistAPI
MountAPI --> JLFAT
JLFAT --> DevOpen
DevOpen --> SD0
DevOpen --> SD1
架构说明:
- 应用层是挂载模块的调用方。
storage_device_ready()用于默认存储路径(CONFIG_STORAGE_PATH,通常为storage/sd0),sdcard_storage_device_ready(sd_name)用于指定 SD 槽位,格式化 API 供设备管理 UI 或产测工具调用; - 挂载模块(本页核心)维护每个 SD 槽位的状态与互斥锁,调用文件系统框架完成真正的挂载;
CONFIG_DMSDX_ENABLE开启时还会探测 SD 卡的多分区(sd0.0、sd0.1…),分别挂载到storage/sd0.0、storage/sd0.1…; - 文件系统框架把设备块读写抽象成文件系统。SDK 默认使用 FAT 文件系统(
CONFIG_JLFAT_ENABLE时用杰理自研jlfat,否则用fat);SDK 中还并存 littlefs(sdk/apps/common/example/third_party/littlefs/lfs.c)与 NOR Flash 文件系统(sdk/apps/common/fat_nor/nor_fs.c),它们通过同一套mount接口注册,业务可按介质选择; - 设备框架负责与真实硬件交互:
dev_online()判断设备是否插入,dev_open()获得句柄,dev_ioctl(IOCTL_GET_CLASS)读取 SD 卡速度等级(用于日志打印),文件系统层通过设备句柄完成块读写。
挂载状态机
挂载模块用 sd_mount[] 数组记录每个 SD 槽位的生命周期状态,状态定义见 device_mount.c 第 45-49 行:
enum {
SD_UNMOUNT = 0,
SD_MOUNT_SUSS,
SD_MOUNT_FAILD,
};
stateDiagram-v2
[*] --> SD_UNMOUNT
SD_UNMOUNT --> SD_MOUNT_SUSS: mount_sd_to_fs() 挂载成功
SD_UNMOUNT --> SD_MOUNT_FAILD: 设备不在线 / mount 返回 NULL
SD_MOUNT_SUSS --> SD_UNMOUNT: unmount_sd_to_fs() 卸载
SD_MOUNT_FAILD --> SD_UNMOUNT: unmount_sd_to_fs()(格式化前先卸载)
SD_MOUNT_FAILD --> SD_MOUNT_SUSS: 格式化成功后重新挂载
设计要点:
SD_MOUNT_SUSS状态下重复调用mount_sd_to_fs()会直接返回成功(幂等),避免重复挂载导致 FAT 缓存错乱;SD_MOUNT_FAILD状态下重复调用会直接返回-EFAULT,不会在本次调用内重试——重试由上层在设备重新插入(dev_online重新为真)后发起,防止挂载失败时反复进行耗时的块设备探测;- 卸载(
unmount_sd_to_fs())无论当前状态如何都执行unmount()并把状态复位为SD_UNMOUNT,因此格式化流程可以安全地"先卸载 → 格式化 → 再挂载"。
核心实现:设备挂载(mount_sd_to_fs)
mount_sd_to_fs() 是挂载模块的心脏,完整实现见 device_mount.c 第 60-148 行。它依次完成:加锁 → 状态检查 → 在线检查 → 挂载 → 读取卡等级 → 更新状态 → 解锁。
static int mount_sd_to_fs(const char *sd_name)
{
int err = 0;
u32 class = 0, capacity = 0, block_size = 0;
struct imount *mt;
int id = sd_name[2] - '0';
const char *dev = sd_name;
void *fd = NULL;
err = os_mutex_pend(&sd_mutex, 0);
if (err) {
return -EFAULT;
}
if (sd_mount[id] == SD_MOUNT_SUSS) {
goto __exit;
}
if (sd_mount[id] == SD_MOUNT_FAILD) {
err = -EFAULT;
goto __exit;
}
if (!dev_online(dev)) {
err = -EFAULT;
goto __exit;
}
...
mt = mount(dev, id ? "storage/sd1" : "storage/sd0", FAT_FILE_SYSTEM_NAME, FAT_CACHE_NUM, NULL);
if (!mt) {
log_error("mount %s fail", sd_name);
err = -EFAULT;
goto __err;
} else {
log_info("mount %s suss", sd_name);
}
fd = dev_open(dev, 0);
if (!fd) {
err = -EFAULT;
goto __err;
}
dev_ioctl(fd, IOCTL_GET_CLASS, (u32)&class);
if (class == SD_CLASS_10) {
log_info("%s card class: 10", sd_name);
} else {
log_info("%s card class: %d", sd_name, class * 2);
}
dev_close(fd);
__err:
sd_mount[id] = err ? SD_MOUNT_FAILD : SD_MOUNT_SUSS;
__exit:
os_mutex_post(&sd_mutex);
return err;
}
逐段解读:
- 解析槽位 id:
id = sd_name[2] - '0'。SD 设备名约定为sd0/sd1三个字符,第三个字符即槽位号,因此id只有 0/1 两个取值,对应sd_mount[2]数组与storage/sd0/storage/sd1挂载路径; - 互斥加锁:
os_mutex_pend(&sd_mutex, 0)以 0 超时(阻塞等待)获取互斥锁。挂载期间持有锁,防止与卸载、格式化并发。锁的创建通过early_initcall(__sd_mutex_init)在系统早期初始化阶段完成(device_mount.c 第 54-58 行); - 状态短路:已成功(
SD_MOUNT_SUSS)直接返回 0;已失败(SD_MOUNT_FAILD)直接返回-EFAULT——失败状态被"记住",避免每次调用都重复探测硬件; - 在线检查:
dev_online(dev)询问设备框架 SD 卡是否插入且可访问;不在线直接失败,不进入耗时的挂载流程; - 执行挂载:
mount(dev, path, FAT_FILE_SYSTEM_NAME, FAT_CACHE_NUM, NULL)。文件系统名由编译宏决定(见下节),缓存数为FAT_CACHE_NUM(默认 32),最后一个参数为挂载私有参数(此处为 NULL)。返回值struct imount *为空表示挂载失败; - 读取卡等级(仅日志):
dev_open(dev, 0)打开设备句柄,dev_ioctl(fd, IOCTL_GET_CLASS, ...)读取 SD 卡速度等级(SD_CLASS_10直接打印 10,否则打印class * 2),随后dev_close()。这一步纯粹用于调试日志,不参与业务逻辑; - 状态落定:
__err标签处统一根据err设置SD_MOUNT_SUSS或SD_MOUNT_FAILD;__exit标签处统一释放互斥锁。注意goto __err与goto __exit的差别——只有挂载失败才需要更新状态为 FAILED(挂载成功路径已通过goto __exit跳过状态写入吗?并非如此:挂载成功后err == 0,走到__err时sd_mount[id]会被写为SD_MOUNT_SUSS,因为err为 0)。实际控制流是:成功路径最终也会落入__err写状态。
文件系统类型选择
文件系统名不是写死的,而是由编译宏决定(device_mount.c 第 33-41 行):
#ifdef CONFIG_JLFAT_ENABLE
#define FAT_FILE_SYSTEM_NAME "jlfat"
#else
#define FAT_FILE_SYSTEM_NAME "fat"
#endif
#ifndef FAT_CACHE_NUM
#define FAT_CACHE_NUM 32
#endif
CONFIG_JLFAT_ENABLE定义时使用杰理自研的jlfat文件系统,否则使用通用fat(FAT16/32)。两者都通过文件系统框架注册,对上层mount/f_format接口透明;FAT_CACHE_NUM控制挂载时分配的 FAT 缓存条目数,默认 32。更大的缓存可减少随机读写的扇区访问次数,但会占用更多 RAM——嵌入式平台需要按内存预算权衡。
多分区支持(CONFIG_DMSDX_ENABLE)
开启 CONFIG_DMSDX_ENABLE 后,SD 卡可被切成多个分区。挂载逻辑会枚举设备子名(sd0.0、sd0.1…),并把每个分区挂载到对应路径(storage/sd0.0、storage/sd0.1…):
#ifdef CONFIG_DMSDX_ENABLE
u8 i = 0;
char sub_dev_name[7] = {0};
char mount_path_name[16] = {0};
strcpy(sub_dev_name, dev);
strcpy(mount_path_name, id ? "storage/sd1" : "storage/sd0");
do {
sub_dev_name[strlen(dev)] = '.';
sub_dev_name[strlen(dev) + 1] = '0' + i;
fd = dev_open(sub_dev_name, 0);
if (fd) {
dev_close(fd);
mount_path_name[11] = '.';
mount_path_name[12] = '0' + i;
++i;
mt = mount(sub_dev_name, mount_path_name, FAT_FILE_SYSTEM_NAME, FAT_CACHE_NUM, NULL);
if (!mt) {
log_error("%s mount %s %s fail, format...", sub_dev_name, mount_path_name, FAT_FILE_SYSTEM_NAME);
f_format(sub_dev_name, FAT_FILE_SYSTEM_NAME, 32 * 1024);
mt = mount(sub_dev_name, id ? "storage/sd1" : "storage/sd0", FAT_FILE_SYSTEM_NAME, FAT_CACHE_NUM, NULL);
} else {
log_info("%s mount %s %s suss", sub_dev_name, mount_path_name, FAT_FILE_SYSTEM_NAME);
}
}
} while (fd != NULL && i < 10);
设计要点:
- 探测方式为尝试打开子设备名:
dev_open("sd0.N")返回非空即认为该分区存在;枚举上限 10 个分区(i < 10); - 分区挂载失败时自动格式化(
f_format(sub_dev_name, ..., 32 * 1024),簇大小 32KB),然后重新挂载到不带分区号的基础路径(storage/sd0)——这是为"未格式化分区"场景提供的自愈逻辑,但请注意它会销毁分区数据,仅适用于可接受数据丢失的场景(如出厂预格式化); - 卸载时(
unmount_sd_to_fs())会先遍历storage/sd0.N直到fmount_exist()返回假,逐一卸载所有分区,再卸载基础路径。
卸载与格式化
unmount_sd_to_fs()(device_mount.c 第 150-179 行)加锁后先卸载全部分区(DMSDX 模式)或基础路径,把 sd_mount[id] 复位为 SD_UNMOUNT,最后解锁。它被格式化和"安全弹出"场景调用。
sdcard_storage_device_format()(device_mount.c 第 216-236 行)是完整的格式化流程:校验设备名长度(必须为 3 个字符,防止非法输入)→ 卸载 → f_format(sd_name, FAT_FILE_SYSTEM_NAME, 32 * 1024) → 成功后重新挂载。格式化期间持有互斥锁,保证不会有其他任务并发访问文件系统。
int sdcard_storage_device_format(const char *sd_name)
{
#if TCFG_SD0_ENABLE || TCFG_SD1_ENABLE
int err;
if (!sd_name || strlen(sd_name) != 3) {
return -EPERM;
}
unmount_sd_to_fs(sd_name);
err = f_format(sd_name, FAT_FILE_SYSTEM_NAME, 32 * 1024);
if (err == 0) {
mount_sd_to_fs(sd_name);
}
return err;
#else
return 0;
#endif
}
核心流程:存储就绪与格式化
就绪检查流程(storage_device_ready)
storage_device_ready()(device_mount.c 第 182-196 行)是系统中最常用的入口。它检查默认 SD 设备(SDX_DEV,宏定义在 app_config.h 中,通常为 "sd0")是否在线,若在线且尚未挂载则执行挂载,最后以 fmount_exist(CONFIG_STORAGE_PATH) 判定存储是否可用:
int storage_device_ready(void)
{
#if TCFG_SD0_ENABLE || TCFG_SD1_ENABLE
if (!dev_online(SDX_DEV)) {
return false;
}
if (SDX_DEV[2] - '0' < 2 && sd_mount[SDX_DEV[2] - '0'] == SD_UNMOUNT) {
mount_sd_to_fs(SDX_DEV);
}
return fmount_exist(CONFIG_STORAGE_PATH);
#else
return false;
#endif
}
sequenceDiagram
participant App as 应用层(播放器/录音/升级)
participant DM as device_mount.c
participant Mutex as sd_mutex
participant Dev as 设备框架
participant FS as 文件系统框架
participant SD as SD 卡
App->>DM: storage_device_ready() / sdcard_storage_device_ready("sd0")
DM->>Dev: dev_online("sd0")
Dev-->>DM: true(已插入)
DM->>Mutex: os_mutex_pend(&sd_mutex)
DM->>DM: sd_mount[0]==SD_UNMOUNT ? 继续 : 短路返回
DM->>FS: mount("sd0", "storage/sd0", "jlfat|fat", 32, NULL)
FS->>SD: 读分区表/初始化 FAT
SD-->>FS: 块读写就绪
FS-->>DM: struct imount*(非空)
DM->>Dev: dev_open("sd0") → dev_ioctl(IOCTL_GET_CLASS) → dev_close()
DM->>DM: sd_mount[0]=SD_MOUNT_SUSS
DM->>Mutex: os_mutex_post(&sd_mutex)
DM-->>App: fmount_exist("storage/sd0") → true
时序说明:就绪检查是"检查 → 必要时挂载 → 再确认"三步走。dev_online 先行判断避免了在无卡时发起挂载;fmount_exist 作为最终确认,保证返回值语义是"路径确实可用",而不是"设备在线"。
格式化流程(sdcard_storage_device_format)
sequenceDiagram
participant App as 应用/产测工具
participant DM as device_mount.c
participant Mutex as sd_mutex
participant FS as 文件系统框架
participant SD as SD 卡
App->>DM: sdcard_storage_device_format("sd0")
DM->>DM: 校验 strlen(sd_name)==3,非法返回 -EPERM
DM->>Mutex: os_mutex_pend(&sd_mutex)
DM->>FS: unmount("storage/sd0")(含 DMSDX 分区逐一卸载)
DM->>DM: sd_mount[0]=SD_UNMOUNT
DM->>FS: f_format("sd0", "jlfat|fat", 32*1024)
FS-->>DM: err==0(格式化完成)
DM->>FS: mount("sd0", "storage/sd0", "jlfat|fat", 32, NULL)
DM->>DM: sd_mount[0]=SD_MOUNT_SUSS
DM->>Mutex: os_mutex_post(&sd_mutex)
DM-->>App: err(0=成功)
格式化流程体现了"先卸载、后格式化、再挂载"的经典安全顺序:格式化会重写 FAT 表,若文件系统仍处于挂载态,缓存中的旧 FAT 数据会造成不一致;先 unmount() 冲刷缓存,格式化后重新 mount() 建立干净的文件系统视图。
使用示例
示例 1:播放器/录音前的存储就绪检查
系统模块在访问 storage/sd0 前调用 storage_device_ready(),例如 init.c 中初始化时对 sdfile_ext_mount_init() 的引用:
extern void app_main(void);
extern void sdfile_ext_mount_init(void);
void __attribute__((weak)) board_init() {}
来源:init.c
storage_device_ready() 的典型调用模式(基于其实现语义):
/* 伪代码模式:业务侧在读写存储前确认文件系统就绪 */
if (storage_device_ready()) {
/* storage/sd0 已挂载,可以 fs_open / fwrite / fread */
} else {
/* 无卡或挂载失败:提示用户插入 SD 卡 */
}
示例 2:指定槽位就绪与格式化
/* 指定 SD1 槽位:等待其文件系统就绪 */
if (sdcard_storage_device_ready("sd1")) {
/* storage/sd1 可用 */
}
/* 设备管理菜单中的"格式化"动作:先卸载 → 格式化 → 重新挂载 */
int err = sdcard_storage_device_format("sd0");
if (err == 0) {
/* 格式化成功,storage/sd0 已重新挂载 */
}
两个函数的完整实现见 device_mount.c 第 198-236 行。
示例 3:多分区设备(CONFIG_DMSDX_ENABLE)
开启 CONFIG_DMSDX_ENABLE 后,SD 卡子分区自动挂载到 storage/sd0.N 路径,挂载模块内部逻辑见前述 mount_sd_to_fs() 的 DMSDX 分支;业务侧通过 fmount_exist("storage/sd0.0") 判断分区是否存在,使用 sdcard_storage_subdevice_format(dev_name) 可对单个子设备格式化(该函数声明于 device_mount.c 第 239 行起)。
配置选项
设备管理与文件系统相关的配置分散在 app_config.h(板级配置)与编译宏(Kconfig/构建系统)中,挂载模块实际使用的选项如下:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
TCFG_SD0_ENABLE | bool | 板级配置 | 是否启用 SD0 设备;为 0 时 storage_device_ready() 直接返回 false,挂载模块整体不编译 |
TCFG_SD1_ENABLE | bool | 板级配置 | 是否启用 SD1 设备 |
CONFIG_JLFAT_ENABLE | bool | 未定义 | 定义时使用杰理自研 jlfat 文件系统,否则使用通用 fat(见 device_mount.c 第 33-37 行) |
FAT_CACHE_NUM | int | 32 | 挂载时分配的 FAT 缓存条目数;越大随机读写越快、RAM 占用越高(device_mount.c 第 39-41 行) |
CONFIG_DMSDX_ENABLE | bool | 未定义 | 启用 SD 卡多分区探测与挂载(sd0.0、sd0.1… → storage/sd0.0…);分区挂载失败时自动格式化(device_mount.c 第 86-115 行) |
SDX_DEV | 字符串 | 板级定义 | 默认 SD 设备名(通常 "sd0"),storage_device_ready() 使用(device_mount.c 第 185 行) |
CONFIG_STORAGE_PATH | 字符串 | "storage/sd0" | 默认存储路径,storage_device_ready() 用 fmount_exist() 确认其可用性 |
TCFG_USB_SLAVE_ENABLE / TCFG_USB_HOST_ENABLE | bool | 板级配置 | 决定是否编译 USB 相关头文件(usb_stack.h、msd.h 等),影响 U 盘等 USB 存储设备支持(device_mount.c 第 11-23 行) |
文件系统配套组件(与挂载模块通过同一 mount 框架协作,各自独立页面/模块):
- littlefs:
sdk/apps/common/example/third_party/littlefs/lfs.c,适用于小容量、掉电安全要求高的 NOR Flash 场景; - NOR Flash 文件系统:
sdk/apps/common/fat_nor/nor_fs.c,把 FAT 语义映射到 SPI NOR 介质; - 录音专用文件系统:
sdk/apps/common/fat_nor/phone_rec_fs.c。
API 参考
int storage_device_ready(void)
检查默认 SD 存储是否就绪。设备在线且未挂载时自动挂载,返回 fmount_exist(CONFIG_STORAGE_PATH) 的结果。
返回值:1(就绪,storage/sd0 已挂载);0(无卡、挂载失败或 TCFG_SD0_ENABLE/TCFG_SD1_ENABLE 均未开启)。
实现位置:device_mount.c 第 182-196 行
int sdcard_storage_device_ready(const char *sd_name)
检查指定 SD 槽位("sd0" / "sd1")的存储是否就绪,语义与 storage_device_ready() 相同,但允许指定槽位。
参数:sd_name —— 设备名,如 "sd0";内部取 sd_name[2] - '0' 作为槽位 id。
返回值:1 就绪;0 不可用。
实现位置:device_mount.c 第 198-214 行
int sdcard_storage_device_format(const char *sd_name)
格式化指定 SD 设备:卸载 → f_format(簇 32KB)→ 重新挂载。
参数:sd_name —— 设备名,必须恰好 3 个字符(如 "sd0")。
返回值:0 成功;-EPERM 参数非法(NULL 或长度≠3);-EFAULT 挂载/格式化失败。
注意:会销毁卡上全部数据。
实现位置:device_mount.c 第 216-236 行
int sdcard_storage_subdevice_format(const char *dev_name)
(CONFIG_DMSDX_ENABLE 时可用)格式化单个 SD 子设备(如 "sd0.0"),用于多分区场景的局部格式化。
实现位置:device_mount.c 第 238-240 行起
挂载模块使用的底层框架 API
| API | 来源头文件 | 在挂载模块中的用途 |
|---|---|---|
dev_online(name) | device/device.h | 判断设备是否插入/可访问 |
dev_open(name, flag) / dev_close(fd) | device/device.h | 打开/关闭设备句柄,探测子分区、读取卡等级 |
dev_ioctl(fd, cmd, arg) | device/device.h | 读取设备属性,如 IOCTL_GET_CLASS(SD 卡速度等级) |
mount(dev, path, fs_name, cache_num, priv) | fs/fs.h | 把设备挂载为文件系统路径,返回 struct imount * |
unmount(path) | fs/fs.h | 卸载文件系统并冲刷缓存 |
fmount_exist(path) | fs/fs.h | 查询路径是否已挂载 |
f_format(dev, fs_name, cluster_size) | fs/fs.h | 格式化设备(簇大小 32KB) |
os_mutex_create/pend/post | 系统内核 | 挂载/卸载/格式化的互斥保护 |
失败模式与边界情况
无卡 / 设备不在线
dev_online() 返回假时,mount_sd_to_fs() 直接返回 -EFAULT,不进入挂载流程。这是预期的快速失败路径:上层通过返回值知道存储不可用,可提示用户插卡。由于状态数组仍保持 SD_UNMOUNT,卡片插入后下一次 storage_device_ready() 调用即可正常挂载,无需重启或手工干预。
挂载失败(未格式化 / 损坏的 FAT)
mount() 返回 NULL 时:
- 普通模式:打印
mount %s fail,状态置为SD_MOUNT_FAILD。之后重复调用会直接返回-EFAULT(失败记忆),防止反复探测;恢复路径是上层调用格式化 API 或设备重新插拔后由状态复位触发重试; - DMSDX 模式:单个分区挂载失败会自动格式化该分区(簇 32KB)并重新挂载到基础路径。这是有数据破坏风险的激进自愈策略,设计意图是保证"分区表异常"的卡也能被用户正常使用,但不应在含用户数据的卡上依赖此行为。
非法参数
sdcard_storage_device_format() 对 sd_name 做 NULL 与长度(必须为 3)校验,非法输入返回 -EPERM,避免越界解析 sd_name[2]。槽位 id 取自 sd_name[2] - '0',因此调用方必须传入 sd0/sd1 格式的名字;storage_device_ready() 额外检查 SDX_DEV[2] - '0' < 2 以防宏配置错误导致数组越界。
分区枚举上限
DMSDX 模式分区枚举上限为 10(i < 10),超过 10 个分区的卡只会挂载前 10 个;mount_path_name 为 16 字节缓冲区,路径形如 storage/sd0.9,不满足该长度约束的分区名会被截断。
并发与一致性
- 单一互斥锁串行化全部挂载操作:
mount_sd_to_fs()、unmount_sd_to_fs()、sdcard_storage_device_format()都持有sd_mutex。挂载/卸载/格式化期间任何其他任务都不能执行同类操作,保证 FAT 结构的一致性; - 锁内持有时间较长:
mount()内部涉及块设备探测与 FAT 表读取,可能耗时数毫秒到数十毫秒(取决于卡速)。os_mutex_pend(&sd_mutex, 0)是阻塞式等待,因此在低优先级任务中调用可能被高优先级任务抢占,需评估实时性要求;若在中断上下文调用,应改用非阻塞或事件通知方式(当前实现面向任务上下文); - 状态与锁的配合:状态检查(
sd_mount[id])与状态写入都发生在锁内,避免了"两个任务同时挂载同一张卡"的竞态——第二个任务会在锁内看到SD_MOUNT_SUSS并短路返回。
性能与运维
- 挂载成本:挂载是较重的操作(探测 + 读 FAT 表)。
SD_MOUNT_SUSS状态短路和dev_online前置检查,使重复调用storage_device_ready()的开销降为一次互斥锁操作 + 一次fmount_exist查询,适合被业务高频调用; - 日志:模块启用
LOG_INFO/DEBUG/ERROR(device_mount.c 第 25-31 行),挂载成功/失败、卡等级、分区卸载均有带[DEVICE_MOUNT]标签的日志,排查"存储不可用"问题应先看该标签日志确认挂载是否成功; - 缓存调优:
FAT_CACHE_NUM是吞吐与内存的权衡点。音乐播放/录音等流式读写场景,32 条目通常够用;若出现明显的随机读写卡顿且 RAM 有余量,可增大该值。
扩展点
- 新增存储介质:本模块仅覆盖 SD 卡。新增介质(如 U 盘、eMMC)可参照
mount_sd_to_fs()的模式,在设备框架中注册设备名,再调用mount()/f_format()挂载到新的storage/路径;文件系统侧只需保证该文件系统通过fs/fs.h注册了名字(jlfat/fat/littlefs 均满足); - 文件系统选择:通过
CONFIG_JLFAT_ENABLE在jlfat与fat间切换,无需修改挂载逻辑;littlefs(sdk/apps/common/example/third_party/littlefs/lfs.c)与 NOR 文件系统(sdk/apps/common/fat_nor/nor_fs.c)作为独立模块存在,可用于 SPI Flash 等介质; - 板级裁剪:
TCFG_SD0_ENABLE/TCFG_SD1_ENABLE关闭时整个挂载模块不参与编译(代码以#if包裹),ROM 占用最小化; - 测试观察点:SD 卡等级日志(
IOCTL_GET_CLASS结果)可用于产测/调试中快速判断卡速等级;格式化返回值可接入设备管理 UI 的提示文案。
相关链接
- device_mount.c(本页核心实现)
- 系统初始化 init.c(含 sdfile_ext_mount_init 引用)
- littlefs 第三方文件系统示例
- NOR Flash 文件系统 nor_fs.c
- 录音文件系统 phone_rec_fs.c
- 蓝牙设备管理(
ext_spi_bt/bt_device_manager.c)属于蓝牙协议栈范畴,详见蓝牙相关页面 - USB 存储设备(U 盘/MSD)涉及 USB 主机栈,详见 USB 相关页面