《Windows核心编程》读书笔记五 作业

第五章 作业

本章内容

5.1 对作业中的进程施加限制

5.2 将进程放入作业中

5.3 终止作业中的所有进程

5.4 作业通知

5.5 Job Lab示例程序


有时候为了完成某些任务需要执行一组进程,需要将一组进程作为一个组来管理。并且可以加以限制。


windows提供一个job内核对象,将应用程序组合在一起并创建一个“沙盒”来限制进程能做什么。

一个StartRestrictedProcess的例子

void StartRestrictedProcess() {
	// Check if we are not already associated with a job.
	// If this is the case ,there is no way to switch to
	// another job.
	BOOL bInJob = FALSE;
	IsProcessInJob(GetCurrentProcess(), NULL, &bInJob);
	if (bInJob) {
		MessageBox(NULL, TEXT("Process already in a job"),
			TEXT(""), MB_ICONINFORMATION | MB_OK);
		return;
	}

	// Create a job kernel object.
	HANDLE hjob = CreateJobObject(NULL,
		TEXT("Wintellect_RestrictedProcessJob"));

	// Place some restrictions on process in the job.

	// First, set some basic restrictions.
	JOBOBJECT_BASIC_LIMIT_INFORMATION jobli = { 0 };

	// The process always runs in the idle priority class.
	jobli.PriorityClass = IDLE_PRIORITY_CLASS;

	// The job cannot use more than 1 second of CPU time.
	jobli.PerJobUserTimeLimit.QuadPart = 10000;		// 1sec in 100-ns intervals

	// These are the only 2 restrictions I want placed on the job (process.
	jobli.LimitFlags = JOB_OBJECT_LIMIT_PRIORITY_CLASS
		| JOB_OBJECT_LIMIT_JOB_TIME;
	SetInformationJobObject(hjob, JobObjectBasicLimitInformation, &jobli,
		sizeof(jobli));

	// Second, set some UI restrictions.
	JOBOBJECT_BASIC_UI_RESTRICTIONS jobuir;
	jobuir.UIRestrictionsClass = JOB_OBJECT_UILIMIT_NONE;	// A fancy zero

	// The process can't log off the system.
	jobuir.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_EXITWINDOWS;

	// The process can't access USER objects(such as other windows)
	// in the system.
	jobuir.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_HANDLES;

	SetInformationJobObject(hjob, JobObjectBasicUIRestrictions, &jobuir,
		sizeof(jobuir));

	// Spawn the process that is to be in the job.
	// Note: you must first spawn the process and then place the process in
	// the job. This means that the process' thread must be initially
	// suspended so that it can't execute any code outside of the job's
	// restrictions.
	STARTUPINFO si = { sizeof(si) };
	PROCESS_INFORMATION pi;
	TCHAR szCmdLine[8];
	_tcscpy_s(szCmdLine, _countof(szCmdLine), TEXT("CMD"));
	BOOL bResult =
		CreateProcess(
		NULL, szCmdLine, NULL, NULL, FALSE,
		CREATE_SUSPENDED | CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);

	// Place the process in the job.
	// Note: if this process spawns any children, the children are
	// automatically part of the same job.
	AssignProcessToJobObject(hjob, pi.hProcess);
	// Now we can allow the child process' thread to execute code.
	ResumeThread(pi.hThread);
	CloseHandle(pi.hThread);

	// Wait for the process to terminate or
	// for all the job's allocated CPU time to be used.

	HANDLE h[2];
	h[0] = pi.hProcess;
	h[1] = hjob;

	DWORD dw = WaitForMultipleObjects(2, h, FALSE, INFINITE);
	switch (dw - WAIT_OBJECT_0) {
	case 0:
		// The process has terminated...
		break;
	case 1:
		// All of the job's allocated CPU time was used...
		break;
	}

	FILETIME CreationTime;
	FILETIME ExitTime;
	FILETIME KernelTime;
	FILETIME UserTime;
	TCHAR szInfo[MAX_PATH];
	GetProcessTimes(pi.hProcess, &CreationTime, &ExitTime,
		&KernelTime, &UserTime);

	StringCchPrintf(szInfo, _countof(szInfo), TEXT("Kernel = %u | User = %u\n"),
		KernelTime.dwLowDateTime / 10000, UserTime.dwLowDateTime / 10000);

	MessageBox(GetActiveWindow(), szInfo, TEXT("Restricted Process times"),
		MB_ICONINFORMATION | MB_OK);

	// Clean up properly.
	CloseHandle(pi.hProcess);
	CloseHandle(hjob);
}

该函数运行机制

首先调用一下函数判断当前进程是否在一个现有的作业之下运行。

