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

相关推荐
zylyehuo9 小时前
Linux 彻底且安全地删除文件
linux
葫芦和十三9 小时前
图解 MongoDB 13|WiredTiger 存储引擎:B-tree、页和 checkpoint 三件套
后端·mongodb·agent
葫芦和十三10 小时前
图解 MongoDB 14|Cache 与淘汰:WiredTiger 的内存治理
后端·mongodb·面试
IT_陈寒13 小时前
Vue这个坑我跳了两次,原来问题出在这
前端·人工智能·后端
ServBay14 小时前
9 个 Python 第三方库推荐,不用 AI 都好像多出一个团队
后端·python
用户83562907805114 小时前
如何使用 Python 添加和管理 Excel 批注(完整示例)
后端·python
用户83562907805114 小时前
使用 Python 管理 Excel 工作表:创建、复制、删除与重命名
后端·python
lizhongxuan14 小时前
Agent Tool
后端
CaffeinePro15 小时前
依赖注入:FastAPI最核心的解耦能力案例解析
后端·fastapi
Assby16 小时前
从 Function Calling 到 MCP:理解 Agent 工具调用的底层通信机制
人工智能·后端