Czy powinienem zawracać sobie głowę wykrywaniem błędów OOM (out of memory) w moim kodzie C?

Poświęciłem dużą liczbę linii kodu C na cleanup-labels/conditionals dla nieudanej alokacji pamięci (wskazanej przez rodzinę alloc zwracającą NULL). Nauczono mnie, że jest to dobra praktyka, więc w przypadku awarii pamięci można oznaczyć odpowiedni status błędu i wywołujący może potencjalnie wykonać "wdzięczne czyszczenie pamięci" i spróbować ponownie.Mam teraz pewne wątpliwości co do tej filozofii, którą mam nadzieję wyjaśnić.

Myślę, że jest to możliwe że wywołujący może dealokować nadmierną przestrzeń bufora lub usuwać obiekty relacyjne ze swoich danych, ale uważam, że wywołujący rzadko ma możliwość (lub jest na odpowiednim poziomie abstrakcji), aby to zrobić. Również, wczesne powrót z wywołanej funkcji bez skutków ubocznych jest często nietrywialne.

Właśnie odkryłem linuksowego zabójcę OOM, który sprawia, że te wysiłki są całkowicie bezcelowe na mojej podstawowej platformie programistycznej.

Domyślnie Linux podąża za optymistyczna strategia alokacji pamięci. Oznacza to, że gdy malloc () zwraca non-NULL nie ma gwarancji, że pamięć naprawdę jest dostępna. To naprawdę zły robak. Na wszelki wypadek okazuje się, że system jest z pamięci, jeden lub więcej procesów będzie zostać zabitym przez niesławnego OOM zabójca.

Domyślam się, że prawdopodobnie istnieją inne platformy, które kierują się tą samą zasadą. czy jest coś pragmatycznego, co sprawia, że sprawdzanie Warunki OOM warte zachodu?

Author: cdleary, 2009-04-18

11 answers

Warunki braku pamięci mogą się zdarzyć nawet na nowoczesnych komputerach z dużą ilością pamięci, jeśli użytkownik lub administrator systemu ogranicza (patrz ulimit) przestrzeń pamięci dla procesu lub system operacyjny obsługuje limity alokacji pamięci na użytkownika. W przypadkach patologicznych fragmentacja sprawia, że jest to dość prawdopodobne, nawet.

Jednakże, ponieważ w nowoczesnych programach użycie dynamicznie przydzielanej pamięci jest powszechne, z dobrych powodów, bardzo trudno jest poradzić sobie z błędami poza pamięcią. Sprawdzanie i obsługa tego rodzaju błędów musiałaby odbywać się wszędzie, przy wysokich kosztach złożoności.

Uważam, że lepiej jest zaprojektować program tak, aby mógł się zawiesić w dowolnym momencie. Na przykład, upewnij się, że dane utworzone przez użytkownika są zapisywane na dysku przez cały czas, nawet jeśli użytkownik nie zapisuje ich jawnie. (Patrz na przykład vi-R.) W ten sposób można utworzyć funkcję przydzielania pamięci, która kończy program w przypadku wystąpienia błędu. Ponieważ aplikacja jest przeznaczona do obsługi awarii w w każdej chwili możesz się rozbić. Użytkownik będzie zaskoczony, ale nie straci (dużo) pracy.

Nigdy nie zawodząca funkcja alokacji może być czymś takim (niesprawdzony, nieskompilowany Kod, tylko do celów demonstracyjnych):

/* Callback function so application can do some emergency saving if it wants to. */
static void (*safe_malloc_callback)(int error_number, size_t requested);

void safe_malloc_set_callback(void (*callback)(int, size_t))
{
    safe_malloc_callback = callback;
}

void *safe_malloc(size_t n)
{
    void *p;

    if (n == 0)
        n = 1; /* malloc(0) is not well defined. */
    p = malloc(n);
    if (p == NULL) {
        if (safe_malloc_callback)
            safe_malloc_callback(errno, n);
        exit(EXIT_FAILURE);
    }
    return p;
}

Artykuł Valerie Auroryoprogramowanie tylko do awarii może być pouczające.

 19
Author: Outlaw Programmer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-18 15:52:23

Spójrz na drugą stronę Pytania: jeśli malloc pamięci, to zawiedzie, i NIE wykryć go w malloc, kiedy wykryć go?

Oczywiście, gdy próbujesz zdereferować wskaźnik.

