A Request Becomes Memory

Visheshrwl1 pts0 comments

How a Request Becomes Memory - The Backend Dispatch

The Backend Dispatch

SubscribeSign in

How a Request Becomes Memory<br>The journey from TCP segment to heap allocation — and where, exactly, a packet becomes memory you can touch.

The Backend Dispatch<br>Jun 07, 2026

Share

The short version, before we go deep. If you only read the next five sentences, read these. The packet has already landed in your machine’s memory before a single line of your program has run, because the network card writes it there itself, without asking the CPU for permission. The call you think of as “reading the request,” the read system call, is not where the data arrives; it is where the data is copied across a wall, and you pay for that copy in proportion to how many bytes cross it. C forces you to make every system call by hand, Go hides the part where you would otherwise block, and Rust hides nothing about the system calls themselves but stations its compiler at the door of the buffer so that nobody can misuse it. The tidy request object that shows up as the argument to your handler is, somewhat counterintuitively, the very last thing to come into existence along this whole path, and it is also the most expensive. Once you can see the path as a sequence of stages, the things that usually feel like dark magic, garbage collector pauses, mysterious tail latency, the marketing word “zero-copy,” all snap into focus, because each of them lives at one particular stage and nowhere else.<br>Most of us begin our mental model of a server somewhere around here:<br>Thanks for reading! Subscribe for free to receive new posts and support my work.

Subscribe

func handler(req Request) Response {<br>// ... your code ...

The request is simply there. It arrives fully formed, like a parcel already sitting on the doorstep when you open the door, and so you read its fields, you do your work, and you hand back a response without ever wondering who carried the parcel up the path. This issue is about that carrying. Before the request was a struct with named fields you could reach into, it was a flat run of bytes living in your process’s address space; before it was that, it was a queue of bytes sitting inside the kernel where your code could not see it; before that, it was an ordered procession of TCP segments; and before even that, it was nothing more dignified than voltage changing on a wire. Somewhere along that chain a packet became memory you are permitted to touch, and the exact location of that transformation, together with the question of who pays for the copy that makes it happen, is the difference between a server that comfortably handles five thousand requests a second and one that handles five hundred thousand.<br>This is the anchor issue of the series, which means everything that comes later, the parsers and the allocators and the schedulers and the garbage-collector pauses you will eventually spend an entire weekend hunting down, refers back to the single path drawn in this figure.

1. The packet arrives before your code exists

A TCP segment shows up at your network interface card. The first fact to absorb, and it is genuinely the one that reorganizes everything else, is that the CPU does not go and fetch it. We tend to imagine the processor as the thing that does all the work, reaching out and pulling data in, and yet that is not what happens at all.<br>A note on what the hardware actually does. When the frame arrives, the network card writes it directly into a region of main memory that the driver set aside in advance, a circular structure usually called the receive ring, and it does this using Direct Memory Access, which is the mechanism by which a peripheral device moves bytes into RAM over the system bus without routing them through the processor. By the time anything else happens, the bytes are already resident in memory. The CPU was not involved in placing them there, and it only discovers that they exist after the fact, when the card raises a hardware interrupt to get its attention.

That interrupt is handled in two deliberately unequal halves, and understanding why the work is split this way explains a great deal about how Linux behaves under load. The first half, the part that runs the instant the interrupt fires, does almost nothing on purpose. It runs in a context where other interrupts may be disabled and where sleeping or blocking is forbidden, so if it tried to do anything substantial it would starve every other device on the machine and bring the system to its knees. Consequently it simply acknowledges the interrupt, notes that there is work to be done, and schedules that work to run slightly later in a softer context. That later context is the softirq, specifically the network-receive softirq, and it is where the real processing happens. The softirq drains the receive ring, wraps each packet in the kernel’s universal packet-carrying structure, the socket buffer that the kernel source calls sk_buff, and carries it upward through the...

memory request before packet work system

Related Articles