Linux信号量操作

article/2025/11/7 3:44:22

信号量简介:

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

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

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


http://chatgpt.dhexx.cn/article/3CvcCTTV.shtml

相关文章

【关于Linux中----信号量及其使用场景】

文章目录 一、解释信号量1.1 概念的引入1.2 信号量操作和使用接口 二、信号量使用场景2.1 引入环形队列&&生产消费问题2.2 代码实现2.3 对于多生产多消费的情况2.4 申请信号量和加锁的顺序问题2.5 多生产多消费的意义 一、解释信号量 1.1 概念的引入 我们知道&#x…

linux(信号量)

信号量 几个基本概念临界资源临界区原子性互斥 信号量后台进程前台进程 信号储存信号处理信号(信号捕捉) 发送信号1、键盘产生&#xff1a;2、系统调用接口发送信号3、由软件条件产生信号4、硬件异常发送信号 内核中的信号量**信号量在内核中的数据结构****信号集操作函数** 信…

Linux--信号量

1.信号量的定义: 信号量是一个特殊的变量&#xff0c;一般取正数值。它的值代表允许访问的资源数目&#xff0c; 获取资源时&#xff0c;需要对信号量的值进行原子减一&#xff0c;该操作被称为p操作。当信号量值为0时&#xff0c;代表没有资源可用&#xff0c;p操作会阻塞。释…

【Linux】Linux的信号量集

所谓信号量集&#xff0c;就是由多个信号量组成的一个数组。作为一个整体&#xff0c;信号量集中的所有信号量使用同一个等待队列。Linux的信号量集为进程请求多个资源创造了条件。Linux规定&#xff0c;当进程的一个操作需要多个共享资源时&#xff0c;如果只成功获得了其中的…

linux信号量简介

一、什么是信号量 为了防止多个程序同时访问一个共享资源而引发的一系列问题&#xff0c;我们需要一种访问机制&#xff0c;它可以通过生成并使用令牌来授权&#xff0c;在同一时刻只能有一个线程访问代码的临界区域。 临界区域是指执行数据更新的代码需要独占式地执行。而信…

Linux下信号量使用总结

目录 1.Linux下信号量简介 2.POSIX信号量 2.1 无名信号量 2.2 有名信号量 3.System V信号量 1.Linux下信号量简介 信号量是解决进程之间的同步与互斥的IPC机制&#xff0c;互斥与同步关系存在的症结在于临界资源。 临界资源是在同一个时刻只容许有限个&#xff08;一般只有…

Linux信号量详解

Linux信号量详解 1.什么是信号量信号量是一种特殊的变量&#xff0c;访问具有原子性。只允许对它进行两个操作&#xff1a;1)等待信号量当信号量值为0时&#xff0c;程序等待&#xff1b;当信号量值大于0时&#xff0c;信号量减1&#xff0c;程序继续运行。2)发送信号量将信号量…

Linux进程间通信—信号量

一、概述 进程间通信&#xff08;interprocess communication&#xff0c;简称 IPC&#xff09;指两个进程之间的通信。系统中的每一个进程都有各自的地址空间&#xff0c;并且相互独立、隔离&#xff0c;每个进程都处于自己的地址空间中。所以同一个进程的不同模块譬如不同的函…

Linux操作系统-信号量

信号量也属于一种进程间通信的机制&#xff0c;与其他的进程间通信不同&#xff0c;信号量不是用来传输数据的&#xff0c;而是用来进程间同步与互斥。除此之外&#xff0c;信号量还可以实现线程间的互斥。 信号量是什么&#xff1f; 信号量的本质是一个计数器。 一个信号量…

Linux·信号量全解

目录 信号量 进程间 【无名信号量完成 有血缘关系的进程间 互斥】 知识点2【有名信号量 没有血缘进程互斥】 1、创建一个有名信号量 2、信号量的关闭&#xff1a; 3、信号量文件的删除 4、P操作 sem_wait V操作sem_post 销毁信号量sem_destroy 知识点3【有名信号量 没…

Linux-----信号量

信号量 信号量原理信号量概念信号量函数基于环形队列的生产消费模型空间和数据资源生产者和消费者申请、释放信号量模拟实现基于环形队列的生产者消费者模型 信号量原理 之前我们知道被多个执行流同时访问的公共资源叫做临界资源&#xff0c;而临界资源不保护的话会造成数据不…

Linux信号量

文章目录 POSIX信号量信号量的原理信号量的概念信号量函数 二元信号量模拟实现互斥功能基于环形队列的生产消费模型空间资源和数据资源生产者和消费者申请和释放资源必须遵守的两个规则代码实现信号量保护环形队列的原理 POSIX信号量 信号量的原理 我们将可能会被多个执行流同…

Linux —— 信号量

目录 一、POSIX信号量 1. 什么是信号量 2. 信号量的基本原理 二、与信号量相关的操作 1. 初始化信号量 2. 销毁信号量 3. 等待信号量 4. 发布信号量 三、基于环形队列的生产者消费者模型 1. 空间资源和数据资源 2. 生产者和消费者申请和释放资源 四、模拟实现基于…

Double取值intValue()与doubleValue()之参数缺省

Double调用intValue()是四舍五入向下取整。 调用doubleValue()才是取double真实值。

java.lang.NullPointerException: Attempt to invoke virtual method ‘int java.lang.Integer.intValue()‘

问题 对于PreparedStatement 对象设置参数时&#xff0c; 提示该错误; java.lang.NullPointerException: Attempt to invoke virtual method ‘int java.lang.Integer.intValue()’ 具体问题 2022-09-06 21:28:10.695 11368-11755/com.example.electronicmall E/AndroidRunt…

IntValue()方法 和 ValueOf()方法

intValue() 1.intValue()是java.lang.Number类的方法&#xff0c;Number是一个抽象类。Java中所有的数值类都继承它。也就是说&#xff0c;不单是Integer有intValue方法&#xff0c;Double&#xff0c;Long等都有此方法。 2.此方法的意思是&#xff1a;输出int数据。每个数值类…

Double取值intValue()与doubleValue()

描述一个之前没注意&#xff0c;手误造成的bug。 可以看出&#xff0c;Double调用intValue()结果类似于RoundingMode.DOWN。 调用doubleValue()才是取double真实值。

java中valueof_JAVA中intValue()和ValueOf()什么意思,还有Value什么意思

展开全部 intValue()和ValueOf()是数据类62616964757a686964616fe59b9ee7ad9431333366306538型转化的两个方法。 intValue() 如Integer类型&#xff0c;就会有intValue()方法&#xff0c;意思是说&#xff0c;把Integer类型转化为Int类型。 valueOf() 如String就有valueOf()方法…

IDEA告警:Unnecessary unboxing ‘xxx.intValue()‘

显式编码拆箱已包装的原始数值。在Java5及以上的版本&#xff0c;拆箱是不必要的&#xff0c;可以安全地删除。那么 JDK5 到底做了啥&#xff1f; 自动装箱&#xff08;auto-boxing&#xff09;与自动拆箱&#xff08;auto-unboxing&#xff09; Java语言的基本类型都有包装&…