男人.No boy no cry

彪悍的人生,不需要解釋...

导航

驱动程序简单入门

Posted on 2005-09-15 12:06  Piccolo Goo  阅读(7198)  评论(5编辑  收藏  举报
我们学习程序设计,都是从“Hello World”开始的,驱动程序也不例外,今天我就写一个驱动版的“Hello World”来热热身,目的希望大家能对驱动程序的基本框架有所了解。


驱动程序分为2类,一个是Kernel模式驱动,另一个是Windows模式驱动,2种模式本质是相同,但细节不同,本文介绍的是内核模式驱动和驱动程序的安装、使用。


驱动程序同普通的EXE,DLL一样,都属于PE文件,而且都有一个入口函数。但EXE中,入口函数是main()/WinMain()和Unicode的wmain()/wWinmain(),DLL的入口函数
则可有可无,它是DllMain()。驱动程序也有入口函数,而且是必须的,它是DriverEntry(),再次提示,它是必须的,因为I/O管理器会首先调用驱动程序的DriverEntry()
,它的作用就像DllMain()--完成一些初始化工作。DriverEntry()一共有2个参数:1)PDRIVER_OBJECT DriverObject,指向驱动程序对象的指针,我们操作驱动程序,
全靠它,它是由I/O管理器传递进来的;2)PUNICODE_STRING RegistryPath,驱动程序的服务主键,这个参数的使用并不多,但要注意,在DriverEntry()返回后,它可能
会消失,所以如果需要使用,记住先要保存下来。DriverEntry()的返回一个NTSTATUS值,它是一个ULONG值,具体的定义,请参见DDK中的NTSTATUS.H头文件,里边有详细
的定义。



既然要写驱动版的“Hello World”,就需要确定如何来与驱动程序通信,常用的共享内存,共享事件,IOCTL宏,或者直接用ReadFile()或WriteFile()进行读写,在本文

里我就采用一种简单的、但又很常用的IOCTL宏,它依赖的IRP派遣例程是IRP_MJ_DEVICE_CONTROL,Win32程序使用DeviceIoControl()与驱动进行通信,根据不同的IOCTL宏,

输出不同的调试信息。为了简便,我并没有使用ReadFile()将信息读出来,而是直接用DbgPrint()输出,所以需要使用DbgView查看,其他调试工具也可以。PS:偷懒!


驱动程序与I/O管理器通信,使用的是IRP,即I/O请求包。IRP分为2部分:1)IRP首部;2)IRP堆栈。IRP首部信息如下:

IRP首部:

IO_STATUS_BLOCK IoStatus                 包含I/O请求的状态 
 
PVOID AssociatedIrp.SystemBuffer         如果执行缓冲区I/O,这个指针指向系统缓冲区 
 
PMDL MdlAddress                          如果直接I/O,这个指针指向用户缓冲区的存储器描述符表 
 
PVOID UserBuffer                         I/O缓冲区的用户空间地址

IRP堆栈:

UCHAR MajorFunction               指示IRP_MJ_XXX派遣例程 
 
UCHAR MinorFunction               同上,一般文件系统和SCSI驱动程序使用它 
 
union Parameters                  MajorFunction的联合类型 

struct Read                       IRP_MJ_READ的参数 
ULONG Length 
ULONG Key 
LARGE_INTEGER ByteOffset 
 
struct Write                      IRP_MJ_WRITE的参数 
ULONG Length 
ULONG Key 
LARGE_INTEGER ByteOffset 
 
struct DeviceIoControl            IRP_MJ_DEVICE_CONTROL和IRP_MJ_INTERNAL_DEVICE_CONTROL的参数 
ULONG OutputBufferLength 
ULONG InputBufferLength 
ULONG IoControlCode 
PVOID Type3InputBuffer 
}  
PDEVICE_OBJECT DeviceObject       请求的目标设备对象的指针 
 
PFILE_OBJECT FileObject           请求的目标文件对象的指针,如果有的话



操作IRP。对于不同的IRP函数,操作也是不同的:有的只操作IRP首部;有的只操作IRP堆栈;还有操作IRP整体,

下面是一些常用的函数:

IRP整体:

    名称                     描述                         调用者
IoStartPacket           发送IRP到Start I/O例程           Dispatch 
 
IoCompleteRequest       表示所有的处理完成               DpcForIsr 
 
IoStartNextPacket       发送下一个IRP到Start I/O例程     DpcForIsr 
 
IoCallDriver            发送IRP请求                      Dispatch 
 
IoAllocateIrp           请求另外的IRP                    Dispatch 
 
IoFreeIrp               释放驱动程序分配的IRP            I/O Completion 

