OS实验指导书(蒋剑修改) - 图文

更新时间:2023-03-09 21:58:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

操作系统原理实验指导书

(讨论稿)

操作系统课程组 2010年2月12日

目 录

概 述............................................................................................................................ 1 实验1 Linux操作系统安装与命令使用 .................................................................. 2 实验2 Linux环境下C语言使用、编译与调试 ................................................... 14 实验3 观察Linux进程的异步并发执行 ............................................................... 18 实验4 观察Linux进程的同步与互斥 ................................................................... 22 实验5 观察Linux进程间的通信 ........................................................................... 26 实验6 观察内存分配结果....................................................................................... 29 实验7 进程调度模拟程序设计............................................................................... 31 实验8 页面置换模拟程序设计............................................................................... 35 实验9 文件系统模拟程序设计............................................................................... 47 实验10 分析Linux进程调度程序 ........................................................................... 51 附录1 /USR/SRC/LINUX/KERNEL/SCHED.C ..................................................... 52

I

概 述

操作系统是一门理论性和实践性都很强的课程。要学好操作系统的设计原理,除了听课、看书、做习题外,最好的方法就是在实践中进行,包括使用操作系统、观察操作系统行为、自己设计小型系统/模块或模拟算法、阅读和分析已有操作系统的源代码等。

本教材安排的实验内容按深度可分为四个层次,即:使用级、观察级、实现级和代码阅读级

(1) 使用级:是指如何使用操作系统,包括对命令、系统调用和系统文件的使用。 (2) 观察级:是指通过使用级的接口,从外部观察操作系统的内部工作过程和结构。 (3) 实现级:是指编程模拟实现操作系统某些功能模块。

(4) 代码阅读级:是指对操作系统源代码进行分析,以加深对操作系统实现原理的了解。 本课程实验所用操作系统平台为Red Hat Linux 9.0,具体实验安排如下: 实验模块 使用级 实验内容 Linux操作系统安装与命令使用 Linux环境下C语言使用、编译与调试 观察Linux进程的异步并发执行 观察级 观察Linux进程的同步与互斥 观察Linux进程间的通信 观察内存分配结果 进程调度模拟程序设计 实现级 页面置换模拟程序设计 文件系统模拟程序设计 代码阅读级 分析Linux进程调度程序

要求 必做 必做 必做 必做 必做 选做 必做 必做 必做 选做 学时 2 2 2 2 2 - 2 2 2 - 1

实验1 Linux操作系统安装与命令使用

一、实验目的

在供实验的微机上安装Linux操作系统,后续实验都将在此环境上进行。通过实验,要求:

1.了解硬件资源要求 2.学会安装Linux系统 3.学会启动Linux系统

4.学会登录和退出Linux系统 5.熟悉Linux常用命令及使用格式 6.掌握在Linux使用U盘方法

二、实验内容

1. 将Linux系统安装到本地硬盘(本地及虚拟机安装方式) 2. 熟悉开机后登录和退出Linux系统过程;

3. 熟悉Linux基本命令使用,如ls、cat、ps、df、find、grep、cd、more、cp、rm、kill、at、vi、cc、man、help等;

4. 用mount命令把U盘的安装到/mnt目录(可能需要root用户口令,请勿乱用);用umount命令把U盘从系统中卸载掉。

三、实验指导

1. 主要安装步骤

(1)如果BIOS支持光盘启动,则插入Linux安装光盘,重新启动计算机。 如果从DOS环境启动,则在DOS提示符下执行批处理命令,如autoboot ? 或者,准备启动软盘,插入并重新启动计算机。 (2)对硬盘分区,留出交换空间和文件系统的空间。 (3)按提示分阶段装入系统。 (4)配置系统。

注意:若要与Linux并存于同一硬盘上,则宜先安装Windows。按屏幕提示选择合适的文件系统时,建议选择NTFS。输入的管理员密码要记住。

2. 本地安装方法

(请蒋老师帮补充,有具体步骤)

在已经安装了windows xp系统的机器上再安装一个linux系统,采用光盘安装方法。Red Hat Linux 9的cd安装盘共有3张。关键安装步骤如下:

?在windows系统中清出一个空闲的分区(8~10G就够了,建议空出最后一个分区,并做好相关的文件备份工作)。在本例中,假定空闲分区为F分区。 补充知识:

①DOS分区可以分为有3种类型:主分区、扩展分区和逻辑分区。一块硬盘最多可以有4个主分区,或是3个主分区1个扩展分区,且一块硬盘只能有1个扩展分区,在这个扩展分区内可以划分多个逻辑分区。主分区与扩展分区是平级的,扩展分区本

2

身无法用来存放数据,要使用它必须将其分成若干个逻辑分区。我们通常说的C分区是主分区,而D、E、F??等分区为逻辑分区。见下图:

②在UNIX/LINUX系统中,将所有的设备都当作一个文件,放在/dev目录下。用户用文件名(如/dev/hda)来访问设备,磁盘也是一样。磁盘的设备名称如下: a) 系统第1个IDE接口上的硬盘的名称为/dev/hda; b) 系统第2个IDE接口上的硬盘的名称为/dev/hdb; c) 系统第1个SCSI接口上的硬盘的名称为/dev/sda;

以此类推,在每块硬盘上的分区所使用的数字编号表示,如:

a) 系统第1个IDE接口上的硬盘的第1个分区名称为/dev/hda1;

b) 系统第1个IDE接口上的硬盘的第5个分区名为/dev/hda5(逻辑分区第1个编号); c) 系统第2个SCSI接口上的硬盘的第1个分区名称为/dev/sdb1;

注意:编号1~4留给主分区或扩展分区使用,逻辑分区编号从5开始。如下图所示:

③所以,本例中的空闲DOS分区F分区,在linux系统中应该叫/dev/hda7。

?将linux第一张安装盘放入光驱,重启系统,修改BIOS设置,将第一启动设备设为光盘启动。首先看到如下安装提示选项。

按enter键进入图形界面安装方式

3

②随后出现以下界面:

是否测试安装CD的内容的完整性,选“OK”开始测试安装CD;选“Skip”不测试安装CD。可以skip跳过,以节省时间。

③选择安装向导所用语言(不是安装系统所用语言),就选“简体中文(简体中文)

④选择键盘类型,一般的键盘多为美式键盘“U.S English”,选择好后,点击“下一步”

4

⑤选择安装类型,可以选“个人桌面”,不同的安装类型只是不同的软件包组合,彼此没有本质区别。

⑥磁盘分区设置是关键的一步,不慎重的话会丢失硬盘有用数据,请注意。

第一种选项“自动分区”有可能会删除现有分区,因而不适用于我们现在探讨的本地安装,故对本地安装来说宜采用第二种选项,即“手工分区”。 若为VMware虚拟机安装,则两种方式都能采用;自动分区操作简单,而手工分区则更优化和专业。

⑦选择“用Disk Druid手工分区”,点击“下一步”显示如下图,将其中的/dev/hda7一项选中后,点选上部的“删除”按钮删除此项,以便释放其所占据的空间,进而在其上安装linux系统的各个分区。

