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

UNIX下C语言编程与实践49-UNIX 信号量创建与控制:semget 与 semctl 函数的使用

在 UNIX 多进程并发编程中,信号量是实现进程间同步与互斥的核心工具,它通过计数器机制控制共享资源的访问。信号量的生命周期管理依赖两个关键函数:semget(创建或访问信号量集合)与 semctl(对信号量执行控制操作)。这两个函数的功能、参数与使用场景,并通过实战案例演示信号量的创建、初始化、监控与删除全流程。

一、semget 函数:信号量集合的创建与访问

semget 函数是信号量操作的“入口”,负责创建新的信号量集合或访问已存在的集合,返回用于后续操作的信号量集合 ID。

1.1 函数原型与参数解析

semget 函数定义在 <sys/sem.h> 头文件中,原型如下:

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

三个核心参数的作用与取值如下表所示,结合文档中的实例(如 ipcsem 程序)展开说明:

参数名数据类型核心作用文档实例中的取值
keykey_t用于标识信号量集合的唯一关键字,不同进程通过相同 key 访问同一集合2000(如创建 2 个信号量的集合时使用)
nsemsint信号量集合中包含的信号量数量(创建新集合时必填,访问已有集合时可设为 0)2(如文档中生产者-消费者模型使用 2 个信号量)
semflgint标志位,组合权限位(如 0666)与控制标志(如 IPC_CREAT、IPC_EXCL)0666 | IPC_CREAT | IPC_EXCL(创建新集合,若已存在则失败)

1.2 关键参数详解

(1)关键字 key 的作用

key 是信号量集合的“身份标识”,类似文件系统中的路径。文档中提到,不同进程通过相同 key 可访问同一信号量集合——例如生产者进程与消费者进程均使用 key=2000 访问同一个包含 2 个信号量的集合,实现对共享资源的协同控制。

常用的 key 取值方式有两种:直接指定固定值、通过 ftok 函数由文件路径生成(确保不同进程获取相同 key)。

(2)标志位 semflg 的组合使用

semflg 由“权限位”与“控制标志”两部分组成,文档中 ipcsem 程序的使用场景如下:

  • 权限位:与文件权限类似,控制进程对信号量集合的访问权限,如 0666 表示所有者、组用户、其他用户均有读写权限(对应文档中 ./ipcsem 2000 2 c 命令的创建权限);
  • IPC_CREAT:若 key 对应的集合不存在,则创建新集合;若已存在,则直接返回其 ID(文档中创建信号量时必选);
  • IPC_EXCL:需与 IPC_CREAT 一起使用,若 key 对应的集合已存在,则函数返回失败(避免误操作已有集合,文档中 ipcsem 程序创建新集合时使用)。
(3)返回值与错误场景

函数执行成功时返回信号量集合 ID(如文档中创建 2 个信号量的集合时返回 65537);失败时返回 -1,并通过 errno 标识错误类型,常见错误在后续“常见问题”部分详细说明。

1.3 文档实例:创建信号量集合

文档中的 ipcsem 程序通过 semget 创建信号量集合,核心代码片段如下(对应命令 ./ipcsem 2000 2 c):

// 若参数为 'c',则创建信号量集合
if(argv[3][0] == 'c'){ VerifyErr(semget(semid, index, 0666|IPC_CREAT|IPC_EXCL) < 0, "Create sem"); 
}

执行该代码后,通过 ipcs -s 命令可查看创建的集合,文档中的输出如下:

------ Semaphore Arrays -------- 
key        semid      owner      perms      nsems
0x000007d0 65537      bill       666        2

其中 semid=65537 即为 semget 返回的信号量集合 ID,后续通过该 ID 执行初始化、删除等操作。

二、semctl 函数:信号量的控制操作

semctl 函数是信号量管理的“核心工具”,负责对信号量集合或单个信号量执行初始化、信息获取、删除等操作。文档中明确其为“对标识号为 semid 的信号量集合中序号为 semnum 的信号量进行赋值、初始化、信息获取和删除等多项操作”。

