前言

  • 原文链接
  • 知识点
    • 消息队列、信号量共享内存 被统称为 system-V IPC

      • 以上都是“持续性”资源,即它们被创建之后, 不会因为进程的退出而消失
  • 说明:
    • 以下 信号量 如无说明,均为 system-V IPC 信号量

5. 信号量

5.1 概念

  • 信号量

    • 信号量可以理解为一个计数器
    • 主要用于保护资源
  • 原子操作:单指令的操作称为原子操作,单条指令的执行时不会被打断的

5.2 工作原理

  • 信号量就是两种操作:P 操作和 V 操作

    • P 操作:就是申请资源,资源 -1
    • V 操作:就是释放资源,资源 +1
    • 资源为 0 时,无资源申请

5.3 操作函数

  • 使用 semget() 创建或获取一个信号量
  • 使用 semop() 进行 PV 操作
  • 使用 semctl() 进行一系列控制操作

5.3.1 semget()

  • 使用 semget() 创建或获取一个信号量
  • 通过命令 man 了解更多
  • 函数原型:int semget(key_t key, int nsems, int semflg);
    • key:信号量键值,可以自定义一个键值,也可以使用 IPC_PRIVATE 创建一个没有 key 的信号量
    • nsems信号量数目
    • semflg:表示创建的信号量的模式标志参数,主要有IPC_CREAT,IPC_EXCL和权限mode,如:
      • IPC_CREAT:没有关键字 key 的信号量就新建一个,有就直接打开
      • IPC_CREAT | IPC_EXCL:信号量不存在,则新建一个,如果信号量存在,则报错
      • IPC_CREAT | 0666:(注:信号量不在意执行权限)
    • 返回:
      • 成功:返回信号量标识符
      • 失败:返回 -1,原因记录在变量 error
        • EACCES:没有访问该信号量集的权限
        • EEXIST:信号量集已经存在,无法创建
        • EINVAL:参数nsems的值小于0或者大于该信号量集的限制;或者是该key关联的信号量集已存在,并且nsems大于该信号量集的信号量数
        • ENOENT:信号量集不存在,同时没有使用IPC_CREAT
        • ENOMEM :没有足够的内存创建新的信号量集
        • ENOSPC:超出系统限制
  • 创建信号量也受下面值限制:(通过命令 ipcs -l 可查)
    • SEMMNI:系统中信号量总数的最大值
    • SEMMSL:每个信号量中信号量元素个数的最大值
    • SEMMNS:系统中所有信号量中的信号量元素总数的最大值。

5.3.2 semop()

  • 使用 semop() 进行 PV 操作
  • 通过命令 man 了解更多
  • 函数原型:int semop(int semid, struct sembuf *sops, size_t nsops);
    • semid:信号量标识符
    • sops:指向存储信号操作结构的数组指针,信号操作结构的原型如下:
      struct sembuf
      {
      unsigned short int sem_num; /* 信号量的序号从0 ~ nsems-1 */
      short int sem_op; /* 对信号量的操作,>0, 0, <0 */
      short int sem_flg; /* 操作标识:0, IPC_WAIT, SEM_UNDO */
      };
      • sem_num:标识信号量中第几个信号量,从 0 开始
      • sem_op:对信号量所进行的操作类型
        • 0:V 操作(回收资源),把 sem_op 的值加到该信号量的信号量当前值 semval 上

        • = 0:表示进程要阻塞等待,直至信号量当前值 semval 变为 0
          • 如果没有设置 IPC_NOWAIT ,则调用该操作的进程或者线程将暂时睡眠,直到信号量的值为0
          • 如果设置 IPC_NOWAIT,则进程或者线程不会睡眠,函数返回错误EAGAIN
        • < 0:P 操作(申请资源),如果其绝对值大于信号值 semval ,则操作会阻塞;直到信号值 semval 大于等于 sem_op 的绝对值
    • nsops:信号操作标志
      • 0:正常操作
      • SEM_UNDO:程序结束时(不论正常或异常),保证信号值会被重设为 semop() 调用前的值。目的是避免资源永远锁定。
      • 信号操作结构的数量,恒大于或等于1
    • 返回:
      • 成功:返回 0
      • 失败:返回 -1,原因记录在变量 error
        • E2BIG:一次对信号的操作数超出系统的限制
        • EACCES:调用进程没有权能执行请求的操作,并且不具有CAP_IPC_OWNER权能
        • EAGAIN:信号操作暂时不能满足,需要重试
        • EFAULT:sops或timeout指针指向的空间不可访问
        • EFBIG:sem_num指定的值无效
        • EIDRM:信号集已被移除
        • EINTR:系统调用阻塞时,被信号中断
        • EINVAL:参数无效
        • ENOMEM:内存不足
        • ERANGE:信号所允许的值越界

5.3.3 semctl()

  • 使用 semctl() 进行一系列控制操作
  • 通过命令 man 了解更多
  • 函数原型:int semctl(int semid, int semnum, int cmd, ...);
    • semid:System V信号量的标识符
    • semnum:表示信号量集中的第 semnum 个信号量。它的取值范围: 0 ~ nsems-1
    • cmd:操作命令,主要有以下命令:
      • IPC_STAT:获取此信号量集合的semid_ds结构,存放在第四个参数的buf中
      • IPC_SET:通过第四个参数的buf来设定信号量集相关联的semid_ds中信号量集合权限为sem_perm中的uid,gid,mode
      • IPC_RMID:从系统中删除该信号量集合
      • GETVAL:返回第semnum个信号量的值
      • SETVAL:设置第semnum个信号量的值,该值由第四个参数中的val指定
      • GETPID:返回第semnum个信号量的sempid,最后一个操作的pid
      • GETNCNT:返回第semnum个信号量的semncnt。等待semval变为大于当前值的线程数
      • GETZCNT:返回第semnum个信号量的semzcnt。等待semval变为0的线程数
      • GETALL:去信号量集合中所有信号量的值,将结果存放到的array所指向的数组
      • SETALL:按arg.array所指向的数组中的值,设置集合中所有信号量的值
    • 第四个参数为一个可选的联合体:
      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)*/
      };

5.4 例程

  • 只有一个资源
  • 等待子进程释放了资源后,父进程才继续往下执行
  • 信号量操作封装文件

#include <sys/sem.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include "sem.h" /**
* @brief 初始化第一个信号量的资源数
* @param sem_id:信号量标识符
* @param init_value:初始化值
*/
int init_sem(int sem_id, int init_value)
{
union semun sem_union;
sem_union.val = init_value; /*init_value 为初始值*/
if (semctl(sem_id, 0, SETVAL, sem_union) == -1)
{
perror("Initialize semaphore");
return -1;
}
return 0;
} /**
* @brief 从系统中删除信号量的函数
* @param sem_id:信号量标识符
*/
int del_sem(int sem_id)
{
union semun sem_union;
if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
{
perror("Delete semaphore");
return -1;
}
} /**
* @brief 对第一个信号量进行 P 操作
* @param sem_id:信号量标识符
*/
int sem_p(int sem_id)
{
struct sembuf sops;
sops.sem_num = 0; /* 单个信号量的编号应该为 0 */
sops.sem_op = -1; /* 表示 P 操作 */
sops.sem_flg = SEM_UNDO; /* 若进程退出,系统将还原信号量*/ if (semop(sem_id, &sops, 1) == -1)
{
perror("P operation");
return -1;
}
return 0;
} /**
* @brief 对第一个信号量进行 V 操作
* @param sem_id:信号量标识符
*/
int sem_v(int sem_id)
{
struct sembuf sops;
sops.sem_num = 0; /* 单个信号量的编号应该为 0 */
sops.sem_op = 1; /* 表示 V 操作 */
sops.sem_flg = SEM_UNDO; /* 若进程退出,系统将还原信号量 */ if (semop(sem_id, &sops, 1) == -1)
{
perror("V operation");
return -1;
}
return 0;
}
  • 信号量demo APP
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include "sem.h"
#define DELAY_TIME 5 /* 子进程释放信号量延时时间 */
int main(void)
{
pid_t result;
int sem_id;
sem_id = semget((key_t)6666, 1, 0666 | IPC_CREAT); /* 创建一个信号量*/
init_sem(sem_id, 0); // 初始化已创建的信号量的资源数为0
/*调用 fork()函数*/
result = fork();
if(result == -1)
{
perror("Fork\n");
}
else if (result == 0) /*返回值为 0 代表子进程*/
{
printf("Child process will wait for %d seconds...\n", DELAY_TIME);
sleep(DELAY_TIME);
printf("The returned value is %d in the child process(PID = %d)\n",result, getpid());
sem_v(sem_id); // V 操作,释放资源
}
else /*返回值大于 0 代表父进程*/
{
sem_p(sem_id); // 申请资源
printf("The returned value is %d in the father process(PID = %d)\n",result, getpid());
sem_v(sem_id); // 释放资源
del_sem(sem_id); // 删除信号量
}
exit(0);
}

参考:

* 野火

