Linux系统编程系列之进程间通信-信号量组

news2024/12/27 4:20:11

一、什么是信号量组

        信号量组是信号量的一种,  是system-V三种IPC对象之一,是进程间通信的一种方式。

二、信号量组的特性

        信号量组不是用来传输数据的,而是作为“旗语”,用来协调各进程或者线程工作的。信号量组可以一次性在其内部设置多个信号量,而信号量本质上是一个数字,用来表征一种资源的数量,当多个进程或者线程争夺这些稀缺资源的时候,信号量用来保证他们合理地,秩序地使用这些资源,而不会陷入逻辑谬误之中。

三、信号量组的使用场景

        1、生产者-消费者模式

        2、进程间同步

        3、进程间通信

四、函数API接口

        1、创建或者打开SEM对象

// 创建或打开SEM对象
int semget(key_t key, int nsems, int semflg);

// 接口说明:
    参数key:SEM对象键值
    参数nsems:信号量组内的信号量元素个数
    参数semflg:创建选项
        IPC_CREAT:如果该key对应的信号量不存在,则创建
        IPC_EXCL:如果该key对应的信号量已存在,则报错
        mode:信号量的访问权限

创建信号量时,还受到以下系统信息的影响:
1、SEMMNI:系统中信号量的总数最大值
2、SEMMSL:每个信号量中信号量元素的个数最大值
3、SEMMNS:系统中所有信号量中的信号量元素的总数最大值

        2、P/V操作

        对于信号量而言,最重要的作用是用来表征对应资源的数量,所谓的P/V操作就是对资源数量进行 +n/-n 操作,既然只是个加减法,那么为什么不使用普通的整型数据呢,原因是:

        (1)、整型数据的加减操作不具有原子性,即操作可能被中断

        (2)、普通加减法无法提供阻塞特性,而申请资源不可得时应该进入阻塞

// PV操作
int semop(int semid, struct sembuf *sops, size_t nsops);

// 接口说明
    参数semid:SEM对象ID
    参数sops:PV操作结构体sembuf数组
    参数nsops:PV操作结构体数组元素个数
    返回值:成功 0,失败 -1

PV操作结构体定义如下:
struct sembuf
{
    unsigned short sem_num;    // 信号量元素序号(数组下标)
    short sem_op;     // 操作参数
    short sem_flg;    // 操作选项
}

根据sem_op的数值,信号量操作分成3种情况:
    (1)当sem_op大于0时:
            当进行V操作(释放),即信号量元素的值(semval)将会被加上sem_op的值。如果SEM_UNDO被设置了,那么该V操作将会被系统记录,V操作永远不会导致进程阻塞。
    (2)当sem_op等于0时:进行等零操作,如果此时semval恰好为零,则semop()立即成功返回,否则如果IPC_NOWAIT被设置,则立即出错返回并将errno设置为EAGAIN,否则将使得进程进入睡眠,直到以下情况发生:
    [1]semval变为0
    [2]信号量被删除 (将导致semop()出错退出,错误码为EIDRM)
    [3]收到信号 (将导致semop()出错退出,错误码为EINTR)

    (3)当sem_op小于0时(申请资源):进行P操作,即信号量元素的值(semval)将会被减去sem_op的绝对值。如果semval大于或等于sem_op的绝对值,则semop()立即成功返回,semval的值将减去sem_op的绝对值,并且如果SEM_UNDO被设置了,那么该P操作将会被系统记录。
如果semval小于sem_op的绝对值并且设置了IPC_NOWAIT,那么semop()将会出错返回且将错误码置为EAGIN,否则将使得进程进入睡眠,直到以下情况发生:
    [1]semval的值变得大于或者等于sem_op的绝对值
    [2]信号量被删除 (将导致semop()出错退出,错误码为EIDRM)
    [3]收到信号 (将导致semop()出错退出,错误码为EINTR)

        3、删除SEM对象

// 删除SEM对象
int semctl(int semid, int semnum, int cmd, ...);

// 接口说明
    semid:信号量组的ID
    semnum:信号量组内的元素序号(从0开始)
    cmd;操作命令字
        IPC_STAT:获取信号量组的一些信息,放入结构体semid_ds中
        IPC_SET:将结构体semid_ds中指定的信息,设置到信号量组中
        IPC_RMID:删除指定的信号量组
        GETALL:获取所有信号量元素的值
        SETALL:设置所有信号量元素的值
        GETVAL:获取第semnum个信号量元素的值
        SETVAL:设置第semnum个信号量的值
    

