放置9年的SD卡会丢失数据吗?
找到一张十多年前的SD卡,不知道里面保存的数据是否完好,先插上看看。
这是什么卡?
$ system_profiler SPCardReaderDataType
Card Reader:
Built in SD Card Reader:
Vendor ID: 0x17a0
Device ID: 0x9755
Subsystem Vendor ID: 0x17a0
Subsystem ID: 0x9755
Revision: 0x0001
Link Width: x1
Link Speed: 5.0 GT/s
SDHC Card (Class 10):
Product Name: SDSL16G
Manufacturer ID: 0x03
Revision: 8.0
Serial Number: 0x2587b27b
Manufacturing Date: 2014-04
Specification Version: 3.0
Capacity: 15.93 GB (15,931,539,456 bytes)
Removable Media: Yes
BSD Name: disk4
Partition Map Type: MBR (Master Boot Record)
S.M.A.R.T. status: Verified
Volumes:
NO NAME:
Capacity: 15.93 GB (15,929,966,592 bytes)
File System: MS-DOS FAT32
BSD Name: disk4s1
Content: Windows_FAT_32
Volume UUID: 60CDB9CD-9285-3884-BF60-8D9D3D90D7C2
生产日期2014-04,大概率是MLC颗粒,最后一次使用是2017年初。12年前生产,又吃了9年的灰之后,数据还完好吗?
先尝试直接在桌面里把东西拷出来。

