news 2026/2/3 4:21:35

Linux同步机制之信号量

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux同步机制之信号量

System V 信号量

概述

System V 信号量是内核提供的同步原语, 用于跨进程的互斥与资源计数。信号量本质上是一个计数器, 支持 P 操作 (wait, 减 1) 和 V 操作 (signal, 加 1)。信号量通常用于控制对共享资源的访问, 实现进程间的同步。

典型用途:

  • 互斥锁: 初值 1, 保证同一时刻只有一个进程进入临界区
  • 计数信号量: 初值为可用资源数, 控制并发访问额度
  • 进程间同步: 作为事件/条件的等待与通知

信号量由内核维护, 通过键值 key 标识, 支持集合 (多个 sem 组成的数组)。

通信原理

基本概念

信号量的特点:

  1. 计数器: 本质上是一个非负整数计数器
  2. 原子操作: P 和 V 操作是原子的, 不会被中断
  3. 阻塞机制: 当信号量为 0 时, P 操作会阻塞
  4. 同步原语: 用于实现互斥和同步
  5. 集合概念: System V 信号量以集合形式存在

实现机制

  1. 创建/获取信号量集:

    • 使用semget()系统调用创建或获取信号量集
    • 通过唯一的键值 (key) 标识信号量集
    • 指定信号量集中信号量的数量
    • 返回信号量集标识符 (semid)
  2. 信号量操作:

    • 使用semop()对信号量进行 P/V 操作
    • P 操作 (wait): 信号量减 1, 如果为 0 则阻塞
    • V 操作 (signal): 信号量加 1, 唤醒等待的进程
  3. 信号量控制:

    • 使用semctl()设置信号量的初始值
    • 可以获取信号量的值
    • 可以删除信号量集
  4. 同步模式:

    进程A: P操作(等待) ──┐ ├──> [信号量] ──> 控制共享资源访问 进程B: P操作(等待) ──┘ 进程C: V操作(释放) ──> 信号量+1,唤醒等待进程
  5. 互斥模式:

    • 信号量初始值为 1
    • 进程访问共享资源前执行 P 操作
    • 访问完成后执行 V 操作
    • 保证同一时刻只有一个进程访问资源

使用流程

  1. semget创建/获取信号量集 (指定 key、数量、权限, 可带 IPC_CREAT/IPC_EXCL)
  2. semctl初始化值 (SETVAL/SETALL), 可查询/删除 (GETVAL/IPC_RMID)
  3. semop执行 P/V 操作 (原子减/加), 支持阻塞或非阻塞 (SEM_UNDO/IPC_NOWAIT)
  4. 使用完后可由负责的进程调用semctl(..., IPC_RMID, ...)删除信号量集

API 说明

semget()

#include<sys/sem.h>intsemget(key_tkey,intnsems,intsemflg);
  • 功能: 创建或获取信号量集
  • 参数:
    • key: 信号量集的键值 (可以用ftok()生成或使用 IPC_PRIVATE)
    • nsems: 信号量集中信号量的数量
    • semflg: 标志位 (IPC_CREAT, IPC_EXCL, 权限等)
  • 返回值: 成功返回信号量集标识符, 失败返回 -1
  • 常见错误: EACCES, EEXIST, ENOENT, ENOMEM, ENOSPC

semop()

#include<sys/sem.h>intsemop(intsemid,structsembuf*sops,size_tnsops);
  • 功能: 原子执行一组 P/V 操作
  • 参数:
    • semid: 信号量集标识符
    • sops: 操作数组指针
    • nsops: 操作数量
  • 返回值: 成功返回 0, 失败返回 -1
  • 常见错误: EACCES, EIDRM, EINVAL, EAGAIN (非阻塞且资源不足), EINTR (被信号中断)

sembuf 结构

