2014-11-27 25 views
7

Chcę wiedzieć, jak kopiowanie przy zapisie dzieje się w fork().Jak działa kopiowanie przy zapisie w fork()?

Zakładając, że posiada proces A, która ma dynamiczny int tablicy:

int *array = malloc(1000000*sizeof(int)); 

elementów w macierzy jest ustalony na pewną znaczące wartości. Następnie używamy widelca(), aby utworzyć procesu potomnego, a mianowicie B. B będzie iterację tablicy i zrobić kilka obliczeń:

for(a in array){ 
    a = a+1; 
} 
  1. znam B nie będzie natychmiast skopiować całą tablicę, ale kiedy czy dziecko B alokuje pamięć dla tablicy? podczas fork()?
  2. Czy przydziela całą tablicę wszystkie naraz lub tylko jedną liczbę całkowitą dla a = a+1?
  3. a = a+1; jak to się dzieje? Czy B odczytuje dane z A i zapisuje nowe dane do swojej własnej tablicy?

Napisałem kod, aby sprawdzić, jak działa COW. My środowiska: ubuntu 14,04, gcc4.8.2

#include <stdlib.h> 
#include <stdio.h> 
#include <sys/sysinfo.h> 

void printMemStat(){ 
    struct sysinfo si; 
    sysinfo(&si); 
    printf("===\n"); 
    printf("Total: %llu\n", si.totalram); 
    printf("Free: %llu\n", si.freeram); 
} 

int main(){ 
    long len = 200000000; 
    long *array = malloc(len*sizeof(long)); 
    long i = 0; 
    for(; i<len; i++){ 
     array[i] = i; 
    } 

    printMemStat(); 
    if(fork()==0){ 
     /*child*/ 
     printMemStat(); 

     i = 0; 
     for(; i<len/2; i++){ 
      array[i] = i+1; 
     } 

     printMemStat(); 

     i = 0; 
     for(; i<len; i++){ 
      array[i] = i+1; 
     } 

     printMemStat(); 

    }else{ 
     /*parent*/ 
     int times=10; 
     while(times-- > 0){ 
      sleep(1); 
     } 
    } 
    return 0; 
} 

Po widełek(), to proces potomny modyfikuje połowę liczby w tablicy, a następnie modyfikuje się całą tablicę. Wyjściami są:

=== 
Total: 16694571008 
Free: 2129162240 
=== 
Total: 16694571008 
Free: 2126106624 
=== 
Total: 16694571008 
Free: 1325101056 
=== 
Total: 16694571008 
Free: 533794816 

Wygląda na to, że tablica nie została przydzielona jako całość. Gdybym nieznacznie zmienić pierwszy etap modyfikacji do:

i = 0; 
for(; i<len/2; i++){ 
    array[i*2] = i+1; 
} 

Wyjścia będą:

=== 
Total: 16694571008 
Free: 2129924096 
=== 
Total: 16694571008 
Free: 2126868480 
=== 
Total: 16694571008 
Free: 526987264 
=== 
Total: 16694571008 
Free: 526987264 
+1

Dziecko nie "przydziela" niczego. Dziecko zaczyna się jako dokładna, kompletna kopia procesu nadrzędnego i po prostu kontynuuje wykonywanie od tego miejsca. –

+0

A, A i B udostępniają tablicę? –

+0

@KerrekSB: Ale otrzyma oddzielną pamięć procesową, która powinna być kopiowana przy zapisie. – Thilo

Odpowiedz

7

zależy od systemu operacyjnego, architektury sprzętowej i libc. Ale tak, w przypadku ostatniego Linuxa z MMU, fork(2) będzie działać z kopiowaniem przy zapisie. Będzie tylko (przydzielać i) kopiować kilka struktur systemowych i tabelę stron, ale strony sterty faktycznie wskazują na te z rodzica, dopóki nie zostaną zapisane.

Większa kontrola nad tym może być wykonywana za pomocą wywołania clone(2). I vfork(2) jest specjalnym wariantem, który nie oczekuje użycia stron. Jest to zazwyczaj używane przed exec().

Co do przydziału: malloc() ma meta informację o żądanych blokach pamięci (adres i rozmiar), a zmienna C jest wskaźnikiem (zarówno w stosie pamięci procesowej, jak i stosach). Te dwa wyglądają tak samo dla potomka (te same wartości, ponieważ ta sama strona pamięci bazowej widziana w przestrzeni adresowej obu procesów). Z punktu widzenia programu C tablica jest już przydzielona, ​​a zmienna zainicjowana po powstaniu procesu. Strony bazowe pamięci wskazują jednak na oryginalne fizyczne procesy nadrzędne, więc nie są potrzebne dodatkowe strony pamięci, dopóki nie zostaną zmodyfikowane.

Jeśli potomek przydziela nową tablicę, zależy to od tego, czy pasuje do już istniejących stron sterty, czy też należy zwiększyć brk procesu. W obu przypadkach tylko zmodyfikowane strony zostaną skopiowane, a nowe strony zostaną przydzielone tylko dla dziecka.

Oznacza to również, że pamięć fizyczna może się wyczerpać po funkcji malloc().(Co jest złe, ponieważ program nie może sprawdzić kodu powrotu błędu "operacji w losowym wierszu kodu"). Niektóre systemy operacyjne nie dopuszczają takiej formy nadmiernego zatwierdzania: więc jeśli rozwiniesz proces, nie będzie on przydzielał stron, ale wymaga, aby były dostępne w danym momencie (rodzaj rezerw) na wszelki wypadek. W systemie Linux jest to .

+0

Ale kiedy proces potomny przydzieli pamięć dla tablicy?(Przydziel pamięć tylko nie kopiuj) –

+1

@MinFu Zależy od tego, co masz na myśli mówiąc "pamięć" i "przydzielić" :) (dodałem dodatkowe wyjaśnienie do odpowiedzi). – eckes

+0

Co się stanie, jeśli dziecko odłączy je od rodzica? Domyślam się, że wszystkie strony rodziców są kopiowane do przestrzeni podrzędnej, zanim dziecko zostanie odłączone. Jeśli nie, co się dzieje, gdy rodzic umiera/istnieje po rozwidleniu? – Siddu

3

Niektóre systemy mają wywołanie systemowe vfork(), który był pierwotnie zaprojektowany jako dolnym napowietrznych wersji fork(). Ponieważ fork() wymagało skopiowania całej przestrzeni adresowej procesu, i dlatego było dość kosztowne, wprowadzono vfork() funkcję (w 3.0BSD).

Jednak od vfork() został wprowadzony, realizacja z fork() poprawiła się drastycznie, zwłaszcza z wprowadzeniem „copy-on-write”, gdzie kopiowanie przestrzeni adresowej procesu jest przejrzyście sfałszowane przez zezwalając obu procesom na odwoływanie się do tej samej pamięci fizycznej, aż do momentu, gdy któryś z nich zmodyfikuje ją. W dużym stopniu usuwa to uzasadnienie dla vfork();, w rzeczywistości duża część systemów nie ma obecnie pełnej funkcjonalności oryginalnej wersji vfork(). Ze względu na kompatybilność może jednak wciąż istnieć połączenie vfork(), które po prostu wywołuje fork() bez próby przekonania wszystkich semantyki vfork().

W związku z tym bardzo nierozsądne jest korzystanie z dowolnej zróżnic między fork() i vfork(). Rzeczywiście, prawdopodobnie nierozsądnie jest używać vfork(), chyba że wiesz dokładnie, dlaczego chcesz.

Podstawowa różnica między nimi polega na tym, że po utworzeniu nowego procesu za pomocą vfork() proces nadrzędny zostaje tymczasowo zawieszony, a proces potomny może pożyczyć przestrzeń adresową nadrzędnego. Ten dziwny stan trwa do momentu, w którym proces potomny albo zakończy działanie, albo zadzwoni pod numer execve(), w którym to momencie proces nadrzędny będzie kontynuowany.

Oznacza to, że proces potomny vfork() musi być ostrożny, aby uniknąć nieoczekiwanej modyfikacji zmiennych procesu nadrzędnego. W szczególności proces dziecko nie może powrócić z funkcji zawierającego wezwanie vfork() i nie musi wywołać exit() (jeśli musi wyjść, należy użyć _exit(); rzeczywiście, jest to również prawdziwe dla dziecka normalnego fork()).