Carga de bibliotecas compartidas y uso de RAM

41

Me pregunto cómo gestiona Linux las bibliotecas compartidas. (En realidad, estoy hablando de Maemo Fremantle, una distribución basada en Debian lanzada en 2009 con 256 MB de RAM).

Supongamos que tenemos dos ejecutables vinculados a libQtCore.so.4 y usando sus símbolos (usando sus clases y funciones). Por simplicidad llamémoslos ay b. Asumimos que ambos ejecutables enlazan a las mismas bibliotecas.

Primero lanzamos a. La biblioteca tiene que ser cargada. ¿Se carga en su totalidad o se carga en la memoria solo en la parte que se requiere (como no usamos cada clase, solo se carga el código con respecto a las clases utilizadas)?

Luego lanzamos b. Asumimos que atodavía se está ejecutando. btambién se vincula a libQtCore.so.4 y usa algunas de las clases que ausa, pero también algunas que no usa a. ¿La biblioteca se cargará dos veces (por aseparado y por separado para b)? ¿O usarán el mismo objeto que ya está en la RAM? Si bno utiliza símbolos nuevos y aya se está ejecutando, ¿aumentará la RAM utilizada por las bibliotecas compartidas? (¿O la diferencia será insignificante)

marmistrz
fuente

Respuestas:

54

NOTA: Voy a suponer que su máquina tiene una unidad de mapeo de memoria (MMU). Hay una versión de Linux (µClinux) que no requiere una MMU, y esta respuesta no se aplica allí.

¿Qué es una MMU? Es hardware, parte del procesador y / o controlador de memoria. Comprender la vinculación de la biblioteca compartida no requiere que comprenda exactamente cómo funciona una MMU, solo que una MMU permite que haya una diferencia entre las direcciones de memoria lógica (las utilizadas por los programas) y las físicas.direcciones de memoria (las que realmente están presentes en el bus de memoria). La memoria se divide en páginas, generalmente de 4K en Linux. Con 4k páginas, las direcciones lógicas 0–4095 son la página 0, las direcciones lógicas 4096–8191 son la página 1, etc. La MMU las asigna a las páginas físicas de RAM, y cada página lógica se puede asignar típicamente a 0 o 1 páginas físicas. Una página física dada puede corresponder a múltiples páginas lógicas (así es como se comparte la memoria: varias páginas lógicas corresponden a la misma página física). Tenga en cuenta que esto se aplica independientemente del sistema operativo; Es una descripción del hardware.

En el cambio de proceso, el kernel cambia las asignaciones de página MMU, de modo que cada proceso tiene su propio espacio. La dirección 4096 en el proceso 1000 puede ser (y generalmente es) completamente diferente de la dirección 4096 en el proceso 1001.

Casi siempre que ves una dirección, es una dirección lógica. Los programas de espacio de usuario casi nunca tratan con direcciones físicas.

Ahora, también hay varias formas de construir bibliotecas. Digamos que un programa llama a la función foo()en la biblioteca. La CPU no sabe nada sobre símbolos o llamadas a funciones realmente, solo sabe cómo saltar a una dirección lógica y ejecutar cualquier código que encuentre allí. Hay un par de formas en que podría hacer esto (y cosas similares se aplican cuando una biblioteca accede a sus propios datos globales, etc.):

  1. Podría codificar alguna dirección lógica para llamarlo. Esto requiere que la biblioteca siempre se cargue en la misma dirección lógica exacta. Si dos bibliotecas requieren la misma dirección, la vinculación dinámica falla y no puede iniciar el programa. Las bibliotecas pueden requerir otras bibliotecas, por lo que esto básicamente requiere que cada biblioteca del sistema tenga direcciones lógicas únicas. Sin embargo, es muy rápido si funciona. (Así es como a.out hizo las cosas, y el tipo de configuración que hace la vinculación previa, más o menos).
  2. Podría codificar una dirección lógica falsa y decirle al vinculador dinámico que edite la correcta al cargar la biblioteca. Esto cuesta bastante tiempo al cargar las bibliotecas, pero después de eso es muy rápido.
  3. Podría agregar una capa de indirección: use un registro de CPU para mantener la dirección lógica en la que se carga la biblioteca y luego acceda a todo como un desplazamiento desde ese registro. Esto impone un costo de rendimiento en cada acceso.

Prácticamente ya nadie usa el # 1, al menos no en sistemas de uso general. Mantener esa lista de direcciones lógicas únicas es imposible en los sistemas de 32 bits (no hay suficiente para todos) y una pesadilla administrativa en los sistemas de 64 bits. Sin embargo, la pre-vinculación hace esto por sistema.

El uso de # 2 o # 3 depende de si la biblioteca se creó con la -fPICopción de GCC (código independiente de posición). # 2 es sin, # 3 es con. En general, las bibliotecas se construyen con -fPIC, por lo que # 3 es lo que sucede.

Para obtener más detalles, consulte Cómo escribir bibliotecas compartidas (PDF) de Ulrich Drepper .

Entonces, finalmente, su pregunta puede ser respondida:

  1. Si la biblioteca está construida con -fPIC (como seguramente debería ser), la gran mayoría de las páginas son exactamente iguales para cada proceso que la carga. Sus procesos ay bbien pueden cargar la biblioteca en diferentes direcciones lógicas, pero esas apuntarán a las mismas páginas físicas: se compartirá la memoria. Además, los datos en la RAM coinciden exactamente con lo que hay en el disco, por lo que solo se puede cargar cuando sea necesario por el controlador de fallas de página.
  2. Si la biblioteca se construye sin -fPIC ella, resulta que la mayoría de las páginas de la biblioteca necesitarán ediciones de enlaces y serán diferentes. Por lo tanto, deben ser páginas físicas separadas (ya que contienen datos diferentes). Eso significa que no son compartidos. Las páginas no coinciden con lo que hay en el disco, por lo que no me sorprendería que se cargara toda la biblioteca. Por supuesto, posteriormente se puede cambiar al disco (en el archivo de intercambio).

Puede examinar esto con la pmapherramienta, o directamente marcando varios archivos /proc. Por ejemplo, aquí hay una salida (parcial) de pmap -xdos bcs recién generados diferentes . Tenga en cuenta que las direcciones que muestra pmap son, como típicas, direcciones lógicas:

pmap -x 14739
Address           Kbytes     RSS   Dirty Mode  Mapping
00007f81803ac000     244     176       0 r-x-- libreadline.so.6.2
00007f81803e9000    2048       0       0 ----- libreadline.so.6.2
00007f81805e9000       8       8       8 r---- libreadline.so.6.2
00007f81805eb000      24      24      24 rw--- libreadline.so.6.2


pmap -x 17739
Address           Kbytes     RSS   Dirty Mode  Mapping
00007f784dc77000     244     176       0 r-x-- libreadline.so.6.2
00007f784dcb4000    2048       0       0 ----- libreadline.so.6.2
00007f784deb4000       8       8       8 r---- libreadline.so.6.2
00007f784deb6000      24      24      24 rw--- libreadline.so.6.2

Puede ver que la biblioteca se carga en varias partes y pmap -xle brinda detalles sobre cada una por separado. Notarás que las direcciones lógicas son diferentes entre los dos procesos; razonablemente esperaría que fueran iguales (ya que se está ejecutando el mismo programa y las computadoras generalmente son predecibles así), pero hay una característica de seguridad llamada aleatorización del diseño del espacio de direcciones que los aleatoriza intencionalmente.

Por la diferencia de tamaño (Kbytes) y tamaño de residente (RSS), puede ver que no se ha cargado todo el segmento de la biblioteca. Finalmente, puede ver que para las asignaciones más grandes, sucio es 0, lo que significa que corresponde exactamente a lo que está en el disco.

Puede volver a ejecutarlo pmap -XXy le mostrará, dependiendo de la versión del kernel que esté ejecutando, ya que la salida -XX varía según la versión del kernel, que la primera asignación tiene un Shared_Clean176, que coincide exactamente con el RSS. Sharedmemoria significa que las páginas físicas se comparten entre múltiples procesos, y dado que coincide con el RSS, eso significa que toda la biblioteca que está en la memoria se comparte (consulte la sección Ver también a continuación para obtener una explicación más detallada de lo compartido frente a lo privado):

pmap -XX 17739
         Address Perm   Offset Device   Inode  Size  Rss Pss Shared_Clean Shared_Dirty Private_Clean Private_Dirty Referenced Anonymous AnonHugePages Swap KernelPageSize MMUPageSize Locked                   VmFlagsMapping
    7f784dc77000 r-xp 00000000  fd:00 1837043   244  176  19          176            0             0             0        176         0             0    0              4           4      0       rd ex mr mw me sd  libreadline.so.6.2
    7f784dcb4000 ---p 0003d000  fd:00 1837043  2048    0   0            0            0             0             0          0         0             0    0              4           4      0             mr mw me sd  libreadline.so.6.2
    7f784deb4000 r--p 0003d000  fd:00 1837043     8    8   8            0            0             0             8          8         8             0    0              4           4      0       rd mr mw me ac sd  libreadline.so.6.2
    7f784deb6000 rw-p 0003f000  fd:00 1837043    24   24  24            0            0             0            24         24        24             0    0              4           4      0    rd wr mr mw me ac sd  libreadline.so.6.2


Ver también

derobert
fuente
¿Eso significa que la vinculación previa ya no sirve (y que el -fPICuso ha cambiado por completo hace algún tiempo)?
Hauke ​​Laging
@crisron Gracias por las correcciones. Para su información, Markdown contará para usted: la salida representada de mi repetido 1. fue correcta. Además, hice algunos cambios en lo que hiciste: "dirección de inicio" es jerga técnica, probablemente causé confusión al poner "lógico" en el medio. Lo cambié para deshacerme de la jerga. Además, las páginas son equivalentes a esas direcciones, AFAIK no es posible que esas direcciones sean una página diferente. Lo intenté de nuevo, intercambiando la orden, con suerte eso está más claro.
derobert
maldición, ahora que es una respuesta !!!
Evan Carroll