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在创建进程时,默认会将这个子进程丢到父进程所在进程池中,如果父进程属于某一个进程池,那么我们再将子进程放到其他进程池中,自然会导致失败,这个标志表示,新创建的子进程不属于任何一个进程池,这样在后面的操作才会成功

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java编程技术

MySQL中流式查询使用

MySQL 是目前使用比较广泛的关系型数据库,而从数据库里面根据条件查询数据到内存的情况想必大家在日常项目实践中都有使用。

1422
来自专栏java架构师

SQL 写入调优

今天看到一篇非常适合本人这种数据库调优小白级别的人学的文章,做个笔记,学习之。 首先建一个用户表: CREATE TABLE [dbo].[jk_users](...

2826
来自专栏清风

原创哈希数据导出算法 原

1587
来自专栏琯琯博客

Yii2 开发小技巧

2674
来自专栏IT杂记

关于MySQL DNS解析探究之二:unauthenticated user

把这篇没写完的文章写完,2015年的事就不留到2016了 开启DNS解析 mysql> show variables like 'skip_name_resol...

2838
来自专栏令仔很忙

存储过程--机房收费系统

存储过程是由流控制和SQL语句书写的过程,这个过程经编译和优化后存储在数据库服务器中,应用程序使用时只要调用即可。

1101
来自专栏草根专栏

用VSCode开发一个基于asp.net core 2.0/sql server linux(docker)/ng5/bs4的项目(2)

为Domain Model添加约束 前一部分, 我们已经把数据库创建出来了. 那么我们先看看这个数据库. 可以在项目里面建立一个database.sql, 并且...

3495
来自专栏文渊之博

使用SQL Server 扩展事件来创建死锁的时间跟踪

我们通过SQL Server 2012图形界面来部署一个扩展事件跟踪会话。然后可以生成SQL脚本,在2008或2008 R2版本下运行类似的跟踪。 步骤1: 通...

2409
来自专栏张善友的专栏

Silverlight 2 Beta 2的Isolated Storage

Silverlight beta 2 的配置有一个重大变化就是对DRM 和Application Storage的配置 ? Application stora...

20510
来自专栏Java帮帮-微信公众号-技术文章全总结

第三十天-加强2-多表查询&JDBC&连接池&DBUtils&综合案例【悟空教程】

第三十天-加强2-多表查询&JDBC&连接池&DBUtils&综合案例【悟空教程】

1704

扫码关注云+社区

领取腾讯云代金券