读wrk系列 关于IRP(1)

NtReadFile为例,

 

1.      建立irp

  1. PIRP  
  2. IopAllocateIrpPrivate(  
  3.     IN CCHAR StackSize,  
  4.     IN BOOLEAN ChargeQuota  
  5. )  
  6.   
  7. //如果大小小于IopLargeIrpStackLocations,那么从prcb->PPLookasideList[number].P;这个快查表里面拿一个,如果大于这个值则构造一个  
  8.   
  9. //然后初始化  
  10. #define IopInitializeIrp( Irp, PacketSize, StackSize ) {          \  
  11.     RtlZeroMemory( (Irp), (PacketSize) );                         \  
  12.     (Irp)->Type = (CSHORT) IO_TYPE_IRP;                           \  
  13.     (Irp)->Size = (USHORT) ((PacketSize));                        \  
  14.     (Irp)->StackCount = (CCHAR) ((StackSize));                    \  
  15.     (Irp)->CurrentLocation = (CCHAR) ((StackSize) + 1);           \  
  16.     (Irp)->ApcEnvironment = KeGetCurrentApcEnvironment();         \  
  17.     InitializeListHead (&(Irp)->ThreadListEntry);                 \  
  18.     (Irp)->Tail.Overlay.CurrentStackLocation =                    \  
  19.         ((PIO_STACK_LOCATION) ((UCHAR *) (Irp) +                  \  
  20.             sizeof( IRP ) +                                       \  
  21.             ( (StackSize) * sizeof( IO_STACK_LOCATION )))); }  
PIRP
IopAllocateIrpPrivate(
    IN CCHAR StackSize,
    IN BOOLEAN ChargeQuota
)

//如果大小小于IopLargeIrpStackLocations,那么从prcb->PPLookasideList[number].P;这个快查表里面拿一个,如果大于这个值则构造一个

//然后初始化
#define IopInitializeIrp( Irp, PacketSize, StackSize ) {          \
    RtlZeroMemory( (Irp), (PacketSize) );                         \
    (Irp)->Type = (CSHORT) IO_TYPE_IRP;                           \
    (Irp)->Size = (USHORT) ((PacketSize));                        \
    (Irp)->StackCount = (CCHAR) ((StackSize));                    \
    (Irp)->CurrentLocation = (CCHAR) ((StackSize) + 1);           \
    (Irp)->ApcEnvironment = KeGetCurrentApcEnvironment();         \
    InitializeListHead (&(Irp)->ThreadListEntry);                 \
    (Irp)->Tail.Overlay.CurrentStackLocation =                    \
        ((PIO_STACK_LOCATION) ((UCHAR *) (Irp) +                  \
            sizeof( IRP ) +                                       \
            ( (StackSize) * sizeof( IO_STACK_LOCATION )))); }

