widelec (wywołanie systemowe)
W informatyce , szczególnie w kontekście systemu operacyjnego Unix i jego odpowiedników , rozwidlenie jest operacją, za pomocą której proces tworzy swoją kopię. Jest to interfejs wymagany do zachowania zgodności ze POSIX i Single UNIX . Zwykle jest implementowany jako opakowanie standardowej biblioteki C (libc) dla rozwidlenia, klonowania lub innych wywołań systemowych jądra . Fork jest podstawową metodą tworzenia procesów w systemach operacyjnych typu Unix.
Przegląd
W wielozadaniowych systemach operacyjnych procesy (uruchamiające programy) potrzebują sposobu na tworzenie nowych procesów, np. uruchamianie innych programów. Fork i jego warianty są zazwyczaj jedynym sposobem na zrobienie tego w systemach uniksopodobnych. Aby proces mógł rozpocząć wykonywanie innego programu, najpierw tworzy swoją kopię. Następnie kopia, zwana „ procesem potomnym ”, wywołuje wywołanie systemowe exec , aby nałożyć się na inny program: przestaje wykonywać swój poprzedni program na korzyść innego.
Operacja rozwidlenia tworzy oddzielną przestrzeń adresową dla dziecka. Proces potomny ma dokładną kopię wszystkich segmentów pamięci procesu nadrzędnego. W nowoczesnych wariantach UNIX, które są zgodne z pamięci wirtualnej z SunOS-4.0, zaimplementowana jest semantyka kopiowania przy zapisie , a pamięć fizyczna nie musi być faktycznie kopiowana. Zamiast tego strony pamięci wirtualnej w obu procesach mogą odnosić się do tych samych stron pamięci fizycznej dopóki któryś z nich nie napisze na takiej stronie: wtedy jest kopiowana. Ta optymalizacja jest ważna w typowym przypadku, gdy fork jest używany w połączeniu z exec do wykonania nowego programu: zazwyczaj proces potomny wykonuje tylko niewielki zestaw akcji, zanim przestanie wykonywać swój program na rzecz programu, który ma zostać uruchomiony, i wymaga bardzo niewielu, jeśli w ogóle, struktur danych swojego rodzica .
Kiedy proces wywołuje rozwidlenie, jest uważany za proces nadrzędny , a nowo utworzony proces jest jego dzieckiem. Po rozwidleniu oba procesy nie tylko uruchamiają ten sam program, ale wznawiają wykonywanie tak, jakby oba wywołały wywołanie systemowe. wartość zwracaną wywołania, aby określić swój status, dziecko lub rodzic, i podjąć odpowiednie działania.
Historia
Jedno z najwcześniejszych odniesień do koncepcji rozwidlenia pojawiło się w A Multiprocessor System Design autorstwa Melvina Conwaya , opublikowanym w 1962 roku. Artykuł Conwaya motywował wdrożenie przez L. Petera Deutscha rozwidlenia w systemie podziału czasu GENIE , gdzie koncepcja została zapożyczona przez Kena Thompson za jego najwcześniejsze pojawienie się w Research Unix . Fork stał się później standardowym interfejsem w POSIX .
Komunikacja
Proces potomny zaczyna się od kopii deskryptorów plików swojego rodzica . W przypadku komunikacji międzyprocesowej proces nadrzędny często tworzy jeden lub kilka potoków , a następnie po rozwidleniu procesy zamykają końce potoków, których nie potrzebują.
Warianty
Vfork
Vfork to odmiana rozwidlenia z tą samą konwencją wywoływania i taką samą semantyką, ale do użytku tylko w ograniczonych sytuacjach. Pochodzi z 3BSD Uniksa, pierwszego systemu Unix obsługującego pamięć wirtualną. Został znormalizowany przez POSIX, który pozwolił vfork na zachowanie dokładnie takie samo jak fork, ale został oznaczony jako przestarzały w wydaniu z 2004 roku i został zastąpiony przez posix_spawn () (który jest zwykle implementowany przez vfork) w kolejnych wydaniach.
Gdy wywołanie systemowe vfork zostanie wydane, proces nadrzędny zostanie zawieszony do czasu, gdy proces potomny zakończy wykonywanie lub zostanie zastąpiony nowym obrazem wykonywalnym za pośrednictwem jednego z wywołań systemowych z rodziny „exec ” . Dziecko pożycza konfigurację MMU od rodzica, a strony pamięci są współdzielone między procesem nadrzędnym i podrzędnym bez kopiowania, a w szczególności bez kopiowania przy zapisie semantyka; w związku z tym, jeśli proces potomny dokona modyfikacji na którejkolwiek ze współdzielonych stron, żadna nowa strona nie zostanie utworzona, a zmodyfikowane strony będą również widoczne dla procesu nadrzędnego. Ponieważ nie ma absolutnie żadnego kopiowania stron (zużywającego dodatkową pamięć), ta technika jest optymalizacją w stosunku do zwykłego rozwidlenia w środowiskach pełnej kopii, gdy jest używana z exec. W POSIX użycie vfork do jakiegokolwiek celu, z wyjątkiem jako wstęp do natychmiastowego wywołania funkcji z rodziny exec (i wybranych kilku innych operacji) powoduje niezdefiniowane zachowanie . Podobnie jak w przypadku vfork, dziecko pożycza struktury danych zamiast je kopiować. vfork jest nadal szybszy niż widelec, który używa semantyki kopiowania przy zapisie.
System V nie obsługiwał tego wywołania funkcji przed wprowadzeniem Systemu VR4, [ potrzebne źródło ] , ponieważ współdzielenie pamięci, które powoduje, jest podatne na błędy:
Vfork nie kopiuje tablic stron, więc jest szybszy niż implementacja forka w Systemie V. Ale proces potomny jest wykonywany w tej samej fizycznej przestrzeni adresowej co proces nadrzędny (aż do exec lub exit ) i może w ten sposób nadpisać dane i stos rodzica. Niebezpieczna sytuacja może powstać, jeśli programista nieprawidłowo użyje vfork , więc ciężar wywołania vfork leży po stronie programisty. Różnica między podejściem Systemu V a podejściem BSD jest filozoficzna: czy jądro powinno ukrywać specyfikę swojej implementacji przed użytkownikami, czy też powinno dawać wyrafinowanym użytkownikom możliwość skorzystania z implementacji w celu wydajniejszego wykonywania funkcji logicznej?
— Maurice J. Bach
Podobnie strona podręcznika Linuksa dla vfork zdecydowanie odradza jego używanie: [ nieudana weryfikacja ] [ dyskusja ]
To raczej niefortunne, że Linux ożywił to widmo z przeszłości. Strona podręcznika BSD stwierdza: „To wywołanie systemowe zostanie wyeliminowane, gdy zostaną zaimplementowane odpowiednie mechanizmy udostępniania systemu. Użytkownicy nie powinni polegać na semantyce współdzielenia pamięci przez vfork(), ponieważ w takim przypadku stanie się ono synonimem fork(2) ”.
Inne problemy z vfork obejmują zakleszczenia , które mogą wystąpić w programach wielowątkowych z powodu interakcji z dynamicznym łączeniem . Jako zamiennik vfork , POSIX wprowadził rodzinę funkcji posix_spawn , które łączą działania fork i exec. Te funkcje mogą być zaimplementowane jako procedury biblioteczne w terminach fork , tak jak ma to miejsce w Linuksie, lub w kategoriach vfork dla lepszej wydajności, jak to ma miejsce w Solarisie, ale specyfikacja POSIX zauważa, że zostały one „zaprojektowane jako operacje jądra ”, szczególnie dla systemów operacyjnych działających na ograniczonym sprzęcie i systemach czasu rzeczywistego .
Podczas gdy implementacja 4.4BSD pozbyła się implementacji vfork, powodując, że vfork zachowywał się tak samo jak fork, została ona później przywrócona w systemie operacyjnym NetBSD ze względu na wydajność.
Niektóre wbudowane systemy operacyjne, takie jak uClinux , pomijają fork i implementują tylko vfork, ponieważ muszą działać na urządzeniach, na których implementacja kopiowania przy zapisie jest niemożliwa z powodu braku MMU .
Rozwidlenie
Plan 9 , stworzony przez projektantów Uniksa, zawiera rozwidlenie, ale także wariant o nazwie „rfork”, który umożliwia precyzyjne współdzielenie zasobów między procesami nadrzędnymi i podrzędnymi, w tym przestrzeni adresowej (z wyjątkiem segmentu stosu, który jest unikalny dla każdego procesu), zmienne środowiskowe i przestrzeń nazw systemu plików; sprawia to, że jest to ujednolicony interfejs do tworzenia zarówno procesów, jak i wątków w nich zawartych. Zarówno FreeBSD, jak i IRIX przyjął wywołanie systemowe rfork z Planu 9, przy czym ten ostatni zmienił jego nazwę na „sproc”.
Klon
clone
to wywołanie systemowe w jądrze Linuksa , które tworzy proces potomny, który może współdzielić część swojego kontekstu wykonania z rodzicem. Podobnie jak rfork FreeBSD i sproc IRIX, klon Linuksa został zainspirowany rfork Planu 9 i może być używany do implementacji wątków (chociaż programiści aplikacji zazwyczaj używają interfejsu wyższego poziomu, takiego jak pthreads , zaimplementowanego na klonie ) . Funkcja „oddzielnych stosów” z planu 9 i IRIX została pominięta, ponieważ (według Linusa Torvaldsa ) powoduje zbyt duże obciążenie.
Rozwidlenie w innych systemach operacyjnych
W pierwotnym projekcie systemu operacyjnego VMS (1977) operacja kopiowania z późniejszą mutacją zawartości kilku konkretnych adresów dla nowego procesu, jak w przypadku rozwidlenia, była uważana za ryzykowną. [ potrzebne źródło ] Błędy w bieżącym stanie procesu mogą zostać skopiowane do procesu potomnego. Zastosowano tu metaforę spawnowania procesu: każdy komponent układu pamięci nowego procesu jest konstruowany na nowo od podstaw. spawnu została później przyjęta w systemach operacyjnych Microsoftu (1993) .
Komponent zgodności z POSIX VM/CMS (OpenExtensions) zapewnia bardzo ograniczoną implementację rozwidlenia, w której rodzic jest zawieszony, podczas gdy dziecko wykonuje, a dziecko i rodzic dzielą tę samą przestrzeń adresową. Zasadniczo jest to vfork oznaczony jako widelec . (Zauważ, że dotyczy to tylko systemu operacyjnego gościa CMS; inne systemy operacyjne gościa maszyny wirtualnej, takie jak Linux, zapewniają standardową funkcjonalność rozwidlenia).
Użycie aplikacji
Poniższy wariant programu Hello World demonstruje mechanikę wywołania systemowego fork w języku programowania C. Program dzieli się na dwa procesy, z których każdy decyduje, jaką funkcję wykonuje na podstawie wartości zwracanej przez wywołanie systemowe rozwidlenia. Kod szablonu , taki jak inkluzje nagłówka, został pominięty.
0
int main ( pusty ) { pid_t pid = widelec (); if ( pid == -1 ) { perror ( "rozwidlenie nie powiodło się" ); wyjście ( EXIT_FAILURE ); } else if ( pid == ) { printf ( "Witaj z procesu potomnego! \n " ); _exit ( EXIT_SUCCESS
0
); } else { status int ; ( void ) waitpid ( pid , & status , ); } powrót EXIT_SUCCESS ; }
Poniżej znajduje się analiza tego programu.
pid_t pid = widelec ();
Pierwsza instrukcja w main wywołuje wywołanie systemowe fork w celu podzielenia wykonania na dwa procesy. Wartość zwracana przez rozwidlenie jest zapisywana w zmiennej typu pid_t , który jest typem POSIX dla identyfikatorów procesów (PID).
if ( pid == -1 ) { perror ( "rozwidlenie nie powiodło się" ); wyjście ( EXIT_FAILURE ); }
Minus jeden oznacza błąd w rozwidleniu : nie utworzono nowego procesu, więc drukowany jest komunikat o błędzie.
Jeśli rozwidlenie powiodło się, istnieją teraz dwa procesy, oba wykonują główną funkcję od punktu, w którym rozwidlenie powróciło. Aby procesy wykonywały różne zadania, program musi rozgałęziać się na wartości zwracanej przez fork , aby określić, czy jest wykonywany jako proces potomny , czy jako proces nadrzędny .
0
else if ( pid == ) { printf ( "Witaj z procesu potomnego! \n " ); _exit ( EXIT_SUCCESS ); }
W procesie potomnym zwracana wartość jest równa zero (co jest nieprawidłowym identyfikatorem procesu). Proces potomny drukuje żądaną wiadomość powitalną, a następnie kończy działanie. (Ze względów technicznych należy tutaj użyć funkcji POSIX _exit zamiast standardowej funkcji wyjścia C. )
0
else { status int ; ( void ) waitpid ( pid , & status , ); }
Drugi proces, rodzic, otrzymuje z rozwidlenia identyfikator procesu dziecka, który zawsze jest liczbą dodatnią. Proces nadrzędny przekazuje ten identyfikator do waitpid , aby zawiesić wykonywanie do czasu zakończenia procesu potomnego. Kiedy tak się stanie, rodzic wznawia wykonywanie i kończy za pomocą return .