30.7. Сервер TCP с предварительным порождением процессов и защитой вызова accept блокировкой файла

30.7. Сервер TCP с предварительным порождением процессов и защитой вызова accept блокировкой файла

Описанная выше реализация, позволяющая нескольким процессам вызывать функцию accept на одном и том же прослушиваемом дескрипторе, возможна только для систем 4.4BSD, в которых функция accept реализована внутри ядра. Ядра системы SVR4, в которых accept реализована как библиотечная функция, не допускают этого. В самом деле, если мы запустим сервер из предыдущего раздела, в котором имеется несколько дочерних процессов, в Solaris 2.5 (система SVR4), то вскоре после того, как клиенты начнут соединяться с сервером, вызов функции accept в одном из дочерних процессов вызовет ошибку EPROTO, что свидетельствует об ошибке протокола.

ПРИМЕЧАНИЕ

Причины возникновения этой проблемы с библиотечной версией функции accept в SVR4 связаны с реализацией потоков STREAMS и тем фактом, что библиотечная функция accept не является атомарной операцией. В Solaris 2.6 эта проблема решена, но в большинстве реализаций SVR4 она остается.

Решением этой проблемы является защита вызова функции accept при помощи блокировки, так что в данный момент времени только один процесс может быть блокирован в вызове этой функции. Другие процессы также будут блокированы, так как они будут стремиться установить блокировку для вызова функции accept.

Существует несколько способов реализации защиты вызова функции accept, о которых рассказывается во втором томе[2] данной серии. В этом разделе мы используем блокировку файла функцией fcntl согласно стандарту POSIX.

Единственным изменением в функции main (см. листинг 30.6) будет добавление вызова функции my_lock_init перед началом цикла, в котором создаются дочерние процессы:

+ my_lock_init("/tmp/lock.XXXXXX"); /* один файл для всех дочерних

                                       процессов */

  for (i = 0; i < nchildren; i++)

   pids[i] = child_make(i, listenfd, addrlen); /* возвращение

                                       родительского процесса */

Функция child_make остается такой же, как в листинге 30.8. Единственным изменением функции child_main (см. листинг 30.9) является блокирование перед вызовом функции accept и снятие блокировки после завершения этой функции:

  for (;;) {

   clilen = addrlen;

+  my_lock_wait();

    connfd = Accept(listenfd, cliaddr, &clilen);

+   my_lock_release();

    web_child(connfd); /* обработка запроса */

    Close(connfd);

   }

В листинге 30.12 показана наша функция my_lock_init, в которой используется блокировка файла согласно стандарту POSIX.

Листинг 30.12. Функция my_lock_init: блокировка файла

//server/lock_fcntl.c

 1 #include "unp.h"

 2 static struct flock lock_it, unlock_it;

 3 static int lock_fd = -1;

 4 /* fcntl() не выполнится, если не будет вызвана функция my_lock_init() */

 5 void

 6 my_lock_init(char *pathname)

 7 {

 8  char lock_file[1024];

 9  /* копируем строку вызывающего процесса на случай, если это константа */

10  strncpy(lock_file, pathname, sizeof(lock_file));

11  lock_fd = Mkstemp(lock_file);

12  Unlink(lock_file); /* но lock_fd остается открытым */

13  lock_it.l_type = F_WRLCK;

14  lock_it.l_whence = SEEK_SET;

15  lock_it.l_start = 0;

16  lock_it.l_len = 0;

17  unlock_it.l_type = F_UNLCK;

18  unlock_it.l_whence = SEEK_SET;

19  unlock_it.l_start = 0;

20  unlock_it.l_len = 0;

21 }

9-12 Вызывающий процесс задает шаблон для имени файла в качестве аргумента функции my_lock_init, и функция mkstemp на основе этого шаблона создает уникальное имя файла. Затем создается файл с этим именем и сразу же вызывается функция unlink, в результате чего имя файла удаляется из каталога. Если в программе впоследствии произойдет сбой, то файл исчезнет безвозвратно. Но пока он остается открытым в одном или нескольких процессах (иными словами, пока счетчик ссылок для этого файла больше нуля), сам файл не будет удален. (Отметим, что между удалением имени файла из каталога и закрытием открытого файла существует фундаментальная разница.)

13-20 Инициализируются две структуры flock: одна для блокирования файла, другая для снятия блокировки. Блокируемый диапазон начинается с нуля (l_whence =SEEK_SET, l_start=0). Значение l_len равно нулю, то есть блокирован весь файл. В этот файл ничего не записывается (его длина всегда равна нулю), но такой тип блокировки в любом случае будет правильно обрабатываться ядром.

ПРИМЕЧАНИЕ

Сначала автор инициализировал эти структуры при объявлении:

static struct flock lock_it = { F_WRLCK, 0, 0, 0, 0 };

static struct flock unlock_it = { F_UNLCK, 0, 0, 0, 0 };

но тут возникли две проблемы: у нас нет гарантии, что константа SEEK_SET равна нулю, но, что более важно, стандарт POSIX не регламентирует порядок расположения полей этой структуры. POSIX гарантирует только то, что требуемые поля присутствуют в структуре. POSIX не гарантирует какого-либо порядка следования полей структуры, а также допускает наличие в ней полей, не относящихся к стандарту POSIX. Поэтому когда требуется инициализировать эту структуру (если только не нужно инициализировать все поля нулями), это приходится делать через фактический код С, а не с помощью инициализатора при объявлении структуры.

Исключением из этого правила является ситуация, когда инициализатор структуры обеспечивается реализацией. Например, при инициализации взаимного исключения в POSIX в главе 26 мы писали:

pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER;

Тип данных pthread_mutex_t — это некая структура, но инициализатор предоставляется реализацией и может быть различным для разных реализаций.

В листинге 30.13 показаны две функции, которые устанавливают и снимают блокировку с файла. Они представляют собой вызовы функции fcntl, использующие структуры, инициализированные в листинге 30.12.

Листинг 30.13. Функции my_lock_wait (установление блокировки файла) и my_lock_release (снятие блокировки файла)

//server/lock_fcntl.c

23 void

24 my_lock_wait()

25 {

26  int rc;

27  while ((rc = fcntl(lock_ld, F_SETLKW, &lock_it)) < 0 {

28   if (errno == EINTR)

29    continue;

30   else

31    errsys("fcntl error for my_lock_wait");

32  }

33 }

34 void

35 my_lock_release()

36 {

37  if (fcntl(lock_fd, F_SETLKW, &unlock_it)) < 0)

38   errsys("fcntl error for my_lock_release");

39 }

Новая версия нашего сервера с предварительным порождением процессов работает теперь под SVR4, гарантируя, что в данный момент времени только один дочерний процесс блокирован в вызове функции accept. Сравнивая строки 2 и 3 в табл. 30.1 (результаты для серверов Digital Unix и BSD/OS), мы видим, что такой тип блокировки увеличивает время, затрачиваемое центральным процессором на узле сервера.

ПРИМЕЧАНИЕ

