企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
## 实验步骤 (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信号量上面。进而产生饥饿状态进入死锁。