Jak to wykrycie? Przez uzyskanie Bus error lub czegoś podobnego, gdzieś po malloc, które będziesz musiał wyśledzić z zrzutu pamięci i debuggera.

Z drugiej strony możesz napisać

  #define OOM 42 /* just some number */

  /* ... */

  if((ptr=malloc(size))==NULL){
      /* a well-behaved fprintf should NOT malloc, so it can be used
       * in this sort of context
       */
      fprintf(stderr,"OOM at %s: %s\n", __FILE__, __LINE__);
      exit(OOM);
   }

I get " OOM at parser.c: 447".

Wybierz.

Update

Dobre pytanie o Pełen wdzięku powrót. Trudność z zapewnieniem pełnego wdzięku powrotu polega na tym, że w ogóle nie można skonfigurować paradygmatu lub wzoru tego, jak to robisz, szczególnie w C, który jest przecież fantazyjnym językiem asemblacji. W środowisku śmieci, można wymusić GC; w języku z wyjątkami, można rzucić wyjątek i odprężyć rzeczy. W C musisz to zrobić sam i dlatego musisz zdecydować ile wysiłku chcesz włożyć w to.

W większości programów, W tym schemacie (mam nadzieję) otrzymasz przydatną wiadomość na stderr - oczywiście może to być również do loggera lub coś w tym stylu-i znaną wartość jako kod powrotu.

Programy o wysokiej niezawodności z krótkim czasem odzyskiwania pchają cię w coś w rodzaju bloki odzyskiwania, gdzie piszesz kod, który próbuje przywrócić system do przetrwania stan. Są świetne, ale skomplikowane; artykuł, który połączyłem, mówi o nich szczegółowo.

W środku możesz wymyślić bardziej skomplikowany schemat zarządzania pamięcią, powiedzmy zarządzanie własną pulą pamięci dynamicznej -- w końcu, jeśli ktoś inny może napisać malloc, to Ty też możesz.

Ale po prostu nie ma ogólnego wzorca (którego i tak jestem świadomy) na sprzątanie wystarczająco dużo, Aby móc powrócić niezawodnie i pozwolić otaczającemu programowi kontynuować.

 13
Author: Charlie Martin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-18 15:07:01

Niezależnie od platformy (może poza systemami wbudowanymi) dobrze jest sprawdzić, czy nie ma NULL, a następnie po prostu wyjść bez ręcznego czyszczenia.

Brak pamięci nie jest prostym błędem. To katastrofa na dzisiejszych systemach.

KsiążkaThe Practice of Programming (Brian W. Kernighan i Rob Pike, 1999) definiuje funkcje takie jak emalloc(), które po prostu wychodzą z Komunikatem o błędzie, jeśli nie ma już pamięci.

 8
Author: stesch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-18 09:05:11

To zależy od tego, co piszesz. Czy to biblioteka ogólnego przeznaczenia? Jeśli tak, chcesz poradzić sobie z brakiem pamięci tak wdzięcznie, jak to możliwe, szczególnie jeśli można oczekiwać, że będzie ona używana na systemach el-cheapo lub urządzeniach wbudowanych.

Rozważ to: programista korzysta z twojej Biblioteki. W jego programie pojawia się błąd (być może zmienna niezainicjowana), który przekazuje głupi argument do kodu, który w konsekwencji próbuje przydzielić pojedynczy blok pamięci o pojemności 3,6 GB. Oczywiście malloc() zwraca NULL. Czy wolałby niewyjaśniony segfault wygenerowany gdzieś w kodzie biblioteki, czy wartość zwracaną, aby wskazać błąd?

Aby uniknąć sprawdzania błędów w całym kodzie, jedną z metod jest przydzielanie rozsądnej ilości pamięci na początku i przydzielanie jej zgodnie z wymaganiami.

Jeśli chodzi o zabójcę Oom Linuksa, słyszałem, że to zachowanie jest teraz domyślnie wyłączone na głównych dystrybucjach. Nawet jeśli jest włączona, nie zrozum mnie źle: malloc() Może zwrócić NULL, i na pewno będzie, jeśli całkowite zużycie pamięci programu przekroczy 4GiB (w systemie 32-bitowym). Innymi słowy, nawet jeśli malloc() nie zabezpieczy Ci przestrzeni pamięci RAM/swap, to zarezerwuje część twojej przestrzeni adresowej.

 6
Author: Artelius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-18 10:09:58

