在Linux中,信号是一种进程间通信(IPC)的机制。它用于通知某个进程发生了某种事件或异常。信号的本质是一种软件层次的模拟硬件中断的方法。当某个事件发生时,操作系统会向该事件的关联进程发送一个信号。进程接收到信号后,可以选择忽略该信号或者执行相应的操作来响应该信号。
可以总结为以下三点:
(1)信号的目的:用来通信
(2)信号是异步的(对比硬件中断)
(3)信号的本质上是int型数字编号(事先定义好的)
信号可以由用户、系统或进程发送给目标进程。用户可以通过键盘输入、终端命令或系统调用等方式发送信号。系统则会在某些特定事件发生时自动发送信号,例如进程终止、硬件异常等。进程也可以通过系统调用或信号发送函数来发送信号给其他进程。在Linux中,可以使用kill命令或raise函数来向其他进程发送信号。某种软件条件满足后也会发出信号,如alarm闹钟时间到会产生SIGALARM信号,向一个读端已经关闭的管道write是会产生SIGPIPE信号。
1.SIGINT (2): 中断信号。通常由用户按下Ctrl + C发送,用于终止正在运行的程序。
2.SIGKILL (9): 杀死信号。这是一个无法被捕获或忽略的信号,强制终止进程。
3.SIGTERM (15): 终止信号。通常用于请求进程正常终止。与SIGKILL不同,进程可以捕获并且可以执行清理工作后再退出。
4.SIGSEGV (11): 段错误信号。当进程访问非法内存时触发,通常表示有bug导致了内存访问错误。
5.SIGILL (4): 非法指令信号。当进程试图执行非法的CPU指令时触发。
6.SIGHUP (1): 挂起信号。通常在与终端的连接断开时发送给进程,要求进程重新加载配置或重新初始化。
7.SIGUSR1 (10)和 SIGUSR2 (12): 用户自定义信号1和2。可以由用户自定义用途。
8.SIGALRM (14): 闹钟信号。通常由alarm()函数设置的定时器超时时发送给进程。
当进程接收到SIGINT信号时,可以注册一个signal函数来执行后台保存操作等。
捕获Ctrl+C 终止信号 代码演示
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void handle_signal(int signal_number) {
printf("捕获到信号 %d\n", signal_number);
printf("我要退出了");
exit(0); // 退出程序
}
int main() {
// 注册SIGINT信号的处理函数为handle_signal
signal(SIGINT, handle_signal);
while (1) {
printf("程序运行中... 正在等待信号\n");
sleep(1); // 暂停1秒
}
return 0;
}
signal函数是用于注册信号处理程序的标准函数之一。有一些优点和缺点:
优点:
缺点:无法简单地直接得知之前设置的对信号的处理方法。如果你使用 signal(SIGINT, new_handler) 来设置对 SIGINT 信号的新处理函数 new_handler,那么你不能直接获取或查询在调用 signal 函数之前对 SIGINT 信号所设置的处理函数。这种情况下,你无法在程序中简单地查询或检查当前 SIGINT 信号的处理函数是什么。这可能导致一定的不确定性,特别是在多个模块设置信号处理函数或者需要了解之前的处理方式时。
sigaction 是用于设置信号处理函数的高级函数,相较于 signal 函数,它提供了更多的控制和选项。通过 sigaction 函数,可以更精确地管理信号处理。
sigaction 定义如下:
#include <signal.h>
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction 结构体包含以下字段:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void sigint_handler(int sig) {
printf("捕获到 SIGINT 信号\n");
printf("处理 SIGINT 信号...\n");
exit(0);
}
int main() {
struct sigaction new_action, old_action;
// 设置新的信号处理程序
new_action.sa_handler = sigint_handler;
sigemptyset(&new_action.sa_mask); // 清空信号屏蔽集
new_action.sa_flags = 0;
// 设置SIGINT信号的新处理程序
if (sigaction(SIGINT, &new_action, &old_action) == -1) {
perror("无法设置信号处理程序");
return 1;
}
printf("按下 Ctrl+C 以发送 SIGINT 信号...\n");
// 让程序保持运行,等待信号
while (1) {
sleep(1);
}
return 0;
}
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
seconds 参数表示定时器的秒数。当定时器到达设定的秒数后,会发送 SIGALRM 信号给当前进程。
注意:返回值有点绕,函数的返回值为前一个定时器剩余的秒数。如果之前有设置过定时器,则返回之前定时器剩余的时间,如果之前没有设置定时器,则返回 0。
代码演示:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#define DELAY 5
void alarm_handler(int signum) {
printf("捕获到 SIGALRM 信号 bye\n");
exit(0);
}
int main() {
signal(SIGALRM, alarm_handler); // 设置 SIGALRM 的处理函数
unsigned int remaining_time = alarm(DELAY); // 设置定时器为5秒
printf("alarm 返回值为 %u \n", remaining_time); //这里的返回值其实为0 因为之前没有设置新的定时器只有一个的话,返回为0
printf("等待%ds\n",DELAY);
while(1) {
// 程序的其他工作可以在这里执行
sleep(DELAY);
}
return 0;
}
需要注意的是,alarm 函数设置的定时器是单次定时器,一旦定时器到时,就会被取消。如果需要重复定时功能,需要在 alarm_handler 函数中再次调用 alarm 来设置新的定时器。
pause 函数是一个系统调用,用于使调用进程挂起直到收到一个信号。它通常用于程序中暂时等待某个信号的到来。
int pause(void);
pause 函数没有参数,调用它会使当前进程挂起,直到接收到一个信号为止。当进程接收到信号后,如果信号的默认处理方式是终止进程,那么进程将会终止。如果信号的默认处理方式是调用一个函数,那么进程会执行相应的信号处理函数,然后继续执行。通常,pause 函数用于让进程等待某个信号的到来,比如等待 SIGINT 或其他自定义信号。
代码演示:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void sig_handler(int signum) {
printf("捕获到自定义信号 %d\n", signum);
if(signum==10){
printf("执行退出命令\n");
exit(0) ;
}
if(signum==12){
printf("执行xxxx命令\n");
}
}
int main() {
signal(SIGUSR1, sig_handler); // 设置自定义信号的处理函数
printf("等待自定义信号...\n");
pause(); // 进程挂起等待自定义信号
printf("自定义信号函数执行完毕,程序退出\n");
return 0;
}
在终端编译运行之后,新开一个终端。可以用kill 发送自定义信号发送给进程。例如:
kill -s 12 5700
其中12 为自定义信号,Linux系统中一般10 或12 为自定义信号,5700为进程的pid。
可以使用ps -aux 查看进程pid
效果如下: