引入信号量之前,我们先要了解四个基本概念,什么是临界资源与临界区,什么是同步与互斥。
1 基本概念
1.1 临界资源与临界区的概念
1.临界资源:对于某些共享资源,一次仅允许一个进程访问,这个资源就称为临界资源。
2.临界区:访问临界资源的那些代码称为临界区。
1.2 同步与互斥
1.互斥:对于某种资源,如果有一个进程正在访问该资源,则其它的进程必须等待,当那个进程访问完成后其它进程才能访问。
2.同步:某些进程的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。
同步最基本的场景就是:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如 A 任务的运行依赖于 B 任务产生的数据。
2 信号量
1.本质:信号量本质上是一个表示临界资源数目的一个计数器。
2.底层:一个计数器加上一个等待队列。
struct semaphore
{
int value; //计数器
pointer_PCB queue; //等待队列
}
3.信号量里有一个特殊的信号量:二元信号量
(1)二元信号量的值非0即1。
(2)二元信号量的本质就是一把互斥锁。
4.在进程间通信里,信号量本身并不具备传输数据的能力,而是通过控制其它的通信资源来实现进程间通信的同步与互斥。
2.1 信号量值的含义
若用S表示信号量
(1)S > 0 ,表示剩余临界资源的数目
(2)S = 0,表示没有可用资源,也没有等待进程
(3)S < 0,|S|表示等待进程的数目
注意:这里的S表示的信号量是通用的信号量,但是对于system V版本的信号量来说,信号量没有负值,当信号量的值等于0的时候,需要申请该资源的进程挂起等待。
2.2 信号量常见的操作
1.信号量常见的操作有P操作和V操作
(1)P操作
对该信号量的值减1,以表明对应申请得到资源。
(2)V操作
对该信号量的值加1,以表明该用户归还申请得到的资源。
2.因为信号量是用来实现同步与互斥的,所以对信号量的操作也必须是原子的。
3.P、V操作具有原子性,因为信号量能被不同进程看到,信号量本身也是一种临界资源。
2.3 信号量集相关的API
因为对于system V版本的信号量来说,创建信号量是以信号量集的方式创建的
1.创建信号量集
(1)通过semget函数
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
(2)参数解析:
key:通过ftok函数产生一个唯一标识信号量集的标识符
nsems:由于该函数是创建一个信号量集,一个集合必须知道元素的个数,这里的nsems指的就是创建的信号量集中信号量的个数。
semflg:与消息队列和共享内存里的用法一致,当为IPC_CREAT表示不存在就创建存在就打开,当同时使用IPC_CREAT和IPC_EXCL表示不存在就创建,存在就失败。
(3)使用示例:
//创建包含num个信号量的信号量集,返回值为信号量集唯一标识semid
int SemCreate(int num)
{
key_t key = ftok(PATHNAME,PROJ_ID);
if(key < 0)
{
perror("ftok");
return -1;
}
int semid = semget(key,num,fIPC_CREAT | IPC_EXCL | 0666);
if(semid < 0)
{
perror("semget");
return -1;
}
return semid;
}
2.删除信号量集
(1)通过semctl函数
int semctl(int semid, int semnum, int cmd, ...);
(2)参数解析:
semid:信号量集的唯一标识,表示对哪个信号量集执行相关操作
semnum:表示信号量集中某个信号量的下标(序号)
cmd:执行的相关命令,当cmd为IPC_RMID时,第二个参数无效(因为对整个信号量集都删除,对哪个信号量标号也就不关心了),表明删除信号量集
(3)使用示例:
int DestroySem(int semid)
{
//因为删除信号量集,所以第二隔参数任意
int ret = semctl(semid,0,IPC_RMID);
}
3.获得信号量集
(1)通过semget函数
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);