windows 下进程池的操作
在Windows上创建进程是一件很容易的事,但是在管理上就不那么方便了,主要体现在下面几个方面:
1. 各个进程的地址空间是独立的,想要在进程间共享资源比较麻烦
2. 进程间可能相互依赖,在进程间需要进行同步时比较麻烦
3. 在服务器上可能会出现一个进程创建一大堆进程来共同为客户服务,这组进程在逻辑上应该属于同一组进程
为了方便的管理同组的进程,Windows上提供了一个进程池来管理这样一组进程,在VC中将这个进程池叫做作业对象。它主要用来限制池中内存的一些属性,比如占用内存数,占用CPU周期,进程间的优先级,同时提供了一个同时关闭池中所有进程的方法。下面来说明它的主要用法
作业对象的创建
调用函数CreateJobObject,可以来创建作业对象,该函数有两个参数,第一个参数是一个安全属性,第二个参数是一个对象名称。作业对象本身也是一个内核对象,所以它的使用与常规的内核对象相同,比如可以通过命名实现跨进程访问,可以通过对应的Open函数打开命名作业对象。
添加进程到作业对象
可以通过AssignProcessToJobObject ,该函数只有两个参数,第一个是对应的作业对象,第二个是对应的进程句柄
关闭作业对象中的进程
可以使用TerminateJobObject 函数来一次关闭作业对象中的所有进程,它相当于对作业对象中的每一个进程调用TerminateProcess,相对来说是一个比较粗暴的方式,在实际中应该劲量避免使用,应该自己设计一种更好的退出方式
控制作业对象中进程的相关属性
可以使用SetInformationJobObject函数设置作业对象中进程的相关属性,函数原型如下:
BOOL WINAPI SetInformationJobObject(
__in HANDLE hJob,
__in JOBOBJECTINFOCLASS JobObjectInfoClass,
__in LPVOID lpJobObjectInfo,
__in DWORD cbJobObjectInfoLength
);
第一个参数是一个作业对象的句柄,第二个是一系列的枚举值,用来限制其中进程的各种信息。第三个参数根据第二参数的不同,需要传入对应的结构体,第四个参数是对应结构体的长度。下面是各个枚举值以及它对应的结构体
枚举值 | 含义 | 对应的结构体 |
---|---|---|
JobObjectAssociateCompletionPortInformation | 设置各种作业对象事件的完成端口 | JOBOBJECT_ASSOCIATE_COMPLETION_PORT |
JobObjectBasicLimitInformation | 设置作业对象的基本信息(如:进程作业集大小,进程亲缘性,进程CPU时间限制值,同时活动的进程数量等) | JOBOBJECT_BASIC_LIMIT_INFORMATION |
JobObjectBasicUIRestrictions | 对作业中的进程UI进行基本限制(如:指定桌面,限制调用ExitWindows函数,限制剪切板读写操作等)一般在服务程序上这个很少使用 | JOBOBJECT_BASIC_UI_RESTRICTIONS |
JobObjectEndOfJobTimeInformation | 指定当作业时间限制到达时,系统采取什么动作(如:通知与作业对象绑定的完成端口一个超时事件等) | JOBOBJECT_END_OF_JOB_TIME_INFORMATION |
JobObjectExtendedLimitInformation | 作业进程的扩展限制信息(限制进程的内存使用量等) | JOBOBJECT_EXTENDED_LIMIT_INFORMATION |
JobObjectSecurityLimitInformation | 限制作业对象进程中的安全属性(如:关闭一些组的特权,关闭某些特权等)要求作业对象所属进程或线程要具备更改这些作业进程安全属性的权限 | JOBOBJECT_SECURITY_LIMIT_INFORMATION |
限制进程异常退出的行为
在Windows中,如果进程发生异常,那么它会寻找处理该异常的对应的异常处理模块,如果没有找到的话,它会弹出一个对话框,让用户选择,但是这样对服务程序来说很不友好,而且有的服务器是在远程没办法操作这个对话框,这个时候需要使用某种方法让其不弹出这个对话框。
在作业对象中的进程,我们可以使用SetInformationJobObject函数中的JobObjectExtendedLimitInformation枚举值,将结构体JOBOBJECT_EXTENDED_LIMIT_INFORMATION中的BasicLimitInformation.LimitFlags成员设置为JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION。这相当于强制每个进程调用SetErrorMode并指定SEM_NOGPFAULTERRORBOX标志
获取作业对象属性和统计信息
调用QueryInformationJobObject函数来获取作业对象属性和统计信息。该函数的使用方法与之前的SetInformationJobObject函数相同。
下面列举下它可选择枚举值:
枚举值 | 含义 | 对应的结构体 |
---|---|---|
JobObjectBasicAccountingInformation | 基本统计信息 | JOBOBJECT_BASIC_ACCOUNTING_INFORMATION |
JobObjectBasicAndIoAccountingInformation | 基本统计信息和IO统计信息 | JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION |
JobObjectBasicLimitInformation | 基本的限制信息 | JOBOBJECT_BASIC_LIMIT_INFORMATION |
JobObjectBasicProcessIdList | 获取作业进程ID列表 | JOBOBJECT_BASIC_PROCESS_ID_LIST |
JobObjectBasicUIRestrictions | 查询进程UI的限制信息 | JOBOBJECT_BASIC_UI_RESTRICTIONS |
JobObjectExtendedLimitInformation | 查询作业进程的扩展限制信息 | JOBOBJECT_EXTENDED_LIMIT_INFORMATION |
JobObjectSecurityLimitInformation | 查询作业对象进程中的安全属性 | JOBOBJECT_SECURITY_LIMIT_INFORMATION |
这些信息基本上与上面的设置限制信息是对应的。使用上也是类似的
作业对象与完成端口
设置作业对象的完成端口一般是使用SetInformationJobObject,并将第二个参数的枚举值指定为JobObjectAssociateCompletionPortInformation,这样就可以完成一个作业对象和完成端口的绑定。
当作业对象发生某些事件的时候可以向完成端口发送对应的事件,这个时候在完成端口的线程中调用GetQueuedCompletionStatus可以获取对应的事件,但是这个函数的使用与之前在文件操作中的使用略有不同,主要体现在它的各个返回参数的含义上。各个参数函数如下:
lpNumberOfBytes:返回一个事件的ID,它的事件如下:
事件 | 事件含义 |
---|---|
JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS | 进程异常退出 |
JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT | 同时活动的进程数达到设置的上限 |
JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO | 作业对象中没有活动的进程了 |
JOB_OBJECT_MSG_END_OF_JOB_TIME | 作业对象的CPU周期耗尽 |
JOB_OBJECT_MSG_END_OF_PROCESS_TIME | 进程的CPU周期耗尽 |
JOB_OBJECT_MSG_EXIT_PROCESS | 进程正常退出 |
JOB_OBJECT_MSG_JOB_MEMORY_LIMIT | 作业对象消耗内存达到上限 |
JOB_OBJECT_MSG_NEW_PROCESS | 有新进程加入到作业对象中 |
JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT | 进程消耗内存数达到上限 |
lpCompletionKey: 返回触发这个事件的对象的句柄,我们将完成端口与作业对象绑定后,这个值自然是对应作业对象的句柄
lpOverlapped: 指定各个事件对应的详细信息,在于进程相关的事件中,它返回一个进程ID
既然知道了各个参数的含义,我们可以使用PostQueuedCompletionStatus函数在对应的位置填充相关的值,然后往完成端口上发送自定义事件。只需要将lpNumberOfBytes设置为我们自己的事件ID,然后在线程中处理即可
下面是作业对象操作的完整例子
#include "stdafx.h"
#include <Windows.h>
DWORD IOCPThread(PVOID lpParam); //完成端口线程
int GetAppPath(LPTSTR pAppName, size_t nBufferSize)
{
TCHAR szAppName[MAX_PATH] = _T("");
DWORD dwLen = ::GetModuleFileName(NULL, szAppName, MAX_PATH);
if(dwLen == 0)
{
return 0;
}
for(int i = dwLen; i > 0; i--)
{
if(szAppName[i] == _T('\\'))
{
szAppName[i + 1] = _T('\0');
break;
}
}
_tcscpy_s(pAppName, nBufferSize, szAppName);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
//获取当前进程的路径
TCHAR szModulePath[MAX_PATH] = _T("");
GetAppPath(szModulePath, MAX_PATH);
//创建作业对象
HANDLE hJob = CreateJobObject(NULL, NULL);
if(hJob == INVALID_HANDLE_VALUE)
{
return 0;
}
//创建完成端口
HANDLE hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 1);
if(hIocp == INVALID_HANDLE_VALUE)
{
return 0;
}
//启动监视进程
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)IOCPThread, (PVOID)hIocp, 0, NULL);
//将作业对象与完成端口绑定
JOBOBJECT_ASSOCIATE_COMPLETION_PORT jacp = {0};
jacp.CompletionKey = hJob;
jacp.CompletionPort = hIocp;
SetInformationJobObject(hJob, JobObjectAssociateCompletionPortInformation, &jacp, sizeof(jacp));
//为作业对象设置限制条件
JOBOBJECT_BASIC_LIMIT_INFORMATION jbli = {0};
jbli.PerProcessUserTimeLimit.QuadPart = 20 * 1000 * 10i64; //限制执行的用户时间为20ms
jbli.MinimumWorkingSetSize = 4 * 1024;
jbli.MaximumWorkingSetSize = 256 * 1024; //限制最大内存为256k
jbli.LimitFlags = JOB_OBJECT_LIMIT_PROCESS_TIME | JOB_OBJECT_LIMIT_JOB_MEMORY;
SetInformationJobObject(hJob, JobObjectBasicLimitInformation, &jbli, sizeof(jbli));
//指定不显示异常对话框
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {0};
jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION;
SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli));
//创建新进程
_tcscat_s(szModulePath, MAX_PATH, _T("JobProcess.exe"));
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
CreateProcess(szModulePath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB, NULL, NULL, &si, &pi);
//将进程加入到作业对象中
AssignProcessToJobObject(hJob, pi.hProcess);
//运行进程
ResumeThread(pi.hThread);
//查询作业对象的运行情况,在这查询基本统计信息和IO信息
JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION jbaai = {0};
DWORD dwRetLen = 0;
QueryInformationJobObject(hJob, JobObjectBasicAndIoAccountingInformation, &jbaai, sizeof(jbaai), &dwRetLen);
//等待进程退出
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
//给完成端口线程发送退出命令
PostQueuedCompletionStatus(hIocp, 0, (ULONG_PTR)hJob, NULL);
//等待线程退出
WaitForSingleObject(hIocp, INFINITE);
CloseHandle(hIocp);
CloseHandle(hJob);
return 0;
}
DWORD IOCPThread(PVOID lpParam)
{
BOOL bLoop = TRUE;
HANDLE hIocp = (HANDLE)lpParam;
DWORD dwReasonId = 0;
HANDLE hJob = NULL;
OVERLAPPED *lpOverlapped = {0};
while (bLoop)
{
BOOL bSuccess = GetQueuedCompletionStatus(hIocp, &dwReasonId, (PULONG_PTR)&hJob, &lpOverlapped, INFINITE);
if(!bSuccess)
{
return 0;
}
switch (dwReasonId)
{
case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS:
{
//进程异常退出
DWORD dwProcessId = (DWORD)lpOverlapped;
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwProcessId);
if(INVALID_HANDLE_VALUE != hProcess)
{
DWORD dwExit = 0;
GetExitCodeProcess(hProcess, &dwExit);
printf("进程[%08x]异常退出,退出码为[%04x]\n", dwProcessId, dwExit);
}
}
break;
case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT:
{
printf("同时活动的进程数达到上限\n");
}
break;
case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO:
{
printf("没有活动的进程了\n");
}
break;
case JOB_OBJECT_MSG_END_OF_JOB_TIME:
{
printf("作业对象CPU时间周期耗尽\n");
}
break;
case JOB_OBJECT_MSG_END_OF_PROCESS_TIME:
{
DWORD dwProcessID = (DWORD)lpOverlapped;
printf("进程[%04x]CPU时间周期耗尽\n", dwProcessID);
}
break;
case JOB_OBJECT_MSG_EXIT_PROCESS:
{
DWORD dwProcessId = (DWORD)lpOverlapped;
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwProcessId);
if(INVALID_HANDLE_VALUE != hProcess)
{
DWORD dwExit = 0;
GetExitCodeProcess(hProcess, &dwExit);
printf("进程[%08x]正常退出,退出码为[%04x]\n", dwProcessId, dwExit);
}
}
break;
case JOB_OBJECT_MSG_JOB_MEMORY_LIMIT:
{
printf("作业对象消耗内存数量达到上限\n");
}
break;
case JOB_OBJECT_MSG_NEW_PROCESS:
{
DWORD dwProcessID = (DWORD)lpOverlapped;
printf("进程[ID:%u]加入作业对象[h:0x%08X]\n",dwProcessID,hJob);
}
break;
case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT:
{
DWORD dwProcessID = (DWORD)lpOverlapped;
printf("进程[%04x]消耗内存数量达到上限\n",dwProcessID);
}
break;
default:
bLoop = FALSE;
break;
}
}
}
在上面的例子中需要注意一点,在创建进程的时候我们给这个进程一个CREATE_BREAKAWAY_FROM_JOB标志,由于Windows在创建进程时,默认会将这个子进程丢到父进程所在进程池中,如果父进程属于某一个进程池,那么我们再将子进程放到其他进程池中,自然会导致失败,这个标志表示,新创建的子进程不属于任何一个进程池,这样在后面的操作才会成功
windows 下进程池的操作的更多相关文章
- windows下进程与线程
windows下进程与线程 Windows是一个单用户多任务的操作系统,同一时间可有多个进程在执行.进程是应用程序的运行实例,可以理解为应用程序的一次动态执行:而线程是CPU调度的单位,是进程的一个执 ...
- windows 下进程与线程的遍历
原文:http://www.cnblogs.com/Apersia/p/6579376.html 在Windows下进程与线程的遍历有好几种方法. 进程与线程的遍历可以使用<TlHelp.h&g ...
- Windows下python3登陆和操作linux服务器
一.环境准备 python3远程连接需要用到pycrytodome和paramiko库,其中后者依赖前者,所以按照顺序来安装 1. 安装pycrytodome 1 pip install pycryt ...
- windows 下文件的高级操作
本文主要说明在Windows下操作文件的高级方法,比如直接读写磁盘,文件的异步操作,而文件普通的读写方式在网上可以找到一大堆资料,在这也就不再进行专门的说明. 判断文件是否存在 在Windows中并没 ...
- windows下cmd记录MYSQL操作
我们在cmd下操作MYSQL,当需要复制某条命令的时候,需要右键标记,然后选取,然后......各种不方便! 有没有比较方便的方式,可以将我们的操作记录自动的实时保存下来,当我们需要操作的时候,可以高 ...
- windows下cmd中命令操作
windows下cmd中命令: cls清空 上下箭头进行命令历史命令切换 ------------------------------------------------------------- ...
- 【windows下进程searchfilterhost.exe分析】
searchfilterhost.exe [进程信息] 进程文件: searchfilterhost.exe 进程名称: n/a 英文描述: searchfilterhost.exe is a pro ...
- windows下进程与线程剖析
进程与线程的解析 进程:一个正在运行的程序的实例,由两部分组成: 1.一个内核对象,操作系统用它来管理进程.内核对象也是系统保存进程统计信息的地方. 2.一个地址空间,其中包含所有可执行文件或DLL模 ...
- windows下 安装 rabbitMQ 及操作常用命令
rabbitMQ是一个在AMQP协议标准基础上完整的,可服用的企业消息系统.它遵循Mozilla Public License开源协议,采用 Erlang 实现的工业级的消息队列(MQ)服务器,Rab ...
随机推荐
- immutable日常操作之深入API
写在前面 本文只是个人在熟悉Immutable.js的一些个人笔记,因此我只根据我自己的情况来熟悉API,所以很多API并没有被列举到,比如常规的push/map/filter/reduce等等操作, ...
- java正则表达式总结
最近工作要使用文件上传解析,上传还好,但是在解析文件的时候,却踩到了好多坑,今天就说说其中的一块吧,正则匹配. 由于上传的文件统一都是csv文件,所以在解析文本的时候,肯定要碰到正则表达式的,先解释一 ...
- Spring Boot实战之逐行释义HelloWorld
一.前言 研究Spring boot也有一小段时间了,最近会将研究东西整理一下给大家分享,大概会有10~20篇左右的博客,整个系列会以一个简单的博客系统作为基础,因为光讲理论很多东西不是特别容易理解 ...
- 自学Zabbix3.5.5-监控项item-User parameters(自定义key)
为什么要自定义KEY,即User parameters功能 有时候我们想让被监控端执行一个zabbix没有预定义的检测,zabbix的用户自定义参数功能提供了这个方法.我们可以在客户端配置文件zabb ...
- django2.0+linux服务器 ,如何让自己电脑访问
这几天一直在搞这个服务器端口开放问题,来让自己电脑可以访问服务器下的django网页,今天终于弄好了~~~~~离成功又进了一步~~~~~ 1.首先,我们来开放一个linux服务器的端口(我开放了828 ...
- Intellij IDEA中使用Protobuf的正确姿势
一..proto文件语法高亮显示 需要安装Protobuf Support插件 依次点击Intellij中的"File"-->"Settings"--&g ...
- Ubuntu系统怎么切换多用户命令界面
ctrl+alt+F2~F6 切换窗口 返回桌面 Ctrl+Alt+F7
- 关于Sublime Text编辑器的实用技巧
本文转载至一篇博文,为您提供Sublime Text编辑器的12个技巧和诀窍,深入挖掘这个看似简洁的代码编辑器,背后所隐藏的实现各种高级功能的无限可能. 1) 选择 以下是一些Sublime Text ...
- iOS UITabView简写瀑布流
代码demo 一.tabViewCell,通过image的比例高算出cell 的高度 #import "TableViewCell.h" @implementation Table ...
- iOS 轮播中遇到的问题(暂停、重新启动)
一. 轮播的优化或者用Collection来实现 二.Timer 问题 我们可以这样来使用一个Timer [NSTimer scheduledTimerWithTimeInterval:1.0 ta ...