WINBASEAPI
BOOL
WINAPI
IsProcessInJob(
    _In_ HANDLE ProcessHandle,
    _In_opt_ HANDLE JobHandle,
    _Out_ PBOOL Result
    );

接下来创建一个Job内核对象

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateJobObjectW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpJobAttributes,
    _In_opt_ LPCWSTR lpName
    );


创建以后别的进程可以通过OpenJobObject函数打开此内核对象。


如果自己的代码不再访问作业对象应该调用CloseHandle来关闭它的句柄。 

注意:关闭一个作业对象不会迫使其下运行的进程终止。作业对象只是加了一个删除标记,只有在其下的所有进程都终止运行后,才会自动销毁。

关闭作业对象会导致所有进程都无法访问此作业,即使该作业仍然存在。

例如一下代码

	HANDLE hJob = CreateJobObject(NULL, TEXT("Jeff"));

	AssignProcessToJobObject(hJob, GetCurrentProcess());

	CloseHandle(hJob);

	hJob = OpenJobObject(JOB_OBJECT_ALL_ACCESS, FALSE, TEXT("Jeff"));
	// OpenJobObject fails and returns NULL here because the name "Jeff
	// was disassociated from the job when CloseHandle was called.
	// There is no way to get a handle to this job now.

5.1 对作业中的进程施加限制

基本限额和扩展基本限额,用于防止作业中的进程独占系统资源。

基本UI限制,用于防止作业内的进程更改用户界面

安全限额,用于防止作业内的进程访问安全资源(文件,注册表子项等)

通过以下函数向作业施加限制

WINBASEAPI
BOOL
WINAPI
SetInformationJobObject(
    _In_ HANDLE hJob,
    _In_ JOBOBJECTINFOCLASS JobObjectInformationClass,
    _In_reads_bytes_(cbJobObjectInformationLength) LPVOID lpJobObjectInformation,
    _In_ DWORD cbJobObjectInformationLength
    );

第一个参数指定要限制的作业。

第二个参数指定了要限制的类型

第三个参数指定了一个数据结构的地址

第四个参数指出了此数据结构的大小(用于版本控制)




以上例子只做了一个基本限制设置JOBOBJECT_BASIC_LIMIT_INFORMATION结构

typedef struct _JOBOBJECT_BASIC_LIMIT_INFORMATION {
    LARGE_INTEGER PerProcessUserTimeLimit;
    LARGE_INTEGER PerJobUserTimeLimit;
    DWORD LimitFlags;
    SIZE_T MinimumWorkingSetSize;
    SIZE_T MaximumWorkingSetSize;
    DWORD ActiveProcessLimit;
    ULONG_PTR Affinity;
    DWORD PriorityClass;
    DWORD SchedulingClass;
} JOBOBJECT_BASIC_LIMIT_INFORMATION, *PJOBOBJECT_BASIC_LIMIT_INFORMATION;




SchedulingClass 成员将设定在相同优先级的作业下进程的调度优先级(0~9)数值越大进程获得的cpu时间越多。

还有一个扩展的结构体用于设置作业的信息

typedef struct _JOBOBJECT_EXTENDED_LIMIT_INFORMATION {
    JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
    IO_COUNTERS IoInfo;
    SIZE_T ProcessMemoryLimit;
    SIZE_T JobMemoryLimit;
    SIZE_T PeakProcessMemoryUsed;
    SIZE_T PeakJobMemoryUsed;
} JOBOBJECT_EXTENDED_LIMIT_INFORMATION, *PJOBOBJECT_EXTENDED_LIMIT_INFORMATION;

他包含一个JOBOBJECT_BASIC_LIMIT_INFORMATION结构

InInfo保留用

PeakProcessMemoryUsed和PeakJobMemoryUsed是只读的。

ProcessMemoryLimit和JobMemoryLimit限制作业中任何一个进程或全部进程所使用的已调拨的存储空间。

需要在LimitFlags中设定JOB_OBJECT_LIMIT_JOB_MEMORY和JOB_OBJECT_LIMIT_PROCESS_MEMORY标志。


再看看JOBOBJECT_BASIC_UI_RESTRICTIONS

typedef struct _JOBOBJECT_BASIC_UI_RESTRICTIONS {
    DWORD UIRestrictionsClass;
} JOBOBJECT_BASIC_UI_RESTRICTIONS, *PJOBOBJECT_BASIC_UI_RESTRICTIONS;



例如在一个限制了访问UI句柄的作业中启动spy++ ,spy++将无法访问其他进程的窗口句柄。


可以看到spy++在作业中访问不了其他进程的句柄,只能访问自己创建的窗口句柄。

