一、信号量IPC原理
信号量与已经介绍过的IPC机构(管道、FIFO以及消息列队)不同。它是一个计数器,用于多进程对共享数据对象的存取。为了获得共享资源,进程需要执行下列操作:
(1) 测试控制该资源的信号量。
(2) 若此信号量的值为正,则进程可以使用该资源。进程将信号量值减1,表示它使用了一个资源单位。(3) 若此信号量的值为0,则进程进入睡眠状态,直至信号量值大于0。若进程被唤醒后,它返回至(第( 1 )步)。
通常所说的创建一个信号量实际上是创建一个信号量集合,在这个信号量集合中,可能有多个信号量。整个信号量集合由以下部分组成:
(1)信号量集合数据结构:在此数据结构中定义了整个信号量集合的基本属性,如访问权限、指针、最近修改的时间和队列中信号量队列信息。
struct semid_ds {
struct ipc_perm sem_perm; /* see Section 14.6.2 */
struct_sem *sem_base; /* ptr to first semaphore in set */
ushort semnsems; /* #of semaphores in set */
time_t semotime; /* last-semop() time */
time_t semctime; /* last-change time */
};
(2)信号量:信号量集合使用指针指向一个由数组组成的信号量单元,在此信号量单元中存储了创建时申请的信号量,每个信号量的数据结构中的成员变量主要为该信号量的当前值。
struct sem {
ushort semval ; /* semaphore val,uealways >= 0 */
pid_t sempid; /* pid for last operarion */
ushort semncnt; /* # processes awaiting semval > currval */
ushort semzcnt ; /* # processes awaiting semval = 0 */
} ;
二、信号量管理操作
1、创建信号量集合
在使用信号量之前,首先需要创建一个信号量集合,该信号量集合中可以包含多个信号量。创建一个信号量集合的函数semget:
#include <sys/sem.h>
int semget(key_t key, int nsems, int flag) ;
//返回:若成功则返回信号量ID,若出错则为- 1
第一个参数key_t类型的key值,一般由ftok函数产生。
第二个参数_nsems为创建的信号量个数,各信号量以数组的方式存储。这个数组用于初始化数组对象。
第三个参数_semflg用来标识信号量集合的权限,如0770为文件访问权限类型。这些值可以与基本权限以或的方式一起使用。
2、控制信号量集合、信号量
在Linux操作系统中,可使用semctl函数对一个信号量集合以及信号量集合中的信号量进行操作。
#include <sys/sem.h>
int semctl(ints emid, int semnum, int cmd, union semunarg);
第二个参数为集合中信号量的编号。如果标识某个信号量,此值为该信号量的下标(从0到n-1);如果标识整个信号量集合,则设置为0.
第三个参数cmd参数指定下列十种命令中的一种,使其在semid指定的信号量集合上执行此命令。其中有五条命令是针对一个特定的信号量值的,它们用semnum指定该集合中的一个成员。semnum值在0和nsems-1之间(包括0和nsems-1)。
(1)IPC_STAT 对此集合取semidds结构,并存放在由arg.buf指向的结构中。
(2)IPC_SET 按由arg.buf指向的结构中的值设置与此集合相关结构中的下列三个字段值:semperm.uid, semperm.gid和semperm.mode。此命令只能由下列两种进程执行:一种是其有效用户I D等于semperm.cuid或semperm.uid的进程;另一种是具有超级用户特权的进程。
(3)IPC_RMID 从系统中删除该信号量集合。这种删除是立即的。仍在使用此信号量的其他进程在它们下次意图对此信号量进行操作时,将出错返回EIDRM。此命令只能由下列两种进程执行:一种是具有效用户ID等于semperm.cuid或semperm.uid的进程;另一种是具有超级用户特权的进程。
(4)GETVAL 返回成员semnum的semval值。
(5)SETVAL 设置成员semnum的semval值。该值由arg.val指定。
(6)GETPID 返回成员semnum的sempid值。
(7)GETNCNT 返回成员semnum的semncnt值。
(8)GETZCNT 返回成员semnum的semzcnt值。
(9)GETALL 取该集合中所有信号量的值,并将它们存放在由arg.array指向的数组中。
(10)SETALL 按arg.array指向的数组中的值设置该集合中所有信号量的值。
对于除了GETALL以外的所有GET命令,semctl函数都返回相应值。其他命令的返回值为0。
第四个参数是可选,是否使用取决于所请求的命令,如果使用该参数,则其类型是semum,它是多个命令特定参数的联合:
union semun {
int val; /* for SETVAL SETVAL的值 */
struct semid_ds *buf; /* for IPC_STAT and IPC_SET */
ushort *array ; /* for GETALL and SETALL */
} ;
(1)如果操作为SETVAL,则第四个参数为val,是相应信号量的值。(2)如果操作为IPC_STAT&IPC_SET,则第四个参数为struct semid_ds结构体变量。
(3)如果操作为GETALL&SETALL,则第四个参数为数组地址。
(4)如果操作为IPC_INFO,则第四个参数为struct seminfo结构体变量。
3、信号量操作
除了可以使用semctl系统调用访问信号量外,还可以通过semo系统调用来操作单个信号量:
#include <sys/sem.h>
int semop(int semid, struct sembufs emoparray[], size_t nops) ;
//返回:若成功则为0,若出错则为-1
第二个参数为struct sembuf结构的变量,其定义如下:
struct sembuf {
ushort semnum; /* member # in set (0,⋯ 1,, nsems-1 ,信号量下标*/
short semop; /* operation(negative, 0,or pasitive ,信号量操作*/
short semflg; /* IPC_NOWAIT, SEM_UNDO ,操作标识*/
} ;
此结构体有3个成员变量:
(1)semnum为操作的信号量编号。
(2)sem_op为作用于信号量的操作:该值如果为正整数标识增加信号量的值(如果是1,表示在原来的基础上加1,如果为3,表示在原来的基础上加3),如果为负整数表示减小信号量的值,如果为0表示信号量的当前值进行是否为0的测试。
(3)sem_flg为操作标识,可选以下各值:
IPC_NOWAIT:在对信号量集合的操作不能执行的情况下,调用立即返回,对某信号量操作,即使其中一个操作失败,也不会导致修改集合中的其他信号量。
SEM_UNDO:当进程退出后,该进程对sem进行的操作将被撤销。
三、使用信号量实现生产消费问题
生产消费问题是一个经典的数学问题,要求生产者--消费者在固定的仓库条件下,生产者每生产一个产品将占用一个仓库空间,生产者生产的产品库存不能越过仓库的存储量,消费者每消费一个产品将增加一个仓库空间,消费者在仓库产品为0时不能再消费。
本例中采用信号量来解决问题。为了便于理解,本例中使用了两个信号量,一个用来管理消费者(以下为sem_produce),一个用来管理生产者(以下为sem_custom),即sem_produce表示当前仓库可用的空间的数量,sem_custom用来表示当前仓库中产品的数量。
(1)对于生产者来说:其需要申请的资源为仓库中的剩余空间,因此,生产者在生产一个产品前需申请sem_produce信号量。当此信号量大于0,即有可用空间,将生产产品,并将sen_produce的值减去1(因为占用了一个空间);同时,当其生产了一个产品后,当前仓库的产品数量增加1,需要将sem_custom信号量自动加1.
(2)对于消费者来说:其需要申请的资源为仓库中的产品,因此,消费者在消费一个产品前将申请sem_custom信号量。当此信号量的值大于0时,即有可用产品,将消费一个产品,并将sem_custom信号量的值减去(因为消费了一个产品);同时,当消费了一个产品,当仓库的剩余空间增加1,需要将sem_produce信号量自动加1.
这两个信号量被生产者-消费者影响,从而保证生产的最大化,并保证生产的产品有可存储的仓库空间。
生产者:
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int sem_id;
void init()
{
key_t key;
int ret;
unsigned short sem_array[2];
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
} arg;
key = ftok(".",'s');
sem_id = semget(key, 2, IPC_CREAT|0644);
sem_array[0] = 0; //identify the productor
sem_array[1] = 100; //idnetify the space
arg.array = sem_array;
ret = semctl(sem_id, 0, SETALL, arg); //初始化
if (ret == -1)
printf("SETALL failed (%d)\n", errno);
printf("productor init is %d\n", semctl(sem_id, 0, GETVAL)); //打印初始化结果,产品数
printf("space init is %d\n\n",semctl(sem_id, 1, GETVAL)); //空间数
}
void del()
{
semctl(sem_id, 0, IPC_RMID); //完成操作后删除,在此程序是一个死循环,不会执行此操作
}
int main(int argc, char *argv[])
{
struct sembuf sops[2]; //操作两个信号量使用的结构体
sops[0].sem_num = 0;
sops[0].sem_op = 1; //执行加1操作,每生产一个产品,对产品数加1
sops[0].sem_flg = 0;
sops[1].sem_num = 1;
sops[1].sem_op = -1; //执行减1操作,每生产一个产品,对空间数加1
sops[1].sem_flg = 0;
init();
printf("this is productor\n");
while(1)
{
printf("\n\nbefore produce: \n");
printf("productor number is %d\n", semctl(sem_id, 0, GETVAL));
printf("space number is %d\n", semctl(sem_id, 1, GETVAL));
semop(sem_id, (struct sembuf *)&sops[1], 1);
printf("now producing ...\n");
semop(sem_id, (struct sembuf *)&sops[0], 1);
printf("now producing ...\n");
printf("\nafter produce\n");
printf("spcaes number is %d\n", semctl(sem_id, 1, GETVAL));
printf("productor number is %d\n", semctl(sem_id, 0, GETVAL));
sleep(4);
}
del();
}
消费者:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/sem.h>
int sem_id;
void init()
{
key_t key;
key = ftok(".", 's');
sem_id = semget(key, 2, IPC_CREAT|0644); //获取id,必须先执行生产者初始化
}
int main(int argc, char *argv[])
{
init();
struct sembuf sops[2];
sops[0].sem_num = 0;
sops[0].sem_op = -1; //执行减1操作,每消费一个产品,对产品数减去1
sops[0].sem_flg = 0;
sops[1].sem_num = 1;
sops[1].sem_op = 1; //执行加1操作,每消费一个产品,对空间数加1
sops[1].sem_flg = 0;
init();
printf("this is customer\n");
while(1)
{
printf("\n\nbefore consume: \n");
printf("productor is %d\n", semctl(sem_id, 0, GETVAL));
printf("space is %d\n", semctl(sem_id, 1, GETVAL));
semop(sem_id, (struct sembuf *)&sops[0], 1);
printf("now consuming.......\n");
semop(sem_id, (struct sembuf *)&sops[1], 1);
printf("\nafter consume\n");
printf("products number is %d\n", semctl(sem_id, 0, GETVAL));
printf("space number is %d\n", semctl(sem_id, 1, GETVAL));
sleep(3);
}
}