简介一份面向操作系统课程实验场景的进程管理指导文档主题为Windows“任务管理器”的进程管理适合高校计算机专业学生、实验室操作人员及自学操作系统的读者使用。文档从背景知识切入先介绍任务管理器“应用程序、进程、性能”三个选项卡的功能再依次演示终止进程、显示其他进程计数器、更改正在运行的程序的优先级等操作并给出完整实验步骤和实验记录表格能帮助读者在熟悉Windows任务管理器用法的同时扎实理解进程、内存、句柄等操作系统核心概念。整个资源包仅含1个doc文档大小约251KB打开即可对照练习无需额外环境配置。这份文档已得到1013人学习下载曾用于操作系统上机实验参考可作为课前预习或课堂实验的模板使用。1. 实验文档里的任务管理器不只是按几个按钮的事很多人以为操作系统实验里“任务管理器”那一节就是开个窗口、看看进程再杀掉交份截图就算完事。等真打开这份实验.doc才知道里面其实埋了两条线索一条是任务管理器的界面操作终止进程、选列、改优先级另一条是Windows进程的“一生”从CreateProcess()创建、GetProcessVersion()查询到ExitProcess()与TerminateProcess()终止。这两条线一条偏界面、一条偏代码结合起来才算是把进程管理看全。这篇笔记把整个实验拆开重点讲清楚哪些操作是表面功夫、哪些参数是真正决定进程行为的关键以及我照着清单编译时踩过的几个坑。适合正在做操作系统实验、或者想搞明白任务管理器底层机制的同学。2. 任务管理器四张选项卡终止、选列与优先级的实际操作背景知识里反复提到“应用程序”“进程”“性能”三个选项卡实验文档还补了一个“联网”。实操时大多数同学只停留在“任务管理器能看CPU和内存”这个层面其实这四张表里真正跟进程管理强相关的是“进程”选项卡。我按实验步骤重新走了一遍把几个关键动作的细节和后果记录如下。2.1 终止进程的正确姿势先分清进程类别实验第一步是打开任务管理器切到“进程”选项卡系统会按照用户名把进程分成三类SYSTEM、NETWORK SERVICE/LOCAL SERVICE、以及Administrator用户进程。清单里记录的40个进程里系统进程25个、服务进程4个、用户进程12个加起来还差一个“System Idle Process”这类空闲进程常被忽略但它确实占着一个PID。终止进程时界面上的“结束进程”按钮会直接调用TerminateProcess()没有保存数据的机会。实验里要求挑一个进程结束然后对比前后记录。我当时选了个输入法相关的进程结果界面闪了一下又自动拉起来——因为这种进程有守护机制。这个现象本身就是知识点任务管理器“结束进程”杀的是单个进程但很多程序有父进程或服务在盯着杀了会被重新拉起。实操建议是终止前先看“用户名”列如果是SYSTEM或者服务类别除非心中有数否则别碰。实验文档里也提示了终止系统服务可能导致网络、打印等组件工作异常。如果只是为了交作业选一个自己启动的应用程序对应进程比如Word的WINWORD.EXE结束之后数据不会丢失因为Word有备份机制但严格来说这属于运气好不是操作正确。终止进程时有个隐藏行为值得注意如果进程有子进程直接结束父进程并不会自动清掉全部子进程树。任务管理器里“结束进程”按钮并没有“结束进程树”选项右键菜单里那个才是。实验表格要求记录“终止某进程后的操作结果”我建议把“子进程是否存活”也写进去这个观察比单纯记“进程消失”更能体现进程独立性的概念。2.2 显示其他进程计数器选列不只是加个表头实验第二部分是“显示其他进程计数器”在“进程”选项卡点“查看”→“选择列”。默认只有映像名称、用户名、CPU、内存使用勾选之后可以增加“页面错误”“句柄计数”“USER对象”等。这部分看起来是界面操作实际上隐藏着一个进程性能观测的核心问题任务管理器展示的指标分别代表什么。页面错误不是错误是进程访问的内存页不在物理内存中、需要从磁盘换入的次数。句柄计数是进程打开的窗口、文件、注册表键等内核对象的数量。USER对象和GDI对象则分别对应窗口管理和图形设备接口资源。如果程序出现窗口泄漏USER对象数值会持续增长。我一般勾选“句柄数”“USER对象”“工作集大小”这列比默认列更能看到进程的资源占用趋势。实验要求记录调整后显示的列名并把“排序”也练一下。任务管理器默认按CPU排序但CPU数值经常跳动我一般改成按“内存使用”排序这样能快速找出吃内存的进程。排序方式只是点击列标题再点一次反转顺序这个动作本身没什么技术含量但可以用来确认“当前占用最高的进程是谁”这种实际问题。2.3 优先级调整的影响边界高优先级不是万能钥匙实验第三部分要求右键设置优先级可选“实时”“高”“高于正常”“正常”“低于正常”“低”。改完运行3-6.exe观察屏幕显示是否有变化。我试过把进程设成“高”PID信息确实变了但界面响应速度变化不明显因为那程序本身就是循环输出瓶颈不在调度而在于控制台IO。优先级真正的意义是告诉调度器“相对其他进程这个进程应获得多少CPU时间”。设置成“实时”会让该进程抢占绝大多数CPU时间鼠标键盘都可能卡顿。实验文档里提到“在多处理器计算机上可以设置处理器关联”这个在Windows 2000时代是限制进程只在指定CPU上运行现在任务管理器的“相关性”就是同一件事。设置相关性会导致总体性能下降因为进程不能利用其他空闲核——这是很容易被忽略的边界。实际操作时还会遇到一个权限问题普通用户只能改自己进程的优先级改系统进程会提示“拒绝访问”。实验代码里用SetPriorityClass()修改自身优先级所以能够成功。如果改成别的进程就需要调试权限或者管理员身份。这一点在避坑章节会展开。3. 进程的一生CreateProcess参数拆解与克隆程序实战实验的第二部分“Windows 2000进程的一生”才是这份文档的硬核内容。文档说“Windows所创建的每个进程都以调用CreateProcess()开始以ExitProcess()或TerminateProcess()终止”接着给了清单3-5的完整代码。这个程序会不断克隆自身每个子进程都带着一个递增的编号最多创建25个。我照着在Visual C 6.0里编译运行中间出了不少问题下面把参数和踩坑过程写清楚。3.1 十个参数逐一说明哪个能填NULL哪个不能省CreateProcess()是Windows进程创建的底层API原声明有10个参数。实验代码里从第三个参数开始填的都是NULL或FALSE容易让人以为不重要。实际不是只是这个场景里需要默认值。代码片段如下BOOL bCreateOK :: CreateProcess( szFilename, // 应用程序名称当前EXE的完整路径 szCmdLine, // 命令行EXE路径 空格 克隆ID NULL, // 进程安全属性使用默认安全描述符 NULL, // 线程安全属性使用默认安全描述符 FALSE, // 不继承调用者的句柄 CREATE_NEW_CONSOLE,// 新进程分配独立控制台窗口 NULL, // 使用父进程的环境变量块 NULL, // 使用父进程的当前目录 si, // STARTUPINFO结构指定窗口样式等 pi // PROCESS_INFORMATION返回句柄和ID );参数说明第一个参数lpApplicationName可以不传完全路径系统会按当前目录和PATH环境变量搜索但传完整路径最稳妥。实验里用GetModuleFileName(NULL, ...)拿到当前进程自己的EXE路径这是“克隆自身”的关键。第二个参数lpCommandLine传给新进程的argv。这里把克隆ID拼在路径后面子进程的main函数里用argc 1判断并解析。第五参数bInheritHandle设为FALSE意味着子进程不会继承父进程已打开的文件句柄、事件等内核对象。这个在复杂程序里很容易埋雷父进程打开的日志文件句柄如果被继承子进程退出也可能影响文件关闭。第六参数CREATE_NEW_CONSOLE专门给控制台程序用的否则子进程会共用父进程的控制台窗口。实验里任务栏上会出现一堆相同图标的窗口就是它造成的。第七、第八参数填NULL表示子进程复制父进程的环境变量和当前目录。第九参数STARTUPINFO要先把结构体清零并设置cb大小否则CreateProcess会校验失败。第十参数PROCESS_INFORMATION是输出缓冲里面放着子进程的进程句柄、线程句柄和PID。CreateProcess()返回后pi.hProcess和pi.hThread指向内核对象。文档里特别强调“调用CloseHandle()关闭这两个句柄很重要”——这是句柄泄漏的高发点。3.2 编译清单3-5的翻车记录语法坑与构建顺序实验文档里的代码文本经历了OCR和复制粘贴我拿到手直接编译是不通过的。最常见的几个坑代码里的引号是中文全角引号“ ”在C字符串里必须替换成半角。sprintf在VC里可能提示不安全建议改成wsprintf或_snprintf不过实验环境是VC6.0老式::sprintf能用。main函数参数类型没问题但StartClone里用到TCHAR和MAX_PATH需要包含windows.h和tchar.h且工程设置里要打开UNICODE或MBCS一致否则字符串类型对不上。编译步骤也有顺序要求先Compile生成目标文件再Build链接成EXE。如果工程没有活动工作区VC6会问“是否建立default project workspace”必须点“是”。如果直接点Build它会先编译再链接但一般会先弹出保存工作区的对话框。我当时因为没建工程直接打开.cpp文件点Compile报了一大堆错误后来新建了“Win32 Console Application”空工程再把代码粘贴进去才通过。这个清单还有个逻辑点if (nClone c_nCloneMax)里常量名是c_nCloneMax代码里写的是C_nCloneMax文档原文有个细节创建常量和判断处大小写不一致会导致编译报“undeclared identifier”。实际复制时要注意统一。这说明实验代码本身可能有笔误属于正常现象能跑通才算完成了“排错”训练。3.3 句柄关闭与CreateProcess的返回值检查代码里只检查了bCreateOK如果创建失败pi里的句柄是无效的不能盲目CloseHandle。正确做法是创建成功再关闭失败时调用GetLastError()看错误码。常见错误码有ERROR_FILE_NOT_FOUND路径不对、ERROR_ACCESS_DENIED权限不够。实验代码里创建成功后立刻CloseHandle这是合理的因为父进程不需要等待子进程也不需要使用子进程句柄做其他事。但要注意关闭句柄不等于终止进程只是释放对内核对象的引用子进程仍然继续运行。这正好能解释为什么程序运行后任务栏上一堆窗口依然活跃。如果真的需要等待子进程结束应该保留pi.hProcess并用WaitForSingleObject(pi.hProcess, INFINITE)阻塞等待。实验里用了Sleep(500)来暂停主线程这属于偷懒写法。实际生产代码里等待子进程应该用Wait函数而不是固定Sleep否则子进程没结束父进程先退出任务栏窗口还在闪。这个区别实验文档没有明说但属于进程编程的基本修养。4. 进程信息查询GetProcessVersion与版本结构的使用清单3-6演示了进程自身的PID查询、系统版本获取以及把当前进程优先级设置为HIGH_PRIORITY_CLASS。代码思路很清晰但有几个细节需要注意——尤其是程序先判断系统是Windows 2000以上再提高优先级这个“版本检查”逻辑在现在的新系统上会走另外一个分支效果不同。4.1 从GetCurrentProcessId到OS版本号解析代码第一段用GetCurrentProcessId()拿到当前进程的PID然后调用GetProcessVersion(PID)。这个API返回一个32位整数高16位是主版本号低16位是次版本号。文档里的代码写作(WORD) dwVerReq 16)明显是OCR错误应该是 16。我补全后运行正常。第二段用OSVERSIONINFOEX结构配合GetVersionEx()获取系统版本。这个结构里dwMajorVersion和dwMinorVersion表示主次版本号。比如Windows 2000是5.0Windows XP是5.1Windows 7是6.1Windows 10是10.0。实验记录里写了“OS: 6.1”说明当时跑在Windows 7上代码判断dwMajorVersion 5通过于是执行了优先级修改。代码片段DWORD dwIdThis ::GetCurrentProcessId(); DWORD dwVerReq ::GetProcessVersion(dwIdThis); WORD wMajorReq (WORD)(dwVerReq 16); WORD wMinorReq (WORD)(dwVerReq 0xffff); std::cout Process ID: dwIdThis , requires OS: wMajorReq . wMinorReq std::endl; OSVERSIONINFOEX osvix; ::ZeroMemory(osvix, sizeof(osvix)); osvix.dwOSVersionInfoSize sizeof(osvix); ::GetVersionEx(reinterpret_castLPOSVERSIONINFO(osvix)); std::cout Running on OS: osvix.dwMajorVersion . osvix.dwMinorVersion std::endl; if (osvix.dwPlatformId VER_PLATFORM_WIN32_NT osvix.dwMajorVersion 5) { ::SetPriorityClass(::GetCurrentProcess(), HIGH_PRIORITY_CLASS); std::cout Task Manager should now indicate this process is high priority. std::endl; }这段代码有个兼容性问题OSVERSIONINFOEX在Windows 2000之后才引入在老系统上编译需要定义_WIN32_WINNT0x0500。如果不定义部分SDK版本会报结构未定义。另外GetVersionEx在新系统上可能被屏蔽返回值微软建议用RtlGetVersion或兼容层但实验教学环境用老VC6.0没问题。代码里的ZeroMemory是memset的宏封装只用于置零没有踩坑点。4.2 SetPriorityClass提高优先级的条件与副作用SetPriorityClass第二个参数传HIGH_PRIORITY_CLASS把进程优先级提到“高”。这个操作在任务管理器里看到的效果是“高”但要注意前提条件进程必须有PROCESS_SET_INFORMATION访问权限。自己改自己没有权限问题但改别的进程很可能失败。副作用方面把进程提为高优先级会让它占用更多CPU时间片系统的其他程序会变卡。实验里这个程序只是输出几行文本就退出了影响几乎为零。但如果把它改成一个死循环再把优先级调成“实时”(REALTIME_PRIORITY_CLASS)键盘鼠标都可能无响应。这个现象值得自己试一次试过就知道为什么操作系统教材强调“实时优先级要谨慎使用”。4.3 在任务管理器里验证代码运行结果实验步骤要求运行3-6.exe后用CtrlAltDel打开任务管理器在“应用程序”选项卡右键该任务选“转到进程”查看对应映像名称。我们的运行结果是对应到VCSPAWN.EXE这其实是Visual C的调试辅助进程不是3-6.exe本身。这给了一个很好的观察点从Visual Studio环境执行程序时编译器可能会用rundll32、vcspawn等辅助进程来启动目标程序导致任务管理器里看到的多了一个“中间进程”。如果直接在命令行运行编译好的3-6.exe任务管理器里就会显示3-6.exe。从这也能解释为什么文档里问“在可视化界面下运行与命令行运行有什么不同”——不只是显示方式不同进程的父进程、路径、甚至映像名都可能不同。这个体会对理解“进程是程序的一次执行”很有帮助。5. 避坑专区进程实验里最常见的五个翻车现场这一章专门记录我反复做这个实验过程中踩过、以及帮同学排过的坑每一条都是“现象→原因→解决”的结构照着排查能省不少时间。5.1 编译清单3-5时一堆未声明标识符现象按文档操作打开源文件直接编译报错“sprintf undeclared”“TCHAR undeclared”或者大量“missing ; before string constant”。 原因工程没建立或者代码粘贴时把全角引号、大小写错误的常量名一起带进来了另外缺少必要的头文件包含。 解决先新建Win32 Console Application空工程再添加.cpp文件。把代码开头的#include windows.h和#include stdio.h保留完整引号统一替换成半角。特别注意c_nCloneMax常量在定义处和判断处的大小写必须一致。5.2 结束进程后程序又自动回来了现象结束某个进程列表里隔几秒又出现同名进程或者电脑弹窗提示“某程序已停止工作”后重启。 原因进程有父进程守护或者系统服务自动拉起。很多输入法、更新程序都采用多进程架构杀主进程后监护进程会重建它。 解决先看“用户名”列判断是用户进程还是服务进程。如果是用户进程尝试右键“结束进程树”来连带终止子进程如果是服务应该先去服务管理单元把它停掉再来清理进程。实验里记录原始数据时要标注“重启前后”这样报告会更完整。5.3 改优先级提示“拒绝访问”现象右键系统进程或别的用户进程选“设置优先级”弹窗拒绝或者确定后优先级并没有变。 原因任务管理器按用户权限限制操作。普通用户只能修改自己启动的进程修改SYSTEM进程需要管理员权限。 解决用管理员身份运行任务管理器。如果还想更深入可以右键任务管理器快捷方式选“以管理员身份运行”。改完优先级后在“进程”选项卡添加“基本优先级”列确认数值变化。5.4 运行3-6.exe后任务管理器里找不到对应进程现象程序运行时间太短还没来得及在任务管理器里刷新列表进程已经退出了。 原因3-6.exe只做了几行输出和一次优先级设置运行时间不超过0.5秒。手速跟不上进程生命周期。 解决在main函数最后加一句Sleep(5000)留出观察时间。或者编译一个带循环版本的测试程序比如连续输出PID并休眠。实验代码本身用了Sleep(500)在主线程但那个是在创建子进程实验里3-6没有。临时修改不算作弊而是实验手段。5.5 程序编译通过但运行时窗口一闪而过现象双击EXE或按CtrlF5执行控制台窗口闪一下就没了看不到输出。 原因main函数正常返回进程调用ExitProcess退出控制台窗口随之关闭。VC6里按CtrlF5有时会附加“pause”参数但命令行直接运行不会。 解决在main函数结尾加getchar();或Sleep(...)保证窗口停留。临时调试可以最终代码不要保留这种无意义的等待或者用实验文档里的Sleep(500)作为折中。6. 把实验代码改造成一个“进程体检器”组合技巧实验做完后这两份清单代码其实可以合并成一个能在命令行下查出关键进程信息的小工具。我一般这样做先调用GetCurrentProcessId拿到自己的PID再用CreateToolhelp32Snapshot遍历全部进程把每个进程的映像名、PID、优先级列出来然后用实验里学到的GetProcessVersion判断可执行文件的兼容性。这样一套下来对任务管理器里看到的每一列都心里有底。具体实现思路不复杂。创建一个快照遍历进程列表比较映像名和PID输出信息。可以直接复用3-6里的GetProcessVersion逻辑来显示“这个EXE要求的主版本号”。如果发现某个进程要求的OS版本比当前系统高那大概率是程序跑不起来的原因。这个检测手段在排查老软件时特别有用。写完之后我习惯手动验证用任务管理器里“进程”选项卡的PID列和命令行程序输出的PID对照如果一致说明快照确实是当下正在跑的进程。再模拟一轮“用任务管理器结束进程”看看自己写的程序里是否还占着句柄——这能验证CloseHandle是否漏关。实验文档里反复强调句柄释放不实际遍历一次进程列表很难有直观感受。从那以后我每次遇到程序被杀后残留进程的问题都会强制走一遍“查看进程树、核对优先级、确认映像名”的流程而不是直接盲杀。资源里的清单3-5和3-6配合任务管理器界面足够把进程的创建到终止这条路走通。把这两份实验串起来做一遍你对进程这个概念的理解会比只刷视频扎实得多。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?