操作系统原理是计算机本科生的必修课,具有较强的理论性和实践性。为了加强实践教学环节,培养学生的实践能力,促进理论与实践的结合30我们在学时独立开设的操作系统原理上增加了计算机实验8课内实验学时。本实验指导通过Windows API提供一些编程实例,使学生熟悉对Windows使用操作系统编程接口,了解如何模拟操作系统原理的实现,加深对操作系统设计原理和实现方法的理解,使学生能够接受程序设计和团队合作的基础培训。
从操作系统的基本原理出发,本计算机实验指导提供了不同类型的计算机实验问题。每个实验问题都有参考源程序代码,并对实验问题的设计进行了必要的解释和指导。在这些计算机实验中,重点是Windows应用程序接口API使用。使用与操作系统直接相关的这些API,编写一些实践操作系统的基本概念和基本原理的例子,方便学生理解和感知抽象概念;通过阅读本实验指南提供的例子程序代码,学生可以获得编程经验和培训。
为了能比较全面覆盖操作系统课程的知识点,本实验指导书提供了九个上机实验,具体包括如下五个方面的内容:
1.:包括“上机实验一 创建过程 线程的创建和并发执行 进程同步和计算机实验4 流程通信。由于课堂实验时间的限制,每个学生通常可以选择一个实验题进行计算机实验。
2.:包括上机实验五 进程调度和上机实验六 演示银行家算法。每个学生都可以选择一个问题,建议选择进程调度的问题。
3.:包括上机实验七 模拟页面置换算法。参考程序提供FIFO有兴趣的学生可以编程页面置换算法LRU或Clock算法。
4.:包括上机实验八 磁盘I/O”。
5.:包括上机实验九 命令解释程序。
以上5九类实验题共有九类22学时(包括每个实验项目的基本要求2学时,共18学时;进程调度和页面置换算法的高级要求2学时),每个学生都可以选择4题(限制每种类型1题),共完成8课内实验计划。由于操作系统原理课程的独立开设30学时的实验内容是文件系统的设计,因此文件系统的实验不包括在本计算机实验指南中。
本机实验指导进程调度I/O和命令解释程序等实验主要参考任爱华编写的《操作系统实用教程(第三版)实验指南》,但都进行了重大修改或重新设计。过程创建spa>MSDN(Microsoft Developet Network)提供的函数CreateProcess和CreateThread实现的;“进程同步”、“进程通信”、“银行家算法”、“页面置换算法”等实验是编者独立设计的,其中“进程同步”所用的信号量对象和“进程通信”所用的Pipe(管道)技术参阅了MSDN的相关资料。
本上机实验指导涉及的Win32 API函数、数据结构和数据类型,可参阅配套的Word文档“OS课内上机实验使用的相关API函数介绍.doc”、“OS实验涉及的API函数中使用的几个数据结构介绍.doc”和“Win32 Simple Data Types.doc”。
限于编者的水平和编写时间仓促,本实验指导一定有许多错误和不妥之处,恳请读者批评指正。
编者
2012年6月
上机实验一 进程的创建 (2学时) 1
1.1 上机实验要求 1
1.2 设计方案介绍 1
1.2.1 程序的总体框架 1
1.2.2 程序所用Win32 API函数介绍 2
1.3 源程序代码与运行结果分析 5
1.3.1 程序参考源代码 5
1.3.2 程序运行结果与分析 7
上机实验二 线程的创建和并发执行 (2学时) 9
2.1 上机实验要求 9
2.2 设计方案介绍 9
2.2.1 程序总体框架 9
2.2.2 程序所用API函数介绍 9
2.3 程序源代码和运行结果 11
2.3.1 程序源代码 11
2.3.2 运行结果和分析 14
2.4 进一步要求 17
上机实验三 进程同步(2学时) 19
3.1 上机实验要求和目的 19
3.2 Windows2000/XP的同步和互斥机制 19
3.3 设计方案介绍 20
3.3.1 数据结构 20
3.3.2 程序的总体设计思想 21
3.3.3 程序中所用API函数介绍 21
3.4 源程序与运行结果 23
3.4.1 程序源代码 23
3.4.2 程序运行输出结果 26
上机实验四 进程通信 (2学时) 30
4.1 上机实验内容和要求 30
4.2 相关知识 30
4.2.1 Windows中的pipe 30
4.2.2 实验所用的几个Win32 API函数介绍 30
4.3 实验总体设计 33
4.4 源程序与运行结果 33
4.4.1 程序源代码 33
4.4.2 程序运行结果 43
上机实验五 进程调度 (4学时) 44
5.1 上机实验基本要求 (2学时) 44
5.2 相关知识 44
5.2.1 Windows中的进程和线程 44
5.2.2 相关线程的几个Win32 API函数介绍 45
5.3 实验设计 46
5.3.1 重要的数据结构 46
5.3.2 程序实现 47
5.4 源程序与运行结果 49
5.4.1 程序源代码 49
5.4.2 程序运行结果 61
5.5 进一步要求 (2学时) 62
上机实验六 演示银行家算法 (2学时) 63
6.1 上机实验要求 63
6.2 实验设计 63
6.2.1 数据结构 63
6.2.2 程序实现 63
6.3 源程序与运行结果 64
上机实验七 模拟页面置换算法 (4学时) 72
7.1 上机实验基本要求(2学时) 72
7.2 实验设计 72
7.2.1 数据结构 72
7.2.2 程序实现 72
7.3 源程序与运行结果 73
7.3.1 参考程序源代码 73
7.3.2 程序运行结果 77
7.4 进一步要求 (2学时) 79
上机实验八 设备管理——磁盘I/O (2学时) 80
8.1 上机实验要求和目的 80
8.2 设计方案介绍 80
8.2.1 程序的总体框架 80
8.2.2 数据结构和程序中用到的API函数介绍 80
8.3 源程序和运行结果 85
8.3.1 源程序代码 85
8.3.2 程序的运行结果解释(操作说明) 94
上机实验九 命令解释程序 (2学时) 98
9.1 上机实验目的 98
9.2 上机实验要求 98
9.3 相关基础知识 99
9.3.1 命令解释程序与操作系统内核的关系 99
9.3.2 系统调用及Win32 API相关函数介绍 99
9.4 实验设计 103
9.4.1 重要的数据结构 103
9.4.2 程序实现 103
9.5 源程序与运行结果 104
9.5.1 程序源代码 104
9.5.2 程序运行结果示例 117
参考文献 121
上机实验一 进程的创建 (2学时)
本实验要求设计一个实现进程创建的程序,使所创建的进程在处理机上并发执行,要求程序能对出现的异常进行报告。通过本上机实验,学会在Win32程序中使用操作系统提供的API创建进程,并通过所创建的进程的运行情况获得对于进程并发的感性认识。
【注】在Windows系统中创建一个进程实质上就是启动执行一个可执行程序(文件)。
程序将“test.txt”文件作为输入,该文本文件的每一行都是有一个应用程序名以及该应用程序的参数构成(应用程序名和参数由空格隔开)。程序从“test.txt”文件中依次读出每一行存入cmdline字符串中,再以cmdline为函数实参数,调用NewProc( )函数,通过CreateProcess( )这个Win32 API函数来建立一个新进程,在该进程中运行对应的应用程序。“cmdline.txt”文件是在记事本中预先编制好的一个文本文本,其文件内容如下:
Process 1
Process 2
Process 3
Process1 4
命令行“Process 1”中的“Process”是指当前目录下的可执行文件“Process.exe”,命令行中的“1”是该可执行文件的输入参数。该命令行也可写成“Process.exe 1”。Process.exe是为了测试进程的并发执行而预先编制的VC++源程序编译而成的,其源代码如下面介绍。上述4个命令行中,第4个命令是为了测试出错信息的,其错误原因是应用程序Process1.exe不存在。用来演示的可执行文件Process.exe的VC++源代码如下:
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <windows.h> //Sleep()
void main(int argc,char **argv) //执行时可带参数的程序
{
unsigned int wt,x=1;
if (argc>=2)
x=1+atoi(argv[1]);
x=x*(unsigned int)time(NULL);
srand(x); // 使用当前时间为随机序列的"种子"
char Title[30]="Process ";
if (argc>=2)
{
strcat(Title,argv[1]);
SetConsoleTitle(Title); //设置标题栏
}
for (int i=1;i<=30;i++)
{
//显示进程正在运行的信息,其中命令参数argv[1]作为进程的编号
printf(" %2d : Process ",i);
if (argc>=2)
printf("%s ",argv[1]);
printf("正在运行!\n");
wt = 200 + rand() % 500 + rand() % 1000;
wt= rand() % wt + 300;
Sleep(wt); //睡眠等待200~2000ms
}
}
需要说明的是,本实验的用来创建进程的命令行的可执行程序名,也可以是GUI的应用程序,例如,命令行“c:\\windows\\notepad.exe 1.txt”将打开“记事本”窗口,同时在记事本窗口中打开文本文件“1.txt”(如果1.txt文件存在的话)。
操作系统所能完成的每一个特殊功能都有一个函数与其对应,即操作系统把它所能完成的功能以函数的形式提供给应用程序使用。应用程序对这些函数的调用叫系统调用。这些函数的集合就是Windows操作系统提供给应用程序的编程接口(Application Programming Interface),简称Windows API或Win32 API。所有在Win32平台上运行的应用程序都可以调用这些函数。
使用Windows API,应用程序可以充分挖掘Windows的32位操作系统潜力。Microsoft的所有32位平台都支持统一的API。
Windows的相关API的说明都可以在MSDN(Microsoft Developet Network)中查到,包括定义、使用方法等。下面简单介绍本实验中涉及的Windows API。
返回最近一次发生的错误代码。
DWORD GetLastError(VOID)
【注】此处的DWORD就是unsugned long,VOID就是void。要更多地了解Win32 API中的数据类型,可参阅配套文档“Win32 Simple Data Types.doc”。
生成具体的出错信息。在本实验程序中,用它将由GetLastError( )得到的最近一次发生的错误代码号转换成具体的出错信息。调用此功能需要一个消息定义作为输入,该消息定义可以在本函数的一个缓冲中设定,也可以是系统定义的消息表,或者是另一个已装载模块中定义的消息表。此函数若调用成功,则返回得到的消息的字节数,否则返回0.
DWORD FormatMessage (
DWORD dwFlags,
LPVOID lpSource,
WORD dwMessageId,
WORD dwLanguageId,
PTSTR lpBuffer,
WORD nSize,
Va_list *Arguments
)
:一个关于后面的lpSource参数的使用及有关输出格式等方面的参数。参数值如下:
n FORMAT_MESSAGE_ALLOCATE_BUFFER:表示系统将自动分配足够大的内存缓冲区存储得到的消息,此时lpBuffer指向该缓冲区的首部(lpvoid型),nSize指明分配给该缓冲区的最少字节数;这时,进程在后面需要通过调用LocalFree( )函数去释放系统分配的缓冲区。
n FORMAT_MESSAGE_FROM_SYSTEM、FORMAT_MESSAGE_FROM_HMODVLE与FORMAT _ MESSAGE_FROM_STRING:前者表明利用错误代码dwMessageId到系统消息定义表去找对应的出错信息,它可以和FORMAT_MESSAGE_FROM_HMODVLE合用,表示先到该模块中定义的消息表查找对应消息,找不到再转向系统消息定义表,但二者不能和FORMAT_MESSAGE_FROM_STRING合用,后者表示表示消息定义在由lpSource指针指向的串中。
n FORMAT_MESSAGE_IGNORE_INSERT:指出在消息定义中的插入值在输出时不被处理,此时后面的ARGUMENTS参数被忽略;除了上面提到的功能,这个参数还可以规定输出消息的行宽格式。
:只有上一参数含FORMAT_MESSAGE_FROM_HMODVLE或FORMAT_MESSAGE _FROM_STRING时才有意义,用于指出所指模块的句柄或者具体消息定义表的首址;否则本参数被忽略;特别是当上一参数含FORMAT_MESSAGE_FROM_HMODVLE而本参数为NULL,默认为当前进程句柄。
:一个32位系统错误代号,和以一个参数一起来定位目标消息,当dwFlags包含FORMAT_MESSAGE_FROM_STRING时,此参数不起作用。当dwFlags包含FORMAT_MESSAGE _FROM_SYSTEM时,本id可以通过GetLastError( )指定。
:选择所用语言代号,但若dwFlags包含FORMAT_MESSAGE_FROM_STRING,则此参数不起作用;也可以指定为0,表示用默认的几种语言以一定的顺序去寻找满足要求的消息,直到找到一个为止。
:输出消息的存放处,当dwFlags包含FORMAT_MESSAGE_ALLOCATE_BUFFER时,系统动态分配内存,此时就不必预先分配内存,而只传一个指针进来。
:当dwFlags包含FORMAT_MESSAGE_ALLOCATE_BUFFER时,表示需要为缓冲区分配的最小字节数(ANSI版),否则表示可输出消息的最大字节数。
:一组消息插入值(具体用法参见MSDN),本例中取值为NULL。
:创建一个新进程和它的主线程,这个新进程运行指定的可执行文件。
:
BOOL CreateProcess (
LPCTSTR lpApplicationName, // 指定可执行模块的字符串
LPCTSTR lpCommandLine, // 指定要运行的命令行
LPSECURITY_ATTRIBUTES lpProcessAttributes, //决定返回句柄能否被继承,
//它定义了进程的安全性
LPSECURITY_ATTRIBUTES lpThreadAttributes, //决定返回句柄能否被继承,
//它定义了进程的主线程的安全性
BOOL bInheritHandles, //表示新进程是否从调用进程处继承了句柄
DWORD dwCreationFalgs, //控制优先类和进程的创建标志
LPVOID lpEnvironment, //指向一个新进程的环境块
LPCTSTR lpCurrentDirectory, //指定子进程的工作路径
LPSTARTUPINFO lpStartupInfo, //决定新进程的主窗体显示方式的结构
LPPROCESS_INFORMATION lpProcessInformation //接收新进程的标识信息
)
下面在对此函数的参数做进一步的说明:
lpApplicationName:用于指定可执行模块的字符串,这个字符串可以是可执行模块的绝对路径,也可以是相对路径。在后一种情况下,函数使用当前驱动器和目录建立可指向模块的路径。这个参数可以设置为NULL(本程序就是如此),在这种情况下,可执行模块的名称必须处于lpCommandLine参数的最前面并由空格与后面的字符分开。可执行模块可以是基于Win32的,也可以是MS-DOS或OS/2下的模块,建议带上扩展名和使用全路径名。
lpCommandLine:指向一个指定要执行的命令行字符串。这个参数可以为NULL,但它不能和第一个参数同时为空。如果lpApplicationName和lpCommandLine都不为空,那么lpApplicationName指定将要运行的模块,lpCommandLine参数指定将要被运行的模块的命令行。
lpProcessAttributes:SECURITY_ATTRIBUTES型指针,指出返回的句柄是否可以被子进程继承,取值为NULL时,表示不可继承。本程序用NULL。
lpThreadAttributes:同上,指出返回的主线程句柄是否可以被子进程继承,取值为NULL时,表示不可继承。本程序用NULL。
bInheritHandles:决定子进程是否可以继承当前打开的句柄,FALSE表示不可继承。本程序中用的是FALSE。
dwCreationFalgs:关于新进程的其他一些环境参数的设定(例如是否另外开辟控制台)和新进程的优先级等问题。程序中创建前台进程时此值为0(可理解为不考虑环境参数的设定和新进程的权限问题),而创建后台进程时此值为CREATE_NEW_CONSOLE(即整数16),表示另外开辟控制台(程序中还另加语句将它开辟的控制台隐藏)。此参数的各种取值和相应含义的详细介绍可参考MSDN。
lpEnvironment:新进程环境变量指针,为NULL则继承父进程的环境变量。本程序用NULL。
lpCurrentDirectory:指定子进程的工作目录(只支持全路径名),本程序用NULL表示是当前目录。
lpStartupInfo:指向STARTUPINFO结构的指针,决定了子进程主窗体的外观,包括窗口的位置、大小,是否有输入和输出机错误输出(详见MSDN的参数说明)。其中输出句柄可以用于进程的管道通信。值得注意的是,该结构第一个成员表示此结构的大小,使用这个结构体时要注意先要初始化它的大小。当进程创建时可以用GetStartupInfo来获得STARTUPINFO结构体(请看本实验的程序)。
lpProcessInformation:指向一个用来接收新进程识别信息的PROCESS_INFORMATION的结构体。其中包含了新进程的多个信息。例如进程句柄、进程主线程的句柄、进程ID、主线程ID。通过获得的进程信息即可对该进程进行进一步操作。本程序未使用这个结果。
:获取当前活动窗口的句柄。
:
HWND GetForegroundWindow(void)
:改变指定窗口的位置和大小。
:
BOOL MoveWindow(
HWND hWnd, // handle to window
int X, // horizontal position
int Y, // vertical position
int nWidth, // width
int nHeight, // height
BOOL bRepaint // repaint option
);
:
bRepaint:指出是否重画窗口。TRUE,重画;FALSE,不重画。
:
#include <fstream.h> //它已经包含了iostream.h
#include <windows.h> //Win32 API
#define MAX_LINE_LEN 128
void NewProc(char*); // 声明函数
char cmdline[MAX_LINE_LEN];
void main()
{
char ch;
ifstream fid;
HWND hWindow;
hWindow=GetForegroundWindow();//获取本程序运行窗口的句柄
//改变本程序运行窗口的位置和大小使之与子进程窗口整齐排列
MoveWindow(hWindow,10,10,425,320,TRUE);
fid.open("cmdline.txt",ios::nocreate);//打开存储命令行的文件
if (!fid) //若文件不存在,报错
{
cout<<"Can't open cmdline.txt !"<<endl;
ch=cin.get();
exit(0);
}
//从文件逐条读出命令行,用于创建进程
while (fid.getline(cmdline,MAX_LINE_LEN)) {
cout<<"cmdline="<<cmdline<<endl;//输出所读命令行
NewProc(cmdline);//创建新进程
}
cout<<"\nEnd"<<endl;
}
void NewProc(char* cmdline)
{
static int counter=1;
STARTUPINFO si={sizeof(STARTUPINFO)};
//使成员dwX,dwY和dwXSize,dwYSize有效
//目的是为了能调整各进程窗口位置和大小
si.dwFlags=STARTF_USEPOSITION|STARTF_USESIZE;
si.dwXSize=400; //窗口宽
si.dwYSize=260; //窗口高
PROCESS_INFORMATION pi;
DWORD dwx=10,dwy=10; //窗口左上角位置
switch (counter%4)
{
case 0 : si.dwX=dwx; //指定第四个子进程的窗口的左上角位置
si.dwY=dwy;
break;
case 1 : si.dwX=dwx+425;//指定第一个子进程的窗口的左上角位置
si.dwY=dwy;
break;
case 2 : si.dwX=dwx; //指定第二个子进程的窗口的左上角位置
si.dwY=dwy+310;
break;
case 3 : si.dwX=dwx+425;//指定第三个子进程的窗口的左上角位置
si.dwY=dwy+310;
break;
}
counter++;
BOOL result=CreateProcess(
NULL, //没指定可执行模块名
cmdline, //路径和可执行模块名
NULL, //子进程不能继承此函数返回的句柄
NULL, //子进程的线程不能继承此函数返回的句柄
FALSE, //新进程不能从调用此函数进程继承此句柄
NORMAL_PRIORITY_CLASS|CREATE_NEW_CONSOLE,
//创建一个没有特殊调度需求的普通进程、开辟新窗口
NULL, //新进程使用调用此函数进程(父进程)的环境块
NULL, // 标签: 对插式针型连接器