一、软中断信号
软中断信号(s i g n a l signalsignal,又简称为信号)用来通知进程发生了事件。进程之间可以通过调用kill函数发送软中断信号。L i n u x LinuxLinux 内核也可能给进程发送信号,通知进程发生了某个事件(例如内存越界)。
注意:信号只是用来通知某进程发生了什么事件,无法给进程传递任何数据,进程对信号的处理方法有三种:
① 忽略信号(SIG_IGN):大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景
② 捕捉信号:需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理
③ 系统默认动作(SIG_DFL),对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。在此,我就不详细展开了,需要查看的,可以自行查看。也可以参考 《UNIX 环境高级编程(第三部)》的 P251——P256中间对于每个信号有详细的说明。
下面我们来介绍信号相关的API:
-
signal
函数void (*signal(int signum, void (*handler)(int))) (int); //给 void (*)(int)函数指针换个标签 typedef void (*sighandler_t)(int); //替换第一行。 sighandler_t signal(int signum, sighandler_t handler)
signum
:要处理的信号编号。可以是预定义的信号常量(如SIGINT
、SIGTERM
等),也可以是自定义的信号编号。handler
:信号处理函数的指针。当接收到指定信号时,系统将调用该信号处理函数来处理该信号。
-
kill
函数int kill(pid_t pid, int signum);
pid
:目标进程的进程 ID。可以使用getpid()
获取当前进程的进程 ID,或者使用其他方式获取指定进程的进程 ID。signum
:要发送的信号编号。
-
信号处理函数的参数
void handler(int signum);
signum
:触发信号处理函数的信号编号。
4.下面是demo实现信号通知进程的(软件中断)
signal:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <signal.h>
5
6
7 void handler(int signum)
8 {
9 switch(signum)
10 {
11 case 2:
12 printf("get signum:%d\n",signum);
13 printf("SIGINT\n");
14 break;
15
16 case 9:
17 printf("get signum:%d\n",signum);
18 printf("SIGKILL!\n");
19 break;
20
21 case 10:
22 printf("get signum:%d\n",signum);
23 printf("SIGUSER1\n");
24 break;
25 default :
26 break;
27
28 }
29
30 }
31 int main()
32 {
33
34 // typedef void (*sighandler_t)(int);
35 // sighandler_t signal(int signum, sighandler_t handler);
36 signal(SIGINT,handler);
37 signal(SIGKILL,handler);
38 signal(SIGUSR1,handler);
39
40 while(1);
41 return 0;
42 }
43
kill:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <signal.h>
5
6 int main(int argc, char *argv[])
7 {
8 int pid; // 进程ID
9 int signum; // 信号编号
10 char cmd[128] = {0}; // 用于存储执行的命令
11
12 // 从命令行参数中获取进程ID和信号编号
13 pid = atoi(argv[1]); // 将字符串转换为整数作为进程ID
14 signum = atoi(argv[2]); // 将字符串转换为整数作为信号编号
15 printf("pid=%d, signum=%d\n", pid, signum);
16 kill(pid,signum);
17 // 构造发送信号的命令
18 // sprintf(cmd, "kill -%d %d", signum, pid);也可以用sysem函数发送指令
20 // 使用 system 函数执行发送信号的命令
21 // system(cmd);
22
23 return 0;
24 }
25
~
~
~
~
~
~
~
~
~
结果:
二、sigaction 函数用于设置信号的处理方式,它提供了更为灵活和可靠的信号处理机制,相较于较旧的 signal 函数,更推荐使用 sigaction。
sigaction 函数的原型如下:
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
signum:要设置处理方式的信号编号。
act:指向一个结构体 struct sigaction,用于设置新的信号处理方式。
oldact:如果不为 NULL,则会将原来的信号处理方式存储在这个结构体中。
struct sigaction 结构体定义如下:
struct sigaction 结构体的成员包括:
struct sigaction {
void (*sa_handler)(int); // 指向信号处理函数的函数指针
void (*sa_sigaction)(int, siginfo_t *, void *); // 用于扩展的信号处理函数
sigset_t sa_mask; // 在信号处理函数执行期间要阻塞的信号集合
int sa_flags; // 控制信号处理的各种选项
void (*sa_restorer)(void); // 用于兼容旧式信号处理函数的恢复函数
};
sa_handler:指向信号处理函数的函数指针,类似于旧的 signal 函数。当信号处理函数返回时,程序会恢复到正常执行。通过设置该指针为 SIG_IGN 可以忽略信号,设置为 SIG_DFL 可以使用系统默认的处理方式。
sa_sigaction:用于扩展的信号处理函数,与 sa_handler 不能同时使用。在某些情况下,可以利用该函数获取更多关于信号的信息。
sa_mask:在信号处理函数执行期间要阻塞的信号集合。当处理某个信号时,可以屏蔽其他特定的信号,以避免信号处理函数被其他信号中断。
sa_flags:控制信号处理的各种选项,例如:
SA_RESTART:在信号处理函数被中断后自动重启被打断的系统调用。
SA_NOCLDSTOP:不接收子进程停止或继续的信号。
SA_SIGINFO:使用扩展的信号处理函数 sa_sigaction。
SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL
SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号
sa_restorer:仅用于兼容旧式信号处理函数的恢复函数。
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
void handler(int signum, siginfo_t *siginfo, void *context);
int main()
{
struct sigaction act;
act.sa_sigaction = handler; // 设置信号处理函数
act.sa_flags = SA_SIGINFO; // 使用扩展模式,传递附加信息
printf("pid=%d\n", getpid());
sigaction(SIGUSR1, &act, NULL); // 注册信号处理函数
while(1);
return 0;
}
void handler(int signum, siginfo_t *siginfo, void *context)
{
printf("signum=%d\n", signum);
printf("si_pid=%d\n", siginfo->si_pid); // 发送信号的进程 ID
printf("sigval_t si_value=%d\n", siginfo->si_value.sival_int); // 传递的整型值
printf("si_int=%d\n", siginfo->si_int); // 废弃字段,不再使用
}
在主函数中,首先创建了一个 struct sigaction
结构体对象 act
,并设置其成员 sa_sigaction
为处理函数 handler
,sa_flags
设置为 SA_SIGINFO
,表示使用扩展模式。
接下来,打印当前进程的 PID,并使用 sigaction()
函数注册了 SIGUSR1
信号的处理函数。
处理函数 handler
接收三个参数,分别是接收到的信号编号、信号附加信息的结构体指针和上下文指针。在该处理函数中,打印出信号编号、发送信号的进程 ID、传递的整型值等信息。
最后,在主函数的无限循环中,程序会一直处于运行状态,等待接收到信号。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
int main(int argc, char *argv[])
{
pid_t pid = atoi(argv[1]); // 获取指定进程的 PID
int signum = atoi(argv[2]); // 获取要发送的信号编号
printf("pid=%d\n", getpid()); // 打印当前进程的 PID
union sigval value;
value.sival_int = 666; // 设置传递的整型值
int ret = sigqueue(pid, signum, value); // 向指定进程发送信号
if (ret == -1) {
perror("发送信号失败"); // 发送信号失败时打印错误信息
exit(-1);
}
return 0;
}
在主函数中,首先通过 atoi()
函数将命令行参数转换为整型,获取了要发送信号的进程的 PID 和要发送的信号编号。
接下来,打印当前进程的 PID。
然后,创建了一个 union sigval
类型的变量 value
,并将其成员 sival_int
设置为666,即要传递的整型值。
之后,使用 sigqueue()
函数向指定的进程发送信号,传递了进程的 PID、信号编号和附加的值。发送成功时,函数返回 0;发送失败时,函数返回 -1,并打印错误信息。
最后,程序正常退出。
结果:
20230821