How do web-servers "listen" to IP addresses, interrupt or polling?

87

31

I'm trying to understand the lower details of web servers. I am wondering if a server, say Apache, is continuously polling for new requests or If it works by some sort of interrupt system. If it is an interrupt, what is sparking the interrupt, is it the network card driver?

user2202911

Posted 2014-11-09T20:39:17.213

Reputation: 875

1The keyword to understand is "server". In the server-client model (versus master-slave model) the server waits for requests from clients. These requests are events that need to be serviced. A webserver is an application program. Your question combines application SW with HW terminology (e.g. interrupt and NIC), rather than keep related concepts at the same abstraction layer. The NIC driver may actually use polling sometimes, e.g. Linux NAPI drivers regress to polling when there is a flood of packets. But that is irrelevant to the event-processing application SW. – sawdust – 2014-11-10T02:20:17.607

1@sawdust Very interesting. The question is really meant to understand the connection between the SW and HW processes – user2202911 – 2014-11-10T14:41:24.400

1It’s very similar to the way command-line (and other GUI) programs listen to the keyboard. Especially in a window system, where you have the step of the kernel receiving the data from the keyboard device and handing it off to the window manager, which identifies the window that has focus and gives the data to that window. – G-Man Says 'Reinstate Monica' – 2014-11-11T01:10:20.560

@G-Man: I theory, yes. In reality most typists don't type at 1 Gbit/s, which justifies having two different architectures. One clean, flexible and slow, one clumsy but high-speed. – MSalters – 2014-11-12T00:25:36.053

Answers

181

The short answer is: some sort of interrupt system. Essentially, they use blocking I/O, meaning they sleep (block) while waiting for new data.

  1. The server creates a listening socket and then blocks while waiting for new connections. During this time, the kernel puts the process into an interruptible sleep state and runs other processes. This is an important point: having the process poll continuously would waste CPU. The kernel is able to use the system resources more efficiently by blocking the process until there is work for it to do.

  2. When new data arrives on the network, the network card issues an interrupt.

  3. Seeing that there is an interrupt from the network card, the kernel, via the network card driver, reads the new data from the network card and stores it into memory. (This must be done quickly and is generally handled inside the interrupt handler.)

  4. The kernel processes the newly arrived data and associates it with a socket. A process that is blocking on that socket will be marked runnable, meaning that it is now eligible to run. It does not necessarily run immediately (the kernel may decide to run other processes still).

  5. At its leisure, the kernel will wake up the blocked webserver process. (Since it is now runnable.)

  6. The webserver process continues executing as if no time has passed. Its blocking system call returns and it processes any new data. Then... go to step 1.

Greg Bowser

Posted 2014-11-09T20:39:17.213

Reputation: 1 491

18+1 for clear delineation of the kernel vs. the webserver process. – Russell Borogove – 2014-11-09T23:03:53.360

Nice answer, just one nitpick: it's interruptible sleep. Possibly a typo. – Jester – 2014-11-09T23:40:50.540

13I can't believe something as complex as this can be summarized so clearly and simply, but you did it. +1 – Brandon – 2014-11-10T00:30:51.743

8

+1 Great answer. Also, the steps between 2 and 3 can get a little more complex with modern NICs, OSs and drivers. For example, with NAPI on Linux, the packets aren't actually received in interrupt context. Instead, the kernel says "Okay NIC, I understand you've got data. Quit bugging me (disable the interrupt source), and I'll be back shortly to grab this packet and any subsequent packets that might arrive before I do."

– Jonathon Reinhart – 2014-11-10T02:13:04.160

8Slight nitpick: It is not really necessary to block. As soon as the server process has created a listening socket, the kernel will accept SYNs on that port, even while you are not blocked inside accept. They are (luckily, or it would totally suck!) independent, asynchronously running tasks. As connections come in, they're placed into a queue where accept pulls them from. Only if there are none, it blocks. – Damon – 2014-11-10T14:51:03.680

3"reads the new data from the network card and stores it into memory. (This must be done quickly and is generally handled inside the interrupt handler.)" Isn't it done with direct memory access? – Siyuan Ren – 2014-11-10T16:09:58.703

Good answer. I spotted one detail which is a bit more complicated in practice. On a busy network interface it may be too expensive to take an interrupt for each packet, so the driver can switch to polling (and obviously the lowest layers of the stack must be done processing previous packets, before the driver polls for more packets from the interface). – kasperd – 2014-11-10T16:10:13.010

3@SiyuanRen Usually so, but not necessarily. Some lower-end microcontrollers or embedded CPUs don't have DMA-enabled NICs. In most cases, you're correct, though. – reirab – 2014-11-10T21:24:26.167

1This answer only covers Thread based webservers like Apache. Recently there has been a push towards event based webservers like node.js NGINX etc... – Aron – 2014-11-11T02:52:55.707

@reirab: But can that kind of (weak) controller power a web server? – Siyuan Ren – 2014-11-11T06:21:29.017

@SiyuanRen Yes. It's not at all uncommon for administrative interfaces. Remember that embedded CPUs today have far more power than the average server in the early days of the web. – reirab – 2014-11-11T06:23:18.733

1@Aron from a hardware and OS point of view they're fairly similar; event based servers will use a different system call (select(), epoll() or NT overlapped IO) and have more internal bookkeeping to determine what to do with the next packet. – pjc50 – 2014-11-11T09:10:14.300

2@Aron it doesn't make much difference to the stuff that's described in this answer — instead of many threads each blocking on their own events, you have one thread blocking until it gets any event, but the principle is still the same. – hobbs – 2014-11-11T15:09:54.780

Does it work the same way for localhost connections? I heard they don't cause any real network traffic, what's the role of the network card, or is it all just a big fake show with traffic simulated in software? – Ray – 2014-11-11T19:18:02.653

1

@DebugErr I am not 100% sure of the specifics, but in Linux traffic destined for a local IP address is routed via the loopback interface, lo. (e.g. ip route get 192.168.1.25 returns local 192.168.1.25 dev lo src 192.168.1.25 for me.) The loopback interface does have a device driver, but no physical device. So I would surmise that this case simply skips steps 2-3.

– Greg Bowser – 2014-11-11T19:41:15.617

1@pjc50 If you quantify bookkeeping in bytes, then event-based servers have substantially less bookkeeping. That's why people use haproxy and nginx: they can handle a larger number of connections without running out of RAM, compared to fork() or threaded servers like apache. – Phil Frost – 2014-11-12T17:07:40.383

@PhilFrost Good catch. I didn't notice that Blocking_I/O redirected to Asynchronous_I/O on Wikipedia. – Greg Bowser – 2014-11-12T18:44:39.363

2@DebugErr This answer is slightly misleading, hence the question about the NIC. The NIC driver receives an interrupt via kernel/hardware. This driver translates the packet into a message format, which is then passed to the network driver (TCP/IP). The network driver maintains a list of PID to IP:PORT:PROTO, and delivers the packet via kernel messaging to the program's I/O or message queue. The kernel then decides which thread to eventually awaken when a pending message is noticed. When using loopback, the NIC driver never sees the packet; TCP/IP passes the data from the first app to the other. – phyrfox – 2014-11-12T20:35:23.507

9

There are quite a lot of "lower" details.

First, consider that the kernel has a list of processes, and at any given time, some of these processes are running, and some are not. The kernel allows each running process some slice of CPU time, then interrupts it and moves to the next. If there are no runnable processes, then the kernel will probably issue an instruction like HLT to the CPU which suspends the CPU until there is a hardware interrupt.

Somewhere in the server is a system call that says "give me something to do". There are two broad categories of ways this can be done. In the case of Apache, it calls accept on a socket Apache has previously opened, probably listening on port 80. The kernel maintains a queue of connection attempts, and adds to that queue every time a TCP SYN is received. How the kernel knows a TCP SYN was received depends on the device driver; for many NICs there's probably a hardware interrupt when network data are received.

accept asks the kernel to return to me the next connection initiation. If queue wasn't empty, then accept just returns immediately. If the queue is empty, then the process (Apache) is removed from the list of running processes. When a connection is later initiated, the process is resumed. This is called "blocking", because to the process calling it, accept() looks like a function that doesn't return until it has a result, which might be some time from now. During that time the process can do nothing else.

Once accept returns, Apache knows that someone is attempting to initiate a connection. It then calls fork to split the Apache process in two identical processes. One of these processes goes on to process the HTTP request, the other calls accept again to get the next connection. Thus, there's always a master process which does nothing but call accept and spawn sub-processes, and then there's one sub-process for each request.

This is a simplification: it's possible to do this with threads instead of processes, and it's also possible to fork beforehand so there's a worker process ready to go when a request is received, thus reducing startup overhead. Depending on how Apache is configured it may do either of these things.

That's the first broad category of how to do it, and it's called blocking IO because the system calls like accept and read and write which operate on sockets will suspend the process until they have something to return.

The other broad way to do it is called non-blocking or event based or asynchronous IO. This is implemented with system calls like select or epoll. These each do the same thing: you give them a list of sockets (or in general, file descriptors) and what you want to do with them, and the kernel blocks until its ready to do one of those things.

With this model, you might tell the kernel (with epoll), "Tell me when there is a new connection on port 80 or new data to read on any of these 9471 other connections I have open". epoll blocks until one of those things is ready, then you do it. Then you repeat. System calls like accept and read and write never block, in part because whenever you call them, epoll just told you that they are ready so there'd be no reason to block, and also because when you open the socket or the file you specify that you want them in non-blocking mode, so those calls will fail with EWOULDBLOCK instead of blocking.

The advantage of this model is that you need only one process. This means you don't have to allocate a stack and kernel structures for each request. Nginx and HAProxy use this model, and it's a big reason they can deal with so many more connections than Apache on similar hardware.

Phil Frost

Posted 2014-11-09T20:39:17.213

Reputation: 460