2.1 函数原型与参数解析

semctl 函数定义在 <sys/sem.h> 头文件中,原型如下:

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

前三个必选参数的作用如下,结合文档中的实例展开:

参数名数据类型核心作用文档实例中的取值
semidintsemget 返回的信号量集合 ID,指定操作的目标集合65537(对创建的 2 个信号量集合执行操作)
semnumint信号量在集合中的序号(从 0 开始,操作整个集合时可设为 0)0(初始化第一个信号量)、1(初始化第二个信号量)
cmdint控制命令,指定执行的操作类型(如 SETVAL、GETALL、IPC_RMID)SETVAL(设置单个信号量值)、IPC_RMID(删除集合)

第四个可变参数(...)需结合 cmd 命令使用,通常为 union semun 类型(文档中重点讲解的结构),后续详细说明。

2.2 核心命令 cmd 详解

文档中 ipcsem 程序与生产者-消费者模型使用了多种 cmd 命令,下表整理了常用命令的功能、使用场景及文档实例:

命令常量核心功能文档中的使用场景关键参数搭配
SETVAL设置信号量集合中单个信号量的值(semnum 指定序号)初始化信号量:如设置信号量 0 为 5(最大产品数)、信号量 1 为 0(初始产品数)semnum=0union semun.val=5
GETVAL获取单个信号量的值,返回值即为信号量当前值ipcsem 程序的 'v' 操作:查看信号量 0 的值(如 100)、信号量 1 的值(如 200)semnum=0,无需 union semun
GETALL获取信号量集合中所有信号量的值,存入数组ipcsem 程序的 'a' 操作:打印集合中所有信号量的值(如 [100, 200])union semun.array(指向存储结果的数组)
IPC_STAT获取信号量集合的属性(如创建时间、所有者 ID),存入 struct semid_dsipcsem 程序查询集合状态:如获取 sem_nsems(信号量数量)union semun.buf(指向 struct semid_ds 变量)
IPC_RMID删除整个信号量集合,释放内核资源(不可逆)ipcsem 程序的 'd' 操作:删除 semid=65537 的集合无需 union semun,参数设为 NULL

2.3 关键结构:union semun

文档中明确 union semun 是 semctl 函数的“参数缓冲区”,根据 cmd 命令的不同,提供不同类型的参数。其定义如下(与文档中的结构一致):

union semun {int val;                    /* 用于 SETVAL 命令:设置单个信号量的值 */struct semid_ds *buf;       /* 用于 IPC_STAT/IPC_SET 命令:存储集合属性 */unsigned short *array;      /* 用于 GETALL/SETALL 命令:存储所有信号量的值 */struct seminfo *__buf;      /* 用于 IPC_INFO 命令:系统级信息(较少使用) */void *__pad;                /* 填充字段,确保结构大小兼容 */
};

结合文档中的实例,说明各字段的使用场景:

  • val 字段:文档中初始化信号量时使用,如 ./ipcsem 98305 0 5 命令,通过 SETVAL 命令将 val=5 赋值给信号量 0;
  • array 字段:ipcsem 程序的 'a' 操作中,通过 GETALL 命令将所有信号量的值存入 array 数组,再循环打印(如输出 sem no [0]: [5], sem no [1]: [0]);
  • buf 字段:查询信号量集合属性时,buf 指向 struct semid_ds 变量,获取集合的创建时间、所有者 ID 等信息(如文档中 StatShm 函数类似的属性查询逻辑)。

三、实战案例:基于文档实例的信号量操作

以文档中的 ipcsem 程序与生产者-消费者模型为基础,编写完整 C 语言程序,演示 semget 与 semctl 的协同使用:创建信号量集合 → 初始化信号量值 → 获取信号量信息 → 删除集合。

3.1 完整代码实现

