南 京 晓 庄 学 院
《操作系统》实验报告
指导老师:
专业班级:
学号 :
姓名 :
完成日期:
数学与信息技术学院
一、实验概述
1.实验目的
深入了解掌握进程的同步、互斥机制,认识理解其调度过程,并用于解决实际生产者/消费者问题。
使用高级编程语言现“生产者——消费者”进程同步问题。
2.实验目标
(1)对课本知识加以巩固,在实践中提高动手能力;
(2)完整的完成此次实验,并有所创新与突破;
(3)独立思考,充分利用网络资源与图书资源;
(4)取得一个被老师认可的高分评价;
二、实验工具
1.操作系统:windows2003 server enterprise
2.开发环境:Visual Studio 2010
三、实验过程
1、实验具体题目,分析题目:
本实验要求利用PV操作实现解决生产者——消费者问题中的同步问 题。此问题描述的是一群生产者进程在生产产品并将这些产品提供给消费者进程去消费,在两者之间设置了一个具有n个缓冲区的缓冲池,生产者进程将它所生产的产品放入一个缓冲区,消费者进程可从缓冲区中取走产品去消费,但它们之间必须保持同步,即不允许消费者进程到一个空缓冲区去取产品,也不允许生产者进程向一个已装满且尚未取出的缓冲区中投放产品,并且生产者消费者互斥使用缓冲区。
2、算法流程:
3、算法实现
主要用到生产者函数RP-ProceducerThread(void* p)来实现缓冲区产品数量的增加,用RP-ConsumerThread(void*p)来实现缓冲区产品的减少。并用到了CreateThread函数来创建生产者消费者线程,利用线程的句柄以及创建线程是立刻运行的特点来进行生产消费操作。至于PV算法的实现是利用buffer_empty和buffer_full来进行控制,buffer_empty的值可以看做资源量,只有空的数值大于0才可以进行生产,buffer_full的数值与buffer_empty的值有对应的关系,利用buffer_full来控制消费的进行。最后,在缓冲区操作临界资源PC_Buffer来说利用EnterCriticalSection( &PC_Buffer ); //等待共享资源LeaveCriticalSection( &PC_Buffer ) ; //释放共享资源来进行缓冲区操作的控制。总之当程序读入测试文件中的数据时,便根据读入的字符时C还是P创造相应的进程,在分配EMPTY或者FULL资源,然后等待共享资源PC_Buffer,得到后便进行操作。最后读入所有的数据,完成所有进程的操作。
4、需要解决的问题:
利用生产者进程进行生产,同时消费者进程也能进行消费,但是必须满足同步的条件才可以允许,否则将提示缓冲区满无法进行生产或者缓冲区空无法进行消费的错误,故程序应该具有判断的功能。若结束当前的生产者——消费者进程,将会提示此次进程中生产消费者分别生产了和消费的产品数目,并统计缓冲区中剩余的产品数目,最后才结束。
5、关键代码分析:
1、做出如下定义 CRITICAL_SECTION PC_Buffer ; //临界区
HANDLE h_semaphore_empty,h_semaphore_full; //信号量对象
struct Buffer
{
char buf[BUFFER_LEN];
}Buf[MAX_THREAD_NUM];
int in = 0 ; //生产者生产指针
int out = 0 ; //消费者消费指针
SYSTEMTIME timeSys;
CRITICAL_SECTION PC_Buffer ; //临界区
HANDLE h_semaphore_empty,h_semaphore_full; //信号量对象
struct ThreadInfo
{
int serial; //线程序号
char entity; //线程类别(P-生产者线程,C-消费者线程)
double delay; //生产产品/延续时间
double persist; //线程把产品加入到缓冲区/持续时间
};
2 ①CreateThread函数创建一个在调用进程的地址空间中执行的线程。此函数为API函数,用于创建生产者消费者进程。
②信号量对象(semaphore)
信号量对象实现了Dijkstra定义中的通用信号量语义。信号量对象就是资源信号量,初始值的取值在0到指定最大值之间,用于限制并发访问的线程数,也可用于进程、线程间的同步。它的相关API包括:CreateSemaphore、OpenSemaphore和ReleaseSemaphore。
(1)CreateSemapore函数是创建一个有名或者无名信号量对象,在输人参数中指定最大值和初值,返回对象句柄。
格式:HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpAttributes,
LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName );
1pAttributes:安全属性。如果是NULL就表示要使用默认属性。
1InitialCount:Semaphore的初值。必须≥0,并且≤MaximumCount。
lMaximumCount:Semaphore的最大值。这也就是在同一时间内能够锁住Semaphore之线程的最多个数。
1pName:Semaphore的名称(一个字符串)。任何线程(或进程)都可以根据这一名称引用到这个Semaphore。这个值可以是NULL,意思是产生一个没有名字的Semaphore。
返回值:如果成功就传回一个handle,否则传回NULL
③等待操作
Windows 2000为对象提供了两个统一的等待操作函数WaitForSingleObject和WaitForMultipleObjiects,等待函数被同步对象用于实现各种Dijkstra定义的P操作。等待的对象包括:Change notification(改变通告);Console input(控制台输入);Event(事件);Job(作业);Mutex(互斥对象);Process(进程);Semaphore(信号量);Thread(线程);Waitable timer(可等待定时器)。函数决定等待条件是否被满足。如果等待条件并没有被满足,调用线程进入一个高效的等待状态,当等待满足条件时占用非常少的处理器时间。在运行前,一个等待函数修改同步对象类型的状态。修改仅发生在引起函数返回的对象身上。例如,信号的计数减1。一个线程通过调用等待函数拥有对象。创建该对象的线程也拥有对象,而不需要调用等待函数。
(1)WaitForSingleObject函数可在指定的时间内等待指定对象为可用状态
当下列情况之一发生时该函数返回:(1)指定对象处于信号态;(2)超时。
格式:DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds );
hHandle:等待对象句柄。
dwMilliseconds:指定以毫秒为单位的超时间隔。如果超时,即使对象的状态是非信号态的并且没有完成,函数也返回。如果它是0,函数测试对象的状态并立刻返回;如果它是INFINITE(定义为0xFFFFFFFF或-1),函数从不超时。
返回值:如果函数调用成功,返回值表明引起函数返回的事件。可能值如下:
WAIT_ABANDONED:指定对象是互斥对象,在线程被终止前,线程没有释放互斥对象。互斥对象的所属关系被授予调用线程,并且该互斥对象被置为非信号态。
WAITOBJECT_0:指定对象的状态被置为信号态。
WAIT_TIMEOUT:超时,并且对象的状态为非信号态。
如果函数调用失败,返回值是WAIT_FAILED。
(2)WaitForMultipleObjects函数可在指定的时间内等待多个对象为可用状态。
格式:DWORD WaitForMultipleObjects( DWORD nCount, CONST HANDLE *lpHandles,
BOOL bWaitAll, DWORD dwMilliseconds );
nCount规定了可引起函数阻塞的一组对象的句柄数目。
lpHandles指向存放一组句柄的数组。
bWaitAll规定了是否函数应该等待一组对象都发送出有信号通知(bWaitAll=TRUE),或者只是等待一个对象(bWaitAll=FLASE)。
dwMilliseconds,它同在WaitForSingleObiect一样。
④临界区对象
临界区对象只能用于在同一个进程内使用的临界区,同一个进程内各线程对它的访问是互斥进行的,临界区对象的运行速度比互斥对象快。把变量说明为CRITICAL_SECTION类型,就可作临界区使用。相关的API包括InitializeCriticalSection、EnterCriticalSection、TryEnterCriticalSection、LeaveCriticalSection和DeleteCriticalSection。
(1)InitializeCriticalSection函数初始化临界区对象。
格式:VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
lpCriticalSection:指向临界区对象的指针。
(2)EnterCriticalSection函数是等待指定临界区对象的所有权。当调用线程被赋予所有权时,该函数返回。
格式:VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
lpCriticalSection:指向临界区对象的指针。
(3)LeaveCriticalSection函数释放指定临界区对象的所有权
格式:VOID LeaveCriticalSection (LPCRITICAL_SECTION lpCriticalSection) ;
1pCriticalSection:指向临界区对象的指针。
(4) DeleteCriticalSection释放与临界区对象相关的所有系统资源。
(5) 临界区对象的应用例
进程通过声明CRITICAL_SECTION类型的变量来完成分配临界区对象使用的存储空间,使用InitializeCriticalSection来初始化该临界区对象。线程在进入临界区之前使用EnterCriticalSection等待占用临界区的使用权,线程在退出临界区之后马上使用LeaveCriticalSection释放临界区的使用权。
利用临界区对象实现一个进程的各线程互斥的程序如下:
……
CRITICAL_SECTION PC_Buffer ;
struct ThreadInfo
{ …… };
void RP_ProceducerThread (void* p)
{
……
EnterCriticalSection( &PC_Buffer ); //等待共享资源
……(临界区) //生产者把产品加入到缓冲区
LeaveCriticalSection( &PC_Buffer ) ; //释放共享资源
……
}
int main(int argc, char* argv[])
{
HANDLE h_Thread;
DWORD thread_ID;
ThreadInfo thread_info[MAX_THREAD_NUM];
InitializeCriticalSection(&PC_Buffer);
……
h_Thread = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)(RP_ProceducerThread),
&thread_info[i], 0, &thread_ID);
……
}
⑤生产者消费者函数
void RP_ProceducerThread (void* p)
{
DWORD m_delay; //生产产品延续时间
DWORD m_persist; //把产品加入到缓冲区持续时间
int m_serial; //线程序号
DWORD wait_for_semaphore_empty; //等待资源信号量所有权
long count;
//从参数中获得信息
m_serial = ((ThreadInfo*)(p))->serial;
m_delay = (DWORD)(((ThreadInfo*)(p))->delay*INTE_PER_SEC) ;
m_persist = (DWORD)(((ThreadInfo*)(p))->persist*INTE_PER_SEC) ;
Sleep(m_delay); //生产产品
printtime("Producer","requests",m_serial);
wait_for_semaphore_empty = WaitForSingleObject(h_semaphore_empty,-1) ;
//等待资源信号量empty
EnterCriticalSection( &PC_Buffer ); //等待共享资源
printtime("Producer","begins",m_serial);
Sleep(m_persist); //生产者把产品加入到缓冲区
printtime("Producer","finishs",m_serial);
LeaveCriticalSection(&PC_Buffer) ; //释放共享资源
ReleaseSemaphore(h_semaphore_full,1,&count) ; //释放资源信号量full
}
void RP_ConsumerThread (void* p)
{
DWORD m_delay; //消费产品持续时间
DWORD m_persist; //从缓冲区取出产品持续时间
int m_serial; //消费线程序号
DWORD wait_for_semaphore_full; //等待资源信号量
long count;
//从参数中获得信息
m_serial = ((ThreadInfo*)(p))->serial;
m_delay = (DWORD)(((ThreadInfo*)(p))->delay*INTE_PER_SEC);
m_persist = (DWORD)(((ThreadInfo*)(p))->persist*INTE_PER_SEC) ;
Sleep(m_delay);
printtime("Consumer","requests",m_serial);
wait_for_semaphore_full = WaitForSingleObject(h_semaphore_full,-1) ;
//等待资源信号量full
EnterCriticalSection(&PC_Buffer); //等待共享资源
printtime("Consumer","begins",m_serial);
Sleep(m_persist); //消费者从缓冲区取出产品
printtime("Consumer","finishs",m_serial);
LeaveCriticalSection(&PC_Buffer); //释放共享资源
ReleaseSemaphore(h_semaphore_empty,1,&count) ; //释放资源信号量empty
Sleep(m_delay); //消费者消费产品
}
3.其它API函数
(1)Sleep函数对于指定的时间间隔挂起当前的执行线程。
格式:VOID Sleep(DWORD dwMilliseconds );
dwMilliseconds:定义挂起执行线程的时间,以毫秒(ms)为单位。取值为0时,该线程将余下的时间片交给处于就绪状态的同一优先级的其他线程。若没有处于就绪状态的同一优先级的其他线程,则函数立即返回,该线程继续执行。若取值为INFINITE则造成无限延迟。
返回值:该函数没有返回值。
(2)GetLocalTime函数取得当前的本地时间和日期。
格式:BOOL GetLocalTime (lpst);
lpst: 指向一个SYSTEMTIME结构,该结构存放当前的本地时间和日期。
typedef struct _SYSTEMTIME {
WORD wYear; WORD wMonth; WORD wDayOfWeek; WORD wDay;
WORD wHour; WORD wMinute; WORD wSecond; WORD wMilliseconds;
} SYSTEMTIME;
四、实验心得
1、实验结果。截图。
2、错误与分析。
该程序总体上来看比较简单,在调试过程中只要注意一些小问题,则不会出现问题了,除了少许语法错误外,控制好程序的走向,则逻辑错误亦可避免;
程序的总体思路很清晰,最主要的就是要实现两个条件判断,即缓冲区满的时候不允许生产者进行生产,若缓冲区空的话,则不进行消费者进行消费;在生产操作和消费操作之间进行相应的判断,正好符合PV信号量,先做P操作,若满足,则执行此进程,若不满足,则阻塞此进程,并做相应的V操作,即唤醒其对应的进程,从而很好的解决了生产者——消费者问题
3、心得体会;
在此次实验中我们模拟PV 操作同步机构,来解决生产者——消费者问 题。
此次实验完成了消费者与生产者这两个进程之间的同步协调问题。
值得注意的是解决进程同步需要做哪些工作,如何利用信号量机制来 解决进程同步问题等等,这些问题其实我们在学习理论知识时都是很少思考的,因为感触不深,所以学了一遍就过去了,但是在自己做实验时才会发现哪些地方是我们需要考虑的,哪些地方是需要注意的,实验给了我们实践的机会,给了我们理论结合实际的机 会,从实验中可以学到很多东西,不仅仅是书本上的东西这么简单,更重要的是对待事情严谨的态度,对待任何事情都要一丝不苟,细节决定成败!
附录:·主要代码
// publick.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <windows.h>
#define NULL 0
struct spcb
{
char name;
char state;
char why;
int dd ;
};
typedef struct spcb pcb;
pcb producter,consumer,*process,*process1;
int s1,s2,i,j,in,out,pc,m;
char array[10];
char c,x;
int pa[6],sa[6];
int p(int s) /* p操作原语 */
{
s=s-1;
if(s<0)
{
process->state='B'; /* B表示阻塞*/
process->why='s';
}
else
{
process->state='W'; /* W表示就绪*/
}
return(s);
}
int v(int s) /*v操作原语*/
{
s=s+1;
if(s<=0)
{
process1->state='W';
}
process->state='W';
return(s);
}
char RanChar()
{
char arr[10]={'a','b','c','d','e','f','g','h','i','j'};
return arr[abs(rand()%10)];
}
void put()
{
// printf("\n please product anychar!");
// scanf("\n%c",&c);
Sleep(1000);
array[in]=RanChar();
in=(in+1)%10;
printf(" product a char is %c!\n ",array[in-1]);
int k = 0;
for(int m=0;m<10;m++)
{
if (array[m]!=' ') {
printf("%c",array[m]);
k = k+1;
}
}
printf("缓冲池中有%d个产品\n",k);
}
void get()
{
Sleep(1000);
x=array[out];
printf("\n%c get a char fron buffer",x);
printf("\n");
array[out]=' ';
out=(out+1)%10;
int k = 0;
for(m=0;m<10;m++)
{
if (array[m]!=' ') {
printf("%c",array[m]);
k = k+1;
}
}
printf("缓冲池中有%d个产品\n",k);
}
void gotol()
{
pc=0;
}
void nop()
{;}
void disp() /*建立进程显示函数,用于显示当前进程*/
{
printf("\n name \t state \t why \t dd \n");
printf("|%c\t",process->name);
printf("|%c\t",process->state);
printf("|%c\t",process->why);
printf("|%d\t",process->dd);
printf("\n");
}
void init()/*初始化程序*/
{
s1=10;/*s1表示空缓冲区的数量*/
s2=0; /*s2表示满缓冲区的数量*/
producter.name='p';/*对生产者进程初始化*/
producter.state='W';
producter.why=' ';
producter.dd=0;
consumer.name='c';/*对消费者进程初始化*/
consumer.state='W';
consumer.why=' ';
consumer.dd=0;
for(int k=0;k<10;k++)
{
array[k] = ' ';
}
}
void bornpa() /*将生产者程序装入pa[]中*/
{
for(i=0;i<=3;i++)
{
pa[i]=i;
}
}
void bornsa()/*将消费者程序装入sa[]中*/
{
for(i=0;i<=3;i++)
{
sa[i]=i;
}
}
void diaodu()/*处理器调度程序*/
{
while((producter.state=='W')||(consumer.state=='W'))
{
x=rand();/*x随机获得一个数*/
x=x%2;/*对X取于*/
if(x==0)/*若X等于零,则执行生产者进程,反之执行消费者进程*/
{
process=&producter;/*process表示现行进程,将现行进程置为生产者进程*/
process1=&consumer;
}
else
{
process=&consumer;
process1=&producter;
}
pc=process->dd;
i=pc;/*此时把PC的值付给I*/
if((process->name=='p')&&(process->state=='W'))/*执行生产者进程且该进程处于 就绪状态*/
{
j=pa[i];
pc=i+1;
switch(j)
{
case 0: s1=p(s1);process->dd=pc;break;/*申请资源,若没有则跳转*/
case 1: put();process->state='W';process->dd=pc;break;
case 2: s2=v(s2);process->dd=pc;break;
case 3: gotol();process->state='W';process->dd=pc;
}
}
else if((process->name=='c') && (process->state=='W'))/*执行消费者进程且该进程处于就绪状态*/
{
process->state='W';
j=sa[i];
pc=i+1;
switch(j)
{
case 0: s2=p(s2);process->dd=pc;break;/*申请资源,若没有申请到则跳转*/
case 1: get();process->dd=pc;break;
case 2: s1=v(s1);process->dd=pc;break;
case 3: gotol();process->state='W';process->dd=pc;
}
}
}
printf("\nThe program is over!\n");
}
void main()
{
init();
bornpa();
bornsa();
diaodu();