Если вместе с параметром — c передается простая команда (без конвейера или последовательности), некоторые командные оболочки (включая bash) для повышения эффективности вызывают команду exec напрямую, не создавая дочернего процесса. Рисунок 27.2 не совсем точно описывает оболочки, выполняющие такую оптимизацию, поскольку в их случае процессов будет два — вызывающий и команда sleep. Однако это не отменяет замечаний о том, как функция system() должна заниматься обработкой сигналов.

Все процессы, показанные на рис. 27.2, являются частью активной группы процессов в терминале (группы процессов будут подробно рассмотрены в разделе 34.2). Следовательно, если набрать в терминале последовательсть прерывания или выхода, то все процессы получат соответствующий сигнал. Сигналы SIGINT и SIGQUIT игнорируются командной оболочкой, которая ждет своих потомков, но по умолчанию приводят к завершению вызывающей программы и процесса sleep.

Каким образом вызывающий процесс и выполняемая команда должны реагировать на эти сигналы? Стандарт SUSv3 гласит следующее.

• Пока выполняется команда, сигналы SIGINT и SIGQUIT должны игнорироваться вызывающим процессом.

• Потомок должен обрабатывать сигналы SIGINT и SIGQUIT так, как это делает вызывающий процесс при использовании вызовов fork() и exec(); то есть диспозиция обрабатываемых сигналов сбрасывается к значениям по умолчанию, а диспозиции других сигналов остаются без изменений.

Рис. 27.2. Организация процессов во время выполнения system("sleep 20")

Работа с сигналами в соответствии со стандартом SUSv3 является наиболее разумным подходом, и вот почему.

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

• Аналогично эти сигналы нельзя игнорировать в процессе, выполняющем команду, и при этом оставлять для них диспозицию по умолчанию в вызывающем процессе. Это позволило бы пользователю, например, завершить вызывающий процесс, но оставить работать запущенную им команду. Это также идет вразрез с тем фактом, что во время выполнения команды, переданной в функцию system(), вызывающий процесс на самом деле теряет управление (то есть блокируется в вызове waitpid()).

• Команда, выполняемая функцией system(), может оказаться интерактивным приложением, и в этом случае имеет смысл позволить ей реагировать на сигналы, генерируемые терминалом.

Стандарт SUSv3 требует, чтобы сигналы SIGINT и SIGQUIT обрабатывались вышеописанным образом, но отмечает, что это может привести к нежелательным последствиям в программах, которые применяют функцию system() в фоновом режиме для выполнения некоторых задач. Нажатие Ctrl+C или Ctrl+\ во время выполнения такой команды приведет к завершению только потомка system(), тогда как само приложение продолжит работу (неожиданно для пользователя). Программа, которая использует функцию system() таким образом, должна проверять код завершения, которая та возвращает, и предпринимать соответствующие меры в случае завершения команды по сигналу.

Улучшенная реализация функции system()

В листинге 27.9 приводится реализация функции system(), отвечающая вышеописанным правилам. Обратите внимание на следующие замечания относительно этой реализации.

• Как было отмечено ранее, если в аргументе command передан нулевой указатель, функция system() должна вернуть ненулевое значение или 0 в зависимости от того, доступна командная оболочка или нет. Единственный надежный способ проверить эту информацию — это попытаться запустить командную оболочку. Для этого следует сделать рекурсивный вызов system() для выполнения встроенной команды:, которая ничего не делает, но всегда возвращает код успешного завершения; после этого нужно проверить, равняется ли полученный результат 0 . Того же результата можно было бы достичь путем запуска консольной команды exit 0. Заметьте, что для этого было бы недостаточно воспользоваться вызовом access(), чтобы проверить, существует ли файл /bin/sh и является ли он исполняемым. В среде после вызова chroot(), даже если исполняемый файл оболочки окажется на месте и он динамически скомпонован, его не получится выполнить, пока не удастся найти необходимые совместно используемые библиотеки.

• В родительском процессе (вызывающем system()) нужно блокировать только сигнал SIGCHLD , а сигналы SIGINT и SIGQUIT следует игнорировать . Однако эти действия необходимо выполнить до вызова fork(), в противном случае возникнет состояние гонки (представьте, к примеру, что потомок завершился до того, как родитель смог заблокировать SIGCHLD). Следовательно, потомок должен отменить изменения атрибутов сигнала, о чем мы поговорим чуть ниже.

Перейти на страницу:

Похожие книги