操作系统实验指导书(完整版)

操作系统实验指导书

烟台大学计算机学院

操作系统课程组

2008-9-20

第一部分  操作系统上机指导

 

Linux操作系统环境:

RedHat Enterprise Linux ES release 3 (Taroon Update 1) (2.4.21-9.EL)

Red Flag Linux release 4.0 (HOT) (2.4)

 

登录到系统

常用命令练习

用root账号(超级用户)注册,口令为computer(注意大小写)。注册成功出现#号(超级用户系统提示符,普通用户的系统提示符为$)。

1. 注销(退出)系统:logout 或exit

3.练习使用命令ls(注意Linux命令区分大小写。)

使用ls 查看当前目录内容;使用ls
查看指定目录内容,如/目录,/etc目录

使用ls  –all
查看当前目录内容;使用dir 查看当前目录内容

4.使用cd改变当前目录

cd ..   回到上层目录  ;cd  /   回到根目录

5.pwd  显示当前路径

6.建立目录mkdir

mkdir  目录名 ;   mkdir  /home/s2001/newdir

7.删除目录:rmdir;

8.复制文件cp: 如  cp  文件名1  文件名2

9.移动文件或目录: mv

10.删除文件  rm

11. 显示文件内容:more   (分页显示);

12. 显示文件:cat 文件名   建立文件:cat >文件名,ctrl+d结束输入

使用编辑器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.out],其中gcc是c的编译器。参数:filename.c
是要编译的源文件的名称,outputfilename表示输出文件名称,中括号表示括号内部的内容可输入也可以不输入(中括号本身不再命令行中出现)。如果不输入outputfilename.out,默认的输出文件是a.out

6. 最后一步是运行程序,方法如下:./outputfilename.out

 

添加新用户、修改文件属性

1. 添加新用户(在root下,按默认值回答)

adduser  用户名;如adduser  s2001  ;
以新用户登录到系统

2.修改用户口令  passwd
用户名

3.控制文件属性

使用ls  –l
查看文件属性

改变用户的所有权:chown  用户名 文件名

改变文件权限:chmod  g+w  文件名;chmod  o-r
文件名

或使用数字方式修改:如chmod  644文件名;chmod  755文件名

u (user用户),g ( group组),o (other其他); w 写访问权限,r 读访问权限, x 执行权限

4.查看相关命令的帮助:man
命令名

5.显示当前系统中已注册的用户信息:who

6.显示当前注册的用户信息:whoami

Tip:

Unix文件目录的属性显示格式:

如:-rw-rw-rw-    1     steve   users    138  Apr  5  19:34  readme

drwxrwxrwx  3     steve   users     80  Apr  5  19:43    dir1

三种权限:  owner group others

第二部分  操作系统实验

实验1  Linux基本环境

1、实验目的

(1)熟悉Linux下的基本操作,学会使用各种Shell命令去操作Linux,对Linux有一个感性认识。

(2)学会使用vi编辑器编辑简单的C语言程序,并能对其编译和调试。

2、实验预备内容

(1)参阅相关Linux操作系统的安装手册,熟悉Linux的基本安装和配置;

(2)参阅相关Linux的命令参考手册,熟悉Linux下的操作命令。

3、实验内容

(1) 以root用户身份登陆,并使用“ls”,“cat”“cd”等命令来实现基本的文件操作并观察Linux文件系统的特点;

(2) 使用vi编辑器编写一C程序,并用gcc命令进行编译和链接,并用a.out来进行输出结果。

4、思考

(1)Linux系统在用户登陆和操作界面以及文件系统上有哪些特点?

实验2  进程管理

1、实验目的

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

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

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

(4)了解Linux系统中进程通信的基本原理。

2、实验预备内容

(1)阅读Linux的sched.h源文件,加深对进程管理概念的理解。

(2)阅读Linux的fork.c源文件,分析进程的创建过程。

3、实验内容

(1)进程的创建

编写一段源程序,使系统调用fork()创建两个子进程,当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符:父进程显示字符“a”;子进程分别显示字符“b”和字符“c”。试观察纪录屏幕上的显示结果,并分析原因。

(2)进程的控制

修改已编写的程序,将每个进程输出一个字符改为每个进程输出一句话,在观察程序执行时屏幕出现的现象,并分析原因。

如果在程序中使用调用lockf()来给每一个子进程加锁,可以实现进程之间的互斥,观察并分析出现的现象。

(3)①编写一段程序,使其现实进程的软中断通信。

要求:使用系统调用fork()创建两个子进程,再用系统调用signal()让父进程捕捉键盘上来的中断信号(即按DEL键);当捕捉到中断信号后,父进程用系统调用Kill()向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止:

Child Processll is Killed by Parent!

Child Processl2 is Killed by Parent!

父进程等待两个子进程终止后,输出如下的信息后终止

Parent Process is Killed!

②在上面的程序中增加语句signal (SIGNAL, SIG-IGN) 和signal (SIGQUIT, SIG-IGN), 观察执行结果,并分析原因。

(4)进程的管道通信

编制一段程序,实现进程的管理通信。

使用系统调用pipe()建立一条管道线;两个子进程P1和P2分别向管道中写一句话:

Child 1 is sending a message!

Child 2 is sending a message!

而父进程则从管道中读出来自于两个子进程的信息,显示在屏幕上。

要求父进程先接收子进程P1发来的消息,然后再接收子进程P2发来的消息。

4、思考

(1)系统是怎样创建进程的?

(2)可执行文件加载时进行了哪些处理?

(3)当首次调用新创建进程时,其入口在哪里?

(4)进程通信有什么特点?

实验3 进程间通信

1、实验目的

Linux系统的进程通信机构 (IPC) 允许在任意进程间大批量地交换数据。本实验的目的是了解和熟悉Linux支持的消息通讯机制及信息量机制。

2、实验预备内容

阅读Linux系统的msg.c、sem.c和shm.c等源码文件,熟悉Linux的三种机制。

3、实验内容

(1)消息的创建,发送和接收。

①使用系统调用msgget (), msgsnd (), msgrev (), 及msgctl () 编制一长度为1k的消息的发送和接收程序。

②观察上面的程序,说明控制消息队列系统调用msgctl () 在此起什么作用?

(2)共享存储区的创建、附接和段接。

使用系统调用shmget(),shmat(),sgmdt(),shmctl(),编制一个与上述功能相同的程序。

(3) 比较上述(1),(2)两种消息通信机制中数据传输的时间。

实验4 存储管理

1、实验目的

存储管理的主要功能之一是合理地分配空间。请求页式管理是一种常用的虚拟存储管理技术。

本实验的目的是通过请求页式存储管理中页面置换算法模拟设计,了解虚拟存储技术的技术特点,掌握请求页式存储管理的页面置换算法。

2、实验内容

(1)通过随机数产生一个指令序列,共320条指令。指令的地址按下述原则生成:

①50%的指令是顺序执行的;

②50%的指令是均匀分布在前地址部分;

③50%的指令是均匀分布在后地址部分。

具体的实施方法是:

①在 [0,319] 的指令之间随即选取一起点m;

②顺序执行一条指令,即执行地址为m+1的指令;

③在前地址[0,m+1]中随机选取一条指令并执行,该指令的地址为m′;

④顺序执行一条指令,其地址为 m′+ 1;

⑤在后地址[m′+ 2,319]中随机选取一条指令并执行;

⑥重复上述步骤①-⑤,直到执行320次指令。

(2)将指令序列变换为页地址流

设:①页面大小为1k;

②用户内存容量为4页到32页;

③用户虚存容量为32k。

在用户虚存中,按每k存放10条指令排在虚存地址,即320条指令在虚存中的存放方式为:

第0条-第9条指令为第0页(对应虚存地址为[0,9]);

第10条-第19条指令为第一页(对应虚存地址为[10,19]);

… …

第310条~第319条指令为第31页(对应虚地址为[310,319])。

按以上方式,用户指令可组成32页。

(3)计算并输出下述各种算法在不同内存容量下的命中率。

①先进先出的算法(FIFO);

②最近最少使用算法(LRR);

③最佳淘汰算法(OPT)先淘汰最不常用的页地址;

④最少访问页面算法(LFR);

⑤最近最不经常使用算法(NUR)。

其中③和④为选择内容。

命中率=1-页面失效次数/页地址流长度

在本实验中,页地址流长度为320,页面失效次数为每次访问相应指令时,该指令所对应的页不在内存的次数。

3、随机数产生办法,Linux或UNIX系统提供函数strand()和rand(),分别进行初始化和产生随机数。例如:

srand ();

语句可初始化一个随机数;

a[0]=10*rand()/65535*319+1;

a[1]=10*rand()/65535*a[0];

语句可用来产生a[0]与a[1]中的随机数。

实验5 文件系统设计

1、实验目的

通过一个简单多用户文件系统的设计,加深理解文件系统的内部功能及内部实现。

2、实验内容

为linux系统设计一个简单的二级文件系统。要求做到以下几点:

(1)可以实现下列几条命令(至少4条);

login 用户登陆

dir     列文件目录

create 创建文件

delete 删除文件

open     打开文件

close 关闭文件

read     读文件

write 写文件

(2)列目录时要列出文件名、物理地址、保护码和文件长度;

(3)源文件可以进行读写保护。

3、实验提示

(1)首先应确定文件系统的数据结构:主目录、子目录及活动文件等。主目录和子目录都以文件的形式存放于磁盘,这样便于查找和修改。

(2)用户创建的文件,可以编号存储于磁盘上。如file0,file1,file2...并以编号作为物理地址,在目录中进行登记。

实验6   处理器调度

一、实验内容

选择一个调度算法,实现处理器调度。

二、实验目的

