操作系统原理与Linux_进程管理实验报告

计算机科学与技术系

实 验 报 告

课程名称:___操作系统原理与Linux___

实验名称:___ 进程管理      _______

班    级:____计算机08-2           

学    号:    08034050217         

姓    名:      XXXX             

20##年  03 月  23日

实验二 进程管理

.   实验目的:

(1)加深对进程概念的理解,明确进程和程序的区别。

(2)进一步认识并发执行的实质。

(3)分析进程竞争资源现象,学习解决进程互斥的方法。

二.    实验内容:

1、进程创建;

2、进程控制。

.   实验作业:

1、调试下面进程控制源程序:试观察纪录屏幕上的显示结果,并分析原因。

〈程序1〉源代码

#include<stdio.h>

main()

{

  int p1,p2,i;

  if(p1=fork())

{

               for(i=0;i<500;i++)

                 printf("parent%d\n",i);

         wait(0); /* 保证在子进程终止前,父进程不会终止*/

                exit(0);

}

  else

  {

        if(p2=fork())

                {

             for(i=0;i<500;i++)

                 printf("son %d\n",i);

          wait(0); /* 保证在子进程终止前,父进程不会终止*/

                 exit(0); /*向父进程信号0且该进程推出*/

}

        else

                {

             for(i=0;i<500;i++) 

                printf(“grandchild %d\n",i);

                exit(0);

}

  }

}

〈运行结果〉:

……

……

……

结果分析:

    进程的并发执行的输出结果具有不可再现性,fork()创建进程所需的时间多于输出字符串的时间。

2、修改上面源代码,将每个进程输出一个字符改为每个进程输出一句话,在观察程序执行时屏幕出现的现象,并分析原因。如果在程序中使用调用lockf()来给每一个子进程加锁,可以实现进程之间的互斥,观察并分析出现的现象。

〈程序代码〉

#include<stdio.h>

main()

{

  int p1,p2,i;

  if(p1=fork())

{

               for(i=0;i<500;i++)

                 printf("parent%d\n",i);

         wait(0); /* 保证在子进程终止前,父进程不会终止*/

                exit(0);

}

  else

  {

        if(p2=fork())

                {

             for(i=0;i<500;i++)

                 printf("son %d\n",i);

          wait(0); /* 保证在子进程终止前,父进程不会终止*/

                 exit(0); /*向父进程信号0且该进程推出*/

}

        else

                {

             for(i=0;i<500;i++) 

                printf(“grandchild %d\n",i);

                exit(0);

}

  }

}

〈运行结果〉

    parent….

    son…

    grandchild…

    grandchild…

或  grandchild

    …son

    …grandchild

    …son

    …parent

    分析:由于函数printf()输出的字符串之间不会被中断,因此,每个字符串内部的字符顺序输出时不变。但是 , 由于进程并发执行时的调度顺序和父子进程的抢占处理机问题,输出字符串的顺序和先后随着执行的不同而发生变化。这与打印单字符的结果相同。

〈程序代码〉

#include<stdio.h>

main()

{

       int p1,p2,i;

       if(p1=fork())

       {

          lockf(1,1,0);

          for(i=0;i<500;i++) 

                     printf("parent %d\n",i);

          lockf(1,0,0);

        wait(0); /* 保证在子进程终止前,父进程不会终止*/

        exit(0);

}

       else

      {

             if(p2=fork())

                     {

                     lockf(1,1,0);

                 for(i=0;i<500;i++)

 printf("son %d\n",i);

                  lockf(1,0,0);

         wait(0); /* 保证在子进程终止前,父进程不会终止*/

                exit(0);

                    }

              else

              {

               lockf(1,1,0);

                 for(i=0;i<500;i++)

 printf("daughter %d\n",i);

           lockf(1,0,0);

        exit(0);

              }

        }

}

<运行结果〉

输出parent块,son块,grandchild块的顺序可能不同,但是每个块的输出过程不会被打断。

分析:因为上述程序执行时,lockf(1,1,0)锁定标准输出设备,lockf(1,0,0)解锁标准输出设备,在lockf(1,1,0)与lockf(1,0,0)中间的for循环输出不会被中断,加锁与不加锁效果不相同。

.   实验心得:

本次实验主要实现Linux操作系统中进程管理的功能。了解了进程的创建、执行特定任务、终止、和同步的相关系统调用。理解了fork、exec、wait、exit3个系统调用的使用。

 

第二篇:20xx操作系统实验指导书

《 操作系统》

实验指导书

适用专业:   计算机科学与技术

      网络工程   

      软件工程   

                                                  

   太原科技大学计算机科学与技术学院

    20##年 3 月

  

操作系统是计算机的核心和灵魂。操作系统软件的设计对整个计算机的功能和性能起着至关重要的作用,所以此门课也是必不可少的,是面向计算机科学与技术、网络工程、软件工程等大多数计算机专业本科生开设的一门计算机专业课程。

操作系统是计算机系统的核心,《操作系统》课程是计算机科学与技术专业的重要必修课。本课程的目的是使学生掌握现代计算机操作系统的基本原理、基本设计方法及实现技术,具有分析现行操作系统和设计、开发实际操作系统的基本能力。

操作系统实验是操作系统课程的重要组成部分,属于学科基础实验范畴。作为与相关教学内容配合的实践性教学环节,应在操作系统理论课教学过程中开设。

操作系统是计算机科学与技术专业必修的专业基础课程,操作系统实验的作用是:理解操作系统的设计和实现思路,掌握典型算法。基本要求是:理解进程的概念,理解死锁,掌握银行家算法;掌握请求页式存储管理的实现原理及页面置换算法。学生应具有高级语言编程能力、具有数据结构等基础知识。


 

实验一  windows进程的创建... 1

实验二  windows并发多线程的应用... 4

实验三  P、V 操作的模拟实现... 7

实验四   银行家算法模拟... 13

实验五  虚拟存储管理中页面置换算法模拟... 16


实验一     windows进程的创建

实验学时:2

实验类型:验证

实验要求:必修

一、实验目的

本课题实习的目的是,加深对wiundows进程概念及进程管理各部分内容的理解;熟悉windows进程管理API的使用。

二、实验要求

1.         将源程序编译、链接后形成master.exe和slave.exe文件。

2.         在命令行方式下输入……> master  slave回车,将在master进程中创建slave进程,观察程序运行的结果。

3.         自己设计一个小程序,完成在master进程中启动该程序的操作。

4.         撰写上机报告。

三、实验内容

下面程序是两个简单的控制台应用程序,第一个程序( MASTER )运行第二个程序( SLAVE ) , 并进入睡眠。 SLAVE 程序从命令行读取MASTER程序的进程 ID(PID), 并等待 MASTER 程序终止之后,SLAVE做了一些事情之后,也结束。这些程序用到了以下几个重要技术:

1.         使用 CreateProcess

 CreateProcess 函数

⑴ 函数原型:

BOOL CreateProcess( LPCTSTR lpApplicationName , LPTSTR lpCommandLine , LPSECURITY_ATTRIBUTES lpProcessAttributes , LPSECURITY_ATTRIBUTES lpThreadAttributes ,BOOL bInheritHandles , DWORD dwCreationFlags , LPVOID lpEnvironment ,

LPCTSTR lpCurrentDirectory , LPSTARTUPINFO lpStartupInfo , LPPROCESS_INFORMATION lpProcessInformation );

⑵ 参数:

lpApplicationName :指向一个以空结尾的串,他指定了要执行的模块

lpCommandLine :指向一个以空结尾的串,该串定义了要执行的命令行。

lpProcessAttributes :指向一个 SECURITY_ATTRIBUTES 结构,该结构决定了返回的句柄是否可被子进程继承。

lpThreadAttributes :指向一个 SECURITY_ATTRIBUTES 结构,该结构决定了返回的句柄是否可被子进程继承。

bInheritHandles , : 表明新进程是否从调用进程继承句柄。

dwCreationFlags : 定义控制优先类和进程创建的附加标志。

lpEnvironment :指向一个新进程的环境块。

lpCurrentDirectory :指向一个以空结尾的串,该串定义了子进程的当前驱动器和当前目录。

lpStartupInfo :指向一个 STARTUPINFO 结构,该结构定义了新进程的主窗口将如何显示。

lpProcessInformation : 指向 PROCESS_INFORMATION 结构,该结构接受关于新进程的表示信息。

⑶ 返回值:

若函数调用成功,则返回值不为 0 ;若函数调用失败,返回值为 0 。

在上述参数中,参数 lpStartupInfo 是 STARTUPINFO 结构。可以用来设置控台的标题,新窗口的的初始大小和位置,及重定向标准输入和输出。新程序通常可以忽略多数这些数据项,如果选择那样做的话。可以规定该结构体中的标志,已表明要设置的数据段。有时,不想设置任何信息,也必须传递一个有效的指针给空结构(确定设置大小到 cb ,及设置 dwFlags 成员为 0 )。参数 lpProcessInformation 返回进程和线程句柄,还包括进程和线程 ID 。这些句柄拥有在参数 lpProcessAttributes 和 lpThreadAttributes 中规定的访问。

要注意,针对 CreateProcess 的一些参数对控制台应用程序是特定的,而其它参数则对各种应用程序有用。大多数情况下,并不一定要填入 STARTUPINFO 结构,但无论如何必须提供它。其返回值是布尔型的,而真正感兴趣的返回值发生于作为参数传送的结构中( PROCESS_INFORMATION )。 CreateProcess 返回该结构中的进程 ID 及其句柄,以及初始线程 ID 及其句柄。可以将 ID 发送到其它进程,或使用句柄来控制新进程。

2.         使用 WaitForSingleObject

WaitForSingleObject 的目的是要确定句柄是否处于发送信号的状态。当进程结束时,进程句柄发出信号。当调用 WaitForSingleObject 时,就规定进程句柄和超时值,如果超时为 0 ,则该命令就立刻返回,且能够确定进程的状态。如果超时是常数 INFINITE ,则命令就不返回,直到目标进程退出为止。当然,还可以规定超时值,其导致该命令等待要结束的进程一段时间。如果进程在超时届满前结束,该命令就返回,并指出句柄在发射信号状态。否则,就返回一个负值。不管句柄在何种状态, WaitForSingleObject 将成功返回,没有错误发生。要确定进程的状态,就必须比较返回值为 WAIT_OBJECT_0 (已发信号的)和 WAIT_TIMEOUT (未发信号的)。真正的错误返回值为 WAIT_FAILED 。另外可能的返回值是 WAIT_ABANDONED ,是不会看到何时处理进程。要等待一个进程,就必须带有 SYNCHRONIZE 特权的打开局柄。

这里要注意,进程 ID 与进程句柄不同。不能简单地在进程之间传送句柄,这意味着除非有句柄,否则不能从外部进程直接操纵一个进程。不过 OpenProcess 命令将允许任何程序(有足够的安全特权)将进程标示符(可以用来于其它进程通信)变换为进程句柄。通过调用 GetCurrentProcessId ,还可以了解当前进程标示符。如果要想与其他无关的进程共享,以使能够打开进程句柄,这是非常有用的。但调用 OpenProcess 时,可以请求对进程的访问。对每种进程的访问,也许有或也许没有访问要打开进程的安全性,于是试图请求是仅仅需要的。例如,如果要了解进程的返回代码,就需要 PROCESS_QUERY_INFORMATION 的访问。要终止进程,就必须有 PROCESS_TERMINATE 的访问。

3.         源程序清单:

//master.cpp

#include <windows.h>

#include <iostream.h>

#include <stdio.h>

#include <string.h>

void main(int argc,char *argv[])

{

char cmd[128];

if (argc!=1)

strcpy(cmd,argv[1]);

else

strcpy(cmd,"slave.exe");

int pid=GetCurrentProcessId(); cout<<"process ID:"<<pid<<endl;

cout<<"Master 准备启动:"<<cmd<<"\n";

sprintf(cmd+strlen(cmd), " %d",pid);

cout.flush();

STARTUPINFO info;

memset(&info,0,sizeof(info));

info.cb=sizeof(info);

PROCESS_INFORMATION pinfo;

if(!CreateProcess(NULL,cmd,NULL,NULL,FALSE,NORMAL_PRIORITY_CLASS,NULL,NULL,&info,&pinfo))

{

cout<<"Master: 从进程"<<cmd<<" 没有找到\n";

cout<<"Master:重新输入从进程名\n";

}

cout<<"Master:睡觉去喽……………………………………………………………………\n";

cout.flush();

Sleep(10000);

for(int i=1;i<5;i++)cout<<"睡醒了\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\n";

cout<<"Master:刷牙、洗脸、吃饭\n";

cout<<"Master:结束!!!!!\n";

exit(0);

}

//slave.cpp

#include <windows.h>

#include <iostream.h>

#include <stdio.h>

void main(int argc,char *argv[])

{

if (argc!=2)

{

cout<<"Slave:请重新运行MASTER.EXE .\n";

exit(1);

}

int pid=atoi(argv[1]);

HANDLE process=OpenProcess(PROCESS_QUERY_INFORMATION|SYNCHRONIZE,FALSE,pid);

if (!process) cout<<"Slave:Error opening process\n";

cout<<"Slave :我要等Master起床。\n";

cout.flush();

if (WaitForSingleObject(process,INFINITE)==WAIT_OBJECT_0)

cout<<"Slave:Master 结束了,该我玩了!!!!!!!!!!!!!!!!\n";

else

cout<<"Slave:出什么错了?\n";

for(int i=1;i<5;i++)

   cout<< i << " 我好高兴好高兴!!\n";

cout<<"Slave 我也该结束了,拜拜!\n";

exit(0);

}

实验二  windows并发多线程的应用

实验学时:4

实验类型:设计

实验要求:必修

一、实验目的

本课题实习的目的是,加深对wiundows线程概念及线程同步管理各部分内容的理解;熟悉windows线程管理API的使用

二、实验要求

1.通过上网查阅资料,了解windows线程同步函数,写出本程序中出现的API函数的定义;

2.阅读案例程序,给出程序的详细注解;

3.运行程序,分析程序结果。

4 .改写程序实现(至少选其中两个项目实现)

(1)《苹果桔子问题》:

    桌上有一只盘子,每次只能放入一只水果;爸爸专向盘子中放苹果(apple),妈妈专向盘子中放桔于(orange),一个儿子专等吃盘子中的桔子,一个女儿专等吃盘子里的苹果。

(2)在一个盒子里混装了数量相等的黑白围棋子,现在用自动分拣系统把黑白子分开,设分拣系统有两个线程P1、P2 ,其中P1拣白子、 P2拣黑子。当一个线程在拣时,不允许另一个线程去拣。当一个线程拣了一子后,必须让另一线程去拣。试写出两个线程能并发正确执行的程序。

(3)设有线程A,B,C,分别调用过程get,copy和 put 对缓冲区S和T进行操作。其中get负责把数据块输入缓冲区S,copy负责从S中提取数据块并复制到缓冲区T中,put负责从缓冲区T中取出信息打印(如图所示)。编程序实现get,copy和 put并发的操作过程。

(4)编程序实现生产者—消费者问题。

5.         撰写上机报告。

三、实验内容  《苹果香蕉问题》范例

    以下程序实现多线程同步,其关系如下:父亲、儿子、女儿三人和一个盘子,当盘子空时,父亲往盘中随机放苹果或香蕉,儿子只从盘中拿苹果,女儿只从盘中拿香蕉。

#include <iostream>

using namespace std;

#include <windows.h>

#include <time.h>

int k;

HANDLE Apple_;HANDLE Banana_;

CRITICAL_SECTION mmutex;

DWORD WINAPI Son(LPVOID n)

{//HANDLE Apple_;CRITICAL_SECTION mmutex;

int i = 1;

OpenSemaphore(MUTEX_ALL_ACCESS,false,"Apple_");

while (1)

{

::WaitForSingleObject(Apple_,INFINITE);//等苹果

cout<<"Son eats "<<i<<" apples"<<endl;

LeaveCriticalSection(&mmutex);

i++;

}

::CloseHandle(Apple_);

return 0;

}

DWORD WINAPI Daughter(LPVOID n)

{

int i = 1;//HANDLE Banana_;CRITICAL_SECTION mmutex;

OpenSemaphore(MUTEX_ALL_ACCESS,false,"Banana_");

while (1)

{

::WaitForSingleObject(Banana_,INFINITE);//等香蕉

cout<<"Daughter eats "<<i<<" bananas"<<endl;

LeaveCriticalSection(&mmutex);

i++;

}

::CloseHandle(Banana_);

return 0;

}

DWORD WINAPI Father(LPVOID n)

{

UINT fruit;//CRITICAL_SECTION mmutex;

EnterCriticalSection(&mmutex);

fruit = rand()%2;

if (fruit == 0)

{

//盘中放入苹果

cout<<k+1<<" father produce an apple"<<endl;

k=k+1;

::ReleaseSemaphore(Apple_,1,NULL);

}

else {//盘中放入香蕉

cout<<k+1<<" father produce a banana"<<endl;

k=k+1;

::ReleaseSemaphore(Banana_,1,NULL);

}

return 0;

}

int main()

{

int j;

k=0;

HANDLE Father_[20];

Apple_ = ::CreateSemaphore(NULL,0,1,"apple");

Banana_ =::CreateSemaphore(NULL,0,1,"banana");

InitializeCriticalSection(&mmutex);

srand(time(NULL));

for (j= 0 ; j < 20; j++)

{

Father_[j]=::CreateThread(NULL,0,Father,NULL,0,0);

}

::CreateThread(NULL,0,Son,NULL,0,0);

::CreateThread(NULL,0,Daughter,NULL,0,0);

 Sleep(1000);

WaitForMultipleObjects(20,Father_,TRUE,INFINITE);

return 0;

}

实验三  P、V 操作的模拟实现

实验学时:2

实验类型:验证

实验要求:必修

一、实验目的

本课题实习的目的是,加深对进程概念及进程管理各部分内容的理解;熟悉进程管理中主要数据结构的设计及进程调度算法,进程控制机构、同步结构、通迅机构的实施。

要求设计一个允许n个进程并发运行的进程管理模拟糸统。该糸统包括有简单的进程控制、同步及通迅机构,其进程调度算法可任意选择。每个进程用一个PCB表示,其内容可根据具体情况设置。各进程之间应有一定的同步关糸。糸统在运行过程中应能显示或打印各进程的状态及有关参数的变化情况,以便观察诸进程的运行过程及糸统的管理过程。

1) 理解信号量相关理论;

2) 掌握记录型信号量结构;

3) 掌握 P、V 原语实现机制。

二、实验要求

1) 输入给定代码;

2) 进行功能测试并得出正确结果。

3) 分析P和V函数功能模块;

4) 在实验报告中画出P和V函数流程图;

5) 撰写上机报告。

三、实验内容

本实验针对操作系统中信号量相关理论进行实验,要求实验者输入实验指导书提供的代码并进行测试。代码主要模拟信号量的 P 操作和 V 操作。

1) 信号量

信号量也称为信号锁,主要应用于进程间的同步和互斥,在用于互斥时,通常作为资源锁。信号量通常通过两个原子操作P和 V来访问。P操作使信号量的值+1,V操作使信号量的值-1。

2) 记录型信号量

记录型信号量采用了“让权等待”的策略,存在多个进程等待访问同一临界资源的情况,所以记录型信号量需要一个等待链表来存放等待该信号量的进程控制块或进程号。在本实验中,使用记录型信号量。

四、范例

1.例: 支持多个进程并发运行的简单进程管理模拟糸统。

本糸统的同步机构采用的信号量上的P、V操作的机制;控制机构包括阻塞和唤醒操作;时间片中断处理程序处理模拟的时间片中断;进程调度程序负责为各进程分配处理机。糸统中设计了1个并发进程。它们之间有如下同步关糸:1个进程需要互斥使用临界资源S2,进程1和进程2又需要互斥使用临界资源S1.本糸统在运行过程中随机打印出各进程的状态变换过程,糸统的调度过程及公共变量的变化情况。

