/*
*多读者多写者对两个缓冲区的操作实现
*原理:通常信号灯与共享内存一起使用可实现操作的同步
*读者按写者的顺序读数据
*设定写者先都向buf[0]中写,读者先从buf[0]中读
*计数信号灯,用来统计资源,其值代表资源数
*共有四类资源:可读缓冲区sem_r,可写缓冲区sem_w
*读的位置pos_r,写的位置pos_w(都用于指定读和写的位置buf[0]还是buf[1])
*注意pos_r和pos_w为临界资源,可用二值信号灯加锁
*所以要设置四个信号灯(编号从0-3)
*一个程序为一个进程,先运行的程序,要创建共享内存,创建信号灯及其初始化
* */
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#define SIZE 64
#define SEM_N 4 //信号灯个数
#define SEM_R 0 //读者信号灯编号 资源数为0
#define SEM_W 1 //写者.. 2
#define SEM_LR 2 //读者锁(由二值信号灯实现).. 1
#define SEM_LW 3 //写者锁.. 1
//定义共享内存结构体
//共享内存包括两个四字节的位置参数,还有两个64字节的缓冲区
struct _shm_{
int pos_r,pos_w;
char buf[2][SIZE];
};
//arg for semctl system calls
union semun{
int val; //value for SETVAL
struct semid_ds *buf; //Buffer for IPC_STAT,IPC_SET
unsigned short *array; //Array for GETALL,SETALL
struct seminfo *__buf; //Buffer for IPC_INFO
};
//定义初始化信号灯的参数
//semid为要修改信号量的ID,
void sem_init(int semid,int array[],int num)
{
int i;
union semun sem;
//i为信号灯编号,
for(i = 0;i < num;i++)
{
//设置编号为i的信号灯个数即有的资源个数
sem.val = array[i];
//指定了命令参数为SETVAL时只要设置sem.val
semctl(semid,i,SETVAL,sem);
}
return;
}
//pv操作函数实质是系统调用semop的操作
//对信号灯集semid中编号为num的信号灯进行op操作
void pv(int semid,int num,int op)
{
struct sembuf sem; //struct sembuf描述对某一个信号灯的操作
sem.sem_num = num;
sem.sem_op = op; //-1表分配资源p操作,1表释放由某一信号灯控制的资源v操作
sem.sem_flg = 0; //0表申请不成功就阻塞
semop(semid,&sem,1); //1表对一个信号灯进行操作
return;
}
int main(int argc,char *argv[])
{
key_t key;
int shmid,semid; //shared-memory id && semaphoreV id
int init_array[] = {0,2,1,1}; //四类资源分别有多少
struct _shm_ *shmaddr; //定义共享内存的结构体指针,用于映射
//不同的对象可使用相同的key
if((key = ftok("./",'s')) < 0){
perror("key make error.\n");
exit(-1);
}
//{dy}个进程进行共享内存的创建,信号灯集的创建和初始化操作
if((shmid = shmget(key,sizeof(struct _shm_),IPC_CREAT|IPC_EXCL|0666)) < 0){//不是{dy}个进程
if(EEXIST == errno){
shmid = shmget(key,sizeof(struct _shm_),0666);//打开共享内存
//NULL表将共享内存映射到此进程地址空间,0表以读写方式
shmaddr = (struct _shm_ *)shmat(shmid,NULL,0);
semid = semget(key,SEM_N,0666); //打开信号灯集
}
}
else{ //是{dy}个进程,共享内存创建之后创建信号灯集
shmaddr = (struct _shm_ *)shmat(shmid,NULL,0);
shmaddr->pos_r = 0; //指定初始读的位置
shmaddr->pos_w = 0; //指定初始写的位置
//创建信号灯集
if((semid = semget(key,SEM_N,IPC_CREAT|0666)) < 0){
perror("semget error.\n");
exit(-1);
}
//对信号灯集的初始化
sem_init(semid,init_array,SEM_N);
}
//读者
while(1)
{
pv(semid,SEM_R,-1); //申请不成功则阻塞
pv(semid,SEM_LR,-1); //通过二值信号灯SEM_LR对临界资源pos_r进行保护
printf("read from shm : %s",shmaddr->buf[shmaddr->pos_r]);
shmaddr->pos_r = (shmaddr->pos_r + 1) % 2;
pv(semid,SEM_LR,1); //释放临界资源pos_r以使其它读者进程可访问
pv(semid,SEM_W,1); //当一个进程读完以后,释放一个控制的可写缓冲区
}
return 0;
}