在采用多道程序设计的系统中,往往有若干个进程同时处于就绪状态。当就绪进程个数大于处理器数时,就必须依照某种策略来决定哪些进程优先占用处理器。本实验模拟在单处理器情况下的处理器调度,帮助学生加深了解处理器调度的工作。

三、实验题目

本实验有两个题,学生可选择其中的一题做实验。

第一题:设计一个按优先数调度算法实现处理器调度的程序。

[提示]:

(1) 假定系统有五个进程,每一个进程用一个进程控制块PCB来代表,进程控制块的格式为:


进程名


指针


要求运行时间


优先数


状态

其中,进程名——作为进程的标识,假设五个进程的进程名分别为P1,P2,P3,P4,P5

指针——按优先数的大小把五个进程连成队列,用指针指出下一个进程的进程控制块的首地址,最后一个进程中的指针为“0”。

要求运行时间——假设进程需要运行的单位时间数。

优先数——赋予进程的优先数,调度时总是选取优先数大的进程先执行。

状态——可假设有两种状态,“就绪”状态和“结束”状态。五个进程的初始状态都为“就绪”,用“R”表示,当一个进程运行结束后,它的状态为“结束”,用“E”表示。

(2) 在每次运行你所设计的处理器调度程序之前,为每个进程任意确定它的“优先数”和“要求运行时间”。

(3) 为了调度方便,把五个进程按给定的优先数从大到小连成队列。用一单元指出队首进程,用指针指出队列的连接情况。例:

队首标志

K2


K1


P1


K2


P2


K3


P3


K4


P4


K5


P5


0


K4


K5


K3


K1


2


3


1


2


4


1


5


3


4


2


R


R


R


R


R


PCB1


PCB2


PCB3


PCB4


PCB5

(4) 处理器调度总是选队首进程运行。采用动态改变优先数的办法,进程每运行一次优先数就减“1”。由于本实验是模拟处理器调度,所以,对被选中的进程并不实际的启动运行,而是执行:

优先数-1

要求运行时间-1

来模拟进程的一次运行。

提醒注意的是:在实际的系统中,当一个进程被选中运行时,必须恢复进程的现场,让它占有处理器运行,直到出现等待事件或运行结束。在这里省去了这些工作。

(5) 进程运行一次后,若要求运行时间10,则再将它加入队列(按优先数大小插入,且置队首标志);若要求运行时间=0,则把它的状态修改成“结束”(E),且退出队列。

(6) 若“就绪”状态的进程队列不为空,则重复上面(4)和(5)的步骤,直到所有进程都成为“结束”状态。

(7) 在所设计的程序中应有显示或打印语句,能显示或打印每次被选中进程的进程名以及运行一次后进程队列的变化。

(8) 为五个进程任意确定一组“优先数”和“要求运行时间”,启动所设计的处理器调度程序,显示或打印逐次被选中进程的进程名以及进程控制块的动态变化过程。

第二题:设计一个按时间片轮转法实现处理器调度的程序。

[提示]:

(1) 假定系统有五个进程,每一个进程用一个进程控制块PCB来代表。进程控制块的格式为:


进程名


指针


要求运行时间


已运行时间


状态

其中,进程名——作为进程的标识,假设五个进程的进程名分别为Q1,Q2,Q3,Q4,Q5

指针——进程按顺序排成循环队列,用指针指出下一个进程的进程控制块的首地址,最后一个进程的指针指出第一个进程的进程控制块首地址。

要求运行时间——假设进程需要运行的单位时间数。

已运行时间——假设进程已经运行的单位时间数,初始值为“0”。

状态——有两种状态,“就绪”和“结束”,初始状态都为“就绪”,用“R”表示。当一个进程运行结束后,它的状态为“结束”,用“E”表示。

(2) 每次运行所设计的处理器调度程序前,为每个进程任意确定它的“要求运行时间”。

(3) 把五个进程按顺序排成循环队列,用指针指出队列连接情况。另用一标志单元记录轮到运行的进程。例如,当前轮到P2执行,则有:

标志单元

K2


K1


Q1


K2


Q2


K3


Q3


K4


Q4


K5


Q5


K2


K3


K4


K5


K1


2


3


1


2


4


1


0


0


0


0


R


R


R


R


R


PCB1


PCB2


PCB3


PCB4


PCB5

(4) 处理器调度总是选择标志单元指示的进程运行。由于本实验是模拟处理器调度的功能,所以,对被选中的进程并不实际的启动运行,而是执行:

已运行时间+1

来模拟进程的一次运行,表示进程已经运行过一个单位的时间。

请同学注意:在实际的系统中,当一个进程被选中运行时,必须置上该进程可以运行的时间片值,以及恢复进程的现场,让它占有处理器运行,直到出现等待事件或运行满一个时间片。在这时省去了这些工作,仅用“已运行时间+1”来表示进程已经运行满一个时间片。

(5) 进程运行一次后,应把该进程的进程控制块中的指针值送到标志单元,以指示下一个轮到运行的进程。同时,应判断该进程的要求运行时间与已运行时间,若该进程的要求运行时间1已运行时间,则表示它尚未执行结束,应待到下一轮时再运行。若该进程的要求运行时间=已运行时间,则表示它已经执行结束,应指导它的状态修改成“结束”(E)且退出队列。此时,应把该进程的进程控制块中的指针值送到前面一个进程的指针位置。

(6) 若“就绪”状态的进程队列不为空,则重复上面的(4)和(5)的步骤,直到所有的进程都成为“结束”状态。

(7) 在所设计的程序中应有显示或打印语句,能显示或打印每次选中进程的进程名以及运行一次后进程队列的变化。

(8) 为五个进程任意确定一组“要求运行时间”,启动所设计的处理器调度程序,显示或打印逐次被选中的进程名以及进程控制块的动态变化过程。

四、实验报告

(1) 实验题目。

(2) 程序中使用的数据结构及符号说明。

(3) 流程图。

(4) 打印一份源程序并附上注释。

(5) 打印程序运行时的初值和运行结果。要求如下:

ⅰ 进程控制块的初始状态。

ⅱ 选中运行的进程名以及选中进程运行后的各进程控制块状态。

对于ⅱ要求每选中一个进程运行后都要打印。

实验7   主存储器空间的分配和回收

一、实验内容

主存储器空间的分配和回收。

二、实验目的

一个好的计算机系统不仅要有一个足够容量的、存取速度高的、稳定可靠的主存储器,而且要能合理地分配和使用这些存储空间。当用户提出申请存储器空间时,存储管理必须根据申请者的要求,按一定的策略分析主存空间的使用情况,找出足够的空闲区域分配给申请者。当作业撤离或主动归还主存资源时,则存储管理要收回作业占用的主存空间或归还部分主存空间。主存的分配和回收的实现虽与主存储器的管理方式有关的,通过本实验帮助学生理解在不同的存储管理方式下应怎样实现主存空间的分配和回收。

三、实验题目

本实验模拟在两种存储管理方式下的主存分配和回收。

第一题:在可变分区管理方式下采用最先适应算法实现主存分配和实现主存回收。

[提示]:

可变分区方式是按作业需要的主存空间大小来分割分区的。当要装入一个作业时,根据作业需要的主存量查看是否有足够的空闲空间,若有,则按需要量分割一个分区分配给该作业;若无,则作业不能装入。随着作业的装入、撤离,主存空间被分成许多个分区,有的分区被作业占用,而有的分区是空闲的。例如:


操作系统


作业1


作业3


空闲区


作业2


空闲区

为了 说明哪些区是空闲的,可以用来装入新作业,必须要有一张空闲区说明表,格式如下:


起    址


长    度


状      态


第一栏


14 K


12 K


未 分 配


第二栏


32 K


96 K


未 分 配


M

M


空 表 目


空 表 目


M

M

其中,起址——指出一个空闲区的主存起始地址。

长度——指出从起始地址开始的一个连续空闲的长度。

状态——有两种状态,一种是“未分配”状态,指出对应的由起址指出的某个长度的区域是空闲区;另一种是“空表目”状态,表示表中对应的登记项目是空白(无效),可用来登记新的空闲区(例如,作业撤离后,它所占的区域就成了空闲区,应找一个“空表目”栏登记归还区的起址和长度且修改状态)。由于分区的个数不定,所以空闲区说明表中应有适量的状态为“空表目”的登记栏目,否则造成表格“溢出”无法登记。

上述的这张说明表的登记情况是按提示(1)中的例所装入的三个作业占用的主存区域后填写的。

(2) 当有一个新作业要求装入主存时,必须查空闲区说明表,从中找出一个足够大的空闲区。有时找到的空闲区可能大于作业需要量,这时应把原来的空闲区变成两部分:一部分分给作业占用;另一部分又成为一个较小的空闲区。为了尽量减少由于分割造成的空闲区,而尽量保存高地址部分有较大的连续空闲区域,以利于大型作业的装入。为此,在空闲区说明表中,把每个空闲区按其地址顺序登记,即每个后继的空闲区其起始地址总是比前者大。为了方便查找还可使表格“紧缩”,总是让“空表目”栏集中在表格的后部。

(3) 采用最先适应算法(顺序分配算法)分配主存空间。

按照作业的需要量,查空闲区说明表,顺序查看登记栏,找到第一个能满足要求的空闲区。当空闲区大于需要量时,一部分用来装入作业,另一部分仍为空闲区登记在空闲区说明表中。

由于本实验是模拟主存的分配,所以把主存区分配给作业后并不实际启动装入程序装入作业,而用输出“分配情况”来代替。最先适应分配算法如图4-1。