Веб-сервер Apache (http://www.apache.org) использует технологию предварительного порождения процессов, причем если позволяет реализация, все дочерние процессы блокируются в вызове функции accept, иначе используется блокировка файла для защиты вызова accept.

Поделитесь на страничке

Следующая глава >

Похожие главы из других книг

С защитой от воды

Из книги Журнал `Компьютерра` N733 автора Журнал «Компьютерра»

С защитой от воды Автор: Олег ВолошинНечасто бывает, что названия некоторых ноутбуков ассоциируются с прочностью. Серия Panasonic Toughbook - как раз тот самый случай, благо большинство из нас слышало про защищенные модели ноутбуков этой уважаемой фирмы. Посему я чрезвычайно


6.5.1. Действие ACCEPT

Из книги Iptables Tutorial 1.1.19 автора Andreasson Oskar

6.5.1. Действие ACCEPT Данная операция не имеет дополнительных ключей. Если над пакетом выполняется действие ACCEPT, то пакет прекращает движение по цепочке (и всем вызвавшим цепочкам, если текущая цепочка была вложенной) и считается ПРИНЯТЫМ (то бишь пропускается), тем не менее,


16.14. Сервер kHTTPd — веб-сервер уровня ядра

Из книги Linux: Полное руководство автора Колисниченко Денис Николаевич

16.14. Сервер kHTTPd — веб-сервер уровня ядра В операционной системе все процессы можно разделить на два типа: процессы уровня ядра и пользовательские процессы. Процесс уровня ядра запускается и работает очень быстро по сравнению с относительно неповоротливым


Операции со ждущей блокировкой

Из книги QNX/UNIX [Анатомия параллелизма] автора Цилюрик Олег Иванович

Операции со ждущей блокировкой Захват и освобождение ждущей блокировки Вызов функций ожидания может производиться только внутри блока захвата и освобождения ждущей блокировки:int pthread_sleepon_lock(void);int pthread_sleepon_unlock(void);Функция захвата pthread_sleepon_lock() возвращает следующие


Пример A-5. encryptedpw: Передача файла на ftp-сервер, с использованием пароля

Из книги UNIX: разработка сетевых приложений автора Стивенс Уильям Ричард

Пример A-5. encryptedpw: Передача файла на ftp-сервер, с использованием пароля #!/bin/bash# Модификация примера "ex72.sh", добавлено шифрование пароля.# Обратите внимание: этот вариант все еще нельзя считать безопасным,#+ поскольку в сеть пароль уходит в незашифрованном виде.# Используйте


4.6. Функция accept

Из книги Выжить в цифровом мире. Иллюстрированные советы от «Лаборатории Касперского» автора Дьяков Михаил

4.6. Функция accept Функция accept вызывается сервером TCP для возвращения следующего установленного соединения из начала очереди полностью установленных соединений (см. рис. 4.2). Если очередь полностью установленных соединений пуста, процесс переходит в состояние ожидания (по


5.11. Прерывание соединения перед завершением функции accept

Из книги автора

5.11. Прерывание соединения перед завершением функции accept Существует другое условие, аналогичное прерванному системному вызову, пример которого был описан в предыдущем разделе. Оно может привести к возвращению функцией accept нефатальной ошибки, в случае чего следует


16.6. Неблокируемая функция accept

Из книги автора

16.6. Неблокируемая функция accept Как было сказано в главе 6, функция select сообщает, что прослушиваемый сокет готов для чтения, когда установленное соединение готово к обработке функцией accept. Следовательно, если мы используем функцию select для определения готовности входящих


30.6. Сервер TCP с предварительным порождением процессов без блокировки для вызова accept

Из книги автора

30.6. Сервер TCP с предварительным порождением процессов без блокировки для вызова accept В первом из рассматриваемых нами «усовершенствованных» серверов используется технология, называемая предварительным созданием процессов (preforking). Вместо того чтобы вызывать функцию fork


30.8. Сервер TCP с предварительным порождением процессов и защитой вызова accept при помощи взаимного исключения

Из книги автора

30.8. Сервер TCP с предварительным порождением процессов и защитой вызова accept при помощи взаимного исключения Как мы уже говорили, существует несколько способов синхронизации процессов путем блокирования. Блокировка файла по стандарту POSIX, рассмотренная в предыдущем


30.9. Сервер TCP с предварительным порождением процессов: передача дескриптора

Из книги автора

30.9. Сервер TCP с предварительным порождением процессов: передача дескриптора Последней модификацией нашего сервера с предварительным порождением процессов является версия, в которой только родительский процесс вызывает функцию accept, а затем «передает» присоединенный


30.11. Сервер TCP с предварительным порождением потоков, каждый из которых вызывает accept

Из книги автора

30.11. Сервер TCP с предварительным порождением потоков, каждый из которых вызывает accept Ранее в этой главе мы обнаружили, что версии, в которых заранее создается пул дочерних процессов, работают быстрее, чем те, в которых для каждого клиентского запроса приходится вызывать


30.12. Сервер с предварительным порождением потоков: основной поток вызывает функцию accept

Из книги автора

30.12. Сервер с предварительным порождением потоков: основной поток вызывает функцию accept Последняя рассматриваемая нами версия сервера устроена следующим образом: главный поток создает пул потоков при запуске сервера, после чего он же вызывает функцию accept и передает


Совет 64: Учеба под защитой

Из книги автора

Совет 64: Учеба под защитой Когда юный Михаил Ломоносов шел через всю Россию учиться наукам, для защиты от лихих людей ему пришлось присоединиться к рыбному обозу. Прошло 300 лет, путь к знаниям стал легче и проще, но едва ли безопаснее. Интернет быстро снабдит пытливый ум