1

如果FILE_SKIP_COMPLETION_PORT_ON_SUCCESS在绑定到 I/O 完成端口的文件句柄上设置,则OVERLAPPED需要在其 I/O 同步完成时释放结构。否则,它需要保持活动状态,直到工作人员处理来自 I/O 完成端口的通知。

这一切听起来都不错,直到您意识到这仅在您自己管理文件句柄时才有效。
但是如果其他人给了你文件句柄,你怎么知道什么时候应该释放这个OVERLAPPED结构呢?有没有办法在事后发现这一点?
否则,这是否基本上意味着您无法在无法保证完成通知状态的任何文件句柄上正确执行重叠 I/O...?

4

2 回答 2

4

我不确定你的场景是否有意义。

您阐明的场景 - 在任意文件句柄上成功执行 I/O,甚至不知道它是否是异步的 - 具有挑战性,我认为非常不寻常,而且几乎可以肯定不是 API 的设计方式,但也许(如你建议)并不完全不可信。

(虽然我认为你不能避免调用者和你的代码之间需要一些合作,因为在 IOCP 的情况下,调用者必须能够知道出队的数据包属于谁的 I/O。你可以通过让正如 RbMm 所建议的那样,调用者分配 OVERLAPPED 结构,但向他们询问要使用的完成键可能更简单。)

如果您提供冗余事件句柄(例如,当 I/O 实际上是同步的或使用 IOCP 时),我不确定 Windows 的行为方式。但我想这在实践中不会成为问题,所以如果你不太担心未来的验证,你可能没问题。


无论如何,处理您的问题所涉及的特定问题并不是那么困难。基本上,你只需要防止结构被释放两次。

  • 在进行每次调用之前,分配一个唯一的完成键并将其添加到链表或其他合适的全局结构中。(该结构必须能够进行原子查找和删除操作,或受临界区或类似的保护。)

  • 如果调用立即成功,即没有报告 I/O 处于未决状态,则将其完全视为从 IOCP 队列接收到排队的数据包。通常,您可以使用由 IOCP 线程和 I/O 线程调用的通用函数,或者调用 PostQueuedCompletionStatus 手动将数据包插入 IOCP 队列。

  • 当接收到一个数据包时(或当调用立即成功时),首先针对全局结构执行完成键的查找和删除。如果查找失败,您就知道您已经收到 I/O 成功的通知,无需执行任何操作。

  • 如果 find-and-remove 成功,则适当处理 I/O 并释放 OVERLAPPED 结构。

毫无疑问,有很多方法可以优化相同的基本方法。

附录:如果调用者正在处理 IOCP 数据包,并为您提供要使用的完成键,您将无法在每个请求上使用唯一的完成键。在这种情况下,您可以改用指向 OVERLAPPED 结构的指针。

不使用指针的原因(在一般情况下)是您可能会收到一个数据包,其中包含来自一个 I/O 请求的完成键以及来自另一个 I/O 请求的 OVERLAPPED 结构,因为 OVERLAPPED 结构可能被释放和重新分配在处理重复通知之前。在这种情况下,这无关紧要,因为无论如何您的所有请求都将使用相同的完成键。

附录^2:如果您对句柄一无所知,您还需要为每个 OVERLAPPED 结构提供一个事件对象,并等待它们以防 I/O 完成通知以这种方式到达。对我来说,试图弄清楚其确切后果已经为时已晚,但这可能意味着在某些情况下,您会收到三个关于同一 I/O 操作的通知。您也许可以避免这种情况,但如果不能,这种方法仍然有效。

于 2016-12-08T01:38:57.277 回答
1

有没有办法在事后发现这一点?

是的,存在 - 需要使用ZwQueryInformationFilewith定义在FileIoCompletionNotificationInformation FILE_IO_COMPLETION_NOTIFICATION_INFORMATIONwdm.h

所以我们需要查询的代码:

FILE_IO_COMPLETION_NOTIFICATION_INFORMATION ficni;
ZwQueryInformationFile(hFile, &iosb, &ficni, sizeof(ficni), FileIoCompletionNotificationInformation);

设置和查询的演示代码

HANDLE hFile;
IO_STATUS_BLOCK iosb;
STATIC_OBJECT_ATTRIBUTES(oa, "\\systemroot\\notepad.exe");
if (0 <= ZwOpenFile(&hFile, FILE_GENERIC_READ, &oa, &iosb, FILE_SHARE_VALID_FLAGS, 0))
{
    FILE_IO_COMPLETION_NOTIFICATION_INFORMATION ficni = { FILE_SKIP_COMPLETION_PORT_ON_SUCCESS };
    if (0 <= ZwSetInformationFile(hFile, &iosb, &ficni, sizeof(ficni), FileIoCompletionNotificationInformation))
    {
        ficni.Flags = 0x12345678;
        if (
            0 > ZwQueryInformationFile(hFile, &iosb, &ficni, sizeof(ficni), FileIoCompletionNotificationInformation)
            ||
            !(ficni.Flags & FILE_SKIP_COMPLETION_PORT_ON_SUCCESS)
            )
        {
            __debugbreak();
        }
    }

    ZwClose(hFile);
}

也让复制粘贴wdm.h(不要说这是“无证”)

//
// Don't queue an entry to an associated completion port if returning success
// synchronously.
//
#define FILE_SKIP_COMPLETION_PORT_ON_SUCCESS    0x1

//
// Don't set the file handle event on IO completion.
//
#define FILE_SKIP_SET_EVENT_ON_HANDLE           0x2

//
// Don't set user supplied event on successful fast-path IO completion.
//
#define FILE_SKIP_SET_USER_EVENT_ON_FAST_IO     0x4

typedef  struct _FILE_IO_COMPLETION_NOTIFICATION_INFORMATION {
    ULONG Flags;
} FILE_IO_COMPLETION_NOTIFICATION_INFORMATION, *PFILE_IO_COMPLETION_NOTIFICATION_INFORMATION;

我有疑问 - 这是出于什么原因声明的wdm.h

于 2016-12-08T01:01:25.317 回答