(4) 当一个作业执行结束撤离时,作业所占的区域应该归还,归还的区域如果与其它空闲区相邻,则应合成一个较大的空闲区,登记在空闲区说明表中。例如,在提示(1)中列举的情况下,如果作业2撤离,归还所占主存区域时,应与上、下相邻的空闲区一起合成一个大的空闲区登记在空闲区说明表中。归还主存时的回收算法如图4-2。

(5) 请按最先适应算法设计主存分配和回收的程序。然后按(1)中假设主存中已装入三个作业,且形成两个空闲区,确定空闲区说明表的初值。现有一个需要主存量为6K的作业4申请装入主存;然后作业3撤离;再作业2撤离。请你为它们进行主存分配和回收,把空闲区说明表的初值以及每次分配或回收后的变化显示出来或打印出来。

第二题:在分页式管理方式下采用位示图来表示主存分配情况,实现主存空间的分配和回收。

[提示]:

(1) 分页式存储器把主存分成大小相等的若干块,作业的信息也按块的大小分页,作业装入主存时可把作业的信息按页分散存放在主存的空闲块中,为了说明主存中哪些块已经被占用,哪些块是尚未分配的空闲块,可用一张位示图来指出。位示图可由若干存储单元来构成,其中每一位与一个物理块对应,用0/1表示对应块为空闲/已占用。

(2) 假设某系统的主存被分成大小相等的64块,则位示图可用8个字节来构成,另用一单元记录当前空闲块数。如果已有第0,1,4,5,6,9,11,13,24,31,共10个主存块被占用了,那么位示图情况如下:


字     位

节    数


0


1


2


3


4


5


6


7


0


1


1


0


0


1


1


1


0


1


0


1


0


1


0


1


0


0


2


0


0


0


0


0


0


0


0


3


1


0


0


0


0


0


0


1


4


0


0


0


0


0


0


0


0


5


0


0


0


0


0


0


0


0


6


0


0


0


0


0


0


0


0


7


0


0


0


0


0


0


0


0

 
 

图4-1  最先适应分配模拟算法

 
 

图4-2  主存回收算法

(3) 当要装入一个作业时,根据作业对主存的需要量,先查当前空闲块数是否能满足作业要求,若不能满足则输出分配不成功。若能满足,则查位示图,找出为“0”的一些位,置上占用标志“1”,从“当前空闲块数”中减去本次占用块数。

按找到的计算出对应的块号,其计算公式为:

块号= j′8+i

其中,j表示找到的是第n个字节,I表示对应的是第n位。

根据分配给作业的块号,为作业建立一张页表,页表格式:


页  号


块  号


0


1


2


M

M

(4) 当一个作业执行结束,归还主存时,根据该作业的页表可以知道应归还的块号,由块号可计算出在位示图中的对应位置,把对应位的占用标志清成“0”,表示对应的块已成为空闲块。归还的块数加入到当前空闲块数中。由块号计算在位示图中的位置的公式如下:

字节号 j=[块号/8]    ([  ]表示取整)

位数   i={块号/8}   ({  }表示取余)

(5) 设计实现主存分配和回收的程序。假定位示图的初始状态如(2)所述,现有一信息量为5页的作业要装入,运行你所设计的分配程序,为作业分配主存且建立页表(格式如(3)所述)。然后假定有另一作业执行结束,它占用的块号为第4,5,6和31块,运行你所设计的回收程序,收回作业归还的主存块。

要求能显示和打印分配或回收前后的位示图和当前空闲块数,对完成一次分配后还要显示或打印为作业建立的页表。

四、实验报告

(1) 实验题目。

(2) 程序中使用的数据结构及符号说明。

(3) 流程图。

(4) 打印一份源程序并附上注释。

(5) 打印程序运行时的初值和运行结果,要求如下:

第一题:打印空闲区说明表的初始状态,作业4的申请量以及为作业4分配后的空闲区说明表状态;再依次打印作业3和作业2的归还量以及回收作业3,作业2所占主存后的空闲区说明表。

第二题:打印位示图和当前空闲块数的初值;要求装入的作业对主存的申请量,为作业分配后的位示图、当前空闲块数和页表;作业归还的块号、回收作业所占主存后的位示图和当前空闲块数。

 

 

 

 

第三部分  操作系统实验指导

实验2 指导

[实验内容]

1.进程的创建

〈任务〉

编写一段程序,使用系统调用fork( )创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符;父进程显示字符“a”,子进程分别显示字符“b”和“c”。试观察记录屏幕上的显示结果,并分析原因。

〈程序〉

#include<stdio.h>

main()

{

int p1,p2;

if(p1=fork())               /*子进程创建成功*/

putchar(‘b‘);

else

{

if(p2=fork())              /*子进程创建成功*/

putchar(‘c‘);

else putchar(‘a‘);           /*父进程执行*/

}

}

<运行结果>

bca(有时会出现abc的任意的排列)

分析:从进程执行并发来看,输出abc的排列都是有可能的。

原因:fork()创建进程所需的时间虽然可能多于输出一个字符的时间,但各个进程的时间片的获得却不是一定是顺序的,所以输出abc的排列都是有可能的。

2.进程的控制

<任务>

修改已编写好的程序,将每个程序的输出由单个字符改为一句话,再观察程序执行时屏幕上出现的现象,并分析其原因。如果在程序中使用系统调用lockf()来给每个程序加锁,可以实现进程之间的互斥,观察并分析出现的现象。

〈程序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);

}

}

}

〈运行结果〉

parent….

son…

grandchild…

grandchild…

或grandchild

…son

…grandchild

…son

…parent

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

〈程序2〉

#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循环输出不会被中断,加锁与不加锁效果不相同。

3.软中断通信

〈任务1〉

编制一段程序,使用系统调用fork()创建两个子进程,再用系统调用signal()让父进程捕捉键盘上来的中断信号(即按ctrl+c键),当捕捉到中断信号后,父进程用系统调用kill()向两个子进程发出信号,子进程捕捉到信号后,分别输出下列信息后终止:

child process1 is killed by parent!

child process2 is killed by parent!

父进程等待两个子进程终止后,输出以下信息后终止:

parent  process  is  killed!

<程序流程图>

〈程序〉

#include<stdio.h>

#include<signal.h>

#include<unistd.h>

void waiting(),stop(),alarming();

int wait_mark;

main()

{

int p1,p2;

if(p1=fork())             /*创建子进程p1*/

{

if(p2=fork())      /*创建子进程p2*/

{

wait_mark=1;

signal(SIGINT,stop);    /*接收到^c信号,转stop*/

signal(SIGALRM,alarming);/*接受SIGALRM

waiting();

kill(p1,16);            /*向p1发软中断信号16*/

kill(p2,17);          /*向p2发软中断信号17*/

wait(0);              /*同步*/

wait(0);

printf("parent process is killed!\n");

exit(0);

}

else

{

wait_mark=1;

signal(17,stop);

signal(SIGINT,SIG_IGN);  /*忽略 ^c信号*/

while (wait_mark!=0);

lockf(1,1,0);

printf("child process2  is killed by parent!\n");

lockf(1,0,0);

exit(0);

}

}

else

{

wait_mark=1;

signal(16,stop);

signal(SIGINT,SIG_IGN);  /*忽略^c信号*/

while (wait_mark!=0)

lockf(1,1,0);

printf("child process1 is killed by parent!\n");

lockf(1,0,0);

exit(0);

}

}

void waiting()

{

sleep(5);

if (wait_mark!=0)

kill(getpid(),SIGALRM);

}

void alarming()

{

wait_mark=0;

}

void stop()

{

wait_mark=0;

}

<运行结果>

不做任何操作等待五秒钟父进程回在子进程县推出后退出,并打印退出的顺序;或者点击ctrl+C后程序退出并打印退出的顺序。

〈任务2〉

在上面的任务1中,增加语句signal(SIGINT,SIG_IGN)和语句signal(SIGQUIT,SIG_IGN),观察执行结果,并分析原因。这里,signal(SIGINT,SIG_IGN)和signal(SIGQUIT,SIG_IGN)分别为忽略键信号以及忽略中断信号。

<程序>

#include<stdio.h>

#include<signal.h>

#include<unistd.h>

int pid1,pid2;

int EndFlag=0;

int pf1=0;

int pf2=0;

void IntDelete()

{

kill(pid1,16);

kill(pid2,17);

}

void Int1()

{

printf("child process 1 is killed !by parent\n");

exit(0);

}

void Int2()

{

printf("child process 2 is killed !by parent\n");

exit(0);

}

main()

{

int exitpid;

if(pid1=fork())

{

if(pid2=fork())

{

signal(SIGINT,IntDelete);

waitpid(-1,&exitpid,0);

waitpid(-1,&exitpid,0);

printf("parent process is killed\n");

exit(0);

}

else

{

signal(SIGINT,SIG_IGN);

signal(17,Int2);

pause();

}

}

else

{

signal(SIGINT,SIG_IGN);

signal(16,Int1);

pause();

}

}

〈运行结果〉

请读者将上述程序输入计算机后,执行并观察。

3.进程的管道通信

〈任务〉

编制一段程序,实现进程的管道通信。使用系统调用pipe()建立一条管道线。两个子进程p1和p2分别向通道个写一句话:

child1 process is sending message!

child2 process is sending message!

而父进程则从管道中读出来自两个进程的信息,显示在屏幕上。

〈程序〉

#include <unistd.h>

#include <signal.h>

#include <stdio.h>

int pid1,pid2;

main( )