Proponuję eksperyment-napisać mały program, który utrzymuje alokację pamięci bez jej zwalniania, a następnie wyświetli mały (stały) komunikat, gdy alokacja nie powiedzie się. Jakie efekty zauważysz na swoim systemie podczas uruchamiania tego programu? Czy wiadomość zostanie wydrukowana?

Jeśli system zachowuje się normalnie i pozostaje responsywny do momentu wyświetlenia błędu, to powiedziałbym, że tak, warto to sprawdzić. OTOH, jeśli system staje się powolny, nie reaguje i zdarzenia nie nadaje się do użytku przed wyświetlaniem wiadomości (Jeśli w ogóle jest), wtedy powiedziałbym Nie, Nie warto tego sprawdzać.

Ważne: przed uruchomieniem tego testu zapisz wszystkie ważne prace. Nie uruchamiaj go na serwerze produkcyjnym.

Regardfing zachowania Oom Linuksa - jest to właściwie pożądane i jest to sposób, w jaki działa większość OSs. Ważne jest, aby zdać sobie sprawę, że gdy malloc () jakąś pamięć nie dostaje się bezpośrednio z systemu operacyjnego, to dostaje się ją z biblioteki C runtime. To zazwyczaj poprosi SYSTEM OPERACYJNY o dużą część pamięci z przodu (lub na pierwsze żądanie), którą następnie zarządza za pośrednictwem interfejsu malloc/free. Ponieważ wiele programów w ogóle nie używa pamięci dynamicznej, nie byłoby pożądane, aby system operacyjny przekazywał" rzeczywistą " pamięć do środowiska wykonawczego C - zamiast tego przekazuje som euncomited vM, które będą faktycznie dostarczane podczas wykonywania połączeń malloc.

 3
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-18 09:58:41

Przy dzisiejszych komputerach i zazwyczaj zainstalowanej ilości pamięci RAM, sprawdzanie wszędzie pod kątem błędów alokacji pamięci jest prawdopodobnie zbyt szczegółowe. Jak już zauważyłeś, często trudno lub niemożliwe jest podjęcie racjonalnej decyzji co do dealokacji. Ponieważ proces alokuje coraz więcej pamięci, system operacyjny odpowiednio zmniejszy ilość pamięci dostępnej dla buforów dyskowych. Gdy spadnie poniżej pewnego progu, system operacyjny rozpocznie przywoływanie pamięci na dysk. (Jest to uproszczenie, ponieważ istnieje wiele czynników w zarządzaniu pamięcią.)

Gdy system operacyjny zacznie stronicować pamięć, cały system staje się coraz wolniejszy i wolniejszy, i prawdopodobnie minie sporo czasu, zanim Twoja aplikacja kiedykolwiek zobaczy NULL z malloc (jeśli w ogóle).

Przy dużej ilości pamięci dostępnej w dzisiejszych systemach, błąd "out of memory" najprawdopodobniej oznacza, że błąd w kodzie próbował przydzielić dowolną ilość pamięci. W takim przypadku nie ma kwoty uwolnienie i ponowna próba ze strony procesu rozwiąże problem.

 2
Author: Greg Hewgill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-18 08:59:49

Musisz zważyć, co jest dla ciebie lepsze lub gorsze: Wkładanie całej pracy w sprawdzenie OOM lub niepowodzenie programu w nieoczekiwanym czasie

 1
Author: PiedPiper,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-18 08:55:57

Procesy są zwykle uruchamiane z limitem zasobów (zobacz ulimit (3)) Na rozmiarze stosu, ale nie na rozmiarze stosu. malloc (3) zarządza wzrostem pamięci obszaru sterty strona po stronie z systemu operacyjnego, a system operacyjny zorganizuje, aby ta strona została fizycznie przydzielona i odpowiadała Twojej stercie dla procesu. Jeśli w komputerze nie ma więcej pamięci RAM, większość systemów operacyjnych ma coś w rodzaju partycji wymiany na dysku. Kiedy Twój system zacznie trzeba użyć swap, a następnie rzeczy stopniowo powoli. Jeśli jeden proces prowadzi do tego, można go łatwo zidentyfikować za pomocą jakiegoś narzędzia, takiego jak ps (1).

Chyba, że Twój kod ma działać z limitem zasobów lub na systemie o słabym rozmiarze pamięci i Bez Swapu, myślę, że można programować przy założeniu, że malloc (3) powiedzie się. Jeśli nie jesteś pewien, po prostu zrób manekin, który może kiedyś zrobić kontrolę i po prostu wyjdź. Wartość zwracana stanu błędu nie ma sensu, ponieważ twój program wymaga pamięć, którą już przeznaczył. Jeśli malloc (3) zawiedzie i nie sprawdzisz, czy nie ma NULL, proces i tak umrze, gdy zacznie uzyskiwać dostęp do wskaźnika (NULL), który otrzymał.