但是在作业外的进程是可以看见spy++的窗口句柄的。

有时候如果限制了作业内的进程访问窗口句柄,则作业内的进程将无法对外发送窗口消息。可以使用函数解决。

WINUSERAPI
BOOL
WINAPI
UserHandleGrantAccess(
    _In_ HANDLE hUserHandle,
    _In_ HANDLE hJob,
    _In_ BOOL   bGrant);

授权作业可以访问或拒绝访问哪些对象。单该函数如果被一个受限的作业内的进程中的线程调用会返回失败


可以向作业施加最后一种安全限制。一旦应用,安全限制就不能撤销。

typedef struct _JOBOBJECT_SECURITY_LIMIT_INFORMATION {
    DWORD SecurityLimitFlags ;
    HANDLE JobToken ;
    PTOKEN_GROUPS SidsToDisable ;
    PTOKEN_PRIVILEGES PrivilegesToDelete ;
    PTOKEN_GROUPS RestrictedSids ;
} JOBOBJECT_SECURITY_LIMIT_INFORMATION, *PJOBOBJECT_SECURITY_LIMIT_INFORMATION ;

通过以下函数查询作业的安全限制

WINBASEAPI
BOOL
WINAPI
QueryInformationJobObject(
    _In_opt_  HANDLE hJob,
    _In_      JOBOBJECTINFOCLASS JobObjectInformationClass,
    _Out_writes_bytes_to_(cbJobObjectInformationLength, *lpReturnLength) LPVOID lpJobObjectInformation,
    _In_      DWORD cbJobObjectInformationLength,
    _Out_opt_ LPDWORD lpReturnLength
    );

该函数也可以用于作用中的进程来调用查询自己被施加了哪些限制。


5.2 将进程放入作业中

CreateProcess创建需要放入作业的进程,并使用CREATE_SUSPENDED标志。

在子进程执行代码以前将其加入作业

WINBASEAPI
BOOL
WINAPI
AssignProcessToJobObject(
    _In_ HANDLE hJob,
    _In_ HANDLE hProcess
    );
并且一个进程只能加入一个作业。

给作业设置

JOBOBJECT_BASIC_LIMIT_INFORMATION 的limitFlags成员设置JOB_OBJECT_LIMIT_BREAKWAWY_OK标志告知系统新生产的进程可以在作业外部执行。

并且CreateProcess函数时指定CREATE_BREAKAWAY_FROM_JOB标志,如果不加此标志CreateProcess会失败。

JOBOBJECT_BASIC_LIMIT_INFORMATION的limitFlags成员 JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK .这样在CreateProcess时并不需要额外传递任何标志。


最后在调用完AssignProcessToJobObject以后调用ResumeThread使得进程可以在作业限制下执行代码。



5.3 终止作业中的所有进程。

WINBASEAPI
BOOL
WINAPI
TerminateJobObject(
    _In_ HANDLE hJob,
    _In_ UINT uExitCode
    );
类似TerminateProcess


查询作业统计信息

QueryInformationJobObject

WINBASEAPI
BOOL
WINAPI
QueryInformationJobObject(
    _In_opt_  HANDLE hJob,
    _In_      JOBOBJECTINFOCLASS JobObjectInformationClass,
    _Out_writes_bytes_to_(cbJobObjectInformationLength, *lpReturnLength) LPVOID lpJobObjectInformation,
    _In_      DWORD cbJobObjectInformationLength,
    _Out_opt_ LPDWORD lpReturnLength
    );

typedef struct _JOBOBJECT_BASIC_ACCOUNTING_INFORMATION {
    LARGE_INTEGER TotalUserTime;
    LARGE_INTEGER TotalKernelTime;
    LARGE_INTEGER ThisPeriodTotalUserTime;
    LARGE_INTEGER ThisPeriodTotalKernelTime;
    DWORD TotalPageFaultCount;
    DWORD TotalProcesses;
    DWORD ActiveProcesses;
    DWORD TotalTerminatedProcesses;
} JOBOBJECT_BASIC_ACCOUNTING_INFORMATION, *PJOBOBJECT_BASIC_ACCOUNTING_INFORMATION;



	JOBOBJECT_BASIC_ACCOUNTING_INFORMATION jobAccountInfo = { 0 };
	QueryInformationJobObject(hjob, JobObjectBasicAccountingInformation,
		&jobAccountInfo, sizeof(jobAccountInfo), NULL);

还可以传入一个一下结构来查询IO信息

typedef struct _JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION {
    JOBOBJECT_BASIC_ACCOUNTING_INFORMATION BasicInfo;
    IO_COUNTERS IoInfo;
} JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION, *PJOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION;