{

int fd[2];

char outpipe[100],inpipe[100];

pipe(fd);                       /*创建一个管道*/

while ((pid1=fork( ))==-1);

if(pid1==0)

{

lockf(fd[1],1,0);

sprintf(outpipe,"child 1 process is sending message!");

/*把串放入数组outpipe中*/

write(fd[1],outpipe,50);     /*向管道写长为50字节的串*/

sleep(5);                 /*自我阻塞5秒*/

lockf(fd[1],0,0);

exit(0);

}

else

{

while((pid2=fork( ))==-1);

if(pid2==0)

{

lockf(fd[1],1,0);           /*互斥*/

sprintf(outpipe,"child 2 process is sending message!");

write(fd[1],outpipe,50);

sleep(5);

lockf(fd[1],0,0);

exit(0);

}

else

{

wait(0);              /*同步*/

read(fd[0],inpipe,50);   /*从管道中读长为50字节的串*/

printf("%s\n",inpipe);

wait(0);

read(fd[0],inpipe,50);

printf("%s\n",inpipe);

exit(0);

}

}

}

〈运行结果〉

延迟5秒后显示:

child1 process is sending message!

再延迟5秒:

child2 process is sending message!

〈分析〉

请读者自行完成 。

<思考>

1、程序中的sleep(5)起什么作用?

2、子进程1和2为什么也能对管道进行操作?

实验3指导

[实验内容]

1 消息的创建,发送和接收

〈任务〉

使用系统调用msgget( ), megsnd( ), msgrev( )及msgctl()编制一长度为1K的消息发送和接收的程序 。

〈程序设计〉

(1) 为了便于操作和观察结果,用一个 程序为“引子”,先后fork( )两个子进程,SERVER和CLIENT,进行通信。

(2) SERVER端建立一个Key为75的消息队列,等待其他进程发来的消息。当遇到类型为1的消息,则作为结束信号,取消该队列,并退出SERVER 。SERVER每接收到一个消息后显示一句“(server)received”。

(3) CLIENT端使用Key为75的消息队列,先后发送类型从10到1的消息,然后退出。最后的一个消息,既是 SERVER端需要的结束信号。CLIENT每发送一条消息后显示一句“(client)sent”。

(4) 父进程在 SERVER和 CLIENT均退出后结束。

〈程序〉

#include <stdio.h>

#include <sys/types.h>

#include <sys/msg.h>

#include <sys/ipc.h>

#define MSGKEY 75          /*定义关键词MEGKEY*/

struct msgform                /*消息结构*/

{

long mtype;

char mtexe[100];         /*文本长度*/

}msg;

int msgqid,i;

void CLIENT( )

{

int i;

msgqid=msgget(MSGKEY,0777|IPC_CREAT);

for(i=10;i>=1;i--)

{

msg.mtype=i;

printf("(client)sent\n");

msgsnd(msgqid,&msg,1030,0);       /*发送消息msg入msgid消息队列*/

}

exit(0);

}

void SERVER( )

{

msgqid=msgget(MSGKEY,0777|IPC_CREAT); /*由关键字获得消息队列*/

do

{

msgrcv(msgqid,&msg,1030,0,0);  /*从队列msgid接受消息msg*/

printf("(server)receive\n");

}while(msg.mtype!=1);             /*消息类型为1时,释放队列*/

msgctl(msgqid, IPC_RMID,0);

}

main()

{

if(fork())

{

SERVER();

wait(0);

}

else CLIENT( );

}

<结果>

从理想的结果来说,应当是每当Client发送一个消息后,server接收该消息,Client再发送下一条。也就是说“(Client)sent”和“(server)received”的字样应该在屏幕上交替出现。实际的结果大多是,先由 Client 发送两条消息,然后Server接收一条消息。此后Client

Server交替发送和接收消息.最后一次接收两条消息. Client 和Server 分别发送和接收了10条消息,与预期设想一致

<分析>

message的传送和控制并不保证完全同步,当一个程序不再激活状态的时候,它完全可能继续睡眠,造成上面现象,在多次send message 后才 receive message.这一点有助于理解消息转送的实现机理.

2.共享存储区的创建,附接和断接

<任务>

使用系统调用shmget(),sgmat(),smgdt(),shmctl()编制一个与上述功能相同的程序.

<程序设计>

(1)为了便于操作 和观察结果,用一个 程序为“引子”,先后fork( )两个子进程,SERVER             和 CLIENT,进行通信。

(2)SERVER端建立一个KEY为75的共享区,并将第一个字节置为-1.作为数据空的标志.等待其他进程发来的消息.当该字节的值发生变化时,表示收到了该消息,进行处理.然后再次把它 的值设为-1.如果遇到的值为0,则视为结束信号,取消该队列,并退出SERVER.SERVER每接 收到一次数据后显示”(server)received”.

(3)CLIENT端建立一个为75的共享区,当共享取得第一个字节为-1时, Server端空闲,可发送 请求. CLIENT 随即填入9到0.期间等待Server端再次空闲.进行完这些操作后, CLIENT    退出. CLIENT每发送一次数据后显示”(client)sent”.

(4)父进程在SERVER和CLIENT均退出后结束.

<程序>

#include<sys/types.h>

#include<sys/msg.h>

#include<sys/ipc.h>

#define SHMKEY  75                   /*定义共享区关键词*/

int shmid,i;

int *addr;

CLIENT()

{

int i;

shmid=shmget(SHMKEY,1024, 0777|IPC_CREAT);    /*获取共享区,长度1024,关键词SHMKEY*/

addr=shmat(shmid,0,0);                /*共享区起始地址为addr*/

for(i=9;i>=0;i--)

{

while(*addr!= -1);

printf("(client)sent\n");                 /*打印(client)sent*/

*addr=i;                             /*把i赋给addr*/

}

exit(0);

}

SERVER()

{

do

{

while(*addr = =-1);

printf("(server)received\n%d",*addr);               /*服务进程使用共享区*/

if(*addr!=0)

*addr=-1;

} while(*addr);

wait(0);

shmctl(shmid,IPC_RMID,0);

}

main()

{

shmid=shmget(SHMKEY,1024,0777|IPC_CREAT);  /*创建共享区*/

addr=shmat(shmid,0,0);                         /*共享区起始地址为addr*/

*addr=-1;

if(fork())

{

SERVER();

}

else

{

CLIENT();

}

}

<结果〉

运行的结果和预想的完全一样。但在运行的过程中,发现每当client发送一次数据后,server要等大约0.1秒才有响应。同样,之后client又需要等待大约0.1秒才发送下一个数据。

<分析〉

出现上述的应答延迟的现象是程序设计的问题。当client端发送了数据后,并没有任何措施通知server端数据已经发出,需要由client的查询才能感知。此时,client端并没有放弃系统的控制权,仍然占用CPU的时间片。只有当系统进行调度时,切换到了server进程,再进行应答。这个问题,也同样存在于server端到client的应答过程之中。

3 比较两种消息通信机制中的数据传输的时间

由于两种机制实现的机理和用处都不一样,难以直接进行时间上的比较。如果比较其性能,应更加全面的分析。

(1) 消息队列的建立比共享区的设立消耗的资源少.前者只是一个软件上设定的问题,后者需要对硬件操作,实现内存的映像,当然控制起来比前者复杂.如果每次都重新进行队列或共享的建立,共享区的设立没有什么优势。

(2) 当消息队列和共享区建立好后,共享区的数据传输,受到了系统硬件的支持,不耗费多余的资源;而消息传递,由软件进行控制和实现,需要消耗一定的CPU资源.从这个意义上讲,共享区更适合频繁和大量的数据传输.

(3) 消息的传递,自身就带有同步的控制.当等到消息的时候,进程进入睡眠状态,不再消耗CPU资源.而共享队列如果不借助其他机制进行同步,接受数据的一方必须进行不断的查询,白白浪费了大量的CPU资源.可见消息方式的使用更加灵活.

 

实验4指导

[实验内容]

<任务>

设计一个虚拟存储区和内存工作区,并使用下列算法计算访问命中率.

(1) 进先出的算法(FIFO)

(2) 最近最少使用的算法(LRU)

(3) 最佳淘汰算法(OPT)

(4) 最少访问页面算法(LFU)

(5) 最近最不经常使用算法(NUR)

命中率=(1-页面失效次数)/页地址流长度

<程序设计〉

本实验的程序设计基本上按照实验内容进行。即首先用srand()和rand()函数定义和产生指令序列,然后将指令序列变换成相应的页地址流,并针对不同的算法计算出相应的命中率。相关定义如下:

1 数据结构

(1)页面类型

typedef struct{

int pn,pfn,counter,time;

}pl-type;

其中pn 为页号,pfn为面号, counter为一个周期内访问该页面的次数, time为访问时间.

(2) 页面控制结构

pfc-struct{

int pn,pfn;

struct pfc_struct *next;

}

typedef   struct pfc_struct pfc_type;

pfc_type  pfc_struct[total_vp],*freepf_head,*busypf_head;

pfc_type  *busypf_tail;

其中pfc[total_vp]定义用户进程虚页控制结构,

*freepf_head为空页面头的指针,

*busypf_head为忙页面头的指针,

*busypf_tail为忙页面尾的指针.

2.函数定义

(1)Void initialize( ):初始化函数,给每个相关的页面赋值.

(2)Void FIFO( ):计算使用FIFO算法时的命中率.

(3)Void LRU( ):计算使用LRU算法时的命中率.

(4)Void OPT( ):计算使用OPT算法时的命中率.

(5)Void LFU( ):计算使用LFU算法时的命中率.

(6)Void NUR( ):计算使用NUR算法时的命中率.

3.变量定义

(1)int a[total_instruction]: 指令流数据组.

(2)int page[total_instruction]: 每条指令所属的页号.

(3)int offset[total_instruction]: 每页装入10条指令后取模运算页号偏移值.

(4)int total_pf: 用户进程的内存页面数.

(5)int disaffect: 页面失效次数.

4.程序参考源码及结果

<程序>

#define TRUE 1

#define FALSE 0

#define INVALID -1