IRP堆栈:

    名称                            描述                         调用者
IoGetCurrentIrpStackLocation   得到调用者堆栈的指针             Dispatch 
   
IoMarkIrpPending               为进一步的处理标记调用者I/O堆栈  Dispatch 
 
IoGetNextIrpStackLocation      得到下一个驱动程序的I/O堆栈的指针   Dispatch 
 
IoSetNextIrpStackLocation      将I/O堆栈指针压入堆栈            Dispatc

在驱动程序,IRP派遣例程起着很重要的作用,每个IRP派遣例程,几乎都有对应的Win32函数,下面是几个常用的:

IRP派遣例程:

    名称                            描述                         调用者
IRP_MJ_CREATE                   请求一个句柄                   CreateFile 
 
IRP_MJ_CLEANUP                  在关闭句柄时取消悬挂的IRP      CloseHandle 
 
IRP_MJ_CLOSE                    关闭句柄                       CloseHandle 
 
IRP_MJ_READ                     从设备得到数据                 ReadFile 
 
IRP_MJ_WRITE                    传送数据到设备                 WriteFile 
 
IRP_MJ_DEVICE_CONTROL           控制操作(利用IOCTL宏)        DeviceIoControl 
 
IRP_MJ_INTERNAL_DEVICE_CONTROL  控制操作(只能被内核调用)       N/A 
 
IRP_MJ_QUERY_INFORMATION        得到文件的长度                 GetFileSize 
 
IRP_MJ_SET_INFORMATION          设置文件的长度                 SetFileSize 
 
IRP_MJ_FLUSH_BUFFERS            写输出缓冲区或者丢弃输入缓冲区 FlushFileBuffers FlushConsoleInputBuffer PurgeComm 
 
IRP_MJ_SHUTDOWN                 系统关闭                       InitiateSystemShutdown

=================================================================================================================================

下面开始写我们的驱动版的“Hello World”,程序很简单,先介绍一下流程:

1,调用IoCreateDevice()创建一个设备,并返回一个设备对象。
2,调用IoCreateSynbolicLink()创建一个符号连接,使Win32程序可以使用驱动程序
3,设置IRP_MJ_DEVICE_CONTROL派遣例程HelloWorldDispatch()和卸载例程HelloWorldUnLoad()。

如果Win32程序使用DeviceIoControl(),则执行HelloWorldDispatch()函数
4,调用IoGetCurrentIrpStackLocation()得到当前调用者的IRP指针
5,取得IO控制代码,完成后使用IoCompleteRequest()完成IRP操作

如果使用ControlService()停止驱动程序,则执行HelloWorldUnLoad()函数
4,调用IoDeleteSymbolicLink()删除符号连接
5,调用IoDeleteDevice()删除已建立的设备



驱动入口DriverEntry()

//创建设备
IoCreateDevice(DriverObject,        //驱动程序对象
               0,                   //扩展设备的大小,由于不需要,所以置0
               &DeviceNameString,   //设备名称
               FILE_DEVICE_UNKNOWN, //设备类型
               0,                   //指示设备允许的操作
               FALSE,               //如果为TRUE,表示只能有一个线程使用该设备,为FALSE,则没有限制
               &lpDeviceObject);    //返回的设备对象

//创建符号连接
IoCreateSymbolicLink(&DeviceLinkString,   //存放符号连接的UNICODE_STRING
                     &DeviceNameString);  //设备名称

//派遣例程和卸载例程
DriverObject->MajorFunction[IRP_MJ_CREATE]=
    DriverObject->MajorFunction[IRP_MJ_CLOSE]=
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=HelloWorldDispatch;
DriverObject->DriverUnload=HelloWorldUnLoad;

IRP派遣例程HelloWorldDispatch()

IrpStack=IoGetCurrentIrpStackLocation(pIrp);   //得到当前调用者的IRP堆栈

