进程间通信 [3] —— 信号SIGNAL、信号量SEMAPHORE
原创首发于CSDN,转载请注明出处,谢谢! https://blog.csdn.net/weixin_46959681/article/details/114527183
文章目录
- 什么是信号
- 信号的处理
- |信号处理函数初级版 signal
- |信号处理函数高级版 sigaction & sigqueue
- 信号量
- 参考资料
- 文章更新记录
什么是信号
实际上信号在操作系统中为 软中断 ,基本上所有的程序都需要处理信号。信号为计算机系统提供了一种处理异步事件的方法。例如,因程序编译运行错误导致终端打印大量乱码,此时仅需输入Ctrl + C
,计算机系统自身的信号机制会强制停止正在运行的错乱程序。
信号的处理
信号的处理有三种方法:
- 捕捉信号。告诉内核,用户希望如何处理某一种信号。编写一个信号处理函数,将该函数传给内核。当信号产生时,由内核来调用用户自定义的函数,实现某种信号的处理。另外,通过自定义编程的信号处理函数可以“修改”系统的默认动作(如
Crtl + C
,具体编程实例请看下文);- 忽略信号。大多数信号可使用该方式处理,但有两种信号不能被忽略(SIGKILL、SIGSTOP)。因其向内核和超级用户提供了进程终止和停止的可靠方法,若忽略则某个进程就变成了没人能管理的的进程;
- 系统的默认动作。所有被设计好的函数在系统中都有默认的处理动作,当产生该信号,系统会自动执行。实际工作中绝大部份的处理方式都直接了当,就是直接杀死该进程 “kill -9 pid” 。
|信号处理函数初级版 signal
函数原型:
#include <signal.h>
//实际的信号处理函数。
typedef void (*sighandler_t)(int);
//信号注册函数。signum为信号的编号,handler为中断函数的指针。
sighandler_t signal(int signum, sighandler_t handler);
通过信号处理的初级函数 signal ,以指令 Ctrl + C
为例实现 信号捕捉信号捕捉信号捕捉 与 默认动作修改默认动作修改默认动作修改。
演示代码:signal1.c
/* signal1.c */
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>void handler(int signum)
{printf("Get signum = %d.\n",signum);switch(signum){case 2:printf("SIGINT\n");break;case 10:printf("SIGUSR1\n");}printf("Never quit.\n");
}int main(){signal(SIGINT,handler);signal(SIGUSR1,handler);while(1);return 0;
}
运行以上代码,当输入 Ctrl + C 时终端显示 signum = 2 ,能看到系统默认的强制退出动作经过以上编写的自定义信号函数后“无效”了,唯有通过指令 killkillkill 杀死该进程。
输入 Ctrl + C 强制退出时 | 使用 kill 命令处理 |
---|---|
|信号处理函数高级版 sigaction & sigqueue
signalsignalsignal 是初级的信号处理函数,仅能发出处理的“动作”。若是要进一步携带其他消息,如 信号值、字符串、PID号、时钟、内存地址 等等内容,则需要更高级的信号处理函数 sigactionsigactionsigaction 、sigqueuesigqueuesigqueue。
对于这两个高级信号处理函数的构造、具体参数等等细节笔者还在摸索阶段,请读者自行使用 man 手册以及阅读参考资料里的博文 —— Linux 信号(signal)。笔者这里直接贴出教学视频里的两段代码,感兴趣的读者请细细品味。
第一步:运行接收端代码文件 sigaction.c
生成的编译文件 receive
。
/* sigaction.c */
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
//发送端所发的一切信息都可用指针info读取。
void handler(int signum, siginfo_t *info,void *context)
{printf("Get signum %d\n",signum);//接收信号并判断内容是否为空。if(context != NULL){printf("Get data = %d\n",info->si_int);//printf("Get data = %d\n",info->si_value.sival_int);printf("From Send's pid = %d\n",info->si_pid);}
}int main()
{struct sigaction act;printf("Receive's pid = %d\n",getpid());act.sa_sigaction = handler;//宏,表示能接收数据。act.sa_flags = SA_SIGINFO;//int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);sigaction(SIGUSR1,&act,NULL); while(1);return 0;
}
第二步:运行发送端代码文件 sigqueue.c
生成的编译文件 send
。
/* sigqueue.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc, char **argv)
{int signum;int pid;signum = atoi(argv[1]);pid = atoi(argv[2]);union sigval value;value.sival_int = 10000;//int sigqueue(pid_t pid, int sig, const union sigval value);sigqueue(pid,signum,value);printf("Send's pid = %d\n",getpid());printf("Over.\n");return 0;
}
运行结果:
发送端 | 接收端 |
---|---|
信号量
信号量(semaphore) 与笔者在前两篇博文中介绍的通信机制 —— 管道 、消息队列与共享内存 —— 不同, 本质上可视为一个 计数器 。信号量具备初始值,若 初始值 > 0,说明其处于“空闲状态”;若 初始值 < 0,说明其处于“占用状态”。当其处于“占用状态”时,不论是进城或线程,都要处于等待状态,等待被“唤醒”(PV操作)。
信号量用于实现系统进程间的互斥与同步以及同一进程的不同线程的同步,而不是用来存储进程间的通信数据。
拓展:
- 同步:处理 Linux 系统里进程的竞争就是同步,安排进程执行的先后顺序就是同步,每个进程都有一定的个先后执行顺序;
- 互斥:访问不可共享的临界资源(如物理设备),会引发两个新的控制问题(互斥可以被说成特殊的同步吗?);
- 竞争:当父子进程又或者并发进程竞争使用同一个资源的时候,我们就称为竞争进程。
函数原型:
//创建一个信号量组,成功返回信号量集ID,失败返回-1。
int semget(key_t key,int nsems,int semflg);
//对信号量组进行操作,改变信号量的值(PV操作)。
int semop(int semid, struct sembuf *sops, size_t nsops);
//控制信号量的相关信息。
int semctl(int semid, int semnum, int cmd, ...);
上文中的 PVPVPV 操作笔者在这里给出一小段含 fork()
函数的演示代码,对于代码中更加细节的部分如 sembuf 等,已经有大量的博客给出了相应的解释和实例代码(请看最下行的参考博文),这里不再多费笔墨。
演示代码:semaphore.c
/* semaphore.c */
#include <stdio.h>
#include <error.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
union semun
{int val; /* Value for SETVAL */struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */unsigned short *array; /* Array for GETALL, SETALL */struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */
};void sem_p(int sem_id)
{struct sembuf set;set.sem_num = 0;set.sem_op = -1;set.sem_flg = SEM_UNDO;semop(sem_id,&set,1);printf("sem_p OK.\n");
}void sem_v(int sem_id)
{struct sembuf set;set.sem_num = 0;set.sem_op = 1;set.sem_flg = SEM_UNDO;semop(sem_id,&set,1);printf("sem_v OK.\n");
}int main(int argc, char **argv[])
{int sem_id;pid_t pid;key_t key;key = ftok(".",'o');printf("key = %x\n",key);//创建信号量。sem_id = semget(key,1,IPC_CREAT|0666);//设置联合体内信号量的值,初始化信号量组以及信号量值为零。union semun initsem;initsem.val = 0;semctl(sem_id,0,SETVAL,initsem);pid = fork();if(pid > 0){//父进程。sem_p(sem_id);printf("Process father: pid = %d.\n",getpid());sem_v(sem_id);semctl(sem_id,0,IPC_RMID);//删除信号量集。printf("sem_del OK.\n");}else if(pid == 0){//子进程。printf("Process child: pid = %d.\n",getpid());sem_v(sem_id);}else{printf("Fork fail.\n");}return 0;
}
运行结果:
主函数内调用函数 fork()fork()fork() 产生父子进程,当父进程运行时因演示代码中联合体内的信号量初始值为零 initsem.val=0initsem.val = 0initsem.val=0 ,无法执行 P 操作 sem_p 进入等待状态,系统进程跳转先执行子进程。子进程执行完毕后,经过完整的 PVPVPV 操作,父进程得以继续执行完毕。
笔者没有学习过计算机操作系统,上述一段只能从代码层面自圆其说,显得过于粗浅。
参考资料
- 参考博客 进程间通信的五种方式
- 参考博客 进程间的五种通信方式介绍-详解
- 知乎文章 进程间通信的方式(四):信号量
文章更新记录
- 文章框架完成。「2021.3.8 12:05」
- “信号的处理”一节完成。 「2022.3.15 11:21」
- “信号处理函数高级版 sigaction & sigqueue”一节完成。「2022.3.17 22:50」
- “信号量”一节完成。「2021.3.19 17:24」
P.S. 《计算机操作系统》与《计算机组成原理》是每一个嵌入式开发者必备的计算机内功心法。
进程间通信 [3] —— 信号SIGNAL、信号量SEMAPHORE相关推荐
- 进程间通信之信号he信号量
信号的篇幅较少,就把他和信号量放在一起了.先讲讲他们之间的区别: 1.信号:(signal)是一种处理异步事件的方式.信号时比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程外,还可以发 ...
- Linux进程间通信(管道、消息队列、共享内存、信号、信号量)
目录 Linux进程间通信概述 1.管道 无名管道(pipe) 有名管道(fifo) 2.消息队列(msg) 消息队列的通信原理 消息队列相关api 消息队列收发数据 键值生成 消息队列移除 3.共享 ...
- 进程间通信IPC(二)(共享内存、信号、信号量)
共享内存: 共享内存就是允许两个或多个进程共享一定的存储区.就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针.当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更 ...
- Linux进程间通信之管道(pipe)、命名管道(FIFO)与信号(Signal)
整理自网络 Unix IPC包括:管道(pipe).命名管道(FIFO)与信号(Signal) 管道(pipe) 管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道 ...
- python进程间通信--信号Signal
信号signal 是python进程间通信多种机制中的其中一种机制.可以对操作系统进程的控制,当进程中发生某种原因而中断时,可以异步处理这个异常. 信号通过注册的方式'挂'在一个进程中,并且不会阻塞该 ...
- Linux进程间通信第三讲 信号signal kill
目录 三.信号(signal) 3.1 概念 3.2 信号的处理 3.3 信号的发送 3.4 信号的屏蔽 三.信号(signal) 3.1 概念 信号本质上是一种软件中断 软件触发的中断.和硬件的处理 ...
- Linux系统编程【文件IO、进程、进程间通信、信号、线程、互斥】
linux系统编程 个人通过学习,手打了一份48000字的Linux系统编程的笔记,包含了[文件IO.进程.进程间通信.信号.多线程.互斥]等知识点,并给出了大量的代码案例对每个重要的知识点进行了代码 ...
- 进程间通信、死锁、信号量、PV原语
文章目录 进程间通信(Inter-Process Communication, IPC) 顺序程序与并发程序的特征 进程互斥.临界资源.临界区 同步的细分 进程间通信目的 进程间通信发展 进程间通信分 ...
- 进程间通信(三)—信号量
我会用几篇博客总结一下在Linux中进程之间通信的几种方法,我会把这个开头的摘要部分在这个系列的每篇博客中都打出来 进程之间通信的方式 管道 消息队列 信号 信号量 共享存储区 套接字(socket) ...
最新文章
- 带您走进松本行弘的程序世界
- 2021年吴文俊人工智能科学技术奖获奖名单公示!潘云鹤院士获最高成就奖
- TextBox中的KeyDown 时间不能响应的问题!
- mano安全_爱立信:O-RAN存在的安全风险
- 计算机科学学院参加些什么比赛,计算机科学学院学生在“2018年中国大学生计算机设计大赛(西北赛区)”中喜获佳绩...
- rabbitmq在web管理界面登录失败login fail
- Spring Data JPA 从入门到精通~查询方法的创建
- Linux 服务器拷贝远程文件 SCP
- 1024 - 十月里 - 金桂飘香 - 送给喜欢编程的你
- 索引-css-第二版-pyhui
- Terraform学习总结(2)——Terraform 语法详解
- 数学之美-隐含马尔可夫模型-笔记
- OD使用教程23 - 调试篇23
- 库存管理软件挑选窍门:怎么才算合格的库存管理软件?
- 联想微型计算机m73拆机,联想M73更换处理器 | 更换i3 4330t处理器_什么值得买
- LiquidCrystal_I2C 显示不正常 只显示第一个首字符!
- DirectX修复工具使用技巧之二——手动修复C++创建失败的文件
- linux中命令du -sm,Linux中du命令使用介绍
- Unity获取物体下的子物体+只获取子物体
- 黑暗城堡(最短路径树)
热门文章
- 往自己脖子以上的投资永远不会贬值
- aws mysql 费用_AWS都收了哪些费用?
- 看了我的 mybatis-plus 用法,全公司同事开始悄悄模仿了。。
- java创建一定长度的list_java第三季中对String泛型的List进行排序(随机生成长度不超过10的字符串)...
- vmware和hyper-v的一个坑
- Oracle 数据库
- hcie培训价格多少钱?
- 基于Unity尝试唇同步/LipSync/OVRLipSync(附Demo及源码)
- HTML5经典面试题
- Matlab从细胞型转换为矩阵From cell to matrix