【linux】系统编程-3-system-V IPC 信号量的更多相关文章

  1. linux进程间通讯-System V IPC 信号量

    进程间通信的机制--信号量.注意请不要把它与之前所说的信号混淆起来,信号与信号量是不同的两种事物.有关信号的很多其它内容,能够阅读我的还有一篇文章:Linux进程间通信--使用信号.以下就进入信号量的 ...

  2. linux系统编程之(一) 信号量

    信号量 一.什么是信号量 信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有. 信号量的值为正的时候,说明它空闲.所测试的线程可以锁定而使用它.若为0,说明 它被占用,测 ...

  3. linux c编程:System V消息队列一

    消息队列可以认为是一个消息链表,System V 消息队列使用消息队列标识符标识.具有足 够特权的任何进程都可以往一个队列放置一个消息,具有足够特权的任何进程都可以从一个给定队列读出一个消息.在某个进 ...

  4. System V IPC 之信号量

    本文继<System V IPC 之共享内存>之后接着介绍 System V IPC 的信号量编程.在开始正式的内容前让我们先概要的了解一下 Linux 中信号量的分类. 信号量的分类 在 ...

  5. 线程同步、信号量、system v IPC

    一.线程同步 条件变量 什么是条件变量? 线程A等待某个条件成立,条件成立,线程A才继续向下执行.线程B的执行使条件成立,条件成立以后唤醒线程A,以继续执行.这个条件就是条件变量. pthread_c ...

  6. 从并发处理谈PHP进程间通信(二)System V IPC

    .container { margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px } .conta ...

  7. Linux 系统编程 学习:04-进程间通信2:System V IPC(1)

    Linux 系统编程 学习:04-进程间通信2:System V IPC(1) 背景 上一讲 进程间通信:Unix IPC-信号中,我们介绍了Unix IPC中有关信号的概念,以及如何使用. IPC的 ...

  8. Linux 系统编程 学习:05-进程间通信2:System V IPC(2)

    Linux 系统编程 学习:05-进程间通信2:System V IPC(2) 背景 上一讲 进程间通信:System V IPC(1)中,我们介绍了System IPC中有关消息队列.共享内存的概念 ...

  9. Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道

    Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道 背景 上一讲我们介绍了创建子进程的方式.我们都知道,创建子进程是为了与父进程协作(或者是为了执行新的程序,参考 Linux ...

  10. Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号

    Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号 背景 上一讲我们介绍了Unix IPC中的2种管道. 回顾一下上一讲的介绍,IPC的方式通常有: Unix IPC包括:管道 ...

随机推荐

  1. git merge bug

    git merge bug 本地分支 dev commit 后, 直接 pull 远程 dev 分支, 导致远程 dev 分支 merge 到本地 dev 分支了, 取消本次 merge 操作? Re ...

  2. React Fragment All In One

    React Fragment All In One React还提供了一个无需包装即可呈现多个元素的组件. https://reactjs.org/docs/react-api.html#fragme ...

  3. OOP & 模块化, 多态, 封装

    OOP 面向对象编程 (OOP) 是用抽象方式创建基于现实世界模型的一种编程模式.它使用先前建立的范例,包括模块化,多态和封装几种技术. 在 OOP 中,每个对象能够接收消息,处理数据和发送消息给其他 ...

  4. Flutter web & Flutter

    Flutter web & Flutter Google I/O 2019 recap & GDG shanghai flutter 与 Android 原生,应用应用,性能对比, d ...

  5. perl 打印简单的help文档

    更多 PrintHelp.pm #!/usr/bin/perl package PrintHelp; require Exporter; use v5.26; use strict; use utf8 ...

  6. 破除区块链支付壁垒,NGK支付架构方案浮出水面

    什么叫做区块链支付?区块链支付系统与传统支付系统有哪些不同?简要地说,原来传统的支付系统是有一个类似于银行的中间平台存在的,用户们的支付交易第一时间是寄存在平台,由平台核实验证交易行为之后,方才放行交 ...

  7. 「NGK每日快讯」12.1日NGK公链第28期官方快讯!

  8. 24_MySQL插入insert语句

    本节涉及SQL语句: -- MYSQL 基础操作 1.插入insert语句 INSERT INTO t_dept(deptno,dname,loc) VALUES(70,"后勤部" ...

  9. Python3.x 基础练习题100例(11-20)

    练习11: 题目: 古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? 分析: 兔子的规律为数列1,1,2, ...

  10. 从零开始搞后台管理系统(2)——shin-server

      shin 的读音是[ʃɪn],谐音就是行,寓意可行的后端系统服务,shin-server 的特点是: 站在巨人的肩膀上,依托KOA2.bunyan.Sequelize等优秀的框架和库所搭建的定制化 ...