驱动开发-windows驱动设计目标

这篇具有很好参考价值的文章主要介绍了驱动开发-windows驱动设计目标。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

驱动程序和应用程序不一样的,由于其直接运行于windows r0级,故对于开发有更多和更严格的标准,一般会有以下一些常见的设计目标:

安全性、可移植性、可配置性、 可被中断、多处理器安全、可重用 IRP、 支持异步 I/O这些是基本目标。

1. 安全性:

驱动程序是足够安全的,它在系统运行的任何时候,都可以执行安装、卸载、禁用、启用等操作,而不引起蓝屏(BlueScreen)、系统运行缓慢等异常问题。

保障安全性方法包括但不限于:

A. 正确的处理编译器的任何警告和错误;和应用层不一样,任何警告都可能包含着错误,在WinDDK 7600中,甚至专门给出了对于警告和错误的工具链,检测并给出如何修改的建议,在新版本中集成到IDE中的,但感觉WinDDK还是很好用的

B. 良好的编码习惯和风格: 每一种标准风格的背后,都有一大堆的经验教训,例如下面的风格:

BOOLEAN
FunctionName (
    IN PVOID                   DeviceExtension, 
    IN PMOUSE_INPUT_DATA       CurrentInput, 
    OUT POUTPUT_PACKET         CurrentOutput,
    IN UCHAR                   StatusByte,
    IN PUCHAR                  DataByte,
    OUT PBOOLEAN               ContinueProcessing,
    OUT PMOUSE_STATE           MouseState,
    OUT PMOUSE_RESET_SUBSTATE  ResetSubState
);

// 这种风格中,每一行都有特定的功能,并用IN和OUT表明参数是输入参数还是输出参数;作为对比我们看看
// 下面的风格

BOOLEAN FunctionName (PVOID DeviceExtension, PMOUSE_INPUT_DATA CurrentInput, POUTPUT_PACKET CurrentOutput, UCHAR StatusByte, PUCHAR DataByte, PBOOLEAN ContinueProcessing, PMOUSE_STATE MouseState, PMOUSE_RESET_SUBSTATE  ResetSubState);

// 对比发现上面风格非常清晰明确;

C. 注重对异常的检查和处理;根据实践的情况,和应用层是相反的,驱动层最好不要抛出异常,毕竟r9级别的异常可能带来非常多的问题,例如,如果我们使用物理地址拷贝数据时候,这时候越界很可能将毫不相关的进程写崩溃掉,此时系统很可能已经无法检测到的;

所有的异常最好是在当前函数中处理掉,故检测输入参数有效和确保输出参数有效非常有必要;

在没有必要的情况下,不要在内核中做一些骚操作,例如跑一个复杂的算法;

非常慎重对使用的内存进行处理;

大量的专业测试和问题调试;所有驱动的代码都需要经过HLK测试和认证,这可以避免一些异常;同时驱动尽可能留下日志,因为驱动有问题等同于系统有问题。

2. 可移植性

驱动程序应该支持所有 Windows 支持的硬件平台移植。 要实现跨平台可移植性,驱动程序开发中应该注意以下几点: 

使用C语言开发:内核模式驱动程序都应使用 C 编写,以便它们可以使用系统兼容的 C 编译器重新编译、重新链接并在不同的 Windows 平台上运行,而无需重写或替换任何代码。不能在内核模式驱动程序中使用许多 C++ 语言构造,因此使用C++要仔细评估。

驱动程序不应依赖于任何特定系统兼容的 C 编译器或 C 支持库的功能,代码应符合 ANSI C 标准,最好避免:

依赖于大小或布局因平台而异的数据类型。

调用维护状态的任何标准 C 运行时库函数。

调用操作系统为其提供替代支持例程的任何标准 C 运行时库函数。

使用 WDK 编程接口

每个Windows NT执行组件导出驱动程序和所有其他内核模式组件调用的一组内核模式驱动程序支持例程。 WDK 提供一组头文件,用于定义特定于系统的数据类型和常量,驱动程序需要保持从一个平台到另一个平台的可移植性。 所有内核模式驱动程序都包含一个主 WDK 内核模式头文件 Wdm.h 或 Ntddk.h。 在使用相应的编译器指令编译驱动程序时,主头文件不仅会引入系统提供的用于定义基本内核模式类型的宏,还会从任何特定于处理器体系结构的宏中拉取适当的选择。

