信号和睡眠无法正常工作
Signals and sleep not working properly
我有一项大学作业要完成,它几乎完成了,大部分工作正常,只有一个方面不工作,我不太确定如何解决它..
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 次。
我必须改变什么?
您的方法不太可能产生有效的程序。
首先,使用信号处理程序,只要捕获到 SIGINT
信号,它只设置一个全局变量(volatile sig_atomic_t
类型)。不要尝试从信号处理程序打印任何内容,因为标准 I/O 不是异步信号安全的。
其次,使用sigaction()
安装信号处理器。使用零标志。换句话说,在安装处理程序时不要使用 SA_RESTART
标志。这样,当信号传递给您的处理程序时,它将中断大多数系统调用(包括睡眠)。 (函数将 return -1 与 errno == EINTR
。)
这样,在您的 main()
安装信号处理程序后,您可以让它打印指令,并进入循环。
在循环中,清除中断标志,休眠几秒。多久都没关系。如果在睡眠完成后没有设置中断标志,continue
(在循环开始时)。
否则,你知道用户按下了Ctrl+C。所以,清除中断标志,再睡三秒钟。如果标志在睡眠完成后设置,您知道用户提供了另一个 Ctrl+C,您可以跳出循环。否则,您只需再次继续循环。
从技术上讲,这里存在竞争条件,因为用户可能会连续按两次 Ctrl+C,速度足够快,因此main()
只能看到一个。
不幸的是,增量(flag++
)不是原子的;编译器或硬件实际上可能会执行 temp = flag; temp = temp + 1; flag = temp;
并且信号可能会在第三步之前传递,导致信号处理程序和 main()
看到 flag
.[=80= 的不同值]
一种解决方法是使用 C11 原子(如果体系结构和 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. The signal handler can increment it (using sem_post()
) safely. In main()
, you can use sem_timedwait()
在有限的时间内等待信号,然后 sem_trywait()
将其递减。
第三种方法是使用 sigtimedwait()
超时捕获 main()
中的信号,无需任何信号处理程序。我相信最后一个是最健壮和最容易实现的,所以这就是我在实际应用程序中使用的。
事实证明,还有另一种方法可以实现这一点,即在三秒内响应连续两次 Ctrl+C 按下,没有留下任何讨厌的角落案例。
这不完全是对 OP 的要求,因此不是他们练习的有效答案,但否则这将是一个很好的方法。
这个想法是使用 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
,我们还设置完成标志,并准备在一秒钟内发生警报,以确保我们捕捉到状态变化最多一秒。
收到SIGALRM
时,中断计数清零。如果是两个或更多,完成标志也被设置。
在main()
中,我们只检查done
和interrupted
,从不修改它们。这避免了我担心的极端情况。
在最坏的情况下,延迟一秒退出,如果第二个Ctrl+C在我们检查后交付,但就在我们睡觉之前。 handle_sigint()
中的 alarm(1)
就是针对这种情况。
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()
),这将是一个非常有趣的练习。
我有一项大学作业要完成,它几乎完成了,大部分工作正常,只有一个方面不工作,我不太确定如何解决它.. 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 次。 我必须改变什么?
您的方法不太可能产生有效的程序。
首先,使用信号处理程序,只要捕获到 SIGINT
信号,它只设置一个全局变量(volatile sig_atomic_t
类型)。不要尝试从信号处理程序打印任何内容,因为标准 I/O 不是异步信号安全的。
其次,使用sigaction()
安装信号处理器。使用零标志。换句话说,在安装处理程序时不要使用 SA_RESTART
标志。这样,当信号传递给您的处理程序时,它将中断大多数系统调用(包括睡眠)。 (函数将 return -1 与 errno == EINTR
。)
这样,在您的 main()
安装信号处理程序后,您可以让它打印指令,并进入循环。
在循环中,清除中断标志,休眠几秒。多久都没关系。如果在睡眠完成后没有设置中断标志,continue
(在循环开始时)。
否则,你知道用户按下了Ctrl+C。所以,清除中断标志,再睡三秒钟。如果标志在睡眠完成后设置,您知道用户提供了另一个 Ctrl+C,您可以跳出循环。否则,您只需再次继续循环。
从技术上讲,这里存在竞争条件,因为用户可能会连续按两次 Ctrl+C,速度足够快,因此main()
只能看到一个。
不幸的是,增量(flag++
)不是原子的;编译器或硬件实际上可能会执行 temp = flag; temp = temp + 1; flag = temp;
并且信号可能会在第三步之前传递,导致信号处理程序和 main()
看到 flag
.[=80= 的不同值]
一种解决方法是使用 C11 原子(如果体系结构和 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. The signal handler can increment it (using sem_post()
) safely. In main()
, you can use sem_timedwait()
在有限的时间内等待信号,然后 sem_trywait()
将其递减。
第三种方法是使用 sigtimedwait()
超时捕获 main()
中的信号,无需任何信号处理程序。我相信最后一个是最健壮和最容易实现的,所以这就是我在实际应用程序中使用的。
事实证明,还有另一种方法可以实现这一点,即在三秒内响应连续两次 Ctrl+C 按下,没有留下任何讨厌的角落案例。
这不完全是对 OP 的要求,因此不是他们练习的有效答案,但否则这将是一个很好的方法。
这个想法是使用 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
,我们还设置完成标志,并准备在一秒钟内发生警报,以确保我们捕捉到状态变化最多一秒。
收到SIGALRM
时,中断计数清零。如果是两个或更多,完成标志也被设置。
在main()
中,我们只检查done
和interrupted
,从不修改它们。这避免了我担心的极端情况。
在最坏的情况下,延迟一秒退出,如果第二个Ctrl+C在我们检查后交付,但就在我们睡觉之前。 handle_sigint()
中的 alarm(1)
就是针对这种情况。
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()
),这将是一个非常有趣的练习。