山东大学操作系统实验报告
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
操作系统实验报告
计算机科学与技术学院
目录
一、进程控制实验 ...................................................................................................
1.1 实验目的................................................................................................................
1.2示例实验...................................................................................................................
1.2.1实验内容................................................................................................................
1.3独立实验...................................................................................................................
1.3.1实验内容................................................................................................................
1.3.2实验步骤................................................................................................................
1.3.3实验演示结果........................................................................................................
1.3.4实验代码................................................................................................................
二、进程调度算法实验 .................................................................................................
2.1 实验目的................................................................................................................
2.2示例实验...................................................................................................................
2.2.1实验内容................................................................................................................
2.2.2实验演示结果........................................................................................................
2.3独立实验...................................................................................................................
2.3.1实验内容................................................................................................................
2.3.2实验步骤................................................................................................................
2.3.3实验演示结果........................................................................................................
2.3.4实验代码................................................................................................................
二、进程同步实验 ...................................................................................................
3.1 实验目的................................................................................................................
3.2示例实验...................................................................................................................
3.2.1实验内容................................................................................................................
3.2.2实验演示结果........................................................................................................
3.3独立实验...................................................................................................................
3.3.1实验内容................................................................................................................
3.3.2实验步骤................................................................................................................
3.3.3实验演示结果........................................................................................................
3.3.4实验代码................................................................................................................
三、内存页面置换算法实验 ...................................................................................
4.1 实验目的................................................................................................................
4.2示例实验...................................................................................................................
4.2.1实验内容................................................................................................................
4.2.2实验演示结果........................................................................................................
4.3独立实验...................................................................................................................
4.3.1实验内容................................................................................................................
4.3.2实验步骤................................................................................................................
4.3.3实验演示结果........................................................................................................
4.3.4实验代码................................................................................................................
四、磁盘移臂调度算法实验 ...................................................................................
5.1 实验目的................................................................................................................
5.2示例实验...................................................................................................................
5.2.1实验内容................................................................................................................
5.2.2实验演示结果........................................................................................................
5.3独立实验...................................................................................................................
5.3.1实验内容................................................................................................................
5.3.2实验步骤................................................................................................................
5.3.3实验演示结果........................................................................................................
5.3.4实验代码................................................................................................................
一、进程控制实验
1.1 实验目的
加深对于进程并发执行概念的理解。
实践并发进程的创建和控制方法。
观察和体验进程的动态特性。
进一步理解进程生命期期间创建、变换、撤销状态变换的过程。
掌握进程控制的方法,了解父子进程间的控制和协作关系。
练习Linux 系统中进程创建与控制有关的系统调用的编程和调试技术。
1.2示例实验
1.2.1实验内容
以下实验示例程序应实现一个类似shell 子命令的功能,它可以从执行程序中启动另一个新的子进程并执行一个新的命令和其并发执行。
1.2.2实验演示结果
1.3独立实验
1.3.1实验内容
参考以上示例程序中建立并发进程的方法,编写一个父子协作进程,父进程创建一个子进程并控制它每隔 3 秒显示一次当前目录中的文件名列表。
1.3.2实验步骤
1.3.
2.1算法设计
通过进程间的通讯,先创建一个父进程一个子进程,父进程沉睡3秒,子进程作为当前父进程再次创建一个他的子进程,当前子进程执行显示当前目录文件列表功能,执行execve()方法后死亡。
While(1)在死循环里无限
进行当前操作。
即达到父进程创建一个子进程并控制它每隔3秒显示一次当前目录中的文件名列表的要求。
1.3.
2.2开发调试过程
打开一终端命令行窗体,新建一个文件夹,在该文件夹中建立名为pctrl.c 的C语言程序;
再建立以下名为pctrl.h 的C 语言头文件;
建立项目管理文件Makefile;
输入make 命令编译连接生成可执行的pctl 程序;
执行pctl 程序;
再次执行带有子进程指定执行命令的pctl 程序。
1.3.
2.3思考与分析
1.反映的进程的特征和功能,在真实的操作系统中是怎样实现和反映出教材中讲解的进程的生命期、进程的实体和进程状态控制的。
对于进程概念和并发概念有哪些新的理解和认识?子进程是如何创建和执行新程序
的?
答:进程是一个可并发执行的程序在某数据集上的一次运行,是程序的一次运行过程。
而程序只是进程的一个组成部分,进程是程序的执行过程。
程序是静态的指令集合,而进程是动态的过程实体,是动态的产生、发展和消失。
此外所谓的进程并发执行是在宏观上并发,而在微观上交替执行。
每个进程都用一个唯一的整数形式的进程标识符来标识,通过fork()系统调用,可创建新进程。
新进程通过复制原来进程的地址空间而成。
这种机制允许父子进程方便的进行通信。
系统调用fork(),得到的子进程实际上是父进程的克隆体,要执行不同的新程序要使用系统调用exec(),以用新程序来取代进程的内存空间。
其功能是根据参数指定的文件名找到程序文件,把它装入内存,覆盖原来进程的映像,形成一个不同于父进程的子进程。
除了进程映像被更换之外,新进程的PID及其他PCB属性均保持不变,实际上是一个新进程“借壳”原来子进程开始运行。
父进程可通过系统调用waitpid()来把自己移出就绪队列来等待子进程的终止。
2.信号的机理是什么?怎样利用信号实现进程控制?
每个信号对应一个正整数常量(为signalnumber。
定义在系统头文件
<signal.h>中),代表同一用户的诸进程之间传送事先约定的信息的类型,用于通知某进程发生了某异常事件。
每个进程运行时,要通过信号机制来检查是否有信号到达。
若有,中断正在执行的程序,转向该信号相对应的处理程序,已完成对事件的处理;处理结束后再返回到原来的断点继续执行。
1.3.3实验演示结果
1.3.4实验代码
pctl.c文件:
#include "pctl.h"
int main()
{
int pid_1,pid_2; //存放子进程号
int status_1,status_2; //存放子进程返回状态
while(1){
pid_1=fork() ;
if(pid_1<0) // 建立子进程失败
{
printf("Create Process fail!\n");
exit(EXIT_FAILURE);
}
if(pid_1 == 0) // 子进程执行代码段
{
//报告父子进程进程号
printf("I am Child-ls process %d\nMy father is %d\n",getpid(),getppid());/*getpid 返回当前进程的进程号,getppid 返回当前进程父进程的进程号*/ pid_2=fork();
if(pid_2<0)// 建立子进程失败
{
printf("Create Process fail!\n");
exit(EXIT_FAILURE);
}
if(pid_2==0) // 子进程执行代码段
{//报告父子进程进程号
printf("I am Child-ps process %d\nMy father is %d\n",getpid(),getppid());
printf("%d child will Running: \n",getpid()); /*子进程被键盘中断信号唤醒继续执行*/ status_2=execve("/bin/ps",NULL,NULL);//装入并执行新的程序
}else{
printf("wait for the ps-child end%d\n",pid_2);
waitpid(pid_2,&status_2,0);//等待子进程2结束
//status 用于保留子进程的退出状态
}
printf("%d child will Running: \n",getpid()); //装入并执行新的程序
char *argv[]={"0",NULL};
status_1 = execve("/bin/ls",argv,NULL);
}
else{
printf("I am Parent process %d\n",getpid());
printf("wait for the ls-child end %d\n",pid_1);
waitpid(pid_1,&status_1,0);
printf("child end,sleep...\n");
sleep(3);// sleep 函数会令调用进程的执行挂起睡眠3秒
}
}
return EXIT_SUCCESS;
}
pctl.h文件:
#include <sys/types.h>
#include <wait.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
//进程自定义的键盘中断信号处理函数
typedef void (*sighandler_t) (int);
void sigcat(){
printf("%d Process continue\n",getpid());
}
二、进程调度算法实验
2.1 实验目的
加深对进程调度概念的理解,体验进程调度机制的功能,了解Linux 系统中进程调度策略的使用方法。
练习进程调度算法的编程和调试技术。
2.2示例实验
2.2.1实验内容
以下示例实验程序要测试在linux 系统中不同调度策略和不同优先数的调度效果。
2.2.2实验演示结果
2.3独立实验
2.3.1实验内容
设有两个并发执行的父子进程,不断循环输出各自进程号、优先数和调度策
略。
进程初始调度策略均为系统默认策略和默认优先级。
当某个进程收到SIGINT
信号时会自动将其优先数加1,收到SIGCSTP 信号时会自动将其优先数减1。
请编
程实现以上功能。
2.3.2实验步骤
2.3.2.1算法设计
建立一个父进程和一个子进程。
通过第一章学习的进程控制相互发送信号。
当按下Ctrl+C 时,父进程实现优先级加1。
当按下Ctrl+Z 时,子进程实
现优先级减1。
用signal 系统调用将父进程注册一个本进程处理SIGINT 的实现优先级
加1 的函数。
子进程注册一个本进程处理SIGTSTP 的实现优先级减1 的函数。
当按下Ctrl+Z 时,子进程做出响应,其优先级减1;当按下Ctrl+C 时,父进程做出响应,父进程优先级加 1.然后再输出各自进程号、优先数和调度策略。
以上行为通过for()语句循环。
2.3.2.2开发调试过程
新建一个文件夹,在该文件夹中建立以下名为pctl.c 的C 语言程序。
再建立以下名为pctl.h的C 语言头文件。
建立项目管理文件Makefile 。
输入make 命令编译连接生成可执行的pctl程序。
执行并调试pctl程序
2.3.2.3思考与分析
进程调度调度策略和功能如下所示:
SCHED_OTHER 默认的分时调度策略(值等于0)
SCHED_FIFO 先进先先出调度策略(值等于1)
SCHED_RR 时间片轮转调度策略(值等于2)
进程调度本质就是让谁先执行,让谁后执行。
在真实的操作系统中,由调度策略和优先级决定谁先执行。
Linux 的调度策略有三种,SCHED_OTHER
分时调度,SCHED_FIFO 先进先出,SCHED_RR 时间片轮转。
后两种专用于对响应时间有特殊要求的进程,并且会抢先于SCHED_OTHER 调度策略的进程而执行。
通过这个系统调用设置进程调度策略,
intsched_setscheduler(pid_t pid,int policy,const struct sched_param *sp); 其中pid 是进程号,policy 是以上说明的3 种调度策略之一,sp 调度参数结构指针,调度参数结构主要存有调度优先数。
进程优先数(prio)由静态优先级和动态优先级两部分组成。
静态优先级与调度策略有关。
动态优先级由以下系统调用设置,int setpriority(int which,int who,int prio);which 设置的对象。
可以是:
进程PRIO_PROCESS
进程组PRIO_PGRP
用户PRIO_USER
who 对应设置对象的进程号或组号或用户号
prio 要设置的进程优先数
2.3.3实验演示结果
2.3.4实验代码
psched.c文件:
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include <sys/resource.h>
#define SIGINT 2
#define SIGTSTP 20
void handler_sigint(int signo) { //用于提升优先数
printf("\n%d 进程优先数增加1\n",getpid());
setpriority(PRIO_PROCESS, getpid(),
getpriority(PRIO_PROCESS,getpid()) + 1);
}
void handler_sigtstp(int signo) { //用于降低优先数
printf("\n%d 进程优先数减小1\n",getpid());
setpriority(PRIO_PROCESS, getpid(),
getpriority(PRIO_PROCESS,getpid()) - 1);
}
int main() {
pid_t pid;
if((pid = fork()) < 0 ){
perror("process create error!");
}
if(pid > 0) {
signal(SIGINT, handler_sigint); //父进程处理Ctrl+C
signal(SIGTSTP, SIG_IGN); //忽略Ctrl+Z
}else{
setpriority(PRIO_PROCESS, getpid(), 10); //设定子进程初始动态优先级为10,否则看不出效果
signal(SIGTSTP, handler_sigtstp); //子进程处理Ctrl+Z
signal(SIGINT, SIG_IGN); //忽略Ctrl+C
}
int i=1;
while(i<=20) {
if(pid > 0) {
printf("I'm parent process, ");
}else{
printf("I'm child process, ");
}
printf("pid = %d, priority = %d, scheduler = %d\n",
getpid(), getpriority(PRIO_PROCESS,
getpid()),sched_getscheduler(getpid()));
sleep(3);
i++;
}
}
其他文件同示例实验。
二、进程同步实验
3.1 实验目的
加深对并发协作进程同步与互斥概念的理解,观察和体验并发进程同步与互斥操作的效果,分析与研究经典进程同步与互斥问题的实际解决方案。
了解Linux 系统中IPC 进程同步工具的用法,练习并发协作进程的同步与互斥操作的编程与调试技术。
3.2示例实验
3.2.1实验内容
以下示例实验程序应能模拟多个生产/消费者在有界缓冲上正确的操作。
它利用N 个字节的共享内存作为有界循环缓冲区,利用写一字符模拟放一个产品,利用读一字符模拟消费一个产品。
当缓冲区空时消费者应阻塞睡眠,而当缓冲区满时生产者应当阻塞睡眠。
一旦缓冲区中有空单元,生产者进程就向空单元中入写字符,并报告写的内容和位置。
一旦缓冲区中有未读过的字符,消费者进程就从该单元中读出字符,并报告读取位置。
生产者不能向同一单元中连续写两次以上相同的字符,消费者也不能从同一单元中连续读两次以上相同的字符。
3.2.2实验演示结果
3.3独立实验
3.3.1实验内容
抽烟者问题。
假设一个系统中有三个抽烟者进程,每个抽烟者不断地卷烟并抽烟。
抽烟者卷起并抽掉一颗烟需要有三种材料:烟草、纸和胶水。
一个抽烟者有烟草,一个有纸,另一个有胶水。
系统中还有两个供应者进程,它们无限地供应所有三种材料,但每次仅轮流提供三种材料中的两种。
得到缺失的两种材料的抽烟者在卷起并抽掉一颗烟后会发信号通知供应者,让它继续提供另外的两种材料。
这一过程重复进行。
请用以上介绍的IPC 同步机制编程,实现该问题要求的功能。
3.3.2实验步骤
3.3.2.1问题分析
有两个供应者,有三个消费者。
用于存放烟草,纸和胶水的公共缓冲区的大小是3.存放顺序要指定。
分别是烟草,纸和胶水。
每一个供应者供应的
物品有三种,(烟草+纸)(烟草+胶水)(纸+胶水)。
三个消费者分别需要三个里面的一种。
约束:
(1)某一时刻,只能有一个供应者,放入一对物品。
(2)某一时刻,只能有一个消费者,且要保证这个消费者恰好需要的就是刚刚生产的物品。
(3)所有供应者提供这种物品之后,不论它要生产什么物品,只有等到消费者拿走了物品之后,才能继续生产。
3.3.2.2算法设计
(1)供应者的上下文完全一样,在程序中fork()子进程之后,让父进程和子进程并发执行供应者代码即可。
(2)消费者有三个,且每个消费者需要的物品不同,所以不能使用同一个代码段。
这样,就需要建立两个子进程和一个父进程同时并发执行。
然后在各自区域执行相似的但不同的代码段。
(3)用信号灯实现同步和互斥约束。
(4)创建公共缓冲区和指向首地址的指针
//获取缓冲区使用的共享内存,buff_ptr 指向缓冲区首地址
buff_ptr = (char *)set_shm(buff_key,buff_num,shm_flg);
//获取供应者放产品首位置指针pput_ptr
pput_ptr = (int *)set_shm(pput_key,pput_num,shm_flg);
3.3.2.3开发调试过程
新建一个文件夹,在该文件夹中建立以下名为ipc.h 的C 语言头文件。
再建立以下名为ipc.c 的C 语言头文件,实现头文件中申明的函数。
再建立以下名为producer.c 的C 语言头文件,实现供应者的功能。
再建立以下名为consumer.c 的C 语言头文件,实现消费者的功能。
建立项目管理文件Makefile 。
输入make 命令编译连接生成可执行的producer 和consumer 程序。
执行并调试producer 和consumer 程序。
3.3.2.4思考与分析
进程之间的同步在该实验中是通过信号灯实现的。
进程之间为了同步而使用的信号灯,必须完全相同的在各自进程中创建。
这样,这些信号灯就会被这些进程共享。
然后通过执行信号灯的up()和down()操作.使得进程并发执行时保持同步。
因为如果信号灯的值小于0,那么这个进程会阻塞,这就是为什么能够同步。
操作系统中,进程同步可以通过信号量机制实现。
在本实验中,信号灯就是信号量,down()操作就是信号量的wait 操作,up()操作就是信号量的signal 操作。
3.3.3实验演示结果
3.3.4实验代码
ipc.h文件:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/msg.h>
#define BUFSZ 256
//建立或获取ipc 的一组函数的原型说明
int get_ipc_id(char *proc_file,key_t key);
char *set_shm(key_t shm_key,int shm_num,int shm_flag);
int set_msq(key_t msq_key,int msq_flag);
int set_sem(key_t sem_key,int sem_val,int sem_flag);
int down(int sem_id);
int up(int sem_id);
typedef union semuns {
int val;
} Sem_uns;
typedef struct msgbuf {
long mtype;
char mtext[1];
} Msg_buf;
//生产消费者共享缓冲区即其有关的变量key_t buff_key;
int buff_num;
char *buff_ptr;
//生产者放产品位置的共享指针
key_t pput_key;
int pput_num;
int *pput_ptr;
//消费者取产品位置的共享指针
key_t cget_key;
int cget_num;
int *cget_ptr;
//生产者有关的信号量
key_t prod_key;
key_t pmtx_key;
int prod_sem;
int pmtx_sem;
//消费者有关的信号量
key_t c_PG_key;
key_t c_TP_key;
key_t c_TG_key;
key_t cmtx_key;
int c_PG_sem;
int c_TP_sem;
int c_TG_sem;
int cmtx_sem;
int sem_val;
int sem_flg;
int shm_flg;
ipc.c文件:
#include "ipc.h"
int get_ipc_id(char *proc_file,key_t key) {
FILE *pf;
int i,j;
char line[BUFSZ],colum[BUFSZ];
if((pf = fopen(proc_file,"r")) == NULL){ perror("Proc file not open");
exit(EXIT_FAILURE);
}
fgets(line, BUFSZ,pf);
while(!feof(pf)){
i = j = 0;
fgets(line, BUFSZ,pf);
while(line[i] == ' ') i++;
while(line[i] !=' ') colum[j++] = line[i++];
colum[j] = '\0';
if(atoi(colum) != key) continue;
j=0;
while(line[i] == ' ') i++;
while(line[i] !=' ') colum[j++] = line[i++];
colum[j] = '\0';
i = atoi(colum);
fclose(pf);
return i;
}
fclose(pf);
return -1;
}
int down(int sem_id)
{
struct sembuf buf;
buf.sem_op = -1;
buf.sem_num = 0;
buf.sem_flg = SEM_UNDO;
if((semop(sem_id,&buf,1)) <0) {
perror("down error ");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
int up(int sem_id)
{
struct sembuf buf;
buf.sem_op = 1;
buf.sem_num = 0;
buf.sem_flg = SEM_UNDO;
if((semop(sem_id,&buf,1)) <0) {
perror("up error ");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
int set_sem(key_t sem_key,int sem_val,int sem_flg)
int sem_id;
Sem_uns sem_arg;
//测试由sem_key 标识的信号灯数组是否已经建立
if((sem_id = get_ipc_id("/proc/sysvipc/sem",sem_key)) < 0 )
{
//semget 新建一个信号灯,其标号返回到sem_id
if((sem_id = semget(sem_key,1,sem_flg)) < 0)
{
perror("semaphore create error");
exit(EXIT_FAILURE);
}
//设置信号灯的初值
sem_arg.val = sem_val;
if(semctl(sem_id,0,SETVAL,sem_arg) <0)
{
perror("semaphore set error");
exit(EXIT_FAILURE);
}
}
return sem_id;
}
char * set_shm(key_t shm_key,int shm_num,int shm_flg)
{
int i,shm_id;
char * shm_buf;
//测试由shm_key 标识的共享内存区是否已经建立
if((shm_id = get_ipc_id("/proc/sysvipc/shm",shm_key)) < 0 )
{
//shmget 新建一个长度为shm_num 字节的共享内存,其标号返回到shm_id
if((shm_id = shmget(shm_key,shm_num,shm_flg)) <0)
{
perror("shareMemory set error");
exit(EXIT_FAILURE);
}
//shmat 将由shm_id 标识的共享内存附加给指针shm_buf
if((shm_buf = (char *)shmat(shm_id,0,0)) < (char *)0)
{
perror("get shareMemory error");
exit(EXIT_FAILURE);
}
for(i=0; i<shm_num; i++) shm_buf[i] = 0; //初始为0
}
//shm_key 标识的共享内存区已经建立,将由shm_id 标识的共享内存附加给指针shm_buf
if((shm_buf = (char *)shmat(shm_id,0,0)) < (char *)0)
perror("get shareMemory error");
exit(EXIT_FAILURE);
}
return shm_buf;
}
int set_msq(key_t msq_key,int msq_flg)
{
int msq_id;
//测试由msq_key 标识的消息队列是否已经建立
if((msq_id = get_ipc_id("/proc/sysvipc/msg",msq_key)) < 0 )
{
//msgget 新建一个消息队列,其标号返回到msq_id
if((msq_id = msgget(msq_key,msq_flg)) < 0)
{
perror("messageQueue set error");
exit(EXIT_FAILURE);
}
}
return msq_id;
}
producer.c文件:
#include "ipc.h"
int main(int argc,char *argv[])
{
int rate;
//可在在命令行第一参数指定一个进程睡眠秒数,以调解进程执行速度if(argv[1] != NULL) rate = atoi(argv[1]);
else rate = 3; //不指定为3 秒
//共享内存使用的变量
buff_key = 101;//缓冲区任给的键值
buff_num = 3;//缓冲区任给的长度
pput_key = 102;//生产者放产品指针的键值
pput_num = 1; //指针数
shm_flg = IPC_CREAT | 0644;//共享内存读写权限
//获取缓冲区使用的共享内存,buff_ptr 指向缓冲区首地址
buff_ptr = (char *)set_shm(buff_key,buff_num,shm_flg);
//获取生产者放产品位置指针pput_ptr
pput_ptr = (int *)set_shm(pput_key,pput_num,shm_flg);
//信号量使用的变量
prod_key = 201;//生产者同步信号灯键值
pmtx_key = 202;//生产者互斥信号灯键值
c_PG_key = 301;//消费者同步信号灯键值
c_TP_key = 302;//消费者互斥信号灯键值
c_TG_key = 303;//消费者互斥信号灯键值
sem_flg = IPC_CREAT | 0644;
//生产者同步信号灯初值设为缓冲区最大可用量
sem_val = 1;
//获取生产者同步信号灯,引用标识存prod_sem
prod_sem = set_sem(prod_key,sem_val,sem_flg);
//消费者初始无产品可取,同步信号灯初值设为0
sem_val = 0;
//获取消费者同步信号灯,引用标识存cons_sem
c_PG_sem = set_sem(c_PG_key,sem_val,sem_flg);
c_TP_sem = set_sem(c_TP_key,sem_val,sem_flg);
c_TG_sem = set_sem(c_TG_key,sem_val,sem_flg);
//生产者互斥信号灯初值为1
sem_val = 1;
//获取生产者互斥信号灯,引用标识存pmtx_sem
pmtx_sem = set_sem(pmtx_key,sem_val,sem_flg);
//循环执行模拟生产者不断放产品
int pid;
int i;
pid = fork();
if(pid == 0) {
while(1){
int r = rand() % 3;
if(r == 0) {
down(prod_sem);
//如果另一生产者正在放产品,本生产者阻塞
down(pmtx_sem);
//用写一字符的形式模拟生产者放产品,报告本进程号和放入的字符及存放的位置buff_ptr[*pput_ptr + 1] = 'P';
buff_ptr[*pput_ptr + 2] = 'G';
sleep(rate);
printf("%d 供应商提供: 纸%c, 胶水%c\n",getpid(),buff_ptr[*pput_ptr
+1] ,buff_ptr[*pput_ptr + 2]);
//唤醒阻塞的生产者
up(pmtx_sem);
//唤醒阻塞的消费者
up(c_PG_sem);
} else if(r == 1) {
down(prod_sem);
//如果另一生产者正在放产品,本生产者阻塞
down(pmtx_sem);
//用写一字符的形式模拟生产者放产品,报告本进程号和放入的字符及存放的位置buff_ptr[*pput_ptr] = 'T';
buff_ptr[*pput_ptr + 2] = 'G';
sleep(rate);
printf("%d供应商提供:烟草%c,胶
水%c\n",getpid(),buff_ptr[*pput_ptr] ,buff_ptr[*pput_ptr + 2]);
//唤醒阻塞的生产者
up(pmtx_sem);
//唤醒阻塞的消费者
up(c_TG_sem);
} else if(r == 2) {
down(prod_sem);
//如果另一生产者正在放产品,本生产者阻塞
down(pmtx_sem);
//用写一字符的形式模拟生产者放产品,报告本进程号和放入的字符及存放的位置buff_ptr[*pput_ptr + 1] = 'P';
buff_ptr[*pput_ptr] = 'T';
sleep(rate);
printf("%d供应商提供:烟草%c,纸%c\n",getpid(),buff_ptr[*pput_ptr] ,buff_ptr[*pput_ptr + 1]);
//唤醒阻塞的生产者
up(pmtx_sem);
//唤醒阻塞的消费者
up(c_TP_sem);
}
}
} else {
while(1){
int r = rand() % 3;
if(r == 0) {
down(prod_sem);
//如果另一生产者正在放产品,本生产者阻塞
down(pmtx_sem);
//用写一字符的形式模拟生产者放产品,报告本进程号和放入的字符及存放的位置buff_ptr[*pput_ptr + 1] = 'P';
buff_ptr[*pput_ptr + 2] = 'G';
sleep(rate);
printf("%d 供应商提供: 纸%c, 胶水%c\n",getpid(),buff_ptr[*pput_ptr +
1] ,buff_ptr[*pput_ptr + 2]);
//唤醒阻塞的生产者
up(pmtx_sem);
//唤醒阻塞的消费者
up(c_PG_sem);
} else if(r == 1) {
down(prod_sem);
//如果另一生产者正在放产品,本生产者阻塞
down(pmtx_sem);
//用写一字符的形式模拟生产者放产品,报告本进程号和放入的字符及存放的位置buff_ptr[*pput_ptr] = 'T';
buff_ptr[*pput_ptr + 2] = 'G';
sleep(rate);
printf("%d供应商提供:烟草%c,胶
水%c\n",getpid(),buff_ptr[*pput_ptr] ,buff_ptr[*pput_ptr + 2]);
//唤醒阻塞的生产者
up(pmtx_sem);
//唤醒阻塞的消费者
up(c_TG_sem);
} else if(r == 2) {
down(prod_sem);
//如果另一生产者正在放产品,本生产者阻塞
down(pmtx_sem);
//用写一字符的形式模拟生产者放产品,报告本进程号和放入的字符及存放的位置buff_ptr[*pput_ptr + 1] = 'P';
buff_ptr[*pput_ptr] = 'T';
sleep(rate);
printf("%d供应商提供:烟草%c,纸%c\n",getpid(),buff_ptr[*pput_ptr] ,buff_ptr[*pput_ptr + 1]);
//唤醒阻塞的生产者
up(pmtx_sem);
//唤醒阻塞的消费者
up(c_TP_sem);
}
}
}
return EXIT_SUCCESS;
}
consumer.c文件:
#include "ipc.h"
int main(int argc,char *argv[])
{
int rate;
//可在在命令行第一参数指定一个进程睡眠秒数,以调解进程执行速度
if(argv[1] != NULL) rate = atoi(argv[1]);
else rate = 3; //不指定为3 秒
//共享内存使用的变量
buff_key = 101; //缓冲区任给的键值
buff_num = 3; //缓冲区任给的长度
cget_key = 103; //消费者取产品指针的键值
cget_num = 1; //指针数
shm_flg = IPC_CREAT | 0644; //共享内存读写权限
//获取缓冲区使用的共享内存,buff_ptr 指向缓冲区首地址
buff_ptr = (char *)set_shm(buff_key,buff_num,shm_flg);
//获取消费者取产品指针,cget_ptr 指向索引地址
cget_ptr = (int *)set_shm(cget_key,cget_num,shm_flg);
//信号量使用的变量
prod_key = 201; //生产者同步信号灯键值
cmtx_key = 203; //生产者互斥信号灯键值
c_PG_key = 301;//消费者同步信号灯键值
c_TP_key = 302;//消费者互斥信号灯键值
c_TG_key = 303;//消费者互斥信号灯键值
sem_flg = IPC_CREAT | 0644; //信号灯操作权限
//生产者同步信号灯初值设为缓冲区最大可用量
sem_val = 1;
//获取生产者同步信号灯,引用标识存prod_sem
prod_sem = set_sem(prod_key,sem_val,sem_flg);
//消费者初始无产品可取,同步信号灯初值设为0
sem_val = 0;
//获取消费者同步信号灯,引用标识存cons_sem
c_PG_sem = set_sem(c_PG_key,sem_val,sem_flg);
c_TP_sem = set_sem(c_TP_key,sem_val,sem_flg);
c_TG_sem = set_sem(c_TG_key,sem_val,sem_flg);
//消费者互斥信号灯初值为1
sem_val = 1;
//获取消费者互斥信号灯,引用标识存pmtx_sem
cmtx_sem = set_sem(cmtx_key,sem_val,sem_flg);
int pid1, pid2;
pid1 = fork();
if(pid1 == 0) {
//循环执行模拟消费者不断取产品
while(1){
//如果无产品消费者阻塞
down(c_PG_sem);
//如果另一消费者正在取产品,本消费者阻塞
down(cmtx_sem);
//用读一字符的形式模拟消费者取产品,报告本进程号和获取的字符及读取的位置sleep(rate);
printf("%d 吸烟者有烟草T,得到纸%c 和胶水%c,开始吸烟......\n",
getpid(),buff_ptr[*cget_ptr + 1],buff_ptr[*cget_ptr + 2]);
//唤醒阻塞的消费者
up(cmtx_sem);
//唤醒阻塞的生产者
up(prod_sem);
}
} else {
pid2 = fork();
if(pid2 == 0) {
//循环执行模拟消费者不断取产品
while(1){
//如果无产品消费者阻塞
down(c_TP_sem);
//如果另一消费者正在取产品,本消费者阻塞
down(cmtx_sem);
//用读一字符的形式模拟消费者取产品,报告本进程号和获取的字符及读取的位置sleep(rate);
printf("%d 吸烟者有胶水G,得到纸%c 和烟草%c,开始吸烟......\n",
getpid(),buff_ptr[*cget_ptr + 1],buff_ptr[*cget_ptr]);
//唤醒阻塞的消费者
up(cmtx_sem);
//唤醒阻塞的生产者
up(prod_sem);
}
} else {
//循环执行模拟消费者不断取产品
while(1){
//如果无产品消费者阻塞
down(c_TG_sem);
//如果另一消费者正在取产品,本消费者阻塞
down(cmtx_sem);
//用读一字符的形式模拟消费者取产品,报告本进程号和获取的字符及读取的位置sleep(rate);
printf("%d 吸烟者有纸P,得到胶水%c 和烟草%c,开始吸烟......\n",
getpid(),buff_ptr[*cget_ptr + 2],buff_ptr[*cget_ptr]);
//唤醒阻塞的消费者
up(cmtx_sem);
//唤醒阻塞的生产者
up(prod_sem);
}
}
}
return EXIT_SUCCESS;
}
三、内存页面置换算法实验
4.1 实验目的
加深对于存储管理的了解,掌握虚拟存储器的实现原理;观察和了解重要的页面置换算法和置换过程。
练习模拟算法的编程技巧,锻炼分析试验数据的能力。
4.2示例实验
4.2.1实验内容
1.示例实验程序中模拟两种置换算法:LRU 算法和FIFO 算法
2.能对两种算法给定任意序列不同的页面引用串和任意帧实内存块数的组
合测试,显示页置换的过程。
3.能统计和报告不同置换算法情况下依次淘汰的页号、缺页次数(页错误数)和缺页率。
比较两种置换算法在给定条件下的优劣。
4.为了能方便的扩充页面置换算法,更好的描述置换过程,示例实验程采
用了C++语言用Replace 类描述了置换算法及其属性。
4.2.2实验演示结果
4.3独立实验
4.3.1实验内容
请在以上示例实验程序中补充“增强二次机会”等置换算法的模拟程序。
输入不同的内存页面引用串和实存帧数,观察并分析其页面置换效果和性能,并将其与LRU和FIFO 算法进行比较。
改进以上示例实验程序,使之能够随机的产生内存页面引用串,以便能动态的观测各种置换算法的性能。
4.3.2实验步骤
4.3.2.1算法设计
Clock()算法:
FIFO算法可能会把经常使用的页面置换出去,为了避免这一问题,对该算法做一个简单的修改:检查最老页面的R位,如果R位为0,那么这个页
面既老又没有被使用过,可以立刻被置换掉,如果R位为1,就将R位清0,并将这个页面重新插入链表尾端,然后继续搜索。
第二次机会算法是
寻找一个最近时间间隔内未被访问过的页面,如果所有的页面都被访问过了,这个算法就是纯粹的FIFO算法。
EClock()算法:
需将内存中的页面按访问位A和修改位M分为4类:
第一类(A=0,M=0)未被访问,又未被修改,是最佳替换页
第二类(A=0,M=1)未被访问,但被修改过,次佳替换页
第三类(A=1,M=0)被访问过,但未被修改,可能再被访问
第四类(A=1,M=1)以访问且被修改,可能再被访问
程序首先查找第一类页面,如果有则替换,如果没有则查找第二类页面;置页面的访问位为0,如果有则替换,如果没有则返回重新查找第一类页面;如果有则替换,如果没有则查找第二类页面,则一定存在。
该算法适用于某些页面经常被修改的场合。
LFU()算法:
即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。
但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数。
MFU算法:
与LFU算法相反,为最经常使用页置换算法。
认为引用次数最小的页以后可能会经常使用。
4.3.2.2开发调试过程
建立程序,将代码写到程序中;
再建立名为vmrp.h的C语言头文件;
建立以下项目管理文件Makefile;
输入make命令编译连接生成可执行的vmrp程序;
执行程序。
4.3.3实验演示结果
4.3.4实验代码
文件:
#include "vmrp.h"
#include <iostream>
#include <cstdlib>
using namespace std;
Replace::Replace()
{
int i;
//设定总得访问页数,并分配相应的引用页号和淘汰页号记录数组空间cout << "Please input page numbers :" ;
cin >> PageNumber;
ReferencePage = new int[sizeof(int) * PageNumber];
EliminatePage = new int[sizeof(int) * PageNumber];
//输入引用页号序列(页面走向),初始化引用页数组
cout << "Please input reference page string :";
for (i = 0; i < PageNumber; i++)
cin >> ReferencePage[i];//引用页暂存引用数组
//设定内存实页数(帧数),并分配相应的实页号记录数组空间(页号栈) cout << "Please input page frames :";
cin >> FrameNumber;
PageFrames = new int[sizeof(int) * FrameNumber];
}
Replace::~Replace()
{
}
void Replace::InitSpace(char * MethodName)
{
int i;
cout << endl << MethodName << endl;
FaultNumber=0;
//引用还未开始,-1 表示无引用页
for (i = 0; i < PageNumber; i++)
EliminatePage[i] = -1;
for(i = 0; i < FrameNumber; i++)
PageFrames[i] = -1;
}
//分析统计选择的算法对于当前输入的页面走向的性能
void Replace::Report(void)
{
//报告淘汰页顺序。