代码之家  ›  专栏  ›  技术社区  ›  tcb

WriteFile是否会在事件同步完成时发出信号

  •  3
  • tcb  · 技术社区  · 7 年前

    是否 WriteFile 功能信号通过 lpOverlapped 参数是否同步完成并成功?如果同步失败,是否会发出事件信号?我打开了一个文件的句柄 FILE_FLAG_OVERLAPPED 旗帜我无法从文档中找出这一点,也无法用代码轻松地重新编写这个案例。

    1 回复  |  直到 7 年前
        1
  •  4
  •   RbMm    7 年前

    首先,这个问题不仅与 WriteFile 但对于任何异步I/O函数,几乎所有函数都会得到指向 OVERLAPPED 结构因为对于所有这些功能 IRP ( I/O请求包 )(查看it定义 wdm。H )已分配。 hEvent 句柄来自 重叠的 转换为对象指针并存储在 PKEVENT UserEvent; 成员 IRP公司 。事件设置(或未设置)的确切时间 IRP公司 在中完成 IopCompleteRequest 常规这个 IRP公司 完成函数对于所有I/O api都是通用的,所以和规则(当完成触发时)与所有相关。不幸的是,这是非常糟糕的记录。win32层(比较NT层)在此处添加了额外的非常精简的问题。

    基于 wrk src code ,我们可以看到异步io的I/O管理器触发完成(有3种类型-事件、apc和iocp(互斥)),当 !NT_ERROR( irp->IoStatus.Status ) irp->PendingReturned

    如果我们使用本机api,则直接返回 NTSTATUS -何时 (ULONG)status < 0xc0000000 .但这是一个非常有问题的范围 0x80000000 <= status < 0xc0000000 NT_WARNING(status) 当不清楚时,将设置完成(偶数设置、apc或数据包到iocp队列)。这是因为在分配之前 IRP公司 输入/输出管理器执行一些基本检查,并可以从此处返回错误。通常I/O管理器会从 NT_ERROR(status) ,这正确意味着将不会完成(不会设置事件)),但存在且很少出现异常。例如 ReadDirectoryChangesW (或 ZwNotifyChangeDirectoryFile )the lpBuffer(lpBuffer) 指针必须与DWORD对齐(与 FILE_NOTIFY_INFORMATION )否则输入/输出管理器返回 STATUS_DATATYPE_MISALIGNMENT (0x80000002)发件人 NT_WARNING 范围但在这种情况下不会完成(事件集),因为函数失败 之前 分配 IRP公司 .如果我们打电话 FSCTL_FILESYSTEM_GET_STATISTICS 缓冲区不够大-文件系统驱动程序(不是I/O管理器)返回 STATUS_BUFFER_OVERFLOW (0x8000005)。但因为在这一点上 IRP公司 已分配且代码不是来自 NT_ERROR 范围-将是事件集。

    因此,如果输入/输出管理器出错(之前 IRP公司 已分配)-将不会完成。否则,如果驱动程序出错(传递到 IRP公司 )如果函数返回,则完成 !NT_ERROR(status) .因此,如果我们得到:

    • NT_SUCCESS(status) (the STATUS_PENDING ( 0x103 )是其中的一部分)-will 是 完成
    • NT\U错误(状态) 将无法完成
    • NT\U警告(状态) -不清楚,取决于输入/输出管理器的此错误 (否)或驾驶员(是)

    但使用win32层会使情况更加糟糕。因为不清楚它是如何解释的 NT\U警告(状态) -大多数win32 api将此解释为错误-返回 错误的 并设置上一个错误(从状态转换)。但是一些api-例如 ReadDirectoryChangesW 将其解释为 成功 代码-返回 符合事实的 并没有设置最后一个错误。如果我们打电话 ReadDirectoryChangesW 如果缓冲区对齐错误(但其他参数有效),则返回。。 符合事实的 不设置任何错误。但api调用确实失败了。这个 ZwNotifyChangeDirectoryFile 内部退货 STATUS\u DATATYPE\u未对齐 在这里

    从另一侧,如果 DeviceIoControl 对于 FSCTL\u FILESYSTEM\u GET\u统计信息 失败(返回false),代码为 ERROR_MORE_DATA (转换自 STATUS\u BUFFER\u溢出 )在此情况下,将设置事件(完成)。

    还有win32错误代码我们无法理解-初始状态为 NT\U错误 NT\U警告 代码-转换( RtlNtStatusToDosError )win32状态错误丢失此信息

    问题在于 NT\U警告(状态) 如果我们使用IOCP完成(而不是事件)并设置 FILE_SKIP_COMPLETION_PORT_ON_SUCCESS 在文件上—在这种情况下,I/O管理器将完成条目排队到端口,当且仅当 STATUS\u挂起 将通过本机api调用返回。对于win32层,这通常意味着api返回false,最后一个错误为 ERROR_IO_PENDING .例外情况- WriteFileEx ,则, ReadFileEx 此处返回true。然而,在这种情况下,这并没有帮助 ReadDirectoryChangesW 无论如何(我假设这是windows bug)

    另请阅读 FILE_SKIP_SET_EVENT_ON_HANDLE section-这隐式表示在case asynchronous函数中设置显式事件(来自overlapped)的时间-当请求返回时 成功代码 ,或返回的错误为 错误\u IO\u挂起 但这里的问题是什么 成功代码 ?win32 api返回true?不总是,从 FSCTL\u FILESYSTEM\u GET\u统计信息 -the ERROR\u MORE\u数据 ( STATUS\u BUFFER\u溢出 )还有成功代码。或 STATUS_NO_MORE_FILES 返回人 NtQueryDirectoryFile 还将设置成功代码-事件(apc或iocp完成)。但还是一样的 NtQueryDirectoryFile 可以返回 STATUS\u DATATYPE\u未对齐 什么时候 FileInformation 对齐错误-这是失败代码,因为在分配之前从I/O管理器返回 IRP公司

    这个 NT\U警告 大多数情况下的状态是成功代码(将是完成),但win32层在大多数情况下将其解释为失败代码(返回false)。

    测试代码示例:

    ULONG BOOL_TO_ERROR(BOOL fOk)
    {
        return fOk ? NOERROR : GetLastError();
    }
    
    void CheckEventState(HANDLE hEvent, ULONG err, NTSTATUS status = RtlGetLastNtStatus())
    {
        DbgPrint("error = %u(%x)", err, err ? status : STATUS_SUCCESS);
    
        switch (WaitForSingleObject(hEvent, 0))
        {
        case WAIT_OBJECT_0:
            DbgPrint("Signaled\n");
            break;
        case WAIT_TIMEOUT:
            DbgPrint("NON signaled\n");
            break;
        default:
            DbgPrint("error=%u\n", GetLastError());
        }
    #if 1
        EVENT_BASIC_INFORMATION ebi;
        if (0 <= ZwQueryEvent(hEvent, EventBasicInformation, &ebi, sizeof(ebi), 0))
        {
            DbgPrint("EventState = %x\n", ebi.EventState);
        }
    #endif
    }
    
    void demoIoEvent()
    {
        WCHAR sz[MAX_PATH];
        GetSystemDirectoryW(sz, RTL_NUMBER_OF(sz));
    
        HANDLE hFile = CreateFileW(sz, 0, FILE_SHARE_VALID_FLAGS, 0, 
            OPEN_EXISTING, FILE_FLAG_OVERLAPPED|FILE_FLAG_BACKUP_SEMANTICS, 0);
    
        if (hFile != INVALID_HANDLE_VALUE)
        {
            FILESYSTEM_STATISTICS fs;
    
            OVERLAPPED ov = {};
    
            if (ov.hEvent = CreateEvent(0, TRUE, FALSE, 0))
            {
                FILE_NOTIFY_INFORMATION fni;
                IO_STATUS_BLOCK iosb;
    
                // STATUS_DATATYPE_MISALIGNMENT from I/O manager
                // event will be not set
                NTSTATUS status = ZwNotifyChangeDirectoryFile(hFile, ov.hEvent, 0, 0, &iosb, 
                    (FILE_NOTIFY_INFORMATION*)(1 + (PBYTE)&fni), 1, FILE_NOTIFY_VALID_MASK, FALSE);
    
                CheckEventState(ov.hEvent, ERROR_NOACCESS, status);
    
                // windows bug ! ReadDirectoryChangesW return .. true and no last error
                // but really api fail. event will be not set and no notifications
                ULONG err = BOOL_TO_ERROR(ReadDirectoryChangesW(hFile,
                    (FILE_NOTIFY_INFORMATION*)(1 + (PBYTE)&fni), 1, 0, FILE_NOTIFY_VALID_MASK, 0, &ov, 0));
    
                CheckEventState(ov.hEvent, err);
    
                // fail with ERROR_INSUFFICIENT_BUFFER (STATUS_BUFFER_TOO_SMALL)
                // NT_ERROR(c0000023) - event will be not set
                err = BOOL_TO_ERROR(DeviceIoControl(hFile, 
                    FSCTL_FILESYSTEM_GET_STATISTICS, 0, 0, 0, 0, 0, &ov));
    
                CheckEventState(ov.hEvent, err);
    
                // ERROR_MORE_DATA (STATUS_BUFFER_OVERFLOW)
                // !NT_ERROR(80000005) - event will be set
                // note - win 32 api return false and error != ERROR_IO_PENDING
                err = BOOL_TO_ERROR(DeviceIoControl(hFile, 
                    FSCTL_FILESYSTEM_GET_STATISTICS, 0, 0, &fs, sizeof(fs), 0, &ov));
    
                CheckEventState(ov.hEvent, err);
    
                if (err == ERROR_MORE_DATA)
                {
                    SYSTEM_INFO si;
                    GetSystemInfo(&si);
    
                    ULONG cb = si.dwNumberOfProcessors * fs.SizeOfCompleteStructure;
    
                    union {
                        PVOID pv;
                        PBYTE pb;
                        PFILESYSTEM_STATISTICS pfs;
                    };
    
                    pv = alloca(cb);
    
                    // must be NOERROR(0) here
                    // !NT_ERROR(0) - event will be set
                    err = BOOL_TO_ERROR(DeviceIoControl(hFile, FSCTL_FILESYSTEM_GET_STATISTICS, 0, 0, 
                        pv, cb, 0, &ov));
    
                    CheckEventState(ov.hEvent, err);
    
                    if (!err && GetOverlappedResult(hFile, &ov, &cb, FALSE))
                    {
                        do 
                        {
                            // use pfs here
                        } while (pb += fs.SizeOfCompleteStructure, --si.dwNumberOfProcessors);
                    }
                }
    
                CloseHandle(ov.hEvent);
            }
            CloseHandle(hFile);
        }
    }
    

    和输出:

    error = 998(80000002)NON signaled
    EventState = 0
    error = 0(0)NON signaled
    EventState = 0
    error = 122(c0000023)NON signaled
    EventState = 0
    error = 234(80000005)Signaled
    EventState = 1
    error = 0(0)Signaled
    EventState = 1