Vi varias publicaciones en la web de personas que aparentemente se quejaban de un VPS alojado que mataba procesos inesperadamente porque usaban demasiada RAM.
¿Cómo es esto posible? Pensé que todos los sistemas operativos modernos proporcionan "RAM infinita" simplemente usando el intercambio de disco para todo lo que pasa por la RAM física. ¿Es esto correcto?
¿Qué podría estar sucediendo si un proceso se "cancela debido a poca RAM"?
Respuestas:
A veces se dice que Linux por defecto nunca niega las solicitudes de más memoria del código de la aplicación, por ejemplo
malloc()
.1 Esto no es de hecho cierto; el valor predeterminado utiliza un heurístico por el cualDe
[linux_src]/Documentation/vm/overcommit-accounting
(todas las citas son del árbol 3.11). Exactamente lo que cuenta como una "asignación seriamente salvaje" no se hace explícito, por lo que tendríamos que pasar por la fuente para determinar los detalles. También podríamos usar el método experimental en la nota al pie 2 (a continuación) para tratar de obtener un reflejo de la heurística; en base a eso, mi observación empírica inicial es que, en circunstancias ideales (== el sistema está inactivo), si no lo hace ' No tiene ningún intercambio, se le permitirá asignar aproximadamente la mitad de su RAM, y si lo hace, obtendrá aproximadamente la mitad de su RAM más todo su intercambio. Eso es más o menos por proceso (pero tenga en cuenta que este límite es dinámico y está sujeto a cambios debido al estado, ver algunas observaciones en la nota 5).La mitad de su intercambio RAM plus es explícitamente el valor predeterminado para el campo "CommitLimit" en
/proc/meminfo
. Esto es lo que significa, y tenga en cuenta que en realidad no tiene nada que ver con el límite que se acaba de discutir (de[src]/Documentation/filesystems/proc.txt
):El documento de contabilidad de sobrecompromiso citado anteriormente establece que el valor predeterminado
vm.overcommit_ratio
es 50. Entonces, si puedesysctl vm.overcommit_memory=2
, puede ajustar vm.covercommit_ratio (consysctl
) y ver las consecuencias. 3 El modo predeterminado, cuandoCommitLimit
no se aplica y solo "se rechazan las sobrecompresiones obvias de espacio de direcciones", es cuándovm.overcommit_memory=0
.Si bien la estrategia predeterminada tiene un límite heurístico por proceso que impide la "asignación seriamente salvaje", sí deja al sistema en su conjunto libre para ponerse seriamente salvaje, en cuanto a la asignación. 4 Esto significa que en algún momento puede quedarse sin memoria y tener que declararse en bancarrota en algunos procesos a través del asesino OOM .
¿Qué mata el asesino OOM? No es necesariamente el proceso que pedía memoria cuando no había ninguno, ya que ese no es necesariamente el proceso verdaderamente culpable y, lo que es más importante, no necesariamente el que sacará al sistema más rápidamente del problema en el que se encuentra.
Aquí se cita esto, que probablemente cita una fuente 2.6.x:
Lo que parece una justificación decente. Sin embargo, sin ser forense, el n. ° 5 (que es redundante del n. ° 1) parece una implementación de venta difícil, y el n. ° 3 es redundante del n. ° 2. Por lo tanto, podría tener sentido considerar esto reducido a # 2/3 y # 4.
Busqué una fuente reciente (3.11) y noté que este comentario ha cambiado mientras tanto:
Esto es un poco más explícito sobre el n. ° 2: "El objetivo es [matar] la tarea que consume la mayor cantidad de memoria para evitar fallas posteriores de OOM" y, por implicación, n. ° 4 ( "queremos matar la cantidad mínima de procesos ( uno ) ) .
Si desea ver al asesino OOM en acción, vea la nota 5.
1 Un engaño que Gilles afortunadamente me libró, ver comentarios.
2 Aquí hay un bit sencillo de C que solicita trozos de memoria cada vez más grandes para determinar cuándo fallará una solicitud de más:
Si no conoce C, puede compilar esto
gcc virtlimitcheck.c -o virtlimitcheck
y luego ejecutar./virtlimitcheck
. Es completamente inofensivo, ya que el proceso no usa nada del espacio que solicita, es decir, en realidad nunca usa RAM.En un sistema 3.11 x86_64 con sistema de 4 GB y 6 GB de intercambio, fallé a ~ 7400000 kB; el número fluctúa, por lo que quizás el estado sea un factor. Esto es por casualidad cerca de la
CommitLimit
de/proc/meminfo
, pero esto a través de la modificaciónvm.overcommit_ratio
no hace ninguna diferencia. Sin embargo, en un sistema ARM 448 MB 3.6.11 de 32 bits con 64 MB de intercambio, fallo a ~ 230 MB. Esto es interesante ya que en el primer caso la cantidad es casi el doble de la cantidad de RAM, mientras que en el segundo es aproximadamente 1/4, lo que implica que la cantidad de intercambio es un factor. Esto se confirmó al desactivar el intercambio en el primer sistema, cuando el umbral de falla bajó a ~ 1.95 GB, una relación muy similar a la pequeña caja ARM.¿Pero es esto realmente por proceso? Parece ser. El breve programa a continuación solicita un fragmento de memoria definido por el usuario y, si tiene éxito, espera a que presione la tecla de retorno; de esta manera puede probar varias instancias simultáneas:
Sin embargo, tenga en cuenta que no se trata estrictamente de la cantidad de RAM e intercambio independientemente del uso; consulte la nota 5 para ver las observaciones sobre los efectos del estado del sistema.
3 se
CommitLimit
refiere a la cantidad de espacio de direcciones permitido para el sistema cuando vm.overcommit_memory = 2. Presumiblemente, entonces, la cantidad que puede asignar debería ser menos menos lo que ya está comprometido, que aparentemente es elCommitted_AS
campo.Un experimento potencialmente interesante que demuestra esto es agregar
#include <unistd.h>
a la parte superior de virtlimitcheck.c (ver nota al pie 2), yfork()
justo antes delwhile()
ciclo. No se garantiza que funcione como se describe aquí sin una sincronización tediosa, pero hay una buena posibilidad de que lo haga, YMMV:Esto tiene sentido: al mirar tmp.txt en detalle, puede ver que los procesos alternan sus asignaciones cada vez más grandes (esto es más fácil si arroja el pid en la salida) hasta que uno, evidentemente, ha reclamado lo suficiente como para que el otro falle. El ganador es libre de agarrar todo hasta
CommitLimit
menosCommitted_AS
.4 Vale la pena mencionar, en este punto, si aún no comprende el direccionamiento virtual y la paginación de demanda, lo que hace posible un compromiso excesivo en primer lugar es que lo que el núcleo asigna a los procesos del usuario no es en absoluto memoria física: es espacio de direcciones virtuales . Por ejemplo, si un proceso reserva 10 MB para algo, se presenta como una secuencia de direcciones (virtuales), pero esas direcciones aún no corresponden a la memoria física. Cuando se accede a dicha dirección, esto da como resultado un error de páginay luego el núcleo intenta mapearlo en la memoria real para que pueda almacenar un valor real. Los procesos generalmente reservan mucho más espacio virtual del que realmente acceden, lo que permite que el núcleo haga el uso más eficiente de la RAM. Sin embargo, la memoria física sigue siendo un recurso finito y, cuando se ha asignado todo al espacio de direcciones virtuales, se debe eliminar parte del espacio de direcciones virtuales para liberar algo de RAM.
5 Primero una advertencia : si intenta hacer esto
vm.overcommit_memory=0
, asegúrese de guardar su trabajo primero y cierre todas las aplicaciones críticas, porque el sistema se congelará durante ~ 90 segundos y algunos procesos morirán.La idea es ejecutar una bomba de horquilla que agota el tiempo de espera después de 90 segundos, con las horquillas asignando espacio y algunas de ellas escribiendo grandes cantidades de datos en la RAM, todo el tiempo informando a stderr.
Compila esto
gcc forkbomb.c -o forkbomb
. Primero, pruébalosysctl vm.overcommit_memory=2
, probablemente obtendrás algo como:En este entorno, este tipo de bomba tenedor no llega muy lejos. Tenga en cuenta que el número en "dice N forks" no es el número total de procesos, es el número de procesos en la cadena / rama que conduce a ese.
Ahora pruébalo con
vm.overcommit_memory=0
. Si redirige stderr a un archivo, puede hacer un análisis crudo después, por ejemplo:Solo 15 procesos no pudieron asignar 1 GB, lo que demuestra que la heurística para overcommit_memory = 0 se ve afectada por el estado. ¿Cuántos procesos hubo? Mirando al final de tmp.txt, probablemente> 100,000. Ahora, ¿cómo puede realmente usar el 1 GB?
Ocho, lo que nuevamente tiene sentido, ya que en ese momento tenía ~ 3 GB de RAM libre y 6 GB de intercambio.
Eche un vistazo a los registros de su sistema después de hacer esto. Debería ver los puntajes de informes del asesino OOM (entre otras cosas); presumiblemente esto se relaciona con
oom_badness
.fuente
Esto no le sucederá si solo carga 1G de datos en la memoria. ¿Qué pasa si cargas mucho, mucho más? Por ejemplo, a menudo trabajo con archivos enormes que contienen millones de probabilidades que deben cargarse en R. Esto toma alrededor de 16 GB de RAM.
Ejecutar el proceso anterior en mi computadora portátil hará que comience a intercambiarse como loco tan pronto como se hayan llenado mis 8 GB de RAM. Eso, a su vez, ralentizará todo porque leer desde el disco es mucho más lento que leer desde la RAM. ¿Qué sucede si tengo una computadora portátil con 2 GB de RAM y solo 10 GB de espacio libre? Una vez que el proceso ha tomado toda la RAM, también llenará el disco porque está escribiendo para intercambiar y me queda sin más RAM y sin más espacio para intercambiar (las personas tienden a limitar el intercambio a una partición dedicada en lugar de a archivo de intercambio exactamente por esa razón). Ahí es donde entra el asesino OOM y comienza a matar procesos.
Entonces, el sistema puede quedarse sin memoria. Además, los sistemas de intercambio excesivo pueden volverse inutilizables mucho antes de que esto suceda simplemente debido a las lentas operaciones de E / S debido al intercambio. En general, se quiere evitar el intercambio tanto como sea posible. Incluso en servidores de gama alta con SSD rápidos, hay una clara disminución en el rendimiento. En mi computadora portátil, que tiene una unidad clásica de 7200 RPM, cualquier intercambio significativo esencialmente deja el sistema inutilizable. Cuanto más intercambia, más lento se vuelve. Si no elimino el proceso ofensivo rápidamente, todo se bloquea hasta que interviene el asesino OOM.
fuente
Los procesos no se eliminan cuando no hay más RAM, se eliminan cuando se los ha engañado de esta manera:
Esto puede ocurrir incluso mientras el sistema no está intercambiando activamente, por ejemplo, si el área de intercambio está llena de páginas de memoria de demonios dormidos.
Esto nunca sucede en sistemas operativos que no comprometen demasiado la memoria. Con ellos, no se elimina ningún proceso aleatorio, pero el primer proceso que solicita memoria virtual mientras está agotado tiene malloc (o similar) que vuelve por error. Por lo tanto, se le da la oportunidad de manejar adecuadamente la situación. Sin embargo, en estos sistemas operativos, también puede ocurrir que el sistema se quede sin memoria virtual mientras todavía hay RAM libre, lo cual es bastante confuso y generalmente mal entendido.
fuente
Cuando se agota la RAM disponible, el núcleo comienza a intercambiar bits de procesamiento en el disco. En realidad, el núcleo comienza a intercambiar a cuando la RAM está a punto de agotarse: comienza a intercambiar de manera proactiva cuando tiene un momento de inactividad, para responder mejor si una aplicación de repente requiere más memoria.
Tenga en cuenta que la RAM no se usa solo para almacenar la memoria de los procesos. En un sistema saludable típico, los procesos usan solo la mitad de la RAM, y la otra mitad se usa para caché de disco y buffers. Esto proporciona un buen equilibrio entre los procesos en ejecución y la entrada / salida de archivos.
El espacio de intercambio no es infinito. En algún momento, si los procesos siguen asignando más y más memoria, los datos indirectos de la RAM llenarán el intercambio. Cuando eso sucede, los procesos que intentan solicitar más memoria ven su solicitud denegada.
Por defecto, Linux sobrecompite la memoria. Esto significa que a veces permitirá que un proceso se ejecute con la memoria que ha reservado, pero que no se utiliza. La razón principal para tener un compromiso excesivo es la forma en que funciona la bifurcación . Cuando un proceso inicia un subproceso, el proceso secundario opera conceptualmente en una réplica de la memoria del padre: los dos procesos inicialmente tienen memoria con el mismo contenido, pero ese contenido divergerá a medida que los procesos hagan cambios cada uno en su propio espacio. Para implementar esto completamente, el núcleo tendría que copiar toda la memoria del padre. Esto haría que la bifurcación fuera lenta, por lo que el núcleo practica copia en escritura: inicialmente, el niño comparte toda su memoria con el padre; cada vez que cualquiera de los procesos escribe en una página compartida, el núcleo crea una copia de esa página para interrumpir el intercambio.
A menudo, un niño dejará muchas páginas intactas. Si el núcleo asignara suficiente memoria para replicar el espacio de memoria del padre en cada bifurcación, se desperdiciaría mucha memoria en las reservas que los procesos secundarios nunca usarán. Por lo tanto, sobrecompromiso: el núcleo solo reserva parte de esa memoria, en base a una estimación de cuántas páginas necesitará el niño.
Si un proceso intenta asignar algo de memoria y no queda suficiente memoria, el proceso recibe una respuesta de error y lo trata como mejor le parezca. Si un proceso solicita memoria indirectamente al escribir en una página compartida que no se debe compartir, es una historia diferente. No hay forma de informar esta situación a la aplicación: cree que tiene datos que se pueden escribir allí, e incluso podría leerlos; es solo que la escritura implica una operación un poco más elaborada bajo el capó. Si el núcleo no puede proporcionar una nueva página de memoria, todo lo que puede hacer es eliminar el proceso de solicitud o eliminar algún otro proceso para llenar la memoria.
Podría pensar en este punto que matar el proceso de solicitud es la solución obvia. Pero en la práctica, esto no es tan bueno. El proceso puede ser importante y ahora solo necesita acceder a una de sus páginas, mientras que puede haber otros procesos menos importantes en ejecución. Entonces, el núcleo incluye heurísticas complejas para elegir qué procesos matar: el (in) famoso asesino OOM .
fuente
Solo para agregar otro punto de vista de las otras respuestas, muchos VPS alojan varias máquinas virtuales en cualquier servidor. Cualquier VM individual tendrá una cantidad específica de RAM para su propio uso. Muchos proveedores ofrecen "RAM en ráfaga", en la que pueden usar RAM más allá de la cantidad que están designados. Esto está destinado a ser solo para uso a corto plazo, y aquellos que van más allá de esta cantidad de tiempo prolongada pueden ser penalizados por los procesos de eliminación del host para reducir la cantidad de RAM en uso para que otros no sufran La máquina host está sobrecargada.
fuente
Algún tiempo, Linux ocupa un espacio virtual externo. Esa es la partición de intercambio. Cuando se llena Ram, Linux toma esta área de intercambio para ejecutar un proceso de baja prioridad.
fuente