11.18. Функции, допускающие повторное вхождение

11.18. Функции, допускающие повторное вхождение

Функция gethostbyname из раздела 11.3 имеет интересную особенность, которую мы еще не рассматривали: она не допускает повторное вхождение (nonreentrant). Мы еще столкнемся с этой проблемой в главе 23, когда будем обсуждать потоки, но не менее интересно найти решение этой проблемы сейчас, без необходимости обращаться к понятию потоков.

Сначала посмотрим, как эта функция работает. Если мы изучим ее исходный код (это несложно, поскольку исходный код для всей реализации BIND свободно доступен), то увидим, что обе функции — и gethostbyname, и gethostbyaddr — содержатся в одном файле, который имеет следующий вид:

static struct hostent host; /* здесь хранится результат */

struct hostent*

gethostbyname(const char *hostname) {

 return(gethostbyname2(hostname, family));

}

struct hostent*

gethostbyname2(const char *hostname, int family) {

 /* вызов функций DNS для запроса А или AAAA */

 /* заполнение структуры адреса узла */

 return(&host);

}

struct hostent*

gethostbyaddr(const char *addr, size_t len, int family) {

 /* вызов функций DNS для запроса PTR в домене in-addr.arpa */

 /* заполнение структуры адреса узла */

 return(&host);

}

Мы выделили полужирным шрифтом спецификатор класса памяти static итоговой структуры, потому что основная проблема в нем. Тот факт, что эти три функции используют общую переменную host, представляет другую проблему, которую мы обсудим в упражнении 11.1. (Вспомните табл. 11.4.) Функция gethostbyname2 появилась в BIND 4.9.4 с добавлением поддержки IPv6. Мы будем игнорировать тот факт, что когда мы вызываем функцию gethostbyname, задействуется функция gethostbyname2, поскольку это не относится к предмету обсуждения.

Проблема повторного вхождения может возникнуть в нормальном процессе Unix, вызывающем функцию gethostbyname или gethostbyaddr и из управляющего элемента главного потока, и из обработчика сигнала. Когда вызывается обработчик сигнала (допустим, это сигнал SIGALRM, который генерируется раз в секунду), главный поток управляющего элемента процесса временно останавливается и вызывается функция обработки сигнала. Рассмотрим следующую ситуацию:

main() {

 struct hostent *hptr;

 ...

 signal(SIGALRM, sig_alrm);

 ...

 hptr = gethostbyname( ... );

 ...

}

void

sig_alrm(int signo) {

 struct hostent *hptr;

 ...

 hptr = gethostbyname( ... );

 ...

}

Если главный поток управления в момент остановки находится в середине выполнения функции gethostbyname (допустим, функция заполнила переменную host и должна сейчас возвратить управление), а затем обработчик сигналов вызывает функцию gethostbyname, то поскольку в процессе существует только один экземпляр переменной host, эта переменная используется снова. При этом значения переменных, вычисленные при вызове из главного потока управления, заменяются значениями, вычисленными при вызове из обработчика сигнала.

Если мы посмотрим на функции преобразования имен и адресов, представленные в этой главе и в главе 9, вместе с функциями inet_XXX из главы 4, мы заметим следующее:

? Функции gethostbyname, gethostbyname2, gethostbyaddr, getservbyname и getservbyport традиционно не допускают повторного вхождения, поскольку все они возвращают указатель на статическую структуру.

Некоторые реализации, поддерживающие программные потоки (Solaris 2.x), предоставляют версии этих четырех функций, допускающие повторное вхождение, с именами, оканчивающимися суффиксом _r. О них рассказывается в следующем разделе.

В качестве альтернативы некоторые реализации с поддержкой программных потоков (Digital Unix 4.0 и HP_UX 10.30) предоставляют версии этих функций, допускающие повторное вхождение за счет использования собственных данных программных потоков.

? Функции inet_pton и inet_ntop всегда допускают повторное вхождение.

