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

什么是信号(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信号:

SignalValueActionComment
SIGHUP1TermHangup detected on controlling terminal or death of controlling process
SIGINT2TermInterrupt from keyboard
SIGQUIT3CoreQuit from keyboard
SIGILL4CoreIllegal Instruction
SIGABRT6CoreAbort signal from abort(3)
SIGFPE8CoreFloating point exception
SIGKILL9TermKill signal
SIGSEGV11CoreInvalid memory reference
SIGPIPE13TermBroken pipe: write to pipe with no readers
SIGALRM14TermTimer signal from alarm(2)
SIGTERM15TermTermination signal
SIGUSR130,10,16TermUser-defined signal 1
SIGUSR231,12,17TermUser-defined signal 2
SIGCHLD20,17,18IgnChild stopped or terminated
SIGCONT19,18,25ContContinue if stopped
SIGSTOP17,19,23StopStop process
SIGTSTP18,20,24StopStop typed at tty
SIGTTIN21,21,26Stoptty input for background process
SIGTTOU22,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 <stdlib.h>
#include <stdio.h>

#include <signal.h> /*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 <signal.h>
#include <sys/signal.h>

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
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值