<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Systems Research</title><link>https://systemsresearch.io/</link><description>Recent content on Systems Research</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Thu, 10 Oct 2024 09:17:21 +0000</lastBuildDate><atom:link href="https://systemsresearch.io/index.xml" rel="self" type="application/rss+xml"/><item><title>The Role of Aligning Memory Addresses With Cachelines</title><link>https://systemsresearch.io/posts/5415d47e0/</link><pubDate>Thu, 10 Oct 2024 09:17:21 +0000</pubDate><guid>https://systemsresearch.io/posts/5415d47e0/</guid><description>&lt;p>There are three common reasons for aligning memory addresses with cachelines:&lt;/p>
&lt;ol>
&lt;li>Improving Performance&lt;/li>
&lt;li>Maintaining Atomicity&lt;/li>
&lt;li>Preventing False Sharing&lt;/li>
&lt;/ol>
&lt;p>The third reason has been widely discussed online, so we will not cover it here. Instead, we will focus on the first two reasons.&lt;/p>
&lt;h1 id="cross-line-performance">Cross-Line Performance&lt;/h1>
&lt;p>Let&amp;rsquo;s start with the following program. The program first allocates an 8K memory space. It&amp;rsquo;s important to note that the starting address of the space provided by &lt;code>malloc&lt;/code> is aligned to 8 or 16 bytes. We need to manually align it to 64 bytes for easier manipulation. The allocated space is 8K, which is the size of two pages. Regardless of the starting address, this space will contain a page boundary, which is a good alignment point and useful for testing. The subsequent operations simply involve repeatedly writing data and measuring the total time to calculate the average time per operation.&lt;/p></description><content>&lt;p>There are three common reasons for aligning memory addresses with cachelines:&lt;/p>
&lt;ol>
&lt;li>Improving Performance&lt;/li>
&lt;li>Maintaining Atomicity&lt;/li>
&lt;li>Preventing False Sharing&lt;/li>
&lt;/ol>
&lt;p>The third reason has been widely discussed online, so we will not cover it here. Instead, we will focus on the first two reasons.&lt;/p>
&lt;h1 id="cross-line-performance">Cross-Line Performance&lt;/h1>
&lt;p>Let&amp;rsquo;s start with the following program. The program first allocates an 8K memory space. It&amp;rsquo;s important to note that the starting address of the space provided by &lt;code>malloc&lt;/code> is aligned to 8 or 16 bytes. We need to manually align it to 64 bytes for easier manipulation. The allocated space is 8K, which is the size of two pages. Regardless of the starting address, this space will contain a page boundary, which is a good alignment point and useful for testing. The subsequent operations simply involve repeatedly writing data and measuring the total time to calculate the average time per operation.&lt;/p>
&lt;p>The program accepts an input parameter &lt;code>offset&lt;/code>, which specifies the starting write position from &lt;code>buf_pageend+offset&lt;/code>. Thus, this parameter controls the starting position of the write operation.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;stdio.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;time.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;string.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;stdlib.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define BUF_SIZE 8192
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define ROUND 100000000UL
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> argc, &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">**&lt;/span>argv)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>buf, &lt;span style="color:#f92672">*&lt;/span>buf_newaddr, &lt;span style="color:#f92672">*&lt;/span>buf_pageend;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span> i &lt;span style="color:#a6e22e">__attribute__&lt;/span>((&lt;span style="color:#a6e22e">aligned&lt;/span>(&lt;span style="color:#ae81ff">64&lt;/span>)));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span> offset &lt;span style="color:#a6e22e">__attribute__&lt;/span>((&lt;span style="color:#a6e22e">aligned&lt;/span>(&lt;span style="color:#ae81ff">64&lt;/span>)));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">struct&lt;/span> timespec start&lt;span style="color:#f92672">=&lt;/span>{&lt;span style="color:#ae81ff">0&lt;/span>,&lt;span style="color:#ae81ff">0&lt;/span>}, end&lt;span style="color:#f92672">=&lt;/span>{&lt;span style="color:#ae81ff">0&lt;/span>,&lt;span style="color:#ae81ff">0&lt;/span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">double&lt;/span> start_ns, end_ns;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (argc &lt;span style="color:#f92672">!=&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;missing args&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">exit&lt;/span>(&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> offset &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">atoi&lt;/span>(argv[&lt;span style="color:#ae81ff">1&lt;/span>]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>again:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> buf &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#f92672">*&lt;/span>)&lt;span style="color:#a6e22e">malloc&lt;/span>(BUF_SIZE);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">/* Align to page boundary */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> buf_pageend &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#f92672">*&lt;/span>)((&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span>)(buf &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">4095&lt;/span>) &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">0xfffffffffffff000UL&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (buf_pageend &lt;span style="color:#f92672">-&lt;/span> buf &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">1024&lt;/span>) { &lt;span style="color:#75715e">// Ensure sufficient space before the pageend in case offset is negative
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">goto&lt;/span> again; &lt;span style="color:#75715e">// No free, continue allocation, otherwise the same memory block may be allocated
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">memset&lt;/span>(buf, &lt;span style="color:#ae81ff">0&lt;/span>, BUF_SIZE); &lt;span style="color:#75715e">// Pre-allocate
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;&amp;amp;i = %lx, &amp;amp;offset=%lx&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>, &lt;span style="color:#f92672">&amp;amp;&lt;/span>i, &lt;span style="color:#f92672">&amp;amp;&lt;/span>offset);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">clock_gettime&lt;/span>(CLOCK_MONOTONIC, &lt;span style="color:#f92672">&amp;amp;&lt;/span>start);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> (i &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; i &lt;span style="color:#f92672">&amp;lt;&lt;/span> ROUND; i&lt;span style="color:#f92672">++&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">*&lt;/span>((&lt;span style="color:#66d9ef">volatile&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span> &lt;span style="color:#f92672">*&lt;/span>)(buf_pageend &lt;span style="color:#f92672">+&lt;/span> offset)) &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; &lt;span style="color:#75715e">// Write 8 bytes
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">clock_gettime&lt;/span>(CLOCK_MONOTONIC, &lt;span style="color:#f92672">&amp;amp;&lt;/span>end);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> start_ns &lt;span style="color:#f92672">=&lt;/span> start.tv_sec &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">1000000000&lt;/span> &lt;span style="color:#f92672">+&lt;/span> start.tv_nsec;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> end_ns &lt;span style="color:#f92672">=&lt;/span> end.tv_sec &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">1000000000&lt;/span> &lt;span style="color:#f92672">+&lt;/span> end.tv_nsec;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;ns per store: %lf&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>, (end_ns &lt;span style="color:#f92672">-&lt;/span> start_ns)&lt;span style="color:#f92672">/&lt;/span>ROUND);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now that we&amp;rsquo;ve gone through the program, let&amp;rsquo;s start testing:&lt;/p>
&lt;p>On my system, the cacheline size of the L1 cache is 64 bytes:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ getconf LEVEL1_DCACHE_LINESIZE
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">64&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When memory is loaded into the cache, it is loaded as a whole cacheline, aligned to a 64-byte boundary. It won&amp;rsquo;t load in smaller granularity. For example, if you access address &lt;code>0x2&lt;/code>, what actually gets loaded is the 64-byte block from &lt;code>0x0&lt;/code> to &lt;code>0x3F&lt;/code>.&lt;/p>
&lt;p>In our program, &lt;code>buf_pageend&lt;/code> points to the page boundary, which is aligned to 4K and, therefore, also aligned to 64B (cacheline-aligned). So, when we write to the range &lt;code>[buf_pageend, buf_pageend+63]&lt;/code>, no matter where we write within this range, we are only accessing one cacheline, and the speed will be the fastest.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ ./a.out 0; ./a.out 1; ./a.out &lt;span style="color:#ae81ff">56&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;amp;i &lt;span style="color:#f92672">=&lt;/span> 7ffccd268780, &amp;amp;offset&lt;span style="color:#f92672">=&lt;/span>7ffccd2687c0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ns per store: 0.429188
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;amp;i &lt;span style="color:#f92672">=&lt;/span> 7ffc0f704140, &amp;amp;offset&lt;span style="color:#f92672">=&lt;/span>7ffc0f704180
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ns per store: 0.436388
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;amp;i &lt;span style="color:#f92672">=&lt;/span> 7ffce5fedbc0, &amp;amp;offset&lt;span style="color:#f92672">=&lt;/span>7ffce5fedc00
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ns per store: 0.434455
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>However, if we pass in the parameter &lt;code>57&lt;/code>, since we are writing 8 bytes, the write range will be &lt;code>[buf_pageend+57, buf_pageend+64]&lt;/code>. Notice that the last byte overflows into the next cacheline, so the speed decreases. The same applies if you pass in values from 58 to 63.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ ./a.out 57; ./a.out 58; ./a.out &lt;span style="color:#ae81ff">63&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;amp;i &lt;span style="color:#f92672">=&lt;/span> 7fff250ea3c0, &amp;amp;offset&lt;span style="color:#f92672">=&lt;/span>7fff250ea400
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ns per store: 0.847032
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;amp;i &lt;span style="color:#f92672">=&lt;/span> 7ffe82952d40, &amp;amp;offset&lt;span style="color:#f92672">=&lt;/span>7ffe82952d80
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ns per store: 0.851936
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;amp;i &lt;span style="color:#f92672">=&lt;/span> 7fff046c6f00, &amp;amp;offset&lt;span style="color:#f92672">=&lt;/span>7fff046c6f40
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ns per store: 0.844823
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you pass in &lt;code>64&lt;/code>, you&amp;rsquo;re back to reading and writing within a single cacheline (&lt;code>[buf_pageend+64, buf_pageend+127]&lt;/code>), and the speed increases again.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ ./a.out &lt;span style="color:#ae81ff">64&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;amp;i &lt;span style="color:#f92672">=&lt;/span> 7ffd12741400, &amp;amp;offset&lt;span style="color:#f92672">=&lt;/span>7ffd12741440
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ns per store: 0.438385
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Why does cross-line access slow down? This is because writing once requires accessing two cachelines to complete the operation, effectively doubling the time.&lt;/p>
&lt;p>If you not only cross cachelines but also cross pages, the speed will be even slower. For example, if the first 4 bytes of the 8-byte write fall at the end of one page and the last 4 bytes fall at the beginning of the next page, the two pages (called virtual pages) typically map to non-contiguous physical pages. The x86 architecture uses VIPT (Virtually Indexed, Physically Tagged), and when the CPU looks up a cacheline, it must resolve the virtual address to a physical address when matching the tag. Since our data crosses cachelines, it first resolves the virtual address and writes the first cacheline, then resolves the virtual address again to write the second cacheline. This address resolution process is slow, and if a TLB miss occurs, it becomes even slower. So, cross-page writes not only incur the penalty of crossing cachelines but also the penalty of crossing pages, resulting in a significant performance degradation.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ ./a.out -1; ./a.out -7
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;amp;i &lt;span style="color:#f92672">=&lt;/span> 7fff143b3400, &amp;amp;offset&lt;span style="color:#f92672">=&lt;/span>7fff143b3440
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ns per store: 10.084426
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;amp;i &lt;span style="color:#f92672">=&lt;/span> 7ffcfb6bb8c0, &amp;amp;offset&lt;span style="color:#f92672">=&lt;/span>7ffcfb6bb900
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ns per store: 10.089472
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Of course, if you pass in &lt;code>-8&lt;/code>, the write will fall entirely within the previous cacheline, and the speed will be fast again.&lt;/p>
&lt;h1 id="atomicity">Atomicity&lt;/h1>
&lt;p>We know that the MESI protocol in cache coherence ensures data consistency when multiple CPUs read and write to the same cacheline, meaning that a single cacheline&amp;rsquo;s reads and writes are inherently atomic. But how does cross-line access break atomicity? Let&amp;rsquo;s look at the following program.&lt;/p>
&lt;p>The main process creates 80 child processes. All parent and child processes share a memory region. Forty of the child processes repeatedly write 8 bytes of all &lt;code>f&lt;/code>s to an address, while the other forty child processes repeatedly write 8 bytes of all &lt;code>0&lt;/code>s. The main process repeatedly reads these 8 bytes. If atomicity is preserved, the main process should see either 8 bytes of all &lt;code>0&lt;/code>s or 8 bytes of all &lt;code>f&lt;/code>s, but never any other value. If other values appear, atomicity has been violated.&lt;/p>
&lt;p>The program accepts an input parameter &lt;code>offset&lt;/code>, which specifies the starting write position from &lt;code>buf+offset&lt;/code>, allowing us to control the starting position of the write operation.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;stdio.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;time.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;string.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;stdlib.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;string.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;unistd.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;sys/mman.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;sys/wait.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define BUF_SIZE 1024
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> argc, &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">**&lt;/span>argv) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>buf;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> pid, status;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> count &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">80&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> i,j;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> buf_realsize;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> write_count &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">10000000&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> read_count &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">30000000&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span> data;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> offset;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (argc &lt;span style="color:#f92672">!=&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;missing args&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">exit&lt;/span>(&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> offset &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">atoi&lt;/span>(argv[&lt;span style="color:#ae81ff">1&lt;/span>]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> buf &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">mmap&lt;/span>(NULL, BUF_SIZE, PROT_READ &lt;span style="color:#f92672">|&lt;/span> PROT_WRITE, MAP_SHARED &lt;span style="color:#f92672">|&lt;/span> MAP_ANONYMOUS, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">memset&lt;/span>(buf, &lt;span style="color:#ae81ff">0xdd&lt;/span>, BUF_SIZE);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> buf &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#f92672">*&lt;/span>)((&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span>)(buf &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">63&lt;/span>) &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">0xffffffffffffffc0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> (i &lt;span style="color:#f92672">=&lt;/span> count; i; i&lt;span style="color:#f92672">--&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pid &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">fork&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (pid &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> child_pid &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">getpid&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (child_pid &lt;span style="color:#f92672">%&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> data &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0xffffffffffffffff&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> data &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0x0000000000000000&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> (j &lt;span style="color:#f92672">=&lt;/span> write_count; j; j&lt;span style="color:#f92672">--&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">*&lt;/span>(&lt;span style="color:#66d9ef">volatile&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span> &lt;span style="color:#f92672">*&lt;/span>)(buf &lt;span style="color:#f92672">+&lt;/span> offset) &lt;span style="color:#f92672">=&lt;/span> data;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">exit&lt;/span>(&lt;span style="color:#ae81ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// parent
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> (j &lt;span style="color:#f92672">=&lt;/span> read_count; j; j&lt;span style="color:#f92672">--&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> data &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">*&lt;/span>(&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span> &lt;span style="color:#f92672">*&lt;/span>)(buf &lt;span style="color:#f92672">+&lt;/span> offset);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (data &lt;span style="color:#f92672">!=&lt;/span> &lt;span style="color:#ae81ff">0xffffffffffffffff&lt;/span> &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> data &lt;span style="color:#f92672">!=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;non-atomic: %016lx&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>, data);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> ((pid &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">wait&lt;/span>(&lt;span style="color:#f92672">&amp;amp;&lt;/span>status)) &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you pass in &lt;code>offset&lt;/code> values like &lt;code>0, 1, ..., 56&lt;/code>, the 8-byte writes will clearly fall within the range &lt;code>[buf, buf+63]&lt;/code>, i.e., within a single cacheline. There is no issue with atomicity.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ ./a.out 0; ./a.out 10; ./a.out 56;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As we can see, there is no output, indicating that no non-atomic situations occurred.&lt;/p>
&lt;p>However, if you pass in &lt;code>57, 58, ..., 63&lt;/code>, the 8-byte write will span two cachelines. There is no atomicity guarantee between writing to the first and second cachelines. As a result, the 80 child processes can interfere with each other. For example, if the offset is &lt;code>60&lt;/code>, half of the data is written to one cacheline and the other half to another. The main process might see a mix of data, like &lt;code>0xffffffff00000000&lt;/code> or &lt;code>0x00000000ffffffff&lt;/code>, from two different processes. Of course, all &lt;code>0&lt;/code>s or all &lt;code>f&lt;/code>s are also possible, but you wouldn&amp;rsquo;t be able to tell just by looking.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ ./a.out 60;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>non-atomic: ffffffff00000000
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>non-atomic: 00000000ffffffff
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>non-atomic: 00000000ffffffff
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>non-atomic: 00000000ffffffff
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>non-atomic: ffffffff00000000
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>non-atomic: 00000000ffffffff
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>non-atomic: ffffffff00000000
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>non-atomic: ffffffff00000000
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>non-atomic: ffffffff00000000
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>non-atomic: ffffffff00000000
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>non-atomic: ffffffff00000000
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>non-atomic: ffffffff00000000
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>non-atomic: ffffffff00000000
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>non-atomic: ffffffff00000000
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>non-atomic: ffffffff00000000
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To avoid such situations, you would need to use the &lt;code>lock&lt;/code> prefix at the assembly level. However, the best solution is simply to ensure alignment.&lt;/p>
&lt;h1 id="compiler-optimization">Compiler Optimization&lt;/h1>
&lt;p>The code compiled with &lt;code>-O0&lt;/code> is unoptimized and contains many performance bottlenecks, such as instruction address alignment, branch prediction failures, pipeline bubbles, store-forwarding stalls, etc. These factors introduce significant overhead and obscure the performance differences of cross-line access. Therefore, the above program must be compiled with &lt;code>-O2&lt;/code> to eliminate these irrelevant performance bottlenecks.&lt;/p>
&lt;p>Additionally, with &lt;code>-O2&lt;/code> optimizations, the compiler will unroll constant loops and perform dead code elimination, meaning intermediate assignment statements may be removed or reduced to a single assignment. Therefore, we write assignment statements in the following way:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">*&lt;/span>(&lt;span style="color:#66d9ef">volatile&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span> &lt;span style="color:#f92672">*&lt;/span>)(buf &lt;span style="color:#f92672">+&lt;/span> offset) &lt;span style="color:#f92672">=&lt;/span> xxx;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>volatile&lt;/code> keyword tells the compiler not to optimize away this statement, even if the same value is written multiple times. Whatever I write, the compiler must assign exactly as specified.&lt;/p></content></item><item><title>Revisiting the Volatile Keyword in C</title><link>https://systemsresearch.io/posts/fb215d593/</link><pubDate>Thu, 10 Oct 2024 08:58:57 +0000</pubDate><guid>https://systemsresearch.io/posts/fb215d593/</guid><description>&lt;h1 id="the-most-common-usage">The Most Common Usage&lt;/h1>
&lt;p>When a variable is declared as &lt;code>volatile&lt;/code>, it tells the compiler that even though the current code being compiled does not modify this variable, the corresponding memory data might be modified for other reasons. There are many possible reasons for this, such as the memory location corresponding to the variable being mapped to a peripheral port using the &lt;code>memory mapped I/O&lt;/code> mechanism. Essentially, we are accessing a hardware register, and the value of that register can change independently of the program.&lt;/p></description><content>&lt;h1 id="the-most-common-usage">The Most Common Usage&lt;/h1>
&lt;p>When a variable is declared as &lt;code>volatile&lt;/code>, it tells the compiler that even though the current code being compiled does not modify this variable, the corresponding memory data might be modified for other reasons. There are many possible reasons for this, such as the memory location corresponding to the variable being mapped to a peripheral port using the &lt;code>memory mapped I/O&lt;/code> mechanism. Essentially, we are accessing a hardware register, and the value of that register can change independently of the program.&lt;/p>
&lt;p>So, why do we need to tell the compiler about this? Because it ensures that during assembly code generation, every access to this variable will involve reading the memory location to get the most recent value. On the other hand, without &lt;code>volatile&lt;/code>, the compiler might load the variable into a register for efficiency, and later accesses to the variable would read from the register without checking the memory again, causing the code to use stale data from the register. Commonly used functions like &lt;code>ioread&lt;/code> encapsulate &lt;code>volatile&lt;/code> operations to ensure the latest data is read. You can refer to &lt;code>build_mmio_read&lt;/code> for specific definitions.&lt;/p>
&lt;p>However, aside from this &lt;code>memory mapped I/O&lt;/code> scenario and a few &lt;a href="https://www.kernel.org/doc/html/latest/process/volatile-considered-harmful.html">special cases&lt;/a>, if a variable may be accessed concurrently by multiple processes, the &lt;code>volatile&lt;/code> keyword should not be used to ensure that each process sees the latest value of the variable. The correct approach is to use locks to protect the variable. Once the lock is acquired, the protected variable only needs to be read from memory once and stored in a register, and subsequent accesses can use the register value, which is more efficient. Since the lock mechanism ensures no other process can modify the variable before we exit the critical section, the data in the register remains valid. Declaring the protected variable as &lt;code>volatile&lt;/code> in this case would be unnecessary and could even hinder optimization within the critical section, forcing the compiler to read from memory every time, which is clearly inefficient.&lt;/p>
&lt;h1 id="preventing-instruction-reordering">Preventing Instruction Reordering&lt;/h1>
&lt;p>Another use case for &lt;code>volatile&lt;/code> involves the &lt;code>READ_ONCE/WRITE_ONCE&lt;/code> macros. Kernel comments mention that these macros prevent compiler reordering:&lt;/p>
&lt;pre tabindex="0">&lt;code>The compiler is also forbidden from reordering successive instances of
READ_ONCE and WRITE_ONCE
&lt;/code>&lt;/pre>&lt;p>We&amp;rsquo;ll analyze the &lt;code>READ_ONCE&lt;/code> macro in detail below.&lt;/p>
&lt;p>From the kernel&amp;rsquo;s definition of this macro, its essence is that it uses the &lt;code>volatile&lt;/code> keyword to modify the type of the variable. At first glance, it doesn&amp;rsquo;t seem like it could act as a compiler barrier to prevent compiler reordering.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define READ_ONCE(x) \
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">({\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> compiletime_assert_rwonce_type(x); \
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> __READ_ONCE(x); \
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">})
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define __READ_ONCE(x) (*(const volatile __unqual_scalar_typeof(x) *)&amp;amp;(x))
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So, let&amp;rsquo;s test it out.&lt;/p>
&lt;p>First, a piece of C code:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> a, b;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> i, j;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">foo&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> a &lt;span style="color:#f92672">=&lt;/span> i;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> b &lt;span style="color:#f92672">=&lt;/span> j &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">16&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Using &lt;code>gcc -O2 example.c -S&lt;/code> generates the following assembly code:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-asm" data-lang="asm">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">movl&lt;/span> &lt;span style="color:#66d9ef">j&lt;/span>(%rip), %edx &lt;span style="color:#75715e">// read j
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">movl&lt;/span> &lt;span style="color:#66d9ef">i&lt;/span>(%rip), %eax &lt;span style="color:#75715e">// read i
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">testl&lt;/span> %edx, %edx
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">movl&lt;/span> %eax, &lt;span style="color:#66d9ef">a&lt;/span>(%rip)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">leal&lt;/span> &lt;span style="color:#ae81ff">15&lt;/span>(%rdx), %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">cmovns&lt;/span> %edx, %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">sarl&lt;/span> &lt;span style="color:#66d9ef">$4&lt;/span>, %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">movl&lt;/span> %eax, &lt;span style="color:#66d9ef">b&lt;/span>(%rip)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Clearly, the read order of &lt;code>i&lt;/code> and &lt;code>j&lt;/code> is reversed compared to the C code. To prevent this optimization, let&amp;rsquo;s first try using a compiler barrier, &lt;code>barrier()&lt;/code>, and see the effect.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define barrier() __asm__ __volatile__(&amp;#34;&amp;#34;: : :&amp;#34;memory&amp;#34;)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> a, b;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> i, j;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">foo&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> a &lt;span style="color:#f92672">=&lt;/span> i;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">barrier&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> b &lt;span style="color:#f92672">=&lt;/span> j &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">16&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The assembly code now looks like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-asm" data-lang="asm">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">movl&lt;/span> &lt;span style="color:#66d9ef">i&lt;/span>(%rip), %eax &lt;span style="color:#75715e">// read i
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">movl&lt;/span> %eax, &lt;span style="color:#66d9ef">a&lt;/span>(%rip)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// Barrier here
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">movl&lt;/span> &lt;span style="color:#66d9ef">j&lt;/span>(%rip), %edx &lt;span style="color:#75715e">// read j
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">testl&lt;/span> %edx, %edx
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">leal&lt;/span> &lt;span style="color:#ae81ff">15&lt;/span>(%rdx), %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">cmovns&lt;/span> %edx, %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">sarl&lt;/span> &lt;span style="color:#66d9ef">$4&lt;/span>, %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">movl&lt;/span> %eax, &lt;span style="color:#66d9ef">b&lt;/span>(%rip)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Clearly, the &lt;code>barrier()&lt;/code> works effectively. It informs the compiler that the code before the barrier and the code after the barrier belong to two different &amp;ldquo;worlds.&amp;rdquo; The instructions before the barrier cannot be reordered with those after, and vice versa. This means no instruction reordering can cross the barrier.&lt;/p>
&lt;p>After witnessing the effect of the compiler barrier, let&amp;rsquo;s see if &lt;code>volatile&lt;/code> can also act as a compiler barrier.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define __READ_ONCE(x) (*(const volatile int *)&amp;amp;(x))
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> a, b;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> i, j;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">foo&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> a &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">__READ_ONCE&lt;/span>(i);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> b &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">__READ_ONCE&lt;/span>(j) &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">16&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The resulting assembly code is:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-asm" data-lang="asm">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">movl&lt;/span> &lt;span style="color:#66d9ef">i&lt;/span>(%rip), %eax &lt;span style="color:#75715e">// read i
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">movl&lt;/span> &lt;span style="color:#66d9ef">j&lt;/span>(%rip), %edx &lt;span style="color:#75715e">// read j
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">movl&lt;/span> %eax, &lt;span style="color:#66d9ef">a&lt;/span>(%rip) &lt;span style="color:#75715e">// write a
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">testl&lt;/span> %edx, %edx
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">leal&lt;/span> &lt;span style="color:#ae81ff">15&lt;/span>(%rdx), %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">cmovns&lt;/span> %edx, %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">sarl&lt;/span> &lt;span style="color:#66d9ef">$4&lt;/span>, %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">movl&lt;/span> %eax, &lt;span style="color:#66d9ef">b&lt;/span>(%rip) &lt;span style="color:#75715e">// write b
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, the read order of &lt;code>i&lt;/code> and &lt;code>j&lt;/code> is preserved. However, notice that while &lt;code>volatile&lt;/code> can ensure the order of some instructions, it is still not a compiler barrier. Using &lt;code>__READ_ONCE&lt;/code>, we can only guarantee the read order of &lt;code>i&lt;/code> and &lt;code>j&lt;/code>, but not the write order of &lt;code>a&lt;/code> and &lt;code>b&lt;/code>. Theoretically, the compiler could generate the following code:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-asm" data-lang="asm">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">movl&lt;/span> &lt;span style="color:#66d9ef">i&lt;/span>(%rip), %eax &lt;span style="color:#75715e">// read i
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">movl&lt;/span> &lt;span style="color:#66d9ef">j&lt;/span>(%rip), %edx &lt;span style="color:#75715e">// read j
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">movl&lt;/span> %eax, %ecx &lt;span style="color:#75715e">// temporarily store i in ecx
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">testl&lt;/span> %edx, %edx
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">leal&lt;/span> &lt;span style="color:#ae81ff">15&lt;/span>(%rdx), %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">cmovns&lt;/span> %edx, %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">sarl&lt;/span> &lt;span style="color:#66d9ef">$4&lt;/span>, %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">movl&lt;/span> %eax, &lt;span style="color:#66d9ef">b&lt;/span>(%rip) &lt;span style="color:#75715e">// write b
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">movl&lt;/span> %ecx, &lt;span style="color:#66d9ef">a&lt;/span>(%rip) &lt;span style="color:#75715e">// write i to a
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Although this situation is difficult to construct, it&amp;rsquo;s theoretically possible.&lt;/p>
&lt;p>The reason why the compiler enforces this behavior with &lt;code>volatile&lt;/code> is due to the following C standard:&lt;/p>
&lt;pre tabindex="0">&lt;code>The least requirements on a conforming implementation are:
At sequence points, volatile objects are stable in the sense that previous accesses are complete and subsequent accesses have not yet occurred.
...
...
...
The following are the sequence points described in 5.1.2.3:
The end of a full expression: an initializer (6.7.8); the expression in an expression
statement (6.8.3); the controlling expression of a selection statement (if or switch)
(6.8.4); the controlling expression of a while or do statement (6.8.5); each of the
expressions of a for statement (6.8.5.3); the expression in a return statement
(6.8.6.4).
&lt;/code>&lt;/pre>&lt;p>Here, the concept of a &lt;code>sequence point&lt;/code> is introduced. In simple terms, a &lt;code>sequence point&lt;/code> ensures that the effects of expressions before it are completed and that subsequent accesses have not yet started. In plain language, a &lt;code>sequence point&lt;/code> acts as a boundary, and before accessing any &lt;code>volatile&lt;/code> variables after the point, all prior accesses must be completed. According to this standard, a semicolon (&lt;code>;&lt;/code>) is considered a &lt;code>sequence point&lt;/code>, so the &lt;code>a = __READ_ONCE(i)&lt;/code> and &lt;code>b = __READ_ONCE(j) / 16&lt;/code> statements are separated by a &lt;code>sequence point&lt;/code>, ensuring that &lt;code>i&lt;/code> is accessed before &lt;code>j&lt;/code>.&lt;/p>
&lt;p>However, the compiler only guarantees the read order of &lt;code>volatile&lt;/code> variables relative to each other. The read order between &lt;code>non-volatile&lt;/code> and &lt;code>volatile&lt;/code> variables may still be reordered.&lt;/p>
&lt;p>For example, if we remove the &lt;code>__READ_ONCE&lt;/code> from &lt;code>j&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define __READ_ONCE(x) (*(const volatile int *)&amp;amp;(x))
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> a, b;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> i, j;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">foo&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> a &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">__READ_ONCE&lt;/span>(i);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> b &lt;span style="color:#f92672">=&lt;/span> j &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">16&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The generated assembly would be:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-asm" data-lang="asm">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">movl&lt;/span> &lt;span style="color:#66d9ef">j&lt;/span>(%rip), %edx &lt;span style="color:#75715e">// read j
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">movl&lt;/span> &lt;span style="color:#66d9ef">i&lt;/span>(%rip), %eax &lt;span style="color:#75715e">// read i
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">testl&lt;/span> %edx, %edx
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">movl&lt;/span> %eax, &lt;span style="color:#66d9ef">a&lt;/span>(%rip)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">leal&lt;/span> &lt;span style="color:#ae81ff">15&lt;/span>(%rdx), %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">cmovns&lt;/span> %edx, %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">sarl&lt;/span> &lt;span style="color:#66d9ef">$4&lt;/span>, %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">movl&lt;/span> %eax, &lt;span style="color:#66d9ef">b&lt;/span>(%rip)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As you can see, the read order of &lt;code>i&lt;/code> and &lt;code>j&lt;/code> has been reversed again.&lt;/p>
&lt;p>At this point, we&amp;rsquo;ve thoroughly analyzed the role of &lt;code>READ_ONCE&lt;/code> (i.e., &lt;code>volatile&lt;/code>) in variable reads. It can ensure that variables are strictly read in the order specified by the code. Similarly, &lt;code>WRITE_ONCE&lt;/code> ensures the write order of variables.&lt;/p>
&lt;p>Now, what happens if &lt;code>READ_ONCE&lt;/code> and &lt;code>WRITE_ONCE&lt;/code> are used together? According to the C standard, the ordering guarantees are not limited to read-read or write-write operations, so read-write order will also be preserved. Let&amp;rsquo;s look at an example:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> a, b;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> i;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">foo&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> a &lt;span style="color:#f92672">=&lt;/span> i &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">16&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> b &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The assembly code for this function is:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-asm" data-lang="asm">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">movl&lt;/span> &lt;span style="color:#66d9ef">$0&lt;/span>, &lt;span style="color:#66d9ef">b&lt;/span>(%rip) &lt;span style="color:#75715e">// write b
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">movl&lt;/span> &lt;span style="color:#66d9ef">i&lt;/span>(%rip), %edx &lt;span style="color:#75715e">// read i
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">testl&lt;/span> %edx, %edx
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">leal&lt;/span> &lt;span style="color:#ae81ff">15&lt;/span>(%rdx), %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">cmovns&lt;/span> %edx, %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">sarl&lt;/span> &lt;span style="color:#66d9ef">$4&lt;/span>, %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">movl&lt;/span> %eax, &lt;span style="color:#66d9ef">a&lt;/span>(%rip) &lt;span style="color:#75715e">// write a
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As you can see, the read of &lt;code>i&lt;/code>, the write to &lt;code>a&lt;/code>, and the write to &lt;code>b&lt;/code> are completely out of order.&lt;/p>
&lt;p>Now, let&amp;rsquo;s use &lt;code>volatile&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define __READ_ONCE(x) (*(const volatile int *)&amp;amp;(x))
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define __WRITE_ONCE(x, val) do {*(volatile typeof(x) *)&amp;amp;(x) = (val);} while(0)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> a, b;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> i;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">foo&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> a &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">__READ_ONCE&lt;/span>(i) &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">16&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">__WRITE_ONCE&lt;/span>(b, &lt;span style="color:#ae81ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The resulting assembly code is:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-asm" data-lang="asm">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">movl&lt;/span> &lt;span style="color:#66d9ef">i&lt;/span>(%rip), %edx &lt;span style="color:#75715e">// read i
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">movl&lt;/span> &lt;span style="color:#66d9ef">$0&lt;/span>, &lt;span style="color:#66d9ef">b&lt;/span>(%rip) &lt;span style="color:#75715e">// write b
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">testl&lt;/span> %edx, %edx
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">leal&lt;/span> &lt;span style="color:#ae81ff">15&lt;/span>(%rdx), %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">cmovns&lt;/span> %edx, %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">sarl&lt;/span> &lt;span style="color:#66d9ef">$4&lt;/span>, %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">movl&lt;/span> %eax, &lt;span style="color:#66d9ef">a&lt;/span>(%rip) &lt;span style="color:#75715e">// write a
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, the read of &lt;code>i&lt;/code> and the write to &lt;code>b&lt;/code> follow the exact order of the C code, indicating that &lt;code>volatile&lt;/code> has taken effect. But the write to &lt;code>a&lt;/code> still occurs after the write to &lt;code>b&lt;/code>. This is because the write to &lt;code>a&lt;/code> was not marked as &lt;code>volatile&lt;/code>. If we rewrite the code like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">__WRITE_ONCE&lt;/span>(a, &lt;span style="color:#a6e22e">__READ_ONCE&lt;/span>(i) &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">16&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">__WRITE_ONCE&lt;/span>(b, &lt;span style="color:#ae81ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The generated assembly would be:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-asm" data-lang="asm">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">movl&lt;/span> &lt;span style="color:#66d9ef">i&lt;/span>(%rip), %edx &lt;span style="color:#75715e">// read i
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">testl&lt;/span> %edx, %edx
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">leal&lt;/span> &lt;span style="color:#ae81ff">15&lt;/span>(%rdx), %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">cmovns&lt;/span> %edx, %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">sarl&lt;/span> &lt;span style="color:#66d9ef">$4&lt;/span>, %eax
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">movl&lt;/span> %eax, &lt;span style="color:#66d9ef">a&lt;/span>(%rip) &lt;span style="color:#75715e">// write a
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">movl&lt;/span> &lt;span style="color:#66d9ef">$0&lt;/span>, &lt;span style="color:#66d9ef">b&lt;/span>(%rip) &lt;span style="color:#75715e">// write b
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, the order perfectly matches the C code. However, this can be simplified further because &lt;code>a&lt;/code> depends on &lt;code>i&lt;/code>, and the compiler will ensure that &lt;code>i&lt;/code> is read before &lt;code>a&lt;/code> is written. Therefore, the first &lt;code>__READ_ONCE&lt;/code> can be removed, and the following code will have the same effect:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">__WRITE_ONCE&lt;/span>(a, i &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">16&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">__WRITE_ONCE&lt;/span>(b, &lt;span style="color:#ae81ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="postscript">Postscript&lt;/h1>
&lt;p>The role of &lt;code>volatile&lt;/code> in preventing reordering is quite clear in Java, where the JVM behaves much like an operating system. In Java, after compilation to bytecode, instruction reordering can still occur, leading to compiler optimizations. Therefore, the &lt;code>volatile&lt;/code> keyword in Java explicitly prevents these optimizations, which has become common knowledge among Java developers. However, in C, the behavior of &lt;code>volatile&lt;/code> is somewhat more obscure.&lt;/p></content></item><item><title>The Principle of Workingset in the Linux Kernel</title><link>https://systemsresearch.io/posts/a9455fa9e/</link><pubDate>Thu, 25 Jul 2024 16:58:14 +0000</pubDate><guid>https://systemsresearch.io/posts/a9455fa9e/</guid><description>&lt;h1 id="overview-of-basic-concepts">Overview of Basic Concepts&lt;/h1>
&lt;p>The concept of workingset was first introduced by Professor Peter Denning in 1968. The fundamental idea is that programs exhibit temporal locality in their memory usage. At time $t$, the workingset of a program is considered to be the set of pages accessed by the process during the interval $(t-\tau, t)$, denoted as $W(t, \tau)$, where $\tau$ is known as the workingset parameter, a user-defined value. The original paper suggested that memory not accessed within $\tau$ could be reclaimed through sampling.&lt;/p></description><content>&lt;h1 id="overview-of-basic-concepts">Overview of Basic Concepts&lt;/h1>
&lt;p>The concept of workingset was first introduced by Professor Peter Denning in 1968. The fundamental idea is that programs exhibit temporal locality in their memory usage. At time $t$, the workingset of a program is considered to be the set of pages accessed by the process during the interval $(t-\tau, t)$, denoted as $W(t, \tau)$, where $\tau$ is known as the workingset parameter, a user-defined value. The original paper suggested that memory not accessed within $\tau$ could be reclaimed through sampling.&lt;/p>
&lt;p>In the Linux kernel, workingset refers to the &lt;code>inactive&lt;/code> and &lt;code>active&lt;/code> LRU (Least Recently Used) lists. For file pages read via the &lt;code>read&lt;/code> system call, they are placed in the &lt;code>inactive&lt;/code> LRU list upon the first read. If these pages are repeatedly read, they are promoted to the &lt;code>active&lt;/code> LRU list. For pages mapped via &lt;code>mmap&lt;/code>, the process is similar: they are initially placed in the &lt;code>inactive&lt;/code> LRU list and may be activated to the &lt;code>active&lt;/code> LRU list if deemed sufficiently active during memory reclamation.&lt;/p>
&lt;p>When memory pressure triggers reclamation, pages are freed from the tail of the &lt;code>inactive&lt;/code> list. If the &lt;code>inactive&lt;/code> list becomes significantly smaller than the &lt;code>active&lt;/code> list, pages from the &lt;code>active&lt;/code> list are moved to the &lt;code>inactive&lt;/code> list to balance their sizes.&lt;/p>
&lt;p>Overall, the aging process of pages involves moving from the head of the &lt;code>active&lt;/code> list to its tail, then to the head of the &lt;code>inactive&lt;/code> list, and finally to the tail of the &lt;code>inactive&lt;/code> list before being reclaimed.&lt;/p>
&lt;p>This article focuses on the workingset for file pages, although the current kernel also considers anonymous pages, as the underlying principles are consistent.&lt;/p>
&lt;h1 id="lifecycle-model-of-file-pages">Lifecycle Model of File Pages&lt;/h1>
&lt;p>Assuming the machine&amp;rsquo;s memory is divided into two parts: the &lt;code>active&lt;/code> list and the &lt;code>inactive&lt;/code> list. This assumption simplifies analysis. The sizes of these two parts are mutually adjustable, with the ratio of &lt;code>active:inactive&lt;/code> not exceeding &lt;code>3:1&lt;/code> according to kernel settings. If the &lt;code>active&lt;/code> list grows too large, pages are moved to the &lt;code>inactive&lt;/code> list to maintain the balance.&lt;/p>
&lt;p>The kernel&amp;rsquo;s page lifecycle model can be summarized as follows:&lt;/p>
&lt;ul>
&lt;li>File pages are placed at the head of the &lt;code>inactive&lt;/code> list when read into memory.&lt;/li>
&lt;li>Each new file page read causes a page at the tail of the &lt;code>inactive&lt;/code> list to be reclaimed, making space for the newly inserted page, effectively moving the pages in the inactive list toward the tail by one position.&lt;/li>
&lt;li>Pages with high access frequency, detected during reclamation or repeated reads via the &lt;code>read&lt;/code> system call, are promoted to the &lt;code>active&lt;/code> list, causing pages ahead of them in the &lt;code>inactive&lt;/code> list to move one position towards the tail.&lt;/li>
&lt;li>If the &lt;code>active&lt;/code> list grows more than three times the size of the &lt;code>inactive&lt;/code> list, pages from the tail of the &lt;code>active&lt;/code> list are moved to the head of the &lt;code>inactive&lt;/code> list to rebalance the ratio.&lt;/li>
&lt;/ul>
&lt;p>This simplified model describes the kernel&amp;rsquo;s page lifecycle. It also shows that pages in the &lt;code>inactive&lt;/code> list are either activated or reclaimed, causing pages ahead of them to move towards the tail (aging). A page is reclaimed after moving toward the tail through &lt;code>len(inactive)&lt;/code> positions.&lt;/p>
&lt;h1 id="problem-introduction">Problem Introduction&lt;/h1>
&lt;p>Some file pages are accessed periodically. When first loaded into memory, they are placed at the head of the &lt;code>inactive&lt;/code> list. As other file pages are read into &lt;code>inactive&lt;/code> list and pages at the tail are reclaimed, these file pages are gradually pushed to the tail of the &lt;code>inactive&lt;/code> list and eventually reclaimed. After being reclaimed for a while, they are accessed again, repeating the process of loading, moving, and reclaiming (referred to as periodically accessed pages).&lt;/p>
&lt;p>These pages are not highly active; if their access frequency were high enough, they would have been promoted to the &lt;code>active&lt;/code> list. The fact that they oscillate within the &lt;code>inactive&lt;/code> list indicates moderate access frequency.&lt;/p>
&lt;p>However, the &lt;code>active&lt;/code> list only moves pages to the &lt;code>inactive&lt;/code> list when it becomes too long. Consequently, the &lt;code>active&lt;/code> list may contain long-unaccessed inactive pages. Since the &lt;code>active&lt;/code> list is not yet long enough, these pages remain there, even though their access frequency is lower than that of the periodically accessed pages. We need a method to allow these inactive pages in the &lt;code>active&lt;/code> list to compete fairly with periodically accessed pages, retaining genuinely valuable and frequently accessed pages.&lt;/p>
&lt;h1 id="basic-idea-of-workingset">Basic Idea of Workingset&lt;/h1>
&lt;p>Workingset is such a method. The &lt;code>inactive&lt;/code> and &lt;code>active&lt;/code> lists together form the workingset, aiming to retain pages accessed by processes in the workingset and prevent their reclamation.&lt;/p>
&lt;p>A periodically accessed page moves from the head of the &lt;code>inactive&lt;/code> list to its tail before being reclaimed, traveling through &lt;code>len(inactive)&lt;/code> positions.&lt;/p>
&lt;p>From the moment a page is reclaimed until it is accessed again, the &lt;code>inactive&lt;/code> list continues to activate and reclaim pages. Suppose &lt;code>X&lt;/code> pages are reclaimed and activated during this period.&lt;/p>
&lt;p>If the page were initially placed &lt;code>X + len(inactive)&lt;/code> positions from the tail, it would be accessed just as it reaches the tail, avoiding reclamation and preventing oscillation.&lt;/p>
&lt;p>The key is that the &lt;code>inactive&lt;/code> list is only &lt;code>len(inactive)&lt;/code> long. What we need is to place the page &lt;code>X + len(inactive)&lt;/code> positions from the tail.&lt;/p>
&lt;p>Since the &lt;code>inactive&lt;/code> list is preceded by the &lt;code>active&lt;/code> list, we can insert the page at the &lt;code>X&lt;/code>-th position from the end of the &lt;code>active&lt;/code> list. Instead of inserting in the middle, we can simply place it at the head of the &lt;code>active&lt;/code> list, achieving protection.&lt;/p>
&lt;p>Additionally, this approach serves another purpose: once the page is added to the active list, it will compete with other active pages. As the length of the active list gradually increases, when it becomes necessary to age a batch of pages from the active list to the inactive list, the kernel will compare the access frequencies. This comparison ensures that genuinely active pages remain in the active list, while less active pages are moved to the inactive list for potential reclamation.&lt;/p>
&lt;p>This is the basic idea of workingset.&lt;/p>
&lt;h1 id="implementation-of-workingset">Implementation of Workingset&lt;/h1>
&lt;p>By placing the page at the head of the &lt;code>active&lt;/code> list, it moves through the &lt;code>active&lt;/code> list to the tail of the &lt;code>inactive&lt;/code> list, providing a maximum travel distance of &lt;code>len(active) + len(inactive)&lt;/code>. The required distance to prevent reclamation is &lt;code>X + len(inactive)&lt;/code>. As long as &lt;code>len(active) + len(inactive)&lt;/code> is greater than &lt;code>X + len(inactive)&lt;/code>, placing the page at the head of the &lt;code>active&lt;/code> list provides sufficient protection; otherwise, the page will eventually be reclaimed.&lt;/p>
&lt;p>The inequality &lt;code>len(active) + len(inactive) &amp;gt; X + len(inactive)&lt;/code> simplifies to &lt;code>len(active) &amp;gt; X&lt;/code>. Just checking this inequality is sufficient. When a page is read into memory, if the inequality holds, it is placed at the head of the &lt;code>active&lt;/code> list to avoid reclamation before the next access.&lt;/p>
&lt;p>The remaining issue is calculating &lt;code>X&lt;/code>. As previously mentioned, it represents the number of pages reclaimed and activated in the &lt;code>inactive&lt;/code> list from the time a periodically accessed page is reclaimed until it is accessed again. We can set up a counter, incremented each time a page in the &lt;code>inactive&lt;/code> list is reclaimed or activated.&lt;/p>
&lt;p>Thus, when a periodically accessed page is reclaimed, we record the current value of this counter in its &lt;code>radix tree slot&lt;/code>. When the page is read again, it is reinserted into the same &lt;code>radix tree slot&lt;/code>. We retrieve the old counter value, subtract it from the current counter value to obtain &lt;code>X&lt;/code>.&lt;/p>
&lt;p>Then, we compare &lt;code>len(active) &amp;gt; X&lt;/code> to decide whether to place the page at the head of the &lt;code>active&lt;/code> list. If protection is feasible, the page is placed at the head of the &lt;code>active&lt;/code> list; otherwise, it is placed in the &lt;code>inactive&lt;/code> list.&lt;/p></content></item><item><title>Understanding the Implementation of qspinlock in the Linux Kernel</title><link>https://systemsresearch.io/posts/f22352cfc/</link><pubDate>Thu, 18 Jul 2024 11:07:08 +0000</pubDate><guid>https://systemsresearch.io/posts/f22352cfc/</guid><description>&lt;p>The &lt;code>qspinlock&lt;/code> is implemented on top of the &lt;code>mcs spinlock&lt;/code>. The &lt;code>MCS&lt;/code> stands for Mellor, Crummey, and Scott, the surnames of the creators. The main idea is to have each spinner spin on its own per-cpu variable, thereby avoiding the constant cacheline bouncing between different CPUs. Cacheline bouncing occurs when all CPUs spin-wait on the same lock variable, causing them to repeatedly read this variable. When one CPU unlocks, this variable is modified, invalidating the cachelines of all other CPUs, which then have to re-read the variable. This results in a performance overhead. The MCS lock mitigates this by having each CPU spin on its own dedicated variable, thus avoiding contention on a single lock variable.&lt;/p></description><content>&lt;p>The &lt;code>qspinlock&lt;/code> is implemented on top of the &lt;code>mcs spinlock&lt;/code>. The &lt;code>MCS&lt;/code> stands for Mellor, Crummey, and Scott, the surnames of the creators. The main idea is to have each spinner spin on its own per-cpu variable, thereby avoiding the constant cacheline bouncing between different CPUs. Cacheline bouncing occurs when all CPUs spin-wait on the same lock variable, causing them to repeatedly read this variable. When one CPU unlocks, this variable is modified, invalidating the cachelines of all other CPUs, which then have to re-read the variable. This results in a performance overhead. The MCS lock mitigates this by having each CPU spin on its own dedicated variable, thus avoiding contention on a single lock variable.&lt;/p>
&lt;p>This article does not delve into the evolution of spinlocks but directly explains the final implementation of the &lt;code>qspinlock&lt;/code>.&lt;/p>
&lt;p>If the text in images is unclear, you can right-click and select &amp;ldquo;Open Image in New Tab&amp;rdquo; for a high-resolution view.&lt;/p>
&lt;h1 id="data-structure-definition">Data Structure Definition&lt;/h1>
&lt;p>The &lt;code>qspinlock&lt;/code> structure is defined as follows:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">typedef&lt;/span> &lt;span style="color:#66d9ef">struct&lt;/span> qspinlock {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">union&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">atomic_t&lt;/span> val;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">/*
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * By using the whole 2nd least significant byte for the
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * pending bit, we can allow better optimization of the lock
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * acquisition for the pending bit holder.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">struct&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u8 locked;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u8 pending;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">struct&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u16 locked_pending;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u16 tail;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>} &lt;span style="color:#66d9ef">arch_spinlock_t&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The memory layout is shown below. Note that a &lt;code>union&lt;/code> type is used in the definition, so using the &lt;code>locked_pending&lt;/code> variable allows simultaneous retrieval or modification of both &lt;code>locked&lt;/code> and &lt;code>pending&lt;/code> variables.&lt;/p>
&lt;p>&lt;img src="https://systemsresearch.io/images/689e687bb.png">&lt;/p>
&lt;p>The CPU holding the lock is called the &lt;code>owner&lt;/code>, and the waiting CPUs are termed &lt;code>spinners&lt;/code>. These &lt;code>spinners&lt;/code> can be further categorized based on their behavior into three types: &lt;code>pender&lt;/code>, &lt;code>successor&lt;/code>, and &lt;code>queuer&lt;/code>. The term &lt;code>pender&lt;/code> is a coined word to better illustrate the spinlock logic.&lt;/p>
&lt;h1 id="locking-and-unlocking-process-demonstration">Locking and Unlocking Process Demonstration&lt;/h1>
&lt;p>The locking and unlocking process is essentially a state machine. We&amp;rsquo;ll demonstrate the state changes graphically.&lt;/p>
&lt;p>Initially, in an unlocked state, the structure members have the values shown below:&lt;/p>
&lt;p>&lt;img src="https://systemsresearch.io/images/8a08cddfd.png">&lt;/p>
&lt;h2 id="0-spinner">0 Spinner&lt;/h2>
&lt;h3 id="locking">Locking&lt;/h3>
&lt;p>Now, let&amp;rsquo;s assume a CPU attempts to acquire the lock. Since no other CPUs are waiting, the state transitions as shown below. The &lt;code>spinlock-&amp;gt;locked&lt;/code> variable is set to 1, indicating that the lock is acquired. At this point, there is only an &lt;code>owner&lt;/code> and no &lt;code>spinners&lt;/code>.&lt;/p>
&lt;p>&lt;img src="https://systemsresearch.io/images/c119c0524.png">&lt;/p>
&lt;h3 id="unlocking">Unlocking&lt;/h3>
&lt;p>In this scenario, if the &lt;code>owner&lt;/code> releases the lock, the state transitions back to the unlocked state:&lt;/p>
&lt;p>&lt;img src="https://systemsresearch.io/images/55989c079.png">&lt;/p>
&lt;h2 id="1-spinner">1 Spinner&lt;/h2>
&lt;h3 id="locking-1">Locking&lt;/h3>
&lt;p>Assume that while the &lt;code>owner&lt;/code> holds the lock, another CPU attempts to acquire it. This CPU will have to wait, and we call this waiting CPU a &lt;code>pender&lt;/code>. The &lt;code>pender&lt;/code> sets the &lt;code>pending&lt;/code> variable to 1 to indicate its presence and then spins-waits for the &lt;code>spinlock-&amp;gt;locked&lt;/code> variable to become 0. The dashed arrow in the diagram indicates the waiting relationship and the corresponding code.&lt;/p>
&lt;p>It&amp;rsquo;s worth noting that the &lt;code>pending&lt;/code> variable is exclusive to the &lt;code>pender&lt;/code>. If the &lt;code>pender&lt;/code> disappears, the variable reverts to 0.&lt;/p>
&lt;p>&lt;img src="https://systemsresearch.io/images/a44190ce7.png">&lt;/p>
&lt;h3 id="unlocking-1">Unlocking&lt;/h3>
&lt;p>In this case, if the &lt;code>owner&lt;/code> releases the lock, two steps occur:&lt;/p>
&lt;ol>
&lt;li>The &lt;code>owner&lt;/code> sets the &lt;code>spinlock-&amp;gt;locked&lt;/code> variable to 0, indicating the lock is released.&lt;/li>
&lt;li>The &lt;code>pender&lt;/code> detects that &lt;code>spinlock-&amp;gt;locked&lt;/code> is 0 and atomically changes &lt;code>(locked=0, pending=1)&lt;/code> to &lt;code>(locked=1, pending=0)&lt;/code>. Here, setting &lt;code>locked&lt;/code> to 1 signifies that the &lt;code>pender&lt;/code> becomes the new &lt;code>owner&lt;/code>, and clearing &lt;code>pending&lt;/code> to 0 indicates the &lt;code>pender&lt;/code>&amp;rsquo;s disappearance (as it has become the &lt;code>owner&lt;/code>). As mentioned earlier, the &lt;code>pending&lt;/code> variable is an exclusive indicator for the &lt;code>pender&lt;/code>.&lt;/li>
&lt;/ol>
&lt;p>The overall state transition for unlocking is shown below. The state within the dashed box on the right represents an intermediate state, where only the first step is completed. Once the second step is completed, the state transitions to the 0 spinners scenario:&lt;/p>
&lt;p>&lt;img src="https://systemsresearch.io/images/1af299073.png">&lt;/p>
&lt;h2 id="2-spinners">2 Spinners&lt;/h2>
&lt;h3 id="locking-2">Locking&lt;/h3>
&lt;p>Let&amp;rsquo;s consider a scenario where, in addition to the &lt;code>pender&lt;/code>, another CPU attempts to acquire the lock. This new CPU will spin-wait on both &lt;code>spinlock-&amp;gt;{locked, pending}&lt;/code> variables. We call this CPU a &lt;code>successor&lt;/code>.&lt;/p>
&lt;p>In this situation, the &lt;code>spinlock-&amp;gt;tail&lt;/code> variable stores the &lt;code>successor&lt;/code>&amp;rsquo;s &lt;code>cpu id&lt;/code>. The &lt;code>idx&lt;/code> field in the diagram indicates the context (i.e. process context, softirq, hardirq, nmi) of this &lt;code>successor&lt;/code>, but we can ignore it for our discussion. It&amp;rsquo;s enough to know that the &lt;code>tail&lt;/code> variable &amp;ldquo;points to&amp;rdquo; the &lt;code>successor cpu&lt;/code>. The arrow in the diagram indicates this pointing relationship starting from the &lt;code>tail&lt;/code> to the &lt;code>successor cpu&lt;/code>&amp;rsquo;s per-cpu &lt;code>mcs node&lt;/code> structure. While the &lt;code>mcs node&lt;/code> appears in this scenario, it hasn&amp;rsquo;t yet come into play. We will discuss it further later.&lt;/p>
&lt;p>&lt;img src="https://systemsresearch.io/images/0400877ac.png">&lt;/p>
&lt;p>To summarize, in this situation, there are two &lt;code>spinners&lt;/code>: the &lt;code>pender&lt;/code> waiting for &lt;code>spinlock-&amp;gt;locked&lt;/code> to become 0, and the &lt;code>successor&lt;/code> waiting for both &lt;code>spinlock-&amp;gt;{locked, pending}&lt;/code> to become 0.&lt;/p>
&lt;h3 id="unlocking-2">Unlocking&lt;/h3>
&lt;p>If the &lt;code>owner&lt;/code> releases the lock, the &lt;code>owner&lt;/code> disappears, and the &lt;code>pender&lt;/code> detects that &lt;code>spinlock-&amp;gt;locked&lt;/code> is 0. The &lt;code>pender&lt;/code> then atomically changes &lt;code>(locked=0, pending=1)&lt;/code> to &lt;code>(locked=1, pending=0)&lt;/code>, becoming the new &lt;code>owner&lt;/code> and indicating the &lt;code>pender&lt;/code>&amp;rsquo;s disappearance (now the &lt;code>owner&lt;/code>). [This part is the same as the &amp;ldquo;1 spinner&amp;rdquo; scenario]&lt;/p>
&lt;p>The unlocking state transition is shown below. You can see that after releasing the lock, the &lt;code>pender&lt;/code> becomes the new &lt;code>owner&lt;/code>, and the &lt;code>pender&lt;/code> disappears. However, the &lt;code>successor&lt;/code> is still there, waiting for both &lt;code>spinlock-&amp;gt;{locked, pending}&lt;/code> to become 0.&lt;/p>
&lt;p>&lt;img src="https://systemsresearch.io/images/6e783c1f4.png">&lt;/p>
&lt;p>Next, if the &lt;code>owner&lt;/code> releases the lock again, the &lt;code>spinlock-&amp;gt;locked&lt;/code> variable becomes 0, and finally, both &lt;code>spinlock-&amp;gt;{locked, pending}&lt;/code> variables become 0. The &lt;code>successor&lt;/code> detects this change and atomically sets the &lt;code>spinlock-&amp;gt;locked&lt;/code> variable to 1, thereby acquiring the lock and becoming the new &lt;code>owner&lt;/code>. The state transition is shown below:&lt;/p>
&lt;p>&lt;img src="https://systemsresearch.io/images/4580482c3.png">&lt;/p>
&lt;p>At this point, you can observe a pattern: the &lt;code>pender&lt;/code> and &lt;code>successor&lt;/code> have equivalent statuses. Their next step is to become the &lt;code>owner&lt;/code>, but they wait for different conditions. So why have two equivalent spinners? This is a small optimization, which we will discuss after explaining the overall process. For now, just focus on understanding the state transitions.&lt;/p>
&lt;h2 id="3-spinners">3 Spinners&lt;/h2>
&lt;h3 id="locking-3">Locking&lt;/h3>
&lt;p>Now, let&amp;rsquo;s make the scenario more complex. Suppose another CPU attempts to acquire the lock while the &lt;code>successor&lt;/code> is waiting. This CPU will wait on its own per-cpu &lt;code>mcs node&lt;/code> structure and is called a &lt;code>queuer&lt;/code>.&lt;/p>
&lt;p>Specifically, each CPU has its own dedicated &lt;code>mcs node&lt;/code> structure, which includes a member called &lt;code>locked&lt;/code>, denoted as &lt;code>mcs-&amp;gt;locked&lt;/code>. This variable initially has a value of 0, and the &lt;code>queuer&lt;/code> spin-waits until its &lt;code>mcs-&amp;gt;locked&lt;/code> variable becomes 1. This way, it doesn&amp;rsquo;t repeatedly read the spinlock variable, which is already being read by the &lt;code>pender&lt;/code> and &lt;code>successor&lt;/code>. This reduces cache contention and bouncing.&lt;/p>
&lt;p>The state transition for this scenario is shown below. The &lt;code>pender&lt;/code> and &lt;code>successor&lt;/code> each wait for their respective variables, while the &lt;code>queuer&lt;/code> waits on its dedicated &lt;code>mcs node&lt;/code>. Note that the &lt;code>spinlock-&amp;gt;tail&lt;/code> variable now points to the &lt;code>queuer&lt;/code>, aligning with the term &amp;ldquo;tail&amp;rdquo;, which indicates the end of the waiting queue.&lt;/p>
&lt;p>&lt;img src="https://systemsresearch.io/images/8aeaebc5a.png">&lt;/p>
&lt;p>At this point, the three &lt;code>spinners&lt;/code> are named &lt;code>pender&lt;/code>, &lt;code>successor&lt;/code>, and &lt;code>queuer&lt;/code>, based on the variables they wait for. This naming convention is to distinguish them.&lt;/p>
&lt;h3 id="unlocking-3">Unlocking&lt;/h3>
&lt;p>Let&amp;rsquo;s examine the state transitions during unlocking in this scenario.&lt;/p>
&lt;p>In the first unlock, the &lt;code>owner&lt;/code> disappears and the &lt;code>pender&lt;/code> moves up (i.e. be promoted):&lt;/p>
&lt;p>&lt;img src="https://systemsresearch.io/images/f2bf63fe7.png">&lt;/p>
&lt;p>In the second unlock, the &lt;code>owner&lt;/code> disappears, and the &lt;code>successor&lt;/code> becomes the &lt;code>owner&lt;/code>. When the &lt;code>successor&lt;/code> moves up, it checks if there&amp;rsquo;s a &lt;code>queuer&lt;/code> behind it. If there is, it promotes the &lt;code>queuer&lt;/code> to the new &lt;code>successor&lt;/code>. In this case, there is a &lt;code>queuer&lt;/code>, so the &lt;code>successor&lt;/code> sets the &lt;code>queuer&lt;/code>&amp;rsquo;s &lt;code>mcs-&amp;gt;locked&lt;/code> to 1, allowing the &lt;code>queuer&lt;/code> to finish waiting and become the new &lt;code>successor&lt;/code>.&lt;/p>
&lt;p>The state transition is shown below:&lt;/p>
&lt;p>&lt;img src="https://systemsresearch.io/images/90a73333b.png">&lt;/p>
&lt;p>If we unlock once more, the &lt;code>successor&lt;/code> moves up to become the &lt;code>owner&lt;/code>, as shown in the diagram. Note the additional arrow on the right side:&lt;/p>
&lt;p>&lt;img src="https://systemsresearch.io/images/df832255e.png">&lt;/p>
&lt;h2 id="4-spinners">4 Spinners&lt;/h2>
&lt;p>Let&amp;rsquo;s make the scenario even more complex by adding another CPU waiting for the lock, making it a second &lt;code>queuer&lt;/code>. The &lt;code>queuer&lt;/code> can have many instances, queued up one after another. As a &lt;code>queuer&lt;/code>, it will spin-wait on its own &lt;code>mcs-&amp;gt;locked&lt;/code> variable, waiting to be promoted to &lt;code>successor&lt;/code>.&lt;/p>
&lt;p>Assume two &lt;code>queuers&lt;/code> are named &lt;code>queuer1&lt;/code> and &lt;code>queuer2&lt;/code>. When &lt;code>queuer1&lt;/code> moves up to &lt;code>successor&lt;/code>, &lt;code>queuer2&lt;/code> becomes the first in line. When the &lt;code>successor&lt;/code> (formerly &lt;code>queuer1&lt;/code>) moves up to &lt;code>owner&lt;/code>, &lt;code>queuer2&lt;/code> can be promoted to the new &lt;code>successor&lt;/code> by the old &lt;code>successor&lt;/code> (formerly &lt;code>queuer1&lt;/code>). The &lt;code>successor&lt;/code> uses the &lt;code>mcs&lt;/code> structure&amp;rsquo;s &lt;code>next&lt;/code> pointer to find and promote the next &lt;code>queuer&lt;/code>.&lt;/p>
&lt;p>The locking and unlocking in this scenario are shown in the bottom row of the diagram. Some intermediate states are omitted for brevity.&lt;/p>
&lt;p>&lt;img src="https://systemsresearch.io/images/126d56723.png">&lt;/p>
&lt;h2 id="generalization">Generalization&lt;/h2>
&lt;p>From the state transition processes above, we can draw the following conclusions:&lt;/p>
&lt;ul>
&lt;li>The &lt;code>pender&lt;/code> and &lt;code>successor&lt;/code> have equivalent statuses, both aiming to become the &lt;code>owner&lt;/code>.&lt;/li>
&lt;li>The first &lt;code>queuer&lt;/code> in line will be promoted to the new &lt;code>successor&lt;/code> by the current &lt;code>successor&lt;/code> upon its departure.&lt;/li>
&lt;li>Subsequent &lt;code>queuers&lt;/code> patiently wait for the &lt;code>successor&lt;/code> to promote them.&lt;/li>
&lt;li>The &lt;code>pender&lt;/code> appears only once at the beginning and never reappears after it disappears.&lt;/li>
&lt;/ul>
&lt;p>Based on these conclusions, we can summarize the state transitions for locking and unlocking processes:&lt;/p>
&lt;h3 id="continuous-locking">Continuous Locking&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span style="display:flex;">&lt;span>unlocked
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>-&amp;gt; 1 owner
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>-&amp;gt; 1 owner, 1 pender
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>-&amp;gt; 1 owner, 1 pender, 1 successor
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>-&amp;gt; 1 owner, 1 pender, 1 successor, 1 queuer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>-&amp;gt; 1 owner, 1 pender, 1 successor, 2 queuer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>-&amp;gt; ...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>-&amp;gt; 1 owner, 1 pender, 1 successor, N queuer
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="continuous-unlocking">Continuous Unlocking&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span style="display:flex;">&lt;span>-&amp;gt; 1 owner, 1 pender, 1 successor, N queuer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>-&amp;gt; 1 owner, 1 successor, N queuer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>-&amp;gt; 1 owner, 1 successor, (N-1) queuer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>-&amp;gt; 1 owner, 1 successor, (N-2) queuer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>-&amp;gt; ...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>-&amp;gt; 1 owner, 1 successor
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>-&amp;gt; 1 owner
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>-&amp;gt; unlocked
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>There are also some subtle variations to consider. For instance, if a &lt;code>spinner&lt;/code> arrives between the &lt;code>owner&lt;/code> releasing the lock and the &lt;code>pender&lt;/code> acquiring it, what happens? I&amp;rsquo;ll leave that for the reader to ponder.&lt;/p>
&lt;h1 id="why-have-both-pender-and-successor-roles">Why Have Both &lt;code>pender&lt;/code> and &lt;code>successor&lt;/code> Roles&lt;/h1>
&lt;p>This primarily stems from practical testing, which revealed that when there are two &lt;code>spinners&lt;/code>, and one waits on the &lt;code>spinlock&lt;/code> variable while the other waits on its &lt;code>mcs node&lt;/code> variable, this can lead to an additional &lt;code>mcs node&lt;/code> cacheline read. This results in a cold cache miss, which is more costly than the potential cache bouncing incurred by having both &lt;code>spinners&lt;/code> wait on the &lt;code>spinlock&lt;/code>. To optimize this, the &lt;code>pending&lt;/code> flag and thus the &lt;code>pender&lt;/code> role were introduced during the development of &lt;code>qspinlock&lt;/code>.&lt;/p>
&lt;h1 id="why-increment-the-cpu-id-by-1-before-storing-it-in-tail-in-actual-code-implementation">Why Increment the CPU ID by 1 Before Storing it in &lt;code>tail&lt;/code> in Actual Code Implementation&lt;/h1>
&lt;p>In actual code implementation, there are instances where it is necessary to determine whether a &lt;code>successor&lt;/code> or &lt;code>queuer&lt;/code> exists. This determination is made by checking if &lt;code>tail&lt;/code> is 0. Therefore, we must encode the &lt;code>cpu id&lt;/code> by incrementing it by 1 before storing it. Otherwise, if &lt;code>tail&lt;/code> is 0, it would be unclear whether it means that &lt;code>successor&lt;/code> and &lt;code>queuer&lt;/code> don&amp;rsquo;t exist, or if it simply indicates that the &lt;code>cpu id&lt;/code> of the &lt;code>successor&lt;/code> or &lt;code>queuer&lt;/code> is 0.&lt;/p></content></item><item><title>About</title><link>https://systemsresearch.io/about/</link><pubDate>Thu, 29 Feb 2024 11:18:48 +0000</pubDate><guid>https://systemsresearch.io/about/</guid><description>&lt;p>I am a programmer who enjoys systems research, nominally a Linux kernel developer. I hope to study some interesting things, including but not limited to operating systems, computer architecture, algorithms, mathematics, etc.&lt;/p></description><content>&lt;p>I am a programmer who enjoys systems research, nominally a Linux kernel developer. I hope to study some interesting things, including but not limited to operating systems, computer architecture, algorithms, mathematics, etc.&lt;/p></content></item></channel></rss>