/dev/hda7即为本例的前面步骤中已经设为空闲区的F分区,并准备在其上安装linux系统各分区。

5

⑧在“步骤⑦”的示意图中选中删除/dev/hda7而释放的空闲空间,点选图上部的“新建”按钮,按下图逐个新建linux各分区。

SWAP分区的文件系统类型只能选“swap”,其他分区的文件类型都是“ext3”

各分区的相关指标可以参考下列磁盘分区方案: a) 最简单的分区方案

1) /分区(建议大小:4G)

2) SWAP分区(建议大小:物理内存的2倍) b) 较安全的分区方案

1) SWAP分区:用于实现虚拟内存(建议大小:物理内存的2倍)。 2) /分区:存放系统命令和用户数据等(建议大小:1GB)。

3) /boot分区:存放与Linux启动相关的程序(建议大小:100MB)。 4) /usr分区:存放Linux的应用程序(建议大小:3~5GB)。 5) /var分区:存放系统中经常变化的数据(建议大小:1GB)。 6) /tmp分区:存放系统临时文件(建议大小:1GB)。

7) /home分区:存放普通用户的数据(建议大小:所有磁盘剩余空间)。 注意:SWAP分区的文件系统类型只能选“swap”,其他分区的文件类型都是“ext3”

⑨配置引导程序:

引导程序有GRUB和LILO两种,默认的引导程序为GRUB,为目前所常用,不用修改。

6

⑩选择系统默认语言,选中“Chinese(P.R.of China)”简体中文

设置根口令即root管理员密码,root帐号在系统中具有最高权根,平时登陆系统一般不用该帐号。

此步很重要:个人桌面默认软件包安装选择,一般用户使用默认的“接收当前软件包列表”即可。但如需要在linux环境下进行c语言编程,则必须选择“定制要安装的软件包集合”。则亦可在安装完成后,系统运行“redhat-config-package”工具来添加/删除软件。

我们的实验在linux环境下进行c语言编程,需要使用gcc编译器,该编译器默认不安装。 故此处必须选“定制安装”,然后在后续操作中安装c编译器gcc。

7

在上一步选择了“定制安装”选项之后,弹出如下对话框,一定要勾选“开发”栏的“开发工具”选项,其中就包含gcc编译器,如有必要,还可以勾选“内核开发”选项。

如需要在Linux环境下进行c语言编程,必须勾选该项。

点击“开发工具”项旁边的细节,可以看到包含gcc。

gcc编译器

最后,进入安装阶段。

8

至此,关键步骤基本结束。

注意:以上步骤为关键步骤,其实安装过程还有其他一些步骤,如:网络配置、显示器配置、屏幕分辨率配置、创建普通用户帐号(包括用户名和密码),日期和时间配置等等。此处不再详述。

安装完成后,就能以windows xp和Red Hat Linux 9双系统启动。

4. Linux登录与退出 (1) 登录

在Linux系统提示符$下 login: (输入username) password:(输入密码)

(2) 退出

在Linux系统提示符$下,输入logout、exit或shutdown 。

例:$ logout

5. Linux常用基本命令介绍 (1) 目录操作

和DOS相似,Linux采用树型目录管理结构,由根目录(/)开始一层层将子目录建下去,各子目录以 / 隔开。用户login后,工作目录的位置称为home directory,由系统管理员设定。?~‘符号代表自己的home directory,例如 ~/myfile 是指自己home目录下myfile这个文件。

Linux的通配符有三种:‘*‘ 和 ‘?‘ 用法与DOS相同, ?-? 代表区间内的任一字符,如test[0-5]即代表test0,test1,……,test5的集合。

① 显示目录文件 ls

执行格式: ls [-atFlgR] [name] (name可为文件或目录名称) 例:ls 显示出当前目录下的文件

ls -a 显示出包含隐藏文件的所有文件 ls -t 按照文件最后修改时间显示文件 ls -F 显示出当前目录下的文件及其类型

ls -l 显示目录下所有文件的许可权、拥有者、文件大小、修改时间及名称 ls -lg 同上

ls -R 显示出该目录及其子目录下的文件

