UNIX: разработка сетевых приложений
struct hostent *hptr;...signal(SIGALRM, sig_alrm);...hptr = gethostbyname( ... );...}voidsig_alrm(int signo) {struct hostent *hptr;...hptr = gethostbyname( ... );...}Если главный поток управления в момент остановки находится в середине выполнения функции
(допустим, функция заполнила переменнуюgethostbynameи должна сейчас возвратить управление), а затем обработчик сигналов вызывает функциюhost, то поскольку в процессе существует только один экземпляр переменнойgethostbyname, эта переменная используется снова. При этом значения переменных, вычисленные при вызове из главного потока управления, заменяются значениями, вычисленными при вызове из обработчика сигнала.hostЕсли мы посмотрим на функции преобразования имен и адресов, представленные в этой главе и в главе 9, вместе с функциями
из главы 4, мы заметим следующее:inet_<i>XXX</i>■ Функции
,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).
Прежде всего заметим, что если ошибки не происходит, значение переменной
не изменяется. Поэтому мы не можем посмотреть значение этой переменной, пока мы не узнаем, что произошла ошибка (обычно на это указывает возвращаемое функцией значение -1).errnoБудем считать, что программа проверяет возвращаемое значение функции
и затем выводит значение переменнойclose, если произошла ошибка, как в следующем примере:errnoif (close(fd) < 0) {fprintf(stderr, "close error, errno = $d\n", errno);exit(1);}Существует небольшой промежуток времени между сохранением кода ошибки в переменной errno в тот момент, когда системный вызов возвращает управление, и выводом этого значения программой. В течение этого промежутка другой программный поток внутри процесса (то есть обработчик сигналов) может изменить значение переменной
. Если, например, при вызове обработчика сигналов главный поток управления находится междуerrnoиcloseи обработчик сигналов делает какой-то другой системный вызов, возвращающий ошибку (допустим, вызывается функцияfprintf), то значение переменнойwrite, записанное при вызове функцииerrno, заменяется на значение, записанное при вызове функцииclose.writeПри рассмотрении этих двух проблем в связи с обработчиками сигналов одним из решений проблемы с функцией
(возвращающей указатель на статическую переменную) будет не вызывать из обработчика сигнала функции, которые не допускают повторное вхождение. Проблемы с переменнойgethostbyname(одна глобальная переменная, которая может быть изменена обработчиком сигнала) можно избежать, перекодировав обработчик сигнала так, чтобы он сохранял и восстанавливал значение переменнойerrnoследующим образом:errnovoid sig_alrm(int signo) {int errno_save;errno_save = errno; /* сохраняем значение этой переменнойпри вхождении */