//获取IO控制代码,并执行指定操作,这里只是DbgPrint()
IoControlCodes=IrpStack->Parameters.DeviceIoControl.IoControlCode;
switch (IoControlCodes)  {
......

IoCompleteRequest(pIrp,IO_NO_INCREMENT);   //完成IRP操作

卸载例程HelloWorldUnLoad()

//删除符号连接和设备
IoDeleteSymbolicLink(&DeviceLinkString);
IoDeleteDevice(DriverObject->DeviceObject);

=================================================================================================================================

完整代码:

=================================================================================================================================

驱动程序的编译需要使用DDK中的build实用程序,它是一个命令行程序,使用不是很方便。VC知识库有一篇在VC++ 6.0中编译驱动的文章,有兴趣可以去看看。

1,makefile
编译驱动程序,首先应该准备一个makefile,这个文件很简单,只有一句代码:
#
# DO NOT EDIT THIS FILE!!!  Edit .\sources. if you want to add a new source
# file to this component.  This file merely indirects to the real make file
# that is shared by all the driver components of the Windows NT DDK
#

!INCLUDE $(NTMAKEENV)\makefile.def

正如描述的那样,不要修改这个文件---它是通用的!

2,sources
准备的第二个文件就是sources,它描述了一些编译的细节。针对本文的程序,sources文件的内容是这样的:
TARGETNAME=HelloWorld   //驱动名称
TARGETPATH=.            //编译后SYS的路径
TARGETTYPE=DRIVER       //类型为驱动程序

SOURCES= HelloWorld.c   //只有一个源文件

有了这2个文件后,就可以使用build进行编译了。进入「开始」菜单\程序\Development Kits\Windows 2000 DDK,
分别有3个CMD程序:1)Checked 64 Bit Build Environment,“Debug”的64位版本;2)Checked Build Environment
“Debug”的32位版本;3)Free Build Environment,“Release”的32位版本。不用说,肯定是使用Free Build Environment。

New or updated MSVC detected.  Updating DDK environment....

Setting environment for using Microsoft Visual C++ tools.
Starting dirs creation...Completed.

C:\NTDDK>cd\

C:\>cd HelloWorld

C:\HelloWorld>build
BUILD: Object root set to: ==> objfre
BUILD: /i switch ignored
BUILD: Compile and Link for i386
BUILD: Loading c:\NTDDK\build.dat...
BUILD: Computing Include file dependencies:
BUILD: Examining c:\helloworld directory for files to compile.
    c:\helloworld - 1 source files (127 lines)
BUILD: Saving c:\NTDDK\build.dat...
BUILD: Compiling c:\helloworld directory
Compiling - helloworld.c for i386
BUILD: Linking c:\helloworld directory
Linking Executable - i386\helloworld.sys for i386
BUILD: Done

    1 file compiled
    1 executable built

C:\HelloWorld>

现在C:\HelloWorld\i386目录下,就有了HelloWorld.sys。

=================================================================================================================================

驱动程序的安装如同安装服务一样,唯一不同的是,创建服务时,类型是内核驱动,其他跟操作服务没什么区别。

安装驱动程序流程:
1,调用OpenSCManager()打开服务控制管理器
2,调用CreateService()创建一个服务,服务类型为内核驱动
3,调用OpenService()取得服务句柄
启动服务
4,调用StartService()启动服务
停止服务
4,调用ControlService()停止服务
删除服务
4,调用DeleteService()删除服务
5,调用CloseServiceHandle()关闭服务句柄

操作驱动程序流程: 
1,调用CreateFile()取得设备句柄
2,调用DeviceIoControl()传递I/O控制代码
3,调用CloseHandle()关闭设备句柄

http://www.xfocus.net/tools/200411/882.html
这里有一个完整的驱动安装程序,所以我就不写了,只给出操作驱动程序的代码

完整代码:

=================================================================================================================================

参考资料

《Windows 2000 DDK》

《Windows 2000 驱动程序设计》


