信号
信号是软件对中断的一个模拟,进程根据接收的信号进行相应的响应。
1. 信号的产生
信号可能由硬件产生,也可能由软件产生,由硬件产生一般包含以下几种情况:
- 执行了非法的指令
- 访问了非法的内存
- 驱动程序报错
- …
由软件产生一般可以以下几种情况来产生:
- 终端当中:
- CTRL C 中断信号
- CTRL | 退出信号
- CTRL z 停止信号
- kill 命令:使用kill命令能够使用发送对应的信号给某个进程
- 在程序中带哦用kill()函数的方式来完成信号的处理
2. 常用的信号
信号名 | 信号编号 | 产生原因 | 默认处理方式 |
---|---|---|---|
SIGHUP | 1 | 关闭终端 | 终止 |
SIGINT | 2 | ctrl+c | 终止 |
SIGQUIT | 3 | ctrl+\ | 终止+转储 |
SIGABRT | 6 | abort() | 终止+转储 |
SIGPE | 8 | 算术错误 | 终止 |
SIGKILL | 9 | kill -9 pid | 终止,不可捕获/忽略 |
SIGUSR1 | 10 | 自定义 | 忽略 |
SIGSEGV | 11 | 段错误 | 终止+转储 |
SIGUSR2 | 12 | 自定义 | 忽略 |
SIGALRM | 14 | alarm() | 终止 |
SIGTERM | 15 | kill pid | 终止 |
SIGCHLD | 17 | (子)状态变化 | 忽略 |
SIGTOP | 19 | ctrl+z | 暂停,不可捕获/忽略 |
3. 信号的处理函数
当需要向外发送信号或者进程接收到信号的时候,需要对这个信号进行正确地响应才可以,在这里需要了解几个常用的函数,来设置信号及其响应。
3.1 signal 函数
signal()函数主要用于捕获信号,并且可以更改进程中对信号的默认行为。
比如,当进程接收到CTRL+C这个信号之后,默认的处理方式是终止程序的运行。但是,如果我们使用了signal函数来捕获这个信号,并且对这个信号的响应进行修改的话,那么可也完成非默认的操作。
signal函数的使用:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signum:是准备捕获或者忽略的信号
handler:是处理函数或者系统支持的两个宏:
- SIG_IGN:忽略该信号
- SIG_DFL:采用系统默认的方式来处理信号
- 使用自定义的处理函数
使用一个简单的例子,来说明signal函数的作用
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <wait.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
void signal_handler(int sig)
{
printf("The signal number is %d\r\n", sig);
if(sig == SIGINT)
{
printf("The handler has received the SIGINT signal!!!\r\n");
printf("The handler will be set to the default\r\n");
signal(SIGINT, SIG_DFL);
}
}
int main(void)
{
printf("This is the signal test demo\r\n");
/*Set the signal handler response to SIGNINT*/
signal(SIGINT, signal_handler);
while (1)
{
printf("Waiting for CTRL+C......\r\n");
sleep(3);
}
}
该代码完成的就是更改signal函数对SIGINT这个信号的处理函数,第一次接收到这个信号的时候,会去打印一个信息,然后会在回调函数里面将处理函数更改为默认的处理函数。
当第二次接收到SIGINT函数的时候,就会退出这个程序。
3.2 kill函数
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
kill函数的参数有两个,
- pid指的是信号的接收进程的ID号。
- sig指的是向进程发送的信号
返回值: - 0:发送成功
- -1:发送失败
kill函数的使用方法和在shell里面直接使用kill命令是类似的,比如我们在shell里面执行一个sleep命令,然后使用kill能够将这个进程发送命令使其停止。
3.3 raise函数
raise函数和kill函数类似,不过raise函数只能由进程自生发送信号给自己。其函数原型如下:
int raise(int sig);
kill和rasie函数举例
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <wait.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
int main(void)
{
/*Create a new process*/
pid_t pid = fork();
if (pid == 0)
{
/*show child process*/
printf("This is clid process pid is %d\r\n",getpid());
/*Raise signal, Interrupt child process*/
raise(SIGSTOP);
/*This will never print*/
printf("exit child process\r\n");
exit(0);
}
else if (pid > 0)
{
sleep(3);
/* kill child process*/
if (kill(pid, SIGKILL) == 0)
{
printf("parent process kill pid %d\r\n", pid);
}
/*wait for child process wxit*/
wait(NULL);
exit(0);
}
}
在上面的例子当中,子进程当中在raise函数之后的内容不会被打印。因为子进程在raise函数中,中断了自己。子进程的退出,是依靠父进程中使用kill命令来杀死这个子进程的。