Існує досить багато "нижчих" деталей.
Спочатку врахуйте, що в ядрі є список процесів, і в будь-який момент часу деякі з цих процесів запущені, а деякі - ні. Ядро дозволяє кожному запущеному процесу деякий фрагмент часу процесора, потім перериває його і переходить до наступного. Якщо немає запущених процесів, ядро, ймовірно, видасть ЦП до інструкції, як HLT, яка призупиняє процесор, поки не буде апаратне переривання.
Десь на сервері стоїть системний виклик, який говорить "дай мені щось зробити". Існує дві широкі категорії способів зробити це. У випадку Apache, він викликає acceptсокет Apache, який раніше відкрився, ймовірно, прослуховуючи порт 80. Ядро підтримує чергу спроб з'єднання і додає до цієї черги щоразу, коли приймається TCP SYN . Наскільки ядро знає, що отримано TCP SYN, залежить від драйвера пристрою; для багатьох НІК, ймовірно, відбувається апаратне переривання при отриманні мережевих даних.
acceptпросить ядро повернути мені наступне ініціювання з'єднання. Якщо черга не була порожньою, acceptпросто повертається негайно. Якщо черга порожня, процес (Apache) видаляється зі списку запущених процесів. Коли пізніше ініціюється з'єднання, процес відновляється. Це називається "блокуванням", тому що процес, що викликає його, accept()виглядає як функція, яка не повертається, поки не отримає результат, який може пройти через деякий час. За цей час процес не може більше нічого робити.
Після acceptповернення Apache знає, що хтось намагається ініціювати з'єднання. Потім він закликає форк розділити процес Apache на два однакові процеси. Один із цих процесів продовжується обробкою HTTP-запиту, інший acceptзнову викликає наступне з'єднання. Таким чином, завжди є головний процес, який не робить нічого, крім виклику acceptта нерестовини підпроцесів, і тоді є один підпроцес для кожного запиту.
Це спрощення: це можна зробити за допомогою потоків замість процесів, а також forkзаздалегідь можливо, щоб під час отримання запиту був готовий робочий процес, таким чином зменшуючи накладні витрати. Залежно від налаштування Apache, він може робити будь-яку з цих речей.
Це перша широка категорія того, як це зробити, і називається блокуванням IO, оскільки виклики системи, як acceptі readі writeякі працюють на сокетах, призупиняють процес, поки їм щось не повернути.
Інший широкий спосіб зробити це називається неблокуючим або на основі подій або асинхронним IO . Це реалізується за допомогою системних викликів типу selectабо epoll. Вони роблять те саме: ви даєте їм список сокетів (або взагалі дескрипторів файлів) і те, що ви хочете зробити з ними, і ядро блокує, поки не буде готове зробити одну з цих речей.
У цій моделі ви можете сказати ядру (з epoll): "Скажіть мені, коли на порту 80 з'явиться нове з'єднання або нові дані для читання на будь-якому з цих 9471 інших з'єднань, які я відкрив". epollблокує, поки одна з цих речей не буде готова, тоді ви це зробите. Потім ви повторите. Системні виклики , як acceptі readі writeНЕ блок, почасти тому , що всякий раз , коли ви їх називаєте, epollпросто сказали, що вони готові , так що не було б ніяких підстав для блокування, а також тому , що при відкритті сокета або файлу ви вказуєте , що ви хочете їх в режимі, що не блокує, тож ці дзвінки не вдасться EWOULDBLOCKзамість блокувати.
Перевага цієї моделі полягає в тому, що вам потрібен лише один процес. Це означає, що вам не потрібно виділяти стек і структури ядра для кожного запиту. Nginx і HAProxy використовують цю модель, і це велика причина, що вони можуть мати справу з набагато більшою кількістю підключень, ніж Apache на аналогічному обладнанні.