2.调用IopSynchronousServiceTail派发irp


  1. NTSTATUS  
  2. IopSynchronousServiceTail(  
  3.     IN PDEVICE_OBJECT DeviceObject,  
  4.     IN PIRP Irp,  
  5.     IN PFILE_OBJECT FileObject,  
  6.     IN BOOLEAN DeferredIoCompletion,  
  7.     IN KPROCESSOR_MODE RequestorMode,  
  8.     IN BOOLEAN SynchronousIo,  
  9.     IN TRANSFER_TYPE TransferType  
  10.     )  
  11.   
  12. {  
  13.     NTSTATUS status;  
  14.     PAGED_CODE();  
  15.   
  16. //异步操作要插入irp的thread->irpList  
  17.   
  18.     if (!SynchronousIo) {  
  19.         IopQueueThreadIrp( Irp );  
  20.     }  
  21. //……  
  22.     //  
  23.     // 向设备发irp  
  24.     //  
  25.   
  26.     status = IoCallDriver( DeviceObject, Irp );  
  27.   
  28.     if (DeferredIoCompletion) {  
  29.   
  30.         if (status != STATUS_PENDING) {  
  31.   
  32.             PKNORMAL_ROUTINE normalRoutine;  
  33.             PVOID normalContext;  
  34.             KIRQL irql = PASSIVE_LEVEL; // Just to shut up the compiler  
  35.   
  36.             ASSERT( !Irp->PendingReturned );  
  37.   
  38.             if (!SynchronousIo) {  
  39.                 KeRaiseIrql( APC_LEVEL, &irql );  
  40.             }  
  41.     //完成请求  
  42.         IopCompleteRequest( &Irp->Tail.Apc,  
  43.                                 &normalRoutine,  
  44.                                 &normalContext,  
  45.                                 (PVOID *) &FileObject,  
  46.                                 &normalContext );  
  47.   
  48.             if (!SynchronousIo) {  
  49.                 KeLowerIrql( irql );  
  50.             }  
  51.         }  
  52.     }  
  53. //同步下等待irp完成  
  54.     if (SynchronousIo) {  
  55.   
  56.         if (status == STATUS_PENDING) {  
  57.   
  58.             status = KeWaitForSingleObject( &FileObject->Event,  
  59.                                             Executive,  
  60.                                             RequestorMode,  
  61.                                             (BOOLEAN) ((FileObject->Flags & FO_ALERTABLE_IO) != 0),  
  62.                                             (PLARGE_INTEGER) NULL );  
  63.   
  64.             if (status == STATUS_ALERTED || status == STATUS_USER_APC) {  
  65.   
  66.                 IopCancelAlertedRequest( &FileObject->Event, Irp );  
  67.   
  68.             }  
  69.   
  70.             status = FileObject->FinalStatus;  
  71.   
  72.         }  
  73.   
  74.         IopReleaseFileObjectLock( FileObject );  
  75.   
  76.     }  
  77.   
  78.     return status;  
  79. }  
NTSTATUS
IopSynchronousServiceTail(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PFILE_OBJECT FileObject,
    IN BOOLEAN DeferredIoCompletion,
    IN KPROCESSOR_MODE RequestorMode,
    IN BOOLEAN SynchronousIo,
    IN TRANSFER_TYPE TransferType
    )

{
    NTSTATUS status;
    PAGED_CODE();

//异步操作要插入irp的thread->irpList

    if (!SynchronousIo) {
        IopQueueThreadIrp( Irp );
    }
//……
    //
    // 向设备发irp
    //

    status = IoCallDriver( DeviceObject, Irp );

    if (DeferredIoCompletion) {

        if (status != STATUS_PENDING) {

            PKNORMAL_ROUTINE normalRoutine;
            PVOID normalContext;
            KIRQL irql = PASSIVE_LEVEL; // Just to shut up the compiler

            ASSERT( !Irp->PendingReturned );

            if (!SynchronousIo) {
                KeRaiseIrql( APC_LEVEL, &irql );
            }
    //完成请求
        IopCompleteRequest( &Irp->Tail.Apc,
                                &normalRoutine,
                                &normalContext,
                                (PVOID *) &FileObject,
                                &normalContext );

            if (!SynchronousIo) {
                KeLowerIrql( irql );
            }
        }
    }
//同步下等待irp完成
    if (SynchronousIo) {

        if (status == STATUS_PENDING) {

            status = KeWaitForSingleObject( &FileObject->Event,
                                            Executive,
                                            RequestorMode,
                                            (BOOLEAN) ((FileObject->Flags & FO_ALERTABLE_IO) != 0),
                                            (PLARGE_INTEGER) NULL );

            if (status == STATUS_ALERTED || status == STATUS_USER_APC) {

                IopCancelAlertedRequest( &FileObject->Event, Irp );

            }

            status = FileObject->FinalStatus;

        }

        IopReleaseFileObjectLock( FileObject );

    }

    return status;
}

3.向设备派发irp

  1. FORCEINLINE  
  2. IopfCallDriver(  
  3.     IN PDEVICE_OBJECT DeviceObject,  
  4.     IN OUT PIRP Irp  
  5.     )  
  6. {  
  7.     PIO_STACK_LOCATION irpSp;  
  8.     PDRIVER_OBJECT driverObject;  
  9.     NTSTATUS status;  
  10.   
  11.     //向下一层  
  12.     Irp->CurrentLocation--;  
  13.   
  14. irpSp = IoGetNextIrpStackLocation( Irp );  
  15.   
  16. //调整irpSp  
  17.     Irp->Tail.Overlay.CurrentStackLocation = irpSp;  
  18.     irpSp->DeviceObject = DeviceObject;  
  19.     driverObject = DeviceObject->DriverObject;  
  20. status = driverObject->MajorFunction[irpSp->MajorFunction]( DeviceObject,  
  21.                                                               Irp );  
  22.   
  23.     return status;  
  24. }  
