Если вместе с параметром — c передается простая команда (без конвейера или последовательности), некоторые командные оболочки (включая bash) для повышения эффективности вызывают команду exec напрямую, не создавая дочернего процесса. Рисунок 27.2 не совсем точно описывает оболочки, выполняющие такую оптимизацию, поскольку в их случае процессов будет два — вызывающий и команда sleep. Однако это не отменяет замечаний о том, как функция system() должна заниматься обработкой сигналов.
Все процессы, показанные на рис. 27.2, являются частью активной группы процессов в терминале (группы процессов будут подробно рассмотрены в разделе 34.2). Следовательно, если набрать в терминале последовательсть
Каким образом вызывающий процесс и выполняемая команда должны реагировать на эти сигналы? Стандарт SUSv3 гласит следующее.
• Пока выполняется команда, сигналы SIGINT и SIGQUIT должны игнорироваться вызывающим процессом.
• Потомок должен обрабатывать сигналы SIGINT и SIGQUIT так, как это делает вызывающий процесс при использовании вызовов fork() и exec(); то есть диспозиция обрабатываемых сигналов сбрасывается к значениям по умолчанию, а диспозиции других сигналов остаются без изменений.
Рис. 27.2.
Работа с сигналами в соответствии со стандартом SUSv3 является наиболее разумным подходом, и вот почему.
• Не следует отвечать на эти сигналы сразу в двух процессах, поскольку, с точки зрения пользователя, это может привести к неожиданному поведению приложения.
• Аналогично эти сигналы нельзя игнорировать в процессе, выполняющем команду, и при этом оставлять для них диспозицию по умолчанию в вызывающем процессе. Это позволило бы пользователю, например, завершить вызывающий процесс, но оставить работать запущенную им команду. Это также идет вразрез с тем фактом, что во время выполнения команды, переданной в функцию system(), вызывающий процесс на самом деле теряет управление (то есть блокируется в вызове waitpid()).
• Команда, выполняемая функцией system(), может оказаться интерактивным приложением, и в этом случае имеет смысл позволить ей реагировать на сигналы, генерируемые терминалом.
Стандарт SUSv3 требует, чтобы сигналы SIGINT и SIGQUIT обрабатывались вышеописанным образом, но отмечает, что это может привести к нежелательным последствиям в программах, которые применяют функцию system() в фоновом режиме для выполнения некоторых задач. Нажатие Ctrl+C или Ctrl+\ во время выполнения такой команды приведет к завершению только потомка system(), тогда как само приложение продолжит работу (неожиданно для пользователя). Программа, которая использует функцию system() таким образом, должна проверять код завершения, которая та возвращает, и предпринимать соответствующие меры в случае завершения команды по сигналу.
В листинге 27.9 приводится реализация функции system(), отвечающая вышеописанным правилам. Обратите внимание на следующие замечания относительно этой реализации.
• Как было отмечено ранее, если в аргументе command передан нулевой указатель, функция system() должна вернуть ненулевое значение или 0 в зависимости от того, доступна командная оболочка или нет. Единственный надежный способ проверить эту информацию — это попытаться запустить командную оболочку. Для этого следует сделать рекурсивный вызов system() для выполнения встроенной команды:, которая ничего не делает, но всегда возвращает код успешного завершения; после этого нужно проверить, равняется ли полученный результат 0
• В родительском процессе (вызывающем system()) нужно блокировать только сигнал SIGCHLD