Ваш подход вряд ли приведет к рабочей программе.
Во-первых, используйте обработчик сигнала, который устанавливает глобальную переменную (volatile sig_atomic_t
) всякий раз, когда вызывается сигнал SIGINT
. Не пытайтесь печатать что-либо из обработчика сигнала, поскольку стандартный ввод-вывод не является безопасным для асинхронного сигнала.
Во-вторых, используйте sigaction()
для установки обработчика сигналов. Используйте нулевые флаги. Другими словами, НЕ используйте флаг SA_RESTART
при установке обработчика. Таким образом, когда сигнал передается вашему обработчику, он прервет большинство системных вызовов (включая спальные). (Функции возвратят -1 с errno == EINTR
.)
Таким образом, после того, как ваш main()
установил обработчик сигнала, вы можете распечатать инструкцию и ввести в цикл.
В цикле очистить флаг прерывания и спящий режим в течение нескольких секунд. Неважно, как долго. Если флаг прерывания не установлен после завершения сна, continue
(в начале цикла).
В противном случае, вы знаете, что пользователь нажал Ctrl +C. Итак, очистите флаг прерывания и спать еще три секунды. Если флаг установлен после завершения сна, вы знаете, что пользователь предоставил другому Ctrl + C, и вы можете выйти из цикла. В противном случае вы снова продолжите цикл.
Технически, есть состояние гонки здесь, так как пользователь может нажать Ctrl + C дважды подряд, достаточно быстро, так что main()
видит только один.
К сожалению, приращения (flag++
) не являются атомарными; компилятор или аппаратное обеспечение действительно могут сделать temp = flag; temp = temp + 1; flag = temp;
, и сигнал может быть доставлен непосредственно перед третьим шагом, что приведет к обработчику сигнала и main()
, видя различные значения flag
.
Один из способов, который заключается в использовании C11 Atomics (если архитектура и библиотека C обеспечивают их, в <stdatomic.h>
, с макро ATOMIC_INT_LOCK_FREE
определены): volatile atomic_int flag;
для флага, __atomic_add_fetch(&flag, 1, __ATOMIC_SEQ_CST)
, чтобы увеличить его и __atomic_sub_fetch(&flag, 1, __ATOMIC_SEQ_CST)
декрементируют.
Другим способом было бы использовать POSIX semaphore. Обработчик сигнала может увеличивать его (используя sem_post()
) безопасно.В main()
вы можете использовать sem_timedwait()
, чтобы ждать сигнала в течение ограниченного времени, и sem_trywait()
, чтобы уменьшить его.
Третий способ заключается в использовании sigtimedwait()
для улавливания сигнала в main()
с тайм-аутом без каких-либо обработчиков сигналов. Этот последний, я считаю, самый надежный и простой в реализации, так что это то, что я буду использовать в реальном приложении.
Оказывается, что есть еще один способ для достижения этой цели, один, который реагирует на два последовательных Ctrl +C машины в течение трех секунд, не оставляя никаких неприятных случаев угловых.
Это не то, что было задано ОП, и, как таковой, не является верным ответом на их осуществление, но в противном случае это был бы хороший подход.
Идея заключается в том, чтобы использовать alarm()
и SIGALRM
обработчик, и два sig_atomic_t
флаги: один, который подсчитывает Ctrl + C нажатий клавиш, и тот, который помечает случай, когда было два в три-секундного периода ,
К сожалению, sleep()
не могут быть использованы в данном случае - вы должны использовать вместо nanosleep()
- как sleep()
, alarm()
и SIGALRM
обработки сигнала могут мешать друг другу.
По существу, мы используем
#define INTR_SECONDS 3
static volatile sig_atomic_t done = 0;
static volatile sig_atomic_t interrupted = 0;
static void handle_sigalrm(int signum)
{
if (interrupted > 1)
done = 1;
interrupted = 0;
}
static void handle_sigint(int signum)
{
interrupted++;
if (interrupted > 1) {
done = 1;
alarm(1);
} else
alarm(INTR_SECONDS);
}
handle_sigalrm()
устанавливается как SIGALRM
обработчика, с SIGINT
в маске сигнала; handle_sigint()
установлен как обработчик SIGINT
, с сигнальной маской SIGALRM
. Таким образом, два обработчика сигналов блокируют друг друга и не прерываются друг другом.
Когда получено первое SIGINT
, сигнал тревоги загрунтован. Если это второй (или третий и т. Д.) SIGINT
без промежуточного SIGALRM
, мы также установим флаг done и запустим аварийный сигнал в течение одной секунды, чтобы гарантировать, что мы перехватим изменение состояния не более одной секунды.
Когда получено SIGALRM
, счет прерывания обнуляется. Если это было два или более, также установлен флаг done.
В main()
мы проверяем только done
и interrupted
, не изменяя их. Это позволяет избежать ситуаций, в которых я беспокоился.
В худшем случае, есть одна вторая задержка бросить курить, если второй Ctrl + C поставляется после проверки, но прежде чем мы спать. alarm(1)
в handle_sigint()
для этого случая.
Петля в main
затем просто
while (!done) {
while (!done && !interrupted)
nanosleep(&naptime, NULL);
if (done)
break;
printf("Ctrl+C again to quit!\n");
fflush(stdout);
while (interrupted == 1 && !done)
nanosleep(&naptime, NULL);
}
Первый внутренний цикл только спит, когда он был в течение трех секунд после последнего SIGINT
(или мы никогда не получили один).Он будет прерван как SIGINT
, так и SIGALRM
, поэтому продолжительность не имеет значения.
if (done) break;
дело просто не печатает ничего, если пользователь имел молнии руки и напечатал Ctrl + C дважды очень быстро.
Второй внутренний цикл только спать, когда нас ждет второй Ctrl + C. Оба они тоже будут прерваны, поэтому продолжительность здесь не имеет значения. Обратите внимание, однако, что мы хотим сначала проверить interrupted
, чтобы обеспечить надежную фиксацию всех изменений. (Если мы сначала проверили done
, мы можем быть прерваны до того, как мы проверим interrupted
, и теоретически возможно, что done
изменится на ненулевое значение и interrupt
на ноль, а затем на 1. Среднее время, но если мы сначала проверим interrupted
, и это 1, любые дополнительные прерывания будет просто установить done
, который мы будем ловить. Так, interrupted == 1 && done == 0
правильная проверка в правильном порядке здесь.)
Как было отмечено выше, длительность указана для nanosleep()
на самом деле не важно, , так как в любом случае он будет прерван подачей сигнала. Что-то вроде десяти секунд должно быть в порядке,
struct timespec naptime = { .tv_sec = 10, .tv_nsec = 0L };
Если преподаватель рекомендовал POSIX.1 функции (sigaction()
, nanosleep()
), это было бы удивительно интересное упражнение.
Не спать, используйте 'clock()', чтобы узнать прошедшее время с момента последнего события Ctrl-C. Вам нужен только один обработчик событий. Тогда ваша программа должна работать правильно, если, скажем, вы нажимаете Ctrl-C три раза с интервалом 5 секунд, 1 секунду. Программе не нужно «забывать» что угодно, но помните отметку времени последнего события. –
@WeatherVane: Нет, 'clock()' не [async-signal safe] (http://man7.org/linux/man-pages/man7/signal.7.html), и он измеряет время процессора, а не настенные часы. Вместо этого используйте ['time()'] (http://man7.org/linux/man-pages/man2/time.2.html). Еще лучше использовать 'sigaction()' вместо 'signal()' и ['clock_gettime (CLOCK_REALTIME, & tspec)'] (http://man7.org/linux/man-pages/man2/clock_gettime.2.html) вместо 'time()' для наносекундной точности. –
@NominalAnimal вопрос не различает. –