#define NULL  0

#define  total_instruction 320     /*指令流长*/

#define  total_vp  32               /*虚页长*/

#define  clear_period  50           /*清0周期*/

typedef struct                      /*页面结构*/

{

int pn;      //页号 logic number

int pfn;     //页面框架号 physical frame number

int counter; //计数器

int time;    //时间

}pl_type;

pl_type pl[total_vp];                      /*页面线性结构---指令序列需要使用地址*/

typedef struct pfc_struct                  /*页面控制结构,调度算法的控制结构*/

{

int pn;

int pfn;

struct pfc_struct *next;

}pfc_type;

pfc_type pfc[total_vp], *freepf_head, *busypf_head, *busypf_tail;

int diseffect,  a[total_instruction]; /* a[]为指令序列*/

int page[total_instruction],  offset[total_instruction];/*地址信息*/

int  initialize(int);

int  FIFO(int);

int  LRU(int);

int  LFU(int);

int  NUR(int); //not use recently

int  OPT(int);

int main( )

{

int s,i,j;

srand(10*getpid());                    /*由于每次运行时进程号不同,故可用来作为初始化随机数队列的“种子”*/

s=(float)319*rand( )/32767/32767/2+1;  /*正态分布*/

for(i=0;i<total_instruction;i+=4)        /*产生指令队列*/

{

if(s<0||s>319)

{

printf("When i==%d,Error,s==%d\n",i,s);

exit(0);

}

a[i]=s;                                   /*任选一指令访问点m*/

a[i+1]=a[i]+1;                            /*顺序执行一条指令*/

a[i+2]=(float)a[i]*rand( )/32767/32767/2; /*执行前地址指令m*/

a[i+3]=a[i+2]+1;                          /*顺序执行一条指令*/

s=(float)(318-a[i+2])*rand( )/32767/32767/2+a[i+2]+2;

if((a[i+2]>318)||(s>319))

printf("a[%d+2],a number which is :%d and s==%d\n",i,a[i+2],s);

}

for (i=0;i<total_instruction;i++) /*将指令序列变换成页地址流*/

{

page[i]=a[i]/10;

offset[i]=a[i]%10;

}

for(i=4;i<=32;i++)   /*用户内存工作区从4个页面到32个页面*/

{

printf("---%2d page frames---\n",i);

FIFO(i);

LRU(i);

LFU(i);

NUR(i);

OPT(i);

}

return 0;

}

/*初始化相关数据结构 total_pf表示内存的块数 */

int initialize(int total_pf)

{

int i;

diseffect=0;

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

{

pl[i].pfn=INVALID;       /*置页面控制结构中的页号,页面为空*/

pl[i].counter=0;         /*页面控制结构中的访问次数为0*/

pl[i].time=-1;           /*访问的时间*/

}

for(i=0;i<total_pf-1;i++) /*建立pfc[i-1]和pfc[i]之间的链接*/

{

pfc[i].next=&pfc[i+1];

pfc[i].pfn=i;

}

pfc[total_pf-1].next=NULL;

pfc[total_pf-1].pfn=total_pf-1;

freepf_head=&pfc[0];         /*空页面队列的头指针为pfc[0]*/

return 0;

}

int FIFO(int total_pf)              /*先进先出算法total_pf:用户进程的内存页面数*/

