背景
近期在学习ProcessHacker的源码,Process Hacker是一个免费的、功能强大的“任务管理器”,可用于监听系统资源的使用情况,调试软件以及检测恶意程序。使用中你会发现其可以与Sysinternals开发的Process Explorer相媲美。最重要的它是开源的,源码均可以在Github上查看,这使得我们有机会深入了解其实现原理和窥探一些重要的Windows系统接口。我的计划是结合《深入解析windows操作系统》这本书籍学习一些Windows系统原理的相关知识。
关于停运保护(Run-Down Protection)机制
关于停运保护(暂且这样翻译)的介绍,发哥我翻找了官方的资料,边理解边做了部分的翻译,如有错误或模棱两可之处,还请你高抬贵手帮忙指出:
实现细则
需要一个结构EX_RUNDOWN_REF用于追踪共享对象停运保护的状态,该结构内容是不透明的(也就是不对外开放的),停运保护机制的相关接口都以指向该结构的指针类型作为传入参数类型,该结构记录当前在共享对象上实施的停运保护的次数。
- 拥有者调用ExInitializeRundownProtection将共享对象绑定到EX_RUNDOWN_REF结构;
- 其他要访问的驱动使用EX_RUNDOWN_REF结构值调用 ExAcquireRundownProtection 和ExReleaseRundownProtection 来请求和释放针对该对象的停运保护;
- 拥有者调用ExWaitForRundownProtectionRelease 来等待对象被释放以此确保对象可以被安全地删除。
代码解析
摘自 phlib\include\phbasesup.h 文件
#define PH_RUNDOWN_ACTIVE 0x1
#define PH_RUNDOWN_REF_SHIFT 1
#define PH_RUNDOWN_REF_INC 0x2
typedef struct _PH_RUNDOWN_PROTECT
{
/*
1. 存储PH_RUNDOWN_WAIT_BLOCK类型变量的地址;
2. 停运保护是否激活的标志位
*/
ULONG_PTR Value;
} PH_RUNDOWN_PROTECT, *PPH_RUNDOWN_PROTECT;
#define PH_RUNDOWN_PROTECT_INIT { 0 }
typedef struct _PH_RUNDOWN_WAIT_BLOCK
{
/*共享对象的请求此处,表明共享对象正在被访问*/
ULONG_PTR Count;
/*
事件抛出表明所有对共享对象的访问已结束,
所有者发起的等待函数将返回,意味着接下来可以对共享对象进行删除或替换
*/
PH_EVENT WakeEvent;
} PH_RUNDOWN_WAIT_BLOCK, *PPH_RUNDOWN_WAIT_BLOCK;
摘自 phlib\sync.c 文件
VOID FASTCALL PhfInitializeRundownProtection(
_Out_ PPH_RUNDOWN_PROTECT Protection
)
{
Protection->Value = 0;
}
BOOLEAN FASTCALL PhfAcquireRundownProtection(
_Inout_ PPH_RUNDOWN_PROTECT Protection
)
{
ULONG_PTR value;
// Increment the reference count only if rundown has not started.
while (TRUE)
{
value = Protection->Value;
if (value & PH_RUNDOWN_ACTIVE)
return FALSE;
/*原子操作:对比后满足相等条件则进行赋值,函数返回目标参数的原有值*/
if ((ULONG_PTR)_InterlockedCompareExchangePointer(
(PVOID *)&Protection->Value,
/*每次请求对象共享则增加引用计数,每次都加2(PH_RUNDOWN_REF_INC)*/
(PVOID)(value + PH_RUNDOWN_REF_INC),
(PVOID)value
) == value)
return TRUE;
}
}
VOID FASTCALL PhfReleaseRundownProtection(
_Inout_ PPH_RUNDOWN_PROTECT Protection
)
{
ULONG_PTR value;
while (TRUE)
{
value = Protection->Value;
/*如果停运保护没被激活,value不可能为奇数,PH_RUNDOWN_ACTIVE的值为1*/
if (value & PH_RUNDOWN_ACTIVE)
{ /*停运保护已被激活*/
PPH_RUNDOWN_WAIT_BLOCK waitBlock;
// Since rundown is active, the reference count has been moved to the waiter's wait
// block. If we are the last user, we must wake up the waiter.
/*一旦停运保护激活后,Protection->Value将改变原有的意义,现在存储的是等待块的地址*/
waitBlock = (PPH_RUNDOWN_WAIT_BLOCK)(value & ~PH_RUNDOWN_ACTIVE);
if (_InterlockedDecrementPointer(&waitBlock->Count) == 0)
{
PhSetEvent(&waitBlock->WakeEvent);
}
break;
}
else
{
// Decrement the reference count normally.
if ((ULONG_PTR)_InterlockedCompareExchangePointer(
(PVOID *)&Protection->Value,
(PVOID)(value - PH_RUNDOWN_REF_INC),
(PVOID)value
) == value)
break;
}
}
}
VOID FASTCALL PhfWaitForRundownProtection(
_Inout_ PPH_RUNDOWN_PROTECT Protection
)
{
ULONG_PTR value;
ULONG_PTR count;
PH_RUNDOWN_WAIT_BLOCK waitBlock;
BOOLEAN waitBlockInitialized;
// Fast path. If the reference count is 0 or rundown has already been completed, return.
value = (ULONG_PTR)_InterlockedCompareExchangePointer(
(PVOID *)&Protection->Value,
(PVOID)PH_RUNDOWN_ACTIVE,
(PVOID)0
);
if (value == 0 || value == PH_RUNDOWN_ACTIVE)
return;
waitBlockInitialized = FALSE;
while (TRUE)
{
value = Protection->Value;
/*
向右移一位,有两个作用:
1. 消除 PH_RUNDOWN_ACTIVE 的影响;
2. 之前每次请求共享对象时都是加2,现在右移1位相当于除以2,得到的是真正的引用次数!
*/
count = value >> PH_RUNDOWN_REF_SHIFT;
// Initialize the wait block if necessary.
if (count != 0 && !waitBlockInitialized)
{
PhInitializeEvent(&waitBlock.WakeEvent);
waitBlockInitialized = TRUE;
}
// Save the existing reference count.
waitBlock.Count = count;
/*
为什么要不厌其烦地使用原子操作?
因为怕在执行此循环的每一条语句时有请求插入,改变Protection->Value的值
*/
if ((ULONG_PTR)_InterlockedCompareExchangePointer(
(PVOID *)&Protection->Value,
(PVOID)((ULONG_PTR)&waitBlock | PH_RUNDOWN_ACTIVE),
(PVOID)value
) == value)
{
/*有共享对象的访问还没结束,要等待,触发事件见 PhfReleaseRundownProtection 函数*/
if (count != 0)
PhWaitForEvent(&waitBlock.WakeEvent, NULL);
break;
}
}
}
总结
看别人的代码就像是在游历一个世界,阅读让批判思维和共情能力显得如此重要。这段代码看得出编码的人是花了心思进行多番重构的,可借鉴的点:
- 同一变量存储的值的意义切换;
- 原子操作Interlocked系列函数的使用;
- 看似简单的奇偶位标识。
通俗的讲,停运保护的机制就比如:一座博物馆,平日敞开大门供游客参观,现在突然说要装修,然后把大门关了,只准出不许入,而博物馆的人不能驱逐里面的游客游客,只能等着,直到所有在里面的游客都出去了,然后才能开始装修。