如果驱动程序需要依赖于平台的定义,最好在 #ifdef 语句中隔离这些定义,以便针对相应的硬件平台编译和链接每个驱动程序。

在目前为止常见的架构是: x86、x64、IA64、Arm x86、Arm x64,我们最常用的还是x64架构;

3. 可中断/抢占

windows操作系统本身是可抢占的,它并非实时操作系统,可抢占意味着中断会按照一定的优先级来抢占,发生抢占时,低优先级的中断被挂起,待高优先级中断运行完成后,在恢复运行;

可中断设计的目标是最大限度地提高系统性能。 任何线程都可以被优先级较高的线程抢占,并且任何驱动程序的中断服务例程 (ISR) 都可以被以更高的中断请求级别运行的例程中断 (IRQL) 。

内核组件根据以下优先级条件之一确定代码序列的运行时间:

线程的内核定义的运行时优先级方案:

系统中的每个线程都有关联的优先级属性。 通常,大多数线程具有 可变 优先级属性:它们始终是抢占的,并计划与当前处于同一优先级的所有其他线程一起运行轮循机制。 某些线程具有 实时 优先级属性:这些时间关键型线程将运行到完成,除非它们被具有更高实时优先级属性的线程抢占。 Microsoft Windows 体系结构不提供固有的实时系统。

无论其优先级属性如何,在发生硬件中断和某些类型的软件中断时,系统中的任何线程都可以被抢占。

内核定义的 中断请求级别 (IRQL):

内核确定硬件和软件中断的优先级,以便某些内核模式代码在更高的 IRQL 下运行,从而使其具有高于系统中其他线程的计划优先级。 执行内核模式驱动程序代码的特定 IRQL 由其基础设备 的硬件优先级确定。

内核模式代码始终是可中断的:具有较高 IRQL 值的中断随时可能发生,从而导致具有更高系统分配 IRQL 的另一段内核模式代码立即在该处理器上运行。 但是,当一段代码在给定 IRQL 中运行时,内核会屏蔽处理器上 IRQL 值较小或相等的所有中断向量。

最低 IRQL 级别称为 PASSIVE_LEVEL。 在此级别,不会屏蔽任何中断向量。 线程通常以 IRQL=PASSIVE_LEVEL 运行。

软件中断中下一个更高的 IRQL 级别适用于软件中断。 这些级别包括APC_LEVEL、DISPATCH_LEVEL或内核调试WAKE_LEVEL。

硬件中断中设备中断的 IRQL 值仍然较高。 内核保留系统关键中断(例如来自系统时钟或总线错误)的最高 IRQL 值。

驱动程序中的每个例程都是可中断的。 这包括以高于 PASSIVE_LEVEL 的 IRQL 运行的任何例程。 仅在运行某个特定 IRQL 时未发生更高 IRQL 中断的情况下,在特定 IRQL 上运行的任何例程才保留对处理器的控制。

在 Windows 中,所有线程都具有线程上下文。 此上下文包含标识拥有线程的进程的信息以及其他特征,例如线程的访问权限。

通常,在请求驱动程序的当前 I/O 操作的线程上下文中,仅调用最高级别驱动程序。 中间级别或最低级别驱动程序永远不能假定它在请求其当前 I/O 操作的线程的上下文中执行。

因此,驱动程序例程通常在 任意线程上下文中执行 -- 调用标准驱动程序例程时,任何线程的上下文都是最新的。 出于性能原因(避免上下文切换),很少有驱动程序会设置自己的线程。

4. 多处理器

基于 Microsoft Windows NT 的操作系统设计为在单处理器和对称多处理器 (SMP) 平台上统一运行,内核模式驱动程序应设计为同样地运行。

在任何 Windows 多处理器平台中,都存在以下条件:

所有 CPU 都是相同的,所有或所有处理器都必须具有相同的协处理器。

所有 CPU 共享内存,并统一访问内存。

在 对称 平台中,每个 CPU 都可以访问内存、中断和访问 I/O 控制寄存器。 (相比之下,在 非对称 多处理器计算机中,一个 CPU 会接受一组从属 CPU 的所有中断。)

若要在 SMP 平台上安全运行,操作系统必须确保在一个处理器上执行的代码不会同时访问和修改另一个处理器正在访问和修改的数据。 

