【问题标题】:poll() c function on windowswindows上的poll() c函数
【发布时间】:2009-11-04 05:27:26
【问题描述】:

我想知道我是否可以在 MinGW 开发链上使用 poll() 函数。我有 CodeBlocks+MinGW。非常感谢。

【问题讨论】:

  • 你试过什么?当你调用它时它存在吗?使用的时候有用吗?你为什么不试试呢?你有任何证据表明它不起作用吗?
  • 嗯,听说只有POSIX,想知道有没有人在MinGW上成功使用过,但我没试过。
  • 可能我应该把它放在这里作为答案,但是这个问题太老了。所以,你不能使用; MinGW 甚至都不知道,这是什么功能。但有趣的是,在 MinGW 的文件 winsock2.h 中定义了一个结构pollfd,甚至不知道为什么。可能是某个人的希望,被残忍地掩埋在源代码的千分之一符号之下。而且,可能,我是那些希望的人之一。

标签: c mingw


【解决方案1】:

我上次听说,在 mingw 上不支持 poll() 或提供的功能有限

那是在 2009 年。

在 2020 年...它受到支持,但仍然存在“缺陷”,如 Git 2.25.2(2020 年 3 月)所示,其中 MinGW 的 poll() 仿真得到了改进。

参见 Alexandr Miloslavskiy (SyntevoAlex)commit 94f4d01(2020 年 2 月 17 日)。
(由 Junio C Hamano -- gitster -- 合并于 commit 1ac37de,2020 年 3 月 9 日)

mingw: 发送 STDIN 时挂起的解决方法

签字人:Alexandr Miloslavskiy

说明

这里的问题是有缺陷的poll() 实现。
当它试图查看是否可以在不阻塞的情况下写入管道时,它最终会调用NtQueryInformationFile() 并测试WriteQuotaAvailable

但是,配额的含义被误解了
当某些数据被写入管道时,配额的值会减少,管道上有一个挂起的读取。
因此,如果等待读取的大小 >= 大于管道的缓冲区大小,poll() 会认为管道不可写并将永远挂起,这通常意味着两个管道用户都死锁了。

我研究了这个问题,发现 Windows 管道跟踪两个值:QuotaUsedBytesInQueue

poll() 中的代码显然想知道BytesInQueue 而不是配额。
不幸的是,BytesInQueue 只能从管道的读取端请求,而poll() 接收写入端。

poll() 的 git 实现是从 gnulib 复制的,迄今为止,它还包含一个有缺陷的实现。

我还查看了 cygwin 中的实现,它也以一种微妙的方式被破坏。它在pipe_data_available() 中使用此代码:

fpli.WriteQuotaAvailable = (fpli.OutboundQuota - fpli.ReadDataAvailable)

但是,ReadDataAvailable 总是为管道的写入端返回 0,将代码转换为返回管道总缓冲区大小的混淆版本,我猜反过来 poll() 总是说管道是可写的。
引入代码的提交并没有说明此更改,因此可能是一些调试代码滑入。

这些是 git 中使用的典型尺寸:

  • 0x2000 - strbuf_read() 中的默认读取大小
  • 0x1000 - CRT 中的默认读取大小,由 strbuf_getwholeline() 使用
  • 0x2000 - compat\mingw.c 中的管道缓冲区大小

因此,一旦子进程使用strbuf_read(),父进程中的poll() 将永远挂起,导致两个进程死锁。

这会导致两个可观察的行为:

  1. 如果父进程开始快速发送 STDIN(通常是这种情况),那么第一个 poll() 将成功并且第一个块将通过。
    MAX_IO_SIZE_DEFAULT 是 8MB,所以如果 STDIN 超过 8MB,那么它将死锁。
  2. 如果父进程因任何原因(包括 OS 调度程序)稍等片刻,而子进程首先发出 strbuf_read(),那么即使在小型 STDIN 上也会立即死锁。

git stash push 说明了这个问题,它当前会将整个补丁读入内存,然后通过 STDIN 将其发送到git apply
如果补丁超过 8MB,则 git 在 Windows 上挂起。

可能的解决方案

  1. 以某种方式获得BytesInQueue 而不是QuotaUsed
    我进行了非常彻底的搜索,但没有找到任何从管道写入端获取值的方法。
    .
  2. 还将管道的读取端发送给poll()
    可以这样做,但它可能会引入一些脏代码,因为poll()
    • 可以一次接受多个管道
    • 可以接受不是管道的东西
    • 应该有一个众所周知的签名。
      .
  3. 使poll() 始终回复“可写”以表示管道的写入结束
    毕竟,cygwin(偶然?)似乎多年来一直这样做。
    另外需要注意的是,pump_io_round() 写入了 8MB 块,完全忽略了管道的缓冲区大小只有 8KB 的事实,这意味着管道在单次写入期间被多次阻塞。
    如果孩子的 STDERR/STDOUT 在尝试处理 8MB 的 STDIN 时被阻塞,这可能会导致死锁。
    这种死锁可以通过每轮写入小于管道的缓冲区大小来解决,并且在开始下一轮之前总是从 STDOUT/STDERR 读取所有内容。
    因此,让poll() 始终回复“可写”不应导致任何新问题或阻止任何未来的解决方案。
    .
  4. 增加管道缓冲区的大小
    BytesInQueueQuotaUsed 之间的区别在于挂起读取的大小。因此,如果缓冲区大于读取大小,poll() 就不会那么容易挂起。但是,我发现例如 strbuf_read() 在读取大量输入时会变得越来越饥饿,最终超过任何合理的管道缓冲区大小。

选择的解决方案

使poll() 始终回复“可写”以表示管道的写入端。
希望有一天有人会找到一种正确实施它的方法。

复制

printf "%8388608s" X >large_file.txt 
git stash push --include-untracked -- large_file.txt

我决定不将此作为测试,以避免减慢测试套件的速度。
我不认为具体问题会再次出现,而且git stash push 可能会被重新设计以避免通过 STDIN 发送整个补丁。

【讨论】:

    【解决方案2】:

    最后我听说,poll() 在 mingw 上要么不受支持,要么提供有限的功能。但是您可以提供自己的基本poll(),在内部使用select()

    【讨论】:

    • 不! Win32 select() 几乎与poll() 函数无关。它不能监听串口,也不能监听命名管道。
    【解决方案3】:

    Windows 也提供了WSAPoll() 功能,但性能比select() 差,来自 MSDN 博客,它只是为了提高可移植性而实现的:

    http://blogs.msdn.com/b/wndp/archive/2006/10/26/wsapoll.aspx

    【讨论】:

    • WSAPoll() 不适用于命名管道,正如我刚刚在您自己的答案中发现的那样。我怀疑这不适用于串行端口。
    • 另外值得注意的是WSAPoll() 不会报告失败的连接。微软对 cURL 的作者的建议是不要使用它,它的 API 在 Windows 8 之后将无关紧要 - curl.haxx.se/mail/lib-2012-10/0038.html
    猜你喜欢
    • 1970-01-01
    • 2012-08-23
    • 2011-05-07
    • 1970-01-01
    • 2016-02-11
    • 1970-01-01
    • 2020-09-04
    • 1970-01-01
    • 2021-11-15
    相关资源
    最近更新 更多