浅谈什么是信号量
在讲信号量之前,先了解两个概念同步和互斥:一条食品生产线上,假设A、B共同完成一个食品的包装任务,A负责将食品放到盒子里,B和C负责将盒子打包。必须得是A先装食品B再打包吧,要是B不按规则先打包,那A还装啥,所以就需要一种机制方法保证A先进行B再进行,“信号量”就是这种机制方法,AB之间的关系就是同步关系;再假设打包要用到刀子,而车间就有一把刀子,这时候B和C就构成了互斥关系。 在多任务操作系统环境下,多个进程会同时运行,并且一些进程间可能会存在一定的关联。多个进程可能为了完成同一个任务相互协作,这就形成了进程间的同步关系。而且在不同进程间,为了争夺有限的系统资源(硬件或软件资源)会进入竞争状态,这就是进程间的互斥关系。
进程间的互斥关系与同步关系存在的根源在于临界资源。临界资源是在同一时刻只允许有限个(通常只有一个)进程可以访问(读)或修改(写)的资源,通常包括硬件资源(处理器、内存、存储器及其它外围设备等)和软件资源(共享代码段、共享结构和变量等)。访问临界资源的代码叫做临界区,临界区本身也会称为临界资源。信号量是用来解决进程间的同步与互斥问题的一种进程间通信机制,包括一个称为信号量的变量和在该信号量下等待资源的进程等待队列,以及对信号量进行的两个原子操作(P/V操作)。其中,信号量对应于某一种资源,取一个非负的整形值。信号量值(常用sem_id表示)指的是当前可用的该资源的数量,若等于0则意味着目前没有可用的资源。
信号量:
信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。
信号量的工作原理
pv原子操作:
- p操作:如果有可用的资源(信号量值>0),则此操作所在的进程占用一个资源(此时信号量值减1,进入临界区代码);如果没有可用的资源(信号量值=0),则此操作所在的进程被阻塞到系统将资源分配给该进程(进入等待队列,一直等到资源轮到该进程)。
- v操作:如果在该信号量的等待队列中有进程在等待资源,则唤醒一个阻塞进程;如果没有进程等待它,则释放一个资源(即信号量值加1).
ftok()函数获取IPC关键字
共享内存,消息队列,信号量三种进程间通信方式都需要ket_t类型的关键字ID值,就像我们需要一个唯一的身份证号来区分一样,有时我们可以直接指定一个固定的整数值作为该ID的值,但通常情况下会通过调用ftok()函数获取该值。在一般的UNIX实现中,该函数是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。如指定文件的索引节点号为65538,换算成十六进程为0x10002,而你指定的ID值为38,换成16进程为0x26,则最后的key_t返回值为0x26010002.查询文件索引节点号的方法是: ls -i filename
ftok():
函数原型
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
函数说明:该函数根据pathname指定的文件或目录的索引节点号和proj_id计算并返回一个key_t类型的ID值,如果失败则返回-1.
参数说明:第一个参数pathname是一个系统中必须存在的文件或文件夹的路径,会使用该文件的索引节点;
第二个参数proj_id是用户指定的一个子序号,这个数字有的称之为proje ID。它是一个8bit的整数,取值范围是1~255.
需要注意的是:如果要确保key值不变,要么确保ftok()的文件不被删除,要么不用ftok()指定一个固定的key值
信号量的相关函数
1、semget()
函数原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget( key_t key, int nsems, int semflg);
函数说明:该函数用来创建一个信号集,或者获取已存在的信号集。成功返回信号集的标识符,失败返回-1。
参数说明:第一个参数:key是所创建或打开信号量集的键值(ftok成功执行的返回值),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget()函数并提供一个键值,再由系统生成一个相应的信号标识符(semget()函数的返回值),只有semget()函数才直接使用信号量键,所有其他的信号量函数使用由semget()函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作;
第二个参数:nsems指定需要的信号量数目,它的值几乎总是1;
第三个参数:semflg是一组标志,当想要信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作,设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已经存在,返回一个错误。
2、semctl()
函数原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semun, int cmd,....);
函数说明:该函数用来初始化信号集,或者删除信号集。成功返回一个正数,失败返回-1。
参数说明:第一个参数:semid是前面讲的semget()函数返回的信号量键值;
第二个参数:semun是操作信号在信号集中的编号,第一个信号是0;
第三个参数:cmd是在semid指定的信号量集合上执行此命令,可以是:
SETVAL:设置信号量集中的一个单独的信号量的值,此时需要传入第四个参数;
IPC_RMID:从系统中删除该信号量集合;
IPC_SEAT:对此集合取semid_ds结构,并存放在由arg.buf指向的结构中;
第四个参数:是可选的,如果使用该参数,则其类型是semun,它是多个特定命令行参数的联合(union),该联合不在任何系统头文件中定义,需要我们在自己的代码中定义:
union semun
{
int val;
struct semid_ds