Jaki jest powód wykonywania podwójnego rozwidlenia podczas tworzenia demona?

165

Próbuję stworzyć demona w Pythonie. Znalazłem następujące pytanie , które zawiera dobre zasoby, które obecnie śledzę, ale jestem ciekaw, dlaczego potrzebny jest podwójny widelec. Przeszukałem google i znalazłem wiele zasobów stwierdzających, że jest to konieczne, ale nie dlaczego.

Niektórzy wspominają, że ma to na celu uniemożliwić demonowi uzyskanie kontrolującego terminala. Jak by to zrobił bez drugiego widelca? Jakie są konsekwencje?

Shabbyrobe
źródło
2
Jedną z trudności w wykonaniu podwójnego rozwidlenia jest to, że rodzic nie może łatwo uzyskać PID procesu wnuka ( fork()wywołanie zwraca PID dziecka do rodzica, więc łatwo jest uzyskać PID procesu dziecka, ale nie jest to łatwe uzyskać PID procesu wnuczka ).
Craig McQueen

Odpowiedzi:

105

Patrząc na kod, do którego odwołuje się pytanie, uzasadnienie jest następujące:

Rozwidlić drugie dziecko i natychmiast wyjść, aby zapobiec zombie. Powoduje to osierocenie drugiego procesu potomnego, co powoduje, że proces init jest odpowiedzialny za jego czyszczenie. A ponieważ pierwsze dziecko jest liderem sesji bez terminala sterującego, możliwe jest, że może go zdobyć otwierając w przyszłości terminal (systemy oparte na Systemie V). Ten drugi rozwidlenie gwarantuje, że dziecko nie jest już liderem sesji, uniemożliwiając demonowi uzyskanie kontrolującego terminala.

Tak więc ma to na celu zapewnienie, że demon zostanie ponownie przeniesiony na init (na wypadek gdyby proces uruchamiający demona trwał długo) i eliminuje wszelkie szanse na to, że demon odzyska kontrolujący terminal tty. Więc jeśli żaden z tych przypadków nie ma zastosowania, jeden widelec powinien wystarczyć. „ Programowanie sieciowe w systemie Unix - Stevens ” ma dobrą sekcję na ten temat.

Uciecha
źródło
28
To nie jest całkowicie poprawne. Standardowym sposobem tworzenia demona jest po prostu zrobienie tego p=fork(); if(p) exit(); setsid(). W tym przypadku rodzic również wychodzi, a pierwszy proces potomny jest ponownie rodzicem. Magia podwójnego rozwidlenia jest wymagana tylko, aby uniemożliwić demonowi uzyskanie tty.
parasietje
1
Tak więc, jak rozumiem, jeśli moich startów programu oraz forksw childprocesie, to pierwszy proces dziecko będzie session leaderi będzie mógł otworzyć terminal TTY. Ale jeśli ponownie rozwidlę się od tego dziecka i zakończę to pierwsze dziecko, drugie rozwidlone dziecko nie będzie session leaderi nie będzie mogło otworzyć terminala TTY. Czy to stwierdzenie jest poprawne?
tonix
2
@tonix: zwykłe rozwidlenie nie tworzy lidera sesji. To jest zrobione przez setsid(). Tak więc pierwszy rozwidlony proces staje się liderem sesji po wywołaniu, setsid()a następnie rozwidlamy ponownie, tak że ostatni, podwójnie rozwidlony proces nie jest już liderem sesji. Poza wymogiem setsid()bycia liderem sesji, jesteś na miejscu.
dbmikus
169

Próbowałem zrozumieć podwójny widelec i natknąłem się tutaj na to pytanie. Po wielu badaniach doszedłem do tego. Mamy nadzieję, że pomoże to lepiej wyjaśnić sprawę każdemu, kto ma takie samo pytanie.

W Uniksie każdy proces należy do grupy, która z kolei należy do sesji. Oto hierarchia…