五、信号量组使用步骤

        1、使用ftok(),获取IPC通信对象KEY值

        2、使用semget(),获取SEM对象ID,并判断是否需要进行初始化

        3、使用semop(),进行P/V操作,操作信号量组

        4、使用命令或者函数删除信号量组

六、案例

        使用信号量组结合共享内存的方式完成两个进程的数据收发。

// 信号量组结合共享内存的案例

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <errno.h>

// 编译时分两个版本,一个直接编译,另外一个把A的宏定义注释,把B的宏定义展开
#define A 1
//#define B 1  // 编译第二版本时,请去掉前面的注释,同时注释A的宏定义

// 注意A进程的P信号量与B进程的V信号量相对应,所以要修改信号量序号的下标
#if A
#define DATA_P_NUM  0
#define DATA_V_NUM  1
#define SPACE_P_NUM 2
#define SPACE_V_NUM 3

#elif B
#define DATA_P_NUM  1
#define DATA_V_NUM  0
#define SPACE_P_NUM 3
#define SPACE_V_NUM 2
#endif


#define SEM_NUM 4       // 4个信号量
#define SEM_KEY 0x01
#define SHM_KEY 0x02
#define SHM_SIZE 4096

int sem_id = -1;
// 映射的虚拟地址
char *shm_addr = NULL;

// 信号量组初始化
int sem_init(void)
{
     // 1、获取IPC对象的KEY值
    key_t sem_key = ftok("./", SEM_KEY);
    if(sem_key == -1)
    {
        perror("ftok fail");
        return -1;
    }

    // 2、获取SEM对象的ID, 申请4个信号量
    sem_id = semget(sem_key, SEM_NUM, IPC_EXCL | IPC_CREAT | 0666);
    // 如果已经存在就不需要初始化,直接获取
    if(sem_id == -1 && errno == EEXIST)
    {
        // 直接获取SEM对象ID
        sem_id = semget(sem_key, SEM_NUM, IPC_CREAT | 0666);
        if(sem_id == -1)
        {
            perror("semget fail");
            return -1;
        }
    }
    // 不存在则需要在获取SEM对象ID后进行初始化
    else if(sem_id > 0)
    {
        
        sem_id = semget(sem_key, SEM_NUM, IPC_CREAT | 0666);
        if(sem_id == -1)
        {
            perror("semget fail");
            return -1;
        }
        // 初始化
        semctl(sem_id, DATA_P_NUM, SETVAL, 0);   // 初始值为0
        semctl(sem_id, DATA_V_NUM, SETVAL, 0);   // 初始值为0
        semctl(sem_id, SPACE_P_NUM, SETVAL, 1);   // 初始值为1
        semctl(sem_id, SPACE_V_NUM, SETVAL, 1);   // 初始值为1
    }
    else
    {
        perror("semget fail");
        return -1;
    }
}

// 共享内存初始化
int shm_init(void)
{
    // 1、获取KEY值
    key_t shm_key = ftok("./", 1);
    if(shm_key == -1)
    {
        perror("ftok fail");
        return -1;
    }

    // 2、指定共享内存,获取共享内存对象ID
    int shm_id = shmget(shm_key, SHM_SIZE, IPC_CREAT | 0666);
    if(shm_id == -1)
    {
        perror("shmget fail");
        return -1;
    }

    // 3、映射共享内存
    shm_addr = (char*)shmat(shm_id, NULL, 0);
    if(shm_addr == (void*)-1)
    {
        perror("shmat fail");
        return -1;
    }
}
    


