您好,欢迎来到五一七教育网。
搜索
您的当前位置:首页《操作系统》大作业

《操作系统》大作业

来源:五一七教育网
西安财经学院信息学院

《操作系统》 实验报告

实验名称 进程描述、创建与撤销 实验室 实验楼316 实验日期 2019年 03 月 28 日 姓名 孙晶钰 学号 1705990821 班级 计算机类1708 年级 2017级 指导教师 谢 晶 一.实验目的 1. 通过对Windows 编程,进一步熟悉操作系统的基本概念,较好地理解Windows的结构。 2. 通过创建进程、观察正在运行的进程和终止进程的程序设计和调试操作,进一步熟悉操作系统的进程概念,理解Windows进程生存过程。 3. 通过阅读和分析实验程序,学习创建进程、观察进程和终止进程的程序设计方法。 二.实验环境 需要准备一台运行Windows XP Professional操作系统的计算机,且该计算机中需安装Visual C++ 6.0专业版或企业版。 三.实验相关知识 1. Windows 编程初步 Windows 可以识别的应用程序包括控制台应用程序、GUI应用程序和服务应用程序。控制台应用程序可以创建GUI,GUI应用程序可以作为服务来运行,服务也可以向标准的输出流写入数据。不同类型应用程序间的惟一重要区别是其启动方法。 Windows 是以NT的技术构建的,它提供了创建控制台应用程序的能力,使用户可以利用标准的C++工具,如iostream库中的cout和cin对象,来创建小型应用程序。当系统运行时,Windows 的服务通常要向系统用户提供所需功能。 服务应用程序类型需要ServiceMail() 函数,由服务控制管理器 (SCM) 加以调用。SCM是操作系统的集成部分,负责响应系统启动以开始服务、指导用户控制或从另一个服务中来的请求。其本身负责使应用程序的行为像一个服务。通常,服务登录到特殊的LocalSystem账号下,此账号具有与开发人员创建的服务不同的权限。 当令C++ 编译器创建可执行程序时,编译器将源代码编译成OBJ文件,然后将其与标准库相链接。产生的EXE文件是装载器指令、机器指令和应用程序的数据的集合。装载器指令告诉系统从哪里装载机器代码。另一个装载器指令告诉系统从哪里开始执行进程的主线程。在进行某些设置后,进入开发者提供的main() 、ServiceMain() 或WinMain() 函数的低级入口点。机器代码中包括有控制逻辑,它所做的事包括跳转到Windows API函数,进行计算或向磁盘写入数据等。 Windows允许开发人员将大型应用程序分为较小的、互相有关系的服务模块,即动态链接库 (DLL) 代码块,在其中包含应用程序所使用的机器代码和应用程序的数据。 2. Windows 进程的“生命” Windows所创建的每个进程都从调用CreateProcess() API函数开始,该函数的任务是在对象管理器子系统内初始化进程对象。每一进程都以调用ExitProcess() 或TerminateProcess() API函数终止。通常应用程序的框架负责调用 ExitProcess() 函数。对于C++ 运行库来说,这一调用发生在应用程序的main() 函数返回之后。 2.1 创建进程 CreateProcess() 调用的核心参数是可执行文件运行时的文件名及其命令行。表 3.4详细地列出了每个参数的类型和名称。 表2.1 CreateProcess() 函数的参数 参数名称 LPCTSTR lpApplivationName LPCTSTR lpCommandLine LPSECURIITY_ATTRIBUTES lpProcessAttributes LPSECURIITY_ATTRIBUTES lpThreadAttributes BOOL bInheritHandle DWORD dwCreationFlage LPVOID lpEnvironment LPCTSTR lpCurrentDirectory STARTUPINFO lpStartupInfo LPPROCESS_INFORMATION lpProcessInformation ID 情 调用的结果块;发送新应用程序的进程和主线程的句柄和使用目的 全部或部分地指明包括可执行代码的EXE文件的文件名 向可执行文件发送的参数 返回进程句柄的安全属性。主要指明这一句柄是否应该由其他子进程所继承 返回进程的主线程的句柄的安全属性 一种标志,告诉系统允许新进程继承创建者进程的句柄 特殊的创建标志 (如CREATE_SUSPENDED) 的位标记 向新进程发送的一套环境变量;如为null值则发送调用者环境 新进程的启动目录 STARTUPINFO结构,包括新进程的输入和输出配置的详 可以指定第一个参数,即应用程序的名称,其中包括相对于当前进程的当前目录的全路径或者利用搜索方法找到的路径;lpCommandLine参数允许调用者向新应用程序发送数据;接下来的三个参数与进程和它的主线程以及返回的指向该对象的句柄的安全性有关。 然后是标志参数,用以在dwCreationFlags参数中指明系统应该给予新进程什么行为。经常使用的标志是CREATE_SUSPNDED,告诉主线程立刻暂停。当准备好时,应该使用ResumeThread() API来启动进程。另一个常用的标志是CREATE_NEW_ CONSOLE,告诉新进程启动自己的控制台窗口,而不是利用父窗口。这一参数还允许设置进程的优先级,用以向系统指明,相对于系统中所有其他的活动进程来说,给此进程多少CPU时间。 接着是CreateProcess() 函数调用所需要的三个通常使用缺省值的参数。第一个参数是lpEnvironment参数,指明为新进程提供的环境;第二个参数是lpCurrent Directory,可用于向主创进程发送与缺省目录不同的新进程使用的特殊的当前目录;第三个参数是STARTUPINFO数据结构所必需的,用于在必要时指明新应用程序的主窗口的外观。 CreateProcess() 的最后一个参数是用于新进程对象及其主线程的句柄和ID的返回值缓冲区。以PROCESS_INFORMATION结构中返回的句柄调用CloseHandle() API函数是重要的,因为如果不将这些句柄关闭的话,有可能危及主创进程终止之前的任何未释放的资源。 2.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 (进程标识号) 。本实验的程序清单2-4中列出了这一API函数与GetVersionEx() 的共同作用,可确定运行进程的系统的版本号。 2.3 终止进程 所有进程都是以调用ExitProcess() 或者TerminateProcess() 函数结束的。但最好使用前者而不要使用后者,因为进程是在完成了它的所有的关闭“职责”之后以正常的终止方式来调用前者的。而外部进程通常调用后者即突然终止进程的进行,由于关闭时的途径不太正常,有可能引起错误的行为。 TerminateProcess() API函数只要打开带有PROCESS_TERMINATE访问权的进程对象,就可以终止进程,并向系统返回指定的代码。这是一种“野蛮”的终止进程的方式,但是有时却是需要的。 如果开发人员确实有机会来设计“谋杀”(终止别的进程的进程) 和“受害”进程 (被终止的进程) 时,应该创建一个进程间通讯的内核对象——如一个互斥程序——这样一来,“受害”进程只在等待或周期性地测试它是否应该终止。 四.实验内容 1.编写GUI应用程序实验 在下面的实验中,C++ 编译器创建一个GUI应用程序,代码中包括了WinMain() 方法,这是GUI类型的应用程序的标准入口点。 步骤1:在\"开始\">\"所有程序\">\"附件\"菜单中单击\"记事本\"命令,将清单2-1中的程序键入记事本中,并把代码保存为2-1.cpp。 清单2-1 Windows XP的GUI应用程序 // msgbox项目 # include // 标准的include // 告诉连接器与包括MessageBox API函数的user32库进行连接 # pragma comment(lib,\"user32.lib\") // 这是一个可以弹出信息框然后退出的筒单的应用程序 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { ::MessageBox( NULL, // 没有父窗口 \"Hello,Windows 2000\ // 消息框中的文本 \"Greetings\ // 消息框标题 MB_OK); // 其中只有一个OK按钮 // 返回0以便通知系统不进入消息循环 return(0); } 也可以利用任何其他文本编辑器键入程序代码,如果这样,例如使用WORD来键入和编辑程序,则应该注意什么问题? 注意格式和内容在中英文下的切换;养成习惯写备注的习惯,以便提醒自己;_注意标点符号的区分。______________________________ _______________________ 步骤2:在\"命令提示符\"窗口运行CL.EXE,产生2-1.EXE文件: C:\\> CL 2-1.cpp 在清单2-1的GUI应用程序中,首先需要Windows.h头文件,以便获得传送给WinMain() 和MessageBox() API函数的数据类型定义。 接着的pragma指令指示编译器/连接器找到User32.LIB库文件并将其与产生的EXE文件连接起来。这样就可以运行简单的命令行命令CL 2-1.CPP来创建这一应用程序,如果没有pragma指令,则MessageBox() API函数就成为未定义的了。这一指令是Visual Studio C++ 编译器特有的。 接下来是WinMain() 方法。其中有四个由实际的低级入口点传递来的参数。hInstance参数用来装入与代码相连的图标或位图一类的资源,无论何时,都可用GetModuleHandle() API函数将这些资源提取出来。系统利用实例句柄来指明代码和初始的数据装在内存的何处。句柄的数值实际上是EXE文件映像的基地址,通常为0x00400000。下一个参数hPrevInstance是为向后兼容而设的,现在系统将其设为NULL。应用程序的命令行 (不包括程序的名称) 是lpCmdLine参数。另外,系统利用nCmdShow参数告诉应用程序如何显示它的主窗口 (选项包括最小化、最大化和正常) 。 最后,程序调用MessageBox() API函数并退出。如果在进入消息循环之前就结束运行的话,最后必须返回0。 请记录:运行结果: 进程对象实验 操作系统将当前运行的应用程序看作是进程对象。利用系统提供的惟一的称为句柄 (HANDLE) 的号码,就可与进程对象交互。这一号码只对当前进程有效。 本实验表示了一个简单的进程句柄的应用。在系统中运行的任何进程都可调用GetCurrentProcess() API函数,此函数可返回标识进程本身的句柄。然后就可在Windows需要该进程的有关情况时,利用这一句柄来提供。 步骤1:将清单2-2.cpp程序键入记事本中,并把代码保存为2-2.cpp。 清单2-2 获得和使用进程的句柄 // prochandle项目 # include # include // 确定自己的优先权的简单应用程序 void main() { // 从当前进程中提取句柄 HANDLE hProcessThis=::GetCurrentProcess(); // 请求内核提供该进程所属的优先权类 DWORD dwPriority=::GetPriorityClass(hProcessThis); // 发出消息,为用户描述该类 std::cout<<\"Current process priority:\"; switch(dwPriority) { case HIGH_PRIORITY_CLASS: std::cout<<\"High\"; break; case NORMAL_PRIORITY_CLASS: std::cout<<\"Normal\"; break; case IDLE_PRIORITY_CLASS: std::cout<<\"Idle\"; break; case REALTIME_PRIORITY_CLASS: std::cout<<\"Realtime\"; break; default: std::cout<<\"\"; break; } std::cout< CL 2-2.cpp 请记录:运行结果: 步骤3:将清单2-3.cpp程序键入记事本中,并把代码保存为2-3.cpp。 清单2-3显示如何找出系统中正在运行的所有进程,如何利用OpenProcess() API函数来获得每一个访问进程的进一步信息。 清单2-3 利用句柄查出进程的详细信息 // proclist项目 # include # include # include // 当在用户模式和内核模式下都提供所耗时间时,在内核模式下进行所耗时间的位计算的帮助方法 DWORD GetKernelModePercentage(const FILETIME & ftKernel, const FILETIME & ftUser) { // 将FILETIME结构转化为位整数 ULONGLONG qwKernel=(((ULONGLONG) ftKernel.dwHighDateTime)<<32)+ ftKernel.dwLowDateTime; ULONGLONG qwUser=(((ULONGLONG) ftUser.dwHighDateTime)<<32)+ ftUser.dwLowDateTime; // 将消耗时间相加,然后计算消耗在内核模式下的时间百分比 ULONGLONG qwTotal=qwKernel+qwUser; DWORD dwPct=(DWORD)(((ULONGLONG)100*qwKernel)/qwTotal); return(dwPct); } // 以下是将当前运行进程名和消耗在内核模式下的时间百分数都显示出来的应用程序 void main() { // 对当前系统中运行的进程拍取\"快照\" HANDLE hSnapshot=::CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, // 提取当前进程 0); // 如果是当前进程,就将其忽略 // 初始化进程入口 PROCESSENTRY32 pe; ::ZeroMemory(&pe,sizeof(pe)); pe.dwSize=sizeof(pe); // 按所有进程循环 BOOL bMore=::Process32First(hSnapshot,&pe); while(bMore) { // 打开用于读取的进程 HANDLE hProcess=::OpenProcess( PROCESS_QUERY_INFORMATION, // 指明要得到信息 FALSE, // 不必继承这一句柄 pe.th32ProcessID); // 要打开的进程 if (hProcess!=NULL) { // 找出进程的时间 FILETIME ftCreation,ftExit,ftKernelMode,ftUserMode; ::GetProcessTimes( hProcess, // 所感兴趣的进程 &ftCreation, // 进程的启动时间 (绝对的) &ftExit, // 结束时间 (如果有的话) &ftKernelMode, // 在内核模式下消耗的时间 &ftUserMode) ; // 在用户模式下消耗的时间 // 计算内核模式消耗的时间百分比 DWORD dwPctKernel=::GetKernelModePercentage( ftKernelMode, // 在内核模式上消耗的时间 ftUserMode); // 在用户模式下消耗的时间 // 向用户显示进程的某些信息 std::cout<<\"Process ID:\" < CL 2-2.cpp 请记录:运行结果: 3.创建进程实验 本实验显示了创建子进程的基本框架。该程序创建的子进程依然执行父进程的程序代码,显示它的系统进程ID和它在进程列表中的位置。 步骤1:登录进入Windows。 步骤2:在\"开始\"菜单中单击\"程序\"> Microsoft Visual Studio 6.0 > Microsoft Visual C++ 6.0命令,进入Visual C++窗口。 步骤3:编辑实验源程序2-3.cpp (也可直接打开下载的源程序文件2-3.cpp) 。 清单2-3 创建子进程 // proccreate项目 # include # include # include // 创建传递过来的进程的克隆过程并赋于其ID值 void StartClone(int nCloneID) { // 提取用于当前可执行文件的文件名 TCHAR szFilename[MAX_PATH]; ::GetModuleFileName(NULL,szFilename,MAX_PATH); // 格式化用于子进程的命令行并通知其EXE文件名和克隆ID TCHAR szCmdLine[MAX_PATH]; ::sprintf(szCmdLine,\"\\\"%s\\\"%d\ // 用于子进程的STARTUPINFO结构 STARTUPINFO si; :: ZeroMemory(reinterpret_cast (&si), sizeof(si)); si.cb=sizeof(si); // 必须是本结构的大小 // 返回的用于子进程的进程信息 PROCESS_INFORMATION pi; // 利用同样的可执行文件和命令行创建进程,并赋于其子进程的性质 BOOL bCreateOK=::CreateProcess( szFilename, // 产生这个EXE的应用程序的名称 szCmdLine, // 告诉其行为像一个子进程的标志 NULL, // 缺省的进程安全性 NULL, // 缺省的线程安全性 FALSE, // 不继承句柄 CREATE_NEW_CONSOLE, // 使用新的控制台 NULL, // 新的环境 NULL, // 当前目录 &si, // 启动信息 &pi); // 返回的进程信息 // 对子进程释放引用 if (bCreateOK) { ::CloseHandle(pi.hProcess); ::CloseHandle(pi.hThread); } } int main(int argc,char* argv[]) { // 确定进程在列表中的位置 int nClone(0); if (argc>1) { // 从第二个参数中提取克隆ID ::sscanf(argv[1],\"%d\ } // 显示进程位置 std::cout<<\"Process ID:\"<<::GetCurrentProcessId() <<\ // 检查是否有创建子进程的需要 const int c_nCloneMax=25; if (nClone # include // 利用进程和操作系统的版本信息的简单示例 void main() { // 提取这个进程的ID号 DWORD dwIdThis=::GetCurrentProcessId(); // 获得这一进程和报告所需的版本,也可以发送0以便指明这一进程 DWORD dwVerReq=::GetProcessVersion(dwIdThis); WORD wMajorReq=(WORD)(dwVerReq>16); WORD wMinorReq=(WORD)(dwVerReq & 0xffff); std::cout<<\"Process ID:\"<(&osvix)); std::cout<<\"Running on OS:\"<= 5) { if(dwProcessP!=HIGH_PRIORITY_CLASS){ // 如果当前优先级不是high,则改变优先级 :: SetPriorityClass( :: GetCurrentProcess(), // 利用这一进程 HIGH_PRIORITY_CLASS); // 改变为high // 报告给用户 dwProcessP=GetPriorityClass(GetCurrentProcess()); std::cout<<\"The process priority have been changed to \"; switch(dwProcessP) { case HIGH_PRIORITY_CLASS: std::cout<<\"High\"; break; case NORMAL_PRIORITY_CLASS: std::cout<<\"Normal\"; break; case IDLE_PRIORITY_CLASS: std::cout<<\"Idle\"; break; case REALTIME_PRIORITY_CLASS: std::cout<<\"Realtime\"; break; default: std::cout<<\"\"; break; } std::cout< # include # include static LPCTSTR g_szMutexName=\"w2kdg.ProcTerm.mutex.Suicide\"; // 创建当前进程的克隆进程的简单方法 void StartClone() { // 提取当前可执行文件的文件名 TCHAR szFilename [MAX_PATH]; ::GetModuleFileName(NULL,szFilename,MAX_PATH); // 格式化用于子进程的命令行,指明它是一个EXE文件和子进程 TCHAR szCmdLine[MAX_PATH]; ::sprintf(szCmdLine,\"\\\" %s\\ \" child\ // 子进程的启动信息结构 STARTUPINFO si; ::ZeroMemory(reinterpret_cast(&si), sizeof(si)); si.cb=sizeof(si); // 应当是此结构的大小 // 返回的用于子进程的进程信息 PROCESS_INFORMATION pi; // 用同样的可执行文件名和命令行创建进程,并指明它是一个子进程 BOOL bCreateOK=::CreateProcess( szFilename, // 产生的应用程序名称 (本EXE文件) szCmdLine, // 告诉我们这是一个子进程的标志 NULL, // 用于进程的缺省的安全性 NULL, // 用于线程的缺省安全性 FALSE, // 不继承句柄 CREATE_NEW_CONSOLE, // 创建新窗口,使输出更直观 NULL, // 新环境 NULL, // 当前目录 &si, // 启动信息结构 &pi); // 返回的进程信息 // 释放指向子进程的引用 if (bCreateOK) { ::CloseHandle(pi.hProcess); ::CloseHandle(pi.hThread); } } void Parent() { // 创建\"自杀\"互斥程序体 HANDLE hMutexSuicide=::CreateMutex( NULL, // 缺省的安全性 TRUE, // 最初拥有的 g_szMutexName); // 为其命名 if (hMutexSuicide!=NULL) { // 创建子进程 std::cout<<\"Creating the child process.\"<l && ::strcmp(argv[l],\"child\")==0) { Child(); } else { Parent(); } return 0; } 清单2-5中的程序说明了一个进程从\"生\"到\"死\"的整个一生。第一次执行时,它创建一个子进程,其行为如同\"父亲\"。在创建子进程之前,先创建一个互斥的内核对象,其行为对于子进程来说,如同一个\"自杀弹\"。当创建子进程时,就打开了互斥体并在其他线程中进行别的处理工作,同时等待着父进程使用ReleaseMutex() API发出\"死亡\"信号。然后用Sleep() API调用来模拟父进程处理其他工作,等完成时,指令子进程终止。 当调用ExitProcess() 时要小心,进程中的所有线程都被立刻通知停止。在设计应用程序时,必须让主线程在正常的C++ 运行期关闭 (这是由编译器提供的缺省行为) 之后来调用这一函数。当它转向受信状态时,通常可创建一个每个活动线程都可等待和停止的终止事件。 在正常的终止操作中,进程的每个工作线程都要终止,由主线程调用ExitProcess()。接着,管理层对进程增加的所有对象释放引用,并将用 GetExitCodeProcess() 建立的退出代码从STILL_ACTIVE改变为在ExitProcess() 调用中返回的值。最后,主线程对象也如同进程对象一样转变为受信状态。 等到所有打开的句柄都关闭之后,管理层的对象管理器才销毁进程对象本身。还没有一种函数可取得终止后的进程对象为其参数,从而使其\"复活\"。当进程对象引用一个终止了的对象时,有好几个API函数仍然是有用的。进程可使用退出代码将终止方式通知给调用GetExitCodeProcess() 的其他进程。同时,GetProcessTimes() API函数可向主调者显示进程的终止时间。 步骤2:单击Build菜单中的Compile 2-5.cpp命令,再单击\"是\"按钮确认。系统对2-5.cpp进行编译。 步骤3:编译完成后,单击Build菜单中的Build 2-5.exe命令,建立2-5.exe可执行文件。 请记录:操作能否正常进行?如果不行,则可能的原因是什么? 可以正常运行,如果不能正常运行,则可能的原因是标点符号是英文输入法输入的或中文输入法输入的问题。 步骤4:在工具栏单击Execute Program按钮,执行2-5.exe程序。 运行结果: 1) 第一个窗口显示的是: 表示: 进程等待命令。____________________________________________ 2) 第二个窗口显示的是: 表示: 创建子进程,子进程杀掉自己。 步骤5:在熟悉清单2-5源代码的基础上,利用本实验介绍的API函数来尝试改进本程序 (例如使用GetProcessTimes() API函数) 并运行。请描述你所做的工作: 创建进程以及子进程以及终止进程,用API去创建,改变以及终止进程。 _____________________________________________________________ ___ 五.实验总结 ______通过这次实验,我学会了怎样创建和终止租序,对操作系统有了更深刻的了解。值得注意的是,终止进程时,一般调用ExitProcess()或者TerminateProcess(),但最好使用前者而不要使用后者,固为进程是在完成了它的所有的关闭“职责”之后以正帝的终止方式来调用前者的.而外都进程通常调用后者即突然终止进程的进行,由于关闭时的途径不太正奢,有可能引起错误的行为。而且,TerminateProces50APl函数只能打开带有PROCESS_TERMINATE访问权的进程对象,比如,如果打开word文档进程的话,在撤销的时候就会出现“拒绝访问”的错误。总之,收获很多。__________

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- 517ttc.cn 版权所有 赣ICP备2024042791号-8

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务