Linux 系统下的进程间通信 IPC 入门 中

以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「ENG八戒」mp.weixin.qq.com/s/39XQUQtGC...

信号量

信号量一般用于配合共享内存的数据传输,共享内存被多个进程之间共享访问,各个进程对共享内存的访问必须被同步才安全和有效。

申请信号量资源时,返回的是一组信号量集合,包含多个信号量,信号量 id 对应的是信号量集合,而不是单个信号量,每个信号量可以分别控制各种同步。信号量通过序号指定,序号从 0 开始。

系统范围内,可以申请的最多信号量集合数为 32000 个,每个信号量集合最多包含的信号量为 32000 个,每个信号量调用的最大操作数是 500,信号量值最大可达 32767。如果需要查看限制值,可以:

shell 复制代码
$ cat /proc/sys/kernel/sem

通过 semget() 申请信号量集合,参数 key 指定 IPC key,参数 nsems 指定包含的信号量数量。参数 semflg = IPC_CREAT | IPC_EXCL 指定尝试创建信号量,如果已经存在则返回失败,失败后可重新尝试获取。

ini 复制代码
int semid = semget(key, nsems, IPC_CREAT | IPC_EXCL | 0666);
if (-1 == semid) {  // failure
    semid = semget(key, nsems, 0666);
    if (-1 == semid) {
        printf("semget %s", strerror(errno));
    }
}

信号量通过请求(抢占)和释放来控制进程间同步,本质上就是满足一定条件下对信号量值的加减。每个信号量有一些关联的变量,比如信号量值 semval。

请求信号量

ini 复制代码
/**
 * @brief           请求信号量
 * @param id        信号量集合 id
 * @param semindex  信号量序号,从 0 开始
 * @param val       信号量值偏移绝对值,一般为 1
 */
void sem_set_wait(int id, int semindex, short val)
{
    int ret;
    struct sembuf buf;

    buf.sem_num = semindex;
    // P(sv) 减1 获取信号量
    buf.sem_op  = (-1) * val;
    buf.sem_flg = 0;

    while ((ret = semop(id, &buf, 1))
            && (errno == EINTR));
    if (ret == -1) {
        perror("semop");
    }
}

请求信号量,也就是常说的 P 操作,实际是对信号量值做减法操作,如果当前信号量值比减数的绝对值小,那么一般情况下会阻塞当前线程。是否阻塞可以通过设置标志 sembuf.sem_flg 来决定。

释放信号量

ini 复制代码
/**
 * @brief           释放信号量
 * @param id        信号量集合 id
 * @param semindex  信号量序号,从 0 开始
 * @param val       信号量值偏移绝对值,一般为 1
 */
void sem_set_signal(int id, int semindex, short val)
{
    struct sembuf buf;

    buf.sem_num = semindex;
    // V(sv) 加1 释放信号量
    buf.sem_op  = val;
    buf.sem_flg = 0;

    if (semop(id, &buf, 1) == -1) {
        perror("semop");
    }
}

释放信号量,也就是常说的 V 操作,实际是对信号量值做加法操作,不会阻塞当前线程。

最佳实践

如果有两个数据生产消费端,一个生产端和一个消费端,为了实现先生产再消费,消费完再生产,以及以此类推的同步流程,那么可以从信号量集合中使用其中的两个信号量,比如信号量 0 和信号量 1。

生产端:

scss 复制代码
// 请求信号量 0
sem_set_wait(semid, 0, 1);

// 生产 ...

// 释放信号量 1
sem_set_signal(semid, 1, 1);

消费端:

scss 复制代码
// 请求信号量 1
sem_set_wait(semid, 1, 1);

// 消费 ...

// 释放信号量 0
sem_set_signal(semid, 0, 1);

请求信号量 0,那么信号量 0 应该先被释放,信号量 1 同理。生产端生产前需要请求信号量 0,消费端消费前需要请求信号量 1,生产完成后释放信号量 1,消费完成后释放信号量 0。意味着生产前必须先消费,消费前必须先生产。

这样子相互依赖的逻辑决定了必须另一端执行完毕,当前端才可以开始执行。

既然是相互依赖,就必须有切入点,那么实际运行开始时,需要先允许生产端请求到信号量,可以设置信号量 0 的值为 1,这样生产端就可以顺利请求信号量 0 并开始生产,生产完成后再释放信号量 1,接着消费端才能够顺利请求到信号量 1。

设置信号量的值需要用到 semctl():

c 复制代码
#include <sys/sem.h>
union semun {
    /* Value for SETVAL */
    int              val;
    /* Buffer for IPC_STAT, IPC_SET */
    struct semid_ds *buf;
    /* Array for GETALL, SETALL */
    unsigned short  *array;
    /* Buffer for IPC_INFO (Linux-specific) */
    struct seminfo  *__buf;
};

int semctl(int semid, int semnum, int cmd, ...);

semctl() 有 4 个参数,semid 指定信号量集合 id,semnum 指定信号量集合中的信号量序号,cmd 指定对信号量的操作命令,最后一个参数可选。

semctl() 可用于信号量的各种设置,当 cmd = SETVAL 时,设置共用体 semun 类型的 val 成员为信号量的目标值,然后值传递 semun 类型变量给 semctl() 最后一个参数,代码如下

perl 复制代码
/**
 * @brief       初始化信号量值
 * @param id        信号量集合 id
 * @param semindex  信号量序号,从 0 开始
 * @param val       信号量初始值
 * @return int  0:成功,-1:失败
 */
int sem_init(int id, int semindex, int val)
{
    union semun su;

    su.val = val;
    if (semctl(id, semindex, SETVAL, su) == -1) {
        perror("semctl");
        return -1;
    }

    return 0;
}

相关推荐
程序员南飞36 分钟前
ps aux | grep smart_webrtc这条指令代表什么意思
java·linux·ubuntu·webrtc
StrokeAce37 分钟前
linux桌面软件(wps)内嵌到主窗口后的关闭问题
linux·c++·qt·wps·窗口内嵌
热爱嵌入式的小许5 小时前
Linux基础项目开发1:量产工具——显示系统
linux·运维·服务器·韦东山量产工具
2401_857622666 小时前
SpringBoot框架下校园资料库的构建与优化
spring boot·后端·php
代码雕刻家6 小时前
数据结构-3.9.栈在递归中的应用
c语言·数据结构·算法
2402_857589366 小时前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
哎呦没7 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
Kalika0-07 小时前
猴子吃桃-C语言
c语言·开发语言·数据结构·算法
_.Switch7 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
代码雕刻家7 小时前
课设实验-数据结构-单链表-文教文化用品品牌
c语言·开发语言·数据结构