int main(int argc, char *argv[])
{
    int ret = 0;
    ret = sem_init();
    if(ret == -1)
    {
        return -1;
    }

    ret = shm_init();
    if(ret == -1)
    {
        return -1;
    }

    // 接收数据, 数据-1
    struct sembuf Data_P = 
    {
        .sem_flg = SEM_UNDO,
        .sem_num = DATA_P_NUM,
        .sem_op = -1
    };

    // 发送数据, 数据+1
    struct sembuf Data_V = 
    {
        .sem_flg = SEM_UNDO,
        .sem_num = DATA_V_NUM,
        .sem_op = 1
    };

    // 占用空间, 空间-1
    struct sembuf Space_P = 
    {
        .sem_flg = SEM_UNDO,
        .sem_num = SPACE_P_NUM,
        .sem_op = -1
    };

    // 释放空间 空间+1
    struct sembuf Space_V = 
    {
        .sem_flg = SEM_UNDO,
        .sem_num = SPACE_V_NUM,
        .sem_op = 1
    };


    pid_t pid = fork();
    // 父进程负责发送数据
    if(pid > 0)
    {
        while(1)
        {
            // 申请空间,P操作
            printf("wait Space_P...\n");
            semop(sem_id, &Space_P, 1);
            printf("get Space_P\n");

            printf("please input data: \n");
            fgets(shm_addr, SHM_SIZE, stdin);

            // 释放数据,V操作
            semop(sem_id, &Data_V, 1);
            printf("set Data_V, send data success\n");
        }
    }
    // 子进程负责接收数据
    else if(pid == 0)
    {
        while(1)
        {
            // 申请数据,P操作
            printf("wait Data_P...\n");
            semop(sem_id, &Data_P, 1);

            printf("read Data: %s", shm_addr);
            memset(shm_addr, 0, SHM_SIZE);

            // 释放空间,V操作
            semop(sem_id, &Space_V, 1);
            printf("set Space_V\n");
        }
    }
    else
    {
        perror("fork fail");
        return -1;
    }

    return 0;
}

        注:编译时,编译两个版本,一个直接编译,另外一个需要注释A的宏定义,然后展开B的宏定义后才能编译第二个版本。

        分析:具体的PV操作这里不讲解,为什么要申请4个信号量,这个要讲明白的话,很难,有空再出另外一篇博客讲,敬请留意。

七、总结

        信号量组只能作为一种信号,不能用来传递数据,多用于使用P/V操作的场景,可以同时操作多个信号量,但是要实现传递数据,必须配合其他通信方式,如共享内存。可以结合案例来加深对信号量组的理解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1054151.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】 OpenSSH_9.3p1 升级到 OpenSSH_9.3p2(亲测无问题,建议收藏)

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; &#x1f40b; 希望大家多多支…

基于Python3搭建qt开发环境

Python可视化编程相信大部分刚接触都是tkinter&#xff0c;tkinter是Python自带的库&#xff0c;不需要安装第三方库即可使用&#xff0c;在我的Python专栏中也有很多基于tkinter来设计的可视化界面。本篇文章将尝试另外一个Python的可视化编程库(pyqt)&#xff0c;与tkinter编…

深度剖析Linux磁盘分区 | LVM逻辑卷 | VDO卷 | AutoFS存储自动挂载

深度剖析Linux磁盘分区 | LVM逻辑卷 | VDO卷 | AutoFS存储自动挂载 前言说明1. 安装操作系统分区配置2. 大磁盘分区管理3. LVM逻辑卷管理3.1. 创建LVM逻辑卷3.1.1. 创建物理卷PV3.1.2. 创建卷组VG3.1.3. 创建逻辑卷LV3.1.4. 创建并挂载文件系统3.1.5. 配置开机自动挂载 3.2. 逻…

bypass disable_function 学习

LD_PRELOAD 我是在做了 buu的 REC ME 来做这个系列 所以 LD_PRELOAD 已经有了解了 我们来做这个题目 CTFHub Bypass disable_function —— LD_PRELOAD本环境来源于AntSword-Labs <!DOCTYPE html> <html> <head><title>CTFHub Bypass disable_func…

零基础Linux_9(进程)环境变量+进程地址空间+进程创建fork

目录 1. 环境变量 1.1 环境变量基本概念 1.2 环境变量PATH 1.3 环境变量HOME和SHELL 1.4 获取环境变量&#xff08;main函数参数&#xff09; 1.4.1 main函数第三个参数 1.4.2 设置普通变量和环境变量 1.4.3 main函数前两个参数 2. 进程地址空间 2.1 验证进程地址空…

技术Leader对下管理的法宝-SMART

SMART方法论 源于国外管理大师的《管理的实践》是管理者能够更加明确员工高效工作的利器&#xff0c;科学、规范的对员工绩效制定考核目标和考核标准5个单词缩写 Specific:目标要具体Measurable:目标成果要可衡量(量化) Attainable:目标要可实现&#xff0c;避免过高/过低Rel…

为什么字节大量用GO而不是Java?

见字如面&#xff0c;我是军哥。 我看很多程序员对字节编程语言选型很好奇&#xff0c;为此我还特地问了在字节的两位4-1的技术大佬朋友&#xff0c;然后加上自己的思考&#xff0c;总结了一下就以下 2 个原因&#xff1a; 1、 选型上没有历史包袱 字节的早期的程序员大多来自于…

OpenHarmony Trace的使用

背景&#xff1a; 近期很多开发者反馈OpenHarmony三方库Imageknife有性能问题&#xff1a;连续拖动很多张图片时&#xff0c;界面有明显的卡顿现象。 因为对这个三方库的源码并不了解&#xff0c;因此需要了解目前Imageknife渲染花费了多少时间&#xff0c;最初想的是只有通过…

