WINCE驱动分析以及MapPtrToProcess 用法 3

时间:2011-09-04

  对于wince 驱动或者linux驱动,或者其他操作系统驱动。基本上就是两个部分,1,访问硬件寄存器。2,编写操作系统接口。从访问硬件上来说,可以有总线驱动,一般的I/O驱动等。从操作系统来说,就wince而言,可以分为,built-driver和stream driver,也可以从另外的一个角度,分为 Layered Device Driver 和 Monolithic driver。总之,驱动的叫法和种类很多,一般都要根据具体开发的驱动再仔细研究。其实大多数工作都是要看E文的datasheet,了解硬件的工作原理,对CPU工作方式有一定的理解,问题就不大了。这里简单介绍一下如何编写wince下的stream driver。

  首先讲一下框架,很简单,按照wince的联机帮助文档,完成下面的接口函数就好了。

  XXX_Close (Device Manager)

  XXX_Deinit (Device Manager)

  XXX_Init (Device Manager)

  XXX_IOControl (Device Manager)

  XXX_Open (Device Manager)

  ……

    无非就是一些读写,IOCTL,POWER管理的接口而已。

    通过前面的学习我们已经对MapPtrToProcess 用法 WINCE驱动分析有了初步了解,接下来我们要使用下面的应用程序代码测试这个driver,我们将使用evc编译。

  #include <windows.h>

  #include<Windev.h>

  #include <stdio.h>

  #include "objbase.h"

  #include "initguid.h"

  #include "foo.h"

  //char data1[10];

  int  WinMain(void)

  {

  HANDLE hnd;

  COPY_STRUCT cs[1];

  int i;

  //static char data1[10];

  auto char data1[10];

  auto char data2[10];

  static char* p1,*p2;

  //cs.pBuffer1 = (char *)malloc(10);

  //cs.pBuffer2 = (char*)malloc(10);

  //cs.nLen = 10;

  p1 = (char *)LocalAlloc(LPTR,10);

  p2 = (char *)malloc(10);

  //cs[0].pBuffer1 = (char *)malloc(10);

  //cs[0].pBuffer2 = (char*)malloc(10);

  cs[0].pBuffer1 = &data1[0];

  cs[0].pBuffer2 = &data2[0];

  cs[0].nLen = 10;

  memset(cs[0].pBuffer1,'a',10);

  hnd = CreateFile(FOO_DEV_NAME,GENERIC_READ|GENERIC_WRITE,0,NULL,0,0,NULL);

  if(hnd==NULL)

  {

  printf("Open device fALIed!\n");

  return;

  }

  DeviceIoControl(hnd,IOCTL_FOO_XER,&cs[0],sizeof(COPY_STRUCT),NULL,0,NULL,NULL);

  //for(i=0;i<9;i++)

  //{

  //printf(" %c",*(cs.pBuffer2++));

  //}

  printf("\n");

  CloseHandle(hnd);

  //  free(cs[0].pBuffer1);

  //  free(cs[0].pBuffer2);

  }

  我们可以通过evc的单步调试看结果。等到一切都完成了,我们就来看看系统是怎么工作的吧,从应用程序开始,

  CreateFile(FOO_DEV_NAME,GENERIC_READ|GENERIC_WRITE,0,NULL,0,0,NULL);

  会调用到

  FOO_Open(DWORD dwContext, DWORD AccessCode, DWORD ShareMode)

  而FOO_DEV_NAME名字定义在foo.h里面。

  #define       FOO_DEV_NAME L"Foo1:"

  注意后面是 1 ,这个是和注册表的这一项匹配的,去注册表确认下以保证万无一失。

  "Index"=dword:1

  当调用CreateFile发生了什么呢?slot之间的转换,一系列系统操作后,调用到我们自己的driver函数FOO_Open,在这个函数里我们返回了一个句柄,它可以用来存储我们的自己driver的信息。在其它I/O操作中可以使用。

  Driver什么时候加载的?在注册表里,device manager会一个个的加载,会调用到FOO_Init函数。这个函数返回一个指针,在调用FOO_Open又传回来了,这样我们就可以实现初始化一些自己driver的东西。

  接着我们来说一个重要的函数,

  DeviceIoControl(hnd,IOCTL_FOO_XER,&cs[0],sizeof(COPY_STRUCT),NULL,0,NULL,NULL);

  调用到

  FOO_IOControl

  走到这里

  case IOCTL_FOO_XER:

  if((pInBuf==NULL))

  {

  SetLastError(ERROR_INVALID_PARAMETER);

  break;

  }

  pcs = (COPY_STRUCT*)pInBuf;

  __try{

  pMap1 =  MapPtrToProcess(pcs->pBuffer1,GetCallerProcess());

  pMap2 =  MapPtrToProcess(pcs->pBuffer2,GetCallerProcess());

  DEBUG_OUT(1, (TEXT("+FOO_IOControl(0x%x,0x%x)\r\n"),pcs->pBuffer1,pcs->pBuffer2));

  memcpy(pcs->pBuffer2,pcs->pBuffer1,pcs->nLen);

  bResult = TRUE;

  }

  __except(EXCEPTION_EXECUTE_HANDLER){

  DEBUG_OUT(1,(TEXT("Exception:FOO_IOCTL\r\n")));

  break;

  }

  break;

  default:

  break;

  这里又很多东西要研究,这里我就不做详细解说了。

  从应用程序传来的参数有, control code,IOCTL_FOO_XER和一个重要的输入参数&cs[0],它是一个指针。cs 是一个结构体,定义在FOO.H

  typedef struct {

  char* pBuffer1;

  char* pBuffer2;

  int nLen;

  }COPY_STRUCT;

  而且这个结构体里有两个指针。

  DeviceIoControl 传过来的指针可以用吗?它包含的两个指针可以直接用吗?

  按照PB连接帮助文档看,

  The operating system (OS ) manages pointers passed directly as parameters. Drivers must map all pointers contained in structures. DeviceIoControl buffers are often structures that contain data, some of which might be pointers.

  You can map a pointer contained in a structure by calling MapPtrToProcess, setting the first parameter to the pointer, and then setting the second parameter to GetCallerProcess.

  cs指针已经映射好了,但是它指向的结构里的指针我们需要自己使用MapPtrToProcess函数映射。

  这也就是:

  pMap1 =  MapPtrToProcess(pcs->pBuffer1,GetCallerProcess());

  pMap2 =  MapPtrToProcess(pcs->pBuffer2,GetCallerProcess());

  的由来,可是后面的代码没有使用pMap1,pMap2。而是直接使用:

  memcpy(pcs->pBuffer2,pcs->pBuffer1,pcs->nLen);

  而且它还工作了,没有出现exception。很奇怪。我次在一个家伙的代码里看见这种情况,很吃惊,但是它工作的很好,是文档出错了?还是别的地方出错了呢?

  那么我们就来分析一下吧。首先我们看看应用程序的代码:

  COPY_STRUCT cs[1];

  auto char data1[10];

  auto char data2[10];

  cs结构和data1,data2数组都是自动变量,存放在堆栈里。假设这个应用程序被加载到0x18000000位置的slot里,那么他们的地址都是0x18XXXXXX。不熟悉wince memory architecture的可以看看资料,了解一下slot。当调用了。

  DeviceIoControl,按照文档的说法,cs指针得到了转换,因为从应用程序的进程转到了device.exe进程,而device进程又是当前的运行的进程,被映射到了slot0,系统负责转换cs指针。而cs包含的pBuffer1和pBuffer2是没有映射不能直接用的。

  事实上,我们传过来的指针根本就是不需要映射,因为他们都是0x18xxxxxx,在应用程序的slot里,所以只要device.exe有访问应用程序的权限,就可以了。而这个权限,系统已经帮我们设置好了。

  那什么情况下要自己映射呢?

  如果应用程序在定义 data1和data2使用static关键字,或者使用LocalAlloc,HeapAlloc的时候,一定要自己映射cs里的指针。

  在应用程序里这样写:

  cs.pBuffer1 = (char *)malloc(10);

  cs.pBuffer2 = (char*)malloc(10);

  cs.nLen = 10;

  如果不使用MapPtrToProcess完成映射,那就出现data abort exception.这是为什么呢?

  因为这些变量都是在堆里分配的,而当应用程序运行时,被映射到slot0,堆的地址也就是处于slot的范围内,传递到device.exe后,device.exe被映射到了slot0,这个时候必须要将应用程序的指针映射回应用程序所在的slot。否则访问的是device.exe的空间,会发生不可知道的结果。

  现在我们来验证一下上面说的地址分配问题。

  我们这样定义:

  COPY_STRUCT cs[1];

  static char data1[10]; 堆里

  auto char data2[10];   栈里

  这样赋值:

  cs[0].pBuffer1 = &data1[0];

  cs[0].pBuffer2 = &data2[0];

  cs[0].nLen = 10;

  调试信息:

  cs[0].pBuffer1 = &data1[0];

  180112D0   ldr       r2, [pc, #0xD0]

  180112D4   str       r2, [sp, #0x10]

  读取&data1[0]使用的是PC作为基址,而此时的应用程序处于运行阶段映射到slot0,那么pc也就在0~01ffffff范围,我的调试结果是在0x000112D0+8,使用的是arm,流水线机制,当前指令地址+8才是pc值。

  143:      cs[0].pBuffer2 = &data2[0];

  180112D8   add       r0, sp, #0x20

  180112DC   str       r0, [sp, #0x14]

  读取&data2[0]采用的是sp作为基址,sp在应用程序加载到slot的时候就确定了的。所以保持了在应用程序slot的值,处于0x18xxxxxx范围。

  我们看到因为wince的slot机制,我们有时候需要映射,有时候不需要。所以wince文档说结构里的指针要映射。毕竟你不知道应用程序怎么写。

  当然,你可以根本不映射,只要把那个结构屏蔽调,写一个STATIC LIBRARY给用户使用,自己保证使用正确的地址分配就可以了。上面我说的那个家伙就是这么干的。

  好了,接着调用:

  CloseHandle(hnd);

  程序就这么结束了,完成了我们进行简单的拷贝就OK了。

  这个框架完成了,driver的基本接口设计,强调了内存指针的使用问题。但是相对于一个真正的driver,还缺少点东西,就是访问硬件的方法。不过我们还是先了解下驱动程序和应用程序之间传递数据时何时调用MapPtrToProcess?

  因为设备管理器负责加载驱动程序DLL,这意味着当应用程序调用驱动程序接口函数的时候,WINCE内核会将调用驱动程序接口函数的线程转移到设备管理器的进程空间然后执行具体的驱动程序代码,应用程序和设备管理器处于两个进程空间,这就造成设备管理器无法访问应用程序传递的指针(虚拟地址),所以当我们在应用程序中传递指针给流驱动程序接口函数时,WINCE内核从中作了一个地址映射,例如ReadFile、WriteFile、DeviceIoControl函数的参数凡是指针都经过了映射才传递给驱动程序,所以很多驱动程序开发者并不了解其中的奥秘就可以编程了。但是如果参数是一个指向一个结构体的指针,而结构体里包括一个或多个指针,那么WINCE内核并不负责映射,所以就需要开发者在驱动程序接口函数中调用API函数MapPtrToProcess来映射地址。例如:pPointer_retval = MapPtrToProcess(pPointer, GetCallerProcess());

  关于MapPtrToProcess 用法 WINCE驱动分析的学习又要告一段落了。我们使用evc编译应用程序代码测试driver。希望大家在学习中举一反三学到更多的东西。当然也许本文还不够完善,在接下来的学习中我们会更加深入地学习MapPtrToProcess 用法 WINCE驱动分析。如果大家有更好的例子可以发出来。学无止境,我的并不一定是的,我只希望对需要这方面知识的人有所帮助,我就满足了。接下来我们要说的就是对一个真正的driver还缺少的东西——访问硬件的方法。希望感兴趣的人多多关注。



  
上一篇:用跳频与扩频对无线机器对机器接口进行设计
下一篇:Windows Embedded开发资源汇总

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

相关技术资料