放置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,确保整张卡都能正常读写。

卡没有问题。全文完。