2009-10-14 15 views
22

Mam pewien kod, w którym często kopiuję duży blok pamięci, często po wprowadzeniu do niego bardzo niewielkich zmian.Czy mogę zrobić memcpy kopiowania przy pisaniu w systemie Linux?

Wdrożyłem system, który śledzi zmiany, ale pomyślałem, że może być miło, jeśli to możliwe, powiedzieć systemowi operacyjnemu, aby wykonał "kopię na piśmie" pamięci, i pozwól sobie poradzić tylko z robieniem kopia tych części, które się zmieniają. Jednak podczas gdy Linux robi kopiowanie przy zapisie, na przykład przy użyciu fork(), nie mogę znaleźć sposobu na kontrolowanie go i robienie tego samemu.

+0

Jakie dane kopiujesz?Kopiowanie przy zapisie może nie być jedynym rozwiązaniem. –

Odpowiedz

16

Twoja najlepsza szansa to prawdopodobnie mmap() oryginalnych danych do pliku, a następnie mmap() ten sam plik ponownie przy użyciu MAP_PRIVATE.

+0

Należy utworzyć dwie mapowania "MAP_PRIVATE" - semantyka COW wymaga, aby wszyscy użytkownicy mieli kopie COW, a nikt nie używał kopii "master". Niestety sam plik wydaje się konieczny. – caf

+0

Dlaczego? Załóżmy, że wzorcem jest "AA", a przyczyną COW jest to, że chcesz kopię, którą możesz zmienić na "AB". Nie ma powodu, dla którego oryginalny "AA" musiałby być prywatnym mapowaniem, ponieważ nikt nie zamierza na niego pisać. To tylko szablon. – MSalters

+1

Mój komentarz był oparty na możliwości, że "oryginalna" kopia może być również zapisana, w którym to przypadku nie byłaby bliżej sprecyzowana, gdyby zmiany te znalazły odzwierciedlenie w kopii COW, czy też nie. Na marginesie, szkoda, że ​​'mmap' nie zapewnia nieodłącznego wsparcia dla tego - mogę bawić się dodając wsparcie dla' mmap' dla duplikowania istniejących mapowań i zobaczenia jak idzie. – caf

2

Mechanizm kopiowania przy zapisie, np. przez fork() jest funkcją MMU (Memory Management Unit), która obsługuje stronicowanie pamięci dla jądra. Dostęp do MMU jest operacją uprzywilejowaną, tj. Nie może być wykonana przez aplikację obszaru użytkownika. Nie jestem również świadomy, że API "kopiuj na piśmie" eksportował do przestrzeni użytkownika.

(Potem znowu, nie jestem dokładnie guru na API Linux, więc inni mogą wskazać odpowiednich wywołań API tęskniłem.)

Edit: A oto MSalters wzrasta do okazji. ;-)

2

Łatwiej jest wdrożyć metodę kopiowania przy zapisie w języku obiektowym, takim jak C++. Na przykład większość klas kontenera w Qt to copy-on-write.

Ale jeśli oczywiście możesz to zrobić w C, to tylko trochę więcej pracy. Kiedy chcesz przypisać dane do nowego bloku danych, nie wykonujesz kopii, zamiast tego po prostu kopiujesz wskaźnik w obrysie otaczającym twoje dane. Musisz śledzić w swoich blokach danych status danych. Jeśli teraz zmienisz coś w swoim nowym bloku danych, zrobisz "prawdziwą" kopię i zmienisz status. Oczywiście nie można już używać prostych operatorów takich jak "=" do przypisania, zamiast tego trzeba mieć funkcje (w C++ wystarczy przeciążenie operatora).

Bardziej niezawodna implementacja powinna wykorzystywać liczniki odwołań zamiast prostej flagi, pozostawiam to Tobie.

szybkie i brudne przykład: Jeśli masz

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

trzeba wdrożyć przypisać funkcje i pobierające/ustawiające się.

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

Musisz napisać konstruktorów i destruktorów, jak również. Naprawdę polecam C++ do tego.

+2

To nie generuje semantyki COW, którą chcę, jeśli system operacyjny to zrobiłby, to skopiowałby tylko (na Mac OS X co najmniej) 4k stronę, która została zmieniona, pozostawiając resztę (pozostałe 10 lub 100 MB) struktura danych wciąż COW. Oczywiście, mogłem i mogłem wdrożyć to, czego naprawdę chcę, ale byłoby miło, gdybym mógł sprawić, żeby system operacyjny zrobił to za mnie. –

+2

Nowsza wersja jądra może zrobić to automagicznie, jądra 2.6.32+ mają kod zastępujący duplikaty stron linią copy-on-write http://lwn.net/Articles/353501/, ale podsystem ksm jest nie bardzo dojrzały i do tej pory działa odwrotnie: strony są skanowane po ich skopiowaniu i zastąpieniu, jeśli są identyczne. Jeśli chcesz, aby sterował on z przestrzeni użytkownika, możesz spojrzeć na linux/mm/ksm.c i wprowadzić potrzebne zmiany. – hirschhornsalz

+4

Zamieszczone rozwiązanie w rzeczywistości nie jest "WKRÓTCE", jest to jego emulacja programowa, która wymusza wszystkie operacje "zapisu" przez warstwę dwukierunkową. Wierzę, że Chris pytał specjalnie o rozwiązanie na poziomie pamięci przy użyciu sprzętu MMU. A FWIW: nie potrzebujesz nowej wersji jądra Linux. BSD mmap() wspiera MAP_PRIVATE od dziesięcioleci - jest częścią POSIX od samego początku. –

1

Powinieneś być w stanie otworzyć własną pamięć poprzez/proc/$ PID/mem, a następnie mmap() interesującą jej część z MAP_PRIVATE w innym miejscu.

+1

To nie zadziała, ponieważ /proc.../mem nie obsługuje mmap. Zobacz także [tutaj] (http://stackoverflow.com/questions/5216326/mmap-on-proc-pid-mem). – coltox

0

Oto przykład praca:

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

jestem trochę niepewny, czy muszę włączyć wykomentowane sekcji, dla bezpieczeństwa.

+0

Dla mnie to zawiesza się podczas drugiego połączenia z mmap. Byłbym zainteresowany, gdybyś później użył tego kodu lub jego ulepszonej wersji, ponieważ mam podobny wymóg dla kopiowania w zapisie w kodzie C? (P.S. zauważ, że wywołanie, aby odłączyć wygląda źle (odłączyć pobiera ciąg, a nie deskryptor pliku)). –

Powiązane problemy