Sesja (SID) → Grupa procesów (PGID) → Proces (PID)

Pierwszy proces w grupie procesów staje się liderem grupy procesów, a pierwszy proces w sesji zostaje liderem sesji. Z każdą sesją może być powiązany jeden TTY. Tylko lider sesji może przejąć kontrolę nad TTY. Aby proces był naprawdę zdemonizowany (działał w tle), powinniśmy upewnić się, że lider sesji został zabity, aby sesja nie mogła kiedykolwiek przejąć kontroli nad TTY.

Uruchomiłem przykładowy demon Pythona Sandera Marechala z tej witryny na moim Ubuntu. Oto wyniki z moimi komentarzami.

1. `Parent`    = PID: 28084, PGID: 28084, SID: 28046
2. `Fork#1`    = PID: 28085, PGID: 28084, SID: 28046
3. `Decouple#1`= PID: 28085, PGID: 28085, SID: 28085
4. `Fork#2`    = PID: 28086, PGID: 28085, SID: 28085

Zauważ, że proces jest liderem sesji po Decouple#1, ponieważ tak jest PID = SID. Nadal może przejąć kontrolę nad TTY.

Zauważ, że Fork#2nie jest już liderem sesji PID != SID. Ten proces nigdy nie może przejąć kontroli nad TTY. Naprawdę zdemonizowany.

Osobiście uważam, że podwójna terminologia jest myląca. Lepszym idiomem może być fork-decouple-fork.

Dodatkowe interesujące linki:

Praveen Gollakota
źródło
Podwójne rozwidlenie zapobiega również tworzeniu się zombie, gdy proces nadrzędny działa przez dłuższy czas, iz jakiegoś powodu usuwa domyślną procedurę obsługi dla sygnału informującego, że proces umarł.
Trismegistos
Ale druga osoba może również zadzwonić do decouple i zostać liderem sesji, a następnie nabyć terminal.
Trismegistos,
2
To nie jest prawda. Pierwsza fork()już uniemożliwia tworzenie zombie, pod warunkiem, że zamkniesz rodzica.
parasietje
1
Minimalny przykład pokazujący
can.
1
Czy byłoby dobrze zadzwonić setsid() przed singlem fork()? Właściwie myślę, że odpowiedzi na to pytanie odpowiadają na to.
Craig McQueen
118

Ściśle mówiąc, double-fork nie ma nic wspólnego z wychowywaniem demona jako dziecka init. Wszystko, co jest potrzebne do ponownego wychowania dziecka, to to, że rodzic musi wyjść. Można to zrobić za pomocą tylko jednego widelca. Również samo wykonanie podwójnego rozwidlenia nie powoduje ponownego zrobienia procesu demona init; rodzic demona musi wyjść. Innymi słowy, rodzic zawsze kończy pracę podczas rozwidlania odpowiedniego demona, aby proces demona był ponownie rodzicielski init.

Dlaczego więc podwójny widelec? POSIX.1-2008 Sekcja 11.1.3, " Terminal sterujący ", zawiera odpowiedź (podkreślenie dodane):

Terminal sterujący sesją jest przydzielany przez lidera sesji w sposób określony w implementacji. Jeśli lider sesji nie ma terminala sterującego i otwiera plik urządzenia terminala, który nie jest już skojarzony z sesją bez użycia O_NOCTTYopcji (zobacz open()), definiowane jest w implementacji, czy terminal stanie się terminalem sterującym lidera sesji. Jeśli proces, który nie jest liderem sesji, otwiera plik terminala, lub O_NOCTTYopcja jest używana na open(), to terminal ten nie powinien stać się terminalem kontrolującym proces wywołujący .

To mówi nam, że jeśli proces demona robi coś takiego ...

int fd = open("/dev/console", O_RDWR);