2.算法

糸统为过程设置了4种运行状态:e------执行态;r-----高就绪态;t------低就绪态(执行进程因时间片到限而转入);w------等待态;c------完成态。各进程的初始态均设置为r.

糸统分时执行各进程,并规定1个进程的执行概率均为11%。通过产生随机数x模拟时间片。当进程process1访问随机数x时,若x.>=0.11;当进程process2访问x时,若x<0.11或x>=0.66;当进程process1访问x时。若x<0.66,则分别认为各进程的执行时间片到限,产生“时间片中断”而转入低就绪态t.

进程调度算法采用剥夺式最高优先数法。各进程的优先数通过键盘输入予以静态设置。调度程序每次总是选择优先数量小(优先权最高)的就绪态进程投入执行。先从r状态进程中选择,再从t状态进程中选择。当现行进程唤醒某个等待进程,而被唤醒进程的优先数小于现行进程时,则剥夺现行进程的执行权。

各进程在使用临界资源S1和S2时,通过调用信号量sem1和sem2上的P、V操作来实现同步。阻塞和唤醒操作和负责完成从进程的执行态到等待态以及从等待态到就绪态的转换。

糸统启动后,在完成必要的糸统初始化后便执行进程调度程序。当执行进程因“时间片中断”,或被排斥使用临界资源,或唤醒某个等待进程时,立即进行进程调度。当1个进程都处于完成状态后,糸统退出运行。

