Non sono uno sviluppatore del kernel, ma ho passato anni a filosofare su questo problema perché mi sono imbattuto in questo moltissime volte. In realtà ho inventato una metafora per l'intera situazione, quindi lascia che te lo dica. Presumo nella mia storia che cose come "swap" non esistano. Al giorno d'oggi Swap non ha molto senso con 32 GB di RAM.
Immagina un tuo quartiere in cui l'acqua è collegata a ciascun edificio attraverso tubi e le città devono gestire la capacità. Supponiamo che tu abbia solo una produzione di 100 unità di acqua al secondo (e tutta la capacità inutilizzata va sprecata perché non hai serbatoi). Ogni casa (home = una piccola app, un terminale, il widget dell'orologio, ecc.) Richiede 1 unità di acqua al secondo. Tutto questo è bello e buono perché la tua popolazione è come 90, quindi tutti hanno abbastanza acqua.
Ora il sindaco (= tu) decide di voler aprire un ristorante di grandi dimensioni (= browser). Questo ristorante ospiterà più cuochi (= schede del browser). Ogni cuoco ha bisogno di 1 unità di acqua al secondo. Si inizia con 10 cuochi, quindi il consumo totale di acqua per l'intero quartiere è di 100 unità di acqua che è ancora tutto buono.
Ora iniziano le cose divertenti: assumi un altro cuoco nel tuo ristorante che rende il fabbisogno totale di acqua 101 che ovviamente non hai. Devi fare qualcosa.
La gestione dell'acqua (= kernel) ha 3 opzioni.
1. La prima opzione è semplicemente disconnettere il servizio per le case che non hanno usato l'acqua di recente. Questo va bene, ma se la casa disconnessa vuole riutilizzare l'acqua, dovrà ripetere il lungo processo di registrazione. La direzione può disconnettere più case per liberare più risorse idriche. In realtà, disconnetteranno tutte le case che non hanno usato acqua di recente mantenendo così una certa quantità di acqua gratuita sempre disponibile.
Sebbene la tua città continui a funzionare, il rovescio della medaglia è che i progressi si fermano. La maggior parte del tempo viene impiegato in attesa della gestione delle risorse idriche per ripristinare il servizio.
Questo è ciò che fa il kernel con le pagine supportate da file. Se esegui un eseguibile di grandi dimensioni (come Chrome), il suo file viene copiato nella memoria. Quando la memoria è insufficiente o se ci sono parti a cui non è stato effettuato l'accesso di recente, il kernel può rilasciarle perché può comunque ricaricarle dal disco. Se ciò viene fatto in modo eccessivo, questo blocca il desktop perché tutti aspetteranno l'IO del disco. Nota che il kernel rilascerà anche molte delle pagine usate meno di recente quando inizi a fare molto IO. Ecco perché ci vogliono anni per passare a un'app in background dopo aver copiato diversi file di grandi dimensioni come immagini DVD.
Questo è il comportamento più fastidioso per me perché odio il singhiozzo e non hai alcun controllo su di esso. Sarebbe bello poterlo spegnere. Sto pensando a qualcosa del genere
sed -i 's/may_unmap = 1/may_unmap = (vm_swappiness >= 0)/' mm/vmscan.c
e quindi è possibile impostare vm_swappiness su -1 per disabilitarlo. Questo ha funzionato abbastanza bene nei miei piccoli test ma purtroppo non sono uno sviluppatore del kernel, quindi non l'ho inviato a nessuno (e ovviamente la piccola modifica sopra non è completa).
2.La direzione potrebbe negare la richiesta di acqua del nuovo cuoco. Questa inizialmente sembra una buona idea. Tuttavia ci sono due aspetti negativi. Innanzitutto, ci sono aziende che richiedono molti abbonamenti idrici anche se non li usano. Una possibile ragione per farlo è evitare tutto il sovraccarico di parlare con la gestione dell'acqua ogni volta che hanno bisogno di un po 'd'acqua in più. Il loro consumo di acqua aumenta e diminuisce a seconda dell'ora del giorno. Ad esempio nel caso del ristorante, la società ha bisogno di molta più acqua a mezzogiorno rispetto a mezzanotte. Quindi richiedono tutta l'acqua possibile che potrebbero usare, ma ciò spreca allocazioni di acqua durante la mezzanotte. Il problema è che non tutte le aziende possono prevedere correttamente il loro picco di utilizzo, quindi richiedono molto di più nella speranza che non debbano mai preoccuparsi di richiedere di più.
Questo è ciò che fa la macchina virtuale Java: alloca un sacco di memoria all'avvio e quindi funziona da quello. Per impostazione predefinita, il kernel alloca la memoria solo quando l'app Java inizia effettivamente a usarla. Tuttavia, se si disabilita l'overcommit, il kernel prenderà sul serio la prenotazione. Permetterà che l'allocazione abbia successo solo se ha effettivamente le risorse per esso.
Tuttavia, c'è un altro problema più serio con questo approccio. Supponiamo che una società inizi a richiedere una singola unità di acqua ogni giorno (anziché con incrementi di 10). Alla fine raggiungerai uno stato in cui hai 0 unità libere. Ora questa azienda non sarà in grado di allocare di più. Va bene, chi se ne frega comunque delle grandi aziende. Ma il problema è che le piccole case non saranno in grado di richiedere più acqua! Non sarai in grado di costruire piccoli bagni pubblici per far fronte all'improvviso afflusso di turisti. Non sarai in grado di fornire acqua di emergenza per l'incendio nella foresta vicina.
In termini di computer: in situazioni di memoria molto bassa senza overcommit non sarai in grado di aprire un nuovo xterm, non sarai in grado di ssh nel tuo computer, non sarai in grado di aprire una nuova scheda per cercare possibili correzioni. In altre parole, disabilitare l'overcommit rende il desktop inutile anche quando la memoria è insufficiente.
3. Ora ecco un modo interessante di gestire il problema quando un'azienda inizia a usare troppa acqua. La gestione dell'acqua fa esplodere! Letteralmente: va nel sito del ristorante, vi lancia dinamiti e aspetta che esploda. Ciò ridurrà istantaneamente di molto il fabbisogno idrico della città in modo che nuove persone possano trasferirsi, creare bagni pubblici, ecc. Tu, come sindaco, puoi ricostruire il ristorante nella speranza che questa volta richiederà meno acqua. Ad esempio, dirai alle persone di non andare nei ristoranti se ci sono già troppe persone all'interno (ad esempio, aprirai meno schede del browser).
Questo è in realtà ciò che fa il kernel quando esaurisce tutte le opzioni e ha bisogno di memoria: chiama il killer OOM. Prende un'applicazione di grandi dimensioni (basata su molte euristiche) e la uccide, liberando un sacco di memoria ma mantenendo un desktop reattivo. In realtà il kernel Android lo fa in modo ancora più aggressivo: uccide l'app utilizzata meno di recente quando la memoria è scarsa (rispetto al kernel stock che lo fa solo come ultima risorsa). Questo si chiama Viking Killer in Android.
Penso che questa sia una delle soluzioni più semplici al problema: non è che tu abbia più opzioni di così, quindi perché non superarlo prima o poi, giusto? Il problema è che il kernel a volte fa parecchio lavoro per evitare di invocare il killer OOM. Ecco perché vedi che il tuo desktop è molto lento e il kernel non sta facendo nulla al riguardo. Ma per fortuna c'è un'opzione per invocare te stesso l'assassino OOM! Innanzitutto, assicurati che il tasto sysrq magico sia abilitato (ad es. echo 1 | sudo tee
/proc/sys/kernel/sysrq
) Ogni volta che ritieni che il kernel stia esaurendo la memoria, premi Alt + SysRQ, Alt + f.
OK, allora è tutto carino, ma vuoi provarlo? La situazione di memoria insufficiente è molto semplice da riprodurre. Ho un'app molto semplice per questo. Dovrai eseguirlo due volte. Il primo avvio determinerà la quantità di RAM libera disponibile, il secondo eseguirà la situazione di memoria insufficiente. Si noti che questo metodo presuppone che lo swap sia disabilitato (ad es. Fare a sudo swapoff -a
). Segue codice e utilizzo:
// gcc -std=c99 -Wall -Wextra -Werror -g -o eatmem eatmem.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char** argv)
{
int limit = 123456789;
if (argc >= 2) {
limit = atoi(argv[1]);
}
setbuf(stdout, NULL);
for (int i = 1; i <= limit; i++) {
memset(malloc(1 << 20), 1, 1 << 20);
printf("\rAllocated %5d MiB.", i);
}
sleep(10000);
return 0;
}
Ed ecco come lo usi:
$ gcc -std=c99 -Wall -Wextra -Werror -g -o eatmem eatmem.c
$ ./eatmem
Allocated 31118 MiB.Killed
$ ./eatmem 31110
Allocated 31110 MiB.Killed
La prima invocazione ha rilevato che abbiamo 31.118 MiB di RAM libera. Quindi ho detto all'applicazione di allocare 31.110 MiB RAM in modo che il kernel non la uccidesse ma consumasse quasi tutta la mia memoria. Il mio sistema si è bloccato: anche il puntatore del mouse non si è mosso. Ho premuto Alt + SysRQ, Alt + f e ha interrotto il mio processo EatMem e il sistema è stato ripristinato.
Anche se abbiamo coperto le nostre opzioni su cosa fare in una situazione di memoria insufficiente, l'approccio migliore (proprio come qualsiasi altra situazione pericolosa) è quello di evitarlo in primo luogo. Ci sono molti modi per farlo. Un modo comune che ho visto è quello di mettere le applicazioni che si comportano male (come i browser) in contenitori diversi rispetto al resto del sistema. In tal caso il browser non sarà in grado di influire sul desktop. Ma la prevenzione stessa è al di fuori del campo di applicazione della domanda, quindi non ne scriverò.
TL; DR: sebbene al momento non sia possibile evitare completamente il paging, è possibile mitigare l'arresto completo del sistema disabilitando l'overcommit. Ma il tuo sistema sarà ancora inutilizzabile in situazioni di memoria insufficiente, ma in modo diverso. Indipendentemente da quanto sopra, in una situazione di memoria insufficiente premere Alt + SysRQ, Alt + f per terminare un grosso processo a scelta del kernel. Il sistema dovrebbe ripristinarne la reattività dopo alcuni secondi. Questo presuppone che tu abbia la chiave sysrq magica abilitata (non è di default).