c/c++ signal(信号)解析

您所在的位置:网站首页 接收信号是什么意思 c/c++ signal(信号)解析

c/c++ signal(信号)解析

2024-07-14 16:44| 来源: 网络整理| 查看: 265

什么是信号(signal)

信号是一种软件中断,一种向进程传递有关其他进程,操作系统和硬件状态的信息的方法。信号是一种中断,因为它可以改变程序的流程。当信号传递给进程时,进程将停止其执行的操作,处理或忽略信号,或者在某些情况下终止,取决于信号。

由于信号可能源自当前正在执行的过程之外的事实,信号也可能以不可预测的方式传递,与程序不一致。查看信号的另一种方法是一种处理异步事件的机制。与同步事件相反,同步事件是标准程序执行迭代,即一行代码跟随另一行。当程序的某些部分按顺序执行时,就会发生异步事件。异步事件通常由于源自硬件或操作系统的外部事件而发生;信号本身是操作系统将这些事件传递给进程的方式,以便进程可以采取适当的操作。

如何使用它们

信号在Unix编程中用于各种各样的目的,我们已经在较小的上下文中使用它们。例如,当我们在shell中工作并希望“杀死所有cat程序”时,我们输入命令:

#> killall cat

killall命令将向所有名为cat的进程发送一个信号,表示“终止”。发送的实际信号是SIGTERM,其目的是将终止请求传送给给定进程,但该进程实际上不必终止…稍后将详细说明。

我们还在终端信令的上下文中使用和查看信号,这是程序停止,启动和终止的方式。我们输入Ctrl-c与发送SIGINT信号相同,输入Ctrl-z与发送SIGTSTP信号相同,我们输入fg或bg与发送SIGCONT信号相同。

这些信号中的每一个都描述了该过程应该采取的响应动作。此操作超出了程序的正常控制流程,事件异步到达,要求进程中断其当前操作以响应事件。对于上述信号,响应是明确的 - SIGTERM终止,SIGSTOP停止,SIGCONT继续 - 但对于其他信号,程序员可以选择正确的响应,这可能只是简单地忽略信号。

常用的Signals

每个信号都有一个名称,它以SIG开头,以描述结束。我们可以在手册页的第7节中查看所有信号,下面是您可能与之交互的标准Linux信号:

SignalValueActionCommentSIGHUP1TermHangup detected on controlling terminal or death of controlling processSIGINT2TermInterrupt from keyboardSIGQUIT3CoreQuit from keyboardSIGILL4CoreIllegal InstructionSIGABRT6CoreAbort signal from abort(3)SIGFPE8CoreFloating point exceptionSIGKILL9TermKill signalSIGSEGV11CoreInvalid memory referenceSIGPIPE13TermBroken pipe: write to pipe with no readersSIGALRM14TermTimer signal from alarm(2)SIGTERM15TermTermination signalSIGUSR130,10,16TermUser-defined signal 1SIGUSR231,12,17TermUser-defined signal 2SIGCHLD20,17,18IgnChild stopped or terminatedSIGCONT19,18,25ContContinue if stoppedSIGSTOP17,19,23StopStop processSIGTSTP18,20,24StopStop typed at ttySIGTTIN21,21,26Stoptty input for background processSIGTTOU22,22,27Stoptty output for background process 信号的name和value关联

每个信号都有名称,值和默认操作。信号名称应该开始变得更加熟悉,信号的值实际上与信号本身相同。实际上,信号名称只是一个#defined值,我们可以通过查看sys / signal.h头文件来看到:

#define SIGHUP 1 /* hangup */ #define SIGINT 2 /* interrupt */ #define SIGQUIT 3 /* quit */ #define SIGILL 4 /* illegal instruction (not reset when caught) */ #define SIGTRAP 5 /* trace trap (not reset when caught) */ #define SIGABRT 6 /* abort() */ #define SIGPOLL 7 /* pollable event ([XSR] generated, not supported) */ #define SIGFPE 8 /* floating point exception */ #define SIGKILL 9 /* kill (cannot be caught or ignored) */ 信号的 Action

每个信号都有一个默认动作。表中描述了四种:

Term : The process will terminate Core : The process will terminate and produce a core dump file that traces the process state at the time of termination. Ign : The process will ignore the signal Stop : The process will stop, like with a Ctrl-Z Cont : The process will continue from being stopped

对于某些信号,我们可以更改默认操作。一些信号,即控制信号,不能改变它们的默认动作,包括SIGKILL和SIGABRT,这就是为什么“kill 9”是最终的kill语句。

处理和生成信号 1. Hello信号处理世界

信号处理的主要系统调用是signal(),它给出信号和功能,只要信号被传送就会执行该功能。此函数称为信号处理程序,因为它处理信号。 signal()函数有一个奇怪的声明:

int signal(int signum, void (*handler)(int))

也就是说,signal有两个参数:第一个参数是信号编号,例如SIGSTOP或SIGINT,第二个参数是对第一个参数为int并返回void的处理函数的引用。

#include #include #include /*for signal() and raise()*/ void hello(int signum){ printf("Hello World!\n"); } int main(){ //execute hello() when receiving signal SIGUSR1 signal(SIGUSR1, hello); //send SIGUSR1 to the calling process raise(SIGUSR1); }

上述程序首先为用户信号SIGUSR1建立信号处理程序。信号处理函数hello()按预期执行:打印“Hello World!”到stdout。程序然后发送SIGUSR1信号,这是通过raise()完成的,执行程序的结果是漂亮的短语:

#> ./hello_signal Hello World! 2.异步执行

从hello程序中取消的一些关键点是signal()的第二个参数是一个函数指针,一个对要调用的函数的引用。这告诉操作系统无论何时将此信号发送到此进程,都要将此函数作为信号处理程序运行。

此外,信号处理程序的执行是异步的,这意味着程序的当前状态将在信号处理程序执行时暂停,然后执行将从暂停点恢复,就像上下文切换一样。

让我们看看另一个示例hello world程序:

/* hello_loop.c*/ void hello(int signum){ printf("Hello World!\n"); } int main(){ //Handle SIGINT with hello signal(SIGINT, hello); //loop forever! while(1); }

上面的程序将为SIGINT设置一个信号处理程序,键入Ctrl-C时生成的信号。问题是,当我们执行这个程序时,键入Ctrl-C会发生什么?

首先,让我们考虑一下程序的执行情况。它将注册信号处理程序,然后进入无限循环。当我们按下Ctrl-C时,我们都同意信号处理程序hello()应该执行并且“Hello World!”打印到屏幕上,但程序处于无限循环中。为了打印“Hello World!”一定是它打破循环执行信号处理程序的情况,对吗?所以它应该退出循环以及程序。让我们来看看:

#> ./hello_loop ^CHello World! ^CHello World! ^CHello World! ^CHello World! ^CHello World! ^CHello World! ^CHello World! ^\Quit: 3

如输出所示,每次我们发出Ctrl-C“Hello World!”打印,但程序返回无限循环。只有在用Ctrl- \发出SIGQUIT信号后,程序才真正退出。

虽然循环将退出的解释是合理的,但它没有考虑信号处理的主要原因,即异步事件处理。这意味着信号处理程序的行为超出了程序控制的标准流程;事实上,整个程序都保存在一个上下文中,并且只为信号处理程序创建了一个新的上下文。如果你再考虑一下,你会发现这很酷,也是一种全新的方式。查看编程。

3.进程间通信

信号也是进程间通信的关键手段。一个进程可以向另一个进程发送信号,指示应该采取措施。要向特定进程发送信号,我们使用kill()系统调用。功能声明如下。

int kill(pid_t pid, int signum);

与命令行版本非常相似,kill()接受一个进程标识符和一个信号,在这种情况下,信号值为int,但值为#defined,因此您可以使用该名称。让我们看看它在使用中。

/*ipc_signal.c*/ void hello(){ printf("Hello World!\n"); } int main(){ pid_t cpid; pid_t ppid; //set handler for SIGUSR1 to hello() signal(SIGUSR1, hello); if ( (cpid = fork()) == 0){ /*CHILD*/ //get parent's pid ppid = getppid(); //send SIGUSR1 signal to parrent kill(ppid, SIGUSR1); exit(0); }else{ /*PARENT*/ //just wait for child to terminate wait(NULL); } }

在这个程序中,首先为SIGUSR1建立一个信号处理程序,即hello()函数。在fork之后,父进程调用wait(),并且子进程将通过SIGUSR1信号“杀死”它来与父进行通信。结果是在父级和“Hello World!”中调用了处理程序。从父级打印到stdout。

虽然这只是一个小例子,但信号对于进程间通信是不可或缺的。在前面的课程中,我们讨论了如何使用pipe()在进程之间传递数据,信号是进程传递状态更改和其他异步事件的方式。也许最相关的是儿童过程中的状态变化。 SIGCHLD信号是孩子终止时传递给父母的信号。到目前为止,我们一直在通过wait()隐式处理这个信号,但您可以选择处理SIGCHLD并在子进程终止时采取不同的操作。

4.忽略信号

到目前为止,我们的处理程序一直在打印“Hello World!” - 但我们可能只是希望我们的处理程序什么也不做,基本上是忽略了信号。这很容易写入代码,例如,这是一个程序,它将通过处理信号忽略SIGINT并且什么都不做:

/*ingore_sigint.c*/ #include #include void nothing(int signum){ /*DO NOTHING*/ } int main(){ signal(SIGINT, nothing); while(1); }

如果我们运行这个程序,我们会看到,是的,Ctrl-c无效,我们必须使用Ctrl- \来退出程序:

>./ignore_sigint ^C^C^C^C^C^C^C^C^C^C^\Quit: 3

signal.h标头定义了一组可用于代替处理程序的操作:

SIG_IGN:忽略信号SIG_DFL:用默认处理程序替换当前信号处理程序 使用这些关键字,我们可以简单地将程序重写为: int main(){ // using SIG_IGN signal(SIGINT, SIG_IGN); while(1); } 5.更改并恢复默认处理程序

设置信号处理程序不是一个单一事件。您始终可以更改处理程序,还可以将处理程序恢复为默认状态。例如,请考虑以下程序:

/*you_shot_me.c*/ void handler_3(int signum){ printf("Don't you dare shoot me one more time!\n"); //Revert to default handler, will exit on next SIGINT signal(SIGINT, SIG_DFL); } void handler_2(int signum){ printf("Hey, you shot me again!\n"); //switch handler to handler_3 signal(SIGINT, handler_3); } void handler_1(int signum){ printf("You shot me!\n"); //switch handler to handler_2 signal(SIGINT, handler_2); } int main(){ //Handle SIGINT with handler_1 signal(SIGINT, handler_1); //loop forever! while(1); } #> ./you_shout_me ^CYou shot me! ^CHey, you shot me again! ^CDon't you dare shoot me one more time! ^C

程序首先启动handler_1()作为SIGINT的信号处理程序。在第一个Ctrl-c之后,在信号处理程序中,处理程序更改为handler_2(),在第二个Ctrl-c之后,它再次从handler_2()更改为handler_3()。最后,在handler_3()中重新建立默认信号处理程序,即在SIGINT上终止,这就是我们在输出中看到的:

#> ./you_shout_me ^CYou shot me! ^CHey, you shot me again! ^CDon't you dare shoot me one more time! ^C 6.有些信号比其他信号更平等

关于信号处理的最后一点是,并非所有信号都是相同的。这意味着,您无法处理所有信号,因为它可能会使系统处于不可恢复的状态。

永远不会被忽略或处理的两个信号是:SIGKILL和SIGSTOP。我们来看一个例子:

/* ignore_stop.c */ int main(){ //ignore SIGSTOP ? signal(SIGSTOP, SIG_IGN); //infinite loop while(1); }

上面的程序试图为SIGSTOP设置忽略信号处理程序,然后进入无限循环。如果我们执行该计划,我们发现这些努力没有结果:

#>./ignore_stop ^Z [1]+ Stopped ./ignore_stop

对于忽略SIGKILL的程序,我们可以看到相同的内容。

int main(){ //ignore SIGSTOP ? signal(SIGKILL, SIG_IGN); //infinite loop while(1); } #>./ignore_kill & [1] 13129 #>kill -SIGKILL 13129 [1]+ Killed: 9 ./ignore_kill 7.检查信号错误()

signal()函数返回一个指向前一个信号处理程序的指针,这意味着这里再次是一个系统调用,我们不能通过检查返回值是否小于0来以典型的方式进行错误检查。这是因为指针类型是无符号的,没有负指针这样的东西。

相反,使用特殊值SIG_ERR,我们可以比较signal()的返回值。这里再次是我们尝试忽略SIGKILL的程序,但这次正确的错误检查:

/*signal_errorcheck.c*/ int main(){ //ignore SIGSTOP ? if( signal(SIGKILL, SIG_IGN) == SIG_ERR){ perror("signal");; exit(1); } //infinite loop while(1); }

输出

#>./signal_errorcheck signal: Invalid argument


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3