是否可以在运行时调整 MTD 分区的大小?
Is it possible to resize MTD partitions at runtime?
我有一个非常特殊的需求:
部分替换闪存的内容 和 移动 MTD 分区边界。
当前地图是:
u-boot 0x000000 0x040000
u-boot-env 0x040000 0x010000
kernel 0x050000 0x230000
initrd 0x280000 0x170000
scripts 0x3f0000 0x010000
filesystem 0x400000 0xbf0000
firmware 0xff0000 0x010000
虽然所需的输出是:
u-boot 0x000000 0x040000
u-boot-env 0x040000 0x010000
kernel 0x050000 0x230000
filesystem 0x280000 0xd70000
firmware 0xff0000 0x010000
这意味着将 initrd
、scripts
和 filesystem
合并为一个区域,而让其他区域保持独立。
问题是这应该从 运行 系统(使用 "old" 配置启动)实现 和 我应该重写内核和 "new" 重启前的文件系统。
该系统是嵌入式的,所以我几乎没有 space 可操作的东西(不过我有 SD 卡)。
当然,重写的内核将在其 DTB 中写入 "new" 配置。
问题是过渡。
注意:我看过this Question,但是它很老,而且需要内核补丁的缺点,我想避免。
注意 2:此问题已被标记为删除,因为 "not about programming"。我不敢苟同:我需要在大约 14k 台设备上执行上述操作,其中大部分已经出售给客户,因此任何可行的解决方案至少应该涉及脚本。
注意 3:如果绝对必要,我什至可以考虑(小的)内核修改(是的,我有办法远程更新内核)。
我有三个ideas/suggestions:
- 您可以不移动分区,而是将 "new" 文件系统映像拆分成块并将它们写入相应的 "old" MTD 分区吗?这样你真的不需要改变 MTD 分区图。引导进入新内核后,它将看到新的连续根文件系统。对于 JFFS2 文件系统,使用 split 或 dd、flash_erase 和 nandwrite 应该相当简单。类似于:
# WARNING: this script assumes that it runs from tmpfs and the old root filesystem is already unmounted.
# Also, it assumes that your shell has arithmetic evaluation, which handles hex (my busybox 1.29 ash does this).
# assuming newrootfs.img is the image of new rootfs
new_rootfs_img="newrootfs.img"
mtd_initrd="/dev/mtd3"
mtd_initrd_size=0x170000
mtd_scripts="/dev/mtd4"
mtd_scripts_size=0x010000
mtd_filesystem="/dev/mtd5"
mtd_filesystem_size=0xbf0000
# prepare chunks of new filesystem image
bs="0x1000"
# note: using arithmetic evaluation $(()) to convert from hex and do the math.
# dd doesn't handle hex numbers ("dd: invalid number '0x1000'") -- $(()) works this around
dd if="${new_rootfs_img}" of="rootfs_initrd" bs=$(( bs )) count=$(( mtd_initrd_size / bs ))
dd if="${new_rootfs_img}" of="rootfs_scripts" bs=$(( bs )) count=$(( mtd_scripts_size / bs )) skip=$(( mtd_initrd_size / bs ))
dd if="${new_rootfs_img}" of="rootfs_filesystem" bs=$(( bs )) count=$(( mtd_filesystem_size / bs )) skip=$(( ( mtd_initrd_size + mtd_scripts_size ) / bs ))
# there's no going back after this point
flash_eraseall -j "${mtd_initrd}"
flash_eraseall -j "${mtd_scripts}"
flash_eraseall -j "${mtd_filesystem}"
nandwrite -p "${mtd_initrd}" rootfs_initrd
nandwrite -p "${mtd_scripts}" rootfs_scripts
nandwrite -p "${mtd_filesystem}" rootfs_filesystem
# don't forget to update the kernel too
concatenating MTD devices 有内核支持(这正是您想要做的)。我没有看到使用它的简单方法,但您可以创建一个内核模块,它将您所需的分区连接到一个连续的 MTD 设备中。
为了将 3 个 MTD 分区合并为一个来写入新的文件系统,您可以在 3 个 mtdblocks 上创建一个 dm-linear 映射,然后使用 block2mtd 将其变回 MTD 设备. (即 mtdblock + device mapper linear + block2mtd)但它看起来很尴尬,我不知道它是否能正常工作(例如,OOB 数据)。
EDIT1:添加了解释 $(( bs ))
使用的注释——从十六进制转换为 dd
不直接处理十六进制数字(既不是 coreutils,也不是 busybox dd)。
我将按原样保留已接受的答案,但是,对于碰巧来到这里寻找解决方案的任何人,我想指出:
最近(<4 岁)mtd-utils,再加上 4.0+ 内核支持:
- "master" 设备的定义(MTD 设备代表完整的、未分区的闪存)。这是一个内核选项。
- mtd-utils 有一个特定的
mtd-part
实用程序,可以动态 add/delete MTD 分区。注意:如果(且仅当)上述内容在内核中定义,此实用程序才会运行。
- 使用上述实用程序可以构建多个可能重叠 分区;小心使用!
据我所知,@andrey 的回答建议 1 是错误的。
一个 mtd 分区由一系列块组成,其中任何一个都可能坏或随时坏。这就是简单的 mtd char 抽象存在的原因:一个 mtd char 设备(不是 mtdblock 设备)被顺序读取并跳过坏块。 nandwrite 也顺序写入并跳过坏块。
mtd 字符设备的行为类似于:
- 无法随机访问的单个文件,您只能从头到尾(或您觉得无聊的地方)顺序阅读。
- 您无法随机访问的单个文件,您只能从头开始(或从您之前停止读取的擦除块)一直写入到最后。 (也就是说,你可以 t运行cate 和追加,但你不能写 mid-file。)要写你需要事先擦除从你开始写的地方到分区末尾的所有擦除块。
这意味着分区大小是最大理论容量,但通常容量会因为坏块而减少,每次重写分区都可以有效减少.您可以永远不会 期望写入 mtd 分区的完整大小。
这是@andrey 的建议 1 是错误的:它将要写入的文件分解为 max-sized 个部分,然后再写入每个部分。但是你永远不知道在不实际写入数据的情况下有多少数据可以放入 mtd 分区。
相反,您通常需要编写 一些 数据,并且您祈祷有足够的好块来容纳它。如果在某个时候没有,则写入失败并且设备到达 end-of-life。不用说,您需要的分区比例越大,写入失败的可能性就越大(发生这种情况时,通常意味着设备已完好)。
要真正实现类似于建议 1 的东西,您需要开始写入一个分区(跳过坏块),当您 运行 擦除块时,您继续写入下一个分区,并且很快。关键是:在您实际写入数据并填充每个分区之前,您无法知道数据边界将位于何处;没有别的办法了。
我有一个非常特殊的需求: 部分替换闪存的内容 和 移动 MTD 分区边界。
当前地图是:
u-boot 0x000000 0x040000
u-boot-env 0x040000 0x010000
kernel 0x050000 0x230000
initrd 0x280000 0x170000
scripts 0x3f0000 0x010000
filesystem 0x400000 0xbf0000
firmware 0xff0000 0x010000
虽然所需的输出是:
u-boot 0x000000 0x040000
u-boot-env 0x040000 0x010000
kernel 0x050000 0x230000
filesystem 0x280000 0xd70000
firmware 0xff0000 0x010000
这意味着将 initrd
、scripts
和 filesystem
合并为一个区域,而让其他区域保持独立。
问题是这应该从 运行 系统(使用 "old" 配置启动)实现 和 我应该重写内核和 "new" 重启前的文件系统。
该系统是嵌入式的,所以我几乎没有 space 可操作的东西(不过我有 SD 卡)。
当然,重写的内核将在其 DTB 中写入 "new" 配置。
问题是过渡。
注意:我看过this Question,但是它很老,而且需要内核补丁的缺点,我想避免。
注意 2:此问题已被标记为删除,因为 "not about programming"。我不敢苟同:我需要在大约 14k 台设备上执行上述操作,其中大部分已经出售给客户,因此任何可行的解决方案至少应该涉及脚本。
注意 3:如果绝对必要,我什至可以考虑(小的)内核修改(是的,我有办法远程更新内核)。
我有三个ideas/suggestions:
- 您可以不移动分区,而是将 "new" 文件系统映像拆分成块并将它们写入相应的 "old" MTD 分区吗?这样你真的不需要改变 MTD 分区图。引导进入新内核后,它将看到新的连续根文件系统。对于 JFFS2 文件系统,使用 split 或 dd、flash_erase 和 nandwrite 应该相当简单。类似于:
# WARNING: this script assumes that it runs from tmpfs and the old root filesystem is already unmounted.
# Also, it assumes that your shell has arithmetic evaluation, which handles hex (my busybox 1.29 ash does this).
# assuming newrootfs.img is the image of new rootfs
new_rootfs_img="newrootfs.img"
mtd_initrd="/dev/mtd3"
mtd_initrd_size=0x170000
mtd_scripts="/dev/mtd4"
mtd_scripts_size=0x010000
mtd_filesystem="/dev/mtd5"
mtd_filesystem_size=0xbf0000
# prepare chunks of new filesystem image
bs="0x1000"
# note: using arithmetic evaluation $(()) to convert from hex and do the math.
# dd doesn't handle hex numbers ("dd: invalid number '0x1000'") -- $(()) works this around
dd if="${new_rootfs_img}" of="rootfs_initrd" bs=$(( bs )) count=$(( mtd_initrd_size / bs ))
dd if="${new_rootfs_img}" of="rootfs_scripts" bs=$(( bs )) count=$(( mtd_scripts_size / bs )) skip=$(( mtd_initrd_size / bs ))
dd if="${new_rootfs_img}" of="rootfs_filesystem" bs=$(( bs )) count=$(( mtd_filesystem_size / bs )) skip=$(( ( mtd_initrd_size + mtd_scripts_size ) / bs ))
# there's no going back after this point
flash_eraseall -j "${mtd_initrd}"
flash_eraseall -j "${mtd_scripts}"
flash_eraseall -j "${mtd_filesystem}"
nandwrite -p "${mtd_initrd}" rootfs_initrd
nandwrite -p "${mtd_scripts}" rootfs_scripts
nandwrite -p "${mtd_filesystem}" rootfs_filesystem
# don't forget to update the kernel too
concatenating MTD devices 有内核支持(这正是您想要做的)。我没有看到使用它的简单方法,但您可以创建一个内核模块,它将您所需的分区连接到一个连续的 MTD 设备中。
为了将 3 个 MTD 分区合并为一个来写入新的文件系统,您可以在 3 个 mtdblocks 上创建一个 dm-linear 映射,然后使用 block2mtd 将其变回 MTD 设备. (即 mtdblock + device mapper linear + block2mtd)但它看起来很尴尬,我不知道它是否能正常工作(例如,OOB 数据)。
EDIT1:添加了解释 $(( bs ))
使用的注释——从十六进制转换为 dd
不直接处理十六进制数字(既不是 coreutils,也不是 busybox dd)。
我将按原样保留已接受的答案,但是,对于碰巧来到这里寻找解决方案的任何人,我想指出:
最近(<4 岁)mtd-utils,再加上 4.0+ 内核支持:
- "master" 设备的定义(MTD 设备代表完整的、未分区的闪存)。这是一个内核选项。
- mtd-utils 有一个特定的
mtd-part
实用程序,可以动态 add/delete MTD 分区。注意:如果(且仅当)上述内容在内核中定义,此实用程序才会运行。 - 使用上述实用程序可以构建多个可能重叠 分区;小心使用!
据我所知,@andrey 的回答建议 1 是错误的。
一个 mtd 分区由一系列块组成,其中任何一个都可能坏或随时坏。这就是简单的 mtd char 抽象存在的原因:一个 mtd char 设备(不是 mtdblock 设备)被顺序读取并跳过坏块。 nandwrite 也顺序写入并跳过坏块。
mtd 字符设备的行为类似于:
- 无法随机访问的单个文件,您只能从头到尾(或您觉得无聊的地方)顺序阅读。
- 您无法随机访问的单个文件,您只能从头开始(或从您之前停止读取的擦除块)一直写入到最后。 (也就是说,你可以 t运行cate 和追加,但你不能写 mid-file。)要写你需要事先擦除从你开始写的地方到分区末尾的所有擦除块。
这意味着分区大小是最大理论容量,但通常容量会因为坏块而减少,每次重写分区都可以有效减少.您可以永远不会 期望写入 mtd 分区的完整大小。
这是@andrey 的建议 1 是错误的:它将要写入的文件分解为 max-sized 个部分,然后再写入每个部分。但是你永远不知道在不实际写入数据的情况下有多少数据可以放入 mtd 分区。
相反,您通常需要编写 一些 数据,并且您祈祷有足够的好块来容纳它。如果在某个时候没有,则写入失败并且设备到达 end-of-life。不用说,您需要的分区比例越大,写入失败的可能性就越大(发生这种情况时,通常意味着设备已完好)。
要真正实现类似于建议 1 的东西,您需要开始写入一个分区(跳过坏块),当您 运行 擦除块时,您继续写入下一个分区,并且很快。关键是:在您实际写入数据并填充每个分区之前,您无法知道数据边界将位于何处;没有别的办法了。