Windows 设备驱动程序






3.94/5 (7投票s)
Windows 设备驱动程序
1. 引言
Windows 驱动程序模型为在 Windows 98/Me 和 Windows 2000/XP 两个操作系统中运行的设备驱动程序提供了一个框架。在本报告中,我将讨论与 Windows XP 相关的设备驱动程序编程方面。XP 运行的每个平台都支持两种执行模式:用户模式和内核模式。用户模式程序调用 ReadFile
等任何函数的 API,该 API 实现为 KERNEL32.DLL 等子系统模块。然后,该子系统会再次调用本机 API(如 NtReadFile
)中的函数。有许多例程的功能与 NtReadFile
类似,它们在内核模式下运行,以响应与设备交互的请求。用户模式程序不必实现这些函数,它们只需创建一个称为 I/O 请求包 (IRP) 的数据结构,然后将其传递给设备驱动程序中的入口点。在上述 ReadFile
调用的情况下,NtReadFile
会创建一个带有主函数代码 IRP_MJ_READ
(设备驱动程序开发工具包头文件中的一个常量)的 IRP。设备驱动程序可能需要访问其硬件来执行 IRP。驱动程序完成 I/O 操作后,通过调用特定的内核模式服务例程来完成 IRP。
下面的图显示了 Windows XP 中不同类型的设备驱动程序。
虚拟设备驱动程序 (VDD) 允许 MS DOS 应用程序访问 Intel x86 上的硬件。内核模式驱动程序有许多子类别。即插即用 (PnP) 驱动程序是即插即用驱动程序,WDM 是理解电源管理协议的 PnP 驱动程序。文件系统驱动程序在硬盘上实现文件系统,而旧式驱动程序是直接控制设备而无需任何其他设备驱动程序的内核模式驱动程序。对于大多数设备,您需要编写 WDM 驱动程序,因为 Microsoft 不提供对它们的官方支持。在下一节中,我将描述 WDM 设备驱动程序的结构。
2. WDM 驱动程序的结构
驱动程序是操作系统调用的一组函数,用于执行与硬件相关的各种操作。下图显示了一些操作。
上面的一些函数,如 DriverEntry
、AddDevice
和某些调度例程,通常存在于每个设备驱动程序中。执行内存访问的驱动程序将具有 AdaptorControl
例程。产生硬件中断的设备的驱动程序将具有中断服务例程和延迟过程调用例程。就像“C”或“C++”可执行文件一样,设备驱动程序也是可执行文件,其文件扩展名为 .SYS。驱动程序还可以包含一些支持库,其中包含驱动程序所需的函数。它还可以包含调试信息和资源数据。驱动程序没有像 C 程序那样的“main”函数,而是构成系统可以调用的子例程集合。操作系统在调用设备驱动程序中的函数时通常遵循以下步骤:
- 当设备插入系统时,系统会将驱动程序加载到虚拟内存中并调用
DriverEntry
例程。此例程然后执行一些操作并返回。 - PnP 管理器然后调用
AddDevice
函数并返回。 - PnP 管理器然后发送一些 IRP,调度函数处理每个 IRP 并返回。
- 应用程序打开设备句柄,系统会发送另一个 IRP,然后调度例程执行一些工作并返回。
- 应用程序然后尝试读取数据,系统发送一个 IRP,调度例程将该 IRP放入队列并返回。
- I/O 操作通过向驱动程序连接的硬件中断发出信号来完成。您的中断例程执行少量工作,安排一个 DPC 并返回。
- 您的 DPC 例程运行,它从上面的队列中移除安排的 IRP,并对硬件进行编程以读取数据,然后返回。
- 系统向驱动程序发出更多调用,最后当用户拔出设备时,PnP 管理器发送 IRP,您处理并返回。然后操作系统调用
DriverUnload
例程,该例程执行一些工作并返回。
与 C 程序不同,驱动程序不创建新线程,而是执行在当时活动的任何线程的上下文中。系统并非总是执行任意线程中的驱动程序代码,驱动程序可以通过调用 PsCreateSystemThread
来创建自己的系统线程。了解线程上下文有时是必要的,例如驱动程序不应阻塞任意线程,因为它阻塞任何线程是不公平的,另一个原因是当驱动程序创建 IRP 发送给其他驱动程序时,您需要在任意线程中创建 IRP,但您可能会在非任意线程中创建另一种类型的 IRP。I/O 管理器将同步类型的 IRP 与您创建 IRP 的线程关联起来。如果线程终止,它将自动取消 IRP,因此系统仅因该线程终止而取消 IRP 是不正确的。
系统通过其电子签名检测即插即用设备,系统可以检测到该签名。对于即插即用设备,系统总线驱动程序会检测硬件是否存在并读取签名以确定硬件类型。旧式驱动程序没有签名,因此用户必须通过启动“添加新硬件”向导来启动检测过程。
在 Windows 驱动程序模型中,每个硬件设备至少有两个驱动程序。其中一个驱动程序称为函数驱动程序,它负责几乎所有的工作,如硬件工作、I/O 操作、处理中断和控制设备。另一个驱动程序是总线驱动程序,它负责管理硬件与计算机之间的连接,例如,外围组件互连 (PCI) 的总线驱动程序是实际检测插入 PCI 插槽的卡件的软件组件。下图显示了驱动程序和设备对象的层次结构。
如果生成了 IRP,则上层过滤器驱动程序会先于函数驱动程序接收它。上层过滤器驱动程序和下层过滤器驱动程序可能存在于设备驱动程序中,也可能不存在,它们通常用于支持函数驱动程序和总线驱动程序。在运行实际驱动程序之前,我们可以选择在这些驱动程序中包含一些额外的功能。上图中的左列显示了向上链接的内核 DEVICE_OBJECT
结构堆栈。
- PDO:代表物理设备对象,总线驱动程序使用此对象来表示设备与总线之间的连接。
- FDO:代表功能设备对象,函数驱动程序使用它来管理设备的功能。
- FiDO:代表过滤器设备对象,由过滤器驱动程序使用,用于存储它需要保留的关于硬件和过滤活动的信息。
当总线驱动程序检测到硬件的插入或移除时,它会调用 IoInvalidateDeviceRelations
来通知 PnP 管理器该总线的子设备列表已更改。为了获得子设备 PDO 的更新列表,PnP 管理器会向总线驱动程序发送一个 IRP。
作为对 PnP 管理器总线响应查询的响应,总线驱动程序返回一个 PDO 列表。然后,PnP 管理器确定哪些 PDO 代表未初始化的设备。PnP 管理器随后再次向总线驱动程序发送一个 IRP,总线驱动程序返回设备 ID。然后,PnP 管理器使用此 ID 在系统注册表中查找硬件项。
所有类型硬件的安装说明都存在于扩展名为 .INF 的文件中。每个 INF 文件包含将特定设备标识符字符串与该 INF 文件内的安装节相关联的语句。当新设备出现时,系统会尝试查找包含与设备标识符字符串对应的安装节的 INF 文件。您有责任提供此文件,这就是为什么上图包含“您”的原因。
数据结构
I/O 管理器使用驱动程序对象数据结构来表示每个设备驱动程序。DDK(Microsoft 提供的设备驱动程序开发 SDK)头文件声明了整个结构以及下面所示的所有其他内核模式数据结构。
typedef struct _DRIVER_OBJECT {
CSHORT Type; CSHORT Size;
} DRIVER_OBJECT, *PDRIVER_OBJECT;
也就是说,头文件声明了一个类型名为 DRIVER_OBJECT
的结构。它还声明了一个指针类型 (PDRIVER_OBJECT
) 并分配了一个结构标签 (_DRIVER_OBJECT
)。
下图显示了 Driver_Object
数据结构。
DeviceObject
包含一个设备对象数据结构列表,用于驱动程序管理的每个设备。I/O 管理器维护此字段,DriverUnload
将使用此字段遍历设备列表以删除它们。DriverExtension
指向一个包含许多字段的数据结构,但只有 AddDevice 字段是可访问的。此字段是指向驱动程序内创建设备对象的函数的指针。HardwareDatabase
包含一个字符串,该字符串命名了设备的一个硬件数据库注册表项,类似于 \Registry\Machine\Hardware\Description\System。FastIoDispatch
指向文件系统和网络驱动程序导出的函数指针表。DriverStartIo
指向您的驱动程序中处理 I/O 请求的函数,这些请求已由 I/O 管理器序列化。DriverUnload
指向您驱动程序中的清理函数。MajorFunction
是一个指针表,指向您驱动程序中处理各种 I/O 请求的函数。
与设备驱动程序的 DriverObject
类似,我们也有设备对象。下图显示了 Device_Object
数据结构。
DriverObject
指向描述与设备关联的驱动程序的。对象。NextDevice
指向与同一驱动程序关联的下一个设备对象。CurrentIrp
由 Microsoft IRP 排队例程StartPacket
和StartNextPacket
使用,用于记录最近发送到StartIo
例程的 IRP。
Flags 包含一组标志位,例如 DO_BUFFERED_IO
用于读写使用缓冲方法,或 DO_DIRECT_IO
用于读写使用 Direct
方法。
Characteristics 是另一组标志位,描述了设备的各种可选特性,例如介质可读不可写、介质是软盘驱动器、介质是虚拟卷、设备可以通过网络连接访问以及更多。
DriverEntry 例程
在前面的章节中,我们讨论了 PnP 管理器会加载驱动程序并调用 AddDevice
函数。一个驱动程序可以用于多个设备,因此需要一些全局初始化,这仅在驱动程序首次加载时完成,并且由 DriverEntry
例程负责。
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
}
函数的第一个参数是驱动程序的对象。WDM 驱动程序的 DriverEntry
函数将完成此对象的初始化并返回。非 WDM 驱动程序有大量额外的工作要做——它们还必须检测其负责的硬件,创建设备对象来表示硬件,并完成使硬件完全功能所需的所有配置和初始化。PnP 管理器为 WDM 驱动程序执行所有这些操作,因为它们是即插即用的。第二个参数是注册表中的服务键,此值不是持久的,因此如果计划稍后使用,应将其存储在某个变量中。
此函数的主要工作是用不同的函数指针填充驱动程序对象,例如:
DriverUnload
:将其设置为指向为驱动程序编写的清理函数的地址。DriverExtension->AddDevice
:将其设置为指向AddDevice
函数的地址。PnP 管理器将为驱动程序负责的每个设备调用此函数。DriverStartIO
:如果驱动程序使用标准的 I/O 请求排队方法,将其设置为指向您的StartIo
例程。MajorFunction
:驱动程序处理一些 IRP,因此这包含一个函数列表,每个函数处理一个 IRP。
为了更好地理解,下面提供了一个 DriverEntry
函数的示例代码。
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
DriverObject->DriverUnload = DriverUnload;
DriverObject->DriverExtension->AddDevice = AddDevice;
DriverObject->MajorFunction[IRP_MJ_PNP] = DispatchPnp;
DriverObject->MajorFunction[IRP_MJ_POWER] = DispatchPower;
DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] =
DispatchWmi;
servkey.Buffer = (PWSTR) ExAllocatePool(PagedPool,
RegistryPath->Length + sizeof(WCHAR));
if (!servkey.Buffer)
return STATUS_INSUFFICIENT_RESOURCES;
servkey.MaximumLength = RegistryPath->Length + sizeof(WCHAR);
RtlCopyUnicodeString(&servkey, RegistryPath);
servkey.Buffer[RegistryPath->Length/sizeof(WCHAR)] = 0;
return STATUS_SUCCESS;
}
AddDevice 例程
PnP 管理器为与驱动程序关联的每个设备调用此函数。该函数具有以下表示:
NTSTATUS AddDevice(PDRIVER_OBJECT DriverObject,
PDEVICE_OBJECT pdo)
{
return STATUS_SOMETHING; // e.g., STATUS_SUCCESS
}
第一个参数指向在 DriverEntry
例程中初始化的同一个 DriverObject
。pdo
参数是设备堆栈底部物理设备对象的地址。此函数的一些职责如下:
- 调用
IoCreateDevice
来创建设备对象。 - 为设备注册接口,以便应用程序知道设备的存在,或为设备对象命名并创建符号链接。
- 初始化设备对象和设备对象的标志。
- 调用
IoAttachDeviceToDeviceStack
将设备置于设备堆栈中。
Windows XP 使用集中式对象管理器来管理其内部数据结构,包括设备和驱动程序对象。为设备对象命名是为了让应用程序能够打开设备句柄并发送 IRP。
3. 驱动程序开发问题
一些设备驱动程序开发问题如下:
- 驱动程序设计策略
- 编码约定和技术
- 驱动程序内存分配
- Unicode 字符串
- 中断同步
- 同步多个 CPU
驱动程序设计策略
传统上,编写设备驱动程序不需要设计策略,但随着设备驱动程序的长度越来越长,设计策略的需求也日益增加。
下面列出了一些设计技术:
- 数据流图有助于将驱动程序分解为功能单元。这些图使人们更容易看到驱动程序中的功能单元如何相互关联。
- 状态机模型是描述驱动程序控制流的另一种好方法。
- 另一个有用的工具是外部事件列表以及这些事件应触发的驱动程序操作。列表应包括来自设备的硬件事件和来自用户的软件 I/O 请求。
使用这些技术有助于将设备驱动程序分解为更小的功能单元。完成初步分析和设计后,以下一些步骤有助于减少调试时间:
- 确定驱动程序需要哪种内核模式对象。
- 首先对
DriverEntry
和 Unload 例程进行编码。不要添加即插即用支持,因为这便于从控制台进行测试。 - 添加处理
IRP_MJ_Create
和IRP_MJ_Close
操作的调度例程。这些例程不需要设备访问,因此驱动程序可以轻松地用简单的应用程序进行测试。 - 添加用于查找硬件并为对象分配内存的代码,以及用于取消分配和卸载驱动程序的相关代码。
- 添加处理任何其他
IRP_MJ_XXX
函数代码的调度例程。 - 最后实现中断服务例程、Start I/O 逻辑和 DPC 例程。
编码约定和技术
驱动程序中不应使用汇编语言。它使代码难以阅读、不可移植且难以维护。对于特定于平台的代码,应使用 #ifdef
/#endif
指令。驱动程序不应链接到标准 C 库,因为这会浪费空间,而且库中的某些例程也不是线程安全和上下文安全的。除了包含 NTDDK.h 或 WDM.h 外,驱动程序还应使用私有头文件来隐藏各种硬件和平台依赖项。某些编译器支持将某些函数声明为可丢弃的选项。此类别中的函数将在驱动程序完成加载后从内存中消失,从而使驱动程序更小。如果开发环境提供此功能,则应使用它。可丢弃函数可以是 DriverEntry
和 DriverEntry
调用的任何函数。下面的代码可用于从内存中丢弃函数。
#ifdef ALLOC_PRAGMA
#pragma alloc_text( init, DriverEntry )
#pragma alloc_text( init, FuncCalledByDriverEntry )
#pragma alloc_text( init, OtherFuncCalledByDriverEntry )
#endif
非分页系统内存对系统很重要,应予以保存。驱动程序可以选择其要在分页内存中使用的例程,从而减轻非分页内存的负担。
仅在 PASSIVE_LEVEL
IRQL 运行的任何函数都可以分页。这包括 Reinitialize 例程、Unload 和 Shutdown 例程、Dispatch 例程、线程函数以及仅在 PASSIVE_LEVEL
IRQL 运行的任何辅助函数。同样,alloc_text
用于此目的。下面的示例展示了这一点。
#ifdef ALLOC_PRAGMA
#pragma alloc_text( page, Unload )
#pragma alloc_text( page, Shutdown )
#pragma alloc_text( page, DispatchRead )
#pragma alloc_text( page, DispatchHelper )
:
#endif
驱动程序内存分配
内存是编程的重要方面之一,驱动程序没有任何类似 malloc 和 free 或 new 和 delete 的函数。因此,必须小心,以便分配正确类型的内存。它们还应编写清理模块,因为内核模式代码没有自动机制。有三种内存分配选项,标准基于大小和请求的持续时间。这三个选项是内核堆栈、分页池和非分页池。第一个提供有限的非分页存储。由于驱动程序中的代码是可重入的,因此会避免全局变量。在使用内核堆栈时,我们需要小心避免数据溢出。以下是一些指南:
- 不要以使内部例程深度嵌套的方式设计驱动程序。
- 避免递归或限制深度(如果不可避免)。
- 不要使用内核堆栈来构建大型数据结构。而是使用池区域之一。
Unicode 字符串
在 Windows 操作系统中,字符字符串内部存储为 Unicode。这种方案使用 16 位表示每个字符,并提高了可移植性。Windows XP 使用 Unicode 结构,以便更轻松地传递 Unicode 字符串。DDK 还定义了一个 ANSI_STRING
结构,该结构与 UNICODE_STRING
结构非常相似。处理 Unicode 可能令人沮丧,因为 Unicode 字符串的字节长度是内容长度的两倍。C 或 C++ 程序员通常认为一个字符占一个字节。在处理 Unicode 时,我们应该记住以下几点:
- Unicode 字符串中的字符数与字节数不同,因此在进行任何计算 Unicode 字符串长度的算术运算时应小心。
- 不应假设字符的排序顺序或大小写字符之间的关系。
- 不要假设一个具有 256 个条目的表能够存储整个字符集。
中断同步
多 IRQL 级别的可重入代码需要适当的同步。如果两个不同 IRQL 级别上执行的代码同时尝试访问同一数据结构,则该结构可能会损坏。下图显示了问题。
假设一个在低 IRQL 上执行的代码将 foo.x 修改为 1,而一个高 IRQL 代码中断了低 IRQL 并将 foo.x 设置为 10,将 foo.y 设置为 20,当控制返回到低 IRQL 时,它不知道高 IRQL 所做的更改,并将 foo.y 设置为 2,最终数据处于不一致状态。
同步多个 CPU
修改一个 CPU 的 IRQL 对其他 CPU 和多处理器系统没有影响。IRQL 仅提供本地 CPU 保护来共享数据。为了防止多处理器环境中数据结构的损坏,Windows XP 使用称为自旋锁的同步对象。
自旋锁是一种与某些数据结构相关的互斥对象。当一段内核模式代码想要修改这些数据结构时,它必须首先请求对关联自旋锁的所有权。由于一次只有一个 CPU 可以拥有自旋锁,因此数据结构免受任何损坏。请求已拥有自旋锁的任何 CPU 都将忙等待直到自旋锁可用。下图解释了该过程。
4. 驱动程序调度例程
在驱动程序能够处理任何请求之前,它必须提供其支持的操作的详细信息。在本节中,我将讨论 I/O 管理器的调度操作,并解释如何使设备驱动程序能够接收 I/O 函数代码。在 Windows XP 中,I/O 管理器通过创建 IRP 工作订单来跟踪系统中发起的所有 I/O 请求。它在 IRP 的 I/O 堆栈位置的 MajorField 中存储 I/O 请求的函数代码。此 MajorField 用于 I/O 管理器与驱动程序的 MajorFunction 表进行映射。此表包含每种请求的调度例程。下面是其图形表示。
为了启用 I/O 函数代码,驱动程序必须定义将处理 I/O 函数代码的调度函数。此定义在 DriverEntry
过程中完成,该过程将调度函数的地址存储在 MajorFunction
数组的相应位置。执行此操作的代码如下所示。
NTSTATUS DriverEntry( IN PDRIVER_OBJECT pdo,
IN PUNICODE_STRING registryPath) {
pDO->MajorFunction[ IRP_MJ_CREATE ] = Create;
pDO->MajorFunction[ IRP_MJ_CLOSE ] = Close;
pDO->MajorFunction[ IRP_MJ_READ ]= Read;
return STATUS_SUCCESS;
IRP_MJ_XXX
是一个符号,代表 I/O 函数代码,并在 NTDDK.h 中声明。驱动程序不支持的函数代码应保持不变,因为 I/O 管理器会在调用 DriverEntry
之前用默认值填充 MajorFunction
中的所有条目。某些函数代码必须由驱动程序支持,例如 IRP_MJ_CREATE
和 IRP_MJ_ClOSE
,因为这些代码是在响应 Win32 CreateFile
和 CloseHandle
调用时生成的。当驱动程序处于分层形式时,则较高层驱动程序必须支持较低层驱动程序支持的函数代码,因为调用总是从较高层驱动程序转到较低层驱动程序。
调度例程具有相同的签名,并且这些例程在 PASSIVE_LEVEL_IRQL
下运行,这意味着它们可以访问分页的系统资源。一个拒绝 IRP 请求的示例调度例程如下所示。
NTSTATUS DispatchWrite( IN PDEVICE_OBJECT pdo,
IN PIRP irp ) {
//This means the request is not supported
//so reject it
irp ->IoStatus.Status = STATUS_NOT_SUPPORTED;
// this means no bytes were transfered
irp ->IoStatus.Information = 0;
// set the IRP as complete and no priority change
IoCompleteRequest(irp, IO_NO_INCREMENT);
return STATUS_NOT_SUPPORTED;
}
一个完成请求的调度例程看起来像这样。
NTSTATUS DispatchClose( IN PDEVICE_OBJECT pdo,
IN PIRP irp ) {
irp ->IoStatus.Status = STATUS_SUCCESS;
// this means no bytes were transfered
irp ->IoStatus.Information = 0;
// mark the irp complete
IoCompleteRequest(irp, IO_NO_INCREMENT );
return STATUS_SUCCESS;
}
5. 驱动程序安装
驱动程序的安装由 .INF 扩展文件控制。驱动程序的安装会更改系统注册表条目,添加新的驱动程序信息。INF 文件是一个简单的文本文件,分为多个节,每个节用方括号 ([ ]) 中的标识符表示。有些节名是必需的,而其他节是驱动程序特定的。每个节下的条目控制某些安装操作,或者链接到其他节。此文件中节的顺序不重要,节会一直持续到遇到另一个节开始或文件结束。节条目的通用格式为:entry = value [, value...]
其中 entry 是指令、关键字或文件名,value 是要应用于 entry 的属性。创建 INF 文件后,必须对其进行处理才能使其正常工作。可以使用 INF 文件进行手动或自动安装。通过在 Windows 资源管理器中打开 INF 文件并在右键单击文件后选择安装选项,可以启动安装过程;或者在即插即用环境中,可以通过插入或移除设备来启动安装。有时需要与自动安装一起使用“添加/删除硬件”向导。INF 文件的一部分看起来如下所示。
[Version]
signature="$CHICAGO$"
Class=DisplayCodec
ClassGUID={E6ABB47D-8339-4c60-BE92-E9045FF5A33D}
Provider=%Intel%
CatalogFile=a303.cat
/* AUTOBLD - NT5 LABEL - DO NOT REMOVE */
DriverVer=10/27/2003,6.14.10.3701
[Manufacturer]
%IntelMfg%=Intel
6. 参考文献
- 《Windows 2000 设备驱动程序手册,程序员指南,第二版》作者:Art Baker 和 Jerry Lozano
- 《Windows 驱动程序模型编程》作者:Walter Oney
- 《Linux 设备驱动程序,第二版》作者:Alessandro Rubini 和 Jonathan Corbet
- 《编写 Windows Wdm 设备驱动程序:涵盖 Nt 4、Win 98 和 Win 2000》作者:Chris Cant
- http://www.phdcc.com/WDMarticle.html
代码
Driver.c
#include <wdm.h>
#include "Header.h"
NTSTATUS DriverEntry(PDRIVER_OBJECT pDObject, PUNICODE_STRING pRegistryPath);
VOID Unload(PDRIVER_OBJECT dObject);
#pragma alloc_text(INIT, DriverEntry)
#pragma alloc_text(PAGE, Unload)
NTSTATUS DriverEntry(PDRIVER_OBJECT pDObject, PUNICODE_STRING pRegistryPath)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
PDEVICE_OBJECT pDeviceObject = NULL;
UNICODE_STRING DriverName, DosDeviceName;
unsigned int Index = 0;
DbgPrint("DriverEntry function has been called \n");
RtlInitUnicodeString(&DriverName, L"\\Device\\New");
RtlInitUnicodeString(&DosDeviceName, L"\\DosDevices\\New");
NtStatus = IoCreateDevice(pDObject, 0, &DriverName,
FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDObject);
if(NtStatus == STATUS_SUCCESS)
{
for(Index = 0; Index < IRP_MJ_MAXIMUM_FUNCTION; Index++)
pDObject->MajorFunction[Index] = UnSupportedFunction;
pDObject ->MajorFunction[IRP_MJ_CLOSE] = Close;
pDObject ->MajorFunction[IRP_MJ_CREATE] = Create;
pDObject ->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IoControl;
pDObject ->MajorFunction[IRP_MJ_READ] = USE_READ_FUNCTION;
pDObject ->MajorFunction[IRP_MJ_WRITE] = USE_WRITE_FUNCTION;
pDObject ->DriverUnload = Unload;
pDeviceObject->Flags |= IO_TYPE;
pDeviceObject->Flags &= (~DO_DEVICE_INITIALIZING);
IoCreateSymbolicLink(&DosDeviceName, &DriverName);
}
return NtStatus;
}
VOID Unload(PDRIVER_OBJECT dObject)
{
UNICODE_STRING DeviceName;
DbgPrint("Unload function has been called \n");
RtlInitUnicodeString(&DeviceName, L"\\DosDevices\\New");
IoDeleteSymbolicLink(&DeviceName);
IoDeleteDevice(DriverObject->DeviceObject);
}
Header.h
/********************************************/
NTSTATUS Create(PDEVICE_OBJECT DObject, PIRP PIRP);
NTSTATUS Close(PDEVICE_OBJECT DObject, PIRP PIRP);
NTSTATUS IoControl(PDEVICE_OBJECT DObject, PIRP PIRP);
NTSTATUS ReadNone(PDEVICE_OBJECT DObject, PIRP PIRP);
NTSTATUS WriteNone(PDEVICE_OBJECT DObject, PIRP PIRP);
NTSTATUS NotSupportedFunction(PDEVICE_OBJECT DObject, PIRP PIRP);
#ifndef IO_TYPE
#define IO_TYPE 0
#define USE_WRITE_FUNCTION WriteNone
#define USE_READ_FUNCTION ReadNone
#endif
Function.c
#include <wdm.h>
#include "Header.h"
#pragma alloc_text(PAGE, Create)
#pragma alloc_text(PAGE, Close)
#pragma alloc_text(PAGE, IoControl)
#pragma alloc_text(PAGE, ReadNone)
#pragma alloc_text(PAGE, WriteNone)
#pragma alloc_text(PAGE, NotSupportedFunction)
NTSTATUS Create(PDEVICE_OBJECT DObject, PIRP PIRP)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
DbgPrint("Create function has been called \n");
return NtStatus;
}
NTSTATUS Close(PDEVICE_OBJECT DObject, PIRP PIRP)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
DbgPrint("Close function has been called \n");
return NtStatus;
}
NTSTATUS IoControl(PDEVICE_OBJECT DObject, PIRP PIRP)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
DbgPrint("IoControl function has been called \n");
return NtStatus;
}
NTSTATUS ReadNone(PDEVICE_OBJECT DObject, PIRP PIRP)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
DbgPrint("ReadNone function has been called \n");
return NtStatus;
}
NTSTATUS WriteNone(PDEVICE_OBJECT DObject, PIRP PIRP)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
DbgPrint("WriteNone function has been called \n");
return NtStatus;
}
NTSTATUS NotSupportedFunction(PDEVICE_OBJECT DObject, PIRP PIRP)
{
NTSTATUS NtStatus = STATUS_NOT_SUPPORTED;
DbgPrint("NotSupportedFunction function has been called \n");
return NtStatus;
}