西安交通大学操作系统课内实验报告.docx
《西安交通大学操作系统课内实验报告.docx》由会员分享,可在线阅读,更多相关《西安交通大学操作系统课内实验报告.docx(67页珍藏版)》请在冰豆网上搜索。
西安交通大学操作系统课内实验报告
西安交通大学
实验报告
——操作系统原理课内实验
姓名:
班级:
学号:
实验一用户接口实验
一、实验目的
1、理解并掌握面向操作命令的接口Shell,学会简单的shell编码。
2、理解操作系统调用的运行机制,掌握创建系统调用的方法。
二、实验内容
1、控制台命令接口实验
理解面向操作命令的接口shell和进行简单的shell编程。
该实验是通过“几种操作系统的控制台命令”、“终端处理程序”、“命令解释程序”和“Linux操作系统的bash”来让实验者理解面向操作命令的接口shell和进行简单的shell编程。
●查看bash版本。
●编写bash脚本,统计/my目录下c语言文件的个数2)系统调用实验。
2、系统调用实验
理解操作系统调用的运行机制。
该实验是通过实验者对“Linux操作系统的系统调用机制”的进一步了解来理解操作系统调用的运行机制;同时通过“自己创建一个系统调用mycall()”和“编程调用自己创建的系统调用”进一步掌握创建和调用系统调用的方法。
●编程调用一个系统调用fork(),观察结果。
●编程调用创建的系统调用foo(),观察结果。
●自己创建一个系统调用mycall(),实现功能:
显示字符串到屏幕上。
●编程调用自己创建的系统调用。
三、实验准备
为了使用户通过操作系统完成各项管理任务,操作系统必须为用户提供各种接口来实现人机交互。
经典的操作系统理论将操作系统的接口分为控制台命令和系统调用两种。
前者主要提供给计算机的操作人员对计算机进行各种控制;而后者则提供个程序员,使他们可以方便地使用计算机的各种资源。
四、实验步骤及结果
1、控制台命令接口实验
(1)查看bash版本
操作:
在shell提示符下输入:
$echo$BASH_VERSION
结果:
版本是4.2.42
(1)-release
(2)建立bash脚本,输出Helloword
操作:
在编辑器中输入以下内容
#!
/bin/bash
echoHelloWorld!
结果:
操作:
执行脚本使用指令:
$./text
结果:
(3)编写bash脚本:
统计/my目录下c语言文件的个数
通过bash脚本,可以有多种方式实现这个功能,而使用函数是其中个一个选择。
在使用函数之前,必须先定义函数。
进入自己的工作目录,编写名为count的文件。
脚本程序:
#!
/bin/bash
functioncount
{
echo–n"Numberofmatchesfor$1:
"#接收程序的第一个参数
ls$1|wc–l#对子程序的第一个参数所在的目录进行操作
}
将count文件复制到当前目录下,然后在当前目录下建立文件夹,在my目录下建立几个c文件,以便用来进行测试。
2、添加系统调用
(1)编程调用一个系统调用fork(),观察结果。
源程序:
#include
intmain()
{
intiUid;
iUid=fork();
if(iUid==0)
for(;;){printf("Thisischildprocess.\n");
sleep
(1);
}
if(iUid>0)
for(;;){
printf("Thisisparentprocess.\n");
sleep
(1);
}
if(iUid<0)printf("Cannotusesystemcall.\n");
return0;
}
实验结果:
(2)操作:
1.Linux-3.0.tar.bz2拷贝到/usr/src目录下——命令:
cplinux-3.0.tar.bz2
/usr/src/
2.打开终端,获得root权限——命令:
sudo–s
3.进入/usr/src目录——命令:
cd/usr/src
4.解压linux源码——命令:
tarxvzflinux-3.0.tar.bz2
5.进入目录linux-3.0.5——命令:
cdlinux-3.0
6.添加系统调用——:
geditkernel/myservice.c在文本编辑器中添加
#include
#include
asmlinkagevoidsys_mycall()
{
printk(KERN_INFO"Hello,world!
\n");
return;
}
7.修改kernel/Makefile添加生成myservice.c添加到Makefile的编译规则中:
obj-y+=myservice.o
8..修改arch/x86/include/asm/unistd_32.h,添加以下内容:
#define__NR_mycallSYS_ID//SYS_ID表示新添加系统调用
的调用号
并修改文件中的NR_syscalls,将其值增加1
9..修改arxh/x86/include/asm/syscalls.h添加以下内容:
asmlinkagevoidsys_mycall();
10.修改arch/x86/kernel/syscall_table_32.S,添加以下内容:
.longsys_mycall
11.配置内核(仅仅修改localversions即可)——命令:
makemenuconfig
12.编译内核——命令:
make–j4bzImage(开4个线程编译)
13.编译内核模块——命令:
make–j4modules
14.安装内核模块——命令:
makemodules_install
15.安装内核——命令:
makeinstall
16.重启系统,在系统选择页面选择进入自己编译的linux-3.0内核
17.在桌面建立测试的C程序test.c程序内容如下:
#include
intmain(intargc,char*argv[]){syscall(SYS_ID);//SYS_ID表示新添加系统调用的调用号
return0;}
18.编译程序——gcctest.c–oa.out
19.运行程序——./a.out
20.查看内核日志(printk的输出信息在内核日志中):
dmesg
结果:
(1)编译内核成功截图:
(2)编译模块成功截图:
五、实验问题及分析
在进行内核编译时,遇到的困难就是将缺少的程序补入,但因为本省Ubantu所带的编辑器很不好用,在输入过程中就花费了非常大的时间。
但最后经过学长的帮助顺利完成。
在编译期间有经历了一个多小时的时间,最后编译成功。
六、实验心得
通过本次实验,我了解并初步掌握了面向操作命令的接口Shell,学会了简单的shell编码,理解了操作系统调用的运行机制,掌握了创建系统调用的方法。
本次实验通过内核编译,将一组源代码变成操作系统的内核,并由此重新引导系统,这让我们初步了解了操作系统的生成过程。
虽然在实验过程中遇到了不少问题,但最终在学长的帮助下,最终还是成功了。
最后看见自己的实验结果心里还是挺高兴的。
总之,这次实验我们学习了linux系统的使用方法,掌握了一些基本的linux命令,学习了添加系统调用的方法,更深入的了解了操作系统,为我们以后的工作学习打下坚实的基础。
实验二:
进程管理实验
一、实验目的
1、理解进程的概念,知道进程与程序的区别;
2、理解并发执行、进程互斥及进程通信的基本概念;
3、了解并掌握进程管理的机制和操作步骤。
二、实验内容
1、编制实现软中断通信的程序
使用系统调用fork()创建两个子进程,再用系统调用signal()让父进程捕捉键盘上发出的中断信号(即按delete键),当父进程接收到这两个软中断的某一个后,父进程用系统调用kill()向两个子进程分别发出整数值为16和17软中断信号,子进程获得对应软中断信号,然后分别输出下列信息后终止:
Childprocess1iskilledbyparent!
!
Childprocess2iskilledbyparent!
!
父进程调用wait()函数等待两个子进程终止后,输入以下信息,结束进程执行:
Parentprocessiskilled!
!
多运行几次编写的程序,简略分析出现不同结果的原因。
2、编制实现进程的管道通信的程序
使用系统调用pipe()建立一条管道线,两个子进程分别向管道写一句话:
Childprocess1issendingamessage!
Childprocess2issendingamessage!
而父进程则从管道中读出来自于两个子进程的信息,显示在屏幕上。
要求:
父进程先接收子进程P1发来的消息,然后再接收子进程P2发来的消息。
三、实验步骤及结果分析
1、进程的软中断通信
(1)算法流程图:
软中断通信程序流程图
(2)程序源代码:
#include
#include
#include
#include
intwait_flag;
voidstop();
main(){
intpid1,pid2;//定义两个进程号变量
signal(3,stop);//或者signal(14,stop);
while((pid1=fork())==-1);//若创建子进程1不成功,则空循环
if(pid1>0){//子进程创建成功,pid1为进程号
while((pid2=fork())==-1);//创建子进程2
if(pid2>0){
wait_flag=1;
sleep(5);//父进程等待5秒
kill(pid1,16);//杀死进程1
kill(pid2,17);//杀死进程2
wait(0);//等待第1个子进程1结束的信号
wait(0);//等待第2个子进程2结束的信号
printf("\nParentprocessiskilled!
!
\n");
exit(0);//父进程结束
}
else{
wait_flag=1;
signal(17,stop);//等待进程2被杀死的中断号17
printf("\nChildprocess2iskilledbyparent!
!
\n");
exit(0);
}
}
else{
wait_flag=1;
signal(16,stop);//等待进程1被杀死的中断号16
printf("\nChildprocess1iskilledbyparent!
!
\n");
exit(0);
}
}
voidstop(){
wait_flag=0;
}
(3)实验结果:
(4)简要分析
1)signal函数
上述程序中,调用函数signal()都放在一段程序的前面部位,而不是在其他接收信号处。
这是因为signal()的执行起的作用只是为进程指定信号量16和17,以及分配相应的与stop()过程连接的指针。
因而signal()函数必须在程序前面部分执行。
2)wait函数
在父进程中调用第1个wait(0)后,则父进程被阻塞。
进入等待第一个子进程运行结束的队列,等待子进程结束。
当子进程结束后,会产生一个终止状态字,系统会向父进程发出SIGCHLD信号。
当接到信号后,父进程提取子进程的终止状态字,从wait()返回继续执行原程序。
同样的方式,父进程继续执行第二个wait(0),并再次阻塞,等待第2个子进程运行结束。
当第二个子进程运行结束后父进程继续执行剩余的语句。
3)关于exit函数
该函数中每个进程退出时都用了语句exit(0),这是进程的正常终止。
在正常终止时,exit()函数返回进程结束状态。
进程终止时,则由系统内核产生一个代表异常终止原因的终止状态,该进程的父进程都能用wait()得到其终止状态。
在子进程调用exit()后,子进程的结束状态会返回给系统内核,由内核根据状态字生成终止状态,供父进程在wait()中读取数据。
若子进程结束后,父进程还没有读取子进程的终止状态,则子进程就变成了“孤儿进程”,系统进程init会自动“收养”该子进程,成为该子进程的父进程,即父进程标识号变成1,当子进程结束时,init会自动调用wait()读取子进程的遗留数据,从而避免在系统中留下大量的垃圾。
4)结果显示
上述结果中“Childprocess1iskilledbyparent!
!
”和“Childprocess2iskilledbyparent!
!
”相继出现,当运行几次后,谁在前谁在后是随机的。
这是因为:
从进程调度的角度看,子进程被创建后处于就绪态。
此时,父进程和子进程作为两个独立的进程,共享同一个代码段,分别参加调度、执行,直至进程结束。
但是谁会先被调度程序选中执行,则与系统的调度策略和系统当前的资源状态有关,是不确定的。
因此,谁先从fork()函数中返回继续执行后面的语句也是不确定的。
2、进程的软中断通信
(1)算法流程图:
管道通信程序流程图
(2)程序源代码
#include
#include
#include
intpid1,pid2;//定义两个进程变量
main(){
intfd[2];
charOutPipe[100],InPipe[100];//定义两个字符数组
pipe(fd);//创建管道
while((pid1=fork())==-1);//如果进程1创建不成功,则空循环
if(pid1==0){//如果子进程1创建成功,pid1为进程号
lockf(fd[1],1,0);//锁定管道
sprintf(OutPipe,"\nChildprocess1issendingmessage!
\n");//给Outpipe赋值
write(fd[1],OutPipe,50);//向管道写入数据
sleep(5);//等待读进程读出数据
lockf(fd[1],0,0);//解除管道的锁定
exit(0);//结束进程1
}
else{
while((pid2=fork())==-1);//若进程2创建不成功,则空循环
if(pid2==0){
lockf(fd[1],1,0);
sprintf(OutPipe,"\nChildprocess2issendingmessage!
\n");
write(fd[1],OutPipe,50);
sleep(5);
lockf(fd[1],0,0);
exit(0);
}
else{
wait(0);//等待子进程1结束
read(fd[0],InPipe,50);//从管道中读出数据
printf("%s\n",InPipe);//显示读出的数据
wait(0);//等待子进程2结束
read(fd[0],InPipe,50);
printf("%s\n",InPipe);
exit(0);//父进程结束
}
}
}
(3)实验结果
(4)简要分析
管道,是指用于连接一个读进程和一个写进程,以实现它们之间信息的共享文件又称pipe文件。
向管道(共享文件)提供输入的发送进程(即写进程),以字符流形式将大量的数据送入管道;而接收管道输送的接收进程(读进程),可以从管道中接收数据。
为了协调双方的通信,管道通信机制必须提供以下3方面的协调能力:
1)互斥。
当一个进程正在对pipe进程读/写操作时,另一进程必须等待,程序中使用lock(fd[1],1,0)函数实现对管道的加锁操作,用lock(fd[1],0,0)解除管道的锁定。
2)同步。
当写进程把一定数量的数据写入pipe后,便去睡眠等待,直到读进程取走数据后,再把它唤醒。
当读进程试图从一空管道中读取数据时,也应睡眠等待,直至写进程将数据写入管道后,才将其唤醒。
3)判断对方是否存在。
只有确定写进程和读进程都存在的情况下,才能通过管道进行通信。
四、实验心得
通过本次实验,我了解了进程的实质和进程管理的机制,并通过实验操作掌握了有关的知识。
实验是在Linux系统下实现进程从创建到终止的全过程,从中我体会了进程的创建过程、父进程和子进程的关系、进程状态的变化、进程之间的同步机制、进程调度的原理和以信号和管道为代表的进程间通信方式的实现。
当然,在实验过程中还有许多不足,我一定会慢慢改正,提升自我。
实验三存储器管理实验
一、实验目的
1、理解内存页面调度的机理
2、掌握几种理论页面置换算法的实现方法
3、了解HASH数据结构的使用
4、通过实验比较几种调度算法的性能优劣
页面置换算法是虚拟存储管理实现的关键,通过本次实验理解内存页面调度的机制,在模拟实现FIFO、LRU、NRU和OPT几种经典页面置换算法的基础上,比较各种页面置换算法的效率及优缺点,从而了解虚拟存储实现的过程。
二、实验内容
对比以下几种算法的命中率:
1、先进先出算法FIFO(FirstInFirstOut)
2、最近最少使用算法LRU(LeastRecentlyUsed)
3、最近未使用算法NUR(NeverUsedRecently)
4、最佳置换算法OPT(OptimalReplacement)
三、实验分析
1、置换算法原理
FIFO原理简述:
(1)在分配内存页面数(AP)小天进程页面数(PP)时,当然是最先运行的AP个页面放入内存;
(2)这时又需要处理新的页面,则将原来放的内存中的AP个页中最先进入的调出(FIFO),再将新页面放入;
(3)以后如果再有新页面需要调入,则都按上述规则进行。
(4)算法特点:
所使用的内存页面构成一个队列。
LRU算法原理简述
(1)当内存分配页面数(AP)小于进程页面数(PP)时,把最先执行的AP个页面放入内存。
(2)当需调页面进入内存,而当前分配的内存页面全部不空闲时,选择将其中最长时间没有用到的那一页调出,以空出内存来放置新调入的页面(LRU)。
算法特点:
每个页面都有属性来表示有多长时间未被CPU使用的信息。
NUR算法原理简述
所谓“最近未使用”,首先是要对“近”做一个界定,比如CLEAR_PERIOD=50,便是指在CPU最近的50次进程页面处理工作中,都没有处理到的页面。
那么可能会有以下几种情况:
(1)如果这样的页面只有一个,就将其换出,放入需要处理的新页面。
(2)如果有这样的页面不止一个,就在这些页面中任取一个换出(可以是下标最小的或者最小的),放入需要处理的页面。
如果没有一个这样的页面,就随意换出一个页面(可以是下标最小的或者最大的)。
OPT原理简述
所谓的最佳算法是一种理想状况下的算法,它要求先遍历所有的CPU待处理的进程页面序列。
在这些页面中,如果有些已经在内存中,而CPU不再处理的,就将其换出;而有些页面已在内存中而CPU即将处理,就从当前位置算起,取最后才会处理到的页面,将其换出。
OPT算法实现:
为每个进程页面设一个“间隔”属性cDistance表示CPU将在第几步处理到该页面,如果页面不再被CPU处理,可以被设为某个很大的值(如32767),这样每次被换出的就是cDistance最大的那个页面。
(1)初始化。
设置两个数组page[ap]和pagecontrol[pp]分别表示进程页面数和内存分
配的页面数,并产生一个随机数序列main[total_instruction(]这个序列由page[ap]的下标随机构成)表示待处理的进程页面顺序,diseffect置0。
然后扫描整个页面访问序列,对cDistance[TOTAL_VP]数组进行赋值,表示该页面将在第几步被处理。
(2)看序列main[]中是否有下一个元素,如果有,就由main[]中获取一个CPU待处理的页面号,并转(3),如果没有则转(6)。
(3)如果该页面已经在内存中了,就转
(2),否则转(4),同时diseffect加1。
(4)看是否有空闲页面,如果有,就返回页面指针,并转到(5),否则,遍历所有未处理的进程页面序列,如果有位于内存中的页面而以后CPU不再处理的,首将其换出,返回页面指针;如果没有这样的页面,找出CPU将最晚处理的页面,将其换出,并返回该页面指针。
(5)在内存页面和待处理的进程页面之间建立联系,转
(2)。
输出计算1-diseffect/total_instruction*100%的结果,完成。
四、实验步骤及结果
(1)实验源程序:
#include
#include
#include
#include
#include
#include
usingnamespacestd;
#defineINVALID-1
constintTOTAL_INSTRUCTION(320);
constintTOTAL_VP(32);
constintCLEAR_PERIOD(50);
#include"Page.h"
#include"PageControl.h"
#include"Memory.h"intmain()
{
inti;CMemorya;for(i=4;i<=32;i++)
{
a.FIFO(i);
a.LRU(i);a.NUR(i);a.OPT(i);cout<<"\n";
}
return0;
}
#ifndef_MEMORY_H
#define_MEMORY_H
classCMemory
{
public:
CMemory();
voidinitialize(consti