此外,在单处理器计算机中序列化的驱动程序的 I/O 操作可以在 SMP 计算机中重叠。 也就是说,处理传入 I/O 请求的驱动程序例程可以在一个处理器上执行,而与设备通信的另一个例程在另一个处理器上并发执行。 无论内核模式驱动程序是在单处理器还是对称多处理器计算机上执行,它们都必须同步对驱动程序例程之间共享的任何驱动程序定义数据或系统提供资源的访问,并同步对物理设备的访问。

Windows NT内核使用称为自旋锁的同步机制,驱动程序可以使用该机制保护共享数据 (或设备寄存器) ,避免在对称多处理器平台上并发运行的一个或多个例程同时访问。 内核强制实施两个有关使用旋转锁的策略:

在任何给定时刻,只有一个例程可以持有特定的旋转锁。 在访问共享数据之前,必须引用数据的每个例程必须首先尝试获取数据的旋转锁。 若要访问相同的数据,另一个例程必须获取旋转锁,但在当前持有者释放旋转锁之前,无法获取旋转锁。

内核将 IRQL 值分配给系统中的每个旋转锁。 内核模式例程仅当在旋转锁的分配 IRQL 上运行该例程时,才能获取特定的旋转锁。

这些策略阻止通常以较低 IRQL 运行但当前持有旋转锁的驱动程序例程被尝试获取相同旋转锁的较高优先级驱动程序例程抢占。 因此,可以避免死锁。

分配给旋转锁的 IRQL 通常是可以获取旋转锁的最高 IRQL 例程的 IRQL。

5. IRP可重用

IRP是驱动程序工作的核心,驱动程序的一切工作基本都是围绕IRP进行,应用层往往使用IRP来控制设备的正常工作,同时I/O 管理器、PNP管理器和电源管理器也会使用 I/O 请求数据包 (IRP) 与内核模式驱动程序通信,同时windows允许驱动程序之间相互通信(基于设备树的结构保证了驱动程序可以从任何一个叶子结点遍历整个内核中所有设备,在这里设备是一个抽象概念,不仅仅包含实际硬件设备)。

IRP创建之初就考虑可重用,本身IRP就是从应用层切换到内核层时候封装的上层请求,故IRP会在不同的驱动和应用程序之间共同使用,它们看起来像下面这样:

驱动开发-windows驱动设计目标,windows内核,驱动开发,windows

IRP可以由系统服务函数或者内核驱动创建,它被传递给驱动程序,驱动程序可以自行决定如何处理它,同时IRP也属于I/O管理器的重要部分,I/O 管理器通过IRP来管理应用程序和设备驱动程序之间的通信;

一个IRP通常会有下面几种处理方式: 

完成这个IRP;

创建新的IRP,并向下传递以完成这个IRP;

复用当前IRP,并向下传递IRP;此时我们可以在IRP上挂载一个IRP完成例程,这样当IRP在下层被完成时,我们的驱动也会得到通知;

6. 支持异步I/O请求

异步I/O对于系统性能的提升非常巨大,非常建议驱动程序提供异步 I/O 支持,以便 I/O 请求的发起方可以继续执行,而不是等待其 I/O 请求完成。 异步 I/O 支持可提高发出 I/O 请求的系统吞吐量和性能。

使用异步I/O主要是带来了系统的性能提升,和应用层不一样,驱动程序考虑系统性能的影响非常必要且重要,异步I/O意味着:

驱动程序不一定按照发送的相同顺序处理 I/O 请求,驱动程序可以在收到 I/O 请求时重新排序;

驱动程序可以将一个数据传输请求拆分为N个传输请求;

驱动程序也可以重叠 I/O 请求处理,尤其是在对称多处理器平台中,这样对性能提高非常有效;

内核模式驱动程序对单个 I/O 请求的处理不一定是序列化的,当驱动程序在开始处理下一个传入 I/O 请求之前,不一定处理每个 IRP 以完成;

驱动程序可以在设备对象的设备扩展中维护有关其当前 I/O 操作的状态信息;

和应用程序不一样的是,驱动程序只有“好”和“更好”两种状态,当代码在R0级运行时,任何细小的错误都会影响整个系统,所以如果仅仅是可以用,那么这个驱动程序很可能带来灾难。文章来源地址https://www.toymoban.com/news/detail-856387.html

