进程间通信——POSIX 有名信号量与无名信号量
liebian365 2024-12-31 12:45 23 浏览 0 评论
前言
在 POSIX 系统中,进程间通信是一个很有意思的话题。
POSIX信号量进程是3种 IPC(Inter-Process Communication) 机制之一,3种 IPC 机制源于 POSIX.1 的实时扩展。Single UNIX Specification 将3种机制(消息队列,信号量和共享存储)置于可选部分中。在 SUSv4 之前,POSIX 信号量接口已经被包含在信号量选项中。在 SUSv4 中,这些接口被移至了基本规范,而消息队列和共享存储接口依然是可选的。
POSIX 信号量接口意在解决 XSI 信号量接口的几个缺陷。
相比于 XSI 接口,POSIX 信号量接口考虑了更高性能的实现。
POSIX 信号量使用更简单:没有信号量集,在熟悉的文件系统操作后一些接口被模式化了。尽管没有要求一定要在文件系统中实现,但是一些系统的确是这么实现的。
POSIX 信号量在删除时表现更完美。回忆一下,当一个 XSI 信号量被删除时,使用这个信号量标识符的操作会失败,并将 errno 设置成 EIDRM。使用 POSIX 信号量时,操作能继续正常工作直到该信号量的最后一次引用被释放。 ——摘自《UNIX高级环境编程(中文第3版)》465-466页
前段时间在写管道通信的时候,探究了一下 POSIX 进程间的两种信号量通信方式:有名信号量和无名信号量。有很多人认为进程间通信只能使用有名信号量,无名信号量只能用于单进程间的多线程通信。其实无名信号量也可以进行进程间通信。
区别
有名信号量和无名信号量的差异在于创建和销毁的形式上,但是其他工作一样。
无名信号量只能存在于内存中,要求使用信号量的进程必须能访问信号量所在的这一块内存,所以无名信号量只能应用在同一进程内的线程之间(共享进程的内存),或者不同进程中已经映射相同内存内容到它们的地址空间中的线程(即信号量所在内存被通信的进程共享)。意思是说无名信号量只能通过共享内存访问。
相反,有名信号量可以通过名字访问,因此可以被任何知道它们名字的进程中的线程使用。
单个进程中使用 POSIX 信号量时,无名信号量更简单。多个进程间使用 POSIX 信号量时,有名信号量更简单。
联系
无论是有名信号量还是无名信号量,都可以通过以下函数进行信号量值操作。
【文章福利】:小编整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!832218493群(需要自取)
wait
weit 为信号量值减一操作,总共有三个函数,函数原型如下:
#include <semaphore.h>
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
Link with -pthread.这一句表示 gcc 编译时,要加 -pthread.返回值:若成功,返回 0 ;若出错,返回-1
复制代码
其中,第一个函数的作用是,若 sem 小于 0 ,则线程阻塞于信号量 sem ,直到 sem 大于 0 ;否则信号量值减1。
第二个函数作用与第一个相同,只是此函数不阻塞线程,如果 sem 小于 0,直接返回一个错误(错误设置为 EAGAIN )。
第三个函数作用也与第一个相同,第二个参数表示阻塞时间,如果 sem 小于 0 ,则会阻塞,参数指定阻塞时间长度。 abs_timeout 指向一个结构体,这个结构体由从 1970-01-01 00:00:00 +0000 (UTC) 开始的秒数和纳秒数构成。结构体定义如下:
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds [0 .. 999999999] */
};
复制代码
如果指定的阻塞时间到了,但是 sem 仍然小于 0 ,则会返回一个错误 (错误设置为 ETIMEDOUT )。
post
post 为信号量值加一操作,函数原型如下:
#include <semaphore.h>
int sem_post(sem_t *sem);
Link with -pthread.返回值:若成功,返回 0 ;若出错,返回-1
复制代码
应用实例
有名信号量
创建
有名信号量创建可以调用 sem_open 函数,函数说明如下:
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);
Link with -pthread.返回值:若成功,返回指向信号量的指针;若出错,返回SEM_FALLED
复制代码
其中第一种函数是当使用已有的有名信号量时调用该函数,flag 参数设为 0 。
如果要调用第二种函数,flag 参数应设为 O_CREAT ,如果有名信号量不存在,则会创建一个新的,如果存在,则会被使用并且不会再初始化。
当我们使用 O_CREAT 标志时,需要提供两个额外的参数:
mode 参数指定谁可以访问信号量,即权限组,mode 的取值和打开文件的权限位相同,比如 0666 表示 所有用户可读写 。因为只有读和写访问要紧,所以实现经常为读和写打开信号量。
value 指定信号量的初始值,取值范围为 0~SEM_VALUE_MAX 。
如果信号量存在,则调用第二个函数会忽略后面两个参数(即 mode 和 value )。
释放
当完成信号量操作以后,可以调用 sem_close 函数来释放任何信号量的资源。函数说明如下:
#include <semaphore.h>
int sem_close(sem_t *sem);
Link with -pthread.返回值:若成功,返回 0 ;若出错,返回-1
复制代码
如果进程没有调用该函数便退出了,内核会自动关闭任何打开的信号量。无论是调用该函数还是内核自动关闭,都不会改变释放之前的信号量值。
销毁
可以使用 sem_unlink 函数销毁一个有名信号量。函数说明如下:
#include <semaphore.h>
int sem_unlink(const char *name);
Link with -pthread.返回值:若成功,返回 0 ;若出错,返回-1
复制代码
sem_unlink 函数会删除信号量的名字。如果没有打开的信号量引用,则该信号量会被销毁,否则,销毁会推迟到最后一个打开的引用关闭时才进行。
例子
例如,管道通信中,如果父进程使用 fork()创建两个子进程1和2,子进程1,2按顺序向管道写一段文字,最后父进程从管道将子进程写入的内容读出来,要保证进程执行的先后顺序,可以用有名信号量来解决。
#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<semaphore.h>
#include<sys/sem.h>
#include <sys/stat.h>
#include<fcntl.h>
int main(){
int pid1,pid2;
sem_t *resource1;
sem_t *resource2;
int Cpid1,Cpid2=-1;
int fd[2];//0为读出段,1为写入端
char outpipe1[100],inpipe[200],outpipe2[100];
pipe(fd);//建立一个无名管道
pid1 = fork();
if(pid1<0){
printf("error in the first fork!");
}else if(pid1==0){//子进程1
resource1=sem_open("name_sem1",O_CREAT,0666,0);
Cpid1 = getpid();
close(fd[0]);//关掉读出端
lockf(fd[1],1,0);//上锁,则锁定从当前偏移量到文件结尾的区域
sprintf(outpipe1,"Child process 1 is sending a message!");
write(fd[1],outpipe1,strlen(outpipe2));
lockf(fd[1],0,0);//解锁
sem_post(resource1);
sem_close(resource1);
exit(0);
}else{
pid2 = fork();
if(pid2<0){
printf("error in the second fork!\n");
}else if(pid2==0){
resource1=sem_open("name_sem1",O_CREAT,0666,0);
resource2=sem_open("name_sem2",O_CREAT,0666,0);
Cpid2 = getpid();
sem_wait(resource1);
close(fd[0]);
lockf(fd[1],1,0);
sprintf(outpipe2,"Child process 2 is sending a message!");
write(fd[1],outpipe2,strlen(outpipe2));
lockf(fd[1],0,0);//解锁
sem_post(resource2);
sem_close(resource1);
sem_close(resource2);
exit(0);
}
if(pid1 > 0 && pid2 >0){
resource2=sem_open("name_sem2",O_CREAT,0666,0);
sem_wait(resource2);
waitpid(pid1,NULL,0);
waitpid(pid2,NULL,0);
close(fd[1]);//关掉写端
read(fd[0],inpipe,200);
printf("%s\n",inpipe);
sem_close(resource2);
exit(0);
}
sem_unlink("name_sem1");
sem_unlink("name_sem2");
}
return 0;
}
复制代码
无名信号量
创建
无名信号量可以通过 sem_init 函数创建,函数说明如下:
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
Link with -pthread.返回值:若成功,返回 0 ;若出错,返回-1
复制代码
pshared 参数指示该信号量是被一个进程的多个线程共享还是被多个进程共享。
如果 pshared 的值为 0 ,那么信号量将被单进程中的多线程共享,并且应该位于某个地址,该地址对所有线程均可见(例如,全局变量或变量在堆上动态分配)。
如果 pshared 非零,那么信号量将在进程之间共享,并且信号量应该位于共享内存区域。
销毁
如果无名信号量使用完成,可以调用 sem_destory 函数销毁该信号量。函数说明如下:
#include <semaphore.h>
int sem_destroy(sem_t *sem);
Link with -pthread.返回值:若成功,返回 0 ;若出错,返回-1
复制代码
注意:
销毁其他进程或线程当前被阻塞的信号量会产生未定义的行为。 使用已销毁的信号量会产生未定义的结果,除非使用 sem_init 重新初始化信号量。 一个无名信号量应该在它所在的内存被释放前用 sem_destroy 销毁。如果不这样做,可能会导致某些实现出现资源泄漏。
例子
使用无名信号量实现有名信号量中的例子:
#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<semaphore.h>
#include<sys/sem.h>
#include <sys/stat.h>
#include <sys/shm.h>
#include<fcntl.h>
int main(){
int pid1,pid2;
int Cpid1,Cpid2=-1;
int fd[2];//0为读出段,1为写入端
char outpipe1[100],inpipe[200],outpipe2[100];
void *shm = NULL;
sem_t *shared;
int shmid = shmget((key_t)(1234), sizeof(sem_t *), 0666 | IPC_CREAT);//创建一个共享内存,返回一个标识符
if(shmid == -1){
perror("shmat :");
exit(0);
}
shm = shmat(shmid, 0, 0);//返回指向共享内存第一个字节的指针
shared = (sem_t *)shm;
sem_init(shared, 1, 0);//初始化共享内存信号量值为0
pipe(fd);//建立一个无名管道
pid1 = fork();
if(pid1<0){
printf("error in the first fork!");
}else if(pid1==0){//子进程1
Cpid1 = getpid();
close(fd[0]);//关掉读出端
lockf(fd[1],1,0);//上锁,则锁定从当前偏移量到文件结尾的区域
sprintf(outpipe1,"Child process 1 is sending a message!");
write(fd[1],outpipe1,strlen(outpipe1));
lockf(fd[1],0,0);//解锁
sem_post(shared);
exit(0);
}else{
pid2 = fork();
if(pid2<0){
printf("error in the second fork!\n");
}else if(pid2==0){
sem_wait(shared);
Cpid2 = getpid();
close(fd[0]);
lockf(fd[1],1,0);
sprintf(outpipe2,"Child process 2 is sending a message!");
write(fd[1],outpipe2,strlen(outpipe2));
lockf(fd[1],0,0);//解锁
exit(0);
}
if(pid1 > 0 && pid2 >0){
waitpid(pid2,NULL,0);//同步,保证子进程先写父进程再读
close(fd[1]);//关掉写端
read(fd[0],inpipe,200);
printf("%s\n",inpipe);
exit(0);
}
}
return 0;
}
作者:ACool47980
链接:https://juejin.cn/post/6844903622375899144
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关推荐
- 精品博文嵌入式6410中蓝牙的使用
-
BluetoothUSB适配器拥有一个BluetoothCSR芯片组,并使用USB传输器来传输HCI数据分组。因此,LinuxUSB层、BlueZUSB传输器驱动程序以及B...
- win10跟这台计算机连接的前一个usb设备工作不正常怎么办?
-
前几天小编闲来无事就跑到网站底下查看粉丝朋友给小编我留言询问的问题,还真的就给小编看到一个问题,那就是win10跟这台计算机连接的一个usb设备运行不正常怎么办,其实这个问题的解决方法时十分简单的,接...
- 制作成本上千元的键盘,厉害在哪?
-
这是稚晖君亲自写的开源资料!下方超长超详细教程预警!!全文导航:项目简介、项目原理说明、硬件说明、软件说明项目简介瀚文智能键盘是一把我为自己设计的——多功能、模块化机械键盘。键盘使用模块化设计。左侧的...
- E-Marker芯片,USB数据线的“性能中枢”?
-
根据线缆行业的研究数据,在2019年搭载Type-C接口的设备出货量已达到20亿台,其中80%的笔记本电脑和台式电脑采用Type-C接口,50%的智能手机和平板电脑也使用Type-C接口。我们都知道,...
- ZQWL-USBCANFD二次开发通讯协议V1.04
-
修订历史:1.功能介绍1.1型号说明本文档适用以下型号: ZQWL-CAN(FD)系列产品,USB通讯采用CDC类实现,可以在PC机上虚拟出一个串口,串口参数N,8,1格式,波特率可以根据需要设置(...
- win10系统无法识别usb设备怎么办(win10不能识别usb)
-
从驱动入手,那么win10系统无法识别usb设备怎么办呢?今天就为大家分享win10系统无法识别usb设备的解决方法。1、右键选择设备管理器,如图: 2、点击更新驱动程序,如图: 3、选择浏览...
- 微软七月Win8.1可选补丁有内涵,含大量修复
-
IT之家(www.ithome.com):微软七月Win8.1可选补丁有内涵,含大量修复昨日,微软如期为Win7、Win8.1发布7月份安全更新,累计为6枚安全补丁,分别修复总计29枚安全漏洞,其中2...
- 如何从零开始做一个 USB 键盘?(怎么制作usb)
-
分两种情况:1、做一个真正的USB键盘,这种设计基本上不涉及大量的软件编码。2、做一个模拟的USB键盘,实际上可以没有按键功能,这种的需要考虑大量的软件编码,实际上是一个单片机。第一种设计:买现成的U...
- 电脑识别U盘失败?5个实用小技巧,让你轻松搞定USB识别难题
-
电脑识别U盘失败?5个实用小技巧,让你轻松搞定USB识别难题注意:有些方法会清除USB设备里的数据,请谨慎操作,如果不想丢失数据,可以先连接到其他电脑,看能否将数据复制出来,或者用一些数据恢复软件去扫...
- 未知usb设备设备描述符请求失败怎么解决
-
出现未知daousb设备设备描述符请求失du败解决办zhi法如下:1、按下Windows+R打开【运行】;2、在版本运行的权限输入框中输入:services.msc按下回车键打开【服务】;2、在服务...
- 读《飘》47章20(飘每章概括)
-
AndAhwouldn'tleaveMissEllen'sgrandchildrenfornotrashystep-patobringup,never.Here,Ah...
- 英翻中 消失的过去 37(消失的英文怎么说?)
-
翻译(三十七):消失的过去/茱迪o皮考特VanishingActs/JodiPicoult”我能做什么?“直到听到了狄利亚轻柔的声音,我才意识到她已经在厨房里站了好一会儿了。当她说话的时候,...
- RabbitMQ 延迟消息实战(rabbitmq如何保证消息不被重复消费)
-
现实生活中有一些场景需要延迟或在特定时间发送消息,例如智能热水器需要30分钟后打开,未支付的订单或发送短信、电子邮件和推送通知下午2:00开始的促销活动。RabbitMQ本身没有直接支持延迟...
- Java对象拷贝原理剖析及最佳实践(java对象拷贝方法)
-
作者:宁海翔1前言对象拷贝,是我们在开发过程中,绕不开的过程,既存在于Po、Dto、Do、Vo各个表现层数据的转换,也存在于系统交互如序列化、反序列化。Java对象拷贝分为深拷贝和浅拷贝,目前常用的...
- 如何将 Qt 3D 渲染与 Qt Quick 2D 元素结合创建太阳系行星元素?
-
Qt组件推荐:QtitanRibbon:遵循MicrosoftRibbonUIParadigmforQt技术的RibbonUI组件,致力于为Windows、Linux和MacOSX提...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)