如何枚举分配给当前进程的所有JobObject?
问题:获取当前进程关联的所有JobObject并计算内存限制聚合值
我用C#写程序时,需要获取分配给当前进程的所有JobObject列表,进而计算内存限制的聚合值(比如最小值)。但现有QueryInformationJobObject函数仅能返回最后一个关联JobObject的限制,多JobObject场景下无法满足需求。示例代码如下:
[DllImport("kernel32.dll", SetLastError = true), SuppressUnmanagedCodeSecurity] public static extern IntPtr CreateJobObjectW(IntPtr securityAttrs, [MarshalAs(UnmanagedType.LPWStr)] string name); [DllImport("kernel32.dll", SetLastError = true), SuppressUnmanagedCodeSecurity] public static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool QueryInformationJobObject(IntPtr hJob, JobObjectInfoType infoClass, IntPtr info, UInt32 infoLength, IntPtr returnLength); public static IntPtr CreateAndAssignJobObject(string name) { var jobObjectHandle = CreateJobObjectW(securityAttrs: IntPtr.Zero, name); var processHandle = Process.GetCurrentProcess().Handle; AssignProcessToJobObject(jobObjectHandle, processHandle); return jobObjectHandle; } public static void LimitJobObjectMemory(IntPtr jobObjectHandle, ulong memoryLimit) { // 设置JobObject内存限制的逻辑 } /// <summary> /// 获取JobObject的扩展限制信息 /// </summary> private static JOBOBJECT_EXTENDED_LIMIT_INFORMATION GetExtendedLimitInformation() { var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); int infoSize = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); IntPtr extendedInfoPtr = Marshal.AllocHGlobal(infoSize); try { Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); if (!QueryInformationJobObject( IntPtr.Zero, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (UInt32)infoSize, IntPtr.Zero)) { throw new Exception($"QueryInformationJobObject failed; err={Marshal.GetLastWin32Error()}"); } extendedInfo = (JOBOBJECT_EXTENDED_LIMIT_INFORMATION)Marshal.PtrToStructure(extendedInfoPtr, typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); } finally { Marshal.FreeHGlobal(extendedInfoPtr); } return extendedInfo; } public static void Main() { var job1Handle = CreateAndAssignJobObject("job1"); LimitJobObjectMemory(job1Handle, 4_000_000_000); var job2Handle = CreateAndAssignJobObject("job2"); }
代码逻辑:创建第一个带内存限制的JobObject(job1Handle),再创建第二个JobObject(job2Handle),此时查询进程限制仅能获取最后一个JobObject的设置,请问如何获取所有关联JobObject的内存限制并计算聚合值,或枚举进程所属的所有JobObject?
解决方案
Windows原生API没有直接提供枚举进程所属所有JobObject的函数,可根据场景选择以下两种方案:
方案1:跟踪自身创建的JobObject(推荐)
如果所有JobObject都是由当前程序创建的,直接维护一个全局列表存储所有Job句柄,后续遍历列表查询每个Job的内存限制即可。这种方法简单可靠,无需额外权限。
实现代码
using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; public class JobObjectManager { // 维护所有创建的JobObject句柄列表 private static readonly List<IntPtr> _jobHandles = new List<IntPtr>(); [DllImport("kernel32.dll", SetLastError = true), SuppressUnmanagedCodeSecurity] public static extern IntPtr CreateJobObjectW(IntPtr securityAttrs, [MarshalAs(UnmanagedType.LPWStr)] string name); [DllImport("kernel32.dll", SetLastError = true), SuppressUnmanagedCodeSecurity] public static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool QueryInformationJobObject(IntPtr hJob, JobObjectInfoType infoClass, IntPtr info, UInt32 infoLength, out UInt32 returnLength); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoClass, IntPtr info, UInt32 infoLength); public enum JobObjectInfoType { ExtendedLimitInformation = 9 } [StructLayout(LayoutKind.Sequential)] public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION { public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; public IO_COUNTERS IoInfo; public UIntPtr ProcessMemoryLimit; public UIntPtr JobMemoryLimit; public UIntPtr PeakProcessMemoryUsed; public UIntPtr PeakJobMemoryUsed; } [StructLayout(LayoutKind.Sequential)] public struct JOBOBJECT_BASIC_LIMIT_INFORMATION { public Int64 PerProcessUserTimeLimit; public Int64 PerJobUserTimeLimit; public UInt32 LimitFlags; public UIntPtr MinimumWorkingSetSize; public UIntPtr MaximumWorkingSetSize; public UInt32 ActiveProcessLimit; public UIntPtr Affinity; public UInt32 PriorityClass; public UInt32 SchedulingClass; } [StructLayout(LayoutKind.Sequential)] public struct IO_COUNTERS { public UInt64 ReadOperationCount; public UInt64 WriteOperationCount; public UInt64 OtherOperationCount; public UInt64 ReadTransferCount; public UInt64 WriteTransferCount; public UInt64 OtherTransferCount; } public static IntPtr CreateAndAssignJobObject(string name) { var jobObjectHandle = CreateJobObjectW(IntPtr.Zero, name); if (jobObjectHandle == IntPtr.Zero) throw new Exception($"CreateJobObjectW failed; err={Marshal.GetLastWin32Error()}"); var processHandle = Process.GetCurrentProcess().Handle; if (!AssignProcessToJobObject(jobObjectHandle, processHandle)) throw new Exception($"AssignProcessToJobObject failed; err={Marshal.GetLastWin32Error()}"); // 添加到全局列表 lock (_jobHandles) { _jobHandles.Add(jobObjectHandle); } return jobObjectHandle; } public static void LimitJobObjectMemory(IntPtr jobObjectHandle, ulong memoryLimit) { var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); extendedInfo.BasicLimitInformation.LimitFlags = 0x1000; // JOB_OBJECT_LIMIT_JOB_MEMORY extendedInfo.JobMemoryLimit = (UIntPtr)memoryLimit; int infoSize = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); IntPtr infoPtr = Marshal.AllocHGlobal(infoSize); try { Marshal.StructureToPtr(extendedInfo, infoPtr, false); if (!SetInformationJobObject(jobObjectHandle, JobObjectInfoType.ExtendedLimitInformation, infoPtr, (uint)infoSize)) throw new Exception($"SetInformationJobObject failed; err={Marshal.GetLastWin32Error()}"); } finally { Marshal.FreeHGlobal(infoPtr); } } /// <summary> /// 获取所有关联JobObject的内存限制最小值 /// </summary> public static ulong GetMinimumJobMemoryLimit() { ulong minLimit = ulong.MaxValue; lock (_jobHandles) { foreach (var jobHandle in _jobHandles) { var extendedInfo = GetJobExtendedLimitInformation(jobHandle); if (extendedInfo.JobMemoryLimit != UIntPtr.Zero) { ulong limit = (ulong)extendedInfo.JobMemoryLimit; if (limit < minLimit) minLimit = limit; } } } return minLimit == ulong.MaxValue ? 0 : minLimit; } private static JOBOBJECT_EXTENDED_LIMIT_INFORMATION GetJobExtendedLimitInformation(IntPtr jobHandle) { var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); int infoSize = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); UInt32 returnLength; IntPtr infoPtr = Marshal.AllocHGlobal(infoSize); try { Marshal.StructureToPtr(extendedInfo, infoPtr, false); if (!QueryInformationJobObject(jobHandle, JobObjectInfoType.ExtendedLimitInformation, infoPtr, (uint)infoSize, out returnLength)) throw new Exception($"QueryInformationJobObject failed; err={Marshal.GetLastWin32Error()}"); extendedInfo = (JOBOBJECT_EXTENDED_LIMIT_INFORMATION)Marshal.PtrToStructure(infoPtr, typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); } finally { Marshal.FreeHGlobal(infoPtr); } return extendedInfo; } public static void Main() { var job1Handle = CreateAndAssignJobObject("job1"); LimitJobObjectMemory(job1Handle, 4_000_000_000); var job2Handle = CreateAndAssignJobObject("job2"); LimitJobObjectMemory(job2Handle, 2_000_000_000); // 获取最小内存限制,结果应为2000000000 ulong minLimit = GetMinimumJobMemoryLimit(); Console.WriteLine($"Minimum memory limit: {minLimit}"); } }
方案2:枚举系统中所有JobObject(适用于非自身创建的Job)
如果需要处理非当前程序创建的JobObject,可通过NtQuerySystemInformation枚举系统中所有Job,再检查每个Job是否包含当前进程。此方法需要管理员权限,且Windows内核结构可能随版本变化,兼容性需测试。
实现代码
using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; public class SystemJobEnumerator { [DllImport("ntdll.dll")] private static extern int NtQuerySystemInformation(int systemInformationClass, IntPtr systemInformation, int systemInformationLength, out int returnLength); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool QueryInformationJobObject(IntPtr hJob, JobObjectInfoType infoClass, IntPtr info, UInt32 infoLength, out UInt32 returnLength); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CloseHandle(IntPtr hObject); public enum JobObjectInfoType { BasicProcessIdList = 3 } [StructLayout(LayoutKind.Sequential)] public struct JOBOBJECT_BASIC_PROCESS_ID_LIST { public uint NumberOfProcessIdsInList; public uint Reserved; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] public uint[] ProcessIdList; } private const int SystemJobObjectInformation = 61; private const uint JOB_OBJECT_QUERY = 0x0001; [StructLayout(LayoutKind.Sequential)] private struct SYSTEM_JOBOBJECT_INFORMATION { public uint NumberOfJobs; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] public JOBOBJECT_BASIC_INFORMATION[] Jobs; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct JOBOBJECT_BASIC_INFORMATION { public IntPtr JobHandle; public uint JobId; [MarshalAs(UnmanagedType.LPWStr)] public string JobName; } /// <summary> /// 获取当前进程所属的所有JobObject句柄 /// </summary> public static List<IntPtr> GetCurrentProcessJobs() { var jobHandles = new List<IntPtr>(); int currentPid = Process.GetCurrentProcess().Id; int bufferSize = 4096; IntPtr buffer = Marshal.AllocHGlobal(bufferSize); try { int returnLength; int status = NtQuerySystemInformation(SystemJobObjectInformation, buffer, bufferSize, out returnLength); // 缓冲区不足,重新分配 while (status == 0xC0000004) // STATUS_INFO_LENGTH_MISMATCH { Marshal.FreeHGlobal(buffer); bufferSize = returnLength; buffer = Marshal.AllocHGlobal(bufferSize); status = NtQuerySystemInformation(SystemJobObjectInformation, buffer, bufferSize, out returnLength); } if (status != 0) throw new Exception($"NtQuerySystemInformation failed; status=0x{status:X8}"); var systemJobInfo = (SYSTEM_JOBOBJECT_INFORMATION)Marshal.PtrToStructure(buffer, typeof(SYSTEM_JOBOBJECT_INFORMATION)); int jobInfoSize = Marshal.SizeOf(typeof(JOBOBJECT_BASIC_INFORMATION)); // 遍历所有Job for (int i = 0; i < systemJobInfo.NumberOfJobs; i++) { IntPtr jobInfoPtr = IntPtr.Add(buffer, sizeof(uint) + i * jobInfoSize); var jobInfo = (JOBOBJECT_BASIC_INFORMATION)Marshal.PtrToStructure(jobInfoPtr, typeof(JOBOBJECT_BASIC_INFORMATION)); // 查询Job包含的进程列表 var processList = new JOBOBJECT_BASIC_PROCESS_ID_LIST(); int listSize = Marshal.SizeOf(typeof(JOBOBJECT_BASIC_PROCESS_ID_LIST)) + 1024 * sizeof(uint); IntPtr listPtr = Marshal.AllocHGlobal(listSize); UInt32 returnLen; try { if (QueryInformationJobObject(jobInfo.JobHandle, JobObjectInfoType.BasicProcessIdList, listPtr, (uint)listSize, out returnLen)) { processList = (JOBOBJECT_BASIC_PROCESS_ID_LIST)Marshal.PtrToStructure(listPtr, typeof(JOBOBJECT_BASIC_PROCESS_ID_LIST)); // 检查当前进程是否在列表中 for (int j = 0; j < processList.NumberOfProcessIdsInList; j++) { if (processList.ProcessIdList[j] == currentPid) { // 复制句柄(避免原句柄被关闭) IntPtr dupHandle = IntPtr.Zero; if (DuplicateHandle(GetCurrentProcess(), jobInfo.JobHandle, GetCurrentProcess(), out dupHandle, JOB_OBJECT_QUERY, false, 0)) { jobHandles.Add(dupHandle); } break; } } } } finally { Marshal.FreeHGlobal(listPtr); } } } finally { Marshal.FreeHGlobal(buffer); } return jobHandles; } [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions); [DllImport("kernel32.dll")] private static extern IntPtr GetCurrentProcess(); // 使用示例 public static void Main() { try { var jobs = GetCurrentProcessJobs(); Console.WriteLine($"当前进程关联的JobObject数量:{jobs.Count}"); // 后续可遍历jobs查询每个Job的内存限制 foreach (var job in jobs) { // 查询内存限制逻辑... CloseHandle(job); // 用完关闭句柄 } } catch (Exception ex) { Console.WriteLine($"错误:{ex.Message}"); } } }
内容的提问来源于stack exchange,提问作者Yahav
相关产品推荐
相关产品推荐

