一、信号量的定义
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。
信号量是一个特殊的变量,程序对其访问都是原子操作(运行开始一直到结束不会切换到另一个线程),且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号量。这里主要讨论二进制信号量。
2、信号量的特性
抽象的来讲,信号量的特性如下:信号量是一个非负整数(车位数),所有通过它的线程/进程(车辆)都会将该整数减一(通过它当然是为了使用资源),当该整数值为零时,所有试图通过它的线程都将处于等待状态。在信号量上我们定义两种操作: Wait(等待) 和 Release(释放)。当一个线程调用Wait操作时,它要么得到资源然后将信号量减一,要么一直等下去(指放入阻塞队列),直到信号量大于等于一时。Release(释放)实际上是在信号量上执行加操作,对应于车辆离开停车场,该操作之所以叫做“释放”是因为释放了由信号量守护的资源。
3、信号量的操作
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
举个例子,就是两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。
4、函数解析
(1)int semget( key_t key, int nsems, int flag);
作用:创建或者获得一个*信号量集***ID
参数:
第一个参数key:函数中参数key用来变换成一个标识符,每一个IPC对象与一个key相对应
第二个参数nsems:参数nsems是一个大于等于0的值,用于指明该信号量集中可用资源数(在创建一个信号量时)。
第三个参数flag:IPC_EXCL;IPC_CREAT
返回值:当打开一个已存在的信号量集时该参数值为0。函数执行成功,则返回信号量集的标识符(一个大于等于0的整数),失败,则返回–1。
(2)int semop( int semid, struct sembuf semoparray[], size_t nops );
作用:操作一个信号量集
参数:
第一个参数semid:semget返回的信号量标识符
第二个参数:sembuf结构体的定义如下:
struct sembuf{
unsigned short sem_num;//信号集数组的下标,对应一个具体的信号
short sem_op;//对该信号进行的操作
short sem_flg;//说明函数semop的行为,通常为SEM_UNDO,使操作系统跟踪信号
}
第三个参数:npos标明了参数semoparray所指向数组中的元素个数
(3)int semctl(int sem_id, int sem_num, int command, …);
作用:用来直接控制信号量信息
参数:
如果有第四个参数,它通常是一个union semum结构,定义如下:
union semun{
int val;//把信号量初始化为val
struct semid_ds *buf;
unsigned short *arry;
};
第三个参数SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
第三个参数IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。