? Исторически функция inet_ntoa не допускает повторное вхождение, но некоторые реализации с поддержкой потоков предоставляют версию, допускающую повторное вхождение, которая строится на основе собственных данных потоков.

? Функция getaddrinfo допускает повторное вхождение, только если она сама вызывает функции, допускающие повторное вхождение, то есть если она вызывает соответствующую версию функции gethostbyname или getservbyname для имени узла или имени службы. Одной из причин, по которым вся память для результатов ее выполнения выделяется динамически, является возможность повторного вхождения.

? Функция getnameinfo допускает повторное вхождение, только если она сама вызывает такие функции, то есть если она вызывает соответствующую версию функции gethostbyaddr для получения имени узла или функции getservbyport для получения имени службы. Обратите внимание, что обе результирующих строки (для имени узла и для имени службы) размещаются в памяти вызывающим процессом, чтобы обеспечить возможность повторного вхождения.

Похожая проблема возникает с переменной errno. Исторически существовало по одной копии этой целочисленной переменной для каждого процесса. Если процесс выполняет системный вызов, возвращающий ошибку, то в этой переменной хранится целочисленный код ошибки. Например, функция close из стандартной библиотеки языка С может выполнить примерно такую последовательность действий:

? поместить аргумент системного вызова (целочисленный дескриптор) в регистр;

? поместить значение в другой регистр, указывая, что был сделан системный вызов функции close;

? активизировать системный вызов (переключиться на ядро со специальной инструкцией);

? проверить значение регистра, чтобы увидеть, что произошла ошибка;

? если ошибки нет, возвратить (0);

? сохранить значение какого-то другого регистра в переменной errno;

? возвратить (-1).

Прежде всего заметим, что если ошибки не происходит, значение переменной errno не изменяется. Поэтому мы не можем посмотреть значение этой переменной, пока мы не узнаем, что произошла ошибка (обычно на это указывает возвращаемое функцией значение -1).

Будем считать, что программа проверяет возвращаемое значение функции close и затем выводит значение переменной errno, если произошла ошибка, как в следующем примере:

if (close(fd) < 0) {

 fprintf(stderr, "close error, errno = $d ", errno);

 exit(1);

}

Существует небольшой промежуток времени между сохранением кода ошибки в переменной errno в тот момент, когда системный вызов возвращает управление, и выводом этого значения программой. В течение этого промежутка другой программный поток внутри процесса (то есть обработчик сигналов) может изменить значение переменной errno. Если, например, при вызове обработчика сигналов главный поток управления находится между close и fprintf и обработчик сигналов делает какой-то другой системный вызов, возвращающий ошибку (допустим, вызывается функция write), то значение переменной errno, записанное при вызове функции close, заменяется на значение, записанное при вызове функции write.

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

void sig_alrm(int signo) {

int errno_save;

errno_save = errno; /* сохраняем значение этой переменной

                       при вхождении */

if (write( ... ) != nbytes)

 fprintf(stderr, "write error, errno = %d ", errno);

 errno = errno_save; /* восстанавливаем значение этой переменной

                        при завершении */

}

В этом коде мы также вызываем функцию fprintf, стандартную функцию ввода-вывода, из обработчика сигнала. Это еще одна проблема повторного вхождения, поскольку многие версии функций стандартной библиотеки ввода-вывода не допускают повторного вхождения: стандартные функции ввода-вывода не должны вызываться из обработчиков сигналов.

Мы вернемся к проблеме повторного вхождения в главе 26 и увидим, как проблема с переменной errno решается с помощью потоков. В следующем разделе описываются некоторые версии функций имен узлов, допускающие повторное вхождение.

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

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

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

7.4. Повторное использование

Из книги Объектно-ориентированный анализ и проектирование с примерами приложений на С++ автора Буч Гради

7.4. Повторное использование Элементы повторного использования Любой программный продукт (текст программы, архитектура, сценарий или документация) может быть использован повторно. Как сказано в главе 3, в объектно-ориентированных языках программирования первичным


Повторное использование (Reusability)

Из книги Основы объектно-ориентированного программирования автора Мейер Бертран

Повторное использование (Reusability) Определение: повторное использованиеПовторное использование есть способность элементов ПО служить для построения многих различных приложений. Необходимость и возможность повторного использования возникает из наблюдений сходства


Повторное использование персонала

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

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


Повторное использование проектов и спецификаций

Из книги Разработка приложений в среде Linux. Второе издание автора Джонсон Майкл К.

Повторное использование проектов и спецификаций Этот подход является, по существу, более организованной версией предыдущего - повторного использования знаний, умений и опыта. Как показало обсуждение вопроса о документации, само представление проекта как независимого


Повторное использование исходного текста

Из книги Linux программирование в примерах автора Роббинс Арнольд

Повторное использование исходного текста Несмотря на полезность повторного использования персонала, проектов и спецификаций, здесь не реализуется ключевая цель повторного использования. Если мы хотели бы найти программистский эквивалент повторно используемых


Повторное использование абстрактных модулей

Из книги C++ для начинающих автора Липпман Стенли

Повторное использование абстрактных модулей Все предыдущие подходы, несмотря на их ограниченную применимость, осветили важные аспекты проблемы повторного использования:[x]. Повторное использование персонала необходимо, но недостаточно. Наилучшие повторно


Динамическое создание и повторное связывание

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

Динамическое создание и повторное связывание Что не было показано при описании структуры объектов периода выполнения, так это в высшей степени динамичная природа настоящей ОО-модели. Статическая и ориентированная на стеки политика управления объектами характерна для


Повторное объявление функции как атрибута

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

Повторное объявление функции как атрибута Повторные объявления позволяют активно применять один из центральных принципов модульности - принцип Унифицированного Доступа (Uniform Access).Напомним (см. лекцию 3), что этот принцип утверждает (первоначально в менее технических


Повторное объявление функции как атрибута

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

Повторное объявление функции как атрибута Правило Утверждения Переобъявления нуждается в небольшом дополнении ввиду возможности при повторном объявлении задать функцию как атрибут. Что произойдет с предусловием функции и ее постусловием, если таковые имелись?Атрибут


Вектора, допускающие сложение

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

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


Типизация и повторное объявление

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

Типизация и повторное объявление Повторное объявление компонентов не требует сохранения сигнатуры. Пока оно виделось нам как замена одного алгоритма другим или - для отложенного компонента - запись алгоритма, соответствующего ранее заданной спецификации.Но, воплощая


Интерфейс и повторное использование реализаций

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

Интерфейс и повторное использование реализаций Знакомясь с объектным подходом по другим источникам, вы могли видеть в них предостережения использования "наследования реализаций". Однако в нем нет ничего плохого.Повторное использование имеет две формы: использование


3.6. Повторное использование идентификаторов

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

3.6. Повторное использование идентификаторов Структура ipc_perm (раздел 3.3) содержит переменную seq, в которой хранится порядковый номер канала. Эта переменная представляет собой счетчик, заводимый ядром для каждого объекта IPC в системе. При удалении объекта IPC номер канала


12.5. Повторное открытие журнальных файлов

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

12.5. Повторное открытие журнальных файлов Большинство системных демонов ведут журнальные файлы, записывая в них все, что они делают. Поскольку многие системы Unix месяцами работают без остановки, эти журнальные файлы могут стать достаточно большими. Простое периодическое


10.4.4. Системные вызовы, допускающие повторный запуск

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

10.4.4. Системные вызовы, допускающие повторный запуск Значение EINTR для errno (см. раздел 4.3 «Определение ошибок») указывает, что системный вызов был прерван. Хотя с этим значением ошибки может завершаться большое количество системных вызовов, двумя наиболее значительными


11.3.3. Повторное возбуждение исключения

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

11.3.3. Повторное возбуждение исключения Может оказаться так, что в одном предложении catch не удалось полностью обработать исключение. Выполнив некоторые корректирующие действия, catch-обработчик может решить, что дальнейшую обработку следует поручить функции, расположенной