进程间通信-信号
0.进程间通信(IPC:Inter-Process Communication )
0.1什么是进程间通信
进程之间存在隔离,各自独立运行于内存空间中。如果需要两个进程之间相互配合完成工作,就需要进程间通信。
0.2进程间通信的方式

1.信号
1.1信号的概念
例如:教室铃响了,就知道下课了。如果手机响了,就知道来信息或者来电话。
以上这些就是信号。信号已经在生活中离不开了。特别是物联网行业。
1.2信号的特点
- 信号是传递信息的,但是不传递具体信息,只是一个标志。
- 信号是一种软件中断机制。(写代码触发,与硬件中断有所区别)
1.3信号的编号
linux中,信号编号的查看,用 kill -l来具体查看。 1-31 号信号称之为常规信号(也叫普通信号或标准信号),34-64称为实时信号。






1.4信号的4要素
- 编号
- 信号名称(和信号编号都一样,后续调用函数传参的时候可以传编号,也可以传名称。)
- 对应事件
- 默认处理动作(终止进程、忽略、暂停)
后续可以 man 7 signal查看signal信号
2.信号的产生与处理
产生
a)当用户按某些终端键时,将产生信号。 终端上按“Ctrl+c”组合键通常产生中断信号 SIGINT 终端上按“Ctrl+\”键通常产生中断信号 SIGQUIT 终端上按“Ctrl+z”键通常产生中断信号 SIGSTOP 等。
b) 硬件异常将产生信号。 除数为0,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内核产生适当的信号发送给相应的进程。
c) 软件异常将产生信号。 当检测到某种软件条件已发生(如:定时器 alarm),并将其通知有关进程时,产生信号。
d) 调用系统函数(如:kill、raise、abort)将发送信号。
e) 运行 kill /killall 命令将发送信号。 此程序实际上是使用 kill 函数来发送信号。
信号的处理
信号的处理:忽略、终止进程、暂停进程,(自定义处理)
3.未决信号集与阻塞信号集
信号是一种异步通信方式,信号发出,不用管是否被接收或者处理。
Linux 内核的进程控制块 PCB 是一个结构体,task_struct, 除了包含进程 id,状态,工作目录,用户 id,组 id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。
未决信号集:未决信号集 信号产生,未决信号集中描述该信号的位立刻翻转为 1,表示信号处于未决状态。当信号被处理对应位翻转回为 0。这一时刻往往非常短暂。
阻塞信号集:将某些信号加入集合,对他们设置屏蔽,当屏蔽 x 信号后,再收到该信号,该信号的处理将推后。(注意,不是不处理,而是推后处理)

如果信号在阻塞信号集内,那么信号得不到执行,未决信号集里这个信号的位图一直是1,直到该信号从阻塞集剔除,执行完毕才会变成0,从未决信号集中剔除。
4.信号相关API函数
4.1kill(向其他进程发送信号,他杀)
函数格式:
#include <sys/types.h>
#include <signal.h>
//向pid发送一个信号sig
int kill(pid_t pid, int sig);参数:
pid : 取值有 4 种情况 :
- pid > 0: 将信号传送给进程 ID 为 pid 的进程。
- pid = 0 : 将信号传送给当前进程所在进程组中的所有进程。
- pid = -1 : 将信号传送给系统内所有的进程。(慎用)
- pid < -1 : 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。(如果传入-9527,其实是发送给9527进程组的所有进程)
sig : 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill - l("l" 为字母)进行相应查看。
不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。
返回值:
- 成功:0
- 失败:-1
代码案例:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>int main(int argc, char const *argv[])
{pid_t pid = fork();if (pid == -1){perror("fork");}else if (pid == 0) // 子进程{for (int i = 1; i <= 5; i++){printf("我还能再玩%ds\n", 5 - i);sleep(1);}while (1){/* code */}}else if (pid > 0) // 父进程{int status = 0;sleep(5);kill(pid, SIGINT); //向pid 发送一个sigint信号(2号信号)wait(&status); //等待资源回收printf("WIFEXITED(status):%d\n", WIFEXITED(status)); // 结果为0 因为非正常退出if (WIFEXITED(status)) //此处不会执行{printf("退出状态%d\n", WEXITSTATUS(status));}}return 0;
}
4.2raise(自己给自己发信号,相当与自杀)
函数原型:
#include <signal.h>
int raise(int sig);参数:sig ,向自己发送一个sig信号 等价于 kill(getpid(),sig);
代码案例:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>int main(int argc, char const *argv[])
{pid_t pid = fork();if (pid == -1){perror("fork");}else if (pid == 0) // 子进程{for (int i = 1; i <= 5; i++){printf("我还能再玩%ds\n", 5 - i);sleep(1);}raise(SIGINT); //向自己发送信号while (1){/* code */}}else if (pid > 0) // 父进程{int status = 0;sleep(5);// kill(pid, SIGINT); //向pid 发送一个sigint信号(2号信号)wait(&status); //等待资源回收printf("WIFEXITED(status):%d\n", WIFEXITED(status)); // 结果为0 因为非正常退出if (WIFEXITED(status)) //此处不会执行{printf("退出状态%d\n", WEXITSTATUS(status));}}return 0;
}4.3abort
函数格式:
#include <stdlib.h>
void abort(void);功能:给自己发送异常终止信号 6) SIGABRT,并产生 core 文件,等价于 kill(getpid(), SIGABRT);

