Necesito usar una memoria compartida entre procesos y encontré un código de muestra aquí. En primer lugar, necesito aprender a crear un bloque de memoria compartida y almacenar una cadena en él. Para hacer eso utilicé el siguiente código:

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>

void* create_shared_memory(size_t size) {
  // Our memory buffer will be readable and writable:
  int protection = PROT_READ | PROT_WRITE;

  // The buffer will be shared (meaning other processes can access it), but
  // anonymous (meaning third-party processes cannot obtain an address for it),
  // so only this process and its children will be able to use it:
  int visibility = MAP_ANONYMOUS | MAP_SHARED;

  // The remaining parameters to `mmap()` are not important for this use case,
  // but the manpage for `mmap` explains their purpose.
  return mmap(NULL, size, protection, visibility, 0, 0);
}



int main() {
  char msg[] = "hello world!";

  void* shmem = create_shared_memory(1);
  printf("sizeof shmem: %lu\n", sizeof(shmem));
  printf("sizeof msg: %lu\n", sizeof(msg));
  memcpy(shmem, msg, sizeof(msg));
  printf("message: %s\n", shmem);

}

Salida:

sizeof shmem: 8
sizeof msg: 13
message: hello world!

En la función principal, estoy creando un bloque de memoria compartida de 1 byte (shmem) e intento almacenar información de 13 bytes (char msg[]) en él . Cuando imprimo el shmem, imprime el mensaje completo. Lo espero, imprime solo el mensaje de 1 byte en este caso es solo "h". O podría dar un error sobre el tamaño de la memoria cuando se compila.

¿La pregunta es que me falta algo aquí? ¿O hay un problema de implementación? ¿memcpy se superpone aquí? Agradezco cualquier breve explicación.

Gracias de antemano.

1
Ersel Er 12 nov. 2017 a las 17:12

2 respuestas

La mejor respuesta
  1. En printf("message: %s\n", shmem);, el especificador %s dice que se imprima la "cadena" que comienza en shmem. Para este propósito, una cadena es una secuencia de caracteres que termina con el carácter nulo. Entonces, printf imprime todos los bytes que encuentra en shmem hasta el carácter nulo. Para limitarlo a un máximo de un carácter, puede utilizar %.1s en su lugar, o puede imprimir explícitamente un carácter con printf("message: %c\n", * (char *) shmem);.

  2. Cuando asigna memoria con mmap, el sistema trabaja con memoria en unidades de páginas. El tamaño de una página varía de un sistema a otro, pero normalmente es algo así como 512 o 4096 bytes, no 1. La especificación estándar para mmap solo garantiza que se proporcione la cantidad de bytes solicitada. Puede haber bytes adicionales accesibles más allá de esto, pero no debe confiar en que estén disponibles. (Incluso si parece que están disponibles momentáneamente, es posible que el sistema no los guarde en el disco cuando su programa se haya eliminado temporalmente de la memoria, por lo que no se restaurarán cuando su programa vuelva a la memoria para continuar ejecutándose).

  3. sizeof(shmem) proporciona el tamaño de shmem, que es un puntero. Por tanto, proporciona el tamaño del puntero, que suele ser de cuatro u ocho bytes en los sistemas modernos. No proporciona el tamaño de la cosa a la que apunta shmem.

  4. En contraste, en sizeof(msg), msg es una matriz, no un puntero, por lo que sizeof(msg) proporciona el tamaño de la matriz, como probablemente pretenda.

  5. memcpy(shmem, msg, sizeof(msg)); copia 13 bytes (el tamaño de su msg) en shmem. Esos trece bytes son "¡hola mundo!" y un carácter nulo (valor 0) al final. memcpy no tiene ninguna forma de saber cuánto dura el origen o el destino, excepto por el parámetro de longitud que pasa. Entonces copia sizeof(msg) bytes. No se limita al tamaño de la memoria señalada por shmem. Es su trabajo pasar la longitud correcta.

Para responder a su pregunta sobre qué sucede si usa más bytes de los que proporciona mmap, el comportamiento no está definido. Si va más allá del límite de una página, lo más probable es que su programa se bloquee porque la memoria más allá de esa dirección no está asignada. Pero puede escribir bytes en un lugar de su memoria que no deseaba, y eso puede causar que sucedan una variedad de cosas, porque puede dañar el código o los datos que su programa necesita para ejecutarse correctamente.

En este caso, no escribió más allá de la memoria asignada. Solicitó 13 bytes y probablemente le dieron 4096 (o lo que sea que sea una página en su sistema). Luego, copió esos 13 bytes en el búfer y los imprimió. Así que todo "funcionó".

6
Eric Postpischil 12 nov. 2017 a las 15:01

Su código viola el contrato de mmap() al escribir más de 1 byte en una asignación de memoria solicitada con un tamaño de 1 byte.

Sin embargo, como ha descubierto, a veces puede funcionar en algunos sistemas. Esto puede deberse a que el tamaño de una página (en la asignación de memoria) es, por ejemplo, 4 KB. Entonces, tal vez el mapeo sea mayor de lo solicitado. Aún así, no tiene derecho a usarlo como lo ha hecho.

Entonces, deja de hacer eso.


Preguntó si debería ser un error de compilación. La respuesta es no: el compilador no tiene casos especiales para cada rutina de biblioteca como mmap(). No sabe que el parámetro size de mmap() significa que el puntero devuelto solo es válido para esa cantidad de bytes. Un analizador estático posiblemente podría resolver esto, pero no sería típico que un compilador lo hiciera.

0
John Zwinck 12 nov. 2017 a las 21:47