{

int i,j;

pfc_type *p; /*中间变量*/

initialize(total_pf);         /*初始化相关页面控制用数据结构*/

busypf_head=busypf_tail=NULL; /*忙页面队列头,队列尾链接*/

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

{

if(pl[page[i]].pfn==INVALID)   /*页面失效*/

{

diseffect+=1;                  /*失效次数*/

if(freepf_head==NULL)         /*无空闲页面*/

{

p=busypf_head->next;

pl[busypf_head->pn].pfn=INVALID;

freepf_head=busypf_head;  /*释放忙页面队列的第一个页面*/

freepf_head->next=NULL;  /*表明还是缺页*/

busypf_head=p;

}

p=freepf_head->next;

freepf_head->pn=page[i];

pl[page[i]].pfn=freepf_head->pfn;

freepf_head->next=NULL; /*使busy的尾为null*/

if(busypf_tail==NULL)

{

busypf_tail=busypf_head=freepf_head;

}

else

{

busypf_tail->next=freepf_head;

busypf_tail=freepf_head;

}

freepf_head=p;

}

}

printf("FIFO:%6.4f\n",1-(float)diseffect/320);

return 0;

int LRU (int total_pf)       /*最近最久未使用算法least recently used*/

{

int min,minj,i,j,present_time; /*minj为最小值下标*/

initialize(total_pf);

present_time=0;

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

{

if(pl[page[i]].pfn==INVALID)             /*页面失效*/

{

diseffect++;

if(freepf_head==NULL)              /*无空闲页面*/

{

min=32767; /*设置最大值*/

for(j=0;j<total_vp;j++)            /*找出time的最小值*/

{

if(min>pl[j].time&&pl[j].pfn!=INVALID)

{

min=pl[j].time;

minj=j;

}

}

freepf_head=&pfc[pl[minj].pfn];   //腾出一个单元

pl[minj].pfn=INVALID;

pl[minj].time=0;

freepf_head->next=NULL;

}

pl[page[i]].pfn=freepf_head->pfn;   //有空闲页面,改为有效

pl[page[i]].time=present_time;

freepf_head=freepf_head->next;      //减少一个free 页面

}

else

{

pl[page[i]].time=present_time;        //命中则增加该单元的访问次数

present_time++;

}

}

printf("LRU:%6.4f\n",1-(float)diseffect/320);

return 0;

}

int NUR(int  total_pf )                  /*最近未使用算法Not Used recently count表示*/

{

int i,j,dp,cont_flag,old_dp;

pfc_type *t;

initialize(total_pf);

dp=0;

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

{

if (pl[page[i]].pfn==INVALID)         /*页面失效*/

{

diseffect++;

if(freepf_head==NULL)               /*无空闲页面*/

{

cont_flag=TRUE;

old_dp=dp;

while(cont_flag)

{

if(pl[dp].counter==0&&pl[dp].pfn!=INVALID)

cont_flag=FALSE;

else

{

dp++;

if(dp==total_vp)

dp=0;

if(dp==old_dp)

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

pl[j].counter=0;

}

}

freepf_head=&pfc[pl[dp].pfn];

pl[dp].pfn=INVALID;

freepf_head->next=NULL;

}

pl[page[i]].pfn=freepf_head->pfn;

freepf_head->pn=page[i];

freepf_head=freepf_head->next;

}

else

pl[page[i]].counter=1;

if(i%clear_period==0)

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

pl[j].counter=0;

}

printf("NUR:%6.4f\n",1-(float)diseffect/320);

return 0;

}

int OPT(int total_pf)       /*最佳置换算法*/

{

int i,j, max,maxpage,d,dist[total_vp];

pfc_type *t;

initialize(total_pf);

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

{

if(pl[page[i]].pfn==INVALID)      /*页面失效*/

{

diseffect++;

if(freepf_head==NULL)         /*无空闲页面*/

{

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

{

if(pl[j].pfn!=INVALID)

dist[j]=32767;

else

dist[j]=0;

}

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

{

if((pl[j].pfn!=INVALID)&&(dist[j]==32767))

{

dist[j]=j;

}

}

max=0;

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

if(max<dist[j])

{

max=dist[j];

maxpage=j;

}

freepf_head=&pfc[pl[maxpage].pfn];

freepf_head->next=NULL;

pl[maxpage].pfn=INVALID;

}

pl[page[i]].pfn=freepf_head->pfn;

freepf_head=freepf_head->next;

}

}

printf("OPT:%6.4f\n",1-(float)diseffect/320);

return 0;

}

/*该算法时根据已知的预测未知的,least frequency  Used是最不经常使用置换法*/

int  LFU(int total_pf)

{

int i,j,min,minpage;

pfc_type *t;

initialize(total_pf);

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

{

if(pl[page[i]].pfn==INVALID)      /*页面失效*/

{

diseffect++;

if(freepf_head==NULL)          /*无空闲页面*/

{

min=32767;

/*获取counter的使用用频率最小的内存*/

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

{

if(min>pl[j].counter&&pl[j].pfn!=INVALID)

{

min=pl[j].counter;

minpage=j;

}

}

freepf_head=&pfc[pl[minpage].pfn];

pl[minpage].pfn=INVALID;

pl[minpage].counter=0;

freepf_head->next=NULL;

}

pl[page[i]].pfn=freepf_head->pfn;   //有空闲页面,改为有效

pl[page[i]].counter++;

freepf_head=freepf_head->next;      //减少一个free 页面

}

else

{

pl[page[i]].counter;

pl[page[i]].counter=pl[page[i]].counter+1;

}

}

printf("LFU:%6.4f\n",1-(float)diseffect/320);

return 0;

}

<结果一:〉

4 page framesFIFO:0.2562LRU:0.2531OPT:0.3031LFU:0.2812NUR:0.2812

5 page framesFIFO:0.2969LRU:0.2906OPT:0.3500LFU:0.3219NUR:0.3094

6 page framesFIFO:0.3375LRU:0.3281OPT:0.3844LFU:0.3375NUR:0.3344

7 page framesFIFO:0.3563LRU:0.3563OPT:0.4031LFU:0.3563NUR:0.3500

8 page framesFIFO:0.3937LRU:0.3750OPT:0.4531LFU:0.3937NUR:0.3719

9 page framesFIFO:0.4219LRU:0.4094OPT:0.4844LFU:0.4156NUR:0.4062

10 page framesFIFO:0.4375LRU:0.4313OPT:0.5062LFU:0.4313NUR:0.4250

11 page framesFIFO:0.4813LRU:0.4625OPT:0.5531LFU:0.4500NUR:0.4656

12 page framesFIFO:0.5406LRU:0.4875OPT:0.5687LFU:0.4938NUR:0.4875

13 page framesFIFO:0.5500LRU:0.5188OPT:0.5969LFU:0.5062NUR:0.5437

14 page framesFIFO:0.5594LRU:0.5531OPT:0.6344LFU:0.5281NUR:0.5469

15 page framesFIFO:0.5687LRU:0.5844OPT:0.6687LFU:0.5469NUR:0.5813

16 page framesFIFO:0.5781LRU:0.5938OPT:0.6813LFU:0.5719NUR:0.5969

17 page framesFIFO:0.5906LRU:0.6156OPT:0.6969LFU:0.6156NUR:0.6156

18 page framesFIFO:0.6156LRU:0.6312OPT:0.7156LFU:0.6344NUR:0.6531

19 page framesFIFO:0.6687LRU:0.6656OPT:0.7344LFU:0.6531NUR:0.6719

20 page framesFIFO:0.6875LRU:0.6969OPT:0.7500LFU:0.6719NUR:0.6906

21 page framesFIFO:0.6906LRU:0.7094OPT:0.7688LFU:0.6969NUR:0.7188

22 page framesFIFO:0.7125LRU:0.7219OPT:0.7969LFU:0.7156NUR:0.7344

23 page framesFIFO:0.7156LRU:0.7406OPT:0.8125LFU:0.7250NUR:0.7812

24 page framesFIFO:0.7281LRU:0.7625OPT:0.8187LFU:0.7406NUR:0.7719

25 page framesFIFO:0.7469LRU:0.7750OPT:0.8344LFU:0.7594NUR:0.8000

26 page framesFIFO:0.8125LRU:0.8000OPT:0.8500LFU:0.7812NUR:0.8063

27 page framesFIFO:0.8313LRU:0.8187OPT:0.8594LFU:0.8031NUR:0.8281

28 page framesFIFO:0.8438LRU:0.8375OPT:0.8688LFU:0.8344NUR:0.8469

29 page framesFIFO:0.8688LRU:0.8531OPT:0.8750LFU:0.8562NUR:0.8562

30 page framesFIFO:0.8781LRU:0.8719OPT:0.8781LFU:0.8750NUR:0.8688

31 page framesFIFO:0.8938LRU:0.8750OPT:0.8844LFU:0.8844NUR:0.8906

32 page framesFIFO:0.9000LRU:0.9000OPT:0.9000LFU:0.9000NUR:0.9000

<分析>

从上述结果可知,在内存页面数较少(4~5页)时,五种算法的命中率差别不大,都是30%左右。在内存页面为7~18个页面之间时,5种算法的访内命中率大致在35%~60%之间变化。但是,FIFO算法与OPT算法之间的差别一般在6~10个百分点左右。在内存页面为25~32个页面时,由于用户进程的所有指令基本上都已装入内存,使命中率增加,从而算法之间的差别不大。

比较上述5种算法,以OPT算法的命中率最高,NUR算法次之,再就是LFU算法和LRU算法,其次是FIFO算法。就本问题,在15页之前,FIFO的命中率比LRU的高。

<结果二:>

4 page framesFIFO:0.2594LRU:0.2562OPT:0.2687LFU:0.2437NUR:0.2625

5 page framesFIFO:0.3000LRU:0.3000OPT:0.3000LFU:0.2969NUR:0.2875

6 page framesFIFO:0.3375LRU:0.3281OPT:0.3281LFU:0.3094NUR:0.3281

7 page framesFIFO:0.3563LRU:0.3563OPT:0.3688LFU:0.3312NUR:0.3469

8 page framesFIFO:0.4031LRU:0.4094OPT:0.3875LFU:0.3406NUR:0.3781

9 page framesFIFO:0.4156LRU:0.4281OPT:0.4156LFU:0.3656NUR:0.4125

10 page framesFIFO:0.4281LRU:0.4469OPT:0.4313LFU:0.3750NUR:0.4406

11 page framesFIFO:0.4531LRU:0.4688OPT:0.4594LFU:0.4281NUR:0.4656

12 page framesFIFO:0.4656LRU:0.4813OPT:0.4906LFU:0.4375NUR:0.4938

13 page framesFIFO:0.4750LRU:0.5000OPT:0.5219LFU:0.4625NUR:0.5312

14 page framesFIFO:0.4906LRU:0.5125OPT:0.5375LFU:0.4938NUR:0.5500

15 page framesFIFO:0.5312LRU:0.5250OPT:0.5625LFU:0.5281NUR:0.5563

16 page framesFIFO:0.5406LRU:0.5625OPT:0.5813LFU:0.5531NUR:0.5844

17 page framesFIFO:0.5906LRU:0.5813OPT:0.6188LFU:0.5750NUR:0.6031

18 page framesFIFO:0.6000LRU:0.5906OPT:0.6344LFU:0.5906NUR:0.6250

19 page framesFIFO:0.6312LRU:0.6156OPT:0.6438LFU:0.6219NUR:0.6438

20 page framesFIFO:0.6406LRU:0.6344OPT:0.6625LFU:0.6438NUR:0.6750

21 page framesFIFO:0.6969LRU:0.6594OPT:0.6875LFU:0.6656NUR:0.6937

22 page framesFIFO:0.7000LRU:0.6781OPT:0.7125LFU:0.6813NUR:0.6844

23 page framesFIFO:0.7156LRU:0.6906OPT:0.7312LFU:0.7188NUR:0.6969

24 page framesFIFO:0.7438LRU:0.7219OPT:0.7531LFU:0.7438NUR:0.7469

25 page framesFIFO:0.7594LRU:0.7562OPT:0.7656LFU:0.7656NUR:0.7719

26 page framesFIFO:0.7750LRU:0.7812OPT:0.7937LFU:0.7781NUR:0.7781

27 page framesFIFO:0.8125LRU:0.7969OPT:0.8094LFU:0.8125NUR:0.7969

28 page framesFIFO:0.8344LRU:0.8313OPT:0.8281LFU:0.8313NUR:0.8406

29 page framesFIFO:0.8406LRU:0.8594OPT:0.8531LFU:0.8375NUR:0.8406

30 page framesFIFO:0.8625LRU:0.8781OPT:0.8750LFU:0.8562NUR:0.8594

31 page framesFIFO:0.8812LRU:0.8812OPT:0.8906LFU:0.8781NUR:0.8656

32 page framesFIFO:0.9000LRU:0.9000OPT:0.9000LFU:0.9000NUR:0.9000

<分析>

从结果可以看出,FIFO的命中率竟然比OPT的还高。至于为什么,请探讨。

 

实验5指导

[实验内容]

<任务>

为Linux系统设计一个简单的二级文件系统。要求做到以下几点:

1.可以实现下列几条命令:

login        用户登录

dir          列目录

create       创建文件

delete       删除文件

open         打开文件

close        关闭文件

read         读文件

write        写文件

2.列目录时要列出文件名,物理地址,保护码和文件长度

3.源文件可以进行读写保护

<程序设计>

(1)设计思想

本文件系统采用两级目录,其中第一级对应于用户账号,第二级对应于用户帐号下的文件。另外,为了简便文件系统未考虑文件共享,文件系统安全以及管道文件与设备文件等特殊内容。对这些内容感兴趣的读者,可以在本系统的程序基础上进行扩充。

(2)主要数据结构

a) I节点

struct inode{

struct inode  *i_forw;

struct inode  *i_back;

char I_flag;

unsigned int i_into;      /*磁盘i节点标号*/

unsigned int i_count;     /*引用计数*/

unsigned short di_number; /*关联文件书,当为0时,则删除该文件*/

unsigned short di_mode;   /*存取权限*/

unsigned short di_uid;    /*磁盘i节点用户*/

unsigned short di_gid;    /*磁盘i节点组*/

Unsigned int di_addr[NADDR];          /*物理块号*/

b) 磁盘i结点

Struct dinode

{

unsigned short di_number;        /*关联文件数*/

unsigned short di_mode;          /*存取权限*/

unsigned short di_uid;

unsigned short di_gid;

unsigned long di_size;                /*文件大小*/

unsigned int di_addr[NADDR];          /*物理块号*/

c)目录项结构

Struct direct

{

char d_name[DIRSIZ];             /*目录名*/

unsigned int d_ino;               /*目录号*/

}

d)超级块

Struct filsys

{

unsigned short s_isize;            /*i节点块块数*/

unsigned long s_fsize;             /*数据块块数*/

unsigned int s_nfree;             /*空闲块块数*/

unsigned short s_pfree;           /*空闲块指针*/

unsigned int s_free[NICFREE];     /*空闲块堆栈*/

unsigned int s_ninode;            /*空闲i节点数*/

unsigned short s_pinode;          /*空闲i节点指针*/

unsigned int s_inode[NICINOD];   /*空闲i节点数组*/

unsigned int s_rinode;           /*铭记i节点*/

char s_fmod;                    /*超级块修改标志*/

};

e)用户密码

Struct pwd

{

unsigned short P_uid;

unsigned short P_gid;

char passward[PWOSIZ];

}

f) 目录

Struct dir

{

strut direct direct[DIRNUM];

int size;

}

g).查找i内存节点的hash表

Struct hinode

{

strut inode *iforw;

}

h).系统打开表

Struct file

{

char f_flag; /*文件操作标志*/

unsigned int f_count; /*引用计数*/

struct inode *f_inode; /*指向内存节点*/

unsigned long f_off; /*读/写指针*/

}

i)用户打开表

Struct user

{

unsigned short u_default_mode;

unsigned short u_uid; /*用户标志*/

unsigned short u_gid; /*用户组标志*/

unsigned short u_ofile[NOFILE]; /*用户打开表*/

}

