zoukankan      html  css  js  c++  java
  • 天书夜读:从汇编语言到Windows内核编程笔记(2)

    内核线程

    在驱动中生成的线程一般是系统线程。系统线程所在的进程名为“System”。

    NTSTATUS

    PsCreateSystemThread(

      OUT PHANDLE ThreadHandle,                          //用来返回句柄,放入一个句柄指针即可

      IN ULONG DesiredAccess,                               //一般总是填写0

      IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,     //NULL

      IN HANDLE ProcessHandle OPTIONAL,             //NULL

      OUT PCLIENT_ID ClientId OPTIONAL,              //NULL

      IN PKSTART_ROUTINE StartRoutine,              //用于该线程启动的时候执行的函数,传入函数名即可

      IN PVOID StartContext);                               //用于传入该函数的参数

    线程的结束应该在线程中自己调用PsTerminateSystemThread来完成。此外得到的句柄也必须要用ZwClose来关闭。关闭句柄并不结束线程。

    例子:

      VOID MyThreadProc(PVOID context)             //我的线程函数,传入一个参数,这个参数是一个字符串

      {  

          PUNICODE_STRING str = (PUNICODESTRING)context;

          KdPrint(("PrintInMyThread:%wZ\r\n",str));

          PsTerminateSystemThread(STATUS_SUCCESS);          //终止一个线程

      }

      VOID MyFunction()

      {

        UNICODE_STRING str = RTL_CONSTANT_STRING(L"Hello!");

        HANDLE thread = NULL;

        status = PsCreateSystemThread(&thread,0L,NULL,NULL,NULL,MyThreadProc,(PVOID)&str);         //开始一个线程

        if(!NT_SUCCESS(status))

        {

          //错如处理

        }

        //如果成功了,可以继续做自己的事,之后得到的句柄要关闭

        ZwClose(thread);

      }

      注意  :MyThreadProc执行的时候,MyFunction可能已经执行完毕了,str就会无效,再执行K的Print去打印str一定会蓝屏。解决方法是在队中分配str的空间,或者str必须在全局空间中。

    睡眠

    #define DELAY_ONE_MICROSECOND (-10)        //10个100纳秒 = 1 微秒, -10表示相对时间

    #define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)

    VOID MySleep(LONG msec)

    {

      LARGE_INTEGER my_interval;

      my_interval.QuadPart = DELAY_ONE_MILLISECOND;

      my_interval.QuadPart *= msec;

      KeDelayExecutionThread(KernelMode,       //表示在内核编程中使用

                    0,       //是否允许线程报警(用于重新唤醒)

                    &my_interval); //表示要睡眠多久

    }

    事件

      内核中的事件是一个数据结构。这个结构的指针可以当作一个参数传入一个等待函数中。如果这个事件不被“设置”,则这个等待函数不会返回,这个线程被阻塞。如果这个事件被“设置”,则等待结束,可以继续下去。

      事件不需要销毁。

      可以发现,关于事件的操作这一部分(如事件的重设,同步等),其原理和MFC中是很类似的,只是MFC中封封装了更好处理的API函数而已。

      实际上等待线程结束并不一定要用事件。线程本身也可以当作一个事件来等待。

     例子:

      KEVENT event;                                                                  //定义一个事件

      KeInitializeEvent(&event,SyschronizationEvent,TRUE);            //事件初始化,SynchoronizationEvent为“自动重设”事件:只有一个线程的wait可以通过,通过之后被自动重设,其他的线程就只能继续等待了;若为NotificationEvent,这个事件必须手动重设KeResetEvent(&event)之后才能使用,否则所有等待线程都通过了。

      KeWaitForSingleObject(&event,Executive,KernelMode,0,0);     //等待这个事件event,直到这个事件被人设置

      

      KeSetEvent(&event);                                                      //在另一个地方,设置这个事件,前面等待的地方将继续执行

    驱动与设备和请求处理

    #include <ntddk.h>

    NTSTATUS

      DriverEntry (

        IN PDRIVER_OBJECT DriverObject,

        IN PUNICODE_STRING RegistryPath

        )

    {

      NTSTATUS status = STATUS_UNSUCCESSFUL;

      return status;

    }

      函数DriverEntry是每个驱动程序中必须的。如同Win32应用程序里的WinMain。

      DriverEntry的第一个参数就是一个 DRIVER_OBJECT的指针。这个DRIVER_OBJECT结构就对应当前编写的驱动程序。其内存是Windows系统已经分配的。

      第二个参数RegistryPath是一个字符串。代表一个注册表子键。这个子键是专门分配给这个驱动程序使用的。用于保存驱动配置信息到注册表中。

      

      DRIVER_OBJECT中含有分发函数指针,分发函数指针的个数为IRP_MJ_MAXIMUM_FUNCTION,保存在一个数组中。这些函数用来处理发到这个驱动的各种请求。Windows总是自己调用DRIVER_OBJECT下的分发函数来处理这些请求。所以编写一个驱动程序,本质就是自己编写这些处理请求的分发函数。

    NTSTATUS

        DriverEntry (

          IN PDRIVER_OBJECT DriverObject,

          IN PUNICODE_STRING RegistryPath

        )

    {

      ULONG i;

      for(i=0;i<IRP_MJ_MAXIMUM_FUNCTION;++i)

      {

        DriverObject->MajorFunctions[i] = MyDispatchFunction;                //分发函数定义,所有的分发函数指针都指向一个分发函数MyDispatchFunction中

      }

    }

    NTSTATUS MyDispatchFunction(PDEVICE_OBJECT device,PIRP irp)               //处理发送给device的irp请求

    {

    ……

    }

    VOID MyDriverUnload(PDRIVER_OBJECT driver)

    {

    ……

    }

    这个函数的地址设置到DriverObject->DriverUnload即可。

    设备与符号链接


    如果驱动程序要和应用程序之间通信,则应该生成设备。此外还必须为设备生成应用程序可以访问的符号链接(就像文件路径一样)

    “\\.\”意味后面是一个符号链接名。 目前生成设备,请总是生成在\Device\目录下。

    例子:

    #include  <ntifs.h>        

    NTSTATUS DriverEntry(

      PDRIVER_OBJECT driver,

      PUNICODE_STRING reg_path)

    {

      NTSTATUS status;

      PDEVICE_OBJECT device;

      UNICODE_STRING device_name = RTL_CONSTANT_STRING("\\Device\\MyCDO");         //设备名

      UNICODE_STRING symb_link = RTL_CONSTANT_STRING("\\DosDevices\\MyCDOSL");         //符号链接名

      status = IoCreateDevice(                                     //生成设备,这个设备必须用系统权限才能访问,IoCreateDeviceSecure可以产生用户权限可以访问的设备

        driver,                                                         //生成设备的驱动对象

        0,      //当用户需要在设备上记录一些额外的信息,就要指定设备扩展区内存的大小,以后就可以从DeviceObject->DeviceExtension中来获取这些信息了

        device_name,                                             //设备名字

        FILE_DEVICE_UNKNOWN,                            //设备类型

        0,

        FALSE,

        &device);

      if(!NT_SUCCESS(status))

        return status;

      status = IoCreateSymbolicLink(

        &symb_link,

        &device_name);

      if(!NT_SUCCESS(status))

      {

        IoDeleteDevice(device);

        return status;

      }

      device->Flags &= ~DO_DEVICE_INITIALIZENG;         //设备生成之后,打开初始化完成标记

      return status;

    }

    符号链接与用户相关性

      某个用户穿件的符号链接只能被该用户访问,系统创建的符号链接可以被所有用户访问。下面的例子生成的符号链接总是随时可以使用:

    UNICODE_STRING device_name;

    UNICODE_STRING symbl_name;

    if(IoIsWdmVersionAvailable(1,0x10))

    {

      RtlInitUnicodeString(&symbl_name,L"\\DosDevices\\Global\\SymbolicLinkName"); 

             //如果是支持符号链接用户相关性的版本的系统,一个用户产生的符号链接只能被这一个用户访问,其它用户则不能访问,所以必须产生全局符号

    }

    else

    {

      RtlInitUnicodeString(&symbl_name,L"\\DosDevices\\SymbolicLinkName");    //如果系统不支持符号链接的用户相关性,则直接创建符号链接即可,所有用户都可以访问

    }

    IoCreateSymbolicLink(&symbl_name,&device_name);

    请求处理

      应用程序为了和驱动通信,首先必须打开设备。然后发送或者接收信息。最后关闭它。这至少需要三个IRP:第一个是打开请求。第二个发送或者接收信息。第三个是关闭请求。

      IRP的种类取决于主功能号。

      应用层调用的API 驱动层收到的IRP主功能号-----即DRIVER_OBJECT中分发函数指针数组中的索引。

      IRP的主功能号在IRP的当前栈空间中;IRP总是发送给一个设备栈,到每个设备上的时候拥有一个“当前栈空间”来保存在这个设备上的请求信息。

      这些功能都有应用层API引发,对应关系如下:

        CreateFile         IRP_MJ_CREATE

        CloseHandle         IRP_MJ_CLOSE

        DeviceIoControl  IRP_MJ_DEVICE_CONTROL

        ReadFile      IRP_MJ_READ

        WriteFile      IRP_MJ_WRITE

    返回时一个IRP成功是一个三部曲

      (1)设置irp->IoStatus.Information为0;

      (2)设置irp->IoStatus.Status的状态;成功或者失败

      (3)调用IoCompleteRequest(irp,IO_NO_INCREMENT),这个函数完成IRP;

      最后返回irp->IoStatus.Status即可。

    打开和关闭请求

      下面的函数能够实现打开和关闭请求:

      NTSTATUS

      MyCreateClose(

        IN PDEVICE_OBJECT device,

        IN PIRP irp)

      {

        irp->IoStatus.Information = 0;

        irp->IoStatus.Status = STATUS_SUCCESS;

        IoCompleteRequest(irp,IO_NO_INCREMENT);

        return irp->IoStatus.Status;

      }

      驱动的分发函数设置如下:

        DriverObject->MajorFunctions[IRP_MJ_CREATE] = MyCreateClose;

        DriverObject->MajorFunctions[IRP_MJ_CLOSE] = MyCreateClose;

    应用层信息的传入

      可以使用WriteFile或者DeviceIoControl(双向的);

      DeviceIoControl:设备控制接口,可以发送一个带有特定控制码的IRP,同时提供输入和输出缓冲区;应用程序可以定义一个控制码,然后把相应的参数填写在输入缓冲区中,同时可以从输出缓冲区得到返回的更多信息。

      驱动获得一个DeviceIoControl产生的IRP的时候,需要获得当前的控制码、输入输出缓冲区的位置和长度;控制码必须预先用一个宏定义:

      #define MY_DVC_IN_CODE \

        (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, \

          0xa01,  \             //自定义的值,其它照抄即可

          METHOD_BUFFERED,  \

          FILE_READ_DATA|FILE_WRITE_DATA)

      可以通过设备的栈空间获得三个要素。

    驱动层信息的传出   

      应用程序开启一个线程,通过调用DeviceIoControl(或者ReadFile)实现该功能,驱动没有消息的时候,则阻塞这个IRP的处理,等待有消息的时候返回。

      具体实现见《天书夜读:从汇编语言到windows内核开发》第92-93页。

    关于上述几项内容的专题论述,请参见相关文档。[5,6]

    参考
    [1] http://www.cnblogs.com/phinecos/archive/2009/02/19/1393803.html
    [2] http://www.cnblogs.com/qsilence/archive/2009/06/11/1501511.html
    [3 http://msdn.microsoft.com/en-us/library/ff557565%28VS.85%29.aspx
    [4] http://www.cnblogs.com/wanghao111/archive/2009/05/25/1489041.html
    [5] Windows驱动编程基础教程.doc
    [6] 天书夜读-从汇编语言到windows内核编程(改)
    [7] Windows DDK

    [8] 天书夜读——从汇编语言到Windows内核编程
    http://download.csdn.net/source/2754275
    http://msdn.microsoft.com/en-us/library/ff557573%28VS.85%29.aspx

  • 相关阅读:
    DIV 模拟模式窗体
    存储过程传递参数时出现类型转换错误!如:varchar转换为int时出错
    数据库改名附加
    VC++动态链接库(DLL)编程深入浅出
    JS获取各种宽度,高度解释
    IE6 中的最大最小寬度和高度 css 高度 控制(兼容版本)
    CSS浏览器兼容大总结
    js获取屏幕宽度高度
    FF下文本无法撑开容器的高度解决办法
    浏览器兼容手册(JS+CSS)
  • 原文地址:https://www.cnblogs.com/forlina/p/2103092.html
Copyright © 2011-2022 走看看