当前位置: 首页 > news >正文

进程间通信-信号

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;
}

最后如果这篇文章给你带来了帮助,请点个关注吧       ≡ᐢ⸝⸝⬮ ω ⬮⸝⸝ᐢ≡

http://www.dtcms.com/a/534280.html

相关文章:

  • 成品网站源码免费分享做网站 使用权 所有权
  • 杭州网站优化企业如何选择wordpress主机
  • 外文网站建站网站建设的市场定位分析
  • 工程施工人员招聘网站关于二手书的网站开发ppt
  • 老渔哥网站建设公司wordpress模板文件命名
  • 网站备案系统登陆不上艺术网页设计欣赏
  • 网页不能运行wordpress网站推广和优化的原因
  • 大模型测试报告
  • 第二十周周报
  • 做网站编程要学什么网站在国内服务器在国外
  • 利用数字孪生技术打造智能工厂的“情境认知”能力
  • NewStarCTF2025-Week3-Pwn
  • 网站建设的基本要求手机网站开发视频教程
  • 购物网站建设需要什么资质wordpress install
  • 国内专业网站制作贺州住房和城乡建设部网站
  • Python 列表排序:快速掌握排序方法
  • 在盐城做网站的网络公司电话网站开发 参考文献
  • 奉化区建设局网站贵州省贵州省建设厅网站
  • 网站规划文案做移动网站优化
  • 郑州网站权重京东官方网上商城
  • C++ 类的学习(四) 继承
  • 企业网站建设开发四个阶段厦门企业网站排名优化
  • 深圳微商城网站制作多少钱郑州网站设计 郑州网站开发
  • 成都网站设计网站制作公司互联网门户网站建设
  • 国际网站怎么进免费分销方案如何打造更强的分销团队
  • 衡水制作网站哪些网站能够免费做公考题
  • 唐山网站制作系统做企业网站首页尺寸
  • 郑州哪家公司给国外做网站毛坯房最便宜装修方法
  • 电影网站开发任务书北京大型网站建设公司
  • 怎样选择网站服务器求职设计师的个人简历模板