- Windows内核编程
- 谭文
- 1387字
- 2020-08-27 14:21:26
6.4 独立性与最小化原则
在内核编程中,有时候需要借助一些第三方或系统提供的现成机制来完成自身逻辑,如系统提供的一些IO队列、工作队列等。
这些机制使用起来确实非常方便,但是根据笔者的经验,过度依赖这些机制,会使内核程序行为变得不可控。
下面以工作队列为例子,“WorkItem”在内核编程中非常常见,如果内核驱动程序需要延时处理一些逻辑,这些逻辑可以放到WorkItem中处理,常用的WorkItem的函数有IoAllocateWorkItem/ IoQueueWorkItem,读者可以通过WDK帮助文档查阅WorkItem的相关信息。
对于WorkItem来说,每个WorkItem对应一个回调函数指针,系统线程池的线程会逐个调用WorkItem中的回调函数。考虑这样一个情况:如果当前驱动的WorkItem,对应回调函数中的逻辑非常复杂,或者涉及IO耗时操作,一旦这种WorkItem插入到工作队列后,会导致回调函数执行时间过长,影响到其他驱动的WorkItem执行时机,反过来,如果其他驱动编写的WorkItem回调函数执行时间过长,也会影响自身驱动的WorkItem执行时机,这种情况是不可控的。
作为一名经验丰富的内核驱动开发人员,应该要尽可能考虑到外界因素对自身驱动的影响。在方案选型时,如果选择了系统提供的机制,不妨从可靠性、稳定性、可替代性等几个方面来对方案进行评估。对于上面WorkItem的例子,在可靠性方面来看,WorkItem并非一个100%可靠的方案,开发者其实可以通过自己创建系统线程来执行需要延迟处理的逻辑。
请记住,驱动程序对外界依赖越大,所带来的风险也越高,不可控因素也越多。这就是提倡内核驱动“独立性”的原因。
下面介绍驱动的“最小化原则”。所谓最小化原则,具体是指内核驱动使用最少的资源来完成自身逻辑。
“最小化原则”提出的原因在于内核中的一些资源相当珍贵,如非分页内存。从前面的章节介绍中可知,在IRQL处于DISPATCH_LEVEL或更高级别的时候,只能访问非分页内存,很多开发者为了简单起见,在所有需要申请内存的地方都使用了非分页内存,这样做的好处是开发者可以不用担心IRQL的问题,坏处是造成了非分页内存浪费,在一些可以使用分页内存的地方使用了非分页内存,一旦内存紧张,很有可能导致非分页内存申请失败,最终导致逻辑处理失败。所以这个做法加大了逻辑处理的失败率。
下面再举一个例子,线程同步在内核开发中也非常常见,常用的同步方式有:自旋锁、事件对象、资源锁等。不同的同步机制所适合的场景、对应的IRQL不同,但自旋锁适用于任何一种场景。由于自旋锁使用简单,很多初学者不管在什么场景,一律使用自旋锁,这虽然是一个可行的方案,但是对于自旋锁来说,加锁后,IRQL会提升到DISPATCH_LEVEL,在这个IRQL,当前CPU被独占,不能调度其他线程,从这个角度来看,对系统性能的影响是巨大的,也违背了“最小化原则”。所以,开发者应该根据不同的IRQL要求,选择可用的同步方案,做到最小性能牺牲。
关于“最小化原则”,最后要介绍的是自身逻辑“最小化”,请读者考虑这样一个场景:系统处于低资源或者中毒状态,自身驱动模块加载时,会遇到一些操作失败的情况,如创建线程失败、写入文件失败等。如果驱动模块遇到一个“小错误”就认为全部执行失败,明显是不合理的。开发者应该对驱动的逻辑进行分类,标识出哪些逻辑是“关键逻辑”,哪些逻辑是“非关键逻辑”,“非关键逻辑”的失败不影响整体驱动执行流程,只有当“关键逻辑”失败时,整体驱动才宣告失败。这样的做法可以保证驱动能够在恶劣的环境下,最大程度地完成逻辑处理。这就是所谓的逻辑最小化。
独立性与最小化原则就介绍到这里,请读者仔细体会,在编写代码时,不妨多思考下,是否已经遵守了上述两个原则。