应用层和驱动层的同步与异步的处理逻辑及底层实现

2023-05-16

应用层的实现:

 

1 ReadFile、WriteFile、DeviceIoControl等,这些都有两种操作方式,一种是同步,一种是异步。

 

操作设备的Win32API主要是这3个函数ReadFileWriteFileDeviceIoControl

 

以DeviceIOControl为例,它的同步&异步操作如下:

 

同步操作时,它的内部会创建一个IRP_MJ_DEVICE_CONTROL类型的IRP,并将这个IRP传送到驱动程序的派遣函数中。处理该IRP需要一段时间,直到IRP处理完毕后,DeviceIoControl函数才会返回

异步操作时,此时的DeviceIoControl函数不会等待IRP结束,而是直接返回。当IRP经过一段时间被结束时,OS会触发一个IRP相关事件,这个事件可以通知应用程序IRP请求被执行完毕

 

Win32 API 的异步操作还必须得到驱动程序的支持,如果驱动程序不支持异步操作,win32API的异步操作就会失败

 

2 IRP的同步完成:同步操作时,在这3个函数的内部,会调用WaitForSingleObject函数去等待一个事件。这个事件直到IRP被结束时,才会被触发。DeviceIoControl内部,会调用WaitForSingleObject函数去等待一个事件,这个事件直到IRP被结束时才会被触发,如果通过反汇编IoCompleteRequest内核函数,就会发现在这个函数内部设置了该事件。

 

3 IRP的“异步完成”:指的是这3个函数会立即返回,而发起IRPwin32函数会有三种形式发起IRP请求:(异步完成:如果派遣函数不调用IoCompleteRequest函数,则需要告诉操作系统此IRP处于“悬挂”状态。这需要调用内核函数IoMarkIrpPending。同时,派遣函数应该返回STATUS_PENDING)

 

【建立在:IRP被“异步完成”时,返回的都是STATUS_PENDING】

 

第一种:用ReadFile函数进行同步读取时 (返回 STATUS_PENDING,这时IO管理器不会激活IRP的UserEvent域并挂起该IRP,线程仍然处于睡眠中)

 

AReadFile函数内部会创建一个事件,这个事件连同IRP一起被传递到派遣函数中(这个事件是IRP的UserEvent域)

B 如果在派遣函数中没有调用IoCompleteRequest函数,该事件就没有被设置,ReadFile会一直等IRP被结束

 

第二种:用ReadFile函数进行异步读取时 (返回 STATUS_PENDING,并挂起该IRP,ReadFile函数立即返回,完成该IRP时IO管理器会激活事件,该事件的作用相当于告知一下应用程序该IRP已经完成处理)

 

      A这时ReadFile内部不会创建事件,但ReadFile函数会接收overlap参数overlap参数中会提供一个事件,这个事件被用作同步处理。IoCompleteRequest内部会设置overlap提供的事件。

B 在ReadFile函数退出前,它不会检测该事件是否被设置,因此可以不等待操作是否完成,与第一种的区别就是不会一直停留在ReadFile函数中,ReadFile函数会马上返回,这时调用GetLastError获得的错误码是ERROR_IO_PENDING

C 当IRP操作被完成后,overlap提供的事件被设置,这个事件会通知应用程序IRP请求被完成

 

HANDLE hDevice =CreateFile("test.dat",GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,/*此处设置FILE_FLAG_OVERLAPPED*/,NULL );

 

        if(hDevice == INVALID_HANDLE_VALUE)

        {

                printf("Read Error\n");

                return 1;

        }

 

        UCHAR buffer[BUFFER_SIZE];

        DWORD dwRead;

 

        //初始化overlap使其内部全部为零

        OVERLAPPED overlap={0};

 

        //创建overlap事件

        overlap.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);

 

        //这里没有设置OVERLAP参数,因此是异步操作

        ReadFile(hDevice,buffer,BUFFER_SIZE,&dwRead,&overlap);

 

        //做一些其他操作,这些操作会与读设备并行执行,这就是异步的作用,呵呵

 

        //等待读设备结束

        WaitForSingleObject(overlap.hEvent,INFINITE);

 

        CloseHandle(hDevice);

 

第三种:用ReadFileEx函数进行异步读取时 (注意这里的完成函数是针对用户层的

 

A ReadFileEx函数不提供事件,但提供一个回调函数,这个回调函数的地址会作为IRP的参数传递给派遣函数

B 当处理完IRP并调用IoCompleteRequest时会将这个完成函数插入APC队列。应用程序只要进入警惕模式,APC队列会自动出队列,完成函数会被执行,这相当于内核通知应用程序IRP操作已经完成 。(APC的作用是等到调用者的线程里运行完成函数,这样完成函数才能使用R3中的数据)

C 与第二种的相同点是都是会返回 STATUS_PENDING,并挂起该IRP,ReadFileEx函数立即返回,这时调用GetLastError获得的错误码是ERROR_IO_PENDING,不同点是没有事件了,因为当回调函数被调用时就相当于通知了应用程序该IRP已经完成了处理

 

重点:若调用了IoMarkIrpPending就会设置了栈顶标志,IoCompleteRequest投递APC是根据栈顶的标志位,该标志位通过IoMarkIrpPending设置并一层层传递到栈顶,APC用于设置事件&投递ACP函数

 

【归纳】

 

用户层的同步指的是线程会阻塞等待函数的返回,这里主要是等待驱动调用IoCompleteRequest为止。

 

线程阻塞:针对第一种情况的同步读取(即等待驱动调用IoCompleteRequest)

 

用户层的异步指的是线程会马上返回。等驱动调用IoCompleteRequest在通知用户层(内核通过事件&用户层的回调函数来通知应用层)。

 

线程不阻塞:针对第二&三种情况的异步读取(即驱动返回调用IoMarkIrpPending并返回STATUS_PENDING),等驱动调用IoCompleteRequest后,会通过R3传递下来的事件&回调函数来通知应用程序驱动已经完成该IRP的处理

驱动层的实现:

 

1 分层驱动是指两个或两个以上的驱动程序,它们分别创建设备对象,并且形成一个由高到低的设备对象栈。

 

2 IRP请求一般会被传送到设备栈的最顶层的设备对象,顶层的设备对象可以选择直接结束IRP请求,也可以选择将IRP请求向下层的设备对象转发

 

3 如果是向下层设备对象转发IRP请求,当IRP请求结束时,IRP会顺着设备栈的方向原路返回,当得知下层驱动程序已经结束IRP请求时,本层设备对象可以选择继续将IRP向上返回,或者选择重新将IRP再次传递给底层设备驱动

 

4 向设备栈挂接用IoAttachDeviceToDeviceStack,而从设备栈中弹出是IoDetachDevice

 

在Windows分层驱动模型中,设备栈中的设备一般都是通过对上层传来的IRP做相应的处理来实现驱动的功能。

 

这里对常用的几种IRP传递及完成的方式进行归纳和总结:

 

1. 在本层驱动中完成

 

1.1 在本层驱动中以同步方式完成

在本层同步完成一般做完相应处理后,设置Irp->IoStatus.Status和Irp->IoStatus.Information,调用IoCompleteRequest完成该IRP,returnIRP的完成状态即可。

 

1.2 在本层驱动中以异步方式完成 在本层异步完成一般是得到IRP后将其入队/起线程另行处理,同时调用IoMarkIrpPending将该IRP标记为Pending,之后即可returnSTATUS_PENDING。此时该IRP并未真正完成,等时机成熟后(R3返回处理结果)再调用IoCompleteRequest来真正完成或调用IoCallDriverIrp继续向下传递,由下层驱动负责完成它(Tdi驱动的处理方式

在返回STATUS_PENDING前,可将该IRP存入到链表&自定义的扩展结构中等,随后在其他IRP的派遣函数&线程&定时器(需在返回PENDING的派遣函数中SET定时器)等中再处理

 

【Example:本层驱动中以异步方式完成】

 

过滤驱动的做法是先存入链表再挂起,等进行逻辑判断(通过应用层的反馈来决定)后再决定是否将该IRP向下转发并进行处理 (应该归属于在本层驱动中的异步完成,只不过完成该IRP的IoCompleteRequest改成了IoCallDriver)

 

if(filterResult== tfrProxy)//返回 if(filterResult == tfrProxy)凡是代理的就先让这个IRP等待,在CompletingDelayedIrpLIst中根据用户层的回调函数的结果再完成它

{

FilterDebugPrint(("IoMarkIrpPending! 0x%X\n", irps->MinorFunction));

if((irp->CurrentLocation<= irp->StackCount + 1))

            IoMarkIrpPending(irp);//在CompletingDelayedIrpLIst中根据用户层的回调函数的结果在完成它

else

{

。。。。。

return STATUS_PENDING;//调用IoCallDriverIrp继续向下传递,由下层驱动负责完成它

 

 

设备堆栈

1

TCP过滤设备 IoMarkIrpPending + return PENDING:说明不见得一定是最后一个设备才能返回PENDING

2

TCP设备

 

凡是堆栈上设置了PENDING标志位后,当调用IoCompleteRequest时,系统都会调度APC来使那个事件变成通知状态的,对于某些IRP,IoCompleteRequest会毫无条件地调度APC,而对于某些IRP,IoCompleteRequest会检查堆栈顶部IO_STACK_LOCATION中是否设置了SL_PENDING_RETURNED标志,只有这个标志被设置时才调度APC。

(超重点:凡是驱动返回了return PENDING就代表告诉系统整个IRP是异步完成, 系统通过APC+堆栈上的PENDING标志位来实现异步完成)

 

2. 转发至下层驱动

 

2.1 本层不作处理(仅适用于第一种情况) (效果图:DriverTool- Tool - IRPTrace - 9 Skipped)

        有时对于某些IRP,本层驱动不需要做任何处理。此时可调用 IoSkipCurrentIrpStackLocation 跳过当前设备栈(这个函数的主要作用是避免了向下层拷贝堆栈的操作),然后调用IoCallDriver将IRP转发至下层驱动,并将转发的结果直接返回。此种处理方式并不需要关心下层驱动处理IRP的方式究竟是同步还是异步。IRP转发至下层后就与己无关了。

 

有两种方式来调用下层驱动:

1.调用IoSkipCurrentIrpStackLocation和IoCallDriver。通常如果当前设备对象不需要处理irp,我们可以这么调用。

2. 调用IoCopyCurrentIrpStackLocationToNext和IoCallDriver。通常如果当前设备也需要处理irp,那么我们可以等处理完后,将irp拷贝到下层驱动,然后再调用IoCallDriver。

 

2.2 同步转发方式

 

同步:等待处理完后再返回

 

【同步转发方式】

 

事件是实现同步转发方式的基础,因为可以通过KeWaitForSingleObject实现等待,两种等待事件的方法:(若判断ntStatus== STATUS_PENDING,则必须在完成函数中传递PEDING位)

 

第一种方法:适用于当前的派遣函数也是处理该IRP的其中一层,支持在完成函数中再次获得该IRP的控制权,然后需要调用IoCompleteRequest来重新完成该IRP

 

//初始化事件

KeInitializeEvent(&event,NotificationEvent, FALSE);

 

//设置完成例程

IoSetCompletionRoutine(pIrp,MyIoCompletion,&event,TRUE,TRUE,TRUE);

 

ntStatus= IoCallDriver(pdx->TargetDevice, pIrp);

 

if(ntStatus == STATUS_PENDING) //若底层驱动返回的是STATUS_SUCCESS,则这里不会执行,也不会永远等待

{

KdPrint(("IoCallDriverreturn STATUS_PENDING,Waiting ...\n"));

KeWaitForSingleObject(&event,Executive,KernelMode ,FALSE,NULL);//完成函数会激活该事件,并返回STATUS_MORE_PROCESSING_REQUIRED,使得重新获得对此IRP的控制权

ntStatus = pIrp->IoStatus.Status;//得到底层的处理结果

}

 

 

NTSTATUSMyIoCompletion(IN PDEVICE_OBJECT DeviceObject,IN PIRP  Irp,INPVOID  Context)

{

if(Irp->PendingReturned == TRUE)

{

KeSetEvent((PKEVENT)Context,IO_NO_INCREMENT, FALSE);

}

 

returnSTATUS_MORE_PROCESSING_REQUIRED;//可重新获得对IRP的控制权

}

 

 

第二种方法:适用于在当前的派遣函数新构建IRP并发向另一个设备栈,因为IRP发向了另一个IRP堆栈,所以在回调函数中不支持返回STATUS_MORE_PROCESSING_REQUIRED,此派遣函数只能等待另一个设备堆栈的处理结果。

 

1、用于同步的事件通过所发向的设备栈,在设备栈对应的派遣函数中调用IoCompleteRequest来激活该事件。这时只需要等待此事件激活即可。当附加在IRP上的事件被激活后,可以通过附加在query_irp->UserIosb上的status变量来得到处理函数对该IRP的处理结果。

2、若所发向的设备栈设置了PEDING,则事件的激活是IRP被调用IoCompleteRequest结束时,通过返回到设备栈顶的APC来激活的,若所发向的IRP的设备栈未设置PEDING(设备栈的处理函数不是异步完成),则该事件的激活不用通过APC激活,这个新的IRP也不用在完成函数中传递PEDING位,这时的完成函数只是为了获得相关的数据信息。

 

 

TdiBuildQueryInformation(query_irp, devobj,irps->FileObject,tdi_create_addrobj_complete2, ctx,TDI_QUERY_ADDRESS_INFO,mdl);//mdl是一个缓冲区,构造一个IRP

 

KeInitializeEvent(&waitEvent,SynchronizationEvent, FALSE);

query_irp->UserIosb= &ioStatus;

query_irp->UserEvent= &waitEvent;

status= IoCallDriver(devobj,query_irp);//这时因为在IRP_MJ_CREATE的完成函数中,所以IRP_MJ_CREATE请求已经下发完成之后,现在发送查询请求发送查询请求,得到生成的地址

 

if(status == STATUS_PENDING)//TDI过滤驱动的完成函数中不用传播pending位,因为它位于最后一层堆栈中。

{

KeWaitForSingleObject(&waitEvent, Executive, KernelMode, FALSE,NULL);//用于等待,确保查询请求完成

status = ioStatus.Status;//这里只需要取到IRP的处理结果并返回上层即可,如上层是应用层函数

}

elseif (status != STATUS_SUCCESS) {

 

FilterDebugPrint(("%s:IoCallDriver: 0x%x\n", __FUNCTION__, status));

gotodone;

}

 

status = STATUS_SUCCESS;//只有这里会命中,在windbg

 

 

2.3 异步转发方式

 

异步:就是先返回然后在处理,处理完后再通知

 

【异步转发方式】(适用于同步转发方式的两种方法

 

1、高层不能再对此IRP进行控制,不会涉及通过事件对象实现的等待操作。但若需要知道处理结果&再做其它处理则需要在完成函数中得知并需要传播pending位

2、异步转发也能在下层驱动完成IRP时获得处理的机会,其主要是采用了异步处理机制。首先本层dispatch调用IoCopyCurrentIrpStackLocationToNext将本层设备栈参数复制到下层,设置相应的CompletionRoutine,然后调用IoCallDriver将IRP转发至下层驱动,并将转发的结果直接return给上层调用者。

 

//将当前IRP堆栈拷贝底层堆栈

IoCopyCurrentIrpStackLocationToNext(pIrp);

 

//设置完成例程

IoSetCompletionRoutine(pIrp,MyIoCompletion,NULL/*即不向完成函数传递事件对象*/,TRUE,TRUE,TRUE);

 

ntStatus= IoCallDriver(pdx->TargetDevice, pIrp);

 

if(ntStatus == STATUS_PENDING)

{

KdPrint(("STATUS_PENDING\n"));

}

 

returnntStatus;

 

 

NTSTATUS

MyIoCompletion(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp,IN PVOID Context)

{

//进入此函数标志底层驱动设备将IRP完成

KdPrint(("EnterMyIoCompletion\n"));

if(Irp->PendingReturned) 只有当底层驱动处理IRP先返回过STATUS_PENDING时

{

        IoMarkIrpPending( Irp);//继续传播pending位,用于系统投递APC激活事件

}

ntStatus = pIrp->IoStatus.Status;//得到底层的处理结果

returnSTATUS_SUCCESS;//同STATUS_CONTINUE_COMPLETION

}

 

 

注:在完成函数中,只有当下层驱动设置了PENDING位(通过判断Irp->PendingReturned得知)和本完成函数不返回STATUS_MORE_PROCESSING_REQUIRED时才需要调用IoMarkIrpPending设置本层的PEDING位,用于系统投递APC激活事件

还有一种情况就是,位于最后一层堆栈中的完成函数不用传播PENDING位,如TDI驱动中的情况。

 

【完成函数的使用归纳】

 

1、完成函数需要判断 if (Irp->PendingReturned)来决定是否通过调用IoMarkIrpPending(Irp)来继续传播堆栈上的PEDING位,若完成函数要返回STATUS_MORE_PROCESSING_REQUIRED 和已知这个IRP不会异步完成(if (Irp->PendingReturned) 返回FALSE时则不用判断Irp->PendingReturned。

2、完成函数可以对下层驱动处理该IRP的结果进行相应修改操作。

3、使用完成函数的一个技巧

 

1)mdl 的起始地址是ctx->tax,构造TDI类型的IRP时的最后一个参数正是这个mdl,这个IRP被处理后生成的地址&端口会填充到这个mdl指向的缓冲区中,即TDI_ADDRESS_INFO_MAX。

2)又因为在构造TDI时将ctx传入完成函数,这样在完成函数中就可以通过ctx->tax取出IP地址和端口了

3)这样做的两大好处是ctx指向的结构体还可包含很多逻辑处理的成员如ctx->fileobj,另一个好处是在回调中获得了生成的IP地址和端口,因为mdl指向的正是ctx->tax,调用IoAllocateMdl中设定的。

 

同步转发方式的第一种方法:

 

 

 

同步转发方式(通过当前设备栈中的完成函数中激活事件并返回STATUS_MORE_PROCESSING_REQUIRED):

 

高层需要对此IRP进行控制,需要在完成函数中激活事件并返回 STATUS_MORE_PROCESSING_REQUIRED(这时需要调用IoCompleteRequest来重新完成该IRP,这样整个IRP不会继续向上返回,KeWaitForSingleObject处则被激活。(在完成函数里激活用于同步的事件对象

 

        有时IRP在本层无法直接处理,需要将其转发至下层,待下层处理完后在其结果上可进行修改再将其返回。这时可以采用同步转发方式进行处理。首先在相应派遣函数中初始化一个未激发的event,调用IoCopyCurrentIrpStackLocationToNext将本层设备栈参数复制到下层。然后设置一个CompletionRoutine(因为完成例程会设置到下层的IO堆栈中,见下图),将刚才初始化过的event作为context传给它。

 

 

 

同步转发方式(通过当前设备栈中的完成函数中激活事件并返回STATUS_MORE_PROCESSING_REQUIRED):

 

高层需要对此IRP进行控制,需要在完成函数中激活事件并返回 STATUS_MORE_PROCESSING_REQUIRED(这时需要调用IoCompleteRequest来重新完成该IRP,这样整个IRP不会继续向上返回,KeWaitForSingleObject处则被激活。(在完成函数里激活用于同步的事件对象

 

        有时IRP在本层无法直接处理,需要将其转发至下层,待下层处理完后在其结果上可进行修改再将其返回。这时可以采用同步转发方式进行处理。首先在相应派遣函数中初始化一个未激发的event,调用IoCopyCurrentIrpStackLocationToNext将本层设备栈参数复制到下层。然后设置一个CompletionRoutine(因为完成例程会设置到下层的IO堆栈中,见下图),将刚才初始化过的event作为context传给它。

 

之后调用IoCallDriver转发IRP至下层,并判断返回值是否为STATUS_PENDING。是则等待之前的event被激活。

 

       在CompletionRoutine中判断该IRP是否PendingReturned,是则说明之前IoCallDriver返回了STATUS_PENDING,于是激发event。 CompletionRoutine返回STATUS_MORE_PROCESSING_REQUIRED使我们的dispatchroutine重新取得对该IRP的控制权。本层dispatch等待结束再次获得控制权后,进行相应处理,之后需再次调用IoCompleteRequest完成该IRP。

 

       同步转发是驱动中常用的一种IRP处理方式。一般会将本层dispatch转发IRP至下层并等待CompletionRoutine激发event的行为独立成一个ForwardIrpSynchronous的函数。几个dispatch只需一个ForwardIrpSynchronous,代码相对简单。

 

NTSTATUS MyIoCompletion(INPDEVICE_OBJECT  DeviceObject,IN PIRP  Irp,IN PVOID Context)

{

//若底层驱动未返回STATUS_PENDING(见使用IoMarkPending的原因及原理),则不用激活事件,因为不会走进if(ntStatus == STATUS_PENDING)块中的KeWaitForSingleObject

if(Irp->PendingReturned == TRUE)

{

KeSetEvent((PKEVENT)Context, IO_NO_INCREMENT, FALSE);

}

 

returnSTATUS_MORE_PROCESSING_REQUIRED;//重新获得对IRP的控制权,JK 这个可能是通知一下IO管理器吧~

}

 

#pragma PAGEDCODE

NTSTATUS HelloDDKRead(INPDEVICE_OBJECT pDevObj,IN PIRP pIrp)

{

KdPrint(("DriverB:EnterB HelloDDKRead\n"));

NTSTATUSntStatus = STATUS_SUCCESS;

//将自己完成IRP,改成由底层驱动负责

 

PDEVICE_EXTENSIONpdx = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;

 

//将本层的IRP堆栈拷贝到底层堆栈

IoCopyCurrentIrpStackLocationToNext(pIrp);

 

KEVENTevent;

//初始化事件

KeInitializeEvent(&event,NotificationEvent, FALSE);

 

//设置完成例程

IoSetCompletionRoutine(pIrp,MyIoCompletion,&event,TRUE,TRUE,TRUE);

 

//调用底层驱动

      ntStatus =IoCallDriver(pdx->TargetDevice, pIrp);

 

if(ntStatus == STATUS_PENDING) //若底层驱动返回的是STATUS_SUCCESS,则这里不会执行,也不会永远等待

{

KdPrint(("IoCallDriverreturn STATUS_PENDING,Waiting ...\n"));

KeWaitForSingleObject(&event,Executive,KernelMode ,FALSE,NULL);//完成函数会激活该事件,并返回STATUS_MORE_PROCESSING_REQUIRED,使得重新获得对此IRP的控制权

ntStatus = pIrp->IoStatus.Status;//得到底层的处理结果

}

 

//虽然在底层驱动已经将IRP完成了,但是由于完成例程返回的是STATUS_MORE_PROCESSING_REQUIRED,因此需要再次调用IoCompleteRequest!

IoCompleteRequest (pIrp, IO_NO_INCREMENT);//由于完成函数中返回了STATUS_MORE_PROCESSING_REQUIRED,所以要再次调用IoCompleteRequest!

 

KdPrint(("DriverB:LeaveB HelloDDKRead\n"));

 

returnntStatus;

}

 

 

同步转发方式的第二种方法:

 

 

同步转发方式(用于同步的事件通过B设备栈的IoCompleteRequest来激活):

 

    A层需要等待发向B设备的IRP的处理结果(A层不能对这个IRP进行控制,只能等待处理结果,因为两个设备不在同一个堆栈上),需要向发送给B设备的IRP附加一个事件,这时只需要等待此事件激活即可。当附加在IRP上的事件被激活后(即B层的处理函数调用了IoCompleteRequest),可以通过附加在query_irp->UserIosb上的status变量来得到B层的处理函数对该IRP的处理结果。

 

           若B层所在的设备栈先返回了PEDING,则事件的激活是向B层发送的IRP被调用IoCompleteRequest结束时,通过返回到B设备栈顶的APC来激活的,若B层所在的设备栈未返回PEDING(B层的处理函数不是异步完成),则该事件的激活不用通过APC激活,这个新的IRP也不用在完成函数中传递PEDING位,这时的完成函数只是为了获得相关的数据信息。

 

KeInitializeEvent(&waitEvent,SynchronizationEvent, FALSE);

 

query_irp->UserIosb =&ioStatus;

query_irp->UserEvent =&waitEvent;

 

//这时因为在IRP_MJ_CREATE的完成函数中,所以IRP_MJ_CREATE请求已经下发完成之后,现在发送查询请求发送查询请求,得到生成的地址

status =IoCallDriver(devobj, query_irp);

 

if(status == STATUS_PENDING)

{

KeWaitForSingleObject(&waitEvent, Executive,KernelMode, FALSE, NULL);//用于等待,确保查询请求完成

status = ioStatus.Status;这里只需要取到IRP的处理结果并返回上层即可,如上层是应用层函数

FilterDebugPrint(("%s:IoCallDriver Async result: 0x%x\n", __FUNCTION__, status));

 

}else if (status !=STATUS_SUCCESS)

{

FilterDebugPrint(("%s:IoCallDriver: 0x%x\n", __FUNCTION__, status));

gotodone;

}

 

return status;

 

 

异步转发方式:

 

 

异步转发方式(通过当前设备栈中的完成函数实现):

 

高层不能再对此IRP进行控制,但若需要知道处理结果&再做其它处理则需要在完成函数中得知并需要传播pending位。

异步转发也能在下层驱动完成IRP时获得处理的机会,其主要是采用了异步处理机制。首先本层dispatch调用IoCopyCurrentIrpStackLocationToNext将本层设备栈参数复制到下层,设置相应的CompletionRoutine,然后调用IoCallDriver将IRP转发至下层驱动,并将转发的结果直接return给上层调用者。

在CompletionRoutine中再判断该IRP是否PendingReturned,是则需要调用IoMarkIrpPending。之后可对下层驱动处理该IRP的结果进行相应操作。最后返回STATUS_CONTINUE_COMPLETION(同STATUS_SUCCESS)。

异步转发在异步处理时性能最佳,但处理的逻辑放在了CompletionRoutine中,因此多个dispatch需要编写多个CompletionRoutine。

 

 

NTSTATUSHelloDDKRead(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp)

{

KdPrint(("DriverB:EnterB HelloDDKRead\n"));

NTSTATUSntStatus = STATUS_SUCCESS;

//将自己完成IRP,改成由底层驱动负责

 

PDEVICE_EXTENSIONpdx = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;

 

//将当前IRP堆栈拷贝底层堆栈

IoCopyCurrentIrpStackLocationToNext(pIrp);

 

//设置完成例程

IoSetCompletionRoutine(pIrp,MyIoCompletion,NULL/*即不向完成函数传递事件对象*/,TRUE,TRUE,TRUE);

 

//调用底层驱动

      ntStatus =IoCallDriver(pdx->TargetDevice, pIrp);

 

//当IoCallDriver后,并且完成例程返回的是STATUS_SUCCESS,IRP就不在属于派遣函数了,就不能对IRP进行操作了

if(ntStatus == STATUS_PENDING)

{

KdPrint(("STATUS_PENDING\n"));

}

 

KdPrint(("DriverB:LeaveB HelloDDKRead\n"));

 

returnntStatus;

}

 

NTSTATUS

 MyIoCompletion(IN PDEVICE_OBJECTDeviceObject,IN PIRP Irp,IN PVOID Context)

{

//进入此函数标志底层驱动设备将IRP完成

KdPrint(("EnterMyIoCompletion\n"));

if(Irp->PendingReturned) 只有当底层驱动处理IRP先返回过STATUS_PENDING时

{

        IoMarkIrpPending( Irp);//继续传播pending位,用于系统投递APC激活事件

}

returnSTATUS_SUCCESS;//同STATUS_CONTINUE_COMPLETION

}

 

 

 

 

 

 

 

 

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

应用层和驱动层的同步与异步的处理逻辑及底层实现 的相关文章

  • Entity Framework 学习总结之一:ADO.NET 实体框架概述

    ADO NET 实体框架概述 新版本中的 ADO NET 以新实体框架为特色 它使开发人员可以通过对象模型 xff08 而不是逻辑 关系数据模型 xff09 专注于数据 实体框架有助于将逻辑数据架构抽象为概念模型 xff0c 并且允许以多种
  • Entity Framework 学习总结之四:对象服务介绍使用

    System Data Objects System Data Entity dll 该命名空间包含一些类 xff0c 用于提供对 对象服务 的核心功能的访问 这些类使您可以藉由作为实体类型实例的强类型 CLR 对象来查询 插入 更新和删除
  • Entity Framework 学习总结之四:对象服务介绍使用

    System Data Objects System Data Entity dll 该命名空间包含一些类 xff0c 用于提供对对象服务的核心功能的访问 这些类使您可以藉由作为实体类型实例的强类型 CLR 对象来查询 插入 更新和删除数据
  • 未执行的URL(MVC异常)

    昨天开始就碰到在IIS里面通过URL无法直接访问到图片 xff0c 提示错误 xff0c 所以经过研究发现 xff0c 合理的配置节应该如下 先改成集成模式 xff0c 然后再改成经典模式 lt system webServer gt lt
  • PIX学习路径-1-选择PIXHAWK作为飞控学习的起点

    xff08 先声明出处 xff1a http blog csdn net qq 21842557 article details 52214425 xff09 创业领域现在最火爆的是什么 xff1f 无疑是机器人和无人机 越来越多的巨头和V
  • PIX学习路径-3-PIXHAWK二次开发之前需要知道的事

    现在作为一个consumer xff0c 能够实现将飞机装配 xff0c 使用MP进行固件烧录 xff0c 初始化校准 xff0c 然后还能够调节PID xff0c 这样算是一个合格的consumer了 xff0c 现在希望对PIXHAWK
  • Java面试题全集(上)

    2013年年底的时候 xff0c 我看到了网上流传的一个叫做 Java面试题大全 的东西 xff0c 认真的阅读了以后发现里面的很多题目是重复且没有价值的题目 xff0c 还有不少的参考答案也是错误的 xff0c 于是我花了半个月时间对这个
  • Netty学习:Channel及其内部接口Unsafe

    连接到网络套接字或组件的一种连接 xff0c 它能够进行I O操作 xff0c 如读 写 连接和绑定 通道为用户提供 通道的当前状态 例如是否打开 它是连接吗 通道的 64 linkplain ChannelConfig配置参数 如接收缓冲
  • 拥抱开源

    使用linux ubuntu 已经一个月多了 xff0c 偶尔用windows是因为要改vb delphi net的程序 在linux下 xff0c 似乎多数软件都是开源的 xff0c 免费的 如今我算是终于义无反顾的走上了这条路了 jav
  • STM32 | C语言对寄存器的封装

    说明 xff1a 这里以GPIO外设为例 xff0c 介绍C语言对寄存器的封装 以此类推其他外设同样可以用这种方法来封装 本文有两部分构成 xff1a 1 介绍宏定义 2 使用结构体封装寄存器列表 1 宏定义 以封装STM32 GPIOH为
  • Intel CPU(i3、i5、i7、i9)型号、性能详细解读

    一 Intel CPU的性能比较 xff1a 它们分为高中低端 xff0c 最低端的G系列 xff0c 然后是低端i3系列 xff0c 中端i5系列 xff0c 高端i7系列和至尊i9系列 Intel CPU 末尾字母含义 xff08 M
  • 字符串搜索函数

    一 字符串中找字符 char strchr const char s int c 表示从左边 开始找这个字符第一次出现的位置 char strrchar const char s int c 表示 从右边 开始找这个字符第一次出现的位置 注
  • 4. Service

    4 Service k8s 中的Pod是朝生夕死的 xff0c 并且是不会重生的 xff0c 尤其是在ReplicaSets中动态创建或销毁Pod 然而每个Pod可以获取自己的IP地址 xff0c 即使这些IP地址是不稳定的 xff08 重
  • STM32 Free RTOS实战

    FreeRTOS是一个开源的实时操作系统 使用的平台 xff1a 秉火STM32 Cortex M3内核开发板 xff0c Free RTOS v8 2 3 多任务流水灯 span class token builtin class nam
  • 参考 | 升级 Win11 移动热点开不了或者开了连不上

    讲道理 就很离谱 一开始我升级了 Win11 后 突然发现 移动热点 开不了了 就是那种 开了之后 手机 ipad 能检测到电脑移动热点的信号 但是会出现这两种情况 死活连不上连上了 在移动端显示 无互联网连接 解决办法 打开 移动热点 打
  • 在 Linux 上安装和使用恶意软件检测工具 LMD

    在 Linux 上安装和使用恶意软件检测工具 LMD xff0c 是个相当简单的过程 xff0c 1 下载资源 wget http www rfxn com downloads maldetect current tar gz 2 解压缩资
  • Docker 使用Dockerfile创建镜像

    基本结构 Dockerfle 由 一行行命令语句组成 xff0c 并且支持以 xff03 开头的注释行 一般而言 xff0c Dockerfle 主体内容分为四部分 xff1a 基础镜像信息 维护者信息 镜像操作指令和容器启动时执行指令 e
  • 为啥我的APP功能引导设计这么low?如何做好功能引导设计?

    功能引导设计历史版本 Level1 APP第一次打开的浏览页 展示主要功能及简单使用方式 xff1b Level2 引导流程页 使用 xff1f 方式在界面右上角 xff0c 点击后展示使用流程详情 xff1b Level3 蒙层 可以有上
  • 【Xshell无法连接虚拟机问题】xshell无法连接虚拟机Ubuntu系统问题

    问题描述 xff1a 电脑新安装虚拟机 xff0c 并且安装Ubuntu系统 xff0c 通过Xshell工具无法连问题 原因是新linux系统未安装 ssh 服务导致 xff0c Xshell连接是依赖 ssh 服务实现的 下面让我们解决
  • [linux下]理解Semaphore及其用法详解

    2009 05 12 13 13 Mutex 是一把钥匙 xff0c 一个人拿了就可进入一个房间 xff0c 出来的时候把钥匙交给队列的第一个 一般的用法是用于串行化对critical section代码的访问 xff0c 保证这段代码不会

随机推荐

  • JavaScript 数据结构——栈

    概念 栈是一种线性结构 xff0c 最大的特点就是先进后出 xff0c 后进先出 入栈push xff1a 出栈pop xff1a 实现 JavaScript中可以用数组表示栈 xff1a span class token keyword
  • Python matplotlib 以pdf形式保存图片

    import matplotlib pyplot as plt from matplotlib backends backend pdf import PdfPages short version plt plot range 10 plt
  • 基于数据报(UDP)编程的接口总结

    文章目录 Udp编程接口socket 创建套接字bind 将套接字绑定到指定的网络地址本机字节序和网络字节序 recvfrom 接收一个数据报并保存源地址 从数据报套接字接收数据调用格式函数功能 sendto 按照指定目的地向数据报套接字发
  • Android-自定义View集合

    学习安卓几个月了 xff0c 也有点自己的体会 xff0c 然而我发现自己真正喜欢的是数据方面的东西 xff0c 当然android自定义view也很好玩 xff0c 很体验一些技巧性的东西 也不想自己以前学习过程总结的东西就此淹没 xff
  • Nginx同一端口部署多个vue项目

    场景 大家在部署项目的时候会遇到 xff0c 只申请了一个公网端口 xff0c 但是需要将多个前端vue项目部署到同一域名同一端口下 xff0c 下面记录一下vue打包时如何配置以及nginx如何配置 我们这个项目有多个vue工程 xff0
  • 树莓派3B安装Ubuntu Mate18.04版

    树莓派3B为什么要装Ubuntu Mate版本 xff0c 因为轻量化 xff0c 就这么简单 1 相关器材 树莓派 xff1a Raspberry PI 3 Model B 树莓派3 microSD卡 闪迪16GB存储卡 HDMI线 读卡
  • 谷歌浏览器提示您的连接不是私密连接的解决方法

    谷歌浏览器是一款非常好用的网络浏览器 xff0c 但是最近有用户反应 xff0c 使用谷歌浏览器时出现提示 xff1a 您的连接不是私密连接 xff0c 这是怎么回事呢 xff1f 接下来就为大家分享使用谷歌浏览器过程中提示您的连接不是私密
  • ElasticSearch7索引管理--别名(基于kibana)

    ElasticSearch可以对一个或者多个索引指定别名 xff0c 通过别名可以查询到一个或者多个索引的内容 在内部ElasticSearch会自动把别名映射到相应的索引上 可以对别名编写过滤器或者路由 xff0c 在系统中别名不能重复
  • matlab粒子群优化算法路径规划代码解读

    前言 粒子群算法是一种群智能优化算法 xff0c 该算法具有原理简单 易实现 控制参数较少等优点 xff0c 下面根据Yarpiz公司的matlab代码就其在路径规划中的应用进行简单的介绍 xff0c 以供读者更好的理解粒子群优化算法的实际
  • (转载)OpenWrt下把SD卡挂载到 /overlay ,扩大软件空间

    在wall内外搜索无数文章 xff0c 唯有这篇文章能够看得懂并奏效 xff0c 感谢作者 原文地址 xff1a https blog samnya cn mount sd card to overlay on openwrt 作者 xff
  • XTW100高速编程器WIN10驱动安装

    按照以下方法可以实现XTW100在WIN10系统下的驱动安装 xff0c 且不需禁用驱动强制签名 先去这个网站 xff1a https zadig akeo ie 下载这个软件件 xff1a zadig 2 5 exe 由于你懂的原因 xf
  • ffmpeg-python库的使用翻译(一)

    原文地址 xff1a https github com kkroening ffmpeg python xff0c 本文为google翻译 43 部分人工翻译 希望能对大家有用 ffmpeg python xff1a FFmpeg的Pyth
  • ffmpeg-python库的使用翻译(二)

    接上篇 ffmpeg python库的使用翻译 xff08 一 xff09 原文 xff1a https github com kkroening ffmpeg python tree master examples assemble vi
  • ffmpeg-python库的使用翻译(三)

    接上篇 ffmpeg python库的使用翻译 xff08 二 xff09 原文地址 xff1a https github com kkroening ffmpeg python blob master ffmpeg filters py
  • C语言中,利用宏定义一个字符串和字符数组定义的字符串其末尾都会自动添加\0字符

    C语言中 xff0c 利用宏定义一个字符串 define MYPHONE 34 AT 43 CMGS 34 字符数组的方式来定义一个字符串unsigned char SMS 61 34 HELLO WELCOME TO RCCTS 34 问
  • 串口卡住问题分析与解决

    对于消息类系统而言任何阻塞都会导致整个系统的消息被卡住造成系统假死的问题 今天来说说串口导致系统卡死的问题 1 问题说明 xff1a 路由器在挂机过程中 xff0c 偶现串口卡住 xff0c web页面进不去 xff0c 有限终端拿不到地址
  • HTTPS 协议到底比 HTTP 协议多些什么?

    来源 xff1a 公众号 杰哥的IT之旅 作者 xff1a 阿拉斯加 ID xff1a Jake Internet 原文地址 xff1a HTTPS 协议到底比 HTTP 协议多些什么 xff1f 大家好 xff0c 我是杰哥 最近卷了一篇
  • Python爬虫实战 | 利用多线程爬取 LOL 高清壁纸

    来源 xff1a 公众号 杰哥的IT之旅 作者 xff1a 阿拉斯加 ID xff1a Jake Internet 如需获取本文完整代码及 LOL 壁纸 xff0c 请为本文右下角点赞并添加杰哥微信 xff1a Hc220088 获取 一
  • 拿来就用的脚本案例(三)

    大家好 xff0c 我是杰哥 xff08 鸽 xff09 之前给大家分享了 7 个非常实用的 Shell 拿来就用脚本实例 xff01 和 超硬核 xff01 11 个非常实用的 Python 和 Shell 拿来就用脚本实例 xff01
  • 应用层和驱动层的同步与异步的处理逻辑及底层实现

    应用层的实现 xff1a 1 ReadFile WriteFile DeviceIoControl等 xff0c 这些都有两种操作方式 xff0c 一种是同步 xff0c 一种是异步 操作设备的Win32API主要是这3个函数ReadFil