2009-10-14 21 views
22

Tengo un código donde copio con frecuencia un gran bloque de memoria, a menudo después de hacer solo pequeños cambios en él.¿Puedo hacer una memcpy copy-on-write en Linux?

He implementado un sistema que realiza un seguimiento de los cambios, pero pensé que sería bueno, si era posible, decirle al sistema operativo que hiciera una "copia en escritura" de la memoria, y dejar que se ocupe solo de hacer una copia de esas partes que cambian. Sin embargo, mientras que Linux hace copy-on-write, por ejemplo cuando fork() ing, no puedo encontrar una manera de controlarlo y hacerlo yo mismo.

+0

¿Qué tipo de datos está copiando?Copy-on-write podría no ser la única solución. –

Respuesta

16

Su mejor oportunidad es probablemente mmap() los datos originales en el archivo, y luego mmap() el mismo archivo otra vez usando MAP_PRIVATE.

+0

Tenga en cuenta que debe crear dos asignaciones 'MAP_PRIVATE': la semántica de COW requiere que todos los usuarios tengan copias COW, sin que nadie use una copia" maestra ". Lamentablemente, el archivo en sí parece ser necesario. – caf

+0

¿Por qué? Supongamos que el maestro es 'AA', y el motivo de COW es que quiere una copia que puede cambiar a' AB'. No hay ninguna razón por la cual el 'AA' original deba ser un mapeo privado, ya que nadie planea escribir en él. Es simplemente una plantilla. – MSalters

+1

Mi comentario se basó en la posibilidad de que la copia "original" también se pueda escribir, en cuyo caso no se especificará si esos cambios se reflejan o no en la copia COW. Por otro lado, es una pena que 'mmap' no proporcione soporte inherente para esto; podría jugar con agregar soporte a' mmap' para duplicar mapeos existentes y ver cómo funciona. – caf

2

El mecanismo de copiado en escritura empleado, p. by fork() es una característica de la MMU (Unidad de gestión de memoria), que maneja la búsqueda de memoria para el kernel. El acceso a la MMU es una operación privilegiada, es decir, no puede ser realizada por una aplicación de espacio de usuario. Tampoco conozco ninguna API de copia en escritura exportada al espacio de usuario.

(Por otra parte, no soy exactamente un gurú en la API de Linux, por lo que otros podrían señalar llamadas a la API relevantes que he pasado por alto.)

Editar: Y he aquí, MSalters levanta a la ocasión. ;-)

2

Es más fácil implementar copy-on-write en un lenguaje orientado a objetos, como C++. Por ejemplo, la mayoría de las clases de contenedor en Qt son copy-on-write.

Pero si por supuesto también puedes hacer eso en C, es solo un poco más de trabajo. Cuando quiere asignar sus datos a un nuevo bloque de datos, no hace una copia, en su lugar solo copia un puntero en un borde de envoltura alrededor de sus datos. Debe realizar un seguimiento en sus bloques de datos del estado de los datos. Si ahora cambias algo en tu nuevo bloque de datos, haces una copia "real" y cambias el estado. No puede, por supuesto, dejar de utilizar los operadores simples como "=" para la asignación, en lugar de eso, debe tener funciones (en C++ simplemente haría sobrecarga del operador).

Una implementación más robusta debería usar contadores de referencia en lugar de una simple bandera, lo dejo a usted.

Un ejemplo rápido y sucio: Si usted tiene un

struct big { 
//lots of data 
    int data[BIG_NUMBER]; 
} 

usted tiene que poner en práctica las funciones y asignar getters/setters mismo.

// assume you want to implent cow for a struct big of some kind 
// now instead of 
struct big a, b; 
a = b; 
a.data[12345] = 6789; 

// you need to use 
struct cow_big a,b; 
assign(&a, b); //only pointers get copied 
set_some_data(a, 12345, 6789); // now the stuff gets really copied 


//the basic implementation could look like 
struct cow_big { 
    struct big *data; 
    int needs_copy; 
} 

// shallow copy, only sets a pointer. 
void assign(struct cow_big* dst, struct cow_big src) { 
    dst->data = src.data; 
    dst->needs_copy = true; 
} 

