《Linux C编程实战》笔记:信号量

信号量在操作系统的书里一般都有介绍,这里就只写书上说的了。

信号量是一个计数器,常用于处理进程或线程的同步问题,特别是对临界资源访问的同步。临界资源可以简单地理解为在某一时刻只能由一个进程或线程进行操作的资源,这里的资源可以是一段代码、一个变量或某种硬件资源。信号量的值大于或等于0时表示可供并发进程使用的资源实体数;小于0时代表正在等待使用临界资源的进程数。

与消息队列类似,Linux内核也为每个信号量集维护了一个semid_ds数据结构示例。该结构定义在头文件linux/sem.h中。

struct semid_ds {struct ipc_perm	sem_perm;		/* 对信号进行操作的许可权,和上一节的消息队列一样的 */__kernel_old_time_t sem_otime;		/* 对信号量进行操作的最后时间 */__kernel_old_time_t sem_ctime;		/* 对信号量进行修改的最后时间 */struct sem	*sem_base;		/* 指向第一个信号量 */struct sem_queue *sem_pending;		/* 等待处理的挂起操作 */struct sem_queue **sem_pending_last;	/* 最后一个正在挂起的操作 */struct sem_undo	*undo;			/* 撤销的请求 */unsigned short	sem_nsems;		/* 数组中的信号量个数 */
};

信号量的创建与使用

信号量集的创建与打开

信号量集和消息队列一样,创建了之后在进程间使用。Linux下使用semget创建或打开信号集,这个函数定义在头文件sys/sem.h中

int semget(key_t key, int nsems, int semflg);
  • key:一个键值,用于唯一标识信号量集。通常可以使用 ftok() 函数来生成该键值。
  • nsems:指定要创建或访问的信号量集中的信号量数量。如果只是打开信号量集,取0即可
  • semflg:这个参数和创建消息队列里的msgflg使用方式一样,通过IPC_CREATE,IPC_EXCL等标志来标志操作方式,具体看上一篇文章

semget() 函数成功时返回一个非负整数,表示信号量集的标识符(或称为信号量集描述符),失败返回-1

示例代码1

下面函数演示创建一个信号量集并对其中所有信号量进行初始化

int createsem(const char *pathname,int proj_id,int members,int init_val){key_t msgkey;int index,sid;union semun semopts;//这个结构体后面会讲if((msgkey=ftok(pathname,proj_id))==-1){perror("ftok error!\n");return -1;;}if((sid=semget(msgkey,members,IPC_CREAT|0666))==-1){perror("semget call failed.\n");return -1;}//后面是初始化操作semopts.val=init_val;for(index=0;index<members;index++){semctl(sid,index,SETVAL,semopts);}return sid;
}

信号量的操作

信号量的值与相应资源的使用情况有关。当它的值大于0时,表示当前可用资源的数量。当它的值小于0时,其绝对值表示等待使用该资源的进程个数。信号量的值仅能由PV操作来改变。在Linux下,PV操作通过调用semop实现。该函数定义在头文件sys/sem.h中

int semop(int semid, struct sembuf *sops, size_t nsops);
  • semid:信号量集的标识符,通常是由 semget() 函数返回的值。
  • sops:一个指向结构数组的指针,每个结构体描述了对单个信号量的操作。sembuf 结构体包含以下字段:
    • sem_num:要操作的信号量在信号量集中的索引。
    • sem_op:操作类型,可以是正数表示增加信号量的值,负数表示减少信号量的值,0 表示等待信号量值变为 0。
    • sem_flg:标志位,用于指定操作的行为,比如操作的方式(阻塞或非阻塞)等。
  • nsops:操作的数量,即 sops 数组中元素的个数。
  • 成功返回0,失败返回-1

当然还是要看一下sembuf这个结构体具体的内容

struct sembuf
{unsigned short int sem_num;	/* 信号在信号量集中的索引 */short int sem_op;		/* 操作类型 */short int sem_flg;		/* 操作标志 */
};

sem_flg可以设置为IPC_NOWAIT,则调用进程直接返回。如果没设置,semop会阻塞进程直到资源可用。

示例代码2

下面是对一个信号量集中的某个信号进行操作的P、V函数

int sem_p(int semid,int index){//p是消耗资源struct sembuf buf={0,-1,IPC_NOWAIT};//所以第二个参数给的是负数,这里是-1if(index<0){perror("index of array cannot equal a minus value!\n");return -1;}buf.sem_num=index;if(semop(semid,&buf,1)==-1){//nsops是1,因为数组大小只有1perror("a wrong operation to semaphore ocurred!\n");return -1;}return 0;
}
int sem_v(int semid,int index){struct sembuf buf={0,1,IPC_NOWAIT};//v是释放资源,所以sem_op是正数1if(index<0){perror("index of array cannot equal a minus value!\n");return -1;}buf.sem_num=index;if(semop(semid,&buf,1)==-1){perror("a wrong operation to semaphore ocurred!\n");return -1;}return 0;
}