到了这里,关于驱动开发-windows驱动设计目标的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux驱动开发——内核模块

    目录 内核模块的由来 第一个内核模块程序  内核模块工具  将多个源文件编译生成一个内核模块  内核模块参数 内核模块依赖 关于内核模块的进一步讨论  习题 最近一直在玩那些其它的技术,眼看快暑假了,我决定夯实一下我的驱动方面的技能,迎接我的实习,找了一本

    2024年02月04日
    浏览(83)
  • linux内核网络驱动框架(linux驱动开发篇)

    网络驱动的核心: 1、就是初始化 net_device 结构体中的各个成员变量, 2、然后将初始化完成以后的 net_device 注册到 Linux 内核中 1、网络设备(用net_device结构体) 2、网络设备的操作集( net_device_ops结构体 ) 3、sk_buff结构体 网络是分层的,对于应用层而言不用关系具体的底层是

    2023年04月08日
    浏览(80)
  • 驱动开发:内核遍历文件或目录

    在笔者前一篇文章 《驱动开发:内核文件读写系列函数》 简单的介绍了内核中如何对文件进行基本的读写操作,本章我们将实现内核下遍历文件或目录这一功能,该功能的实现需要依赖于 ZwQueryDirectoryFile 这个内核API函数来实现,该函数可返回给定文件句柄指定的目录中文件

    2024年02月08日
    浏览(83)
  • 驱动开发:内核文件读写系列函数

    在应用层下的文件操作只需要调用微软应用层下的 API 函数及 C库 标准函数即可,而如果在内核中读写文件则应用层的API显然是无法被使用的,内核层需要使用内核专有API,某些应用层下的API只需要增加Zw开头即可在内核中使用,例如本章要讲解的文件与目录操作相关函数,多

    2024年02月08日
    浏览(46)
  • 驱动开发:内核ShellCode线程注入

    还记得 《驱动开发:内核LoadLibrary实现DLL注入》 中所使用的注入技术吗,我们通过 RtlCreateUserThread 函数调用实现了注入DLL到应用层并执行,本章将继续探索一个简单的问题,如何注入 ShellCode 代码实现反弹Shell,这里需要注意一般情况下 RtlCreateUserThread 需要传入两个最重要的

    2024年02月08日
    浏览(52)
  • 驱动开发:摘除InlineHook内核钩子

    在笔者上一篇文章 《驱动开发:内核层InlineHook挂钩函数》 中介绍了通过替换 函数 头部代码的方式实现 Hook 挂钩,对于ARK工具来说实现扫描与摘除 InlineHook 钩子也是最基本的功能,此类功能的实现一般可在应用层进行,而驱动层只需要保留一个 读写字节 的函数即可,将复杂

    2024年02月10日
    浏览(38)
  • 驱动开发:内核物理内存寻址读写

    在某些时候我们需要读写的进程可能存在虚拟内存保护机制,在该机制下用户的 CR3 以及 MDL 读写将直接失效,从而导致无法读取到正确的数据,本章我们将继续研究如何实现物理级别的寻址读写。 首先,驱动中的物理页读写是指在驱动中直接读写物理内存页(而不是虚拟内

    2024年02月11日
    浏览(44)
  • 驱动开发:内核读写内存多级偏移

    让我们继续在 《内核读写内存浮点数》 的基础之上做一个简单的延申,如何实现多级偏移读写,其实很简单,读写函数无需改变,只是在读写之前提前做好计算工作,以此来得到一个内存偏移值,并通过调用内存写入原函数实现写出数据的目的。 以读取偏移内存为例,如下

    2024年02月11日
    浏览(39)
  • 驱动开发:内核读写内存浮点数

    如前所述,在前几章内容中笔者简单介绍了 内存读写 的基本实现方式,这其中包括了 CR3切换 读写, MDL映射 读写, 内存拷贝 读写,本章将在如前所述的读写函数进一步封装,并以此来实现驱动读写内存浮点数的目的。内存 浮点数 的读写依赖于 读写内存字节 的实现,因为

    2024年02月06日
    浏览(55)
  • 驱动开发:内核远程堆分配与销毁

    在开始学习内核内存读写篇之前,我们先来实现一个简单的内存分配销毁堆的功能,在内核空间内用户依然可以动态的申请与销毁一段可控的堆空间,一般而言内核中提供了 ZwAllocateVirtualMemory 这个函数用于专门分配虚拟空间,而与之相对应的则是 ZwFreeVirtualMemory 此函数则用于

    2024年02月04日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包