You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决跨进程使用DeviceIoControl时的读写阻塞问题?

KMDF驱动读写IOCTL阻塞问题分析与修复

问题根源

先读后写时请求阻塞的核心问题集中在共享资源未同步、挂起请求参数误用两个方面:

1. 共享资源无同步保护

设备扩展中的PendingRequest、isDataAdded属于多IO请求共享的变量,但代码中未使用任何同步机制(如自旋锁),并发访问时会出现竞态条件,导致请求状态混乱。

2. 挂起读请求的参数误用

在IOCTL_WRITE分支处理挂起的读请求时,错误使用了当前写请求的OutputBufferLength,而非从挂起的读请求本身获取输出缓冲区长度。这会导致ZwReadFile调用时传入错误的缓冲区大小,要么读取失败,要么请求无法正常完成。

3. 挂起请求未支持取消

读请求挂起时未调用WdfRequestMarkCancelable,若用户取消请求,驱动无法响应,会导致请求永久阻塞。

修复方案

步骤1:为设备扩展添加同步锁

在设备扩展结构体中添加自旋锁,保护共享变量的访问:

typedef struct _CONTROL_DEVICE_EXTENSION {
    // ... 原有成员
    WDFSPINLOCK  RequestLock; // 新增:保护PendingRequest、isDataAdded的自旋锁
    WDFREQUEST   PendingRequest;
    BOOLEAN      isDataAdded;
    HANDLE       FileHandle;
    // ... 其他成员
} CONTROL_DEVICE_EXTENSION, *PCONTROL_DEVICE_EXTENSION;

在设备初始化时创建自旋锁:

NTSTATUS ControlDeviceCreate(...) {
    // ... 原有代码
    WDF_OBJECT_ATTRIBUTES attributes;
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.ParentObject = Device;
    status = WdfSpinLockCreate(&attributes, &devExt->RequestLock);
    if (!NT_SUCCESS(status)) {
        return status;
    }
    // ... 其他初始化
}

步骤2:修复读请求挂起逻辑

在IOCTL_READ分支中,使用自旋锁保护共享变量,并为挂起请求添加取消支持:

case IOCTL_READ:
    status = WdfRequestRetrieveOutputBuffer(Request, 0, &outBuf, &bufSize);
    if(!NT_SUCCESS(status)) {
        WdfRequestComplete(Request, status);
        return;
    }

    devExt = ControlGetData(WdfIoQueueGetDevice(Queue));
    WdfSpinLockAcquire(devExt->RequestLock);

    if(devExt->FileHandle) {
        if (!devExt->isDataAdded) {
            if (!devExt->PendingRequest) {
                KdPrint(("FileEvtIoRead, data not available...\n"));
                // 标记请求可取消
                status = WdfRequestMarkCancelable(Request, EvtRequestCancel);
                if (!NT_SUCCESS(status)) {
                    WdfRequestComplete(Request, status);
                    WdfSpinLockRelease(devExt->RequestLock);
                    return;
                }
                devExt->PendingRequest = Request;
                WdfSpinLockRelease(devExt->RequestLock);
                return;
            } else {
                WdfRequestComplete(Request, STATUS_UNSUCCESSFUL);
                WdfSpinLockRelease(devExt->RequestLock);
                return;
            }
        }
        // 有数据,直接读取
        position.CurrentByteOffset.QuadPart = 0;
        WdfSpinLockRelease(devExt->RequestLock); // 释放锁后执行文件操作

        status = ZwSetInformationFile(devExt->FileHandle,
                            &ioStatus,
                            &position,
                            sizeof(FILE_POSITION_INFORMATION),
                            FilePositionInformation);
        if (NT_SUCCESS(status)) {
            status = ZwReadFile(devExt->FileHandle, NULL, NULL, NULL,
                                &ioStatus, outBuf, (ULONG)bufSize,
                                0, NULL);
            if (NT_SUCCESS(status)) {
                devExt->isDataAdded = FALSE;
                bytesRead = ioStatus.Information;
            } else {
                status = ioStatus.Status;
            }
        } else {
            status = ioStatus.Status;
        }
    } else {
        WdfSpinLockRelease(devExt->RequestLock);
        status = STATUS_INVALID_HANDLE;
    }

    WdfRequestSetInformation(Request, bytesRead);
    WdfRequestComplete(Request, status);
    break;

同时添加取消请求的回调函数:

VOID EvtRequestCancel(
    IN WDFREQUEST Request
) {
    PCONTROL_DEVICE_EXTENSION devExt = ControlGetData(WdfRequestGetDevice(Request));

    WdfSpinLockAcquire(devExt->RequestLock);
    if (devExt->PendingRequest == Request) {
        devExt->PendingRequest = NULL;
        WdfRequestUnmarkCancelable(Request);
        WdfRequestComplete(Request, STATUS_CANCELLED);
    }
    WdfSpinLockRelease(devExt->RequestLock);
}

步骤3:修复写请求处理挂起读请求的逻辑

在IOCTL_WRITE分支中,使用自旋锁保护共享变量,并从挂起的读请求中获取正确的输出缓冲区参数:

case IOCTL_WRITE:
    status = WdfRequestRetrieveInputBuffer(Request, 0, &inBuf, &bufSize);
    if(!NT_SUCCESS(status)) {
        status = STATUS_INSUFFICIENT_RESOURCES;
        WdfRequestCompleteWithInformation(Request, status, 0);
        return;
    }

    devExt = ControlGetData(WdfIoQueueGetDevice(Queue));
    if(devExt->FileHandle) {
        position.CurrentByteOffset.QuadPart = 0;

        status = ZwSetInformationFile(devExt->FileHandle,
                                &ioStatus,
                                &position,
                                sizeof(FILE_POSITION_INFORMATION),
                                FilePositionInformation);
        if (NT_SUCCESS(status)) {
            status = ZwWriteFile(devExt->FileHandle, NULL,
                                NULL, NULL, &ioStatus, inBuf,
                                (ULONG)InputBufferLength, 0, NULL);
            status = ioStatus.Status;
            bytesWritten = ioStatus.Information;
        } else {
            status = ioStatus.Status;
        }
    } else {
        status = STATUS_INVALID_HANDLE;
    }

    // 完成当前写请求
    WdfRequestCompleteWithInformation(Request, status, bytesWritten);

    // 检查并处理挂起的读请求
    WdfSpinLockAcquire(devExt->RequestLock);
    WDFREQUEST pendingReadReq = devExt->PendingRequest;
    if (pendingReadReq != NULL) {
        devExt->PendingRequest = NULL;
        WdfRequestUnmarkCancelable(pendingReadReq);
        WdfSpinLockRelease(devExt->RequestLock);

        // 从读请求中获取输出缓冲区和长度
        PCHAR readOutBuf = NULL;
        size_t readBufSize = 0;
        rstatus = WdfRequestRetrieveOutputBuffer(pendingReadReq, 0, &readOutBuf, &readBufSize);
        if (!NT_SUCCESS(rstatus)) {
            WdfRequestComplete(pendingReadReq, rstatus);
            return;
        }

        position.CurrentByteOffset.QuadPart = 0;
        rstatus = ZwSetInformationFile(devExt->FileHandle,
                                    &ioStatus,
                                    &position,
                                    sizeof(FILE_POSITION_INFORMATION),
                                    FilePositionInformation);
        if (NT_SUCCESS(rstatus)) {
            rstatus = ZwReadFile(devExt->FileHandle, NULL, NULL, NULL,
                                &ioStatus, readOutBuf, (ULONG)readBufSize,
                                0, NULL);
            if (NT_SUCCESS(rstatus)) {
                WdfRequestCompleteWithInformation(pendingReadReq, rstatus, ioStatus.Information);
                WdfSpinLockAcquire(devExt->RequestLock);
                devExt->isDataAdded = FALSE;
                WdfSpinLockRelease(devExt->RequestLock);
            } else {
                WdfRequestComplete(pendingReadReq, ioStatus.Status);
            }
        } else {
            WdfRequestComplete(pendingReadReq, ioStatus.Status);
        }
    } else {
        devExt->isDataAdded = TRUE;
        WdfSpinLockRelease(devExt->RequestLock);
    }
    return;

关键修复点说明

  • 同步锁:确保PendingRequest和isDataAdded在多IO请求并发访问时的状态一致性,避免竞态条件。
  • 请求取消支持:防止挂起的读请求因用户取消而永久阻塞。
  • 参数修正:从挂起的读请求本身获取输出缓冲区长度,而非复用写请求的参数,确保ZwReadFile调用参数正确。

内容的提问来源于stack exchange,提问作者Артём Варнавский

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 11:22:07