{"version":"https://jsonfeed.org/version/1.1","title":"viniciusvidal.com","home_page_url":"https://viniciusvidal.com","feed_url":"https://viniciusvidal.com/feed.json","language":"en","authors":[{"name":"Vinícius Vidal","url":"https://viniciusvidal.com"}],"items":[{"id":"everything-is-a-file","url":"https://viniciusvidal.com/everything-is-a-file","title":"Everything Is a File, So What Is a File?","summary":"What \"everything is a file\" really means: files, directories, file descriptors, and the system calls that connect them.","content_html":"<p>Can you imagine a computer that takes no input and produces no output?\nPretty useless, right? So I/O is one of the most important and fundamental\npieces of a computer.</p>\n<p>Above the interesting machinery that makes your CPU able to communicate with\ndevices, there&#39;s an API for you, the programmer, to perform the desired operations,\nand that&#39;s what we&#39;re going to focus on today. Specifically: what a file actually\nis, how directories fit in, and the handful of system calls you use to work\nwith them, like open, read, write, lseek and fsync.</p>\n<p>I took inspiration for this text mainly from OSTEP (Operating Systems:\nThree Easy Pieces), one of my favorite books on this subject.</p>\n<h2>Files</h2>\n<p>You must have already heard that everything in Linux is a file, that used to\nconfuse me a little, because what is everything? everything seems like a lot\nof things. What everything refers to, though, comes from the UNIX definition\nof what&#39;s a file: a linear array of bytes, which you can read or write.\nThinking of it, what is I/O besides writting and reading bytes from somewhere?</p>\n<p>So everything means: the keyboard you&#39;re typing on, the terminal printing\nthese bytes back at you, a disk partition, a network socket, even the\nkernel&#39;s own state in /proc. All of them are things you open, read bytes\nfrom, write bytes to, and close.</p>\n<p>With this, you get a common interface across any kind of device. You just\nneed a driver for it and the superpower of abstractions does the job: you\ndon&#39;t need a <code>HARD_DRIVE_SPECIFIC_BRAND_open()</code> call.</p>\n<p>So a file is a linear array of bytes. But that&#39;s what a file is, not where\nit is. If you have a couple hundred thousand of these arrays sitting on a\ndisk, and you ask for one of them by typing <code>main.c</code>, how does the kernel\nknow which one you mean?</p>\n<p>The answer is that every file has a second name, one you never type: a\nnumber, called the inode number, unique within its filesystem. That number\nis what the kernel actually uses. You can see it:</p>\n<div class=\"code-block\"><pre><code>$ ls -i main.c\n1310742 main.c</code></pre></div><p>The number identifies a small record on disk, the inode, holding everything\nthe kernel knows about the file: its size, its owner, its permissions, its\ntimestamps, and where its data blocks actually live.</p>\n<p>Notice what&#39;s missing from that list. The inode doesn&#39;t know the file&#39;s\nname. You have a name, the kernel has a number, and nothing so far connects\nthe two. That&#39;s what a directory is for.</p>\n<h2>Directories</h2>\n<p>Believe it or not, a directory is a file too.</p>\n<p>It&#39;s a linear array of bytes, with an inode and the same everything we\njust talked about. What makes it a directory is a type field in its\ninode saying so, which tells the kernel how to interpret those bytes.\nAnd the interpretation is simple, a dir is basically a list:</p>\n<div class=\"code-block\"><pre><code>.          131074\n..         2\nmain.c     1310742\nnotes.md   1310744</code></pre></div><p>A user-readable and the file unique number. This way you can see that your\nfile&#39;s name doesn&#39;t live in the file, it lives in the directory that points\nto it.</p>\n<p>So when you ask for <code>/home/user/main.c</code>, the kernel walks it:</p>\n<div class=\"code-block\"><pre><code>start at the root\n  look up \"home\"   -> 131074\n  look up \"user\"   -> 262401\n  look up \"main.c\" -> 1310742</code></pre></div><p>If you&#39;re curious about where the walk starts, the root can&#39;t be looked up\nby name, since there&#39;s no directory above it to look in. So it just gets a\nfixed number, decided by the filesystem format.</p>\n<h2>Creating files</h2>\n<p>The first operation is creating a file. That&#39;s done with the <code>open</code> syscall,\nfrom <code>#include &lt;fcntl.h&gt;</code>, passing it the <code>O_CREAT</code> flag. The name of the\nheader is short for file control, and it&#39;s also where the <code>O_*</code> flags live.</p>\n<p>Note that <code>open</code> both creates and opens. There&#39;s no separate call for it.\nWell, there was the <code>creat</code> syscall, but since <code>open</code> is already able to\ndo such thing, it became kinda obsolete.</p>\n<p>Here&#39;s an example:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">int</span><span style=\"color:#EBDBB2\"> fd </span><span style=\"color:#8EC07C\">=</span><span style=\"color:#FABD2F\"> open</span><span style=\"color:#A89984\">(\"my_file\", O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR);</span></span></code></pre></div><p>The first argument is the path, resolved the way we just saw. Every\ncomponent except the last has to exist already: <code>open</code> creates a file, not\na path.</p>\n<p>The second is a single int with flags OR&#39;d together, doing three different\njobs. One access mode, exactly one, either <code>O_RDONLY</code>, <code>O_WRONLY</code> or\n<code>O_RDWR</code>. Then flags for open time, like <code>O_CREAT</code> to make the file if it\nisn&#39;t there and <code>O_TRUNC</code> to empty it if it is. So the line above means\n&quot;give me this file, empty and ready to write&quot;. The rest change how later\nreads and writes behave, and can wait.</p>\n<p>The third is the permissions, and it only matters with <code>O_CREAT</code>, since an\nexisting file already has them. <code>S_IRUSR | S_IWUSR</code> is owner read and write,\nalso written <code>0600</code>.</p>\n<h2>File descriptors</h2>\n<p>See what&#39;s returned from the create operation, an fd, short for file\ndescriptor. This deserves attention because that&#39;s how your process\nmaintains a list of opened files.</p>\n<p>The fd is an index into a list of open files. Just an int, and a signed one,\nwhich is what leaves room for <code>-1</code> to mean failure. &quot;I want to operate on\nthe fourth entry in my table of open files.&quot;</p>\n<p>That table is per process. Here&#39;s xv6&#39;s version, which is about as small as\nit gets:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">struct</span><span style=\"color:#EBDBB2\"> proc </span><span style=\"color:#A89984\">{</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    struct</span><span style=\"color:#EBDBB2\"> file </span><span style=\"color:#8EC07C\">*</span><span style=\"color:#83A598\">ofile</span><span style=\"color:#A89984\">[</span><span style=\"color:#EBDBB2\">NOFILE</span><span style=\"color:#A89984\">];</span><span style=\"color:#928374;font-style:italic\">  // open files</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">};</span></span></code></pre></div><p>Being per process is the important part: fd 3 here and fd 3 in the process\nnext to you are unrelated entries in different tables. And since the array\nis fixed size, there&#39;s a cap on how many files you can have open at once,\nwhich you can see with <code>ulimit -n</code>. In my machine it is 2560.</p>\n<p>The first three entries are already taken before your code runs:</p>\n<div class=\"code-block\"><pre><code>0    stdin\n1    stdout\n2    stderr</code></pre></div><p>That&#39;s why your first <code>open</code> usually comes back as 3, since the kernel hands\nyou the lowest free index. And there&#39;s nothing special about these three,\nthey&#39;re ordinary entries in the same table:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FABD2F\">write</span><span style=\"color:#A89984\">(</span><span style=\"color:#D3869B\">1</span><span style=\"color:#A89984\">,</span><span style=\"color:#A89984\"> \"</span><span style=\"color:#B8BB26\">hello</span><span style=\"color:#D3869B\">\\n</span><span style=\"color:#A89984\">\"</span><span style=\"color:#A89984\">,</span><span style=\"color:#D3869B\"> 6</span><span style=\"color:#A89984\">);</span></span></code></pre></div><p>That&#39;s roughly what <code>printf</code> ends up doing.</p>\n<h2>Reading and writing files</h2>\n<p>Now that you&#39;ve acquired an fd, what can you do with it?</p>\n<p>The most fundamental operations, of course, are reading and writing. And\nto see both in action, there&#39;s no better example than <code>cat</code>, a program\nyou&#39;ve almost certainly used, and one that fits in a few lines:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#A89984\">#</span><span style=\"color:#8EC07C\">include</span><span style=\"color:#A89984\"> &#x3C;</span><span style=\"color:#B8BB26\">fcntl.h</span><span style=\"color:#A89984\">></span></span>\n<span class=\"line\"><span style=\"color:#A89984\">#</span><span style=\"color:#8EC07C\">include</span><span style=\"color:#A89984\"> &#x3C;</span><span style=\"color:#B8BB26\">stdio.h</span><span style=\"color:#A89984\">></span></span>\n<span class=\"line\"><span style=\"color:#A89984\">#</span><span style=\"color:#8EC07C\">include</span><span style=\"color:#A89984\"> &#x3C;</span><span style=\"color:#B8BB26\">unistd.h</span><span style=\"color:#A89984\">></span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#FE8019\">int</span><span style=\"color:#FABD2F\"> main</span><span style=\"color:#A89984\">(</span><span style=\"color:#FE8019\">int</span><span style=\"color:#83A598\"> argc</span><span style=\"color:#A89984\">,</span><span style=\"color:#FE8019\"> char</span><span style=\"color:#8EC07C\"> *</span><span style=\"color:#83A598\">argv</span><span style=\"color:#FE8019\">[]</span><span style=\"color:#A89984\">)</span><span style=\"color:#A89984\"> {</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    int</span><span style=\"color:#EBDBB2\"> fd </span><span style=\"color:#8EC07C\">=</span><span style=\"color:#FABD2F\"> open</span><span style=\"color:#A89984\">(</span><span style=\"color:#83A598\">argv</span><span style=\"color:#A89984\">[</span><span style=\"color:#D3869B\">1</span><span style=\"color:#A89984\">],</span><span style=\"color:#EBDBB2\"> O_RDONLY</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#FB4934\">    if</span><span style=\"color:#A89984\"> (</span><span style=\"color:#EBDBB2\">fd </span><span style=\"color:#8EC07C\">&#x3C;</span><span style=\"color:#D3869B\"> 0</span><span style=\"color:#A89984\">)</span><span style=\"color:#A89984\"> {</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">        perror</span><span style=\"color:#A89984\">(</span><span style=\"color:#A89984\">\"</span><span style=\"color:#B8BB26\">open</span><span style=\"color:#A89984\">\"</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#FB4934\">        return</span><span style=\"color:#D3869B\"> 1</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">    }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    char</span><span style=\"color:#83A598\"> buf</span><span style=\"color:#A89984\">[</span><span style=\"color:#D3869B\">4096</span><span style=\"color:#A89984\">];</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    ssize_t</span><span style=\"color:#EBDBB2\"> n</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FB4934\">    while</span><span style=\"color:#A89984\"> ((</span><span style=\"color:#EBDBB2\">n </span><span style=\"color:#8EC07C\">=</span><span style=\"color:#FABD2F\"> read</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">fd</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> buf</span><span style=\"color:#A89984\">,</span><span style=\"color:#8EC07C\"> sizeof</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">buf</span><span style=\"color:#A89984\">)))</span><span style=\"color:#8EC07C\"> ></span><span style=\"color:#D3869B\"> 0</span><span style=\"color:#A89984\">)</span><span style=\"color:#A89984\"> {</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">        write</span><span style=\"color:#A89984\">(</span><span style=\"color:#D3869B\">1</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> buf</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> n</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">    }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">    close</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">fd</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#FB4934\">    return</span><span style=\"color:#D3869B\"> 0</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">}</span></span></code></pre></div><p>The <code>open</code> part you already know. We ask for the file read only, and if\nwe get -1 back, we print what went wrong and quit.</p>\n<p>The interesting part is the loop:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FB4934\">while</span><span style=\"color:#A89984\"> ((</span><span style=\"color:#EBDBB2\">n </span><span style=\"color:#8EC07C\">=</span><span style=\"color:#FABD2F\"> read</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">fd</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> buf</span><span style=\"color:#A89984\">,</span><span style=\"color:#8EC07C\"> sizeof</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">buf</span><span style=\"color:#A89984\">)))</span><span style=\"color:#8EC07C\"> ></span><span style=\"color:#D3869B\"> 0</span><span style=\"color:#A89984\">)</span></span></code></pre></div><p>The file&#39;s bytes don&#39;t live in our process. They&#39;re on the disk, and once\nread, cached in the kernel&#39;s own memory, in what&#39;s called the page cache.\n<code>read</code> is the way to bring them over, the kernel copies them from the page\ncache into memory we own, with <code>copy_to_user</code>, essentially a <code>memcpy</code> that\nchecks the destination really belongs to our process. That&#39;s why we pass\n<code>buf</code>, it&#39;s the destination for that copy.</p>\n<p>Which means something interesting. If the block isn&#39;t in the page cache\nyet, the kernel first reads it from disk into the cache, then copies it to\nus. Either way, after a <code>read</code> those bytes exist twice in memory: once in\nthe kernel, once in our buffer. <code>mmap</code> avoids this by mapping the page\ncache straight into your address space, but that&#39;s an interface for\nanother day. I suggest this video by Tsoding if you want to know more:\n<a href=\"https://www.youtube.com/watch?v=sFYFuBzu9Ow\">https://www.youtube.com/watch?v=sFYFuBzu9Ow</a></p>\n<p>The kernel has no idea how big <code>buf</code> is. In C, an array passed to a\nfunction decays into a pointer, so all <code>read</code> gets is an address.\n<code>sizeof(buf)</code> is how we tell it the ceiling, so it never writes past the\nend of our array.</p>\n<p>What comes back is the number of bytes it actually copied. We set the\nceiling at 4096, but a file with &quot;hello&quot; in it gives us 6: five letters\nand a newline (\\n). When there&#39;s nothing left, it gives us 0, which is\nhow <code>cat</code> knows it&#39;s done. It never checks the file size, it just keeps\nreading until the kernel says there&#39;s nothing more.</p>\n<p>Then we hand those bytes to fd 1:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FABD2F\">write</span><span style=\"color:#A89984\">(</span><span style=\"color:#D3869B\">1</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> buf</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> n</span><span style=\"color:#A89984\">);</span></span></code></pre></div><p>This is the same copy in the other direction, from our buffer back into\nthe kernel. We pass <code>n</code> because only the first <code>n</code> bytes of <code>buf</code> hold\nwhat we just read. Fd 1, as stated before, is stdout, the entry that\nis already opened in our process by default. As far as <code>cat</code> is\nconcerned, it&#39;s writing bytes to an fd, and whether that ends up on a\nterminal is none of its business.</p>\n<p>Notice that our buffer only holds 4096 bytes. What happens with a file\nbigger than that? Nothing special: the loop reads the first 4096 bytes,\nwrites them out, reads the next 4096, writes those, and so on. We never\nhold the whole file at once, only one chunk at a time, passing each one\nalong before fetching the next.</p>\n<p>The buffer size is a tradeoff. A bigger buffer costs more memory but\nmeans fewer calls to <code>read</code> and <code>write</code>. Remember that every syscall has\na price: crossing into the kernel and back. A smaller one saves memory and pays\nthat price more often. Reading a 1MB file takes 256 round trips with\n4096 bytes, and 16 with 64KB.</p>\n<p>Finally, <code>close</code> gives the table entry back.</p>\n<p>There&#39;s one thing left you may have noticed. If not, you can pretend you\ndid. We call <code>read</code> over and over with the exact same arguments, and each\ntime we get the next chunk of the file. So something is remembering where\nthe last read stopped. How does a function called with identical\narguments hold on to that state?</p>\n<p>That&#39;s our next subject.</p>\n<h2>Behind the fd</h2>\n<p>Remember the table from the file descriptors section:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">struct</span><span style=\"color:#EBDBB2\"> proc </span><span style=\"color:#A89984\">{</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    struct</span><span style=\"color:#EBDBB2\"> file </span><span style=\"color:#8EC07C\">*</span><span style=\"color:#83A598\">ofile</span><span style=\"color:#A89984\">[</span><span style=\"color:#EBDBB2\">NOFILE</span><span style=\"color:#A89984\">];</span><span style=\"color:#928374;font-style:italic\">  // open files</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">};</span></span></code></pre></div><p>The answer is behind those pointers. Each entry points to a <code>struct file</code>, a small object the kernel creates every time you call <code>open</code>.\nHere&#39;s xv6&#39;s, trimmed:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">struct</span><span style=\"color:#EBDBB2\"> file </span><span style=\"color:#A89984\">{</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    int</span><span style=\"color:#EBDBB2\"> ref</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    char</span><span style=\"color:#EBDBB2\"> readable</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    char</span><span style=\"color:#EBDBB2\"> writable</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    struct</span><span style=\"color:#EBDBB2\"> inode </span><span style=\"color:#8EC07C\">*</span><span style=\"color:#EBDBB2\">ip</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    uint</span><span style=\"color:#EBDBB2\"> off</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">};</span></span></code></pre></div><p><code>ip</code> points to the inode, the file itself, and <code>off</code> how far into the\nfile we are, that answers our previous question. Each <code>read</code> starts copying at\n<code>off</code> and moves it forward by the number of bytes it copied. So when\n<code>cat</code> calls <code>read</code> again with the same arguments, it picks up right\nafter the last chunk.</p>\n<p>xv6 keeps these in a fixed global array, since it has no allocator for\nsmall objects, while Linux allocates them dynamically. Either way, your\ntable only holds a pointer:</p>\n<p><img src=\"/assets/struct-file.png\" alt=\"Three fd table entries across two processes pointing into the kernel&#39;s struct file objects: process A&#39;s fd 3 and fd 4 point to two separate struct files with independent offsets (100 and 0), process B&#39;s fd 3 points to a third struct file at offset 4096, and all three struct files point to the same inode 1310742 on disk\"></p>\n<p>Process A opened the file twice and got two of them, each with its own\noffset. Process B opened it once and got a third. All three lead to the\nsame inode.</p>\n<p>Since the table only holds pointers, two entries can point to the same\n<code>struct file</code>, <code>dup</code> does it within a process, <code>fork</code> across parent and\nchild. <code>ref</code> counts those entries, and <code>close</code> only frees the <code>struct file</code> when it reaches zero.</p>\n<p>You can see the independent offsets in a few lines:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">int</span><span style=\"color:#EBDBB2\"> a </span><span style=\"color:#8EC07C\">=</span><span style=\"color:#FABD2F\"> open</span><span style=\"color:#A89984\">(</span><span style=\"color:#A89984\">\"</span><span style=\"color:#B8BB26\">main.c</span><span style=\"color:#A89984\">\"</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> O_RDONLY</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">int</span><span style=\"color:#EBDBB2\"> b </span><span style=\"color:#8EC07C\">=</span><span style=\"color:#FABD2F\"> open</span><span style=\"color:#A89984\">(</span><span style=\"color:#A89984\">\"</span><span style=\"color:#B8BB26\">main.c</span><span style=\"color:#A89984\">\"</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> O_RDONLY</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">read</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">a</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> buf</span><span style=\"color:#A89984\">,</span><span style=\"color:#D3869B\"> 100</span><span style=\"color:#A89984\">);</span><span style=\"color:#928374;font-style:italic\">    // reads bytes 0 to 99, a's offset is now 100</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">read</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">b</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> buf</span><span style=\"color:#A89984\">,</span><span style=\"color:#D3869B\"> 100</span><span style=\"color:#A89984\">);</span><span style=\"color:#928374;font-style:italic\">    // reads bytes 0 to 99 again, b starts at 0</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">read</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">a</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> buf</span><span style=\"color:#A89984\">,</span><span style=\"color:#D3869B\"> 100</span><span style=\"color:#A89984\">);</span><span style=\"color:#928374;font-style:italic\">    // reads bytes 100 to 199</span></span></code></pre></div><p>Reading through <code>a</code> doesn&#39;t touch <code>b</code>&#39;s offset at all.</p>\n<p>Reading front to back is fine for <code>cat</code>. But say you need the bytes at\noffset 5000, and only those. For that there&#39;s <code>lseek</code>:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FABD2F\">lseek</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">fd</span><span style=\"color:#A89984\">,</span><span style=\"color:#D3869B\"> 5000</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> SEEK_SET</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">read</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">fd</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> buf</span><span style=\"color:#A89984\">,</span><span style=\"color:#D3869B\"> 100</span><span style=\"color:#A89984\">);</span></span></code></pre></div><p><code>lseek</code> does no I/O at all. It just sets the offset, and the next <code>read</code>\nstarts from there. The third argument says what the offset is relative\nto, as the man page puts it:</p>\n<div class=\"code-block\"><pre><code>If whence is SEEK_SET, the offset is set to offset bytes.\nIf whence is SEEK_CUR, the offset is set to its current location\nplus offset bytes.\nIf whence is SEEK_END, the offset is set to the size of the file\nplus offset bytes.</code></pre></div><p>This gets tricky with threads. Say you open a file once and hand the\nsame fd to several threads. Since threads share the fd table, they all\nend up using the same <code>struct file</code>, and so the same <code>off</code>.</p>\n<p>You could open a new fd per thread instead, and each would get its own\noffset. But every open is a new path lookup, which may land on a\ndifferent file if it was replaced in between. Sharing one fd means\neveryone reads the same file, at the price of a shared offset.</p>\n<p>And that price shows up when two threads each do a seek followed by a\nread. They can interleave like this:</p>\n<div class=\"code-block\"><pre><code>thread A: lseek(fd, 5000, SEEK_SET)\nthread B: lseek(fd, 9000, SEEK_SET)\nthread A: read(fd, buf, 100)    // reads from 9000\nthread B: read(fd, buf, 100)    // reads from 9100</code></pre></div><p>Thread A ends up with bytes from offset 9000, and no error tells it so.</p>\n<p>I hit exactly this while building an append only storage engine, with\nseveral threads reading records at random offsets. The fix was <code>pread</code>:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FABD2F\">pread</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">fd</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> buf</span><span style=\"color:#A89984\">,</span><span style=\"color:#D3869B\"> 100</span><span style=\"color:#A89984\">,</span><span style=\"color:#D3869B\"> 5000</span><span style=\"color:#A89984\">);</span></span></code></pre></div><p>It takes the offset as an argument and reads from there. Since it never\ntouches the shared offset, threads can call it on the same fd without\nstepping on each other.</p>\n<h2>Reaching the disk</h2>\n<p>Remember the page cache from the reading section? <code>write</code> copies your\nbytes into it, the same cache <code>read</code> copies from, and marks the page\ndirty, which means it has changes the disk doesn&#39;t have yet. Since every process\nshares that cache, anyone reading the file sees your changes right\naway, before they reach the disk.</p>\n<p>The kernel does this on purpose, and it&#39;s why these writes are called\nbuffered. Disks are slow, and many small writes are much cheaper as a\nfew big ones. So it holds dirty pages for a while, letting writes pile\nup, and if you write to the same page twice, only the final version\never goes to disk. Meanwhile <code>write</code> returns as soon as the copy is\ndone.</p>\n<p>Those dirty pages get flushed eventually, for one of two reasons. Either\nthey&#39;ve been dirty for too long, around 30 seconds by default on Linux,\nor too much of memory is dirty and the kernel starts writing back to\nmake room.</p>\n<p>Most of the time that&#39;s exactly what you want. But if the machine loses\npower before the flush, those bytes are gone, even though <code>write</code> told\nyou everything went fine.</p>\n<p>For a database, that&#39;s unacceptable. When I was building my append only\nstorage engine, a record being acknowledged had to mean it was actually\non disk, and a successful <code>write</code> doesn&#39;t promise that. That&#39;s what\n<code>fsync</code> is for:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FABD2F\">write</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">fd</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> buf</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> n</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#FB4934\">if</span><span style=\"color:#A89984\"> (</span><span style=\"color:#FABD2F\">fsync</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">fd</span><span style=\"color:#A89984\">)</span><span style=\"color:#8EC07C\"> &#x3C;</span><span style=\"color:#D3869B\"> 0</span><span style=\"color:#A89984\">)</span><span style=\"color:#A89984\"> {</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">    perror</span><span style=\"color:#A89984\">(</span><span style=\"color:#A89984\">\"</span><span style=\"color:#B8BB26\">fsync</span><span style=\"color:#A89984\">\"</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">}</span></span></code></pre></div><p>It blocks until all of the file&#39;s dirty pages are on disk, along with\nthe inode&#39;s metadata, like its size.</p>\n<p>There&#39;s a catch if the file is new. Its name isn&#39;t stored in the file,\nit&#39;s an entry in the directory, and the directory is a file with its own\ndirty pages. <code>fsync</code> on your file leaves those alone. So after a crash,\nthe data could be on disk with no name pointing to it. To avoid that,\nopen the directory and <code>fsync</code> it too.</p>\n<h2>Wrapping up</h2>\n<p>There&#39;s a lot I left out: hard and symbolic links, permissions, <code>stat</code>,\n<code>mmap</code>, and plenty more. Most of what I covered here comes from OSTEP&#39;s\nchapter on files and directories, and if you want to keep going, I\nreally recommend it. The rest of the book is just as good.</p>\n","date_published":"2026-09-20T00:00:00.000Z"},{"id":"system-calls-in-depth","url":"https://viniciusvidal.com/system-calls-in-depth","title":"System calls in depth","summary":"How a one-line printf actually crosses into the kernel. Privilege levels, traps, MSR_LSTAR, the kernel stack, and entry_SYSCALL_64.","content_html":"<p>If you are not reading this on a carved stone, every character you see on this screen arrived through a system call. Every keystroke you typed triggered one. They run invisibly, underneath everything.</p>\n<p>When learning some language, one of the first things you have contact with is how to print stuff on the terminal, perhaps a &quot;Hello World&quot;. Let&#39;s take C as an example, to print this classic opener we can make use of a procedure called <code>printf()</code>, but underneath that one line, the CPU is switching privilege levels, your register state is being frozen, and the kernel is running on an entirely separate stack, all before the <code>printf</code> returns. By the end, I hope the kernel will feel a little less like a black box.</p>\n<h2>From binary to process</h2>\n<p>One of the most important abstractions the OS gives you is a process. When you create a binary, it is just a file stuffed with instructions (and maybe some static data) sitting on the disk. It is the operating system that gives you a running program. That running program is what we call a process.</p>\n<p>When you run a program, a lot happens before your first line of code executes. The OS creates an entry in the process list, allocates memory for the program, loads program from disk to memory, set up the stack with <code>argc</code> and <code>argv</code>, clears the registers, and finally hands control to your entry point, <code>main()</code>. From there, the CPU fetches your instructions directly from memory and executes them one by one.</p>\n<p>Pretty simple, right? But if your code runs directly on the CPU with no middleman, what stops it from accessing the disk, reading another process&#39;s memory, or turning on your camera? You need to ensure all this security while still maintaining efficiency. Sounds like a hell of a problem.</p>\n<h2>User mode and kernel mode</h2>\n<p>Thankfully, the CPU itself has a built-in solution. It does not treat all code equally. It runs in two distinct modes, each with a different level of privilege. This mode is not an OS policy, it is hardwired into the processor itself.</p>\n<p>The CPU tracks the current mode with a two-bit value called the privilege level. Kernel mode means full access to hardware, memory, and every instruction the CPU can execute. User mode means restricted. If you tried to issue an I/O request while running in user mode, the processor would raise an exception and the OS would likely kill your process.</p>\n<p>Your operating system runs in kernel mode. Every program you write runs in user mode.</p>\n<p>But every real program needs to do some sort of I/O at some point: read a file, write to the terminal, open a socket. If user mode code cannot do any of that directly, how does it get anything done?</p>\n<h2>Traps and system calls</h2>\n<p>Again, the CPU comes to our rescue. It gives us a controlled way to cross that boundary through a mechanism called a trap. A trap is how the CPU transfers control from user mode to the kernel: it stops what it was doing, switches to kernel mode, and jumps to a kernel handler. This happens both when something goes wrong, like a divide by zero or an illegal memory access, and when your program deliberately asks the kernel for help.</p>\n<p>But for any of this to work, the kernel has to tell the CPU where to jump when a trap fires. It does this during boot, in a function called <code>trap_init()</code> (the following code is directly from the Linux codebase, at <code>linux/arch/x86/kernel/traps.c</code>):</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">void</span><span style=\"color:#EBDBB2\"> __init </span><span style=\"color:#FABD2F\">trap_init</span><span style=\"color:#A89984\">(</span><span style=\"color:#FE8019\">void</span><span style=\"color:#A89984\">)</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">{</span></span>\n<span class=\"line\"><span style=\"color:#928374;font-style:italic\">\t/* Init cpu_entry_area before IST entries are set up */</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">\tsetup_cpu_entry_areas</span><span style=\"color:#A89984\">();</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#928374;font-style:italic\">\t/* Init GHCB memory pages when running as an SEV-ES guest */</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">\tsev_es_init_vc_handling</span><span style=\"color:#A89984\">();</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#928374;font-style:italic\">\t/* Initialize TSS before setting up traps so ISTs work */</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">\tcpu_init_exception_handling</span><span style=\"color:#A89984\">(</span><span style=\"color:#D3869B\">true</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#928374;font-style:italic\">\t/* Setup traps as cpu_init() might #GP */</span></span>\n<span class=\"line\"><span style=\"color:#FB4934\">\tif</span><span style=\"color:#A89984\"> (</span><span style=\"color:#FB4934\">!</span><span style=\"color:#FABD2F\">cpu_feature_enabled</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">X86_FEATURE_FRED</span><span style=\"color:#A89984\">))</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">\t\tidt_setup_traps</span><span style=\"color:#A89984\">();</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">\tcpu_init</span><span style=\"color:#A89984\">();</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">}</span></span></code></pre></div><p>Before explaining what matters in this code, let&#39;s state something first: beyond the general purpose registers like <code>rax</code> and <code>rdi</code>, the CPU has a set of special registers that hold its own internal configuration state. These are definitely kernel mode only, your program can never read or write them directly. Some of them are relevant to how traps work, and the kernel writes to them during boot so the CPU knows what to do the moment a trap fires.</p>\n<p>&quot;How does the CPU know where to jump when an exception like a divide by zero happens?&quot; Good question indeed. The answer is a table the kernel builds during boot called the IDT, Interrupt Descriptor Table. Each entry maps an exception type to a handler address. Divide by zero gets one handler, page fault gets another, illegal instruction gets another. The kernel fills this table with <code>idt_setup_traps()</code> and writes its address into a special CPU register. On newer Intel CPUs that support a feature called FRED, this whole mechanism is replaced with a cleaner design, but that is not our subject today.</p>\n<p>Those are the unwanted cases, the errors your program did not ask for. Returning to our question, how can a user mode program do any sort of I/O? Remember that a trap switches the CPU to kernel mode. The CPU gives us a way to trigger one deliberately. Instead of waiting for something to go wrong, your program can intentionally fire a trap, cross the boundary into kernel mode, and ask the kernel to perform a privileged operation on its behalf: write to a file, read from a socket, open a device. That deliberate request is what we call a <strong>system call</strong>.</p>\n<p>And just like exceptions, the CPU needs to know where to jump when a system call fires. Back to <code>trap_init()</code>, the line that handles this is <code>cpu_init()</code>. It looks like generic initialization but it eventually calls <code>syscall_init()</code> inside <code>arch/x86/kernel/cpu/common.c</code>, which writes the system call entry point address into a special CPU register called <code>MSR_LSTAR</code>. When your program fires a system call, the CPU reads that register and jumps there instantly. No table lookup, just one register read and a jump.</p>\n<p>Here is the chain, directly from the kernel source:</p>\n<p><code>cpu_init()</code> calls <code>syscall_init()</code>:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#928374;font-style:italic\">// arch/x86/kernel/cpu/common.c</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">void</span><span style=\"color:#FABD2F\"> cpu_init</span><span style=\"color:#A89984\">(</span><span style=\"color:#FE8019\">void</span><span style=\"color:#A89984\">)</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">{</span></span>\n<span class=\"line\"><span style=\"color:#928374;font-style:italic\">    // ...</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">    syscall_init</span><span style=\"color:#A89984\">();</span></span>\n<span class=\"line\"><span style=\"color:#928374;font-style:italic\">    // ...</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">}</span></span></code></pre></div><p><code>syscall_init()</code> calls <code>idt_syscall_init()</code>:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">void</span><span style=\"color:#FABD2F\"> syscall_init</span><span style=\"color:#A89984\">(</span><span style=\"color:#FE8019\">void</span><span style=\"color:#A89984\">)</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">{</span></span>\n<span class=\"line\"><span style=\"color:#928374;font-style:italic\">\t// ...</span></span>\n<span class=\"line\"><span style=\"color:#FB4934\">    if</span><span style=\"color:#A89984\"> (</span><span style=\"color:#FB4934\">!</span><span style=\"color:#FABD2F\">cpu_feature_enabled</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">X86_FEATURE_FRED</span><span style=\"color:#A89984\">))</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">        idt_syscall_init</span><span style=\"color:#A89984\">();</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">}</span></span></code></pre></div><p>And finally, <code>idt_syscall_init()</code> is where it happens. The kernel writes the address of its system call entry point into <code>MSR_LSTAR</code>:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">static</span><span style=\"color:#FE8019\"> inline</span><span style=\"color:#FE8019\"> void</span><span style=\"color:#FABD2F\"> idt_syscall_init</span><span style=\"color:#A89984\">(</span><span style=\"color:#FE8019\">void</span><span style=\"color:#A89984\">)</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">{</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">    wrmsrq</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">MSR_LSTAR</span><span style=\"color:#A89984\">,</span><span style=\"color:#A89984\"> (</span><span style=\"color:#FE8019\">unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#A89984\">)</span><span style=\"color:#EBDBB2\">entry_SYSCALL_64</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#928374;font-style:italic\">    // ...</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">}</span></span></code></pre></div><p>From this point on, every <code>syscall</code> instruction executed anywhere on the system will cause the CPU to read <code>MSR_LSTAR</code> and jump to <code>entry_SYSCALL_64</code>. That is where your program lands when it triggers a system call.</p>\n<h2>More on system calls</h2>\n<p>Given this mechanism the CPU hands us, how does a program actually trigger a system call? Remember <code>printf()</code> from the beginning? One might think &quot;but it just writes some text to the terminal, why does that need the kernel?&quot;</p>\n<p>Because in Linux, writing to the terminal is just like writing to a file. If you have read my <a href=\"https://viniciusvidal.com/networking-concepts-from-the-ground-up\">networking post</a>, you already know why: every form of I/O in Unix is represented as a file descriptor, a handle to an open resource that lives inside the kernel. Writing to a file requires privileged access.</p>\n<p>Your program cannot just reach into the filesystem and put bytes somewhere. That is what <code>write()</code> is for: a system call that says &quot;take these bytes and write them to this file descriptor.&quot;</p>\n<p>Don&#39;t take my word for it. Let&#39;s use a tool called <code>strace</code>, which intercepts and prints every system call a program makes as it runs. Take this simple C program:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#A89984\">#</span><span style=\"color:#8EC07C\">include</span><span style=\"color:#A89984\"> &#x3C;</span><span style=\"color:#B8BB26\">stdio.h</span><span style=\"color:#A89984\">></span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">int</span><span style=\"color:#FABD2F\"> main</span><span style=\"color:#A89984\">()</span><span style=\"color:#A89984\"> {</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">    printf</span><span style=\"color:#A89984\">(</span><span style=\"color:#A89984\">\"</span><span style=\"color:#B8BB26\">hello world</span><span style=\"color:#D3869B\">\\n</span><span style=\"color:#A89984\">\"</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#FB4934\">    return</span><span style=\"color:#D3869B\"> 0</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">}</span></span></code></pre></div><p>Running it under <code>strace</code> gives us:</p>\n<div class=\"code-block\"><span class=\"code-lang\">bash</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FABD2F\">$</span><span style=\"color:#B8BB26\"> strace</span><span style=\"color:#B8BB26\"> ./main</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">execve(</span><span style=\"color:#FABD2F\">\"./main\"</span><span style=\"color:#FABD2F\">,</span><span style=\"color:#EBDBB2\"> [</span><span style=\"color:#A89984\">\"</span><span style=\"color:#B8BB26\">./main</span><span style=\"color:#A89984\">\"</span><span style=\"color:#EBDBB2\">], ...)      = 0</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">mmap(...</span><span style=\"color:#EBDBB2\">)                              = ...</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">openat(...,</span><span style=\"color:#A89984\"> \"</span><span style=\"color:#B8BB26\">/usr/lib/aarch64-linux-gnu/libc.so.6</span><span style=\"color:#A89984\">\"</span><span style=\"color:#B8BB26\">,</span><span style=\"color:#B8BB26\"> ...</span><span style=\"color:#EBDBB2\">) = 3</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">...</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">write(1,</span><span style=\"color:#A89984\"> \"</span><span style=\"color:#B8BB26\">hello world\\n</span><span style=\"color:#A89984\">\"</span><span style=\"color:#B8BB26\">,</span><span style=\"color:#D3869B\"> 12</span><span style=\"color:#EBDBB2\">)          = 12</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">exit_group(0</span><span style=\"color:#EBDBB2\">)                          = </span><span style=\"color:#8EC07C\">?</span></span></code></pre></div><p>Two things stand out here. First, all those calls before <code>write</code> are not your code at all. That is the dynamic linker loading the C standard library before your <code>main</code> even runs. Second, see that there is no <code>printf</code> in the output? Just <code>write</code>. That is because <code>printf</code> lives in libc and eventually calls <code>write()</code>, which is also a libc wrapper, a thin assembly function dressed up as a nice C API.</p>\n<p>To trigger a system call, the CPU provides a dedicated instruction called <code>syscall</code>. When executed, it switches the CPU to kernel mode and jumps to <code>entry_SYSCALL_64</code>. But before firing it, your program has to tell the kernel what it wants and pass the arguments. This is done through registers, following a contract called the x86-64 syscall ABI. <code>rax</code> gets the syscall number, and the arguments follow in <code>rdi</code>, <code>rsi</code>, <code>rdx</code>, <code>r10</code>, <code>r8</code>, and <code>r9</code> in order. The kernel will read exactly those registers when it takes over.</p>\n<p>For <code>write(1, &quot;hello world\\n&quot;, 12)</code> that looks like this:</p>\n<div class=\"code-block\"><span class=\"code-lang\">asm</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#8EC07C\">mov</span><span style=\"color:#D3869B\"> rax</span><span style=\"color:#EBDBB2\">, </span><span style=\"color:#D3869B\">1</span><span style=\"color:#928374;font-style:italic\">       ; syscall number for write</span></span>\n<span class=\"line\"><span style=\"color:#8EC07C\">mov</span><span style=\"color:#D3869B\"> rdi</span><span style=\"color:#EBDBB2\">, </span><span style=\"color:#D3869B\">1</span><span style=\"color:#928374;font-style:italic\">       ; fd = stdout</span></span>\n<span class=\"line\"><span style=\"color:#8EC07C\">mov</span><span style=\"color:#D3869B\"> rsi</span><span style=\"color:#EBDBB2\">, buf     </span><span style=\"color:#928374;font-style:italic\">; pointer to \"hello world\\n\"</span></span>\n<span class=\"line\"><span style=\"color:#8EC07C\">mov</span><span style=\"color:#D3869B\"> rdx</span><span style=\"color:#EBDBB2\">, </span><span style=\"color:#D3869B\">12</span><span style=\"color:#928374;font-style:italic\">      ; length</span></span>\n<span class=\"line\"><span style=\"color:#8EC07C\">syscall</span><span style=\"color:#928374;font-style:italic\">          ; switch to kernel mode, jump to entry_SYSCALL_64</span></span></code></pre></div><p>Each system call has a unique number. <code>write</code> is 1, <code>read</code> is 0, <code>open</code> is 2. That number goes into <code>rax</code> before firing <code>syscall</code>, and the kernel uses it to look up the right handler in a table. You can see the full table directly in the kernel source <a href=\"https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl\">here</a>, or look up any syscall and its arguments by running:</p>\n<div class=\"code-block\"><span class=\"code-lang\">bash</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FABD2F\">$</span><span style=\"color:#B8BB26\"> man</span><span style=\"color:#D3869B\"> 2</span><span style=\"color:#B8BB26\"> write</span></span></code></pre></div><p>The <code>2</code> refers to section 2 of the manual, which is system calls. Every syscall has a page there.</p>\n<p>The kernel maintains a table where each syscall number maps to a handler function. That <code>1</code> you put in <code>rax</code> is literally just an index into this:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">long</span><span style=\"color:#FABD2F\"> x64_sys_call</span><span style=\"color:#A89984\">(</span><span style=\"color:#FE8019\">const</span><span style=\"color:#FE8019\"> struct</span><span style=\"color:#EBDBB2\"> pt_regs </span><span style=\"color:#8EC07C\">*</span><span style=\"color:#83A598\">regs</span><span style=\"color:#A89984\">,</span><span style=\"color:#FE8019\"> unsigned</span><span style=\"color:#FE8019\"> int</span><span style=\"color:#83A598\"> nr</span><span style=\"color:#A89984\">)</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">{</span></span>\n<span class=\"line\"><span style=\"color:#FB4934\">    switch</span><span style=\"color:#A89984\"> (</span><span style=\"color:#EBDBB2\">nr</span><span style=\"color:#A89984\">)</span><span style=\"color:#A89984\"> {</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">    #</span><span style=\"color:#8EC07C\">include</span><span style=\"color:#A89984\"> &#x3C;</span><span style=\"color:#B8BB26\">asm/syscalls_64.h</span><span style=\"color:#A89984\">></span></span>\n<span class=\"line\"><span style=\"color:#FB4934\">    default</span><span style=\"color:#A89984\">:</span><span style=\"color:#FB4934\"> return</span><span style=\"color:#FABD2F\"> __x64_sys_ni_syscall</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">regs</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">    }</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">}</span></span></code></pre></div><p>The <code>#include &lt;asm/syscalls_64.h&gt;</code> inside the switch is interesting. At build time the kernel runs a script that reads <code>syscall_64.tbl</code> and generates a header that expands to a case for every single syscall. What looks like one line is actually hundreds of cases: <code>case 0: return __x64_sys_read(regs); case 1: return __x64_sys_write(regs);</code> and so on. So <code>nr == 1</code> calls <code>write</code>, <code>nr == 0</code> calls <code>read</code>. Do not worry about <code>pt_regs</code> for now, we will get there.</p>\n<p>The point is that the syscall number is nothing magical, it is just an index. The kernel looks it up, calls the right function, and does the work.</p>\n<h2>What happens when syscall fires</h2>\n<p>Before diving into the entry point assembly, let me clarify a few things.</p>\n<p>Each CPU has one set of registers: <code>rax</code>, <code>rdi</code>, <code>rsi</code>, <code>rbx</code>, and the rest. When your program is running, those registers hold your data: local variables, pointers, return values, things your program is actively using. When <code>syscall</code> fires, the kernel takes over on that same CPU using those same registers. If it just starts using them, your program&#39;s state is gone.</p>\n<p>In a normal C function call, the x86-64 ABI handles this through a split between caller-saved and callee-saved registers. Caller-saved registers like <code>rax</code>, <code>rcx</code>, <code>rdi</code> can be trashed by any function you call. Callee-saved registers like <code>rbx</code>, <code>rbp</code>, <code>r12</code> through <code>r15</code> must be restored before returning. Both sides agree on this contract and everyone is happy.\nBut a syscall is not a normal function call. The kernel does not follow your ABI. It is completely separate code running at a different privilege level. It cannot promise to restore anything. So the kernel has to save everything before it touches a single register. That is the first thing that happens after <code>syscall</code> fires.</p>\n<p>But save them where? The kernel cannot use your user stack. Your stack pointer might be corrupted or pointing at garbage. Even if it is valid, the kernel would be writing sensitive data into memory your program can read and modify. So the kernel has its own stack, one per thread, allocated when the thread is created. It lives in kernel virtual memory, above <code>0xffff800000000000</code>, a region your process can never touch. It does not appear in <code>/proc/PID/maps</code>. It is completely outside your process memory structure, invisible to your program. It sits empty, waiting for a trap to fire.</p>\n<p>Now, for the kernel to switch to that stack the moment <code>entry_SYSCALL_64</code> runs, it needs to know where it is. That is what the TSS (Task State Segment) is for. It is a structure the kernel maintains per CPU core. Its <code>sp0</code> field always holds the kernel stack address of whichever thread is currently running on that CPU:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">struct</span><span style=\"color:#EBDBB2\"> x86_hw_tss </span><span style=\"color:#A89984\">{</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> int</span><span style=\"color:#EBDBB2\">    reserved1</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#EBDBB2\">    u64             sp0</span><span style=\"color:#A89984\">;</span><span style=\"color:#928374;font-style:italic\">    /* kernel stack pointer for the current thread */</span></span>\n<span class=\"line\"><span style=\"color:#EBDBB2\">    u64             sp1</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#EBDBB2\">    u64             sp2</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#928374;font-style:italic\">    // ...</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">}</span><span style=\"color:#FABD2F\"> __attribute__</span><span style=\"color:#A89984\">((</span><span style=\"color:#EBDBB2\">packed</span><span style=\"color:#A89984\">));</span></span></code></pre></div><p>Every thread has its own kernel stack, allocated at thread creation and fixed for its lifetime. The stack itself never moves. What changes is <code>sp0</code>. Every time the scheduler picks a new thread to run on a CPU, it updates <code>sp0</code> and a per-CPU variable called <code>cpu_current_top_of_stack</code> to point at that thread&#39;s kernel stack:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#928374;font-style:italic\">/* called on every context switch */</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">void</span><span style=\"color:#FABD2F\"> update_task_stack</span><span style=\"color:#A89984\">(</span><span style=\"color:#FE8019\">struct</span><span style=\"color:#EBDBB2\"> task_struct </span><span style=\"color:#8EC07C\">*</span><span style=\"color:#83A598\">task</span><span style=\"color:#A89984\">)</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">{</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">    load_sp0</span><span style=\"color:#A89984\">(</span><span style=\"color:#FABD2F\">task_top_of_stack</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">task</span><span style=\"color:#A89984\">));</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">}</span></span></code></pre></div><p>This updates both <code>sp0</code> in the TSS and a per-CPU variable called <code>cpu_current_top_of_stack</code>, always keeping them in sync. By the time <code>entry_SYSCALL_64</code> runs, that variable already holds the right kernel stack address for the current thread. So when <code>syscall</code> fires, the kernel already knows exactly which stack to switch to. It was prepared by the last context switch and it is waiting to be used.</p>\n<p>Remember <code>cpu_init_exception_handling()</code> from <code>trap_init()</code> earlier? That is where the TSS gets registered with the CPU at boot:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">void</span><span style=\"color:#FABD2F\"> cpu_init_exception_handling</span><span style=\"color:#A89984\">(</span><span style=\"color:#FE8019\">bool</span><span style=\"color:#83A598\"> boot_cpu</span><span style=\"color:#A89984\">)</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">{</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    struct</span><span style=\"color:#EBDBB2\"> tss_struct </span><span style=\"color:#8EC07C\">*</span><span style=\"color:#EBDBB2\">tss </span><span style=\"color:#8EC07C\">=</span><span style=\"color:#FABD2F\"> this_cpu_ptr</span><span style=\"color:#A89984\">(</span><span style=\"color:#8EC07C\">&#x26;</span><span style=\"color:#EBDBB2\">cpu_tss_rw</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#928374;font-style:italic\">    // ...</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">    set_tss_desc</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">cpu</span><span style=\"color:#A89984\">,</span><span style=\"color:#8EC07C\"> &#x26;</span><span style=\"color:#FABD2F\">get_cpu_entry_area</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">cpu</span><span style=\"color:#A89984\">)-></span><span style=\"color:#83A598\">tss</span><span style=\"color:#A89984\">.</span><span style=\"color:#83A598\">x86_tss</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">    load_TR_desc</span><span style=\"color:#A89984\">();</span></span>\n<span class=\"line\"><span style=\"color:#928374;font-style:italic\">    // ...</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">}</span></span></code></pre></div><p>This is a two step process. <code>set_tss_desc()</code> registers the TSS in the GDT, the global table where the CPU looks up system structures. Think of it as adding the TSS to a directory so the CPU can find it. Then <code>load_TR_desc()</code> executes the <code>ltr</code> instruction, which loads that GDT entry into the Task Register, a dedicated CPU register that holds the currently active TSS. After <code>ltr</code> runs, the CPU no longer needs to search the GDT. It already knows where the TSS is, and the moment a trap fires, it reads <code>sp0</code> from it instantly.</p>\n<p>You do not need to memorize any of this. I certainly do not. The important thing is just to understand that all this preparation means the kernel knows exactly which stack to switch to the moment it needs it. It was all wired up at boot, waiting to be used.</p>\n<p>To make this concrete, here is the full chain from boot to the moment your syscall lands on the kernel stack:</p>\n<p><img src=\"/assets/kernel_stack_setup.png\" alt=\"Boot-time chain wiring up the kernel stack: cpu_init_exception_handling registers the TSS, ltr loads it into the Task Register, and on every context switch update_task_stack writes the current thread&#39;s kernel stack into sp0 and cpu_current_top_of_stack so it is ready when a trap fires\"></p>\n<p>And with all that wired up at boot, your one-line <code>printf</code> can finally make it to the kernel. Remember <code>MSR_LSTAR</code>, the special CPU register the kernel wrote at boot to tell the CPU where to jump when a <code>syscall</code> fires? That address is <code>entry_SYSCALL_64</code>, and we can finally handle the syscall. And if all this complexity just to cross a privilege boundary feels overwhelming, well, now you know why there are not that many operating systems in the world.</p>\n<h2>Inside the entry_SYSCALL_64</h2>\n<p>The following is real kernel assembly, don&#39;t be scared.</p>\n<p>The CPU jumped to <code>entry_SYSCALL_64</code>. Everything we just covered, the TSS wired up at boot, the kernel stack allocated per thread, <code>cpu_current_top_of_stack</code> updated on every context switch, it all comes together in the very first two instructions.</p>\n<p>The first thing <code>entry_SYSCALL_64</code> does is switch to the kernel stack. The kernel does it itself in the very first instructions, reading from <code>cpu_current_top_of_stack</code>, the per-CPU variable that was updated on the last context switch:</p>\n<div class=\"code-block\"><span class=\"code-lang\">asm</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#8EC07C\">movq</span><span style=\"color:#EBDBB2\">    %</span><span style=\"color:#D3869B\">rsp</span><span style=\"color:#EBDBB2\">, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)</span></span>\n<span class=\"line\"><span style=\"color:#8EC07C\">movq</span><span style=\"color:#EBDBB2\">    PER_CPU_VAR(cpu_current_top_of_stack), %</span><span style=\"color:#D3869B\">rsp</span></span></code></pre></div><p>Your <code>rsp</code> is stashed into a scratch slot in the TSS and replaced with the kernel stack pointer. The stack is now switched.</p>\n<p>Remember when we said the kernel has to save everything before it touches a single register? This is where that actually happens. It builds a <code>pt_regs</code> struct by pushing your registers one by one onto the kernel stack:</p>\n<div class=\"code-block\"><span class=\"code-lang\">asm</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#EBDBB2\">pushq   $__USER_DS</span></span>\n<span class=\"line\"><span style=\"color:#EBDBB2\">pushq   PER_CPU_VAR(cpu_tss_rw + TSS_sp2)  </span><span style=\"color:#928374;font-style:italic\">/* your rsp */</span></span>\n<span class=\"line\"><span style=\"color:#EBDBB2\">pushq   %</span><span style=\"color:#D3869B\">r11</span><span style=\"color:#928374;font-style:italic\">                                /* rflags */</span></span>\n<span class=\"line\"><span style=\"color:#EBDBB2\">pushq   $__USER_CS</span></span>\n<span class=\"line\"><span style=\"color:#EBDBB2\">pushq   %</span><span style=\"color:#D3869B\">rcx</span><span style=\"color:#928374;font-style:italic\">                                /* your return address */</span></span>\n<span class=\"line\"><span style=\"color:#EBDBB2\">pushq   %</span><span style=\"color:#D3869B\">rax</span><span style=\"color:#928374;font-style:italic\">                                /* syscall number */</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#EBDBB2\">PUSH_AND_CLEAR_REGS</span></span></code></pre></div><p>Those pushes map directly onto this struct:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">struct</span><span style=\"color:#EBDBB2\"> pt_regs </span><span style=\"color:#A89984\">{</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> r15</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> r14</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> r13</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> r12</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> bp</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> bx</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> r11</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> r10</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> r9</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> r8</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> ax</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> cx</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> dx</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> si</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> di</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> orig_ax</span><span style=\"color:#A89984\">;</span><span style=\"color:#928374;font-style:italic\">  /* syscall number */</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> ip</span><span style=\"color:#A89984\">;</span><span style=\"color:#928374;font-style:italic\">       /* your return address */</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> cs</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> flags</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> sp</span><span style=\"color:#A89984\">;</span><span style=\"color:#928374;font-style:italic\">       /* your rsp */</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> long</span><span style=\"color:#EBDBB2\"> ss</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">};</span></span></code></pre></div><p>Every register your program was using is now sitting safely on the kernel stack. <code>PUSH_AND_CLEAR_REGS</code> saves the remaining general purpose registers and zeroes them after. The kernel runs with full privileges, and leaving user controlled values in registers is an attack surface. A clean slate before kernel execution is not optional.</p>\n<p>With all of that done, the kernel calls into C:</p>\n<div class=\"code-block\"><span class=\"code-lang\">asm</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#8EC07C\">movq</span><span style=\"color:#EBDBB2\">    %</span><span style=\"color:#D3869B\">rsp</span><span style=\"color:#EBDBB2\">, %</span><span style=\"color:#D3869B\">rdi</span></span>\n<span class=\"line\"><span style=\"color:#EBDBB2\">movslq  %</span><span style=\"color:#D3869B\">eax</span><span style=\"color:#EBDBB2\">, %</span><span style=\"color:#D3869B\">rsi</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#8EC07C\">call</span><span style=\"color:#EBDBB2\">    do_syscall_64</span></span></code></pre></div><p>That assembly is literally calling the function below, passing <code>pt_regs</code> as the first argument and the syscall number as the second. Inside it, <code>do_syscall_x64</code> dispatches to <code>x64_sys_call</code>, the giant switch statement we saw earlier that maps your <code>rax</code> number to the actual handler:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#EBDBB2\">__visible noinstr </span><span style=\"color:#FE8019\">bool</span><span style=\"color:#FABD2F\"> do_syscall_64</span><span style=\"color:#A89984\">(</span><span style=\"color:#FE8019\">struct</span><span style=\"color:#EBDBB2\"> pt_regs </span><span style=\"color:#8EC07C\">*</span><span style=\"color:#83A598\">regs</span><span style=\"color:#A89984\">,</span><span style=\"color:#FE8019\"> int</span><span style=\"color:#83A598\"> nr</span><span style=\"color:#A89984\">)</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">{</span></span>\n<span class=\"line\"><span style=\"color:#EBDBB2\">    nr </span><span style=\"color:#8EC07C\">=</span><span style=\"color:#FABD2F\"> syscall_enter_from_user_mode</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">regs</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> nr</span><span style=\"color:#A89984\">);</span><span style=\"color:#928374;font-style:italic\"> /* seccomp, audit */</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">    add_random_kstack_offset</span><span style=\"color:#A89984\">();</span><span style=\"color:#928374;font-style:italic\"> /* security mitigation */</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">    do_syscall_x64</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">regs</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> nr</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">    syscall_exit_to_user_mode</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">regs</span><span style=\"color:#A89984\">);</span><span style=\"color:#928374;font-style:italic\"> /* check pending signals */</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#FB4934\">    return</span><span style=\"color:#D3869B\"> true</span><span style=\"color:#A89984\">;</span><span style=\"color:#928374;font-style:italic\"> /* use SYSRET */</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#FE8019\">static</span><span style=\"color:#EBDBB2\"> __always_inline </span><span style=\"color:#FE8019\">bool</span><span style=\"color:#FABD2F\"> do_syscall_x64</span><span style=\"color:#A89984\">(</span><span style=\"color:#FE8019\">struct</span><span style=\"color:#EBDBB2\"> pt_regs </span><span style=\"color:#8EC07C\">*</span><span style=\"color:#83A598\">regs</span><span style=\"color:#A89984\">,</span><span style=\"color:#FE8019\"> int</span><span style=\"color:#83A598\"> nr</span><span style=\"color:#A89984\">)</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">{</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    unsigned</span><span style=\"color:#FE8019\"> int</span><span style=\"color:#EBDBB2\"> unr </span><span style=\"color:#8EC07C\">=</span><span style=\"color:#EBDBB2\"> nr</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FB4934\">    if</span><span style=\"color:#A89984\"> (</span><span style=\"color:#FABD2F\">likely</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">unr </span><span style=\"color:#8EC07C\">&#x3C;</span><span style=\"color:#EBDBB2\"> NR_syscalls</span><span style=\"color:#A89984\">))</span><span style=\"color:#A89984\"> {</span></span>\n<span class=\"line\"><span style=\"color:#EBDBB2\">        unr </span><span style=\"color:#8EC07C\">=</span><span style=\"color:#FABD2F\"> array_index_nospec</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">unr</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> NR_syscalls</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#83A598\">        regs</span><span style=\"color:#A89984\">-></span><span style=\"color:#83A598\">ax</span><span style=\"color:#8EC07C\"> =</span><span style=\"color:#FABD2F\"> x64_sys_call</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">regs</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> unr</span><span style=\"color:#A89984\">);</span><span style=\"color:#928374;font-style:italic\"> /* the switch we saw earlier */</span></span>\n<span class=\"line\"><span style=\"color:#FB4934\">        return</span><span style=\"color:#D3869B\"> true</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">    }</span></span>\n<span class=\"line\"><span style=\"color:#FB4934\">    return</span><span style=\"color:#D3869B\"> false</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">}</span></span></code></pre></div><p>Whatever the handler returns lands in <code>regs-&gt;ax</code>. That is the return value of your syscall, the number of bytes written, the file descriptor, the error code. It will be sitting in <code>rax</code> when your program gets control back.\nOnce the handler is done, <code>POP_REGS</code> mirrors what <code>PUSH_AND_CLEAR_REGS</code> did, restoring every register from <code>pt_regs</code> back into the CPU. Then the kernel exits with:</p>\n<div class=\"code-block\"><span class=\"code-lang\">asm</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#EBDBB2\">sysretq</span></span></code></pre></div><p><code>sysretq</code> is the symmetric counterpart to <code>syscall</code>. When <code>syscall</code> fired, the CPU saved your return address into <code>rcx</code> and your flags into <code>r11</code>. <code>sysretq</code> reads them back, restores <code>rip</code> from <code>rcx</code> and <code>rflags</code> from <code>r11</code>, drops the privilege level back to user mode, and jumps to where you left off. One instruction undoes the crossing.\nYour program continues as if nothing happened. <code>write</code> returned its byte count, <code>libc</code> handed it back to <code>printf</code>, <code>printf</code> returned, and your <code>main</code> kept running.\nTo make this concrete, here is the full path from your <code>printf</code> call down to the kernel and back:\n<img src=\"/assets/printf_flux.png\" alt=\"End-to-end flow of a printf: user code calls printf, libc calls write, the syscall instruction switches the CPU to kernel mode and jumps to entry_SYSCALL_64, which switches stacks, saves registers into pt_regs, dispatches through do_syscall_64 to the write handler, then sysretq returns to user mode\"></p>\n<h2>Wrapping up</h2>\n<p>We started with a single <code>printf(&quot;hello world\\n&quot;)</code> and ended up reading kernel assembly. Along the way we saw how the CPU enforces privilege levels in hardware, how the kernel sets up the IDT and <code>MSR_LSTAR</code> at boot so it always knows where to jump, how every thread has a kernel stack sitting invisible in memory waiting for a trap to fire, and how <code>entry_SYSCALL_64</code> orchestrates the entire crossing: switching stacks, freezing your registers, dispatching to the right handler, and returning as if nothing happened.</p>\n<p>The kernel is not magic. It is just code that runs on the same CPU as yours, with a lot of careful setup done at boot time to make the handoff work. I hope it feels a little less like a black box now.</p>\n<p>If you want to go deeper, the next natural steps are signal handling, which happens right before <code>sysretq</code> returns, virtual memory and how the page tables get switched on entry, and the scheduler, which is what keeps <code>cpu_current_top_of_stack</code> always pointing at the right place. Each one of those is its own rabbit hole. But now you have the foundation to read them.</p>\n","date_published":"2026-05-07T00:00:00.000Z"},{"id":"strings-in-rust-how-they-differ-from-c","url":"https://viniciusvidal.com/strings-in-rust-how-they-differ-from-c","title":"Strings in Rust: how they differ from C","summary":"char*, null terminators, fat pointers, DSTs, and Deref. Everything that makes Rust strings different from C, explained through memory.","content_html":"<p>If you come from C, Rust strings will probably feel unfamiliar at first. The underlying concepts are the same: bytes in memory, pointers, lengths. What&#39;s different is that Rust wasn&#39;t built under the same constraints, so it made different tradeoffs. Let&#39;s start from the ground up.</p>\n<h2>Strings in C</h2>\n<p>In C, a string is a <code>char*</code>. A pointer to a sequence of characters that ends when you hit a null byte. That&#39;s the entire contract.</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">char</span><span style=\"color:#8EC07C\"> *</span><span style=\"color:#EBDBB2\">s </span><span style=\"color:#8EC07C\">=</span><span style=\"color:#A89984\"> \"</span><span style=\"color:#B8BB26\">hello</span><span style=\"color:#A89984\">\"</span><span style=\"color:#A89984\">;</span></span></code></pre></div><p><img src=\"/assets/c-string-in-memory.png\" alt=\"C string in memory: a single pointer s on the stack with an arrow to a sequence of bytes &#39;h&#39; &#39;e&#39; &#39;l&#39; &#39;l&#39; &#39;o&#39; &#39;\\0&#39;, the null byte visually distinct and labeled &quot;strlen stops here&quot;\"></p>\n<p>There&#39;s no length stored anywhere. If you want to know how long the string is, you call <code>strlen</code>, which walks every byte until it finds <code>\\0</code>. That&#39;s O(n). Every time.</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">size_t</span><span style=\"color:#FABD2F\"> strlen</span><span style=\"color:#A89984\">(</span><span style=\"color:#FE8019\">const</span><span style=\"color:#FE8019\"> char</span><span style=\"color:#8EC07C\"> *</span><span style=\"color:#83A598\">s</span><span style=\"color:#A89984\">)</span><span style=\"color:#A89984\"> {</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    size_t</span><span style=\"color:#EBDBB2\"> len </span><span style=\"color:#8EC07C\">=</span><span style=\"color:#D3869B\"> 0</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FB4934\">    while</span><span style=\"color:#A89984\"> (</span><span style=\"color:#83A598\">s</span><span style=\"color:#A89984\">[</span><span style=\"color:#EBDBB2\">len</span><span style=\"color:#A89984\">]</span><span style=\"color:#8EC07C\"> !=</span><span style=\"color:#A89984\"> '</span><span style=\"color:#D3869B\">\\0</span><span style=\"color:#A89984\">'</span><span style=\"color:#A89984\">)</span><span style=\"color:#A89984\"> {</span></span>\n<span class=\"line\"><span style=\"color:#EBDBB2\">        len</span><span style=\"color:#8EC07C\">++</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">    }</span></span>\n<span class=\"line\"><span style=\"color:#FB4934\">    return</span><span style=\"color:#EBDBB2\"> len</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">}</span></span></code></pre></div><p>This also means you can&#39;t have a null byte in the middle of a string in any meaningful way. The moment <code>strlen</code> or any standard function hits <code>\\0</code>, the string is over as far as they&#39;re concerned.</p>\n<p>It works, and it&#39;s simple, but you&#39;re trading a lot for that simplicity. C was designed in the early 70s under real constraints: memory was scarce, hardware was limited, and storing an extra integer alongside every string was not a free decision. And it wouldn&#39;t even be a single byte: 1 byte caps you at 255 characters, so you&#39;d need at least 2 or 4 bytes to store anything useful, which starts to feel expensive on machines with kilobytes of RAM. The null terminator was a pragmatic call for its time.</p>\n<p>But it also means every time you allocate or copy a string, you have to remember to account for it. That extra <code>+ 1</code> for the null byte is easy to forget:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">char</span><span style=\"color:#8EC07C\"> *</span><span style=\"color:#EBDBB2\">s </span><span style=\"color:#8EC07C\">=</span><span style=\"color:#A89984\"> \"</span><span style=\"color:#B8BB26\">hello</span><span style=\"color:#A89984\">\"</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">char</span><span style=\"color:#8EC07C\"> *</span><span style=\"color:#EBDBB2\">copy </span><span style=\"color:#8EC07C\">=</span><span style=\"color:#FABD2F\"> malloc</span><span style=\"color:#A89984\">(</span><span style=\"color:#FABD2F\">strlen</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">s</span><span style=\"color:#A89984\">));</span><span style=\"color:#928374;font-style:italic\"> // wrong, should be strlen(s) + 1</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">strcpy</span><span style=\"color:#A89984\">(</span><span style=\"color:#EBDBB2\">copy</span><span style=\"color:#A89984\">,</span><span style=\"color:#EBDBB2\"> s</span><span style=\"color:#A89984\">);</span><span style=\"color:#928374;font-style:italic\">                // writes '\\0' past the allocation</span></span></code></pre></div><p>This class of bug has caused more security vulnerabilities than most people care to count. The null terminator is not just a performance tradeoff, it&#39;s an ongoing source of correctness problems that you carry with you every time you touch a string.</p>\n<h2>Understanding memory first</h2>\n<p>To understand why Rust does it differently, the easiest way is to understand memory itself, without a bunch of abstractions.</p>\n<p>When a program runs, the OS loads it into RAM and gives it a structured chunk of memory to work with. There&#39;s a lot going on in there, but we only need to care about three regions today.</p>\n<p><img src=\"/assets/process-memory-layout.png\" alt=\"Process memory layout: vertical diagram with stack at the top growing downward, heap in the middle growing upward, data/rodata segment below, and text segment at the bottom\"></p>\n<p>The stack is where local variables live. Every time you call a function, a new frame is pushed onto the stack with space for that function&#39;s variables. The size of everything on the stack must be known at compile time, because the compiler has to calculate how much space each frame needs before the program runs. When the function returns, the frame is popped and that memory is gone.</p>\n<p>The heap is a pool of memory you can request chunks from at runtime, in sizes you don&#39;t need to know ahead of time. The allocation lives until you explicitly free it, or in Rust&#39;s case, until the owner goes out of scope.</p>\n<p>The rodata segment is where read-only data lives, things baked into the binary at compile time that never change.</p>\n<p>With that in mind, strings start to make a lot more sense.</p>\n<h2>Rust takes a different bet</h2>\n<p>What if you just stored the length? Then you wouldn&#39;t need a sentinel value at the end. You wouldn&#39;t need to walk the bytes to find the size. A null byte would just be a byte. No more <code>+ 1</code>, no more walking until you find something that isn&#39;t the string.</p>\n<p>That&#39;s what Rust does. The tradeoff is a few extra bytes per string. The gain is that an entire class of bugs simply stops existing. You can&#39;t forget to account for a null terminator that isn&#39;t there. You can&#39;t read past the end of a string because the length is always known. The size is part of the type, not a convention you have to remember to follow.</p>\n<p>But where exactly that length lives depends on where the string lives. There are three cases worth understanding.</p>\n<h3>Stack strings</h3>\n<div class=\"code-block\"><span class=\"code-lang\">rust</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">let</span><span style=\"color:#83A598\"> s</span><span style=\"color:#8EC07C\">:</span><span style=\"color:#A89984\"> [</span><span style=\"color:#FABD2F\">u8</span><span style=\"color:#A89984\">;</span><span style=\"color:#D3869B\"> 5</span><span style=\"color:#A89984\">]</span><span style=\"color:#8EC07C\"> =</span><span style=\"color:#8EC07C\"> *</span><span style=\"color:#B8BB26\">b</span><span style=\"color:#A89984\">\"</span><span style=\"color:#B8BB26\">hello</span><span style=\"color:#A89984\">\"</span><span style=\"color:#A89984\">;</span></span></code></pre></div><p><img src=\"/assets/stack-string-rust.png\" alt=\"Stack string in Rust: a contiguous block of 5 bytes &#39;h&#39; &#39;e&#39; &#39;l&#39; &#39;l&#39; &#39;o&#39; sitting directly in the stack frame, with a pointer pointing at the first byte within the same frame — no arrow going elsewhere\"></p>\n<p>The bytes live directly in the stack frame. There is still a pointer involved when you take a reference to it, but that pointer points back into the stack itself, not somewhere else. Nothing is allocated on the heap, nothing is looked up elsewhere.</p>\n<p>The limitation is the same as anything else on the stack: the size must be known at compile time. <code>[u8; 5]</code> and <code>[u8; 10]</code> are completely different types. It is closer to <code>char s[5]</code> in C than to anything you would normally call a string, but the memory layout is the same idea.</p>\n<h3>The immutable ones</h3>\n<p>The most common compile-time case is the string literal. Its bytes are placed in the rodata segment before your <code>main</code> even runs, and they stay there for the entire lifetime of the program. You can&#39;t modify them and you don&#39;t need to free them.</p>\n<div class=\"code-block\"><span class=\"code-lang\">rust</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">let</span><span style=\"color:#83A598\"> s</span><span style=\"color:#8EC07C\"> =</span><span style=\"color:#A89984\"> \"</span><span style=\"color:#B8BB26\">hello</span><span style=\"color:#A89984\">\"</span><span style=\"color:#A89984\">;</span></span></code></pre></div><p><img src=\"/assets/string-literal-rust.png\" alt=\"String literal in Rust: stack on the left with two fields ptr and len=5, heap in the middle empty and grayed out labeled &quot;no allocation&quot;, rodata on the right containing &#39;h&#39; &#39;e&#39; &#39;l&#39; &#39;l&#39; &#39;o&#39; — arrow from the stack pointer skipping the heap entirely and landing on the rodata bytes, labeled &amp;&#39;static str\"></p>\n<p>No heap, no capacity. Just two fields on the stack: a pointer to the bytes and their length. This is what is generally called a fat pointer, a pointer that carries extra metadata alongside the address. A regular pointer is just an address. A fat pointer is an address plus something else, in this case the length. The type is <code>&amp;&#39;static str</code>. Static because the data lives for the entire duration of the program.</p>\n<h3>The heap case</h3>\n<p>The heap case is what you get when the string needs to be dynamic: it can grow, shrink, be built at runtime from user input or a file or a network response. You don&#39;t know the content at compile time, so the bytes can&#39;t live on the stack or in rodata. They go on the heap.</p>\n<p>When you write <code>String::from(&quot;hello&quot;)</code>, Rust puts the bytes on the heap and creates a struct on the stack to manage them. In Rust, the ownership system handles freeing for you: when the <code>String</code> goes out of scope, the heap allocation is freed automatically.</p>\n<p><img src=\"/assets/heap-string-rust.png\" alt=\"Heap string in Rust: stack frame on the left with three labeled fields ptr, len=5, cap=5 — heap block on the right containing exactly &#39;h&#39; &#39;e&#39; &#39;l&#39; &#39;l&#39; &#39;o&#39; with no null byte, no length prefix, no metadata, just the raw bytes, labeled &quot;just bytes, nothing else&quot;\"></p>\n<p>Three fields on the stack: a pointer to the heap allocation, the current length in bytes, and the total capacity. The heap side is just raw bytes. No null terminator. No length prefix sitting next to the data. Nothing but the characters themselves.</p>\n<p>The length is not near the data. It lives in the struct, on the stack, separated from what it describes. This matters and will come back later.</p>\n<p>You can verify there&#39;s nothing after the bytes:</p>\n<div class=\"code-block\"><span class=\"code-lang\">rust</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">let</span><span style=\"color:#83A598\"> s</span><span style=\"color:#8EC07C\"> =</span><span style=\"color:#FABD2F\"> String</span><span style=\"color:#8EC07C\">::</span><span style=\"color:#FABD2F\">from</span><span style=\"color:#A89984\">(</span><span style=\"color:#A89984\">\"</span><span style=\"color:#B8BB26\">hello</span><span style=\"color:#A89984\">\"</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">let</span><span style=\"color:#83A598\"> ptr</span><span style=\"color:#8EC07C\"> =</span><span style=\"color:#83A598\"> s</span><span style=\"color:#8EC07C\">.</span><span style=\"color:#FABD2F\">as_ptr</span><span style=\"color:#A89984\">();</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#FB4934\">unsafe</span><span style=\"color:#A89984\"> {</span></span>\n<span class=\"line\"><span style=\"color:#FB4934\">    for</span><span style=\"color:#83A598\"> i</span><span style=\"color:#FB4934\"> in</span><span style=\"color:#D3869B\"> 0</span><span style=\"color:#8EC07C\">..</span><span style=\"color:#D3869B\">5</span><span style=\"color:#A89984\"> {</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">        print!</span><span style=\"color:#A89984\">(</span><span style=\"color:#A89984\">\"{}</span><span style=\"color:#A89984\"> \"</span><span style=\"color:#A89984\">,</span><span style=\"color:#8EC07C\"> *</span><span style=\"color:#83A598\">ptr</span><span style=\"color:#8EC07C\">.</span><span style=\"color:#FABD2F\">add</span><span style=\"color:#A89984\">(</span><span style=\"color:#83A598\">i</span><span style=\"color:#A89984\">));</span><span style=\"color:#928374;font-style:italic\"> // 104 101 108 108 111</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">    }</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">    println!</span><span style=\"color:#A89984\">(</span><span style=\"color:#A89984\">\"{}\"</span><span style=\"color:#A89984\">,</span><span style=\"color:#8EC07C\"> *</span><span style=\"color:#83A598\">ptr</span><span style=\"color:#8EC07C\">.</span><span style=\"color:#FABD2F\">add</span><span style=\"color:#A89984\">(</span><span style=\"color:#D3869B\">5</span><span style=\"color:#A89984\">));</span><span style=\"color:#928374;font-style:italic\"> // whatever was in memory before, not a null byte</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">}</span></span></code></pre></div><p><code>len()</code> in Rust is O(1). You read the field. Done. And a <code>\\0</code> in the middle of a <code>String</code> is just another byte:</p>\n<div class=\"code-block\"><span class=\"code-lang\">rust</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">let</span><span style=\"color:#83A598\"> s</span><span style=\"color:#8EC07C\"> =</span><span style=\"color:#FABD2F\"> String</span><span style=\"color:#8EC07C\">::</span><span style=\"color:#FABD2F\">from</span><span style=\"color:#A89984\">(</span><span style=\"color:#A89984\">\"</span><span style=\"color:#B8BB26\">hel</span><span style=\"color:#D3869B\">\\0</span><span style=\"color:#B8BB26\">lo</span><span style=\"color:#A89984\">\"</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">println!</span><span style=\"color:#A89984\">(</span><span style=\"color:#A89984\">\"{}\"</span><span style=\"color:#A89984\">,</span><span style=\"color:#83A598\"> s</span><span style=\"color:#8EC07C\">.</span><span style=\"color:#FABD2F\">len</span><span style=\"color:#A89984\">());</span><span style=\"color:#928374;font-style:italic\"> // 6, not 3</span></span></code></pre></div><h2>str is unsized and that&#39;s the problem</h2>\n<p>You&#39;ll see String and &amp;str all over Rust code. But they are not the same thing, and the reason why gets at something fundamental.</p>\n<p><code>str</code> is the type that represents a sequence of UTF-8 bytes. Not <code>&amp;str</code>. Not <code>String</code>. Just <code>str</code>. And you almost never use it directly, because you can&#39;t.</p>\n<div class=\"code-block\"><span class=\"code-lang\">rust</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">let</span><span style=\"color:#83A598\"> s</span><span style=\"color:#8EC07C\">:</span><span style=\"color:#FABD2F\"> str</span><span style=\"color:#8EC07C\"> =</span><span style=\"color:#A89984\"> \"</span><span style=\"color:#B8BB26\">hello</span><span style=\"color:#A89984\">\"</span><span style=\"color:#A89984\">;</span><span style=\"color:#928374;font-style:italic\"> // doesn't compile</span></span></code></pre></div><p>The compiler refuses because <code>str</code> is a dynamically sized type, a DST. Its size is not known at compile time. Every value you put on the stack needs a known size so the compiler can figure out how much space to reserve. <code>str</code> has no fixed size. It could be 3 bytes, it could be 300. The compiler cannot reserve stack space for something whose size only exists at runtime.</p>\n<p><img src=\"/assets/sized-vs-unsized-types.png\" alt=\"Sized vs unsized types: on the left an i32 box with a clean fixed 4-byte slot, on the right a str box with a question mark where the size should be and a compiler error floating above it\"></p>\n<p>Think about what a slice actually is. <code>[u8]</code> is not a container, it&#39;s not a struct, it has no metadata attached to it. It is literally just the bytes themselves, sitting somewhere in memory. A pointer to a <code>[u8]</code> is a pointer to the first byte and nothing else. There is no way to know where it ends without extra information. The size is not in the type, it&#39;s in the data, and the data is only knowable at runtime.</p>\n<p><code>str</code> is exactly that: a slice of bytes. Same problem. This isn&#39;t specific to strings either. Every slice type in Rust has it. <code>[T]</code> is a DST for the exact same reason, and you can&#39;t have a bare <code>[i32]</code> on the stack either.</p>\n<p>So you can&#39;t have a bare <code>str</code>. You always need to go through a reference, and that reference needs to carry the length with it. Which brings us back to the fat pointer.</p>\n<h2>&amp;str has to be fat</h2>\n<p>A normal pointer would not be enough. If <code>&amp;str</code> were just a pointer to the bytes, you&#39;d lose the length, and you&#39;d be back to C territory, walking until you hit a sentinel.</p>\n<p>So <code>&amp;str</code> is a fat pointer. Two fields: a pointer to the bytes and the length. The size of <code>&amp;str</code> itself is fixed: two machine words. The size of the <code>str</code> it points to is carried inside it. That&#39;s how you hold a reference to an unsized type without losing information. Now the compiler is happy. You can pass <code>&amp;str</code> around, put it on the stack, store it in structs.</p>\n<h2>You have a String and want a &amp;str</h2>\n<p>So you have a <code>String</code>. A struct on the stack with <code>ptr</code>, <code>len</code>, and <code>cap</code>, pointing at bytes on the heap. You want a <code>&amp;str</code>: a fat pointer directly to those bytes.</p>\n<p>These are different things. How do you get from one to the other?</p>\n<p>Manually you&#39;d do this:</p>\n<div class=\"code-block\"><span class=\"code-lang\">rust</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">let</span><span style=\"color:#83A598\"> s</span><span style=\"color:#8EC07C\"> =</span><span style=\"color:#FABD2F\"> String</span><span style=\"color:#8EC07C\">::</span><span style=\"color:#FABD2F\">from</span><span style=\"color:#A89984\">(</span><span style=\"color:#A89984\">\"</span><span style=\"color:#B8BB26\">hello</span><span style=\"color:#A89984\">\"</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">let</span><span style=\"color:#83A598\"> ptr</span><span style=\"color:#8EC07C\"> =</span><span style=\"color:#83A598\"> s</span><span style=\"color:#8EC07C\">.</span><span style=\"color:#FABD2F\">as_ptr</span><span style=\"color:#A89984\">();</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">let</span><span style=\"color:#83A598\"> len</span><span style=\"color:#8EC07C\"> =</span><span style=\"color:#83A598\"> s</span><span style=\"color:#8EC07C\">.</span><span style=\"color:#FABD2F\">len</span><span style=\"color:#A89984\">();</span></span>\n<span class=\"line\"><span style=\"color:#928374;font-style:italic\">// now you have what a &#x26;str needs</span></span></code></pre></div><p>You pull the pointer and the length out of the struct and construct the fat pointer yourself. Capacity is irrelevant, you leave it behind.</p>\n<p><img src=\"/assets/string-to-str.png\" alt=\"From String to &amp;str: String struct on the left with three fields ptr/len/cap and an arrow from ptr to heap bytes on the far right; &amp;str in the middle as just ptr+len pointing to the same heap bytes; the cap field grayed out and labeled &quot;left behind&quot;\"></p>\n<p>This is exactly what happens when you write:</p>\n<div class=\"code-block\"><span class=\"code-lang\">rust</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FE8019\">let</span><span style=\"color:#83A598\"> s</span><span style=\"color:#8EC07C\"> =</span><span style=\"color:#FABD2F\"> String</span><span style=\"color:#8EC07C\">::</span><span style=\"color:#FABD2F\">from</span><span style=\"color:#A89984\">(</span><span style=\"color:#A89984\">\"</span><span style=\"color:#B8BB26\">hello</span><span style=\"color:#A89984\">\"</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">let</span><span style=\"color:#83A598\"> slice</span><span style=\"color:#8EC07C\">:</span><span style=\"color:#8EC07C\"> &#x26;</span><span style=\"color:#FABD2F\">str</span><span style=\"color:#8EC07C\"> =</span><span style=\"color:#8EC07C\"> &#x26;</span><span style=\"color:#83A598\">s</span><span style=\"color:#A89984\">;</span></span></code></pre></div><p>But it doesn&#39;t happen by accident. Something has to automate that extraction. That something is <code>Deref</code>.</p>\n<h2>Deref is the one to blame</h2>\n<p><code>String</code> implements <code>Deref</code> with <code>Target = str</code>. That impl is what makes <code>&amp;String</code> coerce into <code>&amp;str</code> transparently.</p>\n<p>Here&#39;s a simplified version of what it looks like:</p>\n<div class=\"code-block\"><span class=\"code-lang\">rust</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FB4934\">use</span><span style=\"color:#FABD2F\"> std</span><span style=\"color:#8EC07C\">::</span><span style=\"color:#FABD2F\">ops</span><span style=\"color:#8EC07C\">::</span><span style=\"color:#FABD2F\">Deref</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#FE8019\">struct</span><span style=\"color:#FABD2F\"> MyString</span><span style=\"color:#A89984\"> {</span></span>\n<span class=\"line\"><span style=\"color:#83A598\">    ptr</span><span style=\"color:#8EC07C\">:</span><span style=\"color:#8EC07C\"> *</span><span style=\"color:#FE8019\">const</span><span style=\"color:#FABD2F\"> u8</span><span style=\"color:#A89984\">,</span></span>\n<span class=\"line\"><span style=\"color:#83A598\">    len</span><span style=\"color:#8EC07C\">:</span><span style=\"color:#FABD2F\"> usize</span><span style=\"color:#A89984\">,</span></span>\n<span class=\"line\"><span style=\"color:#83A598\">    cap</span><span style=\"color:#8EC07C\">:</span><span style=\"color:#FABD2F\"> usize</span><span style=\"color:#A89984\">,</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#FB4934\">impl</span><span style=\"color:#FABD2F\"> Deref</span><span style=\"color:#FB4934\"> for</span><span style=\"color:#FABD2F\"> MyString</span><span style=\"color:#A89984\"> {</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    type</span><span style=\"color:#FABD2F\"> Target</span><span style=\"color:#8EC07C\"> =</span><span style=\"color:#FABD2F\"> str</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#FB4934\">    fn</span><span style=\"color:#FABD2F\"> deref</span><span style=\"color:#A89984\">(</span><span style=\"color:#8EC07C\">&#x26;</span><span style=\"color:#83A598\">self</span><span style=\"color:#A89984\">)</span><span style=\"color:#8EC07C\"> -></span><span style=\"color:#8EC07C\"> &#x26;</span><span style=\"color:#FABD2F\">str</span><span style=\"color:#A89984\"> {</span></span>\n<span class=\"line\"><span style=\"color:#FB4934\">        unsafe</span><span style=\"color:#A89984\"> {</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">            let</span><span style=\"color:#83A598\"> slice</span><span style=\"color:#8EC07C\"> =</span><span style=\"color:#FABD2F\"> std</span><span style=\"color:#8EC07C\">::</span><span style=\"color:#FABD2F\">slice</span><span style=\"color:#8EC07C\">::</span><span style=\"color:#FABD2F\">from_raw_parts</span><span style=\"color:#A89984\">(</span><span style=\"color:#83A598\">self</span><span style=\"color:#8EC07C\">.</span><span style=\"color:#EBDBB2\">ptr</span><span style=\"color:#A89984\">,</span><span style=\"color:#83A598\"> self</span><span style=\"color:#8EC07C\">.</span><span style=\"color:#EBDBB2\">len</span><span style=\"color:#A89984\">);</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">            std</span><span style=\"color:#8EC07C\">::</span><span style=\"color:#FABD2F\">str</span><span style=\"color:#8EC07C\">::</span><span style=\"color:#FABD2F\">from_utf8_unchecked</span><span style=\"color:#A89984\">(</span><span style=\"color:#83A598\">slice</span><span style=\"color:#A89984\">)</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">        }</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">    }</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">}</span></span></code></pre></div><p>Look at what <code>deref</code> uses: <code>self.ptr</code> and <code>self.len</code>. That&#39;s it. <code>self.cap</code> is never touched. The struct on the stack is the entry point, but what comes out the other side is a fat pointer aimed directly at the heap bytes, with the struct left behind.</p>\n<p>This is why <code>&amp;str</code> feels like it skips the struct. It does. Deref extracts exactly the two pieces a <code>&amp;str</code> needs and constructs one, discarding the rest. Coming from C this feels like the compiler is doing something behind your back. But it&#39;s not hidden, it&#39;s just automated. The real <code>String</code> impl does the same thing, just with safe wrappers around the raw pointer construction.</p>\n<h2>The FFI boundary</h2>\n<p>Everything above works because Rust controls both sides. The length is always there, the null terminator is never needed.</p>\n<p>But C doesn&#39;t know any of this. When you need to pass a string to a C function, you need to go back to the old contract: a pointer to bytes ending in <code>\\0</code>.</p>\n<p>That&#39;s what <code>CString</code> is for:</p>\n<div class=\"code-block\"><span class=\"code-lang\">rust</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FB4934\">use</span><span style=\"color:#FABD2F\"> std</span><span style=\"color:#8EC07C\">::</span><span style=\"color:#FABD2F\">ffi</span><span style=\"color:#8EC07C\">::</span><span style=\"color:#FABD2F\">CString</span><span style=\"color:#A89984\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#FE8019\">let</span><span style=\"color:#83A598\"> s</span><span style=\"color:#8EC07C\"> =</span><span style=\"color:#FABD2F\"> CString</span><span style=\"color:#8EC07C\">::</span><span style=\"color:#FABD2F\">new</span><span style=\"color:#A89984\">(</span><span style=\"color:#A89984\">\"</span><span style=\"color:#B8BB26\">hello</span><span style=\"color:#A89984\">\"</span><span style=\"color:#A89984\">)</span><span style=\"color:#8EC07C\">.</span><span style=\"color:#FABD2F\">unwrap</span><span style=\"color:#A89984\">();</span></span></code></pre></div><p><img src=\"/assets/ffi-boundary.png\" alt=\"FFI boundary: Rust side on the left with a &amp;str fat pointer (ptr+len), C side on the right with a char* pointing to bytes ending in &#39;\\0&#39;, and CString in the middle bridging the two — labeled &quot;the null terminator never went away, it just got pushed to the edges&quot;\"></p>\n<p>It adds the null terminator back and gives you a pointer you can hand to C. The convention never went away, it just got pushed to the edges of the program, behind an explicit type that makes the cost visible.</p>\n<hr>\n<p>The full picture: C strings are bare pointers with an implicit length encoded as a sentinel, a pragmatic decision made under the constraints of its time. Rust stores length explicitly, keeps metadata separate from data, and uses a DST to make slices work uniformly whether the bytes are on the heap, the stack, or baked into the rodata segment. <code>&amp;str</code> exists because <code>str</code> can&#39;t. And Deref is the mechanism that bridges the owned heap type with the borrowed slice type, skipping the struct and handing you a fat pointer straight to the bytes.</p>\n","date_published":"2026-04-22T00:00:00.000Z"},{"id":"networking-concepts-from-the-ground-up","url":"https://viniciusvidal.com/networking-concepts-from-the-ground-up","title":"Networking concepts from the ground up","summary":"Sockets, TCP, UDP, IP addresses, ports, byte order, and DNS. The concepts you need before writing a single line of network code.","content_html":"<h2>What is a Socket?</h2>\n<p>A socket is a way to speak to other programs using standard Unix file descriptors.\nLet&#39;s start with a famous catch: everything in Unix is a file.</p>\n<p>The &quot;everything is a file&quot; idea comes down to one observation: every I/O operation is fundamentally about moving bytes from one place to another. Whether you&#39;re reading from a keyboard, writing to a screen, or reading a file off disk, the operation is structurally identical. That flow of bytes has a name: a stream. An ordered sequence of bytes moving between two endpoints, with no built-in separators. Just bytes from A to B.</p>\n<p>For every kind of I/O in Unix, it uses a file descriptor. A file descriptor is simply an integer associated to an open file. These open files can be a network connection, a FIFO, a terminal, or obviously a disk file. They all use the same API: <code>open()</code>, <code>read()</code>, <code>write()</code>, <code>close()</code>.</p>\n<p>A socket is just another file descriptor, but instead of pointing at a file on disk, it points at a communication endpoint. Behind the hood, it is two kernel data structures layered on top of each other:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#928374;font-style:italic\">// include/linux/net.h</span></span>\n<span class=\"line\"><span style=\"color:#928374;font-style:italic\">// The VFS layer — what the file descriptor actually points to.</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">struct</span><span style=\"color:#EBDBB2\"> socket </span><span style=\"color:#A89984\">{</span></span>\n<span class=\"line\"><span style=\"color:#EBDBB2\">    socket_state        state</span><span style=\"color:#A89984\">;</span><span style=\"color:#928374;font-style:italic\">    // connected, listening, etc.</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    short</span><span style=\"color:#EBDBB2\">               type</span><span style=\"color:#A89984\">;</span><span style=\"color:#928374;font-style:italic\">     // SOCK_STREAM, SOCK_DGRAM, etc.</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    struct</span><span style=\"color:#EBDBB2\"> file         </span><span style=\"color:#8EC07C\">*</span><span style=\"color:#EBDBB2\">file</span><span style=\"color:#A89984\">;</span><span style=\"color:#928374;font-style:italic\">    // back-pointer to the fd</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    struct</span><span style=\"color:#EBDBB2\"> sock         </span><span style=\"color:#8EC07C\">*</span><span style=\"color:#EBDBB2\">sk</span><span style=\"color:#A89984\">;</span><span style=\"color:#928374;font-style:italic\">      // pointer to the protocol layer below</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    const</span><span style=\"color:#FE8019\"> struct</span><span style=\"color:#EBDBB2\"> proto_ops </span><span style=\"color:#8EC07C\">*</span><span style=\"color:#EBDBB2\">ops</span><span style=\"color:#A89984\">;</span><span style=\"color:#928374;font-style:italic\">  // send, recv, bind, connect, etc.</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#928374;font-style:italic\">// include/net/sock.h</span></span>\n<span class=\"line\"><span style=\"color:#928374;font-style:italic\">// The protocol layer — where the actual data and state live.</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">struct</span><span style=\"color:#EBDBB2\"> sock </span><span style=\"color:#A89984\">{</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    struct</span><span style=\"color:#EBDBB2\"> sock_common  __sk_common</span><span style=\"color:#A89984\">;</span><span style=\"color:#928374;font-style:italic\">       // family, addresses, port</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    int</span><span style=\"color:#EBDBB2\">                 sk_rcvbuf</span><span style=\"color:#A89984\">;</span><span style=\"color:#928374;font-style:italic\">         // receive buffer size</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    int</span><span style=\"color:#EBDBB2\">                 sk_sndbuf</span><span style=\"color:#A89984\">;</span><span style=\"color:#928374;font-style:italic\">         // send buffer size</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    struct</span><span style=\"color:#EBDBB2\"> sk_buff_head sk_receive_queue</span><span style=\"color:#A89984\">;</span><span style=\"color:#928374;font-style:italic\">  // incoming packets</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    struct</span><span style=\"color:#EBDBB2\"> sk_buff_head sk_write_queue</span><span style=\"color:#A89984\">;</span><span style=\"color:#928374;font-style:italic\">    // outgoing packets</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    int</span><span style=\"color:#EBDBB2\">                 sk_state</span><span style=\"color:#A89984\">;</span><span style=\"color:#928374;font-style:italic\">          // TCP_ESTABLISHED, TCP_LISTEN, etc.</span></span>\n<span class=\"line\"><span style=\"color:#FE8019\">    struct</span><span style=\"color:#EBDBB2\"> proto        </span><span style=\"color:#8EC07C\">*</span><span style=\"color:#EBDBB2\">sk_prot</span><span style=\"color:#A89984\">;</span><span style=\"color:#928374;font-style:italic\">          // protocol impl (TCP, UDP...)</span></span>\n<span class=\"line\"><span style=\"color:#A89984\">};</span></span></code></pre></div><p><code>struct socket</code> is the thin wrapper the kernel exposes through the file descriptor, it is what syscalls like <code>send()</code> and <code>recv()</code> operate on. <code>struct sock</code> is where the real work happens: the protocol family, addresses, and connection state, plus the two queues that do the actual data movement. The comments point to what each field is for. When you <code>send()</code> data, the kernel copies bytes from your userspace buffer into <code>sk_write_queue</code>, the network stack wraps them in headers and pushes them out. On the receiving end, incoming bytes land in <code>sk_receive_queue</code> and wait until you call <code>recv()</code>.</p>\n<p>The file descriptor you hold is just a handle. The real thing lives in kernel space.</p>\n<p>To get a socket file descriptor you run the syscall <code>socket()</code>. You communicate through this socket using <code>send()</code> and <code>recv()</code>. Although you can still use <code>read()</code> and <code>write()</code> for sockets, <code>send()</code> and <code>recv()</code> offer greater control over the data being sent and received.</p>\n<p>There are more than one group of sockets: Internet Sockets, Raw Sockets, Unix Sockets (for IPC), etc. The two types of Internet Sockets you&#39;ll be most in contact with are Stream sockets (<code>SOCK_STREAM</code>) and Datagram sockets (<code>SOCK_DGRAM</code>), sometimes called &quot;connectionless sockets&quot;.</p>\n<p>Don&#39;t worry if TCP, UDP, IP, or ports don&#39;t mean anything yet. We&#39;ll cover all of them in the sections below.</p>\n<h2>Stream Sockets</h2>\n<p>Stream sockets provide a reliable, two-way, connection-oriented communication stream. If you send data in the order &quot;a, b&quot;, it will arrive in that same order, without errors or duplication.</p>\n<p>If you have used <code>telnet</code> or <code>ssh</code> you have used stream sockets, because all your input needs to be delivered in the same order. If you&#39;re reading this in a browser, you&#39;re already using a stream socket. Web browsers use HTTP (Hypertext Transfer Protocol) which uses streams to get pages.</p>\n<p>What ensures the reliability of stream sockets? It is reliable due a protocol called the Transmission Control Protocol, or TCP. TCP&#39;s job is to make sure those bytes actually arrive, in order, without loss or duplication. The stream itself is just bytes moving from A to B. TCP is what makes that journey reliable.</p>\n<p>You might have seen TCP referred to as part of TCP/IP. That&#39;s because TCP doesn&#39;t work alone, it runs on top of IP (Internet Protocol). You&#39;ll read more about IP later.</p>\n<h2>Datagram Sockets</h2>\n<p>When using stream sockets with TCP you need to maintain an open connection between both sides, whereas with Datagram sockets you don&#39;t, and that&#39;s why they are called &quot;connectionless&quot;.</p>\n<p>Datagram sockets use a protocol called UDP (User Datagram Protocol). With this type of socket, you don&#39;t need a connection, you just build the packet and throw it at the receiver. It doesn&#39;t keep track if everything went right.</p>\n<p>Have you ever heard of FTP (File Transfer Protocol)? Its simpler cousin, tftp, does the same thing but over UDP. But wait, how can you reliably transfer a file over an unreliable protocol? The answer is that tftp builds its own reliability on top of UDP: for every packet received, the receiver sends back a small &quot;I got it&quot; reply, called an ACK (acknowledgment). If the sender doesn&#39;t hear back in time, it throws the packet again.</p>\n<p>The reason why such an unreliable protocol is used is, drumroll please... Speed! It is way faster to fire-and-forget than it is to track the connection. When you&#39;re implementing something that receives a ton of packets per second, it doesn&#39;t really matter if you lose one or two.</p>\n<h2>IP Addresses</h2>\n<p>Before we go further, let&#39;s define something we&#39;ve been throwing around: a packet. When you send data over a network, it doesn&#39;t travel as one continuous stream. It gets chopped up into small chunks, each one wrapped with some header information about where it came from and where it&#39;s going. Each chunk is a packet. Keep this definition for now, we&#39;ll elaborate more later.</p>\n<p>Ok, we have packets, sockets, TCP, UDP... but how does any of this know where to go?</p>\n<p>An IP address is a unique identifier for a machine on a network. Just like a house has a postal address so letters can find it, every machine on the internet has an IP address so packets can find it.</p>\n<h3>IPv4</h3>\n<p>Until recently we only had IPv4, the classic format that looks like 192.168.1.1. Each of those four numbers is separated by a dot, goes from 0 to 255, which is a byte, so an IPv4 address is really just a 32-bit integer under the hood. That gives us about 4 billion possible addresses (2^32), which sounded like plenty at the time. It wasn&#39;t.</p>\n<p><img src=\"/assets/ipv4-address-format.png\" alt=\"IPv4 address format: four 8-bit octets shown in both decimal and binary, totaling 32 bits\"></p>\n<h3>IPv6</h3>\n<p>With the rapid and unpredicted expansion of the internet, someone realized that 4 billion addresses wouldn&#39;t be enough and came up with IPv6. Instead of 32 bits, IPv6 uses 128 bits, giving us 2^128 possible addresses. That&#39;s a number so astronomically large it won&#39;t run out in any foreseeable future.</p>\n<p>It looks quite different from IPv4. Eight groups of four hexadecimal digits (remember that 2 hex digits = 1 byte), separated by colons:</p>\n<p><code>2001:0db8:c9d2:0012:0000:0000:0000:0051</code></p>\n<p>That&#39;s a lot to type. IPv6 allows you to compress it by dropping leading zeros and collapsing consecutive groups of zeros into <code>::</code>:</p>\n<p><code>2001:db8:c9d2:12::51</code></p>\n<p>Both represent the exact same address.</p>\n<p><img src=\"/assets/ipv6-address-format.png\" alt=\"IPv6 address format: eight groups of four hexadecimal digits, with leading zeros and consecutive zero groups collapsed into ::\"></p>\n<p>You&#39;ll also see this a lot:</p>\n<p><code>::1</code></p>\n<p>That&#39;s the loopback address, the equivalent of IPv4&#39;s <code>127.0.0.1</code>. It means &quot;this machine itself&quot;. If you ever see it, it means the traffic never left your computer.</p>\n<h2>Ports</h2>\n<p>An IP address gets a packet to the right machine, but your machine is running dozens of programs that all want to use the network. How does the OS know which packet belongs to which program? That&#39;s what ports are for.</p>\n<p>A port is a 16-bit number, from 0 to 65535. When a packet arrives, the OS looks at three things to decide where to send it: the protocol (TCP or UDP), the destination IP, and the destination port. It then hands the packet to whichever program has a socket open on that combination.</p>\n<p>This applies even locally: when you run a server on your own machine and open <code>localhost:3000</code> in your browser, the <code>3000</code> is the port the OS uses to find the right program.</p>\n<p>Ports exist on both sides of a connection. Servers listen on well-known ports so clients know where to connect. Clients also get a port, called an ephemeral port, automatically assigned by the OS for the duration of the connection so replies know where to come back to.</p>\n<p>Some ports have well-known conventional mappings: port 80 for HTTP, port 22 for SSH, port 23 for telnet. Note that port 80 on TCP and port 80 on UDP are separate things. You can see the conventional mappings in <code>/etc/services</code> on any Unix machine, though it&#39;s just a reference, programs aren&#39;t forced to follow it.</p>\n<p>Ports 0 to 1023 are privileged ports. On Unix, binding to them requires root. This is an OS-level convention, not a property of TCP/IP itself, so a privileged process can still take port 80, and a server will fail to start if something else already claimed it.</p>\n<h2>Byte Order</h2>\n<p>To complicate your life, CPU architectures don&#39;t all agree on the order bytes should be stored in memory. Take the two-byte hex number <code>a1b2</code> as an example. Some machines store it as <code>a1</code> followed by <code>b2</code>, the natural reading order. This is called Big-Endian. Others store it reversed, <code>b2</code> followed by <code>a1</code>. This is called Little-Endian. There&#39;s a good chance you&#39;re on an Intel or AMD machine, which is Little-Endian. The internet agreed on Big-Endian as the standard, which is why it&#39;s also called Network Byte Order.</p>\n<p><img src=\"/assets/network-byte-order.png\" alt=\"Big-Endian vs Little-Endian: the value a1b2 stored in memory, with Big-Endian keeping the natural reading order and Little-Endian reversing the bytes\"></p>\n<p>The reason this matters: when you send a number over the network, the bytes have to be in the right order or the other side will read them backwards and get a completely wrong value. Before sending, you convert to Network Byte Order. When receiving, you convert back to whatever your machine uses.</p>\n<p>The C standard library gives you four functions for this:</p>\n<div class=\"code-block\"><span class=\"code-lang\">c</span><pre class=\"shiki gruvbox-dark-hard\" style=\"background-color:#1d2021;color:#ebdbb2\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#FABD2F\">htons</span><span style=\"color:#A89984\">()</span><span style=\"color:#928374;font-style:italic\"> // host to network short (2 bytes)</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">htonl</span><span style=\"color:#A89984\">()</span><span style=\"color:#928374;font-style:italic\"> // host to network long (4 bytes)</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">ntohs</span><span style=\"color:#A89984\">()</span><span style=\"color:#928374;font-style:italic\"> // network to host short (2 bytes)</span></span>\n<span class=\"line\"><span style=\"color:#FABD2F\">ntohl</span><span style=\"color:#A89984\">()</span><span style=\"color:#928374;font-style:italic\"> // network to host long (4 bytes)</span></span></code></pre></div><p>You&#39;ll use these when filling out socket structs. As for why different hardware stores bytes in different orders in the first place, I&#39;ll spare you that rabbit hole. You&#39;re probably bored enough already.</p>\n<h2>DNS</h2>\n<p>We said IP addresses are how machines find each other. But when you type <code>google.com</code> in your browser, you&#39;re not typing an IP address. So how does your machine know where to go? Someone has to translate that name into an IP. But who keeps track of all of this? Is there a giant notebook somewhere mapping every domain name to an IP address?</p>\n<p>Not quite. The internet only understands IP addresses, but humans only remember names, so DNS (Domain Name System) exists as the bridge between the two. Every time you <code>ping google.com</code>, <code>ssh user@myserver.com</code>, or open a website, something is resolving that name to an IP before a single packet is sent. That translation involves a chain of requests bouncing between servers around the world, but from your perspective it just happens, invisibly, in milliseconds.</p>\n<p>A common misconception is that DNS is a browser thing. That resolution happens at the OS level. The browser just asks the OS &quot;what&#39;s the IP for this name?&quot; and the OS handles the rest. <code>ping</code>, <code>curl</code>, <code>ssh</code>, any program that uses a hostname goes through the same process without knowing or caring how it works.</p>\n<p>For that to work, your computer needs to know which DNS server to ask. That&#39;s just a setting, an IP address pointing to a resolver. By default your router hands you this automatically, usually pointing to your ISP&#39;s (Internet Service Provider, the company that provides your internet connection) DNS servers. But you can override it and point to any resolver you want, like <code>8.8.8.8</code> (Google) or <code>1.1.1.1</code> (Cloudflare).</p>\n<p>You can also bypass DNS entirely for specific names by editing <code>/etc/hosts</code> on your own machine. Before asking any DNS server, your OS checks that file first. That&#39;s how <code>localhost</code> resolves to <code>127.0.0.1</code> without any network request involved.</p>\n<h2>No more yapping</h2>\n<p>Well, if you&#39;ve reached here (without scrolling all the way 🧐), that was a lot of theory. You now have the basic vocabulary of networking. I plan to write a follow-up going deeper into TCP and UDP, the network layers they sit on, and actual code to make all of it concrete.</p>\n","date_published":"2026-04-15T00:00:00.000Z"},{"id":"what-is-a-tar-gz","url":"https://viniciusvidal.com/what-is-a-tar-gz","title":"After all, what is a .tar.gz file?","summary":"What a .tar.gz actually is, where its shape comes from, and what the flags in tar -xzvf mean.","content_html":"<p>If you use Linux, you have downloaded a <code>.tar.gz</code> file. For most of my time using Linux I assumed the format was a Linux thing, the same way <code>.exe</code> feels like a Windows thing or <code>.dmg</code> feels like a Mac thing. You see it everywhere on Linux, so you just assume it is what it is and that&#39;s it.</p>\n<p>I was studying git, trying to understand how it stores objects internally, and somewhere along the way I ran into the fact that before git existed, the way people shared code was by packaging the source into a <code>.tar.gz</code> and putting it on a server for other people to download. That had been the standard way Unix software got distributed for decades before GitHub or anything like it.</p>\n<h2>tar was made for tape</h2>\n<p><code>tar</code> is short for <strong>t</strong>ape <strong>ar</strong>chive, and the name is literal. It was written in 1979, on early Unix, to take files from a filesystem and lay them out as a single flat stream that could be written onto a magnetic tape and read back later.</p>\n<p>Networking was rare and slow, and disks were small and expensive. If you needed to back something up or move it to another machine, the usual option was to write it onto a reel of magnetic tape and physically carry the tape over. Tar was the tool that turned a directory into something a tape drive could write.</p>\n<!-- IMAGE: a photo of a 1970s reel-to-reel magnetic tape drive --><p><img src=\"https://live.staticflickr.com/6220/6293910323_29f8b5c27e_b.jpg\" alt=\"1970s magnetic tape drive\"></p>\n<p>A lot of the format makes more sense once you know it was aimed at tape:</p>\n<ul>\n<li><strong>No index or table of contents.</strong> Tape is linear: you can only read forward, you can&#39;t seek. An index would be pointless because you&#39;d have to read past the data to use it. So tar just alternates metadata with content: header, file, header, file, all the way through.</li>\n<li><strong>512-byte blocks.</strong> Tape drives wrote in fixed-size blocks, and 512 bytes was the standard. Tar&#39;s headers are exactly 512 bytes and file contents are padded to the next 512-byte boundary.</li>\n<li><strong>No compression.</strong> Many tape drives had hardware compression built in, so compressing in software first would have been redundant or counterproductive. Tar was designed to hand over raw bytes and let the drive worry about it.</li>\n</ul>\n<h2>From tape to dial-up</h2>\n<p>By the late 80s and through the 90s, the more common reason to encounter a <code>.tar</code> file was downloading software over the internet, and that brought a new constraint: bandwidth.</p>\n<p>A 56k modem gave you about 5 kilobytes per second of real-world throughput. A one-megabyte download took a few minutes, a ten-megabyte one was something you started before going to do something else. Compression made a real, visible difference, and Unix software started getting distributed as tar archives passed through a compressor on the way out the door.</p>\n<p>The first common combination was <code>.tar.Z</code>, which was tar piped through a program called <code>compress</code>. When gzip was released in 1992 as a free, patent-free replacement, <code>.tar.gz</code> quickly took over and became the default shape of &quot;downloadable Unix source code&quot; for the next couple of decades.</p>\n<h2>Two programs, not one</h2>\n<p><code>tar</code> and <code>gzip</code> are not one tool. They are two completely separate programs, written years apart, that get used together so often we write the two extensions back to back and treat the combination as if it were a single format.</p>\n<p><code>tar</code> is an archiver. It takes files and directories and packs them into a single file, preserving Unix metadata like permissions, ownership, timestamps, and symlink targets. It doesn&#39;t compress anything. A <code>.tar</code> file is usually a little larger than the sum of its inputs because tar adds a 512-byte header in front of every file.</p>\n<p><code>gzip</code> is a compressor. It takes a single file and makes it smaller using an algorithm called DEFLATE (the same one used inside zip and PNG). It only works on one file at a time, and it doesn&#39;t know anything about directories at all. If you run <code>gzip mydir/</code> on a directory, it refuses and exits. If you run <code>gzip report.txt</code>, it replaces the file with <code>report.txt.gz</code>. That&#39;s the whole tool. This is exactly why pairing it with tar works so well: tar first flattens a whole directory into a single file, and then gzip has something it can actually compress.</p>\n<p>So <code>.tar.gz</code> describes, in order, what happened to the data: bundled with tar, then compressed with gzip. <code>.tar.bz2</code> is tar plus bzip2. <code>.tar.xz</code> is tar plus xz. <code>.tar.zst</code> is tar plus zstd. Same bundling step, different compressor.</p>\n<!-- IMAGE: tar then gzip pipeline diagram --><p><img src=\"https://commons.wikimedia.org/wiki/Special:FilePath/Targzip.svg\" alt=\"tar then gzip pipeline\"></p>\n<p>This is also why you basically never see <code>.tar.gz</code> on Windows, where <code>.zip</code> is the default. Zip came out of DOS in 1989, a few years before gzip even existed, and made the opposite design choice: bundling and compression are combined into a single format, handled by a single tool. That fits Windows, where the OS hides the shell and &quot;right-click, extract&quot; needs to just work. Unix went the other way because the whole point of Unix was that small composable tools beat one big format.</p>\n<p>You can write the Unix pipeline out by hand:</p>\n<div class=\"code-block\"><pre><code>tar -cf - mydir/ | gzip > mydir.tar.gz</code></pre></div><p>The <code>-</code> tells tar to write to stdout instead of a file. Gzip reads from stdin, compresses what it gets, and the shell redirects the result into <code>mydir.tar.gz</code>. Tar doesn&#39;t know gzip exists, gzip doesn&#39;t know tar exists, and they only meet inside the pipe.</p>\n<p>The benefit of this design is that you can swap the compressor without touching the archiver. When bzip2 came along and compressed better than gzip, nothing about tar had to change. Same with xz and zstd. The archive format stayed the same across decades of new compressors.</p>\n<p>The <code>z</code> in <code>tar -xzvf</code> (the other letters do unrelated things, which I&#39;ll get to in a bit) is a later convenience: at some point tar gained flags for calling the compressor on your behalf. <code>z</code> for gzip, <code>j</code> for bzip2, <code>J</code> for xz. Tar is still shelling out to a separate program, the flag just hides the pipe.</p>\n<p>You still see <code>.tar.gz</code> pretty commonly. GitHub generates one on the fly when you click &quot;Source code (tar.gz)&quot; on a release page, every Docker image layer is a compressed tar archive, npm packages are <code>.tgz</code>, Python source distributions are <code>.tar.gz</code>. And gzip on its own (without tar) is even more everywhere. HTTP servers compress responses before sending them, and gzip is one of the options they can use, alongside others like brotli or zstd. This page actually got to your browser compressed with the same gzip from the same 1992 program. No tar in that case, because there&#39;s only one file in flight, just the compression half of <code>.tar.gz</code> doing its job on its own.</p>\n<h2>So, after all, what is a .tar.gz?</h2>\n<p>After all this context, the answer is a lot less mysterious. A <code>.tar.gz</code> is a tar archive (a directory flattened into a single stream, in a format originally shaped by magnetic tape) that has then been passed through gzip and compressed with an algorithm called DEFLATE. The format exists in two parts because Unix split bundling and compression into separate tools, so the compressor could be swapped out independently of the archiver.</p>\n<h2>Appendix: what the flags in <code>tar -xzvf</code> actually mean</h2>\n<p>I learned <code>tar -xzvf</code> as a single chunk long before I ever read the flags:</p>\n<ul>\n<li><code>x</code> means <strong>extract</strong>. The other common one is <code>c</code> for create, which you&#39;d use to make an archive instead of unpacking one.</li>\n<li><code>z</code> means <strong>run it through gzip</strong>. For <code>.tar.bz2</code> you&#39;d use <code>j</code>, for <code>.tar.xz</code> you&#39;d use <code>J</code>.</li>\n<li><code>v</code> means <strong>verbose</strong>, so tar prints the name of each file as it processes it. Purely cosmetic, you can drop it.</li>\n<li><code>f</code> means <strong>the next argument is the filename</strong>. Without <code>f</code>, tar would try to read from or write to a tape device, which is another sign of where the tool came from. The default destination really is a tape drive.</li>\n</ul>\n<p>So <code>tar -xzvf something.tar.gz</code> reads as: extract, decompress with gzip, be verbose, the file is <code>something.tar.gz</code>.</p>\n","date_published":"2026-04-08T00:00:00.000Z"}]}