// change some data in struct big. if it hasn't made a deep copy yet, do it here. 
void set_some_data(struct cow_big* dst, int index, int data } { 
    if (dst->needs_copy) { 
     struct big* src = dst->data; 
     dst->data = malloc(sizeof(big)); 
     *(dst->data) = src->data; // now here is the deep copy 
     dst->needs_copy = false; 
    } 
    dst->data[index] = data; 
} 

Necesita escribir constructores y destructores también. Realmente recomiendo C++ para esto.

+2

Eso no genera la semántica de COW que quiero, si el sistema operativo lo hiciera, solo copiaría la (al menos Mac OS X) la página de 4k que se modificó, dejando el resto (otras 10 o 100 de MB) la estructura de datos todavía es COW. Por supuesto, pude, y he implementado lo que realmente quiero, pero estaría bien si pudiera conseguir que el sistema operativo lo hiciera por mí. –

+2

Una versión más nueva del kernel de Linux puede hacerlo automágicamente, los núcleos 2.6.32+ tienen código para reemplazar páginas duplicadas con enlaces de copia en escritura http://lwn.net/Articles/353501/, sin embargo, el subsistema ksm es no muy maduro y hasta ahora funciona al revés: las páginas se escanean después de haber sido copiadas y reemplazadas si son idénticas. Si desea que lo controle desde el espacio de usuario, le recomendamos que consulte linux/mm/ksm.c y realice los cambios que necesite. – hirschhornsalz

+4

La solución publicada realmente no es "CoW" en absoluto, es una emulación de software que fuerza todas las operaciones de "escritura" a través de una capa indirecta. Creo que Chris estaba pidiendo específicamente una solución de nivel de memoria utilizando el hardware MMU. Y FWIW: no necesita una nueva versión del kernel de Linux. BSD mmap() ha admitido MAP_PRIVATE durante décadas: ha sido parte de POSIX desde el principio. –

1

Debería poder abrir su propia memoria a través de/proc/$ PID/mem y luego mmap() la parte interesante de ella con MAP_PRIVATE a algún otro lugar.

+1

Esto no funcionará ya que /proc.../mem no admite mmap. Ver también [aquí] (http://stackoverflow.com/questions/5216326/mmap-on-proc-pid-mem). – coltox

0

Aquí está un ejemplo de trabajo:

#include <stdio.h> 
#include <stdlib.h> 
#include <sys/mman.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 

#define SIZE 4096 

int main(void) { 
    int fd = shm_open("/tmpmem", O_RDWR | O_CREAT, 0666); 
    int r = ftruncate(fd, SIZE); 
    printf("fd: %i, r: %i\n", fd, r); 
    char *buf = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, 
     MAP_SHARED, fd, 0); 
    printf("debug 0\n"); 
    buf[SIZE - 2] = 41; 
    buf[SIZE - 1] = 42; 
    printf("debug 1\n"); 

    // don't know why this is needed, or working 
    //r = mmap(buf, SIZE, PROT_READ | PROT_WRITE, 
    // MAP_FIXED, fd, 0); 
    //printf("r: %i\n", r); 

    char *buf2 = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, 
    MAP_PRIVATE, fd, 0); 
    printf("buf2: %i\n", buf2); 
    buf2[SIZE - 1] = 43; 
    buf[SIZE - 2] = 40; 
    printf("buf[-2]: %i, buf[-1]: %i, buf2[-2]: %i, buf2[-1]: %i\n", 
     buf[SIZE - 2], 
     buf[SIZE - 1], 
     buf2[SIZE - 2], 
     buf2[SIZE - 1]); 

    unlink(fd); 
    return EXIT_SUCCESS; 
} 

estoy un poco inseguro de si necesito habilitar la comentada, por seguridad.

+0

Para mí, esto falla en la segunda llamada a mmap. Me interesaría saber si posteriormente usó este código, o una versión mejorada de él, ya que tengo un requisito similar para copiar y escribir en el código C. (P.S. tenga en cuenta que la llamada para desvincular se ve mal (desvincular toma una cadena, no un descriptor de archivo)). –