站在巨人的肩膀上——Linux信号量操作
感谢那些让我度过此学海的无名勇士
信号量简介:
在对于临界区资源管理的过程中,多个程序同时访问一个共享资源经常容易引发一系列问题:如死锁,结果不唯一等等,
在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,如下所示:
struct sembuf{
unsigned short sem_num; //信号在信号集中的索引,0代表第一个信号,1代表第二个信号
short sem_op; //操作类型
short sem_flg; //操作标志
};
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联合体的结构如下:
union semun{ int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf;
};
对于该函数,只有当command取某些特定的值的时候,才会使用到第4个参数,第4个参数它通常是一个union semum结构,定义如下:
union semun{ int val; struct semid_ds *buf; unsigned short *arry;
};
对于第4个参数arg,
当执行SETVAL命令时用到这个成员,他用于指定要把信号量设置成什么值,涉及成员:val
在命令IPC_STAT/IPC_SET中使用,它代表内核中所使用内部信号量数据结构的一个复制 ,涉及成员:buf
在命令GETALL/SETALL命令中使用时,他代表指向整数值一个数组的指针,在设置或获取集合中所有信号量的值的过程中,将会用到该数组,涉及成员:array
剩下的还有一些用法都将在系统内核中的信号量代码使用,应用程序开发中使用很少,这里也就不介绍了。
实现样例:
这里列举一个别人的样例,主要是为了展示信号量控制进程的操作代码如下:
#include<iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/sem.h>
using namespace std;union semun
{int val;struct semid_ds *buf;unsigned short *arry;
};static int sem_id = 0; static int set_semvalue();
static void del_semvalue();
static int semaphore_p();
static int semaphore_v(); int main(int argc, char *argv[])
{ char message = 'S'; int i = 0; //创建信号量 sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT); if(argc > 1) { //程序第一次被调用,初始化信号量 if(!set_semvalue()) { fprintf(stderr, "Failed to initialize semaphore\n"); exit(EXIT_FAILURE); } //设置要输出到屏幕中的信息,即其参数的第一个字符 message = argv[1][0]; sleep(2); } cout<<argc<<message<<endl;for(i = 0; i < 10; ++i) { //进入临界区 if(!semaphore_p()) exit(EXIT_FAILURE); //向屏幕中输出数据 printf("进入%c", message); //清理缓冲区,然后休眠随机时间 fflush(stdout); sleep(rand() % 3); //离开临界区前再一次向屏幕输出数据 printf("离开%c\n", message); fflush(stdout); //离开临界区,休眠随机时间后继续循环 if(!semaphore_v()) exit(EXIT_FAILURE); sleep(rand() % 2); } sleep(10); printf("\n%d - finished\n", getpid()); if(argc > 1) { //如果程序是第一次被调用,则在退出前删除信号量 sleep(3); del_semvalue(); } exit(EXIT_SUCCESS);
} static int set_semvalue()
{ //用于初始化信号量,在使用信号量前必须这样做 union semun sem_union; sem_union.val = 1; if(semctl(sem_id, 0, SETVAL, sem_union) == -1) return 0; return 1;
} static void del_semvalue()
{ //删除信号量 union semun sem_union; if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1) fprintf(stderr, "Failed to delete semaphore\n"); elsefprintf(stdout, "已经删除信号量\n"); } static int semaphore_p()
{ //对信号量做减1操作,即等待P(sv) struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = -1;//P() sem_b.sem_flg = SEM_UNDO; if(semop(sem_id, &sem_b, 1) == -1) { fprintf(stderr, "semaphore_p failed\n"); return 0; } return 1;
} static int semaphore_v()
{ //这是一个释放操作,它使信号量变为可用,即发送信号V(sv) struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = 1;//V() sem_b.sem_flg = SEM_UNDO; if(semop(sem_id, &sem_b, 1) == -1) { fprintf(stderr, "semaphore_v failed\n"); return 0; } return 1;
}
编译及运行指令为:
g++ -o 可执行文件名 代码文件名 //编译
./可执行文件名 1 2 & ./可执行文件名 //运行
运行效果图如下:
站在巨人的肩膀上——Linux信号量操作相关推荐
- 【Visual C++】游戏开发笔记三十五 站在巨人的肩膀上:游戏引擎导论
看到在留言中很多朋友提到不太清楚DirectX与游戏引擎的区别的问题,在这里浅墨就专门把自己对游戏引擎的一些理解写成一篇文章,作为我们<Visual C++游戏开发>专栏的游戏引擎导论,也 ...
- 游戏开发笔记三十五 站在巨人的肩膀上:游戏引擎导论
本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接: http://blog.csdn.net/zhmxy555/article/details/8250057 作者:毛星云(浅墨 ...
- 站在巨人的肩膀上“思考”问题,重在思考而不是拿来主义
米老师按:觉得值得讨论的小文!我还要认真地想一想 主题:围绕职责链设计模式-计算收费有效时间博客展开讨论 参与人: 讨论时间: 讨论内容 这次讨论主要分为以下几点: 一.职责链模式应用于机房收费系统计 ...
- 站在巨人的肩膀上看Servlet——原来如此(更适合初学者认识Servlet)
前言: 有段时间没更新博客了,这段时间因为要准备考试,考完试后又忙了一阵别的事,一直没能静下心来写博客.大学考试真是越来越恶心了,各种心酸,那酸爽,够味.不过还好,马上就要大三了,听大三学长学姐说大三 ...
- “站在巨人的肩膀上”
站在巨人的肩膀上需要拥有巨人的实力 容易错误理解成:"盗取"巨人的成果并漠视了巨人
- 3小时快速入门数学建模竞赛-建模技巧2:追根溯源,站在巨人的肩膀上
我们要检索哪些信息?和怎样获得快速.准确的信息?这两个问题. 首先回答第一个问题,我们需要检索哪些信息?我们从我们需要求解的内容入手,请看思维导图和系统框图,我们需要求解35年寿命期内的发电总量.经济 ...
- 知识就是力量,让机器站在巨人的肩膀上
作者:微软亚洲研究院 闫峻博士 知识是什么?抽象来说,是人类对世界的认知,对宏观及微观世界客观规律的总结.具体来说,大到牛顿定律.量子力学中的科学发现,小到自家孩子的生日.太太做饭常用的酱油颜色,都是 ...
- 站在巨人的肩膀上看世界,MySQL初步使用语法
站在巨人的肩膀上看世界,MySQL初步使用 MySQL是一个常用的数据库,对于他的使用,我们需要尽量的了解它,但是掌握初步的语法,也可以勉强使用它,这都归功于它的研发者们.正是因为这些大佬,写好了软 ...
- 站在巨人的肩膀上:C#编写WindowsService
本文将为大家详细介绍如何用C#编写一个系统服务,当然肯定不是教大家从头写,而是在一个搭建好的框架上进行自我创作! 一.项目整体架构 本项目的开发是针对某设备的开启异常情况进行邮件提醒,针对监控到的未开 ...
- 双引擎架构 Vite 是如何站在巨人的肩膀上实现的?
在前面的章节中,我们学习了很多 Vite 使用和项目搭建的内容.接下来让我们将目光集中到 Vite 本身的架构上,一起聊聊它是如何站在巨人的肩膀上实现出来的.所谓的巨人,指的就是 Vite 底层所深度 ...
最新文章
- 卫星图像分割--Effective Use of Dilated Convolutions for Segmenting Small Object Instances
- Placement new的用法及用途
- 在 Kubernetes 上弹性深度学习训练利器 -- Elastic Training Operator
- 5月9日 python学习总结 外键、表之间的关联关系、修改表、清空表内容、复制表...
- 定时任务 - 定时关闭超期未支付订单
- noip复赛批量移动文件夹下的文件
- CCF 201403-2 窗口
- 我的面试题-软件测试基础
- 单个对象和多个对象在内存中的结构图
- python tornado websocket_Python:Tornado 第三章:WebSocket概念及应用:第一节:WebSocket概念...
- glibc的几个有用的处理二进制位的内置函数(转)
- linux下ntp服务配置
- 计算机基础阿春第十一讲,编程天才少年“顾神”,放弃清华保送,被麻省理工全额奖学金挖走...
- 电机驱动模块--L298N
- 【IOS网络通信】socket第三方库 AsyncSocket(GCDAsyncSocket)
- 【Unity】Button基础-按钮更换图片样式
- RSA组件之掩码生成函数(MGF)的实现(C源码)
- Observability:Wavefront
- whale 帷幄:crm客户管理营销系统全称是什么
- 关于html中文显示乱码
热门文章
- php微信开发视频教程_PHP微信开发视频资源推荐
- FPGA实现SPI 协议
- 中国联通沃支付echop支付插件
- C#list转JSON(Newtonsoft.Json.dll)(仅做记录)
- java简历模板来了!!
- fanuc系统md和mf区别_法兰克FANUC加工中心MF系统如何调取机床程序?
- 中科大 EPC课程 爬虫(最新,效果良好)
- windows server 2012 安装 VC14(VC2015) 安装失败解决方案
- 斗鱼 虎牙24 小时直播电影教程
- mysql同步 触发器_MySQL触发器运用于迁移和同步数据的实例教程