信号量简介:

在对于临界区资源管理的过程中,多个程序同时访问一个共享资源经常容易引发一系列问题:如死锁,结果不唯一等等,

在1965年,由荷兰科学家E.W.Dijkstra提出了一种新的进程同步工具,信号量及其PV操作。

对于信号量的定义是这样的:

让多个进程通过特殊变量展开交互,一个进程在某一个关键点上被迫停止执行直至接收到对应的特殊变量值,通过这一措施,任何复杂的进程交互要求均可得到满足,这种特殊的变量就是信号量。

信号量的种类分为以下2种:

一般信号量:

设s为一个记录型数据结构,其中value为整型变量,系统初始化时为其赋值,PV操作的原语描述如下:

P(s):将信号量value值减1,若结果小于0,则执行P操作的进程被阻塞,若结果大于等于0,则执行P操作的进程将继续执行。

V(s):将信号量的值加1,若结果不大于0,则执行V操作的进程从信号量s有关的list所知队列中释放一个进程,使其转化为就绪态,自己则继续执行,若结果大于0,则执行V操作的进程继续执行。

二值信号量:

设s为一个记录型数据结构,其中分量value仅能取值0或1,二值信号量的PV操作的原语描述和一般信号量相同,虽然二值信号量仅能取值0或1,但可以证明他有着与其他信号量相同的表达能力

当然以上都是创造者所给出的定义,对于实现方式在不同的平台下接口会有不同,以下的实现部分均是建立在Linux平台下使用C语言编码实现,主要介绍的也是对应的C接口

上面那些定义总结起来可以这样说:通过使用信号量生成令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的一种手段。

函数操作:

对于与信号量操作有关的接口,Linux下主要提供了以下几个函数,值得注意的是,在Linux下的C接口中,这些函数的操作对象都是信号量值组,也就是一个信号量值的链表

int semget(key_t key, int num_sems, int sem_flags);

该函数的作用是创建一个新信号量或取得一个已有信号量。

第一个参数key是整数值(唯一非零),就是Linux线程操作中经常用到的键值,可以通过ftok函数得到,不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。

第二个参数num_sems指定需要的信号量数目,它的值几乎总是1。

第三个参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。

semget函数成功返回一个相应信号标识符(非零),失败返回-1

int semop(int sem_id, struct sembuf *sops, size_t nsops);

该函数的作用是改变信号量的值,其实就是为了信号量的PV操作而准备的,这个函数可以讲的地方比较多,下面会详细介绍:

函数的第一个参数 semid 为信号量集的标识符;

第2个参数 sops 指向进行操作的结构体数组的首地址,在 semop 的第二个参数 sops 指向的结构体数组中,每个 sembuf 结构体对应一个特定信号的操作。因此对信号量进行操作必须熟悉该数据结构,该结构定义在 linux/sem.h,如下所示:

[cpp] view plain copy
  1. struct sembuf{
  2. unsigned short sem_num; //信号在信号集中的索引,0代表第一个信号,1代表第二个信号
  3. short sem_op; //操作类型
  4. short sem_flg; //操作标志
  5. };

struct sembuf{
unsigned short sem_num; //信号在信号集中的索引,0代表第一个信号,1代表第二个信号 
short sem_op; //操作类型
short sem_flg; //操作标志
};

对于该结构中各个成员都具有特殊的含义,具体含义的介绍如下:

sem_op 参数:

sem_op > 0 信号加上 sem_op 的值,表示进程释放控制的资源;

sem_op = 0 如果sem_flg没有设置IPC_NOWAIT,则调用进程进入睡眠状态,直到信号量的值为0;否则进程不会睡眠,直接返回 EAGAIN

sem_op < 0 信号加上 sem_op 的值。若没有设置 IPC_NOWAIT ,则调用进程阻
塞,直到资源可用;否则进程直接返回EAGAIN

sem_flg 参数:

该参数可设置为 IPC_NOWAIT 或 SEM_UNDO 两种状态。只有将 sem_flg 指定为 SEM_UNDO 标志后,semadj (所指定信号量针对调用进程的调整值)才会更新。 此外,如果此操作指定SEM_UNDO,系统更新过程中会撤消此信号灯的计数(semadj)。此操作可以随时进行---它永远不会强制等待的过程。调用进程必须有改变信号量集的权限。
sem_flg公认的标志是 IPC_NOWAIT 和 SEM_UNDO。如果操作指定SEM_UNDO,当该进程终止时它将会自动撤消。

第3个参数 nsops 指出将要进行操作的信号的个数。semop 函数调用成功返回 0,失败返回 -1。

该函数所做的对于信号量的操作都是原子操作,即整个行为是一个整体,是不可打断的。所有操作是否可以立即执行取决于在个人sem_flg领域的IPC_NOWAIT标志的存在。

int semctl(int sem_id, int sem_num, int command, ...);

函数的第一个参数 semid 为信号量集的标识符;

函数的第二个参数sem_num则是表示即将要进行操作的信号量的编号,即信号量集合的索引值,其中第一个信号量的索引值为0。

函数的第3个参数command代表将要在集合上执行的命令,其取值含义如下,通常用特定的宏代替:

IPC_STAT:获取某个信号量集合的semid_ds结构,并将其储存在semun联合体的buf参数所指的地址之中

IPC_SET:设置某个集合的semid_ds结构的ipc_perm成员的值,该命令所取的值是从semun联合体的buf参数中取到的

IPC_RMID:从内核删除该信号量集合

GETALL:用于获取集合中所有信号量的值,整数值存放在无符号短整数的一个数组中,该数组有联合体的array成员所指定

GETNCNT:返回当前正在等待资源的进程的数目

GETPID:返回最后一次执行PV操作(semop函数调用)的进程的PID

GETVAL:返回集合中某个信号量的值

GETZCNT:返回正在等待资源利用率达到百分之百的进程的数目

SETALL:把集合中所有信号量的值,设置为联合体的array成员所包含的对应值

SETVAL:将集合中单个信号量的值设置为联合体的val成员的值

其中semun联合体的结构如下:

[cpp] view plain copy
  1. union semun{
  2. int val;
  3. struct semid_ds *buf;
  4. unsigned short *array;
  5. struct seminfo *__buf;
  6. };

对于该函数,只有当command取某些特定的值的时候,才会使用到第4个参数,第4个参数它通常是一个union semum结构,定义如下:

[cpp] view plain copy
  1. union semun{
  2. int val;
  3. struct semid_ds *buf;
  4. unsigned short *arry;
  5. };

对于第4个参数arg,

当执行SETVAL命令时用到这个成员,他用于指定要把信号量设置成什么值,涉及成员:val

在命令IPC_STAT/IPC_SET中使用,它代表内核中所使用内部信号量数据结构的一个复制 ,涉及成员:buf

在命令GETALL/SETALL命令中使用时,他代表指向整数值一个数组的指针,在设置或获取集合中所有信号量的值的过程中,将会用到该数组,涉及成员:array

剩下的还有一些用法都将在系统内核中的信号量代码使用,应用程序开发中使用很少,这里也就不介绍了。

实现样例:

这里列举一个别人的样例,主要是为了展示信号量控制进程的操作代码如下:

[html] view plain copy
  1. #include<iostream>
  2. #include <unistd.h>
  3. #include <sys/types.h>
  4. #include <sys/stat.h>
  5. #include <fcntl.h>
  6. #include <stdlib.h>
  7. #include <stdio.h>
  8. #include <string.h>
  9. #include <sys/sem.h>
  10. using namespace std;
  11. union semun
  12. {
  13. int val;
  14. struct semid_ds *buf;
  15. unsigned short *arry;
  16. };
  17. static int sem_id = 0;
  18. static int set_semvalue();
  19. static void del_semvalue();
  20. static int semaphore_p();
  21. static int semaphore_v();
  22. int main(int argc, char *argv[])
  23. {
  24. char message = 'S';
  25. int i = 0;
  26. //创建信号量
  27. sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
  28. if(argc > 1)
  29. {
  30. //程序第一次被调用,初始化信号量
  31. if(!set_semvalue())
  32. {
  33. fprintf(stderr, "Failed to initialize semaphore\n");
  34. exit(EXIT_FAILURE);
  35. }
  36. //设置要输出到屏幕中的信息,即其参数的第一个字符
  37. message = argv[1][0];
  38. sleep(2);
  39. }
  40. cout<<argc<<message<<endl;
  41. for(i = 0; i < 10; ++i)
  42. {
  43. //进入临界区
  44. if(!semaphore_p())
  45. exit(EXIT_FAILURE);
  46. //向屏幕中输出数据
  47. printf("进入%c", message);
  48. //清理缓冲区,然后休眠随机时间
  49. fflush(stdout);
  50. sleep(rand() % 3);
  51. //离开临界区前再一次向屏幕输出数据
  52. printf("离开%c\n", message);
  53. fflush(stdout);
  54. //离开临界区,休眠随机时间后继续循环
  55. if(!semaphore_v())
  56. exit(EXIT_FAILURE);
  57. sleep(rand() % 2);
  58. }
  59. sleep(10);
  60. printf("\n%d - finished\n", getpid());
  61. if(argc > 1)
  62. {
  63. //如果程序是第一次被调用,则在退出前删除信号量
  64. sleep(3);
  65. del_semvalue();
  66. }
  67. exit(EXIT_SUCCESS);
  68. }
  69. static int set_semvalue()
  70. {
  71. //用于初始化信号量,在使用信号量前必须这样做
  72. union semun sem_union;
  73. sem_union.val = 1;
  74. if(semctl(sem_id, 0, SETVAL, sem_union) == -1)
  75. return 0;
  76. return 1;
  77. }
  78. static void del_semvalue()
  79. {
  80. //删除信号量
  81. union semun sem_union;
  82. if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
  83. fprintf(stderr, "Failed to delete semaphore\n");
  84. else
  85. fprintf(stdout, "已经删除信号量\n");
  86. }
  87. static int semaphore_p()
  88. {
  89. //对信号量做减1操作,即等待P(sv)
  90. struct sembuf sem_b;
  91. sem_b.sem_num = 0;
  92. sem_b.sem_op = -1;//P()
  93. sem_b.sem_flg = SEM_UNDO;
  94. if(semop(sem_id, &sem_b, 1) == -1)
  95. {
  96. fprintf(stderr, "semaphore_p failed\n");
  97. return 0;
  98. }
  99. return 1;
  100. }
  101. static int semaphore_v()
  102. {
  103. //这是一个释放操作,它使信号量变为可用,即发送信号V(sv)
  104. struct sembuf sem_b;
  105. sem_b.sem_num = 0;
  106. sem_b.sem_op = 1;//V()
  107. sem_b.sem_flg = SEM_UNDO;
  108. if(semop(sem_id, &sem_b, 1) == -1)
  109. {
  110. fprintf(stderr, "semaphore_v failed\n");
  111. return 0;
  112. }
  113. return 1;
  114. }

编译及运行指令为:

[html] view plain copy
  1. g++ -o 可执行文件名 代码文件名   //编译
  2. ./可执行文件名 1 2 & ./可执行文件名   //运行

运行效果图如下:

Linux信号量操作相关推荐

  1. 最全面的linux信号量解析

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

  2. linux申请信号量,linux 信号量

    https://www.jianshu.com/p/6e72ff770244 无名信号量 只适合用于一个进程的不同线程 #include #include #include #include #inc ...

  3. 最全面的 linux 信号量解析

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

  4. linux文件信号量删除,linux信号量_閑の洎茬

    1.1 创建信号量 int semget( key_t key,   //标识信号量的关键字,有三种方法:1.使用IPC--PRIVATE让系统产生, // 2.挑选一个随机数,3.使用ftok从文件 ...

  5. Linux信号量(1)-SYSTEM V

    ​ 信号量概念 信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送 ...

  6. SVN的Windows和Linux客户端操作详解

    SVN的Windows和Linux客户端操作详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Windows客户端操作 1.安装SVN客户端 a>.去官网下载svn软件 ...

  7. linux 线程操作问题undefined reference to ‘pthread_create‘的解决办法(cmake)

    linux 线程操作问题undefined reference to 'pthread_create'的解决办法(cmake) 参考文章: (1)linux 线程操作问题undefined refer ...

  8. 一篇文章搞定Linux基础操作

    文章目录 引言 1.认识Linux 2.相对路径与绝对路径 3.Linux的权限管理 4.Linux的命令 4.1 命令 ls 查看当前文件 4.2 命令 cd 跳转路径 4.3 命令 pwd 查看当 ...

  9. linux 设计一个程序,要求打开文件 pass 所有者,第二章 Linux 文件操作

    文档均来自网络,如有侵权请联系我删除文档 第二章 Linux 文件操作(4学时) 实验一文件属性和无缓冲I/O 实验目的 1.熟悉Linux文件系统属性控制 2.掌握不带缓存的文件I/O程序设计方法 ...

  10. 你一定要知道的关于Linux文件目录操作的12个常用命令

    博客园 首页 新随笔 联系 管理 订阅 随笔- 26  文章- 1  评论- 18  你一定要知道的关于Linux文件目录操作的12个常用命令 转自:http://www.cnblogs.com/yo ...

最新文章

  1. V$session表的妙用
  2. REST 在 Java 中的使用
  3. 用c语言编程矩阵乘法,c语言矩阵相乘
  4. 5.1 jQuery基础
  5. [Leedcode][JAVA][第198题][打家劫舍][动态规划]
  6. 被面试官问懵:TCP 四次挥手收到乱序的 FIN 包会如何处理?
  7. python设计模式六大原则_学习设计模式 - 六大基本原则之迪米特法则(示例代码)...
  8. easyui_datagrid模板代码
  9. 酒店订房管理系统php,酒店预定管理系统(源码+数据库+文档)
  10. 计算机文件夹怎样显示隐藏文件,显示隐藏文件夹,教您电脑如何显示隐藏文件夹...
  11. Url Rewrite Filter 3.2.0中文手册
  12. Simulink Resolver 旋转变压器解码仿真
  13. 使用Hooks实现防抖节流 TS版本
  14. html底部添加备案号,网站底部怎么放备案号 WordPress、CMS、discuz等常见程序添加方法...
  15. Delphi UAC生成默认以管理员身份运行的可执行程序
  16. 使用@Value取不到值,为null
  17. make出错,编译通不过的解决办法
  18. cups共享linux打印机_使用CUPS服务器共享打印机
  19. 最全的食物营养素含量(蛋白质、维生素、铁、钙、锌等)
  20. 双位置继电器ST2-2L/AC220V

热门文章

  1. Day704.Tomcat内存溢出的原因分析及调优 -深入拆解 Tomcat Jetty
  2. SlickEdit基本设置
  3. 总结SlickEdit的快捷键,分享当前自用配置
  4. 推荐一个语音机器人项目
  5. 基于树莓派的语音机器人
  6. 1.ZooKeeper Java客户端的基本使用「第三章 ZooKeeper Java客户端」「架构之路ZooKeeper理论和实战」
  7. abaqus 不收敛问题
  8. 【软件工程】软件需求说明书
  9. 简明Python教程第二部分7-9
  10. 智能制造与MES系统的内在联系