当前位置: 首页 > news >正文

Linux系统编程:信号量Semaphore详解 (IPC)

目录

  • 信号量使用流程
  • 信号量的优点
  • semget函数
    • 函数原型
  • semctl函数
    • 函数原型
    • 常用cmd详解
  • semop函数
    • 函数原型
    • `struct sembuf`结构体
    • 信号量数组与`struct sembuf`结构体的关系
  • 示例

信号量(Semaphore)是一种用于进程间同步的机制,适用于控制对共享资源的访问。

信号量使用流程

1. 创建和初始化信号量

  • 使用 semget 创建信号量集。
  • 使用 semctl 设置初始值。

2. P/V 操作

  • 通过 semop 执行 P(等待)操作,减少信号量值。
  • 通过 semop 执行 V(释放)操作,增加信号量值。

3. 删除信号量

  • 使用 semctlIPC_RMID 删除信号量集。

信号量的优点

1.进程间同步
- 信号量可以保证多个进程对共享资源的有序访问。
2. 灵活性高
- 支持多种操作组合,如 PV
3. 跨进程使用
- 通过唯一的 key,不同进程可共享同一信号量。

semget函数

函数功能:用于创建或获取信号量集的函数。

函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);

参数

key:

  • 用于标识信号量集的键值。
  • 可以通过 ftok 函数生成,也可以直接指定为整数值。
  • 如果多个进程需要共享信号量集,必须使用相同的 key

nsems:

  • 信号量集中的信号量数量。
  • 仅在创建新信号量时有效。
  • 如果信号量集已存在,则该参数会被忽略。

semflg:控制信号量的权限和行为。

  • IPC_CREAT:如果信号量不存在,则创建一个新的信号量集。
  • IPC_EXCL:与 IPC_CREAT
    一起使用。如果信号量已存在,则返回错误。
  • IPC_CREAT一起使用设置权限如0664

返回值

  • 成功:返回信号量集的标识符(semid),用于后续的信号量操作。
  • 失败:返回 -1,并设置
    errno,常见错误包括:
    • EEXIST:信号量已存在,但同时指定了
      IPC_EXCL
    • EACCES:没有权限访问信号量。
    • ENOSPC:系统信号量数量超出限制。
    • EINVALnsems
      超出系统支持的最大信号量数。

示例

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <errno.h>int main() {key_t key = ftok(".", 'a');  // 生成键值if (key == -1) {perror("ftok");exit(1);}// 创建一个包含 1 个信号量的信号量集int semid = semget(key, 1, IPC_CREAT | 0666);if (semid == -1) {perror("semget");exit(1);}printf("信号量创建成功,ID:%d\n", semid);return 0;
}

semctl函数

函数功能:用于控制信号量集或信号量的属性。它能够初始化、获取信号量的值、删除信号量集等操作。

函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ... /* union semun arg */);

参数

semid:信号量标识符

semnum操作的信号量的下标

cmd操作信号量的命令

  • IPC_STAT:获取信号量的信息放到 buf
    这个参数中,这个操作需要我们指定信号量拥有读权限
  • SETVAL:设置信号量的值。
  • GETVAL:获取信号量的当前值。
  • IPC_RMID:删除信号量集。
  • GETPID:获取最后一个操作信号量的进程 ID。
  • GETNCNT:获取等待信号量增加的进程数量。
  • GETZCNT:获取等待信号量值变为 0 的进程数量。

arg(可选):

  • 某些命令需要使用该参数传递额外的信息,如设置信号量的值。
  • 使用的是一个联合体 union semun,需显式定义
union semun {int val;                // 设置单个信号量的值struct semid_ds *buf;   // 获取/设置信号量集的属性unsigned short *array; // 设置多个信号量的值
};

返回值

  • 成功:根据命令返回不同的值。
  • 失败:返回 -1,并设置 errno

常用cmd详解

  • SETVAL:设置单个信号量的值
union semun sem_union;
sem_union.val = 1;  // 将信号量设置为 1
semctl(semid, 0, SETVAL, sem_union);
  • GETVAL:获取信号量的值
