No soy desarrollador de kernel, pero pasé años filosofando sobre este tema porque me encontré con esto muchas veces. De hecho, se me ocurrió una metáfora de toda la situación, así que déjame decirte eso. Asumiré en mi historia que cosas como "swap" no existen. El intercambio no tiene mucho sentido con 32 GB de RAM en estos días de todos modos.
Imagine un vecindario suyo donde el agua está conectada a cada edificio a través de tuberías y las ciudades necesitan administrar la capacidad. Supongamos que solo tiene una producción de 100 unidades de agua por segundo (y toda la capacidad no utilizada se desperdicia porque no tiene tanques de reserva). Cada hogar (hogar = una pequeña aplicación, una terminal, el widget de reloj, etc.) requiere una unidad de agua por segundo. Todo esto es bueno y bueno porque su población es de 90, por lo que todos obtienen suficiente agua.
Ahora el alcalde (= usted) decide que desea abrir un gran restaurante (= navegador). Este restaurante albergará múltiples cocineros (= pestañas del navegador). Cada cocinero necesita 1 unidad de agua por segundo. Comienzas con 10 cocineros, por lo que el consumo total de agua para todo el vecindario es de 100 unidades de agua, lo que sigue siendo bueno.
Ahora comienzan las cosas divertidas: contratas a otro cocinero en tu restaurante, lo que hace que los requisitos totales de agua sean 101, lo que obviamente no tienes. Necesitas hacer algo.
La gestión del agua (= kernel) tiene 3 opciones.
1. La primera opción es simplemente desconectar el servicio para las casas que no usaron el agua recientemente. Esto está bien, pero si la casa desconectada quiere volver a usar el agua, tendrá que pasar por el largo proceso de registro nuevamente. La administración puede desconectar varias viviendas para liberar más recursos hídricos. En realidad, desconectarán todas las casas que no usaron agua recientemente, manteniendo así una cierta cantidad de agua gratuita siempre disponible.
Aunque su ciudad sigue funcionando, la desventaja es que el progreso se detiene. La mayor parte de su tiempo se dedica a esperar a que la administración del agua restablezca su servicio.
Esto es lo que hace el núcleo con las páginas respaldadas por archivos. Si ejecuta un archivo ejecutable grande (como Chrome), su archivo se copia en la memoria. Cuando hay poca memoria o si hay partes a las que no se ha accedido recientemente, el kernel puede descartar esas partes porque de todos modos puede volver a cargarlas desde el disco. Si esto se hace en exceso, esto detiene su escritorio porque todo estará esperando el disco IO. Tenga en cuenta que el kernel también soltará muchas páginas menos utilizadas recientemente cuando comience a hacer muchas IO. Es por eso que lleva años cambiar a una aplicación en segundo plano después de copiar varios archivos grandes, como imágenes de DVD.
Este es el comportamiento más molesto para mí porque odio los hickups y no tienes ningún control sobre eso. Sería bueno poder apagarlo. Estoy pensando en algo en la línea de
sed -i 's/may_unmap = 1/may_unmap = (vm_swappiness >= 0)/' mm/vmscan.c
y luego puede establecer vm_swappiness en -1 para deshabilitar esto. Esto funcionó bastante bien en mis pequeñas pruebas, pero lamentablemente no soy un desarrollador de kernel, así que no se lo envié a nadie (y obviamente la pequeña modificación anterior no está completa).
2)La gerencia podría negar la solicitud de agua del nuevo cocinero. Esto inicialmente suena como una buena idea. Sin embargo, hay dos desventajas. Primero, hay compañías que solicitan muchas suscripciones de agua a pesar de que no las usan. Una posible razón para hacer esto es evitar toda la sobrecarga de hablar con la administración del agua siempre que necesiten un poco de agua adicional. Su consumo de agua aumenta y disminuye según la hora del día. Por ejemplo, en el caso del restaurante, la compañía necesita mucha más agua durante el mediodía en comparación con la medianoche. Entonces, solicitan toda el agua posible que puedan usar, pero eso desperdicia las asignaciones de agua durante la medianoche. El problema es que no todas las compañías pueden prever su uso máximo correctamente, por lo que solicitan mucho más con la esperanza de que nunca tengan que preocuparse por solicitar más.
Esto es lo que hace la máquina virtual de Java: asigna un montón de memoria al inicio y luego funciona a partir de eso. Por defecto, el núcleo solo asignará la memoria cuando su aplicación Java realmente comience a usarla. Sin embargo, si deshabilita el exceso de confirmación, el núcleo tomará en serio la reserva. Solo permitirá que la asignación tenga éxito si realmente tiene los recursos para ello.
Sin embargo, hay otro problema más grave con este enfoque. Digamos que una empresa comienza a solicitar una sola unidad de agua todos los días (en lugar de hacerlo en pasos de 10). Eventualmente alcanzarás un estado donde tienes 0 unidades libres. Ahora esta compañía no podrá asignar más. De todos modos, a quién le importan las grandes empresas. ¡Pero el problema es que las casas pequeñas tampoco podrán solicitar más agua! No podrá construir pequeños baños públicos para hacer frente a la afluencia repentina de turistas. No podrá proporcionar agua de emergencia para el incendio en el bosque cercano.
En términos de computadora: en situaciones de poca memoria sin exceso de compromiso, no podrá abrir un nuevo xterm, no podrá ingresar a su máquina, no podrá abrir una nueva pestaña para buscar posibles arreglos En otras palabras, deshabilitar el exceso de compromiso también hace que su escritorio sea inútil cuando tiene poca memoria.
3. Ahora aquí hay una forma interesante de manejar el problema cuando una empresa comienza a usar demasiada agua. ¡La gestión del agua lo explota! Literalmente: va al sitio del restaurante, arroja dinamitas y espera hasta que explota. Esto reducirá instantáneamente los requisitos de agua de la ciudad en gran medida para que las personas nuevas puedan mudarse, pueda crear baños públicos, etc. Usted, como alcalde, puede reconstruir el restaurante con la esperanza de que esta vez requiera menos agua. Por ejemplo, le dirá a la gente que no vaya a los restaurantes si ya hay demasiada gente adentro (por ejemplo, abrirá menos pestañas del navegador).
Esto es realmente lo que hace el núcleo cuando se queda sin todas las opciones y necesita memoria: llama al asesino OOM. Elige una aplicación grande (basada en muchas heurísticas) y la mata, liberando un montón de memoria pero manteniendo un escritorio receptivo. En realidad, el kernel de Android hace esto aún más agresivamente: mata la aplicación utilizada menos recientemente cuando la memoria es baja (en comparación con el kernel de inventario que lo hace solo como último recurso). Esto se llama Viking Killer en Android.
Creo que esta es una de las soluciones más simples para el problema: no es que tenga más opciones que esta, así que ¿por qué no superarlo más pronto que tarde, verdad? El problema es que el kernel a veces hace mucho trabajo para evitar invocar al asesino OOM. Es por eso que ves que tu escritorio es muy lento y el kernel no está haciendo nada al respecto. ¡Pero afortunadamente hay una opción para invocar al asesino OOM usted mismo! Primero, asegúrese de que la clave mágica sysrq esté habilitada (por ejemplo echo 1 | sudo tee
/proc/sys/kernel/sysrq
), luego, cuando sienta que el núcleo se está quedando sin memoria, simplemente presione Alt + SysRQ, Alt + f.
Bien, ¿todo eso es bueno pero quieres probarlo? La situación de poca memoria es muy simple de reproducir. Tengo una aplicación muy simple para eso. Deberá ejecutarlo dos veces. La primera ejecución determinará la cantidad de RAM libre que tiene, la segunda ejecución creará la situación de poca memoria. Tenga en cuenta que este método supone que tiene el intercambio deshabilitado (por ejemplo, hacer a sudo swapoff -a
). Código y uso a continuación:
// 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;
}
Y así es como lo usas:
$ gcc -std=c99 -Wall -Wextra -Werror -g -o eatmem eatmem.c
$ ./eatmem
Allocated 31118 MiB.Killed
$ ./eatmem 31110
Allocated 31110 MiB.Killed
La primera invocación detectó que tenemos 31,118 MiB de RAM libre. Entonces le dije a la aplicación que asignara 31,110 MiB RAM para que el kernel no lo matara, sino que consumiera casi toda mi memoria. Mi sistema se congeló: incluso el puntero del mouse no se movió. Presioné Alt + SysRQ, Alt + f y mató mi proceso eatmem y el sistema se restauró.
Aunque cubrimos nuestras opciones sobre qué hacer en una situación de poca memoria, el mejor enfoque (como cualquier otra situación peligrosa) es evitarlo en primer lugar. Hay muchas maneras de hacer esto. Una forma común que he visto es colocar las aplicaciones que se comportan mal (como los navegadores) en diferentes contenedores que el resto del sistema. En ese caso, el navegador no podrá afectar su escritorio. Pero la prevención en sí está fuera del alcance de la pregunta, por lo que no escribiré al respecto.
TL; DR: aunque actualmente no hay forma de evitar completamente la paginación, puede mitigar la detención total del sistema deshabilitando el exceso de compromiso. Pero su sistema seguirá siendo inutilizable durante una situación de poca memoria, pero de una manera diferente. Independientemente de lo anterior, en una situación de poca memoria, presione Alt + SysRQ, Alt + f para eliminar un gran proceso de elección del núcleo. Su sistema debería restaurar su capacidad de respuesta después de unos segundos. Esto supone que tiene habilitada la clave mágica sysrq (no está predeterminada).