信号量主要用于进程间同步,避免并发访问共享资源。
信号量集合数据结构struct semid_ds:在此数据结构中定义了整个信号量集合的基本属性,如访问权限。
信号量数据结构struct sem:信号量集合使用指针指向一个由数组组成的信号量单元,在此信号量单元中存储了各信号量的值。
它们的定义:
信号量集合数据结构:
struct semid_ds{
struct ipc_perm sem_operm; //权限
__kernel_time_t sem_otime; //最近semop时间。
__kernel_time_t sem_ctime //最近修改时间
struct sem *sem_base; //队列第一个信号量
struct sem_queue *sem_pending //阻塞信号量
struct sem_queue **sem_pending_last; //最后一个阻塞的信号量
struct sem_undo *undo; //undo 队列
unisigned short sem_nsems;
}
每个信号量结构:
struct sem{
int semval; //信号量当前值
int sempid //最近一个操作的进程号PID。
}
信号量使用步骤:
1、创建信号量或获得在系统已存在的信号量,此时需要调用semget()函数。不同的进程通过使用同一个信号量键值来获得同一个信号量。
2、初始化信号量,此时使用semctl()函数的SETVAL操作,当使用二维信号量时,通常将信号量初始化为1。
3、进行信号量的PV操作,此时调用semop()函数。这一步是实现进程之间的同步和互斥的核心工作部分。
4、如果不需要信号量,则从系统中删除它,此时使用semctl()函数的IPC_RMID操作。此时需要注意,在程序中不应该出现对已经被删除的信号量的操作。
信号量/信号量集合操作:
1、semget()函数创建信号量集合:
原型:int semget(key_t key,int nsems,int semflg)
参数:
1)key:信号量的键值,多个进程可以通过访问同一个信号量,其中有个特殊的键值IPC_PRIVATE。它用于创建当前进程的私有数据信号量。
2)nsems:需要创建的信号量数目,通常取值为1。
3)semflg:同open()函数的权限位。其中使用IPC_CREAT标志创建新的信号量,信号量已经存在也不报错。同时使用IPC_EXCL创建一个新的唯一的信号量,若信号量已存在则返回出错。
2、semctl()函数控制信号量或信号量集合:
原型:int semctl(int semid,int semnum,int cmd,union semun arg)
参数:
1)semid:semget()函数返回的信号量集标识符。
2)semnum:信号量在信号集中的编号,操作信号量集时,此参数无意义。单个信号量中取为0。单个信号量也即只用一个信号量的信号量集。
3)cmd:对信号量集或信号量集中某些信号量的操作方式。
如果操作信号量集,cmd可取IPC_RMID、IPC_SET、IPC_STAT和IPC_INFO、GETALL、SETALL等。
含义(同msgctl的相关操作):
IPC_STAT:获取信号量集的semid_ds结构,并存放在由第四个参数arg的buf指向的semid_ds结构中。semid_ds是在系统中描述信号量集的数据结构。
IPC_RMID:从系统中删除信号量集。
如操作单个信号量,cmd可取IPC_SETVAL、IPC_GETVAL等。
含义:
IPC_SETVAL:将信号量值设置为arg的val值
IPC_GETVAL:返回信号量的当前值
4)arg:是union semun结构,该结构可能在某些系统中并不给出定义,此时必须由程序员自己定义。
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
}
关于semctl()函数,更详细的信息请man一下semctl,使用这个函数很简单的,只是内容有点多而已。
3、semop()函数:
多个进程中使用此函数修改同一个信号量集合中的各个信号量的值(在原信号量值的基础上进行加/减,即信号量的PV操作,注意PV操作都是原子操作),各进程根据获取的信号量值来决定自己的行为,实现进程间通信。
原型:int semop(int semid,struct sembuf *sops,size_t nsops)
参数:
nsops:操作数组sops(第二个参数)中的操作个数(元素数目),通常取值为1(一个操作)
sops:指向信号量的操作数组,一个操作包括以下成员:
struct sembuf
{
short sem_num;
short sem_op;
short sem_flg;
}
成员含义:
1)sem_num:要操作的信号量在信号量集合中的编号。
2)sem_op:整数,表示作用于信号量的操作方式,该值如果为正数则表示增加信号量的值(例如sem_op为1,表示在原来基础上加1,如果为3,表示在原来基础上加3),如果为负整数则表示减小信号量的值,如果为0则表示对信号量的当前值进行是否为0的测试。
3)sem_flg:操作标识,可选以下各值:
IPC_NOWAIT:在对信号量集合的操作不能执行的情况下,调用立即返回,对某信号量的操作,即使其中一个操作失败,也不会导致修改集合中的其他信号量。
SEM_UNDO:当进程退出后,该进程对sem进行的操作将被取消。
使用示例:(假设信号量集中的各信号量的值已经初始化)
struct sembuf sops[4];
sops[0].sem_num = 1;
sops[0].sem_op = -1;
sops[0].sem_flg = 0;
sops[1].sem_num = 2;
sops[1].sem_op = 3;
sops[1].sem_flg = 0;
semop(mysemid,sops,2);
调用了semop后,上面的代码进行了这样的操作:
第一个sop即sops[0],用于操作信号集中的第二个信号量(sem_num=1),对其信号量进行减1操作(sem_op=-1)。
第二个sop即sops[1],用于操作信号集中的第三个信号量(sem_num=2),对其信号量进行加3操作(sem_op=3)。
信号量实现生产消费模型:
这里用的不是二元信号量,不是用信号量来实现互斥访问其他资源。这里用了两个信号量,一个信号量的值表示产品数,一个信号量的值表示仓库空间。消费者和生产者都能修改这两个值,生产者每生产一个产品,产品数加1,仓库空间减1,消费者每消费一个产品,产品数减1,空间仓库空间加1。重点是信号量的PV操作。
sem_productor.c:
#include<stdlib.h>
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<errno.h>
#include<sys/sem.h>
int sem_id;
void init()
{
key_t key;
int ret;
unsigned short sem_array[2];
union semun
{
int val;
struct semid_d *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; //identify the space
arg.array = sem_array;
ret = semctl(sem_id,0,SETALL,arg); //初始化
if(ret == -1)
{
printf("SETAL 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,IPC_RMID,0);//完成操作后删除,在此程序是一个死循环,不会执行此操作
}
int main(int argc,char *argv[])
{
struct sembuf sops[2]; //操作两个信号使用的结构体
sops[0].sem_num = 0;
sops[0].sem_op = 1; //执行加1操作,每生产一个产品,对空间数加1
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); //get the space to instore the productor
printf("now producing....\n");
semop(sem_id,(struct sembuf*)&sops[0],1); //now tell the customr canbu cusume
printf("\nafter produce\n");
printf("spaces number is %d\n",semctl(sem_id,1,GETVAL));
printf("productor number is %d\n",semctl(sem_id,0,GETVAL));
sleep(4);
}
del();
}
sem_customer.c:
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<errno.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("the is customer\n");
while(1)
{
printf("\n\nbefore consume:");
printf("procductor is %d\n",semctl(sem_id,0,GETVAL));
printf("space si %d\n",semctl(sem_id,1,GETVAL));
semop(sem_id,(struct sembuf *)&sops[1],1); //now tell the productor canbu produce
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);
}
}