## 实验步骤
(1)在ubuntu下,用系统提供的sem_open()、sem_close()、sem_wait()和sem_post()等信号量相关的系统调用编写pc.c程序。
(2)在ubuntu上编译并运行pc.c,检查运行结果。
### 终端也是临界资源
用printf()向终端输出信息是很自然的事情,但当多个进程同时输出时,终端也成为了一个临界资源,需要做好互斥保护,否则输出的信息可能错乱。
另外,printf()之后,信息只是保存在输出缓冲区内,还没有真正送到终端上,这也可能造成输出信息时序不一致。用fflush(stdout)可以确保数据送到终端。
本次实验相较于往届已极大地简化,毕竟时间有限,所以不用在Linux0.11下实现信号量(里面没有sem_open()等系统调用,需要自己添加),仅需要在Ubantu下运行生产者,消费者的相关程序。
首先介绍一下所需函数
~~~
int fseek(FILE *stream, long offset, int fromwhere);
函数设置文件指针stream的位置。
如果执行成功,stream将指向以fromwhere为基准,偏移offset(指针偏移量)个字节的位置,函数返回0。
如果执行失败(比如offset超过文件自身大小),则不改变stream指向的位置,函数返回一个非0值。
size_t fread(void *buffer,size_t size,size_t count, FILE *stream );
buffer 是读取的数据存放的内存的指针
size 是每次读取的字节数
count 是读取次数
stream 是要读取的文件的指针
从一个文件流中读数据,最多读取count个元素,每个元素size字节,如果调用成功返回实际读取到的元素个数,如果不成功或读到文件末尾返回 0。
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
(1)buffer:是一个指针,对fwrite来说,是要获取数据的地址;
(2)size:要写入内容的单字节数;
(3)count:要进行写入size字节的数据项的个数;
(4)stream:目标文件指针;
(5)返回实际写入的数据项个数count。
~~~
算法的思想是:建立一个文件缓冲区,0~9位存储生产出的数据,第10位存储当前读到的位置;因为缓冲区是覆盖写入,例如当消费者消费到第6位,而生产者此时可以生产覆盖前5位,但消费者消费是顺序消费的,必须要读到缓冲区尾才可以再从头读。
这就有必要存储当前读取的位置(因为进程可能被中断,下次再来就不知道读到哪里了),所以下面要执行两次,第一次读出当前所读位置,再根据此位置计算位偏移。
~~~
fseek( fp, 10*sizeof(int), SEEK_SET );
fread( &Outpos, sizeof(int), 1, fp);
fseek( fp, Outpos*sizeof(int), SEEK_SET );
fread( &costnum, sizeof(int), 1, fp);
~~~
pc.c
~~~
#define __LIBRARY__
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#define Total 500
#define PNUM 5
#define BUFFERSIZE 10
/*
*/
int main()
{
int i, j, k;
int costnum;
int Outpos = 0;
int Inpos = 0;
sem_t *empty, *full, *mutex;
FILE *fp = NULL;
empty =(sem_t *)sem_open("empty", O_CREAT, 0064, 10);
full = (sem_t *)sem_open("full", O_CREAT, 0064, 0);
mutex = (sem_t *)sem_open("mutex",O_CREAT, 0064, 1);
fp=fopen("FileBuffer.txt", "wb+");
fseek( fp, 10*sizeof(int) , SEEK_SET );
fwrite( &Outpos, sizeof(int), 1, fp);
fflush(fp);
if( !fork() )
{
for( i = 0 ; i < Total; i++)
{
sem_wait(empty);
sem_wait(mutex);
fseek( fp, Inpos * sizeof(int), SEEK_SET );
fwrite( &i, sizeof(int), 1, fp );
fflush(fp);
Inpos = ( Inpos + 1 ) % BUFFERSIZE;
sem_post(mutex);
sem_post(full);
}
exit(0);
}
for( k = 0; k < PNUM ; k++ )
{
if( !fork() )
{
for( j = 0; j < Total/PNUM; j++ )
{
sem_wait(full);
sem_wait(mutex);
/*
fseek( fp , 10*sizeof(int) , SEEK_SET );
if(!fread( &outlocate, sizeof(int),1, fp))
{
printf("read error!\n");
exit(1);
}
*/
/*
fseek(fp, outlocate*sizeof(int), SEEK_SET );
if(!fread( &costnum, sizeof(int),1, fp))
{
printf("read error!\n");
exit(1);
}
*/
fflush(stdout);
fseek( fp, 10*sizeof(int), SEEK_SET );
fread( &Outpos, sizeof(int), 1, fp);
fseek( fp, Outpos*sizeof(int), SEEK_SET );
fread( &costnum, sizeof(int), 1, fp);
printf("%d: %d\n",getpid(),costnum);
fflush(stdout);
Outpos = (Outpos + 1) % BUFFERSIZE;
fseek( fp, 10*sizeof(int), SEEK_SET );
fwrite( &Outpos, sizeof(int),1, fp );
fflush(fp);
sem_post(mutex);
sem_post(empty);
}
exit(0);
}
}
wait(NULL);
wait(NULL);
wait(NULL);
wait(NULL);
wait(NULL);
sem_unlink("empty");
sem_unlink("full");
sem_unlink("mutex");
fclose(fp);
return 0;
}
~~~
附report
1.在pc.c中去掉所有与信号量有关的代码,再运行程序,执行效果有变化吗?为什么会这样?
答:在去掉与信号量有关的代码后,执行结果Customer的消费数据没有按递增的顺序输出,且fread()函数将产生错误。
因为没有信号量P(S)控制,导致生产者可能在缓冲区满后继续生产,导致没有被消费的数据被覆盖,使得消费者消费的数据不是递增序列。
同时,没有信号量V(S)控制,导致消费者可能在读取所有数据后仍然继续读取,导致读取的数据无效。
没有mutex信号量控制导致出现多进程并发访问缓冲区,导致出现fread()错误。
2.这样可行吗?如果可行,那么它和标准解法在执行效果上会有什么不同?如果不可行,那么它有什么问题使它不可行?
答:这样不可行。程序在某种情况下会出现死锁状态。
例如:当mutex = 1,并且生产者要进入生产一个数据,假设此时empty = 0,mutex = 0,P(empty)后小于0,生产者进程进入等待在信号量empty的等待队列上面调用schedule(),可是此时并未解锁,即mutex.value值仍然为0。它们都等待在信号量mutex上面。同理,消费者进程也是如此,若mutex.value = 1,full.value = 0,在执行完P(mutex)P(full)之后,mutex = 0,并且将消费者进程放入等待在信号量full的等待队列上面,而此时也并未释放mutex,因此消费者和生产者进程都等待在mutex信号量上面。进而产生饥饿状态进入死锁。