
1. 从“android dd”这个标题说起它到底在指什么第一次看到“android dd”这个标题很多人会愣一下——它太短了短到几乎没有上下文。但恰恰是这种极简的命名方式在移动端开发和调试圈子里反而很常见。我个人的判断是这里的“dd”大概率指向两类东西一类是命令行工具dd另一类是调试桥debug bridge相关的操作习惯。考虑到标题前缀是 android最合理的解读是在 Android 设备或 Android 开发环境中围绕dd这个底层数据搬运工具展开的一整套操作实践。dd是什么用一句大白话说它是一个“按块搬运数据”的工具。你可以把它想象成搬家时用的那种标准纸箱不管里面装的是书、衣服还是锅碗瓢盆箱子本身不关心内容它只负责把东西从 A 点整箱搬到 B 点。dd也是这样它不解析文件系统不关心数据是镜像、分区还是普通文件只按照你给的块大小bs和数量count老老实实地读写。这个特性让它在 Android 场景下特别有用——备份分区、制作镜像、写入引导数据、做存储性能粗测都离不开它。这个内容适合谁看如果你只是日常用手机装装应用那基本用不上。但如果你做过 Android 设备的系统级调试、刷机包制作、分区备份恢复或者需要在没有图形界面的环境下搬运大块数据那dd就是绕不开的基本功。它不需要你懂 Java 或 Kotlin但需要你对 Linux 命令行、块设备概念、权限模型有基本认知。下面我会从“为什么是 dd”“怎么用才不出事”“出了事怎么救”三个层面把这套东西讲透。需要提前说明的是本文涉及的设备操作均基于公开的调试接口和通用命令行工具所有案例使用虚构代称不指向任何具体产品或机构。文中提到的路径、分区名在不同设备上会有差异实操时请以自己设备的实际情况为准。2. dd 在 Android 场景下的真实定位它不是万能的但缺了它很麻烦2.1 为什么 Android 调试绕不开块级数据搬运Android 系统本质上是跑在 Linux 内核上的它的存储结构沿用了 Linux 的那一套块设备、分区表、文件系统层层叠加。当你需要备份一个分区、提取一个镜像、或者往某个裸设备写入数据时文件级的复制工具比如cp就不够用了因为它会经过文件系统层遇到权限、挂载状态、特殊文件等一堆限制。而dd直接操作块设备节点绕开了文件系统的解释属于“从底层动手”。举个实际场景某开发者需要把一个设备的boot分区完整备份出来做分析。用文件管理器复制根本看不到这个分区。用cp /dev/block/xxx在部分环境下会因为设备节点被占用而失败。这时候dd if/dev/block/xxx of/sdcard/boot.img就是最直接的路子。它不关心这个分区里是什么文件系统也不关心有没有挂载只要你有读权限它就能把原始字节流搬出来。再比如制作一个可烧录的镜像文件需要把多个分区的数据按偏移拼接。dd的seek参数可以精确控制写入位置配合convnotrunc就能在不破坏已有数据的前提下做局部覆盖。这种“按偏移精确落笔”的能力是普通复制工具给不了的。2.2 dd 的能力边界它不负责“理解”只负责“搬运”很多人对dd有个误解以为它能“修复”分区或者“转换”格式。实际上它什么都不理解。你给它一个损坏的镜像它照样原样搬过去你给它一个加密的分区它搬出来的还是加密数据。它的价值在于保真而不是智能。这个特性决定了它的适用面凡是需要“原样搬运”的场合它都合适凡是需要“解析内容”的场合它都不合适。比如你想从镜像里提取某个文件那应该用挂载或解包工具而不是dd。但如果你想确认某个分区的原始数据有没有被篡改dd配合校验和工具就是标准做法。我在实际使用中总结了一条判断标准如果操作对象是“设备节点”或“原始镜像”优先考虑 dd如果操作对象是“挂载后的目录”优先考虑常规文件工具。这条标准帮我避免了很多次“用错工具白折腾”的情况。2.3 和常见替代方案的对比什么时候该选 dd方案适用场景相对 dd 的劣势cp普通文件复制无法直接操作块设备节点受文件系统限制cat重定向简单镜像拼接无法控制块大小和偏移大文件效率低专用备份工具整机备份依赖工具链灵活性差不适合单分区操作dd块级搬运、分区备份、偏移写入参数写错后果严重无二次确认从表里能看出来dd的优势是灵活和底层代价是没有容错。它不会问你“确定要覆盖吗”参数一敲回车就执行。这也是为什么后面我要花大量篇幅讲参数校验和风险控制——这个工具的威力全在参数里风险也全在参数里。3. 参数拆解每一个字母背后都是一次取舍3.1 if 与 of方向搞反是最低级的错误if是 input fileof是 output file。这两个参数看起来简单但我在社区里见过太多把两者写反的案例。写反的后果是什么如果if指向一个普通文件、of指向一个设备节点那等于把设备数据覆盖到文件上文件废了但设备还在如果反过来of指向设备节点、if指向一个空文件或错误文件那就可能把设备的关键区域写坏。我的习惯是先写 of再写 if。因为 of 是“要动的地方”先把它想清楚、写下来再去确认数据来源。这个顺序强迫我先考虑“我要往哪里写”而不是“我有什么数据”。另外执行前我一定会用ls -l看一眼 of 指向的节点确认它的类型和大小。块设备节点和普通文件在ls -l的输出里第一列字符不同块设备是b开头这个细节能救命。提示在正式执行前把完整命令先 echo 出来看一遍确认 if 和 of 没有颠倒再复制执行。多花五秒钟少一次事故。3.2 bs 与 count块大小选错效率差十倍bs是 block sizecount是块数量。最终搬运的字节数等于bs × count。这两个参数直接决定了操作的效率和精度。块大小怎么选太小了系统调用次数多速度慢太大了内存占用高而且在某些设备上会触发对齐问题。我的经验值是普通文件搬运用 1M 到 4M块设备操作用 512 字节到 4K 的整数倍。为什么块设备要小一些因为很多存储设备的物理扇区是 512 字节或 4K用非对齐的块大小会导致读改写放大反而更慢。count的使用要特别小心。如果你只想复制前 100M 数据bs1M count100是安全的。但如果你不写countdd会一直读到文件末尾或设备末尾。对于块设备来说“末尾”可能意味着几个 G 甚至几十个 G 的数据一旦 of 指向的是另一个设备这就是一次全量覆盖。所以我的原则是只要 of 是设备节点count 必须显式写出哪怕你确实想全量复制也写一个足够大的值让自己心里有数。3.3 seek 与 skip偏移操作里的“从哪读”和“往哪写”skip控制输入端的跳过量seek控制输出端的跳过量单位都是bs指定的块大小。这两个参数是做“局部写入”的关键。假设你要把一段数据写到某个设备的第 2048 个扇区开始的位置扇区大小 512 字节那可以这样写dd ifpayload.bin of/dev/block/sdX bs512 seek2048 convnotrunc这里的convnotrunc非常关键。默认情况下dd在写入时会先把输出文件截断到写入长度对于普通文件这没问题但对于设备节点或需要保留后续数据的镜像截断等于把后面的内容全抹了。加上notrunc就是告诉dd只覆盖我指定的这段别动其他区域。skip和seek容易混淆记法是skip 是“跳过输入的开头”seek 是“定位到输出的中间”。一个管读一个管写方向不同。3.4 状态与进度别让 dd 闷头干活dd默认是不输出进度的一个大操作跑起来终端就卡在那里你不知道它是在干活还是卡死了。statusprogress这个参数会周期性打印已搬运的字节数和速度非常实用。如果环境不支持这个参数老版本 coreutils可以用kill -USR1 pid给进程发信号它会在下一次输出时打印一次进度。另外convfsync值得单独提一句。dd写完数据后数据可能还在系统的页缓存里没有真正落到存储介质上。如果这时候断电或者拔设备数据就丢了。加上fsync会强制在结束前把缓存刷到设备虽然会慢一点但对于备份和烧录场景这个等待是值得的。4. 典型场景实操从备份到写入的完整链路4.1 分区备份把 boot 分区完整取出来这是最常见的用法。前提是设备已经通过调试接口连上并且当前 shell 有读取目标分区的权限。步骤分三步确认分区节点、执行 dd、校验结果。第一步确认分区节点。不同设备的分区命名不一样常见的有/dev/block/by-name/boot这种符号链接形式也有/dev/block/mmcblk0pXX这种直接编号形式。用ls -l /dev/block/by-name/可以看到符号链接指向的真实节点。这一步不能省因为符号链接可能指向你意想不到的地方。第二步执行备份dd if/dev/block/by-name/boot of/sdcard/boot_backup.img bs4M statusprogress这里 of 指向/sdcard下的文件是因为这个路径通常有写权限而且方便后续取回。bs 用 4M 是折中值兼顾速度和内存。执行完会看到类似33554432 bytes (34 MB) copied的输出。第三步校验。最直接的办法是比对源和目标的校验和md5sum /dev/block/by-name/boot /sdcard/boot_backup.img两个值一致才算备份成功。我见过有人备份完不校验结果发现 of 路径写错、文件是空的等到需要恢复时才发现那就晚了。4.2 镜像写入把数据精确落到指定偏移这个场景常见于制作启动介质或恢复出厂镜像。假设你有一个payload.img需要写到设备的userdata分区起始位置并且保留分区后续内容dd ifpayload.img of/dev/block/by-name/userdata bs1M convnotrunc,fsync statusprogress注意这里没有写count因为payload.img的大小就是写入量dd读到文件末尾自然停止。convnotrunc保证不截断分区fsync保证落盘。如果 payload 比分区大dd会在写满分区后报错停止这时候分区已经被写坏了一部分所以写入前一定要确认 payload 大小不超过目标分区容量。用blockdev --getsize64 /dev/block/by-name/userdata可以查分区字节数和ls -l payload.img对比一下这个检查花不了几秒钟。4.3 存储性能粗测用 dd 看读写速度dd也能做简单的吞吐测试。写测试dd if/dev/zero of/sdcard/testfile bs1M count512 convfsync读测试先清缓存再读否则读的是内存dd if/sdcard/testfile of/dev/null bs1M这里要提醒一句dd测出来的数字只能作为粗略参考因为它受文件系统缓存、块大小、设备调度策略影响很大。同样的设备bs 从 1M 改成 4K数字可能差好几倍。所以这个结果适合做“同一设备不同时期”的纵向对比不适合做“不同设备之间”的横向排名。测完记得删掉测试文件512M 的垃圾文件留在存储里不合适。5. 踩过的坑那些参数写错之后的真实后果5.1 把 of 写成 if一次差点清空分区的经历早些年我在一台测试设备上做分区备份命令敲到一半接了个电话回来接着敲结果把if和of写反了。原本是dd if/dev/block/by-name/boot of/sdcard/boot.img写成了dd if/sdcard/boot.img of/dev/block/by-name/boot。万幸的是那个boot.img文件当时还不存在dd直接报错“No such file or directory”什么都没写进去。但如果那个文件存在且内容不对boot 分区就被覆盖了设备直接起不来。从那以后我养成了两个习惯一是命令分两段敲先敲dd of...确认目标再补if...二是执行前一定ls -l看一眼 of 指向的节点类型。这两个习惯后来帮我拦住了好几次类似的失误。5.2 bs 用了非对齐值速度反而变慢有一次为了“精确控制”我把 bs 设成了 1000 字节想着这样 count 好算。结果同样的数据量耗时比 bs4K 多了将近三倍。原因在于存储设备的物理扇区是 512 字节或 4K1000 字节的块会导致跨扇区读写每次都要读改写。后来我固定用 512 的整数倍速度就正常了。这个坑的教训是块大小要顺着硬件的脾气来不要顺着人脑的方便来。人觉得 1000 好算硬件觉得 1024 才顺。5.3 忘了 convnotrunc分区尾部数据被抹掉这个坑比较隐蔽。有一次往一个镜像文件的中间位置写数据没加notrunc结果dd写完指定长度后把文件截断了后面原本存在的数据全没了。因为那个镜像是拼接出来的尾部还有别的分区数据一截断整个镜像就废了。dd的默认行为是“写多少就留多少”对于需要保留后续内容的场景notrunc不是可选项是必选项。判断标准很简单只要 of 指向的东西在你写入范围之外还有需要保留的数据就必须加 notrunc。5.4 权限不足导致的“假成功”有些环境下dd执行完会打印“copied”但实际写入的字节数是 0 或者远小于预期。这种情况多半是权限问题——对 of 指向的节点没有写权限dd打开失败但错误信息被淹没在输出里。我的做法是执行后立刻用ls -l看目标文件大小和预期值对比。如果对不上先查权限再查路径别急着下结论说“操作成功了”。6. 安全操作习惯让 dd 从“危险工具”变成“可控工具”6.1 三查一备执行前的固定动作我把执行前的检查固化成“三查一备”查 of 节点类型、查 if 文件存在且大小合理、查 bs 和 count 的乘积是否符合预期最后备份 of 指向的原始数据如果它是有价值的数据。这四步走完再执行命令。“一备”经常被忽略。有人觉得“我就是要覆盖它备什么份”。但实际操作中参数写错、路径搞混的情况太常见了有一份原始数据在手至少还有退路。备份不一定要全量关键区域备份就行但一定要有。6.2 用 echo 做干跑不执行也能验证dd没有--dry-run参数但可以用echo模拟。把完整命令前面加个echo终端会把命令原样打印出来你肉眼检查一遍 if、of、bs、count、seek 有没有问题。这个动作看起来傻但比出事之后后悔强。更进一步可以用dd if... of/dev/null做只读验证确认 if 能正常读取、数据量符合预期再换成真正的 of 执行。多一步稳很多。6.3 进度监控与中断处理大操作跑起来之后别干等着。用statusprogress看进度或者另开一个终端用ps找到 dd 进程kill -USR1让它打印一次状态。如果发现进度异常比如速度突然掉到 0或者已传输量远超预期立刻CtrlC中断。中断之后不要马上重试。先检查 of 指向的目标当前状态文件大小是多少、校验和是否变化、设备是否还能正常识别。确认影响范围之后再决定下一步。盲目重试可能让问题叠加。6.4 恢复场景下的顺序意识如果是恢复数据顺序很重要先确认目标分区容量足够、再确认源镜像完整、最后才执行写入。写入完成后立刻校验校验通过再重启设备。这个顺序不能乱因为重启之后如果发现数据不对现场已经变了排查难度会大很多。7. 几个容易被忽略的细节和延伸思路7.1 块设备节点可能“看起来存在但不可用”ls能看到节点不代表能读写。有些设备在特定状态下会把分区节点保留但拒绝访问这时候dd会报“Permission denied”或“Input/output error”。遇到这种情况先确认设备当前的工作模式再确认节点对应的分区是否处于可访问状态。不要一上来就怀疑命令写错了。7.2 大文件操作时的内存与缓存影响dd的 bs 设得很大比如 64M时会占用较多内存做缓冲区。在内存紧张的设备上这可能导致其他进程被挤压甚至触发 OOM。我的经验是 bs 不超过 16M大多数场景 4M 足够。另外写完之后的fsync在大数据量下可能耗时较长这是正常的不要以为卡死了就中断。7.3 把 dd 和其他工具组合起来用dd本身只做搬运但配合其他工具能解决更复杂的问题。比如配合gzip做压缩备份dd if/dev/block/xxx bs4M | gzip backup.img.gz配合sha256sum做完整性校验配合split把大镜像切成小块便于传输。这些组合的灵活性正是dd作为“管道工具”的价值所在。7.4 关于“android dd”这个标题的再思考回到标题本身。“android dd”这四个字本质上描述的是一种工作方式在 Android 这个平台上用最底层的块级工具去处理数据。它不花哨不依赖图形界面甚至有点“原始”但正是这种原始让它可靠、可预测、可复现。我见过太多封装得很漂亮的工具在关键时刻掉链子而dd只要参数对它永远按你说的做。如果你刚开始接触这类操作我的建议是先在无关紧要的文件上练手把 if、of、bs、count、seek、notrunc 这几个参数的手感练出来再去碰设备节点。工具本身不危险危险的是对参数后果没有概念。把每一次执行都当成一次不可逆操作来对待这个习惯比任何技巧都重要。最后分享一个我自己的小规矩凡是 of 指向/dev/开头的路径命令敲完之后强制等十秒再回车。这十秒用来干什么用来把命令从头到尾再读一遍。十秒换一次确认这笔账怎么算都划算。