让我们与星光探索者一起,探索Windows吧!
内核对象作业Job
在Visual Studio中,我们有时候想在编译文件中途终止编译,调试程序中途终止调试,Visual Studio是怎么做的呢?虽然这是一个简单而常见的问题,但windows没有维护线程之间的父子关系,解决起来非常难。
Windows提供了一个作业(job)内核对象,它允许你将进程组合在一起并创建一个”沙箱”来限制进程能够做什么.最好将作业对象想象成一个进程容器.但是,即使作业中只包含一个进程,也是非常有用的,因为这样可以对进程施加平时不能施加的限制.点我查看微软官方对于作业对象的介绍
下面的程序代码,将尝试将一个进程放到一个作业中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| #include <windows.h> #include <stdio.h> #include <tchar.h> #pragma comment(lib, "user32.lib")
int _tmain() { BOOL bInJob = FALSE; IsProcessInJob(GetCurrentProcess(), NULL, &bInJob); if (bInJob) { MessageBox(NULL, TEXT("本进程已被放入作业"), TEXT(""), MB_OK); return 0; }
HANDLE hJob = CreateJobObject(NULL, TEXT("MySecretJob"));
JOBOBJECT_BASIC_LIMIT_INFORMATION joblimit = {0}; joblimit.PriorityClass = IDLE_PRIORITY_CLASS; joblimit.PerProcessUserTimeLimit.QuadPart = 10000; joblimit.LimitFlags = JOB_OBJECT_LIMIT_PRIORITY_CLASS | JOB_OBJECT_LIMIT_JOB_TIME;
JOBOBJECT_BASIC_UI_RESTRICTIONS jobuir; jobuir.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_EXITWINDOWS; jobuir.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_HANDLES;
SetInformationJobObject(hJob, JobObjectBasicLimitInformation, &joblimit, sizeof(joblimit)); SetInformationJobObject(hJob, JobObjectBasicUIRestrictions, &jobuir, sizeof(jobuir));
STARTUPINFO si = {sizeof(si)}; PROCESS_INFORMATION pi; CreateProcess(TEXT("C:\\windows\\System32\\cmd.exe"), NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
AssignProcessToJobObject(hJob, pi.hProcess); ResumeThread(pi.hThread); CloseHandle(pi.hThread);
HANDLE handles[2]; handles[0] = pi.hProcess; handles[1] = hJob;
DWORD result = WaitForMultipleObjects(2, handles, FALSE, INFINITE); switch (result - WAIT_OBJECT_0) { case 0: MessageBox(NULL, TEXT("作业关联的进程已终止"), TEXT("提示"), MB_ICONINFORMATION); break; case 1: MessageBox(NULL, TEXT("作业分配的CPU时间用尽"), TEXT("提示"), MB_ICONINFORMATION); break; }
CloseHandle(pi.hProcess); CloseHandle(hJob); return 0; }
|
和所有内核对象一样,第一个参数将安全信息与新的作业对象关联。调用CreateJobObject 函数可创建一个内核对象。调用CloseHandle关掉作业句柄后,并不会迫使作业内所有进程终止运行,实际上只是加了一个删除标记,只有作业中所有进程都已终止运行,才会自动销毁。
1 2 3 4
| HANDLE CreateJobObject( LPSECURITY_ATTRIBUTES lpJobAttributes, LPCTSTR lpName, )
|
给作业添加限制
之后,我们设置了作业的一些基本限制信息后,调用AssignProcessToJobObject函数,将一个进程放入作业。
关于设置作业的基本限制项,我们可以限制:
在上述示例代码中,调用了SetInformationJobObject 函数,来使设置的基本设置项生效。
1 2 3 4 5 6
| BOOL SetInformationJobObject( HANDLE hJob, JOBOBJECTINFOCLASS JobObjectInformationClass, LPVOID lpJobObjectInformation, DWORD cbJobObjectInformationLength );
|
其中第二个参数,我们可以选择下面的类,当然还有其他的
- JOBOBJECT_BASIC_LIMIT_INFORMATION(作业基本限额)查看详细
- JOBOBJECT_BASIC_UI_RESTRICTIONS(作业基本UI限制)查看详细
- JOBOBJECT_CPU_RATE_CONTROL_INFORMATION(作业CPU速率限制)查看详细
在上方示例代码中,我们设置了JOB_OBJECT_UILIMIT_HANDLES,这个限制意味着作业内任何进程都不能访问作业外部进程所创建的用户对象。例如在一个作业中运行Microsoft Spy++,就只能看到Spy++自己创建的窗口。
这个UI限制只是单向的,也就是说作业外部的进程可以看到作业内部进程所创建的用户对象,即使内部指定了JOB_OBJECT_UILIMIT_HANDLES
要为作业中的进程创建一个真正安全的沙箱,对UI句柄进行限制是很强大的一个能力,不过,有时仍然需要让用户内部的一个进程同作业外部的一个进程进行通信。我们可以使用窗口消息进行通信,但是如果作业中的进程不能访问UI句柄,那么这个方法就失效了。幸运的是,我们可以用UserHandleGrantAccess 函数来通信。
1 2 3 4 5
| BOOL UserHandleGrantAccess( HANDLE hUserHandle, HANDLE hJob, BOOL bGrant );
|
调用TerminateJobObject 函数 ,可以把作业内的所有进程都终止。
我们可调用QueryInformationJobObject 函数来查询一个作业的相应限制信息
1 2 3 4 5 6 7
| BOOL QueryInformationJobObject( HANDLE hJob, JOBOBJECTINFOCLASS JobObjectInformationClass, LPVOID lpJobObjectInformation, DWORD cbJobObjectInformationLength, LPDWORD lpReturnLength );
|
如果第一个参数传NULL,并且执行代码的进程被放入作业中,将查询这个作业的相关信息。对于SetInformationJobObject这样调用是失败的,目的是防止进程删掉施加给自己身上的限制。
作业通知
如果我们很想知道作业中的所有进程何时终止执行,所有已分配的CPU时间是否已经到期,我们应该怎么做呢?
作业中的进程如果尚未用完已分配的CPU时间,作业对象处于未触发状态,一旦作业用完所有已分配的CPU时间,Windows就强行“杀死”作业中的所有进程,并触发作业对象。通过调用WaitForSingleObject或一个类似的函数,我们可以轻松捕获到这个事件。我们之后还可以把作业对象重置为未触发状态,只需调用SetInformationJobObject并授予更多CPU时间即可。
如果想获得更高级的作业通知,我们必须创建一个I/O完成端口(completion port)内核对象,并将我们的作业对象与其关联,然后处理。