int val = semctl(semid, 0, GETVAL);
printf("信号量的当前值:%d\n", val);
  • GETPID:获取最后一个操作信号量的进程 ID
int pid = semctl(semid, 0, GETPID);
printf("最后一个操作信号量的进程 ID:%d\n", pid);
  • IPC_RMID:删除信号量集
semctl(semid, 0, IPC_RMID);

semop函数

函数功能:用于执行信号量的 P(等待)和 V(释放)操作,从而实现对信号量的操作和控制。

函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);

参数

semid:信号量集的标识符,由
semget 返回。

sops信号量集合的操作结构体数组,可以一次性操作全部的信号量集合。(要么一次性执行完数组里面的东西要么就不执行,不能只执行里面的一个元素)

nsops这个数组有多少个元素

返回值

  • 成功时返回 0
  • 失败时返回 -1,并设置 errno

struct sembuf结构体

struct sembuf {unsigned short sem_num;  // 信号量下标short sem_op;            // 操作值short sem_flg;           // 操作标志
};

sem_num:信号量下标,从0开始(和数组一样)

sem_op:要对数组元素进行何种操作

  • >0:增加信号量(通常表示表示释放资源)
  • <0:减少信号量(通常表示占用资源)
  • =0:等待信号量值变为0

sem_flg:一般写成0代表阻塞等待,IPC_NOWAIT表示非阻塞等待,如果无法立即完成,返回-1,SEM_UNDO表示当进程终止时,系统会撤销该操作对信号量的影响。

信号量数组与struct sembuf结构体的关系

  1. 信号量数组
    • 通过 semget() 创建的信号量数组是内核中实际维护的信号量集合。例如:
semget(ipc_key, 3, IPC_CREAT | 0664);

这会创建一个信号量数组,其中包含 3 个信号量,索引分别为 0, 1, 和 2

  1. 操作结构数组 (struct sembuf semoparr[i])
  • struct sembuf semoparr[i] 表示定义了一个数组,包含 istruct sembuf 类型的元素。
  • 每个 semoparr[index] 是一个完整的结构体,包含 sem_numsem_opsem_flg 三个字段。

比如说想操作3个信号量中的两个,那么就要定义semoparr[2]

struct sembuf semoparr[2];  // 定义一个包含 2 个结构体的数组// 配置第一个操作
semoparr[0].sem_num = 0;   // 操作信号量数组的第 0 个信号量
semoparr[0].sem_op = -1;   // 执行减 1 操作 (P 操作)
semoparr[0].sem_flg = 0;   // 阻塞操作// 配置第二个操作
semoparr[1].sem_num = 1;   // 操作信号量数组的第 1 个信号量
semoparr[1].sem_op = 1;    // 执行加 1 操作 (V 操作)
semoparr[1].sem_flg = 0;   // 阻塞操作

示例