... wtedy proces demona może zostać przyjęty /dev/consolejako terminal sterujący, w zależności od tego, czy proces demona jest liderem sesji oraz w zależności od implementacji systemu. Program może zagwarantować, że powyższe wywołanie nie uzyska terminala sterującego, jeśli program najpierw upewni się, że nie jest on liderem sesji.

Normalnie, podczas uruchamiania demona, setsidjest wywoływana (z procesu potomnego po wywołaniu fork) w celu odłączenia demona od jego kontrolującego terminala. Jednak wywołanie setsidoznacza również, że proces wywołujący będzie liderem sesji nowej sesji, co pozostawia otwartą możliwość, że demon mógłby ponownie uzyskać kontrolujący terminal. Technika podwójnego rozwidlenia zapewnia, że ​​proces demona nie jest liderem sesji, co gwarantuje, że wywołanie open, jak w powyższym przykładzie, nie spowoduje, że proces demona ponownie przejmie kontrolujący terminal.

Technika podwójnego widelca jest nieco paranoiczna. Może to nie być konieczne, jeśli wiesz, że demon nigdy nie otworzy pliku urządzenia terminala. Ponadto w niektórych systemach może nie być konieczne, nawet jeśli demon otwiera plik urządzenia terminala, ponieważ to zachowanie jest zdefiniowane w implementacji. Jednak jedną rzeczą, która nie jest zdefiniowana w ramach implementacji, jest to, że tylko lider sesji może przydzielić terminal sterujący. Jeśli proces nie jest liderem sesji, nie może przydzielić kontrolującego terminala. Dlatego, jeśli chcesz być paranoikiem i być pewnym, że proces demona nie może przypadkowo uzyskać kontrolującego terminala, niezależnie od jakichkolwiek specyfikacji zdefiniowanych w implementacji, niezbędna jest technika podwójnego rozwidlenia.

Dan Moulding
źródło
3
+1 Szkoda, że ​​ta odpowiedź pojawiła się ~ cztery lata po zadaniu pytania.
Tim Seguine,
12
Ale to nadal nie wyjaśnia, dlaczego jest tak strasznie ważne, że demon nie może
odzyskać
7
Słowem kluczowym jest „nieumyślnie” zdobądź kontrolujący terminal. Jeśli zdarzy się, że proces otworzy terminal i stanie się procesem kontrolującym terminal, to jeśli ktoś wyśle ​​^ C z tego terminala, może zakończyć proces. Więc dobrze byłoby zabezpieczyć proces przed przypadkowym zdarzeniem. Osobiście będę trzymał się pojedynczego rozwidlenia i setsid () dla kodu, który piszę, o którym wiem, że nie będzie otwierał terminali.
BobDoolittle
1
@BobDoolittle, jak to się mogło stać „nieumyślnie”? Proces nie tylko chcąc nie chcąc kończy się otwarciem terminali, jeśli nie jest napisane, aby to zrobić. Może podwójne rozwidlenie jest przydatne, jeśli programista nie znasz kodu i nie wiesz, czy może on otworzyć terminal.
Marius
10
@Marius Wyobraź sobie, co może się zdarzyć, jeśli dodać taką linię do pliku konfiguracyjnego demona: LogFile=/dev/console. Programy nie zawsze mają kontrolę nad tym, które pliki mogą otwierać w czasie kompilacji;)
Dan Moulding
11

Zaczerpnięte z Bad CTK :

„W niektórych odmianach Uniksa jesteś zmuszony do wykonania podwójnego rozwidlenia podczas uruchamiania, aby przejść do trybu demona. Dzieje się tak, ponieważ pojedyncze rozwidlenie nie jest gwarantowane do odłączenia się od kontrolującego terminala.”