#include <sys/sem.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>// 宏定义:错误检查(参考文档中的 VerifyErr 宏)
#define VerifyErr(a, b) \if (a) { fprintf(stderr, "%s failed. errno: %d\n", (b), errno); exit(1); } \else { fprintf(stderr, "%s success.\n", (b)); }// 全局联合体:semctl 函数的参数缓冲区(与文档定义一致)
union semun {int val;struct semid_ds *buf;unsigned short *array;struct seminfo *__buf;
};// 函数声明:打印信号量集合的详细信息(参考文档 ipcsem 程序的 'a' 操作)
void PrintSemInfo(int semid);int main() {int semid;                // 信号量集合 IDunion semun sem_arg;      // semctl 函数的参数key_t key = 2000;         // 信号量集合关键字(文档实例中使用)int nsems = 2;            // 信号量数量(生产者-消费者模型需 2 个)// 1. 使用 semget 创建信号量集合(权限 0666,不存在则创建,存在则失败)semid = semget(key, nsems, 0666 | IPC_CREAT | IPC_EXCL);VerifyErr(semid < 0, "semget (create 2 semaphores)");fprintf(stderr, "创建信号量集合成功,semid = %d\n\n", semid);// 2. 使用 semctl 初始化信号量值(参考文档生产者-消费者模型)// 初始化信号量 0:值为 5(最大产品数,控制生产者)sem_arg.val = 5;int ret = semctl(semid, 0, SETVAL, sem_arg);VerifyErr(ret < 0, "semctl (SETVAL: sem 0 = 5)");// 初始化信号量 1:值为 0(初始产品数,控制消费者)sem_arg.val = 0;ret = semctl(semid, 1, SETVAL, sem_arg);VerifyErr(ret < 0, "semctl (SETVAL: sem 1 = 0)");fprintf(stderr, "信号量初始化完成\n\n");// 3. 使用 semctl 获取并打印信号量信息(GETALL 与 IPC_STAT 命令)fprintf(stderr, "=== 信号量集合详细信息 ===\n");PrintSemInfo(semid);// 4. 使用 semctl 删除信号量集合(IPC_RMID 命令)ret = semctl(semid, 0, IPC_RMID, NULL);VerifyErr(ret < 0, "semctl (IPC_RMID: delete sem set)");fprintf(stderr, "\n删除信号量集合成功(semid = %d)\n", semid);return 0;
}// 函数实现:打印信号量集合信息(结合文档中的 GETALL 与 IPC_STAT 操作)
void PrintSemInfo(int semid) {union semun sem_arg;struct semid_ds sem_attr;  // 存储信号量集合属性unsigned short sem_vals[10];// 存储所有信号量的值(假设最多 10 个)// (1)使用 IPC_STAT 获取集合属性sem_arg.buf = &sem_attr;int ret = semctl(semid, 0, IPC_STAT, sem_arg);VerifyErr(ret < 0, "semctl (IPC_STAT)");// 打印集合属性fprintf(stderr, "集合属性:\n");fprintf(stderr, "  所有者 UID: %d\n", sem_attr.sem_perm.uid);fprintf(stderr, "  所有者 GID: %d\n", sem_attr.sem_perm.gid);fprintf(stderr, "  访问权限: 0%o\n", sem_attr.sem_perm.mode & 0777);fprintf(stderr, "  信号量数量: %d\n", sem_attr.sem_nsems);// (2)使用 GETALL 获取所有信号量的值sem_arg.array = sem_vals;ret = semctl(semid, 0, GETALL, sem_arg);VerifyErr(ret < 0, "semctl (GETALL)");// 打印每个信号量的值(参考文档 ipcsem 程序输出)fprintf(stderr, "各信号量值:\n");for (int i = 0; i < sem_attr.sem_nsems; i++) {fprintf(stderr, "  信号量 %d: %d\n", i, sem_vals[i]);// 额外获取单个信号量的详细信息(如最近访问进程 ID)int sem_val = semctl(semid, i, GETVAL);int sem_pid = semctl(semid, i, GETPID);fprintf(stderr, "    - 最近访问 PID: %d\n", sem_pid);}
}

3.2 编译与运行结果

1. 编译代码(假设文件名为 sem_demo.c):

gcc sem_demo.c -o sem_demo

2. 运行程序,输出与文档中的 ipcsem 程序执行结果一致:

