Jak bezpiecznie korzystać z uszkodzonej pamięci RAM bez BadRAM.

BadRAM był fajny, ale jest nieutrzymywany. Ostatnią działająca u mnie wersja była do kernela 2.6.25.x, późniejsze, choć istniały (np. dla 2.6.29; dead link), to nie udało się ich – wbrew wcześniejszej radości – zmusić do poprawnego działania – nadal pojawiały się błędy np. na liczeniu sum kontrolnych.

Winny w tej maszynie jest ewidentnie RAM, co zostało już dawno stwierdzone, ale maszyna na tyle niekrytyczna, że inwestować się nie opłaca (poza tym, szkoda środowiska), a ze starszym (tj. 2.6.25.x) kernelem spokojnie i poprawnie działa. Poza tym, przecież to Linux, więc da się poprawić. I jaki uroczy temat do notek jest. 😉 Z okazji świątecznej wizyty w domu, postanowiłem jednak zerknąć, czy nie pojawiły się patche BadRAM do jakichś nowszych kerneli (serii 2.6.3x, znaczy).

Nie pojawiły się, ale zamiast tego, trafiłem na pierwszej stronie wyników na sposób radzenia sobie z uszkodzoną pamięcią pod Ubuntu, który w ogóle z BadRAM nie korzysta. Chwilę później trafiłem na ten wpis (dead link). Okazuje się, że za pomocą parametrów, które można przekazać kernelowi, w szczególności mem=XX oraz memmap=X$YY, można wyłączyć obszary pamięci z użytkowania, co przekłada się w praktyce, na możliwość bezpiecznego korzystania z uszkodzonej i dotychczas powodującej błędy pamięci. Więcej o parametrach w kernelowym Documentation/kernel-parameters.txt, ale na potrzeby tego zagadnienia wystarczą te dwa.

Pierwszy parametr (mem=) ogranicza wykorzystaną pamięć. Jeśli uszkodzenie jest w okolicy 312 MB (memtest+ prawdę powie), to mem=310M co prawda obniży dostępną pamięć do 310 MB, za to system będzie działał bez problemów. Tyle tylko, że stracimy 200 MB pamięci. Trochę sporo, zwłaszcza, jeśli całość do dyspozycji to tylko 512 MB.

Drugi (memmap=) jest ciekawszy, bo rezerwuje X pamięci od adresu YY. Przykładowo memmap=10M$305M oznaczy pamięć od  305 MB do 315 MB jako wykorzystaną. Czyli stracimy raptem 10 MB, a zyskamy niezawodny system. Tyle teorii. W praktyce na dystrybucyjnym 2.6.26 z Lenny’ego, mem=300M działało poprawnie (najprościej sprawdzić przez free -m), natomiast memmap=10M$305M był radośnie olewany – nadal pokazywało dostępną całą pamięć.

Przyczyny tego stanu rzeczy nie udało mi się ustalić (podejrzewam limit 4GB zamiast 1GB, błąd w kernelu lub korzystanie z initrd – jeśli ktoś zna odpowiedź, to proszę o info), natomiast skompilowanie własnego 2.6.32.2 na podstawie konfiga od 2.6.25.x (z którego spatchowanego BadRAM korzystałem do tej pory) rozwiązało problem – memmap=2M$311M, czyli wyłączenie tylko 2 MB spowodowało, że system działa poprawnie.

Ponieważ najłatwiej zaobserwować błędy było dotychczas na sumach kontrolnych, to testowanie wykonałem prostym skryptem (brzydki bash napędzany perlem – pewnie dałoby się prosto przespisać na gołego basha, ale kto tam ma czas…; skrypt na końcu wpisu). Stosunkowo duży plik (większy, niż dostępna pamięć RAM, mój tworzony przez dd if=/dev/urandom of=random.dat bs=1MB count=1024), z losową zawartością (tworzony raz, bo czasochłonne), liczenie sum kontrolnych. Jeśli błąd pojawi się w buforze dyskowym, to przy braku wielkiego pecha suma kontrolna będzie się różnić przed i po skopiowaniu. Zapuszczone w pętli, z logowaniem do pliku – nawet przy uszkodzonej pamięci nie wystarczy 1 przebieg – błąd nie pojawia się za każdym razem. Natomiast choćby jeden błąd oznacza, że coś jest nie tak jak być powinno.

Podstawą jest jednak free -m. Jeśli on nie widzi mniej pamięci, to można nie zaczynać nawet ze skryptem.

Jeśli po dłuższym teście brak błędów (pojedynczy błąd oznacza, że nie jest dobrze), to wystarczy dopisać linię do konfigu gruba, by przy każdej aktualizacji kernela dodawał do parametrów określony argument:

#kopt=root=/dev/hda2 ro memmap=2M$311M

Dzięki temu możemy korzystać z dowolnej (najnowszej!) wersji jądra, bez upierdliwego patchowania (cóż, patche badram były dość kijowe, włączenie z tym, że zdarzało im się mieć literówki uniemożliwiające kompilację).

Na koniec wspomniany skrypcik:

#!/usr/bin/perl
$src="/random.dat";
$dst="/tmp/memtest_tmp.dat";
$log="memtest_copy.log";
if (-f $dst){
   system (" rm $dst ");
}
system (" date >> $log ");
while (1){
  system (" cp $src $dst ");
  $res = `md5sum $dst`;
  $res2 = `sha1sum $dst`;
  $res_ = `md5sum $src`;
  $res2_ = `sha1sum $src`;
  $check = "ERROR";
   if (($res == $res_) && ($res2 == $res2_)){
     $check="OK";
   }
system (" echo \"$check $res $res_ $res2 $res2_ \" >> $log ");
system ("rm $dst");
}

Podsumowując: żegnaj BadRAM!