生产者消费者同步问题:生产者进程产生消息放在某个共享资源区,共享资源区的消息不为空时消费者进程从该共享资源区读取消息,为了避免脏读生产者和消费者不能同时访问共享资源,这就涉及到生产者和消费者进程之间的同步。利用信号量解决该问题:
定义两个信号量(不能是一个,否则生产者或者消费者可能会无限的访问共享资源,形成死循环)S1、S2,初始值为1,0。S1=1表示生产者进程可写共享资源,为0则不可;S2=1表示消费者进程可读共享资源,为0则不可。伪代码描述如下:
生产者进程:
whlie(true)
{
产生一个消息
P(S1)
消息写到共享资源
V(S2)
}
消费者进程:
whlie(true)
{
P(S2)
从共享资源读取消息
V(S1)
处理消息
}
1.信号量
信号量可用于多进程的同步,为多个进程提供对共享资源的访问。
(1)获取信号量
int semget(key_t key, int semnum, int flag);
semnum为信号数量,如果是新创建信号量,则大于0;如果是打开已有的信号量,则semnum可为0;
(2)对信号量进行操作
int semctl(int semid, int semnm, int cmd [, union semun arg]);
参数semnm是信号量集合中某个信号量的下标,从0开始,在0与nsems-1之间;
参数中联合体结构如下(有些系统需要自定义):
union semun
{
int val; /*for SETVAL*/
struct semid_ds *buf; /*for IPC_STAT IPC_SET*/
unsigned short *array; /*for SETALL*/
};
这里union semun arg是可选参数,例如cmd=IPC_RMID就不需要该参数,cmd为SETVAL或者SETALL的时候需要该参数。
以cmd=SETALL时,此时信号量集合的值由arg.array数组指定。
(3)自动执行信号量集合上的操作数组 semop是原子操作
int semctl(int semid, int semnm, int cmd [, union semun arg]);
其中结构体struct sembuf在(sys/sem.h)中定义如下:
struct sembuf
{
unsigned short sem_num; /*0~nsems-1*/
short sem_op; /*negative, 0, pasitive*/
short sem_flg; /*0 IPC_UNDO IPC_NOWAIT*/
};
输入域参数semoparray指向一个信号量操作结构体数组,参数nops是操作的信号量的个数。
这里可以看用semop可以包装P、V操作,以便实现进程之间的同步,这里回顾一下PV操作。
定义一个信号量sem,则有:
P操作:令sem=sem-1,若sem>=0,则进程继续执行,否则挂起等待,加入等待队列。
V操作:令sem=sem+1,若sem<0,唤醒等待队列中的一个进程。(注意:sem<0才需要唤醒,因为此时有阻塞; s>=0说明没阻塞也不需要唤醒)。
2.共享内存
共享内存是IPC的一种,共享内存为多个进程提供一个可以共同访问的存储区,共享内存的数据,不需要在各个进程间复制,因此是速度最快的IPC。多个进程访问共享内存时,为了避免脏读,需要实现进程的同步,通常用信号量实现共享内存的同步访问。
共享内存的接口函数如下:
(1)创建或者获得共享内存
int shmget(key_t key, size_t size, int flag);
(2)共享内存操作
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
(3)链接共享内存
void *shmat(int shmid, const void *arr, int flag); //成功返回共享内存的指针,否则返回NULL
addr通常使用NULL,表示链接到内核选择的第一个可用地址上;
addr非NULL,且没有指定flag为SHM_RND则链接到arr指定的地址;
addr非NULL,且指定了flag为SHM_RND则连接到arr-(addr mod SHMLBA)表示的地址,就是向下取近1个SHMLBA的倍数数
flag如果为RDONLY,则共享内存只读
(4)结束共享内存段访问(并未删除)
int shmdt(const void *shmArr); //分离链接的共享内存,成功返回0,失败-1
shmArr是shmat返回的值。
3.信号量与共享内存解决生产者消费者问题
生产者进程负责从键盘读取消息写入到共享内存,消费者进程从共享内存读数据输出到屏幕,生产者进程写共享内存结束前,消费者进程不能读;消费者进程读共享内存时,生产者进程不能写共享内存。
(1)shm_sem.h
# ifndef _SHM_SEM_H
# define _SHM_SEM_H
# define SHM_KEY 38111
# define SEM_KEY 38222
# define SHM_SIZE 2048
# define SEM_NUM 2
int sem_p(int semid, int semnum);
int sem_v(int semid, int semnum);
int get_semval(int semid, int semnum);
union semun
{
int val; /*for SETVAL*/
struct semid_ds *buf; /*for IPC_STAT ans IPC_SET*/
unsigned short *array; /*for GETALL ans SET_ALL*/
};
# endif
(2)pubfun.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
/*sem P*/
int sem_p(int semid, int semnum)
{
/*int semop(int semid, struct sembuf semoparray[], size_t cnts); */
struct sembuf semOpr ;
semOpr.sem_num = semnum;
semOpr.sem_op = -1;
semOpr.sem_flg = SEM_UNDO;
return semop(semid, &semOpr, 1);
}
/*sem V*/
int sem_v(int semid, int semnum)
{
/*int semop(int semid, struct sembuf semoparray[], size_t cnts); */
struct sembuf semOpr ;
semOpr.sem_num = semnum;
semOpr.sem_op = 1;
semOpr.sem_flg = SEM_UNDO;
return semop(semid, &semOpr, 1);
}
/*get semval*/
int get_semval(int semid, int semnum)
{
return semctl(semid, semnum, GETVAL);
}
(3)server.c 生产者进程
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include "shm_sem.h"
# if 0
struct sembuf
{
unsigned short sem_num; /*0-nsems-1*/
short sem_op; /*negative, 0, pasitive*/
short sem_flg; /*0 IPC_UNDO IPC_NOWAIT*/
}
#endif
int main(void)
{
int ret;
int shmid;
int semid;
void *ptrShm = NULL;
struct shmid_ds shm_ds;
struct semid_ds sem_ds;
char buf[1024] = {0};
union semun arg;
unsigned short semvalArr[SEM_NUM] = {1, 0};
memset(&shm_ds, 0x00, sizeof(shm_ds));
memset(&sem_ds, 0x00, sizeof(sem_ds));
/*semget*/
semid = semget((key_t)SEM_KEY, SEM_NUM, 0660|IPC_CREAT|IPC_EXCL); /*int semget(key_t key, int nsems, int flag);*/
if(semid == -1)
{
if(errno == EEXIST) /*no need to init sem*/
{
printf("semget() warning: %s\n", strerror(errno));
semid = semget((key_t)SEM_KEY, 0, 0660|IPC_CREAT); /*nsems can be 0 when semid already exists*/
if(semid == -1)
{
printf("semget() error: %s\n", strerror(errno));
return -1;
}
printf("semget() success. semid=[%d]\n", semid);
}
else
{
printf("semget() error: %s\n", strerror(errno));
return -1;
}
}
else /*need to init sem*/
{
printf("semget() success. semid=[%d]\n", semid);
/*semctl(): init sem, set semvalArr[0]=1 semvalArr[1]=0*/
arg.array = semvalArr;
ret = semctl(semid, 0, SETALL, arg); /*int semctl(int semid, int semnm, int cmd [, union semun arg]); semnum between 0~nsems-1*/
if(ret == -1)
{
printf("semctl() SETALL error: %s\n", strerror(errno));
printf("init sem error.\n");
/*semctl IPC_RMID*/
ret = semctl(semid, 0, IPC_RMID);
if(ret == -1)
{
printf("semctl() error: %s\n", strerror(errno));
}
printf("semctl() success. Sem is deleted.\n");
return -1;
}
printf("semget() success.\n");
}
printf("init sem success. semval[0]=[%d] semval[1]=[%d].\n", get_semval(semid, 0), get_semval(semid, 1));
/*shmget*/
shmid = shmget((key_t)SHM_KEY, SHM_SIZE, 0660|IPC_CREAT|IPC_EXCL); /*int shmget(key_t key, size_t size, int flag);*/
if(shmid == -1)
{
if(errno == EEXIST)
{
printf("shmget() warning: %s\n", strerror(errno));
shmid = shmget((key_t)SHM_KEY, SHM_SIZE, 0660|IPC_CREAT);
if(shmid == -1)
{
printf("shmget() error: %s\n", strerror(errno));
return -1;
}
}
else
{
printf("shmget() error: %s\n", strerror(errno));
return -1;
}
}
printf("shmget() success. shmid=[%d]\n", shmid);
/*shmat*/
ptrShm = shmat(shmid, NULL, !SHM_RDONLY); /*void *shmat(int shmid, const void *shmarr, int flag);*/
if(ptrShm == NULL)
{
printf("shmat() error: %s\n", strerror(errno));
goto FAILED;
}
printf("shmat() success.\n");
/*Begin to write*/
printf("Begin to write shared memory.\n");
while(1)
{
/*input*/
memset(&buf, 0x00, sizeof(buf));
printf("server: ");
if(fgets(buf, sizeof(buf)-1, stdin) < 0)
{
printf("fgets() error\n");
fflush(stdin);
continue;
}
fflush(stdin);
/*P*/
ret = sem_p(semid, 0);
if(ret == -1)
{
printf("sem_p() error\n");
goto FAILED;
}
/*write into shm*/
memset(ptrShm, 0x00, SHM_SIZE);
memcpy((char*)ptrShm, buf, strlen(buf));
/*V*/
ret = sem_v(semid, 1);
if(ret == -1)
{
printf("sem_v() error\n");
goto FAILED;
}
if(memcmp(buf, "exit", 4) == 0)
{
break;
}
}
/*shmdt*/
ret = shmdt(ptrShm);
if(ret == -1)
{
printf("shmdt() error: %s\n", strerror(errno));
}
printf("shmdt() success.\n");
return 0;
FAILED:
/*shmctl IPC_RMID*/
ret = shmctl(shmid, IPC_RMID, &shm_ds);
if(ret == -1)
{
printf("shmctl() error: %s\n", strerror(errno));
}
printf("shmctl() success. Shm is deleted.\n");
/*semctl IPC_RMID*/
ret = semctl(semid, 0, IPC_RMID);
if(ret == -1)
{
printf("semctl() error: %s\n", strerror(errno));
}
printf("semctl() success. Sem is deleted.\n");
return -1;
}
(4)client.c 消费者进程
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include "shm_sem.h"
int main(void)
{
int ret;
int shmid;
int semid;
void *ptrShm = NULL;
struct shmid_ds shm_ds;
struct semid_ds sem_ds;
char buf[1024] = {0};
union semun arg;
unsigned short semvalArr[SEM_NUM];
memset(&shm_ds, 0x00, sizeof(shm_ds));
memset(&sem_ds, 0x00, sizeof(sem_ds));
/*semget*/
semid = semget((key_t)SEM_KEY, SEM_NUM, 0660|IPC_CREAT|IPC_EXCL); /*int semget(key_t key, int nsems, int flag);*/
if(semid == -1)
{
if(errno == EEXIST) /*no need to init sem*/
{
printf("semget() warning: %s\n", strerror(errno));
semid = semget((key_t)SEM_KEY, 0, 0660|IPC_CREAT); /*nsems can be 0 when semid already exists*/
if(semid == -1)
{
printf("semget() error: %s\n", strerror(errno));
return -1;
}
printf("semget() success. semid=[%d]\n", semid);
}
else
{
printf("semget() error: %s\n", strerror(errno));
return -1;
}
}
else /*need to init sem*/
{
printf("semget() success. semid=[%d]\n", semid);
/*semctl(): init sem, set semvalArr[0]=1 semvalArr[1]=0*/
semvalArr[0]= (unsigned short)1 ;
semvalArr[1]= (unsigned short)0 ;
arg.array = semvalArr;
ret = semctl(semid, 0, SETALL, arg); /*int semctl(int semid, int semnm, int cmd [, union semun arg]); semnum between 0~nsems-1*/
if(ret == -1)
{
printf("semctl() SETALL error: %s\n", strerror(errno));
printf("init sem error.\n");
/*semctl IPC_RMID*/
ret = semctl(semid, 0, IPC_RMID);
if(ret == -1)
{
printf("semctl() error: %s\n", strerror(errno));
}
printf("semctl() success. Sem is deleted.\n");
return -1;
}
printf("semget() success.\n");
}
printf("semval[0]=[%d] semval[1]=[%d].\n", get_semval(semid, 0), get_semval(semid, 1));
/*shmget*/
shmid = shmget((key_t)SHM_KEY, SHM_SIZE, 0660|IPC_CREAT|IPC_EXCL); /*int shmget(key_t key, size_t size, int flag);*/
if(shmid == -1)
{
if(errno == EEXIST)
{
printf("shmget() warning: %s\n", strerror(errno));
shmid = shmget((key_t)SHM_KEY, SHM_SIZE, 0660|IPC_CREAT);
if(shmid == -1)
{
printf("shmget() error: %s\n", strerror(errno));
return -1;
}
}
else
{
printf("shmget() error: %s\n", strerror(errno));
return -1;
}
}
printf("shmget() success. shmid=[%d]\n", shmid);
/*shmat*/
ptrShm = shmat(shmid, NULL, SHM_RDONLY); /*void *shmat(int shmid, const void *shmarr, int flag);*/
if(ptrShm == NULL)
{
printf("shmat() error: %s\n", strerror(errno));
goto FAILED;
}
printf("shmat() success.\n");
printf("Begin to read shared memory.\n");
//防止消费者在生产者之前启动,导致死锁
while(1)
{
/*test semvalArr[0]*/
ret = get_semval(semid, 1);
if(ret < 0) /**/
{
printf("get_semval() error.\n");
goto FAILED;
}
else if(ret == 0) /*It suggests that the server hasn't start yet*/
continue;
else
break;
}
/*Begin to read*/
while(1)
{
/*P*/
ret = sem_p(semid, 1);
if(ret == -1)
{
printf("sem_p() error\n");
goto FAILED;
}
/*read shm*/
memset(&buf, 0x00, sizeof(buf));
memcpy(buf, (char*)ptrShm, sizeof(buf));
/*V*/
ret = sem_v(semid, 0);
if(ret == -1)
{
printf("sem_v() error\n");
goto FAILED;
}
printf("client: %s", buf);
if(buf[0]=='\0')
printf("\n");
if(memcmp(buf, "exit", 4) == 0)
{
break;
}
}
/*shmdt*/
ret = shmdt(ptrShm);
if(ret == -1)
{
printf("shmdt() error: %s\n", strerror(errno));
}
printf("shmdt() success.\n");
/*shmctl IPC_RMID*/
ret = shmctl(shmid, IPC_RMID, &shm_ds);
if(ret == -1)
{
printf("shmctl() error: %s\n", strerror(errno));
}
printf("shmctl() success. Shm is deleted.\n");
/*semctl IPC_RMID*/
ret = semctl(semid, 0, IPC_RMID);
if(ret == -1)
{
printf("semctl() error: %s\n", strerror(errno));
}
printf("semctl() success. Sem is deleted.\n");
return 0;
FAILED:
/*shmctl IPC_RMID*/
ret = shmctl(shmid, IPC_RMID, &shm_ds);
if(ret == -1)
{
printf("shmctl() error: %s\n", strerror(errno));
}
printf("shmctl() success. Shm is deleted.\n");
/*semctl IPC_RMID*/
ret = semctl(semid, 0, IPC_RMID);
if(ret == -1)
{
printf("semctl() error: %s\n", strerror(errno));
}
printf("semctl() success. Sem is deleted.\n");
return -1;
}
(5)makefile
编译
gcc -c pubfun.c
gcc -c server.c
gcc -c client.c
gcc pubfun.o server.o -o server
gcc pubfun.o client.o -o client
运行
./server
./client
测试结果
server进程输入exit则结束进程,client读取到exit后结束进程并删除共享内存和信号量。