一、信号 signal
1、概念:
(1)信号是进程间通信的方式之一,这种方式不能传输数据,只是在内核
中传递一个信号(整数),信号表示一个整数。
(2)不同信号的值所代表的信号不同。
(3)允许用户自定义信号,自定义信号的含义由程序员自行定义,但是信号值不允许和系统默认拥有的信号值一样。
2、五种默认执行方式:
信号 | 默认执行方式 | 描述 |
---|---|---|
Term | 中止 | 终止进程,通常用于正常终止进程。 |
Ign | 忽略 | 忽略信号,进程收到此信号时不做任何动作。 |
Core | 中止并输出 | 终止进程,并生成核心转储文件,用于调试和分析进程崩溃时的状态。 |
Stop | 停止 | 暂停进程执行,使其停留在当前状态,直到收到 SIGCONT 信号。 |
Cont | 继续执行 | 用于恢复因 SIGSTOP 信号而暂停的进程,使其继续执行。 |
二、信号表
三、信号处理
1.收到信号的三种处理方式
(1)捕获信号: 通过将一个信号与用户自定义的处理函数关联起来,进程可以捕获并处理信号。当进程收到与已注册的处理函数关联的信号时,操作系统会调用相应的处理函数,允许程序员指定自定义的行为。
(2)默认行为: 对于大多数信号,操作系统定义了默认的行为。这些默认行为通常是终止进程,生成核心转储文件,或暂停进程执行等。当进程收到信号时,如果没有设置信号的自定义处理函数,将会执行操作系统默认的行为。
(3)忽略信号: 进程可以选择忽略某些信号,这意味着进程在收到该信号时不做任何动作。这通常用于对某些不需要处理的信号进行过滤,或者是在特定情况下暂时屏蔽某些信号的影响。
2.信号处理过程
- 接收信号
- 中断执行
- 执行信号处理函数
- 恢复执行
上下文切换: 上下文切换属于信号处理过程中的"中断执行"这一步骤。在接收到信号后,操作系统会暂停当前进程的执行,保存当前进程的上下文(包括寄存器状态、堆栈信息等),然后执行信号处理函数。这个过程中就发生了上下文切换,因为操作系统需要从当前进程切换到信号处理函数的上下文。
关于“上下文,进程上下文和中断上下文概念,上下文切换”的讲解可以参考这位博主的帖子,个人觉得简单易懂。 https://blog.csdn.net/lqy971966/article/details/119103989
四、Linux下信号相关API
1.发送信号 kill raise alarm
(1)kill: 发送一个指定信号给指定的进程
函数原型:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数:
@pid 指定接收该信号的进程id
pid > 0 发送指定信号给 pid 对应的那个进程
pid = 0 发送指定信号给 调用kill 的进程 同组的所有的进程(getpgrp 获取进程组 pid)
pid = -1 发送指定信号给 所有进程(有权限发送)
pid < -1 发送指定信号给 进程组pid = abs(pid) 同组的所有的进程
@sig 指定要发送的信号<参照信号表,尽量使用宏>
返回值:
成功返回 0
失败返回 -1 errno被设置。
(2)raise: 发送一个指定的信号给自己<也就是调用处的进程>
函数原型:
#include <signal.h>
int raise(int sig);
参数:
@sig: 指定要发送的信号<参照信号表,尽量使用宏>
返回值:
成功返回 0
失败返回 -1 errno被设置。
(3)alarm: 发送一个时钟信号
函数原型:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
参数:
@seconds: 多少 秒 后发送一个闹钟信号[SIGALARM]
返回值:
成功返回 上一个 "闹钟" 的剩余时间,如果没有上一个闹钟,则返回 0
注意:
①alarm(0) 表示取消所有的 alarm
②每个进程都有属于自己的一个闹钟,如果设定的时间到了,就会收到SIGALARM,默认执行为Term[中断]
③同一时刻一个进程只有一个"闹钟",即:在程序中多次调用 alarm() 函数设置定时器,只有最后一次设置的定时器会生效,之前设置的定时器会被新的定时器覆盖。因此,在任何时刻,一个进程只有一个定时器在运行,控制着定时器到期时发送的 SIGALRM 信号。
2.等待信号 pause
pause: 让 进程/线程 休眠,直到等待到一个可以被捕捉的信号
函数原型:
#include <unistd.h>
int pause(void);
参数:
无参数
返回值:
失败返回 -1,errno被设置,并且一般情况下错误码为 EINTR,表示收到了一个信号,导致 pause 被中断。
注:SIGKILL、SIGTOP这两个信号是不可被捕获的,所以不适用于 pause。
3.自定义信号
(1)定义信号量
(2)定义信号处理函数
(3)注册信号
(4)发送信号
函数原型:
#include <signal.h>
void signal(int signum, void (*handler)(int));
参数:
@signum: 要设置处理函数的信号编号
@handler:指向处理函数的指针。可以是一个自定义的函数,也可以是系统提供的预定义处理函数。
返回值:
无返回值
注意:
(1)关于参数函数指针的参数:
调用 signal() 函数将自定义信号 MY_SIGNAL 与信号处理函数 signal_handler 关联起来。当进程收到信号 MY_SIGNAL 时,操作系统会调用 signal_handler 函数,并将收到的信号编号作为参数传递给它。
(2)关于信号的接收调用:
信号处理函数是由操作系统调用的,而不是直接调用的。当进程收到指定的信号时,操作系统会检查信号处理函数,如果已经注册了与该信号对应的处理函数,就会调用这个处理函数,并传递信号编号作为参数。
(3)总结:
信号处理函数是由操作系统来调用的,它能够知道有信号发生是因为操作系统调用了它。当信号处理函数被调用时,它就知道有信号发生了,并且能够通过参数来获取信号的编号,从而知道这个信号是给自己的。
五、代码实现
五、代码演示
1.kill的使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h> // wait 头文件
#include <sys/types.h>
int main()
{
int i = 0;
if(fork() == 0)
{
while(1)
{
printf("子进程: 死循环 %d\n",i++);
if(i == 5)
{
printf("子进程: 发送信号\n");
// 接收这个信号的进程 id, 信号值
kill(getpid(),SIGKILL);
}
sleep(1);
}
}
else{
printf("父进程: 等待子进程结束\n");
wait(NULL);
}
printf("进程结束\n");
return 0;
}
2.raise的使用
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main()
{
int i = 0;
while(1)
{
printf("i = %d\n",i++);
sleep(1);
if(i == 5)
{
printf("发送 SIGKILL 信号\n");
raise(SIGKILL);
}
}
printf("over\n");
return 0;
}
3.alarm pause 的使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
// 定义信号处理函数
void alarm_handler(int signum) {
printf("定时器到期,收到 SIGALRM 信号\n");
}
int main() {
// 注册信号处理函数
signal(SIGALRM, alarm_handler);
printf("设置定时器,在 5 秒后发送 SIGALRM 信号\n");
// 设置定时器,在 5 秒后发送 SIGALRM 信号
alarm(5);
// 等待 SIGALRM 信号的到来
pause();
printf("程序退出\n");
return 0;
}
4.自定义信号的使用
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
// 定义自定义信号
#define MY_SIGNAL 36
// #define MY_SIGNAL SIGUSR1
// 定义信号处理函数
void signal_handler(int signum)
{
printf("收到自定义信号!\n");
}
int main()
{
printf("注册自定义信号\n");
// 注册信号处理函数
signal(MY_SIGNAL,signal_handler);
// 等待信号
printf("等待一个信号\n");
sleep(2);
// 发送自定义信号
pid_t pid = getpid();
int k = kill(pid,MY_SIGNAL);
if(k == -1)
{
perror("信号发送失败\n");
exit(1);
}
return 0;
}