如何兼顾FILE_SKIP_COMPLETION_PORT_ON_SUCCESS,IOCP和清理

时间:2016-12-08 00:15:41

标签: c winapi iocp overlapped-io io-completion-ports

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

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

2 个答案:

答案 0 :(得分:4)

我不确定你的场景是否合理。

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

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

如果您提供冗余事件句柄,例如I / O实际上是同步或使用IOCP时,我不确定Windows的行为方式。但是我猜它在实践中不会成为一个问题,所以如果你不太担心未来的发展,你可能还可以。

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

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

  • 如果呼叫立即成功,即没有报告I / O挂起,则将其视为与从IOCP队列收到排队的数据包完全一样。通常,您要么使用由IOCP线程和I / O线程调用的公共函数,要么使用PostQueuedCompletionStatus来手动将数据包插入IOCP队列。

  • 当收到数据包时(或当呼叫立即成功时)首先针对全局结构执行完成键的查找和删除。如果查找失败,您知道您已经收到I / O成功通知,并且不需要做任何事情。

  • 如果查找和删除成功,请根据需要处理I / O并释放OVERLAPPED结构。

毫无疑问,优化相同基本方法的方法也是如此。

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

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

附录^ 2:如果您对句柄一无所知,您还需要为每个OVERLAPPED结构提供一个事件对象,并在I / O完成通知的情况下等待它们到达那边。我当天来得太晚,试图找出确切后果,但这可能意味着在某些情况下你会得到三个通知用于相同的I / O操作。您可能可以避免这种情况,但如果没有,这种方法仍然有效。

答案 1 :(得分:1)

  

事后有没有办法发现这个?

是的,存在 - 需要ZwQueryInformationFile使用FileIoCompletionNotificationInformation FILE_IO_COMPLETION_NOTIFICATION_INFORMATION定义了wdm.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声明这是什么原因?