图1和图2分别示出了糸统主控程序和进程调度程序的大致流程。

 

1、数据结构

(1)每个进程有一个进程控制块PCB,内容包括:

Id        进程标识数,id=0,1,2;

Status     进程状态,可为e,r,t,w,c;

Priority    进程优先数;

Nextwr    等待链指针,指示在同一信号量上等待的下一个进程的标识数。

(2)信号量semaphore,对应于临界资源s1和s2分别有sem1和sem2,均为互斥信号量,内容包括:

     Value      信号量值,初值为1;

     Firstwr     等待链首指针,指示该信号量上第一个等待进程的标识数。

(1)现场保留区,用数组savearea[1][3]表示。即每个进程都有一个大小为3个单元的保留区,用来保存被“中断”时的现场信息,如通用寄存器的内容和断点地址等。

此外,糸统中还用到下列主要全程变量:

  Exe   执行进程指针,其值为进程标识数;

    I      用来模拟一个通用寄存器;

五、程序源代码


#include<stdio.h>

#define TRUE 1

#define FALSE 0

#define MAXPRI 100

#define NIL -1

struct {

         int id;

         char status;

         int nextwr;

         int priority;

}pcb[1];

struct {

         int value;

         int firstwr;

} sem[2];

char savearea[1][3],addr;

