我正在编写一个数据记录应用程序,所有程序的启动方式如下:
./program > out.bin
数据收集器定期汇集 stdout 输出文件并读取数据。
问题是 IO 流被缓冲,如果某些程序以每秒 1 个字节的速度输出数据,则需要很长时间(默认 4kB 缓冲区大小,最多需要 4k 秒)才能实际刷新数据。
我的问题是如何强制 stdout/pipe/printf 缓冲区从外部刷新,即从外部调用类似fflush(stdout)
.
我读过各种网站,例如关闭管道中的缓冲,但我无法禁用缓冲区,因为它对 IO 性能有巨大的影响(测量)。
我正在寻找高性能的生产解决方案,并且始终满足以下条件:
- 程序(数据生产者)PID 始终已知
- 输出始终是具有已知路径的文件
- 数据记录进程具有完全root访问权限
答案1
gdb -p PID -batch -ex 'p fflush(stdout)'
与任何调试和黑客攻击一样,YMMV。
答案2
您有权访问正在运行的程序的源代码吗?
强制刷新任意可执行文件虽然理论上并非不可能,但非常困难。您需要fflush
在代码中找到该函数和stdout
参数,然后中断程序的执行,安排对 的调用fflush
,然后继续执行。如果程序使用共享库,那么至少使第一部分变得更容易,查找fflush
和stdout
,但您仍然需要模拟调用。另外,对于未知的二进制文件,您无法知道它是否使用了 stdio 或者是否实现了自己的缓冲机制。知道输出文件的路径对您没有帮助。
您可以尝试使用gdb
,然后使用attach
命令附加到进程。也许gdb
可以调用该fflush
函数。
如果您有程序的源代码,请实现一个刷新缓冲区的信号处理程序,并在需要刷新缓冲区时发送信号。
您可以尝试使用管道,如果输出是管道,程序可能不会缓冲。将其更改为
./program | your_program > out.bin
您的程序可以接受输入,对其进行缓冲并在收到信号时刷新缓冲区。它仍然会增加 CPU 开销,但不会增加磁盘开销。
答案3
user313992 的答案几乎是最好的方法,并且至少也可能与其他二进制文件一起使用。解释:启动gdb,没有任何输出,附加到pid,执行flush,隐式:退出。
稍微改进一下:
gdb -batch -p $PID -ex 'call (int)fflush(stdout)' -ex 'call (int)fflush(stderr)'
这样做的好处是确保正确的返回类型并且没有任何打印。只是为了记住这一点,我还添加了 stderr 的刷新。
如果内部使用其他文件句柄或者这些句柄是 GDB 无法解析的宏,则此方法将不起作用。
自己的应用程序的最佳方法是:
- 提供一个
extern
用于执行刷新的 al 函数(它消除了有关宏或不同文件句柄的所有问题,并且已经可以call
由 GDB 编辑,如上面的示例所示) - 在程序中的有用状态下调用该函数:
- 注册一个信号处理程序,例如
SIGUSR1
调用该函数 - >您可以发送该信号来强制刷新 - 轮询文件是否存在(如果存在 - 删除然后刷新)
- 轮询消息/信号量/套接字/...
- 注册一个信号处理程序,例如