信号量集的控制

使用信号量时,往往需要对信号量集进行一些控制操作。比如删除信号量集、对内核维护的信号量集的数据结构semid_ds进行设置,获取信号量集中信号值等。通过semtcl控制函数可以完成这些操作,该函数定义在sys/sem.h,如下图所示:

int semctl(int semid, int semnum, int cmd, ...);
  • semid:信号量集的标识符。
  • semnum:信号量在集合中的索引。
  • cmd:要执行的控制操作。
  • ...:根据 cmd 参数指定的控制操作,可能需要附加参数。

最后的...说明函数的参数是可选的,它依赖于第三个参数cmd,它通过共用体变量semun选择要操作的参数,semun定义在linux/sem.h

union semun {int val;			/* 设置某个信号的值等于val for SETVAL */struct semid_ds *buf;	/* 存取semid_ds for IPC_STAT & IPC_SET */unsigned short *array;	/*  for GETALL & SETALL */struct seminfo *__buf;	/* 为控制IPC_INFO 提供的缓存 */void *__pad;
};

第二个参数cmd,通过宏来只是操作类型

  1. IPC_STAT:获取信号量集的当前状态,包括信号量集中每个信号量的当前值等信息。

  2. IPC_SET:设置信号量集的状态,可以用于设置信号量集中每个信号量的值。

  3. IPC_RMID:从系统中删除信号量集,释放其占用的资源。

  4. GETALL:获取信号量集中所有信号量的当前值。

  5. GETNCNT:获取在等待信号量值增加的进程数量。

  6. GETPID:获取上次执行 semop 函数的进程的进程ID。

  7. GETVAL:获取特定信号量的当前值。

  8. GETZCNT:获取在等待信号量值变为 0 的进程数量。

  9. SETALL:设置信号量集中所有信号量的值。

  10. SETVAL:设置特定信号量的值。

示例代码3

下面是获取和设置单个新信号的函数

int semval_op(int semid,int index,int cmd){if(index<0){printf("index cannot be minus!\n");return -1;}if(cmd==GETVAL||cmd==SETVAL)return semctl(semid,index,cmd,0);//0只有在SETVAL才有用,表示我们要把该信号量的值设置为0printf("function cannot support cmd:%d\n",cmd);return -1;
}

之前介绍的时候没有说setctl的返回值,因为也是根据cmd的不同而不同的。比如这个示例代码。如果cmd是GETVAL,那么函数的返回值就是对应信号量的值;如果cmd是SETVAL,那么函数的返回值就是用来标识操作是否成功,成功返回0,失败返回-1.

示例程序4

信号量一般用于处理访问临界资源的同步问题。下面也是两个程序,server和client。server创建一个信号量集,并对信号量循环减1,相当于分配资源。client执行时检查信号量,如果其值大于0表示有资源可用,继续执行,如果小于等于0代表资源已经分配完毕,进程client退出。

server:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h> 
union semun {int val;                // Value for SETVALstruct semid_ds *buf;   // Buffer for IPC_STAT, IPC_SETunsigned short *array;  // Array for GETALL, SETALLstruct seminfo *__buf;  // Buffer for IPC_INFO (Linux-specific)
};#define MAX_RESOURCE 5
int main(int argc,char **argv,char **environ){key_t key;int semid;struct sembuf sbuf={0,-1,IPC_NOWAIT};//-1代表使用资源union semun semopts;if((key=ftok(".",'s'))==-1){perror("ftok error!\n");exit(1);}if((semid=semget(key,1,IPC_CREAT|0666))==-1){//1表面信号量集只有一个信号量perror("semget error!\n");exit(1);}semopts.val=MAX_RESOURCE;if(semctl(semid,0,SETVAL,semopts)==-1){//把信号量集的那个信号量的值设置成MAX_RESOURCEperror("semctl error!\n");exit(1);}while (1){if(semop(semid,&sbuf,1)==-1){//程序循环减少信号量的值,也就是隔3秒使用一个资源perror("semop error!\n");exit(1);}sleep(3);}exit(0);
}

编译这个文件的问题很多,按照书上所说的应该加上linux/sem.h这个头文件,因为union semun就是在这个头文件定义的,但是它和 <sys/sem.h>一起包含的话会出现很多的重复定义,最后只能不包含linux/sem.h,自己去定义union semun了。

而如果只用linux/sem.h的话像semctl这些函数又没有定义,程序也用不了。

所以感觉要么是书上有错误,要么是书太老了,很多东西都改了导致照书上敲有问题

client:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/sem.h>
//把sys/ipc.h删了是因为我发现sem.h里就已经包含了它
#include <unistd.h> 
union semun {int val;                // Value for SETVALstruct semid_ds *buf;   // Buffer for IPC_STAT, IPC_SETunsigned short *array;  // Array for GETALL, SETALLstruct seminfo *__buf;  // Buffer for IPC_INFO (Linux-specific)
};
int main(int argc,char **argv,char **environ){key_t key;int semid,semval;union semun semopts;if((key=ftok(".",'s'))==-1){perror("ftok error!\n");exit(1);}if((semid=semget(key,1,IPC_CREAT|0666))==-1){perror("semget error!\n");exit(1);}while (1){if((semval=semctl(semid,0,GETVAL))==-1){//去获取信号量的值perror("semctl error!\n");exit(1);}if(semval>0){//大于0说明还有资源可用printf("Still %d resources can be used\n",semval);}else{printf("No more resources can be used!\n");break;}sleep(3);}exit(0);
}

运行结果:

稍微解释一下 ,先执行server,再执行client,资源不是从5开始应该是因为client不是第一时间执行,漏了两个。而server在信号量为0后就自动退出也是因为struct sembuf sbuf={0,-1,IPC_NOWAIT};设置了IPC_NOWAIT,这样在semop(semid,&sbuf,1)==-1这个判断中不会阻塞而是直接返回-1从而退出程序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/698832.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【零基础入门】什么样的人适合学网络安全?该怎么学?

有很多想要转行网络安全或者选择网络安全专业的人在进行决定之前一定会有的问题&#xff1a; 什么样的人适合学习网络安全&#xff1f;我适不适合学习网络安全&#xff1f; 到底什么样的人适合学习网络安全呢&#xff1f;&#xff08;需要什么前提条件呢&#xff09; 开门见山…

订单到期关闭如何实现(延迟场景处理)

文章目录 概述种处理方案任务调度实现(定时任务)基于redis 如何实现1redis过期key实现&#xff08;键通知机制&#xff09;基于redis延迟队列Redisson实现一个延迟队列 基于MQ的延迟队列实现 概述 订单30分钟未支付自动取消怎么实现 日常开发中&#xff0c;我们经常遇到这种业…

2. Linux进程管理

2. 进程管理 2.1 Linux进程 进程是程序执行时的一个动态实体&#xff0c;包含程序计数器、全部CPU 寄存器的值和进程堆栈中存储着的一些临时数据&#xff0c;如子程序参数、返回地址及变量等&#xff0c;反映的是当前处理器的活动状态。 而程序是仅包含指令和数据的一段静态代…

【Appium UI自动化】pytest运行常见错误解决办法

通过Appium工具录制代码在pycharm上运行报错&#xff1a; 错误一&#xff1a; 1.提示 setup() 方法运行 error failed 解决办法&#xff1a;未创建 init __ 方法&#xff0c;创建一个空的__init.py文件就解决了。 原因&#xff1a; 错误二&#xff1a; 2.运行代码&#xff…

Linux之ACL权限管理

文章目录 1.ACL权限介绍二、操作步骤1. 添加测试目录、用户、组&#xff0c;并将用户添加到组2. 修改目录的所有者和所属组3. 设定权限4. 为临时用户分配权限5. 验证acl权限6. 控制组的acl权限 1.ACL权限介绍 每个项目成员有一个自己的项目目录&#xff0c;对自己的目录有完全…

构建生物医学知识图谱from zero to hero (4):通过Neo4j构建知识图谱

图数据库是一种专门用于存储图形数据的 NoSQL 数据库。与传统的关系型数据库和其他 NoSQL 数据库不同,图数据库利用图形数据模型来存储和管理数据。图形数据模型由节点和边组成,节点代表实体,边代表实体之间的关系。例如,在社交网络中,用户可以表示为节点,朋友关系可以表…

xff注入 [CISCN2019 华东南赛区]Web111

打开题目 看见smarty 想到模板注入 又看见ip 想到xff注入 一般情况下输入{$smarty.version}就可以看到返回的smarty的版本号。该题目的Smarty版本是3.1.30 在Smarty3的官方手册里有以下描述: Smarty已经废弃{php}标签&#xff0c;强烈建议不要使用。在Smarty 3.1&#xff…

C# OpenVINO 百度PaddleSeg实时人像抠图PP-MattingV2

目录 效果 项目 代码 下载 C# OpenVINO 百度PaddleSeg实时人像抠图PP-MattingV2 效果 项目 代码 using OpenCvSharp; using Sdcb.OpenVINO; using System; using System.Diagnostics; using System.Drawing; using System.Security.Cryptography; using System.Text; us…

SparkSQL学习03-数据读取与存储

文章目录 1 数据的加载1.1 方式一&#xff1a;spark.read.format1.1.1读取json数据1.1.2 读取jdbc数据 1.2 方式二&#xff1a;spark.read.xxx1.2.1 读取json数据1.2.2 读取csv数据1.2.3 读取txt数据1.2.4 读取parquet数据1.2.5 读取orc数据1.2.6 读取jdbc数据 2 数据的保存2.1…

SmartX 携手 openGauss 社区发布联合方案评测与性能最佳实践

近日&#xff0c;北京志凌海纳科技有限公司&#xff08;以下简称 “SmartX”&#xff09;携手 openGauss 社区完成了 openGauss 数据库基于 SmartX 超融合平台&#xff08;SMTX OS&#xff09;和 SmartX 分布式存储平台&#xff08;SMTX ZBS&#xff09;的性能测试和调优。 结果…

JavaScript中的可选链——通过示例解释

JavaScript开发经常涉及导航嵌套对象&#xff0c;这可能很麻烦且容易出错&#xff0c;特别是在处理null或undefined值时。可选链是现代JavaScript语法中的一个改革性特性。 在本文中&#xff0c;我们将通过实际示例探讨可选链&#xff0c;演示它如何简化代码并使开发更加高效。…

MySQL数据库基础(十三):关系型数据库三范式介绍

文章目录 关系型数据库三范式介绍 一、什么是三范式 二、数据冗余 三、范式的划分 四、一范式 五、二范式 六、三范式 七、总结 关系型数据库三范式介绍 一、什么是三范式 设计关系数据库时&#xff0c;遵从不同的规范要求&#xff0c;设计出合理的关系型数据库&…

代码随想录算法训练营第五十九天|

583. 两个字符串的删除操作 本题和动态规划&#xff1a;115.不同的子序列 相比&#xff0c;其实就是两个字符串都可以删除了&#xff0c;情况虽说复杂一些&#xff0c;但整体思路是不变的。 代码随想录 class Solution {public int minDistance(String word1, String word2) {…

流畅的Python(十一)-从协议到抽象基类

一、核心要义 主要讨论Python中的接口&#xff0c;所谓接口就是类实现或继承的一套公开(按照定义,受保护的属性和私有属性不在接口中)属性和方法&#xff0c;包括特殊方法&#xff0c;如__getitem__或__add__等。Python有两套规范接口的方式: 1. 鸭子类型和协议&#xff0c;这…

几种后端开发中常用的语言。

几种后端开发中常用的语言。 C/C 语言 C 语言最初是用于系统开发工作&#xff0c;特别是组成操作系统的程序。由于 C 语言所产生的代码运行速度与汇编语言编写的代码运行速度几乎一样&#xff0c;所以采用 C 语言作为系统开发语言。目前&#xff0c;C 语言是最广泛使用的系统…

MongoDB聚合运算符:$atan2

$atan2用来计算反正切&#xff0c;返回指定表达式的反正切值&#xff0c;与$antan的区别主要是参数不同。 语法 { $atan2: [<expression1>, <expression1>] }<expression>为可被解析为数值的表达式$atan2返回弧度&#xff0c;使用$radiansToDegrees运算符可…

数据结构与算法-常用排序算法

一、常用排序说明 当涉及排序算法时&#xff0c;理解每个算法的工作原理、时间复杂度和空间复杂度是至关重要的。下面对常用排序算法进行详细说明&#xff1a; 1、冒泡排序&#xff08;Bubble Sort&#xff09;&#xff1a; 工作原理&#xff1a;比较相邻的元素并交换&am…

python bug与debug

一、什么是bug&#xff08;软件缺陷&#xff09;&#xff1f; 产品说明书中规定要做的事情&#xff0c;而软件没有实现。 产品说明书中规定不要做的事情&#xff0c;而软件确实现了。 产品说明书中没有提到过的事情&#xff0c;而软件确实现了。 产品说明书中没有提到但是必…

跨语言的序列化与反序列化

在Java中实现跨语言的序列化与反序列化通常可以采用以下几种方式 使用标准的跨语言序列化格式 可以选择使用一些标准的跨语言序列化格式,例如JSON、XML、Protocol Buffers(ProtoBuf)等。这些格式都是跨语言的,可以方便地在不同的编程语言之间进行数据交换。在Java中,可以…

紫光同创初使用

芯片PGC2KG-6LPG144 1、安装好软件接&#xff0c;加载license,有两个&#xff0c;与电脑MAC地址绑定的 2、正常使用后&#xff0c;新建个工程&#xff0c;配置管脚Tools→UCE 3、程序中有些信号被软件认为是时钟信号&#xff0c;会报错&#xff08;时钟输入I0约束在非专用时钟…