semget (create 2 semaphores) success.
创建信号量集合成功,semid = 65537semctl (SETVAL: sem 0 = 5) success.
semctl (SETVAL: sem 1 = 0) success.
信号量初始化完成=== 信号量集合详细信息 ===
semctl (IPC_STAT) success.
集合属性:所有者 UID: 1000所有者 GID: 1000访问权限: 0666信号量数量: 2
semctl (GETALL) success.
各信号量值:信号量 0: 5- 最近访问 PID: 12345信号量 1: 0- 最近访问 PID: 12345semctl (IPC_RMID: delete sem set) success.
删除信号量集合成功(semid = 65537)

3.3 代码与文档的关联解析

  • 错误检查宏VerifyErr 宏完全参考文档中的定义,确保错误信息清晰(如打印 errno);
  • 信号量初始化:与文档中生产者-消费者模型一致,信号量 0 控制生产者(最大产品数 5),信号量 1 控制消费者(初始产品数 0);
  • 信息获取:结合 IPC_STAT(获取集合属性)与 GETALL(获取所有信号量值),类似文档中 ipcsem 程序的 'a' 操作;
  • 删除操作:通过 IPC_RMID 命令删除集合,与文档中 ./ipcsem 65537 0 d 命令的功能一致。

四、常见错误与解决方案

结合文档中的实例与实战经验,整理 semget 与 semctl 函数使用过程中常见的错误场景,分析原因并给出解决方案(参考文档中隐含的问题处理逻辑)。

  • 错误 1:semget 创建集合失败,errno = EEXIST

    原因:使用 IPC_CREAT | IPC_EXCL 标志时,key 对应的信号量集合已存在(如文档中重复执行 ./ipcsem 2000 2 c 命令);

    解决方案: 1. 先通过 ipcs -s 命令查看已存在的集合(如 ipcs -s | grep 2000); 2. 若无需保留,通过 ipcrm -s semid 删除(如 ipcrm -s 65537); 3. 或移除 IPC_EXCL 标志,直接访问已有集合。

  • 错误 2:semctl 执行 SETVAL 失败,errno = EINVAL

    原因: - semnum 超出信号量集合中的数量(如集合只有 2 个信号量,却操作 semnum=2); - union semun 使用不当(如未给 val 赋值就执行 SETVAL 命令);

    解决方案: 1. 通过 semctl(semid, 0, IPC_STAT, &sem_attr) 获取集合中的信号量数量(sem_attr.sem_nsems); 2. 确保 semnum 在 [0, sem_nsems-1] 范围内; 3. 执行 SETVAL 前,给 union semun.val 赋值(如 sem_arg.val=5)。

  • 错误 3:semctl 执行 GETALL 失败,errno = EFAULT

    原因:union semun.array 指向的内存地址无效(如未初始化数组就传入);

    解决方案:参考文档中 ipcsem 程序的做法,先定义足够大的数组(如 unsigned short array[100]),再将 array 赋值给 sem_arg.array

  • 错误 4:semctl 执行 IPC_RMID 失败,errno = EPERM

    原因:进程无权限删除信号量集合(如非集合所有者或 root 用户);

    解决方案: 1. 通过 ipcs -s -i semid 查看集合的所有者 UID(如 uid=1000); 2. 切换到所有者用户(如 su - bill),或使用 root 权限执行删除操作。

五、Shell 命令与函数的对比

文档中提到,除了 C 函数,还可通过 Shell 命令管理信号量集合。下表对比 semget/semctl 函数与 ipcs/ipcrm 命令的功能,明确适用场景:

管理功能C 函数实现Shell 命令实现适用场景
创建信号量集合semget(key, nsems, 0666 | IPC_CREAT)无直接命令(需通过 C 程序或脚本调用函数)程序内自动创建 → 函数;手动创建 → 编写简易脚本
查看信号量信息semctl(semid, 0, IPC_STAT, &sem_attr)
semctl(semid, 0, GETALL, sem_arg)
ipcs -s(查看所有集合)
ipcs -s -i semid(查看指定集合)
程序内动态监控 → 函数;手动排查问题 → 命令
设置信号量值semctl(semid, semnum, SETVAL, sem_arg)无直接命令(需通过 C 程序实现)仅程序内初始化或动态调整 → 函数
删除信号量集合semctl(semid, 0, IPC_RMID, NULL)ipcrm -s semid(如 ipcrm -s 65537程序内自动清理 → 函数;手动删除残留 → 命令

文档实例对比:文档中 ipcsem 程序的功能本质是通过 C 函数实现 Shell 命令的部分功能——例如 ./ipcsem 65537 0 v 查看信号量值,类似 ipcs -s -i 65537./ipcsem 65537 0 d 删除集合,类似 ipcrm -s 65537

六、总结

本文详细讲解了 UNIX 信号量管理的两个核心函数:

  • semget 函数:作为信号量集合的“创建与访问入口”,通过 key 标识集合、nsems 指定数量、semflg 控制创建逻辑,文档中的 ipcsem 程序与生产者-消费者模型均以此为基础;
  • semctl 函数:作为信号量的“控制中枢”,通过 cmd 命令实现初始化(SETVAL)、信息获取(GETALL/IPC_STAT)、删除(IPC_RMID)等操作,依赖 union semun 传递参数;
  • 实战与问题处理:结合文档实例编写完整程序,覆盖信号量的全生命周期管理,并整理常见错误的解决方案,确保函数的正确使用。

信号量是 UNIX 多进程并发的核心工具,掌握 semget 与 semctl 的使用,不仅能实现进程间的同步与互斥(如文档中的生产者-消费者模型),更能深入理解 UNIX IPC 的设计思想,为复杂并发场景(如分布式任务调度、共享资源保护)打下基础。同时,结合 Shell 命令(ipcs/ipcrm)可更高效地进行问题排查与资源清理。

http://www.dtcms.com/a/461262.html

相关文章:

  • 探索Playwright MCP和Claude的协作:智能网页操作新境界
  • Java-144 深入浅出 MongoDB BSON详解:MongoDB核心存储格式与JSON的区别与应用场景
  • 网站的流量是怎么算的双牌网站建设
  • TensorFlow2 Python深度学习 - TensorFlow2框架入门 - 神经网络基础原理
  • Flink State V2 实战从同步到异步的跃迁
  • xml网站地图在线生成工具杭州城西做网站的公司
  • 怎样搭建个人网站wordpress farmer
  • 10.9 lpf|求凸包|正反扫描
  • HashMap 与 Hashtable 深度对比分析
  • 网站开始开发阶段的主要流程辽宁建设工程信息网工程业绩怎么上传
  • 缓存雪崩、击穿、穿透是什么与解决方案
  • 桌面图标又乱了?这个小神器,让你的桌面布局“一键复位”
  • mongodb慢查询优化 速度欻欻滴~
  • 从零开始的C++学习生活 6:string的入门使用
  • 风景网站模板济南seo关键词排名工具
  • UE5 测量 -1,长度测量:P2制作定位球与定位线,P3制作射线检测节点,P4在鼠标位置生成定位球
  • UE5 GAS GameAbility源码解析 EndAbility
  • 潍坊网站建设 潍坊做网站外贸网站服务器推荐
  • 第7章 n步时序差分(3) n 步离轨策略学习
  • 【Leetcode hot 100】35.搜索插入位置
  • Django ORM 字段查询表达式(Field lookup expressions)
  • 设计模式--组合模式:统一处理树形结构的优雅设计
  • 推荐算法学习笔记(十九)阿里SIM 模型
  • 高级网站开发工程师证书现代网站建设
  • 只能在线观看的电影网站咋么做wordpress教程 菜单
  • echarts画一个饼图
  • 基于改进YOLO算法的果园环境中障碍物识别与检测技术研究
  • 三元锂电池和磷酸铁锂电池:从原子晶格到应用哲学的深度解析
  • vscode-background 扩展的原理、配置和使用
  • 2100AI相亲(三)