accept_mutex_off ();
process the new_connection;
}
accept_mutex_on和accept_mutex_off 两个函数实现了互斥量(mutual exclusion semaphore),
在任意时刻只能有一个子进程拥有互斥量。多种方法可以实现互斥量。在src/conf.h(1.3版之前)
或src/include/ap_config.h(1.3版及以后)可以作出以下选择。一些系统不提供任何互斥方法。
在这些系统上使用多个Listen命令是不保险的。
USE_FLOCK_SERIALIZED_ACCEPT
此方法用flock(2)系统调用对一个锁文件加锁。(此文件在LockFile命令中指定)
USE_FCNTL_SERIALIZED_ACCEPT
此方法用flock(2)系统调用对一个锁文件加锁。(此文件在LockFile命令中指定)
USE_SYSVSEM_SERIALIZED_ACCEPT
(1.3版及以后)此方法借助SysV的信号量(semaphores)实现互斥。但不巧的是SysV信号量有一些
负面作用。一是Apache可能在清除信号量之前非正常终止;二是在使用信号量API时需要考虑到任何
与服务器UID相同的CGI程序可以进行拒绝服务攻击(就是说所有的CGI程序都可以这样做,除非使用
suexec或cgiwrapper之类的方法)。所以,这种方法并不被IRIX之外的系统广泛采纳(由于大多数
IRIX系统上,使用前两种方法的代价太大)。
USE_USLOCK_SERIALIZED_ACCEPT
(1.3版及以后)此方法仅在IRIX上可用。它调用usconfig(2)创建互斥量。虽然这种方法避免了对
SysV信号量的种种争议,但它不是IRIX的缺省方案。这是由于在单处理器的IRIX系统 (5.3或6.2)上,
uslock代码比SysV信号量慢两个数量级;但在多处理器的IRIX中前者比后者快一个数量级。这无非使
问题复杂化了。所以在多处理器IRIX系统上,您需要用如下的附加参数编译Apache:
在EXTRA_CFLAGS中添加-DUSE_USLOCK_SERIALIZED_ACCEPT
USE_PTHREAD_SERIALIZED_ACCEPT
(1.3版及以后)此方法实现了POSIX标准互斥量。它理应可以工作在任何实现了全部POSIX线程规范的
系统上,但事实是只有在Solaris 2.5或以上的系统及特定的配置中才能工作。如果您尝试这种方法的
话,需要小心服务器挂起或者没有响应。服务器在只输出静态网页的情况下运行得很好。
如果您的系统上有其他串行化的方法,为它书写代码(并把补丁寄给Apache)是值得的。
有一个考虑到但从未实现的方案是对循环部分地串行化——即允许一定数目的进程进入循环。在同一时
刻可运行若干进程的多处理器系统上,这个主意是满不错的。而且前面提到的方案并没有充分利用带宽。
可由于高度并行化的服务器实在少见,这个方案的优先级比较低。
为了得到最佳性能,不用多侦听命令是最理想的。请继续往下看。
单socket中的accept串行化
以上言及的方案对多socket服务器是相当不错的,但只有一个socket的情况又如何呢?理论上,由于
在连接请求到来之前所有子进程将阻塞在accept中,单个socket不会产生上述种种问题。但实际上,
上述非阻塞解决方案所带来的“回旋(spinning)”问题在这里只不过被掩盖起来了。在绝大多数TCP
协议栈的实现中,一个接请求到来时内核将唤醒所有阻塞在accept中的进程。它们之一将得到此请求并
返回用户空间,其余的进程将返回内核重新休眠。这将带来与多socket非阻塞解决方案相同的资源浪费。
由于这点原因,我们发现如果为socket串行化,许多系统表现得更“友好”——即使是一个socket的情
况。这是单个socket串行化作为绝大多数情况的缺省配置的原因。在Linux上不甚精确的
(Linux 2.0.30 / 双Pentium Pro 166 w / 128Mb内存)实验表明,对每次请求而言,串行化的单个
socket仅比没有串行化的socket损失不到3%的性能。但未串行化的socket显示出每次连接请求100毫秒
的延时。这也可能仅仅由于过长的通讯距离造成的。如果您不想串行化单个socket,可以定义宏
SINGLE_LISTEN_UNSERIALIZED_ACCEPT。这样,仅有一个socket的服务器将不会串行化。
延迟关闭(Lingering Close)
就象draft-ietf-http-connection-00.txt第8节讨论的那样,为了使服务器能够可靠地实现HTTP协议,
有必要独立地关闭每个方向上的通讯(每个TCP连接有两个方向,每个方向是分别独立的)。这个事实往
往被其他服务器所忽视,而Apache 1.2就已经正确地处理了。
当这个特性增加到Apache中时却在许多版本的Unix中引起了问题。这是TCP规范的短见造成的——它没有
声明FIN_WAIT_2有超时,但也没有阻止这样的实现。在没有超时的系统中,Apache 1.2将导致许多
socket将永远处于FIN_WAIT_2的状态。这可以简单地用打最新TCP/IP补丁的方法避免。然而在提供商从
不发行补丁的系统上(也就是SunOS4——虽然得到源代码许可证的人可以自己打补丁),我们决定不直
接使用这一特性。
有两种实现这个特性的办法:一是socket的SO_LINGER选项。但似乎是命中注定,在多数TCP/IP协议栈中
它从来不能正确地实现。即使是在提供了正确实现的平台(即Linux 2.0.31)上,这种方法也要比第二
种方法代价(指CPU时间)高得多。
大多数情况下,Apache在一个叫lingering_close的函数中实现了它(在 http_main.c)。这个函数大致
如下所示:
void lingering_close (int s)
{
char junk_buffer[2048];
/* shutdown the sending side */
shutdown (s, 1);
signal (SIGALRM, lingering_death);
alarm (30);
for (;
{
select (s for reading, 2 second timeout);
if (error) break;
if (s is ready for reading) {
read (s, junk_buffer, sizeof (junk_buffer));
/* just toss away whatever is here */
}
}
close (s);
}
这自然增加了连接结束时的开销,但它是可靠的实现所必需的。随着HTTP/1.1的日益盛行,所有连
接都是持久的,这种开销将被众多的连接请求抵消。如果您想冒险禁止这一特性的话,可以定义宏
NO_LINGCLOSE,但这显然是不被推荐的。实际上,由于在HTTP/1.0中持久的管道式连接越来越普遍,
lingering_close几乎是必须的选择。(管道式连接非常高效,所以您还是希望支持它的吧)
记分板文件
Apache利用一种叫做记分板(scoreboard)的技术在父、子进程间通讯。它的理想实现是在共享内
存中。有的操作系统允许我们直接访问共享内存,或者提供它们的确切端口。在这些系统中的典型
实现就是共享内存记分板。其他的系统则将磁盘上的文件作为缺省实现。磁盘文件不仅低效而且不
稳定(又没有什么优势)。请为您的操作系统仔细阅读src/main/conf.h文件,并在其中寻找
USE_MMAP_SCOREBOARD或者USE_SHMGET_SCOREBOARD。定义它们之一(以及相应的HAVE_MMAP和HAVE_SHMGET)
将允许Apache使用共享内存。如果您系统的内存共享机制与众不同,请编辑src/main/http_main.c
并增加Apache所需的挂钩函数(同时请把补丁寄给我们)
注:直到1.2版,Apache的Linux版才开始使用共享内存。这一疏忽使得以前版本的Apache在Linux上
表现得很不理想。
DYNAMIC_MODULE_LIMIT
如果您不打算支持动态加载模块的话(准备榨出最后一滴性能的您可能希望如此),编译服务器时
请设定参数-DDYNAMIC_MODULE_LIMIT=0。这将节省出为动态加载模块而分配的内存。