信号量
信号量(semaphore)是一个计数器,用于多进程对共享数据的访问。
为了获得共享资源,进程需要执行下列操作:
⑴ 测试控制资源的信号量。
⑵ 若此信号量的值为正,则进程可以使用该资源。进程将信号量值减1,表示它使用了一个资源单位。
⑶ 若此信号量的值为0,则进程进入休眠状态,直至信号量值大于0。进程被唤醒后,它返回执行第1步。
当进程不再使用此共享资源时,该信号量值增1。(返还一个资源单位) 如果有进程正在休眠等待此信号量,则唤醒它们。
为了正确地实现信号量,信号量值的测试及减1操作应当是原子操作。为此,信号量通常是在内核中实现的。
信号量、互斥量、条件变量之间的差异:
1、互斥锁必须总是由给它上锁的线程解锁,信号量的释放(即挂出)和等待操作不必由同一线程(进程)执行。
2、互斥锁要么被锁住,要么被解开
3、信号量有一个与之关联的状态(它的计数值),信号量释放的操作总是被记住。但当向一个条件变量发送信号时,如果没有线程等待在该条件变量上,那么该信号将丢失。
【信号量的意图在于进程间同步,互斥锁和条件变量的意图则在于线程间同步。但是信号量也可用于线程间,互斥锁和条件变量也可用于进程间。我们应该使用适合具体应用的那组原语。】
对于系统中的每个信号量集,内核维护一个如下的信息结构:
struct semid_ds {
struct ipc_permsem_perm ;
structsem* sem_base ; //信号数组指针
ushort sem_nsem ; //此集中信号个数
time_t sem_otime ; //最后一次semop时间
time_t sem_ctime ; //最后一次创建时间
} ;
某个给定信号量的结构体
struct sem {
ushort_t semval ; //信号量的值
short sempid ; //最后一个调用semop的进程ID
ushort semncnt ; //等待该信号量值大于当前值的进程数(一有进程释放资源 就被唤醒)
ushort semzcnt ; //等待该信号量值等于0的进程数
} ;
semget函数
semget函数创建一个信号量集或访问一个已存在的信号量集。
#include <sys/sem.h>
int semget (key_t key, int nsem, int oflag) ;
返回值是一个称为信号量标识符的整数,semop和semctl函数将使用它。
参数nsem指定集合中的信号量数。(若用于访问一个已存在的集合,那就可以把该参数指定为0)
参数oflag可以是SEM_R(read)和SEM_A(alter)常值的组合。(打开时用到),也可以是IPC_CREAT或IPC_EXCL ;
semop函数
使用semget打开一个信号量集后,对其中一个或多个信号量的操作就使用semop(op--operate)函数来执行。
使用semget打开一个信号量集后,对其中一个或多个信号量的操作就使用semop函数来执行。
#include <sys/sem.h>
int semop (int semid, struct sembuf * opsptr, size_t nops) ;
参数opsptr是一个指针,它指向一个信号量操作数组,信号量操作由sembuf结构表示:
structsembuf {
unsigned short sem_num ; //信号量在信号量集中的index(对哪个信号量操作)
short sem_op ; //操作的类型(P操作 还是 V操作)
short sem_flg ; //是否等待(当信号量的值不够消耗时 是否等待其他进进程释放资源)
} ;
参数nops规定opsptr数组中元素个数。
sem_op值:
⑴若sem_op为正,这对应于进程释放占用的资源数。sem_op值加到信号量的值上。(V操作)
⑵若sem_op为负,这表示要获取该信号量控制的资源数。信号量值减去sem_op的绝对值。(P操作)
⑶若sem_op为0,这表示调用进程希望等待到该信号量值变成0
如果信号量值小于sem_op的绝对值(资源不能满足要求),则:
⑴若指定了IPC_NOWAIT,则semop()出错返回EAGAIN。
⑵若未指定IPC_NOWAIT,则信号量的semncnt值加1(因为调用进程将进入休眠状态),然后调用进程被挂起直至:①此信号量变成大于或等于sem_op的绝对值;②从系统中删除了此信号量,返回EIDRM;③进程捕捉到一个信号,并从信号处理程序返回,返回EINTR。
(与消息队列的阻塞处理方式 很相似)
semctl函数
semctl函数对一个信号量执行各种控制操作。
#include <sys/sem.h>
int semctl (int semid, int semnum, int cmd, /*可选参数*/ ) ;
第四个参数是可选的,取决于第三个参数cmd。
参数semnum指定信号集中的哪个信号(操作对象)
参数cmd指定以下10种命令中的一种,在semid指定的信号量集合上执行此命令。
IPC_RMID :从系统中删除该信号量集合
GETVAL :返回成员semnum信号量的semval值
SETVAL :设置成员semnum信号量的semval值
信号量值的初始化
semget并不初始化各个信号量的值,这个初始化必须通过以SETVAL命令(设置集合中的一个值)或SETALL命令(设置集合中的所有值) 调用semctl来完成。
SystemV信号量的设计中,创建一个信号量集并将它初始化需两次函数调用是一个致命的缺陷。一个不完备的解决方案是:在调用semget时指定IPC_CREAT | IPC_EXCL标志,这样只有一个进程(首先调用semget的那个进程)创建所需信号量,该进程随后初始化该信号量。
【示例】用UNIX信号量实现睡眠的理发师问题
用
1)控制变量waiting用来记录等候理发的顾客数,初值均为0;
2)信号量customers用来记录等候理发的顾客数,并用作阻塞理发师进程,初值为0;
3)信号量barbers用来记录正在等候顾客的理发师数,并用作阻塞顾客进程,初值为0;
4)信号量 mutex用于互斥,初值为1
int waiting=0 ; //等候理发的顾客数 int chairs=n; //为顾客准备的椅子数 semaphore customers=0, barbers=0,mutex=1; cobegin第一章 THE P,V THEOREM 10 barber() begin while(TRUE); //理完一人,还有顾客吗? P(cutomers); //若无顾客,理发师睡眠 P(mutex); //进程互斥 waiting := waiting – 1; //等候顾客数少一个 V(barbers); //理发师去为一个顾客理发 V(mutex); //开放临界区 cut-hair( ); //正在理发 end customer() begin P(mutex); //进程互斥 if (waiting) begin waiting := waiting+1; // 等候顾客数加1 V(customers); //必要的话唤醒理发师 V(mutex); //开放临界区 P(barbers); //无理发师, 顾客坐着养神 get-haircut( ); //一个顾客坐下等理/ end else V(mutex); //人满了,走吧! end