W jaki sposób serwery internetowe „nasłuchują” adresów IP, przerywają lub odpytują?

87

Próbuję zrozumieć niższe szczegóły dotyczące serwerów sieciowych. Zastanawiam się, czy serwer, powiedzmy Apache, ciągle odpytuje o nowe żądania, czy też działa przez jakiś rodzaj systemu przerwań. Jeśli jest to przerwanie, co powoduje przerwanie, czy to sterownik karty sieciowej?

użytkownik2202911
źródło
1
Słowem kluczowym do zrozumienia jest „serwer” . W modelu serwer-klient (w porównaniu z modelem master-slave) serwer czeka na żądania od klientów. Te żądania to zdarzenia wymagające obsługi. Serwer to aplikacja. Twoje pytanie łączy w sobie oprogramowanie SW z terminologią HW (np. Interrupt i NIC), zamiast utrzymywać powiązane pojęcia na tej samej warstwie abstrakcji. Sterownik NIC może czasami używać odpytywania, np. Sterowniki Linux NAPI cofają się do odpytywania, gdy jest zalew pakietów. Nie ma to jednak znaczenia dla oprogramowania do przetwarzania zdarzeń.
trociny
1
@sawdust Bardzo interesujące. Pytanie naprawdę ma na celu zrozumienie związku między procesami SW i HW
user2202911
1
Jest to bardzo podobne do sposobu, w jaki programy wiersza polecenia (i inne GUI) nasłuchują na klawiaturze. Zwłaszcza w systemie okiennym, w którym krok jądra odbiera dane z urządzenia klawiatury i przekazuje je menedżerowi okien, który identyfikuje okno, które jest fokusowe i przekazuje dane do tego okna.
G-Man,
@ G-Man: I teoria, tak. W rzeczywistości większość maszynistek nie pisze z prędkością 1 Gbit / s, co uzasadnia posiadanie dwóch różnych architektur. Jeden czysty, elastyczny i wolny, jeden niezdarny, ale szybki.
MSalters

Odpowiedzi:

181

Krótka odpowiedź brzmi: jakiś system przerwań. Zasadniczo używają blokujących We / Wy, co oznacza, że ​​śpią (blokują) podczas oczekiwania na nowe dane.

  1. Serwer tworzy gniazdo nasłuchujące, a następnie blokuje się podczas oczekiwania na nowe połączenia. W tym czasie jądro przełącza proces w przerywany stan uśpienia i uruchamia inne procesy. To ważna kwestia: ciągłe sondowanie procesu zmarnowałoby procesor. Jądro jest w stanie efektywniej wykorzystywać zasoby systemowe, blokując proces, dopóki nie będzie pracy.

  2. Gdy nowe dane docierają do sieci, karta sieciowa wydaje przerwanie.

  3. Widząc, że nastąpiła przerwa w karcie sieciowej, jądro, poprzez sterownik karty sieciowej, odczytuje nowe dane z karty sieciowej i zapisuje je w pamięci. (Musi to zostać wykonane szybko i zwykle jest obsługiwane w module obsługi przerwań.)

  4. Jądro przetwarza nowo przybyłe dane i kojarzy je z gniazdem. Proces blokujący na tym gnieździe zostanie oznaczony jako uruchamialny, co oznacza, że ​​można go teraz uruchomić. Nie musi działać natychmiast (jądro może zdecydować o uruchomieniu innych procesów).

  5. W wolnym czasie jądro obudzi zablokowany proces serwera WWW. (Ponieważ można go teraz uruchomić).

  6. Proces serwera kontynuuje działanie, jakby nie upłynęło czasu. Blokuje wywołanie systemowe i zwraca wszelkie nowe dane. Następnie ... przejdź do kroku 1.

Greg Bowser
źródło
18
+1 za jasne wytyczenie jądra a proces serwera.
Russell Borogove,
13
Nie mogę uwierzyć w coś tak złożonego, jak to można streścić tak jasno i prosto, ale ty to zrobiłeś. +1
Brandon
8
+1 Świetna odpowiedź. Ponadto kroki od 2 do 3 mogą stać się nieco bardziej skomplikowane dzięki nowoczesnym kartom sieciowym, systemom operacyjnym i sterownikom. Na przykład w przypadku NAPI w systemie Linux pakiety nie są faktycznie odbierane w kontekście przerwania. Zamiast tego jądro mówi: „Dobra karta sieciowa, rozumiem, że masz dane. Przestań mnie wkurzać (wyłącz źródło przerwań), a wkrótce wrócę, aby pobrać ten pakiet i wszystkie kolejne pakiety, które mogą przybyć, zanim to zrobię”.
Jonathon Reinhart
8
Niewielki nitpick: nie jest tak naprawdę konieczne blokowanie. Jak tylko proces serwera utworzy gniazdo nasłuchujące, jądro zaakceptuje SYN na tym porcie, nawet jeśli nie jesteś w nim zablokowany accept. Są (na szczęście, albo całkowicie by to było do kitu) niezależnymi, asynchronicznie działającymi zadaniami. Gdy nadchodzą połączenia, są one umieszczane w kolejce, z której są acceptpobierane. Tylko jeśli nie ma, blokuje.
Damon,
3
„odczytuje nowe dane z karty sieciowej i zapisuje je w pamięci. (Musi to zostać zrobione szybko i ogólnie jest obsługiwane w module obsługi przerwań.)” Czy nie jest to zrobione z bezpośrednim dostępem do pamięci?
Siyuan Ren,
9

