Хабрахабр

[Из песочницы] Умирающий хард Hitachi, bash и технонекрофилия

Повторение описанных действий может нанести вред оборудованию. Предостережение: решение, описанное в данной статье, не является профессиональным, возможно, создано исходя из неправильного понимания строения и принципов работы жестких дисков.

Недавно наткнулся на статью о использовании старых HDD с бэдблоками и подумал, что мой опыт тоже может быть кому-то интересным.

У ноута, судя по виду, была тяжелая жизнь: трещины корпуса, помяты углы, выломаны стойки. Однажды знакомые попросили помочь разобраться с ноутом, на который они не смогли переустановить Windows. Ну людям, понятное дело, я поставил SSD, а информацию с их винта скопировал в образ командой: Понятно — проблема в повреждении жесткого диска в результате многочисленных ударов, что подтвердил и смарт: более 200 срабатываний G-sensor'а, 500 Reallocated Sector Count и еще есть в Current Pending'е.

dd if=/dev/sdb of=/media/hddimages/ht320.img bs=409600 conv=noerror,notrunc,sync

Параметры "conv=noerror,notrunc,sync" нужны, чтоб в случае ошибок чтения определенных секторов, в выходном файле по этим адресам записывались нули, а данные записывались на свое место без смещения.

Секторы здесь по 4кб, поэтому после первого прохода dd, если были ошибки чтения, я пробую повторно читать эти участки блоками по 4кб: Бывает, что при чтении большими блоками (400кб) диск не читает весь блок, а меньшими — не считывается лишь 1 сектор.

n=<отступ>;dd if=/dev/sdb of=/media/hddimages/ht320.img bs=4096 conv=noerror,notrunc,sync skip=$n seek=$n count=100

Сам отступ берется из вывода первого выполнения dd, только для соответствия размеру блока, число умножаем на 100. Параметры skip и seek нужны, чтоб чтение и запись начинались с одинаковым отступом от начала диска.

Это было интересно и позволяло, подключив хард и введя команду, дней через 10 получить максимально полный образ. Иногда диски при обращении к сбойным секторам надолго зависают, да так, что помогает только переподключение к питанию и лет 5 назад был сделан, громко говоря, программно-аппаратный комплекс (с микроконтроллером даже) для автоматизированного чтения сбойных хардов с автоматическим переподключением питания в случае слишком долгого отсутствия ответа. Но подопытный герой статьи не зависал намертво, поэтому необходимости доставать описанный тяжелый костыль не было.

Если бы диск не монтировался или многие файлы не читались — я бы открыл образ программой R-Studio и восстанавливал бы через нее, но именно с образа. Итак, диск считался, монтирую все разделы образа через losetup с оффсетами начала разделов из fdisk'а, умноженных на размер логического блока в мбр — 512 байт, копирую все данные людям на новый ССД.

Теоретически контроллер диска отмечает сектора как поврежденные и переназначает на их адреса резервные в случае многократных неудачных попыток записи либо невосстановимых (с помощью ECC) ошибок чтения. А вот хард, хоть он и побитый, выкидывать жалко, поэтому я решил как-то его реанимировать.

Через несколько циклов смарт особо меняться перестал, пендинги не релочатся, а подвисания с ошибками встречаются каждый раз в тех же местах или очень рядом. Для начала пробую wipe'нуть диск (dd if=/dev/zero...) и читать после этого: скорость также нестабильна, диск подвисает и иногда выпадает input/output error, но в смарте растет количество релоков и пендингов. Ведь если поврежденный бит, вне зависимости от того, что именно в него пытались записать, с большей вероятностью читается как "1", то при записи в него "1", последующее чтение будет проходить без ошибок, но при записи иного паттерна — может набраться достаточно несоответствий, чтоб не справилось ECC и случилась невосстановимая ошибка чтения и через нескольких таких случаев, сектор получил статус "Bad". Пробую принудительно ремапить вручную командой "hdparm --make-bad-sector", но на данной модели это не работает и мне приходит осознание, что просто стирание-чтение, как и запись-чтение не сможет проявить все проблемные места. Поэтому для максимального выявления всех плохих секторов нужно генерировать относительно случайный паттерн, записывать его на диск, считывать и сравнивать значение. Кстати, записываемое значение может так накладываться на распределение поврежденных битов, что считанное неправильное значение даже удовлетворит ECC. Встречаются также нестабильные сектора, которые меняют свои значения постепенно за некоторое время или после обработки его соседей.

С учетом всего перечисленного, я решил реализовать в bash-скрипте следующую стратегию:

  • генерируем случайный паттерн и считаем для него контрольную сумму;
  • читаем смарт;
  • записываем диск нолями;
  • читаем диск;
  • записываем диск случайным паттерном с чтением только что записанного блока и сравнением его чек-суммы;
  • читаем диск после полной записи, проверяя чек-суммы каждого блока;
  • читаем смарт;
  • self-test;
  • goto 1.

Кстати, как работает self test у данной модели диска, я не представляю; не знаю, чем отличается long от short'а (хотя вероятно лонг работает со всей поверхностью, а шорт — ориентируясь на ранеесобранную статистику, как при форматировании: полное и быстрое). Продолжаем так, пока не перестанут случаться неверно считанные сектора и IO-error'ы или пока винт не накроется окончательно. Я надеюсь, что это подтолкнет винт принять во внимание недавний опыт и ремапить сбойные сектора.

