百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

进程间通信——POSIX 有名信号量与无名信号量

liebian365 2024-12-31 12:45 17 浏览 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 。

如果信号量存在,则调用第二个函数会忽略后面两个参数(即 modevalue )。

释放

当完成信号量操作以后,可以调用 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
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

相关推荐

4万多吨豪华游轮遇险 竟是因为这个原因……

(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...

“菜鸟黑客”必用兵器之“渗透测试篇二”

"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...

科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白

作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...

麦子陪你做作业(二):KEGG通路数据库的正确打开姿势

作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...

知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势

智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...

每日新闻播报(September 14)_每日新闻播报英文

AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...

香港新巴城巴开放实时到站数据 供科技界研发使用

中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...

5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper

本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...

Qt动画效果展示_qt显示图片

今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...

如何从0到1设计实现一门自己的脚本语言

作者:dong...

三年级语文上册 仿写句子 需要的直接下载打印吧

描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...

C++|那些一看就很简洁、优雅、经典的小代码段

目录0等概率随机洗牌:1大小写转换2字符串复制...

二年级上册语文必考句子仿写,家长打印,孩子照着练

二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...

一年级语文上 句子专项练习(可打印)

...

亲自上阵!C++ 大佬深度“剧透”:C++26 将如何在代码生成上对抗 Rust?

...

取消回复欢迎 发表评论: