内核知识第五讲.驱动框架编写,以及3环和0环通信.

 

         内核知识第五讲.驱动框架编写,以及3环和0环通信.

一丶了解内核驱动加载方式

内核加载方式有两种方式.

1.动态加载方式.

2.静态加载方式

动态加载方式:

 

  动态态加载方式则是调用3环API 进行代码加载.

详情请点击 : 内核驱动加载工具的编写.

 

静态加载方式

  静态的加载方式则是利用 后缀为.inf  的文件进行加载.

有关.inf的语法,可以百度或者通过学习WDK中提供的源码例子进行学习.

动态加载一般很常用.

二丶驱动框架的介绍.

 

在讲解内核驱动框架的是否,我们要先了解设备是什么.  设备和驱动之间的数据关系是什么.

1.什么是驱动.什么是设备

驱动:   驱动则是用来操作设备的. 

设备:   设备则是我们常说的外设. 比如键盘.  显示器.  鼠标等等..

其中.任何一个驱动操作设备.  都应该提供公共的方法.

打开.  关闭.  读. 写. 控制....

如图: 

  用户操作设备的是否. 这个时候会通过内核驱动.提供的 回调方法.(也就是公共的接口)进而来操作设备.

 

 2.驱动和设备之间的关系.

驱动和设备之间的关系是一对多的关系.

驱动可以操作很多设备.

比如我们的键盘驱动有一个. 但是可以操作的键盘则有很多个. 你键盘坏了.换了很多.但是驱动则没换过.

所以如果是数据关系的时候.   驱动作为外键放到设备表中.

例如以下:

  

设备

驱动

A键盘

标准驱动

B键盘

标准驱动

有了数据关系.那么就可以讲解驱动框架了.

3.驱动框架的介绍.

驱动对象.设备对象.

在驱动中有这两个概念. 

驱动对象:  简单来说就是一个结构体,存储了驱动的各种信息.

设备对象: 简单来说也是一个结构体,存储的是设备的各种信息.

但依据上面的数据关系来说. 设备对象中肯定会存储驱动对象结构体的指针.  驱动对象做外键存储到设备对象中.

设备对象结构体:

typedef struct _DRIVER_OBJECT {
    CSHORT Type;          //类型
    CSHORT Size;          //当前结构体大小.内核中任何一个结构体都是这两个成员开头.

    //
    // The following links all of the devices created by a single driver
    // together on a list, and the Flags word provides an extensible flag
    // location for driver objects.
    //

    PDEVICE_OBJECT DeviceObject;//设备对象指针,存疑?  不是说数据关系是  设备表中有驱动对象吗. 怎么驱动对象表中有设备对象指针.???????
    ULONG Flags;          //通讯协议以及方式.

    //
    // The following section describes where the driver is loaded.  The count
    // field is used to count the number of times the driver has had its
    // registered reinitialization routine invoked.
    //

    PVOID DriverStart;
    ULONG DriverSize;
    PVOID DriverSection;
    PDRIVER_EXTENSION DriverExtension;

    //
    // The driver name field is used by the error log thread
    // determine the name of the driver that an I/O request is/was bound.
    //

    UNICODE_STRING DriverName;

    //
    // The following section is for registry support.  Thise is a pointer
    // to the path to the hardware information in the registry
    //

    PUNICODE_STRING HardwareDatabase;

    //
    // The following section contains the optional pointer to an array of
    // alternate entry points to a driver for "fast I/O" support.  Fast I/O
    // is performed by invoking the driver routine directly with separate
    // parameters, rather than using the standard IRP call mechanism.  Note
    // that these functions may only be used for synchronous I/O, and when
    // the file is cached.
    //

    PFAST_IO_DISPATCH FastIoDispatch;

    //
    // The following section describes the entry points to this particular
    // driver.  Note that the major function dispatch table must be the last
    // field in the object so that it remains extensible.
    //

    PDRIVER_INITIALIZE DriverInit;
    PDRIVER_STARTIO DriverStartIo;
    PDRIVER_UNLOAD DriverUnload;
    PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];  //提供的公共方法的接口. 创建.打开,关闭.读写控制... 里面存放的是函数指针.单用户操作设备的是否.则会调用这些回调函数指针.

} DRIVER_OBJECT;
typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT; 

存疑部分:

  上面标红的部分. 不是说表关系应该是上面那种吗.?如下图所示

设备

驱动

A键盘

标准驱动

B键盘

标准驱动

可为何设计为这样.

原因:

  我们的内核驱动可以操作设备. 但是我们要知道有多少设备怎么办. 所以这里给了一个设备对象的指针. 而不是我们说的数据关系.

而在设备对象中.存储的则是我们的驱动对象指针.

而这里的指针.则是一个链表形式的. 为了方便遍历.

例如:

  

typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _DEVICE_OBJECT {
    CSHORT Type;
    USHORT Size;
    LONG ReferenceCount;
    struct _DRIVER_OBJECT *DriverObject;  //驱动对象做外键存储
    struct _DEVICE_OBJECT *NextDevice;      //链表.
    struct _DEVICE_OBJECT *AttachedDevice;
    struct _IRP *CurrentIrp;
    PIO_TIMER Timer;
    ULONG Flags;                                // See above:  DO_...
    ULONG Characteristics;                      // See ntioapi:  FILE_...
    __volatile PVPB Vpb;
    PVOID DeviceExtension;
    DEVICE_TYPE DeviceType;
    CCHAR StackSize;
    union {
        LIST_ENTRY ListEntry;
        WAIT_CONTEXT_BLOCK Wcb;
    } Queue;
    ULONG AlignmentRequirement;
    KDEVICE_QUEUE DeviceQueue;
    KDPC Dpc;

    //
    //  The following field is for exclusive use by the filesystem to keep
    //  track of the number of Fsp threads currently using the device
    //

    ULONG ActiveThreadCount;
    PSECURITY_DESCRIPTOR SecurityDescriptor;
    KEVENT DeviceLock;

    USHORT SectorSize;
    USHORT Spare1;

    struct _DEVOBJ_EXTENSION  *DeviceObjectExtension;
    PVOID  Reserved;

} DEVICE_OBJECT;

typedef struct _DEVICE_OBJECT *PDEVICE_OBJECT; 

三丶编写驱动框架.

上面我们已经简单的了解了驱动对象.设备对象是什么了.那么现在开始编写驱动框架

步骤

1.首先注册设备回调函数.当用户对设备进行操作的是否.驱动会调用这个回调函数进行操作.

2.创建设备.创建虚拟的设备给用户使用.

3.指定通讯方式. 什么意思?比如ring3中操作设备进行读写的时候 如果用ReadFile读取.那么你们的通讯方式是否是字符串

4.创建符号连接.

  符号连接: 我们知道.在操作系统下有C盘.D盘一说.但是在驱动下面.则没有怎么一说.只有卷一说.所以我们要绑定一下.

PS: 鉴于篇幅原因.只写重点.如果想要完整的驱动框架. 请下载资料进行查看.

1.注册回调函数.

  pDriverObject->MajorFunction[IRP_MJ_CREATE] = 创建的回调函数指针;
  pDriverObject->MajorFunction[IRP_MJ_CLOSE] =  关闭的回调函数指针;
  pDriverObject->MajorFunction[IRP_MJ_READ] =   读取的回调函数指针;
  pDriverObject->MajorFunction[IRP_MJ_WRITE] =  写入的回调函数指针;
  pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 控制的回调函数指针;

回调函数的写法.

NTSTATUS 自定义的函数名(__in struct _DEVICE_OBJECT *DeviceObject,
                      __inout struct _IRP *Irp)
{
 
  .........
  return STATUS_SUCCESS;
}

 

2.创建虚拟设备.

创建设备等等.都属于IO操作.

IO操作创建设备的API

NTSTATUS 
  IoCreateDevice(
    IN PDRIVER_OBJECT  DriverObject,      //调用者驱动对象的指针.一般是驱动入口的参数
    IN ULONG  DeviceExtensionSize,           //设备扩展大小
    IN PUNICODE_STRING  DeviceName  OPTIONAL, //设备对象名称,注意不是我们ring3下的路径.
    IN DEVICE_TYPE  DeviceType,         //我们的设备类型
    IN ULONG  DeviceCharacteristics,      //驱动的附加信息.
    IN BOOLEAN  Exclusive,            //创建的设备是否其余的可以使用,是否独占
    OUT PDEVICE_OBJECT  *DeviceObject        //传出参数,设备的信息.
    );

注意红点标注:

  在内核中并没有路径一说.所以这个路径是特殊的.

UNICODE_STRING 内核中新的字符串格式.其实是一个结构体.系统提供了操作这种结构体的API

我们拼接一个路径

UNICODE_STRING  uStrDeviceName;

RtlInitUnicodeString(&uStrDeviceName,L"\\Device\\xxx名字即可");

注意,创建设备的时候.我们前边需要加上 \Device.  这里因为是转义字符.所以加了好多\\

拼接好则可以给了.

