如何增加 Windows 服务生成的子进程最大数量 -- 桌面堆栈限制

26

问:我有一个生成大量子进程的windows服务。当进程数量到达约100个时,无法再启动更多进程。CreateProcess()调用返回一个pid,但是进程就是无法生成。如果我将我们的服务器作为控制台应用程序运行,此限制就会消失。此外,如果我设置DETACHED_PROCESS标志,则限制将增加一倍多。但是,如果我设置DETACHED_PROCESS并调用CreateProcesssWithLogonW(),就会出现故障。

2个回答

40
这是一个“桌面堆”问题。可以在此处找到非常好的讨论:桌面堆概述桌面堆,第二部分
请注意,这仅适用于作为服务运行的程序,因为服务的默认桌面堆大小比应用程序小得多。
在我们的情况下,我们能够在没有更改的情况下启动约100个子进程,然后耗尽资源。通过更改,这个数字可以大大增加。
以下是我们在知识库中向终端用户提供的答案:
警告:这会影响所有服务的桌面堆!不要使其比必要的更大,否则您将迫使系统消耗更多资源,并可能遇到总可用桌面堆大小的问题。
如果您发现即使在非常大的RAM服务器上也无法打开超过约100个项目,则可能已经达到了Windows“桌面堆大小”的限制。
问题在于Windows下的服务会话(服务运行的地方)具有较少的此“桌面堆”空间可用于创建窗口。
简短的版本是:
- 服务获得比交互式会话更小的桌面堆。 - 桌面堆大小限制窗口数量 - 每个子服务器都会创建一个或多个“窗口”,即使我们看不到它们。
解决方案:
1. 在进行任何更改之前备份您的注册表! 2. 以管理员身份运行regedit.exe 3. 编辑注册表值:
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SubSystems\Windows
  • 你会看到一个字符串,例如:

  • %SystemRoot%\system32\csrss.exe ObjectDirectory=\Windows SharedSection=1024,20480,768 Windows=On SubSystemType=Windows ServerDll=basesrv,1 ServerDll=winsrv:UserServerDllInitialization,3 ServerDll=winsrv:ConServerDllInitialization,2 ServerDll=sxssrv,4 ProfileControl=Off MaxRequestThreads=16
    

    关键部分是:

    SharedSection=1024,20480,768
    

    第二个数字(20480)是交互式会话的大小。第三个数字(768)是非交互式(服务)会话的大小。请注意,第三个数字比第二个数字小26倍。通过实验,我们发现将其更改为:

    SharedSection=1024,20480,2048
    

    将项目限制从106增加到270,与堆大小几乎完美地比例。选择反映您预计系统上所有用户同时打开的最大项目数的值。不要使此值过大,并且不要超过8192,因为系统中的每个服务都会消耗更多宝贵的资源。

    您需要重新启动计算机以使这些新设置生效。


    1
    我不理解所有这些关于“桌面堆”写作的语言,似乎适用于分叉服务,但是这个答案拯救了我们。 - covener
    3
    令人难以置信的是,Windows 2008 Server Web Edition(Vista Core),Windows 2008 Server R2(Windows 7 Core)和Windows 2012 Server R2(Windows 8.1 Core)都受到了同样的限制。我把它们改成了1024、20480、4096,并将最大线程数从16增加到32,在这些服务器上没有对资源使用造成不良影响,但是使得运行在这些服务器上的服务能够同时成功地生成其他进程的能力显著提高了。谢谢! - Graphic Equaliser
    非常清晰的描述和解决方法,点个赞。但是需要说明的是,如果你在服务中创建了这么多子进程,那么你的做法可能是错误的。:-) [当然,如果你正在将代码从UNIX移植过来,重新设计可能并不值得努力。] - Harry Johnston
    Harry,我完全理解你的意思。不幸的是,我们需要进程级别的隔离,因为我们有一个系统与用户指定的数据库驱动程序和其他各种质量和稳定性级别的库进行接口。我们从失败中学到了教训,尝试在一个进程中托管多个“项目”会导致各种糟糕和不可预测的交互。能够终止进程是我们的紧急出口。 - Wheezil
    这篇文章链接表明在Windows Vista中已经全部更改,但我的Windows 7企业版机器(可能存在类似问题)具有相同的默认配置。有人能进一步澄清这里的问题吗? - Toby Wilson
    我认为数量上略有不同,但根本问题是相同的。 - Wheezil

    1
    我们有许多远程桌面服务器需要测试,因此我们编写了一个PowerShell脚本来查询AD中的RD服务器,然后应用此注册表更改。祝使用愉快。
       $newValue = "%SystemRoot%\system32\csrss.exe ObjectDirectory=\Windows SharedSection=1024,20480,2048 Windows=On SubSystemType=Windows ServerDll=basesrv,1 ServerDll=winsrv:UserServerDllInitialization,3 ServerDll=sxssrv,4 ProfileControl=Off MaxRequestThreads=16"
    $origValue = "%SystemRoot%\system32\csrss.exe ObjectDirectory=\Windows SharedSection=1024,20480,768 Windows=On SubSystemType=Windows ServerDll=basesrv,1 ServerDll=winsrv:UserServerDllInitialization,3 ServerDll=sxssrv,4 ProfileControl=Off MaxRequestThreads=16"
    
    if ($update) {
        Clear-Variable update
    }
    
    $updateConfirm= [System.Windows.Forms.MessageBox]::Show("Update Desktop Heap Limitation to 2048?" , "Status" , 4) 
    if ($updateConfirm -eq "YES" ) {
        $update = $true
    } else { 
        $revertConfirm= [System.Windows.Forms.MessageBox]::Show("Revert Desktop Heap Limitation to 768?" , "Status" , 4) 
        if ($revertConfirm -eq "YES" ) {
            $update = $false
        } 
    }
    
    if (($updateConfirm -ne "YES") -and ($revertConfirm -ne "YES")) {
        Write-Host "User did not specify whether to update or revert Desktop Heap Limitation. Exiting Setup."
        Read-Host "Press Enter to exit."
        break
    }
    
    #Import Active Directory PowerShell module
    if (Test-Path C:\Users\${env:USERNAME}\Documents\WindowsPowerShell\Modules\ActiveDirectory\ActiveDirectory.psm1) {
        Import-Module ActiveDirectory -prefix AD
    } else {
        $s = New-PSSession -computerName DC01WDC01
        Invoke-command { import-module ActiveDirectory } -session $s
        Export-PSSession -session $s -commandname *-AD* -outputmodule ActiveDirectory -allowclobber
        Import-Module ActiveDirectory -prefix AD
        Remove-PSSession -session $s
    }
    
    $servers =  Get-ADADComputer -Filter {(Name -Like "RDS*")}  | Select -Expand Name
    
    foreach ($server in $servers) {
        Write-Host "Working on $server" -ForegroundColor Magenta
        if(!(Test-Connection -ComputerName $server -Count 1 -quiet)) {
            Write-Warning "$server : Offline"
            Continue
        }
    
        if ($update -eq $true) {
            Invoke-Command -ComputerName $server -ArgumentList $newValue -ScriptBlock {
                Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems" -Name "Windows" -Value $args[0]
                $result = Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems" | SELECT Windows
                if ($result.Windows -like "*SharedSection=1024,20480,2048*") {
                    Write-Host "Successfully reverted Desktop Heap Limit to 2048" -ForegroundColor Green
                } else {
                    Write-Warning "Update to registry value unsuccessful on $env:ComputerName" 
                }              
            }
        } elseif ($update -eq $false) {
            Invoke-Command -ComputerName $server -ArgumentList $origValue -ScriptBlock {
                Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems" -Name "Windows" -Value $args[0]
                $result = Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems" | SELECT Windows    
                if ($result.Windows -like "*SharedSection=1024,20480,768*") {
                    Write-Host "Successfully reverted Desktop Heap Limit to 768" -ForegroundColor Green
                } else {
                    Write-Warning "Update to registry value unsuccessful on $env:ComputerName" 
                }       
            }
        }
    }
    

    不幸的是,在我们的情况下,这个设置对我们没有起作用,但也许这个脚本可以帮助其他需要测试大量机器的人。 :) - Shaun
    对我来说有用,但在更改后必须重新启动才能生效。 - user3725395

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