【注意】代码在文末,以下为详细实验报告
【实验目的】
以读者写者问题为例,学习并熟悉Linux下进程通信、同步机制的具体实现方法,主要是了解并掌握信号量机制的使用方法,进一步熟悉Linux系统的相关指令的调用。
【实验内容】
在Linux环境下,创建一个控制台进程,此进程包含n个线程。用这n个线程来表示n个读者或写者。每个线程按相应进行读写操作。用信号量机制分别实现读者优先和写者优先的读者-写者问题。
读者-写者问题的读写操作限制(包括读者优先和写者优先):
写-写互斥,即不能有两个写者同时进行写操作。
读-写互斥,即不能同时有一个线程在读,而另一个线程在写。
读-读允许,即可以有一个或多个读者在读。
读者优先的附加限制:如果一个读者申请进行读操作时已有另一个读者正在进行读操作,则该读者可直接开始读操作。
写者优先的附加限制:如果一个读者申请进行读操作时已有另一写者在等待访问共享资源,则该读者必须等到没有写者处于等待状态才能开始读操作。
【实验步骤、过程】(含原理图、流程图、关键代码,或实验过程中的记录、数据等)
一、数据结构
读者写者问题中,共定义了以下两个数据结构,分别是共享内存和readcount,详细定义内容如下所示
图1 共享内存数据结构
图2 readcount数据结构
二、算法描述
大致可以分为以下两个部分
1.读者优先
(1)初始化,产生共享内存并初始化共享内存,初始读取的进程为0个,将信号量集中的互斥读取信号量和文件资源信号量进行初始化,在后续读者写者进程运行时会对内容改写。
图3 初始化
(2)读者先通过p操作访问Readcount资源,通过Readcount是否为0判断该进程是否为第一个读者进程,若为第一个读者进程则取得文件资源信号量然后Readcount++,再通过v操作退出访问Readcount资源此后调用read()函数读取临界资源,在退出临界资源也与进入时一样,需判断是否为第一个读者进程,若是则需对file信号量进行v操作
图4 读者访问资源readcount
(3)写者进程在一开始的时候就需要对file信号量进行p操作以申请访问临界资源,在没有其他进程使用临界资源是才可以进入,从而实现了读者优先。
2.写者优先
在写者优先问题中,相较于读者优先,在读进程里增加一个信号量,让读进程与写进程公平竞争前需要先争夺该信号量实现写进程优先。
图6 写进程
3.原子操作
实现的P操作,通过得到信号量集的id并获取需要操作的信号量对其信号量进行-1操作。
图7 P操作
实现V操作,通过得到信号量集的id并获取需要操作的信号量对其进行+1操作。
图8 V操作
三、程序流程图
图9 读者优先流程图
图10 写者优先流程图
四、伪代码描述
1.读者优先
int readcount=0;
semaphore rmutex=1;
semaphore wmutex=1;
writer()
{
while(1)
{
P(rmutex);
…
Perform write operation
…
V(wmutex);
}
}
reader()
{
while(1)
{
P(rmutex);
if(count==0)
P(wmutex);
readcount++;
V(rmutex);
…
Perform read operation
…
P(rmutex);
readcount--;
if(count==0)
V(wmutex);
V(rmutex);
}
}
2.写者优先
int Writercount=0;
int Readercount=0;
semaphore wmutex=1;
semaphore rmutex=1;
semaphore wpmutex=1;
semaphore source=1;
writer()
{
while(1)
{
P(wmutux);
if(Writercount==0)
P(wpmuex);
Writercount++;
V(wmutux)
P(souce);
…
Perform write operation
…
V(souce);
P(wmutux);
Writercount--;
if(Writercount==0)
V(wpmutex);
V(wmutux);
}
}
reader()
{
while(1)
{
P(wpmutex);
P(rmutex);
if(Readercount==0)
P(souce);
Readercount++;
V(rmutex);
V(wpmutex);
…
Perform read operation
…
P(r_mutex);
Readercount--;
if(Readercount==0)
V(souce);
V(rmutex);
}
}
五、编译指令
//读者优先
$ gcc -o r_first.out r_first.c
$ ./r_first.out
//写者优先
$ gcc -o w_first.out w_first.c
$ ./w_first.out
图11 所有编译指令
六、运行结果
1.读者优先
图12 r_first.c运行结果
图13 w_first.c运行结果
【实验结果或总结】(对实验结果进行相应分析,或总结实验的心得体会,并提出实验的改进意见)
在读者优先中,如果读者获得了访问权,那么当一个进程读完,另一个也可以开始进行读,但是如果写进程获得了访问权,而读者可以优先获得临界区的访问权,从而写进程被阻塞,进入阻塞队列,因此,在读者优先中,写进程只有等待没有读者访问临界区的时候,才能得到临界区的访问权,而此时读者在任意时候都可以得到临界区的访问权。
在写者优先中,如果读者获得了访问权,那么当一个进程读完,另一个也可以开始进行读,但是如果读进程获得了访问权,而写者可以优先获得临界区的访问权,从而读进程被阻塞,进入阻塞队列,等此时的读者刚执行完V操作时,那么写者就可以进程P操作继续执行了。因此,在写者优先中,我们需要增加一个信号量,让读进程与写进程公平竞争前需要先争夺该信号量实现写进程优先。
设想当一个读者在读数据,另一个读者也来访问,由于同时允许多个读者同时进行读操作,所以第二个读者也被允许进入,同理第三个读者也被允许进入。现在假设一个写者到来,由于写操作是排他的,所以它不能写数据,而是被阻塞。随后其他的读者到来,这样只要有一个读者活跃,随后而来的读者都被允许访问数据。这样的结果是只要有读者陆续到来,它们一来就被允许进入,而写者将一直被挂起直到没有一个读者为止。
这次作业让我加深了对于信号量机制以及进程同步的理解,也让学到了许多东西,只有理论与实践相结合,才能把知识学得更加牢固更加扎实。
最后,感谢倪福川老师以及其他同学给予我的帮助。
代码
#include
#include
#include
#include
#define SHM_SIZE (1024*1024)
#define SHM_MODE 0600
#define SEM_MODE 0600
#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
#else
#define n 4
#define DELAY (rand() % 5 + 1)
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
};
#endif
int semid=-1,shmId=-1;
struct readcount
{
int read;
int l;
}*rdc;
//reader process
void reader(int i)
{
sleep(DELAY);
printf("This is process %d,and there are %d are reading\n",i,rdc->read);
}
//writer process
void writer()
{
sleep(DELAY);
printf("This is the writing process\n");
}
void wait(int semid,int i)
{
struct sembuf sb;
sb.sem_num = i;
sb.sem_op = -1;
sb.sem_flg = SEM_UNDO;
if(semop(semid,&sb,1) < 0)
{
perror("Wait Failed");
exit(1);
}
}
void signal(int semid,int i)
{
struct sembuf sb;
sb.sem_num = i;
sb.sem_op = 1;
sb.sem_flg = SEM_UNDO;
if(semop(semid,&sb,1) < 0)
{
perror("Signal Failed");
exit(1);
}
}
void init() //initialization
{
//share readcount
if((shmId = shmget(IPC_PRIVATE,SHM_SIZE,SHM_MODE)) < 0)
{
perror("Create Chared Memory Failed");
exit(1);
}
rdc = (struct readcount*)shmat(shmId,0,0);
rdc->read= 0;
semid = semget(IPC_PRIVATE,3,IPC_CREAT | 0666);
if(semid < 0)
{
perror("Create Semaphore Failed");
exit(1);
}
union semun sem;
sem.val = 1;
//exclusive read
if(semctl(semid,0,SETVAL, sem) < 0)
{
perror("Semctl Failed");
exit(1);
}
sem.val = 0;
//readcount count
if(semctl(semid,1,SETVAL, sem) < 0)
{
perror("Semctl Failed");
exit(1);
}
sem.val = 1;
//source
if(semctl(semid,2,SETVAL, sem) < 0)
{
perror("Semctl Failed");
exit(1);
}
}
int main()
{
init();
int i=-1,child=-1;
for(i=0;i<n;i++)
{
if((child = fork()) < 0)
{
perror("Fork Failed");
exit(1);
}
else if(child == 0)
{
printf("This is the kid %d , pid = %d , ppid = %d\n",i,getpid(),getppid());
while(1)
{
Wait(semid,0);
if(rdc->read==0)
Wait(semid,2);
rdc->read = rdc->read + 1;
Signal(semid,0);
reader(i);
Wait(semid,0);
rdc->read--;
printf("%d process have read\n",i);
if(rdc->read==0)
Signal(semid,2);
Signal(semid,0);
sleep(2);
}
break;
}
}
if(child > 0) //parent process
{
while(1)
{
Wait(semid,2);
writer();
Signal(semid,2);
}
}
return 0;
}