信号和睡眠无法正常工作

时间:2016-04-03 16:12:45

标签: c signals sleep

我有一项任务要做,对于大学来说,它几乎已经完成,大多数工作,只有一个方面不起作用,我不太确定如何解决它.. 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秒后继续重置,在一个循环中..但是我想在我点击ctrl + c并经过3秒后只重置一次.. 我必须改变什么?

1 个答案:

答案 0 :(得分:3)

您的方法不太可能导致工作计划。

首先,使用仅在捕获volatile sig_atomic_t信号时设置全局变量(SIGINT类型)的信号处理程序。不要尝试从信号处理程序打印任何内容,因为标准I / O不是异步信号安全的。

其次,使用sigaction()安装信号处理程序。使用零标志。换句话说,安装处理程序时请勿使用SA_RESTART标志。这样,当信号传递给您的处理程序时,它将中断大多数系统调用(包括睡眠)。 (函数将使用errno == EINTR返回-1。)

这样,在main()安装了信号处理程序后,您可以让它打印指令,然后进入循环。

在循环中,清除中断标志,然后休眠几秒钟。多久没关系。如果在睡眠完成后没有设置中断标志,continue(在循环开始时)。

否则,您知道用户已按 Ctrl + C 。因此,清除中断标志,再睡3秒钟。如果在睡眠完成后设置了标志,则表示用户提供了另一个 Ctrl + C ,您可以跳出循环。否则,你只需再次继续循环。

从技术上讲,这里存在竞争条件,因为用户可能会连续两次按 Ctrl + C ,以便仅main() flag++看到一个。

不幸的是,增量(temp = flag; temp = temp + 1; flag = temp;)不是原子的;编译器或硬件实际上可能main(),信号可能在第三步之前传递,导致信号处理程序和flag看到<stdatomic.h>的不同值。

另一种方法是使用C11原子(如果体系结构和C库在ATOMIC_INT_LOCK_FREE中提供了它们,并定义了宏volatile atomic_int flag;):__atomic_add_fetch(&flag, 1, __ATOMIC_SEQ_CST)用于标记,{{ 1}}递增它,__atomic_sub_fetch(&flag, 1, __ATOMIC_SEQ_CST)递减它。

另一种方法是使用POSIX semaphore。信号处理程序可以安全地增加它(使用sem_post())。在main()中,您可以使用sem_timedwait()在有限时间内等待信号,并sem_trywait()递减信号。

第三种方法是使用sigtimedwait()main()中捕获超时信号,而不使用任何信号处理程序。我相信,最后一个是最强大和最简单的实现,因此这是我在实际应用中使用的内容。

事实证明,还有另一种方法可以实现这一目标,即在三秒内响应两个连续的 Ctrl + C ,而不会留下任何令人讨厌的角落情况。

这并不是OP所要求的,因此不是他们练习的有效答案,但这不是一个好方法。

我的想法是使用alarm()SIGALRM处理程序以及两个sig_atomic_t标记:一个计算 Ctrl + C keypresses,以及在三秒钟内有两个时标记的情况。

不幸的是,在这种情况下无法使用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,我们还设置完成标志,并在一秒钟内发出警报,以确保我们捕获状态变化最多一秒钟。

当收到SIGALRM时,中断计数归零。如果是两个或更多,则还设置完成标志。

main()中,我们只会检查doneinterrupted,而不会修改它们。这避免了我担心的角落案件。

在最糟糕的情况下,如果第二个 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(或我们从未收到过一个)超过三秒时才会休眠。它会被SIGINTSIGALRM中断,因此持续时间无关紧要。

if (done) break;案例只是避免打印任何内容,如果用户手上有闪电,并且非常快地输入 Ctrl + C 两次。

第二个内循环只在我们等待第二个 Ctrl + C 时才会睡眠。它也会被两个信号中断,因此这里的持续时间也无关紧要。但请注意,我们希望首先检查interrupted,以确保我们可靠地捕获所有更改。 (如果我们先检查done,我们可能会在检查interrupted之前被中断,理论上,done可能会变为非零,interrupt变为零,然后,如果我们首先检查interrupted并且它是1,那么任何其他中断都会设置done,我们将会捕获它。所以,{{1这里正确检查是否正确。)

如上所述,为interrupted == 1 && done == 0指定的持续时间实际上并不重要,因为无论如何它都会被信号传递中断。像十秒这样的东西应该没问题,

nanosleep()

如果讲师推荐了POSIX.1函数(struct timespec naptime = { .tv_sec = 10, .tv_nsec = 0L }; sigaction()),那么这将是令人惊讶的有趣练习。