Dlaczego fork () powinien być zaprojektowany do zwracania deskryptora pliku?


16

Na swojej stronie internetowej o tej sztuczki self-pipe , Dan Bernstein wyjaśnia warunek wyścigu select()i sygnałów, oferuje obejście i stwierdza, że

Oczywiście właściwą rzeczą byłoby fork()zwrócenie deskryptora pliku, a nie identyfikatora procesu.

Co on przez to rozumie - czy jest to coś w tym, że select()w procesach potomnych można radzić sobie ze zmianami stanu zamiast korzystać z procedury obsługi sygnałów, aby otrzymywać powiadomienia o tych zmianach stanu?


Czy w tym artykule pomieszano dane wejściowe i wyjściowe, czy też nie czytam go poprawnie?
ctrl-alt-delor

Możesz poprosić o przesyłanie sygnałów przez rury. Tak robię.
ctrl-alt-delor

@ ctrl-alt-delor, tak, wydaje się, że używa „wejścia / wyjścia potoku” nieco dziwnie, ale myślę, że jest jasne, gdzie pisze i gdzie czyta z potoku. Ten tekst pochodzi z 2003 roku i nie jestem pewien, signalfdczy wtedy coś takiego było?
ilkkachu

5
Dan wie, o czym mówi, choć może być nieco celowo prowokujący. Gdybym był celowo prowokujący, wybrałbym to. Oczywiście, właściwą rzeczą byłoby pozbycie się SIGCHLD.
Steve Summit

1
@mosvy Lekko przesadzam, ale każdy program i każdy programista, którego widziałem, który próbował użyć SIGCHLD, miał z tym problemy. To warunek wyścigu, który czeka. Kiedy wszystko, co mieliśmy, blokowało wait(), były rzeczy, których nie można było zrobić, więc ktoś wymyślił SIGCHLD, ale to była zła robota. Z mojego doświadczenia, a teraz, że one istnieją, zraszanie ładne, powodują blokowania wait3(), wait4()i / lub waitpid()połączeń w kluczowych miejscach (chyba głównym pętla zdarzenie) jest znacznie lepsza alternatywa.
Steve Summit

Odpowiedzi:


14

Problem jest opisany w twoim źródle, select()powinien być przerwany przez sygnały takie jak SIGCHLD, ale w niektórych przypadkach nie działa tak dobrze. Obejściem tego problemu jest zapis sygnału na potoku, który jest następnie obserwowany select(). Po to jest oglądanie deskryptorów plików select(), aby obejść problem.

Obejście to zasadniczo zamienia zdarzenie sygnalizacyjne w zdarzenie deskryptora pliku. Gdyby fork()po prostu zwrócił fd w pierwszej kolejności, obejście nie byłoby wymagane, ponieważ fd można by prawdopodobnie użyć bezpośrednio z nim select().

Więc tak, twój opis w ostatnim akapicie wydaje mi się odpowiedni.


Innym powodem, dla którego fd (lub inny rodzaj uchwytu jądra) byłby lepszy niż zwykły numer identyfikacyjny procesu, jest to, że PID mogą zostać ponownie wykorzystane po śmierci procesu. Może to stanowić problem w niektórych przypadkach podczas wysyłania sygnałów do procesów, może nie być pewne, że proces jest tym, o którym myślisz, że nie, a nie innym, który ponownie wykorzystuje ten sam PID. (Chociaż myślę, że nie powinno to stanowić problemu przy wysyłaniu sygnałów do procesu potomnego, ponieważ rodzic musi uruchomić wait()dziecko, aby jego identyfikator PID został zwolniony).


To powiedziawszy, nie pamiętam dokładnie przypadków, które przeczytałem o ponownym wykorzystywaniu PID jako problemu, więc jeśli ktoś chce to wyjaśnić lub wyjaśnić, a nawet edytować powyższe, nie krępuj się.
ilkkachu

2
Jak już wspomniano, nie ma usprawiedliwienia dla tego, że rodzic odkrył, że jego własne dziecko zostało ponownie wykorzystane. Ma pełną kontrolę nad tą sytuacją, ponieważ to ona dzwoni wait().
Joshua