附录代码:
  1. #ifndef __HELLOWORLD_C__
  2. #define __HELLOWORLD_C__
  3. #define DEBUGMSG
  4. #include <ntddk.h>
  5. #define DEVICE_HELLO_INDEX 0x860
  6. //2个IOCTL宏
  7. #define START_HELLPWORLD CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_HELLO_INDEX,METHOD_BUFFERED,FILE_ANY_ACCESS)
  8. #define STOP_HELLPWORLD  CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_HELLO_INDEX+1,METHOD_BUFFERED,FILE_ANY_ACCESS)
  9. #define NT_DEVICE_NAME L"\\Device\\HelloWorld"        //设备名称
  10. #define DOS_DEVICE_NAME L"\\DosDevices\\HelloWorld"   //符号连接
  11. NTSTATUS HelloWorldDispatch (IN PDEVICE_OBJECT DeviceObject,IN PIRP pIrp);
  12. VOID HelloWorldUnLoad (IN PDRIVER_OBJECT DriverObject);
  13. //驱动入口
  14. NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath)
  15. {
  16.     NTSTATUS ntStatus=STATUS_SUCCESS;
  17.     PDEVICE_OBJECT lpDeviceObject=NULL;       //指向设备对象的指针
  18.     UNICODE_STRING DeviceNameString={0};      //设备名称
  19.     UNICODE_STRING DeviceLinkString={0};      //符号连接
  20.     //调试信息
  21.     #ifdef DEBUGMSG
  22.            DbgPrint("Starting DriverEntry()\n");
  23.     #endif
  24.     RtlInitUnicodeString(&DeviceNameString,NT_DEVICE_NAME);  //初始化Unicode字符串
  25.     //创建设备
  26.     ntStatus=IoCreateDevice(DriverObject,0,&DeviceNameString,FILE_DEVICE_UNKNOWN,0,FALSE,&lpDeviceObject);
  27.     //使用NT_SUCCESS宏检测函数调用是否成功
  28.     if (!NT_SUCCESS(ntStatus))
  29.     {
  30.         #ifdef DEBUGMSG
  31.                DbgPrint("IoCreateDevice() error reports 0x%08X\n",ntStatus);
  32.         #endif
  33.         return ntStatus;
  34.     }
  35.     RtlInitUnicodeString(&DeviceLinkString,DOS_DEVICE_NAME);
  36.     //创建符号连接
  37.     ntStatus=IoCreateSymbolicLink(&DeviceLinkString,&DeviceNameString);
  38.     if (!NT_SUCCESS(ntStatus))
  39.     {
  40.         #ifdef DEBUGMSG
  41.                DbgPrint("IoCreateSymbolicLink() error reports 0x%08X\n",ntStatus);
  42.         #endif
  43.         if (lpDeviceObject)
  44.             IoDeleteDevice(lpDeviceObject);
  45.         return ntStatus;
  46.     }
  47.     //设置IRP派遣例程和卸载例程
  48.     DriverObject->MajorFunction[IRP_MJ_CREATE]=
  49.     DriverObject->MajorFunction[IRP_MJ_CLOSE]=
  50.     DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=HelloWorldDispatch;
  51.     DriverObject->DriverUnload=HelloWorldUnLoad;
  52.     return ntStatus;
  53. }
  54. NTSTATUS HelloWorldDispatch (IN PDEVICE_OBJECT DeviceObject,IN PIRP pIrp)
  55. {
  56.     NTSTATUS ntStatus=STATUS_SUCCESS;
  57.     PIO_STACK_LOCATION IrpStack=NULL;   //IRP堆栈
  58.     ULONG IoControlCodes=0;             //I/O控制代码
  59.     //设置IRP状态
  60.     pIrp->IoStatus.Status=STATUS_SUCCESS;
  61.     pIrp->IoStatus.Information=0;
  62.     #ifdef DEBUGMSG
  63.            DbgPrint("Starting HelloWorldDispatch()\n");
  64.     #endif
  65.     IrpStack=IoGetCurrentIrpStackLocation(pIrp);    //得到当前调用者的IRP
  66.     switch (IrpStack->MajorFunction)
  67.     {
  68.             case IRP_MJ_CREATE:
  69.                  #ifdef DEBUGMSG
  70.                         DbgPrint("IRP_MJ_CREATE\n");
  71.                  #endif
  72.                  break;
  73.             case IRP_MJ_CLOSE:
  74.                  #ifdef DEBUGMSG
  75.                         DbgPrint("IRP_MJ_CLOSE\n");
  76.                  #endif
  77.                  break;
  78.             case IRP_MJ_DEVICE_CONTROL:
  79.                  #ifdef DEBUGMSG
  80.                         DbgPrint("IRP_MJ_DEVICE_CONTROL\n");
  81.                  #endif
  82.                  //取得I/O控制代码
  83.                  IoControlCodes=IrpStack->Parameters.DeviceIoControl.IoControlCode;
  84.                  switch (IoControlCodes)
  85.                  {
  86.                          //启动
  87.                          case START_HELLPWORLD:
  88.                               DbgPrint("Starting \"Hello World\"\n");
  89.                               break;
  90.                          //停止
  91.                          case STOP_HELLPWORLD:
  92.                               DbgPrint("Stoping \"Hello World\"\n");
  93.                               break;
  94.                          default:
  95.                               pIrp->IoStatus.Status=STATUS_INVALID_PARAMETER;
  96.                               break;
  97.                  }
  98.                  break;
  99.             default:
  100.                  break;
  101.     }
  102.     ntStatus=pIrp->IoStatus.Status;
  103.     IoCompleteRequest(pIrp,IO_NO_INCREMENT);
  104.     return ntStatus;
  105. }
  106. VOID HelloWorldUnLoad (IN PDRIVER_OBJECT DriverObject)
  107. {
  108.      UNICODE_STRING DeviceLinkString={0};
  109.      PDEVICE_OBJECT DeviceObjectTemp1=NULL;
  110.      PDEVICE_OBJECT DeviceObjectTemp2=NULL;
  111.      #ifdef DEBUGMSG
  112.             DbgPrint("Starting HelloWorldUnLoad()\n");
  113.      #endif
  114.      RtlInitUnicodeString(&DeviceLinkString,DOS_DEVICE_NAME);
  115.      if (DeviceLinkString.Buffer)
  116.          IoDeleteSymbolicLink(&DeviceLinkString);
  117.      if (DriverObject)
  118.      {
  119.          DeviceObjectTemp1=DriverObject->DeviceObject;
  120.          while (DeviceObjectTemp1)
  121.          {
  122.                 DeviceObjectTemp2=DeviceObjectTemp1;
  123.                 DeviceObjectTemp1=DeviceObjectTemp1->NextDevice;
  124.                 IoDeleteDevice(DeviceObjectTemp2);
  125.          }
  126.      }
  127. }
  128. #endif