Это сподвигло меня поиграться с размером блока, протестировать разные алгоритмы хэша для контрольных сумм, попробовать прямую верификацию diff'ом, а не сравнивание контрольных сумм, но скорости обработки выше 12 мегабайт в секунду достигнуть я так и не смог. Когда я закончил писать bash-скрипт, запустил его и на следующий день проверил результаты — увидел, что очень медленно работает верификация, при этом загрузка процессора не доходит и до 60% ни на одном ядре. В итоге остановился на сравнении diff'ом блоков по 400кб, а контрольные суммы вычисляю только в случае несовпадения лишь для последующего анализа лога.

Скрипт получился такой:

#!/bin/bash
# формат строки запуска hddtest.sh diskdev logfile [blocksize] diskdev="$1";
test_log="$2"; #"~/work/hdd/test.log"
blsz="$";
n="1";
sizebyte=`fdisk -l "$diskdev"|grep "Disk $diskdev:"|cut -d" " -f5`;
let sizebl=$sizebyte/$blsz; #"781428" for 320GB while true;do echo "starting iteration $n"; dd if=/dev/urandom of=fil bs="$blsz" count=1; md5ok=`md5sum fil|cut -d" " -f1`; cp fil fil_"$n"; echo "random pattern md5sum $md5ok">>"$test_log"; smartctl -A "$diskdev">>"$test_log"; echo "filling disk with zeroes">>"$test_log"; dd if=/dev/zero of="$diskdev" bs="$blsz"; #count="$sizebl"; echo "disk is wiped fully">>"$test_log"; dd of=/dev/null if="$diskdev" bs="$blsz"; # count="$sizebl"; echo "writing disk with fil-pattern">>"$test_log"; i="0"; while [ "$i" -le "$sizebl" ]; do #echo "writing fil: $i ">>"$test_log"; dd if=fil of="$diskdev" bs="$blsz" seek="$i"; dd if=/dev/null of=tst; dd if="$diskdev" bs="$blsz" of=tst skip="$i" count=1 conv=notrunc,noerror,sync; #md5tst=`md5sum tst|cut -d" " -f1`; verf=`diff -s fil tst|sed 's/.* //g'`; if [ "$verf" != "identical" ]; #if [ "$md5ok" != "$md5tst" ]; then md5tst=`md5sum tst|cut -d" " -f1`; echo "$i : md5 $md5tst is not ok">>"$test_log"; cp tst tst_"$n"_"$i"; fi; let i="$i"+1; done; echo "test of full writed with fil-pattern disk">>"$test_log"; i="0"; while [ "$i" -le "$sizebl" ]; do #echo "after writing test: $i">>"$test_log"; dd if=/dev/null of=tst; dd if="$diskdev" bs="$blsz" of=tst skip="$i" count=1 conv=notrunc,noerror,sync; #md5tst=`md5sum tst|cut -d" " -f1`; verf=`diff -s fil tst|sed 's/.* //g'`; if [ "$verf" != "identical" ]; #if [ "$md5ok" != "$md5tst" ]; then md5tst=`md5sum tst|cut -d" " -f1`; echo "$i : md5 $md5tst is not ok">>"$test_log"; cp tst tst_"$n"_"$i"; fi; let i="$i"+1; done; smartctl -A "$diskdev" >>"$test_log"; smartctl -t long "$diskdev">>"$test_log"; sleep 5000; #smartctl -t short "$diskdev">>"$test_log"; #sleep 240; let n="$n"+1;
done

Последние 15 прогонов диск не видел подозрительных (pending) секторов, всё уже было как бы ремаплено, но где-то посредине 13-го Гигабайта периодически неверно считывался один блок или блоки недалеко от него по разным адресам. Как после многократного выполнения скрипта показали логи, все сбойные сектора находились в первых 13 ГБ диска, там было несколько "очагов" поражения (вероятно, при ударе головкой побило-поцарапало поверхность). Так что отловить последние 10 сбойных секторов — была долгая операция. Причем, один блок мог 2 цикла подряд считаться неверно, затем 2 раза верно и снова раз неверно. И в конце меня ждал сюрприз: когда всё уже работало стабильно, после очередного self-test'а параметр Reallocated Sector Count стал "0" и о проблемах напоминал только счетчик событий Reallocated Event Count и записи о последних 5 ошибках (с адресом и временем от начала работы), хранимых в журнале. Всего было ремаплено 1268 секторов!

Я просто отступил немного с запасом и создал раздел, начиная с 15-го Гигабайта. Несмотря на стабильную работу, я все же решил минимизировать взаимодействие с поврежденной областью, чтоб не травмировать головку о возможные неровности в местах с поврежденной поверхностью пластин, да и местным секторам в долгосрочной перспективе доверять не хотелось. И, как показало время, диск чувствует себя довольно хорошо и стабильно работает в носимом ноутбуке уже 10 месяцев.

Хотя полностью доверять восстановленному диску нельзя и экономическая целесообразность затеи сомнительна, но иногда результат — лишь приятное дополнение к хорошему пути.

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»