2016-04-03 5 views
2

У меня есть задание сделать, для университета это почти сделано, большинство работает, есть только один аспект, который не работает, и я не совсем уверен, как его исправить .. Objetivo заключается в том, чтобы проблема дождалась 2 ctrl + C и закрылась. Но если он поймает первый ctrl + C и пройдет более 3 секунд, программа должна забыть об этом и снова ждать еще 2 ctrl + C. Это, как я это делаю:Сигналы и сон не работают должным образом

/*Problem 2. Write a program that sleeps forever until the user interrupts it twice with a Ctrl-C, and 
then exits. Once the first interrupt is received, tell the user: “Interrupt again to exit.”. The first 
interrupt should be forgotten 3 seconds after it has occurred. Additionally, the program should block 
the SIGQUIT signal, and ignore the SIGTSTP signal. The program should start by printing “Interrupt 
twice with Ctrl-C to quit.” on the screen.*/ 

#include <stdlib.h> 
#include <stdio.h> 
#include <unistd.h> 
#include <signal.h> 
#include <sys/types.h> 

//handler to catch the first ctrl_c and ask user to do it another time(no reference to time limit) 
void ctrl_c(int sig){ 
    signal(sig, SIG_IGN); 
    printf("\nInterrupt again to exit.\n"); 
} 

//handler for second ctrl_c. If called, program will end 
void second_catch(int sig){ 
    if(sig == SIGINT){ 
     printf("\n"); 
     exit(0); 
    } 
} 

//handler to always ignore ctrl_z 
void ctrl_z(int sig){ 
    signal(sig, SIG_IGN); 
} 

int main(){ 
    //blocking SIQUIT (Ctrl+\) using series of command to change the mask value of SIGQUIT 
    sigset_t sg; 
    sigemptyset (&sg); 
    sigaddset(&sg, SIGQUIT); 
    sigprocmask(SIG_BLOCK, &sg, NULL); 

    //installing handler to ignore SIGTSTP (Ctrl+Z) 
    signal(SIGTSTP, ctrl_z); 

    //two part SIGINT handling 
    printf("\nInterrupt twice with Ctrl+C to quit.\n"); 
    signal(SIGINT, ctrl_c); //first handler install 

    do{ //cycle for second hanler install and 3 second timer 
     if(sleep(3) == 0){ 
      main(); //if second_catch handler is not called within 3 seconds,  program will restart 
     } 
     else { 
      signal(SIGINT, second_catch); //upon call, program will end 
     } 
    }while(1); 

    return 0; 
} 

То, что происходит в том, что он держит переустановку через 3 секунды, в цикле .. Но я хочу, чтобы сбросить только 1 раз, после того, как я нажимаю Ctrl + C и 3 секунды прошло .. Что я должен изменить?

+0

Не спать, используйте 'clock()', чтобы узнать прошедшее время с момента последнего события Ctrl-C. Вам нужен только один обработчик событий. Тогда ваша программа должна работать правильно, если, скажем, вы нажимаете Ctrl-C три раза с интервалом 5 секунд, 1 секунду. Программе не нужно «забывать» что угодно, но помните отметку времени последнего события. –

+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()' для наносекундной точности. –

+0

@NominalAnimal вопрос не различает. –

ответ

2

Ваш подход вряд ли приведет к рабочей программе.

Во-первых, используйте обработчик сигнала, который устанавливает глобальную переменную (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()), это было бы удивительно интересное упражнение.

+0

Как поточно-безопасная атомика важна для однопоточного асинхронного кода? – EOF

+0

@EOF: Поскольку '++ flag' и' --flag' не являются атомарными и могут быть прерваны сигналом после 'temp = flag; temp = temp + 1; ', но перед' flag = temp', что приводит к некорректному значению 'flag'. Пользователь может набирать Ctrl + C быстро дважды подряд. Чтобы справиться с этим, «установка флага» означает, что обработчик сигнала увеличивает его, а «очистка» (после первого, но до окончания трехсекундного льготного периода) означает его уменьшение. Он только обнуляется в начале каждой итерации цикла. Я объясню это лучше, с лучшим обходным решением. –

+0

Ну, к сожалению, 'sig_atomic_t' не относится к типам' _Atomic', подходящим для функций 'atomic_fetch_op()'. Я предлагаю переосмыслить ваш подход и уточнить, что вы думаете о том, что программа действительно должна делать. – EOF