Книга: UNIX: разработка сетевых приложений
Реализация 4.4BSD
Реализация 4.4BSD
Если вы никогда ранее не сталкивались с таким типом устройства сервера (несколько процессов, вызывающих функцию accept
на одном и том же прослушиваемом сокете), вас, вероятно, удивляет, что это вообще может работать. Пожалуй, здесь уместен краткий экскурс, описывающий реализацию этого механизма в Беркли-ядрах (более подробную информацию вы найдете в [128]).
Родитель сначала создает прослушиваемый сокет, а затем — дочерние процессы. Напомним, что каждый раз при вызове функции fork
происходит копирование всех дескрипторов в каждый дочерний процесс. На рис. 30.2 показана организация структур proc
(по одной структуре на процесс), одна структура file
для прослушиваемого дескриптора и одна структура socket
.
Рис. 30.2. Организация структур proc, file и socket
Дескрипторы — это просто индексы массива, содержащегося в структуре proc
, который ссылается на структуру file
. Одна из целей дублирования дескрипторов в дочерних процессах, осуществляемого функцией fork
, заключается в том, чтобы данный дескриптор в дочернем процессе ссылался на ту же структуру file
, на которую этот дескриптор ссылается в родительском процессе. Каждая структура file
содержит счетчик ссылок, который начинается с единицы, когда открывается первый файл или сокет, и увеличивается на единицу при каждом вызове функции fork и при каждом дублировании дескриптора (с помощью функции dup
). В нашем примере с N дочерними процессами счетчик ссылок в структуре file
будет содержать значение N+1 (учитывая родительский процесс, у которого по-прежнему открыт прослушиваемый дескриптор, хотя родительский процесс никогда не вызывает функцию accept
).
При запуске программы создается N дочерних процессов, каждый из которых может вызывать функцию accept
, и все они переводятся родительским процессом в состояние ожидания [128, с. 458]. Когда от клиента прибывает первый запрос на соединение, все N дочерних процессов «просыпаются», так как все они были переведены в состояние ожидания по одному и тому же «каналу ожидания» — полю so_timeo
структуры socket
, как совместно использующие один и тот же прослушиваемый дескриптор, указывающий на одну и ту же структуру socket
. Хотя «проснулись» все N дочерних процессов, только один из них будет связан с клиентом. Остальные N - 1 снова перейдут в состояние ожидания, так как длина очереди клиентских запросов снова станет равна нулю, после того как первый из дочерних процессов займется обработкой поступившего запроса.
Такая ситуация иногда называется thundering herd — более или менее дословный перевод будет звучать как «общая побудка», так как все N процессов должны быть выведены из спящего состояния, хотя нужен всего один процесс, и остальные потом снова «засыпают». Тем не менее этот код работает, хотя и имеет побочный эффект — необходимость «будить» слишком много дочерних процессов каждый раз, когда требуется принять (accept
) очередное клиентское соединение. В следующем разделе мы исследуем, как это влияет на производительность в целом.
- 1.8. История сетевого обеспечения BSD
- 9.4.1. Реализация графа в виде матрицы смежности
- Реализация языка SQL
- 9.2.1. Более строгая реализация стека
- 9.2 Реализация массива ftAID на платформе Windows NT
- Реализация семафоров в Linux
- 16.8. Реализация отношений в Core Data
- 10.16. Реализация с использованием семафоров System V
- Реализация очередей отложенных действий
- Реализация класса бинарных деревьев
- 8.1.4. The Mach BSD UNIX Server
- Desktop’изация BSD