int i,s1,s2,seed,exe=NIL;

init( )

{  int j;

    for (j=0;j<1;j++)

         {

         pcb[j].id=j;

         pcb[j].status='r';

         pcb[j].nextwr=NIL;

         printf("\n process%d priority?",j+1);

         scanf ("%d",&i);

         pcb[j].priority=i;

         }

         sem[0].value=1;sem[0].firstwr=NIL;

         sem[1].value=1;sem[1].firstwr=NIL;

         for (i=1;i<1;i++)

                   for (j=0;j<3;j++)

                            savearea[i][j]='0';

}

float random ( )

{int m;

    if (seed<0)

                   m=-seed;

         else m=seed;

         seed=(24151*seed+11839)%64416;

         return(m/12565.0);

}

timeint(char addr)

{

float x;

x=random();

if ((x<0.11)&&(exe==0))return(FALSE);

if ((x<0.66)&&(exe==1))return(FALSE);

if ((x<1.0)&&(exe==2))return(FALSE);

savearea[exe][0]=i;

savearea[exe][1]=addr;

pcb[exe].status='t';

printf("Times silce interrupt'\nprocess%d enter into ready.\n", exe+1);

exe=NIL;

return (TRUE);

}

find()

{

int j,pd=NIL, w=MAXPRI;

for (j=0;j<1;j++)

if (pcb[j].status=='r')

if (pcb[j].priority<w){

         w=pcb[j].priority; pd=j;

}  

if (pd==NIL)

for (j=0; j<1;j++)

if (pcb[j].status=='t')

if (pcb[j].priority<w)

 {

         w=pcb[j].priority; pd=j;

}

   return(pd) ;

}

