2009-10-14 6 views
22

Ich habe einen Code, wo ich häufig einen großen Speicherblock kopiere, oft nachdem ich nur sehr kleine Änderungen daran vorgenommen habe.Kann ich in Linux eine Kopie-auf-Schreib-Memcpy erstellen?

Ich habe ein System implementiert, das die Änderungen verfolgt, aber ich dachte, dass es nett sein könnte, wenn möglich, dem OS zu sagen, dass man den Speicher kopieren und nur mit einem machen soll Kopie jener Teile, die sich ändern. Aber während Linux schreibt, zum Beispiel beim fork(), kann ich keinen Weg finden, es zu kontrollieren und es selbst zu tun.

+0

Welche Art von Daten kopieren Sie?Copy-on-Write ist möglicherweise nicht die einzige Lösung. –

Antwort

16

Ihre beste Chance ist wahrscheinlich, mmap() die ursprünglichen Daten in Datei und dann mmap() die gleiche Datei wieder mit MAP_PRIVATE.

+0

Beachten Sie, dass Sie zwei MAP_PRIVATE-Zuordnungen erstellen müssen. Die COW-Semantik erfordert, dass alle Benutzer über COW-Kopien verfügen, ohne dass eine "Masterkopie" verwendet wird. Leider scheint die Datei selbst notwendig zu sein. – caf

+0

Warum? Angenommen, der Master ist "AA", und der Grund für COW ist, dass Sie eine Kopie möchten, die Sie in "AB" ändern können. Es gibt keinen Grund, dass das Original "AA" eine private Zuordnung sein muss, da niemand plant, daran zu schreiben. Es ist nur eine Vorlage. – MSalters

+1

Mein Kommentar basiert auf der Möglichkeit, dass auch die "Original" -Kopie geschrieben werden kann. In diesem Fall wäre es unspezifiziert, ob diese Änderungen in der COW-Kopie reflektiert werden oder nicht. Abgesehen davon ist es schade, dass "mmap" keine inhärente Unterstützung dafür bietet - ich könnte damit umgehen, dass ich "mmap" um Unterstützung für die Duplizierung bestehender Mappings und um zu sehen, wie es geht, unterstütze. – caf

2

Der Kopier-auf-Schreib-Mechanismus, der z. by fork() ist eine Funktion der MMU (Memory Management Unit), die den Speicher-Paging für den Kernel übernimmt. Der Zugriff auf die MMU ist eine privilegierte Operation, d. H. Kann nicht von einer Benutzerbereichsanwendung ausgeführt werden. Mir ist auch nicht bekannt, dass eine API zum Kopieren und Schreiben in den Benutzerbereich exportiert wurde.

(Dann wieder, ich bin nicht gerade ein Experte auf dem Linux-API, so dass andere könnte ich verpasst haben relevante API-Aufrufe hinweisen.)

Edit: Und siehe, steigt MSalters zum Anlass. ;-)

2

Es ist einfacher, Copy-on-Write in einer objektorientierten Sprache wie C++ zu implementieren. Zum Beispiel sind die meisten Containerklassen in Qt Copy-on-Write.

Aber natürlich können Sie das auch in C machen, es ist nur ein bisschen mehr Arbeit. Wenn Sie Ihre Daten einem neuen Datenblock zuweisen möchten, führen Sie keine Kopie durch, sondern kopieren einfach einen Zeiger in einen Wrapper um Ihre Daten. Sie müssen in Ihren Datensätzen den Status der Daten verfolgen. Wenn Sie nun etwas in Ihrem neuen Datenblock ändern, erstellen Sie eine "echte" Kopie und ändern den Status. Sie können natürlich nicht mehr die einfachen Operatoren wie "=" für die Zuweisung verwenden, sondern müssen Funktionen haben (In C++ würden Sie nur den Operator überladen).

Eine robustere Implementierung sollte Referenzzähler anstelle einer einfachen Flagge verwenden, ich überlasse es Ihnen.

A quick and dirty Beispiel: Wenn Sie eine

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

haben Sie assign Funktionen und Getter/Setter selbst implementieren müssen.

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

Sie müssen auch Konstruktoren und Destruktoren schreiben. Ich empfehle wirklich C++ dafür.

+2

Das erzeugt nicht die COW-Semantik, die ich will, wenn das OS es tun würde, würde es nur die (auf Mac OS X mindestens) 4k Seite kopieren, die geändert wurde, den Rest der anderen 10s oder 100s von MB lassen Datenstruktur noch COW. Natürlich konnte und konnte ich implementieren, was ich eigentlich wollte, aber es wäre schön, wenn ich das OS dazu bringen könnte, es für mich zu tun. –

+2

Eine neuere Version des Linux-Kernels kann dies automatisch tun, die Kernel 2.6.32+ haben Code zum Ersetzen von doppelten Seiten durch Copy-on-Write-Links http://lwn.net/Articles/353501/, aber das ksm-Subsystem ist nicht sehr ausgereift und funktioniert bis jetzt anders herum: Die Seiten werden gescannt, nachdem sie kopiert und ersetzt worden sind, wenn sie identisch sind. Wenn Sie möchten, dass es vom Benutzerbereich aus gesteuert wird, sollten Sie sich linux/mm/ksm.c ansehen und die erforderlichen Änderungen vornehmen. – hirschhornsalz

+4

Die veröffentlichte Lösung ist wirklich nicht "CoW" überhaupt, es ist eine Software-Emulation davon, die alle "Schreib" -Operationen durch eine Indirektionsschicht zwingt. Ich glaube, Chris fragte speziell nach einer Lösung auf Speicherebene mit der MMU-Hardware. Und FWIW: Sie brauchen keine neue Version des Linux-Kernels. BSD mmap() unterstützt seit Jahrzehnten MAP_PRIVATE - seit Beginn ist es Teil von POSIX. –

1

Sie sollten in der Lage sein, Ihren eigenen Speicher über/proc/$ PID/mem zu öffnen und dann den interessanten Teil davon mit MAP_PRIVATE an einen anderen Ort zu mappen.

+1

Dies funktioniert nicht, da /proc.../mem mmap nicht unterstützt. Siehe auch [hier] (http://stackoverflow.com/questions/5216326/mmap-on-proc-pid-mem). – coltox

0

Hier ist ein funktionierendes Beispiel:

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

Ich bin ein wenig unsicher, ob ich brauche den auf Kommentar Abschnitt, für Sicherheit zu ermöglichen.

+0

Bei mir stürzt dies beim zweiten Aufruf von mmap ab. Es würde mich interessieren zu wissen, ob Sie diesen Code später oder eine verbesserte Version davon verwenden, da ich eine ähnliche Anforderung für das Kopieren-in-Schreiben in C-Code habe? (P. S. Beachten Sie, dass der Aufruf zum Aufheben der Verbindung falsch aussieht (unlink nimmt eine Zeichenfolge und keinen Dateideskriptor)). –