设备驱动程序的开发与应用

时间:2011-07-04

  摘要: 介绍了Windows NT4。0内核模式设备驱动程序开发中的一般性过程。通过提供一个化驱动程序的代码,解释各组成部分的结构功能和使用方法。在实践中,结合自身的开发需要,可编写出具有实用价值的驱动程序。

  何被引用的关系,可使开发者立即进人到与目标有关的源代码文件中一则提供了一个功能强大的理解和分析源代码的工程环境。它具有一个强大的源代码浏览器,帮助开发者快速理僻源代码,并为团队开发建立了一个框架。允许团队成员共享一个公共的,驻留于服务器的程序代码库集成的支持共享程序库的编译,可跟踪代码的改变,自动生成在实际应用中,编译器的性能将决定能否生成有效的,简洁的,高性能的代码,的/"编译器都是针对嵌入式应用的,它们与集成在一起,并对不同的目标机处理器进行了优化以利用特定处理器体系结构的优势,并尽可能使用处理器特有的优化功能。通过完全的代码控制和数据内存分配来支持共同的需求。对处理器提供各种基于,的调试器,允许在软件运行之前调试硬件。还为许多第三方厂家的电路仿真器提供支持是基于宿主机的目标机的调试代理。的调试器均可控制程序的执行,定位和分析实时错误,显示所有的系统对象。并可设置不同类型的断点及在断点处执行调试命令的选项,允许单步调试程序。提供了,和等图形化的多层次动态分析工具通过周期性采集对象的快照。监视目标机的行为的任务,消息队栈和内存使用等都可以图形化显示,使开发者及时了解目标机的实时行为。并可将采集的数据保存起来,输入到统计分析程序做进一步分析。象一个软件的逻辑分析仪,在定义好触发条件后,可记录并显示发生在目标机上的每个事件,并以时间索引图显示,查询每个任务和的使用率,了解系统性能瓶颈。可分析每个任务,确定程序故障点,分析函数与子函数的运行时间对比及分析代码的覆盖率,运用等级化分析显示函数被调用的额率。提供对程序内存配置的多重的,同步的浏览,允许交互地创建,查看,编辑和优化应用程序连接命令文档建立于工业标准的框架之上,使得其它支持标准的工具厂家通过开放的的,就可与集成在一起。

  1 分层结构与设备驱动程序

  Windows NT分层结构(如图1所示)包括运行于用户模式及内核模式的各种部件,设备驱动程序在图1的左下角,处于内核模式下I/O管理器之中。

  2 驱动程序工作方式

    随着电子技术的飞速发展,电脑硬件的性能越来越强大。驱动程序是直接工作在各种硬件设备上的软件,其“驱动”这个名称也十分形象的指明了它的功能。正是通过驱动程序,各种硬件设备才能正常运行,达到既定的工作效果。


   硬件如果缺少了驱动程序的“驱动”,那么本来性能非常强大的硬件就无法根据软件发出的指令进行工作,硬件就是空有一身本领都无从发挥,毫无用武 之地。这时候,电脑就正如古人所说的“万事俱备,只欠东风”,这“东风”的角色就落在了驱动程序身上。如此看来,驱动程序在电脑使用上还真起着举足轻重的作用。

  内核模式驱动程序与应用程序之间的差别之一是驱动程序的控制结构。内核模式驱动程序没有main或WinMain,而是由I/O管理器根据需要调用一个驱动程序例程:

  · 驱动程序被装入时;

  · 驱动程序被卸出或系统关闭时;

  · 用户程序发出I/O系统服务调用时;

  · 共享硬件资源对驱动程序可用时;

  · 设备操作过程中的任何时候。

  3 初始化过程

  3.1 系统注册表中有关设备驱动程序的项目是系统加载设备驱动程序的入口点

  系统注册表中用于系统加载设备驱动程序的项目如下:

  [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\DriverName]

  

  ″Type″ = dword00000001

  ″Start″ = dword00000002

  ″Group″ = ″Extended Base″

  ″ErrorControl″ = dword∶00000001

  其中Start含义如下:

  SERVICE_BOOT_START (0×0)  操作系统装入时

  SERVICE_SYSTEM_START (0×01)  操作系统初始化时

  SERVICE_AUTO_START (0×02)  服务控制管理器启动时

  SERVICE_DEMAND_START (0×03)  服务控制管理器手工启动

  SERVICE_DISABLED (0×04)  不启动

  Type含义如下:

  SERVICE_KERNEL_DRIVER (0×1)

  SERVICE_FILE_SYSTEM_DRIVER (0×2)

  SERVICE_ADAPTER (0×4)

  系统注册表中用于设备驱动程序加载后读取的项目如下:

  [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\DriverName\Parameters]

  ″Parameter1″ = dword∶00000001

  ″Parameter2″ = dword∶00000004

  3.2 加载驱动程序的装入例程

  I/O管理器调用驱动程序的DriverEntry例程,执行初始化。该例程完成:

  · 初始化其它例程的入口;

  · 创建命名设备对象;

  · 创建或初始化任意驱动程序使用的对象、类型和资源;

  · 返回状态值。

  I/O管理器建立与设备关联的Driver对象,并将其传递给DriverEntry例程。实际上Driver对象基本上是一个目录,含有指向各个驱动程序服务例程函数的指针,其结构如表1所示。

  表1 Driver对象

  域 说明

  PDRIVER_STARTIO DriverStartIo 驱动程序的Start I/O例程的地址

  PDRIVER_UNLOAD  DriverUnload 驱动程序的Unload例程地址

  PDRIVER_DISPATCH  Majorfunction[ ] 驱动程序的Dispatch例程的表,由I/O操作代码索引

  PDEVICE_OBJECT  DeviceObject 驱动程序创建的Device对象链表

  I/O管理器能够找到DriverEntry例程,是因为它有一个公认的名字,而其他的例程则通过下列两种方法查找:

  ·在Driver对象中有明确槽的函数如DirverObject->DriverUnload;

  ·在Driver对象的MajorFunction数组中——Driver对象的MajorFunction支持两种类型的功能代码。一种为标准的功能代码,如IRP_MJ_CREATE。另一种是用户自定义的功能代码,如IRP_MJ_DEVICE_CONTROL。

  所有驱动程序必须支持IRP_MJ_CREATE功能代码,这是因为Win32子系统下的用户程序调用CreateFile函数创建设备时,产生该功能代码。如果不处理这个功能代码,Win32程序就不能得到设备句柄。

  用户自定义的功能代码IRP_MJ_DEVICE_CONTROL只有在用户模式下的客户程序执行自定义的功能时可用。

  NTSTATUS DriverEntry(IN  PDRIVER_OBJECT DriverObject,IN  PUNICODE_STRING RegistryPath)

  {

  

  //声明设备对象

  PDEVICE_OBJECT DeviceObject,

  //生成函数接口指针

  DriverObject->MajorFunction[IRP_MJ_CREATE]=XxSelfDispatch;

  DriverObject->MajorFunction[IRP_MJ_CLOSE]=

  XxSelfDispatch;

  DriverObject->MajorFunction[IRP_MJ_READ]=

  XxReadDispatch;

  DriverObject->MajorFunction[IRP_MJ_WRITE]=

  XxWriteDispatch

  DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=XxSelfDispatch

  DriverObject->DriverUnload=XxUnload

  //生成Windows NT Executive知道的设备名

  RtlInitUnicodeString(&NtDeviceName, SelfDeviceName); 

  //生成自己的设备

  Status=IoCreateDevice(

  DriverObject, // Driver对象

  sizeof(SELF_DEVICE_INFO), // Device对象

  Extension结构大小

  &NtDeviceName,

  DeviceType,

  0,

  FALSE, // 不执行

  &DeviceObject //Device对象指针

  );

   //生成Win32子系统下的用户程序可识别的设备名

  RtlInitUnicodeString(&Win32DeviceName, SelfWin32Name);

  //联接内部设备名与Win32子系统下的设备名

  Status = IoCreateSymbolicLink( &Win32DeviceName, &NtDeviceName);

  //利用RtlQueryRegistryValues函数读出注册表中Parameters下的参数值,初始化自己的硬件

  …

  }

  

  4 驱动程序服务例程

  驱动程序初始化之后,始终等待发自用户的命令或由其它事件源引起的事件。一旦命令或事件发生,I/O管理器就调用相应的服务例程提供服务。而几乎所有的I/O都是通过I/O请求包IRP 驱动的。所谓IRP驱动,就是I/O管理器负责在非分页的系统内存中分配一定空间,当接受用户发出的命令或由事件引发后,将工作指令按一定的数据结构置于其中,传递到驱动程序服务例程。换言之,IRP包含了驱动程序服务例程所需要的信息指令。表2、表3为IRP的一些数据结构。

  设备驱动程序用来将硬件本身的功能告诉操作系统,完成硬件设备电子信号与操作系统及软件的编程语言之间的互相翻译。当操作系统需要使用某个硬件时,比如:让声卡播放音乐,它会先发送相应指令到声卡驱动程序,声卡驱动程序接收到后,马上将其翻译成声卡才能听懂的电子信号命令,从而让声卡播放音乐。

  所以简单的说,驱动程序提供了硬件到操作系统的一个接口以及协调二者之间的关系,而因为驱动程序有如此重要的作用,所以人们都称“驱动程序是硬件的灵魂”、“硬件的主宰”,同时驱动程序也被形象的称为“硬件和系统之间的桥梁”。

  同时,I/O管理器和驱动程序都需要在所有时候知道一个I/O设备所进行的情况。系统提供Device对象以满足此要求。该对象在DriverEntry例程中生成设备时由系统创建后,分配给驱动程序,并在整个驱动程序生存期内有效。当I/O管理器调用驱动程序服务例程时,传递该对象。表4为Device对象的外部可见域。

  表2 IRP标头中外部可见的域

  域 内  容 含  义

  标头 I/O_STATUS_BLOCK  IoStatus Stutus I/O请求的状态

  Information

  AssociatedIrp.Systembuffer   执行缓冲I/O时,系统空间缓冲区指针

  PMDL MdlAddress   执行直接I/O时,用户空间缓冲区的内存描述符列表的指针

  PVOID User Buffer   I/O缓冲区的用户空间地址

  BOOLEAN Cancel   指示IRP已被取消

  表3 IRP堆栈单元的一些内容

  IO_STACK_LOCATION,*PIO_STACK_LOCATION

  I/O堆栈单元 UCHAR MajorFunction   指定操作的 IRP_MJ_XXX 函数

  UCHAR MinorFunction   有文件系统和 SCSI 驱动程序

  union Parameters   Majorfunction 代码的联合类型

  struct Read ULONG Length IRP_MJ_READ 的参数

  ULONG Key

  LARGE_INTEGER  ByteOffset

  struct Write ULONG Length IRP_MJ_WRITE 的参数据

  ULONG Key

  LARGE_INTEGER  ByteOffset

  struct DeviceIoControl ULONG  OutputBufferLength IRP_MJ_DEVICE_CONTROL, IRP_MJ_INTERNAL_DEVICE _CONTROL 的参数

  OutputBuffer Length

  ULONG   Iocontrolcode

  表4 Device 对象的外部可见域

  域 含   义

  PVOID DeviceExtension 指向 Device Extension 结构的指针

  PDRIVER_OBJECT   DriverObject 指向这个设备 Driver 对象

  ULONG Flags 指定这个设备的缓冲策略  DO_BUFFER_IO   DO_DIRECT_IO

  PDEVICE_OBJECT   NextDevice 指向属于这个驱动程序的下一个设备

  CCHAR StackSize 发送到这个设备的IRP需要的I/O堆栈单元的数目

  ULONG AlignmentRequirement 缓冲区要求的内存对齐

  其中,DeviceExtension域是一个重要的数据结构。它是由I/O管理器创建并自动挂接到Device对象的非分页池,是保存驱动程序任意全局变量的办法。因为DeviceExtension是驱动程序特定的,要自定义它的数据结构。

  下面是一个驱动程序服务例程利用Device对象和IRP的片段:

  NTSTATUS XxSelfDispatch(IN PDEVICE_OBJECT pDO IN PIRP pIrp); 

  {

  

  PLOCAL_DEVICE_INFO pLDI;

  PIO_STACK_LOCATION pIrpStack;

  PULONG pIOBuffer;

  //得到全局信息

  pLDI = (PSELF_DEVICE_INFO)pDO->DeviceExtension;

  pIrpStack = IoGetCurrentIrpStackLocation(pIrp);

  

  //得到由用户应用程序发来的用户数据,并在需要时,将结果通过此变量返回给用户

  pIOBuffer=PULONG pIrp->AssociatedIrp。System

  Buffer;

  // 由IRP携带的信息决定驱动程序的执行

  switch (pIrpStack->MajorFunction)

  {

  case IRP_MJ_CREATE:

  case IRP_MJ_CLOSE:

  Status = STATUS_SUCCESS;

  break;

  case IRP_MJ_DEVICE_CONTROL:

  //由Parameters进一步解释控制代码含义

  switch (pIrpStack->Parameters。DeviceIoControl。IoControlCode)

  {

  case IOCTL_Function1:

  //执行功能代码

  Field1 = pLDI->SelfField1;

  …

  break;

  case IOCTL_Function2:

  //执行功能代码

  …

  break;

  }

  break

  }

  

  // 返回I/O操作的状态

  pIrp->IoStatus。Status = Status;

  IoCompleteRequest(pIrp IO_NO_INCREMENT);  

  return Status;

  }

  

  5 驱动程序终止例程

  Unload例程负责取消由DriverEntry例程所做的任何事情,包括解除属于该驱动程序的任何硬件资源的分配,以及删除属于驱动程序的任何内核对象。通常这仅在系统关闭时需要。

  VOID XxUnload(PDRIVER_OBJECT DriverObject)

  {

  

  PLOCAL_DEVICE_INFO pLDI;

  UNICODE_STRING Win32DeviceName;

  // 得到全局数据,根据全局数据进行清理工作

  pLDI=PLOCAL_DEVICE_INFO DriverObject->Device

  Object->DeviceExtension

  if (pLDI->Field2 == TRUE)

  {

  …

  

  // 删除分配的设备名及设备

  RtlInitUnicodeString(&Win32DeviceName, SelfWin32 Name); 

  IoDeleteSymbolicLink(&Win32DeviceName);

  IoDeleteDevice(pLDI->DeviceObject);

  }

  

  6 用户层应用程序与驱动程序间的接口

  驱动程序完成后,将在系统重新引导时装入并初始化(由DriverEntry例程完成)。此时,驱动程序处于可用状态,等待用户层应用程序使用。用户层应用程序可以:

  ·打开该设备文件(由IRP_MJ_CREATE功能代码完成)

  ·读出数据(由IRP_MJ_READ功能代码完成)

  ·写入数据(由IRP_MJ_WRITE功能代码完成)

  ·执行用户自定义的功能代码(由IRP_MJ_DEVICE_CONTROL功能代码完成)

  ·关闭该设备文件(由IRP_MJ_CLOSE功能代码完成)

  以下是部分实现代码:

  void main()

  {

  HANDLE hndFile; // 由CreateFile得到

  union {

  ULONG   LongData;

  USHORT ShortData;

  UCHAR   CharData;

   }DataBuffer; //从设备驱动程序中得到的数据

  LONG  IoctlCode; //功能代码

  ULONG DataLength;

  LONG Parameter1;

  //调用IRP中的IRP _MJ_CREATE功能

  hndFile = CreateFile(

  ″\\\\。\\SelfWin32Name″, // 打开设备文件″ SelfWin32Name″

  GENERIC_READ | GENERIC_WRITE,

  FILE_SHARE_READ | FILE_SHARE_WRITE,

  NULL,

  OPEN_EXISTING,

  0,

  NULL

  if (hndFile == INVALID_HANDLE_VALUE)

  {

  printf(″Unable to open the device。\n″);

  exit(1);

  }

  IoctlCode = IOCTL_Function1; //自定义功能代码

  Parameter1 = 1;

  DataLength = sizeof(DataBuffer。CharData);

  IoctlResult = DeviceIoControl(

  hndFile //设备文件句柄

  IoctlCode//功能代码,对应IRP中的Parameter。

  //DeviceIoControl。IoControlCode域

  &Parameter1,//传递到驱动程序的参数缓冲区,对应

  //IRP中的AssociatedIrp。SystemBuffer

  sizeof(Parameter1)  //参数缓冲区长度

  &DataBuffer, //从驱动程序传出的数据缓冲区

  DataLength, //缓冲区长度

  &ReturnedLength, //返回的实际缓冲区长度

  NULL //等待,直到操作完成

  );

  if(!CloseHandlehndFile)) //关闭设备

  

  {

  printf(″Failed to close device。\n″);

  }

  }

  以上介绍了Windows NT4。0设备驱动程序开发中的一般性过程。用户可利用NT SDK 及DDK开发工具包,并根据自身需要,对以上代码进行扩充完成所需任务。


  
上一篇:嵌入式控制系统引用网络功能
下一篇:Microsemi 验证IP

免责声明: 凡注明来源本网的所有作品,均为本网合法拥有版权或有权使用的作品,欢迎转载,注明出处。非本网作品均来自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

相关技术资料