Widziałem kilka postów w sieci ludzi najwyraźniej narzekających na hostowany VPS nieoczekiwanie zabijający procesy, ponieważ zużyli zbyt dużo pamięci RAM.
Jak to jest możliwe? Myślałem, że wszystkie współczesne systemy operacyjne zapewniają „nieskończoną pamięć RAM”, używając po prostu wymiany dysku na wszystko, co przechodzi przez fizyczną pamięć RAM. Czy to jest poprawne?
Co może się stać, jeśli proces zostanie „zabity z powodu małej pamięci RAM”?
Odpowiedzi:
Czasami mówi się, że linux domyślnie nigdy nie odrzuca próśb o więcej pamięci z kodu aplikacji - np
malloc()
. 1 To nie jest prawdą; domyślnie używa heurystykiFrom
[linux_src]/Documentation/vm/overcommit-accounting
(wszystkie cytaty pochodzą z drzewa 3.11). Dokładnie to, co liczy się jako „poważnie dziki przydział”, nie jest wyraźnie określone, więc musielibyśmy przejrzeć źródło, aby ustalić szczegóły. Możemy również użyć metody eksperymentalnej w przypisie 2 (poniżej), aby spróbować uzyskać odbicie heurystyki - na tej podstawie moja początkowa obserwacja empiryczna jest taka, że w idealnych okolicznościach (== system jest bezczynny), jeśli nie „ Jeśli masz zamianę, będziesz mógł przeznaczyć około połowy pamięci RAM, a jeśli masz zamianę, dostaniesz około połowy pamięci RAM plus całą swoją wymianę. To mniej więcej na proces (należy jednak pamiętać, że limit ten jest dynamiczny i może ulec zmianie ze względu na stan, zob. Uwagi w przypisie 5).Połowa pamięci RAM plus swap jest wyraźnie domyślną wartością dla pola „CommitLimit” w
/proc/meminfo
. Oto, co to znaczy - i zauważ, że tak naprawdę nie ma to nic wspólnego z omówionym limitem (z[src]/Documentation/filesystems/proc.txt
):Cytowany poprzednio dokument księgowania nadmiarowego stwierdza, że wartością domyślną
vm.overcommit_ratio
jest 50. Więc jeśli możeszsysctl vm.overcommit_memory=2
, możesz dostosować vm.covercommit_ratio (withsysctl
) i zobaczyć konsekwencje. 3 Tryb domyślny, gdyCommitLimit
nie jest wymuszony i tylko „oczywiste nadmierne polecenia przestrzeni adresowej są odrzucane”, to kiedyvm.overcommit_memory=0
.Chociaż strategia domyślna ma heurystyczny limit na proces zapobiegający „poważnie dzikiej alokacji”, pozostawia system jako całość wolną od poważnie dzikiej alokacji. 4 Oznacza to, że w pewnym momencie może zabraknąć pamięci i musi ogłosić bankructwo w niektórych procesach za pośrednictwem zabójcy OOM .
Co zabija zabójca OOM? Niekoniecznie proces, który poprosił o pamięć, gdy jej nie było, ponieważ niekoniecznie jest to naprawdę winny proces, a co ważniejsze, niekoniecznie ten, który najszybciej usunie system z problemu, w którym się znajduje.
Jest to cytowane stąd, które prawdopodobnie przytacza źródło 2.6.x:
Co wydaje się być porządnym uzasadnieniem. Jednak bez uzyskania wiedzy sądowej numer 5 (który jest zbędny z numeru 1) wydaje się być trudny pod względem implementacji sprzedaży, a numer 3 jest zbędny z numeru 2. Dlatego warto rozważyć sprowadzenie tego do # 2/3 i # 4.
Przeszukałem najnowsze źródło (3.11) i zauważyłem, że ten komentarz zmienił się w międzyczasie:
Jest to nieco bardziej wyraźnie na temat nr 2: „Celem jest [zabicie] zadania zajmującego najwięcej pamięci, aby uniknąć późniejszych awarii oom”, a przez domniemanie nr 4 ( „chcemy zabić minimalną liczbę procesów ( jeden ) ) .
Jeśli chcesz zobaczyć zabójcę OOM w akcji, zobacz przypis 5.
1 Złudzenie, którego Gilles na szczęście mnie pozbył, zobacz komentarze.
2 Oto prosty fragment C, który prosi o coraz większe fragmenty pamięci, aby określić, kiedy żądanie o więcej nie powiedzie się:
Jeśli nie znasz C, możesz to skompilować
gcc virtlimitcheck.c -o virtlimitcheck
, a następnie uruchomić./virtlimitcheck
. Jest całkowicie nieszkodliwy, ponieważ proces nie zajmuje miejsca, o które prosi - tzn. Tak naprawdę nigdy nie wykorzystuje pamięci RAM.W systemie 3.11 x86_64 z systemem 4 GB i 6 GB wymiany nie udało mi się przy ~ 7400000 kB; liczba się zmienia, więc być może stan jest czynnikiem. To przypadkowo blisko
CommitLimit
IN/proc/meminfo
, ale poprzez modyfikację tegovm.overcommit_ratio
nie robi żadnej różnicy. W 32-bitowym systemie ARM 3.6.11 z 448 MB i 64 MB wymiany, jednak nie udaje mi się przy ~ 230 MB. Jest to interesujące, ponieważ w pierwszym przypadku ilość ta jest prawie dwa razy większa niż ilość pamięci RAM, podczas gdy w drugim przypadku jest to około 1/4, co - silnie implikuje ilość wymiany. Zostało to potwierdzone przez wyłączenie swapu w pierwszym systemie, gdy próg awarii spadł do ~ 1,95 GB, co jest bardzo podobnym współczynnikiem do małego pudełka ARM.Ale czy to naprawdę na proces? To wydaje się być. Krótki program poniżej prosi o zdefiniowaną przez użytkownika część pamięci, a jeśli się powiedzie, czeka na trafienie return - w ten sposób możesz wypróbować wiele instancji jednocześnie:
Uważaj jednak, że nie chodzi wyłącznie o ilość pamięci RAM i wymiany bez względu na użycie - uwagi na temat skutków stanu systemu znajdują się w przypisie 5.
3
CommitLimit
odnosi się do ilości przestrzeni adresowej dozwolonej dla systemu, gdy vm.overcommit_memory = 2. Przypuszczalnie wtedy ilość, którą można przeznaczyć, powinna być równa minus to, co już zostało zatwierdzone, co jest najwyraźniejCommitted_AS
polem.Potencjalnie interesującym eksperymentem pokazującym to jest dodanie
#include <unistd.h>
na początku virtlimitcheck.c (patrz przypis 2) ifork()
tuż przedwhile()
pętlą. Nie gwarantuje się, że będzie działać zgodnie z opisem tutaj bez żmudnej synchronizacji, ale istnieje spora szansa, że tak, YMMV:Ma to sens - patrząc szczegółowo na tmp.txt możesz zobaczyć procesy naprzemiennie ich coraz większe alokacje (jest to łatwiejsze, jeśli wrzucisz pid do wyniku), dopóki jeden, najwyraźniej, nie stwierdzi wystarczająco, że drugi zawiedzie. Zwycięzca może następnie zgarnąć wszystko do
CommitLimit
minusCommitted_AS
.4 Warto w tym miejscu wspomnieć, że jeśli jeszcze nie rozumiesz adresowania wirtualnego i stronicowania na żądanie, to, co sprawia, że możliwe jest przejęcie zobowiązań, to przede wszystkim to, że to, co jądro alokuje do procesów użytkownika, wcale nie jest pamięcią fizyczną - wirtualna przestrzeń adresowa . Na przykład, jeśli proces rezerwuje na coś 10 MB, to jest to sekwencja adresów (wirtualnych), ale adresy te nie odpowiadają jeszcze pamięci fizycznej. Gdy taki adres jest dostępny, powoduje to błąd stronya następnie jądro próbuje zmapować je na prawdziwą pamięć, aby mogła przechowywać prawdziwą wartość. Procesy zwykle rezerwują znacznie więcej przestrzeni wirtualnej, niż faktycznie uzyskują dostęp, co pozwala jądru na najbardziej efektywne wykorzystanie pamięci RAM. Jednak pamięć fizyczna jest nadal zasobem skończonym, a gdy wszystko zostało zamapowane na wirtualną przestrzeń adresową, część wirtualnej przestrzeni adresowej należy wyeliminować, aby zwolnić trochę pamięci RAM.
5 Najpierw ostrzeżenie : jeśli spróbujesz tego
vm.overcommit_memory=0
, pamiętaj, aby najpierw zapisać swoją pracę i zamknąć wszystkie krytyczne aplikacje, ponieważ system zostanie zamrożony na około 90 sekund, a niektóre procesy umrą!Chodzi o to, aby uruchomić bombę wideł, która wygaśnie po 90 sekundach, a widelce przydzielają miejsce, a niektóre z nich zapisują duże ilości danych do pamięci RAM, jednocześnie raportując do stderr.
Skompiluj to
gcc forkbomb.c -o forkbomb
. Najpierw spróbuj zsysctl vm.overcommit_memory=2
- prawdopodobnie dostaniesz coś takiego:W tym środowisku taka widelecowa bomba nie dociera zbyt daleko. Zauważ, że liczba w „mówi N widelców” nie jest całkowitą liczbą procesów, jest to liczba procesów w łańcuchu / gałęzi prowadzących do tego.
Teraz spróbuj
vm.overcommit_memory=0
. Jeśli przekierujesz stderr do pliku, możesz później przeprowadzić zgrubną analizę, np .:Tylko 15 procesom nie udało się przydzielić 1 GB - co pokazuje, że stan ma wpływ na heurystykę dla overcommit_memory = 0 . Ile tam było procesów? Patrząc na koniec tmp.txt, prawdopodobnie> 100 000. W jaki sposób można faktycznie korzystać z 1 GB?
Osiem - co znowu ma sens, ponieważ miałem wtedy ~ 3 GB wolnej pamięci RAM i 6 GB wymiany.
Po wykonaniu tej czynności przejrzyj dzienniki systemu. Powinieneś zobaczyć wyniki raportowania zabójcy OOM (między innymi); przypuszczalnie dotyczy to
oom_badness
.źródło
To się nie stanie, jeśli kiedykolwiek załadujesz tylko 1G danych do pamięci. Co jeśli załadujesz znacznie więcej? Na przykład często pracuję z ogromnymi plikami zawierającymi miliony prawdopodobieństw, które należy załadować do R. To zajmuje około 16 GB pamięci RAM.
Uruchomienie powyższego procesu na moim laptopie spowoduje, że zacznie się zamieniać jak szalony, gdy tylko moje 8 GB pamięci RAM zostanie wypełnione. To z kolei spowolni wszystko, ponieważ odczyt z dysku jest znacznie wolniejszy niż odczyt z pamięci RAM. Co jeśli mam laptopa z 2 GB pamięci RAM i tylko 10 GB wolnego miejsca? Gdy proces zajmie całą pamięć RAM, zapełni również dysk, ponieważ pisze do zamiany, a ja nie mam już więcej pamięci RAM ani miejsca do zamiany (ludzie mają tendencję do ograniczania zamiany do dedykowanej partycji zamiast plik wymiany właśnie z tego powodu). Właśnie tam pojawia się zabójca OOM i rozpoczyna proces zabijania.
Tak więc w systemie może zabraknąć pamięci. Co więcej, silnie wymieniane systemy mogą stać się bezużyteczne na długo przed tym, po prostu z powodu powolnych operacji we / wy z powodu zamiany. Zasadniczo chce się unikać zamiany w jak największym stopniu. Nawet na wysokiej klasy serwerach z szybkimi dyskami SSD wyraźnie widać spadek wydajności. Na moim laptopie, który ma klasyczny dysk 7200 RPM, każda znacząca zamiana zasadniczo uniemożliwia korzystanie z systemu. Im więcej zamienia, tym wolniej się rozwija. Jeśli nie zabiję szybko obrażającego procesu, wszystko zawiesza się, dopóki nie wkroczy zabójca OOM.
źródło
Procesy nie są zabijane, gdy nie ma już pamięci RAM, są zabijane, gdy zostaną oszukane w ten sposób:
Może się to zdarzyć, nawet gdy system nie aktywnie zamienia, na przykład jeśli obszar wymiany jest wypełniony stronami pamięci demonów śpiących.
Tak się nigdy nie dzieje w systemach operacyjnych, które nie przepełniają pamięci. Dzięki nim żaden losowy proces nie jest zabijany, ale pierwszy proces proszący o pamięć wirtualną, gdy jest wyczerpana, ma błąd malloc (lub podobny). Dzięki temu ma szansę odpowiednio poradzić sobie z sytuacją. Jednak w tych systemach operacyjnych może się również zdarzyć, że w systemie zabraknie pamięci wirtualnej, podczas gdy wciąż jest wolna pamięć RAM, co jest dość mylące i ogólnie źle zrozumiane.
źródło
Gdy dostępna pamięć RAM zostanie wyczerpana, jądro zaczyna wymieniać fragmenty przetwarzania na dysk. W rzeczywistości jądro zaczyna zamieniać, gdy pamięć RAM jest prawie wyczerpana: zaczyna proaktywnie zamieniać, gdy ma wolny czas, aby być bardziej responsywnym, jeśli aplikacja nagle potrzebuje więcej pamięci.
Pamiętaj, że pamięć RAM nie jest używana tylko do przechowywania pamięci procesów. W typowym zdrowym systemie tylko około połowa pamięci RAM jest wykorzystywana przez procesy, a druga połowa jest wykorzystywana do buforowania dysku i buforów. Zapewnia to dobrą równowagę między uruchomionymi procesami a wejściami / wyjściami plików.
Przestrzeń wymiany nie jest nieskończona. W pewnym momencie, jeśli procesy będą nadal alokować coraz więcej pamięci, dane przelewu z pamięci RAM wypełnią swap. Kiedy tak się dzieje, procesy, które próbują zażądać więcej pamięci, odrzucają swoje żądania.
Domyślnie Linux przeciąża pamięć. Oznacza to, że czasami pozwala na uruchomienie procesu z pamięcią, która została zarezerwowana, ale nie została użyta. Głównym powodem nadmiernego zaangażowania jest sposób rozwidlania się . Kiedy proces uruchamia podproces, proces potomny funkcjonuje koncepcyjnie w replice pamięci rodzica - dwa procesy początkowo mają pamięć o tej samej treści, ale treść ta będzie się rozchodzić, gdy procesy wprowadzają zmiany w swoich własnych przestrzeniach. Aby w pełni to zaimplementować, jądro musiałoby skopiować całą pamięć rodzica. Spowodowałoby to spowolnienie rozwidlania, więc jądro ćwiczy kopiowanie przy zapisie: początkowo dziecko dzieli całą swoją pamięć z rodzicem; za każdym razem, gdy którykolwiek proces zapisuje na udostępnionej stronie, jądro tworzy kopię tej strony, aby przerwać udostępnianie.
Często dziecko pozostawia wiele stron nietkniętych. Jeśli jądro przydzieli wystarczającą ilość pamięci, aby zreplikować przestrzeń pamięci rodzica w każdym rozwidleniu, dużo pamięci zostanie zmarnowane na rezerwacje, których procesy potomne nigdy nie będą wykorzystywać. Stąd nadmierne zaangażowanie: jądro rezerwuje tylko część tej pamięci, na podstawie oszacowania, ile stron będzie potrzebne dziecko.
Jeśli proces próbuje przydzielić część pamięci i nie ma wystarczającej ilości pamięci, proces odbiera odpowiedź na błąd i radzi sobie z tym, co uzna za stosowne. Jeśli proces pośrednio żąda pamięci, pisząc na udostępnionej stronie, której należy udostępnić, to inna historia. Nie ma możliwości zgłoszenia tej sytuacji do aplikacji: uważa, że ma tam zapisywalne dane, a nawet może je odczytać - po prostu pisanie wymaga nieco bardziej skomplikowanej operacji pod maską. Jeśli jądro nie jest w stanie zapewnić nowej strony pamięci, wszystko, co może zrobić, to zabić proces żądania lub inny proces, aby wypełnić pamięć.
W tym momencie możesz pomyśleć, że zabicie żądającego procesu jest oczywistym rozwiązaniem. Ale w praktyce nie jest tak dobrze. Proces ten może być ważnym, który akurat potrzebuje dostępu teraz tylko do jednej ze swoich stron, podczas gdy mogą być uruchomione inne, mniej ważne procesy. Jądro zawiera złożoną heurystykę, aby wybrać, które procesy zabić - (nie) znanego zabójcę OOM .
źródło
Aby dodać inny punkt widzenia z innych odpowiedzi, wielu VPS hostuje kilka maszyn wirtualnych na danym serwerze. Każda pojedyncza maszyna wirtualna będzie miała określoną ilość pamięci RAM na własny użytek. Wielu dostawców oferuje „seryjną pamięć RAM”, w której mogą korzystać z pamięci RAM przekraczającej wyznaczoną liczbę. Ma to być przeznaczone tylko do krótkotrwałego użytku, a ci, którzy przekroczą ten wydłużony czas, mogą zostać ukarani przez hosta zabijającego procesy w celu zmniejszenia ilości używanej pamięci RAM, aby inni nie cierpieli z powodu przeciążona maszyna hosta.
źródło
Czas linux zajmuje zewnętrzną przestrzeń wirtualną. To jest partycja wymiany. Kiedy Ram jest wypełniony, linux zajmuje ten obszar wymiany, aby uruchomić proces o niskim priorytecie.
źródło