Są to tak zwane procesy zombie : „proces, który zakończył wykonywanie, ale nadal ma wpis w tabeli procesów: jest to proces w stanie„ Zakończony ”. Dzieje się tak w przypadku procesów potomnych, w których wpis jest nadal potrzebny, aby zezwolić rodzicowi proces odczytu statusu wyjścia dziecka ”
Lassi

6
Warto wspomnieć, że teraz Linux może zwrócić deskryptor pliku (pidfd) clone, który jest rzeczywistym wywołaniem systemowym wywoływanym przez fork na LInux. Flaga umożliwiająca to nazywa się CLONE_PIDFD- patrz na przykład lwn.net/Articles/784831 .
KJ Tsanaktsidis

1
@Lie Ryan, Re „ Posiadanie rozwidlenia globalnego (zamiast uchwytu lokalnego) jest bardziej poprawne koncepcyjnie, ponieważ ” system Windows używa uchwytów procesu. Kod pid i kod wyjścia pozostają w pobliżu, dopóki wszystkie uchwyty procesu nie zostaną zamknięte (zamiast czekać, aż rodzic zbierze), unikając warunków wyścigowych typowych dla systemów uniksowych. Kiedy uchwyty utrzymują proces przy życiu, sensowniejsze jest, aby były to uchwyty lokalne, a nie globalne.
ikegami

9

To tylko rozmyślanie w stylu „byłoby świetnie, gdyby Unix był zaprojektowany inaczej niż jest”.

Problem z PID polega na tym, że żyją w globalnej przestrzeni nazw, w której mogą być ponownie wykorzystane do innego procesu, i byłoby miło, gdyby fork()zwrócono rodzicowi jakiś uchwyt, który gwarantowałby, że zawsze będzie odnosił się do procesu potomnego, i że może przejść do innych procesów przez dziedziczenie lub gniazda unix / SCM_RIGHTS[1].

Zapoznaj się również z dyskusją tutaj dotyczącą niedawnego wysiłku „naprawienia” tego w Linuksie, w tym dodanie flagi, do clone()której spowoduje, że zwróci pid-fd zamiast PID.

Ale nawet wtedy nie wyeliminowałoby to potrzeby takiego hakowania [2] lub lepszych interfejsów, ponieważ sygnały powiadamiające proces nadrzędny o stanie dziecka nie są jedynymi, które chciałbyś obsłużyć w głównej pętli programu. Niestety, rzeczy takie jak epoll(7) + signalfd(2)Linux czy kqueue(2)BSD nie są standardowe - jedyny standardowy interfejs (ale nie obsługiwany w starszych systemach) jest znacznie gorszy pselect(2).

[1] Zapobieganie ponownemu cyklowi PID do czasu waitpid()zwrócenia wywołania syscall i wykorzystania jego wartości zwracanej można prawdopodobnie osiągnąć w nowszych systemach, używając waitid(.., WNOWAIT)zamiast tego.

[2] Nie komentowałbym twierdzenia DJ Bernsteina, że ​​to on wymyślił (przepraszam za apofazę ;-)).


8

Bernstein nie podaje zbyt dużego kontekstu dla tej uwagi „Right Thing”, ale zaryzykuję zgadnięcie: powrót rozwidlenia (2) PID jest niezgodny z zwracaniem deskryptorów plików przez open (2), creat (2) itp. Reszta systemu uniksowego mogła manipulować procesem za pomocą deskryptora pliku reprezentującego proces, zamiast PID. Istnieje systemowy sygnał wywołania signalfd (2) , który pozwala na nieco lepszą interakcję między sygnałami i deskryptorami plików i pokazuje, że proces reprezentujący deskryptor pliku reprezentujący proces może zadziałać.


signalfd (2) wygląda świetnie, dziękuję, że o tym wspomniałeś! Szkoda, że ​​to tylko Linux.
Lassi

1
Dyskusje dotyczyły pidfd_openrównież Linuksa, patrz na przykład lwn.net/Articles/789023
dhag
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.