3.主要函数

(1)i节点内容获取函数iget()(详细描述略)。

(2)节点内容释放函数iput()(详细描述略)。

(3)目录创建函数mkdir()(详细描述略)。

(4)目录搜索函数namei()(详细描述略)。

(5)磁盘块分配函数balloc()(详细描述略)。

(6)磁盘块释放函数bfree()(详细描述略)。

(7)分配i节点区函数ialloc()(详细描述略)。

(8)解释结点区函数ifree( )(详细描述略)。

(9)搜索当前目录下文件的函数iname( )(详细描述略)。

(10)访问控制函数access( )(详细描述略)。

(11)显示目录和文件函数_dir( )(详细描述略)。

(12)改变当前目录用函数chdir( )(详细描述略)。

(13)打开文件函数open( )(详细描述略)。

(14)创建文件函数create( )(详细描述略)。

(15)读文件用函数read( )(详细描述略)。

(16)读文件用函数write( )(详细描述略)。

(17)用户登陆函数login( )(详细描述略)。

(18)用户退出函数logout( )(详细描述略)。

(19)文件系统格式化函数format( )(详细描述略)。

(20)进入文件系统函数install( )(详细描述略)。

(21)关闭文件函数close( )(详细描述略)。

(22)退出文件系统函数halt( )(详细描述略)。

(23)文件删除函数delecte( )(详细描述略)。

4.主程序说明

begin

Step1       对磁盘进行格式化

Step2       调用install(),进入文件系统

Step3       调用_dir(),显示当前目录

Step4       调用login(),用户注册

Step5       调用mkdir()和chdir()创建目录

Step6       调用create(),创建文件0

Step7       分配缓冲区

Step8       写文件0

Step9       关闭文件0和释放缓冲

Step10      调用mkdir()和chdir()创建子目录

Step11      调用create(),创建文件1

Step12      分配缓冲区

Step13      写文件1

Step14     关闭文件1和释放缓冲

Step15     调用chdir将当前目录移到上一级

Step16     调用create(),创建文件2

Step17     分配缓冲区

Step18     调用write(),写文件2

Step19     关闭文件1和释放缓冲

Step20     调用delecte(),删除文件0

Step21     调用create(),创建文件1

Step22     为文件3分配缓冲区

Step23     调用write(),写文件2

Step24     关闭文件3并释放缓冲区

Step25     调用open(),打开文件2

Step26     为文件2分配缓冲

Step27     写文件3后关闭文件3

Step28     释放缓冲

Step29     用户退出(logout)

Step30     关闭(halt)

End

由上述的描述过乘可知,该文件系统实际是为用户提供一个解释执行相关命令的环境。主程序中的大部分语句都被用来执行相应的命令。

下面我们给出每个过程的相关C语言程序。读者也可以使用这些子过程,编写一个用Shell控制的文件系统界面。

<程序>

1.编写管理文件makefile

本文件系统程序用编写makefile管理工具进行管理。其内容如下:

***********************************************************************/

/*******************************************

makefile

*******************************************/

filsys:main.o iallfre.o ballfre.o name.o access.o log.o close.o creat.o delete.o dir.o

open.o rdwt.o format.o install.o halt.o cc-o filsys main.o iallfre.o ballfre.o

name.o access.o log.o close.o creat.o delete.o dir.o open.o format.o install.o halt.o

main.o:main.c filesys.h

cc-c main.c

igetput.o: igetput.c filesys.h

cc-c igetput.c

iallfre.o: iallfre.c filesys.h

cc-c iallfre.c

ballfre.o: ballfre.c filesys.h

cc-c ballfre.c

name.o:name.c filesys.h

cc-c name.c

access.o:access.c filesys.h

cc-c access.c

log.o:log.c filesys.h

cc-c log.c

close.o:close.c filesys.h

cc-c close.c

creat.c:creat.c filesys.h

cc-c creat.c

delete.o:delete.c filesys.h

cc-c delete.c

dir.o:dir.c filesys.h

cc-c dir.c

open.o:open.c filesys.h

cc-c open.c

rdwt.o:rdwt.c filesys.h

cc-c rdwt.c

format.o:format.c filesys.h

cc-c format.c

install.o: install.c filesys.h

cc-c install.c

halt.o:halt.c

cc-c halt.c

系统调用函数说明、参数值及定义

1、fork()

创建一个新进程

int fork()

其中返回int取值意义如下:

0:创建子进程,从子进程返回的id值

大于0:从父进程返回的子进程id值

-1:创建失败

2、lockf(files,function,size):

用作锁定文件的某些段或者整个文件,本函数适用的头文件为:

#include<unistd.h>

参数定义:

int lockf(files,function,size)

int files,function;

long size;

其中:files是文件描述符:function是锁定和解锁;1表示锁定,0表示解锁。size是锁定和解锁的字节数,若用0,表示从文件的当前位置到文件尾。

3、msgget(key,flag):

获得一个消息的描述符,该描述符指定一个消息队列以便用于其他系统调用。

该函数使用偷文件如下:

#include<sy/types.h>

#include<sy/ipc.h>

#include<sy/msg.h>

参数定义

int msgget(key,flag)

key_tkey;

int flag;

语法格式:msgqid=msgget(key,flag)

其中:msgid是该系统调用返回的描述符,失败则返回-1;flag 本身由操作允许权和控制命令值相“或”得到。

如:IP_CREAT|0400     是否该队列应被创建;

IP_EXCL |0400     是否该队列的创建应是互斥的;等。

4、msgsnd(id,msgp,size,flag):

发送一消息。

该函数是用头文件如下:

#include<sy/types.h>

#include<sy/ipc.h>

#include<sy/msg.h>

参数定义

int msgnd(id,msgp,size,flag)

int id,size,flag;

struct msgbuf * msgp;

其中:id是返回消息队列的描述符;msgp是指向用户存储区的一个构造体指针,size指示由msgp指向的数据结构中字符数组的长度;即消息的长度。这个数组的最大值由MSG-MAX系统可调用参数来确定。flag规定当核心用尽内部缓冲空间时应执行的动作;若在标志flag中末设置IPC_NOWAIT位,则当该消息队列中字节数超过一最大值时,或系统范围的消息数超过某一最大值时,调用msgsnd进程睡眠。若是设置IPC_NOWAIT,则在此情况下,msgsnd立即返回。

5、msgrcv(id,msgp,size,type,flag):

接受一消息。

该函数调用使用头文件如下:

#include<sy/types.h>

#include<sy/ipc.h>

#include<sy/msg.h>

参数定义

int msgrcv(id,msgp,size,type,flag)

int id,size,type,flag;

struct msgbuf * msgq;

struct sgbuf{long mtpe;chat mtext[];};

语法格式:

count=msgrcv(id,msgp,size,type,flag)

其中:id是用来存放欲接收消息的拥护数据结构的地址;size是msgp中数据数组的大小; type是用户要读的消息类型:

type为0:接收该队列的第一个消息;

type为正:接收类型type的第一个消息;

type为负:接收小于或等于type绝对值的最低类型的第一个消息。

flag规定倘若该队列无消息,核心应当做什么事,如果此时设置了IPC_NOWAIT标志,则立即返回,若在flag中设置了MSG_NOERROR,且所接收的消息大小大于size,核心截断所接受的消息。

count是返回消息正文的字节数。

6、msgctl(id,cmd,buf):

查询一个消息描述符的状态,设置它的状态及删除一个消息描述符。

调用该函数使用头文件如下:

#include<sy/types.h>

#include<sy/ipc.h>

#include<sy/msg.h>

参数定义

int msgctl(id,cmd,buf)

int id,cmd;

struct msgbuf * msgq;

struct msqid_ds * buf;

其中:函数调用成功时返回0,调用不成功时返回-1。id用来识别该消息的描述符;cmd规定命令的类型。

IPC_START将与id相关联的消息队列首标读入buf。

IPC_SET为这个消息序列设置有效的用户和小组标识及操作允许权和字节的数量。

IPC_RMID删除id的消息队列。

buf是含有控制参数或查询结果的用户数据结构的地址。

附:msgid_ds结构定义如下:

struct msgid_ds

{struct ipc_perm msg_perm;   /*许可权结构*/

shot padl[7];                /*由系统使用*/

ushort onsg_qnum;         /*队列上消息数*/

ushort msg_qbytes;         /*队列上最大字节数*/

ushort msg_lspid; /*最后发送消息的PID*/

ushort msg_lrpid; /*最后接收消息的PID*/

time_t msg__stime; /*最后发送消息的时间*/

time_t msg_rtime; /*最后接收消息的时间*/

me_t msg_ctime;     /*最后更改时间*/

};

struct ipc_perm

{ushort uid; /*当前用户id*/

ushort gid;     /*当前进程组id*/

ushort cuid; /*创建用户id*/

ushort cgid /*创建进程组id*/

ushort mode; /*存取许可权*/

{shot patl;long pad2} /*由系统使用*/

};

7、shmget(key,size,flag):

获得一个共享存储区。

该函数使用头文件如下:

#include<sy/types.h>

#include<sy/ipc.h>

#include<sy/shm.h>

语法格式:

shmid=shmaget(key,size,flag)

参数定义:

int shmaget(key,size,flag)

key_t key;

int size,flag;

其中:size是存储区的字节数,key和flag与系统调用msgget中的参数含义相同。

附:

操作允许权 八进制数

用户可读 00400

用户可读 00200

小组可读 00040

小组可读 00020

其他可读 00004

其他可读 00002

控制命令 值

IPC_CREAT 0001000

IPC_EXCL 0002000

如:shmid=shmget(key,size,(IPC_CREAT|0400));

