4 进程
进程通常被定义为一个正在运行的实例,由两部分组成:系统用来管理这个进程的内核对象、地址空间(包括所有可执行模块或DLL模块的数据和代码)。
应用程序进入点
32位系统中,HINSTANCE和HMODULE是完全相同的对象,它表示可执行文件或DLL文件加载到进程空间时所用的基地址。可以用下面的函数得到一个模块的句柄
/基地址:
HMODULE GetModuleHandle( PCTSTR pszModule );
如果传入NULL,返回调用的可执行文件的基地址(即使是通过包含在DLL中的代码调用,返回的不是DLL文件的基地址)。另外,它只查看调用进程的地址空间。
进程的命令行
新进程创建时,要传递一个命令行,它几乎永远不会是空的,因为可执行文件的名字是命令行上的第一个标记。如果要修改命令行字符串,最好先将命令行拷贝到本地缓存中(即拷贝出新的使用)。
还可以使用GetCommandLine获取一个指向进程的完整命令行的指针。用CommandLineToArgvW将Unicode字符串分割成它的各种标记(其实就是得到argc,argv)。
进程的环境变量
环境块是进程的空间中分配的一个内存块,每个环境块都包含一组字符串:
VarName1=VarValue1\0
VarName2=VarValue1\0
VarName3=VarValue1\0
.
.
\0
环境块中的字符串必须按环境变量名的字母顺序排序。
进程启动时,要查看环境块,就可以找出某些变量的值,供程序使用,改变程序属性或行为。
有关环境变量的几个函数:GetEnvironmentVariable, ExpandEnviromentString, SetEnviromentVariable
如果创建一个子进程之后,不关心它的执行情况或希望放弃与子进程之间的联系,可以通过调用CloseHandle来关闭子进程的句柄和它的主线程的句柄。
五、作业
如果需要把一组进程当作单个实体来处理,可以使用作业。作业内核对象,能够将进程组合在一起,并且创建一个“沙框”,以便限制进程能够进行的操作。因此可以将作业视为进程的一个容器。
首先,可以创建一个作业,CreateJobObject( PSECURITY_ATTRIBUTES psa, PCTSTR pszName );
通过作业名字可以在另一个进程中访问该作业,OpenJobObject( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName );
在进程中关闭作业的句柄,也只是使进程无法访问该作业,作业仍然存在,只有作业中的所有进程全部终止运行之后,作业内核对象才会被自动撤销。
对作业中的进程可以作的限制:
1)基本限制和扩展基本限制,用于防止作业中的进程垄断系统的资源;
2)基本的UI限制,用于防止作业中的进程改变用户界面;
3)安全性限制,用于防止作业中的进程访问保密资源(文件、注册表关键字等);
BOOL WINAPI SetInformationJobObject( __in HANDLE hJob, __in JOBOBJECTINFOCLASS JobObjectInfoClass, __in LPVOID lpJobObjectInfo, __in DWORD cbJobObjectInfoLength );
第一个参数指定要限制的作业。第二个参数是一个枚举类型,指定了要施加的限制的类型。第三个参数是一个数据类型的地址,该数据类型包含着具体的限制设置。第四个参数指出此数据结构的大小(用于版本控制)。
可以使用下面的函数来查询这些限制:
BOOL QueryInformationJobObject(
__in_opt HANDLE hJob,
__in JOBOBJECTINFOCLASS JobObjectInformationClass,
__out_bcount_part(cbJobObjectInformationLength, *lpReturnLength) LPVOID lpJobObjectInformation,
__in DWORD cbJobObjectInformationLength,
__out_opt LPDWORD lpReturnLength
);
这个函数还可以用来获得作业的基本的统计信息,此时向第2个参数传递JobObjectBasicAccountingInformation和一个JOBOBJECT_BASIC_ACCOUNTING_INFORMATION结构的地址。
还可以传入JobObjectBasicAndIoAccountingInformation来查询基本统计信息和I/O统计信息。
如果要将一个进程显式的放入作业中,使用下面的函数:
BOOL AssignProcessToJobObject(
__in HANDLE hJob,
__in HANDLE hProcess
);
注意:一个进程一旦属于一个作业的一部分,就不能再移动到另一个作业中,也不能成为让它从原有的作业中脱离成为“无业的”。
作业中的一个进程生成了另一个进程的时候,新进程将自动成为父进程所属于的作业的一部分。如果不希望这样,可以通过以下方式改变这种行为:
1)打开基本限制中的JOB_OBJECT_LIMIT_BREAKAWAY_OK标志,告诉系统新生成的进程可以在作业外部执行。同时,在CreateProcess时指定CREATE_BREAKAWAY_FROM_JOB标志。
2)打开基本限制中的JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_ON标志,此时不需要在CreateProcess时指定任何标志,新进程都会脱离当前作业。
要“杀死”作业内的所有进程,只需调用以下代码:
BOOL TerminateJobObject( HANDLE hJob, UINT uExitCode );
如果想获得作业的一些通知(比如进程创建或终止运行),应该创建一个I/O完成端口内核对象,并将作业对象与完成端口关联。然后,必须有一个或多个线程等待作业通知到达完成端口,以便对它们进行处理。
1)创建完成端口,句柄为hIOCP;
2) 将完成端口和作业关联,关联以后,系统监视作业,只要有事件发生,就会将它们投递(Post)到I/O完成端口。
JOBOBJECT_ASSOCIATE_COMPLETE_PORT joacp;
joacp.CompletionKey = 1;
joacp.CompletionPort = hIOCP;
SetInformationJobObject( hJob, JobObjectAssociateCompletionPortInformation, &joacp, sizeof( jaocp ) );
3)线程通过调用GetQueuedCompletionStatus来监视完成端口。
BOOL
WINAPI
GetQueuedCompletionStatus(
__in HANDLE CompletionPort,
__out LPDWORD lpNumberOfBytesTransferred,
__out PULONG_PTR lpCompletionKey,
__out LPOVERLAPPED *lpOverlapped,
__in DWORD dwMilliseconds
);
lpNumberOfBytesTransferred的值指出了具体发生了什么事件(查询系统定义的枚举表)。