如何限制应用程序可以分配的内存

3
我需要一种限制服务程序可分配内存的方法,以防止服务程序耗尽系统资源,类似于SQL Server允许设置“最大服务器内存”的方式。
我知道SetProcessWorkingSetSize并不完全符合我的需求,但我正在尝试让它按照我认为应该的方式运行。无论我使用什么值,我的测试应用程序的工作集都没有受到限制。此外,如果我立即调用GetProcessWorkingSetSize,则返回的值与我之前指定的值不同。以下是我的测试应用程序使用的代码:
var
  MinWorkingSet: SIZE_T;
  MaxWorkingSet: SIZE_T;
begin
  if not SetProcessWorkingSetSize(GetCurrentProcess(), 20, 12800 ) then
    RaiseLastOSError();

  if GetProcessWorkingSetSize(GetCurrentProcess(), MinWorkingSet, MaxWorkingSet) then
    ShowMessage(Format('%d'#13#10'%d', [MinWorkingSet, MaxWorkingSet]));

没有发生错误,但是 GetProcessWorkingSetSize 返回的最小值和最大值都是 81,920。我尝试在 Flags 参数中使用 QUOTA_LIMITS_HARDWS_MAX_ENABLE($00000004)使用 SetProcessWorkingSetSizeEx。不幸的是,如果在 Flags 中传递 $00000000 以外的任何内容,则 SetProcessWorkingSetSizeEx 失败并显示“代码 87。参数不正确。” 我还尝试使用作业对象来实现相同的目标。当启动子进程时,我使用作业对象设置了内存限制。但是,我需要让服务自己设置内存限制,而不是依赖于“启动”服务来完成。到目前为止,我还没有找到一种方法让单个进程创建作业对象,然后将自己添加到作业对象中。这总是失败并显示访问被拒绝。您有什么想法或建议吗?

1
我认为你需要使用任务来完成这个。 - David Heffernan
2
文档说明(https://msdn.microsoft.com/en-us/library/windows/desktop/ms686234(v=vs.85).aspx)指出,最小值必须为20 *页面大小(即4K页面大小的情况下为81920)或更大,否则将使用最小的20 *页面大小。(这些值以字节为单位指定。) - Ondrej Kelle
2
在我看来,你正在错误的方向上进行。如果你想编写一个不会使系统饥饿的服务,那么就要以这样一种方式编写它,使其不需要大量的内存。考虑像 SQL Server 这样的东西(根据问题):它使用大量内存的原因是为了更快地访问尽可能多的数据而故意将尽可能多的数据缓存到内存中。当然,当物理内存不足时,它可以简单地依赖于操作系统交换空间,但这样效率会更低,因为操作系统无法使用索引表。因此,当你配置 SQL 内存使用时,你实际上只是在配置它的内部缓存。 - Disillusioned
1
你需要问自己的问题是:为什么你需要这么多内存?你能否编写代码更有效地使用内存?强制操作系统更早地交换应用程序使用的内存,或在较低的阈值报告“内存不足”并不能真正解决潜在的问题。 - Disillusioned
1
请注意,当您使用钝器来避免“使其他进程的内存饥饿”时,您会冒着以下风险:由于没有足够的内存,为自己的应用程序创建问题;或者在内核花费更多时间交换您的“体贴应用程序”时,使其他进程的IO资源饥饿。因此,无论您做什么:测量和比较 - Disillusioned
显示剩余12条评论
2个回答

3
SetProcessWorkingSetSize函数的文档说明如下:

dwMinimumWorkingSetSize [in]

...

此参数必须大于零,但小于或等于最大工作集大小。默认大小为50页(例如,在页面大小为4K的系统上,这是204,800字节)。如果值大于零但小于20页,则将最小值设置为20页。

在页面大小为4K的情况下,强制规定的最小值为20 * 4096 = 81920字节,这就是您看到的值。这些值以字节为单位指定。 要实际限制服务进程的内存,我认为可以创建一个新作业 (CreateJobObject),设置内存限制 (SetInformationJobObject) 并在服务启动程序中将当前进程分配给该作业 (AssignProcessToJobObject)。
不幸的是,在Windows 8和Server 2012之前的Windows上,如果进程已经属于作业,则此方法无效:

Windows 7、Windows Server 2008 R2、Windows XP with SP3、Windows Server 2008、Windows Vista和Windows Server 2003:进程不能已分配给作业;如果已分配,则该函数将失败并显示ERROR_ACCESS_DENIED。从Windows 8和Windows Server 2012开始,这种行为发生了变化。

如果您的情况是这样的(即在旧版Windows上出现ERROR_ACCESS_DENIED),请检查进程是否已分配给作业(在这种情况下,您无法使用此方法),但也要确保它具有所需的访问权限:PROCESS_SET_QUOTAPROCESS_TERMINATE

0

获取包含进程的对象句柄,首先使用IsProcessInJob()检查是否有可找到的作业。然后使用半文档函数ZwOpenDirectoryObject(L"\BaseNamedObjects")和ZwQueryDirectoryObject()获取所有命名系统级对象,仅查看类型为L“Job”的对象。然后尝试使用JOB_OBJECT_QUERY访问权限打开每个作业对象,并使用IsProcessInJob()检查是否是正确的对象。从那里,使用实际需要的访问权限重新打开作业,重新检查IsProcessInJob()(以避免竞争条件),并根据需要使用句柄。

请注意,一些但不是所有的作业API都接受NULL句柄作为当前作业的别名,但并非您所指示要使用的所有API都是如此。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接