exit(0);
}
步骤5:
分别屏蔽While循环中的两个for循环,或调整两个for循环的次数,写出运行结果。
屏蔽i循环:
屏幕中一直输出:
enterkernelmoderunning.
屏蔽j循环:
程序没有输出,不能终止程序运行。
调整循环变量i的循环次数:
将i的值减小或增大,程序运行结果一直输出enterkernelmoderunning.
调整循环变量j的循环次数:
将j的值减小或增大,程序运行结果一直输出enterkernelmoderunning.
3、实验结论
对Win32 Consol Application有进一步的认识,WIN32 API也就是Windows 32平台的应用程序编程接口。
用户态运行和核心态运行,核心态就是一个是直接的代码运行,即win32consol Application下代码运行;而用户态是在DOS下运行的,对编译好的程序进行的运行,核心态速度较快,而用户态的运行需要时间较长,由于有相应的约束。
实验二进程管理
背景知识
Windows所创建的每个进程都从调用CreateProcess()API函数开始,该函数的任务是在对象管理器子系统内初始化进程对象。
每一进程都以调用ExitProcess()或TerminateProcess()API函数终止。
通常应用程序的框架负责调用ExitProcess()函数。
对于C++运行库来说,这一调用发生在应用程序的main()函数返回之后。
1.创建进程
CreateProcess()调用的核心参数是可执行文件运行时的文件名及其命令行。
表3-4详细地列出了每个参数的类型和名称。
表3-4CreateProcess()函数的参数
参数名称
使用目的
LPCTSTRlpApplivationName
全部或部分地指明包括可执行代码的EXE文件的文件名
LPCTSTRlpCommandLine
向可执行文件发送的参数
LPSECURIITY_ATTRIBUTESlpProcessAttributes
返回进程句柄的安全属性。
主要指明这一句柄是否应该由其他子进程所继承
LPSECURIITY_ATTRIBUTESlpThreadAttributes
返回进程的主线程的句柄的安全属性
BOOLbInheritHandle
一种标志,告诉系统允许新进程继承创建者进程的句柄
DWORDdwCreationFlage
特殊的创建标志(如CREATE_SUSPENDED)的位标记
LPVOIDlpEnvironment
向新进程发送的一套环境变量;如为null值则发送调用者环境
LPCTSTRlpCurrentDirectory
新进程的启动目录
STARTUPINFOlpStartupInfo
STARTUPINFO结构,包括新进程的输入和输出配置的详情
LPPROCESS_INFORMATIONlpProcessInformation
调用的结果块;发送新应用程序的进程和主线程的句柄和ID
可以指定第一个参数,即应用程序的名称,其中包括相对于当前进程的当前目录的全路径或者利用搜索方法找到的路径;lpCommandLine参数允许调用者向新应用程序发送数据;接下来的三个参数与进程和它的主线程以及返回的指向该对象的句柄的安全性有关。
然后是标志参数,用以在dwCreationFlags参数中指明系统应该给予新进程什么行为。
经常使用的标志是CREATE_SUSPNDED,告诉主线程立刻暂停。
当准备好时,应该使用ResumeThread()API来启动进程。
另一个常用的标志是CREATE_NEW_CONSOLE,告诉新进程启动自己的控制台窗口,而不是利用父窗口。
这一参数还允许设置进程的优先级,用以向系统指明,相对于系统中所有其他的活动进程来说,给此进程多少CPU时间。
接着是CreateProcess()函数调用所需要的三个通常使用缺省值的参数。
第一个参数是lpEnvironment参数,指明为新进程提供的环境;第二个参数是lpCurrentDirectory,可用于向主创进程发送与缺省目录不同的新进程使用的特殊的当前目录;第三个参数是STARTUPINFO数据结构所必需的,用于在必要时指明新应用程序的主窗口的外观。
CreateProcess()的最后一个参数是用于新进程对象及其主线程的句柄和ID的返回值缓冲区。
以PROCESS_INFORMATION结构中返回的句柄调用CloseHandle()API函数是重要的,因为如果不将这些句柄关闭的话,有可能危及主创进程终止之前的任何未释放的资源。
2.正在运行的进程
如果一个进程拥有至少一个执行线程,则为正在系统中运行的进程。
通常,这种进程使用主线程来指示它的存在。
当主线程结束时,调用ExitProcess()API函数,通知系统终止它所拥有的所有正在运行、准备运行或正在挂起的其他线程。
当进程正在运行时,可以查看它的许多特性,其中少数特性也允许加以修改。
首先可查看的进程特性是系统进程标识符(PID),可利用GetCurrentProcessId()API函数来查看,与GetCurrentProcess()相似,对该函数的调用不能失败,但返回的PID在整个系统中都可使用。
其他的可显示当前进程信息的API函数还有GetStartupInfo()和GetProcessShutdownParameters(),可给出进程存活期内的配置详情。
通常,一个进程需要它的运行期环境的信息。
例如API函数GetModuleFileName()和GetCommandLine(),可以给出用在CreateProcess()中的参数以启动应用程序。
在创建应用程序时可使用的另一个API函数是IsDebuggerPresent()。
可利用API函数GetGuiResources()来查看进程的GUI资源。
此函数既可返回指定进程中的打开的GUI对象的数目,也可返回指定进程中打开的USER对象的数目。
进程的其他性能信息可通过GetProcessIoCounters()、GetProcessPriorityBoost()、GetProcessTimes()和GetProcessWorkingSetSize()API得到。
以上这几个API函数都只需要具有PROCESS_QUERY_INFORMATION访问权限的指向所感兴趣进程的句柄。
另一个可用于进程信息查询的API函数是GetProcessVersion()。
此函数只需感兴趣进程的PID(进程标识号)。
本实验程序清单3-6中列出了这一API函数与GetVersionEx()的共同作用,可确定运行进程的系统的版本号。
3.终止进程
所有进程都是以调用ExitProcess()或者TerminateProcess()函数结束的。
但最好使用前者而不要使用后者,因为进程是在完成了它的所有的关闭“职责”之后以正常的终止方式来调用前者的。
而外部进程通常调用后者即突然终止进程的进行,由于关闭时的途径不太正常,有可能引起错误的行为。
TerminateProcess()API函数只要打开带有PROCESS_TERMINATE访问权的进程对象,就可以终止进程,并向系统返回指定的代码。
这是一种“野蛮”的终止进程的方式,但是有时却是需要的。
如果开发人员确实有机会来设计“谋杀”(终止别的进程的进程)和“受害”进程(被终止的进程)时,应该创建一个进程间通讯的内核对象——如一个互斥程序——这样一来,“受害”进程只在等待或周期性地测试它是否应该终止。
4.进程同步
Windows2000/XP提供的常用对象可分成三类:
核心应用服务、线程同步和线程间通讯。
其中,开发人员可以使用线程同步对象来协调线程和进程的工作,以使其共享信息并执行任务。
此类对象包括互锁数据、临界段、事件、互斥体和信号等。
多线程编程中关键的一步是保护所有的共享资源,工具主要有互锁函数、临界段和互斥体等;另一个实质性部分是协调线程使其完成应用程序的任务,为此,可利用内核中的事件对象和信号。
在进程内或进程间实现线程同步的最方便的方法是使用事件对象,这一组内核对象允许一个线程对其受信状态进行直接控制(见表2-1)。
而互斥体则是另一个可命名且安全的内核对象,其主要目的是引导对共享资源的访问。
拥有单一访问资源的线程创建互斥体,所有想要访问该资源的线程应该在实际执行操作之前获得互斥体,而在访问结束时立即释放互斥体,以允许下一个等待线程获得互斥体,然后接着进行下去。
与事件对象类似,互斥体容易创建、打开、使用并清除。
利用CreateMutex()API可创建互斥体,创建时还可以指定一个初始的拥有权标志,通过使用这个标志,只有当线程完成了资源的所有的初始化工作时,才允许创建线程释放互斥体。
表2-1用于管理事件对象的API
API名称
描述
CreateEvent()
在内核中创建一个新的事件对象。
此函数允许有安全性设置、手工还是自动重置的标志以及初始时已接受还是未接受信号状态的标志
OpenEvent()
创建对已经存在的事件对象的引用。
此API函数需要名称、继承标志和所需的访问级别
SetEvent()
将手工重置事件转化为已接受信号状态
ResetEvent()
将手工重置事件转化为非接受信号状态
PulseEvent()
将自动重置事件对象转化为已接受信号状态。
当系统释放所有的等待它的线程时此种转化立即发生
为了获得互斥体,首先,想要访问调用的线程可使用OpenMutex()API来获得指向对象的句柄;然后,线程将这个句柄提供给一个等待函数。
当内核将互斥体对象发送给等待线程时,就表明该线程获得了互斥体的拥有权。
当线程获得拥有权时,线程控制了对共享资源的访问——必须设法尽快地放弃互斥体。
放弃共享资源时需要在该对象上调用ReleaseMutex()API。
然后系统负责将互斥体拥有权传递给下一个等待着的线程(由到达时间决定顺序)。
1、实验目的
1)通过创建进程、观察正在运行的进程和终止进程的程序设计和调试操作,进一步熟悉操作系统的进程概念,理解Windows进程的“一生”。
2)通过阅读和分析实验程序,学习创建进程、观察进程、终止进程以及父子进程同步的基本程序设计方法。
2、实验内容和步骤
(1).创建进程
本实验显示了创建子进程的基本框架。
该程序只是再一次地启动自身,显示它的系统进程ID和它在进程列表中的位置。
步骤1:
创建一个“Win32ConsolApplication”工程,然后拷贝清单2-1中的程序,编译成可执行文件。
步骤2:
在“命令提示符”窗口运行步骤1中生成的可执行文件。
运行结果:
范例:
E:
\课程\os课\os实验\程序\os11\debug>os21
(假设编译生成的可执行文件是os21.exe)
ProcessID:
xxx,CloneID:
0
ProcessID:
xxx,CloneID:
1
ProcessID:
xxx,CloneID:
2
ProcessID:
xxx,CloneID:
3
ProcessID:
xxx,CloneID:
4
ProcessID:
xxx,CloneID:
5
生成6个控制台窗口,按Enter键终止窗口,直至退出程序。
按下ctrl+alt+del,调用windows的任务管理器,记录进程相关的行为属性:
步骤3:
在“命令提示符”窗口加入参数重新运行生成的可执行文件。
运行结果:
范例:
E:
\课程\os课\os实验\程序\os11\debug>os213
(假设编译生成的可执行文件是os21.exe)
ProcessID:
xxx,CloneID:
3
ProcessID:
xxx,CloneID:
4
ProcessID:
xxx,CloneID:
5
生成3个控制台窗口,按Enter键终止窗口,直至退出程序。
按下ctrl+alt+del,调用windows的任务管理器,记录进程相关的行为属性:
步骤4:
修改清单2-1中的程序,将nClone的定义和初始化方法按程序注释中的修改方法进行修改,编译成可执行文件(执行前请先保存已经完成的工作)。
再按步骤2中的方式运行,看看结果会有什么不一样。
运行结果:
第一次修改后不影响程序运行的结果;第二次修改后造成程序死循环,即程序一直创建新的进程且CloneId都是0。
从中你可以得出什么结论:
nClone的作用:
确定要克隆的进程个数,以及克隆的进程ID号。
从nClone开始依次创建新进程到nClone=5结束。
变量的定义和初始化方法(位置)对程序的执行结果有影响吗?
为什么?
有影响。
因为程序是根据nClone的值来创建新的进程的,若nClone的值在不一样的位置会影响判断语句,当nClone=5时程序终止,不再创建新的进程。
第二次修改后nClone的值始终为0,所以会进入死循环创建新进程。
(2).父子进程的简单通信及终止进程
步骤1:
创建一个“Win32ConsolApplication”工程,然后拷贝清单2-2中的程序,编译成可执行文件。
步骤2:
在VC的工具栏单击“ExecuteProgram”(执行程序)按钮,或者按Ctrl+F5键,或者在“命令提示符”窗口运行步骤1中生成的可执行文件。
运行结果:
范例:
E:
\课程\os课\os实验\程序\os11\debug>os22
(假设编译生成的可执行文件是os22.exe)
程序先创建父进程,然后再创建子进程,并且子进程结束与否由父进程来控制。
步骤3:
按源程序中注释中的提示,修改源程序2-2,编译执行(执行前请先保存已经完成的工作)。
运行结果:
程序进入死循环,一直处于创建父进程的状态,并没有创建子进程。
在程序中加入跟踪语句,或调试运行程序,同时参考MSDN中的帮助文件CreateProcess()的使用方法,理解父子进程如何传递参数。
给出程序执行过程的大概描述:
_____________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
步骤4:
填空
CreateProcess()函数有10个核心参数?
本实验程序中设置的各个参数的值是:
a.产生的应用程序的名称(本EXE文件)szFilename
b.告诉我们这是一个子进程的标志szCmdLine
c.用于进程的缺省的安全性NULL
d.用于线程的缺省安全性NULL
e.不继承句柄FALSE
f.创建新窗口CREATE_NEW_CONSOLE
g.新环境NULL
h.当前目录NULL
i.启动信息结构&si
j.返回的进程信息&pi
步骤5:
按源程序中注释中的提示,修改源程序2-2,编译执行。
运行结果:
程序创建父进程,子进程闪了一下就消失了。
步骤6:
参考MSDN中的帮助文件CreateMutex()、OpenMutex()、ReleaseMutex()和WaitForSingleObject()的使用方法,理解父子进程如何利用互斥体进行同步的。
给出父子进程同步过程的一个大概描述:
在进程内或进程间实现线程同步的最方便的方法是使用事件对象,这一组内核对象允许一个线程对其受信状态进行直接控制。
利用CreateMutex()API可创建互斥体,创建时还可以指定一个初始的拥有权标志,通过使用这个标志,只有当线程完成了资源的所有的初始化工作时,才允许创建线程释放互斥体。
_首先,进程创建一个互斥体,打开互斥体,如遇到互斥,则进行处理,处理完后,释放互斥体,下面便是进程等待下一个要处理的项目。
3、实验结论
每个进程都从调用CreateProcess() API函数开始,该函数的任务是在对象管理器子系统内初始化进程对象。
每一进程都以调用ExitProcess() 或TerminateProcess() API函数终止。
通常应用程序的框架负责调用 ExitProcess() 函数。
进程都是有始有终,其中有中断,还有处理进程间互斥的函数,已达到进程的完成后自然终止。
实验三进程同步的经典算法
背景知识
Windows2000提供的常用对象可分成三类:
核心应用服务、线程同步和线程间通讯。
其中,开发人员可以使用线程同步对象来协调线程和进程的工作,以使其共享信息并执行任务。
此类对象包括互锁数据、临界段、事件、互斥体和信号等。
多线程编程中关键的一步是保护所有的共享资源,工具主要有互锁函数、临界段和互斥体等;另一个实质性部分是协调线程使其完成应用程序的任务,为此,可利用内核中的事件对象和信号。
在进程内或进程间实现线程同步的最方便的方法是使用事件对象,这一组内核对象允许一个线程对其受信状态进行直接控制(见表3-1)。
而互斥体则是另一个可命名且安全的内核对象,其主要目的是引导对共享资源的访问。
拥有单一访问资源的线程创建互斥体,所有想要访问该资源的线程应该在实际执行操作之前获得互斥体,而在访问结束时立即释放互斥体,以允许下一个等待线程获得互斥体,然后接着进行下去。
与事件对象类似,互斥体容易创建、打开、使用并清除。
利用CreateMutex()API可创建互斥体,创建时还可以指定一个初始的拥有权标志,通过使用这个标志,只有当线程完成了资源的所有的初始化工作时,才允许创建线程释放互斥体。
表3-1用于管理事件对象的API
API名称
描述
CreateEvent()
在内核中创建一个新的事件对象。
此函数允许有安全性设置、手工还是自动重置的标志以及初始时已接受还是未接受信号状态的标志
OpenEvent()
创建对已经存在的事件对象的引用。
此API函数需要名称、继承标志和所需的访问级别
SetEvent()
将手工重置事件转化为已接受信号状态
ResetEvent()
将手工重置事件转化为非接受信号状态
PulseEvent()
将自动重置事件对象转化为已接受信号状态。
当系统释放所有的等待它的线程时此种转化立即发生
为了获得互斥体,首先,想要访问调用的线程可使用OpenMutex()API来获得指向对象的句柄;然后,线程将这个句柄提供给一个等待函数。
当内核将互斥体对象发送给等待线程时,就表明该线程获得了互斥体的拥有权。
当线程获得拥有权时,线程控制了对共享资源的访问——必须设法尽快地放弃互斥体。
放弃共享资源时需要在该对象上调用ReleaseMute()API。
然后系统负责将互斥体拥有权传递给下一个等待着的线程(由到达时间决定顺序)。
1、实验目的
1)回顾系统进程、线程的有关概念,加深对Windows2000线程的理解。
2)了解互斥体对象,通过对生产者消费者等进程间同步与互斥经典算法的实现,加深对P、V原语以及利用P、V原语进行进程间同步与互斥操作的理解。
2、实验内容和步骤
(1).生产者消费者问题
步骤1:
创建一个“Win32ConsolApplication”工程,然后拷贝清单3-1中的程序,编译成可执行文件。
步骤2:
在“命令提示符”窗口运行步骤1中生成的可执行文件。
运行结果:
范例:
E:
\课程\os课\os实验\程序\os11\debug>os31
(假设编译生成的可执行文件是os31.exe)
消费者个数多于生产者,生产速度慢,消费者经常等待生产者。
步骤3:
仔细阅读源程序,找出创建线程的WINDOWSAPI函数,回答下列问题:
线程的第一个执行函数是什么(从哪里开始执行)?
它位于创建线程的API函数的第几个参数中?
创建生产者线程:
CreateThread(NULL,0,Producer,NULL,0,&producerID[i])
创建消费者线程:
CreateThread(NULL,0,Consumer,NULL,0,&consumerID[i])
生产者函数:
DWORDWINAPIProducer(LPVOIDlpPara)
消费者函数:
DWORDWINAPIConsumer(LPVOIDlpPara)
执行函数位于第三个参数中
步骤4:
修改清单3-1中的程序,调整生产者线程和消费者线程的个数,使得消费者数目大与生产者,看看结果有何不同。
运行结果:
修改:
生产者2,消费者5
生产者个数多于消