#include <stdio.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/wait.h> 
#define KEY 1234void P(int semid, int sem_num) {struct sembuf sop = {sem_num, -1, 0}; // P 操作(占用资源)semop(semid, &sop, 1);
}void V(int semid, int sem_num) {struct sembuf sop = {sem_num, 1, 0}; // V 操作(释放资源)semop(semid, &sop, 1);
}int main() {int semid = semget(KEY, 1, IPC_CREAT | 0666); // 创建信号量集semctl(semid, 0, SETVAL, 1); // 初始化信号量值为 1if (fork() == 0) { // 子进程P(semid, 0); // 占用资源printf("Child: Entering critical section\n");sleep(2); // 模拟操作printf("Child: Leaving critical section\n");V(semid, 0); // 释放资源} else { // 父进程P(semid, 0); // 占用资源printf("Parent: Entering critical section\n");sleep(2); // 模拟操作printf("Parent: Leaving critical section\n");V(semid, 0); // 释放资源wait(NULL); // 等待子进程结束semctl(semid, 0, IPC_RMID); // 删除信号量集}return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
#include <errno.h>
#include <sys/sem.h>//根据semctl要求,第四个参数必须定义以下共用体,根据不同的cmd(semctl的第三个参数)来决定用里面的哪个元素
union semun {int              val;    /* Value for SETVAL */struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */unsigned short  *array;  /* Array for GETALL, SETALL */struct seminfo  *__buf;  /* Buffer for IPC_INFO(Linux-specific) */};int main()
{key_t ipc_key;int sem_id;ipc_key = ftok(".",1);sem_id = semget(ipc_key,1,IPC_CREAT|0664);//获取一个信号量,1代表创建的信号量个数(信号量数组元素的个数)//定义共用体并赋值union semun setval = {.val = 0};semctl(sem_id,0,SETVAL,setval);//设置信号量的初值:0代表操作信号量数组的第几个元素,SETVAL代表设置他的初值//定义操作结构体数组,可以一次性操作全部的信号量集合里面的信号struct sembuf semoparr[1];while(1){semoparr[0].sem_num = 0; //操作信号量的下标semoparr[0].sem_op = -1; //对这个信号进行-1操作semoparr[0].sem_flg = 0; //正常的操作信号量,代表如果进行减操作会变成一个负数,则semop函数调用会陷入阻塞semop(sem_id,semoparr,1);//进行PV操作(减加操作),1代表的是上面操作的数组元素个数printf("P(减)操作成功\n");}return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
#include <errno.h>
#include <sys/sem.h>//根据semctl要求,第四个参数必须定义以下共用体,根据不同的cmd(semctl的第三个参数)来决定用里面的哪个元素
union semun {int              val;    /* Value for SETVAL */struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */unsigned short  *array;  /* Array for GETALL, SETALL */struct seminfo  *__buf;  /* Buffer for IPC_INFO(Linux-specific) */};int main()
{key_t ipc_key;int sem_id;ipc_key = ftok(".",1);sem_id = semget(ipc_key,1,IPC_CREAT|0664);//获取一个信号量,1代表创建的信号量个数(信号量数组元素的个数)//定义操作结构体数组,可以一次性操作全部的信号量集合里面的信号struct sembuf semoparr[1];while(1){getchar();//等待键盘锹下去semoparr[0].sem_num = 0; //操作信号量的下标semoparr[0].sem_op = 1; //对这个信号进行+1操作semoparr[0].sem_flg = 0; //正常的操作信号量,代表如果进行减操作会变成一个负数,则semop函数调用会陷入阻塞semop(sem_id,semoparr,1);//进行PV操作(减加操作),1代表的是上面操作的数组元素个数printf("V(加)操作成功\n");}return 0;
}

相关文章:

  • vue3中ref在js中为什么需要.value才能获取/修改值?
  • C++:STL——list
  • 可以隐藏列的表格
  • 单片机 + 图像处理芯片 + TFT彩屏 复选框控件
  • Linux内核中的编译时安全防护:以网络协议栈控制块校验为例
  • 单片机之间的双向通信
  • terraform查看资源建的关联关系
  • 一、linux系统启动过程操作记录
  • 插入排序(直接插入排序、折半插入排序和希尔排序)
  • C++中析构函数
  • log4cpp进阶指南
  • LeetCode 每日一题 2025/4/21-2025/4/27
  • 关于Spark知识点与代码测试的学习总结
  • element-ui dropdown 组件源码分享
  • 【c++】AVL树模拟实现
  • Comfy UI 笔记
  • 文章记单词 | 第47篇(六级)
  • 面试记录1-春招补录0427
  • 基础学习:(9)vit -- vision transformer 和其变体调研
  • 《大型网站技术架构-核心原理与案例分析》笔记
  • 影子调查丨危房之下,百余住户搬离梦嘉商贸楼
  • 美大学建“私人联盟”对抗政府:学校已存在300年,特朗普才上任3个月
  • 李勇已任内蒙古乌兰察布市委副书记,曾在中央编办任职
  • 下任美联储主席热门人选沃什:美联储犯下“系统性错误”,未能控制一代人以来最严重的通胀
  • 独家丨申万宏源研究所将迎来新所长:首席策略分析师王胜升任
  • 钟声:美以芬太尼为借口滥施关税,纯属“内病外治”