2009-10-14 10 views
22

Ho un codice in cui copio spesso un grande blocco di memoria, spesso dopo aver apportato solo piccole modifiche.Posso fare una memcpy copy-on-write in Linux?

Ho implementato un sistema che tiene traccia delle modifiche, ma ho pensato che sarebbe bello, se possibile, dire al sistema operativo di fare un 'copia-on-write' della memoria, e lasciare che si occupi solo di fare un copia di quelle parti che cambiano. Comunque mentre Linux fa il copy-on-write, per esempio quando fork() ing, non riesco a trovare un modo per controllarlo e farlo da solo.

+0

Che tipo di dati stai copiando?Copy-on-write potrebbe non essere l'unica soluzione. –

risposta

16

Probabilmente la tua migliore possibilità è quella di mmap() i dati originali da archiviare, quindi mmap() lo stesso file utilizzando nuovamente MAP_PRIVATE.

+0

Si noti che è necessario creare due mapping 'MAP_PRIVATE' - La semantica COW richiede che tutti gli utenti abbiano copie COW, con nessuno che usi una copia" master ". Sfortunatamente il file stesso sembra essere necessario. – caf

+0

Perché? Supponiamo che il master sia 'AA', e il motivo per COW è che vuoi una copia che puoi cambiare in' AB'. Non c'è motivo per cui l'originale 'AA' debba essere una mappatura privata, dato che nessuno ha intenzione di scrivere su di esso. È semplicemente un modello. – MSalters

+1

Il mio commento era basato sulla possibilità che la copia "originale" possa essere scritta anche in quel caso, nel qual caso non sarebbe specificata se tali modifiche si riflettano nella copia COW o meno. Per inciso, è un peccato che 'mmap' non fornisca un supporto intrinseco per questo - potrei giocare con l'aggiunta del supporto a' mmap' per duplicare i mapping esistenti e vedere come va. – caf

2

Il meccanismo di copia su scrittura impiegato, ad es. by fork() è una funzionalità della MMU (Memory Management Unit), che gestisce il paging della memoria per il kernel. L'accesso alla MMU è un'operazione privilegiata, ovvero non può essere eseguita da un'applicazione dello spazio utente. Non sono a conoscenza di alcuna API copy-on-write esportata nello spazio utente.

(Poi di nuovo, io non sono esattamente un guru sulle API di Linux, in modo che altri potrebbero notare le chiamate alle API importanti che ho perso.)

Edit: Ed ecco, MSalters aumenta all'occasione. ;-)

4

A seconda di cosa esattamente si sta copiando, un persistent data structure potrebbe essere una soluzione per il tuo problema.

2

È più facile implementare la copia su scrittura in un linguaggio orientato agli oggetti, come C++. Ad esempio, la maggior parte delle classi contenitore in Qt sono copy-on-write.

Ma se lo si può fare anche in C, è solo un po 'più di lavoro. Quando si desidera assegnare i dati a un nuovo blocco di dati, non si esegue una copia, ma si copia semplicemente un puntatore in un intreccio di wrapper attorno ai dati. È necessario tenere traccia dei blocchi di dati dello stato dei dati. Se ora modifichi qualcosa nel tuo nuovo blocco dati, fai una copia "reale" e cambia lo stato. Ovviamente non è più possibile utilizzare gli operatori semplici come "=" per l'assegnazione, ma è necessario avere funzioni (in C++ si farebbe semplicemente un sovraccarico dell'operatore).

Un'implementazione più robusta dovrebbe utilizzare i contatori di riferimento invece di una semplice bandiera, lascio a voi.

Un esempio veloce e sporco: Se si dispone di un

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

è necessario implementare le funzioni di assegnare e getter/setter te stesso.

// 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; 
} 

È necessario scrivere anche costruttori e distruttori. Consiglio vivamente C++ per questo.

+2

Questo non genera la semantica COW che voglio, se il sistema operativo lo facesse, coperebbe solo la pagina (su Mac OS X almeno) 4k che è stata cambiata, lasciando il resto degli altri (altri 10 o 100 di MB) struttura dei dati ancora MUCCA. Ovviamente, potrei e ho implementato ciò che effettivamente desidero, ma sarebbe bello se riuscissi a far funzionare il sistema operativo per me. –

+2

Una versione più recente del kernel linux potrebbe farlo automaticamente, i kernel 2.6.32+ hanno il codice per sostituire le pagine duplicate con i link copy-on-write http://lwn.net/Articles/353501/, tuttavia il sottosistema ksm è non molto maturo e fino ad ora funziona in senso inverso: le pagine vengono scansionate dopo essere state copiate e sostituite se identiche. Se si desidera controllarlo dallo spazio utente, si potrebbe voler guardare linux/mm/ksm.c e apportare le modifiche necessarie. – hirschhornsalz

+4

La soluzione postata non è affatto "CoW", è una sua emulazione software che forza tutte le operazioni di "scrittura" attraverso uno strato di riferimento indiretto. Credo che Chris stesse chiedendo specificamente una soluzione a livello di memoria utilizzando l'hardware MMU. E FWIW: non hai bisogno di una nuova versione del kernel Linux. BSD mmap() ha supportato MAP_PRIVATE da decenni - fa parte di POSIX sin dall'inizio. –

1

Dovresti essere in grado di aprire la tua memoria tramite/proc/$ PID/mem e poi mmap() la parte interessante di esso con MAP_PRIVATE in qualche altro posto.

+1

Questo non funzionerà come /proc.../mem non supporta mmap. Vedi anche [qui] (http://stackoverflow.com/questions/5216326/mmap-on-proc-pid-mem). – coltox

0

Ecco un esempio di lavoro:

#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; 
} 

Sono un po 'sicuri di se ho bisogno di abilitare la sezione commentata, per la sicurezza.

+0

Per me questo si blocca durante la seconda chiamata a mmap. Sarei interessato a sapere se in seguito hai utilizzato questo codice o una versione migliorata di esso, in quanto ho un requisito simile per la copia in scrittura nel codice C? (P.S. nota che la chiamata allo scollegamento sembra errata (lo scollegamento prende una stringa, non un descrittore di file)). –