system(s) /* run command line s */
char *s;
{
int status, pid, w, tty;
int (*istat)(), (*qstat)();
extern char *progname;
fflush(stdout);
tty = open("/dev/tty", 2);
if (tty == -1) {
fprintf(stderr, "%s: can't open /dev/tty\n", progname);
return -1;
}
if ((pid = fork()) == 0) {
close(0);
dup(tty);
close(1);
dup(tty);
close(2);
dup(tty);
close(tty);
execlp("sh", "sh", "-c", s, (char*)0);
exit(127);
}
close(tty);
istat = signal(SIGINT, SIG_IGN);
qstat = signal(SIGQUIT, SIG_IGN);
while ((w = wait(&status)) != pid && w != -1)
;
if (w == -1)
status = -1;
signal(SIGINT, istat);
signal(SIGQUIT, qstat);
return status;
}
Отметим, что /dev/tty открыта с режимом 2 — чтение и запись. С помощью dup формируются стандартный входной и выходной потоки. Здесь можно провести аналогию со сборкой системой стандартных входного и выходного потоков и потока ошибок, когда вы в нее входите. Поэтому в ваш стандартный входной поток можно писать:
$ echo hello 1>&0
hello
$
Это означает, что вам следует применить dup к дескриптору файла 2, чтобы вновь связать стандартные ввод и вывод, но открытие /dev/tty является более естественным и безопасным. Даже system имеет потенциальные проблемы: открытые файлы в вызывающей программе, такие, как tty в подпрограмме ttin программы p, будут передаваться процессу-потомку.
Смысл изложенного выше состоит не в том, что вы должны использовать нашу версию system для своих программ (она могла бы разрушить недиалоговый ed, например), а в том, чтобы понять, как управляют процессами и корректно используют примитивы; значение слова "корректно" меняется в зависимости от приложения и может быть не согласовано со стандартной реализацией system.
7.5 Сигналы и прерывания
Теперь мы рассмотрим работу с сигналами извне (такими, как прерывания) и ошибками программы. Последние возникают главным образом из-за некорректных обращений к памяти, выполнения привилегированных команд или при выполнении операций с плавающей запятой. Наиболее распространенными внешними сигналами являются прерывание, посылаемый при печати символа del, выйти, генерируемый символом FS (ctrl-\), отбой, вызываемый завершением телефонной связи, и закончить, генерируемый командой kill. Когда происходит одно из этих событий, посылается сигнал всем процессам, запущенным с того же терминала, и если не были приняты другие меры, процесс завершается. Для большинства сигналов пишется файл образа памяти, который может потребоваться при поиске ошибок (см. справочное руководство по adb(1) , sdb(1)).
Системный вызов signal изменяет действие, заданное по умолчанию. Он имеет два аргумента: номер, определяющий сигнал, и адрес функции или код, предписывающий игнорировать сигнал либо запустить процедуру, принятую по умолчанию. Файл <signal.h> содержит определения для различных аргументов. Так,
#include <signal.h>
signal(SIGINT, SIG_IGN);
Специфицирует игнорирование прерываний, тогда как
signal(SIGINT, SIG_DEL);
восстанавливает действие по умолчанию, означающее завершение процесса. В любом случае signal возвращает предыдущее значение сигнала. Если второй аргумент signal представляет собой имя функции, которая уже должна быть описана в том же самом исходном файле, то функция будет вызвана, когда возникнет сигнал. Это практикуется довольно часто, чтобы программа могла "подчищать" неоконченные работы перед своим завершением, например удалять временный файл:
#include <signal.h>
char *tempfile = "temp.xxxxxx";
main() {
extern onintr();
if (signal(SIGINT, SIG_IGN) != SIG_IGN)
signal(SIGINT, onintr);
mktemp(tempfile);
/* Process ... */
exit(0);
}
onintr() { /* почистить, если прервано */
unlink(tempfile);
exit(1);
}
Почему в main имеют место проверки и двойной вызов signal? Вспомните, что сигналы посылаются всем процессам, запущенным с данного терминала. Соответственно если программа должна быть запущена не в диалоговом режиме (с помощью &), shell делает так, что она будет игнорировать прерывания. Поэтому сигналы прерывания, посланные основным процессам, не остановят ее. Если бы эта программа началась с объявления о том, что все прерывания, которые должны быть посланы подпрограмме onintr, не принимаются во внимание, были бы сведены на нет все усилия shell защитить ее при запуске в фоновом режиме.