typedef struct _IO_COUNTERS {
    ULONGLONG  ReadOperationCount;
    ULONGLONG  WriteOperationCount;
    ULONGLONG  OtherOperationCount;
    ULONGLONG ReadTransferCount;
    ULONGLONG WriteTransferCount;
    ULONGLONG OtherTransferCount;
} IO_COUNTERS;
typedef IO_COUNTERS *PIO_COUNTERS;


对于不属于作业的进程可以调用GetProcessIoCounters函数来获得未放入作业那些进程的信息。

枚举当前作业中的进程数和id

void EnumProcessIdsInJob(HANDLE hjob) {

	// I assume that there will never be more
	// than 10 processes in this job.
#define MAX_PROCESS_IDS 10

	// calculate the number of bytes needed for structure & process IDs.
	DWORD cb = sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST) +
		(MAX_PROCESS_IDS - 1) * sizeof(DWORD);

	// Allocate the block of memory.
	PJOBOBJECT_BASIC_PROCESS_ID_LIST pjobpil =
		(PJOBOBJECT_BASIC_PROCESS_ID_LIST)_alloca(cb);	// alloc the memory in stack so we don't need to free when return.

	// Tell the function the maximum number of processes
	// that we allocated space for.
	pjobpil->NumberOfAssignedProcesses = MAX_PROCESS_IDS;

	// Request the current set of process IDs.
	QueryInformationJobObject(hjob, JobObjectBasicProcessIdList,
		pjobpil, cb, &cb);

	// Enumerate the process IDs.
	for (DWORD x = 0; x < pjobpil->NumberOfProcessIdsInList; x++) {
		// Use pjobpil->ProcessIdList[x]...
	}

	// Since _alloca was used to allocate the memory,
	// w don't need to free it here.
}

可以利用Performance Data Helper来获取更多作业相关的统计信息

利用Process Explorer可以观察作业



5.4 作业通知

如果作业进程使用完所有分配的cpu使用,作业会强行杀死进程并触发作业对象。

之后还可以调用SetInformationJobObject设置为未触发状态并授予更多的cpu时间。

注意作业并不会在其内部的进程都执行完毕而置于触发状态,而是取决于释放用完了分配的cpu时间。

因此想判断作业是否执行完毕可以等待主进程的进程对象句柄触发。


为了获得一些更高级的通知,可以创建一些IO完成端口内核对象,并将其与作业关联。然后有一个或多个线程等待作业通知到达完成端口,以对他们进行处理

创建了IO完成端口,可以调用SetInformationJobObject将其与作业关联。

	HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0,
		0);
	//...

	JOBOBJECT_ASSOCIATE_COMPLETION_PORT jacp;
	jacp.CompletionKey = hjob;
	jacp.CompletionPort = hIOCP; // handle of completion port that recieves notifications
	SetInformationJobObject(hjob, JobObjectAssociateCompletionPortInformation,
		&jacp, sizeof(jacp));


线程调用GetQueuedCompletionStatus来监视完成端口:

WINBASEAPI
BOOL
WINAPI
GetQueuedCompletionStatus(
    _In_ HANDLE CompletionPort,
    _Out_ LPDWORD lpNumberOfBytesTransferred,
    _Out_ PULONG_PTR lpCompletionKey,
    _Out_ LPOVERLAPPED * lpOverlapped,
    _In_ DWORD dwMilliseconds
    );

lpNumberOfBytesTransferred参见下表。



注意:默认情况下,如果作业分配的CPU时间到期,他会终止所有的进程,但不会投递 JOB_OBJECT_MSG_END_OF_JOB_TIME

需要执行以下类似的代码:

	// Create a JOBOBJECT_END_OF_JOB_TIME_INFORMATION structure
	// and initialize its only member.

	JOBOBJECT_END_OF_JOB_TIME_INFORMATION joeojti;
	joeojti.EndOfJobTimeAction = JOB_OBJECT_POST_AT_END_OF_JOB;

	// Tell the job object what we want it to do when the job time is execeeded.
	SetInformationJobObject(hJob, JobObjectEndOfJobTimeInformation,
		&joeojti, sizeof(joeojti));

对于EndOfJobTimeAction成员,创建作业时候的默认值是 JOB_OBJECT_TERMINATE_AT_END_OF_JOB


5.5 Job Lab示例程序

该示例创建了一个作业对象,一个IO完成端口并与之关联。面板上可以进行各种限额设置,然后在此作业环境下创建进程观察进程的运行。

并且可以收到IO完成端口收到的各种通知并显示相关统计信息。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值