用户态程序:
  1. #define DEBUGMSG
  2. #include <windows.h>
  3. #include <winioctl.h>
  4. #include <stdio.h>
  5. #define DEVICE_FILTER_INDEX 0x860
  6. #define START_HELLPWORLD CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_FILTER_INDEX,METHOD_BUFFERED,FILE_ANY_ACCESS)
  7. #define STOP_HELLPWORLD CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_FILTER_INDEX+1,METHOD_BUFFERED,FILE_ANY_ACCESS)
  8. #define erron GetLastError()
  9. #define MY_DEVICE_NAME "\\\\.\\HelloWorld"
  10. #define MY_DEVICE_START "-start"
  11. #define MY_DEVICE_STOP "-stop"
  12. BOOL DriverControl (TCHAR *Maik);
  13. void Usage (TCHAR *Paramerter);
  14. int main (int argc,TCHAR *argv[])
  15. {
  16.     if (argc!=2)
  17.     {
  18.         Usage(argv[0]);
  19.         return 0;
  20.     }
  21.     if (strcmpi(argv[1],MY_DEVICE_START)==0 || strcmpi(argv[1],MY_DEVICE_STOP)==0)
  22.         DriverControl(argv[1]);
  23.     else
  24.     {
  25.         Usage(argv[0]);
  26.         return 0;
  27.     }
  28.     return 0;
  29. }
  30. BOOL DriverControl (TCHAR *Maik)
  31. {
  32.      HANDLE hDevice=NULL;  //设备句柄
  33.      //获得设备句柄
  34.      hDevice=CreateFile(MY_DEVICE_NAME,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
  35.      if (hDevice==INVALID_HANDLE_VALUE)
  36.      {
  37.          #ifdef DEBUGMSG
  38.                 printf("CreateFile() GetLastError reports %d\n",erron);
  39.          #endif
  40.          return FALSE;
  41.      }
  42.      //启动
  43.      if (strcmpi(Maik,MY_DEVICE_START)==0)
  44.      {
  45.          //传递启动的I/O控制代码
  46.          if (!(DeviceIoControl(hDevice,START_HELLPWORLD,NULL,0,NULL,0,NULL,NULL)))
  47.          {
  48.              #ifdef DEBUGMSG
  49.                     printf("DeviceIoControl() GetLastError reports %d\n",erron);
  50.              #endif
  51.              CloseHandle(hDevice);
  52.              return FALSE;
  53.          }
  54.      }
  55.      //停止
  56.      if (strcmpi(Maik,MY_DEVICE_STOP)==0)
  57.      {
  58.          //传递停止的I/O控制代码
  59.          if (!(DeviceIoControl(hDevice,STOP_HELLPWORLD,NULL,0,NULL,0,NULL,NULL)))
  60.          {
  61.              #ifdef DEBUGMSG
  62.                     printf("DeviceIoControl() GetLastError reports %d\n",erron);
  63.              #endif
  64.              CloseHandle(hDevice);
  65.              return FALSE;
  66.          }
  67.      }
  68.      if (hDevice)
  69.          CloseHandle(hDevice);  //关闭句柄
  70.      return TRUE;
  71. }
  72. void Usage (TCHAR *Paramerter)
  73. {
  74.      fprintf(stderr,"============================================================================\n"
  75.              "      驱动版Hello World\n"
  76.              "作者:dahubaobao[E.S.T]\n"
  77.              "主页:www.eviloctal.com\n"
  78.              "OICQ:382690\n\n"
  79.              "%s -start\t启动\n"
  80.              "%s -stop \t停止\n\n"
  81.              "本程序只是用做代码交流,如有错误,还请多多包含!\n"
  82.              "============================================================================\n"
  83.              ,Paramerter,Paramerter);
  84. }