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;
}

相关推荐
行百里er4 小时前
2026:一名码农的“不靠谱”年度规划
后端·程序员·架构
ICscholar6 小时前
ExaDigiT/RAPS
linux·服务器·ubuntu·系统架构·运维开发
sim20206 小时前
systemctl isolate graphical.target命令不能随便敲
linux·mysql
计算机程序设计小李同学6 小时前
基于SpringBoot的个性化穿搭推荐及交流平台
java·spring boot·后端
米高梅狮子6 小时前
4. Linux 进程调度管理
linux·运维·服务器
用户47949283569157 小时前
同事一个比喻,让我搞懂了Docker和k8s的核心概念
前端·后端
再创世纪7 小时前
让USB打印机变网络打印机,秀才USB打印服务器
linux·运维·网络
fengyehongWorld8 小时前
Linux ssh端口转发
linux·ssh
li.wz9 小时前
Spring Bean 生命周期解析
java·后端·spring
sanggou9 小时前
【实战总结】Spring Boot 后端接口防抖详解与实现方案(含注解 + Redis)
spring boot·后端