<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>原子操作 on 大飞的博客</title>
    <link>https://www.dafei.me/tags/%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C/</link>
    <description>Recent content in 原子操作 on 大飞的博客</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <lastBuildDate>Thu, 04 Jun 2026 02:00:00 +0000</lastBuildDate>
    <atom:link href="https://www.dafei.me/tags/%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>从零写OS（三十九）：自旋锁 —— 多核下的第一道防线</title>
      <link>https://www.dafei.me/posts/os-39-spinlock/</link>
      <pubDate>Thu, 04 Jun 2026 02:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-39-spinlock/</guid>
      <description>&lt;p&gt;ch38 把两颗 CPU 都启动了，但这带来了一个新问题：两颗核心同时访问同一份数据会怎样？&lt;/p&gt;
&lt;p&gt;考虑这个场景：CPU0 和 CPU1 同时调用 &lt;code&gt;pmm_alloc()&lt;/code&gt; 申请物理页，都扫描到了同一个空闲位，都标记为&amp;quot;已用&amp;quot;，于是把同一个物理页分配给了两个不同的进程。这两个进程会互相覆盖对方的内存，然后崩溃。&lt;/p&gt;
&lt;p&gt;这章实现&lt;strong&gt;自旋锁（spinlock）&lt;/strong&gt;，让同一时间只有一颗核心能进入临界区。&lt;/p&gt;
&lt;h2 id=&#34;为什么普通变量不能做锁&#34;&gt;为什么普通变量不能做锁&lt;/h2&gt;
&lt;p&gt;直觉上，可以用一个整数变量做锁：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; lock &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (lock &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {   &lt;span style=&#34;color:#75715e&#34;&gt;// CPU0 读到 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    lock &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;      &lt;span style=&#34;color:#75715e&#34;&gt;// CPU1 也读到 0，同时进入！
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 临界区
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;问题在于&amp;quot;读-判断-写&amp;quot;不是原子操作。两颗核心在读和写之间有一个竞争窗口，都能同时通过检查。&lt;/p&gt;
&lt;h2 id=&#34;xchg原子交换&#34;&gt;xchg：原子交换&lt;/h2&gt;
&lt;p&gt;x86 提供了 &lt;code&gt;xchg&lt;/code&gt; 指令，它原子地交换寄存器和内存的值——读和写在硬件层面是不可分割的：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;inline&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;spin_lock&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;spinlock_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;lock) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    __asm__ &lt;span style=&#34;color:#66d9ef&#34;&gt;volatile&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;1: xchgl %0, %1&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// 原子：把 1 写入 lock，把旧值读到 val
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;   testl %0, %0&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// 旧值为 0？
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;   jz 2f&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;         &lt;span style=&#34;color:#75715e&#34;&gt;// 是 → 获锁成功
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;   pause&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;         &lt;span style=&#34;color:#75715e&#34;&gt;// 否 → 稍等，再试
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;   jmp 1b&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;+r&amp;#34;&lt;/span&gt;(val), &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;+m&amp;#34;&lt;/span&gt;(lock&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;locked)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;memory&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;xchg&lt;/code&gt; 隐含 &lt;code&gt;lock&lt;/code&gt; 前缀，直接是总线级原子操作。如果拿到的旧值是 0，说明锁之前是空闲的，现在已经被我们锁上了。如果拿到的旧值是 1，说明别人持有锁，自旋等待。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
