16

我正在研究一些以 Apache 的 MPM prefork 服务器为模型的 Python 代码。我更像是一名应用程序程序员而不是网络程序员,自从我阅读 Stevens 以来已经 10 年了,所以我正在努力加快理解代码的速度。

我找到了Sander Temme 对 Apache 的 prefork 代码如何工作的简短描述。

通常以 root 身份运行的父进程绑定到一个套接字(通常是端口 80 或 443)。它产生子节点,继承套接字的打开文件描述符,并将 uid 和 gid 更改为非特权用户和组。孩子们构造一个监听器文件描述符的轮询集(如果有多个监听器)并观察它/它们上的活动。如果找到活动,则子进程在活动套接字上调用 accept() 并处理连接。完成后,它会返回查看轮询集(或侦听器文件描述符)。

由于多个子节点处于活动状态并且它们都继承了相同的套接字文件描述符,因此它们将监视相同的轮询集。一个接受互斥体只允许一个孩子实际观看轮询集,一旦找到一个活动套接字,它将解锁互斥体,以便下一个孩子可以开始观看轮询集。如果只有一个侦听器,则不使用该接受互斥体,并且所有子级都将挂在接受()中。

这几乎就是我正在查看的代码的工作方式,但我不明白一些事情。

1)“孩子”和“听众”有什么区别?我认为每个孩子都是一个听众,这对于我正在查看的代码来说是正确的,但是在 Temme 的描述中可以有“单个听众”和“孩子”。一个孩子什么时候会有多个听众?

2)(与 1 相关)这是每个进程的互斥锁还是系统互斥锁?就此而言,为什么要有互斥锁?不接受(2)在所有听众中做自己的互斥锁吗?我的研究表明我确实需要一个互斥锁,并且该互斥锁必须跨越整个系统。(群、信号量等)

泰姆接着说:

当他们最后一次服务请求时,孩子们会在共享内存区域(记分板)中记录。空闲子进程可能会被父进程杀死以满足 MaxSpareServers。如果空闲的子节点太少,父节点将产生子节点以满足 MinSpareServers。

3)这个实现有没有很好的参考代码(最好是Python)?我找到了 Perl 的Net::Server::Prefork,它使用管道而不是共享内存作为记分牌。我发现了Randal Schwartz的一篇文章,它只做预分叉,但不做记分牌。

Perl Cookbook中的pre-fork 示例没有围绕 select 进行任何类型的锁定,Chris Siebenmann 的 Python 示例说它基于 Apache,但使用配对套接字作为记分板,而不是共享内存,并使用套接字进行控制,包括控制给定的孩子“接受”。这根本不符合 Apache 的描述。

4

1 回答 1

16

关于 (1),侦听器只是对接受连接的套接字存在的引用。由于 Apache 可以同时接受多个套接字上的连接,例如 80/443,因此存在多个侦听器套接字。每个子进程都需要在时机成熟时侦听所有这些套接字。由于accept() 一次只能在一个套接字上执行,因此它前面是poll/select,因此知道应该在哪个侦听器套接字上执行accept。

关于(2),它是一个全局或跨进程互斥体。也就是说,一个进程锁定它会阻止其他试图获取相同锁的进程。尽管 accept() 在技术上会序列化进程,但多个侦听器套接字的存在意味着您不能依赖它,因为您事先不知道要在哪个套接字上执行接受。即使在单个侦听器套接字的情况下,接受互斥锁的原因是,如果有大量进程处理请求,那么如果操作系统唤醒所有进程以查看哪些进程有 accept() 返回,则可能会非常昂贵。由于 prefork 模式下的 Apache 可能有 100 多个进程,这可能会导致问题。

因此,如果您只有一个侦听器套接字并且知道您只有几个进程想要执行 accept() 调用,那么您可以取消跨进程接受互斥锁。

于 2009-10-18T22:32:47.937 回答