【问题标题】:How to safely share a file descriptor between two processes?如何在两个进程之间安全地共享文件描述符?
【发布时间】:2013-07-02 17:28:02
【问题描述】:

这是my previous question here 的后续问题,针对 UNIX。

我想知道进程打开的文件描述符是否可以安全地用于分叉进程。

我通过同时运行数百个进程运行了一些测试,所有进程都连续写入同一个文件描述符。我发现:

  • fwrite()调用达到8192字节时,所有调用都完美序列化,文件正常。
  • fwrite() 调用超过 8192 字节时,字符串会被分成 8192 字节的块,这些块会随机写入文件,最终会损坏。

我尝试使用flock(),但没有成功,因为每个进程都试图锁定/解锁相同的文件描述符,这没有意义。结果是一样的。

有没有办法在所有进程之间安全地共享文件描述符,并正确序列化所有fwrite() 调用?

【问题讨论】:

标签: unix multiprocessing file-descriptor


【解决方案1】:

首先需要注意的是 stdio 缓冲区。因为您使用的是 stdio (fwrite()) 而不是系统调用 directlr (write()),所以您不知道数据何时会真正刷新到文件中。要绕过这个问题,您必须在每次释放锁之前刷新临界区中的 stdio 缓冲区:

take the lock
fwrite(foo, ...);
fflush(thefile);
release the lock

...或者您可以直接切换到使用write()

现在,进入主要问题:如何锁定文件,以便一次只有一个进程可以独占访问该文件。

您可能无法使用flock()。这取决于不同进程如何获得同一个文件的文件描述符。 flock() 锁与打开的文件表条目相关联。因为fork()dup() 创建了新的文件描述符来引用同一个文件表条目,所以从flock() 的角度来看,它们是同一个对象,所以在这种情况下你不能使用flock()。另一方面,如果每个进程直接用open()打开自己的文件副本,那么你可以使用flock()

fcntl() 风格的锁定不会遇到这个问题(相反,它会遇到不同类型的问题!)。 fcntl() 锁是每个进程的,所以进程如何获取同一个文件的文件描述符并不重要。

所以我建议你尝试fcntl()-style 锁定:

struct flock ll;

/* lock */
ll.l_start = ll.l_len = ll.l_whence = 0; /* lock the whole file */
ll.l_type = F_WRLCK; /* exclusive lock */
fcntl(fd, F_SETLKW /* or F_SETLK */, &ll);

/* unlock */
ll.l_type = F_UNLCK;
fcntl(fd, F_SETLKW /* or F_SETLK */, &ll);

【讨论】:

  • 谢谢,您能否详细说明fcntl() 遇到的问题?
  • fcntl() 的每进程锁定的主要问题是,如果同一进程的两个不同部分对同一文件进行操作(例如,主应用程序使用的 2 个不同的库,但'彼此不了解)然后他们将相互冲突。由于锁是针对每个进程的,因此使用 one 文件描述符解锁文件也会从所有引用同一文件的 other 文件描述符的角度释放锁。参见例如perlmonks.org/bare/?node_id=392435
  • 好的,有道理!或者,是否有可能在给定系统上知道fwrite()(或write())调用是原子的(在我的情况下为1023)的最大长度?这仅适用于文件记录器,因此预计写入量无论如何都会非常小,但如果以编程方式知道给定的 write() 是否是原子的或不给定它的长度,那就太好了。
  • 详情请见stackoverflow.com/questions/14417806/…。总结:如果你正在写入管道,原子写入的最大大小是PIPE_BUF,如果你正在写入管道以外的任何东西,那么写入永远不会是原子的。
  • 是的,我已经阅读了这个链接,但奇怪的是,虽然我没有写入管道,但我的经验表明写入 原子高达 1023 字节.欢迎任何解释!
【解决方案2】:

您使用文件描述符的方式是完全安全的。写入不同步,因此输出可能不是您所期望的,但没有什么“不安全”的。如果您要问“如何将写入同步到通用文件描述符”,那么答案很明显:使用同步机制。在您描述在多个进程之间共享一个文件描述符的情况下,也许最简单的事情是在第二个管道上传递一个令牌。有一个在所有进程之间共享的管道并将单个字符写入其中。当一个进程想要写入第一个 fd 时,它会尝试从管道中读取一个字符。读取成功后,继续写入(确保刷新),然后将字符写回管道。当任何进程处于临界区时(即,它已读取但尚未写入令牌),任何其他进程都会阻塞读取,直到其他进程完成其写入,只要您不要忘记刷新!这是一个相当昂贵的操作,因为它需要每个进程保持两个额外的文件描述符打开,并且可用文件描述符数量的上限相当低,通常在 1024 的数量级,但它通常非常有效。

【讨论】:

  • 非常有趣的解释,谢谢。话虽如此,我只使用共享文件描述符写入日志,一次一行。因此,只要我保持在这 8192 个字节以下,我根本就不需要锁定机制。你知道这个数字(我凭经验得出的)是从哪里来的吗?
  • writes 保证在特定值(PIPE_BUF)之前是原子的,具体取决于系统。在您的情况下,该值很可能是 8192。实际值可能更小(512 和 4096 很常见)。检查 PIPE_BUF 的值。在pubs.opengroup.org/onlinepubs/009695399/functions/write.html中搜索“原子”
  • 在他的回答和下面的 cmets 中,@Celada 说 PIP_BUF 仅适用于管道。为什么它在我写入文件时适用?
  • Celada 是正确的;该标准仅保证管道的原子性。很可能,该实现对常规文件使用了类似的机制,并且您将获得原子写入作为副作用,但这不是可以依赖的行为。
猜你喜欢
  • 2012-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-29
  • 1970-01-01
  • 1970-01-01
  • 2013-06-04
  • 2014-01-19
相关资源
最近更新 更多