【注意】代码在文末,以下为详细实验报告
【实验目的】
以生产者和消费者问题为例,学习并熟悉Linux下进程通信、同步机制的具体实现方法,主要是了解并掌握信号量机制和共享内存的使用方法,进一步熟悉Linux系统的相关指令的调用。
【实验内容】
使用共享内存和信号量机制来实现生产者和消费者进程间的一对一、一对多和多对多的通信和同步,要求在Linux下实现。
【实验步骤、过程】(含原理图、流程图、关键代码,或实验过程中的记录、数据等)
一、数据结构
1.共享内存定义为一个结构,使得其数据成员更清晰且操作变得简单。
2.共享缓冲区采用循环队列的数据结构,如下图所示。
图1 共享缓冲区数据结构
其中,head为队头指针,tail为队尾指针,str[MAX_BUFFER_SIZE]为数据区域,num为数据数量,is_empty为一个标志,用来指明缓冲区是否为空。
二、算法描述
大致可以分为以下四个部分
1.主程序(main)
(1)创建信号量、共享内存并初始化,将申请的共享内存附加到申请通信的进程空间
图2 初始化参数和信号量
(2)创建生产者、消费者进程,并执行
(3)等待所有子进程结束
(4)删除信号量和共享内存,释放空间
2.生产者进程
(1)通过shmat函数连接共享内存标识符为shm_id的共享内存,把共享内存区对象映射到该生产者进程的地址空间
图3 生产者进程—连接共享内存
(2)P(SEM_EMPTY),P(MUTEX),product,V(MUTEX),V(SEM_FULL)
图4 生产者进程—生产
3.消费者进程
(1)通过shmat函数连接共享内存标识符为shm_id的共享内存,把共享内存区对象映射到该生产者进程的地址空间
图6 消费者进程—连接共享内存
(2)P(SEM_FULL),P(MUTEX),product,V(MUTEX),V(SEM_EMPTY)
图7 消费者进程—消费
4.循环队列部分
图9 循环队列—加入数据
图10 循环队列—取出数据
5.原子操作
P操作、V操作对封装的信号量进行“减1”“加1”操作。
在LINUX下,通过使用 semop 函数改变信号量的值。以下P、V操作分别使用两种代码书写方式。
图11 wait函数(P操作)
图12 signal函数(V操作)
三、程序流程图
图14 生产者和消费者进程流程图
四、编译指令
//1对1
$ gcc -o pro(1)_con(1).out pro(1)_con(1).c
$ ./pro(1)_con(1).out
//1对多
$ gcc -o pro(1)_con(n).out pro(1)_con(n).c
$ ./pro(1)_con(n).out
$ gcc -o pro(n)_con(1).out pro(n)_con(1).c
$ ./pro(n)_con(1).out
//多对多
$ gcc -o pro(n)_con(n).out pro(n)_con(n).c
$ ./pro(n)_con(n).out
图15 所有编译指令
五、运行结果
1.一个生产者+一个消费者
图16 pro(1)-con(1).c运行结果
2.一个生产者+多个消费者(这里选3个消费者)
图17 pro(1)-con(n).c运行结果
3.多个生产者(这里选4个生产者)+一个消费者
图18 pro(n)-con(1).c运行结果
4.多个生产者(这里选5个生产者)+多个消费者(这里选5个消费者)
图19 pro(n)-con(n).c运行结果
【实验结果或总结】(对实验结果进行相应分析,或总结实验的心得体会,并提出实验的改进意见)
本次实验是关于生产者和消费者之间互斥和同步的问题。问题的实质是P, V操作,实验设一个共享缓冲区,生产者和消费者互斥的使用,当一个线程使用缓冲区的时候,另一个让其等待知道前一个线程释放缓冲区为止。
生产者消费者问题是互相合作进程关系的一种抽象,例如,在输入时,输入进程是消费者,计算进程是生产者,在输出时,计算进程是生产者,打印进程是消费者,因此该问题很大的代表性和使用价值,也是操作系统在学习进程间同步与互斥的经典问题。
通过本次实验,我对操作系统的P,V进一步的认识,深入的了解P,V操作的实质和其重要性。课本的理论知识进一步阐述了现实的实际问题。
【代码】
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_BUFFER_SIZE 10
#define SHM_MODE 0600
#define SEM_MODE 0600
#define SEM_FULL 0 //semaphore 1 --- full
#define SEM_EMPTY 1 //semaphore 2 --- empty
#define MUTEX 2 //semaphore 3 --- exclusive access
struct my_buffer //circular queue
{
int head;
int tail;
char str[MAX_BUFFER_SIZE];
int num; //number of letter
int is_empty;
};
const int N_CONSUMER = 5;
const int N_PRODUCER = 5;
const int N_BUFFER = 10;
const int N_WORKTIME = 10;
int shm_id = -1;
int sem_id = -1;
pid_t child; //process type
pid_t parent; //process type
int get_rand_num() //0~9 random number
{
int digit;
srand((unsigned)(getpid() + time(NULL)));
digit = rand() % 10;
return digit;
}
char get_rand_letter() //A~Z random letter
{
char letter;
srand((unsigned)(getpid() + time(NULL)));
letter = (char)((rand() % 26) + 'A');
return letter;
}
/*
struct sembuf
{
unsigned short int sem_num; //semaphore sequence number
short int sem_op; //operation: >0 or =0 or <0
short int sem_flg; //operation ID: 0(normal),IPC_WAIT,SEM_UNDO
}
*/
void Wait(int sem_id,int sem_num) //P operation
{
struct sembuf sb;
sb.sem_num = sem_num;
sb.sem_op = -1; //Wait(-1)
sb.sem_flg = SEM_UNDO; //add abs(sem_op)
//the last 1 is the size of the sembuf type---sb
if(semop(sem_id,&sb,1) < 0) //judge
{
perror("Wait Operation Failed!\n");
exit(1);
}
}
void Signal(int sem_id,int sem_num) //V operation
{
struct sembuf sb;
sb.sem_num = sem_num;
sb.sem_op = 1; //Signal(+1)
sb.sem_flg = SEM_UNDO; //sub sem_op
//the last 1 is the size of the sembuf type---sb
if(semop(sem_id,&sb,1) < 0) //judge
{
perror("Signal Operation Failed!\n");
exit(1);
}
}
void printTime() //output running time
{
time_t now;
struct tm *timenow;
time(&now);
timenow = localtime(&now);
printf("runtime: %s",asctime(timenow));
}
int main()
{
shm_id = shmget(IPC_PRIVATE,MAX_BUFFER_SIZE,SHM_MODE); //apply for shared memory
if(shm_id < 0)
{
perror("apply for shared memory failed!\n");
exit(1);
}
struct my_buffer *bf;
bf = shmat(shm_id, 0, 0);//add buffer to process space
if (bf == (void*)-1)
{
perror("add buffer to process space failed!\n");
exit(1);
}
if((sem_id = semget(IPC_PRIVATE,3,SEM_MODE)) < 0) //create three semaphores
{
//SEM_FULL,SEM_EMPTY,MUTEX
perror("create three semaphores failed!\n");
exit(1);
}
//set the three semaphores
if(semctl(sem_id,SEM_FULL,SETVAL,0) == -1) //SEM_FULL->0
{
perror("SEM_FULL set error!\n");
exit(1);
}
if(semctl(sem_id,SEM_EMPTY,SETVAL,10) == -1) //SEM_EMPTY->10
{
perror("SEM_EMPTY set error!\n");
exit(1);
}
if(semctl(sem_id,MUTEX,SETVAL,1) == -1) //MUTEX->1
{
perror("MUTEX set error!\n");
exit(1);
}
//set queue(buffer-shmptr) message
bf->head = 0;
bf->tail = 0;
bf->is_empty = 1;
bf->num = 0;
//gcc not allow for(int i=0;...)
int i,j;
for(i = 0; i < N_PRODUCER; i++) //producer
{
parent = fork();
if(parent < 0)
{
perror("fork failed!\n");
exit(1);
}
else if(parent == 0)
{
bf = shmat(shm_id, 0, 0);
if(bf == (void*) - 1)
{
perror("add buffer to process space failed!\n");
exit(1);
}
int count = 0;
for(j = 0; j < N_WORKTIME; j++)
{
Wait(sem_id, SEM_EMPTY);
Wait(sem_id, MUTEX);
sleep(get_rand_num());
//printf message and pid ppid
printf("-------------------------------------------------------------\n");
printf("This is producer %d , PID = %d , PPID = %d\n", i + 1, getpid(), getppid());
//product
char c = get_rand_letter();
bf->str[bf->tail] = c;
bf->tail = (bf->tail + 1) % MAX_BUFFER_SIZE;
bf->is_empty = 0;
bf->num++;
printTime();
//printf buffer
int p;
printf("buffer data (%d letters):",bf->num);
p = (bf->tail-1 >= bf->head) ? (bf->tail-1) : (bf->tail-1 + MAX_BUFFER_SIZE);
for (p; !(bf->is_empty) && p >= bf->head; p--)
{
printf("%c", bf->str[p % MAX_BUFFER_SIZE]);
}
//printf message
printf("\nproducer %d puts '%c'.\n", i + 1, c);
printf("-------------------------------------------------------------\n");
//flush buffer
fflush(stdout);
Signal(sem_id, MUTEX);
Signal(sem_id, SEM_FULL);
}
//disconnect the shared segment from the process
shmdt(bf);
exit(0);
}
}
for(i = 0; i < N_CONSUMER; i++)
{
child = fork();
if(child < 0)
{
perror("fork failed!\n");
exit(1);
}
else if(child == 0)
{
int count = 0;
bf = shmat(shm_id, 0, 0);
if (bf == (void*) - 1)
{
perror("add buffer to process space failed!\n");
exit(1);
}
for(j = 0; j < N_WORKTIME; j++) //consumer
{
Wait(sem_id, SEM_FULL);
Wait(sem_id, MUTEX);
sleep(get_rand_num());
//printf message and pid ppid
printf("-------------------------------------------------------------\n");
printf("This is consumer %d , PID = %d , PPID = %d\n", i + 1, getpid(), getppid());
//consume
char lt = bf->str[bf->head];
bf->head = (bf->head + 1) % MAX_BUFFER_SIZE;
bf->is_empty = (bf->head == bf->tail);
bf->num--;
printTime();
//printf buffer
int p;
printf("buffer data (%d letters):",bf->num);
p = (bf->tail-1 >= bf->head) ? (bf->tail-1) : (bf->tail-1 + MAX_BUFFER_SIZE);
for (p; !(bf->is_empty) && p >= bf->head; p--)
{
printf("%c", bf->str[p % MAX_BUFFER_SIZE]);
}
//printf message
printf("\nconsumer %d gets '%c'.\n", i + 1, lt);
printf("-------------------------------------------------------------\n");
//flush buffer
fflush(stdout);
Signal(sem_id,MUTEX);
Signal(sem_id,SEM_EMPTY);
}
//disconnect the shared segment from the process
shmdt(bf);
exit(0);
}
}
//main process quit
while (wait(0) != -1);
//disconnect the shared segment from the process
shmdt(bf);
shmctl(shm_id,IPC_RMID,0);
shmctl(sem_id,IPC_RMID,0);
printf("the main process is over !\n");
fflush(stdout);
exit(0);
return 0;
}