Linux系统编程---进程间Signal信号通信
0、说明
在前面几篇博客笔记文章对Linux系统编程部分的进程知识体系做了完善的梳理,这篇文章将继续在前几篇的基础上,对Linux系统编程进程间通信的知识点进行讲解。
本文主要对信号的内容进行讲解,因为内容比较多,所以信号集部分的内容,将在下一篇博客文章中进行梳理。
有需要的博客网友,可以在我的Linux系统专栏中查询参考交流:
系统编程_奔跑的蜗牛!的博客-CSDN博客https://blog.csdn.net/weixin_49337111/category_12952742.html?spm=1001.2014.3001.5482
1、信号基础梳理
(1)、什么是信号?
在Linux操作系统中,进程间通信(Inter-Process Communication, IPC)是一个重要的概念,它允许不同的进程之间交换信息或同步它们的行为。信号(signal)是一种基本的进程间通信机制,用于通知一个进程发生了某个特定事件。
信号是一种异步通信机制,一般情况下,进程什么时候会收到信号、收到什么信号是无法事先预料的。(就像你家的门铃,你不知道它什么时候会响,但是门铃响的时候我们可以下楼开门(处理))
(2)、Linux中的信号有哪些?
在Linux终端中,可以通过 kill -l 查看系统支持的信号。
可以看见,Linux 系统中有许多信号,其中前面 31 个信号都有一个特殊的名字,对应一个特殊的事件,比如 1 号信号 SIGHUP(Signal Hang UP),表示每当系统中的一个控制终端被关闭(即挂断,hang up)时,即会产生这个信号,有时会将他们称为非实时信号,这些信号都是从 Unix 系统继承下来的,它们还有个名称叫“不可靠信号”。
这些信号有如下特点:
①、非实时信号不排队,信号的响应会相互嵌套(假设正在响应1号信号的时候收到9号信号则会在响应一号信号的过程中去响应9号信号)。
②、如果目标进程没有及时响应非实时信号,那么随后到达的该信号将会被丢弃。(被挂起的信号列表中是不存在相同的信号)
③、每一个非实时信号都对应一个系统事件,当这个事件发生时,将产生这个信号。(当某一个特定的系统时间会自动产生一个信号)
④、如果进程的挂起信号中含有实时和非实时信号,那么进程优先响应实时信号并且会从大到小依此响应,而非实时信号没有固定的次序。
后面的 31 个信号(从 SIGRTMIN[34] 到 SIGRTMAX[64])是 Linux 系统新增的实时信号,也被称为“可靠信号”。
这些信号有如下特点:
①、实时信号的响应次序按接收顺序排队,不嵌套。
②、即使相同的实时信号被同时发送多次,也不会被丢弃,而会依次挨个响应。
③、实时信号没有特殊的系统事件与之对应。(需要用户自己去产生设置)
下图为信号和其对应的注释说明:
①、上面这个表中列出来的信号的“值”,在 x86、PowerPC 和 ARM 平台下是有效的,但是别的平台的信号值可能跟这个表的不一致。
②、“备注”部分中注明的事件发生时会产生相应的信号,但并不是说该信号的产生就一定发生了这个事件。事实上,任何进程都可以使用函数 kill( )来产生任何信号。
③、信号 SIGKILL(9) 和 SIGSTOP(19)(18号信号也可能不能被捕获??) 是两个特殊的信号,他们不能被忽略、阻塞或捕捉,只能按缺省动作来响应。
除了这两个信号之外的其他信号,接收信号的目标进程按照如下顺序来做出反应:
A) 如果该信号被阻塞,那么将该信号挂起,不对其做任何处理,等到解除对其阻塞为止。
否则进入 B。
B) 如果该信号被捕捉,那么进一步判断捕捉的类型:
B1) 如果设置了响应函数,那么执行该响应函数。
B2) 如果设置为忽略,那么直接丢弃该信号。
否则进入 C。
C) 执行该信号的缺省动作。
(3)、如何发信号给进程?
方法一:
1)首先查看目标进程的PID号: ps -ef
Snail@ubuntu:~/Desktop/process$ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 14:59 ? 00:00:05 /sbin/init auto noprompt
root 2 0 0 14:59 ? 00:00:00 [kthreadd]
root 3 2 0 14:59 ? 00:00:00 [pool_workqueue_release]
root 4 2 0 14:59 ? 00:00:00 [kworker/R-rcu_g]
...
2)通过kill命令发送某一信号给该进程,例如发送信号9,就是杀死进程。
kill -9 4630
kill -SIGKILL 4630 或者 kill -KILL 4630
方法二:
直接通过killall命令给进程名字发送信号
killall -9 a.out
killall -SIGKILL a.out 或者 killall -KILL a.out
只要名字为a.out,都会收到这个信号。
2、信号API接口
(1)、发送信号给其它进程:kill
函数功能:向指定进程或者进程组,发送一个指定的信号函数原型:#include <sys/types.h>#include <signal.h>int kill(pid_t pid, int sig);函数参数:
pid:要发送信号的目标进程 ID。pid > 0:向 PID 等于 pid 的指定进程发送信号。pid == 0:向调用进程所在的进程组中的所有进程发送信号。pid < -1:向进程组 ID 等于 pid 的绝对值的所有进程发送信号。pid == -1:向所有有权限发送信号的进程发送信号(一般不包括某些系统进程)。sig:要发送的信号编号SIGTERM:终止进程。SIGKILL:强制终止进程。SIGHUP:挂起信号。SIGSTOP:暂停进程。SIGCONT:继续被暂停的进程。...函数返回值:成功时返回 0。出错时返回 -1,并设置 errno 表示错误原因。
(2)、捕捉信号:signal
函数功能:捕捉一个指定的信号,即预先为某信号的到来做好准备函数原型:#include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);函数参数:signum:要捕获或忽略的信号编号。handler:指向自定义信号处理函数的指针。或者可以使用以下两个特殊值之一:SIG_DFL:使用默认处理方式(如终止进程);SIG_IGN:忽略该信号。函数返回值:成功时返回前一个信号处理函数的指针(即旧的处理程序)。出错时返回 SIG_ERR,并设置 errno 表示错误原因。
(3)、挂起进程:pause
函数功能:用于挂起当前进程,直到它接收到一个信号为止。函数原型:#include <unistd.h>int pause(void);函数参数:void:NONE函数返回值:始终返回 -1,并且设置 errno 为 EINTR,表示被信号中断。
(4)、给自己发送信号:raise
函数功能:用于向当前进程发送一个信号(signal)。函数原型:#include <signal.h>int raise(int sig);函数参数:sig:要发送的信号编号。函数返回值:成功返回 0失败返回非零值
3、信号的处理
(1)、忽略(将信号丢弃)
signal(signum,SIG_IGN); //signal ignore
(2)、 缺省(默认动作)
signal(signum,SIG_DFL);
(3)、捕捉(去执行指定的函数)
signal(signum,function);
(4)、阻塞(信号挂起)
设置阻塞之后,来了阻塞的指定信号,并不是将信号丢弃,而是将信号挂起,等到解除阻塞之后才去响应这个信号。
注意:9) SIGKILL 和 19) SIGSTOP 这两个信号不能被忽略,阻塞、捕捉。必须是执行默认动作。
4、编程实现
signal_demo.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>void signalHandle(int signum)
{printf("\nCautgh Signal:%d\n", signum);switch(signum){case SIGINT:printf("OK, you kill me success!\n");exit(signum);break;case SIGUSR1:printf("Hello, This signal from myself!\n");break;}
}int main(int argc, char **argv)
{signal(SIGINT, signalHandle);signal(SIGUSR1, signalHandle);int cnt = 0;while(1){printf("I am still alive!\n");sleep(3);if(++cnt == 5) {raise(SIGUSR1);}}return 0;
}
signal_demo.c文件执行的结果如下图所示: