20. Нелокальные Выходы
Иногда, когда ваша программа обнаруживает необычную ситуацию внутри глубоко вложенного набора обращений к функциям, Вы можете захотеть немедленно возвратиться к внешнему уровню управления. Этот раздел описывает, как делать такие нелокальные выходы, используя setjmp и longjmp функции.
20.1 Введение в нелокальные Выходы
Вот пример ситуации, где нелокальный выход может быть полезен: предположим, Вы имеете интерактивную программу, которая имеет "основной цикл" который запрашивает и выполняет команды. Предположите, что команда "read" читает ввод из файла, делая некоторый лексический анализ и анализируя ввод. Если входная ошибка низкого уровня обнаружена, то было бы полезно возвратиться немедленно в " основной цикл " вместо того, чтобы иметь необходимость делать лексический анализ, синтаксический анализ, и все фазы обработки, которые должны явно иметь дело с ситуациями ошибки, первоначально обнаруженными вложенными обращениями.
Некоторым образом нелокальный выход подобен использованию "return", чтобы возвратиться из функции. Но в то время как "return" отказывается только от обращения, пересылая управление обратно к функции, из которой оно вызывалось, нелокальный выход может потенциально отказываться от многих уровней вложенных обращений к функциям.
Вы определяете куда возвращать управление при нелокальных выходах, вызывая функцию setjmp. Эта функция сохраняет информацию относительно среды выполнения, в которой появляется обращение к setjmp в объекте типа jmp_buf. После обращения к setjmp выполнение программы продолжается как обычно, но если позже вызывается longjmp с соответствующим объектом jmp_buf, управление передается обратно в то место, где вызывалась setjmp. Возвращаемое значение из setjmp используется, чтобы отличить обычный возврат и возврат, сделанный обращением к longjmp, так что обращения к setjmp обычно появляются в ` if '.
Вот пример программы, описанный выше:
Функция abort_to_main_loop вызывает непосредственную передачу управления в main программы, независимо от того, где она вызывается.
#include
#include #include jmp_buf main_loop; void abort_to_main_loop (int status) { longjmp (main_loop, status); } int main (void) { while (1) if (setjmp (main_loop)) puts ("Back at main loop...."); else do_command (); } void do_command (void) { char buffer[128]; if (fgets (buffer, 128, stdin) == NULL) abort_to_main_loop (-1); else exit (EXIT_SUCCESS); }
Способ управления внутри функции main может показаться сначала немного таинственным, но это - фактически общая идиома для setjmp. Нормальное обращение к setjmp возвращает нуль, так что "else"-часть условного выражения выполнена. Если abort_to_main_loop вызывается где-нибудь внутри выполнения команды do, то это фактически действует как будто обращение к setjmp в main возвращалось со значением -1.
Так, общий шаблон для использования setjmp выглядит вроде:
if (setjmp (buffer)) /* Код, для выполнения после преждевременного возврата. */ . . . else /* Код, который будет выполнен после обычной установки возвращающей отметки. */ . . .
20.2 Подробности нелокальных Выходов
Имеются некоторые подробности относительно функций и структур данных, используемых для выполнения нелокальных выходов.
Эти средства объявлены в " setjmp.h ".
Объекты типа jmp_buf содержат информацию о состоянии, которое будет восстановлено при нелокальном выходе.
jmp_buf (тип данных)
Содержимое jmp_buf идентифицирует конкретное место возвращения.
setjmp сохраняет информацию относительно состояния выполнения программы в state и возвращает нуль. Если longjmp позже используется, чтобы выполнить нелокальный выход к этому состоянию, setjmp возвращает значение отличное от нуля.
int setjmp (jmp_buf state) (макрос)
Эта функция восстанавливает текущее выполнение в состояние, сохраненное в state, и продолжает выполнение от обращения к setjmp. Возвращение из setjmp посредством longjmp возвращает значение аргумента, который был передан к longjmp, а не 0. (Но если значение задано как 0, setjmp возвращает 1).
void longjmp (jmp_buf state, int value)
Имеется множество неизвестных, но важных ограничений на использование setjmp и longjmp. Большинство этих ограничений присутствует, потому что нелокальные выходы требуют некоторых волшебных свойств от части компилятора Cи и могут взаимодействовать с другими частями языка странными способами. setjmp - фактически макрокоманда без определения функции, так что Вы не должны пробовать к " #undef " ее или брать адрес. Кроме того, обращения к setjmp безопасны в только следующих контекстах:
- Как тестовое выражение в операторе выбора или цикла (типа "if" или "while").
- Как один операнд равенства или сравнения, которое появляется как тестовое выражение оператора выбора или цикла. Другой операнд должен быть целочисленным постоянным выражением.
- Как операнд унарного оператора "!", который появляется как как тестовое выражение оператора выбора или цикла.
- Как выражение утверждения.
Вы должны использовать в longjmp аргумент отличный от нуля. То что longjmp отказывается передавать обратно аргумент нуля как возвращаемое значение из setjmp, предназначено для безопасности при случайном неправильном употреблении и не является хорошим стилем программирования.
Когда Вы выполняете нелокальный выход, все доступные объекты сохраняют любые значения, которые они имели во время вызова longjmp. Исключение - значения динамических локальных переменных, локальных для функции, содержащей обращение к setjmp, будут изменены и начиная с обращения на setjmp являются неопределенными, если Вы не объявили их отдельно.
20.3 Нелокальные Выходы и Сигналы
В системах UNIX BSD, setjmp и longjmp также сохраняют и восстанавливают набор блокированных сигналов; см. Раздел 21.7 [Блокирование Сигналов]. Однако, POSIX.1 стандарт требует чтобы setjmp и longjmp не изменяли набор блокированных сигналов, и обеспечивает дополнительную пару функций (sigsetjmp и sigsetjmp) чтобы получить поведение BSD функций.
Поведение setjmp и longjmp в библиотеке GNU управляется макрокомандами теста возможностей; см. Раздел 1.3.4 [Макрокоманды Проверки Возможностей]. Значение по умолчанию в системе GNU POSIX.1 поведение, а не поведение BSD.
Средства в этом разделе объявлены в заглавном файле " setjmp.h ".
Подобен jmp_buf, за исключением того, что он может также сохранять информацию о состоянии набора блокированных сигналов.
sigjmp_buf (тип данных)
Подобна setjmp. Если savesigs отличен от нуля, набор блокированных сигналов сохранен в state и будет восстановлен, если siglongjmp позже будет выполнена с этим state.
int sigsetjmp (sigjmp_buf state, int savesigs) (функция)
void siglongjmp (sigjmp_buf state, int value) (функция)
Подобна longjmp кроме типа аргумента state. Если обращение к sigsetjmp, которое устанавило это состояние, использовало savesigs флаг отличный от нуля, siglongjmp также восстанавливает набор блокированных сигналов.
21. Обработка Сигнала
Сигнал - программное прерывание процесса. Операционная система использует сигналы, чтобы сообщить исключительные ситуации выполняемой программе. Некоторые сигналы сообщают об ошибках типа ссылок к недопустимым адресам памяти; другие сообщают асинхронные события, типа разъединения телефонной линии.
Библиотека GNU C определяет ряд типов сигналов, каждый для конкретного вида события. Некоторые виды событий делают нецелесообразным или невозможным обычное продолжение программы, и соответствующие сигналы обычно прерывают программу. Другие виды сигналов сообщают о безобидных событиях, и игнорируются по умолчанию.
Если Вы ожидаете событие, которое вызывает сигналы, Вы можете определить функцию-обработчик и сообщить операционной системе, чтобы она выполнила ее, когда придет заданный тип сигнала.
В заключение нужно сказать, что один процесс может посылать сигнал другому процессу; это позволяет родительскому процессу прерывать дочерние, или сообщаться и синхронизироваться двум связанным процессам.
21.1 Базисные Понятия Сигналов
Этот раздел объясняет базисные понятия того, как сгенерированы сигналы, что случается после получения сигнала, и как программы могут обрабатывать сигналы.
Некоторые виды Сигналов
Сигнал сообщает об исключительном событии. Вот - некоторые из событий, которые могут вызывать (или генерировать) сигнал:
- Ошибка в программе типа деления на нуль или выдачой адреса вне допустимого промежутка.
- Запрос пользователя, чтобы прерывать или завершить программу.
Большинство сред допускают пользователю приостанавливать программу, печатая C-z, или завершать с C-c.
- Окончание дочернего процесса.
- Окончание ожидания таймера или будильника.
- Обращение "уничтожить процесс" из другого (или из этого) процесса. Сигналы - ограниченная но полезная форма межпроцессорной связи.
Каждый из этих видов событий (за исключением явных обращений, чтобы уничтожать и вызывать) генерирует собственный вид сигнала. Различные виды сигналов перечислены и описываются подробно в Разделе 21.2 [Стандартные Сигналы].
Понятия Порождения Сигналов
Вообще, события, которые генерируют сигналы, относятся к трем главным категориям: ошибки, внешние события, и явные запросы.
Ошибки означают, что программа сделала кое-что недопустимое и не может продолжать выполнение. Но не все виды ошибок генерируют сигналы фактически, как раз наоборот. Например, открытие несуществующего файла - ошибка, но она не вызывает сигнал; взамен, open возвращает -1. Вообще, ошибки, которые обязательно связаны с некоторыми библиотечными функциями, сообщаются возвратом значения, которое указывает ошибку. Ошибки, которые вызывают сигналы могут случаться где-нибудь в программе, а не только в вызовах из библиотек. Они включают деление на нуль и недопустимые адреса памяти.
Явный запрос означает использование библиотечной функции типа kill, чья цель - специально генерировать сигнал.
Сигналы могут быть сгенерированы синхронно или асинхронно. Синхронный сигнал относится к специфическому действию в программе, и вызывается (если не блокирован) в течение этого действия.
Асинхронные сигналы сгенерированы снаружи при контроле над процессом, который получает их. Эти сигналы занимают непредсказуемое время в течение выполнения. Внешние события генерируют сигналы асинхронно, и так делают явные запросы, которые обращаются к некоторому другому процессу.
Данный тип сигнала является обычно синхронным или асинхронным. Например, сигналы для ошибок обычно синхронны. Но любой тип сигнала может быть сгенерирован синхронно или асинхронно с явным запросом.
Как Передаются Сигналы
Когда сигнал сгенерирован, он откладывается. Обычно он задерживается на короткий период времени и потом передаеся процессу. Однако, если этот вид сигнала в настоящее время блокирован, он может оставаться отложенным, пока сигнал этого вида не откроют. Если только он откроется, он будет передан немедленно. См. Раздел 21.7 [Блокированные Сигналы].
Когда сигнал - передан, или сразу же или после задержки, выполняется заданное действие для этого сигнала. Для некоторых сигналов, типа SIGKILL и SIGSTOP, действие фиксировано, но для большинства сигналов, программа имеет выбор: игнорировать сигнал, определить функцию обработчика, или принять заданное по умолчанию действие для этого вида сигнала. Программа определяет свой выбор используя функции типа signal или sigaction (см. Раздел 21.3 [Действия Сигнала]). Мы иногда говорим, что обработчик захватывает сигнал. В то время как обработчик выполняется, этот специфический сигнал обычно блокируется.
Если заданное действие для вида сигнала - игнорировать его, то любой такой сигнал, будет отброшен немедленно. Это случается, даже если сигнал также блокирован в это время. Сигнал, отброшенный таким образом, передан не будет никогда, даже если программа впоследствии определяет другое действие для этого вида сигнала и откроет его.
Если прибывает сигнал, который программа не обрабатывает, и не игнорирует, происходит заданное по умолчанию действие. Каждый вид сигнала имеет собственное заданное по умолчанию действие, зарегистрированное ниже (см. Раздел 21.2 [Стандартные Сигналы]). Для большинства видов сигналов, заданное по умолчанию действие должно завершить процесс. Для некоторых видов сигналов, которые представляют "безобидные" события, заданное по умолчанию действие должны не делать ничего.
Когда сигнал завершает процесс, родительский процесс может определять причину окончания, исследуя код состояния окончания, сообщенный wait или waitpid функциями. (Это обсуждено более подробно в Разделе 23.6 [Завершение Процесса].) информация, которую он может получать, включает предложение, что окончание было из-за сигнала, и вида включаемого сигнала. Если программа, которую Вы выполняете из оболочки, завершена сигналом, оболочка обычно печатает некоторое сообщение об ошибках.
Сигналы, которые обычно представляют ошибки в программе, имеют специальную особенность: когда один из этих сигналов завершает процесс, он также формирует дамп core-файла, в который записывает состояние процесса во время окончания. Вы можете исследовать core файл отладчиком, чтобы исследовать то, что вызвало ошибку.
Если Вы вызываете сигнал "ошибки в программе" явным запросом, и он завершает процесс, он сделает core-файл, точно как если бы сигнал был непосредственно благодаря ошибке.
21.2 Стандартные Сигналы
Этот раздел перечисляет имена для различных стандартных видов сигналов и описывает какое событие, которое они означают. Каждое имя сигнала - макрокоманда, которая замещает положительное целое число - число сигнала для этого вида сигнала. Ваши программы никогда не должны делать предположения относительно числового кода для специфического вида сигнала, а обращаться к ним всегда именами, определенными здесь. Потому что число для данного вида сигнала может изменяться от системы до системы, но значения имен стандартизированы и довольно однородны.
Имена сигналов определены в заглавном файле " signal.h ".
int NSIG (макрос)
Значение этой символической константы - общее число определенных сигналов. Так как номера сигналов размещены последовательно, NSIG на один больше чем самое большое определенное число сигнала.
Сигналы Ошибки в программе
Следующие сигналы сгенерированы, когда операционной системой или компьютером непосредственно обнаружена серьезная ошибка в программе. Вообще, все эти сигналы - индикации, что ваша программа прервана некоторым способом, и не имеется обычно никакого способа продолжить вычисление, которое столкнулось с ошибкой.
Некоторые программы обрабатывают сигналы, возникающие при ошибке в программе, чтобы закончиться логически перед завершением. Обработчик должен завершить работу, определяя заданное по умолчанию действие для сигнала, который случался; это заставит программу завершаться с этим сигналом, как будто без обработчика. (См. Раздел 21.4.2 [Окончание Обработчика].)
Окончание - результат ошибки в программе в большинстве программ. Однако, системы программирования типа Лиспа, могут затем возвратить управление к уровню команды.
Заданное по умолчанию действие для всех этих сигналов должно заставить процесс завершиться. Если Вы блокируете или игнорируете эти сигналы или устанавливаете обработчики для них, которые просто возвращаются, ваша программа возможно пострадает, если сигналы не сгенерированы raise или kill вместо реальной ошибки.
Когда один из этих сигналов об ошибке в программе завершает процесс, он также формирует core-файл, который записывает состояние процесса во время окончания. Файл называется " core " и записан в текущий каталог процесса. (В системе GNU, Вы можете определять имя файла для дампов переменной среды COREFILE.)
Цель core-файлов - чтобы Вы могли исследовать их отладчиком, чтобы найти то, что вызвало ошибку.
int SIGFPE (макрос)
Сигнал SIGFPE сообщает фатальную арифметическую ошибку. Хотя имя происходит от " исключение с плавающей запятой ", этот сигнал фактически покрывает все арифметические ошибки, включая деление на нуль и переполнение. Если программа сохраняет целочисленные данные в стандарте, который используется операциями с плавающей запятой, это часто, вызывает исключение " недопустимая операция ", потому что процессор не может распознать данные как число с плавающей запятой.
FPE_INTOVF_TRAP
Целочисленное переполнение (невозможно в C программе, если Вы не даете возможность прерыванию переполнения в аппаратно-специфическом режиме).
FPE_INTDIV_TRAP
Целочисленное деление на нуль.
FPE_SUBRNG_TRAP
Переход нижнего индекса (кое-что, что программы C никогда не проверяют).
FPE_FLTOVF_TRAP
Плавающее переполнения.
FPE_FLTDIV_TRAP
Плавающее/десятичное деление на нуль.
FPE_FLTUND_TRAP
Плавающее антипереполнение (переполнение снизу - слишком маленькое число).
FPE_DECOVF_TRAP
Десятичное переполнения. (Только несколько машин имеют десятичную арифметику, и C никогда не использует ее.)
int SIGILL (макрос)
Имя этого сигнала происходите от " запрещенная команда "; это означает что ваша программа пробует выполнить привилегированную команду. Так как компилятор C генерирует только допустимые команды, SIGILL обычно указывает, что исполняемый файл разрушен, или что Вы пробуете выполнять данные. Некоторые общие положения входящие в последнюю ситуацию - передача недопустимого объекта там, где ожидался указатель на функцию, или запись после конца автоматического массива (или подобные проблемы с указателями на динамические локальные переменные) и разрушение других данных стека типа адреса возврата.
int SIGSEGV (макрос)
Этот сигнал сгенерирован, когда программа пробует читать или писать вне памяти, которая размещена для этого. (Фактически, сигналы происходят только, когда программа идет достаточно далеко, чтобы быть обнаруженной механизмом защиты памяти системы.) имя сокращение " нарушение сегментации ".
int SIGBUS (макрос)
Этот сигнал сгенерирован, когда недопустимый указатель применяется. Подобно SIGSEGV, этот сигнал - обычно результат применения неинициализированного указателя. Различие между ними в том, что SIGSEGV указывает на недопустимый доступ к допустимой памяти, в то время как SIGBUS указывает на доступ к недопустимому адресу. В частности SIGBUS сигналы часто следуют из применения расположенного с нарушением границ указателя, типа целого числа (из четырех слов) с адресом, не делимым на четыре. (Каждый вид компьютера имеет собственные требования для выравнивания адреса.)
Имя этого сигнала - сокращение для " ошибка шины ".
int SIGABRT (макрос)
Этот сигнал указывает ошибку, обнаруженную программой непосредственно и сообщается, вызовом abort. См. Раздел 22.3.4 [Прерывание выполнения Программы].
Сигналы Завершения
Эти сигналы используются, чтобы сообщить, что процесс завершился. Они имеют различные имена, потому что они используются для немного различных целей, и программы могли бы хотеть обрабатывать их по-разному.
Причина обработки этих сигналов - обычно то, что ваша программа должна выполнить некоторые действия перед фактическим завершением. Например, Вы могли бы хотеть сохранять информацию о состоянии, удалить временные файлы, или восстанавливать предыдущие режимы терминала. Такой обработчик должен закончиться определением заданного по умолчанию действия для сигнала, см. раздел 21.4.2 [Окончание Обработчика].) (Очевидное) заданное по умолчанию действие для всех этих сигналов должно заставить процесс завершаться.
int SIGHUP (макрос)
SIGHUP ("зависание") сигнал используется, чтобы сообщить, что терминал пользователя разъединен, возможно, потому что сетевое или телефонное соединение было прервано. Для получения более подробной информации см. Раздел 12.4.6 [Режимы Управления].
Этот сигнал также используется, чтобы сообщить окончание процесса управления на терминале; это окончание действительно разъединяет все процессы в сеансе с терминалом управления. Для подробной информации см. раздел 22.3.5 [Внутренняя организация Окончания].
int SIGINT (макрос)
SIGINT (" прерывание программы ") сигнал посылается, когда пользователь печатает INTR символ (обычно C-c). См. Раздел 12.4.9 [Специальные Символы], для уточнения инфрмации относительно поддержки драйвера терминала для C-c.
int SIGQUIT (макрос)
Сигнал SIGQUIT подобен SIGINT, за исключением того, что он управляется другой клавишей (символом QUIT), обычно C-\ и производит core-файл, когда он завершает процесс, точно так же как сигнал ошибки в программе. Вы можете думать об этом как об условии ошибки в программе "обнаруженном" пользователем.
См. Раздел 21.2.1 [Сигналы Ошибки в программе], для уточнения инфрмации относительно core-файлов. См. Раздел 12.4.9 [Специальные Символы], для уточнения инфрмации относительно поддержки драйвера терминала.
int SIGTERM (макрос)
Сигнал SIGTERM - обобщенный сигнал, используемый, чтобы вызвать окончание программы. В отличие от SIGKILL, этот сигнал может быть блокирован, обрабатываться, и игнорироваться.
Команда оболочки kill генерирует SIGTERM по умолчанию.
int SIGKILL (макрос)
Сигнал SIGKILL используется, чтобы вызвать непосредственное окончание программы. Он не может быть обработан или игнорироваться, и следовательно всегда фатален. Также не возможно блокировать этот сигнал.
Этот сигнал сгенерирован только явным запросом. Так как он не может быть обработан, Вы должны генерировать его только после попытки применения менее сильнодействующего лекарственного средства типа C-c или SIGTERM.
Фактически, если SIGKILL будет не в состоянии завершать процесс, то это ошибка операционной системы, которую Вы должны сообщить.
Сигнализация
Эти сигналы используются, чтобы указать окончание времени таймеров. См. Раздел 17.3 [Установка Сигнализаци], для уточнения информации относительно функций, которые заставляют эти сигналы быть посланными.
Заданное по умолчанию поведение для этих сигналов должно вызвать окончание программы. Это значение по умолчанию не очень полезно; но большинство способов использования этих сигналов требовало бы функций обработчика в любом случае.
int SIGALRM (макрос)
Этот сигнал обычно указывает окончание таймера, который измеряет реальное время или время часов.
Он используется функцией alarm, например.
int SIGVTALRM (макрос)
Этот сигнал обычно указывает окончание таймера, который измеряет CPU время, используемое текущим процессом. Имя - сокращение " виртуальный таймер ".
int SIGPROF (макрос)
Этот сигнал - обычно указывает окончание таймера, который измеряет оба и CPU время, используемое текущим процессом, и CPU время, израсходованное от имени процесса системой.
Асинхронные Сигналы ввода - вывода
Сигналы, перечисленные в этом разделе используются вместе с асинхронными средствами ввода - вывода. Вы должны явно вызвать fcntl, чтобы дать возможность специфическому описателю файла генерировать эти сигналы (см. Раздел 8.12 [Прерывания Ввода]). Заданное по умолчанию действие для этих сигналов - игнорировать их.
int SIGIO (макрос)
Этот сигнал посылается, когда дескриптор файла готов выполнить ввод или вывод.
В большинстве операционных систем, мониторы и сокеты единственые виды файлов, которые могут генерировать SIGIO; другие виды, включая обычные файлы, никогда не генерируют SIGIO, даже если Вы спрашиваете их.
int SIGURG (макрос)
Этот сигнал послан, когда "срочные" данные или данные вне потока прибывают в этот сокет. См. Раздел 11.8.8 [Данные вне потока].
Сигналы Управления заданиями
Эти сигналы используются, чтобы поддерживать управление заданиями. Если ваша система не поддерживает управление заданиями, то эти макрокоманды, определены, но сигналы непосредственно не могут быть вызваны или обрабатываться.
Вы должны вообще оставить эти сигналы, если Вы действительно не понимаете, как управление заданиями работает. См. Главу 24 [Управление заданиями].
int SIGCHLD (макрос)
Этот сигнал послан родительскому процессу всякий раз, когда один из дочерних процессов завершается или останавливается.
Заданное по умолчанию действие для этого сигнала - игнорировать это. Если Вы устанавливаете обработчик для этого сигнала, в то время как имеются дочерние процессы, которые завершились, но не сообщили об их состоянии через wait или waitpid (см. Раздел 23.6 [Завершение Процесса]), то применяется ли ваш обработчик к этим процессам или нет зависит от специфической операционной системы.
int SIGCONT (макрос)
Вы можете посылать сигнал SIGCONT процессу, чтобы заставить его продолжиться. Заданное по умолчанию поведение для этого сигнала должно заставить процесс продолжиться, если он остановлен, и игнорировать его иначе.
Большинство программ не имеет никакой причины обработать SIGCONT; они просто продолжают выполнение без информации, что они когда-либо останавливались. Вы можете использовать обработчик для SIGCONT, чтобы заставить программу сделать нечто специальное, когда она остановлена и продолжиться; например, перепечатывать подсказку, когда она приостановлена при ожидании ввода.
int SIGSTOP (макрос)
Сигнал SIGSTOP останавливает процесс. Он не может быть обработан, игнорироваться, или блокироваться.
int SIGTSTP (макрос)
Сигнал SIGTSTP - интерактивный сигнал останова. В отличие от SIGSTOP, этот сигнал может быть обработан и игнорироваться.
Ваша программа должна обработать этот сигнал, если Вы имеете специальную потребность оставить файлы или таблицы системы в безопасном состоянии, при остановке процесса. Например, программы, которые выключают отображение на экране, должны обработать SIGTSTP, так чтобы они могли направлять отображение обратно на экран перед остановкой.
Этот сигнал сгенерирован, когда пользователь печатает символ SUSP (обычно C-z). Для получения более подробной информации, см. Раздел 12.4.9 [Специальные Символы].
int SIGTTIN (макрос)
Процесс не может читать с терминала пользователя, в то время как он выполняется как фоновый. Когда любой фоновый процесс пробует читать с терминала, всем процессам посылется сигнал SIGTTIN. Заданное по умолчанию действие для этого сигнала должно остановить процесс. Для получения более подробной информации, относительно того как он взаимодействует с драйвером терминала, см. Раздел 24.4 [Доступ к Терминалу].
int SIGTTOU (макрос)
Подобен SIGTTIN, но сгенерирован, когда фоновый процесс делает попытку записи на терминал или устанавливать режимы. Снова, заданное по умолчанию действие должно остановить процесс.
В то время когда процесс приостановлен, сигналы не могут быть переданы ему, за исключением сигналов SIGKILL и (очевидно) SIGCONT. Сигнал SIGKILL всегда вызывает окончание процесса и не может быть блокирован или игнорироваться. Вы можете блокировать или игнорировать SIGCONT, но он всегда заставляет процесс быть продолженным во всяком случае, если он остановлен. Посылка сигнала SIGCONT на процесс заставляет любые отложенные сигналы останова для этого процесса быть отброшенными. Аналогично, любая задержка SIGCONT сигнала для процесса отброшена, когда он получает сигнал останова.
Разнообразные Сигналы
Эти сигналы используются, чтобы сообщить некоторые другие условия. Заданное по умолчанию действие для всех из них должно заставить процесс завершиться.
int SIGPIPE (макрос)
Если Вы используете водопроводы или FIFO, Вы должны разработать ваше приложение так, чтобы один процесс открыл водопровод для чтения перед тем как другой начал запиись. Если процесс считывания никогда не начинается, или завершается неожиданно, запись в водопровод или FIFO вызывает сигнал SIGPIPE. Если SIGPIPE блокирован, обрабатывается или игнорируется, дргие обращения вызывают EPIPE взамен.
Водопроводы и FIFO обсуждены более подробно в Главе 10 [Водопроводы и FIFO].
Другая причина SIGPIPE - когда Вы пробуете выводить на сокет, который не соединен. См. Раздел 11.8.5.1 [Посылка Данных].
int SIGUSR1 (макрос) int SIGUSR2 (макрос)
SIGUSR1 и SIGUSR2 отложены для Вас, чтобы использовать их любым способом, которым Вы хотите.
Они полезны для межпроцессорной связи. Так как эти сигналы обычно фатальны, Вы должны написать обработчик сигнала для них в программе, которая получает сигнал.
Имеется пример, показывающий использование SIGUSR1 и SIGUSR2 в Разделе 21.6.2 [Передача сигналов Другому Процессу].
Нестандартные Сигналы
Специфические операционные системы поддерживают дополнительные сигналы, не перечисленные выше. Стандарт ANSI C резервирует все идентификаторы, начинающиеся с " SIG " сопровождаемые символом верхнего регистра для имен сигналов. Вам нужно смотреть документацию или заголовочные файлами для вашей конкретной операционной системы и типа процессора, чтобы выяснить какие сигналы поддерживаются.
Например, некоторые системы особо поддерживают сигналы, которые соответствуют аппаратным средствам. Некоторые другие виды сигналов используются, чтобы выполнить ограничения времени CPU или использования файловой системы, для асинхронных изменений конфигурации терминала, и т.п.. Системы могут также определять имена сигналов, которые являются побочными результатами исследований для стандартных имен сигналов.
Заданное по умолчанию действие (или действие, установленное оболочкой) для определенных реализацией сигналов обычно приемлемо. Фактически, обычно не стоит игнорировать или блокировать сигналы, о которых Вы не знаете, или пробовать устанавливать обработчик для сигналов, чьи значения Вы не понимаете.
Вот нгекоторые сигналы, которые обычно используются в операционных системах:
SIGCLD Устаревшее имя для SIGCHLD. SIGTRAP Сгенерированный командой breakpoint машины. Используется отладчиками. Заданное по умолчанию действие должно формировать core-файл. SIGIOT Сгенерированный PDP-11 "iot" командой; эквивалент SIGABRT. Заданное по умолчанию действие должно формировать coreфайл. SIGEMT Ловушка эмулятора; следует из некоторых невыполненных команд. Это - сигнал ошибки в программе. SIGSYS Ошибочный системный вызов; то есть команда системного вызова была выполнена, но код системного вызова недопустим. Это - сигнал ошибки в программе. SIGPOLL Это - имя сигнала System V, более или менее подобное SIGIO. SIGXCPU Превышение ограничения времени CPU. Заданное по умолчанию действие - окончание программы. SIGXFSZ Файлом превышено ограничение размера. Заданное по умолчанию действие - окончание программы. SIGWINCH Изменение размера окна. Он генерируется на некоторых системах, когда размер текущего окна на экране изменен. Заданное по умолчанию действие - игнорировать это.
Сообщения Сигнала
Мы упомянули выше, что оболочка печатает сообщение, описывающее сигнал, который завершил дочерний процесс. Простой способ печатать сообщение, описывающее сигнал состоит в том, чтобы использовать функции strsignal и psignal. Эти функции используют номер сигнала, чтобы определить, какой вид сигнала описывать. Номер сигнала может исходить из состояния окончания дочернего процесса (см. Раздел 23.6 [Завершение Процесса]) или он может исходить из обработчика сигнала на том же самом процессе.
char * strsignal (int signum) (функция)
Эта функция возвращает указатель на статически размещенную строку, содержащую сообщение, описывающее сигнал. Вы не должны изменять содержимое этой строки; и, так как она может быть перезаписана при последующих обращениях, Вы должны сохранить его копию, если Вы должны сослаться на него позже.
Эта функция - расширение GNU, объявленное в заглавном файле "string.h".
void psignal (int signum, const char *message)
Эта функция печатает сообщение, описывающее сигнал, в стандартный поток ошибкок - stderr; см. Раздел 7.2 [Стандартные Потоки].
Если Вы вызываете psignal с сообщением, которое является или пустым указателем или пустой строкой, psignal печатает сообщение, соответствующее сигналу, добавляя конечный символ перевода строки.
Если Вы обеспечиваете непустой аргумент сообщения, то psignal предворяет вывод этой строкой. Она добавляет двоеточие и пробел, чтобы отделить сообщение от строки, соответствующей сигналу.
Эта функция - возможность BSD, объявленная в заглавном файле "stdio.h ".
Имеется также массив sys_siglist, который содержит сообщения для различных кодов сигнала. Этот массив существует в системах BSD, в отличие от strsignal.
21.3 Определение Действий Сигнала
Самый простой способ изменить действие для сигнала состоит в том, чтобы использовать функцию signal. Вы можете определять встроенное действие (типа игнорировать сигнал), или Вы можете устанавливать обработчик.
Библиотека GNU также осуществляет более универсальное средство sigaction. Этот раздел описывает эти средства и дает предложения, когда их использовать.
Основная Обработка сигналов
Функция signal обеспечивает простой интерфейс для установки действия для специфического сигнала.
Функция и связанные макрокоманды объявлены в заголовочном файле "signal.h ".
sighandler_t (тип данных)
Это - тип функций обработчика сигнала. Обработчики Сигнала воспринимают один целочисленный аргумент, определяющий номер сигнала, и возвращают тип void. Вы должны определять функции обработчика примерно так:
void handler (int signum) { . . . }
Имя sighandler_t для этого типа данных - расширение GNU. sighandler_t signal (int signum, sighandler_t action) (функция)
Функция signal устанавливает action как действие для сигнала signum.
Первый аргумент, signum, идентифицирует сигнал, чьим поведением Вы хотите управлять, и должен быть номером сигнала. Соответствующий способ определять номер сигнала - одним из символических имен сигналов, описанных в Разделе 21.2 [Стандартные Сигналы] не используют явное число, потому что числовой код для данного вида сигнала может измениться от операционной системы к операционной системе.
Второй аргумент, action, определяет действие используемое для сигнала signum. Оно может быть одно из следующих: SIG_DFL определяет заданное по умолчанию действие для специфического сигнала. Заданные по умолчанию действия для различных видов сигналов установлены в Разделе 21.2 [Стандартные Сигналы].
SIG_IGN определяет, что сигнал должен игнорироваться.
Ваша программа вообще не должна игнорировать сигналы, которые представляют серьезные события, или обычно используется, чтобы запросить окончание. Вы не можете игнорировать SIGKILL или SIGSTOP вообще. Вы можете игнорировать сигналы ошибки в программе подобно SIGSEGV, но игнорирование ошибки не будет давать возможность программе продолжить осмысленное выполнение. Игнорирование запросов пользователя типа SIGINT, SIGQUIT, и SIGTSTP некультурно!!.
Когда Вы не желаете, чтобы сигналы были передан в некоторую части программы, нужно блокировать их, а не игнорировать. См. Раздел 21.7 [Блокированные Сигналы]. handler обеспечивает адрес функции обработчика в вашей программе, выполнение этого обработчика определяется как способ передать сигнал.
Для получения более подробной информации относительно функции обработчика сигнала, см. Раздел 21.4 [Определение Обработчиков].
Функция signal возвращает action, который был задан для указанного signum. Вы можете сохранять это значение и восстанавливать его позже, вызывая signal снова.
Если signal не может выполнить запрос, она возвращает SIG_ERR. Следующие errno условия ошибки определены для этой функции:
- EINVAL
-
Вы определили недопустимый signum; или Вы пробовали игнорировать или обеспечивать обработчик для SIGKILL или SIGSTOP.
Вот простой пример установки обработчика, чтобы удалить временные файлы, при получении некоторых фатальных сигналов:
#include
void termination_handler (int signum) { struct temp_file *p; for (p = temp_file_list; p; p = p->next) unlink (p->name); } int main (void) { . . . if (signal (SIGINT, termination_handler) == SIG_IGN) signal (SIGINT, SIG_IGN); if (signal (SIGHUP, termination_handler) == SIG_IGN) signal (SIGHUP, SIG_IGN); if (signal (SIGTERM, termination_handler) == SIG_IGN) signal (SIGTERM, SIG_IGN); . . . }
Обратите внимание, что, если данный сигнал был предварительно установлен, чтобы игнорироваться, то код избегает изменять эту установку. Потому что оболочки часто игнорируют некоторые сигналы, при старте дочерних процессов, и эти процессы не долхны изменять это.
Мы не обрабатываем SIGQUIT или сигналы ошибки в программе в этом примере, потому что они разработаны, чтобы обеспечить информацию для отладки (core-файл), и временные файлы могут давать полезную информацию.
sighandler_t ssignal (int signum, sighandler_t action) (функция)
Функция ssignal делает ту же самую вещь что signal; она предоставляется только для совместимости с SVID.
sighandler_t SIG_ERR (макрос)
Значение этой макрокоманды используется как возвращаемое значение из signal, чтобы указать ошибку.
Сложная Обработка Сигнала
Функция sigaction имеет тот же самый основной эффект как signal: определять, как сигнал должен быть обработан процессом. Однако, sigaction предлагает большое количество управления, за счет большего количества сложности. В частности sigaction позволяет Вам определять дополнительные флаги, чтобы управлять тем, когда генерируется сигнал и как вызывается обработчик.
Функция sigaction объявлена в "signal.h".
struct sigaction (тип данных)
Структуры типа struct sigaction используются в функции sigaction, чтобы определить всю информацию относительно того, как обработать специфический сигнал. Эта структура содержит по крайней мере следующие элементы:
sighandler_t sa_handler
Это используется таким же образом, как аргумент action функции signal. Значение может быть SIG_DFL, SIG_IGN, или указатель на функцию. См. Раздел 21.3.1 [Общая Обработка сигнала].
sigset_t sa_mask
Этот определяет набор сигналов, которые будут блокированы, в то время как обработчик выполняется. Блокирование объясняется в Разделе 21.7.5 [Блокирование для Обработчика]. Обратите внимание, что сигнал, который был передан, автоматически блокирован по умолчанию прежде, чем обработчик начат; это - истина независимо от значения в sa_mask. Если Вы хотите, чтобы этот сигнал не был блокирован внутри обработчика, Вы должен записать в обработчике код чтобы разблокировать его.
int sa_flags
Определяет различные флаги, которые могут воздействовать на поведение сигнала. Они описаны более подробно в Разделе 21.3.5 [Флаги для Sigaction].
int sigaction (int signum, const struct sigaction *action, struct sigaction *old_action)
Аргумент action используется, чтобы установить новое действие для указанного сигнала, в то время как old_action аргумент используется, чтобы возвратить информацию относительно действия, предварительно связанного с этим сигналом. (Другими словами, Вы можете выяснить, что старое действие в действительности делало для сигнала, и восстановить его позже, если Вы хотите.)
Возвращаемое значение от sigaction - нуль, если она преуспевает, и -1 при отказе. Следующие errno условия ошибки определены для этой функции:
- EINVAL
-
аргумент signum не допустим, или Вы пробуете обрабатывать или игнорировать SIGKILL или SIGSTOP.
Взаимодействие signal и sigaction
Возможно использовать signal и sigaction внутри одной программы, но Вы должны быть внимательным, потому что они могут взаимодействовать немного странными способами.
Функция sigaction определяет более подробную информацию, чем функция signal, так что возвращаемое значение из signal не может выражать все возможности sigaction. Следовательно, если Вы используете signal, чтобы сохранить и позже восстанавливать действие, она может быть не способна восстановить правильно обработчик, который был установлен с sigaction.
Чтобы избежать проблем всегда используйте sigaction, чтобы сохранить и восстановить обработчик, если ваша программа использует sigaction вообще. Так как sigaction более общая, она может правильно сохранять и восстанавливать любое действие, независимо от того, было ли оно установлено первоначально с signal или sigaction.
Если Вы устанавливаете действие с signal, а потом исследуете его sigaction, адрес обработчика, который Вы получаете, может быть не такой же как тот, что Вы определили с signal. Он так же не может использоваться как аргумент action в signal. Но Вы можете полагаться на использование его как аргумента sigaction.
Так что лучше отказаться от использования того и другого механизма последовательно внутри однй программы.
Примечание о переносимости: общая функция сигнала - возможность ANSI C, в то время как sigaction - часть POSIX.1 стандарта. Если Вы беспокоитесь относительно переносимости на не-posix системы, то Вы должены использовать функцию signal.
Пример Функции sigaction
В Разделе 21.3.1 [Общая Обработка сигнала], мы привели пример установки простого обработчика для сигналов окончания, используя signal. Вот эквивалентный пример, использующий sigaction:
#include
void termination_handler (int signum) { struct temp_file *p; for (p = temp_file_list; p; p = p->next) unlink (p->name); } int main (void) { . . . struct sigaction new_action, old_action; new_action.sa_handler = termination_handler; sigemptyset (&new_action.sa_mask); new_action.sa_flags = 0; sigaction (SIGINT, NULL, &old_action); if (old_action.sa_handler != SIG_IGN) sigaction (SIGINT, &new_action, NULL); sigaction (SIGHUP, NULL, &old_action); if (old_action.sa_handler != SIG_IGN) sigaction (SIGHUP, &new_action, NULL); sigaction (SIGTERM, NULL, &old_action); if (old_action.sa_handler != SIG_IGN) sigaction (SIGTERM, &new_action, NULL); . . . }
Программа просто загружает структуру new_action с желательными параметрами и передает ее в sigaction. Использование sigemptyset описано позже; см. Раздел 21.7 [Блокированные Сигналы].
В примере, использующем signal, мы старались избегать обрабатывать предварительно установленные сигналы. Здесь мы можем мгновенно избегать изменять обработчик сигнала, используя возможность sigaction, которая позволяет исследовать текущее действие без определения нового.
Вот другой пример. Он отыскивает информацию относительно текущего действия для SIGINT без замены этого действия.
struct sigaction query_action; if (sigaction (SIGINT, NULL, &query_action) < 0) /* sigaction возвращает -1 в случае ошибки. */ else if (query_action.sa_handler == SIG_DFL) /* SIGINT обработан заданным по умолчанию, фатальным способом. */ else if (query_action.sa_handler == SIG_IGN) / * SIGINT игнорируется. * / else /* Определенный программистом обработчик сигнала. */
Флаги для sigaction
элемент структуры sa_flags - sigaction определяет специальные возможности. В большинстве случаев, SA_RESTART - хорошее значение, чтобы использовать для этого поля.
Значение sa_flags интерпретируется как битовая маска. Таким образом, Вы должны выбрать флаги, которые Вы хотите установить, сделать операцию OR для этих флагов, и сохранить результат в sa_flags элементе вашей структуры sigaction.
Каждый номер сигнала имеет собственный набор флагов. Каждое обращение к sigaction воздействует на один номер сигнала, и флаги, которые Вы определяете, применяются только к этому специфическому сигналу.
В библиотеке GNU C, установка обработчика сигнала обнуляет все флаги, кроме SA_RESTART, чье значение зависит от установок, которые Вы сделали в siginterrupt. См. Раздел 21.5 [Прерванные Примитивы].
Эти макрокоманды определены в заголовочном файле " signal.h ".
int SA_NOCLDSTOP (макрос)
Этот флаг имеет смысл только для сигнала SIGCHLD. Когда флаг установлен, система передает сигнал для завершенного дочернего процесса, но не для того, который остановлен. По умолчанию, SIGCHLD - передан для и завершенных дочерних процессов и для остановленных дочерних процессов.
При установке этого флага для сигнала отличного от SIGCHLD не имеет никакого эффекта.
int SA_ONSTACK (макрос)
Если этот флаг установлен для конкретного сигнала, система использует стек сигнала при сообщении этого вида сигнала. См. Раздел 21.9 [Обработка Сигнала BSD].
int SA_RESTART (макрос)
Этот флаг управляет тем, что случается, когда сигнал - передан в течение выполнения некоторых примитивов (типа open, read или write). Имеются два варианта: библиотечная функция может продолжиться, или она может возвратить отказ с кодом ошибки EINTR.
Выбор управляется SA_RESTART флагом для конкретного вида сигнала, который был передан. Если флаг установлен, по возвращении из обработчика продолжается библиотечная функция. Если флаг не установлен, происходит сбой функции. См. Раздел 21.5 [Прерванные Примитивы].
Начальные Действия Сигнала
После создания, новый процесс (см. Раздел 23.4 [Создание Процесса]) наследует обработку сигналов из родительского процесса. Однако, когда Вы загружаете новый образ процесса, используя функцию exec (см. Раздел 23.5 [Выполнение Файла] ) обработчики любых сигналов возвращаются к SIG_DFL. Конечно, новая программа может устанавливать собственные обработчики.
Когда программа выполняется оболочкой, оболочка обычно устанавливает начальные действия для дочернего процесса как SIG_DFL или SIG_IGN, соответственно.
Вот пример того, как устанавливать обработчик для SIGHUP, но если SIGHUP в настоящее время не игнорируется:
. . . struct sigaction temp; sigaction (SIGHUP, NULL, &temp); if (temp.sa_handler != SIG_IGN) { temp.sa_handler = handle_sighup; sigemptyset (&temp.sa_mask); sigaction (SIGHUP, &temp, NULL); }
21.4 Определение Обработчиков Сигнала
Этот раздел описывает, как написать функцию обработчика сигнала, которая может быть установлена функциями sigaction или signal.
Обработчик сигнала - функция, которую Вы компилируете вместе с остальной частью программы. Вместо непосредственно вызова функции, Вы используете signal или sigaction, чтобы сообщить, чтобы операционная система вызвала ее, когда приходит сигнал. Это называется установкой обработчика. См. Раздел 21.3 [Действия Сигнала].
Имеются две cтратегии, которые Вы можете использовать в функциях обработчика сигнала:
- Вы можете иметь функцию обработчика, которая отмечает, что сигнал пришел, в некоторых глобальных структурах данных, и нормально возвращается.
- Вы можете иметь функцию обработчика, которая завершают программу или передает управление к отметке, где программа может избавиться от ситуации, которая вызвала сигнал.
Вы должны быть очень осторожны при написании функций обработчика, потому что они могут вызываться асинхронно. То есть обработчик может вызываться в любом месте программы, непредсказуемо. Если два сигнала прибывают в течение очень короткого интервала, один обработчик может выполняться внутри другого. Этот раздел описывает то, что ваш обработчик должен делать, и чего Вы должны избежать.
Обработчики Сигнала, которые Возвращаются
Обработчики, которые возвращаются обычно используются для сигналов типа SIGALRM и ввода-вывода и межпроцессорных сигналов связи. Но обработчик для SIGINT может также возвращаться после установки флага, который сообщает, чтобы программа завершилась в удобное время.
Небезопасно возвращаться из обработчика при сигнале ошибки в программе, потому что поведение программы, когда функция обработчика возвращается, не определено после ошибки в программе. См. Раздел 21.2.1 [Сигналы Ошибки в программе].
Обработчики, которые возвращаются, должны изменять некоторую глобальную переменную, чтобы иметь какой-нибудь эффект.
Обычно, эта переменная исследуется периодически программой. Тип данных должен быть sig_atomic_t по причинам, описанным в Разделе 21.4.7 [Быстрый Доступ к данным].
Вот простой пример такой программы. Она выполняет тело цикла, пока она не отметит, что сигнал SIGALRM прибыл.
#include
#include #include volatile sig_atomic_t keep_going = 1; void catch_alarm (int sig) { keep_going = 0; signal (sig, catch_alarm); } void do_stuff (void) { puts ("Doing stuff while waiting for alarm...."); } int main (void) { signal (SIGALRM, catch_alarm); alarm (2); while (keep_going) do_stuff (); return EXIT_SUCCESS; }
Обработчики, которые Завершают Процесс
Функции-Обработчики, которые завершают программу, обычно используются, чтобы вызвать организованную очистку от сигналов ошибки в программе и интерактивных прерываний.
Самый чистый способ для обработчика завершить процесс состоит в том, чтобы вызвать тот же самый сигнал, который активизировал обработчик. Вот, как сделать это:
volatile sig_atomic_t fatal_error_in_progress = 0; void fatal_error_signal (int sig) { if (fatal_error_in_progress) raise (sig); fatal_error_in_progress = 1; / * Теперь делаем очищающие действия: - установка режимов терминала - уничтожение дочерних процессов - удаление временных файлов * / . . . raise (sig); }
Нелокальная Передача Управления в Обработчиках
Вы можете делать нелокальную передачу управления вне обработчика сигнала, используя setjmp и longjmp средства (см. Главу 20 [Нелокальные Выходы]).
Когда обработчик делает нелокальную передачу управления, часть программы, которая выполнялась, не будет продолжаться. Если эта часть программы была в процессе изменения важной структуры данных, структура данных останется несогласованной. Так как программа не завершается, несогласованность должна быть отмечена позже.
Имеются два способа избежать этой проблемы. Первый - блокировать сигнал для частей программы, которые изменяют важные структуры данных. Блокирование сигнала задерживает выдачу, пока он не открыт, как только критическое изменение закончено. См. Раздел 21.7 [Блокированные Сигналы].
Иначе, нужно повторно инициализировать определяющие структуры данных в обработчике сигнала, или сделать их значения непротиворечивыми.
Вот довольно схематический пример, показывающий переинициализацию одной глобальной переменной.
#include
#include jmp_buf return_to_top_level; volatile sig_atomic_t waiting_for_input; void handle_sigint (int signum) { waiting_for_input = 0; longjmp (return_to_top_level, 1); } int main (void) { . . . signal (SIGINT, sigint_handler); . . . while (1) { prepare_for_command (); if (setjmp (return_to_top_level) == 0) read_and_execute_command (); } } char * read_data () { if (input_from_terminal) { waiting_for_input = 1; . . . waiting_for_input = 0; } else { . . . } }
Прибытие Сигналов во Время Выполнения Обработчика
Что случается, если другой сигнал приходит, когда выполняется ваша функция обработчика сигнала?
Когда вызывается обработчик для конкретного сигнала, этот сигнал обычно блокируется, пока обработчик не возвращается. Это означает что, если два сигнала того же самого вида приходят через очень короткий интервал времени, второй будет приостановлен, пока первый не будет обработан. (Обработчик может явно открыть сигнал, используя sigprocmask, если Вы хотите позволить прибывать большему количеству сигналов этого типа; см. Раздел 21.7.3 [Маска Сигнала Процесса].)
Однако, ваш обработчик может все еще прерываться получением другого вида сигнала. Чтобы избежать этого, Вы можете использовать sa_mask - элемент структуры action, передаваемой sigaction, чтобы явно определить, какие сигналы должны быть блокированы в то время как обработчик сигнала выполняется. См. Раздел 21.7.5 [Блокирование для Обработчика].
Примечание о переносимости: Всегда используйте sigaction, чтобы установить обработчик для сигнала, который Вы ожидаете получать асинхронно, если Вы хотите, чтобы ваша программа работала правильно на System V Unix.
Близкие (по времени) Сигналы Обьединяются в Один
Если несколько сигналов того же самого типа переданы вашему процессу прежде, чем ваш обработчик сигнала может быть вызван вообще, то обработчик может вызываться только один раз, как будто только одиночный сигнал прибыл. В результате сигналы обьединяются в один. Эта ситуация может возникать, когда сигнал блокирован, или в многопроцессорной среде, где система - занята выполнением некоторых других процессов, в то время как сигналы - передан. Это означает, например, что Вы не можете надежно использовать обработчик сигнала, чтобы считать сигналы. Единственное, что Вы можете сделать узнать, прибыл ли по крайней мере один сигнал начиная с данного времени.
Вот пример обработчика для SIGCHLD, который компенсирует предложение, что число полученных сигналов не может равняться числу дочерних процессов, генерируя их. Он подразумевает, что программа следит за всеми дочерними процессами цепочкой структур следующим образом:
struct process { struct process *next; int pid; int input_descriptor; int status; }; struct process *process_list;
Этот пример также использует флаг, чтобы указать, прибыли ли сигналы начиная с некоторого времени в прошлом, когда программа в последний раз обнулила его.
int process_status_change;
Вот обработчик непосредственно:
void sigchld_handler (int signo) { int old_errno = errno; while (1) { register int pid; int w; struct process *p; do { errno = 0; pid = waitpid (WAIT_ANY, &w, WNOHANG | WUNTRACED); } while (pid <= 0 && errno == EINTR); if (pid <= 0) { errno = old_errno; return; } for (p = process_list; p; p = p->next) if (p->pid == pid) { p->status = w; p->have_status = 1; if (WIFSIGNALED (w) || WIFEXITED (w)) if (p->input_descriptor) FD_CLR (p->input_descriptor, &input_wait_mask); ++process_status_change; } } }
Вот соответствующий способ проверять флаг process_status_change:
if (process_status_change) { struct process *p; process_status_change = 0; for (p = process_list; p; p = p->next) if (p->have_status) { ... Исследуют p->status ... } }
Важно очистить флаг перед исследованием списка; иначе, если сигнал был передан только перед очисткой флага, и после того, как соответствующий элемент списка процесса был проверен, изменение состояния будет неотмеченным, пока следующий сигнал не прибыл, чтобы установить флаг снова. Вы могли бы, конечно, избегать этой проблемы, блокировав сигнал при просмотре списка, но более важно гарантировать правильность, делая все в правильном порядке.
Цикл, который проверяет состояние процесса избегает исследовать p->status, пока он не видит, что состояние было законно сохранено. Он должен удостовериться, что status не может изменяться в середине доступа к нему. Как только p->have_status установлен, это означает что дочерний процесс остановлен или завершен, и в любом случае он не может останавливаться или завершаться снова. См. Раздел 21.4.7.3 [Быстрое Использование], для получения более подробной информации относительно копирования с прерываниями во время доступов к переменной.
Вот, таким образом Вы можете проверять, выполнился ли обработчик начиная с последней проверки.
Эта методика использует счетчик, который никогда не изменяется снаружи обработчика. Вместо того, чтобы очищать счетчик, программа помнит предыдущее значение, и видит, изменилось ли оно начиная с предыдущей проверки. Преимущество этого метода - в том, что различные части программы могут проверять независимо, каждая часть проверяет имелся ли сигнал начиная с последней проверки в этой части.
sig_atomic_t process_status_change; sig_atomic_t last_process_status_change; . . . { sig_atomic_t prev=last_process_status_change; last_process_status_change = process_status_change; if (last_process_status_change != prev) { struct process *p; for (p = process_list; p; p = p->next) if (p->have_status) { ... Проверка p->status ... } } }
Обработка Сигнала и Неповторно используемые Функции
Функции-Обработчики обычно делают не очень много. Самый лучший обработчик - который не делает ничего, но устанавливает внешнюю переменную, которую программа проверяет регулярно, и оставляет всю серьезную работу программе. Это самое лучшее, потому что обработчик может вызываться асинхронно, в непредсказуемое время, возможно в середине системного вызова, или даже между началом и концом оператора Cи, который требует выполнения многократных команд. Изменяемые структуры данных могут быть в несогласованном состоянии, когда вызывается функция обработчика. Даже копирование одной int переменной в другую занимает две команды на большинстве машин.
Это означает, что Вы должны быть очень осторожын относительно того, что Вы делаете в обработчике сигнала.
- Если ваш обработчик должен обратиться к некоторым глобальным переменным вашей программы, объявляйте эти переменные независимо. Это сообщает компилятору, что значение переменной может изменяться асинхронно, и запрещает некоторые оптимизации.
- Если Вы вызываете функцию в обработчике, удостоверьтесь, что она повторно используема относительно сигналов, и еще удостоверьтесь, что сигнал не может прервать обращение к зависимой функции. Функция не может повторно использоваться, если она использует память, которая не на стеке.
- Если функция использует статическую переменную или глобальную переменную, или динамически размещенный объект, который она устанавливает для себя, то она - неповторно используемая и любые два обращения к функции могут смешиваться. Например, предположите, что обработчик сигнала использует gethostbyname. Эта функция возвращает значение в статическом объекте, многократно используя тот же самый объект каждый раз. Если сигнал прибывает в течение обращения в gethostbyname, или даже после него (в то время как программа все еще использует значение), то он затрет значение еще не прочитанное программой. Однако, если программа не использует gethostbyname или любую другую функцию, которая возвращает информацию в том же самом объекте, или если она всегда блокирует сигналы вокруг каждого использования, то Вы в безопасности. Имеется большое количество библиотечных функций возвращающих значения в фиксированном объекте, всегда многократно используя тот же самый объект в этом режиме, и все из них вызывают ту же самую проблему. Описание функции в этом руководстве всегда упоминает это поведение.
- Если функция использует и изменяет объект, который Вы обеспечиваете, то она неповторно используема; два обращения могут смешиваться, если они используют тот же самый объект. Этот случай возникает, когда Вы делаете ввод - вывод, используя потоки. Предположите, что обработчик сигнала печатает сообщение с fprintf. Предположите, что программа была в середине обращения к fprintf, используя некоторый поток, когда был получен сигнал. И сообщение обработчика сигнала и данные программы могут быть разрушены, потому что оба обращения функционируют на том же самом потоке непосредственно. Однако, если Вы знаете, что поток используемый обработчиком не может использоваться программой одновременно, когда прибывают сигналы, то все хорошо. Это не проблема, если программа использует какой-нибудь другой поток.
- На большинстве систем, malloc и free неповторно используемы, потому что они используют статическую структуру данных, которая хранит сведения о свободных блоках. В результате, все библиотечные функции, которые резервируют или освобождают память, не повторно используемы, включая функции, которые резервируют место, чтобы сохранить результат. Самый лучший способ избегать потребности зарезервировать память - зарезервировать заранее пространство для обработчиков сигнала. Самый лучший способ избегать освобождения памяти в обработчике отмечать или запоминать объекты, которые будут освобождены, и иметь возможность проверять в программе время от времени, ждет ли что-нибудь, чтобы его освобождили. Но это должно быть выполнено аккуратно, т.к. размещение объекта в цепочке - не быстрая операция, и если оно прервано другим обработчиком сигнала, который делает ту же самую вещь, Вы можете "потерять" один из объектов. В системе GNU, malloc и free безопасны для использования в обработчиках сигнала, потому что они блокируют сигналы. В результате, библиотечные функции, которые резервируют пространство для результата также безопасны в обработчиках сигналов. Obstack функции резервирования безопасны, если Вы не используете тот же самый obstack, и внутри и вне обработчика сигнала. Функции резервирования настройки (см. Раздел 3.6 [Настройка Программы распределения]) конечно не безопасны, для использования в обработчике сигнала.
- Любая функция, которая изменяет errno, неповторно используема, но Вы можете исправить это: в обработчике, сохраните первоначальное значение errno, и восстановите его перед возвращением. Это предотвращает смешивание ошибок, которые происходят внутри обработчика сигнала, с ошибками из системных вызовов в точке прерывания программы, чтобы выполнить обработчик. Эта методика вообще применима; если Вы хотите вызвать обработчик, который изменяет специфический объект в памяти, Вы можете делать его безопасным, сохраняя и восстанавливая этот объект.
- Просто чтение из объекта памяти безопасно, если Вы можете иметь дело с любым из значений, которые могли бы появляться в объекте одновременно, когда сигнал может быть получен. Имейте в виду, что доступ к некоторым типам данных требует больше чем одну команду, это означает что обработчик может выполняться "в середине" доступа к переменной. См. Раздел 21.4.7 [Быстрый Доступ к данным].
- Просто запиись в объект памяти безопасна, если только внезапное изменение значения, в любое время, когда обработчик мог бы выполняться, не будет нарушать чего-нибудь.
Быстрый Доступ к данным и Обработка Сигнала
Для любого вида данных в вашем приложении, Вы должны быть внимательны к тому, что доступ к одиночному элементу данных не обязательно быстрый. Это означает, что это может занимать больше чем одну команду. В таких случаях, обработчик сигнала может выполняться в середине чтения или запииси объекта.
Имеются три способа, при помощи которых Вы можете справляться с этой проблемой. Вы можете использовать типы данных, к которым всегда обращаются быстро; Вы можете тщательно упорядочивать их, так что ничего неблагоприятного не случается, если доступ прерван, или Вы можете блокировать все сигналы вокруг любого доступа, который лучше не прерывать (см. Раздел 21.7 [Блокированные Сигналы]).
Проблемы с Немгновенным Доступом
Вот пример, который показывает то, что может случиться, если обработчик сигнала выполняется в середине изменения переменной. (Прерывание чтения переменной может также привести к парадоксальным результатам, но здесь мы показываем только запиись.)
#include
#include struct two_words { int a, b; } memory; void handler(int signum) { printf ("%d,%d\n", memory.a, memory.b); alarm (1); } int main (void) { static struct two_words zeros = {0,0}, ones = {1,1}; signal (SIGALRM, handler); memory = zeros; alarm (1); while (1) { memory = zeros; memory = ones; } }
Эта программа заполняет память с нулями, еденицами, нулями, еденицами, чередуя все время; тем временем, раз в секунду, обработчик сигнала таймера печатает текущее содержимое. (Вызов printf в обработчике безопасен в этой программе, потому что она конечно не вызывается снаружи обработчика, когда случается сигнал.)
Ясно, эта программа может печатать пару нулей или пару едениц. Но это - не все что она может делать! На большинстве машин сохранение нового значения в памяти занимает несколько команд, и значение сохраняется по одному слову. Если сигнал передан между этими командами, обработчик, может находить, что memory.a - нуль, а memory.b - один (или наоборот).
На некоторых машинах может быть возможно сохранить новое значение в памяти только одной командой, которая не может быть прервана. На этих машинах, обработчик будет всегда печатать два нуля или две еденицы.
Типы Данных, к которым Быстрый Доступ
Чтобы избегать неопределенности относительно прерывания доступа к переменной, Вы можете использовать специфический тип данных, для которого доступ является всегда быстрым: sig_atomic_t. При чтении и запииси этого типа данных, как гарантируется, выполняется одиночная команда, так что не имеется никакого способа для обработчика, чтобы выполниться "в середине " доступа.
Тип sig_atomic_t - всегда целочисленный тип данных, но сколько битов он содержит, может изменяться от машины до машины.
sig_atomic_t
Это - целочисленный тип данных. К объектам этого типа всегда обращаются быстро.
Практически, Вы можете считать, что int и другие целочисленные типы не большие, чем int - быстрые.
Вы можете также считать, что к типам pointer быстрый доступ; это очень удобно. Оба этих утверждения верны для всех машин, которые поддерживает библиотека GNU C, и на всех системах POSIX, о которых мы знаем.
Быстрое Использование Шаблонов
Некоторые шаблоны доступа избегают любой проблемы, даже если доступ прерван. Например, флаг, который установлен обработчиком, и проверяется и очищается программой main время от времени, всегда безопасен, даже если доступ к нему фактически требует двух команд. Чтобы показать, что это так, мы должны рассмотреть каждый доступ, который мог быть прерван, и показывать, что не имеется никакой проблемы, если он прерван.
Прерывание в середине тестирования флага безопасно, потому что либо он распознан отличным от нуля, тогда точное значение не важно, либо он будет отличным от нуля, в следующий раз.
Прерывание в середине очистки флага не проблема, потому что либо программа записывает нуль, если сигнал входит прежде, чем флаг очищен, либо обрабтчик заканчивается, и последующие события происходят при флаге отличном от нуля.
Если код обрабатывает оба этих случая правильно, то он может также обрабатывать сигнал в середине очистки флага.
Иногда Вы можете обеспечивать непрерывный доступ к одному объекту, защищая его использование другим объектом, возможно тем, чей тип гарантирует быстроту. См. Раздел 21.4.5 [Обьединенные Сигналы].
21.5 Примитивы, прерванные Сигналами
Сигнал может прибывать и обрабатываться, в то время как примитив ввода - вывода типа open или read ждет устройство ввода - вывода. Если обработчик сигнала возвращается, система задает вопрос: что должно случиться затем?
POSIX определяет один подход: делайте примитивный сбой сразу же. Код ошибки для этого вида отказа - EINTR. Это гибко, но обычно неудобно. Обычно, приложения POSIX, которые используют обработчики сигнала, должны проверить EINTR после каждой библиотечной функции, которая может возвращать его, чтобы пытаться обратиться снова. Часто программисты забывают это проверять, что является общим источником ошибок.
Библиотека GNU обеспечивает удобный способ повторить обращение после временного отказа макрокомандой TEMP_FAILURE_RETRY:
TEMP_FAILURE_RETRY (expression) (макрос)
Эта макрокоманда оценивает выражение один раз. Если оно терпит неудачу и код ошибки EINTR, TEMP_FAILURE_RETRY оценивает это снова, и много раз, пока результат не временный отказ.
Значение, возвращенное TEMP_FAILURE_RETRY - любое произведенное выражением значение.
BSD избегает EINTR полностью и обеспечивает более удобный подход: перезапускать прерванный примитив. Если Вы выбираете этот подход, Вы не нуждаетесь в EINTR.
Вы можете выбирать любой подход в библиотеке GNU. Если Вы используете sigaction, чтобы установить обработчик сигнала, Вы можете определять, как этот обработчик должен вести себя. Если Вы определяете SA_RESTART флаг, после возврата из этого обработчика продолжится примитив; иначе, возврат из этого обработчика вызовет EINTR. См. Раздел 21.3.5 [Флаги для Sigaction].
Другой способ определять выбор - siginterrupt функцией. См. Раздел 21.9.1 [POSIX против BSD].
Когда Вы не определяете с sigaction или siginterrupt, что специфический обработчик должен делать, он использует заданный по умолчанию выбор. Заданный по умолчанию выбор в библиотеке GNU зависит от возможностей макрокоманд, которые Вы определили. Если Вы определяете _BSD_SOURCE или _GNU_SOURCE перед вызовом сигнала, значение по умолчанию - продолжить примитивы; иначе, значение по умолчанию должно делать сбой с EINTR. (Библиотека содержит альтернативные версии функции signal, и макрокоманды возможностей определяют, которую Вы действительно вызываете.) См. Раздел 1.3.4 [Макрокоманды Возможностей].
Примитивы, на которые воздействует это: close, fcntl (операция F_SETLK), open, read, recv, recvfrom, select, send, sendto, tcdrain, waitpid, wait, и write.
Имеется одна ситуация, где возобновление никогда не случается, независимо от того, какой выбор Вы делаете: когда функция преобразования типа например read или write прервана сигналом после пересылки части данных. В этом случае, функция возвращает число байтов, уже перемещенных, указывая частичный успех.
Это может вызвать ненадежное поведение на устройствах для записи (включая датаграммный сокет; см. Раздел 11.9 [Датаграммы]), при разбивании одного чтения или записи на два чтения или две записи для двух единиц. Фактически, не имеется никакой проблемы, потому что прерывание после частичной передачи не может случаться на таких устройствах; они всегда передают всю запись в одном пакете, без ожидания, если только передача данных началась.
21.6 Сигналы Производства
Кроме сигналов, которые сгенерированы в результате аппаратной проверки или прерывания, ваша программа может явно посылать сигналы себе или другому процессу.
Передача Сигналов Самому себе
Процесс может посылать себе сигнал функцией raise. Эта функция объявлена в "signal.h".
int raise (int signum)
Функция raise посылает сигнал процессу вызова. Она возвращает нуль, если она успешна и значение отличное от нуля, если она терпит неудачу. Единственная причина для отказа - если значение signum недопустимо.
int gsignal (int signum)
Функция gsignal функция делает то же самое, что и raise; она нужна только для совместимости с SVID.
Удобное использование raise - воспроизвести заданное по умолчанию поведение сигнала, который Вы обработали. Например, предположите, что пользователь вашей программы печатает символ SUSP (обычно C-z; см. Раздел 12.4.9 [Специальные Символы]) чтобы послать интерактивный сигнал останова (SIGTSTP), и Вы хотите очистить некоторые внутренние буфера данных перед остановкой. Вы можете сделать это примерно так:
#include
void tstp_handler (int sig) { signal (SIGTSTP, SIG_DFL); . . . raise (SIGTSTP); } void cont_handler (int sig) { signal (SIGCONT, cont_handler); signal (SIGTSTP, tstp_handler); } int main (void) { signal (SIGCONT, cont_handler); signal (SIGTSTP, tstp_handler); . . . }
Примечание о переносимости: raise произошла из ANSI C. Более старые системы не могут поддерживать ее, так что ее использование уничтожает возможность переноса. См. Раздел 21.6.2 [Передача сигналов Другомму Процессу].
Передача сигналов Другому Процессу
Функция kill может использоваться, чтобы послать сигнал другому процессу. Несмотря на имя, она может использоваться для множества вещей отличных от завершения процесса. Вот некоторые примеры ситуаций, где Вы могли бы хотеть посылать сигналы между процессами:
- Родительский процесс назначает дочернему выполнять задачу, возможно имеющую бесконечный цикл, и завершает дочерний процесс, когда задача больше ненужна.
- Процесс выполняется как часть группы, и должен завершать или информировать другие процессы в группе, когда ошибка или другое событие происходит.
- Два процесса должны синхронизироваться при работе вместе.
Функция kill объявлена в " signal.h ".
int kill (pid_t pid, int signum) (функция)
Функция kill посылает сигнал signum процессу или группе процесса, заданной pid. Кроме сигналов, перечисленных в Разделе 21.2 [Стандартные Сигналы], signum может также иметь нулевое значение, чтобы проверить правильность pid. Pid определяет процесс или группу процесса, как получателя сигнала:
- Pid > 0
-
Процесс, чей идентификатор - pid.
- Pid == 0
-
Все процессы в той же самой группе что и отправитель. Отправитель непосредственно не получает сигнал.
- Pid < -1
-
Группа процесса, чей идентификатор есть -pid.
- Pid == -1
-
Если процесс привилегирован, посылает сигнал всем процессам, кроме некоторых специальных процессов системы. Иначе, посылает сигнал всем процессам с тем же самым эффективным ID пользователя.
Процесс может посылать сигнала себе обращением
kill (getpid (), signum).
Если kill используется процессом, чтобы послать сигнал себе, и сигнал не блокирован, то kill позволяет этому процессу принять по крайней мере один сигнал (который мог некоторым другим задержанным сигналом вместо сигнала signum) прежде чем он возвращается.
Возвращаемое значение из kill - нуль, если сигнал может быть послан успешно. Иначе, никакой сигнал не послан, и возвращается значение -1. Если pid определяет посылку сигнала отдельным процессам, kill успешно завершается, если он может посылать сигнал по крайней мере одному из них. Не имеется никакого способа, которым Вы можете узнать, который из процессов получил сигнал или что все они получили его.
Следующие errno условия ошибки определены для этой функции:
- EINVAL
-
аргумент signum - недопустимое или неподдерживаемое число.
- EPERM
-
Вы не имеете привилегий, чтобы послать сигнал процессу или любому из процессов в группе процесса, именованной pid.
- ESCRH
-
id аргумент не относится к существующему процессу или группе.
int killpg (int pgid, int signum) (функция)
Подобна kill, но посылает сигнала группе процесса pgid. Эта функция предусмотрена для совместимости с BSD.
Как простой пример kill, обращение kill (getpid (), sig) имеет тот же самый эффект как raise (sig).
Права для использования kill
Имеются ограничения, которые запрещают Вам использовать kill, чтобы послать сигнал любому процессу.
Они предназначены, чтобы предотвратить антиобщественное поведение типа произвольного уничтожения процессов, принадлежащих другому пользователю. Обычно, kill используется, чтобы передать сигналы между родителем и дочерним процессами, или процессами братьями, и в этих ситуациях Вы обычно имеете право послать сигнал. Единственное общее исключение - когда Вы выполняете программу setuid на дочернем процессе; если программа изменяет реальный UID также как эффективный UID, Вы не можете иметь право, чтобы послать сигнал. Программа su делает это.
Имеет ли процесс право, чтобы послать сигнал другому процессу, определяется пользовательскими ID этих двух процессов. Это понятие обсуждено подробно в Разделе 25.2 [Владелец Процесса].
Вообще, чтобы можно было посылать сигнал другому процессу, посылающий процесс должен принадлежать привилегированному пользователю (подобно " root "), или реальный, или эффективный пользовательский ID процесса посылки должен соответствовать реальному, или эффективному пользовательскому ID процесса получения. В некоторых реализациях, родительский процесс способен послать сигнал дочернему процессу, даже если пользовательские ID не соответствуют. Другие реализации, могут предписывать другие ограничения.
Сигнал SIGCONT - частный случай. Он может быть послан, если отправитель - часть того же самого сеанса что и получатель, независимо от пользовательских ID.
Использование kill для Связи
Вот более длинный пример, показывающий, как сигналы могут использоваться для межпроцессорной связи. Это то, для чего предусмотрены сигналы SIGUSR1 и SIGUSR2. Так как эти сигналы фатальны по умолчанию, процесс, который, как предполагается, получает их, должен обрабатывать их через signal или sigaction.
В этом примере, родительского процесс порождает дочерний процесс и ждет пока дочерний завершит инициализацию. Дочерний процесс сообщает родителю, когда он готов, посылая ему сигнал SIGUSR1, используя функцию kill.
#include
#include #include #include volatile sig_atomic_t usr_interrupt = 0; void synch_signal (int sig) { usr_interrupt = 1; } /* Дочерний процесс выполняет эту функцию. */ void child_function (void) { printf ("I'm here!!! My pid is %d.\n", (int) getpid ()); kill (getppid (), SIGUSR1); puts ("Bye, now...."); exit (0); } int main (void) { struct sigaction usr_action; sigset_t block_mask; pid_t child_id; sigfillset (&block_mask); usr_action.sa_handler = synch_signal; usr_action.sa_mask = block_mask; usr_action.sa_flags = 0; sigaction (SIGUSR1, &usr_action, NULL); /* Создание дочернего процесса. */ child_id = fork (); if (child_id == 0) child_function (); while (!usr_interrupt) ; puts ("That's all, folks!"); return 0; }
Этот пример использует активное ожидание, которое является плохим, потому что это потеря времени CPU, которое другие программы могли бы иначе использовать. Лучше просить, чтобы система ждала, пока сигнал не прибывает. См. пример в Разделе 21.8 [Ожидание Сигнала].
21.7 Блокированные Сигналы
Блокирование сигнала означает сообщение операционной системе, чтобы задержать его и передать его позже. Вообще, программа просто так не блокирует сигналы, она может также игнорировать их, устанавливая их действия как SIG_IGN. Но полезно блокировать сигналы ненадолго, чтобы предотвратить прерывание чувствительных операций. Например:
- Вы можете использовать функцию sigprocmask, чтобы блокировать сигналы, в то время как Вы изменяете глобальные переменные, которые также изменяются обработчиками для этих сигналов.
- Вы можете устанавливать sa_mask в вашем обращении к sigaction, чтобы блокировать некоторые сигналы, в то время как выполняется специфический обработчик сигнала. Этим способом, обработчик сигнала может выполняться без того, чтобы прерваться сигналами.
Почему Полезно Блокированиие Сигналов
Временное блокирование сигналов с sigprocmask дает Вам возможность предотвратить прерывания при выполнении критических частей вашего кода. Если сигналы прибывают в эту часть программы, они будут переданы позже, после того, как Вы откроете их.
Например, это полезно - для совместного использования данных обработчиком сигнала и остальной частью программы. Если тип данных - не sig_atomic_t (см. Раздел 21.4.7 [Быстрый Доступ к данным]), то обработчик сигнала может выполняться, когда остальная часть программы закончила только половину чтения или запииси данных.
Чтобы делать программу надежной, Вы можете предотвращать выполнение обработчика сигнала, в то время как остальная часть программы исследует или изменяет эти данные, блокировав соответствующий сигнал в частях программы, которые касаются данных.
Блокирование сигналов также необходимо, когда Вы хотите выполнить некоторое действие только, если сигнал не прибыл. Предположите, что обработчик для сигнала устанавливает флаг типа sig_atomic_t; Вы хотели бы проверить флаг и выполнить действие, если флаг не установлен. Это ненадежно. Предположите, что сигнал передан немедленно после того, как Вы проверяете флаг, но перед последовательным действием: тогда программа выполнит действие, даже если сигнал прибыл.
Единственый способ проверять, надежно ли прибыл ли сигнал, состоит в том, чтобы проверять это в то время, когда сигнал блокирован.
Наборы Сигналов
Все функции блокирования сигнала используют структуру данных называемую набором сигналов, чтобы определить на какие сигналы воздействовать. Таким образом, каждое действие включает две стадии: создание набора сигналов, и передача его как аргумента библиотечной функции.
Эти средства объявлены в заглавном файле " signal.h ".
sigset_t
Тип данных sigset_t используется, чтобы представить набор сигналов. Внутренне, он может быть выполнен как целый или как структурный тип.
Для переносимости, чтобы инициализировать, изменять и читать информацию из объектов sigset_t, используйте только функции, описанные в этом разделе, не пробуйте манипулировать ими непосредственно.
Имеются два способа инициализировать набор сигналов. Вы можете первоначально определить его пустыми через sigemptyset и затем добавлять заданные сигналы индивидуально. Или Вы можете определить его полными через sigfillset и тогда удалять заданные сигналы индивидуально.
Вы должны всегда инициализировать набор сигналов одной из этих двух функций перед использованием его любым другим способом. Не пробуйте устанавливать все сигналы явно, потому что объект sigset_t может включать некоторую другую информацию (подобно полю версии) которое должно быть инициализировано также.
int sigemptyset (sigset_t *set)
Эта функция инициализирует набор наборов сигналов, чтобы исключить все определенные сигналы. Она всегда возвращает 0.
int sigfillset (sigset_t *set)
Эта функция инициализирует набор наборов сигналов, чтобы включить все определенные сигналы. Снова, возвращаемое значение - 0.
int sigaddset (sigset_t *set, int signum)
Эта функция добавляет сигнал signum к набору наборов сигналов. Все что она делает - изменяет набор; она не блокирует или не открывает никаких сигналов.
Возвращаемое значение - 0 при успехе и -1 при отказе. Следующее errno - условие ошибки определено для этой функции:
- EINVAL
-
аргумент знака не определяет допустимый сигнал.
int sigdelset (sigset_t *set, int signum)
Эта функция удаляет сигнал signum из набора сигналов. Возвращаемое значение и условия ошибки - такие же как для sigaddset.
В заключение, имеется функция, чтобы проверить то, что сигналы находятся в наборе сигналов:
int sigismember (const sigset_t *set, int signum)
Функция sigismember проверяет, является ли сигнал signum элементом набора сигналов. Она возвращает 1, если сигнал находится в наборе, 0 если нет, и -1, если имеется ошибка.
Следующее errno условие ошибки определено для этой функции:
- EINVAL
-
аргумент signum не определяет допустимый сигнал.
Маска Сигналов Процесса
Набор сигналов, которые в настоящее время блокированы, называется маской сигналов. Каждый процесс имеет собственную маску сигналов. Когда Вы создаете новый процесс (см. Раздел 23.4 [Создание Процесса]), он наследует маску родителя. Вы можете блокировать или открывать сигналы с большей гибкостью, изменяя маску сигналов.
Прототип для sigprocmask функции находится в " signal.h ".
int sigprocmask (int how, const sigset_t *set, sigset_t *oldset)
Функция Sigprocmask используется, чтобы исследовать или изменять маску сигналов процесса, аргумент how определяет, как изменяется маска сигналов, и должен быть одним из следующих значений:
SIG_BLOCK
Блокирует сигналы в set, и добавляет их к существующей маске. Другими словами, новая маска - объединение существующей маски и set.
SIG_UNBLOCK
Открывает сигналы в set и удаляет их из существующей маски.
SIG_SETMASK
Использует set для маски; игнорируя предыдущее значение маски.
Последний аргумент, oldset, используется, чтобы возвратить информацию относительно старой маски сигналов процесса. Если Вы хотите только изменять маску без того, чтобы рассмотривать ее, передавайте пустой указатель как oldset аргумент. Аналогично, если Вы хотите знать что находится в маске, без того, чтобы заменить ее, передайте пустой указатель для set. Oldset аргумент часто используется, чтобы запомнить предыдущую маску сигналов, чтобы восстановить ее позже.
Если вызов sigprocmask открывает любые отложенные сигналы, то по крайней мере один из этих сигналов будет передан процессу прежде, чем sigprocmask возвртится. Порядок, в котором передаются отложенные сигналы является не определенным, но Вы можете управлять порядком явно, делая несколько обращений к sigprocmask, чтобы открывать различные сигналы по одному. Sigprocmask функция возвращает 0, если она успешна, и -1, в противном случае. Следующие errno условия ошибки определены для этой функции:
- EINVAL
-
Аргумент how недопустим.
Вы не можете блокировать SIGKILL и SIGSTOP, но если набор сигналов включает их, то sigprocmask только игнорируют их вместо того, чтобы возвратить состояние ошибки.
Блокирование для Проверки Наличия Сигнала
Вот простой пример. Предположите, что Вы устанавливаете обработчик для сигналов SIGALRM, который устанавливает флаг всякий раз, когда прибывает сигнал, и ваша программа main проверяет этот флаг время от времени и сбрасывает его. Вы можете предотвращать прибытие дополнительных сигналов SIGALRM в неподходящее время, ограничивая критическую часть кода обращениями к sigprocmask, примерно так:
sig_atomic_t flag = 0; int main (void) { sigset_t block_alarm; . . . sigemptyset (&block_alarm); sigaddset (&block_alarm, SIGALRM); while (1) { sigprocmask (SIG_BLOCK, &block_alarm, NULL); if (flag) { actions-if-not-arrived flag = 0; } sigprocmask (SIG_UNBLOCK, &block_alarm, NULL); . . . } }
Блокирование Сигналов для Обработчика
Когда обработчик сигнала вызывается, Вы обычно хотите, чтобы он закончился без прерываний другим сигналом. С момента старта обработчика до момента его окончания, Вы должны блокировать сигналы, которые могли бы запутать его или разрушить данные.
Когда функция обработчика вызывается для сигнала, этот сигнал автоматически блокируется (в дополнение к любым другим сигналам, которые являются уже в маске сигналов процесса) пока обработчик выполняется. Если Вы устанавливаете обработчик для SIGTSTP, например, то поступление этого сигнала, вынуждает дальнейшие SIGTSTP сигналы ждать в течение выполнения обработчика.
Однако, по умолчанию, другие виды сигналов не блокированы; они могут прибывать в течение выполнения обработчика.
Надежный способ блокировать другие виды сигналов в течение выполнения обработчика состоит в том, чтобы использовать sa_mask элемент структуры sigaction.
Вот пример:
#include
#include void catch_stop (); void install_handler (void) { struct sigaction setup_action; sigset_t block_mask; sigemptyset (&block_mask); sigaddset (&block_mask, SIGINT); sigaddset (&block_mask, SIGQUIT); setup_action.sa_handler = catch_stop; setup_action.sa_mask = block_mask; setup_action.sa_flags = 0; sigaction (SIGTSTP, &setup_action, NULL); } Это более надежно чем блокирование других сигналов явно в коде обработчика. Если Вы блокируете сигналы в обработчике, Вы не может избежать по крайней мере короткого интервала в начале обработчика, где они еще не блокированы. Вы не можете удалять сигналы из текущей маски процесса, используя этот механизм. Однако, Вы можете делать обращения к sigprocmask внутри вашего обработчика, чтобы блокировать или открыть сигналы, как Вы желаете. В любом случае, когда обработчик возвращается, система восстанавливает маску, которая была до обработчика. Прверка Отложенных Сигналов Вы можете выяснять, какие сигналы отложены в любое время, вызывая sigpending. Эта функция объявлена в " signal.h ".
int sigpending (sigset_t *set) (функция)
Sigpending функция сохраняет информацию относительно отложенных сигналов в set. Если там - отложенный сигнал, который блокирован, то этот сигнал - элемент возвращенного set. (Вы можете проверять является ли специфический сигнал элемент этого set, использующего sigismember; см. Раздел 21.7.2 [Наборы Сигналов].)
Возвращаемое значение - 0 при успехе, и -1 при отказе.
Тестирование отложен ли сигнал полезно не часто. Тестирование сигнала который не блокирован - почти всегда бессмысленно.
Вот пример.
#include
#include sigset_t base_mask, waiting_mask; sigemptyset (&base_mask); sigaddset (&base_mask, SIGINT); sigaddset (&base_mask, SIGTSTP); sigprocmask (SIG_SETMASK, &base_mask, NULL); . . . sigpending (&waiting_mask); if (sigismember (&waiting_mask, SIGINT)) { /* Пользователь пробовал уничтожать процесс. */ } else if (sigismember (&waiting_mask, SIGTSTP)) { /*Пользователь пробовал остановить процесс.*/ }
Не забудьте, что, если имеется задержка некоторого сигнала для вашего процесса, дополнительные сигналы этого же самого типа могут быть отброшены. Например, если сигнал SIGINT отложен, когда прибывает другой сигнал SIGINT, ваша программа будет возможно видеть только один из них, когда Вы откроете этот сигнал.
Примечание Переносимости: функция sigpending новая в POSIX.1. Более старые системы не имеют никакого эквивалентного средства.
Запоминание Сигнала, для отложенного вызова
Вместо того, чтобы блокировать сигнал используя библиотечные средства, Вы можете получить почти те же самые результаты, делая так чтобы обработчик устанавливал флаг, который будет проверен позже, когда Вы "откроете". Вот пример:
volatile sig_atomic_t signal_pending; volatile sig_atomic_t defer_signal; void handler (int signum) { if (defer_signal) signal_pending = signum; else ... /*"Действительно" обрабатываем сигнал.*/ } . . . void update_mumble (int frob) { defer_signal++; mumble.a = 1; mumble.b = hack (); mumble.c = frob; defer_signal--; if (defer_signal == 0 && signal_pending != 0) raise (signal_pending); }
Обратите внимание, как специфический сигнал сохранен в signal_pending. Этим способом, мы можем обрабатывать несколько типов неудобных сигналов.
Мы увеличиваем и уменьшаем defer_signal так, чтобы вложенные критические разделы работали правильно; таким образом, если update_mumble вызывалась с signal_pending, уже отличным от нуля, сигналы будут отсрочены не только внутри update_mumble, но также внутри вызывающего оператора. Вот почему мы не проверяем signal_pending, если defer_signal все еще отличен от нуля.
Приращение и уменьшение defer_signal требует больше чем одну команду; и возможно сигнал случиться в середине. Но это не вызывает никакой проблемы. Если сигнал случается достаточно рано чтобы увидеть значение до приращения или уменьшения, то это эквивалентно сигналу который, пришел перед началом приращения или уменьшения, что является случаем который работает правильно.
Абсолютно необходимо увеличить defer_signal перед тестированием signal_pending, потому что это позволяет избежать тонкой ошибки. Если бы мы делали это в другом порядке, примерно так,
if (defer_signal == 1 && signal_pending != 0) raise (signal_pending); defer_signal--;
то сигнал, прибывающий между условным оператором и оператором уменьшения был бы эффективно "потерян" на неопределенное количество времени. Обработчик просто установил бы defer_signal, но программа, уже проверявшая эту переменную, не будет проверять переменную снова.
Ошибки подобно этим, называются ошибками синхронизации. Они особенно опасны, потому что они случаются редко и их почти невозможны воспроизвести. Вы не сможете найти их отладчиком, как Вы нашли бы воспроизводимую ошибку. Так что надо быть особенно осторожным, чтобы избежать их.
21.8 Ожидание Сигнала
Если ваша программа управляется внешними событиями, или использует сигналы для синхронизации, то она должена возможно ждать, пока сигнал не прибудет.
Использование pause
Простой способ ждать прибытия сигнала - вызвать pause.
int pause () (функция)
Функция pause приостанавливает выполнение программы, пока не прибывает сигнал, чье действие должно также выполнить функцию обработчика, или завершить процесс.
Если сигнал выполняет функцию обработчика, то pause возвращается. Это рассматривается как неудача (так как "успешное" поведение должно было бы приостановить программу навсегда), так что возвращаемое значение -1. Даже если Вы определяете, что другие примитивы должны продолжиться, когда обработчик системы возвращается (см. Раздел 21.5 [Прерванные Примитивы]), это не имеет никакого эффекта на pause; она всегда терпит неудачу, когда сигнал обработан.
Следующие errno условия ошибки определены для этой функции:
- EINTR
-
функция была прервана сигналом.
Если сигнал вызывает окончание программы, pause не возвращается (очевидно).
Функция pause объявлена в " unistd.h ".
Проблемы с pause
Простота pause может скрывать серьезные ошибки синхронизации, которые могут привести программу к зависанию.
Безопасно использовать pause, если реальная работа вашей программы выполняется обработчиками сигнала непосредственно, а программа не делает ничего кроме обращения к pause. Каждый сигнал будет заставлять обработчик делать следующий пакет работы, которая должна быть выполнена, и возвращаться, так чтобы цикл программы мог вызывать pause снова.
Вы не можете безопасно использовать pause, чтобы ждать, пока не прибудет еще один сигнал, и тогда продолжить реальную работу.
Даже если Вы принимаете меры, чтобы обработчик сигнала сотрудничал, устанавливая флаг, Вы все еще не можете использовать pause надежно. Вот пример такой проблемы:
if (!usr_interrupt) pause (); /* работа, после прибытия сигнала. * / . . .
Она имеет ошибку: сигнал может прибывать после того, как переменная usr_interrupt проверена, но перед обращением к pause. Если никакие дальнейшие сигналы не прибывают, процесс никогда не выполнится снова.
Вы можете изменять верхнее ограничение ожидания, используя sleep в цикле, вместо того чтобы использовать pause. (См. Раздел 17.4 [Бездействие].) Вот, на что это походит:
while (!usr_interrupt) sleep (1); /* работа, после прибытия сигнала. */ . . .
Для некоторых целей это достаточно удобно. Но немного более сложно. Вы можете ждать, пока специфический обработчик сигнала не выполнен, надежно, используя sigsuspend.
Использование sigsuspend
Чистый и надежный способ ждать сигнал состоит в том, чтобы блокировать его и тогда использовать sigsuspend.
Используя sigsuspend в цикле, Вы можете ждать некоторые виды сигналов, разрешая другим видам сигналов обрабатываться их обработчиками.
int sigsuspend (const sigset_t *set) (функция)
Эта функция заменяет маску сигналов процесса на set и тогда приостанавливает процесс, пока не передан сигнал, чье действие должно завершать процесс или вызывать функцию обработки сигнала. Другими словами, программа действительно будет приостановлена, пока один из сигналов, который - не элемент set, не прибудет.
Если процесс пробужден сигналом, который вызывает функцию обработчика, и функция обработчика возвращается, то sigsuspend также возвращается.
Маска остается set только, пока sigsuspend ждет. Функция sigsuspend всегда восстанавливает предыдущую маску сигналов, когда она возвращается.
Возвращаемое значение и условия ошибки - такие же как для pause.
С sigsuspend, Вы можете заменять pause или цикл sleep в предыдущем разделе кое-чем полностью надежным:
sigset_t mask, oldmask; . . . sigemptyset (&mask); sigaddset (&mask, SIGUSR1); . . . / * Ждем получения сигнала. * / sigprocmask (SIG_BLOCK, &mask, &oldmask); while (!usr_interrupt) sigsuspend (&oldmask); sigprocmask (SIG_UNBLOCK, &mask, NULL);
Этот последний фрагмент кода немного сложен. Отметте, что когда sigsuspend возвращается, она сбрасывает маску сигналов процесса к первоначальному значению, в этом случае сигнал SIGUSR1 еще раз блокирован. Второе обращение к sigprocmask необходимо чтобы явно открыть этот сигнал.
21.9 BSD Обработка Сигнала
Этот раздел описывает альтернативные функции обработки сигнала, происходящие от UNIX BSD. Эти средства были современными, в их время; сегодня, они обычно устаревшие, и обеспечены в основном для совместимости с UNIX BSD.
Они обеспечивают одну возможность, которая не доступна через функции POSIX: Вы можете определять отдельный стек для использования в некоторых обработчиках сигнала. Использование стека сигнала единственый способ, которым Вы можете обрабатывать сигнал, вызванный переполнением стека.
POSIX и BSD Средства Обработки Сигналов
Имеются много подобий между BSD и POSIX средствми обрабатывающими сигналы, потому что средства POSIX были вдохновлены средствами BSD. Кроме наличия различных имен для всех функций, чтобы избежать конфликтов, есть несколько основных различий:
- UNIX BSD представляет маски сигналов как int битовая маска, а не как объект sigset_t.
- Средства BSD используют отличное значение по умолчанию для про верки, должен ли прерванный примитив терпеть неудачу или нет. Средства POSIX делают сбой системных вызовов, если Вы не определяете, что они должны продолжиться. Со средством BSD, значение по умолчанию не должно делать сбой системных вызовов, если Вы не говорите, что они должны терпеть неудачу. См. Раздел 21.5 [Прерванные Примитивы].
- UNIX BSD имеет понятие стека сигналов. Это - альтернативный стек, который используется в течение выполнения функций обработчика сигнала, вместо нормального стека выполнения.
Средства BSD объявлены в " signal.h ".
21.10 Функция BSD, чтобы Установить Обработчик
struct sigvec (тип данных)
Этот тип данных - эквивалент BSD struct sigaction (см. Раздел 21.3.2 [Сложная Обработка Сигнала] ); он используется, чтобы опреде лить действия сигнала для sigvec функции. Он содержит следующие эле менты:
sighandler_t sv_handler
Это - функция обработчика.
int sv_mask
Это - маска дополнительных сигналов, которые будут блокированы, в то время как функция обработчика вызывается.
int sv_flags
Это - битовая маска, используемая, чтобы определить различные флаги, которые воздействуют на поведение сигнала. Вы можете также обратиться к этому полю как sv_onstack. Эти символические константы могут использоваться, чтобы обеспе чить значения для sv_flags поля структуры sigvec. Это поле - значение битовой маски,следовательно Вам необходимо слить флаги, представляющие интерес для Вас вместе через OR.
int SV_ONSTACK
Если этот бит установлен в sv_flags поле структуры sigvec, это означает - использовать стек сигнала при получении сигнала.
int SV_INTERRUPT (макрос)
Если этот бит установлен в sv_flags поле структуры sigvec, это означает что, системные вызовы, прерванные этим видом сигнала не долж ны быть перезапущены, если обработчик возвращается; взамен, системные вызовы должны возвратиться с EINTR состоянием ошибки. См. Раздел 21.5 [Прерванные Примитивы].
int SV_RESETHAND (макрос)
Если этот бит усткновлен в sv_flags поле структуры sigvec, это означает - сбросить действие для сигнала обратно к SIG_DFL, когда сиг нал получен.
int sigvec (int signum, const struct sigvec *action,struct sigvec *old_action)
Эта функция - эквивалент sigaction; она устанавливает действие для сигнала signum, возвращая информацию относительно предыдущего действия для этого сигнала в old_action.
int siginterrupt (int signum, int failflag) (функция)
Эта функция определяет, что использовать, когда некоторые прими тивы прерваны обрабаткой сигнала signum. Если failflag - ложь, то при митивы рестартуют после сигнала. Если failflag - истина, обработка signum заставляет эти примитивы терпеть неудачу с кодом ошибки EINTR. См. Раздел 21.5 [Прерванные Примитивы].
Функции BSD для Блокирования Сигналов
int sigmask (int signum) (макрос)
Эта макрокоманда возвращает маску сигналов, которая имеет бит для установки сигнала signum. Вы можете слить через OR результаты отдель ных обращений к sigmask вместе, чтобы определять больше чем один сиг нал. Например,
(sigmask (SIGTSTP) | sigmask (SIGSTOP) | sigmask (SIGTTIN) | sigmask (SIGTTOU))
определяет маску, которая включает все сигналы останова управления заданиями.
int sigblock (int mask) (функция)
Эта функция эквивалентна sigprocmask (см. Раздел 21.7.3 [Маска сигналов Процесса]) с аргументом how - SIG_BLOCK: она добавляет сигна лы, заданные маской к набору блокированных сигналов процесса вызова. Возвращаемое значение - предыдущий набор блокированных сигналов.
int sigsetmask (int mask) (функция)
Это эквивалент функции sigprocmask (см. Раздел 21.7.3 [Маска сиг налов Процесса]) с аргументом how - SIG_SETMASK: она устанавливает маску сигналов вызывающего процесса как mask. Возвращаемое значение предыдущий набор блокированных сигналов.
int sigpause (int mask) (функция)
Эта функция - эквивалент sigsuspend (см. Раздел 21.8 [Ожидание Сигнала]): она устанавливает маску сигналов вызывающего процесса как mask, и ждет прибытия сигнала. Она при возвращаении восстанавливает предыдущий набор блокированных сигналов.
Использование Отдельного Стека Сигнала
Стек сигнала - специальная область памяти, которую нужно исполь зовать как стек в течение выполнения обработчиков сигнала. Он должен быть довольно большим, чтобы избежать переполнения; макрокоманда SIGS TKSZ определяет канонический размер для стеков сигналов. Вы можете ис пользовать malloc, чтобы зарезервировать пространство для стека. Вызо вите sigaltstack или sigstack, чтобы система использовала это прост ранство для стека сигнала.
Вам не нужно писать обработчик сигнала по-другому чтобы использо вать стек сигнала. Переключение одного стека на другой происходит ав томатически. Однако, некоторые отладчики на некоторых машинах могут запутаться, если Вы исследуете след стека, в то время как обработчик, который использует стек сигнала, выполняется.
Имеются два интерфейса для сообщения системе использовать отдель ный стек сигнала. Sigstack - более старый интерфейс, который исходит из 4.2 BSD. Sigaltstack - более новый интерфейс, и исходит из 4.4 BSD. Интерфейс sigaltstack имеет преимущество - не требуется, чтобы ваша программа знала в каком направлении растет стек, что зависит от специ фической машины и операционной системы.
struct sigaltstack (тип данных)
Эта структура описывает стек сигнала. Она содержит следующие эле менты:
void *ss_sp
Этим указываем на основание стека сигнала.
size_t ss_size
- размер (в байтах) стека сигнала, на который указывает "ss_sp ". Вы должны установить здесь - сколько места Вы зарезервирова ли для стека. Есть две макрокоманды, определенные в " signal.h " которые Вы должны использовать в вычислении этого размера:
SIGSTKSZ
- каноническиий размер для стека сигнала. Он должен быть дос таточным для нормальных использований.
MINSIGSTKSZ
- количество пространства стека сигнала, нужное операционной системе только, чтобы выполнить сигнал. Размер стека сигнала должен быть больший чем этот.
Для большинства случаев SIGSTKSZ для ss_size достаточен. Но Вы можете захотеть использовать различный размер. В этом случае, Вы долж ны зарезервировать MINSIGSTKSZ дополнительных байт для стека сигнала и увеличивать ss_size.
int ss_flags
Это поле содержит поразрядное OR этих флагов:
SA_DISABLE
Сообщает системе, что она не должна использовать стек сигнала.
SA_ONSTACK
Устанавливается системой, и указывает, что стек сигнала использован в настоящее время.
int sigaltstack (const struct sigaltstack *stack, struct sigaltstack *oldstack) (функция)
Sigaltstack функция определяет альтернативный стек для использо вания в течение обработки сигнала.
Если oldstack - не пустой указатель, информация относительно в настоящее время установленного стека сигнала будет возвращена в распо ложение, на которое он указывает. Если stack - не пустой указатель, то он будет установлен как новый стек для использования обработчиками сигнала.
Возвращаемое значение - 0 при успехе и -1 при отказе. Если si galtstack сбоит, она устанавливает errno как одно из этих значений:
- EINVAL
-
Вы пробовали отключать стек, который был фактически использован в настоящее время.
- ENOMEM
-
Размер альтернативного стека был слишком мал. Он должен быть большее чем MINSIGSTKSZ.
Вот более старый интерфейс sigstack.
struct sigstack (тип данных)
Эта структура описывает стек сигнала. Она содержит следующие эле менты:
void *ss_sp
- указатель вершины стека. Если стек растет вниз на вашей машине, он должен указывать на начало области, которую Вы зарезервировали. Если стек растет вверх, он должен указывать на нижнюю часть.
int ss_onstack
Это поле истинно, если процесс в настоящее время использует этот стек.
int sigstack (const struct sigstack *stack, struct sigstack *oldstack) (функция)
Sigstack функция определяет альтернативный стек для использования в течение обработки сигнала.
Когда сигнал получен процессом, и стек сигнала используется, система переключается на в настоящее время установленный стек сигнала, в то время как выполняется обработчик для этого сигнала.
Если oldstack - не, пустой указатель, информация относительно в настоящее время установленного стека сигнала будет возвращена в расположение, на которое он указывает. Если stack - не пустой указатель, то он будет установлен как новый стек для использования обработчиками сигнала.
Возвращаемое значение - 0 при успехе и -1 при отказе.
22. Запуск и Окончание Процесса
Процессы - примитивные модули для распределения ресурсов системы. Каждый процесс имеет собственное адресное пространство. Процесс выполняет программу; Вы можете иметь многократные процессы, выполняющие ту же самую программу, но каждый процесс имеет собственную копию программы внутри собственного адресного пространства и выполняет ее независимо от других копий.
Эта глава объясняет, что ваша программа должна делать, чтобы обработать запуск процесса, завершить процесс, и получить информацию (аргументы и среду) из родительского процесса.
22.1 Аргументы Программы
Система начинает программу C, вызывая функцию main. Вы должны написать функцию, именованную main, иначе Вы не будете способны линковать вашу программу без ошибок.
Вы можете определять main без аргументов, или брать два аргумента, которые представляют аргументы командной строки программы, примерно так:
int main (int argc, char *argv[])
Аргументы командной строки - отделяемые пропуском лексемы, заданные в команде оболочки, используемой, чтобы вызвать программу; таким образом, в " cat foo bar ", аргументы - " foo " и " bar ". Программа может рассматривать аргументы командной строки единственым способом - через аргументы main.
Значение argc аргумента - число аргументов командной строки. Аргумент argv - вектор строк; элементы - индивидуальные строки аргументов командной строки. Имя файла выполняемой программы также включено в вектор как первый элемент; значение argc учитывает этот элемент. Пустой указатель всегда следует за последним элементом: argv [argc] - это пустой указатель.
Для команды " cat foo bar ", argc - 3, и argv имеет три элемента, " cat ", " foo " и " bar ".
Если синтаксис для аргументов командной строки вашей программы является достаточно простым, Вы может просто выбирать аргументы из argv вручную. Но если ваша программа берет фиксированное число аргументов, или все аргументы интерпретируются одинаковым образом (как имена файлов, например), Вам лучше использовать getopt, чтобы делать синтаксический анализ.
Синтаксические Соглашения Аргументов Программы
POSIX рекомендует эти соглашения для аргументов командной строки. Getopt (см. Раздел 22.1.2 [Опции Синтаксического анализа]) облегчит их реализацию.
- Аргументы - опции, если они начинаются с разделителя дефиса (" - ").
- За разделителем могут следовать много опций в одиночной лексеме, если опции не берут аргументов. Таким образом, " -abc " эквивалентно " -a -b -c ".
- Имена опций - одиночные алфавитно-цифровые символы (как для isalnum; см. Раздел 4.1 [Классификация Символов]).
- Некоторые опции требуют аргумента. Например, команда ` -o ' ld требует аргумент - имя выходного файла.
- Опция и ее аргумент могут не занимать отдельные лексемы. (Другими словами, пропуск, отделяющий их необязателен.) Таким образом, " -o foo " и " -ofoo " эквивалентны.
- Опции обычно предшествуют другим аргументам, не-опциям. команды
Реализация getopt в библиотеке GNU C обычно делает так, как будто все аргументы опции были определены перед всеми аргументами не-опциями для целей синтаксического анализа, даже если пользователь вашей программы смешал опции и аргументы не-опции. Она делает это, переупорядочивая элементы массива argv. Это поведение нестандартно; если Вы хотите подавлять его, определите _POSIX_OPTION_ORDER переменную среды. См. Раздел 22.2.2 [Стандартная Среда].
- Аргумент " -- " завершает все опции; все остальные аргументы обрабатываются как аргументы-не-опции, даже если они начинаются с дефиса.
- Лексема, состоящая из одиночного символа дефиса интерпретируется как обычный аргумент-не-опция. Обычно, она используется, чтобы определить ввод из или вывод в стандартный ввод и вывод.
- Опции могут быть обеспечены в любом порядке, или появляться многократно. Интерпретация оставлена до специфической прикладной программы.
GNU добавляет длинные опции к этому соглашению. Длинные опции состоят из " -- " сопровождаемых именем, составленным из алфавитно цифровых символов, и подчеркивания. Имена опций - обычно от одного до трех слов длинной, с дефисами, чтобы отделить слова. Пользователи могут сокращать имена опций, если только сокращения уникальны.
Пример длинной опции " --name=value ". Этот синтаксис дает возможность длинной опции принять аргумент, который является самостоятельно необязательным.
В конечном счете, система GNU будет обеспечивать длинные имена опций в оболочке.
Опции Программ Синтаксического анализа
Имеются подробности относительно того, как вызвать getopt функцию. Чтобы использовать это средство, ваша программа должна включить заглавный файл " unistd.h ".
int opterr (переменная)
Если значение этой переменной является отличным от нуля, то getopt, печатает сообщение об ошибках в стандартный поток ошибки, если она сталкивается с неизвестным символом опции или опцией с отсутствующим требуемым аргументом. Это - заданное по умолчанию поведение. Если Вы обнуляете эту переменную, getopt, не печатает никаких сообщений, но она все еще возвращает символ ? чтобы указывать ошибку.
int optopt (переменная)
Когда getopt сталкивается с неизвестным символом опции или опцией с отсутствующим требуемым аргументом, она сохраняет этот символ опции в этой переменной. Вы можете использовать ее для обеспечения ваших собственных диагностических сообщений.
int optind (переменная)
Эта переменная будет установлена getopt как индекс следующего элемента массива argv, который будет обработан. Если getopt нашла все аргументы-опции, Вы можете использовать эту переменную, чтобы определить, где начинаются остающиеся аргументы-не-опции. Начальное значение этой переменной 1.
char * optarg (переменная)
Эта переменная будет установлена getopt, чтобы указать число аргументов опций, для тех опций кторые принимают аргументы.
int getopt (int argc, char **argv, const char *options) (функция)
Getopt функция получает следующий аргумент-опцию списка параметров, заданного argv и argc аргументами.
Аргумент-опция - строка, которая определяет символы опции, которые являются допустимыми для этой программы. Символ опции в этой строке может сопровождаться двоеточием (": ") чтобы указать, что она берет требуемый аргумент.
Если строка аргумента-опции начинается с дефиса (" - "), она обрабатывается особенно. Это разрешает аргументам-не-опциям, возвращаться, как будто они были связаны с последним символом опции.
Getopt функция возвращает символ опции для следующей опции командной строки. Когда нет больше аргументов-опций, она возвращает -1. Может все еще иметься большое количество аргументов-не-опций; Вы должны сравнить внешнюю переменную optind c параметром argc, чтобы проверить это.
Если опция имеет аргумент, getopt возвращает аргумент, сохраняя его в переменной optarg. Вы обычно не должны копировать optarg строку, так как это - указатель в первоначальный массив argv, а не в статическую область, которая могла бы быть перезаписана.
Если getopt находит символ опции в argv, который не был включен в опции, или отсутствующий аргумент некоторой опции, она возвращает "? ", устанавливает внешнюю переменную optopt как фактический символ опции. Если первый символ опции - двоеточие (":"), то getopt возвращает ":" вместо "? " Чтобы указать отсутствующий аргумент опции. Кроме того, если внешняя переменная opterr отлична от нуля (который является значением по умолчанию), getopt печатает сообщение об ошибках.
Пример Синтаксического Анализа Аргументов с getopt
Вот пример, показывающий, как getopt обычно используется:
#include
#include int main (int argc, char **argv) { int aflag = 0; int bflag = 0; char *cvalue = NULL; int index; int c; opterr = 0; while ((c = getopt (argc, argv, "abc:")) != -1) switch (c) { case 'a': aflag = 1; break; case 'b': bflag = 1; break; case 'c': cvalue = optarg; break; case '?': if (isprint (optopt)) fprintf (stderr, "Unknown option `-%c'.\n", optopt); else fprintf (stderr, "Unknown option character `\\x%x'.\n", optopt); return 1; default: abort (); } printf ("aflag = %d, bflag = %d, cvalue = %s\n", aflag, bflag, cvalue); for (index = optind; index < argc; index++) printf ("Non-option argument %s\n", argv[index]); return 0; }
Имеются некоторые примеры, показывающие, что эта программа печатает с различными комбинациями аргументов:
% testopt aflag = 0, bflag = 0, cvalue = (null) % testopt -a -b aflag = 1, bflag = 1, cvalue = (null) % testopt -ab aflag = 1, bflag = 1, cvalue = (null) % testopt -c foo aflag = 0, bflag = 0, cvalue = foo % testopt -cfoo aflag = 0, bflag = 0, cvalue = foo % testopt arg1 aflag = 0, bflag = 0, cvalue = (null) Non-option argument arg1 % testopt -a arg1 aflag = 1, bflag = 0, cvalue = (null) Non-option argument arg1 % testopt -c foo arg1 aflag = 0, bflag = 0, cvalue = foo Non-option argument arg1 % testopt -a -- -b aflag = 1, bflag = 0, cvalue = (null) Non-option argument -b % testopt -a aflag = 1, bflag = 0, cvalue = (null) Non-option argument
Синтаксический анализ Длинных Опций
Чтобы воспринимать GNU стиль длинных опций также как одиночно символьные опции, используйте getopt_long вместо getopt. Вы должны заставить каждую программу принимать длинные опции, если она использует опции, это занимает немного ресурсов, и помогает новичкам помнить, как использовать программу.
struct option (тип данных)
Эта структура описывает одиночное длинное имя опции для getopt_long. Аргумент longopts должен быть массивом этих структур, по одной для каждой длинной опции.
Завершите массив элементом, содержащим все нули.
Структура option имеет поля:
const char *name
Это поле - имя опции. Это - строка.
int has_arg
Это поле говорит, берет ли опция аргумент. Это - целое число, и имеются три законных значения: no_argument, required_argument и optional_argument.
int *flag int val
Эти поля управляют, как сообщать или действовать на опцию, когда она прочитана.
Если flag - пустой указатель, то val - значение, которое идентифицирует эту опцию. Часто эти значения выбраны, чтобы однозначно идентифицировать специфические длинные опции.
Если flag - не пустой указатель, это должен быть адрес int переменной, которая является флагом для этой опции. Значение в val - значение, которое нужно сохранить во флаге, чтобы указать, что опция была замечена.
int getopt_long (int argc, char **argv, const char *shortopts, struct option *longopts, int *indexptr) (функция)
Декодирует опции из вектора argv (чья длина argc). Аргумент shortopts описывает короткие опции, принимаемые точно так же как это делается в getopt. Аргумент longopts описывает длинные опции (см. выше).
Когда getopt_long сталкивается с короткой опцией, она делает ту же самую вещь, что и getopt: она возвращает символьный код для опции, и сохраняет аргумент этой опции (если он имеется) в optarg.
Когда getopt_long сталкивается с длинной опцией, она действует, основываясь на flag и val полях определения этой опции.
Если flag - пустой указатель, то getopt_long возвращает содержимое val, чтобы указать какую опцию она нашла. Вы должны указывать различные значения в val поле для опций с различными значениями, так что Вы можете декодировать эти значения после того, как getopt_long возвращается. Если длинная опция эквивалентна короткой опции, Вы может использовать код символа короткой опции в val.
Если flag - не пустой указатель, значит эта опция должна только установить флаг в программе. Флаг - переменная типа int, что Вы и определяете. Поместите адрес флага в поле flag. Поместите в val поле значение, которое Вы хотели бы, чтобы эта опция сохранила во флаге. В этом случае, getopt_long возвращает 0.
Для любой длинной опции, getopt_long сообщает Вам индекс в массиве longopts определения опций, сохраняя его в *indexptr. Вы можете получить имя опции через longopts [*indexptr].name . Так что Вы можете различать длинные опции или значениями в их val полях или их индексами.
Когда длинная опция имеет аргумент, getopt_long помещает значение аргумента в переменную optarg перед возвращением. Когда опция не имеет никакого аргумента, значение в optarg - пустой указатель.
Когда getopt_long не имеет больше опций для обработки, она возвращает -1, и оставляет в переменной optind индекс следующего остающегося аргумента в argv.
Пример Синтаксического анализа Длинных Опций
#include
static int verbose_flag; int main (argc, argv) int argc; char **argv; { int c; while (1) { static struct option long_options[] = { {"verbose", 0, &verbose_flag, 1}, {"brief", 0, &verbose_flag, 0}, {"add", 1, 0, 0}, {"append", 0, 0, 0}, {"delete", 1, 0, 0}, {"create", 0, 0, 0}, {"file", 1, 0, 0}, {0, 0, 0, 0} }; int option_index = 0; c = getopt_long (argc, argv, "abc:d:", long_options, &option_index); if (c == -1) break; switch (c) { case 0: if (long_options[option_index].flag != 0) break; printf ("option %s", long_options[option_index].name); if (optarg) printf (" with arg %s", optarg); printf ("\n"); break; case 'a': puts ("option -a\n"); break; case 'b': puts ("option -b\n"); break; case 'c': printf ("option -c with value `%s'\n", optarg); break; case 'd': printf ("option -d with value `%s'\n", optarg); break; case '?': /* getopt_long already printed an error message. */ break; default: abort (); } } if (verbose_flag) puts ("verbose flag is set"); /* Печатаем любые остающиеся аргументы командной строки (не опции). */ if (optind < argc) { printf ("non-option ARGV-elements: "); while (optind < argc) printf ("%s ", argv[optind++]); putchar ('\n'); } exit (0); }
22.2 Переменные среды
Когда программа выполняется, она получает информацию относительно контекста, в котором она вызывалась двумя способами. Первый механизм использует argv и argc аргументы функции main, и обсужден в Разделе 22.1 [Аргументы Программы]. Второй механизм использует переменные среды и обсужден в этом разделе.
Механизм argv обычно используется, чтобы передать аргументы командной строки, специфические для специфической вызываемой программы. Среда, с другой стороны, следит за информацией, которая разделена многими программами, и к ней менее часто обращаются.
Переменные среды, обсужденные в этом разделе - те же самые переменные среды, что Вы устанавливаете используя присванивание и команду export в оболочке. Программы, выполненные из оболочки наследуют все переменные среды из оболочки.
Стандартные переменные среды используются для уточнения инфрмации относительно исходного каталога пользователя, типа терминала, текущего стандарта, и так далее; Вы можете определять дополнительные переменные для других целей. Набор всех переменных среды, которые имеют значения, общеизвестен как среда.
Имена переменных среды чувствительны к регистру и не должны содержать символ "=".
Определенные системой переменные среды неизменны относительно верхнего регистра.
Значения переменных среды могут быть чем угодно, что может представляться как строка. Значение не должно содержать внедренный пустой символ, так как им принято завершать строку.
Доступ к Среде
К значению переменной среды можно обращаться getenv функцией. Это объявлено в заглавном файле " stdlib.h ".
char * getenv (const char *name) (функция)
Эта функция возвращает строку, которая является значением переменной среды. Вы не должны изменять эту строку. В некоторых системах не-UNIX, не использующих библиотеку GNU, она может быть перезаписана поверх последующими обращениями к getenv (но не к любой другой библиотечной функции). Если имя переменной среды не определено, значение - пустой указатель.
int putenv (const char *string) (функция)
Putenv функция добавляет или удаляет определения из среды. Если строка имеет форму " name=value ", определение будет добавлено к среде. Иначе, строка интерпретируется как имя переменной среды, и любое определение для этой переменной в среде будет удалено.
Библиотека GNU обеспечивает эту функцию для совместимости с SVID; она не может быть доступна в других системах.
Вы можете иметь дело непосредственно с основным представлением объектов среды, чтобы добавить большое количество переменных к среде (например, связываться с другой программой, которую Вы собираетесь выполнять; см. Раздел 23.5 [Выполнение Файла]).
char ** environ (переменная)
Среда представляется как массив строк. Каждая строка имеет формат " name=value ". Порядок, в котором строки появляются в среде не значителен, но то же самое имя не должно появиться больше чем один раз. Последний элемент массива - пустой указатель.
Эта переменная объявлена в заглавном файле " unistd.h ".
Если Вы хотите только получить значение переменной среды, использует getenv.
Стандартные Переменные среды
Эти переменные среды имеют стандартные значения. Это не означает, что они всегда представляются в среде; но если эти переменные присутствуют, они имеют эти значения, и Вы не должны пробовать использовать эти имена переменных среды для некоторой другой цели.
HOME
Это - строка представляет исходный каталог пользователя, или начальное значение рабочего каталога по умолчанию.
Пользователь может устанавливать HOME как любое значение. Если, Вы должны получить соответствующий исходный каталог для специфического пользователя, Вы не должен использовать HOME; взамен, найдите имя пользователя в базе данных пользователей (см. Раздел 25.12 [База данных Пользователей]).
LOGNAME
Это - имя пользователя, используемое для входа в систему. Так как значение в среде может быть произвольно, это - не надежный способ идентифицировать пользователя, который выполняет процесс; функция getlogin (см. Раздел 25.11 [Кто Вошел В Систему] ) лучше для той цели.
Для большинства целей, лучше использовать LOGNAME, потому что она позволяет пользователю определять значение.
PATH
Путь - последовательность имен каталогов, которая используется для поиска файла. Переменная PATH содержит путь, используемый для поиска программ, которые будут выполнены.
Execlp и execvp функции (см. Раздел 23.5 [Выполнение Файла]) используют эту переменную среды, как и многие оболочки и другие утилиты, которые выполнены в терминах этих функций.
Синтаксис пути - последовательность имен каталогов, отделяемых двоеточиями. Пустая строка вместо имени каталога замещает текущий каталог (см. Раздел 9.1 [Рабочий каталог]).
Типичное значение для этой переменной среды могло бы быть: :/bin:/etc:/usr/bin:/usr/new/X11:/usr/new:/usr/local/bin
Это означает что, если пользователь пробует выполнять программу, именованную foo, система будет искать файлы, именованные " foo ", " /bin/foo ", " /etc/foo ", и так далее. Первый из этих файлов, который существует - будет выполнен.
TERM
Определяет вид терминала, который получает вывод программы. Некоторые программы могут использовать эту информацию, чтобы пользоваться преимуществом специальных escape-последовательностей или режимов терминала, обеспечиваемых специфическими видами терминалов. Многие программы, которые используют termcap библиотеку (см. раздел " Поиск Описания Терминала " в Библиотечном Руководстве Termcap) использует переменную среды TERM.
TZ
Определяет часовой пояс. См. Раздел 17.2.5 [Переменная TZ], для уточнения инфрмации относительно формата этой строки и как она используется.
LANG
Определяет заданный по умолчанию стандарт, используемый для категорий атрибутов, если ни LC_ALL ни специфическая переменная среды для этого класса не установлены. См. Главу 19 [Стандарты], для получения более подробной информации.
LC_COLLATE
Определяет какой стандарт использовать для строковой сортировки.
LC_CTYPE
Определяет какой стандарт использовать для символьных наборов и символьной классификации.
LC_MONETARY
Определяет какой стандарт использовать для форматирования валютных значений.
LC_NUMERIC
Определяет какой стандарт использовать для форматирования чисел.
LC_TIME
Определяет то, какой стандарт использовать для форматирования даты/времени.
_POSIX_OPTION_ORDER
Если эта переменная среды определена, она подавляет обычное переупорядочение аргументов командной строки getopt. См. Раздел 22.1.1 [Синтаксис Аргумента].
22.3 Завершение Программы
Обычный способ завершения программы - просто возврат функции main. Значение состояния выхода, возвращенное из функции main используется, чтобы сообщить информацию обратно родительскому процессу или оболочке.
Программа может также завершаться вызывая функцию exit.
Кроме того, программы могут быть завершены сигналами; это обсуждено более подробно в Главе 21 [Обработка Сигналов]. Функция abort вызывает сигнал, который уничтожает программу.
Нормальное Окончание
Процесс завершается обычно, когда программа вызывает exit. Возвращение из main эквивалентно вызову exit, и значение, которое main возвращает, используется как аргумент exit.
void exit (int status) (функция)
Функция exit завершает процесс с состоянием status. Эта функция не возвращается.
Нормальное окончание вызывает следующие действия:
- Функции, которые были зарегистрированы с atexit или on_exit функциями, вызываются в обратном порядке их регистрации. Этот механизм позволяет вашему приложению определять собственные действия "очистки", которые нужно выполнить по окончании программы. Обычно, это используется, чтобы делать вещи подобно сохранению информации о состоянии программы в файле, или размыкании блокировок в базах общих данных.
- Все открытые потоки будут закрыты. См. Раздел 7.4 [Закрытие Потоков]. Кроме того, временные файлы, открытые с tmpfile функцией будут удалены; см. Раздел 9.10 [Временные Файлы].
- _exit вызывается, завершая программу. См. Раздел 22.3.5 [Внутренняя организация Окончания].
Состояние Выхода
Когда программа выходит, она может возвращать родительскому процессу малое количество информации относительно причины окончания, используя состояние exit. Это - значение между 0 и 255, которое выходящий процесс передает как аргумент exit.
Обычно Вы должны использовать состояние exit, чтобы сообщить очень широкую информацию относительно успеха или отказа. Вы не можете обеспечивать множество подробностей относительно причин для отказа, да и большинство родительских процессов не требуют много подробностей.
Имеются соглашения для того, что некоторые программы должны возвратить. Наиболее общее соглашение - просто 0 для успеха и 1 для отказа. Программы, которые выполняют сравнение, используют другое соглашение: они используют состояние, 1, чтобы указать несоответствие, и состояние 2, чтобы указать неспособность сравнить. Ваша программа должна следовать за существующим соглашением, если существующее соглашение имеет смысл для нее.
Общее соглашение резервирует значения 128 состояний и более для специальных целей. В частности значение 128 используется, чтобы указать отказ выполнить другую программу в подпроцессе. Это соглашение не удовлетворяет унивверсальным условиям, но неплохо следовать за ним в ваших программах.
Предупреждение: Не пробуйте использовать число ошибок как состояние exit. Это фактически не очень полезно; родительский процесс вообще не должен заботиться, сколько ошибок произошло. К тому же значение состояния усекается до восьми бит. Таким образом, если программа пробовала передать 256, родитель получит 0 шибок т.е. успех.
По той же самой причине не работает использование значения errno как состояния exit.
Примечание Переносимости: Некоторые не-posix системы используют различные соглашения для значений состояния exit. Для большей переносимости, Вы можете использовать макрокоманды EXIT_SUCCESS и EXIT_FAILURE для стандартного значения состояния успеха и отказа, соответственно. Они объявлены в файле " stdlib.h ".
int EXIT_SUCCESS (макрос)
Эта макрокоманда может использоваться с функцией exit, чтобы указать успешное завершение программы.
На системах POSIX, значение этой макрокоманды - 0. В других системах, значение может быть другим (возможно не-константа) целочисленным выражением.
int EXIT_FAILURE (макрос)
Эта макрокоманда может использоваться с функцией exit, чтобы указать неудачное завершение программы в общем смысле.
На системах POSIX, значение этой макрокоманды 1. На других системах, значение может быть другим.
Очистки на Выходе
Ваша программа может выполнить собственные функции очистки при нормальное окончании. Ненадежно вызывать функции очистки явно перед выходом. Намного лучше делать очистку невидимой для приложения, устанавливая функцию очистки используя atexit или on_exit.
int atexit (void (*function) (void)) (функция)
Atexit функция регистрирует функцию function, которую нужно вызвать при нормальном окончании программы. Функция вызывается без аргументов.
Возвращаемое значение из atexit - нуль при успехе и отличное от нуля, если функция не может быть зарегистрирована.
int on_exit (void (*function)(int status, void *arg), void *arg)
Эта функция - несколько более мощный вариант atexit. Она принимает два аргумента, функцию и произвольный указатель. При нормальном окончании программы, функция вызывается с двумя аргументами: значением состояния, переданным exit, и параметром arg.
Эта функция включена в библиотеку GNU C только для совместимости с SunOS, и может не обеспечиваться другими реализациями.
Имеется тривиальная программа, которая иллюстрирует использование exit и atexit:
#include
#include void bye (void) { puts ("Goodbye, cruel world...."); } int main (void) { atexit (bye); exit (EXIT_SUCCESS); }
Когда эта программа выполнена, она печатает сообщение и выходит.
Прерывание выполнения Программы
Вы можете прервать вашу программу, используя функцию abort. Прототип для этой функции находится в " stdlib.h ".
void abort (void) (функция)
Функция abort вызывает аварийное окончание программы. Она не выполняет функции очистки, зарегистрированные с atexit или on_exit.
Эта функция фактически завершает процесс, вызывая сигнал SIGABRT, и ваша программа может включать обработчик, чтобы прервать этот сигнал; см. Главу 21 [Обработка Сигнала].
Внутренняя организация Окончания
Функция _exit - примитив, используемый для окончания процесса exit. Она объявлена в заглавном файле " unistd.h ".
void _exit (int status) (функция)
_exit - функция для завершения процесса с состоянием status. Вызов этой функции не выполняет функции очистки, зарегистрированные с atexit или on_exit.
Когда процесс завершается по любой причине либо явным запросом окончания, либо окончанием в результате сигнала, производятся следующие действия:
- Все описатели открытого файла в процессе будут закрыты. См. Главу 8 [Ввод - вывод низкого уровня].
- 8 битов младшего разряда возвращающегося кода состояния сохранены, для передачи родительскому процессу через wait или waitpid; см. Раздел 23.6 [Завершение Процесса].
- Любым дочерним процессам завершаемого процесса будет назначен новый родительский процесс. (Это - init процесс, с ID процесса 1.)
- Сигнал SIGCHLD послан родительскому процессу.
- Если, процесс является лидером сеанса, который контролировал терминал управления, то сигнал SIGHUP будет послан каждому процессу в приоритетной работе, и терминал управления - будет отсоединен от этого сеанса. См. Главу 24 [Управление заданиями].
- Если окончание процесса останавлвает любой элемент группы этого процесса, то сигнал SIGHUP и сигнал SIGCONT будет послан каждому процессу в группе. См. Главу 24 [Управление заданиями].
23. Дочерние Процессы
Процессы - примитивные модули для резервирования ресурсов системы. Каждый процесс имеет собственное адресное пространство. Процесс выполняет программу; Вы можете иметь многократные процессы, выполняющие ту же самую программу, но каждый процесс имеет собственную копию программы внутри собственного адресного пространства и выполняет это независимо от других копий.
Процессы организованы иерархически. Каждый процесс имеет родительский процесс. Процессы, созданные данным родителем называются дочерними процессами. Дочерний наследует многие из атрибутов родительского процесса.
Эта глава описывает, как программа может создавать, завершать, и управлять дочерними процессами. Фактически, имеются три различных операции: создание нового дочернего процесса, назначение новому процессу выполнить программу, и координирование завершения дочернего процесса.
Функция системы обеспечивает простой механизм для выполнения другой программы; он делает все три шага автоматически. Если Вы нуждаетесь в большом количестве контроля, Вы можете использовать примитивные функции, чтобы делать каждый шаг индивидуально.
23.1 Выполнение Команды
Простой способ выполнять другую программу состоит в том, чтобы использовать функцию system. Эта функция делает всю работу выполнения подпрограммы, но она не дает Вам контроля над подробностями: Вы должны ждать, пока подпрограмма не завершится прежде, чем Вы сможете делать что-нибудь еще.
int system (const char *command) (функция)
Эта функция выполняет command как команду оболочки. В библиотеке GNU C, она всегда использует заданную по умолчанию оболочку sh, чтобы выполнить команду. В частности она ищет каталоги в PATH, чтобы найти программу для выполнения. Возвращаемое значение -1, если не возможно создать процесс оболочки, иначе - состояние процесса оболочки. См. Раздел 23.6 [Завершение Процесса], для подробностей относительно того, как этот код состояния может интерпретироваться.
Функция system объявлена в заглавном файле " stdlib.h ".
Примечание Переносимости: Некоторые реализации C могут не иметь понятие командного процессора, который может выполнять другие программы. Вы можете определить, существует ли командный процессор, выполняя system (NULL); если возвращаемое значение отлично от нуля, командный процессор доступен.
Popen и pclose функции (см. Раздел 10.2 [Трубопровод на Подпроцесса]) близко связаны функцией system. Они позволяют родительскому процессу связываться со стандартным вводом и выводом выполняемой команды.
23.2 Понятия Создания Процесса
Этот раздел дает краткий обзор действий и шагов по созданию процесса и выполнения им другой программы.
Каждый процесс именован ID процесса. Уникальный ID процесса дан каждому процессу при создании.
Процессы создаются системным вызовом fork (так что операция создания нового процесса иногда вызывает раздваивание процесса). Дочерний процесс, созданный fork - точный аналог первоначального родительского процесса, за исключением того, что он имеет собственный ID.
Если Вы хотите, чтобы ваша программа ждала завершения дочернего процесса, Вы должен делать это явно после операции fork, вызовом wait или waitpid (см. Раздел 23.6 [Завершение Процесса]). Эти функции дают Вам ограниченную информацию относительно того, почему завершился дочерний прцесс - например, код состояния exit.
Раздвоенный дочерний процесс продолжает выполнять ту же самую программу как родительский процесс, в точке возвращения fork. Вы можете использовать возвращаемое значение от fork, чтобы отличить, выполняется ли программа в родительском процессе или в дочернем.
Наличие нескольких процессов выполняющх ту же самую программу не очень полезно. Но дочерний может выполнять другую программу, используя одну из запускающих функций; см. Раздел 23.5 [Выполнение Файла]. Программа, которую процесс выполняет, называется образом процесса. Начало выполнения новой программы заставляет процесс забыть все относительно предыдущего образа процесса; когда программа выходит, процесс тоже выходит, вместо того, чтобы возвратиться к предыдущему образу процесса.
23.3 Идентификация Процесса
Pid_t тип данных для ID процесса. Вы можете получить ID процесса, вызывая getpid. Функция getppid возвращает ID родителя текущего процесса (это также известно как ID родительского процесса). Ваша программа должна включить заглавные файлы " unistd.h " и " sys/types.h " чтобы использовать эти функции.
pid_t (тип данных)
Pid_t тип данных - целое число со знаком, который способен представить ID процесса. В библиотеке GNU, это - int.
pid_t getpid (void) (функция)
Getpid функция возвращает ID текущего процесса.
pid_t getppid (void) (функция)
Getppid функция возвращает ID родителя текущего процесса.
23.4 Создание Процесса
Функция fork - примитив для создания процесса. Она объявлена в заглавном файле " unistd.h ".
pid_t fork (void) (функция)
Функция fork создает новый процесс.
Если операция является успешной, то и родительский и дочерний процессы видят что fork возвращается, но с различными значениями: она возвращает значение 0 в дочернем процессе и ID порожденного процесса (ребенка) в родительском процессе.
Если создание процесса потерпело неудачу, fork возвращает значение -1 в родительском процессе. Следующие errno условия ошибки определены для fork:
- EAGAIN
-
не имеется достаточных ресурсов системы чтобы создать другой процесс, или пользователь уже имеет слишком много процессов.
- ENOMEM
-
процесс требует большего количества места чем система могла обеспечить.
Специфические атрибуты дочернего процесса, которые отличаются от родительского процесса:
- Дочерний процесс имеет собственный уникальный ID.
- ID родителя дочернего процесса - ID родительского процесса.
- Дочерний процесс получает собственные копии описателей открытых файлов родительского процесса. Впоследствии изменение атрибутов описателей файла в родительском процессе не будет воздействовать на описатели файла в дочернем, и наоборот. См. Раздел 8.7 [Операции Управления].
- Прошедшее процессорное время для дочернего процесса установлено на нуль; см. Раздел 17.1 [Процессорное время].
- Дочерний не наследует набор блокировок файла родительского процессоа. См. Раздел 8.7 [Операции Управления].
- Дочерний не наследует набор таймеров родительского процесса. См. Раздел 17.3 [Установка Сигнализации].
- Набор отложенных сигналов (см. Раздел 21.1.3 [Получение Сигналов] ) для дочернего процесса, очищен. (Дочерний процесс наследует маску блокированных сигналов и действий сигналов из родительского процесса.)
pid_t vfork (void) (функция)
Vfork функция подобна fork, но более эффективна; однако, имеются ограничения, которым Вы должны следовать, чтобы использовать ее безопасно.
В то время как fork делает полную копию адресного пространства вызывающего процесса и позволяет, и родителю и дочернему выполняться независимо, vfork не делает эту копию.
Взамен, дочерний процесс, созданный с vfork совместно использует адресное пространство родителя, пока он не вызывает одну из функций exec. Тем временем, родительский процесс приостанавливает свое выполнение.
Вы должны быть очень осторожны, чтобы не позволить дочернему процессу, созданному с vfork изменять любые глобальные данные или даже локальные переменные, общедоступнные с родителем. Кроме того, дочерний процесс не может возвращаться из (или делать длинный переход) функции, которая вызвала vfork! Это спутало бы информацию управления родительского процесса. Если Вы сомневаетесь, используйте fork.
Некоторые операционные системы не выполняют vfork. Библиотека GNU C разрешает Вам использовать vfork на всех системах, но фактически выполняет fork, если vfork не доступна. Если Вы соблюдаете соответствующие предосторожности при использовании vfork, ваша программа будет работать, даже если система использует fork взамен.
23.5 Выполнение Файла
Этот раздел описывает совокупность exec функций, для выполнения файла как образа процесса. Вы можете использовать эти функции, чтобы заставить дочерний процесс выполнить новую программу после того, как он был раздвоен.
Эти функции отличаются тем, как Вы определяете аргументы, но они все делают ту же самую вещь. Они объявлены в заглавном файле " unistd.h ".
int execv (const char *filename, char *const argv[]) (функция)
Execv функция выполняет файл, именованный filename как новый образ процесса.
Аргумент argv - массив строк с нулевым символом в конце, который используется, чтобы обеспечить значение для аргумента argv функции main программы, которая будет выполнена. Последний элемент этого массива должен быть пустой указатель. Обычно, первый элемент этого массива - имя файла программы. См. Раздел 22.1 [Аргументы Программы] , для подробностей относительно того, как программы могут обращаться к этим аргументам.
Среда для нового образа процесса берется из переменной environ текущего образа процесса; см. Раздел 22.2 [Переменные среды], для уточнения инфрмации относительно сред.
int execl (const char *filename, const char *arg0, . . .) (функция)
Подобна execv, но строки argv определены индивидуально, а не как массив. Пустой указатель должен быть передан как последний такой аргумент.
int execve (const char *filename, char *const argv[], char *const env[])
Подобна execv, но разрешает Вам определять среду для новой программы явно как env аргумент. Это должен быть массив строк в том же самом формате как переменная environ; см. Раздел 22.2.1 [Доступ Среды].
int execle (const char *filename, const char *arg0, char *const env[], . . .)
Подобна execl, но разрешает Вам определять среду для новой программы явно. Аргумент среды передан после пустого указателя, который отмечает последний аргумент argv, и должен быть массивом строк в том же самом формате как переменная environ.
int execvp (const char *filename, char *const argv[]) (функция)
Execvp функция подобна execv, за исключением того, что она ищет каталоги, перечисленные в переменной среды PATH (см. Раздел 22.2.2 [Стандартная Среда]) чтобы найти полное имя файла filename, если filename не содержит наклонную черту вправо.
Эта функция полезна для выполняющихся утилит системы, потому что она ищет их в местах, которые пользователь выбрал. Оболочки используют ее, чтобы выполнить команды написанные пользователем.
int execlp (const char *filename, const char *arg0, . . .) (функция)
Эта функция - подобна execl, за исключением того, что она выполняет тот же поиск имени файла как в execvp.
Размер списка параметров и списка среды, вместе не должен быть больше чем ARG_MAX байт. См. Раздел 27.1 [Общие Ограничения]. В системе GNU, размер (который сравнивается c ARG_MAX) включает, для каждой строки, число символов в строке, плюс размер char*, плюс один, округленный вверх после умножения на размер char*. Другие системы могут иметь несколько отличные правила для подсчета.
Эти функции обычно не возвращаются, так как выполнение новой программы заставляет завершиться программу выполнения в настоящее время. Значение -1 возвращено в случае отказа. В дополнение к обычным синтаксическим ошибкам имени файла (см. Раздел 6.2.3 [Ошибки Имени файла]), следующие errno условия ошибки определены для этих функций: E2BIG объединенный размер списка параметров новой программы и списка среды больше чем ARG_MAX байт. Система GNU не имеет никакого специфического ограничения размера списка параметров, так что этот код ошибки не может получиться, но Вы можете получать ENOMEM взамен, если аргументы слишком большие для доступной памяти.
- ENOEXEC
-
заданный файл не может быть выполнен, потому что он не находится в правильном формате.
- ENOMEM
-
Выполнение заданного файла требует большего количества памяти чем было доступно.
Если выполнение нового файла преуспевает, это модифицирует поле времени доступа файла, как будто файл был прочитан. См. Раздел 9.8.9 [Времена Файла].
Выполнение нового образа процесса полностью не изменяет содержимое памяти, копируются только аргументы и строки среды. Но много других атрибутов процесса неизменяемы:
- ID процесса и ID родительского процесса. См. Раздел 23.2 [Понятия Создания Процесса].
- Групповая принадлежность сеанса и процесса. См. Раздел 24.1 [Понятия Управления заданиями].
- Реальный пользовательский ID, ID группы, и дополнительный ID группы. См. Раздел 25.2 [Владелец Процесса].
- Отложенные таймеры. См. Раздел 17.3 [Установка Сигнализации].
- Текущий рабочий каталог и корневой каталог. См. Раздел 9.1 [Рабочий каталог].
- Маска режима создаваемого файла. См. Раздел 9.8.7 [Установка Прав].
- Маска сигналов Процесса; см. Раздел 21.7.3 [Маска сигналов Процесса].
- Отложенные сигналы; см. Раздел 21.7 [Блокированные Сигналы].
- Прошедшее процессорное время, связанное с процессом; см. Раздел 17.1 [Процессорное время]. Если set-user-ID и set-group-ID биты режима файла образа процесса установлены, это воздействует на эффективный ID пользователя и эффективный ID группы (соответственно) процесса. Эти понятия обсуждены подробно в Разделе 25.2 [Владелец Процесса].
Сигналы которые игнорируются в существующем образе процесса, также будут установлены, чтобы игнорироваться в новом образе процесса. Все другие сигналы будт установлены по умолчанию в новом образе процесса. См. Главу 21 [Обработка Сигнала].
Описатели Файла, открытые в существующем образе процесса остаются открытыми в новом образе процесса, если они не имеют FD_CLOEXEC флага. Файлы, которые остаются открытыми, наследуют все атрибуты описания открытого файла из существующего образа процесса, включая блокировки файла. Описатели Файла обсуждены в Главе 8 [Ввод - вывод низкого уровня].
Новый образ процесса не имеет никаких потоков за исключением тех, что он создает заново.
Каждый из потоков в предыдущем образе процесса имеет описатель внутри него, и эти описатели остаются после exec (если они не имеют FD_CLOEXEC). Новый образ процесса может повторно соединять их с новыми потоками, используя fdopen (см. Раздел 8.4 [Описатели и Потоки]).
23.6 Завершение Процесса
Функции, описанные в этом разделе используются, чтобы ждать завершения или останова дочернего процесса и определять его состояние. Эти функции объявлены в заглавном файле " sys/wait.h ".
pid_t waitpid (pid_t pid, int *status_ptr, int options) (функция)
Waitpid функция используется, чтобы запросить информацию состояния дочернего процесса, чей ID является pid. Обычно, вызывающий процесс приостановлен, пока дочерний процесс не делает информацию состояния доступной, завершаясь.
Другие значения для pid аргумента имеют специальные интерпретации. Значение -1 или WAIT_ANY информация состояния для любого дочернего процесса; значение 0 или WAIT_MYPGRP запрашивает информацию для любого дочернего процесса в той же самой группе процесса как вызывающий процесс; и любое другое отрицательное значение - pgid запрашивает информацию для любого дочернего процесса, чей ID группы - pgid.
Если информация состояния дочернего процесса доступна немедленно, эта функция возвращается немедленно без ожидания. Если доступна информация состояния больше чем одного готового продолжиться дочернего процесса, один из них будет выбран беспорядочно, и его состояние возвращено немедленно.
Чтобы получить состояние других готовых продолжиться дочерних процессов, Вы должны вызвать waitpid снова.
Аргумент options - битовая маска. Значение должно быть поразрядным ИЛИ (то есть `|') нуля или большого количества WNOHANG и WUNTRACED флагов. Вы можете использовать WNOHANG флаг, чтобы указать, что родительский процесс не должен ждать; и WUNTRACED флаг, чтобы запросить информацию состояния остановленных процессов также как процессов, которые завершились.
Информация состояния дочернего процесса сохранена в объекте, на который указывает status_ptr, если status_ptr не пустой указатель.
Возвращаемое значение - обычно ID дочернего процесса, о чьем состояние сообщено. Если WNOHANG опция была определена и никакой дочерний процесс, не ждет, чтобы быть отмеченным, то значение - нуль. Значение -1 возвращено в случае ошибки. Следующие errno ошибки определены для этой функции:
- EINTR
-
Функция была прервана получением сигнала. См. Раздел 21.5 [Прерванные Примитивы].
- ECHILD
-
Не имеется никаких дочерних процессов, или заданный pid не дочерний для вызывающего процесса.
- EINVAL
-
Недопустимое значение аргумента options.
Эти символические константы определены как значения для pid аргумента waitpid функции.
WAIT_ANY
Эта макрокоманда (чье значение -1) определяет, что waitpid должен возвратить информацию состояния относительно любого дочернего процесса.
WAIT_MYPGRP
Эта константа (со значением 0) определяет, что waitpid должен возвратить информацию состояния относительно любого дочернего процесса в той же самой группе процесса что и вызывающий процесс.
Эти символические константы определены как флаги для аргумента options функции waitpid.
Вы можете сделать OR флагов вместе, чтобы получить значение, и использовать его как аргумент.
WNOHANG
Этот флаг определяет, что waitpid должна возвратиться немедленно вместо ожидания, если не имеется никакого дочернего процесса, готового быть отмеченным.
WUNTRACED
Этот флаг определяет, что waitpid должна сообщить состояние любых дочерних процессов, которые были остановлены также как тех, которые завершились.
pid_t wait (int *status_ptr) (функция)
Это - упрощенная версия waitpid; используется, чтобы ждать пока не завершится любой дочерний процесс. Обращение:
wait (&status)
эквивалентно:
waitpid (-1, &status, 0)
Имеется пример того, как использовать waitpid, чтобы получить состояние всех дочерних процессов, которые завершились, без какого либо ожидания. Эта функция разработана, чтобы быть обработчиком для сигнала SIGCHLD, который указывает, что по крайней мере один дочерний процесс завершился.
void sigchld_handler (int signum) { int pid; int status; while (1) { pid = waitpid (WAIT_ANY, &status, WNOHANG); if (pid < 0) { perror ("waitpid"); break; } if (pid == 0) break; notice_termination (pid, status); } }
23.7 Состояние Завершения Процесса
Если значение состояния выхода (см. Раздел 22.3 [Завершение Программы]) дочернего процесса - нуль, то значение состояния, сообщенное waitpid или wait - также нуль. Вы можете проверять другие виды информации, закодированные в возвращенном значении состояния, используя следующие макрокоманды. Эти макрокоманды определены в заглавном файле " sys/wait.h ".
int WIFEXITED (int status)
Эта макрокоманда возвращает значение отличное от нуля если дочерний процесс завершон exit или _exit.
int WEXITSTATUS (int status)
Если WIFEXITED - истина, эта макрокоманда возвращает 8 битов младшего разряда значения состояния выхода из дочернего процесса. См. Раздел 22.3.2 [Состояние Выхода].
int WIFSIGNALED (int status)
Эта макрокоманда возвращает значение отличное от нуля, если дочерний процесс завершен потому что он получил сигнал который не был обработан. См. Главу 21 [Обработка Сигнала].
int WTERMSIG (int status)
Если WIFSIGNALED - истина, эта макрокоманда возвращает номер сигнала, который завершил дочерний процесс.
int WCOREDUMP (int status)
Эта макрокоманда возвращает значение отличное от нуля, если дочерний процесс завершен и произведен core-файл.
int WIFSTOPPED (int status)
Эта макрокоманда возвращает значение отличное от нуля, если дочерний процесс остановлен.
int WSTOPSIG (int status)
Если WIFSTOPPED - истина, эта макрокоманда возвращает номер сигнала, который заставил дочерний процесс остановиться.
23.8 BSD Функции Ожидания Процесса
Библиотека GNU также обеспечивает эти средства для совместимости с UNIX BSD. BSD использует тип данных union, чтобы представить значения состояния, а не int. Два представления фактически взаимозаменяемы; они описывают те же самые битовые шаблоны. Библиотека GNU C определяет макрокоманды типа WEXITSTATUS так, чтобы они работали на любом виде объекта, и функция wait определена, чтобы принять любой тип указателя как аргумент status_ptr.
Эти функции объявлены в " sys/wait.h ".
union wait (тип данных)
Этот тип данных представляет значения состояния окончания программы. Он имеет следующие элементы:
int w_termsig
Значение этого элемента - то же что результат WTERMSIG макрокоманды.
int w_coredump
Значение этого элемента - результат WCOREDUMP макрокоманды.
int w_retcode
Значение этого элемента - результат WEXITSTATUS макрокоманды.
int w_stopsig
Значение этого элемента - результат WSTOPSIG макрокоманды.
Вместо того, чтобы обращаться к этим элементам непосредственно, Вы должны использовать эквивалентные макрокоманды.
pid_t wait3 (union wait *status_ptr, int options, struct rusage *usage)
Если usage - пустой указатель, wait3 эквивалентна waitpid (-1, status_ptr, options).
Если usage - не пустой символ, wait3 сохраняет тип использования для дочернего процесса в *rusage (но только, если дочерний завершился, а не остановился). См. Раздел 17.5 [Использование Ресурсов].
pid_t wait4 (pid_t pid, union wait *status_ptr, int options, struct rusage *usage)
Если usage - пустой указатель, wait4 эквивалентна waitpid (pid, status_ptr, options).
Если usage - не пустой символ, wait4 сохраняет тип использования для дочернего процесса в *rusage (но только, если дочерний завершился, а не остановился). См. Раздел 17.5 [Использование Ресурсов].
23.9 Пример Создания Процесса
Вот пример программы, показывающий, как Вы могли бы написать функцию, подобную встроенной системе. Она выполняет аргумент command, используя " sh -c command ".
#include
#include #include #include #include #define SHELL "/bin/sh" int my_system (const char *command) { int status; pid_t pid; pid = fork (); if (pid == 0) { execl (SHELL, SHELL, "-c", command, NULL); _exit (EXIT_FAILURE); } else if (pid < 0) status = -1; else if (waitpid (pid, &status, 0) != pid) status = -1; return status; }
Имеется две вещей, на которые Вы должны обратить внимание в этом примере.
Не забудьте, что первый аргумент argv, представляет имя выполняемой программы. Именно поэтому, в обращении к execl, SHELL обеспечена один раз, чтобы назвать выполняемую программу, и второй раз, чтобы обеспечить значение для argv [0].
Вызов execl в дочернем процессе не возвращается, если он успешен. Если он терпит неудачу, Вы должен делать кое-что, чтобы заставить дочерний процесс завершиться. Правильное поведение для дочернего процесса - сообщить отказ родительскому процессу.
Вызовите _exit, чтобы выполнить это. Причина для использования _exit вместо exit состоит в том, чтобы избежать flush полностью буферизированных потоков типа stdout. Буфера этих потоков возможно содержат данные, которые были скопированы из родительского процесса функцией fork, эти данные будут выводиться в конечном счете родительским процессом. Вызов exit в дочернем вывел бы данные дважды. См. Раздел 22.3.5 [Внутренняя организация Окончания].
24. Управление заданиями
Управление заданиями относится к протоколу для разрешения пользователю двигаться между многими группами процессов (или работ) внутри одиночного сеанса входа в систему. Средства управления заданиями установлены так, чтобы соответствующее поведение для большинства программ устанавливалось автоматически и они не должны делать что-нибудь специальное относительно управления заданиями. Так что Вы можете возможно игнорировать материал этой главы, если Вы не пишите программу входа в систему или оболочку.
Вы должны быть знакомы с понятиями создания процесса (см. Раздел 23.2 [Понятия Создания Процесса]) и обработки сигналов (см. Главу 21 [Обработка Сигналов]) чтобы понять материал этой главы.
24.1 Понятия Управления заданиями
Фундаментальная цель интерактивной оболочки читать команды из терминала пользователя и создавать процессы, чтобы выполнить программы, заданные этими командами. Это можно делать использованием fork (см. Раздел 23.4 [Создание Процесса]) и exec (см. Раздел 23.5 [Выполнение Файла]) функций.
Одиночная команда может выполнять только один процесс, но часто одна команда использует отдельные процессы.
Если Вы используете оператор `|' в команде оболочки, Вы явно, запрашиваете несколько программ в их собственных процессах. Но даже если Вы выполняете только одну программу, она может использовать многократные процессы внутренне. Например, одиночная команда трансляции типа " cc -c foo .c " обычно использует четыре процесса. Если Вы выполняете make, ее работа - выполнить другие программы в отдельных процессах.
Процессы, принадлежащие одной команде называются группой процессов или работой. Для того, чтобы Вы могли функционировать на всех сразу. Например, печать C-c посылает сигнал SIGINT, чтобы завершить все процессы в приоритетной группе процессов.
Сеанс - большая группа процессов. Обычно все процессы одиночного входа в систему принадлежат тому же самому сеансу.
Каждый процесс принадлежит группе процессов. Когда процесс создан, он становится элементом той же самой группы процессов и сеанса как и родительский процесс. Вы можете помещать его в другую группу процессов, используя setpgid функцию, если группа процессов принадлежит тому же самому сеансу.
Единственый способ поместить процесс в другой сеанс состоит в том, чтобы сделать его начальным процессом нового сеанса или лидером сеанса, используя setsid функцию. Она также помещает лидера сеанса в новую группу процессов, и Вы не можете перемещать его вне этой группы процессов снова.
Обычно, новые сеансы создаются программой входа в систему, и лидер сеанса - процесс, выполняющий оболочку входа в систему пользователя.
Оболочка, которая поддерживает управление заданиями, должна знать, которая работа может использовать терминал в любое время. Иначе имелось бы сразу много работ, пробующих читать из терминала, и беспорядок относительно того, который процесс должен получить ввод, печатаемый пользователем. Чтобы предотвращать это, оболочка должна сотрудничать с драйвером терминала, используя протокол, описанный в этой главе.
Оболочка может давать безграничный доступ к терминалу управления только одной группы процессов одновременно. Она называется приоритетной работой на этом терминале. Другие группы процессов, управляемые оболочкой, которые выполняются без такого доступа к терминалу, называются фоновыми работами.
Если фоновая работа нуждается в чтении из или записи на терминал управления, она остановлена драйвером терминала. Пользователь может останавливать приоритетную работу, печатая символ SUSP (см. Раздел 12.4.9 [Специальные Символы]) и программа может останавливать любую работу, посылая ей сигнал SIGSTOP. Ответственность оболочки - обратить внимание на останов работ, сообщать пользователю относительно них, и обеспечивать механизмы для разрешения пользователю в интерактивном режиме продолжить остановленные работы и переключать работы между проритетной и фоновыми.
См. Раздел 24.4 [Доступ к Терминалу], для получения более подробной информации о терминале управления,
24.2 Управление Заданиями Необязательно
Не все операционные системы поддерживают управление заданиями. Система GNU поддерживает управление заданиями, но если Вы используете библиотеку GNU на некоторой другой системе, эта система может не поддерживать управление заданиями непосредственно.
Вы можете использовать _POSIX_JOB_CONTROL макрокоманду, чтобы проверить в во время компиляции, поддерживает ли система управление заданиями. См. Раздел 27.2 [Опции Системы].
Если управление заданиями не обеспечивается, то может быть только одна группа процессов на сеанс, который ведет себя, как будто он всегда приоритетный. Функции для создания дополнительных групп процессов просто терпят неудачу с кодом ошибки ENOSYS.
макрокоманды, именующие различные сигналы управления заданиями (см. Раздел 21.2.5 [Сигналы Управления заданиями]) определены, даже если управление заданиями не обеспечивается.
24.3 Управление Терминалом Процесса
Один из атрибутов процесса - терминал управления. Дочерние процессы, созданные с fork наследуют терминал управления из их родительского процесса. Таким образом,, все процессы в сеансе наследуют терминал управления от лидера сеанса. Лидер сеанса, который имеет контроль над терминалом, называется процессом управления этого терминала.
Вы вообще не должны волноваться относительно точного механизма, используемого, чтобы зарезервировать терминал управления сеанса, так как это выполнено для Вас системой когда Вы входите в систему.
Индивидуальный процесс отделяется от терминала управления, когда это вызывает setsid, чтобы стать лидером нового сеанса. См. Раздел 24.7.2 [Функции Группы процессов].
24.4 Доступ к Терминалу Управления
Процессы в приоритетной работе терминала управления имеют неограниченный доступ к этому терминалу; а фоновые процессы - нет. Этот раздел описывает более подробно, что случается, когда процесс в фоновой работе пробует обращаться к терминалу управления.
Когда процесс в фоновой работе пробует читать из терминала управления, группе процессов обычно послан сигнал SIGTTIN. Это обычно заставляет все процессы в той группе останавливаться (если они не обрабатывают сигнал и не останавливают себя). Однако, если процесс считывания игнорирует или блокирует этот сигнал, то происходит сбои read с EIO ошибкой.
Аналогично, когда процесс в фоновой работе пробует писать на терминал управления, заданное по умолчанию поведение - послать сигнал SIGTTOU группе процессов. Однако, поведение изменяется TOSTOP битом флагов автономных режимов (см. Раздел 12.4.7 [Автономные режимы]). Если этот бит не установлен (по умолчанию), то запись на терминал управления всегда разрешается без сигнала. Запись также разрешается, если сигнал SIGTTOU игнорируется или блокируется процессом записи.
Большинство других операций терминала, которые программа может делать, обрабатываются как чтение или как запиись. (Описание каждой операции должно говорить как.)
24.5 Свободные Группы процессов
Когда процесс управления завершается, терминал становится свободным, и на нем может быть установлен новый сеанс. (Фактически, другой пользователь мог бы войти в систему на терминале.) Это может вызывать проблему, если любые процессы из старого сеанса все еще пробуют использовать этот терминал.
Чтобы предотвратить проблемы, группы процессов, которые продолжают выполняться даже после завершения лидера сеанса, отмечены как свободные группы процессов. Процессы в свободной группе процессов не могут читать из или писать на терминал управления. Это вызовет EIO ошибку.
Когда группа процессов становится свободной, процессам послан сигнал SIGHUP. Обычно, это заставляет процессы завершиться. Однако, если программа игнорирует этот сигнал или устанавливает обработчик для него (см. Главу 21 [Обработка Сигнала] ), она может продолжать выполнять свободную группу процессов даже после того, как процесс управления завершается.
24.6 Выполнение Оболочки Управления заданиями
Этот раздел описывает то, что оболочка должна делать, чтобы выполнить управление заданиями, обеспечивая протяженную типовую программу, чтобы проиллюстрировать включаемые понятия.
- Раздел 24.6.1 [Структуры Данных], представляет пример и первичные структуры данных.
- Раздел 24.6.2 [Инициализация Оболочки], обсуждает действия, которые оболочка должна выполнить, чтобы реализовать управление заданиями.
- Раздел 24.6.3 [Запуск Работ], включает информацию относительно того, как создать работы, чтобы выполнить команды.
- Раздел 24.6.4 [Приоритетный и Фоновые], обсуждает то, что оболочка должна делать при запуске приоритетной работы в противоположность фоновой работе.
- Раздел 24.6.5 [Остановленные и Завершенные Работы], обсуждает сообщения состояния работы для оболочки.
- Раздел 24.6.6 [Продолжение Остановленных Работ], сообщает Вам, как продолжить работы, которые были остановлены.
- Раздел 24.6.7 [Отсутствующие Части], обсуждает другие части оболочки.
Структуры Данных для Оболочки
Все примеры программы, включенные в эту главу - часть простой программы оболочки. Этот раздел представляет структуры данных и сервисные функции, которые используются в примере.
Типовая оболочка имеет дело в основном с двумя структурами данных. Тип job содержит информацию относительно работы, которая является набором подпроцессов, связанных вместе трубопроводами. Тип process содержит информацию относительно одиночного подпроцесса. Имеются релевантные объявления структуры данных:
typedef struct process { struct process *next; char **argv; pid_t pid; char completed; char stopped; int status; } process; typedef struct job { struct job *next; char *command; process *first_process; pid_t pgid; char notified; struct termios tmodes; int stdin, stdout, stderr; } job; job *first_job = NULL;
Имеются некоторые сервисные функции, которые используются для оперирования объектами job.
job * find_job (pid_t pgid) { job *j; for (j = first_job; j; j = j->next) if (j->pgid == pgid) return j; return NULL; } int job_is_stopped (job *j) { process *p; for (p = j->first_process; p; p = p->next) if (!p->completed && !p->stopped) return 0; return 1; } int job_is_completed (job *j) { process *p; for (p = j->first_process; p; p = p->next) if (!p->completed) return 0; return 1; }
Инициализация Оболочки
Когда программа оболочки, которая обычно выполняет управление заданиями, начата, она должна быть внимательна в случае, если она вызвалось из другой оболочки, которая уже делает собственное управление заданиями.
Подоболочка, которая выполняется в интерактивном режиме, должна убедиться, что она была помещена на передний план родительской оболочкой прежде, чем она может давать возможность управлению заданиями непосредственно. Она делает это, получая начальную ID группы процессов с getpgrp функцией, и сравнивая его с ID группы процессов текущей приоритетной работы, связанной с терминалом управления (который может быть восстановлен, использованием функции tcgetpgrp).
Если подоболочка не выполняется как приоритетная работа, она должна остановить себя, посылая сигнал SIGTTIN собственной группе процессов. Она не может произвольно помещаться на передний план; она должна ждать пока пользователь сообщит родительской оболочке сделала это. Если подоболочка продолжена снова, она должна повторить проверку и останов непосредственно снова, если она все еще не на переднем плане.
Если только подоболочка была помещена на передний план родительской оболочкой, она может давать возможность собственному управлению заданиями. Она делает это, вызывая setpgid, чтобы поместить себя в собственную группу процессов, и вызывая tcsetpgrp, чтобы поместить эту группу процессов на передний план.
Когда оболочка дает возможность управлению заданиями, она должна установить себя, чтобы игнорировать все сигналы останова управления заданиями так, чтобы она случайно не остановила себя. Вы можете сделать это, устанавливая действие для всех сигналов останова как SIG_IGN.
Подоболочка, которая выполняется не-в интерактивном режиме, не может и не должна поддерживать управление заданиями. Она должна оставить все процессы, которые она создает в той же самой группе процессов как оболочка непосредственно; это позволяет родительской оболочке обрабатывать не-интерактивную оболочку и дочерние процессы как одиночную работу. Это просто сделать, только не используйте любой из примитивов управления заданиями, но Вы не должны забыть заставить оболочку делать это.
Вот код инициализации для типовой оболочки, который показывает, как сделать все это.
#include
#include #include pid_t shell_pgid; struct termios shell_tmodes; int shell_terminal; int shell_is_interactive; void init_shell () { shell_terminal = STDIN_FILENO; shell_is_interactive=isatty(shell_terminal); if (shell_is_interactive) { while (tcgetpgrp (shell_terminal) != (shell_pgid = getpgrp ())) kill (- shell_pgid, SIGTTIN); signal (SIGINT, SIG_IGN); signal (SIGQUIT, SIG_IGN); signal (SIGTSTP, SIG_IGN); signal (SIGTTIN, SIG_IGN); signal (SIGTTOU, SIG_IGN); signal (SIGCHLD, SIG_IGN); shell_pgid = getpid (); if (setpgid(shell_pgid,shell_pgid) < 0) { perror ("Couldn't put the shell in its own process group"); exit (1); } tcsetpgrp (shell_terminal, shell_pgid); tcgetattr(shell_terminal,&shell_tmodes); } }
Запуск Работ
Если только оболочка приняла ответственность за выполнение управления заданиями на терминале управления, она может начинать работы в ответ на команды, печатаемые пользователем.
Чтобы создавать процессы в группе процессов, Вы используете те же самые fork и exec, описанные в Разделе 23.2 [Понятия Создания Процесса]. Так как имеются многократные дочерние включаемые процессы, Вы должны быть внимательны, чтобы делать дела в правильном порядке.
Вы имеете два выбора для того, как структурировать дерево родитель - дочерних связей среди процессов. Вы можете либо делать все процессы в группе процессов дочерними процесса оболочки, либо Вы можете делать один процесс в группе предком всех других процессов в той группе. Типовая программа оболочки, обеспеченная в этой главе использует первый подход, потому что это кажется несколько более простым.
При раздвоении процесса, он должен помещаться в новую группу процессов, вызовом setpgid; см. Раздел 24.7.2 [Функции Группы процессов]. Первый процесс в новой группе становится лидером группы процессов, и его ID, становится ID группы процессов.
Оболочка должна также вызвать setpgid, чтобы поместить каждый из дочерних процессов в новую группу процессов.
Имеется потенциальная проблема синхронизации: каждый дочерний процесс должен быть помещен в группу процессов прежде, чем он начинает выполнять новую программу, и оболочка зависит от наличия всех дочерних процессов в группе прежде, чем она продолжает выполняться. Если и дочерние процессы и оболочка вызывает setpgid, это гарантирует, что все будет правильно независимо от того, который процесс принимается за это раньше.
Если работа начинается как приоритетная работа, новая группа процессов также, должна быть помещена на передний план на терминале управления, используя tcsetpgrp. Снова, это должно быть выполнено оболочкой также как и каждым из дочерних процессов, чтобы избежать условий состязания.
Следующая вещь, которую каждый дочерний процесс должен делать, - сбросить действия сигналов.
В течение инициализации, процесс оболочки устанавливает себя, чтобы игнорировать сигналы управления заданиями; см. Раздел 24.6.2 [Инициализация Оболочки]. В результате, любые дочерние процессы, которые он создает, игнорируют эти сигналы наследованием. Это нежелательно, так что каждый дочерний процесс должен явно установить действия для этих сигналов обратно к SIG_DFL после того, как он раздвоено.
Так как оболочки следуют за этим соглашением, приложения могут принимать, что они наследуют правильную обработку этих сигналов из родительского процесса. Но каждое приложение не должно изменять обработку сигналов останова. Приложения, которые отключают нормальную интерпретацию символа SUSP, должны обеспечить некоторый другой механизм для пользователя, чтобы остановить работу. Когда пользователь вызывает этот механизм, программа должна послать сигнал SIGTSTP группе процессов, а не только на процесс непосредственно. См. Раздел 21.6.2 [Передача сигналов Другому Процессу].
В заключение, каждый дочерний процесс должен вызвать exec нормальным способом. Это - также точка, в которой должна быть обработана переадресация стандартного ввода и каналов вывода. См. Раздел 8.8 [Дублирование Описателей], для объяснения того, как делать это.
Вот функция из типовой программы оболочки, которая ответственна за запуск программы. Функция выполняется каждым дочерним процессом немедленно после того, как он был раздвоен оболочкой, и никогда не возвращается.
void launch_process (process *p, pid_t pgid, int infile, int outfile, int errfile, int foreground) { pid_t pid; if (shell_is_interactive) { pid = getpid (); if (pgid == 0) pgid = pid; setpgid (pid, pgid); if (foreground) tcsetpgrp (shell_terminal, pgid); signal (SIGINT, SIG_DFL); signal (SIGQUIT, SIG_DFL); signal (SIGTSTP, SIG_DFL); signal (SIGTTIN, SIG_DFL); signal (SIGTTOU, SIG_DFL); signal (SIGCHLD, SIG_DFL); } if (infile != STDIN_FILENO) { dup2 (infile, STDIN_FILENO); close (infile); } if (outfile != STDOUT_FILENO) { dup2 (outfile, STDOUT_FILENO); close (outfile); } if (errfile != STDERR_FILENO) { dup2 (errfile, STDERR_FILENO); close (errfile); } execvp (p->argv[0], p->argv); perror ("execvp"); exit (1); }
Если оболочка не выполняется в интерактивном режиме, эта функция, не делает ничего с группами процессов или сигналами. Не забудьте, что оболочка, не выполняющая управление заданиями должна хранить все подпроцессы в той же самой группе процессов что и оболочка непосредственно.
Вот функция, что фактически начинает полную работу. После создания дочерних процессов, эта функция вызывает некоторые другие функции, чтобы поместить недавно созданную работу на передний план (или как фон); они обсуждены в Разделе 24.6.4 [Приоритетный и Фоновые].
void launch_job (job *j, int foreground) { process *p; pid_t pid; int mypipe[2], infile, outfile; infile = j->stdin; for (p = j->first_process; p; p = p->next) { if (p->next) { if (pipe (mypipe) < 0) { perror ("pipe"); exit (1); } outfile = mypipe[1]; } else outfile = j->stdout; pid = fork (); if (pid == 0) launch_process(p, j->pgid,infile, outfile, j->stderr, foreground); else if (pid < 0) { perror ("fork"); exit (1); } else { p->pid = pid; if (shell_is_interactive) { if (!j->pgid) j->pgid = pid; setpgid (pid, j->pgid); } } if (infile != j->stdin) close (infile); if (outfile != j->stdout) close (outfile); infile = mypipe[0]; } format_job_info (j, "launched"); if (!shell_is_interactive) wait_for_job (j); else if (foreground) put_job_in_foreground (j, 0); else put_job_in_background (j, 0); }
Приоритетный и Фоновые
Теперь давайте рассматривать, какие же действия должны предприниматься оболочкой, когда она начинает приоритетную работу (на переднем плане), и как это отличается от того, что должно быть выполнено, когда начинается фоновая работа.
Когда начинается приоритетная работа, оболочка должна сначала дать ей доступ к терминалу управления, вызывая tcsetpgrp. Затем, оболочка должна ждать завершения или останова процессов в этой группе процессов. Это обсуждено более подробно в Разделе 24.6.5 [Останов и Завершенные Работы].
Когда все процессы в группе завершились или остановились, оболочка должна восстановить контроль над терминалом для собственной группы процессов, вызывая tcsetpgrp снова. Так как сигналы останова вызванны вводом - выводом из фонового процесса или символом SUSP, печатаемым пользователем посланы группе процессов, обычно все процессы работы останавливаются вместе.
Приоритетная работа может оставить терминал в странном состоянии, так что оболочка должна восстановить собственные сохраненные режимы терминала перед продолжением. В случае, если работа просто остановлена, оболочка должна сначала сохранить текущие режимы терминала так, чтобы она могла восстанавливать их позже, если работа будет продолжена. Функции для обработки режимов терминала - tcgetattr и tcsetattr; они описаны в Разделе 12.4 [Режимы Терминала].
Вот функция оболочки для выполнения всего этого.
void put_job_in_foreground (job *j, int cont) { tcsetpgrp (shell_terminal, j->pgid); if (cont) { tcsetattr (shell_terminal, TCSADRAIN, &j->tmodes); if (kill (- j->pgid, SIGCONT) < 0) perror ("kill (SIGCONT)"); } wait_for_job (j); tcsetpgrp (shell_terminal, shell_pgid); tcgetattr (shell_terminal, &j->tmodes); tcsetattr (shell_terminal, TCSADRAIN, &shell_tmodes); }
Если группа процессов начата как фоновая работа, оболочка должна остаться на переднем плане непосредственно и продолжить читать команды с терминала.
Вот функция, которая должна быть выполнена, чтобы поместить работу в фон:
void put_job_in_background (job *j, int cont) { if (cont) if (kill (-j->pgid, SIGCONT) < 0) perror ("kill (SIGCONT)"); }
Останов и Завершенные Работы
Когда начат приоритетный процесс, оболочка должна блокироваться, пока все процессы в этой работе не завершились или не остановились. Она может делать это, вызывая waitpid функцию; см. Раздел 23.6 [Завершение Процесса]. Используйте WUNTRACED опцию, чтобы состояние было сообщено и для процессов, что останавливаются, и для процессов, которые завершаются.
Оболочка должна также проверить состояния фоновых работ так, чтобы она могла сообщать о завершении или останове работы пользователю; это может быть выполнено вызовом waitpid с WNOHANG опцией. Хорошее место, чтобы поместить такую проверку для завершенных и остановленных работ - перед запросом новой команды.
Оболочка может также получать асинхронное уведомление, что имелась информация состояния дочернего процесса, устанавливая обработчик для сигналов SIGCHLD. См. Главу 21 [Обработка Сигнала].
В типовой программе оболочки, сигнал SIGCHLD обычно игнорируется. Чтобы избежать проблемы повторной входимости включая обработку глобальных данных структурировали оболочку. Но в определенных местах, когда оболочка не использует эти структуры данных, например, когда она ждет ввод на терминале, имеет смысл давать возможность обработчику для SIGCHLD. Та же самая функция, которая используется, чтобы делать синхронные проверки состояния может также вызываться изнутри этого обработчика.
Имеются части типовой программы оболочки, которые имеют дело с проверкой состояния работ и сообщением информации пользователю.
int mark_process_status (pid_t pid, int status) { job *j; process *p; if (pid > 0) { for (j = first_job; j; j = j->next) for (p =j->first_process; p ;p=p->next) if (p->pid == pid) { p->status = status; if (WIFSTOPPED (status)) p->stopped = 1; else { p->completed = 1; if (WIFSIGNALED (status)) fprintf (stderr, "%d: Terminated by signal %d.\n", (int) pid, WTERMSIG (p->status)); } return 0; } fprintf (stderr, "No child process %d.\n", pid); return -1; } else if (pid==0 || errno==ECHILD) return -1; else { perror ("waitpid"); return -1; } } void update_status (void) { int status; pid_t pid; do pid = waitpid (WAIT_ANY, &status, WUNTRACED|WNOHANG); while (!mark_process_status (pid, status)); } void wait_for_job (job *j) { int status; pid_t pid; do pid = waitpid (WAIT_ANY, &status, WUNTRACED); while (!mark_process_status (pid, status) && !job_is_stopped (j) && !job_is_completed (j)); } void format_job_info (job *j, const char *status) { fprintf (stderr, "%ld (%s): %s\n", (long)j->pgid, status, j->command); } void do_job_notification (void) { job *j, *jlast, *jnext; process *p; update_status (); jlast = NULL; for (j = first_job; j; j = jnext) { jnext = j->next; if (job_is_completed (j)) { format_job_info (j, "completed"); if (jlast) jlast->next = jnext; else first_job = jnext; free_job (j); } else if (job_is_stopped (j) && !j->notified) { format_job_info (j, "stopped"); j->notified = 1; jlast = j; } else jlast = j; } }
Продолжение Остановленных Работ
Оболочка может продолжать остановленную работу, посылая сигнал SIGCONT группе процессов. Если работа продолжается на переднем плане, оболочка должна сначала вызвать tcsetpgrp, чтобы дать работе доступ к терминалу и восстановить сохраненные установки терминала. После продолжения работы на переднем плане, оболочка должна ждать останова или завершения работы, как будто работа только что была начата в переднем плане.
Типовая программа оболочки обрабатывает, и недавно созданные и непрерывные работы той же самой парой функций, put_job_in_foreground и put_job_in_background. Определения этих функций были даны в Разделе 24.6.4 [Приоритетный и Фоновые]. При продолжении остановленной работы, значение отлично от нуля передано как cont аргумент, чтобы гарантировать, что сигнал SIGCONT послан и режимы терминала установлены соответствующе.
Осталась только функция для модификации внутреннего состояния оболочки относительно продолжаемой работы:
void mark_job_as_running (job *j) { Process *p; for (p = j->first_process; p; p = p->next) p->stopped = 0; j->notified = 0; } void continue_job (job *j, int foreground) { mark_job_as_running (j); if (foreground) put_job_in_foreground (j, 1); else put_job_in_background (j, 1); }
Отсутствующие Части
Извлечения кода для типовой оболочки, включенные в эту главу - только часть всей программы оболочки. В частности ничто вообще не упоминалось относительно того, как размещена и инициализируется работа и структуры данных программы.
Более реальные оболочки обеспечивают сложный интерфейс пользователя, который имеет поддержку для языка команд; переменные; сокращения, замены, и сопоставление с образцом для имен файлов; и т.п.. Все это слишком сложно, чтобы объяснить здесь! Взамен, мы сконцентрировались на показе создания core-файла процесса и функций управления заданиями, которые могут вызываться из такой оболочки.
Вот таблица, подводящая итог основных точек входа, которые мы обеспечили:
void init_shell (void)
Инициализирует внутреннее состояние оболочки. См. Раздел 24.6.2 [Инициализация Оболочки].
void launch_job (job *j, int foreground)
Начинает работу j или как приоритетную или фоновую работа. См. Раздел 24.6.3 [Запуск Работ].
void do_job_notification (void)
Проверяет и сообщает о любых работах, которые завершились или остановились. Может вызываться синхронно или внутри обработчика для сигналов SIGCHLD. См. Раздел 24.6.5 [Останов и Завершение Работы].
void continue_job (job *j, int foreground)
Продолжает работу . См. Раздел 24.6.6 [Продолжение Остановленных Работ].
Конечно, реальная оболочка также должна бы обеспечивать другие функции для управления работами. Например, было бы полезно иметь команды, чтобы перечислить все текущие задания или послать сигнал (типа SIGKILL) к работе.
24.7 Функции для Управления заданиями
Этот раздел содержит детализированные описания функций в отношении управления заданиями.
Идентификация Терминала Управления
Вы можете использовать ctermid функцию, чтобы получить имя файла, которое Вы можете использовать, чтобы открыть терминал управления. В библиотеке GNU, она возвращает ту же самую строку все время: "/dev/tty". Это - специальное "волшебное" имя файла, которое относится к терминалу управления текущего процесса (если он его имеет). Функция ctermid объявлена в заглавном файле " stdio.h ".
char * ctermid (char *string) (функция)
Ctermid функция возвращает строку, содержащую имя файла терминала управления для текущего процесса. Если строка - не пустой указатель, это должен быть массив, который может содержать по крайней мере L_ctermid символов; строка возвращается в этом массиве. Иначе, возвращается указатель на строку в статической области, которая может быть перезаписана поверх при последующих обращениях к этой функции.
Пустая строка возвращена, если имя файла не может быть определено по любой причине. Даже если имя файла возвращено, доступ к файлу, который она представляет, не гарантируется.
int L_ctermid (макрос)
Значение этой макрокоманды - целочисленное постоянное выражение, которое представляет размер строки, достаточно большой, чтобы содержать имя файла, возвращенное ctermid.
См. также isatty и ttyname функции, в Разделе 12.1 [Терминал Ли Это].
Функции Группы процессов
Имеются описания функций для управления группами процессов. Ваша программа должна включить заглавные файлы " sys/types.h " и " unistd.h " чтобы использовать эти функции.
pid_t setsid (void) (функция)
Setsid функция создает новый сеанс. Вызывающий процесс становится лидером сеанса, и помещен в новую группу процессов, чей ID группа процессов тот же что и ID этого процесса. Не имеется первоначально никаких других процессов в новой группе процессов, и никаких других групп процессов в новом сеансе.
Эта функция также заставит вызывающий процесс не иметь никакого терминал управления.
Setsid функция возвращает ID новой группы процессов в случае успеха. Возвращаемое значение -1 указывает ошибку. Следующие errno условия ошибки определены для этой функции:
- EPERM
-
Вызывающий процесс - уже лидер группы процессов, или имеется уже другая группа процессов, которая имеет тот же самый ID группы процессов.
Getpgrp функция имеет два определения: одно происходил от UNIX BSD, а одно от POSIX.1 стандарта. макрокоманды возможностей, которые Вы выбрали (см. Раздел 1.3.4 [Макрокоманды Возможностей]) определяют, которое определение Вы получаете. Вы получаете BSD версию, если Вы определяете _BSD_SOURCE; иначе, Вы получаете POSIX версию, если Вы определяете _POSIX_SOURCE или _GNU_SOURCE. Программы, которые пишутся для старых BSD систем не будут включать " unistd.h ", который определяет getpgrp для _BSD_SOURCE. Вы должны линковать такие программы с -lbsd-compat опцией, чтобы получить определение BSD.
pid_t getpgrp (void) (POSIX.1 функция)
POSIX. 1 определение getpgrp возвращает ID группы процессов вызывающего процесса.
pid_t getpgrp (pid_t pid) (BSD функция)
Определение BSD getpgrp возвращает ID группы процессов процесса pid. Вы можете обеспечивать значение 0 для pid аргумента, чтобы получить информацию относительно вызывающего процесса.
int setpgid (pid_t pid, pid_t pgid) (функция)
Setpgid функция помещает процесс pid в группу процессов pgid. Как частный случай, или pid или pgid может быть нуль, чтобы указать ID вызывающего процесса.
Эта функция терпит неудачу на системе, которая не поддерживает управление заданиями. См. Раздел 24.2 [Управление Заданиями Необязательно !], для подробной информации.
Если операция является успешной, setpgid, возвращает нуль. Иначе она возвращает -1. Следующие errno условия ошибки определены для этой функции:
- EACCES
-
Дочерний процесс, именованный pid выполнил функцию exec после раздвоения.
- EINVAL
-
Значение pgid не допустимо.
- ENOSYS
-
Система не поддерживает управление заданиями.
- EPERM
-
Процесс, обозначенный pid аргументом - лидер сеанса, или не в том же самом сеансе как вызывающий процесс, или значение pgid аргумента не соответствует ID группы процессов в том же самом сеансе как вызывающий процесс.
- ESRCH
-
Процесс, обозначенный pid аргументом - не вызывающий процесс или дочерний из вызывающего процесса.
int setpgrp (pid_t pid, pid_t pgid) (функция)
Это - имя Unix BSD для setpgid. Обе функции делают точно то же самое.
Функции для Управления Доступом к Терминалу
Это функции для чтения или установки группы приоритетного процесса терминала. Вы должны включить заглавные файлы " sys/types.h " и " unistd.h " в вашем приложении, чтобы использовать эти функции.
Хотя эти функции берут аргумент-описатель файла, чтобы определить устройство терминала, приоритетная работа связана с файлом терминала непосредственно, а не с описателем открытого файла.
pid_t tcgetpgrp (int filedes) (функция)
Эта функция возвращает ID приоритетной группы процессов, связанной с терминалом, открытым на описателе filedes.
Если нее никакой приоритетной группы процессов, возвращаемое значение - число больше чем 1, которое не соответствует ни одному ID любой существующей группы процессов. Это может случаться, если все процессы в работе, которая была прежде приоритетная работа, завершились, и никакая другая работа не переместилась на передний план.
В случае ошибки возвращается значение -1. Следующие errno условия ошибки определены для этой функции:
- EBADF
-
Filedes аргумент - не допустимый описатель файла.
- ENOSYS
-
Система не поддерживает управление заданиями.
- ENOTTY
-
Файл терминала, связанный с filedes аргументом не есть Терминал управления вызывающего процесса.
int tcsetpgrp (int filedes, pid_t pgid) (функция)
Эта функция используется, чтобы установить ID приоритетной группы процессов терминала. Аргумент filedes - описатель, который определяет терминал; pgid определяет группу процессов. Вызывающий процесс должен быть элементом того же самого сеанса как pgid и должен иметь тот же самый терминал управления.
Для целей доступа к терминалу, эта функция обрабатывается как вывод. Если она вызывается из фонового процесса на терминале управления, обычно всем процессам в группе процессов, послан сигнал SIGTTOU. Исключение - если вызывающий процесс непосредственно игнорирует или блокирует сигналы SIGTTOU, когда операция выполняется, и никакой сигнал не послан.
При успехе tcsetpgrp возвращает 0. Возвращаемое значение -1 указывает ошибку. Следующие errno условия ошибки определены для этой функции:
- EBADF
-
Filedes аргумент - не допустимый описатель файла.
- EINVAL
-
Pgid аргумент не допустим.
- ENOSYS
-
Система не поддерживает управление заданиями.
- ENOTTY
-
Filedes не терминал управления вызывающего процесса.
- EPERM
-
Pgid не группа процессов в том же самом сеансе как вызывающий процесс.
25. Пользователи и Группы
Каждый пользователь, кто может войти в систему, идентифицирован уникальным числом называемым пользовательский ID.
Каждый процесс имеет эффективный пользовательский ID, который говорит, какие права доступа пользователя он имеет.
Пользователи классифицированы в группы для целей управления доступом. Каждый процесс имеет одно или большее количество значений ID группы, которые говорят, которую группу процесс может использовать для доступа к файлам.
Эффективные пользовательский ID и групповой ID процесса формируют persona(владельца) процесса. Он определяет, к которым файлам процесс может обращаться. Обычно, процесс наследует persona из родительского процесса, но при специальных обстоятельствах, процесс может изменять persona и таким образом изменять права доступа.
Каждый файл в системе также имеет пользовательский ID и ID группы. Управление доступом работает, сравнивая ID пользователя и группы файла с таковыми выполняющегося процесса.
Система хранит базу данных всех зарегистрированных пользователей, и другую базу данных всех определенных групп. Имеются библиотечные функции, которые Вы можете использовать, чтобы исследовать эти базы данных.
25.1 ID пользователя и группы
Каждый пользователь компьютерной системы идентифицирован именем пользователя (или именем входа в систему) и пользовательским ID. Обычно, каждое имя пользователя имеет уникальный пользовательский ID, но возможно для отдельных имен входа в систему, иметь тот же самый пользовательский ID. Пользовательские имена и соответствующий пользовательский IDS сохранены в базе данных, к которой Вы можете обращаться как описано в Разделе 25.12 [База Данных Пользователей].
Пользователи классифицированы на группы. Каждое имя пользователя также принадлежит одной или большему количеству групп, и имеет одну заданную по умолчанию группу. Пользователи - элементы той же самой группы, могут совместно использовать ресурсы (типы файлов) которые не доступны для пользователей - не элементов этой группы. Каждая группа имеет имя группы и ID группы. См. Раздел 25.13 [База Данных Групп], для того, как найти информацию относительно ID группы или имени группы.
25.2 Persona Процесса
В любое время, каждый процесс имеет отдельный пользовательский ID и ID группы, которые определяют привилегии процесса. Они коллективно называются persona процесса, потому что они определяют " кто это " для целей управления доступом. Эти ID также называются эффективным пользовательским ID и эффективным ID группы процесса.
Ваша оболочка входа в систему начинается с persona, который состоит из вашего пользовательского ID и вашего значения ID группы по умолчанию. В нормальных обстоятельствах, все ваши другие процессы наследуют эти значения.
Процесс также имеет реальный пользовательский ID, который идентифицирует пользователя, который создал процесс, и реальный ID группы, который идентифицирует заданную по умолчанию группу этого пользователя. Эти значения не играют роль в управлении доступом, так что мы не рассматриваем их частью persona. Но они - также важны.
И реальный и эффективный пользовательский ID может быть изменен в течение срока службы процесса. См. Раздел 25.3 [Почему Изменяется Persona].
Кроме того, пользователь может принадлежать многим группам, так что persona включает дополнительные ID группы, которые также относятся к правам.
Пользовательский ID процесса также управляет правами для посылки сигналов, используя функцию kill. См. Раздел 21.6.2 [Передача сигналов Другому Процессу].
25.3 Почему Изменяется Persona Процесса?
Наиболее очевидная ситуация, когда процессу необходимо изменить пользователя и/или ID группы - программа входа в систему. Когда вход в систему начинает выполняться, пользовательский ID корневой. Работа должна начать оболочку, чей ID пользователя и группы являются таковыми регистрируемого пользователя. (Чтобы выполнять это полностью, вход в систему должен установить реальный ID пользователя и группы также как persona. Но это - частный случай.)
Более общий случай изменения persona - когда обычный пользователь программирует потребности доступа к ресурсу, который обычно не доступен для пользователя, фактически выполняющего это.
Например, Вы можете иметь файл, который управляется вашей программой, но он не должен читаться или изменяться непосредственно другими пользователями, потому что он осуществляет некоторый протокол блокировки, или потому что Вы хотите сохранять целостность или секретность информации, которую он содержит. Этот вид ограниченного доступа может быть выполнен при наличии программы, изменяющей эффективного пользователя или ID группы соответствующего таковому ресурса.
Таким образом, вообразите готовую программу, которая сохраняет очки в файле. Готовая программа непосредственно должна быть способной модифицировать этот файл независимо из того, кто выполняет ее, но если пользователи могут записывать в файл без того, чтобы пройти игру, они могут давать себе любое количество очков, которое они находят приятным. Некоторые люди рассматривают этот нежелательным, или даже предосудительным. Это может быть предотвращено, созданием нового пользовательского ID и имени входа в систему (напр. games) чтобы обладать файлом, и сделать файл перезаписываемым только этим пользователем. Когда готовая программа хочет модифицировать этот файл, она может изменять эффективный пользовательский ID на games. В действительности, программа должна принять persona games, чтобы она могла писать в этот файл.
25.4 Как Приложение Может Изменить Persona
Способность изменять persona процесса может быть источником ненамеренных нарушений секретности, или даже намеренного неправильного обращения. Из-за потенциальных проблем, замена persona ограничена специальными обстоятельствами.
Вы не можете произвольно устанавливать ваш пользовательский ID или ID группы; только привилегированные процессы могут делать это. Взамен, нормальный способ для программы, чтобы изменить persona состоит в том, чтобы было установлено заранее соглашение изменения специфического пользователя или группы. Это делают функции setuid и setgid битов режима доступа файла. См. Раздел 9.8.5 [Биты Прав].
Когда setuid бит исполняемого файла установлен, выполнение этого файла автоматически изменяет эффективный пользовательский ID на пользователя, который обладает файлом. Аналогично, при выполнении файла, чей setgid бит установлен изменяется эффективный ID группы на группу файла. См. Раздел 23.5 [Выполнение Файла]. Создание файла, который изменяется к специфическому ID пользователz или ID группы таким образом, требует полного доступа к этому пользователю или группе.
См. Раздел 9.8 [Атрибуты Файла], для более общего обсуждения режимов файла и достижимости.
Процесс может всегда изменять эффективный пользовательский (или групповой) ID обратно реальному ID. Программы делают это, чтобы выключить их специальные привилегии, когда они ненужны, что делается для большей ошибкоустойчивости.
25.5 Чтение Persona Процесса
Имеются детализированные описания функций для чтения ID пользователя и группы процесса, и реального и эффективного. Чтобы использовать эти средства, Вы должны включить заглавные файлы " sys/types.h " и " unistd.h ".
uid_t (тип данных)
Это - целочисленный тип данных, используемый, чтобы представить пользовательский ID. В библиотеке GNU, это - побочный результат исследования для unsigned int.
gid_t (тип данных)
Это - целочисленный тип данных, используемый, чтобы представить ID группы. В библиотеке GNU, это - побочный результат исследования для unsigned int.
uid_t getuid (void) (функция)
Getuid функция возвращает реальный пользовательский ID процесса.
gid_t getgid (void) (функция)
Getgid функция возвращает реальный ID группы процесса.
uid_t geteuid (void) (функция)
Geteuid функция возвращает эффективный пользовательский ID процесса.
gid_t getegid (void) (функция)
Getegid функция возвращает эффективный ID группы процесса.
int getgroups (int count, gid_t *groups) (функция)
Getgroups функция используется, чтобы запросить относительно ID дополнительные группы процесса. До count этих ID групп сохранено в массиве groups; возвращаемое значение из функции - число групп, фактически сохраненных. Если count меньше чем общее число дополнительных групп, то getgroups возвращает значение -1, и errno установлена как EINVAL.
Если count - 0, то getgroups только возвращает общее число дополнительных групп. Для систем, которые не поддерживают дополнительные группы, это будет всегда 0.
Вот как использовать getgroups для чтения вся ID дополнительных групп:
gid_t * read_all_groups (void) { int ngroups = getgroups (NULL, 0); gid_t *groups = (gid_t *) xmalloc (ngroups * sizeof (gid_t)); int val = getgroups (ngroups, groups); if (val < 0) { free (groups); return NULL; } return groups; }
25.6 Установка Пользовательского ID
Этот раздел описывает функции для изменения пользовательского ID (реального и/или эффективного) процесса.
Чтобы использовать эти средства, Вы должны включить заглавные файлы " sys/types.h " и " unistd.h ".
int setuid (uid_t newuid) (функция)
Эта функция устанавливает, и реальный и эффективный пользовательский ID процесса как newuid, если процесс имеет соответствующие привилегии.
Если процесс не привилегирован, то newuid, должен быть равен реальному пользовательскому ID или сохраненному пользовательскому ID (если система поддерживает возможность _POSIX_SAVED_IDS). В этом случае, setuid устанавливает только эффективный пользовательский ID, а не реальный пользовательский ID.
Setuid функция возвращает значение 0, чтобы указать успешное завершение, и значение -1, чтобы указать ошибку. Следующие errno условия ошибки определены для этой функции:
- EINVAL
-
Значение newuid аргумента недопустимо.
- EPERM
-
Процесс не имеет соответствующих привилегий.
int setreuid (uid_t ruid, uid_t euid) (функция)
Эта функция устанавливает реальный пользовательский ID процесса как ruid и эффективный пользовательский ID как euid. Если ruid -1, это означает, что реальный пользовательский ID не изменился; аналогично, если euid -1, это означает, чтобы не изменился эффективный пользовательский ID.
Setreuid функция существует для совместимости с 4.3 UNIX BSD, который не поддерживает сохранение ID. Вы можете использовать эту функцию, чтобы изменять эффективного и реального пользователя процесса. (Привилегированные процессы не ограничены этим специфическим использованием.) если сохраненный ID обеспечивается, Вы должны использовать эту возможность вместо этой функции. См. раздел 25.8 [ВКЛЮЧЕНИЕ/ОТКЛЮЧЕНИЕ Setuid].
Возвращаемое значение - 0 при успехе и -1 при отказе. Следующие errno условия ошибки определены для этой функции:
- EPERM
-
Процесс не имеет соответствующих привилегий; Вы не имеете прав изменить на заданный ID.
25.7 Установка ID Группы
Этот раздел описывает функции для изменения ID группы (реальный и эффективный) процесса. Чтобы использовать эти средства, Вы должны включить заглавные файлы " sys/types.h " и " unistd.h ".
int setgid (gid_t newgid) (функция)
Эта функция устанавливает, и реальный и эффективный ID группы процесса как newgid, если процесс имеет соответствующие привилегии.
Если процесс не привилегирован, то newgid, должен также быть равен реальному ID группы или сохраненному ID группы. В этом случае, setgid устанавливает только эффективный ID группы, а не реальный ID группы.
Возвращаемые значения и условия ошибки для setgid - такие же как для setuid.
int setregid (gid_t rgid, fid_t egid) (функция)
Эта функция устанавливает реальный ID группы процесса как rgid, а эффективный ID группы как egid. Если rgid -1, это означает, чтобы реальный ID группы не изменялся; аналогично, если egid -1, это означает, чтобы не изменялся эффективный ID группы.
Setregid функция предусмотрена для совместимости с 4.3 UNIX BSD, который не поддерживает сохраненные ID. Вы можете использовать эту функцию, чтобы изменять эффективный и реальный ID группы процесса. (Привилегированные процессы не ограничены этим использованием.) если сохраненные ID обеспечиваются, Вы должны использовать эту возможность вместо того, чтобы использовать эту функцию. См. раздел 25.8 [ВКЛЮЧЕНИЕ/ОТКЛЮЧЕНИЕ Setuid].
Возвращаемые значения и условия ошибки для setregid - такие же как для setreuid.
Система GNU также допускает привилегированным процессам, изменять их дополнительные ID группы. Чтобы использовать setgroups или initgroups, ваши программы должны включить заглавный файл " grp.h ". int setgroups (size_t count, gid_t *groups) (функция)
Эта функция устанавливает дополнительный ID группы процесса. Она может вызываться только из привилегированных процессов. Аргумент count определяет число ID групп в массиве groups.
Эта функция возвращает 0 в случае успеха и -1 при ошибке. Следующие errno условия ошибки определены для этой функции:
- EPERM
-
Вызывающий процесс не привилегирован.
int initgroups (const char *user, gid_t gid) (функция)
Initgroups функция вызывает setgroups, чтобы установить дополнительный ID группы. ID группы gid также включен.
25.8 Предоставление и Отключение Setuid
Типичная программа setuid не нуждается в специальном доступе все время. Хорошая идея выключить этот доступ, когда он ненужен, так что она возможно не может давать непривелегированный доступ.
Если система поддерживает сохраненный пользовательский ID, Вы можете выполнить это с setuid. Когда стартует программа game, ее реальный пользовательский ID - jdoe, эффективный пользовательский ID - games, и сохраненный пользовательский ID - также games. Программа должна записать оба значения пользовательских ID один раз в начале, примерно так:
user_user_id = getuid (); game_user_id = geteuid ();
Теперь она может выключить доступ к файлу game
setuid (user_user_id);
И и включить его
setuid (game_user_id);
Во время этого процесса, реальный пользовательский ID остается jdoe, и сохраненный пользовательский ID остается games, так что программа может всегда устанавливать эффективный пользовательский ID как любой из них.
На других системах, которые не поддерживают сохраненный пользовательский ID, Вы можете переключать доступ setuid используя setreuid, чтобы менять реального и эффективного пользователя процесса, следующим образом:
setreuid (geteuid (), getuid ());
Этот частный случай не может терпеть неудачу.
Почему это имеет эффект переключения доступа setuid? Предположите, что программа game только что началась, и реальный пользовательский ID - jdoe, в то время как эффективный пользовательский ID - games. В этом состоянии, game может запись файл scores (очков). Если она меняет два универсальных идентификатора, реальный, становится games, а эффективный становится jdoe; теперь программа имеет только jdoe доступ. Другая перестановка приводит games обратно к эффективному пользовательскому ID и восстанавливает доступ к файлу scores.
Чтобы обрабатывать оба вида систем, проверьте сохранение пользовательского ID условным выражением препроцессора, примерно так:
#ifdef _POSIX_SAVED_IDS setuid (user_user_id); #else setreuid (geteuid (), getuid ()); #endif
25.9 Пример Setuid Программы
Имеется пример, показывающий, как установить программу, которая изменяет эффективный пользовательский ID.
Это - часть программы game, которая манипулирует файлом " scores " который должен быть перезаписываем только программой game непосредственно. Программа считает, что исполняемый файл будет установлен с set-user-ID набором битов и принадлежать тому же самому пользователю как " scores " файл.
Исполняемому файлу дается режим 4755, при выполнении его " ls -l " производится вывод подобно:
-rwsr-xr-x 1 games 184422 Jul 30 15:17 caber-toss
Set-user-ID бит обнаруживается в режимах файла как " s'.
Файл scores имеет режим 644:
-rw-r--r-- 1 games 0 Jul 31 15:33 scores
Имеются части программы, которые показывают, как установить измененный пользовательский ID. Эта программа - сделана так, чтобы она использовала возможность сохранения ID, если она обеспечивается, и иначе использует setreuid, чтобы изменять эффективного и реального пользователя.
#include
#include #include #include static uid_t euid, ruid; void do_setuid (void) { int status; #ifdef _POSIX_SAVED_IDS status = setuid (euid); #else status = setreuid (ruid, euid); #endif if (status < 0) { fprintf (stderr, "Couldn't set uid.\n"); exit (status); } } void undo_setuid (void) { int status; #ifdef _POSIX_SAVED_IDS status = setuid (ruid); #else status = setreuid (euid, ruid); #endif if (status < 0) { fprintf (stderr, "Couldn't set uid.\n"); exit (status); } } int main (void) { ruid = getuid (); euid = geteuid (); undo_setuid (); . . . }
Когда программа должна открыть файл scores она включает обратно оригинальный эффективный пользовательский ID, примерно так:
int record_score (int score) { FILE *stream; char *myname; do_setuid (); stream = fopen (SCORES_FILE, "a"); undo_setuid (); if (stream) { myname = cuserid (NULL); if (score < 0) fprintf (stream, "%10s: Couldn't lift the caber.\n", myname); else fprintf (stream, "%10s: %d feet.\n", myname, score); fclose (stream); return 0; } else return -1; }
25.10 Советы для Написания Программы Setuid
Для программ setuid возможно дать доступ пользователю, который не предусмотрен фактически, если Вы хотите избежать этого, Вы должны быть внимательны. Имеются некоторые руководящие принципы для предотвращения непредназначенного доступа и уменьшения следствий, когда он происходит:
- Не Иметь программы setuid с привилегированным пользовательским ID типа root, если это не абсолютно необходимо. Если ресурс является специфическим для вашей специфической программы, это лучше определить новый, непривилегированный пользовательский ID или ID группы для управления этим ресурсом.
- Быть осторожным относительно использования системы и функций exec в комбинации с изменением эффективного пользовательского ID. Не допускайте пользователем вашей программы выполнять произвольные программы под измененным пользовательским ID. Если Вы должны запустить другую программу под измененным ID, определяйте абсолютное имя файла для выполнимой программы, и удостоверитесь, что защиты на выполнимой программе и каталогах являются такими, что обычные пользователи не могут заменять ее на некоторую другую программу.
- Используйте пользовательский ID управления ресурсами только в части программы, которая фактически использует тот ресурс. Когда вы закончили с этим, восстановите эффективный пользовательский ID обратно фактическому пользователю.
- Если часть setuid вашей программы должна обратиться к другим файлам кроме управляемого ресурса, она должна проверить, что реальный пользователь имел бы право обратиться к этим файлам. Вы можете использовать функцию access (см. Раздел 9.8.6 [Право Доступа]) чтобы проверить это; она использует реальный ID пользователя и группы, а не эффективные ID.
25.11 Идентификация, кто Регистрируется
Вы можете использовать функции, перечисленные в этом разделе, чтобы определить имя входа в систему пользователя, который выполняет процесс, и имя пользователя, который зарегистрирован в текущем сеансе. См. также функцию getuid (см. Раздел 25.5 [Чтение Persona]).
Getlogin функция объявлена в " unistd.h ", в то время как cuserid и L_cuserid объявлены в " stdio.h ".
char * getlogin (void) (функция)
Getlogin функция возвращает указатель на строку, содержащую имя пользователя, зарегистрированного на терминале управления процесса, или пустой указатель, если эта информация не может быть определена. Строка статически размещена и могла бы быть записана поверх при последующих обращениях к этой функции или к cuserid.
char * cuserid (char *string) (функция)
Cuserid функция возвращает указатель на строку, содержащую имя пользователя, связанное с эффективным ID процесса. Если это - не пустой указатель, это должен быть массив, который может содержать по крайней мере L_cuserid символов; строка возвращается в этом массиве. Иначе, возвращается указатель на строку в статической области. Эта строка статически размещена и может быть записана поверх при последующих обращениях к этой функции или к getlogin.
int L_cuserid (макрос)
Целочисленная константа, которая указывает какой длины массив Вам нужен чтобы сохранить имя пользователя.
Эти функции допускают вашей программе идентифицировать положительно пользователя, кто выполняется или пользователь, кто зарегистрирован в этом сеансе. (Они могут отличиться, когда программы setuid включаются; См. Раздел 25.2 [Процесс Persona].) пользователь не может сделать ничего, чтобы ввести в заблуждение эти функции.
Для большинства целей более полезно использовать переменную среды LOGNAME, чтобы выяснить, кто пользователь. Это более гибко потому что пользователь может устанавливать LOGNAME произвольно. См. Раздел 22.2.2 [Стандартная Среда].
25.12 База данных Пользователей
Этот раздел описывает все относительно поиска и просмотра базы данных зарегистрированных пользователей. База данных непосредственно сохраняется в файле " /etc/passwd " на большинстве систем, но на некоторых системах специальный сетевой сервер дает доступ к этому.
Структура Данных, которая Описывает Пользователя
Функции и структуры данных для доступа к базе данных пользователей системы объявлены в заглавном файле " pwd.h ".
struct passwd (тип данных)
Passwd структуры данных используется, чтобы содержать информацию относительно входов в базу данных пользователя системы. Она имеет по крайней мере следующие элементы:
char *pw_name
Имя входа в систему пользователя.
char *pw_passwd.
Шифрованная строка пароля.
uid_t pw_uid
Пользовательский ID.
gid_t pw_gid
Значение по умолчанию ID группы.
char *pw_gecos
Строка, обычно содержащая реальное имя пользователя, и возможно другую информацию типа номера телефона.
char *pw_dir
Исходный каталог пользователя, или начальный рабочий каталог. Это может быть пустой указатель, когда интерпретация зависима от системы.
char *pw_shell
Оболочка пользователя по умолчанию, или начальная выполненная программа, когда пользователь регистрируется. Это может быть пустой указатель, указывая, что должно использоваться системное значение по умолчанию.
Поиск Одного Пользователя
Вы можете искать специфического пользователя, используюя getpwuid или getpwnam. Эти функции объявлены в " pwd.h ".
struct passwd * getpwuid (uid_t uid) (функция)
Эта функция возвращает указатель на статически размещенную структуру, содержащую информацию относительно пользователя, чей пользовательский ID является uid. Эта структура может быть записана поверх на последующих обращениях к getpwuid.
Пустое значение указателя указывает, что не имеется никакого пользователя в базе данных с пользовательским ID uid.
struct passwd * getpwnam (const char *name) (функция)
Эта функция возвращает указатель на статически размещенную структуру, содержащую информацию относительно пользователя, чье имя пользователя является name. Эта структура может быть записана поверх при последующих обращениях к getpwnam.
Пустое значение указателя указывает, что не имеется никакого пользователя, именованного name.
Просмотр Списка Всех Пользователей
Этот раздел объясняет, как программа может читать список всех пользователей в системе, по одному пользователю одновременно. Функции, описанные здесь объявлены в " pwd.h ".
Вы можете использовать fgetpwent функцию для чтения входов пользователя из специфического файла.
struct passwd * fgetpwent (FILE *stream) (функция)
Эта функция читает следующий вход пользователя из потока и возвращает указатель на вход. Структура статически размещена и перезаписываема при последующих обращениях к fgetpwent. Вы должны копировать содержимое структуры, если Вы желаете сохранить информацию.
Этот поток должен соответствовать файлу в том же самом формате как стандартный файл базы данных паролей. Эта функция исходит из System V.
Способ просматривать все входы в базе данных пользователей - с setpwent, getpwent, и endpwent.
void setpwent (void) (функция)
Эта функция инициализирует поток, который getpwent использует для read базу данных пользователей.
struct passwd * getpwent (void) (функция)
Getpwent функция читает следующий вход из потока, инициализированного setpwent.
Она возвращает указатель на вход. Структура статически размещена и перезаписываема при последующих обращениях к getpwent. Вы должны копировать содержимое структуры, если Вы желаете сохранить информацию.
void endpwent (void) (функция)
Эта функция закрывает внутренний поток, используемый getpwent.
Запиись Входа Пользователя
int putpwent (const struct passwd *p, FILE *stream) (функция)
Эта функция записывает вход пользователя *p в указанный поток, в формате, используемом для стандартного файла базы данных пользователей. Возвращаемое значение - 0 при успехе и отличное от нуля при отказе.
Эта функция существует для совместимости с SVID. Мы рекомендуем, чтобы Вы избегали использовать ее, потому что она имеет смысл только при условии, что структура struct passwd не имеет никаких элементов за исключением стандартных; на системе, которая обьединяет традиционную базу данных UNIX с другой расширенной информацией относительно пользователей, эта функция неизбежно не учла бы многое из важной информации.
Функция putpwent объявлена в " pwd.h ".
25.13 База данных Групп
Этот раздел описывает все относительно того, как искать и просмотреть базу данных зарегистрированных групп. База данных непосредственно сохраняется в файле " /etc/group " на большинстве систем, но на некоторых системах, специальное сетевое обслуживание обеспечивает доступ к ней.
Структура Данных для Группы
Функции и структуры данных для доступа к базе данных групп системы объявлены в заглавном файле " grp.h ".
struct group (тип данных)
Структура group используется, чтобы содержать информацию относительно входа в базе данных групп системы. Она имеет по крайней мере следующие элементы:
char *gr_name
Имя группы.
gid_t gr_gid
ID группы.
char **gr_mem
Вектор указателей на имена пользователей в группе. Каждое имя пользователя - строка с нулевым символом в конце, и вектор непосредственно завершен пустым указателем.
Поиск Одной Группы
Вы можете искать специфическую группу, используя getgrgid или getgrnam. Эти функции объявлены в " grp.h ".
struct group * getgrgid (gid_t gid) (функция)
Эта функция возвращает указатель на статически размещенную структуру, содержащую информацию относительно группы, чей ID группы является gid. Эта структура может быть записана поверх последующими обращениями к getgrgid.
Пустой указатель указывает, что не имеется никакой группы с ID gid.
struct group * getgrnam (const char *name) (функция)
Эта функция возвращает указатель на статически размещенную структуру, содержащую информацию относительно группы, чье имя группы является name. Эта структура может быть записана поверх последующими обращениями к getgrnam.
Пустой указатель указывает, что нет никакой группы, именованной name.
Просмотр Списка Всех Групп
Этот раздел объясняет, как программа может читать список всех групп в системе, по одной группе одновременно. Функции, описанные здесь объявлены в " grp.h ".
Вы можете использовать fgetgrent функцию, чтобы читать входы группы из специфического файла.
struct group * fgetgrent (FILE *stream) (функция)
Fgetgrent функция читает следующий вход из потока. Она возвращает указатель на вход. Структура статически размещена и перезаписываема при последующих обращениях к fgetgrent. Вы должны копировать содержимое структуры, если Вы желаете сохранить информацию.
Поток должен соответствовать файлу в том же самом формате как стандартный файл базы данных групп.
Способ просматривать все входы в базе данных групп - с setgrent, getgrent, и endgrent.
void setgrent (void) (функция)
Эта функция инициализирует поток для чтения из базы данных групп. Вы используете этот поток, вызывая getgrent.
struct group * getgrent (void) (функция)
Getgrent функция читает следующий вход из потока, инициализированного setgrent.
Она возвращает указатель на вход. Структура статически размещена и перезаписываема при последующих обращениях к getgrent. Вы должны копировать содержимое структуры, если Вы желаете сохранить информацию.
void endgrent (void) (функция)
Эта функция закрывает внутренний поток, используемый getgrent.
25.14 Пример Базы данных Пользователей и Групп
Вот пример программы, показывающий использование функций запроса базы данных системы. Программа печатает некоторую информацию относительно пользователя, выполняющего программу.
#include
#include #include #include #include int main (void) { uid_t me; struct passwd *my_passwd; struct group *my_group; char **members; me = getuid (); my_passwd = getpwuid (me); if (!my_passwd) { printf ("Couldn't find out about user %d.\n", (int) me); exit (EXIT_FAILURE); } printf ("I am %s.\n", my_passwd->pw_gecos); printf ("My login name is %s.\n", my_passwd->pw_name); printf ("My uid is %d.\n", (int) (my_passwd->pw_uid)); printf ("My home directory is %s.\n", my_passwd->pw_dir); printf ("My default shell is %s.\n", my_passwd->pw_shell); my_group = getgrgid (my_passwd->pw_gid); if (!my_group) { printf ("Couldn't find out about group %d.\n", (int) my_passwd->pw_gid); exit (EXIT_FAILURE); } printf ("My default group is %s (%d).\n", my_group->gr_name, (int) (my_passwd->pw_gid)); printf ("The members of this group are:\n"); members = my_group->gr_mem; while (*members) { printf (" %s\n", *(members)); members++; } return EXIT_SUCCESS; }
Вот некоторый вывод этой программы:
I am Throckmorton Snurd. My login name is snurd. My uid is 31093. My home directory is /home/fsg/snurd. My default shell is /bin/sh. My default group is guest (12). The members of this group are: friedman tami
26. Информационная Система
Эта глава описывает функции, которые возвращают информацию относительно специфической машины, тип аппаратных средств, тип программного обеспечения, и имя индивидуальной машины.
26.1 Главная Идентификация
Этот раздел объясняет, как идентифицировать специфическую машину, на которой ваша программа выполняется. Идентификация машины состоит из имени главной ЭВМ Internet и адреса Internet; см. Раздел 11.5 [Именное пространство Internet].
Прототипы для этих функций появляются в " unistd.h ". Команды оболочки hostname и hostid работают, вызывая их.
int gethostname (char *name, size_t size) (функция)
Эта функция возвращает имя главной машины в массиве name. Аргумент size определяет размер этого массива, в байтах.
Возвращаемое значение - 0 при успехе и -1 при отказе. В библиотеке GNU C gethostname терпит неудачу, если размер не достаточно большой; Вы можете пробовать снова с большим массивом. Следующее errno условие ошибки определено для этой функции:
- ENAMETOOLONG
Аргумент size - меньше чем размер главного имени плюс один.
На некоторых системах, имеется символ для максимально возможной длины главного имени: MAXHOSTNAMELEN. Он определен в " sys/param.h ". Но Вы не можете расчитывать на его существование, так что более чисто обработать отказ и попытаться снова.
Gethostname сохраняет начало главного имени в name, даже если главное имя полностью не будет сохранено. Для некоторых целей, усеченное главное имя достаточно. Если так, то Вы можете игнорировать код ошибки.
int sethostname (const char *name, size_t length) (функция)
Sethostname функция устанавливает имя главной машины как name. Только привилегированные процессы могут делать это. Обычно это случается только один раз, при начальной загрузке системы.
Возвращаемое значение - 0 при успехе и -1 при отказе. Следующее errno условие ошибки определено для этой функции:
- EPERM
Этот процесс не может устанавливать главное имя, потому что он не привилегирован.
long int gethostid (void) (функция)
Эта функция возвращает " главный ID " машины. Обычно, это - первичный адрес Internet этой машины, преобразованный в long int. Но на некоторых системах это - бессмысленное но уникальное число, которое является жестко закодированным для каждой машины.
int sethostid (long int id) (функция)
Sethostid функция устанавливает " главный ID " главной машины id. Только привилегированным процессам позволяются делать это. Обычно это случается только один раз, при начальной загрузке системы.
Возвращаемое значение - 0 при успехе и -1 при отказе. Следующие errno условия ошибки определено для этой функции:
- EPERM
Этот процесс не может устанавливать главное имя, потому что он не привилегирован.
- ENOSYS
операционная система не поддерживает установку главного ID. На некоторых системах, главный ID - бессмысленное но уникальное число, жестко закодированное для каждой машины.
26.2 Идентификация Типа АППАРАТНЫХ СРЕДСТВ/ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ
Вы можете использовать uname функцию, чтобы выяснить некоторую информацию относительно типа компьютера. Эта функция и связанный тип данных объявлены в заглавном файле " sys/utsname.h ".
struct utsname (тип данных)
Структура utsname используется, чтобы содержать информацию, возвращенную uname функцией. Она имеет следующие элементы:
char sysname[]
Это - имя используемой операционной системы.
char nodename[]
Это - сетевое имя этого специфического компьютера. В библиотеке GNU, значение - такое же как возвращенное gethostname; см. Раздел 26.1 [Главная Идентификация].
char release[]
Это - текущий уровень выпуска реализации операционной системы.
char version[]
Это - текущая версия выпуска операционной системы.
char machine[]
Это - описание типа аппаратных средств, которые являются используемыми.
Некоторые системы обеспечивают механизм, чтобы опросить ядро непосредственно для этой информации. На системах без такого механизма, библиотека GNU C вносит это поле, основанное на имени конфигурации, которое было определено при формировании и установке библиотеки.
GNU использует имя с тремя частями, чтобы описать конфигурацию системы; три части - центральный процессор, изготовитель и тип системы, и они отделяются подчеркиванием. Любая возможная комбинация трех имен потенциально значима, но большинство таких комбинаций бессмысленно практически и даже значимые не обязательно обеспечиваются любой специфической программой GNU.
Так как значение в machine, как предполагается, описывает только аппаратные средства, оно состоит из первых двух частей имени конфигурации " центральный процессор - изготовитель ". Например:
"sparc-sun", "i386-anything", "m68k-hp", "m68k-sony", "m68k-sun", "mips-dec"
int uname (struct utsname *info) (функция)
Uname функция вносит в структуру, указанную info информацию относительно операционной системы и главной машины. Неотрицательное значение указывает, что данные были успешно сохранены.
-1 указывает ошибку. Единственая возможная ошибка - EFAULT, которую мы обычно не упоминаем, поскольку она - всегда возможна.27. Параметры Конфигурации Системы
Функции и макрокоманды, перечисленные в этой главе дают информацию относительно параметров конфигурации операционной системы, например, ограничений пропускной способности, присутствие необязательных POSIX возможностей, и заданный по умолчанию путь для исполняемых файлов (см. Раздел 27.12 [Строковые Параметры]).27.1 Общие Ограничения Пропускной способности
POSIX.1 и POSIX.2 стандарты определяют ряд параметров, которые описывают ограничения пропускной способности системы. Эти ограничения могут быть фиксированные константы для данной операционной системы, или они могут изменяться от машины к машине.
Каждый из следующих параметров ограничения имеет макрокоманду, которая определена в " limits.h " только, если система имеет фиксированное, однородное ограничение для рассматриваемого параметра. Если система позволяет различным файловым системам или файлам иметь различные ограничения, то макрокоманда неопределена; используйте sysconf, чтобы выяснить ограничение, которое применяется в специфическое время на специфической машине. См. Раздел 27.4 [Sysconf].
Каждый из этих параметров также имеет другую макрокоманду, с именем, начинающимся с " _POSIX ", которая дает самое низкое значение, которое ограничению позволяется иметь на любой системе POSIX. См. Раздел 27.5 [Минимумы].
int ARG_MAX
Если этот макрос определен, то это неизменяющийся максимум объединенной длины argv и environ аргументов которые могут быть переданы функции exec.
int CHILD_MAX
Если этот макрос определен, то это неизменяющееся число максимума процессов, которые могут существовать с тем же самым реальным пользовательским ID одновременно.
int OPEN_MAX
Если этот макрос определен, то это неизменяющееся число максимума файлов, которые одиночный процесс может иметь открытым одновременно.
int STREAM_MAX
Если этот макрос определен, то это неизменяющееся число максимума потоков, которые одиночный процесс может иметь открытым одновременно. См. Раздел 7.3 [Открытие Потоков].
int TZNAME_MAX
Если этот макрос определен, то это неизменяющаяся максимальная длина имени часового пояса. См. Раздел 17.2.6 [Функции Часового пояса].
Эти макрокоманды ограничений всегда определяются в " limits.h ".
int NGROUPS_MAX
Максимальное число ID дополнительных групп, которые один процесс может иметь.
int SSIZE_MAX
Самое большое значение, которое может содержаться в объекте типа ssize_t. Действительно, это - ограничение числа байтов, которые могут читаться или записываться в одиночной операции.
Эта макрокоманда определена во всех системах POSIX, т. к. это ограничение никогда не переконфигурируется.
int RE_DUP_MAX (макрос)
Самое большое число повторений, которое Вам позволяется в конструкции " \{min,max\} " в регулярном выражении.
Эта макрокоманда определена во всех POSIX.2 системах, т. к. POSIX.2 говорит, что она должна всегда определяться, даже если не имеется никакого специфического наложенного ограничения.27.2 Полные Опции Системы
POSIX определяет некоторые системно-специфические опции, которые не все системы POSIX поддерживают. Так как эти опции обеспечиваются в ядре, а не в библиотеке, просто использование библиотек GNU C не гарантирует любой из этих возможностей; это зависит от системы, которую Вы используете.
Вы можете проверять доступность данной опции, используя макрокоманды в этом разделе, вместе с функцией sysconf. макрокоманды определены только, если Вы включаете " unistd.h ".
Для следующих макрокоманд, если макрокоманда определена в " unistd.h ", то опция обеспечивается.
Иначе, опция может или не может обеспечена; используйте sysconf для выяснения. См. Раздел 27.4 [Sysconf].
int _POSIX_JOB_CONTROL (макрос)
Если этот символ определен, это указывает что система поддерживает управление заданиями. Иначе, реализация ведет себя, как будто все процессы внутри сеанса принадлежат одиночной группе процессов. См. Главу 24 [Управление заданиями].
int _POSIX_SAVED_IDS (макрос)
Если этот символ определен, это указывает, что система запоминает эффективный ID пользователя и группы процесса прежде, чем она выполняет исполняемый файл с установкой set-user-ID или set-group-ID битов, и что явное изменение эффективного пользователя или группы обратно к этим значениям разрешается. Если эта опция не определена, то, если непривилегированный процесс изменяет эффективного пользователя или группу на реального пользователя или группу процесса, то он не может изменять их обратно снова. См. раздел 25.8 [ВКЛЮЧЕНИЕ/ОТКЛЮЧЕНИЕ Setuid].
Для следующих макрокоманд, если макрокоманда определена в " unistd.h ", то значение указывает, обеспечивается ли опция. Значение -1 означает нет, а любое другое значение означает да. Если макрокоманда не определена, то опция может или не может обеспечиваться; используйте sysconf для выяснения. См. Раздел 27.4 [Sysconf].
int _POSIX2_C_DEV (макрос)
Если этот символ определен, это указывает, что система имеет POSIX.2 команду компилятора C, c89. Библиотека GNU C всегда определяет его как 1.
int _POSIX2_FORT_DEV (макрос)
Если этот символ определен, это указывает, что система имеет POSIX.2 команду компилятора ФОРТРАНа, fort77. Библиотека GNU C никогда не определяет его, т. к. мы не знаем то, что система имеет.
int _POSIX2_FORT_RUN
Если этот символ определен, это указывает, что система имеет POSIX.2 команду asa для интерпретирования управление каретки ФОРТРАНа. Библиотека GNU C никогда не определяет его, т. к. мы не знаем что имеет система.
int _POSIX2_LOCALEDEF (макрос)
Если этот символ определен, это указывает, что система имеет POSIX.2 команду localedef. Библиотека GNU C никогда не определяет его.
int _POSIX2_SW_DEV (макрос)
Если этот символ определен, это указывает, что система имеет POSIX.2 команды ar, make, и strip. Библиотека GNU C всегда определяет его как 1.27.3 Которая Версия POSIX Обеспечивается
long int _POSIX_VERSION (макрос)
Эта константа представляет версию POSIX.1 стандарта который соответствует реализация. Для реализации, соответсвующей 1990 POSIX.1 стандарту, значение - целое число 199009L. _POSIX_VERSION всегда определяется (в " unistd.h ") в любой системе POSIX.
Примечание Использования: Не пробуйте тестировать поддерживает ли система POSIX, включая " unistd.h " и тогда проверяя, определен ли _POSIX_VERSION. На не - posix системах, это возможно выдаст ошибку, т. к. там нет никакого " unistd.h ". Мы не знаем любого способа, которым Вы можете надежно проверять при трансляции, поддерживает ли ваша целевая система POSIX или существует ли " unistd.h ".
Компилятор GNU C предопределяет символ __ POSIX __, если целевая система - система POSIX. Если Вы не используете любой, другие компиляторы на системах POSIX, проверяя определенный (__ POSIX __) надежно обнаружят такие системы.
long int _POSIX2_C_VERSION (макрос)
Эта константа представляет версию POSIX.2 стандарта, который поддерживает библиотека и ядро системы. Мы не знаем какое значение это будет для первой версии POSIX.2 стандарта, т. к. значение включает год и месяц, в котором стандарт официально принят.
Значение этого символа не говорит ничто относительно утилит, установленных на системе.
Примечание Использования: Вы можете использовать эту макрокоманду, чтобы проверить что POSIX.1 библиотека систем поддерживает POSIX.2 также. Любая POSIX.1 система содержит " unistd.h ", так что включайте этот файл и тогда проверяйте defined (_POSIX2_C_VERSION).27.4 Использование sysconf
Когда ваша система имеет ограничения системы с перестраиваемой конфигурацией, Вы можете использовать функцию sysconf для выяснения значений, которые применяются для любой специфической машины. Функция и связанные константы параметров объявлены в заглавном файле " unistd.h ".Определение sysconf
long int sysconf (int parameter) (функция)
Эта функция используется, для запроса относительно параметров системы. Аргумент parameter должен быть один из " _SC_ " символов, перечисленных ниже.
Нормальное возвращаемое значение из sysconf - значение, которое Вы запросили. Значение -1 возвращается, если реализация не налагает ограничения, и в случае ошибки.
Следующие errno условия ошибки определены для этой функции:
- EINVAL
значение параметра недопустимо.
Константы для Sysconf Параметров
Имеются символические константы которые используют как аргументы в sysconf. Значения - все целочисленные константы (более специально, значения перечислимого типа).
_SC_ARG_MAX
Запрос относительно параметра, соответствующего ARG_MAX.
_SC_CHILD_MAX
Запрос относительно параметра, соответствующего CHILD_MAX.
_SC_OPEN_MAX
Запрос относительно параметра, соответствующего OPEN_MAX.
_SC_STREAM_MAX
Запрос относительно параметра, соответствующего STREAM_MAX.
_SC_TZNAME_MAX
Запрос относительно параметра, соответствующего TZNAME_MAX.
_SC_NGROUPS_MAX
Запрос относительно параметра, соответствующего NGROUPS_MAX.
_SC_JOB_CONTROL
Запрос относительно параметра, соответствующего
_POSIX_JOB_CONTROL. _SC_SAVED_IDS
Запрос относительно параметра, соответствующего
_POSIX_SAVED_IDS. _SC_VERSION
Запрос относительно параметра, соответствующего _POSIX_VERSION.
_SC_CLK_TCK
Запрос относительно параметра, соответствующего CLOCKS_PER_SEC; см. Раздел 17.1.1 [Основное Время CPU].
_SC_2_C_DEV
Запрос относительно того, имеет ли система POSIX.2 команду компилятора C, c89.
_SC_2_FORT_DEV
Запрос относительно того, имеет ли система POSIX.2 команду компилятора ФОРТРАНа, fort77.
_SC_2_FORT_RUN
Запрос относительно того, ли система имеет POSIX.2 команду asa для интерпретирования управления каретки ФОРТРАНа.
_SC_2_LOCALEDEF
Запросите относительно того, имеет ли система POSIX.2 команду localedef.
_SC_2_SW_DEV
Запросите относительно того, имеет ли система POSIX.2 команды ar, make, и strip.
_SC_BC_BASE_MAX
Запрос относительно максимального значения obase в утилите bc.
_SC_BC_DIM_MAX
Запрос относительно максимального размера массива в утилите bc.
_SC_BC_SCALE_MAX
Запрос относительно максимального значения масштаба в утилите bc.
_SC_BC_STRING_MAX
Запрос относительно максимального размера строковой константы в утилите bc.
_SC_COLL_WEIGHTS_MAX
Запрос относительно максимального числа весов, которые могут обязательно использоваться в определении последовательности объединений для стандарта.
_SC_EXPR_NEST_MAX
Запрос относительно максимального числа выражений, вложенных внутри круглых скобок при использовании утилиты expr.
_SC_LINE_MAX
Запрос относительно максимального размера текстовой строки который POSIX.2 текстовые утилиты могут обрабатывать.
_SC_EQUIV_CLASS_MAX
Запрос относительно максимального числа весов, которые могут быть назначены на вход LC_COLLATE класса (" порядок " ключевое слово в определении стандарта). Библиотека GNU C не поддерживает определения стандарта.
_SC_VERSION
Запрос относительно номера версии POSIX.1 поддерживаемой ядром.
_SC_2_VERSION
Запрос относительно номера версии POSIX.2, поддерживаемой утилитами системы.
_SC_PAGESIZE
Запрос относительно виртуального размера страницы памяти машины. Getpagesize возвращает то же самое значение. См. <неопределенный> [XXX getpagesize].Примеры sysconf
Мы рекомендуем, чтобы Вы сначала проверили макроопределение для параметра в котором Вы заинтересованы, и вызывали sysconf только, если макрокоманда не определена. Например, вот как проверяют обеспечивается ли управление заданиями:
int have_job_control (void) { #ifdef _POSIX_JOB_CONTROL return 1; #else int value = sysconf (_SC_JOB_CONTROL); if (value < 0) fatal (strerror (errno)); return value; #endif } Here is how to get the value of a numeric limit: int get_child_max () { #ifdef CHILD_MAX return CHILD_MAX; #else int value = sysconf (_SC_CHILD_MAX); if (value < 0) fatal (strerror (errno)); return value; #endif }
27.5 Минимальные Значения для Общих Ограничений Емкости
Имеются названия для POSIX минимальных верхних пределов для параметров ограничений системы.
Смысл этих значений состоит в том, что Вы можете безопасно помещать в эти ограничения без проверки, может ли специфическая система, которую Вы используете принять их.
_POSIX_ARG_MAX
Значение этой макрокоманды - наименьшее ограничение, разрешенное POSIX для максимума объединенной длины argv и других аргументов, которые могут быть переданы запускаемой функции. Значение - 4096.
_POSIX_CHILD_MAX
Значение этой макрокоманды наименьшее ограничение разрешенное POSIX для максимального числа одновременных процессов на реального пользователя. Значение - 6.
_POSIX_NGROUPS_MAX
Значение этой макрокоманды - наименьшее значение ограничения, разрешенное POSIX для максимального числа дополнительных групп процесса. Значение - 0.
_POSIX_OPEN_MAX
Значение этой макрокоманды - наименьшее значение ограничения, разрешенное POSIX для максимального числа файлов, которые одиночный процесс может иметь открытым одновременно. Значение - 16.
_POSIX_SSIZE_MAX
Значение этой макрокоманды - наименьшее значение ограничения, разрешенное POSIX для максимального значения, которое может быть сохранено в объекте типа ssize_t. Значение - 32767.
_POSIX_STREAM_MAX
Значение этой макрокоманды - наименьшее значение ограничения, разрешенное POSIX для максимального числа потоков, которые одиночный процесс может иметь открытым одновременно. Значение - 8.
_POSIX_TZNAME_MAX
Значение этой макрокоманды - наименьшее значение ограничения, разрешенное POSIX для максимальной длины имени часового пояса. Значение - 3.
_POSIX2_RE_DUP_MAX
Значение этой макрокоманды - наименьшее значение ограничения, разрешенное POSIX для чисел, используемых в " \ {min, max\} " конструкциях в регулярном выражении. Значение - 255.27.6 Ограничения Емкости Файловой системы
POSIX.1 стандарт определяет ряд параметров, которые описывают ограничения файловой системы. Система может иметь фиксированное, однородное ограничение для параметра, но это не обычный случай. На большинстве систем, для различных файловых систем (и, для некоторых параметров, даже для различных файлов) имеются различные максимальные ограничения. Например, если Вы используете NFS для устанавливки некоторых файловых систем из других машин.
Каждая из следующих макрокоманд определена в " limits.h " только, если система имеет фиксированное, однородное ограничение для рассматриваемого параметра. Если система позволяет различным файловым системам или файлам иметь различные ограничения, то макрокоманда неопределена; используйте pathconf или fpathconf, чтобы выяснить ограничение, которое применяется к специфическому файлу. См. Раздел 27.9 [Pathconf].
Каждый параметр также имеет другую макрокоманду, с именем, начинающимся с " _POSIX ", которая дает самое низкое значение, которое ограничению позволяется иметь на любой POSIX системе. См. Раздел 27.8 [Минимумы Файла].
int LINK_MAX (макрос)
Однородное ограничение системы для числа имен для данного файла. См. Раздел 9.3 [Жесткие связи].
int MAX_CANON (макрос)
Однородное ограничение системы для количества текста в строке ввода, когда допускается входное редактирование. См. Раздел 12.3 [Каноническиий или Нет].
int MAX_INPUT (макрос)
Однородное ограничение системы для общего числа символов, печатаемых вперед как ввод. См. Раздел 12.2 [Очереди ввода - вывода].
int NAME_MAX (макрос)
Однородное ограничение системы для длины компонента имени файла.
int PATH_MAX (макрос)
Однородное ограничение системы для длины всего имени файла (то есть аргумент, данный системным вызовам типа open).
int PIPE_BUF (макрос)
Однородное ограничение системы для числа байтов, которые можно написать в трубопровод быстро. См. Главу 10 [Трубопроводы и FIFOs].
Это - альтернативные имена макрокоманд.
int MAXNAMLEN (макрос)
Это - имя BSD для NAME_MAX. Она определена в " dirent.h ".
int FILENAME_MAX (макрос)
Значение этой макрокоманды - целочисленное постоянное выражение, которое представляет максимальную длину строки имени файла. Она определена в " stdio.h ".
В отличие от PATH_MAX, эта макрокоманда определена, даже если не имеется никакого фактического наложенного ограничения. В таком случае, значение - обычно очень большое количество. Так всегда в системе GNU.
Примечание Использования: Не используйте FILENAME_MAX как размер массива, чтобы сохранить имя файла! Вы не можете возможно делать массив таким большим! Используйте динамическое резервирование (см. Главу 3 [Распределение памяти]).27.7 Необязательные Возможности в Поддержке Файлов
POSIX определяет некоторые системно-специфические опции в системных вызовах для файлов. Некоторые системы поддерживают эти опции, а другие - нет. Так как эти опции обеспечиваются в ядре, а не в библиотеке, просто использование библиотеки GNU С не гарантирует, что любая из этих возможностей обеспечивается; это зависит от системы, которую Вы используете. Они могут также изменяться между файловыми системами на одиночной машине.
Этот раздел описывает макрокоманды, которые Вы можете проверять, чтобы определить, обеспечивается ли специфическая опция на вашей машине. Если данная макрокоманда определена в " unistd.h ", то значение, говорит обеспечивается ли соответствующая возможность. (Значение -1 указывает нет; любое другое значение указывает да.) если макрокоманда неопределена, это означает специфические файлы могут и не поддерживать эту возможность.
Так как все машины, которые поддерживают библиотеку GNU C также поддерживают NFS, можно делать общее утверждение относительно того, все ли файловые системы поддерживают возможности _POSIX_NO_TRUNC и _POSIX_CHOWN_RESTRICTED. Так что эти имена никогда не определены как макрокоманды в библиотеке GNU C.
int _POSIX_CHOWN_RESTRICTED (макрос)
Если эта опция есть, chown функция ограничена так, что единственые изменения, разрешенные непривилегированным процессам: изменить владельца группы файла либо на эффективный ID группы процесса, либо на один из ID дополнительных групп. См. Раздел 9.8.4 [Владелец Файла].
int _POSIX_NO_TRUNC (макрос)
Если эта опция обеспечивается, когда компоненты имени файла длиннее чем NAME_MAX, генерируется ENAMETOOLONG ошибка. Иначе, компоненты имени файла, которые являются слишком длинными будут тихо усечены.
unsigned char _POSIX_VDISABLE (макрос)
Эта опция значима только для файлов, которые являются устройствами терминала. Если она допускается, то обработчик для специальных управляющих символов, может быть заблокирован индивидуально. См. Раздел 12.4.9 [Специальные Символы].
Если одна из этих макрокоманд является неопределенной, это означает что эта опция может быть определена в действительности для некоторых файлов и не определена для других. Чтобы запрашивать относительно специфического файла, вызовите pathconf или fpathconf. См. Раздел 27.9 [Pathconf].27.8 Минимальные Значения для Ограничений Файловой системы
Имеются имена для POSIX минимальных верхних пределов для некоторых из вышеупомянутых параметров.
Смысл этих значений состоит в том, что Вы можете безопасно помещать в эти ограничения без проверки, может ли специфическая система, которую Вы используете принять их.
_POSIX_LINK_MAX
Наименьшее значение предела, разрешенное POSIX для максимального значения числа связей файла. Значение этой константы - 8; таким образом, Вы можете всегда создавать до восьми имен для файла без столкновений с ограничением системы.
_POSIX_MAX_CANON
Наименьшее значение предела, разрешенное POSIX для максимального числа байтов в каноническиой входной строке из устройства терминала. Значение этой константы - 255.
_POSIX_MAX_INPUT
Наименьшее значение предела, разрешенное POSIX для максимального числа байтов во входной очереди устройства терминала (или буфере клавиатуры). См. Раздел 12.4.4 [Режимы Ввода]. Значение этой константы - 255.
_POSIX_NAME_MAX
Наименьшее значение предела, разрешенное POSIX для максимального числа байтов в компоненте имени файла. Значение этой константы - 14.
_POSIX_PATH_MAX
Наименьшее значение предела, разрешенное POSIX для максимального числа байтов в имени файла. Значение этой константы - 255.
_POSIX_PIPE_BUF
Наименьшее значение предела, разрешенное POSIX для максимального числа байтов, которые можно написать в трубопровод быстро. Значение этой константы - 512.27.9 Использование pathconf
Когда ваша машина позволяет различным файлам иметь различные значения для параметра файловой системы, Вы можете использовать функции в этом разделе, чтобы выяснить значение, которое применяется к любому специфическому файлу.
Эти функции и связанные константы для аргумента parameter объявлены в заглавном файле " unistd.h ".
long int pathconf (const char *filename, int parameter) (функция)
Эта функция используется, чтобы запросить относительно ограничений, которые применяются к файлу, именованному filename.
Аргумент parameter должен быть одной из " _PC_ " констант, перечисленных ниже.
Нормальное возвращаемое значение из pathconf - значение, которое Вы запросили. Значение -1 будет возвращено, если реализация не налагает ограничений, и в случае ошибки.
В вышеупомянутом случае, errno не установлена, в то время как в последнем случае, errno установлена, чтобы указать причину проблемы. Так что единственый способ использовать эту функцию robustly состоит в том, чтобы сохранить 0 в errno перед ее вызовом.
Кроме обычных синтаксических ошибок имени файла (см. Раздел 6.2.3 [Ошибки Имени файла]), следующие условия ошибки определены для этой функции:
- EINVAL
значение параметра недопустимо, или реализация не поддерживает параметр для специфического файла.
long int fpathconf (int filedes, int parameter) (функция)
Точно такая же как pathconf за исключением того, что вместо имени файла используется описатель открытого файла, чтобы определить файл, для которого запрошена информация.
Следующие errno условия ошибки определены для этой функции:
- EBADF
filedes аргумент - не допустимый описатель файла.
- EINVAL
значение параметра недопустимо, или реализация не поддерживает параметр для специфического файла.
Имеются символические константы, которые Вы можете использовать как аргумент parameter в pathconf и fpathconf. Значения - целочисленные константы.
_PC_LINK_MAX
Запрос относительно значения LINK_MAX.
_PC_MAX_CANON
Запрос относительно значения MAX_CANON.
_PC_MAX_INPUT
Запрос относительно значения MAX_INPUT.
_PC_NAME_MAX
Запрос относительно значения NAME_MAX.
_PC_PATH_MAX
Запрос относительно значения PATH _MAX.
_PC_PIPE_BUF
Запрос относительно значения PIPE_BUF.
_PC_CHOWN_RESTRICTED
Запрос относительно значения _POSIX_CHOWN_RESTRICTED.
_PC_NO_TRUNC
Запрос относительно значения _POSIX_NO_TRUNC.
_PC_VDISABLE
Запрос относительно значения _POSIX_VDISABLE.27.10 Ограничения для Утилит
POSIX.2 стандарт определяет некоторые ограничения системы, к которым Вы можете обращаться через sysconf, которые относятся к поведению некоторых утилит а не к поведению библиотеки или операционной системы.
Библиотека GNU C определяет макрокоманды для этих ограничений, и sysconf возвращает значения для них, если Вы спрашиваете; но эти значения не передают никакую значимую информацию. Они - просто самые маленькие значения которые POSIX. 2 разрешает.
int BC_BASE_MAX (макрос)
Самое большое значение obase, который утилита bc, как гарантируют, будет поддерживать.
int BC_SCALE_MAX (макрос)
Самое большое значение масштаба, который утилита bc, как гарантируют, будет поддерживать.
int BC_DIM_MAX (макрос)
Самое большое число элементов в одном массиве, который утилита bc, как гарантируют, будет поддерживать.
int BC_STRING_MAX (макрос)
Самое большое число символов в одной строковой константе, которую утилита bc, как гарантируют, будет поддерживать.
int BC_DIM_MAX (макрос)
Самое большое число элементов в одном массиве, который утилита bc, как гарантируют, будет поддерживать.
int COLL_WEIGHTS_MAX (макрос)
Самое большое число весов, которые могут обязательно использоваться в определении последовательности объединений для стандарта.
int EXPR_NEST_MAX (макрос)
Максимальное число выражений, которые могут быть вложены круглыми скобками для утилиты expr.
int LINE_MAX (макрос)
Самая большая текстовая строка, которую POSIX.2 текстовые утилиты могут поддерживать. (Если, Вы используете GNU версии этих утилит, то нет никакого фактического ограничения за исключением тех, что наложены доступной виртуальной памятью, но не никакого способа, которым библиотека может сообщать Вам это.)27.11 Минимальные Значения для Пределов Утилит
_POSIX2_BC_BASE_MAX
Наименьшее значение предела, разрешенное POSIX.2 для максимального значения obase в утилите bc. Значение - 99.
_POSIX2_BC_DIM_MAX
Наименьшее значение предела, разрешенное POSIX.2 для максимального размера массива в утилите bc. Значение - 2048.
_POSIX2_BC_SCALE_MAX
Наименьшее значение предела, разрешенное POSIX.2 для максимального значения масштаба в утилите bc. Значение - 99.
_POSIX2_BC_STRING_MAX
Наименьшее значение предела, разрешенное POSIX. 2 для максимального размера строковой константы в утилите bc. Значение - 1000.
_POSIX2_COLL_WEIGHTS_MAX
Наименьшее значение предела, разрешенное POSIX. 2 для максимального числа весов, которые могут использоваться в определении последовательности объединений для стандарта. Значение - 2.
_POSIX2_EXPR_NEST_MAX
Наименьшее значение предела, разрешенное POSIX. 2 для максимального числа выражений, вложенных внутри круглых скобок при использовании утилиты expr. Значение - 32.
_POSIX2_LINE_MAX
Наименьшее значение предела, разрешенное POSIX. 2 для максимального размера текстовой строки, которую текстовые утилиты могут обрабатывать. Значение - 2048.
27.12 Строковые Параметры
POSIX. 2 определяет способ получить строковые параметры из операционной системы - функцией confstr:
size_t confstr (int parameter, char *buf, size_t len) (функция)
Эта функция читает значение строкового параметра системы, сохраняя строку в len байты пространства памяти, начинающегося в buf. Аргумент parameter должен быть один из " _CS_ " символов, перечисленных ниже.
Нормальное возвращаемое значение из confstr - длина строкового значения, о котором Вы просили. Если Вы обеспечиваете пустой указатель для buf, то confstr, не пробует сохранять строку; она только возвращает длину. Значение 0 указывает ошибку.
Если строка, о которой Вы просили, слишком длинная для буфера (то есть длиньше чем len - 1), то confstr сохраняет только то что помещается (оставляя участок памяти для пустого символа завершения). Вы можете понять, что это случилось, потому что confstr возвращает значение больше чем или равный len.
Следующие errno условия ошибки определены для этой функции:
- EINVAL
значение параметра недопустимо.
В настоящее время имеется только один параметр, который Вы можете читать с confstr:
_CS_PATH значение этого параметра - рекомендуемый заданный по умолчанию путь для поиска исполняемых файлов. Это - путь, который пользователь имеет по умолчанию только после регистрации.
Способ использовать confstr без любого произвольного ограничения строкового размера состоит в том, чтобы вызвать ее дважды: сначала вызвать ее, чтобы получить длину, зарезервировать буфер соответственно, и тогда вызывать confstr снова, чтобы заполнить буфер, примерно так:
char * get_default_path (void) { size_t len = confstr (_CS_PATH, NULL, 0); char *buffer = (char *) xmalloc (len); if (confstr (_CS_PATH, buf, len + 1) == 0) { free (buffer); return NULL; } return buffer; }
28. Приложение А: Средства Языка C в Библиотеке
О некоторых средствах, выполненных библиотекой C действительно нужно думать как о части Языка C непосредственно. Эти средства должны быть зарегистрированы в Руководстве Языка C, а не в библиотечном руководстве; но так как мы пока не имеем руководства языка, и документация для этих возможностей пишется, мы издаем его здесь.
28.1 A.1 Явная Проверка Внутренней Непротиворечивости
Когда вы - пишите программу, часто неплохо поместить проверки в стратегических местах для "невозможных" ошибок или нарушений базисных предположений. Эти проверки полезны при отладке проблем из-за непониманий между различными частями программы.
макрокоманда assert, определенная в заглавном файле " assert.h ", обеспечивает удобный способ прервать программу при печати сообщения относительно того, где в программе была обнаружена ошибка.
Если только Вы думаете, что ваша программа отлажена, Вы можете отключать проверки ошибки, выполняемые макрокомандой assert, перетранслируя с определенной макрокомандой NDEBUG. Это означает что Вы, фактически не должны изменить исходный текст программы, чтобы отключить эти проверки.
Но отключение этих проверок непротиворечивости нежелательно, если они не делают программу значительно медленнее. Большое количество проверок ошибок - хорошо независимо от того, кто выполняет программу.
Знающий пользователь хотел бы иметь аварийный отказ программы, явно, чем возврат ерунды без указания, что что-то неправильно.
void assert (int expression) (макрос)
Проверяет убеждение программиста, что выражение должно быть отлично от нуля в этом месте программы.
Если NDEBUG не определен, assert проверяет значение выражения. Если оно является ложным (нулем), assert прерывает программу (см. Раздел 22.3.4 [Прерывание выполнения Программы]) после печати сообщения вида:
`file':linenum: Assertion `expression' failed.
в стандартный поток ошибки stderr (см. Раздел 7.2 [Стандартные Потоки]). Filename и номер строки берутся из макрокоманд препроцессора C __FILE__ и __LINE__.
Если макрокоманда препроцессора NDEBUG определена в отметке, где "assert.h " включен, макрокоманда assert не будет делать абсолютно ничего.
Предупреждение: Даже выражение аргумента не будет оценено, если NDEBUG - определен. Так что никогда не используйте assert с аргументами, которые включают побочные эффекты. Например, assert (++ i> 0); является плохой идеей, потому что i не будет увеличена, если NDEBUG определен.
Примечание Использования: средство assert разработано для обнаружения внутренней несогласованности; оно не подходит для сообщения недопустимого ввода или неподходящего использования пользователем программы.
Информация в диагностических сообщениях, напечатанных макрокомандой assert предназначена, чтобы помочь Вам, программисту, прослеживать причину ошибки, но не полезна для сообщения пользователю вашей программы, почему его или ее ввод был недопустим или почему команда не могла быть проведена. Так что Вы не можете использовать assert, чтобы печатать сообщения об ошибках для этих целей.
Более того, ваша программа не должна прерываться когда дан недопустимый ввод, а assert сделала бы это выйдя с состоянием отличным от нуля (см. Раздел 22.3.2 [Состояние Выхода]) после печати сообщений об ошибках.28.2 A.2 Variadic Функции
ANSI C определяет синтаксис для объявления функции, берущей переменное число или тип аргументов. (Такие функции упоминаются как varargs функции или variadic функции.) Однако, язык непосредственно не обеспечивает никакого механизма для таких функций, чтобы обратиться к их не-требуемым аргументам; взамен, Вы используете переменные макрокоманды аргументов, определенные в " stdarg.h ".
Этот раздел описывает, как объявить variadic функции, как написать их, и как вызвать их правильно.
Примечание Совместимости: Намного более старые диалекты C обеспечивают подобный, но несовместимый, механизм для определения функций с переменным числом аргументов, используя " varargs.h ".A.2.1 Зачем Используются Variadic Функции
Обычные функции C берут фиксированное число аргументов. Когда Вы определяете функцию, Вы определяете тип данных для каждого аргумента. Каждое обращение к функции должно обеспечить ожидаемое число аргументов, с типами, которые могут быть преобразованы в заданные. Таким образом, если функция " foo " объявлена: int foo (int, char*); то Вы должны вызвать ее с двумя аргументами: числом и строковым указателем.
Но некоторые функции выполняют операции, которые могут принимать неограниченное число аргументов.
В некоторых случаях функция может обрабатывать любое число значений, действуя на все из них, как на блок. Например, рассмотрите функцию, которая резервирует одномерный массив через malloc, чтобы содержать заданный набор значений. Эта операция имеет смысл для любого числа значений, если только длина массива соответствует к заданному числу. Без средств для переменных аргументов, Вы были бы должны определять отдельную функцию для каждого возможного размера массива.
Библиотечная функция printf (см. Раздел 7.9 [Форматируемый Вывод]) - пример другого класса функции, где переменные аргументы полезны. Эта функция печатает аргументы (которые могут изменяться в типе также как в числе) при контроле над строкой шаблона формата.
Это причины определить variadic функцию, которая может обрабатывать так много аргументов, как вызывающий оператор выбирает.
Некоторые функции типа open берут фиксированный набор аргументов, но иногда игнорируют несколько последних. Строгая приверженность ANSI C требует, чтобы эти функции были определены как variadic; практически, однако, компилятор GNU C и большинство других компиляторов C допускает Вам определять такую функцию, чтобы брать фиксированный набор аргументов больше чем она может когда-либо использовать и тогда только объявлять функцию как variadic (или не объявлять аргументы вообще!).A.2.2 Как Variadic Функции определяются и Используются
Определение и использование variadic функций включает три шага:
- Определить функцию как variadic, используя ". . ." в списке параметров, и использовать специальные макрокоманды, чтобы обратиться к переменным аргументам. См. Раздел A. 2.2.2 [Получение Аргументов].
- Объявить функцию как variadic, используя прототип c ". . .", во всех файлах, которые ее вызывают. См. Раздел A. 2.2.1 [Variadic Прототипы].
- Вызвать функцию записью с фиксированными аргументами, сопровождаемыми дополнительными переменными аргументами. См. Раздел A. 2.2.4 [Вызов Variadic].
A.2.2.1 Синтаксис для Переменных Аргументов
Функция, которая принимает переменное число аргументов, должна быть объявлена с прототипом, который говорит это. Вы записываете фиксированные аргументы как обычные, и тогда указываете ". . ." чтобы указать возможность дополнительных аргументов. Синтаксис ANSI C требует по крайней мере одного фиксированного аргумента перед ". . .". Например,
int func (const char *a, int b, . . .) { . . . }
выделяет определение функции func, которая возвращает int и берет два требуемых аргумента, const char * и int. Они сопровождаются любым числом анонимных аргументов.A.2.2.2 Получение Значения Аргумента
Обычные фиксированные аргументы имеют индивидуальные имена, и Вы можете использовать эти имена, чтобы обратиться к их значениям. Но необязательные аргументы не имеют никаких имен коме ". . . ". Как Вы может обращаться к ним ?
Единственый способ обращаться к ним - последовательно, в порядке, в котором они написаны, и Вы должны использовать специальные макрокоманды из " stdarg.h " в следующем процессе:
- Вы инициализируете переменную указателя аргумента типа va_list, используя va_start. Указатель аргумента, после инициализации, указывает на первый необязательный аргумент.
- Вы обращаетесь к необязательным аргументам последовательными обращениями к va_arg. Первое обращение к va_arg дает Вам, первый необязательный аргумент, следующее обращение дает Вам второй, и так далее.
- Вы указываете, что Вы закончили с переменной указателя аргумента, вызывая va_end.
См. Раздел A. 2.2.5 [Макросы Аргумента], для полных определений va_start, va_arg и va_end.
Шаги 1 и 3 должны выполниться в функции, которая принимает необязательные аргументы. Однако, Вы можете передавать va_list переменную как аргумент другой функции и выполнять весь или часть шага 2 там.
Вы можете выполнять всю последовательность из трех шагов несколько раз внутри одного вызова функции. Если Вы хотите игнорировать необязательные аргументы, Вы можете не делать этого.
Вы можете иметь больше чем одну переменную указателя аргумента, если Вы находите приятным. Вы можете инициализировать каждую переменную с va_start, когда Вы пожелаете, и т. д.A.2.2.3 Сколько Аргументов Обеспечивается
Не имеется никакого общего способа для функции, чтобы определить число и тип необязательных аргументов, с которыми она вызывалась.
Один вид соглашения о вызовах должен передать число необязательных аргументов как один из фиксированных аргументов. Это соглашение работает, принимая что все необязательные аргументы имеют тот же самый тип.
Подобный вариант должен иметь один из требуемых аргументов как битовую маску, с битом для каждой возможной цели, для которой необязательный аргумент мог бы быть обеспечен. Вы проверили бы биты в предопределенной последовательности; если бит установлен, выборка значения следующего аргумента, иначе используется значение по умолчанию.
Требуемый аргумент может использоваться как шаблон, чтобы определить и число и типы необязательных аргументов. Аргумент строки формата printf - один пример этого (см. Раздел 7.9.7 [Функции Форматированного Вывода]).
Другая возможность состоит в том, чтобы передать значение " end marker " как последний необязательный аргумент. Например, для функции, которая манипулирует, произвольным числом аргументов указателей, пустой указатель мог бы указывать конец списка параметров. (Она принимает, что пустой указатель иначе не значим для функции.) execl функция работает только этим способом; см. Раздел 23.5 [Выполнение Файла].A.2.2.4 Вызов Variadic Функции
Вы не должны писать что-нибудь специальное, когда Вы вызываете variadic функцию. Только запишите аргументы (требуемые аргументы, сопровождаемые необязательными) внутри круглых скобок, отделенные запятыми, как обычно.
В принципе, функции, которые определены, чтобы быть variadic, должны также быть объявлены, чтобы быть variadic использованием прототипа функции всякий раз, когда Вы вызываете их. (См. Раздел A. 2.2.1 [Variadic Прототипы], для того какэто сделать.) Это - потому что некоторые компиляторы C используют различное соглашение о вызовах, чтобы передать тот же самый набор значений аргумента к функции в зависимости от того, берет ли та функция переменные аргументы или фиксированне аргументы.
Но имеются несколько функций, которые чрезвычайно удобно не объявлять как variadic, например open и printf.
Так как прототип не определяет типы для необязательных аргументов, в обращении к variadic функции, заданные по умолчанию поддержки аргумента выполняются на необязательных значениях аргумента. Это означает объекты типа char или short int (или signed, или нет) преобразуются или в int или в unsigned int, соответственно; а объекты float типа - в double тип. Так, если вызывающий оператор передает char как необязательный аргумент, получится int, и функция должна получить его с va_arg (ap, int).
Преобразование требуемых аргументов управляется прототипом функции обычным способом: выражение аргумента преобразовано в объявленный тип аргумента.A.2.2.5 Макросы Доступа к Аргументу
Имеются описания макрокоманд, используемых, чтобы отыскать переменные аргументы. Эти макрокоманды определены в заглавном файле " stdarg.h ".
va_list (тип данных)
Тип va_list используется для переменной указателя аргумента.
void va_start (va_list ap, last_required) (макрос)
Эта макрокоманда инициализирует переменную указателя аргумента ap, чтобы указать на первый из необязательных аргументов текущей функции; last_required должен быть последний требуемый аргумент функции.
См. Раздел A.2.3.1 [Старый Varargs], для альтернативного определения va_start в заглавном файле " varargs.h ".
type va_arg (va_list ap, type) (макрос)
Va_arg макрокоманда возвращает значение следующего необязательного аргумента, и изменяет значение ap, чтобы указsdать на последующий аргумент. Таким образом, последовательные использования va_arg возвращают последовательные необязательные аргументы.
Тип значения, возвращенного va_arg - type как определено в обращении. type должен быть само-поддерживающийся тип (не char или short int или float) который соответствует типу фактического аргумента.
void va_end (va_list ap) (макрос)
Он заканчивает использование ap. После обращения va_end, дальнейшие va_arg обращения с тем же самым ap не могут работать. Вы должны вызвать va_end перед возвращением из функции, в которой va_start вызывался с тем же самым аргументом ap.
В библиотеке GNU C, va_end не делает ничего, и Вы не нуждаетесь в его использовании, кроме причин переносимости.A.2.3 Пример Variadic Функции
Вот полная типовая функция, которая принимает переменное число аргументов. Первый аргумент функции - число остающихся аргументов, которые складываются и возвращается результат.
#include
#include int add_em_up (int count,...) { va_list ap; int i, sum; va_start (ap, count); sum = 0; for (i = 0; i < count; i++) sum += va_arg (ap, int); va_end (ap); return sum; } int main (void) { printf ("%d\n", add_em_up (3, 5, 5, 6)); printf ("%d\n", add_em_up (10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); return 0; } A.2.3.1 Variadic Функции Старого стиля
Раньше ANSI C программисты использовали немного отличное средство для написания variadic функции. Компилятор GNU C все еще поддерживает его; в настоящее время оно более переносимое чем средство ANSI C, так как поддержка для ANSI C все еще не универсальна. Заглавный файл, который определяет традиционное variadic средство называется " varargs.h ".
Использование " varargs.h " почти такое же как использование " stdarg.h ". Не имеется никакого различия в том, как Вы вызываете variadic функцию; См.Раздел A. 2.2.4 [Вызов Variadics]. Единственое различие находится в том, как Вы определяете их. Прежде всего Вы должны использовать синтаксис не-прототипа старого стиля, примерно так:
tree build (va_alist) va_dcl {
Во-вторых, Вы должны дать va_start только один аргумент, примерно так:
va_list p; va_start (p);
Вот специальные макрокоманды, используемые для определения variadic функций старого стиля:
va_alist (макрос)
Эта макрокоманда замещает список имени аргумента, требуемый в variadic функции.
va_dcl (макрос)
Эта макрокоманда объявляет неявный аргумент или аргументы для variadic функции.
void va_start (va_list ap) (макрос)
Эта макрокоманда, как определено в " varargs.h ", инициализирует ap переменную указателя аргумента, чтобы указывать на первый аргумент текущей функции.
Другие макрокоманды аргумента, va_arg и va_end, те же самые в " varargs.h " как и в " stdarg.h "; см. Раздел A. 2.2.5 [Макросы Аргумента].
Это не работает, чтобы включить, и " varargs.h " и " stdarg.h " в той же самой трансляции; они определяют va_start противоречии способами.28.3 A.3 Константа - Нулевой Указатель
Пустой указатель, как гарантируют, не укажет на любой реальный объект. Вы можете назначать ее для любой переменной указателя, так как она имеет тип void *. Обычный способ записи пустого указателя - NULL.
void * NULL (макрос)
Это - пустой указатель.
Вы можете также использовать 0 или (void *) 0 как нулевые указатели, но использование NULL более чистое, потому что это делает цель константы более очевидной.
Если Вы используете NULL как аргумент функции, то для полной переносимости, Вы должен удостовериться, что функция имеет объявление прототипа. Иначе, если целевая машина имеет два различных представления указателя, компилятор не будет знать которое представление использовать для этого аргумента. Вы можете избежать этой проблемы, явно приводя константу к соответствующему типу указателя, но мы рекомендуем добавить прототип для функции, которую Вы вызываете.28.4 A.4 Важные Типы Данных
Результат вычитания двух указателей на C - всегда целое число,но точный тип данных изменяется от компилятора к компилятору. Аналогично, тип данных результата sizeof также изменяется между компиляторами. ANSI определяет стандартные побочные результаты исследования для этих двух типов, так что Вы можете обратиться к ним в переносимом режиме. Они определены в заглавном файле " stddef.h ".
ptrdiff_t (тип данных)
Это - целочисленный со знаком тип результата вычитания двух указателей. Например, с объявлением char *p1*, p2;, выражение p2 - p1 имеет тип ptrdiff_t.
Это будет возможно один из стандартных целочисленных со знаком типов (short int, int или long int), но мог бы быть нестандартный тип, который существует только для этой цели.
size_t (тип данных)
Это - целочисленный беззнаковый тип, используемый, чтобы представить размеры объектов. Результат оператора sizeof имеет этот тип, и функции типа malloc (см. Раздел 3.3 [Беспрепятственное Резервирование]) и memcpy (см. Раздел 5.4 [Копирование и конкатенация]) принимают аргументы этого типа, чтобы определить объектные размеры.
Примечание Использования: size_t - привилегированный способ объявить любые аргументы или переменные, которые содержат размер объекта.
В системе GNU size_t эквивалентен или unsigned int или unsigned long int. Эти типы имеют идентичные свойства в системе GNU, и для большинства целей, Вы можете использовать их неизменяя. Однако, они различны как типы данных, что дает различие в некоторых контекстах.
Примечание Совместимости: Реализации C перед появлением ANSI C вообще использовали unsigned int для представления объектных размеров и int для результатов вычитания указателей. Они не обязательно определяли или size_t или ptrdiff_t. Системы UNIX определяли size_t, в " sys/types.h ", но определение было обычно знаковым типом.28.5 A.5 Размеры Типов Данных
Большинство времени, если Вы выбираете соответствующий тип данных для каждого объекта в вашей программе, Вы не должны иметь отношение к тому, как они представляются или сколько битов они используют. Когда Вы нуждаетесь в такой информации, Язык C непосредственно не обеспечивает способ получить это. Заглавные файлы " limits.h " и " float .h " содержит макрокоманды, которые дают Вам эту информацию.A.5.1 Вычисление Ширины Целого
Наиболее общая причина, по которой программа должна знать, сколько битов находятся в целочисленном типе - для использования массива long int как битового вектора. Вы можете обращаться к биту с индексом n как vector[n / LONGBITS] & (1 << (n % LONGBITS)), если Вы определяете LONGBITS как число битов в long int.
Не имеется никакого оператора в Языке C, который может давать Вам число битов в целочисленном типе данных. Но Вы можете вычислять его из макрокоманды CHAR_BIT, определенный в заглавном файле " limits.h ".
CHAR_BIT Это - число битов в char; восемь, на большинстве систем. Значение имеет тип int.
Вы можете вычислять число битов в любом типе данных примерно так:
sizeof (type) * CHAR_BIT
A.5.2 Промежуток Целого Типа
Предположите, что Вы должны сохранить целочисленное значение, которое может быть в промежутке от нуля до одного миллиона. Который тип является самым маленьким типом, который Вы можете использовать? Не имеется никакого общего правила; это зависит от компилятора C и целевой машины. Вы можете использовать " MIN " и " МАX " макрокоманды в " limits.h " чтобы определить, который тип будет работать.
Каждый целочисленный со знаком тип имеет пару макрокоманд, которые дают самые маленькие и самые большие значения, которые он может содержать. Каждый целочисленный беззнаковый тип имеет одну такую макрокоманду, для максимального значения; минимальное значение, конечно - нуль.
Значения этих макрокоманд - все целочисленные постоянные выражения. " MAX " и " MIN " макрокоманды для char и short int имеют значения типа int. " MAX " и " MIN " макрокоманды для других типов имеет значения того же самого типа, описанного макрокомандой таким образом, ULONG_MAX имеет unsigned long int тип.
SCHAR_MIN
Это - минимальное значение, которое может представляться signed char.
SCHAR_MAX UCHAR_MAX
Это - максимальные значения, которые могут представлять signed char и char без знака, соответственно.
CHAR_MIN
Это - минимальное значение, которое может представлять char. Оно равно SCHAR_MIN, если char - со знаком, или нуль иначе.
CHAR_MAX
Это - максимальное значение, которое может представлять char. Оно равно SCHAR_MAX, если char - со знаком, или UCHAR_MAX иначе.
SHRT_MIN
Это - минимальное значение, которое может представлять signed short int. На большинстве машин, на которых библиотека GNU C выполняется, короткие целые - 16 битовые.
SHRT_MAX USHRT_MAX
Это - максимум, который может представляться signed short int и unsigned short int, соответственно.
INT_MIN
Это - минимальное значение, которое может представляться signed int. На большинстве машин, на которых система GNU C выполняется, int - 32 битовый.
INT_MAX UINT_MAX
Это - максимум, который может представляться, соответственно, signed int типом и unsigned int типом.
LONG_MIN
Это - минимальное значение, которое может представляться signed long int. На большинстве машин, на которых система GNU C выполняется, длинные целые числа - 32 битовые, того же самого размера как int.
LONG_MAX ULONG_MAX
Это - максимум, который может представляться signed long int и unsigned long int, соответственно.
LONG_LONG_MIN
Это - минимальное значение, которое может представляться signed long long int. На большинстве машин, на которых система GNU C выполняется, длинные длинные целые числа - 64 битовые.
LONG_LONG_MAX ULONG_LONG_MAX
Это - максимум, который может представляться signed long long int и unsigned long long int, соответственно.
WCHAR_MAX
Это - максимальное значение, которое может представляться wchar_t. См. Раздел 18.4 [Введение в Расширенные Символы].
Заглавный файл " limits.h " также определяет некоторые дополнительные константы, которые операционная система и файловая система ограничивает. Эти константы описаны в Главе 27 [Конфигурация Системы].A.5.3 Плавающий Тип
Специфическое представление чисел с плавающей запятой изменяется от машины к машине. Потому что числа с плавающей запятой представляются внутренне как приблизительные количества, алгоритмы для управления данными с плавающей запятой часто должны принять во внимание точные характеристики конкретной машины.
Заглавный файл " float .h " описывает формат, используемый вашей машиной.
A.5.3.1 Концепции Представления Чисел С Плавающей Запятой
Этот раздел представляет терминологию для описания представлений с плавающей запятой.
Вы возможно уже знакомы с большинством этих понятий в терминах научного или экспоненциального представления чисел для чисел с плавающей запятой. Например, число 123456.0 могло бы быть выражено в экспоненциальном представлении как 1.23456e + 05, укорченная запись, указывающая, что мантисса 1.23456 умножена на основание 10 в степени 5.
Более формально, внутреннее представление числа са плавающей запятой может характеризоваться в терминах следующих параметров:
- Знак является или -1 или 1.
- Основа или основание системы счисления для возведения в степень, целое число больше чем 1. Это - константа для специфического представления.
- Экспонента, в которую основание возводится. Верхние и нижние пределы значения экспоненты - константы для специфического представления.
- Мантисса - целочисленное беззнаковое, которое является частью каждого числа с плавающей запятой.
- Точность мантиссы. Если основа представления - b, то точность - число из b цифр в мантиссе. Это - константа для специфического представления.
Снова, библиотека GNU не обеспечивает никаких средств для работы с такими аспектами представления низкого уровня.
Мантисса числа с плавающей запятой фактически представляет неявную дробь, чей знаменатель является основой в степени точности. Так как самая большая представимая мантисса на один меньше чем этот знаменатель, значение дроби - всегда строго меньше чем 1. Математическое значение числа с плавающей запятой - произведние этой дроби, знака, и основы в стпени экспоненты.
Мы говорим, что число с плавающей запятой нормализовано, если дробь по крайней мере 1/b, где b - основа. Другими словами, мантисса была слишком большая для представления, если она была бы умножена на основу. Не нормализованные числа иногда называются нестандартными; они содержат меньшее количество точности чем представление, обычно может содержать.
Если число не нормализовано, то Вы может вычитать 1 из экспоненты при умножении мантиссы на основу, и получать другое число с плавающей запятой с тем же самым значением. Нормализация состоит из выполнения этого неоднократно, пока число не нормализовано. Два различных нормализованных числа с плавающей запятой не могут быть равны в значении.A.5.3.2 Параметры с плвающей точкой
К этим макроопределениям можно обращаться, включая заглавный файл " кfloat.h " в вашей программе.
Имена макрокоманд, начинающиеся с " FLT_ " относятся к float типу, в то время как имена, начинающиеся с " DBL_ " обращаются к double типу, а имена, начинающиеся " LDBL_ " относит к long double типу. (В настоящее время GCC не поддерживает long double как отдельный тип данных, так что значения для "LDBL_ " константы равны соответствующим константам для double типа.)
Из этих макрокоманд, только FLT_RADIX, как гарантируют, будет постоянным выражением. Другие макрокоманды, перечисленные здесь не могут надежно использоваться в местах, которые требуют постоянных выражений, типа " #if " директив предварительной обработки или в размерах статических массивов.
Хотя стандарт ANSI C определяет минимальные и максимальные значения для большинства этих параметров, реализация GNU C использует любые значения, описывающие представление с плавающей запятой целевой машины. Так в принципе GNU C фактически удовлетворяет требования ANSI C только, если целевая машина подходящая. Практически, все машины, в настоящее время обеспечиваемые, подходящие.
FLT_ROUNDS
Это значение характеризует режим округления для сложения с плавающей запятой. Следующие значения указывают режимы округления:
-1 Режим - неопределенный 0 Округление к нулю. 1 Округление к самому близкому числу. 2 Округление к положительной бесконечности. 3 Округление к отрицательной бесконечности.
Любое другое значение представляет машинно-зависимый нестандартный режим округления.
На большинстве машин, значение 1, в соответствии с стандартом ИИЭРа для чисел с плавающей запятой.
Вот таблица, показывающая, как некоторые значения округляются для каждого возможного значения FLT_ROUNDS.
0 1 2 3 1.00000003 1.0 1.0 1.00000012 1.0 1.00000007 1.0 1.00000012 1.00000012 1.0 -1.00000003 -1.0 -1.0 -1.0 -1.00000012 -1.00000007 -1.0 -1.00000012 -1.0 -1.00000012 FLT_RADIX
Это - значение основы или основания системы счисления. Оно, как гарантируют, будет постоянным выражением, в отличие от других макрокоманд, описанных в этом разделе. Значение - 2 на всех машинах, о которых мы знаем за исключением IBM 360 и производных.
FLT_MANT_DIG
Это - число (FLT_RADIX-ичных) цифр в мантиссе с плавающей запятой для типа данных float. Следующее выражение производит 1.0 (даже если математически этого не должно быть) из-за ограниченного числа цифр мантиссы:
float radix = FLT_RADIX; 1.0f + 1.0f / radix / radix / . . . / radix
где основание системы счисления появляется FLT_MANT_DIG раз.
DBL_MANT_DIG LDBL_MANT_DIG
Это - число (FLT_RADIX-ичных) цифр в мантиссе с плавающей запятой для double и long double, соответственно.
FLT_DIG
Это - число десятичных цифр точности для типа данных float. Технически, если p и b - точность и основа (соответственно) для представления, то десятичная точность q - максимальное число десятичных цифр таких, что любое число с плавающей запятой с q-основанием из 10 цифр может быть округлено к числу с плавающей запятой с p-основанием из b цифр и обратно снова, без изменения q десятичных цифр.
Значение этой макрокоманды, как предполагается, является по крайней мере 6, для удовлетворения ANSI C.
DBL_DIG LDBL_DIG
Они подобны FLT_DIG, но для double и long double, соответственно. Значения этих макрокоманд, как предполагается, являются по крайней мере 10.
FLT_MIN_EXP
Это - самое маленькое возможное значение экспоненты для float типа. Более точно - минимальное отрицательное целое число такое, что значение FLT_RADIX в этой степени минус 1, может представляться как нормализованное число с плавающей запятой float типа.
DBL_MIN_EXP LDBL_MIN_EXP
Они подобны FLT_MIN_EXP, но для double и long double, соответственно.
FLT_MIN_10_EXP
Это - минимальное отрицательное целое число такое, что 10 в этой степени минус 1, может представляться как нормализованное число с плавающей запятой float типа. Оно, как предполагается, является -37 или даже меньше.
DBL_MIN_10_EXP LDBL_MIN_10_EXP
Они подобны FLT_MIN_10_EXP, но для double и long double, соответственно.
FLT_MAX_EXP
Это - самое большое возможное значение экспоненты для float типа. Более точно, это - максимальное положительное целое число такое, что значение FLT_RADIX в этой степени минус 1, может представляться как число с плавающей запятой float типа.
DBL_MAX_EXP LDBL_MAX_EXP
Они подобны FLT_MAX_EXP, но для double и long double, соответственно.
FLT_MAX_10_EXP
Это - максимальное положительное целое число такое, что 10 в этой степени минус 1, может представляться как нормализованное число с плавающей запятой float типа. Оно, как предполагается, является по крайней мере 37.
DBL_MAX_10_EXP LDBL_MAX_10_EXP
Они подобны FLT_MAX_10_EXP, но для double и long double, соответственно.
FLT_MAX
Значение этой макрокоманды - максимальное число, представимое в float типе. Предполагается что оно по крайней мере 1E + 37. Значение имеет float тип.
Самое маленькое представимое число - -FLT_MAX.
DBL_MAX LDBL_MAX
Они подобны FLT_MAX, но для double и long double, соответственно. Тип значения макрокоманды - такой же как тип, который она описывает.
FLT_MIN
Значение этой макрокоманды - минимальное нормализованное положительное число с плавающей запятой, которое является представимым в float типе. Предполагается быть не больше, чем 1E-37.
DBL_MIN LDBL_MIN
Они подобны FLT_MIN, но для double и long double, соответственно. Тип значения макрокоманды - такой же как тип, который она описывает.
FLT_EPSILON
Это - минимальное положительное число с плавающей запятой float типа такое, что 1.0 + FLT_EPSILON != 1.0 является истинным. Предполагается быть не больше чем 1E-5.
DBL_EPSILON LDBL_EPSILON
Они подобны FLT_EPSILON, но для double и long double, соответственно. Тип значения макрокоманды - такой же как тип, который она описывает. Значения, как предполагается, не больше чем 1E-9.A.5.3.3 ИИЭР(IEEE) числа с плавающей запятой
Вот пример, показывающий, как размеры типа с плавающей запятой выступают в поддержку наиболее общего представления с плавающей запятой, заданного ИИЭР Стандартом для Двоичной Арифметики С плавающей запятой (ANSI/IEEE Std 754-1985). Почти все компьютеры, разработанные начиная с 1980 ого используют этот формат.
ИИЭР представление float с одинарной точностью использует основание 2. Имеется знаковый разряд, мантисса с 23 битами плюс один скрытый бит (так что общая точность - 24 2-ичные цифры), и экспонента с 8 битами, которая может представлять значения в промежутке от -125 до 128, включительно.
Так, для реализации, которая использует, это представление для типа данных float, соответствующие значения для соответствующих параметров:
FLT_RADIX 2 FLT_MANT_DIG 24 FLT_DIG 6 FLT_MIN_EXP -125 FLT_MIN_10_EXP -37 FLT_MAX_EXP 128 FLT_MAX_10_EXP +38 FLT_MIN 1.17549435E-38F FLT_MAX 3.40282347E+38F FLT_EPSILON 1.19209290E-07F
Имеются значения для типа данных double:
DBL_MANT_DIG 53 DBL_DIG 15 DBL_MIN_EXP -1021 DBL_MIN_10_EXP -307 DBL_MAX_EXP 1024 DBL_MAX_10_EXP 308 DBL_MAX 1.7976931348623157E+308 DBL_MIN 2.2250738585072014E-308 DBL_EPSILON 2.2204460492503131E-016
A.5.4 Величина Смещения Поля Структуры
Вы можете использовать offsetof, чтобы измерить расположение внутри структурного типа специфического элемента структуры.
size_t offsetof (type, member) (макрос)
Он расширяется до целочисленного постоянного выражения, которое является смещением элемента структуры, именованного member в структурном типе type. Например, offsetof (struct s, elem) - смещение, в байтах, элемента elem в struct s.
Эта макрокоманда не будет работать, если элемент - битовое поле; Вы получаете ошибку из компилятора C в этом случае.