作业(VC_Win32)
作业
(本章节中例子都是用 VS2005 编译调试的)
参考文献:
用途:
Windows 提供一个作业对象,它允许我们将进程组合在一起并创建一个"沙箱"来限制进程能做什么.可以将作业想象成一个进程容器.但是,只包含一个进程的作业同样有用,因为这样可以对进程施加平时不能施加的限制.
注意:
- 如果进程已与一个作业相关联,就无法将当前进程或者它的任何子进程从作业中除去,这个安全特性可以确保进程无法摆脱对它施加的限制.
- 如果确定在自己的代码中不再访问作业对象,就必须调用 CloseHandle 来关闭它的句柄.但是关闭一个作业对象,不会迫使作业中的所有进程都终止运行.作业对象时间只是加了一个删除标记,只有在作业中的所有进程都终止运行之后,才会自动销毁.还需注意的是关闭作业对象的句柄导致进程都不可访问此作业.即使这个作业已然存在.
对作业中的进程加限制:
限制类型:
-
- 基本限额和扩展基本限额: 用于防止作业中的进程独占系统资源
- 基本的 UI 限额: 用于防止作业中的进程更改用户界面
- 安全限额: 用于防止作业中的进程访问安全资源(文件,注册表子项等)
SetInformationJobObject 函数简单说明
在 SetInformationJobObject 函数中的第二,第三参数.来设置作业中进程对应限制,具体如下表
限制类型 | 第二参数的值 | 第三参数的值 |
基本限额 | JobObjectBasicLimitInformation | JOBOBJECT_BASIC_LIMIT_INFORMATION |
扩展后的基本限额 | JobObjectExtendedLimitInformation | JOBOBJECT_EXTENDED_LIMIT_INFORMATION |
基本的 UI 限额 | JobObjectBasicUIRestrictions | JOBOBJECT_BASIC_UI_RESTRICTIONS |
安全限额 | JobObjectSecurityLimitInformation | JOBOBJECT_SECURITY_LIMIT_INFORMATION |
还有许多限额类型可以参看 SetInformationJobObject .
让子进程脱离父进程作业对象的方法:
当作业中的一个进程生成了另一个进程的时候,新进程将自动成为父进程所属的作业的一部分.但可以通过以下方式改变这种行为
-
- 打开 JOBOBJECT_BASIC_LIMIT_INFORMATION 的 LimitFlags 成员的 JOB_OBJECT_LIMIT_BREAKAWAY_OK 标志,告诉系统新生成的进程可以在作业外部执行.而且必须在调用 CreateProcess 函数时指定新的 CREATE_BREAKAWAY_FROM_JOB 标志. 如果 CreateProcess 中设置了 CREATE_BREAKAWAY_FROM_JOB标志,但作业没有打开 JOB_OBJECT_LIMIT_BREAKAWAY_OK 限额标志, CreateProcess 将调用失败.
- 打开 JOBOBJECT_BACIS_LIMIT_INFORMATION 的 LimitFlags 成员的 JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK 标志.此标志也告诉系统新生成的子进程不应该是作业的一部分.但是,现在就咩有必要向 CreateProcess 函数传递任何额外的标志.事实上,此标志会强制新进程脱离但前作业.如果进程在设计之初对作业对象一无所知,这个标志相当有用
相关函数:
- CreateJobObject (创建作业对象)
- IsProcessInJob (是否与作业关联)
- SetInformationJobObject (设置作业对象中进程的限制, 在<<windows核心编程>> -- P124~131 有具体描述)
- QueryInformationJobObject (查询作业队进程施加的限制)
- AssignProcessToJobObject (将进程放入作业中, 在<<windows核心编程>> -- P132~135 有具体描述)
- TerminateJobObject ("杀死"作业中所有的进程)
- CloseHandle (关闭作业对象)
执行流程:
代码样例:
首先生成一个对话框的 MFC 工程,然后进行界面设置,界面样式如下.
接着为按钮添加对应的事件响应函数以及一个在对话框销毁时候的响应事件来做对应清理工作,MFC 会分别在工程中的 **Dlg.cpp 和 **Dlg.h 文件的添加以下代码
// **Dlg.cpp --------------------------------------------------------------------- ... public: //事件响应函数 afx_msg void OnBnClickedButton1(); afx_msg void OnBnClickedButton2(); afx_msg void OnBnClickedButton3(); afx_msg void OnBnClickedButton4(); afx_msg void OnDestroy(); ... // **Dlg.h ----------------------------------------------------------------------- ... BEGIN_MESSAGE_MAP(C**Dlg, CDialog) ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP //消息映射 ON_BN_CLICKED(IDC_BUTTON1, &C**Dlg::OnBnClickedButton1) ON_BN_CLICKED(IDC_BUTTON2, &C**Dlg::OnBnClickedButton2) ON_BN_CLICKED(IDC_BUTTON3, &C**Dlg::OnBnClickedButton3) ON_BN_CLICKED(IDC_BUTTON4, &C**Dlg::OnBnClickedButton4) ON_WM_DESTROY() END_MESSAGE_MAP() ...
后为第一个按钮添加对应的响应的功能函数.即完成创建子进程.在此前先为工程的 C**Dlg 对话框类来添加一个成员变量.来保存子进程句柄.
... public: HANDLE m_hPro; ...
而后在先定义个函数用它来创建子进程(函数名为 CreatePro,如果在 Windows Vista 中在创建子进程时候记得先让子进程脱离默认的 "PCA" 前缀的作业.让子进程脱离作业对象的限制).代码如下:
void C**Dlg::CreatePro() { STARTUPINFO sui; PROCESS_INFORMATION pi; ZeroMemory(&sui,sizeof(STARTUPINFO)); //创建子进程,首先要让子进程脱离 "PCA" 前缀作业的关联所以在 dwCreationFlags 设置为CREATE_BREAKAWAY_FROM_JOB if(!CreateProcess(L"c:\\program files\\vim\\vim73\\gvim.exe",NULL,NULL,NULL, true,CREATE_BREAKAWAY_FROM_JOB,NULL,NULL,&sui,&pi)) { MessageBox(L"创建子进程失败!",L"警告",MB_OK); return; } else { m_hPro = pi.hProcess; CloseHandle(pi.hThread); } }
跟着便可实现 OnBnClickedButton1 具体功能,代码如下:
void C**Dlg::OnBnClickedButton1() { // TODO: 在此添加控件通知处理程序代码 if(m_hPro == NULL) CreatePro(); else { //判断子进程句柄是否有效 DWORD dwRetValue = WaitForSingleObject(m_hPro, 1); switch(dwRetValue) { case WAIT_OBJECT_0: CreatePro(); return; case WAIT_TIMEOUT: MessageBox(L"子进程已经存在!",L"警告",MB_OK); return; } } }
然后来实现创建作业对象和设置作业对象中的进程的限制,但在此之前,为工程 C**Dlg 对话框类添加一个成员变量来保存作业对象句柄.
... public: HANDLE m_hJob; ...
接着便可实现 OnBnClickedButton2 的具体功能来完成创建作业对象和设置作业对象中的进程限制,代码如下:
void C**Dlg::OnBnClickedButton2() { // TODO: 在此添加控件通知处理程序代码 BOOL InJob = FALSE; if(m_hJob == NULL) { m_hJob = CreateJobObject(NULL,NULL); //设置作业对象的基本限制 JOBOBJECT_BASIC_LIMIT_INFORMATION jobli={0}; jobli.PriorityClass = IDLE_PRIORITY_CLASS;//设置作业中进程为低优先级 //jobli.PerJobUserTimeLimit.QuadPart = 10000;//设置作业中进程不会运行超过 1 秒钟,1秒钟后作业中进程会自动运行结束 jobli.LimitFlags = JOB_OBJECT_LIMIT_PRIORITY_CLASS; //jobli.LimitFlags = JOB_OBJECT_LIMIT_PRIORITY_CLASS | JOB_OBJECT_LIMIT_JOB_TIME; SetInformationJobObject(m_hJob,JobObjectBasicLimitInformation,&jobli,sizeof(jobli)); } if(m_hPro != NULL) { //判断子进程是否在作业中 IsProcessInJob(m_hPro,NULL,&InJob); if(!InJob) { //把子进程放入作业对象中 bool is; if(is = AssignProcessToJobObject(m_hJob,m_hPro)) MessageBox(L"子进程与作业关联成功!",L"提示",MB_OK); else { DWORD errorCode; errorCode = GetLastError(); MessageBox(L"子进程与作业关联失败!",L"提示",MB_OK); } } } }
在此之后,为 OnBnClickedButton3 函数添加具体查看作业对象中限制(查看作业对象中的进程)实现功能.代码如下:
void C**Dlg::OnBnClickedButton3() { // TODO: 在此添加控件通知处理程序代码 if(m_hPro != NULL && m_hJob != NULL) { BOOL InJob = FALSE; //判断子进程是否在作业中 IsProcessInJob(m_hPro,m_hJob,&InJob); if(InJob) { CString pIdStr,buffer; /* //由与此例子只有一个进程,所以可以采用下列方式访问进程 ID 号 DWORD len = sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST); JOBOBJECT_BASIC_PROCESS_ID_LIST jobPil; QueryInformationJobObject(m_hJob,JobObjectBasicProcessIdList,&jobPil,len,&len); pIdStr = L"子进程的进程ID为: "; buffer.Format(L"%d",jobPil.ProcessIdList[0]); pIdStr += buffer; MessageBox(pIdStr); */ const DWORD num = 5;//带查询的作业中可能的最大进程数 DWORD len = sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST)+(num-1)*sizeof(DWORD); PJOBOBJECT_BASIC_PROCESS_ID_LIST pJobPil = (PJOBOBJECT_BASIC_PROCESS_ID_LIST)malloc(len); pJobPil->NumberOfAssignedProcesses = num; QueryInformationJobObject(m_hJob,JobObjectBasicProcessIdList,pJobPil,len,&len); for(DWORD i=0;i<pJobPil->NumberOfProcessIdsInList;i++) { pIdStr = L"子进程的进程ID为: "; buffer.Format(L"%d",pJobPil->ProcessIdList[0]); pIdStr += buffer; MessageBox(pIdStr); } } } }
最后.为杀死作业对象中所有进程的功能编写函数 OnBnClickedButton3.代码如下:
void C**Dlg::OnBnClickedButton4() { // TODO: 在此添加控件通知处理程序代码 if(m_hJob != NULL) TerminateJobObject(m_hJob,0); }
还有,最开头的在对话框销毁时候的对应清理工作,代码如下:
void C**Dlg::OnDestroy() { CDialog::OnDestroy(); // TODO: 在此处添加消息处理程序代码 TerminateJobObject(m_hJob,0); if(m_hPro == NULL) CloseHandle(m_hPro); if(m_hJob == NULL) CloseHandle(m_hJob); CDialog::OnClose(); }
运行结果
在运行程序时候先点击创建子进程,结果如下图:(此时打开 Windows 任务管理器.在进程这个页面将找不到 Vim 的运行实例)
然后点击创建作业,将子进程添加到作业中.后运行结果如下图:
接着查询子进程 ID(即PID),显示结果如下:(为了检测显示内容的正确性我打开了 Sysinternals 的 Process Explorer 查看 Vim 的 PID以作比较)
然后在单击杀死作业中进程.Vim 即被杀死.