一、信号量简介
信号量是一种进程间同步机制。信号量的常见用途就是同步对一块共享内存的访问,以防止一个进程在访问内存的时候另一个内存在更新这块内存的数据。
常见的同步就是互斥锁了,互斥锁常用于线程(也可用于进程),互斥锁有两种状态——上锁和未上锁状态。当一个线程访问共享资源时,线程对共享资源上锁,防止别的线程访问,当共享资源访问结束再对共享资源解锁,以便另一个线程对该共享资源上锁并访问。由此可见,互斥锁解决资源冲突的方式从共享资源的访问权限来控制的。线程使用互斥锁是比较简单的,但是对于两个进程间,如果给一个公共资源上锁,我觉得是有一些难度的。
信号量则是从进程是否可以执行的角度去解决资源冲突的。一个信号量是由内核维护的整数,其值大于等于0。
信号量的三种状态:①信号量当前值的基础上加 1 ;② 信号当前值的基础上减 1 操作;③信号量此时值为 0 ,某一进程想执行减 1 操作,于是该进程被阻塞。
假设,我对信号量的操作,只有加 1 和减 1 ,于是有如下现象:进程A创建信号量,将信号量初始化为0,此时进程B想对信号量执行减1操作,于是进程B被阻塞,若进程A执行到某处给信号量执行了加1操作,然后进程A主动放弃CPU,然后A再对信号量执行减1操作,此时由于B给信号量已经执行了减 1 操作,于是进程A 被阻塞,进程B开始执行。由此可见,通过内核提供的一个整数,进程之间实现了同步,以下附上两个进程之间的执行流程图(重在理解):
注意:信号量是内核提供的一种机制,当减小一个信号量的值时,内核会将所有试图将信号量的值降低到 0 之下的进程阻塞。而进程间实现共享时,是进程间约定好的,然后通过信号量实现。
二、API介绍
1、创建或打开一个信号量集
#include <sys/types.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflag);
参数:
- key:可以传入 IPC_PRIVATE 或 ftok() 产生唯一key,但是一般做进程间同步需要指定一个任意正整数,作为信号量的唯一标识。
- nsems:指定信号量集合中信号量的数量。
- semflg:新信号量集上的权限(同创建文件时权限一样,传八进制)还有可选参数 IPC_CREATE (如果key相关的信号量集合不存在就创建)、IPC_EXCL(指定key关联的信号集合存在则返回 EEXIST 错误)
- 该函数调用成功后返回信号量集合,或既有信号量集合的标识符。
2、信号量控制
#include <sys/types.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, union semun arg);
参数:
- semid:信号量集的标识符
- semnum:信号量集合中的具体信号(信号量集合看做一个数组,该参数表示信号量集合数组中第几个元素)
- cmd:制定需要执行的操作。
cmd参数设置:- IPC_RMID :立即删除信号量集及其关联的semid_ds数据结构。所有因在semop()调用中等待这个信号量而阻塞的进程都会被立即唤醒。semop()会报告EIDRM错误。
- IPC_STAT:在arg.buf指向的缓冲器中放置一份与这个信号量集相关联的semid_ds数据结构副本。
- IPC_SET:使用arg.buf指向的缓冲器中的值来更新与这个信号集相关联的semid_ds数据结构中选中的字段。
- GETVAL:semctl()返回由semid指定的信号集中第semnum个信号量的值
- SETVAL:将由semid指定的信号集中第semnum个信号量的值初始化为arg.val
- GETALL:获取由semid指向的信号量集中所有信号量的值并将它们放在arg.array指向的数组中。
- SETALL:使用arg.array指向的数组中的元素初始化semid指向的集合中的所有的喜好量。
- GETPID:返回上一个在该信号量上执行semop()的进程ID,这个值被称为semid值,若没有进程在该信号量上执行过semop(),那么就返回0
- GETNCNT:返回当前等待该信号量的值增长的进程数,这个值被称为semncnt值。
- GETZCNT:返回当前等待该信号量的值变为0的进程数,这个值称为semzcnt值。
- union semnum 枚举
- 有些系统可能没有定义该枚举类型(但是还需要传入该枚举值,系统是否定义,用宏‘_SEM_SEMUN_UNDEFINED’值为1就是没定义)那就需要程序员自己定义: