2026/2/21 22:58:15
网站建设
项目流程
网站建设资格预审公告,运营推广岗位职责,网站建设组织,怎么查找网站为什么说Windows句柄表是每个进程独立的#xff0c;并且可以动态增长。当进程需要更多句柄时#xff0c;系统可以扩展句柄表。 而x86的GDT/LDT是全局的#xff0c;且大小固定#xff08;由GDTR/LDTR寄存器中的界限决定#xff09;#xff0c;无法动态增长。
以Windows和x…为什么说Windows句柄表是每个进程独立的并且可以动态增长。当进程需要更多句柄时系统可以扩展句柄表。 而x86的GDT/LDT是全局的且大小固定由GDTR/LDTR寄存器中的界限决定无法动态增长。以Windows和x86系统为例详细解释两种机制的核心原理和差异。Windows句柄表每个进程独立可动态增长核心机制和原理进程句柄表作为进程地址空间的一部分每个Windows进程都有一个独立的句柄表存储在进程的地址空间中实际上是在内核空间但属于进程上下文。这意味着每个进程都可以有自己独立的句柄表互不干扰。句柄表的多级结构Windows的句柄表通常采用多级表结构类似于页表。例如在32位系统中句柄表可能是一个两层的表。第一层是固定大小的指针数组每个指针指向一个第二层的“句柄页”。当进程需要更多句柄时系统可以分配新的句柄页并链接到第一层表中。动态扩展当进程创建的句柄数量超过当前句柄表容量时系统会分配新的句柄页并更新第一层表。这个过程对应用程序是透明的。句柄表的大小只受系统资源如内存限制而不是编译时或启动时固定的。句柄作为索引句柄本质上是一个索引通过这个索引在句柄表中查找对应的内核对象指针。由于句柄表是每个进程独立的所以不同进程中相同的句柄值可能指向不同的内核对象。实例说明假设一个进程初始时句柄表只能容纳100个句柄。当进程创建第101个内核对象时系统会检测到句柄表已满然后执行以下步骤分配一个新的内存页例如4KB作为新的句柄页。将这个新页链接到进程句柄表的第一层索引中。在新分配的句柄页中分配一个句柄条目并返回这个句柄例如句柄值可能是100。进程使用这个句柄来引用新创建的内核对象。这样句柄表就动态扩展了。理论上只要系统有足够的内存句柄表可以一直扩展。x86 GDT/LDT全局/局部描述符表核心机制和原理GDT/LDT是全局数据结构GDT全局描述符表是系统中唯一的由GDTR寄存器指向。LDT局部描述符表虽然每个任务可以有一个但它的位置和大小也是在任务切换时由LDTR寄存器加载且大小固定。固定大小的数组GDT和LDT在内存中是一个连续数组每个描述符占8字节32位模式。GDTR和LDTR寄存器中有一个16位的界限字段表示表的最后一个字节的偏移。因此表的大小在加载GDTR/LDTR时就已经确定且最大为64KB因为界限是16位最大值为0xFFFF即65535字节最多8191个描述符。无法动态扩展一旦GDTR/LDTR被加载表的大小就固定了。如果要增加更多的描述符必须重新分配一个更大的表然后重新加载GDTR/LDTR这需要系统暂停并更新所有引用该表的段寄存器这在实际操作系统中是不现实的。硬件依赖x86架构的段机制是硬件强制的描述符表的结构和访问方式由硬件规定。硬件在访问段寄存器时会使用GDTR/LDTR寄存器中的基址和界限来检查索引是否越界。这种硬件机制限制了表的动态扩展。实例说明假设系统启动时操作系统设置了GDT其中包含10个描述符从索引0到9。GDTR的界限设置为10*8-179因为每个描述符8字节所以10个描述符占用0到79字节。当一个程序试图加载一个段寄存器比如mov ax, 0x50假设TI0索引10即第11个描述符CPU会做如下检查从GDTR中获取基址和界限。计算描述符的偏移索引10* 8 80。检查80787是否超出界限79。因为8779CPU会触发一个异常一般保护异常#GP。如果要添加第11个描述符操作系统必须分配一个新的内存区域大小至少为11*888字节。将原来的10个描述符复制到新区域。添加新的描述符到第11个位置。重新加载GDTR设置基址为新区域界限为87。确保没有其他CPU正在使用旧的GDT在多核系统中需要同步。这个过程非常复杂而且需要系统暂停因为任何在更新GDT过程中访问段寄存器的操作都可能导致错误。因此操作系统通常会在启动时分配一个足够大的GDT而不是动态扩展。总结对比特性Windows句柄表x86 GDT/LDT所属范围每个进程独立全局GDT或每个任务LDT存储位置进程地址空间内核部分由GDTR/LDTR寄存器指定结构多级表类似页表连续数组大小可动态扩展受内存限制固定由GDTR/LDTR的界限决定扩展操作透明分配新页更新指针需要重新分配并复制整个表重新加载寄存器硬件依赖软件实现不依赖特定硬件硬件强制由CPU检查界限最大容量理论上受虚拟内存大小限制最大64KB8191个描述符因此Windows句柄表的设计更加灵活能够适应现代操作系统动态创建大量内核对象的需求。而x86的段描述符表受限于硬件设计是静态的无法动态扩展这也是现代操作系统倾向于使用分页而不是分段的原因之一。Windows句柄表与x86 GDT/LDT的机制差异详解一、Windows句柄表的核心机制1. 三级表结构设计Windows句柄表采用多级表结构类似于页表机制// Windows句柄表三级结构示例简化版typedefstruct_HANDLE_TABLE{ULONG_PTR TableCode;// 指向句柄表的指针低2位表示层级struct_EPROCESS*QuotaProcess;HANDLE UniqueProcessId;EX_PUSH_LOCK HandleLock;LIST_ENTRY HandleTableList;ULONG HandleTableSize;// 当前句柄表大小ULONG NextHandleNeedingPool;// 下一个需要分配池的句柄索引// ...}HANDLE_TABLE;// 句柄表条目结构typedefstruct_HANDLE_TABLE_ENTRY{union{PVOID Object;// 指向内核对象的指针低3位为标志ULONG_PTR Value;};union{ACCESS_MASK GrantedAccess;struct{USHORT GrantedAccessIndex;USHORT CreatorBackTraceIndex;};};}HANDLE_TABLE_ENTRY;2. 动态增长机制实例// 句柄表扩展的实际过程HANDLECreateNewHandle(PVOID Object,ACCESS_MASK AccessMask){// 1. 获取当前进程的句柄表HANDLE_TABLE*handleTablePsGetCurrentProcess()-ObjectTable;// 2. 检查句柄表是否需要扩展if(handleTable-NextHandleNeedingPoolhandleTable-HandleTableSize){// 3. 扩展句柄表ExpandHandleTable(handleTable);}// 4. 分配新句柄HANDLE handleAllocateHandleFromTable(handleTable);// 5. 设置句柄条目HANDLE_TABLE_ENTRY*entryExpLookupHandleTableEntry(handleTable,handle);entry-Object(PVOID)((ULONG_PTR)Object|OBJ_HANDLE_ATTRIBUTES);entry-GrantedAccessAccessMask;returnhandle;}VOIDExpandHandleTable(HANDLE_TABLE*HandleTable){ULONG newSize;// 计算新大小通常加倍if(HandleTable-HandleTableSize0){newSizeINITIAL_HANDLE_TABLE_SIZE;// 初始大小如512}else{newSizeHandleTable-HandleTableSize*2;}// 分配新的句柄表内存PVOID newTableExAllocatePoolWithTag(NonPagedPool,newSize*sizeof(HANDLE_TABLE_ENTRY),htaB);if(newTable){// 复制现有句柄条目if(HandleTable-HandleTableSize0){RtlCopyMemory(newTable,HandleTable-TableCode~3,HandleTable-HandleTableSize*sizeof(HANDLE_TABLE_ENTRY));}// 更新句柄表HandleTable-TableCode(ULONG_PTR)newTable|(HandleTable-TableCode3);// 保持层级标志HandleTable-HandleTableSizenewSize;// 释放旧表如果存在if(HandleTable-TableCode~3){ExFreePoolWithTag((PVOID)(HandleTable-TableCode~3),htaB);}}}3. 三级查找过程// 句柄的三级查找机制PVOIDDereferenceHandle(HANDLE Handle){// 1. 获取当前进程的句柄表HANDLE_TABLE*handleTablePsGetCurrentProcess()-ObjectTable;// 2. 从句柄值计算索引ULONG handleIndex(ULONG)Handle2;// 去掉低2位标志// 3. 根据TableCode的低2位确定层级ULONG levelhandleTable-TableCode3;switch(level){case0:// 单级表小进程if(handleIndexhandleTable-HandleTableSize){HANDLE_TABLE_ENTRY*entry(HANDLE_TABLE_ENTRY*)(handleTable-TableCode~3)handleIndex;return(PVOID)((ULONG_PTR)entry-Object~OBJ_HANDLE_ATTRIBUTES_MASK);}break;case1:// 两级表中等进程{// 第一级中间表ULONG midIndexhandleIndex/HANDLE_ENTRIES_PER_MID;ULONG lowIndexhandleIndex%HANDLE_ENTRIES_PER_MID;HANDLE_TABLE_ENTRY**midTable(HANDLE_TABLE_ENTRY**)(handleTable-TableCode~3);if(midIndexMID_TABLE_SIZEmidTable[midIndex]){HANDLE_TABLE_ENTRY*entrymidTable[midIndex]lowIndex;return(PVOID)((ULONG_PTR)entry-Object~OBJ_HANDLE_ATTRIBUTES_MASK);}}break;case2:// 三级表大进程{// 第一级顶级表ULONG topIndexhandleIndex/(HANDLE_ENTRIES_PER_MID*MID_TABLE_SIZE);ULONG midIndex(handleIndex%(HANDLE_ENTRIES_PER_MID*MID_TABLE_SIZE))/HANDLE_ENTRIES_PER_MID;ULONG lowIndexhandleIndex%HANDLE_ENTRIES_PER_MID;HANDLE_TABLE_ENTRY***topTable(HANDLE_TABLE_ENTRY***)(handleTable-TableCode~3);if(topIndexTOP_TABLE_SIZEtopTable[topIndex]topTable[topIndex][midIndex]){HANDLE_TABLE_ENTRY*entrytopTable[topIndex][midIndex]lowIndex;return(PVOID)((ULONG_PTR)entry-Object~OBJ_HANDLE_ATTRIBUTES_MASK);}}break;}returnNULL;// 无效句柄}二、x86 GDT/LDT的固定大小机制1. GDT硬件数据结构; x86 GDT寄存器结构 struc GDTR .limit: resw 1 ; 16位界限表大小-1 .base: resd 1 ; 32位基地址 endstruc ; GDT描述符格式每个8字节 struc GDT_ENTRY .limit_low: resw 1 ; 段限长低16位 .base_low: resw 1 ; 段基址低16位 .base_mid: resb 1 ; 段基址中8位 .access: resb 1 ; 访问权限 .gran_limit: resb 1 ; 粒度/限长高4位 .base_high: resb 1 ; 段基址高8位 endstruc2. 硬件强制限制的原因; 示例GDT的初始化和限制 section .data gdt_start: dq 0x0000000000000000 ; 空描述符必须 gdt_code32: dw 0xFFFF ; 限长低16位 (0-15) dw 0x0000 ; 基址低16位 (16-31) db 0x00 ; 基址中8位 (32-39) db 0x9A ; 访问字节 (40-47): P1, DPL00, S1, Type1010 db 0xCF ; 标志/限长高4位 (48-55): G1, D/B1, L0, AVL0, 限长16-190xF db 0x00 ; 基址高8位 (56-63) gdt_data32: dw 0xFFFF dw 0x0000 db 0x00 db 0x92 ; Type0010 (数据段) db 0xCF db 0x00 gdt_end: gdt_descriptor: dw gdt_end - gdt_start - 1 ; 16位界限值编译时确定 dd gdt_start ; 32位基地址 section .text global _start _start: ; 加载GDT一次性操作 lgdt [gdt_descriptor] ; 此时GDT大小已固定无法动态扩展 ; 选择子计算索引 * 8 ; 最大描述符数 (界限 1) / 8 ; 示例界限0xFFFF (65535) → 最大8191个描述符3. 无法动态扩展的技术原因// 尝试扩展GDT的问题voidattempt_to_expand_gdt(){// 1. 当前GDT大小uint16_tcurrent_limitget_gdtr_limit();// 例如 0x7FF (1023字节127个描述符)// 2. 需要添加新描述符if(need_new_descriptor()){// 3. 分配新的更大的GDTuint8_t*new_gdtkmalloc(current_limit8EXTRA_SPACE);// 4. 复制旧的GDT内容memcpy(new_gdt,get_gdt_base(),current_limit1);// 5. 添加新描述符setup_descriptor(new_gdtcurrent_limit1,...);// 6. 问题1更新GDTRstruct{uint16_tlimit;uint32_tbase;}__attribute__((packed))new_gdtr;new_gdtr.limitcurrent_limit8;// 新界限new_gdtr.base(uint32_t)new_gdt;// 7. 关键问题加载新GDTR需要非常小心asmvolatile(lgdt %0\n\t// 此时所有基于旧GDT的段选择子都失效了// 必须立即重新加载所有段寄存器movw $0x10, %%ax\n\t// 数据段选择子movw %%ax, %%ds\n\tmovw %%ax, %%es\n\tmovw %%ax, %%fs\n\tmovw %%ax, %%gs\n\tmovw %%ax, %%ss\n\tljmp $0x08, $1f\n\t// 代码段选择子1:\n\t::m(new_gdtr):eax);// 8. 问题2多核同步// 其他CPU可能还在使用旧的GDT// 需要IPI处理器间中断来同步所有CPU// 9. 问题3延迟问题// 中断处理程序可能使用任务门/调用门这些依赖GDT中的特定条目}}4. 硬件检查的严格性; CPU对GDT访问的硬件检查 load_segment_register: ; 假设要加载DS 0x0010索引2TI0RPL0 ; 1. CPU从GDTR获取基址和界限 ; base GDTR.base ; limit GDTR.limit ; 2. 计算描述符地址 ; descriptor_address base (selector.index * 8) ; 3. 硬件检查1描述符是否越界 ; if (descriptor_address 7 limit) { ; trigger_gp_fault(); // 通用保护故障 ; } ; 4. 硬件检查2描述符类型是否有效 ; if (!descriptor.present) { ; trigger_np_fault(); // 段不存在故障 ; } ; 5. 硬件检查3权限检查 ; if (descriptor.dpl cpl descriptor.type ! conforming) { ; trigger_gp_fault(); ; } ; 这些检查在每条涉及段寄存器的指令中都会发生三、关键差异对比实例实例1创建大量对象Windows进程创建10000个事件对象// 这可以正常工作HANDLE handles[10000];for(inti0;i10000;i){handles[i]CreateEvent(NULL,FALSE,FALSE,NULL);// 句柄表自动扩展// 1. 初始512个条目// 2. 扩展到1024// 3. 扩展到2048// ... 直到足够}// 每个句柄只需24字节开销x86尝试创建10000个LDT描述符; 这不可能实现 ; 假设每个LDT描述符8字节 ; 10000个描述符需要80000字节 ; 但LDTR限制寄存器只有16位 ; 最大限长 0xFFFF 65535字节 ; 最大描述符数 65536 / 8 8191 ; 即使我们尝试 mov ax, 0x1000 ; 设置LDT选择子 lldt ax ; 加载LDT ; LDT描述符中的限长字段只有20位加上粒度 ; 实际最大段大小 0xFFFFF * 4096 4GB如果G1 ; 但这是段的大小不是LDT表的大小 ; LDT表本身的大小受限于 ; 1. LDTR中的16位限长 ; 2. 描述符中的20位限长如果LDT被当作段来访问实例2内存布局差异Windows句柄表内存布局进程A地址空间 0x00000000-0x0000FFFF: 预留 ... 0x7FFE0000-0x7FFE0FFF: 进程A句柄表一级表 [0]: 0xFFFFF80012345678 (指向File对象) [1]: 0xFFFFF80087654321 (指向Thread对象) ... 当需要更多句柄时 系统分配新页面0x7FFE1000-0x7FFE1FFF 更新句柄表指针指向新的两级结构x86 GDT内存布局物理内存布局 0x00001000-0x000017FF: GDT固定大小 [0]: 00000000 00000000 (空描述符) [1]: 00CF9A00 0000FFFF (代码段) [2]: 00CF9200 0000FFFF (数据段) ... [127]: 最后一个可用描述符 0x00001800: 无法扩展后面可能是其他重要数据 GDTR寄存器 Base: 0x00001000 Limit: 0x07FF (2047字节127个描述符) 要扩展必须 1. 在别处分配新内存0x00100000-0x00100FFF 2. 复制旧GDT 3. 暂停所有CPU 4. 更新所有CPU的GDTR 5. 祈祷没有竞争条件实例3实际系统参数Windows实际限制// Windows 10/11实际参数#defineHANDLE_TABLE_INITIAL_SIZE512// 初始句柄数#defineHANDLE_TABLE_MAX_SIZE0x400000// 每个进程最大句柄数(4194304)// 三级表结构参数#defineHANDLE_ENTRIES_PER_PAGE(PAGE_SIZE/sizeof(HANDLE_TABLE_ENTRY))#defineHANDLE_ENTRIES_PER_MID256// 每个中间表条目数#defineMID_TABLE_SIZE1024// 中间表大小#defineTOP_TABLE_SIZE1024// 顶级表大小// 最大理论句柄数 1024 * 1024 * 256 268,435,456// 实际限制远小于此但仍然是巨大的数字x86 GDT实际限制; x86硬件强制限制 ; 16位界限寄存器 → 最大表大小64KB ; 每个描述符8字节 → 最大8191个描述符 ; 实际操作系统使用情况 ; Linux: 通常32-64个描述符 ; - 内核代码/数据段 ; - 用户代码/数据段 ; - 每个CPU的TSS ; - APM段等 ; Windows x86: ; 0x0000: 空描述符 ; 0x0008: 内核代码段 ; 0x0010: 内核数据段 ; 0x0018: 用户代码段 ; 0x0020: 用户数据段 ; 0x0028: TSS段 ; 0x0030: LDT段如果使用 ; ... ; 总共通常不超过32个四、根本原因总结1.设计年代与目标不同GDT/LDT (1985)设计于16/32位时代硬件资源有限主要目标是内存分段保护Windows句柄表 (1993)设计于32/64位时代目标是为大量对象提供灵活引用2.硬件与软件实现差异// 硬件机制GDT的特点// 1. 硬件寄存器固定大小GDTR.limit 16位// 2. 每次内存访问都需要硬件检查// 3. 更改需要原子操作和所有CPU同步// 4. 硬件缓存描述符缓存使动态更改复杂// 软件机制句柄表的特点// 1. 纯数据结构大小由内存管理器决定// 2. 软件控制所有检查和转换// 3. 可按需分配惰性初始化// 4. 每个进程独立无需全局同步3.性能权衡; GDT访问性能 mov eax, [ebx] ; 1. 检查DS选择子 ; 2. 从GDTR.base DS.index*8读取描述符 ; 3. 验证权限和界限 ; 4. 计算线性地址 DS.base ebx ; 5. 通过分页转换为物理地址 ; 这些步骤很多被硬件并行化但仍有限制 ; 相比之下句柄解引用 Object ObReferenceObjectByHandle(Handle); // 1. 边界检查软件 // 2. 表查找可能多级 // 3. 权限检查软件 // 4. 引用计数增加 // 虽然软件实现更灵活但比硬件GDT查找慢4.现代发展// x86-64基本废弃了分段// 段基址强制为0除了FS/GS// 内存保护完全通过分页实现// Windows在x64上进一步优化句柄表typedefstruct_HANDLE_TABLE_FREE_LIST{ULONG_PTR FreeLists[1];}HANDLE_TABLE_FREE_LIST;// 使用低碎片堆LFH管理句柄分配// 支持锁消除等优化结论Windows句柄表能动态增长的核心原因是纯软件实现不受硬件寄存器限制多级表结构类似于虚拟内存分页支持稀疏分配按需分配初始小需要时扩展进程独立每个进程可以有不同的句柄表大小x86 GDT/LDT固定大小的核心原因是硬件强制由16位界限寄存器限制全局共享更改需要所有CPU同步硬件缓存描述符缓存在段寄存器中原子性要求GDT更新必须原子完成这种差异反映了计算机系统设计从硬件为中心到软件为中心的演进趋势。现代操作系统更倾向于使用灵活的软件机制而将硬件仅用于关键性能路径的加速。