FORCEINLINE
IopfCallDriver(
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PIRP Irp
    )
{
    PIO_STACK_LOCATION irpSp;
    PDRIVER_OBJECT driverObject;
    NTSTATUS status;

	//向下一层
    Irp->CurrentLocation--;

irpSp = IoGetNextIrpStackLocation( Irp );

//调整irpSp
    Irp->Tail.Overlay.CurrentStackLocation = irpSp;
    irpSp->DeviceObject = DeviceObject;
    driverObject = DeviceObject->DriverObject;
status = driverObject->MajorFunction[irpSp->MajorFunction]( DeviceObject,
                                                              Irp );

    return status;
}

driverObject->MajorFunction[irpSp->MajorFunction]这个调用以ClassRead为例,

 

ClassRead调用 IoMarkIrpPending –IoStartPacket – return Status_Pending


  1. VOID  
  2. IoStartPacket(  
  3.     IN PDEVICE_OBJECT DeviceObject,  
  4.     IN PIRP Irp,  
  5.     IN PULONG Key OPTIONAL,  
  6.     IN PDRIVER_CANCEL CancelFunction OPTIONAL  
  7.     )  
  8. {  
  9.     KIRQL oldIrql;  
  10.     KIRQL cancelIrql = PASSIVE_LEVEL;  
  11.     BOOLEAN i;  
  12.   
  13.     KeRaiseIrql( DISPATCH_LEVEL, &oldIrql );  
  14.   
  15.     if (CancelFunction) {  
  16.         IoAcquireCancelSpinLock( &cancelIrql );  
  17.         Irp->CancelRoutine = CancelFunction;  
  18.     }  
  19.   
  20.     //如果设备正忙,挂入设备链表队尾  
  21.     if (Key) {  
  22.         i = KeInsertByKeyDeviceQueue( &DeviceObject->DeviceQueue,  
  23.                                       &Irp->Tail.Overlay.DeviceQueueEntry,  
  24.                                       *Key );  
  25.     } else {  
  26.         i = KeInsertDeviceQueue( &DeviceObject->DeviceQueue,  
  27.                                  &Irp->Tail.Overlay.DeviceQueueEntry );  
  28.     }  
  29.   
  30.      
  31.     if (!i) {  
  32.         //不忙的话直接调用startIo处理  
  33.         DeviceObject->CurrentIrp = Irp;  
  34.   
  35.         if (CancelFunction) {  
  36.   
  37.             if (DeviceObject->DeviceObjectExtension->StartIoFlags & DOE_STARTIO_NO_CANCEL) {  
  38.                 Irp->CancelRoutine = NULL;  
  39.             }  
  40.   
  41.             IoReleaseCancelSpinLock( cancelIrql );  
  42.         }  
  43.   
  44.         DeviceObject->DriverObject->DriverStartIo( DeviceObject, Irp );  
  45.   
  46.     } else {  
  47.         if (CancelFunction) {  
  48.             if (Irp->Cancel) {  
  49.                 Irp->CancelIrql = cancelIrql;  
  50.                 Irp->CancelRoutine = (PDRIVER_CANCEL) NULL;  
  51.                 CancelFunction( DeviceObject, Irp );  
  52.             } else {  
  53.                 IoReleaseCancelSpinLock( cancelIrql );  
  54.             }  
  55.         }  
  56.     }  
  57.     KeLowerIrql( oldIrql );  
  58. }  
VOID
IoStartPacket(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PULONG Key OPTIONAL,
    IN PDRIVER_CANCEL CancelFunction OPTIONAL
    )
{
    KIRQL oldIrql;
    KIRQL cancelIrql = PASSIVE_LEVEL;
    BOOLEAN i;

    KeRaiseIrql( DISPATCH_LEVEL, &oldIrql );

    if (CancelFunction) {
        IoAcquireCancelSpinLock( &cancelIrql );
        Irp->CancelRoutine = CancelFunction;
    }

	//如果设备正忙,挂入设备链表队尾
    if (Key) {
        i = KeInsertByKeyDeviceQueue( &DeviceObject->DeviceQueue,
                                      &Irp->Tail.Overlay.DeviceQueueEntry,
                                      *Key );
    } else {
        i = KeInsertDeviceQueue( &DeviceObject->DeviceQueue,
                                 &Irp->Tail.Overlay.DeviceQueueEntry );
    }

   
    if (!i) {
		//不忙的话直接调用startIo处理
        DeviceObject->CurrentIrp = Irp;

        if (CancelFunction) {

            if (DeviceObject->DeviceObjectExtension->StartIoFlags & DOE_STARTIO_NO_CANCEL) {
                Irp->CancelRoutine = NULL;
            }

            IoReleaseCancelSpinLock( cancelIrql );
        }

        DeviceObject->DriverObject->DriverStartIo( DeviceObject, Irp );

    } else {
        if (CancelFunction) {
            if (Irp->Cancel) {
                Irp->CancelIrql = cancelIrql;
                Irp->CancelRoutine = (PDRIVER_CANCEL) NULL;
                CancelFunction( DeviceObject, Irp );
            } else {
                IoReleaseCancelSpinLock( cancelIrql );
            }
        }
    }
    KeLowerIrql( oldIrql );
}


4.完成irp 

  1. VOID  
  2. FASTCALL  
  3. IopfCompleteRequest(  
  4.     IN PIRP Irp,  
  5.     IN CCHAR PriorityBoost  
  6. )  
  7. {  
  8.     //…  
  9.     // Irp->CurrentLocation为0时是栈底,每深入一层就减1  
  10.     //循环所有已经跑过的栈对他们调用完成历程  
  11.    for (stackPointer = IoGetCurrentIrpStackLocation( Irp ),  
  12.          Irp->CurrentLocation++,  
  13.          Irp->Tail.Overlay.CurrentStackLocation++;  
  14.          Irp->CurrentLocation <= (CCHAR) (Irp->StackCount + 1);  
  15.          stackPointer++,  
  16.          Irp->CurrentLocation++,  
  17.          Irp->Tail.Overlay.CurrentStackLocation++) {  
  18.   
  19.         Irp->PendingReturned = stackPointer->Control & SL_PENDING_RETURNED;  
  20.   
  21.         if (!NT_SUCCESS(Irp->IoStatus.Status)) {  
  22.   
  23.             if (Irp->IoStatus.Status != errorStatus) {  
  24.                 errorStatus = Irp->IoStatus.Status;  
  25.                 stackPointer->Control |= SL_ERROR_RETURNED;  
  26.                 bottomSp->Parameters.Others.Argument4 = (PVOID)(ULONG_PTR)errorStatus;  
  27.                 bottomSp->Control |= SL_ERROR_RETURNED; // Mark that there is status in this location  
  28.             }  
  29.         }  
  30.   
  31.         if ( (NT_SUCCESS( Irp->IoStatus.Status ) &&  
  32.              stackPointer->Control & SL_INVOKE_ON_SUCCESS) ||  
  33.              (!NT_SUCCESS( Irp->IoStatus.Status ) &&  
  34.              stackPointer->Control & SL_INVOKE_ON_ERROR) ||  
  35.              (Irp->Cancel &&  
  36.              stackPointer->Control & SL_INVOKE_ON_CANCEL)  
  37.            ) {  
  38.             //调用完成函数  
  39.             ZeroIrpStackLocation( stackPointer );  
  40.   
  41.             if (Irp->CurrentLocation == (CCHAR) (Irp->StackCount + 1)) {  
  42.                 deviceObject = NULL;  
  43.             }  
  44.             else {  
  45.                 deviceObject = IoGetCurrentIrpStackLocation( Irp )->DeviceObject;  
  46.             }  
  47.   
  48.             status = stackPointer->CompletionRoutine( deviceObject,  
  49.                                                       Irp,  
  50.                                                       stackPointer->Context );  
  51.   
  52.             if (status == STATUS_MORE_PROCESSING_REQUIRED) {  
  53.   
  54.                 //返回这个值就什么都别做了,表示完成例程在获得irp后还要做处理  
  55.                 //如果驱动设置了IpCompletion 例程,那么他在上一步(第  
  56. //一步)中必须使用 IoCopyCurrentIrpStackLocationToNext  
  57.                 return;  
  58.             }  
  59.   
  60.         } else {  
  61.             if (Irp->PendingReturned && Irp->CurrentLocation <= Irp->StackCount) {  
  62.                 IoMarkIrpPending( Irp );  
  63.             }  
  64.             ZeroIrpStackLocation( stackPointer );  
  65.         }  
  66.     }  
  67.         //  
  68.     // Check to see whether this is an associated IRP. 略  
  69.   
  70. if (Irp->Flags & IRP_ASSOCIATED_IRP) { …}  
  71.   
  72.   
  73.     //  
  74.     // Check to see if we have a name junction. If so set the stage to  
  75.     // transmogrify the reparse point data in IopCompleteRequest.  
  76.     //  
  77.   
  78.     if ((Irp->IoStatus.Status == STATUS_REPARSE )  && //…略  
  79. // Check to see if this is paging I/O or a close operation. 略  
  80.     //略很多  
  81.   
  82.   
  83.     //这是一个同步操作 那么直接返回,调用方会执行IopCompleteRequest  
  84.         if (Irp->Flags & IRP_DEFER_IO_COMPLETION && !Irp->PendingReturned) {  
  85.   
  86.         if ((Irp->IoStatus.Status == STATUS_REPARSE )  &&  
  87.             (Irp->IoStatus.Information == IO_REPARSE_TAG_MOUNT_POINT)) {  
  88.   
  89.             //  
  90.             // For name junctions we reinstate the address of the appropriate  
  91.             // buffer. It is freed in parse.c  
  92.             //  
  93.   
  94.             Irp->Tail.Overlay.AuxiliaryBuffer = saveAuxiliaryPointer;  
  95.         }  
  96.   
  97.         return;  
  98.     }  
  99.     //  
  100.     // Finally, initialize the IRP as an APC structure and queue the special  
  101.     // kernel APC to the target thread.  
  102.     //异步操作,向该线程投掷APC执行IopCompleteRequest  
  103.   
  104.     thread = Irp->Tail.Overlay.Thread;  
  105.     fileObject = Irp->Tail.Overlay.OriginalFileObject;  
  106.   
  107.     if (!Irp->Cancel) {  
  108.   
  109.         KeInitializeApc( &Irp->Tail.Apc,  
  110.                          &thread->Tcb,  
  111.                          Irp->ApcEnvironment,  
  112.                          IopCompleteRequest,  
  113.                          IopAbortRequest,  
  114.                          (PKNORMAL_ROUTINE) NULL,  
  115.                          KernelMode,  
  116.                          (PVOID) NULL );  
  117.   
  118.         (VOID) KeInsertQueueApc( &Irp->Tail.Apc,  
  119.                                  fileObject,  
  120.                                  (PVOID) saveAuxiliaryPointer,  
  121.                                  PriorityBoost );  
  122. }  


  1. //IopCompleteRequest  Apc  
  2. //将缓冲区考回用户态,释放系统内存 释放mdl  
  3. RtlCopyMemory( irp->UserBuffer,  
  4.                                irp->AssociatedIrp.SystemBuffer,  
  5.                                irp->IoStatus.Information );  
  6.   
  7.     if (irp->MdlAddress) {  
  8.         for (mdl = irp->MdlAddress; mdl != NULL; mdl = nextMdl) {  
  9.             nextMdl = mdl->Next;  
  10.             IoFreeMdl( mdl );  
  11.         }  
  12.     }  
  13.   
  14.     异步完成,可通知应用层,插用户apc  
  15.       
  16.     KeSetEvent( irp->UserEvent, 0, FALSE );  
  17.   
  18.        if (irp->Overlay.AsynchronousParameters.UserApcRoutine) {  
  19.             KeInitializeApc( &irp->Tail.Apc,  
  20.                              &thread->Tcb,  
  21.                              CurrentApcEnvironment,  
  22.                              IopUserCompletion,  
  23.                              (PKRUNDOWN_ROUTINE) IopUserRundown,  
  24.                              (PKNORMAL_ROUTINE) irp->Overlay.AsynchronousParameters.UserApcRoutine,  
  25.                              irp->RequestorMode,  
  26.                              irp->Overlay.AsynchronousParameters.UserApcContext );  
  27.   
  28.             KeInsertQueueApc( &irp->Tail.Apc,  
  29.                               irp->UserIosb,  
  30.                               NULL,  
  31.                               2 );  
  32.   
  33.         } else if (port && irp->Overlay.AsynchronousParameters.UserApcContext) {  
  34.   
  35.             //  
  36.             // If there is a completion context associated w/this I/O operation,  
  37.             // send the message to the port. Tag completion packet as an Irp.  
  38.             //  
  39.   
  40.             irp->Tail.CompletionKey = key;  
  41.             irp->Tail.Overlay.PacketType = IopCompletionPacketIrp;  
  42.   
  43.             KeInsertQueue( (PKQUEUE) port,  
  44.                            &irp->Tail.Overlay.ListEntry );  
  45.   
  46.         } else {  
  47.   
  48.             //  
  49.             // Free the IRP now since it is no longer needed.  
  50.             //  
  51.   
  52.             IoFreeIrp( irp );  
  53.         }  
//IopCompleteRequest  Apc
//将缓冲区考回用户态,释放系统内存 释放mdl
RtlCopyMemory( irp->UserBuffer,
                               irp->AssociatedIrp.SystemBuffer,
                               irp->IoStatus.Information );

    if (irp->MdlAddress) {
        for (mdl = irp->MdlAddress; mdl != NULL; mdl = nextMdl) {
            nextMdl = mdl->Next;
            IoFreeMdl( mdl );
        }
    }

	异步完成,可通知应用层,插用户apc
	
	KeSetEvent( irp->UserEvent, 0, FALSE );

       if (irp->Overlay.AsynchronousParameters.UserApcRoutine) {
            KeInitializeApc( &irp->Tail.Apc,
                             &thread->Tcb,
                             CurrentApcEnvironment,
                             IopUserCompletion,
                             (PKRUNDOWN_ROUTINE) IopUserRundown,
                             (PKNORMAL_ROUTINE) irp->Overlay.AsynchronousParameters.UserApcRoutine,
                             irp->RequestorMode,
                             irp->Overlay.AsynchronousParameters.UserApcContext );

            KeInsertQueueApc( &irp->Tail.Apc,
                              irp->UserIosb,
                              NULL,
                              2 );

        } else if (port && irp->Overlay.AsynchronousParameters.UserApcContext) {

            //
            // If there is a completion context associated w/this I/O operation,
            // send the message to the port. Tag completion packet as an Irp.
            //

            irp->Tail.CompletionKey = key;
            irp->Tail.Overlay.PacketType = IopCompletionPacketIrp;

            KeInsertQueue( (PKQUEUE) port,
                           &irp->Tail.Overlay.ListEntry );

        } else {

            //
            // Free the IRP now since it is no longer needed.
            //

            IoFreeIrp( irp );
        }

5.完成历程

6.取消IO

NtCancelIoFile  对thread->IrpList中的irp进行比对,凡是fileObject相符的就调用IoCancelIrp, IoCancelIrp则调用驱动注册的cancelRoutine

7.irp 完成的fix


*. tips about IpCompletion 

a.如果驱动设置了IpCompletion 例程,那么他在上一步(第一步)中必须使用IoCopyCurrentIrpStackLocationToNext

 

b.一个IoCompletion 例程能够返回两个状态值中的任一个:

STATUS_CONTINUE_COMPLETION – 继续向上完成特定 IRP 。I/O 管理器提升IRP 栈指针,并且调用上一个驱动的 IoCompletion 例程。

STATUS_MORE_PROCESSING_REQUIRED – 中断向上完成特定 IRP 的过程,并且把IRP 栈指针留在当前位置。返回这一状态的驱动通常会在过后调用

IoCompleteRequest例程来重新开始向上完成特定 IRP 的过程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值