4、MTD分区框架:MTD子系统介绍、mtdparts命令详解、分区描述符格式

说到嵌入式Linux的存储分区,MTD(Memory Technology Device)是个绕不开的话题。我最早接触MTD是在做一款工业级路由器的时候,当时被Flash分区搞得焦头烂额——U-Boot传参、内核解析、文件系统挂载,每个环节都可能出问题。后来把MTD这套机制吃透了,才算是真正掌握了分区管理的主动权。

4.1 MTD子系统:存储设备的抽象层

MTD子系统,说白了就是Linux内核给Flash类存储设备搭的一个统一框架。它把NOR Flash、NAND Flash、SPI Flash这些底层硬件差异给屏蔽掉了,向上提供一套标准化的接口。

为什么要搞这么一层?你想想看,NOR Flash可以按字节访问,NAND Flash只能按页读写,SPI Flash又是个串行协议。如果没有MTD,驱动工程师得为每种Flash写一套完全不同的分区管理代码——那画面太美我不敢看。

MTD子系统的核心架构分三层:

  • 底层驱动层:直接操作硬件,实现擦除、读写、坏块管理等基础操作
  • MTD核心层:提供设备注册、分区管理、字符/块设备接口
  • 上层用户层:通过/dev/mtdX字符设备或/dev/mtdblockX块设备访问

我在项目中遇到过一个问题:某款NAND Flash的坏块管理在MTD层和文件系统层都做了,结果导致坏块标记重复,数据越写越乱。后来统一在MTD层处理坏块,上层只管读写,问题就解决了。

关键点:MTD分区是逻辑上的划分,不是物理上的切割。同一个物理Flash芯片,可以划分成多个MTD分区,每个分区独立管理。

4.2 mtdparts命令:U-Boot里的分区利器

在U-Boot环境下,mtdparts命令是我们操作MTD分区的主要工具。我记得第一次用这个命令时,被它的参数格式搞得晕头转向——又是mtd-id又是size@offset,稍不注意就写错了。

mtdparts命令的核心功能就三个:

  • 查看分区表:mtdparts(不带参数)显示当前所有MTD设备的分区信息
  • 设置分区表:mtdparts default恢复默认分区,mtdparts add添加新分区
  • 保存分区表:通过saveenv将分区配置保存到环境变量中

实际使用中,我一般这样操作:

# 查看当前分区
U-Boot> mtdparts

# 设置分区(假设Flash总大小为16MB)
U-Boot> setenv mtdparts 'mtdparts=nand0:1M(u-boot),4M(kernel),8M(rootfs),3M(data)'
U-Boot> saveenv
U-Boot> mtdparts

这里有个坑——mtdparts环境变量的格式必须和内核的MTD_PARTITIONS配置严格对应。我曾经因为分区名里多了一个空格,导致内核解析失败,整块板子起不来。嗯,从那以后我写分区字符串都格外小心。

注意:mtdparts命令依赖于U-Boot的CONFIG_CMD_MTDPARTS配置。如果编译时没打开这个选项,命令会提示"Unknown command"。

4.3 分区描述符格式:写对才能用对

分区描述符的格式,说白了就是告诉系统"哪块区域归谁用"。标准格式如下:

mtdparts=<mtd-id>:<size>[@<offset>](<name>)[<ro>]

各字段含义:

字段 说明 示例
mtd-id MTD设备标识,如nand0、spi0.0 nand0
size 分区大小,支持K/M/G后缀,-表示剩余全部 4M、1G、-
offset 起始偏移,可省略(自动接续上一个分区末尾) @0x100000
name 分区名称,用于标识 kernel
ro 只读标志,可选 ro

举个完整的例子:

mtdparts=nand0:1M(u-boot)ro,4M(kernel),8M(rootfs),3M(data)

这个配置的意思是:

  • nand0设备上,第一个分区1MB,只读,放U-Boot
  • 第二个分区4MB,放内核
  • 第三个分区8MB,放根文件系统
  • 第四个分区3MB,放用户数据

注意,这里没有指定偏移量,分区会自动从0地址开始连续排列。如果你需要跳过某些区域(比如坏块保留区),可以手动指定偏移:

mtdparts=nand0:1M(u-boot)ro,@0x2000004M(kernel)

我个人习惯在分区名里加上版本号或用途标记,比如kernel_v2、rootfs_ro,这样在调试时一眼就能看出哪个分区在起作用。

小技巧:如果Flash总大小不确定,可以用-表示"占满剩余空间"。比如mtdparts=nand0:1M(u-boot),-(data),这样data分区会自动吃掉所有剩余空间。

4.4 知识体系总览

下面这张图是我自己整理的MTD分区框架核心逻辑,从U-Boot到内核再到用户空间,整个链路一目了然:

MTD分区框架核心逻辑 U-Boot 环境 mtdparts 命令 / 环境变量配置 Linux 内核 MTD 子系统 MTD核心层 → 分区解析 → 设备注册 /dev/mtdX (字符) / /dev/mtdblockX (块) 用户空间应用 文件系统挂载 (JFFS2/UBIFS/YAFFS2) Flash操作工具 (flash_erase/nandwrite) 数据流向 → 关键点 • 分区是逻辑划分 • 支持动态重分区 • 坏块管理在MTD层 • 分区名必须唯一 • 大小对齐擦除块 • 只读分区防误写 • 偏移量可省略 • 支持多MTD设备 💡 分区描述符示例:mtdparts=nand0:1M(u-boot)ro,4M(kernel),8M(rootfs),3M(data) U-Boot通过mtdparts环境变量传递分区信息 → 内核MTD子系统解析 → 用户空间通过设备节点访问

从这张图可以看得很清楚:U-Boot负责定义分区,内核负责解析和注册,用户空间负责使用。三个环节环环相扣,任何一个地方出错都会导致分区不可用。

最后说一句,MTD分区表的设计一定要提前规划好。我见过太多项目因为分区大小预留不足,后期要升级内核却发现空间不够,只能重新烧写整个Flash——那叫一个痛苦。所以,宁可多留点余量,也别抠抠搜搜的。

可可精品资料库