我有一项任务要做,对于大学来说,它几乎已经完成,大多数工作,只有一个方面不起作用,我不太确定如何解决它.. 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秒后只重置一次.. 我必须改变什么?
答案 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()
中,我们只会检查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
变为零,然后,如果我们首先检查interrupted
并且它是1,那么任何其他中断都会设置done
,我们将会捕获它。所以,{{1这里正确检查是否正确。)
如上所述,为interrupted == 1 && done == 0
指定的持续时间实际上并不重要,因为无论如何它都会被信号传递中断。像十秒这样的东西应该没问题,
nanosleep()
如果讲师推荐了POSIX.1函数(struct timespec naptime = { .tv_sec = 10, .tv_nsec = 0L };
,sigaction()
),那么这将是令人惊讶的有趣练习。