点点滴滴

关于Vicky一切

  DonewsBlog  |  Donews首页  |  Donews社区  |  Donews邮箱  |  我的首页  |  联系作者  |  聚合   |  登录
  275篇文章 :: 1篇收藏:: 157篇评论:: 0个Trackbacks

公告

希望大家看了文章多读评论。对于错,优与劣,只有在交流中才能提高。

文章

收藏

相册

Java链接

Windows底层开发

经营管理

开源项目

跬步千里

朋友

软件工程

书籍下载站

网络

休闲

存档


正在读取评论……


2007年10月04日


    摘要:深入理解IRQL【翻译】    (全文共30129字)——点击此处阅读全文


2007年06月30日


    摘要:文件系统过滤驱动开发知识    (全文共16037字)——点击此处阅读全文


2007年06月29日


    摘要:文件系统过滤驱动开发知识    (全文共21402字)——点击此处阅读全文




    摘要:文件系统过滤驱动开发知识    (全文共677字)——点击此处阅读全文




    摘要:文件系统过滤驱动开发知识    (全文共5893字)——点击此处阅读全文




    摘要:文件系统过滤驱动开发知识    (全文共610字)——点击此处阅读全文


2007年03月20日


    摘要:

这一章讲述当编写Windows2000的任何网络驱动程序时通常要考虑的几点问题。

此处阅读全文



2007年03月18日


    摘要:

 

这一章讲述Windows2000此处阅读全文



2007年03月17日


    摘要:

这一章为网络驱动程序设计指南提供了一个导航,它将以你将编写的内核模式网络驱动程序的类型为基础,告诉你需要参见这个指南的哪些部分。

微软的此处阅读全文



2007年01月04日

    上一节仅仅生成了控制设备对象。但是不要忘记,去冬开发的主要工作是撰写分发历程(dispatch functions)。接上一节,我们已经知道自己的DriverObject保存在上文代码的driver中。现在我写如下一个函数来指定一个默认的dispatch function给它。

//-----------------wdf.h中的代码----------------------

typedef PDRIVER_DISPATCH wd_disp_fuc;

_inline wd_void wd_drv_set_dispatch(in wd_drv * driver , in wd_disp_fuc disp)

{

    wd_size i;

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

    {

        driver->MajorFunction[i] = disp;

    }

}

    在前边的wd_main中,我只要加

wd_drv_set_dispatch(driver , my_dispatch_func);

    就为这个驱动指定了一个默认的Dispatch Function。所有的IRP请求,都会被发送到这个函数。但是,我可能不希望这个函数处理过于复杂,而希望把一些常见的请求独立出来,如Read、Write、Create、Close,那我又写了几个函数专门用来设置这几个Dispatch Functions。

//-----------------wdf.h中的代码----------------------




第二部分:Hello World驱动对象与设备对象

    这里所说的驱动对象是一种数据结构,在DDK中名为DRIVER_OBJECT。任何驱动程序都对应一个DRIVER_OBJECT。如何获得本人所写的驱动对应的DRIVER_OBJECT呢?驱动程序的入口函数为DriverEntry,因此,当你写一个驱动的开始,你会写下如下的代码:

    NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject , IN PUNICODE_STRING RegistryPath )

   {

    }

    这个函数就相当于与喜欢C语言的您所常用的main()。IN是无意义的宏,仅仅表明后面的参数是一种输入,而对应的OUT则代表这个参数是一种返回。这里没有使用引用,因此如果想在参数中返回结果,一律传入指针。

    DriverObject就是您所写的驱动对应的DRIVER_OBJECT,是系统在加载您的驱动时所分配的。RegisteryPath是专用于您记录驱动相关参数的注册表路径。

    DriverObject重要之处,在于它拥有一组函数指针,称为dispatch functions。

    开发驱动的主要任务就是亲手撰写这些dispatch functions。当系统用到你的驱动,会向你的DO发送IRP(这是Windows所有驱动的共同工作方式)。您的任务是在这些dispatch function中处理这些请求。您可以让IRP失败,也可以成功返回,也可以修改这些IRP,甚至可以自己发出IRP。

    设备对象则是指DEVICE_OBJECT。下边简称DO。

    但是实际上每个IRP都是针对DO发出的。只有针对由该驱动所生成的DO的IRP,才会发给该驱动来处理。

    当一个应用程序打开文件并读写文件的时候,Windows系统将这些请求变成IRP发送给文件系统驱动。

    文件系统过滤驱动可以iguolv这些IRP。这样,您就拥有了捕获和改变文件系统的能力。

    像Fat32、NTFS这样的文件系统(File System简称FS),可能生成好几种设备。首先文件系统驱动本身往往生成一个控制设备(CDO)。这个设备的主要任务是修改驱动的内部配置。因此一个Driver只对应一个CDO。

    另一种设备是被这个文件系统Mount的Volume。一个FS可能有多个Volume,也可能一个都没有。解释一下,如果您有C:、D:、E:、F:四个分区。C:、D:为NTFS,E:、F:为Fat32,那么C:、D:则是Fat的两个Volume设备对象。(不是太明白,谁能给解释一下?)

    实际上“C:”是该设备的符号连接(Symbolic Link)名。而不是真正的设备名。可以打开Symbolic Links Viewer,能看到:C: DeviceHarddiskVolume1。因此该设备的设备名为:“DeviceHarddiskVolume1”。

    这里也看出来,文件系统驱动是针对每个Volume来生成一个DeviceObject,而不是针对每个文件的。实际上对文件的读写IRP都发送到Volume设备对象上去了。并不会生成一个“文件设备对象”。

    掌握了这些概括的话,我们现在用简单的代码来生成我们的CDO,作为我们开发文件系统驱动的第一步小试牛刀。

    我不喜欢用微软风格的代码。太长而且难看。我对大部分的数据结构和函数进行了重定义。为此我写了一个名为wdf.h的头文件帮助我转换。有兴趣的读者可以发邮件向我索取这个文件。没有也没有关系,我总是会写出WD_XXX系列的东西在DDK中的原型。

// -----------------wdf_filter.c中的内容-------------------------

#include "wdf.h"

wd_stat wdff_cdo_create(in wd_drv *driver , in wd_size exten_len , in wd_ustr *name , out wd_dev **device)

{

    return wd_dev_create(driver , exten_len , name , wd_dev_disk_fs , wdf_dev_secure_open , wd_false , device);

}

wd_stat wd_main(in wd_drv * driver , in wd_ustr * reg_path)

{

    wd_ustr name;

    wd_stat status = wd_stat_suc;

    //然后我生成控制设备,虽然现在我的控制设备什么都不干

    wd_ustr_init(&name , L"\FileSystem\Filters\our_fs_filter");

    status = wdff_cdo_create(driver , 0 , &name , &g_cdo);

    if (!wd_suc(status))

    {

        if (status == wd_stat_path_not_found)

       {

            //这种情况发生于FileSystemFilters路径不存在。这个路径是

            //在XP上才加上的。所以2000下会运行到这里。

            wd_ustr_init(&name , L"\FileSystem\our_fs_filter");

        }

        if (!wd_suc(status))

        {

            wd_printf0("Error : Create CDO Failed.m");

            return status;

        }

    }

    wd_printf0("Success : Create CDO Ok.m");

    return status;

}

    为了让代码看起来像上边的那样,我不得不作了很多转换。如:#define DriverEntry wd_main

    一种爽得感觉,终于可以在写看起来更像是main()的函数中工作了。wd_dev_create这个函数内部调用的是IoCreateDevice。而wd_suc实际上是SUCCESS()这样的宏。

// ----------------------wdf.h中的内容------------------------------

#include "ntifs.h"

#define in IN
#define out OUT
#define optional     OPTIONAL
#define wd_ustr     UNICODE_STRING
#define wdp_ustr   PUNICODE_STRING
#define wd_main    DriverEntry

// 设备、驱动对象类型
typedef DRIVER_OBJECT   wd_drv;
typedef DEVICE_OBJECT   wd_dev;
typedef PDRIVER_OBJECT wd_pdrv;
typedef PDEVICE_OBJECT wd_pdev;

enum {
              wd_dev_disk_fs        = FILE_DEVICE_DISK_FILE_SYSTEM,
              wd_dev_cdrom_fs    = FILE_DEVICE_CD_ROM_FILE_SYSTEM,
              wd_dev_network_fs = FILE_DEVICE_NETWORK_FILE_SYSTEM
         };

//
状态相关的类型和宏
typedef NTSTATUS wd_stat;

enum {
              wd_stat_suc                             = STATUS_SUCCESS,
              wd_stat_path_not_found          = STATUS_OBJECT_PATH_NOT_FOUND,
              wd_stat_insufficient_res            = STATUS_INSUFFICIENT_RESOURCES,
              wd_stat_invalid_dev_req           = STATUS_INVALID_DEVICE_REQUEST,
              wd_stat_no_such_dev              = STATUS_NO_SUCH_DEVICE,
              wd_stat_image_already_loaded = STATUS_IMAGE_ALREADY_LOADED,
              wd_stat_more_processing         = STATUS_MORE_PROCESSING_REQUIRED,
              wd_stat_pending                       = STATUS_PENDING
          };

    (inline的使用是有所限制的,inline函数一般必须在头文件内,inline只适合函数体内代码简单的函数使用,不能包含复杂的结构控制语句例如while、switch,并且不能内联函数本身不能是直接地跪函数(自己内部还调用自己的函数))

_inline wd_bool wd_suc(wd_stat state)    //内聯函數
{
     return NT_SUCCESS(state);
}

#define wd_printf0 DbgPrint

_inline wd_void wd_ustr_init(in out wd_ustr* str, in const wd_wchar* chars)
{
    RtlInitUnicodeString(str,chars);
};

_inline wd_void wd_ustr_init_em(in out wd_ustr *str ,in wd_wchar *chars, in wd_size size)
{
    RtlInitEmptyUnicodeString(str,chars,size);
};

    wdf.h这个文件我仅仅节选了需要的部分。以上您已经拥有了一个简单的“驱动”的完整的代码。它甚至可以编译、安装(请修改sfilter.inf文件,其方法不过是将多处的sfilter修改为“our_fs_filter”,希望这个过程中您不会出现问题)。然后把wdf.h和wdf_filter.c放在您新建立的目录下,这个目录下还应该有另两个文件。一个是MakeFile,请从SFilter目录下拷贝。另一个是SOURCES,请输入如下内容:

    TARGETNAME = our_fs_filter

    TARGETPATH = obj

    TARGETTYPE = DRIVER

    DRIVERTYPE = FS

    BROWSER_INFO = 1

    SOURCES = wdf_filter.c

    使用DDK编译之后您将得到our_fs_filter.sys。把这个文件与前所描述的inf文件同一目录,按上节所述方法安装。

    这个驱动不起任何作用,但是您已经成功的完成了“Hello World”。



2007年01月03日


    摘要:第一部分:概述,钻研目的和准备    (全文共2214字)——点击此处阅读全文


2006年11月15日

IRP处理的“标准模型”


粒子物理学里有关于宇宙的“标准模型”,WDM也是这样。图5-5显示了一个典型的IRP在各个处理阶段的所有权流程。并不是每种IRP都经过这些步骤,由于设备类型和IRP种类的不同某些步骤会改变或根本不存在。尽管这个过程可能有各种变化形式,但这个图为我们将要展开的讨论提供了一个很好的起点。

图5-5. IRP处理的“标准模型”

创建IRP

IRP开始于某个实体调用I/O管理器函数创建它。在上图中,我使用术语“I/O管理器”来描述这个实体,尽管系统中确实有一个单独的系统部件用于创建IRP。事实上,更精确地说,应该是某个实体创建了IRP,并不是操作系统的某个例程创建了IRP。例如,你的驱动程序有时会创建IRP,而此时出现在图中第一个方框中的实体就应该是你的驱动程序。

可以使用下面任何一种函数创建IRP:

  • IoBuildAsynchronousFsdRequest 创建异步IRP(不需要等待其完成)。该函数和下一个函数仅适用于创建某些类型的IRP。
  • IoBuildSynchronousFsdRequest 创建同步IRP(需要等待其完成)。
  • IoBuildDeviceIoControlRequest 创建一个同步IRP_MJ_DEVICE_CONTROL或IRP_MJ_INTERNAL_DEVICE_CONTROL请求。
  • IoAllocateIrp 创建上面三个函数不支持的其它种类的IRP。

前两个函数中的Fsd表明这些函数专用于文件系统驱动程序(FSD)。虽然FSD是这两个函数的主要使用者,但其它驱动程序也可以调用这些函数。DDK还公开了一个IoMakeAssociatedIrp函数,该函数用于创建某些IRP的从属IRP。WDM驱动程序不应该使用这个函数。

决定该调用哪一个函数,和决定对IRP执行什么额外的初始化是更复杂的问题,我将在本章的结尾再回到这个问题上。

发往派遣例程

创建完IRP后,你可以调用IoGetNextIrpStackLocation函数获得该IRP第一个堆栈单元的指针。然后初始化这个堆栈单元。在初始化过程的最后,你需要填充MajorFunction代码。堆栈单元初始化完成后,就可以调用IoCallDriver函数把IRP发送到设备驱动程序:

PDEVICE_OBJECT DeviceObject; 		//something gives you this
PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(Irp);
stack->MajorFunction = IRP_MJ_Xxx;
<other initialization of "stack">
NTSTATUS status = IoCallDriver(DeviceObject, Irp);

IoCallDriver函数的第一个参数是你在某处获得的设备对象的地址。我将在本章的结尾处描述获得设备对象指针的两个常用方法。在这里,我们先假设你已经有了这个指针。

IRP中的第一个堆栈单元指针被初始化成指向该堆栈单元之前的堆栈单元,因为I/O堆栈实际上是IO_STACK_LOCATION结构数组,你可以认为这个指针被初始化为指向一个不存在的“-1”元素,因此当我们要初始化第一个堆栈单元时我们实际需要的是“下一个”堆栈单元。IoCallDriver将沿着这个堆栈指针找到第0个表项,并提取我们放在那里的主功能代码,在上例中为IRP_MJ_Xxx。然后IoCallDriver函数将利用DriverObject指针找到设备对象中的MajorFunction表。IoCallDriver将使用主功能代码索引这个表,最后调用找到的地址(派遣函数)。

你可以把IoCallDriver函数想象为下面代码:

NTSTATUS IoCallDriver(PDEVICE_OBJECT device, PIRP Irp)
{
  IoSetNextIrpStackLocation(Irp);
  PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
  stack->DeviceObject = device;
  ULONG fcn = stack->MajorFunction;
  PDRIVER_OBJECT driver = device->DriverObject;
  return (*driver->MajorFunction[fcn])(device, Irp);
}

派遣例程的职责

IRP派遣例程的原型看起来像下面这样:

NTSTATUS DispatchXxx(PDEVICE_OBJECT device, PIRP Irp)
{
  PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);				<--1
  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) device->DeviceExtension;			<--2
  ...
  return STATUS_Xxx;											<--3
}

  1. 你通常需要访问当前堆栈单元以确定参数或副功能码。
  2. 你可能还需要访问你创建的设备扩展。
  3. 你将向IoCallDriver函数返回某个NTSTATUS代码,而IoCallDriver函数将把这个状态码返回给它的调用者。

在本书中,我使用DispatchXxx(如DispatchReadDispatchPnp,等等)来代表例子驱动程序中的派遣例程。其它人可能会使用另外的约定,但Microsoft推荐用这样的方法,例如,如果你的驱动程序名为RANDOM.SYS,那么你应该命名IRP_MJ_READ派遣函数为RandomDispatchRead。这个方法使驱动程序调试跟踪起来更容易,但它同时也需要你输入更多的文字。由于这些名称在驱动程序的名空间之外是不可见的,所以由你自己决定是使用Microsoft推荐的命名方案,还是使用你认为更有意义的命名方法。

在上面派遣函数原型中省略号的地方,是派遣函数必须做出决定的地方,有三种选择:

  • 派遣函数立即完成该IRP。
  • 把该IRP传递到处于同一堆栈的下层驱动程序。
  • 排队该IRP以便由这个驱动程序中的其它例程来处理。

我将在本章详细讨论这三种选择,但在这里我仅讨论排队的可能性,因为这个过程就是IRP处理标准模型所描述的。你知道,当有大量读写请求进入设备时,通常需要把这些请求放入一个队列中,以便使硬件访问串行化。

每个设备对象都自带一个请求队列对象,下面是使用这个队列的标准方法:

NTSTATUS DispatchXxx(...)
{
  ...
  IoMarkIrpPending(Irp);								<--1
  IoStartPacket(device, Irp, NULL, NULL);						<--2
  return STATUS_PENDING;								<--3
}

  1. 无论何时,当你的派遣例程返回STATUS_PENDING状态代码时,你应该先调用这个IoMarkIrpPending函数,以帮助I/O管理器避免内部竞争。我们必须在放弃IRP所有权之前做这一点。
  2. 如果设备正忙,IoStartPacket就把请求放到队列中。如果设备空闲,IoStartPacket将把设备置成忙并调用StartIo例程。IoStartPacket的第三个参数是用于排序队列的键(ULONG)的地址,例如磁盘驱动程序将在这里指定一个柱面地址以提供顺序搜索的排队。如过你在这里指定一个NULL,则该请求被加到队列的尾部。最后一个参数是取消例程的地址。我将在本章的后面讨论取消例程,这种例程比较复杂。
  3. 返回STATUS_PENDING以通知调用者我们没有完成这个IRP。

注意,一旦我们调用了IoStartPacket函数,就不要再碰IRP。因为在该函数返回之前,IRP可能已经被完成并且其占用的内存可能被释放,而我们拥有的该IRP的指针也许是无效的。

StartIo例程

每处理一个IRP,I/O管理器就调用一次StartIo例程:

VOID StartIo(PDEVICE_OBJECT device, PIRP Irp)
{
  PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) device->DeviceExtension;
  ...
}

StartIo例程在DISPATCH_LEVEL级上获得控制,这意味着该函数不能生成任何页故障。另外,设备对象的CurrentIrp域和Irp参数都指向I/O管理器送来的IRP。

StartIo的工作是就着手处理IRP。如何做要完全取决于你的设备。通常你需要访问硬件寄存器,但可能有其它例程,如你的中断服务例程,或者是驱动程序中的其它例程也需要访问这些寄存器。实际上,有时着手一个新操作的最容易的方式是在设备扩展中保存某些状态信息,然后伪造一个中断。由于这些方法的执行都需要在一个自旋锁的保护之下,而这个自旋锁与保护你的ISR所使用的是同一个自旋锁,所以正确的方法是调用KeSynchronizeExecution函数。例如:

VOID StartIo(...)
{
  ...
  KeSynchronizeExecution(pdx->InterruptObject, TransferFirst, (PVOID) pdx);
}

BOOLEAN TransferFirst(PVOID context)
{
  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) context;
  ...
  return TRUE;
}

这里的TransferFirst例程是同步关键段(SynchCritSection)的一个例子,之所以这样做是因为StartIo需要与ISR同步。我将在第七章中详细讨论同步关键段(SynchCritSection)的概念。

一旦StartIo使设备忙于处理新请求,它就立即返回。当设备完成传输并发出中断时你将看到下一个请求。

中断服务例程

当设备完成数据传输后,它将以硬件中断形式发出通知。在第七章中,我将讲述如何用IoConnectInterrupt函数“钩住”一个中断,该函数的一个参数就是ISR的地址。因此当中断发生时,硬件抽象层(HAL)就调用你的ISR。ISR运行在DIRQL上,并由ISR专用的自旋锁保护。ISR的函数原型如下:

BOOLEAN OnInterrupt(PKINTERRUPT InterruptObject, PVOID context)
{
  ...
}

ISR的第一个参数是中断对象的地址,中断对象由IoConnectInterrupt函数创建,但是你不太可能用到这个参数。第二个参数是在调用IoConnectInterrupt时你指定的任意上下文值;它可能是设备对象或设备扩展的地址,完全由你决定。

我将在第七章中详细讨论ISR的职责。为了继续标准模型的讨论,我要告诉你一点,一个ISR最可能做的事就是调度DPC例程(推迟过程调用)。而DPC的目的就是让你做某些事情,如调用IoCompleteRequest,而该调用不可能运行在ISR运行的DIRQL级上。所以,你的ISR中将有下面一行语句(device是指向设备对象的指针):

IoRequestDpc(device, device->CurrentIrp, NULL);

那么下一次你将在DPC例程中看到这个IRP,这个DPC例程是你在AddDevice函数中用IoInitializeDpcRequest寄存的。DPC例程的传统名称为DpcForIsr,因为它是由ISR请求的。

DPC例程

DpcForIsr例程在DISPATCH_LEVEL级上获得控制。通常,它的工作就是完成IRP(导致最近的中断发生)。但一般情况下,它通过调用IoCompleteRequest函数把剩余的工作交给完成例程来做。

VOID DpcForIsr(PKDPC Dpc, PDEVICE_OBJECT device, PIRP Irp, PDEVICE_EXTENSION pdx)
{
  ...
  IoStartNextPacket(device, FALSE);						<--1
  IoCompleteRequest(Irp, boost);						<--2
}

  1. IoStartNextPacket 取出设备队列中的下一个IRP并发送到StartIo。FALSE参数指出该IRP不能以通常方式取消。
  2. IoCompleteRequest 完成第一个参数指定的IRP。第二参数是等待线程的优先级提高值。注意在调用IoCompleteRequest之前你还要填充IRP中的IoStatus块。

调用IoCompleteRequest例程是处理I/O请求的标准结束方式。在这个调用之后,I/O管理器(或者是任何在开始处创建该IRP的实体)将再次拥有该IRP。最后该IRP被这个实体销毁并解除等侍线程的阻塞状态。

定制队列

有些设备的操作需要多个请求队列。一个常见的例子就是串行口,它可以同时地并且分开地处理输入输出请求流。IoStartPacket和IoStartNextPacket函数(以及其它含有键排序功能的等价函数)都使用设备对象自带的队列。创建与标准队列有相同工作方式的附加队列要相对容易一些。

为了使我们更容易讨论问题,让我们假设你需要一个单独的队列来管理IRP_MJ_SPECIAL(并不存在这个主功能码,使用它是为了使问题更具体一些)请求。你将写两个与StartIo和DpcForIsr例程功能类似的,但专用于处理这些假想IRP的辅助例程:

  • 与StartIo类似的函数 --- 我们称它为StartIoSpecial --- 它启动下一个IRP_MJ_SPECIAL请求。
  • 与DPC类似的函数 ---  我们称它为DpcSpecial --- 它处理IRP_MJ_SPECIAL请求的完成。

你还需要在你的设备扩展中创建一个KDEVICE_QUEUE对象,并在AddDevice例程中初始化这个队列对象:

NTSTATUS AddDevice(...)
{
  ...
  KeInitializeDeviceQueue(&pdx->dqSpecial);
  ...
}

dqSpecial就是KDEVICE_OBJECT对象的名字,用于排队IRP_MJ_SPECIAL请求。设备队列对象是一种三态对象(见图5-6)。这三种状态反映了设备队列例程是如何操作设备队列的:

  • idle状态是指设备不忙于处理任何请求并且队列为空。KeInsertDeviceQueueKeInsertByKeyDeviceQueue函数把队列标记为busy-empty状态并返回FALSE。你不应该在队列为idle状态时调用KeRemoveDeviceQueueKeRemoveByKeyDeviceQueue函数。
  • busy-empty状态是指设备忙但队列中没有IRP。KeInsertDeviceQueue和KeInsertByKeyDeviceQueue函数向队列尾加入新IRP,使队列进入busy-not empty状态,并返回TRUE。KeRemoveDeviceQueue或KeRemoveByKeyDeviceQueue函数返回NULL并使队列进入idle状态。
  • busy-not empty状态是指设备忙且队列中至少存有一个IRP。KeInsertDeviceQueue和KeInsertByKeyDeviceQueue函数向队列尾加入新IRP并返回TRUE,但队列状态不变。KeRemoveDeviceQueue或KeRemoveByKeyDeviceQueue函数提取队列的第一个IRP并返回其地址,此时,如果队列为空,这些函数将把队列置成busy-empty状态。

图5-6. KDEVICE_QUEUE队列的三种状态

在下面代码中,我们在派遣例程和DPC例程中使用了这些支持例程和我们专用的设备队列:

NTSTATUS DispatchSpecial(PDEVICE_OBJECT fdo, PIRP Irp)
{
  IoMarkIrpPending(Irp);								<--1
  KIRQL oldirql;
  KeRaiseIrql(DISPATCH_LEVEL, &oldirql);						<--2
  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
  if (!KeInsertDeviceQueue(&pdx->dqSpecial, &Irp->Tail.Overlay.DeviceQueueEntry))	<--3
    StartIoSpecial(fdo, Irp);
  KeLowerIrql(oldirql);
  return STATUS_PENDING;
}

VOID DpcSpecial(...)
{
  ...
  PKDEVICE_QUEUE_ENTRY qep = KeRemoveDeviceQueue(&pdx->dqSpecial);			<--4
  if (qep)
    StartIoSpecial(fdo, CONTAINING_RECORD(qep, IRP, Tail.Overlay.DeviceQueueEntry));
  ...
}

  1. 作为一个“规矩”的派遣例程,我们把该IRP标记为pending,因为我们要让它进入队列,然后派遣例程返回STATUS_PENDING状态。
  2. KeInsertDeviceQueue函数和我们自己的StartIoSpecial例程希望在DISPATCH_LEVEL级上被调用。所以我们明确地提升了IRQL,之后很快我们又调用KeLowerIrql函数把IRQL降低到原来的级别(可能是PASSIVE_LEVEL)。
  3. KeInsertDeviceQueue函数把IRP加入到专用队列中,如果返回值为TRUE,表明IRP已被加入队列中,所以我们不用再做任何关于IRP的事。如果设备正空闲,那么返回值应该为FALSE并且IRP也用不着排入队列,我们直接调用StartIoSpecial例程。
  4. DPC中的这个KeRemoveDeviceQueue调用将产生两种结果。如果队列当前为空,则返回值为NULL并且我们也不用启动新请求。否则,返回值将为某IRP内嵌连接域的地址。我们可以使用CONTAINING_RECORD宏来获得外围的真正的IRP地址。然后我们把这个地址传递给StartIoSpecial例程。注意,这个DPC例程已经运行在DISPATCH_LEVEL级上,所以我们不需要在删除队列表项或调用StartIo之前调整IRQL。

我以前描述的StartPacket和StartNextPacket函数使用一个名为DeviceQueue的KDEVICE_QUEUE对象。该对象是设备对象中的一个不透明域,其工作原理与管理私有设备队列相同。





    摘要:IRP数据结构    (全文共5515字)——点击此处阅读全文


2006年06月16日


    摘要:以后填表的时候得填已婚了。哈哈,帅。    (全文共18字)——点击此处阅读全文