放置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,確保整張卡都能正常讀寫。

卡沒有問題。全文完。