资源描述
资料内容仅供您学习参考,如有不当之处,请联系改正或者删除。
《操作系统》
实 验 指 导 书
绍兴文理学院计算机系
前 言
1.实验总体目标
经过学生自己动手设计实验验证理论知识, 使学生掌握操作系统特征和功能, 掌握不同调度算法下进程的调度、 进程控制、 进程调度与死锁, 并必须掌握作业管理、 存储器管理、 设备管理和文件管理的主要原理。加深对操作系统基本原理理解。
⒉ 适用专业
计算机科学与技术
⒊ 先修课程
C语言程序设计、 计算机组成原理、 数据结构
⒋ 实验课时分配
序号
实验名称
学时
实验要求
实验类型
1
分析操作系统所面临的操作需求
2
必修
验证
2
进程管理
4
必修
设计
3
存储管理
4
必修
设计
4
设备管理
2
必修
设计
5
文件管理
4
必修
设计
⒌ 实验环境
有70台中等配置的计算机组成的小型局域网的实验室环境。计算机的具体要求: (1)Pentium 133Hz以上的CPU; (2)建议至少256MB的内存; (3)建议硬盘至少2GB, 并有1GB空闲空间。(4)安装Windows操作系统及C语言编译程序或Linux虚拟环境。
⒍ 实验总体要求
培养计算机专业的学生的系统程序设计能力, 是操作系统课程的一个非常重要的环节。经过操作系统上机实验, 能够培养学生程序设计的方法和技巧, 提高学生编制清晰、 合理、 可读性好的系统程序的能力, 加深对操作系统课程的理解。使学生更好地掌握操作系统的基本概念、 基本原理、 及基本功能, 具有分析实际操作系统、 设计、 构造和开发现代操作系统的基本能力。
实验要求做到:
1) 详细描述实验设计思想、 程序结构及各模块设计思路;
2) 详细描述程序所用数据结构及算法;
3) 明确给出测试用例和实验结果;
4) 为增加程序可读性, 在程序中进行适当注释说明;
5) 认真进行实验总结, 包括: 设计中遇到的问题、 解决方法与收获等;
6) 实验报告撰写要求结构清晰、 描述准确逻辑性强;
7) 实验过程中, 同学之间能够进行讨论互相提高, 但绝对禁止抄袭。
⒎ 本实验的重点、 难点及教学方法建议
重点: 理解进程调度中PCB的设计, 以实现对进程的调度。
难点: 进程调度程序的设计, 设备管理程序的设计。
教学方法建议: 力争在本指导书的帮助下, 独立设计程序以加深理解。
实验一 分析操作系统所面临的操作需求
( 一) 实验目的
使学生理解操作系统所面临的操作需求, 掌握操作系统中的进程管理、 存储管理、 设备管理和文件管理等功能。
( 二) 实验内容
1. 分析操作系统所面临的操作需求;
2. 熟悉实验环境;
3. 资料搜集与整理, 进行实验的前期准备。
熟悉编程环境
本课程中的实验题目既能够在windows下用控制台应用程序实现, 也能够在linux下用全屏幕程序实现。这里我们首先介绍在windows下用vc++6.0设计控制台应用程序的步骤, 然后介绍在linux下用C语言编写全屏幕程序的步骤。
1. windows的控制台应用程序
图1-1 图1-2
图1-3
步骤1: 开机, 单击”开始”按钮, 选择”程序 ->Microsoft Visual Studio 6.0->Microsoft Visual C++6.0”进入Microsoft Visual C++6.0。见图1-1。
步骤2: 在Microsoft Visual C++6.0中, 单击”File”菜单, 选择”New”菜单命令, 见图1-2。
步骤3: 在”Files”选项卡中选择”C++ Source File”, 见图1-3
2. linux的vi应用编程
登录 Linux是一个多用户多任务操作系统, 多个用户能够拥有自己独立的用户账号
登录提示:
Red Hat Linux release 6.0 (Hedwing)
Kernel 2.2.5-15 on an i686
Login:
此时输入用户户名( 账号) 并键入回车, 则系统显示”passward”。在输入密码和回车。
登录后: [root@hawk/root]#
#表示是按root方式登录,$表示是普通用户。Linux大小写敏感, 用”-”加参数
zlinux:~# ls –F
HowTo/ HowToMin/ linux@ nag/ sag/
获取帮助: Linux带有联机手册, 能够用man命令来阅读
Zlinux:~$ man ls
虚拟终端 Linux可有多个用户登录到同一个计算机, 但一般微机只有一个终端难以体现。能够使用多个虚拟终端, 用Alt+F1、 Alt+F2等来切换。
退出系统 在停止使用系统时, 要退出系统。具体方法: exit或logout, 或Ctrl+D
关机 如果没有用户在使用系统, 能够关机。可是不能直接关闭电源, 而要按正常顺序关机。一般用户是不能关机的, 只有root用户能够关机。
方法: 能够使用halt或shutdown命令, 也能够同时键入Ctrl+Alt+Del。
Windows 虚拟机环境:
登录到系统
点击桌面”VMware”图标——> Vmware Workstation窗口——>Commands——>Start this virtual machine
进入fedora后, 用户名: root
口 令: 123456
使用编辑器vi 编辑文件
1. 进入linux的文本模式之后, 在命令行键入vi filename.c 然后回车。下面作一些简单的解释: 首先vi命令是打开vi编辑器。后面的filename.c是用户即将编辑的c文件名字, 注意扩展名字是.c; 当然, vi编辑器功能很强, 能够用它来编辑其它格式的文件, 比如汇编文件, 其扩展名字是.s; 也能够直接用vi打开一个新的未命名的文件, 当保存的时候再给它命名, 只是这样做不很方便。
2. 最基本的命令I : 当进入刚打开的文件时, 不能写入信息, 这时按一下键盘上的I键( insert) , 插入的意思, 就能够进入编辑模式了。如下图所示:
3. a与i是相同的用法
4. 当文件编辑完后, 需要保存退出, 这时需要经过以下几个步骤: 1) 按一下键盘上的Esc 键; 2) 键入冒号(: ), 紧跟在冒号后面是wq( 意思是保存并退出) 。如果不想保存退出, 则在第二步键入冒号之后, 键入! q( 不带w, 机尾部保存) 。如下图所示:
5. 退出vi编辑器的编辑模式之后, 要对刚才编写的程序进行编译。编译的命令是: gcc filename.c [-o outputfilename], 其中gcc是c的编译器。参数: filename.c 是刚才编辑的c 文件( 当然也能够是以前编写好的c文件) ; 后面中括号里面的参数是可选的, 它是一个输出文件。如果不选, 默认的输出文件是a.out , 选了之后输出文件就是outputfilename.out.
6. 最后一步是运行程序, 方法如下: ./outputfilename.out
实验二 进程管理
( 一) 实验目的
掌握临界区的概念及临界区的设计原则; 掌握信号量的概念、 PV操作的含义以及应用PV操作实现进程的同步与互斥; 分析进程争用资源的现象, 学习解决进程互斥的方法; 掌握进程的状态及状态转换; 掌握常见的进程调度算法。
( 二) 实验内容
1.分析进程的同步与互斥现象, 编程实现经典的进程同步问题——生产者消费者问题的模拟;
2. 编写允许进程并行执行的进程调度程序, 在常见的进程( 作业) 调度算法: 先来先服务算法、 短作业优先算法、 最高响应比优先算法、 高优先权优先算法等调度算法中至少选择三种调度算法进行模拟, 并输出平均周转时间和平均带权周转时间。
本实验涉及内容较多, 能够在两个题目里选择一个完成。
编程实现经典的进程同步问题——生产者消费者问题的模拟
模拟实现用同步机构避免发生进程执行时可能出现的与时间有关的错误。
进程是程序在一个数据集合上运行的过程, 进程是并发执行的, 也即系统中的多个进程轮流地占用处理器运行。
我们把若干个进程都能进行访问和修改的那些变量称为公共变量。由于进程是并发地执行的, 因此, 如果对进程访问公共变量不加限制, 那么就会产生”与时间有关”的错误, 即进程执行后所得到的结果与访问公共变量的时间有关。为了防止这类错误, 系统必须要用同步机构来控制进程对公共变量的访问。一般说, 同步机构是由若干条原语——同步原语——所组成。本实验要求模拟PV操作同步机构的实现, 模拟进程的并发执行, 了解进程并发执行时同步机构的作用。
此次用到的数据结构知识如下:
typedef struct Pcb{
char name[10]; //进程名
char state[10]; //运行状态
char reason[10]; //若阻塞, 其原因
int breakp; //断点保护
struct Pcb *next; //阻塞时的顺序
}Pcb,*link;
进程名
状态
等待原因
断点
后继进程
进程控制块结构
定义两个进程: link p1;//生产者进程, link c1;//消费者进程。pc程序计数器和link ready; 就绪队列, link b_s1; s1阻塞队列, link b_s2; s2阻塞队列。
实验指导:
a. h头文件
#include<string.h>
#include<ctype.h>
#include<malloc.h> /* malloc()等 */
#include<limits.h> /* INT_MAX等 */
#include<stdio.h> /* EOF(=^Z或F6),NULL */
#include<stdlib.h> /* atoi() */
#include<io.h> /* eof() */
#include<math.h> /* floor(),ceil(),abs() */
#include<process.h> /* exit() */
#include <iostream>
using namespace std;
#include <time.h>
#define BUF 10 //缓存的大小
#define MAX 20 //最大能够输入的字符
b. h头文件
//数据结构的定义和全局变量
typedef struct Pcb{
char name[10]; //进程名
char state[10]; //运行状态
char reason[10]; //若阻塞, 其原因
int breakp; //断点保护
struct Pcb *next; //阻塞时的顺序
}Pcb,*link;
int s1,s2; //信号量
link p1;//生产者进程
link c1;//消费者进程
char str[MAX]; //输入的字符串
char buffer[BUF]; //缓冲池
int len; //输入长度
int sp=0; //string的指针
int in=0; //生产者指针
int out=0; //消费者指针
char temp; //供打印的临时产品
char rec_p[MAX];//生产记录
int rp1=0;//生产记录指针
char rec_c[MAX];//消费记录
int rp2=0;//消费记录指针
link ready; //就绪队列
link b_s1; //s1阻塞队列
link b_s2; //s2阻塞队列
int pc; //程序计数器
int count; //字符计数器
int con_cnt; //消费计数器
c. h头文件
void init(); //初始化
void p(int s); //P操作
void v(int s); //V操作
void block(int s);//阻塞函数
void wakeup(int s);//唤醒函数
void control(); //处理机调度
void processor();//处理机执行
void print(); //打印函数
void init(){ //初始化
s1=BUF;
s2=0;
p1=(link)malloc(sizeof(Pcb));//建立新的结点,并初始化为生产者
strcpy(p1->name,"Producer");
strcpy(p1->state,"Ready");
strcpy(p1->reason,"Null");
p1->breakp=0;
p1->next=NULL;
c1=(link)malloc(sizeof(Pcb));//建立新的结点,并初始化为消费者
strcpy(c1->name,"Consumer");
strcpy(c1->state,"Ready");
strcpy(c1->reason,"Null");
c1->breakp=0;
c1->next=NULL;
ready=p1;
ready->next=c1;//初始化为生产进程在前, 消费进程在后
c1->next=NULL;
b_s1=NULL;
b_s2=NULL;//阻塞进程为NULL
pc=0;
con_cnt=0; //消费计数器
}
void p(int s){
if(s==1){ //p(s1)
s1--;
if(s1<0)
block(1); //阻塞当前生产进程
else{
printf("\t* s1信号申请成功!\n");
ready->breakp=pc; //保存断点
}
}
else{ //p(s2)
s2--;
if(s2<0)
block(2);//阻塞当前消费进程
else{
printf("\t* s2信号申请成功!\n");
ready->breakp=pc; //保存断点
}
}
}
void v(int s){
if(s==1){ //v(s1)
s1++;
if(s1<=0)
wakeup(1); //唤醒生产进程
ready->breakp=pc; //保存断点
}
else{ //v(s2)
s2++;
if(s2<=0)
wakeup(2);//唤醒消费进程
ready->breakp=pc; //保存断点
}
}
void block(int s){//阻塞函数的定义
link p;
int num1=0;
int num2=0;
if(s==1){//生产进程
strcpy(p1->state,"Block");//改变状态
strcpy(p1->reason,"S1");//说明原因
p=b_s1;
while(p){
num1++;
p=p->next;//p的值为NULL,表示队尾
}
if(!b_s1)
b_s1=p1;
else
p=p1;
p1->next=NULL;
printf("\t* p1生产进程阻塞了!\n");
ready->breakp=pc; //保存断点
ready=ready->next;//在就绪队列中去掉,指向下一个
num1++;
}
else{//消费进程
strcpy(c1->state,"Block");
strcpy(c1->reason,"S2");
p=b_s2;
while(p){
num2++;
p=p->next;//p的值为NULL,表示队尾
}
if(!b_s2)
b_s2=c1;
else
p=c1;
ready->breakp=pc; //保存断点
ready=ready->next;//在就绪队列中去掉,指向下一个
c1->next=NULL;
printf("\t* c1消费进程阻塞了!\n");
num2++;
}
printf("\t* 阻塞的生产进程个数为:%d\n",num1);
printf("\t* 阻塞的消费进程个数为:%d\n",num2);
}
void wakeup(int s){//唤醒函数的定义
link p;
link q=ready;
if(s==1){ //唤醒b_s1队首进程,生产进程队列
p=b_s1;
b_s1=b_s1->next;//阻塞指针指向下一个阻塞进程
strcpy(p->state,"Ready");
strcpy(p->reason,"Null");
while(q)//插入就绪队列
q=q->next;
q=p;
p->next=NULL;
printf("\t* p1生产进程唤醒了!\n");
}
else{ //唤醒b_s2队首进程,消费进程队列
p=b_s2;
b_s2=b_s2->next;//阻塞指针指向下一个阻塞进程
strcpy(p->state,"Ready");
strcpy(p->reason,"Null");
while(q->next)//插入就绪队列
q=q->next;
q->next=p;
p->next=NULL;
printf("\t* c1消费进程唤醒了!\n");
}
}
void control() //处理器调度程序
{
int rd;
int num=0;
link p=ready;
if(ready==NULL) //若无就绪进程,结束
return;
while(p) //统计就绪进程个数
{
num++;
p=p->next;//最终p变为NULL
}
printf("\t* 就绪进程个数为:%d\n",num);
time_t t;
srand((unsigned) time(&t));
rd=rand()%num;//随机函数产生随机数
if(rd==1){
p=ready;
ready=ready->next;
ready->next=p;
p->next=NULL;
strcpy(ready->state,"Run");
strcpy(ready->next->state,"Ready");
}
else
strcpy(ready->state,"Run");
pc=ready->breakp;
}
void processor(){ //模拟处理器指令执行
if(strcmp(ready->name,"Producer")==0) //当前进程为生产者
switch(pc)
{
case 0://produce
printf("\t* 生产者生产了字符%c\n",str[sp]);
rec_p[rp1]=str[sp];//添加到生产记录
sp=(sp+1)%len;
pc++;
ready->breakp=pc; //保存断点
break;
case 1: //p(s1)
pc++;
p(1);
break;
case 2: //put
buffer[in]=rec_p[rp1]; //放到缓冲区
printf("\t* %c字符成功入驻空缓存!\n",buffer[in]);
rp1++;
in=(in+1)%BUF;
pc++;
ready->breakp=pc; //保存断点
break;
case 3: //v(s2)
pc++;
printf("\t* 释放一个s2信号\n");
v(2);
break;
case 4://goto01
printf("\t* 生产进程goto 0 操作\n");
pc=0;
count--; //剩余字符个数减1
printf("\t* 剩余字符count=%d个\n",count);
ready->breakp=pc; //保存断点
if(count<=0){ //生产结束
printf("\t* 生产者结束生产!\n");
strcpy(p1->state,"Stop");
strcpy(p1->reason,"Null");
ready->breakp=-1;
ready=ready->next;//在就绪队列中去掉
}
}
else //当前进程为消费者
switch(pc)
{
case 0: //p(s2)
pc++;
p(2);
break;
case 1: //get
printf("\t* 消费者取字符!\n");
temp=buffer[out];
out=(out+1)%BUF;
pc++;
ready->breakp=pc; //保存断点
break;
case 2: //v(s1)
pc++;
printf("\t* 释放一个s1\n");
v(1);
break;
case 3: //consume
printf("\t* 消费了字符%c\n",temp);
rec_c[rp2]=temp;//添加到消费记录
rp2++;
con_cnt++;
if(con_cnt>=len){
strcpy(c1->state,"Stop");//完成态
c1->breakp=-1;
return;
}
pc++;
ready->breakp=pc; //保存断点
break;
case 4: //goto0
printf("\t* 消费进程goto 0 操作\n");
pc=0;
ready->breakp=pc; //保存断点
}
}
void print(){
int i,j;
printf("--------生产者消费者模拟-------\n");
printf("* 模拟过程的字符串为:\t");
printf("%s\n",&str);
printf("* 已生产:");
for(j=0;j<=rp1;j++)
printf("%c",rec_p[j]);
printf("\n* 空缓存:");
for(j=rp2;j<=rp1;j++)
printf("%c",buffer[j]);
printf("\n* 已消费:");
for(j=0;j<=rp2;j++)
printf("%c",rec_c[j]);
printf("\n-------进程控制块的信息--------\n");
printf("进程名\t\t状态\t等待原因\t断点\n");
printf("%s\t%s\t%s\t\t%d\n\n",p1->name,p1->state,p1->reason,p1->breakp);
printf("%s\t%s\t%s\t\t%d\n",c1->name,c1->state,c1->reason,c1->breakp);
printf("-----------------------\n");
printf("1.继续 0.退出\n");
scanf("%d",&i);
if(i==0){
exit(0);
}
}
主程序
#include "a.h"
#include "b.h"
#include "c.h"
void main(){
printf("*生产者消费者模拟\n");
printf("---------\n");
printf("*请输入字符串:\n");
scanf("%s",str); //string数组存放将要产生的字符
len=strlen(str);
count=len; //输入字符的个数
init(); //初始化
while(con_cnt<len) //消费完所有的字符为结束
{
system("cls"); //清屏操作
printf("---------模拟指令流程--------\n");
control(); //处理器调度程序
processor(); //模拟处理器指令执行
print(); //输出显示各个信息
}
printf("\n程序结束!\n");
}
进程调度算法模拟
进程管理是操作系统中的重要功能, 用来创立进程、 撤消进程、 实现进程状态转换, 它提供了在可运行的进程之间复用CPU的方法。在进程管理中, 进程调度是核心, 因为在采用多道程序设计的系统中, 往往有若干个进程同时处于就绪状态, 当就绪进程个数大于处理器数目时, 就必须依照某种策略决定哪些进程优先占用处理器。
本实验模拟在单处理器情况下的进程调度, 目的是加深对进程调度工作的理解, 掌握不同调度算法的优缺点。
设计一个按先来先服务、 时间片轮转法、 优先数调度算法实现处理器调度的程序。
实验指导:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<conio.h>
typedef struct node
{
char name[10]; /*进程标识符*/
int prio; /*进程优先数*/
int round; /*进程时间轮转时间片*/
int cputime; /*进程占用CPU时间*/
int needtime; /*进程到完成还要的时间*/
int arrivetime; /*进程到达时间*/
int starttime; /*进程开始时间*/
int finishtime; /*进程完成时间*/
int servicetime; /*进程服务时间*/
float turnaroundtime; /*进程周转时间*/
float weightedturnaroundtime; /*进程带权周转时间*/
int count; /*计数器*/
char state; /*进程的状态*/
struct node *next; /*链指针*/
}PCB;
PCB *finish,*ready,*tail,*run; /*队列指针*/
int N; /*进程数*/
/*将就绪队列中的第一个进程投入运行*/
void firstin()
{
run=ready; /*就绪队列头指针赋值给运行头指针*/
run->state='R'; /*进程状态变为运行态*/
ready=ready->next; /*就绪对列头指针后移到下一进程*/
}
/*****标题输出函数*****/
void prt1(char a)
{
switch(a)
{
case 1: /*优先数法*/
printf("名字 进程占用CPU时间 进程到完成还要的时间 优先级数 状态\n");break;
case 2: /*时间片算法*/
printf("名字 进程占用CPU时间 进程到完成还要的时间 计数器 时间片 状态\n");break;
case 3: /*先来先服务算法*/
printf("名字 到达时间 开始时间 服务时间 完成时间 周转时间 带权周转时间 状态\n");break;
default:break;
}
}
/*****进程PCB输出*****/
void prt2(char a,PCB *q)
{
switch(a)
{
case 1: /*优先数法的输出*/
printf("%-10s\t%-10d\t%-10d\t%-10d\t%c\n",q->name,
q->cputime,q->needtime,q->prio,q->state);break;
case 2:/*轮转法的输出*/
printf("%-10s%-20d%-15d%-10d%-10d%-c\n",q->name,
q->cputime,q->needtime,q->count,q->round,q->state);break;
case 3:/*先来先服务算法输出*/
printf("%s%10d%10d%10d%10d%10.1f%10.2f\t\t%c\n",q->name,q->arrivetime,q->starttime,q->servicetime,q->finishtime,
q->turnaroundtime,q->weightedturnaroundtime,q->state);break;
default:break;
}
}
/*****输出函数*****/
void prt(char algo)
{
PCB *p;
prt1(algo); /*输出标题*/
if(run!=NULL) /*如果运行指针不空*/
prt2(algo,run); /*输出当前正在运行的PCB*/
p=ready; /*输出就绪队列PCB*/
while(p!=NULL)
{
prt2(algo,p);
p=p->next;
}
p=finish; /*输出完成队列的PCB*/
while(p!=NULL)
{
prt2(algo,p);
p=p->next;
}
getch(); /*压任意键继续*/
}
/*****优先数的插入算法*****/
void insert1(PCB *q)
{
PCB *p1,*s,*r;
int b;
s=q; /*待插入的PCB指针*/
p1=ready; /*就绪队列头指针*/
r=p1; /*r做p1的前驱指针*/
b=1;
while((p1!=NULL)&&b) /*根据优先数确定插入位置*/
if(p1->prio>=s->prio)
{
r=p1;
p1=p1->next;
}
else
b=0;
if(r!=p1) /*如果条件成立说明插入在r与p1之间*/
{
r->next=s;
s->next=p1;
}
else
{
s->next=p1; /*否则插入在就绪队列的头*/
ready=s;
}
}
/*****轮转法插入函数*****/
void insert2(PCB *p2)
{
tail->next=p2; /*将新的PCB插入在当前就绪队列的尾*/
tail=p2;
p2->next=NULL;
}
/*****先来先服务插入函数*****/
void insert3(PCB *q)
{
PCB *p1,*s,*r;
int b;
s=q; /*指针s指向新要插入的进程*/
p1=ready; /*指针p1指向原来的进程的对首*/
r=p1; /*使用指针r指向p1前面的进程*/
b=1;
while((p1!=NULL)&&b)
if(p1->arrivetime<s->arrivetime)
{
r=p1;
p1=p1->next;
}
else
b=0;
if(r!=p1)
{
r->next=s;
s->next=p1;
}
else
{
s->next=p1;
ready=s;
}
}
/*****优先数创立初始PCB信息*****/
void create1(char alg)
{
PCB *p;
int i,time;
char na[10];
ready=NULL; /*就绪队列头指针*/
finish=NULL; /*完成队列头指针*/
run=NULL; /*运行队列头指针*/
printf("请输入进程的名字和运行所需要的时间\n"); /*输入进程标识和所需时间创立PCB*/
for(i=1;i<=N;i++)
{
p=(PCB *)malloc(sizeof(PCB));
scanf("%s",na);
scanf("%d",&time);
strcpy(p->name,na);
p->cputime=0;
p->
展开阅读全文