Stefan Thyberg
źródło
3
W jaki sposób pojedynczy widelec nie może odłączyć się od terminala sterującego, ale podwójny widelec to zrobić? Na jakich unixach to się dzieje?
bdonlan
12
Demon musi zamknąć swoje deskryptory plików wejściowych i wyjściowych (fds), w przeciwnym razie nadal byłby podłączony do terminala, w którym został uruchomiony. Rozwidlony proces dziedziczy te po rodzicu. Najwyraźniej pierwsze dziecko zamyka fds, ale to nie porządkuje wszystkiego. Na drugim rozwidleniu fds nie istnieją, więc drugie dziecko nie może być już do niczego podłączone.
Aaron Digulla
4
@Aaron: Nie, demon prawidłowo "odłącza się" od terminala sterującego, wywołując setsidpo początkowym forku. Następnie zapewnia, że pozostanie odłączony od kontrolującego terminala, ponownie rozwidlając i kończąc sesję lidera (proces, który wywołał setsid).
Dan Molding
2
@bdonlan: To nie jest tak, forkże odłącza się od kontrolującego terminala. To jest setsidto. Ale setsidzakończy się niepowodzeniem, jeśli zostanie wezwany przez lidera grupy procesów. Dlatego forknależy wykonać inicjały wcześniej, setsidaby upewnić się, że setsidjest wywoływane z procesu, który nie jest liderem grupy procesów. Drugi forkzapewnia, że ​​końcowy proces (ten, który będzie demonem) nie jest liderem sesji. Tylko liderzy sesji mogą zdobyć terminal kontrolujący, więc ta druga rozwidlenie gwarantuje, że demon nieumyślnie nie przejmie ponownie terminala kontrolującego. Dotyczy to każdego systemu operacyjnego POSIX.
Dan Molding
@DanMoulding To nie gwarantuje, że drugie dziecko nie zdobędzie kontrolnego terminala, ponieważ może wywołać setid i zostać liderem sesji, a następnie zdobyć terminal kontrolny.
Trismegistos,
7

Według „Advanced Programming in the Unix Environment” Stephens and Rago, drugi fork jest raczej zaleceniem i ma na celu zagwarantowanie, że demon nie uzyska kontrolującego terminala w systemach opartych na Systemie V.

Paolo Tedesco
źródło
3

Jednym z powodów jest to, że proces nadrzędny może natychmiast wait_pid () dla dziecka, a następnie zapomnieć o tym. Kiedy wnuk umiera, jego rodzic jest inicjowany i będzie czekał () na to - i wyprowadzi go ze stanu zombie.

W rezultacie proces nadrzędny nie musi być świadomy rozwidlonych elementów potomnych, a także umożliwia rozwidlenie długo działających procesów z bibliotek itp.

KarlP
źródło
2

Wywołanie daemon () ma wywołanie nadrzędne _exit (), jeśli się powiedzie. Pierwotną motywacją mogło być pozwolenie rodzicowi na wykonanie dodatkowej pracy, podczas gdy dziecko demonizuje.

Może również opierać się na błędnym przekonaniu, że jest to konieczne, aby upewnić się, że demon nie ma procesu nadrzędnego i jest ponownie inicjowany - ale tak się stanie, gdy rodzic umrze w przypadku pojedynczego rozwidlenia.

Przypuszczam więc, że w końcu wszystko sprowadza się do tradycji - pojedynczy widelec wystarczy, o ile i tak rodzic umiera w krótkim czasie.

bdonlan
źródło
2

Wydaje się, że porządna dyskusja na ten temat znajduje się pod adresem http://www.developerweb.net/forum/showthread.php?t=3025

Cytując mlampkin stamtąd:

... pomyśl o wywołaniu setsid () jako o "nowym" sposobie robienia rzeczy (odłączenie się od terminala), a [drugie] wywołanie fork () po nim jako o redundancji do obsługi SVr4 ...

Stobor
źródło
-1

W ten sposób może być łatwiejsze do zrozumienia:

  • Pierwszy fork i setsid utworzą nową sesję (ale identyfikator procesu == identyfikator sesji).
  • Drugi rozwidlenie zapewnia, że ​​identyfikator procesu! = Identyfikator sesji.
pandy.song
źródło