ReactOS 图形系统分析(20):引擎信号量 --- EngSemaphore(semaphor.c)
1. 概述
semaphor.c 位于 win32ss/gdi/eng/semaphor.c,实现 GDI 引擎的同步原语族(HSEMAPHORE) ------EngCreateSemaphore / EngAcquireSemaphore / EngReleaseSemaphore 等一组 DDI 服务。它是引擎与显示驱动共享的互斥/共享锁接口。
本质: 这里的"信号量"不是计数信号量 ,而是 ERESOURCE(内核资源对象)的轻量封装 ------支持排他(写)/共享(读)两种获取模式,并天然支持递归持有(同线程多次获取不自我死锁)。配合**关键区(Critical Region)**使用,保证锁持有期间普通内核 APC 不会打断(避免"锁被中断持有着替换")。
┌─────────────────────────────────────────────────────────────┐
│ 使用方 │
│ eng/device.c ghsemGraphicsDeviceList(图形设备链表锁) │
│ eng/ldevobj.c ghsemLDEVList(逻辑设备链表锁) │
│ eng/pdevobj.c ghsemPDEV + ppdev->hsemDevLock(每物理设备)│
│ ntgdi/dclife.c / print.c / eng/mouse.c(hsemDevLock 使用) │
│ reactx/dxg, ntddraw(DirectX 全局句柄表锁 ghsemHmgr 等) │
├─────────────────────────────────────────────────────────────┤
│ gdi/eng/semaphor.c(本分析对象) │
│ HSEMAPHORE = PERESOURCE(ERESOURCE*,不透明句柄) │
│ ├── EngCreateSemaphore / EngDeleteSemaphore │
│ ├── EngAcquireSemaphore(排他)/ AcquireSemaphoreShared(共享)│
│ ├── EngReleaseSemaphore │
│ ├── EngIsSemaphoreOwned / OwnedByCurrentThread │
│ └── EngInitializeSafeSemaphore / DeleteSafeSemaphore │
│ (ENGSAFESEMAPHORE:延迟初始化 + 引用计数的安全封装) │
├─────────────────────────────────────────────────────────────┤
│ 底层:ntoskrnl ERESOURCE(ExInitializeResourceLite 等)+ │
│ ExEnterCriticalRegion / KeEnterCriticalRegion │
└─────────────────────────────────────────────────────────────┘
2. 核心函数实现
2.1 EngCreateSemaphore --- 创建(分配 + 初始化 ERESOURCE)
c
HSEMAPHORE EngCreateSemaphore(VOID)
{
PERESOURCE psem = ExAllocatePoolWithTag(NonPagedPool,
sizeof(ERESOURCE),
GDITAG_SEMAPHORE); // 非分页池!
if (!psem) return NULL;
if (!NT_SUCCESS(ExInitializeResourceLite(psem))) { // 初始化资源
ExFreePoolWithTag(psem, GDITAG_SEMAPHORE);
return NULL;
}
return (HSEMAPHORE)psem; // 句柄 = 资源指针
}
关键点: 分配于 NonPagedPool (锁代码路径不允许分页缺页);HSEMAPHORE 实际就是 ERESOURCE*,只是对外不透明。EngDeleteSemaphore 逆操作:ExDeleteResourceLite + 释放池。
2.2 获取 / 释放
c
VOID EngAcquireSemaphore(HSEMAPHORE hsem) // 排他(写锁)
{
if (hsem == NULL) return; // Windows 语义:NULL 忽略
ExEnterCriticalRegionAndAcquireResourceExclusive((PERESOURCE)hsem);
W32Thread = PsGetThreadWin32Thread(PsGetCurrentThread());
if (W32Thread) W32Thread->dwEngAcquireCount++; // 引擎锁嵌套计数统计
}
VOID EngAcquireSemaphoreShared(HSEMAPHORE hsem) // 共享(读锁)
{
ExEnterCriticalRegionAndAcquireResourceShared((PERESOURCE)hsem);
if (pti) ++pti->dwEngAcquireCount;
}
VOID EngReleaseSemaphore(HSEMAPHORE hsem)
{
if (hsem == NULL) return;
if (W32Thread) --W32Thread->dwEngAcquireCount;
ExReleaseResourceAndLeaveCriticalRegion((PERESOURCE)hsem);
}
设计要点:
- Critical Region :
ExEnterCriticalRegion.../...AndLeaveCriticalRegion把"进入关键区"和"获取/释放资源"合成一步。关键区内普通 APC 被屏蔽,保证线程持锁期间状态不被异步打断。 - 递归安全 :ERESOURCE 允许同线程递归获取(每次 Release 减递归计数),引擎多处嵌套持有同一把
hsemDevLock依赖此特性。 - 共享模式 :
EngAcquireSemaphoreShared让多个读者并发进入(设备表面属性读取场景),写者互斥------类似读写锁。 dwEngAcquireCount(win32.h(file:///d:/reactos/win32ss/user/ntuser/win32.h#L68) 中 THREADINFO 字段):跟踪当前线程持有引擎锁的嵌套深度,为未来的死锁/锁泄漏诊断预留(当前仅计数、未消费)。
2.3 查询函数
| 函数 | 实现 | 语义 |
|---|---|---|
EngIsSemaphoreOwned |
ActiveCount > 0 |
是否被(任意线程)持有 |
EngIsSemaphoreOwnedByCurrentThread |
ExIsResourceAcquiredExclusiveLite |
当前线程是否持有排他锁(递归检测用) |
2.4 ENGSAFESEMAPHORE --- 安全信号量(winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h#L559-L562))
c
typedef struct _ENGSAFESEMAPHORE {
HSEMAPHORE hsem;
LONG lCount; // 引用计数(初次使用线程数)
} ENGSAFESEMAPHORE;
c
BOOL EngInitializeSafeSemaphore(ENGSAFESEMAPHORE *Semaphore)
{
if (InterlockedIncrement(&Semaphore->lCount) == 1) // 第一个使用者
{
hSem = EngCreateSemaphore(); // 才真正创建
if (hSem == 0) { InterlockedDecrement(...); return FALSE; }
InterlockedExchangePointer(&Semaphore->hsem, hSem); // 发布
}
else
{
/* 其他线程等待创建者完成初始化 */
while (Semaphore->hsem == NULL); // 忙等
}
return TRUE;
}
用途: 驱动结构体里内嵌的"可能同时被多线程首次访问"的延迟初始化锁------第一次使用时创建,后续使用只增加引用计数。EngDeleteSafeSemaphore 对称地 InterlockedDecrement,归零才删除。
已知缺陷(源码 FIXME 标注): 用 InterlockedExchangePointer 而非 InterlockedCompareExchangePointer------若两线程同时首次进入,可能创建两个 ERESOURCE,后写的覆盖先写的(泄漏一个)。正确写法应 CAS 校验后释放失败者。这是 ReactOS 明确记录的待修复点。
3. 使用场景与全局锁清单
| 全局/每对象锁 | 创建位置 | 保护对象 |
|---|---|---|
ghsemGraphicsDeviceList |
device.c(file:///d:/reactos/win32ss/gdi/eng/device.c#L28) | 图形设备链表(EngpRegisterGraphicsDevice 增删/枚举) |
ghsemLDEVList |
ldevobj.c(file:///d:/reactos/win32ss/gdi/eng/ldevobj.c#L38) | 逻辑设备(LDEVOBJ)链表 |
ghsemPDEV |
pdevobj.c(file:///d:/reactos/win32ss/gdi/eng/pdevobj.c#L33) | 物理设备(PDEV)全局状态 |
ppdev->hsemDevLock |
pdevobj.c(file:///d:/reactos/win32ss/gdi/eng/pdevobj.c#L73) | 单个物理设备:模式切换、表面、DC 关联(最常用) |
ghsemShareDevLock |
ntddraw/dxeng.c(file:///d:/reactos/win32ss/reactx/ntddraw/dxeng.c#L182) | DirectDraw 共享设备锁 |
ghsemHmgr |
dxg/ddhmg.c(file:///d:/reactos/win32ss/reactx/dxg/ddhmg.c#L57) | DirectX 全局句柄管理器 |
典型调用链(设备锁):
NtGdiXXX (操作设备表面)
↓
ntgdi/dclife.c / print.c / eng/mouse.c
↓ EngAcquireSemaphore(ppdev->hsemDevLock)
访问/修改 PDEV 状态(模式、表面关联、指针)
↓ EngReleaseSemaphore(ppdev->hsemDevLock)
4. 导出与消费路径
4.1 win32k.spec 导出(win32k.spec(file:///d:/reactos/win32ss/win32k.spec))
@ stdcall EngAcquireSemaphore(ptr) # L8
@ stdcall EngCreateSemaphore() # L30
@ stdcall EngDeleteSafeSemaphore(ptr) # L40
@ stdcall EngDeleteSemaphore(ptr) # L41
@ stdcall EngInitializeSafeSemaphore(ptr) # L79
@ stdcall EngIsSemaphoreOwned(ptr) # L80
@ stdcall EngIsSemaphoreOwnedByCurrentThread(ptr) # L81
@ stdcall EngReleaseSemaphore(ptr) # L115
(注:EngAcquireSemaphoreShared 未导出------仅供引擎内部使用,声明在 eng.h(file:///d:/reactos/win32ss/gdi/eng/eng.h#L10-L17)。)
4.2 DXGTHK 重新导出(用户态 DirectX 驱动)
dxgthk.spec(file:///d:/reactos/win32ss/reactx/dxgthk/dxgthk.spec) 把 EngAcquireSemaphore / EngCreateSemaphore / EngReleaseSemaphore / EngDeleteSemaphore 从 win32k.sys 重新导出到 dxgthk.dll------用户态 DirectX 驱动(如显示驱动 thunk)可通过 dxgthk 调用内核锁。
5. 与《分析_4》标注对照
| 标注 | 核对 |
|---|---|
| 《_4》第 40 行:semaphor.c =「信号量:EngAcquireSemaphore」 | 准确。补充:完整 8 个函数族;基于 ERESOURCE 的排他/共享"资源锁"而非计数信号量;EngAcquireSemaphoreShared 仅供内部 |
6. 源码索引
| 文件 | 关键内容 |
|---|---|
| semaphor.c(file:///d:/reactos/win32ss/gdi/eng/semaphor.c) | EngCreate/Delete/Acquire/Release/IsOwned/IsOwnedByCurrentThread、AcquireSemaphoreShared、SafeSemaphore 初始化/删除 |
| eng.h(file:///d:/reactos/win32ss/gdi/eng/eng.h#L10-L17) | EngAcquireSemaphoreShared 内部声明 |
| winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h#L559-L562) | ENGSAFESEMAPHORE 结构 |
| device.c(file:///d:/reactos/win32ss/gdi/eng/device.c#L28) | ghsemGraphicsDeviceList |
| ldevobj.c(file:///d:/reactos/win32ss/gdi/eng/ldevobj.c#L38) | ghsemLDEVList |
| pdevobj.c(file:///d:/reactos/win32ss/gdi/eng/pdevobj.c#L33-L95) | ghsemPDEV、ppdev->hsemDevLock 创建/删除 |
| win32.h(file:///d:/reactos/win32ss/user/ntuser/win32.h#L68) | THREADINFO.dwEngAcquireCount |
| dxgthk.spec(file:///d:/reactos/win32ss/reactx/dxgthk/dxgthk.spec) | 向用户态重新导出 Eng* 信号量 |
关联模块:
- 底层机制:ntoskrnl ERESOURCE / Critical Region
- 主要使用者:eng/device.c(file:///d:/reactos/win32ss/gdi/eng/device.c)、eng/ldevobj.c(file:///d:/reactos/win32ss/gdi/eng/ldevobj.c)、eng/pdevobj.c(file:///d:/reactos/win32ss/gdi/eng/pdevobj.c)、reactx/(file:///d:/reactos/win32ss/reactx/)
- 锁保护对象:图形设备链表 / LDEV / PDEV(见《分析_4》2.3 节设备对象体系)
本文档基于 ReactOS 源代码
win32ss/gdi/eng/semaphor.c及关联模块分析(2026 年 8 月)