说明:ls与其它命令搭配使用可以生出很多技巧(最简单的如\,更多用法请输入ls --help查看,其它命令的更多用法请输入 命令名 --help 查看.

② 建新目录 mkdir

执行格式: mkdir directory-name

例: mkdir dir1 (新建一名为dir1的目录) ③删除目录 rmdir

执行格式: rmdir directory-name 或 rm directory-name

例:rmdir dir1 删除目录dir1,但它必须是空目录,否则无法删除 rm -r dir1 删除目录dir1及其下所有文件及子目录

rm -rf dir1 不管是否空目录,统统删除,而且不给出提示,使用时要小心

④ 改变工作目录位置 cd

执行格式: cd [name]

例: cd 改变目录位置至用户login时的working directory cd dir1 改变目录位置,至dir1目录

cd ~user 改变目录位置,至用户的working directory cd .. 改变目录位置,至当前目录的上层目录

cd ../user 改变目录位置,至上一级目录下的user目录

cd /dir-name1/dir-name2 改变目录位置,至绝对路径(Full path)

9

cd - 回到进入当前目录前的上一个目录

⑤ 显示当前所在目录 pwd 执行格式: pwd ⑥ 查看目录大小du

执行格式: du [-s] directory

例: du dir1 显示目录dir1及其子目录容量(以kb为单位) du -s dir1 显示目录dir1的总容量 ⑦ 显示环境变量

echo $HOME 显示家目录

echo $PATH 显示可执行文件搜索路径

env 显示所有环境变量(可能很多,最好用\等)

⑧修改环境变量,在bash下用export,如: export PATH=$PATH:/usr/local/bin

想知道export的具体用法,可以用shell的help命令:help export

(2)文件操作 ① 查看文件(可以是二进制的)内容 cat

执行格式:cat filename或more filename 或cat filename|more

例: cat file1 以连续显示方式,查看文件file1的内容

more file1

或 cat file1|more 以分页方式查看文件的内容 ② 删除文件 rm

执行格式: rm filename 例: rm file?

rm f*

③ 复制文件 cp

执行格式: cp [-r] source destination

例: cp file1 file2 将file1复制成file2

cp file1 dir1 将file1复制到目录dir1 cp /tmp/file1 将file1复制到当前目录

cp /tmp/file1 file2 将file1 复制到当前目录名为file2

cp –r dir1 dir2 (recursive copy)复制整个目录。

④ 移动或更改文件、目录名称mv

执行格式: mv source destination

例: mv file1 file2 将文件file1,更名为file2

mv file1 dir1 将文件file1,移到目录dir1下

mv dir1 dir2

⑤ 比较文件(可以是二进制的)或目录的内容 diff

执行格式: diff [-r] name1 name2 (name1、name2同为文件或目录) 例: diff file1 file2 比较file1与file2的不同处

diff -r dir1 dir2 比较dir1与dir2的不同处

⑥ 文件中字符串的查找 grep 执行格式: grep string file

例: grep abc file1 查找并列出串abc所在的整行文字 ⑦ 文件或命令的路径寻找

执行格式一:whereis command 显示命令的路径

执行格式二:which command 显示路径及使用者所定义的别名 执行格式三:whatis command 显示命令的功能摘要 执行格式四:find search -path -name filename -print

搜寻指定路径下某文件的路径

10

执行格式五:locate filename

根据系统预先生成的文件/目录数据库(/var/lib/slocate/slocate.db)查找匹配的文件/目录,查找速度很快,如果有刚进行的文件改变而系统未到执行定时更新数据库的时间,可以打入updatedb命令手动更新.

⑧ 建立文件或目录的链接 ln

例: ln source target1 建立source文件(已存在)的硬链接,命名为target1 ln -s source target2 建立source文件的符号链接,命名为target2

(3) 系统询问与权限口令 ① 查看系统中的使用者 执行格式: who ② 查看username

执行格式: who am I 查看自己的username ③ 改变自己的username的帐号与口令 su 执行格式: su username

例: su username 输入帐号 password 输入密码

④ 文件属性的设置 chmod

改变文件或目录的读、写、执行的允许权

执行格式: chmod [-R] mode name

其中:[-R]为递归处理,将指定目录下所有文件及子目录一并处理

mode为3-8位数字,是文件/目录读、写、执行允许权的缩写(r:read,数字代号为\ w:write,数字代号为\ x:execute,数字代号为\

mode: rwx rwx rwx user group other 缩写: (u) (g) (o)

例:chmod 755 dir1 将目录dir1设定成任何人皆有读取及执行的权利,但只有拥有者可作写修改。其中7=4+2+1,5=4+1

chmod 700 file1 将file1设为拥有者可以读、写和执行 chmod o+x file2 将file2,增加拥有者可执行的权利

chmod g+x file3 将file3,增加组使用者可执行的权利 chmod o-r file4 将file4,除去其它使用者可读取的权利

⑤ 改变文件或目录所有权 chown

执行格式: chown [-R] username name

例: chown user file1 将文件file1改为user所有 chown .fox file1 将文件file1改为fox组所有

chown user.fox file1 将文件file1改为fox组的user所有

chown -R user dir1 将目录dir1及其下所有文件和子目录,改为user 所有 ⑥ 检查用户所在组名称 groups 执行格式: groups ⑦ 改变文件或目录的组拥有权 chgrp

执行格式:chgrp [-R] groupname name 例:chgrp vlsi file1 将文件file1改为vlsi组所有

chgrp -R image dir1 将目录dir1及其下所有文件和子目录,改为image群组 ⑧ 改变文件或目录的最后修改时间 touch 执行格式: touch name (4) 进程操作 ① 查看系统目前的进程 ps 执行格式:ps [-aux]

例: ps 或ps -x 查看系统中属于自己的process

11

ps -au 查看系统中所有使用者的process

ps -aux 查看系统中包含系统内部及所有使用者的process

ps -aux|grep apache 找出系统中运行的所有名称中带有\串的process ② 查看正在background中执行的process 执行格式: jobs

③ 结束或终止进程 kill

执行格式: kill [-9] PID (PID为利用ps命令所查出的process ID) 例: klill 456

或 kill -9 456 终止process ID 为456的process ④ 后台(background)执行process command的命令

执行格式: command & (在命令后加上 &) 例: gcc file1 & 在后台编译file1.c 注意:按下^Z,暂停正在执行的process。键入‖bg‖,将所暂停的process置入background中继续执行。

例: gcc file1 & ^Z stopped bg ⑤ 结束或终止在background中的进程 kill 执行格式: kill %n

例: kill %1 终止在background中的第一个job kill %2 终止在background中的第二个job ⑥ 显示系统中程序的执行状态

6. 使用mount和unmount安装和卸载U盘 (请蒋老师补充具体步骤)

在redhat linux 9环境中使用U盘的步骤如下1:

?创建U盘的挂载点:mkdir /mnt/usb2,执行如下: [root@localhost root]# mkdir /mnt/usb 1

对redhat linux 9来说必须执行下述步骤来使用U盘,其关键是将U盘模拟成SCSI设备;但在较新的Fedora Core版本中已经能直接识别U盘,无需下述步骤 2

此处挂载点必须在/mnt目录下,而/mnt下的‖usb‖子目录由用户自己命名。

12

?将U盘插入机器(若为虚拟机上的linux系统,插入前须确定是虚拟机而不是宿主机处于主控状态),然后执行:fdisk –l,以查看文件系统:

[root@localhost root]# fdisk –l Disk /dev/sda: 4294 MB, 4294967296 bytes 255 heads, 63 sectors/track, 522 cylinders Units = cylinders of 16065 * 512 = 8225280 bytes Device Boot Start End Blocks Id System /dev/sda1 * 1 13 104391 83 Linux /dev/sda2 14 457 3566430 83 Linux /dev/sda3 458 522 522112+ 82 Linux swap Disk /dev/sdb: 4008 MB, 4008706048 bytes 124 heads, 62 sectors/track, 1018 cylinders Units = cylinders of 7688 * 512 = 3936256 bytes /dev/sdb即为挂载的U盘,之所以被命名为sdb,是因为VMware虚拟机默认将其虚拟硬盘模拟成SCSI硬盘,而SCSI设备在linux硬件命名中被称为sd,故虚拟机硬盘被称为sda;另一方面,USB设备也被模拟成SCSI设备,所以后插入的U盘就只能叫sdb了。

?用命令mount将/dev/sdb(也就是插入的U盘)挂载到刚刚建立的挂载点/mnt/usb上: [root@localhost root]# mount /dev/sdb /mnt/usb 因为前一个命令fdisk mount: block device /dev/sdb is write-protected, mounting read-only –l查到的U盘文件名

是/dev/sdb,若查到的

?用以下命令观察并将U盘中的一个文件aaa复制到/tmp目录下:

U盘文件名为

/dev/sdb1,则用[root@localhost root]# cd /mnt/usb /dev/sdb1,二者必居[root@localhost usb]# ls 其一。 ??? aaa ppt?? ??? ???????????????.doc pwd.c [root@localhost usb]# cp aaa /tmp [root@localhost usb]# cd /tmp [root@localhost tmp]# ls aaa orbit-stu2 vmware-root dir1 ssh-XXmIB307 VMwareTools-6.0.2-59824.tar.gz orbit-root VMwareDnD vmware-tools-distrib

?在当前路径已经离开U盘挂载点/mnt/usb的情况下,使用umount命令卸载U盘,完成整个U盘操作过程。

回到当前用户的主目录(也就是[root@localhost usb]# cd 离开挂载点/mnt/usb),以便随后[root@localhost root]# pwd 卸载U盘;否则,系统会报设备/root 忙,无法卸载。 [root@localhost root]# umount /mnt/usb

13

实验2 Linux环境下C语言使用、编译与调试

一、实验目的

1.复习C语言程序基本知识

2.练习并掌握Linux提供的vi编辑器来编译C程序

3.学会利用gcc、gdb编译、调试C程序

4.掌握在Linux操作系统环境上编辑、编译、调试、运行一个C语言程序的全过程。

二、实验内容

1.掌握一种Linux的编辑器,特别是字符界面的vi工具的使用。

2.用vi编写一个简单的、显示\的C程序,用gcc编译并观察编译后的结果

3.利用gdb调试该程序 4.运行生成的可执行文件。 三、实验指导

1.C语言使用简介

LINUX中包含了很多软件开发工具。它们中的很多是用于C和C++应用程序开发的。 C是一种能在LINUX的早期就被广泛使用的通用编程语言。它最早是由Bell实验室的Dennis Ritchie为了LINUX的辅助开发而写的,从此C就成为世界上使用最广泛的计算机语言。

C能在编程领域里得到如此广泛支持的原因有:

(1)它是一种非常通用的语言,并且它的语法和函数库在不同的平台上都是统一的,对开发者非常有吸引力;

(2)用C写的程序执行速度很快;

(3)C是所有版本LINUX上的系统语言。 2.文件编辑器vi

vi是在LINUX 上被广泛使用的中英文编辑软件。vi是visual editor的缩写,是LINUX提供给用户的一个窗口化编辑环境。

进入vi,直接执行vi编辑程序即可。 例:$vi test.c

显示器出现vi的编辑窗口,同时vi会将文件复制一份至缓冲区(buffer)。vi先对缓冲区的文件进行编辑,保留在磁盘中的文件则不变。编辑完成后,使用者可决定是否要取代原来旧有的文件。

(1) vi工作模式

vi提供两种工作模式:输入模式(insert mode)和命令模式(command mode)。使用者进入vi后,即处在命令模式下,此刻键入的任何字符皆被视为命令,可进行删除、修改、存盘等操作。要输入信息,应转换到输入模式。

①命令模式

在输入模式下,按ESC可切换到命令模式。命令模式下,可选用下列指令离开vi:

:q! 离开vi,并放弃刚在缓冲区内编辑的内容 :wq 将缓冲区内的资料写入磁盘中,并离开vi :ZZ 同wq :x 同wq :w 将缓冲区内的资料写入磁盘中,但并不离开vi :q 离开vi,若文件被修改过,则要被要求

14

确认是否放弃修改的内容,此指令可与:w配合使用

②命令模式下光标的移动

H 左移一个字符 J 下移一个字符 K 上移一个字符 L 右移一个字符 0 移至该行的首 $ 移至该行的末 ^ 移至该行的第一个字符处 H 移至窗口的第一列 M 移至窗口中间那一列 L 移至窗口的最后一列 G 移至该文件的最后一列 W, W 下一个单词 (W 忽略标点) B, B 上一个单词 (B 忽略标点) + 移至下一列的第一个字符处 - 移至上一列的第一个字符处 ( 移至该句首 ) 移至该句末 { 移至该段首 } 移至该段末 NG 移至该文件的第n列 N+ 移至光标所在位置之后第n列 n- 移至光标所在位置之前第n列

③ 输入模式

输入以下命令即可进入vi输入模式:

a(append) 在光标之后加入资料 A 在该行之末加入资料 I(insert) 在光标之前加入资料 I 在该行之首加入资料 o(open) 新增一行于该行之下,供输入资料用 O 新增一行于该行之上,供输入资料用 Dd 删除当前光标所在行 X 删除当前光标字符 X 删除当前光标之前字符 U 撤消 · 重做 F 查找 s 替换,例如:将文件中的所有\换成\用

\

ESC 离开输入模式

更多用法见 info vi 3.GNU C编译器

LINUX上可用的C编译器是GNU C编译器,它建立在自由软件基金会编程许可证的基础上,因此可以自由发布。

LINUX 上的GNU C编译器(GCC)是一个全功能的ANCI C兼容编译器,而一般LINUX用的编译器是CC。下面介绍GCC和一些GCC编译器最常用的选项。

(1) 使用GCC

通常后跟一些选项和文件名来使用GCC编译器。GCC命令的基本用法如下: gcc [options] [filenames]

15

命令行选项指定的编译过程中的具体操作 (2) GCC常用选项

GCC有超过100个的编译选项可用,这些选项中的许多可能永远都不会用到,但一些主要的选项将会频繁使用。很多的GCC选项包括一个以上的字符,因此必须为每个选项指定各自的连字符,并且就像大多数LINUX 命令一样不能在一个单独的连字符后跟一组选项。例如,下面的命令是不同的:

gcc -p-g test.c gcc -pg test.c

第一条命令告诉GCC编译test.c时为prof命令建立剖析(profile)信息并且把调试信息加入到可执行文件里。第二条命令告诉GCC只为gprof命令建立剖析信息。

当不用任何选项编译一个程序时,GCC将建立(假定编译成功)一个名为a.out的可执行文件。例如,

gcc test.c

编译成功后,当前目录下就产生了一个a.out文件。

也可用-o选项来为即将产生的可执行文件指定一个文件名来代替a.out。例如:

gcc –o count count.c

此时得到的可执行文件就不再是a.out,而是count。 GCC也可以指定编译器处理步骤多少。-c选项告诉GCC仅把源代码编译为目标代码而跳过汇编和连接步骤。这个选项使用得非常频繁因为它编译多个C程序时速度更快且更易于管理。默认时GCC建立的目标代码文件有一个.o的扩展名。

(3) 执行文件

格式: ./可执行文件名 例:./a.out ./count

4. gdb调试工具

LINUX包含了一个叫gdb的GNU调试程序。gdb是一个用来调试C和C++程序的强有力调试器。它使你能在程序运行时观察程序的内部结构和内存的使用情况。它具有以下功能:

·监视程序中变量的值; ·设置断点以使程序在指定的代码行上停止执行; ·一行行的执行代码。

以下是利用gdb进行调试的步骤: (1) 调试编译代码

为了使gdb正常工作,必须使你的程序在编译时包含调试信息。调试信息里包含你程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号。gdb利用这些信息使源代码和机器码相关联。

在编译时用 –g 选项打开调试选项。 (2) gdb基本命令 命 令 描 述 file 装入欲调试的可执行文件 kill 终止正在调试的程序 list 列出产生执行文件的源代码部分 next 执行一行源代码但不进入函数内部 step 执行一行源代码并进入函数内部 run 执行当前被调试的程序 quit 终止gdb watch 监视一个变量的值而不管它何时被改变 break 在代码里设置断点,使程序执行到这里时被挂起 make 不退出gdb就可以重新产生可执行文件 shell 不离开gdb就执行UNIX shell 命令

(3) 应用举例

16

设有一源程序greet.c

——编译,gcc -ggdb –o greet greet.c,出错

——gdb greet,出现提示符(gdb),此时可在提示符下输入gdb的命令了,如: ——(gdb)run ——(gdb)list

——退出调试状态,返回系统提示符下, (gdb)quit 5.参考程序 main( ) {

printf(\

}

17

实验3 观察Linux进程的异步并发执行

一、实验目的

1.掌握进程的概念,明确进程的含义 2.认识并了解并发执行的实质 二、实验内容

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

2.修改上述程序,每一个进程循环显示一句话。子进程显示'daughter …'及'son ……',父进程显示 'parent ……',观察结果,分析原因。

三、实验指导

1.进程

LINUX中,进程既是一个独立拥有资源的基本单位,又是一个独立调度的基本单位。一个进程实体由若干个区(段)组成,包括程序区、数据区、栈区、共享存储区等。每个区又分为若干页,每个进程配置有唯一的进程控制块PCB,用于控制和管理进程。

PCB的数据结构如下:

(1) 进程表项(Process Table Entry)。包括一些最常用的核心数据:

进程标识符PID、用户标识符UID、进程状态、事件描述符、进程和U区在内存或外存的地址、软中断信号、计时域、进程的大小、偏置值nice、指向就绪队列中下一个PCB的指针P_Link、指向U区进程正文、数据及栈在内存区域的指针。

(2) U区(U Area)。用于存放进程表项的一些扩充信息。 每一个进程都有一个私用的U区,其中含有:进程表项指针、真正用户标识符u-ruid(read user ID)、有效用户标识符u-euid(effective user ID)、用户文件描述符表、计时器、内部I/O参数、限制字段、差错字段、返回值、信号处理数组。

由于LINUX系统采用段页式存储管理,为了把段的起始虚地址变换为段在系统中的物理地址,便于实现区的共享,所以还有:

(3) 系统区表项。以存放各个段在物理存储器中的位置等信息。 系统把一个进程的虚地址空间划分为若干个连续的逻辑区,有正文区、数据区、栈区等。这些区是可被共享和保护的独立实体,多个进程可共享一个区。为了对区进行管理,核心中设置一个系统区表,各表项中记录了以下有关描述活动区的信息:

区的类型和大小、区的状态、区在物理存储器中的位置、引用计数、指向文件索引结点的指针。

(4) 进程区表

系统为每个进程配置了一张进程区表。表中,每一项记录一个区的起始虚地址及指向系统区表中对应的区表项。核心通过查找进程区表和系统区表,便可将区的逻辑地址变换为物理地址。

2. 进程映像

LINUX系统中,进程是进程映像的执行过程,也就是正在执行的进程实体。它由三部分组成:

(1) 用户级上、下文。主要成分是用户程序;

(2) 寄存器上、下文。由CPU中的一些寄存器的内容组成,如PC,PSW,SP及通用寄存器等;

(3) 系统级上、下文。包括OS为管理进程所用的信息,有静态和动态之分。

18

3. 所涉及的系统调用

fork( ) 创建一个新进程。 系统调用格式:

pid=fork( )

参数定义:

int fork( )

fork( )返回值意义如下:

0:在子进程中,pid变量保存的fork( )返回值为0,表示当前进程是子进程。

>0:在父进程中,pid变量保存的fork( )返回值为子进程的id值(进程唯一标识符)。 -1:创建失败。

如果fork( )调用成功,它向父进程返回子进程的PID,并向子进程返回0,即fork( )被调用了一次,但返回了两次。此时OS在内存中建立一个新进程,所建的新进程是调用fork( )父进程(parent process)的副本,称为子进程(child process)。子进程继承了父进程的许多特性,并具有与父进程完全相同的用户级上下文。父进程与子进程并发执行。

核心为fork( )完成以下操作: ① 为新进程分配一进程表项和进程标识符

进入fork( )后,核心检查系统是否有足够的资源来建立一个新进程。若资源不足,则fork( )系统调用失败;否则,核心为新进程分配一进程表项和唯一的进程标识符。

② 检查同时运行的进程数目

超过预先规定的最大数目时,fork( )系统调用失败。 ③ 拷贝进程表项中的数据

将父进程的当前目录和所有已打开的数据拷贝到子进程表项中,并置进程的状态为―创建‖状态。

④ 子进程继承父进程的所有文件

对父进程当前目录和所有已打开的文件表项中的引用计数加1。 ⑤ 为子进程创建进程上、下文

进程创建结束,设子进程状态为―内存中就绪‖并返回子进程的标识符。 ⑥ 子进程执行

虽然父进程与子进程程序完全相同,但每个进程都有自己的程序计数器PC(注意子进程的PC开始位置),然后根据pid变量保存的fork( )返回值的不同,执行了不同的分支语句。

例:

….. PC pid=fork( );

if (! pid)

printf(\

else if (pid>0)

printf(\

else

printf(\

…… fork( )调用前 fork( )调用后 ….. pid=fork( ); PC if (! pid) printf(\else if (pid>0) printf(\ else printf(\……

PC ….. pid=fork( ); if (! pid) printf(\else if (pid>0) printf(\ else printf(\…… 19

5. 参考程序 (1)

#include main( ) {

int p1,p2;

while((p1=fork( ))= = -1); /*创建子进程p1*/ if (p1= =0) putchar('b'); else

{

while((p2=fork( ))= = -1); /*创建子进程p2*/ if(p2= =0) putchar('c'); else putchar('a'); }

} (2)

#include main( ) {

int p1,p2,i;

while((p1=fork( ))= = -1); /*创建子进程p1*/ if (p1= =0)

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

printf(\ %d\\n\

else

{

while((p2=fork( ))= = -1); /*创建子进程p2*/

if(p2= =0)

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

printf(\ %d\\n\

else

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

printf(\ %d\\n\

}

}

运行结果

(1) bca,bac, abc ,……都有可能。 (2) parent… son… daughter.. daughter.. 或 parent… son… parent… daughter…等 原因分析

除strace 外,也可用ltrace -f -i -S ./executable-file-name查看以上程序执行过程。 (1) 从进程并发执行来看,各种情况都有可能。上面的三个进程没有同步措施,所以父进程与子进程的输出内容会叠加在一起。输出次序带有随机性。

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

补充:进程树

在LINUX系统中,只有0进程是在系统引导时被创建的,在系统初启时由0进程创建

20

1进程,以后0进程变成对换进程,1进程成为系统中的始祖进程。LINUX利用fork( )为每个终端创建一个子进程为用户服务,如等待用户登录、执行SHELL命令解释程序等,每个终端进程又可利用fork( )来创建其子进程,从而形成一棵进程树。可以说,系统中除0进程外的所有进程都是用fork( )创建的。

6.思考题

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

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

21

实验4 观察Linux进程的同步与互斥

一、实验目的

1.掌握进程另外的创建方法

2.熟悉进程的睡眠、同步、撤消等进程控制方法 3.进一步认识并发执行的实质

4.分析进程竞争资源的现象,学习解决进程互斥的方法 二、实验内容

1.用fork( )创建一个进程,再调用exec( )用新的程序替换该子进程的内容

2.利用wait( )来控制进程执行顺序

3.修改实验上述,用lockf( )来给每一个进程加锁,以实现进程之间的互斥 4. 观察并分析出现的现象

三、实验指导

1.所涉及的系统调用

在LINUX中,fork( )是一个非常有用的系统调用,但在LINUX中建立进程除了fork( )之外,也可用与fork( ) 配合使用的exec( )。

(1) exec( )系列

系统调用exec( )系列,也可用于新程序的运行。fork( )只是将父进程的用户级上下文拷贝到新进程中,而exec( )系列可以将一个可执行的二进制文件覆盖在新进程的用户级上下文的存储空间上,以更改新进程的用户级上下文。exec( )系列中的系统调用都完成相同的功能,它们把一个新程序装入内存,来改变调用进程的执行代码,从而形成新进程。如果exec( )调用成功,调用进程将被覆盖,然后从新程序的入口开始执行,这样就产生了一个新进程,新进程的进程标识符id 与调用进程相同。

exec( )没有建立一个与调用进程并发的子进程,而是用新进程取代了原来进程。所以exec( )调用成功后,没有任何数据返回,这与fork( )不同。exec( )系列系统调用在LINUX系统库unistd.h中,共有execl、execlp、execle、execv、execvp五个,其基本功能相同,只是以不同的方式来给出参数。

一种是直接给出参数的指针,如: int execl(path,arg0[,arg1,...argn],0); char *path,*arg0,*arg1,...,*argn;

另一种是给出指向参数表的指针,如: int execv(path,argv); char *path,*argv[ ];

具体使用可参考有关书。 (2) exec( )和fork( )联合使用

系统调用exec和fork( )联合使用能为程序开发提供有力支持。用fork( )建立子进程,然后在子进程中使用exec( ),这样就实现了父进程与一个与它完全不同子进程的并发执行。

一般,wait、exec联合使用的模型为: int status; ............

if (fork( )= =0) {

...........; execl(...); ...........; }

22

wait(&status); (3) wait( )

等待子进程运行结束。如果子进程没有完成,父进程一直等待。wait( )将调用进程挂起,直至其子进程因暂停或终止而发来软中断信号为止。如果在wait( )前已有子进程暂停或终止,则调用进程做适当处理后便返回。

系统调用格式: int wait(status) int *status;

其中,status是用户空间的地址。它的低8位反应子进程状态,为0表示子进程正常结束,非0则表示出现了各种各样的问题;高8位则带回了exit( )的返回值。exit( )返回值由系统给出。

核心对wait( )作以下处理:

首先查找调用进程是否有子进程,若无,则返回出错码;

若找到一处于―僵死状态‖的子进程,则将子进程的执行时间加到父进程的执行时间上,并释放子进程的进程表项;

若未找到处于―僵死状态‖的子进程,则调用进程便在可被中断的优先级上睡眠,等待其子进程发来软中断信号时被唤醒。

(4) exit( )

终止进程的执行。 系统调用格式:

void exit(status) int status;

其中,status是返回给父进程的一个整数,以备查考。 为了及时回收进程所占用的资源并减少父进程的干预,UNIX/LINUX利用exit( )来实现进程的自我终止,通常父进程在创建子进程时,应在进程的末尾安排一条exit( ),使子进程自我终止。exit(0)表示进程正常终止,exit(1)表示进程运行有错,异常终止。

如果调用进程在执行exit( )时,其父进程正在等待它的终止,则父进程可立即得到其返回的整数。核心须为exit( )完成以下操作:

① 关闭软中断 ② 回收资源 ③ 写记帐信息 ④ 置进程为―僵死状态‖ (5) lockf(files, function, size)

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

#include \

参数定义:

int lockf(files,function,size) int files,function; long size;

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

2.参考程序1 #include #include main( ) {

int pid;

pid=fork( ); /*创建子进程*/

switch(pid)

{

case -1: /*创建失败*/ printf(\

23

exit(1);

case 0: /*子进程*/ execl(\ printf(\ exit(1);

default: /*父进程*/ wait(NULL); /*同步*/ printf(\ exit(0); }

}

运行结果及分析原因

执行命令ls -l -color ,(按倒序)列出当前目录下所有文件和子目录; ls completed!

程序在调用fork( )建立一个子进程后,马上调用wait( ),使父进程在子进程结束之前,一直处于睡眠状态。子进程用exec( )装入命令ls ,exec( )后,子进程的代码被ls的代码取代,这时子进程的PC指向ls的第1条语句,开始执行ls的命令代码。

3.参考程序2:采用wait( )命令实现进程同步

#include #include main( ) {

int p1,p2,i;

while((p1=fork( ))= = -1); /*创建子进程p1*/

if (p1= =0) {

lockf(1,1,0); /*加锁,这里第一个参数为stdout(标准输出设备描述符)*/ for(i=0;i<10;i++)

printf(\ lockf(1,0,0); /*解锁*/ } else {

while((p2=fork( ))= =-1); /*创建子进程p2*/

if (p2= =0)

{

lockf(1,1,0); /*加锁*/ for(i=0;i<10;i++)

printf(\

lockf(1,0,0); /*解锁*/ } else

{

lockf(1,1,0); /*加锁*/ for(i=0;i<10;i++)

printf(\ lockf(1,0,0); /*解锁*/

}

}

} 运行结果: parent… son… daughter.. daughter..

24

或parent… son… parent… daughter…

大致与未上锁的输出结果相同,也是随着执行时间不同,输出结果的顺序有所不同。 原因分析:

上述程序执行时,不同进程之间不存在共享临界资源(其中打印机的互斥性已由操作系统保证)问题,所以加锁与不加锁效果相同。

25

实验5 观察Linux进程间的通信

一、实验目的

学习如何利用管道机制进行进程间的通信,以加深对通信机制的理解。 二、实验内容

1.了解系统调用pipe()的功能和实现过程。

2.编写程序实现进程的管道通信。用系统调用pipe( )建立一管道,二个子进程P1和P2分别向管道各写一句话: Child 1 is sending a message! Child 2 is sending a message!

父进程从管道中读出二个来自子进程的信息并显示(要求先接收P1,后P2)。

3.运行该程序,观察、记录并简单分析其运行结果。

三、实验指导

1.什么是管道

LINUX系统在OS的发展上,最重要的贡献之一便是该系统首创了管道(pipe)。这也是LINUX系统的一大特色。

所谓管道,是指能够连接一个写进程和一个读进程的、并允许它们以生产者—消费者方式进行通信的一个共享文件,又称为pipe文件。由写进程从管道的写入端(句柄1)将数据写入管道,而读进程则从管道的读出端(句柄0)读出数据。

句柄fd[0] 读出端

句柄fd[1] 写入端 2.管道的类型:

(1) 有名管道

一个可以在文件系统中长期存在的、具有路径名的文件。用系统调用mknod( )建立。它克服无名管道使用上的局限性,可让更多的进程也能利用管道进行通信。因而其它进程可以知道它的存在,并能利用路径名来访问该文件。对有名管道的访问方式与访问其他文件一样,需先用open( )打开。

(2) 无名管道

一个临时文件。利用pipe( )建立起来的无名文件(无路径名)。只用该系统调用所返回的文件描述符来标识该文件,故只有调用pipe( )的进程及其子孙进程才能识别此文件描述符,才能利用该文件(管道)进行通信。当这些进程不再使用此管道时,核心收回其索引结点。

二种管道的读写方式是相同的,本文只讲无名管道。 3.pipe文件的建立

分配磁盘和内存索引结点、为读进程分配文件表项、为写进程分配文件表项、分配用户文件描述符

4.读/写进程互斥

内核为地址设置一个读指针和一个写指针,按先进先出顺序读、写。

26

为使读、写进程互斥地访问pipe文件,需使各进程互斥地访问pipe文件索引结点中的直接地址项。因此,每次进程在访问pipe文件前,都需检查该索引文件是否已被上锁。若是,进程便睡眠等待,否则,将其上锁,进行读/写。操作结束后解锁,并唤醒因该索引结点上锁而睡眠的进程。

5.所涉及的系统调用 (1) pipe( )

建立一无名管道。 系统调用格式

pipe(filedes) 参数定义

int pipe(filedes); int filedes[2];

其中,filedes[1]是写入端,filedes[0]是读出端。 该函数使用头文件如下:

#include #inlcude #include

(2) read( ) 系统调用格式

read(fd,buf,nbyte)

功能:从fd所指示的文件中读出nbyte个字节的数据,并将它们送至由指针buf所指示的缓冲区中。如该文件被加锁,等待,直到锁打开为止。

参数定义

int read(fd,buf,nbyte); int fd; char *buf;

unsigned nbyte; (3) write( ) 系统调用格式

read(fd,buf,nbyte)

功能:把nbyte 个字节的数据,从buf所指向的缓冲区写到由fd所指向的文件中。如文件加锁,暂停写入,直至开锁。

参数定义同read( )。 6.参考程序

#include #include #include 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,\

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

write(fd[1],outpipe,50); /*向管道写长为50字节的串*/ sleep(5); /*自我阻塞5秒*/ lockf(fd[1],0,0); exit(0);

}

27

else

{

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

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

sprintf(outpipe,\

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

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

else

{ wait(0); /*同步*/

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

printf(\

wait(0);

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

exit(0); } }

}

运行结果:

延迟5秒后显示:child 1 process is sending message! 再延迟5秒: child 2 process is sending message!

7.思考题

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

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

28

实验6 观察内存分配结果

一.实验目的

学习如何利用Linux的malloc函数动态申请一段内存空间。 二.实验内容

1.了解malloc函数的功能和Linux虚拟内存管理的原理。

2.编写一C语言程序,用malloc函数申请一段存储空间,并在终端上显示起始地址。 3.运行该程序,观察、记录其运行结果,并分析说明结果的地址是否为物理地址。 三.实验指导

(请蒋老师补充一个简单实例)

1.首先将VMWare虚拟机的内存大小设置为256M。

在red hat linux 9环境下,编写一个名为testmalloc.c的程序,其代码如下: # include # include int main(){ int i; int *p[3];

for(i=0;i<3;i++){

if((p[i]=(int *)malloc(5*sizeof(int)))==NULL){ printf(\ exit(1); }

printf(\//打印指针地址 }

for(i=0;i<3;i++) free(p[i]);

printf(\//显示本系统int型数据占几个字节 return 0; }

2. 程序解释:

该程序利用malloc进行了3次动态内存分配,每次分配20字节(因为代码显示本系统int型数据占4个字节)。运行结果:

[root@localhost testmalloc]# gcc testmalloc.c [root@localhost testmalloc]# ./a.out

allocated addresses pointed to by p[0] is 0x8049750 allocated addresses pointed to by p[1] is 0x8049768 allocated addresses pointed to by p[2] is 0x8049780 the size of int in this system is 4

29

可见,每次用malloc进行动态内存分配时,分配的连续内存块起始地址分别为0x8049750、0x8049768、0x8049780。

从结果看,打印出来的指针地址间距为24字节,能容纳每次分配的20字节。

3. 再将虚拟机内存设为128M,然后再次启动虚拟机运行该程序,结果为: [root@localhost testmalloc]# ./a.out

allocated addresses pointed to by p[0] is 0x8049750 allocated addresses pointed to by p[1] is 0x8049768 allocated addresses pointed to by p[2] is 0x8049780 the size of int in this system is 4

从结果看,动态分配的内存情况仍然和虚拟机内存为256M时一样(但注意:多运行几次内存起始地址的具体数值有可能发生变化)。

假设malloc分配的是实际的物理地址,那么按实验结果显示的地址值,这些地址值长度至少有28b,虚拟机内存大小仅为为128M(即2),无法描述这些地址。故可以判断malloc分配的一定是虚拟内存地址,因而能获得比实际物理地址更大的地址空间。

27 30

实验7 进程调度模拟程序设计

一、实验目的

在采用多道程序设计的系统中,往往有若干个进程同时处于就绪状态。当就绪进程个数大于处理器数时,就必须依照某种策略来决定哪些进程优先占用处理器。

本实验模拟在单处理器情况下的处理器调度,帮助学生加深了解处理器调度的工作。

二、实验内容

设计一个按优先数调度算法或时间片轮转法实现处理器调度。

1.给出进程调度的算法描述(基于动态优先级和时间片轮转调度算法的描述)。 2.用C语言设计一个对n个并发进程进行调度的程序,每个进程由一个进程控制块(PCB)结构表示,该进程控制块应包括下述信息:进程标识ID、进程优先数PRIORITY(并规定优先数与优先权成正比)、时间片数CHIP、进程已经占用CPU的时间CPUTIME,进程还需要运行的时间ALLTIME(当进程运行完毕时,其值为0)、进程的状态STATE(为简化起见。设每个进程处于运行E(excecuting)、就绪R(ready)和完成F(finish)三种状态之一,并假设起始状态都是就绪状态R。),以及进程队列指针NEXT(用来将PCB排成队列)等,可按照调度算法的不同而增删。

3.调度程序应当包含2种不同的调度算法,运行时可以任选一种,以利于各种方法的分析和比较。

4.程序应能显示或打印各种进程状态和参数变化情况,便于观察。即要显示每个时间片内各进程的情况,并且指出运行进程及就绪和阻塞队列中的内容。

三、实验指导

1. 按优先数调度算法

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

进程名 指针 要求运行时间 优先数 状态 其中,进程名——作为进程的标识,假设五个进程的进程名分别为P1,P2,P3,P4,P5。

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

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

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

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

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

31

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

队首标志 K2 K1 P1 0 2 1 R PCB1 K2

P2 K4 3 5 R PCB2 K3

P3 K5 1 3 R PCB3 K4

P4 K3 2 4 R PCB4 K5

P5 K1 4 2 R PCB5

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

优先数-1 要求运行时间-1

来模拟进程的一次运行。

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

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

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

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

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

2. 时间片轮转法

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

进程名 指针 要求运行时间 已运行时间 状态 其中,进程名——作为进程的标识,假设五个进程的进程名分别为Q1,Q2,Q3,Q4,Q5。

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

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

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

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

(2) 每次运行所设计的处理器调度程序前,为每个进程任意确定它的―要求运行时间‖。 (3) 把五个进程按顺序排成循环队列,用指针指出队列连接情况。另用一标志单元记录

32

轮到运行的进程。例如,当前轮到P2执行,则有:

标志单元 K2

K1

Q1 K2 2 1 R PCB1 K2

Q2 K3 3 0 R PCB2 K3

Q3 K4 1 0 R PCB3 K4

Q4 K5 2 0 R PCB4 K5

Q5 K1 4 0 R PCB5

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

已运行时间+1

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

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

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

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

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

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

3.数据结构及参考算法 (1) 数据结构

程序中进程可用PCB表示,其类型描述如下: struct PCB_type {

char name ; //进程名

int state ; //进程状态

2——表示―执行‖状态 1——表示―就绪‖状态 0——表示―阻塞‖状态

int cpu_time ; //运行需要的CPU时间(需运行的时间片个数) }

设置两个队列,将处于―就绪‖状态的进程PCB挂在队列ready中;将处于―阻塞‖状态的进程PCB挂在队列blocked中。 队列类型描述如下:

struct QueueNode{

struct PCB_type PCB;

33

struct QueueNode *next;

}

并设全程量:

struct QueueNode *ready_head=NULL, //ready队列队首指针

*ready_tail=NULL , //ready队列队尾指针 *blocked_head=NULL, //blocked队列队首指针 *blocked_tail=NULL; //blocked队列队尾指针 (2) 设计子程序 begin start_state(); //读入假设的数据,设置系统初始状态 dispath(); //模拟调度 calculate(); //计算CPU利用率 use_cpu=0 (3) dispath()算法流程图: x=0 unuse_cpu=0

ready队列不空或end

blocked队列不空

ready队列不空

p? 取ready队首元素 p->PCB.state置“运行” 输出p->PCB.name p->PCB.cpu_time-- use_cpu++ unuse_cpu++ p->PCB.cpu_time>0 释放p p入ready队列队尾 x++ x==t blocked队首进程入ready队列队尾;x=0 34

实验8 页面置换模拟程序设计

一、实验目的

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

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

二、实验内容

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,页面失效次数为每次访问相应指令时,该指令所对应的页不在内存的次数。

35

三、实验指导

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

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

语句可初始化一个随机数; a[0]=10*rand()/65535*319+1; a[1]=10*rand()/65535*a[0];

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

2.数据结构 (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为忙页面尾的指针. 3.函数定义

(1)Void initialize( ):初始化函数,给每个相关的页面赋值. (2)Void FIFO( ):计算使用FIFO算法时的命中率. (3)Void LRU( ):计算使用LRU算法时的命中率. (4)Void OPT( ):计算使用OPT算法时的命中率. (5)Void LFU( ):计算使用LFU算法时的命中率. (6)Void NUR( ):计算使用NUR算法时的命中率. 4.变量定义 (1) int a[total_instruction]: 指令流数据组.

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

(3) int offset[total_instruction]: 每页装入10条指令后取模运算页号偏移值. (4) int total_pf: 用户进程的内存页面数. (5) int disaffect: 页面失效次数.

5. 程序框架:

36

#define total_instruction 320 #define total_vp 32 typedef struct {

int pn ,pfn,counter,time;

}pl_type;

pl_type p[total_vp]; pfc_struct{

int pn , pfn;

struct pfc_struct *next; };

typedef struct pfc_struct pfc_type;

pfc_type pfc[total_vp],*freepf_head,*busypf_head, *busypf_tail; int diseffect, a[total_struction];

int page[total_struction],offset[total_struction]; void initialize( ); {

…… }

void FIFO( ); / LRU( ); {

…… }

main( ) { int s,i,j;

sand(getpid()*10);

s=(float)319*rand()/32767+1; for(i=0;i < total_struction;i++)

{a[i]=s; a[i+1]=a[i]+1; a[I+2]=(float)a[i]*rand()/32767;a[i+3]=a[i+2]+1; s=(float)rand()*(318-a[i+2])/32767+a[i+2]+2; } for( i=0;i

{page[i]=a[i]/10;offset[i]=a[i];} for(i=4;i<=32;i++) FIFO(i);/ LRU( ); }

例如:a[0]=194 a[1]=195 a[2]=55 a[3]=56

a[4]=73 a[5]=74 a[6]=30 a[7]=31 a[8]=257 a[9]=258 a[10]=210 a[11]=211 a[12]=319 a[13]=320 a[14]=46 a[15]=47 a[16]=273 a[17]=274 a[18]=205 a[19]=206

6. 程序参考源码及结果 #define TRUE 1 #define FALSE 0 #define INVALID -1

37

#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());/*由于每次运行时进程号不同,故可用来作为初始化随机数队列的―种子‖*/

38

s=(float)319*rand( )/32767/32767/2+1; /*正态分布*/ for(i=0;i319) { printf(\ 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(\ } for (i=0;i

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

int initialize(int total_pf) { int i; diseffect=0;

39

for(i=0;i

int FIFO(int total_pf) /*先进先出算法total_pf:用户进程的内存页面数*/ { int i,j; pfc_type *p; /*中间变量*/ initialize(total_pf); /*初始化相关页面控制用数据结构*/ busypf_head=busypf_tail=NULL; /*忙页面队列头,队列尾链接*/ for(i=0;inext; 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) {

40

busypf_tail=busypf_head=freepf_head; } else { busypf_tail->next=freepf_head; busypf_tail=freepf_head; } freepf_head=p; } } printf(\ 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;ipl[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

41

{ pl[page[i]].time=present_time; //命中则增加该单元的访问次数 present_time++; } } printf(\ 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;inext=NULL; }

42

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

printf(\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

43

for(j=0;jnext=NULL; pl[maxpage].pfn=INVALID; } pl[page[i]].pfn=freepf_head->pfn; freepf_head=freepf_head->next; } } printf(\ return 0; }

/*该算法时根据已知的预测未知的,least frequency Used是最不经常使用置换法*/ int LFU(int total_pf) { int i,j,min,minpage; pfc_type *t; initialize(total_pf); for(i=0;ipl[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;

44

}

} 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(\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

45

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的还高。至于为什么,请探讨。

46

实验9 文件系统模拟程序设计

一、实验目的

加深对文件系统的内部功能及其实现的理解。 二、实验内容

用C语言设计一个简单的二级文件系统。要求做到以下几点: 1.可以实现下列几条命令(至少4条): login 用户登录 dir 列文件目录 creat 创建文件 delete 删除文件 open 打开文件 close 关闭文件 read 读文件 write 写文件

2.列目录时要列出文件名、物理地址、保护码和文件长度。 3.源文件可以进行读写保护。 三、实验指导

首先应确定文件系统的数据结构:主目录、子目录及活动文件等。主目录和子目录都以文件的形式存放于磁盘,这样便于查找和修改。用户创建的文件,可以编号存储于磁盘上。如file0,file1,file2...并以编号作为物理地址,在目录中进行登记。

1.设计思想

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

2.主要数据结构 (1) 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]; /*物理块号*/ }

(2) 磁盘i结点

47

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]; /*物理块号*/ }

(3) 目录项结构 struct direct {

char d_name[DIRSIZ]; /*目录名*/ unsigned int d_ino; /*目录号*/ }

(4) 超级块 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; /*超级块修改标志*/ };

(5) 用户密码 struct pwd {

unsigned short P_uid; unsigned short P_gid; char passward[PWOSIZ]; }

(6) 目录 struct dir {

strut direct direct[DIRNUM]; int size; }

(7) 查找i内存节点的hash表

48

本文来源:https://www.bwwdw.com/article/px8a.html

Top