eMMC 4MB特殊区域完整管理方案
第一部分 可能的坑
一、4MB区域分区详解
1.1 4MB区域的分区方式
方法一:GPT分区表方式(推荐)
# Linux下创建分区 sudo parted /dev/mmcblk0 --script \ mklabel gpt \ mkpart primary ext4 1MB 5MB \ name 1 emmc_region \ set 1 legacy_boot off # 验证分区 sudo parted /dev/mmcblk0 print # 输出: # Number Start End Size File system Name Flags # 1 1049kB 5243kB 4194kB ext4 emmc_region
方法二:固定偏移量方式(无分区表)
/* 在eMMC固定位置预留4MB,不使用分区表 */ #define EMMC_RESERVED_START_LBA 2048 /* 起始块号(1MB对齐) */ #define EMMC_RESERVED_SIZE_LBA 8192 /* 4MB = 8192块(512B/块) */ /* 注意:此方式需要确保该区域不被其他分区覆盖 */
1.2 4MB区域的实际用途规划
┌─────────────────────────────────────────────────────────────────┐ │ 4MB eMMC 区域布局规划 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ Offset 0x000000 - 0x0FFFFF (1MB) - 关键数据区 │ │ │ │ ├─ 0x000000-0x00FFFF: 设备配置 (64KB) │ │ │ │ │ ├─ MAC地址、序列号、校准数据 │ │ │ │ │ └─ 冗余备份 (镜像) │ │ │ │ ├─ 0x010000-0x07FFFF: 工厂参数 (448KB) │ │ │ │ │ └─ 生产测试数据、硬件配置 │ │ │ │ └─ 0x080000-0x0FFFFF: 运行时配置 (512KB) │ │ │ │ └─ 用户设置、网络配置、日志 │ │ │ ├────────────────────────────────────────────────────────────┤ │ │ │ Offset 0x100000 - 0x2FFFFF (2MB) - 固件/应用区 │ │ │ │ ├─ 0x100000-0x1FFFFF: 安全固件 (1MB) │ │ │ │ │ └─ 加密密钥、安全启动固件 │ │ │ │ └─ 0x200000-0x2FFFFF: 应用备份 (1MB) │ │ │ │ └─ 关键应用备份、恢复镜像 │ │ │ ├────────────────────────────────────────────────────────────┤ │ │ │ Offset 0x300000 - 0x3FFFFF (1MB) - 文件系统区 │ │ │ │ └─ 小文件系统 (FAT32/ext4) │ │ │ │ └─ 脚本文件、证书、临时数据 │ │ │ └────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘
二、文件系统对比与选择
2.1 三种文件系统详细对比
| 特性 | ext4 | FAT32 | SquashFS |
|---|---|---|---|
| U-Boot支持 | 需配置CONFIG_CMD_EXT4 |
原生支持 | 需要CONFIG_CMD_SQUASHFS |
| Linux支持 | 完整支持 | 完整支持 | 完整支持 |
| 读写能力 | 读写 | 读写 | 只读 |
| 日志功能 | 有(可禁用) | 无 | 无 |
| 压缩 | 无 | 无 | 支持(LZ4/ZSTD) |
| 最大文件 | 16TB | 4GB | 4GB |
| 4MB区域适用 | 动态配置数据 | 跨平台数据交换 | 只读固件/证书 |
| 坏块处理 | 由MMC层处理 | 由MMC层处理 | 由MMC层处理 |
| 元数据开销 | ~4KB | ~512B | ~8KB(压缩) |
2.2 选择建议
/* 使用场景决策树 */
const char* select_filesystem(int usage_type) {
switch(usage_type) {
case USAGE_CONFIG_RW: /* 动态读写配置 */
return "ext4"; /* 支持日志,数据安全 */
case USAGE_CROSS_PLATFORM: /* U-Boot和Windows交换数据 */
return "FAT32"; /* 兼容性最好 */
case USAGE_FIRMWARE_RO: /* 只读固件 */
return "SquashFS"; /* 压缩节省空间 */
case USAGE_TEMP_DATA: /* 临时数据 */
return "FAT32"; /* 轻量级,无日志开销 */
default:
return "ext4"; /* 默认选择 */
}
}
三、U-Boot与Linux读写接口差异树形分析
3.1 完整接口调用链对比树
┌─────────────────────────────────────────────────────────────────────┐ │ U-Boot 调用链(命令行) │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ User Command: mmc read 0xA0000000 0x4000 0x2000 │ │ │ │ │ ▼ │ │ common/cmd_mmc.c::do_mmc_read() │ │ │ │ │ ├─► 参数解析:地址、起始块、块数 │ │ │ │ │ ▼ │ │ drivers/mmc/mmc.c::mmc_bread() │ │ │ │ │ ├─► 块对齐检查 │ │ ├─► 循环调用 mmc_send_cmd() │ │ │ │ │ ▼ │ │ drivers/mmc/mmc.c::mmc_send_cmd() │ │ │ │ │ ├─► 构建CMD17(单块)或CMD18(多块) │ │ ├─► 设置数据传输方向(读) │ │ │ │ │ ▼ │ │ drivers/mmc/×××_mmc.c::×××_mmc_send_cmd() [控制器驱动] │ │ │ │ │ ├─► 配置DMA地址(0xA0000000) │ │ ├─► 等待中断/轮询 │ │ │ │ │ ▼ │ │ eMMC 硬件寄存器 │ │ │ │ │ └─► 数据传输到DDR内存 0xA0000000 │ │ │ └─────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────┐ │ Linux 调用链(用户态) │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ User App: read(fd, buffer, size) │ │ │ │ │ ▼ │ │ glibc: syscall(SYS_read, ...) │ │ │ │ │ ▼ │ │ kernel/fs/read_write.c::ksys_read() │ │ │ │ │ ├─► fd → struct file * │ │ ├─► file->f_op->read_iter() │ │ │ │ │ ▼ │ │ kernel/fs/ext4/file.c::ext4_file_read_iter() [VFS → ext4] │ │ │ │ │ ├─► 页缓存检查 (page cache) │ │ │ ├─► 命中 → 直接从内存复制 │ │ │ └─► 未命中 → 调用底层读取 │ │ │ │ │ ▼ │ │ kernel/fs/ext4/inode.c::ext4_mpage_readpages() │ │ │ │ │ ├─► 映射文件逻辑块到物理块 │ │ ├─► 构建bio请求 │ │ │ │ │ ▼ │ │ kernel/block/blk-core.c::submit_bio() │ │ │ │ │ ├─► 请求合并、调度 │ │ ├─► 电梯算法(CFQ/deadline) │ │ │ │ │ ▼ │ │ drivers/mmc/core/block.c::mmc_blk_issue_rq() │ │ │ │ │ ├─► 转换为MMC命令 │ │ ├─► 处理分区切换 │ │ │ │ │ ▼ │ │ drivers/mmc/core/core.c::mmc_start_request() │ │ │ │ │ ├─► 添加DMA映射 │ │ ├─► 发送命令到主机控制器 │ │ │ │ │ ▼ │ │ drivers/mmc/host/×××-mmc.c [主机控制器驱动] │ │ │ │ │ └─► 硬件数据传输 │ │ │ └─────────────────────────────────────────────────────────────────────┘
3.2 关键差异点详细对比
┌────────────────────────────────────────────────────────────────────┐ │ 数据流差异对比树形图 │ ├────────────────────────────────────────────────────────────────────┤ │ │ │ U-Boot 数据流: │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ Command │───►│ 解析 │───►│ MMC驱动 │───►│ 硬件 │ │ │ │ 参数 │ │ 地址 │ │ 直接DMA │ │ 寄存器 │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ │ ┌─────────┐ │ │ │ │ └────────►│ eMMC │ │ │ │ │ │ 控制器 │ │ │ │ │ └─────────┘ │ │ │ │ │ │ │ │ ▼ ▼ │ │ │ ┌─────────┐ ┌─────────┐ │ │ └────────►│ DDR内存 │◄─────────────────│ eMMC │ │ │ │ 0xA... │ DMA │ 存储 │ │ │ └─────────┘ └─────────┘ │ │ │ │ 特点:无缓存、无文件系统、直接物理地址 │ │ │ ├────────────────────────────────────────────────────────────────────┤ │ │ │ Linux 数据流: │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ 用户App │───►│ glibc │───►│ VFS │───►│ Page │ │ │ │ read() │ │ syscall │ │ 层 │ │ Cache │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ ▲ │ │ │ │ │ │ │ │ ▼ │ │ │ │ │ ┌─────────┐ │ │ │ │ │ │ 缺页 │ │ │ │ │ │ │ 处理 │ │ │ │ │ │ └─────────┘ │ │ │ │ │ │ │ │ │ │ ▼ ▼ │ │ │ │ ┌─────────┐ ┌─────────┐ │ │ │ │ │ 文件系统│───►│ Block │ │ │ │ │ │ (ext4) │ │ 层 │ │ │ │ │ └─────────┘ └─────────┘ │ │ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ │ ┌─────────┐ │ │ │ │ │ │ IO调度器│ │ │ │ │ │ └─────────┘ │ │ │ │ │ │ │ │ │ │ ▼ ▼ │ │ │ │ ┌─────────┐ ┌─────────┐ │ │ └──────────────┴─────────┤ MMC驱动 │───►│ eMMC │ │ │ 复制 │ 层 │ │ 硬件 │ │ │ (用户态/内核态) └─────────┘ └─────────┘ │ │ │ │ 特点:多层缓存、文件系统抽象、虚拟地址 │ │ │ └────────────────────────────────────────────────────────────────────┘
四、数据Pipeline流程树形分析
4.1 完整数据流Pipeline
═══════════════════════════════════════════════════════════════════════ 数据写入Pipeline(4MB区域) ═══════════════════════════════════════════════════════════════════════ Stage 1: 应用层准备 ┌─────────────────────────────────────────────────────────────────────┐ │ [用户数据] → 缓冲区分配 → 数据准备完成 │ │ 耗时: O(1) │ │ 关键点: 数据对齐、大小边界检查 │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ Stage 2: 系统调用层(仅Linux) ┌─────────────────────────────────────────────────────────────────────┐ │ write(fd, buf, size) │ │ │ │ │ ├─► 用户态/内核态切换 (约1-2μs) │ │ ├─► copy_from_user() 复制数据到内核缓冲区 (size/带宽) │ │ │ └─► 性能: ~5GB/s on DDR4 │ │ └─► 返回系统调用 │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ Stage 3: 页缓存层(仅Linux) ┌─────────────────────────────────────────────────────────────────────┐ │ Page Cache 处理: │ │ ├─► 查找/分配页帧 │ │ ├─► 标记脏页 (dirty page) │ │ └─► 后台pdflush线程定期刷盘 │ │ 延迟: 异步写入,实际落盘延迟不确定 │ │ 风险: 掉电丢失数据(需调用fsync) │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ Stage 4: 文件系统层 ┌─────────────────────────────────────────────────────────────────────┐ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ ext4 处理流程: │ │ │ │ ├─► 检查权限/磁盘配额 │ │ │ │ ├─► 日志记录 (jbd2) - 可选 │ │ │ │ │ └─► 先写日志,后写数据 (data=ordered模式) │ │ │ │ ├─► 块分配 (ext4_get_block) │ │ │ │ │ ├─► 查询inode块映射 │ │ │ │ │ └─► 分配新块(如需要) │ │ │ │ └─► 生成bio请求 │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ FAT32 处理流程: │ │ │ │ ├─► 查找/更新FAT表 │ │ │ │ ├─► 更新目录项 │ │ │ │ └─► 直接生成bio(无日志) │ │ │ └──────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ Stage 5: 块层 ┌─────────────────────────────────────────────────────────────────────┐ │ Block Layer: │ │ ├─► 请求合并 (merge adjacent requests) │ │ │ └─► 减少IO次数,提升吞吐量 │ │ ├─► IO调度器: │ │ │ ├─► CFQ: 公平队列 (桌面/服务器) │ │ │ ├─► Deadline: 延迟优先 (实时系统) │ │ │ └─► NOOP: 无调度 (SSD/eMMC推荐) │ │ └─► 生成块设备请求 (struct request) │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ Stage 6: MMC核心层 ┌─────────────────────────────────────────────────────────────────────┐ │ MMC Core (drivers/mmc/core/): │ │ ├─► 分区映射: LBA → 物理地址 │ │ ├─► 坏块管理: 重映射 (由eMMC内部处理) │ │ ├─► 命令构建: │ │ │ ├─► CMD24: 单块写 │ │ │ ├─► CMD25: 多块写 │ │ │ └─► 自动CMD23: 预定义块数 (eMMC 5.0+) │ │ └─► DMA准备: 映射内存地址 │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ Stage 7: MMC主机控制器驱动 ┌─────────────────────────────────────────────────────────────────────┐ │ Host Controller Driver: │ │ ├─► 配置控制器寄存器 │ │ ├─► 设置DMA描述符 │ │ ├─► 发送命令到eMMC │ │ ├─► 等待中断/轮询完成 │ │ └─► 处理错误/重试 │ │ 耗时: 取决于总线速度 (HS400: 200MB/s, HS200: 200MB/s) │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ Stage 8: eMMC硬件 ┌─────────────────────────────────────────────────────────────────────┐ │ eMMC Device Internal: │ │ ├─► MMC协议解析 │ │ ├─► 写入缓冲区 (DRAM-like cache) │ │ ├─► 磨损均衡算法 (动态/静态) │ │ ├─► 坏块替换 (BBT - Bad Block Table) │ │ └─► 实际写入NAND Flash (最耗时) │ │ └─► 编程时间: ~1-2ms per page (256KB) │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ [数据持久化完成]
4.2 各阶段性能瓶颈分析
Pipeline 瓶颈分析树 │ ├─ Stage 1-2 (应用层) │ ├─ 瓶颈: 数据拷贝 (copy_from_user) │ ├─ 优化: 使用mmap绕过拷贝 │ └─ 延迟: 0.5-2μs per 4KB │ ├─ Stage 3 (页缓存) [Linux only] │ ├─ 瓶颈: 脏页回写延迟 │ ├─ 优化: sync/fsync强制落盘 │ └─ 延迟: 异步,可能5ms-5s │ ├─ Stage 4 (文件系统) │ ├─ 瓶颈: 元数据更新、日志写入 │ ├─ 优化: 禁用日志 (ext4: data=writeback) │ └─ 延迟: 0.1-1ms per operation │ ├─ Stage 5-6 (块层+MMC) │ ├─ 瓶颈: 请求队列深度 │ ├─ 优化: 使用blktrace分析 │ └─ 延迟: 0.01-0.1ms │ └─ Stage 7-8 (硬件) ├─ 瓶颈: NAND编程时间、总线速度 ├─ 优化: 使用HS400模式、批量写入 └─ 延迟: 1-2ms per page (256KB)
五、文件变成只读的Bug分析与调试
5.1 常见只读问题原因树形分析
文件变成只读 (Read-Only) 问题树 │ ├─ 1. 文件系统层面 │ │ │ ├─ 1.1 文件系统错误 (FS Corruption) │ │ ├─ 症状: dmesg显示 "ext4-fs error" │ │ ├─ 原因: 突然掉电、硬件不稳定 │ │ ├─ 检查: fsck /dev/mmcblk0p1 │ │ └─ 修复: e2fsck -p /dev/mmcblk0p1 │ │ │ ├─ 1.2 文件系统挂载为只读 (ro) │ │ ├─ 症状: mount | grep emmc 显示 (ro) │ │ ├─ 原因: │ │ │ ├─ 内核检测到错误自动remount-ro │ │ │ ├─ /etc/fstab 配置为 ro │ │ │ └─ 手动mount时加了 -o ro │ │ ├─ 检查: cat /proc/mounts | grep emmc │ │ └─ 修复: mount -o remount,rw /mnt/emmc │ │ │ └─ 1.3 文件系统日志回放失败 │ ├─ 症状: "Journal has aborted" │ ├─ 原因: 日志区域损坏 │ └─ 修复: tune2fs -O ^has_journal /dev/mmcblk0p1 │ ├─ 2. 文件权限层面 │ │ │ ├─ 2.1 文件属性不可变 (Immutable) │ │ ├─ 症状: chattr +i 设置 │ │ ├─ 检查: lsattr file.bin │ │ ├─ 输出: ----i--------e--- file.bin │ │ └─ 修复: chattr -i file.bin │ │ │ ├─ 2.2 文件权限不足 │ │ ├─ 症状: Permission denied │ │ ├─ 检查: ls -l file.bin │ │ └─ 修复: chmod 666 file.bin │ │ │ └─ 2.3 目录权限不足 │ ├─ 症状: 目录不可写 │ └─ 修复: chmod 777 /mnt/emmc │ ├─ 3. 存储设备层面 │ │ │ ├─ 3.1 eMMC进入只读模式 │ │ ├─ 症状: mmcblk0: error -110, critical target failure │ │ ├─ 原因: │ │ │ ├─ 超过寿命 (eMMC写寿命耗尽) │ │ │ ├─ 坏块过多触发保护 │ │ │ └─ 温度过高保护 │ │ ├─ 检查: mmc extcsd read /dev/mmcblk0 │ │ │ └─ 关注: EXT_CSD_PRE_EOL_INFO (预寿命信息) │ │ └─ 修复: 硬件替换 (不可逆) │ │ │ ├─ 3.2 分区表损坏 │ │ ├─ 症状: "Partition table invalid" │ │ ├─ 检查: fdisk -l /dev/mmcblk0 │ │ └─ 修复: 重建分区表 │ │ │ └─ 3.3 eMMC硬件故障 │ ├─ 症状: 持续CRC错误、CMD超时 │ ├─ 检查: mmc health check │ └─ 修复: 更换硬件 │ └─ 4. 内核层面 │ ├─ 4.1 驱动Bug导致remount-ro │ ├─ 症状: "mmc0: timeout waiting for hardware interrupt" │ ├─ 分析: 查看dmesg时间戳 │ └─ 修复: 升级内核/驱动 │ └─ 4.2 电源管理问题 ├─ 症状: 唤醒后变只读 ├─ 原因: 休眠时eMMC掉电 └─ 修复: 调整PM策略
5.2 调试手段与处理流程
5.2.1 问题诊断命令树
# ==================== 诊断命令树 ==================== # 1. 检查挂载状态 mount | grep emmc cat /proc/mounts | grep emmc # 2. 检查dmesg错误 dmesg | grep -i "mmc\|ext4\|fat\|error\|read-only" dmesg | grep -i "remount" # 3. 检查文件系统完整性 # ext4: e2fsck -n /dev/mmcblk0p1 # 只检查,不修复 e2fsck -p /dev/mmcblk0p1 # 自动修复 dumpe2fs -h /dev/mmcblk0p1 | grep -i "error" # FAT32: fsck.vfat -v /dev/mmcblk0p1 # 4. 检查eMMC健康状态 # 安装mmc-utils git clone https://git.kernel.org/pub/scm/utils/mmc/mmc-utils.git cd mmc-utils && make # 读取EXT_CSD寄存器 ./mmc extcsd read /dev/mmcblk0 # 关键字段解读: # EXT_CSD_PRE_EOL_INFO: 预寿命信息 # 0x00: 正常 # 0x01: 消耗80%寿命 # 0x02: 消耗90%寿命 (警告) # 0x03: 寿命耗尽 (只读) # # EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_A/B: 使用时间估算 # 5. 性能测试 # 写入测试 dd if=/dev/zero of=/mnt/emmc/test bs=1M count=10 conv=fsync # 读取测试 dd if=/mnt/emmc/test of=/dev/null bs=1M # 6. 跟踪系统调用 strace -e open,write,read,fsync dd if=/dev/zero of=/mnt/emmc/test bs=1M count=1 # 7. 监控块IO blktrace -d /dev/mmcblk0 -o - | blkparse -i - # 8. 查看eMMC统计 cat /sys/block/mmcblk0/stat # 字段: 读完成次数, 读合并次数, 读扇区数, 读耗时(ms), # 写完成次数, 写合并次数, 写扇区数, 写耗时(ms)
5.2.2 调试脚本示例
#!/bin/bash
# debug_emmc_ro.sh - eMMC只读问题调试脚本
echo "========== eMMC Read-Only Debug Tool =========="
echo ""
# 1. 检查设备存在性
echo "[1] Checking eMMC device..."
if [ ! -e /dev/mmcblk0 ]; then
echo "ERROR: /dev/mmcblk0 not found!"
exit 1
fi
echo "OK: /dev/mmcblk0 exists"
echo ""
# 2. 检查分区
echo "[2] Checking partition..."
lsblk /dev/mmcblk0
echo ""
# 3. 检查挂载状态
echo "[3] Mount status:"
mount | grep mmcblk0 || echo "Not mounted"
echo ""
# 4. 检查写保护
echo "[4] Checking write-protect status:"
WP=$(cat /sys/block/mmcblk0/force_ro)
if [ "$WP" == "1" ]; then
echo "WARNING: Device is force read-only!"
else
echo "OK: Device is not force read-only"
fi
echo ""
# 5. 检查dmesg错误
echo "[5] Recent MMC errors in dmesg:"
dmesg | grep -i "mmc.*error" | tail -10
echo ""
# 6. 测试写入
echo "[6] Testing write capability..."
TEST_FILE="/tmp/emmc_test_$$"
dd if=/dev/zero of=$TEST_FILE bs=1M count=1 2>/dev/null
if [ -f "$TEST_FILE" ]; then
echo "OK: Write to /tmp successful"
rm -f $TEST_FILE
else
echo "ERROR: Cannot write to /tmp"
fi
# 7. 如果挂载了,测试分区写入
MOUNT_POINT=$(mount | grep mmcblk0p1 | awk '{print $3}')
if [ -n "$MOUNT_POINT" ]; then
echo "Testing write to $MOUNT_POINT..."
TEST_FILE="$MOUNT_POINT/test_$$"
echo "test" > $TEST_FILE 2>&1
if [ $? -eq 0 ]; then
echo "OK: Write successful"
rm -f $TEST_FILE
else
echo "ERROR: Write failed! Attempting remount..."
mount -o remount,rw $MOUNT_POINT 2>&1
if [ $? -eq 0 ]; then
echo "OK: Remounted as read-write"
else
echo "ERROR: Cannot remount as RW"
fi
fi
fi
echo ""
# 8. eMMC健康检查(如果有mmc工具)
if command -v mmc &> /dev/null; then
echo "[7] eMMC health info:"
mmc extcsd read /dev/mmcblk0 | grep -E "PRE_EOL|LIFE_TIME"
else
echo "[7] mmc-utils not installed (optional)"
fi
echo ""
echo "========== Debug Complete =========="
5.3 只读问题处理流程图
┌─────────────────────────────────────────────────────────────────────┐ │ 只读问题处理决策树 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 发现文件只读 │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 步骤1: 快速诊断 │ │ │ │ mount | grep -E "ro|rw" │ │ │ │ dmesg | tail -50 │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ dmesg有错误? │ │ │ │ │ ┌───┴───┐ │ │ 是 否 │ │ │ │ │ │ ▼ ▼ │ │ ┌───┐ ┌─────────────────────────────────────────┐ │ │ │A │ │ 步骤2: 检查挂载选项 │ │ │ └───┘ │ mount | grep mmcblk0 │ │ │ └─────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ 挂载为ro? │ │ │ │ │ ┌───┴───┐ │ │ 是 否 │ │ │ │ │ │ ▼ ▼ │ │ ┌───┐ ┌─────────────────────────────────┐ │ │ │B │ │ 步骤3: 检查文件权限 │ │ │ └───┘ │ ls -l /mnt/emmc │ │ │ └─────────────────────────────────┘ │ │ │ │ │ ▼ │ │ 权限不足? │ │ │ │ │ ┌───┴───┐ │ │ 是 否 │ │ │ │ │ │ ▼ ▼ │ │ ┌───┐ ┌─────────────────────────┐ │ │ │C │ │ 步骤4: 检查文件属性 │ │ │ └───┘ │ lsattr /mnt/emmc/file │ │ │ └─────────────────────────┘ │ │ │ │ │ ▼ │ │ 不可变属性? │ │ │ │ │ ┌───┴───┐ │ │ 是 否 │ │ │ │ │ │ ▼ ▼ │ │ ┌───┐ ┌─────────────────┐ │ │ │D │ │ 步骤5: eMMC硬件 │ │ │ └───┘ │ 健康检查 │ │ │ └─────────────────┘ │ │ │ │ │ ▼ │ │ 寿命耗尽? │ │ │ │ │ ┌───┴───┐ │ │ 是 否 │ │ │ │ │ │ ▼ ▼ │ │ ┌───┐ ┌─────────────────┐ │ │ │E │ │ 步骤6: 文件系统 │ │ │ └───┘ │ 完整性检查 │ │ │ └─────────────────┘ │ │ │ │ │ ▼ │ │ 损坏? │ │ │ │ │ ┌───┴───┐ │ │ 是 否 │ │ │ │ │ │ ▼ ▼ │ │ ┌───┐ ┌─────────────┐ │ │ │F │ │ 步骤7: 驱动 │ │ │ └───┘ │ Bug分析 │ │ │ └─────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ 处理方案: ┌─────────────────────────────────────────────────────────────────────┐ │ A: 文件系统错误修复 │ │ ├─ e2fsck -f -y /dev/mmcblk0p1 │ │ └─ 如果失败,考虑备份后重新格式化 │ │ │ │ B: 重新挂载为读写 │ │ ├─ mount -o remount,rw /mnt/emmc │ │ └─ 如果失败,检查/etc/fstab是否配置为ro │ │ │ │ C: 修改权限 │ │ ├─ chmod 666 /mnt/emmc/file │ │ └─ chown user:group /mnt/emmc/file │ │ │ │ D: 移除不可变属性 │ │ └─ chattr -i /mnt/emmc/file │ │ │ │ E: 硬件替换(不可恢复) │ │ ├─ 备份重要数据 │ │ ├─ 更换eMMC芯片 │ │ └─ 重新烧录固件 │ │ │ │ F: 文件系统修复 │ │ ├─ fsck.ext4 -f -y /dev/mmcblk0p1 │ │ └─ 如果频繁损坏,检查电源稳定性 │ └─────────────────────────────────────────────────────────────────────┘
5.4 预防性措施
/* 预防只读问题的代码实践 */
/**
* @fn safe_write_with_fsync
* @brief 安全的写入操作,确保数据持久化
*/
int safe_write_with_fsync(const char *filename, const uint8_t *data, size_t size)
{
int fd;
ssize_t written;
int ret = -1;
fd = open(filename, O_WRONLY | O_CREAT | O_SYNC, 0644);
if (fd < 0) {
printf("Failed to open %s: %s\n", filename, strerror(errno));
return -1;
}
/* 写入数据 */
written = write(fd, data, size);
if (written != (ssize_t)size) {
printf("Write failed\n");
goto out;
}
/* 强制同步到存储(关键!)*/
if (fsync(fd) != 0) {
printf("fsync failed: %s\n", strerror(errno));
goto out;
}
ret = 0;
out:
close(fd);
return ret;
}
/**
* @fn monitor_emmc_health
* @brief 监控eMMC健康状态的后台线程
*/
void *monitor_emmc_health(void *arg)
{
while (1) {
/* 读取eMMC寿命信息 */
FILE *fp = fopen("/sys/block/mmcblk0/device/life_time", "r");
if (fp) {
char life_time[64];
if (fgets(life_time, sizeof(life_time), fp)) {
int used_a = (life_time[0] & 0xFF);
int used_b = (life_time[1] & 0xFF);
/* 寿命超过90%时告警 */
if (used_a >= 0x0A || used_b >= 0x0A) {
printf("WARNING: eMMC lifetime exceeded 90%%!\n");
/* 触发告警、发送日志等 */
}
}
fclose(fp);
}
/* 每24小时检查一次 */
sleep(24 * 3600);
}
return NULL;
}
/**
* @fn periodic_fsck
* @brief 定期检查文件系统(在系统启动时)
*/
void periodic_fsck(const char *device)
{
struct stat st;
char cmd[256];
/* 检查上次检查时间 */
if (stat("/var/lib/fsck_timestamp", &st) == 0) {
time_t now = time(NULL);
if (now - st.st_mtime < 30 * 24 * 3600) {
return; /* 30天内检查过 */
}
}
/* 执行文件系统检查 */
snprintf(cmd, sizeof(cmd), "e2fsck -p %s", device);
int ret = system(cmd);
if (ret == 0) {
/* 更新检查时间 */
system("touch /var/lib/fsck_timestamp");
} else {
printf("fsck returned %d, manual intervention needed\n", ret);
}
}
六、完整解决方案总结
6.1 推荐方案组合
| 使用场景 | 分区方式 | 文件系统 | 访问方式 |
|---|---|---|---|
| U-Boot配置参数 | 固定偏移量 | 无文件系统 | mmc raw读写 |
| Linux系统配置 | GPT分区 | ext4 | 文件系统API |
| 跨平台数据 | GPT分区 | FAT32 | 文件系统API |
| 只读固件 | GPT分区 | SquashFS | 文件系统API |
6.2 关键注意事项
-
分区对齐: 起始扇区必须对齐到1MB (2048扇区),避免性能损失
-
文件系统选择: 根据实际需求选择,不要盲目使用ext4
-
同步操作: 关键数据必须使用
fsync()或O_SYNC -
健康监控: 定期检查eMMC寿命信息,提前预警
-
掉电保护: 考虑使用UPS或超级电容确保关键写入完成
第二部分 uboot 处理阶段
U-Boot与Linux读写接口对比分析
U-Boot的读写接口确实与Linux完全不同。这是嵌入式开发中需要特别注意的地方。
1. 接口差异对比表
| 维度 | U-Boot | Linux |
|---|---|---|
| 设备访问方式 | 直接命令操作 + 内存地址 | VFS文件系统接口 |
| 数据传递 | 通过内存地址(如0xA0000000) |
通过用户态缓冲区指针 |
| 块操作 | mmc read/write 按块操作 |
read()/write() 系统调用 |
| 文件系统 | ext4load/ext4write 命令 |
POSIX API(open/read/write) |
| 设备标识 | mmc 0:6(设备号:分区号) |
/dev/mmcblk0p6 |
| 抽象层次 | 直接操作MMC控制器 | 经过块层→文件系统→VFS |
| API风格 | 命令行 + C函数接口 | 标准POSIX系统调用 |
2. U-Boot MMC接口详解
2.1 底层API函数(U-Boot源码)
U-Boot的MMC驱动位于drivers/mmc/mmc.c,核心函数如下:
/**
* @file uboot_mmc_interface.h
* @brief U-Boot MMC底层接口定义(根据源码分析)
* @note 这些是U-Boot内部的函数,命令层会调用它们
*/
#ifndef UBOOT_MMC_INTERFACE_H
#define UBOOT_MMC_INTERFACE_H
#include <mmc.h>
/**
* @struct mmc
* @brief MMC设备结构体(U-Boot定义)
* @note 位于 include/mmc.h
*/
struct mmc {
void *priv; /**< 控制器私有数据 */
uint64_t capacity; /**< 设备容量(字节) */
uint32_t block_size; /**< 块大小(通常512字节) */
uint32_t bus_width; /**< 总线宽度(1/4/8位) */
uint32_t clock; /**< 时钟频率(Hz) */
/* ... 更多字段 ... */
};
/**
* @fn mmc_send_cmd
* @brief 发送MMC命令到设备
* @param mmc MMC设备指针
* @param cmd 命令结构体
* @param data 数据指针(可选)
* @return 0 成功,负数为错误
* @note 这是最底层的命令发送函数
*/
int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data);
/**
* @fn mmc_bread
* @brief 从MMC设备读取块数据
* @param dev MMC设备指针(或udevice/block_desc,取决于配置)
* @param start 起始块号(LBA)
* @param blkcnt 块数量
* @param dst 目标内存地址
* @return 成功读取的块数量,0为失败
* @note
* 【设计模式】适配器模式 - 统一BLK层和传统接口
* 【性能分析】O(n) 时间复杂度,n为块数量,受总线速度限制
*/
ulong mmc_bread(struct mmc *mmc, lbaint_t start, lbaint_t blkcnt, void *dst);
/**
* @fn mmc_bwrite
* @brief 向MMC设备写入块数据
* @param mmc MMC设备指针
* @param start 起始块号(LBA)
* @param blkcnt 块数量
* @param src 源内存地址
* @return 成功写入的块数量,0为失败
* @note 需要配置 CONFIG_MMC_WRITE 才能启用
*/
ulong mmc_bwrite(struct mmc *mmc, lbaint_t start, lbaint_t blkcnt, const void *src);
/**
* @fn mmc_berase
* @brief 擦除MMC设备块
* @param mmc MMC设备指针
* @param start 起始块号(LBA)
* @param blkcnt 块数量
* @return 成功擦除的块数量,0为失败
* @note 擦除粒度受 Erase Group Size 限制
*/
ulong mmc_berase(struct mmc *mmc, lbaint_t start, lbaint_t blkcnt);
/**
* @fn mmc_switch
* @brief 切换MMC分区或配置
* @param mmc MMC设备指针
* @param set 设置类型
* @param index 索引
* @param value 值
* @return 0 成功,负数为错误
*/
int mmc_switch(struct mmc *mmc, u8 set, u8 index, u8 value);
#endif /* UBOOT_MMC_INTERFACE_H */
2.2 文件系统访问函数
U-Boot的文件系统接口位于fs/目录,提供以下功能:
/** * @file uboot_fs_interface.h * @brief U-Boot文件系统抽象层 * @note 这些函数被 ext4load/fatload 等命令使用 */ /** * @fn fs_set_blk_dev * @brief 设置当前操作的块设备 * @param ifname 接口名称("mmc", "usb", "scsi"等) * @param dev_part 设备号:分区号(如 "0:6") * @return 0 成功,负数为错误 */ int fs_set_blk_dev(const char *ifname, const char *dev_part); /** * @fn fs_read * @brief 从文件系统读取文件 * @param filename 文件路径 * @param addr 加载到内存的地址 * @param offset 文件内偏移 * @param len 读取长度 * @return 实际读取的字节数,负数为错误 * @note * 【关键差异】U-Boot需要指定内存地址,Linux不需要 * 【性能分析】每次调用都会重新挂载文件系统,无缓存 */ int fs_read(const char *filename, ulong addr, loff_t offset, ulong len); /** * @fn fs_write * @brief 向文件系统写入文件 * @param filename 文件路径 * @param addr 数据所在内存地址 * @param offset 文件内偏移 * @param len 写入长度 * @return 实际写入的字节数,负数为错误 * @note 需要配置 CONFIG_CMD_EXT4_WRITE 或 CONFIG_CMD_FAT_WRITE */ int fs_write(const char *filename, ulong addr, loff_t offset, ulong len); /** * @fn fs_ls * @brief 列出目录内容 * @param dirname 目录路径 * @return 0 成功,负数为错误 */ int fs_ls(const char *dirname);
3. U-Boot命令层接口
U-Boot用户实际使用的是命令行命令,其实现位于common/cmd_mmc.c:
| 命令 | 语法 | 功能 | 对应底层函数 |
|---|---|---|---|
mmc list |
mmc list |
列出所有MMC设备 | 遍历mmc设备列表 |
mmc dev |
mmc dev [dev] [part] |
切换当前设备/分区 | mmc_select_hwpart() |
mmc info |
mmc info |
显示设备信息 | 读取CSD/EXT_CSD |
mmc read |
mmc read addr blk# cnt |
原始块读取 | mmc_bread() |
mmc write |
mmc write addr blk# cnt |
原始块写入 | mmc_bwrite() |
mmc erase |
mmc erase blk# cnt |
原始块擦除 | mmc_berase() |
ext4load |
ext4load <dev>:<part> <addr> <file> |
读取ext4文件 | fs_read() |
ext4write |
ext4write <dev>:<part> <addr> <file> <bytes> |
写入ext4文件 | fs_write() |
fatload |
fatload <dev>:<part> <addr> <file> |
读取FAT文件 | fs_read() |
fatwrite |
fatwrite <dev>:<part> <addr> <file> <bytes> |
写入FAT文件 | fs_write() |
4. U-Boot专用代码实现
基于上述接口差异,下面是专门为U-Boot编写的4MB区域管理代码:
/**
* @file uboot_emmc_manager.c
* @brief U-Boot环境下的eMMC 4MB区域管理
* @author Embedded Team
* @date 2026-03-30
*
* @details 本文件实现U-Boot控制台下的eMMC管理功能。
* 与Linux版本的关键差异:
* 1. 使用内存地址传递数据(如0xA0000000)
* 2. 直接调用mmc_bread/mmc_bwrite等底层函数
* 3. 通过命令或API操作,无VFS层
*
* 【设计模式】
* - 命令模式:将每个操作封装为独立函数
* - 策略模式:支持RAW模式和文件系统模式
*/
#include <common.h>
#include <command.h>
#include <mmc.h>
#include <fs.h>
#include <malloc.h>
#include <linux/ctype.h>
/* ==================== 配置定义 ==================== */
#define EMMC_DEVICE_NUM 0 /**< eMMC设备号 */
#define EMMC_PARTITION_NUM 1 /**< 分区号(GPT分区1) */
#define EMMC_REGION_START_BLK 2048 /**< 4MB区域起始块(1MB对齐) */
#define EMMC_REGION_BLK_COUNT (4 * 1024 * 1024 / 512) /**< 4MB = 8192块 */
#define EMMC_TEMP_ADDR 0xA0000000 /**< 临时内存地址(需bdinfo确认安全) */
#define EMMC_REGION_FILE "/emmc_region.bin" /**< 区域文件路径 */
/* ==================== 内部函数 ==================== */
/**
* @fn uboot_mmc_get_device
* @brief 获取MMC设备指针
* @return mmc结构体指针,失败返回NULL
* @internal
*
* 【设计模式】工厂模式 - 根据设备号创建设备引用
*/
static struct mmc *uboot_mmc_get_device(void)
{
struct mmc *mmc = find_mmc_device(EMMC_DEVICE_NUM);
if (!mmc) {
printf("[ERROR] MMC device %d not found\n", EMMC_DEVICE_NUM);
return NULL;
}
/* 初始化MMC设备(如果未初始化) */
if (mmc_init(mmc)) {
printf("[ERROR] MMC device %d init failed\n", EMMC_DEVICE_NUM);
return NULL;
}
/* 切换到指定分区 */
if (mmc_switch_part(mmc, EMMC_PARTITION_NUM)) {
printf("[ERROR] Failed to switch to partition %d\n", EMMC_PARTITION_NUM);
return NULL;
}
return mmc;
}
/**
* @fn uboot_mmc_raw_write
* @brief 原始块写入(绕过文件系统)
* @param mmc MMC设备指针
* @param start_blk 起始块号
* @param blk_cnt 块数量
* @param src_addr 源内存地址
* @return 写入的块数量,0为失败
* @internal
*
* 【设计模式】策略模式 - RAW写入策略
* 【性能分析】直接操作块设备,无文件系统开销
*/
static ulong uboot_mmc_raw_write(struct mmc *mmc, lbaint_t start_blk,
lbaint_t blk_cnt, ulong src_addr)
{
printf("[INFO] Writing %lu blocks from 0x%08lX to block %lu\n",
blk_cnt, src_addr, start_blk);
/* 调用底层块写入函数 */
ulong written = mmc_bwrite(mmc, start_blk, blk_cnt, (void *)src_addr);
if (written != blk_cnt) {
printf("[ERROR] Write failed: wrote %lu, expected %lu\n",
written, blk_cnt);
return 0;
}
return written;
}
/**
* @fn uboot_mmc_raw_read
* @brief 原始块读取(绕过文件系统)
* @param mmc MMC设备指针
* @param start_blk 起始块号
* @param blk_cnt 块数量
* @param dst_addr 目标内存地址
* @return 读取的块数量,0为失败
* @internal
*/
static ulong uboot_mmc_raw_read(struct mmc *mmc, lbaint_t start_blk,
lbaint_t blk_cnt, ulong dst_addr)
{
printf("[INFO] Reading %lu blocks to 0x%08lX from block %lu\n",
blk_cnt, dst_addr, start_blk);
ulong read = mmc_bread(mmc, start_blk, blk_cnt, (void *)dst_addr);
if (read != blk_cnt) {
printf("[ERROR] Read failed: read %lu, expected %lu\n",
read, blk_cnt);
return 0;
}
return read;
}
/**
* @fn uboot_mmc_raw_erase
* @brief 原始块擦除
* @param mmc MMC设备指针
* @param start_blk 起始块号
* @param blk_cnt 块数量
* @return 擦除的块数量,0为失败
* @internal
*
* 【设计模式】命令模式 - 擦除命令封装
*/
static ulong uboot_mmc_raw_erase(struct mmc *mmc, lbaint_t start_blk,
lbaint_t blk_cnt)
{
printf("[INFO] Erasing %lu blocks from block %lu\n", blk_cnt, start_blk);
ulong erased = mmc_berase(mmc, start_blk, blk_cnt);
if (erased != blk_cnt) {
printf("[ERROR] Erase failed: erased %lu, expected %lu\n",
erased, blk_cnt);
return 0;
}
return erased;
}
/**
* @fn uboot_fs_file_read
* @brief 通过文件系统读取文件
* @param filename 文件路径
* @param dst_addr 目标内存地址
* @param max_len 最大读取长度
* @return 实际读取字节数,0为失败
* @internal
*
* 【关键差异】需要先设置块设备,然后指定内存地址
*/
static int uboot_fs_file_read(const char *filename, ulong dst_addr, ulong max_len)
{
char dev_part[16];
int ret;
/* 构造设备:分区字符串,如 "0:1" */
snprintf(dev_part, sizeof(dev_part), "%d:%d",
EMMC_DEVICE_NUM, EMMC_PARTITION_NUM);
/* 设置块设备 */
ret = fs_set_blk_dev("mmc", dev_part);
if (ret) {
printf("[ERROR] Failed to set block device %s\n", dev_part);
return 0;
}
/* 读取文件到内存 */
ret = fs_read(filename, dst_addr, 0, max_len);
if (ret < 0) {
printf("[ERROR] Failed to read file %s\n", filename);
return 0;
}
printf("[INFO] Read %d bytes from %s to 0x%08lX\n", ret, filename, dst_addr);
return ret;
}
/**
* @fn uboot_fs_file_write
* @brief 通过文件系统写入文件
* @param filename 文件路径
* @param src_addr 源内存地址
* @param len 写入长度
* @return 实际写入字节数,0为失败
* @internal
*
* @note U-Boot默认禁用ext4write,需要配置:
* CONFIG_CMD_EXT4_WRITE=y
*/
static int uboot_fs_file_write(const char *filename, ulong src_addr, ulong len)
{
char dev_part[16];
int ret;
snprintf(dev_part, sizeof(dev_part), "%d:%d",
EMMC_DEVICE_NUM, EMMC_PARTITION_NUM);
ret = fs_set_blk_dev("mmc", dev_part);
if (ret) {
printf("[ERROR] Failed to set block device %s\n", dev_part);
return 0;
}
ret = fs_write(filename, src_addr, 0, len);
if (ret < 0) {
printf("[ERROR] Failed to write file %s\n", filename);
return 0;
}
printf("[INFO] Wrote %d bytes from 0x%08lX to %s\n", ret, src_addr, filename);
return ret;
}
/* ==================== 公共API ==================== */
/**
* @fn uboot_emmc_raw_region_write
* @brief 通过原始块设备写入4MB区域
* @param data 数据缓冲区(内存地址)
* @param size 数据大小(字节)
* @param offset 区域偏移(字节)
* @return 0成功,负数为错误
*
* 【使用场景】需要绕过文件系统直接操作块设备
* 【性能】最高,无文件系统开销
*/
int uboot_emmc_raw_region_write(ulong data_addr, ulong size, ulong offset)
{
struct mmc *mmc;
lbaint_t start_blk;
ulong blk_cnt;
ulong written;
/* 参数验证 */
if (data_addr == 0 || size == 0 || offset + size > 4 * 1024 * 1024) {
printf("[ERROR] Invalid parameters\n");
return -1;
}
/* 获取MMC设备 */
mmc = uboot_mmc_get_device();
if (!mmc) return -2;
/* 计算起始块和块数(512字节块) */
start_blk = EMMC_REGION_START_BLK + (offset / 512);
blk_cnt = (size + 511) / 512; /* 向上取整到块对齐 */
/* 执行写入 */
written = uboot_mmc_raw_write(mmc, start_blk, blk_cnt, data_addr);
return (written == blk_cnt) ? 0 : -3;
}
/**
* @fn uboot_emmc_raw_region_read
* @brief 通过原始块设备读取4MB区域
* @param buffer_addr 目标内存地址
* @param size 读取大小(字节)
* @param offset 区域偏移(字节)
* @return 0成功,负数为错误
*/
int uboot_emmc_raw_region_read(ulong buffer_addr, ulong size, ulong offset)
{
struct mmc *mmc;
lbaint_t start_blk;
ulong blk_cnt;
ulong read;
if (buffer_addr == 0 || size == 0 || offset + size > 4 * 1024 * 1024) {
return -1;
}
mmc = uboot_mmc_get_device();
if (!mmc) return -2;
start_blk = EMMC_REGION_START_BLK + (offset / 512);
blk_cnt = (size + 511) / 512;
read = uboot_mmc_raw_read(mmc, start_blk, blk_cnt, buffer_addr);
return (read == blk_cnt) ? 0 : -3;
}
/**
* @fn uboot_emmc_raw_region_erase
* @brief 擦除4MB区域(原始块方式)
* @return 0成功,负数为错误
*/
int uboot_emmc_raw_region_erase(void)
{
struct mmc *mmc;
ulong erased;
mmc = uboot_mmc_get_device();
if (!mmc) return -1;
erased = uboot_mmc_raw_erase(mmc, EMMC_REGION_START_BLK, EMMC_REGION_BLK_COUNT);
return (erased == EMMC_REGION_BLK_COUNT) ? 0 : -2;
}
/**
* @fn uboot_emmc_fs_region_write
* @brief 通过文件系统写入4MB区域
* @param data_addr 数据内存地址
* @param size 数据大小
* @return 0成功,负数为错误
*
* @note 使用前需确保分区已格式化为ext4或FAT
* 并且已配置CONFIG_CMD_EXT4_WRITE
*/
int uboot_emmc_fs_region_write(ulong data_addr, ulong size)
{
int written;
if (data_addr == 0 || size == 0 || size > 4 * 1024 * 1024) {
return -1;
}
written = uboot_fs_file_write(EMMC_REGION_FILE, data_addr, size);
return (written == (int)size) ? 0 : -2;
}
/**
* @fn uboot_emmc_fs_region_read
* @brief 通过文件系统读取4MB区域
* @param buffer_addr 目标内存地址
* @param max_len 最大读取长度
* @return 实际读取字节数,负数为错误
*/
int uboot_emmc_fs_region_read(ulong buffer_addr, ulong max_len)
{
int read;
if (buffer_addr == 0 || max_len == 0) {
return -1;
}
read = uboot_fs_file_read(EMMC_REGION_FILE, buffer_addr, max_len);
return read;
}
/**
* @fn uboot_emmc_region_info
* @brief 显示4MB区域信息
*/
void uboot_emmc_region_info(void)
{
struct mmc *mmc;
printf("\n=== eMMC 4MB Region Info ===\n");
printf("Device: mmc %d:%d\n", EMMC_DEVICE_NUM, EMMC_PARTITION_NUM);
printf("Region start block: %lu\n", EMMC_REGION_START_BLK);
printf("Region block count: %lu (%lu bytes)\n",
EMMC_REGION_BLK_COUNT, EMMC_REGION_BLK_COUNT * 512);
printf("Region size: 4 MB\n");
mmc = uboot_mmc_get_device();
if (mmc) {
printf("Block size: %u bytes\n", mmc->block_size);
printf("Bus width: %u-bit\n", mmc->bus_width);
printf("Clock: %u Hz\n", mmc->clock);
}
printf("============================\n\n");
}
5. U-Boot命令封装(可选)
可以将上述函数封装为U-Boot命令,方便调试:
/**
* @file cmd_emmc_region.c
* @brief U-Boot命令:emmc_region
* @note 编译后可在U-Boot控制台直接使用
*/
#include <command.h>
/**
* @fn do_emmc_region
* @brief emmc_region 命令处理函数
*
* 用法:
* emmc_region info - 显示区域信息
* emmc_region write <addr> <size> - 从内存写入区域
* emmc_region read <addr> <size> - 读取区域到内存
* emmc_region erase - 擦除区域
*/
static int do_emmc_region(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
ulong addr, size;
int ret;
if (argc < 2) {
return CMD_RET_USAGE;
}
if (strcmp(argv[1], "info") == 0) {
uboot_emmc_region_info();
return CMD_RET_SUCCESS;
}
if (strcmp(argv[1], "write") == 0) {
if (argc < 4) return CMD_RET_USAGE;
addr = simple_strtoul(argv[2], NULL, 16);
size = simple_strtoul(argv[3], NULL, 10);
ret = uboot_emmc_raw_region_write(addr, size, 0);
return ret ? CMD_RET_FAILURE : CMD_RET_SUCCESS;
}
if (strcmp(argv[1], "read") == 0) {
if (argc < 4) return CMD_RET_USAGE;
addr = simple_strtoul(argv[2], NULL, 16);
size = simple_strtoul(argv[3], NULL, 10);
ret = uboot_emmc_raw_region_read(addr, size, 0);
return ret ? CMD_RET_FAILURE : CMD_RET_SUCCESS;
}
if (strcmp(argv[1], "erase") == 0) {
ret = uboot_emmc_raw_region_erase();
return ret ? CMD_RET_FAILURE : CMD_RET_SUCCESS;
}
return CMD_RET_USAGE;
}
U_BOOT_CMD(
emmc_region, 4, 1, do_emmc_region,
"eMMC 4MB region management",
"info - Show region info\n"
"emmc_region write <addr> <size> - Write from memory to region\n"
"emmc_region read <addr> <size> - Read region to memory\n"
"emmc_region erase - Erase region"
);
6. 使用示例
6.1 U-Boot控制台操作
# 进入U-Boot控制台(启动时按空格键) # 1. 查看MMC设备 => mmc list mmc@fe2e0000: 0 (eMMC) mmc@fe2c0000: 1 (SD) # 2. 切换eMMC设备 => mmc dev 0 switch to partitions #0, OK mmc0(part 0) is current device # 3. 查看分区 => mmc part Partition Map for MMC device 0 -- Partition Type: EFI Part Start LBA End LBA Name 1 0x00004000 0x00005fff "uboot" 2 0x00006000 0x00007fff "misc" 3 0x00008000 0x00017fff "emmc_region" # 4MB区域分区 # 4. 使用自定义命令操作4MB区域 => emmc_region info # 5. 准备测试数据(在内存0xA0000000填充0x55) => mw.b 0xA0000000 0x55 0x1000 # 6. 写入4MB区域 => emmc_region write 0xA0000000 0x1000 # 7. 读取验证 => emmc_region read 0xA1000000 0x1000 => md.b 0xA1000000 0x20 # 查看内存数据
6.2 与Linux对比示例
| 操作 | U-Boot命令 | Linux命令 |
|---|---|---|
| 查看分区 | mmc part |
cat /proc/partitions |
| 读取文件 | ext4load mmc 0:1 0xA0000000 /file.bin |
cat /mnt/emmc/file.bin |
| 写入文件 | ext4write mmc 0:1 0xA0000000 /file.bin 0x1000 |
dd if=/dev/zero of=/mnt/emmc/file.bin bs=1k count=4 |
| 原始读块 | mmc read 0xA0000000 0x4000 0x2000 |
dd if=/dev/mmcblk0 of=/tmp/data bs=512 skip=16384 count=8192 |
| 原始写块 | mmc write 0xA0000000 0x4000 0x2000 |
dd if=/tmp/data of=/dev/mmcblk0 bs=512 seek=16384 count=8192 |
7. 关键差异总结
| 特性 | U-Boot | Linux |
|---|---|---|
| 内存地址 | 必须指定(如0xA0000000) |
由内核管理,用户空间用指针 |
| 数据传递 | 直接DMA到指定物理地址 | 经过用户态/内核态拷贝 |
| 文件系统缓存 | 无 | 有页缓存 |
| 并发访问 | 单线程命令执行 | 支持多进程并发 |
| 错误处理 | 命令返回码 | errno + 异常 |
| 分区识别 | 需要手动mmc part |
内核自动识别 |
8. 配置建议
在U-Boot源码中需要启用的配置选项:
# Kconfig配置 CONFIG_CMD_MMC=y # 启用mmc命令 CONFIG_MMC_WRITE=y # 启用MMC写入功能 CONFIG_CMD_EXT4=y # 启用ext4文件系统支持 CONFIG_CMD_EXT4_WRITE=y # 启用ext4写入(默认禁用!) CONFIG_CMD_FAT=y # 启用FAT支持 CONFIG_CMD_FAT_WRITE=y # 启用FAT写入 CONFIG_MMC_HS400_SUPPORT=y # 启用HS400高速模式(可选)
重点提醒:
-
CONFIG_CMD_EXT4_WRITE默认是禁用的,需要在menuconfig中手动开启 -
使用原始块操作(
mmc read/write)无需文件系统支持,但需要自己管理块映射 -
U-Boot无文件系统缓存,每次文件操作都会重新挂载
第三部分 kernel 处理阶段
Linux/U-Boot eMMC 4MB区域管理方案
1. 方案概述
Linux和U-Boot已提供完整的文件系统支持,可直接使用现有方案管理eMMC的4MB区域。
核心结论:
-
eMMC自带MMC控制器,内置坏块管理和磨损均衡
-
直接使用块设备 + 文件系统接口,无需操作原始NAND
-
现有文件系统已满足4MB区域管理需求
2. 文件系统选型
| 文件系统 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| FAT32 | U-Boot与Linux共享数据 | U-Boot原生支持,跨平台兼容性好 | 不支持权限,单文件<4GB |
| ext4 | Linux专用数据分区 | 日志功能,性能好,支持大文件 | U-Boot支持有限 |
| SquashFS | 只读数据(如固件) | 压缩存储,节省空间 | 只读,不可写 |
推荐组合:
-
U-Boot可访问的分区 → FAT32
-
Linux专用分区 → ext4
3. 分区创建方法
方法一:使用fdisk分区(Linux环境)
# 1. 查看eMMC设备 lsblk # 查找 /dev/mmcblkX sudo fdisk /dev/mmcblk0 # 2. 创建分区(fdisk交互命令) Command (m for help): n # 新建分区 Select (default p): p # 主分区 Partition number: 1 # 分区号 First sector: 2048 # 起始扇区(1MB对齐) Last sector: +4M # 大小4MB Command (m for help): w # 保存并退出 # 3. 格式化分区 sudo mkfs.ext4 /dev/mmcblk0p1 # ext4格式 # 或 sudo mkfs.vfat -F 32 /dev/mmcblk0p1 # FAT32格式 # 4. 挂载使用 sudo mount /dev/mmcblk0p1 /mnt/emmc
方法二:使用parted分区
sudo parted /dev/mmcblk0 (parted) mklabel gpt (parted) mkpart primary ext4 1MB 5MB # 4MB空间 (parted) quit
方法三:U-Boot中分区
# 在U-Boot中创建GPT分区
=> gpt write mmc 0 ${partitions}
=> mmc part
4. 代码实现架构
4.1 目录树形结构
emmc_manager/ ├── include/ │ ├── emmc_manager.h # 模块公共接口 │ ├── partition_manager.h # 分区管理接口 │ └── fs_interface.h # 文件系统抽象层 ├── src/ │ ├── emmc_manager.c # 主控模块 │ ├── partition_manager.c # 分区操作实现 │ ├── fs_interface.c # 文件系统封装 │ ├── mmc_io.c # MMC块设备IO │ └── config.c # 配置管理 ├── tests/ │ ├── test_emmc.c # 单元测试 │ └── benchmark.c # 性能测试 └── docs/ └── doxygen_config # Doxygen配置
4.2 关键函数树形分析
emmc_manager/ │ ├── emmc_init() # 模块初始化 │ ├── mmc_device_open() # 打开MMC设备 │ ├── detect_partition() # 检测分区信息 │ └── fs_mount() # 挂载文件系统 │ ├── emmc_write_region() # 写入4MB区域 │ ├── validate_region() # 区域范围验证 │ ├── fs_open() # 打开文件 │ ├── fs_write() # 写入数据 │ └── fs_sync() # 同步到存储 │ ├── emmc_read_region() # 读取4MB区域 │ ├── fs_open() # 打开文件 │ ├── fs_read() # 读取数据 │ └── fs_close() # 关闭文件 │ ├── emmc_erase_region() # 擦除区域 │ ├── fs_unlink() # 删除文件 │ └── fs_trim() # TRIM优化 │ └── emmc_get_status() # 状态查询 ├── mmc_get_csd() # 获取CSD寄存器 └── fs_statvfs() # 获取文件系统状态
5. 完整代码实现
5.1 头文件 - emmc_manager.h
/**
* @file emmc_manager.h
* @brief eMMC 4MB区域管理模块头文件
* @author Embedded Team
* @version 1.0.0
* @date 2026-03-30
*
* @details 提供eMMC存储设备4MB专用区域的统一管理接口。
* 支持分区创建、文件读写、区域擦除等操作。
* 【设计模式】门面模式 - 为复杂的eMMC操作提供简化接口
*/
#ifndef EMMC_MANAGER_H
#define EMMC_MANAGER_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @defgroup emmc_config 配置宏定义
* @{
*/
#define EMMC_DEVICE_PATH "/dev/mmcblk0" /**< eMMC设备路径 */
#define EMMC_PARTITION_NUM 1 /**< 分区编号 */
#define EMMC_MOUNT_POINT "/mnt/emmc" /**< 挂载点路径 */
#define EMMC_REGION_SIZE (4 * 1024 * 1024) /**< 区域大小 4MB */
#define EMMC_REGION_FILE "/mnt/emmc/region.bin" /**< 区域文件路径 */
/** @} */
/**
* @enum emmc_error_t
* @brief 错误码定义
*/
typedef enum {
EMMC_OK = 0, /**< 操作成功 */
EMMC_ERR_INIT = -1, /**< 初始化失败 */
EMMC_ERR_DEVICE = -2, /**< 设备打开失败 */
EMMC_ERR_MOUNT = -3, /**< 挂载失败 */
EMMC_ERR_FILE_OPEN = -4, /**< 文件打开失败 */
EMMC_ERR_READ = -5, /**< 读取失败 */
EMMC_ERR_WRITE = -6, /**< 写入失败 */
EMMC_ERR_INVALID_PARAM = -7,/**< 参数无效 */
EMMC_ERR_REGION_OVERFLOW = -8 /**< 区域越界 */
} emmc_error_t;
/**
* @struct emmc_region_info_t
* @brief 区域信息结构体
*/
typedef struct {
uint64_t offset; /**< 区域起始偏移(字节) */
uint64_t size; /**< 区域大小(字节) */
char mount_point[64]; /**< 挂载点路径 */
char device_path[64]; /**< 设备路径 */
uint32_t block_size; /**< 块大小(字节) */
} emmc_region_info_t;
/**
* @fn emmc_init
* @brief 初始化eMMC管理模块
* @return EMMC_OK 成功,其他失败
*
* 【设计模式】单例模式 - 确保模块只初始化一次
* 【性能分析】O(1) 时间复杂度,主要耗时在设备探测和挂载操作
*/
emmc_error_t emmc_init(void);
/**
* @fn emmc_deinit
* @brief 反初始化eMMC管理模块
* @return EMMC_OK 成功,其他失败
*/
emmc_error_t emmc_deinit(void);
/**
* @fn emmc_write_region
* @brief 向4MB区域写入数据
* @param data 待写入数据缓冲区指针
* @param size 待写入数据大小(字节)
* @param offset 区域内部偏移(字节)
* @return EMMC_OK 成功,其他失败
*
* 【设计模式】策略模式 - 根据offset和size选择最优写入策略
* 【性能分析】O(n) 时间复杂度,n为写入大小,受底层块设备写入速度限制
*/
emmc_error_t emmc_write_region(const uint8_t *data, size_t size, uint64_t offset);
/**
* @fn emmc_read_region
* @brief 从4MB区域读取数据
* @param buffer 数据缓冲区指针
* @param size 待读取数据大小(字节)
* @param offset 区域内部偏移(字节)
* @return EMMC_OK 成功,其他失败
*
* 【设计模式】策略模式 - 根据offset和size选择最优读取策略
* 【性能分析】O(n) 时间复杂度,n为读取大小,受底层块设备读取速度限制
*/
emmc_error_t emmc_read_region(uint8_t *buffer, size_t size, uint64_t offset);
/**
* @fn emmc_erase_region
* @brief 擦除4MB区域(删除区域文件)
* @return EMMC_OK 成功,其他失败
*
* 【设计模式】命令模式 - 将擦除操作封装为命令对象
* 【性能分析】O(1) 时间复杂度,实际擦除由文件系统完成
*/
emmc_error_t emmc_erase_region(void);
/**
* @fn emmc_get_region_info
* @brief 获取4MB区域信息
* @param info 区域信息结构体指针
* @return EMMC_OK 成功,其他失败
*/
emmc_error_t emmc_get_region_info(emmc_region_info_t *info);
/**
* @fn emmc_verify_region
* @brief 验证4MB区域完整性
* @param expected_checksum 期望校验和
* @return EMMC_OK 校验通过,其他失败
*/
emmc_error_t emmc_verify_region(uint32_t expected_checksum);
#ifdef __cplusplus
}
#endif
#endif /* EMMC_MANAGER_H */
5.2 源文件 - emmc_manager.c
/**
* @file emmc_manager.c
* @brief eMMC 4MB区域管理模块实现
* @author Embedded Team
* @version 1.0.0
* @date 2026-03-30
*
* @details 实现eMMC存储设备4MB区域的完整管理功能。
* 支持FAT32和ext4文件系统,提供文件读写、区域擦除等操作。
*
* 【设计模式汇总】
* 1. 门面模式 - emmc_manager.h提供简化接口
* 2. 单例模式 - 模块初始化状态管理
* 3. 策略模式 - 根据操作类型选择不同IO策略
* 4. 命令模式 - erase操作封装
* 5. 适配器模式 - 适配不同文件系统接口
*/
#include "emmc_manager.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
/** @brief 模块初始化标志 - 单例模式状态管理 */
static int g_initialized = 0;
/** @brief 区域信息缓存 */
static emmc_region_info_t g_region_info = {
.offset = 0,
.size = EMMC_REGION_SIZE,
.mount_point = EMMC_MOUNT_POINT,
.device_path = EMMC_DEVICE_PATH,
.block_size = 512
};
/**
* @fn mmc_device_open
* @brief 打开MMC块设备
* @return 设备文件描述符,负数为错误
* @internal
*/
static int mmc_device_open(void)
{
int fd = open(EMMC_DEVICE_PATH, O_RDWR);
if (fd < 0) {
fprintf(stderr, "[ERROR] Failed to open %s: %s\n",
EMMC_DEVICE_PATH, strerror(errno));
return -errno;
}
return fd;
}
/**
* @fn mmc_get_block_size
* @brief 获取MMC设备块大小
* @param fd 设备文件描述符
* @return 块大小(字节),0表示失败
* @internal
*/
static uint32_t mmc_get_block_size(int fd)
{
uint32_t block_size = 512;
/* 使用ioctl获取块设备扇区大小 */
if (ioctl(fd, BLKSSZGET, &block_size) < 0) {
/* 默认512字节扇区 */
block_size = 512;
}
return block_size;
}
/**
* @fn fs_mount
* @brief 挂载文件系统
* @return EMMC_OK 成功,其他失败
* @internal
*
* 【设计模式】适配器模式 - 适配不同文件系统类型
*/
static emmc_error_t fs_mount(void)
{
struct stat st;
char partition_path[64];
/* 创建挂载点目录 */
if (stat(g_region_info.mount_point, &st) != 0) {
if (mkdir(g_region_info.mount_point, 0755) != 0) {
fprintf(stderr, "[ERROR] Failed to create mount point: %s\n",
strerror(errno));
return EMMC_ERR_MOUNT;
}
}
/* 构建分区设备路径 /dev/mmcblk0p1 */
snprintf(partition_path, sizeof(partition_path), "%sp%d",
EMMC_DEVICE_PATH, EMMC_PARTITION_NUM);
/* 尝试挂载ext4 */
if (mount(partition_path, g_region_info.mount_point, "ext4",
MS_NOATIME, NULL) == 0) {
return EMMC_OK;
}
/* 尝试挂载vfat/FAT32 */
if (mount(partition_path, g_region_info.mount_point, "vfat",
MS_NOATIME, NULL) == 0) {
return EMMC_OK;
}
fprintf(stderr, "[ERROR] Failed to mount %s: %s\n",
partition_path, strerror(errno));
return EMMC_ERR_MOUNT;
}
/**
* @fn fs_umount
* @brief 卸载文件系统
* @return EMMC_OK 成功,其他失败
* @internal
*/
static emmc_error_t fs_umount(void)
{
if (umount(g_region_info.mount_point) != 0) {
fprintf(stderr, "[ERROR] Failed to umount: %s\n", strerror(errno));
return EMMC_ERR_MOUNT;
}
return EMMC_OK;
}
/**
* @fn fs_file_write
* @brief 向文件写入数据
* @param data 数据缓冲区
* @param size 写入大小
* @param offset 文件内偏移
* @return EMMC_OK 成功,其他失败
* @internal
*
* 【性能分析】pwrite是原子操作,适合多线程场景
* 写入速度取决于eMMC硬件性能,通常20-50MB/s
*/
static emmc_error_t fs_file_write(const uint8_t *data, size_t size, uint64_t offset)
{
int fd;
ssize_t written;
char file_path[128];
snprintf(file_path, sizeof(file_path), "%s/region.bin",
g_region_info.mount_point);
/* 打开文件:读写模式,不存在则创建 */
fd = open(file_path, O_RDWR | O_CREAT, 0644);
if (fd < 0) {
fprintf(stderr, "[ERROR] Failed to open file: %s\n", strerror(errno));
return EMMC_ERR_FILE_OPEN;
}
/* 定位到偏移位置并写入 */
written = pwrite(fd, data, size, offset);
if (written != (ssize_t)size) {
fprintf(stderr, "[ERROR] Write failed: wrote %zd, expected %zu\n",
written, size);
close(fd);
return EMMC_ERR_WRITE;
}
/* 确保数据同步到存储 */
fsync(fd);
close(fd);
return EMMC_OK;
}
/**
* @fn fs_file_read
* @brief 从文件读取数据
* @param buffer 数据缓冲区
* @param size 读取大小
* @param offset 文件内偏移
* @return EMMC_OK 成功,其他失败
* @internal
*
* 【性能分析】预读机制会提高顺序读性能
* 随机读性能取决于eMMC控制器
*/
static emmc_error_t fs_file_read(uint8_t *buffer, size_t size, uint64_t offset)
{
int fd;
ssize_t read_bytes;
char file_path[128];
snprintf(file_path, sizeof(file_path), "%s/region.bin",
g_region_info.mount_point);
fd = open(file_path, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "[ERROR] Failed to open file: %s\n", strerror(errno));
return EMMC_ERR_FILE_OPEN;
}
read_bytes = pread(fd, buffer, size, offset);
if (read_bytes != (ssize_t)size) {
fprintf(stderr, "[ERROR] Read failed: read %zd, expected %zu\n",
read_bytes, size);
close(fd);
return EMMC_ERR_READ;
}
close(fd);
return EMMC_OK;
}
/**
* @fn fs_file_unlink
* @brief 删除区域文件
* @return EMMC_OK 成功,其他失败
* @internal
*
* 【设计模式】命令模式 - 擦除操作封装
*/
static emmc_error_t fs_file_unlink(void)
{
char file_path[128];
snprintf(file_path, sizeof(file_path), "%s/region.bin",
g_region_info.mount_point);
if (unlink(file_path) != 0 && errno != ENOENT) {
fprintf(stderr, "[ERROR] Failed to unlink: %s\n", strerror(errno));
return EMMC_ERR_WRITE;
}
return EMMC_OK;
}
/**
* @fn validate_offset_and_size
* @brief 验证读写偏移和大小是否在4MB区域内
* @param size 操作大小
* @param offset 区域偏移
* @return EMMC_OK 有效,EMMC_ERR_REGION_OVERFLOW 越界
* @internal
*/
static emmc_error_t validate_offset_and_size(size_t size, uint64_t offset)
{
if (size == 0) {
return EMMC_ERR_INVALID_PARAM;
}
if (offset + size > EMMC_REGION_SIZE) {
fprintf(stderr, "[ERROR] Region overflow: offset=%llu, size=%zu, region=%d\n",
(unsigned long long)offset, size, EMMC_REGION_SIZE);
return EMMC_ERR_REGION_OVERFLOW;
}
return EMMC_OK;
}
/* ==================== 公共API实现 ==================== */
/**
* @brief 初始化eMMC管理模块
* @note 单例模式实现,重复调用返回成功
*/
emmc_error_t emmc_init(void)
{
/* 单例模式检查:已初始化则直接返回 */
if (g_initialized) {
return EMMC_OK;
}
int fd = mmc_device_open();
if (fd < 0) {
return EMMC_ERR_DEVICE;
}
/* 获取设备块大小 */
g_region_info.block_size = mmc_get_block_size(fd);
close(fd);
/* 挂载文件系统 */
emmc_error_t ret = fs_mount();
if (ret != EMMC_OK) {
return ret;
}
g_initialized = 1;
printf("[INFO] eMMC module initialized, block size=%u bytes\n",
g_region_info.block_size);
return EMMC_OK;
}
/**
* @brief 反初始化eMMC管理模块
*/
emmc_error_t emmc_deinit(void)
{
if (!g_initialized) {
return EMMC_OK;
}
emmc_error_t ret = fs_umount();
if (ret != EMMC_OK) {
return ret;
}
g_initialized = 0;
printf("[INFO] eMMC module deinitialized\n");
return EMMC_OK;
}
/**
* @brief 向4MB区域写入数据
* @note 策略模式:根据offset和size自动选择最优写入方式
*/
emmc_error_t emmc_write_region(const uint8_t *data, size_t size, uint64_t offset)
{
/* 参数验证 */
if (data == NULL) {
return EMMC_ERR_INVALID_PARAM;
}
emmc_error_t ret = validate_offset_and_size(size, offset);
if (ret != EMMC_OK) {
return ret;
}
if (!g_initialized) {
return EMMC_ERR_INIT;
}
/* 策略选择:大数据块使用直接写入,小数据考虑缓存策略 */
ret = fs_file_write(data, size, offset);
return ret;
}
/**
* @brief 从4MB区域读取数据
* @note 策略模式:根据offset和size选择最优读取方式
*/
emmc_error_t emmc_read_region(uint8_t *buffer, size_t size, uint64_t offset)
{
/* 参数验证 */
if (buffer == NULL) {
return EMMC_ERR_INVALID_PARAM;
}
emmc_error_t ret = validate_offset_and_size(size, offset);
if (ret != EMMC_OK) {
return ret;
}
if (!g_initialized) {
return EMMC_ERR_INIT;
}
ret = fs_file_read(buffer, size, offset);
return ret;
}
/**
* @brief 擦除4MB区域
* @note 命令模式:将擦除操作封装为独立的命令
*/
emmc_error_t emmc_erase_region(void)
{
if (!g_initialized) {
return EMMC_ERR_INIT;
}
/* 命令模式执行:删除文件实现逻辑擦除 */
emmc_error_t ret = fs_file_unlink();
if (ret == EMMC_OK) {
printf("[INFO] Region erased successfully\n");
}
return ret;
}
/**
* @brief 获取4MB区域信息
*/
emmc_error_t emmc_get_region_info(emmc_region_info_t *info)
{
if (info == NULL) {
return EMMC_ERR_INVALID_PARAM;
}
memcpy(info, &g_region_info, sizeof(emmc_region_info_t));
return EMMC_OK;
}
/**
* @brief 验证4MB区域完整性
* @note 计算区域文件CRC32并与期望值比较
*/
emmc_error_t emmc_verify_region(uint32_t expected_checksum)
{
uint8_t *buffer = NULL;
uint32_t calculated_checksum = 0;
emmc_error_t ret;
if (!g_initialized) {
return EMMC_ERR_INIT;
}
/* 分配缓冲区 */
buffer = (uint8_t *)malloc(EMMC_REGION_SIZE);
if (buffer == NULL) {
return EMMC_ERR_READ;
}
/* 读取整个区域 */
ret = emmc_read_region(buffer, EMMC_REGION_SIZE, 0);
if (ret != EMMC_OK) {
free(buffer);
return ret;
}
/* 简单累加校验和(实际应用应使用CRC32) */
for (uint64_t i = 0; i < EMMC_REGION_SIZE; i++) {
calculated_checksum += buffer[i];
}
free(buffer);
if (calculated_checksum == expected_checksum) {
printf("[INFO] Region verification PASSED\n");
return EMMC_OK;
} else {
printf("[ERROR] Region verification FAILED: calc=0x%08X, expected=0x%08X\n",
calculated_checksum, expected_checksum);
return EMMC_ERR_READ;
}
}
6. 使用示例
#include "emmc_manager.h"
#include <stdio.h>
#include <string.h>
int main(void)
{
emmc_error_t ret;
uint8_t write_data[1024];
uint8_t read_data[1024];
/* 1. 初始化模块 */
ret = emmc_init();
if (ret != EMMC_OK) {
printf("Init failed: %d\n", ret);
return -1;
}
/* 2. 准备测试数据 */
memset(write_data, 0xAA, sizeof(write_data));
/* 3. 写入数据到区域偏移0位置 */
ret = emmc_write_region(write_data, sizeof(write_data), 0);
if (ret == EMMC_OK) {
printf("Write success\n");
}
/* 4. 读取数据验证 */
memset(read_data, 0, sizeof(read_data));
ret = emmc_read_region(read_data, sizeof(read_data), 0);
if (ret == EMMC_OK) {
if (memcmp(write_data, read_data, sizeof(write_data)) == 0) {
printf("Read verification PASSED\n");
}
}
/* 5. 擦除区域 */
ret = emmc_erase_region();
/* 6. 反初始化 */
emmc_deinit();
return 0;
}
7. 性能分析
| 操作 | 时间复杂度 | 实际耗时(4MB) | 影响因素 |
|---|---|---|---|
| 顺序写入 | O(n) | ~0.2-0.5秒 | eMMC写入速度20-50MB/s |
| 顺序读取 | O(n) | ~0.1-0.3秒 | eMMC读取速度50-100MB/s |
| 随机读写 | O(n) | 略高于顺序 | eMMC控制器缓存命中率 |
| 擦除操作 | O(1) | <0.1秒 | 文件系统删除操作 |
| 挂载操作 | O(1) | ~0.05秒 | 文件系统类型和日志 |
优化建议:
-
使用
noatime挂载选项减少写入 -
分区起始地址对齐到1MB边界(2048扇区)
-
格式化时设置
stride和stripe-width优化ext4 -
启用写缓存但配合
fsync确保关键数据持久化
8. 总结
| 问题 | 结论 |
|---|---|
| 是否需要自己编写文件系统? | 不需要,Linux/U-Boot已支持ext4/FAT32 |
| 哪些文件系统可用? | ext4、FAT32、SquashFS |
| 如何分区4MB区域? | fdisk/parted创建,起始扇区对齐到2048 |
| 代码设计模式 | 门面模式、单例模式、策略模式、命令模式、适配器模式 |
| 性能预期 | 读50-100MB/s,写20-50MB/s |
更多推荐



所有评论(0)