我正在尝试了解Web服务器的较低细节。我想知道服务器(例如Apache)是否正在持续轮询新请求,或者服务器是否可以通过某种中断系统工作。如果是中断,是什么引发了该中断,这是网卡驱动程序吗?
我正在尝试了解Web服务器的较低细节。我想知道服务器(例如Apache)是否正在持续轮询新请求,或者服务器是否可以通过某种中断系统工作。如果是中断,是什么引发了该中断,这是网卡驱动程序吗?
Answers:
简短的答案是:某种中断系统。本质上,他们使用阻塞I / O,这意味着他们在等待新数据时会休眠(阻塞)。
服务器创建一个监听套接字,然后在等待新连接时阻塞。在这段时间内,内核将进程置于可中断的睡眠状态并运行其他进程。这一点很重要:连续进行进程轮询会浪费CPU。内核可以通过阻止进程直到有工作要做,才能更有效地使用系统资源。
当新数据到达网络时,网卡将发出一个中断。
看到网卡有中断,内核通过网卡驱动程序从网卡读取新数据并将其存储到内存中。(这必须快速完成,并且通常在中断处理程序中进行处理。)
内核处理新到达的数据并将其与套接字关联。在该套接字上阻塞的进程将被标记为可运行,这意味着它现在可以运行了。它不一定立即运行(内核可能决定仍然运行其他进程)。
内核会在闲时唤醒被阻止的Web服务器进程。(因为它现在可以运行了。)
Web服务器进程将继续执行,就像没有时间过去一样。它的阻塞系统调用返回并处理任何新数据。然后...转到步骤1。
accept
。它们是(幸运的是,或将完全糟透了!)独立的异步运行任务。随着连接的进入,它们被放入一个队列中,accept
从中拉出它们。只有不存在时,它才会阻塞。
有很多“较低”的细节。
首先,请考虑内核具有进程列表,并且在任何给定时间,这些进程中有一些正在运行,而有些则没有。内核允许每个正在运行的进程占用一部分CPU时间,然后中断它并移至下一个。如果没有可运行的进程,则内核可能会向CPU 发出类似HLT的指令,从而使CPU挂起,直到发生硬件中断为止。
服务器中某处的系统调用显示“给我一些事情”。有两种大致的实现方法。对于Apache,它调用accept
Apache先前打开的套接字,可能监听端口80。内核维护一个连接尝试队列,并在每次接收到TCP SYN时将其添加到该队列中。内核如何知道已接收到TCP SYN取决于设备驱动程序。对于许多NIC,接收网络数据时可能会发生硬件中断。
accept
请内核将我的下一个连接初始化返回给我。如果队列不为空,则accept
立即返回。如果队列为空,那么将从正在运行的进程列表中删除该进程(Apache)。稍后启动连接时,该过程将恢复。这称为“阻止”,因为对于调用它的进程而言,它accept()
看起来像一个函数,直到它产生结果后才返回,这可能要从现在开始。在这段时间内,该过程无能为力。
一旦accept
返回,Apache就会知道有人正在尝试建立连接。然后,它将调用fork将Apache进程拆分为两个相同的进程。这些过程之一继续处理HTTP请求,其他过程accept
再次调用以获取下一个连接。因此,总会有一个主进程,除了调用accept
和生成子进程外什么都不做,然后每个请求都有一个子进程。
这是一个简化:可以使用线程而不是进程来执行此操作,并且还可以fork
预先执行,这样在收到请求时便有一个工作进程可供使用,从而减少了启动开销。取决于Apache的配置方式,它可以执行以下任一操作。
这是执行此操作的第一大类,它称为阻塞IO,因为系统调用like accept
和read
and write
在套接字上进行操作将挂起进程,直到有返回值为止。
另一种广泛的实现方式称为非阻塞或基于事件或异步IO。这是通过系统调用(例如select
或)实现的epoll
。它们每个都做同样的事情:给它们一个套接字列表(或一般来说,文件描述符),以及它们要使用的功能,内核阻塞直到它准备做其中一件事情。
使用此模型,您可能会告诉内核(带有epoll
),“告诉我端口80上是否有新连接,或者要从我打开的这9471个其他连接中的任何一个上读取新数据。” epoll
直到这些事情之一准备就绪,然后再做。然后重复。系统调用像accept
和read
和write
永不块,部分是因为当你打电话给他们,epoll
只是告诉你,他们已经准备好所以会是没有任何理由阻止,而且还因为当你打开插座或您指定的文件,你希望他们在非阻塞模式下,因此这些调用将失败,EWOULDBLOCK
而不是阻塞。
该模型的优点是您只需要一个过程。这意味着您不必为每个请求分配堆栈和内核结构。Nginx和HAProxy使用此模型,这是一个主要原因,在相似的硬件上,它们可以处理比Apache多得多的连接。