scheduler()

{

         int pd;

    if((pd=find())==NIL&& exe==NIL)

         return(NIL);

    if (pd!=NIL)  {

         if(exe==NIL)

         {

                   pcb[pd].status='e';

         exe=pd;

         printf("process%d is executing.\n", exe+1);

}

else if (pcb[pd].priority<pcb[exe].priority) {

         pcb[exe].status='r';

                   printf("process%d enter into ready\n", exe+1);

         pcb[pd].status='e';

         exe=pd;

         printf ("process%d enter into ready\n", exe+1);

}

}

i=savearea[exe][0];

addr=savearea[exe][1];

return (exe);

}

block(int se)

{

         int w;

         printf("process%d is blocked\n", exe+1);

                   pcb[exe].status='w';

             pcb[exe].nextwr=NIL;

         if ((w=sem[se].firstwr)==NIL)

                   sem[se].firstwr=exe;

         else {

             while(pcb[w].nextwr!=NIL)

                            w=pcb[w].nextwr;

                   pcb[w].nextwr=exe;

         }

}

p(int se,char ad)

{

         if (--sem[se].value>=0) return(FALSE);

         block(se);

         savearea[exe][0]=i;

         savearea[exe][1]=ad;

         exe=NIL;

         return(TRUE);

}

wakeup(int se)

{

int w;

w=sem[se].firstwr;

if(w!=NIL){

   sem[se].firstwr=pcb[w].nextwr;

   pcb[w].status='r';

   printf("process%d is waken up\n",w+1);

}

}

v(int se,char ad)

{

if(++sem[se].value>0)   return(FALSE);

wakeup(se);

savearea[exe][1]=ad;

savearea[exe][0]=i;

return(TRUE);

}

eexit(int n)

{

         pcb[n].status='c';

         printf("process%d is completed!\n", n+1);

    exe=NIL;

}

processl()

{

    if(addr=='a')goto a1;

    if(addr=='b')goto b1;

    if(addr=='c')goto c1;

    if(addr=='d')goto d1;

    if(addr=='e')goto e1;

    if(addr=='f')goto f1;

         for(i=1;i<6;i++)

   {

     printf("process1 calls P on the semaphore1\n");

       if(p(0,'a'))break;

    a1:printf("process1 is executing in the cretical section 1\n");

     if(timeint('b'))  break;

    b1:printf("s1=%d\n",++s1);

      printf("process1 calls V on semaphorel and quit cretical section1\n");

      if(v(0,'c'))break;

    c1:printf("process1 calls P on esemaphorel 2\n");

      if(p(1,'d'))break;

   d1:printf("process1 is execting cretical section 2\n");

     if(timeint('e'))break;

   e1:printf("s2=%d\n",++s2);

   printf("process1 calls V on semaphore2 and quit cretical section2\n");

if(v(1,'f'))break;

  f1:printf("process1 cycle count=%d\n",i);

}

  if(i<6)   return;

  eexit(0);

}

process2()

{

         if(addr=='a')goto a2;

         if(addr=='b')goto b2;

         if(addr=='c')goto c2;

         if(addr=='d')goto d2;

         if(addr=='e')goto e2;

         if(addr=='f')goto f2;

         for(i=1;i<6;++i){

printf("process2 calls P on the semaphore2\n");

if(p(1,'a'))break;

a2:printf("process2 is executing in the cretical section2\n");

if(timeint('b'))break;

b2:printf("s2=%d\n",++s2);

printf("process2 calls V on semaphore2 and quit cretical section2.\n");

if(v(1,'c'))break;

c2:printf("process2 calls P on esemaphore1.\n");

if(p(0,'d'))break;

d2:printf("process2 is execting cretical section1.\n");

if(timeint('e'))break;

e2:printf("s1=%d\n",++s1);

printf("process2 calls V on semaphore1 and quit cretical section1.\n");

if(v(0,'f'))break;

f2:printf("process2 cycle count=%d\n",i);

}

  if(i<6)  return;

eexit(1);

}