Problemy z malloc (3) w większości przypadków nie wynikają z braku pamięci, ale z błędu logicznego w twoim programie, który prowadzi do błędnych wywołań do malloc i free. Ten zwykły problem nie zostanie wykryty przez sprawdzenie sukcesu malloc.

 1
Author: hept,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-18 11:46:58

Cóż. Wszystko zależy od sytuacji.

Po pierwsze. Jeśli wykryłeś, że pamięć nie jest wystarczająca do Twoich potrzeb - co zrobisz? Najczęściej używane to:
if (ptr == NULL) {
    fprintf(log /* stderr or anything */, "Cannot allocate memory");
    exit(2);
}
Cóż. Nawet jeśli nie używa malloc, może przydzielić bufory. Dodatkowo szkoda, jeśli jest to aplikacja GUI - twój użytkownik raczej go nie zauważy. Jeśli twój użytkownik jest "wystarczająco inteligentny", aby uruchomić aplikację z konsoli, aby sprawdzić błędy, prawdopodobnie zobaczy, że coś zjadło całą jego pamięć. Ok. Więc może być wyświetlany dialog? Ale wyświetlanie okna dialogowego może zjadać zasoby - i zwykle tak będzie.

Po Drugie-po co Ci informacje o OOM? Występuje w dwóch przypadkach:

  1. inne oprogramowanie jest wadliwe. Nie możesz nic z tym zrobić
  2. twój program jest wadliwy. W takim przypadku jest to program graficzny, w którym jest mało prawdopodobne, aby powiadomić użytkownika w jakikolwiek sposób (nie wspominając o tym, że 99% użytkowników nie czyta wiadomości i powie, że oprogramowanie uległo awarii bez dalszych szczegółów). Jeśli nie jest użytkownik prawdopodobnie i tak go zauważy (monitoruje system Ops lub używa bardziej specjalistycznego oprogramowania).
  3. aby uwolnić niektóre pamięci podręczne itp. Należy sprawdzić w systemie jednak ostrzega, że prawdopodobnie nie będzie działać. Możesz obsługiwać tylko własne sbrk / mmap / etc. połączenia i w Linuksie i tak dostaniesz OOM
 1
Author: Maciej Piechotka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-18 20:01:57

Tak, wierzę, że jest, jeśli konsekwentnie przestrzegać praktyki. Może to być niepraktyczne w przypadku dużego programu napisanego w języku C ze względu na stopień pracy fizycznej, który może tego wymagać, ale w bardziej nowoczesnym języku większość tej pracy jest wykonywana za Ciebie, ponieważ stan braku pamięci powoduje wyrzucenie wyjątku.

Korzyści płynące z tego konsekwentnego działania są takie, że program nie wejdzie w nieokreślony stan z powodu stanu braku pamięci, co spowoduje przekroczenie bufora (to oczywiście pozostawia możliwość nieokreślonego stanu z powodu wcześniejszego wyjścia z funkcji, chociaż jest to inna klasa błędu). Po dokonaniu tego, twój program może konsekwentnie obsługiwać warunek błędu, lub jeśli awaria była krytyczna, zdecydować się zamknąć w sposób pełen wdzięku.

 0
Author: 1800 INFORMATION,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-18 08:57:18

Sprawdzenie warunków OOM i podjęcie odpowiednich działań może być trudne, jeśli źle zaprojektujesz oprogramowanie. To, czy rzeczywiście trzeba sprawdzić w takich sytuacjach, zależy od niezawodności oprogramowania, które chcesz uzyskać.

Hypervisor VirtualBox wykryje błędy poza pamięcią i z wdziękiem wstrzyma maszynę wirtualną, umożliwiając użytkownikowi zamknięcie niektórych aplikacji na wolnej pamięci. Obserwowałem takie zachowanie pod oknami. Właściwie prawie wszystkie wywołania w VirtualBox mają wskaźnik sukcesu jako Zwraca wartość i możesz po prostu zwrócić VERR_NO_MEMORY, aby zaznaczyć, że alokacja pamięci nie powiodła się. Wprowadza to kilka dodatkowych kontroli, ale w tym przypadku warto.
 0
Author: dragonfly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-18 09:32:53