进程间通信方式---信号量
1、概念
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。
信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号量。
2、工作原理
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1。
举个例子,就是两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。
注:为了获得共享资源,进程需要执行下列操作:
(1)测试控制该资源的信号量。
(2)若此信号量的值为正,则进程可以使用该资源。进程将信号量值减1,表示它使用了一个资源单位。
(3)若此信号量的值为0,则进程进人休眠状态,直至信号量值大于0。进程被唤醒后,它返回至第(1)步。
当进程不再使用由一个信号量控制的共享资源时,该信号量值增1。如果有进程正在休眠等待此信号量,则唤醒它们。
为了正确地实现信号量,信号量值的测试及减1操作应当是原子操作。为此,信号量通常是在内核中实现的。
常用的信号量形式被称为二元信号量或双态信号量(binary semaphore)。它控制单个资源,初始值为1。但是一般而言,信号量的初值可以是任一正值,该值说明有多少个共享资源单位可供共享应用。
3、Linux信号量机制
3.1 semeget函数
它的作用创建一个新的信号量或者取得一个已有信号量。原型如下:
int semget(key_t key, int num_sems, int sem_flags);
第一个参数key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。
第二个参数num_sems指定需要的信号量数目,它的值几乎总是1。
第三个参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
semget函数成功返回一个相应信号标识符(非零),失败返回-
3.2 semop函数
它的作用是改变信号量的值 ,原型为:
int semop(int sem_id, sembuf *semopa, size_t num_sem_ops);
sem_id是由semget返回的信号量标识符,sembuf结构的定义如下:
struct sembuf{
short sem_num;//除非使用一组信号量,否则它为0
short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
//一个是+1,即V(发送信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};
3.3 semctl函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semun, int cmd, ...);
- 用来初始化信号集,或者删除信号集。
- semid:信号量集I P C 标识符。
- semun:操作信号在信号集中的编号,第一个信号的号是0.
- cmd:在semid指定的信号量集合上执行此命令。
- 第四个参数是可选的,如果使用该参数,则其类型是semun,它是多个特定命令参数的联合(union):
union semun
{
int val;
struct semid_ds * buf;
unsigned short * array;
struct seminfo * __buf;
};
- 第三个参数cmd常用命令:
- IPC_SEAT:对此集合取semid_ds 结构,并存放在由arg.buf指向的结构中。
- IPC_RMID:从系统中删除该信号量集合。
- SETVAL:设置信号量集中的一个单独的信号量的值,此时需要传入第四个参数。
- 返回值:成功返回一个正数,失败返回-1。
4、信号量相关背景知识
4.1 原子操作(atomic operation)
原子操作意为不可被中断的一个或一系列操作,也可以理解为就是一件事情要么做了,要么没做。而原子操作的实现,一般是依靠硬件来实现的。
4.2 同步与互斥
同步:在访问资源的时候,以某种特定顺序的方式去访问资源
互斥:一个资源每次只能被一个进程所访问。
同步与互斥是保证在高效率运行的同时,可以正确运行。大部分情况下同步是在互斥的基础上进行的。
4.3 临界资源
各个进程采取互斥的方式,实现共享的资源称作临界资源。属于临界资源的硬件有打印机、磁带机等。软件有消息缓冲队列、变量、数组、缓冲区等。诸进程间应采取互斥方式,实现对这种资源的共享。
4.4 临界区
每个进程中访问临界资源的那段代码称作临界区。显然若能保证诸进程互斥的进入自己的临界区,便可实现诸进程对临界资源的互斥访问。为此,每个进程在进入临界区之前,应先对欲访问的临界资源进行检查,看他是否正在被访问,如果此刻该临界资源未被访问,进程便可进入临界区对临界资源进行访问,并设置它正被访问标志;如果此刻该临界资源正被访问,则本进程不能进入临界区。
* 在操作系统中,有临界区的概念。临界区内放的一般是被一个以上的进程或线程共用的数据。
* 临界区的数据依次只能同时被一个进程访问,当一个进程使用临界区的数据时,其他需要使用临界区的数据的进程进入等待状态。
* 操作系统需合理的分配临界区以到达多进程的同步和互斥,如果协调不好,就容易使系统不安全,甚至出现死锁。