structsembuf{unsignedshortsem_num;// 信号量索引/编号shortsem_op;// 操作值: 负数=等待并减 (P), 正数=加 (V), 0=等待为 0shortsem_flg;// 标志位: SEM_UNDO (进程退出自动回滚), IPC_NOWAIT (非阻塞)};

semctl()

#include<sys/sem.h>intsemctl(intsemid,intsemnum,intcmd,...);
  • 功能: 控制/查询信号量
  • 参数:
    • semid: 信号量集标识符
    • semnum: 信号量编号
    • cmd: 命令 (SETVAL 设置值, GETVAL 获取值, IPC_RMID 删除等)
    • ...: 可变参数 (根据 cmd 不同而不同, 需要 union semun 作为第四参数)
  • 常用 cmd:
    • SETVAL/GETVAL: 设定/获取单个信号量值
    • SETALL/GETALL: 设定/获取信号量数组
    • IPC_RMID: 删除信号量集
    • IPC_STAT/IPC_SET: 查询/设置权限与属性
  • 返回值: 根据 cmd 不同而不同, 失败返回 -1
  • 注意: 需要 union semun 作为第四参数 (用户需自行声明)

示例代码

示例 1: 互斥锁风格的 P/V

#include<stdio.h>#include<sys/ipc.h>#include<sys/sem.h>#include<unistd.h>unionsemun{intval;structsemid_ds*buf;unsignedshort*array;};// P 操作: 获取锁 (阻塞等待)intsem_lock(intsemid,intidx){structsembufop={.sem_num=idx,.sem_op=-1,.sem_flg=SEM_UNDO};returnsemop(semid,&op,1);}// V 操作: 释放锁intsem_unlock(intsemid,intidx){structsembufop={.sem_num=idx,.sem_op=1,.sem_flg=SEM_UNDO};returnsemop(semid,&op,1);}intmain(void){key_tkey=ftok(".",'s');intsemid=semget(key,1,IPC_CREAT|0666);if(semid==-1){perror("semget");return1;}// 初始化为 1 (互斥锁)unionsemun arg;arg.val=1;if(semctl(semid,0,SETVAL,arg)==-1){perror("semctl");return1;}if(sem_lock(semid,0)==-1){perror("lock");return1;}printf("PID %d in critical section\n",getpid());sleep(1);// 模拟工作sem_unlock(semid,0);// 由负责的进程删除信号量 (可选)semctl(semid,0,IPC_RMID);return0;}

说明:

  • 互斥锁语义: 初值设为 1, P=-1 获取, V=+1 释放
  • 阻塞行为: 未持有锁时 P 会睡眠; 使用 IPC_NOWAIT 可改为非阻塞
  • SEM_UNDO: 进程异常退出时内核回滚 sem 值, 降低死锁风险

示例 2: 计数信号量 (资源池)

// 初始化为资源数 N; P 获取一个资源, V 归还// 将 SETVAL 初值设为可用资源总数 (例如连接数、缓冲块数)// 当 sem_op=-1 时, 若值为 0 则阻塞或 EAGAIN (IPC_NOWAIT)

参考examples/05-semaphore/目录下的更多示例代码。

性能评价

优点

  1. 同步机制: 提供强大的进程同步能力
  2. 原子操作: P/V 操作是原子的, 保证正确性
  3. 阻塞机制: 自动处理进程阻塞和唤醒
  4. 灵活性: 可以用于互斥、同步、资源计数等多种场景
  5. 持久性: 信号量集在系统中持久存在

缺点

  1. 复杂性: API 相对复杂, 需要理解信号量概念
  2. 系统限制: 系统对信号量数量有限制
  3. 死锁风险: 使用不当可能导致死锁
  4. 系统资源: 占用系统资源, 需要显式删除
  5. 性能开销: 需要系统调用, 有一定开销

性能特点

  • 延迟: 中等 (需要系统调用)
  • 吞吐量: 中等 (受系统限制影响)
  • CPU 占用: 中等
  • 内存占用: 小 (每个信号量占用少量内存)

适用场景

  • ✅ 进程同步和互斥
  • ✅ 控制共享资源访问
  • ✅ 生产者-消费者问题
  • ✅ 读者-写者问题
  • ✅ 需要复杂同步机制的场景
  • ❌ 简单的单向通信 (管道更合适)
  • ❌ 不需要同步的场景

注意事项

  1. 键值管理: 使用ftok()生成键值或使用 IPC_PRIVATE; key 相同会共享同一信号量集, 部署时需管理好 key 分配
  2. 初始值设置: 使用semctl(SETVAL)设置信号量初始值
  3. 资源清理: 使用完毕后应该删除信号量集 (semctl(IPC_RMID)); 约定哪个进程初始化/删除信号量, 避免误删
  4. 死锁避免: 注意避免死锁, 合理设计 P/V 操作顺序; 保持一致的加锁顺序, 避免循环等待; 对长时间持锁的操作谨慎设计
  5. 错误处理: 注意处理信号量操作失败的情况
  6. 系统限制: 注意系统的信号量数量限制 (ipcs -l查看)
  7. SEM_UNDO 标志: 建议在 P/V 中使用, 防止进程崩溃后信号量泄漏, 降低死锁风险; 但大量使用可能有内核开销
  8. 权限与安全: 合理设置权限位, 避免被非预期进程操作
  9. 非阻塞模式: IPC_NOWAIT 可避免阻塞, 需处理 EAGAIN 重试或回退
  10. 删除时机: 删除会使阻塞的 semop 返回 EIDRM; 先确保使用方已退出或能正确处理
  11. 多 sem 场景:semop支持一次操作多个信号量, 可用于原子更新多个资源状态

常见错误码提示

  • EACCES: 权限不足
  • EEXIST: IPC_CREAT|IPC_EXCL 且已存在
  • ENOENT: 未指定 IPC_CREAT 且不存在
  • ENOMEM/ENOSPC: 资源不足或达到系统上限
  • EAGAIN: 非阻塞且当前不可用
  • EIDRM: 信号量集已被删除
  • EINTR: 被信号中断, 可按需重试
  • EINVAL: 参数无效

扩展阅读

  • man 2 semget
  • man 2 semop
  • man 2 semctl
  • man 7 sem_overview
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/29 13:48:30

计算机毕业设计必看必学~ 基于SSM的大学生就业平台的设计与实现85751,原创定制程序、单片机、java、PHP、Python、小程序、文案全套、毕设成品等!

目录 1 绪论 1.1 选题背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2系统分析 2.1 可行性分析 2.1.1技术可行性 2.1.2经济可行性 2.1.3社会可行性 2.2 系统流程分析 2.2.1系统开发流程 2.2.2 用户登录流程 2.2.3 系统操作流程 2.2.4 添加信息流程 2.2.5 …

作者头像 李华
网站建设 2026/1/29 13:48:31

46、Asterisk VoIP系统配置指南

Asterisk VoIP系统配置指南 1. Asterisk模块加载建议 对于Asterisk新手而言,由于其模块较为“挑剔”,为避免出现问题,最好自动加载 modules 目录下的所有模块。随着对Asterisk的熟悉程度增加,可以使用 noload 指令明确告知Asterisk不加载不需要的模块。 2. Asterisk…

作者头像 李华
网站建设 2026/1/29 14:51:48

14、充分利用语言的完整工具集

充分利用语言的完整工具集 在配置管理中,我们常常需要高效地管理和分配资源。Puppet 提供了一系列强大的功能,让我们可以更灵活地处理各种资源。下面将详细介绍 Puppet 中的资源标签、资源导出与导入、资源参数覆盖以及资源默认值等重要特性。 资源标签的使用 Puppet 会隐…

作者头像 李华
网站建设 2026/1/29 13:48:42

AI算力:驱动智能时代多元计算与高效调度的基础

各行各业正被人工智能发展浪潮以前所未有的力道进行重塑&#xff0c;而且支撑其运作的底层基础是算力。换种通俗易懂的说法&#xff0c;AI算力是指用来执行人工智能算法&#xff0c;对海量数据予以处理以及解析所需要的计算能力。它不是一项简单的硬件指标&#xff0c;而是包含…

作者头像 李华