Windows进程管理
Windows 所创建的每个进程都从调用CreateProcess()API函数开始,
其函数的任务是在对象管理器子系统内初始化进程对象.
每一个进程都以调用ExitProcess()或TerminateProcess() API函数终止.
通常应用程序的框架负责调用ExitProcess()函数.
对C++运行库,这一调用发生在应用程序的main()函数返回之后.
Windows
CreateProcess()
ExitProcess() / TerminateProcess()
C++
main()
1.创建进程
CreateProcess()调用的核心参数是可执行文件运行时的文件名及其命令行.
参数名称和使用目的
LPCSTR lpApplivationName
全部或部分地指明包含可执行代码的EXE文件的文件名
LPCTSTR lpCommandLine
向可执行文件发送的参数
LPSECURIITY_ATTRIBUTES lpProcessAttributes
返回进程句柄的安全属性.主要指明这一句柄是否应该由其他子进程所继承
LPSECURIITY_ATTRIBUTES lpThreadAttributes
返回进程的主线程的句柄的安全属性
BOOL bInheritHandle
一种标志,告诉系统允许新进程继承者进程的句柄
DWORD dwCreationFlage
特殊的创建标志(如CREATE_SUSPENDED) 的位标记
LPVOID lpEnvironment
向新进程发送的一套环境变量;如为null值则发送调用者环境
LPCTSTR lpCurrentDirectory
新进程的启动目录
STARTUPINFO lpStartupInfo
STARTUPINFO结构,包括新进程的输入和输出配置的详情.
可以指定第一个参数,即应用程序的名称,其中包括相对于当前进程的当前目录的全路径或者利用搜索方法找到的路径;
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.进程同步
Windows 2000/XP提供的常用对象可分成三类:核心应用服务,线程同步和线程间通讯.其中,开发人员可以使用线程同步对象来协调线程和进程的工作,以使其共享信息并执行任务.此类对象包括互锁数据,临界段,事件,互斥体和信号等.
多线程编程中关键的一步是保护所有的共享资源,工具主要有互锁函数,临界段和互斥体等;另一个实质性部分是协调线程使其完成应用程序的任务,为此,可利用内核中的事件对象和信号.
在进程内或进程间实现线程同步的最方便的方法是使用事件对象,这一组内核对象允许一个线程对其受信状态进行直接控制(表2-1).
而互斥体则是另一个可命名且安全的内核对象,其主要目的是引导对共享资源的访问.拥有单一访问资源的线程创建互斥体,所有想要访问该资源的线程应该在实际执行操作之前获得互斥体,而在访问结束时立即释放互斥体,以允许下一个等待线程获得互斥体,然后接着进行下去.
与事件对象类似,互斥体容易创建,打开,使用并清除.利用CreateMutex()API可创建互斥体,创建时还可以指定一个初始的拥有权标志,通过使用这个标志,只有当线程完成了资源的所有的初始化工作时,才允许创建线释放互斥体.
表1-2 用于管理事件对象的API
API名称以及描述
CreateEvent()
在内核中创建一个新的事件对象.此函数允许有安全性设置,手工还是自动重置的标志以及初始时已接受还是未接受信号状态的标志
OpenEvent()
创建对已经存在的事件对象的引用.此API函数需名称,继承标志和所需的访问级别
SetEvent()
将手工重置事件转化为已接受信号状态
ResetEvent()
将手工重置事件转化为非接受信号状态
PulseEvent()
将自动重置事件对象转化为已接受信号状态.
当系统释放所有的等待它的线程时此种转化立即发生.
为了获得互斥体,首先,想要访问调用的线程可使用IOpenMutex() API函数来获得指向对象的句柄;
然后,线程这个句柄提供给一个等待函数.当内核将互斥体对象发送给等待线程时,就表明该线程获得了互斥体的拥有权.
当线程获得拥有权时,线程控制了对共享资源的访问----必须设法尽快地放弃互斥体.放弃共享资源时需要在该对象上调用ReleaseMute() API.hu(由到达时间决定顺序).
清单2-1 创建子进程
// proccreate项目
#include <windows.h>
#include
#include <stdio.h>
// 创建传递过来的进程的克隆过程并赋于其ID值
void StartClone(int nCloneID)
{
// 提取用于当前可执行文件的文件名
TCHAR szFilename[MAX_PATH] ;
GetModuleFileName(NULL, szFilename, MAX_PATH) ;
// 格式化用于子进程的命令行并通知其EXE文件名和克隆ID
TCHAR szCmdLine[MAX_PATH];
sprintf(szCmdLine,"\"%s\" %d",szFilename,nCloneID);
// 用于子进程的STARTUPINFO结构
STARTUPINFO si;
ZeroMemory(&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;
//修改语句:int nClone;
//第一次修改:nClone=0;
if (argc > 1)
{
// 从第二个参数中提取克隆ID
:: sscanf(argv[1] , “%d” , &nClone) ;
}
//第二次修改:nClone=0;
// 显示进程位置
std :: cout << "Process ID:" << :: GetCurrentProcessId()
<< ", Clone ID:" << nClone
<< std :: endl;
// 检查是否有创建子进程的需要
const int c_nCloneMax=5;
if (nClone < c_nCloneMax)
{
// 发送新进程的命令行和克隆号
StartClone(++nClone) ;
}
// 等待响应键盘输入结束进程
getchar();
return 0;
}
清单2-2 父子进程的简单通信及终止进程的示例程序
// procterm项目
include <windows.h>
include
include <stdio.h>
static LPCTSTR g_szMutexName = “w2kdg.ProcTerm.mutex.Suicide” ;
// 创建当前进程的克隆进程的简单方法
void StartClone()
{
// 提取当前可执行文件的文件名
TCHAR szFilename[MAX_PATH] ;
GetModuleFileName(NULL, szFilename, MAX_PATH) ;
// 格式化用于子进程的命令行,字符串“child”将作为形参传递给子进程的main函数
TCHAR szCmdLine[MAX_PATH] ;
//实验2-2步骤3:将下句中的字符串child改为别的字符串,重新编译执行,执行前请先保存已经完成的工作
sprintf(szCmdLine, “”%s"child" , szFilename) ;
// 子进程的启动信息结构
STARTUPINFO si;
ZeroMemory(&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.” << std :: endl;
StartClone() ;
// 指令子进程“杀”掉自身
std :: cout << "Telling the child process to quit. "<< std :: endl;
//等待父进程的键盘响应
getchar() ;
//释放互斥体的所有权,这个信号会发送给子进程的WaitForSingleObject过程
ReleaseMutex(hMutexSuicide) ;
// 消除句柄
CloseHandle(hMutexSuicide) ;
}
}
void Child()
{
// 打开“自杀”互斥体
HANDLE hMutexSuicide = OpenMutex(
SYNCHRONIZE, // 打开用于同步
FALSE, // 不需要向下传递
g_szMutexName) ; // 名称
if (hMutexSuicide != NULL)
{
// 报告我们正在等待指令
std :: cout <<"Child waiting for suicide instructions. " << std :: endl;
//子进程进入阻塞状态,等待父进程通过互斥体发来的信号
WaitForSingleObject(hMutexSuicide, INFINITE) ;
//实验2-2步骤5:将上句改为WaitForSingleObject(hMutexSuicide, 0) ,重新编译执行
// 准备好终止,清除句柄
std :: cout << "Child quiting." << std :: endl;
CloseHandle(hMutexSuicide) ;
}
}
int main(int argc, char* argv[] )
{
// 决定其行为是父进程还是子进程
if (argc>1 && :: strcmp(argv[1] , “child” )== 0)
{
Child() ;
}
else
{
Parent() ;
}
return 0;
}