process1()

{

if (addr=='a') goto a1;

if (addr=='b') goto b1;

if (addr=='c') goto c1;

for (i=1;i<6; ++i){

         printf("process1,calls P on semaphore2\n");

         if(p(1,'a')) break;    /// * process 1 is biocked * /

a1: printf("process 1 is execcuting on its cretical section\n");

         if(timeint('b')) break;

b1: printf ("s2=%d\n", ++s2);

    printf ("process1 calls V on semapore2 and quit cretical section\n");

         if(v(1,'c')) break; // * wake up a biocked process * /

c1: printf("process1 cycien count=%d\n",i);

}

    if(i<6) return;

         eexit(2);

}

main ( )

{

         int k;

         printf ("* * * * process management * * * * \n\n");

         init();

         printf("s1=%d, s2=%d\n", s1, s2);

         printf("process1, process2, process1 are all in ready ! \n");

         for (  ;  ;)

                   if ((k=scheduler())!=NIL)

                   switch(k)

                   {

                               case 0: process1();

                                  break;

                               case 1: process2();

                                        break;

                               case 2: process1();

                                        break;

                               default: printf("process identifer error\n");

                                        break;

                   }

                   else break;

                   printf ("s1=%d, s2=%d\n", s1, s2);

                   printf ("\n * * * * END * * * * \n");


六、思考

1) 如何修改 P 操作,使之能一次申请多个信号量?

2)在某个时刻,一个进程最多可以等待多少个信号量?

实验四   银行家算法模拟

实验学时:2

实验类型:设计

实验要求:必修

一、实验目的

(1)进一步理解利用银行家算法避免死锁的问题;

(2)在了解和掌握银行家算法的基础上,编制银行家算法通用程序,将调试结果显示在计算机屏幕上,再检测和笔算的一致性。

(3)理解和掌握安全序列、安全性算法

二、实验内容

(1)了解和理解死锁;

(2)理解利用银行家算法避免死锁的原理;

(3)会使用某种编程语言。

(4)撰写上机报告。

三、实验原理

    (一)安全状态

指系统能按照某种顺序如<P1,P2,…,Pn>(称为<P1,P2,…,Pn>序列为安全序列),为每个进程分配所需的资源,直至最大需求,使得每个进程都能顺利完成。

(二)银行家算法

假设在进程并发执行时进程i提出请求j类资源k个后,表示为Requesti[j]=k。系统按下述步骤进行安全检查:

(1)如果Requesti≤Needi则继续以下检查,否则显示需求申请超出最大需求值的错误。

(2)如果Requesti≤Available则继续以下检查,否则显示系统无足够资源,Pi阻塞等待。

(3)系统试探着把资源分配给进程Pi,并修改下面数据结构中的数值:

  Available[j]∶=Available[j]-Requesti[j];

  Allocation[i,j]∶=Allocation[i,j]+Requesti[j];

  Need[i,j]∶=Need[i,j]-Requesti[j];

(4)系统执行安全性算法,检查此次资源分配后,系统是否处于安全状态。若安全,才正式将资源分配给进程Pi,以完成本次分配;否则, 将本次的试探分配作废,恢复原来的资源分配状态,让进程Pi等待。

(三)安全性算法

(1)设置两个向量:

① 工作向量Work: 它表示系统可提供给进程继续运行所需的各类资源数目,它含有m个元素,在执行安全算法开始时,Work∶=Available;

    ② Finish: 它表示系统是否有足够的资源分配给进程,使之运行完成。开始时先做Finish[i]∶=false; 当有足够资源分配给进程时, 再令Finish[i]∶=true。

(2)从进程集合中找到一个能满足下述条件的进程:

    ① Finish[i]=false;

    ② Need[i,j]≤Work[j]; 若找到, 执行步骤(3), 否则,执行步骤(4)。

(3)当进程Pi获得资源后,可顺利执行,直至完成,并释放出分配给它的资源,故应执行:

Ø   Work[j]∶=Work[i]+Allocation[i,j];

Ø    Finish[i]∶=true;

Ø    go to step 2;

(4)如果所有进程的Finish[i]=true都满足, 则表示系统处于安全状态;否则,系统处于不安全状态。

四、实验算法

(1)参考图1-1所示流程图编写安全性算法。

