内核知识第七讲,内核中设备常用的三种通信方式,以及控制回调的编写

2022-05-10 13:16:41 浏览数 (1)

        内核知识第七讲,内核中设备常用的三种通信方式,以及控制回调的编写

一丶ring3和ring0下的三种通讯方式

ring3和ring0下有常用三种通信方式:

1.缓冲区通信方式

2.直接IO通信方式

3.其它通信方式

缓冲区通信方式

我们的ring3和ring0通讯的时候.ring3会给一个虚拟地址. 然后内核中的参数会通过IRP来获取.

其中有个缓冲区. 我们只要操作这个缓冲区.那么对应的就是操作了三环的缓冲区.

例如:

当我们三环和0环通信的时候, 3环如果选择的是缓冲区通信. 那么久类似于上图.  操作系统会在高2G申请一个额外的缓冲区.

然后ring3下的缓冲区拷贝到里面. 然后我们的内核程序操作这个缓冲区之后. 操作系统将这个缓冲区的数据重新写入到ring3下的虚拟缓冲区中.

优点:

  优点是安全.操作系统会创建一个中间层的缓冲区让我们进行操作.然后操作中间层就相当于操作ring3的缓冲区.

缺点:

  高2G内核中的内存是很宝贵的.如果我们交互的时候.传出的数据太大.那么就会消耗计算机内存资源.

如果想看完整流程图,请查看WDK的帮助文档.

2.直接IO通信方式

  为什么叫做直接通信方式. 原因是 ring3可以直接和ring0进行通讯了.不需要额外的缓冲区进行操作.

ring3的虚拟内存会通过内存映射的方式.映射到高2G的内存. 然后ring3的虚拟内存进行保护. 这样我们操作ring0的高2G缓冲区.就相当于操作ring3的缓冲区.

优点:

  如果数据量比较大.可以使用这种

缺点:

  不安全.如果我们的ring3不进行保护.那么通过C语言进行对ring3缓冲区越界访问.那么就相当于访问ring0的物理内存了.

不过幸好.内核中提供了宏让我们自己进行操作.

代码语言:javascript复制
lpBuff = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority);

需要注意的是我们这种通信方式获取的缓冲区不是 IRP中的 SystemBuf;

3.其它通信方式

其它通信方式,这是直接使用用户的虚拟内存,也就是IRP中的 userBuf;

二丶控制回调的编写

以前我们操作设备的时候. 都是通过Read或者Write去操作.

但是现在我们有控制了. 

这个时候我们要控制设备,就要编写控制码.

控制码:

代码语言:javascript复制
CTL_CODE(FILE_DEVICE_UNKNOWN, (CODE_BASE (code)), METHOD_BUFFERED, FILE_ANY_ACCESS)

VC 6.0给了一个宏.而NTDDK.h中也有这个. 如果你配置好了环境,那么你就要用VC中提供的了.

控制码的格式:

设备类型,控制码,通讯方式. 权限.

如果用我们的上面的宏,则填写即可.内部会自己进行移位运算.

完整代码:

代码语言:javascript复制
 PIO_STACK_LOCATION pIrpStack = NULL;
  PVOID lpBuff = NULL;
  ULONG IoControlCode;            //获取控制的控制码
  ULONG InputBufferLength;         //用户输入的缓冲区,这个缓冲区一般做参数
  ULONG OutputBufferLength;        //ring0返回出去的缓冲区.一般做写出.
  NTSTATUS status = STATUS_UNSUCCESSFUL;//各种变量.暂时不用管.
  ULONG bytes = 0;

  KdBreakPoint();
  
  KdPrint(("[FirstWDK] DispatchControl PID:%d TID:%dn", PsGetCurrentProcessId(), PsGetCurrentThreadId()));


  pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
  InputBufferLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;//获取长度
  OutputBufferLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;//获取输出缓冲区
  IoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;//获取控制码
  lpBuff = pIrp->AssociatedIrp.SystemBuffer;

  switch(IoControlCode)
  {
  case MYCTL_GET_GDT_SIZE:      //自定义的控制码
    {
      break;
    }
  case MYCTL_GET_GDT:
    {
      PCONTORL_PARAMS pParams = (PCONTORL_PARAMS)lpBuff;
      char szGDT[6];

      if (InputBufferLength < sizeof(CONTORL_PARAMS))
        break;

      __asm
      {
        sgdt szGDT
      }
      KdPrint(("limit:%p  GDT:%pn", *(short*)szGDT, *(int*)(szGDT   2)));
      if (OutputBufferLength >= sizeof(szGDT))
      {
        RtlCopyMemory(lpBuff, szGDT, sizeof(szGDT));
        bytes = sizeof(szGDT);
        status = STATUS_SUCCESS;
      }
      break;
    }
  case MYCTL_GET_LDT:
    {
      break;
    }
  }

 
  
  pIrp->IoStatus.Status = status;
  pIrp->IoStatus.Information = bytes;
  IoCompleteRequest(pIrp, IO_NO_INCREMENT);
  
  return status;
}

关于自定义的控制码.

这些本质来说就是我们的宏替换.

代码语言:javascript复制
#define CODE_BASE 0x800
#define MY_CTL_CODE(code)  CTL_CODE(FILE_DEVICE_UNKNOWN, (CODE_BASE (code)), METHOD_BUFFERED, FILE_ANY_ACCESS)

#define MYCTL_GET_GDT      MY_CTL_CODE(1)  
#define MYCTL_GET_GDT_SIZE MY_CTL_CODE(2) 
#define MYCTL_GET_LDT      MY_CTL_CODE(3) 

我们每次定义,都要写CTL_CODE.很麻烦.所以我们用心的宏替换一下即可.

PS:

  当控制码为缓冲区方式,直接方式.以及其它方式的时候.我们分别从IRP中获取的参数缓冲区是不同的.

1.当我们的控制码给定的是缓冲区通信方式

  如果是缓冲区通信方式,那么获得的就是IRP中的SystemBuf.   这个缓冲区可以当做ring3传递的参数.然后你往这个缓冲区写数据,则是传出的数据

2.当我们的控制码为直接IO的方式

  如果是直接IO的方式.那么你要二选一. 如果你指定了用户的输入缓冲区为直接IO方式,那么对应的输出缓冲区则是缓冲区方式,

  那么用户缓冲区的获得方式就是使用上面介绍直接方式的API进行获取了.

例如:

代码语言:javascript复制
lpBuff = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority);

3.如果我们的控制码为其它方式

  如果是其它方式,那么用户的缓冲区是 IRP中的UserBuf缓冲区.而输入缓冲区则是用IRP中的.Type3InputBuffer

缓冲区.

0 人点赞