Finder会失败,需要尝试数据恢复了。
错误的尝试
一开始直接使用了dd,但并不合适,因为dd在遇到错误时的策略不符合我们的要求:
$ diskutil unmount /Volumes/NO\ NAME/ # 先移除disk4上的所有挂载点
Volume NO NAME on disk4s1 unmounted
$ sudo gdd if=/dev/rdisk4 of=card.img bs=1M status=progress conv=noerror,sync
Written: 295 MB 30 MB, 315 MiB) copied, 9 s, 36.6 MB/s
gdd: error reading '/dev/rdisk4': Operation timed out
327+0 records in
327+0 records out
342884352 bytes (343 MB, 327 MiB) copied, 35.5718 s, 9.6 MB/s
Written: 374 MB 97 MB, 379 MiB) copied, 37 s, 10.7 MB/s
gdd: error reading '/dev/rdisk4': Operation timed out
402+1 records in
403+0 records out
422576128 bytes (423 MB, 403 MiB) copied, 63.8949 s, 6.6 MB/s
Written: 582 MB 16 MB, 587 MiB) copied, 102 s, 6.0 MB/s
gdd: error reading '/dev/rdisk4': Operation timed out
608+2 records in
610+0 records out
639631360 bytes (640 MB, 610 MiB) copied, 128.827 s, 5.0 MB/s
664797184 bytes (665 MB, 634 MiB) copied, 130 s, 5.1 MB/s
gdd: error reading '/dev/rdisk4': Operation timed out
631+3 records in
634+0 records out
...(余下略)
此处使用GNU dd。
conv=noerror,sync遇到读取错误时不中断,用零填充该块之后继续。
超时出现了40多次,每个超时的1MB块都会被全部替换成全零的1MB。每个被丢弃的1MB中也许只有512B是真正损坏的,这样的粒度显然让人无法接受。
使用ddrescue
这种情况下应该使用ddrescue,它会尝试使用多种方式读取,尽可能恢复数据:
$ sudo ddrescue -b512 /dev/rdisk4 card_rescue.img card_rescue.mapfile
GNU ddrescue 1.30
Press Ctrl-C to interrupt
ipos: 859242 kB, non-trimmed: 524288 B, current rate: 609 kB/s
opos: 859242 kB, non-scraped: 0 B, average rate: 2768 kB/s
non-tried: 9223 PB, bad-sector: 0 B, error rate: 0 B/s
rescued: 858193 kB, bad areas: 0, run time: 5m 10s
pct rescued: 0.00%, read errors: 8, remaining time: n/a
time since last successful read: 0s
Copying non-tried blocks... Pass 1 (forwards)^C
Interrupted by user
macOS使用
/dev/rdiskX代表访问raw disk,绕过缓冲区。若在Linux上使用,请在ddrescue命令之后添加-d参数来绕过缓冲区。
512字节是SD卡的最小粒度,
-b512让ddrescue按这个单位逐扇区重试,一个扇区读不出来只会丢失512字节,不会波及相邻扇区。
有点问题,9223 PB代表它认为磁盘无限大,手动指定上限:
$ diskutil info /dev/disk4 # 先找到卡容量的精确值
...
Disk Size: 15.9 GB (15931539456 Bytes) (exactly 31116288 512-Byte-Units)
...
$ sudo ddrescue -b512 -s 15931539456 /dev/rdisk4 card_rescue.img card_rescue.mapfile
GNU ddrescue 1.30
Press Ctrl-C to interrupt
Initial status (read from mapfile)
(sizes limited to domain from 0 B to 15_558_144 KiB of 9_223_372_036_854_775_807 B)
rescued: 1125 MB, tried: 786432 B, bad-sector: 0 B, bad areas: 0
Current status
ipos: 15931 MB, non-trimmed: 2752 kB, current rate: 36700 kB/s
opos: 15931 MB, non-scraped: 0 B, average rate: 9239 kB/s
non-tried: 2752 kB, bad-sector: 0 B, error rate: 0 B/s
rescued: 15926 MB, bad areas: 0, run time: 26m 39s
pct rescued: 99.96%, read errors: 30, remaining time: 1s
time since last successful read: 0s
Copying non-tried blocks... Pass 1 (forwards)
ipos: 343277 kB, non-trimmed: 2752 kB, current rate: 80659 B/s
opos: 343277 kB, non-scraped: 0 B, average rate: 9142 kB/s
non-tried: 0 B, bad-sector: 0 B, error rate: 0 B/s
rescued: 15928 MB, bad areas: 0, run time: 26m 59s
pct rescued: 99.98%, read errors: 30, remaining time: 1s
time since last successful read: 0s
Copying non-tried blocks... Pass 2 (backwards)
ipos: 1983 MB, non-trimmed: 0 B, current rate: 2969 B/s
opos: 1983 MB, non-scraped: 33792 B, average rate: 4393 kB/s
non-tried: 0 B, bad-sector: 38912 B, error rate: 25 B/s
rescued: 15931 MB, bad areas: 72, run time: 56m 10s
pct rescued: 99.99%, read errors: 106, remaining time: 12s
time since last successful read: 0s
Trimming failed blocks... (forwards)
ipos: 1983 MB, non-trimmed: 0 B, current rate: 0 B/s
opos: 1983 MB, non-scraped: 0 B, average rate: 3442 kB/s
non-tried: 0 B, bad-sector: 66560 B, error rate: 0 B/s
rescued: 15931 MB, bad areas: 48, run time: 1h 11m 41s
pct rescued: 99.99%, read errors: 160, remaining time: 0s
time since last successful read: 1m 45s
Scraping failed blocks... (forwards)
Finished
ddrescue允许Ctrl-C打断,下一次重启时会按照mapfile中的进度继续。
ddrescue一轮结束,坏块分散在48个不同位置,一共丢失正好65KB数据。这张卡实际只使用了2GB空间,而这48个坏块全部分布在这2GB有效数据上,一个可能的解释是,这张卡从一开始就只写过这么一次,剩余的容量从来没有被写入过,未写入的区域仍处于闪存的擦除态(全0xFF),浮栅中没有电荷可以泄漏,因此不会出现数据衰退。
算一下损坏率:65 KB / 2 GB = 3e-5,大约丢失0.003%。
我不确定这个数字是否符合MLC的预期,但说实话,我觉得这个数字没有什么很大的意义,丢失了就是丢失了,丢失了和没丢失有本质的区别。
探索极限
有一些块处于临界状态,多重复几次有概率能读出。
继续读其实没有什么很明显的意义,对这张卡而言,每多读一次都在read disturb,临界状态的块会越来越不稳定,但我仍然想探索它的极限。
使用-r参数来指定重试次数,例如-r3重试三次:
sudo ddrescue -r3 -b512 -s 15931539456 /dev/rdisk4 card_rescue.img card_rescue.mapfile
GNU ddrescue 1.30
Press Ctrl-C to interrupt
Initial status (read from mapfile)
(sizes limited to domain from 0 B to 15_558_144 KiB of 9_223_372_036_854_775_807 B)
rescued: 15931 MB, tried: 66560 B, bad-sector: 66560 B, bad areas: 48
Current status
ipos: 1983 MB, non-trimmed: 0 B, current rate: 0 B/s
opos: 1983 MB, non-scraped: 0 B, average rate: 3 B/s
non-tried: 0 B, bad-sector: 59392 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 44, run time: 31m 21s
pct rescued: 99.99%, read errors: 116, remaining time: 1h 50m
time since last successful read: 2m 5s
Retrying bad sectors... Retry 1 (forwards)
ipos: 343216 kB, non-trimmed: 0 B, current rate: 0 B/s
opos: 343216 kB, non-scraped: 0 B, average rate: 3 B/s
non-tried: 0 B, bad-sector: 53248 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 35, run time: 58m 47s
pct rescued: 99.99%, read errors: 220, remaining time: 1h 8m
time since last successful read: 3m 35s
Retrying bad sectors... Retry 2 (backwards)
ipos: 1983 MB, non-trimmed: 0 B, current rate: 0 B/s
opos: 1983 MB, non-scraped: 0 B, average rate: 3 B/s
non-tried: 0 B, bad-sector: 50688 B, error rate: 0 B/s
rescued: 15931 MB, bad areas: 31, run time: 1h 24m 28s
pct rescued: 99.99%, read errors: 319, remaining time: 7h 2m
time since last successful read: 14m 5s
Retrying bad sectors... Retry 3 (forwards)
Finished
损坏数据减少到了约50KB,那么,如果我们再继续下去呢?能否收敛到一个“极限”?
当然,这么做并不会让奇迹发生,我就是想测出一个实验结果,以供后人参考。
来,再重复20次,接下来这段log会有点长:
$ sudo ddrescue -r20 -b512 -s 15931539456 /dev/rdisk4 card_rescue.img card_rescue.mapfile
GNU ddrescue 1.30
Press Ctrl-C to interrupt
Initial status (read from mapfile)
(sizes limited to domain from 0 B to 15_558_144 KiB of 9_223_372_036_854_775_807 B)
rescued: 15931 MB, tried: 50688 B, bad-sector: 50688 B, bad areas: 31
Current status
ipos: 1983 MB, non-trimmed: 0 B, current rate: 0 B/s
opos: 1983 MB, non-scraped: 0 B, average rate: 1 B/s
non-tried: 0 B, bad-sector: 48128 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 29, run time: 24m 14s
pct rescued: 99.99%, read errors: 94, remaining time: 4h 27m
time since last successful read: 10m 30s
Retrying bad sectors... Retry 1 (forwards)
ipos: 640186 kB, non-trimmed: 0 B, current rate: 0 B/s
opos: 640186 kB, non-scraped: 0 B, average rate: 1 B/s
non-tried: 0 B, bad-sector: 46080 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 26, run time: 47m 20s
pct rescued: 99.99%, read errors: 184, remaining time: 2h 33m
time since last successful read: 3m 5s
Retrying bad sectors... Retry 2 (backwards)
ipos: 1983 MB, non-trimmed: 0 B, current rate: 0 B/s
opos: 1983 MB, non-scraped: 0 B, average rate: 1 B/s
non-tried: 0 B, bad-sector: 45056 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 25, run time: 1h 9m 41s
pct rescued: 99.99%, read errors: 272, remaining time: 12h 30m
time since last successful read: 4m 35s
Retrying bad sectors... Retry 3 (forwards)
ipos: 640186 kB, non-trimmed: 0 B, current rate: 0 B/s
opos: 640186 kB, non-scraped: 0 B, average rate: 1 B/s
non-tried: 0 B, bad-sector: 44544 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 25, run time: 1h 31m 32s
pct rescued: 99.99%, read errors: 359, remaining time: n/a
time since last successful read: 8m
Retrying bad sectors... Retry 4 (backwards)
ipos: 1983 MB, non-trimmed: 0 B, current rate: 0 B/s
opos: 1983 MB, non-scraped: 0 B, average rate: 0 B/s
non-tried: 0 B, bad-sector: 44032 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 26, run time: 1h 53m 8s
pct rescued: 99.99%, read errors: 445, remaining time: n/a
time since last successful read: 5m 31s
Retrying bad sectors... Retry 5 (forwards)
ipos: 640186 kB, non-trimmed: 0 B, current rate: 0 B/s
opos: 640186 kB, non-scraped: 0 B, average rate: 0 B/s
non-tried: 0 B, bad-sector: 43520 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 25, run time: 2h 14m 34s
pct rescued: 99.99%, read errors: 530, remaining time: 12h 5m
time since last successful read: 8m 21s
Retrying bad sectors... Retry 6 (backwards)
ipos: 1983 MB, non-trimmed: 0 B, current rate: 0 B/s
opos: 1983 MB, non-scraped: 0 B, average rate: 0 B/s
non-tried: 0 B, bad-sector: 43520 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 25, run time: 2h 35m 49s
pct rescued: 99.99%, read errors: 615, remaining time: n/a
time since last successful read: 29m 36s
Retrying bad sectors... Retry 7 (forwards)
ipos: 640186 kB, non-trimmed: 0 B, current rate: 0 B/s
opos: 640186 kB, non-scraped: 0 B, average rate: 0 B/s
non-tried: 0 B, bad-sector: 43008 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 26, run time: 2h 57m 5s
pct rescued: 99.99%, read errors: 699, remaining time: n/a
time since last successful read: 15m 6s
Retrying bad sectors... Retry 8 (backwards)
ipos: 1983 MB, non-trimmed: 0 B, current rate: 0 B/s
opos: 1983 MB, non-scraped: 0 B, average rate: 0 B/s
non-tried: 0 B, bad-sector: 43008 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 26, run time: 3h 18m 6s
pct rescued: 99.99%, read errors: 783, remaining time: n/a
time since last successful read: 36m 7s
Retrying bad sectors... Retry 9 (forwards)
ipos: 640186 kB, non-trimmed: 0 B, current rate: 0 B/s
opos: 640186 kB, non-scraped: 0 B, average rate: 0 B/s
non-tried: 0 B, bad-sector: 43008 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 26, run time: 3h 39m 7s
pct rescued: 99.99%, read errors: 867, remaining time: n/a
time since last successful read: 57m 8s
Retrying bad sectors... Retry 10 (backwards)
ipos: 1983 MB, non-trimmed: 0 B, current rate: 0 B/s
opos: 1983 MB, non-scraped: 0 B, average rate: 0 B/s
non-tried: 0 B, bad-sector: 42496 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 25, run time: 4h 2s
pct rescued: 99.99%, read errors: 950, remaining time: 11h 48m
time since last successful read: 3m 5s
Retrying bad sectors... Retry 11 (forwards)
ipos: 640186 kB, non-trimmed: 0 B, current rate: 0 B/s
opos: 640186 kB, non-scraped: 0 B, average rate: 0 B/s
non-tried: 0 B, bad-sector: 41984 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 25, run time: 4h 20m 38s
pct rescued: 99.99%, read errors: 1032, remaining time: n/a
time since last successful read: 17m 20s
Retrying bad sectors... Retry 12 (backwards)
ipos: 1983 MB, non-trimmed: 0 B, current rate: 0 B/s
opos: 1983 MB, non-scraped: 0 B, average rate: 0 B/s
non-tried: 0 B, bad-sector: 41472 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 25, run time: 4h 41m 9s
pct rescued: 99.99%, read errors: 1113, remaining time: n/a
time since last successful read: 15m 6s
Retrying bad sectors... Retry 13 (forwards)
ipos: 640186 kB, non-trimmed: 0 B, current rate: 0 B/s
opos: 640186 kB, non-scraped: 0 B, average rate: 0 B/s
non-tried: 0 B, bad-sector: 41472 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 25, run time: 5h 1m 25s
pct rescued: 99.99%, read errors: 1194, remaining time: n/a
time since last successful read: 35m 22s
Retrying bad sectors... Retry 14 (backwards)
ipos: 1983 MB, non-trimmed: 0 B, current rate: 0 B/s
opos: 1983 MB, non-scraped: 0 B, average rate: 0 B/s
non-tried: 0 B, bad-sector: 40960 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 25, run time: 5h 21m 41s
pct rescued: 99.99%, read errors: 1274, remaining time: n/a
time since last successful read: 12m 21s
Retrying bad sectors... Retry 15 (forwards)
ipos: 640186 kB, non-trimmed: 0 B, current rate: 0 B/s
opos: 640186 kB, non-scraped: 0 B, average rate: 0 B/s
non-tried: 0 B, bad-sector: 40960 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 25, run time: 5h 41m 42s
pct rescued: 99.99%, read errors: 1354, remaining time: n/a
time since last successful read: 32m 22s
Retrying bad sectors... Retry 16 (backwards)
ipos: 1983 MB, non-trimmed: 0 B, current rate: 0 B/s
opos: 1983 MB, non-scraped: 0 B, average rate: 0 B/s
non-tried: 0 B, bad-sector: 40448 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 25, run time: 6h 1m 37s
pct rescued: 99.99%, read errors: 1433, remaining time: 11h 14m
time since last successful read: 12m 20s
Retrying bad sectors... Retry 17 (forwards)
ipos: 640186 kB, non-trimmed: 0 B, current rate: 0 B/s
opos: 640186 kB, non-scraped: 0 B, average rate: 0 B/s
non-tried: 0 B, bad-sector: 39936 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 26, run time: 6h 21m 13s
pct rescued: 99.99%, read errors: 1511, remaining time: n/a
time since last successful read: 15m 46s
Retrying bad sectors... Retry 18 (backwards)
ipos: 1983 MB, non-trimmed: 0 B, current rate: 0 B/s
opos: 1983 MB, non-scraped: 0 B, average rate: 0 B/s
non-tried: 0 B, bad-sector: 39936 B, error rate: 34 B/s
rescued: 15931 MB, bad areas: 26, run time: 6h 40m 44s
pct rescued: 99.99%, read errors: 1589, remaining time: n/a
time since last successful read: 35m 17s
Retrying bad sectors... Retry 19 (forwards)
ipos: 640186 kB, non-trimmed: 0 B, current rate: 0 B/s
opos: 640186 kB, non-scraped: 0 B, average rate: 0 B/s
non-tried: 0 B, bad-sector: 39424 B, error rate: 0 B/s
rescued: 15931 MB, bad areas: 25, run time: 7h 5s
pct rescued: 99.99%, read errors: 1666, remaining time: n/a
time since last successful read: 16m 1s
Retrying bad sectors... Retry 20 (backwards)
Finished
经过7小时的锤炼,损坏数据量从50KB降低到了39KB,可喜可贺(大概吧)。但是,直到最后一回合都没有出现我期望中的收敛,在最后一回合bad-sector又从39936 B减到了39424 B,不过再继续下去也没有什么意思了,我已经在这里浪费了7小时的人生。
记录下这些实验数据,让我们格式化,重新出发吧,这张卡还能继续发光发热。
之前读不出来的数据一般只是漏电导致无法辨认数据,而非物理坏块,格式化之后这些块还是可用的,但以防万一,在重新投入使用之前还是先过一遍f3,确保整张卡都能正常读写。

卡没有问题。全文完。