UNIX/Linux进程间通信IPC系列(六)信号量

信号量


信号量(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


 

 

 

你可能感兴趣的:(UNIX/Linux进程间通信IPC系列(六)信号量)