I want more handle! [草稿]

来源:百度文库 编辑:神马文学网 时间:2024/04/27 14:50:54
Windows 系统中句柄 (handle) 是一个应用非常广泛的概念,从文件到窗口到线程到 socket 连接,凡是涉及到对内核资源的间接引用的情况,都可以通过句柄这个广义的概念所涵盖。而我们在日常使用中,往往也只是随用随取,很少会考虑到句柄的数量也是有限制的。
而实际上我们所通称的句柄,严格意义上是可以分为几类的。
从大的范畴上,可以分为窗口和图形子系统 (Windows and Graphics System) 句柄和内核对象句柄两类。
前者包括窗口句柄 HWND, HDC 和 HFONT 等 GDI 对象,需要通过 CreateWindow(Ex)/DestroyWindow 等专用 API 管理,其作用域是窗口和图形子系统自身,主要受到系统共享内存方面的限制。此外因为这部分 API 需要提供对 16bit 系统的兼容性,因此保留了一些 16bit 系统下的限制。例如 HWND 被定义为一个 16 位无符合数,这就限制其最大数量在 65536 以下。
我以前的一篇文章 《HWND 句柄分配算法浅析》 中详细讨论了 HWND 句柄的分配过程,类似的限制也适用于 cursor, timer 等向下兼容的用户态对象。
除此之外窗口管理器还在资源使用上存在其它限制。例如 win95 下窗口管理器使用一片64K的共享堆内存,而每个 HWND 指向的窗口需要占用部分内存,这也在客观上限制了窗口的最大数量。详细的讨论可以参考 IT 史料堆 The Old New Thing 的 Windows are not cheap objects 一文。
而在 WinNT 架构下,此共享内存区进一步分为系统范围和桌面(Desktop)范围,可通过修改注册表项 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SubSystems 下 SharedSection 的值,增加共享内存区域大小以加大最大窗口数的支持。详细的说明可参考 KB126962。
除了上述原理上的限制以外,NT 还可以针对进程设置句柄的限额,例如每进程的最大GDI和User对象句柄数缺省是10000个,可通过修改注册表项 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows 下 USERProcessHandleQuota 和 ProcessHandleQuota(NT)/GDIProcessHandleQuota(2K+) 来增大配额。
不过 GDI 和 User 对象因为共享一个16bit句柄表,因此系统范围内的最大句柄总数是受到理论上限约束的。具体的分析可参考前面提到的 《HWND 句柄分配算法浅析》 一文。
而对内核对象句柄来说就要复杂一些,因为通过对象管理器 (Object Manager) 进行管理的内核对象,根据类型不同可以由不同的子模块和驱动来提供实现,受到的限制也来自于不同的方面。
首先从进程角度来说,其 PCB 中维护了一个进程独有的句柄表。
在早期 WinNT 的实现中,此句柄表是由一个数组实现的链表,在 ExCreateHandle 调用时判断是否有可用节点,为空则按一定增长幅度,调用 ExpAllocateHandleTableEntries 重新分配句柄表。此句柄表初始大小和增量可通过内核变量 ExpDefaultHandleTableSize/ExpDefaultHandleTableGrowth 获得,缺省情况下是 15/16,小内存模式下是 7/8。
而从 Win2K 开始此句柄表改为三层定位机制,用 32bit HANDLE 的 18-25, 10-17, 2-9 三个 8bit 作为索引,在三层树中迅速定位到一个句柄执行的内核对象头。其中实现句柄表定位的 ExpLookupHandleTableEntry 函数伪代码如下:
以下为引用:
typedef struct _EXHANDLE {
union {
struct {
//
//  Application available tag bits
//
ULONG TagBits : 2;
//
//  The handle table entry index
//
ULONG Index : 30;
};
HANDLE GenericHandleOverlay;
};
} EXHANDLE, *PEXHANDLE;
PHANDLE_TABLE_ENTRY ExpLookupHandleTableEntry (IN PHANDLE_TABLE HandleTable, IN EXHANDLE Handle)
{
ULONG l = (Handle.Index >> 24) & 255;
ULONG i = (Handle.Index >> 16) & 255;
ULONG j = (Handle.Index >> 8)  & 255;
ULONG k = (Handle.Index)       & 255;
return &(HandleTable->Table[i][j][k]);
}
这样一来句柄表的上限就很容易达到 16M 个,而且在增长和定位时都能确保整体效率。有兴趣深入了解的朋友可以参考 《Undocument windows》一书中 Object Management 相关章节。
而在内核对象中,相对来说容易碰到上限问题的是线程和 socket。
对线程来说,缺省参数调用 CreateThread 大概只能创建 2k 个左右的线程,这是因为线程堆栈空间缺省会保留 1M,而用户态的地址空间总共就只有 2G。如果希望创建更多的线程,可以在创建线程时,指定较小的堆栈尺寸,例如
以下为引用:
HANDLE h = CreateThread(NULL, 4096, ThreadProc, NULL, STACK_SIZE_PARAM_IS_A_RESERVATION, &id);
但即使你指定 4k 这样小的线程堆栈尺寸,实际能创建的线程总数也只是一万多个,这是因为内存分配是以 64K 边界进行对其的,而系统自身在用户态也占用了一定的内存地址空间。如果希望进一步提高这个上限,就只能通过启动时在 boot.ini 中增加 /3GB 之类的选项来处理。不过更好的方式是修改你的编程模型,使用异步 I/O、完成端口、线程池乃至纤程等其它技术,来降低对线程数量的依赖性。这部分内容的详细讨论可参考 Does Windows have a limit of 2000 threads per process? 一文。
对 socket 来说,其数量限制也是需要从几个方面来考虑的。
首先是由物理资源决定的理论上限,这个直接跟系统物理内存大小相关。因为目前 socket 实现上需要使用一片 nonpage 内存,这部分内存根据实现在 4k-10k 不等,但平均 6k 左右的占用直接导致物理内存迅速被耗尽。因此 socket 数量的理论上限基本上可通过系统非分页内存的上限求出,例如通过 windbg 可以查看当前内存情况:
以下为引用:
lkd> !vm
*** Virtual Memory Usage ***
Physical Memory:    325129   ( 1300516 Kb)
...
NonPagedPool Usage:   6713   (   26852 Kb)
NonPagedPool Max:    64654   (  258616 Kb)
...
虽然可以通过修改注册表项 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management 下的 NonPagedPoolSize/NonPagedPoolQuota[/url] 来调整这个上限,但为确保系统其它部件可用性,此上限不应占用过多的资源。MS 上的推荐值大致如下: 以下为引用: Physical Memory (MB) 128 256 512 1024 2048 4096Min Nonpaged Pool Size 4 8 16 32 64 128Max Nonpaged Pool Size 50 100 200 256 256 256 如果有必要的话,可以将上限调整到 1G 内存,这样可以大大提高最大 socket 数。详细的调整算法可参考 KB126402。相关的讨论可参考 4.8 - How many simultaneous sockets can I have open with Winsock? 小节的内容。关于内存调整的相关信息,可参考 Windows NT Workstation Resource Kit 中第 12 章 Detecting Memory Bottlenecks而除了理论上限外,tcpip.sys 驱动一级还可以通过设置注册表项 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters 下的 MaxUserPort 来限定最大端口数。这个限制会直接导致使用 winsock 建立连接时,因为使用端口超过5k个,返回 WSAENOBUFS (10055) 错误信息。详细的问题描述可参考 KB196271。与之相关的还有 http.sys 的相关参数配置,可参考 HOWTO: Maximize the Number of Concurrent Connections to IIS6如果有兴趣进一步了解相关知识,可参考文档  Microsoft Windows 2000 TCP/IP Implementation Details