(2)编写统一的输出格式。

       每次提出申请之后输出申请成功与否的结果。如果成功还需要输出变化前后的各种数据,并且输出安全序列。

(3)参考图1-2所示流程图编写银行家算法。

(4)编写主函数来循环调用银行家算法。

 


五、思考题

   (1)在编程中遇到了哪些问题?你是如何解决的?

(2)在安全性算法中,为什么不用变量Available,而又定义一个临时变量work?

实验五 虚拟存储管理中页面置换算法模拟

实验学时:2

实验类型:设计

实验要求:必修

一、实验目的

(1)了解内存分页管理策略

(2)掌握调页策略

(3)掌握一般常用的调度算法

(4)学会各种存储分配算法的实现方法。

(5)了解页面大小和内存实际容量对命中率的影响。

二、实验内容

(1)采用页式分配存储方案,通过分别计算不同算法的命中率来比较算法的优劣,同时也考虑页面大小及内存实际容量对命中率的影响;

(2)实现OPT 算法 (最优置换算法) 、LRU 算法 (Least Recently) 、 FIFO 算法 (First IN First Out)的模拟;

(3)会使用某种编程语言。

(4)撰写实验报告。

三、实验原理

分页存储管理将一个进程的逻辑地址空间分成若干大小相等的片,称为页面或页。

在进程运行过程中,若其所要访问的页面不在内存而需把它们调入内存,但内存已无空闲空间时,为了保证该进程能正常运行,系统必须从内存中调出一页程序或数据,送磁盘的对换区中。但应将哪 个页面调出,须根据一定的算法来确定。通常,把选择换出页面的算法称为页面置换算法(Page_Replacement Algorithms)。

一个好的页面置换算法,应具有较低的页面更换频率。从理论上讲,应将那些以后不再会访问的页面换出,或将那些在较长时间内不会再访问的页面调出。

1、最佳置换算法OPT(Optimal)

它是由Belady于1966年提出的一种理论上的算法。其所选择的被淘汰页面,将是以后永不使用的或许是在最长(未来)时间内不再被访问的页面。采用最佳置换算法,通常可保证获得最低的缺页率。但由于人目前还无法预知一个进程在内存的若干个页面中,哪一个页面是未来最长时间内不再被访问的,因而该算法是无法实现的,便可以利用此算法来评价其它算法。

2、先进先出(FIFO)页面置换算法

这是最早出现的置换算法。该算法总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面予以淘汰。该算法实现简单只需把一个进程已调入内存的页面,按先后次序链接成一个队列,并设置一个指针,称为替换指针,使它总是指向最老的页面。

3、最近最久未使用置换算法

(1)LRU(Least Recently Used)置换算法的描述

FIFO置换算法性能之所以较差,是因为它所依据的条件是各个页面调入内存的时间,而页面调入的先后并不能反映页面的使用情况。最近最久未使用(LRU)置换算法,是根据页面调入内存后的使用情况进行决策的。由于无法预测各页面将来的使用情况,只能利用“最近的过去”作为“最近的将来”的近似,因此,LRU置换算法是选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间t,,当须淘汰一个页面时,选择现有页面中其t值最大的,即最近最久未使用的页面予以淘汰。

(2)LRU置换算法的硬件支持

     LRU置换算法虽然是一种比较好的算法,但要求系统有较多的支持硬件。为了了解一个进程在内存中的各个页面各有多少时间未被进程访问,以及如何快速地知道哪一页是最近最久未使用的页面,须有以下两类硬件之一的支持:

a)寄存器

       为了记录某个进程在内存中各页的使用情况,须为每个在内存中的页面配置一个移位寄存器,可表示为

       R=Rn-1Rn-2Rn-3……R2R1R0 当进程访问某物理块时,要将相应寄存器的Rn-1位置成1。此时,定时信号将每隔一定时间(例如100ms)将寄存器右移一位。如果我们把n位寄存器的数看作是一个整数,那么具有最小数值的寄存器所对应的页面,就是最近最久未使用的页面。如图1示出了某进程在内存中具有8个页面,为每个内存页面配置一个8位寄存器时的LRU访问情况。这里,把8个内存页面的序号分别定为1——=8。由图可以看出,第7个内存页面的R值最小,当发生缺页时首先将它置换出去。

b)栈

       可利用一个特殊的栈来保存当前使用的各个页面的页面号。每当进程访问某页面时,便将页面的页面号从栈中移出,将它压入栈顶。因此,栈顶始终是最新被访问页面的编号民,而栈底则是最近最久未使用的页面的页面号。

四、思考题

    (1)补充完成FIFO与OPT的算法。

(2)为什么在实际的系统中不用LRU置换算法,而用它的近似算法?

    (3)OPT算法为什么难以实现?

相关推荐