在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 下进程池的操作的更多相关文章

  1. windows下进程与线程

    windows下进程与线程 Windows是一个单用户多任务的操作系统,同一时间可有多个进程在执行.进程是应用程序的运行实例,可以理解为应用程序的一次动态执行:而线程是CPU调度的单位,是进程的一个执 ...

  2. windows 下进程与线程的遍历

    原文:http://www.cnblogs.com/Apersia/p/6579376.html 在Windows下进程与线程的遍历有好几种方法. 进程与线程的遍历可以使用<TlHelp.h&g ...

  3. Windows下python3登陆和操作linux服务器

    一.环境准备 python3远程连接需要用到pycrytodome和paramiko库,其中后者依赖前者,所以按照顺序来安装 1. 安装pycrytodome 1 pip install pycryt ...

  4. windows 下文件的高级操作

    本文主要说明在Windows下操作文件的高级方法,比如直接读写磁盘,文件的异步操作,而文件普通的读写方式在网上可以找到一大堆资料,在这也就不再进行专门的说明. 判断文件是否存在 在Windows中并没 ...

  5. windows下cmd记录MYSQL操作

    我们在cmd下操作MYSQL,当需要复制某条命令的时候,需要右键标记,然后选取,然后......各种不方便! 有没有比较方便的方式,可以将我们的操作记录自动的实时保存下来,当我们需要操作的时候,可以高 ...

  6. windows下cmd中命令操作

    windows下cmd中命令:   cls清空 上下箭头进行命令历史命令切换 ------------------------------------------------------------- ...

  7. 【windows下进程searchfilterhost.exe分析】

    searchfilterhost.exe [进程信息] 进程文件: searchfilterhost.exe 进程名称: n/a 英文描述: searchfilterhost.exe is a pro ...

  8. windows下进程与线程剖析

    进程与线程的解析 进程:一个正在运行的程序的实例,由两部分组成: 1.一个内核对象,操作系统用它来管理进程.内核对象也是系统保存进程统计信息的地方. 2.一个地址空间,其中包含所有可执行文件或DLL模 ...

  9. windows下 安装 rabbitMQ 及操作常用命令

    rabbitMQ是一个在AMQP协议标准基础上完整的,可服用的企业消息系统.它遵循Mozilla Public License开源协议,采用 Erlang 实现的工业级的消息队列(MQ)服务器,Rab ...

随机推荐

  1. Jenkins+tomcat+jdk setup

    Jenkins download: http://jenkins-ci.org/ jdk version:jdk-7u45-linux-x64.tar.gz tomcat version:apache ...

  2. Qt---自定义界面之 Style Sheet

    这次讲Qt Style Sheet(QSS),QSS是一种与CSS类似的语言,实际上这两者几乎完全一样.既然谈到CSS我们就有必要说一下盒模型. 1. 盒模型(The Box Model) 在样式中, ...

  3. itextpdf添加非自带字体(例如微软雅黑)

    找到需要的字体,例如 在windows系统中找到需要字体,本例使用微软雅黑,使用C:\\Windows\\Fonts\\msyh.ttf. 代码如下: /** * 创建pdf,使用微软雅黑字体 * * ...

  4. Selenium Python 安装指导

    最近无聊.又重新装了个selenium 果然时代变了.安装的时候的方法和以前不太一样了.因此觉得有必要单列出来加以说明 另外备注:测试小伙伴们.安装此类工具报错.尝试以下两个方案之一: 1.请转sta ...

  5. Mixed Reality-宁波市VR/AR技术应用高研班总结

    年,全球AR与VR市场规模将达到1500亿美元,而根据市场研究机构BI Intelligence的统计,2020年仅头戴式VR硬件市场规模将达到28亿美元,未来5年复合增长率超过100%.本次培训从V ...

  6. 《程序员修炼之道:从小工到专家》【PDF】下载

    <程序员修炼之道:从小工到专家>[PDF]下载链接: https://u253469.ctfile.com/fs/253469-231196340 内容简介 <程序员修炼之道> ...

  7. LeetCode #1 TwoSum

    Description Given an array of integers, return indices of the two numbers such that they add up to a ...

  8. Mac和Xcode常用的快捷键

    Mac电脑一般都不怎么用鼠标,因此除了触摸屏的各种双指.三指甚至四指的操作之外,快捷键的使用可以带来非常大的便利,本文则主要收集整理了自己在Mac常规和Xcode开发过程中常用的一些快捷键. 一.Ma ...

  9. iOS Swift3.0 OC 数据储存--归档

    一.Swift 3.0 1.model class userModel: NSObject,NSCoding { var account: String = "" var regm ...

  10. python 将文件夹内的图片转换成PDF

    import os import stringfrom PIL import Imagefrom reportlab.lib.pagesizes import A4, landscapefrom re ...