status = IoCreateDevice(pDriverObject, 
                          0, 
                          &ustrDeviceName,          //设备路径
                          FILE_DEVICE_UNKNOWN,//设备类型设置为不知道
                          FILE_DEVICE_SECURE_OPEN, 
                          FALSE,                           //是否独占
                          &pDevObj);
  if (status != STATUS_SUCCESS)
  {
    return status;
  }        

 

3.设置通讯方式.

pDevObj->Flags |= DO_BUFFERED_IO; //指定通讯方式,为缓冲区

4.创建符号连接

我们创建的符号连接.可以通过 Win0bj工具进行查看. 这个工具可以查看所有设备.但是只有0环才可以操作.

 status = IoCreateSymbolicLink(&g_ustrSymbolName, &ustrDeviceName);
  if (status != STATUS_SUCCESS)
  {
    //删除设备
    IoDeleteDevice(pDevObj);
    return status;
  }

注意符号连接名字.我们也是初始化得出的.

RtlInitUnicodeString(&g_ustrSymbolName, L"\\DosDevices\\xxxx名字");

完整的框架已经写完.剩下的就是 三环和0环驱动通讯. 依赖我们写的框架.

 

四丶三环和0环的通讯.

三环操作设备的API就是 CreateFile ReadFile.....等等.不做介绍了.

利用三环程序.操作我们的设备.从中读取内容.

HANDLE hFile = CreateFile("\\\\?\\符号连接名称", 
             GENERIC_WRITE | GENERIC_READ, 
             0, 
             NULL, 
             OPEN_EXISTING, 
             FILE_ATTRIBUTE_NORMAL, 
             NULL);

打开我们的设备.注意文件名并不是路径.而是我们绑定的符号连接. 这里我们可以写成\\?

读取内容.

char szBuf[10];
ReadFile(hFile, szBuff, sizeof(szBuff), &dwBytes, NULL)

请注意,当读取设备的是否.我们一开始注册的回调函数就会来. 这时候我们给它什么都可以了.

但是此时需要讲解一下通讯协议.

当读取的是否,回调函数会来. 然后操作系统会填写 struct _IRP 结构体.用来和我们的零环通信.

typedef struct _IRP {
  .            //省略了两个成员,这两个成员一个是类型.一个是大小.
  .
  PMDL  MdlAddress;
  ULONG  Flags;
  union {
    struct _IRP  *MasterIrp;
    .
    .
    PVOID  SystemBuffer;//ring3下的缓冲区.操作系统会填写.我们给里面填写什么内容.那么ring3就读取到什么.
  } AssociatedIrp;
  .
  .
  IO_STATUS_BLOCK  IoStatus;
  KPROCESSOR_MODE  RequestorMode;
  BOOLEAN PendingReturned;
  .
  .
  BOOLEAN  Cancel;
  KIRQL  CancelIrql;
  .
  .
  PDRIVER_CANCEL  CancelRoutine;
  PVOID UserBuffer;
  union {
    struct {
    .
    .
    union {
      KDEVICE_QUEUE_ENTRY DeviceQueueEntry;
      struct {
        PVOID  DriverContext[4];
      };
    };
    .
    .
    PETHREAD  Thread;
    .
    .
    LIST_ENTRY  ListEntry;
    .
    .
    } Overlay;
  .
  .
  } Tail;
} IRP, *PIRP;

我们有了缓冲区,但是不知道缓冲区的大小.这个是否需要通过IO函数.从当前栈中获取参数.

IoGetCurrentIrpStackLocation(pIrp)

返回当前IRP的栈.我们从返回值中获取参数即可.

操作完成之后,我们完成IRP请求即可.这个IRP请求主要是为了多线程使用.有的时候可能在读取的是否.线程就切换了.

 

ring0下的读取回调完整编写.

 //获取当前irp堆栈
  PIO_STACK_LOCATION pIrpStack = NULL;
  PVOID lpBuff = NULL;
  ULONG Length = 0;

  //PsGetCurrentThreadId();
  KdBreakPoint();

  __try
  {
    pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
    //check

    lpBuff = pIrp->AssociatedIrp.SystemBuffer;
    Length = pIrpStack->Parameters.Read.Length;
    
    KdPrint(("[FirstWDK] DispatchRead\n"));
    
    RtlStringCbCopyA(lpBuff, Length, "Hello World!");
    //check


    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 6;
    
    //完成Irp请求
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    //check
  }
  __except(1)
  {
  }


  return STATUS_SUCCESS;

 

课堂3环和0环的完整代码资料:

  链接:https://pan.baidu.com/s/1edffGy 密码:vpo0

原创不易,转载请注明出处.

转载于:https://www.cnblogs.com/iBinary/p/8283642.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值