自己实现管道,如何知道有多少个进程拥有我创建的管道的文件描述符?

4
我需要自己实现一个管道,它将具有类似于 pipe()read()write()close() 的常规管道函数。该管道用作父子进程之间的通信通道,这意味着程序将使用 fork(),可能不止一次。
我的想法是在 pipe() 函数中使用 malloc 实现,然后在 close() 函数中释放。但是,当 fork 命令发生时,我会得到多个进程持有我的管道文件描述符的情况,这是我无法解决的部分:
如何知道 fork() 已经被调用了多少次,有多少进程可以访问我的管道?如何防止关闭其管道一端的进程关闭所有其他进程的管道一端?如何确保关闭管道的最后一个进程会释放内存?
更新:
实际上,它是要与文件描述符一起使用的,而进程不管理此内存,它们只能访问它。我需要一种方法来防止一个进程在所有其他进程关闭之前关闭其管道的一端,并确保一旦所有端点都关闭,内存就会被释放。

你是在内核中进行这个操作,还是在普通用户空间代码中模拟它?我猜是后者,但这会让你的生活变得复杂,尤其是因为其中一个子进程可能会自己分叉,追踪回去会很困难。在内核中有优势,可以正确地进行此类跟踪。 - Jonathan Leffler
1
如果你想要实现无需使用内核管道的管道,我建议你看一下共享内存段,可以在这里查看教程链接。但显然这并不是一个完全自给自足的实现方式。 - user3159253
@JonathanLeffler 我不确定有什么区别,我只是用C代码简单地实现它,注意它应该在一个我没有root访问权限的服务器上运行。 - Ravid Goldenberg
虽然它已经被malloc分配了一次,但是在fork之后,您将获得两个完全独立的进程,它们互相是副本(字面上的副本,它们之间唯一的初始差异是fork()返回值)。但是,一旦其中一个进程更改了其内存中的任何内容,另一个进程就不会再看到这些更改(除非更改的内存是显式的共享内存,即使用shmget()等分配的内存)。 - user3159253
1
@petric,我认为如果你能提供你的原始问题的完整描述会很有帮助。像你想象中的用户空间管道那样,在用户空间中是完全不可能的(例如,自定义文件描述符需要编写一个内核空间模块)。 - Dummy00001
显示剩余9条评论
3个回答

3
我认为你想要实现的是类似于pipe()的东西,但不实际使用pipe()(甚至没有FDs),而是使用内存。当程序执行fork()时,它的FD对子进程和父进程都可用。因此,使用pipe(),子进程可以写入,父进程可以读取(或反之亦然)。但是,子进程继承了父进程内存的写时复制副本。因此,由子进程执行的任何写操作都不会被父进程看到,反之亦然。另一方面,如果使用线程,则内存映射是真正共享的。
你正在尝试进行一种IPC(进程间通信)形式。如果您不想使用内存,则需要一种特殊类型的内存。一种方法是使用shmgetshmctl等 - 这里有一个示例here
然而,您将不得不自行管理内存分配,而不是使用malloc()free()malloc()free()作用于堆,不会使用共享内存段。
为了完整起见,对于您的问题,技术上说父进程和所有子进程必须在fork()之前释放父进程所做的任何分配,因为它们每个人不仅有分配的内存副本,还有内存分配控制结构的副本。但是,它们实际上每个人都有一个包含相同数据的不同分配(通过写时复制实现)。

@petric:如果你在意哪一端先关闭管道,那么就不要关闭另一端,直到需要先关闭的那一端已经完成。您可以使用正常的FD语义来确定管道是否已关闭(例如,select表示它已准备好读取,但read没有返回字节)。然后让最后一个关闭的端口进行释放。 - abligh
我认为您可能需要澄清您的问题。如果另一端可靠地关闭连接,则在此之后简单地关闭您的端口即可。如果另一端没有可靠地关闭连接,并且在关闭之前无法释放共享内存,据我所见,您可能会无限期地等待它关闭。 - abligh
@petric - 你是说在不同的管道另一端有多个进程吗?如果是这样,只需维护一个计数器。还是在同一管道的另一端有多个进程,例如因为另一端已经进行了fork()dup()操作来复制FD?如果是这样,我相信闭包检测将成功检测到最后一个进程关闭了管道的另一端(如果你仔细想想,这必须是这样的,否则在fork()之后关闭所有FD将是危险的)。 - abligh
这是后一种选项,所谓的"闭包检测"是什么意思? 如果不清楚的话,我是在编写"管道库"的人,任何我自己不做的事情都不会自动完成。 - Ravid Goldenberg
“闭包检测”指的是从select()返回可读状态的管道中读取0字节(标准读端关闭检测),或者向select()返回可写状态的管道中写入数据并收到EPIPE错误(标准写端关闭检测)。 - abligh
显示剩余3条评论

2

我不确定您确切想要什么,但有一些选项:

  1. 使用命名信号量,每个子进程打开并增加它,等待在父进程中返回到0。这是最简单和最有效的方法。
  2. 获取每个子进程的PID,并使用其中一个等待函数'wait(),wait3(),wait4(),waitpid()'确保它们已全部退出。如果您的子进程没有退出,则程序可能会挂起。
  3. 解析系统“lsof”命令的输出。该命令可以直接在命名文件上运行,但效率不高。

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接