操作系统:信号量机制之生产者与消费者实验
实验目的:了解和熟悉linux系统下的信号量集和共享内存。
任务:使用linux系统提供的信号量集和共享内存实现生产者和消费者问题。
实验要求:
- 写两个程序,一个模拟生产者过程,一个模拟消费者过程;
- 创建一个共享内存模拟生产者-消费者问题中缓冲队列,该缓冲队列有N(N=10)个缓冲区,每个缓冲区的大小为1024B,每个生产者和消费者对缓冲区必须互斥访问;
- 由第一个生产者创建信号量和共享内存,其他生产者和消费者可以使用该信号量和共享内存;
- 生产者程序:生产者生产产品(即是从键盘输入长度小于1024B的字符)放入空的缓冲区;
- 消费者程序:消费者消费产品(即从满的缓冲区中取出内容在屏幕上打印出来),然后满的缓冲区变为空的缓冲区;
- 多次运行生产者程序和消费者进程,可以产生多个生产者进程和多个消费者进程,这些进程可以共享这些信号量和共享内存,实现生产者和消费者问题;
- 在生产者程序和消费者程序中,都可以选择:
- 生产产品/消费产品;
- 退出。退出进程,但信号量和共享内存仍然存在,其他生产者进程和消费者进程还可以继续使用;
- 删除信号量和共享内存。显性删除信号量和共享内存,其他生产者进程和消费者进程都不能使用这些信号量和共享内存。
代码:
环境:Linux,需要将头文件对应的c文件,和另外两个主要的c程序分别联合编译。
如:gcc pubfun.c producer.c -o producer
文件1:shm_sem.h
# ifndef _SHM_SEM_H
# define _SHM_SEM_H
# define N 10
# define SHM_KEY 38111
# define SEM_KEY 38222
# define SHM_SIZE (1024*(N+1))
# define SEM_NUM 3
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
文件二: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)
{
/**********************************************
* name: sem_p(int semid, int semnum)
* description:进行p操作
* paras: semid:信号量的标识符,也就是semget()函数的返回值 semnum:操作的信号在信号集中的编号
* return: semop的返回值
***********************************************/
/*int semop(int semid, struct sembuf semoparray[], size_t cnts); */
/**********************************************
* name: semop(int semid, struct sembuf semoparray[], size_t cnts)
* description: 用户改变信号量的值。也就是使用资源或释放资源。对信号量集标识符为semid中的一个或多
个信号量进行P操作或V操作。
* paras: semid, sembuf semoparray[]:指向进行操作的信号量集结构体数组 cnts:进行操作的信号量个数,常为1
*struct sembuf{
short sem_num; //信号量集合中的信号量编号,0代表第1个信号量
short sem_op; //若sem_op大于0,进行v操作,信号量值加sem_op,表示进程释放控制的资源
//若sem_op小于0,进行p操作,信号量值减sem_op,若(semval-sem_op)<0 (semval为该信号量值),进程阻塞直到资源可用
//sem_op==0时,调用进程进入睡眠状态,直到信号值为0.
short flag; // “0”设置信号量的默认操作
//IPC_NOWAIT 设置信号量操作不等待
//SEM_UNDO 选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值
}
* return: 成功返回信号量集的标识符,失败返回-1
***********************************************/
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); */
/*int semop(int semid, struct sembuf semoparray[], size_t cnts); */
/**********************************************
* name: semop(int semid, struct sembuf semoparray[], size_t cnts)
* description: 用户改变信号量的值。也就是使用资源或释放资源。对信号量集标识符为semid中的一个或多
个信号量进行P操作或V操作。
* paras: semid, sembuf semoparray[]:指向进行操作的信号量集结构体数组 cnts:进行操作的信号量个数,常为1
*struct sembuf{
short sem_num; //信号量集合中的信号量编号,0代表第1个信号量
short sem_op; //若sem_op大于0,进行v操作,信号量值加sem_op,表示进程释放控制的资源
//若sem_op小于0,进行p操作,信号量值减sem_op,若(semval-sem_op)<0 (semval为该信号量值),进程阻塞直到资源可用
//sem_op==0时,调用进程进入睡眠状态,直到信号值为0.
short flag; // “0”设置信号量的默认操作
//IPC_NOWAIT 设置信号量操作不等待
//SEM_UNDO 选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值
}
* return: 成功返回信号量集的标识符,失败返回-1
***********************************************/
struct sembuf semOpr ;
semOpr.sem_num = semnum;
semOpr.sem_op = 1;
//semOpr.sem_flg = SEM_UNDO; //通常为SEM_UNDO,使操作系统跟踪信号,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
return semop(semid, &semOpr, 1);
}
/*get semval*/
int get_semval(int semid, int semnum)
{
/**********************************************
* name: semctl(semid, semnum, GETVAL)
* description: 控制信号量的信息
* paras: semid是信号量的标识码,也就是semget()函数的返回值 semnum是操作的信号在信号集中的编号 GETVAL:返回信号量集合内单个信号量的值
* return: 成功返回0,失败返回-1
***********************************************/
return semctl(semid, semnum, GETVAL);
}
文件三:producer.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;
char ch;
void *ptrShm = NULL;
struct shmid_ds shm_ds;
struct semid_ds sem_ds;
char buf[1024] = {0};
union semun arg;
int *addr,*addr1,in,out;
unsigned short semvalArr[SEM_NUM] = {1,0,10}; //mutex,full,empty
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);*/
/*功能:创建一个新的信号量或获取一个已经存在的信号量的键值。
*SEM_NUM为初始化信号量的个数 0660|IPC_CREAT|IPC_EXCL:表示信号量的读写权限和创建方式
IPC_CREAT:
如果消息队列对象不存在,则创建之,否则则进行打开操作;
IPC_EXCL:
和IPC_CREAT一起使用(用"|"连接),如果消息对象不存在则创建之,否则产生一个错误并返回。
如果单独使用IPC_CREAT标志,msgget()函数要么返回一个已经存在的消息队列对象的标识符,要么返回一个新建立的消息队列对象的标识符。
如果将IPC_CREAT和IPC-EXCL标志一起使用,msgget()将返回一个新建的消息对象的标识符,
或者返回-1如果消息队列对象已存在。IPC-EXCL标志本身并没有太大的意义,
但和IPC-CREAT标志一起使用可以用来保证所得的消息队列对象是新创建的而不是打开的已有的对象。
*返回值:成功返回信号量的标识码ID。失败返回-1;*/
if(semid == -1) //semget 创建失败
{
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("existe semget() success. semid=[%d]\n", semid);
}
else
{
printf("semget() error: %s\n", strerror(errno));
return -1;
}
}
else /*need to init sem*/
{
printf("creat semget() success. semid=[%d]\n", semid);
/*semctl(): init sem, set semvalArr[0]=1 semvalArr[1]=0*/
/**********************************************
* name: semctl(semid, semnum, GETVAL)
* description: 控制信号量的信息
* paras: semid是信号量的标识码,也就是semget()函数的返回值 semnum是操作的信号在信号集中的编号 GETVAL:返回信号量集合内单个信号量的值
SETALL:与GETALL正好相反,用联合体中val成员的值设置信号量集合中单个信号量的值
arg是可选的,是union semun的实例
union semun {
int val; //SETVAL的用值
struct semid_ds *buf; //IPC_STAT和IPC_SET 用的semid_ds结构
unsigned short *arrary; //为控制IPC_INFO提供的缓存
};
* return: 成功返回0,失败返回-1
***********************************************/
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("init sem success.\n");
}
/*shmget*/
shmid = shmget((key_t)SHM_KEY, SHM_SIZE, 0660|IPC_CREAT|IPC_EXCL);
/*int shmget(key_t key, size_t size, int flag);*/
/**********************************************
* name: shmget(key_t key, size_t size, int flag)
* description: 该函数用来创建共享内存
* paras: size以字节为单位指定需要共享的内存容量
0660|IPC_CREAT|IPC_EXCL设置读写权限和创建方式 :
IPC_CREAT 如果共享内存不存在,则创建一个共享内存,否则打开操作。
IPC_EXCL 只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误。
如果单独使用IPC_CREAT,shmget()函数要么返回一个已经存在的共享内存的操作符,要么返回一个新建的共享内存的标识符。
如果将IPC_CREAT和IPC_EXCL标志一起使用,shmget()将返回一个新建的共享内存的标识符;
* return: 不成功返回-1
***********************************************/
if(shmid == -1)
{
if(errno == EEXIST)//共享内存存在
{
printf("shmget() warning: %s\n", strerror(errno));
shmid = shmget((key_t)SHM_KEY, SHM_SIZE, 0660|IPC_CREAT); //如果已经存在:则更改创建方式为IPC_CREAT
if(shmid == -1)
{
printf("shmget() error: %s\n", strerror(errno));
return -1;
}
printf("exist shmget() success. shmid=[%d]\n", shmid);
ptrShm = (int *)shmat(shmid, NULL, !SHM_RDONLY);
/*void *shmat(int shmid, const void *shmarr, int flag);*/
/**********************************************
* name:shmat(int shmid, const void *shmarr, int flag)
* description: 把共享内存区对象映射到调用进程的地址空间
(连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问)
* paras: shmid 共享内存标识符
shmaddr 指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置
shmflg SHM_RDONLY:为只读模式,其他为读写模式
* return: 出错返回-1,错误原因存于errno中;成功返回共享内存地址
***********************************************/
if(ptrShm == NULL)
{
printf("shmat() error: %s\n", strerror(errno));
goto FAILED;
}
printf("exist shmat() success.\n");
}
else//其他错误:输出并退出
{
printf("shmget() error: %s\n", strerror(errno));
return -1;
}
}
else//创建共享内存成功
{
printf("create shmget() success. shmid=[%d]\n", shmid);
/*shmat*/
ptrShm = (int *)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("create shmat() success.\n");
addr=(int *)(ptrShm+N*sizeof(buf)); //生产者指针
*addr=0;
in=*addr;
addr1=(int *)(ptrShm+N*sizeof(buf)+4); //消费者指针
*addr1=0;
out=*addr1;
}
/*Begin to write*/
printf("Begin to write shared memory.\n");
while(1)
{
/*input*/
printf("\n生产者进程: 1.生产产品;2.退出;3.删除信号量和共享内存\n");
ch=getchar();
if (ch=='2' )
break;
else if (ch=='3')
goto FAILED;
else if (ch!='1')
continue;
getchar();
printf("输入产品:\n");
memset(&buf, 0x00, sizeof(buf));
if(fgets(buf, sizeof(buf)-1, stdin) < 0)
{
printf("fgets() error\n");
fflush(stdin);
continue;
}
fflush(stdin);
printf("product before. semval[0]=[%d] semval[1]=[%d] semval[2]=[%d].\n", get_semval(semid, 0), get_semval(semid, 1), get_semval(semid, 2));
/*P*/
ret = sem_p(semid, 2); //wait Empty
if(ret == -1)
{
printf("Empty sem_p() error\n");
goto FAILED;
}
ret = sem_p(semid, 0);
if(ret == -1)
{
printf("Mutex sem_p() error\n");
goto FAILED;
}
/*write into shm*/
addr=(int *)(ptrShm+N*sizeof(buf)); //生产者指针
in=*addr;
printf("pruduct in=[%d].\n",in);
memset(ptrShm+in*sizeof(buf), 0x00, sizeof(buf));
memcpy((char*)(ptrShm+in*sizeof(buf)), buf, strlen(buf));
printf("产品: %s", buf);
in=(in+1)%N;
*addr=in;
sleep(1);
/*V*/
ret = sem_v(semid, 1);
if(ret == -1)
{
printf("Full sem_v() error\n");
goto FAILED;
}
ret = sem_v(semid, 0);
if(ret == -1)
{
printf("Mutex sem_v() error\n");
goto FAILED;
}
printf("product finished. semval[0]=[%d] semval[1]=[%d] semval[2]=[%d] in=[%d].\n", get_semval(semid, 0), get_semval(semid, 1), get_semval(semid, 2),in);
}
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;
}
文件四:comsumer.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"
/************************
* client
* P(sem[1])
* read
* V(sem[0])
************************/
int main(void)
{
int ret;
int shmid;
int semid;
void *ptrShm = NULL;
void *ptrShm1 = NULL;
struct shmid_ds shm_ds;
struct semid_ds sem_ds;
char buf[1024] = {0};
char ch;
//union semun arg;
//unsigned short semvalArr[SEM_NUM];
int *addr,out;
memset(&shm_ds, 0x00, sizeof(shm_ds));
memset(&sem_ds, 0x00, sizeof(sem_ds));
/*semget*/
semid = semget((key_t)SEM_KEY, SEM_NUM, 0660); /*int semget(key_t key, int nsems, int flag);*/
if(semid == -1)
{
printf("semget() error: %s\n", strerror(errno));
return -1;
}
else
{
printf("exist semget() success. semid=[%d]\n", semid);
}
/*shmget*/
shmid = shmget((key_t)SHM_KEY, SHM_SIZE, 0660); /*int shmget(key_t key, size_t size, int flag);*/
if(shmid == -1)
{
printf("shmget() error: %s\n", strerror(errno));
return -1;
}
else
{
printf("shmget() success. shmid=[%d]\n", shmid);
ptrShm = (int *)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");
/*Begin to read*/
while(1)
{
/*P*/
printf("\n消费者进程: 1.消费产品;2.退出;3.删除信号量和共享内存\n");
ch=getchar();
if (ch=='2' )
break;
else if (ch=='3')
goto FAILED;
else if (ch!='1')
continue;
getchar();
printf("comsume before. semval[0]=[%d] semval[1]=[%d] semval[2]=[%d].\n", get_semval(semid, 0), get_semval(semid, 1), get_semval(semid, 2));
ret = sem_p(semid, 1);
if(ret == -1)
{
printf("sem_p() error\n");
goto FAILED;
}
ret = sem_p(semid, 0);
if(ret == -1)
{
printf("sem_p() error\n");
goto FAILED;
}
/*read shm*/
addr=(int *)(ptrShm+N*sizeof(buf)+4); //消费者指针
out=*addr;
printf("pruduct out=[%d].\n",out);
memset(&buf, 0x00, sizeof(buf));
ptrShm1=ptrShm+out*sizeof(buf);
memcpy(buf, (char*)ptrShm1, sizeof(buf));
sleep(1);
out=(out+1)%N;
*addr=out;
/*V*/
ret = sem_v(semid, 0);
if(ret == -1)
{
printf("sem_v() error\n");
goto FAILED;
}
ret = sem_v(semid, 2);
if(ret == -1)
{
printf("sem_v() error\n");
goto FAILED;
}
printf("产品: %s", buf);
printf("comsume finished. semval[0]=[%d] semval[1]=[%d] semval[2]=[%d] out=[%d].\n", get_semval(semid, 0), get_semval(semid, 1), get_semval(semid, 2),out);
}
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;
}
实验结果:
执行生产者程序:
输入产品a之前,空的缓冲区semval[2]为10,满的缓冲区semval[1]为0
输入产品a后,空的缓冲区semval[2]变为9,满的缓冲区semval[1]变为1
生产者指针由0变为1。
执行消费者程序:
消费产品前因为只生产了a,b,c三个产品,所以空的缓冲区semval[2]为7,满的缓冲区semval[1]为3。消费产品后,空的缓冲区semval[2]变为8,满的缓冲区semval[1]变为2。消费者指针由0变为1,消费的产品为消费者指针指向的缓冲区里的产品a。
然后再生产d,e,f产品。
再消费:
进一步说明生产者进程和消费者进程可以共享信号量和共享内存。