背景

近期在学习ProcessHacker的源码,Process Hacker是一个免费的、功能强大的任务管理器,可用于监听系统资源的使用情况,调试软件以及检测恶意程序。使用中你会发现其可以与Sysinternals开发的Process Explorer相媲美。最重要的它是开源的,源码均可以在Github上查看,这使得我们有机会深入了解其实现原理和窥探一些重要的Windows系统接口。我的计划是结合《深入解析windows操作系统》这本书籍学习一些Windows系统原理的相关知识。

关于停运保护(Run-Down Protection)机制

关于停运保护(暂且这样翻译)的介绍,发哥我翻找了官方的资料,边理解边做了部分的翻译,如有错误或模棱两可之处,还请你高抬贵手帮忙指出:

WindowsXP开始,内核驱动就支持停运保护机制。驱动通过停运机制可以安全地访问在系统内存中的对象,通常这些对象是由其他内核驱动创建和销毁的。

当对一个对象的所有访问操作已经完成并且不再允许其他新的操作请求,那么就可以将这个对象视为停运的。比如说一个共享对象可能需要被停运,这样的话它就可以被清理然后用新的对象替换它。

拥有共享对象的驱动允许其他驱动对该对象请求并实施停运保护机制。当停运保护生效时,除对象的所有者外,其他驱动可以访问该对象而不用担心在访问结束前该对象会被其所有者删除。在访问开始之前,要访问的驱动会提出对目标对象实施停运保护的请求。对于一个存活周期较长的对象来说,这类请求几乎都是被允许的。当访问结束时,执行访问的驱动会卸除之前对对象实施的停运保护。

常规的停运保护流程

要想共享一个对象,拥有该对象的驱动要调用ExInitializeRundownProtection函数以初始化停运保护机制,在这之后,其他要访问此对象的驱动就可以对其实施和撤销停运保护功能。

要访问共享对象的驱动通过调用ExAcquireRundownProtection函数来请求对该对象的停运保护,当访问结束后,驱动通过调用 ExReleaseRundownProtection 来取消停运保护。

如果对象拥有者打算删除共享对象,它将调用ExWaitForRundownProtectionRelease来等待对象停运。在这期间,驱动调用线程会被阻塞,该函数会一直等待直至在之前被允许的所有停运保护被释放,同时拒绝新的停运保护请求。直到最后一次的访问结束并且所有停运保护被释放后,ExWaitForRundownProtectionRelease方才返回,这时对象的拥有者就可以安全地删除该对象了。为了防止等待阻塞过长时间,访问对象的驱动线程在实施停运保护的过程中应避免出现延缓的情况。

适合使用场景

停运机制很适合用于那些经常有效可用但不知何时会突然被删除或替换的共享对象,访问共享对象数据的驱动或者是调用线程在对象被删除后需确保不再尝试访问该对象,否则这些非法访问可能会造成无法预料的行为后果比如数据损坏,更严重点甚至会出现系统崩溃。

举个例子,典型的病毒防御驱动在操作系统运行时需要长时间加载到内存中。运行期间,其他驱动会发送IO请求到防御驱动以访问驱动中的数据和函数,但有时驱动需要被卸载和更新,为避免驱动还在处理IO请求时过早地被卸载,在发送IO请求之前,一个内核组件如文件系统过滤管理器,可以请求停运保护,当IO请求完成后,停运保护被释放,这时再卸载和更新就安全了。

停运保护不支持串行访问共享对象,如果两个或两个以上的驱动同时对同一对象实施停运保护并且要求必须要串行访问的话,那么一些其他的防护措施比如说互斥锁就需要派上用场了。

相对于锁

停运保护是众多用于保证安全访问共享对象的方式之一,而另外一种方式是使用互斥软件锁。如果一个驱动需要访问一个已被其他驱动上锁的对象,那么前者必须要等待后者释放锁才可以对其进行访问。然而,请求和释放锁会造成性能上的瓶颈,并且会消耗大量的内存。如果使用不正确,锁可能还会对同时进行资源竞争的驱动造成死锁的局面,但为检测和避免死锁,往往也需要耗费大量的计算资源。

原文翻译自:MSDN官方原文链接

实现细则

需要一个结构EX_RUNDOWN_REF用于追踪共享对象停运保护的状态,该结构内容是不透明的(也就是不对外开放的),停运保护机制的相关接口都以指向该结构的指针类型作为传入参数类型,该结构记录当前在共享对象上实施的停运保护的次数。

  1. 拥有者调用ExInitializeRundownProtection将共享对象绑定到EX_RUNDOWN_REF结构;
  2. 其他要访问的驱动使用EX_RUNDOWN_REF结构值调用 ExAcquireRundownProtectionExReleaseRundownProtection 来请求和释放针对该对象的停运保护;
  3. 拥有者调用ExWaitForRundownProtectionRelease 来等待对象被释放以此确保对象可以被安全地删除。

代码解析

摘自 phlib\include\phbasesup.h 文件

#define PH_RUNDOWN_ACTIVE 0x1
#define PH_RUNDOWN_REF_SHIFT 1
#define PH_RUNDOWN_REF_INC 0x2typedef 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;}}
}

总结

看别人的代码就像是在游历一个世界,阅读让批判思维和共情能力显得如此重要。这段代码看得出编码的人是花了心思进行多番重构的,可借鉴的点:

  1. 同一变量存储的值的意义切换;
  2. 原子操作Interlocked系列函数的使用;
  3. 看似简单的奇偶位标识。

通俗的讲,停运保护的机制就比如:一座博物馆,平日敞开大门供游客参观,现在突然说要装修,然后把大门关了,只准出不许入,而博物馆的人不能驱逐里面的游客游客,只能等着,直到所有在里面的游客都出去了,然后才能开始装修

转载于:https://www.cnblogs.com/liaoguifa/p/9670868.html

同步下的资源互斥:停运保护(Run-Down Protection)机制相关推荐

  1. 访问WEB-INF下的资源

    WEB-INF下的资源是受保护的,我们无法通过http://localhost:8080/xmm/WEB-INF/index.jsp的方式直接访问WEB-INF下的资源,但是可以通过controlle ...

  2. Linux下多线程编程互斥锁和条件变量的简单使用

    Linux下的多线程遵循POSIX线程接口,称为pthread.编写Linux下的多线程程序,需要使用头文件pthread.h,链接时需要使用库libpthread.a.线程是进程的一个实体,是CPU ...

  3. Multi_thread--Linux下多线程编程互斥锁和条件变量的简单使用

    Linux下的多线程遵循POSIX线程接口,称为pthread.编写Linux下的多线程程序,需要使用头文件pthread.h,链接时需要使用库libpthread.a.线程是进程的一个实体,是CPU ...

  4. Oracle 11g新特性direct path read引发的系统停运故障诊断处理

    Oracle 11g新特性direct path read引发的系统停运故障诊断处理 黎俊杰 | 2016-07-28 14:37 声明:部分表名为了脱敏而用XX代替 1.故障现象 (1)一个业务系统 ...

  5. k1658停运到什么时候_2021年春运就要开始啦!快来看看各大快递的停运时间吧

    每年我们过了元旦以后,就离春运不远了,那么在大家的印象里我们的春运是什么样子的呢?在多多还是学生党的时候,是在外地上的大学,但是由于我们学校每年放假都比较早,所以小编当然是很少赶上春运的,那么今天就不 ...

  6. 酷狗歌曲资源最新版权保护和反爬机制

    本文写于2021年9月24日,目标状态也是这时期的状态,未来目标状态可能会发生变化. 酷狗歌曲资源最新版权保护和反爬机制 一.获取酷狗资源----后端机制 一.获取酷狗资源----前端机制 三.酷狗版 ...

  7. Liqui发布停运公告,熊市漫漫,中小交易所难逃“短命”厄运?

    北京时间1月28日,乌克兰交易平台Liqui(Liqui.io)在官网发布公告称,停止服务,关闭所有账户,并告知,用户可在30天内提取资产. 来源:https://liqui.io/ 据第三方行情数据 ...

  8. 快递春节停运时间表刷屏,假的!但或涨价10-20元

    中新网客户端北京1月18日电(张旭)"1月18日,快递公司就要放假了,要网购的赶紧下单!"这两天,不少人的朋友圈被"春节快递停运时间表"刷屏了. 网传快递停运时 ...

  9. k1075停运吗_怀化火车站(怀化火车停运最新消息)

    [关键词为空!怀车发布]列车停运信息发布 广铁集团怀化火车站官方公告:7月17日停运的列车有:K1075/8宁波-重庆北K807上海南-怀化K71/4上海南-重庆北K193/2广州-成都. 不是,老火 ...

最新文章

  1. [置顶] Android面试题目之四: 归并排序
  2. java同步list_Java集合--ArrayList出现同步问题的原因
  3. [转]学会使用DB2指令
  4. 5在ios上无法选取文件_无法在 Ubuntu 20.04 上安装 Deb 文件?这是你需要做的! | Linux 中国...
  5. 傅里叶变换性质证明卷积_图傅里叶变换
  6. 开发基于大数据平台的搜索引擎
  7. iso 绝对pe_深度 WinPE 4.2 维护光盘ISO(含U盘PE制作工具) 下载地址
  8. openGL细分着色器详解
  9. 什么是Automata(I): Web 3.0的最后一块拼图
  10. idea 编译器注释汉字变繁体字解决办法
  11. python课程回顾复习记录简要6
  12. 2022夏每日一题(三)
  13. 从vue文件中抽取出子组件的流程及过程中踩过的坑
  14. 软件测试师具备的素质_软件测试工程师的所需要的职业素养是什么?
  15. 从arduino板开始做到阿里云app控制--WeMos D1或ESP8266连接阿里云文章合集及源代码
  16. 斯坦福大学开发PETE装置吸收60%的太阳能
  17. ios客户端发现_世界杯送流量活动项目总结
  18. 中国科学院计算机所张浩,专家人才库数据----中国科学院计算技术研究所
  19. jstorm 读取mysql_jstorm集成kafka
  20. 计算机信息的安全威胁包含( ),计算机信息系统存在的主要安全威胁不包括()

热门文章

  1. PLC PLSY 指令
  2. “墙裂”推荐!PDF文档办公必备的四大功能
  3. win10聚焦功能失效的解决方法
  4. 农业遥感技术科研成果汇总
  5. 《测量助理》最新版本V3.0.221215发布更新
  6. Win10系统无法打开桌面的个性化设置、显示设置及任务栏设置等,处理方法及参考链接如下。
  7. SparkRDD函数详解
  8. 5G NR RLC层
  9. SpringBoot spring-data-jpa表的生成
  10. 小木虫论坛-学术科研互动平台 爬虫