Istnieje wiele „niższych” szczegółów.

Po pierwsze, rozważ, że jądro ma listę procesów, aw dowolnym momencie niektóre z nich są uruchomione, a niektóre nie. Jądro pozwala każdemu działającemu procesowi na wycinek czasu procesora, a następnie przerywa go i przechodzi do następnego. Jeśli nie ma żadnych uruchomialnych procesów, jądro prawdopodobnie wyda instrukcję taką jak HLT do CPU, która zawiesza procesor, dopóki nie nastąpi przerwanie sprzętowe.

Gdzieś na serwerze jest wywołanie systemowe, które mówi „daj mi coś do zrobienia”. Można to zrobić na dwie szerokie kategorie. W przypadku Apache wywołuje acceptgniazdo, które Apache wcześniej otworzył, prawdopodobnie nasłuchując na porcie 80. Jądro utrzymuje kolejkę prób połączenia i dodaje do tej kolejki za każdym razem, gdy odbierany jest TCP SYN . To, jak jądro wie, że odebrano TCP SYN, zależy od sterownika urządzenia; w przypadku wielu kart sieciowych prawdopodobnie nastąpiło przerwanie sprzętowe podczas odbierania danych sieciowych.

acceptprosi jądro o zwrócenie mi następnej inicjacji połączenia. Jeśli kolejka nie była pusta, acceptnatychmiast wraca. Jeśli kolejka jest pusta, proces (Apache) jest usuwany z listy uruchomionych procesów. Gdy połączenie zostanie później zainicjowane, proces zostanie wznowiony. Nazywa się to „blokowaniem”, ponieważ wywołujący go proces accept()wygląda jak funkcja, która nie powraca, dopóki nie uzyska wyniku, co może potrwać jakiś czas. W tym czasie proces nie może zrobić nic więcej.

Po acceptpowrocie Apache wie, że ktoś próbuje nawiązać połączenie. Następnie wywołuje funkcję fork, aby podzielić proces Apache na dwa identyczne procesy. Jeden z tych procesów kontynuuje przetwarzanie żądania HTTP, a drugi wywołuje acceptponownie, aby uzyskać następne połączenie. Dlatego zawsze istnieje proces główny, który nie robi nic poza wywoływaniem accepti spawaniem podprocesów, a następnie jest jeden podproces dla każdego żądania.

Jest to uproszczenie: można to zrobić za pomocą wątków zamiast procesów, a także z forkwyprzedzeniem, aby proces roboczy był gotowy do działania po otrzymaniu żądania, zmniejszając w ten sposób narzut związany z uruchamianiem. W zależności od konfiguracji Apache może wykonać jedną z tych czynności.

To pierwsza szeroka kategoria, jak to zrobić, i to się nazywa blokowanie IO ponieważ system nazywa jak accepti readi writektóre działają na gniazdach zawiesi proces, dopóki mają coś do powrotu.

Innym szerokim sposobem na to jest nazywanie nieblokującego lub opartego na zdarzeniach lub asynchronicznego We / Wy . Jest to realizowane za pomocą wywołań systemowych, takich jak selectlub epoll. Każdy z nich robi to samo: dajesz im listę gniazd (lub ogólnie deskryptorów plików) i co chcesz z nimi zrobić, a jądro blokuje się, dopóki nie będzie gotowe do zrobienia jednej z tych rzeczy.

Za pomocą tego modelu możesz powiedzieć kernelowi (za pomocą epoll): „Powiedz mi, kiedy pojawi się nowe połączenie na porcie 80 lub nowe dane do odczytania na każdym z 9471 innych połączeń, które mam otwarte”. epollblokuje, dopóki jedna z tych rzeczy nie będzie gotowa, to robicie to. Potem powtarzasz. Wywołań systemowych jak accepti readi writenigdy blok, po części dlatego, kiedy ich nazwać, epollpo prostu powiedział, że są gotowe, więc nie byłoby powodu do blokowania, a także dlatego, że po otwarciu gniazda lub plik określić, że chcesz je w trybie nieblokującym, więc te połączenia zakończą się niepowodzeniem EWOULDBLOCKzamiast blokowania.

Zaletą tego modelu jest to, że potrzebujesz tylko jednego procesu. Oznacza to, że nie musisz przydzielać stosu i struktur jądra dla każdego żądania. Nginx i HAProxy używają tego modelu i jest to duży powód, dla którego mogą one obsługiwać o wiele więcej połączeń niż Apache na podobnym sprzęcie.

Phil Frost
źródło