创建一个关键字为key,长度为size的共享存储区。

8、shmat(id,addr,flag):

从逻辑上将一个共享存储区附接到进程的虚拟地址空间上。

该函数调用使用头文件如下:

#include<sy/types.h>

#include<sy/ipc.h>

#include<sy/msg.h>

参数定义:

char * shmat(id,addr,flag)

int id,flag;

char * addr;

语法格式:virtaddr=shmat(id,addr,flag)

其中:id是共享存储区的标识符,addr是用户要使用共享存储区附接的虚地址,若addr是0,系统是否对应用户规定的地址做舍入操作。如果flag中设置了shm_rnd即表示操作系统在必要时舍去这个地址。如果设置了shm_rdonly,即表示只允许读操作。viraddr是附接的虚地址。

9、shmdt(addr):

把一个共享存储区从指定进程的虚地址空间分开。

调用该函数使用头文件:

#include<sy/types.h>

#include<sy/ipc.h>

#include<sy/mhm.h>

参数定义:

int shmdt(addr)

char * addr

其中,当调用成功时,返回0值,调用不成功,返回-1,addr是系统调用shmat所返回的地址。

10、shmctl(id,cmd,buf):

对与共享存储区关联的各种参数进行操作,从而对共享存储区进行控制。

调用该函数使用头文件:

#include<sy/types.h>

#include<sy/ipc.h>

#include<sy/shm.h>

参数定义:

int shmctl(id,cmd,buf)

int id,cmd;

struct shmid_ds * buf;

其中:调用成功返回0,否则返回-1。id为被共享存储区的标识符。cmd规定操作的类型。规定如下:

IPC_STAT:返回包含在指定的shmid相关数据结构中的状态信息,并且把它放置在用户存储区中的*but指针所指的数据结构中。执行此命令的进程必须有读取允许权。

IPC_SET:对于指定的shmid,为它设置有效用户和小组标识和操作存取权。

IPC_RMID:删除指定的shmid以及与它相关的共享存储区的数据结构。

SHM_LOCK:在内存中锁定指定的共享存储区,必须是超级用户才可以进行此项操作。

Buf是一个用户级数据结构地址。

附:

shmid_ds

{struct ipc_perm shm_perm; /*允许权结构*/

int shm_segsz; /*段大小*/

int padl; /*由系统使用;*/

ushort shm_lpid; /*最后操作的进程id;*/

ushort shm_cpid; /*创建者的进程id;*/

ushort shm_nattch; /*当前附界数;*/

short pad2; /*由系统使用;*/

time_t shm_atime; /*最后附接时间*/

time_t shm_dtime; /*最后段接时间*/

time_t shm_ctime; /*最后修改时间*/

}

11、signal(sig,function):

允许调用进程控制软中断信号的处理。

头文件为:

#include<signal.h>

参数定义:

signal(sig,function);

int sig;

void(*func)();

其中:sig的值是:

SIGHVP 挂起

SIGINT 键盘按^c键或break键

SIGQUIT 键盘按quit键

SIGILL 非法指令

SIGIOT IOT指令

SIGEMT EMT指令

SIGFPE 浮点运算溢出

SIGKILL 要求终止进程

SIGBUS 总线错

SIGSEGV 段违例

SIGSYS 系统调用参数错

SIGPIPE 向无读者管道上写

SIGALRM 闹钟

SIGTERM 软件终结

SIGUSRI 用户定义信号

SIGUSR2 第二个用户定义信号

SIGCLD 子进程死

SIGPWR 电源故障

function的解释如下:

SIG_DEL:缺省操作。对除SIGPWR和SIGCLD外所有信号的缺省操作是进程终结对信号 SIGQUIT,SIGILL,SIGTRA,SIGIOT,SIGEMT,SIGFPE,SIGBUS,SIGSEGV和SIGSYS它产生一内存映像文件。

SIG_IGN:忽视该信号的出现。

Function:在该进程中的一个函数地址,在核心返回用户态时,它以软中断信号的序号作为参数调用该函数,对除了信号SIGILL,SIGTRAP和SIGTWR以外的信号,核心自动地重新设置软中断信号处理程序的值为SIG_DEL,一个进程不能捕获SIGKILL信号。

时间: 2024-10-13 02:02:13

操作系统实验指导书(完整版)的相关文章

web实验指导书和课后习题参考答案

实验指导书 :http://course.baidu.com/view/daf55bd026fff705cc170add.html 课后习题参考答案:http://wenku.baidu.com/link?url=LJ9YiTw0GISMKZDl0yik8IBFnEZvgrZBsLMyZ8leqWDnRSRKvZfhEf2r39TNPzVLLEmSy92uVCQIJFKdArozoOBL60yg0v9MR5xcbTPESAm

MSTP+OSPF+VRRP综合实验指导书

(一)拓扑描述: 本次实验采用MSTP+OSPF+VRRP技术,提供网络高可靠性.稳定性.MSTP(多生成树实例)主要解决网络中(SW5-SW4-SW6)设备间的环路问题,对接入交换机双上行链路进行备份:对业务流进行负载分担.OSPF(开放最短路径优先协议)动态路由技术,处理(SW5-SW4-SW7)之间的路由学习,计算出多条路径,实现出口链路负载:vrrp(虚拟网关路由协议)通过将两台核心交换机上面的地址虚拟出来一个虚拟IP地址作为用户提供网关服务.核心层交换机任意一台出现故障不会影响用户网络

RIP实验指导书

(一)配置各设备接口IP R1 interface LoopBack0 ip address 192.168.1.1 255.255.255.255 # interface LoopBack1 ip address 192.168.2.1 255.255.255.255 interface GigabitEthernet0/0/0 ip address 10.0.0.1 255.255.255.0 R2 interface GigabitEthernet0/0/0 ip address 10.0

北航操作系统实验2019:Lab4-1流程梳理

北航操作系统实验2019:Lab4-1流程梳理 前言 操作系统的实验课实在令人头秃.我们需要在两周时间内学习相关知识.读懂指导书.读懂代码.补全代码.处理玄学bug和祖传bug,以及回答令人窒息的思考题.可以说,这门课的要求非常高,就个人感觉,远比计算机组成实验课要难受. 一方面,想要达到细致理解操作系统每个实现细节,非常困难,需要大量时间和经历的投入:但另一方面,如果我们能够理解了操作系统实现的每个细节,我们的水平也会有大幅度的提升.在这里,我记录下本次实验课下我的学习经历,如果有不对的地方,

Alien Skin Exposure v6.x 最新通用完整版汉化补丁

    完整汉化版说明: 我于2014-06-19上传的"Alien Skin Exposure 6.x 通用汉化补丁",有些网友反映预设不能显示中文,调查了一下:预设能显示中文的,操作系统基本上是完整版,精简版.克隆版的系统由于被精简了某些文件,所有不能正常显示.为了这些朋友能正常使用,我把汉化包重新编辑了一下,在几个不同操作系统的电脑上做了实验,全部通过.这次保你满意,祝你成功! Alien Skin Exposure v6.0 是一款专业的PS胶片调色滤镜软件,使用Alien S

Linux下搭建Apache服务器(完整版)

Linux下搭建Apache服务器(完整版) 什么是Apache? Apache Licence是著名的非盈利开源组织Apache采用的协议.该协议和BSD类似,同样鼓励代码共享和尊重原作者的著作权,同样允许代码修改,再发布(作为开源或商业软件).需要满足的条件也和BSD类似 Apache主要特点 1.开放源代码.跨平台应用 2.支持多种网页编程语言 3.模块化设计 .运行稳定.良好的安全性 Apache软件版本 1.X  1.目前最高版本是1.3,运行稳定  2.向下兼容性较好,但缺乏一些较新

[操作系统实验lab4]实验报告

昨天跟老师建议了OS实验改革的事情,感觉助教老师给的指导书挺坑哈,代码注释也不全.我也算沦落到看别人家的源码了... 我参考的源码注释是:https://github.com/benwei/MIT-JOS/ 这个源码质量暂且不评价,但这个注释质量真心不错!!!良心注释啊!!! 本不想去找源码注释啥来看的,毕竟可能一不小心就抄袭了源码的思想?不知道HT和WLM是怎么写的,他们做的都好快啊=.=难道只有我一个人做OS实验的周期是1~2周吗... 哎,不吐槽了,这篇文章留着慢慢更,不着急.感觉在鸣神的

sed实例精解--例说sed完整版

原文地址:sed实例精解--例说sed完整版 作者:xiaozhenggang 最近在学习shell,怕学了后面忘了前面的就把学习和实验的过程记录下来了.这里是关于sed的,前面有三四篇分开的,现在都把它们弄到了一起,并做了一些调整,二十多页,有点长啦.不过大部分都是例子来着,呵呵. 在电脑前坐太久了还真是不行,脖子都歪啦!强烈建议各位找点时间多动动,多动动!还是身体重要嘛!!! 我的实验环境是: fedora 14 ,bash. 在实验中遇到了一些问题,都在后面的例子中提到啦.有些问题纠结了好

凯文&#183;凯利最新演讲完整版:未来的十二个趋势

凯文·凯利最新演讲完整版:未来的十二个趋势 凯文·凯利(KK)<连线>(Wired)杂志创始主编.著有<失控>.<科技想要什么>.<技术元素>.<必然>. KK在深圳分享了未来将发生的十二个趋势: 一.所有的产业都在向分散式结构靠拢 1.“个体专家”分散式: 世界正在发生着翻天覆地的变化,在变动和未知的情况下,就没有所谓的专家了,每一个人都可以做出一些变革和创新,也就都有可能成为专家. 2.企业组织结构分散式: 层级化结构变成分散式的网络结构是一