不愧是疑问解决神器!你强任你强

不愧是疑问解决神器&#xff01;你强任你强&#x1f44d;&#x1f44d;&#x1f44d; 在过去&#xff0c;我习惯用这种方式来阅读书籍或文章&#xff1a;先快速浏览一遍&#xff0c;然后再进行复读&#xff0c;并最终总结所学的知识点。然而&#xff0c;长期以来&#xff0c;我…

VMware中虚拟机没网

VMware中虚拟机没网 1 打开服务2 还原虚拟机网络设置 1 打开服务 2 还原虚拟机网络设置 ————————————————————— 以上就是今日博客的全部内容了 创作不易,若对您有帮助,可否点赞、关注一二呢,感谢支持.

scrapy爬取图片

文章目录 ImagesPipeline使用步骤&#xff1a;1. 数据解析&#xff1a; 获取图片的地址 & 2. 将存储图片地址的item提交到指定的管道类&#xff08;hotgirls.py&#xff09;3. 在管道文件中自制一个基于ImagesPipeLine的一个管道类&#xff01;&#xff01;天大的坑 &#…

Linux常见操作命令(1)

​ 前言&#xff1a;作者也是初学Linux&#xff0c;可能总结的还不是很到位 ♈️今日夜电波&#xff1a;达尔文—林俊杰 0:30━━━━━━️&#x1f49f;──────── 4:06 &#x1f504; ◀️ …

容易理解的归并排序(C语言)

以二路归并为例&#xff0c;可简单理解为&#xff1a; 把一个大的数组拆分成两个数组&#xff0c;拆出来的两个数组再分别拆出两个数组&#xff0c;以此类推&#xff0c;直到每个数组只有一个元素。然后将这些只有一个元素的数组两两合并到一个临时数组&#xff0c;即可完成排序…

ffmpeg、ffplay在线安装,离线导出整个程序,移植到其他服务器使用(linux系统)

环境说明 以ubuntu系统作为说明 在线安装 下面命令会同时安装ffplay和ffmpeg sudo apt-get install ffmpeg怎么验证安装成功&#xff1f; 输入ffmpeg命令 ffmpeg&#xff0c;如图则说明安装成功 转储可执行程序和依赖的文件 找到安装路径&#xff0c;一般在/usr/bin目录…

Redis与分布式-哨兵模式

接上文 Redis与分布式-主从复制 1.哨兵模式 启动一个哨兵&#xff0c;只需要修改配置文件即可&#xff0c; sentinel monitor lbwnb 1247.0.0.1 6001 1先将所有服务关闭&#xff0c;然后修改配置文件&#xff0c;redis Master&#xff0c;redis Slave&#xff0c;redis Slave…

buuctf-[Zer0pts2020]Can you guess it?

点击source&#xff0c;进入源代码 <?php include config.php; // FLAG is defined in config.phpif (preg_match(/config\.php\/*$/i, $_SERVER[PHP_SELF])) {exit("I dont know what you are thinking, but I wont let you read it :)"); }if (isset($_GET[so…

nodejs缓存策略

nodejs缓存策略 缓存是一种强大的技术&#xff0c;可以显著提高nodejs应用程序的性能。 在现代Web应用程序中&#xff0c;性能和效率是直接影响用户体验和应用程序成功的关键因素。 通过将频繁访问的数据存储在缓存中&#xff0c;我们可以减少对昂贵的计算或外部API调用的需求…

记录:Unity脚本的编写

目录 前言添加脚本到unity编写c#脚本查看效果 前言 在学习软件构造这门课的时候&#xff0c;对unity和c#进行了 一定程度的学习&#xff0c;包括简单的建立地形&#xff0c;添加对象&#xff0c;添加材质等&#xff0c;前不久刚好学习了如何通过c#脚本对模型进行操控&#xff…

加入PreAuthorize注解鉴权之后NullPointerException报错

记录一次很坑的bug&#xff0c;加入PreAuthorize注解鉴权之后NullPointerException报错&#xff0c;按理来说没有权限应该403报错&#xff0c;但是这个是500报错&#xff0c;原因是因为controller层的service注入失败&#xff0c;然而我去掉注解后service注入成功&#xff0c;并…

简历项目优化关键方法论-START

START方法论是非常著名的面试法则&#xff0c;经常被面试官使用的工具 Situation:情况、事情、项目需求是在什么情况下发生Task:任务&#xff0c;你负责的做的是什么Action:动作&#xff0c;针对这样的情况分析&#xff0c;你采用了什么行动方式Result:结果&#xff0c;在这样…