实验一:进程控制
实验目的
通过在Windows 任务管理器中对进程进行相应的管理操作,熟悉操作系统进程管理的概念,学习观察操作系统运行的动态性能;学习创建进程、观察进程和终止进程的程序设计方法;理解Windows进程的“一生”。
实验内容
(1)使用任务管理器查看、终止进程;
(2)创建进程
(3)获取进程运行信息,改变进程优先级
(4)终止进程
实验步骤
1、创建进程
(1)父进程创建子进程,子进程创建下一个子进程;
(2)创建子进程时将子进程序号作为命令行参数传给子进程,子进程将序号加一后继续创建下一个子进程;
(3)限制创建子进程的最大个数;
(4)进程在终止之前暂停一下,输入一个字符后,进程结束,以便观察。
CreateProcess.cpp
//Defines the entry point for the console application
//Defines the entry point for the console application
#include"stdafx.h"
#include<windows.h>
#include<iostream>
#include<stdio.h>
//创建子进程,与父进程执行相同的程序
void StartClone(int nCloneID)
{
//获取当前进程的可执行文件名
TCHAR szFilename[MAX_PATH];
//获取当前应用程序的路径
::GetModuleFileName(NULL,szFilename,MAX_PATH);
/*
第一个参数:
模块句柄指向的就是EXE和DLL等在虚拟地址空间的位置。
如果该参数为NULL,该函数返回该应用程序全路径。
第二个参数:
存放返回的名字的内存块的指针,是一个输出参数
第三个参数:
内存块的大小,用于防止溢出.
*/
// 格式化用于创建新进程的命令行,包括EXE文件名和克隆ID
//char Tchar(char,wchar) LPCSTR LPSTR
TCHAR szCmdLine[MAX_PATH];
::sprintf(szCmdLine,"\"%s\"%d",szFilename,nCloneID);
//用于新进程的STARTUPINFO结构
STARTUPINFO si;
::ZeroMemory(reinterpret_cast<void*>(&si),sizeof(si));
si.cb =sizeof(si);// 必须是本结构的大小
// 返回的用于新进程的进程信息
PROCESS_INFORMATION pi;
//使用同一可执行文件和带有克隆ID的命令行创建新进程。
//CreateProcess 是一个在 CreateProcessA 和 CreateProcessW 之间切换的宏,它们分别采用 ANSI 或 Unicode 中的字符串
/*
第一个参数:新进程的可执行文件名
第二个参数:传给新进程的命令行参数
第三个参数:缺省的进程安全性
第四个参数:缺省的线程安全性
第五个参数:不继承句柄
第六个参数:使用新的控制台
第七个参数:新的环境CREATE_NEW_CONSOLE,
第八个参数:当前目录
第九个参数:启动信息
第十个参数:返回进行的信息
*/
BOOL bCreateOK=::CreateProcess(
szFilename,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[])
{
//进程的克隆ID
int nClone(0);
//第一个第一个进程argc为1,以后每个进程命令行有两个参数,
//其中第二个是克隆ID。
if (argc>1)
{
//从第二个参数中提取克隆
::sscanf(argv[1],"%d",&nClone);
}
//显示进程的克隆ID
std::cout<<"Process ID:"<<::GetCurrentProcessId()<<",Clone ID:"<<nClone<<std::endl;
//创建新进程,其nClone为1-8
const int c_nCloneMax=8;
if (nClone==0)
{
//argc main函数中参数个数
for(int i=1;i<=c_nCloneMax;i++)
{
StartClone(i);
}
}
//等待输入一个字符,以便观察。输入一个字符后,程序结束。
std::cout<<"input a char:";
getchar();
std::cout<<"I'm stopped ... Bye-Bye";
::Sleep(1000);
return 0;
}
控制在一个窗口的检测
控制每一个进程一个窗口的检测
思考题
1.运行后在系统任务管理器中查看所创建进程映像名,有什么规律?
映像名一样,与创建顺序相匹配
2.父子进程使用同一程序,如何进行区别?
nClone克隆ID进行区分
3.不同进程的克隆ID是如何获取到的?
//传输:
:: sprintf(szCmdLine,"\"%s\"%d",szFilename, nCloneID);
//中间阶段:
在进程的main函数中取出, argv[1]
//获取:从第二个参数中提取克隆ID
:: sscanf(argv[1] , "%d" , &nClone);
4.如果在克隆ID为0的进程中创建克隆ID为1-8的进程,应该如何修改程序?
if (nClone==0)
{
for(int i=1;i<=c_nCloneMax;i++)
{
StartClone(i);
}
}
2、查看进程运行信息
(1) 在main()函数中获取当前进程ID,获取当前进程版本信息;
(2) 获取操作系统版本信息;
(3) 获取当前进程优先级;
(4) 如果操作系统版本>=Windows XP,并且当前进程优先级不是HIGH_PRIORITY_CLASS,将当前进程优先级改为HIGH_PRIORITY_CLASS;
(5) 显示改变后的进程优先级。
ViewPRI.cpp
// ViewPRI.cpp : Defines the entry point for the console application.
#include "stdafx.h"
#include <windows.h>
#include <iostream>
// 显示进程和操作系统信息的简单示例
int main(int argc, char* argv[])
{
// 获取这个进程的ID号
DWORD dwIdThis=:: GetCurrentProcessId();
// 获得和显示这一进程所需的版本,也可以用0表示当前进程
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_cast < LPOSVERSIONINFO > (&osvix)) ;
std :: cout << "Running on OS:" << osvix.dwMajorVersion <<"."
<< osvix.dwMinorVersion << std :: endl;
//显示当前进程的优先级。
//GetCurrentProcess()返回当前进程的句柄,事实上这个函数目前只是简单的返回-1这个值。
//但是为了兼容性考虑,我们最好不要直接使用-1,因为并不能保证以后这个值不会改变。
DWORD dwProcessP=::GetPriorityClass(GetCurrentProcess());
std::cout<<"Current process priority is :";
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<<"<unknow>";
break;
}
std::cout<<std::endl;
// 如果是NTS(Windows 2000) 系统,则提高其优先权
if (osvix.dwPlatformId==VER_PLATFORM_WIN32_NT && osvix.dwMajorVersion >= 5)
{
if(dwProcessP!=HIGH_PRIORITY_CLASS)
{
// 如果当前优先级不是high,则改变优先级为high
:: SetPriorityClass(
:: GetCurrentProcess() ,
HIGH_PRIORITY_CLASS);
//报告给用户
DWORD 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<<"<unknow>";
break;
}
std::cout<<std::endl;
}
}
return 0;
}
思考题
1.进程的优先级存放在哪里?PCB(进程控制块)中
//获取当前进程的优先级
GetPriorityClass(GetCurrentProcess())
3、终止进程
(1) 在程序一的基础上,当前进程每创建一个新进程,增加一个参数,用以表示当前进程的进程ID;
(2) 新进程运行时获取父进程的进程ID;
(3) 在指定的进程中获取父进程的句柄;
(4) 在指定的进程中终止父进程。
TerminateProcess
// TerminateProcess.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include <stdio.h>
//创建子进程,与父进程执行相同的程序。
void StartClone(int nCloneID)
{
// 获取当前进程的可执行文件名取当前进程的可执行文件名
TCHAR szFilename[MAX_PATH] ;
:: GetModuleFileName(
NULL, //一个模块的句柄。模块句柄跟一般的句柄不一样,
//模块句柄指向的就是EXE和DLL等在虚拟地址空间的位置。
//如果该参数为NULL,该函数返回该应用程序全路径。
szFilename,
MAX_PATH) ;
// 格式化用于创建新进程的命令行,包括EXE文件名、克隆ID和当前进程的ID。
TCHAR szCmdLine[MAX_PATH];
:: sprintf(szCmdLine,"\"%s\"%d %d",szFilename,nCloneID,::GetCurrentProcessId());
// 用于新进程的STARTUPINFO结构
STARTUPINFO si;
:: ZeroMemory(reinterpret_cast <void*> (&si) , sizeof(si) ) ;
si.cb = sizeof(si) ; // 必须是本结构的大小
// 返回的用于新进程的进程信息
PROCESS_INFORMATION pi;
// 使用同一可执行文件和带有克隆ID、当前进程ID的命令行创建新进程。
BOOL bCreateOK=::CreateProcess(
szFilename, // 新进程的可执行文件名
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[] )
{
// 进程的克隆ID
int nClone(0) ;
// 保存父进程的进程ID
int ProcessId(0);
//第一个进程argc为1,以后每个进程命令行有3个参数,
//其中第3个是父进程ID。
if (argc > 1)
{
// 从第2个参数中提取克隆ID
:: sscanf(argv[1] , "%d" , &nClone) ;
// 从第3个参数中提取父进程ID
:: sscanf(argv[2] , "%d" , &ProcessId) ;
}
// 显示进程ID和克隆ID
std :: cout << "Process ID:" << :: GetCurrentProcessId()
<< ", Clone ID:" << nClone
<< std :: endl;
//终止父进程
if(nClone==3)
{
::TerminateProcess(OpenProcess(PROCESS_TERMINATE,FALSE,ProcessId),1);
std::cout<<"Terminate Process Clone ID=2"
<< std :: endl;
}
// 创建新进程,其nClone为1-8
const int c_nCloneMax=8;
if (nClone < c_nCloneMax)
{
:: Sleep(1000) ;
StartClone(nClone+1) ;
}
// 等待输入一个字符,以便观察。输入一个字符后,程序结束。
std::cout<<"input a char:";
getchar();
std::cout<<"I'm stopped ... Bye-Bye";
:: Sleep(1000) ;
return 0;
}
思考题
1.一个进程终止其父进程所需的句柄是如何获取的?
先从第3个参数中提取父进程ID
::sscanf(argv[2],”%d”,&processId)
然后通过父进程ID调用OpenProcess获取句柄,
OpenProcess(PROCESS_TERMINATE,FALSE,ProcessId)
2.Windows下的进程ID和进程句柄有什么区别?
进程id是进程唯一标识符,通用、公开,因此,可以在线程\进程之间共享。
进程句柄实际是一个指针的指针,指向一地包含具体信息数据的内存,每次打开这个程序(OpenProcess)返回的句柄是变化的。
一个进程在不同的调用时间中可能句柄值是不同的,但是ID只能有一个,且进程句柄只在当前进程内有效
3.试通过修改程序来说明对一个进程OpenProcess()多次,返回的句柄值是否相同?
不同,
因为当前进程结束的是上一进程,
句柄是指指针数组的索引号,打开多次访问的是同一个文件,
不同的句柄显示不同的值,但本质上是相同的。
通过在进程nClone为了进程中对父进程调用三次OpenProcess
for(int i=0;i<3;i++)
{
HANDLE hProcess= OpenProcess(PROCESS_TERMINATE,FALSE,processId);
std::cout<<"TEST:"<<hProcess<<std::endl;
}
所打印内容为:TEST:00000088
TEST:000000D0
TEST:000000D4
所以得到结论:对一个进程OpenProcess多次返回的句柄不同。
句柄(Handle) (指针的指针)
是一个是用来标识对象或者项目的标识符,可以用来描述窗体、文件等,值得注意的是句柄不能是常量 。
Windows之所以要设立句柄,根本上源于内存管理机制的问题,即虚拟地址。
简而言之数据的地址需要变动,变动以后就需要有人来记录、管理变动,因此系统用 句柄来记载数据地址的变更。
在程序设计中,句柄是一种特殊的智能指针,当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