Як веб-сервери «слухають» IP-адреси, перебивають або опитують?


87

Я намагаюся зрозуміти нижню деталь веб-серверів. Мені цікаво, чи сервер, скажімо, Apache, постійно запитує на нові запити або якщо він працює за допомогою якоїсь системи переривань. Якщо це перерва, що ініціює переривання, це драйвер мережевої карти?


1
Ключове слово для розуміння - "сервер" . У моделі сервер-клієнт (проти моделі master-slave) сервер чекає запитів від клієнтів. Ці запити - це події, які потребують обслуговування. Веб-сервер - це прикладна програма. Ваше запитання поєднує додаток SW з термінологією HW (наприклад, переривання та NIC), а не зберігає споріднені поняття на одному рівні абстракції. Драйвер NIC іноді фактично може використовувати опитування, наприклад, драйвери NAPI для Linux регресують до опитування, коли відбувається затоплення пакетів. Але це не має значення для SW-програми для обробки подій.
тирса

1
@sawdust Дуже цікаво. Питання справді має на меті зрозуміти зв’язок між процесами SW та HW
user2202911

1
Це дуже схоже на те, як програми командного рядка (та інших графічних інтерфейсів) слухають клавіатуру. Особливо у віконній системі, де у вас є крок ядра, який отримує дані від клавіатурного пристрою та передає їх менеджеру вікон, який ідентифікує вікно, яке має фокус, і надає дані цьому вікну.
G-Man

@ G-Man: Я теорія, так. Насправді більшість машиністів не набирають швидкості 1 Гбіт / с, що виправдовує наявність двох різних архітектур. Один чистий, гнучкий і повільний, один незграбний, але швидкісний.
MSalters

Відповіді:


181

Коротка відповідь: якась система переривання. По суті, вони використовують блокування вводу-виводу, тобто вони сплять (блокують) під час очікування нових даних.

  1. Сервер створює прослуховувальний сокет, а потім блокує, очікуючи нових підключень. За цей час ядро ​​переводить процес у стан переривання сну і запускає інші процеси. Це важливий момент: постійне опитування процесів витрачає процесор. Ядро здатне ефективніше використовувати системні ресурси, блокуючи процес, поки не буде виконано роботи.

  2. Коли нові дані надходять у мережу, мережева карта видає перерву.

  3. Бачачи, що відбувається переривання з мережевої карти, ядро ​​через драйвер мережевої карти зчитує нові дані з мережевої карти та зберігає їх у пам'яті. (Це потрібно зробити швидко і, як правило, обробляється всередині обробника переривання.)

  4. Ядро обробляє нещодавно отримані дані та пов'язує його з сокетом. Процес, який блокується в цьому сокеті, буде позначений запущеним, що означає, що він тепер може бути запущений. Він не обов'язково запускається відразу (ядро може вирішити запускати інші процеси).

  5. У вільний час ядро ​​прокине заблокований процес веб-сервера. (Оскільки це зараз можна виконати.)

  6. Процес веб-сервера продовжує виконуватись так, ніби не минуло часу. Його система блокування дзвінків повертається і обробляє будь-які нові дані. Потім ... перейдіть до кроку 1.


18
+1 для чіткого розмежування ядра та процесу веб-сервера.
Рассел Борогов

13
Я не можу повірити в щось таке складне, оскільки це можна так чітко і просто узагальнити, але ти це зробив. +1
Брендон

8
+1 Відмінна відповідь. Крім того, кроки між 2 та 3 можуть стати трохи складнішими з сучасними NIC, ОС та драйверами. Наприклад, з NAPI на Linux пакети насправді не отримуються в контексті переривання. Натомість ядро ​​каже: "Гаразд, NIC, я розумію, що у вас є дані. Покиньте мене з помилками (вимкніть джерело переривання), і я незабаром повернуся, щоб схопити цей пакет і всі наступні пакети, які можуть надійти до того, як я це зробити".
Джонатан Райнхарт

8
Незначна нитка: блокувати насправді не потрібно. Як тільки серверний процес створив розетку для прослуховування, ядро ​​прийме SYN на цьому порту, навіть поки ви не заблоковані всередині accept. Вони (на щастя, або це було б цілком смоктано!) Незалежні, асинхронно виконувані завдання. По мірі підключення вони розміщуються в черзі, звідки acceptїх витягують. Тільки якщо таких немає, він блокує.
Деймон

3
"зчитує нові дані з мережевої карти та зберігає їх у пам'яті. (Це потрібно зробити швидко і, як правило, обробляється всередині обробника переривань.)" Чи не робиться це з прямим доступом до пам'яті?
Siyuan Ren

9

Існує досить багато "нижчих" деталей.

Спочатку врахуйте, що в ядрі є список процесів, і в будь-який момент часу деякі з цих процесів запущені, а деякі - ні. Ядро дозволяє кожному запущеному процесу деякий фрагмент часу процесора, потім перериває його і переходить до наступного. Якщо немає запущених процесів, ядро, ймовірно, видасть ЦП до інструкції, як 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 на аналогічному обладнанні.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.