HOWTO · C

Use la función mmap para escribir en la memoria en C

Este artículo educa sobre la función mmap() y muestra cómo podemos usar la función para escribir en la memoria en C.

En esta página

mmap() crea un mapeo de memoria en un proceso. Puedes usar un mapeo anónimo con permisos de escritura como memoria para datos de C, o un mapeo anónimo compartido para transmitir cambios entre procesos relacionados. Esta guía explica los seis argumentos, cómo detectar errores al crear el mapeo y cómo liberarlo con munmap().

Qué hace mmap() y qué devuelve

Un mapeo conecta un rango del espacio de direcciones virtuales de un proceso con un archivo, un dispositivo o memoria anónima. La memoria anónima no está respaldada por un archivo, por lo que sus cambios no se guardan en uno. Incluye <sys/mman.h> para usar mmap() y sus constantes relacionadas.

La función recibe seis argumentos y, si tiene éxito, devuelve la dirección del nuevo mapeo. Si falla, devuelve MAP_FAILED; compárala con ese valor centinela en lugar de comprobar si es NULL o cero. La llamada también puede establecer errno, lo que puede ayudar a diagnosticar el fallo.

Comprender los parámetros y las banderas de mmap()

Los argumentos, en este orden, son address, length, prot, flags, fd y offset:

  • address es una dirección inicial sugerida. Si pasas NULL, el núcleo elige una dirección adecuada; las aplicaciones suelen hacerlo así.
  • length es la cantidad de bytes que se asignarán al mapeo. El núcleo se encarga de la alineación de páginas del rango mapeado, pero usa de forma coherente la longitud solicitada al liberarlo.
  • prot establece los permisos de acceso. Combina PROT_READ y PROT_WRITE para los datos que debas leer y modificar. Añade PROT_EXEC solo si necesitas memoria ejecutable.
  • flags selecciona el comportamiento de compartición y respaldo. MAP_PRIVATE mantiene las actualizaciones como privadas del proceso; MAP_SHARED hace que sean visibles a través del mapeo compartido. MAP_ANONYMOUS crea memoria sin respaldo de archivo. Estas opciones suelen combinarse con una de las banderas de compartición.
  • fd identifica un archivo para un mapeo respaldado por archivo. En sistemas Unix-like comunes, se ignora el descriptor cuando se usa MAP_ANONYMOUS; los ejemplos suelen pasar -1.
  • offset selecciona el byte inicial de un archivo y, para los mapeos respaldados por archivo, debe ser múltiplo del tamaño de página del sistema. Para un mapeo anónimo, usa cero.

Evita MAP_FIXED salvo que tengas una necesidad concreta y cuidadosamente controlada de ocupar una dirección: puede reemplazar los mapeos existentes en el rango solicitado. Muchos sistemas Unix-like ofrecen estas banderas y fork(), pero no están disponibles en todos los entornos de C.

Escribir en memoria anónima con mmap()

Este ejemplo completo mapea espacio para cinco enteros, escribe y lee sus valores y luego libera exactamente el rango solicitado. Presupone un sistema Unix-like que proporciona MAP_ANONYMOUS. Cuando se ejecuta correctamente, muestra la salida que aparece a continuación.

El código y las etiquetas o salidas visibles de esta sección se mantienen en inglés.

#include <stdio.h>
#include <sys/mman.h>

int main(void) {
    const size_t count = 5;
    const size_t length = count * sizeof(int);
    int *values = mmap(NULL, length, PROT_READ | PROT_WRITE,
                       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (values == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    for (size_t i = 0; i < count; ++i) {
        values[i] = (int)(i * 10);
    }
    for (size_t i = 0; i < count; ++i) {
        printf("[%d] ", values[i]);
    }
    putchar('\n');

    if (munmap(values, length) == -1) {
        perror("munmap");
        return 1;
    }
    return 0;
}
[0] [10] [20] [30] [40]

El mapeo permite escrituras porque sus permisos combinan PROT_READ y PROT_WRITE. MAP_PRIVATE es adecuado en este caso porque ningún otro proceso necesita ver las escrituras. munmap() debe recibir la dirección del mapeo y la misma longitud solicitada; no recibe la cantidad de elementos.

Compartir un mapeo anónimo entre procesos

Un mapeo creado con MAP_SHARED puede transmitir cambios a través de fork(). Así, el proceso hijo y el padre acceden a la misma región mapeada. El ejemplo vacía el búfer de la línea inicial antes de fork() para que no se duplique cuando la salida se redirige. El padre espera a que termine el hijo antes de leer sus actualizaciones. Al igual que el ejemplo anterior, este presupone compatibilidad con MAP_ANONYMOUS y fork() en un sistema Unix-like. La ejecución proporcionada produjo la salida que aparece a continuación. Las etiquetas visibles «Initial values» y «Updated values» permanecen en inglés en el código y la salida.

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

int main(void) {
    const size_t count = 5;
    const size_t length = count * sizeof(int);
    int *values = mmap(NULL, length, PROT_READ | PROT_WRITE,
                       MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (values == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    for (size_t i = 0; i < count; ++i) {
        values[i] = (int)(i + 7);
    }
    printf("Initial values:");
    for (size_t i = 0; i < count; ++i) {
        printf(" %d", values[i]);
    }
    putchar('\n');
    fflush(stdout);

    pid_t child_pid = fork();
    if (child_pid == -1) {
        perror("fork");
        munmap(values, length);
        return 1;
    }
    if (child_pid == 0) {
        for (size_t i = 0; i < count; ++i) {
            values[i] *= 5;
        }
        if (munmap(values, length) == -1) {
            _exit(1);
        }
        _exit(0);
    }

    int status;
    if (waitpid(child_pid, &status, 0) == -1) {
        perror("waitpid");
        munmap(values, length);
        return 1;
    }
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
        fputs("child process failed\n", stderr);
        munmap(values, length);
        return 1;
    }

    printf("Updated values:");
    for (size_t i = 0; i < count; ++i) {
        printf(" %d", values[i]);
    }
    putchar('\n');

    if (munmap(values, length) == -1) {
        perror("munmap");
        return 1;
    }
    return 0;
}
Initial values: 7 8 9 10 11
Updated values: 35 40 45 50 55

El hijo escribe a través del mapeo compartido y termina; cuando concluye waitpid(), el padre lee esos mismos cambios. Un mapeo MAP_PRIVATE no permitiría compartir los cambios de esta manera. Ambos procesos liberan su propio mapeo antes de terminar. En los mapeos respaldados por archivos, MAP_SHARED puede hacer visibles las actualizaciones a través del mapeo, pero guardarlas en el archivo requiere tener en cuenta aspectos adicionales de sincronización y del sistema de archivos.