use*_*911 89 networking embedded webserver
我试图了解网络服务器的底层细节。我想知道服务器,比如 Apache,是否在不断轮询新请求,或者它是否通过某种中断系统工作。如果是中断,是什么触发中断,是网卡驱动?
Gre*_*ser 182
简短的回答是:某种中断系统。本质上,它们使用阻塞 I/O,这意味着它们在等待新数据时休眠(阻塞)。
服务器创建一个侦听套接字,然后在等待新连接时阻塞。在此期间,内核将进程置于可中断睡眠状态并运行其他进程。这是很重要的一点:让进程不断轮询会浪费 CPU。内核可以通过阻塞进程直到有工作要做,从而更有效地使用系统资源。
当新数据到达网络时,网卡发出中断。
内核看到有来自网卡的中断,通过网卡驱动,从网卡中读取新数据并存入内存。(这必须快速完成,并且通常在中断处理程序中处理。)
内核处理新到达的数据并将其与套接字相关联。在该套接字上阻塞的进程将被标记为可运行,这意味着它现在可以运行。它不一定立即运行(内核可能决定仍然运行其他进程)。
在空闲时,内核将唤醒被阻止的网络服务器进程。(因为它现在可以运行了。)
网络服务器进程继续执行,就好像没有时间过去一样。它的阻塞系统调用返回并处理任何新数据。然后……转到第 1 步。
小智 9
有很多“较低”的细节。
首先,考虑内核有一个进程列表,并且在任何给定时间,这些进程中的一些正在运行,而另一些则没有。内核允许每个正在运行的进程占用一些 CPU 时间,然后中断它并移动到下一个。如果没有可运行的进程,那么内核可能会向 CPU发出类似于HLT的指令,该指令会暂停 CPU,直到出现硬件中断。
服务器中的某处是一个系统调用,上面写着“给我做点什么”。有两大类方法可以做到这一点。在 Apache 的情况下,它调用acceptApache 先前打开的套接字,可能监听端口 80。内核维护一个连接尝试队列,并在每次收到TCP SYN时添加到该队列中。内核如何知道接收到 TCP SYN 取决于设备驱动程序;对于许多 NIC,接收网络数据时可能会出现硬件中断。
accept要求内核在下一次连接启动时返回给我。如果队列不为空,则accept立即返回。如果队列为空,则从正在运行的进程列表中删除进程 (Apache)。当稍后启动连接时,该过程将恢复。这被称为“阻塞”,因为对于调用它的进程来说,它accept()看起来像一个函数,直到它有结果才返回,这可能是从现在开始的一段时间。在此期间,该过程无能为力。
一旦accept返回,Apache 就知道有人正在尝试发起连接。然后调用fork将 Apache 进程拆分为两个相同的进程。其中一个进程继续处理 HTTP 请求,另一个进程accept再次调用以获取下一个连接。因此,总有一个主进程只调用accept和生成子进程,然后每个请求都有一个子进程。
这是一种简化:可以使用线程而不是进程来执行此操作,也可以fork事先这样做,以便在收到请求时准备好工作进程,从而减少启动开销。根据 Apache 的配置方式,它可以执行以下任一操作。
这是如何做到这一点的第一个大类,它被称为阻塞 IO,因为像accept和read和write对套接字进行操作的系统调用将挂起进程,直到它们有东西要返回。
另一种广泛的方法称为非阻塞或基于事件或异步 IO。这是通过像selector 之类的系统调用来实现的epoll。它们都做同样的事情:你给它们一个套接字列表(或者一般来说,文件描述符)以及你想用它们做什么,内核会阻塞,直到它准备好做这些事情之一。
使用此模型,您可能会告诉内核(使用epoll),“告诉我何时在端口 80 上有新连接或在我打开的 9471 个其他连接中的任何一个上读取新数据”。epoll阻塞,直到其中一项准备就绪,然后你才去做。然后你重复。系统调用accept和read和write从不阻塞,部分是因为每当你调用它们时,epoll只是告诉你它们已经准备好了,所以没有理由阻塞,还因为当你打开套接字或文件时,你指定了你想要它们在非阻塞模式下,因此这些调用将失败EWOULDBLOCK而不是阻塞。
这种模型的优点是您只需要一个过程。这意味着您不必为每个请求分配堆栈和内核结构。Nginx和HAProxy使用这种模型,这是它们在类似硬件上可以处理比 Apache 多得多的连接的一个重要原因。
| 归档时间: |
|
| 查看次数: |
8573 次 |
| 最近记录: |