信号处理程序如何获取信号编号而不将参数传递给signalhandler()
中的main()
函数?
例如,在main()
内的下面的源代码中,将信号系统调用的第二个参数作为signalhandler
传递而没有信号处理程序中的任何参数但是在进入signalhandler
定义时它正在收集一个参数名称为sig_num
...
实际上怎么可能?
根据ANSI C,如果我们不传递任何参数,那么函数定义将不会收集任何参数。
请帮我解决这个问题。
#include<stdio.h>
#include<signal.h>
void signalhandler(int sig_num)
{
printf("caught signal number: %d\n", sig_num);
}
int main(void)
{
while(1)
{
printf("hello world\n");
sleep(1);
signal(SIGINT, signalhandler);
}
}
答案 0 :(得分:2)
当您将函数signalhandler
作为signal(2)
的参数时,不会调用它。原型是:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
所以你可以看到第二个参数是一个函数指针。您的函数signalhandler
将在稍后调用(当传递信号时),并且将调用它的代码将为其提供int
参数。
看看其他一些函数指针示例,以更好地理解这个回调机制。
答案 1 :(得分:2)
signal
API注册提供的函数指针,以便在收到指定信号时调用。可以针对多个信号注册相同的函数指针,因此调用时的函数将被提供接收到的信号值。
在Linux(以及我所知道的所有操作系统)中,信号传递与进程的执行是异步的,并且通知是立即的。操作系统的行为就好像它抢占程序并在程序当前正在执行的任何操作之上注入信号处理函数调用。操作系统知道传送了哪个信号,并将其作为参数传递给函数调用。
注意: 可以使用
raise
为程序生成同步信号。但是,它通常会使用OS服务将其传递给进程,而不是直接调用信号处理程序。
如果您使用调试器并在信号处理程序中设置断点并传递适当的信号,您可能会看到回溯将显示操作系统正在注入信号处理程序。
例如,考虑一下程序:
void signalhandler (int sig)
{
write(2, "signal!\n", 8);
}
void foo (void)
{
for (;;) {}
}
int main (void)
{
signal(SIGINT, signalhandler);
foo();
}
在gdb
中运行程序时,您可以使用signal
命令发送信号。产生的回溯看起来像:
(gdb) signal SIGINT
Continuing with signal SIGINT.
Breakpoint 1, signalhandler (sig=2) at s.c:5
5 write(2, "signal!\n", 8);
(gdb) bt
#0 signalhandler (sig=2) at s.c:5
#1 <signal handler called>
#2 foo () at s.c:10
#3 0x08048498 in main () at s.c:16
注意: 重要的是要意识到信号处理函数调用不应该像常规函数调用那样对待。由于操作系统注入了呼叫,因此它具有下面讨论的限制。
可以在代码执行中的任意位置注入信号调用。因此,POSIX要求从信号处理程序调用某些函数是安全的。如果函数被注入调用信号处理函数中断,那么那些不设计为可重入的函数会冒着处于不一致状态的风险,然后信号处理函数调用被中断的函数。
作为可能发生的问题的示例,假设您正在编写操纵数据结构的代码,例如从链接列表中删除节点。但是,如果在传递信号时元素的指针尚未完全修复,则信号处理程序可能会看到损坏的链表。这种情况可能比你想象的更频繁发生,特别是如果你调用一个需要堆分配的函数。
因此,使信号处理程序变得简单是最安全的。例如,信号处理程序可能只是设置一个标志,然后您的应用程序代码将需要代码来检测该标志是否已设置。
答案 2 :(得分:1)
我将用简单的语言解释。
信号是向程序传递消息的方式。例如,当程序在终端窗口中运行并按Ctrl+C
时,终端窗口会将SIGINT
发送到程序。
现在,对于每个进程,内核都维护一个表,该表从每个信号映射到接收信号时要调用的内容。默认情况下,在SIGINT
上,行为设置为终止。但是,程序可以通过调用函数signal来更改某些信号的默认行为。
EG。 signal(SIGINT, funcHandler)
将接收SIGINT
的行为从终止更改为调用funcHandler
。但它永远不会调用funcHandler
。程序收到SIGINT时将调用funcHandler
。
现在,当程序收到SIGINT
时,内核会查找表以决定调用哪个函数(比如处理程序),然后内核以接收到的信号作为参数设置处理程序的堆栈然后返回该程序。因为使用signal_number作为程序上下文中的参数调用效果funcHandler
。