Я пытаюсь понять нижние детали веб-серверов. Мне интересно, если сервер, скажем, Apache, постоянно опрашивает новые запросы или работает ли он какой-то системой прерываний. Если это прерывание, что вызывает прерывание, это драйвер сетевой карты?
Я пытаюсь понять нижние детали веб-серверов. Мне интересно, если сервер, скажем, Apache, постоянно опрашивает новые запросы или работает ли он какой-то системой прерываний. Если это прерывание, что вызывает прерывание, это драйвер сетевой карты?
Ответы:
Короткий ответ: какая-то система прерываний. По сути, они используют блокирующий ввод / вывод, что означает, что они спят (блокируются), ожидая новых данных.
Сервер создает сокет прослушивания, а затем блокирует при ожидании новых подключений. В течение этого времени ядро переводит процесс в состояние прерывистого сна и запускает другие процессы. Это важный момент: непрерывный процессный опрос может привести к потере ресурсов процессора. Ядро может использовать системные ресурсы более эффективно, блокируя процесс, пока не будет выполнено работы.
Когда новые данные поступают в сеть, сетевая карта выдает прерывание.
Видя, что есть прерывание от сетевой карты, ядро через драйвер сетевой карты считывает новые данные с сетевой карты и сохраняет их в памяти. (Это должно быть сделано быстро и обычно обрабатывается внутри обработчика прерываний.)
Ядро обрабатывает вновь поступившие данные и связывает их с сокетом. Процесс, который блокирует этот сокет, будет помечен как работоспособный, что означает, что он теперь может быть запущен. Он не обязательно запускается немедленно (ядро может решить запустить другие процессы).
На досуге ядро разбудит заблокированный процесс веб-сервера. (Так как теперь он работает.)
Процесс веб-сервера продолжает выполняться, как будто времени не прошло. Его системный вызов блокировки возвращается и обрабатывает любые новые данные. Тогда ... перейдите к шагу 1.
accept
. Они являются (к счастью, или это было бы совершенно отстой!) Независимыми, асинхронно выполняющимися задачами. Когда устанавливаются соединения, они помещаются в очередь, откуда accept
их тянет. Только если их нет, он блокирует.
Здесь довольно много «нижних» деталей.
Во-первых, учтите, что в ядре есть список процессов, и в любой момент времени некоторые из этих процессов работают, а некоторые нет. Ядро позволяет каждому выполняющемуся процессу некоторое время процессора, затем прерывает его и переходит к следующему. Если нет запущенных процессов, то ядро, вероятно, выдаст команду типа HLT для процессора, которая приостанавливает работу процессора до тех пор, пока не произойдет аппаратное прерывание.
Где-то на сервере находится системный вызов, который говорит «дай мне что-нибудь сделать». Есть две широкие категории способов, которыми это может быть сделано. В случае Apache он вызывает accept
сокет, который Apache ранее открыл, вероятно, прослушивая порт 80. Ядро поддерживает очередь попыток подключения и добавляет в эту очередь каждый раз, когда получен TCP SYN . Как ядро узнает, что получен TCP SYN, зависит от драйвера устройства; для многих сетевых карт возможно получение аппаратного прерывания при получении сетевых данных.
accept
просит ядро вернуть мне следующую инициализацию соединения. Если очередь не была пуста, то accept
сразу возвращается. Если очередь пуста, то процесс (Apache) удаляется из списка запущенных процессов. Когда позднее соединение инициируется, процесс возобновляется. Это называется «блокирование», потому что для вызывающего его процесса accept()
выглядит как функция, которая не возвращает, пока не получит результат, который может пройти через некоторое время. За это время процесс больше ничего не может сделать.
После accept
возвращения Apache знает, что кто-то пытается установить соединение. Затем он вызывает fork, чтобы разделить процесс Apache на два идентичных процесса. Один из этих процессов продолжает обрабатывать HTTP-запрос, другой вызывает accept
снова, чтобы установить следующее соединение. Таким образом, всегда есть главный процесс, который делает только подпроцессы вызова accept
и вызова , а затем есть один подпроцесс для каждого запроса.
Это упрощение: это можно сделать с потоками, а не с процессами, а также можно сделать это fork
заранее, чтобы рабочий процесс был готов к работе при получении запроса, что снижает накладные расходы при запуске. В зависимости от того, как настроен Apache, он может выполнять одно из следующих действий.
Это первая широкая категория , как это сделать, и это называется блокирование операций ввода - вывода , так как системные вызовы , как accept
и read
и write
которые действуют на патрубках приостановит процесс , пока они не имеют что - то вернуть.
Другой широкий способ сделать это называется неблокирующим, основанным на событиях или асинхронным вводом-выводом . Это реализуется с помощью системных вызовов, таких как select
или epoll
. Каждый из них делает одно и то же: вы даете им список сокетов (или вообще файловых дескрипторов) и того, что вы хотите с ними делать, и ядро блокируется, пока не будет готово выполнить одну из этих вещей.
С этой моделью вы могли бы сказать ядру (с epoll
): «Скажите мне, когда будет новое соединение на порту 80 или новые данные для чтения на любом из этих 9471 других соединений, которые у меня открыты». epoll
блоки, пока одна из этих вещей не будет готова, тогда вы делаете это. Тогда вы повторяете. Системные вызовы , как accept
и read
и write
не блок, отчасти потому , что всякий раз , когда вы их называете, epoll
просто сказали, что они готовы , так что не было бы никаких оснований для блокирования, а также потому , что при открытии сокета или файла вы указываете , что вы хотите их в неблокирующем режиме, поэтому эти вызовы не будут EWOULDBLOCK
блокироваться вместо.
Преимущество этой модели в том, что вам нужен только один процесс. Это означает, что вам не нужно выделять стек и структуры ядра для каждого запроса. Nginx и HAProxy используют эту модель, и это большая причина, по которой они могут иметь дело с гораздо большим количеством соединений, чем Apache на аналогичном оборудовании.