4.4alarm(定时杀)
函数原型:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);功能:
- 设置定时器(闹钟)。在指定 seconds 后,内核会给当前进程发送 14)SIGALRM 信号。进程收到该信号,默认动作终止。每个进程都有且只有唯一的一个定时器。
- 取消定时器 alarm(0),返回旧闹钟余下秒数。
参数:
seconds:指定的时间,以秒为单位
返回值:
返回 0 或剩余的秒数
代码案例:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>int main(int argc, char const *argv[])
{pid_t pid = fork();if (pid == -1){perror("fork");}else if (pid == 0) // 子进程{alarm(5); //5s以后向该进程发送一个14号新号for (int i = 1; i <= 5; i++){printf("我还能再玩%ds\n", 5 - i);sleep(1);if (i==3){int ret = alarm(0); //3s以后,定时器取消,返回值为剩余时间 2sprintf("ret:%d\n",ret);}}while (1){/* code */}}else if (pid > 0) // 父进程{int status = 0;sleep(5);// kill(pid, SIGINT); //向pid 发送一个sigint信号(2号信号)wait(&status); //等待资源回收printf("WIFEXITED(status):%d\n", WIFEXITED(status)); // 结果为0 因为非正常退出if (WIFEXITED(status)) //此处不会执行{printf("退出状态%d\n", WEXITSTATUS(status));}}return 0;
}
4.5 setitimer
函数原型:
#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);功能:设置定时器(闹钟)。 可代替 alarm 函数。精度微秒 us,可以实现周期定时。
参数
- which:指定定时方式
- a) 自然定时:ITIMER_REAL → 14)SIGALRM 计算自然时间(时钟时间)
- b) 虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM 只计算进程占用 cpu 的时间
- c) 运行时计时(用户 + 内核):ITIMER_PROF → 27)SIGPROF 计算占用 cpu 及执行系统调用的时间
- struct itimerval *new_value 结构体指针,而且传入后不能修改。
- 结构体1
struct itimerval {struct timerval it_interval; // 闹钟触发周期 ,后续每一次触发的时间间隔 struct timerval it_value; //闹钟触发时间 从现在开始,到闹钟第一次触发的时间 };结构体2:
struct timeval {long tv_sec; // 秒long tv_usec; // 微秒 }
- old_value: 存放旧的 timeout 值,一般指定为 NULL
代码案例:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/time.h>int main(int argc, char const *argv[])
{struct itimerval new, old;new.it_interval.tv_sec = 5; // 后续每次触发是5snew.it_interval.tv_usec = 0;new.it_value.tv_sec = 10; // 第一次触发是10s以后new.it_value.tv_usec = 0;setitimer(ITIMER_REAL, &new, &old);//按照new规定的时间进行定时器设置,包括循环时间和第一次执行的时间//old 用来保存上一次定时器状态,例如剩余时间(在此处是0,因为上次没有调用定时器)sleep(2);setitimer(ITIMER_REAL, &new, &old); ////按照new规定的时间进行定时器设置,包括循环时间和第一次执行的时间//old 此时,保存上一个定时器计数剩下的时间,大约是8s,可以用于后续恢复这个计时器状态printf("%ld\n", old.it_interval.tv_sec);printf("%ld\n", old.it_interval.tv_usec);printf("%ld\n", old.it_value.tv_sec);printf("%ld\n", old.it_value.tv_usec);setitimer(ITIMER_REAL, &old, NULL);int count = 1;while (1){sleep(1);printf("过了%ds\n", count);count++;}return 0;
}5.给信号注册自定义函数
进程收到信号后的操作:
1.执行默认动作,例如结束进程,或者是暂停进程。
2.直接忽略
3.执行自定义信号处理函数(捕获) 用用户定义的信号处理函数处理该信号。
5.1信号自定义函数的作用

5.2signal函数
函数原型:
#include <signal.h> //头文件
typedef void(*sighandler_t)(int); //函数指针 int
sighandler_t signal(int signum, sighandler_t handler);函数功能:注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),即确定收到信号后处理函数的入口地址。此函数不会阻塞。不再按照系统默认方式处理。
参数:
- signum:信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill - l("l" 为字母)进行相应查看。
- handler : 取值有 3 种情况:
- SIG_IGN:忽略该信号
- SIG_DFL:执行系统默认动作
- 信号处理函数名:自定义信号处理函数。
代码案例:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/time.h>
#include <stdlib.h>char *p = NULL;void my_deal(int signal) //后续这个signal 可以用于监控是哪个信号触发了这个函数
{if (p !=NULL ){free(p);p = NULL;}_exit(0);
}
int main(int argc, char const *argv[])
{p = (char *)malloc(128);signal(SIGINT,my_deal); //我想在终端 按下 ctrl + c的时候,将这个p释放掉while (1){printf("%p\n",p);sleep(1);}free(p);return 0;
}
5.3sigaction
函数原型:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction*oldact);
sigaction()是一个用于设置信号处理函数的 POSIX 系统调用,比传统的signal()函数更强大和灵活。函数功能:检查或修改指定信号的设置(或同时执行这两种操作)。
参数:
- signum:要操作的信号。一般传宏
- act: 要设置的对信号的新处理方式(传入参数)。
- oldact:原来对信号的处理方式(传出参数)。
如果 act 指针非空,则要改变指定信号的处理方式(设置),如果 oldact 指针非空,则系统将此前指定信号的处理方式存入 oldact。
- struct sigaction 结构体:
struct sigaction {//两个信号处理函数指针 选择一个即可 void(*sa_handler)(int); //旧 信号处理函数指针 相当于 signal函数的第二参数。 void(*sa_sigaction)(int, siginfo_t *, void *); //新的信号处理 函数指针 sigset_t sa_mask; //信号阻塞集 int sa_flags; //信号处理的方式 void(*sa_restorer)(void); //已弃用 };//sa_flags:用于指定信号处理的行为,通常设置为 0,表使用默认属性。它可以是以下值的“按位或”组合 /* Ø SA_RESTART:使被信号打断的系统调用自动重新发起(已经废弃) Ø SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。 Ø SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。 Ø SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。 Ø SA_RESETHAND:信号处理之后重新设置为默认的处理方式。 Ø SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。 */
案例代码
#include <stdio.h>
#include <signal.h>
#include <unistd.h>// 简单的信号处理函数
void signal_handler(int sig) {printf("收到信号: %d\n", sig);
}int main() {struct sigaction sa;// 设置信号处理函数sa.sa_handler = signal_handler;sigemptyset(&sa.sa_mask);sa.sa_flags = 0;// 注册 SIGINT 信号处理if (sigaction(SIGINT, &sa, NULL) == -1) {perror("sigaction");return 1;}printf("按下 Ctrl+C 测试信号处理...\n");// 等待信号while(1) {pause();}return 0;
}6.信号集
注意:信号相关操作中,其实是操作信号的集合,例如,如果想把SIGINT这个信号放到阻塞集合,需要先把这个信号放到一个信号集合里,再把这个信号集合放到信号阻塞集中。
函数:
sigset_t 信号集合变量类型
#include <signal.h>
int sigemptyset(sigset_t *set); //将 set 集合置空
int sigfillset(sigset_t *set); //将所有信号加入 set 集合
int sigaddset(sigset_t *set, int signo); //将 signo 信号加入到 set 集合
int sigdelset(sigset_t *set, int signo); //从 set 集合中移除 signo 信号
int sigismember(const sigset_t *set, int signo); //判断信号是否存在代码案例:
#define _POSIX_SOURCE
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/time.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{sigset_t s;int ret = sigemptyset(&s);if (ret == 0){printf("集合置空成功\n");}else{printf("集合置空失败\n");}if (sigismember(&s,SIGINT) ){printf("SIGINT在集合中\n");}else{printf("SIGINT不在集合中\n");}sigaddset(&s,SIGINT);//添加信号到s集合中if (sigismember(&s,SIGINT) ){printf("SIGINT在集合中\n");}else{printf("SIGINT不在集合中\n");}return 0;
}
7.信号阻塞集
信号阻塞集也称信号屏蔽集、信号掩码。每个进程都有一个阻塞集,创建子进程时子进程将继承父进程的阻塞集。信号阻塞集用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它,直到进程准备好时再将信号通知进程)。信号阻塞的时候,不是不执行,而是延缓执行。
sigprocmask函数:
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);功能:
检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定,而原先的信号阻塞集合由 oldset 保存。
参数:
- how : 信号阻塞集合的修改方法,有 3 种情况:
- SIG_BLOCK:向信号阻塞集合中添加 set 信号集,新的信号掩码是 set 和旧信号掩码的并集。 相当于 mask = mask|set。
- SIG_UNBLOCK:从信号阻塞集合中删除 set 信号集,从当前信号掩码中去除 set 中的信号。相当于 mask = mask & ~ set。
- SIG_SETMASK:将信号阻塞集合设为 set 信号集,相当于原来信号阻塞集的内容清空,然后按照 set 中的信号重新设置信号阻塞集。相当于 mask = set。
- set : 要操作的信号集地址。
- 若 set 为 NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到oldset 中。
- oldset : 保存原先信号阻塞集地址
返回值:
成功:0,
失败:-1,失败时错误代码只可能是 EINVAL,表示参数 how 不合法。
代码案例:
#define _POSIX_SOURCE
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/time.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{sigset_t s;int ret = sigemptyset(&s);if (ret == 0){printf("集合置空成功\n");}else{printf("集合置空失败\n");}if (sigismember(&s,SIGINT) ){printf("SIGINT在集合中\n");}else{printf("SIGINT不在集合中\n");}sigaddset(&s,SIGINT);//添加信号到s集合中if (sigismember(&s,SIGINT) ){printf("SIGINT在集合中\n");}else{printf("SIGINT不在集合中\n");}int val = sigprocmask(SIG_BLOCK,&s,NULL); //不关系阻塞集里的数据,传nullif (val == 0){printf("SIGINT添加到阻塞集中\n");}else{printf("SIGINT添加失败中\n");}printf("5s后将SIGINT从阻塞集中清除\n");sleep(5);sigprocmask(SIG_UNBLOCK,&s,NULL); while(1){}return 0;
}
8.信号未决集
sigpending 函数
#include <signal.h>
int sigpending(sigset_t *set);
功能:
用于检查当前进程被阻塞(pending)的信号集合,即那些已经产生但尚未被处理的信号。
参数:
set:未决信号集
返回值:
成功:0
失败:-1
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>void print_pending_signals(sigset_t *pending_set) {printf("待处理信号: ");for (int sig = 1; sig < NSIG; sig++) {if (sigismember(pending_set, sig)) {printf("%d(%s) ", sig, sys_siglist[sig]);}}printf("\n");
}int main() {sigset_t pending_set;// 获取待处理信号集合if (sigpending(&pending_set) == -1) {perror("sigpending");return 1;}print_pending_signals(&pending_set);return 0;
}最后如果这篇文章给你带来了帮助,请点个关注吧 ≡ᐢ⸝⸝⬮ ω ⬮⸝⸝ᐢ≡
