должна тем не менее вновь связать стандартный входной и выходной потоки с терминалом, в частности
/dev/tty
.
Системный вызов
dup(fd)
дублирует дескриптор файла
fd
на незанятый дескриптор файла с наименьшим номером и возвращает новый дескриптор, ссылающийся на тот же самый открытый файл. Следующая программа "присоединяет" стандартный входной поток программы к файлу:
int fd;
fd = open("file", 0);
close(0);
dup(fd);
close(fd);
Вызов
close(fd)
освобождает дескриптор файла 0 (стандартный входной поток), но, как правило, не влияет на процесс-родитель. Здесь приведена наша версия
system
для диалоговых программ, использующая
progname
для вывода сообщений об ошибках. Вам следует игнорировать те части функции, которые имеют дело с сигналами (мы вернемся к ним позднее).
/*
* Safer version of system for interactive programs
*/
#include <signal.h>
#include <stdio.h>
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
представляет собой имя функции, которая уже должна быть описана в том же самом исходном файле, то функция будет вызвана, когда возникнет сигнал. Это практикуется довольно часто, чтобы программа могла "подчищать" неоконченные работы перед своим завершением, например удалять временный файл: