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 accept
gniazdo, 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.
accept
prosi jądro o zwrócenie mi następnej inicjacji połączenia. Jeśli kolejka nie była pusta, accept
natychmiast 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 accept
powrocie 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 accept
ponownie, aby uzyskać następne połączenie. Dlatego zawsze istnieje proces główny, który nie robi nic poza wywoływaniem accept
i 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 fork
wyprzedzeniem, 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 accept
i read
i write
któ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 select
lub 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”. epoll
blokuje, dopóki jedna z tych rzeczy nie będzie gotowa, to robicie to. Potem powtarzasz. Wywołań systemowych jak accept
i read
i write
nigdy blok, po części dlatego, kiedy ich nazwać, epoll
po 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 EWOULDBLOCK
zamiast 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.