<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-CN"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://weinan.tech/feed.xml" rel="self" type="application/atom+xml" /><link href="https://weinan.tech/" rel="alternate" type="text/html" hreflang="zh-CN" /><updated>2026-07-10T12:20:14+08:00</updated><id>https://weinan.tech/feed.xml</id><title type="html">阿男聊技术</title><subtitle>阿男的技术博客：Linux 内核、云原生、Java/Rust 与系统编程笔记。</subtitle><author><name>阿男</name></author><entry><title type="html">Spring AI 2 实战：订票聊天 Demo 的架构、ReAct 与 ToolCallingAdvisor 链</title><link href="https://weinan.tech/2026/06/26/spring-ai-2-booking-demo-react-tool-calling.html" rel="alternate" type="text/html" title="Spring AI 2 实战：订票聊天 Demo 的架构、ReAct 与 ToolCallingAdvisor 链" /><published>2026-06-26T00:00:00+08:00</published><updated>2026-06-26T00:00:00+08:00</updated><id>https://weinan.tech/2026/06/26/spring-ai-2-booking-demo-react-tool-calling</id><content type="html" xml:base="https://weinan.tech/2026/06/26/spring-ai-2-booking-demo-react-tool-calling.html"><![CDATA[<style>
/* Mermaid diagram container */
.mermaid-container {
  position: relative;
  display: block;
  cursor: pointer;
  transition: opacity 0.2s;
  max-width: 100%;
  margin: 20px 0;
  overflow-x: auto;
}

.mermaid-container:hover {
  opacity: 0.8;
}

.mermaid-container svg {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Modal overlay */
.mermaid-modal {
  display: none;
  position: fixed;
  z-index: 9999;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9);
  animation: fadeIn 0.3s;
}

.mermaid-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Modal content */
.mermaid-modal-content {
  position: relative;
  width: 90vw;
  height: 90vh;
  overflow: hidden;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
}

.mermaid-modal-diagram {
  transform-origin: center center;
  transition: transform 0.2s ease;
  display: inline-block;
  min-width: 100%;
  cursor: grab;
  user-select: none;
}

.mermaid-modal-diagram.dragging {
  cursor: grabbing;
  transition: none;
}

.mermaid-modal-diagram svg {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;
}

/* Control buttons */
.mermaid-controls {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
  z-index: 10000;
}

.mermaid-btn {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
  color: #333;
  font-weight: 500;
}

.mermaid-btn:hover {
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Close button */
.mermaid-close {
  background: #f44336;
  color: white;
  border: none;
}

.mermaid-close:hover {
  background: #d32f2f;
}

/* Zoom indicator */
.mermaid-zoom-level {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 10000;
}
</style>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  mermaid.initialize({
    startOnLoad: false,
    theme: 'default',
    securityLevel: 'loose',
    htmlLabels: true,
    themeVariables: {
      fontSize: '14px'
    }
  });

  let currentZoom = 1;
  let currentModal = null;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let translateX = 0;
  let translateY = 0;

  // Create modal HTML
  function createModal() {
    const modal = document.createElement('div');
    modal.className = 'mermaid-modal';
    modal.innerHTML = `
      <div class="mermaid-controls">
        <button class="mermaid-btn zoom-in">放大 +</button>
        <button class="mermaid-btn zoom-out">缩小 -</button>
        <button class="mermaid-btn zoom-reset">重置</button>
        <button class="mermaid-btn mermaid-close">关闭 ✕</button>
      </div>
      <div class="mermaid-modal-content">
        <div class="mermaid-modal-diagram"></div>
      </div>
      <div class="mermaid-zoom-level">100%</div>
    `;
    document.body.appendChild(modal);
    return modal;
  }

  // Show modal with diagram
  function showModal(diagramContent) {
    if (!currentModal) {
      currentModal = createModal();
      setupModalEvents();
    }

    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.innerHTML = diagramContent;

    // Remove any width/height attributes from SVG to make it responsive
    const svg = modalDiagram.querySelector('svg');
    if (svg) {
      svg.removeAttribute('width');
      svg.removeAttribute('height');
      svg.style.width = '100%';
      svg.style.height = 'auto';
    }

    // Setup drag functionality
    setupDrag(modalDiagram);

    currentModal.classList.add('active');
    currentZoom = 1;
    resetPosition();
    updateZoom();
    document.body.style.overflow = 'hidden';
  }

  // Hide modal
  function hideModal() {
    if (currentModal) {
      currentModal.classList.remove('active');
      document.body.style.overflow = '';
    }
  }

  // Update zoom level
  function updateZoom() {
    if (!currentModal) return;
    const diagram = currentModal.querySelector('.mermaid-modal-diagram');
    const zoomLevel = currentModal.querySelector('.mermaid-zoom-level');
    diagram.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentZoom})`;
    zoomLevel.textContent = `${Math.round(currentZoom * 100)}%`;
  }

  // Reset position when zoom changes
  function resetPosition() {
    translateX = 0;
    translateY = 0;
  }

  // Setup drag functionality
  function setupDrag(element) {
    element.addEventListener('mousedown', startDrag);
    element.addEventListener('touchstart', startDrag);
  }

  function startDrag(e) {
    if (e.type === 'mousedown' && e.button !== 0) return; // Only left click

    isDragging = true;
    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.classList.add('dragging');

    if (e.type === 'touchstart') {
      startX = e.touches[0].clientX - translateX;
      startY = e.touches[0].clientY - translateY;
    } else {
      startX = e.clientX - translateX;
      startY = e.clientY - translateY;
    }

    document.addEventListener('mousemove', drag);
    document.addEventListener('touchmove', drag);
    document.addEventListener('mouseup', stopDrag);
    document.addEventListener('touchend', stopDrag);
  }

  function drag(e) {
    if (!isDragging) return;
    e.preventDefault();

    if (e.type === 'touchmove') {
      translateX = e.touches[0].clientX - startX;
      translateY = e.touches[0].clientY - startY;
    } else {
      translateX = e.clientX - startX;
      translateY = e.clientY - startY;
    }

    updateZoom();
  }

  function stopDrag() {
    isDragging = false;
    const modalDiagram = currentModal?.querySelector('.mermaid-modal-diagram');
    if (modalDiagram) {
      modalDiagram.classList.remove('dragging');
    }
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('touchmove', drag);
    document.removeEventListener('mouseup', stopDrag);
    document.removeEventListener('touchend', stopDrag);
  }

  // Setup modal event listeners
  function setupModalEvents() {
    if (!currentModal) return;

    // Close button
    currentModal.querySelector('.mermaid-close').addEventListener('click', hideModal);

    // Zoom buttons
    currentModal.querySelector('.zoom-in').addEventListener('click', () => {
      currentZoom = Math.min(currentZoom + 0.25, 3);
      updateZoom();
    });

    currentModal.querySelector('.zoom-out').addEventListener('click', () => {
      currentZoom = Math.max(currentZoom - 0.25, 0.5);
      updateZoom();
    });

    currentModal.querySelector('.zoom-reset').addEventListener('click', () => {
      currentZoom = 1;
      resetPosition();
      updateZoom();
    });

    // Close on background click
    currentModal.addEventListener('click', (e) => {
      if (e.target === currentModal) {
        hideModal();
      }
    });

    // Close on ESC key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && currentModal.classList.contains('active')) {
        hideModal();
      }
    });
  }

  // Convert Jekyll-rendered code blocks to mermaid divs
  document.addEventListener('DOMContentLoaded', async function() {
    const codeBlocks = document.querySelectorAll('code.language-mermaid');

    for (const codeBlock of codeBlocks) {
      const pre = codeBlock.parentElement;
      const container = document.createElement('div');
      container.className = 'mermaid-container';

      const mermaidDiv = document.createElement('div');
      mermaidDiv.className = 'mermaid';
      mermaidDiv.textContent = codeBlock.textContent;

      container.appendChild(mermaidDiv);
      pre.replaceWith(container);
    }

    // Render all mermaid diagrams
    try {
      await mermaid.run({
        querySelector: '.mermaid'
      });
      console.log('Mermaid diagrams rendered successfully');
    } catch (error) {
      console.error('Mermaid rendering error:', error);
    }

    // Add click handlers to rendered diagrams
    document.querySelectorAll('.mermaid-container').forEach((container, index) => {
      // Find the rendered SVG inside the container
      const svg = container.querySelector('svg');
      if (!svg) {
        console.warn(`No SVG found in container ${index}`);
        return;
      }

      // Make the container clickable
      container.style.cursor = 'pointer';
      container.title = '点击查看大图';

      container.addEventListener('click', function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Clone the SVG for the modal
        const svgClone = svg.cloneNode(true);
        const tempDiv = document.createElement('div');
        tempDiv.appendChild(svgClone);

        console.log('Opening modal for diagram', index);
        showModal(tempDiv.innerHTML);
      });

      console.log(`Click handler added to diagram ${index}`);
    });
  });
</script>

<blockquote>
  <p>用户说「我要订票」，大模型不应凭空编造结果——它应调用 Java 方法改数据库，前端列表随之刷新。本文以 <a href="https://github.com/liweinan/springai_demo">springai_demo</a> 为样本，对照 <a href="https://github.com/spring-projects/spring-ai">Spring AI 2.0</a> 源码，说明这套 ReAct 全栈 Demo 怎么搭、亮点在哪，以及 2.0 相对 1.x 到底改了什么。</p>
</blockquote>

<h2 id="引言从聊天框到数据库的一次订票">引言：从聊天框到数据库的一次订票</h2>

<p>Spring AI 1.x 时代，很多入门示例把 Tool Calling 写进 <code class="language-plaintext highlighter-rouge">ChatModel.call()</code> 的黑盒里：业务代码一行 <code class="language-plaintext highlighter-rouge">.call()</code>，中间几轮「模型返回 tool_calls → 执行 Java 方法 → 把结果塞回 Prompt → 再调模型」在模型实现内部悄悄完成，控制台往往只能看到首尾两次 HTTP 请求。</p>

<p>Spring AI 2.0 把这条循环<strong>抬进 Advisor 链</strong>。<a href="https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java"><code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code></a> 在 <code class="language-plaintext highlighter-rouge">@since 2.0.0</code> 引入，注释写得很直白：<em>「Recursive Advisor that disables the internal tool execution flow and instead implements the tool calling loop as part of the advisor chain.」</em><sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> 与此同时，各 Provider 的 <code class="language-plaintext highlighter-rouge">ChatModel</code> <strong>不再</strong>自带内部工具执行循环<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>。</p>

<p><a href="https://github.com/liweinan/springai_demo"><code class="language-plaintext highlighter-rouge">springai_demo</code></a> 正是为吃透这一变化而写的 Fullstack 学习项目：React 双栏订票列表 + 自然语言聊天，后端 Spring Boot 4.1 + Spring AI 2.0 + DeepSeek <code class="language-plaintext highlighter-rouge">deepseek-chat</code>，用 <code class="language-plaintext highlighter-rouge">@Tool</code> 把「订票 / 取消 / 查列表」暴露给大模型，ReAct 循环由框架驱动，业务侧仍只需一行 <code class="language-plaintext highlighter-rouge">chatClient.prompt().user().call().content()</code>。</p>

<p>全文脉络：</p>

<ol>
  <li><strong>项目架构</strong> — 前后端分层、写入口收敛、一次聊天的时序</li>
  <li><strong>前端设计</strong> — 容器/展示分离、API 封装、聊天后刷新列表</li>
  <li><strong>实现亮点</strong> — 业务兜底、PromptLoggingAdvisor 调用链与 before/after 时机、E2E 与 Docker Compose 编排</li>
  <li><strong>Spring AI 2 核心变动</strong> — 1.x 三种写法回溯、Advisor 链源码走读（<code class="language-plaintext highlighter-rouge">nextCall</code> → <code class="language-plaintext highlighter-rouge">ChatModel</code>）、Upgrade Notes</li>
  <li><strong>变动在项目中的落地</strong> — <code class="language-plaintext highlighter-rouge">ChatConfig</code>、<code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code>、日志如何读 ReAct</li>
</ol>

<hr />

<h2 id="一项目架构经典三层--ai-工具层">一、项目架构：经典三层 + AI 工具层</h2>

<h3 id="11-技术栈与职责划分">1.1 技术栈与职责划分</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">层级</th>
      <th style="text-align: left">技术</th>
      <th style="text-align: left">职责</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">前端</td>
      <td style="text-align: left">React 18、TypeScript、Vite</td>
      <td style="text-align: left">双栏列表 + 聊天 UI；<code class="language-plaintext highlighter-rouge">/api</code> 代理到 8080</td>
    </tr>
    <tr>
      <td style="text-align: left">后端</td>
      <td style="text-align: left">Spring Boot 4.1、Spring Data JPA、H2</td>
      <td style="text-align: left">REST API、订票业务、ChatClient 入口</td>
    </tr>
    <tr>
      <td style="text-align: left">AI</td>
      <td style="text-align: left">Spring AI 2.0、<code class="language-plaintext highlighter-rouge">spring-ai-starter-model-deepseek</code></td>
      <td style="text-align: left">DeepSeek ChatModel、Tool Calling、Advisor 链</td>
    </tr>
    <tr>
      <td style="text-align: left">测试</td>
      <td style="text-align: left">Playwright E2E</td>
      <td style="text-align: left">列表、聊天订票、取消、健康检查</td>
    </tr>
  </tbody>
</table>

<p>后端 Maven 坐标见 <a href="https://github.com/liweinan/springai_demo/blob/main/backend/pom.xml"><code class="language-plaintext highlighter-rouge">backend/pom.xml</code></a>：<code class="language-plaintext highlighter-rouge">spring-boot-starter-parent</code> <strong>4.1.0</strong>，<code class="language-plaintext highlighter-rouge">spring-ai-bom</code> <strong>2.0.0</strong>。Spring AI 2.0 基于 Spring Boot 4 依赖模型，不能与 Boot 3.x 混用<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>。</p>

<h3 id="12-整体数据流">1.2 整体数据流</h3>

<pre><code class="language-mermaid">flowchart TB
  subgraph browser [浏览器 localhost:5173]
    App[App.tsx]
    List[BookingList 双栏]
    Chat[ChatPanel 聊天]
    App --&gt; List
    App --&gt; Chat
  end

  subgraph vite [Vite Dev Server]
    Proxy["/api → :8080"]
  end

  subgraph backend [Spring Boot localhost:8080]
    BC[BookingController]
    CC[ChatController]
    BS[BookingService]
    CS[ChatService]
    BT[BookingTools @Tool]
    BR[BookingRepository]
    CFG[ChatConfig → ChatClient]
    DB[(H2 bookings)]
    BC --&gt; BS
    CC --&gt; CS
    CS --&gt; CFG
    CFG --&gt; ChatModel[DeepSeekChatModel]
    CFG --&gt; BT
    BT --&gt; BS
    BS --&gt; BR
    BR --&gt; DB
  end

  subgraph external [外部]
    DS[DeepSeek API]
  end

  App --&gt;|fetch /api| Proxy
  Proxy --&gt; BC
  Proxy --&gt; CC
  ChatModel --&gt; DS
</code></pre>

<p><strong>设计原则</strong>：所有<strong>写数据库</strong>的操作只经过 <a href="https://github.com/liweinan/springai_demo/blob/main/backend/src/main/java/com/demo/booking/service/BookingService.java"><code class="language-plaintext highlighter-rouge">BookingService</code></a>。REST 的 <code class="language-plaintext highlighter-rouge">BookingController</code> 与 AI 的 <code class="language-plaintext highlighter-rouge">BookingTools</code> 都调它——避免「聊天改了一套、列表查另一套」的分叉。</p>

<p>后端包结构可概括为：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">层次</th>
      <th style="text-align: left">包</th>
      <th style="text-align: left">能否直接碰 DB</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">表现层</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">controller/</code></td>
      <td style="text-align: left">否</td>
    </tr>
    <tr>
      <td style="text-align: left">业务层</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">service/</code></td>
      <td style="text-align: left">通过 Repository</td>
    </tr>
    <tr>
      <td style="text-align: left">AI 工具层</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">tools/</code></td>
      <td style="text-align: left">否，只调 Service</td>
    </tr>
    <tr>
      <td style="text-align: left">配置层</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">config/</code>、<code class="language-plaintext highlighter-rouge">advisor/</code></td>
      <td style="text-align: left">否</td>
    </tr>
    <tr>
      <td style="text-align: left">数据层</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">repository/</code>、<code class="language-plaintext highlighter-rouge">model/</code></td>
      <td style="text-align: left">是</td>
    </tr>
  </tbody>
</table>

<h3 id="13-领域模型与状态机">1.3 领域模型与状态机</h3>

<p>实体 <a href="https://github.com/liweinan/springai_demo/blob/main/backend/src/main/java/com/demo/booking/model/Booking.java"><code class="language-plaintext highlighter-rouge">Booking</code></a> 只有 <code class="language-plaintext highlighter-rouge">id</code>、<code class="language-plaintext highlighter-rouge">title</code>、<code class="language-plaintext highlighter-rouge">status</code>（<code class="language-plaintext highlighter-rouge">SUBSCRIBED</code> / <code class="language-plaintext highlighter-rouge">UNSUBSCRIBED</code>）。启动时 <a href="https://github.com/liweinan/springai_demo/blob/main/backend/src/main/resources/data.sql"><code class="language-plaintext highlighter-rouge">data.sql</code></a> 插入 3 条未订阅票；H2 内存库，进程退出即清空。</p>

<p>前端 <a href="https://github.com/liweinan/springai_demo/blob/main/frontend/src/App.tsx"><code class="language-plaintext highlighter-rouge">App.tsx</code></a> 在挂载时并行拉两栏列表；聊天成功后<strong>必须</strong>再次 <code class="language-plaintext highlighter-rouge">loadBookings()</code>——因为数据库已被 <code class="language-plaintext highlighter-rouge">@Tool</code> 修改，React state 不会自动同步。</p>

<h3 id="14-一次我要订票的时序">1.4 一次「我要订票」的时序</h3>

<pre><code class="language-mermaid">sequenceDiagram
  participant User as 用户
  participant FE as React App
  participant CC as ChatController
  participant CS as ChatService
  participant Client as ChatClient
  participant Adv as ToolCallingAdvisor
  participant Log as PromptLoggingAdvisor
  participant DS as DeepSeek
  participant Tools as BookingTools
  participant Svc as BookingService
  participant DB as H2

  User-&gt;&gt;FE: 输入「我要订票」
  FE-&gt;&gt;CC: POST /api/chat
  CC-&gt;&gt;CS: chat(message)
  CS-&gt;&gt;Client: prompt().user().call()
  Client-&gt;&gt;Adv: adviseCall（TCA 远离 ChatModel，驱动 ReAct）
  Note over Adv,Log: 第 1 轮 copy(this).nextCall()
  Adv-&gt;&gt;Log: before() 第1步
  Log-&gt;&gt;DS: ChatModel HTTP
  DS--&gt;&gt;Log: tool_call subscribeTicket
  Log-&gt;&gt;Adv: after() 第1步
  Adv-&gt;&gt;Tools: executeToolCalls subscribeTicket
  Tools-&gt;&gt;Svc: subscribeTicket(null)
  Svc-&gt;&gt;DB: UPDATE status=SUBSCRIBED
  Note over Adv,Log: 第 2 轮 copy(this).nextCall()
  Adv-&gt;&gt;Log: before() 第2步 含 TOOL_RESPONSE
  Log-&gt;&gt;DS: ChatModel HTTP
  DS--&gt;&gt;Log: 最终中文 reply
  Log-&gt;&gt;Adv: after() 第2步
  Adv--&gt;&gt;CS: content()
  CS--&gt;&gt;FE: JSON reply
  FE-&gt;&gt;FE: GET /api/bookings 刷新双栏
</code></pre>

<p>用户只说「我要订票」、未指定票名时，<code class="language-plaintext highlighter-rouge">BookingService.subscribeTicket(null)</code> 会订<strong>第一张</strong>未订阅票（按 id 排序）——这是刻意为 AI 传参不准准备的兜底，降低 Tool 调用成功但业务失败的概率。</p>

<h3 id="15-前端设计刻意保持薄-ui厚后端">1.5 前端设计：刻意保持「薄 UI、厚后端」</h3>

<p>前端没有引入 Redux、React Router 或 UI 库，目的是把学习成本压在 <strong>Spring AI 与 ReAct</strong> 上。结构遵循经典的<strong>容器组件 / 展示组件</strong>分离：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">文件</th>
      <th style="text-align: left">角色</th>
      <th style="text-align: left">是否调 API</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><a href="https://github.com/liweinan/springai_demo/blob/main/frontend/src/App.tsx"><code class="language-plaintext highlighter-rouge">App.tsx</code></a></td>
      <td style="text-align: left">页面总控：state、拉列表、处理聊天回调</td>
      <td style="text-align: left">是</td>
    </tr>
    <tr>
      <td style="text-align: left"><a href="https://github.com/liweinan/springai_demo/blob/main/frontend/src/components/BookingList.tsx"><code class="language-plaintext highlighter-rouge">BookingList.tsx</code></a></td>
      <td style="text-align: left">双栏列表纯展示</td>
      <td style="text-align: left">否</td>
    </tr>
    <tr>
      <td style="text-align: left"><a href="https://github.com/liweinan/springai_demo/blob/main/frontend/src/components/ChatPanel.tsx"><code class="language-plaintext highlighter-rouge">ChatPanel.tsx</code></a></td>
      <td style="text-align: left">聊天 UI；发送逻辑由父组件 <code class="language-plaintext highlighter-rouge">onSend</code> 注入</td>
      <td style="text-align: left">否</td>
    </tr>
    <tr>
      <td style="text-align: left"><a href="https://github.com/liweinan/springai_demo/blob/main/frontend/src/api/client.ts"><code class="language-plaintext highlighter-rouge">api/client.ts</code></a></td>
      <td style="text-align: left">通用 <code class="language-plaintext highlighter-rouge">getJson</code> / <code class="language-plaintext highlighter-rouge">postJson</code></td>
      <td style="text-align: left">—</td>
    </tr>
    <tr>
      <td style="text-align: left"><a href="https://github.com/liweinan/springai_demo/blob/main/frontend/src/api/bookingApi.ts"><code class="language-plaintext highlighter-rouge">api/bookingApi.ts</code></a></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">GET /api/bookings?status=...</code></td>
      <td style="text-align: left">—</td>
    </tr>
    <tr>
      <td style="text-align: left"><a href="https://github.com/liweinan/springai_demo/blob/main/frontend/src/api/chatApi.ts"><code class="language-plaintext highlighter-rouge">api/chatApi.ts</code></a></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">POST /api/chat</code></td>
      <td style="text-align: left">—</td>
    </tr>
  </tbody>
</table>

<p><strong>状态设计</strong>：<code class="language-plaintext highlighter-rouge">App.tsx</code> 用 <code class="language-plaintext highlighter-rouge">useState</code> 维护两栏列表（<code class="language-plaintext highlighter-rouge">subscribed</code> / <code class="language-plaintext highlighter-rouge">unsubscribed</code>）、聊天历史（<code class="language-plaintext highlighter-rouge">messages</code>）、加载与错误态。挂载时 <code class="language-plaintext highlighter-rouge">Promise.all</code> 并行请求两个 status，减少首屏等待。</p>

<p><strong>与 AI 路径的关键衔接</strong>：聊天成功后必须再次 <code class="language-plaintext highlighter-rouge">loadBookings()</code>。ReAct 改的是 H2 数据库，React state 不会自动感知 <code class="language-plaintext highlighter-rouge">@Tool</code> 的副作用——<a href="https://github.com/liweinan/springai_demo/blob/main/frontend/src/App.tsx"><code class="language-plaintext highlighter-rouge">handleSendMessage</code></a> 在收到 <code class="language-plaintext highlighter-rouge">ChatResponse</code> 后显式刷新双栏，E2E 用例也正是靠「左栏 +1」来验证 Tool 真正执行。</p>

<p><strong>跨域策略</strong>：开发态所有请求走相对路径 <code class="language-plaintext highlighter-rouge">/api/...</code>，由 Vite dev server 代理到 Spring Boot，浏览器始终认为前后端同源。</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// vite.config.ts: 11-19</span>
<span class="nx">server</span><span class="p">:</span> <span class="p">{</span>
  <span class="nl">proxy</span><span class="p">:</span> <span class="p">{</span>
    <span class="dl">'</span><span class="s1">/api</span><span class="dl">'</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">target</span><span class="p">:</span> <span class="nx">apiProxyTarget</span><span class="p">,</span>  <span class="c1">// 本地默认 http://localhost:8080</span>
      <span class="na">changeOrigin</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="p">},</span>
  <span class="p">},</span>
<span class="p">},</span>
</code></pre></div></div>

<p>本地 <code class="language-plaintext highlighter-rouge">pnpm dev</code> 时 <code class="language-plaintext highlighter-rouge">apiProxyTarget</code> 默认为 <code class="language-plaintext highlighter-rouge">http://localhost:8080</code>；Docker Compose 里通过环境变量 <code class="language-plaintext highlighter-rouge">VITE_API_PROXY_TARGET=http://backend:8080</code> 把代理目标改成 Compose 网络内的服务名。后端 <a href="https://github.com/liweinan/springai_demo/blob/main/backend/src/main/java/com/demo/booking/config/WebConfig.java"><code class="language-plaintext highlighter-rouge">WebConfig</code></a> 另配了 CORS，允许 <code class="language-plaintext highlighter-rouge">http://localhost:5173</code> 直连 8080——便于 curl、Playwright 或不走代理的调试；日常开发仍以 Vite proxy 为主<sup id="fnref:10"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>。</p>

<p><strong>pnpm workspace</strong>：根目录 <a href="https://github.com/liweinan/springai_demo/blob/main/package.json"><code class="language-plaintext highlighter-rouge">package.json</code></a> + <a href="https://github.com/liweinan/springai_demo/blob/main/pnpm-workspace.yaml"><code class="language-plaintext highlighter-rouge">pnpm-workspace.yaml</code></a> 统一管理 <code class="language-plaintext highlighter-rouge">frontend</code> 与 <code class="language-plaintext highlighter-rouge">e2e</code>，根脚本 <code class="language-plaintext highlighter-rouge">pnpm dev</code> 等价于 <code class="language-plaintext highlighter-rouge">pnpm --dir frontend dev</code>。</p>

<hr />

<h2 id="二实现亮点">二、实现亮点</h2>

<h3 id="21-单一写入口--面向-ai-的业务规则">2.1 单一写入口 + 面向 AI 的业务规则</h3>

<p><a href="https://github.com/liweinan/springai_demo/blob/main/backend/src/main/java/com/demo/booking/tools/BookingTools.java"><code class="language-plaintext highlighter-rouge">BookingTools</code></a> 用 <code class="language-plaintext highlighter-rouge">@Tool</code> 暴露四个方法：查未订阅、查已订阅、订票、取消。每个方法只打日志并委托 <code class="language-plaintext highlighter-rouge">BookingService</code>，不碰 Repository。</p>

<p>取消逻辑比「模糊匹配 title」更进一步：<a href="https://github.com/liweinan/springai_demo/blob/main/backend/src/main/java/com/demo/booking/service/BookingService.java"><code class="language-plaintext highlighter-rouge">BookingService.cancelSubscription</code></a> 支持航班号（<code class="language-plaintext highlighter-rouge">G123</code>）、「北京到上海」与「北京-上海 G123」的归一化、用户只说「取消订票」且仅一张已订阅票时的兜底。这些规则对 REST 与 AI <strong>共用</strong>，大模型只需选对工具，不必背 SQL。</p>

<p>System Prompt 在 <a href="https://github.com/liweinan/springai_demo/blob/main/backend/src/main/java/com/demo/booking/config/ChatConfig.java"><code class="language-plaintext highlighter-rouge">ChatConfig</code></a> 里用 <code class="language-plaintext highlighter-rouge">defaultSystem</code> 约束：<strong>禁止未调工具就声称成功</strong>、订票/取消/查列表必须走对应 <code class="language-plaintext highlighter-rouge">@Tool</code>。配合 <code class="language-plaintext highlighter-rouge">temperature: 0.1</code>（<a href="https://github.com/liweinan/springai_demo/blob/main/backend/src/main/resources/application.yml"><code class="language-plaintext highlighter-rouge">application.yml</code></a>），让模型更稳定地触发 function calling。</p>

<h3 id="22-promptloggingadvisor把-react-从黑盒里拽出来">2.2 PromptLoggingAdvisor：把 ReAct 从黑盒里拽出来</h3>

<p>Spring AI 2.0 的关键收益是：<strong>order 大于 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code>（+300）的 Advisor，会挂在每一轮 <code class="language-plaintext highlighter-rouge">copy(this).nextCall()</code> 子链上、更靠近 <code class="language-plaintext highlighter-rouge">ChatModel</code></strong>。<a href="https://github.com/liweinan/springai_demo/blob/main/backend/src/main/java/com/demo/booking/advisor/PromptLoggingAdvisor.java"><code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code></a> 的 <code class="language-plaintext highlighter-rouge">getOrder()</code> 返回 <code class="language-plaintext highlighter-rouge">HIGHEST_PRECEDENCE + 400</code>，比 +300 <strong>更接近</strong> <code class="language-plaintext highlighter-rouge">ChatModelCallAdvisor</code>，因此在每一轮「发 Prompt → 收 Response → 执行 Tool → 再发 Prompt」时都能逐步观测。更细的调用链、before/after 时机与日志解读见项目 <a href="https://github.com/liweinan/springai_demo/blob/main/docs/PROMPT_LOGGING_ADVISOR.md"><code class="language-plaintext highlighter-rouge">docs/PROMPT_LOGGING_ADVISOR.md</code></a>。</p>

<h4 id="221-核心结论">2.2.1 核心结论</h4>

<table>
  <thead>
    <tr>
      <th style="text-align: left">问题</th>
      <th style="text-align: left">答案</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">谁调用 <code class="language-plaintext highlighter-rouge">before</code> / <code class="language-plaintext highlighter-rouge">after</code>？</td>
      <td style="text-align: left"><strong>Spring AI Advisor 链</strong>，不是业务代码</td>
    </tr>
    <tr>
      <td style="text-align: left">业务代码直接碰这个类做什么？</td>
      <td style="text-align: left">仅 <code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor.resetSteps()</code> 重置 <code class="language-plaintext highlighter-rouge">[AI 第N步]</code> 计数</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">before</code> 何时触发？</td>
      <td style="text-align: left"><strong>每一轮</strong>调用 DeepSeek <strong>之前</strong></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">after</code> 何时触发？</td>
      <td style="text-align: left">DeepSeek <strong>刚返回之后</strong>、<strong>执行 <code class="language-plaintext highlighter-rouge">@Tool</code> Java 方法之前</strong></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">[Tool 被调用]</code> 何时出现？</td>
      <td style="text-align: left">在 <strong><code class="language-plaintext highlighter-rouge">after</code> 之后</strong>，由 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> → <code class="language-plaintext highlighter-rouge">ToolCallingManager</code> 触发</td>
    </tr>
    <tr>
      <td style="text-align: left">为何要 <code class="language-plaintext highlighter-rouge">order = +400</code>？</td>
      <td style="text-align: left">必须 <strong>大于</strong> <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code>（+300），才能进入每一轮 ReAct 的 <code class="language-plaintext highlighter-rouge">copy().nextCall()</code> <strong>子链</strong>（更靠近 <code class="language-plaintext highlighter-rouge">ChatModel</code>）</td>
    </tr>
  </tbody>
</table>

<p><strong>一句话</strong>：<code class="language-plaintext highlighter-rouge">before</code> / <code class="language-plaintext highlighter-rouge">after</code> 包的是 <strong>单次模型 HTTP 往返</strong>，不包含工具 Java 执行本身。</p>

<h4 id="222-注册与入口">2.2.2 注册与入口</h4>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Advisor</th>
      <th style="text-align: left">order</th>
      <th style="text-align: left">如何注册</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">HIGHEST_PRECEDENCE + 300</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">defaultTools(bookingTools)</code> 后 <strong>Spring AI 2.0 自动注册</strong></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">HIGHEST_PRECEDENCE + 400</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ChatConfig</code> 显式 <code class="language-plaintext highlighter-rouge">.defaultAdvisors(new PromptLoggingAdvisor())</code></td>
    </tr>
  </tbody>
</table>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ChatConfig.java: 61-62</span>
<span class="o">.</span><span class="na">defaultTools</span><span class="o">(</span><span class="n">bookingTools</span><span class="o">)</span>   <span class="c1">// 工具 schema 写入 options.toolCallbacks，非 SYSTEM 文本</span>
<span class="o">.</span><span class="na">defaultAdvisors</span><span class="o">(</span><span class="k">new</span> <span class="nc">PromptLoggingAdvisor</span><span class="o">())</span>  <span class="c1">// INFO=messages，DEBUG=tools</span>
</code></pre></div></div>

<p><strong>注意</strong>：不要再手动 <code class="language-plaintext highlighter-rouge">.advisors(ToolCallingAdvisor.builder()...)</code>，否则会与自动注册的实例 <strong>重复</strong>，链中出现两个工具 Advisor。</p>

<p><a href="https://github.com/liweinan/springai_demo/blob/main/backend/src/main/java/com/demo/booking/service/ChatService.java"><code class="language-plaintext highlighter-rouge">ChatService.chat</code></a> 在每次用户聊天前调用 <code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor.resetSteps()</code>，保证一次 HTTP 请求从 <code class="language-plaintext highlighter-rouge">[AI 第1步]</code> 重新计数；业务代码 <strong>从不</strong> 直接调用 <code class="language-plaintext highlighter-rouge">before()</code> / <code class="language-plaintext highlighter-rouge">after()</code>：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ChatService.java: 60-67</span>
<span class="nc">PromptLoggingAdvisor</span><span class="o">.</span><span class="na">resetSteps</span><span class="o">();</span>
<span class="nc">String</span> <span class="n">reply</span> <span class="o">=</span> <span class="n">chatClient</span>
        <span class="o">.</span><span class="na">prompt</span><span class="o">()</span>
        <span class="o">.</span><span class="na">user</span><span class="o">(</span><span class="n">userMessage</span><span class="o">.</span><span class="na">trim</span><span class="o">())</span>
        <span class="o">.</span><span class="na">call</span><span class="o">()</span>
        <span class="o">.</span><span class="na">content</span><span class="o">();</span>
</code></pre></div></div>

<h4 id="223-spring-ai-如何调用-before--after">2.2.3 Spring AI 如何调用 <code class="language-plaintext highlighter-rouge">before</code> / <code class="language-plaintext highlighter-rouge">after</code>？</h4>

<p><code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code> 实现 <a href="https://github.com/spring-projects/spring-ai/blob/v2.0.0/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/api/BaseAdvisor.java"><code class="language-plaintext highlighter-rouge">BaseAdvisor</code></a>。框架对 <code class="language-plaintext highlighter-rouge">BaseAdvisor</code> 的默认 <code class="language-plaintext highlighter-rouge">adviseCall</code> 模板是：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// BaseAdvisor.java: 47-53</span>
<span class="k">default</span> <span class="nc">ChatClientResponse</span> <span class="nf">adviseCall</span><span class="o">(</span><span class="nc">ChatClientRequest</span> <span class="n">chatClientRequest</span><span class="o">,</span> <span class="nc">CallAdvisorChain</span> <span class="n">callAdvisorChain</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">ChatClientRequest</span> <span class="n">processedChatClientRequest</span> <span class="o">=</span> <span class="n">before</span><span class="o">(</span><span class="n">chatClientRequest</span><span class="o">,</span> <span class="n">callAdvisorChain</span><span class="o">);</span>
    <span class="nc">ChatClientResponse</span> <span class="n">chatClientResponse</span> <span class="o">=</span> <span class="n">callAdvisorChain</span><span class="o">.</span><span class="na">nextCall</span><span class="o">(</span><span class="n">processedChatClientRequest</span><span class="o">);</span>
    <span class="k">return</span> <span class="nf">after</span><span class="o">(</span><span class="n">chatClientResponse</span><span class="o">,</span> <span class="n">callAdvisorChain</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>调用链：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ChatService.chat()
    └─ chatClient.prompt().user(...).call()
        └─ DefaultChatClient.doGetObservableChatClientResponse()
            └─ DefaultAroundAdvisorChain.nextCall()     【链调度器】
                └─ advisor.adviseCall(...)              【按 order 逐个 Advisor】
                    └─ PromptLoggingAdvisor
                        ├─ before()   ← 日志
                        ├─ nextCall() → 更靠近 ChatModel
                        └─ after()    ← 日志
</code></pre></div></div>

<p><a href="https://github.com/spring-projects/spring-ai/blob/v2.0.0/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/DefaultAroundAdvisorChain.java#L98-L120"><code class="language-plaintext highlighter-rouge">DefaultAroundAdvisorChain.nextCall</code></a> 从 deque 弹出 <strong>order 最小</strong>（<strong>最远离 ChatModel</strong>）的 Advisor，执行其 <code class="language-plaintext highlighter-rouge">adviseCall</code>；该 Advisor 若继续 <code class="language-plaintext highlighter-rouge">nextCall()</code>，则向 <strong>更靠近 ChatModel</strong> 的方向递进。完整链路见 <strong>§3.2.1</strong>。</p>

<h4 id="224-order-与距-chatmodel-远近为何必须是-400">2.2.4 order 与距 ChatModel 远近：为何必须是 +400</h4>

<p><strong>速记</strong>：在 Advisor 链上，<strong>order 数值越大 → 越靠近 <code class="language-plaintext highlighter-rouge">ChatModel</code></strong>；<strong>order 越小 → 越远离 <code class="language-plaintext highlighter-rouge">ChatModel</code></strong>（到达模型前要经过更多 Advisor）。链终点是 <code class="language-plaintext highlighter-rouge">ChatModelCallAdvisor</code>（<code class="language-plaintext highlighter-rouge">Ordered.LOWEST_PRECEDENCE</code>，order 最大）。</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Advisor</th>
      <th style="text-align: left">order</th>
      <th style="text-align: left">距 ChatModel</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code></td>
      <td style="text-align: left">+300</td>
      <td style="text-align: left"><strong>较远</strong>：驱动 ReAct <code class="language-plaintext highlighter-rouge">do-while</code>，自身不直接调模型</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code></td>
      <td style="text-align: left">+400</td>
      <td style="text-align: left"><strong>更近</strong>：每轮 <code class="language-plaintext highlighter-rouge">copy().nextCall()</code> 子链上，紧贴 <code class="language-plaintext highlighter-rouge">ChatModelCallAdvisor</code> 之前</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ChatModelCallAdvisor</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">LOWEST_PRECEDENCE</code></td>
      <td style="text-align: left"><strong>最近</strong>：链终点，<code class="language-plaintext highlighter-rouge">chatModel.call()</code></td>
    </tr>
  </tbody>
</table>

<p>若 <code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code> 的 order <strong>≤ +300</strong>（与 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> 同级或更远离 ChatModel），它不会进入每轮 <code class="language-plaintext highlighter-rouge">copy(this).nextCall()</code> 子链，往往 <strong>只能看到 ReAct 首尾</strong>，看不到中间 <code class="language-plaintext highlighter-rouge">[AI 第1步] tool_calls</code> → <code class="language-plaintext highlighter-rouge">[AI 第2步] TOOL_RESPONSE</code> 的完整往返。</p>

<h4 id="225-react-每轮before--after-的精确时机">2.2.5 ReAct 每轮：<code class="language-plaintext highlighter-rouge">before</code> / <code class="language-plaintext highlighter-rouge">after</code> 的精确时机</h4>

<p><code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> <strong>不使用</strong> <code class="language-plaintext highlighter-rouge">BaseAdvisor</code> 的默认 <code class="language-plaintext highlighter-rouge">adviseCall</code>，而是自定义 <code class="language-plaintext highlighter-rouge">do { ... } while (isToolCall)</code> 循环。每一轮核心逻辑（简化）：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ToolCallingAdvisor.java（简化）</span>
<span class="k">do</span> <span class="o">{</span>
    <span class="n">processedChatClientRequest</span> <span class="o">=</span> <span class="n">doBeforeCall</span><span class="o">(...);</span>
    <span class="n">chatClientResponse</span> <span class="o">=</span> <span class="n">callAdvisorChain</span><span class="o">.</span><span class="na">copy</span><span class="o">(</span><span class="k">this</span><span class="o">).</span><span class="na">nextCall</span><span class="o">(</span><span class="n">processedChatClientRequest</span><span class="o">);</span>
    <span class="c1">// ↑ 子链（更靠近 ChatModel）：PromptLoggingAdvisor.before → ChatModel → PromptLoggingAdvisor.after</span>
    <span class="n">chatClientResponse</span> <span class="o">=</span> <span class="n">doAfterCall</span><span class="o">(...);</span>

    <span class="n">isToolCall</span> <span class="o">=</span> <span class="n">toolExecutionEligibilityChecker</span><span class="o">.</span><span class="na">isToolCallResponse</span><span class="o">(</span><span class="n">chatResponse</span><span class="o">);</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">isToolCall</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">toolExecutionResult</span> <span class="o">=</span> <span class="n">toolCallingManager</span><span class="o">.</span><span class="na">executeToolCalls</span><span class="o">(...);</span>  <span class="c1">// ← BookingTools 在这里</span>
        <span class="n">instructions</span> <span class="o">=</span> <span class="n">doGetNextInstructionsForToolCall</span><span class="o">(...);</span>
    <span class="o">}</span>
<span class="o">}</span> <span class="k">while</span> <span class="o">(</span><span class="n">isToolCall</span><span class="o">);</span>
</code></pre></div></div>

<p>单轮时序：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ToolCallingAdvisor 第 N 轮
    │
    ├─ PromptLoggingAdvisor.before()          【打 [AI 第N步] 发送 Prompt】
    ├─ ChatModel → DeepSeek HTTP              【真正模型请求】
    ├─ PromptLoggingAdvisor.after()           【打 [AI 第N步] 收到 Response】
    │       （可能是 tool_calls，也可能是最终中文）
    └─ 回到 ToolCallingAdvisor
            ├─ 若 hasToolCalls → executeToolCalls()
            │       └─ MethodToolCallback → BookingTools  【打 [Tool 被调用]】
            └─ 更新 instructions，进入第 N+1 轮
</code></pre></div></div>

<p><strong>易错点</strong>：<code class="language-plaintext highlighter-rouge">[Tool 被调用]</code> <strong>不在</strong> <code class="language-plaintext highlighter-rouge">before</code> 与 <code class="language-plaintext highlighter-rouge">after</code> 之间，而在 <strong><code class="language-plaintext highlighter-rouge">after</code> 返回之后</strong>。</p>

<p>以「我要订票」为例的完整日志顺序：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">顺序</th>
      <th style="text-align: left">日志 / 事件</th>
      <th style="text-align: left">发生环节</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">1</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">[Chat] 收到用户消息: 我要订票</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ChatService</code></td>
    </tr>
    <tr>
      <td style="text-align: left">2</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">[AI 第1步] 发送 Prompt</code> — SYSTEM + USER</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">before()</code> 第 1 轮</td>
    </tr>
    <tr>
      <td style="text-align: left">3</td>
      <td style="text-align: left">DEBUG：<code class="language-plaintext highlighter-rouge">注册的工具</code> … subscribeTicket 等</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">before()</code> 第 1 轮</td>
    </tr>
    <tr>
      <td style="text-align: left">4</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">[AI 第1步] 收到 Response</code> — ASSISTANT (tool_calls)</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">after()</code> 第 1 轮</td>
    </tr>
    <tr>
      <td style="text-align: left">5</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">[Tool 被调用] subscribeTicket</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> 执行工具</td>
    </tr>
    <tr>
      <td style="text-align: left">6</td>
      <td style="text-align: left">Hibernate UPDATE bookings</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">BookingService</code></td>
    </tr>
    <tr>
      <td style="text-align: left">7</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">[AI 第2步] 发送 Prompt</code> — 含 TOOL_RESPONSE</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">before()</code> 第 2 轮</td>
    </tr>
    <tr>
      <td style="text-align: left">8</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">[AI 第2步] 收到 Response</code> — 最终中文</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">after()</code> 第 2 轮</td>
    </tr>
    <tr>
      <td style="text-align: left">9</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">[Chat] AI 回复: ...</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ChatService</code> 拿到 <code class="language-plaintext highlighter-rouge">content()</code></td>
    </tr>
  </tbody>
</table>

<p>一次用户 <code class="language-plaintext highlighter-rouge">POST /api/chat</code> 对应 <strong>一次</strong> <code class="language-plaintext highlighter-rouge">chatClient.call()</code>，但 Advisor 链内可能有 <strong>多轮</strong> DeepSeek 请求。</p>

<pre><code class="language-mermaid">sequenceDiagram
  autonumber
  participant CS as ChatService
  participant TCA as ToolCallingAdvisor
  participant PLA as PromptLoggingAdvisor
  participant DS as DeepSeek
  participant BT as BookingTools

  CS-&gt;&gt;CS: resetSteps()
  CS-&gt;&gt;TCA: chatClient.call()

  Note over TCA,DS: 第 1 轮
  TCA-&gt;&gt;PLA: copy(this).nextCall()
  PLA-&gt;&gt;PLA: before() 第1步
  PLA-&gt;&gt;DS: ChatModel HTTP
  DS--&gt;&gt;PLA: tool_calls subscribeTicket
  PLA-&gt;&gt;PLA: after() 第1步
  PLA--&gt;&gt;TCA: ChatClientResponse

  TCA-&gt;&gt;BT: executeToolCalls()
  BT--&gt;&gt;TCA: BookingResponse

  Note over TCA,DS: 第 2 轮
  TCA-&gt;&gt;PLA: copy(this).nextCall()
  PLA-&gt;&gt;PLA: before() 第2步 含 TOOL_RESPONSE
  PLA-&gt;&gt;DS: ChatModel HTTP
  DS--&gt;&gt;PLA: 最终中文
  PLA-&gt;&gt;PLA: after() 第2步
  PLA--&gt;&gt;TCA: ChatClientResponse

  TCA--&gt;&gt;CS: content()
</code></pre>

<h4 id="226-before-与-after-各打印什么">2.2.6 <code class="language-plaintext highlighter-rouge">before()</code> 与 <code class="language-plaintext highlighter-rouge">after()</code> 各打印什么？</h4>

<p><code class="language-plaintext highlighter-rouge">before()</code> — 发送前：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">日志级别</th>
      <th style="text-align: left">内容</th>
      <th style="text-align: left">对应 DeepSeek 请求</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>INFO</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">messages</code>：SYSTEM / USER / ASSISTANT / TOOL_RESPONSE</td>
      <td style="text-align: left">请求体 <code class="language-plaintext highlighter-rouge">messages[]</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>DEBUG</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ToolDefinition</code>：name / description / inputSchema</td>
      <td style="text-align: left">请求体 <code class="language-plaintext highlighter-rouge">tools[]</code>（<strong>不在 messages 里</strong>）</td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">after()</code> — 模型返回后：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">模型输出</th>
      <th style="text-align: left">日志表现</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">要调工具</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ASSISTANT (tool_calls)</code> + <code class="language-plaintext highlighter-rouge">- subscribeTicket({})</code></td>
    </tr>
    <tr>
      <td style="text-align: left">最终回复</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ASSISTANT: 已为您成功订票...</code></td>
    </tr>
    <tr>
      <td style="text-align: left">工具结果（作为下一轮输入）</td>
      <td style="text-align: left">出现在 <strong>下一轮</strong> <code class="language-plaintext highlighter-rouge">before()</code> 的 <code class="language-plaintext highlighter-rouge">TOOL_RESPONSE</code>，不在本轮 <code class="language-plaintext highlighter-rouge">after()</code></td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">after()</code> <strong>不修改</strong> request/response，只观测并原样返回。还有一个容易踩坑的细节：<code class="language-plaintext highlighter-rouge">@Tool</code> 注册结果<strong>不在</strong> INFO 日志的 <code class="language-plaintext highlighter-rouge">messages</code> 里，而是进入 <code class="language-plaintext highlighter-rouge">ToolCallingChatOptions.toolCallbacks</code>，由 <code class="language-plaintext highlighter-rouge">DeepSeekChatModel</code> 转成 HTTP 请求体的 <code class="language-plaintext highlighter-rouge">tools</code> 字段——仅看 INFO 会误以为「没注册工具」。</p>

<h4 id="227-与-toolcallingadvisor-的分工">2.2.7 与 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> 的分工</h4>

<table>
  <thead>
    <tr>
      <th style="text-align: left">组件</th>
      <th style="text-align: left">职责</th>
      <th style="text-align: left">本 Demo 是否自定义</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code></td>
      <td style="text-align: left">ReAct <code class="language-plaintext highlighter-rouge">do-while</code>、检测 <code class="language-plaintext highlighter-rouge">tool_calls</code>、调用 <code class="language-plaintext highlighter-rouge">executeToolCalls</code>、拼下一轮 messages</td>
      <td style="text-align: left">否（框架自动）</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code></td>
      <td style="text-align: left">每轮模型往返前后打日志</td>
      <td style="text-align: left">是（本项目实现）</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">BookingTools</code></td>
      <td style="text-align: left">真正执行业务 <code class="language-plaintext highlighter-rouge">@Tool</code></td>
      <td style="text-align: left">是</td>
    </tr>
  </tbody>
</table>

<p>Spring AI 2.0 相对 1.x：<strong>ReAct 循环从 ChatModel 内部上移到 Advisor 链</strong>，因此 <code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code> 才能挂在 <strong>更靠近 ChatModel</strong> 的子链上逐步观测；1.x 路径 A 下，远离 ChatModel 的 Advisor 往往只能看到首尾两次调用（§3.0）。</p>

<h4 id="228-日志配置与验证关键字">2.2.8 日志配置与验证关键字</h4>

<table>
  <thead>
    <tr>
      <th style="text-align: left">环境</th>
      <th style="text-align: left">配置</th>
      <th style="text-align: left">能看到什么</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">本地默认</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">application.yml</code> → <code class="language-plaintext highlighter-rouge">com.demo.booking: INFO</code></td>
      <td style="text-align: left">仅 INFO：<code class="language-plaintext highlighter-rouge">messages</code> + Response</td>
    </tr>
    <tr>
      <td style="text-align: left">Docker Compose</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">LOGGING_LEVEL_COM_DEMO_BOOKING=DEBUG</code></td>
      <td style="text-align: left">额外 DEBUG：tools schema</td>
    </tr>
    <tr>
      <td style="text-align: left">临时本地 DEBUG</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">logging.level.com.demo.booking.advisor.PromptLoggingAdvisor=DEBUG</code></td>
      <td style="text-align: left">同上</td>
    </tr>
  </tbody>
</table>

<p><strong>勿</strong>在 <code class="language-plaintext highlighter-rouge">application.yml</code> 里单独把 <code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code> 锁为 INFO，否则会覆盖 Docker 的 DEBUG，看不到 <code class="language-plaintext highlighter-rouge">inputSchema</code>。</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">关键字</th>
      <th style="text-align: left">含义</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">[AI 第N步] 发送 Prompt</code></td>
      <td style="text-align: left">第 N 轮 <code class="language-plaintext highlighter-rouge">before()</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">[AI 第N步] 注册的工具</code></td>
      <td style="text-align: left">第 N 轮 <code class="language-plaintext highlighter-rouge">before()</code> DEBUG</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">[AI 第N步] 收到 Response</code></td>
      <td style="text-align: left">第 N 轮 <code class="language-plaintext highlighter-rouge">after()</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ASSISTANT (tool_calls)</code></td>
      <td style="text-align: left">模型决定调工具</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">TOOL_RESPONSE</code></td>
      <td style="text-align: left">下一轮 <code class="language-plaintext highlighter-rouge">before()</code> 中可见工具返回值</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">[Tool 被调用]</code></td>
      <td style="text-align: left">Java <code class="language-plaintext highlighter-rouge">@Tool</code> 真正执行（在 <code class="language-plaintext highlighter-rouge">after</code> 之后）</td>
    </tr>
  </tbody>
</table>

<h4 id="229-常见误解">2.2.9 常见误解</h4>

<table>
  <thead>
    <tr>
      <th style="text-align: left">误解</th>
      <th style="text-align: left">事实</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ChatService</code> 调用了 <code class="language-plaintext highlighter-rouge">before()</code></td>
      <td style="text-align: left">只调 <code class="language-plaintext highlighter-rouge">resetSteps()</code>；<code class="language-plaintext highlighter-rouge">before</code>/<code class="language-plaintext highlighter-rouge">after</code> 由 Advisor 链触发</td>
    </tr>
    <tr>
      <td style="text-align: left">工具执行在 <code class="language-plaintext highlighter-rouge">before</code> 和 <code class="language-plaintext highlighter-rouge">after</code> 之间</td>
      <td style="text-align: left">工具在 <strong><code class="language-plaintext highlighter-rouge">after</code> 之后</strong>，由 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> 执行</td>
    </tr>
    <tr>
      <td style="text-align: left">工具定义在 INFO 的 SYSTEM 里</td>
      <td style="text-align: left">工具在 <strong>DEBUG 的 options → API <code class="language-plaintext highlighter-rouge">tools</code> 字段</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">一次 chat 只对应一步 <code class="language-plaintext highlighter-rouge">[AI 第1步]</code></td>
      <td style="text-align: left">有 tool call 时通常 <strong>至少两步</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">order 越大越靠近 ChatModel</td>
      <td style="text-align: left"><strong>+400 &gt; +300</strong>：<code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code> 比 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> <strong>更靠近</strong> <code class="language-plaintext highlighter-rouge">ChatModel</code>；<code class="language-plaintext highlighter-rouge">ChatModelCallAdvisor</code> 的 order 最大，是链终点</td>
    </tr>
  </tbody>
</table>

<h3 id="23-chatconfig-刻意保持精简">2.3 ChatConfig 刻意保持精简</h3>

<p><a href="https://github.com/liweinan/springai_demo/blob/main/backend/src/main/java/com/demo/booking/config/ChatConfig.java"><code class="language-plaintext highlighter-rouge">ChatConfig</code></a> 只做三件事：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ChatConfig.java: 49-63</span>
<span class="k">return</span> <span class="nc">ChatClient</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="n">chatModel</span><span class="o">)</span>
        <span class="o">.</span><span class="na">defaultSystem</span><span class="o">(</span><span class="s">""" ... """</span><span class="o">)</span>
        <span class="o">.</span><span class="na">defaultTools</span><span class="o">(</span><span class="n">bookingTools</span><span class="o">)</span>
        <span class="o">.</span><span class="na">defaultAdvisors</span><span class="o">(</span><span class="k">new</span> <span class="nc">PromptLoggingAdvisor</span><span class="o">())</span>
        <span class="o">.</span><span class="na">build</span><span class="o">();</span>
</code></pre></div></div>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">defaultTools(bookingTools)</code></strong> — 注册 <code class="language-plaintext highlighter-rouge">@Tool</code>，并触发 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> 自动装配（见第三节）</li>
  <li><strong>不再</strong>手动 <code class="language-plaintext highlighter-rouge">new ToolCallAdvisor</code> / <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> — 显式再加会导致链中出现两个 <code class="language-plaintext highlighter-rouge">ToolAdvisor</code>，<a href="https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java"><code class="language-plaintext highlighter-rouge">DefaultChatClient</code></a> 会校验并报错</li>
  <li><strong><code class="language-plaintext highlighter-rouge">ToolCallingManager</code></strong> 仍由 Spring Boot 自动配置，业务代码无需注入</li>
</ul>

<h3 id="24-docker-compose开发态双容器编排">2.4 Docker Compose：开发态双容器编排</h3>

<p>项目<strong>刻意</strong>不做前端静态资源 + Nginx 的生产打包镜像，而是 Compose 里跑 <strong>Vite dev server + Spring Boot JAR</strong>——与本地 <code class="language-plaintext highlighter-rouge">mvn spring-boot:run</code> + <code class="language-plaintext highlighter-rouge">pnpm dev</code> 行为一致，改代码 rebuild 即可，适合 Demo 与学习。</p>

<p><a href="https://github.com/liweinan/springai_demo/blob/main/docker-compose.yml"><code class="language-plaintext highlighter-rouge">docker-compose.yml</code></a> 定义两个 service：</p>

<pre><code class="language-mermaid">flowchart LR
  subgraph host [宿主机]
    Browser[浏览器 :5173]
  end
  subgraph compose [Docker Compose 网络]
    FE[frontend 容器 Vite dev]
    BE[backend 容器 Spring Boot]
  end
  Browser --&gt;|/api 经 Vite proxy| FE
  FE --&gt;|VITE_API_PROXY_TARGET| BE
  BE --&gt;|DEEPSEEK_API_KEY| DS[DeepSeek API]
</code></pre>

<table>
  <thead>
    <tr>
      <th style="text-align: left">设计点</th>
      <th style="text-align: left">实现</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>启动顺序</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">frontend.depends_on.backend.condition: service_healthy</code> — 等 backend 通过 HEALTHCHECK 后再起 Vite，避免首屏 <code class="language-plaintext highlighter-rouge">/api</code> 502</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Backend 镜像</strong></td>
      <td style="text-align: left"><a href="https://github.com/liweinan/springai_demo/blob/main/backend/Dockerfile"><code class="language-plaintext highlighter-rouge">backend/Dockerfile</code></a> 多阶段：Maven 编译 → JRE 21 Alpine；内置 <code class="language-plaintext highlighter-rouge">curl</code> 供 HEALTHCHECK</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>存活探针 vs 完整检查</strong></td>
      <td style="text-align: left">HEALTHCHECK 调 <code class="language-plaintext highlighter-rouge">GET /api/health/live</code>（<a href="https://github.com/liweinan/springai_demo/blob/main/backend/src/main/java/com/demo/booking/controller/HealthController.java"><code class="language-plaintext highlighter-rouge">HealthController</code></a> 只返回 <code class="language-plaintext highlighter-rouge">up</code>，<strong>不</strong>调 DeepSeek）；手动验收 Key 用 <code class="language-plaintext highlighter-rouge">GET /api/health</code>（会 <code class="language-plaintext highlighter-rouge">ping</code> 一次模型，勿拿来做高频探针）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Frontend 镜像</strong></td>
      <td style="text-align: left"><a href="https://github.com/liweinan/springai_demo/blob/main/frontend/Dockerfile"><code class="language-plaintext highlighter-rouge">frontend/Dockerfile</code></a> 从<strong>仓库根</strong>复制 <code class="language-plaintext highlighter-rouge">pnpm-lock.yaml</code> 与 workspace 配置，<code class="language-plaintext highlighter-rouge">pnpm install --filter frontend...</code> 后 <code class="language-plaintext highlighter-rouge">pnpm dev --host 0.0.0.0</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>密钥注入</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">DEEPSEEK_API_KEY</code> 从宿主机环境或 <code class="language-plaintext highlighter-rouge">.env</code> 传入 backend 容器，<strong>不</strong> bake 进镜像</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>调试日志</strong></td>
      <td style="text-align: left">Compose 将 <code class="language-plaintext highlighter-rouge">LOGGING_LEVEL_COM_DEMO_BOOKING</code> 与 <code class="language-plaintext highlighter-rouge">LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_AI</code> 设为 <strong>DEBUG</strong>，便于在容器日志里看到 <code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code> 打印的工具 schema</td>
    </tr>
  </tbody>
</table>

<p>一键启动：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">DEEPSEEK_API_KEY</span><span class="o">=</span>your-key-here
docker compose up <span class="nt">--build</span>
<span class="c"># 前端 http://localhost:5173  后端 http://localhost:8080</span>
</code></pre></div></div>

<h3 id="25-工程化e2e-与健康检查">2.5 工程化：E2E 与健康检查</h3>

<ul>
  <li><strong>Playwright</strong>（<a href="https://github.com/liweinan/springai_demo/blob/main/e2e/tests/app.spec.ts"><code class="language-plaintext highlighter-rouge">e2e/tests/app.spec.ts</code></a>）：初始 3 未订阅 / 0 已订阅、聊天订票、聊天取消、健康检查；<code class="language-plaintext highlighter-rouge">capture-screenshots</code> 脚本生成 README 效果截图</li>
  <li><strong>API Key</strong> 仅通过环境变量注入，仓库内无密钥；跨域与 Proxy 实测见 <a href="https://github.com/liweinan/springai_demo/blob/main/docs/CORS.md"><code class="language-plaintext highlighter-rouge">docs/CORS.md</code></a></li>
</ul>

<p>更细的模块说明见项目 <a href="https://github.com/liweinan/springai_demo/blob/main/docs/ARCHITECTURE.md"><code class="language-plaintext highlighter-rouge">docs/ARCHITECTURE.md</code></a>。</p>

<hr />

<h2 id="三spring-ai-20-核心变动对照源码">三、Spring AI 2.0 核心变动（对照源码）</h2>

<p>官方 <a href="https://docs.spring.io/spring-ai/reference/2.0/upgrade-notes.html">Upgrade Notes 2.0</a> 篇幅很长；对本 Demo 与 Tool Calling 场景，下面几条最关键。先回答一个常见误解：<strong>Spring AI 1.x 并非做不了订票 ReAct</strong>——<code class="language-plaintext highlighter-rouge">@Tool</code> + <code class="language-plaintext highlighter-rouge">ChatClient</code> 在 1.x 就能跑通业务；变的是<strong>循环放在哪、Advisor 能否插进中间轮次、以及配置要手写多少</strong>。</p>

<h3 id="30-回溯1x-下同一-demo-怎么写已对照-v118--v200-tag-源码">3.0 回溯：1.x 下同一 Demo 怎么写？（已对照 <code class="language-plaintext highlighter-rouge">v1.1.8</code> / <code class="language-plaintext highlighter-rouge">v2.0.0</code> tag 源码）</h3>

<p>以下结论来自本地 <a href="https://github.com/spring-projects/spring-ai"><code class="language-plaintext highlighter-rouge">spring-ai</code></a> 仓库 tag 比对：<strong><code class="language-plaintext highlighter-rouge">v1.1.8</code></strong>（1.x 末版）、<strong><code class="language-plaintext highlighter-rouge">v2.0.0</code></strong>（2.0 首发）。1.x 内部仍有版本差：<strong><code class="language-plaintext highlighter-rouge">ToolCallAdvisor</code> 自 <code class="language-plaintext highlighter-rouge">v1.1.0</code> 才出现</strong>；<code class="language-plaintext highlighter-rouge">v1.0.x</code> 只有「<code class="language-plaintext highlighter-rouge">ChatModel</code> 内部循环」或手写 <code class="language-plaintext highlighter-rouge">while</code>，没有 Advisor 链式 ReAct。</p>

<p>本 Demo 的三块能力拆开看：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">能力</th>
      <th style="text-align: left">1.x 能否实现</th>
      <th style="text-align: left">说明（对照 tag）</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Tool</code> 订票 / 取消 / 查列表</td>
      <td style="text-align: left">✅ 可以</td>
      <td style="text-align: left">业务代码与版本无关</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">chatClient.prompt().user().call()</code> 驱动 ReAct</td>
      <td style="text-align: left">✅ 可以（路径 A）</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">v1.1.8</code> 的 <a href="https://github.com/spring-projects/spring-ai/blob/v1.1.8/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java"><code class="language-plaintext highlighter-rouge">DeepSeekChatModel</code></a> 在 <code class="language-plaintext highlighter-rouge">call()</code> 内递归 <code class="language-plaintext highlighter-rouge">internalCall</code>，由 <code class="language-plaintext highlighter-rouge">toolExecutionEligibilityPredicate</code> 触发 <code class="language-plaintext highlighter-rouge">executeToolCalls</code><sup id="fnref:14"><a href="#fn:14" class="footnote" rel="footnote" role="doc-noteref">5</a></sup></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code> 逐步打印每轮 Prompt</td>
      <td style="text-align: left">⚠️ 分路径</td>
      <td style="text-align: left"><strong>路径 A 默认不行</strong>；<strong><code class="language-plaintext highlighter-rouge">v1.1.0+</code> 路径 B</strong> 手动注册 <code class="language-plaintext highlighter-rouge">ToolCallAdvisor</code> 后<strong>可以</strong>（见下）<sup id="fnref:15"><a href="#fn:15" class="footnote" rel="footnote" role="doc-noteref">6</a></sup></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">defaultTools</code> 后自动注册 Tool Advisor</td>
      <td style="text-align: left">❌ 无</td>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">autoRegisterToolCallingAdvisor</code> 仅 <code class="language-plaintext highlighter-rouge">v2.0.0</code> 起</strong>存在于 <a href="https://github.com/spring-projects/spring-ai/blob/v2.0.0/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java"><code class="language-plaintext highlighter-rouge">DefaultChatClient</code></a>；<code class="language-plaintext highlighter-rouge">v1.1.8</code> 的 <code class="language-plaintext highlighter-rouge">buildAdvisorChain()</code> 只追加 <code class="language-plaintext highlighter-rouge">ChatModelCallAdvisor</code>，<strong>不会</strong>自动加 <code class="language-plaintext highlighter-rouge">ToolCallAdvisor</code><sup id="fnref:16"><a href="#fn:16" class="footnote" rel="footnote" role="doc-noteref">7</a></sup></td>
    </tr>
  </tbody>
</table>

<h4 id="路径-a最常见依赖-chatmodel-内部循环v10xv118-默认">路径 A：最常见——依赖 ChatModel 内部循环（<code class="language-plaintext highlighter-rouge">v1.0.x</code>～<code class="language-plaintext highlighter-rouge">v1.1.8</code> 默认）</h4>

<p><code class="language-plaintext highlighter-rouge">v1.1.8</code> 中，对带 tools 的 <code class="language-plaintext highlighter-rouge">Prompt</code> 调用 <code class="language-plaintext highlighter-rouge">chatModel.call()</code> 时，<a href="https://github.com/spring-projects/spring-ai/blob/v1.1.8/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java"><code class="language-plaintext highlighter-rouge">DeepSeekChatModel</code></a> 在 <strong><code class="language-plaintext highlighter-rouge">internalCall</code> 末尾</strong>判断 <code class="language-plaintext highlighter-rouge">toolExecutionEligibilityPredicate.isToolExecutionRequired(...)</code>，执行 <code class="language-plaintext highlighter-rouge">toolCallingManager.executeToolCalls</code>，再递归 <code class="language-plaintext highlighter-rouge">internalCall</code> 把 tool 结果塞回 Prompt——循环在<strong>模型类内部</strong>完成<sup id="fnref:14:1"><a href="#fn:14" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>。<code class="language-plaintext highlighter-rouge">v2.0.0</code> 的同文件已删掉这段 tool 执行逻辑，<code class="language-plaintext highlighter-rouge">internalCall</code> 只返回单次 HTTP 响应<sup id="fnref:2:1"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>。</p>

<p>因此路径 A 的 <code class="language-plaintext highlighter-rouge">ChatConfig</code> 往往比 2.0 <strong>更短</strong>——<strong>不必</strong>注册 <code class="language-plaintext highlighter-rouge">ToolCallAdvisor</code>：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Spring AI 1.x + Spring Boot 3.x — 路径 A（v1.1.8 验证）</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">ChatClient</span> <span class="nf">chatClient</span><span class="o">(</span><span class="nc">ChatModel</span> <span class="n">chatModel</span><span class="o">,</span> <span class="nc">BookingTools</span> <span class="n">bookingTools</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="nc">ChatClient</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="n">chatModel</span><span class="o">)</span>
            <span class="o">.</span><span class="na">defaultSystem</span><span class="o">(</span><span class="s">""" ... 订票助手 system prompt ... """</span><span class="o">)</span>
            <span class="o">.</span><span class="na">defaultTools</span><span class="o">(</span><span class="n">bookingTools</span><span class="o">)</span>   <span class="c1">// v1.0.9+ 已有；亦可用 defaultToolCallbacks</span>
            <span class="o">.</span><span class="na">build</span><span class="o">();</span>
    <span class="c1">// 无 ToolCallAdvisor；ReAct 在 DeepSeekChatModel.internalCall 内完成</span>
<span class="o">}</span>
</code></pre></div></div>

<p><a href="https://github.com/liweinan/springai_demo/blob/main/backend/src/main/java/com/demo/booking/service/ChatService.java"><code class="language-plaintext highlighter-rouge">ChatService</code></a> 的 <code class="language-plaintext highlighter-rouge">.call().content()</code> <strong>写法可以不变</strong>。差别在 <code class="language-plaintext highlighter-rouge">.call()</code> <strong>内部</strong>：若只加一层 <strong>远离 ChatModel</strong> 的 <code class="language-plaintext highlighter-rouge">BaseAdvisor</code>（如 <code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code> 但 order ≤ +300），它通常只在<strong>整次</strong> <code class="language-plaintext highlighter-rouge">.call()</code> 的首尾各执行一次 <code class="language-plaintext highlighter-rouge">before</code>/<code class="language-plaintext highlighter-rouge">after</code>——中间多轮 <code class="language-plaintext highlighter-rouge">internalCall</code> 发生在 <code class="language-plaintext highlighter-rouge">ChatModel</code> 黑盒里，<strong>看不见</strong> <code class="language-plaintext highlighter-rouge">[AI 第1步] … (tool_calls)</code> 与 <code class="language-plaintext highlighter-rouge">[AI 第2步] … TOOL_RESPONSE</code>。</p>

<h4 id="路径-bv110-手动注册-toolcalladvisoradvisor-链-react20-前身">路径 B：<code class="language-plaintext highlighter-rouge">v1.1.0+</code> 手动注册 <code class="language-plaintext highlighter-rouge">ToolCallAdvisor</code>（Advisor 链 ReAct，2.0 前身）</h4>

<p>自 <strong><code class="language-plaintext highlighter-rouge">v1.1.0</code></strong> 起，<a href="https://github.com/spring-projects/spring-ai/blob/v1.1.8/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallAdvisor.java"><code class="language-plaintext highlighter-rouge">ToolCallAdvisor</code></a> 与 2.0 的 <a href="https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java"><code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code></a> <strong>同类</strong>：类注释即写明 <em>Recursive Advisor… implements the tool calling loop as part of the advisor chain</em>，默认 <strong><code class="language-plaintext highlighter-rouge">advisorOrder = HIGHEST_PRECEDENCE + 300</code></strong>，<code class="language-plaintext highlighter-rouge">do { … callAdvisorChain.copy(this).nextCall(…) … } while (isToolCall)</code><sup id="fnref:15:1"><a href="#fn:15" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>。</p>

<p>与文章旧稿不同、以 <strong><code class="language-plaintext highlighter-rouge">v1.1.8</code> 源码为准</strong>的几点：</p>

<ol>
  <li><strong>不必</strong>手动设 <code class="language-plaintext highlighter-rouge">internalToolExecutionEnabled(false)</code>——<code class="language-plaintext highlighter-rouge">ToolCallAdvisor.adviseCall</code> 在循环开始前会对 options 副本执行 <code class="language-plaintext highlighter-rouge">setInternalToolExecutionEnabled(false)</code><sup id="fnref:17"><a href="#fn:17" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">ToolCallingManager</code> 通常不必注入</strong>——<code class="language-plaintext highlighter-rouge">ToolCallAdvisor.builder()</code> 默认 <code class="language-plaintext highlighter-rouge">ToolCallingManager.builder().build()</code>。</li>
  <li><strong>必须手动</strong> <code class="language-plaintext highlighter-rouge">.defaultAdvisors(ToolCallAdvisor.builder()...)</code>——1.x <strong>没有</strong> 2.0 的 <code class="language-plaintext highlighter-rouge">autoRegisterToolCallingAdvisor</code>。</li>
</ol>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Spring AI 1.1.x — 路径 B（v1.1.8 验证）</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">ChatClient</span> <span class="nf">chatClient</span><span class="o">(</span><span class="nc">ChatModel</span> <span class="n">chatModel</span><span class="o">,</span> <span class="nc">BookingTools</span> <span class="n">bookingTools</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="nc">ChatClient</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="n">chatModel</span><span class="o">)</span>
            <span class="o">.</span><span class="na">defaultSystem</span><span class="o">(</span><span class="s">""" ... """</span><span class="o">)</span>
            <span class="o">.</span><span class="na">defaultTools</span><span class="o">(</span><span class="n">bookingTools</span><span class="o">)</span>
            <span class="o">.</span><span class="na">defaultAdvisors</span><span class="o">(</span><span class="nc">ToolCallAdvisor</span><span class="o">.</span><span class="na">builder</span><span class="o">().</span><span class="na">build</span><span class="o">())</span>
            <span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong><code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code> 在 1.1.x 并非「根本无法实现」</strong>：若像本项目一样再注册一个 <code class="language-plaintext highlighter-rouge">order = HIGHEST_PRECEDENCE + 400</code> 的 <code class="language-plaintext highlighter-rouge">BaseAdvisor</code>，且其 order <strong>大于</strong> <code class="language-plaintext highlighter-rouge">ToolCallAdvisor</code>（+300），则 <code class="language-plaintext highlighter-rouge">copy(this).nextCall</code> 重入链时会带上该 Advisor——逐步日志<strong>可以</strong>做到。难在 1.x <strong>默认走路径 A</strong>，且路径 B 需<strong>手动</strong>组装 <code class="language-plaintext highlighter-rouge">ToolCallAdvisor</code>；2.0 则是 <strong><code class="language-plaintext highlighter-rouge">defaultTools</code> 即自动启用 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code></strong>，本项目只加观测 Advisor 即可。</p>

<h4 id="路径-c完全手写-whilechatmodel-直调">路径 C：完全手写 while（<code class="language-plaintext highlighter-rouge">ChatModel</code> 直调）</h4>

<p>对 <code class="language-plaintext highlighter-rouge">chatModel.call()</code> 设 <code class="language-plaintext highlighter-rouge">internalToolExecutionEnabled(false)</code>（<code class="language-plaintext highlighter-rouge">v1.1.8</code> 的 <a href="https://github.com/spring-projects/spring-ai/blob/v1.1.8/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java"><code class="language-plaintext highlighter-rouge">ToolCallingChatOptions</code></a> 默认 <strong><code class="language-plaintext highlighter-rouge">true</code></strong><sup id="fnref:18"><a href="#fn:18" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>），再用 <code class="language-plaintext highlighter-rouge">ToolCallingManager</code> + <code class="language-plaintext highlighter-rouge">while (response.hasToolCalls())</code> 驱动循环。Upgrade Notes 在 2.0 仍给出等价示例<sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">10</a></sup>；编排代码全在业务里，不适合本 Demo「一行 <code class="language-plaintext highlighter-rouge">.call()</code>」的目标。</p>

<h4 id="版本与路径速查">版本与路径速查</h4>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Tag</th>
      <th style="text-align: left">ToolCallAdvisor</th>
      <th style="text-align: left">ChatModel 内 tool 循环</th>
      <th style="text-align: left">本 Demo 默认路径</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">v1.0.x</code></td>
      <td style="text-align: left">❌ 无</td>
      <td style="text-align: left">✅ 有</td>
      <td style="text-align: left">仅路径 A 或 C</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">v1.1.0</code>～<code class="language-plaintext highlighter-rouge">v1.1.8</code></td>
      <td style="text-align: left">✅ 有（需手动注册）</td>
      <td style="text-align: left">✅ 有（路径 A 默认）</td>
      <td style="text-align: left">路径 A 最常见；路径 B 可观测 ReAct</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">v2.0.0+</code></td>
      <td style="text-align: left">更名为 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> + <strong>自动注册</strong></td>
      <td style="text-align: left">❌ <strong>已移除</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">defaultTools</code> → Advisor 链</td>
    </tr>
  </tbody>
</table>

<h4 id="1x-与-20-配置对照本-demo-视角">1.x 与 2.0 配置对照（本 Demo 视角）</h4>

<pre><code class="language-mermaid">graph TB
  subgraph v1_default [1.x 路径 A 默认 v1.1.8]
    CS1[ChatService.call]
    CC1[ChatClient + defaultTools]
    CM1[DeepSeekChatModel.internalCall 内 while]
    CS1 --&gt; CC1 --&gt; CM1
  end
  subgraph v1_b [1.x 路径 B v1.1.0+]
    CS1b[ChatService.call]
    CC1b[ChatClient + ToolCallAdvisor +300]
    PLA1[PromptLoggingAdvisor +400 可选]
    CS1b --&gt; CC1b --&gt; PLA1
  end
  subgraph v2 [2.0 本项目 v2.0.0]
    CS2[ChatService.call]
    CC2[ChatClient + defaultTools]
    TCA[ToolCallingAdvisor 自动 +300]
    PLA[PromptLoggingAdvisor +400]
    CS2 --&gt; CC2 --&gt; TCA --&gt; PLA
  end
</code></pre>

<table>
  <thead>
    <tr>
      <th style="text-align: left">项目</th>
      <th style="text-align: left">1.x 路径 A（<code class="language-plaintext highlighter-rouge">v1.1.8</code> 默认）</th>
      <th style="text-align: left">1.x 路径 B（<code class="language-plaintext highlighter-rouge">v1.1.0+</code>）</th>
      <th style="text-align: left">2.0（本项目）</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">Boot</td>
      <td style="text-align: left">3.x</td>
      <td style="text-align: left">3.x</td>
      <td style="text-align: left"><strong>4.1.0</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">注册工具</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">defaultTools</code> / <code class="language-plaintext highlighter-rouge">defaultToolCallbacks</code></td>
      <td style="text-align: left">同左</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">defaultTools</code></td>
    </tr>
    <tr>
      <td style="text-align: left">ReAct 驱动者</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ChatModel.internalCall</code></td>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">ToolCallAdvisor</code> 链</strong></td>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> 链</strong>（自动）</td>
    </tr>
    <tr>
      <td style="text-align: left">逐步 Prompt 日志</td>
      <td style="text-align: left">❌ 远离 ChatModel 的 Advisor 不可见中间轮</td>
      <td style="text-align: left">✅ 可（order +400，更靠近 ChatModel）</td>
      <td style="text-align: left">✅ 自动链 + <code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">internalToolExecutionEnabled</code></td>
      <td style="text-align: left">默认 <code class="language-plaintext highlighter-rouge">true</code>；路径 B 由 Advisor 自动关</td>
      <td style="text-align: left">Advisor 内自动 <code class="language-plaintext highlighter-rouge">false</code></td>
      <td style="text-align: left"><strong>选项已删除</strong><sup id="fnref:13"><a href="#fn:13" class="footnote" rel="footnote" role="doc-noteref">11</a></sup></td>
    </tr>
    <tr>
      <td style="text-align: left">业务 <code class="language-plaintext highlighter-rouge">.call()</code></td>
      <td style="text-align: left">相同</td>
      <td style="text-align: left">相同</td>
      <td style="text-align: left">相同</td>
    </tr>
  </tbody>
</table>

<p><strong>结论</strong>：订票 Demo 的 <strong>业务层</strong>在 1.x <strong>完全可以实现</strong>；2.0 的实质变化是 <strong><code class="language-plaintext highlighter-rouge">v2.0.0</code> 移除 <code class="language-plaintext highlighter-rouge">ChatModel</code> 内循环</strong>，Tool 循环<strong>统一</strong>由 Advisor 链承担，且 <strong><code class="language-plaintext highlighter-rouge">defaultTools</code> 自动注册 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code></strong>——因此本项目的 <code class="language-plaintext highlighter-rouge">ChatConfig</code> 才能只写 <code class="language-plaintext highlighter-rouge">defaultTools</code> + <code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code>。从 <code class="language-plaintext highlighter-rouge">v1.1.8</code> 迁移：去掉手动 <code class="language-plaintext highlighter-rouge">ToolCallAdvisor</code>、删除 <code class="language-plaintext highlighter-rouge">internalToolExecutionEnabled</code> 配置、升级 Boot 4 + AI 2.0 即可<sup id="fnref:13:1"><a href="#fn:13" class="footnote" rel="footnote" role="doc-noteref">11</a></sup>。</p>

<h3 id="31-chatmodel-内部循环被移除">3.1 ChatModel 内部循环被移除</h3>

<p>1.x 中，对带 tools 的 <code class="language-plaintext highlighter-rouge">Prompt</code> 调用 <code class="language-plaintext highlighter-rouge">chatModel.call()</code>，各 Provider 会在实现内部自动执行 tool calls 并循环，直到模型返回纯文本。</p>

<p>2.0 <strong>删除</strong>了所有 <code class="language-plaintext highlighter-rouge">ChatModel</code> 实现中的这一内部循环<sup id="fnref:2:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>。<code class="language-plaintext highlighter-rouge">ChatModel.call()</code> / <code class="language-plaintext highlighter-rouge">stream()</code> 现在返回<strong>原始</strong>模型响应——若有 tool_calls，<strong>不会</strong>自动执行。</p>

<p>迁移路径：改用 <code class="language-plaintext highlighter-rouge">ChatClient</code> + 自动注册的 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code>（推荐），或自行用 <code class="language-plaintext highlighter-rouge">ToolCallingManager</code> 写 while 循环。</p>

<h3 id="32-toolcallingadvisor-拥有工具执行生命周期">3.2 ToolCallingAdvisor 拥有工具执行生命周期</h3>

<p><a href="https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java"><code class="language-plaintext highlighter-rouge">ToolCallingAdvisor.adviseCall</code></a> 的核心是一个 <code class="language-plaintext highlighter-rouge">do { ... } while (isToolCall)</code> 循环：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ToolCallingAdvisor.java: 141-187</span>
<span class="k">do</span> <span class="o">{</span>
    <span class="n">processedChatClientRequest</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">doBeforeCall</span><span class="o">(</span><span class="n">processedChatClientRequest</span><span class="o">,</span> <span class="n">callAdvisorChain</span><span class="o">);</span>
    <span class="n">chatClientResponse</span> <span class="o">=</span> <span class="n">callAdvisorChain</span><span class="o">.</span><span class="na">copy</span><span class="o">(</span><span class="k">this</span><span class="o">).</span><span class="na">nextCall</span><span class="o">(</span><span class="n">processedChatClientRequest</span><span class="o">);</span>
    <span class="n">chatClientResponse</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">doAfterCall</span><span class="o">(</span><span class="n">chatClientResponse</span><span class="o">,</span> <span class="n">callAdvisorChain</span><span class="o">);</span>
    <span class="n">isToolCall</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">toolExecutionEligibilityChecker</span><span class="o">.</span><span class="na">isToolCallResponse</span><span class="o">(</span><span class="n">chatResponse</span><span class="o">);</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">isToolCall</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">ToolExecutionResult</span> <span class="n">toolExecutionResult</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">toolCallingManager</span>
            <span class="o">.</span><span class="na">executeToolCalls</span><span class="o">(</span><span class="n">processedChatClientRequest</span><span class="o">.</span><span class="na">prompt</span><span class="o">(),</span> <span class="n">chatResponse</span><span class="o">);</span>
        <span class="n">instructions</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">doGetNextInstructionsForToolCall</span><span class="o">(...);</span>
    <span class="o">}</span>
<span class="o">}</span> <span class="k">while</span> <span class="o">(</span><span class="n">isToolCall</span><span class="o">);</span>
</code></pre></div></div>

<p>注意 <code class="language-plaintext highlighter-rouge">callAdvisorChain.copy(this).nextCall(...)</code>：每一轮 ReAct <strong>重新进入</strong> order 更大（<strong>更靠近 ChatModel</strong>）的 Advisor 子链——这正是 <code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code> 能逐步打印的原因；1.x 路径 A 下，中间轮次发生在 <code class="language-plaintext highlighter-rouge">ChatModel</code> 内部，远离 ChatModel 的 Advisor <strong>不可见</strong>。</p>

<p>默认 order：<code class="language-plaintext highlighter-rouge">ToolCallingAdvisor.DEFAULT_ORDER = HIGHEST_PRECEDENCE + 300</code><sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">12</a></sup>。<code class="language-plaintext highlighter-rouge">MessageChatMemoryAdvisor</code> 默认 order 从 +1000 改为 <strong>+200</strong>，比 +300 <strong>更远离 ChatModel</strong>——不参与每一轮 tool 迭代的子链，只存最终 user/assistant 交换，不把 tool_call / tool_response 写进多数 <code class="language-plaintext highlighter-rouge">ChatMemoryRepository</code> 不支持的存储<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">13</a></sup>。</p>

<h4 id="321-源码走读从-nextcall-到-chatmodelcall含-handletoolcallrecursion">3.2.1 源码走读：从 <code class="language-plaintext highlighter-rouge">nextCall</code> 到 <code class="language-plaintext highlighter-rouge">ChatModel.call()</code>（含 <code class="language-plaintext highlighter-rouge">handleToolCallRecursion</code>）</h4>

<p>先澄清一个常见混淆：<strong><code class="language-plaintext highlighter-rouge">handleToolCallRecursion</code> 只出现在流式路径</strong>（<a href="https://github.com/spring-projects/spring-ai/blob/v2.0.0/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java#L294-L352"><code class="language-plaintext highlighter-rouge">ToolCallingAdvisor.adviseStream</code></a>）。本 Demo 的 <code class="language-plaintext highlighter-rouge">ChatService</code> 走 <strong><code class="language-plaintext highlighter-rouge">.call()</code> 同步路径</strong>，ReAct 递归由 <strong><code class="language-plaintext highlighter-rouge">adviseCall</code> 里的 <code class="language-plaintext highlighter-rouge">do-while</code></strong> 完成（约 141–187 行）。两条路径语义等价：都是「<strong>更靠近 ChatModel</strong> 的子链调模型 → 检测 tool_calls → 执行工具 → 更新 instructions → 再进子链」，只是流式在 SSE chunk 聚合完毕后才进入 <code class="language-plaintext highlighter-rouge">handleToolCallRecursion</code>，再递归调用 <code class="language-plaintext highlighter-rouge">internalStream</code>。</p>

<h5 id="链是怎么组出来的">链是怎么组出来的？</h5>

<p>每次 <code class="language-plaintext highlighter-rouge">chatClient.prompt().user(...).call()</code> 时，<a href="https://github.com/spring-projects/spring-ai/blob/v2.0.0/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java#L1189-L1203"><code class="language-plaintext highlighter-rouge">DefaultChatClient.buildAdvisorChain</code></a> 做三件事：</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">autoRegisterToolCallingAdvisor()</code></strong> — 若链中尚无 <code class="language-plaintext highlighter-rouge">ToolAdvisor</code>，自动加入 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code>（+300）</li>
  <li>把用户注册的 Advisor（本项目的 <code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code> +400）与自动注册的 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> 合并</li>
  <li><strong>在栈底追加终结 Advisor</strong>：<code class="language-plaintext highlighter-rouge">ChatModelCallAdvisor</code>（<code class="language-plaintext highlighter-rouge">Ordered.LOWEST_PRECEDENCE</code>）与 <code class="language-plaintext highlighter-rouge">ChatModelStreamAdvisor</code>（流式用）</li>
</ol>

<p><code class="language-plaintext highlighter-rouge">DefaultAroundAdvisorChain.Builder.pushAll</code> 按 <code class="language-plaintext highlighter-rouge">OrderComparator</code> 排序后装入 <strong>双端队列</strong>：<strong>order 越小，越先被 <code class="language-plaintext highlighter-rouge">pop()</code></strong>——即 <strong>越远离 ChatModel</strong>（请求要先经过它，才能继续 <code class="language-plaintext highlighter-rouge">nextCall</code> 靠近模型）。</p>

<p>本 Demo 同步调用链（<strong>从远离 ChatModel → 靠近 ChatModel</strong>）可概括为：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">顺序</th>
      <th style="text-align: left">Advisor</th>
      <th style="text-align: left">order</th>
      <th style="text-align: left">距 ChatModel</th>
      <th style="text-align: left">角色</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">1</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code></td>
      <td style="text-align: left">+300</td>
      <td style="text-align: left">较远</td>
      <td style="text-align: left">驱动 ReAct <code class="language-plaintext highlighter-rouge">do-while</code>；<strong>不</strong>走 <code class="language-plaintext highlighter-rouge">BaseAdvisor</code> 默认模板</td>
    </tr>
    <tr>
      <td style="text-align: left">2</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code></td>
      <td style="text-align: left">+400</td>
      <td style="text-align: left">更近</td>
      <td style="text-align: left">观测；走 <code class="language-plaintext highlighter-rouge">BaseAdvisor</code>：<code class="language-plaintext highlighter-rouge">before → nextCall → after</code></td>
    </tr>
    <tr>
      <td style="text-align: left">3</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ChatModelCallAdvisor</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">LOWEST_PRECEDENCE</code></td>
      <td style="text-align: left">最近（终点）</td>
      <td style="text-align: left">直接 <code class="language-plaintext highlighter-rouge">chatModel.call(prompt)</code>，<strong>不再</strong> <code class="language-plaintext highlighter-rouge">nextCall</code></td>
    </tr>
  </tbody>
</table>

<h5 id="defaultaroundadvisorchainnextcall调度器如何层层递进"><code class="language-plaintext highlighter-rouge">DefaultAroundAdvisorChain.nextCall</code>：调度器如何「层层递进」？</h5>

<p><a href="https://github.com/spring-projects/spring-ai/blob/v2.0.0/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/DefaultAroundAdvisorChain.java#L98-L121"><code class="language-plaintext highlighter-rouge">nextCall</code></a> 逻辑极简：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">advisor</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">callAdvisors</span><span class="o">.</span><span class="na">pop</span><span class="o">();</span>          <span class="c1">// 弹出 order 最小（最远离 ChatModel）的 Advisor</span>
<span class="k">return</span> <span class="n">advisor</span><span class="o">.</span><span class="na">adviseCall</span><span class="o">(</span><span class="n">chatClientRequest</span><span class="o">,</span> <span class="k">this</span><span class="o">);</span>
</code></pre></div></div>

<p>每个 Advisor 的 <code class="language-plaintext highlighter-rouge">adviseCall</code> 决定是否继续向 ChatModel 递进：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">类型</th>
      <th style="text-align: left"><code class="language-plaintext highlighter-rouge">adviseCall</code> 行为</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">BaseAdvisor</code>（如 <code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code>）</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">before()</code> → <code class="language-plaintext highlighter-rouge">nextCall()</code> → <code class="language-plaintext highlighter-rouge">after()</code>（<a href="https://github.com/spring-projects/spring-ai/blob/v2.0.0/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/api/BaseAdvisor.java#L47-L53">模板 :47-53</a>）</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code></td>
      <td style="text-align: left">自定义 <code class="language-plaintext highlighter-rouge">do-while</code>；每轮 <code class="language-plaintext highlighter-rouge">copy(this).nextCall()</code> 重入 <strong>更靠近 ChatModel</strong> 的子链</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ChatModelCallAdvisor</code></td>
      <td style="text-align: left"><strong>链终点（最近）</strong>：<code class="language-plaintext highlighter-rouge">chatModel.call(prompt)</code>，返回 <code class="language-plaintext highlighter-rouge">ChatClientResponse</code></td>
    </tr>
  </tbody>
</table>

<p><a href="https://github.com/spring-projects/spring-ai/blob/v2.0.0/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/DefaultAroundAdvisorChain.java#L169-L195"><code class="language-plaintext highlighter-rouge">copy(CallAdvisor after)</code></a> 从<strong>完整 Advisor 列表</strong>里截取 <strong><code class="language-plaintext highlighter-rouge">after</code> 之后</strong>的剩余节点，构造一条<strong>新链</strong>。<code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> 调用 <code class="language-plaintext highlighter-rouge">copy(this).nextCall()</code> 时，<code class="language-plaintext highlighter-rouge">this</code> 是 +300，剩余子链为 <code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor (+400)</code> → <code class="language-plaintext highlighter-rouge">ChatModelCallAdvisor</code>——即每一轮 ReAct 都会走一遍 <strong>更靠近 ChatModel</strong> 的观测链。</p>

<h5 id="同步路径call-如何最终落到-chatmodel">同步路径：<code class="language-plaintext highlighter-rouge">.call()</code> 如何最终落到 <code class="language-plaintext highlighter-rouge">ChatModel</code>？</h5>

<p>入口在 <a href="https://github.com/spring-projects/spring-ai/blob/v2.0.0/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java#L656-L658"><code class="language-plaintext highlighter-rouge">DefaultChatClient.doGetObservableChatClientResponse</code></a>：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 注释写明：Apply the advisor chain that terminates with the ChatModelCallAdvisor.</span>
<span class="kt">var</span> <span class="n">response</span> <span class="o">=</span> <span class="n">advisorChain</span><span class="o">.</span><span class="na">nextCall</span><span class="o">(</span><span class="n">chatClientRequest</span><span class="o">);</span>
</code></pre></div></div>

<p><a href="https://github.com/spring-projects/spring-ai/blob/v2.0.0/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ChatModelCallAdvisor.java#L53-L64"><code class="language-plaintext highlighter-rouge">ChatModelCallAdvisor.adviseCall</code></a> 是链上<strong>唯一</strong>真正调用大模型的地方：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">ChatResponse</span> <span class="n">chatResponse</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">chatModel</span><span class="o">.</span><span class="na">call</span><span class="o">(</span><span class="n">formattedChatClientRequest</span><span class="o">.</span><span class="na">prompt</span><span class="o">());</span>
<span class="k">return</span> <span class="nc">ChatClientResponse</span><span class="o">.</span><span class="na">builder</span><span class="o">().</span><span class="na">chatResponse</span><span class="o">(</span><span class="n">chatResponse</span><span class="o">).</span><span class="na">context</span><span class="o">(...).</span><span class="na">build</span><span class="o">();</span>
</code></pre></div></div>

<p>对本项目即 <code class="language-plaintext highlighter-rouge">DeepSeekChatModel.call(Prompt)</code> → HTTP 请求 DeepSeek API。2.0 下这次 <code class="language-plaintext highlighter-rouge">call()</code> <strong>只返回单轮原始响应</strong>（含 <code class="language-plaintext highlighter-rouge">tool_calls</code> 时也不执行工具），工具执行由 <strong>更远离 ChatModel</strong> 的 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> 在子链 <code class="language-plaintext highlighter-rouge">after</code> 返回后调用 <code class="language-plaintext highlighter-rouge">toolCallingManager.executeToolCalls</code> 完成。</p>

<h5 id="流式路径handletoolcallrecursion-做了什么">流式路径：<code class="language-plaintext highlighter-rouge">handleToolCallRecursion</code> 做了什么？</h5>

<p>流式时 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> 不走 <code class="language-plaintext highlighter-rouge">do-while</code>，而是：</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">internalStream</code> → <code class="language-plaintext highlighter-rouge">chainCopy.nextStream()</code> 把 chunk 流式传给客户端</li>
  <li><code class="language-plaintext highlighter-rouge">streamWithToolCallResponses</code> 在流结束后调用 <strong><code class="language-plaintext highlighter-rouge">handleToolCallRecursion</code></strong></li>
  <li>若聚合后的 <code class="language-plaintext highlighter-rouge">ChatResponse</code> 含 <code class="language-plaintext highlighter-rouge">tool_calls</code>：在 <code class="language-plaintext highlighter-rouge">boundedElastic</code> 上 <code class="language-plaintext highlighter-rouge">executeToolCalls</code>，再 <strong><code class="language-plaintext highlighter-rouge">return internalStream(...)</code></strong> 进入下一轮——与同步的 <code class="language-plaintext highlighter-rouge">do-while</code> 同构<sup id="fnref:21"><a href="#fn:21" class="footnote" rel="footnote" role="doc-noteref">14</a></sup></li>
</ol>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ToolCallingAdvisor.java: 343-348（流式递归核心）</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Message</span><span class="o">&gt;</span> <span class="n">nextInstructions</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">doGetNextInstructionsForToolCallStream</span><span class="o">(...);</span>
<span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">internalStream</span><span class="o">(</span><span class="n">streamAdvisorChain</span><span class="o">,</span> <span class="n">originalRequest</span><span class="o">,</span> <span class="n">optionsCopy</span><span class="o">,</span>
        <span class="n">nextInstructions</span><span class="o">,</span> <span class="n">usageAccumulator</span><span class="o">);</span>
</code></pre></div></div>

<h5 id="mermaid本-demo-同步-call-完整时序第-1-轮--工具--第-2-轮">Mermaid：本 Demo 同步 <code class="language-plaintext highlighter-rouge">.call()</code> 完整时序（第 1 轮 + 工具 + 第 2 轮）</h5>

<pre><code class="language-mermaid">sequenceDiagram
  autonumber
  participant CS as ChatService
  participant DCC as DefaultChatClient
  participant Chain as DefaultAroundAdvisorChain
  participant TCA as ToolCallingAdvisor +300
  participant PLA as PromptLoggingAdvisor +400
  participant CMA as ChatModelCallAdvisor
  participant CM as DeepSeekChatModel
  participant TCM as ToolCallingManager
  participant BT as BookingTools

  CS-&gt;&gt;DCC: prompt().user().call()
  DCC-&gt;&gt;DCC: buildAdvisorChain()&lt;br/&gt;自动注册 TCA + 栈底 CMA
  DCC-&gt;&gt;Chain: nextCall(request)

  Note over Chain,TCA: 先 pop order 最小（远离 ChatModel）→ TCA
  Chain-&gt;&gt;TCA: adviseCall(req, chain)

  Note over TCA,CM: 第 1 轮 ReAct
  TCA-&gt;&gt;TCA: doBeforeCall()
  TCA-&gt;&gt;Chain: copy(TCA).nextCall(req₁)
  Note over Chain: 子链 PLA → CMA（更靠近 ChatModel）

  Chain-&gt;&gt;PLA: adviseCall（BaseAdvisor 模板）
  PLA-&gt;&gt;PLA: before() 日志第1步
  PLA-&gt;&gt;Chain: nextCall(req₁)
  Chain-&gt;&gt;CMA: adviseCall
  CMA-&gt;&gt;CM: chatModel.call(prompt)
  CM--&gt;&gt;CMA: ChatResponse tool_calls
  CMA--&gt;&gt;PLA: ChatClientResponse
  PLA-&gt;&gt;PLA: after() 日志第1步
  PLA--&gt;&gt;TCA: ChatClientResponse

  TCA-&gt;&gt;TCA: doAfterCall()
  TCA-&gt;&gt;TCM: executeToolCalls(prompt, response)
  TCM-&gt;&gt;BT: subscribeTicket @Tool
  BT--&gt;&gt;TCM: BookingResponse
  TCM--&gt;&gt;TCA: ToolExecutionResult + 新 instructions

  Note over TCA,CM: 第 2 轮 ReAct（instructions 含 TOOL_RESPONSE）
  TCA-&gt;&gt;Chain: copy(TCA).nextCall(req₂)
  Chain-&gt;&gt;PLA: before() 第2步 → CMA → CM.call()
  CM--&gt;&gt;PLA: 最终中文
  PLA-&gt;&gt;PLA: after() 第2步
  PLA--&gt;&gt;TCA: ChatClientResponse

  TCA-&gt;&gt;TCA: isToolCall=false，退出 do-while
  TCA--&gt;&gt;DCC: ChatClientResponse
  DCC--&gt;&gt;CS: content()
</code></pre>

<p><strong>读图要点</strong>：</p>

<ul>
  <li><strong>第 4–6 步</strong>：<code class="language-plaintext highlighter-rouge">DefaultChatClient</code> 只调一次 <code class="language-plaintext highlighter-rouge">nextCall</code>，首先进入的是 <strong>最远离 ChatModel</strong> 的 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code>（+300），不是 <code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code>。</li>
  <li><strong>第 8–9 步</strong>：<code class="language-plaintext highlighter-rouge">copy(TCA)</code> 是关键——每一轮 ReAct 都<strong>重新构造</strong>「PLA → CMA」子链（更靠近 ChatModel），因此 <code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code> 的 <code class="language-plaintext highlighter-rouge">before/after</code> 在每一轮都会触发。</li>
  <li><strong>第 17–18 步</strong>：<code class="language-plaintext highlighter-rouge">ChatModelCallAdvisor</code> 是<strong>链终点（最近）</strong>；<code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> 与 <code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code> 都不会直接碰 <code class="language-plaintext highlighter-rouge">ChatModel</code>。</li>
  <li><strong>第 20–22 步</strong>：工具执行在 <code class="language-plaintext highlighter-rouge">PLA.after()</code> <strong>之后</strong>、下一轮 <code class="language-plaintext highlighter-rouge">copy().nextCall()</code> <strong>之前</strong>，与 §2.2.5 日志顺序一致。</li>
</ul>

<h5 id="三条路径对照">三条路径对照</h5>

<table>
  <thead>
    <tr>
      <th style="text-align: left">路径</th>
      <th style="text-align: left">ReAct 驱动</th>
      <th style="text-align: left">向 ChatModel 递进</th>
      <th style="text-align: left">调 <code class="language-plaintext highlighter-rouge">ChatModel</code> 的类</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">同步 <code class="language-plaintext highlighter-rouge">.call()</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">adviseCall</code> 的 <code class="language-plaintext highlighter-rouge">do-while</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">copy(this).nextCall()</code> 子链</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ChatModelCallAdvisor</code></td>
    </tr>
    <tr>
      <td style="text-align: left">流式 <code class="language-plaintext highlighter-rouge">.stream()</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">handleToolCallRecursion</code> → <code class="language-plaintext highlighter-rouge">internalStream</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">copy(this).nextStream()</code> 子链</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ChatModelStreamAdvisor</code></td>
    </tr>
    <tr>
      <td style="text-align: left">1.x 路径 A</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ChatModel.internalCall</code> 内递归</td>
      <td style="text-align: left">无 Advisor 子链重入</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">DeepSeekChatModel</code> 自身</td>
    </tr>
  </tbody>
</table>

<h3 id="33-defaulttools-与自动注册">3.3 defaultTools 与自动注册</h3>

<p><a href="https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java"><code class="language-plaintext highlighter-rouge">DefaultChatClient.Spec.autoRegisterToolCallingAdvisor</code></a> 在每次 <code class="language-plaintext highlighter-rouge">call()</code> 前检查：</p>

<ul>
  <li>若 <code class="language-plaintext highlighter-rouge">AdvisorParams.toolCallingAdvisorAutoRegister(false)</code> 未禁用</li>
  <li>且链中尚无 <code class="language-plaintext highlighter-rouge">ToolAdvisor</code> 实例</li>
</ul>

<p>则自动加入 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code>。全局开关：<code class="language-plaintext highlighter-rouge">spring.ai.chat.client.tool-calling.enabled</code>（默认 <code class="language-plaintext highlighter-rouge">true</code>）<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">15</a></sup>。</p>

<p><code class="language-plaintext highlighter-rouge">defaultToolCallbacks</code> 等在 2.0 <strong>deprecated</strong>；<code class="language-plaintext highlighter-rouge">defaultTools(Object...)</code> 在 <strong><code class="language-plaintext highlighter-rouge">v1.0.9</code> 已存在</strong><sup id="fnref:19"><a href="#fn:19" class="footnote" rel="footnote" role="doc-noteref">16</a></sup>，2.0 是将其作为唯一推荐入口。</p>

<h3 id="34-删除与新增的选项迁移清单">3.4 删除与新增的选项（迁移清单）</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">1.x</th>
      <th style="text-align: left">2.0</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">internalToolExecutionEnabled</code></td>
      <td style="text-align: left"><strong>已删除</strong> — 不再有「模型内执行 / 用户执行」双模式</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ToolExecutionEligibilityPredicate</code></td>
      <td style="text-align: left"><strong>已删除</strong> — 改用 <code class="language-plaintext highlighter-rouge">ToolExecutionEligibilityChecker</code> 挂在 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor.Builder</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ToolCallAdvisor</code>（手动注册）</td>
      <td style="text-align: left">由 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> <strong>自动注册</strong>取代</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">streamToolCallResponses</code></td>
      <td style="text-align: left"><strong>已删除</strong> — 中间 tool 块不再以特殊流式选项泄露；需观测循环请用自定义 Advisor 或手动 loop</td>
    </tr>
  </tbody>
</table>

<p>若需完全自控循环，可 <code class="language-plaintext highlighter-rouge">AdvisorParams.toolCallingAdvisorAutoRegister(false)</code>，配合 <code class="language-plaintext highlighter-rouge">ToolCallingManager.executeToolCalls</code> 手写 while——Upgrade Notes 给出了完整示例<sup id="fnref:8:1"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">10</a></sup>。</p>

<h3 id="35-advisor-链-order-速查距-chatmodel-远近">3.5 Advisor 链 order 速查（距 ChatModel 远近）</h3>

<pre><code class="language-mermaid">graph LR
  Mem["MessageChatMemoryAdvisor +200&lt;br/&gt;远离 ChatModel"]
  TCA["ToolCallingAdvisor +300"]
  PLA["PromptLoggingAdvisor +400&lt;br/&gt;更靠近 ChatModel"]
  CMA["ChatModelCallAdvisor&lt;br/&gt;最近（链终点）"]
  Mem --&gt; TCA --&gt; PLA --&gt; CMA
</code></pre>

<p><strong>速记</strong>：<strong>order 越大 → 越靠近 <code class="language-plaintext highlighter-rouge">ChatModel</code></strong>。要把自定义观测逻辑插进<strong>每一轮</strong> tool 迭代，order 必须 <strong>&gt; 300</strong>（比 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> 更近），这样才会进入 <code class="language-plaintext highlighter-rouge">copy(this).nextCall()</code> 子链，在每次 <code class="language-plaintext highlighter-rouge">chatModel.call()</code> 前后执行。</p>

<hr />

<h2 id="四变动在本项目中的落地">四、变动在本项目中的落地</h2>

<h3 id="41-业务代码几乎不变可观测性大变">4.1 业务代码几乎不变，可观测性大变</h3>

<p><a href="https://github.com/liweinan/springai_demo/blob/main/backend/src/main/java/com/demo/booking/service/ChatService.java"><code class="language-plaintext highlighter-rouge">ChatService</code></a> 仍是一行：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ChatService.java: 63-67</span>
<span class="nc">String</span> <span class="n">reply</span> <span class="o">=</span> <span class="n">chatClient</span>
        <span class="o">.</span><span class="na">prompt</span><span class="o">()</span>
        <span class="o">.</span><span class="na">user</span><span class="o">(</span><span class="n">userMessage</span><span class="o">.</span><span class="na">trim</span><span class="o">())</span>
        <span class="o">.</span><span class="na">call</span><span class="o">()</span>
        <span class="o">.</span><span class="na">content</span><span class="o">();</span>
</code></pre></div></div>

<p>差异在 <code class="language-plaintext highlighter-rouge">.call()</code> <strong>内部</strong>：2.0 由自动注册的 <code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> 驱动多轮 DeepSeek 请求；1.x <strong>路径 A</strong> 则在 <code class="language-plaintext highlighter-rouge">DeepSeekChatModel.internalCall</code> 内循环（§3.0），远离 ChatModel 的 Advisor 只见首尾——<strong>1.1.x 路径 B</strong> 手动加 <code class="language-plaintext highlighter-rouge">ToolCallAdvisor</code> 后，子链也可多轮靠近 ChatModel。</p>

<h3 id="42-配置层从手动-advisor--manager到defaulttools-即可">4.2 配置层：从「手动 Advisor + Manager」到「defaultTools 即可」</h3>

<p>1.x 下路径 A/B/C 的代码级对比见 <strong>§3.0</strong>（已对照 <code class="language-plaintext highlighter-rouge">v1.1.8</code> tag）。本节只保留迁移速查表：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">维度</th>
      <th style="text-align: left">Spring AI 1.x（<code class="language-plaintext highlighter-rouge">v1.1.8</code>）</th>
      <th style="text-align: left">Spring AI 2.0（本项目）</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">Boot 版本</td>
      <td style="text-align: left">3.x</td>
      <td style="text-align: left"><strong>4.1.0</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">工具循环位置</td>
      <td style="text-align: left">路径 A：<code class="language-plaintext highlighter-rouge">ChatModel</code> 内部；路径 B：<code class="language-plaintext highlighter-rouge">ToolCallAdvisor</code> 链</td>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> 链（默认）</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">Tool Advisor 注册</td>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">v1.1.0+</code> 路径 B 手动</strong>；<code class="language-plaintext highlighter-rouge">v1.0.x</code> 无此类</td>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">defaultTools</code> 自动注册</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">逐步日志</td>
      <td style="text-align: left">路径 A：远离 ChatModel 不可见；路径 B：可（order +400）</td>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code> + 自动链</strong></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ToolCallingManager</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ToolCallAdvisor.builder()</code> 自带默认实例</td>
      <td style="text-align: left"><strong>Boot 自动配置</strong></td>
    </tr>
  </tbody>
</table>

<p><a href="https://github.com/liweinan/springai_demo/blob/main/backend/src/main/resources/application.yml"><code class="language-plaintext highlighter-rouge">application.yml</code></a> 无需额外打开 tool-calling 开关——默认已启用。</p>

<h3 id="43-如何验证-react-真的发生">4.3 如何验证 ReAct 真的发生</h3>

<p>按优先级排查：</p>

<ol>
  <li>控制台 <code class="language-plaintext highlighter-rouge">[Tool 被调用] subscribeTicket</code> — 工具层日志</li>
  <li><code class="language-plaintext highlighter-rouge">[AI 第N步]</code> — Advisor 链内每轮 Prompt/Response</li>
  <li>Hibernate <code class="language-plaintext highlighter-rouge">update bookings set status=...</code> — JPA <code class="language-plaintext highlighter-rouge">show-sql: true</code></li>
  <li>前端左栏「已订阅」+1 — 聊天后 <code class="language-plaintext highlighter-rouge">loadBookings()</code></li>
</ol>

<p>若只有 AI 文字、无 Tool 日志、列表不变 → 模型可能未调工具（LLM 常见情况）；可调 system prompt、<code class="language-plaintext highlighter-rouge">temperature</code>，或重试。</p>

<h3 id="44-与-spring-ai-文档的对应关系">4.4 与 Spring AI 文档的对应关系</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">项目文件</th>
      <th style="text-align: left">Spring AI 概念</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">BookingTools</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Tool</code> / <code class="language-plaintext highlighter-rouge">ToolCallback</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ChatConfig.defaultTools</code></td>
      <td style="text-align: left">静态工具注册 + 触发 auto-register</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">PromptLoggingAdvisor</code></td>
      <td style="text-align: left">更靠近 ChatModel 的观测；对应文档 <a href="https://docs.spring.io/spring-ai/reference/api/advisors-recursive.html">Recursive Advisors</a></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ChatService</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ChatClient</code> 唯一调用点</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">HealthController</code></td>
      <td style="text-align: left">与 AI 路径解耦的连通性探测</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="五快速上手与延伸">五、快速上手与延伸</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">DEEPSEEK_API_KEY</span><span class="o">=</span>your-key-here
<span class="nb">cd </span>backend <span class="o">&amp;&amp;</span> mvn spring-boot:run
<span class="c"># 另开终端</span>
pnpm <span class="nb">install</span> <span class="o">&amp;&amp;</span> pnpm dev
</code></pre></div></div>

<p>浏览器打开 <code class="language-plaintext highlighter-rouge">http://localhost:5173</code>，说「我要订票」或「取消订票 G123」。不启前端也可 curl：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> POST http://localhost:8080/api/chat <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{"message":"我要订票"}'</span>
curl <span class="s2">"http://localhost:8080/api/bookings?status=SUBSCRIBED"</span>
</code></pre></div></div>

<p>仓库 README 还列出了 SSE 流式、SQLite 持久化、意图规则兜底等扩展方向<sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">17</a></sup>。</p>

<hr />

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p><a href="https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java"><code class="language-plaintext highlighter-rouge">ToolCallingAdvisor</code> 类注释</a> — <em>Recursive Advisor… implements the tool calling loop as part of the advisor chain</em> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p><a href="https://docs.spring.io/spring-ai/reference/2.0/upgrade-notes.html#removed-internal-tool-execution-loop-in-chatmodel">Upgrade Notes — Removed: Internal Tool Execution Loop in <code class="language-plaintext highlighter-rouge">ChatModel</code></a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:2:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:3">
      <p><a href="https://docs.spring.io/spring-ai/reference/2.0/upgrade-notes.html#upgrading-to-2-0-0">Upgrade Notes — Upgrading to 2.0.0</a>；本项目 <a href="https://github.com/liweinan/springai_demo/blob/main/backend/pom.xml"><code class="language-plaintext highlighter-rouge">pom.xml</code></a> 使用 Spring Boot 4.1.0 + spring-ai-bom 2.0.0 <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:10">
      <p><a href="https://github.com/liweinan/springai_demo/blob/main/docs/CORS.md">springai_demo docs/CORS.md — Vite Proxy 与 Spring CORS</a> <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:14">
      <p><a href="https://github.com/spring-projects/spring-ai/blob/v1.1.8/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java"><code class="language-plaintext highlighter-rouge">DeepSeekChatModel</code> @ v1.1.8 — <code class="language-plaintext highlighter-rouge">internalCall</code> 内 <code class="language-plaintext highlighter-rouge">executeToolCalls</code> + 递归</a>（约 208–219 行）；<a href="https://github.com/spring-projects/spring-ai/blob/v2.0.0/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java"><code class="language-plaintext highlighter-rouge">v2.0.0</code> 同文件已移除该段</a> <a href="#fnref:14" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:14:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:15">
      <p><a href="https://github.com/spring-projects/spring-ai/blob/v1.1.8/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallAdvisor.java"><code class="language-plaintext highlighter-rouge">ToolCallAdvisor</code> @ v1.1.8 — Recursive Advisor、<code class="language-plaintext highlighter-rouge">copy(this).nextCall</code>、<code class="language-plaintext highlighter-rouge">DEFAULT_ORDER +300</code></a>（<code class="language-plaintext highlighter-rouge">v1.1.0</code> 引入；<code class="language-plaintext highlighter-rouge">v1.0.x</code> tag 下无此文件） <a href="#fnref:15" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:15:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:16">
      <p><a href="https://github.com/spring-projects/spring-ai/blob/v1.1.8/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java"><code class="language-plaintext highlighter-rouge">DefaultChatClient.buildAdvisorChain</code> @ v1.1.8</a> 仅 push <code class="language-plaintext highlighter-rouge">ChatModelCallAdvisor</code>；<a href="https://github.com/spring-projects/spring-ai/blob/v2.0.0/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java"><code class="language-plaintext highlighter-rouge">autoRegisterToolCallingAdvisor</code> @ v2.0.0</a> <a href="#fnref:16" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:17">
      <p><a href="https://github.com/spring-projects/spring-ai/blob/v1.1.8/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallAdvisor.java"><code class="language-plaintext highlighter-rouge">ToolCallAdvisor.adviseCall</code> @ v1.1.8 — <code class="language-plaintext highlighter-rouge">setInternalToolExecutionEnabled(false)</code></a>（约 119–122 行） <a href="#fnref:17" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:18">
      <p><a href="https://github.com/spring-projects/spring-ai/blob/v1.1.8/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java"><code class="language-plaintext highlighter-rouge">ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_ENABLED = true</code> @ v1.1.8</a> <a href="#fnref:18" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8">
      <p><a href="https://docs.spring.io/spring-ai/reference/2.0/upgrade-notes.html#removed-streamtoolcallresponses-from-advisor-builders-and-auto-configuration">Upgrade Notes — User-controlled loop 示例</a>；<a href="https://docs.spring.io/spring-ai/reference/api/tools/tool-calling-advisor.html">ToolCallingAdvisor 参考</a> <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:8:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:13">
      <p><a href="https://docs.spring.io/spring-ai/reference/2.0/upgrade-notes.html#removed-internaltoolexecutionenabled-from-toolcallingchatoptions">Upgrade Notes — Removed: <code class="language-plaintext highlighter-rouge">internalToolExecutionEnabled</code></a> <a href="#fnref:13" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:13:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:4">
      <p><a href="https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java"><code class="language-plaintext highlighter-rouge">ToolCallingAdvisor.DEFAULT_ORDER</code></a> — <code class="language-plaintext highlighter-rouge">HIGHEST_PRECEDENCE + 300</code> <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5">
      <p><a href="https://docs.spring.io/spring-ai/reference/2.0/upgrade-notes.html#changed-advisor-default_chat_memory_precedence_order-default-value">Upgrade Notes — <code class="language-plaintext highlighter-rouge">DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER</code> 与 Memory 在工具循环外</a> <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:21">
      <p><a href="https://github.com/spring-projects/spring-ai/blob/v2.0.0/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallingAdvisor.java#L294-L352"><code class="language-plaintext highlighter-rouge">ToolCallingAdvisor.handleToolCallRecursion</code> @ v2.0.0</a> 与 <a href="https://github.com/spring-projects/spring-ai/blob/v2.0.0/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/DefaultAroundAdvisorChain.java#L98-L121"><code class="language-plaintext highlighter-rouge">DefaultAroundAdvisorChain.nextCall</code></a>、<a href="https://github.com/spring-projects/spring-ai/blob/v2.0.0/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ChatModelCallAdvisor.java#L53-L64"><code class="language-plaintext highlighter-rouge">ChatModelCallAdvisor</code></a> <a href="#fnref:21" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6">
      <p><a href="https://docs.spring.io/spring-ai/reference/2.0/upgrade-notes.html#new-spring-ai-chat-client-tool-calling-enabled-property">Upgrade Notes — <code class="language-plaintext highlighter-rouge">spring.ai.chat.client.tool-calling.enabled</code></a> <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:19">
      <p><a href="https://github.com/spring-projects/spring-ai/blob/v1.0.9/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClientBuilder.java"><code class="language-plaintext highlighter-rouge">DefaultChatClientBuilder.defaultTools</code> @ v1.0.9</a> 与 <a href="https://github.com/spring-projects/spring-ai/blob/v1.1.8/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClientBuilder.java"><code class="language-plaintext highlighter-rouge">v1.1.8</code></a> 均已存在 <a href="#fnref:19" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:9">
      <p><a href="https://github.com/liweinan/springai_demo/blob/main/README.md">springai_demo README — 可扩展方向</a> <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="java" /><category term="spring-ai" /><category term="spring-boot" /><category term="deepseek" /><category term="react" /><summary type="html"><![CDATA[基于 springai_demo 全栈项目与 Spring AI 2.0 源码，梳理 React + Spring Boot 4 订票聊天 Demo 的分层架构、@Tool 业务写入口、PromptLoggingAdvisor 可观测性，以及 ToolCallingAdvisor 取代 ChatModel 内部循环的核心变动。]]></summary></entry><entry><title type="html">Spring Bean 与 RESTEasy Resource：两个生命周期的分界与合作</title><link href="https://weinan.tech/2026/06/17/spring-bean-vs-resteasy-resource-lifecycle.html" rel="alternate" type="text/html" title="Spring Bean 与 RESTEasy Resource：两个生命周期的分界与合作" /><published>2026-06-17T00:00:00+08:00</published><updated>2026-06-17T00:00:00+08:00</updated><id>https://weinan.tech/2026/06/17/spring-bean-vs-resteasy-resource-lifecycle</id><content type="html" xml:base="https://weinan.tech/2026/06/17/spring-bean-vs-resteasy-resource-lifecycle.html"><![CDATA[<style>
/* Mermaid diagram container */
.mermaid-container {
  position: relative;
  display: block;
  cursor: pointer;
  transition: opacity 0.2s;
  max-width: 100%;
  margin: 20px 0;
  overflow-x: auto;
}

.mermaid-container:hover {
  opacity: 0.8;
}

.mermaid-container svg {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Modal overlay */
.mermaid-modal {
  display: none;
  position: fixed;
  z-index: 9999;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9);
  animation: fadeIn 0.3s;
}

.mermaid-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Modal content */
.mermaid-modal-content {
  position: relative;
  width: 90vw;
  height: 90vh;
  overflow: hidden;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
}

.mermaid-modal-diagram {
  transform-origin: center center;
  transition: transform 0.2s ease;
  display: inline-block;
  min-width: 100%;
  cursor: grab;
  user-select: none;
}

.mermaid-modal-diagram.dragging {
  cursor: grabbing;
  transition: none;
}

.mermaid-modal-diagram svg {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;
}

/* Control buttons */
.mermaid-controls {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
  z-index: 10000;
}

.mermaid-btn {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
  color: #333;
  font-weight: 500;
}

.mermaid-btn:hover {
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Close button */
.mermaid-close {
  background: #f44336;
  color: white;
  border: none;
}

.mermaid-close:hover {
  background: #d32f2f;
}

/* Zoom indicator */
.mermaid-zoom-level {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 10000;
}
</style>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  mermaid.initialize({
    startOnLoad: false,
    theme: 'default',
    securityLevel: 'loose',
    htmlLabels: true,
    themeVariables: {
      fontSize: '14px'
    }
  });

  let currentZoom = 1;
  let currentModal = null;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let translateX = 0;
  let translateY = 0;

  // Create modal HTML
  function createModal() {
    const modal = document.createElement('div');
    modal.className = 'mermaid-modal';
    modal.innerHTML = `
      <div class="mermaid-controls">
        <button class="mermaid-btn zoom-in">放大 +</button>
        <button class="mermaid-btn zoom-out">缩小 -</button>
        <button class="mermaid-btn zoom-reset">重置</button>
        <button class="mermaid-btn mermaid-close">关闭 ✕</button>
      </div>
      <div class="mermaid-modal-content">
        <div class="mermaid-modal-diagram"></div>
      </div>
      <div class="mermaid-zoom-level">100%</div>
    `;
    document.body.appendChild(modal);
    return modal;
  }

  // Show modal with diagram
  function showModal(diagramContent) {
    if (!currentModal) {
      currentModal = createModal();
      setupModalEvents();
    }

    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.innerHTML = diagramContent;

    // Remove any width/height attributes from SVG to make it responsive
    const svg = modalDiagram.querySelector('svg');
    if (svg) {
      svg.removeAttribute('width');
      svg.removeAttribute('height');
      svg.style.width = '100%';
      svg.style.height = 'auto';
    }

    // Setup drag functionality
    setupDrag(modalDiagram);

    currentModal.classList.add('active');
    currentZoom = 1;
    resetPosition();
    updateZoom();
    document.body.style.overflow = 'hidden';
  }

  // Hide modal
  function hideModal() {
    if (currentModal) {
      currentModal.classList.remove('active');
      document.body.style.overflow = '';
    }
  }

  // Update zoom level
  function updateZoom() {
    if (!currentModal) return;
    const diagram = currentModal.querySelector('.mermaid-modal-diagram');
    const zoomLevel = currentModal.querySelector('.mermaid-zoom-level');
    diagram.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentZoom})`;
    zoomLevel.textContent = `${Math.round(currentZoom * 100)}%`;
  }

  // Reset position when zoom changes
  function resetPosition() {
    translateX = 0;
    translateY = 0;
  }

  // Setup drag functionality
  function setupDrag(element) {
    element.addEventListener('mousedown', startDrag);
    element.addEventListener('touchstart', startDrag);
  }

  function startDrag(e) {
    if (e.type === 'mousedown' && e.button !== 0) return; // Only left click

    isDragging = true;
    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.classList.add('dragging');

    if (e.type === 'touchstart') {
      startX = e.touches[0].clientX - translateX;
      startY = e.touches[0].clientY - translateY;
    } else {
      startX = e.clientX - translateX;
      startY = e.clientY - translateY;
    }

    document.addEventListener('mousemove', drag);
    document.addEventListener('touchmove', drag);
    document.addEventListener('mouseup', stopDrag);
    document.addEventListener('touchend', stopDrag);
  }

  function drag(e) {
    if (!isDragging) return;
    e.preventDefault();

    if (e.type === 'touchmove') {
      translateX = e.touches[0].clientX - startX;
      translateY = e.touches[0].clientY - startY;
    } else {
      translateX = e.clientX - startX;
      translateY = e.clientY - startY;
    }

    updateZoom();
  }

  function stopDrag() {
    isDragging = false;
    const modalDiagram = currentModal?.querySelector('.mermaid-modal-diagram');
    if (modalDiagram) {
      modalDiagram.classList.remove('dragging');
    }
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('touchmove', drag);
    document.removeEventListener('mouseup', stopDrag);
    document.removeEventListener('touchend', stopDrag);
  }

  // Setup modal event listeners
  function setupModalEvents() {
    if (!currentModal) return;

    // Close button
    currentModal.querySelector('.mermaid-close').addEventListener('click', hideModal);

    // Zoom buttons
    currentModal.querySelector('.zoom-in').addEventListener('click', () => {
      currentZoom = Math.min(currentZoom + 0.25, 3);
      updateZoom();
    });

    currentModal.querySelector('.zoom-out').addEventListener('click', () => {
      currentZoom = Math.max(currentZoom - 0.25, 0.5);
      updateZoom();
    });

    currentModal.querySelector('.zoom-reset').addEventListener('click', () => {
      currentZoom = 1;
      resetPosition();
      updateZoom();
    });

    // Close on background click
    currentModal.addEventListener('click', (e) => {
      if (e.target === currentModal) {
        hideModal();
      }
    });

    // Close on ESC key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && currentModal.classList.contains('active')) {
        hideModal();
      }
    });
  }

  // Convert Jekyll-rendered code blocks to mermaid divs
  document.addEventListener('DOMContentLoaded', async function() {
    const codeBlocks = document.querySelectorAll('code.language-mermaid');

    for (const codeBlock of codeBlocks) {
      const pre = codeBlock.parentElement;
      const container = document.createElement('div');
      container.className = 'mermaid-container';

      const mermaidDiv = document.createElement('div');
      mermaidDiv.className = 'mermaid';
      mermaidDiv.textContent = codeBlock.textContent;

      container.appendChild(mermaidDiv);
      pre.replaceWith(container);
    }

    // Render all mermaid diagrams
    try {
      await mermaid.run({
        querySelector: '.mermaid'
      });
      console.log('Mermaid diagrams rendered successfully');
    } catch (error) {
      console.error('Mermaid rendering error:', error);
    }

    // Add click handlers to rendered diagrams
    document.querySelectorAll('.mermaid-container').forEach((container, index) => {
      // Find the rendered SVG inside the container
      const svg = container.querySelector('svg');
      if (!svg) {
        console.warn(`No SVG found in container ${index}`);
        return;
      }

      // Make the container clickable
      container.style.cursor = 'pointer';
      container.title = '点击查看大图';

      container.addEventListener('click', function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Clone the SVG for the modal
        const svgClone = svg.cloneNode(true);
        const tempDiv = document.createElement('div');
        tempDiv.appendChild(svgClone);

        console.log('Opening modal for diagram', index);
        showModal(tempDiv.innerHTML);
      });

      console.log(`Click handler added to diagram ${index}`);
    });
  });
</script>

<h2 id="引言一个-resource-类两个容器">引言：一个 Resource 类，两个容器</h2>

<p>在 RESTEasy 集成 Spring 的项目里，一个典型的 <code class="language-plaintext highlighter-rouge">@Path</code> Resource 往往长这样：<code class="language-plaintext highlighter-rouge">@Autowired</code> 注入 <code class="language-plaintext highlighter-rouge">CustomerService</code>，方法参数用 <code class="language-plaintext highlighter-rouge">@QueryParam</code>，类上还有 <code class="language-plaintext highlighter-rouge">@Transactional</code>。运行时一切正常——但调试启动顺序、singleton Resource 的 <code class="language-plaintext highlighter-rouge">@Context UriInfo</code> 行为，或 prototype Resource 为何「首请求才创建」时，很容易问：<strong>实例是谁创建的？路由是谁注册的？请求进来后谁先谁后？</strong></p>

<p>答案并不在一个容器里。Spring 把它当 Bean 管（创建、DI、AOP、销毁），RESTEasy 把它当 HTTP 端点管（路由、取实例、JAX-RS 注入、方法调用）。<strong>两者既不合并，也不互相替代</strong>；<a href="https://github.com/resteasy/resteasy/tree/main/resteasy-spring"><code class="language-plaintext highlighter-rouge">resteasy-spring</code></a> 通过 <a href="https://github.com/resteasy/resteasy/blob/main/resteasy-spring/src/main/java/org/jboss/resteasy/plugins/spring/SpringBeanProcessor.java"><code class="language-plaintext highlighter-rouge">SpringBeanProcessor</code></a> 与 <a href="https://github.com/resteasy/resteasy/blob/main/resteasy-spring/src/main/java/org/jboss/resteasy/plugins/spring/SpringResourceFactory.java"><code class="language-plaintext highlighter-rouge">SpringResourceFactory</code></a> 在启动期与运行期各管一段。</p>

<p>全文脉络：</p>

<ol>
  <li><strong>Spring Bean 生命周期</strong> — 所有 scope 共用 <code class="language-plaintext highlighter-rouge">doCreateBean</code> 流水线；singleton 在 <code class="language-plaintext highlighter-rouge">refresh()</code> 批量创建，prototype 在每次 <code class="language-plaintext highlighter-rouge">getBean()</code> 触发</li>
  <li><strong>RESTEasy Resource 生命周期</strong> — <code class="language-plaintext highlighter-rouge">Registry</code> 登记 <code class="language-plaintext highlighter-rouge">ResourceFactory</code>（取实例策略）+ <code class="language-plaintext highlighter-rouge">ResourceMethodInvoker</code>；<code class="language-plaintext highlighter-rouge">registered</code> / <code class="language-plaintext highlighter-rouge">createResource</code> / <code class="language-plaintext highlighter-rouge">requestFinished</code></li>
  <li><strong>resteasy-spring 桥接</strong> — BFPP 扫描 <code class="language-plaintext highlighter-rouge">@Path</code>、BPP 做 JAX-RS 字段注入、<code class="language-plaintext highlighter-rouge">ContextRefreshedEvent</code> 注册 Registry、运行期 <code class="language-plaintext highlighter-rouge">getBean()</code> 取实例</li>
  <li><strong>运行期 call chain</strong> — 单次 HTTP 请求从 Dispatcher 到 <code class="language-plaintext highlighter-rouge">getBean()</code> 的完整路径</li>
  <li><strong>WAR vs Spring Boot</strong> — 启动时序差异，以及 <a href="https://github.com/resteasy/resteasy-spring-boot"><code class="language-plaintext highlighter-rouge">resteasy-spring-boot</code></a> 的额外编排</li>
</ol>

<h3 id="术语与缩写">术语与缩写</h3>

<p>全文反复出现下列缩写；首次出现时尽量给出全称，此处集中对照，便于查阅。</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">缩写</th>
      <th style="text-align: left">英文全称</th>
      <th style="text-align: left">含义</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>RESTEasy</strong></td>
      <td style="text-align: left">RESTful Web Services framework（JBoss RESTEasy）</td>
      <td style="text-align: left">JBoss 出品的 JAX-RS 实现，负责 HTTP 路由、Resource 实例获取与 JAX-RS 调用链</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>JAX-RS</strong></td>
      <td style="text-align: left">Java API for RESTful Web Services</td>
      <td style="text-align: left">Java 定义 REST 服务编程模型（<code class="language-plaintext highlighter-rouge">@Path</code>、<code class="language-plaintext highlighter-rouge">@GET</code>、<code class="language-plaintext highlighter-rouge">@Provider</code> 等）的规范</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IoC</strong></td>
      <td style="text-align: left">Inversion of Control（控制反转）</td>
      <td style="text-align: left">对象创建与依赖关系不由调用方 <code class="language-plaintext highlighter-rouge">new</code>，而由容器装配——Spring 容器的核心思想</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>DI</strong></td>
      <td style="text-align: left">Dependency Injection（依赖注入）</td>
      <td style="text-align: left">IoC 的具体手段：通过构造器、字段或 setter 注入依赖（如 <code class="language-plaintext highlighter-rouge">@Autowired</code>）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>AOP</strong></td>
      <td style="text-align: left">Aspect-Oriented Programming（面向切面编程）</td>
      <td style="text-align: left">在 Bean 外围织入横切逻辑（事务、日志等），常表现为 CGLIB/JDK 动态代理</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>BFPP</strong></td>
      <td style="text-align: left">BeanFactoryPostProcessor</td>
      <td style="text-align: left">Spring 在 Bean <strong>定义</strong>加载后、实例化<strong>前</strong>的扩展点；<code class="language-plaintext highlighter-rouge">SpringBeanProcessor.postProcessBeanFactory</code> 在此扫描 <code class="language-plaintext highlighter-rouge">@Path</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>BPP</strong></td>
      <td style="text-align: left">BeanPostProcessor</td>
      <td style="text-align: left">Spring 在每个 Bean <strong>初始化前后</strong>的扩展点；<code class="language-plaintext highlighter-rouge">ResteasyBeanPostProcessor</code> 在此做 JAX-RS 字段注入</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Registry</strong></td>
      <td style="text-align: left"><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceMethodRegistry.java"><code class="language-plaintext highlighter-rouge">ResourceMethodRegistry</code></a></td>
      <td style="text-align: left">RESTEasy 的路由注册表：URL 树 + 每个 HTTP 端点对应的 <code class="language-plaintext highlighter-rouge">ResourceMethodInvoker</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Dispatcher</strong></td>
      <td style="text-align: left"><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/core/SynchronousDispatcher.java"><code class="language-plaintext highlighter-rouge">SynchronousDispatcher</code></a> 等</td>
      <td style="text-align: left">RESTEasy 请求调度器：匹配 URI、取 Invoker、驱动 Filter 链与方法调用</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>ResourceFactory</strong></td>
      <td style="text-align: left">—</td>
      <td style="text-align: left">RESTEasy 接口：回答「每次请求如何拿到 Resource 实例」；<strong>不是</strong> Bean 实例本身</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>ResourceMethodInvoker</strong></td>
      <td style="text-align: left">—</td>
      <td style="text-align: left">每个 JAX-RS HTTP 端点（如 <code class="language-plaintext highlighter-rouge">@GET /users</code>）一个调用器，持有 <code class="language-plaintext highlighter-rouge">ResourceFactory</code> 引用</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>ProviderFactory</strong></td>
      <td style="text-align: left"><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core-spi/src/main/java/org/jboss/resteasy/spi/ResteasyProviderFactory.java"><code class="language-plaintext highlighter-rouge">ResteasyProviderFactory</code></a></td>
      <td style="text-align: left">RESTEasy 的 <code class="language-plaintext highlighter-rouge">@Provider</code> 注册中心（MessageBodyReader/Writer、Filter 等）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>SRF</strong></td>
      <td style="text-align: left">SpringResourceFactory</td>
      <td style="text-align: left">resteasy-spring 桥接类：Registry 中登记的「向 Spring <code class="language-plaintext highlighter-rouge">getBean()</code> 取实例」策略</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>SBP</strong></td>
      <td style="text-align: left">SpringBeanProcessor</td>
      <td style="text-align: left">resteasy-spring 启动编排类：实现 BFPP + 监听 <code class="language-plaintext highlighter-rouge">ContextRefreshedEvent</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>POJO</strong></td>
      <td style="text-align: left">Plain Old Java Object</td>
      <td style="text-align: left">普通 Java 对象；<code class="language-plaintext highlighter-rouge">POJOResourceFactory</code> 表示 RESTEasy 自己 <code class="language-plaintext highlighter-rouge">new</code>，不经 Spring</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>JNDI</strong></td>
      <td style="text-align: left">Java Naming and Directory Interface</td>
      <td style="text-align: left">Java 命名与目录接口；<code class="language-plaintext highlighter-rouge">JndiResourceFactory</code> 从 JNDI 查找 Resource 实例</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>WAR</strong></td>
      <td style="text-align: left">Web Application Archive</td>
      <td style="text-align: left">传统 Java Web 应用打包格式，部署到外置 Servlet 容器</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>URI</strong></td>
      <td style="text-align: left">Uniform Resource Identifier</td>
      <td style="text-align: left">统一资源标识符；RESTEasy 用 URI 路径 + HTTP 动词匹配端点</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>HTTP</strong></td>
      <td style="text-align: left">Hypertext Transfer Protocol</td>
      <td style="text-align: left">超文本传输协议；本文指 GET/POST 等动词与请求/响应语义</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Call Chain</strong></td>
      <td style="text-align: left">—</td>
      <td style="text-align: left">调用链：一次 HTTP 请求从 Dispatcher 到 <code class="language-plaintext highlighter-rouge">getBean()</code> 的完整方法调用序列</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Scope</strong></td>
      <td style="text-align: left">—</td>
      <td style="text-align: left">Bean 作用域；文中主要指 Spring 的 <code class="language-plaintext highlighter-rouge">singleton</code>（单例缓存）与 <code class="language-plaintext highlighter-rouge">prototype</code>（每次新建）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Deployment</strong></td>
      <td style="text-align: left"><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/spi/ResteasyDeployment.java"><code class="language-plaintext highlighter-rouge">ResteasyDeployment</code></a></td>
      <td style="text-align: left">RESTEasy 运行时部署对象，持有 Registry、ProviderFactory、Dispatcher 等</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>MVC</strong></td>
      <td style="text-align: left">Model-View-Controller</td>
      <td style="text-align: left">Spring Web MVC 模块；WAR 集成常通过 <code class="language-plaintext highlighter-rouge">springmvc-resteasy.xml</code> 注册 <code class="language-plaintext highlighter-rouge">SpringBeanProcessor</code></td>
    </tr>
  </tbody>
</table>

<h2 id="一两个容器各自管什么">一、两个容器各自管什么</h2>

<table>
  <thead>
    <tr>
      <th style="text-align: left">维度</th>
      <th style="text-align: left">Spring</th>
      <th style="text-align: left">RESTEasy</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">核心抽象</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ApplicationContext</code> / <code class="language-plaintext highlighter-rouge">BeanFactory</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ResteasyDeployment</code></td>
    </tr>
    <tr>
      <td style="text-align: left">关键组件</td>
      <td style="text-align: left">Bean 定义、<code class="language-plaintext highlighter-rouge">BeanPostProcessor</code>、事件</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">Registry</code>、<code class="language-plaintext highlighter-rouge">Dispatcher</code>、<code class="language-plaintext highlighter-rouge">ResteasyProviderFactory</code></td>
    </tr>
    <tr>
      <td style="text-align: left">典型成员</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">CustomerService</code>、<code class="language-plaintext highlighter-rouge">HelloResource</code>（<code class="language-plaintext highlighter-rouge">@Component</code>）</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ResourceMethodRegistry</code> 中的 <code class="language-plaintext highlighter-rouge">ResourceFactory</code></td>
    </tr>
    <tr>
      <td style="text-align: left">注解</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Autowired</code>、<code class="language-plaintext highlighter-rouge">@Transactional</code>…</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Path</code>、<code class="language-plaintext highlighter-rouge">@GET</code>、<code class="language-plaintext highlighter-rouge">@Provider</code>、<code class="language-plaintext highlighter-rouge">@Context</code>…</td>
    </tr>
  </tbody>
</table>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Spring 容器   ≈  对象工厂 + 业务依赖注入 + 生命周期（创建 / 销毁）
RESTEasy 容器 ≈  路由注册表 + ResourceFactory 策略 + Provider 链（不是 BeanFactory）
</code></pre></div></div>

<p>RESTEasy <strong>故意不做</strong>通用 IoC：业务 DI 交给 Spring，自己只管 HTTP 运行时——Registry 登记「怎么拿实例」，ProviderFactory 登记 <code class="language-plaintext highlighter-rouge">@Provider</code> 单例，Dispatcher 编排请求链。</p>

<h3 id="jax-rs-注解spring-产出实例resteasy-消费元数据">JAX-RS 注解：Spring 产出实例，RESTEasy 消费元数据</h3>

<p>一个常见的误解是：既然 Resource 类是 Spring Bean，Spring 是否也会处理 <code class="language-plaintext highlighter-rouge">@Path</code>、<code class="language-plaintext highlighter-rouge">@GET</code>？<strong>不会。</strong> Spring 创建出来的 Bean，对 Spring 自身而言就是一个普通 Java 对象（可能被 AOP 代理）；Spring 只关心 <code class="language-plaintext highlighter-rouge">@Autowired</code>、<code class="language-plaintext highlighter-rouge">@Transactional</code>、scope 等 <strong>Spring 语义</strong>，不会因为类上挂了 JAX-RS 注解就去注册路由或处理 HTTP。</p>

<p>JAX-RS 注解在这套集成里扮演的是 <strong>元数据</strong>——挂在类和方法上，<strong>解释与执行</strong>它们的始终是 RESTEasy：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">注解</th>
      <th style="text-align: left">Spring 是否处理</th>
      <th style="text-align: left">RESTEasy 如何处理</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Path</code>、<code class="language-plaintext highlighter-rouge">@GET</code>、<code class="language-plaintext highlighter-rouge">@POST</code>…</td>
      <td style="text-align: left">❌ 不注册路由</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">Registry</code> 扫描，建立 URL → 方法映射</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Provider</code></td>
      <td style="text-align: left">❌ 不加入 Filter 链</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">registerProviderInstance</code> 注册到 ProviderFactory</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Context</code>（字段）</td>
      <td style="text-align: left">❌ 不走 <code class="language-plaintext highlighter-rouge">@Autowired</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">PropertyInjector</code> 注入（singleton：启动期 BPP；prototype：请求期 <code class="language-plaintext highlighter-rouge">getBean</code> 链）</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@QueryParam</code>、<code class="language-plaintext highlighter-rouge">@PathParam</code>（方法参数）</td>
      <td style="text-align: left">❌</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">MethodInjector</code> 在方法调用前注入</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Autowired</code>、<code class="language-plaintext highlighter-rouge">@Transactional</code></td>
      <td style="text-align: left">✅ Bean 创建时 DI / AOP 代理</td>
      <td style="text-align: left">❌ 不解析，调用时已注入完毕</td>
    </tr>
  </tbody>
</table>

<p>因此分工可以概括为：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Spring 产出：带 JAX-RS 注解的 Bean 实例（+ 业务 DI / AOP / scope）
RESTEasy 消费：读注解建路由，按请求做 JAX-RS 注入和方法调用
</code></pre></div></div>

<p><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-spring/src/main/java/org/jboss/resteasy/plugins/spring/SpringBeanProcessor.java"><code class="language-plaintext highlighter-rouge">SpringBeanProcessor</code></a> 在 Spring 启动期 <strong>发现</strong> 带 <code class="language-plaintext highlighter-rouge">@Path</code> 的 Bean 并建立 <code class="language-plaintext highlighter-rouge">SpringResourceFactory</code> 映射——这是桥接层的扫描，不是 Spring 框架本身理解 JAX-RS。真正把 Resource 注册进 <code class="language-plaintext highlighter-rouge">Registry</code>、在请求进来时匹配 URI 并调用 <code class="language-plaintext highlighter-rouge">@GET</code> 方法的，仍是 RESTEasy（见第四节 <code class="language-plaintext highlighter-rouge">ContextRefreshedEvent</code> → <code class="language-plaintext highlighter-rouge">addResourceFactory</code>）。</p>

<h2 id="二spring-bean-生命周期从-refresh-到-destroy">二、Spring Bean 生命周期：从 refresh 到 destroy</h2>

<p>Spring 容器的 Bean 生命周期是一个经过精心编排的流程，从 <code class="language-plaintext highlighter-rouge">BeanDefinition</code> 解析到实例销毁，每个阶段都有明确扩展点。<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup></p>

<h3 id="applicationcontextrefresh-中的位置">ApplicationContext.refresh() 中的位置</h3>

<p><a href="https://github.com/spring-projects/spring-framework/blob/main/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java#L582-L625"><code class="language-plaintext highlighter-rouge">refresh()</code></a> 结束时发布 <code class="language-plaintext highlighter-rouge">ContextRefreshedEvent</code>——Resteasy-Spring 正是在这个事件上把 Resource 注册进 <code class="language-plaintext highlighter-rouge">Registry</code>。<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></p>

<p><strong>singleton 与 prototype 在启动期的差异从这里开始</strong>：</p>

<ul>
  <li><strong>singleton</strong>（及默认 scope）：在 <code class="language-plaintext highlighter-rouge">finishBeanFactoryInitialization()</code> → <a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java#L1102-L1134"><code class="language-plaintext highlighter-rouge">preInstantiateSingletons()</code></a> 中 <strong>批量</strong> <code class="language-plaintext highlighter-rouge">getBean()</code>，走完整 <code class="language-plaintext highlighter-rouge">doCreateBean</code>，实例写入一级缓存。</li>
  <li><strong>prototype</strong>：<code class="language-plaintext highlighter-rouge">refresh()</code> 阶段 <strong>只注册 <code class="language-plaintext highlighter-rouge">BeanDefinition</code>，不创建实例</strong>——首次 <code class="language-plaintext highlighter-rouge">getBean()</code>（或依赖链触发）才走 <code class="language-plaintext highlighter-rouge">doCreateBean</code>。</li>
  <li><strong>Resteasy-Spring 的等待点</strong>：<code class="language-plaintext highlighter-rouge">SpringBeanProcessor.onApplicationEvent()</code> 注释写明需等 <em>“spring singleton bean creation is completed”</em><sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>——确保 singleton <code class="language-plaintext highlighter-rouge">@Path</code> Resource 及其 <code class="language-plaintext highlighter-rouge">@Autowired</code> 依赖在注册进 RESTEasy Registry 前已就绪；<strong>prototype Resource 则通常不在此阶段实例化</strong>。</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>refresh()  —  AbstractApplicationContext.refresh() :582-625
├── prepareRefresh() … registerBeanPostProcessors()     :590-606
│     └── SpringBeanProcessor.postProcessBeanFactory()  :235-261  【仅扫描定义，不建实例】
├── finishBeanFactoryInitialization(beanFactory)          :622
│     └── preInstantiateSingletons()                      :1102-1134
│           └── 仅 singleton：getBean() → createBean() → doCreateBean()
│           └── prototype：跳过，等首次 getBean()
└── finishRefresh() → ContextRefreshedEvent               :625
      └── SpringBeanProcessor.onApplicationEvent()        :453-467
            └── Registry.addResourceFactory(SpringResourceFactory)
                  └── SpringResourceFactory.registered()  :44-45
</code></pre></div></div>

<p>容器关闭时，<a href="https://github.com/spring-projects/spring-framework/blob/main/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java#L1246-L1247"><code class="language-plaintext highlighter-rouge">destroyBeans()</code></a> 调用 <a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java#L693-L705"><code class="language-plaintext highlighter-rouge">destroySingletons()</code></a>——<strong>只销毁 singleton</strong>；prototype 实例 Spring <strong>不跟踪</strong>，<a href="https://github.com/resteasy/resteasy/blob/main/resteasy-spring/src/main/java/org/jboss/resteasy/plugins/spring/SpringResourceFactory.java"><code class="language-plaintext highlighter-rouge">SpringResourceFactory.unregistered()</code> / <code class="language-plaintext highlighter-rouge">requestFinished()</code></a> 在集成路径下也为空实现。<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">4</a></sup><sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">5</a></sup></p>

<h3 id="核心骨架docreatebean--所有-scope-共用触发时机不同">核心骨架：doCreateBean — 所有 scope 共用，触发时机不同</h3>

<p><strong>常见误解</strong>：<code class="language-plaintext highlighter-rouge">doCreateBean</code> 是 singleton 专属。<strong>实际上，无论 singleton 还是 prototype，只要容器要「新建一个 Bean 实例」，都走同一条 <code class="language-plaintext highlighter-rouge">doCreateBean</code> → <code class="language-plaintext highlighter-rouge">initializeBean</code> 流水线</strong>——scope 不改变流水线步骤，只改变 <strong>何时触发</strong> 以及 <strong>完成后是否入缓存</strong>。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>createBeanInstance → populateBean → initializeBean → 【singleton 收尾：见下节】
  AbstractAutowireCapableBeanFactory.doCreateBean()  :556-603
    createBeanInstance()  :565
    populateBean()        :602   — @Autowired 在此
    initializeBean()      :603   — BPP、@PostConstruct 在此
    └─ prototype: 直接 return exposedObject，容器不登记
</code></pre></div></div>

<p><strong>注意</strong>：<code class="language-plaintext highlighter-rouge">addSingleton()</code> <strong>不在</strong> <code class="language-plaintext highlighter-rouge">doCreateBean()</code> 内部调用，而在其外层 <a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java#L255-L404"><code class="language-plaintext highlighter-rouge">getSingleton(beanName, ObjectFactory)</code></a> 中——<code class="language-plaintext highlighter-rouge">ObjectFactory</code>  lambda 执行 <code class="language-plaintext highlighter-rouge">createBean()</code> → <code class="language-plaintext highlighter-rouge">doCreateBean()</code>，成功返回后才 <code class="language-plaintext highlighter-rouge">addSingleton()</code> 写入 <a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java#L85-L86"><code class="language-plaintext highlighter-rouge">singletonObjects</code></a>。</p>

<h3 id="singleton-缓存defaultsingletonbeanregistry-三级结构与-addsingleton">Singleton 缓存：<code class="language-plaintext highlighter-rouge">DefaultSingletonBeanRegistry</code> 三级结构与 <code class="language-plaintext highlighter-rouge">addSingleton</code></h3>

<p><a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java"><code class="language-plaintext highlighter-rouge">DefaultSingletonBeanRegistry</code></a> 是 Spring 各 <code class="language-plaintext highlighter-rouge">BeanFactory</code> 实现管理 singleton 实例的基类。类注释说明它 <em>“factoring out the common management of singleton bean instances”</em>，与 <code class="language-plaintext highlighter-rouge">BeanDefinition</code>、具体创建流程解耦。<sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">6</a></sup></p>

<h4 id="三个-map--一个注册表">三个 Map + 一个注册表</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// spring-beans/.../DefaultSingletonBeanRegistry.java:85-98</span>
<span class="cm">/** Cache of singleton objects: bean name to bean instance. */</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">singletonObjects</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ConcurrentHashMap</span><span class="o">&lt;&gt;(</span><span class="mi">256</span><span class="o">);</span>

<span class="cm">/** Creation-time registry of singleton factories: bean name to ObjectFactory. */</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">ObjectFactory</span><span class="o">&lt;?&gt;&gt;</span> <span class="n">singletonFactories</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ConcurrentHashMap</span><span class="o">&lt;&gt;(</span><span class="mi">16</span><span class="o">);</span>

<span class="cm">/** Cache of early singleton objects: bean name to bean instance. */</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">earlySingletonObjects</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ConcurrentHashMap</span><span class="o">&lt;&gt;(</span><span class="mi">16</span><span class="o">);</span>

<span class="cm">/** Set of registered singletons, containing the bean names in registration order. */</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">Set</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">registeredSingletons</span> <span class="o">=</span> <span class="nc">Collections</span><span class="o">.</span><span class="na">synchronizedSet</span><span class="o">(</span><span class="k">new</span> <span class="nc">LinkedHashSet</span><span class="o">&lt;&gt;(</span><span class="mi">256</span><span class="o">));</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Map</th>
      <th style="text-align: left">俗称</th>
      <th style="text-align: left">存什么</th>
      <th style="text-align: left">何时写入</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">singletonObjects</code></td>
      <td style="text-align: left"><strong>一级缓存</strong></td>
      <td style="text-align: left">完全初始化完成的 singleton（含 AOP 包装后的最终对象）</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">addSingleton()</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">singletonFactories</code></td>
      <td style="text-align: left"><strong>二级缓存</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ObjectFactory</code>，用于循环依赖时提前暴露半成品</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">addSingletonFactory()</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">earlySingletonObjects</code></td>
      <td style="text-align: left"><strong>三级缓存</strong></td>
      <td style="text-align: left">从二级 <code class="language-plaintext highlighter-rouge">ObjectFactory.getObject()</code> 取出的早期引用</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">getSingleton()</code> 解析循环依赖时</td>
    </tr>
  </tbody>
</table>

<h4 id="创建-singleton-的完整-call-chain">创建 singleton 的完整 call chain</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>AbstractBeanFactory.doGetBean()  :343-356
└─ getSingleton(beanName, () -&gt; createBean(...))     DefaultSingletonBeanRegistry :255
     ├─ singletonObjects 已有 → 直接返回（运行期 RESTEasy getBean 命中此路径）
     └─ 未命中 → singletonFactory.getObject()
          └─ createBean() → doCreateBean()
               ├─ createBeanInstance()
               ├─ addSingletonFactory(...)   :587-596  【仅循环依赖场景，populateBean 之前】
               ├─ populateBean()
               ├─ initializeBean()            — BPP、@PostConstruct
               └─ return exposedObject
          └─ addSingleton(beanName, singletonObject)  :402-404
               — 写入 singletonObjects，清除 singletonFactories / earlySingletonObjects
</code></pre></div></div>

<p><a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java#L343-L356"><code class="language-plaintext highlighter-rouge">AbstractBeanFactory.doGetBean()</code></a> 对 singleton 的入口：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// spring-beans/.../AbstractBeanFactory.java:343-356</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mbd</span><span class="o">.</span><span class="na">isSingleton</span><span class="o">())</span> <span class="o">{</span>
    <span class="n">sharedInstance</span> <span class="o">=</span> <span class="n">getSingleton</span><span class="o">(</span><span class="n">beanName</span><span class="o">,</span> <span class="o">()</span> <span class="o">-&gt;</span> <span class="o">{</span>
        <span class="k">try</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nf">createBean</span><span class="o">(</span><span class="n">beanName</span><span class="o">,</span> <span class="n">mbd</span><span class="o">,</span> <span class="n">args</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="k">catch</span> <span class="o">(</span><span class="nc">BeansException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">destroySingleton</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span>  <span class="c1">// 创建失败时清理二/三级缓存</span>
            <span class="k">throw</span> <span class="n">ex</span><span class="o">;</span>
        <span class="o">}</span>
    <span class="o">});</span>
    <span class="n">beanInstance</span> <span class="o">=</span> <span class="n">getObjectForBeanInstance</span><span class="o">(</span><span class="n">sharedInstance</span><span class="o">,</span> <span class="n">requiredType</span><span class="o">,</span> <span class="n">name</span><span class="o">,</span> <span class="n">beanName</span><span class="o">,</span> <span class="n">mbd</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">doCreateBean</code> 中 <strong>循环依赖</strong> 的提前暴露（<a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java#L587-L596"><code class="language-plaintext highlighter-rouge">AbstractAutowireCapableBeanFactory.java:587-596</code></a>）：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// populateBean 之前：允许其他 Bean 在创建完成前先引用</span>
<span class="kt">boolean</span> <span class="n">earlySingletonExposure</span> <span class="o">=</span> <span class="o">(</span><span class="n">mbd</span><span class="o">.</span><span class="na">isSingleton</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="k">this</span><span class="o">.</span><span class="na">allowCircularReferences</span> <span class="o">&amp;&amp;</span>
        <span class="n">isSingletonCurrentlyInCreation</span><span class="o">(</span><span class="n">beanName</span><span class="o">));</span>
<span class="k">if</span> <span class="o">(</span><span class="n">earlySingletonExposure</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">addSingletonFactory</span><span class="o">(</span><span class="n">beanName</span><span class="o">,</span> <span class="o">()</span> <span class="o">-&gt;</span> <span class="n">getEarlyBeanReference</span><span class="o">(</span><span class="n">beanName</span><span class="o">,</span> <span class="n">mbd</span><span class="o">,</span> <span class="n">bean</span><span class="o">));</span>
<span class="o">}</span>
</code></pre></div></div>

<p><a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java#L208-L244"><code class="language-plaintext highlighter-rouge">getSingleton(beanName, allowEarlyReference)</code></a> 的查找顺序——<strong>先一级，再三级，最后通过二级 <code class="language-plaintext highlighter-rouge">ObjectFactory</code> 生成早期引用</strong>：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// DefaultSingletonBeanRegistry.java:210-229</span>
<span class="nc">Object</span> <span class="n">singletonObject</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">singletonObjects</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">singletonObject</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">isSingletonCurrentlyInCreation</span><span class="o">(</span><span class="n">beanName</span><span class="o">))</span> <span class="o">{</span>
    <span class="n">singletonObject</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">earlySingletonObjects</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">singletonObject</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">allowEarlyReference</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">ObjectFactory</span><span class="o">&lt;?&gt;</span> <span class="n">singletonFactory</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">singletonFactories</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">singletonFactory</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">singletonObject</span> <span class="o">=</span> <span class="n">singletonFactory</span><span class="o">.</span><span class="na">getObject</span><span class="o">();</span>
            <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">singletonFactories</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">beanName</span><span class="o">)</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">this</span><span class="o">.</span><span class="na">earlySingletonObjects</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">beanName</span><span class="o">,</span> <span class="n">singletonObject</span><span class="o">);</span>
            <span class="o">}</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>创建成功后 <a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java#L159-L173"><code class="language-plaintext highlighter-rouge">addSingleton()</code></a> 将 <strong>最终对象</strong> 写入一级缓存，并清理二、三级：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// DefaultSingletonBeanRegistry.java:159-167</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">addSingleton</span><span class="o">(</span><span class="nc">String</span> <span class="n">beanName</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">singletonObject</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">Object</span> <span class="n">oldObject</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">singletonObjects</span><span class="o">.</span><span class="na">putIfAbsent</span><span class="o">(</span><span class="n">beanName</span><span class="o">,</span> <span class="n">singletonObject</span><span class="o">);</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">oldObject</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Could not register object [...] there is already object [...] bound"</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="k">this</span><span class="o">.</span><span class="na">singletonFactories</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span>
    <span class="k">this</span><span class="o">.</span><span class="na">earlySingletonObjects</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span>
    <span class="k">this</span><span class="o">.</span><span class="na">registeredSingletons</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>prototype 完全不进入这三个 Map</strong>——<a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java#L359-L369"><code class="language-plaintext highlighter-rouge">doGetBean</code> 的 prototype 分支</a> 直接 <code class="language-plaintext highlighter-rouge">createBean()</code> 后 <code class="language-plaintext highlighter-rouge">getObjectForBeanInstance()</code> 返回，容器不持有引用。</p>

<h4 id="与-resteasy-spring-的关系">与 Resteasy-Spring 的关系</h4>

<ul>
  <li><strong>singleton <code class="language-plaintext highlighter-rouge">@Path</code> Resource</strong>：<code class="language-plaintext highlighter-rouge">refresh()</code> 期间 <code class="language-plaintext highlighter-rouge">preInstantiateSingletons()</code> → 上述 call chain 走完 → 实例在 <code class="language-plaintext highlighter-rouge">singletonObjects</code>；之后 RESTEasy 每次 <code class="language-plaintext highlighter-rouge">SpringResourceFactory.createResource()</code> → <code class="language-plaintext highlighter-rouge">getBean()</code> <strong>命中一级缓存</strong>，不再 <code class="language-plaintext highlighter-rouge">doCreateBean</code>。</li>
  <li><strong>prototype Resource</strong>：不进入 <code class="language-plaintext highlighter-rouge">singletonObjects</code>；每次 <code class="language-plaintext highlighter-rouge">createResource()</code> → <code class="language-plaintext highlighter-rouge">getBean()</code> 走 prototype 分支，重新 <code class="language-plaintext highlighter-rouge">doCreateBean</code>（见第四节）。</li>
</ul>

<p>容器关闭时 <a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java#L725-L727"><code class="language-plaintext highlighter-rouge">destroySingletons()</code></a> 清空三个 Map——仅影响 singleton；prototype 实例从未登记，无从销毁。</p>

<p>其中 <code class="language-plaintext highlighter-rouge">initializeBean</code> 是生命周期回调最密集的阶段（<code class="language-plaintext highlighter-rouge">:1797-1821</code>）——<strong>singleton 在 <code class="language-plaintext highlighter-rouge">refresh()</code> 走一次，prototype 在每次 <code class="language-plaintext highlighter-rouge">getBean()</code> 再走一次</strong>：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>initializeBean  —  AbstractAutowireCapableBeanFactory.initializeBean()  :1797-1821
├── invokeAwareMethods()                         :1803
├── applyBeanPostProcessorsBeforeInitialization  :1807
├── invokeInitMethods()                            :1811  — @PostConstruct
└── applyBeanPostProcessorsAfterInitialization     :1818
      └── ResteasyBeanPostProcessor.postProcessAfterInitialization()  :114-146
            └── inject() — singleton 启动期；prototype 请求期（有 HttpRequest 时）
</code></pre></div></div>

<p>源码（<a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java"><code class="language-plaintext highlighter-rouge">AbstractAutowireCapableBeanFactory.java</code></a>）：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// spring-beans/.../AbstractAutowireCapableBeanFactory.java:556-603</span>
<span class="kd">protected</span> <span class="nc">Object</span> <span class="nf">doCreateBean</span><span class="o">(</span><span class="nc">String</span> <span class="n">beanName</span><span class="o">,</span> <span class="nc">RootBeanDefinition</span> <span class="n">mbd</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">Object</span> <span class="nd">@Nullable</span> <span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">BeanWrapper</span> <span class="n">instanceWrapper</span> <span class="o">=</span> <span class="n">createBeanInstance</span><span class="o">(</span><span class="n">beanName</span><span class="o">,</span> <span class="n">mbd</span><span class="o">,</span> <span class="n">args</span><span class="o">);</span>
    <span class="nc">Object</span> <span class="n">bean</span> <span class="o">=</span> <span class="n">instanceWrapper</span><span class="o">.</span><span class="na">getWrappedInstance</span><span class="o">();</span>
    <span class="c1">// ...</span>
    <span class="n">populateBean</span><span class="o">(</span><span class="n">beanName</span><span class="o">,</span> <span class="n">mbd</span><span class="o">,</span> <span class="n">instanceWrapper</span><span class="o">);</span>
    <span class="n">exposedObject</span> <span class="o">=</span> <span class="n">initializeBean</span><span class="o">(</span><span class="n">beanName</span><span class="o">,</span> <span class="n">exposedObject</span><span class="o">,</span> <span class="n">mbd</span><span class="o">);</span>
    <span class="c1">// ...</span>
<span class="o">}</span>
</code></pre></div></div>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// spring-beans/.../AbstractAutowireCapableBeanFactory.java:1797-1821</span>
<span class="kd">protected</span> <span class="nc">Object</span> <span class="nf">initializeBean</span><span class="o">(</span><span class="nc">String</span> <span class="n">beanName</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">bean</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">RootBeanDefinition</span> <span class="n">mbd</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">invokeAwareMethods</span><span class="o">(</span><span class="n">beanName</span><span class="o">,</span> <span class="n">bean</span><span class="o">);</span>
    <span class="n">wrappedBean</span> <span class="o">=</span> <span class="n">applyBeanPostProcessorsBeforeInitialization</span><span class="o">(</span><span class="n">wrappedBean</span><span class="o">,</span> <span class="n">beanName</span><span class="o">);</span>
    <span class="n">invokeInitMethods</span><span class="o">(</span><span class="n">beanName</span><span class="o">,</span> <span class="n">wrappedBean</span><span class="o">,</span> <span class="n">mbd</span><span class="o">);</span>
    <span class="n">wrappedBean</span> <span class="o">=</span> <span class="n">applyBeanPostProcessorsAfterInitialization</span><span class="o">(</span><span class="n">wrappedBean</span><span class="o">,</span> <span class="n">beanName</span><span class="o">);</span>
    <span class="k">return</span> <span class="n">wrappedBean</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="一条完整的-bean-生命周期时间线">一条完整的 Bean 生命周期时间线</h3>

<p><strong>创建流水线（singleton 与 prototype 相同）</strong>：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">阶段</th>
      <th style="text-align: left">事件</th>
      <th style="text-align: left">扩展点</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">定义</td>
      <td style="text-align: left">解析 XML / 扫描 <code class="language-plaintext highlighter-rouge">@Component</code> / <code class="language-plaintext highlighter-rouge">@Bean</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">BeanFactoryPostProcessor</code></td>
    </tr>
    <tr>
      <td style="text-align: left">实例化</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">createBeanInstance()</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">InstantiationAwareBeanPostProcessor</code></td>
    </tr>
    <tr>
      <td style="text-align: left">依赖注入</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">populateBean()</code> — <code class="language-plaintext highlighter-rouge">@Autowired</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">AutowiredAnnotationBeanPostProcessor</code></td>
    </tr>
    <tr>
      <td style="text-align: left">Aware</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">setBeanName</code>、<code class="language-plaintext highlighter-rouge">setBeanFactory</code>…</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">*Aware</code> 接口</td>
    </tr>
    <tr>
      <td style="text-align: left">BPP 前置</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">postProcessBeforeInitialization</code></td>
      <td style="text-align: left">自定义 <code class="language-plaintext highlighter-rouge">BeanPostProcessor</code></td>
    </tr>
    <tr>
      <td style="text-align: left">初始化</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@PostConstruct</code> / <code class="language-plaintext highlighter-rouge">InitializingBean</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">InitDestroyAnnotationBeanPostProcessor</code></td>
    </tr>
    <tr>
      <td style="text-align: left">BPP 后置</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">postProcessAfterInitialization</code></td>
      <td style="text-align: left">AOP 代理、<code class="language-plaintext highlighter-rouge">ResteasyBeanPostProcessor</code></td>
    </tr>
  </tbody>
</table>

<p><strong>scope 决定「何时走流水线」与「收尾」</strong>（Resteasy-Spring 集成下）：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">维度</th>
      <th style="text-align: left">singleton（默认）</th>
      <th style="text-align: left">prototype</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>何时 <code class="language-plaintext highlighter-rouge">doCreateBean</code></strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">refresh()</code> → <code class="language-plaintext highlighter-rouge">preInstantiateSingletons()</code></td>
      <td style="text-align: left">每次 <code class="language-plaintext highlighter-rouge">getBean()</code>（含 RESTEasy <code class="language-plaintext highlighter-rouge">createResource()</code> 触发）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>实例缓存</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">singletonObjects</code> 一级缓存（<a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java"><code class="language-plaintext highlighter-rouge">DefaultSingletonBeanRegistry</code></a>）</td>
      <td style="text-align: left">不进入三个 singleton Map</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">@Autowired</code> 注入</strong></td>
      <td style="text-align: left">启动期 <code class="language-plaintext highlighter-rouge">populateBean</code> 一次</td>
      <td style="text-align: left">每次 <code class="language-plaintext highlighter-rouge">getBean</code> 重新 <code class="language-plaintext highlighter-rouge">populateBean</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>JAX-RS 字段 <code class="language-plaintext highlighter-rouge">@Context</code></strong></td>
      <td style="text-align: left">启动期 BPP（<code class="language-plaintext highlighter-rouge">HttpRequest == null</code>）</td>
      <td style="text-align: left">请求期 BPP（<code class="language-plaintext highlighter-rouge">HttpRequest</code> 存在）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>销毁</strong></td>
      <td style="text-align: left">容器关闭 <code class="language-plaintext highlighter-rouge">destroySingletons()</code> / <code class="language-plaintext highlighter-rouge">@PreDestroy</code></td>
      <td style="text-align: left"><strong>容器不跟踪</strong>，无自动 <code class="language-plaintext highlighter-rouge">@PreDestroy</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>典型 <code class="language-plaintext highlighter-rouge">@Path</code> Resource</strong></td>
      <td style="text-align: left">启动期实例就绪 → <code class="language-plaintext highlighter-rouge">ContextRefreshedEvent</code> 注册路由</td>
      <td style="text-align: left">启动期仅注册 Factory + 路由；<strong>首请求</strong>才实例化</td>
    </tr>
  </tbody>
</table>

<p><a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java"><code class="language-plaintext highlighter-rouge">AbstractBeanFactory.doGetBean()</code></a> 是运行期 scope 分叉点——singleton 命中缓存则 <strong>跳过</strong> 整条 <code class="language-plaintext highlighter-rouge">doCreateBean</code>；prototype 每次进入 <code class="language-plaintext highlighter-rouge">createBean()</code>：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// spring-beans/.../AbstractBeanFactory.java:257-268 — singleton：缓存命中，不再 doCreateBean</span>
<span class="nc">Object</span> <span class="n">sharedInstance</span> <span class="o">=</span> <span class="n">getSingleton</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">sharedInstance</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">args</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">beanInstance</span> <span class="o">=</span> <span class="n">getObjectForBeanInstance</span><span class="o">(</span><span class="n">sharedInstance</span><span class="o">,</span> <span class="o">...);</span>
<span class="o">}</span>
<span class="c1">// :359-364 — prototype：每次 createBean → doCreateBean</span>
<span class="k">else</span> <span class="nf">if</span> <span class="o">(</span><span class="n">mbd</span><span class="o">.</span><span class="na">isPrototype</span><span class="o">())</span> <span class="o">{</span>
    <span class="n">prototypeInstance</span> <span class="o">=</span> <span class="n">createBean</span><span class="o">(</span><span class="n">beanName</span><span class="o">,</span> <span class="n">mbd</span><span class="o">,</span> <span class="n">args</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>下文 RESTEasy 侧用 <code class="language-plaintext highlighter-rouge">ResourceFactory</code> 策略表达「取实例」；Spring 集成时 scope 仍由 Spring 决定，第四节详述 singleton / prototype 在 bridge 模式下的差异。</p>

<h2 id="三resteasy-resource-生命周期registry-与-resourcefactory">三、RESTEasy Resource 生命周期：Registry 与 ResourceFactory</h2>

<p>RESTEasy 对 <code class="language-plaintext highlighter-rouge">@Path</code> Resource 的管理精简得多：不解析 <code class="language-plaintext highlighter-rouge">@Autowired</code>、不管理 AOP 代理、不处理 <code class="language-plaintext highlighter-rouge">@PreDestroy</code>——只关心 <strong>每次 HTTP 请求如何拿到 Resource 实例</strong>。</p>

<p>这里要先澄清一个常见混淆：<strong>RESTEasy 注册的不是 Resource 实例，而是 ResourceFactory——一个「取实例策略」对象</strong>。这与 Spring 在容器里登记 <code class="language-plaintext highlighter-rouge">BeanDefinition</code>、再按需 <code class="language-plaintext highlighter-rouge">createBean()</code> 是不同层级的抽象；理解这一点，是读懂 RESTEasy 与 Spring 分工的基础。</p>

<h3 id="核心模式一个-resource-类--一个-resourcefactory--多个-invoker">核心模式：一个 Resource 类 → 一个 ResourceFactory → 多个 Invoker</h3>

<p>RESTEasy 采用 <strong>Strategy 模式</strong>：<a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core-spi/src/main/java/org/jboss/resteasy/spi/ResourceFactory.java"><code class="language-plaintext highlighter-rouge">ResourceFactory</code></a> 接口注释写得很直白——<em>“Implementations of this interface are registered through the Registry class”</em>。每个 <code class="language-plaintext highlighter-rouge">@Path</code> Resource 类在 Registry 中对应 <strong>一个</strong> Factory 实例；Factory 回答的问题是：<strong>「每次请求来了，怎么拿到 Resource 对象？」</strong>——而不是「如何初始化一个通用 Bean」。</p>

<p><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceMethodRegistry.java"><code class="language-plaintext highlighter-rouge">ResourceMethodRegistry</code></a> 提供了多种 <strong>注册 API</strong>，本质是在选择不同的 Factory 实现：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ResourceMethodRegistry.java — 按创建策略选择 Factory，而非直接注册实例</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">addPerRequestResource</span><span class="o">(</span><span class="nc">Class</span> <span class="n">clazz</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">addResourceFactory</span><span class="o">(</span><span class="k">new</span> <span class="nc">POJOResourceFactory</span><span class="o">(</span><span class="n">resourceBuilder</span><span class="o">,</span> <span class="n">clazz</span><span class="o">));</span>   <span class="c1">// 每请求 new</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kt">void</span> <span class="nf">addSingletonResource</span><span class="o">(</span><span class="nc">Object</span> <span class="n">singleton</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">ResourceClass</span> <span class="n">resourceClass</span> <span class="o">=</span> <span class="n">resourceBuilder</span><span class="o">.</span><span class="na">getRootResourceFromAnnotations</span><span class="o">(</span><span class="n">singleton</span><span class="o">.</span><span class="na">getClass</span><span class="o">());</span>
    <span class="n">addResourceFactory</span><span class="o">(</span><span class="k">new</span> <span class="nc">SingletonResource</span><span class="o">(</span><span class="n">singleton</span><span class="o">,</span> <span class="n">resourceClass</span><span class="o">));</span>   <span class="c1">// 固定对象</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kt">void</span> <span class="nf">addJndiResource</span><span class="o">(</span><span class="nc">String</span> <span class="n">jndiName</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">addResourceFactory</span><span class="o">(</span><span class="k">new</span> <span class="nc">JndiResourceFactory</span><span class="o">(</span><span class="n">jndiName</span><span class="o">));</span>                   <span class="c1">// JNDI 查找</span>
<span class="o">}</span>
<span class="c1">// Spring 集成：addResourceFactory(new SpringResourceFactory(beanName, beanFactory, scannable))</span>
</code></pre></div></div>

<p><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/plugins/server/resourcefactory/POJOResourceFactory.java"><code class="language-plaintext highlighter-rouge">POJOResourceFactory</code></a> 的类注释 <em>“Allocates an instance of a class at each invocation”</em> 进一步说明：Factory 的职责是 <strong>分配（allocate）实例</strong>，不是长期持有实例。</p>

<h4 id="注册期-call-chainfactory-与-invoker-如何绑定">注册期 call chain：Factory 与 Invoker 如何绑定</h4>

<p><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceMethodRegistry.java#L200-L220"><code class="language-plaintext highlighter-rouge">ResourceMethodRegistry.addResourceFactory()</code></a> 是 Registry 的核心入口，分两步：<sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">7</a></sup></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>addResourceFactory(ResourceFactory ref, …, Class[] classes)
    ├─ ref.registered(providerFactory)          — 启动期：预建 ConstructorInjector / PropertyInjector
    └─ for each clazz:
         register(ref, base, resourceClass)
             └─ processMethod(rf, base, method)   — 每个 @GET/@POST 方法
                  └─ new ResourceMethodInvoker(method, injectorFactory, rf, providerFactory)
                       — 同一 rf 被所有方法的 Invoker 共享
</code></pre></div></div>

<p>源码：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// resteasy-core/.../ResourceMethodRegistry.java:200-220</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">addResourceFactory</span><span class="o">(</span><span class="nc">ResourceFactory</span> <span class="n">ref</span><span class="o">,</span> <span class="nc">ResourceBuilder</span> <span class="n">resourceBuilder</span><span class="o">,</span> <span class="nc">String</span> <span class="n">base</span><span class="o">,</span> <span class="nc">Class</span><span class="o">[]</span> <span class="n">classes</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">ref</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span>
        <span class="n">ref</span><span class="o">.</span><span class="na">registered</span><span class="o">(</span><span class="n">providerFactory</span><span class="o">);</span>
    <span class="k">for</span> <span class="o">(</span><span class="nc">Class</span> <span class="n">clazz</span> <span class="o">:</span> <span class="n">classes</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">ResourceClass</span> <span class="n">resourceClass</span> <span class="o">=</span> <span class="n">resourceBuilder</span><span class="o">.</span><span class="na">getRootResourceFromAnnotations</span><span class="o">(</span><span class="n">clazz</span><span class="o">);</span>
        <span class="n">register</span><span class="o">(</span><span class="n">ref</span><span class="o">,</span> <span class="n">base</span><span class="o">,</span> <span class="n">resourceClass</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>

<span class="c1">// :295-310 — 每个 HTTP 方法绑定同一个 ResourceFactory rf</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">processMethod</span><span class="o">(</span><span class="nc">ResourceFactory</span> <span class="n">rf</span><span class="o">,</span> <span class="nc">String</span> <span class="n">base</span><span class="o">,</span> <span class="nc">ResourceLocator</span> <span class="n">method</span><span class="o">)</span> <span class="o">{</span>
    <span class="c1">// ...</span>
    <span class="nc">ResourceMethodInvoker</span> <span class="n">invoker</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ResourceMethodInvoker</span><span class="o">((</span><span class="nc">ResourceMethod</span><span class="o">)</span> <span class="n">method</span><span class="o">,</span> <span class="n">injectorFactory</span><span class="o">,</span> <span class="n">rf</span><span class="o">,</span> <span class="n">providerFactory</span><span class="o">);</span>
    <span class="n">root</span><span class="o">.</span><span class="na">addInvoker</span><span class="o">(</span><span class="n">classExpression</span><span class="o">,</span> <span class="n">fullpath</span><span class="o">,</span> <span class="n">invoker</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>要点</strong>：</p>

<ul>
  <li>Registry 的「路由表」存的是 <code class="language-plaintext highlighter-rouge">ResourceMethodInvoker</code>（URL → 方法调用器），每个 Invoker <strong>持有</strong> 同一个 <code class="language-plaintext highlighter-rouge">ResourceFactory</code> 引用（<a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceMethodInvoker.java"><code class="language-plaintext highlighter-rouge">ResourceMethodInvoker</code></a> 字段 <code class="language-plaintext highlighter-rouge">protected ResourceFactory resource</code>）。</li>
  <li><code class="language-plaintext highlighter-rouge">registered()</code> 在 <strong>注册期</strong> 调用一次，预建注入器；<strong>不在此阶段创建 Resource 实例</strong>（<code class="language-plaintext highlighter-rouge">SingletonResource</code> 除外——实例由调用方在 <code class="language-plaintext highlighter-rouge">addSingletonResource(singleton)</code> 时传入，Factory 仅持有引用）。</li>
  <li>一个 <code class="language-plaintext highlighter-rouge">HelloResource</code> 类若有 3 个 <code class="language-plaintext highlighter-rouge">@GET</code> 方法 → 1 个 Factory + 3 个 Invoker，Factory 决定 3 个 Invoker 共享的取实例策略。</li>
</ul>

<h4 id="为何每个端点一个-resourcemethodinvoker">为何每个端点一个 ResourceMethodInvoker？</h4>

<p>需要澄清：<strong>不是「每个 Java 方法」一个 Invoker，而是每个 JAX-RS HTTP 端点一个 Invoker</strong>——<code class="language-plaintext highlighter-rouge">@GET /users</code> 与 <code class="language-plaintext highlighter-rouge">@POST /users</code> 在 Registry 路由表里是两个不同叶子节点，<a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceMethodInvoker.java"><code class="language-plaintext highlighter-rouge">ResourceMethodInvoker</code></a> 就是这个叶子。</p>

<p><strong>1. 路由匹配的目标是一个端点（Resource Method），不是一个类</strong></p>

<p><code class="language-plaintext highlighter-rouge">registry.getResourceInvoker(request)</code> 做的是 <strong>URI + HTTP 动词 → 唯一 Invoker</strong>，不是「先找到 Resource 类再反射找方法」。JAX-RS 规范（Section 3.7）的匹配粒度就是 <strong>Resource Method</strong>；Registry 的 URL 树（<code class="language-plaintext highlighter-rouge">RootClassNode</code>）在注册期把每个 <code class="language-plaintext highlighter-rouge">@Path</code> + <code class="language-plaintext highlighter-rouge">@GET</code>/<code class="language-plaintext highlighter-rouge">@POST</code> 组合挂到独立 Invoker 上，请求期才能直接命中，而无需每次反射扫描。</p>

<p><strong>2. Invoker 绑定的是「这个方法独有」的运行时上下文</strong></p>

<p><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceMethodInvoker.java#L97-L192"><code class="language-plaintext highlighter-rouge">ResourceMethodInvoker</code> 构造函数</a> 在 <strong>注册期</strong> 为 <strong>这一个</strong> 端点预建好调用链——这些 metadata 因方法而异，无法多个端点共享：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// resteasy-core/.../ResourceMethodInvoker.java:97-141（节选）</span>
<span class="kd">public</span> <span class="nf">ResourceMethodInvoker</span><span class="o">(</span><span class="kd">final</span> <span class="nc">ResourceMethod</span> <span class="n">method</span><span class="o">,</span> <span class="kd">final</span> <span class="nc">InjectorFactory</span> <span class="n">injector</span><span class="o">,</span>
        <span class="kd">final</span> <span class="nc">ResourceFactory</span> <span class="n">resource</span><span class="o">,</span> <span class="kd">final</span> <span class="nc">ResteasyProviderFactory</span> <span class="n">providerFactory</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">this</span><span class="o">.</span><span class="na">resource</span> <span class="o">=</span> <span class="n">resource</span><span class="o">;</span>   <span class="c1">// 共享：取实例策略（ResourceFactory）</span>
    <span class="k">this</span><span class="o">.</span><span class="na">method</span> <span class="o">=</span> <span class="n">method</span><span class="o">;</span>       <span class="c1">// 独有：这个端点的 metadata</span>

    <span class="k">this</span><span class="o">.</span><span class="na">methodInjector</span> <span class="o">=</span> <span class="n">injector</span><span class="o">.</span><span class="na">createMethodInjector</span><span class="o">(</span><span class="n">method</span><span class="o">,</span> <span class="n">resourceMethodProviderFactory</span><span class="o">);</span>

    <span class="n">requestFilters</span> <span class="o">=</span> <span class="n">resourceMethodProviderFactory</span><span class="o">.</span><span class="na">getContainerRequestFilterRegistry</span><span class="o">()</span>
            <span class="o">.</span><span class="na">postMatch</span><span class="o">(</span><span class="n">method</span><span class="o">.</span><span class="na">getResourceClass</span><span class="o">().</span><span class="na">getClazz</span><span class="o">(),</span> <span class="n">method</span><span class="o">.</span><span class="na">getAnnotatedMethod</span><span class="o">());</span>
    <span class="n">responseFilters</span> <span class="o">=</span> <span class="n">resourceMethodProviderFactory</span><span class="o">.</span><span class="na">getContainerResponseFilterRegistry</span><span class="o">()</span>
            <span class="o">.</span><span class="na">postMatch</span><span class="o">(</span><span class="n">method</span><span class="o">.</span><span class="na">getResourceClass</span><span class="o">().</span><span class="na">getClazz</span><span class="o">(),</span> <span class="n">method</span><span class="o">.</span><span class="na">getAnnotatedMethod</span><span class="o">());</span>
    <span class="c1">// … @Produces / 异步 / SSE / Bean Validation 等也按 method 配置</span>
<span class="o">}</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th style="text-align: left">注册期绑定</th>
      <th style="text-align: left">为何 per-method</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">methodInjector</code></td>
      <td style="text-align: left">每个方法参数列表不同（<code class="language-plaintext highlighter-rouge">@QueryParam("id")</code> vs <code class="language-plaintext highlighter-rouge">@PathParam("name")</code>）</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">requestFilters</code> / <code class="language-plaintext highlighter-rouge">responseFilters</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@NameBinding</code>、Filter 按 <strong>具体方法</strong> <code class="language-plaintext highlighter-rouge">postMatch(clazz, method)</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Produces</code> / <code class="language-plaintext highlighter-rouge">@Consumes</code></td>
      <td style="text-align: left">内容协商、是否期望 request body，因方法而异</td>
    </tr>
    <tr>
      <td style="text-align: left">异步 / SSE / 校验</td>
      <td style="text-align: left">返回类型、<code class="language-plaintext highlighter-rouge">@Stream</code>、Validation 均按方法配置</td>
    </tr>
  </tbody>
</table>

<p><strong>3. 共享 Factory，不共享 MethodInjector</strong></p>

<p>运行时 Invoker 的职责拆成两步——取实例与调方法：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ResourceMethodInvoker.java:348-356 + :555-560</span>
<span class="kd">public</span> <span class="nc">BuiltResponse</span> <span class="nf">invoke</span><span class="o">(</span><span class="nc">HttpRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpResponse</span> <span class="n">response</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">Object</span> <span class="n">resource</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">resource</span><span class="o">.</span><span class="na">createResource</span><span class="o">(...);</span>  <span class="c1">// 共享 ResourceFactory</span>
    <span class="k">return</span> <span class="nf">invoke</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="n">response</span><span class="o">,</span> <span class="n">resource</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="nc">Object</span> <span class="nf">internalInvokeOnTarget</span><span class="o">(...,</span> <span class="nc">Object</span> <span class="n">target</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">methodInjector</span><span class="o">.</span><span class="na">invoke</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="n">response</span><span class="o">,</span> <span class="n">target</span><span class="o">);</span>  <span class="c1">// 此方法专属</span>
<span class="o">}</span>
</code></pre></div></div>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">ResourceFactory</code></strong>（一个 Resource 类一个）：回答「怎么拿实例」</li>
  <li><strong><code class="language-plaintext highlighter-rouge">MethodInjector</code></strong>（一个 HTTP 端点一个）：回答「调哪个方法、参数怎么填」</li>
</ul>

<p><strong>4. 与 Spring 的分工对比</strong></p>

<table>
  <thead>
    <tr>
      <th style="text-align: left"> </th>
      <th style="text-align: left">Spring</th>
      <th style="text-align: left">RESTEasy</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">登记粒度</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">BeanDefinition</code>（一个类一条）</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ResourceFactory</code>（一个类一条）+ <code class="language-plaintext highlighter-rouge">ResourceMethodInvoker</code>（一个端点一条）</td>
    </tr>
    <tr>
      <td style="text-align: left">运行时</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">getBean()</code> 拿到对象，调用方自己决定调哪个方法</td>
      <td style="text-align: left">路由直接命中 Invoker，<strong>已知</strong>调哪个方法</td>
    </tr>
  </tbody>
</table>

<p>Spring 管到 <strong>对象</strong>；RESTEasy 还要管 <strong>HTTP 路由 + 方法级 JAX-RS 语义</strong>，Invoker 粒度自然是端点而非类。</p>

<p><strong>5. 设计收益：注册期预计算，请求期少反射</strong></p>

<p>若在请求期才「找类 → 扫描方法 → 匹配 URI → 解析参数注解」，每次请求都要反射 + 元数据解析。RESTEasy 在 <code class="language-plaintext highlighter-rouge">addResourceFactory</code> 时把 Injector、Filter 链、Produces 等 <strong>固化进 Invoker</strong>；请求路径只做 URL 匹配 + <code class="language-plaintext highlighter-rouge">createResource()</code> + <code class="language-plaintext highlighter-rouge">methodInjector.invoke()</code>。</p>

<h4 id="请求期-call-chaininvoker-委托-factory-取实例">请求期 call chain：Invoker 委托 Factory 取实例</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SynchronousDispatcher.invoke()
    └─ registry.getResourceInvoker(request)     — URL 匹配，返回 ResourceMethodInvoker
         └─ ResourceMethodInvoker.invoke()
              └─ this.resource.createResource(request, response, factory)  — 委托 Factory 策略
                   ├─ POJOResourceFactory: constructorInjector.construct() + propertyInjector.inject()
                   ├─ SingletonResource:    return obj
                   └─ SpringResourceFactory:  beanFactory.getBean(beanName)
              └─ methodInjector.invoke(request, response, target)  — @QueryParam 等
</code></pre></div></div>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// resteasy-core/.../ResourceMethodInvoker.java:348-356</span>
<span class="kd">public</span> <span class="nc">BuiltResponse</span> <span class="nf">invoke</span><span class="o">(</span><span class="nc">HttpRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpResponse</span> <span class="n">response</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">Object</span> <span class="n">resource</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">resource</span><span class="o">.</span><span class="na">createResource</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="n">response</span><span class="o">,</span> <span class="n">resourceMethodProviderFactory</span><span class="o">);</span>
    <span class="c1">// ...</span>
    <span class="k">return</span> <span class="nf">invoke</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="n">response</span><span class="o">,</span> <span class="n">resource</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>RESTEasy <strong>永远</strong>通过 <code class="language-plaintext highlighter-rouge">ResourceMethodInvoker</code> 调用 <code class="language-plaintext highlighter-rouge">createResource()</code> 取实例——无论底层是 per-request <code class="language-plaintext highlighter-rouge">new</code>、singleton 固定对象还是 Spring <code class="language-plaintext highlighter-rouge">getBean()</code>。这是 RESTEasy 侧 Resource 生命周期的 <strong>唯一入口</strong>。</p>

<h3 id="设计思想对比spring-bean-容器-vs-resteasy-factory-模式">设计思想对比：Spring Bean 容器 vs RESTEasy Factory 模式</h3>

<p>两者解决不同问题，抽象层级也不同：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">维度</th>
      <th style="text-align: left">Spring <code class="language-plaintext highlighter-rouge">BeanFactory</code></th>
      <th style="text-align: left">RESTEasy <code class="language-plaintext highlighter-rouge">Registry</code> + <code class="language-plaintext highlighter-rouge">ResourceFactory</code></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>设计目标</strong></td>
      <td style="text-align: left">通用 IoC：管理应用中所有对象</td>
      <td style="text-align: left">HTTP 运行时：URI 匹配 + 取 Resource 实例 + JAX-RS 调用链</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Registry 存什么</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">BeanDefinition</code>（名称 → 定义/metadata）</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ResourceMethodInvoker</code>（URL 路径 → 调用器 → Factory 引用）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>实例从哪来</strong></td>
      <td style="text-align: left">容器 <code class="language-plaintext highlighter-rouge">createBean()</code> / <code class="language-plaintext highlighter-rouge">getBean()</code>，scope 内置于 Definition</td>
      <td style="text-align: left"><strong>委托</strong>给 <code class="language-plaintext highlighter-rouge">ResourceFactory.createResource()</code>，scope 由 Factory <strong>实现类</strong>决定</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>scope 表达</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">singleton</code> / <code class="language-plaintext highlighter-rouge">prototype</code> / <code class="language-plaintext highlighter-rouge">request</code> / 自定义 Scope</td>
      <td style="text-align: left">无统一 scope 框架；选 <code class="language-plaintext highlighter-rouge">POJOResourceFactory</code> ≈ per-request，<code class="language-plaintext highlighter-rouge">SingletonResource</code> ≈ singleton，<code class="language-plaintext highlighter-rouge">SpringResourceFactory</code> ≈ 外包给 Spring scope</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>元数据扫描</strong></td>
      <td style="text-align: left">启动期扫描全部 BeanDefinition</td>
      <td style="text-align: left">注册 Factory 时扫描 <code class="language-plaintext highlighter-rouge">getScannableClass()</code> 上的 <code class="language-plaintext highlighter-rouge">@Path</code> / <code class="language-plaintext highlighter-rouge">@GET</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>依赖注入</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Autowired</code>、<code class="language-plaintext highlighter-rouge">@Value</code>、构造器注入</td>
      <td style="text-align: left">仅 JAX-RS：<code class="language-plaintext highlighter-rouge">@Context</code> 字段、<code class="language-plaintext highlighter-rouge">@QueryParam</code> 方法参数；业务 DI <strong>故意不做</strong></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>扩展点</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">BeanFactoryPostProcessor</code>、<code class="language-plaintext highlighter-rouge">BeanPostProcessor</code>、<code class="language-plaintext highlighter-rouge">*Aware</code></td>
      <td style="text-align: left">自定义 <code class="language-plaintext highlighter-rouge">ResourceFactory</code> 实现；<code class="language-plaintext highlighter-rouge">@Provider</code> 注册到 ProviderFactory</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>销毁</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">destroySingletons()</code>、<code class="language-plaintext highlighter-rouge">@PreDestroy</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">requestFinished()</code> 回调——<strong>可选</strong>，由 Factory 实现决定是否 destroy</td>
    </tr>
  </tbody>
</table>

<p><strong>Spring 的思路</strong>：一切皆 Bean。容器持有 <strong>定义（Definition）</strong>，<strong>所有 scope 共用 <code class="language-plaintext highlighter-rouge">doCreateBean</code> 流水线</strong>；singleton 在 <code class="language-plaintext highlighter-rouge">refresh()</code> 批量实例化并缓存，prototype 在每次 <code class="language-plaintext highlighter-rouge">getBean()</code> 重新走流水线且不入缓存。AOP/事务/事件都挂在这条流水线上。</p>

<p><strong>RESTEasy 的思路</strong>：容器只管 HTTP。Registry 持有 <strong>策略（Factory）</strong>，每次请求问 Factory「给我一个实例」；Factory 可以 <code class="language-plaintext highlighter-rouge">new</code>、可以返回已有对象、可以查 JNDI、可以调 Spring <code class="language-plaintext highlighter-rouge">getBean()</code>——RESTEasy <strong>不关心</strong> Spring scope，只保证调用链一致（<code class="language-plaintext highlighter-rouge">createResource</code> → 注入参数 → 反射调用 → <code class="language-plaintext highlighter-rouge">requestFinished</code>）。</p>

<p>用一张图概括两种「登记」：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Spring                          RESTEasy
──────                          ────────
BeanDefinition                  ResourceFactory（策略对象）
  "helloResource"                 SpringResourceFactory(beanName, …)
  scope=singleton|prototype            │
  class=HelloResource                  ├─ registered() → 预建 Injector
       │                               └─ createResource() → getBean() / new
       ▼                                      │
getBean("helloResource")                      ▼
  singleton: 缓存 or prototype: doCreateBean   ResourceMethodInvoker
  （同一 doCreateBean 流水线）                  GET /hello → invoke()
                                           → createResource() → 实例
                                           → MethodInjector → @GET 方法
</code></pre></div></div>

<p><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core-spi/src/main/java/org/jboss/resteasy/spi/ResourceFactory.java"><code class="language-plaintext highlighter-rouge">ResourceFactory</code></a> 接口把 <strong>注册期</strong> 与 <strong>请求期</strong> 拆成五个回调，正是这一策略模式的契约：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// resteasy-core-spi/.../ResourceFactory.java:9-44</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">ResourceFactory</span> <span class="o">{</span>
    <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">getScannableClass</span><span class="o">();</span>                    <span class="c1">// :15</span>
    <span class="kt">void</span> <span class="nf">registered</span><span class="o">(</span><span class="nc">ResteasyProviderFactory</span> <span class="n">factory</span><span class="o">);</span> <span class="c1">// :22</span>
    <span class="nc">Object</span> <span class="nf">createResource</span><span class="o">(</span><span class="nc">HttpRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpResponse</span> <span class="n">response</span><span class="o">,</span>
            <span class="nc">ResteasyProviderFactory</span> <span class="n">factory</span><span class="o">);</span>         <span class="c1">// :32</span>
    <span class="kt">void</span> <span class="nf">requestFinished</span><span class="o">(</span><span class="nc">HttpRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpResponse</span> <span class="n">response</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">resource</span><span class="o">);</span> <span class="c1">// :41</span>
    <span class="kt">void</span> <span class="nf">unregistered</span><span class="o">();</span>                             <span class="c1">// :43</span>
<span class="o">}</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th style="text-align: left">回调</th>
      <th style="text-align: left">触发时机</th>
      <th style="text-align: left">用途</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">registered()</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">Registry.addResourceFactory</code> 之后</td>
      <td style="text-align: left">创建 <code class="language-plaintext highlighter-rouge">PropertyInjector</code> / <code class="language-plaintext highlighter-rouge">ConstructorInjector</code>，为 JAX-RS 字段注入做准备</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">createResource()</code></td>
      <td style="text-align: left"><strong>每次请求</strong></td>
      <td style="text-align: left">获取 Resource 实例</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">requestFinished()</code></td>
      <td style="text-align: left">请求结束</td>
      <td style="text-align: left">纯 RESTEasy 场景可触发 <code class="language-plaintext highlighter-rouge">@PreDestroy</code> 语义；Spring 集成为空实现</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">unregistered()</code></td>
      <td style="text-align: left">从 Registry 移除</td>
      <td style="text-align: left">Spring 集成为空实现</td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">requestFinished</code> 的接口注释写得很清楚：<em>“usable for things like @PreDestroy if the underlying factory supports it”</em>——<strong>Spring 集成里实例销毁归 Spring，RESTEasy 不在此回调里 <code class="language-plaintext highlighter-rouge">destroy</code> Bean</strong>。<sup id="fnref:6:1"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">5</a></sup></p>

<h3 id="三种内置-resourcefactory三种取实例策略">三种内置 ResourceFactory：三种「取实例策略」</h3>

<p>以下三种实现 <strong>不是</strong> Bean 初始化流程，而是三种不同的 <code class="language-plaintext highlighter-rouge">createResource()</code> 策略，由 Registry 注册 API 或集成层（如 <code class="language-plaintext highlighter-rouge">SpringBeanProcessor</code>）选择绑定：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">策略</th>
      <th style="text-align: left">Factory 实现</th>
      <th style="text-align: left"><code class="language-plaintext highlighter-rouge">createResource()</code> 行为</th>
      <th style="text-align: left">近似 Spring scope</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">per-request</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">POJOResourceFactory</code></td>
      <td style="text-align: left">每请求 <code class="language-plaintext highlighter-rouge">construct()</code> + JAX-RS 字段注入</td>
      <td style="text-align: left">类似 prototype（但无 Spring DI）</td>
    </tr>
    <tr>
      <td style="text-align: left">singleton</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">SingletonResource</code></td>
      <td style="text-align: left">返回构造时传入的固定 <code class="language-plaintext highlighter-rouge">obj</code></td>
      <td style="text-align: left">类似 singleton（但无 Spring 容器）</td>
    </tr>
    <tr>
      <td style="text-align: left">外包</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">SpringResourceFactory</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">beanFactory.getBean(beanName)</code></td>
      <td style="text-align: left">由 Spring scope 决定</td>
    </tr>
  </tbody>
</table>

<p><strong>1. POJOResourceFactory —— per-request，RESTEasy 自己 new</strong>（<a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/plugins/server/resourcefactory/POJOResourceFactory.java"><code class="language-plaintext highlighter-rouge">POJOResourceFactory.java</code></a>）</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// :51-60 registered</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">registered</span><span class="o">(</span><span class="nc">ResteasyProviderFactory</span> <span class="n">factory</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">ResourceConstructor</span> <span class="n">constructor</span> <span class="o">=</span> <span class="n">resourceClass</span><span class="o">.</span><span class="na">getConstructor</span><span class="o">();</span>
    <span class="c1">// ...</span>
    <span class="k">this</span><span class="o">.</span><span class="na">constructorInjector</span> <span class="o">=</span> <span class="n">factory</span><span class="o">.</span><span class="na">getInjectorFactory</span><span class="o">().</span><span class="na">createConstructor</span><span class="o">(</span><span class="n">constructor</span><span class="o">,</span> <span class="n">factory</span><span class="o">);</span>
    <span class="k">this</span><span class="o">.</span><span class="na">propertyInjector</span> <span class="o">=</span> <span class="n">factory</span><span class="o">.</span><span class="na">getInjectorFactory</span><span class="o">().</span><span class="na">createPropertyInjector</span><span class="o">(</span><span class="n">resourceClass</span><span class="o">,</span> <span class="n">factory</span><span class="o">);</span>
<span class="o">}</span>

<span class="c1">// :63-77 createResource</span>
<span class="kd">public</span> <span class="nc">Object</span> <span class="nf">createResource</span><span class="o">(</span><span class="nc">HttpRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpResponse</span> <span class="n">response</span><span class="o">,</span> <span class="nc">ResteasyProviderFactory</span> <span class="n">factory</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">Object</span> <span class="n">obj</span> <span class="o">=</span> <span class="n">constructorInjector</span><span class="o">.</span><span class="na">construct</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="n">response</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
    <span class="nc">CompletionStage</span><span class="o">&lt;</span><span class="nc">Void</span><span class="o">&gt;</span> <span class="n">propertyStage</span> <span class="o">=</span> <span class="n">propertyInjector</span><span class="o">.</span><span class="na">inject</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="n">response</span><span class="o">,</span> <span class="n">obj</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
    <span class="k">return</span> <span class="n">propertyStage</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">obj</span> <span class="o">:</span> <span class="n">propertyStage</span><span class="o">.</span><span class="na">thenApply</span><span class="o">(</span><span class="n">v</span> <span class="o">-&gt;</span> <span class="n">obj</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>2. SingletonResource —— 固定对象，registered 时注入一次</strong>（<a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/plugins/server/resourcefactory/SingletonResource.java"><code class="language-plaintext highlighter-rouge">SingletonResource.java</code></a>）</p>

<p><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceMethodRegistry.java#L95-L98"><code class="language-plaintext highlighter-rouge">addSingletonResource(Object singleton)</code></a> 要求调用方 <strong>先把实例建好</strong> 再交给 Registry——RESTEasy 不会在此路径下 <code class="language-plaintext highlighter-rouge">new</code> Resource：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// resteasy-core/.../ResourceMethodRegistry.java:95-98</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">addSingletonResource</span><span class="o">(</span><span class="nc">Object</span> <span class="n">singleton</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">ResourceClass</span> <span class="n">resourceClass</span> <span class="o">=</span> <span class="n">resourceBuilder</span><span class="o">.</span><span class="na">getRootResourceFromAnnotations</span><span class="o">(</span><span class="n">singleton</span><span class="o">.</span><span class="na">getClass</span><span class="o">());</span>
    <span class="n">addResourceFactory</span><span class="o">(</span><span class="k">new</span> <span class="nc">SingletonResource</span><span class="o">(</span><span class="n">singleton</span><span class="o">,</span> <span class="n">resourceClass</span><span class="o">));</span>
<span class="o">}</span>
</code></pre></div></div>

<p><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/plugins/server/resourcefactory/SingletonResource.java"><code class="language-plaintext highlighter-rouge">SingletonResource</code></a> 类注释写得很直白：<em>“VERY simple implementation that just returns the instance the SingletonResource was created with”</em>。Factory 用 <code class="language-plaintext highlighter-rouge">final</code> 字段 <strong>持有</strong> 传入的实例引用：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// resteasy-core/.../SingletonResource.java:19-44</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SingletonResource</span> <span class="kd">implements</span> <span class="nc">ResourceFactory</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">Object</span> <span class="n">obj</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ResourceClass</span> <span class="n">resourceClass</span><span class="o">;</span>

    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">registered</span><span class="o">(</span><span class="nc">ResteasyProviderFactory</span> <span class="n">factory</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">factory</span><span class="o">.</span><span class="na">getInjectorFactory</span><span class="o">().</span><span class="na">createPropertyInjector</span><span class="o">(</span><span class="n">resourceClass</span><span class="o">,</span> <span class="n">factory</span><span class="o">).</span><span class="na">inject</span><span class="o">(</span><span class="n">obj</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nc">Object</span> <span class="nf">createResource</span><span class="o">(</span><span class="nc">HttpRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpResponse</span> <span class="n">response</span><span class="o">,</span> <span class="nc">ResteasyProviderFactory</span> <span class="n">factory</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">obj</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">unregistered</span><span class="o">()</span> <span class="o">{</span> <span class="o">}</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">requestFinished</span><span class="o">(</span><span class="nc">HttpRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpResponse</span> <span class="n">response</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">resource</span><span class="o">)</span> <span class="o">{</span> <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">registered()</code>：启动期对 <strong>已有</strong> <code class="language-plaintext highlighter-rouge">obj</code> 做一次 JAX-RS 字段注入（<code class="language-plaintext highlighter-rouge">@Context</code> 等）。</li>
  <li><code class="language-plaintext highlighter-rouge">createResource()</code>：每次请求直接 <code class="language-plaintext highlighter-rouge">return obj</code>，不再创建。</li>
  <li><code class="language-plaintext highlighter-rouge">unregistered()</code> / <code class="language-plaintext highlighter-rouge">requestFinished()</code>：<strong>空实现</strong>——RESTEasy 不负责销毁；实例生命周期由 <strong>传入实例的调用方</strong> 管理。</li>
</ul>

<h4 id="singleton-的两种路径谁维护-instance">Singleton 的两种路径：谁维护 instance？</h4>

<p>RESTEasy <strong>没有</strong> Spring 那样内建的 scope 框架；「singleton」语义通过 <strong>选择不同的 Factory 实现</strong> 表达。是否由 RESTEasy 维护 instance，取决于走哪条路径：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">路径</th>
      <th style="text-align: left">RESTEasy 是否持有 instance</th>
      <th style="text-align: left">实例实际存放位置</th>
      <th style="text-align: left">谁创建</th>
      <th style="text-align: left">谁销毁</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">SingletonResource</code></td>
      <td style="text-align: left">✅ Factory 内 <code class="language-plaintext highlighter-rouge">final Object obj</code></td>
      <td style="text-align: left"><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/plugins/server/resourcefactory/SingletonResource.java"><code class="language-plaintext highlighter-rouge">SingletonResource</code></a> 对象</td>
      <td style="text-align: left">调用方 <code class="language-plaintext highlighter-rouge">addSingletonResource(singleton)</code> 传入</td>
      <td style="text-align: left">调用方；RESTEasy 回调为空</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">SpringResourceFactory</code> + singleton scope</td>
      <td style="text-align: left">❌ 只持有 Factory 策略</td>
      <td style="text-align: left">Spring 一级单例缓存</td>
      <td style="text-align: left">Spring <code class="language-plaintext highlighter-rouge">refresh()</code> → <code class="language-plaintext highlighter-rouge">doCreateBean</code></td>
      <td style="text-align: left">Spring <code class="language-plaintext highlighter-rouge">destroySingletons()</code> / <code class="language-plaintext highlighter-rouge">@PreDestroy</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">POJOResourceFactory</code></td>
      <td style="text-align: left">❌ 不长期持有</td>
      <td style="text-align: left">无（每请求新建）</td>
      <td style="text-align: left">RESTEasy <code class="language-plaintext highlighter-rouge">ConstructorInjector</code></td>
      <td style="text-align: left">无自动 destroy</td>
    </tr>
  </tbody>
</table>

<p><strong>纯 RESTEasy singleton</strong>：Registry 登记的是 <code class="language-plaintext highlighter-rouge">SingletonResource</code> 策略对象，策略对象里 <strong>握着</strong> 实例引用；RESTEasy 维护的是 <strong>引用 + 复用</strong>，不是完整的 Bean 生命周期（不 create、不 destroy）。</p>

<p><strong>Spring 集成 singleton</strong>：Registry 登记的是 <code class="language-plaintext highlighter-rouge">SpringResourceFactory</code>（只存 <code class="language-plaintext highlighter-rouge">beanName</code> + <code class="language-plaintext highlighter-rouge">beanFactory</code>），<strong>不存 instance</strong>：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// resteasy-spring/.../SpringResourceFactory.java:35-37</span>
<span class="kd">public</span> <span class="nc">Object</span> <span class="nf">createResource</span><span class="o">(</span><span class="nc">HttpRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpResponse</span> <span class="n">response</span><span class="o">,</span> <span class="nc">ResteasyProviderFactory</span> <span class="n">factory</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">beanFactory</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span>   <span class="c1">// singleton 时命中 Spring 缓存</span>
<span class="o">}</span>
</code></pre></div></div>

<p>每次 <code class="language-plaintext highlighter-rouge">createResource()</code> 向 Spring 要 Bean；RESTEasy 侧每次返回同一对象，是因为 Spring 缓存了 singleton，不是因为 RESTEasy Factory 里有一个 <code class="language-plaintext highlighter-rouge">obj</code> 字段。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>纯 RESTEasy singleton                    Spring bridge + singleton scope
─────────────────────                    ────────────────────────────────
调用方 new HelloResource()               Spring refresh() → doCreateBean
    │                                        │
    ▼                                        ▼
addSingletonResource(instance)           SpringBeanProcessor 建 SpringResourceFactory
    │                                        │
    ▼                                        ▼
SingletonResource.obj = instance         Registry 登记 Factory（无 instance 字段）
    │                                        │
    ▼                                        ▼
createResource() → return obj            createResource() → getBean() → Spring 缓存
</code></pre></div></div>

<p>因此，「singleton Resource 是否由 RESTEasy 维护 instance」的准确答案是：<strong>纯 <code class="language-plaintext highlighter-rouge">SingletonResource</code> 路径下是——RESTEasy Factory 持有引用；Spring 集成路径下否——实例归 Spring 容器，RESTEasy 只维护取实例策略。</strong></p>

<p><strong>3. SpringResourceFactory —— 实例来源外包给 Spring</strong>（<a href="https://github.com/resteasy/resteasy/blob/main/resteasy-spring/src/main/java/org/jboss/resteasy/plugins/spring/SpringResourceFactory.java"><code class="language-plaintext highlighter-rouge">SpringResourceFactory.java</code></a>）</p>

<p>RESTEasy 仍只持有一个 Factory 策略对象；<code class="language-plaintext highlighter-rouge">createResource()</code> 把取实例委托给 Spring <code class="language-plaintext highlighter-rouge">getBean()</code>。singleton / prototype scope 差异见 <strong>第四节</strong>。</p>

<h3 id="resteasy-与-spring-生命周期对照spring-集成按-scope">RESTEasy 与 Spring 生命周期对照（Spring 集成，按 scope）</h3>

<pre><code class="language-mermaid">sequenceDiagram
    participant Spring as Spring 容器
    participant RESTEasy as RESTEasy Registry
    participant Resource as @Path Resource

    Note over Spring,Resource: 启动阶段（singleton Resource）
    Spring-&gt;&gt;Spring: preInstantiateSingletons → doCreateBean
    Spring-&gt;&gt;Spring: ResteasyBeanPostProcessor（JAX-RS 字段注入）
    Spring-&gt;&gt;Resource: singleton 实例就绪
    Note over Spring,Resource: prototype Resource：此阶段通常无实例
    Note over Spring,Resource: ContextRefreshedEvent（两种 scope 均注册 Factory）
    Spring-&gt;&gt;RESTEasy: addResourceFactory(SpringResourceFactory)
    RESTEasy-&gt;&gt;RESTEasy: SpringResourceFactory.registered()

    Note over Spring,Resource: 每次请求
    RESTEasy-&gt;&gt;Spring: createResource() → getBean(beanName)
    alt singleton
        Spring--&gt;&gt;RESTEasy: 缓存实例（跳过 doCreateBean）
    else prototype
        Spring-&gt;&gt;Spring: doCreateBean 全流程
        Spring--&gt;&gt;RESTEasy: 新实例
    end
    RESTEasy-&gt;&gt;Resource: MethodInjector 注入 @QueryParam
    RESTEasy-&gt;&gt;Resource: 调用 @GET 方法
    RESTEasy-&gt;&gt;RESTEasy: requestFinished()（Spring 路径无操作）
</code></pre>

<p>第三节对比了纯 RESTEasy 的三种 <code class="language-plaintext highlighter-rouge">ResourceFactory</code>；Spring 集成路径下，桥接逻辑集中在 <strong>第四节</strong>。</p>

<h2 id="四resteasy-spring-如何桥接">四、resteasy-spring 如何桥接</h2>

<p>桥接由两个类分工：<code class="language-plaintext highlighter-rouge">SpringBeanProcessor</code> 在 Spring 启动三阶段（BFPP → BPP → <code class="language-plaintext highlighter-rouge">ContextRefreshedEvent</code>）扫描 <code class="language-plaintext highlighter-rouge">@Path</code> / <code class="language-plaintext highlighter-rouge">@Provider</code>、建 <code class="language-plaintext highlighter-rouge">SpringResourceFactory</code> 映射并注册 Registry；<code class="language-plaintext highlighter-rouge">SpringResourceFactory</code> 在运行期把 RESTEasy 的 <code class="language-plaintext highlighter-rouge">createResource()</code> 转译为 <code class="language-plaintext highlighter-rouge">getBean()</code>。</p>

<h3 id="springbeanprocessor启动期编排">SpringBeanProcessor：启动期编排</h3>

<p><code class="language-plaintext highlighter-rouge">SpringBeanProcessor</code> 实现 <code class="language-plaintext highlighter-rouge">BeanFactoryPostProcessor</code> 与 <code class="language-plaintext highlighter-rouge">SmartApplicationListener</code>，在 Spring 启动的三个关键时刻插入逻辑。<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">8</a></sup> <strong>它不会自动出现在 classpath 上</strong>——WAR 需 <code class="language-plaintext highlighter-rouge">SpringContextLoaderSupport.addBeanFactoryPostProcessor()</code>，Spring MVC 集成 import <code class="language-plaintext highlighter-rouge">springmvc-resteasy.xml</code>，Spring Boot 由 <code class="language-plaintext highlighter-rouge">ResteasyAutoConfiguration</code> 的 <code class="language-plaintext highlighter-rouge">@Bean</code> 注册。<sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">9</a></sup></p>

<h4 id="阶段一bean-定义期postprocessbeanfactory-235-261">阶段一：Bean 定义期（postProcessBeanFactory :235-261）</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// resteasy-spring/.../SpringBeanProcessor.java:235-261</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">postProcessBeanFactory</span><span class="o">(</span><span class="nc">ConfigurableListableBeanFactory</span> <span class="n">beanFactory</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">beanFactory</span><span class="o">.</span><span class="na">registerResolvableDependency</span><span class="o">(</span><span class="nc">Registry</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">getRegistry</span><span class="o">());</span>
    <span class="n">beanFactory</span><span class="o">.</span><span class="na">registerResolvableDependency</span><span class="o">(</span><span class="nc">ResteasyProviderFactory</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">getProviderFactory</span><span class="o">());</span>
    <span class="n">beanFactory</span><span class="o">.</span><span class="na">addBeanPostProcessor</span><span class="o">(</span><span class="k">new</span> <span class="nc">ResteasyBeanPostProcessor</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">));</span>

    <span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">name</span> <span class="o">:</span> <span class="n">beanFactory</span><span class="o">.</span><span class="na">getBeanDefinitionNames</span><span class="o">())</span> <span class="o">{</span>
        <span class="n">processBean</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">,</span> <span class="n">dependsOnBeans</span><span class="o">,</span> <span class="n">name</span><span class="o">,</span> <span class="n">beanDef</span><span class="o">);</span>
        <span class="c1">// @Provider → providerNames (:276-286)</span>
        <span class="c1">// 根 @Path → resourceFactories.put(name, new SpringResourceFactory(...)) (:288-289)</span>
    <span class="o">}</span>
    <span class="c1">// Resource depends-on 非 MessageBody Reader/Writer 的 @Provider (:256-260)</span>
<span class="o">}</span>
</code></pre></div></div>

<p>此阶段 <strong>只建 <code class="language-plaintext highlighter-rouge">SpringResourceFactory</code> 映射，不创建 Bean 实例，也不注册进 Registry</strong>。</p>

<h4 id="阶段二每个-bean-初始化后resteasybeanpostprocessorpostprocessafterinitialization-114-146">阶段二：每个 Bean 初始化后（ResteasyBeanPostProcessor.postProcessAfterInitialization :114-146）</h4>

<p>类注释写明了 <code class="language-plaintext highlighter-rouge">@Path</code> 与 <code class="language-plaintext highlighter-rouge">@Provider</code> 的分工（<a href="https://github.com/resteasy/resteasy/blob/main/resteasy-spring/src/main/java/org/jboss/resteasy/plugins/spring/SpringBeanProcessor.java#L95-L108"><code class="language-plaintext highlighter-rouge">SpringBeanProcessor.java:95-108</code></a>）。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// resteasy-spring/.../SpringBeanProcessor.java:114-146</span>
<span class="kd">public</span> <span class="nc">Object</span> <span class="nf">postProcessAfterInitialization</span><span class="o">(</span><span class="nc">Object</span> <span class="n">bean</span><span class="o">,</span> <span class="nc">String</span> <span class="n">beanName</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">providerNames</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">beanName</span><span class="o">))</span> <span class="o">{</span>
        <span class="nc">PropertyInjector</span> <span class="n">injector</span> <span class="o">=</span> <span class="n">getInjector</span><span class="o">(</span><span class="nc">AopUtils</span><span class="o">.</span><span class="na">getTargetClass</span><span class="o">(</span><span class="n">bean</span><span class="o">));</span>
        <span class="n">injector</span><span class="o">.</span><span class="na">inject</span><span class="o">(</span><span class="n">bean</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
        <span class="n">providerFactory</span><span class="o">.</span><span class="na">registerProviderInstance</span><span class="o">(</span><span class="n">bean</span><span class="o">);</span>
    <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
        <span class="nc">SpringResourceFactory</span> <span class="n">resourceFactory</span> <span class="o">=</span> <span class="n">resourceFactories</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">resourceFactory</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">inject</span><span class="o">(</span><span class="n">beanName</span><span class="o">,</span> <span class="n">bean</span><span class="o">,</span> <span class="n">getInjector</span><span class="o">(</span><span class="n">resourceFactory</span><span class="o">.</span><span class="na">getScannableClass</span><span class="o">()));</span>
        <span class="o">}</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="n">bean</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Bean 类型</th>
      <th style="text-align: left">本阶段</th>
      <th style="text-align: left">何时发生</th>
      <th style="text-align: left">为何如此</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Provider</code>（singleton）</td>
      <td style="text-align: left">注入 + <code class="language-plaintext highlighter-rouge">registerProviderInstance</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">refresh()</code> 期间</td>
      <td style="text-align: left">Filter / Interceptor 须在 Resource 之前就位</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Path</code> Resource（<strong>singleton</strong>）</td>
      <td style="text-align: left">仅 <code class="language-plaintext highlighter-rouge">PropertyInjector.inject</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">refresh()</code> 期间</td>
      <td style="text-align: left">JAX-RS 字段启动期注入；Registry 注册推迟到 <code class="language-plaintext highlighter-rouge">ContextRefreshedEvent</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Path</code> Resource（<strong>prototype</strong>）</td>
      <td style="text-align: left">—</td>
      <td style="text-align: left"><strong>通常跳过</strong>（未实例化）</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">preInstantiateSingletons</code> 不包含 prototype；首请求 <code class="language-plaintext highlighter-rouge">getBean</code> 时才走 BPP</td>
    </tr>
  </tbody>
</table>

<h4 id="阶段三容器刷新完成onapplicationevent-453-467">阶段三：容器刷新完成（onApplicationEvent :453-467）</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// resteasy-spring/.../SpringBeanProcessor.java:453-467</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onApplicationEvent</span><span class="o">(</span><span class="nc">ApplicationEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">for</span> <span class="o">(</span><span class="nc">SpringResourceFactory</span> <span class="n">resourceFactory</span> <span class="o">:</span> <span class="n">resourceFactories</span><span class="o">.</span><span class="na">values</span><span class="o">())</span> <span class="o">{</span>
        <span class="n">getRegistry</span><span class="o">().</span><span class="na">removeRegistrations</span><span class="o">(</span><span class="n">resourceFactory</span><span class="o">.</span><span class="na">getScannableClass</span><span class="o">());</span>
    <span class="o">}</span>
    <span class="k">for</span> <span class="o">(</span><span class="nc">SpringResourceFactory</span> <span class="n">resourceFactory</span> <span class="o">:</span> <span class="n">resourceFactories</span><span class="o">.</span><span class="na">values</span><span class="o">())</span> <span class="o">{</span>
        <span class="n">getRegistry</span><span class="o">().</span><span class="na">addResourceFactory</span><span class="o">(</span><span class="n">resourceFactory</span><span class="o">,</span> <span class="n">resourceFactory</span><span class="o">.</span><span class="na">getContext</span><span class="o">());</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>注释（<code class="language-plaintext highlighter-rouge">:446-448</code>）：<em>“Register all of the resources into RESTEasy only when Spring finishes its life-cycle and the spring singleton bean creation is completed.”</em><sup id="fnref:3:1"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>——此处强调 <strong>singleton 批量创建完毕</strong>，以保证 singleton <code class="language-plaintext highlighter-rouge">@Path</code> Resource 的 <code class="language-plaintext highlighter-rouge">@Autowired</code> 依赖链完整；<strong>prototype Resource 的 Factory 映射同样在此时注册进 Registry</strong>，只是实例尚未创建。</p>

<p>Registry 侧会回调 <code class="language-plaintext highlighter-rouge">ResourceFactory.registered()</code>：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// resteasy-core/.../ResourceMethodRegistry.java:220-222</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">addResourceFactory</span><span class="o">(</span><span class="nc">ResourceFactory</span> <span class="n">ref</span><span class="o">,</span> <span class="nc">ResourceBuilder</span> <span class="n">resourceBuilder</span><span class="o">,</span> <span class="nc">String</span> <span class="n">base</span><span class="o">,</span> <span class="nc">Class</span><span class="o">&lt;?&gt;[]</span> <span class="n">classes</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">ref</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span>
        <span class="n">ref</span><span class="o">.</span><span class="na">registered</span><span class="o">(</span><span class="n">providerFactory</span><span class="o">);</span>
    <span class="c1">// ... register(ref, base, resourceClass) 扫描 @Path/@GET 建路由</span>
<span class="o">}</span>
</code></pre></div></div>

<p>若 Resource 依赖的 <code class="language-plaintext highlighter-rouge">CustomerService</code> 尚未创建，过早注册会导致依赖不完整。</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant Spring as Spring 启动
    participant SBP as SpringBeanProcessor
    participant Registry as RESTEasy Registry

    Note over Spring,Registry: T1 — postProcessBeanFactory
    Spring-&gt;&gt;SBP: 扫描 @Path，建 SpringResourceFactory
    Note over Spring,Registry: T2 — 逐 Bean 初始化（主要 singleton）
    loop 每个已实例化的 Bean
        Spring-&gt;&gt;SBP: postProcessAfterInitialization
        alt @Provider singleton
            SBP-&gt;&gt;Registry: registerProviderInstance
        else @Path singleton
            SBP-&gt;&gt;SBP: JAX-RS 字段注入
        else @Path prototype
            Note over SBP: 通常不在此 loop（尚未 getBean）
        end
    end
    Note over Spring,Registry: T3 — ContextRefreshedEvent
    Spring-&gt;&gt;SBP: onApplicationEvent
    SBP-&gt;&gt;Registry: addResourceFactory + registered()
</code></pre>

<h3 id="springresourcefactory从-startup-映射到-registry再到运行期调用">SpringResourceFactory：从 Startup 映射到 Registry，再到运行期调用</h3>

<p>上一小节按 Spring 启动三阶段（T1/T2/T3）编排；本节把 <strong>SRF 对象本身</strong> 在 RESTEasy runtime 中的位置串成一条线：<strong>先进入 SBP 的本地 Map，再进入 Registry，最后被 Invoker 持有并在每次请求调用</strong>。</p>

<h4 id="三阶段srf-何时存在何时生效">三阶段：SRF 何时「存在」、何时「生效」</h4>

<table>
  <thead>
    <tr>
      <th style="text-align: left">阶段</th>
      <th style="text-align: left">触发点</th>
      <th style="text-align: left">SRF 状态</th>
      <th style="text-align: left">是否已在 Registry</th>
      <th style="text-align: left">是否可被 HTTP 命中</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>T1</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">postProcessBeanFactory</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">resourceFactories.put(beanName, new SpringResourceFactory(...))</code></td>
      <td style="text-align: left">❌</td>
      <td style="text-align: left">❌</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>T2</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ResteasyBeanPostProcessor.postProcessAfterInitialization</code></td>
      <td style="text-align: left">同一 SRF 对象仍在 Map 中；singleton <code class="language-plaintext highlighter-rouge">@Path</code> 做字段 <code class="language-plaintext highlighter-rouge">@Context</code> 注入</td>
      <td style="text-align: left">❌</td>
      <td style="text-align: left">❌</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>T3</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ContextRefreshedEvent</code> → <code class="language-plaintext highlighter-rouge">onApplicationEvent</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">getRegistry().addResourceFactory(srf, …)</code></td>
      <td style="text-align: left">✅</td>
      <td style="text-align: left">✅（Servlet 已就绪后）</td>
    </tr>
  </tbody>
</table>

<p>T1 扫描逻辑在 <a href="https://github.com/resteasy/resteasy/blob/main/resteasy-spring/src/main/java/org/jboss/resteasy/plugins/spring/SpringBeanProcessor.java"><code class="language-plaintext highlighter-rouge">processBean()</code></a>：<code class="language-plaintext highlighter-rouge">GetRestful.isRootResource(scannableClass)</code> 为 true 的根 <code class="language-plaintext highlighter-rouge">@Path</code> Bean，按 <strong>bean 名称</strong> 建 SRF，<strong>此时既不创建 Resource 实例，也不调用 <code class="language-plaintext highlighter-rouge">Registry.addResourceFactory</code></strong>——Registry 里还没有任何与该 Resource 相关的路由。</p>

<p>T2 对 <code class="language-plaintext highlighter-rouge">@Path</code> singleton：BPP 用 <code class="language-plaintext highlighter-rouge">resourceFactories.get(beanName)</code> 取到 <strong>T1 已建好的同一个 SRF</strong>，只对 <strong>已实例化的 Bean</strong> 做 <code class="language-plaintext highlighter-rouge">PropertyInjector.inject</code>；prototype Resource 通常尚未 <code class="language-plaintext highlighter-rouge">getBean</code>，此 loop <strong>跳过</strong>，但 T1 里已为它建好 SRF 映射。</p>

<p>T3 才是 SRF <strong>正式进入 RESTEasy runtime</strong> 的时刻：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// resteasy-spring/.../SpringBeanProcessor.java:453-467</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">SpringResourceFactory</span> <span class="n">resourceFactory</span> <span class="o">:</span> <span class="n">resourceFactories</span><span class="o">.</span><span class="na">values</span><span class="o">())</span> <span class="o">{</span>
    <span class="n">getRegistry</span><span class="o">().</span><span class="na">removeRegistrations</span><span class="o">(</span><span class="n">resourceFactory</span><span class="o">.</span><span class="na">getScannableClass</span><span class="o">());</span>
<span class="o">}</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">SpringResourceFactory</span> <span class="n">resourceFactory</span> <span class="o">:</span> <span class="n">resourceFactories</span><span class="o">.</span><span class="na">values</span><span class="o">())</span> <span class="o">{</span>
    <span class="n">getRegistry</span><span class="o">().</span><span class="na">addResourceFactory</span><span class="o">(</span><span class="n">resourceFactory</span><span class="o">,</span> <span class="n">resourceFactory</span><span class="o">.</span><span class="na">getContext</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">addResourceFactory</code> 内部链式发生三件事（见第三节 <a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceMethodRegistry.java#L200-L220"><code class="language-plaintext highlighter-rouge">ResourceMethodRegistry.addResourceFactory</code></a>）：</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">srf.registered(providerFactory)</code></strong> — 为 SRF 关联的类创建 <code class="language-plaintext highlighter-rouge">PropertyInjector</code> 等（Spring 路径下主要注入已在 T2 BPP 完成）。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">register(ref, base, resourceClass)</code></strong> — 扫描 <code class="language-plaintext highlighter-rouge">@Path</code> / <code class="language-plaintext highlighter-rouge">@GET</code> / <code class="language-plaintext highlighter-rouge">@POST</code>，在 URL 树（<code class="language-plaintext highlighter-rouge">RootClassNode</code>）上挂叶子。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">processMethod(...)</code></strong> — <strong>每个 HTTP 端点</strong> 生成一个 <a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceMethodInvoker.java"><code class="language-plaintext highlighter-rouge">ResourceMethodInvoker</code></a>，其 <code class="language-plaintext highlighter-rouge">protected ResourceFactory resource</code> 字段 <strong>指向同一个 SRF 引用</strong>。</li>
</ol>

<p>因此：<strong>Registry 存的不是 HelloResource 实例，而是 SRF 策略 + 若干 Invoker</strong>；singleton 与 prototype 在 T3 <strong>注册方式相同</strong>，差异只在运行期 <code class="language-plaintext highlighter-rouge">getBean()</code> 是否命中缓存。</p>

<h4 id="运行期invoker--srf--spring">运行期：Invoker → SRF → Spring</h4>

<p>HTTP 请求进入后，SRF 的唯一调用入口是 Invoker：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SynchronousDispatcher.invoke()
  → registry.getResourceInvoker(request)     // URI + HTTP 动词 → ResourceMethodInvoker
  → ResourceMethodInvoker.invoke()
       → resource.createResource(...)      // resource 即 SRF
            → beanFactory.getBean(beanName)
       → methodInjector.invoke(...)        // @QueryParam 等
  → resource.requestFinished(...)           // Spring 路径：空实现
</code></pre></div></div>

<p><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-spring/src/main/java/org/jboss/resteasy/plugins/spring/SpringResourceFactory.java#L35-L37"><code class="language-plaintext highlighter-rouge">SpringResourceFactory.createResource()</code></a> 不含 scope 分支；singleton 返 Spring 一级缓存，prototype 每次重走 <code class="language-plaintext highlighter-rouge">doCreateBean</code>。RESTEasy 侧 <strong>始终</strong> 认为自己在调 <code class="language-plaintext highlighter-rouge">ResourceFactory.createResource()</code>，实例语义完全外包给 Spring。</p>

<h4 id="war-与-bootregistry-从哪来srf-注册时机不变">WAR 与 Boot：Registry 从哪来，SRF 注册时机不变</h4>

<p>SRF 的 T1→T3 流程在两种部署下 <strong>相同</strong>；差别在 <strong>Registry / ProviderFactory 何时挂到 <code class="language-plaintext highlighter-rouge">ResteasyDeployment</code> 上</strong>：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">路径</th>
      <th style="text-align: left">Registry 初始来源</th>
      <th style="text-align: left">SRF 注册进 Registry</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>WAR / ServletContainerInitializer</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">new SpringBeanProcessor(deployment)</code> 构造时从已有 deployment 取 Registry</td>
      <td style="text-align: left">T3 <code class="language-plaintext highlighter-rouge">ContextRefreshedEvent</code>（同左）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Spring Boot</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Bean SpringBeanProcessor()</code> 无参构造，内部 <strong>新建空 Registry</strong></td>
      <td style="text-align: left">T3 同上；Servlet 启动后 <a href="https://github.com/resteasy/resteasy-spring-boot/blob/main/resteasy-spring-boot-starter/src/main/java/org/jboss/resteasy/spring/boot/DeploymentCustomizer.java"><code class="language-plaintext highlighter-rouge">DeploymentCustomizer</code></a> 把 SBP 的 Registry/ProviderFactory <strong>合并</strong>进 <code class="language-plaintext highlighter-rouge">ResteasyDeployment</code>，再 <code class="language-plaintext highlighter-rouge">deployment.start()</code></td>
    </tr>
  </tbody>
</table>

<p>Boot 下可能出现：<code class="language-plaintext highlighter-rouge">ContextRefreshedEvent</code> 时 SRF 已进 <strong>SBP 持有的 Registry</strong>，但 deployment 合并稍晚——合并的是 <strong>同一个 Registry 对象引用</strong>，故路由不会重复注册。运行期 Dispatcher 使用的仍是合并后的 deployment，Invoker → SRF → <code class="language-plaintext highlighter-rouge">getBean()</code> 链路与 WAR 一致。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Startup（两种部署 SRF 路径相同）
────────────────────────────────
T1  SBP.resourceFactories[beanName] = new SpringResourceFactory(...)
T2  BPP：singleton 字段 @Context；prototype 通常跳过
T3  Registry.addResourceFactory(SRF) → registered() + Invoker(srf)

Runtime（两种部署相同）
──────────────────────
HTTP → Invoker.invoke() → SRF.createResource() → getBean(beanName)
</code></pre></div></div>

<h3 id="springresourcefactory运行期取实例">SpringResourceFactory：运行期取实例</h3>

<p><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-spring/src/main/java/org/jboss/resteasy/plugins/spring/SpringResourceFactory.java"><code class="language-plaintext highlighter-rouge">SpringResourceFactory</code></a> 实现 <code class="language-plaintext highlighter-rouge">ResourceFactory</code>，使 RESTEasy 在 Registry 中登记「向 Spring 要实例」的策略，而不是自己 <code class="language-plaintext highlighter-rouge">new</code>。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// resteasy-spring/.../SpringResourceFactory.java:25-29</span>
<span class="kd">public</span> <span class="nf">SpringResourceFactory</span><span class="o">(</span><span class="kd">final</span> <span class="nc">String</span> <span class="n">beanName</span><span class="o">,</span> <span class="kd">final</span> <span class="nc">BeanFactory</span> <span class="n">beanFactory</span><span class="o">,</span> <span class="kd">final</span> <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">scannable</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">this</span><span class="o">.</span><span class="na">beanName</span> <span class="o">=</span> <span class="n">beanName</span><span class="o">;</span>
    <span class="k">this</span><span class="o">.</span><span class="na">beanFactory</span> <span class="o">=</span> <span class="n">beanFactory</span><span class="o">;</span>
    <span class="k">this</span><span class="o">.</span><span class="na">scannableClass</span> <span class="o">=</span> <span class="n">scannable</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">beanName</code> + <code class="language-plaintext highlighter-rouge">beanFactory</code>：运行时 <code class="language-plaintext highlighter-rouge">getBean()</code></li>
  <li><code class="language-plaintext highlighter-rouge">scannableClass</code>：RESTEasy 扫描 <code class="language-plaintext highlighter-rouge">@Path</code> / <code class="language-plaintext highlighter-rouge">@GET</code> 建路由用（可能与 AOP 代理类不同，故单独保存）</li>
</ul>

<p><strong>RESTEasy 认为自己在「每次请求创建 Resource」，实际上是在「每次请求从 Spring 取 Bean」</strong>——实例的创建、DI、AOP、销毁都由 Spring 决定。</p>

<p>分工要点：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">createResource()</code> <strong>只</strong>向 Spring 要 Bean，<strong>不做</strong> JAX-RS 字段注入（那是 <code class="language-plaintext highlighter-rouge">ResteasyBeanPostProcessor</code> 在 Spring BPP 阶段或 prototype 的 <code class="language-plaintext highlighter-rouge">getBean</code> 链路上完成的）。</li>
  <li><code class="language-plaintext highlighter-rouge">registered()</code> 仍会创建 <code class="language-plaintext highlighter-rouge">PropertyInjector</code>，但 Spring 路径下主要注入逻辑在 Spring 启动的 BPP 里。</li>
  <li><code class="language-plaintext highlighter-rouge">requestFinished()</code> <strong>空实现</strong>——prototype Resource 的销毁靠 Spring scope，不靠 RESTEasy 回调。</li>
</ul>

<h3 id="singleton-vs-prototypebridge-模式下-scope-如何改变行为">Singleton vs Prototype：bridge 模式下 scope 如何改变行为</h3>

<p><strong>桥接机制本身不变</strong>——无论 singleton 还是 prototype，Registry 里登记的仍是同一个 <code class="language-plaintext highlighter-rouge">SpringResourceFactory</code>，每次请求仍走 <code class="language-plaintext highlighter-rouge">createResource()</code> → <code class="language-plaintext highlighter-rouge">getBean(beanName)</code>。区别在 Spring scope 改变了 <code class="language-plaintext highlighter-rouge">getBean()</code> 的返回值与创建路径，进而影响 JAX-RS 字段注入时机、AOP 代理和销毁语义。</p>

<h4 id="createresource-与-getbean入口相同分支不同">createResource 与 getBean：入口相同，分支不同</h4>

<p><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-spring/src/main/java/org/jboss/resteasy/plugins/spring/SpringResourceFactory.java#L35-L37"><code class="language-plaintext highlighter-rouge">SpringResourceFactory.createResource()</code></a> 对两种 scope <strong>不做区分</strong>，统一委托 Spring：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// resteasy-spring/.../SpringResourceFactory.java:35-37</span>
<span class="kd">public</span> <span class="nc">Object</span> <span class="nf">createResource</span><span class="o">(</span><span class="nc">HttpRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpResponse</span> <span class="n">response</span><span class="o">,</span> <span class="nc">ResteasyProviderFactory</span> <span class="n">factory</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">beanFactory</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p><a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java"><code class="language-plaintext highlighter-rouge">AbstractBeanFactory.doGetBean()</code></a> 在这里分叉：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// spring-beans/.../AbstractBeanFactory.java:257-268 — singleton 命中一级缓存</span>
<span class="nc">Object</span> <span class="n">sharedInstance</span> <span class="o">=</span> <span class="n">getSingleton</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">sharedInstance</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">args</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">beanInstance</span> <span class="o">=</span> <span class="n">getObjectForBeanInstance</span><span class="o">(</span><span class="n">sharedInstance</span><span class="o">,</span> <span class="n">requiredType</span><span class="o">,</span> <span class="n">name</span><span class="o">,</span> <span class="n">beanName</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="o">}</span>

<span class="c1">// :359-364 — prototype 每次走完整 createBean → doCreateBean</span>
<span class="k">else</span> <span class="nf">if</span> <span class="o">(</span><span class="n">mbd</span><span class="o">.</span><span class="na">isPrototype</span><span class="o">())</span> <span class="o">{</span>
    <span class="n">prototypeInstance</span> <span class="o">=</span> <span class="n">createBean</span><span class="o">(</span><span class="n">beanName</span><span class="o">,</span> <span class="n">mbd</span><span class="o">,</span> <span class="n">args</span><span class="o">);</span>
    <span class="n">beanInstance</span> <span class="o">=</span> <span class="n">getObjectForBeanInstance</span><span class="o">(</span><span class="n">prototypeInstance</span><span class="o">,</span> <span class="n">requiredType</span><span class="o">,</span> <span class="n">name</span><span class="o">,</span> <span class="n">beanName</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<ul>
  <li><strong>singleton</strong>：直接返回 <code class="language-plaintext highlighter-rouge">refresh()</code> 期间 <code class="language-plaintext highlighter-rouge">preInstantiateSingletons()</code> 已创建并缓存的实例（可能被 AOP 包装）。</li>
  <li><strong>prototype</strong>：每次请求重新走 <code class="language-plaintext highlighter-rouge">createBeanInstance</code> → <code class="language-plaintext highlighter-rouge">populateBean</code>（<code class="language-plaintext highlighter-rouge">@Autowired</code>）→ <code class="language-plaintext highlighter-rouge">initializeBean</code>（BPP、<code class="language-plaintext highlighter-rouge">@PostConstruct</code>）全流程。</li>
</ul>

<h4 id="启动期prototype-resource-通常不在-refresh-时创建">启动期：prototype Resource 通常不在 refresh 时创建</h4>

<p><a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java#L1102-L1134"><code class="language-plaintext highlighter-rouge">DefaultListableBeanFactory.preInstantiateSingletons()</code></a> <strong>只 eagerly 初始化 singleton</strong>。prototype <code class="language-plaintext highlighter-rouge">@Path</code> Resource 在 <code class="language-plaintext highlighter-rouge">refresh()</code> 阶段 <strong>不会被创建</strong>，因此也不会在启动期经过 <code class="language-plaintext highlighter-rouge">ResteasyBeanPostProcessor.postProcessAfterInitialization()</code>。</p>

<p><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-spring/src/main/java/org/jboss/resteasy/plugins/spring/SpringBeanProcessor.java"><code class="language-plaintext highlighter-rouge">SpringBeanProcessor</code></a> 在 BFPP 阶段仍会为 prototype Resource 建立 <code class="language-plaintext highlighter-rouge">SpringResourceFactory</code> 映射并在 <code class="language-plaintext highlighter-rouge">ContextRefreshedEvent</code> 注册进 Registry——<strong>路由注册与 scope 无关</strong>；但 <strong>Bean 实例与 JAX-RS 字段注入</strong>推迟到第一次 HTTP 请求、<code class="language-plaintext highlighter-rouge">createResource()</code> 首次触发 <code class="language-plaintext highlighter-rouge">getBean()</code> 时才发生。</p>

<h4 id="inject-分支jax-rs-字段注入时机的核心开关">inject() 分支：JAX-RS 字段注入时机的核心开关</h4>

<p><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-spring/src/main/java/org/jboss/resteasy/plugins/spring/SpringBeanProcessor.java#L152-L163"><code class="language-plaintext highlighter-rouge">ResteasyBeanPostProcessor.inject()</code></a> 根据 <strong>当前线程是否有 <code class="language-plaintext highlighter-rouge">HttpRequest</code></strong> 以及 <strong>bean 是否为 singleton</strong> 决定注入方式：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// resteasy-spring/.../SpringBeanProcessor.java:152-163</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">inject</span><span class="o">(</span><span class="nc">String</span> <span class="n">beanName</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">bean</span><span class="o">,</span> <span class="nc">PropertyInjector</span> <span class="n">propertyInjector</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">propertyInjector</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="nc">HttpRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="nc">ResteasyContext</span><span class="o">.</span><span class="na">getContextData</span><span class="o">(</span><span class="nc">HttpRequest</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">request</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">isSingleton</span><span class="o">(</span><span class="n">beanName</span><span class="o">))</span> <span class="o">{</span>
        <span class="n">propertyInjector</span><span class="o">.</span><span class="na">inject</span><span class="o">(</span><span class="n">bean</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
    <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
        <span class="nc">HttpResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="nc">ResteasyContext</span><span class="o">.</span><span class="na">getContextData</span><span class="o">(</span><span class="nc">HttpResponse</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
        <span class="n">propertyInjector</span><span class="o">.</span><span class="na">inject</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="n">response</span><span class="o">,</span> <span class="n">bean</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th style="text-align: left">条件</th>
      <th style="text-align: left">走哪个分支</th>
      <th style="text-align: left">典型场景</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">request == null</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">inject(bean, false)</code></td>
      <td style="text-align: left">启动期 <code class="language-plaintext highlighter-rouge">refresh()</code> 创建 singleton Resource</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">isSingleton(beanName)</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">inject(bean, false)</code></td>
      <td style="text-align: left">运行期 <code class="language-plaintext highlighter-rouge">getBean()</code> 命中 singleton 缓存（即使有 HttpRequest）</td>
    </tr>
    <tr>
      <td style="text-align: left">prototype 且 <code class="language-plaintext highlighter-rouge">request != null</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">inject(request, response, bean, false)</code></td>
      <td style="text-align: left">请求线程 <code class="language-plaintext highlighter-rouge">createResource()</code> → <code class="language-plaintext highlighter-rouge">getBean()</code> 新建 prototype 实例</td>
    </tr>
  </tbody>
</table>

<p><strong>singleton 路径</strong>：字段 <code class="language-plaintext highlighter-rouge">@Context UriInfo</code> 等在启动期注入一次（此时 <code class="language-plaintext highlighter-rouge">HttpRequest == null</code>），之后所有请求复用同一对象——<code class="language-plaintext highlighter-rouge">UriInfo</code> 等可能 <strong>不是当前请求的值</strong>，这是集成里最容易踩坑的点。</p>

<p><strong>prototype 路径</strong>：<code class="language-plaintext highlighter-rouge">getBean()</code> 发生在 RESTEasy 请求线程内，<code class="language-plaintext highlighter-rouge">ResteasyContext</code> 已绑定当前 <code class="language-plaintext highlighter-rouge">HttpRequest</code>，字段 <code class="language-plaintext highlighter-rouge">@Context</code> 按 <strong>当前请求</strong> 注入——行为更符合 JAX-RS per-request 语义，代价是每请求完整 Bean 创建。</p>

<p>方法参数上的 <code class="language-plaintext highlighter-rouge">@QueryParam</code> / <code class="language-plaintext highlighter-rouge">@PathParam</code> 不受此分支影响，始终由 RESTEasy <a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/core/MethodInjectorImpl.java"><code class="language-plaintext highlighter-rouge">MethodInjector</code></a> 在方法调用前注入（见第五节）。</p>

<h4 id="singleton-vs-prototype-完整对照">singleton vs prototype 完整对照</h4>

<table>
  <thead>
    <tr>
      <th style="text-align: left">维度</th>
      <th style="text-align: left">singleton Resource</th>
      <th style="text-align: left">prototype Resource</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">桥接入口</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">SpringResourceFactory</code> → <code class="language-plaintext highlighter-rouge">getBean()</code></td>
      <td style="text-align: left">同左</td>
    </tr>
    <tr>
      <td style="text-align: left">Registry 注册</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ContextRefreshedEvent</code> 一次</td>
      <td style="text-align: left">同左（登记的是 Factory，不是实例）</td>
    </tr>
    <tr>
      <td style="text-align: left">每次请求 <code class="language-plaintext highlighter-rouge">getBean()</code></td>
      <td style="text-align: left">命中缓存，同一实例</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">createBean()</code> → 完整 <code class="language-plaintext highlighter-rouge">doCreateBean</code></td>
    </tr>
    <tr>
      <td style="text-align: left">启动期 <code class="language-plaintext highlighter-rouge">refresh()</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">preInstantiateSingletons()</code> 创建 + BPP 注入</td>
      <td style="text-align: left"><strong>通常不创建</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">字段 <code class="language-plaintext highlighter-rouge">@Context</code></td>
      <td style="text-align: left">启动期注入，全请求共享</td>
      <td style="text-align: left">请求期 <code class="language-plaintext highlighter-rouge">getBean</code> 链注入，每请求新实例</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Autowired</code></td>
      <td style="text-align: left">启动期注入一次</td>
      <td style="text-align: left">每请求 <code class="language-plaintext highlighter-rouge">populateBean</code> 重新注入</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Transactional</code> / AOP</td>
      <td style="text-align: left">启动期代理，每请求复用</td>
      <td style="text-align: left">每请求新建实例（及可能的代理）</td>
    </tr>
    <tr>
      <td style="text-align: left">性能</td>
      <td style="text-align: left">低（缓存命中）</td>
      <td style="text-align: left">高（每请求完整生命周期）</td>
    </tr>
  </tbody>
</table>

<h4 id="销毁bridge-模式下-prototype-的盲区">销毁：bridge 模式下 prototype 的盲区</h4>

<p><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-spring/src/main/java/org/jboss/resteasy/plugins/spring/SpringResourceFactory.java#L48-L50"><code class="language-plaintext highlighter-rouge">SpringResourceFactory.requestFinished()</code></a> 在 Spring 集成路径下 <strong>空实现</strong>：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// resteasy-spring/.../SpringResourceFactory.java:48-50</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">requestFinished</span><span class="o">(</span><span class="nc">HttpRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpResponse</span> <span class="n">response</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">resource</span><span class="o">)</span> <span class="o">{</span>
<span class="o">}</span>
</code></pre></div></div>

<p>对比纯 RESTEasy <a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/core/POJOResourceFactory.java"><code class="language-plaintext highlighter-rouge">POJOResourceFactory</code></a>——每请求 <code class="language-plaintext highlighter-rouge">new</code> 实例，RESTEasy 可在 <code class="language-plaintext highlighter-rouge">requestFinished()</code> 触发 <code class="language-plaintext highlighter-rouge">@PreDestroy</code> 语义；Spring bridge 路径下：</p>

<ul>
  <li>RESTEasy <strong>不</strong>在请求结束时 destroy 实例；</li>
  <li>Spring 对 prototype Bean <strong>默认也不</strong>在 <code class="language-plaintext highlighter-rouge">getBean()</code> 返回后调 <code class="language-plaintext highlighter-rouge">@PreDestroy</code>（调用方负责生命周期）。</li>
</ul>

<p>若 prototype Resource 持有连接、文件句柄等需释放的资源，不能指望框架自动清理——应在业务方法内显式释放，或避免对 Resource 使用 prototype scope。</p>

<h4 id="与-pojoresourcefactory-对比">与 POJOResourceFactory 对比</h4>

<table>
  <thead>
    <tr>
      <th style="text-align: left"> </th>
      <th style="text-align: left">POJOResourceFactory（纯 RESTEasy）</th>
      <th style="text-align: left">Spring bridge + prototype</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">谁创建实例</td>
      <td style="text-align: left">RESTEasy <code class="language-plaintext highlighter-rouge">ConstructorInjector.construct()</code></td>
      <td style="text-align: left">Spring <code class="language-plaintext highlighter-rouge">doCreateBean()</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Autowired</code></td>
      <td style="text-align: left">❌</td>
      <td style="text-align: left">✅ 每请求 <code class="language-plaintext highlighter-rouge">populateBean</code></td>
    </tr>
    <tr>
      <td style="text-align: left">字段 <code class="language-plaintext highlighter-rouge">@Context</code></td>
      <td style="text-align: left">每请求 <code class="language-plaintext highlighter-rouge">PropertyInjector.inject(request, response, …)</code></td>
      <td style="text-align: left">每请求 BPP → <code class="language-plaintext highlighter-rouge">inject(request, response, …)</code></td>
    </tr>
    <tr>
      <td style="text-align: left">请求结束</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">requestFinished()</code> 可触发销毁语义</td>
      <td style="text-align: left"><strong>空实现</strong>，Spring prototype 亦无自动 destroy</td>
    </tr>
  </tbody>
</table>

<pre><code class="language-mermaid">sequenceDiagram
    participant HTTP as HTTP 请求
    participant Invoker as ResourceMethodInvoker
    participant SRF as SpringResourceFactory
    participant Spring as BeanFactory
    participant BPP as ResteasyBeanPostProcessor

    Note over HTTP,BPP: prototype Resource — 首次及后续每次请求
    HTTP-&gt;&gt;Invoker: invoke()
    Invoker-&gt;&gt;SRF: createResource()
    SRF-&gt;&gt;Spring: getBean(beanName)
    Spring-&gt;&gt;Spring: createBean → doCreateBean
    Spring-&gt;&gt;BPP: postProcessAfterInitialization
    BPP-&gt;&gt;BPP: inject(request, response, bean) — 字段 @Context
    Spring--&gt;&gt;SRF: 新 prototype 实例
    SRF--&gt;&gt;Invoker: resource
    Invoker-&gt;&gt;Invoker: MethodInjector — @QueryParam
    Invoker-&gt;&gt;Invoker: 调用 @GET
    Note over SRF: requestFinished() 空实现，无 destroy
</code></pre>

<p>启动期桥接就绪后，下面看 <strong>运行期</strong> 单次请求的完整 call chain。</p>

<h2 id="五运行期resteasy-主控spring-供实例">五、运行期：RESTEasy 主控，Spring 供实例</h2>

<h3 id="运行期-call-chain单次-http-请求">运行期 Call Chain（单次 HTTP 请求）</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SynchronousDispatcher / AsynchronousDispatcher
  → ResourceMethodInvoker.invoke(request, response)           :348-356
      → ResourceFactory.createResource(...)                     SpringResourceFactory :35-37
          → BeanFactory.getBean(beanName)                       AbstractBeanFactory :197-198
              → doGetBean(name, ...)                          :249-396
                  ├─ singleton: getSingleton() 命中缓存        :257-268
                  └─ prototype: createBean() → doCreateBean() :359-364 → :556-603
      → ResourceMethodInvoker.invoke(request, response, target) :370-378
          → invokeOnTarget()                                    :403-417
              → internalInvokeOnTarget()                        :555-560
                  → MethodInjector.invoke(request, response, target)  :560
</code></pre></div></div>

<h3 id="resourcemethodinvoker每次请求先-createresource">ResourceMethodInvoker：每次请求先 createResource</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// resteasy-core/.../ResourceMethodInvoker.java:348-356</span>
<span class="kd">public</span> <span class="nc">BuiltResponse</span> <span class="nf">invoke</span><span class="o">(</span><span class="nc">HttpRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpResponse</span> <span class="n">response</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">Object</span> <span class="n">resource</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">resource</span><span class="o">.</span><span class="na">createResource</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="n">response</span><span class="o">,</span> <span class="n">resourceMethodProviderFactory</span><span class="o">);</span>
    <span class="c1">// ...</span>
    <span class="k">return</span> <span class="nf">invoke</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="n">response</span><span class="o">,</span> <span class="n">resource</span><span class="o">);</span>
<span class="o">}</span>

<span class="c1">// :555-560 — JAX-RS 方法参数注入 + 反射调用</span>
<span class="kd">private</span> <span class="nc">Object</span> <span class="nf">internalInvokeOnTarget</span><span class="o">(</span><span class="nc">HttpRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpResponse</span> <span class="n">response</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">target</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">Object</span> <span class="n">methodResponse</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">methodInjector</span><span class="o">.</span><span class="na">invoke</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="n">response</span><span class="o">,</span> <span class="n">target</span><span class="o">);</span>
    <span class="c1">// ...</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="getbean-分支singleton-vs-prototype">getBean 分支（singleton vs prototype）</h3>

<p>与第二节相同，<a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java"><code class="language-plaintext highlighter-rouge">doGetBean()</code></a> 在运行期按 scope 分叉——<strong>singleton 命中缓存则不再进入 <code class="language-plaintext highlighter-rouge">doCreateBean</code></strong>，prototype 每次进入：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// spring-beans/.../AbstractBeanFactory.java:257-268 — singleton 命中缓存</span>
<span class="nc">Object</span> <span class="n">sharedInstance</span> <span class="o">=</span> <span class="n">getSingleton</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">sharedInstance</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">args</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">beanInstance</span> <span class="o">=</span> <span class="n">getObjectForBeanInstance</span><span class="o">(</span><span class="n">sharedInstance</span><span class="o">,</span> <span class="n">requiredType</span><span class="o">,</span> <span class="n">name</span><span class="o">,</span> <span class="n">beanName</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="o">}</span>

<span class="c1">// :359-364 — prototype 每次 createBean</span>
<span class="k">else</span> <span class="nf">if</span> <span class="o">(</span><span class="n">mbd</span><span class="o">.</span><span class="na">isPrototype</span><span class="o">())</span> <span class="o">{</span>
    <span class="n">prototypeInstance</span> <span class="o">=</span> <span class="n">createBean</span><span class="o">(</span><span class="n">beanName</span><span class="o">,</span> <span class="n">mbd</span><span class="o">,</span> <span class="n">args</span><span class="o">);</span>  <span class="c1">// → doCreateBean</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="运行期注入分工">运行期注入分工</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">注入目标</th>
      <th style="text-align: left">负责方</th>
      <th style="text-align: left">singleton</th>
      <th style="text-align: left">prototype</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Autowired CustomerService</code></td>
      <td style="text-align: left">Spring <code class="language-plaintext highlighter-rouge">populateBean</code></td>
      <td style="text-align: left">启动期一次</td>
      <td style="text-align: left">每次 <code class="language-plaintext highlighter-rouge">getBean</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@QueryParam</code> / <code class="language-plaintext highlighter-rouge">@PathParam</code></td>
      <td style="text-align: left">RESTEasy <code class="language-plaintext highlighter-rouge">MethodInjector</code></td>
      <td style="text-align: left">每次请求</td>
      <td style="text-align: left">每次请求</td>
    </tr>
    <tr>
      <td style="text-align: left">字段 <code class="language-plaintext highlighter-rouge">@Context UriInfo</code></td>
      <td style="text-align: left">RESTEasy <code class="language-plaintext highlighter-rouge">PropertyInjector</code></td>
      <td style="text-align: left">启动期 BPP</td>
      <td style="text-align: left">请求期 BPP（有 <code class="language-plaintext highlighter-rouge">HttpRequest</code>）</td>
    </tr>
  </tbody>
</table>

<p>RESTEasy <strong>不重新做</strong> Spring DI；Spring <strong>不解析</strong> JAX-RS 方法参数。</p>

<h3 id="spring-能力在运行时是否保留">Spring 能力在运行时是否保留</h3>

<p>Resource / Service 仍是 Spring Bean（可能是 CGLIB/JDK 代理）：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">能力</th>
      <th style="text-align: left">singleton Resource</th>
      <th style="text-align: left">prototype Resource</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Transactional</code></td>
      <td style="text-align: left">✅ 启动期代理，每请求复用</td>
      <td style="text-align: left">✅ 每请求新建实例（及代理）</td>
    </tr>
    <tr>
      <td style="text-align: left">AOP <code class="language-plaintext highlighter-rouge">@Aspect</code></td>
      <td style="text-align: left">✅ 同上</td>
      <td style="text-align: left">✅ 每请求重新织入</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Autowired</code> 字段</td>
      <td style="text-align: left">✅ 启动期已注入</td>
      <td style="text-align: left">✅ 每请求 <code class="language-plaintext highlighter-rouge">populateBean</code></td>
    </tr>
    <tr>
      <td style="text-align: left">每请求新建 Resource 实例</td>
      <td style="text-align: left">❌ 缓存复用</td>
      <td style="text-align: left">✅ 每次 <code class="language-plaintext highlighter-rouge">getBean</code> → <code class="language-plaintext highlighter-rouge">doCreateBean</code></td>
    </tr>
  </tbody>
</table>

<pre><code class="language-mermaid">sequenceDiagram
    participant HTTP as HTTP 请求
    participant Invoker as ResourceMethodInvoker
    participant SRF as SpringResourceFactory
    participant Spring as BeanFactory
    participant Resource as @Path Bean

    HTTP-&gt;&gt;Invoker: invoke()
    Invoker-&gt;&gt;SRF: createResource()
    SRF-&gt;&gt;Spring: getBean(beanName)
    alt singleton
        Spring--&gt;&gt;SRF: 缓存实例
    else prototype
        Spring-&gt;&gt;Spring: doCreateBean 全流程
        Spring--&gt;&gt;SRF: 新实例
    end
    SRF--&gt;&gt;Invoker: resource
    Invoker-&gt;&gt;Resource: 注入 @QueryParam，调用 @GET
    Resource-&gt;&gt;Spring: 调用 @Autowired Service（可能 @Transactional）
</code></pre>

<p>运行期桥接逻辑在 WAR 与 Boot 下相同；差异在 <strong>启动编排</strong>——第六节对比两种部署方式。</p>

<h2 id="六传统-spring-与-spring-boot启动差异与-resteasy-spring-boot">六、传统 Spring 与 Spring Boot：启动差异与 resteasy-spring-boot</h2>

<p><strong>运行期桥接逻辑不变</strong>——仍是 <code class="language-plaintext highlighter-rouge">SpringResourceFactory.createResource()</code> → <code class="language-plaintext highlighter-rouge">getBean()</code>。<code class="language-plaintext highlighter-rouge">resteasy-spring-boot</code> 解决的是 <strong>嵌入式 Tomcat 没有 <code class="language-plaintext highlighter-rouge">ServletContainerInitializer</code></strong> 时，如何把 RESTEasy deployment 与 Spring 容器按正确时序拼起来。<sup id="fnref:7:1"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">9</a></sup></p>

<h3 id="传统-war--spring-mvc-启动时序">传统 WAR / Spring MVC 启动时序</h3>

<p>WAR 部署下，RESTEasy 常通过 <code class="language-plaintext highlighter-rouge">ServletContainerInitializer</code> <strong>先于</strong> Spring 完成 deployment 初始化：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ServletContainerInitializer（RESTEasy）
    └─ ResteasyDeployment 创建
         └─ new SpringBeanProcessor(deployment)  :177-179
              └─ ContextLoaderListener 启动 WebApplicationContext
                   └─ refresh()
                        ├─ BFPP / BPP（同第四节）
                        └─ ContextRefreshedEvent → Registry.addResourceFactory
</code></pre></div></div>

<p>此时 <code class="language-plaintext highlighter-rouge">SpringBeanProcessor</code> 构造函数接收 <strong>已存在的</strong> <code class="language-plaintext highlighter-rouge">ResteasyDeployment</code>，Registry 与 ProviderFactory 来自 servlet 层；Spring 的 <code class="language-plaintext highlighter-rouge">refresh()</code> 只负责 Bean 创建与 Resource 注册，不再创建 deployment。</p>

<h3 id="spring-boot-嵌入式-tomcat-启动时序">Spring Boot 嵌入式 Tomcat 启动时序</h3>

<p>Boot 路径下 Spring 容器先起，Servlet 后注册：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SpringApplication.run()
    └─ ResteasyAutoConfiguration @Bean SpringBeanProcessor()  :85-88
    └─ refresh()
         ├─ BFPP / BPP（同第四节）
         └─ ContextRefreshedEvent → Registry.addResourceFactory
    └─ Tomcat 启动
         └─ ResteasyBeanProcessorTomcat：扫描 @ApplicationPath  :36-44
         └─ ServletRegistrationBean 注册 HttpServletDispatcher
         └─ resteasyBootstrapListener()  :50-56
              └─ DeploymentCustomizer.customizeRestEasyDeployment()  :29-47
                   └─ 把 SpringBeanProcessor 的 Registry/ProviderFactory 写回 ResteasyDeployment
                   └─ deployment.start()
</code></pre></div></div>

<p>Boot 先建 <strong>空的</strong> <code class="language-plaintext highlighter-rouge">SpringBeanProcessor</code>，Servlet 启动后 <code class="language-plaintext highlighter-rouge">DeploymentCustomizer</code> 再把 Registry / ProviderFactory <strong>合并进</strong> ResteasyDeployment。<strong>Resource 等到 <code class="language-plaintext highlighter-rouge">ContextRefreshedEvent</code> 才进 Registry</strong>——这一点 WAR 与 Boot <strong>一致</strong>。</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">维度</th>
      <th style="text-align: left">传统 WAR</th>
      <th style="text-align: left">Spring Boot</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">deployment 创建时机</td>
      <td style="text-align: left">Servlet 容器初始化，常早于 Spring</td>
      <td style="text-align: left">Servlet 注册时，Spring 已 refresh</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">SpringBeanProcessor</code> 构造</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">new SpringBeanProcessor(deployment)</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Bean</code> 无参构造</td>
    </tr>
    <tr>
      <td style="text-align: left">Registry 来源</td>
      <td style="text-align: left">构造时注入 deployment</td>
      <td style="text-align: left">先空 Registry，后由 <code class="language-plaintext highlighter-rouge">DeploymentCustomizer</code> 合并</td>
    </tr>
    <tr>
      <td style="text-align: left">Resource 注册触发</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ContextRefreshedEvent</code></td>
      <td style="text-align: left">同左</td>
    </tr>
    <tr>
      <td style="text-align: left">额外注册入口</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">SpringContextLoaderSupport</code> / <code class="language-plaintext highlighter-rouge">springmvc-resteasy.xml</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ResteasyAutoConfiguration</code></td>
    </tr>
  </tbody>
</table>

<h3 id="resteasy-spring-boot-额外组件">resteasy-spring-boot 额外组件</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">组件</th>
      <th style="text-align: left">类</th>
      <th style="text-align: left">关键行</th>
      <th style="text-align: left">作用</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">自动配置</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ResteasyAutoConfiguration</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">:85-88</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Bean</code> 注册 <code class="language-plaintext highlighter-rouge">SpringBeanProcessor</code></td>
    </tr>
    <tr>
      <td style="text-align: left">Servlet 启动</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ResteasyAutoConfiguration.resteasyBootstrapListener()</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">:50-56</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">DeploymentCustomizer</code> + <code class="language-plaintext highlighter-rouge">deployment.start()</code></td>
    </tr>
    <tr>
      <td style="text-align: left">BFPP 扫描</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ResteasyBeanProcessorTomcat</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">:36-44</code></td>
      <td style="text-align: left">发现 <code class="language-plaintext highlighter-rouge">@ApplicationPath</code>、注册 <code class="language-plaintext highlighter-rouge">ServletRegistrationBean</code></td>
    </tr>
    <tr>
      <td style="text-align: left">Deployment 合并</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">DeploymentCustomizer</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">:29-47</code></td>
      <td style="text-align: left">把 <code class="language-plaintext highlighter-rouge">SpringBeanProcessor</code> 的 Registry/ProviderFactory 写入 <code class="language-plaintext highlighter-rouge">ResteasyDeployment</code></td>
    </tr>
  </tbody>
</table>

<h2 id="七总结两个生命周期的分界">七、总结：两个生命周期的分界</h2>

<h3 id="启动期">启动期</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Spring refresh()
├── BFPP: SpringBeanProcessor 扫描 @Path → SpringResourceFactory（singleton / prototype 均建映射）
├── preInstantiateSingletons → doCreateBean（仅 singleton）
│     ResteasyBeanPostProcessor: @Provider 注册；singleton @Path 做 JAX-RS 字段注入
│     prototype @Path：通常不在此阶段实例化
└── finishRefresh → ContextRefreshedEvent
      → Registry.addResourceFactory → registered()（scope 无关，均注册路由）
</code></pre></div></div>

<p><strong>Spring 管：</strong> 实例创建（<code class="language-plaintext highlighter-rouge">doCreateBean</code>，scope 决定时机）、<code class="language-plaintext highlighter-rouge">@Autowired</code>、AOP、<code class="language-plaintext highlighter-rouge">@PostConstruct</code> / <code class="language-plaintext highlighter-rouge">@PreDestroy</code>（prototype 无自动 destroy）。<br />
<strong>RESTEasy 管：</strong> 路由元数据、<code class="language-plaintext highlighter-rouge">@Provider</code> 链、JAX-RS 字段 / 方法参数注入策略。</p>

<h3 id="运行期">运行期</h3>

<p><strong>RESTEasy 管：</strong> URI 匹配、<code class="language-plaintext highlighter-rouge">createResource()</code>、Filter/Interceptor 链、方法调用。<br />
<strong>Spring 管：</strong> <code class="language-plaintext highlighter-rouge">getBean()</code> — singleton 返缓存，prototype 再走 <code class="language-plaintext highlighter-rouge">doCreateBean</code>；业务依赖、事务与 AOP 随实例而来。</p>

<h3 id="一句话">一句话</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Spring：所有 scope 共用 doCreateBean 流水线；singleton 启动期入缓存，prototype 每次 getBean 重走
RESTEasy：Registry 登记 ResourceFactory 策略 + 每端点 ResourceMethodInvoker
SpringResourceFactory：createResource() → getBean()，scope 由 Spring 定义
</code></pre></div></div>

<h2 id="references">References</h2>

<p>对照源码仓库：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">仓库</th>
      <th style="text-align: left">GitHub</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">spring-framework</td>
      <td style="text-align: left"><a href="https://github.com/spring-projects/spring-framework"><code class="language-plaintext highlighter-rouge">spring-projects/spring-framework</code></a></td>
    </tr>
    <tr>
      <td style="text-align: left">resteasy</td>
      <td style="text-align: left"><a href="https://github.com/resteasy/resteasy"><code class="language-plaintext highlighter-rouge">resteasy/resteasy</code></a></td>
    </tr>
    <tr>
      <td style="text-align: left">resteasy-spring-boot</td>
      <td style="text-align: left"><a href="https://github.com/resteasy/resteasy-spring-boot"><code class="language-plaintext highlighter-rouge">resteasy/resteasy-spring-boot</code></a></td>
    </tr>
  </tbody>
</table>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p><a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java#L556-L603"><code class="language-plaintext highlighter-rouge">AbstractAutowireCapableBeanFactory.doCreateBean()</code></a>、<a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java#L1797-L1821"><code class="language-plaintext highlighter-rouge">initializeBean()</code></a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p><a href="https://github.com/spring-projects/spring-framework/blob/main/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java#L582-L625"><code class="language-plaintext highlighter-rouge">AbstractApplicationContext.refresh()</code></a>、<a href="https://github.com/spring-projects/spring-framework/blob/main/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java#L1003-L1017"><code class="language-plaintext highlighter-rouge">finishRefresh()</code></a> <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-spring/src/main/java/org/jboss/resteasy/plugins/spring/SpringBeanProcessor.java#L446-L448"><code class="language-plaintext highlighter-rouge">SpringBeanProcessor.onApplicationEvent()</code></a> 注释 <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:3:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:5">
      <p><a href="https://github.com/spring-projects/spring-framework/blob/main/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java#L1246-L1247"><code class="language-plaintext highlighter-rouge">AbstractApplicationContext.destroyBeans()</code></a> → <a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java#L693-L705"><code class="language-plaintext highlighter-rouge">DefaultSingletonBeanRegistry.destroySingletons()</code></a> <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6">
      <p><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core-spi/src/main/java/org/jboss/resteasy/spi/ResourceFactory.java#L34-L40"><code class="language-plaintext highlighter-rouge">ResourceFactory.requestFinished()</code></a> 接口注释 <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:6:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:9">
      <p><a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java"><code class="language-plaintext highlighter-rouge">DefaultSingletonBeanRegistry</code></a>（<code class="language-plaintext highlighter-rouge">singletonObjects</code> <code class="language-plaintext highlighter-rouge">:85-86</code>、<code class="language-plaintext highlighter-rouge">addSingleton()</code> <code class="language-plaintext highlighter-rouge">:159-173</code>、<code class="language-plaintext highlighter-rouge">getSingleton()</code> <code class="language-plaintext highlighter-rouge">:208-244</code>；创建入口见 <a href="https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java#L343-L356"><code class="language-plaintext highlighter-rouge">AbstractBeanFactory.doGetBean()</code></a>） <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8">
      <p><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceMethodRegistry.java"><code class="language-plaintext highlighter-rouge">ResourceMethodRegistry</code></a>（<code class="language-plaintext highlighter-rouge">addResourceFactory</code> <code class="language-plaintext highlighter-rouge">:200-220</code>、<code class="language-plaintext highlighter-rouge">processMethod</code> <code class="language-plaintext highlighter-rouge">:295-310</code>）、<a href="https://github.com/resteasy/resteasy/blob/main/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceMethodInvoker.java#L348-L356"><code class="language-plaintext highlighter-rouge">ResourceMethodInvoker.invoke()</code></a> <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p><a href="https://github.com/resteasy/resteasy/blob/main/resteasy-spring/src/main/java/org/jboss/resteasy/plugins/spring/SpringBeanProcessor.java"><code class="language-plaintext highlighter-rouge">SpringBeanProcessor</code></a>（BFPP <code class="language-plaintext highlighter-rouge">:235-261</code>，BPP <code class="language-plaintext highlighter-rouge">:114-146</code>，事件 <code class="language-plaintext highlighter-rouge">:453-467</code>） <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7">
      <p><a href="https://github.com/resteasy/resteasy-spring-boot/blob/main/servlet/resteasy-servlet-spring-boot-starter/src/main/java/org/jboss/resteasy/springboot/ResteasyAutoConfiguration.java"><code class="language-plaintext highlighter-rouge">ResteasyAutoConfiguration</code></a>（<code class="language-plaintext highlighter-rouge">:85-88</code> 注册 <code class="language-plaintext highlighter-rouge">SpringBeanProcessor</code>，<code class="language-plaintext highlighter-rouge">:50-56</code> Servlet 启动） <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:7:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="java" /><category term="spring" /><category term="resteasy" /><summary type="html"><![CDATA[对照 spring-framework、resteasy-spring 与 resteasy-spring-boot 源码，梳理 Spring Bean（singleton/prototype）与 RESTEasy Resource 两条生命周期、桥接机制，以及 WAR 与 Spring Boot 启动时序差异。]]></summary></entry><entry><title type="html">AI 辅助编程中沉淀的六个核心洞见</title><link href="https://weinan.tech/2026/06/09/ai-coding-six-core-insights.html" rel="alternate" type="text/html" title="AI 辅助编程中沉淀的六个核心洞见" /><published>2026-06-09T00:00:00+08:00</published><updated>2026-06-09T00:00:00+08:00</updated><id>https://weinan.tech/2026/06/09/ai-coding-six-core-insights</id><content type="html" xml:base="https://weinan.tech/2026/06/09/ai-coding-six-core-insights.html"><![CDATA[<style>
/* Mermaid diagram container */
.mermaid-container {
  position: relative;
  display: block;
  cursor: pointer;
  transition: opacity 0.2s;
  max-width: 100%;
  margin: 20px 0;
  overflow-x: auto;
}

.mermaid-container:hover {
  opacity: 0.8;
}

.mermaid-container svg {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Modal overlay */
.mermaid-modal {
  display: none;
  position: fixed;
  z-index: 9999;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9);
  animation: fadeIn 0.3s;
}

.mermaid-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Modal content */
.mermaid-modal-content {
  position: relative;
  width: 90vw;
  height: 90vh;
  overflow: hidden;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
}

.mermaid-modal-diagram {
  transform-origin: center center;
  transition: transform 0.2s ease;
  display: inline-block;
  min-width: 100%;
  cursor: grab;
  user-select: none;
}

.mermaid-modal-diagram.dragging {
  cursor: grabbing;
  transition: none;
}

.mermaid-modal-diagram svg {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;
}

/* Control buttons */
.mermaid-controls {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
  z-index: 10000;
}

.mermaid-btn {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
  color: #333;
  font-weight: 500;
}

.mermaid-btn:hover {
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Close button */
.mermaid-close {
  background: #f44336;
  color: white;
  border: none;
}

.mermaid-close:hover {
  background: #d32f2f;
}

/* Zoom indicator */
.mermaid-zoom-level {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 10000;
}
</style>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  mermaid.initialize({
    startOnLoad: false,
    theme: 'default',
    securityLevel: 'loose',
    htmlLabels: true,
    themeVariables: {
      fontSize: '14px'
    }
  });

  let currentZoom = 1;
  let currentModal = null;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let translateX = 0;
  let translateY = 0;

  // Create modal HTML
  function createModal() {
    const modal = document.createElement('div');
    modal.className = 'mermaid-modal';
    modal.innerHTML = `
      <div class="mermaid-controls">
        <button class="mermaid-btn zoom-in">放大 +</button>
        <button class="mermaid-btn zoom-out">缩小 -</button>
        <button class="mermaid-btn zoom-reset">重置</button>
        <button class="mermaid-btn mermaid-close">关闭 ✕</button>
      </div>
      <div class="mermaid-modal-content">
        <div class="mermaid-modal-diagram"></div>
      </div>
      <div class="mermaid-zoom-level">100%</div>
    `;
    document.body.appendChild(modal);
    return modal;
  }

  // Show modal with diagram
  function showModal(diagramContent) {
    if (!currentModal) {
      currentModal = createModal();
      setupModalEvents();
    }

    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.innerHTML = diagramContent;

    // Remove any width/height attributes from SVG to make it responsive
    const svg = modalDiagram.querySelector('svg');
    if (svg) {
      svg.removeAttribute('width');
      svg.removeAttribute('height');
      svg.style.width = '100%';
      svg.style.height = 'auto';
    }

    // Setup drag functionality
    setupDrag(modalDiagram);

    currentModal.classList.add('active');
    currentZoom = 1;
    resetPosition();
    updateZoom();
    document.body.style.overflow = 'hidden';
  }

  // Hide modal
  function hideModal() {
    if (currentModal) {
      currentModal.classList.remove('active');
      document.body.style.overflow = '';
    }
  }

  // Update zoom level
  function updateZoom() {
    if (!currentModal) return;
    const diagram = currentModal.querySelector('.mermaid-modal-diagram');
    const zoomLevel = currentModal.querySelector('.mermaid-zoom-level');
    diagram.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentZoom})`;
    zoomLevel.textContent = `${Math.round(currentZoom * 100)}%`;
  }

  // Reset position when zoom changes
  function resetPosition() {
    translateX = 0;
    translateY = 0;
  }

  // Setup drag functionality
  function setupDrag(element) {
    element.addEventListener('mousedown', startDrag);
    element.addEventListener('touchstart', startDrag);
  }

  function startDrag(e) {
    if (e.type === 'mousedown' && e.button !== 0) return; // Only left click

    isDragging = true;
    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.classList.add('dragging');

    if (e.type === 'touchstart') {
      startX = e.touches[0].clientX - translateX;
      startY = e.touches[0].clientY - translateY;
    } else {
      startX = e.clientX - translateX;
      startY = e.clientY - translateY;
    }

    document.addEventListener('mousemove', drag);
    document.addEventListener('touchmove', drag);
    document.addEventListener('mouseup', stopDrag);
    document.addEventListener('touchend', stopDrag);
  }

  function drag(e) {
    if (!isDragging) return;
    e.preventDefault();

    if (e.type === 'touchmove') {
      translateX = e.touches[0].clientX - startX;
      translateY = e.touches[0].clientY - startY;
    } else {
      translateX = e.clientX - startX;
      translateY = e.clientY - startY;
    }

    updateZoom();
  }

  function stopDrag() {
    isDragging = false;
    const modalDiagram = currentModal?.querySelector('.mermaid-modal-diagram');
    if (modalDiagram) {
      modalDiagram.classList.remove('dragging');
    }
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('touchmove', drag);
    document.removeEventListener('mouseup', stopDrag);
    document.removeEventListener('touchend', stopDrag);
  }

  // Setup modal event listeners
  function setupModalEvents() {
    if (!currentModal) return;

    // Close button
    currentModal.querySelector('.mermaid-close').addEventListener('click', hideModal);

    // Zoom buttons
    currentModal.querySelector('.zoom-in').addEventListener('click', () => {
      currentZoom = Math.min(currentZoom + 0.25, 3);
      updateZoom();
    });

    currentModal.querySelector('.zoom-out').addEventListener('click', () => {
      currentZoom = Math.max(currentZoom - 0.25, 0.5);
      updateZoom();
    });

    currentModal.querySelector('.zoom-reset').addEventListener('click', () => {
      currentZoom = 1;
      resetPosition();
      updateZoom();
    });

    // Close on background click
    currentModal.addEventListener('click', (e) => {
      if (e.target === currentModal) {
        hideModal();
      }
    });

    // Close on ESC key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && currentModal.classList.contains('active')) {
        hideModal();
      }
    });
  }

  // Convert Jekyll-rendered code blocks to mermaid divs
  document.addEventListener('DOMContentLoaded', async function() {
    const codeBlocks = document.querySelectorAll('code.language-mermaid');

    for (const codeBlock of codeBlocks) {
      const pre = codeBlock.parentElement;
      const container = document.createElement('div');
      container.className = 'mermaid-container';

      const mermaidDiv = document.createElement('div');
      mermaidDiv.className = 'mermaid';
      mermaidDiv.textContent = codeBlock.textContent;

      container.appendChild(mermaidDiv);
      pre.replaceWith(container);
    }

    // Render all mermaid diagrams
    try {
      await mermaid.run({
        querySelector: '.mermaid'
      });
      console.log('Mermaid diagrams rendered successfully');
    } catch (error) {
      console.error('Mermaid rendering error:', error);
    }

    // Add click handlers to rendered diagrams
    document.querySelectorAll('.mermaid-container').forEach((container, index) => {
      // Find the rendered SVG inside the container
      const svg = container.querySelector('svg');
      if (!svg) {
        console.warn(`No SVG found in container ${index}`);
        return;
      }

      // Make the container clickable
      container.style.cursor = 'pointer';
      container.title = '点击查看大图';

      container.addEventListener('click', function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Clone the SVG for the modal
        const svgClone = svg.cloneNode(true);
        const tempDiv = document.createElement('div');
        tempDiv.appendChild(svgClone);

        console.log('Opening modal for diagram', index);
        showModal(tempDiv.innerHTML);
      });

      console.log(`Click handler added to diagram ${index}`);
    });
  });
</script>

<blockquote>
  <p>经过一年多的日常实践，AI 辅助编程在我的工作流中从”辅助工具”演变成了”核心协作伙伴”。这个过程沉淀出了一些认知——它们不追求新奇，只追求准确。这篇文章记录的是截至 2026 年中，我个人认知框架中的六个核心洞见。</p>
</blockquote>

<h2 id="引言认知的演化路径">引言：认知的演化路径</h2>

<p>过去一年半，我在 AI 辅助编程中的角色经历了三次转变：</p>

<ol>
  <li><strong>工具使用阶段</strong>：把 AI 当成更聪明的搜索引擎和代码补全<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup></li>
  <li><strong>流程重构阶段</strong>：围绕 AI 重新设计自己的工作流，从”写代码”转向”引导 AI 写代码”<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></li>
  <li><strong>范式反思阶段</strong>：开始区分不同场景下 AI 的最佳使用模式，意识到”单人”和”多人”模式下 AI 的角色截然不同</li>
</ol>

<p>这篇文章属于第三阶段的产物。它不是操作指南，而是一个仍在演进中的认知框架——关于 LLM 能力边界、协作模式选择，以及技术和人的关系。</p>

<h2 id="洞见一llm-能力是地基但上下文窗口同样关键">洞见一：LLM 能力是地基，但上下文窗口同样关键</h2>

<p>模型本身的能力决定了理解和生成的天花板——这是一个常识，但在实践中容易被低估。不同模型在同一任务上的表现差距是系统性的，prompt 工程能优化但无法跨越这个差距。</p>

<p>但真正影响日常工程体验的，还有另一个因素：<strong>稳定且足够长的上下文窗口</strong>。</p>

<p>一个在单项得分上略低但能稳定处理 200K token 上下文的模型，在跨项目重构场景中的实际体验，往往优于一个单项得分更高但上下文窗口受限的模型。原因在于：多项目重构的决策不是孤立的——修改包 A 的接口时，需要同时理解它在包 B、C、D 中的调用模式。只有能一次性消化这些关联代码，才能做出不”短视”的方案。</p>

<p>这涉及到 LLM 的一项基础能力——<strong>长上下文中的”注意力持久性”</strong>。相关研究表明，模型在长上下文中的信息检索和推理能力会随位置衰减<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>。稳定的大上下文窗口意味着这种衰减被控制在了可接受的范围内，使得跨文件的语义理解成为可能。</p>

<p>所以更准确的表述是：<strong>模型能力决定上限，上下文窗口决定这个上限在真实工程中能被兑现多少。</strong></p>

<h2 id="洞见二plan-的核心价值是理清人的思路">洞见二：Plan 的核心价值是理清人的思路</h2>

<p>在 AI 辅助编程的讨论中，”Plan”经常被理解成”让 AI 按步骤执行”的手段。但我逐渐发现，Plan 的最大受益者不是 AI，而是<strong>人自己</strong>。</p>

<p>原因有两点：</p>

<p><strong>第一，Plan 是外挂工作记忆。</strong> 复杂任务涉及大量上下文——文件依赖关系、接口契约、状态迁移路径。把这些结构化成 Plan 写在 prompt 里，相当于给思考过程加了一个”外挂存储区”。这与人脑的工作记忆限制有关——我们同时能追踪的信息量是有限的<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>，Plan 把这个限制放大了。</p>

<p><strong>第二，Plan 是人和 AI 之间唯一的可追溯沟通证据。</strong> 在长对话中，AI 的行为可能因为上下文压缩、模型更新等因素产生漂移。一份结构化的 Plan 是判断”AI 是否偏离了原定方向”的锚点。当 AI 生成的代码和预期不符时，不是凭感觉重来，而是对照 Plan 指出”你在第 3 步偏离了方向”。这让调试协作过程变得可操作。</p>

<p>这个概念和 Chain-of-Thought Prompting<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup> 有相似之处——显式的推理步骤能显著降低模型在长上下文中的错误率。但 Plan 在工程中的意义不止于此：它从”推理工具”变成了 <strong>“通信协议”</strong> ，让人和 AI 共享一个可参照的路线图。</p>

<h2 id="洞见三monorepo-让-ai-跨项目整体提交成为可能">洞见三：Monorepo 让 AI 跨项目整体提交成为可能</h2>

<p>这是实践中沉淀出的一个很具体的观察：<strong>单仓库（Monorepo）结构天然适合 AI 主导的跨项目修改。</strong></p>

<p>Google 在 2016 年发表的论文中详细阐述了 Monorepo 的优势——统一的依赖管理、原子化的跨项目变更、简化的分支策略<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>。这些优势在传统开发中体现为”减少沟通成本”，在 AI 辅助开发中则体现为<strong>“让 AI 能看见全局”</strong>。</p>

<p>举个例子：当你修改一个共享库的接口时，在 Monorepo 中，AI 可以同时看到：</p>
<ul>
  <li>这个接口的定义（共享库中的源码）</li>
  <li>所有调用方的使用模式（同一仓库中的其他项目）</li>
  <li>对应的测试代码如何 mock 这个接口</li>
</ul>

<p>然后 AI 可以一次性完成：修改接口定义 → 更新所有调用方 → 调整测试用例 → 验证编译通过。这个完整的原子提交在 Monorepo 中是一个自然的操作，在多仓库（Polyrepo）中则需要跨仓库协调，AI 目前难以胜任。</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant Human as 开发者
    participant AI as AI Agent
    participant Repo as Monorepo

    Human-&gt;&gt;AI: 重构共享库接口
    AI-&gt;&gt;Repo: 读取接口定义（包 A）
    AI-&gt;&gt;Repo: 扫描所有调用方（包 B、C、D）
    AI-&gt;&gt;Repo: 读取对应测试代码
    AI-&gt;&gt;AI: 制定修改方案
    AI-&gt;&gt;Repo: 修改接口定义
    AI-&gt;&gt;Repo: 更新包 B 调用
    AI-&gt;&gt;Repo: 更新包 C 调用
    AI-&gt;&gt;Repo: 更新包 D 调用
    AI-&gt;&gt;Repo: 调整测试用例
    AI-&gt;&gt;Repo: 编译验证
    AI--&gt;&gt;Human: 提交完整变更
</code></pre>

<p>这个模式在多仓库环境下很难复制——AI 需要同时打开多个仓库、理解它们的依赖关系、协调跨仓库的提交顺序，目前的主流工具链对此支持有限。</p>

<h2 id="洞见四tao-模式的关键在于可验证的闭环">洞见四：TAO 模式的关键在于可验证的闭环</h2>

<p>“TAO”指的是 <strong>Thought（推理）→ Action（执行）→ Observation（反馈观察）</strong> 的结构化提示范式。它直接源于 ReAct（Reasoning + Acting）模式<sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>——让 AI 不是一次性输出最终结果，而是在”推理 → 行动 → 观察结果”的循环中逐步逼近目标。其中 Thought（推理步骤）是 ReAct 论文中的标准术语，在工程实践中常具体化为 Task Decomposition（任务分解），但其概念源头是 Thought。</p>

<p>但在工程实践中，TAO 的关键不在于 T 和 A，而在于 <strong>O——Observation（观察）</strong>。</p>

<pre><code class="language-mermaid">graph LR
    T["Thought&lt;br/&gt;推理"] --&gt; A["Action&lt;br/&gt;执行"]
    A --&gt; O["Observation&lt;br/&gt;观察反馈"]
    O --&gt;|"未达到预期"| T
    O --&gt;|"达到预期"| D["Done&lt;br/&gt;完成"]

    style T fill:#87CEEB
    style A fill:#90EE90
    style O fill:#FFD700
    style D fill:#90EE90
</code></pre>

<p>为什么 O 是最关键的？因为没有 Observation 的闭环，TAO 就退化成了”让 AI 一步一步做”的简单链式调用，和普通的 prompt 没有本质区别。TAO 的真正价值在于让 AI <strong>每一步的产出都被检验</strong>——检验结果反馈到下一步的推理中，形成一个可验证的闭环。</p>

<p>这和软件工程中的闭环反馈原则是一致的：每次修改都应该有对应的验证手段（测试、lint、类型检查），验证结果决定下一步方向。TAO 只是把这个原则应用到了和 AI 的协作过程中。</p>

<p>在单人高效模式下，这个闭环可以非常紧凑：AI 执行 → 人肉眼验证 → 反馈修正 → AI 再执行。但在多人场景下，这个闭环需要被编码为更正式的流程——代码评审、CI 门禁、契约检查——每一个都是 Observation 环节的工程化实现。</p>

<h4 id="observation-环节的数据基础设施">Observation 环节的数据基础设施</h4>

<p>要让 Observation 真正可操作，需要建设一套覆盖 AI <strong>全链路</strong>的数据基础设施。仅仅依赖”看 AI 的输出”是不够的——因为很多错误的根因不在最终输出，而在中间某个环节的状态。具体来说，需要关注以下数据源：</p>

<ul>
  <li><strong>日志数据</strong>：AI 的每次输入输出、每次工具调用、每次决策路径都需要可追溯的结构化日志。这是最基础的观测手段——没有日志，无法定位 AI 行为异常的原因。这在之前的文章中曾作为”埋点驱动迭代”的核心组件展开过<sup id="fnref:7a"><a href="#fn:7a" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>。</li>
  <li><strong>数据库状态</strong>：AI 执行的变更会体现在数据库状态中。当 AI 操作涉及数据迁移、批处理任务或状态变更时，能查询和对比执行前后的数据库状态是定位问题的关键手段。</li>
  <li><strong>1:1 可复刻的测试环境</strong>：AI 行为与运行环境强相关——同一个 prompt 在不同依赖版本、不同数据分布下的输出可能不同。拥有一个可随时重建、与生产环境配置一致的测试环境，是隔离 AI 行为变量的前提。否则你无法判断”这次输出不对”是因为 prompt 有问题，还是因为环境中的数据状态与上次不同。</li>
  <li><strong>页面层的多模态可观测性</strong>：当 AI 的操作涉及用户界面时（无论是生成前端代码、模拟用户操作、还是验证 UI 行为），传统日志无法覆盖”视觉语义”层面的信息。需要组合使用 Playwright 等工具采集 UI 截图、通过 OCR 提取界面文本内容、再借助多模态大模型理解页面状态。这一层观测正在成为 AI 协作中不可或缺的一环——它能捕捉到”按钮没渲染出来”、”布局错位”等日志完全无法反映的问题。</li>
</ul>

<h4 id="从-tao-到-harness-engineering">从 TAO 到 Harness Engineering</h4>

<p>当 Observation 环节从”人眼看一下”升级为上述这套数据基础设施时，TAO 模式就超越了 prompt 技巧的范畴，进入了<strong>工程化</strong>的层面。我把建设这套基础设施的实践称为 <strong>Harness Engineering</strong>——它关注的不是”怎么写 prompt”，而是”如何构建支撑 AI 稳定运行的工程体系”。</p>

<p>这套体系的关键组件包括：</p>

<ul>
  <li><strong>可观测性管道</strong>：采集 AI 全链路的日志、状态、产出，形成可检索、可回溯的观测数据湖</li>
  <li><strong>可复现环境</strong>：保证 AI 的每次执行都在一致的上下文和数据状态下进行，行为偏差可归因</li>
  <li><strong>门禁系统</strong>：在 AI 的每个产出节点设置自动检查（类型校验、契约验证、测试覆盖）</li>
  <li><strong>回滚与补偿机制</strong>：当 AI 行为不符合预期时，能快速回滚并补偿受影响的下游</li>
</ul>

<p>Harness Engineering 的核心思想是：<strong>不要依赖 AI 的可靠性，而要通过工程手段把 AI 的不可靠性控制在可接受的范围内。</strong> 这和传统软件工程中防御式编程的逻辑相通——你不能假设输入永远合法，所以用类型系统、契约测试、降级策略来兜底。Harness Engineering 就是对 AI 的”防御式工程”。</p>

<p>这套思路也与之前文章中讨论的”LLM 工程化的自举迭代框架”<sup id="fnref:7b"><a href="#fn:7b" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>一脉相承——两者都强调用系统能力而非人工干预来管理 AI 行为的不确定性。</p>

<h2 id="洞见五ai-拉平了技术壁垒但拉不平决策与责任">洞见五：AI 拉平了技术壁垒，但拉不平决策与责任</h2>

<p>AI 确实在拉平一些长期以来区分小团队和大团队的壁垒：</p>

<ol>
  <li><strong>知识壁垒被拉平了</strong>——以前大团队才养得起专门研究 Monorepo、形式化验证、性能分析等领域的专家，现在小团队一个人 + AI 就能达到相当的高度</li>
  <li><strong>执行力差距被拉平了</strong>——大团队靠人多并行推进，但 AI 可以让小团队的单人具备”伪并行”能力（一个上午同时在多个方向上推进）</li>
  <li><strong>规范落地难度被拉平了</strong>——大团队靠制度强制统一风格，小团队靠 AI 自动遵循规范，甚至比人更一致</li>
</ol>

<p>但”拉平”不等于”消失”。拉平的是技术实现层面的壁垒，拉不平的是三个更本质的东西：</p>

<table>
  <thead>
    <tr>
      <th>维度</th>
      <th>被拉平</th>
      <th>没被拉平</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>知识</td>
      <td>获取技术知识的速度</td>
      <td>判断”该学什么、该信什么”</td>
    </tr>
    <tr>
      <td>执行</td>
      <td>编码实现的效率</td>
      <td>决定”功能做不做、何时上线”</td>
    </tr>
    <tr>
      <td>规范</td>
      <td>代码风格的统一</td>
      <td>架构决策的责任归属</td>
    </tr>
    <tr>
      <td>质量</td>
      <td>局部代码的正确性</td>
      <td>长期维护成本和技术债务治理</td>
    </tr>
    <tr>
      <td>界面</td>
      <td>页面结构与组件实现</td>
      <td>视觉还原标准与交互细节的验收</td>
    </tr>
  </tbody>
</table>

<p><strong>技术壁垒的降低，反而让剩余竞争点——人的判断力——变得更加重要。</strong> AI 可以写代码，但”这个接口要不要这样设计”、”这个依赖该不该升级”、”这个功能现在做还是以后做”——这些决策依赖的是人对业务逻辑、系统边界和长期成本的理解，不是 prompt 技巧。</p>

<p>这个观察和 Frederick Brooks 在《人月神话》中的观点有跨越时空的呼应：软件工程的本质困难不是”写代码”，而是”理解要写什么”<sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">10</a></sup>。AI 降低了”写”的成本，但”理解”的成本仍然由人承担。</p>

<p>与此相关但容易被忽视的另一面是：<strong>具体落地的技术细节和实现仍然需要人去把控。</strong> AI 擅长生成”看起来正确”的代码，但边界条件、错误处理、并发安全、性能敏感路径——这些需要结合具体运行环境做判断的细节，往往是 AI 输出中最薄弱也最隐蔽的地方。一个模式正确的实现，可能因为遗漏了某个 NULL 检查、搞错了锁的粒度、或忽略了跨平台的文件路径差异，在生产环境中引发严重问题。这些细节不会在架构图和接口定义中体现，但恰恰是决定系统能否稳定运行的关键。人在这里的角色不是”写”这些细节，而是<strong>知道哪些细节需要被关注、在什么场景下需要特殊处理</strong>——这仍然是经验驱动的判断力，不属于 AI 能可靠替代的范畴。</p>

<p><strong>前端与用户界面是上述判断压力最集中的地带之一。</strong> 后端背景的开发者借助 AI 调整 UI 时，常出现一种反差：接口或业务逻辑的小幅改动往往几轮对话即可收敛，而一旦涉及布局、间距、响应式或视觉细节，同样的协作方式却可能消耗整天仍达不到预期——不是因为 prompt 不够精确，而是因为「看起来对不对」缺乏像编译错误那样可机器判定的反馈信号。个人项目里，界面与参考稿达到约七成视觉相似往往已是可接受的上限；前端人力被压缩的团队则更常明确退化为「AI 产出什么样就用什么样，能用即可」。这不是对质量的放弃，而是<strong>在 Observation 无法工程化闭环的前提下，对 UI craft 这道仍未被拉平的壁垒所做的理性妥协</strong>——也解释了洞见四中「页面层多模态可观测性」为何不是锦上添花，而是 UI 场景下 TAO 闭环能否成立的前置条件。</p>

<h2 id="洞见六ai-作为执行者与辅助者的模式切换">洞见六：AI 作为执行者与辅助者的模式切换</h2>

<p>这是所有认知中最根本的一个二分法。经过反复实践，我意识到 AI 和人协作存在两种截然不同的模式，它们对流程、信任和团队结构的要求完全不同：</p>

<table>
  <thead>
    <tr>
      <th>维度</th>
      <th>执行者模式（单人）</th>
      <th>辅助者模式（团队）</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>AI 角色</td>
      <td>自主规划、跨项目整体执行</td>
      <td>代码补全、生成单测、解释代码</td>
    </tr>
    <tr>
      <td>决策者</td>
      <td>AI 出方案，人确认方向</td>
      <td>人做所有决策</td>
    </tr>
    <tr>
      <td>提交粒度</td>
      <td>跨包原子的整体提交</td>
      <td>按模块拆解的小粒度 MR</td>
    </tr>
    <tr>
      <td>信任模型</td>
      <td>人对 AI 的输出全盘审查</td>
      <td>团队逐条归因、可追溯</td>
    </tr>
    <tr>
      <td>适用团队</td>
      <td>≤5 人，高度信任</td>
      <td>大团队，传统流程</td>
    </tr>
    <tr>
      <td>协作协议</td>
      <td>Plan + TAO 闭环</td>
      <td>规范文档 + 人工评审</td>
    </tr>
  </tbody>
</table>

<p>这两种模式对技术能力、流程、信任机制的要求不仅不同，甚至<strong>互斥</strong>——在执行者模式下建立的自动化流程，在辅助者模式下会变成噪音和负担。</p>

<p>团队规模的约束不是偶然的。Robin Dunbar 在 1992 年的研究中提出了一个认知上限：人类的社交网络规模受限于新皮层容量，稳定协作的群体层级从核心信任圈（约 5 人）到氏族（约 50 人）再到熟人网络（约 150 人）<sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">11</a></sup>。软件团队中的”认知开销”同样存在规模效应——当团队超过 5-8 人，成员之间需要同步的协作协议、依赖关系和变更影响信息量就会超出个人能有效追踪的范围。</p>

<pre><code class="language-mermaid">graph TD
    subgraph "执行者模式（≤5人）"
        A1["人定方向"] --&gt; A2["AI 自主执行"]
        A2 --&gt; A3["人审查结果"]
        A3 --&gt; A1
    end

    subgraph "辅助者模式（大团队）"
        B1["人定方向"] --&gt; B2["人写需求/设计"]
        B2 --&gt; B3["AI 局部辅助"]
        B3 --&gt; B4["人工评审/集成"]
        B4 --&gt; B1
    end

    style A1 fill:#90EE90
    style A2 fill:#87CEEB
    style A3 fill:#FFD700
    style B1 fill:#90EE90
    style B2 fill:#FFD700
    style B3 fill:#87CEEB
    style B4 fill:#FFD700
</code></pre>

<p>这两种模式之间的切换成本，是很多关于 AI 生产力的讨论中被忽略的因素。单人场景下的高效模式（AI 接管、整体提交）一旦进入多人场景，就必须切换回更传统的辅助模式，而之前建立的那套自动化流程并不能平滑迁移。</p>

<h2 id="没有银弹认知闭环的终点">没有银弹：认知闭环的终点</h2>

<p>Fred Brooks 在 1986 年的经典文章《No Silver Bullet》中提出，软件工程中没有能够”在一个数量级上提升生产率”的单一技术突破<sup id="fnref:10"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">12</a></sup>。这个判断在近四十年后被重新审视——LLM 带来的变化可能确实超出了 Brooks 当年的预测框架。但 Brooks 的另一句话在今天依然成立：<strong>软件的本质困难在于”概念结构”的复杂性，而不是”实现”的复杂性。</strong></p>

<p>AI 解决了实现层面的复杂性，但概念结构的复杂性——需要做什么、为什么这么做、什么时候不做——仍然需要人的判断。</p>

<p>这六个洞见最终指向同一个结论：<strong>AI 不会消除软件工程的困难，它只是把这些困难重新分配到不同的位置。</strong> 知识壁垒被拉平了，决策质量的门槛变高了；写代码变快了，但搞清楚”该写什么”变得更关键了。人和 AI 之间没有最优的协作模式，只有最适合当前场景的协作模式。</p>

<p>没有银弹，但有清晰的认知框架——这就够了。</p>

<hr />

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>一年多的实践总结：参见《One Year of AI-Assisted Programming: Insights, Practices, and Reflections》，2026-01-31。记录了从 AI 工具使用者到深度融入日常开发工作流的认知演化。 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>角色转变的框架性论述：参见《AI Reshaping Software Development Workflow: From Code Writer to AI Conductor》，2026-02-10。提出了从”代码写手”到”AI 指挥”的角色转换模型。 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p>长上下文中注意力衰减的研究：Liu, N. F. et al., <em>Lost in the Middle: How Language Models Use Long Contexts</em>, arXiv:2307.03172, 2023。研究发现模型在长上下文中的信息检索能力随信息位置而显著变化——位于中间位置的信息被有效利用的概率最低。https://arxiv.org/abs/2307.03172 <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p>工作记忆容量的经典研究：Miller, G. A., <em>The Magical Number Seven, Plus or Minus Two: Some Limits on Our Capacity for Processing Information</em>, Psychological Review, 63(2): 81–97, 1956。提出了人类工作记忆同时处理信息量的有限性，是理解”Plan 作为外挂存储”重要性的认知心理学基础。https://doi.org/10.1037/h0043158 <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5">
      <p>Chain-of-Thought Prompting：Wei, J. et al., <em>Chain-of-Thought Prompting Elicits Reasoning in Large Language Models</em>, NeurIPS 2022。提出在 prompt 中加入显式的推理步骤链能显著提升 LLM 在复杂推理任务上的表现。https://arxiv.org/abs/2201.11903 <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6">
      <p>Google 的 Monorepo 实践：Potvin, R. &amp; Levenberg, J., <em>Why Google Stores Billions of Lines of Code in a Single Repository</em>, Communications of the ACM, 59(7): 78–87, 2016。详细阐述了 Google 在单一仓库中管理数十亿行代码的经验、工具链和工程实践。https://doi.org/10.1145/2854146 <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7">
      <p>ReAct 模式：Yao, S. et al., <em>ReAct: Synergizing Reasoning and Acting in Language Models</em>, ICLR 2023。提出在 LLM 中交错进行推理轨迹（Reasoning Traces）和动作步骤（Actions），让模型在”Thought → Action → Observation”的循环中与环境交互。TAO 模式直接源于此——Thought（推理步骤）是该论文中的标准术语，Observation 则构成闭环中的验证环节。https://arxiv.org/abs/2210.03629 <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7a">
      <p>埋点数据驱动迭代的工程实践：参见《大模型工程化的自举迭代框架：从 Demo 到可持续生产系统的五层实践》，2026-05-04。其中的”埋点驱动迭代”和”强类型驯服 LLM 不确定性”两节详细讨论了全链路日志采集和 schema 校验的工程方案。 <a href="#fnref:7a" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7b">
      <p>自举迭代框架的整体论述：同上。该文提出的五层实践（流程固化、tmux 运行时、Compact 卡点解法、埋点驱动迭代、强类型驯服 LLM）构成了 Harness Engineering 在当前阶段的工程原型。 <a href="#fnref:7b" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8">
      <p>Brooks, F. P., <em>The Mythical Man-Month: Essays on Software Engineering</em>, Addison-Wesley, 1975（第二版 1995）。提出了 Brooks’ Law：”向一个已经延期的项目增加人手，只会让它更延期。” 本文引用的是其中关于软件工程本质困难（essential complexity）和附带困难（accidental complexity）区分的论述。 <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:9">
      <p>Dunbar, R. I. M., <em>Neocortex size as a constraint on group size in primates</em>, Journal of Human Evolution, 22(6): 469–493, 1992。提出了著名”邓巴数”——灵长类动物的新皮层体积限制了其社交网络规模，对人类而言这个上限约为 150 人。稳定协作的小团体（如狩猎采集群体）通常在 30-50 人，核心信任圈约 5-15 人。 <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:10">
      <p>Brooks, F. P., <em>No Silver Bullet: Essence and Accidents of Software Engineering</em>, IEEE Computer, 20(4): 10–19, 1987。提出了软件工程中”本质困难”（概念结构、规格、设计的复杂性）和”附带困难”（编程语言、工具、平台的限制）的区分，并断言没有单一技术能在一个数量级上提升软件开发的生产率。 <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="ai" /><summary type="html"><![CDATA[总结 AI 辅助编程一年实践中的六个核心洞见，覆盖工作流、代码质量与团队协作变化。]]></summary></entry><entry><title type="html">Linux hrtimer 纳秒精度从何而来：从 TSC 到红黑树的完整链路</title><link href="https://weinan.tech/2026/05/16/hrtimer-nanosecond-precision-analysis.html" rel="alternate" type="text/html" title="Linux hrtimer 纳秒精度从何而来：从 TSC 到红黑树的完整链路" /><published>2026-05-16T00:00:00+08:00</published><updated>2026-05-16T00:00:00+08:00</updated><id>https://weinan.tech/2026/05/16/hrtimer-nanosecond-precision-analysis</id><content type="html" xml:base="https://weinan.tech/2026/05/16/hrtimer-nanosecond-precision-analysis.html"><![CDATA[<style>
/* Mermaid diagram container */
.mermaid-container {
  position: relative;
  display: block;
  cursor: pointer;
  transition: opacity 0.2s;
  max-width: 100%;
  margin: 20px 0;
  overflow-x: auto;
}

.mermaid-container:hover {
  opacity: 0.8;
}

.mermaid-container svg {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Modal overlay */
.mermaid-modal {
  display: none;
  position: fixed;
  z-index: 9999;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9);
  animation: fadeIn 0.3s;
}

.mermaid-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Modal content */
.mermaid-modal-content {
  position: relative;
  width: 90vw;
  height: 90vh;
  overflow: hidden;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
}

.mermaid-modal-diagram {
  transform-origin: center center;
  transition: transform 0.2s ease;
  display: inline-block;
  min-width: 100%;
  cursor: grab;
  user-select: none;
}

.mermaid-modal-diagram.dragging {
  cursor: grabbing;
  transition: none;
}

.mermaid-modal-diagram svg {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;
}

/* Control buttons */
.mermaid-controls {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
  z-index: 10000;
}

.mermaid-btn {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
  color: #333;
  font-weight: 500;
}

.mermaid-btn:hover {
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Close button */
.mermaid-close {
  background: #f44336;
  color: white;
  border: none;
}

.mermaid-close:hover {
  background: #d32f2f;
}

/* Zoom indicator */
.mermaid-zoom-level {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 10000;
}
</style>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  mermaid.initialize({
    startOnLoad: false,
    theme: 'default',
    securityLevel: 'loose',
    htmlLabels: true,
    themeVariables: {
      fontSize: '14px'
    }
  });

  let currentZoom = 1;
  let currentModal = null;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let translateX = 0;
  let translateY = 0;

  // Create modal HTML
  function createModal() {
    const modal = document.createElement('div');
    modal.className = 'mermaid-modal';
    modal.innerHTML = `
      <div class="mermaid-controls">
        <button class="mermaid-btn zoom-in">放大 +</button>
        <button class="mermaid-btn zoom-out">缩小 -</button>
        <button class="mermaid-btn zoom-reset">重置</button>
        <button class="mermaid-btn mermaid-close">关闭 ✕</button>
      </div>
      <div class="mermaid-modal-content">
        <div class="mermaid-modal-diagram"></div>
      </div>
      <div class="mermaid-zoom-level">100%</div>
    `;
    document.body.appendChild(modal);
    return modal;
  }

  // Show modal with diagram
  function showModal(diagramContent) {
    if (!currentModal) {
      currentModal = createModal();
      setupModalEvents();
    }

    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.innerHTML = diagramContent;

    // Remove any width/height attributes from SVG to make it responsive
    const svg = modalDiagram.querySelector('svg');
    if (svg) {
      svg.removeAttribute('width');
      svg.removeAttribute('height');
      svg.style.width = '100%';
      svg.style.height = 'auto';
    }

    // Setup drag functionality
    setupDrag(modalDiagram);

    currentModal.classList.add('active');
    currentZoom = 1;
    resetPosition();
    updateZoom();
    document.body.style.overflow = 'hidden';
  }

  // Hide modal
  function hideModal() {
    if (currentModal) {
      currentModal.classList.remove('active');
      document.body.style.overflow = '';
    }
  }

  // Update zoom level
  function updateZoom() {
    if (!currentModal) return;
    const diagram = currentModal.querySelector('.mermaid-modal-diagram');
    const zoomLevel = currentModal.querySelector('.mermaid-zoom-level');
    diagram.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentZoom})`;
    zoomLevel.textContent = `${Math.round(currentZoom * 100)}%`;
  }

  // Reset position when zoom changes
  function resetPosition() {
    translateX = 0;
    translateY = 0;
  }

  // Setup drag functionality
  function setupDrag(element) {
    element.addEventListener('mousedown', startDrag);
    element.addEventListener('touchstart', startDrag);
  }

  function startDrag(e) {
    if (e.type === 'mousedown' && e.button !== 0) return; // Only left click

    isDragging = true;
    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.classList.add('dragging');

    if (e.type === 'touchstart') {
      startX = e.touches[0].clientX - translateX;
      startY = e.touches[0].clientY - translateY;
    } else {
      startX = e.clientX - translateX;
      startY = e.clientY - translateY;
    }

    document.addEventListener('mousemove', drag);
    document.addEventListener('touchmove', drag);
    document.addEventListener('mouseup', stopDrag);
    document.addEventListener('touchend', stopDrag);
  }

  function drag(e) {
    if (!isDragging) return;
    e.preventDefault();

    if (e.type === 'touchmove') {
      translateX = e.touches[0].clientX - startX;
      translateY = e.touches[0].clientY - startY;
    } else {
      translateX = e.clientX - startX;
      translateY = e.clientY - startY;
    }

    updateZoom();
  }

  function stopDrag() {
    isDragging = false;
    const modalDiagram = currentModal?.querySelector('.mermaid-modal-diagram');
    if (modalDiagram) {
      modalDiagram.classList.remove('dragging');
    }
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('touchmove', drag);
    document.removeEventListener('mouseup', stopDrag);
    document.removeEventListener('touchend', stopDrag);
  }

  // Setup modal event listeners
  function setupModalEvents() {
    if (!currentModal) return;

    // Close button
    currentModal.querySelector('.mermaid-close').addEventListener('click', hideModal);

    // Zoom buttons
    currentModal.querySelector('.zoom-in').addEventListener('click', () => {
      currentZoom = Math.min(currentZoom + 0.25, 3);
      updateZoom();
    });

    currentModal.querySelector('.zoom-out').addEventListener('click', () => {
      currentZoom = Math.max(currentZoom - 0.25, 0.5);
      updateZoom();
    });

    currentModal.querySelector('.zoom-reset').addEventListener('click', () => {
      currentZoom = 1;
      resetPosition();
      updateZoom();
    });

    // Close on background click
    currentModal.addEventListener('click', (e) => {
      if (e.target === currentModal) {
        hideModal();
      }
    });

    // Close on ESC key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && currentModal.classList.contains('active')) {
        hideModal();
      }
    });
  }

  // Convert Jekyll-rendered code blocks to mermaid divs
  document.addEventListener('DOMContentLoaded', async function() {
    const codeBlocks = document.querySelectorAll('code.language-mermaid');

    for (const codeBlock of codeBlocks) {
      const pre = codeBlock.parentElement;
      const container = document.createElement('div');
      container.className = 'mermaid-container';

      const mermaidDiv = document.createElement('div');
      mermaidDiv.className = 'mermaid';
      mermaidDiv.textContent = codeBlock.textContent;

      container.appendChild(mermaidDiv);
      pre.replaceWith(container);
    }

    // Render all mermaid diagrams
    try {
      await mermaid.run({
        querySelector: '.mermaid'
      });
      console.log('Mermaid diagrams rendered successfully');
    } catch (error) {
      console.error('Mermaid rendering error:', error);
    }

    // Add click handlers to rendered diagrams
    document.querySelectorAll('.mermaid-container').forEach((container, index) => {
      // Find the rendered SVG inside the container
      const svg = container.querySelector('svg');
      if (!svg) {
        console.warn(`No SVG found in container ${index}`);
        return;
      }

      // Make the container clickable
      container.style.cursor = 'pointer';
      container.title = '点击查看大图';

      container.addEventListener('click', function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Clone the SVG for the modal
        const svgClone = svg.cloneNode(true);
        const tempDiv = document.createElement('div');
        tempDiv.appendChild(svgClone);

        console.log('Opening modal for diagram', index);
        showModal(tempDiv.innerHTML);
      });

      console.log(`Click handler added to diagram ${index}`);
    });
  });
</script>

<blockquote>
  <p>在前面的 tokio 系列文章中，我们提到了 Linux 内核的 hrtimer 能为 <code class="language-plaintext highlighter-rouge">epoll_wait(timeout)</code> 提供纳秒精度的定时中断。这个说法引出了一个追问：<strong>内核里的 hrtimer 如何实现纳秒级别的精度？</strong></p>
</blockquote>

<h2 id="引言">引言</h2>

<p>“纳秒精度”这个表述很容易被误解。说 hrtimer 精度纳秒，到底是什么意思？</p>

<p>一个 <code class="language-plaintext highlighter-rouge">nanosleep(&amp;ts)</code> 系统调用，传入的 <code class="language-plaintext highlighter-rouge">timespec</code> 确实是以纳秒为单位的。但内核真的能保证在恰好那个纳秒点唤醒你的进程吗？</p>

<p>答案是否定的。但”纳秒精度”也不是一个营销话术——它是内核在数据结构、时钟硬件和中断机制三层协作下，确实能达到的能力。理解这个协作过程，就能同时理解精度的来源和它的边界。</p>

<h2 id="概念先行精度来自三层协作">概念先行：精度来自三层协作</h2>

<p>hrtimer 的纳秒精度是三层协作的结果：</p>

<ol>
  <li><strong>数据结构层</strong>：hrtimer 的到期时间用 <code class="language-plaintext highlighter-rouge">ktime_t</code>（纳秒）表示，用红黑树（Red-Black Tree）按到期时间排序——这是”纳秒”作为数据精度的入口</li>
  <li><strong>时钟源层</strong>：CPU 内部的 TSC（Time Stamp Counter）在每个 cycle 递增——现代 CPU 上相邻两次递增的时间间隔在 <strong>0.3-0.5ns</strong> 之间，这是”纳秒”在硬件上能被读取到的来源</li>
  <li><strong>中断触发层</strong>：LAPIC 的 TSC Deadline 模式允许内核写入一个<strong>绝对 TSC 值</strong>，硬件在该 TSC 值到达时直接触发中断——这是”纳秒”的硬实时出口</li>
</ol>

<p>把这三层串起来，就是 hrtimer 从用户注册到期时间到实际触发中断的完整链路：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>用户态: clock_nanosleep(&amp;ts)  // ts = 100ns
    ↓ 系统调用
内核态: ktime_t expires = timespec_to_ktime(ts)  // 纳秒存为 ktime_t
    ↓ 插入红黑树
       hrtimer 红黑树按 expires 排序
    ↓ 如果是当前最早到期
       hrtimer_reprogram() → tick_program_event(expires)
    ↓ 转到时钟事件设备
       clockevents_program_event(dev, expires, force)
    ↓ TSC Deadline 模式
       lapic_next_deadline():
         tsc = rdtsc()
         native_wrmsrq(MSR_IA32_TSC_DEADLINE, tsc + delta)
    ↓ 硬件层
       LAPIC 监视 TSC 寄存器，到达指定值时触发中断
</code></pre></div></div>

<p>从这条链路可以看到，”纳秒精度”是数据结构精度（<code class="language-plaintext highlighter-rouge">ktime_t</code>）、硬件读时精度（TSC）、和中断编程精度（TSC Deadline）三者叠加的结果。下面逐层展开。</p>

<h2 id="一数据结构层hrtimer-的红黑树">一、数据结构层：hrtimer 的红黑树</h2>

<h3 id="11-ktime_t纳秒级的时间表示">1.1 ktime_t：纳秒级的时间表示</h3>

<p>hrtimer 的核心数据结构没有使用内核传统的 <code class="language-plaintext highlighter-rouge">jiffies</code>（基于定时器中断周期，通常是 1ms/4ms），而是使用 <code class="language-plaintext highlighter-rouge">ktime_t</code>——一个专门设计的高精度时间类型<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// include/linux/ktime.h</span>
<span class="k">typedef</span> <span class="n">s64</span> <span class="n">ktime_t</span><span class="p">;</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">ktime_t</code> 就是一个 64 位有符号整数，单位是纳秒。取值范围从 <code class="language-plaintext highlighter-rouge">-292 年</code> 到 <code class="language-plaintext highlighter-rouge">+292 年</code>。所有 hrtimer 的到期时间、剩余时间、间隔值都以此为单位。</p>

<p>这也直接体现在 hrtimer 源码顶部的宏定义上<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// kernel/time/hrtimer.c: 61-62</span>
<span class="c1">// HIGH_RES_NSEC = 1 表示 hrtimer 在高精度模式下分辨率为 1ns</span>
<span class="cp">#define HIGH_RES_NSEC		1
</span></code></pre></div></div>

<p>对比传统 <code class="language-plaintext highlighter-rouge">timer_list</code>（基于 jiffies 的时间轮），hrtimer 的优势在于”到期时间”和”现在时间”的比较不需要经过 jiffies 这个中间层：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">方面</th>
      <th style="text-align: left">传统 timer_list</th>
      <th style="text-align: left">hrtimer</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>时间单位</strong></td>
      <td style="text-align: left">jiffies（~4ms @ 250Hz）</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ktime_t</code>（1ns）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>排序结构</strong></td>
      <td style="text-align: left">多级时间轮（O(1) 但粒度粗）</td>
      <td style="text-align: left">红黑树（O(log n) 但粒度细）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>中断编程</strong></td>
      <td style="text-align: left">等下一个 tick</td>
      <td style="text-align: left">精确到硬件允许的任意时刻</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>精度来源</strong></td>
      <td style="text-align: left">定时器中断的周期</td>
      <td style="text-align: left">TSC + 时钟事件设备</td>
    </tr>
  </tbody>
</table>

<h3 id="12-红黑树按到期时间排序">1.2 红黑树：按到期时间排序</h3>

<p>每个 CPU 有 8 个 <code class="language-plaintext highlighter-rouge">hrtimer_clock_base</code>，分别对应不同的时钟源（<code class="language-plaintext highlighter-rouge">CLOCK_MONOTONIC</code>、<code class="language-plaintext highlighter-rouge">CLOCK_REALTIME</code>、<code class="language-plaintext highlighter-rouge">CLOCK_BOOTTIME</code> 等，每个时钟分 hard 和 soft 两种模式）。每个 base 内部用一棵红黑树管理所有活跃的 hrtimer<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// include/linux/hrtimer_defs.h: 28-43</span>
<span class="k">struct</span> <span class="n">hrtimer_clock_base</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">hrtimer_cpu_base</span>    <span class="o">*</span><span class="n">cpu_base</span><span class="p">;</span>
    <span class="k">const</span> <span class="kt">unsigned</span> <span class="kt">int</span>         <span class="n">index</span><span class="p">;</span>
    <span class="k">const</span> <span class="n">clockid_t</span>            <span class="n">clockid</span><span class="p">;</span>
    <span class="n">seqcount_raw_spinlock_t</span>    <span class="n">seq</span><span class="p">;</span>
    <span class="n">ktime_t</span>                    <span class="n">expires_next</span><span class="p">;</span>   <span class="c1">// 此 base 最早到期时间</span>
    <span class="k">struct</span> <span class="n">hrtimer</span>             <span class="o">*</span><span class="n">running</span><span class="p">;</span>       <span class="c1">// 正在执行回调的 timer</span>
    <span class="k">struct</span> <span class="n">timerqueue_linked_head</span>  <span class="n">active</span><span class="p">;</span>     <span class="c1">// 红黑树根节点</span>
    <span class="n">ktime_t</span>                    <span class="n">offset</span><span class="p">;</span>         <span class="c1">// 相对于 MONOTONIC 的偏移</span>
<span class="p">};</span>
</code></pre></div></div>

<p>每个 CPU 的 8 个 clock base 打包在 <code class="language-plaintext highlighter-rouge">hrtimer_cpu_base</code> 中<sup id="fnref:3:1"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// include/linux/hrtimer_defs.h: 82-108</span>
<span class="k">struct</span> <span class="n">hrtimer_cpu_base</span> <span class="p">{</span>
    <span class="n">raw_spinlock_t</span>              <span class="n">lock</span><span class="p">;</span>
    <span class="kt">unsigned</span> <span class="kt">int</span>                <span class="n">cpu</span><span class="p">;</span>
    <span class="kt">unsigned</span> <span class="kt">int</span>                <span class="n">active_bases</span><span class="p">;</span>     <span class="c1">// 哪些 base 有活跃 timer</span>
    <span class="n">bool</span>                        <span class="n">hres_active</span><span class="p">;</span>      <span class="c1">// 高精度模式已启用</span>
    <span class="c1">// ...</span>
    <span class="n">ktime_t</span>                     <span class="n">expires_next</span><span class="p">;</span>     <span class="c1">// 本 CPU 最早到期时间</span>
    <span class="k">struct</span> <span class="n">hrtimer</span>              <span class="o">*</span><span class="n">next_timer</span><span class="p">;</span>      <span class="c1">// 最早到期的 timer 指针</span>
    <span class="k">struct</span> <span class="n">hrtimer_clock_base</span>   <span class="n">clock_base</span><span class="p">[</span><span class="n">HRTIMER_MAX_CLOCK_BASES</span><span class="p">];</span>  <span class="c1">// 8 个 base</span>
<span class="p">};</span>
</code></pre></div></div>

<p>当一个 <code class="language-plaintext highlighter-rouge">hrtimer</code> 被插入时，它被放入对应 clock base 的红黑树，按 <code class="language-plaintext highlighter-rouge">expires</code>（到期时间）排序。插入操作的时间复杂度是 O(log n)<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// kernel/time/hrtimer.c: 640-660</span>
<span class="k">static</span> <span class="n">bool</span> <span class="nf">enqueue_hrtimer</span><span class="p">(</span><span class="k">struct</span> <span class="n">hrtimer</span> <span class="o">*</span><span class="n">timer</span><span class="p">,</span> <span class="k">struct</span> <span class="n">hrtimer_clock_base</span> <span class="o">*</span><span class="n">base</span><span class="p">,</span>
                            <span class="k">enum</span> <span class="n">hrtimer_mode</span> <span class="n">mode</span><span class="p">,</span> <span class="n">bool</span> <span class="n">was_armed</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">base</span><span class="o">-&gt;</span><span class="n">cpu_base</span><span class="o">-&gt;</span><span class="n">active_bases</span> <span class="o">|=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="n">base</span><span class="o">-&gt;</span><span class="n">index</span><span class="p">;</span>
    <span class="n">WRITE_ONCE</span><span class="p">(</span><span class="n">timer</span><span class="o">-&gt;</span><span class="n">is_queued</span><span class="p">,</span> <span class="n">HRTIMER_STATE_ENQUEUED</span><span class="p">);</span>

    <span class="c1">// 如果新插入的 timer 是当前 base 中最左的（最早到期），返回 true</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">timerqueue_linked_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">base</span><span class="o">-&gt;</span><span class="n">active</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">timer</span><span class="o">-&gt;</span><span class="n">node</span><span class="p">))</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>

    <span class="n">base</span><span class="o">-&gt;</span><span class="n">expires_next</span> <span class="o">=</span> <span class="n">hrtimer_get_expires</span><span class="p">(</span><span class="n">timer</span><span class="p">);</span>
    <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">timerqueue_linked_add</code> 返回 true 表示新插入的节点成为了红黑树的最左节点——也就是最早到期的那个。这种情况下，内核需要重新编程时钟事件设备。</p>

<h3 id="13-什么时候编程硬件">1.3 什么时候编程硬件？</h3>

<p>从红黑树到硬件只差一步：每插入一个 timer，都检查它是不是当前最早的；如果是，重新编程时钟事件设备<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// kernel/time/hrtimer.c: 732-760</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">hrtimer_reprogram</span><span class="p">(</span><span class="k">struct</span> <span class="n">hrtimer</span> <span class="o">*</span><span class="n">timer</span><span class="p">,</span> <span class="n">bool</span> <span class="n">reprogram</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">struct</span> <span class="n">hrtimer_cpu_base</span> <span class="o">*</span><span class="n">cpu_base</span> <span class="o">=</span> <span class="n">this_cpu_ptr</span><span class="p">(</span><span class="o">&amp;</span><span class="n">hrtimer_bases</span><span class="p">);</span>
    <span class="n">ktime_t</span> <span class="n">expires</span> <span class="o">=</span> <span class="n">hrtimer_get_expires</span><span class="p">(</span><span class="n">timer</span><span class="p">);</span>
    <span class="n">expires</span> <span class="o">=</span> <span class="n">ktime_sub</span><span class="p">(</span><span class="n">expires</span><span class="p">,</span> <span class="n">base</span><span class="o">-&gt;</span><span class="n">offset</span><span class="p">);</span>
    <span class="c1">// ...</span>

    <span class="c1">// 如果到期时间在当前最早之后，什么都不做</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">expires</span> <span class="o">&gt;=</span> <span class="n">cpu_base</span><span class="o">-&gt;</span><span class="n">expires_next</span><span class="p">)</span>
        <span class="k">return</span><span class="p">;</span>

    <span class="n">cpu_base</span><span class="o">-&gt;</span><span class="n">next_timer</span> <span class="o">=</span> <span class="n">timer</span><span class="p">;</span>
    <span class="n">__hrtimer_reprogram</span><span class="p">(</span><span class="n">cpu_base</span><span class="p">,</span> <span class="n">timer</span><span class="p">,</span> <span class="n">expires</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">__hrtimer_reprogram</code> 最终调用 <code class="language-plaintext highlighter-rouge">tick_program_event(expires_next, 1)</code>，进入时钟事件设备层<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// kernel/time/hrtimer.c: 700-708</span>
<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">hrtimer_rearm_event</span><span class="p">(</span><span class="n">ktime_t</span> <span class="n">expires_next</span><span class="p">,</span> <span class="n">bool</span> <span class="n">deferred</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">trace_hrtimer_rearm</span><span class="p">(</span><span class="n">expires_next</span><span class="p">,</span> <span class="n">deferred</span><span class="p">);</span>
    <span class="n">tick_program_event</span><span class="p">(</span><span class="n">expires_next</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="14-中断到来后批量收割">1.4 中断到来后：批量收割</h3>

<p>当硬件中断到来时，<code class="language-plaintext highlighter-rouge">hrtimer_interrupt()</code> 被调用。它遍历红黑树，收割所有已到期的 timer，执行回调<sup id="fnref:2:1"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// kernel/time/hrtimer.c: 2083-2138</span>
<span class="kt">void</span> <span class="nf">hrtimer_interrupt</span><span class="p">(</span><span class="k">struct</span> <span class="n">clock_event_device</span> <span class="o">*</span><span class="n">dev</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">struct</span> <span class="n">hrtimer_cpu_base</span> <span class="o">*</span><span class="n">cpu_base</span> <span class="o">=</span> <span class="n">this_cpu_ptr</span><span class="p">(</span><span class="o">&amp;</span><span class="n">hrtimer_bases</span><span class="p">);</span>

    <span class="n">raw_spin_lock_irqsave</span><span class="p">(</span><span class="o">&amp;</span><span class="n">cpu_base</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span>
    <span class="n">entry_time</span> <span class="o">=</span> <span class="n">now</span> <span class="o">=</span> <span class="n">hrtimer_update_base</span><span class="p">(</span><span class="n">cpu_base</span><span class="p">);</span>

<span class="nl">retry:</span>
    <span class="c1">// 将最早到期时间设为 KTIME_MAX（阻止远程 CPU 插入新 timer）</span>
    <span class="n">cpu_base</span><span class="o">-&gt;</span><span class="n">expires_next</span> <span class="o">=</span> <span class="n">KTIME_MAX</span><span class="p">;</span>

    <span class="c1">// 收割所有到期的 hard hrtimer</span>
    <span class="n">__hrtimer_run_queues</span><span class="p">(</span><span class="n">cpu_base</span><span class="p">,</span> <span class="n">now</span><span class="p">,</span> <span class="n">flags</span><span class="p">,</span> <span class="n">HRTIMER_ACTIVE_HARD</span><span class="p">);</span>

    <span class="c1">// 重新计算下一个到期时间</span>
    <span class="n">now</span> <span class="o">=</span> <span class="n">hrtimer_update_base</span><span class="p">(</span><span class="n">cpu_base</span><span class="p">);</span>
    <span class="n">expires_next</span> <span class="o">=</span> <span class="n">hrtimer_update_next_event</span><span class="p">(</span><span class="n">cpu_base</span><span class="p">);</span>

    <span class="c1">// 如果还有 timer 已到期（更新 now 后发现的），重试</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">expires_next</span> <span class="o">&lt;</span> <span class="n">now</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">++</span><span class="n">retries</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">)</span>
            <span class="k">goto</span> <span class="n">retry</span><span class="p">;</span>
        <span class="c1">// 超过 3 次仍到期 → 判定为 hang，设定 max_hang_time</span>
        <span class="n">cpu_base</span><span class="o">-&gt;</span><span class="n">hang_detected</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// 重新编程硬件到下一个到期时间</span>
    <span class="n">hrtimer_interrupt_rearm</span><span class="p">(</span><span class="n">cpu_base</span><span class="p">,</span> <span class="n">expires_next</span><span class="p">);</span>
    <span class="n">raw_spin_unlock_irqrestore</span><span class="p">(</span><span class="o">&amp;</span><span class="n">cpu_base</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">__hrtimer_run_queues</code> 遍历每个有活跃 timer 的 clock base，从红黑树中取出第一个到期时间在 <code class="language-plaintext highlighter-rouge">basenow</code> 之前的 timer，执行其回调函数，然后取下下一个，直到遇到还没到期的为止<sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// kernel/time/hrtimer.c: 1968-1992</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">__hrtimer_run_queues</span><span class="p">(</span><span class="k">struct</span> <span class="n">hrtimer_cpu_base</span> <span class="o">*</span><span class="n">cpu_base</span><span class="p">,</span> <span class="n">ktime_t</span> <span class="n">now</span><span class="p">,</span>
                                 <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">flags</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">active_mask</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">active</span> <span class="o">=</span> <span class="n">cpu_base</span><span class="o">-&gt;</span><span class="n">active_bases</span> <span class="o">&amp;</span> <span class="n">active_mask</span><span class="p">;</span>

    <span class="n">for_each_active_base</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">cpu_base</span><span class="p">,</span> <span class="n">active</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">ktime_t</span> <span class="n">basenow</span> <span class="o">=</span> <span class="n">ktime_add</span><span class="p">(</span><span class="n">now</span><span class="p">,</span> <span class="n">base</span><span class="o">-&gt;</span><span class="n">offset</span><span class="p">);</span>
        <span class="k">struct</span> <span class="n">hrtimer</span> <span class="o">*</span><span class="n">timer</span><span class="p">;</span>

        <span class="k">while</span> <span class="p">((</span><span class="n">timer</span> <span class="o">=</span> <span class="n">clock_base_next_timer</span><span class="p">(</span><span class="n">base</span><span class="p">)))</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">basenow</span> <span class="o">&lt;</span> <span class="n">hrtimer_get_softexpires</span><span class="p">(</span><span class="n">timer</span><span class="p">))</span>
                <span class="k">break</span><span class="p">;</span>
            <span class="n">__run_hrtimer</span><span class="p">(</span><span class="n">cpu_base</span><span class="p">,</span> <span class="n">base</span><span class="p">,</span> <span class="n">timer</span><span class="p">,</span> <span class="n">basenow</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>遍历从 Red-Black Tree 取最左节点（最早到期），如果到期时间（<code class="language-plaintext highlighter-rouge">softexpires</code>）还在 <code class="language-plaintext highlighter-rouge">basenow</code> 之后则停止。注意这里用了 <code class="language-plaintext highlighter-rouge">softexpires</code>（soft expiration）而不是硬到期时间——这是 hrtimer 的”软到期”特性，允许 timer 在软到期后、硬到期前的任意时刻触发，目的是减少中断次数，将多个接近到期的 timer 合并为一次中断<sup id="fnref:4:1"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>。</p>

<h2 id="二硬件时钟源层tsc-如何提供纳秒级读数">二、硬件时钟源层：TSC 如何提供纳秒级读数</h2>

<p>数据结构层用 <code class="language-plaintext highlighter-rouge">ktime_t</code>（纳秒）做比较和存储。但内核从哪里拿到”当前纳秒时间”？答案是时钟源（clocksource）层。</p>

<h3 id="21-clocksource-抽象">2.1 clocksource 抽象</h3>

<p>内核有一个 <code class="language-plaintext highlighter-rouge">clocksource</code> 抽象层，每种计时硬件注册为一个 <code class="language-plaintext highlighter-rouge">clocksource</code> 实例，启动时自动选择精度最高的那个<sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// include/linux/clocksource.h: 35-135</span>
<span class="k">struct</span> <span class="n">clocksource</span> <span class="p">{</span>
    <span class="n">u64</span>     <span class="p">(</span><span class="o">*</span><span class="n">read</span><span class="p">)(</span><span class="k">struct</span> <span class="n">clocksource</span> <span class="o">*</span><span class="n">cs</span><span class="p">);</span>  <span class="c1">// 读取当前计数值</span>
    <span class="n">u64</span>     <span class="n">mask</span><span class="p">;</span>                              <span class="c1">// 计数值的位掩码</span>
    <span class="n">u32</span>     <span class="n">mult</span><span class="p">;</span>                              <span class="c1">// 计数值 → 纳秒 的乘数</span>
    <span class="n">u32</span>     <span class="n">shift</span><span class="p">;</span>                             <span class="c1">// 计数值 → 纳秒 的位移</span>
    <span class="n">u64</span>     <span class="n">max_idle_ns</span><span class="p">;</span>                       <span class="c1">// 最大无中断安全闲置时间</span>
    <span class="kt">int</span>     <span class="n">rating</span><span class="p">;</span>                            <span class="c1">// 精度评级（选源依据）</span>
    <span class="c1">// ...</span>
<span class="p">};</span>
</code></pre></div></div>

<p>每种平台的可用时钟源不同，内核按 <code class="language-plaintext highlighter-rouge">rating</code> 择优选取：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">时钟源</th>
      <th style="text-align: left">典型分辨率</th>
      <th style="text-align: left">评级 (rating)</th>
      <th style="text-align: left">平台</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">TSC</td>
      <td style="text-align: left">~0.3ns (按 GHz 频率)</td>
      <td style="text-align: left">250-300</td>
      <td style="text-align: left">x86</td>
    </tr>
    <tr>
      <td style="text-align: left">ARM Generic Timer</td>
      <td style="text-align: left">~0.5ns</td>
      <td style="text-align: left">250-300</td>
      <td style="text-align: left">ARM/ARM64</td>
    </tr>
    <tr>
      <td style="text-align: left">HPET</td>
      <td style="text-align: left">~100ns (10MHz)</td>
      <td style="text-align: left">50-100</td>
      <td style="text-align: left">x86（备选）</td>
    </tr>
    <tr>
      <td style="text-align: left">ACPI PM</td>
      <td style="text-align: left">~280ns (3.58MHz)</td>
      <td style="text-align: left">50</td>
      <td style="text-align: left">所有（fallback）</td>
    </tr>
    <tr>
      <td style="text-align: left">jiffies</td>
      <td style="text-align: left">4ms (250Hz)</td>
      <td style="text-align: left">1</td>
      <td style="text-align: left">最后回退</td>
    </tr>
  </tbody>
</table>

<p>现代 x86 平台上 TSC 是默认首选。一个 3GHz 的 CPU 上，TSC 每 0.33ns 递增一次。</p>

<h3 id="22-tsc-的演进为什么它可信">2.2 TSC 的演进：为什么它可信</h3>

<p>早期 TSC 有不稳定的问题（不同步、频率可变）。现代 CPU 上这些问题已经解决：</p>

<ul>
  <li><strong>Invariant TSC</strong>：所有内核的 TSC 在启动时同步，之后不受频率变化（P-state、C-state）影响</li>
  <li><strong>ARAT（Always Running APIC Timer）</strong>：即使 CPU 进入 C-state 休眠，TSC 仍然递增</li>
  <li><strong>TSC_ADJUST MSR</strong>：允许管理程序修正不同 CPU 核之间的 TSC 偏差</li>
</ul>

<h2 id="三中断触发层从-tsc-到硬件中断">三、中断触发层：从 TSC 到硬件中断</h2>

<p>有了 <code class="language-plaintext highlighter-rouge">ktime_t</code> 做数据结构、TSC 做纳秒级读数，最后一步是：”如何在恰好那个纳秒点触发中断？”</p>

<h3 id="31-时钟事件设备clock_event_device">3.1 时钟事件设备（clock_event_device）</h3>

<p>和 <code class="language-plaintext highlighter-rouge">clocksource</code>（只读，告诉内核”现在几点”）配对的是一套 <strong><code class="language-plaintext highlighter-rouge">clock_event_device</code></strong> 层（可写，告诉硬件”什么时候触发中断”）。每个 CPU 有一个本地时钟事件设备，在 x86 上就是 Local APIC Timer<sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// arch/x86/kernel/apic/apic.c: 495-509</span>
<span class="k">static</span> <span class="k">struct</span> <span class="n">clock_event_device</span> <span class="n">lapic_clockevent</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">.</span><span class="n">name</span>               <span class="o">=</span> <span class="s">"lapic"</span><span class="p">,</span>
    <span class="p">.</span><span class="n">features</span>           <span class="o">=</span> <span class="n">CLOCK_EVT_FEAT_PERIODIC</span> <span class="o">|</span> <span class="n">CLOCK_EVT_FEAT_ONESHOT</span><span class="p">,</span>
    <span class="p">.</span><span class="n">shift</span>              <span class="o">=</span> <span class="mi">32</span><span class="p">,</span>
    <span class="p">.</span><span class="n">set_state_shutdown</span> <span class="o">=</span> <span class="n">lapic_timer_shutdown</span><span class="p">,</span>
    <span class="p">.</span><span class="n">set_state_oneshot</span>  <span class="o">=</span> <span class="n">lapic_timer_set_oneshot</span><span class="p">,</span>
    <span class="p">.</span><span class="n">set_next_event</span>     <span class="o">=</span> <span class="n">lapic_next_event</span><span class="p">,</span>       <span class="c1">// 传统模式</span>
    <span class="p">.</span><span class="n">rating</span>             <span class="o">=</span> <span class="mi">100</span><span class="p">,</span>
<span class="p">};</span>
</code></pre></div></div>

<h3 id="32-传统模式-vs-tsc-deadline-模式">3.2 传统模式 vs TSC Deadline 模式</h3>

<p>这里有一个关键的区别，决定了 hrtimer 精度的登顶：</p>

<p><strong>传统 LAPIC 模式</strong>（<code class="language-plaintext highlighter-rouge">lapic_next_event</code>）<sup id="fnref:9:1"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// arch/x86/kernel/apic/apic.c: 415-419</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">lapic_next_event</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">delta</span><span class="p">,</span> <span class="k">struct</span> <span class="n">clock_event_device</span> <span class="o">*</span><span class="n">evt</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">apic_write</span><span class="p">(</span><span class="n">APIC_TMICT</span><span class="p">,</span> <span class="n">delta</span><span class="p">);</span>  <span class="c1">// 写入 cycle 数，递减到 0 时中断</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>向 LAPIC 的 <code class="language-plaintext highlighter-rouge">TMICT</code> 寄存器写入一个<strong>相对值</strong>，LAPIC 以总线时钟频率递减，到 0 时触发中断。这里有一个重要限制：写入的值需要换算成 APIC 总线周期，而 APIC 总线频率和 CPU 频率不同，存在换算误差。</p>

<p><strong>TSC Deadline 模式</strong>（<code class="language-plaintext highlighter-rouge">lapic_next_deadline</code>）<sup id="fnref:9:2"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// arch/x86/kernel/apic/apic.c: 421-428</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">lapic_next_deadline</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">delta</span><span class="p">,</span> <span class="k">struct</span> <span class="n">clock_event_device</span> <span class="o">*</span><span class="n">evt</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">u64</span> <span class="n">tsc</span> <span class="o">=</span> <span class="n">rdtsc</span><span class="p">();</span>       <span class="c1">// 读当前 TSC 值</span>
    <span class="n">native_wrmsrq</span><span class="p">(</span><span class="n">MSR_IA32_TSC_DEADLINE</span><span class="p">,</span> <span class="n">tsc</span> <span class="o">+</span> <span class="p">(((</span><span class="n">u64</span><span class="p">)</span> <span class="n">delta</span><span class="p">)</span> <span class="o">*</span> <span class="n">TSC_DIVISOR</span><span class="p">));</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>向 <code class="language-plaintext highlighter-rouge">MSR_IA32_TSC_DEADLINE</code> 写入一个<strong>绝对 TSC 值</strong>。LAPIC 内部监视 TSC 寄存器，当 TSC 到达该值时直接触发 <code class="language-plaintext highlighter-rouge">LVTT</code> 定时器中断。</p>

<p>TSC Deadline 模式的优势：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">方面</th>
      <th style="text-align: left">传统模式（TMICT）</th>
      <th style="text-align: left">TSC Deadline 模式</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>写入值</strong></td>
      <td style="text-align: left">相对值（递减计数）</td>
      <td style="text-align: left">绝对值（TSC 比较）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>换算</strong></td>
      <td style="text-align: left">需要从 ns → bus cycle</td>
      <td style="text-align: left">直接从 TSC 选未来值</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>精度</strong></td>
      <td style="text-align: left">受 APIC 总线频率限制</td>
      <td style="text-align: left">等同于 TSC 精度</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>累积误差</strong></td>
      <td style="text-align: left">逐次积累</td>
      <td style="text-align: left">无累积（每次写绝对值）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>需要功能</strong></td>
      <td style="text-align: left">通用 LAPIC</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">X86_FEATURE_TSC_DEADLINE_TIMER</code></td>
    </tr>
  </tbody>
</table>

<p>TSC Deadline 模式在 CPUID 中通过 <code class="language-plaintext highlighter-rouge">X86_FEATURE_TSC_DEADLINE_TIMER</code> 标识<sup id="fnref:10"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">10</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// arch/x86/include/asm/cpufeatures.h: 129</span>
<span class="cp">#define X86_FEATURE_TSC_DEADLINE_TIMER  ( 4*32+24) </span><span class="cm">/* "tsc_deadline_timer" */</span><span class="cp">
</span></code></pre></div></div>

<p>内核启用它时，把 LAPIC 时钟事件设备的 <code class="language-plaintext highlighter-rouge">set_next_event</code> 指针替换为 <code class="language-plaintext highlighter-rouge">lapic_next_deadline</code><sup id="fnref:9:3"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// arch/x86/kernel/apic/apic.c: 581-598</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">setup_APIC_timer</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// ...</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">this_cpu_has</span><span class="p">(</span><span class="n">X86_FEATURE_TSC_DEADLINE_TIMER</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">levt</span><span class="o">-&gt;</span><span class="n">name</span> <span class="o">=</span> <span class="s">"lapic-deadline"</span><span class="p">;</span>
        <span class="n">levt</span><span class="o">-&gt;</span><span class="n">features</span> <span class="o">|=</span> <span class="n">CLOCK_EVT_FEAT_CLOCKSOURCE_COUPLED</span><span class="p">;</span>
        <span class="n">levt</span><span class="o">-&gt;</span><span class="n">cs_id</span> <span class="o">=</span> <span class="n">CSID_X86_TSC</span><span class="p">;</span>
        <span class="n">levt</span><span class="o">-&gt;</span><span class="n">set_next_event</span> <span class="o">=</span> <span class="n">lapic_next_deadline</span><span class="p">;</span>  <span class="c1">// ← 关键替换</span>
        <span class="n">clockevents_config_and_register</span><span class="p">(</span>
            <span class="n">levt</span><span class="p">,</span> <span class="n">tsc_khz</span> <span class="o">*</span> <span class="p">(</span><span class="mi">1000</span> <span class="o">/</span> <span class="n">TSC_DIVISOR</span><span class="p">),</span> <span class="mh">0xF</span><span class="p">,</span> <span class="o">~</span><span class="mi">0UL</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">clockevents_register_device</span><span class="p">(</span><span class="n">levt</span><span class="p">);</span>  <span class="c1">// 传统模式</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">CLOCK_EVT_FEAT_CLOCKSOURCE_COUPLED</code> 表示这个时钟事件设备”绑定”到了 TSC 时钟源。<code class="language-plaintext highlighter-rouge">clockevents_program_event</code> 识别到这个标志后，走 <code class="language-plaintext highlighter-rouge">ktime_t</code> → TSC 的直接转换路径，跳过中间换算<sup id="fnref:11"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">11</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// kernel/time/clockevents.c: 360-380</span>
<span class="kt">int</span> <span class="nf">clockevents_program_event</span><span class="p">(</span><span class="k">struct</span> <span class="n">clock_event_device</span> <span class="o">*</span><span class="n">dev</span><span class="p">,</span> <span class="n">ktime_t</span> <span class="n">expires</span><span class="p">,</span> <span class="n">bool</span> <span class="n">force</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// ...</span>
    <span class="c1">// 如果设备支持 COUPLED 模式，走 ktime → TSC 直接路径</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">likely</span><span class="p">(</span><span class="n">clockevent_set_next_coupled</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">expires</span><span class="p">)))</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>

    <span class="c1">// 否则：传统路径，ktime → delta ns → 换算 → 写入</span>
    <span class="n">delta</span> <span class="o">=</span> <span class="n">ktime_to_ns</span><span class="p">(</span><span class="n">ktime_sub</span><span class="p">(</span><span class="n">expires</span><span class="p">,</span> <span class="n">ktime_get</span><span class="p">()));</span>
    <span class="n">cycles</span> <span class="o">=</span> <span class="p">((</span><span class="n">u64</span><span class="p">)</span><span class="n">delta</span> <span class="o">*</span> <span class="n">dev</span><span class="o">-&gt;</span><span class="n">mult</span><span class="p">)</span> <span class="o">&gt;&gt;</span> <span class="n">dev</span><span class="o">-&gt;</span><span class="n">shift</span><span class="p">;</span>
    <span class="n">dev</span><span class="o">-&gt;</span><span class="n">set_next_event</span><span class="p">((</span><span class="kt">unsigned</span> <span class="kt">long</span><span class="p">)</span> <span class="n">cycles</span><span class="p">,</span> <span class="n">dev</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>传统路径多了一步从 delta-ns 到设备 tick 的换算（mult/shift），可能引入误差。Coupled 路径直接使用 TSC 值，无换算。</p>

<p>当然，使用 <code class="language-plaintext highlighter-rouge">lapic_next_deadline</code> 也有注意事项：由于历史原因，一些 CPU 型号（如部分 Haswell/Broadwell/Skylake X）存在 TSC Deadline 的 bug，需要在特定 microcode 版本后才能启用。内核维护了一张 bug 型号表<sup id="fnref:9:4"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// arch/x86/kernel/apic/apic.c: 514-545</span>
<span class="k">static</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">x86_cpu_id</span> <span class="n">deadline_match</span><span class="p">[]</span> <span class="n">__initconst</span> <span class="o">=</span> <span class="p">{</span>
    <span class="n">X86_MATCH_VFM_STEPS</span><span class="p">(</span><span class="n">INTEL_HASWELL_X</span><span class="p">,</span>   <span class="mh">0x2</span><span class="p">,</span> <span class="mh">0x2</span><span class="p">,</span> <span class="mh">0x3a</span><span class="p">),</span>
    <span class="n">X86_MATCH_VFM_STEPS</span><span class="p">(</span><span class="n">INTEL_BROADWELL_D</span><span class="p">,</span> <span class="mh">0x5</span><span class="p">,</span> <span class="mh">0x5</span><span class="p">,</span> <span class="mh">0x0e000003</span><span class="p">),</span>
    <span class="n">X86_MATCH_VFM_STEPS</span><span class="p">(</span><span class="n">INTEL_SKYLAKE_X</span><span class="p">,</span>   <span class="mh">0x5</span><span class="p">,</span> <span class="mh">0xf</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
    <span class="c1">// ...</span>
<span class="p">};</span>
</code></pre></div></div>

<p>如果 microcode 不满足，内核会自动禁用 TSC Deadline 模式，回退到传统 LAPIC 定时器。</p>

<h3 id="33-完整链路图">3.3 完整链路图</h3>

<p>将三层串起来，hrtimer 从注册到触发的完整链路是：</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant User as 用户程序
    participant HR as hrtimer 框架
    participant RB as 红黑树
    participant CE as 时钟事件设备
    participant LAPIC as LAPIC (TSC Deadline)
    participant TSC as TSC 寄存器

    User-&gt;&gt;HR: clock_nanosleep(100ns)
    HR-&gt;&gt;HR: ktime_t expires = now + 100
    HR-&gt;&gt;RB: 插入红黑树 (O(log n))
    Note over RB: 如果是最早到期
    HR-&gt;&gt;CE: hrtimer_reprogram(expires)
    CE-&gt;&gt;CE: clockevents_program_event(expires)
    CE-&gt;&gt;LAPIC: lapic_next_deadline(delta)
    LAPIC-&gt;&gt;TSC: rdtsc() + delta → MSR_IA32_TSC_DEADLINE
    Note over LAPIC,TSC: 硬件监视 TSC&lt;br/&gt;到达目标值时触发中断
    TSC--&gt;&gt;LAPIC: TSC == deadline
    LAPIC--&gt;&gt;CE: LVTT 定时器中断
    CE--&gt;&gt;HR: hrtimer_interrupt()
    HR-&gt;&gt;RB: 收割到期 timer
    RB--&gt;&gt;HR: 回调函数
    HR--&gt;&gt;User: 进程被唤醒
</code></pre>

<h2 id="四精度-vs-准确性一个重要的区分">四、精度 vs 准确性：一个重要的区分</h2>

<p>让很多人困惑的一件事是：hrtimer 的”纳秒精度”和”实际中断是否能准时在纳秒级触发”是两回事。</p>

<ul>
  <li><strong>精度（precision）</strong>：hrtimer 的 <code class="language-plaintext highlighter-rouge">ktime_t</code> 确实是纳秒粒度的，红黑树按到期时间排序，比较的单位就是纳秒。时钟源（TSC）读出的值分辨率也在亚纳秒级。这是数据层面的精度。</li>
  <li><strong>准确性（accuracy）</strong>：中断实际触发时间到理论到期时间的偏差，受中断延迟（interrupt latency）影响。</li>
</ul>

<p>中断延迟的来源：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>理想路径：
  t=0ns:    注册 hrtimer(deadline = t + 100ns)
  t=100ns:  TSC deadline 到达 → LAPIC 拉高 IRQ 线 → CPU 响应 → handler

实际路径：
  t=0ns:    注册 hrtimer(deadline = t + 100ns)
  t=50ns:   CPU 进入 spin_lock_irqsave 临界区（关中断）
  t=100ns:  TSC deadline 到达 → LAPIC 拉高 IRQ 线
            但 CPU 关中断 → IRQ 在 CPU 的 INTR 引脚等待
  t=300ns:  退出临界区 → 开中断 → CPU 从 IDT 读取中断向量
            → 跳转到 hrtimer_interrupt → handler 执行
            晚了 200ns
</code></pre></div></div>

<p>这个偏差在普通 Linux 内核上通常在 1-10μs 级别，在 <strong>PREEMPT_RT</strong>（实时内核）上可以压到亚微秒级。</p>

<p>但即便是普通内核，hrtimer 和老的时间轮(<code class="language-plaintext highlighter-rouge">timer_list</code>)也有本质区别：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>传统 timer_list (jiffies):
  到期间隔: t=0 注册 5ms 的 timer
  下一个 jiffies tick 在 3ms 后
  再下一个 jiffies tick 在 7ms 后
  → 实际触发在 7ms，偏差 ~2ms

hrtimer (TSC Deadline):
  到期间隔: t=0 注册 5ms 的 hrtimer
  → TSC deadline 写入 t + 5ms 对应的 TSC 值
  → 即使关中断导致延迟，但 5ms+300ns 比 7ms 精确得多
  → 且 hrtimer 不会因为 jiffies 的周期而被延迟
</code></pre></div></div>

<h2 id="五nohz--hrtimer为什么-tickless-需要-hrtimer">五、NOHZ + hrtimer：为什么 tickless 需要 hrtimer</h2>

<p>hrtimer 的纳秒精度还有一个关键的应用场景：<strong>tickless 模式（NOHZ）</strong>。</p>

<p>在没有 NOHZ 的传统内核上，即使 CPU 空闲，每个 tick（1ms/4ms）都会触发一次定时器中断，唤醒 CPU 检查是否需要调度。这浪费了功耗。</p>

<p>在 NOHZ 模式下（桌面和服务器内核默认启用），空闲 CPU 可以完全关闭 tick 中断，靠 hrtimer 在真正有事情需要做时才唤醒它。</p>

<p>这个过程是这样的<sup id="fnref:6:1"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CPU 进入 idle:
  1. 检查所有 hrtimer 的下一个到期时间
  2. 如果没有任何即将到期的 hrtimer → 无限期 idle（可被外部中断唤醒）
  3. 如果有 hrtimer 在 50ms 后到期
     → tick_program_event(50ms 后的 TSC)
     → LAPIC 在 TSC deadline 触发时唤醒 CPU

CPU 从 idle 被 hrtimer 中断唤醒:
  1. hrtimer_interrupt() 收割到期的 hrtimer
  2. 重新编程下一个到期时间
  3. 执行可能由于被阻塞的 RCU 回调等
  4. 如果没有更多工作，再次进入 idle
</code></pre></div></div>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// kernel/time/tick-sched.c: 892</span>
<span class="c1">// NOHZ 模式下，tick 被 hrtimer 替代</span>
<span class="n">tick_program_event</span><span class="p">(</span><span class="n">expires</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
</code></pre></div></div>

<p>这里 hrtimer 替代了传统的 tick 定时器，实现了”没有工作时完全不打扰 CPU”的效果。</p>

<h2 id="总结">总结</h2>

<p>回到最初的问题：hrtimer 的纳秒精度来自三个层次：</p>

<ol>
  <li><strong>数据结构层</strong>（纳秒的”意愿”）：<code class="language-plaintext highlighter-rouge">ktime_t</code> 以纳秒为单位存储和比较到期时间，红黑树 O(log n) 管理</li>
  <li><strong>时钟源层</strong>（纳秒的”眼见”）：TSC 以 ~0.3ns 的粒度提供当前时间读数</li>
  <li><strong>中断触发层</strong>（纳秒的”手到”）：TSC Deadline 模式写入绝对 TSC 值，硬件在确切时刻触发中断</li>
</ol>

<p>三层叠加，实现了 <code class="language-plaintext highlighter-rouge">clock_nanosleep()</code> 在数据层面、读时层面、触发层面均能达到纳秒级精度。但实际触发时间受关中断影响，在普通内核上通常有 1-10μs 的偏差——这是准确性受限，而非精度受限。</p>

<p>hrtimer 和传统 <code class="language-plaintext highlighter-rouge">timer_list</code>（jiffies 时间轮）的区别，不仅仅在于分辨率从毫秒级降到纳秒级——更在于<strong>hrtimer 的触发完全不依赖 tick，它按需编程时钟事件设备，在 NOHZ 模式下实现了真正的 tickless 空闲</strong>。如果把内核时钟系统比作一个厨房：</p>

<ul>
  <li><strong>jiffies 时间轮</strong>像一个每隔固定时间响一次的闹钟——无论有没有事，它都响</li>
  <li><strong>hrtimer</strong>像一个贴在墙上的倒计时器列表——每个 timer 指定了精确的到期时间，CPU 按需设置最近的一个，到期后响一次，然后设置下一个最近的一个。没事的时候，所有计时器静默，CPU 可以安心睡觉</li>
</ul>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>Linux 内核源码，<code class="language-plaintext highlighter-rouge">ktime_t</code> 类型定义，<a href="https://github.com/torvalds/linux/blob/master/include/linux/ktime.h"><code class="language-plaintext highlighter-rouge">include/linux/ktime.h</code></a>。<code class="language-plaintext highlighter-rouge">ktime_t</code> 定义为 <code class="language-plaintext highlighter-rouge">s64</code>，单位纳秒。64 位有符号纳秒可以表示约 ±292 年的时间范围。 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>Linux 内核源码，hrtimer 核心实现，<a href="https://github.com/torvalds/linux/blob/master/kernel/time/hrtimer.c"><code class="language-plaintext highlighter-rouge">kernel/time/hrtimer.c</code></a>。<code class="language-plaintext highlighter-rouge">HIGH_RES_NSEC = 1</code> 表示在高精度模式下分辨率可达 1 纳秒。<code class="language-plaintext highlighter-rouge">hrtimer_interrupt()</code> 是 hrtimer 中断处理函数，在单个中断中遍历所有到期 hrtimer，支持最多 3 次重试防止 livelock。 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:3">
      <p>Linux 内核源码，hrtimer 数据结构定义，<a href="https://github.com/torvalds/linux/blob/master/include/linux/hrtimer_defs.h"><code class="language-plaintext highlighter-rouge">include/linux/hrtimer_defs.h</code></a>。定义了 <code class="language-plaintext highlighter-rouge">hrtimer_cpu_base</code>（包含 8 个 clock base、最早到期时间缓存、<code class="language-plaintext highlighter-rouge">next_timer</code> 指针等）和 <code class="language-plaintext highlighter-rouge">hrtimer_clock_base</code>（红黑树根节点 <code class="language-plaintext highlighter-rouge">active</code>、<code class="language-plaintext highlighter-rouge">expires_next</code> 缓存）。 <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:3:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:4">
      <p>Linux 内核源码，hrtimer 入队和软到期机制，<a href="https://github.com/torvalds/linux/blob/master/kernel/time/hrtimer.c"><code class="language-plaintext highlighter-rouge">kernel/time/hrtimer.c</code></a>。<code class="language-plaintext highlighter-rouge">enqueue_hrtimer()</code> 实现红黑树插入，<code class="language-plaintext highlighter-rouge">timerqueue_linked_add</code> 返回 true 表示新 timer 成为最左节点。<code class="language-plaintext highlighter-rouge">__hrtimer_run_queues()</code> 使用 <code class="language-plaintext highlighter-rouge">softexpires</code> 判断——允许在软到期和硬到期之间优化合并中断。 <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:4:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:5">
      <p>Linux 内核源码，hrtimer 重编程流程，<a href="https://github.com/torvalds/linux/blob/master/kernel/time/hrtimer.c"><code class="language-plaintext highlighter-rouge">kernel/time/hrtimer.c</code></a>。<code class="language-plaintext highlighter-rouge">hrtimer_reprogram()</code> 和 <code class="language-plaintext highlighter-rouge">__hrtimer_reprogram()</code> 组成了从红黑树到硬件编程的桥梁：比较新到期时间和 <code class="language-plaintext highlighter-rouge">cpu_base-&gt;expires_next</code>，只在更早时触发硬件重编程。 <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6">
      <p>Linux 内核源码，<code class="language-plaintext highlighter-rouge">hrtimer_rearm_event()</code> 和 <code class="language-plaintext highlighter-rouge">tick_program_event</code> 调用链，<a href="https://github.com/torvalds/linux/blob/master/kernel/time/hrtimer.c"><code class="language-plaintext highlighter-rouge">kernel/time/hrtimer.c</code></a> 第 700-708 行，以及 <a href="https://github.com/torvalds/linux/blob/master/kernel/time/tick-oneshot.c"><code class="language-plaintext highlighter-rouge">kernel/time/tick-oneshot.c</code></a>。<code class="language-plaintext highlighter-rouge">hrtimer_rearm_event</code> → <code class="language-plaintext highlighter-rouge">tick_program_event</code> → <code class="language-plaintext highlighter-rouge">clockevents_program_event</code>，这是 hrtimer 框架到硬件时钟事件设备的统一出口。 <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:6:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:7">
      <p>Linux 内核源码，<code class="language-plaintext highlighter-rouge">__hrtimer_run_queues</code> 的遍历逻辑，<a href="https://github.com/torvalds/linux/blob/master/kernel/time/hrtimer.c"><code class="language-plaintext highlighter-rouge">kernel/time/hrtimer.c</code></a> 第 1968-1992 行。使用 <code class="language-plaintext highlighter-rouge">for_each_active_base</code> 宏遍历有活跃 timer 的 clock base，对每个 base 从红黑树最左节点开始收割，直到遇到尚未到期的 timer。 <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8">
      <p>Linux 内核源码，clocksource 抽象层，<a href="https://github.com/torvalds/linux/blob/master/include/linux/clocksource.h"><code class="language-plaintext highlighter-rouge">include/linux/clocksource.h</code></a>。<code class="language-plaintext highlighter-rouge">struct clocksource</code> 定义只读时钟源接口，包含 <code class="language-plaintext highlighter-rouge">read()</code>、<code class="language-plaintext highlighter-rouge">mult/shift</code>、<code class="language-plaintext highlighter-rouge">rating</code> 等字段。内核通过 <code class="language-plaintext highlighter-rouge">rating</code> 择优选择最佳时钟源。 <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:9">
      <p>Linux 内核源码，x86 LAPIC 定时器实现，<a href="https://github.com/torvalds/linux/blob/master/arch/x86/kernel/apic/apic.c"><code class="language-plaintext highlighter-rouge">arch/x86/kernel/apic/apic.c</code></a>。<code class="language-plaintext highlighter-rouge">lapic_clockevent</code> 定义 LAPIC 时钟事件设备。<code class="language-plaintext highlighter-rouge">lapic_next_event</code> 和 <code class="language-plaintext highlighter-rouge">lapic_next_deadline</code> 分别实现传统模式（<code class="language-plaintext highlighter-rouge">APIC_TMICT</code> 相对值）和 TSC Deadline 模式（<code class="language-plaintext highlighter-rouge">MSR_IA32_TSC_DEADLINE</code> 绝对值）。<code class="language-plaintext highlighter-rouge">setup_APIC_timer()</code> 在支持 TSC Deadline 时替换 <code class="language-plaintext highlighter-rouge">set_next_event</code> 指针。<code class="language-plaintext highlighter-rouge">deadline_match[]</code> 存储有 bug 的 CPU 型号列表，microcode 不满足时自动禁用 TSC Deadline。 <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:9:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:9:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:9:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:9:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a></p>
    </li>
    <li id="fn:10">
      <p>Linux 内核源码，<code class="language-plaintext highlighter-rouge">X86_FEATURE_TSC_DEADLINE_TIMER</code> 定义，<a href="https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/cpufeatures.h"><code class="language-plaintext highlighter-rouge">arch/x86/include/asm/cpufeatures.h</code></a> 第 129 行。<code class="language-plaintext highlighter-rouge">MSR_IA32_TSC_DEADLINE</code> 的 MSR 地址为 <code class="language-plaintext highlighter-rouge">0x6E0</code>，定义在 <a href="https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/msr-index.h"><code class="language-plaintext highlighter-rouge">arch/x86/include/asm/msr-index.h</code></a> 第 1119 行。 <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:11">
      <p>Linux 内核源码，<code class="language-plaintext highlighter-rouge">clockevents_program_event</code> 实现，<a href="https://github.com/torvalds/linux/blob/master/kernel/time/clockevents.c"><code class="language-plaintext highlighter-rouge">kernel/time/clockevents.c</code></a> 第 332-390 行。支持 <code class="language-plaintext highlighter-rouge">CLOCK_EVT_FEAT_CLOCKSOURCE_COUPLED</code> 标志——设置了此标志的设备走 <code class="language-plaintext highlighter-rouge">ktime_t</code> → TSC 直接路径，跳过 mult/shift 换算。 <a href="#fnref:11" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="rust" /><category term="tokio" /><category term="linux-kernel" /><summary type="html"><![CDATA[从 TSC 硬件时钟到红黑树与 hrtimer，说明 Linux 内核如何为 epoll_wait 提供纳秒级定时精度。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://weinan.tech/images/og/tokio-async.png" /><media:content medium="image" url="https://weinan.tech/images/og/tokio-async.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">从 tokio::time::sleep 到 tokio::net::TcpStream：时间驱动与事件驱动的两种异步实现</title><link href="https://weinan.tech/2026/05/15/tokio-io-driver-mio-scheduledio.html" rel="alternate" type="text/html" title="从 tokio::time::sleep 到 tokio::net::TcpStream：时间驱动与事件驱动的两种异步实现" /><published>2026-05-15T00:00:00+08:00</published><updated>2026-05-15T00:00:00+08:00</updated><id>https://weinan.tech/2026/05/15/tokio-io-driver-mio-scheduledio</id><content type="html" xml:base="https://weinan.tech/2026/05/15/tokio-io-driver-mio-scheduledio.html"><![CDATA[<style>
/* Mermaid diagram container */
.mermaid-container {
  position: relative;
  display: block;
  cursor: pointer;
  transition: opacity 0.2s;
  max-width: 100%;
  margin: 20px 0;
  overflow-x: auto;
}

.mermaid-container:hover {
  opacity: 0.8;
}

.mermaid-container svg {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Modal overlay */
.mermaid-modal {
  display: none;
  position: fixed;
  z-index: 9999;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9);
  animation: fadeIn 0.3s;
}

.mermaid-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Modal content */
.mermaid-modal-content {
  position: relative;
  width: 90vw;
  height: 90vh;
  overflow: hidden;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
}

.mermaid-modal-diagram {
  transform-origin: center center;
  transition: transform 0.2s ease;
  display: inline-block;
  min-width: 100%;
  cursor: grab;
  user-select: none;
}

.mermaid-modal-diagram.dragging {
  cursor: grabbing;
  transition: none;
}

.mermaid-modal-diagram svg {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;
}

/* Control buttons */
.mermaid-controls {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
  z-index: 10000;
}

.mermaid-btn {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
  color: #333;
  font-weight: 500;
}

.mermaid-btn:hover {
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Close button */
.mermaid-close {
  background: #f44336;
  color: white;
  border: none;
}

.mermaid-close:hover {
  background: #d32f2f;
}

/* Zoom indicator */
.mermaid-zoom-level {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 10000;
}
</style>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  mermaid.initialize({
    startOnLoad: false,
    theme: 'default',
    securityLevel: 'loose',
    htmlLabels: true,
    themeVariables: {
      fontSize: '14px'
    }
  });

  let currentZoom = 1;
  let currentModal = null;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let translateX = 0;
  let translateY = 0;

  // Create modal HTML
  function createModal() {
    const modal = document.createElement('div');
    modal.className = 'mermaid-modal';
    modal.innerHTML = `
      <div class="mermaid-controls">
        <button class="mermaid-btn zoom-in">放大 +</button>
        <button class="mermaid-btn zoom-out">缩小 -</button>
        <button class="mermaid-btn zoom-reset">重置</button>
        <button class="mermaid-btn mermaid-close">关闭 ✕</button>
      </div>
      <div class="mermaid-modal-content">
        <div class="mermaid-modal-diagram"></div>
      </div>
      <div class="mermaid-zoom-level">100%</div>
    `;
    document.body.appendChild(modal);
    return modal;
  }

  // Show modal with diagram
  function showModal(diagramContent) {
    if (!currentModal) {
      currentModal = createModal();
      setupModalEvents();
    }

    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.innerHTML = diagramContent;

    // Remove any width/height attributes from SVG to make it responsive
    const svg = modalDiagram.querySelector('svg');
    if (svg) {
      svg.removeAttribute('width');
      svg.removeAttribute('height');
      svg.style.width = '100%';
      svg.style.height = 'auto';
    }

    // Setup drag functionality
    setupDrag(modalDiagram);

    currentModal.classList.add('active');
    currentZoom = 1;
    resetPosition();
    updateZoom();
    document.body.style.overflow = 'hidden';
  }

  // Hide modal
  function hideModal() {
    if (currentModal) {
      currentModal.classList.remove('active');
      document.body.style.overflow = '';
    }
  }

  // Update zoom level
  function updateZoom() {
    if (!currentModal) return;
    const diagram = currentModal.querySelector('.mermaid-modal-diagram');
    const zoomLevel = currentModal.querySelector('.mermaid-zoom-level');
    diagram.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentZoom})`;
    zoomLevel.textContent = `${Math.round(currentZoom * 100)}%`;
  }

  // Reset position when zoom changes
  function resetPosition() {
    translateX = 0;
    translateY = 0;
  }

  // Setup drag functionality
  function setupDrag(element) {
    element.addEventListener('mousedown', startDrag);
    element.addEventListener('touchstart', startDrag);
  }

  function startDrag(e) {
    if (e.type === 'mousedown' && e.button !== 0) return; // Only left click

    isDragging = true;
    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.classList.add('dragging');

    if (e.type === 'touchstart') {
      startX = e.touches[0].clientX - translateX;
      startY = e.touches[0].clientY - translateY;
    } else {
      startX = e.clientX - translateX;
      startY = e.clientY - translateY;
    }

    document.addEventListener('mousemove', drag);
    document.addEventListener('touchmove', drag);
    document.addEventListener('mouseup', stopDrag);
    document.addEventListener('touchend', stopDrag);
  }

  function drag(e) {
    if (!isDragging) return;
    e.preventDefault();

    if (e.type === 'touchmove') {
      translateX = e.touches[0].clientX - startX;
      translateY = e.touches[0].clientY - startY;
    } else {
      translateX = e.clientX - startX;
      translateY = e.clientY - startY;
    }

    updateZoom();
  }

  function stopDrag() {
    isDragging = false;
    const modalDiagram = currentModal?.querySelector('.mermaid-modal-diagram');
    if (modalDiagram) {
      modalDiagram.classList.remove('dragging');
    }
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('touchmove', drag);
    document.removeEventListener('mouseup', stopDrag);
    document.removeEventListener('touchend', stopDrag);
  }

  // Setup modal event listeners
  function setupModalEvents() {
    if (!currentModal) return;

    // Close button
    currentModal.querySelector('.mermaid-close').addEventListener('click', hideModal);

    // Zoom buttons
    currentModal.querySelector('.zoom-in').addEventListener('click', () => {
      currentZoom = Math.min(currentZoom + 0.25, 3);
      updateZoom();
    });

    currentModal.querySelector('.zoom-out').addEventListener('click', () => {
      currentZoom = Math.max(currentZoom - 0.25, 0.5);
      updateZoom();
    });

    currentModal.querySelector('.zoom-reset').addEventListener('click', () => {
      currentZoom = 1;
      resetPosition();
      updateZoom();
    });

    // Close on background click
    currentModal.addEventListener('click', (e) => {
      if (e.target === currentModal) {
        hideModal();
      }
    });

    // Close on ESC key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && currentModal.classList.contains('active')) {
        hideModal();
      }
    });
  }

  // Convert Jekyll-rendered code blocks to mermaid divs
  document.addEventListener('DOMContentLoaded', async function() {
    const codeBlocks = document.querySelectorAll('code.language-mermaid');

    for (const codeBlock of codeBlocks) {
      const pre = codeBlock.parentElement;
      const container = document.createElement('div');
      container.className = 'mermaid-container';

      const mermaidDiv = document.createElement('div');
      mermaidDiv.className = 'mermaid';
      mermaidDiv.textContent = codeBlock.textContent;

      container.appendChild(mermaidDiv);
      pre.replaceWith(container);
    }

    // Render all mermaid diagrams
    try {
      await mermaid.run({
        querySelector: '.mermaid'
      });
      console.log('Mermaid diagrams rendered successfully');
    } catch (error) {
      console.error('Mermaid rendering error:', error);
    }

    // Add click handlers to rendered diagrams
    document.querySelectorAll('.mermaid-container').forEach((container, index) => {
      // Find the rendered SVG inside the container
      const svg = container.querySelector('svg');
      if (!svg) {
        console.warn(`No SVG found in container ${index}`);
        return;
      }

      // Make the container clickable
      container.style.cursor = 'pointer';
      container.title = '点击查看大图';

      container.addEventListener('click', function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Clone the SVG for the modal
        const svgClone = svg.cloneNode(true);
        const tempDiv = document.createElement('div');
        tempDiv.appendChild(svgClone);

        console.log('Opening modal for diagram', index);
        showModal(tempDiv.innerHTML);
      });

      console.log(`Click handler added to diagram ${index}`);
    });
  });
</script>

<blockquote>
  <p>上篇文章跟踪了 <code class="language-plaintext highlighter-rouge">tokio::time::sleep</code> 从 Future::poll 到哈希时间轮的完整链路。那篇文章的核心是一个问题：<strong>时间</strong>怎么做到异步等待的？本文要回答另一个问题：<strong>网络 I/O</strong>又是怎么做到异步等待的？它们共享同一个 runtime 主循环，但背后是两种完全不同的驱动模型。</p>
</blockquote>

<h2 id="引言为什么需要第二篇">引言：为什么需要第二篇？</h2>

<p>上篇文章<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>中，我们看到了 <code class="language-plaintext highlighter-rouge">tokio::time::sleep</code> 的实现链条：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Future::poll → TimerEntry → Wheel(哈希时间轮) → Driver::park_timeout → epoll_wait
</code></pre></div></div>

<p>那条链路的核心架构是：<strong>用户态维护时间轮，只向内核注册一个「最早 deadline」的定时器</strong>。当线程从 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 返回后，驱动去时间轮里收割所有到期 timer，批量唤醒对应的任务。</p>

<p>但这里有一个容易被忽略的细节：<code class="language-plaintext highlighter-rouge">park_timeout</code> 最终调用的 <code class="language-plaintext highlighter-rouge">epoll_wait</code>，本身就是 Linux 上最核心的 <strong>I/O 事件通知机制</strong>。时间驱动把 I/O 驱动的 <code class="language-plaintext highlighter-rouge">park_timeout</code> 当成了「闹钟」来用——它只是顺路借用了 epoll 的超时功能。真正的主角是<strong>I/O 驱动自己</strong>。</p>

<p>当你写下下面这行代码时，背后发生的事情和 <code class="language-plaintext highlighter-rouge">sleep</code> 有着本质区别：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="k">mut</span> <span class="n">stream</span> <span class="o">=</span> <span class="nn">TcpStream</span><span class="p">::</span><span class="nf">connect</span><span class="p">(</span><span class="s">"127.0.0.1:8080"</span><span class="p">)</span><span class="k">.await</span><span class="o">?</span><span class="p">;</span>
<span class="n">stream</span><span class="nf">.read</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">buf</span><span class="p">)</span><span class="k">.await</span><span class="o">?</span><span class="p">;</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">sleep</code> 只关心”时间到了没”；<code class="language-plaintext highlighter-rouge">TcpStream::read</code> 关心的是”数据到了没”。前者在用户态就能判断（查时间轮），后者<strong>必须由内核通知</strong>——因为只有内核知道网卡上有没有新数据包到达。</p>

<p>这篇文章就从 <code class="language-plaintext highlighter-rouge">TcpStream</code> 注册到 I/O 驱动的完整流程讲起，拆开 <code class="language-plaintext highlighter-rouge">ScheduledIo</code>、<code class="language-plaintext highlighter-rouge">Registration</code>、<code class="language-plaintext highlighter-rouge">PollEvented</code>、I/O Driver 的事件循环，然后和上篇的 Timer 驱动做横向对比，看看它们<strong>在同一套 runtime 主循环里是如何共存并各自工作的</strong>。</p>

<h3 id="先看一段代码">先看一段代码</h3>

<p>和上一篇文章一样，先从一个可运行的程序建立直觉。下面是一个简单的 TCP echo 服务，用 Tokio 同时处理多个连接：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio_echo.rs</span>
<span class="k">use</span> <span class="nn">tokio</span><span class="p">::</span><span class="nn">net</span><span class="p">::</span><span class="n">TcpListener</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">tokio</span><span class="p">::</span><span class="nn">io</span><span class="p">::{</span><span class="n">AsyncReadExt</span><span class="p">,</span> <span class="n">AsyncWriteExt</span><span class="p">};</span>

<span class="nd">#[tokio::main]</span>
<span class="k">async</span> <span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="nb">Box</span><span class="o">&lt;</span><span class="k">dyn</span> <span class="nn">std</span><span class="p">::</span><span class="nn">error</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">listener</span> <span class="o">=</span> <span class="nn">TcpListener</span><span class="p">::</span><span class="nf">bind</span><span class="p">(</span><span class="s">"127.0.0.1:8080"</span><span class="p">)</span><span class="k">.await</span><span class="o">?</span><span class="p">;</span>

    <span class="k">loop</span> <span class="p">{</span>
        <span class="c1">// 等待新连接（异步）</span>
        <span class="k">let</span> <span class="p">(</span><span class="k">mut</span> <span class="n">socket</span><span class="p">,</span> <span class="n">addr</span><span class="p">)</span> <span class="o">=</span> <span class="n">listener</span><span class="nf">.accept</span><span class="p">()</span><span class="k">.await</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 每个连接 spawn 一个独立的任务</span>
        <span class="nn">tokio</span><span class="p">::</span><span class="nf">spawn</span><span class="p">(</span><span class="k">async</span> <span class="k">move</span> <span class="p">{</span>
            <span class="k">let</span> <span class="k">mut</span> <span class="n">buf</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">;</span> <span class="mi">1024</span><span class="p">];</span>

            <span class="c1">// 在一个连接上循环读写（异步）</span>
            <span class="k">loop</span> <span class="p">{</span>
                <span class="k">match</span> <span class="n">socket</span><span class="nf">.read</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">buf</span><span class="p">)</span><span class="k">.await</span> <span class="p">{</span>
                    <span class="nf">Ok</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="k">break</span><span class="p">,</span>            <span class="c1">// 连接关闭</span>
                    <span class="nf">Ok</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
                        <span class="c1">// 把读到的内容原样写回去</span>
                        <span class="k">let</span> <span class="n">_</span> <span class="o">=</span> <span class="n">socket</span><span class="nf">.write_all</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buf</span><span class="p">[</span><span class="o">..</span><span class="n">n</span><span class="p">])</span><span class="k">.await</span><span class="p">;</span>
                    <span class="p">}</span>
                    <span class="nf">Err</span><span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="k">break</span><span class="p">,</span>
                <span class="p">}</span>
            <span class="p">}</span>

            <span class="nd">println!</span><span class="p">(</span><span class="s">"连接 {} 关闭"</span><span class="p">,</span> <span class="n">addr</span><span class="p">);</span>
        <span class="p">});</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>用一条线程跑上面的服务，它就可以同时接受成千上万个连接。每个连接上的 <code class="language-plaintext highlighter-rouge">accept</code>、<code class="language-plaintext highlighter-rouge">read</code>、<code class="language-plaintext highlighter-rouge">write</code> 都是异步的——调用时不会阻塞线程，线程会去服务其他连接。</p>

<p>对比一下同步阻塞版本的多线程实现：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// sync_echo.rs（多线程阻塞版）</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">net</span><span class="p">::</span><span class="n">TcpListener</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="n">thread</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="nb">Box</span><span class="o">&lt;</span><span class="k">dyn</span> <span class="nn">std</span><span class="p">::</span><span class="nn">error</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">listener</span> <span class="o">=</span> <span class="nn">TcpListener</span><span class="p">::</span><span class="nf">bind</span><span class="p">(</span><span class="s">"127.0.0.1:8080"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="k">loop</span> <span class="p">{</span>
        <span class="k">let</span> <span class="p">(</span><span class="k">mut</span> <span class="n">socket</span><span class="p">,</span> <span class="n">addr</span><span class="p">)</span> <span class="o">=</span> <span class="n">listener</span><span class="nf">.accept</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 每个连接启动一条新线程</span>
        <span class="nn">thread</span><span class="p">::</span><span class="nf">spawn</span><span class="p">(</span><span class="k">move</span> <span class="p">||</span> <span class="p">{</span>
            <span class="k">let</span> <span class="k">mut</span> <span class="n">buf</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">;</span> <span class="mi">1024</span><span class="p">];</span>

            <span class="k">loop</span> <span class="p">{</span>
                <span class="k">let</span> <span class="n">n</span> <span class="o">=</span> <span class="n">socket</span><span class="nf">.read</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">buf</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
                <span class="k">if</span> <span class="n">n</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span> <span class="k">break</span><span class="p">;</span> <span class="p">}</span>
                <span class="n">socket</span><span class="nf">.write_all</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buf</span><span class="p">[</span><span class="o">..</span><span class="n">n</span><span class="p">])</span><span class="nf">.unwrap</span><span class="p">();</span>
            <span class="p">}</span>

            <span class="nd">println!</span><span class="p">(</span><span class="s">"连接 {} 关闭"</span><span class="p">,</span> <span class="n">addr</span><span class="p">);</span>
        <span class="p">});</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>同步版本也能处理多连接，但代价是<strong>每条连接一条 OS 线程</strong>。每个 <code class="language-plaintext highlighter-rouge">thread::spawn</code> 都涉及内核态的线程创建、栈分配（默认 2MB）、以及调度器的上下文切换。几千个连接就需要几千条线程——OS 调度器很快就不堪重负。</p>

<p>而 Tokio 版本却用<strong>一条线程</strong>做到了同步版本需要几千条线程才能做到的事。秘密就藏在这三个 <code class="language-plaintext highlighter-rouge">.await</code> 背后：它们不会阻塞线程，只会把任务挂起，把线程还给调度器去服务其他连接。</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">方面</th>
      <th style="text-align: left">同步多线程版</th>
      <th style="text-align: left">Tokio 单线程版</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>并发模型</strong></td>
      <td style="text-align: left">每个连接一条 OS 线程</td>
      <td style="text-align: left">单线程管理海量连接</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>资源开销</strong></td>
      <td style="text-align: left">每条线程 2MB 栈 + OS 调度</td>
      <td style="text-align: left">每个任务几十字节的 Future 状态</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>上下文切换</strong></td>
      <td style="text-align: left">OS 内核态切换</td>
      <td style="text-align: left">用户态 async 切换</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>极限连接数</strong></td>
      <td style="text-align: left">几千条线程后难以扩展</td>
      <td style="text-align: left">单线程可处理数十万连接</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IO 操作</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">read</code>/<code class="language-plaintext highlighter-rouge">write</code> 阻塞线程</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">.await</code> 挂起任务不占线程</td>
    </tr>
  </tbody>
</table>

<p>再看客户端怎么写。用 tokio 连接服务器并发送一条消息：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio_client.rs</span>
<span class="k">use</span> <span class="nn">tokio</span><span class="p">::</span><span class="nn">io</span><span class="p">::{</span><span class="n">AsyncReadExt</span><span class="p">,</span> <span class="n">AsyncWriteExt</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">tokio</span><span class="p">::</span><span class="nn">net</span><span class="p">::</span><span class="n">TcpStream</span><span class="p">;</span>

<span class="nd">#[tokio::main]</span>
<span class="k">async</span> <span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="nb">Box</span><span class="o">&lt;</span><span class="k">dyn</span> <span class="nn">std</span><span class="p">::</span><span class="nn">error</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">stream</span> <span class="o">=</span> <span class="nn">TcpStream</span><span class="p">::</span><span class="nf">connect</span><span class="p">(</span><span class="s">"127.0.0.1:8080"</span><span class="p">)</span><span class="k">.await</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 发送数据</span>
    <span class="n">stream</span><span class="nf">.write_all</span><span class="p">(</span><span class="s">b"hello from tokio!"</span><span class="p">)</span><span class="k">.await</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 读取回显</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">buf</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">;</span> <span class="mi">1024</span><span class="p">];</span>
    <span class="k">let</span> <span class="n">n</span> <span class="o">=</span> <span class="n">stream</span><span class="nf">.read</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">buf</span><span class="p">)</span><span class="k">.await</span><span class="o">?</span><span class="p">;</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"收到回显：{:?}"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">buf</span><span class="p">[</span><span class="o">..</span><span class="n">n</span><span class="p">]);</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</code></pre></div></div>

<p>服务端和客户端都用 tokio，双方都不占线程——两个 <code class="language-plaintext highlighter-rouge">.await</code> 已经把线程还给了各自的调度器。</p>

<p>完整的实验场景可以这样跑：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 终端 1：启动 echo 服务器</span>
<span class="nv">$ </span>cargo run <span class="nt">--example</span> tokio_echo

<span class="c"># 终端 2：启动客户端，看到回显</span>
<span class="nv">$ </span>cargo run <span class="nt">--example</span> tokio_client
收到回显：<span class="s2">"hello from tokio!"</span>
</code></pre></div></div>

<p>接下来的内容就是拆开这个「秘密」——看看 <code class="language-plaintext highlighter-rouge">.await</code> 背后 Tokio 的 I/O 驱动是怎么利用 epoll 实现非阻塞 I/O 的。</p>

<h3 id="与上一篇的关系">与上一篇的关系</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">上一篇</th>
      <th style="text-align: left">本文</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">tokio::time::sleep</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">tokio::net::TcpStream</code></td>
    </tr>
    <tr>
      <td style="text-align: left">时间驱动（Timer Driver）</td>
      <td style="text-align: left">I/O 驱动（I/O Driver）</td>
    </tr>
    <tr>
      <td style="text-align: left">哈希时间轮（用户态）</td>
      <td style="text-align: left">epoll（内核态）</td>
    </tr>
    <tr>
      <td style="text-align: left">一个内核定时器管理所有 sleep</td>
      <td style="text-align: left">所有 fd 注册到 epoll</td>
    </tr>
    <tr>
      <td style="text-align: left">「时间到了」由内核定时器通知</td>
      <td style="text-align: left">「数据到了」由 epoll 事件通知</td>
    </tr>
    <tr>
      <td style="text-align: left">主动检查（poll time wheel）</td>
      <td style="text-align: left">被动等待（wait on epoll）</td>
    </tr>
  </tbody>
</table>

<h2 id="概念先行时间驱动-vs-事件驱动">概念先行：时间驱动 vs 事件驱动</h2>

<p><code class="language-plaintext highlighter-rouge">tokio::time::sleep</code> 和 <code class="language-plaintext highlighter-rouge">TcpStream::read</code> 虽然都返回 <code class="language-plaintext highlighter-rouge">Future</code>，都可以 <code class="language-plaintext highlighter-rouge">.await</code>，但它们的驱动模型有本质区别。</p>

<p><strong>时间驱动</strong>的 sleep 是「到时即醒」：线程把 park timeout 设到最早的 deadline，内核在 deadline 到达时通过时钟中断唤醒线程。判断”是否到期”完全可以在用户态通过比较 deadline 和当前时间完成，不需要内核在事件发生时通知。</p>

<p><strong>事件驱动</strong>的 I/O 是「有数即醒」：线程阻塞在 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 上，内核在数据到达网卡时通过硬件中断 → 驱动 → epoll 事件通知唤醒线程。判断”是否可读”只有内核知道——因为数据包什么时候到达是不可预测的。</p>

<p>用一句话概括核心区别：</p>

<blockquote>
  <p><strong>sleep 的等待是确定性的——我知道 5 秒后肯定到；I/O 的等待是不确定性的——我不知道数据什么时候来。</strong></p>
</blockquote>

<p>这对数据结构选择产生了深远的影响：</p>

<pre><code class="language-mermaid">graph LR
    subgraph "时间驱动（sleep）"
        A1["用户态时间轮&lt;br/&gt;O(1) 插入/Wakeup"] --&gt;|"最近 deadline"| B1["内核 hrtimer × 1"]
        B1 --&gt;|"定时中断"| C1["线程醒来"]
        C1 --&gt;|"批量收割"| A1
    end

    subgraph "事件驱动（I/O）"
        A2["ScheduledIo&lt;br/&gt;原子 readiness 位"] --&gt;|"注册到"| B2["epoll&lt;br/&gt;内核管理所有 fd"]
        B2 --&gt;|"可读/可写事件"| C2["线程醒来"]
        C2 --&gt;|"更新 readiness"| A2
    end
</code></pre>

<p>有了这个直觉，接下来就可以拆开源码了。</p>

<h2 id="一driver-堆栈timedriver-在-iodriver-之上">一、Driver 堆栈：TimeDriver 在 IODriver 之上</h2>

<p>在深入 TcpStream 之前，需要先理解 Tokio 运行时的 Driver 堆栈结构。</p>

<p>Tokio 运行时的驱动是<strong>分层</strong>的——内层是 I/O 驱动（epoll），外层是时间驱动<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/driver.rs: 44-50</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">Driver</span> <span class="p">{</span>
    <span class="n">inner</span><span class="p">:</span> <span class="n">TimeDriver</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">Handle</span> <span class="p">{</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">io</span><span class="p">:</span> <span class="n">IoHandle</span><span class="p">,</span>        <span class="c1">// I/O 驱动句柄</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">signal</span><span class="p">:</span> <span class="n">SignalHandle</span><span class="p">,</span> <span class="c1">// 信号驱动句柄</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">time</span><span class="p">:</span> <span class="n">TimeHandle</span><span class="p">,</span>    <span class="c1">// 时间驱动句柄</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">clock</span><span class="p">:</span> <span class="n">Clock</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这是 Worker 线程唯一能看到的 Driver struct——<strong><code class="language-plaintext highlighter-rouge">runtime::driver::Driver</code> 是整个 runtime 对外暴露的统一 park 入口</strong>。Worker 的主循环永远只调这个 struct 的 <code class="language-plaintext highlighter-rouge">park(handle)</code> 和 <code class="language-plaintext highlighter-rouge">park_timeout(handle, duration)</code>，从不关心内部嵌套了多少层。</p>

<p>用一句话概括：<strong>Driver 就是 runtime 的 sleep/wait 机制</strong>——线程通过它知道该睡多久、被谁叫醒、醒来后该干什么。</p>

<p>创建时，先创建 I/O 驱动，然后在其上包裹时间驱动<sup id="fnref:2:1"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>：</p>

<p>不过要注意，<code class="language-plaintext highlighter-rouge">TimeDriver</code> 的名字是历史遗留——它实际是一个条件编译 enum。当 time feature <strong>关闭</strong>时，<code class="language-plaintext highlighter-rouge">TimeDriver</code> 被直接退化为 <code class="language-plaintext highlighter-rouge">IoStack</code>，<code class="language-plaintext highlighter-rouge">Driver { inner: TimeDriver }</code> 变成了 <code class="language-plaintext highlighter-rouge">Driver { inner: IoStack }</code>，Timer 层完全不存在：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// runtime/driver.rs</span>
<span class="nd">cfg_not_time!</span> <span class="p">{</span>
    <span class="k">type</span> <span class="n">TimeDriver</span> <span class="o">=</span> <span class="n">IoStack</span><span class="p">;</span>  <span class="c1">// ← 没有 time 时，inner 直接是 IoStack</span>
<span class="p">}</span>
</code></pre></div></div>

<p>所以三种 feature 组合下的实际形态是：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">time</th>
      <th style="text-align: left">io</th>
      <th style="text-align: left">Driver  inner 的实际类型</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">开启</td>
      <td style="text-align: left">开启</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">TimeDriver::Enabled { driver: time::Driver { park: IoStack(...) } }</code></td>
    </tr>
    <tr>
      <td style="text-align: left">关闭</td>
      <td style="text-align: left">开启</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">IoStack::Enabled(...)</code>（<code class="language-plaintext highlighter-rouge">TimeDriver</code> 退化为此）</td>
    </tr>
    <tr>
      <td style="text-align: left">关闭</td>
      <td style="text-align: left">关闭</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">IoStack::Disabled(ParkThread)</code>（连 I/O 都没有时退化到 <code class="language-plaintext highlighter-rouge">Condvar</code>）</td>
    </tr>
  </tbody>
</table>

<p>不论哪种配置，Worker 线程式始终只调 <code class="language-plaintext highlighter-rouge">driver.park(handle)</code>——<strong><code class="language-plaintext highlighter-rouge">runtime::driver::Driver</code> 是唯一入口，内部嵌套是私有的实现细节</strong>。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/driver.rs: 47-58</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">cfg</span><span class="p">:</span> <span class="n">Cfg</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">(</span><span class="k">Self</span><span class="p">,</span> <span class="n">Handle</span><span class="p">)</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">io_stack</span><span class="p">,</span> <span class="n">io_handle</span><span class="p">,</span> <span class="n">signal_handle</span><span class="p">)</span> <span class="o">=</span> <span class="nf">create_io_stack</span><span class="p">(</span><span class="n">cfg</span><span class="py">.enable_io</span><span class="p">,</span> <span class="n">cfg</span><span class="py">.nevents</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">clock</span> <span class="o">=</span> <span class="nf">create_clock</span><span class="p">(</span><span class="n">cfg</span><span class="py">.enable_pause_time</span><span class="p">,</span> <span class="n">cfg</span><span class="py">.start_paused</span><span class="p">);</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">time_driver</span><span class="p">,</span> <span class="n">time_handle</span><span class="p">)</span> <span class="o">=</span>
        <span class="nf">create_time_driver</span><span class="p">(</span><span class="n">cfg</span><span class="py">.enable_time</span><span class="p">,</span> <span class="n">cfg</span><span class="py">.timer_flavor</span><span class="p">,</span> <span class="n">io_stack</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">clock</span><span class="p">);</span>
    <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">IoStack</code> 本身又是一个小堆栈<sup id="fnref:2:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>IoStack (if io enabled):
  ProcessDriver (子进程事件)
    └── SignalDriver (Unix 信号)
         └── IoDriver (mio::Poll)
</code></pre></div></div>

<p>当时间驱动需要 park 时，它把 <code class="language-plaintext highlighter-rouge">park_timeout</code> 委托给 <code class="language-plaintext highlighter-rouge">IoStack</code>，后者最终调用 <code class="language-plaintext highlighter-rouge">mio::Poll::poll</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/mod.rs: 255-260</span>
<span class="k">fn</span> <span class="nf">park_internal</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">rt_handle</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">driver</span><span class="p">::</span><span class="n">Handle</span><span class="p">,</span> <span class="n">limit</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Duration</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// ...</span>
    <span class="k">match</span> <span class="n">next_wake</span> <span class="p">{</span>
        <span class="nf">Some</span><span class="p">(</span><span class="n">when</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="c1">// 有定时器在将来到期：带超时 park</span>
            <span class="k">self</span><span class="nf">.park_thread_timeout</span><span class="p">(</span><span class="n">rt_handle</span><span class="p">,</span> <span class="n">duration</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="nb">None</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="c1">// 没有定时器：无限期 park（除非有 limit）</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">duration</span><span class="p">)</span> <span class="o">=</span> <span class="n">limit</span> <span class="p">{</span>
                <span class="k">self</span><span class="nf">.park_thread_timeout</span><span class="p">(</span><span class="n">rt_handle</span><span class="p">,</span> <span class="n">duration</span><span class="p">);</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="k">self</span><span class="py">.park</span><span class="nf">.park</span><span class="p">(</span><span class="n">rt_handle</span><span class="p">);</span>  <span class="c1">// 无限期等待 I/O 事件</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>换句话说，<strong>Tokio 的 worker 线程只有一个统一的 park 循环</strong>：时间驱动检查时间轮中的最近 deadline，如果时间轮不为空，就用 <code class="language-plaintext highlighter-rouge">epoll_wait(timeout)</code> 带超时地等待 I/O 事件；如果时间轮为空，就无限期等待 I/O 事件——I/O 事件或者 <code class="language-plaintext highlighter-rouge">mio::Waker</code> 的 unpark 会把线程叫醒。</p>

<p>这个设计的精妙之处在于：<strong>时间驱动和 I/O 驱动共享一次 epoll 系统调用</strong>。如果时间轮里有一个 5 秒后到期的 sleep，同时一个 TCP socket 在 3 秒时收到数据——线程会在 3 秒时被 epoll 叫醒，然后时间驱动顺便检查时间轮、收割到期定时器。不需要两套独立的等待机制。</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant TDriver as Time Driver
    participant IDriver as I/O Driver (epoll)
    participant Kernel as Linux Kernel
    participant Clock as 时钟中断

    TDriver-&gt;&gt;IDriver: park_timeout(5000ms)
    IDriver-&gt;&gt;Kernel: epoll_wait(timeout=5000ms)
    Note over Kernel: 等待 I/O 事件或超时
    Kernel--&gt;&gt;IDriver: 数据到达 → epoll 返回
    IDriver--&gt;&gt;TDriver: 醒来
    TDriver-&gt;&gt;TDriver: process_at_time() → 收割到期定时器
    TDriver-&gt;&gt;IDriver: 处理 I/O 事件
</code></pre>

<p>但这里的细节是：<strong>上述 seq 图是简化版</strong>。实际的 I/O Driver 的 <code class="language-plaintext highlighter-rouge">turn()</code> 首先被调用，它负责处理 epoll 事件并设置 <code class="language-plaintext highlighter-rouge">ScheduledIo</code> 的 readiness；然后才轮到 Time Driver 处理定时器。两者的协作是通过 driver::Handle 来协调的。</p>

<h3 id="11-再往下看mio-封装了什么">1.1 再往下看：mio 封装了什么？</h3>

<p>至此，IoStack 的堆叠关系已经清楚。但再往下追问一步——<code class="language-plaintext highlighter-rouge">IoDriver (mio::Poll)</code> 这个最底层又是什么？</p>

<p>答案是 <code class="language-plaintext highlighter-rouge">mio::Poll</code> 是跨平台 I/O 多路复用的统一接口，它在<strong>编译时</strong>根据目标操作系统选择后端实现<sup id="fnref:11"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// mio/src/sys/mod.rs: 42-56</span>
<span class="nd">#[cfg(any(unix,</span> <span class="nd">target_os</span> <span class="nd">=</span> <span class="s">"hermit"</span><span class="nd">,</span> <span class="err">...</span><span class="nd">))]</span>
<span class="nd">cfg_os_poll!</span> <span class="p">{</span>
    <span class="k">mod</span> <span class="n">unix</span><span class="p">;</span>
    <span class="k">pub</span> <span class="k">use</span> <span class="k">self</span><span class="p">::</span><span class="nn">unix</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>   <span class="c1">// unix → 再根据 OS 选 epoll / kqueue / poll</span>
<span class="p">}</span>

<span class="nd">#[cfg(windows)]</span>
<span class="nd">cfg_os_poll!</span> <span class="p">{</span>
    <span class="k">mod</span> <span class="n">windows</span><span class="p">;</span>
    <span class="k">pub</span> <span class="k">use</span> <span class="k">self</span><span class="p">::</span><span class="nn">windows</span><span class="p">::</span><span class="o">*</span><span class="p">;</span> <span class="c1">// Windows IOCP</span>
<span class="p">}</span>
</code></pre></div></div>

<p>在 Unix 下，mio 的选择器文件分别对应三个级别的 I/O 多路复用机制：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">操作系统</th>
      <th style="text-align: left">mio 选择器</th>
      <th style="text-align: left">内核接口</th>
      <th style="text-align: left">核心文件</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">Linux</td>
      <td style="text-align: left">epoll</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">epoll_create</code> / <code class="language-plaintext highlighter-rouge">epoll_ctl</code> / <code class="language-plaintext highlighter-rouge">epoll_wait</code></td>
      <td style="text-align: left"><a href="https://github.com/tokio-rs/mio/blob/mio-v1.2.0/src/sys/unix/selector/epoll.rs"><code class="language-plaintext highlighter-rouge">mio/src/sys/unix/selector/epoll.rs</code></a></td>
    </tr>
    <tr>
      <td style="text-align: left">macOS / iOS / FreeBSD</td>
      <td style="text-align: left">kqueue</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">kqueue</code> / <code class="language-plaintext highlighter-rouge">kevent</code></td>
      <td style="text-align: left"><a href="https://github.com/tokio-rs/mio/blob/mio-v1.2.0/src/sys/unix/selector/kqueue.rs"><code class="language-plaintext highlighter-rouge">mio/src/sys/unix/selector/kqueue.rs</code></a></td>
    </tr>
    <tr>
      <td style="text-align: left">其他 Unix 回退</td>
      <td style="text-align: left">poll</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">poll</code></td>
      <td style="text-align: left"><a href="https://github.com/tokio-rs/mio/blob/mio-v1.2.0/src/sys/unix/selector/poll.rs"><code class="language-plaintext highlighter-rouge">mio/src/sys/unix/selector/poll.rs</code></a></td>
    </tr>
    <tr>
      <td style="text-align: left">Windows</td>
      <td style="text-align: left">IOCP</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">CreateIoCompletionPort</code> / <code class="language-plaintext highlighter-rouge">GetQueuedCompletionStatus</code></td>
      <td style="text-align: left"><a href="https://github.com/tokio-rs/mio/blob/mio-v1.2.0/src/sys/windows/"><code class="language-plaintext highlighter-rouge">mio/src/sys/windows/</code></a></td>
    </tr>
    <tr>
      <td style="text-align: left">WASI (p1)</td>
      <td style="text-align: left">wasip1</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">poll_oneoff</code></td>
      <td style="text-align: left"><a href="https://github.com/tokio-rs/mio/blob/mio-v1.2.0/src/sys/wasip1/"><code class="language-plaintext highlighter-rouge">mio/src/sys/wasip1/</code></a></td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">epoll.rs</code> 中的 <code class="language-plaintext highlighter-rouge">Selector</code> 直接封装了 epoll 系统调用<sup id="fnref:12"><a href="#fn:12" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// mio/src/sys/unix/selector/epoll.rs: 32-66</span>
<span class="k">impl</span> <span class="n">Selector</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="n">Selector</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// epoll_create1(EPOLL_CLOEXEC) 系统调用</span>
        <span class="k">let</span> <span class="n">ep</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
            <span class="nn">OwnedFd</span><span class="p">::</span><span class="nf">from_raw_fd</span><span class="p">(</span><span class="nd">syscall!</span><span class="p">(</span><span class="nf">epoll_create1</span><span class="p">(</span><span class="nn">libc</span><span class="p">::</span><span class="n">EPOLL_CLOEXEC</span><span class="p">))</span><span class="o">?</span><span class="p">)</span>
        <span class="p">};</span>
        <span class="nf">Ok</span><span class="p">(</span><span class="n">Selector</span> <span class="p">{</span> <span class="n">ep</span><span class="p">,</span> <span class="o">..</span> <span class="p">})</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">select</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">events</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Events</span><span class="p">,</span> <span class="n">timeout</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Duration</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// epoll_wait 系统调用</span>
        <span class="nd">syscall!</span><span class="p">(</span><span class="nf">epoll_wait</span><span class="p">(</span>
            <span class="k">self</span><span class="py">.ep</span><span class="nf">.as_raw_fd</span><span class="p">(),</span>
            <span class="n">events</span><span class="nf">.as_mut_ptr</span><span class="p">(),</span>
            <span class="n">events</span><span class="nf">.capacity</span><span class="p">()</span> <span class="k">as</span> <span class="nb">i32</span><span class="p">,</span>
            <span class="n">timeout</span><span class="p">,</span>
        <span class="p">))</span>
        <span class="nf">.map</span><span class="p">(|</span><span class="n">n</span><span class="p">|</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="n">events</span><span class="nf">.set_len</span><span class="p">(</span><span class="n">n</span> <span class="k">as</span> <span class="nb">usize</span><span class="p">)</span> <span class="p">})</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">kqueue.rs</code> 则是对另一套系统调用的封装<sup id="fnref:13"><a href="#fn:13" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// mio/src/sys/unix/selector/kqueue.rs: 96-139</span>
<span class="k">impl</span> <span class="n">Selector</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="n">Selector</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// kqueue 系统调用</span>
        <span class="k">let</span> <span class="n">kq</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="nn">OwnedFd</span><span class="p">::</span><span class="nf">from_raw_fd</span><span class="p">(</span><span class="nd">syscall!</span><span class="p">(</span><span class="nf">kqueue</span><span class="p">())</span><span class="o">?</span><span class="p">)</span> <span class="p">};</span>
        <span class="nf">Ok</span><span class="p">(</span><span class="n">Selector</span> <span class="p">{</span> <span class="n">kq</span><span class="p">,</span> <span class="o">..</span> <span class="p">})</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">select</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">events</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Events</span><span class="p">,</span> <span class="n">timeout</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Duration</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// kevent 系统调用</span>
        <span class="nd">syscall!</span><span class="p">(</span><span class="nf">kevent</span><span class="p">(</span>
            <span class="k">self</span><span class="py">.kq</span><span class="nf">.as_raw_fd</span><span class="p">(),</span>
            <span class="nn">ptr</span><span class="p">::</span><span class="nf">null</span><span class="p">(),</span>     <span class="c1">// 无 changelist（只读不写）</span>
            <span class="mi">0</span><span class="p">,</span>
            <span class="n">events</span><span class="nf">.as_mut_ptr</span><span class="p">()</span><span class="nf">.cast</span><span class="p">(),</span>
            <span class="n">events</span><span class="nf">.capacity</span><span class="p">()</span> <span class="k">as</span> <span class="n">Count</span><span class="p">,</span>
            <span class="n">timeout_ref</span><span class="p">,</span>
        <span class="p">))</span>
        <span class="nf">.map</span><span class="p">(|</span><span class="n">n</span><span class="p">|</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="n">events</span><span class="nf">.set_len</span><span class="p">(</span><span class="n">n</span> <span class="k">as</span> <span class="nb">usize</span><span class="p">)</span> <span class="p">})</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>mio 的 <code class="language-plaintext highlighter-rouge">cfg</code> 在 <code class="language-plaintext highlighter-rouge">Cargo.toml</code> 中通过 feature <code class="language-plaintext highlighter-rouge">os-poll</code> 触发条件编译<sup id="fnref:14"><a href="#fn:14" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>：Linux 上只编译 <code class="language-plaintext highlighter-rouge">epoll.rs</code>，macOS/FreeBSD 上只编译 <code class="language-plaintext highlighter-rouge">kqueue.rs</code>，Windows 上编译 IOCP。Tokio 的 I/O Driver 从不关心底层——它只调用 <code class="language-plaintext highlighter-rouge">mio::Poll::poll</code>，剩下的由 mio 按平台转发：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/io/driver.rs: 122-140 — Tokio 只看这一层</span>
<span class="k">fn</span> <span class="nf">turn</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">handle</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Handle</span><span class="p">,</span> <span class="n">max_wait</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Duration</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">match</span> <span class="k">self</span><span class="py">.poll</span><span class="nf">.poll</span><span class="p">(</span><span class="n">events</span><span class="p">,</span> <span class="n">max_wait</span><span class="p">)</span> <span class="p">{</span>  <span class="c1">// ← 不管下面是 epoll 还是 kqueue</span>
        <span class="nf">Ok</span><span class="p">(())</span> <span class="k">=&gt;</span> <span class="p">{}</span>
        <span class="nf">Err</span><span class="p">(</span><span class="k">ref</span> <span class="n">e</span><span class="p">)</span> <span class="k">if</span> <span class="n">e</span><span class="nf">.kind</span><span class="p">()</span> <span class="o">==</span> <span class="nn">io</span><span class="p">::</span><span class="nn">ErrorKind</span><span class="p">::</span><span class="n">Interrupted</span> <span class="k">=&gt;</span> <span class="p">{}</span>
        <span class="nf">Err</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="nd">panic!</span><span class="p">(</span><span class="s">"..."</span><span class="p">),</span>
    <span class="p">}</span>
    <span class="k">for</span> <span class="n">event</span> <span class="k">in</span> <span class="n">events</span><span class="nf">.iter</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 通过 token 反查 ScheduledIo，设置 readiness，唤醒任务</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这个抽象的意义在于：<strong>Tokio 的整个 I/O 驱动——<code class="language-plaintext highlighter-rouge">ScheduledIo</code>、<code class="language-plaintext highlighter-rouge">Registration</code>、<code class="language-plaintext highlighter-rouge">PollEvented</code>——全部是跨平台的</strong>。同一份代码编译后，在 Linux 上走 epoll，在 macOS 上走 kqueue，在 Windows 上走 IOCP。平台的差异被 mio 完全封装在 <code class="language-plaintext highlighter-rouge">src/sys/unix/selector/</code> 下的三个文件中。</p>

<p>回到最初的问题：<strong>driver 层，是不是就是 tokio + mio 封装不同操作系统的 NIO 库细节的层面（比如 epoll 和 kqueue）？</strong></p>

<p>是的，但可以更精确地分为两层：</p>

<ul>
  <li><strong>Tokio I/O Driver</strong>（<code class="language-plaintext highlighter-rouge">runtime::io::Driver</code>）：持有 <code class="language-plaintext highlighter-rouge">mio::Poll</code>，管理 <code class="language-plaintext highlighter-rouge">ScheduledIo</code> 的原子 readiness 状态机和 waker 分发</li>
  <li><strong>mio</strong>（操作系统接口层）：编译时选择 epoll / kqueue / IOCP，把原生事件统一为 <code class="language-plaintext highlighter-rouge">mio::Event</code></li>
</ul>

<p>完整层叠关系可以用一张图来总结：</p>

<pre><code class="language-mermaid">graph TD
    A["TcpStream::read().await&lt;br/&gt;应用层"] --&gt; B["Registration + PollEvented + ScheduledIo&lt;br/&gt;注册层"]
    B --&gt; C["runtime::io::Driver (持有 mio::Poll)&lt;br/&gt;Tokio 驱动层"]
    C --&gt; D["mio::Poll → Registry::register / poll&lt;br/&gt;mio 抽象层"]
    D --&gt; E["cfg!(target_os) 选择 epoll/kqueue/poll/IOCP&lt;br/&gt;平台选择层（编译时）"]
    E --&gt; F["epoll_wait / kevent / poll / GetQueuedCompletionStatus&lt;br/&gt;内核接口层"]
    F --&gt; G["网卡中断 → 协议栈 → socket buffer → 就绪通知&lt;br/&gt;物理层"]
</code></pre>

<h3 id="12-driver-堆叠结构timer-和-io-如何共享同一次-epoll_wait">1.2 Driver 堆叠结构：Timer 和 I/O 如何共享同一次 epoll_wait</h3>

<p>前面的层叠图描绘了完整的竖线链路。但横着看，Driver 实际运行时是一层套一层套一层套一层——Tokio 有<strong>五个 Driver structs</strong> 和两个 enum/alias 层，它们的定义位置和嵌套关系如下：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Driver 结构</th>
      <th style="text-align: left">定义文件:行</th>
      <th style="text-align: left">角色</th>
      <th style="text-align: left">持有</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">runtime::driver::Driver</code></td>
      <td style="text-align: left"><a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/driver.rs#L16"><code class="language-plaintext highlighter-rouge">runtime/driver.rs:16</code></a></td>
      <td style="text-align: left">最外层，暴露给 Worker</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">inner: TimeDriver</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">TimeDriver</code>（enum）</td>
      <td style="text-align: left"><a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/driver.rs#L290"><code class="language-plaintext highlighter-rouge">runtime/driver.rs:290</code></a></td>
      <td style="text-align: left">上方 Driver 的 inner，可选启用</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">Enabled { driver: time::Driver }</code> / <code class="language-plaintext highlighter-rouge">Disabled(IoStack)</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">runtime::time::Driver</code></td>
      <td style="text-align: left"><a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/time/mod.rs#L90"><code class="language-plaintext highlighter-rouge">runtime/time/mod.rs:90</code></a></td>
      <td style="text-align: left"><strong>Timer Driver</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">park: IoStack</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">IoStack</code>（enum）</td>
      <td style="text-align: left"><a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/driver.rs#L138"><code class="language-plaintext highlighter-rouge">runtime/driver.rs:138</code></a></td>
      <td style="text-align: left">park 栈桥</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">Enabled(ProcessDriver)</code> / <code class="language-plaintext highlighter-rouge">Disabled(ParkThread)</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">runtime::process::Driver</code></td>
      <td style="text-align: left"><a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/process.rs#L13"><code class="language-plaintext highlighter-rouge">runtime/process.rs:13</code></a></td>
      <td style="text-align: left">Process Driver（子进程清理）</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">park: SignalDriver</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">runtime::signal::Driver</code></td>
      <td style="text-align: left"><a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/signal/mod.rs#L19"><code class="language-plaintext highlighter-rouge">runtime/signal/mod.rs:19</code></a></td>
      <td style="text-align: left">Signal Driver（Unix 信号）</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">io: io::Driver</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">runtime::io::Driver</code></td>
      <td style="text-align: left"><a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/io/driver.rs#L25"><code class="language-plaintext highlighter-rouge">runtime/io/driver.rs:25</code></a></td>
      <td style="text-align: left"><strong>I/O Driver</strong>，最底层</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">poll: mio::Poll</code>, <code class="language-plaintext highlighter-rouge">events: mio::Events</code></td>
    </tr>
  </tbody>
</table>

<p>五个 <code class="language-plaintext highlighter-rouge">Driver</code> struct 的定义文件按创建顺序是：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>runtime::driver::Driver::new()
  → create_io_stack()               → runtime::io::Driver          (runtime/io/driver.rs:25)
    → create_signal_driver(io_drv)   → runtime::signal::Driver      (runtime/signal/mod.rs:19)
      → create_process_driver(sig)   → runtime::process::Driver     (runtime/process.rs:13)
  → create_time_driver(io_stack)     → runtime::time::Driver        (runtime/time/mod.rs:90)
  → Driver { inner: TimeDriver }    → runtime::driver::Driver       (runtime/driver.rs:16)
</code></pre></div></div>

<p>Type alias 的条件编译退化：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">alias</th>
      <th style="text-align: left">启用时 →</th>
      <th style="text-align: left">关闭时退化为</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">IoDriver</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">runtime::io::Driver</code>（<code class="language-plaintext highlighter-rouge">runtime/driver.rs:135</code>）</td>
      <td style="text-align: left">—（无 IO 时无 alias）</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">SignalDriver</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">runtime::signal::Driver</code>（<code class="language-plaintext highlighter-rouge">runtime/driver.rs:244</code>）</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">IoDriver</code>（<code class="language-plaintext highlighter-rouge">runtime/driver.rs:258</code>）</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ProcessDriver</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">runtime::process::Driver</code>（<code class="language-plaintext highlighter-rouge">runtime/driver.rs:269</code>）</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">SignalDriver</code>（<code class="language-plaintext highlighter-rouge">runtime/driver.rs:278</code>）</td>
    </tr>
  </tbody>
</table>

<h4 id="一个-runtime-有多少个-driver-实例">一个 Runtime 有多少个 Driver 实例？</h4>

<p><code class="language-plaintext highlighter-rouge">builder.rs</code> 中创建 Driver 的入口只有一行：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/builder.rs: 1672 / 1860</span>
<span class="k">let</span> <span class="p">(</span><span class="n">driver</span><span class="p">,</span> <span class="n">driver_handle</span><span class="p">)</span> <span class="o">=</span> <span class="nn">driver</span><span class="p">::</span><span class="nn">Driver</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">cfg</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</code></pre></div></div>

<p>无论 current_thread 还是 multi_thread 调度器，<strong>一个 Runtime 只调一次</strong>。所以默认全部启用时，每种 struct 恰好一个实例，<strong>按值嵌套</strong>（<code class="language-plaintext highlighter-rouge">struct</code> 字段直接持有所属 struct，不是 <code class="language-plaintext highlighter-rouge">Arc</code> 也不是 <code class="language-plaintext highlighter-rouge">Box</code>）：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>runtime::driver::Driver   × 1   ← 最外层
runtime::time::Driver     × 1   ← Timer 层
runtime::process::Driver  × 1   ← 子进程清理层 (Linux)
runtime::signal::Driver   × 1   ← Unix 信号层 (Unix)
runtime::io::Driver       × 1   ← 最底层，持有 mio::Poll
</code></pre></div></div>

<p>嵌套链是用字段按值 <code class="language-plaintext highlighter-rouge">struct A { b: B }</code> 串起来的，不是指针。</p>

<h4 id="创建过程从内到外逐层包裹">创建过程：从内到外逐层包裹</h4>

<p><code class="language-plaintext highlighter-rouge">driver::Driver::new(cfg)</code> 内部按<strong>从内到外</strong>的顺序依次创建每个 struct，每个 <code class="language-plaintext highlighter-rouge">::new()</code> 接收下一层的 struct 作为参数，按值取走所有权：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/driver.rs: 47-58</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">cfg</span><span class="p">:</span> <span class="n">Cfg</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">(</span><span class="k">Self</span><span class="p">,</span> <span class="n">Handle</span><span class="p">)</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// 第一步：从内到外包</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">io_stack</span><span class="p">,</span> <span class="n">io_handle</span><span class="p">,</span> <span class="n">signal_handle</span><span class="p">)</span> <span class="o">=</span> <span class="nf">create_io_stack</span><span class="p">(</span><span class="n">cfg</span><span class="py">.enable_io</span><span class="p">,</span> <span class="n">cfg</span><span class="py">.nevents</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">clock</span> <span class="o">=</span> <span class="nf">create_clock</span><span class="p">(</span><span class="n">cfg</span><span class="py">.enable_pause_time</span><span class="p">,</span> <span class="n">cfg</span><span class="py">.start_paused</span><span class="p">);</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">time_driver</span><span class="p">,</span> <span class="n">time_handle</span><span class="p">)</span> <span class="o">=</span>
        <span class="nf">create_time_driver</span><span class="p">(</span><span class="n">cfg</span><span class="py">.enable_time</span><span class="p">,</span> <span class="n">cfg</span><span class="py">.timer_flavor</span><span class="p">,</span> <span class="n">io_stack</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">clock</span><span class="p">);</span>

    <span class="nf">Ok</span><span class="p">((</span>
        <span class="k">Self</span> <span class="p">{</span> <span class="n">inner</span><span class="p">:</span> <span class="n">time_driver</span> <span class="p">},</span>   <span class="c1">// 最外层</span>
        <span class="c1">// ...</span>
    <span class="p">))</span>
<span class="p">}</span>
</code></pre></div></div>

<p>三层帮助函数展开如下。</p>

<p><code class="language-plaintext highlighter-rouge">create_io_stack()</code>（<code class="language-plaintext highlighter-rouge">runtime/driver.rs:149-170</code>）：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">create_io_stack</span><span class="p">(</span><span class="n">enabled</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span> <span class="n">nevents</span><span class="p">:</span> <span class="nb">usize</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">...</span> <span class="p">{</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">io_driver</span><span class="p">,</span> <span class="n">io_handle</span><span class="p">)</span> <span class="o">=</span> <span class="nn">io</span><span class="p">::</span><span class="nn">Driver</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">nevents</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>        <span class="c1">// ⑤ io::Driver</span>

    <span class="k">let</span> <span class="p">(</span><span class="n">signal_driver</span><span class="p">,</span> <span class="n">signal_handle</span><span class="p">)</span> <span class="o">=</span>
        <span class="nf">create_signal_driver</span><span class="p">(</span><span class="n">io_driver</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">io_handle</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>               <span class="c1">// ④ signal::Driver 拿走了 io::Driver</span>

    <span class="k">let</span> <span class="n">process_driver</span> <span class="o">=</span> <span class="nf">create_process_driver</span><span class="p">(</span><span class="n">signal_driver</span><span class="p">);</span>      <span class="c1">// ③ process::Driver 拿走了 signal::Driver</span>

    <span class="p">(</span><span class="nn">IoStack</span><span class="p">::</span><span class="nf">Enabled</span><span class="p">(</span><span class="n">process_driver</span><span class="p">),</span> <span class="o">...</span><span class="p">)</span>                         <span class="c1">// IoStack 拿走了 process::Driver</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">create_signal_driver()</code>（<code class="language-plaintext highlighter-rouge">runtime/driver.rs:247-253</code>）：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">create_signal_driver</span><span class="p">(</span><span class="n">io_driver</span><span class="p">:</span> <span class="n">IoDriver</span><span class="p">,</span> <span class="n">io_handle</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">io</span><span class="p">::</span><span class="n">Handle</span><span class="p">)</span>
    <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">(</span><span class="n">SignalDriver</span><span class="p">,</span> <span class="n">SignalHandle</span><span class="p">)</span><span class="o">&gt;</span>
<span class="p">{</span>
    <span class="k">let</span> <span class="n">driver</span> <span class="o">=</span> <span class="k">crate</span><span class="p">::</span><span class="nn">runtime</span><span class="p">::</span><span class="nn">signal</span><span class="p">::</span><span class="nn">Driver</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">io_driver</span><span class="p">,</span> <span class="n">io_handle</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="c1">//                        io::Driver 被按值移入 ↑</span>
    <span class="k">let</span> <span class="n">handle</span> <span class="o">=</span> <span class="n">driver</span><span class="nf">.handle</span><span class="p">();</span>
    <span class="nf">Ok</span><span class="p">((</span><span class="n">driver</span><span class="p">,</span> <span class="nf">Some</span><span class="p">(</span><span class="n">handle</span><span class="p">)))</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">create_process_driver()</code>（<code class="language-plaintext highlighter-rouge">runtime/driver.rs:271-275</code>）：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">create_process_driver</span><span class="p">(</span><span class="n">signal_driver</span><span class="p">:</span> <span class="n">SignalDriver</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">ProcessDriver</span> <span class="p">{</span>
    <span class="nn">ProcessDriver</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">signal_driver</span><span class="p">)</span>
    <span class="c1">//         SignalDriver 被按值移入 ↑</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">create_time_driver()</code>（<code class="language-plaintext highlighter-rouge">runtime/driver.rs:305-323</code>）：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">create_time_driver</span><span class="p">(</span><span class="n">enable</span><span class="p">,</span> <span class="n">timer_flavor</span><span class="p">,</span> <span class="n">io_stack</span><span class="p">:</span> <span class="n">IoStack</span><span class="p">,</span> <span class="n">clock</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="p">(</span><span class="n">TimeDriver</span><span class="p">,</span> <span class="n">TimeHandle</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">driver</span><span class="p">,</span> <span class="n">handle</span><span class="p">)</span> <span class="o">=</span> <span class="nn">time</span><span class="p">::</span><span class="nn">Driver</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">io_stack</span><span class="p">,</span> <span class="n">clock</span><span class="p">);</span>
    <span class="c1">//                       IoStack 被按值移入 ↑</span>
    <span class="p">(</span><span class="nn">TimeDriver</span><span class="p">::</span><span class="n">Enabled</span> <span class="p">{</span> <span class="n">driver</span> <span class="p">},</span> <span class="nf">Some</span><span class="p">(</span><span class="n">handle</span><span class="p">))</span>
<span class="p">}</span>
</code></pre></div></div>

<p>最终 <code class="language-plaintext highlighter-rouge">Driver { inner: TimeDriver }</code> 拿到最外层。所以这 5 个 struct 并不是”各自创建后组装”，而是一个接一个被<strong>吞进</strong>下一层：<code class="language-plaintext highlighter-rouge">io::Driver</code> 被 <code class="language-plaintext highlighter-rouge">signal::Driver</code> 吞掉，<code class="language-plaintext highlighter-rouge">signal::Driver</code> 被 <code class="language-plaintext highlighter-rouge">process::Driver</code> 吞掉，<code class="language-plaintext highlighter-rouge">process::Driver</code> 被 <code class="language-plaintext highlighter-rouge">IoStack</code> 吞掉，<code class="language-plaintext highlighter-rouge">IoStack</code> 被 <code class="language-plaintext highlighter-rouge">time::Driver</code> 吞掉，<code class="language-plaintext highlighter-rouge">time::Driver</code> 被 <code class="language-plaintext highlighter-rouge">TimeDriver</code> 吞掉，<code class="language-plaintext highlighter-rouge">TimeDriver</code> 被 <code class="language-plaintext highlighter-rouge">driver::Driver</code> 吞掉。最后只有 <code class="language-plaintext highlighter-rouge">driver::Driver</code> 这一个 struct 活着暴露给 Worker。</p>

<p>五个 struct 全部在堆栈上从一个 <code class="language-plaintext highlighter-rouge">driver::Driver::new()</code> 调用中构建完成。多线程下，整个嵌套体被放进 <code class="language-plaintext highlighter-rouge">TryLock&lt;Driver&gt;</code>，所有 worker 共享，只有执行 <code class="language-plaintext highlighter-rouge">park_timeout</code> 的线程才能排他地拿走 Driver。</p>

<h4 id="包含关系与-park-调用链映射">包含关系与 park 调用链映射</h4>

<p>上面的嵌套链是<strong>静态包含关系</strong>——编译时就确定的 <code class="language-plaintext highlighter-rouge">struct</code> 字段层级。而 park 调用链是<strong>运行时方法调用路径</strong>——从 <code class="language-plaintext highlighter-rouge">Driver::park()</code> 开始一直委派到 <code class="language-plaintext highlighter-rouge">io::Driver::turn()</code>。两者是平行映射的。</p>

<p>每层的委派模式<strong>不完全一致</strong>——<code class="language-plaintext highlighter-rouge">time::Driver</code> 在前面先做自己的事然后委派，<code class="language-plaintext highlighter-rouge">process</code>/<code class="language-plaintext highlighter-rouge">signal</code> 在后面先委派再处理自己的事：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>静态包含关系                      运行时 park 调用链
─────────────────                ────────────────────
driver::Driver                    Driver::park(handle)
  └── inner: TimeDriver(enum)       → TimeDriver::park(handle)
        └── TimeDriver::Enabled        → time::Driver::park_internal(handle)
              └── park: IoStack(enum)      → IoStack::park_timeout(handle, dur)
                    └── IoStack::Enabled      → ProcessDriver::park_timeout(handle, dur)
                          └── process::Driver     → process::Driver::park_timeout(handle, dur)
                                └── park: SignalDriver  → SignalDriver::park_timeout(handle, dur)
                                      └── io: io::Driver      → io::Driver::park_timeout(handle, dur)
                                                                  → io::Driver::turn()
                                                                    → self.poll.poll() (epoll_wait)
</code></pre></div></div>

<p>每一层的 <code class="language-plaintext highlighter-rouge">park</code>/<code class="language-plaintext highlighter-rouge">park_timeout</code> 方法签名完全一致：<code class="language-plaintext highlighter-rouge">fn(&amp;mut self, handle: &amp;driver::Handle)</code> 或带 <code class="language-plaintext highlighter-rouge">duration: Duration</code>。但注意<strong>没有 trait</strong>——每个 <code class="language-plaintext highlighter-rouge">impl</code> 块是独立写的，只是碰巧签名相同。</p>

<p>但委派的时机不同——各层是<strong>前序、后序混合</strong>：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>time::Driver::park_internal:     前序 → 先查时间轮（自己事），再调 IoStack
process::Driver::park:           后序 → 先调 signal::Driver，再 reap_orphans
signal::Driver::park:            后序 → 先调 io::Driver，再 process() 处理信号
io::Driver::park:                终点 → 没有委派，直接 turn() → epoll_wait
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">time::Driver</code> 需要先知道 timeout（查时间轮），然后才决定是否委派，所以是前序。<code class="language-plaintext highlighter-rouge">process</code>/<code class="language-plaintext highlighter-rouge">signal</code> 没有需要提前得到的信息，委派回来后顺便干点自己的活，所以是后序。</p>

<p>例外还有最外层 <code class="language-plaintext highlighter-rouge">runtime::driver::Driver</code>——它不做自己的事，<code class="language-plaintext highlighter-rouge">self.inner.park(handle)</code> 纯委派。</p>

<p>这就是整个 Driver 堆叠的静态结构和动态路径的完整映射。</p>

<h4 id="每层的职责">每层的职责</h4>

<p>从外到内，每层在 park 循环中做的事情：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">层级</th>
      <th style="text-align: left">在 park 调用链中做的事</th>
      <th style="text-align: left">在返回路径中做的事</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">driver::Driver</code></strong></td>
      <td style="text-align: left"><strong>纯壳子</strong>：直接委派给 <code class="language-plaintext highlighter-rouge">TimeDriver</code>，Worker 只认这一个 struct</td>
      <td style="text-align: left">无</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">TimeDriver</code> (enum)</strong></td>
      <td style="text-align: left"><strong>开关层</strong>：判断 timer 是否启用。启用则委派给 <code class="language-plaintext highlighter-rouge">time::Driver</code>，关闭则委派给 <code class="language-plaintext highlighter-rouge">IoStack</code></td>
      <td style="text-align: left">无</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">time::Driver</code></strong></td>
      <td style="text-align: left"><strong>查时间轮</strong>：<code class="language-plaintext highlighter-rouge">next_expiration_time()</code> 算最早 deadline。到期则 <code class="language-plaintext highlighter-rouge">park_timeout(0s)</code>，未到期则设 timeout</td>
      <td style="text-align: left"><strong>收割到期 timer</strong>：<code class="language-plaintext highlighter-rouge">process_at_time()</code> → <code class="language-plaintext highlighter-rouge">fire()</code> → <code class="language-plaintext highlighter-rouge">wake()</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">IoStack</code> (enum)</strong></td>
      <td style="text-align: left"><strong>I/O 开关</strong>：启用则走 epoll 栈（<code class="language-plaintext highlighter-rouge">process::Driver</code>），关闭则退化为 <code class="language-plaintext highlighter-rouge">ParkThread::park</code>（<code class="language-plaintext highlighter-rouge">Condvar::wait</code>）</td>
      <td style="text-align: left">无</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">process::Driver</code></strong></td>
      <td style="text-align: left">委派给 <code class="language-plaintext highlighter-rouge">SignalDriver</code></td>
      <td style="text-align: left"><strong>清理用户子进程</strong>：<code class="language-plaintext highlighter-rouge">GlobalOrphanQueue::reap_orphans()</code>——回收用户通过 <code class="language-plaintext highlighter-rouge">tokio::process::Command</code> 启动的子进程退出后的僵尸进程表项。runtime 本身不启动任何额外进程</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">signal::Driver</code></strong></td>
      <td style="text-align: left">委派给 <code class="language-plaintext highlighter-rouge">io::Driver</code></td>
      <td style="text-align: left"><strong>处理信号</strong>：读 self-pipe，<code class="language-plaintext highlighter-rouge">globals().broadcast()</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">io::Driver</code></strong></td>
      <td style="text-align: left"><strong>唯一真正干活</strong>：<code class="language-plaintext highlighter-rouge">turn()</code> 调 <code class="language-plaintext highlighter-rouge">self.poll.poll(events, max_wait)</code> 执行 <code class="language-plaintext highlighter-rouge">epoll_wait</code> / <code class="language-plaintext highlighter-rouge">kevent</code></td>
      <td style="text-align: left"><strong>分发 I/O 事件</strong>：遍历 events，设 <code class="language-plaintext highlighter-rouge">ScheduledIo</code> readiness，<code class="language-plaintext highlighter-rouge">wake()</code> 任务</td>
    </tr>
  </tbody>
</table>

<p>调用委派方向是<strong>从外到内</strong>，每层先委派完再处理自己的逻辑：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>park 方向:   外 → TimeDriver → time::Driver → IoStack → process → signal → io::Driver::turn()
                                                                              ↓
                                                                        epoll_wait
返回方向:   外 ← TimeDriver ← time::Driver ← IoStack ← process ← signal ← io::Driver::turn()
                                      ↓                  ↓         ↓
                                process_at_time     reap      process
                                收割到期 timer      orphans   信号
</code></pre></div></div>

<p>epoll_wait 返回后，事件处理的顺序是固定的：<strong>I/O 事件最先 → 信号处理 → 子进程清理 → 到期 timer 最后</strong>。这不是显式配置的优先级，而是嵌套结构带来的自然结果——最内层（<code class="language-plaintext highlighter-rouge">io::Driver</code>) 最先被调用也最先返回处理，最外层（<code class="language-plaintext highlighter-rouge">time::Driver</code>）最后处理。嵌套越深，优先级越高。</p>

<p>这个模型可以类比为线性链的<strong>后序遍历（post-order）</strong>：子节点处理完 → 父节点处理。Driver 嵌套链是一个线性调用树（每个节点只有一个子节点），park 方向是前序（先查时间轮，再进子节点），返回方向是后序（先分发 I/O 事件，再处理信号，最后收割 timer）。</p>

<p>条件编译可以缩短链：无 process feature 时 <code class="language-plaintext highlighter-rouge">process::Driver</code> 退化为 <code class="language-plaintext highlighter-rouge">SignalDriver</code>，无 signal 时 <code class="language-plaintext highlighter-rouge">SignalDriver</code> 退化为 <code class="language-plaintext highlighter-rouge">IoDriver</code>。但剩余层的顺序和职责不变。</p>

<p>但链不是无限可缩的——最外层和最内层是固定的：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>runtime::driver::Driver    ← 必须存在，Worker 只看这一个入口
    └── TimeDriver          ← 必须存在，enum 天生存在
        ├── time::Driver    ← 可关（关 time 时 → IoStack）
        └── IoStack         ← 必须存在，enum 天生存在
            ├── process     ← 可关（退化 → SignalDriver）
            ├── signal      ← 可关（退化 → io::Driver）
            └── io::Driver  ← 可关（关 io 时 → ParkThread）
                └── ParkThread  ← 最终兜底（Condvar::wait）
</code></pre></div></div>

<p>所以真正<strong>永远无法跳过</strong>的是三层 struct/enum：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">层级</th>
      <th style="text-align: left">为什么不可缩</th>
      <th style="text-align: left">最简形态</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">runtime::driver::Driver</code></td>
      <td style="text-align: left">Worker 线程调它的 <code class="language-plaintext highlighter-rouge">park(handle)</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">Driver { inner: TimeDriver }</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">TimeDriver</code></td>
      <td style="text-align: left">enum 天生存在；time 关闭时退化为 <code class="language-plaintext highlighter-rouge">IoStack</code> 别名</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">TimeDriver = IoStack</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">IoStack</code></td>
      <td style="text-align: left">enum 天生存在；io 关闭时退化为 <code class="language-plaintext highlighter-rouge">ParkThread</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">IoStack::Disabled(ParkThread)</code></td>
    </tr>
  </tbody>
</table>

<p>链必须从 <code class="language-plaintext highlighter-rouge">runtime::driver::Driver</code> 走到某个能阻塞线程的兜底机制。<code class="language-plaintext highlighter-rouge">io::Driver</code>（带 <code class="language-plaintext highlighter-rouge">epoll_wait</code>）是最常见的兜底，但不是唯一的——<code class="language-plaintext highlighter-rouge">ParkThread</code>（<code class="language-plaintext highlighter-rouge">Condvar::wait</code>）才是永远存在的最后防线。关掉所有可选层后，runtime 退化为一个可以用 <code class="language-plaintext highlighter-rouge">tokio::spawn</code> 做纯计算任务调度器，没有 I/O、没有 Timer、没有 Process、没有 Signal。</p>

<p>全部默认启用时的完整堆叠可以用下面的关系图表示。左侧是 Driver 嵌套（实线 <code class="language-plaintext highlighter-rouge">--&gt;</code> = 按值包含），右侧是 Handle 共享（虚线 <code class="language-plaintext highlighter-rouge">o--</code> = Arc 聚合）：</p>

<pre><code class="language-mermaid">graph LR
    subgraph "Driver 嵌套（按值包含）"
        D["runtime::driver::Driver"] --&gt;|"inner"| TD["TimeDriver (enum)"]
        TD --&gt;|"Enabled.driver"| T["runtime::time::Driver"]
        T --&gt;|"park"| IS["IoStack (enum)"]
        IS --&gt;|"Enabled"| PD["ProcessDriver =&lt;br/&gt;process::Driver"]
        PD --&gt;|"park"| SD["signal::Driver"]
        SD --&gt;|"io"| ID["runtime::io::Driver&lt;br/&gt;{ poll: mio::Poll }"]
    end

    subgraph "Handle 聚合（Arc 共享）"
        H["driver::Handle"] --&gt;|"io"| IOH["IoHandle (enum)"]
        IOH -.-&gt;|"Enabled (Arc)"| IOH2["io::Handle&lt;br/&gt;{ registry, waker }"]
    end
</code></pre>

<ul>
  <li>左侧实线 <code class="language-plaintext highlighter-rouge">--&gt;</code>：按值组合（<code class="language-plaintext highlighter-rouge">struct A { b: B }</code>），每层包含下一层的完整 struct 实例</li>
  <li><code class="language-plaintext highlighter-rouge">IoStack</code> 和 <code class="language-plaintext highlighter-rouge">TimeDriver</code> 的 <code class="language-plaintext highlighter-rouge">Enabled</code> / <code class="language-plaintext highlighter-rouge">Disabled</code> 分支在条件编译时可跳过对应层级</li>
  <li>右侧虚线 <code class="language-plaintext highlighter-rouge">o--</code>：Arc 聚合，<code class="language-plaintext highlighter-rouge">io::Handle</code> 被所有 worker 线程通过 <code class="language-plaintext highlighter-rouge">Arc</code> 共享，用于 <code class="language-plaintext highlighter-rouge">add_source()</code> 和 <code class="language-plaintext highlighter-rouge">unpark()</code>，不走 Driver 的 park 栈</li>
</ul>

<p>除 <code class="language-plaintext highlighter-rouge">io::Handle</code> 通过 <code class="language-plaintext highlighter-rouge">Arc</code> 聚合共享外，<code class="language-plaintext highlighter-rouge">time::Handle</code> 也是 <code class="language-plaintext highlighter-rouge">Option&lt;time::Handle&gt;</code>（<code class="language-plaintext highlighter-rouge">Arc&lt;InnerState&gt;</code>），所有 worker 线程都通过 <code class="language-plaintext highlighter-rouge">Arc&lt;driver::Handle&gt;</code> 共享同一组 Handle。</p>

<p>关键认识（两个层次——外部统一入口 vs 内部各搞各的）：</p>

<blockquote>
  <p><strong>外部统一入口</strong>：Worker 线程只看 <code class="language-plaintext highlighter-rouge">runtime::driver::Driver</code> 这一个 struct，永远只调它的 <code class="language-plaintext highlighter-rouge">park</code>/<code class="language-plaintext highlighter-rouge">park_timeout</code>，不关心内部嵌套。<code class="language-plaintext highlighter-rouge">inner: TimeDriver</code> 根据 feature 配置退化为 <code class="language-plaintext highlighter-rouge">IoStack</code> 或 <code class="language-plaintext highlighter-rouge">ParkThread</code>，暴露给 Worker 的 API 不变。</p>

  <p><strong>内部各搞各的</strong>：链上的五个 Driver struct（<code class="language-plaintext highlighter-rouge">driver</code>/<code class="language-plaintext highlighter-rouge">time</code>/<code class="language-plaintext highlighter-rouge">process</code>/<code class="language-plaintext highlighter-rouge">signal</code>/<code class="language-plaintext highlighter-rouge">io</code>）<strong>没有共享的 <code class="language-plaintext highlighter-rouge">trait</code></strong>。每个 <code class="language-plaintext highlighter-rouge">impl Driver { park() }</code> 是孤立定义的，只是碰巧有相同的签名。调用链靠硬编码的字段委派维系，编译器不检查签名一致性，也没有 <code class="language-plaintext highlighter-rouge">Box&lt;dyn Park&gt;</code> 的动态替换能力。</p>
</blockquote>

<p>具体 struct 关系：</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">IoDriver</code> 不是独立 struct，它是 <code class="language-plaintext highlighter-rouge">runtime::io::Driver</code> 的类型别名</strong>，定义在 <code class="language-plaintext highlighter-rouge">runtime/driver.rs:92</code>：<code class="language-plaintext highlighter-rouge">pub(crate) type IoDriver = crate::runtime::io::Driver;</code></li>
  <li><strong><code class="language-plaintext highlighter-rouge">TimeDriver</code> 不是独立 Driver</strong>，它是 <code class="language-plaintext highlighter-rouge">runtime/driver.rs</code> 内部的 enum，用来表达 timer 是否启用、启用哪个 flavor</li>
  <li><strong><code class="language-plaintext highlighter-rouge">ProcessDriver</code> / <code class="language-plaintext highlighter-rouge">SignalDriver</code></strong> 也是 type alias，它们的存在是为了在条件编译下保持类型统一——如果 process/signal 被关掉，它们就退化为下一层的别名，不增加嵌套深度</li>
  <li><strong><code class="language-plaintext highlighter-rouge">runtime::io::Driver</code> 是最底层</strong>，直接持有 <code class="language-plaintext highlighter-rouge">mio::Poll</code></li>
  <li><code class="language-plaintext highlighter-rouge">Handle</code> 中的 <code class="language-plaintext highlighter-rouge">io: IoHandle</code> 是独立于 park 栈的——它通过 <code class="language-plaintext highlighter-rouge">Arc&lt;runtime::io::Handle&gt;</code> 直接 unpark I/O Driver，不走 Timer Driver</li>
</ul>

<p>park 调用链：<code class="language-plaintext highlighter-rouge">Driver::park</code> → <code class="language-plaintext highlighter-rouge">TimeDriver::park</code> → <code class="language-plaintext highlighter-rouge">time::Driver::park_internal</code> → <code class="language-plaintext highlighter-rouge">IoStack::park_timeout</code> → <code class="language-plaintext highlighter-rouge">ProcessDriver::park</code> → <code class="language-plaintext highlighter-rouge">...</code> → <code class="language-plaintext highlighter-rouge">io::Driver::turn</code> → <code class="language-plaintext highlighter-rouge">mio::Poll::poll</code>。</p>

<p>这条结构链决定了两种使用场景的走法完全不同：</p>

<p><strong>场景 A：只有 I/O，没有 timer（TcpStream 纯读）</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Worker 线程主循环
  → park_internal()
    → 查 Wheel → next_wake = None（时间轮是空的）
    → IoStack::park_timeout(None)   ← 无 timer，传 None
      → I/O Driver::turn()
        → epoll_wait(events, -1)    ← 无限期等 I/O
          → [数据到达，网卡中断]
          → epoll_wait 返回
          → 遍历 events → ScheduledIo::set_readiness()
          → waker.wake()            ← 唤醒 TcpStream 的任务
    → process_at_time(now)          ← 时间轮为空，no-op
    → 返回到调度器，执行就绪任务
</code></pre></div></div>

<p><strong>场景 B：只有 timer，没有 I/O（纯 sleep）</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Worker 线程主循环
  → park_internal()
    → 查 Wheel → next_wake = Some(10000)
    → duration = 10000 - now = 5000ms
    → IoStack::park_timeout(5000ms)
      → I/O Driver::turn()
        → epoll_wait(events, 5000)  ← 带 5s 超时
          → [超时，hrtimer 到期]
          → epoll_wait 返回（无 I/O 事件）
    → process_at_time(now=10000)    ← 收割到期 timer
    → fire() → waker.wake()         ← 唤醒 Sleep 的任务
</code></pre></div></div>

<p><strong>场景 C：既有 I/O 又有 timer（常见混合场景）</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Worker 线程主循环
  → park_internal()
    → 查 Wheel → next_wake = Some(10000)
    → IoStack::park_timeout(5000ms)
      → I/O Driver::turn()
        → epoll_wait(events, 5000)
          → [2s 时，TcpStream 数据到达]
          → epoll_wait 提前返回      ← I/O 事件先到
          → 遍历 events → ScheduledIo::set_readiness()
          → waker.wake()             ← 唤醒 TcpStream 的任务
    → process_at_time(now=7000)      ← elapsed 只走到 7000，不是 10000
    → 没有 timer 到期（deadline 10000 &gt; 7000）
    → 返回调度器，继续处理
</code></pre></div></div>

<p>精妙之处在场景 C：<strong>epoll_wait 的 OR 语义让两个驱动共享一次系统调用</strong>——TcpStream 数据先到就提前回来，Timer 顺便看一眼谁到期了；hrtimer 先到就准时处理 timer。不需要两套独立的等待线程或两套 epoll 实例。</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant Worker as Worker 线程
    participant TD as Time Driver
    participant IOD as I/O Driver
    participant EP as epoll_wait

    note over Worker: 场景 C：5s timer + 1 个 TcpStream
    Worker-&gt;&gt;TD: park_internal()
    TD-&gt;&gt;TD: next_wake = 10000, duration = 5000
    TD-&gt;&gt;IOD: park_timeout(5000ms)
    IOD-&gt;&gt;EP: epoll_wait(events, 5000)
    note over EP: OR 等待：I/O 事件 or 5000ms 超时
    EP--&gt;&gt;IOD: [2s] TcpStream 可读
    IOD-&gt;&gt;IOD: 处理 events → wake TcpStream
    IOD--&gt;&gt;TD: turn() 返回
    TD-&gt;&gt;TD: process_at_time(now=7000)
    note over TD: deadline 10000 &gt; 7000 → 无 timer 到期
    TD--&gt;&gt;Worker: 回到调度器
</code></pre>

<p>这就是 Tokio 的 <code class="language-plaintext highlighter-rouge">Driver { inner: TimeDriver }</code> 设计的核心：<strong>外层 Timer 决定等多久，内层 I/O 执行等待，醒来后各管各的数据。I/O Driver 不在 <code class="language-plaintext highlighter-rouge">inner</code> 字段里，它在 TimeDriver 的 <code class="language-plaintext highlighter-rouge">park</code> 字段里层层嵌套地藏着。</strong></p>

<h3 id="13-底层底座所有-driver-共享同一个-runtimeiodriver">1.3 底层底座：所有 Driver 共享同一个 <code class="language-plaintext highlighter-rouge">runtime::io::Driver</code></h3>

<p>堆叠链中的每一层都有自己的 <code class="language-plaintext highlighter-rouge">impl Driver { park() }</code>，但它们最终都有一个共同的落脚点——<strong><code class="language-plaintext highlighter-rouge">runtime::io::Driver</code>（宿主 <code class="language-plaintext highlighter-rouge">mio::Poll</code>）</strong>。不管是 Timer、Signal 还是 Process，park 链的归宿都一样：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>runtime::time::Driver::park_internal
  → IoStack::park_timeout
    → ProcessDriver::park_timeout
      → SignalDriver::park_timeout
        → io::Driver::park_timeout      ← runtime::io::Driver
          → self.poll.poll(events, timeout)
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">runtime::io::Driver</code> 的 struct 定义直接暴露了这一点<sup id="fnref:15"><a href="#fn:15" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/io/driver.rs: 25-37</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">Driver</span> <span class="p">{</span>
    <span class="cd">/// True when an event with the signal token is received</span>
    <span class="n">signal_ready</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>

    <span class="cd">/// Reuse the `mio::Events` value across calls to poll.</span>
    <span class="n">events</span><span class="p">:</span> <span class="nn">mio</span><span class="p">::</span><span class="n">Events</span><span class="p">,</span>

    <span class="cd">/// The system event queue.</span>
    <span class="n">poll</span><span class="p">:</span> <span class="nn">mio</span><span class="p">::</span><span class="n">Poll</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>它的 <code class="language-plaintext highlighter-rouge">park_timeout</code> 和 <code class="language-plaintext highlighter-rouge">turn()</code> 是实际执行 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 的地方<sup id="fnref:15:1"><a href="#fn:15" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/io/driver.rs: 138-154</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">park_timeout</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">rt_handle</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">driver</span><span class="p">::</span><span class="n">Handle</span><span class="p">,</span> <span class="n">duration</span><span class="p">:</span> <span class="n">Duration</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">handle</span> <span class="o">=</span> <span class="n">rt_handle</span><span class="nf">.io</span><span class="p">();</span>
    <span class="k">self</span><span class="nf">.turn</span><span class="p">(</span><span class="n">handle</span><span class="p">,</span> <span class="nf">Some</span><span class="p">(</span><span class="n">duration</span><span class="p">));</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">turn</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">handle</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Handle</span><span class="p">,</span> <span class="n">max_wait</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Duration</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">handle</span><span class="nf">.release_pending_registrations</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">events</span> <span class="o">=</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="py">.events</span><span class="p">;</span>

    <span class="c1">// 阻塞等待：这行是真正的 epoll_wait</span>
    <span class="k">match</span> <span class="k">self</span><span class="py">.poll</span><span class="nf">.poll</span><span class="p">(</span><span class="n">events</span><span class="p">,</span> <span class="n">max_wait</span><span class="p">)</span> <span class="p">{</span>   <span class="c1">// ← mio::Poll::poll → epoll_wait</span>
        <span class="nf">Ok</span><span class="p">(())</span> <span class="k">=&gt;</span> <span class="p">{}</span>
        <span class="nf">Err</span><span class="p">(</span><span class="k">ref</span> <span class="n">e</span><span class="p">)</span> <span class="k">if</span> <span class="n">e</span><span class="nf">.kind</span><span class="p">()</span> <span class="o">==</span> <span class="nn">io</span><span class="p">::</span><span class="nn">ErrorKind</span><span class="p">::</span><span class="n">Interrupted</span> <span class="k">=&gt;</span> <span class="p">{}</span>
        <span class="nf">Err</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="nd">panic!</span><span class="p">(</span><span class="s">"{e:?}"</span><span class="p">),</span>
    <span class="p">}</span>

    <span class="c1">// 分发事件给 ScheduledIo</span>
    <span class="k">for</span> <span class="n">event</span> <span class="k">in</span> <span class="n">events</span><span class="nf">.iter</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">ready</span> <span class="o">=</span> <span class="nn">Ready</span><span class="p">::</span><span class="nf">from_mio</span><span class="p">(</span><span class="n">event</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">ptr</span> <span class="o">=</span> <span class="k">super</span><span class="p">::</span><span class="n">EXPOSE_IO</span><span class="nf">.from_exposed_addr</span><span class="p">(</span><span class="n">event</span><span class="nf">.token</span><span class="p">()</span><span class="na">.0</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">io</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">ScheduledIo</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="o">&amp;*</span><span class="n">ptr</span> <span class="p">};</span>
        <span class="n">io</span><span class="nf">.set_readiness</span><span class="p">(</span><span class="nn">Tick</span><span class="p">::</span><span class="n">Set</span><span class="p">,</span> <span class="p">|</span><span class="n">curr</span><span class="p">|</span> <span class="n">curr</span> <span class="p">|</span> <span class="n">ready</span><span class="p">);</span>
        <span class="n">io</span><span class="nf">.wake</span><span class="p">(</span><span class="n">ready</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这些代码是 park 链最内层的一次调用。但多线程下，<strong>所有 worker 共享同一个 <code class="language-plaintext highlighter-rouge">runtime::io::Driver</code></strong>——通过 <code class="language-plaintext highlighter-rouge">TryLock&lt;Driver&gt;</code> 做排他访问<sup id="fnref:16"><a href="#fn:16" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/scheduler/multi_thread/park.rs: 48-65</span>
<span class="k">struct</span> <span class="n">Shared</span> <span class="p">{</span>
    <span class="cd">/// Shared driver. Only one thread at a time can use this</span>
    <span class="n">driver</span><span class="p">:</span> <span class="n">TryLock</span><span class="o">&lt;</span><span class="n">Driver</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>

<span class="c1">// park_timeout 时尝试拿锁：</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">park_timeout</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">handle</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">driver</span><span class="p">::</span><span class="n">Handle</span><span class="p">,</span> <span class="n">duration</span><span class="p">:</span> <span class="n">Duration</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">HadDriver</span> <span class="p">{</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="k">mut</span> <span class="n">driver</span><span class="p">)</span> <span class="o">=</span> <span class="k">self</span><span class="py">.inner.shared.driver</span><span class="nf">.try_lock</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 拿到锁 → 走 Driver::park_timeout → epoll_wait</span>
        <span class="k">self</span><span class="py">.inner</span><span class="nf">.park_driver</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">driver</span><span class="p">,</span> <span class="n">handle</span><span class="p">,</span> <span class="nf">Some</span><span class="p">(</span><span class="n">duration</span><span class="p">))</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">!</span><span class="n">duration</span><span class="nf">.is_zero</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 没拿到锁 → 退化为 condvar 等待</span>
        <span class="k">self</span><span class="py">.inner</span><span class="nf">.park_condvar</span><span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="n">duration</span><span class="p">));</span>
        <span class="nn">HadDriver</span><span class="p">::</span><span class="n">No</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这就是为什么一个 Runtime 五个 Driver struct 但只一个 <code class="language-plaintext highlighter-rouge">io::Driver</code>（一个 <code class="language-plaintext highlighter-rouge">mio::Poll</code> / 一个 epoll fd）：<strong>epoll fd 不能被多个线程同时 <code class="language-plaintext highlighter-rouge">epoll_wait</code>，所以多线程下通过 <code class="language-plaintext highlighter-rouge">TryLock</code> 串行化访问</strong>。拿不到锁的线程退化为 condvar 等待，等拿到锁的线程执行完 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 后唤醒它。</p>

<p><strong>TcpStream 不走 park 链</strong>。它不是通过 <code class="language-plaintext highlighter-rouge">park()</code> 去用 <code class="language-plaintext highlighter-rouge">mio::Poll</code> 的，而是直接通过 <code class="language-plaintext highlighter-rouge">Handle::add_source()</code> 把 fd 注册到<strong>同一个 <code class="language-plaintext highlighter-rouge">mio::Poll</code> 实例</strong>上：<sup id="fnref:15:2"><a href="#fn:15" class="footnote" rel="footnote" role="doc-noteref">7</a></sup></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/io/driver.rs: 182-207 (Handle::add_source)</span>
<span class="k">pub</span><span class="p">(</span><span class="k">super</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">add_source</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">source</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="k">impl</span> <span class="nn">mio</span><span class="p">::</span><span class="nn">event</span><span class="p">::</span><span class="n">Source</span><span class="p">,</span> <span class="n">interest</span><span class="p">:</span> <span class="n">Interest</span><span class="p">)</span>
    <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="nb">Arc</span><span class="o">&lt;</span><span class="n">ScheduledIo</span><span class="o">&gt;&gt;</span>
<span class="p">{</span>
    <span class="k">let</span> <span class="n">scheduled_io</span> <span class="o">=</span> <span class="k">self</span><span class="py">.registrations</span><span class="nf">.allocate</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="py">.synced</span><span class="nf">.lock</span><span class="p">())</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">token</span> <span class="o">=</span> <span class="n">scheduled_io</span><span class="nf">.token</span><span class="p">();</span>
    <span class="k">self</span><span class="py">.registry</span><span class="nf">.register</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">token</span><span class="p">,</span> <span class="n">interest</span><span class="nf">.to_mio</span><span class="p">())</span><span class="o">?</span><span class="p">;</span>
    <span class="c1">//  ↑ 通过 mio::Registry::register → epoll_ctl(EPOLL_CTL_ADD)</span>
    <span class="nf">Ok</span><span class="p">(</span><span class="n">scheduled_io</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>而 park 链是通过 <code class="language-plaintext highlighter-rouge">self.poll.poll(events, timeout)</code>（即 <code class="language-plaintext highlighter-rouge">epoll_wait</code>）来<strong>消费</strong>事件。注册（<code class="language-plaintext highlighter-rouge">epoll_ctl</code>）和等待（<code class="language-plaintext highlighter-rouge">epoll_wait</code>）都在同一个 <code class="language-plaintext highlighter-rouge">mio::Poll</code> 上完成：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>                   同一个 mio::Poll 实例
                   ┌──────────────────────┐
                   │                      │
  epoll_ctl ADD ◄──┤  runtime::io::Driver  ├──► epoll_wait
  (TcpStream 注册)  │     { poll }         │  (park 链等待)
                   │                      │
                   └──────────────────────┘
                              ▲
                 ┌────────────┼────────────┐
                 │            │            │
            Timer park   Signal park   Process park
            epoll_wait   epoll_wait    epoll_wait
            (timeout=5s) (timeout=-1)  (timeout=-1)
</code></pre></div></div>

<p>换个角度看，五个 <code class="language-plaintext highlighter-rouge">Driver</code> struct 的关系是：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Driver</th>
      <th style="text-align: left">作用</th>
      <th style="text-align: left">是否调用 epoll_wait</th>
      <th style="text-align: left">是否注册 fd</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">driver::Driver</code></td>
      <td style="text-align: left">最外层调度入口</td>
      <td style="text-align: left">否，委派给 TimeDriver</td>
      <td style="text-align: left">否</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">time::Driver</code></td>
      <td style="text-align: left">查时间轮 + 设 timeout</td>
      <td style="text-align: left">否，委派给 IoStack</td>
      <td style="text-align: left">否</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">process::Driver</code></td>
      <td style="text-align: left">子进程清理</td>
      <td style="text-align: left">否，委派给 SignalDriver</td>
      <td style="text-align: left">否</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">signal::Driver</code></td>
      <td style="text-align: left">信号处理</td>
      <td style="text-align: left">否，委派给 io::Driver</td>
      <td style="text-align: left">是，注册一个 UnixStream</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">io::Driver</code></strong></td>
      <td style="text-align: left"><strong>真正的 epoll 封装</strong></td>
      <td style="text-align: left"><strong>是</strong></td>
      <td style="text-align: left"><strong>是，所有 fd</strong></td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">runtime::io::Driver</code> 是最底层——<strong>它是唯一真正执行 epoll 系统调用、唯一管理 epoll 注册表的那一层</strong>。上面四层 Driver 全部通过 <code class="language-plaintext highlighter-rouge">io::Driver</code> 间接完成等待，没有自己的 epoll 实例。</p>

<p>这也就意味着：<strong>即使一个 Worker 线程没有 Timer（<code class="language-plaintext highlighter-rouge">time</code> feature 关闭）、没有 Signal、没有 Process，它依然需要一个 <code class="language-plaintext highlighter-rouge">runtime::io::Driver</code> 来运行 <code class="language-plaintext highlighter-rouge">mio::Poll::poll()</code>——否则连基本的 TcpStream 等待都没法做。</strong> <code class="language-plaintext highlighter-rouge">io::Driver</code> 是唯一的硬性依赖，其他 Driver 都是可选的 wrapper。</p>

<h3 id="14-driver-分层设计要点总结">1.4 Driver 分层设计要点总结</h3>

<p>将前文的分析浓缩为以下七条：</p>

<ol>
  <li>
    <p><strong>Tokio 内部共有五层 Drivers</strong>：<code class="language-plaintext highlighter-rouge">runtime::driver::Driver</code>、<code class="language-plaintext highlighter-rouge">runtime::time::Driver</code>、<code class="language-plaintext highlighter-rouge">runtime::process::Driver</code>、<code class="language-plaintext highlighter-rouge">runtime::signal::Driver</code>、<code class="language-plaintext highlighter-rouge">runtime::io::Driver</code>，以及两个辅助 enum <code class="language-plaintext highlighter-rouge">TimeDriver</code> 和 <code class="language-plaintext highlighter-rouge">IoStack</code>。</p>
  </li>
  <li>
    <p><strong>没有共享的 trait</strong>：这些 Drivers 虽然都提供 <code class="language-plaintext highlighter-rouge">park</code>/<code class="language-plaintext highlighter-rouge">park_timeout</code>/<code class="language-plaintext highlighter-rouge">shutdown</code> 方法，接口签名完全一致，但<strong>不是</strong>通过 <code class="language-plaintext highlighter-rouge">impl SomeTrait for Driver</code> 实现的——每个 <code class="language-plaintext highlighter-rouge">impl</code> 块是独立定义的，只有人工约定的签名一致性。</p>
  </li>
  <li>
    <p><strong>五层构成一条线性调用链</strong>：最外层 <code class="language-plaintext highlighter-rouge">runtime::driver::Driver</code> → <code class="language-plaintext highlighter-rouge">TimeDriver</code> → <code class="language-plaintext highlighter-rouge">time::Driver</code> → <code class="language-plaintext highlighter-rouge">IoStack</code> → <code class="language-plaintext highlighter-rouge">process::Driver</code> → <code class="language-plaintext highlighter-rouge">signal::Driver</code> → <code class="language-plaintext highlighter-rouge">io::Driver</code>（最底层）。链的长度可通过条件编译缩短，但顺序不变。</p>
  </li>
  <li>
    <p><strong>每一层调用下一层的 <code class="language-plaintext highlighter-rouge">park</code> 方法</strong>：通过 <code class="language-plaintext highlighter-rouge">self.next.park(handle)</code> 或 <code class="language-plaintext highlighter-rouge">self.next.park_timeout(handle, duration)</code> 委派，最终到达 <code class="language-plaintext highlighter-rouge">io::Driver::turn()</code> → <code class="language-plaintext highlighter-rouge">self.poll.poll()</code>（<code class="language-plaintext highlighter-rouge">epoll_wait</code> / <code class="language-plaintext highlighter-rouge">kevent</code>）。</p>
  </li>
  <li>
    <p><strong>前序调用与后序调用并存</strong>：<code class="language-plaintext highlighter-rouge">time::Driver</code> 是先查时间轮再委派（前序），<code class="language-plaintext highlighter-rouge">process::Driver</code> 和 <code class="language-plaintext highlighter-rouge">signal::Driver</code> 是先委派再处理自己的逻辑（后序）。</p>
  </li>
  <li>
    <p><strong>条件编译可缩短链</strong>：通过 Cargo features（<code class="language-plaintext highlighter-rouge">net</code>、<code class="language-plaintext highlighter-rouge">time</code>、<code class="language-plaintext highlighter-rouge">process</code>、<code class="language-plaintext highlighter-rouge">signal</code>）和 Builder 选项（<code class="language-plaintext highlighter-rouge">.enable_io()</code>、<code class="language-plaintext highlighter-rouge">.enable_time()</code>）控制每层的启用。关掉所有可选层后，最少剩 <code class="language-plaintext highlighter-rouge">Driver</code> → <code class="language-plaintext highlighter-rouge">TimeDriver</code> → <code class="language-plaintext highlighter-rouge">IoStack::Disabled(ParkThread)</code>。</p>
  </li>
  <li>
    <p><strong>每层全局唯一实例</strong>：一个 Runtime 恰好一套 Driver 实例，按值嵌套在 <code class="language-plaintext highlighter-rouge">runtime::driver::Driver</code> 这一个 struct 中（不是全局 static 变量，是 <code class="language-plaintext highlighter-rouge">Runtime</code> 的字段）。多线程下所有 worker 共享同一个 <code class="language-plaintext highlighter-rouge">TryLock&lt;Driver&gt;</code>。</p>
  </li>
</ol>

<h2 id="二tcpstream-的创建一条完整的注册链">二、TcpStream 的创建：一条完整的注册链</h2>

<p>现在来看 <code class="language-plaintext highlighter-rouge">TcpStream::connect("127.0.0.1:8080")</code> 背后到底发生了什么。</p>

<h3 id="21-从-tcpstream-到-pollevented">2.1 从 TcpStream 到 PollEvented</h3>

<p><code class="language-plaintext highlighter-rouge">TcpStream</code> 的结构非常简单——它只是一个 <code class="language-plaintext highlighter-rouge">PollEvented</code> 的包装<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/net/tcp/stream.rs: 72-74</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">TcpStream</span> <span class="p">{</span>
    <span class="n">io</span><span class="p">:</span> <span class="n">PollEvented</span><span class="o">&lt;</span><span class="nn">mio</span><span class="p">::</span><span class="nn">net</span><span class="p">::</span><span class="n">TcpStream</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">PollEvented</code> 是 Tokio 的一个通用包装器：它接收一个实现了 <code class="language-plaintext highlighter-rouge">mio::event::Source</code> 的类型（如 <code class="language-plaintext highlighter-rouge">mio::net::TcpStream</code>），将其注册到运行时的 I/O 驱动上<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">10</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/io/poll_evented.rs: 89-94</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">PollEvented</span><span class="o">&lt;</span><span class="n">E</span><span class="p">:</span> <span class="n">Source</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="n">io</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">E</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">registration</span><span class="p">:</span> <span class="n">Registration</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>创建 <code class="language-plaintext highlighter-rouge">PollEvented</code> 时，需要拿到当前 runtime 的调度器句柄（<code class="language-plaintext highlighter-rouge">scheduler::Handle</code>），然后用它来完成注册<sup id="fnref:4:1"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">10</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/io/poll_evented.rs: 130-139</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">new_with_interest_and_handle</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">io</span><span class="p">:</span> <span class="n">E</span><span class="p">,</span>
    <span class="n">interest</span><span class="p">:</span> <span class="n">Interest</span><span class="p">,</span>
    <span class="n">handle</span><span class="p">:</span> <span class="nn">scheduler</span><span class="p">::</span><span class="n">Handle</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">registration</span> <span class="o">=</span> <span class="nn">Registration</span><span class="p">::</span><span class="nf">new_with_interest_and_handle</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">io</span><span class="p">,</span> <span class="n">interest</span><span class="p">,</span> <span class="n">handle</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nf">Ok</span><span class="p">(</span><span class="k">Self</span> <span class="p">{</span> <span class="n">io</span><span class="p">:</span> <span class="nf">Some</span><span class="p">(</span><span class="n">io</span><span class="p">),</span> <span class="n">registration</span> <span class="p">})</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">TcpStream::new</code> 只做了一件事：把 <code class="language-plaintext highlighter-rouge">mio::net::TcpStream</code> 交给 <code class="language-plaintext highlighter-rouge">PollEvented::new</code><sup id="fnref:3:1"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/net/tcp/stream.rs: 160-163</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">connected</span><span class="p">:</span> <span class="nn">mio</span><span class="p">::</span><span class="nn">net</span><span class="p">::</span><span class="n">TcpStream</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="n">TcpStream</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">io</span> <span class="o">=</span> <span class="nn">PollEvented</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">connected</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nf">Ok</span><span class="p">(</span><span class="n">TcpStream</span> <span class="p">{</span> <span class="n">io</span> <span class="p">})</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="22-从-pollevented-到-registration">2.2 从 PollEvented 到 Registration</h3>

<p><code class="language-plaintext highlighter-rouge">Registration</code> 是更底层的抽象。它持有一个 <code class="language-plaintext highlighter-rouge">Arc&lt;ScheduledIo&gt;</code>，后者是 I/O 驱动中真正管理状态的结构<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">11</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/io/registration.rs: 64-72</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">Registration</span> <span class="p">{</span>
    <span class="n">handle</span><span class="p">:</span> <span class="nn">scheduler</span><span class="p">::</span><span class="n">Handle</span><span class="p">,</span>
    <span class="n">shared</span><span class="p">:</span> <span class="nb">Arc</span><span class="o">&lt;</span><span class="n">ScheduledIo</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Registration::new_with_interest_and_handle</code> 是整条注册链的关键入口<sup id="fnref:5:1"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">11</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/io/registration.rs: 91-98</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">new_with_interest_and_handle</span><span class="p">(</span>
    <span class="n">io</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="k">impl</span> <span class="n">Source</span><span class="p">,</span>
    <span class="n">interest</span><span class="p">:</span> <span class="n">Interest</span><span class="p">,</span>
    <span class="n">handle</span><span class="p">:</span> <span class="nn">scheduler</span><span class="p">::</span><span class="n">Handle</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="n">Registration</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">shared</span> <span class="o">=</span> <span class="n">handle</span><span class="nf">.driver</span><span class="p">()</span><span class="nf">.io</span><span class="p">()</span><span class="nf">.add_source</span><span class="p">(</span><span class="n">io</span><span class="p">,</span> <span class="n">interest</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nf">Ok</span><span class="p">(</span><span class="n">Registration</span> <span class="p">{</span> <span class="n">handle</span><span class="p">,</span> <span class="n">shared</span> <span class="p">})</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这里 <code class="language-plaintext highlighter-rouge">handle.driver().io()</code> 拿到 I/O Driver 的 <code class="language-plaintext highlighter-rouge">Handle</code>，然后调用 <code class="language-plaintext highlighter-rouge">add_source</code>。</p>

<h3 id="23-从-registration-到-io-driver">2.3 从 Registration 到 I/O Driver</h3>

<p><code class="language-plaintext highlighter-rouge">Handle::add_source</code> 是真正把 fd 注册到 epoll 的地方<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">12</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/io/driver.rs: 182-207</span>
<span class="k">pub</span><span class="p">(</span><span class="k">super</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">add_source</span><span class="p">(</span>
    <span class="o">&amp;</span><span class="k">self</span><span class="p">,</span>
    <span class="n">source</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="k">impl</span> <span class="nn">mio</span><span class="p">::</span><span class="nn">event</span><span class="p">::</span><span class="n">Source</span><span class="p">,</span>
    <span class="n">interest</span><span class="p">:</span> <span class="n">Interest</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="nb">Arc</span><span class="o">&lt;</span><span class="n">ScheduledIo</span><span class="o">&gt;&gt;</span> <span class="p">{</span>
    <span class="c1">// 1. 从 RegistrationSet 中分配一个新的 ScheduledIo</span>
    <span class="k">let</span> <span class="n">scheduled_io</span> <span class="o">=</span> <span class="k">self</span><span class="py">.registrations</span><span class="nf">.allocate</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="py">.synced</span><span class="nf">.lock</span><span class="p">())</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">token</span> <span class="o">=</span> <span class="n">scheduled_io</span><span class="nf">.token</span><span class="p">();</span>

    <span class="c1">// 2. 通过 mio::Registry 将 source 注册到 epoll</span>
    <span class="c1">//    token 是 ScheduledIo 对象的指针值（作为 epoll 的标识）</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nf">Err</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="o">=</span> <span class="k">self</span><span class="py">.registry</span><span class="nf">.register</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">token</span><span class="p">,</span> <span class="n">interest</span><span class="nf">.to_mio</span><span class="p">())</span> <span class="p">{</span>
        <span class="c1">// 注册失败：从 set 中移除分配的 ScheduledIo</span>
        <span class="k">unsafe</span> <span class="p">{</span> <span class="k">self</span><span class="py">.registrations</span><span class="nf">.remove</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="py">.synced</span><span class="nf">.lock</span><span class="p">(),</span> <span class="o">&amp;</span><span class="n">scheduled_io</span><span class="p">)</span> <span class="p">};</span>
        <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">self</span><span class="py">.metrics</span><span class="nf">.incr_fd_count</span><span class="p">();</span>
    <span class="nf">Ok</span><span class="p">(</span><span class="n">scheduled_io</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这里有一个关键设计：<strong>token 是 <code class="language-plaintext highlighter-rouge">ScheduledIo</code> 对象的地址</strong>。<code class="language-plaintext highlighter-rouge">mio::Token</code> 是一个 <code class="language-plaintext highlighter-rouge">usize</code>，而 <code class="language-plaintext highlighter-rouge">ScheduledIo</code> 是一个 <code class="language-plaintext highlighter-rouge">Arc</code> 管理的堆对象。<code class="language-plaintext highlighter-rouge">add_source</code> 使用 <code class="language-plaintext highlighter-rouge">PtrExposeDomain</code> 把 <code class="language-plaintext highlighter-rouge">ScheduledIo</code> 的地址暴露为 <code class="language-plaintext highlighter-rouge">mio::Token</code>，这样 epoll 返回事件时，Tokio 可以直接通过 token 反向找到对应的 <code class="language-plaintext highlighter-rouge">ScheduledIo</code><sup id="fnref:6:1"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">12</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/io/scheduled_io.rs: 268-270</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">token</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">mio</span><span class="p">::</span><span class="n">Token</span> <span class="p">{</span>
    <span class="nn">mio</span><span class="p">::</span><span class="nf">Token</span><span class="p">(</span><span class="k">super</span><span class="p">::</span><span class="n">EXPOSE_IO</span><span class="nf">.expose_provenance</span><span class="p">(</span><span class="k">self</span><span class="p">))</span>
<span class="p">}</span>
</code></pre></div></div>

<p>整条注册链可以用下图来总结：</p>

<pre><code class="language-mermaid">graph TD
    A["TcpStream::connect(addr)"] --&gt; B["mio::net::TcpStream::connect(addr)"]
    B --&gt; C["TcpStream::new(sys)"]
    C --&gt; D["PollEvented::new(connected)"]
    D --&gt; E["Registration::new_with_interest_and_handle"]
    E --&gt; F["Handle::add_source"]
    F --&gt; G["RegistrationSet::allocate → Arc&lt;ScheduledIo&gt;"]
    F --&gt; H["mio::Registry::register(source, token, interest)"]
    H --&gt; I["epoll_ctl(EPOLL_CTL_ADD, fd, ...)"]

    style G fill:#e1f5fe
    style H fill:#e1f5fe
    style I fill:#c8e6c9
</code></pre>

<p>注册完成后，<code class="language-plaintext highlighter-rouge">TcpStream</code> 的 <code class="language-plaintext highlighter-rouge">PollEvented</code> 内部持有一个 <code class="language-plaintext highlighter-rouge">Registration</code>，<code class="language-plaintext highlighter-rouge">Registration</code> 内部持有一个 <code class="language-plaintext highlighter-rouge">Arc&lt;ScheduledIo&gt;</code>，而 <code class="language-plaintext highlighter-rouge">ScheduledIo</code> 的地址就是它在 epoll 中的 token。通过这个 token，epoll 事件可以反向定位到 <code class="language-plaintext highlighter-rouge">ScheduledIo</code>，运行时可快速处理就绪事件。</p>

<h2 id="三scheduledio边沿触发的原子状态机">三、ScheduledIo：边沿触发的原子状态机</h2>

<p><code class="language-plaintext highlighter-rouge">ScheduledIo</code> 是 I/O 驱动的核心数据结构。如果说 Timer 驱动的核心是哈希时间轮和 <code class="language-plaintext highlighter-rouge">TimerShared</code>，那 I/O 驱动的核心就是 <code class="language-plaintext highlighter-rouge">ScheduledIo</code> 和它的原子状态<sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">13</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/io/scheduled_io.rs: 59-61</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">ScheduledIo</span> <span class="p">{</span>
    <span class="n">linked_list_pointers</span><span class="p">:</span> <span class="n">UnsafeCell</span><span class="o">&lt;</span><span class="nn">linked_list</span><span class="p">::</span><span class="n">Pointers</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">readiness</span><span class="p">:</span> <span class="n">AtomicUsize</span><span class="p">,</span>     <span class="c1">// 打包的 readiness 状态</span>
    <span class="n">waiters</span><span class="p">:</span> <span class="n">Mutex</span><span class="o">&lt;</span><span class="n">Waiters</span><span class="o">&gt;</span><span class="p">,</span>    <span class="c1">// 等待队列</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="31-打包的原子状态">3.1 打包的原子状态</h3>

<p><code class="language-plaintext highlighter-rouge">readiness</code> 字段是一个 <code class="language-plaintext highlighter-rouge">AtomicUsize</code>，但它承载了三个独立的信息，通过位打包（bit packing）放在一个原子变量里<sup id="fnref:7:1"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">13</a></sup>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/io/scheduled_io.rs: 224-230</span>
<span class="c1">// | shutdown | driver tick | readiness |</span>
<span class="c1">// |----------+-------------+-----------|</span>
<span class="c1">// |   1 bit  |   15 bits   |  16 bits  |</span>

<span class="k">const</span> <span class="n">READINESS</span><span class="p">:</span> <span class="nn">bit</span><span class="p">::</span><span class="n">Pack</span> <span class="o">=</span> <span class="nn">bit</span><span class="p">::</span><span class="nn">Pack</span><span class="p">::</span><span class="nf">least_significant</span><span class="p">(</span><span class="mi">16</span><span class="p">);</span>
<span class="k">const</span> <span class="n">TICK</span><span class="p">:</span> <span class="nn">bit</span><span class="p">::</span><span class="n">Pack</span> <span class="o">=</span> <span class="n">READINESS</span><span class="nf">.then</span><span class="p">(</span><span class="mi">15</span><span class="p">);</span>
<span class="k">const</span> <span class="n">SHUTDOWN</span><span class="p">:</span> <span class="nn">bit</span><span class="p">::</span><span class="n">Pack</span> <span class="o">=</span> <span class="n">TICK</span><span class="nf">.then</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
</code></pre></div></div>

<ul>
  <li><strong>readiness（16 位）</strong>：当前就绪状态的位掩码（可读、可写、错误、挂起等）</li>
  <li><strong>tick（15 位）</strong>：I/O 驱动的 tick 计数器，每次 <code class="language-plaintext highlighter-rouge">set_readiness</code> 递增，用于防止清理旧的就绪事件</li>
  <li><strong>shutdown（1 位）</strong>：I/O 驱动是否已关闭</li>
</ul>

<p>三个状态打包在一个 <code class="language-plaintext highlighter-rouge">AtomicUsize</code> 中，意味着对 readiness 的更新和检查都是原子操作，<strong>无需外部锁</strong>。</p>

<h3 id="32-和-timershared-的对比">3.2 和 TimerShared 的对比</h3>

<p>上篇文章介绍了 <code class="language-plaintext highlighter-rouge">TimerShared</code> 及其 <code class="language-plaintext highlighter-rouge">StateCell</code>（用 <code class="language-plaintext highlighter-rouge">AtomicU64</code> 实现的原子状态机）。两者都是原子状态机，但解决的问题不同：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">方面</th>
      <th style="text-align: left"><code class="language-plaintext highlighter-rouge">TimerShared</code> (sleep)</th>
      <th style="text-align: left"><code class="language-plaintext highlighter-rouge">ScheduledIo</code> (I/O)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>核心状态</strong></td>
      <td style="text-align: left">到期时间、是否已触发</td>
      <td style="text-align: left">readiness、tick</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>驱动方式</strong></td>
      <td style="text-align: left">时间轮到期触发</td>
      <td style="text-align: left">epoll 事件通知</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>更新方式</strong></td>
      <td style="text-align: left">CAS 循环（<code class="language-plaintext highlighter-rouge">set_expiration</code>/<code class="language-plaintext highlighter-rouge">mark_pending</code>）</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">fetch_update</code>（<code class="language-plaintext highlighter-rouge">set_readiness</code>）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>通知机制</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">fire()</code> → <code class="language-plaintext highlighter-rouge">Waker::wake()</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">wake(ready)</code> → <code class="language-plaintext highlighter-rouge">Waker::wake()</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>并发角色</strong></td>
      <td style="text-align: left">用户端（<code class="language-plaintext highlighter-rouge">TimerEntry</code>）+ 驱动端（<code class="language-plaintext highlighter-rouge">Driver</code>）</td>
      <td style="text-align: left">用户端（<code class="language-plaintext highlighter-rouge">Registration</code>）+ 驱动端（<code class="language-plaintext highlighter-rouge">Driver</code>）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>数据结构</strong></td>
      <td style="text-align: left">侵入式链表节点（用于时间轮 slot）</td>
      <td style="text-align: left">侵入式链表节点（用于 RegistrationSet）</td>
    </tr>
  </tbody>
</table>

<h3 id="33-边沿触发et模型为什么需要-tick">3.3 边沿触发（ET）模型：为什么需要 tick</h3>

<p>mio 在 Linux 上默认使用 epoll 的<strong>边沿触发（Edge-Triggered, ET）模式</strong>。ET 模式的特点是：<strong>只在状态从「不可用」变为「可用」时通知一次</strong>。这意味着：</p>

<ol>
  <li>epoll 通知 readable → <code class="language-plaintext highlighter-rouge">ScheduledIo</code> 设置 READABLE 位</li>
  <li>用户尝试 <code class="language-plaintext highlighter-rouge">read()</code> → 成功读到部分数据</li>
  <li>epoll <strong>不会再次通知</strong>（因为状态没有从不可读变为可读）</li>
  <li>用户可以继续 <code class="language-plaintext highlighter-rouge">read()</code> 直到返回 <code class="language-plaintext highlighter-rouge">WouldBlock</code> → 清除 READABLE 位 → 下次 epoll 通知再来</li>
</ol>

<p>但这里有一个微妙的问题：<strong>如何防止清理旧事件时误清新事件？</strong> 这就是 tick 的作用。</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant EPoll as epoll
    participant SI as ScheduledIo
    participant Task as User Task

    EPoll-&gt;&gt;SI: poll 返回 readable（tick=1）
    SI-&gt;&gt;SI: set_readiness: 设置 READABLE + tick=2
    SI--&gt;&gt;Task: wake()
    Task-&gt;&gt;SI: poll_read_ready()
    SI--&gt;&gt;Task: READABLE（tick=2）
    Task-&gt;&gt;Task: read() → 成功
    Note over Task: 还没读完...
    EPoll-&gt;&gt;SI: 新数据到达，再次通知（tick=3）
    SI-&gt;&gt;SI: set_readiness: tick=3
    Task-&gt;&gt;SI: clear_readiness(tick=2)  ← 旧 tick
    SI-&gt;&gt;SI: tick 不匹配 → 无操作
    Task-&gt;&gt;SI: poll_read_ready()
    SI--&gt;&gt;Task: READABLE（tick=3 时设置）
    Task-&gt;&gt;Task: 继续读取
</code></pre>

<p>关键机制在 <code class="language-plaintext highlighter-rouge">set_readiness</code> 中<sup id="fnref:7:2"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">13</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/io/scheduled_io.rs: 277-298</span>
<span class="k">pub</span><span class="p">(</span><span class="k">super</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">set_readiness</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">tick_op</span><span class="p">:</span> <span class="n">Tick</span><span class="p">,</span> <span class="n">f</span><span class="p">:</span> <span class="k">impl</span> <span class="nf">Fn</span><span class="p">(</span><span class="n">Ready</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Ready</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">_</span> <span class="o">=</span> <span class="k">self</span><span class="py">.readiness</span><span class="nf">.fetch_update</span><span class="p">(</span><span class="n">AcqRel</span><span class="p">,</span> <span class="n">Acquire</span><span class="p">,</span> <span class="p">|</span><span class="n">curr</span><span class="p">|</span> <span class="p">{</span>
        <span class="k">const</span> <span class="n">MAX_TICK</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="n">TICK</span><span class="nf">.max_value</span><span class="p">()</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">tick</span> <span class="o">=</span> <span class="n">TICK</span><span class="nf">.unpack</span><span class="p">(</span><span class="n">curr</span><span class="p">);</span>

        <span class="k">let</span> <span class="n">new_tick</span> <span class="o">=</span> <span class="k">match</span> <span class="n">tick_op</span> <span class="p">{</span>
            <span class="c1">// 清理 readiness 时：如果 tick 不匹配，跳过此次操作</span>
            <span class="nn">Tick</span><span class="p">::</span><span class="nf">Clear</span><span class="p">(</span><span class="n">t</span><span class="p">)</span> <span class="k">if</span> <span class="n">tick</span> <span class="k">as</span> <span class="nb">u8</span> <span class="o">!=</span> <span class="n">t</span> <span class="k">=&gt;</span> <span class="k">return</span> <span class="nb">None</span><span class="p">,</span>
            <span class="nn">Tick</span><span class="p">::</span><span class="nf">Clear</span><span class="p">(</span><span class="n">t</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="n">t</span> <span class="k">as</span> <span class="nb">usize</span><span class="p">,</span>
            <span class="c1">// 设置 readiness 时：递增 tick</span>
            <span class="nn">Tick</span><span class="p">::</span><span class="n">Set</span> <span class="k">=&gt;</span> <span class="n">tick</span><span class="nf">.wrapping_add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="o">%</span> <span class="n">MAX_TICK</span><span class="p">,</span>
        <span class="p">};</span>
        <span class="k">let</span> <span class="n">ready</span> <span class="o">=</span> <span class="nn">Ready</span><span class="p">::</span><span class="nf">from_usize</span><span class="p">(</span><span class="n">READINESS</span><span class="nf">.unpack</span><span class="p">(</span><span class="n">curr</span><span class="p">));</span>
        <span class="nf">Some</span><span class="p">(</span><span class="n">TICK</span><span class="nf">.pack</span><span class="p">(</span><span class="n">new_tick</span><span class="p">,</span> <span class="nf">f</span><span class="p">(</span><span class="n">ready</span><span class="p">)</span><span class="nf">.as_usize</span><span class="p">()))</span>
    <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">fetch_update</code> 是一个乐观 CAS 循环：它尝试读取当前值、调用闭包生成新值、然后原子地替换。如果闭包返回 <code class="language-plaintext highlighter-rouge">None</code>（例如清理时的 tick 不匹配），则不进行任何更新。</p>

<p>这正是 ET 模型所需要的：<strong>每次 epoll 通知都会递增 tick，旧的事件清不到新 tick 的脏位</strong>。如果没有这个 tick 机制，一个慢任务可能在清理旧事件时无意中清掉了新到达的事件，导致数据静默丢失。</p>

<h3 id="34-waiters等待队列">3.4 Waiters：等待队列</h3>

<p><code class="language-plaintext highlighter-rouge">ScheduledIo</code> 的第三个字段 <code class="language-plaintext highlighter-rouge">waiters</code> 管理了等待此 I/O 资源的任务<sup id="fnref:7:3"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">13</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/io/scheduled_io.rs: 70-78</span>
<span class="nd">#[derive(Debug,</span> <span class="nd">Default)]</span>
<span class="k">struct</span> <span class="n">Waiters</span> <span class="p">{</span>
    <span class="cd">/// 所有等待者的侵入式链表</span>
    <span class="n">list</span><span class="p">:</span> <span class="n">WaitList</span><span class="p">,</span>

    <span class="cd">/// AsyncRead 专用 waker 槽</span>
    <span class="n">reader</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Waker</span><span class="o">&gt;</span><span class="p">,</span>

    <span class="cd">/// AsyncWrite 专用 waker 槽</span>
    <span class="n">writer</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Waker</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这里有两个并行的机制：</p>

<ol>
  <li><strong>专用 waker 槽</strong>（<code class="language-plaintext highlighter-rouge">reader</code> / <code class="language-plaintext highlighter-rouge">writer</code>）：用于 <code class="language-plaintext highlighter-rouge">poll_read_ready</code> / <code class="language-plaintext highlighter-rouge">poll_write_ready</code> 快速路径——每个方向只有一个任务在等待，直接用 <code class="language-plaintext highlighter-rouge">Option&lt;Waker&gt;</code> 存，无需链表</li>
  <li><strong>通用等待链表</strong>：用于 <code class="language-plaintext highlighter-rouge">readiness()</code> async 方法——支持多个任务同时等待同一个 <code class="language-plaintext highlighter-rouge">ScheduledIo</code> 的不同 Interest</li>
</ol>

<p>当 I/O 事件到达时，<code class="language-plaintext highlighter-rouge">wake()</code> 方法遍历这两者并唤醒对应的任务<sup id="fnref:7:4"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">13</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/io/scheduled_io.rs: 304-348</span>
<span class="k">pub</span><span class="p">(</span><span class="k">super</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">wake</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">ready</span><span class="p">:</span> <span class="n">Ready</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">wakers</span> <span class="o">=</span> <span class="nn">WakeList</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">waiters</span> <span class="o">=</span> <span class="k">self</span><span class="py">.waiters</span><span class="nf">.lock</span><span class="p">();</span>

    <span class="c1">// 检查 read 槽</span>
    <span class="k">if</span> <span class="n">ready</span><span class="nf">.is_readable</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">waker</span><span class="p">)</span> <span class="o">=</span> <span class="n">waiters</span><span class="py">.reader</span><span class="nf">.take</span><span class="p">()</span> <span class="p">{</span>
            <span class="n">wakers</span><span class="nf">.push</span><span class="p">(</span><span class="n">waker</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="c1">// 检查 write 槽</span>
    <span class="k">if</span> <span class="n">ready</span><span class="nf">.is_writable</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">waker</span><span class="p">)</span> <span class="o">=</span> <span class="n">waiters</span><span class="py">.writer</span><span class="nf">.take</span><span class="p">()</span> <span class="p">{</span>
            <span class="n">wakers</span><span class="nf">.push</span><span class="p">(</span><span class="n">waker</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// 遍历链表中的等待者</span>
    <span class="nv">'outer</span><span class="p">:</span> <span class="k">loop</span> <span class="p">{</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">iter</span> <span class="o">=</span> <span class="n">waiters</span><span class="py">.list</span><span class="nf">.drain_filter</span><span class="p">(|</span><span class="n">w</span><span class="p">|</span> <span class="n">ready</span><span class="nf">.satisfies</span><span class="p">(</span><span class="n">w</span><span class="py">.interest</span><span class="p">));</span>
        <span class="k">while</span> <span class="n">wakers</span><span class="nf">.can_push</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">match</span> <span class="n">iter</span><span class="nf">.next</span><span class="p">()</span> <span class="p">{</span>
                <span class="nf">Some</span><span class="p">(</span><span class="n">waiter</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
                    <span class="c1">// ... 收集 waker，标记已就绪</span>
                <span class="p">}</span>
                <span class="nb">None</span> <span class="k">=&gt;</span> <span class="k">break</span> <span class="nv">'outer</span><span class="p">,</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="c1">// 批次满了：释放锁后批量唤醒</span>
        <span class="nf">drop</span><span class="p">(</span><span class="n">waiters</span><span class="p">);</span>
        <span class="n">wakers</span><span class="nf">.wake_all</span><span class="p">();</span>
        <span class="n">waiters</span> <span class="o">=</span> <span class="k">self</span><span class="py">.waiters</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="p">}</span>
    <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">wake_all</code> 在释放锁之后进行，避免持锁时调用外部代码导致死锁——这和上篇文章中 Time Driver 的 <code class="language-plaintext highlighter-rouge">waker_list.wake_all()</code> 是相同的模式。</p>

<h2 id="四事件循环turn">四、事件循环：turn()</h2>

<p>当一切就绪后，是谁来驱动 I/O 事件的处理的？答案是 I/O Driver 的 <code class="language-plaintext highlighter-rouge">turn()</code> 方法<sup id="fnref:6:2"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">12</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/io/driver.rs: 122-174</span>
<span class="k">fn</span> <span class="nf">turn</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">handle</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Handle</span><span class="p">,</span> <span class="n">max_wait</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Duration</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 处理待释放的注册（被 drop 的 Registration）</span>
    <span class="n">handle</span><span class="nf">.release_pending_registrations</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">events</span> <span class="o">=</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="py">.events</span><span class="p">;</span>

    <span class="c1">// 核心：阻塞在 epoll_wait 上</span>
    <span class="k">match</span> <span class="k">self</span><span class="py">.poll</span><span class="nf">.poll</span><span class="p">(</span><span class="n">events</span><span class="p">,</span> <span class="n">max_wait</span><span class="p">)</span> <span class="p">{</span>
        <span class="nf">Ok</span><span class="p">(())</span> <span class="k">=&gt;</span> <span class="p">{}</span>
        <span class="nf">Err</span><span class="p">(</span><span class="k">ref</span> <span class="n">e</span><span class="p">)</span> <span class="k">if</span> <span class="n">e</span><span class="nf">.kind</span><span class="p">()</span> <span class="o">==</span> <span class="nn">io</span><span class="p">::</span><span class="nn">ErrorKind</span><span class="p">::</span><span class="n">Interrupted</span> <span class="k">=&gt;</span> <span class="p">{}</span>
        <span class="c1">// ...</span>
    <span class="p">}</span>

    <span class="c1">// 处理所有返回的事件</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">ready_count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">for</span> <span class="n">event</span> <span class="k">in</span> <span class="n">events</span><span class="nf">.iter</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">token</span> <span class="o">=</span> <span class="n">event</span><span class="nf">.token</span><span class="p">();</span>

        <span class="k">if</span> <span class="n">token</span> <span class="o">==</span> <span class="n">TOKEN_WAKEUP</span> <span class="p">{</span>
            <span class="c1">// mio::Waker 的 unpark，无需处理</span>
        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">token</span> <span class="o">==</span> <span class="n">TOKEN_SIGNAL</span> <span class="p">{</span>
            <span class="k">self</span><span class="py">.signal_ready</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">ready</span> <span class="o">=</span> <span class="nn">Ready</span><span class="p">::</span><span class="nf">from_mio</span><span class="p">(</span><span class="n">event</span><span class="p">);</span>
            <span class="c1">// token 就是 ScheduledIo 的地址</span>
            <span class="k">let</span> <span class="n">ptr</span> <span class="o">=</span> <span class="k">super</span><span class="p">::</span><span class="n">EXPOSE_IO</span><span class="nf">.from_exposed_addr</span><span class="p">(</span><span class="n">token</span><span class="na">.0</span><span class="p">);</span>
            <span class="k">let</span> <span class="n">io</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">ScheduledIo</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="o">&amp;*</span><span class="n">ptr</span> <span class="p">};</span>

            <span class="c1">// 更新 readiness 状态并唤醒等待任务</span>
            <span class="n">io</span><span class="nf">.set_readiness</span><span class="p">(</span><span class="nn">Tick</span><span class="p">::</span><span class="n">Set</span><span class="p">,</span> <span class="p">|</span><span class="n">curr</span><span class="p">|</span> <span class="n">curr</span> <span class="p">|</span> <span class="n">ready</span><span class="p">);</span>
            <span class="n">io</span><span class="nf">.wake</span><span class="p">(</span><span class="n">ready</span><span class="p">);</span>

            <span class="n">ready_count</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">turn()</code> 的工作流程清晰明了：</p>

<ol>
  <li><strong>释放待处理的注册</strong>：清理之前被 drop 但尚未从 epoll 中移除的 <code class="language-plaintext highlighter-rouge">Registration</code></li>
  <li><strong>阻塞等待</strong>：<code class="language-plaintext highlighter-rouge">self.poll.poll(events, max_wait)</code> —— 对，这就是 <code class="language-plaintext highlighter-rouge">epoll_wait</code></li>
  <li><strong>分发事件</strong>：遍历 epoll 返回的每个事件，通过 token 找到对应的 <code class="language-plaintext highlighter-rouge">ScheduledIo</code>，设置 readiness 位，唤醒等待的任务</li>
</ol>

<p>当 I/O 驱动独立运行时（如 Time Driver 没有启用），<code class="language-plaintext highlighter-rouge">max_wait</code> 为 <code class="language-plaintext highlighter-rouge">None</code> 表示无限期等待；当 Time Driver 有定时器时，<code class="language-plaintext highlighter-rouge">max_wait</code> 为”到最早 deadline 的时间差”。</p>

<h3 id="41-epoll-不保存数据">4.1 epoll 不保存数据</h3>

<p>一个容易产生的误解是认为 epoll 返回事件时”带着数据一起回来了”。实际上，<strong>epoll 只通知”fd 可读了”，它不保存也不传递任何用户数据</strong>。数据从到达网卡到被用户程序读取，经历了完整的链路：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>客户端发送数据包
    → 网卡硬件中断
        → 内核网络协议栈（IP/TCP 层）处理
            → 数据放入 socket 的接收缓冲区（内核内存）
                → epoll 检测到 fd 状态从不可读变为可读
                    → epoll_wait 返回「fd X 可读」
                        → Worker 线程醒来（turn() 返回）
                            → 设置 ScheduledIo readiness 位
                                → Waker::wake()
                                    → 任务重新入队，被调度执行
                                        → 任务调用 read(fd, buf)
                                            → read 系统调用把数据从内核 socket buffer 拷贝到用户缓冲区
</code></pre></div></div>

<p>epoll 的角色是<strong>门铃</strong>——它通知你”有东西到了，快来拿”，但它不负责把东西送来。真正的数据一直安静地躺在内核的 <strong>socket 接收缓冲区</strong>里，等用户任务被调度后通过 <code class="language-plaintext highlighter-rouge">read()</code> 系统调用拷贝到用户态。</p>

<p>这也是为什么 <code class="language-plaintext highlighter-rouge">PollEvented::poll_read</code> 的循环会在 <code class="language-plaintext highlighter-rouge">read()</code> 返回 <code class="language-plaintext highlighter-rouge">WouldBlock</code> 时清除 readiness 然后重试——因为如果假阳性发生了（数据被别的线程读走了或者已被处理），再次 <code class="language-plaintext highlighter-rouge">read()</code> 会告诉你 socket buffer 是空的，你需要清除 readiness 让 epoll 下次有新数据到达时重新通知你。</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant NIC as 网卡硬件
    participant Kernel as 内核协议栈
    participant Buf as Socket Buffer
    participant EPoll as epoll
    participant Task as 用户任务

    NIC-&gt;&gt;Kernel: 数据包到达（硬件中断）
    Kernel-&gt;&gt;Buf: TCP 重组 → 数据放入 socket buffer
    Buf-&gt;&gt;EPoll: fd 状态变更（不可读 → 可读）
    EPoll-&gt;&gt;EPoll: epoll_wait 返回
    Note over EPoll: epoll 只告诉你"fd X 可读"&lt;br/&gt;但不携带用户数据
    EPoll--&gt;&gt;Task: Waker::wake()
    Task-&gt;&gt;Task: 任务被调度
    Task-&gt;&gt;Buf: read(fd, buf)
    Buf--&gt;&gt;Task: 数据拷贝到用户缓冲区
</code></pre>

<p>对比一下 sleep 的”门铃”：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Timer Driver: epoll_wait(timeout) → 超时返回
    → 时间轮的定时器"只告诉你时间到了"
    → 没有数据要拷贝，直接 fire() → waker.wake()

I/O Driver:  epoll_wait(events, timeout) → 事件返回
    → epoll 的 fd 事件"只告诉你 fd 可读了"
    → 数据还在内核 socket buffer 里
    → 设置 readiness → waker.wake()
    → 任务醒来后需要 read() 把数据拷贝到用户态
</code></pre></div></div>

<p>在这一点上，两种驱动的”通知”机制在语义上是类似的——都只是<strong>信号通知</strong>，而不是<strong>数据传输</strong>。区别仅在于：时间通知直接触发任务的唤醒（时间到了就够了），而 I/O 通知后任务还需要多一步 <code class="language-plaintext highlighter-rouge">read()</code> 系统调用来搬运数据。</p>

<h3 id="整体驱动循环">整体驱动循环</h3>

<p>将 I/O 驱动和时间驱动放在一起看，Tokio 的 worker 线程主循环大致是：</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant Worker as Worker Thread
    participant Time as Time Driver
    participant IO as I/O Driver
    participant Sched as Scheduler

    loop 主循环
        Worker-&gt;&gt;Time: park_internal()
        Time-&gt;&gt;Time: 检查时间轮 → 下一个 deadline
        Time-&gt;&gt;IO: park/park_timeout(duration)
        IO-&gt;&gt;IO: epoll_wait(events, duration)
        Note over IO: 被 I/O 事件或超时唤醒
        IO--&gt;&gt;Time: 返回
        Time-&gt;&gt;Time: process_at_time(now)
        Time-&gt;&gt;Time: 收割到期 timer
        Time--&gt;&gt;Worker: 线程继续
        Worker-&gt;&gt;Sched: 运行就绪任务
    end
</code></pre>

<p>这也就是在上一篇文章中看到的 <code class="language-plaintext highlighter-rouge">park_internal</code> 流程——只是现在我们把 <code class="language-plaintext highlighter-rouge">park</code> 的底层展开了，看到了 I/O 驱动这一层。</p>

<h3 id="42-epoll-是-blocking-的non-blocking-在哪">4.2 epoll 是 blocking 的，non-blocking 在哪？</h3>

<p>到这里你可能有一个疑惑：<strong>既然 <code class="language-plaintext highlighter-rouge">turn()</code> 里的 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 会阻塞线程，那 non-blocking I/O 到底体现在哪里？</strong></p>

<p>答案是：<strong>阻塞在线程层次，非阻塞在任务层次。这是多路复用（multiplexing）的精髓。</strong></p>

<p>对比两种模式就一目了然：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>同步阻塞模式：一条线程 = 一个连接
  Thread A ── read(socket1) ── 阻塞等数据 ── 继续
  Thread B ── read(socket2) ── 阻塞等数据 ── 继续
  → N 个连接需要 N 条线程，OS 调度开销大

epoll 多路复用：一条线程 = 所有连接
  Worker ── epoll_wait([socket1, socket2, ...]) ── 阻塞等任意 fd 就绪 ──
      ├─ socket1 可读 → read(socket1) → 处理 → 回到 epoll_wait
      └─ socket2 可读 → read(socket2) → 处理 → 回到 epoll_wait
  → N 个连接只需要少量线程，zero-cost 任务切换
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">epoll_wait</code> 的阻塞正是 tokio 需要的——它不是在阻塞等一个特定的 socket，而是在阻塞等<strong>所有注册过的 fd 中任何一个就绪</strong>。一条线程的通话时间被分给成千上万个 fd 共享，没有空转、没有忙等。</p>

<p>但这里还有第二层：<strong>单个 socket 的 <code class="language-plaintext highlighter-rouge">read()</code> 本身也是非阻塞的</strong>。tokio 的 <code class="language-plaintext highlighter-rouge">mio::net::TcpStream</code> 在注册之前就已经被设为 <code class="language-plaintext highlighter-rouge">O_NONBLOCK</code> 了。所以 <code class="language-plaintext highlighter-rouge">read()</code> 永远不会真正卡住线程：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>read(nonblocking_fd, buf)  →  要么返回数据
                            →  要么返回 EAGAIN / EWOULDBLOCK
                            →  绝不阻塞等待
</code></pre></div></div>

<p>如果 <code class="language-plaintext highlighter-rouge">read()</code> 返回 <code class="language-plaintext highlighter-rouge">WouldBlock</code>，tokio 不会傻等——任务返回 <code class="language-plaintext highlighter-rouge">Poll::Pending</code>，线程立刻还给 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 去服务其他 fd。等到下一次内核通知 “fd 可读” 时，epoll 会再次唤醒线程，再尝试读取。</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant Task1 as 任务 A (fd1)
    participant Task2 as 任务 B (fd2)
    participant EPoll as epoll
    participant Worker as Worker 线程

    Worker-&gt;&gt;EPoll: epoll_wait([fd1, fd2])
    Note over Worker: 线程阻塞等待
    EPoll--&gt;&gt;Worker: fd1 可读
    Worker-&gt;&gt;Task1: poll fd1
    Task1-&gt;&gt;Task1: read(fd1, buf) → 成功
    Task1--&gt;&gt;Worker: Ready
    Worker-&gt;&gt;EPoll: 再次 epoll_wait
    Note over Worker: 线程阻塞等待
    EPoll--&gt;&gt;Worker: fd2 可读
    Worker-&gt;&gt;Task2: poll fd2
    Task2-&gt;&gt;Task2: read(fd2, buf) → WouldBlock! (假阳性)
    Task2-&gt;&gt;Task1: clear_readiness + Pending
    Task2--&gt;&gt;Worker: Pending
    Worker-&gt;&gt;EPoll: 再次 epoll_wait
</code></pre>

<p>注意到上面 seq 图中 <code class="language-plaintext highlighter-rouge">WouldBlock</code> 场景：任务 B 被唤醒后 <code class="language-plaintext highlighter-rouge">read()</code> 返回 <code class="language-plaintext highlighter-rouge">WouldBlock</code>，它没有阻塞线程，而是返回 <code class="language-plaintext highlighter-rouge">Pending</code> 让线程继续回到 <code class="language-plaintext highlighter-rouge">epoll_wait</code>。</p>

<h4 id="常问追问epoll_wait-阻塞线程那-await-会-blocking-吗">常问追问：epoll_wait 阻塞线程，那 <code class="language-plaintext highlighter-rouge">.await</code> 会 blocking 吗？</h4>

<p>把这个问题拆开看，一个 <code class="language-plaintext highlighter-rouge">.await</code> 的完整生命周期是：</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">Future::poll</code> → 数据没到 → 注册 Waker → 返回 <code class="language-plaintext highlighter-rouge">Poll::Pending</code></li>
  <li>调度器把这个任务从<strong>运行队列</strong>移到<strong>等待队列</strong></li>
  <li>调度器拿出下一个就绪任务运行</li>
  <li>如果<strong>没有任何任务就绪</strong>，线程才进入 <code class="language-plaintext highlighter-rouge">epoll_wait</code></li>
</ol>

<p>所以答案很清楚：<strong><code class="language-plaintext highlighter-rouge">.await</code> 本身从不导致线程阻塞</strong>。<code class="language-plaintext highlighter-rouge">.await</code> 返回 <code class="language-plaintext highlighter-rouge">Poll::Pending</code> 之后就回到了调度器，线程继续处理其他就绪任务。只有所有任务都挂起后，线程才去 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 等事件——这时的等待是<strong>多路复用的高效等待</strong>：一条线程替所有挂起的任务盯着所有 fd，任何一个 fd 就绪，线程就醒来。</p>

<p>反过来说，如果 <code class="language-plaintext highlighter-rouge">.await</code> 真的会 blocking，那下面的代码就不可能工作：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 伪代码：两个 .await 背靠背</span>
<span class="k">let</span> <span class="n">data1</span> <span class="o">=</span> <span class="n">socket1</span><span class="nf">.read</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">buf1</span><span class="p">)</span><span class="k">.await</span><span class="o">?</span><span class="p">;</span>
<span class="k">let</span> <span class="n">data2</span> <span class="o">=</span> <span class="n">socket2</span><span class="nf">.read</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">buf2</span><span class="p">)</span><span class="k">.await</span><span class="o">?</span><span class="p">;</span>
</code></pre></div></div>

<p>如果第一个 <code class="language-plaintext highlighter-rouge">.await</code> 阻塞线程，第二个 <code class="language-plaintext highlighter-rouge">.await</code> 永远没机会执行——但事实上它们都能正确执行。秘密就在于：第一个 <code class="language-plaintext highlighter-rouge">.await</code> 返回 <code class="language-plaintext highlighter-rouge">Pending</code> 后，线程回到调度器，调度器看到第二个任务就绪。即使第二个 <code class="language-plaintext highlighter-rouge">.await</code> 也返回 <code class="language-plaintext highlighter-rouge">Pending</code>，线程还可以去服务其他等待的 fd。<strong>阻塞在 epoll_wait 上的是线程，但任务早就从线程上被摘下来了。</strong></p>

<p>所以完整回答是两层含义的叠加：</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">epoll_wait</code> 的阻塞</strong>是<strong>线程层次的阻塞</strong>——它是在「等任何一个 fd 就绪」，不是「等一个特定的 fd」</li>
  <li><strong>单个 fd 的 <code class="language-plaintext highlighter-rouge">read()</code> 永不阻塞线程</strong>——因为 fd 被设为 <code class="language-plaintext highlighter-rouge">O_NONBLOCK</code>，数据没准备好就立即返回 <code class="language-plaintext highlighter-rouge">WouldBlock</code>，不会卡住线程</li>
</ol>

<p>这两层合在一起就是 tokio 异步 I/O 的引擎：<strong>一条线程开着 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 同时等所有连接，每个连接上的 <code class="language-plaintext highlighter-rouge">read</code>/<code class="language-plaintext highlighter-rouge">write</code> 都不会卡住线程，遇到 <code class="language-plaintext highlighter-rouge">WouldBlock</code> 就把任务挂起，线程回去继续等。</strong></p>

<p>这也顺便回答了上篇文章 sleep 和 I/O 的一个本质差异：<strong>sleep 的 <code class="language-plaintext highlighter-rouge">epoll_wait(timeout)</code> 在超时返回后，时间轮的”到期检测”是纯粹的 CPU 计算（比较 tick），不需要任何系统调用；而 I/O 的 <code class="language-plaintext highlighter-rouge">epoll_wait(events)</code> 返回后，任务还需要调用 <code class="language-plaintext highlighter-rouge">read()</code> 这个系统调用来真正获取数据</strong>。sleep 的用户态时间轮避免了额外的内核交互，而 I/O 的每一次数据读取都是一次系统调用。</p>

<h3 id="43-一个值得思考的对比io_uring-在哪">4.3 一个值得思考的对比：io_uring 在哪？</h3>

<p>到这里你可能还有一个问题：<strong>Linux 上有 io_uring，它可以完全异步地提交 I/O 操作并从 completion queue 中收割结果，tokio 为什么不用它来替代 epoll？</strong></p>

<p>这是一个很好的问题。答案是：<strong>mio 是 readiness-based（就绪态）抽象，而 io_uring 是 completion-based（完成态）抽象，两者的编程模型从根本上不兼容。</strong></p>

<h4 id="readiness-based-vs-completion-based">Readiness-based vs Completion-based</h4>

<p><strong>Readiness-based 模型</strong>（epoll / kqueue）的工作方式是：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>epoll_wait → 返回「fd X 可读」→ 用户调用 read(fd, buf) → 数据从内核拷贝到用户态
</code></pre></div></div>

<p>epoll 只告诉你”可以读了”，实际的读操作仍然由用户发起（<code class="language-plaintext highlighter-rouge">read()</code> 系统调用），且必须是同步的。这正是为什么上一节强调 <code class="language-plaintext highlighter-rouge">O_NONBLOCK</code>——因为 <code class="language-plaintext highlighter-rouge">read()</code> 虽然不阻塞线程，但它毕竟是同步完成的。</p>

<p><strong>Completion-based 模型</strong>（io_uring）的工作方式是：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>用户提交 read(fd, buf) 到 SQ（Submission Queue）
    → io_uring_enter 通知内核干活
        → 内核直接 DMA 把数据放入 buf
            → 完成后在 CQ（Completion Queue）放入一条完成记录
                → 用户从 CQ 收割结果
</code></pre></div></div>

<p>io_uring 把「等待就绪 + 读数据」合并成一步：你提交一个读请求，内核在数据到达时<strong>直接 DMA 到你的 buffer</strong>，然后通知你。你连 <code class="language-plaintext highlighter-rouge">read()</code> 系统调用都不需要再做了。</p>

<p>这两种模型在驱动层面的架构差异是根本性的：</p>

<pre><code class="language-mermaid">graph LR
    subgraph "Readiness-based (epoll / mio)"
        A1["epoll_wait: 就绪通知"] --&gt; A2["用户 read(fd, buf)"]
        A2 --&gt; A3["read 系统调用&lt;br/&gt;同步拷贝数据"]
    end

    subgraph "Completion-based (io_uring)"
        B1["用户提交 read op 到 SQ"] --&gt; B2["内核异步执行 I/O"]
        B2 --&gt; B3["CQ 完成通知"]
    end
</code></pre>

<h4 id="为什么-tokio-的默认-io-驱动用-mioepoll">为什么 tokio 的默认 I/O 驱动用 mio（epoll）？</h4>

<p>mio 是一个跨平台的 I/O 抽象层：Linux 上它用 epoll，macOS/FreeBSD 上用 kqueue，Windows 上用 IOCP。它定义的编程模型是 <strong>Source + Poll + Events</strong>——你把 fd 作为一个 <code class="language-plaintext highlighter-rouge">Source</code> 注册到 <code class="language-plaintext highlighter-rouge">Poll</code> 上，然后阻塞等待事件。</p>

<p>Tokio 的整个 I/O 驱动——<code class="language-plaintext highlighter-rouge">ScheduledIo</code>、<code class="language-plaintext highlighter-rouge">Registration</code>、<code class="language-plaintext highlighter-rouge">PollEvented</code>——全是围绕这个模型构建的：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">ScheduledIo</code> 用原子位追踪 fd 的 readiness 状态</li>
  <li><code class="language-plaintext highlighter-rouge">Registration</code> 处理「等待就绪 → 尝试读写 → WouldBlock → 清除 readiness → 再等待」的循环</li>
  <li><code class="language-plaintext highlighter-rouge">PollEvented</code> 在 ET 模式下用 tick 保护 stale event</li>
</ul>

<p>这套架构在 io_uring 面前完全不适用。io_uring 没有「fd readiness」的概念——它只有「这个 op 完成了」。你不能用 <code class="language-plaintext highlighter-rouge">ScheduledIo</code> 的 readiness 位来表达”异步读操作完成”。你需要一个完全不同的状态机来追踪每个 submitted op 的生命周期。</p>

<h4 id="tokio-实际是怎么支持-io_uring-的">Tokio 实际是怎么支持 io_uring 的？</h4>

<p>Tokio <strong>确实支持 io_uring</strong>，但它被严格限定在文件系统操作上，且需要显式启用<sup id="fnref:uring"><a href="#fn:uring" class="footnote" rel="footnote" role="doc-noteref">14</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/io/driver.rs: 56-70 (条件编译)</span>
<span class="c1">// 需要: tokio_unstable + feature = "io-uring" + feature = "fs" + Linux</span>

<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">uring_context</span><span class="p">:</span> <span class="n">Mutex</span><span class="o">&lt;</span><span class="n">UringContext</span><span class="o">&gt;</span><span class="p">,</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">uring_probe</span><span class="p">:</span> <span class="n">OnceCell</span><span class="o">&lt;</span><span class="nb">Option</span><span class="o">&lt;</span><span class="nn">io_uring</span><span class="p">::</span><span class="n">Probe</span><span class="o">&gt;&gt;</span><span class="p">,</span>
</code></pre></div></div>

<p>启用后，tokio 采用<strong>混合架构</strong>：</p>

<ul>
  <li><strong>网络 I/O（socket）</strong>：走默认的 mio/epoll 路径——epoll + readiness 模型</li>
  <li><strong>文件 I/O</strong>：走 io_uring 路径——用户提交 <code class="language-plaintext highlighter-rouge">read</code>/<code class="language-plaintext highlighter-rouge">write</code>/<code class="language-plaintext highlighter-rouge">open</code>/<code class="language-plaintext highlighter-rouge">stat</code> 等 ops 到 SQ，内核异步完成，结果在 CQ 中收割</li>
</ul>

<p>这个混合体现在 <code class="language-plaintext highlighter-rouge">turn()</code> 方法里：处理完 epoll 事件后，立即 dispatch io_uring 的 completions<sup id="fnref:uring:1"><a href="#fn:uring" class="footnote" rel="footnote" role="doc-noteref">14</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/io/driver.rs: 227-236</span>
<span class="c1">// 处理完 epoll 事件后，dispatch io_uring 的 completion</span>
<span class="nd">#[cfg(all(</span>
    <span class="nd">tokio_unstable,</span>
    <span class="nd">feature</span> <span class="nd">=</span> <span class="s">"io-uring"</span><span class="nd">,</span>
    <span class="nd">feature</span> <span class="nd">=</span> <span class="s">"rt"</span><span class="nd">,</span>
    <span class="nd">feature</span> <span class="nd">=</span> <span class="s">"fs"</span><span class="nd">,</span>
    <span class="nd">target_os</span> <span class="nd">=</span> <span class="s">"linux"</span><span class="nd">,</span>
<span class="nd">))]</span>
<span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">guard</span> <span class="o">=</span> <span class="n">handle</span><span class="nf">.get_uring</span><span class="p">()</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">ctx</span> <span class="o">=</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="o">*</span><span class="n">guard</span><span class="p">;</span>
    <span class="n">ctx</span><span class="nf">.dispatch_completions</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>io_uring 的 eventfd 甚至也是一个普通的 fd，注册到 epoll 上的<sup id="fnref:uring:2"><a href="#fn:uring" class="footnote" rel="footnote" role="doc-noteref">14</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/io/driver/uring.rs: 180-184</span>
<span class="k">impl</span> <span class="n">Handle</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">add_uring_source</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">uringfd</span><span class="p">:</span> <span class="n">RawFd</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">source</span> <span class="o">=</span> <span class="nf">SourceFd</span><span class="p">(</span><span class="o">&amp;</span><span class="n">uringfd</span><span class="p">);</span>
        <span class="k">self</span><span class="py">.registry</span>
            <span class="nf">.register</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">source</span><span class="p">,</span> <span class="n">TOKEN_WAKEUP</span><span class="p">,</span> <span class="nn">Interest</span><span class="p">::</span><span class="n">READABLE</span><span class="nf">.to_mio</span><span class="p">())</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>也就是说，io_uring 的 eventfd 被注册到 epoll 上，completion 到达时 epoll 通知驱动，然后驱动在 <code class="language-plaintext highlighter-rouge">turn()</code> 中 dispatch 这些 completion——<strong>io_uring 被挂在 epoll 的事件循环上</strong>。</p>

<p>从运行时的视角看，三种事件的驱动关系是：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>epoll_wait 返回
    ├─ socket fd 就绪 → 设置 ScheduledIo readiness → 唤醒等待的任务
    ├─ io_uring eventfd 可读 → dispatch io_uring completions → 唤醒等待的任务
    ├─ TOKEN_WAKEUP → 外部 unpark
    └─ TOKEN_SIGNAL → 信号处理
</code></pre></div></div>

<h4 id="为什么不直接用-io_uring-替代-epoll-处理所有-socket-io">为什么不直接用 io_uring 替代 epoll 处理所有 socket I/O？</h4>

<p>io_uring 的 socket 支持虽然已经存在，但<strong>用 io_uring 做网络 I/O 是否比 epoll 更好，直到今天（2026 年）仍然是一个开放问题</strong><sup id="fnref:uring2"><a href="#fn:uring2" class="footnote" rel="footnote" role="doc-noteref">15</a></sup>。原因有三：</p>

<ol>
  <li>
    <p><strong>延迟开销</strong>：io_uring 提交一个网络操作至少需要一次 <code class="language-plaintext highlighter-rouge">io_uring_enter</code> 系统调用（或者通过 SQ 轮询来避免），而 epoll 模式下一次 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 可以返回多个就绪的 fd，然后逐个 <code class="language-plaintext highlighter-rouge">read()</code>。对于高吞吐但低延迟的网络场景，epoll 的批处理模式仍然有竞争力。</p>
  </li>
  <li>
    <p><strong>epoll 的”够用”</strong>：对于网络 I/O，<code class="language-plaintext highlighter-rouge">epoll_wait</code> + 非阻塞 <code class="language-plaintext highlighter-rouge">read()</code> 的模式已经足够好了。<code class="language-plaintext highlighter-rouge">O_NONBLOCK</code> 确保了 <code class="language-plaintext highlighter-rouge">read()</code> 不会阻塞线程，epoll 的多路复用确保了单线程可以管理海量连接。io_uring 在网络上的优势主要在于减少系统调用次数和降低延迟，但在 epoll 已经做到几乎最优的场景下，替换的动力不足。</p>
  </li>
  <li>
    <p><strong>跨平台问题</strong>：io_uring 是 Linux-only（5.1+）。Tokio 的 runtime 需要跨平台一致性，把核心的网络 I/O 基础设施绑定在 io_uring 上会破坏跨平台的架构统一性。</p>
  </li>
</ol>

<p>所以 Tokio 选择了一条务实的路径：<strong>网络 I/O 用 epoll（mio），文件 I/O 用 io_uring（可选）</strong>。不是技术上的”不能”，而是架构上的”不值得为了替换而替换”。</p>

<h2 id="五读写的完整路径从-poll-到-buffer">五、读写的完整路径：从 poll 到 buffer</h2>

<p>理解了数据结构后，来看 <code class="language-plaintext highlighter-rouge">TcpStream::read(&amp;mut buf).await</code> 的完整路径。当一个 <code class="language-plaintext highlighter-rouge">PollEvented</code> 包装的 socket 被 <code class="language-plaintext highlighter-rouge">poll_read</code> 时<sup id="fnref:4:2"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">10</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/io/poll_evented.rs: 169-194</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="n">poll_read</span><span class="o">&lt;</span><span class="nv">'a</span><span class="o">&gt;</span><span class="p">(</span>
    <span class="o">&amp;</span><span class="nv">'a</span> <span class="k">self</span><span class="p">,</span>
    <span class="n">cx</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Context</span><span class="o">&lt;</span><span class="nv">'_</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">buf</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">ReadBuf</span><span class="o">&lt;</span><span class="nv">'_</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Poll</span><span class="o">&lt;</span><span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;&gt;</span>
<span class="k">where</span> <span class="o">&amp;</span><span class="nv">'a</span> <span class="n">E</span><span class="p">:</span> <span class="nn">io</span><span class="p">::</span><span class="n">Read</span> <span class="o">+</span> <span class="nv">'a</span><span class="p">,</span>
<span class="p">{</span>
    <span class="k">loop</span> <span class="p">{</span>
        <span class="c1">// 1. 等待读就绪</span>
        <span class="k">let</span> <span class="n">evt</span> <span class="o">=</span> <span class="nd">ready!</span><span class="p">(</span><span class="k">self</span><span class="py">.registration</span><span class="nf">.poll_read_ready</span><span class="p">(</span><span class="n">cx</span><span class="p">))</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 2. 尝试读取</span>
        <span class="k">match</span> <span class="k">self</span><span class="py">.io</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">()</span><span class="nf">.read</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="p">{</span>
            <span class="nf">Ok</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="c1">// 4. ET 优化：读了一部分但没读完，不清除 readiness</span>
                <span class="c1">//    （epoll 模式下可以继续读）</span>
                <span class="nd">#[cfg(not(mio_unsupported_force_poll_poll))]</span>
                <span class="k">if</span> <span class="mi">0</span> <span class="o">&lt;</span> <span class="n">n</span> <span class="o">&amp;&amp;</span> <span class="n">n</span> <span class="o">&lt;</span> <span class="n">len</span> <span class="p">{</span>
                    <span class="k">self</span><span class="py">.registration</span><span class="nf">.clear_readiness</span><span class="p">(</span><span class="n">evt</span><span class="p">);</span>
                <span class="p">}</span>

                <span class="n">buf</span><span class="nf">.advance</span><span class="p">(</span><span class="n">n</span><span class="p">);</span>
                <span class="k">return</span> <span class="nn">Poll</span><span class="p">::</span><span class="nf">Ready</span><span class="p">(</span><span class="nf">Ok</span><span class="p">(()));</span>
            <span class="p">}</span>
            <span class="c1">// 3. 读阻塞了（false positive）：清除 readiness 后重试</span>
            <span class="nf">Err</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="k">if</span> <span class="n">e</span><span class="nf">.kind</span><span class="p">()</span> <span class="o">==</span> <span class="nn">io</span><span class="p">::</span><span class="nn">ErrorKind</span><span class="p">::</span><span class="n">WouldBlock</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="k">self</span><span class="py">.registration</span><span class="nf">.clear_readiness</span><span class="p">(</span><span class="n">evt</span><span class="p">);</span>
            <span class="p">}</span>
            <span class="nf">Err</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="k">return</span> <span class="nn">Poll</span><span class="p">::</span><span class="nf">Ready</span><span class="p">(</span><span class="nf">Err</span><span class="p">(</span><span class="n">e</span><span class="p">)),</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这个模式比 <code class="language-plaintext highlighter-rouge">Sleep::poll</code> 复杂——因为 I/O 有<strong>假阳性（false positive）</strong>。epoll 返回 readable 后，fd 可能因为竞争等原因实际上不可读了。所以 I/O 的 poll 循环是：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>poll_read_ready → 返回 Ready → 尝试 read
    ├─ 成功 → 返回数据
    └─ WouldBlock → clear_readiness → 重新 poll
</code></pre></div></div>

<p>而 <code class="language-plaintext highlighter-rouge">Sleep::poll</code> 是：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>检查时间轮 → 到期了吗？
    ├─ 是 → 返回 Ready
    └─ 否 → 注册 waker → 返回 Pending
</code></pre></div></div>

<p>sleep 没有假阳性——deadline 到了就是到了。I/O 有假阳性——epoll 说 readable 了，但数据可能已经被别的线程读了，或者在监听 socket 上有新的 accept 提前消耗了事件。</p>

<p><code class="language-plaintext highlighter-rouge">Registration</code> 还提供了一个更底层的 <code class="language-plaintext highlighter-rouge">poll_io</code> 方法——它把”等待就绪 → 尝试操作 → WouldBlock → 清理 → 重试”的循环封装在一个函数里<sup id="fnref:5:2"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">11</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/io/registration.rs: 162-175</span>
<span class="k">fn</span> <span class="n">poll_io</span><span class="o">&lt;</span><span class="n">R</span><span class="o">&gt;</span><span class="p">(</span>
    <span class="o">&amp;</span><span class="k">self</span><span class="p">,</span>
    <span class="n">cx</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Context</span><span class="o">&lt;</span><span class="nv">'_</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">direction</span><span class="p">:</span> <span class="n">Direction</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">f</span><span class="p">:</span> <span class="k">impl</span> <span class="nf">FnMut</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="n">R</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Poll</span><span class="o">&lt;</span><span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="n">R</span><span class="o">&gt;&gt;</span> <span class="p">{</span>
    <span class="k">loop</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">ev</span> <span class="o">=</span> <span class="nd">ready!</span><span class="p">(</span><span class="k">self</span><span class="nf">.poll_ready</span><span class="p">(</span><span class="n">cx</span><span class="p">,</span> <span class="n">direction</span><span class="p">))</span><span class="o">?</span><span class="p">;</span>
        <span class="k">match</span> <span class="nf">f</span><span class="p">()</span> <span class="p">{</span>
            <span class="nf">Ok</span><span class="p">(</span><span class="n">ret</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="k">return</span> <span class="nn">Poll</span><span class="p">::</span><span class="nf">Ready</span><span class="p">(</span><span class="nf">Ok</span><span class="p">(</span><span class="n">ret</span><span class="p">)),</span>
            <span class="nf">Err</span><span class="p">(</span><span class="k">ref</span> <span class="n">e</span><span class="p">)</span> <span class="k">if</span> <span class="n">e</span><span class="nf">.kind</span><span class="p">()</span> <span class="o">==</span> <span class="nn">io</span><span class="p">::</span><span class="nn">ErrorKind</span><span class="p">::</span><span class="n">WouldBlock</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="k">self</span><span class="nf">.clear_readiness</span><span class="p">(</span><span class="n">ev</span><span class="p">);</span>
            <span class="p">}</span>
            <span class="nf">Err</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="k">return</span> <span class="nn">Poll</span><span class="p">::</span><span class="nf">Ready</span><span class="p">(</span><span class="nf">Err</span><span class="p">(</span><span class="n">e</span><span class="p">)),</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="六你还在读同一条线程吗关于既处理-io-又处理-timer">六、你还在读同一条线程吗：关于「既处理 I/O 又处理 Timer」</h2>

<p>一个常见的困惑是：<strong>Time Driver 和 I/O Driver 是在同一条线程上运行的吗？不冲突吗？</strong></p>

<p>答案是<strong>是的，同一条线程，不冲突</strong>。原因可以用一句话概括：<strong>当线程在 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 上阻塞时，时间驱动作了 I/O 驱动的一层薄薄的外包装，它做的所有事就是设置 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 的 timeout 参数</strong>。</p>

<p>当 Time Driver 调用 <code class="language-plaintext highlighter-rouge">park_timeout(duration)</code> 时，它最终调用了 I/O Driver 的 <code class="language-plaintext highlighter-rouge">park_timeout(duration)</code>。I/O Driver 的 <code class="language-plaintext highlighter-rouge">turn()</code> 把 <code class="language-plaintext highlighter-rouge">duration</code> 传给 <code class="language-plaintext highlighter-rouge">epoll_wait</code>。这也就是 sleep 文章中提到的那条链路。</p>

<p>当 I/O 事件在 duration 到达之前触发，线程提前醒来，Time Driver 在 <code class="language-plaintext highlighter-rouge">park_internal</code> 中返回后立即调用 <code class="language-plaintext highlighter-rouge">process()</code> 收割到期定时器。<strong>I/O 事件的处理和时间到期检测在同一个函数调用栈中完成，没有竞争、没有额外的上下文切换</strong>。</p>

<pre><code class="language-mermaid">graph TD
    subgraph "Worker 线程主循环"
        A["开始"] --&gt; B["Time::park_internal()"]
        B --&gt; C["计算 next_wake"]
        C --&gt; D["IoStack::park_timeout(duration)"]
        D --&gt; E["epoll_wait(timeout)"]
        E --&gt;|"I/O 事件或超时"| F["Time::process_at_time()"]
        F --&gt; G["收割到期 timer"]
        G --&gt; H["Scheduler::run 处理就绪任务"]
        H --&gt; B
    end
</code></pre>

<p>这种「一层包一层」的设计，使得 Time Driver 和 I/O Driver 完全共享同一个线程和同一次 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 调用。没有线程切换、没有锁竞争（除了 <code class="language-plaintext highlighter-rouge">ScheduledIo</code> 内部的 <code class="language-plaintext highlighter-rouge">Mutex</code>）、没有额外的调度开销。</p>

<h2 id="七对比总览时间驱动-vs-事件驱动">七、对比总览：时间驱动 vs 事件驱动</h2>

<p>现在可以把两种表并列在一起看：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">维度</th>
      <th style="text-align: left">时间驱动（sleep）</th>
      <th style="text-align: left">事件驱动（I/O）</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>核心数据结构</strong></td>
      <td style="text-align: left">6 层哈希时间轮（<code class="language-plaintext highlighter-rouge">Wheel</code>）</td>
      <td style="text-align: left">逐 fd 的原子状态机（<code class="language-plaintext highlighter-rouge">ScheduledIo</code>）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>状态管理</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">StateCell</code>（AtomicU64：到期时间 + 标记位）</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">AtomicUsize</code>（16b readiness + 15b tick + 1b shutdown）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>等待机制</strong></td>
      <td style="text-align: left">比较 deadline vs now（用户态）</td>
      <td style="text-align: left">等待 epoll 通知（内核态）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>唤醒方式</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">fire()</code> → <code class="language-plaintext highlighter-rouge">Waker::wake()</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">wake(ready)</code> → <code class="language-plaintext highlighter-rouge">Waker::wake()</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>假阳性</strong></td>
      <td style="text-align: left">无（时间到了就是到了）</td>
      <td style="text-align: left">有（epoll 可能虚报）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>插入/注册</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">Wheel::insert()</code> → O(1)</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">mio::Registry::register()</code> → 系统调用</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>单次检查</strong></td>
      <td style="text-align: left">检查当前 slot 内的所有 entry</td>
      <td style="text-align: left">检查 epoll 返回的所有 event</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>超时/时间驱动</strong></td>
      <td style="text-align: left">核心机制：靠 deadline 驱动</td>
      <td style="text-align: left">仅用于设置 <code class="language-plaintext highlighter-rouge">epoll_wait</code> timeout</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>内核交互</strong></td>
      <td style="text-align: left">1 个定时器（hrtimer）</td>
      <td style="text-align: left">所有 fd 注册到 epoll</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>精确度</strong></td>
      <td style="text-align: left">1ms</td>
      <td style="text-align: left">纳秒级（取决于内核）</td>
    </tr>
  </tbody>
</table>

<h3 id="核心差异的本质">核心差异的本质</h3>

<p>两种驱动模型差异的根源在于同一个问题：<strong>「事件」发生的时机是否可控</strong>。</p>

<p><strong>时间</strong>是可控的。给定一个 deadline，Tokio 可以在用户态用时间轮 O(1) 地判断是否到期。它不需要内核在”到期”这个事件发生时主动通知——它只需要知道当前时间，然后比较即可。内核定时器只是用来让线程不要一直空转。</p>

<p><strong>网络 I/O</strong>是不可控的。内核之外的客户端可能在任意时刻发送数据包。Tokio <strong>必须</strong>靠内核来通知——因为只有内核驱动（网卡中断 → 协议栈）知道数据什么时候到达。epoll 就是这个”内核通知机制”的接口。</p>

<p>这种「可控 vs 不可控」的区别，导致了两套完全不同的数据结构选择：</p>

<ul>
  <li><strong>可控的时间</strong> → 用户态哈希时间轮 → O(1) 插入，批量降级，线程只在必要时被唤醒</li>
  <li><strong>不可控的 I/O</strong> → epoll + per-fd 原子状态机 → 内核事件驱动，用户态只做快速的位操作和 waker 分发</li>
</ul>

<pre><code class="language-mermaid">graph LR
    subgraph "可控事件"
        A1["sleep(5s)"] --&gt; A2["deadline = now + 5s"]
        A2 --&gt; A3["插入时间轮（O(1)）"]
        A3 --&gt; A4["等待时：检查时间轮&lt;br/&gt;全部在用户态完成"]
        A4 --&gt; A5["到期：fire()"]
    end

    subgraph "不可控事件"
        B1["TcpStream::read()"] --&gt; B2["注册 fd 到 epoll"]
        B2 --&gt; B3["等待时：epoll_wait&lt;br/&gt;内核挂起线程"]
        B3 --&gt; B4["数据到达：epoll 通知"]
        B4 --&gt; B5["设置 readiness + wake()"]
    end
</code></pre>

<h2 id="八另一种不可控事件asyncfd">八、另一种不可控事件：AsyncFd</h2>

<p>tokio 的 I/O 驱动不只服务于网络 socket。<code class="language-plaintext highlighter-rouge">AsyncFd</code> 是一个通用的包装器，允许<strong>任何实现了 <code class="language-plaintext highlighter-rouge">AsRawFd</code> 的非阻塞文件描述符</strong>接入 Tokio 的 I/O 驱动<sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">16</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/io/async_fd.rs: 248-255</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">AsyncFd</span><span class="o">&lt;</span><span class="n">T</span><span class="p">:</span> <span class="n">AsRawFd</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="n">registration</span><span class="p">:</span> <span class="n">Registration</span><span class="p">,</span>
    <span class="n">inner</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>它的创建流程和 <code class="language-plaintext highlighter-rouge">TcpStream</code> 本质相同<sup id="fnref:8:1"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">16</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/io/async_fd.rs: 406-414</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">try_new_with_handle_and_interest</span><span class="p">(</span>
    <span class="n">inner</span><span class="p">:</span> <span class="n">T</span><span class="p">,</span>
    <span class="n">handle</span><span class="p">:</span> <span class="nn">scheduler</span><span class="p">::</span><span class="n">Handle</span><span class="p">,</span>
    <span class="n">interest</span><span class="p">:</span> <span class="n">Interest</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="k">Self</span><span class="p">,</span> <span class="n">AsyncFdTryNewError</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">inner</span><span class="nf">.as_raw_fd</span><span class="p">();</span>

    <span class="k">match</span> <span class="nn">Registration</span><span class="p">::</span><span class="nf">new_with_interest_and_handle</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="nf">SourceFd</span><span class="p">(</span><span class="o">&amp;</span><span class="n">fd</span><span class="p">),</span> <span class="n">interest</span><span class="p">,</span> <span class="n">handle</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="nf">Ok</span><span class="p">(</span><span class="n">registration</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="nf">Ok</span><span class="p">(</span><span class="n">AsyncFd</span> <span class="p">{</span> <span class="n">registration</span><span class="p">,</span> <span class="n">inner</span><span class="p">:</span> <span class="nf">Some</span><span class="p">(</span><span class="n">inner</span><span class="p">)</span> <span class="p">}),</span>
        <span class="nf">Err</span><span class="p">(</span><span class="n">cause</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="nf">Err</span><span class="p">(</span><span class="n">AsyncFdTryNewError</span> <span class="p">{</span> <span class="n">inner</span><span class="p">,</span> <span class="n">cause</span> <span class="p">}),</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>区别在于：<code class="language-plaintext highlighter-rouge">TcpStream</code> 使用 <code class="language-plaintext highlighter-rouge">mio::net::TcpStream</code>（它实现了 <code class="language-plaintext highlighter-rouge">mio::event::Source</code>），而 <code class="language-plaintext highlighter-rouge">AsyncFd</code> 使用 <code class="language-plaintext highlighter-rouge">mio::unix::SourceFd</code>——一个简单的包装器，不修改 fd 的配置（因此要求 fd 已经设置为非阻塞模式）。</p>

<p><code class="language-plaintext highlighter-rouge">AsyncFd</code> 的使用模式体现了 ET 模型的完整语义<sup id="fnref:8:2"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">16</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 典型的 AsyncFd 使用模式</span>
<span class="k">loop</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">guard</span> <span class="o">=</span> <span class="n">async_fd</span><span class="nf">.readable</span><span class="p">()</span><span class="k">.await</span><span class="o">?</span><span class="p">;</span>     <span class="c1">// 等待可读</span>
    <span class="k">match</span> <span class="n">guard</span><span class="nf">.try_io</span><span class="p">(|</span><span class="n">inner</span><span class="p">|</span> <span class="n">inner</span><span class="nf">.get_ref</span><span class="p">()</span><span class="nf">.read</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">buf</span><span class="p">))</span> <span class="p">{</span>
        <span class="nf">Ok</span><span class="p">(</span><span class="n">result</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="k">return</span> <span class="n">result</span><span class="p">,</span>                   <span class="c1">// 成功读取</span>
        <span class="nf">Err</span><span class="p">(</span><span class="n">_would_block</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="k">continue</span><span class="p">,</span>                <span class="c1">// 假阳性，重试</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这里 <code class="language-plaintext highlighter-rouge">guard.try_io()</code> 的语义是：如果 I/O 操作返回 <code class="language-plaintext highlighter-rouge">WouldBlock</code>，自动调用 <code class="language-plaintext highlighter-rouge">clear_ready()</code> 清除 readiness 位。这确保了下次数据到达时，epoll 会发出新的边沿触发通知。</p>

<h2 id="总结">总结</h2>

<p>Tokio 的 I/O 驱动和 Timer 驱动虽然在同一个 worker 线程中运行、共享同一次 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 调用，但它们的核心机制截然不同：</p>

<p><strong>Timer 驱动</strong>是一个<strong>用户态的时间调度器</strong>。它用哈希时间轮 O(1) 地管理成千上万个定时器，只向内核注册一个「最近 deadline」的唤醒点。线程被 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 的超时机制叫醒，然后批量收割到期定时器。</p>

<p><strong>I/O 驱动</strong>是一个<strong>内核态事件的分发器</strong>。它通过 epoll 把 fd 的 read/write 事件从内核调度到用户态，用 <code class="language-plaintext highlighter-rouge">ScheduledIo</code> 的原子状态机追踪每个 fd 的就绪状态，用 tick 机制解决边沿触发的 stale event 问题。</p>

<p>两者在 Tokio 中的分层设计是：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>应用层：tokio::time::sleep         tokio::net::TcpStream
                |                          |
运行时层：TimerEntry / Wheel          Registration / ScheduledIo
                |                          |
驱动层：  Time Driver                I/O Driver (mio)
                |                          |
内核层：  hrtimer × 1                epoll (所有 fd)
</code></pre></div></div>

<p>驱动层通过 <code class="language-plaintext highlighter-rouge">IoStack</code> 堆叠在一起：外层 Timer 检查时间轮确定 timeout，内层 I/O 执行 <code class="language-plaintext highlighter-rouge">epoll_wait</code>。两条线在同一个线程上优雅地交织，互不干扰。</p>

<p>最终，这两种异步机制的实现都可以回溯到 Rust async 的核心契约<sup id="fnref:1:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>：<strong>返回 <code class="language-plaintext highlighter-rouge">Poll::Pending</code> 时注册 Waker，事件就绪时通过 <code class="language-plaintext highlighter-rouge">Waker::wake()</code> 通知调度器重新 poll</strong>。<code class="language-plaintext highlighter-rouge">sleep</code> 用「时间到达」作为就绪条件，<code class="language-plaintext highlighter-rouge">TcpStream</code> 用「IO 就绪」作为就绪条件——只是事件源不同，契约完全一样。</p>

<h2 id="九feature-与-builder-如何控制-driver-启用">九、Feature 与 Builder 如何控制 Driver 启用</h2>

<p>前面提到 <code class="language-plaintext highlighter-rouge">time::Driver</code>、<code class="language-plaintext highlighter-rouge">process::Driver</code>、<code class="language-plaintext highlighter-rouge">signal::Driver</code>、<code class="language-plaintext highlighter-rouge">io::Driver</code> 都可以通过条件编译或 Builder 选项跳过。这里把实际控制机制拆开看。</p>

<h3 id="91-cargo-feature--编译时是否包含">9.1 Cargo feature — 编译时是否包含</h3>

<p>四个 feature 决定对应层的<strong>代码是否存在</strong>：</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Cargo.toml (tokio 的 default features)</span>
<span class="nn">tokio</span> <span class="o">=</span> <span class="p">{</span> <span class="py">version</span> <span class="p">=</span> <span class="s">"1"</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="p">[</span><span class="s">"rt"</span><span class="p">,</span> <span class="s">"net"</span><span class="p">,</span> <span class="s">"time"</span><span class="p">,</span> <span class="s">"process"</span><span class="p">,</span> <span class="s">"signal"</span><span class="p">]</span> <span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">cfg_io_driver!</code> 宏检查 feature <code class="language-plaintext highlighter-rouge">net</code>（编译时才展开）：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/driver.rs: 134</span>
<span class="nd">cfg_io_driver!</span> <span class="p">{</span>                        <span class="c1">// 仅在 feature = "net" 时编译</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">type</span> <span class="n">IoDriver</span> <span class="o">=</span> <span class="k">crate</span><span class="p">::</span><span class="nn">runtime</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="n">Driver</span><span class="p">;</span>
<span class="p">}</span>
<span class="nd">cfg_not_io_driver!</span> <span class="p">{</span>                    <span class="c1">// feature = "net" 关闭时</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="nf">IoStack</span><span class="p">(</span><span class="n">ParkThread</span><span class="p">);</span>
    <span class="k">fn</span> <span class="nf">create_io_stack</span><span class="p">(</span><span class="o">..</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">...</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">park_thread</span> <span class="o">=</span> <span class="nn">ParkThread</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>  <span class="c1">// 没有 io::Driver，纯 Condvar</span>
        <span class="p">(</span><span class="nf">IoStack</span><span class="p">(</span><span class="n">park_thread</span><span class="p">),</span> <span class="o">..</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">cfg_time!</code> / <code class="language-plaintext highlighter-rouge">cfg_not_time!</code> 控制 <code class="language-plaintext highlighter-rouge">TimeDriver</code> enum 是否存在：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/driver.rs: 284</span>
<span class="nd">cfg_time!</span> <span class="p">{</span>                             <span class="c1">// feature = "time" → TimeDriver 是 enum</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">enum</span> <span class="n">TimeDriver</span> <span class="p">{</span>
        <span class="n">Enabled</span> <span class="p">{</span> <span class="n">driver</span><span class="p">:</span> <span class="k">crate</span><span class="p">::</span><span class="nn">runtime</span><span class="p">::</span><span class="nn">time</span><span class="p">::</span><span class="n">Driver</span> <span class="p">},</span>
        <span class="nf">EnabledAlt</span><span class="p">(</span><span class="n">IoStack</span><span class="p">),</span>
        <span class="nf">Disabled</span><span class="p">(</span><span class="n">IoStack</span><span class="p">),</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="nd">cfg_not_time!</span> <span class="p">{</span>                         <span class="c1">// 没有 time feature → TimeDriver 是 IoStack 别名</span>
    <span class="k">type</span> <span class="n">TimeDriver</span> <span class="o">=</span> <span class="n">IoStack</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>同理 <code class="language-plaintext highlighter-rouge">signal</code> 和 <code class="language-plaintext highlighter-rouge">process</code> 的退化：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/driver.rs: 244</span>
<span class="nd">cfg_signal_internal_and_unix!</span> <span class="p">{</span>         <span class="c1">// feature = "signal" + Unix</span>
    <span class="k">type</span> <span class="n">SignalDriver</span> <span class="o">=</span> <span class="k">crate</span><span class="p">::</span><span class="nn">runtime</span><span class="p">::</span><span class="nn">signal</span><span class="p">::</span><span class="n">Driver</span><span class="p">;</span>
<span class="p">}</span>
<span class="nd">cfg_not_signal_internal!</span> <span class="p">{</span>              <span class="c1">// 无 signal 时退化为 IoDriver</span>
    <span class="k">type</span> <span class="n">SignalDriver</span> <span class="o">=</span> <span class="n">IoDriver</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// tokio/src/runtime/driver.rs: 269</span>
<span class="nd">cfg_process_driver!</span> <span class="p">{</span>                   <span class="c1">// feature = "process"</span>
    <span class="k">type</span> <span class="n">ProcessDriver</span> <span class="o">=</span> <span class="k">crate</span><span class="p">::</span><span class="nn">runtime</span><span class="p">::</span><span class="nn">process</span><span class="p">::</span><span class="n">Driver</span><span class="p">;</span>
<span class="p">}</span>
<span class="nd">cfg_not_process_driver!</span> <span class="p">{</span>               <span class="c1">// 无 process 时退化为 SignalDriver</span>
    <span class="k">type</span> <span class="n">ProcessDriver</span> <span class="o">=</span> <span class="n">SignalDriver</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>编译时未启用的 feature，相关代码在最终二进制中完全不存在。</p>

<h3 id="92-builder-选项--运行时启用或跳过">9.2 Builder 选项 — 运行时启用或跳过</h3>

<p>编译已包含的代码，可以通过 Builder 决定是否激活：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/builder.rs: 1672 / 1860</span>
<span class="k">let</span> <span class="p">(</span><span class="n">driver</span><span class="p">,</span> <span class="n">driver_handle</span><span class="p">)</span> <span class="o">=</span> <span class="nn">driver</span><span class="p">::</span><span class="nn">Driver</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">cfg</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">cfg</code> 来自 builder 的设置<sup id="fnref:17"><a href="#fn:17" class="footnote" rel="footnote" role="doc-noteref">17</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/builder.rs: 1668-1675</span>
<span class="k">fn</span> <span class="nf">get_cfg</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">driver</span><span class="p">::</span><span class="n">Cfg</span> <span class="p">{</span>
    <span class="nn">driver</span><span class="p">::</span><span class="n">Cfg</span> <span class="p">{</span>
        <span class="n">enable_io</span><span class="p">:</span> <span class="k">self</span><span class="py">.enable_io</span><span class="p">,</span>          <span class="c1">// ← .enable_io()</span>
        <span class="n">enable_time</span><span class="p">:</span> <span class="k">self</span><span class="py">.enable_time</span><span class="p">,</span>      <span class="c1">// ← .enable_time()</span>
        <span class="n">nevents</span><span class="p">:</span> <span class="k">self</span><span class="py">.nevents</span><span class="p">,</span>
        <span class="n">timer_flavor</span><span class="p">:</span> <span class="k">self</span><span class="py">.timer_flavor</span><span class="p">,</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">driver::Driver::new()</code> 在运行时判断这两个值：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/driver.rs: 47-58</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">cfg</span><span class="p">:</span> <span class="n">Cfg</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">(</span><span class="k">Self</span><span class="p">,</span> <span class="n">Handle</span><span class="p">)</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">io_stack</span><span class="p">,</span> <span class="n">io_handle</span><span class="p">,</span> <span class="n">signal_handle</span><span class="p">)</span> <span class="o">=</span>
        <span class="nf">create_io_stack</span><span class="p">(</span><span class="n">cfg</span><span class="py">.enable_io</span><span class="p">,</span> <span class="n">cfg</span><span class="py">.nevents</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>   <span class="c1">// ← 是否创建 io::Driver</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">time_driver</span><span class="p">,</span> <span class="n">time_handle</span><span class="p">)</span> <span class="o">=</span>
        <span class="nf">create_time_driver</span><span class="p">(</span><span class="n">cfg</span><span class="py">.enable_time</span><span class="p">,</span> <span class="o">..</span><span class="p">,</span> <span class="n">io_stack</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">clock</span><span class="p">);</span> <span class="c1">// ← 是否创建 time::Driver</span>
    <span class="nf">Ok</span><span class="p">((</span><span class="k">Self</span> <span class="p">{</span> <span class="n">inner</span><span class="p">:</span> <span class="n">time_driver</span> <span class="p">},</span> <span class="o">..</span><span class="p">))</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">create_io_stack</code> 内部根据 <code class="language-plaintext highlighter-rouge">enabled</code> 选择真正的 I/O 层或 <code class="language-plaintext highlighter-rouge">ParkThread</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/driver.rs: 149-170</span>
<span class="k">fn</span> <span class="nf">create_io_stack</span><span class="p">(</span><span class="n">enabled</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span> <span class="n">nevents</span><span class="p">:</span> <span class="nb">usize</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">..</span> <span class="p">{</span>
    <span class="k">if</span> <span class="n">enabled</span> <span class="p">{</span>
        <span class="k">let</span> <span class="p">(</span><span class="n">io_driver</span><span class="p">,</span> <span class="n">io_handle</span><span class="p">)</span> <span class="o">=</span> <span class="nn">io</span><span class="p">::</span><span class="nn">Driver</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">nevents</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="p">(</span><span class="nn">IoStack</span><span class="p">::</span><span class="nf">Enabled</span><span class="p">(</span><span class="n">process_driver</span><span class="p">),</span> <span class="nn">IoHandle</span><span class="p">::</span><span class="nf">Enabled</span><span class="p">(</span><span class="n">io_handle</span><span class="p">),</span> <span class="o">..</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">park_thread</span> <span class="o">=</span> <span class="nn">ParkThread</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
        <span class="p">(</span><span class="nn">IoStack</span><span class="p">::</span><span class="nf">Disabled</span><span class="p">(</span><span class="n">park_thread</span><span class="p">),</span> <span class="nn">IoHandle</span><span class="p">::</span><span class="nf">Disabled</span><span class="p">(</span><span class="n">unpark_thread</span><span class="p">),</span> <span class="o">..</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="93-两层控制的关系">9.3 两层控制的关系</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Cargo feature</th>
      <th style="text-align: left">Builder 选项</th>
      <th style="text-align: left">代码状态</th>
      <th style="text-align: left">运行时结果</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">net</code> + <code class="language-plaintext highlighter-rouge">.enable_io()</code></td>
      <td style="text-align: left">—</td>
      <td style="text-align: left">代码存在</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">IoStack::Enabled</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">net</code> + 无 <code class="language-plaintext highlighter-rouge">.enable_io()</code></td>
      <td style="text-align: left">—</td>
      <td style="text-align: left">代码存在</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">IoStack::Disabled</code></td>
    </tr>
    <tr>
      <td style="text-align: left">无 <code class="language-plaintext highlighter-rouge">net</code></td>
      <td style="text-align: left">—</td>
      <td style="text-align: left">代码不存在</td>
      <td style="text-align: left">编译报错</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">time</code> + <code class="language-plaintext highlighter-rouge">.enable_time()</code></td>
      <td style="text-align: left">—</td>
      <td style="text-align: left">代码存在</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">TimeDriver::Enabled</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">time</code> + 无 <code class="language-plaintext highlighter-rouge">.enable_time()</code></td>
      <td style="text-align: left">—</td>
      <td style="text-align: left">代码存在</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">TimeDriver::Disabled</code></td>
    </tr>
    <tr>
      <td style="text-align: left">无 <code class="language-plaintext highlighter-rouge">time</code></td>
      <td style="text-align: left">—</td>
      <td style="text-align: left">代码不存在</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">TimeDriver = IoStack</code></td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">rt</code> feature 也必须启用（默认开启）。最终最简的 runtime 只包含 <code class="language-plaintext highlighter-rouge">runtime::driver::Driver</code> + <code class="language-plaintext highlighter-rouge">IoStack::Disabled(ParkThread)</code>，可以用 <code class="language-plaintext highlighter-rouge">tokio::spawn</code> 做纯计算任务调度，完全不需要任何 I/O 或 Timer 能力。</p>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>上篇文章：《从 tokio::time::sleep 看异步 Timer 的实现：一次从 Future::poll 到哈希时间轮的源码之旅》，2026-05-03。详细分析了 <code class="language-plaintext highlighter-rouge">tokio::time::sleep</code> 从 <code class="language-plaintext highlighter-rouge">Future::poll</code>、<code class="language-plaintext highlighter-rouge">TimerEntry</code>、<code class="language-plaintext highlighter-rouge">StateCell</code> 到 <code class="language-plaintext highlighter-rouge">Wheel</code> 哈希时间轮的完整链路。以及更早的文章：《Rust async/await 的底层契约：从 Future::poll 到 Tokio 运行时》，2026-04-30，阐述了 <code class="language-plaintext highlighter-rouge">Future::poll</code>、<code class="language-plaintext highlighter-rouge">Context</code>、<code class="language-plaintext highlighter-rouge">Waker</code> 的核心协议。 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:1:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:2">
      <p>Tokio 源码，Driver 分层结构，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/driver.rs"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/driver.rs</code></a>。定义了 <code class="language-plaintext highlighter-rouge">Driver</code>（<code class="language-plaintext highlighter-rouge">TimeDriver</code> 包装 <code class="language-plaintext highlighter-rouge">IoStack</code>）、<code class="language-plaintext highlighter-rouge">Handle</code>（<code class="language-plaintext highlighter-rouge">IoHandle</code> + <code class="language-plaintext highlighter-rouge">SignalHandle</code> + <code class="language-plaintext highlighter-rouge">TimeHandle</code>）、以及 <code class="language-plaintext highlighter-rouge">create_io_stack</code> / <code class="language-plaintext highlighter-rouge">create_time_driver</code> 的创建逻辑。 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:2:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:11">
      <p>mio 源码，<code class="language-plaintext highlighter-rouge">cfg</code> 编译时选择操作系统后端，<a href="https://github.com/tokio-rs/mio/blob/mio-v1.2.0/src/sys/mod.rs#L42-L56"><code class="language-plaintext highlighter-rouge">mio/src/sys/mod.rs</code></a>。<code class="language-plaintext highlighter-rouge">cfg_os_poll!</code> 宏控制模块编译：unix 下进入 <code class="language-plaintext highlighter-rouge">unix/</code> 子模块（进一步选择 epoll / kqueue / poll），windows 下进入 <code class="language-plaintext highlighter-rouge">windows/</code> 模块（IOCP），wasi 下进入 <code class="language-plaintext highlighter-rouge">wasip1/</code> 模块。 <a href="#fnref:11" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:12">
      <p>mio 源码，epoll selector 实现，<a href="https://github.com/tokio-rs/mio/blob/mio-v1.2.0/src/sys/unix/selector/epoll.rs"><code class="language-plaintext highlighter-rouge">mio/src/sys/unix/selector/epoll.rs</code></a>。<code class="language-plaintext highlighter-rouge">Selector::new()</code> 通过 <code class="language-plaintext highlighter-rouge">epoll_create1</code> 创建 epoll fd，<code class="language-plaintext highlighter-rouge">Selector::select()</code> 通过 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 获取就绪事件，<code class="language-plaintext highlighter-rouge">Selector::register()</code> 通过 <code class="language-plaintext highlighter-rouge">epoll_ctl(EPOLL_CTL_ADD)</code> 注册 fd。默认使用 <code class="language-plaintext highlighter-rouge">EPOLLET</code> 边沿触发模式。 <a href="#fnref:12" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:13">
      <p>mio 源码，kqueue selector 实现，<a href="https://github.com/tokio-rs/mio/blob/mio-v1.2.0/src/sys/unix/selector/kqueue.rs"><code class="language-plaintext highlighter-rouge">mio/src/sys/unix/selector/kqueue.rs</code></a>。<code class="language-plaintext highlighter-rouge">Selector::new()</code> 通过 <code class="language-plaintext highlighter-rouge">kqueue()</code> 创建 kqueue fd，<code class="language-plaintext highlighter-rouge">Selector::select()</code> 通过 <code class="language-plaintext highlighter-rouge">kevent</code> 获取事件，<code class="language-plaintext highlighter-rouge">Selector::register()</code> 使用 <code class="language-plaintext highlighter-rouge">EVFILT_READ</code> / <code class="language-plaintext highlighter-rouge">EVFILT_WRITE</code> + <code class="language-plaintext highlighter-rouge">EV_CLEAR</code> 边沿触发标志。 <a href="#fnref:13" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:14">
      <p>mio Cargo.toml feature 配置，<a href="https://github.com/tokio-rs/mio/blob/mio-v1.2.0/Cargo.toml"><code class="language-plaintext highlighter-rouge">mio/Cargo.toml</code></a>。<code class="language-plaintext highlighter-rouge">os-poll</code> feature 启用 <code class="language-plaintext highlighter-rouge">cfg_os_poll!</code> 条件编译；<code class="language-plaintext highlighter-rouge">os-ext</code> feature 启用 <code class="language-plaintext highlighter-rouge">cfg_any_os_ext!</code> 额外扩展接口。 <a href="#fnref:14" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:15">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">runtime::io::Driver</code> 结构定义和 <code class="language-plaintext highlighter-rouge">turn</code> / <code class="language-plaintext highlighter-rouge">park_timeout</code> / <code class="language-plaintext highlighter-rouge">add_source</code> 方法，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/io/driver.rs#L25-L37"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/io/driver.rs</code></a>。<code class="language-plaintext highlighter-rouge">Driver</code> 持有 <code class="language-plaintext highlighter-rouge">poll: mio::Poll</code>；<code class="language-plaintext highlighter-rouge">turn()</code> 方法调用 <code class="language-plaintext highlighter-rouge">self.poll.poll(events, max_wait)</code> 执行 <code class="language-plaintext highlighter-rouge">epoll_wait</code>；<code class="language-plaintext highlighter-rouge">add_source()</code> 通过 <code class="language-plaintext highlighter-rouge">self.registry.register(source, token, interest)</code> 执行 <code class="language-plaintext highlighter-rouge">epoll_ctl(ADD)</code>。注册和等待在同一个 mio::Poll 实例上完成。 <a href="#fnref:15" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:15:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:15:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:16">
      <p>Tokio 源码，多线程 Parker 的 <code class="language-plaintext highlighter-rouge">TryLock&lt;Driver&gt;</code> 共享机制，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/scheduler/multi_thread/park.rs#L48-L65"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/scheduler/multi_thread/park.rs</code></a>。<code class="language-plaintext highlighter-rouge">Shared::driver</code> 是 <code class="language-plaintext highlighter-rouge">TryLock&lt;Driver&gt;</code>，一次只有一个线程可以拿到锁执行 <code class="language-plaintext highlighter-rouge">epoll_wait</code>，其他线程退化为 condvar 等待。 <a href="#fnref:16" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">TcpStream</code> 结构定义，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/net/tcp/stream.rs#L72-L74"><code class="language-plaintext highlighter-rouge">tokio/src/net/tcp/stream.rs</code></a>。<code class="language-plaintext highlighter-rouge">TcpStream</code> 只是 <code class="language-plaintext highlighter-rouge">PollEvented&lt;mio::net::TcpStream&gt;</code> 的一层包装，构建时调用 <code class="language-plaintext highlighter-rouge">PollEvented::new(connected)</code>。 <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:3:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:4">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">PollEvented</code> 结构定义与 <code class="language-plaintext highlighter-rouge">poll_read</code> 方法，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/io/poll_evented.rs"><code class="language-plaintext highlighter-rouge">tokio/src/io/poll_evented.rs</code></a>。<code class="language-plaintext highlighter-rouge">PollEvented</code> 是通用 I/O 包装器，其 <code class="language-plaintext highlighter-rouge">poll_read</code> 方法展示了 ET 模式下的 read 循环（<code class="language-plaintext highlighter-rouge">WouldBlock</code> → <code class="language-plaintext highlighter-rouge">clear_readiness</code> → 重试）以及针对 edge-triggered selector 的 <code class="language-plaintext highlighter-rouge">read</code> 部分结果优化。 <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:4:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:4:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:5">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">Registration</code> 结构定义与 <code class="language-plaintext highlighter-rouge">new_with_interest_and_handle</code>、<code class="language-plaintext highlighter-rouge">poll_io</code> 等方法，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/io/registration.rs"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/io/registration.rs</code></a>。<code class="language-plaintext highlighter-rouge">Registration</code> 是 I/O 资源与 reactor 的桥梁，封装了注册、就绪轮询和 WouldBlock 循环。 <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:5:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:5:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:6">
      <p>Tokio 源码，I/O Driver 的 <code class="language-plaintext highlighter-rouge">Driver::new</code>、<code class="language-plaintext highlighter-rouge">Handle::add_source</code>、<code class="language-plaintext highlighter-rouge">Driver::turn</code>，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/io/driver.rs"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/io/driver.rs</code></a>。<code class="language-plaintext highlighter-rouge">add_source</code> 将 <code class="language-plaintext highlighter-rouge">mio::Source</code> 注册到 epoll，token 为 <code class="language-plaintext highlighter-rouge">ScheduledIo</code> 的指针地址；<code class="language-plaintext highlighter-rouge">turn()</code> 阻塞在 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 上并分发事件。 <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:6:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:6:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:7">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">ScheduledIo</code> 结构定义、<code class="language-plaintext highlighter-rouge">set_readiness</code>、<code class="language-plaintext highlighter-rouge">wake</code> 方法，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/io/scheduled_io.rs"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/io/scheduled_io.rs</code></a>。核心结构：<code class="language-plaintext highlighter-rouge">readiness</code> 为打包的 AtomicUsize（16b readiness + 15b tick + 1b shutdown），<code class="language-plaintext highlighter-rouge">waiters</code> 为 <code class="language-plaintext highlighter-rouge">Mutex&lt;Waiters&gt;</code> 管理专用槽和链表等待者。<code class="language-plaintext highlighter-rouge">set_readiness</code> 使用 <code class="language-plaintext highlighter-rouge">fetch_update</code> CAS 实现 tick 保护，<code class="language-plaintext highlighter-rouge">wake</code> 使用 <code class="language-plaintext highlighter-rouge">WakeList</code> 批量唤醒。 <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:7:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:7:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:7:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:7:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a></p>
    </li>
    <li id="fn:uring">
      <p>Tokio 源码，io_uring 集成代码，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/io/driver/uring.rs"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/io/driver/uring.rs</code></a>。<code class="language-plaintext highlighter-rouge">UringContext</code> 管理 io_uring 实例和 op 生命周期，<code class="language-plaintext highlighter-rouge">dispatch_completions()</code> 在 <code class="language-plaintext highlighter-rouge">turn()</code> 方法中被调用，io_uring 的 eventfd 通过 <code class="language-plaintext highlighter-rouge">SourceFd</code> 注册到 epoll 上。 <a href="#fnref:uring" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:uring:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:uring:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:uring2">
      <p>《io_uring and networking: 2024》，LWN.net，<a href="https://lwn.net/Articles/961273/">https://lwn.net/Articles/961273/</a>。讨论了 io_uring 在网络 I/O 场景下的现状，包括性能对比和与 epoll 的取舍。 <a href="#fnref:uring2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">AsyncFd</code> 结构定义与使用模式，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/io/async_fd.rs"><code class="language-plaintext highlighter-rouge">tokio/src/io/async_fd.rs</code></a>。通用的 fd → async 包装器，通过 <code class="language-plaintext highlighter-rouge">mio::unix::SourceFd</code> 接入 I/O 驱动，提供 <code class="language-plaintext highlighter-rouge">readable()</code> / <code class="language-plaintext highlighter-rouge">writable()</code> / <code class="language-plaintext highlighter-rouge">async_io()</code> 等高级 API 和 <code class="language-plaintext highlighter-rouge">poll_read_ready()</code> / <code class="language-plaintext highlighter-rouge">try_io()</code> 等底层 API。 <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:8:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:8:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:17">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">Builder::get_cfg</code> 和 <code class="language-plaintext highlighter-rouge">Cfg</code> 定义，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/builder.rs#L1668-L1675"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/builder.rs</code></a>。<code class="language-plaintext highlighter-rouge">Cfg</code> 三个关键字段：<code class="language-plaintext highlighter-rouge">enable_io</code>（来自 <code class="language-plaintext highlighter-rouge">.enable_io()</code>）、<code class="language-plaintext highlighter-rouge">enable_time</code>（来自 <code class="language-plaintext highlighter-rouge">.enable_time()</code>）、<code class="language-plaintext highlighter-rouge">nevents</code>（来自 <code class="language-plaintext highlighter-rouge">.max_io_events_per_tick()</code>）。 <a href="#fnref:17" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="rust" /><category term="tokio" /><summary type="html"><![CDATA[对比 tokio::time::sleep 与 tokio::net::TcpStream，拆解时间驱动与 I/O 驱动如何共享同一 runtime 主循环。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://weinan.tech/images/og/tokio-async.png" /><media:content medium="image" url="https://weinan.tech/images/og/tokio-async.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">从 O(n) 到 O(1)：经典定时器论文《Hashed and Hierarchical Timing Wheels》的七种设计</title><link href="https://weinan.tech/2026/05/08/hashed-hierarchical-timing-wheels-paper.html" rel="alternate" type="text/html" title="从 O(n) 到 O(1)：经典定时器论文《Hashed and Hierarchical Timing Wheels》的七种设计" /><published>2026-05-08T00:00:00+08:00</published><updated>2026-05-08T00:00:00+08:00</updated><id>https://weinan.tech/2026/05/08/hashed-hierarchical-timing-wheels-paper</id><content type="html" xml:base="https://weinan.tech/2026/05/08/hashed-hierarchical-timing-wheels-paper.html"><![CDATA[<style>
/* Mermaid diagram container */
.mermaid-container {
  position: relative;
  display: block;
  cursor: pointer;
  transition: opacity 0.2s;
  max-width: 100%;
  margin: 20px 0;
  overflow-x: auto;
}

.mermaid-container:hover {
  opacity: 0.8;
}

.mermaid-container svg {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Modal overlay */
.mermaid-modal {
  display: none;
  position: fixed;
  z-index: 9999;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9);
  animation: fadeIn 0.3s;
}

.mermaid-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Modal content */
.mermaid-modal-content {
  position: relative;
  width: 90vw;
  height: 90vh;
  overflow: hidden;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
}

.mermaid-modal-diagram {
  transform-origin: center center;
  transition: transform 0.2s ease;
  display: inline-block;
  min-width: 100%;
  cursor: grab;
  user-select: none;
}

.mermaid-modal-diagram.dragging {
  cursor: grabbing;
  transition: none;
}

.mermaid-modal-diagram svg {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;
}

/* Control buttons */
.mermaid-controls {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
  z-index: 10000;
}

.mermaid-btn {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
  color: #333;
  font-weight: 500;
}

.mermaid-btn:hover {
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Close button */
.mermaid-close {
  background: #f44336;
  color: white;
  border: none;
}

.mermaid-close:hover {
  background: #d32f2f;
}

/* Zoom indicator */
.mermaid-zoom-level {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 10000;
}
</style>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  mermaid.initialize({
    startOnLoad: false,
    theme: 'default',
    securityLevel: 'loose',
    htmlLabels: true,
    themeVariables: {
      fontSize: '14px'
    }
  });

  let currentZoom = 1;
  let currentModal = null;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let translateX = 0;
  let translateY = 0;

  // Create modal HTML
  function createModal() {
    const modal = document.createElement('div');
    modal.className = 'mermaid-modal';
    modal.innerHTML = `
      <div class="mermaid-controls">
        <button class="mermaid-btn zoom-in">放大 +</button>
        <button class="mermaid-btn zoom-out">缩小 -</button>
        <button class="mermaid-btn zoom-reset">重置</button>
        <button class="mermaid-btn mermaid-close">关闭 ✕</button>
      </div>
      <div class="mermaid-modal-content">
        <div class="mermaid-modal-diagram"></div>
      </div>
      <div class="mermaid-zoom-level">100%</div>
    `;
    document.body.appendChild(modal);
    return modal;
  }

  // Show modal with diagram
  function showModal(diagramContent) {
    if (!currentModal) {
      currentModal = createModal();
      setupModalEvents();
    }

    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.innerHTML = diagramContent;

    // Remove any width/height attributes from SVG to make it responsive
    const svg = modalDiagram.querySelector('svg');
    if (svg) {
      svg.removeAttribute('width');
      svg.removeAttribute('height');
      svg.style.width = '100%';
      svg.style.height = 'auto';
    }

    // Setup drag functionality
    setupDrag(modalDiagram);

    currentModal.classList.add('active');
    currentZoom = 1;
    resetPosition();
    updateZoom();
    document.body.style.overflow = 'hidden';
  }

  // Hide modal
  function hideModal() {
    if (currentModal) {
      currentModal.classList.remove('active');
      document.body.style.overflow = '';
    }
  }

  // Update zoom level
  function updateZoom() {
    if (!currentModal) return;
    const diagram = currentModal.querySelector('.mermaid-modal-diagram');
    const zoomLevel = currentModal.querySelector('.mermaid-zoom-level');
    diagram.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentZoom})`;
    zoomLevel.textContent = `${Math.round(currentZoom * 100)}%`;
  }

  // Reset position when zoom changes
  function resetPosition() {
    translateX = 0;
    translateY = 0;
  }

  // Setup drag functionality
  function setupDrag(element) {
    element.addEventListener('mousedown', startDrag);
    element.addEventListener('touchstart', startDrag);
  }

  function startDrag(e) {
    if (e.type === 'mousedown' && e.button !== 0) return; // Only left click

    isDragging = true;
    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.classList.add('dragging');

    if (e.type === 'touchstart') {
      startX = e.touches[0].clientX - translateX;
      startY = e.touches[0].clientY - translateY;
    } else {
      startX = e.clientX - translateX;
      startY = e.clientY - translateY;
    }

    document.addEventListener('mousemove', drag);
    document.addEventListener('touchmove', drag);
    document.addEventListener('mouseup', stopDrag);
    document.addEventListener('touchend', stopDrag);
  }

  function drag(e) {
    if (!isDragging) return;
    e.preventDefault();

    if (e.type === 'touchmove') {
      translateX = e.touches[0].clientX - startX;
      translateY = e.touches[0].clientY - startY;
    } else {
      translateX = e.clientX - startX;
      translateY = e.clientY - startY;
    }

    updateZoom();
  }

  function stopDrag() {
    isDragging = false;
    const modalDiagram = currentModal?.querySelector('.mermaid-modal-diagram');
    if (modalDiagram) {
      modalDiagram.classList.remove('dragging');
    }
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('touchmove', drag);
    document.removeEventListener('mouseup', stopDrag);
    document.removeEventListener('touchend', stopDrag);
  }

  // Setup modal event listeners
  function setupModalEvents() {
    if (!currentModal) return;

    // Close button
    currentModal.querySelector('.mermaid-close').addEventListener('click', hideModal);

    // Zoom buttons
    currentModal.querySelector('.zoom-in').addEventListener('click', () => {
      currentZoom = Math.min(currentZoom + 0.25, 3);
      updateZoom();
    });

    currentModal.querySelector('.zoom-out').addEventListener('click', () => {
      currentZoom = Math.max(currentZoom - 0.25, 0.5);
      updateZoom();
    });

    currentModal.querySelector('.zoom-reset').addEventListener('click', () => {
      currentZoom = 1;
      resetPosition();
      updateZoom();
    });

    // Close on background click
    currentModal.addEventListener('click', (e) => {
      if (e.target === currentModal) {
        hideModal();
      }
    });

    // Close on ESC key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && currentModal.classList.contains('active')) {
        hideModal();
      }
    });
  }

  // Convert Jekyll-rendered code blocks to mermaid divs
  document.addEventListener('DOMContentLoaded', async function() {
    const codeBlocks = document.querySelectorAll('code.language-mermaid');

    for (const codeBlock of codeBlocks) {
      const pre = codeBlock.parentElement;
      const container = document.createElement('div');
      container.className = 'mermaid-container';

      const mermaidDiv = document.createElement('div');
      mermaidDiv.className = 'mermaid';
      mermaidDiv.textContent = codeBlock.textContent;

      container.appendChild(mermaidDiv);
      pre.replaceWith(container);
    }

    // Render all mermaid diagrams
    try {
      await mermaid.run({
        querySelector: '.mermaid'
      });
      console.log('Mermaid diagrams rendered successfully');
    } catch (error) {
      console.error('Mermaid rendering error:', error);
    }

    // Add click handlers to rendered diagrams
    document.querySelectorAll('.mermaid-container').forEach((container, index) => {
      // Find the rendered SVG inside the container
      const svg = container.querySelector('svg');
      if (!svg) {
        console.warn(`No SVG found in container ${index}`);
        return;
      }

      // Make the container clickable
      container.style.cursor = 'pointer';
      container.title = '点击查看大图';

      container.addEventListener('click', function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Clone the SVG for the modal
        const svgClone = svg.cloneNode(true);
        const tempDiv = document.createElement('div');
        tempDiv.appendChild(svgClone);

        console.log('Opening modal for diagram', index);
        showModal(tempDiv.innerHTML);
      });

      console.log(`Click handler added to diagram ${index}`);
    });
  });
</script>

<blockquote>
  <p>TCP 超时重传、时钟信号、异步 I/O 超时——一个操作系统内核里同时活跃着成千上万个定时器。如果每次时钟 tick 都要遍历所有定时器才能知道谁该到期，系统会随着并发量增加越跑越慢。这个问题直到 1996 年才被一篇论文系统性地解决。</p>
</blockquote>

<h2 id="引言">引言</h2>

<p>先想象一个具体的场景。一个网络服务器上有 5 万个并发 TCP 连接，每个连接都有一个自己的超时定时器。你的操作系统每秒会产生 1000 次时钟中断（HZ=1000）。如果每次中断都要遍历全部 5 万个定时器，那每秒仅仅是检查定时器就要做 5000 万次操作。</p>

<p>这恰好是 Varghese 和 Lauck 在 1996 年论文<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>里定义的问题起点。</p>

<p>这篇论文的贡献不在于提出了一个全新的算法——时间轮的思想此前已有萌芽——而在于它<strong>系统性地遍历了整个定时器实现的设计空间</strong>，从最简单粗暴的 Scheme 1 到精巧的分层时间轮 Scheme 7，完整地展示了不同约束下的最优选择。</p>

<h2 id="概念先行时间轮的直觉">概念先行：时间轮的直觉</h2>

<p>论文的核心思想可以用一句话概括：<strong>将定时器按到期时间散列到环形数组的不同槽位中，每 tick 只处理当前指针指向的槽位，从而让 per-tick 的工作量与定时器总数解耦。</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>朴素做法（Scheme 1）：  每个 tick 遍历所有 50000 个定时器 → O(n)
                         ↓
时间轮（Scheme 4）：    每个 tick 只检查 1 个槽位       → 平均 O(1)
</code></pre></div></div>

<p>这个对比锚定了整篇论文的动机——在定时器数量达到数万时，O(n) 和 O(1) 的差距不是常数倍的，而是能否工作的区别。</p>

<h2 id="定时器模块的四个基本操作">定时器模块的四个基本操作</h2>

<p>论文首先建立了一个通用分析框架：任何定时器模块都由四个基本例程组成<sup id="fnref:1:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>。</p>

<table>
  <thead>
    <tr>
      <th>例程</th>
      <th>调用方</th>
      <th>功能</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">STARTTIMER(Interval, RequestId, ExpiryAction)</code></td>
      <td>应用程序</td>
      <td>启动一个定时器</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">STOPTIMER(RequestId)</code></td>
      <td>应用程序</td>
      <td>取消一个定时器</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">PERTICKBOOKKEEPING</code></td>
      <td>时钟中断</td>
      <td>每 tick 调用，检查定时器</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">EXPIRYPROCESSING</code></td>
      <td>PERTICKBOOKKEEPING</td>
      <td>执行到期的回调</td>
    </tr>
  </tbody>
</table>

<pre><code class="language-mermaid">sequenceDiagram
    participant App as 应用程序
    participant Timer as 定时器模块
    participant Clock as 硬件时钟
    participant Handler as 回调函数

    App-&gt;&gt;Timer: STARTTIMER(100ms, id, callback)
    Note over Timer: 将定时器插入数据结构
    App-&gt;&gt;App: 继续执行其他工作

    loop 每 tick
        Clock-&gt;&gt;Timer: 时钟中断
        Timer-&gt;&gt;Timer: PERTICKBOOKKEEPING()
        alt 有到期定时器
            Timer-&gt;&gt;Handler: EXPIRYPROCESSING(callback)
        end
    end

    App-&gt;&gt;Timer: STOPTIMER(id)
    Note over Timer: 从数据结构中移除
</code></pre>

<p>七种方案的本质，就是在 STARTTIMER 的延迟和 PERTICKBOOKKEEPING 的工作量之间做取舍。注意这四个例程是<strong>作为一个整体在每种方案中完整实现的</strong>——不是选几个用，而是用不同的数据结构实现同一套接口：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Scheme 1（无序列表）：
  STARTTIMER:    O(1)        STOPTIMER:     O(1)
  PERTICKBK:     O(n)        EXPIRYPROC:    O(1)

Scheme 6（哈希时间轮）：
  STARTTIMER:    O(1)        STOPTIMER:     O(1)
  PERTICKBK:     平均 O(1)   EXPIRYPROC:    O(1)
</code></pre></div></div>

<p>四种例程按调用方式和性能敏感度可以分成两组：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">维度</th>
      <th style="text-align: left">STARTTIMER / STOPTIMER</th>
      <th style="text-align: left">PERTICKBOOKKEEPING / EXPIRYPROCESSING</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>调用方</strong></td>
      <td style="text-align: left">应用程序主动调用</td>
      <td style="text-align: left">时钟中断被动触发</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>是否暴露给用户</strong></td>
      <td style="text-align: left">是，延迟直接影响体验</td>
      <td style="text-align: left">否，但决定系统吞吐上限</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>性能敏感度</strong></td>
      <td style="text-align: left">延迟敏感（越快越好）</td>
      <td style="text-align: left">吞吐敏感（不能拖太久）</td>
    </tr>
  </tbody>
</table>

<p>沿着这个分组，每种方案都在两个方向上做了不同取舍：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>                STARTTIMER 敏感               PERTICKBOOKKEEPING 敏感
                ──────────────►               ────────────────►
Scheme 1         O(1)                                     O(n)
Scheme 2         O(n)                                     O(1)
Scheme 3         O(log n)                                 O(1)
Scheme 4/6/7     O(1) / O(m)                              O(1)
</code></pre></div></div>

<p>时间轮方案（Scheme 4/6/7）之所以是论文的推荐方案，正是因为它们在两个方向上同时做到了 O(1) 或接近 O(1)——而不是像之前的设计那样，总要牺牲一个方向。</p>

<h2 id="方案-1-3论文之前的设计">方案 1-3：论文之前的设计</h2>

<p>在引入时间轮之前，论文列举了三种已有的实现方式。它们构成了”对照组”。</p>

<h3 id="scheme-1直白法">Scheme 1：直白法</h3>

<p><strong>数据结构</strong>：无序定时器列表。
<strong>做法</strong>：每 tick 遍历所有定时器，逐个递减计数器。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Scheme 1 的 PERTICKBOOKKEEPING</span>
<span class="kt">void</span> <span class="nf">per_tick_bookkeeping</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="err">每个定时器</span> <span class="n">t</span> <span class="n">in</span> <span class="err">链表</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">t</span><span class="o">-&gt;</span><span class="n">counter</span><span class="o">--</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">t</span><span class="o">-&gt;</span><span class="n">counter</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">EXPIRYPROCESSING</span><span class="p">(</span><span class="n">t</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>操作</th>
      <th>复杂度</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>STARTTIMER</td>
      <td>O(1)</td>
    </tr>
    <tr>
      <td>STOPTIMER</td>
      <td>O(1)</td>
    </tr>
    <tr>
      <td>PERTICKBOOKKEEPING</td>
      <td><strong>O(n)</strong></td>
    </tr>
  </tbody>
</table>

<p>每 tick O(n) 是灾难性的。但它有一个优势：<strong>实现最简单，内存占用最小</strong>。</p>

<h3 id="scheme-2有序链表">Scheme 2：有序链表</h3>

<p><strong>数据结构</strong>：按绝对过期时间排序的链表。
<strong>做法</strong>：每 tick 只检查链表头。</p>

<table>
  <thead>
    <tr>
      <th>操作</th>
      <th>复杂度</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>STARTTIMER</td>
      <td><strong>O(n)</strong></td>
    </tr>
    <tr>
      <td>STOPTIMER</td>
      <td>O(1)</td>
    </tr>
    <tr>
      <td>PERTICKBOOKKEEPING</td>
      <td>O(1)</td>
    </tr>
  </tbody>
</table>

<p>相当于把 per-tick 的 O(n) 转嫁到了 STARTTIMER 上。适合定时器启动频率低的场景。</p>

<h3 id="scheme-3树结构">Scheme 3：树结构</h3>

<p><strong>数据结构</strong>：平衡树（红黑树）或堆。</p>

<table>
  <thead>
    <tr>
      <th>操作</th>
      <th>复杂度</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>STARTTIMER</td>
      <td>O(log n)</td>
    </tr>
    <tr>
      <td>STOPTIMER</td>
      <td>O(log n)</td>
    </tr>
    <tr>
      <td>PERTICKBOOKKEEPING</td>
      <td>O(1)</td>
    </tr>
  </tbody>
</table>

<p>虽然三个操作都是对数复杂度，但论文指出其常数较大，而且 O(log n) 在定时器数量极大时依然不够理想。</p>

<p>这三种方案的共同问题是：<strong>当定时器数量 n 增大时，至少有一个操作的时间复杂度随 n 增长。</strong></p>

<h2 id="方案-4基本时间轮论文的核心创新">方案 4：基本时间轮——论文的核心创新</h2>

<p>Scheme 4 是整篇论文的突破点，后两个方案都是它的变体。</p>

<h3 id="数据结构">数据结构</h3>

<p>一个大小为 N 的环形数组（circular buffer），每个槽位是一个定时器链表：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>       当前指针
          │
          ▼
    ┌───┬───┬───┬───┬───┬───┬───┬───┐
    │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │    ... 环形数组（N=8）
    └───┴───┴───┴───┴───┴───┴───┴───┘
      │           │           │
      ▼           ▼           ▼
   链表1        链表2        链表3
</code></pre></div></div>

<h3 id="操作">操作</h3>

<ul>
  <li><strong>STARTTIMER</strong>：<code class="language-plaintext highlighter-rouge">slot = (current_pointer + Interval) % N</code>，直接插入该槽位的链表。O(1)</li>
  <li><strong>PERTICKBOOKKEEPING</strong>：移动指针到下一个槽位，处理该槽位链表中的<strong>所有</strong>定时器。O(1)</li>
  <li><strong>STOPTIMER</strong>：从链表中移除（持有节点指针）。O(1)</li>
</ul>

<p>论文特别强调，在 PERTICKBOOKKEEPING 中，如果当前槽位为空，什么也不用做<sup id="fnref:1:2"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>：</p>

<blockquote>
  <p>“If the element is 0 (no list of timers waiting to expire), no more work is done on that timer tick.”</p>
</blockquote>

<h3 id="为什么叫时间轮">为什么叫”时间轮”？</h3>

<p>论文中的原话<sup id="fnref:1:3"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>：</p>

<blockquote>
  <p>“We call this data structure a timing wheel. It can be viewed as a circular array of lists, where the position in the array corresponds to the time remaining before expiration.”</p>
</blockquote>

<h3 id="限制">限制</h3>

<p>基本时间轮要求数组大小 N 至少等于最大可能的定时器间隔。32 位计数器下，N 需要达到 4 亿以上——内存完全不可接受。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Scheme 4:  O(1) 的神器，但 N 必须 ≥ MaxInterval → 内存爆炸
</code></pre></div></div>

<h2 id="方案-5-6哈希时间轮">方案 5-6：哈希时间轮</h2>

<p>基本时间轮的核心限制是数组大小必须覆盖最大间隔。哈希的思路是：用一个较小的表，把间隔哈希到槽位。</p>

<h3 id="scheme-5哈希--有序链表">Scheme 5：哈希 + 有序链表</h3>

<p>每个槽位的链表按到期时间排序。</p>

<table>
  <thead>
    <tr>
      <th>操作</th>
      <th>复杂度</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>STARTTIMER</td>
      <td>平均 O(1)</td>
    </tr>
    <tr>
      <td>STOPTIMER</td>
      <td>O(1)</td>
    </tr>
    <tr>
      <td>PERTICKBOOKKEEPING</td>
      <td>平均 O(1)</td>
    </tr>
  </tbody>
</table>

<p>排序的好处是 PERTICKBOOKKEEPING 只需检查链表头部的定时器。但 STARTTIMER 需要通过二分或顺序查找找到插入位置，平均不是严格 O(1)。</p>

<h3 id="scheme-6哈希--无序链表">Scheme 6：哈希 + 无序链表</h3>

<p>每个槽位的链表不排序，新定时器直接插入头部。</p>

<table>
  <thead>
    <tr>
      <th>操作</th>
      <th>复杂度</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>STARTTIMER</td>
      <td><strong>严格的 O(1)</strong></td>
    </tr>
    <tr>
      <td>STOPTIMER</td>
      <td>O(1)</td>
    </tr>
    <tr>
      <td>PERTICKBOOKKEEPING</td>
      <td>平均 O(1) — 遍历当前槽位整个链表</td>
    </tr>
  </tbody>
</table>

<p><strong>这是论文为通用操作系统推荐的首选方案之一。</strong> BSD UNIX 后来实际采用的就是 Scheme 6，只修改了约 500 行内核代码<sup id="fnref:1:4"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>。</p>

<p>为什么无序链表更好？论文的推理是：<strong>STARTTIMER 的延迟通常比 PERTICKBOOKKEEPING 更敏感</strong>。前者发生在应用程序的关键路径上，后者发生在时钟中断上下文中，可以接受稍大的工作量。无条件 O(1) 的 STARTTIMER 比平均 O(1) 更有价值。</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Scheme 5（有序）：      STARTTIMER 平均 O(1) —— 但有哈希冲突时的退化风险
Scheme 6（无序）：      STARTTIMER 严格 O(1) —— 确定性延迟，更适合实时场景
</code></pre></div></div>

<h2 id="方案-7分层时间轮">方案 7：分层时间轮</h2>

<p>哈希时间轮用固定大小的表解决了”最大间隔”问题，但槽位数仍然是固定的。分层时间轮用另一个思路——<strong>用多组不同粒度的数组</strong>—来覆盖巨大的时间范围。</p>

<h3 id="数据结构-1">数据结构</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>   天轮（100 槽）          时轮（24 槽）          分轮（60 槽）          秒轮（60 槽）
   ┌──┬──┬──┬──┬──┐       ┌──┬──┬──┬──┬──┐       ┌──┬──┬──┬──┬──┐       ┌──┬──┬──┬──┬──┐
   │0 │1 │2 │...│99│       │0 │1 │2 │...│23│       │0 │1 │2 │...│59│       │0 │1 │2 │...│59│
   └──┴──┴──┴──┴──┘       └──┴──┴──┴──┴──┘       └──┴──┴──┴──┴──┘       └──┴──┴──┴──┴──┘
</code></pre></div></div>

<p>总共只需要 100 + 24 + 60 + 60 = <strong>244 个槽位</strong>，就可以覆盖 100 天的时间范围。</p>

<h3 id="cascading逐级降级">Cascading：逐级降级</h3>

<p>分层时间轮最核心的机制是 <strong>cascading</strong>——定时器从上层逐渐”下沉”到底层，只有到达最底层的秒轮时才真正触发回调。</p>

<p>插入一个 50 分 45 秒的定时器的完整路径：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>STARTTIMER(50分45秒, callback)

  时轮（1小时后到期）
    → 时轮槽位 = 当前时 + 1
    → 存储: { minutes=15, seconds=15 }
      ↓ 1 小时后，时轮指针到达该槽位
  分轮（15分钟后到期）
    → 分轮槽位 = 当前分 + 15
    → 存储: { seconds=15 }
      ↓ 15 分钟后，分轮指针到达该槽位
  秒轮（15秒后到期）
    → 秒轮槽位 = 当前秒 + 15
    → 存储: { callback }
      ↓ 15 秒后，秒轮指针到达该槽位
  执行 callback()
</code></pre></div></div>

<pre><code class="language-mermaid">sequenceDiagram
    participant App as STARTTIMER
    participant Hour as 时轮
    participant Minute as 分轮
    participant Second as 秒轮

    App-&gt;&gt;Hour: 插入 50分45秒
    Note over Hour: 等待 1 小时
    Hour-&gt;&gt;Hour: 指针到达当前槽位
    Hour-&gt;&gt;Minute: cascading → 剩余 15 分钟
    Note over Minute: 等待 15 分钟
    Minute-&gt;&gt;Minute: 指针到达当前槽位
    Minute-&gt;&gt;Second: cascading → 剩余 15 秒
    Note over Second: 等待 15 秒
    Second-&gt;&gt;App: 到期 → EXPIRYPROCESSING
</code></pre>

<p>论文中的原话描述这个机制<sup id="fnref:1:5"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>：</p>

<blockquote>
  <p>“When the hour timer reaches 11, the list is examined. The expiry processing routine will insert the remainder of the seconds (15) in the minute array, 15 elements after the current minute pointer (0).”</p>
</blockquote>

<h3 id="复杂度">复杂度</h3>

<table>
  <thead>
    <tr>
      <th>操作</th>
      <th>复杂度</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>STARTTIMER</td>
      <td>O(m)，m 为层数（通常 ≤ 5）</td>
    </tr>
    <tr>
      <td>STOPTIMER</td>
      <td>O(1)</td>
    </tr>
    <tr>
      <td>PERTICKBOOKKEEPING</td>
      <td>O(1)</td>
    </tr>
  </tbody>
</table>

<p>论文的评价<sup id="fnref:1:6"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>：</p>

<blockquote>
  <p>“Scheme 7 uses essentially logarithmic time to insert an element; thus it is comparable in complexity to standard priority queue implementations like heaps. However, the constants appear to be better for Scheme 7.”</p>
</blockquote>

<p>虽然插入是 O(m)，但 m 是常数（4 或 5），远小于 log n。而且<strong>内存占用完全固定</strong>。</p>

<h2 id="七种方案全景对比">七种方案全景对比</h2>

<table>
  <thead>
    <tr>
      <th style="text-align: left">方案</th>
      <th style="text-align: left">数据结构</th>
      <th style="text-align: left">STARTTIMER</th>
      <th style="text-align: left">STOPTIMER</th>
      <th style="text-align: left">PERTICK</th>
      <th style="text-align: left">内存</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">Scheme 1</td>
      <td style="text-align: left">无序列表</td>
      <td style="text-align: left">O(1)</td>
      <td style="text-align: left">O(1)</td>
      <td style="text-align: left"><strong>O(n)</strong></td>
      <td style="text-align: left">O(n)</td>
    </tr>
    <tr>
      <td style="text-align: left">Scheme 2</td>
      <td style="text-align: left">有序链表</td>
      <td style="text-align: left"><strong>O(n)</strong></td>
      <td style="text-align: left">O(1)</td>
      <td style="text-align: left">O(1)</td>
      <td style="text-align: left">O(n)</td>
    </tr>
    <tr>
      <td style="text-align: left">Scheme 3</td>
      <td style="text-align: left">树/堆</td>
      <td style="text-align: left">O(log n)</td>
      <td style="text-align: left">O(log n)</td>
      <td style="text-align: left">O(1)</td>
      <td style="text-align: left">O(n)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Scheme 4</strong></td>
      <td style="text-align: left">基本时间轮</td>
      <td style="text-align: left"><strong>O(1)</strong></td>
      <td style="text-align: left"><strong>O(1)</strong></td>
      <td style="text-align: left"><strong>O(1)</strong></td>
      <td style="text-align: left">O(MaxInterval)</td>
    </tr>
    <tr>
      <td style="text-align: left">Scheme 5</td>
      <td style="text-align: left">哈希+有序</td>
      <td style="text-align: left">平均 O(1)</td>
      <td style="text-align: left">O(1)</td>
      <td style="text-align: left">平均 O(1)</td>
      <td style="text-align: left">O(TableSize + n)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Scheme 6</strong></td>
      <td style="text-align: left">哈希+无序</td>
      <td style="text-align: left"><strong>O(1)</strong></td>
      <td style="text-align: left">O(1)</td>
      <td style="text-align: left">平均 O(1)</td>
      <td style="text-align: left">O(TableSize + n)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Scheme 7</strong></td>
      <td style="text-align: left">分层时间轮</td>
      <td style="text-align: left">O(m)</td>
      <td style="text-align: left">O(1)</td>
      <td style="text-align: left">O(1)</td>
      <td style="text-align: left">O(ΣSizes)</td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p>注：m 为层数（常数 4-5），TableSize 为哈希表大小，ΣSizes 为各层数组大小之和（如 244 个槽位）</p>
</blockquote>

<p>论文的最终推荐<sup id="fnref:1:7"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>：</p>

<blockquote>
  <p>“For a general timer module… we recommend <strong>Scheme 6 or 7</strong>.”</p>
</blockquote>

<pre><code class="language-mermaid">graph TD
    Start["定时器实现需求"] --&gt; Decision{"设计约束?"}

    Decision --&gt;|"定时器数量少"| S1["Scheme 1&lt;br/&gt;直白法 O(n)/tick"]
    Decision --&gt;|"启动频率低"| S2["Scheme 2&lt;br/&gt;有序链表 O(n) start"]
    Decision --&gt;|"通用 OS"| S6["Scheme 6&lt;br/&gt;哈希+无序 O(1) start"]
    Decision --&gt;|"内存受限"| S7["Scheme 7&lt;br/&gt;分层时间轮 极小内存"]
    Decision --&gt;|"短间隔精确"| S4["Scheme 4&lt;br/&gt;基本时间轮 O(1) all"]

    style S6 fill:#90EE90
    style S7 fill:#87CEEB
</code></pre>

<h2 id="论文的影响">论文的影响</h2>

<h3 id="bsd-unix">BSD UNIX</h3>

<p>Costello 将 Scheme 6 嵌入 BSD 内核。实测表明：新实现中 STARTTIMER 和 STOPTIMER 的时间恒定，不受定时器数量影响；而旧实现（Scheme 2，有序链表）的时间随定时器数量线性增长。</p>

<h3 id="linux-内核">Linux 内核</h3>

<p>Linux 内核对这篇论文的思想有正反两个方向的借鉴：</p>

<ul>
  <li><strong>低精度定时器（timer wheel）</strong>：位于 <code class="language-plaintext highlighter-rouge">kernel/time/timer.c</code>，本质就是分层时间轮——8 到 9 层、每层 64 个 bucket（<code class="language-plaintext highlighter-rouge">LVL_SIZE = 64</code>，<code class="language-plaintext highlighter-rouge">LVL_DEPTH = 8/9</code>）<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></li>
  <li><strong>高精度定时器（hrtimer）</strong>：使用红黑树，追求纳秒级精度</li>
</ul>

<p>低精度定时器的层级设计几乎是 Scheme 7 的直接工程实现：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// kernel/time/timer.c  (HZ=1000 时)
// Level  粒度            覆盖范围
//   0     1 ms             0-63 ms
//   1     8 ms            64-511 ms
//   2    64 ms           512-4095 ms
//   3   512 ms          4096-32767 ms
//   4  4096 ms (~4s)   32768-262143 ms (~32s-~4m)
//   5 32768 ms (~32s) 262144-2097151 ms (~4m-~34m)
//   6 262144 ms (~4m) 2097152-16777215 ms (~34m-~4h)
//   7 2097152 ms (~34m) 16777216-134217727 ms (~4h-~1d)
//   8 16777216 ms (~4h) 134217728-1073741822 ms (~1d-~12d)
</code></pre></div></div>

<h3 id="tokiorust-异步运行时">Tokio（Rust 异步运行时）</h3>

<p>Tokio 的异步 timer 使用 6 层、每层 64 slot 的哈希时间轮，代码注释明确引用了 Varghese &amp; Lauck 的论文<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Level 0: 64 × 1 ms      = 64 ms
Level 1: 64 × 64 ms     = ~4 s
Level 2: 64 × ~4 s      = ~4 min
...                     ...
Level 5: 64 × ~12 day   = ~2 年
</code></pre></div></div>

<h2 id="总结这篇论文为什么经典">总结：这篇论文为什么经典</h2>

<p>这篇论文的成就不是某个石破天惊的算法，而是<strong>系统性地定义了一个问题空间，然后完整地遍历了它</strong>。</p>

<p>三点贡献值得记住：</p>

<ol>
  <li><strong>通用的分析框架</strong>：四个例程（STARTTIMER、STOPTIMER、PERTICKBOOKKEEPING、EXPIRYPROCESSING）成为后来所有定时器实现的基准</li>
  <li><strong>完整的设计空间</strong>：从 Scheme 1 到 Scheme 7，每一种方案都是不同约束下的最优解——没有银弹，只有权衡</li>
  <li><strong>一个简单的思想</strong>：时间轮——利用时间单调递增的特性，把定时器按时间散列，每 tick 只处理一个槽位——这是空间换时间的一个教科书级范例</li>
</ol>

<p>而全篇论文中我最喜欢的一段话，是下面这个不起眼的观察<sup id="fnref:1:8"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>：</p>

<blockquote>
  <p>“In timer algorithms, however, the crucial observation is that <strong>some entity needs to do O(1) work per tick to update the current time</strong>; it costs only a few more instructions for the same entity to step through an empty bucket.”</p>
</blockquote>

<p>系统本来就要在每 tick 做固定工作来更新时间。时间轮只是在这条不可避免的执行路径上，顺便检查一下当前槽位。当槽位为空时——这是最常见的情况——额外开销几乎为零。这就是时间轮能够同时实现 O(1) STARTTIMER、O(1) STOPTIMER、O(1) PERTICKBOOKKEEPING 的秘密。</p>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>George Varghese 和 Tony Lauck，《Hashed and Hierarchical Timing Wheels: Data Structures for the Efficient Implementation of a Timer Facility》，1996。论文链接：<a href="http://www.cs.columbia.edu/~nahum/w6998/papers/ton97-timing-wheels.pdf">Hashed and Hierarchical Timing Wheels</a>。 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:1:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:1:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:1:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:1:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a> <a href="#fnref:1:5" class="reversefootnote" role="doc-backlink">&#8617;<sup>6</sup></a> <a href="#fnref:1:6" class="reversefootnote" role="doc-backlink">&#8617;<sup>7</sup></a> <a href="#fnref:1:7" class="reversefootnote" role="doc-backlink">&#8617;<sup>8</sup></a> <a href="#fnref:1:8" class="reversefootnote" role="doc-backlink">&#8617;<sup>9</sup></a></p>
    </li>
    <li id="fn:2">
      <p>Linux 内核源码，timer wheel 实现，<a href="https://github.com/torvalds/linux/blob/master/kernel/time/timer.c"><code class="language-plaintext highlighter-rouge">kernel/time/timer.c</code></a>。低精度定时器使用 8-9 层、每层 64 bucket 的分层时间轮设计。 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">Wheel</code> 结构定义与文档，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/time/wheel/mod.rs"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/time/wheel/mod.rs</code></a>。Tokio 使用 6 层、每层 64 slot 的哈希时间轮，代码注释明确引用 [Varghese &amp; Lauck 1996]。 <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="rust" /><category term="tokio" /><summary type="html"><![CDATA[解读 Varghese 与 Lauck 1996 经典论文，从七种 Hashed and Hierarchical Timing Wheels 设计理解 O(1) 定时器。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://weinan.tech/images/og/tokio-async.png" /><media:content medium="image" url="https://weinan.tech/images/og/tokio-async.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">大模型工程化的自举迭代框架：从 Demo 到可持续生产系统的五层实践</title><link href="https://weinan.tech/2026/05/04/llm-engineering-bootstrapping-iteration-framework.html" rel="alternate" type="text/html" title="大模型工程化的自举迭代框架：从 Demo 到可持续生产系统的五层实践" /><published>2026-05-04T00:00:00+08:00</published><updated>2026-05-04T00:00:00+08:00</updated><id>https://weinan.tech/2026/05/04/llm-engineering-bootstrapping-iteration-framework</id><content type="html" xml:base="https://weinan.tech/2026/05/04/llm-engineering-bootstrapping-iteration-framework.html"><![CDATA[<style>
/* Mermaid diagram container */
.mermaid-container {
  position: relative;
  display: block;
  cursor: pointer;
  transition: opacity 0.2s;
  max-width: 100%;
  margin: 20px 0;
  overflow-x: auto;
}

.mermaid-container:hover {
  opacity: 0.8;
}

.mermaid-container svg {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Modal overlay */
.mermaid-modal {
  display: none;
  position: fixed;
  z-index: 9999;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9);
  animation: fadeIn 0.3s;
}

.mermaid-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Modal content */
.mermaid-modal-content {
  position: relative;
  width: 90vw;
  height: 90vh;
  overflow: hidden;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
}

.mermaid-modal-diagram {
  transform-origin: center center;
  transition: transform 0.2s ease;
  display: inline-block;
  min-width: 100%;
  cursor: grab;
  user-select: none;
}

.mermaid-modal-diagram.dragging {
  cursor: grabbing;
  transition: none;
}

.mermaid-modal-diagram svg {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;
}

/* Control buttons */
.mermaid-controls {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
  z-index: 10000;
}

.mermaid-btn {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
  color: #333;
  font-weight: 500;
}

.mermaid-btn:hover {
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Close button */
.mermaid-close {
  background: #f44336;
  color: white;
  border: none;
}

.mermaid-close:hover {
  background: #d32f2f;
}

/* Zoom indicator */
.mermaid-zoom-level {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 10000;
}
</style>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  mermaid.initialize({
    startOnLoad: false,
    theme: 'default',
    securityLevel: 'loose',
    htmlLabels: true,
    themeVariables: {
      fontSize: '14px'
    }
  });

  let currentZoom = 1;
  let currentModal = null;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let translateX = 0;
  let translateY = 0;

  // Create modal HTML
  function createModal() {
    const modal = document.createElement('div');
    modal.className = 'mermaid-modal';
    modal.innerHTML = `
      <div class="mermaid-controls">
        <button class="mermaid-btn zoom-in">放大 +</button>
        <button class="mermaid-btn zoom-out">缩小 -</button>
        <button class="mermaid-btn zoom-reset">重置</button>
        <button class="mermaid-btn mermaid-close">关闭 ✕</button>
      </div>
      <div class="mermaid-modal-content">
        <div class="mermaid-modal-diagram"></div>
      </div>
      <div class="mermaid-zoom-level">100%</div>
    `;
    document.body.appendChild(modal);
    return modal;
  }

  // Show modal with diagram
  function showModal(diagramContent) {
    if (!currentModal) {
      currentModal = createModal();
      setupModalEvents();
    }

    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.innerHTML = diagramContent;

    // Remove any width/height attributes from SVG to make it responsive
    const svg = modalDiagram.querySelector('svg');
    if (svg) {
      svg.removeAttribute('width');
      svg.removeAttribute('height');
      svg.style.width = '100%';
      svg.style.height = 'auto';
    }

    // Setup drag functionality
    setupDrag(modalDiagram);

    currentModal.classList.add('active');
    currentZoom = 1;
    resetPosition();
    updateZoom();
    document.body.style.overflow = 'hidden';
  }

  // Hide modal
  function hideModal() {
    if (currentModal) {
      currentModal.classList.remove('active');
      document.body.style.overflow = '';
    }
  }

  // Update zoom level
  function updateZoom() {
    if (!currentModal) return;
    const diagram = currentModal.querySelector('.mermaid-modal-diagram');
    const zoomLevel = currentModal.querySelector('.mermaid-zoom-level');
    diagram.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentZoom})`;
    zoomLevel.textContent = `${Math.round(currentZoom * 100)}%`;
  }

  // Reset position when zoom changes
  function resetPosition() {
    translateX = 0;
    translateY = 0;
  }

  // Setup drag functionality
  function setupDrag(element) {
    element.addEventListener('mousedown', startDrag);
    element.addEventListener('touchstart', startDrag);
  }

  function startDrag(e) {
    if (e.type === 'mousedown' && e.button !== 0) return; // Only left click

    isDragging = true;
    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.classList.add('dragging');

    if (e.type === 'touchstart') {
      startX = e.touches[0].clientX - translateX;
      startY = e.touches[0].clientY - translateY;
    } else {
      startX = e.clientX - translateX;
      startY = e.clientY - translateY;
    }

    document.addEventListener('mousemove', drag);
    document.addEventListener('touchmove', drag);
    document.addEventListener('mouseup', stopDrag);
    document.addEventListener('touchend', stopDrag);
  }

  function drag(e) {
    if (!isDragging) return;
    e.preventDefault();

    if (e.type === 'touchmove') {
      translateX = e.touches[0].clientX - startX;
      translateY = e.touches[0].clientY - startY;
    } else {
      translateX = e.clientX - startX;
      translateY = e.clientY - startY;
    }

    updateZoom();
  }

  function stopDrag() {
    isDragging = false;
    const modalDiagram = currentModal?.querySelector('.mermaid-modal-diagram');
    if (modalDiagram) {
      modalDiagram.classList.remove('dragging');
    }
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('touchmove', drag);
    document.removeEventListener('mouseup', stopDrag);
    document.removeEventListener('touchend', stopDrag);
  }

  // Setup modal event listeners
  function setupModalEvents() {
    if (!currentModal) return;

    // Close button
    currentModal.querySelector('.mermaid-close').addEventListener('click', hideModal);

    // Zoom buttons
    currentModal.querySelector('.zoom-in').addEventListener('click', () => {
      currentZoom = Math.min(currentZoom + 0.25, 3);
      updateZoom();
    });

    currentModal.querySelector('.zoom-out').addEventListener('click', () => {
      currentZoom = Math.max(currentZoom - 0.25, 0.5);
      updateZoom();
    });

    currentModal.querySelector('.zoom-reset').addEventListener('click', () => {
      currentZoom = 1;
      resetPosition();
      updateZoom();
    });

    // Close on background click
    currentModal.addEventListener('click', (e) => {
      if (e.target === currentModal) {
        hideModal();
      }
    });

    // Close on ESC key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && currentModal.classList.contains('active')) {
        hideModal();
      }
    });
  }

  // Convert Jekyll-rendered code blocks to mermaid divs
  document.addEventListener('DOMContentLoaded', async function() {
    const codeBlocks = document.querySelectorAll('code.language-mermaid');

    for (const codeBlock of codeBlocks) {
      const pre = codeBlock.parentElement;
      const container = document.createElement('div');
      container.className = 'mermaid-container';

      const mermaidDiv = document.createElement('div');
      mermaidDiv.className = 'mermaid';
      mermaidDiv.textContent = codeBlock.textContent;

      container.appendChild(mermaidDiv);
      pre.replaceWith(container);
    }

    // Render all mermaid diagrams
    try {
      await mermaid.run({
        querySelector: '.mermaid'
      });
      console.log('Mermaid diagrams rendered successfully');
    } catch (error) {
      console.error('Mermaid rendering error:', error);
    }

    // Add click handlers to rendered diagrams
    document.querySelectorAll('.mermaid-container').forEach((container, index) => {
      // Find the rendered SVG inside the container
      const svg = container.querySelector('svg');
      if (!svg) {
        console.warn(`No SVG found in container ${index}`);
        return;
      }

      // Make the container clickable
      container.style.cursor = 'pointer';
      container.title = '点击查看大图';

      container.addEventListener('click', function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Clone the SVG for the modal
        const svgClone = svg.cloneNode(true);
        const tempDiv = document.createElement('div');
        tempDiv.appendChild(svgClone);

        console.log('Opening modal for diagram', index);
        showModal(tempDiv.innerHTML);
      });

      console.log(`Click handler added to diagram ${index}`);
    });
  });
</script>

<blockquote>
  <p>大模型项目的最大陷阱不是模型不够强，而是做完 Demo 就停滞了。让一个 LLM 驱动的系统持续稳定运行，需要的不是更好的 prompt，而是一套”让模型能够优化自己工作过程”的工程体系。</p>
</blockquote>

<h2 id="引言demo-之后是什么">引言：Demo 之后是什么</h2>

<p>过去一年多，我用 AI 辅助编程从”新奇工具”逐步演变成了日常开发的核心环节<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>。在这个过程中反复出现一个规律：<strong>做一个能跑一次的 Demo 很简单，但让系统持续稳定运行几个月，难度是指数级上升的。</strong></p>

<p>原因不难理解。Demo 只需要 model + prompt 就能出效果，但生产系统面临的是完全不同的约束：</p>

<ul>
  <li>上下文爆炸：多轮对话越长，token 成本越高，压缩后又丢失关键逻辑</li>
  <li>流程不稳定：同样的 prompt 昨天还能用，今天就输出完全不同</li>
  <li>成本失控：没有反馈循环，低效的交互模式被无限重复</li>
  <li>Agent 迷失：长期运行的 Agent 在复杂任务链中丢失上下文和决策依据</li>
</ul>

<p>这套经验的本质，可以概括为一个闭环：</p>

<pre><code class="language-mermaid">graph LR
    A[手动验证] --&gt; B[固化脚本]
    B --&gt; C[定时触发 / CI]
    C --&gt; D[自动收集反馈]
    D --&gt; E[分析 Debug 信息]
    E --&gt; F[蒸馏 / 优化模型或 prompt]
    F --&gt; A

    style A fill:#90EE90
    style B fill:#87CEEB
    style C fill:#87CEEB
    style D fill:#FFD700
    style E fill:#FFD700
    style F fill:#FF6347
</code></pre>

<p><strong>让 LLM 不仅解决问题，还能持续优化解决过程本身</strong>——这就是”自举”的含义。</p>

<p>下面按成熟度从低到高，逐一拆解五层工程实践。</p>

<h2 id="一复杂流程固化不要让-llm-重复学习同一件事">一、复杂流程固化：不要让 LLM 重复学习同一件事</h2>

<p>最基础也最容易被忽略的一层。LLM 每次运行都是从零开始”思考”，但很多流程是稳定的、可重复的。反复让模型重新推导这些固定流程，既是浪费 token，也是引入不确定性。</p>

<p>核心原则：<strong>一旦某个流程跑通了三次以上，就把它从 prompt 里抽出来，固化成脚本或 API。</strong></p>

<table>
  <thead>
    <tr>
      <th>流程类型</th>
      <th>固化方式</th>
      <th>实例</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>稳定的 prompt 链</td>
      <td>Python 脚本</td>
      <td>自动加载 CLAUDE.md，注入项目上下文</td>
    </tr>
    <tr>
      <td>周期性测试流程</td>
      <td>Nightly 脚本</td>
      <td>夜间批量跑回归测试用例<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></td>
    </tr>
    <tr>
      <td>Agent 重启 / 恢复</td>
      <td>tmux + 脚本</td>
      <td>自动重启 <code class="language-plaintext highlighter-rouge">/loop</code> 循环</td>
    </tr>
    <tr>
      <td>外部调用</td>
      <td>FastAPI 封装</td>
      <td>将 Agent 能力暴露为 HTTP API</td>
    </tr>
  </tbody>
</table>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 示例：定时自动化脚本</span>
0 2 <span class="k">*</span> <span class="k">*</span> <span class="k">*</span> /path/to/nightly_test.sh       <span class="c"># 夜间测试</span>
<span class="k">*</span>/30 <span class="k">*</span> <span class="k">*</span> <span class="k">*</span> <span class="k">*</span> tmux send-keys <span class="nt">-t</span> claude <span class="s2">"load claude.md"</span> Enter  <span class="c"># 定时刷新上下文</span>
</code></pre></div></div>

<p>固化不仅仅是省 token。更重要的是，一旦流程变成脚本，它就进入了版本控制，可审计、可回归、可复用。这是从”人在驱动模型”到”系统在驱动模型”的第一步。</p>

<h2 id="二tmux-作为-agent-运行时核心">二、tmux 作为 Agent 运行时核心</h2>

<p>这是整套框架里最独特的一层。大多数 LLM 工具链关注的是”调用 API 一次拿结果”，但长期运行的 Agent 需要的是一个<strong>持久化的运行时环境</strong>。tmux 恰好提供了这个能力。</p>

<p>核心模式：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 创建持久化 Agent 会话</span>
tmux new <span class="nt">-s</span> claude_agent

<span class="c"># 发送 prompt 而不打断会话</span>
tmux send-keys <span class="nt">-t</span> claude_agent <span class="s2">"analyze the latest logs"</span> Enter

<span class="c"># 捕获输出到文件</span>
tmux capture-pane <span class="nt">-t</span> claude_agent <span class="nt">-p</span> <span class="o">&gt;</span> latest_output.txt
</code></pre></div></div>

<p>为什么用 tmux 而不是直接调用 API？三个原因：</p>

<ol>
  <li><strong>LLM 保持长期状态</strong>：上下文、记忆、思维宫殿不因单次调用结束而丢失</li>
  <li><strong>自动化脚本可以”悄悄观测”</strong>：通过 <code class="language-plaintext highlighter-rouge">capture-pane</code> 读取输出，而不干扰正在进行的推理</li>
  <li><strong>崩溃后可恢复</strong>：tmux session 独立于进程，即使客户端断开，Agent 仍在运行</li>
</ol>

<pre><code class="language-mermaid">sequenceDiagram
    participant Script as 定时脚本
    participant tmux as tmux Session
    participant Agent as Claude Agent
    participant FS as 文件系统

    Script-&gt;&gt;tmux: send-keys "nightly test"
    tmux-&gt;&gt;Agent: 传递指令
    Agent-&gt;&gt;Agent: 执行测试分析
    Script-&gt;&gt;tmux: capture-pane 捕获输出
    tmux--&gt;&gt;Script: 输出内容
    Script-&gt;&gt;FS: 保存 latest_output.txt
    Note over Script: 解析结果，决定下一步
</code></pre>

<p>tmux 在这里承担的角色，类似于容器运行时之于微服务——它提供进程隔离、生命周期管理和输入输出信道。这是把 LLM 当作”长期服务”而非”一次性函数调用”的关键基础设施。</p>

<h2 id="三compact-conversation-卡点的工程解法">三、Compact Conversation 卡点的工程解法</h2>

<p>上下文爆炸是长期运行 Agent 面临的最棘手的工程问题。典型症状：</p>

<ul>
  <li>多轮对话后 token 消耗指数增长</li>
  <li><code class="language-plaintext highlighter-rouge">/compact</code> 压缩后丢失关键决策逻辑</li>
  <li>Agent 在压缩后”失忆”，行为偏离原定方向</li>
</ul>

<p>解法是一个<strong>混合记忆策略</strong>，把不同生命周期的信息放在不同层级：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">HybridMemory</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">short_term</span> <span class="o">=</span> <span class="p">[]</span>           <span class="c1"># 当前对话窗口
</span>        <span class="bp">self</span><span class="p">.</span><span class="n">memory_palace</span> <span class="o">=</span> <span class="n">MemoryPalace</span><span class="p">()</span>  <span class="c1"># 结构化长时记忆
</span>        <span class="bp">self</span><span class="p">.</span><span class="n">claude_memory</span> <span class="o">=</span> <span class="n">ClaudeCodeMemory</span><span class="p">()</span>  <span class="c1"># 原生 memory 机制
</span>
    <span class="k">def</span> <span class="nf">compact</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="c1"># 在压缩前自动提取关键决策
</span>        <span class="n">decisions</span> <span class="o">=</span> <span class="n">extract_important_decisions</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">short_term</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">memory_palace</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">decisions</span><span class="p">)</span>
        <span class="c1"># 再调用原生压缩
</span>        <span class="bp">self</span><span class="p">.</span><span class="n">short_term</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">claude_memory</span><span class="p">.</span><span class="n">compress</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">short_term</span><span class="p">)</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>卡点症状</th>
      <th>方案</th>
      <th>工程实现</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>对话过长</td>
      <td>思维宫殿</td>
      <td>结构化记忆：<code class="language-plaintext highlighter-rouge">summary/entities/relations</code> 保存到 JSON</td>
    </tr>
    <tr>
      <td>压缩丢关键信息</td>
      <td>定时脚本固化</td>
      <td>每次 compact 前自动提取 debug 信息</td>
    </tr>
    <tr>
      <td>Agent 迷失</td>
      <td>Memory 机制</td>
      <td>Claude Code 内置 memory + 外部结构化存储</td>
    </tr>
  </tbody>
</table>

<p>关键洞察：<strong>compact 不是纯压缩，而是一次”蒸馏”——把对话里的噪声压掉，把决策留下来。</strong> 这要求在压缩动作前后各加一层逻辑：压缩前提取，压缩后注入核心上下文。</p>

<h2 id="四埋点驱动的迭代构建数据飞轮">四、埋点驱动的迭代：构建数据飞轮</h2>

<p>模型本身不会告诉你它哪里做得不好。要让系统持续改进，需要在生产代码里埋入多层 debug 信息，形成反馈闭环。</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">Level 1 — 输入输出</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">raw_prompt</span>
  <span class="pi">-</span> <span class="s">llm_response</span>
  <span class="pi">-</span> <span class="s">latency / token_usage</span>

<span class="na">Level 2 — 决策路径</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">which_functions_called</span>
  <span class="pi">-</span> <span class="s">compact_triggered_at</span>
  <span class="pi">-</span> <span class="s">retry_sequence</span>

<span class="na">Level 3 — 业务结果</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">task_success (bool)</span>
  <span class="pi">-</span> <span class="s">retry_count</span>
  <span class="pi">-</span> <span class="s">hallucination_flag</span>
</code></pre></div></div>

<p>三层埋点的设计遵循从”发生了什么”到”为什么发生”的递进：</p>

<pre><code class="language-mermaid">graph TD
    A["Nightly 测试失败"] --&gt; B["查 L1 日志：输入输出"]
    B --&gt; C["查 L2 日志：决策路径"]
    C --&gt; D["定位某类错误高频"]
    D --&gt; E["蒸馏正确 case"]
    E --&gt; F["优化 prompt / 微调模型"]
    F --&gt; G["下一轮 Nightly 覆盖"]

    style A fill:#FF6347
    style D fill:#FFD700
    style G fill:#90EE90
</code></pre>

<p>这套循环已经在实践中验证过有效。例如在做 Java task_server 到 Rust 的迁移项目中<sup id="fnref:2:1"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>，Nightly CI 测试失败 → 查 debug 日志 → 发现某类边界条件高频出错 → 把正确处理方法蒸馏到 prompt 模板 → 下一轮覆盖率提升。没有埋点，这个循环就断了。</p>

<h2 id="五用强类型驯服-llm-的不确定性">五、用强类型驯服 LLM 的不确定性</h2>

<p>LLM 输出天然 unstructured，但生产系统需要 structured。这两者之间的张力，是所有 LLM 工程化项目的终局问题。</p>

<p>Python 生态里常见的写法是：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="s">"action"</span> <span class="ow">in</span> <span class="n">response</span><span class="p">:</span>
    <span class="n">do_something</span><span class="p">()</span>
<span class="k">elif</span> <span class="n">response</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"result"</span><span class="p">):</span>
    <span class="p">...</span>
</code></pre></div></div>

<p>这种防御式编程在面对 LLM 的输出多样性时非常脆弱——模型换一个表达方式，if-else 就漏过去了。</p>

<p>Rust 的强类型系统提供了一种更可靠的方案：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 强类型约束 LLM 输出结构</span>
<span class="nd">#[derive(Deserialize)]</span>
<span class="k">struct</span> <span class="n">LLMAction</span> <span class="p">{</span>
    <span class="n">action_type</span><span class="p">:</span> <span class="n">ActionType</span><span class="p">,</span>      <span class="c1">// 枚举，非法值直接反序列化失败</span>
    <span class="n">parameters</span><span class="p">:</span> <span class="n">ValidatedParams</span><span class="p">,</span>  <span class="c1">// Option 强制处理缺失</span>
<span class="p">}</span>

<span class="c1">// 而不是散落各处的 `if "action" in response`</span>
</code></pre></div></div>

<p>强类型在这里的价值不是性能，而是<strong>把校验从运行时提到编译期</strong>：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">ActionType</code> 枚举限定了合法动作空间，模型输出不在集合内 → 直接拒绝，不会悄悄执行错误逻辑</li>
  <li><code class="language-plaintext highlighter-rouge">Option&lt;T&gt;</code> 强制处理缺失字段，不会出现 <code class="language-plaintext highlighter-rouge">KeyError</code> 或 <code class="language-plaintext highlighter-rouge">NoneType</code> 的运行时崩溃</li>
  <li>Schema 本身是可版本化的文档，团队对 LLM 输出的结构约定一目了然</li>
</ul>

<p>具体应用场景包括：</p>
<ul>
  <li>API 调用参数的 schema 校验</li>
  <li>Agent 之间的消息协议（防止幻觉渗透到下游）</li>
  <li>部署脚本的类型安全（避免”模型乱编文件路径”）</li>
</ul>

<h2 id="工程化成熟度总结">工程化成熟度总结</h2>

<table>
  <thead>
    <tr>
      <th>实践</th>
      <th>解决的问题</th>
      <th>成熟度</th>
      <th>复用性</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>流程固化（脚本/API）</td>
      <td>避免 LLM 重复学习</td>
      <td>⭐ 基础</td>
      <td>极高</td>
    </tr>
    <tr>
      <td>tmux 运行时</td>
      <td>Agent 持久化的工程基础</td>
      <td>⭐⭐ 进阶</td>
      <td>高（独特点）</td>
    </tr>
    <tr>
      <td>Compact 卡点解法</td>
      <td>控制 token 成本 + 保留关键信息</td>
      <td>⭐⭐⭐ 高阶</td>
      <td>高</td>
    </tr>
    <tr>
      <td>埋点驱动迭代</td>
      <td>形成数据飞轮，持续改进</td>
      <td>⭐⭐⭐ 高阶</td>
      <td>极高</td>
    </tr>
    <tr>
      <td>强类型驯服不确定性</td>
      <td>对抗 LLM 输出不可靠</td>
      <td>⭐⭐⭐⭐ 前瞻</td>
      <td>中高</td>
    </tr>
  </tbody>
</table>

<h2 id="自举的本质">自举的本质</h2>

<p>回到最核心的那个闭环图。五层实践的共性不是”让模型更强”，而是<strong>把人的工程判断逐步沉淀为系统能力</strong>：</p>

<ol>
  <li>手动跑通 → 写成脚本（自动化）</li>
  <li>脚本稳定 → 接入 CI（持续化）</li>
  <li>CI 出问题 → 埋点收集数据（可观测）</li>
  <li>数据积累 → 分析 pattern → 优化 prompt 或蒸馏模型（自优化）</li>
</ol>

<p>每一步都是在把”人驱动模型”的占比降低一点，把”系统驱动模型”的占比提高一点。这就是自举的含义——<strong>不是一次性配置好就完事，而是构建一套能持续自我改进的机制。</strong></p>

<p>这和软件工程的经典原则并无不同：自动化、可观测、持续集成、反馈闭环。只是这一次，优化的对象不是代码，而是”模型理解代码、生成代码、维护代码”的过程本身。</p>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>之前的文章：《One Year of AI-Assisted Programming: Insights, Practices, and Reflections》，2026-01-31。总结了从 AI 工具使用者到将其深度融入日常开发工作流的经验。 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>之前的文章：《从 Java task_server 到 Rust（htyts / htyproc）：用 AI 推进迁移，用 GitHub CI 与基础设施兜住 E2E》，2026-03-22。用 AI 驱动的跨语言迁移实践，包含 Nightly CI、Docker Compose 联调、AuthCore 集成等工程化细节。 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="ai" /><summary type="html"><![CDATA[提出大模型工程化自举迭代框架，从 Demo 到可持续生产系统分五层落地实践。]]></summary></entry><entry><title type="html">从 tokio::time::sleep 看异步 Timer 的实现：一次从 Future::poll 到哈希时间轮的源码之旅</title><link href="https://weinan.tech/2026/05/03/tokio-sleep-implementation-hashed-timer-wheel.html" rel="alternate" type="text/html" title="从 tokio::time::sleep 看异步 Timer 的实现：一次从 Future::poll 到哈希时间轮的源码之旅" /><published>2026-05-03T00:00:00+08:00</published><updated>2026-05-03T00:00:00+08:00</updated><id>https://weinan.tech/2026/05/03/tokio-sleep-implementation-hashed-timer-wheel</id><content type="html" xml:base="https://weinan.tech/2026/05/03/tokio-sleep-implementation-hashed-timer-wheel.html"><![CDATA[<style>
/* Mermaid diagram container */
.mermaid-container {
  position: relative;
  display: block;
  cursor: pointer;
  transition: opacity 0.2s;
  max-width: 100%;
  margin: 20px 0;
  overflow-x: auto;
}

.mermaid-container:hover {
  opacity: 0.8;
}

.mermaid-container svg {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Modal overlay */
.mermaid-modal {
  display: none;
  position: fixed;
  z-index: 9999;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9);
  animation: fadeIn 0.3s;
}

.mermaid-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Modal content */
.mermaid-modal-content {
  position: relative;
  width: 90vw;
  height: 90vh;
  overflow: hidden;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
}

.mermaid-modal-diagram {
  transform-origin: center center;
  transition: transform 0.2s ease;
  display: inline-block;
  min-width: 100%;
  cursor: grab;
  user-select: none;
}

.mermaid-modal-diagram.dragging {
  cursor: grabbing;
  transition: none;
}

.mermaid-modal-diagram svg {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;
}

/* Control buttons */
.mermaid-controls {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
  z-index: 10000;
}

.mermaid-btn {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
  color: #333;
  font-weight: 500;
}

.mermaid-btn:hover {
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Close button */
.mermaid-close {
  background: #f44336;
  color: white;
  border: none;
}

.mermaid-close:hover {
  background: #d32f2f;
}

/* Zoom indicator */
.mermaid-zoom-level {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 10000;
}
</style>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  mermaid.initialize({
    startOnLoad: false,
    theme: 'default',
    securityLevel: 'loose',
    htmlLabels: true,
    themeVariables: {
      fontSize: '14px'
    }
  });

  let currentZoom = 1;
  let currentModal = null;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let translateX = 0;
  let translateY = 0;

  // Create modal HTML
  function createModal() {
    const modal = document.createElement('div');
    modal.className = 'mermaid-modal';
    modal.innerHTML = `
      <div class="mermaid-controls">
        <button class="mermaid-btn zoom-in">放大 +</button>
        <button class="mermaid-btn zoom-out">缩小 -</button>
        <button class="mermaid-btn zoom-reset">重置</button>
        <button class="mermaid-btn mermaid-close">关闭 ✕</button>
      </div>
      <div class="mermaid-modal-content">
        <div class="mermaid-modal-diagram"></div>
      </div>
      <div class="mermaid-zoom-level">100%</div>
    `;
    document.body.appendChild(modal);
    return modal;
  }

  // Show modal with diagram
  function showModal(diagramContent) {
    if (!currentModal) {
      currentModal = createModal();
      setupModalEvents();
    }

    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.innerHTML = diagramContent;

    // Remove any width/height attributes from SVG to make it responsive
    const svg = modalDiagram.querySelector('svg');
    if (svg) {
      svg.removeAttribute('width');
      svg.removeAttribute('height');
      svg.style.width = '100%';
      svg.style.height = 'auto';
    }

    // Setup drag functionality
    setupDrag(modalDiagram);

    currentModal.classList.add('active');
    currentZoom = 1;
    resetPosition();
    updateZoom();
    document.body.style.overflow = 'hidden';
  }

  // Hide modal
  function hideModal() {
    if (currentModal) {
      currentModal.classList.remove('active');
      document.body.style.overflow = '';
    }
  }

  // Update zoom level
  function updateZoom() {
    if (!currentModal) return;
    const diagram = currentModal.querySelector('.mermaid-modal-diagram');
    const zoomLevel = currentModal.querySelector('.mermaid-zoom-level');
    diagram.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentZoom})`;
    zoomLevel.textContent = `${Math.round(currentZoom * 100)}%`;
  }

  // Reset position when zoom changes
  function resetPosition() {
    translateX = 0;
    translateY = 0;
  }

  // Setup drag functionality
  function setupDrag(element) {
    element.addEventListener('mousedown', startDrag);
    element.addEventListener('touchstart', startDrag);
  }

  function startDrag(e) {
    if (e.type === 'mousedown' && e.button !== 0) return; // Only left click

    isDragging = true;
    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.classList.add('dragging');

    if (e.type === 'touchstart') {
      startX = e.touches[0].clientX - translateX;
      startY = e.touches[0].clientY - translateY;
    } else {
      startX = e.clientX - translateX;
      startY = e.clientY - translateY;
    }

    document.addEventListener('mousemove', drag);
    document.addEventListener('touchmove', drag);
    document.addEventListener('mouseup', stopDrag);
    document.addEventListener('touchend', stopDrag);
  }

  function drag(e) {
    if (!isDragging) return;
    e.preventDefault();

    if (e.type === 'touchmove') {
      translateX = e.touches[0].clientX - startX;
      translateY = e.touches[0].clientY - startY;
    } else {
      translateX = e.clientX - startX;
      translateY = e.clientY - startY;
    }

    updateZoom();
  }

  function stopDrag() {
    isDragging = false;
    const modalDiagram = currentModal?.querySelector('.mermaid-modal-diagram');
    if (modalDiagram) {
      modalDiagram.classList.remove('dragging');
    }
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('touchmove', drag);
    document.removeEventListener('mouseup', stopDrag);
    document.removeEventListener('touchend', stopDrag);
  }

  // Setup modal event listeners
  function setupModalEvents() {
    if (!currentModal) return;

    // Close button
    currentModal.querySelector('.mermaid-close').addEventListener('click', hideModal);

    // Zoom buttons
    currentModal.querySelector('.zoom-in').addEventListener('click', () => {
      currentZoom = Math.min(currentZoom + 0.25, 3);
      updateZoom();
    });

    currentModal.querySelector('.zoom-out').addEventListener('click', () => {
      currentZoom = Math.max(currentZoom - 0.25, 0.5);
      updateZoom();
    });

    currentModal.querySelector('.zoom-reset').addEventListener('click', () => {
      currentZoom = 1;
      resetPosition();
      updateZoom();
    });

    // Close on background click
    currentModal.addEventListener('click', (e) => {
      if (e.target === currentModal) {
        hideModal();
      }
    });

    // Close on ESC key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && currentModal.classList.contains('active')) {
        hideModal();
      }
    });
  }

  // Convert Jekyll-rendered code blocks to mermaid divs
  document.addEventListener('DOMContentLoaded', async function() {
    const codeBlocks = document.querySelectorAll('code.language-mermaid');

    for (const codeBlock of codeBlocks) {
      const pre = codeBlock.parentElement;
      const container = document.createElement('div');
      container.className = 'mermaid-container';

      const mermaidDiv = document.createElement('div');
      mermaidDiv.className = 'mermaid';
      mermaidDiv.textContent = codeBlock.textContent;

      container.appendChild(mermaidDiv);
      pre.replaceWith(container);
    }

    // Render all mermaid diagrams
    try {
      await mermaid.run({
        querySelector: '.mermaid'
      });
      console.log('Mermaid diagrams rendered successfully');
    } catch (error) {
      console.error('Mermaid rendering error:', error);
    }

    // Add click handlers to rendered diagrams
    document.querySelectorAll('.mermaid-container').forEach((container, index) => {
      // Find the rendered SVG inside the container
      const svg = container.querySelector('svg');
      if (!svg) {
        console.warn(`No SVG found in container ${index}`);
        return;
      }

      // Make the container clickable
      container.style.cursor = 'pointer';
      container.title = '点击查看大图';

      container.addEventListener('click', function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Clone the SVG for the modal
        const svgClone = svg.cloneNode(true);
        const tempDiv = document.createElement('div');
        tempDiv.appendChild(svgClone);

        console.log('Opening modal for diagram', index);
        showModal(tempDiv.innerHTML);
      });

      console.log(`Click handler added to diagram ${index}`);
    });
  });
</script>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">tokio::time::sleep</code> 看起来和 <code class="language-plaintext highlighter-rouge">thread::sleep</code> 只差一个 <code class="language-plaintext highlighter-rouge">.await</code>，但背后连接的是一整套哈希时间轮、原子状态机和操作系统的定时唤醒机制。这篇文章从源码出发，把这条链路拆开来看。</p>
</blockquote>

<h2 id="引言两个-sleep-的对比">引言：两个 sleep 的对比</h2>

<p>在 Rust 里让程序”等一下”有两种常见方式：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 方式一：标准库的阻塞 sleep</span>
<span class="nn">std</span><span class="p">::</span><span class="nn">thread</span><span class="p">::</span><span class="nf">sleep</span><span class="p">(</span><span class="nn">Duration</span><span class="p">::</span><span class="nf">from_secs</span><span class="p">(</span><span class="mi">5</span><span class="p">));</span>

<span class="c1">// 方式二：Tokio 的异步 sleep</span>
<span class="nn">tokio</span><span class="p">::</span><span class="nn">time</span><span class="p">::</span><span class="nf">sleep</span><span class="p">(</span><span class="nn">Duration</span><span class="p">::</span><span class="nf">from_secs</span><span class="p">(</span><span class="mi">5</span><span class="p">))</span><span class="k">.await</span><span class="p">;</span>
</code></pre></div></div>

<p>从表面看，两者似乎差不多——都是在某个地方停了 5 秒。但它们在运行时的行为有本质区别。用一个简单的对比实验就能看出来。</p>

<p>如果把下面三个场景跑一遍：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">场景</th>
      <th style="text-align: left">任务1</th>
      <th style="text-align: left">任务2</th>
      <th style="text-align: left">总耗时</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">原生线程（2 个线程）</td>
      <td style="text-align: left">同步 sleep 5s</td>
      <td style="text-align: left">同步 sleep 2s</td>
      <td style="text-align: left"><strong>5s</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">Tokio 单线程 + 同步阻塞</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">thread::sleep</code> 5s</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">tokio::time::sleep</code> 2s</td>
      <td style="text-align: left"><strong>7s</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">Tokio 单线程 + 全异步</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">tokio::time::sleep</code> 5s</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">tokio::time::sleep</code> 2s</td>
      <td style="text-align: left"><strong>5s</strong></td>
    </tr>
  </tbody>
</table>

<p>先看场景一——在 async 任务里混入同步阻塞的版本：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio_block_bad.rs</span>
<span class="k">use</span> <span class="nn">tokio</span><span class="p">::</span><span class="n">time</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">time</span><span class="p">::</span><span class="n">Duration</span><span class="p">;</span>

<span class="nd">#[tokio::main(flavor</span> <span class="nd">=</span> <span class="s">"current_thread"</span><span class="nd">)]</span>
<span class="k">async</span> <span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">start</span> <span class="o">=</span> <span class="nn">std</span><span class="p">::</span><span class="nn">time</span><span class="p">::</span><span class="nn">Instant</span><span class="p">::</span><span class="nf">now</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">task1</span> <span class="o">=</span> <span class="nn">tokio</span><span class="p">::</span><span class="nf">spawn</span><span class="p">(</span><span class="k">async</span> <span class="k">move</span> <span class="p">{</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"[任务1] 开始阻塞, 时间: {:?}"</span><span class="p">,</span> <span class="n">start</span><span class="nf">.elapsed</span><span class="p">());</span>
        <span class="nn">std</span><span class="p">::</span><span class="nn">thread</span><span class="p">::</span><span class="nf">sleep</span><span class="p">(</span><span class="nn">Duration</span><span class="p">::</span><span class="nf">from_secs</span><span class="p">(</span><span class="mi">5</span><span class="p">));</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"[任务1] 结束阻塞, 时间: {:?}"</span><span class="p">,</span> <span class="n">start</span><span class="nf">.elapsed</span><span class="p">());</span>
    <span class="p">});</span>

    <span class="k">let</span> <span class="n">task2</span> <span class="o">=</span> <span class="nn">tokio</span><span class="p">::</span><span class="nf">spawn</span><span class="p">(</span><span class="k">async</span> <span class="k">move</span> <span class="p">{</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"[任务2] 开始异步等待, 时间: {:?}"</span><span class="p">,</span> <span class="n">start</span><span class="nf">.elapsed</span><span class="p">());</span>
        <span class="nn">time</span><span class="p">::</span><span class="nf">sleep</span><span class="p">(</span><span class="nn">Duration</span><span class="p">::</span><span class="nf">from_secs</span><span class="p">(</span><span class="mi">2</span><span class="p">))</span><span class="k">.await</span><span class="p">;</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"[任务2] 结束异步等待, 时间: {:?}"</span><span class="p">,</span> <span class="n">start</span><span class="nf">.elapsed</span><span class="p">());</span>
    <span class="p">});</span>

    <span class="k">let</span> <span class="n">_</span> <span class="o">=</span> <span class="nn">tokio</span><span class="p">::</span><span class="nd">join!</span><span class="p">(</span><span class="n">task1</span><span class="p">,</span> <span class="n">task2</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>运行结果：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[任务1] 开始阻塞, 时间: 32.1µs
... 卡住 5 秒，task2 根本没机会开始 ...
[任务1] 结束阻塞, 时间: 5.001s
[任务2] 开始异步等待, 时间: 5.001s
[任务2] 结束异步等待, 时间: 7.002s
</code></pre></div></div>

<p>task2 被 task1 的同步阻塞卡了整整 5 秒，总耗时 7 秒。</p>

<p>再看场景二——两个任务都用异步 sleep：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio_async_good.rs</span>
<span class="k">use</span> <span class="nn">tokio</span><span class="p">::</span><span class="n">time</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">time</span><span class="p">::</span><span class="n">Duration</span><span class="p">;</span>

<span class="nd">#[tokio::main(flavor</span> <span class="nd">=</span> <span class="s">"current_thread"</span><span class="nd">)]</span>
<span class="k">async</span> <span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">start</span> <span class="o">=</span> <span class="nn">std</span><span class="p">::</span><span class="nn">time</span><span class="p">::</span><span class="nn">Instant</span><span class="p">::</span><span class="nf">now</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">task1</span> <span class="o">=</span> <span class="nn">tokio</span><span class="p">::</span><span class="nf">spawn</span><span class="p">(</span><span class="k">async</span> <span class="k">move</span> <span class="p">{</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"[任务1] 开始异步等待5秒, 时间: {:?}"</span><span class="p">,</span> <span class="n">start</span><span class="nf">.elapsed</span><span class="p">());</span>
        <span class="nn">time</span><span class="p">::</span><span class="nf">sleep</span><span class="p">(</span><span class="nn">Duration</span><span class="p">::</span><span class="nf">from_secs</span><span class="p">(</span><span class="mi">5</span><span class="p">))</span><span class="k">.await</span><span class="p">;</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"[任务1] 结束异步等待, 时间: {:?}"</span><span class="p">,</span> <span class="n">start</span><span class="nf">.elapsed</span><span class="p">());</span>
    <span class="p">});</span>

    <span class="k">let</span> <span class="n">task2</span> <span class="o">=</span> <span class="nn">tokio</span><span class="p">::</span><span class="nf">spawn</span><span class="p">(</span><span class="k">async</span> <span class="k">move</span> <span class="p">{</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"[任务2] 开始异步等待2秒, 时间: {:?}"</span><span class="p">,</span> <span class="n">start</span><span class="nf">.elapsed</span><span class="p">());</span>
        <span class="nn">time</span><span class="p">::</span><span class="nf">sleep</span><span class="p">(</span><span class="nn">Duration</span><span class="p">::</span><span class="nf">from_secs</span><span class="p">(</span><span class="mi">2</span><span class="p">))</span><span class="k">.await</span><span class="p">;</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"[任务2] 结束异步等待, 时间: {:?}"</span><span class="p">,</span> <span class="n">start</span><span class="nf">.elapsed</span><span class="p">());</span>
    <span class="p">});</span>

    <span class="k">let</span> <span class="n">_</span> <span class="o">=</span> <span class="nn">tokio</span><span class="p">::</span><span class="nd">join!</span><span class="p">(</span><span class="n">task1</span><span class="p">,</span> <span class="n">task2</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>运行结果：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[任务1] 开始异步等待5秒, 时间: 41.5µs
[任务2] 开始异步等待2秒, 时间: 43.2µs
[任务2] 结束异步等待, 时间: 2.001s
[任务1] 结束异步等待, 时间: 5.002s
</code></pre></div></div>

<p>总耗时 5 秒，两个任务在单线程上并发执行，互不阻塞。</p>

<p>场景一的 7 秒暴露了问题：当 task1 在 Tokio 的单线程 runtime 里直接调用 <code class="language-plaintext highlighter-rouge">thread::sleep</code>，整个工作线程都被阻塞，task2 根本得不到执行机会。而场景二的两个异步 sleep 却在单线程上实现了并发——task2 在 2 秒后准时完成。</p>

<p>关键不在于”等待”本身，而在于<strong>用什么方式等待</strong>。<code class="language-plaintext highlighter-rouge">thread::sleep</code> 把线程卡住；<code class="language-plaintext highlighter-rouge">tokio::time::sleep</code> 只让任务挂起，线程依然可以服务其他任务。</p>

<p>这篇文章的目的就是拆开 <code class="language-plaintext highlighter-rouge">tokio::time::sleep</code> 的内部实现，看看它到底是怎么做到的。</p>

<p>在进入细节之前，可以先回顾一下异步系统的基础契约。在之前的文章《Rust async/await 的底层契约：从 Future::poll 到 Tokio 运行时》<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>中，我们梳理了 <code class="language-plaintext highlighter-rouge">Future::poll</code>、<code class="language-plaintext highlighter-rouge">Context</code>、<code class="language-plaintext highlighter-rouge">Waker</code> 组成的核心协议：任务被 <code class="language-plaintext highlighter-rouge">poll</code>，如果未就绪就返回 <code class="language-plaintext highlighter-rouge">Poll::Pending</code> 并保存 <code class="language-plaintext highlighter-rouge">Waker</code>，事件就绪后通过 <code class="language-plaintext highlighter-rouge">wake()</code> 通知运行时重新调度。<code class="language-plaintext highlighter-rouge">sleep</code> 正是这套协议的一个具体应用——它用”时间到达”作为就绪条件。</p>

<h3 id="概念先行从伪代码理解核心思路">概念先行：从伪代码理解核心思路</h3>

<p>在深入源码之前，先建立一个直觉。<code class="language-plaintext highlighter-rouge">tokio::time::sleep</code> 的秘密可以用一句话概括：<strong>它不会让线程去空等，而是注册一个”闹钟”后立刻把线程还给调度器，到时间了再由闹钟叫醒它。</strong></p>

<p>具体来说，下面这行代码实际触发了 6 个步骤：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">tokio</span><span class="p">::</span><span class="nn">time</span><span class="p">::</span><span class="nf">sleep</span><span class="p">(</span><span class="nn">Duration</span><span class="p">::</span><span class="nf">from_secs</span><span class="p">(</span><span class="mi">5</span><span class="p">))</span><span class="k">.await</span><span class="p">;</span>
</code></pre></div></div>

<pre><code class="language-mermaid">sequenceDiagram
    participant Task as 你的任务
    participant Exec as Tokio 调度器
    participant Timer as Tokio 定时器(全局)

    Task-&gt;&gt;Timer: 1. 注册："5秒后唤醒我"
    Task-&gt;&gt;Exec: 2. 返回 Poll::Pending
    Exec-&gt;&gt;Exec: 3. 切换去执行其他任务
    Note over Timer: 时钟滴答滴答...
    Timer-&gt;&gt;Task: 4. 5秒到了，调用 Waker.wake()
    Task-&gt;&gt;Exec: 5. 重新排队等待执行
    Exec-&gt;&gt;Task: 6. 再次 poll，返回 Poll::Ready
</code></pre>

<p>如果把这个过程写成极简的伪代码，大致是这样的：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 简化的 Sleep Future（概念示意）</span>
<span class="k">struct</span> <span class="n">Sleep</span> <span class="p">{</span>
    <span class="n">deadline</span><span class="p">:</span> <span class="n">Instant</span><span class="p">,</span>
    <span class="n">registered</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Future</span> <span class="k">for</span> <span class="n">Sleep</span> <span class="p">{</span>
    <span class="k">type</span> <span class="n">Output</span> <span class="o">=</span> <span class="p">();</span>

    <span class="k">fn</span> <span class="nf">poll</span><span class="p">(</span><span class="k">mut</span> <span class="k">self</span><span class="p">:</span> <span class="nb">Pin</span><span class="o">&lt;&amp;</span><span class="k">mut</span> <span class="k">Self</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">cx</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Context</span><span class="o">&lt;</span><span class="nv">'_</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Poll</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="nn">Instant</span><span class="p">::</span><span class="nf">now</span><span class="p">()</span> <span class="o">&gt;=</span> <span class="k">self</span><span class="py">.deadline</span> <span class="p">{</span>
            <span class="c1">// 时间到了，完成</span>
            <span class="k">return</span> <span class="nn">Poll</span><span class="p">::</span><span class="nf">Ready</span><span class="p">(());</span>
        <span class="p">}</span>

        <span class="k">if</span> <span class="o">!</span><span class="k">self</span><span class="py">.registered</span> <span class="p">{</span>
            <span class="c1">// 时间没到：把 waker 注册到全局定时器</span>
            <span class="c1">// 定时器会在 deadline 时调用 waker.wake()</span>
            <span class="k">let</span> <span class="n">waker</span> <span class="o">=</span> <span class="n">cx</span><span class="nf">.waker</span><span class="p">()</span><span class="nf">.clone</span><span class="p">();</span>
            <span class="n">TOKIO_TIMER</span><span class="nf">.insert</span><span class="p">(</span><span class="k">self</span><span class="py">.deadline</span><span class="p">,</span> <span class="n">waker</span><span class="p">);</span>
            <span class="k">self</span><span class="py">.registered</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// 关键：返回 Pending，交出执行权</span>
        <span class="nn">Poll</span><span class="p">::</span><span class="n">Pending</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>核心行为只有三个：</p>

<ol>
  <li><strong>首次 poll</strong>：发现时间没到 → 把 <code class="language-plaintext highlighter-rouge">Waker</code>（闹钟）注册到全局定时器 → 返回 <code class="language-plaintext highlighter-rouge">Pending</code>，线程继续服务其他任务</li>
  <li><strong>等待期间</strong>：零轮询、零浪费——不需要每秒检查 1000 次”时间到了吗”，定时器在精确时间点触发唤醒</li>
  <li><strong>定时器触发</strong>：<code class="language-plaintext highlighter-rouge">Waker::wake()</code> 通知调度器把任务放回就绪队列，下次 poll 时返回 <code class="language-plaintext highlighter-rouge">Ready</code></li>
</ol>

<p>这和张嘴就来的 <code class="language-plaintext highlighter-rouge">thread::sleep</code> 有着本质区别：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">方面</th>
      <th style="text-align: left"><code class="language-plaintext highlighter-rouge">thread::sleep</code></th>
      <th style="text-align: left"><code class="language-plaintext highlighter-rouge">tokio::time::sleep</code></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>线程状态</strong></td>
      <td style="text-align: left">OS 挂起线程</td>
      <td style="text-align: left">线程继续执行其他任务</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>CPU 利用</strong></td>
      <td style="text-align: left">0%（但线程资源被占）</td>
      <td style="text-align: left">100%（有效利用）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>可并发量</strong></td>
      <td style="text-align: left">≈ 线程数（几千上限）</td>
      <td style="text-align: left">百万级任务</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>响应方式</strong></td>
      <td style="text-align: left">时间到自动唤醒</td>
      <td style="text-align: left">通过 <code class="language-plaintext highlighter-rouge">Waker</code> 通知调度器</td>
    </tr>
  </tbody>
</table>

<p>有了这个心理模型，接下来就可以拆开源代码，看 Tokio 怎么把伪代码里的 <code class="language-plaintext highlighter-rouge">TOKIO_TIMER</code> 变成真正的数据结构。</p>

<h2 id="一sleep-的结构一个-future-的解剖">一、Sleep 的结构：一个 Future 的解剖</h2>

<p><code class="language-plaintext highlighter-rouge">tokio::time::sleep(duration)</code> 的签名返回一个 <code class="language-plaintext highlighter-rouge">Sleep</code> 结构体：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">sleep</span><span class="p">(</span><span class="n">duration</span><span class="p">:</span> <span class="n">Duration</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Sleep</span> <span class="p">{</span>
    <span class="c1">// 计算 deadline = now + duration</span>
    <span class="c1">// 然后调用 Sleep::new_timeout(deadline, location)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>它实现了 <code class="language-plaintext highlighter-rouge">Future</code> trait，所以可以 <code class="language-plaintext highlighter-rouge">.await</code>。但 <code class="language-plaintext highlighter-rouge">Sleep</code> 里面到底放了什么？核心字段如下<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/time/sleep.rs: 220-232</span>
<span class="nd">pin_project!</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">struct</span> <span class="n">Sleep</span> <span class="p">{</span>
        <span class="c1">// 内部字段（tracing 相关，非关键路径）</span>
        <span class="n">inner</span><span class="p">:</span> <span class="n">Inner</span><span class="p">,</span>

        <span class="c1">// 连接 Sleep 实例和驱动它的 Timer 的关键纽带</span>
        <span class="nd">#[pin]</span>
        <span class="n">entry</span><span class="p">:</span> <span class="n">Timer</span><span class="p">,</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">entry</code> 字段的类型是 <code class="language-plaintext highlighter-rouge">Timer</code>，它在 <code class="language-plaintext highlighter-rouge">tokio::runtime</code> 内部被定义为一个 enum<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/mod.rs: 437-442</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">enum</span> <span class="n">Timer</span> <span class="p">{</span>
    <span class="nf">Traditional</span><span class="p">(</span><span class="nn">time</span><span class="p">::</span><span class="n">TimerEntry</span><span class="p">),</span>
    <span class="nf">Alternative</span><span class="p">(</span><span class="nn">time_alt</span><span class="p">::</span><span class="n">Timer</span><span class="p">),</span>  <span class="c1">// 仅 tokio_unstable + rt-multi-thread</span>
<span class="p">}</span>
</code></pre></div></div>

<p>本文聚焦在 <code class="language-plaintext highlighter-rouge">Traditional</code> 分支——也就是默认的 Timer 实现。<code class="language-plaintext highlighter-rouge">TimerEntry</code> 的结构如下<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/entry.rs: 287-310</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">TimerEntry</span> <span class="p">{</span>
    <span class="c1">// Arc 引用到 runtime 的调度器句柄</span>
    <span class="n">driver</span><span class="p">:</span> <span class="nn">scheduler</span><span class="p">::</span><span class="n">Handle</span><span class="p">,</span>

    <span class="c1">// 共享的内部结构，参与侵入式链表</span>
    <span class="nd">#[pin]</span>
    <span class="n">inner</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">TimerShared</span><span class="o">&gt;</span><span class="p">,</span>

    <span class="c1">// deadline：首次 poll 时才注册到时间轮</span>
    <span class="n">deadline</span><span class="p">:</span> <span class="n">Instant</span><span class="p">,</span>

    <span class="c1">// 是否已经注册</span>
    <span class="n">registered</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>而 <code class="language-plaintext highlighter-rouge">TimerShared</code> 是前端（<code class="language-plaintext highlighter-rouge">TimerEntry</code>）和驱动端（<code class="language-plaintext highlighter-rouge">Driver</code>）之间共享的状态<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/entry.rs: 336-360</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">TimerShared</span> <span class="p">{</span>
    <span class="c1">// 侵入式双向链表的指针</span>
    <span class="n">pointers</span><span class="p">:</span> <span class="nn">linked_list</span><span class="p">::</span><span class="n">Pointers</span><span class="o">&lt;</span><span class="n">TimerShared</span><span class="o">&gt;</span><span class="p">,</span>

    <span class="c1">// 注册到 Wheel 时的时间（原子变量）</span>
    <span class="n">registered_when</span><span class="p">:</span> <span class="n">AtomicU64</span><span class="p">,</span>

    <span class="c1">// 当前状态：到期时间、已触发、或已注销</span>
    <span class="n">state</span><span class="p">:</span> <span class="n">StateCell</span><span class="p">,</span>

    <span class="n">_p</span><span class="p">:</span> <span class="n">PhantomPinned</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这几个结构的层次关系可以总结为下图：</p>

<pre><code class="language-mermaid">graph TD
    A["tokio::time::sleep(duration)"] --&gt; B["Sleep&lt;br/&gt;(Future)"]
    B --&gt; C["Timer&lt;br/&gt;(enum)"]
    C --&gt; D["TimerEntry&lt;br/&gt;(Traditional 分支)"]
    D --&gt; E["TimerShared&lt;br/&gt;(共享状态)"]
    E --&gt; F["StateCell&lt;br/&gt;(原子状态 + Waker)"]
    E --&gt; G["registered_when&lt;br/&gt;(AtomicU64)"]
    E --&gt; H["pointers&lt;br/&gt;(侵入式链表节点)"]
</code></pre>

<p>这个图展示的是数据归属关系——从用户 API（<code class="language-plaintext highlighter-rouge">sleep()</code>）一层层往下走到 <code class="language-plaintext highlighter-rouge">StateCell</code>。但 <code class="language-plaintext highlighter-rouge">TimerEntry</code> 持有的 <code class="language-plaintext highlighter-rouge">driver: scheduler::Handle</code> 不在这条垂直链上——它是横向的通道，把每个 <code class="language-plaintext highlighter-rouge">TimerEntry</code> 连接到全局唯一的 Driver。这里 <code class="language-plaintext highlighter-rouge">TimerEntry</code> 持有 <code class="language-plaintext highlighter-rouge">scheduler::Handle</code>，但真正处理 <code class="language-plaintext highlighter-rouge">StateCell</code> 的是 <code class="language-plaintext highlighter-rouge">time::Handle</code>。它们之间隔着三层导航，容易混淆。下面是完整的 Handle 类型层次和职责划分：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TimerEntry.driver : scheduler::Handle    ← 运行时全貌（task 调度 + blocking pool + 所有 driver）
  │
  └─ .driver()  → driver::Handle        ← 各 driver 的聚合体
       ├─ .io: IoHandle                 ← I/O driver（mio）
       ├─ .signal: SignalHandle
       ├─ .clock: Clock
       └─ .time() → time::Handle        ← Time Driver 专用句柄
                       ├─ time_source    ← Instant ↔ tick
                       └─ inner         ← Mutex&lt;InnerState { wheel }&gt;
                                             └─ 通过 wheel.poll → fire → StateCell
</code></pre></div></div>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">scheduler::Handle</code></strong> 是”门卡”——<code class="language-plaintext highlighter-rouge">TimerEntry</code> 存它是因为创建时只拿得到这个（<code class="language-plaintext highlighter-rouge">Handle::current()</code> 返回的就是它）。它上面挂载了完整的运行时能力（spawn task、blocking pool、所有 driver），不只是 timer</li>
  <li><strong><code class="language-plaintext highlighter-rouge">driver::Handle</code></strong> 是”楼层卡”——I/O、time、signal、clock 的聚合体。<code class="language-plaintext highlighter-rouge">reset()</code> 需要走 <code class="language-plaintext highlighter-rouge">self.driver.driver().io</code> 来 unpark，所以 <code class="language-plaintext highlighter-rouge">TimerEntry</code> 不能只存 <code class="language-plaintext highlighter-rouge">time::Handle</code></li>
  <li><strong><code class="language-plaintext highlighter-rouge">time::Handle</code></strong> 才是真正操作 <code class="language-plaintext highlighter-rouge">StateCell</code> 的”钥匙”——它持有 <code class="language-plaintext highlighter-rouge">Mutex&lt;InnerState { wheel }&gt;</code>，执行 <code class="language-plaintext highlighter-rouge">process(clock)</code> → <code class="language-plaintext highlighter-rouge">Wheel::poll</code> → <code class="language-plaintext highlighter-rouge">entry.fire()</code> → <code class="language-plaintext highlighter-rouge">StateCell::fire()</code>。但 <code class="language-plaintext highlighter-rouge">TimerEntry</code> 从不在字段里直接存它，而是每次通过 <code class="language-plaintext highlighter-rouge">.driver().time()</code> 导航获取</li>
</ul>

<pre><code class="language-mermaid">graph TD
    A["scheduler::Handle&lt;br/&gt;运行时全貌"] --&gt;|".driver()"| B["driver::Handle&lt;br/&gt;所有 driver 聚合"]
    B --&gt;|".io"| C["IoHandle&lt;br/&gt;I/O driver"]
    B --&gt;|".time()"| D["time::Handle&lt;br/&gt;Time Driver 专用"]
    B --&gt;|".clock"| E["Clock"]
    D --&gt;|"持有"| F["Mutex&amp;lt;InnerState&amp;gt;"]
    F --&gt;|"包含"| G["Wheel&lt;br/&gt;哈希时间轮"]
    F --&gt;|"包含"| H["next_wake"]

    I["TimerEntry&lt;br/&gt;用户侧句柄"] --&gt;|".driver 字段"| A
    I --&gt;|"持有"| J["TimerShared&lt;br/&gt;共享状态"]
    J --&gt;|"包含"| K["StateCell&lt;br/&gt;原子状态机"]
    J --&gt;|"包含"| L["registered_when"]

    G --&gt;|"poll 返回"| J
    D --&gt;|"process → fire"| K

    style A fill:#e1f5fe
    style B fill:#fff3e0
    style D fill:#c8e6c9
    style I fill:#f3e5f5
    style K fill:#ffcdd2
</code></pre>

<p><code class="language-plaintext highlighter-rouge">TimerEntry</code> 用 <code class="language-plaintext highlighter-rouge">scheduler::Handle</code> 当通行证，需要 timer 就往下走到 <code class="language-plaintext highlighter-rouge">time()</code>，需要 I/O 就走到 <code class="language-plaintext highlighter-rouge">io</code>。但 <code class="language-plaintext highlighter-rouge">StateCell</code> 只归 <code class="language-plaintext highlighter-rouge">time::Handle</code> 的 <code class="language-plaintext highlighter-rouge">process()</code> 链操作——I/O driver 和 signal driver 永远不会碰它。</p>

<p>下面的调用链展示了从 <code class="language-plaintext highlighter-rouge">TimerEntry.driver</code>（<code class="language-plaintext highlighter-rouge">scheduler::Handle</code>）导航到 <code class="language-plaintext highlighter-rouge">time::Handle</code> 再到 <code class="language-plaintext highlighter-rouge">process()</code> 的完整路径——这就是上文类图和数据流图的代码对应：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/entry.rs: TimerEntry::poll_elapsed 及相关方法</span>
<span class="k">impl</span> <span class="n">TimerEntry</span> <span class="p">{</span>
    <span class="c1">// TimerEntry 拿到 time::Handle 的唯一途径</span>
    <span class="k">fn</span> <span class="nf">driver</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">&amp;</span><span class="k">super</span><span class="p">::</span><span class="n">Handle</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.driver</span>        <span class="c1">// ① scheduler::Handle (timer 创建时存入)</span>
            <span class="nf">.driver</span><span class="p">()</span>      <span class="c1">// ② → driver::Handle   (调度器句柄上的 .driver() 方法)</span>
            <span class="nf">.time</span><span class="p">()</span>        <span class="c1">// ③ → time::Handle     (driver 聚合体上的 .time() 方法)</span>
    <span class="p">}</span>

    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">poll_elapsed</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">cx</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Context</span><span class="o">&lt;</span><span class="nv">'_</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Poll</span><span class="o">&lt;...&gt;</span> <span class="p">{</span>
        <span class="c1">// self.driver() 返回 time::Handle，但 poll 不需要它——</span>
        <span class="c1">// StateCell 就在自己内部的 TimerShared 里</span>
        <span class="k">self</span><span class="py">.inner.state</span><span class="nf">.poll</span><span class="p">(</span><span class="n">cx</span><span class="nf">.waker</span><span class="p">())</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>三个 Handle 的精简结构定义——只列出与本链相关的字段和方法：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// === tokio/src/runtime/scheduler/mod.rs ===</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">enum</span> <span class="n">Handle</span> <span class="p">{</span>                      <span class="c1">// ① scheduler::Handle</span>
    <span class="nf">CurrentThread</span><span class="p">(</span><span class="nb">Arc</span><span class="o">&lt;</span><span class="nn">current_thread</span><span class="p">::</span><span class="n">Handle</span><span class="o">&gt;</span><span class="p">),</span>
    <span class="nf">MultiThread</span><span class="p">(</span><span class="nb">Arc</span><span class="o">&lt;</span><span class="nn">multi_thread</span><span class="p">::</span><span class="n">Handle</span><span class="o">&gt;</span><span class="p">),</span>
<span class="p">}</span>
<span class="k">impl</span> <span class="n">Handle</span> <span class="p">{</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">driver</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">&amp;</span><span class="nn">driver</span><span class="p">::</span><span class="n">Handle</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>  <span class="c1">// → ②</span>
<span class="p">}</span>

<span class="c1">// === tokio/src/runtime/driver.rs ===</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">Handle</span> <span class="p">{</span>                    <span class="c1">// ② driver::Handle</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">io</span><span class="p">:</span> <span class="n">IoHandle</span><span class="p">,</span>                  <span class="c1">// I/O driver 句柄</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">signal</span><span class="p">:</span> <span class="n">SignalHandle</span><span class="p">,</span>          <span class="c1">// 信号 driver 句柄</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">time</span><span class="p">:</span> <span class="n">TimeHandle</span><span class="p">,</span>              <span class="c1">// TimeHandle = Option&lt;time::Handle&gt;</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">clock</span><span class="p">:</span> <span class="n">Clock</span><span class="p">,</span>                  <span class="c1">// 时钟源</span>
<span class="p">}</span>
<span class="k">impl</span> <span class="n">Handle</span> <span class="p">{</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">time</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">&amp;</span><span class="nn">time</span><span class="p">::</span><span class="n">Handle</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>  <span class="c1">// → ③</span>
<span class="p">}</span>

<span class="c1">// === tokio/src/runtime/time/handle.rs ===</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">Handle</span> <span class="p">{</span>                    <span class="c1">// ③ time::Handle</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">super</span><span class="p">)</span> <span class="n">time_source</span><span class="p">:</span> <span class="n">TimeSource</span><span class="p">,</span>       <span class="c1">// Instant ↔ tick 转换</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">super</span><span class="p">)</span> <span class="n">inner</span><span class="p">:</span> <span class="k">super</span><span class="p">::</span><span class="n">Inner</span><span class="p">,</span>           <span class="c1">// Inner::Traditional { state: Mutex&lt;InnerState&gt;, ... }</span>
<span class="p">}</span>
<span class="c1">// InnerState = { next_wake: Option&lt;NonZeroU64&gt;, wheel: wheel::Wheel }</span>
</code></pre></div></div>

<p>导航链路的三个关键跃迁点：</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">scheduler::Handle::driver()</code> — 从运行时全貌进入 driver 聚合层</li>
  <li><code class="language-plaintext highlighter-rouge">driver::Handle::time()</code> — 从 driver 聚合中提取 Time Driver 句柄（若未 enable_time 则 panic）</li>
  <li><code class="language-plaintext highlighter-rouge">time::Handle</code> 持有 <code class="language-plaintext highlighter-rouge">Mutex&lt;InnerState{ wheel }&gt;</code> — 从这里开始 <code class="language-plaintext highlighter-rouge">process()</code> → <code class="language-plaintext highlighter-rouge">Wheel::poll()</code> → <code class="language-plaintext highlighter-rouge">fire()</code> → <code class="language-plaintext highlighter-rouge">StateCell</code></li>
</ol>

<p>简单来说：用户持有的 <code class="language-plaintext highlighter-rouge">Sleep</code> 是一个 Future，它的内部通过 <code class="language-plaintext highlighter-rouge">TimerEntry</code> 管理一个 deadline，而 <code class="language-plaintext highlighter-rouge">TimerShared</code> 以侵入式链表节点的形式嵌入到底层的<strong>哈希时间轮（Hashed Timing Wheel）</strong>中。</p>

<h2 id="二首次-poll注册到时间轮">二、首次 poll：注册到时间轮</h2>

<p>当 <code class="language-plaintext highlighter-rouge">Sleep</code> 第一次被 poll 时，<code class="language-plaintext highlighter-rouge">TimerEntry</code> 还没有注册到时间轮。这时它会调用 <code class="language-plaintext highlighter-rouge">reset</code>，将自己插入到底层的 <code class="language-plaintext highlighter-rouge">Wheel</code> 数据结构中<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/entry.rs: 598-617</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">poll_elapsed</span><span class="p">(</span>
    <span class="k">mut</span> <span class="k">self</span><span class="p">:</span> <span class="nb">Pin</span><span class="o">&lt;&amp;</span><span class="k">mut</span> <span class="k">Self</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">cx</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Context</span><span class="o">&lt;</span><span class="nv">'_</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Poll</span><span class="o">&lt;</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="k">super</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;&gt;</span> <span class="p">{</span>
    <span class="k">if</span> <span class="o">!</span><span class="k">self</span><span class="py">.registered</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">deadline</span> <span class="o">=</span> <span class="k">self</span><span class="py">.deadline</span><span class="p">;</span>
        <span class="c1">// 首次 poll：将 deadline 注册到时间轮</span>
        <span class="k">self</span><span class="nf">.as_mut</span><span class="p">()</span><span class="nf">.reset</span><span class="p">(</span><span class="n">deadline</span><span class="p">,</span> <span class="k">true</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">let</span> <span class="n">inner</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.inner</span><span class="p">()</span>
        <span class="nf">.expect</span><span class="p">(</span><span class="s">"inner should already be initialized by `self.reset()`"</span><span class="p">);</span>
    <span class="c1">// 通过 StateCell 检查是否已经到期</span>
    <span class="n">inner</span><span class="py">.state</span><span class="nf">.poll</span><span class="p">(</span><span class="n">cx</span><span class="nf">.waker</span><span class="p">())</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">reset</code> 的核心逻辑是：</p>
<ol>
  <li>把 <code class="language-plaintext highlighter-rouge">Instant</code> 格式的 deadline 通过 <code class="language-plaintext highlighter-rouge">TimeSource</code> 转换成 <code class="language-plaintext highlighter-rouge">u64</code> 类型的 tick（毫秒级）</li>
  <li>先尝试原子操作 <code class="language-plaintext highlighter-rouge">extend_expiration</code>——如果新 deadline 比旧的晚，可以无锁更新</li>
  <li>如果 <code class="language-plaintext highlighter-rouge">extend_expiration</code> 失败（比如 deadline 提前了，或者 timer 已经在触发队列中），则走 <code class="language-plaintext highlighter-rouge">reregister</code> 路径重新插入 Wheel</li>
</ol>

<p>注意这里的类型转换和调用链。<code class="language-plaintext highlighter-rouge">reset()</code> 的源码中<sup id="fnref:entry-rs-reset"><a href="#fn:entry-rs-reset" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/entry.rs: 638-649</span>
<span class="k">let</span> <span class="n">inner</span> <span class="o">=</span> <span class="k">match</span> <span class="k">self</span><span class="nf">.inner</span><span class="p">()</span> <span class="p">{</span>
    <span class="nf">Some</span><span class="p">(</span><span class="n">inner</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="n">inner</span><span class="p">,</span>          <span class="c1">// inner: &amp;TimerShared</span>
    <span class="nb">None</span> <span class="k">=&gt;</span> <span class="p">{</span> <span class="cm">/* 初始化 */</span> <span class="p">}</span>
<span class="p">};</span>

<span class="c1">// extend_expiration 失败时走 reregister 路径</span>
<span class="k">if</span> <span class="n">reregister</span> <span class="p">{</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="k">self</span><span class="nf">.driver</span><span class="p">()</span>
            <span class="nf">.reregister</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="py">.driver</span><span class="nf">.driver</span><span class="p">()</span><span class="py">.io</span><span class="p">,</span> <span class="n">tick</span><span class="p">,</span> <span class="n">inner</span><span class="nf">.into</span><span class="p">());</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">inner</code> 是 <code class="language-plaintext highlighter-rouge">&amp;TimerShared</code>，<code class="language-plaintext highlighter-rouge">.into()</code> 通过标准库的 <code class="language-plaintext highlighter-rouge">From&lt;&amp;T&gt; for NonNull&lt;T&gt;</code> 将其转换为 <code class="language-plaintext highlighter-rouge">NonNull&lt;TimerShared&gt;</code>——一个原始指针包装。这个 <code class="language-plaintext highlighter-rouge">NonNull</code> 随后被传入 <code class="language-plaintext highlighter-rouge">Handle::reregister()</code><sup id="fnref:reregister"><a href="#fn:reregister" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/mod.rs: 398-435</span>
<span class="k">pub</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">reregister</span><span class="p">(</span>
    <span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">unpark</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">IoHandle</span><span class="p">,</span> <span class="n">new_tick</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="n">entry</span><span class="p">:</span> <span class="n">NonNull</span><span class="o">&lt;</span><span class="n">TimerShared</span><span class="o">&gt;</span><span class="p">,</span>    <span class="c1">// ← 来自 inner.into()</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">lock</span> <span class="o">=</span> <span class="k">self</span><span class="py">.inner</span><span class="nf">.lock</span><span class="p">();</span>

    <span class="c1">// 如果 timer 还在 Wheel 中，先移除</span>
    <span class="k">if</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="n">entry</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.might_be_registered</span><span class="p">()</span> <span class="p">}</span> <span class="p">{</span>
        <span class="n">lock</span><span class="py">.wheel</span><span class="nf">.remove</span><span class="p">(</span><span class="n">entry</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// 创建 TimerHandle——一个 NonNull&lt;TimerShared&gt; 的 "唯一指针"</span>
    <span class="k">let</span> <span class="n">entry</span> <span class="o">=</span> <span class="n">entry</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.handle</span><span class="p">();</span>

    <span class="n">entry</span><span class="nf">.set_expiration</span><span class="p">(</span><span class="n">new_tick</span><span class="p">);</span>  <span class="c1">// 设置新的到期时间</span>

    <span class="c1">// 插入 Wheel</span>
    <span class="k">match</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="n">lock</span><span class="py">.wheel</span><span class="nf">.insert</span><span class="p">(</span><span class="n">entry</span><span class="p">)</span> <span class="p">}</span> <span class="p">{</span>
        <span class="nf">Ok</span><span class="p">(</span><span class="n">when</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="c1">// 如果新到期时间比当前最早更早 → unpark 线程</span>
            <span class="k">if</span> <span class="n">lock</span><span class="py">.next_wake</span><span class="nf">.map</span><span class="p">(|</span><span class="n">n</span><span class="p">|</span> <span class="n">when</span> <span class="o">&lt;</span> <span class="n">n</span><span class="nf">.get</span><span class="p">())</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="k">true</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">unpark</span><span class="nf">.unpark</span><span class="p">();</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="nf">Err</span><span class="p">((</span><span class="n">entry</span><span class="p">,</span> <span class="nn">InsertError</span><span class="p">::</span><span class="n">Elapsed</span><span class="p">))</span> <span class="k">=&gt;</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="n">entry</span><span class="nf">.fire</span><span class="p">(</span><span class="nf">Ok</span><span class="p">(()))</span> <span class="p">},</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这里 <code class="language-plaintext highlighter-rouge">entry.as_ref().handle()</code> 创建 <code class="language-plaintext highlighter-rouge">TimerHandle</code>——它的定义极其简单<sup id="fnref:entry-rs-timerhandle"><a href="#fn:entry-rs-timerhandle" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/entry.rs: 183-188</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">TimerHandle</span> <span class="p">{</span>
    <span class="n">inner</span><span class="p">:</span> <span class="n">NonNull</span><span class="o">&lt;</span><span class="n">TimerShared</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>

<span class="c1">// tokio/src/runtime/time/entry.rs: 308-312</span>
<span class="k">impl</span> <span class="n">TimerShared</span> <span class="p">{</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">super</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">handle</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">TimerHandle</span> <span class="p">{</span>
        <span class="n">TimerHandle</span> <span class="p">{</span> <span class="n">inner</span><span class="p">:</span> <span class="nn">NonNull</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">TimerHandle</code> 不拥有 <code class="language-plaintext highlighter-rouge">TimerShared</code>——它只是一个”唯一指针”，借用规则由 unsafe 契约保证（持有驱动锁时才能操作）。</p>

<p>随后 <code class="language-plaintext highlighter-rouge">Wheel::insert()</code> 根据到期时间计算层级和 slot 位置，调用 <code class="language-plaintext highlighter-rouge">Level::add_entry()</code> 将 <code class="language-plaintext highlighter-rouge">TimerHandle</code>（即 <code class="language-plaintext highlighter-rouge">NonNull&lt;TimerShared&gt;</code>）push 到 slot 的侵入式链表上<sup id="fnref:wheel_insert"><a href="#fn:wheel_insert" class="footnote" rel="footnote" role="doc-noteref">10</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/wheel/mod.rs: 88-112</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">insert</span><span class="p">(</span>
    <span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">item</span><span class="p">:</span> <span class="n">TimerHandle</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">u64</span><span class="p">,</span> <span class="p">(</span><span class="n">TimerHandle</span><span class="p">,</span> <span class="n">InsertError</span><span class="p">)</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">when</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="n">item</span><span class="nf">.sync_when</span><span class="p">()</span> <span class="p">};</span>  <span class="c1">// 同步到期时间</span>
    <span class="k">if</span> <span class="n">when</span> <span class="o">&lt;=</span> <span class="k">self</span><span class="py">.elapsed</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nf">Err</span><span class="p">((</span><span class="n">item</span><span class="p">,</span> <span class="nn">InsertError</span><span class="p">::</span><span class="n">Elapsed</span><span class="p">));</span>  <span class="c1">// 已过期</span>
    <span class="p">}</span>

    <span class="k">let</span> <span class="n">level</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.level_for</span><span class="p">(</span><span class="n">when</span><span class="p">);</span>  <span class="c1">// 计算层级</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="k">self</span><span class="py">.levels</span><span class="p">[</span><span class="n">level</span><span class="p">]</span><span class="nf">.add_entry</span><span class="p">(</span><span class="n">item</span><span class="p">);</span> <span class="p">}</span>
    <span class="nf">Ok</span><span class="p">(</span><span class="n">when</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// tokio/src/runtime/time/wheel/level.rs: 122-128</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">add_entry</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">item</span><span class="p">:</span> <span class="n">TimerHandle</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">slot</span> <span class="o">=</span> <span class="nf">slot_for</span><span class="p">(</span><span class="k">unsafe</span> <span class="p">{</span> <span class="n">item</span><span class="nf">.registered_when</span><span class="p">()</span> <span class="p">},</span> <span class="k">self</span><span class="py">.level</span><span class="p">);</span>
    <span class="k">self</span><span class="py">.slot</span><span class="p">[</span><span class="n">slot</span><span class="p">]</span><span class="nf">.push_front</span><span class="p">(</span><span class="n">item</span><span class="p">);</span>  <span class="c1">// push 到侵入式链表</span>
    <span class="k">self</span><span class="py">.occupied</span> <span class="p">|</span><span class="o">=</span> <span class="nf">occupied_bit</span><span class="p">(</span><span class="n">slot</span><span class="p">);</span>  <span class="c1">// 标记 slot 非空</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">self.slot[slot]</code> 的类型是 <code class="language-plaintext highlighter-rouge">EntryList</code>，即 <code class="language-plaintext highlighter-rouge">LinkedList&lt;TimerShared, TimerShared&gt;</code>——一个侵入式双向链表。<code class="language-plaintext highlighter-rouge">push_front</code> 将 <code class="language-plaintext highlighter-rouge">TimerHandle</code> 中的 <code class="language-plaintext highlighter-rouge">NonNull&lt;TimerShared&gt;</code> 作为链表节点插入，<code class="language-plaintext highlighter-rouge">TimerShared</code> 的 <code class="language-plaintext highlighter-rouge">pointers</code> 字段就是它的 <code class="language-plaintext highlighter-rouge">prev/next</code> 指针。</p>

<p>这里有一个关键设计——<strong>两层到期时间缓存</strong>。<code class="language-plaintext highlighter-rouge">TimerShared</code> 中有两个时间字段：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/entry.rs: 269-281</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">TimerShared</span> <span class="p">{</span>
    <span class="n">registered_when</span><span class="p">:</span> <span class="n">AtomicU64</span><span class="p">,</span>  <span class="c1">// ① 写入 slot 时的到期时间缓存</span>
    <span class="n">state</span><span class="p">:</span> <span class="n">StateCell</span><span class="p">,</span>            <span class="c1">// ② StateCell 内部的 AtomicU64 存 true expiration</span>
    <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">registered_when</code></strong>：timer 插入 slot 时的到期时间。Wheel 用它计算 slot 索引（<code class="language-plaintext highlighter-rouge">slot_for()</code>），在 <code class="language-plaintext highlighter-rouge">remove()</code> 时定位 slot。这个值只在插入和移动时更新——<strong>它可能过期</strong>。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">StateCell.state</code></strong>：真正的到期时间。它由 <code class="language-plaintext highlighter-rouge">TimerEntry::reset()</code> 写入，可以通过 <code class="language-plaintext highlighter-rouge">extend_expiration()</code> 在无锁的情况下<strong>延后</strong>（CAS 更新），Driver 在收割时用它做最终判断。</li>
</ul>

<p><code class="language-plaintext highlighter-rouge">sync_when()</code> 是两层之间的桥梁——把 <code class="language-plaintext highlighter-rouge">StateCell.state</code>（true_when）同步到 <code class="language-plaintext highlighter-rouge">registered_when</code>，供 Wheel 做 slot 定位：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Wheel::insert() 先调用 sync_when()</span>
<span class="k">let</span> <span class="n">when</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="n">item</span><span class="nf">.sync_when</span><span class="p">()</span> <span class="p">};</span>  <span class="c1">// StateCell.state → registered_when</span>
<span class="k">if</span> <span class="n">when</span> <span class="o">&lt;=</span> <span class="k">self</span><span class="py">.elapsed</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="n">Elapsed</span><span class="p">);</span> <span class="p">}</span>
<span class="k">let</span> <span class="n">level</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.level_for</span><span class="p">(</span><span class="n">when</span><span class="p">);</span>           <span class="c1">// 用 sync 后的值算层级</span>
<span class="k">self</span><span class="py">.levels</span><span class="p">[</span><span class="n">level</span><span class="p">]</span><span class="nf">.add_entry</span><span class="p">(</span><span class="n">item</span><span class="p">);</span>         <span class="c1">// add_entry 内部用 registered_when 算 slot</span>
</code></pre></div></div>

<p>收割时，<code class="language-plaintext highlighter-rouge">process_expiration()</code> 用 <code class="language-plaintext highlighter-rouge">mark_pending(expiration.deadline)</code> 直接读 <strong><code class="language-plaintext highlighter-rouge">StateCell.state</code></strong> 做最终比对：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// process_expiration 简化</span>
<span class="k">match</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="n">item</span><span class="nf">.mark_pending</span><span class="p">(</span><span class="n">expiration</span><span class="py">.deadline</span><span class="p">)</span> <span class="p">}</span> <span class="p">{</span>
    <span class="nf">Ok</span><span class="p">(())</span> <span class="k">=&gt;</span> <span class="n">pending</span><span class="nf">.push_front</span><span class="p">(</span><span class="n">item</span><span class="p">),</span>  <span class="c1">// StateCell.state ≤ deadline → 到期</span>
    <span class="nf">Err</span><span class="p">(</span><span class="n">expiration_tick</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>            <span class="c1">// StateCell.state &gt; deadline → 还没到</span>
        <span class="c1">// 把 true_when 写回 registered_when，重插入正确 slot</span>
        <span class="k">self</span><span class="py">.levels</span><span class="p">[</span><span class="n">level</span><span class="p">]</span><span class="nf">.add_entry</span><span class="p">(</span><span class="n">item</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这种两层设计是为 <code class="language-plaintext highlighter-rouge">extend_expiration</code> 乐观路径服务的：用户通过 CAS 无锁延后了 <code class="language-plaintext highlighter-rouge">StateCell.state</code>，但 <code class="language-plaintext highlighter-rouge">registered_when</code> 还指向旧的 slot。Driver 收割时发现 <code class="language-plaintext highlighter-rouge">state &gt; slot deadline</code>，不触发 fire，而是把 <code class="language-plaintext highlighter-rouge">state</code> 同步回 <code class="language-plaintext highlighter-rouge">registered_when</code> 并重插入正确 slot——整个过程零锁争用。</p>

<p>所以整条注册链是：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Sleep::poll_elapsed()
    → TimerEntry::reset()
        → extend_expiration(tick)  ← 乐观路径：CAS 无锁延后
        → Handle::reregister()      ← 失败时走此路径
            → lock.wheel.insert(handle)  ← TimerHandle 入 Wheel
                → Level::add_entry()
                    → slot[slot_index].push_front(item)
                    → occupied |= (1 &lt;&lt; slot)
</code></pre></div></div>

<p>现在可以更精确地描述数据流：<strong><code class="language-plaintext highlighter-rouge">Sleep</code> 持有 <code class="language-plaintext highlighter-rouge">TimerEntry</code>，<code class="language-plaintext highlighter-rouge">TimerEntry</code> 持有 <code class="language-plaintext highlighter-rouge">Pin&lt;Option&lt;TimerShared&gt;&gt;</code>，<code class="language-plaintext highlighter-rouge">TimerShared</code> 通过 <code class="language-plaintext highlighter-rouge">NonNull&lt;TimerShared&gt;</code>（即 <code class="language-plaintext highlighter-rouge">TimerHandle</code>）以侵入式链表节点形式直接挂在 <code class="language-plaintext highlighter-rouge">Level.slot[slot]</code> 上</strong>。没有复制、没有中间分配——移动 <code class="language-plaintext highlighter-rouge">TimerHandle</code> 就是在操作 <code class="language-plaintext highlighter-rouge">TimerShared</code> 的 <code class="language-plaintext highlighter-rouge">pointers</code> 指针。</p>

<p>这个过程可以用下面的序列图看清：</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant Sleep as Sleep Future
    participant Entry as TimerEntry
    participant Shared as TimerShared
    participant Handle as TimerHandle
    participant Wheel as Wheel
    participant Level as Level.slot[64]

    Sleep-&gt;&gt;Entry: poll_elapsed(cx)
    Entry-&gt;&gt;Entry: reset(deadline)
    Entry-&gt;&gt;Shared: extend_expiration(tick)
    Note over Shared: 如果新 deadline&lt;br/&gt;比旧的晚→CAS 成功&lt;br/&gt;跳过重新注册
    alt extend_expiration 失败
        Entry-&gt;&gt;Shared: inner.into() → TimerHandle
        Entry-&gt;&gt;Wheel: Handle.reregister(tick)
        Wheel-&gt;&gt;Wheel: remove(old slot)
        Wheel-&gt;&gt;Wheel: insert(handle)
        Wheel-&gt;&gt;Level: level_for(when) 计算层级
        Level-&gt;&gt;Level: slot[slot_index].push_front(item)
        level--&gt;&gt;Wheel: OK(when)
        Wheel--&gt;&gt;Entry: 注册完成
    end
    Entry-&gt;&gt;Shared: state.poll(waker)
    Shared--&gt;&gt;Entry: Poll::Pending
    Entry--&gt;&gt;Sleep: Poll::Pending
</code></pre>

<p>这里有一个关键细节：<strong>首次注册不是在 <code class="language-plaintext highlighter-rouge">sleep()</code> 调用时发生的，而是在第一次 <code class="language-plaintext highlighter-rouge">poll</code> 时发生的</strong>。这也是为什么如果在 runtime 外部直接调用 <code class="language-plaintext highlighter-rouge">sleep()</code> 会 panic——它需要拿到当前的调度器句柄，而句柄只存在于 runtime 上下文中。如果把它包在一个 async block 里，调用就是惰性的，等到真正 poll 时已经在 runtime 内部了，就不会 panic<sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">11</a></sup>。</p>

<p>具体来说，<code class="language-plaintext highlighter-rouge">scheduler::Handle</code>（内含 <code class="language-plaintext highlighter-rouge">time::Handle</code>）的注入分两步：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>① sleep(duration) 被调用时 ── Handle 存入 Sleep，但 TimerEntry 还未创建
② 首次 .await 触发 poll 时 ── Handle 从 Sleep 传递给 TimerEntry::new()
</code></pre></div></div>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ① tokio/src/time/sleep.rs: Sleep::new_timeout —— sleep() 调用时</span>
<span class="k">impl</span> <span class="n">Sleep</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">new_timeout</span><span class="p">(</span><span class="n">deadline</span><span class="p">:</span> <span class="n">Instant</span><span class="p">,</span> <span class="o">..</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">driver</span><span class="p">:</span> <span class="nn">Handle</span><span class="p">::</span><span class="nf">current</span><span class="p">(),</span>  <span class="c1">// ← 从 thread-local 取出 scheduler::Handle</span>
            <span class="n">timer</span><span class="p">:</span> <span class="nb">None</span><span class="p">,</span>               <span class="c1">// ← 这里还是 None，TimerEntry 懒创建</span>
            <span class="n">deadline</span><span class="p">,</span>
            <span class="o">..</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// ② tokio/src/time/sleep.rs: Sleep::poll_elapsed —— 首次 poll 时</span>
<span class="k">fn</span> <span class="nf">poll_elapsed</span><span class="p">(</span><span class="k">self</span><span class="p">:</span> <span class="nb">Pin</span><span class="o">&lt;&amp;</span><span class="k">mut</span> <span class="k">Self</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">cx</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Context</span><span class="o">&lt;</span><span class="nv">'_</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">..</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">timer</span> <span class="o">=</span> <span class="k">match</span> <span class="n">this</span><span class="py">.timer</span><span class="nf">.as_mut</span><span class="p">()</span><span class="nf">.as_pin_mut</span><span class="p">()</span> <span class="p">{</span>
        <span class="nf">Some</span><span class="p">(</span><span class="n">timer</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="n">timer</span><span class="p">,</span>
        <span class="nb">None</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">handle</span> <span class="o">=</span> <span class="n">this</span><span class="py">.driver</span><span class="p">;</span>  <span class="c1">// ← 步骤①存入的 scheduler::Handle</span>
            <span class="k">let</span> <span class="n">timer</span> <span class="o">=</span> <span class="nn">Timer</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">handle</span><span class="nf">.clone</span><span class="p">(),</span> <span class="o">*</span><span class="n">this</span><span class="py">.deadline</span><span class="p">);</span>
            <span class="c1">//  ↑ Timer::new() → TimerEntry::new() → TimerEntry { driver: scheduler::Handle }</span>
            <span class="n">this</span><span class="py">.timer</span><span class="nf">.set</span><span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="n">timer</span><span class="p">));</span>
            <span class="o">..</span>
        <span class="p">}</span>
    <span class="p">};</span>
    <span class="n">timer</span><span class="nf">.poll_elapsed</span><span class="p">(</span><span class="n">cx</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">Timer::new()</code> 内部调用 <code class="language-plaintext highlighter-rouge">TimerEntry::new()</code>，后者将 <code class="language-plaintext highlighter-rouge">scheduler::Handle</code> 存入 <code class="language-plaintext highlighter-rouge">TimerEntry.driver</code> 字段。这个 Handle 贯穿 timer 的整个生命周期——<code class="language-plaintext highlighter-rouge">poll</code>、<code class="language-plaintext highlighter-rouge">reset</code>、<code class="language-plaintext highlighter-rouge">reregister</code> 都用它来导航到 <code class="language-plaintext highlighter-rouge">time::Handle</code> 或 <code class="language-plaintext highlighter-rouge">io::Handle</code>。</p>
</blockquote>

<p>由此可以画出两条独立的调用路径——它们不直接调用对方，而是在 <code class="language-plaintext highlighter-rouge">StateCell</code> 处交汇：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>═══════════ Task 侧（poll 路径）═══════════     ═══════════ Driver 侧（park 路径）═══════════
                                              park_internal(rt_handle: &amp;driver::Handle)
Sleep::poll(cx)                                 │
  │                                             ├─ let handle = rt_handle.time()
  └─ poll_elapsed(cx)                           │    // → &amp;time::Handle
       │                                        │
       └─ TimerEntry::poll_elapsed(cx)          ├─ handle.process(clock)
            │                                   │    │
            └─ self.inner.state                 │    └─ process_at_time(now)
                 .poll(cx.waker())              │         │
                 │                              │         └─ lock.wheel.poll(now)
                 │  StateCell::poll():          │              │
                 ├─ store waker                 │              └─ entry.fire(Ok(()))
                 └─ load state                  │                   │
                    └─ Poll::Pending            │                   └─ StateCell::fire()
                                                      ↑              ├─ store DEREGISTERED
                                                 交汇点 ──────────────┘  └─ take waker
                                                                              │
                                                                         waker.wake()
</code></pre></div></div>

<p>Task 侧写入 Waker（<code class="language-plaintext highlighter-rouge">poll</code>），Driver 侧取出 Waker（<code class="language-plaintext highlighter-rouge">fire</code>）。两者操作的是<strong>同一个 <code class="language-plaintext highlighter-rouge">TimerShared</code> 内的同一个 <code class="language-plaintext highlighter-rouge">StateCell</code></strong>，但 Task 侧不需要 <code class="language-plaintext highlighter-rouge">time::Handle</code>——它直接操作自己持有的 <code class="language-plaintext highlighter-rouge">TimerShared.state</code>；Driver 侧也不需要知道 <code class="language-plaintext highlighter-rouge">TimerEntry</code> 的存在——它通过 <code class="language-plaintext highlighter-rouge">TimerHandle</code>（一个 <code class="language-plaintext highlighter-rouge">NonNull&lt;TimerShared&gt;</code> 裸指针）间接操作。</p>

<h2 id="三哈希时间轮hashed-timing-wheel">三、哈希时间轮（Hashed Timing Wheel）</h2>

<p>Timer 的核心数据结构是一个<strong>多层哈希时间轮</strong>。Tokio 的注释明确引用了 Varghese 和 Lauck 的经典论文<sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">12</a></sup>，该论文也因 Linux 内核的高精度定时器实现而广为人知。</p>

<p>时间轮的核心思想很简单：<strong>把到期的定时器散列到不同的 slot 里，时钟走动时只检查当前 slot，避免每次都扫描全部定时器</strong>。</p>

<p>它本质上是用<strong>空间换时间</strong>：用 6 层 × 64 slot 的数组，换取每次 tick 只扫一个 slot 的能力。对比一下就能明白——如果用一个普通 <code class="language-plaintext highlighter-rouge">Vec</code> 或链表存储所有定时器，每次 tick 都得全量扫描一遍，O(n)；而时间轮把定时器按到期时间散列，每次 tick 只检查当前 slot，O(1)。存储只是手段，快速检索到期定时器才是时间轮存在的目的。</p>

<p>Tokio 使用了 6 层时间轮，每层 64 个 slot：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Level 0: 64 × 1 ms      = 64 ms 覆盖范围
Level 1: 64 × 64 ms     = ~4 秒覆盖范围
Level 2: 64 × ~4 s      = ~4 分钟覆盖范围
Level 3: 64 × ~4 min    = ~4 小时覆盖范围
Level 4: 64 × ~4 hr     = ~12 天覆盖范围
Level 5: 64 × ~12 day   = ~2 年覆盖范围
</code></pre></div></div>

<p>下图展示了一个简化的 3 层时间轮的工作方式：</p>

<pre><code class="language-mermaid">graph LR
    subgraph "Level 2 (~4 min)"
        L2["slot 0-63&lt;br/&gt;每 slot ~4s"]
    end
    subgraph "Level 1 (~4 sec)"
        L1["slot 0-63&lt;br/&gt;每 slot 64ms"]
    end
    subgraph "Level 0 (64 ms)"
        L0["slot 0-63&lt;br/&gt;每 slot 1ms"]
    end

    L2 --&gt;|"降级：slot 到期&lt;br/&gt;重新分配"| L1
    L1 --&gt;|"降级"| L0
    L0 --&gt;|"到期触发"| F["fire → wake"]
</code></pre>

<p>定时器首先被插入到与它的到期时间匹配的层级。比如一个 10 秒后到期的定时器，会落在 Level 2 的某个 slot 里。当该 slot 到期时，里面的所有定时器会被重新分配到 Level 1；然后再降到 Level 0；最终在 Level 0 的 slot 到期时真正触发。</p>

<p>这种层级设计的优势在于：</p>
<ul>
  <li><strong>插入 O(1)</strong>：只需要根据到期时间算出 slot 位置</li>
  <li><strong>每 tick 的检查量很小</strong>：只处理当前 slot 内的定时器</li>
  <li><strong>覆盖范围大</strong>：6 层 64 slot 的结构可以覆盖约 2 年的时间范围，精度 1 毫秒</li>
</ul>

<p>同一 Level 内的 64 个 slot 共享同一个精度（<code class="language-plaintext highlighter-rouge">slot_range</code>），但覆盖不同的时间窗口。例如 Level 2 的 <code class="language-plaintext highlighter-rouge">slot[0]</code> 覆盖 0~4s，<code class="language-plaintext highlighter-rouge">slot[1]</code> 覆盖 4~8s，以此类推——精度始终是 ~4s，只是窗口在平移。</p>

<p><code class="language-plaintext highlighter-rouge">Wheel</code> 结构体的关键字段<sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">13</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/wheel/mod.rs: 22-39</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">Wheel</span> <span class="p">{</span>
    <span class="c1">// 时间轮启动以来经过的毫秒数</span>
    <span class="n">elapsed</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>

    <span class="c1">// 6 层，每层 64 个 slot</span>
    <span class="n">levels</span><span class="p">:</span> <span class="nb">Box</span><span class="o">&lt;</span><span class="p">[</span><span class="n">Level</span><span class="p">;</span> <span class="n">NUM_LEVELS</span><span class="p">]</span><span class="o">&gt;</span><span class="p">,</span>

    <span class="c1">// 等待触发的定时器队列</span>
    <span class="n">pending</span><span class="p">:</span> <span class="n">EntryList</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>每个 <code class="language-plaintext highlighter-rouge">Level</code> 包含一个 <code class="language-plaintext highlighter-rouge">[EntryList; 64]</code> 数组，而 <code class="language-plaintext highlighter-rouge">EntryList</code> 就是 <code class="language-plaintext highlighter-rouge">LinkedList&lt;TimerShared&gt;</code><sup id="fnref:18"><a href="#fn:18" class="footnote" rel="footnote" role="doc-noteref">14</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/wheel/level.rs: 6-15</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">Level</span> <span class="p">{</span>
    <span class="n">level</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="n">occupied</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>              <span class="c1">// 位图标记哪些 slot 非空</span>
    <span class="n">slot</span><span class="p">:</span> <span class="p">[</span><span class="n">EntryList</span><span class="p">;</span> <span class="mi">64</span><span class="p">],</span>      <span class="c1">// 64 个 slot，每个存放一个侵入式链表</span>
<span class="p">}</span>

<span class="c1">// tokio/src/runtime/time/entry.rs: 195</span>
<span class="k">pub</span><span class="p">(</span><span class="k">super</span><span class="p">)</span> <span class="k">type</span> <span class="n">EntryList</span> <span class="o">=</span> <span class="n">LinkedList</span><span class="o">&lt;</span><span class="n">TimerShared</span><span class="p">,</span> <span class="n">TimerShared</span><span class="o">&gt;</span><span class="p">;</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">occupied</code> 是一个 <strong>u64 位图</strong>——第 i 位为 1 表示 slot[i] 非空。<code class="language-plaintext highlighter-rouge">next_expiration()</code> 用 <code class="language-plaintext highlighter-rouge">trailing_zeros()</code> 直接在一条指令中找到最低位的 1，避免遍历 64 个 slot：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/wheel/level.rs: 60-78</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">next_expiration</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">now</span><span class="p">:</span> <span class="nb">u64</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Expiration</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// occupied &gt;&gt; now_slot: 跳过当前 slot 之前的位</span>
    <span class="k">let</span> <span class="n">mask</span> <span class="o">=</span> <span class="k">self</span><span class="py">.occupied</span><span class="nf">.rotate_right</span><span class="p">(</span><span class="n">now_slot</span> <span class="k">as</span> <span class="nb">u32</span><span class="p">)</span> <span class="o">&gt;&gt;</span> <span class="mi">1</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">next</span> <span class="o">=</span> <span class="n">mask</span><span class="nf">.trailing_zeros</span><span class="p">();</span>      <span class="c1">// ← 一条指令找下一个非空 slot</span>
    <span class="c1">//                        ↑ 如果 mask = 0（无更多非空 slot），返回 64</span>
    <span class="c1">//                          调用者据此判断本层无到期</span>
    <span class="k">if</span> <span class="n">next</span> <span class="o">&gt;=</span> <span class="mi">64</span> <span class="p">{</span> <span class="k">return</span> <span class="nb">None</span><span class="p">;</span> <span class="p">}</span>

    <span class="k">let</span> <span class="n">slot</span> <span class="o">=</span> <span class="p">(</span><span class="n">now_slot</span> <span class="o">+</span> <span class="n">next</span> <span class="k">as</span> <span class="nb">usize</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">%</span> <span class="mi">64</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">level_range</span> <span class="o">=</span> <span class="mi">64_usize</span><span class="nf">.pow</span><span class="p">(</span><span class="k">self</span><span class="py">.level</span> <span class="k">as</span> <span class="nb">u32</span><span class="p">)</span> <span class="o">*</span> <span class="mi">64</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">slot_range</span> <span class="o">=</span> <span class="mi">64_usize</span><span class="nf">.pow</span><span class="p">(</span><span class="k">self</span><span class="py">.level</span> <span class="k">as</span> <span class="nb">u32</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">level_start</span> <span class="o">=</span> <span class="p">(</span><span class="n">now</span> <span class="o">/</span> <span class="n">level_range</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">)</span> <span class="o">*</span> <span class="n">level_range</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">deadline</span> <span class="o">=</span> <span class="n">level_start</span> <span class="o">+</span> <span class="p">(</span><span class="n">slot</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">)</span> <span class="o">*</span> <span class="n">slot_range</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">;</span>
    <span class="nf">Some</span><span class="p">(</span><span class="n">Expiration</span> <span class="p">{</span> <span class="n">level</span><span class="p">:</span> <span class="k">self</span><span class="py">.level</span><span class="p">,</span> <span class="n">slot</span><span class="p">,</span> <span class="n">deadline</span> <span class="p">})</span>
<span class="p">}</span>
</code></pre></div></div>

<p>所以 <code class="language-plaintext highlighter-rouge">Wheel::poll()</code> 的 <code class="language-plaintext highlighter-rouge">loop</code> 内部相当于：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>for each level [0..5]:
    if level.occupied != 0:                    // 位图非空
        mask = (occupied &gt;&gt; now_slot) &gt;&gt; 1
        next = mask.trailing_zeros()           // 硬件指令，~1 个周期
        if next &lt; 64: return (level, slot, deadline)
    // 否则本层无到期 → 继续查下一层
return None  // 所有层级都空
</code></pre></div></div>

<p>最坏情况（没有任何 timer）：6 次 <code class="language-plaintext highlighter-rouge">occupied != 0</code> 检查 + 6 次 <code class="language-plaintext highlighter-rouge">trailing_zeros</code>，总开销约几十个 CPU 周期。无空轮询、无循环遍历 64 slot——这是位图优化在时间轮中的经典用法。</p>

<p><strong>每层只检查 1 个 slot</strong>——<code class="language-plaintext highlighter-rouge">trailing_zeros()</code> 找到最低位的 1 后直接返回，每层最多输出一个 <code class="language-plaintext highlighter-rouge">Expiration</code>。所以 <code class="language-plaintext highlighter-rouge">next_expiration()</code> 扫描 6 层最多做 6 次 <code class="language-plaintext highlighter-rouge">trailing_zeros</code>、返回 1 个 <code class="language-plaintext highlighter-rouge">(level, slot, deadline)</code>，不继续找第二个 slot。</p>

<p>这里涉及三个 <code class="language-plaintext highlighter-rouge">next_expiration</code> 方法，分散在两个 struct 中，各管一级：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Wheel::next_expiration_time()           ← mod.rs:194，pub(super)
  └→ Wheel::next_expiration()            ← mod.rs:168，private
       └→ for each level [0..5]:
            Level::next_expiration(elapsed)   ← level.rs:51，pub(crate)
               → occupied.trailing_zeros()
               → 返回该层最早的 Expiration { level, slot, deadline }
</code></pre></div></div>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">Level::next_expiration(now)</code></strong>：查<strong>一个层</strong>的 <code class="language-plaintext highlighter-rouge">occupied</code> 位图，返回该层最早非空 slot 的 <code class="language-plaintext highlighter-rouge">Expiration</code>。<code class="language-plaintext highlighter-rouge">now</code> 参数用于跳过当前已处理过的 slot。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">Wheel::next_expiration()</code></strong>：遍历 6 层，每层调 <code class="language-plaintext highlighter-rouge">Level::next_expiration(elapsed)</code>，返回<strong>全局最早</strong>的 <code class="language-plaintext highlighter-rouge">Expiration</code>。<code class="language-plaintext highlighter-rouge">for (level_num, level) in self.levels.iter().enumerate()</code> 从 <code class="language-plaintext highlighter-rouge">levels[0]</code>（Level 0，1ms/slot，最精细）往 <code class="language-plaintext highlighter-rouge">levels[5]</code>（Level 5，~12天/slot，最粗）扫——找到立刻返回，不继续查更高层。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">Wheel::next_expiration_time()</code></strong>：调 <code class="language-plaintext highlighter-rouge">next_expiration().map(|ex| ex.deadline)</code>，只取 <code class="language-plaintext highlighter-rouge">deadline</code>（<code class="language-plaintext highlighter-rouge">u64</code> tick）暴露给 <code class="language-plaintext highlighter-rouge">park_internal()</code> 做 timeout 计算。</li>
</ul>

<h3 id="收割与降级process_expiration-的决策">收割与降级：process_expiration 的决策</h3>

<p><code class="language-plaintext highlighter-rouge">Wheel::poll(now)</code> 找到 <code class="language-plaintext highlighter-rouge">expiration.deadline ≤ now</code> 的 slot 后，调用 <code class="language-plaintext highlighter-rouge">process_expiration(expiration)</code> 处理该 slot 中所有 entry。对每个 entry，处理逻辑不区分层级——完全由 <code class="language-plaintext highlighter-rouge">mark_pending()</code> 的返回值决定：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  process_expiration（在 Wheel::poll 内，持 driver 锁）
         │
     mark_pending(deadline)
         │
   ┌─────┴─────┐
   │           │
 state ≤ deadline   state &gt; deadline
   │           │
 Ok(())      Err(tick)
   │           │
 pending     level_for() + add_entry()
 入队         （降级，仍留在轮子上）

  ─── 同一轮 Wheel::poll 的下一次迭代，或 process_at_time 的 while 循环 ───

 pending.pop() → Handle::process_at_time 里 entry.fire(Ok(()))
                      → WakeList → 释放锁后 waker.wake()
</code></pre></div></div>

<p>关键的认识：</p>

<ul>
  <li><strong>到期与触发是两步，不在同一函数里完成</strong>：<code class="language-plaintext highlighter-rouge">process_expiration</code> 只做 <code class="language-plaintext highlighter-rouge">mark_pending</code> + 入 <code class="language-plaintext highlighter-rouge">pending</code> 队列（或降级）；真正的 <code class="language-plaintext highlighter-rouge">fire()</code> 在 <code class="language-plaintext highlighter-rouge">Handle::process_at_time</code> 里，由 <code class="language-plaintext highlighter-rouge">wheel.poll(now)</code> 从 <code class="language-plaintext highlighter-rouge">pending</code> 弹出 handle 后调用。不要把「进 pending」和「调用 fire」画成同一步。</li>
  <li><strong>不是 Level 0 才 fire，Level &gt; 0 才降级</strong>——判据是 <code class="language-plaintext highlighter-rouge">mark_pending</code> 的返回值，不是 <code class="language-plaintext highlighter-rouge">level == 0</code>。Level 0 只是 slot 宽度仅 1ms，所以绝大多数 entry 在 Level 0 收割时会进入 <code class="language-plaintext highlighter-rouge">pending</code>。但用户通过 <code class="language-plaintext highlighter-rouge">extend_expiration</code> 把 <code class="language-plaintext highlighter-rouge">state</code> 延后到当前 slot 的 <code class="language-plaintext highlighter-rouge">deadline</code> 之外时，即使在 Level 0 收割，也会 <code class="language-plaintext highlighter-rouge">Err</code> 并降级。</li>
  <li><strong>降级的目标层级不一定是当前层级减 1</strong>——<code class="language-plaintext highlighter-rouge">level_for()</code> 用 <code class="language-plaintext highlighter-rouge">deadline</code> 和 <code class="language-plaintext highlighter-rouge">expiration_tick</code> 重新计算，可能跳过多层直接回到 Level 3（如果 timer 还剩好几分钟）。降级是 O(1) 的：一次 <code class="language-plaintext highlighter-rouge">level_for</code> 计算 + 一次 <code class="language-plaintext highlighter-rouge">add_entry</code>，无需重排链表。</li>
  <li><strong>收割一个 slot 时，slot 内的 entry 链表被整组取出（<code class="language-plaintext highlighter-rouge">take_entries</code>）后逐个处理</strong>，每个 entry 独立决策入 <code class="language-plaintext highlighter-rouge">pending</code> 还是降级。同一个 slot 里可能有部分到期、部分被延后的 entry——tokio 的这个设计正确处理了两者混存的情况。</li>
</ul>

<p>所以整个 Level 层级的职责是统一的：<strong>每层只负责按自己的 slot 粒度找到最早的到期 slot，把整组 entry 取出来；<code class="language-plaintext highlighter-rouge">mark_pending</code> 裁決入队还是降级，<code class="language-plaintext highlighter-rouge">fire</code> 则由上层的 <code class="language-plaintext highlighter-rouge">process_at_time</code> 统一处理。</strong> 没有 per-level 特化代码，6 个 Level 实例（<code class="language-plaintext highlighter-rouge">self.levels: Box&lt;[Level; 6]&gt;</code>）共享同一份算法。</p>

<p>用一句话区分三个关键方法：<strong><code class="language-plaintext highlighter-rouge">next_expiration_time()</code> 扫描 6 层找最早 deadline（算睡多久）；<code class="language-plaintext highlighter-rouge">process_expiration()</code> 逐 entry 裁決入 <code class="language-plaintext highlighter-rouge">pending</code> 还是降级；<code class="language-plaintext highlighter-rouge">process_at_time()</code> 从 <code class="language-plaintext highlighter-rouge">pending</code> 取出并 <code class="language-plaintext highlighter-rouge">fire</code>、批量唤醒任务。</strong></p>

<p><code class="language-plaintext highlighter-rouge">next_wake</code> 的值是<strong>所有非空层级中最早的 deadline</strong>，不一定是 Level 0 的 slot：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Level 0: empty        (occupied = 0)
Level 1: empty
Level 2: slot[9]      → deadline = elapsed + 4096×9 = elapsed + 36,864ms
Level 3: slot[1]      → deadline = elapsed + 262144×1 = elapsed + 262,144ms

next_expiration(): 从 Level 0 开始逐层检查
  → Level 0: occupied=0 → 跳过
  → Level 1: occupied=0 → 跳过
  → Level 2: slot[9] occupied → 返回 deadline ≈ 当前 + 36s
  → 不再检查 Level 3+（因为已有更早的结果）

next_wake = 36,864  ← 来自 Level 2 而非 Level 0
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">next_expiration()</code> 从 Level 0 到 Level 5 逐层扫描，只要低层有非空 slot 就立即返回——不再继续查更高层。所以 <code class="language-plaintext highlighter-rouge">next_wake</code> 永远是最小粒度的那个到期 slot，可能是 Level 2 的 slot 9（≈36s 后），也可能是 Level 0 的 slot 3（≈3ms 后），取决于当前时间轮中的 timer 分布。</p>

<p>所以 TimerShared 和 Wheel 的关系是：</p>

<pre><code class="language-mermaid">graph TD
    subgraph Wheel
        W["Wheel"]
        subgraph Levels
            L0["Level 0: [slot0, slot1, ..., slot63]"]
            L1["Level 1: [slot0, slot1, ..., slot63]"]
            L5["... Level 5"]
        end
        Pend["pending: EntryList"]
    end

    subgraph Timer
        TS1["TimerShared A&lt;br/&gt;(state + pointers)"]
        TS2["TimerShared B&lt;br/&gt;(state + pointers)"]
    end

    L0 --&gt;|"slot[5]"| TS1
    L0 --&gt;|"slot[5]"| TS2

    TS1 --&gt;|"pointers.prev"| TS2
    TS2 --&gt;|"pointers.next"| TS1

    subgraph Sleep
        S["Sleep 1"] --&gt; TE1["TimerEntry 1"]
        TE1 --&gt; TS1
        S2["Sleep 2"] --&gt; TE2["TimerEntry 2"]
        TE2 --&gt; TS2
    end
</code></pre>

<p>这里的关键是 <strong><code class="language-plaintext highlighter-rouge">TimerShared</code> 通过侵入式链表的 <code class="language-plaintext highlighter-rouge">pointers</code> 字段直接挂在 <code class="language-plaintext highlighter-rouge">Level.slot[slot_index]</code> 上</strong>，Wheel 不复制也不拥有 <code class="language-plaintext highlighter-rouge">TimerShared</code> 的所有权——它只通过指针链接。这就是”侵入式”的含义：被链接的对象自身包含链接指针，而非由容器分配独立的节点。这也意味着 <code class="language-plaintext highlighter-rouge">TimerShared</code> 必须被 pin 住不能移动，因为链表指针指向的是它自身的内存地址。</p>

<h3 id="32-一个完整的例子5-分钟-sleep-的降级之旅">3.2 一个完整的例子：5 分钟 sleep 的降级之旅</h3>

<blockquote>
  <p><strong>关于时间参考点的说明</strong>：<code class="language-plaintext highlighter-rouge">Wheel.elapsed</code>、<code class="language-plaintext highlighter-rouge">StateCell.state</code> 和 <code class="language-plaintext highlighter-rouge">registered_when</code> 都是相对于同一个 <code class="language-plaintext highlighter-rouge">TimeSource.start_time</code> 的 <strong>绝对毫秒偏移量</strong>。下面的例子设 <code class="language-plaintext highlighter-rouge">start_time = sleep 创建的时刻</code>，此时 <code class="language-plaintext highlighter-rouge">elapsed</code> 在第一次 <code class="language-plaintext highlighter-rouge">process_at_time</code> 前尚未推进，设为例值 <code class="language-plaintext highlighter-rouge">0</code>。实际运行中 <code class="language-plaintext highlighter-rouge">elapsed</code> 一般是个小的非零值（runtime 启动后到首次 park 之间经过的毫秒数），但这一点偏移不影响层级计算逻辑，因为 <code class="language-plaintext highlighter-rouge">state - elapsed</code> 的差值不受影响。</p>
</blockquote>

<p>把前面的概念串起来，假设你在 <code class="language-plaintext highlighter-rouge">elapsed=0</code>（即 runtime 刚启动时）创建了一个 <code class="language-plaintext highlighter-rouge">tokio::time::sleep(Duration::from_secs(300)).await</code>，看看它在 Wheel 中如何从高层降级到低层，最终被 fire。</p>

<p><strong>t=0：创建和注册</strong></p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>StateCell.state = STATE_DEREGISTERED  // 初始值 u64::MAX
Wheel.elapsed = 0                     // Wheel 刚开始
</code></pre></div></div>

<p><strong>首次 poll 时</strong>：<code class="language-plaintext highlighter-rouge">TimerEntry::reset(deadline)</code> 被调用。<code class="language-plaintext highlighter-rouge">TimeSource::deadline_to_tick()</code> 将 5 分钟（300 秒）转为毫秒 tick：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tick = 300,000   // 5 min = 300,000 ms
extend_expiration(tick=300000)
  → 失败（初始状态是 STATE_DEREGISTERED，不是有效的到期时间）
  → 走 reregister 路径
     → set_expiration(300000)
        StateCell.state = 300,000    // ← true expiration
     → Wheel::insert()
        → sync_when()
            registered_when = 300,000
            StateCell.state = 300,000 (不变)
        → level_for(0, 300000) = 3
          // 计算过程：masked = 0 ^ 300000 | 63 = 300063
          // leading_zeros = 45, significant = 18, 18/6 = 3
        → Level 3, slot 1
          // slot_for(300000, 3) = (300000 &gt;&gt; 18) % 64 = 1
</code></pre></div></div>

<p>此时 Wheel 状态：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Wheel.elapsed = 0
  Level 3: slot[1] → TimerShared (registered_when=300000, state=300000)
</code></pre></div></div>

<p><strong>t=0 ~ 4分钟：线程 park，等最早 deadline</strong></p>

<p><code class="language-plaintext highlighter-rouge">park_internal()</code> 查 <code class="language-plaintext highlighter-rouge">next_expiration_time()</code>，发现 Level 3 slot 1 的 deadline 在约 4.37 分钟后（<code class="language-plaintext highlighter-rouge">1 × 64^3 = 262,144ms</code>），于是 <code class="language-plaintext highlighter-rouge">park_timeout(262s)</code>。</p>

<p><strong>t=262s：Level 3 slot 1 到期，降级到 Level 2</strong></p>

<p>Driver 醒来，<code class="language-plaintext highlighter-rouge">process_at_time(262144)</code> → <code class="language-plaintext highlighter-rouge">Wheel::poll(262144)</code>。<code class="language-plaintext highlighter-rouge">Wheel::poll()</code> 的内部循环<sup id="fnref:9:1"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">13</a></sup>先检查 <code class="language-plaintext highlighter-rouge">next_expiration()</code> 找到 Level 3 slot 1，发现其 <code class="language-plaintext highlighter-rouge">deadline=262144 ≤ now</code>，于是调用 <code class="language-plaintext highlighter-rouge">self.process_expiration(expiration)</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/wheel/mod.rs: 126-133</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">poll</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">now</span><span class="p">:</span> <span class="nb">u64</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">TimerHandle</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">loop</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">handle</span><span class="p">)</span> <span class="o">=</span> <span class="k">self</span><span class="py">.pending</span><span class="nf">.pop_back</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">Some</span><span class="p">(</span><span class="n">handle</span><span class="p">);</span> <span class="p">}</span>
        <span class="k">match</span> <span class="k">self</span><span class="nf">.next_expiration</span><span class="p">()</span> <span class="p">{</span>
            <span class="nf">Some</span><span class="p">(</span><span class="k">ref</span> <span class="n">expiration</span><span class="p">)</span> <span class="k">if</span> <span class="n">expiration</span><span class="py">.deadline</span> <span class="o">&lt;=</span> <span class="n">now</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="k">self</span><span class="nf">.process_expiration</span><span class="p">(</span><span class="n">expiration</span><span class="p">);</span>  <span class="c1">// ← 收割降级</span>
                <span class="k">self</span><span class="nf">.set_elapsed</span><span class="p">(</span><span class="n">expiration</span><span class="py">.deadline</span><span class="p">);</span>
            <span class="p">}</span>
            <span class="n">_</span> <span class="k">=&gt;</span> <span class="p">{</span> <span class="k">self</span><span class="nf">.set_elapsed</span><span class="p">(</span><span class="n">now</span><span class="p">);</span> <span class="k">break</span><span class="p">;</span> <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">self</span><span class="py">.pending</span><span class="nf">.pop_back</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">process_expiration</code> 先取走 slot 中所有 entry（<code class="language-plaintext highlighter-rouge">take_entries</code>），然后逐个调用 <code class="language-plaintext highlighter-rouge">mark_pending(deadline)</code><sup id="fnref:9:2"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">13</a></sup>。对每个 entry，<code class="language-plaintext highlighter-rouge">mark_pending</code> 用 <code class="language-plaintext highlighter-rouge">StateCell.state</code> 和 <code class="language-plaintext highlighter-rouge">deadline</code> 做对比：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/wheel/mod.rs: 171-196</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">process_expiration</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">expiration</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Expiration</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">entries</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.take_entries</span><span class="p">(</span><span class="n">expiration</span><span class="p">);</span>  <span class="c1">// 取出 slot 链表</span>
    <span class="k">while</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="o">=</span> <span class="n">entries</span><span class="nf">.pop_back</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">match</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="n">item</span><span class="nf">.mark_pending</span><span class="p">(</span><span class="n">expiration</span><span class="py">.deadline</span><span class="p">)</span> <span class="p">}</span> <span class="p">{</span>
            <span class="nf">Ok</span><span class="p">(())</span> <span class="k">=&gt;</span> <span class="k">self</span><span class="py">.pending</span><span class="nf">.push_front</span><span class="p">(</span><span class="n">item</span><span class="p">),</span>  <span class="c1">// 到期</span>
            <span class="nf">Err</span><span class="p">(</span><span class="n">expiration_tick</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>        <span class="c1">// 还没到</span>
                <span class="k">let</span> <span class="n">level</span> <span class="o">=</span> <span class="nf">level_for</span><span class="p">(</span><span class="n">expiration</span><span class="py">.deadline</span><span class="p">,</span> <span class="n">expiration_tick</span><span class="p">);</span>
                <span class="k">unsafe</span> <span class="p">{</span> <span class="k">self</span><span class="py">.levels</span><span class="p">[</span><span class="n">level</span><span class="p">]</span><span class="nf">.add_entry</span><span class="p">(</span><span class="n">item</span><span class="p">);</span> <span class="p">}</span>  <span class="c1">// 降级</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">item</code> 是 <code class="language-plaintext highlighter-rouge">TimerHandle</code>（即 <code class="language-plaintext highlighter-rouge">NonNull&lt;TimerShared&gt;</code>）。<code class="language-plaintext highlighter-rouge">mark_pending(262144)</code> 内部：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/entry.rs: 171-199 (StateCell::mark_pending)</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">cur_state</span> <span class="o">=</span> <span class="k">self</span><span class="py">.state</span><span class="nf">.load</span><span class="p">(</span><span class="nn">Ordering</span><span class="p">::</span><span class="n">Relaxed</span><span class="p">);</span>
<span class="c1">// cur_state = 300,000（5分钟到期）</span>
<span class="c1">// not_after = 262,144（Level 3 slot 1 deadline）</span>
<span class="k">if</span> <span class="n">cur_state</span> <span class="o">&gt;</span> <span class="n">not_after</span> <span class="p">{</span>  <span class="c1">// 300,000 &gt; 262,144 → true</span>
    <span class="k">break</span> <span class="nf">Err</span><span class="p">(</span><span class="n">cur_state</span><span class="p">);</span>   <span class="c1">// 还没到点，返回 true expiration</span>
<span class="p">}</span>
</code></pre></div></div>

<p>返回 <code class="language-plaintext highlighter-rouge">Err(300000)</code> 后，<code class="language-plaintext highlighter-rouge">TimerHandle::mark_pending</code> 把 <code class="language-plaintext highlighter-rouge">true_when</code> 写回 <code class="language-plaintext highlighter-rouge">registered_when</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/entry.rs: 250-264 (TimerHandle::mark_pending)</span>
<span class="nf">Err</span><span class="p">(</span><span class="n">tick</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="k">self</span><span class="py">.inner</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.set_registered_when</span><span class="p">(</span><span class="n">tick</span><span class="p">);</span> <span class="p">}</span>
    <span class="nf">Err</span><span class="p">(</span><span class="n">tick</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>回到 <code class="language-plaintext highlighter-rouge">process_expiration</code>，用 <code class="language-plaintext highlighter-rouge">level_for(262144, 300000)</code> 重新计算层级：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>masked = 262144 ^ 300000 | 63 = 37887
leading_zeros = 48, significant = 15, 15/6 = 2
→ Level 2, slot 9
  slot_for(300000, 2) = (300000 &gt;&gt; 12) % 64 = 9
→ add_entry: Level 2, slot[9].push_front(item)
→ timer 已从 Level 3 slot 1 移至 Level 2 slot 9
</code></pre></div></div>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Wheel.elapsed = 262,144
  Level 2: slot[9] → TimerShared (registered_when=300000, state=300000)
  Level 3: slot[1] → (空)
</code></pre></div></div>

<p><strong>t=262s ~ 298s：继续 park</strong></p>

<p><code class="language-plaintext highlighter-rouge">next_expiration_time()</code> 找到 Level 2 slot 9 的 deadline：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>slot_range(2) = 64^2 = 4,096 ms
slot 9 × 4,096 ms = 36,864 ms
Level 2 slot 9 deadline ≈ 262,144 + 36,864 = 298,...
</code></pre></div></div>

<p>线程 <code class="language-plaintext highlighter-rouge">park_timeout(~36s)</code>。</p>

<p><strong>t=298s：Level 2 slot 9 到期，降级到 Level 1</strong></p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>process_expiration(Level 2, slot 9, deadline=298xxx):
  → mark_pending(298xxx)
     StateCell.state = 300,000 &gt; 298,xxx → 还没到点
     → Err(300000)
  → level_for(298xxx, 300000) = 1
     → Level 1, slot 对应 300,000

Wheel.elapsed = 298,...
  Level 1: slot[...] → TimerShared
  Level 2: slot[9] → (空)
</code></pre></div></div>

<p><strong>t=298s ~ 300s：Level 1 到 Level 0，最终 fire</strong></p>

<p>类似逻辑继续降级到 Level 0。Level 0 slot 宽度是 1ms：</p>

<p>当 <code class="language-plaintext highlighter-rouge">elapsed &gt;= 300,000</code> 时，Level 0 到期。<code class="language-plaintext highlighter-rouge">process_expiration</code> 再次被调用，但这次是 Level 0，<code class="language-plaintext highlighter-rouge">mark_pending</code> 中的判断不同了：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/entry.rs: 171-199 (StateCell::mark_pending)</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">cur_state</span> <span class="o">=</span> <span class="k">self</span><span class="py">.state</span><span class="nf">.load</span><span class="p">(</span><span class="nn">Ordering</span><span class="p">::</span><span class="n">Relaxed</span><span class="p">);</span>
<span class="c1">// cur_state = 300,000</span>
<span class="c1">// not_after = 300,000（Level 0 slot deadline）</span>
<span class="k">if</span> <span class="n">cur_state</span> <span class="o">&gt;</span> <span class="n">not_after</span> <span class="p">{</span>  <span class="c1">// 300,000 &gt; 300,000 → false</span>
    <span class="k">break</span> <span class="nf">Err</span><span class="p">(</span><span class="n">cur_state</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// cur_state ≤ not_after → 到期！</span>
<span class="nf">compare_exchange</span><span class="p">(</span><span class="n">cur_state</span><span class="p">,</span> <span class="n">STATE_PENDING_FIRE</span><span class="p">,</span> <span class="n">AcqRel</span><span class="p">,</span> <span class="n">Acquire</span><span class="p">)</span>
</code></pre></div></div>

<p>CAS 成功将状态从 <code class="language-plaintext highlighter-rouge">300000</code> 置为 <code class="language-plaintext highlighter-rouge">STATE_PENDING_FIRE</code>，返回 <code class="language-plaintext highlighter-rouge">Ok(())</code>，entry 被推入 <code class="language-plaintext highlighter-rouge">self.pending</code>。随后 <code class="language-plaintext highlighter-rouge">Wheel::poll()</code> 的 <code class="language-plaintext highlighter-rouge">loop</code> 下一次迭代从 <code class="language-plaintext highlighter-rouge">pending</code> 弹出这个 entry，<code class="language-plaintext highlighter-rouge">process_at_time</code> 拿到后调用 <code class="language-plaintext highlighter-rouge">fire()</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/entry.rs: 211-231 (StateCell::fire)</span>
<span class="k">let</span> <span class="n">cur_state</span> <span class="o">=</span> <span class="k">self</span><span class="py">.state</span><span class="nf">.load</span><span class="p">(</span><span class="nn">Ordering</span><span class="p">::</span><span class="n">Relaxed</span><span class="p">);</span>
<span class="k">if</span> <span class="n">cur_state</span> <span class="o">==</span> <span class="n">STATE_DEREGISTERED</span> <span class="p">{</span> <span class="k">return</span> <span class="nb">None</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// 不为 u64::MAX</span>

<span class="c1">// 写入结果</span>
<span class="k">unsafe</span> <span class="p">{</span> <span class="k">self</span><span class="py">.result</span><span class="nf">.with_mut</span><span class="p">(|</span><span class="n">p</span><span class="p">|</span> <span class="o">*</span><span class="n">p</span> <span class="o">=</span> <span class="nf">Ok</span><span class="p">(()))</span> <span class="p">};</span>
<span class="c1">// state → STATE_DEREGISTERED（u64::MAX）</span>
<span class="k">self</span><span class="py">.state</span><span class="nf">.store</span><span class="p">(</span><span class="n">STATE_DEREGISTERED</span><span class="p">,</span> <span class="nn">Ordering</span><span class="p">::</span><span class="n">Release</span><span class="p">);</span>

<span class="c1">// 取出 waker 返回</span>
<span class="k">self</span><span class="py">.waker</span><span class="nf">.take_waker</span><span class="p">()</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">fire</code> 返回 <code class="language-plaintext highlighter-rouge">Some(waker)</code>，<code class="language-plaintext highlighter-rouge">process_at_time</code> 将其压入 <code class="language-plaintext highlighter-rouge">WakeList</code>，释放锁后批量 <code class="language-plaintext highlighter-rouge">wake_all()</code>——任务被放回调度队列，下次 <code class="language-plaintext highlighter-rouge">Sleep::poll()</code> 返回 <code class="language-plaintext highlighter-rouge">Poll::Ready(())</code>。</p>

<p><strong>如果用 <code class="language-plaintext highlighter-rouge">reset()</code> 延长 deadline 呢？</strong></p>

<p>假设在第 1 分钟时，用户调用了 <code class="language-plaintext highlighter-rouge">sleep.reset(new_deadline=10min)</code>：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TimerEntry::reset(10min)
  → tick = 600,000
  → extend_expiration(600000)
     StateCell.state 从 300,000 CAS 到 600,000 ✓
     → 成功，return（跳过 reregister！）
</code></pre></div></div>

<p>此时 timer 还在 Level 3 slot 1 里，<code class="language-plaintext highlighter-rouge">registered_when</code> 仍然是 300,000。<strong>没有任何链表操作</strong>——这就是”乐观延后”的零锁优势。</p>

<p>当 Driver 在 <code class="language-plaintext highlighter-rouge">elapsed=262144</code> 时收割 Level 3 slot 1：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mark_pending(262144):
  StateCell.state = 600,000 &gt; 262,144
  → Err(600000)
  → 把 true_when(600000) 同步回 registered_when
  → 按 600,000 重新计算层级
     level_for(262144, 600000) = 更高的层级...
  → 插入到正确的 slot
</code></pre></div></div>

<p>整个过程零锁争用——<code class="language-plaintext highlighter-rouge">extend_expiration</code> 是 CAS，<code class="language-plaintext highlighter-rouge">mark_pending</code> 也是 CAS，没有 Mutex。</p>

<h3 id="33-elapsed-是如何维护的">3.3 elapsed 是如何维护的？</h3>

<p>一个关键的设计细节是：<strong><code class="language-plaintext highlighter-rouge">elapsed</code> 不依赖任何外部定时器来推进</strong>。它是 Driver 在每次 <code class="language-plaintext highlighter-rouge">process_at_time(now)</code> 时，用 <code class="language-plaintext highlighter-rouge">now</code> 和 <code class="language-plaintext highlighter-rouge">elapsed</code> 的差值来”推”的。</p>

<p>每次 Driver 醒来（无论是被 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 超时叫醒，还是被 I/O 事件提前 unpark），都会调用 <code class="language-plaintext highlighter-rouge">handle.process(clock)</code>，内部是 <code class="language-plaintext highlighter-rouge">process_at_time(now)</code>。Wheel 收到 <code class="language-plaintext highlighter-rouge">now</code>（<code class="language-plaintext highlighter-rouge">u64</code> tick，来自 <code class="language-plaintext highlighter-rouge">TimeSource</code>），和当前的 <code class="language-plaintext highlighter-rouge">elapsed</code> 比较差值，把落在差值内的 slot 逐个处理——收割到期 timer、降级上层 timer。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Driver 被 I/O 事件提前叫醒（本应在 5 秒后才醒）：
  → process_at_time(now=1003)    // 只走了 3ms
  → Wheel: 从 elapsed=1000 到 now=1003
           第 1000 slot 收割 → 无到期 timer
           第 1001-1002 slot 无内容
           elapsed = 1003
  → 所有 timer 都还在，下次 park_timeout 重新计算

Driver 在 deadline 时被叫醒：
  → process_at_time(now=6000)
  → Wheel: 从 elapsed=1000 到 now=6000
           第 1000、1001、…、5000 slot 逐个处理
           → elapsed=5000 时有到期 timer → fire → wake
           → 继续推进到 6000
           elapsed = 6000
  → 重新计算 next_wake
</code></pre></div></div>

<p>所以时间轮是完全<strong>自洽</strong>的：它不和”墙上时间”绑定，只和 <code class="language-plaintext highlighter-rouge">elapsed</code> 这个单调递增的计数器绑定。<code class="language-plaintext highlighter-rouge">TimeSource</code> 的 <code class="language-plaintext highlighter-rouge">clock.now()</code> 只用来算增量——增量多大，<code class="language-plaintext highlighter-rouge">elapsed</code> 就推多远。即使系统被暂停（suspend-to-RAM），恢复后 <code class="language-plaintext highlighter-rouge">clock.now()</code> 返回更新后的时间，增量包含暂停时长，<code class="language-plaintext highlighter-rouge">elapsed</code> 一次推到位，所有”超期的 timer”被立即收割。</p>

<p><strong>tokio 不依赖任何外部定时器来推进时间轮</strong>。外部定时器（<code class="language-plaintext highlighter-rouge">epoll_wait</code> 的 timeout）的唯一作用是”到点叫醒线程”——醒来后 tokio 用自己的 <code class="language-plaintext highlighter-rouge">TimeSource</code> 计算增量、推进 <code class="language-plaintext highlighter-rouge">elapsed</code>、收割到期 timer。这就是为什么 <code class="language-plaintext highlighter-rouge">park_timeout(0)</code> 也能工作：即使不进入内核定时器，<code class="language-plaintext highlighter-rouge">process_at_time</code> 也能用 <code class="language-plaintext highlighter-rouge">now = TimeSource::now()</code> 推进 Wheel。</p>

<h2 id="四statecell原子状态机">四、StateCell：原子状态机</h2>

<p><code class="language-plaintext highlighter-rouge">TimerShared</code> 中最关键的部分是 <code class="language-plaintext highlighter-rouge">StateCell</code>——一个用原子变量实现的状态机，负责协调用户端（<code class="language-plaintext highlighter-rouge">TimerEntry</code>）和驱动端（<code class="language-plaintext highlighter-rouge">Driver</code>）之间的并发访问<sup id="fnref:10"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">15</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/entry.rs: 91-102</span>
<span class="k">pub</span><span class="p">(</span><span class="k">super</span><span class="p">)</span> <span class="k">struct</span> <span class="n">StateCell</span> <span class="p">{</span>
    <span class="c1">// 保存到期时间，或特殊标志值 u64::MAX（已注销）</span>
    <span class="n">state</span><span class="p">:</span> <span class="n">AtomicU64</span><span class="p">,</span>

    <span class="c1">// 定时器触发后的结果</span>
    <span class="n">result</span><span class="p">:</span> <span class="n">UnsafeCell</span><span class="o">&lt;</span><span class="n">TimerResult</span><span class="o">&gt;</span><span class="p">,</span>

    <span class="c1">// 已注册的 Waker</span>
    <span class="n">waker</span><span class="p">:</span> <span class="n">AtomicWaker</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">state</code> 字段用单个 <code class="language-plaintext highlighter-rouge">AtomicU64</code> 同时承载”到期时间”和”状态标记”——它是一个 <strong>tagged union</strong>，利用值域范围区分含义：</p>

<pre><code class="language-mermaid">stateDiagram-v2
    [*] --&gt; Deregistered
    Deregistered --&gt; Scheduled: set_expiration(tick)
    Scheduled --&gt; PendingFire: mark_pending() 成功
    PendingFire --&gt; Deregistered: fire(result)
    Scheduled --&gt; Deregistered: 直接 fire (注)
    Deregistered --&gt; Scheduled: 重新注册 (set_expiration)
</code></pre>

<blockquote>
  <p>（注）包括 <code class="language-plaintext highlighter-rouge">clear_entry(cancel/drop)</code> → <code class="language-plaintext highlighter-rouge">fire</code>、<code class="language-plaintext highlighter-rouge">reregister</code> 检测到 <code class="language-plaintext highlighter-rouge">InsertError::Elapsed</code> 时直接 fire、runtime shutdown 时 <code class="language-plaintext highlighter-rouge">fire(Err(…))</code> 等路径。所有路径最终都通过 <code class="language-plaintext highlighter-rouge">fire()</code> 进入 <code class="language-plaintext highlighter-rouge">Deregistered</code>——<code class="language-plaintext highlighter-rouge">StateCell</code> 层面不存在独立的 “Cancelled” 状态，取消和正常到期共享同一个终端状态。</p>
</blockquote>

<p>三个离散状态的编码定义在 <code class="language-plaintext highlighter-rouge">entry.rs</code> 顶部（L72-78）：</p>

<table>
  <thead>
    <tr>
      <th>状态</th>
      <th><code class="language-plaintext highlighter-rouge">state</code> 的 <code class="language-plaintext highlighter-rouge">u64</code> 取值</th>
      <th>含义</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Scheduled（已调度）</strong></td>
      <td><code class="language-plaintext highlighter-rouge">[0, MAX_SAFE_MILLIS_DURATION]</code>，即 <code class="language-plaintext highlighter-rouge">&lt; STATE_MIN_VALUE</code> 的任意值</td>
      <td>定时器在时间轮中等待；<code class="language-plaintext highlighter-rouge">state</code> 存储的就是具体的<strong>到期 tick</strong></td>
    </tr>
    <tr>
      <td><strong>PendingFire（待触发）</strong></td>
      <td><code class="language-plaintext highlighter-rouge">STATE_PENDING_FIRE = u64::MAX - 1</code></td>
      <td>已被驱动从时间轮取出，放入 <code class="language-plaintext highlighter-rouge">pending</code> 链表，即将触发</td>
    </tr>
    <tr>
      <td><strong>Deregistered（已触发/已注销）</strong></td>
      <td><code class="language-plaintext highlighter-rouge">STATE_DEREGISTERED = u64::MAX</code></td>
      <td>已触发或已注销；结果已写入 <code class="language-plaintext highlighter-rouge">result</code> 字段，<code class="language-plaintext highlighter-rouge">poll()</code> 返回 <code class="language-plaintext highlighter-rouge">Ready(result)</code></td>
    </tr>
  </tbody>
</table>

<p>其中 <strong>Scheduled 是”胖状态”</strong>——state 可以是 <code class="language-plaintext highlighter-rouge">[0, MAX_SAFE_MILLIS_DURATION]</code> 之间的任意值，编码了具体的到期时间。而 PendingFire 和 Deregistered 是纯标记，不携带额外信息。这种设计的精妙之处在于：</p>

<ol>
  <li><strong>原子性</strong>——<code class="language-plaintext highlighter-rouge">compare_exchange_weak</code> 可以一次原子操作、”检查到期时间 + 标记状态转换”。例如 <code class="language-plaintext highlighter-rouge">mark_pending()</code> 中的 CAS 同时验证 <code class="language-plaintext highlighter-rouge">state ≤ not_after</code> 并将值替换为 <code class="language-plaintext highlighter-rouge">STATE_PENDING_FIRE</code>。</li>
  <li><strong>无锁乐观更新</strong>——<code class="language-plaintext highlighter-rouge">extend_expiration()</code> 用 CAS 尝试把到期时间往后推；如果 <code class="language-plaintext highlighter-rouge">state</code> 已被驱动改成 <code class="language-plaintext highlighter-rouge">STATE_DEREGISTERED</code>，CAS 失败，调用者就知道需要走完整的重新注册路径，无需先取锁。</li>
  <li><strong>隐式 Release-Acquire 同步</strong>——<code class="language-plaintext highlighter-rouge">fire()</code> 先写 <code class="language-plaintext highlighter-rouge">result</code> 再以 <code class="language-plaintext highlighter-rouge">Release</code> 顺序写 <code class="language-plaintext highlighter-rouge">state = STATE_DEREGISTERED</code>；<code class="language-plaintext highlighter-rouge">read_state()</code> 以 <code class="language-plaintext highlighter-rouge">Acquire</code> 顺序读 <code class="language-plaintext highlighter-rouge">state</code>，保证读到 <code class="language-plaintext highlighter-rouge">STATE_DEREGISTERED</code> 时 <code class="language-plaintext highlighter-rouge">result</code> 一定已写入完毕，零额外栅栏。</li>
</ol>

<p>当前状态标记只占用了 <code class="language-plaintext highlighter-rouge">u64</code> 顶部 2 个值：</p>

<table>
  <thead>
    <tr>
      <th>取值</th>
      <th>状态</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">u64::MAX - 1 = 0xFFFF_FFFF_FFFF_FFFE</code></td>
      <td><code class="language-plaintext highlighter-rouge">STATE_PENDING_FIRE</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">u64::MAX = 0xFFFF_FFFF_FFFF_FFFF</code></td>
      <td><code class="language-plaintext highlighter-rouge">STATE_DEREGISTERED</code></td>
    </tr>
  </tbody>
</table>

<p>但预留边界的定义很灵活：<code class="language-plaintext highlighter-rouge">STATE_MIN_VALUE</code> 被定义为 <code class="language-plaintext highlighter-rouge">STATE_PENDING_FIRE</code>，加上注释 <code class="language-plaintext highlighter-rouge">// This value should be updated if any other signal values are added above.</code> 表明，只需把 <code class="language-plaintext highlighter-rouge">STATE_MIN_VALUE</code> 减 1，就能让 <code class="language-plaintext highlighter-rouge">u64::MAX - 2</code> 成为第 3 个可用信号值。整个 <code class="language-plaintext highlighter-rouge">u64</code> 值域中大于 <code class="language-plaintext highlighter-rouge">MAX_SAFE_MILLIS_DURATION</code>（<code class="language-plaintext highlighter-rouge">0xFFFF_FFFF_FFFF_FFFD</code>）的部分都是预留空间，目前只占用了顶部 2 个，扩展空间充裕。</p>

<p>事实上 <code class="language-plaintext highlighter-rouge">MAX_SAFE_MILLIS_DURATION ≈ 18.4 × 10¹⁸</code> 个 tick，按 tokio 默认的毫秒粒度约 <strong>5.8 亿年</strong>，时间表达范围远超实际需求，划顶部几个值给状态标记几乎不影响可用 tick 空间。</p>

<blockquote>
  <p><strong>驱动端的两段式处理</strong>：<code class="language-plaintext highlighter-rouge">PendingFire</code> 由 <code class="language-plaintext highlighter-rouge">process_expiration</code> 里的 <code class="language-plaintext highlighter-rouge">mark_pending</code> 通过 CAS 写入；<code class="language-plaintext highlighter-rouge">Deregistered</code> 由 <code class="language-plaintext highlighter-rouge">park_internal</code> 醒来后的 <code class="language-plaintext highlighter-rouge">process_at_time</code> → <code class="language-plaintext highlighter-rouge">wheel.poll</code> → <code class="language-plaintext highlighter-rouge">fire</code> 写入。下文分述三个原子操作，注意 <strong><code class="language-plaintext highlighter-rouge">mark_pending</code> 与 <code class="language-plaintext highlighter-rouge">fire</code> 不在同一调用栈</strong>。</p>
</blockquote>

<p>几个关键操作的实现：</p>

<p><strong><code class="language-plaintext highlighter-rouge">poll</code>——用户端检查是否到期</strong><sup id="fnref:10:1"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">15</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/entry.rs: 142-162</span>
<span class="k">fn</span> <span class="nf">poll</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">waker</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Waker</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Poll</span><span class="o">&lt;</span><span class="n">TimerResult</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// 先注册 waker，确保 fire 和 poll 之间的竞争不会丢失唤醒</span>
    <span class="k">self</span><span class="py">.waker</span><span class="nf">.register_by_ref</span><span class="p">(</span><span class="n">waker</span><span class="p">);</span>
    <span class="c1">// 读取状态</span>
    <span class="k">self</span><span class="nf">.read_state</span><span class="p">()</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">read_state</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Poll</span><span class="o">&lt;</span><span class="n">TimerResult</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">cur_state</span> <span class="o">=</span> <span class="k">self</span><span class="py">.state</span><span class="nf">.load</span><span class="p">(</span><span class="nn">Ordering</span><span class="p">::</span><span class="n">Acquire</span><span class="p">);</span>
    <span class="k">if</span> <span class="n">cur_state</span> <span class="o">==</span> <span class="n">STATE_DEREGISTERED</span> <span class="p">{</span>
        <span class="c1">// 已经触发，读取 result</span>
        <span class="nn">Poll</span><span class="p">::</span><span class="nf">Ready</span><span class="p">(</span><span class="k">unsafe</span> <span class="p">{</span> <span class="k">self</span><span class="py">.result</span><span class="nf">.with</span><span class="p">(|</span><span class="n">p</span><span class="p">|</span> <span class="o">*</span><span class="n">p</span><span class="p">)</span> <span class="p">})</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nn">Poll</span><span class="p">::</span><span class="n">Pending</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>注意 <code class="language-plaintext highlighter-rouge">poll</code> 里先注册 waker 再读状态的顺序——如果反过来，可能会发生在读状态之后、注册 waker 之前这个窗口期内定时器恰好触发并调用 <code class="language-plaintext highlighter-rouge">fire</code>，从而导致任务永久丢失唤醒信号。</p>

<p><strong><code class="language-plaintext highlighter-rouge">mark_pending</code>——驱动端将定时器移入待触发队列</strong><sup id="fnref:10:2"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">15</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/entry.rs: 171-199</span>
<span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">mark_pending</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">not_after</span><span class="p">:</span> <span class="nb">u64</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="nb">u64</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">cur_state</span> <span class="o">=</span> <span class="k">self</span><span class="py">.state</span><span class="nf">.load</span><span class="p">(</span><span class="nn">Ordering</span><span class="p">::</span><span class="n">Relaxed</span><span class="p">);</span>
    <span class="k">loop</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">cur_state</span> <span class="o">&gt;</span> <span class="n">not_after</span> <span class="p">{</span>
            <span class="c1">// 实际到期时间比当前 tick 晚——不应该触发</span>
            <span class="k">break</span> <span class="nf">Err</span><span class="p">(</span><span class="n">cur_state</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">match</span> <span class="k">self</span><span class="py">.state</span><span class="nf">.compare_exchange_weak</span><span class="p">(</span>
            <span class="n">cur_state</span><span class="p">,</span>
            <span class="n">STATE_PENDING_FIRE</span><span class="p">,</span>
            <span class="nn">Ordering</span><span class="p">::</span><span class="n">AcqRel</span><span class="p">,</span>
            <span class="nn">Ordering</span><span class="p">::</span><span class="n">Acquire</span><span class="p">,</span>
        <span class="p">)</span> <span class="p">{</span>
            <span class="nf">Ok</span><span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="k">break</span> <span class="nf">Ok</span><span class="p">(()),</span>
            <span class="nf">Err</span><span class="p">(</span><span class="n">actual_state</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="n">cur_state</span> <span class="o">=</span> <span class="n">actual_state</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这里使用 CAS（Compare-And-Swap）来原子地将定时器从”已调度”状态转换到”待触发”状态。CAS 失败意味着有人并发修改了状态（比如用户端重置了定时器），需要重试。</p>

<p><code class="language-plaintext highlighter-rouge">TimerHandle::mark_pending</code> 在成功时还会把 <code class="language-plaintext highlighter-rouge">registered_when</code> 标成 <code class="language-plaintext highlighter-rouge">STATE_DEREGISTERED</code>，表示该 entry 已脱离轮子链表、挂在 <code class="language-plaintext highlighter-rouge">Wheel.pending</code> 上——此时<strong>尚未</strong>写入 <code class="language-plaintext highlighter-rouge">result</code>，也<strong>尚未</strong>取出 waker。调用方（<code class="language-plaintext highlighter-rouge">process_expiration</code>）负责 <code class="language-plaintext highlighter-rouge">pending.push_front(item)</code>；<strong>此处不会调用 <code class="language-plaintext highlighter-rouge">fire</code></strong>。</p>

<p><strong><code class="language-plaintext highlighter-rouge">fire</code>——完成定时器</strong>（由 <code class="language-plaintext highlighter-rouge">Handle::process_at_time</code> 调用）<sup id="fnref:10:3"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">15</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/entry.rs: 211-231</span>
<span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">fire</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">result</span><span class="p">:</span> <span class="n">TimerResult</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Waker</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">cur_state</span> <span class="o">=</span> <span class="k">self</span><span class="py">.state</span><span class="nf">.load</span><span class="p">(</span><span class="nn">Ordering</span><span class="p">::</span><span class="n">Relaxed</span><span class="p">);</span>
    <span class="k">if</span> <span class="n">cur_state</span> <span class="o">==</span> <span class="n">STATE_DEREGISTERED</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nb">None</span><span class="p">;</span>  <span class="c1">// 已经触发过了</span>
    <span class="p">}</span>

    <span class="c1">// 写入结果</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="k">self</span><span class="py">.result</span><span class="nf">.with_mut</span><span class="p">(|</span><span class="n">p</span><span class="p">|</span> <span class="o">*</span><span class="n">p</span> <span class="o">=</span> <span class="n">result</span><span class="p">)</span> <span class="p">};</span>
    <span class="c1">// 标记为已注销</span>
    <span class="k">self</span><span class="py">.state</span><span class="nf">.store</span><span class="p">(</span><span class="n">STATE_DEREGISTERED</span><span class="p">,</span> <span class="nn">Ordering</span><span class="p">::</span><span class="n">Release</span><span class="p">);</span>

    <span class="c1">// 取出之前注册的 waker 以便唤醒</span>
    <span class="k">self</span><span class="py">.waker</span><span class="nf">.take_waker</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">fire</code> 本身只写 <code class="language-plaintext highlighter-rouge">result</code>、置 <code class="language-plaintext highlighter-rouge">STATE_DEREGISTERED</code> 并取出 waker；<strong>谁在何时调用它</strong>由驱动层的 <code class="language-plaintext highlighter-rouge">process_at_time</code> 决定：</p>

<h4 id="为什么-fire-用普通-store-而不是-cas">为什么 <code class="language-plaintext highlighter-rouge">fire()</code> 用普通 store 而不是 CAS？</h4>

<p>你可能会注意到，<code class="language-plaintext highlighter-rouge">fire()</code> 修改 <code class="language-plaintext highlighter-rouge">state</code> 时用的是普通的 <code class="language-plaintext highlighter-rouge">store(Release)</code>，而不是 CAS。这和其他方法（如 <code class="language-plaintext highlighter-rouge">mark_pending()</code>、<code class="language-plaintext highlighter-rouge">extend_expiration()</code>）形成对比——为什么呢？</p>

<p>关键原因：<strong><code class="language-plaintext highlighter-rouge">fire()</code> 被调用时，已经没有并发写入者了。</strong></p>

<p>定时器触发的完整路径是<strong>两阶段</strong>的：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mark_pending()                                    fire()
    │                                               │
    ├─ CAS: tick → STATE_PENDING_FIRE               ├─ store: PENDING_FIRE → DEREGISTERED
    │  (条件更新：只有 state 还是原 tick 才改)        │  (确定性更新：state 一定是 PENDING_FIRE)
    │                                               │
    └─ 入 pending 链表                              └─ 写 result、取 waker
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">mark_pending()</code> 已经用 CAS 把 <code class="language-plaintext highlighter-rouge">state</code> 从具体的 tick 值原子地转换成了 <code class="language-plaintext highlighter-rouge">STATE_PENDING_FIRE</code>（<code class="language-plaintext highlighter-rouge">u64::MAX - 1</code>，即 <code class="language-plaintext highlighter-rouge">≥ STATE_MIN_VALUE</code>）。而 <code class="language-plaintext highlighter-rouge">extend_expiration()</code>（用户线程调用的无锁乐观更新）的保护逻辑是：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">extend_expiration</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">new_timestamp</span><span class="p">:</span> <span class="nb">u64</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">prior</span> <span class="o">=</span> <span class="k">self</span><span class="py">.state</span><span class="nf">.load</span><span class="p">(</span><span class="nn">Ordering</span><span class="p">::</span><span class="n">Relaxed</span><span class="p">);</span>
    <span class="k">loop</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">new_timestamp</span> <span class="o">&lt;</span> <span class="n">prior</span> <span class="p">||</span> <span class="n">prior</span> <span class="o">&gt;=</span> <span class="n">STATE_MIN_VALUE</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nf">Err</span><span class="p">(());</span>  <span class="c1">// state 已被驱动改成了 PENDING_FIRE → 走慢路径</span>
        <span class="p">}</span>
        <span class="c1">// CAS 尝试更新</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>一旦 <code class="language-plaintext highlighter-rouge">state</code> 变成 <code class="language-plaintext highlighter-rouge">STATE_PENDING_FIRE</code>（<code class="language-plaintext highlighter-rouge">≥ STATE_MIN_VALUE</code>），<code class="language-plaintext highlighter-rouge">extend_expiration()</code> 会因 <code class="language-plaintext highlighter-rouge">prior &gt;= STATE_MIN_VALUE</code> 立即返回 <code class="language-plaintext highlighter-rouge">Err</code>，转而走需要获取驱动锁的重新注册路径。所以当 <code class="language-plaintext highlighter-rouge">fire()</code> 执行时，<strong>可能的并发写入者已经全部退出了</strong>——<code class="language-plaintext highlighter-rouge">fire()</code> 对 <code class="language-plaintext highlighter-rouge">state</code> 拥有独占访问权，一个普通的 <code class="language-plaintext highlighter-rouge">store(Release)</code> 就足够了。</p>

<table>
  <thead>
    <tr>
      <th>场景</th>
      <th><code class="language-plaintext highlighter-rouge">mark_pending</code> / <code class="language-plaintext highlighter-rouge">extend_expiration</code></th>
      <th><code class="language-plaintext highlighter-rouge">fire</code></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>是否有并发写入者</td>
      <td>✅ 是（用户线程可能同时 <code class="language-plaintext highlighter-rouge">extend_expiration</code>）</td>
      <td>❌ 否（<code class="language-plaintext highlighter-rouge">extend_expiration</code> 已因 state ≠ tick 而退出）</td>
    </tr>
    <tr>
      <td>需要的语义</td>
      <td>条件性更新：”只有 state 还是我期望的值才改”</td>
      <td>确定性更新：”state 一定是 PENDING_FIRE，直接写 DEREGISTERED”</td>
    </tr>
    <tr>
      <td>使用的原子操作</td>
      <td><code class="language-plaintext highlighter-rouge">compare_exchange_weak</code></td>
      <td><code class="language-plaintext highlighter-rouge">store(Release)</code></td>
    </tr>
  </tbody>
</table>

<p>行首的防御性检查 <code class="language-plaintext highlighter-rouge">if cur_state == STATE_DEREGISTERED { return None; }</code> 只是防止 <code class="language-plaintext highlighter-rouge">fire()</code> 被意外重复调用的安全网——正常情况下走到这里的 state 一定是 <code class="language-plaintext highlighter-rouge">STATE_PENDING_FIRE</code>。</p>

<p><strong>总结</strong>：<code class="language-plaintext highlighter-rouge">mark_pending()</code> 的 CAS 相当于”加锁获取所有权”，<code class="language-plaintext highlighter-rouge">fire()</code> 的 store 相当于”持有所有权时直接写入”。两阶段设计让每个操作使用最合适的原子原语，既保证正确性又避免不必要的 CAS 开销。</p>

<p><strong><code class="language-plaintext highlighter-rouge">process_at_time</code>——从 <code class="language-plaintext highlighter-rouge">pending</code> 弹出并 <code class="language-plaintext highlighter-rouge">fire</code></strong><sup id="fnref:11"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">16</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/mod.rs: 296-337（核心循环，略去时钟回拨处理）</span>
<span class="k">pub</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">process_at_time</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="k">mut</span> <span class="n">now</span><span class="p">:</span> <span class="nb">u64</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">waker_list</span> <span class="o">=</span> <span class="nn">WakeList</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">lock</span> <span class="o">=</span> <span class="k">self</span><span class="py">.inner</span><span class="nf">.lock</span><span class="p">();</span>

    <span class="k">while</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">entry</span><span class="p">)</span> <span class="o">=</span> <span class="n">lock</span><span class="py">.wheel</span><span class="nf">.poll</span><span class="p">(</span><span class="n">now</span><span class="p">)</span> <span class="p">{</span>
        <span class="nd">debug_assert!</span><span class="p">(</span><span class="k">unsafe</span> <span class="p">{</span> <span class="n">entry</span><span class="nf">.is_pending</span><span class="p">()</span> <span class="p">});</span>

        <span class="c1">// 持 driver 锁；entry 已从轮子链表 / pending 中移除</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">waker</span><span class="p">)</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="n">entry</span><span class="nf">.fire</span><span class="p">(</span><span class="nf">Ok</span><span class="p">(()))</span> <span class="p">}</span> <span class="p">{</span>
            <span class="n">waker_list</span><span class="nf">.push</span><span class="p">(</span><span class="n">waker</span><span class="p">);</span>

            <span class="k">if</span> <span class="o">!</span><span class="n">waker_list</span><span class="nf">.can_push</span><span class="p">()</span> <span class="p">{</span>
                <span class="nf">drop</span><span class="p">(</span><span class="n">lock</span><span class="p">);</span>           <span class="c1">// 避免持锁 wake 死锁</span>
                <span class="n">waker_list</span><span class="nf">.wake_all</span><span class="p">();</span>
                <span class="n">lock</span> <span class="o">=</span> <span class="k">self</span><span class="py">.inner</span><span class="nf">.lock</span><span class="p">();</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="n">lock</span><span class="py">.next_wake</span> <span class="o">=</span> <span class="n">lock</span><span class="py">.wheel</span><span class="nf">.poll_at</span><span class="p">()</span><span class="nf">.map</span><span class="p">(</span><span class="cm">/* ... */</span><span class="p">);</span>
    <span class="nf">drop</span><span class="p">(</span><span class="n">lock</span><span class="p">);</span>
    <span class="n">waker_list</span><span class="nf">.wake_all</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">wheel.poll(now)</code> 每次迭代<strong>优先</strong> <code class="language-plaintext highlighter-rouge">pending.pop_back()</code>；若 <code class="language-plaintext highlighter-rouge">pending</code> 为空，才在内部调 <code class="language-plaintext highlighter-rouge">process_expiration</code>（其中的 <code class="language-plaintext highlighter-rouge">mark_pending</code> 可能 newly 推入 <code class="language-plaintext highlighter-rouge">pending</code>，下一轮循环即弹出）。因此 <code class="language-plaintext highlighter-rouge">mark_pending</code> 与 <code class="language-plaintext highlighter-rouge">entry.fire</code> 同属一次 <code class="language-plaintext highlighter-rouge">process_at_time</code> 调用，但分属 <code class="language-plaintext highlighter-rouge">Wheel</code> 与 <code class="language-plaintext highlighter-rouge">Handle</code> 两层——上文的 <code class="language-plaintext highlighter-rouge">StateCell::fire</code> 正是被这里的 <code class="language-plaintext highlighter-rouge">entry.fire(Ok(()))</code> 调用。</p>

<p>触发入口：<code class="language-plaintext highlighter-rouge">Driver::park_internal</code> 在 <code class="language-plaintext highlighter-rouge">park</code> / <code class="language-plaintext highlighter-rouge">park_timeout</code> 返回后执行 <code class="language-plaintext highlighter-rouge">handle.process(clock)</code> → <code class="language-plaintext highlighter-rouge">process_at_time(now)</code>（§3.2 走读、§5 有更完整的 <code class="language-plaintext highlighter-rouge">park_internal</code> 上下文）。</p>

<p>少数路径会<strong>跳过 <code class="language-plaintext highlighter-rouge">mark_pending</code></strong>、在持锁下直接 <code class="language-plaintext highlighter-rouge">fire</code>：例如 <code class="language-plaintext highlighter-rouge">reregister</code> 时 <code class="language-plaintext highlighter-rouge">insert</code> 返回 <code class="language-plaintext highlighter-rouge">InsertError::Elapsed</code>，或 <code class="language-plaintext highlighter-rouge">clear_entry</code> 取消已注册 timer。</p>

<h3 id="41-为什么每个-timershared-独立拥有-statecell">4.1 为什么每个 TimerShared 独立拥有 StateCell？</h3>

<p>理解 StateCell 之后，一个自然的问题是：<strong>为什么每个 <code class="language-plaintext highlighter-rouge">TimerShared</code>（即每个 timer）都独享自己的 <code class="language-plaintext highlighter-rouge">StateCell</code>？能不能多个 timer 共享一个？</strong></p>

<p>答案是否定的。因为”一个 timer 的 <code class="language-plaintext highlighter-rouge">poll</code> 和另一个 timer 的 <code class="language-plaintext highlighter-rouge">fire</code> 之间的并发”和”同一个 timer 的 <code class="language-plaintext highlighter-rouge">poll</code> 和 <code class="language-plaintext highlighter-rouge">fire</code> 之间的并发”性质完全不同：</p>

<ul>
  <li><strong>不同 timer 之间</strong>：没有状态依赖——task A 的到期不影响 task B</li>
  <li><strong>同一 timer 之内</strong>：<code class="language-plaintext highlighter-rouge">poll</code>（用户端）和 <code class="language-plaintext highlighter-rouge">fire</code>（驱动端）同时对同一个状态 CAS——一旦共享，就会把隔离开的并发变成全局争用</li>
</ul>

<p>如果所有 timer 共享一个 StateCell，那就是一把全局锁的缩影——task A 的 <code class="language-plaintext highlighter-rouge">poll()</code> 和 task B 的 <code class="language-plaintext highlighter-rouge">poll()</code> 也要争同一个原子变量，毫无隔离性可言。</p>

<p>所以模式是：<strong>每个 timer 的并发边界是单 timer 粒度的</strong>，独立 StateCell 是最自然的选择。</p>

<h4 id="如果没有-statecell-会怎样">如果没有 StateCell 会怎样？</h4>

<p>三个具体问题，按严重程度排列：</p>

<p><strong>1. 丢失唤醒（lost wakeup）——最致命</strong></p>

<p>用传统 <code class="language-plaintext highlighter-rouge">Mutex&lt;bool&gt;</code> 替代 StateCell 的话，存在一个经典的 TOCTOU（Time-Of-Check to Time-Of-Use）窗口：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>时间线:
  Driver 线程:   检查到期 → 设 flag=true → waker.wake()
                    ↑
  Task 线程:     poll() → 检查 flag=false → 注册 waker → 返回 Pending
</code></pre></div></div>

<p>Task 线程读 flag（false）和注册 waker 之间插入了 Driver 线程：flag 被设为 true，wake 被调用——但 Task 已经检查了 flag 为 false，即将注册 waker 后返回 Pending。这个 wake 永久丢失，任务永远醒不过来。</p>

<p>StateCell 用 <code class="language-plaintext highlighter-rouge">AtomicU64</code> + 先注册 waker 再读状态的顺序消除这个窗口：即使在写入 waker 后、读状态前 Driver 调用了 <code class="language-plaintext highlighter-rouge">fire</code>，<code class="language-plaintext highlighter-rouge">read_state()</code> 读到 <code class="language-plaintext highlighter-rouge">STATE_DEREGISTERED</code> 后直接返回 Ready，不会丢失。</p>

<p><strong>2. 重复触发（double fire）</strong></p>

<p>没有原子状态区分”正在 fire”和”已被取消”——Driver 可能在一个被 <code class="language-plaintext highlighter-rouge">cancel()</code> 的 timer 上再次调用 <code class="language-plaintext highlighter-rouge">waker.wake()</code>，造成任务被错误唤醒两次。</p>

<p>StateCell 的 <code class="language-plaintext highlighter-rouge">fire()</code> 中先检查 <code class="language-plaintext highlighter-rouge">cur_state == STATE_DEREGISTERED</code>，是则返回 <code class="language-plaintext highlighter-rouge">None</code>；CAS 确保一次到期只转换为一次 <code class="language-plaintext highlighter-rouge">wake()</code>。</p>

<p><strong>3. 锁竞争放大</strong></p>

<p>没有原子状态，每层操作都持锁：</p>
<ul>
  <li>用 <code class="language-plaintext highlighter-rouge">Mutex&lt;Wheel&gt;</code> 整层加锁：Driver 收割时锁住所有 timer，Task 的 <code class="language-plaintext highlighter-rouge">poll()</code> 全得等</li>
  <li>用 <code class="language-plaintext highlighter-rouge">PerTimer Mutex&lt;bool&gt;</code>：Driver 批量收割上千 timer，每个取锁放锁，锁开销随连接数线性增长</li>
</ul>

<p>StateCell 把高频路径——<code class="language-plaintext highlighter-rouge">poll()</code> 检查是否到期——变成一次 <code class="language-plaintext highlighter-rouge">AtomicU64::load</code>：免锁、无竞争、单条指令。只在真正的竞争边界（CAS 更新状态）需要一条原子指令，不持有任何锁。</p>

<h4 id="每个-timer-一个-atomicu64遍历-n-个-timer-要-n-次-cas影响精度吗">每个 timer 一个 AtomicU64，遍历 N 个 timer 要 N 次 CAS，影响精度吗？</h4>

<p>一个自然的担心：Driver 在 <code class="language-plaintext highlighter-rouge">process_at_time</code> 的一次 <code class="language-plaintext highlighter-rouge">while wheel.poll(now)</code> 里可能要处理大量到期 timer（<code class="language-plaintext highlighter-rouge">process_expiration</code> 里多次 <code class="language-plaintext highlighter-rouge">mark_pending</code>，弹出后再 <code class="language-plaintext highlighter-rouge">fire</code>，每个 entry 至少两次 CAS），如果同一 slot 里有几千个 timer，遍历开销会不会让它们”不准时”？</p>

<p>实际约束让这个开销无关紧要：</p>

<p><strong>约束一：每个 CAS 的成本极低</strong>。一次 <code class="language-plaintext highlighter-rouge">AtomicU64::compare_exchange</code> 在 x86 上是一条 <code class="language-plaintext highlighter-rouge">lock cmpxchgq</code> 指令，无争用时约 <strong>10-20ns</strong>。1,000 个 timer 同时到期 ≈ 10-20μs 的遍历时间。</p>

<p><strong>约束二：slot 粒度本身就是保护层</strong>。时间轮 level 0 的 slot 宽度是 <strong>1ms</strong>。同一 slot 内的所有 timer 本来就在同一个 1ms 窗口内到期。遍历开销占 slot 宽度的 <strong>1-2%</strong>。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>t=1000ms   level-0 slot 边界
    ├─ timer A (1000.1ms 到期)
    ├─ timer B (1000.5ms 到期)
    ├─ timer C (1000.7ms 到期)
    └─ ... 最多几千个 timer
t=1001ms   level-0 slot 边界
           ↑ 遍历 1,000 个 timer 耗时 ~15μs
           ↑ 不影响下一个 slot（1001ms 才需要检查）
</code></pre></div></div>

<p><strong>约束三：锁零争用</strong>。CAS 只在 Driver 和 Task 同时操作同一个 timer 时才失败。大多数 timer 到期时对应的任务睡眠在 <code class="language-plaintext highlighter-rouge">Poll::Pending</code>——CAS 一次成功，无重试。对比 Mutex 方案：收割时 <code class="language-plaintext highlighter-rouge">lock()</code> + <code class="language-plaintext highlighter-rouge">unlock()</code> 一千次，且 Task 端的 <code class="language-plaintext highlighter-rouge">poll()</code> 也要抢同一把锁，争用放大。</p>

<p><strong>真实的权衡是</strong><sup id="fnref:16"><a href="#fn:16" class="footnote" rel="footnote" role="doc-noteref">17</a></sup>：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>每个 timer 一个 AtomicU64：
  × 遍历 N 个 timer 需要 N 次 CAS
  ✓ 零锁争用、零 syscall、O(N) 的 ~10ns/timer 线性代价

替代方案——每个 slot 一把锁：
  × Task poll() 必须等 Driver 放锁（可能活锁）
  × 锁竞争随 timer 数量超线性放大
  ✓ 批量移动 slot 时只需一次锁操作
</code></pre></div></div>

<p>Tokio 选择了 CAS 方案。因为 timer 数量越多，CAS 的优势越明显——锁竞争是超线性放大，CAS 遍历是严格 O(N) 且常数极小。</p>

<h2 id="五运行时如何驱动定时器">五、运行时如何驱动定时器</h2>

<p>前面讲的是 <code class="language-plaintext highlighter-rouge">Sleep</code> 的结构和状态管理，但这些定时器到底是怎么被驱动的？答案在运行时的主循环里。</p>

<p>驱动 <code class="language-plaintext highlighter-rouge">park_internal()</code> 的不是某个特殊事件，而是调度器 worker 线程的<strong>空闲检测</strong>——”没活干了就得等，等的同时顺便把到期的 timer 处理掉”。两种 runtime 的触发逻辑一致，但 park 机制不同：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>multi_thread（每个 worker 线程的主循环）:

  loop {
      从本地队列取 task → poll 它
      如果本地空 → 偷其他 worker 的 task
      如果全都空 → park_timeout()
                    │
                    └─ try_lock(Driver) 抢到?
                         ├─ 是 → driver.park_timeout()
                         │        └─ time::Driver::park_internal()
                         │             ├─ next_wake = wheel.next_expiration
                         │             ├─ epoll_wait(timeout)    ← 线程休眠
                         │             └─ handle.process(clock)  ← 处理到期 timer
                         │
                         └─ 否 → Condvar.wait()（其他 worker 已在当 driver）
  }

current_thread（单线程主循环）:

  block_on(future) {
      loop {
          从本地队列取 task → poll 它
          如果本地空 → 查 inject 队列
          如果全空 → park()
                      └─ time::Driver::park_internal()
                           ├─ (同上：查 next_wake → epoll_wait → process)
                           └─ handle.process(clock)
      }
  }
</code></pre></div></div>

<p>所以 worker 线程的时间线是 <strong>poll task 和 park driver 交替进行</strong>：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>worker 线程时间线:
  poll task A → poll task B → 队列空了 → park(driver) → process() → 有 task 被唤醒 → poll task C → ...
                              ↑ 这里触发           ↑ 这里处理到期 timer，唤醒等待 task
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">multi_thread</code> 里哪个 worker 抢到 <code class="language-plaintext highlighter-rouge">TryLock</code> 哪个就当 driver，其余用 Condvar 退化等待。这就解释了为什么 StateCell 的并发窗口存在：worker-A 在 poll Sleep 的同时，worker-B 可能正作为 driver 执行 <code class="language-plaintext highlighter-rouge">process()</code>。</p>

<p>在这个主循环的驱动下，Tokio 的运行时中有两个 Driver，各有自己的核心函数：</p>

<ul>
  <li><strong>Timer Driver</strong>（<code class="language-plaintext highlighter-rouge">tokio/src/runtime/time/mod.rs</code>）：核心函数是 <code class="language-plaintext highlighter-rouge">park_internal()</code>。它查时间轮得最早 deadline，然后委托 I/O Driver 执行阻塞等待，醒来后收割时间轮。</li>
  <li><strong>I/O Driver</strong>（<code class="language-plaintext highlighter-rouge">tokio/src/runtime/io/driver.rs</code>）：核心函数是 <code class="language-plaintext highlighter-rouge">turn()</code>。它持有 <code class="language-plaintext highlighter-rouge">mio::Poll</code>，执行 <code class="language-plaintext highlighter-rouge">epoll_wait(events, timeout)</code>，醒来后分发 I/O 事件到 <code class="language-plaintext highlighter-rouge">ScheduledIo</code>。</li>
</ul>

<p>这两个 Driver 是<strong>分别独立初始化</strong>的——在 <code class="language-plaintext highlighter-rouge">runtime::driver::Driver::new()</code> 中，先创建 I/O Driver，再在其上包裹 Time Driver，两者各自持有一份内部状态<sup id="fnref:19"><a href="#fn:19" class="footnote" rel="footnote" role="doc-noteref">18</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/driver.rs: 108-117</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">cfg</span><span class="p">:</span> <span class="n">Cfg</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">(</span><span class="k">Self</span><span class="p">,</span> <span class="n">Handle</span><span class="p">)</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">io_stack</span><span class="p">,</span> <span class="n">io_handle</span><span class="p">,</span> <span class="n">signal_handle</span><span class="p">)</span> <span class="o">=</span> <span class="nf">create_io_stack</span><span class="p">(</span><span class="n">cfg</span><span class="py">.enable_io</span><span class="p">,</span> <span class="n">cfg</span><span class="py">.nevents</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="c1">//                ↑ I/O Driver 从这里创建，持有 mio::Poll</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">time_driver</span><span class="p">,</span> <span class="n">time_handle</span><span class="p">)</span> <span class="o">=</span>
        <span class="nf">create_time_driver</span><span class="p">(</span><span class="n">cfg</span><span class="py">.enable_time</span><span class="p">,</span> <span class="n">cfg</span><span class="py">.timer_flavor</span><span class="p">,</span> <span class="n">io_stack</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">clock</span><span class="p">);</span>
    <span class="c1">//  ↑ Time Driver 从这里创建，持有 Wheel</span>
    <span class="c1">//  ↑ io_stack 作为 IoStack 传给 Time Driver——这就是"堆叠"</span>
<span class="p">}</span>
</code></pre></div></div>

<p>创建出的 <code class="language-plaintext highlighter-rouge">Handle</code> 包含了两个子句柄：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/driver.rs: 52-59</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">Handle</span> <span class="p">{</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">io</span><span class="p">:</span> <span class="n">IoHandle</span><span class="p">,</span>       <span class="c1">// I/O Driver 的句柄</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">signal</span><span class="p">:</span> <span class="n">SignalHandle</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">time</span><span class="p">:</span> <span class="n">TimeHandle</span><span class="p">,</span>   <span class="c1">// Time Driver 的句柄</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">clock</span><span class="p">:</span> <span class="n">Clock</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这个 <code class="language-plaintext highlighter-rouge">Handle</code> 被 <code class="language-plaintext highlighter-rouge">Arc</code> 包装后分发给所有 worker 线程——所有线程共享同一个 Driver 实例，没有 per-thread 的 Driver。多线程访问通过内部锁串行化：Time Driver 的 Wheel 受 <code class="language-plaintext highlighter-rouge">Mutex&lt;InnerState&gt;</code> 保护（一次只有一个线程收割），I/O Driver 的 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 在 <code class="language-plaintext highlighter-rouge">&amp;mut self</code> 上自然串行。</p>

<p>两者的关系是<strong>堆叠调用</strong>——Timer 的 <code class="language-plaintext highlighter-rouge">park_internal()</code> 内部调用 I/O Driver 的 <code class="language-plaintext highlighter-rouge">turn()</code>，后者执行实际的 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 阻塞：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Timer::park_internal()
    ├── 查时间轮 → 计算 timeout（最早 deadline - now）
    ├── IoStack::park_timeout(timeout)
    │   └── I/O Driver::turn()
    │       └── epoll_wait(events, timeout)   ← 真正的阻塞点
    ├── process_at_time()                      ← 收割到期 timer
    └── (I/O 事件已在 turn() 里处理：ScheduledIo::wake)
</code></pre></div></div>

<p>每个 Driver 维护自己的数据结构，醒来后各管各的：</p>

<pre><code class="language-mermaid">graph TD
    subgraph Runtime::park_internal
        A["开始"] --&gt; B["Timer::park_internal()"]
        B --&gt; C["1. 查 Wheel → next_wake"]
        C --&gt; D["2. IoStack::park_timeout(duration)"]
        D --&gt; E["I/O Driver::turn()"]
        E --&gt; F["epoll_wait(events, timeout)"]
        F --&gt;|"唤醒"| G["分发 I/O 事件&lt;br/&gt;→ ScheduledIo::wake()"]
        G --&gt; H["Timer::process_at_time()"]
        H --&gt; I["收割到期 timer&lt;br/&gt;→ StateCell::fire()"]
    end
    style B fill:#e1f5fe
    style E fill:#c8e6c9
    style H fill:#e1f5fe
    style G fill:#c8e6c9
</code></pre>

<p>Timer Driver 决定”等多久”，I/O Driver 负责”执行等待”，醒来后各处理各的数据——Timer 收割时间轮，I/O 分发事件到 <code class="language-plaintext highlighter-rouge">ScheduledIo</code>。两者在同一个线程上、同一次 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 返回后依次完成。</p>

<p>下面来看 Timer Driver 的 <code class="language-plaintext highlighter-rouge">park_internal</code> 具体实现<sup id="fnref:11:1"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">16</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/mod.rs: 213-256</span>
<span class="k">fn</span> <span class="nf">park_internal</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">rt_handle</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">driver</span><span class="p">::</span><span class="n">Handle</span><span class="p">,</span> <span class="n">limit</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Duration</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">handle</span> <span class="o">=</span> <span class="n">rt_handle</span><span class="nf">.time</span><span class="p">();</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">lock</span> <span class="o">=</span> <span class="n">handle</span><span class="py">.inner</span><span class="nf">.lock</span><span class="p">();</span>

    <span class="c1">// 获取时间轮中最近的到期时间</span>
    <span class="k">let</span> <span class="n">next_wake</span> <span class="o">=</span> <span class="n">lock</span><span class="py">.wheel</span><span class="nf">.next_expiration_time</span><span class="p">();</span>
    <span class="n">lock</span><span class="py">.next_wake</span> <span class="o">=</span> <span class="n">next_wake</span><span class="nf">.map</span><span class="p">(|</span><span class="n">t</span><span class="p">|</span>
        <span class="nn">NonZeroU64</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">t</span><span class="p">)</span><span class="nf">.unwrap_or_else</span><span class="p">(||</span> <span class="nn">NonZeroU64</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">())</span>
    <span class="p">);</span>
    <span class="nf">drop</span><span class="p">(</span><span class="n">lock</span><span class="p">);</span>

    <span class="k">match</span> <span class="n">next_wake</span> <span class="p">{</span>
        <span class="nf">Some</span><span class="p">(</span><span class="n">when</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">now</span> <span class="o">=</span> <span class="n">handle</span><span class="py">.time_source</span><span class="nf">.now</span><span class="p">(</span><span class="n">rt_handle</span><span class="nf">.clock</span><span class="p">());</span>
            <span class="k">let</span> <span class="k">mut</span> <span class="n">duration</span> <span class="o">=</span> <span class="n">handle</span><span class="py">.time_source</span>
                <span class="nf">.tick_to_duration</span><span class="p">(</span><span class="n">when</span><span class="nf">.saturating_sub</span><span class="p">(</span><span class="n">now</span><span class="p">));</span>

            <span class="k">if</span> <span class="n">duration</span> <span class="o">&gt;</span> <span class="nn">Duration</span><span class="p">::</span><span class="nf">from_millis</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="c1">// 有定时器在将来才到期：带超时地 park 线程</span>
                <span class="k">self</span><span class="nf">.park_thread_timeout</span><span class="p">(</span><span class="n">rt_handle</span><span class="p">,</span> <span class="n">duration</span><span class="p">);</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="c1">// 有定时器已经到期或即将到期：立即返回</span>
                <span class="k">self</span><span class="py">.park</span><span class="nf">.park_timeout</span><span class="p">(</span><span class="n">rt_handle</span><span class="p">,</span> <span class="nn">Duration</span><span class="p">::</span><span class="nf">from_secs</span><span class="p">(</span><span class="mi">0</span><span class="p">));</span>
                <span class="c1">// ↑ 不能留空。park_timeout(0s) 走完整 Driver 链但每层只做</span>
                <span class="c1">//   非阻塞检查——epoll_wait(0)、release_pending_registrations、</span>
                <span class="c1">//   信号处理、子进程清理——不阻塞，但每层的事都办了。</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="nb">None</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="c1">// 没有定时器且有 limit：带超时 park</span>
            <span class="c1">// 没有定时器且没有 limit：无限期 park</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// 醒来后处理到期的定时器</span>
    <span class="n">handle</span><span class="nf">.process</span><span class="p">(</span><span class="n">rt_handle</span><span class="nf">.clock</span><span class="p">());</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">handle.process(clock)</code> 是一条从驱动层通往时间轮的完整调用链<sup id="fnref:11:2"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">16</a></sup>：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>time::Handle::process(clock)                     → time/mod.rs
  └→ time::Handle::process_at_time(now)          → time/mod.rs
      └→ Inner::lock() → MutexGuard&lt;InnerState&gt;   ← 持 driver 锁
         while InnerState.wheel: Wheel::poll(now) → wheel/mod.rs
           ├─ Wheel.pending.pop_back()            → 返回 TimerHandle ─┐
           └─ 或 Wheel::poll 内 loop:            │                    │
                Wheel::next_expiration()         │                    │
                Wheel::process_expiration(exp)   │                    │
                   TimerHandle::mark_pending(d)  │  ← 不 fire         │
                      └→ StateCell::mark_pending │                    │
                   Ok  → Wheel.pending.push_front│                    │
                   Err → level_for()             │                    │
                         Level::add_entry(h)     │                    │
                Wheel::set_elapsed(deadline)     │                    │
           └─ 返回 TimerHandle ──────────────────┘                    │
         TimerHandle::fire(Ok(()))               → entry.rs（consume）│
            └→ StateCell::fire()                 → 写 result、取 waker
         InnerState.next_wake ← Wheel::poll_at()
         drop(MutexGuard)
         WakeList::wake_all()
</code></pre></div></div>

<p>类型分层：<strong><code class="language-plaintext highlighter-rouge">time::Handle</code></strong> 持锁并驱动循环；<strong><code class="language-plaintext highlighter-rouge">wheel::Wheel</code></strong> 推进时间、收割 slot（<code class="language-plaintext highlighter-rouge">process_expiration</code> 是 <code class="language-plaintext highlighter-rouge">Wheel</code> 的方法，不是 <code class="language-plaintext highlighter-rouge">Handle</code> 的）；<strong><code class="language-plaintext highlighter-rouge">TimerHandle</code></strong> 是对 <code class="language-plaintext highlighter-rouge">TimerShared</code> 的短命句柄，<strong><code class="language-plaintext highlighter-rouge">mark_pending</code> / <code class="language-plaintext highlighter-rouge">fire</code> 定义在 <code class="language-plaintext highlighter-rouge">TimerHandle</code> 上</strong>，内部再委托 <strong><code class="language-plaintext highlighter-rouge">StateCell</code></strong> 做原子操作；降级时 <strong><code class="language-plaintext highlighter-rouge">level_for()</code></strong>（模块级函数）配合 <strong><code class="language-plaintext highlighter-rouge">wheel::Level::add_entry</code></strong> 把 handle 挂回更低层 slot。</p>

<p>流程很清晰：</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant RT as Runtime Worker
    participant Driver as Time Driver
    participant Wheel as Hashed Wheel
    participant OS as OS Timer

    RT-&gt;&gt;Driver: park_internal()
    Driver-&gt;&gt;Wheel: next_expiration_time()
    Wheel--&gt;&gt;Driver: Some(5000) = 5 秒后
    Driver-&gt;&gt;OS: park_timeout(5s)
    Note over OS: 线程休眠，CPU 可服务其他任务
    OS--&gt;&gt;Driver: 5 秒后唤醒（或更早被 unpark）
    Driver-&gt;&gt;Wheel: process_at_time → wheel.poll(now)
    Note over Wheel: 收割时 mark_pending 入 pending
    Wheel--&gt;&gt;Driver: 弹出 TimerHandle
    Driver-&gt;&gt;Driver: entry.fire → WakeList
    Driver-&gt;&gt;RT: wake_all()
    RT-&gt;&gt;RT: 重新 poll 对应的 Sleep Future
</code></pre>

<p>关键点在于：<strong>线程的休眠时间是由时间轮中最早的到期时间决定的</strong>。如果有多个 sleep 在等待，线程会以最早的 deadline 作为 park timeout；当时间到达（或被其他事件提前 unpark）后，驱动处理所有到期的定时器，批量唤醒对应的任务。</p>

<p>具体的处理逻辑在 <code class="language-plaintext highlighter-rouge">Handle::process_at_time</code><sup id="fnref:11:3"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">16</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/mod.rs: 296-337</span>
<span class="k">pub</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">process_at_time</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="k">mut</span> <span class="n">now</span><span class="p">:</span> <span class="nb">u64</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">waker_list</span> <span class="o">=</span> <span class="nn">WakeList</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">lock</span> <span class="o">=</span> <span class="k">self</span><span class="py">.inner</span><span class="nf">.lock</span><span class="p">();</span>

    <span class="c1">// 从时间轮中收集所有到期（≤ now）的定时器</span>
    <span class="k">while</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">entry</span><span class="p">)</span> <span class="o">=</span> <span class="n">lock</span><span class="py">.wheel</span><span class="nf">.poll</span><span class="p">(</span><span class="n">now</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">waker</span><span class="p">)</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="n">entry</span><span class="nf">.fire</span><span class="p">(</span><span class="nf">Ok</span><span class="p">(()))</span> <span class="p">}</span> <span class="p">{</span>
            <span class="n">waker_list</span><span class="nf">.push</span><span class="p">(</span><span class="n">waker</span><span class="p">);</span>
            <span class="k">if</span> <span class="o">!</span><span class="n">waker_list</span><span class="nf">.can_push</span><span class="p">()</span> <span class="p">{</span>
                <span class="c1">// 批次满了，释放锁后批量唤醒</span>
                <span class="nf">drop</span><span class="p">(</span><span class="n">lock</span><span class="p">);</span>
                <span class="n">waker_list</span><span class="nf">.wake_all</span><span class="p">();</span>
                <span class="n">lock</span> <span class="o">=</span> <span class="k">self</span><span class="py">.inner</span><span class="nf">.lock</span><span class="p">();</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// 更新下一次唤醒时间</span>
    <span class="n">lock</span><span class="py">.next_wake</span> <span class="o">=</span> <span class="n">lock</span><span class="py">.wheel</span><span class="nf">.poll_at</span><span class="p">()</span>
        <span class="nf">.map</span><span class="p">(|</span><span class="n">t</span><span class="p">|</span> <span class="nn">NonZeroU64</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">t</span><span class="p">)</span><span class="nf">.unwrap_or_else</span><span class="p">(||</span> <span class="nn">NonZeroU64</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">()));</span>
    <span class="nf">drop</span><span class="p">(</span><span class="n">lock</span><span class="p">);</span>

    <span class="c1">// 唤醒剩余 waker</span>
    <span class="n">waker_list</span><span class="nf">.wake_all</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Wheel::poll(now)</code> 内部的 loop 是这条链的核心——它每次调 <code class="language-plaintext highlighter-rouge">next_expiration()</code> 找到最早到期的 slot，处理完再找下一个，直到没有 <code class="language-plaintext highlighter-rouge">deadline ≤ now</code> 的 slot 为止。所以如果线程睡了 5 秒后醒来、<code class="language-plaintext highlighter-rouge">now - elapsed = 5000ms</code>，这个 loop 会把这 5000ms 内跨过的所有 slot（从 Level 0 到 Level 5）逐个处理完：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Wheel::poll(now=6000), elapsed=1000
  → next_expiration → Level 0 slot 1000 (deadline=1000) → process → set_elapsed(1000)
  → next_expiration → Level 0 slot 1001 (deadline=1001) → process → set_elapsed(1001)
  ...
  → next_expiration → Level 1 slot 0  (deadline=4096)  → process → set_elapsed(4096)
  → next_expiration → Level 1 slot 1  (deadline=8192)  → 8192 &gt; 6000 → break
→ set_elapsed(6000)
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">next_expiration_time()</code>（在 <code class="language-plaintext highlighter-rouge">park_internal</code> 中用于算睡多久）只<strong>找一个</strong>最早 deadline；而 <code class="language-plaintext highlighter-rouge">Wheel::poll(now)</code>（在 <code class="language-plaintext highlighter-rouge">process_at_time</code> 中用于醒来后收割）<strong>循环收割全部</strong> <code class="language-plaintext highlighter-rouge">deadline ≤ now</code> 的 slot，不限层级不限数量。前者说”最早什么时候醒”，后者处理”该醒时哪些到了”。</p>

<p>这里使用 <code class="language-plaintext highlighter-rouge">WakeList</code> 来批量收集 waker，在释放驱动锁之后再统一调用 <code class="language-plaintext highlighter-rouge">wake()</code>，避免持有锁时调用外部代码带来死锁风险。</p>

<hr />

<h3 id="5x-三者关系梳理handlestatecell-与-waker">5.x 三者关系梳理：Handle、StateCell 与 Waker</h3>

<p>前面四、五两节分别详细拆解了 <code class="language-plaintext highlighter-rouge">StateCell</code> 的原子状态机和 Driver 的驱动流程。这里用一张结构图把两者的交汇点——<strong>Handle（驱动入口）、StateCell（状态机）、Waker（唤醒令牌）三者之间的协作关系</strong>集中梳理清楚，便于形成整体 mental model。</p>

<p>从 <code class="language-plaintext highlighter-rouge">Sleep</code> 到 <code class="language-plaintext highlighter-rouge">Waker</code> 的三层封装关系如下：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Sleep (用户层面的 Future)
  └── timer: TimerEntry                  → 拥有 timer 的所有权
        ├── driver: scheduler::Handle    → 共享的调度器句柄（Arc 引用）
        │     │
        │     └── .time(): &amp;Handle       → 获取 Time Driver 句柄
        │           ├── time_source      → Instant ↔ tick 转换
        │           └── inner            → Mutex&lt;InnerState { wheel: Wheel }&gt;
        │
        └── inner: TimerShared           → 侵入式节点，同时存在于 Sleep 和 Wheel 中
              ├── state: StateCell       → 原子状态机，存 deadline 和 Waker
              ├── registered_when        → wheel 中的槽位位置
              └── pointers               → 侵入式双向链表指针
</code></pre></div></div>

<p>这里的关键不对称性：<code class="language-plaintext highlighter-rouge">TimerEntry</code> <strong>引用</strong> <code class="language-plaintext highlighter-rouge">Handle</code>（多对一，所有 timer 共享同一个 driver）；而 <code class="language-plaintext highlighter-rouge">TimerShared</code> 作为侵入式节点<strong>链入</strong> <code class="language-plaintext highlighter-rouge">Wheel</code>（一对一，每个 timer 恰好占据时间轮中的一个槽位）。<code class="language-plaintext highlighter-rouge">StateCell</code> 是这两者的交汇点——它被 <code class="language-plaintext highlighter-rouge">TimerShared</code> 持有，同时被 Driver 和 task 两侧并发访问。</p>

<blockquote>
  <p><strong>StateCell 的多线程并发是真实存在的。</strong> 在 multi-thread runtime 中，worker 线程 A 上运行的任务调用 <code class="language-plaintext highlighter-rouge">Sleep::poll()</code> → <code class="language-plaintext highlighter-rouge">StateCell::poll()</code>（无锁），与此同时 worker 线程 B 正在执行 <code class="language-plaintext highlighter-rouge">park_internal()</code> → <code class="language-plaintext highlighter-rouge">process()</code> → <code class="language-plaintext highlighter-rouge">StateCell::fire()</code>（持有 <code class="language-plaintext highlighter-rouge">Mutex&lt;InnerState&gt;</code> 保护 Wheel，但 StateCell 本身在此锁之外）。<code class="language-plaintext highlighter-rouge">Mutex&lt;InnerState&gt;</code> 只串行化了 Driver 端对 Wheel 的访问，Task 端对 StateCell 的访问完全不受此锁约束——这正是 <code class="language-plaintext highlighter-rouge">StateCell</code> 必须用 CAS 和 <code class="language-plaintext highlighter-rouge">AtomicU64</code> 的原因。如果是 <code class="language-plaintext highlighter-rouge">current_thread</code> runtime 则所有操作在同一线程上，原子操作虽无争用但仍正确执行。</p>
</blockquote>

<p>下面用两条线程路径的具体代码来展示这个并发窗口——不是伪代码，而是真实的 Tokio 源码调用链，标注了每条路径上当前持有哪些锁、线程 ID 如何不同。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>工作线程 A（执行 User Task）               工作线程 B（执行 Driver）
══════════════════════════════            ═══════════════════════════
                                          park_internal()
                                          │
tokio::spawn(async {                      ├─ lock = handle.inner.lock()
    // poll() 触发注册:                      │   ↓ 持有 Mutex&lt;InnerState&gt;
    Sleep::poll(cx)                       │   保护的是 Wheel（链表操作），
    │                                     │   不是 StateCell
    └→ Sleep::poll_elapsed(cx)            │
       │                                  ├─ next_wake = wheel
       └→ TimerEntry::poll_elapsed(cx)    │     .next_expiration_time()
          │                               ├─ drop(lock)  ← 释放锁
          │  // 无锁！                      │
          └→ self.inner.state             ├─ self.park.park_timeout(
                .poll(cx.waker())       │     duration)
                │                         │   ↓ epoll_wait, 线程休眠
                │  StateCell::poll():     │
                ├─ waker.register(waker)  │   醒来！
                │  // AtomicWaker::       │
                │  // register_by_ref     ├─ handle.process(clock)
                │                         │
                ├─ state.load(Acquire)    └→ process_at_time(now)
                │  // 读 AtomicU64          │
                │                           ├─ lock = inner.lock()
                ├─ 若 != DEREGISTERED        │   ↓ 再次持有 Mutex
                │  └→ Poll::Pending         │
                │                           ├─ while entry =
                                          │     lock.wheel.poll(now)
    // Task 让出执行权，线程 A 继续跑         │
    // 其他 task...                         │   对每个到期的 entry:
                                          │
                                          │   └→ entry.fire(Ok(()))
                                          │      │
                                          │      │  TimerHandle::fire()
                                          │      │  └→ StateCell::fire()
                                          │      │     ├─ state.load(Relaxed)
                                          │      │     ├─ state.store(
                                          │      │     │   DEREGISTERED,
                                          │      │     │   Release)
                                          │      │     └→ waker.take_waker()
                                          │      │        // AtomicWaker::take
                                          │      │        // → Option&lt;Waker&gt;
                                          │      │
                                          ├─ drop(lock)
                                          └→ waker_list.wake_all()
                                              └→ Waker::wake()
                                                  ↑ 唤醒线程 A 上的 task
</code></pre></div></div>

<p>两条路径在 <code class="language-plaintext highlighter-rouge">StateCell</code> 上汇合，但走的是不同的原子操作：</p>

<table>
  <thead>
    <tr>
      <th>路径</th>
      <th>操作</th>
      <th>原子指令</th>
      <th>持有的锁</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Worker A（task）</td>
      <td><code class="language-plaintext highlighter-rouge">poll(cx.waker())</code></td>
      <td><code class="language-plaintext highlighter-rouge">AtomicWaker::register_by_ref</code> + <code class="language-plaintext highlighter-rouge">AtomicU64::load(Acquire)</code></td>
      <td>无</td>
    </tr>
    <tr>
      <td>Worker B（driver）</td>
      <td><code class="language-plaintext highlighter-rouge">fire(Ok(()))</code></td>
      <td><code class="language-plaintext highlighter-rouge">AtomicU64::load(Relaxed)</code> + <code class="language-plaintext highlighter-rouge">AtomicU64::store(Release)</code> + <code class="language-plaintext highlighter-rouge">AtomicWaker::take</code></td>
      <td><code class="language-plaintext highlighter-rouge">Mutex&lt;InnerState&gt;</code>（仅保护 Wheel，不保护 StateCell）</td>
    </tr>
  </tbody>
</table>

<p>关键观察：Worker B 的 <code class="language-plaintext highlighter-rouge">Mutex&lt;InnerState&gt;</code> 保护的是 <code class="language-plaintext highlighter-rouge">lock.wheel.poll(now)</code>（遍历侵入式链表、从 slot 移除 entry），但 <code class="language-plaintext highlighter-rouge">StateCell::fire()</code> 内部的原子操作完全在此锁的保护范围之外——锁只保证 entry 被正确从 Wheel 移除并拿到 <code class="language-plaintext highlighter-rouge">TimerHandle</code>，之后 <code class="language-plaintext highlighter-rouge">fire()</code> 对 <code class="language-plaintext highlighter-rouge">StateCell</code> 的写入是 lock-free 的。Worker A 的 <code class="language-plaintext highlighter-rouge">poll()</code> 全程无锁，只靠原子操作与 Worker B 协调。</p>

<p>这个并发窗口真实存在的原因是：Tokio 的 multi-thread runtime 中，<strong>Driver 并不是一个独立的后台线程</strong>，而是由当前恰好空闲、需要 park 的那个 worker 线程来执行的。所以在任意时刻：</p>
<ul>
  <li>某些 worker 在执行用户 task（可能正在 poll Sleep）</li>
  <li>某个 worker 在执行 Driver 代码（正在 park 并处理到期 timer）</li>
</ul>

<p>两者可以同时操作<strong>不同</strong>的 <code class="language-plaintext highlighter-rouge">StateCell</code>（各自处理各自的 timer），也可以同时操作<strong>同一个</strong> <code class="language-plaintext highlighter-rouge">StateCell</code>（一个在 poll，另一个在 fire）。后者正是 CAS 要处理的竞争窗口。</p>

<p>用一个最简单的 Tokio 程序就可以创造出这个场景：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// worker_threads &gt;= 2，让 Driver 和 task 跑在不同的 worker 上</span>
<span class="nd">#[tokio::main(flavor</span> <span class="nd">=</span> <span class="s">"multi_thread"</span><span class="nd">,</span> <span class="nd">worker_threads</span> <span class="nd">=</span> <span class="mi">2</span><span class="nd">)]</span>
<span class="k">async</span> <span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 任务 A：一个即将到期的 sleep，会被 Driver 快速 fire</span>
    <span class="k">let</span> <span class="n">task_a</span> <span class="o">=</span> <span class="nn">tokio</span><span class="p">::</span><span class="nf">spawn</span><span class="p">(</span><span class="k">async</span> <span class="p">{</span>
        <span class="c1">// poll() 在 worker-1 上注册 Waker 到 StateCell</span>
        <span class="nn">tokio</span><span class="p">::</span><span class="nn">time</span><span class="p">::</span><span class="nf">sleep</span><span class="p">(</span><span class="nn">Duration</span><span class="p">::</span><span class="nf">from_millis</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span><span class="k">.await</span><span class="p">;</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"task A woken"</span><span class="p">);</span>
    <span class="p">});</span>

    <span class="c1">// 任务 B：让另一个 worker 保持忙碌，迫使 Driver 跑在 worker-2 上</span>
    <span class="k">let</span> <span class="n">task_b</span> <span class="o">=</span> <span class="nn">tokio</span><span class="p">::</span><span class="nf">spawn</span><span class="p">(</span><span class="k">async</span> <span class="p">{</span>
        <span class="c1">// 这个 sleep 较长，worker-2 最终会 park，期间执行 Driver 逻辑</span>
        <span class="nn">tokio</span><span class="p">::</span><span class="nn">time</span><span class="p">::</span><span class="nf">sleep</span><span class="p">(</span><span class="nn">Duration</span><span class="p">::</span><span class="nf">from_millis</span><span class="p">(</span><span class="mi">100</span><span class="p">))</span><span class="k">.await</span><span class="p">;</span>
    <span class="p">});</span>

    <span class="c1">// task A 的 sleep(1ms) 几乎立即到期：</span>
    <span class="c1">//   worker-1: 正在 poll task A 的 Sleep → StateCell::poll() 注册 Waker</span>
    <span class="c1">//   worker-2: park 中 → process() 收割到期 timer → 同一个 StateCell::fire()</span>
    <span class="c1">//   ↑ 这就是并发窗口</span>
    <span class="k">let</span> <span class="n">_</span> <span class="o">=</span> <span class="nn">tokio</span><span class="p">::</span><span class="nd">join!</span><span class="p">(</span><span class="n">task_a</span><span class="p">,</span> <span class="n">task_b</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>严格来说，这个示例<strong>不能保证</strong>一定会触发竞争窗口——线程调度由 OS 决定，CAS 也可能一次成功。但它展示了并发窗口<strong>存在</strong>的架构条件：<code class="language-plaintext highlighter-rouge">worker_threads &gt;= 2</code>、有 task 在 poll Sleep 的同时有 worker 在执行 Driver。</p>
</blockquote>

<h4 id="为什么-multi_thread-有并发而-current_thread-没有">为什么 multi_thread 有并发而 current_thread 没有？</h4>

<p>上面的并发场景只在 <code class="language-plaintext highlighter-rouge">flavor = "multi_thread"</code> 下存在。两种 runtime 的本质差异在于 <strong>Driver 的共享方式和 park 机制</strong>不同。</p>

<p>在 <code class="language-plaintext highlighter-rouge">current_thread</code> 中，<code class="language-plaintext highlighter-rouge">Driver</code> 被 <code class="language-plaintext highlighter-rouge">Core</code> 独占持有<sup id="fnref:21"><a href="#fn:21" class="footnote" rel="footnote" role="doc-noteref">19</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/scheduler/current_thread/mod.rs: Core 结构体</span>
<span class="k">struct</span> <span class="n">Core</span> <span class="p">{</span>
    <span class="n">tasks</span><span class="p">:</span> <span class="n">VecDeque</span><span class="o">&lt;</span><span class="n">Notified</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">driver</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Driver</span><span class="o">&gt;</span><span class="p">,</span>  <span class="c1">// ← Driver 归 Core 独占，Option 取出/放回</span>
    <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>唯一的线程 park 时直接从 <code class="language-plaintext highlighter-rouge">Core</code> 取出 Driver、执行 <code class="language-plaintext highlighter-rouge">park_internal</code>→<code class="language-plaintext highlighter-rouge">process()</code>、用完放回。task 的 <code class="language-plaintext highlighter-rouge">Sleep::poll()</code> 和 Driver 的 <code class="language-plaintext highlighter-rouge">process()</code> <strong>在同一线程上严格串行</strong>，<code class="language-plaintext highlighter-rouge">StateCell</code> 的 CAS 不会遇到真正的多核竞争。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>current_thread（单线程）:
  poll task A → poll task B → 无活 → park(driver) → process() → 继续 poll
                                    ↑ 同一线程，所有操作串行
</code></pre></div></div>

<p>在 <code class="language-plaintext highlighter-rouge">multi_thread</code> 中，Driver 被包在 <code class="language-plaintext highlighter-rouge">Arc&lt;Shared&gt;</code> 里，多个 worker 通过 <code class="language-plaintext highlighter-rouge">TryLock</code> 竞争<sup id="fnref:22"><a href="#fn:22" class="footnote" rel="footnote" role="doc-noteref">20</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/scheduler/multi_thread/park.rs: Shared</span>
<span class="k">struct</span> <span class="n">Shared</span> <span class="p">{</span>
    <span class="n">driver</span><span class="p">:</span> <span class="n">TryLock</span><span class="o">&lt;</span><span class="n">Driver</span><span class="o">&gt;</span><span class="p">,</span>  <span class="c1">// ← 多 worker 共享，谁抢到谁当 driver</span>
<span class="p">}</span>

<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">park_timeout</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">handle</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">driver</span><span class="p">::</span><span class="n">Handle</span><span class="p">,</span> <span class="n">duration</span><span class="p">:</span> <span class="n">Duration</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">HadDriver</span> <span class="p">{</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="k">mut</span> <span class="n">driver</span><span class="p">)</span> <span class="o">=</span> <span class="k">self</span><span class="py">.inner.shared.driver</span><span class="nf">.try_lock</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 抢到了 → 成为 driver 线程，执行 park_internal → process()</span>
        <span class="k">self</span><span class="py">.inner</span><span class="nf">.park_driver</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">driver</span><span class="p">,</span> <span class="n">handle</span><span class="p">,</span> <span class="nf">Some</span><span class="p">(</span><span class="n">duration</span><span class="p">))</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="c1">// 没抢到 → 用 Condvar 退化等待，不做 driver 工作</span>
        <span class="k">self</span><span class="py">.inner</span><span class="nf">.park_condvar</span><span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="n">duration</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">TryLock</code> 只保护”谁有资格当 driver”——抢到的 worker 独占 <code class="language-plaintext highlighter-rouge">park_internal</code> 和 <code class="language-plaintext highlighter-rouge">process()</code> 的执行权。但另一个 worker 仍然可以无锁地执行 task 并调用 <code class="language-plaintext highlighter-rouge">StateCell::poll()</code>，两者在 <code class="language-plaintext highlighter-rouge">StateCell</code> 上形成真正的多核并发。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>multi_thread（双 worker）:
  worker-0: poll task A → StateCell::poll(waker) → Pending ──→ poll 其他 task
  worker-1: poll task B ──→ 无活 → try_lock(driver) → 抢到！
                               ↓
                             park_internal()
                               ↓
                             process() → StateCell::fire() → waker.wake()
                               ↑
                             同一时刻 worker-0 也在操作 StateCell（poll 其他 task）
</code></pre></div></div>

<p>两种 runtime 的 Driver 共享模式对比：</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th><code class="language-plaintext highlighter-rouge">current_thread</code></th>
      <th><code class="language-plaintext highlighter-rouge">multi_thread</code></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Driver 持有</td>
      <td><code class="language-plaintext highlighter-rouge">Core</code> 独占 <code class="language-plaintext highlighter-rouge">Option&lt;Driver&gt;</code></td>
      <td><code class="language-plaintext highlighter-rouge">Arc&lt;Shared { TryLock&lt;Driver&gt; }&gt;</code> 共享</td>
    </tr>
    <tr>
      <td>Park 机制</td>
      <td>直接从 Core 取</td>
      <td><code class="language-plaintext highlighter-rouge">try_lock()</code> 竞争，抢不到退化 <code class="language-plaintext highlighter-rouge">Condvar</code></td>
    </tr>
    <tr>
      <td>Task poll vs Driver process</td>
      <td>同一线程，串行</td>
      <td>可能在不同线程，并发</td>
    </tr>
    <tr>
      <td>StateCell 并发</td>
      <td>不存在（原子操作无实际争用）</td>
      <td>存在（CAS 多核竞争）</td>
    </tr>
  </tbody>
</table>

<pre><code class="language-mermaid">classDiagram
    class Sleep {
        +deadline: Instant
        +entry: Timer
        +poll(cx) Poll
    }
    class TimerEntry {
        -driver: scheduler~Handle
        -inner: TimerShared
        -deadline: Instant
        +poll_elapsed(cx) Poll
        +reset(deadline)
    }
    class TimerShared {
        +state: StateCell
        +registered_when: AtomicU64
        +pointers: linked_list.Pointers
    }
    class StateCell {
        -state: AtomicU64
        -waker: AtomicWaker
        +poll(waker) Poll
        +fire(result) Option~Waker~
        +mark_pending(not_after) Result
    }
    class Handle {
        +time_source: TimeSource
        +inner: Inner
        +process(clock)
        +process_at_time(tick)
    }
    class Waker {
        +wake()
    }
    class Wheel {
        +poll(now) Option~TimerHandle~
        +insert(entry) Result
        +next_expiration_time() Option~u64~
    }

    Sleep *--&gt; TimerEntry
    TimerEntry *--&gt; TimerShared
    TimerShared *--&gt; StateCell
    TimerEntry --&gt; Handle : 引用（Arc）
    Handle --&gt; Wheel : 持有（Mutex 保护）
    Wheel --&gt; TimerShared : 存储（侵入式链表）
    StateCell ..&gt; Waker : fire() 时取出
</code></pre>

<p>这张图的三个关键连接：</p>

<ol>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">StateCell</code> ←→ <code class="language-plaintext highlighter-rouge">Waker</code></strong>：<code class="language-plaintext highlighter-rouge">StateCell::poll()</code> 存入 Waker（来自 <code class="language-plaintext highlighter-rouge">cx.waker()</code>），<code class="language-plaintext highlighter-rouge">StateCell::fire()</code> 返回 Waker。这是 task 线程与 driver 线程的唯一”数据交换点”——driver 通过 <code class="language-plaintext highlighter-rouge">fire()</code> 拿到 waker 后调用 <code class="language-plaintext highlighter-rouge">wake()</code>，通知 executor 重新调度 task。</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">Handle</code> → <code class="language-plaintext highlighter-rouge">Wheel</code> → <code class="language-plaintext highlighter-rouge">TimerShared</code> → <code class="language-plaintext highlighter-rouge">StateCell</code></strong>：Driver 的 <code class="language-plaintext highlighter-rouge">process()</code> 是驱动链的起点，它获取锁、驱动 <code class="language-plaintext highlighter-rouge">Wheel::poll()</code> 收割到期 slot、对每个到期的 <code class="language-plaintext highlighter-rouge">TimerShared</code> 调用 <code class="language-plaintext highlighter-rouge">fire()</code> 取出 Waker、释放锁后批量 <code class="language-plaintext highlighter-rouge">wake_all()</code>。整个过程 <code class="language-plaintext highlighter-rouge">Handle</code> 完全不需要知道具体 task 的存在——它只和 waker 打交道。</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">TimerEntry</code> → <code class="language-plaintext highlighter-rouge">Handle</code></strong>：多个 <code class="language-plaintext highlighter-rouge">TimerEntry</code> 共享同一个 <code class="language-plaintext highlighter-rouge">Handle</code>（通过 <code class="language-plaintext highlighter-rouge">scheduler::Handle</code> 中的 <code class="language-plaintext highlighter-rouge">Arc</code>），这使得 driver 是全局单例，避免每个 timer 各自维护一套时间轮的重复开销。</p>
  </li>
</ol>

<p>三者分工如下：</p>

<table>
  <thead>
    <tr>
      <th>组件</th>
      <th>角色</th>
      <th>所属关系</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Handle</strong></td>
      <td>驱动入口，持有 <code class="language-plaintext highlighter-rouge">TimeSource</code> 和被 <code class="language-plaintext highlighter-rouge">Mutex</code> 保护的 <code class="language-plaintext highlighter-rouge">Wheel</code></td>
      <td>所有 timer 共享一个（Arc-like）</td>
    </tr>
    <tr>
      <td><strong>StateCell</strong></td>
      <td>每个 timer 的原子状态机；存储 deadline/状态和注册的 Waker</td>
      <td>每个 <code class="language-plaintext highlighter-rouge">Sleep</code> 一个（在 <code class="language-plaintext highlighter-rouge">TimerShared</code> 内）</td>
    </tr>
    <tr>
      <td><strong>Waker</strong></td>
      <td>来自 executor 的不透明唤醒令牌；<code class="language-plaintext highlighter-rouge">wake()</code> 将任务放回就绪队列</td>
      <td>每次 <code class="language-plaintext highlighter-rouge">poll()</code> 一个（克隆到 <code class="language-plaintext highlighter-rouge">AtomicWaker</code> 中）</td>
    </tr>
  </tbody>
</table>

<p>上文描述了数据关系，下面把实际代码的调用链逐层追踪，看清楚 <code class="language-plaintext highlighter-rouge">Handle</code> → <code class="language-plaintext highlighter-rouge">Wheel</code> → <code class="language-plaintext highlighter-rouge">TimerShared</code> → <code class="language-plaintext highlighter-rouge">StateCell</code> → <code class="language-plaintext highlighter-rouge">Waker</code> 在代码层面是怎么串联起来的。</p>

<h4 id="实际代码调用链">实际代码调用链</h4>

<p>整条链从 <code class="language-plaintext highlighter-rouge">park_internal</code> 末尾的 <code class="language-plaintext highlighter-rouge">handle.process(clock)</code> 出发<sup id="fnref:11:4"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">16</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/mod.rs: park_internal 末尾</span>
<span class="c1">// 线程从 park 醒来后，处理所有到期的定时器</span>
<span class="n">handle</span><span class="nf">.process</span><span class="p">(</span><span class="n">rt_handle</span><span class="nf">.clock</span><span class="p">());</span>
</code></pre></div></div>

<p>① <strong>第一层：<code class="language-plaintext highlighter-rouge">Handle::process</code> → <code class="language-plaintext highlighter-rouge">process_at_time</code></strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/mod.rs: Handle::process</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">process</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">clock</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Clock</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// TimeSource 将 Clock::now() 转为驱动启动以来的毫秒 tick</span>
    <span class="k">let</span> <span class="n">now</span> <span class="o">=</span> <span class="k">self</span><span class="py">.time_source</span><span class="nf">.now</span><span class="p">(</span><span class="n">clock</span><span class="p">);</span>

    <span class="c1">// 根据定时器类型分派：</span>
    <span class="c1">//   Traditional → self.process_at_time(now)</span>
    <span class="c1">//   Alternative → self.process_at_time_alt(...)</span>
    <span class="k">self</span><span class="nf">.process_at_time</span><span class="p">(</span><span class="n">now</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">process()</code> 是公开入口（被 <code class="language-plaintext highlighter-rouge">park_internal</code> 调用），它只做两件事：获取当前时间、委托给 <code class="language-plaintext highlighter-rouge">process_at_time</code>。</p>

<p>② <strong>第二层：<code class="language-plaintext highlighter-rouge">process_at_time</code> → <code class="language-plaintext highlighter-rouge">Wheel::poll</code></strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/mod.rs: Handle::process_at_time</span>
<span class="k">pub</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">process_at_time</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="k">mut</span> <span class="n">now</span><span class="p">:</span> <span class="nb">u64</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">waker_list</span> <span class="o">=</span> <span class="nn">WakeList</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">lock</span> <span class="o">=</span> <span class="k">self</span><span class="py">.inner</span><span class="nf">.lock</span><span class="p">();</span>          <span class="c1">// ← 获取 Mutex&lt;InnerState&gt;</span>

    <span class="c1">// Wheel::poll 循环：收割所有 deadline ≤ now 的 slot</span>
    <span class="k">while</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">entry</span><span class="p">)</span> <span class="o">=</span> <span class="n">lock</span><span class="py">.wheel</span><span class="nf">.poll</span><span class="p">(</span><span class="n">now</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// ↓ entry 类型是 TimerHandle（NonNull&lt;TimerShared&gt;）</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">waker</span><span class="p">)</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="n">entry</span><span class="nf">.fire</span><span class="p">(</span><span class="nf">Ok</span><span class="p">(()))</span> <span class="p">}</span> <span class="p">{</span>
            <span class="n">waker_list</span><span class="nf">.push</span><span class="p">(</span><span class="n">waker</span><span class="p">);</span>            <span class="c1">// ← 收集 Waker，延迟唤醒</span>
            <span class="c1">// 批次满了就释放锁、批量唤醒、再获取锁</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// 更新下次唤醒时间</span>
    <span class="n">lock</span><span class="py">.next_wake</span> <span class="o">=</span> <span class="n">lock</span><span class="py">.wheel</span><span class="nf">.poll_at</span><span class="p">()</span><span class="o">...</span><span class="p">;</span>
    <span class="nf">drop</span><span class="p">(</span><span class="n">lock</span><span class="p">);</span>                                <span class="c1">// ← 释放锁</span>

    <span class="n">waker_list</span><span class="nf">.wake_all</span><span class="p">();</span>                     <span class="c1">// ← 批量调用 Waker::wake()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这里 <code class="language-plaintext highlighter-rouge">lock.wheel.poll(now)</code> 返回的 <code class="language-plaintext highlighter-rouge">entry</code> 是 <code class="language-plaintext highlighter-rouge">TimerHandle</code>——一个 <code class="language-plaintext highlighter-rouge">NonNull&lt;TimerShared&gt;</code> 包装。它被从 <code class="language-plaintext highlighter-rouge">Wheel</code> 的侵入式链表中取出后，所有权交给了这段循环。</p>

<p>③ <strong>第三层：<code class="language-plaintext highlighter-rouge">entry.fire()</code> → <code class="language-plaintext highlighter-rouge">StateCell::fire</code> → <code class="language-plaintext highlighter-rouge">Option&lt;Waker&gt;</code></strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/entry.rs: TimerHandle::fire</span>
<span class="k">pub</span><span class="p">(</span><span class="k">super</span><span class="p">)</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">fire</span><span class="p">(</span><span class="k">self</span><span class="p">,</span> <span class="n">completed_state</span><span class="p">:</span> <span class="n">TimerResult</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Waker</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="k">self</span><span class="py">.inner</span><span class="nf">.as_ref</span><span class="p">()</span><span class="py">.state</span><span class="nf">.fire</span><span class="p">(</span><span class="n">completed_state</span><span class="p">)</span> <span class="p">}</span>
    <span class="c1">//        ↑ NonNull&lt;TimerShared&gt;       ↑ StateCell::fire()</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">TimerHandle::fire</code> 是一层薄薄的代理，真正的逻辑在 <code class="language-plaintext highlighter-rouge">StateCell::fire</code><sup id="fnref:10:4"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">15</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/entry.rs: StateCell::fire</span>
<span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">fire</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">result</span><span class="p">:</span> <span class="n">TimerResult</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Waker</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// 原子检查：是否已触发（STATE_DEREGISTERED）</span>
    <span class="k">let</span> <span class="n">cur_state</span> <span class="o">=</span> <span class="k">self</span><span class="py">.state</span><span class="nf">.load</span><span class="p">(</span><span class="nn">Ordering</span><span class="p">::</span><span class="n">Relaxed</span><span class="p">);</span>
    <span class="k">if</span> <span class="n">cur_state</span> <span class="o">==</span> <span class="n">STATE_DEREGISTERED</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nb">None</span><span class="p">;</span>  <span class="c1">// 已触发过，返回 None（避免重复唤醒）</span>
    <span class="p">}</span>

    <span class="c1">// 写入结果</span>
    <span class="k">self</span><span class="py">.result</span><span class="nf">.with_mut</span><span class="p">(|</span><span class="n">p</span><span class="p">|</span> <span class="o">*</span><span class="n">p</span> <span class="o">=</span> <span class="n">result</span><span class="p">);</span>
    <span class="c1">// 原子标记为已注销</span>
    <span class="k">self</span><span class="py">.state</span><span class="nf">.store</span><span class="p">(</span><span class="n">STATE_DEREGISTERED</span><span class="p">,</span> <span class="nn">Ordering</span><span class="p">::</span><span class="n">Release</span><span class="p">);</span>

    <span class="c1">// 取出之前 poll() 时存入的 Waker 并返回</span>
    <span class="k">self</span><span class="py">.waker</span><span class="nf">.take_waker</span><span class="p">()</span>
    <span class="c1">//     ↑ AtomicWaker::take() → Option&lt;Waker&gt;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这是整条链的核心跃迁点：<code class="language-plaintext highlighter-rouge">StateCell</code> 的 <code class="language-plaintext highlighter-rouge">AtomicU64</code> 状态<strong>从任意非终止状态</strong>（PendingFire 或 Scheduled）切换到 <code class="language-plaintext highlighter-rouge">STATE_DEREGISTERED</code>，同时取出 <code class="language-plaintext highlighter-rouge">AtomicWaker</code> 中存储的 <code class="language-plaintext highlighter-rouge">Waker</code>。这个 <code class="language-plaintext highlighter-rouge">Waker</code> 正是任务首次 <code class="language-plaintext highlighter-rouge">poll</code> 时通过 <code class="language-plaintext highlighter-rouge">StateCell::poll(waker)</code> 注册进去的——一进一出，任务唤醒的闭环完成。</p>

<p>④ <strong>第四层：回到 <code class="language-plaintext highlighter-rouge">process_at_time</code> → <code class="language-plaintext highlighter-rouge">waker_list.wake_all()</code></strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/mod.rs: 回到 process_at_time</span>
<span class="nf">drop</span><span class="p">(</span><span class="n">lock</span><span class="p">);</span>                <span class="c1">// 释放 driver 锁</span>
<span class="n">waker_list</span><span class="nf">.wake_all</span><span class="p">();</span>     <span class="c1">// 批量调用所有收集到的 Waker::wake()</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">WakeList</code> 将收集到的 <code class="language-plaintext highlighter-rouge">Waker</code> 批量调用 <code class="language-plaintext highlighter-rouge">wake()</code>，通知 executor 将对应 task 放回就绪队列。在释放锁之后再唤醒是刻意设计——如果在持有 <code class="language-plaintext highlighter-rouge">Mutex&lt;InnerState&gt;</code> 时调用 <code class="language-plaintext highlighter-rouge">wake()</code>，被唤醒的任务可能立即被调度并尝试获取同一把锁，导致死锁。</p>

<h4 id="整条链的数据流图">整条链的数据流图</h4>

<pre><code class="language-mermaid">graph LR
    A["TimerEntry&lt;br/&gt;.driver 字段"] --&gt;|"scheduler::Handle"| B["scheduler::Handle&lt;br/&gt;运行时全貌"]
    B --&gt;|".driver()"| C["driver::Handle&lt;br/&gt;各 driver 聚合"]
    C --&gt;|".time()"| D["time::Handle&lt;br/&gt;Time Driver 专用"]
    D --&gt;|".process(clock)"| E["time::Handle::process()"]
    E --&gt;|"time_source.now(clock)"| F["now: u64"]
    E --&gt;|"委托"| G["time::Handle::process_at_time(now)"]
    G --&gt;|"time::Handle.inner.lock()"| H["time::InnerState&lt;br/&gt;(under Mutex)"]
    H --&gt;|"lock.wheel.poll(now)"| I["time::wheel::Wheel::poll()"]
    I --&gt;|"循环返回"| J["time::entry::TimerHandle"]
    J --&gt;|"entry.fire(Ok(()))"| K["time::entry::TimerHandle::fire()"]
    K --&gt;|"代理"| L["time::entry::StateCell::fire()"]
    L --&gt;|"CAS: store DEREGISTERED"| M["AtomicU64::store&lt;br/&gt;(Release)"]
    L --&gt;|"提取"| N["Option&amp;lt;Waker&amp;gt;"]
    N --&gt;|"push"| O["crate::util::WakeList"]
    O --&gt;|"drop(lock) 后"| P["waker_list.wake_all()"]
    P --&gt;|"调用"| Q["std::task::Waker::wake()"]
    Q --&gt;|"通知"| R["Executor 就绪队列"]

    style A fill:#f3e5f5
    style B fill:#e1f5fe
    style C fill:#fff3e0
    style D fill:#c8e6c9
    style L fill:#c8e6c9
    style Q fill:#f3e5f5
    style R fill:#f3e5f5
</code></pre>

<p>从 <code class="language-plaintext highlighter-rouge">TimerEntry.driver</code>（<code class="language-plaintext highlighter-rouge">scheduler::Handle</code>）到 <code class="language-plaintext highlighter-rouge">std::task::Waker::wake()</code>，整条链先经过三层 Handle 导航（<code class="language-plaintext highlighter-rouge">scheduler::Handle</code> → <code class="language-plaintext highlighter-rouge">driver::Handle</code> → <code class="language-plaintext highlighter-rouge">time::Handle</code>），再进入四个 crate 私有模块（<code class="language-plaintext highlighter-rouge">runtime/time/mod.rs</code> → <code class="language-plaintext highlighter-rouge">runtime/time/wheel/mod.rs</code> → <code class="language-plaintext highlighter-rouge">runtime/time/entry.rs</code> → StateCell），最终收敛到标准库的 <code class="language-plaintext highlighter-rouge">Waker::wake()</code>。每一步都是纯粹的数据流动：<code class="language-plaintext highlighter-rouge">time::Handle</code> 持有锁驱动 wheel，<code class="language-plaintext highlighter-rouge">Wheel</code> 返回 <code class="language-plaintext highlighter-rouge">time::entry::TimerHandle</code>，<code class="language-plaintext highlighter-rouge">TimerHandle::fire()</code> 触发 <code class="language-plaintext highlighter-rouge">time::entry::StateCell</code> 状态迁移，<code class="language-plaintext highlighter-rouge">StateCell</code> 吐出 <code class="language-plaintext highlighter-rouge">Waker</code>。链上没有任何地方持有 task 的引用或了解 task 的身份——这也是为什么一套 Driver 可以驱动百万级 <code class="language-plaintext highlighter-rouge">Sleep</code> 实例的根本原因。</p>

<p>协作方向是单向的：<code class="language-plaintext highlighter-rouge">Handle::process()</code> 驱动 <code class="language-plaintext highlighter-rouge">Wheel</code> → Wheel 返回到期的 <code class="language-plaintext highlighter-rouge">TimerShared</code> → 调用 <code class="language-plaintext highlighter-rouge">StateCell::fire()</code> 提取 <code class="language-plaintext highlighter-rouge">Waker</code> → 调用 <code class="language-plaintext highlighter-rouge">Waker::wake()</code> 通知 executor。</p>

<p>这种解耦设计使得 Time Driver 与 executor 完全无关——driver 只需要知道”有 waker 需要调用”，而不需要知道 waker 背后是哪个 task、哪个 scheduler。<code class="language-plaintext highlighter-rouge">StateCell</code> 充当纯桥梁：<code class="language-plaintext highlighter-rouge">poll()</code> 接收 Waker，<code class="language-plaintext highlighter-rouge">fire()</code> 返回 Waker。而原子状态机（已在前一节详细分析）保证了即使 task 线程和 driver 线程并发访问同一个 <code class="language-plaintext highlighter-rouge">StateCell</code>，也能通过 CAS 正确协调，无需额外的锁。</p>

<h2 id="六最底层从-park_timeout-到时钟中断">六、最底层：从 park_timeout 到时钟中断</h2>

<p>前面说 <code class="language-plaintext highlighter-rouge">park_timeout</code> 让线程休眠到最近的 deadline，但它到底是怎么”准时醒来”的？答案在最底层的硬件时钟中断。</p>

<p>Tokio 的 <code class="language-plaintext highlighter-rouge">park_timeout</code> 在 Linux 上最终会调用 <code class="language-plaintext highlighter-rouge">mio::Poll::poll(events, timeout)</code>，后者通过 <code class="language-plaintext highlighter-rouge">epoll_wait(timeout_ms)</code> 进入内核。内核内部使用<strong>高精度定时器（hrtimer）</strong>来实现这个超时<sup id="fnref:20"><a href="#fn:20" class="footnote" rel="footnote" role="doc-noteref">21</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// fs/eventpoll.c: 2017-2031 (Linux 6.x, ep_poll)</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">eavail</span><span class="p">)</span>
    <span class="n">timed_out</span> <span class="o">=</span> <span class="o">!</span><span class="n">ep_schedule_timeout</span><span class="p">(</span><span class="n">to</span><span class="p">)</span> <span class="o">||</span>
        <span class="o">!</span><span class="n">schedule_hrtimeout_range</span><span class="p">(</span><span class="n">to</span><span class="p">,</span> <span class="n">slack</span><span class="p">,</span>
                                  <span class="n">HRTIMER_MODE_ABS</span><span class="p">);</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">epoll_wait</code> 的 <code class="language-plaintext highlighter-rouge">int timeout_ms</code> 参数先被转为 <code class="language-plaintext highlighter-rouge">timespec64</code>，再转为 <code class="language-plaintext highlighter-rouge">ktime_t</code> 绝对到期时间，最后通过 <code class="language-plaintext highlighter-rouge">schedule_hrtimeout_range()</code> 注册到内核的 hrtimer 红黑树中：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>epoll_wait(epfd, events, maxevents, timeout_ms)
  → do_epoll_wait()
    → ep_poll()
      → schedule_hrtimeout_range(&amp;expires, slack, HRTIMER_MODE_ABS)
        → hrtimer_start_range_ns()    ← 插入 hrtimer 红黑树
        → schedule()                   ← 让出 CPU
</code></pre></div></div>

<pre><code class="language-mermaid">sequenceDiagram
    participant Tokio as Tokio Worker
    participant mio as mio / epoll
    participant Kernel as Linux Kernel
    participant hrtimer as hrtimer 子系统
    participant Clock as 时钟事件设备 (APIC/HPET)

    Tokio-&gt;&gt;mio: park_timeout(5000ms)
    mio-&gt;&gt;Kernel: epoll_wait(timeout=5000ms)
    Kernel-&gt;&gt;hrtimer: schedule_hrtimeout_range()
    hrtimer-&gt;&gt;hrtimer: enqueue_hrtimer() 插入红黑树
    hrtimer-&gt;&gt;Clock: 编程设备为 one-shot，5 秒后触发中断
    Note over Tokio: 线程休眠，CPU 可服务其他任务
    Note over Clock: ... 5 秒 ...
    Clock--&gt;&gt;Kernel: 硬件时钟中断
    Kernel-&gt;&gt;hrtimer: hrtimer_interrupt()
    hrtimer-&gt;&gt;hrtimer: __hrtimer_run_queues() 遍历红黑树
    hrtimer-&gt;&gt;Kernel: hrtimer_wakeup() → wake_up_process()
    Kernel--&gt;&gt;Tokio: epoll_wait 返回
    Tokio-&gt;&gt;Tokio: process_at_time() 处理到期定时器
</code></pre>

<h3 id="61-c-语言的等价实现">6.1 C 语言的等价实现</h3>

<p>Tokio 的 <code class="language-plaintext highlighter-rouge">park_timeout</code> 在 Linux 上依赖 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 的超时参数。在 C 语言中，有两种不同的方式使用内核定时器，分别对应”主动检查”和”自动回调”两种模型。</p>

<h4 id="timerfd--epoll主动检查模型">timerfd + epoll（主动检查模型）</h4>

<p>Tokio 底层走的是这条路径。<code class="language-plaintext highlighter-rouge">timerfd</code> 将定时器暴露为文件描述符，<code class="language-plaintext highlighter-rouge">epoll</code> 可以像监听网络事件一样监听它：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 创建一个定时器文件描述符</span>
<span class="kt">int</span> <span class="n">tfd</span> <span class="o">=</span> <span class="n">timerfd_create</span><span class="p">(</span><span class="n">CLOCK_MONOTONIC</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="k">struct</span> <span class="n">itimerspec</span> <span class="n">spec</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">.</span><span class="n">it_value</span> <span class="o">=</span> <span class="p">{</span> <span class="p">.</span><span class="n">tv_sec</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="p">.</span><span class="n">tv_nsec</span> <span class="o">=</span> <span class="mi">0</span> <span class="p">}</span>  <span class="c1">// 2 秒后触发</span>
<span class="p">};</span>
<span class="n">timerfd_settime</span><span class="p">(</span><span class="n">tfd</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">spec</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>

<span class="c1">// 将 timerfd 注册到 epoll</span>
<span class="kt">int</span> <span class="n">epfd</span> <span class="o">=</span> <span class="n">epoll_create1</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
<span class="k">struct</span> <span class="n">epoll_event</span> <span class="n">ev</span> <span class="o">=</span> <span class="p">{</span> <span class="p">.</span><span class="n">events</span> <span class="o">=</span> <span class="n">EPOLLIN</span><span class="p">,</span> <span class="p">.</span><span class="n">data</span><span class="p">.</span><span class="n">fd</span> <span class="o">=</span> <span class="n">tfd</span> <span class="p">};</span>
<span class="n">epoll_ctl</span><span class="p">(</span><span class="n">epfd</span><span class="p">,</span> <span class="n">EPOLL_CTL_ADD</span><span class="p">,</span> <span class="n">tfd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ev</span><span class="p">);</span>

<span class="c1">// 阻塞直到定时器到期</span>
<span class="n">epoll_wait</span><span class="p">(</span><span class="n">epfd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ev</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="kt">uint64_t</span> <span class="n">exp</span><span class="p">;</span>
<span class="n">read</span><span class="p">(</span><span class="n">tfd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">exp</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">exp</span><span class="p">));</span>  <span class="c1">// 消耗到期事件</span>
</code></pre></div></div>

<p>当定时器到期时，<code class="language-plaintext highlighter-rouge">timerfd</code> 变为可读，<code class="language-plaintext highlighter-rouge">epoll_wait</code> 返回。这与 Tokio 的 <code class="language-plaintext highlighter-rouge">park_timeout</code> 最终调用 <code class="language-plaintext highlighter-rouge">epoll_wait(timeout_ms)</code> 是同一个底层机制——只不过 Tokio 把超时时间作为 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 的参数传递，而 C 版本显式创建了一个独立的 <code class="language-plaintext highlighter-rouge">timerfd</code>。两种方式最终都进入内核的 hrtimer 子系统。</p>

<h4 id="timer_create--sigev_thread自动回调模型">timer_create + SIGEV_THREAD（自动回调模型）</h4>

<p>C 标准还提供了另一种方式——POSIX 定时器。设置 <code class="language-plaintext highlighter-rouge">sigev_notify = SIGEV_THREAD</code> 后，内核在定时器到期时<strong>自动创建一个新线程</strong>来运行回调函数：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;signal.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;time.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;pthread.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;unistd.h&gt;</span><span class="cp">
</span>
<span class="kt">void</span> <span class="nf">timer_callback</span><span class="p">(</span><span class="k">union</span> <span class="n">sigval</span> <span class="n">val</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">id</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="kt">int</span> <span class="o">*</span><span class="p">)</span><span class="n">val</span><span class="p">.</span><span class="n">sival_ptr</span><span class="p">;</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"定时器 %d 到期！线程 ID: %lu</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">id</span><span class="p">,</span> <span class="n">pthread_self</span><span class="p">());</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">timer_t</span> <span class="n">timer</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">id</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">sigevent</span> <span class="n">sev</span> <span class="o">=</span> <span class="p">{</span>
        <span class="p">.</span><span class="n">sigev_notify</span> <span class="o">=</span> <span class="n">SIGEV_THREAD</span><span class="p">,</span>
        <span class="p">.</span><span class="n">sigev_value</span><span class="p">.</span><span class="n">sival_ptr</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">id</span><span class="p">,</span>
        <span class="p">.</span><span class="n">sigev_notify_function</span> <span class="o">=</span> <span class="n">timer_callback</span><span class="p">,</span>
    <span class="p">};</span>
    <span class="n">timer_create</span><span class="p">(</span><span class="n">CLOCK_MONOTONIC</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sev</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">timer</span><span class="p">);</span>

    <span class="k">struct</span> <span class="n">itimerspec</span> <span class="n">spec</span> <span class="o">=</span> <span class="p">{</span>
        <span class="p">.</span><span class="n">it_value</span> <span class="o">=</span> <span class="p">{</span> <span class="p">.</span><span class="n">tv_sec</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="p">.</span><span class="n">tv_nsec</span> <span class="o">=</span> <span class="mi">0</span> <span class="p">}</span>
    <span class="p">};</span>
    <span class="n">timer_settime</span><span class="p">(</span><span class="n">timer</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">spec</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">"主线程继续做其他事...</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="n">pause</span><span class="p">();</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这种方式看起来很像”注册回调，自动执行”的理想模型。但在高性能服务器中很少使用——从信号/线程上下文中有太多限制：不能可靠访问线程局部存储、锁的约束更严格、线程频繁创建销毁的开销不可控。因此，实际的高性能 C 程序（如 Nginx、Redis）都走 <code class="language-plaintext highlighter-rouge">timerfd</code> + <code class="language-plaintext highlighter-rouge">epoll</code> 路径，而不是 <code class="language-plaintext highlighter-rouge">SIGEV_THREAD</code>。</p>

<p>Tokio 的 <code class="language-plaintext highlighter-rouge">park_timeout</code> 本质上更接近 <code class="language-plaintext highlighter-rouge">timerfd</code> 模型——它不是靠回调自动执行的，而是把超时时间传给 <code class="language-plaintext highlighter-rouge">epoll_wait</code>，到期后线程被内核唤醒，再由 Driver 去时间轮里收割到期定时器。</p>

<h4 id="为什么-tokio-不走-sigev_thread-或信号路径">为什么 Tokio 不走 SIGEV_THREAD 或信号路径？</h4>

<p>既然内核提供了 <code class="language-plaintext highlighter-rouge">SIGEV_THREAD</code> 这种”自动回调”机制，Tokio 为什么不用？原因有三。</p>

<p><strong>1. 锁竞争爆炸。</strong> 如果几十个定时器同时到期，每个都起一个线程去跑回调，这些线程会同时争 <code class="language-plaintext highlighter-rouge">Wheel</code> 的锁和任务的运行队列锁。而 Tokio 现在的设计是只有一个 Driver 线程在 <code class="language-plaintext highlighter-rouge">park</code>/<code class="language-plaintext highlighter-rouge">unpark</code> 循环里处理定时器——处理完一把锁释放、批量唤醒，零锁争用。</p>

<p><strong>2. 没有批处理。</strong> <code class="language-plaintext highlighter-rouge">epoll_wait</code> 返回后，Driver 在 <code class="language-plaintext highlighter-rouge">process_at_time</code> 里一次性收割<strong>所有到期定时器</strong>，把 waker 收集到 <code class="language-plaintext highlighter-rouge">WakeList</code> 里批量调用（参见第五节）。如果每个定时器单独起一个线程或信号，就失去了批处理的机会——每个回调都得单独走一遍上下文切换。</p>

<p><strong>3. 信号的安全黑洞与合作式调度冲突。</strong> 信号处理函数只能调用 async-signal-safe 的函数，不能锁 mutex、不能分配内存，几乎什么也干不了。<code class="language-plaintext highlighter-rouge">SIGEV_THREAD</code> 虽然避开了信号限制，但它的抢占式特性会破坏 Tokio 的合作式调度模型。而现在的 <code class="language-plaintext highlighter-rouge">epoll_wait</code> → Driver 处理 → <code class="language-plaintext highlighter-rouge">Waker::wake()</code> → 任务入队 → 调度器 poll，每一步都在运行时掌控之中。</p>

<p>本质上，Tokio 已经有了回调机制——<strong>Waker</strong>。问题不是”怎么通知”，而是”怎么高效地通知”。<code class="language-plaintext highlighter-rouge">epoll_wait</code> 不是轮询，而是被内核挂起；醒来后批量收割时间轮，本质是把 n 个独立的”自动回调”合并成 1 个可预测的批处理循环。</p>

<h3 id="62-红黑树-vs-哈希时间轮">6.2 红黑树 vs 哈希时间轮</h3>

<p>有趣的是，内核的 hrtimer 和 Tokio 用了不同的数据结构。Tokio 的 time driver 使用<strong>哈希时间轮</strong>（本文第三节），而 Linux 的 hrtimer 使用<strong>红黑树（rbtree）</strong>来组织定时器<sup id="fnref:12"><a href="#fn:12" class="footnote" rel="footnote" role="doc-noteref">22</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// include/linux/hrtimer_defs.h: 27-35</span>
<span class="k">struct</span> <span class="n">hrtimer_clock_base</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">hrtimer_cpu_base</span>    <span class="o">*</span><span class="n">cpu_base</span><span class="p">;</span>
    <span class="n">clockid_t</span>                  <span class="n">clockid</span><span class="p">;</span>
    <span class="kt">unsigned</span> <span class="kt">int</span>               <span class="n">seq</span><span class="p">;</span>
    <span class="c1">// 按到期时间排序的红黑树根节点</span>
    <span class="k">struct</span> <span class="n">timerqueue_linked_head</span> <span class="n">active</span><span class="p">;</span>
    <span class="n">ktime_t</span>                    <span class="p">(</span><span class="o">*</span><span class="n">get_time</span><span class="p">)(</span><span class="kt">void</span><span class="p">);</span>
    <span class="n">ktime_t</span>                    <span class="n">offset</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">enqueue_hrtimer()</code> 将定时器插入红黑树，时间复杂度 O(log n)<sup id="fnref:13"><a href="#fn:13" class="footnote" rel="footnote" role="doc-noteref">23</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// kernel/time/hrtimer.c: 1096-1104</span>
<span class="cm">/*
 * enqueue_hrtimer - internal function to (re)start a timer
 *
 * The timer is inserted in expiry order. Insertion into the
 * red black tree is O(log(n)).
 */</span>
<span class="k">static</span> <span class="n">bool</span> <span class="n">enqueue_hrtimer</span><span class="p">(</span><span class="k">struct</span> <span class="n">hrtimer</span> <span class="o">*</span><span class="n">timer</span><span class="p">,</span>
                            <span class="k">struct</span> <span class="n">hrtimer_clock_base</span> <span class="o">*</span><span class="n">base</span><span class="p">,</span>
                            <span class="k">enum</span> <span class="n">hrtimer_mode</span> <span class="n">mode</span><span class="p">,</span> <span class="n">bool</span> <span class="n">was_armed</span><span class="p">)</span>
</code></pre></div></div>

<p>这并不是说哪种数据结构更优——它们只是面向不同的约束。内核的 hrtimer 需要纳秒级精度和动态的到期时间分布，红黑树的 O(log n) 插入更为稳妥。Tokio 的哈希时间轮只需要毫秒级精度，但需要管理海量定时器（成千上万个 <code class="language-plaintext highlighter-rouge">Sleep</code> 并发），O(1) 插入和批量降级更适合这种场景。</p>

<p>有意思的是，Linux 内核也有一套<strong>低精度定时器</strong>（timer wheel），代码路径在 <code class="language-plaintext highlighter-rouge">kernel/time/timer.c</code>，它用的正是和 Tokio 类似的层级哈希轮结构——8 到 9 层，每层 64 个 bucket（<code class="language-plaintext highlighter-rouge">LVL_SIZE = 64</code>，<code class="language-plaintext highlighter-rouge">LVL_DEPTH = 8/9</code>），粒度和覆盖范围的表<sup id="fnref:14"><a href="#fn:14" class="footnote" rel="footnote" role="doc-noteref">24</a></sup>：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// kernel/time/timer.c: 104-115 (HZ=1000 时)
//
// HZ 1000 steps
// Level Offset  Granularity            Range
//  0      0         1 ms                0 ms -         63 ms
//  1     64         8 ms               64 ms -        511 ms
//  2    128        64 ms              512 ms -       4095 ms
//  3    192       512 ms             4096 ms -      32767 ms
//  4    256      4096 ms (~4s)      32768 ms -     262143 ms
//  5    320     32768 ms (~32s)    262144 ms -    2097151 ms
//  6    384    262144 ms (~4m)    2097152 ms -   16777215 ms
//  7    448   2097152 ms (~34m)  16777216 ms -  134217727 ms
//  8    512  16777216 ms (~4h)  134217728 ms - 1073741822 ms
</code></pre></div></div>

<p>对比 Tokio 的 6 层 64 slot 设计，两者的核心思想同源——都源自哈希时间轮的经典设计。</p>

<h3 id="63-从时钟中断到进程唤醒">6.3 从时钟中断到进程唤醒</h3>

<p>当硬件时钟事件设备（Local APIC Timer 或 HPET）在 deadline 到达时产生中断，内核进入 <code class="language-plaintext highlighter-rouge">hrtimer_interrupt()</code>。这个函数做了几件关键的事<sup id="fnref:13:1"><a href="#fn:13" class="footnote" rel="footnote" role="doc-noteref">23</a></sup>：</p>

<ol>
  <li><strong>更新时间基准</strong>：<code class="language-plaintext highlighter-rouge">hrtimer_update_base()</code> 读取硬件计数器和系统时间</li>
  <li><strong>处理到期定时器</strong>：<code class="language-plaintext highlighter-rouge">__hrtimer_run_queues()</code> 遍历红黑树中所有到期时间 ≤ now 的定时器</li>
  <li><strong>调用定时器回调</strong>：对于 sleep 类定时器，回调是 <code class="language-plaintext highlighter-rouge">hrtimer_wakeup()</code></li>
</ol>

<p><code class="language-plaintext highlighter-rouge">hrtimer_wakeup</code> 的实现非常直接——找到等待中的进程，把它叫醒<sup id="fnref:13:2"><a href="#fn:13" class="footnote" rel="footnote" role="doc-noteref">23</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// kernel/time/hrtimer.c: 2184-2193</span>
<span class="k">static</span> <span class="k">enum</span> <span class="n">hrtimer_restart</span> <span class="nf">hrtimer_wakeup</span><span class="p">(</span><span class="k">struct</span> <span class="n">hrtimer</span> <span class="o">*</span><span class="n">timer</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">struct</span> <span class="n">hrtimer_sleeper</span> <span class="o">*</span><span class="n">t</span> <span class="o">=</span>
        <span class="n">container_of</span><span class="p">(</span><span class="n">timer</span><span class="p">,</span> <span class="k">struct</span> <span class="n">hrtimer_sleeper</span><span class="p">,</span> <span class="n">timer</span><span class="p">);</span>
    <span class="k">struct</span> <span class="n">task_struct</span> <span class="o">*</span><span class="n">task</span> <span class="o">=</span> <span class="n">t</span><span class="o">-&gt;</span><span class="n">task</span><span class="p">;</span>

    <span class="n">t</span><span class="o">-&gt;</span><span class="n">task</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">task</span><span class="p">)</span>
        <span class="n">wake_up_process</span><span class="p">(</span><span class="n">task</span><span class="p">);</span>

    <span class="k">return</span> <span class="n">HRTIMER_NORESTART</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">wake_up_process()</code> 将进程状态设为 <code class="language-plaintext highlighter-rouge">TASK_RUNNING</code> 并放入就绪队列。随后内核的调度器在合适的时机把它调度回 CPU。Tokio 的 worker 线程从 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 返回，继续执行 <code class="language-plaintext highlighter-rouge">process_at_time()</code>，完成定时器的处理。</p>

<h3 id="64-关键细节这不是周期性的-tick">6.4 关键细节：这不是周期性的 tick</h3>

<p>需要注意，现代 Linux 在 NOHZ（dynticks）模式下并不是靠周期性的时钟中断来驱动定时器的，而是把时钟事件设备编程为<strong>一次性（one-shot）触发</strong>。<code class="language-plaintext highlighter-rouge">hrtimer_interrupt()</code> 在处理完当前到期的定时器后，会重新编程时钟设备，让它在下一次最早到期的 deadline 时再产生中断<sup id="fnref:13:3"><a href="#fn:13" class="footnote" rel="footnote" role="doc-noteref">23</a></sup>。</p>

<p>这意味着如果系统上没有定时器在等待（或者最近的定时器在 5 秒后），CPU 可以进入深度休眠状态，期间<strong>不产生任何时钟中断</strong>。这也是为什么 Tokio 的 <code class="language-plaintext highlighter-rouge">park_timeout</code> 在”没有定时器且没有 limit”时可以直接无限期 park——内核不会为它浪费任何 CPU 周期。</p>

<p>这条从 <code class="language-plaintext highlighter-rouge">tokio::time::sleep</code> 到硬件时钟中断的链路也就清楚了：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tokio::time::sleep(5s).await
    → Sleep::poll() 返回 Pending，注册到哈希时间轮
    → Driver::park_timeout(5s)
    → mio → epoll_wait(timeout=5000ms)
    → 内核 schedule_hrtimeout_range() → 插入红黑树
    → 编程时钟设备 one-shot 5 秒后触发
    → [CPU 休眠或服务其他任务]
    → 5 秒后时钟中断 → hrtimer_interrupt()
    → __hrtimer_run_queues() → hrtimer_wakeup() → wake_up_process()
    → epoll_wait 返回 → 线程醒来
    → process_at_time() → fire → wake_all()
    → Sleep::poll() 返回 Ready
</code></pre></div></div>

<h3 id="65-定时器管理的存放位置用户态-vs-内核态">6.5 定时器管理的存放位置：用户态 vs 内核态</h3>

<p>如果把这几种方式放在一起对比，一个更本质的区别浮现出来——定时器到底存放在哪一层管理：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">方案</th>
      <th style="text-align: left">定时器存储位置</th>
      <th style="text-align: left">数据结构</th>
      <th style="text-align: left">操作复杂度</th>
      <th style="text-align: left">内核中管理的定时器数量</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">C <code class="language-plaintext highlighter-rouge">timerfd</code></td>
      <td style="text-align: left">内核 hrtimer</td>
      <td style="text-align: left">红黑树</td>
      <td style="text-align: left">O(log n)</td>
      <td style="text-align: left"><strong>n 个</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">C <code class="language-plaintext highlighter-rouge">timer_create(SIGEV_THREAD)</code></td>
      <td style="text-align: left">内核 hrtimer</td>
      <td style="text-align: left">红黑树</td>
      <td style="text-align: left">O(log n)</td>
      <td style="text-align: left"><strong>n 个</strong></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Tokio 时间轮</strong></td>
      <td style="text-align: left">用户态哈希时间轮</td>
      <td style="text-align: left">多层时间轮</td>
      <td style="text-align: left">O(1) + 1 × O(log 1)</td>
      <td style="text-align: left"><strong>1 个</strong></td>
    </tr>
  </tbody>
</table>

<p>Tokio 在用户态维护时间轮，只向内核注册一个”最近 deadline”的唤醒点。这意味着几万个定时器的插入和删除都在用户态完成，O(1)，<strong>没有系统调用</strong>；内核只需要管理 1 个定时器（而不是 n 个），红黑树的 O(log 1) ≈ O(1)。精度降低到毫秒级，但吞吐量大幅提升。</p>

<pre><code class="language-mermaid">graph LR
    subgraph "C timerfd（n 个定时器全部进内核）"
        A1["用户态 app"] --&gt;|"timerfd_settime × n"| K1["内核 hrtimer&lt;br/&gt;红黑树&lt;br/&gt;O(log n) 管理 n 个"]
    end

    subgraph "Tokio（1 个定时器进内核）"
        A2["用户态 app"] --&gt;|"O(1) 插入"| W["用户态哈希时间轮&lt;br/&gt;6 层 × 64 slot"]
        W --&gt;|"只注册最早 deadline"| K2["内核 hrtimer&lt;br/&gt;红黑树&lt;br/&gt;O(log 1) 管理 1 个"]
    end
</code></pre>

<p>这正是 Varghese &amp; Lauck 论文<sup id="fnref:8:1"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">12</a></sup>思想在工程中的体现：<strong>用用户态的时间轮 + 少量内核辅助，替代全部在内核红黑树中管理</strong>。Tokio 不是在”绕过”内核机制，而是在它之上加了一层更高效的用户态调度。</p>

<h3 id="66-epoll_wait-在这条链路中的真实角色">6.6 epoll_wait 在这条链路中的真实角色</h3>

<p>前面说了那么多”内核 hrtimer”和”epoll_wait”，容易产生一个误解：<strong>epoll_wait 在管理 tokio 的定时器</strong>吗？</p>

<p>答案是 epoll_wait <strong>完全不知道 tokio 时间轮的存在</strong>。epoll_wait 的 timeout 参数只是一个”线程睡眠闹钟”。</p>

<p>本质上，<code class="language-plaintext highlighter-rouge">epoll_wait</code> 内部是两个唤醒源的 <strong>OR</strong> 组合：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>epoll_wait(epfd, events, maxevents, timeout)
                ↑                       ↑
           I/O 事件就绪              hrtimer 到期
          （网卡中断 →               （时钟中断 →
           协议栈 → epoll）            LAPIC → hrtimer）
</code></pre></div></div>

<p>任何一个条件满足，<code class="language-plaintext highlighter-rouge">epoll_wait</code> 就返回。内核里大致是这样的循环<sup id="fnref:17"><a href="#fn:17" class="footnote" rel="footnote" role="doc-noteref">25</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 内核 ep_poll() 简化伪代码</span>
<span class="kt">int</span> <span class="nf">epoll_wait</span><span class="p">(...,</span> <span class="kt">int</span> <span class="n">timeout_ms</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">timeout_ms</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span>
        <span class="n">schedule_hrtimeout_range</span><span class="p">(</span><span class="n">timeout_ms</span><span class="p">);</span>  <span class="c1">// 设一个 hrtimer</span>

    <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">events_ready</span><span class="p">)</span>  <span class="k">return</span><span class="p">;</span>        <span class="c1">// I/O 事件来了</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">timeout_expired</span><span class="p">)</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>    <span class="c1">// hrtimer 到了</span>
        <span class="n">schedule</span><span class="p">();</span>  <span class="c1">// 让出 CPU，等任一条件满足</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>tokio 利用的正是这个”谁先到就醒谁”的 OR 语义——I/O 事件先到就提前回来顺便收割 timer，hrtimer 先到就准时处理到期 timer 并回到 epoll 继续等 I/O。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tokio 时间轮：管理 1,000,000 个 Sleep
    ↓ 算出最早 deadline = now + 5s
    ↓ 传给 epoll_wait 作为 timeout
epoll_wait：设 1 个内核 hrtimer，5s 后叫醒线程
    ↓ epoll_wait 不知道 tokio 的时间轮
    ↓ epoll_wait 不知道 StateCell
    ↓ epoll_wait 不知道 Waker
    ↓ 它只是一个准确的线程睡眠定时器
    ↓ 5s 后
epoll_wait 返回 → 回到 tokio Driver
    ↓
tokio process_at_time()：用自己维护的 elapsed 推进时间轮
    ↓ 收割到期 timer
    ↓ fire → waker.wake()
</code></pre></div></div>

<p>如果把 <code class="language-plaintext highlighter-rouge">epoll_wait(timeout)</code> 换成 <code class="language-plaintext highlighter-rouge">nanosleep(timeout)</code> + <code class="language-plaintext highlighter-rouge">epoll_wait(-1)</code>，效果等价<sup id="fnref:17:1"><a href="#fn:17" class="footnote" rel="footnote" role="doc-noteref">25</a></sup>。Tokio 用 <code class="language-plaintext highlighter-rouge">epoll_wait(timeout)</code> 只是因为<strong>方便</strong>——一个系统调用同时等两件事（I/O 事件和定时器到期），比分开用 <code class="language-plaintext highlighter-rouge">nanosleep</code> + <code class="language-plaintext highlighter-rouge">epoll_wait</code> 少一次上下文切换。</p>

<p>所以 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 的角色是：<strong>tokio 的”定时闹钟”，不是 tokio 的”定时器驱动”</strong>。tokio 的定时器驱动完全在用户态完成——<code class="language-plaintext highlighter-rouge">elapsed</code> 自维护、时间轮自推进、StateCell 自管理。<code class="language-plaintext highlighter-rouge">epoll_wait</code> 只是”到点把线程叫醒”的门铃。</p>

<p>这个角色区分是整个体系的精妙之处：<strong>内核做它最擅长的事——准确到毫秒地叫醒线程；tokio 做它最擅长的事——在海量定时器中高效找出哪些已经到期</strong>。</p>

<h2 id="七timesource时间与-tick-的桥梁">七、TimeSource：时间与 tick 的桥梁</h2>

<p><code class="language-plaintext highlighter-rouge">TimerEntry</code> 使用 <code class="language-plaintext highlighter-rouge">Instant</code> 作为对外的时间抽象，而 <code class="language-plaintext highlighter-rouge">Wheel</code> 内部使用 <code class="language-plaintext highlighter-rouge">u64</code> 毫秒级 tick。<code class="language-plaintext highlighter-rouge">TimeSource</code> 负责两者之间的转换<sup id="fnref:15"><a href="#fn:15" class="footnote" rel="footnote" role="doc-noteref">26</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tokio/src/runtime/time/source.rs: 6-38</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">TimeSource</span> <span class="p">{</span>
    <span class="n">start_time</span><span class="p">:</span> <span class="n">Instant</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">TimeSource</span> <span class="p">{</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">deadline_to_tick</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">t</span><span class="p">:</span> <span class="n">Instant</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u64</span> <span class="p">{</span>
        <span class="c1">// 向上取整到最近的毫秒</span>
        <span class="k">self</span><span class="nf">.instant_to_tick</span><span class="p">(</span><span class="n">t</span> <span class="o">+</span> <span class="nn">Duration</span><span class="p">::</span><span class="nf">from_nanos</span><span class="p">(</span><span class="mi">999_999</span><span class="p">))</span>
    <span class="p">}</span>

    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">instant_to_tick</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">t</span><span class="p">:</span> <span class="n">Instant</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u64</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">dur</span><span class="p">:</span> <span class="n">Duration</span> <span class="o">=</span> <span class="n">t</span><span class="nf">.saturating_duration_since</span><span class="p">(</span><span class="k">self</span><span class="py">.start_time</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">ms</span> <span class="o">=</span> <span class="n">dur</span><span class="nf">.as_millis</span><span class="p">()</span><span class="nf">.try_into</span><span class="p">()</span>
            <span class="nf">.unwrap_or</span><span class="p">(</span><span class="n">MAX_SAFE_MILLIS_DURATION</span><span class="p">);</span>
        <span class="n">ms</span><span class="nf">.min</span><span class="p">(</span><span class="n">MAX_SAFE_MILLIS_DURATION</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">now</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">clock</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Clock</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u64</span> <span class="p">{</span>
        <span class="k">self</span><span class="nf">.instant_to_tick</span><span class="p">(</span><span class="n">clock</span><span class="nf">.now</span><span class="p">())</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>展开来看两个转换函数。<code class="language-plaintext highlighter-rouge">instant_to_tick</code> 是核心<sup id="fnref:15:1"><a href="#fn:15" class="footnote" rel="footnote" role="doc-noteref">26</a></sup>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">instant_to_tick</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">t</span><span class="p">:</span> <span class="n">Instant</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u64</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">dur</span><span class="p">:</span> <span class="n">Duration</span> <span class="o">=</span> <span class="n">t</span><span class="nf">.saturating_duration_since</span><span class="p">(</span><span class="k">self</span><span class="py">.start_time</span><span class="p">);</span>
    <span class="c1">//                    ↑ 核心公式：t - start_time</span>
    <span class="k">let</span> <span class="n">ms</span> <span class="o">=</span> <span class="n">dur</span><span class="nf">.as_millis</span><span class="p">()</span><span class="nf">.try_into</span><span class="p">()</span><span class="o">...</span><span class="p">;</span>  <span class="c1">// u128 → u64</span>
    <span class="n">ms</span><span class="nf">.min</span><span class="p">(</span><span class="n">MAX_SAFE_MILLIS_DURATION</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">deadline_to_tick</code> 在它之上加了向上取整：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">deadline_to_tick</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">t</span><span class="p">:</span> <span class="n">Instant</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u64</span> <span class="p">{</span>
    <span class="k">self</span><span class="nf">.instant_to_tick</span><span class="p">(</span><span class="n">t</span> <span class="o">+</span> <span class="nn">Duration</span><span class="p">::</span><span class="nf">from_nanos</span><span class="p">(</span><span class="mi">999_999</span><span class="p">))</span>
    <span class="c1">//                    ↑ 加 0.999999ms，任何不足 1ms 都被进位</span>
<span class="p">}</span>
</code></pre></div></div>

<p>关系是：<strong><code class="language-plaintext highlighter-rouge">Wheel.elapsed</code> 和 <code class="language-plaintext highlighter-rouge">StateCell.state</code> 都来自 <code class="language-plaintext highlighter-rouge">TimeSource</code> 的同一个参考原点 <code class="language-plaintext highlighter-rouge">start_time</code></strong>。</p>

<p>以 <code class="language-plaintext highlighter-rouge">reset(deadline)</code> 为例，tick 是通过 <code class="language-plaintext highlighter-rouge">deadline_to_tick(deadline)</code> 算出的：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tick = deadline_to_tick(deadline)
     = instant_to_tick(deadline + 999_999ns)
     = ((deadline + 999_999ns) - start_time).as_millis()
     = (deadline - start_time + 999_999ns).as_millis()
</code></pre></div></div>

<p>如果此时 <code class="language-plaintext highlighter-rouge">now - start_time = 5000ms</code>，而 <code class="language-plaintext highlighter-rouge">deadline = now + 5min</code>：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tick = ((now + 5min + 999_999ns) - start_time).as_millis()
     = ((start_time + 5000 + 300000 + 0.999999 - start_time)).as_millis()
     = (300000 + 5000 + 0.999999).as_millis()
     = 305,000  // as_millis() 丢纳米，等价于向下取整
</code></pre></div></div>

<p><strong><code class="language-plaintext highlighter-rouge">elapsed</code> 和 <code class="language-plaintext highlighter-rouge">state</code> 的差值就是真正的剩余时间</strong>。因为两者都是相对于 <code class="language-plaintext highlighter-rouge">start_time</code> 的毫秒偏移量：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>state - elapsed = (deadline - start_time) - (now - start_time)
                = deadline - now
                = 剩余等待时间（毫秒）
</code></pre></div></div>

<p>三个关键数值的关系可以总结为：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">数值</th>
      <th style="text-align: left">保存在哪</th>
      <th style="text-align: left">本质</th>
      <th style="text-align: left">用途</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">Wheel.elapsed</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">Wheel</code></td>
      <td style="text-align: left"><strong>坐标原点偏移量</strong>：当前 <code class="language-plaintext highlighter-rouge">now - start_time</code> 的毫秒值</td>
      <td style="text-align: left">推进时间轮（<code class="language-plaintext highlighter-rouge">elapsed += delta</code>），判定 timer 是否到期（<code class="language-plaintext highlighter-rouge">state &lt;= elapsed</code>）</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">registered_when</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">TimerShared</code></td>
      <td style="text-align: left"><strong>slot 定位缓存</strong>：上次插入时的 <code class="language-plaintext highlighter-rouge">state</code> 快照，持 <code class="language-plaintext highlighter-rouge">Relaxed</code> 顺序</td>
      <td style="text-align: left">算 slot 索引（<code class="language-plaintext highlighter-rouge">slot_for</code>）、从 slot 链表移除（<code class="language-plaintext highlighter-rouge">remove</code>）</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">StateCell.state</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">StateCell</code></td>
      <td style="text-align: left"><strong>真实到期时间</strong>：<code class="language-plaintext highlighter-rouge">deadline - start_time</code>，更新需 CAS（<code class="language-plaintext highlighter-rouge">AcqRel</code>）</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">mark_pending()</code> 中做最终到期判断（<code class="language-plaintext highlighter-rouge">state &gt; not_after ?</code>)</td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">registered_when</code> 是 <code class="language-plaintext highlighter-rouge">state</code> 的只读缓存——两者绝大多数时候相同，唯一的例外是 <code class="language-plaintext highlighter-rouge">extend_expiration</code> 无锁延后了 <code class="language-plaintext highlighter-rouge">state</code> 但未更新 <code class="language-plaintext highlighter-rouge">registered_when</code> 的间隙。此时 slot 位置暂时”不准”，但 Driver 在收割时通过 <code class="language-plaintext highlighter-rouge">mark_pending</code> 返回的 <code class="language-plaintext highlighter-rouge">Err</code> 检测到这个偏差，用 <code class="language-plaintext highlighter-rouge">sync_when()</code> 把 <code class="language-plaintext highlighter-rouge">state</code> 同步回 <code class="language-plaintext highlighter-rouge">registered_when</code> 并重插入正确 slot。</p>

<p>所以决定”到没到期”的只有一行判断——<code class="language-plaintext highlighter-rouge">state &gt; not_after</code>。<code class="language-plaintext highlighter-rouge">registered_when</code> 只是加速 slot 定位的缓存，<code class="language-plaintext highlighter-rouge">elapsed</code> 只是标识”现在几点”的指针。<strong><code class="language-plaintext highlighter-rouge">state</code> 是唯一的 source of truth</strong>。</p>

<p>这就验证了第 3.2 节例子中的核心假设：<code class="language-plaintext highlighter-rouge">elapsed=0</code> 时，<code class="language-plaintext highlighter-rouge">tick=300000</code> 等价于 <code class="language-plaintext highlighter-rouge">deadline - start_time = 300000ms</code>。如果 <code class="language-plaintext highlighter-rouge">elapsed=5000</code>（已经运行了 5 秒），<code class="language-plaintext highlighter-rouge">tick</code> 就是 <code class="language-plaintext highlighter-rouge">305000</code>——<code class="language-plaintext highlighter-rouge">state</code> 永远比 <code class="language-plaintext highlighter-rouge">elapsed</code> 大一个固定差值。</p>

<p>一个具体的数值对比能说清”绝对 vs 相对”的区别。假设 <code class="language-plaintext highlighter-rouge">elapsed=10000</code>（runtime 已运行 10 秒），此时创建 <code class="language-plaintext highlighter-rouge">sleep(500ms)</code>：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sleep(500ms).await
  → deadline = now + 500ms
  → deadline_to_tick(deadline)
     = (deadline + 999_999ns - start_time).as_millis()
     = (now + 500ms - start_time).as_millis()
     = (10000 + 500).as_millis()
     = 10500                          ← 绝对毫秒偏移量

state - elapsed = 10500 - 10000 = 500  ← 差值才等于注册时长
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">state</code> 是<strong>绝对毫秒偏移量</strong>，不是相对值。如果存相对值（<code class="language-plaintext highlighter-rouge">state=500</code>），那 <code class="language-plaintext highlighter-rouge">state - elapsed = 500 - 10000</code> 就会包装溢出。<code class="language-plaintext highlighter-rouge">elapsed ≥ state</code> 的到期判断也变成一句加法——选绝对值省了这条指令，还保持了 <code class="language-plaintext highlighter-rouge">u64</code> 减法的一致性。</p>

<p><code class="language-plaintext highlighter-rouge">deadline_to_tick</code> 的 <code class="language-plaintext highlighter-rouge">999_999ns</code> 加法是向上取整的关键：假设用户调用了 <code class="language-plaintext highlighter-rouge">sleep(Duration::from_micros(1))</code>，<code class="language-plaintext highlighter-rouge">deadline</code> 和 <code class="language-plaintext highlighter-rouge">start_time</code> 只差 1μs，直接 <code class="language-plaintext highlighter-rouge">as_millis()</code> 得到 0，timer 立刻到期，根本不等待。加上 <code class="language-plaintext highlighter-rouge">999_999ns</code> 后 <code class="language-plaintext highlighter-rouge">as_millis()</code> 得到 1——最小等待 1ms。这正是 1ms 精度约束在代码中的直接体现。</p>

<p><code class="language-plaintext highlighter-rouge">MAX_SAFE_MILLIS_DURATION</code> 是 <code class="language-plaintext highlighter-rouge">u64::MAX - 1</code>，约为 5.84 亿年，远超实际需要。</p>

<h4 id="u64-溢出保护">u64 溢出保护</h4>

<p>你可能会担心 <code class="language-plaintext highlighter-rouge">elapsed</code> 和 <code class="language-plaintext highlighter-rouge">state</code> 都是 <code class="language-plaintext highlighter-rouge">u64</code> 毫秒值——如果运行足够久会溢出吗？</p>

<p>有<strong>三层保护</strong>防止溢出问题：</p>

<p><strong>第一层：<code class="language-plaintext highlighter-rouge">STATE_DEREGISTERED</code> 和 <code class="language-plaintext highlighter-rouge">STATE_PENDING_FIRE</code> 占用了 <code class="language-plaintext highlighter-rouge">u64</code> 的顶值</strong>。<code class="language-plaintext highlighter-rouge">StateCell.state</code> 中 <code class="language-plaintext highlighter-rouge">u64::MAX</code> 被用作 <code class="language-plaintext highlighter-rouge">STATE_DEREGISTERED</code>（已注销），<code class="language-plaintext highlighter-rouge">u64::MAX - 1</code> 被用作 <code class="language-plaintext highlighter-rouge">STATE_PENDING_FIRE</code>（待触发）。所以有效的最大 tick 是 <code class="language-plaintext highlighter-rouge">u64::MAX - 2</code>。如果 <code class="language-plaintext highlighter-rouge">TimeSource::instant_to_tick</code> 算出来的值超过了这个范围，会被 <code class="language-plaintext highlighter-rouge">ms.min(MAX_SAFE_MILLIS_DURATION)</code> 钳住。</p>

<p><strong>第二层：时间轮的 <code class="language-plaintext highlighter-rouge">MAX_DURATION</code> 限制</strong>。6 层 × 64 slot 的时间轮能表达的最大范围是 <code class="language-plaintext highlighter-rouge">2^36 - 1 ≈ 2.18 年</code>（<code class="language-plaintext highlighter-rouge">MAX_DURATION</code>）。超过这个范围的 timer 会被自动放入顶层（Level 5）的合适 slot，不会因为到期时间远大于 wheel 范围而出错——这是哈希时间轮论文描述的”wrap-around”处理<sup id="fnref:8:2"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">12</a></sup>。</p>

<p><strong>第三层：纯数值比较</strong>。<code class="language-plaintext highlighter-rouge">Wheel::insert()</code> 的 <code class="language-plaintext highlighter-rouge">when &lt;= self.elapsed</code> 是直接的 <code class="language-plaintext highlighter-rouge">u64</code> 比较。即使 <code class="language-plaintext highlighter-rouge">elapsed</code> 和 <code class="language-plaintext highlighter-rouge">state</code> 都接近 <code class="language-plaintext highlighter-rouge">u64::MAX</code>，差值 <code class="language-plaintext highlighter-rouge">state - elapsed</code>（用于计算 park_timeout）是正确的无符号结果——因为 Rust 的 <code class="language-plaintext highlighter-rouge">saturating_sub</code> 保证了安全性。</p>

<p>实际上，<code class="language-plaintext highlighter-rouge">elapsed</code> 从 0 以毫秒为单位递增到溢出需要约 <strong>5.84 亿年</strong>。在那之前 runtime 早就被重启无数次了。<code class="language-plaintext highlighter-rouge">MAX_SAFE_MILLIS_DURATION</code> 约 <code class="language-plaintext highlighter-rouge">1.8 × 10^14</code> 天，也远超任何实际运行时长。</p>

<h2 id="八把整条链路串起来">八、把整条链路串起来</h2>

<p>现在可以把 <code class="language-plaintext highlighter-rouge">tokio::time::sleep</code> 的完整执行路径画出来：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tokio::time::sleep(Duration::from_secs(5))
    │
    ├─ 创建 Sleep { entry: TimerEntry { deadline: now + 5s, registered: false } }
    │
    ├─ 首次 poll:
    │   ├─ TimerEntry::reset(deadline, true)
    │   │   ├─ 初始化 TimerShared（如果还没有）
    │   │   ├─ 将 deadline 转为 tick → 插入 Wheel（哈希时间轮）
    │   │   └─ 设置 registered = true
    │   ├─ StateCell::poll(cx.waker())
    │   │   ├─ 注册 waker 到 AtomicWaker
    │   │   └─ 加载 state → 还不是 STATE_DEREGISTERED
    │   └─ 返回 Poll::Pending
    │
    ├─ Runtime::park_internal → time::Driver::park_internal():
    │   ├─ Wheel::next_expiration_time() → Some(5000)
    │   ├─ 计算 duration = 5000ms
    │   ├─ self.park_thread_timeout(rt_handle, 5000ms)
    │   │   └─ self.park.park_timeout(handle, 5000ms)
    │   │       └─ IoStack::park_timeout(handle, 5000ms)
    │   │           └─ ProcessDriver::park_timeout(handle, 5000ms)
    │   │               └─ SignalDriver::park_timeout(handle, 5000ms)  [Unix, 可选]
    │   │                   └─ io::Driver::park_timeout(handle, 5000ms)
    │   │                       └─ io::Driver::turn(handle, Some(5000ms))
    │   │                           └─ self.poll.poll(events, Some(5000ms))
    │   │                               └─ mio::Poll::poll(events, 5000ms)
    │   │                                   └─ epoll_wait(epfd, events, maxevents, 5000)
    │   │                                       └─ 内核 hrtimer → 线程休眠 5 秒
    │
    ├─ 5 秒后 OS 唤醒线程:
    │   ├─ Wheel::poll(now) → 找到到期的 entry
    │   ├─ entry.fire(Ok(()))
    │   │   ├─ result ← Ok(())
    │   │   ├─ state ← STATE_DEREGISTERED
    │   │   └─ 取出 waker
    │   └─ waker_list.wake_all()  ← 把任务放回调度队列
    │
    └─ 任务被再次 poll:
        ├─ StateCell::read_state()
        │   └─ cur_state == STATE_DEREGISTERED → 读取 result = Ok(())
        └─ 返回 Poll::Ready(())
</code></pre></div></div>

<pre><code class="language-mermaid">sequenceDiagram
    participant User as User Task
    participant Sleep as Sleep Future
    participant Entry as TimerEntry
    participant Cell as StateCell
    participant Wheel as Hashed Wheel
    participant Driver as Time Driver
    participant OS as OS

    User-&gt;&gt;Sleep: .await → poll(cx)
    Sleep-&gt;&gt;Entry: poll_elapsed(cx)
    Entry-&gt;&gt;Wheel: reset(deadline) → insert to Wheel
    Entry-&gt;&gt;Cell: poll(waker) → register waker
    Cell--&gt;&gt;Entry: Poll::Pending
    Entry--&gt;&gt;Sleep: Poll::Pending
    Sleep--&gt;&gt;User: Poll::Pending

    Note over User: Worker 线程继续服务其他任务

    Driver-&gt;&gt;Wheel: next_expiration_time() → 5000ms
    Driver-&gt;&gt;OS: park_timeout(5000ms)
    Note over OS: ... 5 秒 ...
    OS--&gt;&gt;Driver: 唤醒

    Driver-&gt;&gt;Wheel: poll(now) → 到期 entry
    Driver-&gt;&gt;Cell: fire(Ok(()))
    Cell--&gt;&gt;Driver: Some(waker)
    Driver-&gt;&gt;Driver: waker.wake()

    Note over User: 任务被重新调度
    User-&gt;&gt;Sleep: poll(cx)
    Sleep-&gt;&gt;Cell: read_state()
    Cell--&gt;&gt;Sleep: Poll::Ready(Ok(()))
    Sleep--&gt;&gt;User: Poll::Ready(())
</code></pre>

<h2 id="九取消重置与交互细节">九、取消、重置与交互细节</h2>

<p><code class="language-plaintext highlighter-rouge">Sleep</code> 的设计还支持两个重要的交互场景：</p>

<p><strong>取消</strong>：直接 drop 掉 <code class="language-plaintext highlighter-rouge">Sleep</code> 实例即可。<code class="language-plaintext highlighter-rouge">TimerEntry</code> 的 <code class="language-plaintext highlighter-rouge">PinnedDrop</code> 实现会调用 <code class="language-plaintext highlighter-rouge">cancel()</code>，将定时器从时间轮中移除<sup id="fnref:4:1"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>。没有额外的清理工作。</p>

<p><strong>重置</strong>：可以通过 <code class="language-plaintext highlighter-rouge">Pin&lt;&amp;mut Sleep&gt;::reset(new_deadline)</code> 来改变 <code class="language-plaintext highlighter-rouge">Sleep</code> 的到期时间，而无需创建新的 Future<sup id="fnref:2:1"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>。内部实现会先尝试原子地延后到期时间（<code class="language-plaintext highlighter-rouge">extend_expiration</code>），如果 CAS 成功则无需操作时间轮；如果提前了到期时间，则会重新走 <code class="language-plaintext highlighter-rouge">reregister</code> 路径。</p>

<p>这种”乐观延后”的优化利用了定时器一个常见的使用模式——超时通常在操作开始时设置，然后在操作结束前被取消。延后触发时，旧的 slot 位置依然是”安全的”（不会过早触发），所以可以用 CAS 无锁更新。</p>

<h2 id="总结">总结</h2>

<p><code class="language-plaintext highlighter-rouge">tokio::time::sleep</code> 的实现体现了异步系统设计的核心分层：</p>

<ol>
  <li><strong>接口层</strong>（<code class="language-plaintext highlighter-rouge">sleep()</code> / <code class="language-plaintext highlighter-rouge">Sleep</code>）：对用户暴露一个 <code class="language-plaintext highlighter-rouge">.await</code> 即可等待的 Future</li>
  <li><strong>运行时层</strong>（<code class="language-plaintext highlighter-rouge">TimerEntry</code> / <code class="language-plaintext highlighter-rouge">TimerShared</code> / <code class="language-plaintext highlighter-rouge">StateCell</code>）：用一个原子状态机管理定时器的生命周期，协调用户端和驱动端的并发访问</li>
  <li><strong>数据结构层</strong>（<code class="language-plaintext highlighter-rouge">Wheel</code>）：用 6 层哈希时间轮实现 O(1) 插入和高效的到期检测</li>
  <li><strong>驱动层</strong>（<code class="language-plaintext highlighter-rouge">Driver::park_internal</code>）：通过操作系统的定时唤醒（<code class="language-plaintext highlighter-rouge">park_timeout</code>）驱动整个机制运转</li>
</ol>

<p>最终的效果是：<strong>一个单线程可以同时管理成千上万个定时器，线程只会在最近的 deadline 时休眠，期间 CPU 可以服务其他任务或进入省电状态</strong>。<code class="language-plaintext highlighter-rouge">tokio::time::sleep</code> 的高效并发能力不是来自魔法，而是来自一条从 Future 到 OS 的精心设计的链路。</p>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>之前的文章：《Rust async/await 的底层契约：从 Future::poll 到 Tokio 运行时》，2026-04-30。详细阐述了 <code class="language-plaintext highlighter-rouge">Future::poll</code>、<code class="language-plaintext highlighter-rouge">Context</code>、<code class="language-plaintext highlighter-rouge">Waker</code> 组成的核心协议。 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">Sleep</code> 结构定义，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/time/sleep.rs"><code class="language-plaintext highlighter-rouge">tokio/src/time/sleep.rs</code></a>。包含 <code class="language-plaintext highlighter-rouge">Sleep</code> 的完整 Future 实现，<code class="language-plaintext highlighter-rouge">sleep()</code> 和 <code class="language-plaintext highlighter-rouge">sleep_until()</code> 的构造逻辑。 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:3">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">Timer</code> enum 定义，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/mod.rs#L437-L442"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/mod.rs</code></a>。<code class="language-plaintext highlighter-rouge">Timer</code> 是 <code class="language-plaintext highlighter-rouge">Traditional(TimerEntry)</code> 和 <code class="language-plaintext highlighter-rouge">Alternative(time_alt::Timer)</code> 的枚举封装。 <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">TimerEntry</code> 结构定义，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/time/entry.rs#L287-L310"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/time/entry.rs</code></a>。包含 <code class="language-plaintext highlighter-rouge">TimerEntry</code> 的关键方法：<code class="language-plaintext highlighter-rouge">new</code>、<code class="language-plaintext highlighter-rouge">reset</code>、<code class="language-plaintext highlighter-rouge">poll_elapsed</code>、<code class="language-plaintext highlighter-rouge">cancel</code>。 <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:4:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:5">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">TimerShared</code> 结构定义，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/time/entry.rs#L336-L360"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/time/entry.rs</code></a>。前端与驱动端共享的状态，包含侵入式链表指针、<code class="language-plaintext highlighter-rouge">registered_when</code> 和 <code class="language-plaintext highlighter-rouge">StateCell</code>。 <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">TimerEntry::poll_elapsed</code> 方法，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/time/entry.rs#L598-L617"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/time/entry.rs</code></a>。首次 poll 时注册到时间轮，后续 poll 时检查状态。 <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:entry-rs-reset">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">TimerEntry::reset</code> 方法，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/time/entry.rs#L626-L649"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/time/entry.rs</code></a>。<code class="language-plaintext highlighter-rouge">reset()</code> 先尝试 <code class="language-plaintext highlighter-rouge">extend_expiration</code> 乐观路径，失败后通过 <code class="language-plaintext highlighter-rouge">inner.into()</code> 将 <code class="language-plaintext highlighter-rouge">&amp;TimerShared</code> 转为 <code class="language-plaintext highlighter-rouge">NonNull&lt;TimerShared&gt;</code> 并调用 <code class="language-plaintext highlighter-rouge">Handle::reregister()</code>。 <a href="#fnref:entry-rs-reset" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:reregister">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">Handle::reregister</code> 方法，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/time/mod.rs#L398-L435"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/time/mod.rs</code></a>。接收 <code class="language-plaintext highlighter-rouge">NonNull&lt;TimerShared&gt;</code>，通过 <code class="language-plaintext highlighter-rouge">entry.as_ref().handle()</code> 创建 <code class="language-plaintext highlighter-rouge">TimerHandle</code>，调用 <code class="language-plaintext highlighter-rouge">lock.wheel.insert(entry)</code> 重新插入时间轮。 <a href="#fnref:reregister" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:entry-rs-timerhandle">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">TimerHandle</code> 结构定义和 <code class="language-plaintext highlighter-rouge">TimerShared::handle()</code> 方法，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/time/entry.rs#L183-L188"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/time/entry.rs</code></a>。<code class="language-plaintext highlighter-rouge">TimerHandle</code> 是 <code class="language-plaintext highlighter-rouge">NonNull&lt;TimerShared&gt;</code> 的包装，不拥有数据，由 unsafe 契约确保安全使用。 <a href="#fnref:entry-rs-timerhandle" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:wheel_insert">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">Wheel::insert</code> 和 <code class="language-plaintext highlighter-rouge">Level::add_entry</code>，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/time/wheel/mod.rs#L88-L112"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/time/wheel/mod.rs</code></a> 以及 <a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/time/wheel/level.rs#L122-L128"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/time/wheel/level.rs</code></a>。<code class="language-plaintext highlighter-rouge">Wheel::insert()</code> 计算层级后调用 <code class="language-plaintext highlighter-rouge">Level::add_entry()</code>，后者将 <code class="language-plaintext highlighter-rouge">TimerHandle</code> push 到对应 slot 的侵入式链表中。 <a href="#fnref:wheel_insert" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7">
      <p>Tokio 文档，<code class="language-plaintext highlighter-rouge">sleep()</code> 的 panic 说明，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/time/sleep.rs#L100-L113"><code class="language-plaintext highlighter-rouge">tokio/src/time/sleep.rs</code></a>。解释了为什么 <code class="language-plaintext highlighter-rouge">rt.block_on(sleep(...))</code> 会 panic 而 <code class="language-plaintext highlighter-rouge">rt.block_on(async {sleep(...).await})</code> 不会。 <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8">
      <p>George Varghese 和 Tony Lauck，《Hashed and Hierarchical Timing Wheels: Data Structures for the Efficient Implementation of a Timer Facility》，1996。论文链接：<a href="http://www.cs.columbia.edu/~nahum/w6998/papers/ton97-timing-wheels.pdf">Hashed and Hierarchical Timing Wheels</a>。Tokio 的哈希时间轮数据结构直接引用了此论文的设计。 <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:8:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:8:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:9">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">Wheel</code> 结构定义与文档，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/time/wheel/mod.rs"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/time/wheel/mod.rs</code></a>。6 层、每层 64 slot 的哈希时间轮实现，以及 <code class="language-plaintext highlighter-rouge">NUM_LEVELS</code> 和 <code class="language-plaintext highlighter-rouge">MAX_DURATION</code> 常量。 <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:9:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:9:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:18">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">Level</code> 和 <code class="language-plaintext highlighter-rouge">EntryList</code> 定义，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/time/wheel/level.rs#L6-L15"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/time/wheel/level.rs</code></a>。<code class="language-plaintext highlighter-rouge">Level</code> 包含 <code class="language-plaintext highlighter-rouge">occupied</code> 位图和 <code class="language-plaintext highlighter-rouge">slot: [EntryList; 64]</code>；<code class="language-plaintext highlighter-rouge">EntryList</code> 是 <code class="language-plaintext highlighter-rouge">LinkedList&lt;TimerShared, TimerShared&gt;</code>，定义在 <a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/time/entry.rs#L195"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/time/entry.rs</code></a>。TimerShared 通过侵入式链表的 <code class="language-plaintext highlighter-rouge">pointers</code> 字段直接链接在 slot 的链表中。 <a href="#fnref:18" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:10">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">StateCell</code> 结构及其方法，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/time/entry.rs#L91-L278"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/time/entry.rs</code></a>。包含 <code class="language-plaintext highlighter-rouge">poll</code>、<code class="language-plaintext highlighter-rouge">mark_pending</code>（CAS 循环）、<code class="language-plaintext highlighter-rouge">fire</code>、<code class="language-plaintext highlighter-rouge">set_expiration</code>、<code class="language-plaintext highlighter-rouge">extend_expiration</code> 等原子操作。 <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:10:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:10:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:10:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:10:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a></p>
    </li>
    <li id="fn:11">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">Driver::park_internal</code> 和 <code class="language-plaintext highlighter-rouge">Handle::process_at_time</code>，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/time/mod.rs#L213-L337"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/time/mod.rs</code></a>。运行时驱动定时器的核心逻辑：计算 next_wake、带超时 park、处理到期定时器、批量唤醒。 <a href="#fnref:11" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:11:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:11:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:11:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:11:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a></p>
    </li>
    <li id="fn:16">
      <p>关于 per-timer StateCell 的权衡分析：每个 timer 独立持有 <code class="language-plaintext highlighter-rouge">AtomicU64</code>，Driver 遍历 slot 时逐 timer CAS。对比替代方案（per-slot Mutex），CAS 方案在零锁争用和 O(N) 线性代价上占据优势，且 slot 的 1ms 宽度提供了天然的保护层，使遍历时间（~10ns/timer）不会影响精度。此分析基于 x86-64 上 <code class="language-plaintext highlighter-rouge">lock cmpxchgq</code> 指令的理论延迟约 10-20ns（无 cache miss 时），以及 level-0 slot 宽度 1ms 的事实。 <a href="#fnref:16" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:19">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">runtime::driver::Driver::new</code> 和 <code class="language-plaintext highlighter-rouge">Handle</code> 结构定义，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/driver.rs#L108-L117"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/driver.rs</code></a> 以及 <a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/driver.rs#L52-L59"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/driver.rs</code></a>。Time Driver 和 I/O Driver 分别独立初始化，通过 <code class="language-plaintext highlighter-rouge">create_io_stack</code> 和 <code class="language-plaintext highlighter-rouge">create_time_driver</code> 堆叠。Handle 包含 <code class="language-plaintext highlighter-rouge">io</code>、<code class="language-plaintext highlighter-rouge">time</code>、<code class="language-plaintext highlighter-rouge">signal</code> 三个子句柄，通过 <code class="language-plaintext highlighter-rouge">Arc</code> 分发给所有 worker 线程。 <a href="#fnref:19" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:21">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">current_thread</code> 调度器的 <code class="language-plaintext highlighter-rouge">Core</code> 结构，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/scheduler/current_thread/mod.rs#L67-L90"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/scheduler/current_thread/mod.rs</code></a>。Driver 以 <code class="language-plaintext highlighter-rouge">Option&lt;Driver&gt;</code> 形式存储，park 时通过 <code class="language-plaintext highlighter-rouge">take()</code> 取出。 <a href="#fnref:21" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:22">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">multi_thread</code> 调度器的 <code class="language-plaintext highlighter-rouge">Shared</code> 结构和 <code class="language-plaintext highlighter-rouge">Parker::park_timeout</code> 方法，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/scheduler/multi_thread/park.rs#L40-L44"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/scheduler/multi_thread/park.rs</code></a>（Shared）及 <a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/scheduler/multi_thread/park.rs#L83-L93"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/scheduler/multi_thread/park.rs</code></a>（park_timeout）。<code class="language-plaintext highlighter-rouge">TryLock&lt;Driver&gt;</code> 保证同一时刻只有一个 worker 能持有 Driver，但其他 worker 仍可无锁执行 task。 <a href="#fnref:22" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:20">
      <p>Linux 内核源码，<code class="language-plaintext highlighter-rouge">ep_poll</code> 中 <code class="language-plaintext highlighter-rouge">schedule_hrtimeout_range</code> 调用，<a href="https://github.com/torvalds/linux/blob/master/fs/eventpoll.c#L2027-L2031"><code class="language-plaintext highlighter-rouge">fs/eventpoll.c</code></a>。<code class="language-plaintext highlighter-rouge">epoll_wait</code> 的 <code class="language-plaintext highlighter-rouge">timeout_ms</code> 参数经 <code class="language-plaintext highlighter-rouge">ep_timeout_to_timespec()</code> 转为 <code class="language-plaintext highlighter-rouge">timespec64</code> 绝对时间后，由 <code class="language-plaintext highlighter-rouge">schedule_hrtimeout_range()</code> 注册到 hrtimer 红黑树，实现纳秒精度的超时唤醒。 <a href="#fnref:20" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:12">
      <p>Linux 内核源码，<code class="language-plaintext highlighter-rouge">hrtimer_clock_base</code> 结构定义，<a href="https://github.com/torvalds/linux/blob/master/include/linux/hrtimer_defs.h#L27-L35"><code class="language-plaintext highlighter-rouge">include/linux/hrtimer_defs.h</code></a>。hrtimer 使用红黑树（<code class="language-plaintext highlighter-rouge">timerqueue_linked_head</code>）而非哈希时间轮来组织定时器。 <a href="#fnref:12" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:13">
      <p>Linux 内核源码，<code class="language-plaintext highlighter-rouge">enqueue_hrtimer</code>、<code class="language-plaintext highlighter-rouge">hrtimer_wakeup</code>、<code class="language-plaintext highlighter-rouge">hrtimer_interrupt</code>，<a href="https://github.com/torvalds/linux/blob/master/kernel/time/hrtimer.c"><code class="language-plaintext highlighter-rouge">kernel/time/hrtimer.c</code></a>。<code class="language-plaintext highlighter-rouge">enqueue_hrtimer()</code>（行 1096-1104）将定时器插入红黑树；<code class="language-plaintext highlighter-rouge">hrtimer_wakeup()</code>（行 2184-2193）在定时器触发时唤醒进程；<code class="language-plaintext highlighter-rouge">hrtimer_interrupt()</code>（行 2083-2114）是时钟中断的处理入口。 <a href="#fnref:13" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:13:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:13:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:13:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a></p>
    </li>
    <li id="fn:14">
      <p>Linux 内核源码，timer wheel 实现，<a href="https://github.com/torvalds/linux/blob/master/kernel/time/timer.c"><code class="language-plaintext highlighter-rouge">kernel/time/timer.c</code></a>。行 64-150 的注释详细描述了低精度定时器的层级哈希轮设计：8-9 层、每层 64 bucket（<code class="language-plaintext highlighter-rouge">LVL_SIZE=64</code>，<code class="language-plaintext highlighter-rouge">LVL_DEPTH=8/9</code>），与 Tokio 的实现思想同源。 <a href="#fnref:14" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:17">
      <p><code class="language-plaintext highlighter-rouge">epoll_wait(timeout)</code> 在本场景中等价于 <code class="language-plaintext highlighter-rouge">nanosleep(timeout)</code> + <code class="language-plaintext highlighter-rouge">epoll_wait(-1)</code>——都是”先睡 timeout 时间，醒来后再 epoll 检查 I/O 事件”。Tokio 选前者只是因为一个系统调用比两个高效，功能上没有区别。 <a href="#fnref:17" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:17:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:15">
      <p>Tokio 源码，<code class="language-plaintext highlighter-rouge">TimeSource</code> 实现，<a href="https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/time/source.rs"><code class="language-plaintext highlighter-rouge">tokio/src/runtime/time/source.rs</code></a>。负责 <code class="language-plaintext highlighter-rouge">Instant</code> 和 <code class="language-plaintext highlighter-rouge">u64</code> tick 之间的转换，向上取整到毫秒精度。 <a href="#fnref:15" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:15:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="rust" /><category term="tokio" /><summary type="html"><![CDATA[从 Future::poll 到哈希时间轮，完整拆解 tokio::time::sleep 的源码实现与 Driver 定时机制。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://weinan.tech/images/og/tokio-async.png" /><media:content medium="image" url="https://weinan.tech/images/og/tokio-async.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Rust async/await 的底层契约：从 Future::poll 到 Tokio 运行时</title><link href="https://weinan.tech/2026/04/30/rust-async-await-future-poll-tokio-runtime.html" rel="alternate" type="text/html" title="Rust async/await 的底层契约：从 Future::poll 到 Tokio 运行时" /><published>2026-04-30T00:00:00+08:00</published><updated>2026-04-30T00:00:00+08:00</updated><id>https://weinan.tech/2026/04/30/rust-async-await-future-poll-tokio-runtime</id><content type="html" xml:base="https://weinan.tech/2026/04/30/rust-async-await-future-poll-tokio-runtime.html"><![CDATA[<style>
/* Mermaid diagram container */
.mermaid-container {
  position: relative;
  display: block;
  cursor: pointer;
  transition: opacity 0.2s;
  max-width: 100%;
  margin: 20px 0;
  overflow-x: auto;
}

.mermaid-container:hover {
  opacity: 0.8;
}

.mermaid-container svg {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Modal overlay */
.mermaid-modal {
  display: none;
  position: fixed;
  z-index: 9999;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9);
  animation: fadeIn 0.3s;
}

.mermaid-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Modal content */
.mermaid-modal-content {
  position: relative;
  width: 90vw;
  height: 90vh;
  overflow: hidden;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
}

.mermaid-modal-diagram {
  transform-origin: center center;
  transition: transform 0.2s ease;
  display: inline-block;
  min-width: 100%;
  cursor: grab;
  user-select: none;
}

.mermaid-modal-diagram.dragging {
  cursor: grabbing;
  transition: none;
}

.mermaid-modal-diagram svg {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;
}

/* Control buttons */
.mermaid-controls {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
  z-index: 10000;
}

.mermaid-btn {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
  color: #333;
  font-weight: 500;
}

.mermaid-btn:hover {
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Close button */
.mermaid-close {
  background: #f44336;
  color: white;
  border: none;
}

.mermaid-close:hover {
  background: #d32f2f;
}

/* Zoom indicator */
.mermaid-zoom-level {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 10000;
}
</style>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  mermaid.initialize({
    startOnLoad: false,
    theme: 'default',
    securityLevel: 'loose',
    htmlLabels: true,
    themeVariables: {
      fontSize: '14px'
    }
  });

  let currentZoom = 1;
  let currentModal = null;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let translateX = 0;
  let translateY = 0;

  // Create modal HTML
  function createModal() {
    const modal = document.createElement('div');
    modal.className = 'mermaid-modal';
    modal.innerHTML = `
      <div class="mermaid-controls">
        <button class="mermaid-btn zoom-in">放大 +</button>
        <button class="mermaid-btn zoom-out">缩小 -</button>
        <button class="mermaid-btn zoom-reset">重置</button>
        <button class="mermaid-btn mermaid-close">关闭 ✕</button>
      </div>
      <div class="mermaid-modal-content">
        <div class="mermaid-modal-diagram"></div>
      </div>
      <div class="mermaid-zoom-level">100%</div>
    `;
    document.body.appendChild(modal);
    return modal;
  }

  // Show modal with diagram
  function showModal(diagramContent) {
    if (!currentModal) {
      currentModal = createModal();
      setupModalEvents();
    }

    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.innerHTML = diagramContent;

    // Remove any width/height attributes from SVG to make it responsive
    const svg = modalDiagram.querySelector('svg');
    if (svg) {
      svg.removeAttribute('width');
      svg.removeAttribute('height');
      svg.style.width = '100%';
      svg.style.height = 'auto';
    }

    // Setup drag functionality
    setupDrag(modalDiagram);

    currentModal.classList.add('active');
    currentZoom = 1;
    resetPosition();
    updateZoom();
    document.body.style.overflow = 'hidden';
  }

  // Hide modal
  function hideModal() {
    if (currentModal) {
      currentModal.classList.remove('active');
      document.body.style.overflow = '';
    }
  }

  // Update zoom level
  function updateZoom() {
    if (!currentModal) return;
    const diagram = currentModal.querySelector('.mermaid-modal-diagram');
    const zoomLevel = currentModal.querySelector('.mermaid-zoom-level');
    diagram.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentZoom})`;
    zoomLevel.textContent = `${Math.round(currentZoom * 100)}%`;
  }

  // Reset position when zoom changes
  function resetPosition() {
    translateX = 0;
    translateY = 0;
  }

  // Setup drag functionality
  function setupDrag(element) {
    element.addEventListener('mousedown', startDrag);
    element.addEventListener('touchstart', startDrag);
  }

  function startDrag(e) {
    if (e.type === 'mousedown' && e.button !== 0) return; // Only left click

    isDragging = true;
    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.classList.add('dragging');

    if (e.type === 'touchstart') {
      startX = e.touches[0].clientX - translateX;
      startY = e.touches[0].clientY - translateY;
    } else {
      startX = e.clientX - translateX;
      startY = e.clientY - translateY;
    }

    document.addEventListener('mousemove', drag);
    document.addEventListener('touchmove', drag);
    document.addEventListener('mouseup', stopDrag);
    document.addEventListener('touchend', stopDrag);
  }

  function drag(e) {
    if (!isDragging) return;
    e.preventDefault();

    if (e.type === 'touchmove') {
      translateX = e.touches[0].clientX - startX;
      translateY = e.touches[0].clientY - startY;
    } else {
      translateX = e.clientX - startX;
      translateY = e.clientY - startY;
    }

    updateZoom();
  }

  function stopDrag() {
    isDragging = false;
    const modalDiagram = currentModal?.querySelector('.mermaid-modal-diagram');
    if (modalDiagram) {
      modalDiagram.classList.remove('dragging');
    }
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('touchmove', drag);
    document.removeEventListener('mouseup', stopDrag);
    document.removeEventListener('touchend', stopDrag);
  }

  // Setup modal event listeners
  function setupModalEvents() {
    if (!currentModal) return;

    // Close button
    currentModal.querySelector('.mermaid-close').addEventListener('click', hideModal);

    // Zoom buttons
    currentModal.querySelector('.zoom-in').addEventListener('click', () => {
      currentZoom = Math.min(currentZoom + 0.25, 3);
      updateZoom();
    });

    currentModal.querySelector('.zoom-out').addEventListener('click', () => {
      currentZoom = Math.max(currentZoom - 0.25, 0.5);
      updateZoom();
    });

    currentModal.querySelector('.zoom-reset').addEventListener('click', () => {
      currentZoom = 1;
      resetPosition();
      updateZoom();
    });

    // Close on background click
    currentModal.addEventListener('click', (e) => {
      if (e.target === currentModal) {
        hideModal();
      }
    });

    // Close on ESC key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && currentModal.classList.contains('active')) {
        hideModal();
      }
    });
  }

  // Convert Jekyll-rendered code blocks to mermaid divs
  document.addEventListener('DOMContentLoaded', async function() {
    const codeBlocks = document.querySelectorAll('code.language-mermaid');

    for (const codeBlock of codeBlocks) {
      const pre = codeBlock.parentElement;
      const container = document.createElement('div');
      container.className = 'mermaid-container';

      const mermaidDiv = document.createElement('div');
      mermaidDiv.className = 'mermaid';
      mermaidDiv.textContent = codeBlock.textContent;

      container.appendChild(mermaidDiv);
      pre.replaceWith(container);
    }

    // Render all mermaid diagrams
    try {
      await mermaid.run({
        querySelector: '.mermaid'
      });
      console.log('Mermaid diagrams rendered successfully');
    } catch (error) {
      console.error('Mermaid rendering error:', error);
    }

    // Add click handlers to rendered diagrams
    document.querySelectorAll('.mermaid-container').forEach((container, index) => {
      // Find the rendered SVG inside the container
      const svg = container.querySelector('svg');
      if (!svg) {
        console.warn(`No SVG found in container ${index}`);
        return;
      }

      // Make the container clickable
      container.style.cursor = 'pointer';
      container.title = '点击查看大图';

      container.addEventListener('click', function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Clone the SVG for the modal
        const svgClone = svg.cloneNode(true);
        const tempDiv = document.createElement('div');
        tempDiv.appendChild(svgClone);

        console.log('Opening modal for diagram', index);
        showModal(tempDiv.innerHTML);
      });

      console.log(`Click handler added to diagram ${index}`);
    });
  });
</script>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">async fn</code> 看起来像同步函数，但它真正交给运行时的不是一段会自己运行的代码，而是一个需要被反复 <code class="language-plaintext highlighter-rouge">poll</code> 的状态机。</p>
</blockquote>

<h2 id="前言">前言</h2>

<p>Rust 的异步编程很容易给人一种错觉：既然 <code class="language-plaintext highlighter-rouge">.await</code> 写起来像普通函数调用，那么运行时似乎只是在背后替我们开了很多轻量线程。这个理解只对了一小部分。真正的关键不是线程，而是 <strong><code class="language-plaintext highlighter-rouge">Future::poll</code> 这条契约</strong>。</p>

<p>在 Rust 标准库里，<code class="language-plaintext highlighter-rouge">Future</code> 被描述为一种尚未完成的异步计算；它不会自动推进，只有被主动 <code class="language-plaintext highlighter-rouge">poll</code> 时才会继续执行。<code class="language-plaintext highlighter-rouge">poll</code> 如果暂时无法产出结果，就返回 <code class="language-plaintext highlighter-rouge">Poll::Pending</code>，并通过 <code class="language-plaintext highlighter-rouge">Context</code> 里的 <code class="language-plaintext highlighter-rouge">Waker</code> 安排后续唤醒<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>。</p>

<p>这篇文章从 Rust 源码出发，再回到 Tokio 的运行时模型，拆开三个问题：</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">async fn</code> 最终为什么会变成状态机？</li>
  <li><code class="language-plaintext highlighter-rouge">Future::poll</code>、<code class="language-plaintext highlighter-rouge">Context</code>、<code class="language-plaintext highlighter-rouge">Waker</code> 分别承担什么职责？</li>
  <li>为什么在 async 任务里直接调用阻塞 I/O 会拖垮运行时？</li>
</ol>

<hr />

<h2 id="一先把边界划清楚rust-定义契约tokio-负责驱动">一、先把边界划清楚：Rust 定义契约，Tokio 负责驱动</h2>

<p>Rust 语言和标准库提供的是异步抽象的核心接口：<code class="language-plaintext highlighter-rouge">Future</code>、<code class="language-plaintext highlighter-rouge">Poll</code>、<code class="language-plaintext highlighter-rouge">Context</code>、<code class="language-plaintext highlighter-rouge">Waker</code>、<code class="language-plaintext highlighter-rouge">Pin</code>。Tokio 这样的运行时则负责把这些接口接到实际的调度器、I/O 事件源和线程池上。</p>

<p>下图可以作为整篇文章的地图：</p>

<pre><code class="language-mermaid">flowchart TD
    A["async fn / async block"] --&gt; B["编译器生成 Future 状态机"]
    B --&gt; C["Future::poll"]
    C --&gt; D{"是否就绪"}
    D --&gt;|"Ready(value)"| E["返回结果"]
    D --&gt;|"Pending"| F["保存 Waker"]
    F --&gt; G["Tokio Reactor 等待 I/O 事件"]
    G --&gt; H["事件就绪后调用 wake"]
    H --&gt; I["Tokio Executor 重新调度任务"]
    I --&gt; C
</code></pre>

<p>这里有一个非常重要的分层：</p>

<ul>
  <li>Rust 标准库规定：<code class="language-plaintext highlighter-rouge">poll</code> 不能阻塞；未就绪时必须安排唤醒。</li>
  <li>编译器负责：把 <code class="language-plaintext highlighter-rouge">async</code> 代码降成可以挂起和恢复的 coroutine 状态机。</li>
  <li>Tokio 负责：创建任务、调度任务、监听 I/O、在事件就绪时调用 <code class="language-plaintext highlighter-rouge">Waker</code>。</li>
</ul>

<p>也就是说，Tokio 不是在执行一段普通函数，而是在驱动一个个实现了 <code class="language-plaintext highlighter-rouge">Future</code> 的状态机。</p>

<hr />

<h2 id="二futurepoll异步系统的唯一入口">二、Future::poll：异步系统的唯一入口</h2>

<p>Rust 源码中 <code class="language-plaintext highlighter-rouge">Future</code> 的核心方法只有一个：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">poll</span><span class="p">(</span><span class="k">self</span><span class="p">:</span> <span class="nb">Pin</span><span class="o">&lt;&amp;</span><span class="k">mut</span> <span class="k">Self</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">cx</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Context</span><span class="o">&lt;</span><span class="nv">'_</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Poll</span><span class="o">&lt;</span><span class="k">Self</span><span class="p">::</span><span class="n">Output</span><span class="o">&gt;</span><span class="p">;</span>
</code></pre></div></div>

<p>这个签名里有三个关键词：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Pin&lt;&amp;mut Self&gt;</code>：状态机被轮询时不能随意移动。</li>
  <li><code class="language-plaintext highlighter-rouge">Context&lt;'_&gt;</code>：运行时传入当前任务的上下文。</li>
  <li><code class="language-plaintext highlighter-rouge">Poll&lt;Self::Output&gt;</code>：本次轮询的结果，要么完成，要么等待。</li>
</ul>

<p><code class="language-plaintext highlighter-rouge">Poll</code> 本身也非常直接，只有两个状态：<code class="language-plaintext highlighter-rouge">Ready(T)</code> 和 <code class="language-plaintext highlighter-rouge">Pending</code>。源码注释明确写到：返回 <code class="language-plaintext highlighter-rouge">Pending</code> 时，函数还必须确保当前任务会在可以继续推进时被唤醒<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>。</p>

<p>这意味着 <code class="language-plaintext highlighter-rouge">Pending</code> 不是一句“我还没好”的空话，而是一份责任：</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant Runtime as Tokio Executor
    participant Future as Future 状态机
    participant Context as Context / Waker
    participant Source as I/O 或其他事件源

    Runtime-&gt;&gt;Future: poll(cx)
    Future-&gt;&gt;Source: 尝试读取或检查状态
    Source--&gt;&gt;Future: 暂时未就绪
    Future-&gt;&gt;Context: 保存 cx.waker()
    Future--&gt;&gt;Runtime: Poll::Pending
    Source--&gt;&gt;Context: 后续事件就绪
    Context--&gt;&gt;Runtime: wake()
    Runtime-&gt;&gt;Future: 再次 poll(cx)
    Future--&gt;&gt;Runtime: Poll::Ready(value)
</code></pre>

<p>如果一个 Future 返回 <code class="language-plaintext highlighter-rouge">Pending</code>，却没有保存或安排 <code class="language-plaintext highlighter-rouge">Waker</code>，这个任务就可能永远睡下去。反过来，如果运行时在没有唤醒信号时疯狂重复 <code class="language-plaintext highlighter-rouge">poll</code>，就会变成忙等，浪费 CPU。</p>

<p>Rust 标准库的文档也特别强调：Future 是惰性的，必须被主动 <code class="language-plaintext highlighter-rouge">poll</code> 才能推进；<code class="language-plaintext highlighter-rouge">poll</code> 不应该在紧密循环中反复调用，而应该在 Future 表示可以继续推进时再调用<sup id="fnref:1:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>。</p>

<hr />

<h2 id="三context-与-waker状态机和运行时之间的回拨协议">三、Context 与 Waker：状态机和运行时之间的回拨协议</h2>

<p><code class="language-plaintext highlighter-rouge">Context</code> 当前最核心的作用，就是提供一个 <code class="language-plaintext highlighter-rouge">&amp;Waker</code>。源码里的 <code class="language-plaintext highlighter-rouge">Context</code> 结构体保存了 <code class="language-plaintext highlighter-rouge">waker</code> 字段，并通过 <code class="language-plaintext highlighter-rouge">Context::waker()</code> 返回它<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>。</p>

<p><code class="language-plaintext highlighter-rouge">Waker</code> 则是状态机反向通知运行时的句柄。Rust 源码对它的描述很清楚：<code class="language-plaintext highlighter-rouge">Waker</code> 用于通知 executor 某个任务已经可以重新运行；如果 Future 返回 <code class="language-plaintext highlighter-rouge">Poll::Pending</code>，它必须以某种方式保存 waker，并在 Future 应该再次被 <code class="language-plaintext highlighter-rouge">poll</code> 时调用 <code class="language-plaintext highlighter-rouge">wake()</code><sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>。</p>

<p>这套设计的妙处在于解耦：</p>

<ul>
  <li>Future 不需要知道自己运行在哪个 executor 上。</li>
  <li>Reactor 不需要知道等待事件的是哪个业务函数。</li>
  <li>Executor 不需要理解 socket、timer 或文件 I/O 的具体细节。</li>
</ul>

<p>它们只共享一个最小协议：<strong>未就绪时保存 waker，就绪后调用 wake，运行时随后重新 poll</strong>。</p>

<p><code class="language-plaintext highlighter-rouge">Waker::wake()</code> 的实现也印证了这一点。源码中真正的唤醒动作会通过 <code class="language-plaintext highlighter-rouge">RawWaker</code> 的 vtable 委托给 executor 提供的实现<sup id="fnref:4:1"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>。换句话说，Rust 标准库定义了“按钮”的形状，但按钮背后接到哪个调度队列，由运行时决定。</p>

<hr />

<h2 id="四async-fn-如何变成状态机">四、async fn 如何变成状态机</h2>

<p><code class="language-plaintext highlighter-rouge">async fn</code> 不会在调用时立即执行完函数体。调用它会得到一个 Future。这个 Future 内部保存了必要的局部变量、当前执行到哪个挂起点，以及恢复执行所需的信息。</p>

<p>Rust 编译器的 coroutine transform pass 对这个过程有一段非常直白的说明：它会把 coroutine 转换成状态机，最终结构大致包含 upvars、状态字段，以及跨挂起点仍然存活的 MIR locals<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>。</p>

<p>源码注释给出的示意结构可以简化理解为：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">Coroutine</span> <span class="p">{</span>
    <span class="n">upvars</span><span class="p">:</span> <span class="o">...</span><span class="p">,</span>
    <span class="n">state</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="n">mir_locals</span><span class="p">:</span> <span class="o">...</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>其中 <code class="language-plaintext highlighter-rouge">state</code> 至少有几个保留状态：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">0</code>：还没有开始执行。</li>
  <li><code class="language-plaintext highlighter-rouge">1</code>：已经返回或完成。</li>
  <li><code class="language-plaintext highlighter-rouge">2</code>：已经 poisoned。</li>
  <li>其他状态：对应具体的挂起点。</li>
</ul>

<p>当 <code class="language-plaintext highlighter-rouge">async fn</code> 执行到 <code class="language-plaintext highlighter-rouge">.await</code>，如果被等待的 Future 还没完成，外层 Future 就必须挂起。编译器会让跨越这个挂起点仍然需要的局部变量进入状态机结构体，并把恢复点记录下来。</p>

<p>这就是为什么下面这段代码看起来像顺序执行：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">fn</span> <span class="nf">read_then_parse</span><span class="p">(</span><span class="n">socket</span><span class="p">:</span> <span class="n">TcpStream</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="n">Message</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">bytes</span> <span class="o">=</span> <span class="nf">read_frame</span><span class="p">(</span><span class="n">socket</span><span class="p">)</span><span class="k">.await</span><span class="o">?</span><span class="p">;</span>
    <span class="nf">parse_message</span><span class="p">(</span><span class="n">bytes</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>但编译器看到的是类似这样的状态流转：</p>

<pre><code class="language-mermaid">stateDiagram-v2
    [*] --&gt; Start
    Start --&gt; WaitingRead: read_frame().poll() 返回 Pending
    WaitingRead --&gt; WaitingRead: 被唤醒后再次 Pending
    WaitingRead --&gt; Parsing: read_frame().poll() 返回 Ready
    Parsing --&gt; Done: parse_message 完成
    Done --&gt; [*]
</code></pre>

<p>这也是 <code class="language-plaintext highlighter-rouge">Pin</code> 出现在 <code class="language-plaintext highlighter-rouge">Future::poll</code> 签名里的原因。异步状态机内部可能持有跨挂起点的引用；一旦状态机在内存中被移动，这些引用的安全性就会被破坏。<code class="language-plaintext highlighter-rouge">Pin</code> 给这类值提供了“位置不再随意移动”的约束。标准库文档在 <code class="language-plaintext highlighter-rouge">Pin</code> 模块里也专门用 <code class="language-plaintext highlighter-rouge">async fn</code> 返回的 Future 作为常见例子，展示了用 <code class="language-plaintext highlighter-rouge">Box::pin</code> 固定 Future 的方式<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>。</p>

<hr />

<h2 id="五编译器如何把-coroutine-接到-futurepoll">五、编译器如何把 coroutine 接到 Future::poll</h2>

<p>前面讲的是概念，Rust 编译器源码里还有一个关键连接点：对于 async coroutine，编译器会把它的主入口 ABI 映射成 <code class="language-plaintext highlighter-rouge">Future::poll(_, &amp;mut Context&lt;'_&gt;) -&gt; Poll&lt;Output&gt;</code>。</p>

<p>在 <code class="language-plaintext highlighter-rouge">rustc_ty_utils/src/abi.rs</code> 中，源码注释明确区分了普通 coroutine、async construct 和 gen construct：普通 coroutine 对应 <code class="language-plaintext highlighter-rouge">Coroutine::resume</code>，async construct 对应 <code class="language-plaintext highlighter-rouge">Future::poll</code>，gen construct 对应 <code class="language-plaintext highlighter-rouge">Iterator::next</code><sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>。</p>

<p>同一处代码还做了两件具体的事：</p>

<ul>
  <li>把 async coroutine 的返回类型组装成 <code class="language-plaintext highlighter-rouge">Poll&lt;Output&gt;</code>。</li>
  <li>把类型检查阶段使用的 <code class="language-plaintext highlighter-rouge">ResumeTy</code> 替换成 codegen 阶段使用的 <code class="language-plaintext highlighter-rouge">&amp;mut Context&lt;'_&gt;</code>。</li>
</ul>

<p>这就把语法层面的 <code class="language-plaintext highlighter-rouge">async fn</code>、中间表示里的 coroutine，以及标准库里的 <code class="language-plaintext highlighter-rouge">Future::poll</code> 签名连接成了一条线：</p>

<pre><code class="language-mermaid">flowchart LR
    A["async fn"] --&gt; B["HIR / MIR 中的 coroutine"]
    B --&gt; C["coroutine transform"]
    C --&gt; D["Future::poll(Pin&lt;&amp;mut Self&gt;, &amp;mut Context)"]
    D --&gt; E["Poll::Pending / Poll::Ready"]
</code></pre>

<p>编译器的 coroutine transform pass 还会把 <code class="language-plaintext highlighter-rouge">return x</code> 和 <code class="language-plaintext highlighter-rouge">yield y</code> 改写成状态设置与返回值构造。对 async 来说，最终对应的是 <code class="language-plaintext highlighter-rouge">Poll::Ready(x)</code> 和 <code class="language-plaintext highlighter-rouge">Poll::Pending</code><sup id="fnref:5:1"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>。</p>

<p>所以，<code class="language-plaintext highlighter-rouge">.await</code> 的本质不是“阻塞等一下”，而是：</p>

<ol>
  <li>轮询被等待的 Future。</li>
  <li>如果未就绪，把当前状态机的必要局部变量保存到自身结构里。</li>
  <li>返回 <code class="language-plaintext highlighter-rouge">Poll::Pending</code> 给运行时。</li>
  <li>等 waker 被触发后，从记录的状态继续执行。</li>
</ol>

<hr />

<h2 id="六tokio-的位置executorreactor-和任务队列">六、Tokio 的位置：Executor、Reactor 和任务队列</h2>

<p>Tokio 接手的是运行时部分。它需要把大量 Future 包装成任务，安排它们在线程上被 <code class="language-plaintext highlighter-rouge">poll</code>，并把网络 I/O、timer 等事件和对应的 <code class="language-plaintext highlighter-rouge">Waker</code> 关联起来。</p>

<p>可以把 Tokio 的异步 I/O 路径理解成下面这条闭环：</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant Task as Tokio Task
    participant Exec as Executor
    participant Reactor as Reactor
    participant OS as OS 事件队列

    Exec-&gt;&gt;Task: poll(cx)
    Task-&gt;&gt;Reactor: 注册 socket 可读兴趣
    Task--&gt;&gt;Exec: Poll::Pending
    Reactor-&gt;&gt;OS: 等待 epoll / kqueue / IOCP 事件
    OS--&gt;&gt;Reactor: socket 可读
    Reactor-&gt;&gt;Exec: 调用对应 Waker
    Exec-&gt;&gt;Task: 放回可运行队列并再次 poll
    Task--&gt;&gt;Exec: Poll::Ready(result)
</code></pre>

<p>这里的 <code class="language-plaintext highlighter-rouge">Reactor</code> 不是 Rust 标准库的一部分，而是运行时实现的一部分。Rust 只要求 Future 和运行时之间遵守 <code class="language-plaintext highlighter-rouge">poll</code> / <code class="language-plaintext highlighter-rouge">wake</code> 协议；Tokio 则把这个协议接到具体操作系统的事件通知机制上。</p>

<p>这也解释了为什么一个 async 任务在等待网络 I/O 时不会占住线程。它返回 <code class="language-plaintext highlighter-rouge">Pending</code> 后，运行时线程可以继续 <code class="language-plaintext highlighter-rouge">poll</code> 其他任务。等 socket 真的可读，Reactor 再通过 waker 把任务放回调度队列。</p>

<hr />

<h2 id="七阻塞-io-为什么会破坏这套模型">七、阻塞 I/O 为什么会破坏这套模型</h2>

<p><code class="language-plaintext highlighter-rouge">Future::poll</code> 的文档对运行时特性有一个非常关键的要求：<code class="language-plaintext highlighter-rouge">poll</code> 的实现应该快速返回，不应该阻塞；如果提前知道某个调用可能耗时较长，应该把工作转移到线程池或类似机制中<sup id="fnref:1:2"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>。</p>

<p>这不是风格建议，而是异步运行时的基本假设。</p>

<p>如果在 async 任务中直接调用阻塞 I/O，例如：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">fs</span><span class="p">::</span><span class="n">File</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="n">Read</span><span class="p">;</span>

<span class="k">async</span> <span class="k">fn</span> <span class="nf">read_config_bad</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">std</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">file</span> <span class="o">=</span> <span class="nn">File</span><span class="p">::</span><span class="nf">open</span><span class="p">(</span><span class="s">"config.toml"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">contents</span> <span class="o">=</span> <span class="nn">String</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="n">file</span><span class="nf">.read_to_string</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">contents</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nf">Ok</span><span class="p">(</span><span class="n">contents</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>那么当前 executor 工作线程会被同步文件 I/O 占住。它无法继续轮询其他 Future，也无法及时处理已经被唤醒的任务。任务没有阻塞，线程却阻塞了；对运行时来说，结果仍然是吞吐下降和延迟放大。</p>

<p>Tokio 的 <code class="language-plaintext highlighter-rouge">spawn_blocking</code> 文档也明确指出：在 future 中执行阻塞调用或大量不让出执行权的计算是有问题的，因为这可能阻止 executor 推进其他 futures；<code class="language-plaintext highlighter-rouge">spawn_blocking</code> 会把闭包放到允许阻塞的线程上运行<sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>。</p>

<p>因此，上面的代码应该改成类似这样：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">fs</span><span class="p">::</span><span class="n">File</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="n">Read</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">tokio</span><span class="p">::</span><span class="n">task</span><span class="p">;</span>

<span class="k">async</span> <span class="k">fn</span> <span class="nf">read_config</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">std</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="nn">task</span><span class="p">::</span><span class="nf">spawn_blocking</span><span class="p">(||</span> <span class="p">{</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">file</span> <span class="o">=</span> <span class="nn">File</span><span class="p">::</span><span class="nf">open</span><span class="p">(</span><span class="s">"config.toml"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">contents</span> <span class="o">=</span> <span class="nn">String</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
        <span class="n">file</span><span class="nf">.read_to_string</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">contents</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="nf">Ok</span><span class="p">(</span><span class="n">contents</span><span class="p">)</span>
    <span class="p">})</span>
    <span class="k">.await</span>
    <span class="nf">.expect</span><span class="p">(</span><span class="s">"blocking task panicked"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>如果使用 <code class="language-plaintext highlighter-rouge">tokio::fs</code>，也要知道它并不是在所有平台上都使用真正的内核异步文件 I/O。Tokio 文档说明，多数操作系统并不提供异步文件系统 API，因此 <code class="language-plaintext highlighter-rouge">tokio::fs</code> 会在后台使用普通阻塞文件操作，并通过 <code class="language-plaintext highlighter-rouge">spawn_blocking</code> 线程池运行它们<sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>。</p>

<p>所以，对于 async 代码里的文件 I/O，性能判断不能只看 API 名字里有没有 <code class="language-plaintext highlighter-rouge">async</code>。更重要的是理解它背后的调度成本：</p>

<ul>
  <li>网络 I/O 通常可以交给 Reactor 等待内核事件。</li>
  <li>普通文件 I/O 在 Tokio 当前实现里通常会进入 blocking 线程池。</li>
  <li>CPU 密集型任务也不应该长时间占住 executor 工作线程。</li>
</ul>

<hr />

<h2 id="八一个更实用的心智模型">八、一个更实用的心智模型</h2>

<p>把所有源码线索合起来，可以得到一个更可靠的心智模型：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>async fn 调用
    -&gt; 得到 Future 状态机
        -&gt; executor 调用 Future::poll
            -&gt; Ready：任务完成
            -&gt; Pending：保存 Waker 并让出线程
                -&gt; 外部事件完成后调用 wake
                    -&gt; executor 再次 poll
</code></pre></div></div>

<p>这套模型里最容易混淆的是“等待”和“阻塞”的区别：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Poll::Pending</code> 是任务级别的等待，它把线程还给运行时。</li>
  <li>阻塞 I/O 是线程级别的等待，它让运行时无法使用这条线程。</li>
  <li><code class="language-plaintext highlighter-rouge">.await</code> 本身不保证非阻塞；它只是在语法层面等待一个 Future。</li>
  <li>真正的非阻塞来自 Future 的 <code class="language-plaintext highlighter-rouge">poll</code> 实现是否遵守快速返回和正确唤醒的契约。</li>
</ul>

<p>这也是 Rust async 的精髓：语言和标准库只定义最小协议，编译器把代码降成状态机，运行时负责把状态机接到调度器和 I/O 事件源上。Tokio 的高并发能力并不来自魔法，而是来自这条协议被严格执行。</p>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>Rust 源码，<code class="language-plaintext highlighter-rouge">Future</code> trait 文档与 <code class="language-plaintext highlighter-rouge">poll</code> 签名，commit <a href="https://github.com/rust-lang/rust/tree/f53b654a8882"><code class="language-plaintext highlighter-rouge">f53b654a8882</code></a>：<a href="https://github.com/rust-lang/rust/blob/f53b654a8882/library/core/src/future/future.rs#L7-L113"><code class="language-plaintext highlighter-rouge">library/core/src/future/future.rs</code></a>。其中说明 Future 是惰性的、<code class="language-plaintext highlighter-rouge">poll</code> 不应阻塞，并且未就绪时应保存 <code class="language-plaintext highlighter-rouge">Waker</code>。 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:1:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:1:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:2">
      <p>Rust 源码，<code class="language-plaintext highlighter-rouge">Poll&lt;T&gt;</code> 定义，commit <a href="https://github.com/rust-lang/rust/tree/f53b654a8882"><code class="language-plaintext highlighter-rouge">f53b654a8882</code></a>：<a href="https://github.com/rust-lang/rust/blob/f53b654a8882/library/core/src/task/poll.rs#L6-L27"><code class="language-plaintext highlighter-rouge">library/core/src/task/poll.rs</code></a>。<code class="language-plaintext highlighter-rouge">Pending</code> 的文档要求在可以继续推进时安排当前任务被唤醒。 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p>Rust 源码，<code class="language-plaintext highlighter-rouge">Context&lt;'a&gt;</code> 定义和 <code class="language-plaintext highlighter-rouge">waker()</code> 方法，commit <a href="https://github.com/rust-lang/rust/tree/f53b654a8882"><code class="language-plaintext highlighter-rouge">f53b654a8882</code></a>：<a href="https://github.com/rust-lang/rust/blob/f53b654a8882/library/core/src/task/wake.rs#L212-L249"><code class="language-plaintext highlighter-rouge">library/core/src/task/wake.rs</code></a>。 <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p>Rust 源码，<code class="language-plaintext highlighter-rouge">Waker</code> 文档与 <code class="language-plaintext highlighter-rouge">wake()</code> 实现，commit <a href="https://github.com/rust-lang/rust/tree/f53b654a8882"><code class="language-plaintext highlighter-rouge">f53b654a8882</code></a>：<a href="https://github.com/rust-lang/rust/blob/f53b654a8882/library/core/src/task/wake.rs#L378-L448"><code class="language-plaintext highlighter-rouge">library/core/src/task/wake.rs</code></a>。源码说明实际唤醒动作通过 executor 提供的 vtable 实现委托出去。 <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:4:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:5">
      <p>Rust 编译器源码，coroutine state transform pass，commit <a href="https://github.com/rust-lang/rust/tree/f53b654a8882"><code class="language-plaintext highlighter-rouge">f53b654a8882</code></a>：<a href="https://github.com/rust-lang/rust/blob/f53b654a8882/compiler/rustc_mir_transform/src/coroutine.rs#L1-L51"><code class="language-plaintext highlighter-rouge">compiler/rustc_mir_transform/src/coroutine.rs</code></a>。该 pass 会创建 <code class="language-plaintext highlighter-rouge">Coroutine::resume</code> / <code class="language-plaintext highlighter-rouge">Future::poll</code> 实现，并把 coroutine 转换成包含状态字段和跨挂起点 locals 的状态机。 <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:5:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:6">
      <p>Rust 源码，<code class="language-plaintext highlighter-rouge">Pin</code> 文档与 <code class="language-plaintext highlighter-rouge">Pin&lt;Ptr&gt;</code> 定义，commit <a href="https://github.com/rust-lang/rust/tree/f53b654a8882"><code class="language-plaintext highlighter-rouge">f53b654a8882</code></a>：<a href="https://github.com/rust-lang/rust/blob/f53b654a8882/library/core/src/pin.rs#L1010-L1093"><code class="language-plaintext highlighter-rouge">library/core/src/pin.rs</code></a>。文档使用 <code class="language-plaintext highlighter-rouge">async fn</code> 返回的 Future 作为 pinning 的常见例子。 <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7">
      <p>Rust 编译器源码，async coroutine ABI 映射，commit <a href="https://github.com/rust-lang/rust/tree/f53b654a8882"><code class="language-plaintext highlighter-rouge">f53b654a8882</code></a>：<a href="https://github.com/rust-lang/rust/blob/f53b654a8882/compiler/rustc_ty_utils/src/abi.rs#L118-L168"><code class="language-plaintext highlighter-rouge">compiler/rustc_ty_utils/src/abi.rs</code></a>。源码注释说明 async construct 的主入口对应 <code class="language-plaintext highlighter-rouge">Future::poll(_, &amp;mut Context&lt;'_&gt;) -&gt; Poll&lt;Output&gt;</code>。 <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8">
      <p>Tokio 文档，<code class="language-plaintext highlighter-rouge">tokio::task::spawn_blocking</code>：<a href="https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html">https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html</a>。文档说明在 future 中执行阻塞调用或大量计算会阻止 executor 推进其他 futures，<code class="language-plaintext highlighter-rouge">spawn_blocking</code> 用于在允许阻塞的线程上运行闭包。 <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:9">
      <p>Tokio 文档，<code class="language-plaintext highlighter-rouge">tokio::fs</code> 模块：<a href="https://docs.rs/tokio/latest/tokio/fs/index.html">https://docs.rs/tokio/latest/tokio/fs/index.html</a>。文档说明多数操作系统不提供异步文件系统 API，因此 Tokio 文件操作会在后台使用普通阻塞文件操作，并通过 <code class="language-plaintext highlighter-rouge">spawn_blocking</code> 线程池运行。 <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="rust" /><category term="tokio" /><summary type="html"><![CDATA[从 Rust 标准库 Future::poll 契约出发，拆解 async/await 状态机与 Tokio 运行时如何协作驱动异步任务。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://weinan.tech/images/og/tokio-async.png" /><media:content medium="image" url="https://weinan.tech/images/og/tokio-async.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">x86架构下的中断与异常处理：从IDT到FRED的演进之路</title><link href="https://weinan.tech/2026/04/22/x86-idt-int80-syscall-fred.html" rel="alternate" type="text/html" title="x86架构下的中断与异常处理：从IDT到FRED的演进之路" /><published>2026-04-22T00:00:00+08:00</published><updated>2026-04-22T00:00:00+08:00</updated><id>https://weinan.tech/2026/04/22/x86-idt-int80-syscall-fred</id><content type="html" xml:base="https://weinan.tech/2026/04/22/x86-idt-int80-syscall-fred.html"><![CDATA[<style>
/* Mermaid diagram container */
.mermaid-container {
  position: relative;
  display: block;
  cursor: pointer;
  transition: opacity 0.2s;
  max-width: 100%;
  margin: 20px 0;
  overflow-x: auto;
}

.mermaid-container:hover {
  opacity: 0.8;
}

.mermaid-container svg {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Modal overlay */
.mermaid-modal {
  display: none;
  position: fixed;
  z-index: 9999;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9);
  animation: fadeIn 0.3s;
}

.mermaid-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Modal content */
.mermaid-modal-content {
  position: relative;
  width: 90vw;
  height: 90vh;
  overflow: hidden;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
}

.mermaid-modal-diagram {
  transform-origin: center center;
  transition: transform 0.2s ease;
  display: inline-block;
  min-width: 100%;
  cursor: grab;
  user-select: none;
}

.mermaid-modal-diagram.dragging {
  cursor: grabbing;
  transition: none;
}

.mermaid-modal-diagram svg {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;
}

/* Control buttons */
.mermaid-controls {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
  z-index: 10000;
}

.mermaid-btn {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
  color: #333;
  font-weight: 500;
}

.mermaid-btn:hover {
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Close button */
.mermaid-close {
  background: #f44336;
  color: white;
  border: none;
}

.mermaid-close:hover {
  background: #d32f2f;
}

/* Zoom indicator */
.mermaid-zoom-level {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 10000;
}
</style>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  mermaid.initialize({
    startOnLoad: false,
    theme: 'default',
    securityLevel: 'loose',
    htmlLabels: true,
    themeVariables: {
      fontSize: '14px'
    }
  });

  let currentZoom = 1;
  let currentModal = null;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let translateX = 0;
  let translateY = 0;

  // Create modal HTML
  function createModal() {
    const modal = document.createElement('div');
    modal.className = 'mermaid-modal';
    modal.innerHTML = `
      <div class="mermaid-controls">
        <button class="mermaid-btn zoom-in">放大 +</button>
        <button class="mermaid-btn zoom-out">缩小 -</button>
        <button class="mermaid-btn zoom-reset">重置</button>
        <button class="mermaid-btn mermaid-close">关闭 ✕</button>
      </div>
      <div class="mermaid-modal-content">
        <div class="mermaid-modal-diagram"></div>
      </div>
      <div class="mermaid-zoom-level">100%</div>
    `;
    document.body.appendChild(modal);
    return modal;
  }

  // Show modal with diagram
  function showModal(diagramContent) {
    if (!currentModal) {
      currentModal = createModal();
      setupModalEvents();
    }

    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.innerHTML = diagramContent;

    // Remove any width/height attributes from SVG to make it responsive
    const svg = modalDiagram.querySelector('svg');
    if (svg) {
      svg.removeAttribute('width');
      svg.removeAttribute('height');
      svg.style.width = '100%';
      svg.style.height = 'auto';
    }

    // Setup drag functionality
    setupDrag(modalDiagram);

    currentModal.classList.add('active');
    currentZoom = 1;
    resetPosition();
    updateZoom();
    document.body.style.overflow = 'hidden';
  }

  // Hide modal
  function hideModal() {
    if (currentModal) {
      currentModal.classList.remove('active');
      document.body.style.overflow = '';
    }
  }

  // Update zoom level
  function updateZoom() {
    if (!currentModal) return;
    const diagram = currentModal.querySelector('.mermaid-modal-diagram');
    const zoomLevel = currentModal.querySelector('.mermaid-zoom-level');
    diagram.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentZoom})`;
    zoomLevel.textContent = `${Math.round(currentZoom * 100)}%`;
  }

  // Reset position when zoom changes
  function resetPosition() {
    translateX = 0;
    translateY = 0;
  }

  // Setup drag functionality
  function setupDrag(element) {
    element.addEventListener('mousedown', startDrag);
    element.addEventListener('touchstart', startDrag);
  }

  function startDrag(e) {
    if (e.type === 'mousedown' && e.button !== 0) return; // Only left click

    isDragging = true;
    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.classList.add('dragging');

    if (e.type === 'touchstart') {
      startX = e.touches[0].clientX - translateX;
      startY = e.touches[0].clientY - translateY;
    } else {
      startX = e.clientX - translateX;
      startY = e.clientY - translateY;
    }

    document.addEventListener('mousemove', drag);
    document.addEventListener('touchmove', drag);
    document.addEventListener('mouseup', stopDrag);
    document.addEventListener('touchend', stopDrag);
  }

  function drag(e) {
    if (!isDragging) return;
    e.preventDefault();

    if (e.type === 'touchmove') {
      translateX = e.touches[0].clientX - startX;
      translateY = e.touches[0].clientY - startY;
    } else {
      translateX = e.clientX - startX;
      translateY = e.clientY - startY;
    }

    updateZoom();
  }

  function stopDrag() {
    isDragging = false;
    const modalDiagram = currentModal?.querySelector('.mermaid-modal-diagram');
    if (modalDiagram) {
      modalDiagram.classList.remove('dragging');
    }
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('touchmove', drag);
    document.removeEventListener('mouseup', stopDrag);
    document.removeEventListener('touchend', stopDrag);
  }

  // Setup modal event listeners
  function setupModalEvents() {
    if (!currentModal) return;

    // Close button
    currentModal.querySelector('.mermaid-close').addEventListener('click', hideModal);

    // Zoom buttons
    currentModal.querySelector('.zoom-in').addEventListener('click', () => {
      currentZoom = Math.min(currentZoom + 0.25, 3);
      updateZoom();
    });

    currentModal.querySelector('.zoom-out').addEventListener('click', () => {
      currentZoom = Math.max(currentZoom - 0.25, 0.5);
      updateZoom();
    });

    currentModal.querySelector('.zoom-reset').addEventListener('click', () => {
      currentZoom = 1;
      resetPosition();
      updateZoom();
    });

    // Close on background click
    currentModal.addEventListener('click', (e) => {
      if (e.target === currentModal) {
        hideModal();
      }
    });

    // Close on ESC key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && currentModal.classList.contains('active')) {
        hideModal();
      }
    });
  }

  // Convert Jekyll-rendered code blocks to mermaid divs
  document.addEventListener('DOMContentLoaded', async function() {
    const codeBlocks = document.querySelectorAll('code.language-mermaid');

    for (const codeBlock of codeBlocks) {
      const pre = codeBlock.parentElement;
      const container = document.createElement('div');
      container.className = 'mermaid-container';

      const mermaidDiv = document.createElement('div');
      mermaidDiv.className = 'mermaid';
      mermaidDiv.textContent = codeBlock.textContent;

      container.appendChild(mermaidDiv);
      pre.replaceWith(container);
    }

    // Render all mermaid diagrams
    try {
      await mermaid.run({
        querySelector: '.mermaid'
      });
      console.log('Mermaid diagrams rendered successfully');
    } catch (error) {
      console.error('Mermaid rendering error:', error);
    }

    // Add click handlers to rendered diagrams
    document.querySelectorAll('.mermaid-container').forEach((container, index) => {
      // Find the rendered SVG inside the container
      const svg = container.querySelector('svg');
      if (!svg) {
        console.warn(`No SVG found in container ${index}`);
        return;
      }

      // Make the container clickable
      container.style.cursor = 'pointer';
      container.title = '点击查看大图';

      container.addEventListener('click', function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Clone the SVG for the modal
        const svgClone = svg.cloneNode(true);
        const tempDiv = document.createElement('div');
        tempDiv.appendChild(svgClone);

        console.log('Opening modal for diagram', index);
        showModal(tempDiv.innerHTML);
      });

      console.log(`Click handler added to diagram ${index}`);
    });
  });
</script>

<h2 id="前言">前言</h2>

<p>在x86架构四十余年的发展历程中，中断和异常处理机制一直是操作系统与硬件交互的核心桥梁。从最初Intel 80286时代诞生的IDT（Interrupt Descriptor Table，中断描述符表）标准，到如今x86生态联盟力推的FRED（Flexible Return and Event Delivery，灵活返回与事件传递）技术，这条演进之路见证了计算机体系结构对性能与安全的不懈追求。</p>

<p>本文将从Linux内核的视角，深入剖析三种核心事件——软中断（<code class="language-plaintext highlighter-rouge">int $0x80</code>）、硬件中断和CPU异常——在传统IDT机制下的完整处理流程，包括硬件自动操作、内核栈切换、寄存器保存等关键环节。最后，我们还将对比<code class="language-plaintext highlighter-rouge">syscall</code>指令的快速路径，并展望FRED这一x86架构未来的统一事件分发框架。</p>

<hr />

<h2 id="一idt机制概述事件分发的基石">一、IDT机制概述：事件分发的基石</h2>

<p>中断描述符表（IDT）是x86架构处理事件的核心数据结构。它最多包含256个门描述符（gate descriptors），每个描述符定义了对应中断/异常的处理程序入口地址、段选择子、特权级等信息。</p>

<p>当CPU检测到事件发生时，它会根据事件向量号在IDT中查找对应的门描述符，经过特权级检查后，跳转到内核中预设的处理程序。</p>

<p><strong>三种事件类型的本质区别：</strong></p>

<table>
  <thead>
    <tr>
      <th>事件类型</th>
      <th>触发方式</th>
      <th>典型例子</th>
      <th>是否可屏蔽</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>软中断</strong></td>
      <td>软件指令主动触发</td>
      <td><code class="language-plaintext highlighter-rouge">int $0x80</code></td>
      <td>否</td>
    </tr>
    <tr>
      <td><strong>硬件中断</strong></td>
      <td>外设通过中断控制器发送</td>
      <td>时钟中断、键盘中断</td>
      <td>是（IF标志位）</td>
    </tr>
    <tr>
      <td><strong>CPU异常</strong></td>
      <td>指令执行过程中CPU检测</td>
      <td>#PF缺页异常、#DE除零异常</td>
      <td>否</td>
    </tr>
  </tbody>
</table>

<p>尽管触发源不同，但CPU在处理这些事件时遵循相似的核心流程。下面我们以最经典的<code class="language-plaintext highlighter-rouge">int $0x80</code>为例，详细解剖这一过程。</p>

<hr />

<h2 id="二软中断int-0x80的完整流程">二、软中断（int $0x80）的完整流程</h2>

<p><code class="language-plaintext highlighter-rouge">int $0x80</code>是x86架构传统的系统调用入口。在x86_64 Linux内核中，它作为32位兼容层继续存在，用于执行32位系统调用。</p>

<h3 id="21-硬件自动执行的操作">2.1 硬件自动执行的操作</h3>

<p>当用户态程序执行<code class="language-plaintext highlighter-rouge">int $0x80</code>指令时，CPU在切换到内核态之前会<strong>自动完成</strong>以下操作：</p>

<pre><code class="language-mermaid">flowchart LR
    A["执行 int 0x80"] --&gt; B["检查特权级"]
    B --&gt; C["从TSS加载内核栈"]
    C --&gt; D["压入用户态上下文"]
    D --&gt; E["跳转到 IDT 向量 0x80 入口"]
</code></pre>

<p>具体步骤如下：</p>

<p><strong>第一步：特权级检查</strong></p>
<ul>
  <li>CPU检查当前CPL（Current Privilege Level，当前特权级，值为3）是否允许调用IDT[0x80]门</li>
  <li>由于<code class="language-plaintext highlighter-rouge">int $0x80</code>门描述符的DPL（Descriptor Privilege Level，描述符特权级）被设置为3，用户态程序可以合法调用</li>
</ul>

<p><strong>第二步：内核栈切换</strong></p>
<ul>
  <li>CPU读取TR（Task Register，任务寄存器）获取当前任务的TSS（Task State Segment，任务状态段）地址</li>
  <li>从TSS中取出<code class="language-plaintext highlighter-rouge">SS0</code>和<code class="language-plaintext highlighter-rouge">ESP0</code>字段——这两个字段预装了内核数据段选择子和内核栈顶指针</li>
  <li>将<code class="language-plaintext highlighter-rouge">RSP</code>（或<code class="language-plaintext highlighter-rouge">ESP</code>）切换为TSS.ESP0的值，完成从用户栈到内核栈的切换</li>
</ul>

<p><strong>第三步：保存用户态上下文</strong></p>
<ul>
  <li>CPU将以下内容<strong>依次压入新切换的内核栈</strong>：
    <ul>
      <li><code class="language-plaintext highlighter-rouge">SS</code>（用户态栈段选择子）</li>
      <li><code class="language-plaintext highlighter-rouge">RSP</code>（用户态栈指针）</li>
      <li><code class="language-plaintext highlighter-rouge">RFLAGS</code>（标志寄存器）</li>
      <li><code class="language-plaintext highlighter-rouge">CS</code>（用户态代码段选择子）</li>
      <li><code class="language-plaintext highlighter-rouge">RIP</code>（<code class="language-plaintext highlighter-rouge">int $0x80</code>的下一条指令地址）</li>
    </ul>
  </li>
</ul>

<h3 id="22-tssesp0的指向trampoline机制">2.2 TSS.ESP0的指向：trampoline机制</h3>

<p>在x86_64 Linux中，TSS.ESP0<strong>并不直接指向最终的任务内核栈</strong>，而是指向 <strong>per-CPU entry trampoline stack</strong>（与 <code class="language-plaintext highlighter-rouge">cpu_entry_stack</code> / <code class="language-plaintext highlighter-rouge">load_sp0</code> 一致）。这样既能在用户↔内核切换时落到固定、可预测的栈地址，又与 KPTI / 内核入口安全模型相衔接（不单因某一侧信道攻击而设立）。</p>

<p><strong><code class="language-plaintext highlighter-rouge">TSS.RSP0</code> / <code class="language-plaintext highlighter-rouge">sp0</code> 与 entry stack</strong>（每 CPU 在 <code class="language-plaintext highlighter-rouge">cpu_init()</code> 中设置）：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cpu_init()（arch/x86/kernel/cpu/common.c:2466）
    └─ load_sp0(…)（common.c:2506）
</code></pre></div></div>

<p><a href="https://github.com/torvalds/linux/blob/master/arch/x86/kernel/cpu/common.c"><code class="language-plaintext highlighter-rouge">cpu_init()</code></a>将 <strong><code class="language-plaintext highlighter-rouge">sp0</code></strong> 设为该 CPU <strong>entry trampoline stack</strong> 顶端一侧：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* arch/x86/kernel/cpu/common.c — cpu_init() 内 */</span>
<span class="n">load_sp0</span><span class="p">((</span><span class="kt">unsigned</span> <span class="kt">long</span><span class="p">)(</span><span class="n">cpu_entry_stack</span><span class="p">(</span><span class="n">cpu</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">));</span>
</code></pre></div></div>

<p>trampoline栈是一个每CPU独立的小型栈（大小约4KB），它的作用是：</p>
<ol>
  <li>提供一个安全的临时运行环境</li>
  <li>允许内核安全地切换CR3（页表基址寄存器）</li>
  <li>在切换到真正任务栈之前完成必要的安全检查</li>
</ol>

<h3 id="23-内核入口ia32-兼容">2.3 内核入口（IA32 兼容）</h3>

<p>x86_64 且 <strong><code class="language-plaintext highlighter-rouge">CONFIG_IA32_EMULATION</code></strong> 时，IDT[0x80] 进入后顺序如下（<code class="language-plaintext highlighter-rouge">sync_regs</code> <strong>返回到</strong> <code class="language-plaintext highlighter-rouge">idtentry_body</code> 再继续，<strong>不在</strong> <code class="language-plaintext highlighter-rouge">sync_regs</code> 内 <code class="language-plaintext highlighter-rouge">call int80_emulation</code> / <code class="language-plaintext highlighter-rouge">do_int80_emulation</code>）：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CPU: INT $0x80 → IDT[0x80]；RSP ← TSS.RSP0（entry trampoline stack）
    └─ asm_int80_emulation（DECLARE_IDTENTRY_RAW → entry_64.S，见 idtentry.h）
        └─ idtentry_body（entry_64.S）
              call error_entry →（用户态）jmp sync_regs（traps.c）→ 返回 %rax = pt_regs*
              movq %rax, %rsp；movq %rsp, %rdi；call int80_emulation（entry_64_compat.S）
                  jmp do_int80_emulation()（syscall_32.c）
</code></pre></div></div>

<p><a href="https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/idtentry.h"><code class="language-plaintext highlighter-rouge">idtentry.h</code></a> · <a href="https://github.com/torvalds/linux/blob/master/arch/x86/entry/entry_64.S"><code class="language-plaintext highlighter-rouge">entry_64.S</code></a> · <a href="https://github.com/torvalds/linux/blob/master/arch/x86/kernel/traps.c#L1044"><code class="language-plaintext highlighter-rouge">sync_regs</code></a> · <a href="https://github.com/torvalds/linux/blob/master/arch/x86/entry/entry_64_compat.S"><code class="language-plaintext highlighter-rouge">entry_64_compat.S</code></a> · <a href="https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscall_32.c"><code class="language-plaintext highlighter-rouge">syscall_32.c</code></a></p>

<p>纯 32 位：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>entry_INT80_32（entry_32.S）
    └─ do_int80_syscall_32()（syscall_32.c）
</code></pre></div></div>

<p><a href="https://github.com/torvalds/linux/blob/master/arch/x86/entry/entry_32.S"><code class="language-plaintext highlighter-rouge">entry_32.S</code></a> 注释里的 <strong><code class="language-plaintext highlighter-rouge">entry_INT80_compat</code></strong> 指 64 位内核上的 IA32 兼容路径；符号为 <strong><code class="language-plaintext highlighter-rouge">asm_int80_emulation</code></strong> / <strong><code class="language-plaintext highlighter-rouge">int80_emulation</code></strong>，无名为 <code class="language-plaintext highlighter-rouge">entry_INT80_compat</code> 的入口标签。</p>

<h3 id="24-从-trampoline-栈到任务栈">2.4 从 trampoline 栈到任务栈</h3>

<p>硬件帧先压在 <strong><code class="language-plaintext highlighter-rouge">TSS.RSP0</code></strong> 的 <strong>per-CPU entry / trampoline</strong>（<code class="language-plaintext highlighter-rouge">cpu_entry_stack</code>）。<strong>从用户态</strong>进入的 <strong><code class="language-plaintext highlighter-rouge">INT $0x80</code>、常见外设 IRQ、IST=0 的典型异常</strong>，与 §2.3 共用 <strong><code class="language-plaintext highlighter-rouge">idtentry_body → error_entry → sync_regs()</code></strong>；<a href="https://github.com/torvalds/linux/blob/master/arch/x86/kernel/traps.c#L1044"><code class="language-plaintext highlighter-rouge">sync_regs</code></a> 把 <code class="language-plaintext highlighter-rouge">struct pt_regs</code> 落到 <code class="language-plaintext highlighter-rouge">current</code> 线程栈约定位置（含 trampoline / 部分 IST 情形，见该函数注释）。与 <strong><code class="language-plaintext highlighter-rouge">syscall</code></strong> 同源的是线程栈上的 <strong><code class="language-plaintext highlighter-rouge">task_pt_regs</code> 语义</strong>，<strong>不是</strong> <code class="language-plaintext highlighter-rouge">RSP0</code> 指针本身。</p>

<h3 id="25-save_all与system_call">2.5 SAVE_ALL与system_call</h3>

<p>在32位时代，<code class="language-plaintext highlighter-rouge">int $0x80</code>的入口是<code class="language-plaintext highlighter-rouge">system_call</code>，其中会调用<code class="language-plaintext highlighter-rouge">SAVE_ALL</code>宏来保存所有寄存器：</p>

<pre><code class="language-assembly">#define SAVE_ALL \
    cld; \
    pushl %es; \
    pushl %ds; \
    pushl %eax; \
    pushl %ebp; \
    pushl %edi; \
    pushl %esi; \
    pushl %edx; \
    pushl %ecx; \
    pushl %ebx; \
    movl $(__KERNEL_DS),%edx; \
    movl %edx,%ds; \
    movl %edx,%es;
</code></pre>

<p><code class="language-plaintext highlighter-rouge">SAVE_ALL</code>不仅保存了寄存器状态，还巧妙地完成了系统调用参数的传递——在<code class="language-plaintext highlighter-rouge">int $0x80</code>之前，用户将系统调用号放入<code class="language-plaintext highlighter-rouge">EAX</code>，参数放入<code class="language-plaintext highlighter-rouge">EBX</code>、<code class="language-plaintext highlighter-rouge">ECX</code>等寄存器，<code class="language-plaintext highlighter-rouge">SAVE_ALL</code>将这些值压入内核栈后，C处理函数就可以通过栈指针访问这些参数。</p>

<h3 id="26-完整流程图">2.6 完整流程图</h3>

<p>示意 <strong><code class="language-plaintext highlighter-rouge">INT $0x80</code></strong>；外设 IRQ 与用户态 <strong><code class="language-plaintext highlighter-rouge">error_entry</code>/<code class="language-plaintext highlighter-rouge">sync_regs</code></strong> 共用骨架的差异见 §3.3。</p>

<pre><code class="language-mermaid">flowchart TD
    subgraph User["用户态"]
        A["执行 int 0x80"] --&gt; B["CPU 硬件自动操作"]
    end

    subgraph Hardware["CPU 硬件自动操作"]
        B --&gt; C["从 TSS RSP0 加载 entry trampoline 栈"]
        C --&gt; D["压入 SS、RSP、RFLAGS、CS、RIP"]
        D --&gt; E["跳转 asm_int80_emulation"]
    end

    subgraph Kernel["内核态 IA32_EMU"]
        E --&gt; F["error_entry 与 sync_regs"]
        F --&gt; G["int80_emulation 到 do_int80_emulation"]
        G --&gt; J["构建 pt_regs 并完成调用"]
        J --&gt; K["系统调用返回路径"]
    end

    K --&gt; L["IRET 返回用户态"]
</code></pre>

<hr />

<h2 id="三硬件中断的处理流程">三、硬件中断的处理流程</h2>

<p>用户态触发时<strong>第一站栈</strong>与 §2 相同（<code class="language-plaintext highlighter-rouge">load_sp0</code> → <strong><code class="language-plaintext highlighter-rouge">TSS.RSP0</code></strong> → entry trampoline）；与 <code class="language-plaintext highlighter-rouge">int $0x80</code> 的差别在<strong>向量桩</strong>与<strong>最终 C 入口</strong>（§3.3）。</p>

<h3 id="31-中断控制器与中断向量">3.1 中断控制器与中断向量</h3>

<p>外部设备通过中断控制器（如APIC）向CPU发送中断请求。每个中断源被分配一个中断向量号（32-255），CPU根据这个向量号在IDT中查找对应的门描述符。</p>

<h3 id="32-中断处理的核心差异">3.2 中断处理的核心差异</h3>

<table>
  <thead>
    <tr>
      <th>对比项</th>
      <th>int $0x80（软中断）</th>
      <th>硬件中断</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>TSS.RSP0（用户态→ring 0）</strong></td>
      <td>per-CPU entry / trampoline stack（<code class="language-plaintext highlighter-rouge">cpu_entry_stack</code>）</td>
      <td><strong>同上</strong>，并非直接落到进程 thread stack</td>
    </tr>
    <tr>
      <td><strong>是否再迁到线程栈</strong></td>
      <td>经 <code class="language-plaintext highlighter-rouge">error_entry</code>/<code class="language-plaintext highlighter-rouge">sync_regs</code> 等到线程栈</td>
      <td>同样经 <code class="language-plaintext highlighter-rouge">error_entry</code>/<code class="language-plaintext highlighter-rouge">sync_regs</code> 等到线程栈</td>
    </tr>
    <tr>
      <td><strong>中断屏蔽</strong></td>
      <td>不可屏蔽</td>
      <td>可通过CLI/STI控制IF位</td>
    </tr>
    <tr>
      <td><strong>EOI处理</strong></td>
      <td>不需要</td>
      <td>需要发送EOI给中断控制器</td>
    </tr>
    <tr>
      <td><strong>入口函数</strong></td>
      <td><code class="language-plaintext highlighter-rouge">asm_int80_emulation</code> → <code class="language-plaintext highlighter-rouge">do_int80_emulation</code></td>
      <td><code class="language-plaintext highlighter-rouge">asm_common_interrupt</code> → <a href="https://github.com/torvalds/linux/blob/master/arch/x86/kernel/irq.c"><code class="language-plaintext highlighter-rouge">common_interrupt()</code></a>（内部 <code class="language-plaintext highlighter-rouge">call_irq_handler()</code> 等）</td>
    </tr>
  </tbody>
</table>

<h3 id="33-中断入口common_interrupt">3.3 中断入口：common_interrupt</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>向量 v（从用户态）：RSP ← TSS.RSP0（entry trampoline stack）
    └─ irq_entries_start：push v → jmp asm_common_interrupt（桩由 idtentry.h 生成）
        └─ idtentry_body（与 §2.3 asm_int80_emulation 同源；IRQ 宏为 DEFINE_IDTENTRY_IRQ 等）
              call error_entry → jmp sync_regs → 返回 %rax = regs
              movq %rax, %rsp；movq %rsp, %rdi；call common_interrupt(regs, v)（irq.c）【v 由 DEFINE_IDTENTRY_IRQ 等展开带到 handler】
                  └─ call_irq_handler → handle_irq / apic_eoi 等
</code></pre></div></div>

<p><a href="https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/idtentry.h"><code class="language-plaintext highlighter-rouge">idtentry.h</code></a> · <a href="https://github.com/torvalds/linux/blob/master/arch/x86/kernel/irq.c"><code class="language-plaintext highlighter-rouge">irq.c</code></a></p>

<hr />

<h2 id="四cpu异常的处理流程">四、CPU异常的处理流程</h2>

<p>CPU异常是处理器在执行指令过程中检测到异常情况时触发的事件，如缺页异常（#PF）、除零异常（#DE）等。</p>

<h3 id="41-异常的分类与向量">4.1 异常的分类与向量</h3>

<p>x86定义了多种异常，每个异常有固定的向量号：</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">#DE</code>（除零异常）：向量0</li>
  <li><code class="language-plaintext highlighter-rouge">#PF</code>（缺页异常）：向量14</li>
  <li><code class="language-plaintext highlighter-rouge">#GP</code>（通用保护故障）：向量13</li>
  <li><code class="language-plaintext highlighter-rouge">#DF</code>（双重故障）：向量8</li>
</ul>

<h3 id="42-异常的栈切换ist机制">4.2 异常的栈切换：IST机制</h3>

<p><strong>IST≠0</strong> 时（如 <strong>#DF、#NMI、#MC</strong> 等常见配置），硬件把 <strong>RSP</strong> 切到 <strong>IST</strong> 应急栈，而不是 <strong><code class="language-plaintext highlighter-rouge">TSS.RSP0</code></strong> entry stack；与 <strong>IRQ / <code class="language-plaintext highlighter-rouge">INT $0x80</code></strong> 走 <strong>RSP0</strong> 并列，属另一类交付栈。IST 用于栈可能已损坏时仍能进入独立栈处理。</p>

<ol>
  <li>IDT 门描述符 <strong>IST</strong> 字段（3 bit，<strong>0</strong> = 不用 IST，改用 <strong>RSP0</strong>）</li>
  <li>自 <strong>TSS.IST[]</strong> 取该槽栈指针并切换 <strong>RSP</strong></li>
  <li>压入异常帧并进入异常入口（后续仍可能经 <strong><code class="language-plaintext highlighter-rouge">sync_regs</code></strong> 等到线程 <strong><code class="language-plaintext highlighter-rouge">pt_regs</code></strong>，见 traps.c / entry_64.S 具体向量）</li>
</ol>

<pre><code class="language-mermaid">flowchart TD
    A["CPU 检测到异常"] --&gt; B{"是否使用 IST"}
    B --&gt;|是| C["从 TSS 的 IST 项取应急栈"]
    B --&gt;|否| D["从 TSS RSP0 取栈"]
    C --&gt; E["在 IST 栈上压入上下文"]
    D --&gt; F["在普通内核栈上压入上下文"]
    E --&gt; G["跳转异常处理入口"]
    F --&gt; G
</code></pre>

<h3 id="43-异常入口exc_-函数">4.3 异常入口：exc_* 函数</h3>

<p>每个异常对应专门的处理函数，如 <strong><a href="https://github.com/torvalds/linux/blob/master/arch/x86/mm/fault.c"><code class="language-plaintext highlighter-rouge">exc_page_fault</code></a></strong>（实现于 <strong><code class="language-plaintext highlighter-rouge">arch/x86/mm/fault.c</code></strong>）处理缺页异常、<strong><code class="language-plaintext highlighter-rouge">exc_divide_error</code></strong> 处理除零异常。这些函数会：</p>
<ol>
  <li>读取错误码（如果有）</li>
  <li>从<code class="language-plaintext highlighter-rouge">CR2</code>寄存器获取缺页地址（#PF）</li>
  <li>调用核心处理逻辑（如<code class="language-plaintext highlighter-rouge">handle_mm_fault</code>）</li>
  <li>根据处理结果恢复执行或发送信号</li>
</ol>

<hr />

<h2 id="五快速路径syscall指令">五、快速路径：syscall指令</h2>

<p>在x86_64架构中，<code class="language-plaintext highlighter-rouge">syscall</code>指令是<strong>更高效的系统调用方式</strong>，它专为快速特权级切换而设计。</p>

<h3 id="51-syscall-vs-int-0x80">5.1 syscall vs int 0x80</h3>

<table>
  <thead>
    <tr>
      <th>对比维度</th>
      <th>int 0x80</th>
      <th>syscall</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>性能</strong></td>
      <td>较慢（需内存访问IDT/TSS）</td>
      <td>更快（专用指令）</td>
    </tr>
    <tr>
      <td><strong>栈切换</strong></td>
      <td>硬件自动切换（通过TSS.RSP0）</td>
      <td>不自动切换，软件需手动设置</td>
    </tr>
    <tr>
      <td><strong>寄存器破坏</strong></td>
      <td>保存完整上下文</td>
      <td>破坏RCX和R11</td>
    </tr>
    <tr>
      <td><strong>适用架构</strong></td>
      <td>32位/64位兼容</td>
      <td>x86_64原生</td>
    </tr>
    <tr>
      <td><strong>内核入口</strong></td>
      <td><code class="language-plaintext highlighter-rouge">asm_int80_emulation</code> → <code class="language-plaintext highlighter-rouge">do_int80_emulation</code>（IA32_EMU）</td>
      <td><code class="language-plaintext highlighter-rouge">entry_SYSCALL_64</code></td>
    </tr>
  </tbody>
</table>

<h3 id="52-syscall的独特之处">5.2 syscall的独特之处</h3>

<p><strong><code class="language-plaintext highlighter-rouge">syscall</code> 所用 MSR</strong> 在每 CPU <strong><code class="language-plaintext highlighter-rouge">cpu_init()</code></strong> 里设置；<strong>异常/中断 IDT</strong> 由更早的 <strong><code class="language-plaintext highlighter-rouge">trap_init()</code></strong> 路径初始化，二者分开：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>start_kernel()（init/main.c）
    └─ trap_init()（traps.c）【陷阱与 IDT】

cpu_init()（common.c，每 CPU）
    └─ syscall_init() → idt_syscall_init()
        └─ MSR_STAR、MSR_LSTAR → entry_SYSCALL_64、MSR_SYSCALL_MASK（段内亦含 CSTAR/SYSENTER 等与配置相关项）
</code></pre></div></div>

<p><a href="https://github.com/torvalds/linux/blob/master/init/main.c#L1017"><code class="language-plaintext highlighter-rouge">start_kernel</code></a> · <a href="https://github.com/torvalds/linux/blob/master/arch/x86/kernel/traps.c#L1682"><code class="language-plaintext highlighter-rouge">trap_init</code></a> · <a href="https://github.com/torvalds/linux/blob/master/arch/x86/kernel/cpu/common.c#L2268-L2466"><code class="language-plaintext highlighter-rouge">cpu_init</code> / <code class="language-plaintext highlighter-rouge">syscall_init</code> / <code class="language-plaintext highlighter-rouge">idt_syscall_init</code></a></p>

<p><code class="language-plaintext highlighter-rouge">syscall</code>指令的设计哲学是<strong>硬件做最少的事，软件做最多的事</strong>，以此换取极致性能：</p>

<ul>
  <li><strong>不保存RSP</strong>：硬件不自动切换栈，入口代码必须立即从<code class="language-plaintext highlighter-rouge">cpu_current_top_of_stack</code>加载内核栈</li>
  <li><strong>不保存RCX/R11</strong>：硬件将RIP保存到RCX，RFLAGS保存到R11，这意味着这两个寄存器被破坏</li>
  <li><strong>无内存访问</strong>：不查阅IDT，直接从MSR读取目标地址</li>
</ul>

<p>入口汇编 <a href="https://github.com/torvalds/linux/blob/master/arch/x86/entry/entry_64.S"><code class="language-plaintext highlighter-rouge">entry_SYSCALL_64</code></a>（<code class="language-plaintext highlighter-rouge">syscall</code> → <strong><code class="language-plaintext highlighter-rouge">entry_SYSCALL_64</code></strong>）。<strong><code class="language-plaintext highlighter-rouge">entry_SYSCALL_64</code></strong> 把用户栈指针暂存在 per-CPU <strong>TSS 的 <code class="language-plaintext highlighter-rouge">sp2</code> 槽</strong>：</p>

<pre><code class="language-assembly">ENTRY(entry_SYSCALL_64)
    swapgs
    movq    %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
    SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp
    movq    PER_CPU_VAR(cpu_current_top_of_stack), %rsp
    ...
</code></pre>

<p>由于RCX被<code class="language-plaintext highlighter-rouge">syscall</code>指令破坏，而x86-64 System V ABI规定第四个参数使用RCX传递，内核必须使用R10来接收第四个参数。这就是为什么64位系统调用包装器中会出现<code class="language-plaintext highlighter-rouge">mov %rcx, %r10</code>指令。</p>

<hr />

<h2 id="六fredx86架构的未来">六、FRED：x86架构的未来</h2>

<h3 id="61-为什么需要fred">6.1 为什么需要FRED？</h3>

<p>IDT机制诞生于20世纪80年代的Intel 80286时代，已有40余年历史。现代程序员普遍认为其设计“杂乱且别扭”：</p>

<ul>
  <li><strong>上下文保存不完整</strong>：硬件只保存最少状态，软件需手动补齐</li>
  <li><strong>边缘情况复杂</strong>：需要处理NMI嵌套、#DF双重故障等各种极端场景</li>
  <li><strong>CR2/DR6瞬时状态</strong>：缺页地址和调试状态易被覆盖</li>
  <li><strong>IST机制有限</strong>：只有7个IST槽位，且不可动态扩展</li>
</ul>

<h3 id="62-fred的核心改进">6.2 FRED的核心改进</h3>

<p>FRED（Flexible Return and Event Delivery，灵活返回与事件传递）由Intel提出，AMD已承诺在Zen 6架构中支持，标志着x86生态的重大统一。</p>

<p><strong>主要改进包括：</strong></p>

<ol>
  <li><strong>原子性上下文保存/恢复</strong>
    <ul>
      <li>FRED事件传递时自动保存完整的管理程序/用户上下文</li>
      <li>避免%CR2/%DR6等瞬时状态问题</li>
      <li>不再需要处理“半生不熟”的入口状态</li>
    </ul>
  </li>
  <li><strong>显式NMI控制</strong>
    <ul>
      <li>用<code class="language-plaintext highlighter-rouge">ERETS</code>/<code class="language-plaintext highlighter-rouge">ERETU</code>指令替代<code class="language-plaintext highlighter-rouge">IRET</code></li>
      <li>明确控制NMI的解锁时机，避免嵌套混乱</li>
    </ul>
  </li>
  <li><strong>栈级别替代IST</strong>
    <ul>
      <li>引入4个栈级别（0-3）代替不可重入的IST</li>
      <li>可为每个向量配置独立的栈级别</li>
      <li>支持栈级别的动态升降</li>
    </ul>
  </li>
  <li><strong>消除SWAPGS</strong>
    <ul>
      <li>引入<code class="language-plaintext highlighter-rouge">LKGS</code>指令管理GS段</li>
      <li>FRED事件传递自动交换GS基址</li>
      <li><code class="language-plaintext highlighter-rouge">SWAPGS</code>在FRED下变为非法（#UD）</li>
    </ul>
  </li>
  <li><strong>统一栈结构</strong>
    <ul>
      <li>所有事件使用一致的栈帧格式</li>
      <li>开发者无需为边缘案例编写规避代码</li>
    </ul>
  </li>
</ol>

<h3 id="63-两级事件分发">6.3 两级事件分发</h3>

<p>FRED要求软件根据<strong>事件类型和向量</strong>进行两级分发，而非IDT的直接向量索引：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>事件类型 (fred_ss.type)
    ├── EVENT_TYPE_EXTINT（外部中断）→ 中断分发
    ├── EVENT_TYPE_NMI（NMI）→ NMI处理
    ├── EVENT_TYPE_HWEXC（硬件异常）→ 异常分发
    ├── EVENT_TYPE_SWINT（软件中断）→ 系统调用
    └── EVENT_TYPE_OTHER（其他）→ 特殊处理
</code></pre></div></div>

<p>这种设计让软件重新掌握了事件路由的控制权，同时保持了灵活性。</p>

<h3 id="64-linux内核支持状态">6.4 Linux内核支持状态</h3>

<p>Linux内核从 6.9 起逐步加入 FRED 支持，主要文件：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>entry_fred.c（arch/x86/entry/）
    └─ fred.c、fred.h（kernel / include/asm）
</code></pre></div></div>

<ul>
  <li><a href="https://github.com/torvalds/linux/blob/master/arch/x86/entry/entry_fred.c"><code class="language-plaintext highlighter-rouge">arch/x86/entry/entry_fred.c</code></a>：事件分发</li>
  <li><a href="https://github.com/torvalds/linux/blob/master/arch/x86/kernel/fred.c"><code class="language-plaintext highlighter-rouge">arch/x86/kernel/fred.c</code></a>：初始化与 MSR</li>
  <li><a href="https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/fred.h"><code class="language-plaintext highlighter-rouge">arch/x86/include/asm/fred.h</code></a>：宏与类型</li>
</ul>

<hr />

<h2 id="七总结">七、总结</h2>

<p>从<code class="language-plaintext highlighter-rouge">int $0x80</code>到<code class="language-plaintext highlighter-rouge">syscall</code>，再到即将到来的FRED，x86架构的事件处理机制走过了漫长的演进之路：</p>

<table>
  <thead>
    <tr>
      <th>时代</th>
      <th>机制</th>
      <th>特点</th>
      <th>缺陷</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1980s-2000s</td>
      <td>IDT + int 0x80</td>
      <td>统一框架，硬件自动栈切换</td>
      <td>性能较低，边缘情况复杂</td>
    </tr>
    <tr>
      <td>2000s-2020s</td>
      <td>syscall/sysenter</td>
      <td>专用指令，性能优化</td>
      <td>与IDT并存，增加复杂度</td>
    </tr>
    <tr>
      <td>2026+</td>
      <td>FRED</td>
      <td>原子性操作，统一栈结构，简化软件</td>
      <td>尚未大规模部署</td>
    </tr>
  </tbody>
</table>

<p>传统的IDT机制虽然历史悠久且功能完备，但其设计已难以满足现代操作系统对性能和安全性的双重追求。FRED通过硬件层面的重新设计，有望在保证兼容性的同时，为x86架构带来更高效、更健壮的事件处理框架。</p>

<p>正如Linus Torvalds所言，FRED是“更完整的解决方案”。随着Intel和AMD在下一代处理器中共同拥抱这一技术，我们有理由期待FRED将成为x86架构下一个四十年的基石。</p>

<hr />

<h2 id="参考文献">参考文献</h2>

<ol>
  <li>Linux内核源码: <a href="https://github.com/torvalds/linux/blob/master/arch/x86/entry/entry_64.S"><code class="language-plaintext highlighter-rouge">arch/x86/entry/entry_64.S</code></a>, <a href="https://github.com/torvalds/linux/blob/master/arch/x86/entry/entry_64_compat.S"><code class="language-plaintext highlighter-rouge">arch/x86/entry/entry_64_compat.S</code></a>, <a href="https://github.com/torvalds/linux/blob/master/arch/x86/entry/entry_fred.c"><code class="language-plaintext highlighter-rouge">arch/x86/entry/entry_fred.c</code></a>, <a href="https://github.com/torvalds/linux/blob/master/arch/x86/kernel/cpu/common.c"><code class="language-plaintext highlighter-rouge">arch/x86/kernel/cpu/common.c</code></a>, <a href="https://github.com/torvalds/linux/blob/master/arch/x86/kernel/irq.c"><code class="language-plaintext highlighter-rouge">arch/x86/kernel/irq.c</code></a>, <a href="https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscall_32.c"><code class="language-plaintext highlighter-rouge">arch/x86/entry/syscall_32.c</code></a></li>
  <li>Linux内核文档: <a href="https://github.com/torvalds/linux/blob/master/Documentation/arch/x86/x86_64/fred.rst"><code class="language-plaintext highlighter-rouge">Documentation/arch/x86/x86_64/fred.rst</code></a></li>
  <li>“Linux系统调用中syscall与int 0x80的实现方式性能及适用场景对比”，阿里云开发者社区</li>
  <li>“终结 40 年 IDT 旧标准，AMD Zen 6 架构将使用英特尔 FRED 技术”，IT之家</li>
  <li>“软中断指令int $0x80的执行过程”，CSDN博客</li>
  <li>“Linux内核之中断INT 0x80的作用”，ChinaUnix博客</li>
  <li>Stack Overflow: “x86_64 Linux函数与syscalls之间的ABI差异”</li>
</ol>]]></content><author><name>阿男</name></author><category term="linux-kernel" /><summary type="html"><![CDATA[从 IDT 与 int 0x80 到 SYSCALL 与 FRED，梳理 x86 架构中断、异常与系统调用机制的演进脉络。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://weinan.tech/images/og/linux-kernel.png" /><media:content medium="image" url="https://weinan.tech/images/og/linux-kernel.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">当Linux内核不再「迁就」PostgreSQL：一次抢占模型变更引发的性能风暴</title><link href="https://weinan.tech/2026/04/08/linux-kernel-7-preempt-lazy-postgresql-performance.html" rel="alternate" type="text/html" title="当Linux内核不再「迁就」PostgreSQL：一次抢占模型变更引发的性能风暴" /><published>2026-04-08T00:00:00+08:00</published><updated>2026-04-08T00:00:00+08:00</updated><id>https://weinan.tech/2026/04/08/linux-kernel-7-preempt-lazy-postgresql-performance</id><content type="html" xml:base="https://weinan.tech/2026/04/08/linux-kernel-7-preempt-lazy-postgresql-performance.html"><![CDATA[<style>
/* Mermaid diagram container */
.mermaid-container {
  position: relative;
  display: block;
  cursor: pointer;
  transition: opacity 0.2s;
  max-width: 100%;
  margin: 20px 0;
  overflow-x: auto;
}

.mermaid-container:hover {
  opacity: 0.8;
}

.mermaid-container svg {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Modal overlay */
.mermaid-modal {
  display: none;
  position: fixed;
  z-index: 9999;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9);
  animation: fadeIn 0.3s;
}

.mermaid-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Modal content */
.mermaid-modal-content {
  position: relative;
  width: 90vw;
  height: 90vh;
  overflow: hidden;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
}

.mermaid-modal-diagram {
  transform-origin: center center;
  transition: transform 0.2s ease;
  display: inline-block;
  min-width: 100%;
  cursor: grab;
  user-select: none;
}

.mermaid-modal-diagram.dragging {
  cursor: grabbing;
  transition: none;
}

.mermaid-modal-diagram svg {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;
}

/* Control buttons */
.mermaid-controls {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
  z-index: 10000;
}

.mermaid-btn {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
  color: #333;
  font-weight: 500;
}

.mermaid-btn:hover {
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Close button */
.mermaid-close {
  background: #f44336;
  color: white;
  border: none;
}

.mermaid-close:hover {
  background: #d32f2f;
}

/* Zoom indicator */
.mermaid-zoom-level {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 10000;
}
</style>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  mermaid.initialize({
    startOnLoad: false,
    theme: 'default',
    securityLevel: 'loose',
    htmlLabels: true,
    themeVariables: {
      fontSize: '14px'
    }
  });

  let currentZoom = 1;
  let currentModal = null;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let translateX = 0;
  let translateY = 0;

  // Create modal HTML
  function createModal() {
    const modal = document.createElement('div');
    modal.className = 'mermaid-modal';
    modal.innerHTML = `
      <div class="mermaid-controls">
        <button class="mermaid-btn zoom-in">放大 +</button>
        <button class="mermaid-btn zoom-out">缩小 -</button>
        <button class="mermaid-btn zoom-reset">重置</button>
        <button class="mermaid-btn mermaid-close">关闭 ✕</button>
      </div>
      <div class="mermaid-modal-content">
        <div class="mermaid-modal-diagram"></div>
      </div>
      <div class="mermaid-zoom-level">100%</div>
    `;
    document.body.appendChild(modal);
    return modal;
  }

  // Show modal with diagram
  function showModal(diagramContent) {
    if (!currentModal) {
      currentModal = createModal();
      setupModalEvents();
    }

    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.innerHTML = diagramContent;

    // Remove any width/height attributes from SVG to make it responsive
    const svg = modalDiagram.querySelector('svg');
    if (svg) {
      svg.removeAttribute('width');
      svg.removeAttribute('height');
      svg.style.width = '100%';
      svg.style.height = 'auto';
    }

    // Setup drag functionality
    setupDrag(modalDiagram);

    currentModal.classList.add('active');
    currentZoom = 1;
    resetPosition();
    updateZoom();
    document.body.style.overflow = 'hidden';
  }

  // Hide modal
  function hideModal() {
    if (currentModal) {
      currentModal.classList.remove('active');
      document.body.style.overflow = '';
    }
  }

  // Update zoom level
  function updateZoom() {
    if (!currentModal) return;
    const diagram = currentModal.querySelector('.mermaid-modal-diagram');
    const zoomLevel = currentModal.querySelector('.mermaid-zoom-level');
    diagram.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentZoom})`;
    zoomLevel.textContent = `${Math.round(currentZoom * 100)}%`;
  }

  // Reset position when zoom changes
  function resetPosition() {
    translateX = 0;
    translateY = 0;
  }

  // Setup drag functionality
  function setupDrag(element) {
    element.addEventListener('mousedown', startDrag);
    element.addEventListener('touchstart', startDrag);
  }

  function startDrag(e) {
    if (e.type === 'mousedown' && e.button !== 0) return; // Only left click

    isDragging = true;
    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.classList.add('dragging');

    if (e.type === 'touchstart') {
      startX = e.touches[0].clientX - translateX;
      startY = e.touches[0].clientY - translateY;
    } else {
      startX = e.clientX - translateX;
      startY = e.clientY - translateY;
    }

    document.addEventListener('mousemove', drag);
    document.addEventListener('touchmove', drag);
    document.addEventListener('mouseup', stopDrag);
    document.addEventListener('touchend', stopDrag);
  }

  function drag(e) {
    if (!isDragging) return;
    e.preventDefault();

    if (e.type === 'touchmove') {
      translateX = e.touches[0].clientX - startX;
      translateY = e.touches[0].clientY - startY;
    } else {
      translateX = e.clientX - startX;
      translateY = e.clientY - startY;
    }

    updateZoom();
  }

  function stopDrag() {
    isDragging = false;
    const modalDiagram = currentModal?.querySelector('.mermaid-modal-diagram');
    if (modalDiagram) {
      modalDiagram.classList.remove('dragging');
    }
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('touchmove', drag);
    document.removeEventListener('mouseup', stopDrag);
    document.removeEventListener('touchend', stopDrag);
  }

  // Setup modal event listeners
  function setupModalEvents() {
    if (!currentModal) return;

    // Close button
    currentModal.querySelector('.mermaid-close').addEventListener('click', hideModal);

    // Zoom buttons
    currentModal.querySelector('.zoom-in').addEventListener('click', () => {
      currentZoom = Math.min(currentZoom + 0.25, 3);
      updateZoom();
    });

    currentModal.querySelector('.zoom-out').addEventListener('click', () => {
      currentZoom = Math.max(currentZoom - 0.25, 0.5);
      updateZoom();
    });

    currentModal.querySelector('.zoom-reset').addEventListener('click', () => {
      currentZoom = 1;
      resetPosition();
      updateZoom();
    });

    // Close on background click
    currentModal.addEventListener('click', (e) => {
      if (e.target === currentModal) {
        hideModal();
      }
    });

    // Close on ESC key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && currentModal.classList.contains('active')) {
        hideModal();
      }
    });
  }

  // Convert Jekyll-rendered code blocks to mermaid divs
  document.addEventListener('DOMContentLoaded', async function() {
    const codeBlocks = document.querySelectorAll('code.language-mermaid');

    for (const codeBlock of codeBlocks) {
      const pre = codeBlock.parentElement;
      const container = document.createElement('div');
      container.className = 'mermaid-container';

      const mermaidDiv = document.createElement('div');
      mermaidDiv.className = 'mermaid';
      mermaidDiv.textContent = codeBlock.textContent;

      container.appendChild(mermaidDiv);
      pre.replaceWith(container);
    }

    // Render all mermaid diagrams
    try {
      await mermaid.run({
        querySelector: '.mermaid'
      });
      console.log('Mermaid diagrams rendered successfully');
    } catch (error) {
      console.error('Mermaid rendering error:', error);
    }

    // Add click handlers to rendered diagrams
    document.querySelectorAll('.mermaid-container').forEach((container, index) => {
      // Find the rendered SVG inside the container
      const svg = container.querySelector('svg');
      if (!svg) {
        console.warn(`No SVG found in container ${index}`);
        return;
      }

      // Make the container clickable
      container.style.cursor = 'pointer';
      container.title = '点击查看大图';

      container.addEventListener('click', function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Clone the SVG for the modal
        const svgClone = svg.cloneNode(true);
        const tempDiv = document.createElement('div');
        tempDiv.appendChild(svgClone);

        console.log('Opening modal for diagram', index);
        showModal(tempDiv.innerHTML);
      });

      console.log(`Click handler added to diagram ${index}`);
    });
  });
</script>

<blockquote>
  <p>一个调度标志位的改变，如何让数据库吞吐量瞬间腰斩？</p>
</blockquote>

<h2 id="引言从完美运行到性能腰斩">引言：从”完美运行”到”性能腰斩”</h2>

<p>想象一下这样的场景：你的数据库服务器刚刚升级了最新的Linux Kernel 7.0，期待着更好的性能和安全性。然而，上线后监控图表却显示了一个触目惊心的画面——PostgreSQL的吞吐量在毫无征兆的情况下<strong>骤降了将近一半</strong>。</p>

<p>这背后的根源，直指Linux内核调度器在Kernel 7.0中引入的一次重大变更——<strong>惰性抢占（PREEMPT_LAZY）</strong> 模型<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup><sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>。本文将深入技术底层，剖析这次性能衰退的来龙去脉，并探讨其背后的设计哲学冲突。</p>

<h2 id="一linux抢占模型速览吞吐量-vs-响应时间的权衡">一、Linux抢占模型速览：吞吐量 vs. 响应时间的权衡</h2>

<p>要理解这个问题，我们首先需要明白Linux内核是如何决定”何时暂停一个任务，让另一个任务运行”的。这个决策过程被称为<strong>抢占（Preemption）</strong>。多年来，Linux内核提供了几种抢占模式，在<strong>系统吞吐量</strong>和<strong>交互响应时间</strong>之间做出权衡。</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">抢占模式</th>
      <th style="text-align: left">核心机制</th>
      <th style="text-align: left">特点</th>
      <th style="text-align: left">典型场景</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>PREEMPT_NONE</strong></td>
      <td style="text-align: left">任务仅在时间片用完或主动让出时被抢占。</td>
      <td style="text-align: left"><strong>吞吐量最高</strong>，但响应延迟可能较大。</td>
      <td style="text-align: left">服务器、批处理系统</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>PREEMPT_VOLUNTARY</strong></td>
      <td style="text-align: left">在内核代码的”检查点”（如<code class="language-plaintext highlighter-rouge">cond_resched()</code>）主动让出CPU。</td>
      <td style="text-align: left">吞吐量与响应时间的<strong>折中方案</strong>。</td>
      <td style="text-align: left">通用发行版内核默认选项</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>PREEMPT_FULL</strong></td>
      <td style="text-align: left">除了极少数临界区（如持有自旋锁），几乎任何地方都可抢占。</td>
      <td style="text-align: left"><strong>响应延迟极低</strong>，适合桌面和多媒体应用。</td>
      <td style="text-align: left">桌面系统、需要低延迟的场景</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>PREEMPT_RT</strong> (实时补丁)</td>
      <td style="text-align: left">进一步将自旋锁变为可抢占，提供硬实时能力。</td>
      <td style="text-align: left"><strong>确定性响应</strong>，但吞吐量有损耗。</td>
      <td style="text-align: left">工业控制、音视频处理</td>
    </tr>
  </tbody>
</table>

<p>对于绝大多数Linux发行版内核，默认采用的是<code class="language-plaintext highlighter-rouge">PREEMPT_VOLUNTARY</code>模式。而像PostgreSQL这样的数据库，则极度依赖于<code class="language-plaintext highlighter-rouge">PREEMPT_NONE</code>或<code class="language-plaintext highlighter-rouge">PREEMPT_VOLUNTARY</code>带来的高吞吐量特性。</p>

<p>下图展示了不同抢占模型在性能特性上的定位：</p>

<pre><code class="language-mermaid">graph LR
    subgraph "抢占模型的演化与权衡"
        A[PREEMPT_NONE&lt;br/&gt;服务器优化] --&gt;|引入检查点| B[PREEMPT_VOLUNTARY&lt;br/&gt;折中方案]
        B --&gt;|全面可抢占| C[PREEMPT_FULL&lt;br/&gt;桌面优化]
        C --&gt;|硬实时| D[PREEMPT_RT&lt;br/&gt;实时系统]
        B -.-&gt;|v7.0新增| E[PREEMPT_LAZY&lt;br/&gt;简化内核]
    end
    
    style A fill:#90EE90
    style B fill:#FFD700
    style C fill:#FFB6C1
    style D fill:#FF6347
    style E fill:#87CEEB
    
    classDef throughput fill:#90EE90,stroke:#333,stroke-width:2px
    classDef latency fill:#FFB6C1,stroke:#333,stroke-width:2px
    classDef balanced fill:#FFD700,stroke:#333,stroke-width:2px
    classDef newmodel fill:#87CEEB,stroke:#333,stroke-width:3px
</code></pre>

<p><strong>关键特性对比：</strong></p>

<ul>
  <li>🟢 <strong>PREEMPT_NONE/VOLUNTARY</strong>：高吞吐，PostgreSQL的最佳拍档</li>
  <li>🔵 <strong>PREEMPT_LAZY</strong>：试图保持高吞吐，同时简化内核</li>
  <li>🔴 <strong>PREEMPT_FULL/RT</strong>：低延迟优先，牺牲部分吞吐</li>
</ul>

<h3 id="cond_resched一个权宜之计"><code class="language-plaintext highlighter-rouge">cond_resched()</code>：一个权宜之计</h3>

<p>在<code class="language-plaintext highlighter-rouge">PREEMPT_NONE</code>模式下，如果一个内核线程执行了过长的循环，可能会导致其他任务”饿死”。为了解决这个问题，内核开发者在代码中插入了数百个<code class="language-plaintext highlighter-rouge">cond_resched()</code>调用<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>。这就像在高速公路上设置的临时检查站——内核线程运行到这里时，会主动”看一眼”是否有更高优先级的任务需要CPU，如果有，就主动让出。</p>

<p>但这终究是一个<strong>启发式（heuristic）的权宜之计</strong>：它依赖于开发者”猜”对哪里需要插入检查点，而且这些额外的检查点本身也会带来性能开销。</p>

<h2 id="二kernel-70-的改变惰性抢占的登场">二、Kernel 7.0 的改变：惰性抢占的登场</h2>

<p>Kernel 7.0 的调度器迎来了一次重大重构。维护者Peter Zijlstra引入了一个新的抢占模式——<strong>PREEMPT_LAZY（惰性抢占）</strong>。在commit <a href="https://github.com/torvalds/linux/commit/7dadeaa6e851"><code class="language-plaintext highlighter-rouge">7dadeaa6e851</code></a>中，他详细解释了引入这一机制的三个核心原因<sup id="fnref:2:1"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>：</p>

<blockquote>
  <p>The introduction of PREEMPT_LAZY was for multiple reasons:</p>

  <ul>
    <li>PREEMPT_RT suffered from over-scheduling, hurting performance compared to !PREEMPT_RT.</li>
    <li>the introduction of (more) features that rely on preemption; like folio_zero_user() which can do large memset() without preemption checks.</li>
    <li>the endless and uncontrolled sprinkling of cond_resched() – mostly cargo cult or in response to poor to replicate workloads.</li>
  </ul>
</blockquote>

<p>简单来说，核心目标是<strong>简化内核代码，并为最终移除所有的<code class="language-plaintext highlighter-rouge">cond_resched()</code>铺平道路</strong>。</p>

<p>在支持<code class="language-plaintext highlighter-rouge">PREEMPT_LAZY</code>的架构（包括x86和ARM64）上，传统的<code class="language-plaintext highlighter-rouge">PREEMPT_VOLUNTARY</code>选项已从配置菜单中移除<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>。在<a href="https://github.com/torvalds/linux/blob/master/kernel/Kconfig.preempt"><code class="language-plaintext highlighter-rouge">kernel/Kconfig.preempt</code></a>中可以看到：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>config PREEMPT_VOLUNTARY
	bool "Voluntary Kernel Preemption (Desktop)"
	depends on !ARCH_HAS_PREEMPT_LAZY
	depends on !ARCH_NO_PREEMPT
</code></pre></div></div>

<h3 id="技术核心两个标志位的故事">技术核心：两个标志位的故事</h3>

<p><code class="language-plaintext highlighter-rouge">PREEMPT_LAZY</code>的实现非常巧妙，它引入了两个关键的线程标志位。在commit <a href="https://github.com/torvalds/linux/commit/26baa1f1c4bd"><code class="language-plaintext highlighter-rouge">26baa1f1c4bd</code></a>中，Peter Zijlstra描述了这一基础设施<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>：</p>

<blockquote>
  <p>Add the basic infrastructure to split the TIF_NEED_RESCHED bit in two.
Either bit will cause a resched on return-to-user, but only
TIF_NEED_RESCHED will drive IRQ preemption.</p>
</blockquote>

<p>具体来说：</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">TIF_NEED_RESCHED</code>（紧急标志）</strong>：设置此标志意味着<strong>必须立即抢占</strong>当前任务。这通常用于高优先级实时任务被唤醒的场景。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">TIF_NEED_RESCHED_LAZY</code>（惰性标志）</strong>：设置此标志意味着”最好”抢占当前任务，但<strong>不是现在</strong>。这用于普通的调度公平性考虑。</li>
</ol>

<p>在commit <a href="https://github.com/torvalds/linux/commit/7c70cb94d29c"><code class="language-plaintext highlighter-rouge">7c70cb94d29c</code></a>中，Peter Zijlstra进一步说明了工作机制<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>：</p>

<blockquote>
  <p>This LAZY bit will be promoted to the full NEED_RESCHED bit on tick.
As such, the average delay between setting LAZY and actually
rescheduling will be TICK_NSEC/2.</p>

  <p>In short, Lazy preemption will delay preemption for fair class but
will function as Full preemption for all the other classes, most
notably the realtime (RR/FIFO/DEADLINE) classes.</p>
</blockquote>

<p><strong>工作机制：</strong></p>

<ul>
  <li><strong>大多数情况</strong>：当一个普通的高优先级任务被唤醒时，调度器只会设置<code class="language-plaintext highlighter-rouge">TIF_NEED_RESCHED_LAZY</code>标志，而不是传统的<code class="language-plaintext highlighter-rouge">TIF_NEED_RESCHED</code>。</li>
  <li><strong>检查点行为改变</strong>：在<code class="language-plaintext highlighter-rouge">PREEMPT_VOLUNTARY</code>模式下，<code class="language-plaintext highlighter-rouge">cond_resched()</code>会检查<code class="language-plaintext highlighter-rouge">TIF_NEED_RESCHED</code>标志并立即让出CPU。但在新的惰性模式下，<strong><code class="language-plaintext highlighter-rouge">cond_resched()</code>不再检查惰性标志</strong>。</li>
  <li><strong>最终抢占</strong>：当前任务会继续运行，直到下一个<strong>时钟中断（timer tick）</strong> 到来。此时，内核会检查惰性标志，如果被设置，则将其”升级”为紧急标志，并触发抢占。</li>
</ul>

<p>内核在<a href="https://github.com/torvalds/linux/blob/master/kernel/sched/core.c"><code class="language-plaintext highlighter-rouge">kernel/sched/core.c</code></a>中实现了这一机制：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">__always_inline</span> <span class="kt">int</span> <span class="nf">get_lazy_tif_bit</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">dynamic_preempt_lazy</span><span class="p">())</span>
		<span class="k">return</span> <span class="n">TIF_NEED_RESCHED_LAZY</span><span class="p">;</span>

	<span class="k">return</span> <span class="n">TIF_NEED_RESCHED</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">resched_curr_lazy</span><span class="p">(</span><span class="k">struct</span> <span class="n">rq</span> <span class="o">*</span><span class="n">rq</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">__resched_curr</span><span class="p">(</span><span class="n">rq</span><span class="p">,</span> <span class="n">get_lazy_tif_bit</span><span class="p">());</span>
<span class="p">}</span>
</code></pre></div></div>

<p>在时钟中断处理中，惰性标志会被升级为常规的重调度标志：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>	<span class="k">if</span> <span class="p">(</span><span class="n">dynamic_preempt_lazy</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="n">tif_test_bit</span><span class="p">(</span><span class="n">TIF_NEED_RESCHED_LAZY</span><span class="p">))</span>
		<span class="n">resched_curr</span><span class="p">(</span><span class="n">rq</span><span class="p">);</span>
</code></pre></div></div>

<h3 id="改变前后的对比">改变前后的对比</h3>

<p><strong>Kernel 6.x (PREEMPT_VOLUNTARY)</strong>：高优先级任务醒来 → 设置 <code class="language-plaintext highlighter-rouge">TIF_NEED_RESCHED</code> → 当前任务运行到下一个<code class="language-plaintext highlighter-rouge">cond_resched()</code> → <strong>立即让出CPU</strong>。</p>

<p><strong>Kernel 7.0 (PREEMPT_LAZY)</strong>：高优先级任务醒来 → 设置 <code class="language-plaintext highlighter-rouge">TIF_NEED_RESCHED_LAZY</code> → 当前任务<strong>忽略所有<code class="language-plaintext highlighter-rouge">cond_resched()</code>检查点</strong> → 继续运行直到<strong>时钟中断</strong>（例如几毫秒后）→ 升级标志，让出CPU。</p>

<p>下面的时序图展示了这两种模式的关键差异：</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant TS as 任务调度器
    participant CT as 当前任务
    participant CP as cond_resched()
    participant TI as 时钟中断
    
    Note over TS,TI: Kernel 6.x (PREEMPT_VOLUNTARY)
    TS-&gt;&gt;CT: 设置 TIF_NEED_RESCHED
    CT-&gt;&gt;CT: 继续执行...
    CT-&gt;&gt;CP: 运行到检查点
    CP-&gt;&gt;CP: 检查 TIF_NEED_RESCHED
    CP--&gt;&gt;TS: 立即让出CPU (快速响应)
    
    Note over TS,TI: Kernel 7.0 (PREEMPT_LAZY)
    TS-&gt;&gt;CT: 设置 TIF_NEED_RESCHED_LAZY
    CT-&gt;&gt;CT: 继续执行...
    CT-&gt;&gt;CP: 运行到检查点
    CP-&gt;&gt;CP: 忽略 LAZY 标志
    CT-&gt;&gt;CT: 继续执行...
    CT-&gt;&gt;TI: 时钟中断到达
    TI-&gt;&gt;TI: 升级 LAZY → NEED_RESCHED
    TI--&gt;&gt;TS: 触发抢占 (延迟响应)
</code></pre>

<p>简单来说，<strong>内核将抢占决策权从”代码中的分散检查点”收拢到了”调度器的时钟中断”中</strong>。这简化了内核，但也意味着一个任务在被抢占前，可能会运行更长时间。</p>

<h2 id="三postgresql-的自旋锁机制一场对低延迟的极致追求">三、PostgreSQL 的自旋锁机制：一场对低延迟的极致追求</h2>

<p>那么，为什么内核的这个改动会让PostgreSQL”崩溃”呢？答案藏在PostgreSQL为了极致性能而设计的<strong>自旋锁（Spinlock）</strong> 机制中。</p>

<h3 id="自旋而不是睡眠">自旋，而不是睡眠</h3>

<p>在PostgreSQL的源代码<a href="https://github.com/postgres/postgres/blob/master/src/backend/storage/lmgr/s_lock.c"><code class="language-plaintext highlighter-rouge">src/backend/storage/lmgr/s_lock.c</code></a>中，我们可以看到其自旋锁的实现逻辑<sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>。当一个进程尝试获取一个已被其他进程持有的自旋锁时，它不会立即进入睡眠状态（这会导致上下文切换，开销巨大），而是会执行一个<strong>紧凑的循环，反复检查锁是否已被释放</strong>。这个过程被称为<strong>自旋（spinning）</strong>。</p>

<p>PostgreSQL的代码注释清楚地说明了这一点：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
 * When waiting for a contended spinlock we loop tightly for awhile, then
 * delay using pg_usleep() and try again.  Preferably, "awhile" should be a
 * small multiple of the maximum time we expect a spinlock to be held.  100
 * iterations seems about right as an initial guess.  However, on a
 * uniprocessor the loop is a waste of cycles, while in a multi-CPU scenario
 * it's usually better to spin a bit longer than to call the kernel, so we try
 * to adapt the spin loop count depending on whether we seem to be in a
 * uniprocessor or multiprocessor.
 */</span>
</code></pre></div></div>

<p>实际的自旋锁实现：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span>
<span class="nf">s_lock</span><span class="p">(</span><span class="k">volatile</span> <span class="n">slock_t</span> <span class="o">*</span><span class="n">lock</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">file</span><span class="p">,</span> <span class="kt">int</span> <span class="n">line</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">func</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">SpinDelayStatus</span> <span class="n">delayStatus</span><span class="p">;</span>

	<span class="n">init_spin_delay</span><span class="p">(</span><span class="o">&amp;</span><span class="n">delayStatus</span><span class="p">,</span> <span class="n">file</span><span class="p">,</span> <span class="n">line</span><span class="p">,</span> <span class="n">func</span><span class="p">);</span>

	<span class="k">while</span> <span class="p">(</span><span class="n">TAS_SPIN</span><span class="p">(</span><span class="n">lock</span><span class="p">))</span>
	<span class="p">{</span>
		<span class="n">perform_spin_delay</span><span class="p">(</span><span class="o">&amp;</span><span class="n">delayStatus</span><span class="p">);</span>
	<span class="p">}</span>

	<span class="n">finish_spin_delay</span><span class="p">(</span><span class="o">&amp;</span><span class="n">delayStatus</span><span class="p">);</span>

	<span class="k">return</span> <span class="n">delayStatus</span><span class="p">.</span><span class="n">delays</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>PostgreSQL的设计哲学是：<strong>自旋锁保护的临界区代码</strong>应该<strong>极其短小</strong>，通常只是修改几个指针或标志位。因此，持有锁的时间预期只有几十个CPU指令周期。在这种情况下，<strong>“自旋等待”几乎总是比”睡眠唤醒”更快</strong>。</p>

<h3 id="当被误解的自旋锁遭遇更懒的内核">当”被误解”的自旋锁遭遇”更懒”的内核</h3>

<p>问题在于，PostgreSQL的自旋锁机制对内核的抢占行为有一个<strong>强烈的隐含假设</strong>：</p>

<blockquote>
  <p><strong>“我已经把临界区做得非常短了。因此，当我持有自旋锁时，请千万不要抢占我。让我赶紧执行完，释放锁，比让其他CPU上的几十个线程一起自旋空转要好得多。”</strong></p>
</blockquote>

<p>在旧的<code class="language-plaintext highlighter-rouge">PREEMPT_VOLUNTARY</code>模式下，内核”尊重”了这个假设。虽然理论上任何地方都可能被抢占，但实际情况是，由于临界区极短，在它内部触发抢占的概率微乎其微。</p>

<p>但在Kernel 7.0的<code class="language-plaintext highlighter-rouge">PREEMPT_LAZY</code>模式下，情况发生了根本性的变化。虽然临界区很短，但<strong>现在，持锁进程在释放锁之前，更有可能”撞上”时钟中断</strong>。</p>

<p>让我们一步步推演这个灾难场景：</p>

<ol>
  <li><strong>CPU 0</strong>上的进程A获得自旋锁L，开始执行临界区代码。</li>
  <li><strong>此时</strong>，由于某些原因（例如时间片即将用完，或有其他任务被唤醒），调度器为CPU 0设置了<code class="language-plaintext highlighter-rouge">TIF_NEED_RESCHED_LAZY</code>标志。</li>
  <li>进程A继续执行，它并不知道自己被标记了。它快速执行着临界区代码，眼看就要完成了。</li>
  <li><strong>然而</strong>，时钟中断发生了。Kernel 7.0的中断处理程序检查到惰性标志，并将其<strong>升级为紧急抢占标志</strong>。</li>
  <li><strong>内核执行抢占</strong>：进程A的上下文被保存，它被”踢出”CPU。而它<strong>手上还死死握着那把锁L</strong>。</li>
  <li>现在，其他CPU（如CPU 1, CPU 2, …）上的进程B、C、D想要获取锁L。它们执行<code class="language-plaintext highlighter-rouge">TAS</code>操作，发现锁被占用，于是<strong>开始自旋</strong>。</li>
  <li>这些进程在用户态疯狂地自旋、自旋、自旋……<strong>消耗着宝贵的CPU周期，却什么有用的工作都没做</strong>。</li>
  <li>进程A虽然被抢占了，但由于它持有锁，且可能优先级不高，调度器迟迟没有让它重新运行。</li>
  <li>最终，经过漫长的等待（对CPU而言），进程A被重新调度，释放了锁。但此时，整个系统的CPU时间已经被无意义的自旋消耗殆尽。</li>
</ol>

<p>下图展示了这个灾难性的时序：</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant CPU0 as CPU 0 (进程A)
    participant Lock as 自旋锁L
    participant Sched as 调度器
    participant CPU1 as CPU 1 (进程B)
    participant CPU2 as CPU 2 (进程C)
    
    CPU0-&gt;&gt;Lock: 获取锁L
    activate Lock
    CPU0-&gt;&gt;CPU0: 执行临界区代码
    
    Note over Sched: 设置 TIF_NEED_RESCHED_LAZY
    Sched--&gt;&gt;CPU0: (标记，但不立即抢占)
    
    CPU0-&gt;&gt;CPU0: 继续执行临界区...
    
    Note over CPU0: 时钟中断到达！
    Sched-&gt;&gt;CPU0: 升级标志，强制抢占
    Note right of CPU0: 被换出 (仍持有锁L!)
    
    Note over CPU1,CPU2: 其他CPU上的进程尝试获取锁
    
    CPU1-&gt;&gt;Lock: TAS_SPIN(lock)
    Lock--&gt;&gt;CPU1: 失败 (锁被占用)
    CPU1-&gt;&gt;CPU1: 自旋等待...
    CPU1-&gt;&gt;CPU1: 自旋等待...
    
    CPU2-&gt;&gt;Lock: TAS_SPIN(lock)
    Lock--&gt;&gt;CPU2: 失败 (锁被占用)
    CPU2-&gt;&gt;CPU2: 自旋等待...
    CPU2-&gt;&gt;CPU2: 自旋等待...
    
    Note over CPU1,CPU2: CPU空转，浪费算力！
    
    CPU1-&gt;&gt;CPU1: 继续自旋...
    CPU2-&gt;&gt;CPU2: 继续自旋...
    
    Note over Sched,CPU0: 经过漫长等待...
    Sched-&gt;&gt;CPU0: 重新调度进程A
    CPU0-&gt;&gt;CPU0: 完成临界区
    CPU0-&gt;&gt;Lock: 释放锁L
    deactivate Lock
    
    CPU1-&gt;&gt;Lock: TAS_SPIN(lock)
    Lock--&gt;&gt;CPU1: 成功！
    activate Lock
    Note over CPU1,CPU2: 终于可以继续工作了
</code></pre>

<h2 id="四修复方案内核的设计立场与rseq时间片扩展">四、修复方案：内核的设计立场与RSEQ时间片扩展</h2>

<p>面对PostgreSQL的性能问题，调度器维护者Peter Zijlstra在commit <a href="https://github.com/torvalds/linux/commit/476e8583ca16"><code class="language-plaintext highlighter-rouge">476e8583ca16</code></a>中坚定地在x86架构上启用了PREEMPT_LAZY<sup id="fnref:12"><a href="#fn:12" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>，提交信息非常简洁：</p>

<blockquote>
  <p>sched, x86: Enable Lazy preemption</p>

  <p>Add the TIF bit and select the Kconfig symbol to make it go.</p>
</blockquote>

<p>这一决定背后的设计理念可以从commit <a href="https://github.com/torvalds/linux/commit/7dadeaa6e851"><code class="language-plaintext highlighter-rouge">7dadeaa6e851</code></a>中看出<sup id="fnref:2:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>：引入<code class="language-plaintext highlighter-rouge">PREEMPT_LAZY</code>的核心目标是<strong>简化内核代码，最终移除所有<code class="language-plaintext highlighter-rouge">cond_resched()</code>调用</strong>。这是一个正确的技术方向，体现了内核社区的长期愿景：</p>

<ol>
  <li><strong>简化内核</strong>：消除内核代码中数百个启发式的<code class="language-plaintext highlighter-rouge">cond_resched()</code>检查点</li>
  <li><strong>统一调度</strong>：将抢占决策集中到调度器，而非分散在代码各处</li>
  <li><strong>明确责任</strong>：如果用户空间程序依赖特定的抢占行为来保证性能，应该通过显式的内核接口来声明需求，而非依赖隐式假设</li>
</ol>

<h3 id="官方解决方案让postgresql使用rseq时间片扩展">官方解决方案：让PostgreSQL使用RSEQ时间片扩展</h3>

<p>Peter Zijlstra和Thomas Gleixner给出的解决方案是：<strong>让PostgreSQL使用Kernel 7.0中新增的RSEQ（Restartable Sequences）时间片扩展功能</strong><sup id="fnref:13"><a href="#fn:13" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>。</p>

<p><strong>什么是RSEQ？</strong> RSEQ是一种允许用户空间程序与内核安全地协作，执行一系列原子操作的机制。</p>

<p><strong>时间片扩展是什么？</strong> 这是Thomas Gleixner在2025年12月提交的一系列补丁引入的新特性。在commit <a href="https://github.com/torvalds/linux/commit/d7a5da7a0f7f"><code class="language-plaintext highlighter-rouge">d7a5da7a0f7f</code></a>的用户空间API文档中，明确说明了其目的<sup id="fnref:14"><a href="#fn:14" class="footnote" rel="footnote" role="doc-noteref">10</a></sup>：</p>

<blockquote>
  <p>This allows a thread to request a time slice extension when it enters a
critical section to avoid contention on a resource when the thread is
scheduled out inside of the critical section.</p>
</blockquote>

<p>这正是为了解决像PostgreSQL这样的应用在持锁期间被抢占导致的性能问题而设计的！</p>

<p>Linux内核在<a href="https://github.com/torvalds/linux/blob/master/include/uapi/linux/rseq.h"><code class="language-plaintext highlighter-rouge">include/uapi/linux/rseq.h</code></a>中定义了相关接口<sup id="fnref:15"><a href="#fn:15" class="footnote" rel="footnote" role="doc-noteref">11</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
 * rseq_slice_ctrl - Time slice extension control structure
 * ...
 */</span>
<span class="k">struct</span> <span class="n">rseq_slice_ctrl</span> <span class="p">{</span>
	<span class="k">union</span> <span class="p">{</span>
		<span class="n">__u32</span>		<span class="n">all</span><span class="p">;</span>
		<span class="k">struct</span> <span class="p">{</span>
			<span class="n">__u8</span>	<span class="n">request</span><span class="p">;</span>
			<span class="n">__u8</span>	<span class="n">granted</span><span class="p">;</span>
			<span class="n">__u16</span>	<span class="n">__reserved</span><span class="p">;</span>
		<span class="p">};</span>
	<span class="p">};</span>
<span class="p">};</span>

<span class="k">struct</span> <span class="n">rseq</span> <span class="p">{</span>
	<span class="c1">// ...</span>
	<span class="k">struct</span> <span class="n">rseq_slice_ctrl</span> <span class="n">slice_ctrl</span><span class="p">;</span>
	<span class="c1">// ...</span>
<span class="p">};</span>
</code></pre></div></div>

<p>其效果是：<strong>当该线程持有关键锁（即处于RSEQ临界区）时，内核调度器将暂时”无视”针对它的惰性抢占标志，不会在时钟中断时强行抢占它</strong>。这相当于PostgreSQL向内核宣告：”给我几十微秒，我马上就完事，别打断我。”</p>

<p>这完美地解决了我们之前分析的”持锁被抢”的困境。PostgreSQL可以获得它梦寐以求的”短时不可抢占”保证，同时内核也可以继续朝着更简洁、更统一的调度架构演进。</p>

<p>PostgreSQL社区需要在其代码中集成RSEQ时间片扩展的支持。这需要修改PostgreSQL锁管理器（<code class="language-plaintext highlighter-rouge">s_lock.c</code>）的实现，在获取自旋锁前请求时间片扩展，释放锁后清除请求，从而避免在持锁期间被抢占。</p>

<h3 id="如何使用rseq时间片扩展">如何使用RSEQ时间片扩展</h3>

<p>根据内核文档<sup id="fnref:14:1"><a href="#fn:14" class="footnote" rel="footnote" role="doc-noteref">10</a></sup>，应用程序需要按以下步骤启用这个功能：</p>

<ol>
  <li><strong>注册RSEQ</strong>：通过<code class="language-plaintext highlighter-rouge">rseq()</code>系统调用注册一个用户空间内存区域</li>
  <li><strong>启用时间片扩展</strong>：通过<code class="language-plaintext highlighter-rouge">prctl()</code>启用该功能：</li>
</ol>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">prctl</span><span class="p">(</span><span class="n">PR_RSEQ_SLICE_EXTENSION</span><span class="p">,</span> <span class="n">PR_RSEQ_SLICE_EXTENSION_SET</span><span class="p">,</span>
      <span class="n">PR_RSEQ_SLICE_EXT_ENABLE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</code></pre></div></div>

<ol>
  <li><strong>请求扩展</strong>：在进入临界区前，在<code class="language-plaintext highlighter-rouge">rseq-&gt;slice_ctrl.request</code>字段设置请求位</li>
  <li><strong>检查授权</strong>：内核会在<code class="language-plaintext highlighter-rouge">rseq-&gt;slice_ctrl.granted</code>字段返回是否授权</li>
</ol>

<p>下图展示了RSEQ时间片扩展的完整工作流程：</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant App as 用户态应用(PostgreSQL)
    participant RSEQ as RSEQ结构(共享内存)
    participant Kernel as 内核调度器
    participant Timer as 时钟中断
    
    Note over App,Kernel: 初始化阶段
    App-&gt;&gt;Kernel: rseq() 系统调用
    Kernel--&gt;&gt;RSEQ: 分配共享内存区域
    App-&gt;&gt;Kernel: prctl(PR_RSEQ_SLICE_EXTENSION_SET)
    Kernel--&gt;&gt;App: 启用成功
    
    Note over App,Timer: 运行时：进入临界区
    App-&gt;&gt;RSEQ: 设置 slice_ctrl.request = 1
    App-&gt;&gt;App: 获取自旋锁
    App-&gt;&gt;App: 执行临界区代码...
    
    Note over Kernel,Timer: 调度压力出现
    Kernel-&gt;&gt;Kernel: 设置 TIF_NEED_RESCHED_LAZY
    Timer-&gt;&gt;Kernel: 时钟中断到达
    
    Kernel-&gt;&gt;RSEQ: 检查 slice_ctrl.request
    alt 请求有效且无其他待处理工作
        Kernel-&gt;&gt;RSEQ: 设置 slice_ctrl.granted = 1
        Kernel-&gt;&gt;Kernel: 忽略抢占，允许继续运行
        Note over Kernel: 授予时间片扩展
    else 有待处理工作或其他条件不满足
        Kernel-&gt;&gt;RSEQ: 拒绝请求 (granted = 0)
        Kernel-&gt;&gt;App: 执行抢占
    end
    
    Note over App,Timer: 完成临界区
    App-&gt;&gt;App: 释放自旋锁
    App-&gt;&gt;RSEQ: 清除 slice_ctrl.request
    RSEQ--&gt;&gt;Kernel: 下次时钟中断时清除 granted
</code></pre>

<p>这个机制的核心实现在commit <a href="https://github.com/torvalds/linux/commit/dfb630f548a7"><code class="language-plaintext highlighter-rouge">dfb630f548a7</code></a>中，由Thomas Gleixner详细说明了授权决策过程<sup id="fnref:16"><a href="#fn:16" class="footnote" rel="footnote" role="doc-noteref">12</a></sup>：只有在从中断返回用户态、且没有其他待处理工作（如信号）时，才会授予时间片扩展。</p>

<h2 id="五postgresql与linux内核的协作历史numa案例">五、PostgreSQL与Linux内核的协作历史：NUMA案例</h2>

<p>有趣的是，PostgreSQL和Linux内核之间的互动并非总是冲突。一个很好的协作案例发生在2025年，当时PostgreSQL 18引入了新的NUMA内省功能。</p>

<p>在开发过程中，PostgreSQL开发者发现了Linux内核中<code class="language-plaintext highlighter-rouge">do_pages_stat()</code>函数的一个长期存在的bug（自2010年起）<sup id="fnref:10"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">13</a></sup>。这个bug影响所有在64位内核上运行32位用户空间的系统。PostgreSQL开发者Christoph Berg提交了内核修复<a href="https://github.com/torvalds/linux/commit/10d04c26ab2b"><code class="language-plaintext highlighter-rouge">10d04c26ab2b</code></a>：</p>

<blockquote>
  <p>Discovered while working on PostgreSQL 18’s new NUMA introspection code.</p>

  <p>For arrays with more than 16 entries, the old code would incorrectly
advance the pages pointer by 16 words instead of 16 compat_uptr_t.</p>
</blockquote>

<p>同时，PostgreSQL也在自己的代码中实现了规避措施，在commit <a href="https://github.com/postgres/postgres/commit/7fe2f67c7c9"><code class="language-plaintext highlighter-rouge">7fe2f67c7c9</code></a>中限制了<code class="language-plaintext highlighter-rouge">numa_move_pages</code>请求的大小<sup id="fnref:11"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">14</a></sup>：</p>

<blockquote>
  <p>This is a long-standing kernel bug (since 2010), affecting pretty much
all kernels, so it’ll take time until all systems get a fixed kernel.
Luckily, we can work around the issue by chunking the requests the same
way do_pages_stat() does, at least on affected systems.</p>
</blockquote>

<p>这个案例展示了<strong>开源项目之间健康的协作模式</strong>：发现问题后，同时修复内核bug并在应用层实现兼容性处理，确保在旧内核上也能正常工作。</p>

<h2 id="六总结与展望一次痛苦的蜕变">六、总结与展望：一次痛苦的蜕变</h2>

<p>Kernel 7.0与PostgreSQL的这次”冲突”，并非谁的错，而是计算机系统设计中的一个经典矛盾：<strong>通用操作系统的演进 vs. 特定领域应用的极致优化</strong>。</p>

<ul>
  <li><strong>对Linux而言</strong>：<code class="language-plaintext highlighter-rouge">PREEMPT_LAZY</code>是一次勇敢的”自我简化”手术。它摒弃了历史包袱，为未来几十年的调度器发展奠定了基础<sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">15</a></sup>。尽管短期内带来了阵痛，但方向是正确的。</li>
  <li><strong>对PostgreSQL而言</strong>：这次事件是一次警醒。它揭示了自己过去一直依赖的”在<code class="language-plaintext highlighter-rouge">PREEMPT_VOLUNTARY</code>下不会被抢占”的假设，其实只是一个美丽而脆弱的巧合。拥抱RSEQ等新内核机制，将使其性能模型更加健壮和可移植。</li>
</ul>

<p>这次性能腰斩事件，本质上是<strong>两个高度复杂的系统在”无锁化”和”抢占”的边缘地带，发生的一次深刻的碰撞</strong>。它再次证明了一个朴素的真理：在系统软件的世界里，没有银弹。每一个看似微小的”优化”，都可能在其他地方掀起惊涛骇浪。而解决之道，不在于互相指责和回退，而在于更深层次的<strong>协作与适配</strong>。</p>

<p>最终，一个更简洁、更强大的Linux内核，和一个更健壮、更高效的PostgreSQL，都将从这个痛苦的蜕变中诞生。</p>

<hr />

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>LKML讨论：《Re: [PATCH v3 00/20] sched: EEVDF and latency-nice and/or slice-attr》，讨论了抢占模型变化对数据库工作负载的影响。参见：<a href="https://lkml.kernel.org/r/20241007075055.555778919@infradead.org">https://lkml.kernel.org/r/20241007075055.555778919@infradead.org</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>Peter Zijlstra，Linux内核提交 <a href="https://github.com/torvalds/linux/commit/7dadeaa6e851"><code class="language-plaintext highlighter-rouge">7dadeaa6e851</code></a> — <em>sched: Further restrict the preemption modes</em>。详细说明了引入PREEMPT_LAZY的三个核心原因，以及为何限制PREEMPT_NONE和PREEMPT_VOLUNTARY。完整提交信息：<a href="https://patch.msgid.link/20251219101502.GB1132199@noisy.programming.kicks-ass.net">https://patch.msgid.link/20251219101502.GB1132199@noisy.programming.kicks-ass.net</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:2:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:3">
      <p>Linux内核文档，《preempt-locking.rst》，详细说明了内核抢占模型的演化和<code class="language-plaintext highlighter-rouge">cond_resched()</code>的使用。参见：<a href="https://github.com/torvalds/linux/blob/master/Documentation/locking/preempt-locking.rst">Documentation/locking/preempt-locking.rst</a> <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p>Paul E. McKenney，Linux内核提交 <a href="https://github.com/torvalds/linux/commit/78c2ce0fd6dd"><code class="language-plaintext highlighter-rouge">78c2ce0fd6dd</code></a> — <em>scftorture: Update due to x86 not supporting none/voluntary preemption</em>。明确说明”As of v7.0-rc1, architectures that support preemption, including x86 and arm64, no longer support CONFIG_PREEMPT_NONE or CONFIG_PREEMPT_VOLUNTARY.” 链接：<a href="https://patch.msgid.link/20260303235903.1967409-4-paulmck@kernel.org">https://patch.msgid.link/20260303235903.1967409-4-paulmck@kernel.org</a> <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5">
      <p>Peter Zijlstra，Linux内核提交 <a href="https://github.com/torvalds/linux/commit/26baa1f1c4bd"><code class="language-plaintext highlighter-rouge">26baa1f1c4bd</code></a> — <em>sched: Add TIF_NEED_RESCHED_LAZY infrastructure</em>。说明：”Add the basic infrastructure to split the TIF_NEED_RESCHED bit in two.” 链接：<a href="https://lkml.kernel.org/r/20241007075055.219540785@infradead.org">https://lkml.kernel.org/r/20241007075055.219540785@infradead.org</a> <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6">
      <p>Peter Zijlstra，Linux内核提交 <a href="https://github.com/torvalds/linux/commit/7c70cb94d29c"><code class="language-plaintext highlighter-rouge">7c70cb94d29c</code></a> — <em>sched: Add Lazy preemption model</em>。说明：”This LAZY bit will be promoted to the full NEED_RESCHED bit on tick. As such, the average delay between setting LAZY and actually rescheduling will be TICK_NSEC/2.” 链接：<a href="https://lkml.kernel.org/r/20241007075055.331243614@infradead.org">https://lkml.kernel.org/r/20241007075055.331243614@infradead.org</a> <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7">
      <p>PostgreSQL源码 <a href="https://github.com/postgres/postgres/blob/master/src/backend/storage/lmgr/s_lock.c"><code class="language-plaintext highlighter-rouge">src/backend/storage/lmgr/s_lock.c</code></a> — 自旋锁的实现，包括<code class="language-plaintext highlighter-rouge">s_lock()</code>函数和相关注释，说明了为何选择自旋而非立即睡眠。 <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:12">
      <p>Peter Zijlstra，Linux内核提交 <a href="https://github.com/torvalds/linux/commit/476e8583ca16"><code class="language-plaintext highlighter-rouge">476e8583ca16</code></a> — <em>sched, x86: Enable Lazy preemption</em>。在x86架构上启用PREEMPT_LAZY的关键提交。链接：<a href="https://lkml.kernel.org/r/20241007075055.555778919@infradead.org">https://lkml.kernel.org/r/20241007075055.555778919@infradead.org</a> <a href="#fnref:12" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:13">
      <p>LKML patch series：《[PATCH 00/14] Restartable Sequences: selftests, time-slice extension》，Thomas Gleixner提出RSEQ时间片扩展机制，共14个补丁。链接：<a href="https://lkml.kernel.org/r/20251215155615.870031952@linutronix.de">https://lkml.kernel.org/r/20251215155615.870031952@linutronix.de</a> <a href="#fnref:13" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:14">
      <p>Thomas Gleixner，Linux内核提交 <a href="https://github.com/torvalds/linux/commit/d7a5da7a0f7f"><code class="language-plaintext highlighter-rouge">d7a5da7a0f7f</code></a> — <em>rseq: Add fields and constants for time slice extension</em>。在用户空间API文档（<code class="language-plaintext highlighter-rouge">Documentation/userspace-api/rseq.rst</code>）中说明：”This allows a thread to request a time slice extension when it enters a critical section to avoid contention on a resource when the thread is scheduled out inside of the critical section.” 链接：<a href="https://patch.msgid.link/20251215155708.669472597@linutronix.de">https://patch.msgid.link/20251215155708.669472597@linutronix.de</a> <a href="#fnref:14" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:14:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:15">
      <p>Linux内核UAPI头文件 <a href="https://github.com/torvalds/linux/blob/master/include/uapi/linux/rseq.h"><code class="language-plaintext highlighter-rouge">include/uapi/linux/rseq.h</code></a> — 定义了<code class="language-plaintext highlighter-rouge">struct rseq_slice_ctrl</code>和相关的RSEQ时间片扩展接口。相关的<code class="language-plaintext highlighter-rouge">prctl()</code>接口定义在commit <a href="https://github.com/torvalds/linux/commit/28621ec2d46c"><code class="language-plaintext highlighter-rouge">28621ec2d46c</code></a>中。 <a href="#fnref:15" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:16">
      <p>Thomas Gleixner，Linux内核提交 <a href="https://github.com/torvalds/linux/commit/dfb630f548a7"><code class="language-plaintext highlighter-rouge">dfb630f548a7</code></a> — <em>rseq: Implement rseq_grant_slice_extension()</em>。详细说明了时间片扩展的授权决策逻辑：”The decision is made in two stages. First an inline quick check to avoid going into the actual decision function.” 链接：<a href="https://patch.msgid.link/20251215155709.195303303@linutronix.de">https://patch.msgid.link/20251215155709.195303303@linutronix.de</a> <a href="#fnref:16" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:10">
      <p>Christoph Berg，Linux内核提交 <a href="https://github.com/torvalds/linux/commit/10d04c26ab2b"><code class="language-plaintext highlighter-rouge">10d04c26ab2b</code></a> — <em>mm/migrate: fix do_pages_stat in compat mode</em>。说明：”Discovered while working on PostgreSQL 18’s new NUMA introspection code.” 修复了一个自2010年以来的内核bug。链接：<a href="https://lkml.kernel.org/r/aGREU0XTB48w9CwN@msg.df7cb.de">https://lkml.kernel.org/r/aGREU0XTB48w9CwN@msg.df7cb.de</a> <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:11">
      <p>Tomas Vondra，PostgreSQL提交 <a href="https://github.com/postgres/postgres/commit/7fe2f67c7c9"><code class="language-plaintext highlighter-rouge">7fe2f67c7c9</code></a> — <em>Limit the size of numa_move_pages requests</em>。PostgreSQL侧对内核bug的规避措施。讨论：<a href="https://postgr.es/m/aEtDozLmtZddARdB@msg.df7cb.de">https://postgr.es/m/aEtDozLmtZddARdB@msg.df7cb.de</a> <a href="#fnref:11" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:9">
      <p>Linux内核UAPI头文件 <a href="https://github.com/torvalds/linux/blob/master/include/uapi/linux/rseq.h"><code class="language-plaintext highlighter-rouge">include/uapi/linux/rseq.h</code></a> — 定义了<code class="language-plaintext highlighter-rouge">struct rseq_slice_ctrl</code>和相关的RSEQ时间片扩展接口。 <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="linux-kernel" /><summary type="html"><![CDATA[剖析 Linux 内核 PREEMPT_LAZY 抢占模型变更如何影响 PostgreSQL 等数据库工作负载的性能表现。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://weinan.tech/images/og/linux-kernel.png" /><media:content medium="image" url="https://weinan.tech/images/og/linux-kernel.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Linux内核调度的时钟心跳：定时器中断、抢占与实时性的权衡</title><link href="https://weinan.tech/2026/04/07/linux-kernel-scheduling-timer-interrupt-preemption.html" rel="alternate" type="text/html" title="Linux内核调度的时钟心跳：定时器中断、抢占与实时性的权衡" /><published>2026-04-07T00:00:00+08:00</published><updated>2026-04-07T00:00:00+08:00</updated><id>https://weinan.tech/2026/04/07/linux-kernel-scheduling-timer-interrupt-preemption</id><content type="html" xml:base="https://weinan.tech/2026/04/07/linux-kernel-scheduling-timer-interrupt-preemption.html"><![CDATA[<style>
/* Mermaid diagram container */
.mermaid-container {
  position: relative;
  display: block;
  cursor: pointer;
  transition: opacity 0.2s;
  max-width: 100%;
  margin: 20px 0;
  overflow-x: auto;
}

.mermaid-container:hover {
  opacity: 0.8;
}

.mermaid-container svg {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Modal overlay */
.mermaid-modal {
  display: none;
  position: fixed;
  z-index: 9999;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9);
  animation: fadeIn 0.3s;
}

.mermaid-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Modal content */
.mermaid-modal-content {
  position: relative;
  width: 90vw;
  height: 90vh;
  overflow: hidden;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
}

.mermaid-modal-diagram {
  transform-origin: center center;
  transition: transform 0.2s ease;
  display: inline-block;
  min-width: 100%;
  cursor: grab;
  user-select: none;
}

.mermaid-modal-diagram.dragging {
  cursor: grabbing;
  transition: none;
}

.mermaid-modal-diagram svg {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;
}

/* Control buttons */
.mermaid-controls {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
  z-index: 10000;
}

.mermaid-btn {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
  color: #333;
  font-weight: 500;
}

.mermaid-btn:hover {
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Close button */
.mermaid-close {
  background: #f44336;
  color: white;
  border: none;
}

.mermaid-close:hover {
  background: #d32f2f;
}

/* Zoom indicator */
.mermaid-zoom-level {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 10000;
}
</style>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  mermaid.initialize({
    startOnLoad: false,
    theme: 'default',
    securityLevel: 'loose',
    htmlLabels: true,
    themeVariables: {
      fontSize: '14px'
    }
  });

  let currentZoom = 1;
  let currentModal = null;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let translateX = 0;
  let translateY = 0;

  // Create modal HTML
  function createModal() {
    const modal = document.createElement('div');
    modal.className = 'mermaid-modal';
    modal.innerHTML = `
      <div class="mermaid-controls">
        <button class="mermaid-btn zoom-in">放大 +</button>
        <button class="mermaid-btn zoom-out">缩小 -</button>
        <button class="mermaid-btn zoom-reset">重置</button>
        <button class="mermaid-btn mermaid-close">关闭 ✕</button>
      </div>
      <div class="mermaid-modal-content">
        <div class="mermaid-modal-diagram"></div>
      </div>
      <div class="mermaid-zoom-level">100%</div>
    `;
    document.body.appendChild(modal);
    return modal;
  }

  // Show modal with diagram
  function showModal(diagramContent) {
    if (!currentModal) {
      currentModal = createModal();
      setupModalEvents();
    }

    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.innerHTML = diagramContent;

    // Remove any width/height attributes from SVG to make it responsive
    const svg = modalDiagram.querySelector('svg');
    if (svg) {
      svg.removeAttribute('width');
      svg.removeAttribute('height');
      svg.style.width = '100%';
      svg.style.height = 'auto';
    }

    // Setup drag functionality
    setupDrag(modalDiagram);

    currentModal.classList.add('active');
    currentZoom = 1;
    resetPosition();
    updateZoom();
    document.body.style.overflow = 'hidden';
  }

  // Hide modal
  function hideModal() {
    if (currentModal) {
      currentModal.classList.remove('active');
      document.body.style.overflow = '';
    }
  }

  // Update zoom level
  function updateZoom() {
    if (!currentModal) return;
    const diagram = currentModal.querySelector('.mermaid-modal-diagram');
    const zoomLevel = currentModal.querySelector('.mermaid-zoom-level');
    diagram.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentZoom})`;
    zoomLevel.textContent = `${Math.round(currentZoom * 100)}%`;
  }

  // Reset position when zoom changes
  function resetPosition() {
    translateX = 0;
    translateY = 0;
  }

  // Setup drag functionality
  function setupDrag(element) {
    element.addEventListener('mousedown', startDrag);
    element.addEventListener('touchstart', startDrag);
  }

  function startDrag(e) {
    if (e.type === 'mousedown' && e.button !== 0) return; // Only left click

    isDragging = true;
    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.classList.add('dragging');

    if (e.type === 'touchstart') {
      startX = e.touches[0].clientX - translateX;
      startY = e.touches[0].clientY - translateY;
    } else {
      startX = e.clientX - translateX;
      startY = e.clientY - translateY;
    }

    document.addEventListener('mousemove', drag);
    document.addEventListener('touchmove', drag);
    document.addEventListener('mouseup', stopDrag);
    document.addEventListener('touchend', stopDrag);
  }

  function drag(e) {
    if (!isDragging) return;
    e.preventDefault();

    if (e.type === 'touchmove') {
      translateX = e.touches[0].clientX - startX;
      translateY = e.touches[0].clientY - startY;
    } else {
      translateX = e.clientX - startX;
      translateY = e.clientY - startY;
    }

    updateZoom();
  }

  function stopDrag() {
    isDragging = false;
    const modalDiagram = currentModal?.querySelector('.mermaid-modal-diagram');
    if (modalDiagram) {
      modalDiagram.classList.remove('dragging');
    }
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('touchmove', drag);
    document.removeEventListener('mouseup', stopDrag);
    document.removeEventListener('touchend', stopDrag);
  }

  // Setup modal event listeners
  function setupModalEvents() {
    if (!currentModal) return;

    // Close button
    currentModal.querySelector('.mermaid-close').addEventListener('click', hideModal);

    // Zoom buttons
    currentModal.querySelector('.zoom-in').addEventListener('click', () => {
      currentZoom = Math.min(currentZoom + 0.25, 3);
      updateZoom();
    });

    currentModal.querySelector('.zoom-out').addEventListener('click', () => {
      currentZoom = Math.max(currentZoom - 0.25, 0.5);
      updateZoom();
    });

    currentModal.querySelector('.zoom-reset').addEventListener('click', () => {
      currentZoom = 1;
      resetPosition();
      updateZoom();
    });

    // Close on background click
    currentModal.addEventListener('click', (e) => {
      if (e.target === currentModal) {
        hideModal();
      }
    });

    // Close on ESC key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && currentModal.classList.contains('active')) {
        hideModal();
      }
    });
  }

  // Convert Jekyll-rendered code blocks to mermaid divs
  document.addEventListener('DOMContentLoaded', async function() {
    const codeBlocks = document.querySelectorAll('code.language-mermaid');

    for (const codeBlock of codeBlocks) {
      const pre = codeBlock.parentElement;
      const container = document.createElement('div');
      container.className = 'mermaid-container';

      const mermaidDiv = document.createElement('div');
      mermaidDiv.className = 'mermaid';
      mermaidDiv.textContent = codeBlock.textContent;

      container.appendChild(mermaidDiv);
      pre.replaceWith(container);
    }

    // Render all mermaid diagrams
    try {
      await mermaid.run({
        querySelector: '.mermaid'
      });
      console.log('Mermaid diagrams rendered successfully');
    } catch (error) {
      console.error('Mermaid rendering error:', error);
    }

    // Add click handlers to rendered diagrams
    document.querySelectorAll('.mermaid-container').forEach((container, index) => {
      // Find the rendered SVG inside the container
      const svg = container.querySelector('svg');
      if (!svg) {
        console.warn(`No SVG found in container ${index}`);
        return;
      }

      // Make the container clickable
      container.style.cursor = 'pointer';
      container.title = '点击查看大图';

      container.addEventListener('click', function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Clone the SVG for the modal
        const svgClone = svg.cloneNode(true);
        const tempDiv = document.createElement('div');
        tempDiv.appendChild(svgClone);

        console.log('Opening modal for diagram', index);
        showModal(tempDiv.innerHTML);
      });

      console.log(`Click handler added to diagram ${index}`);
    });
  });
</script>

<blockquote>
  <p>内核如何决定”现在该换谁运行了”？</p>
</blockquote>

<h2 id="引言操作系统的心跳">引言：操作系统的”心跳”</h2>

<p>当你的Linux系统同时运行着数百个进程，内核是如何决定在什么时刻暂停一个任务、让另一个任务运行的？这个看似简单的问题，背后隐藏着操作系统设计中最核心的权衡：<strong>公平性 vs. 实时性</strong>，<strong>吞吐量 vs. 响应延迟</strong>。</p>

<p>答案的关键在于一个持续跳动的”心跳”——<strong>定时器中断（Timer Interrupt）</strong>。它就像一个永不停歇的闹钟，每隔几毫秒就提醒内核：”该检查一下，是不是要换个任务运行了？”</p>

<p>但这只是故事的一部分。本文将深入Linux内核的调度子系统，揭示定时器中断在任务调度中的真实角色，以及它与抢占机制、实时操作系统的微妙关系。</p>

<h2 id="一定时器中断调度的驱动力还是可选项">一、定时器中断：调度的驱动力还是可选项？</h2>

<h3 id="11-传统观点定时器中断是调度的核心">1.1 传统观点：定时器中断是调度的核心</h3>

<p>在经典的操作系统教科书中，任务调度的基本模型是这样的：</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant HW as 硬件定时器
    participant IRQ as 中断控制器
    participant Kernel as 内核调度器
    participant Task as 当前任务
    
    Note over HW: 每隔固定时间(如1ms)
    HW-&gt;&gt;IRQ: 产生定时器中断
    IRQ-&gt;&gt;Kernel: 触发中断处理程序
    Kernel-&gt;&gt;Kernel: scheduler_tick()
    Kernel-&gt;&gt;Kernel: 检查时间片是否用完
    alt 时间片用完
        Kernel-&gt;&gt;Task: 设置 TIF_NEED_RESCHED
        Kernel-&gt;&gt;Kernel: 触发任务切换
    else 继续运行
        Kernel-&gt;&gt;Task: 返回继续执行
    end
</code></pre>

<p>这个模型在Linux早期版本（以及许多教学用的简化内核）中是准确的：</p>

<ol>
  <li><strong>硬件定时器</strong>（如x86的PIT或APIC timer）每隔固定时间（称为一个”tick”，通常是1ms或10ms）产生中断</li>
  <li>内核的<strong>时钟中断处理程序</strong>被调用</li>
  <li>调度器检查当前任务的<strong>时间片（time slice）</strong>是否用完</li>
  <li>如果用完，设置”需要重新调度”标志，在中断返回时触发任务切换</li>
</ol>

<h3 id="12-现代linuxtickless与动态时钟">1.2 现代Linux：Tickless与动态时钟</h3>

<p>然而，现代Linux内核（特别是启用了<strong>CONFIG_NO_HZ_FULL</strong>的系统）引入了”tickless”模式<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>，彻底改变了这个模型：</p>

<p><strong>传统模式（HZ=1000）</strong>：即使CPU完全空闲，每秒也会产生1000次定时器中断。</p>

<p><strong>Tickless模式</strong>：当CPU只运行一个任务且没有定时器到期时，内核会<strong>完全停止周期性的时钟中断</strong>，只在以下情况下设置定时器：</p>
<ul>
  <li>有定时器事件需要处理</li>
  <li>调度器需要检查任务状态</li>
  <li>RCU需要进行grace period处理</li>
</ul>

<p>这意味着：<strong>定时器中断不是调度的必要条件，而是一种优化手段</strong>。</p>

<pre><code class="language-mermaid">graph LR
    subgraph "传统定时器模式 (CONFIG_HZ_PERIODIC)"
        A[1ms] --&gt; B[中断]
        B --&gt; C[1ms]
        C --&gt; D[中断]
        D --&gt; E[1ms]
        E --&gt; F[中断]
    end
    
    subgraph "Tickless模式 (CONFIG_NO_HZ_FULL)"
        G[运行中...] -.-&gt;|仅在需要时| H[中断]
        H -.-&gt;|可能很长时间| I[中断]
    end
    
    style B fill:#FF6347
    style D fill:#FF6347
    style F fill:#FF6347
    style H fill:#87CEEB
    style I fill:#87CEEB
</code></pre>

<h3 id="13-那么调度到底在哪里发生">1.3 那么，调度到底在哪里发生？</h3>

<p>Linux内核中，任务切换（调用<code class="language-plaintext highlighter-rouge">schedule()</code>函数）可以在以下几个<strong>调度点（scheduling point）</strong>发生：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">调度点</th>
      <th style="text-align: left">触发条件</th>
      <th style="text-align: left">是否依赖定时器中断</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>中断返回</strong></td>
      <td style="text-align: left">从任何中断（包括时钟中断）返回用户态时，检查<code class="language-plaintext highlighter-rouge">TIF_NEED_RESCHED</code>标志</td>
      <td style="text-align: left">部分依赖</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>系统调用返回</strong></td>
      <td style="text-align: left">系统调用结束返回用户态前</td>
      <td style="text-align: left">不依赖</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>主动调用</strong></td>
      <td style="text-align: left">任务调用<code class="language-plaintext highlighter-rouge">schedule()</code>、<code class="language-plaintext highlighter-rouge">yield()</code>或阻塞在I/O上</td>
      <td style="text-align: left">不依赖</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>抢占点</strong></td>
      <td style="text-align: left">内核代码中的<code class="language-plaintext highlighter-rouge">preempt_enable()</code>或<code class="language-plaintext highlighter-rouge">cond_resched()</code></td>
      <td style="text-align: left">不依赖</td>
    </tr>
  </tbody>
</table>

<p><strong>结论</strong>：定时器中断<strong>不是唯一的调度驱动力</strong>，但它是保证<strong>公平性和防止任务饿死</strong>的关键机制。</p>

<h2 id="二深入内核代码定时器中断如何触发调度">二、深入内核代码：定时器中断如何触发调度</h2>

<h3 id="21-时钟中断的处理路径">2.1 时钟中断的处理路径</h3>

<p>在Linux内核中，时钟中断的处理流程如下（以x86-64为例）：</p>

<p><strong>硬件中断 → 中断处理程序 → 调度器检查</strong></p>

<p>关键函数调用链（基于Linux 6.x/7.x）：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// arch/x86/kernel/time.c - 时钟中断入口</span>
<span class="kt">void</span> <span class="n">__irq_entry</span> <span class="nf">smp_apic_timer_interrupt</span><span class="p">(</span><span class="k">struct</span> <span class="n">pt_regs</span> <span class="o">*</span><span class="n">regs</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">entering_irq</span><span class="p">();</span>
    <span class="n">trace_local_timer_entry</span><span class="p">(</span><span class="n">LOCAL_TIMER_VECTOR</span><span class="p">);</span>
    <span class="n">local_apic_timer_interrupt</span><span class="p">();</span>  <span class="c1">// 处理本地APIC定时器</span>
    <span class="n">trace_local_timer_exit</span><span class="p">(</span><span class="n">LOCAL_TIMER_VECTOR</span><span class="p">);</span>
    <span class="n">exiting_irq</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>调用链继续：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>local_apic_timer_interrupt()
  └─&gt; tick_handle_periodic() 或 hrtimer_interrupt()  // 取决于是否启用高精度定时器
      └─&gt; update_process_times()
          └─&gt; scheduler_tick()  // 调度器的时钟处理函数
</code></pre></div></div>

<h3 id="22-调度器的时钟心跳scheduler_tick">2.2 调度器的时钟心跳：<code class="language-plaintext highlighter-rouge">scheduler_tick()</code></h3>

<p>这是调度器在每个时钟中断中被调用的核心函数，定义在<a href="https://github.com/torvalds/linux/blob/master/kernel/sched/core.c"><code class="language-plaintext highlighter-rouge">kernel/sched/core.c</code></a>中<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
 * This function gets called by the timer code, with HZ frequency.
 * We call it with interrupts disabled.
 */</span>
<span class="kt">void</span> <span class="nf">scheduler_tick</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">int</span> <span class="n">cpu</span> <span class="o">=</span> <span class="n">smp_processor_id</span><span class="p">();</span>
    <span class="k">struct</span> <span class="n">rq</span> <span class="o">*</span><span class="n">rq</span> <span class="o">=</span> <span class="n">cpu_rq</span><span class="p">(</span><span class="n">cpu</span><span class="p">);</span>
    <span class="k">struct</span> <span class="n">task_struct</span> <span class="o">*</span><span class="n">curr</span> <span class="o">=</span> <span class="n">rq</span><span class="o">-&gt;</span><span class="n">curr</span><span class="p">;</span>
    
    <span class="c1">// 更新运行队列时钟</span>
    <span class="n">update_rq_clock</span><span class="p">(</span><span class="n">rq</span><span class="p">);</span>
    
    <span class="c1">// 调用当前调度类的 task_tick 方法</span>
    <span class="n">curr</span><span class="o">-&gt;</span><span class="n">sched_class</span><span class="o">-&gt;</span><span class="n">task_tick</span><span class="p">(</span><span class="n">rq</span><span class="p">,</span> <span class="n">curr</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    
    <span class="c1">// 检查是否需要触发负载均衡</span>
    <span class="n">trigger_load_balance</span><span class="p">(</span><span class="n">rq</span><span class="p">);</span>
    
    <span class="c1">// ... 其他统计和处理</span>
<span class="p">}</span>
</code></pre></div></div>

<p>关键点：</p>
<ol>
  <li><strong>每个CPU独立处理</strong>：<code class="language-plaintext highlighter-rouge">scheduler_tick()</code>在每个CPU上独立运行</li>
  <li><strong>调度类多态</strong>：通过<code class="language-plaintext highlighter-rouge">task_tick</code>回调，不同调度策略（CFS、RT、DEADLINE）有不同的处理逻辑</li>
  <li><strong>不直接切换任务</strong>：这个函数只<strong>标记</strong>是否需要重新调度，真正的切换在中断返回时发生</li>
</ol>

<h3 id="23-cfs调度类的时钟处理">2.3 CFS调度类的时钟处理</h3>

<p>对于普通任务（<code class="language-plaintext highlighter-rouge">SCHED_OTHER</code>），调度器使用<strong>完全公平调度器（CFS）</strong>。在<a href="https://github.com/torvalds/linux/blob/master/kernel/sched/fair.c"><code class="language-plaintext highlighter-rouge">kernel/sched/fair.c</code></a>中<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">task_tick_fair</span><span class="p">(</span><span class="k">struct</span> <span class="n">rq</span> <span class="o">*</span><span class="n">rq</span><span class="p">,</span> <span class="k">struct</span> <span class="n">task_struct</span> <span class="o">*</span><span class="n">curr</span><span class="p">,</span> <span class="kt">int</span> <span class="n">queued</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">struct</span> <span class="n">cfs_rq</span> <span class="o">*</span><span class="n">cfs_rq</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">sched_entity</span> <span class="o">*</span><span class="n">se</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">curr</span><span class="o">-&gt;</span><span class="n">se</span><span class="p">;</span>
    
    <span class="n">for_each_sched_entity</span><span class="p">(</span><span class="n">se</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">cfs_rq</span> <span class="o">=</span> <span class="n">cfs_rq_of</span><span class="p">(</span><span class="n">se</span><span class="p">);</span>
        <span class="n">entity_tick</span><span class="p">(</span><span class="n">cfs_rq</span><span class="p">,</span> <span class="n">se</span><span class="p">,</span> <span class="n">queued</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="c1">// ... NUMA平衡等</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">entity_tick</span><span class="p">(</span><span class="k">struct</span> <span class="n">cfs_rq</span> <span class="o">*</span><span class="n">cfs_rq</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sched_entity</span> <span class="o">*</span><span class="n">curr</span><span class="p">,</span> <span class="kt">int</span> <span class="n">queued</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// 更新当前任务的虚拟运行时间</span>
    <span class="n">update_curr</span><span class="p">(</span><span class="n">cfs_rq</span><span class="p">);</span>
    
    <span class="c1">// 检查是否需要抢占</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">cfs_rq</span><span class="o">-&gt;</span><span class="n">nr_running</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">)</span>
        <span class="n">check_preempt_tick</span><span class="p">(</span><span class="n">cfs_rq</span><span class="p">,</span> <span class="n">curr</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>虚拟运行时间（vruntime）</strong> 是CFS的核心概念：</p>
<ul>
  <li>每个任务都有一个<code class="language-plaintext highlighter-rouge">vruntime</code>，表示它已经”使用”了多少CPU时间（按优先级加权）</li>
  <li>调度器总是选择<code class="language-plaintext highlighter-rouge">vruntime</code>最小的任务运行</li>
  <li><code class="language-plaintext highlighter-rouge">check_preempt_tick()</code>检查当前任务的<code class="language-plaintext highlighter-rouge">vruntime</code>是否明显大于队列中其他任务，如果是，则设置<code class="language-plaintext highlighter-rouge">TIF_NEED_RESCHED</code></li>
</ul>

<h2 id="三抢占机制何时真正切换任务">三、抢占机制：何时真正切换任务？</h2>

<h3 id="31-抢占标志位tif_need_resched">3.1 抢占标志位：<code class="language-plaintext highlighter-rouge">TIF_NEED_RESCHED</code></h3>

<p>设置这个标志只是”建议”内核应该切换任务，但何时真正切换取决于<strong>抢占模型</strong>：</p>

<pre><code class="language-mermaid">flowchart TD
    A[scheduler_tick 检测到需要切换] --&gt; B[设置 TIF_NEED_RESCHED]
    B --&gt; C{当前在哪里?}
    
    C --&gt;|用户态| D[立即抢占&lt;br/&gt;在中断返回时]
    C --&gt;|内核态| E{抢占模型?}
    
    E --&gt;|PREEMPT_NONE| F[等待系统调用返回&lt;br/&gt;或显式调度点]
    E --&gt;|PREEMPT_VOLUNTARY| G[等待 cond_resched&lt;br/&gt;检查点]
    E --&gt;|PREEMPT_FULL| H[几乎立即抢占&lt;br/&gt;除非持有自旋锁]
    
    style D fill:#90EE90
    style F fill:#FFD700
    style G fill:#FFB6C1
    style H fill:#FF6347
</code></pre>

<h3 id="32-中断返回路径实际的切换点">3.2 中断返回路径：实际的切换点</h3>

<p>在x86-64架构上，中断返回时的处理（<a href="https://github.com/torvalds/linux/blob/master/arch/x86/entry/entry_64.S"><code class="language-plaintext highlighter-rouge">arch/x86/entry/entry_64.S</code></a>）<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ENTRY(interrupt_return)
    // ... 保存寄存器等
    
    testl $_TIF_NEED_RESCHED, %edi  // 检查是否需要重新调度
    jz restore_regs_and_return       // 如果不需要,直接返回
    
    // 需要调度
    call schedule                     // 调用调度器
    
restore_regs_and_return:
    // ... 恢复寄存器并返回用户态
    iretq
</code></pre></div></div>

<p><strong>关键点</strong>：</p>
<ul>
  <li>如果返回<strong>用户态</strong>，总是会检查并响应<code class="language-plaintext highlighter-rouge">TIF_NEED_RESCHED</code></li>
  <li>如果返回<strong>内核态</strong>，取决于配置的抢占模型</li>
</ul>

<h2 id="四rtos-vs-通用linux调度哲学的根本差异">四、RTOS vs. 通用Linux：调度哲学的根本差异</h2>

<h3 id="41-实时操作系统的调度特点">4.1 实时操作系统的调度特点</h3>

<p>你的草稿中提到了一个关键区别：<strong>RTOS的核心不是时间片轮转，而是基于优先级的抢占</strong><sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>。</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">特征</th>
      <th style="text-align: left">Linux (CFS)</th>
      <th style="text-align: left">RTOS (如FreeRTOS)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>调度目标</strong></td>
      <td style="text-align: left">公平性：确保所有任务都能获得CPU时间</td>
      <td style="text-align: left">确定性：最高优先级任务必须最快响应</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>时间片</strong></td>
      <td style="text-align: left">动态计算的虚拟运行时间</td>
      <td style="text-align: left">相同优先级才使用时间片轮转</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>抢占延迟</strong></td>
      <td style="text-align: left">毫秒级（取决于抢占模型）</td>
      <td style="text-align: left">微秒级（优先级抢占几乎立即发生）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Tickless</strong></td>
      <td style="text-align: left">支持（省电）</td>
      <td style="text-align: left">部分RTOS支持，但优先保证实时性</td>
    </tr>
  </tbody>
</table>

<h3 id="42-preempt_rt将linux变成rtos">4.2 PREEMPT_RT：将Linux变成RTOS</h3>

<p>Linux的<strong>PREEMPT_RT补丁</strong><sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>通过以下改造，将通用内核变成硬实时系统：</p>

<p><strong>关键技术1：中断线程化</strong></p>

<pre><code class="language-mermaid">sequenceDiagram
    participant HW as 硬件中断
    participant Handler as 中断处理程序(顶半部)
    participant Thread as 内核中断线程
    participant Sched as 实时调度器
    participant RT as 高优先级RT任务
    
    Note over HW,RT: 标准Linux模式
    HW-&gt;&gt;Handler: 中断到来
    activate Handler
    Handler-&gt;&gt;Handler: 长时间处理（关闭抢占）
    deactivate Handler
    Note right of Handler: RT任务必须等待
    
    Note over HW,RT: PREEMPT_RT模式
    HW-&gt;&gt;Handler: 中断到来
    Handler-&gt;&gt;Thread: 唤醒中断线程（极快）
    Thread-&gt;&gt;Sched: 进入就绪队列
    Sched-&gt;&gt;Sched: 比较优先级
    alt RT任务优先级更高
        Sched-&gt;&gt;RT: 立即运行RT任务
    else 中断线程优先级更高
        Sched-&gt;&gt;Thread: 运行中断处理
    end
</code></pre>

<p><strong>关键技术2：自旋锁变互斥锁</strong></p>

<p>标准Linux中的<code class="language-plaintext highlighter-rouge">spinlock</code>在PREEMPT_RT下被替换为<code class="language-plaintext highlighter-rouge">rt_mutex</code>（支持优先级继承），避免了高优先级任务在自旋锁上空转的问题。</p>

<h2 id="五lazy抢占kernel-70的新权衡">五、Lazy抢占：Kernel 7.0的新权衡</h2>

<p>你的另一篇文章分析的<code class="language-plaintext highlighter-rouge">PREEMPT_LAZY</code><sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>正是这个权衡的最新演化：</p>

<p><strong>传统<code class="language-plaintext highlighter-rouge">PREEMPT_VOLUNTARY</code></strong>：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 内核代码中散布的检查点</span>
<span class="k">if</span> <span class="p">(</span><span class="n">need_resched</span><span class="p">())</span>  <span class="c1">// 检查 TIF_NEED_RESCHED</span>
    <span class="n">schedule</span><span class="p">();</span>       <span class="c1">// 立即让出CPU</span>
</code></pre></div></div>

<p><strong>新的<code class="language-plaintext highlighter-rouge">PREEMPT_LAZY</code></strong>：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 设置惰性标志</span>
<span class="n">set_tsk_need_resched_lazy</span><span class="p">(</span><span class="n">current</span><span class="p">);</span>

<span class="c1">// cond_resched() 不再检查惰性标志</span>
<span class="c1">// 只在时钟中断时升级为紧急标志</span>
<span class="k">if</span> <span class="p">(</span><span class="n">tick_happened</span> <span class="o">&amp;&amp;</span> <span class="n">test_lazy_flag</span><span class="p">())</span>
    <span class="n">set_tsk_need_resched</span><span class="p">(</span><span class="n">current</span><span class="p">);</span>  <span class="c1">// 升级为紧急抢占</span>
</code></pre></div></div>

<p><strong>设计哲学转变</strong>：</p>
<ul>
  <li><strong>旧模式</strong>：通过代码中的启发式检查点实现”礼貌让出”</li>
  <li><strong>新模式</strong>：将抢占决策集中到调度器的时钟中断中，简化内核但增加了抢占延迟</li>
</ul>

<p>这个改变导致PostgreSQL性能下降的原因，正是因为它破坏了数据库自旋锁对”临界区内不会被抢占”的隐含假设。</p>

<h2 id="六总结调度是一门平衡的艺术">六、总结：调度是一门平衡的艺术</h2>

<p>回到最初的问题：<strong>Linux内核是否核心依赖定时器中断来进行任务调度？</strong></p>

<p><strong>答案是分层的</strong>：</p>

<ol>
  <li><strong>理论上</strong>：不依赖。系统调用返回、主动让出、I/O阻塞等都可以触发调度。</li>
  <li><strong>实践上</strong>：依赖。定时器中断是保证公平性、防止任务饿死、更新调度统计的关键机制。</li>
  <li><strong>现代内核</strong>：可选。Tickless模式下，单任务运行时可以完全没有周期性中断。</li>
  <li><strong>实时系统</strong>：弱依赖。RTOS更依赖事件驱动的抢占，时钟中断仅用于时间片轮转。</li>
</ol>

<p><strong>关键技术实现</strong>：</p>
<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">scheduler_tick()</code></strong>：每个时钟中断调用，更新vruntime，检查是否需要抢占</li>
  <li><strong><code class="language-plaintext highlighter-rouge">TIF_NEED_RESCHED</code>标志</strong>：建议切换的信号，但何时响应取决于抢占模型</li>
  <li><strong>中断返回路径</strong>：实际任务切换的执行点</li>
  <li><strong>抢占模型</strong>：决定了内核态代码的可中断性</li>
</ul>

<p><strong>设计权衡</strong>：</p>
<ul>
  <li>频繁的时钟中断 → 更好的公平性和响应，但更高的开销</li>
  <li>Tickless → 省电和减少干扰，但需要更复杂的调度逻辑</li>
  <li>全抢占 → 低延迟，但吞吐量可能下降</li>
  <li>惰性抢占 → 简化内核，但需要应用层适配（如使用RSEQ）</li>
</ul>

<p>这些权衡没有”完美答案”，只有针对不同场景的”合适选择”。这也是为什么从通用服务器到硬实时系统，Linux提供了如此丰富的调度配置选项。</p>

<hr />

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>Linux内核文档，《Reducing OS jitter due to per-cpu kthreads》，详细说明了NO_HZ_FULL模式的设计和使用。参见：<a href="https://github.com/torvalds/linux/blob/master/Documentation/timers/no_hz.rst">Documentation/timers/no_hz.rst</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>Linux内核源码 <a href="https://github.com/torvalds/linux/blob/master/kernel/sched/core.c"><code class="language-plaintext highlighter-rouge">kernel/sched/core.c</code></a> — <code class="language-plaintext highlighter-rouge">scheduler_tick()</code>函数是时钟中断调用调度器的入口点，更新运行队列时钟并调用调度类的task_tick回调。 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p>Linux内核源码 <a href="https://github.com/torvalds/linux/blob/master/kernel/sched/fair.c"><code class="language-plaintext highlighter-rouge">kernel/sched/fair.c</code></a> — CFS调度器的实现，包括<code class="language-plaintext highlighter-rouge">task_tick_fair()</code>和虚拟运行时间的更新逻辑。 <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p>Linux内核源码 <a href="https://github.com/torvalds/linux/blob/master/arch/x86/entry/entry_64.S"><code class="language-plaintext highlighter-rouge">arch/x86/entry/entry_64.S</code></a> — x86-64架构的中断返回路径，包括<code class="language-plaintext highlighter-rouge">TIF_NEED_RESCHED</code>标志检查和调度调用。 <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5">
      <p>FreeRTOS文档，《The FreeRTOS Kernel》，说明了基于优先级的抢占式调度机制。参见：<a href="https://www.freertos.org/implementation/a00008.html">https://www.freertos.org/implementation/a00008.html</a> <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6">
      <p>Linux PREEMPT_RT项目，《Real-Time Linux Wiki》，详细介绍了实时补丁的实现原理，包括中断线程化和优先级继承互斥锁。参见：<a href="https://wiki.linuxfoundation.org/realtime/start">https://wiki.linuxfoundation.org/realtime/start</a> <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7">
      <p>Peter Zijlstra，Linux内核提交 <a href="https://github.com/torvalds/linux/commit/7c70cb94d29c"><code class="language-plaintext highlighter-rouge">7c70cb94d29c</code></a> — <em>sched: Add Lazy preemption model</em>。引入了惰性抢占标志<code class="language-plaintext highlighter-rouge">TIF_NEED_RESCHED_LAZY</code>，改变了传统的抢占检查机制。链接：<a href="https://lkml.kernel.org/r/20241007075055.331243614@infradead.org">https://lkml.kernel.org/r/20241007075055.331243614@infradead.org</a> <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="linux-kernel" /><summary type="html"><![CDATA[从定时器中断与调度时钟心跳出发，拆解 Linux 内核抢占式调度如何在响应性与开销之间做权衡。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://weinan.tech/images/og/linux-kernel.png" /><media:content medium="image" url="https://weinan.tech/images/og/linux-kernel.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">IDT 与 SYSCALL：差异、演化、Linux 实现与性能</title><link href="https://weinan.tech/2026/03/30/x86-64-syscall-idt-linux-kernel-sdm.html" rel="alternate" type="text/html" title="IDT 与 SYSCALL：差异、演化、Linux 实现与性能" /><published>2026-03-30T00:00:00+08:00</published><updated>2026-03-30T00:00:00+08:00</updated><id>https://weinan.tech/2026/03/30/x86-64-syscall-idt-linux-kernel-sdm</id><content type="html" xml:base="https://weinan.tech/2026/03/30/x86-64-syscall-idt-linux-kernel-sdm.html"><![CDATA[<style>
/* Mermaid diagram container */
.mermaid-container {
  position: relative;
  display: block;
  cursor: pointer;
  transition: opacity 0.2s;
  max-width: 100%;
  margin: 20px 0;
  overflow-x: auto;
}

.mermaid-container:hover {
  opacity: 0.8;
}

.mermaid-container svg {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Modal overlay */
.mermaid-modal {
  display: none;
  position: fixed;
  z-index: 9999;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9);
  animation: fadeIn 0.3s;
}

.mermaid-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Modal content */
.mermaid-modal-content {
  position: relative;
  width: 90vw;
  height: 90vh;
  overflow: hidden;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
}

.mermaid-modal-diagram {
  transform-origin: center center;
  transition: transform 0.2s ease;
  display: inline-block;
  min-width: 100%;
  cursor: grab;
  user-select: none;
}

.mermaid-modal-diagram.dragging {
  cursor: grabbing;
  transition: none;
}

.mermaid-modal-diagram svg {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;
}

/* Control buttons */
.mermaid-controls {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
  z-index: 10000;
}

.mermaid-btn {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
  color: #333;
  font-weight: 500;
}

.mermaid-btn:hover {
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Close button */
.mermaid-close {
  background: #f44336;
  color: white;
  border: none;
}

.mermaid-close:hover {
  background: #d32f2f;
}

/* Zoom indicator */
.mermaid-zoom-level {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 10000;
}
</style>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  mermaid.initialize({
    startOnLoad: false,
    theme: 'default',
    securityLevel: 'loose',
    htmlLabels: true,
    themeVariables: {
      fontSize: '14px'
    }
  });

  let currentZoom = 1;
  let currentModal = null;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let translateX = 0;
  let translateY = 0;

  // Create modal HTML
  function createModal() {
    const modal = document.createElement('div');
    modal.className = 'mermaid-modal';
    modal.innerHTML = `
      <div class="mermaid-controls">
        <button class="mermaid-btn zoom-in">放大 +</button>
        <button class="mermaid-btn zoom-out">缩小 -</button>
        <button class="mermaid-btn zoom-reset">重置</button>
        <button class="mermaid-btn mermaid-close">关闭 ✕</button>
      </div>
      <div class="mermaid-modal-content">
        <div class="mermaid-modal-diagram"></div>
      </div>
      <div class="mermaid-zoom-level">100%</div>
    `;
    document.body.appendChild(modal);
    return modal;
  }

  // Show modal with diagram
  function showModal(diagramContent) {
    if (!currentModal) {
      currentModal = createModal();
      setupModalEvents();
    }

    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.innerHTML = diagramContent;

    // Remove any width/height attributes from SVG to make it responsive
    const svg = modalDiagram.querySelector('svg');
    if (svg) {
      svg.removeAttribute('width');
      svg.removeAttribute('height');
      svg.style.width = '100%';
      svg.style.height = 'auto';
    }

    // Setup drag functionality
    setupDrag(modalDiagram);

    currentModal.classList.add('active');
    currentZoom = 1;
    resetPosition();
    updateZoom();
    document.body.style.overflow = 'hidden';
  }

  // Hide modal
  function hideModal() {
    if (currentModal) {
      currentModal.classList.remove('active');
      document.body.style.overflow = '';
    }
  }

  // Update zoom level
  function updateZoom() {
    if (!currentModal) return;
    const diagram = currentModal.querySelector('.mermaid-modal-diagram');
    const zoomLevel = currentModal.querySelector('.mermaid-zoom-level');
    diagram.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentZoom})`;
    zoomLevel.textContent = `${Math.round(currentZoom * 100)}%`;
  }

  // Reset position when zoom changes
  function resetPosition() {
    translateX = 0;
    translateY = 0;
  }

  // Setup drag functionality
  function setupDrag(element) {
    element.addEventListener('mousedown', startDrag);
    element.addEventListener('touchstart', startDrag);
  }

  function startDrag(e) {
    if (e.type === 'mousedown' && e.button !== 0) return; // Only left click

    isDragging = true;
    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.classList.add('dragging');

    if (e.type === 'touchstart') {
      startX = e.touches[0].clientX - translateX;
      startY = e.touches[0].clientY - translateY;
    } else {
      startX = e.clientX - translateX;
      startY = e.clientY - translateY;
    }

    document.addEventListener('mousemove', drag);
    document.addEventListener('touchmove', drag);
    document.addEventListener('mouseup', stopDrag);
    document.addEventListener('touchend', stopDrag);
  }

  function drag(e) {
    if (!isDragging) return;
    e.preventDefault();

    if (e.type === 'touchmove') {
      translateX = e.touches[0].clientX - startX;
      translateY = e.touches[0].clientY - startY;
    } else {
      translateX = e.clientX - startX;
      translateY = e.clientY - startY;
    }

    updateZoom();
  }

  function stopDrag() {
    isDragging = false;
    const modalDiagram = currentModal?.querySelector('.mermaid-modal-diagram');
    if (modalDiagram) {
      modalDiagram.classList.remove('dragging');
    }
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('touchmove', drag);
    document.removeEventListener('mouseup', stopDrag);
    document.removeEventListener('touchend', stopDrag);
  }

  // Setup modal event listeners
  function setupModalEvents() {
    if (!currentModal) return;

    // Close button
    currentModal.querySelector('.mermaid-close').addEventListener('click', hideModal);

    // Zoom buttons
    currentModal.querySelector('.zoom-in').addEventListener('click', () => {
      currentZoom = Math.min(currentZoom + 0.25, 3);
      updateZoom();
    });

    currentModal.querySelector('.zoom-out').addEventListener('click', () => {
      currentZoom = Math.max(currentZoom - 0.25, 0.5);
      updateZoom();
    });

    currentModal.querySelector('.zoom-reset').addEventListener('click', () => {
      currentZoom = 1;
      resetPosition();
      updateZoom();
    });

    // Close on background click
    currentModal.addEventListener('click', (e) => {
      if (e.target === currentModal) {
        hideModal();
      }
    });

    // Close on ESC key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && currentModal.classList.contains('active')) {
        hideModal();
      }
    });
  }

  // Convert Jekyll-rendered code blocks to mermaid divs
  document.addEventListener('DOMContentLoaded', async function() {
    const codeBlocks = document.querySelectorAll('code.language-mermaid');

    for (const codeBlock of codeBlocks) {
      const pre = codeBlock.parentElement;
      const container = document.createElement('div');
      container.className = 'mermaid-container';

      const mermaidDiv = document.createElement('div');
      mermaidDiv.className = 'mermaid';
      mermaidDiv.textContent = codeBlock.textContent;

      container.appendChild(mermaidDiv);
      pre.replaceWith(container);
    }

    // Render all mermaid diagrams
    try {
      await mermaid.run({
        querySelector: '.mermaid'
      });
      console.log('Mermaid diagrams rendered successfully');
    } catch (error) {
      console.error('Mermaid rendering error:', error);
    }

    // Add click handlers to rendered diagrams
    document.querySelectorAll('.mermaid-container').forEach((container, index) => {
      // Find the rendered SVG inside the container
      const svg = container.querySelector('svg');
      if (!svg) {
        console.warn(`No SVG found in container ${index}`);
        return;
      }

      // Make the container clickable
      container.style.cursor = 'pointer';
      container.title = '点击查看大图';

      container.addEventListener('click', function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Clone the SVG for the modal
        const svgClone = svg.cloneNode(true);
        const tempDiv = document.createElement('div');
        tempDiv.appendChild(svgClone);

        console.log('Opening modal for diagram', index);
        showModal(tempDiv.innerHTML);
      });

      console.log(`Click handler added to diagram ${index}`);
    });
  });
</script>

<p>全文分三部分：</p>

<ul>
  <li><strong>IDT 与 <code class="language-plaintext highlighter-rouge">SYSCALL</code> 的机制差异与历史脉络</strong></li>
  <li><strong>x86-64 Linux 上从 <code class="language-plaintext highlighter-rouge">syscall</code> 指令到内核服务的执行路径</strong>（对照 SDM 与 <code class="language-plaintext highlighter-rouge">arch/x86</code>）</li>
  <li><strong>经 IDT 的入核与 <code class="language-plaintext highlighter-rouge">SYSCALL</code> 入核在开销与实现上的对比</strong></li>
</ul>

<p>硬件叙述以 Intel <em>Software Developer’s Manual</em>（Volume 3A 等）为准，软件以 Linux 主线 <code class="language-plaintext highlighter-rouge">arch/x86</code> 为准；引用标号见文末 <strong>References</strong>。</p>

<hr />

<h2 id="主题一idt-与-syscall-的区别与演化">主题一：IDT 与 <code class="language-plaintext highlighter-rouge">SYSCALL</code> 的区别与演化</h2>

<h3 id="谁在决定内核入口">谁在决定内核入口</h3>

<ul>
  <li><strong>异常、硬件中断、<code class="language-plaintext highlighter-rouge">INT n</code></strong>：CPU 用 <strong>IDT（Interrupt Descriptor Table）</strong> 按 <strong>向量号</strong> 取门描述符，再按架构规则完成特权级与栈等处理；OS 负责 <strong>填表</strong> 并用 <strong><code class="language-plaintext highlighter-rouge">LIDT</code></strong> 之类加载 <strong>IDTR</strong>。该路径与一组 <strong>MSR</strong> 配合编程的 <strong><code class="language-plaintext highlighter-rouge">SYSCALL</code> 入核</strong>是两套并存机制<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup><sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">SYSCALL</code>（64 位长模式下的系统调用主路径之一）</strong>：CPU 根据 <strong><code class="language-plaintext highlighter-rouge">IA32_STAR</code>、<code class="language-plaintext highlighter-rouge">IA32_LSTAR</code>、<code class="language-plaintext highlighter-rouge">IA32_FMASK</code></strong> 等 <strong>MSR</strong> 切到 ring 0 并跳转到 <strong><code class="language-plaintext highlighter-rouge">IA32_LSTAR</code> 指向的 RIP</strong>，<strong>不查 IDT</strong><sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup><sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>。</li>
</ul>

<p>二者都是架构规定的入口协议，但针对的事件类别不同：前者服务 <strong>异步/异常类事件</strong> 的统一交付，后者服务 <strong>用户态主动发起的系统调用</strong> 的专用快速通道。</p>

<h3 id="64-位模式下的-idt-索引">64 位模式下的 IDT 索引</h3>

<p>在 <strong>64-bit / IA-32e</strong> 下，门描述符为 <strong>16 字节</strong>；向量 <em>k</em> 对应表项在 IDT 中的字节偏移为 <strong>k × 16</strong>（与 legacy 模式下 8 字节项不同）<sup id="fnref:1:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>。</p>

<p>手册在 64-bit mode IDT gate 处写道<sup id="fnref:11"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>：</p>

<blockquote>
  <p>In 64-bit mode, the IDT index is formed by scaling the interrupt vector by 16. The first eight bytes (bytes 7:0) of a 64-bit mode interrupt gate are similar but not identical to legacy 32-bit interrupt gates. The type field (bits 11:8 in bytes 7:4) is described in Table 3-2. The Interrupt Stack Table (IST) field (bits 4:0 in bytes 7:4) is used by the stack switching mechanisms described in Section 6.14.5, “Interrupt Stack Table.” Bytes 11:8 hold the upper 32 bits of the target RIP (interrupt segment offset) in canonical form.</p>
</blockquote>

<h3 id="对照表">对照表</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">特性</th>
      <th style="text-align: left">经 IDT 的路径</th>
      <th style="text-align: left"><code class="language-plaintext highlighter-rouge">SYSCALL</code> 路径</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">典型触发</td>
      <td style="text-align: left">硬件中断、CPU 异常、<code class="language-plaintext highlighter-rouge">INT n</code>（含历史上的 <code class="language-plaintext highlighter-rouge">int 0x80</code>）</td>
      <td style="text-align: left">用户态执行 <strong><code class="language-plaintext highlighter-rouge">syscall</code></strong></td>
    </tr>
    <tr>
      <td style="text-align: left">入口定位</td>
      <td style="text-align: left">CPU 按向量查 <strong>IDT 门</strong></td>
      <td style="text-align: left">CPU 读 <strong><code class="language-plaintext highlighter-rouge">IA32_LSTAR</code> 等 MSR</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">门/MSR 语义</td>
      <td style="text-align: left">类型、DPL、IST、段选择子等 <strong>由 CPU 解释</strong></td>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">STAR</code>/<code class="language-plaintext highlighter-rouge">LSTAR</code>/<code class="language-plaintext highlighter-rouge">FMASK</code> 组合</strong>，由 OS 预编程</td>
    </tr>
    <tr>
      <td style="text-align: left">是否使用 IDT</td>
      <td style="text-align: left">是</td>
      <td style="text-align: left"><strong>否</strong>（本条目不讨论 FRED 等后续扩展）</td>
    </tr>
  </tbody>
</table>

<h3 id="与系统调用号--内核函数的关系">与「系统调用号 → 内核函数」的关系</h3>

<p>抽象上都可说成 <strong>编号映射到处理逻辑</strong>：IDT 用 <strong>中断向量</strong>，系统调用用 <strong><code class="language-plaintext highlighter-rouge">RAX</code> 中的调用号</strong>。<br />
<strong>差别在于</strong>：IDT 的查表与跳转是 <strong>CPU 事件交付的一部分</strong>；而 <strong><code class="language-plaintext highlighter-rouge">RAX → __x64_sys_*</code></strong> 属于 <strong>内核在进入 <code class="language-plaintext highlighter-rouge">do_syscall_64</code> 之后的纯软件分发</strong>，处理器并不解析“系统调用号”的语义。</p>

<h4 id="三条不同的表--入口--快车道">三条不同的「表 / 入口 / 快车道」</h4>

<p>将机制分为以下三层（可与 <strong>上文「对照表」</strong>、<strong>下文「机制层对比」</strong> 对照阅读）：</p>

<ul>
  <li>
    <p><strong>IDT（及经其投递的中断/异常/<code class="language-plaintext highlighter-rouge">INT n</code>）</strong>
 由 CPU 规定、面向<strong>全体异步与异常事件</strong>的 <strong>通用交付协议</strong>：功能全、约束多，不以“最短一次用户主动系统调用”为唯一优化目标<sup id="fnref:1:2"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup><sup id="fnref:2:1"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>。</p>
  </li>
  <li>
    <p><strong>系统调用分发（软件）</strong>
 Linux 仍保留 <strong><code class="language-plaintext highlighter-rouge">sys_call_table[]</code></strong>，方便 <strong>trace</strong> 等子系统解析符号地址；<strong>64 位主路径</strong>上则由 <strong><code class="language-plaintext highlighter-rouge">x64_sys_call()</code> 的 <code class="language-plaintext highlighter-rouge">switch (nr)</code></strong> 落到 <strong><code class="language-plaintext highlighter-rouge">__x64_sys_*</code></strong>。无论数组还是 <strong><code class="language-plaintext highlighter-rouge">switch</code></strong>，都属于 <strong><code class="language-plaintext highlighter-rouge">syscall</code> 已经进核之后</strong> 的普通控制流，<strong>不是 CPU 替代的 IDT 查表</strong><sup id="fnref:10"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>。</p>
  </li>
  <li>
    <p><strong>系统调用硬件快车道（<code class="language-plaintext highlighter-rouge">SYSCALL</code> + 若干 MSR）</strong>
 <strong>入口 <code class="language-plaintext highlighter-rouge">RIP</code> 与 <code class="language-plaintext highlighter-rouge">CS</code>/<code class="language-plaintext highlighter-rouge">SS</code>/<code class="language-plaintext highlighter-rouge">RFLAGS</code> 掩码</strong>由 <strong><code class="language-plaintext highlighter-rouge">STAR</code>/<code class="language-plaintext highlighter-rouge">LSTAR</code>/<code class="language-plaintext highlighter-rouge">FMASK</code>（及 <code class="language-plaintext highlighter-rouge">EFER.SCE</code>）</strong> 预编程；这是在 <strong>不进 IDT</strong> 的前提下完成的 <strong><code class="language-plaintext highlighter-rouge">ring 3 → ring 0</code> 专用序列</strong><sup id="fnref:3:1"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup><sup id="fnref:11:1"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>。<strong><code class="language-plaintext highlighter-rouge">__x64_sys_*</code> 分发</strong>在这一硬件入核序列完成之后，才由 <strong><code class="language-plaintext highlighter-rouge">do_syscall_64</code> / <code class="language-plaintext highlighter-rouge">x64_sys_call</code></strong> 等以 <strong>普通内核控制流</strong>执行<sup id="fnref:10:1"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>。</p>
  </li>
</ul>

<h3 id="一条简化的演化脉络x86--linux-相关">一条简化的演化脉络（x86 / Linux 相关）</h3>

<ul>
  <li><strong>80386 及保护模式</strong>：<strong>IDT</strong> 与 <strong><code class="language-plaintext highlighter-rouge">INT n</code></strong> 成为统一的异常/中断/软中断交付入口；内核通过设置向量 <em>n</em> 的门，把控制流交给对应处理例程。</li>
  <li><strong>32 位 Linux</strong>：用户态系统调用长期使用 <strong><code class="language-plaintext highlighter-rouge">int 0x80</code></strong>，即 <strong>CPU 查 IDT 向量 0x80</strong> 进入内核（仍属 IDT 路径）<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>。</li>
  <li><strong>约 Pentium II / Pro 一代</strong>：Intel 引入 <strong><code class="language-plaintext highlighter-rouge">SYSENTER</code>/<code class="language-plaintext highlighter-rouge">SYSEXIT</code></strong>，配合 <strong>MSR</strong> 提供另一条 <strong>不经 IDT 门描述符的</strong> 快速进核通道（Linux 在 <strong>32 位兼容路径</strong>等场景仍会碰到与 <strong><code class="language-plaintext highlighter-rouge">SYSENTER</code>/<code class="language-plaintext highlighter-rouge">SYSCALL</code></strong> 相关的入口约定）<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>。</li>
  <li><strong>x86-64（AMD64 / Intel 64）</strong>：架构在 <strong>长模式</strong>下提供 <strong><code class="language-plaintext highlighter-rouge">SYSCALL</code>/<code class="language-plaintext highlighter-rouge">SYSRET</code></strong>（由 <strong><code class="language-plaintext highlighter-rouge">IA32_EFER.SCE</code></strong> 等控制使能，细节以 SDM 为准）。<strong>64 位 Linux 用户态</strong>通常通过 <strong>glibc 等内联 <code class="language-plaintext highlighter-rouge">syscall</code></strong>，内核入口落在 <strong><code class="language-plaintext highlighter-rouge">entry_SYSCALL_64</code></strong><sup id="fnref:3:2"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup><sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>。</li>
  <li><strong>并存</strong>：今日 64 位内核仍可能为 <strong>32 位进程</strong> 保留 <strong><code class="language-plaintext highlighter-rouge">int 0x80</code> / <code class="language-plaintext highlighter-rouge">SYSENTER</code> / 兼容入口</strong>（向量与实现见内核头文件与 <code class="language-plaintext highlighter-rouge">entry_64_compat</code> 等）；<strong>本文明细以 64 位 <code class="language-plaintext highlighter-rouge">syscall</code> 主线为主</strong>。</li>
</ul>

<hr />

<h2 id="主题二x86-64-linux-上-syscall-从-cpu-到内核的完整机制">主题二：x86-64 Linux 上 <code class="language-plaintext highlighter-rouge">syscall</code> 从 CPU 到内核的完整机制</h2>

<h3 id="三层结构总览">三层结构（总览）</h3>

<ul>
  <li><strong>CPU（SDM）</strong>：用户态约定 <strong><code class="language-plaintext highlighter-rouge">RAX</code>=调用号</strong>、参数寄存器后执行 <strong><code class="language-plaintext highlighter-rouge">syscall</code></strong>。硬件将 <strong><code class="language-plaintext highlighter-rouge">RIP → RCX</code>、<code class="language-plaintext highlighter-rouge">RFLAGS → R11</code></strong>，按 <strong>MSR</strong> 加载 <strong><code class="language-plaintext highlighter-rouge">CS</code>/<code class="language-plaintext highlighter-rouge">SS</code>/<code class="language-plaintext highlighter-rouge">RIP</code></strong>，并令 <strong><code class="language-plaintext highlighter-rouge">RFLAGS &lt;- RFLAGS &amp; ~IA32_FMASK</code></strong>；<strong>不保存 <code class="language-plaintext highlighter-rouge">RSP</code></strong>、不向栈压帧。</li>
  <li><strong>内核入口 <code class="language-plaintext highlighter-rouge">entry_SYSCALL_64</code></strong>（<code class="language-plaintext highlighter-rouge">arch/x86/entry/entry_64.S</code>）：<strong><code class="language-plaintext highlighter-rouge">swapgs</code></strong>、切换到 <strong>per-CPU 内核栈</strong>，在栈上构造 <strong><code class="language-plaintext highlighter-rouge">struct pt_regs</code></strong>，再 <strong><code class="language-plaintext highlighter-rouge">call do_syscall_64</code></strong>。</li>
  <li><strong>分发与返回</strong>：<strong><code class="language-plaintext highlighter-rouge">do_syscall_64</code></strong> → <strong><code class="language-plaintext highlighter-rouge">x64_sys_call</code></strong> 的 <strong><code class="language-plaintext highlighter-rouge">switch (nr)</code></strong> → 各 <strong><code class="language-plaintext highlighter-rouge">__x64_sys_*</code></strong>。返回时若满足契约则 <strong><code class="language-plaintext highlighter-rouge">SYSRET</code></strong>，否则 <strong><code class="language-plaintext highlighter-rouge">IRET</code></strong>。</li>
</ul>

<p>对比 <strong>IDT 路径</strong>：<strong>IDT</strong> 处理「向量 → 硬件按门交付」；<strong><code class="language-plaintext highlighter-rouge">syscall</code></strong> 处理「寄存器约定 + <strong>MSR</strong> 指定 <strong><code class="language-plaintext highlighter-rouge">RIP</code></strong> → <strong>软件</strong>补全栈帧再交付」。</p>

<h3 id="syscall-与-msr多寄存器协同而非单一-lstar"><code class="language-plaintext highlighter-rouge">SYSCALL</code> 与 MSR：多寄存器协同，而非单一 <code class="language-plaintext highlighter-rouge">LSTAR</code></h3>

<p><strong>MSR（Model Specific Register）</strong> 指通过 <strong><code class="language-plaintext highlighter-rouge">RDMSR</code>/<code class="language-plaintext highlighter-rouge">WRMSR</code></strong> 访问的 <strong>按编号独立编址</strong> 的一类寄存器；体系结构里与 <code class="language-plaintext highlighter-rouge">SYSCALL</code> 相关的常量名 <strong><code class="language-plaintext highlighter-rouge">IA32_STAR</code>、<code class="language-plaintext highlighter-rouge">IA32_LSTAR</code>、<code class="language-plaintext highlighter-rouge">IA32_FMASK</code></strong> 等各自对应不同 MSR 地址与语义。长模式下执行 <strong><code class="language-plaintext highlighter-rouge">SYSCALL</code></strong> 时，处理器按 <strong><code class="language-plaintext highlighter-rouge">IA32_EFER.SCE</code></strong> 判定该机制是否可用，再从 <strong><code class="language-plaintext highlighter-rouge">STAR</code>/<code class="language-plaintext highlighter-rouge">LSTAR</code>/<code class="language-plaintext highlighter-rouge">FMASK</code></strong> 读出 CS/SS、目标 RIP 与 RFLAGS 掩码<sup id="fnref:3:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup><sup id="fnref:11:2"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>。</p>

<p>SDM 在 <strong><code class="language-plaintext highlighter-rouge">STAR</code>/<code class="language-plaintext highlighter-rouge">LSTAR</code>/<code class="language-plaintext highlighter-rouge">FMASK</code> 布局</strong>处写明<sup id="fnref:11:3"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>：</p>

<blockquote>
  <p>See Figure 5-14 for the layout of IA32_STAR, IA32_LSTAR and IA32_FMASK.</p>
</blockquote>

<p>并在同一节给出 <strong><code class="language-plaintext highlighter-rouge">RIP</code> 取自 <code class="language-plaintext highlighter-rouge">IA32_LSTAR</code>、<code class="language-plaintext highlighter-rouge">RFLAGS</code> 与 <code class="language-plaintext highlighter-rouge">IA32_FMASK</code> 的组合关系</strong>（正文 <strong>「CPU 侧（与 Vol.3A §5.8.8 等一致）」</strong> 一节另有逐句引文）。</p>

<p>Linux 在 <strong>64 位内核引导路径</strong>中与上述分工对齐：<strong><code class="language-plaintext highlighter-rouge">syscall_init()</code></strong> 写 <strong><code class="language-plaintext highlighter-rouge">MSR_STAR</code></strong>（用户/内核段选择子约定），再调用 <strong><code class="language-plaintext highlighter-rouge">idt_syscall_init()</code></strong> 写 <strong><code class="language-plaintext highlighter-rouge">MSR_LSTAR</code></strong>（<code class="language-plaintext highlighter-rouge">entry_SYSCALL_64</code>）与 <strong><code class="language-plaintext highlighter-rouge">MSR_SYSCALL_MASK</code></strong>（对应 <strong><code class="language-plaintext highlighter-rouge">IA32_FMASK</code></strong>）<sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">10</a></sup>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">syscall_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
	<span class="cm">/* The default user and kernel segments */</span>
	<span class="n">wrmsr</span><span class="p">(</span><span class="n">MSR_STAR</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="p">(</span><span class="n">__USER32_CS</span> <span class="o">&lt;&lt;</span> <span class="mi">16</span><span class="p">)</span> <span class="o">|</span> <span class="n">__KERNEL_CS</span><span class="p">);</span>

	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">cpu_feature_enabled</span><span class="p">(</span><span class="n">X86_FEATURE_FRED</span><span class="p">))</span>
		<span class="n">idt_syscall_init</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">idt_syscall_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">wrmsrq</span><span class="p">(</span><span class="n">MSR_LSTAR</span><span class="p">,</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">long</span><span class="p">)</span><span class="n">entry_SYSCALL_64</span><span class="p">);</span>
	<span class="cm">/* ia32_enabled() / SYSENTER_* / MSR_CSTAR 分支：见 common.c 全文 */</span>
	<span class="n">wrmsrq</span><span class="p">(</span><span class="n">MSR_SYSCALL_MASK</span><span class="p">,</span>
	       <span class="n">X86_EFLAGS_CF</span><span class="o">|</span><span class="n">X86_EFLAGS_PF</span><span class="o">|</span><span class="n">X86_EFLAGS_AF</span><span class="o">|</span>
	       <span class="n">X86_EFLAGS_ZF</span><span class="o">|</span><span class="n">X86_EFLAGS_SF</span><span class="o">|</span><span class="n">X86_EFLAGS_TF</span><span class="o">|</span>
	       <span class="n">X86_EFLAGS_IF</span><span class="o">|</span><span class="n">X86_EFLAGS_DF</span><span class="o">|</span><span class="n">X86_EFLAGS_OF</span><span class="o">|</span>
	       <span class="n">X86_EFLAGS_IOPL</span><span class="o">|</span><span class="n">X86_EFLAGS_NT</span><span class="o">|</span><span class="n">X86_EFLAGS_RF</span><span class="o">|</span>
	       <span class="n">X86_EFLAGS_AC</span><span class="o">|</span><span class="n">X86_EFLAGS_ID</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>内核里 <strong><code class="language-plaintext highlighter-rouge">MSR_SYSCALL_MASK</code></strong> 与手册 <strong><code class="language-plaintext highlighter-rouge">IA32_FMASK</code></strong> 对应同一类编程接口；<strong><code class="language-plaintext highlighter-rouge">idt_syscall_init()</code></strong> 在 <strong><code class="language-plaintext highlighter-rouge">MSR_LSTAR</code> 与兼容路径 MSRs</strong> 之间的分支仍以 <code class="language-plaintext highlighter-rouge">arch/x86/kernel/cpu/common.c</code> 为准，<strong>「内核源码摘录（与上表对应）」</strong> 一节给出与当前主线一致的更长摘录。</p>

<p>从机制上概括：<strong><code class="language-plaintext highlighter-rouge">IA32_LSTAR</code> 只给出 ring-0 入口 <code class="language-plaintext highlighter-rouge">RIP</code></strong>；<strong><code class="language-plaintext highlighter-rouge">IA32_STAR</code> 给出 <code class="language-plaintext highlighter-rouge">SYSCALL</code>/<code class="language-plaintext highlighter-rouge">SYSRET</code> 使用的 CS/SS 选择子场</strong>；<strong><code class="language-plaintext highlighter-rouge">IA32_FMASK</code> 规定 <code class="language-plaintext highlighter-rouge">RFLAGS</code> 在进入时被清除的位</strong>；<strong><code class="language-plaintext highlighter-rouge">IA32_EFER.SCE</code> 使能整条 <code class="language-plaintext highlighter-rouge">SYSCALL</code>/<code class="language-plaintext highlighter-rouge">SYSRET</code> 路径</strong><sup id="fnref:3:4"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup><sup id="fnref:11:4"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>。三颗 MSR 与总开关共同构成 SDM <strong>Figure 5-14</strong> 所描述的配置平面，操作系统需一并初始化，而不是仅写 <strong><code class="language-plaintext highlighter-rouge">LSTAR</code></strong> 一项。</p>

<h3 id="长模式专用syscall-与-sysret--三颗-msr-如何协同工作">长模式专用：<code class="language-plaintext highlighter-rouge">SYSCALL</code> 与 <code class="language-plaintext highlighter-rouge">SYSRET</code> —— 三颗 MSR 如何协同工作</h3>

<h4 id="核心概念三个-msr-各司其职">核心概念：三个 MSR 各司其职</h4>

<p>在 x86-64 长模式下，<code class="language-plaintext highlighter-rouge">syscall</code> 和 <code class="language-plaintext highlighter-rouge">sysret</code> 指令依赖三个 MSR（模型特定寄存器）来完成用户态到内核态、再回到用户态的完整流程。可以这样理解：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">MSR 寄存器</th>
      <th style="text-align: left">作用</th>
      <th style="text-align: left">类比</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>IA32_STAR</strong></td>
      <td style="text-align: left">告诉 CPU：进入内核时用什么段（CS/SS），返回用户时用什么段</td>
      <td style="text-align: left"><strong>门禁卡的双重配置</strong>——进去刷A区，出来刷B区</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IA32_LSTAR</strong></td>
      <td style="text-align: left">告诉 CPU：内核的入口函数地址在哪里</td>
      <td style="text-align: left"><strong>紧急出口的指向标</strong>——从这里进内核</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IA32_FMASK</strong></td>
      <td style="text-align: left">告诉 CPU：进入内核时，RFLAGS 寄存器里哪些位要强制清零</td>
      <td style="text-align: left"><strong>安检过滤器</strong>——某些标志位不能带进内核</td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p><strong>重要说明</strong>：本文只讨论 <strong>IA-32e 长模式</strong>下带 <code class="language-plaintext highlighter-rouge">REX.W</code> 的 <code class="language-plaintext highlighter-rouge">syscall</code>/<code class="language-plaintext highlighter-rouge">sysret</code> 指令，不涉及 <code class="language-plaintext highlighter-rouge">IA32_CSTAR</code> 和 <code class="language-plaintext highlighter-rouge">SYSENTER</code>/<code class="language-plaintext highlighter-rouge">SYSEXIT</code> 等其他机制。</p>
</blockquote>

<hr />

<h4 id="流程图一条系统调用的完整旅程">流程图：一条系统调用的完整旅程</h4>

<p>下面这个流程图展示了从<strong>用户态执行 <code class="language-plaintext highlighter-rouge">syscall</code></strong> 到<strong>内核处理</strong>再到<strong>返回用户态</strong>的完整过程。每个框里都注明了“此时谁在读/写哪个 MSR”。</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant OS as 操作系统(启动时)
    participant User as 用户态程序
    participant CPU as CPU硬件
    participant Kernel as 内核态代码

    Note over OS: 操作系统启动时，预先配置 MSR
    OS-&gt;&gt;CPU: IA32_EFER.SCE = 1 (开启 syscall 支持)
    OS-&gt;&gt;CPU: IA32_STAR = 入核/出核的 CS/SS 选择子
    OS-&gt;&gt;CPU: IA32_LSTAR = 内核入口地址
    OS-&gt;&gt;CPU: IA32_FMASK = RFLAGS 清零掩码

    Note over User: 用户态准备系统调用
    User-&gt;&gt;User: RAX = 系统调用号，参数存入 RDI/RSI/RDX/R10/R8/R9
    User-&gt;&gt;User: RSP 指向用户栈

    User-&gt;&gt;CPU: 执行 syscall 指令

    Note over CPU: syscall 指令的硬件自动行为
    CPU-&gt;&gt;CPU: RCX = 用户态下一条指令的 RIP
    CPU-&gt;&gt;CPU: R11 = 用户态完整 RFLAGS
    CPU-&gt;&gt;CPU: RIP = IA32_LSTAR (读 MSR)
    CPU-&gt;&gt;CPU: CS/SS = IA32_STAR 入核位域
    CPU-&gt;&gt;CPU: RFLAGS = RFLAGS &amp; (~IA32_FMASK) (按 FMASK 清零)
    
    Note over CPU: 特权级从 Ring 3 切换到 Ring 0
    CPU-&gt;&gt;Kernel: 跳转到 LSTAR 指向的内核入口

    Note over Kernel: 内核处理系统调用
    Kernel-&gt;&gt;Kernel: swapgs (切换到内核 GS)
    Kernel-&gt;&gt;Kernel: 手动切换 RSP 到内核栈
    Kernel-&gt;&gt;Kernel: 保存完整寄存器到内核栈 (形成 pt_regs)
    Kernel-&gt;&gt;Kernel: 根据 RAX 查 sys_call_table 分发
    Kernel-&gt;&gt;Kernel: 执行具体内核函数，返回值写入 RAX
    Kernel-&gt;&gt;Kernel: 恢复寄存器，准备返回

    Kernel-&gt;&gt;CPU: 执行 sysretq 指令

    Note over CPU: sysret 指令的硬件自动行为
    CPU-&gt;&gt;CPU: CS/SS = IA32_STAR 出核位域
    CPU-&gt;&gt;CPU: RIP = RCX (恢复用户态返回地址)
    CPU-&gt;&gt;CPU: RFLAGS = R11 (恢复用户态标志位)

    Note over CPU: 特权级从 Ring 0 切换回 Ring 3
    CPU-&gt;&gt;User: 跳转到用户态返回地址

    Note over User: 继续执行，RAX 中为系统调用返回值
</code></pre>

<hr />

<h4 id="关键要点避免踩坑">关键要点（避免踩坑）</h4>

<h5 id="syscall-不会自动切换-rsp"><code class="language-plaintext highlighter-rouge">syscall</code> 不会自动切换 RSP</h5>
<ul>
  <li>用户栈指针（RSP）<strong>不会</strong>被 <code class="language-plaintext highlighter-rouge">syscall</code> 指令改变。</li>
  <li>内核必须在入口代码中<strong>手动切换</strong>到内核栈（通常用 <code class="language-plaintext highlighter-rouge">swapgs</code> + 写 <code class="language-plaintext highlighter-rouge">rsp</code>）。</li>
  <li>这意味着：<strong>RSP 的保存和恢复是软件的责任</strong>，硬件不管。</li>
</ul>

<h5 id="sysret-的契约"><code class="language-plaintext highlighter-rouge">sysret</code> 的「契约」</h5>
<ul>
  <li><code class="language-plaintext highlighter-rouge">sysret</code> 指令假设：
    <ul>
      <li><strong>RCX</strong> 中存放着用户态的返回地址（由 <code class="language-plaintext highlighter-rouge">syscall</code> 自动保存）。</li>
      <li><strong>R11</strong> 中存放着用户态的 RFLAGS（由 <code class="language-plaintext highlighter-rouge">syscall</code> 自动保存）。</li>
    </ul>
  </li>
  <li><strong>如果内核代码不小心破坏了 RCX 或 R11，就不能再用 <code class="language-plaintext highlighter-rouge">sysret</code> 返回</strong>，必须改用 <code class="language-plaintext highlighter-rouge">iret</code> 路径。</li>
</ul>

<h5 id="返回值约定">返回值约定</h5>
<ul>
  <li>系统调用的返回值<strong>必须放在 RAX</strong> 中。</li>
  <li>这是用户态和内核态的约定，<code class="language-plaintext highlighter-rouge">sysret</code> 不会动 RAX。</li>
</ul>

<hr />

<h4 id="与-int-0x80--idt-路径的对比可选扩展">与 <code class="language-plaintext highlighter-rouge">int 0x80</code> + IDT 路径的对比（可选扩展）</h4>

<p>如果你想理解为什么这套机制比 <code class="language-plaintext highlighter-rouge">int 0x80</code> 快，可以这样对比：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">动作</th>
      <th style="text-align: left"><code class="language-plaintext highlighter-rouge">int 0x80</code>（老方法）</th>
      <th style="text-align: left"><code class="language-plaintext highlighter-rouge">syscall</code>（新方法）</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">保存返回地址</td>
      <td style="text-align: left">压栈（内存访问）</td>
      <td style="text-align: left">存 RCX（寄存器）</td>
    </tr>
    <tr>
      <td style="text-align: left">保存 RFLAGS</td>
      <td style="text-align: left">压栈（内存访问）</td>
      <td style="text-align: left">存 R11（寄存器）</td>
    </tr>
    <tr>
      <td style="text-align: left">查找入口</td>
      <td style="text-align: left">查内存中的 IDT 表</td>
      <td style="text-align: left">读 MSR 寄存器（CPU 内部）</td>
    </tr>
    <tr>
      <td style="text-align: left">切换栈</td>
      <td style="text-align: left">硬件自动切（TSS 机制）</td>
      <td style="text-align: left">软件手动切（更灵活）</td>
    </tr>
    <tr>
      <td style="text-align: left">保存段寄存器</td>
      <td style="text-align: left">硬件自动保存 5 个</td>
      <td style="text-align: left">根本不保存（因为用不上）</td>
    </tr>
    <tr>
      <td style="text-align: left">返回指令</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">iret</code>（重量级）</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">sysret</code>（轻量级）</td>
    </tr>
  </tbody>
</table>

<p><strong>核心结论</strong>：<code class="language-plaintext highlighter-rouge">syscall</code> 快，不是因为它“做的事少”，而是因为它“用寄存器代替了内存”，并且“去掉了历史包袱”。</p>

<p>在 <code class="language-plaintext highlighter-rouge">syscall</code>/<code class="language-plaintext highlighter-rouge">sysret</code> 机制中，最核心的 MSR 寄存器是以下<strong>三个</strong>：</p>

<hr />

<h4 id="核心三颗-msr">核心三颗 MSR</h4>

<table>
  <thead>
    <tr>
      <th style="text-align: left">MSR 名称</th>
      <th style="text-align: left">地址</th>
      <th style="text-align: left">作用</th>
      <th style="text-align: left">读/写时机</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>IA32_STAR</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xC0000081</code></td>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">syscall</code>/<code class="language-plaintext highlighter-rouge">sysret</code> 各自的 CS、SS 怎么取</strong>由 <strong>Figure 5-14</strong> 规定的<strong>不同位域</strong>决定（<strong><code class="language-plaintext highlighter-rouge">syscall</code></strong> 用入核场、<strong><code class="language-plaintext highlighter-rouge">sysret</code>（长模式）</strong>用出核场；<strong>不是</strong>「高 32 位=内核段、低 32 位=用户段」这种对半分）</td>
      <td style="text-align: left">操作系统启动时写入一次</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IA32_LSTAR</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xC0000082</code></td>
      <td style="text-align: left">存储<strong>内核入口地址</strong>：<br /><code class="language-plaintext highlighter-rouge">syscall</code> 指令执行后 RIP 跳转的目标</td>
      <td style="text-align: left">操作系统启动时写入一次</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>IA32_FMASK</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xC0000084</code></td>
      <td style="text-align: left">存储<strong>RFLAGS 掩码</strong>：<br />进入内核时，RFLAGS 中对应位被强制清零</td>
      <td style="text-align: left">操作系统启动时写入一次</td>
    </tr>
  </tbody>
</table>

<hr />

<h4 id="辅助-msr">辅助 MSR</h4>

<p>还有一个<strong>前提条件</strong>相关的 MSR：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">MSR 名称</th>
      <th style="text-align: left">地址</th>
      <th style="text-align: left">作用</th>
      <th style="text-align: left">说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>IA32_EFER</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xC0000080</code></td>
      <td style="text-align: left">第 0 位（SCE 位）必须为 1</td>
      <td style="text-align: left">否则 <code class="language-plaintext highlighter-rouge">syscall</code> 指令会触发 <code class="language-plaintext highlighter-rouge">#UD</code> 异常</td>
    </tr>
  </tbody>
</table>

<hr />

<h4 id="一句话总结">一句话总结</h4>

<blockquote>
  <p><strong><code class="language-plaintext highlighter-rouge">IA32_STAR</code> 管“段”（权限），<code class="language-plaintext highlighter-rouge">IA32_LSTAR</code> 管“地址”（去哪），<code class="language-plaintext highlighter-rouge">IA32_FMASK</code> 管“标志位”（环境），三颗 MSR 配合 <code class="language-plaintext highlighter-rouge">IA32_EFER.SCE</code> 开关，共同决定了 <code class="language-plaintext highlighter-rouge">syscall</code> 的完整行为。</strong></p>
</blockquote>

<h3 id="端到端序列示意">端到端序列（示意）</h3>

<pre><code class="language-mermaid">sequenceDiagram
    participant User as 用户态进程
    participant CPU as CPU硬件
    participant Kernel as Linux内核
    User-&gt;&gt;User: RAX=nr，RDI/RSI/RDX/R10/R8/R9 为 arg0–arg5
    User-&gt;&gt;CPU: 执行 syscall
    CPU-&gt;&gt;CPU: RCX←返回点 RIP，R11←RFLAGS
    CPU-&gt;&gt;CPU: RIP←IA32_LSTAR；RFLAGS 按 IA32_FMASK 清零若干位
    CPU-&gt;&gt;Kernel: 进入 entry_SYSCALL_64
    Kernel-&gt;&gt;Kernel: swapgs，切内核栈，推 pt_regs
    Kernel-&gt;&gt;Kernel: do_syscall_64，x64_sys_call 按 nr 分发
    Kernel-&gt;&gt;Kernel: 写回 RAX 返回值或负 errno
    Kernel-&gt;&gt;Kernel: 可 SYSRET 则 SYSRET，否则 IRET
    CPU-&gt;&gt;User: 回到用户态，自 RCX 所指指令继续
</code></pre>

<h4 id="与上图步骤对应的内核代码linuxarchx86">与上图步骤对应的内核代码（<code class="language-plaintext highlighter-rouge">linux/arch/x86</code>）</h4>

<p>序列图里最前段由用户态约定（glibc / vDSO 等内联 <strong><code class="language-plaintext highlighter-rouge">syscall</code></strong>，见 <strong>man <code class="language-plaintext highlighter-rouge">syscall(2)</code></strong><sup id="fnref:7:1"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>）；其后为 CPU 根据 <strong><code class="language-plaintext highlighter-rouge">IA32_LSTAR</code>/<code class="language-plaintext highlighter-rouge">IA32_FMASK</code>/<code class="language-plaintext highlighter-rouge">IA32_STAR</code></strong> 的行为，内核侧在启动时写 MSR（<strong><code class="language-plaintext highlighter-rouge">idt_syscall_init()</code></strong> 等，见 <strong>「内核源码摘录（与上表对应）」</strong> 与 <sup id="fnref:8:1"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">10</a></sup>）。自 <strong><code class="language-plaintext highlighter-rouge">entry_SYSCALL_64</code> 起</strong> 按下述代码块列举，惯例与 <strong><code class="language-plaintext highlighter-rouge">/Users/weli/works/bootimage-example/LINUX_X86_64_ENTRY_AND_PT_REGS.md</code></strong> 一致：围栏第一行为 <strong><code class="language-plaintext highlighter-rouge">起始行:结束行:arch/…/文件</code></strong>（相对 <strong><code class="language-plaintext highlighter-rouge">linux/</code></strong> 源码树根；本文行号依 <strong><code class="language-plaintext highlighter-rouge">/Users/weli/works/linux</code></strong>）。</p>

<p><strong><code class="language-plaintext highlighter-rouge">entry_SYSCALL_64</code>（<code class="language-plaintext highlighter-rouge">arch/x86/entry/entry_64.S</code>）</strong> — <strong><code class="language-plaintext highlighter-rouge">IA32_LSTAR</code></strong> 指向此处：<strong><code class="language-plaintext highlighter-rouge">swapgs</code></strong>、装入 <strong><code class="language-plaintext highlighter-rouge">cpu_current_top_of_stack</code></strong>、<strong><code class="language-plaintext highlighter-rouge">pt_regs</code></strong> 布局压栈、<strong><code class="language-plaintext highlighter-rouge">PUSH_AND_CLEAR_REGS</code></strong>、<strong><code class="language-plaintext highlighter-rouge">movq %rsp,%rdi</code></strong> / <strong><code class="language-plaintext highlighter-rouge">movslq %eax,%rsi</code></strong>、<strong><code class="language-plaintext highlighter-rouge">call do_syscall_64</code></strong>。</p>

<pre><code class="language-87:121:arch/x86/entry/entry_64.S">SYM_CODE_START(entry_SYSCALL_64)
	UNWIND_HINT_ENTRY
	ENDBR

	swapgs
	/* tss.sp2 is scratch space. */
	movq	%rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
	SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp
	movq	PER_CPU_VAR(cpu_current_top_of_stack), %rsp

SYM_INNER_LABEL(entry_SYSCALL_64_safe_stack, SYM_L_GLOBAL)
	ANNOTATE_NOENDBR

	/* Construct struct pt_regs on stack */
	pushq	$__USER_DS				/* pt_regs-&gt;ss */
	pushq	PER_CPU_VAR(cpu_tss_rw + TSS_sp2)	/* pt_regs-&gt;sp */
	pushq	%r11					/* pt_regs-&gt;flags */
	pushq	$__USER_CS				/* pt_regs-&gt;cs */
	pushq	%rcx					/* pt_regs-&gt;ip */
SYM_INNER_LABEL(entry_SYSCALL_64_after_hwframe, SYM_L_GLOBAL)
	pushq	%rax					/* pt_regs-&gt;orig_ax */

	PUSH_AND_CLEAR_REGS rax=$-ENOSYS

	/* IRQs are off. */
	movq	%rsp, %rdi
	/* Sign extend the lower 32bit as syscall numbers are treated as int */
	movslq	%eax, %rsi

	/* clobbers %rax, make sure it is after saving the syscall nr */
	IBRS_ENTER
	UNTRAIN_RET
	CLEAR_BRANCH_HISTORY

	call	do_syscall_64		/* returns with IRQs disabled */
</code></pre>

<p><strong><code class="language-plaintext highlighter-rouge">do_syscall_64</code>（前半）、<code class="language-plaintext highlighter-rouge">do_syscall_x64</code>、<code class="language-plaintext highlighter-rouge">x64_sys_call</code>（<code class="language-plaintext highlighter-rouge">arch/x86/entry/syscall_64.c</code>）</strong> — 与上引 <strong>112–114</strong> 行入参一致；合法系统调用号下 <strong><code class="language-plaintext highlighter-rouge">regs-&gt;ax</code></strong> 在 <strong><code class="language-plaintext highlighter-rouge">do_syscall_x64</code> → <code class="language-plaintext highlighter-rouge">x64_sys_call</code></strong> 链上更新。</p>

<pre><code class="language-87:100:arch/x86/entry/syscall_64.c">__visible noinstr bool do_syscall_64(struct pt_regs *regs, int nr)
{
	add_random_kstack_offset();
	nr = syscall_enter_from_user_mode(regs, nr);

	instrumentation_begin();

	if (!do_syscall_x64(regs, nr) &amp;&amp; !do_syscall_x32(regs, nr) &amp;&amp; nr != -1) {
		/* Invalid system call, but still a system call. */
		regs-&gt;ax = __x64_sys_ni_syscall(regs);
	}

	instrumentation_end();
	syscall_exit_to_user_mode(regs);
</code></pre>

<pre><code class="language-53:67:arch/x86/entry/syscall_64.c">static __always_inline bool do_syscall_x64(struct pt_regs *regs, int nr)
{
	/*
	 * Convert negative numbers to very high and thus out of range
	 * numbers for comparisons.
	 */
	unsigned int unr = nr;

	if (likely(unr &lt; NR_syscalls)) {
		unr = array_index_nospec(unr, NR_syscalls);
		regs-&gt;ax = x64_sys_call(regs, unr);
		return true;
	}
	return false;
}
</code></pre>

<pre><code class="language-34:41:arch/x86/entry/syscall_64.c">#define __SYSCALL(nr, sym) case nr: return __x64_##sym(regs);
long x64_sys_call(const struct pt_regs *regs, unsigned int nr)
{
	switch (nr) {
	#include &lt;asm/syscalls_64.h&gt;
	default: return __x64_sys_ni_syscall(regs);
	}
}
</code></pre>

<p><strong><code class="language-plaintext highlighter-rouge">__x64_sys_*</code> 原型、<code class="language-plaintext highlighter-rouge">sys_call_table[]</code>、生成 <code class="language-plaintext highlighter-rouge">syscalls_64.h</code></strong> — 各 <strong><code class="language-plaintext highlighter-rouge">__x64_sys_*</code></strong> 实现分布在 <strong><code class="language-plaintext highlighter-rouge">kernel/</code></strong>、<strong><code class="language-plaintext highlighter-rouge">fs/</code></strong> 等；编号表 <strong><code class="language-plaintext highlighter-rouge">arch/x86/entry/syscalls/syscall_64.tbl</code></strong>，<strong>Kbuild</strong> 生成 <strong><code class="language-plaintext highlighter-rouge">arch/x86/include/generated/asm/syscalls_64.h</code></strong>（<strong><code class="language-plaintext highlighter-rouge">$(out)</code></strong> 见下）。</p>

<pre><code class="language-12:14:arch/x86/entry/syscall_64.c">#define __SYSCALL(nr, sym) extern long __x64_##sym(const struct pt_regs *);
#define __SYSCALL_NORETURN(nr, sym) extern long __noreturn __x64_##sym(const struct pt_regs *);
#include &lt;asm/syscalls_64.h&gt;
</code></pre>

<pre><code class="language-28:31:arch/x86/entry/syscall_64.c">#define __SYSCALL(nr, sym) __x64_##sym,
const sys_call_ptr_t sys_call_table[] = {
#include &lt;asm/syscalls_64.h&gt;
};
</code></pre>

<pre><code class="language-1:3:arch/x86/entry/syscalls/Makefile"># SPDX-License-Identifier: GPL-2.0
out := arch/$(SRCARCH)/include/generated/asm
uapi := arch/$(SRCARCH)/include/generated/uapi/asm
</code></pre>

<pre><code class="language-8:9:arch/x86/entry/syscalls/Makefile">syscall32 := $(src)/syscall_32.tbl
syscall64 := $(src)/syscall_64.tbl
</code></pre>

<pre><code class="language-53:55:arch/x86/entry/syscalls/Makefile">$(out)/syscalls_64.h: abis := common,64
$(out)/syscalls_64.h: $(syscall64) $(systbl) FORCE
	$(call if_changed,systbl)
</code></pre>

<p><strong><code class="language-plaintext highlighter-rouge">SYSRET</code> 快路径与 <code class="language-plaintext highlighter-rouge">IRET</code> 慢路径</strong> — <strong><code class="language-plaintext highlighter-rouge">do_syscall_64</code></strong> 末尾 <strong><code class="language-plaintext highlighter-rouge">return true</code></strong> 且 <strong><code class="language-plaintext highlighter-rouge">entry_SYSCALL_64</code></strong> 中 <strong><code class="language-plaintext highlighter-rouge">testb %al,%al</code></strong> 成功则 <strong><code class="language-plaintext highlighter-rouge">sysretq</code></strong>；否则 <strong><code class="language-plaintext highlighter-rouge">jmp</code> / <code class="language-plaintext highlighter-rouge">jz</code></strong> 汇入 <strong><code class="language-plaintext highlighter-rouge">swapgs_restore_regs_and_return_to_usermode</code></strong> 后经 <strong><code class="language-plaintext highlighter-rouge">iretq</code></strong>。</p>

<pre><code class="language-102:140:arch/x86/entry/syscall_64.c">	/*
	 * Check that the register state is valid for using SYSRET to exit
	 * to userspace.  Otherwise use the slower but fully capable IRET
	 * exit path.
	 */

	/* XEN PV guests always use the IRET path */
	if (cpu_feature_enabled(X86_FEATURE_XENPV))
		return false;

	/* SYSRET requires RCX == RIP and R11 == EFLAGS */
	if (unlikely(regs-&gt;cx != regs-&gt;ip || regs-&gt;r11 != regs-&gt;flags))
		return false;

	/* CS and SS must match the values set in MSR_STAR */
	if (unlikely(regs-&gt;cs != __USER_CS || regs-&gt;ss != __USER_DS))
		return false;

	if (unlikely(regs-&gt;ip &gt;= TASK_SIZE_MAX))
		return false;

	if (unlikely(regs-&gt;flags &amp; (X86_EFLAGS_RF | X86_EFLAGS_TF)))
		return false;

	/* Use SYSRET to exit to userspace */
	return true;
</code></pre>

<pre><code class="language-123:166:arch/x86/entry/entry_64.S">	/*
	 * Try to use SYSRET instead of IRET if we're returning to
	 * a completely clean 64-bit userspace context.  If we're not,
	 * go to the slow exit path.
	 * In the Xen PV case we must use iret anyway.
	 */

	ALTERNATIVE "testb %al, %al; jz swapgs_restore_regs_and_return_to_usermode", \
		"jmp swapgs_restore_regs_and_return_to_usermode", X86_FEATURE_XENPV

	/*
	 * We win! This label is here just for ease of understanding
	 * perf profiles. Nothing jumps here.
	 */
syscall_return_via_sysret:
	IBRS_EXIT
	POP_REGS pop_rdi=0

	/*
	 * Now all regs are restored except RSP and RDI.
	 * Save old stack pointer and switch to trampoline stack.
	 */
	movq	%rsp, %rdi
	movq	PER_CPU_VAR(cpu_tss_rw + TSS_sp0), %rsp
	UNWIND_HINT_END_OF_STACK

	pushq	RSP-RDI(%rdi)	/* RSP */
	pushq	(%rdi)		/* RDI */

	/*
	 * We are on the trampoline stack.  All regs except RDI are live.
	 * We can do future final exit work right here.
	 */
	STACKLEAK_ERASE_NOCLOBBER

	SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi

	popq	%rdi
	popq	%rsp
SYM_INNER_LABEL(entry_SYSRETQ_unsafe_stack, SYM_L_GLOBAL)
	ANNOTATE_NOENDBR
	swapgs
	CLEAR_CPU_BUFFERS
	sysretq
</code></pre>

<pre><code class="language-559:580:arch/x86/entry/entry_64.S">SYM_CODE_START_LOCAL(common_interrupt_return)
SYM_INNER_LABEL(swapgs_restore_regs_and_return_to_usermode, SYM_L_GLOBAL)
	IBRS_EXIT
#ifdef CONFIG_XEN_PV
	ALTERNATIVE "", "jmp xenpv_restore_regs_and_return_to_usermode", X86_FEATURE_XENPV
#endif
#ifdef CONFIG_MITIGATION_PAGE_TABLE_ISOLATION
	ALTERNATIVE "", "jmp .Lpti_restore_regs_and_return_to_usermode", X86_FEATURE_PTI
#endif

	STACKLEAK_ERASE
	POP_REGS
	add	$8, %rsp	/* orig_ax */
	UNWIND_HINT_IRET_REGS

.Lswapgs_and_iret:
	swapgs
	CLEAR_CPU_BUFFERS
	/* Assert that the IRET frame indicates user mode. */
	testb	$3, 8(%rsp)
	jnz	.Lnative_iret
	ud2
</code></pre>

<pre><code class="language-640:659:arch/x86/entry/entry_64.S">.Lnative_iret:
	UNWIND_HINT_IRET_REGS
	/*
	 * Are we returning to a stack segment from the LDT?  Note: in
	 * 64-bit mode SS:RSP on the exception stack is always valid.
	 */
#ifdef CONFIG_X86_ESPFIX64
	testb	$4, (SS-RIP)(%rsp)
	jnz	native_irq_return_ldt
#endif

SYM_INNER_LABEL(native_irq_return_iret, SYM_L_GLOBAL)
	ANNOTATE_NOENDBR // exc_double_fault
	/*
	 * This may fault.  Non-paranoid faults on return to userspace are
	 * handled by fixup_bad_iret.  These include #SS, #GP, and #NP.
	 * Double-faults due to espfix64 are handled in exc_double_fault.
	 * Other faults here are fatal.
	 */
	iretq
</code></pre>

<p>从 <strong><code class="language-plaintext highlighter-rouge">entry_SYSCALL_64</code></strong> 经 <strong><code class="language-plaintext highlighter-rouge">ALTERNATIVE</code></strong> 失败分支也会落到 <strong><code class="language-plaintext highlighter-rouge">swapgs_restore_regs_and_return_to_usermode</code></strong>，最终 <strong><code class="language-plaintext highlighter-rouge">iretq</code></strong>（上引 <strong>559–580</strong>、<strong>640–659</strong> 行；完整标签关系见 <sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">11</a></sup>）。</p>

<p>本地树路径：<strong><code class="language-plaintext highlighter-rouge">/Users/weli/works/linux</code></strong>（与主线 <code class="language-plaintext highlighter-rouge">torvalds/linux</code> 同源时行号一致；若你本地的 fork 有差异，以 <strong><code class="language-plaintext highlighter-rouge">git blame</code></strong> / 实际文件为准。）</p>

<h3 id="cpu-侧与-vol3a-588-等一致">CPU 侧（与 Vol.3A §5.8.8 等一致）</h3>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">RIP</code>（下一条指令）→ <code class="language-plaintext highlighter-rouge">RCX</code></strong>；<strong><code class="language-plaintext highlighter-rouge">RFLAGS</code> → <code class="language-plaintext highlighter-rouge">R11</code></strong><sup id="fnref:3:5"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">RIP</code></strong> 来自 <strong><code class="language-plaintext highlighter-rouge">IA32_LSTAR</code></strong>；<strong><code class="language-plaintext highlighter-rouge">CS</code>/<code class="language-plaintext highlighter-rouge">SS</code></strong> 的选择子与 <strong><code class="language-plaintext highlighter-rouge">IA32_STAR</code></strong> 的位域布局按 SDM Figure 5-14<sup id="fnref:3:6"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">RFLAGS &lt;- RFLAGS &amp; ~IA32_FMASK</code></strong>。Linux 在 <strong><code class="language-plaintext highlighter-rouge">arch/x86/kernel/cpu/common.c</code></strong> 的 <strong><code class="language-plaintext highlighter-rouge">idt_syscall_init()</code></strong> 中向 <strong><code class="language-plaintext highlighter-rouge">MSR_SYSCALL_MASK</code></strong> 写入含 <strong><code class="language-plaintext highlighter-rouge">X86_EFLAGS_IF</code></strong> 等位，使进入内核后 <strong><code class="language-plaintext highlighter-rouge">IF</code> 通常被清除</strong><sup id="fnref:3:7"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup><sup id="fnref:8:2"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">10</a></sup>。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">SYSCALL</code> 不改变 <code class="language-plaintext highlighter-rouge">RSP</code></strong>；<strong><code class="language-plaintext highlighter-rouge">SYSRET</code> 也不恢复 <code class="language-plaintext highlighter-rouge">RSP</code></strong>，栈由内核显式管理<sup id="fnref:3:8"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup><sup id="fnref:4:1"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>。</li>
</ol>

<p>同一节（§5.8.8）对 <code class="language-plaintext highlighter-rouge">SYSCALL</code>/<code class="language-plaintext highlighter-rouge">SYSRET</code> 的英文原文可对照如下<sup id="fnref:11:5"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>：</p>

<blockquote>
  <p>For SYSCALL, the processor saves RFLAGS into R11 and the RIP of the next instruction into RCX; it then gets the privilege-level 0 target code segment, instruction pointer, stack segment, and flags as follows:</p>

  <p>Target instruction pointer — Reads a 64-bit address from IA32_LSTAR. (The WRMSR instruction ensures that the value of the IA32_LSTAR MSR is canonical.)<br />
Flags — The processor sets RFLAGS to the logical-AND of its current value with the complement of the value in the IA32_FMASK MSR.</p>
</blockquote>

<blockquote>
  <p>The SYSCALL instruction does not save the stack pointer, and the SYSRET instruction does not restore it. It is likely that the OS system-call handler will change the stack pointer from the user stack to the OS stack. If so, it is the responsibility of software first to save the user stack pointer.</p>
</blockquote>

<p>（手册在「gets the … as follows」之后对 <strong>Target code segment</strong>、<strong>Stack segment</strong> 等另有逐条说明，此处摘入与 <strong><code class="language-plaintext highlighter-rouge">LSTAR</code>/<code class="language-plaintext highlighter-rouge">FMASK</code></strong> 及 <strong>RSP</strong> 最直接相关的句子；完整列举见 <sup id="fnref:1:3"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> 中 <strong>§5.8.8</strong> 与 <strong>Figure 5-14</strong>。）</p>

<h3 id="linux-侧源码锚点">Linux 侧（源码锚点）</h3>

<table>
  <thead>
    <tr>
      <th>内容</th>
      <th>文件与要点</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">STAR</code>/<code class="language-plaintext highlighter-rouge">LSTAR</code>/<code class="language-plaintext highlighter-rouge">SYSCALL_MASK</code> 初始化</strong></td>
      <td><code class="language-plaintext highlighter-rouge">arch/x86/kernel/cpu/common.c</code>：<code class="language-plaintext highlighter-rouge">syscall_init()</code>、<code class="language-plaintext highlighter-rouge">idt_syscall_init()</code></td>
    </tr>
    <tr>
      <td><strong>入口汇编</strong></td>
      <td><code class="language-plaintext highlighter-rouge">arch/x86/entry/entry_64.S</code>：<code class="language-plaintext highlighter-rouge">entry_SYSCALL_64</code>（<code class="language-plaintext highlighter-rouge">swapgs</code>、<code class="language-plaintext highlighter-rouge">pt_regs</code>、<code class="language-plaintext highlighter-rouge">do_syscall_64</code>、若可则 <code class="language-plaintext highlighter-rouge">sysretq</code>）</td>
    </tr>
    <tr>
      <td><strong>C 分发与 <code class="language-plaintext highlighter-rouge">SYSRET</code>/<code class="language-plaintext highlighter-rouge">IRET</code> 判定</strong></td>
      <td><code class="language-plaintext highlighter-rouge">arch/x86/entry/syscall_64.c</code>：<code class="language-plaintext highlighter-rouge">do_syscall_64</code>、<code class="language-plaintext highlighter-rouge">x64_sys_call</code>；<strong><code class="language-plaintext highlighter-rouge">sys_call_table[]</code></strong> 仍存在于镜像中，<strong>主路径分发</strong>为 <strong><code class="language-plaintext highlighter-rouge">switch</code></strong></td>
    </tr>
  </tbody>
</table>

<h3 id="内核源码摘录与上表对应">内核源码摘录（与上表对应）</h3>

<p>下列片段与主线 Linux 树一致，便于和 SDM 对照阅读<sup id="fnref:8:3"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">10</a></sup><sup id="fnref:9:1"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">11</a></sup><sup id="fnref:10:2"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>。</p>

<p><code class="language-plaintext highlighter-rouge">arch/x86/kernel/cpu/common.c</code> — <code class="language-plaintext highlighter-rouge">idt_syscall_init()</code> 中写入 <strong><code class="language-plaintext highlighter-rouge">MSR_LSTAR</code></strong> 与 <strong><code class="language-plaintext highlighter-rouge">MSR_SYSCALL_MASK</code></strong>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">idt_syscall_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">wrmsrq</span><span class="p">(</span><span class="n">MSR_LSTAR</span><span class="p">,</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">long</span><span class="p">)</span><span class="n">entry_SYSCALL_64</span><span class="p">);</span>
	<span class="cm">/* ... IA32_SYSENTER_* and ia32_enabled() branches omitted ... */</span>
	<span class="cm">/*
	 * Flags to clear on syscall; clear as much as possible
	 * to minimize user space-kernel interference.
	 */</span>
	<span class="n">wrmsrq</span><span class="p">(</span><span class="n">MSR_SYSCALL_MASK</span><span class="p">,</span>
	       <span class="n">X86_EFLAGS_CF</span><span class="o">|</span><span class="n">X86_EFLAGS_PF</span><span class="o">|</span><span class="n">X86_EFLAGS_AF</span><span class="o">|</span>
	       <span class="n">X86_EFLAGS_ZF</span><span class="o">|</span><span class="n">X86_EFLAGS_SF</span><span class="o">|</span><span class="n">X86_EFLAGS_TF</span><span class="o">|</span>
	       <span class="n">X86_EFLAGS_IF</span><span class="o">|</span><span class="n">X86_EFLAGS_DF</span><span class="o">|</span><span class="n">X86_EFLAGS_OF</span><span class="o">|</span>
	       <span class="n">X86_EFLAGS_IOPL</span><span class="o">|</span><span class="n">X86_EFLAGS_NT</span><span class="o">|</span><span class="n">X86_EFLAGS_RF</span><span class="o">|</span>
	       <span class="n">X86_EFLAGS_AC</span><span class="o">|</span><span class="n">X86_EFLAGS_ID</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">arch/x86/entry/entry_64.S</code> — <code class="language-plaintext highlighter-rouge">entry_SYSCALL_64</code> 入口（硬件不压栈后，由这里构造 <strong><code class="language-plaintext highlighter-rouge">pt_regs</code></strong> 并调用 <strong><code class="language-plaintext highlighter-rouge">do_syscall_64</code></strong>）：</p>

<pre><code class="language-asm">SYM_CODE_START(entry_SYSCALL_64)
	swapgs
	movq	%rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
	SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp
	movq	PER_CPU_VAR(cpu_current_top_of_stack), %rsp
	/* Construct struct pt_regs on stack */
	pushq	$__USER_DS				/* pt_regs-&gt;ss */
	pushq	PER_CPU_VAR(cpu_tss_rw + TSS_sp2)	/* pt_regs-&gt;sp */
	pushq	%r11					/* pt_regs-&gt;flags */
	pushq	$__USER_CS				/* pt_regs-&gt;cs */
	pushq	%rcx					/* pt_regs-&gt;ip */
	pushq	%rax					/* pt_regs-&gt;orig_ax */
	PUSH_AND_CLEAR_REGS rax=$-ENOSYS
	movq	%rsp, %rdi
	movslq	%eax, %esi
	call	do_syscall_64		/* returns with IRQs disabled */
</code></pre>

<p><code class="language-plaintext highlighter-rouge">arch/x86/entry/syscall_64.c</code> — <strong><code class="language-plaintext highlighter-rouge">sys_call_table[]</code> 注释</strong>与 <strong><code class="language-plaintext highlighter-rouge">x64_sys_call()</code></strong> 的 <strong><code class="language-plaintext highlighter-rouge">switch</code></strong> 分发：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
 * The sys_call_table[] is no longer used for system calls, but
 * kernel/trace/trace_syscalls.c still wants to know the system
 * call address.
 */</span>
<span class="cp">#define __SYSCALL(nr, sym) case nr: return __x64_##sym(regs);
</span><span class="kt">long</span> <span class="nf">x64_sys_call</span><span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">pt_regs</span> <span class="o">*</span><span class="n">regs</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">nr</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">switch</span> <span class="p">(</span><span class="n">nr</span><span class="p">)</span> <span class="p">{</span>
	<span class="cp">#include</span> <span class="cpf">&lt;asm/syscalls_64.h&gt;</span><span class="cp">
</span>	<span class="nl">default:</span> <span class="k">return</span> <span class="n">__x64_sys_ni_syscall</span><span class="p">(</span><span class="n">regs</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>同文件 <strong><code class="language-plaintext highlighter-rouge">do_syscall_64()</code></strong> — 前半dispatch、末尾返回值决定 <strong><code class="language-plaintext highlighter-rouge">SYSRET</code></strong> 与 <strong><code class="language-plaintext highlighter-rouge">IRET</code></strong>（以下与中版内核树连续片段一致，仅删去空白行以便排版）：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* Returns true to return using SYSRET, or false to use IRET */</span>
<span class="n">__visible</span> <span class="n">noinstr</span> <span class="n">bool</span> <span class="nf">do_syscall_64</span><span class="p">(</span><span class="k">struct</span> <span class="n">pt_regs</span> <span class="o">*</span><span class="n">regs</span><span class="p">,</span> <span class="kt">int</span> <span class="n">nr</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">add_random_kstack_offset</span><span class="p">();</span>
	<span class="n">nr</span> <span class="o">=</span> <span class="n">syscall_enter_from_user_mode</span><span class="p">(</span><span class="n">regs</span><span class="p">,</span> <span class="n">nr</span><span class="p">);</span>
	<span class="n">instrumentation_begin</span><span class="p">();</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">do_syscall_x64</span><span class="p">(</span><span class="n">regs</span><span class="p">,</span> <span class="n">nr</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">do_syscall_x32</span><span class="p">(</span><span class="n">regs</span><span class="p">,</span> <span class="n">nr</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">nr</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">regs</span><span class="o">-&gt;</span><span class="n">ax</span> <span class="o">=</span> <span class="n">__x64_sys_ni_syscall</span><span class="p">(</span><span class="n">regs</span><span class="p">);</span>
	<span class="p">}</span>
	<span class="n">instrumentation_end</span><span class="p">();</span>
	<span class="n">syscall_exit_to_user_mode</span><span class="p">(</span><span class="n">regs</span><span class="p">);</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">cpu_feature_enabled</span><span class="p">(</span><span class="n">X86_FEATURE_XENPV</span><span class="p">))</span>
		<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="n">regs</span><span class="o">-&gt;</span><span class="n">cx</span> <span class="o">!=</span> <span class="n">regs</span><span class="o">-&gt;</span><span class="n">ip</span> <span class="o">||</span> <span class="n">regs</span><span class="o">-&gt;</span><span class="n">r11</span> <span class="o">!=</span> <span class="n">regs</span><span class="o">-&gt;</span><span class="n">flags</span><span class="p">))</span>
		<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="n">regs</span><span class="o">-&gt;</span><span class="n">cs</span> <span class="o">!=</span> <span class="n">__USER_CS</span> <span class="o">||</span> <span class="n">regs</span><span class="o">-&gt;</span><span class="n">ss</span> <span class="o">!=</span> <span class="n">__USER_DS</span><span class="p">))</span>
		<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="n">regs</span><span class="o">-&gt;</span><span class="n">ip</span> <span class="o">&gt;=</span> <span class="n">TASK_SIZE_MAX</span><span class="p">))</span>
		<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="n">regs</span><span class="o">-&gt;</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="p">(</span><span class="n">X86_EFLAGS_RF</span> <span class="o">|</span> <span class="n">X86_EFLAGS_TF</span><span class="p">)))</span>
		<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
	<span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="主题三经-idt-的路径与-syscall-路径的性能与开销">主题三：经 IDT 的路径与 <code class="language-plaintext highlighter-rouge">SYSCALL</code> 路径的性能与开销</h2>

<p><strong><code class="language-plaintext highlighter-rouge">syscall</code> 相对 <code class="language-plaintext highlighter-rouge">int</code> + IDT 更快，主要不是因为“少查一次内存里的表”</strong>，而是因为 <strong><code class="language-plaintext highlighter-rouge">int</code> 走 IDT 门与异常/中断类交付</strong>，含 <strong>门与特权相关检查、中断帧布局</strong>，返回侧又常配合 <strong><code class="language-plaintext highlighter-rouge">IRET</code></strong>；<strong><code class="language-plaintext highlighter-rouge">SYSCALL</code>/<code class="language-plaintext highlighter-rouge">SYSRET</code></strong> 针对系统调用做了裁剪。内核里的 <strong>调用号分发</strong>发生在两条路径<strong>入核之后</strong>，不是整体差距的主因。</p>

<h3 id="路径对比示意">路径对比（示意）</h3>

<pre><code class="language-mermaid">graph TD
    subgraph 快路径_syscall
    A[用户态] --&gt;|syscall| B[CPU]
    B --&gt;|读 LSTAR/STAR/FMASK| C[内核入口 entry_SYSCALL_64]
    C --&gt;|do_syscall_64 + x64_sys_call| D[__x64_sys_*]
    end

    subgraph 传统路径_int0x80
    E[用户态] --&gt;|int 0x80| F[CPU]
    F --&gt;|经 IDT 向量门| G[中断门入口]
    G --&gt;|中断类交付与返回| H[内核处理]
    end
</code></pre>

<pre><code class="language-mermaid">graph TD
    subgraph 快路径_syscall
    A1[用户态] --&gt;|syscall| B1[CPU]
    B1 --&gt;|从 MSR 取入口| C1[内核入口]
    C1 --&gt;|软件分发| D1[__x64_sys_* 等]
    end

    subgraph 慢路径_int_idt
    E1[用户态] --&gt;|int 0x80| F1[CPU]
    F1 --&gt;|查 IDT| G1[IDT 门]
    G1 --&gt;|特权与栈检查 + 转入处理程序| H1[内核入口]
    H1 --&gt;|再做软件分发| I1[具体例程]
    end
</code></pre>

<h3 id="机制层对比">机制层对比</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">特性</th>
      <th style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">int 0x80</code> + IDT</strong></th>
      <th style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">syscall</code> + MSR</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">核心机制</td>
      <td style="text-align: left">软件中断，走 <strong>异常/中断类交付</strong></td>
      <td style="text-align: left"><strong>系统调用专用指令</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">入口</td>
      <td style="text-align: left">CPU <strong>按向量查 IDT 门</strong></td>
      <td style="text-align: left">CPU <strong>从 MSR 取目标 <code class="language-plaintext highlighter-rouge">RIP</code> 等</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">特权与门</td>
      <td style="text-align: left"><strong>DPL、门类型</strong> 等</td>
      <td style="text-align: left"><strong>不经同一套 IDT 门</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">硬件保存的现场</td>
      <td style="text-align: left"><strong>中断/异常帧</strong>（含段与标志等，因事件与模式而异）</td>
      <td style="text-align: left"><strong>主要为 <code class="language-plaintext highlighter-rouge">RCX</code>/<code class="language-plaintext highlighter-rouge">R11</code> 的返回契约</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">返回</td>
      <td style="text-align: left">常见 <strong><code class="language-plaintext highlighter-rouge">IRET</code></strong></td>
      <td style="text-align: left">条件满足时 <strong><code class="language-plaintext highlighter-rouge">SYSRET</code></strong>，否则 <strong><code class="language-plaintext highlighter-rouge">IRET</code></strong></td>
    </tr>
  </tbody>
</table>

<h3 id="单次查表与整条路径">单次查表与整条路径</h3>

<p><strong>硬件对 IDT 的一次访问</strong>与 <strong>内核对 <code class="language-plaintext highlighter-rouge">switch (nr)</code> 的几条指令</strong>各自都很快；差别主要来自 <strong>整条入核/出核</strong>：多保存了哪些状态、是否经过 <strong>IDT 门语义</strong>、返回是 <strong><code class="language-plaintext highlighter-rouge">IRET</code> 全功能</strong>还是 <strong><code class="language-plaintext highlighter-rouge">SYSRET</code> 窄契约</strong>、以及 Linux 在出口是否 <strong>回退到 <code class="language-plaintext highlighter-rouge">IRET</code></strong>。</p>

<h3 id="入核与出核int-0x80-与-syscall-的步骤对照">入核与出核：<code class="language-plaintext highlighter-rouge">int 0x80</code> 与 <code class="language-plaintext highlighter-rouge">syscall</code> 的步骤对照</h3>

<p>下表沿用在 <strong>IDT + <code class="language-plaintext highlighter-rouge">IRET</code></strong> 与 <strong><code class="language-plaintext highlighter-rouge">SYSCALL</code> + <code class="language-plaintext highlighter-rouge">SYSRET</code>（及 Linux 可能回退的 <code class="language-plaintext highlighter-rouge">IRET</code>）</strong> 之间做对照的常见写法；其中 <strong><code class="language-plaintext highlighter-rouge">int</code> 路径的栈帧</strong>以 <strong>64 位长模式</strong>下向内核栈压入的字段为准（<strong>SS、RSP、RFLAGS、CS、RIP</strong> 及可能的错误码等）<sup id="fnref:1:4"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>，与 legacy 保护模式下部分教材中的“多段寄存器”示意图并不完全同形。</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">动作</th>
      <th style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">int 0x80</code>（经 IDT，<code class="language-plaintext highlighter-rouge">IRET</code> 返回）</strong></th>
      <th style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">syscall</code>（<code class="language-plaintext highlighter-rouge">SYSRET</code> 快路径；条件不满足则 <code class="language-plaintext highlighter-rouge">IRET</code>）</strong></th>
      <th style="text-align: left">性能与实现上的含义</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>特权级切换</strong></td>
      <td style="text-align: left">Ring 3 → Ring 0</td>
      <td style="text-align: left">Ring 3 → Ring 0</td>
      <td style="text-align: left"><strong>两者都必须发生</strong>；不是时间差的主要来源。</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>栈切换</strong></td>
      <td style="text-align: left">与 <strong>TSS / IST</strong> 等绑定的 <strong>中断交付</strong> 语义下切到 <strong>内核栈</strong></td>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">swapgs</code></strong>，再由软件把 <strong><code class="language-plaintext highlighter-rouge">RSP</code></strong> 切到 <strong>per-CPU 内核栈顶</strong><sup id="fnref:9:2"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">11</a></sup></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">int</code> 走通用中断模型的硬件路径；<code class="language-plaintext highlighter-rouge">syscall</code> 由内核显式维护 <strong><code class="language-plaintext highlighter-rouge">RSP</code></strong>，与 <strong>“<code class="language-plaintext highlighter-rouge">SYSCALL</code> 不改 <code class="language-plaintext highlighter-rouge">RSP</code>”</strong> 的硬件契约一致<sup id="fnref:3:9"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>。</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>硬件自动保存</strong></td>
      <td style="text-align: left"><strong>向栈压中断帧</strong>（长模式典型含 <strong>SS、RSP、RFLAGS、CS、RIP</strong>；另视向量压错误码）<sup id="fnref:1:5"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup></td>
      <td style="text-align: left"><strong>不向栈压帧</strong>；仅用 <strong><code class="language-plaintext highlighter-rouge">RCX</code>/<code class="language-plaintext highlighter-rouge">R11</code></strong> 等约定配合 <strong>MSR</strong> 改变 <strong><code class="language-plaintext highlighter-rouge">RIP</code>/特权级/<code class="language-plaintext highlighter-rouge">RFLAGS</code> 掩码</strong><sup id="fnref:3:10"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">int</code> 在硬件一侧完成较多现场记录；<code class="language-plaintext highlighter-rouge">syscall</code> 把栈上工作留到 <strong><code class="language-plaintext highlighter-rouge">entry_SYSCALL_64</code></strong>。</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>软件补全现场</strong></td>
      <td style="text-align: left">入口例程继续保存其余寄存器、建 <strong><code class="language-plaintext highlighter-rouge">pt_regs</code></strong></td>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">PUSH_AND_CLEAR_REGS</code> 等</strong>补齐 <strong><code class="language-plaintext highlighter-rouge">pt_regs</code></strong><sup id="fnref:9:3"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">11</a></sup></td>
      <td style="text-align: left">进入 <strong>C 分发</strong>前，两条路径通常都要把通用寄存器镜像补全。</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>权限 / 门检查</strong></td>
      <td style="text-align: left"><strong>IDT 门</strong>的 <strong>DPL、类型</strong> 等与 <strong><code class="language-plaintext highlighter-rouge">INT n</code></strong> 相关的一致检查</td>
      <td style="text-align: left"><strong>不经</strong>与 <strong><code class="language-plaintext highlighter-rouge">int</code> 同一条</strong> 门描述符路径</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">int</code> 多一层 <strong>IDT 门禁</strong> 语义的固定成本。</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>返回时现场恢复</strong></td>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">IRET</code></strong> 从栈帧恢复 <strong>SS、RSP、RFLAGS、CS、RIP</strong> 等</td>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">SYSRET</code></strong>：<strong><code class="language-plaintext highlighter-rouge">RIP←RCX</code>、<code class="language-plaintext highlighter-rouge">RFLAGS←R11</code></strong>（窄）；否则走 <strong><code class="language-plaintext highlighter-rouge">IRET</code></strong><sup id="fnref:10:3"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">6</a></sup></td>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">IRET</code></strong> 通用、重；<strong><code class="language-plaintext highlighter-rouge">SYSRET</code></strong> 轻，但 Linux 在 <strong><code class="language-plaintext highlighter-rouge">do_syscall_64</code></strong> 中细查与 <strong><code class="language-plaintext highlighter-rouge">SYSRET</code> 契约</strong>是否仍可满足<sup id="fnref:10:4"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>。</td>
    </tr>
  </tbody>
</table>

<p>同一组维度在 <strong><code class="language-plaintext highlighter-rouge">syscall</code> 专题</strong>里也可以压缩理解：宏观上都要完成 <strong>ring 切换与寄存器约定</strong>，微观上 <strong><code class="language-plaintext highlighter-rouge">SYSCALL</code>/<code class="language-plaintext highlighter-rouge">SYSRET</code> 把可由专用指令“包办”的部分收紧</strong>，<strong><code class="language-plaintext highlighter-rouge">int</code>/IDT/<code class="language-plaintext highlighter-rouge">IRET</code></strong> 为覆盖全体中断/异常类型保留更宽的默认行为。</p>

<h4 id="与上表对应的三个技术要点64-位长模式">与上表对应的三个技术要点（64 位长模式）</h4>

<p>以下三点承接 <strong>上文「入核与出核」对照表</strong>，用语与 <strong>IA-32e 长模式</strong> 下的栈帧布局及当前 <strong>Linux <code class="language-plaintext highlighter-rouge">arch/x86/entry</code></strong> 实现一致。</p>

<ul>
  <li>
    <p><strong>硬件保存的寄存器现场不同</strong>
 <strong><code class="language-plaintext highlighter-rouge">INT n</code> 经 IDT</strong> 时走 <strong>通用中断/异常交付</strong>：在 <strong>64 位长模式</strong>下，CPU 向 <strong>当前特权级 0 栈</strong> 压入 <strong>SS、RSP、RFLAGS、CS、RIP</strong> 及视向量而定的 <strong>错误码</strong> 等，与同一条 <strong>IRET</strong> 恢复约定兼容、并由<strong>全体 IDT 向量</strong>共享这一框架<sup id="fnref:1:6"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>。<strong><code class="language-plaintext highlighter-rouge">SYSCALL</code></strong> <strong>不向栈压帧</strong>，仅用 <strong><code class="language-plaintext highlighter-rouge">RCX</code>、<code class="language-plaintext highlighter-rouge">R11</code></strong> 分别保留 <strong><code class="language-plaintext highlighter-rouge">RIP</code>、<code class="language-plaintext highlighter-rouge">RFLAGS</code> 的返回契约信息</strong>；通用寄存器与 <strong><code class="language-plaintext highlighter-rouge">RSP</code> 等</strong>由 <strong><code class="language-plaintext highlighter-rouge">entry_SYSCALL_64</code></strong> 等 <strong>软件路径</strong> 写入 <strong><code class="language-plaintext highlighter-rouge">struct pt_regs</code></strong><sup id="fnref:3:11"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup><sup id="fnref:9:4"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">11</a></sup>。</p>
  </li>
  <li>
    <p><strong>是否经过 IDT 与 DPL 检查</strong>
 <strong><code class="language-plaintext highlighter-rouge">INT n</code></strong> 根据 <strong>门描述符</strong> 做 <strong>DPL、门类型</strong> 等与 <strong>软件中断</strong> 相关的一致性检查<sup id="fnref:1:7"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>。<strong><code class="language-plaintext highlighter-rouge">SYSCALL</code></strong> <strong>不读取 IDT 门</strong>：<strong>CPL 0 入口 <code class="language-plaintext highlighter-rouge">RIP</code></strong>、<strong>段与 <code class="language-plaintext highlighter-rouge">RFLAGS</code> 掩码</strong>由 <strong><code class="language-plaintext highlighter-rouge">IA32_LSTAR</code>、<code class="language-plaintext highlighter-rouge">IA32_STAR</code>、<code class="language-plaintext highlighter-rouge">IA32_FMASK</code></strong> 及 <strong><code class="language-plaintext highlighter-rouge">IA32_EFER.SCE</code></strong> 预先约定<sup id="fnref:3:12"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup><sup id="fnref:11:6"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>；<strong>合法性</strong>依赖 <strong>OS 对这些 MSR 与 GDT 项的初始化</strong>以及内核入口实现。</p>
  </li>
  <li>
    <p><strong>返回路径的恢复范围</strong>
 <strong><code class="language-plaintext highlighter-rouge">IRET</code></strong> 从栈上 <strong>中断帧</strong> 恢复 <strong>SS、RSP、RFLAGS、CS、RIP</strong> 等，<strong>语义覆盖完整</strong><sup id="fnref:1:8"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>。<strong><code class="language-plaintext highlighter-rouge">SYSRET</code></strong>（长模式下 <strong><code class="language-plaintext highlighter-rouge">REX.W</code></strong>）在契约成立时仅从 <strong><code class="language-plaintext highlighter-rouge">RCX</code>、<code class="language-plaintext highlighter-rouge">R11</code></strong> 恢复 <strong><code class="language-plaintext highlighter-rouge">RIP</code>、<code class="language-plaintext highlighter-rouge">RFLAGS</code></strong>，<strong>用户态 <code class="language-plaintext highlighter-rouge">CS</code>/<code class="language-plaintext highlighter-rouge">SS</code></strong> 按 <strong><code class="language-plaintext highlighter-rouge">IA32_STAR</code></strong> 出核位域装载<sup id="fnref:4:2"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>。<strong>Linux</strong> 在 <strong><code class="language-plaintext highlighter-rouge">do_syscall_64</code></strong> 中若判定 <strong><code class="language-plaintext highlighter-rouge">SYSRET</code> 契约</strong>不成立或须走通用返回路径，则 <strong>改用 <code class="language-plaintext highlighter-rouge">IRET</code></strong><sup id="fnref:10:5"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>。</p>
  </li>
</ul>

<h3 id="数量级举例">数量级举例</h3>

<p>在常见 x86-64 桌面平台上，对 <strong><code class="language-plaintext highlighter-rouge">getpid</code> 类极短系统调用</strong>做周期计数，<strong><code class="language-plaintext highlighter-rouge">int 0x80</code></strong> 有时可达约 <strong>二百周期</strong>量级，<strong><code class="language-plaintext highlighter-rouge">syscall</code></strong> 多在约 <strong>数十至百余周期</strong>量级，可差数倍。结果强依赖 <strong>CPU、微架构、是否实际走 <code class="language-plaintext highlighter-rouge">SYSRET</code> 与测量方法</strong>；定量的结论应在目标机上用 <strong><code class="language-plaintext highlighter-rouge">perf</code> 等</strong>重复测量。</p>

<h3 id="小结">小结</h3>

<ul>
  <li><strong>IDT</strong>：通用 <strong>事件交付</strong> 机制，优先保证覆盖面与一致性，<strong>不以最短系统调用为唯一目标</strong>。</li>
  <li><strong>系统调用分发</strong>：<strong><code class="language-plaintext highlighter-rouge">x64_sys_call</code> 的 <code class="language-plaintext highlighter-rouge">switch</code></strong> 为主路径；<strong><code class="language-plaintext highlighter-rouge">sys_call_table[]</code></strong> 仍服务 <strong>观测/枚举</strong> 等需求；二者都在 <strong><code class="language-plaintext highlighter-rouge">syscall</code> 已进核之后</strong> 执行。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">SYSCALL</code> + MSR</strong>：系统调用 <strong>专用</strong>硬件入口协议；真正缩短的是 <strong>经 MSR 的入核与在条件允许时的 <code class="language-plaintext highlighter-rouge">SYSRET</code> 返回</strong>，不是“少做一次 C 层分发”。</li>
  <li><strong>Linux</strong>：即便从 <strong><code class="language-plaintext highlighter-rouge">syscall</code></strong> 入核，仍可能在出口选用 <strong><code class="language-plaintext highlighter-rouge">IRET</code></strong>，与 <strong><code class="language-plaintext highlighter-rouge">SYSRET</code> 契约</strong>及历史、安全问题有关<sup id="fnref:10:6"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>。</li>
</ul>

<hr />

<h2 id="建议的自修顺序">建议的自修顺序</h2>

<ul>
  <li>SDM：<strong>中断/异常与 IDT</strong>、<strong><code class="language-plaintext highlighter-rouge">SYSCALL</code>/<code class="language-plaintext highlighter-rouge">SYSRET</code></strong>。</li>
  <li>Linux：<strong><code class="language-plaintext highlighter-rouge">common.c</code>（MSR）→ <code class="language-plaintext highlighter-rouge">entry_64.S</code> → <code class="language-plaintext highlighter-rouge">syscall_64.c</code></strong>。</li>
  <li>对照阅读：<code class="language-plaintext highlighter-rouge">entry_64.S</code> 与 <code class="language-plaintext highlighter-rouge">syscall_64.c</code>，结合文末 References。</li>
</ul>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p><a href="https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html">Intel® 64 and IA-32 Architectures SDM — Combined Volumes</a> - 官方总入口（含 Volume 3 系统编程）；文中 IDT 64-bit 描述与中断/异常机制以此为准 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:1:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:1:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:1:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:1:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a> <a href="#fnref:1:5" class="reversefootnote" role="doc-backlink">&#8617;<sup>6</sup></a> <a href="#fnref:1:6" class="reversefootnote" role="doc-backlink">&#8617;<sup>7</sup></a> <a href="#fnref:1:7" class="reversefootnote" role="doc-backlink">&#8617;<sup>8</sup></a> <a href="#fnref:1:8" class="reversefootnote" role="doc-backlink">&#8617;<sup>9</sup></a></p>
    </li>
    <li id="fn:2">
      <p><a href="https://wiki.osdev.org/Interrupt_Descriptor_Table">OSDev Wiki — Interrupt Descriptor Table</a> - IDT 结构与模式差异的教学索引 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:3">
      <p><a href="https://www.felixcloutier.com/x86/syscall">x86 Instruction Reference — SYSCALL</a> - 指令级语义（<code class="language-plaintext highlighter-rouge">RCX</code>/<code class="language-plaintext highlighter-rouge">R11</code>、<code class="language-plaintext highlighter-rouge">LSTAR</code>、<code class="language-plaintext highlighter-rouge">FMASK</code>、<code class="language-plaintext highlighter-rouge">RSP</code> 不保存） <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:3:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:3:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:3:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:3:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a> <a href="#fnref:3:5" class="reversefootnote" role="doc-backlink">&#8617;<sup>6</sup></a> <a href="#fnref:3:6" class="reversefootnote" role="doc-backlink">&#8617;<sup>7</sup></a> <a href="#fnref:3:7" class="reversefootnote" role="doc-backlink">&#8617;<sup>8</sup></a> <a href="#fnref:3:8" class="reversefootnote" role="doc-backlink">&#8617;<sup>9</sup></a> <a href="#fnref:3:9" class="reversefootnote" role="doc-backlink">&#8617;<sup>10</sup></a> <a href="#fnref:3:10" class="reversefootnote" role="doc-backlink">&#8617;<sup>11</sup></a> <a href="#fnref:3:11" class="reversefootnote" role="doc-backlink">&#8617;<sup>12</sup></a> <a href="#fnref:3:12" class="reversefootnote" role="doc-backlink">&#8617;<sup>13</sup></a></p>
    </li>
    <li id="fn:4">
      <p><a href="https://www.felixcloutier.com/x86/sysret">x86 Instruction Reference — SYSRET</a> - <code class="language-plaintext highlighter-rouge">SYSRET</code> 返回语义与 <code class="language-plaintext highlighter-rouge">RSP</code> 处理约束 <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:4:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:4:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:11">
      <p>正文所引 <strong>Intel SDM 英文原文</strong>出自 <em>Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A: System Programming Guide, Part 1</em>（约 <strong>§6.14</strong> 64-bit IDT gate、<strong>§5.8.8</strong> <code class="language-plaintext highlighter-rouge">SYSCALL</code>/<code class="language-plaintext highlighter-rouge">SYSRET</code>）；完整手册见 <sup id="fnref:1:9"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> 的官方下载入口 <a href="#fnref:11" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:11:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:11:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:11:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:11:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a> <a href="#fnref:11:5" class="reversefootnote" role="doc-backlink">&#8617;<sup>6</sup></a> <a href="#fnref:11:6" class="reversefootnote" role="doc-backlink">&#8617;<sup>7</sup></a></p>
    </li>
    <li id="fn:10">
      <p><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/entry/syscall_64.c">Linux Source — arch/x86/entry/syscall_64.c</a> - <code class="language-plaintext highlighter-rouge">do_syscall_64</code>、<code class="language-plaintext highlighter-rouge">x64_sys_call</code> 与 <code class="language-plaintext highlighter-rouge">SYSRET/IRET</code> 判定 <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:10:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:10:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:10:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:10:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a> <a href="#fnref:10:5" class="reversefootnote" role="doc-backlink">&#8617;<sup>6</sup></a> <a href="#fnref:10:6" class="reversefootnote" role="doc-backlink">&#8617;<sup>7</sup></a></p>
    </li>
    <li id="fn:5">
      <p><a href="https://www.kernel.org/doc/html/latest/arch/x86/entry_64.html">Linux Kernel Documentation — entry_64</a> - x86 多入口说明（含 <code class="language-plaintext highlighter-rouge">entry_INT80_compat</code>、<code class="language-plaintext highlighter-rouge">system_call</code> 等） <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6">
      <p><a href="https://www.felixcloutier.com/x86/sysenter">Intel x86 Instruction Set Reference — SYSENTER</a> - <code class="language-plaintext highlighter-rouge">SYSENTER/SYSEXIT</code> 的历史快速调用路径 <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7">
      <p><a href="https://man7.org/linux/man-pages/man2/syscall.2.html">man7 — syscall(2)</a> - Linux 用户态系统调用 ABI 与调用约定说明 <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:7:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:8">
      <p><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/common.c">Linux Source — arch/x86/kernel/cpu/common.c</a> - <code class="language-plaintext highlighter-rouge">syscall_init()</code> / <code class="language-plaintext highlighter-rouge">idt_syscall_init()</code> 与 <code class="language-plaintext highlighter-rouge">MSR_SYSCALL_MASK</code> 初始化 <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:8:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:8:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:8:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a></p>
    </li>
    <li id="fn:9">
      <p><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/entry/entry_64.S">Linux Source — arch/x86/entry/entry_64.S</a> - <code class="language-plaintext highlighter-rouge">entry_SYSCALL_64</code> 路径（<code class="language-plaintext highlighter-rouge">swapgs</code>、<code class="language-plaintext highlighter-rouge">pt_regs</code>、<code class="language-plaintext highlighter-rouge">sysretq</code>） <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:9:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:9:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:9:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:9:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="linux-kernel" /><summary type="html"><![CDATA[对比 x86 IDT 中断门与 SYSCALL 快速系统调用机制，梳理 Linux 内核从用户态进入内核态的执行路径与性能差异。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://weinan.tech/images/og/linux-kernel.png" /><media:content medium="image" url="https://weinan.tech/images/og/linux-kernel.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">RDD 编程模型：从 Bash 脚本到分布式数据集的技术映射</title><link href="https://weinan.tech/2026/03/29/rdd-bash-mapreduce-spark.html" rel="alternate" type="text/html" title="RDD 编程模型：从 Bash 脚本到分布式数据集的技术映射" /><published>2026-03-29T00:00:00+08:00</published><updated>2026-03-29T00:00:00+08:00</updated><id>https://weinan.tech/2026/03/29/rdd-bash-mapreduce-spark</id><content type="html" xml:base="https://weinan.tech/2026/03/29/rdd-bash-mapreduce-spark.html"><![CDATA[<p>RDD（Resilient Distributed Dataset，弹性分布式数据集）是 Apache Spark 的核心抽象。本文通过将 RDD 编程模型与经典的 Bash 脚本管道、MapReduce 计算范式进行系统类比，帮助开发者建立从单机脚本思维到分布式数据处理的平滑过渡。文章涵盖执行模型、操作分类、容错机制及实际代码对比。</p>

<hr />

<h2 id="1-引言">1. 引言</h2>

<p>在单机环境中，Bash 脚本通过管道组合文本处理工具（如 <code class="language-plaintext highlighter-rouge">grep</code>、<code class="language-plaintext highlighter-rouge">sort</code>、<code class="language-plaintext highlighter-rouge">uniq</code>、<code class="language-plaintext highlighter-rouge">wc</code>）完成数据处理任务。在分布式环境中，RDD 提供了类似的函数式 API，但将执行扩展到集群，并引入了<strong>惰性求值</strong>与<strong>容错机制</strong>。</p>

<p>理解 RDD 的一种有效方式是将其视为「<strong>分布式版的 Bash 管道</strong>」，其中每个命令对应一个转换操作，管道的末端对应一个触发执行的动作。</p>

<hr />

<h2 id="2-核心概念映射">2. 核心概念映射</h2>

<h3 id="21-执行模型对比">2.1 执行模型对比</h3>

<table>
  <thead>
    <tr>
      <th>概念</th>
      <th>Bash</th>
      <th>RDD</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>数据源</td>
      <td>文件、标准输入</td>
      <td><code class="language-plaintext highlighter-rouge">textFile()</code>、<code class="language-plaintext highlighter-rouge">parallelize()</code></td>
    </tr>
    <tr>
      <td>中间结果</td>
      <td>管道传递或临时文件</td>
      <td>RDD 引用，可缓存</td>
    </tr>
    <tr>
      <td>操作类型</td>
      <td>立即执行的命令</td>
      <td>转换（Transformation）与动作（Action）</td>
    </tr>
    <tr>
      <td>执行触发</td>
      <td>命令输入即执行</td>
      <td>动作调用时触发 DAG 执行</td>
    </tr>
    <tr>
      <td>并行性</td>
      <td>单进程，需手动 <code class="language-plaintext highlighter-rouge">&amp;</code></td>
      <td>自动分片并行</td>
    </tr>
    <tr>
      <td>容错</td>
      <td>脚本退出或重试</td>
      <td>基于血缘（Lineage）自动重建</td>
    </tr>
  </tbody>
</table>

<h3 id="22-操作类比">2.2 操作类比</h3>

<table>
  <thead>
    <tr>
      <th>功能</th>
      <th>Bash</th>
      <th>RDD</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>过滤行</td>
      <td><code class="language-plaintext highlighter-rouge">grep pattern</code></td>
      <td><code class="language-plaintext highlighter-rouge">filter(_.contains(pattern))</code></td>
    </tr>
    <tr>
      <td>提取字段</td>
      <td><code class="language-plaintext highlighter-rouge">cut -d',' -f2</code></td>
      <td><code class="language-plaintext highlighter-rouge">map(_.split(",")(1))</code></td>
    </tr>
    <tr>
      <td>排序</td>
      <td><code class="language-plaintext highlighter-rouge">sort</code></td>
      <td><code class="language-plaintext highlighter-rouge">sortBy()</code></td>
    </tr>
    <tr>
      <td>聚合计数</td>
      <td><code class="language-plaintext highlighter-rouge">uniq -c</code></td>
      <td><code class="language-plaintext highlighter-rouge">reduceByKey(_ + _)</code></td>
    </tr>
    <tr>
      <td>限制输出</td>
      <td><code class="language-plaintext highlighter-rouge">head -n</code></td>
      <td><code class="language-plaintext highlighter-rouge">take(n)</code></td>
    </tr>
    <tr>
      <td>保存结果</td>
      <td><code class="language-plaintext highlighter-rouge">&gt; output.txt</code></td>
      <td><code class="language-plaintext highlighter-rouge">saveAsTextFile(path)</code></td>
    </tr>
    <tr>
      <td>变量存储</td>
      <td><code class="language-plaintext highlighter-rouge">var=$(command)</code></td>
      <td><code class="language-plaintext highlighter-rouge">val rdd = transformation</code></td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="3-示例分析web-访问日志处理">3. 示例分析：Web 访问日志处理</h2>

<h3 id="31-业务场景">3.1 业务场景</h3>

<p>分析 Web 服务器日志，统计状态码为 404 的请求中，出现次数最多的前 5 个 URL 路径。</p>

<h3 id="32-bash-脚本实现">3.2 Bash 脚本实现</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 过滤状态码为404的行，提取URL路径，统计并排序</span>
<span class="nb">grep</span> <span class="s2">" 404 "</span> access.log | <span class="se">\</span>
<span class="nb">awk</span> <span class="s1">'{print $7}'</span> | <span class="se">\</span>
<span class="nb">sort</span> | <span class="se">\</span>
<span class="nb">uniq</span> <span class="nt">-c</span> | <span class="se">\</span>
<span class="nb">sort</span> <span class="nt">-nr</span> | <span class="se">\</span>
<span class="nb">head</span> <span class="nt">-5</span>
</code></pre></div></div>

<p><strong>执行特点</strong>：</p>

<ul>
  <li>每条命令立即执行</li>
  <li>中间结果通过管道在内存中传递</li>
  <li>单机顺序处理</li>
</ul>

<h3 id="33-rdd-实现">3.3 RDD 实现</h3>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">val</span> <span class="nv">logRDD</span> <span class="k">=</span> <span class="nv">sc</span><span class="o">.</span><span class="py">textFile</span><span class="o">(</span><span class="s">"hdfs://cluster/logs/access.log"</span><span class="o">)</span>

<span class="k">val</span> <span class="nv">top404Urls</span> <span class="k">=</span> <span class="n">logRDD</span>
  <span class="o">.</span><span class="py">filter</span><span class="o">(</span><span class="n">line</span> <span class="k">=&gt;</span> <span class="nv">line</span><span class="o">.</span><span class="py">contains</span><span class="o">(</span><span class="s">" 404 "</span><span class="o">))</span>          <span class="c1">// 等价于 grep</span>
  <span class="o">.</span><span class="py">map</span><span class="o">(</span><span class="n">line</span> <span class="k">=&gt;</span> <span class="nv">line</span><span class="o">.</span><span class="py">split</span><span class="o">(</span><span class="s">" "</span><span class="o">)(</span><span class="mi">6</span><span class="o">))</span>                 <span class="c1">// 等价于 awk，提取URL</span>
  <span class="o">.</span><span class="py">map</span><span class="o">(</span><span class="n">url</span> <span class="k">=&gt;</span> <span class="o">(</span><span class="n">url</span><span class="o">,</span> <span class="mi">1</span><span class="o">))</span>                            <span class="c1">// 准备计数</span>
  <span class="o">.</span><span class="py">reduceByKey</span><span class="o">(</span><span class="k">_</span> <span class="o">+</span> <span class="k">_</span><span class="o">)</span>                              <span class="c1">// 等价于 uniq -c</span>
  <span class="o">.</span><span class="py">map</span><span class="o">(</span><span class="nv">_</span><span class="o">.</span><span class="py">swap</span><span class="o">)</span>                                     <span class="c1">// 交换键值以便排序</span>
  <span class="o">.</span><span class="py">sortByKey</span><span class="o">(</span><span class="n">ascending</span> <span class="k">=</span> <span class="kc">false</span><span class="o">)</span>                    <span class="c1">// 等价于 sort -nr</span>
  <span class="o">.</span><span class="py">take</span><span class="o">(</span><span class="mi">5</span><span class="o">)</span>                                         <span class="c1">// 等价于 head -5</span>

<span class="nv">top404Urls</span><span class="o">.</span><span class="py">foreach</span><span class="o">(</span><span class="n">println</span><span class="o">)</span>
</code></pre></div></div>

<p><strong>执行特点</strong>：</p>

<ul>
  <li>所有转换（filter、map、reduceByKey）构建 DAG，不立即执行</li>
  <li><code class="language-plaintext highlighter-rouge">take(5)</code> 作为动作触发分布式计算</li>
  <li>数据自动分片，并行处理</li>
  <li>节点故障时自动基于血缘重算</li>
</ul>

<hr />

<h2 id="4-执行机制深入">4. 执行机制深入</h2>

<h3 id="41-惰性求值lazy-evaluation">4.1 惰性求值（Lazy Evaluation）</h3>

<p>Bash 采用<strong>渴望求值</strong>（Eager Evaluation），每个命令立即执行：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 立即执行 grep，再执行 wc</span>
<span class="nb">grep</span> <span class="s2">"ERROR"</span> app.log | <span class="nb">wc</span> <span class="nt">-l</span>
</code></pre></div></div>

<p>RDD 采用<strong>惰性求值</strong>，只有动作调用时才执行：</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">val</span> <span class="nv">errors</span> <span class="k">=</span> <span class="nv">logRDD</span><span class="o">.</span><span class="py">filter</span><span class="o">(</span><span class="nv">_</span><span class="o">.</span><span class="py">contains</span><span class="o">(</span><span class="s">"ERROR"</span><span class="o">))</span>  <span class="c1">// 仅记录转换</span>
<span class="k">val</span> <span class="nv">count</span> <span class="k">=</span> <span class="nv">errors</span><span class="o">.</span><span class="py">count</span><span class="o">()</span>                       <span class="c1">// 触发执行</span>
</code></pre></div></div>

<p><strong>优势</strong>：</p>

<ul>
  <li>允许执行计划优化（如谓词下推）</li>
  <li>避免不必要的数据扫描</li>
  <li>支持中间结果缓存复用</li>
</ul>

<h3 id="42-缓存机制类比">4.2 缓存机制类比</h3>

<table>
  <thead>
    <tr>
      <th>Bash</th>
      <th>RDD</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>中间结果写入临时文件</td>
      <td><code class="language-plaintext highlighter-rouge">rdd.cache()</code> 或 <code class="language-plaintext highlighter-rouge">rdd.persist()</code></td>
    </tr>
    <tr>
      <td>复用需重新读取文件</td>
      <td>缓存保留在内存/磁盘供后续复用</td>
    </tr>
    <tr>
      <td>手动清理临时文件</td>
      <td>自动 LRU 或显式 <code class="language-plaintext highlighter-rouge">unpersist()</code></td>
    </tr>
  </tbody>
</table>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">val</span> <span class="nv">intermediate</span> <span class="k">=</span> <span class="nv">logRDD</span><span class="o">.</span><span class="py">filter</span><span class="o">(</span><span class="nv">_</span><span class="o">.</span><span class="py">contains</span><span class="o">(</span><span class="s">"404"</span><span class="o">))</span>
<span class="nv">intermediate</span><span class="o">.</span><span class="py">cache</span><span class="o">()</span>                         <span class="c1">// 类似写入临时文件</span>
<span class="k">val</span> <span class="nv">count</span> <span class="k">=</span> <span class="nv">intermediate</span><span class="o">.</span><span class="py">count</span><span class="o">()</span>             <span class="c1">// 首次计算并缓存</span>
<span class="k">val</span> <span class="nv">sample</span> <span class="k">=</span> <span class="nv">intermediate</span><span class="o">.</span><span class="py">take</span><span class="o">(</span><span class="mi">10</span><span class="o">)</span>           <span class="c1">// 从缓存直接读取</span>
</code></pre></div></div>

<hr />

<h2 id="5-容错机制">5. 容错机制</h2>

<h3 id="51-bash-的容错">5.1 Bash 的容错</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 简单的重试逻辑</span>
<span class="k">for </span>i <span class="k">in</span> <span class="o">{</span>1..3<span class="o">}</span><span class="p">;</span> <span class="k">do
    </span><span class="nb">grep</span> <span class="s2">"ERROR"</span> app.log <span class="o">&gt;</span> result.txt <span class="o">&amp;&amp;</span> <span class="nb">break
    sleep </span>5
<span class="k">done</span>
</code></pre></div></div>

<h3 id="52-rdd-的容错血缘-lineage">5.2 RDD 的容错（血缘 Lineage）</h3>

<p>RDD 记录每个转换操作的血缘关系。当分区数据丢失时，系统自动从源头或缓存重建：</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">val</span> <span class="nv">rdd1</span> <span class="k">=</span> <span class="nv">sc</span><span class="o">.</span><span class="py">textFile</span><span class="o">(</span><span class="s">"data.txt"</span><span class="o">)</span>      <span class="c1">// 源头</span>
<span class="k">val</span> <span class="nv">rdd2</span> <span class="k">=</span> <span class="nv">rdd1</span><span class="o">.</span><span class="py">filter</span><span class="o">(</span><span class="nv">_</span><span class="o">.</span><span class="py">contains</span><span class="o">(</span><span class="s">"key"</span><span class="o">))</span> <span class="c1">// 转换1</span>
<span class="k">val</span> <span class="nv">rdd3</span> <span class="k">=</span> <span class="nv">rdd2</span><span class="o">.</span><span class="py">map</span><span class="o">(</span><span class="nv">_</span><span class="o">.</span><span class="py">split</span><span class="o">(</span><span class="s">","</span><span class="o">)(</span><span class="mi">0</span><span class="o">))</span>      <span class="c1">// 转换2</span>
<span class="k">val</span> <span class="nv">result</span> <span class="k">=</span> <span class="nv">rdd3</span><span class="o">.</span><span class="py">count</span><span class="o">()</span>                 <span class="c1">// 动作</span>

<span class="c1">// 若某分区在计算 count 时丢失，Spark 根据血缘从 data.txt 重新计算 rdd1→rdd2→rdd3 的该分区</span>
</code></pre></div></div>

<hr />

<h2 id="6-思维模型总结">6. 思维模型总结</h2>

<table>
  <thead>
    <tr>
      <th>思维维度</th>
      <th>Bash 模型</th>
      <th>RDD 模型</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>数据视角</td>
      <td>文本流</td>
      <td>分区集合</td>
    </tr>
    <tr>
      <td>操作视角</td>
      <td>命令链</td>
      <td>转换链 + 动作触发</td>
    </tr>
    <tr>
      <td>执行视角</td>
      <td>立即顺序执行</td>
      <td>延迟并行执行</td>
    </tr>
    <tr>
      <td>容错视角</td>
      <td>脚本退出</td>
      <td>血缘自动重建</td>
    </tr>
    <tr>
      <td>扩展视角</td>
      <td>手动分片、<code class="language-plaintext highlighter-rouge">xargs</code></td>
      <td>自动分片、动态资源</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="7-结论">7. 结论</h2>

<p>RDD 可以视为<strong>分布式、容错、惰性求值的 Bash 管道</strong>。它将 Bash 脚本中「命令 → 管道 → 重定向」的模型，扩展为「转换 → 血缘 → 动作」的分布式计算模型。对于熟悉单机文本处理的开发者，通过这种类比可以快速理解：</p>

<ul>
  <li><strong>转换</strong> = 管道中的命令（如 filter、map）</li>
  <li><strong>动作</strong> = 触发执行的命令（如 count、collect）</li>
  <li><strong>缓存</strong> = 临时文件复用</li>
  <li><strong>血缘</strong> = 自动化的错误重试机制</li>
</ul>

<p>这种映射不仅有助于降低学习曲线，也为设计高效的分布式数据处理流程提供了清晰的思维框架。</p>

<hr />

<h2 id="附录操作对照表">附录：操作对照表</h2>

<table>
  <thead>
    <tr>
      <th>操作类型</th>
      <th>Bash 命令</th>
      <th>RDD 方法</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>读取文件</td>
      <td><code class="language-plaintext highlighter-rouge">cat file.txt</code></td>
      <td><code class="language-plaintext highlighter-rouge">sc.textFile(path)</code></td>
    </tr>
    <tr>
      <td>过滤</td>
      <td><code class="language-plaintext highlighter-rouge">grep pattern</code></td>
      <td><code class="language-plaintext highlighter-rouge">filter(predicate)</code></td>
    </tr>
    <tr>
      <td>映射</td>
      <td><code class="language-plaintext highlighter-rouge">awk '{print $1}'</code></td>
      <td><code class="language-plaintext highlighter-rouge">map(func)</code></td>
    </tr>
    <tr>
      <td>扁平映射</td>
      <td><code class="language-plaintext highlighter-rouge">xargs -n1</code></td>
      <td><code class="language-plaintext highlighter-rouge">flatMap(func)</code></td>
    </tr>
    <tr>
      <td>聚合</td>
      <td><code class="language-plaintext highlighter-rouge">sort \| uniq -c</code></td>
      <td><code class="language-plaintext highlighter-rouge">reduceByKey(_ + _)</code></td>
    </tr>
    <tr>
      <td>排序</td>
      <td><code class="language-plaintext highlighter-rouge">sort -k2 -nr</code></td>
      <td><code class="language-plaintext highlighter-rouge">sortByKey()</code></td>
    </tr>
    <tr>
      <td>限制</td>
      <td><code class="language-plaintext highlighter-rouge">head -n</code></td>
      <td><code class="language-plaintext highlighter-rouge">take(n)</code></td>
    </tr>
    <tr>
      <td>保存</td>
      <td><code class="language-plaintext highlighter-rouge">&gt; output.txt</code></td>
      <td><code class="language-plaintext highlighter-rouge">saveAsTextFile(path)</code></td>
    </tr>
    <tr>
      <td>计数</td>
      <td><code class="language-plaintext highlighter-rouge">wc -l</code></td>
      <td><code class="language-plaintext highlighter-rouge">count()</code></td>
    </tr>
    <tr>
      <td>变量赋值</td>
      <td><code class="language-plaintext highlighter-rouge">var=$(cmd)</code></td>
      <td><code class="language-plaintext highlighter-rouge">val rdd = transformation</code></td>
    </tr>
  </tbody>
</table>

<hr />

<p><strong>文档版本</strong>：1.0<br />
<strong>适用场景</strong>：RDD 编程入门、技术培训、思维模型转换</p>]]></content><author><name>阿男</name></author><category term="cloud-native" /><summary type="html"><![CDATA[用 Bash 到分布式数据集的映射理解 RDD 编程模型，建立 MapReduce 与 Spark 之间的概念桥梁。]]></summary></entry><entry><title type="html">从 Java task_server 到 Rust（htyts / htyproc）：用 AI 推进迁移，用 GitHub CI 与基础设施兜住 E2E</title><link href="https://weinan.tech/2026/03/22/rust-task-server-migration-ai-ci-e2e.html" rel="alternate" type="text/html" title="从 Java task_server 到 Rust（htyts / htyproc）：用 AI 推进迁移，用 GitHub CI 与基础设施兜住 E2E" /><published>2026-03-22T00:00:00+08:00</published><updated>2026-03-22T00:00:00+08:00</updated><id>https://weinan.tech/2026/03/22/rust-task-server-migration-ai-ci-e2e</id><content type="html" xml:base="https://weinan.tech/2026/03/22/rust-task-server-migration-ai-ci-e2e.html"><![CDATA[<style>
/* Mermaid diagram container */
.mermaid-container {
  position: relative;
  display: block;
  cursor: pointer;
  transition: opacity 0.2s;
  max-width: 100%;
  margin: 20px 0;
  overflow-x: auto;
}

.mermaid-container:hover {
  opacity: 0.8;
}

.mermaid-container svg {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Modal overlay */
.mermaid-modal {
  display: none;
  position: fixed;
  z-index: 9999;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9);
  animation: fadeIn 0.3s;
}

.mermaid-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Modal content */
.mermaid-modal-content {
  position: relative;
  width: 90vw;
  height: 90vh;
  overflow: hidden;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
}

.mermaid-modal-diagram {
  transform-origin: center center;
  transition: transform 0.2s ease;
  display: inline-block;
  min-width: 100%;
  cursor: grab;
  user-select: none;
}

.mermaid-modal-diagram.dragging {
  cursor: grabbing;
  transition: none;
}

.mermaid-modal-diagram svg {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;
}

/* Control buttons */
.mermaid-controls {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
  z-index: 10000;
}

.mermaid-btn {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
  color: #333;
  font-weight: 500;
}

.mermaid-btn:hover {
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Close button */
.mermaid-close {
  background: #f44336;
  color: white;
  border: none;
}

.mermaid-close:hover {
  background: #d32f2f;
}

/* Zoom indicator */
.mermaid-zoom-level {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 10000;
}
</style>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  mermaid.initialize({
    startOnLoad: false,
    theme: 'default',
    securityLevel: 'loose',
    htmlLabels: true,
    themeVariables: {
      fontSize: '14px'
    }
  });

  let currentZoom = 1;
  let currentModal = null;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let translateX = 0;
  let translateY = 0;

  // Create modal HTML
  function createModal() {
    const modal = document.createElement('div');
    modal.className = 'mermaid-modal';
    modal.innerHTML = `
      <div class="mermaid-controls">
        <button class="mermaid-btn zoom-in">放大 +</button>
        <button class="mermaid-btn zoom-out">缩小 -</button>
        <button class="mermaid-btn zoom-reset">重置</button>
        <button class="mermaid-btn mermaid-close">关闭 ✕</button>
      </div>
      <div class="mermaid-modal-content">
        <div class="mermaid-modal-diagram"></div>
      </div>
      <div class="mermaid-zoom-level">100%</div>
    `;
    document.body.appendChild(modal);
    return modal;
  }

  // Show modal with diagram
  function showModal(diagramContent) {
    if (!currentModal) {
      currentModal = createModal();
      setupModalEvents();
    }

    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.innerHTML = diagramContent;

    // Remove any width/height attributes from SVG to make it responsive
    const svg = modalDiagram.querySelector('svg');
    if (svg) {
      svg.removeAttribute('width');
      svg.removeAttribute('height');
      svg.style.width = '100%';
      svg.style.height = 'auto';
    }

    // Setup drag functionality
    setupDrag(modalDiagram);

    currentModal.classList.add('active');
    currentZoom = 1;
    resetPosition();
    updateZoom();
    document.body.style.overflow = 'hidden';
  }

  // Hide modal
  function hideModal() {
    if (currentModal) {
      currentModal.classList.remove('active');
      document.body.style.overflow = '';
    }
  }

  // Update zoom level
  function updateZoom() {
    if (!currentModal) return;
    const diagram = currentModal.querySelector('.mermaid-modal-diagram');
    const zoomLevel = currentModal.querySelector('.mermaid-zoom-level');
    diagram.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentZoom})`;
    zoomLevel.textContent = `${Math.round(currentZoom * 100)}%`;
  }

  // Reset position when zoom changes
  function resetPosition() {
    translateX = 0;
    translateY = 0;
  }

  // Setup drag functionality
  function setupDrag(element) {
    element.addEventListener('mousedown', startDrag);
    element.addEventListener('touchstart', startDrag);
  }

  function startDrag(e) {
    if (e.type === 'mousedown' && e.button !== 0) return; // Only left click

    isDragging = true;
    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.classList.add('dragging');

    if (e.type === 'touchstart') {
      startX = e.touches[0].clientX - translateX;
      startY = e.touches[0].clientY - translateY;
    } else {
      startX = e.clientX - translateX;
      startY = e.clientY - translateY;
    }

    document.addEventListener('mousemove', drag);
    document.addEventListener('touchmove', drag);
    document.addEventListener('mouseup', stopDrag);
    document.addEventListener('touchend', stopDrag);
  }

  function drag(e) {
    if (!isDragging) return;
    e.preventDefault();

    if (e.type === 'touchmove') {
      translateX = e.touches[0].clientX - startX;
      translateY = e.touches[0].clientY - startY;
    } else {
      translateX = e.clientX - startX;
      translateY = e.clientY - startY;
    }

    updateZoom();
  }

  function stopDrag() {
    isDragging = false;
    const modalDiagram = currentModal?.querySelector('.mermaid-modal-diagram');
    if (modalDiagram) {
      modalDiagram.classList.remove('dragging');
    }
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('touchmove', drag);
    document.removeEventListener('mouseup', stopDrag);
    document.removeEventListener('touchend', stopDrag);
  }

  // Setup modal event listeners
  function setupModalEvents() {
    if (!currentModal) return;

    // Close button
    currentModal.querySelector('.mermaid-close').addEventListener('click', hideModal);

    // Zoom buttons
    currentModal.querySelector('.zoom-in').addEventListener('click', () => {
      currentZoom = Math.min(currentZoom + 0.25, 3);
      updateZoom();
    });

    currentModal.querySelector('.zoom-out').addEventListener('click', () => {
      currentZoom = Math.max(currentZoom - 0.25, 0.5);
      updateZoom();
    });

    currentModal.querySelector('.zoom-reset').addEventListener('click', () => {
      currentZoom = 1;
      resetPosition();
      updateZoom();
    });

    // Close on background click
    currentModal.addEventListener('click', (e) => {
      if (e.target === currentModal) {
        hideModal();
      }
    });

    // Close on ESC key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && currentModal.classList.contains('active')) {
        hideModal();
      }
    });
  }

  // Convert Jekyll-rendered code blocks to mermaid divs
  document.addEventListener('DOMContentLoaded', async function() {
    const codeBlocks = document.querySelectorAll('code.language-mermaid');

    for (const codeBlock of codeBlocks) {
      const pre = codeBlock.parentElement;
      const container = document.createElement('div');
      container.className = 'mermaid-container';

      const mermaidDiv = document.createElement('div');
      mermaidDiv.className = 'mermaid';
      mermaidDiv.textContent = codeBlock.textContent;

      container.appendChild(mermaidDiv);
      pre.replaceWith(container);
    }

    // Render all mermaid diagrams
    try {
      await mermaid.run({
        querySelector: '.mermaid'
      });
      console.log('Mermaid diagrams rendered successfully');
    } catch (error) {
      console.error('Mermaid rendering error:', error);
    }

    // Add click handlers to rendered diagrams
    document.querySelectorAll('.mermaid-container').forEach((container, index) => {
      // Find the rendered SVG inside the container
      const svg = container.querySelector('svg');
      if (!svg) {
        console.warn(`No SVG found in container ${index}`);
        return;
      }

      // Make the container clickable
      container.style.cursor = 'pointer';
      container.title = '点击查看大图';

      container.addEventListener('click', function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Clone the SVG for the modal
        const svgClone = svg.cloneNode(true);
        const tempDiv = document.createElement('div');
        tempDiv.appendChild(svgClone);

        console.log('Opening modal for diagram', index);
        showModal(tempDiv.innerHTML);
      });

      console.log(`Click handler added to diagram ${index}`);
    });
  });
</script>

<p>记录将现网 Java task_server、proc_server 与共享契约迁到 huiwing workspace（htyts_models + htyts + htyproc）的过程：如何用 Cursor 里的迁移计划驱动迭代、如何复用 AuthCore/htycommons，以及如何用 GitHub Actions、Diesel 迁移、Docker Compose 与 AuthCore 联调测试把回归成本压进流水线。</p>

<h2 id="背景计划里写什么代码里落什么">背景：计划里写什么，代码里落什么</h2>

<p>迁移前在 Cursor 里整理了一份结构化计划（<code class="language-plaintext highlighter-rouge">rust_迁移_task_ts_proc</code>），核心不是「逐文件翻译 Java」，而是先把<strong>契约</strong>钉死：</p>

<ul>
  <li><strong>task_server</strong> → 对外仍是 <code class="language-plaintext highlighter-rouge">**/api/v1/ts/**</code>：任务 CRUD、<code class="language-plaintext highlighter-rouge">one_pending_task</code> / <code class="language-plaintext highlighter-rouge">one_zombie_task</code>、分页列表，以及原 Quartz 承载的<strong>课程通知</strong>（改为 Rust 侧调度 + HTTP 调 htyuc/htykc）。</li>
  <li><strong>proc_server</strong> → <code class="language-plaintext highlighter-rouge">htyproc</code>：拉取 pending、按 <code class="language-plaintext highlighter-rouge">TaskType</code> 分发、与 Ts/Ai/Ngx/Uc/Ws 等下游 HTTP 对齐。</li>
  <li><strong>共享数据</strong> → <code class="language-plaintext highlighter-rouge">htyts_models</code>：与现网 JSON 兼容的 <code class="language-plaintext highlighter-rouge">ReqTask</code>、payload、<code class="language-plaintext highlighter-rouge">DbTask</code> 行结构；PostgreSQL + Redis（<code class="language-plaintext highlighter-rouge">TS_</code> 前缀等与 Java 一致），并优先复用 <strong>AuthCore <code class="language-plaintext highlighter-rouge">htycommons</code></strong> 的 <code class="language-plaintext highlighter-rouge">HtyResponse</code>、Axum 提取器、JWT 等，而不是在私有仓库里再造一套「长得像」的协议。</li>
</ul>

<p>计划里同时写清了<strong>仓库边界</strong>：任务域专有逻辑默认闭环在 huiwing；对 AuthCore 的改动要满足开源仓库的兼容、通用与安全预期——这一条直接影响了「哪些代码进 <code class="language-plaintext highlighter-rouge">htyts_models</code>、哪些只借鉴模式」。</p>

<h3 id="改造前后进程与-crate-边界">改造前后：进程与 crate 边界</h3>

<pre><code class="language-mermaid">flowchart LR
  subgraph before [改造前 Java]
    direction TB
    TS[task_server&lt;br/&gt;JAX-RS /api/v1/ts]
    PR[proc_server&lt;br/&gt;TaskProcessor]
    TC[task_commons&lt;br/&gt;DbTask ReqTask Redis]
    TS --&gt; TC
    PR --&gt; TC
  end

  subgraph after [改造后 Rust / huiwing]
    direction TB
    HTYTS[htyts&lt;br/&gt;Axum /api/v1/ts]
    HPROC[htyproc&lt;br/&gt;拉取与处理器]
    HM[htyts_models&lt;br/&gt;契约与 Diesel]
    HC[htycommons&lt;br/&gt;AuthCore]
    HTYTS --&gt; HM
    HPROC --&gt; HM
    HTYTS --&gt; HC
    HPROC --&gt; HC
  end

  TC -.-&gt;|契约对齐&lt;br/&gt;HTTP 路径与 JSON| HM
</code></pre>

<h3 id="迁移后的运行时结构与现网调用关系">迁移后的运行时结构（与现网调用关系）</h3>

<pre><code class="language-mermaid">flowchart TB
  subgraph clients [调用方]
    WEB[htymusic / htyadmin]
    NGXL[OpenResty / Lua]
  end

  subgraph htyts_crate [htyts]
    API["/api/v1/ts"]
    KC["/api/v1/ts/kc 课程通知调度"]
  end

  PG[(PostgreSQL&lt;br/&gt;dbtask)]
  RD[(Redis&lt;br/&gt;TS_ payload)]

  subgraph htyproc_crate [htyproc]
    LOOP[拉取 one_pending_task]
    HANDLERS[按 TaskType 处理]
  end

  subgraph downstream [下游 HTTP]
    UC[htyuc]
    WS[htyws]
    KC2[htykc]
    AISVC[ai 服务]
    NGX[ngx / OpenResty]
  end

  WEB --&gt; API
  NGXL --&gt; API
  API --&gt; PG
  API --&gt; RD
  KC --&gt; UC
  KC --&gt; KC2

  LOOP --&gt;|GET pending| API
  HANDLERS --&gt; UC
  HANDLERS --&gt; WS
  HANDLERS --&gt; KC2
  HANDLERS --&gt; AISVC
  HANDLERS --&gt; NGX
  HANDLERS --&gt;|POST update_task| API
</code></pre>

<h2 id="实际落到仓库里的变更2026-03-22-前后">实际落到仓库里的变更（2026-03-22 前后）</h2>

<p>主线已合入 <code class="language-plaintext highlighter-rouge">huiwing</code> 的 <code class="language-plaintext highlighter-rouge">main</code>（含 <a href="https://github.com/alchemy-studio/huiwing/pull/1631">PR #1631</a> 的合并），与本次主题相关的提交大致可以读成三层：</p>

<ol>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">feat(rust): task_server / proc_server 迁移</code></strong><br />
在工作区引入 <code class="language-plaintext highlighter-rouge">htyts_models</code>、<code class="language-plaintext highlighter-rouge">htyts</code>、<code class="language-plaintext highlighter-rouge">htyproc</code>，把 Java 侧任务 API 与处理器迁到 Rust，并与现有 workspace（Axum、<code class="language-plaintext highlighter-rouge">htycommons</code>、依赖版本）对齐。</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">feat(htyts_models): Diesel migrations + schema/DbTaskRow</code> + <code class="language-plaintext highlighter-rouge">refactor(htyts): move DB ops into htyts_models</code></strong><br />
用 <strong>Diesel</strong> 管理 <code class="language-plaintext highlighter-rouge">dbtask</code> 表结构（<code class="language-plaintext highlighter-rouge">migrations/</code>、<code class="language-plaintext highlighter-rouge">diesel.toml</code>、<code class="language-plaintext highlighter-rouge">schema.rs</code>），把数据库访问集中到 <code class="language-plaintext highlighter-rouge">htyts_models</code>（风格上对齐 <code class="language-plaintext highlighter-rouge">htyuc_models</code>），<code class="language-plaintext highlighter-rouge">htyts</code> 通过重导出与 handler 调用；E2E 侧改为 <strong><code class="language-plaintext highlighter-rouge">diesel migration run</code></strong>，去掉维护一份独立 <code class="language-plaintext highlighter-rouge">init.sql</code> 的漂移风险。实现细节上顺带处理了与 workspace 里 <strong>reqwest 版本</strong>相关的 URL 拼装（例如用 <code class="language-plaintext highlighter-rouge">url::form_urlencoded</code> 等与现有服务一致）。</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">ci: HTYTS + AuthCore 周更联调与本地 docker-compose</code></strong></p>
    <ul>
      <li><strong>GitHub Actions</strong>：轻量的 <code class="language-plaintext highlighter-rouge">rust-ts.yml</code> 仍在 <strong>PR/push</strong> 上跑：Postgres + Redis + <code class="language-plaintext highlighter-rouge">diesel_cli</code> + 迁移 + <code class="language-plaintext highlighter-rouge">cargo test -p htyts --test ts_e2e_http</code>。</li>
      <li><strong>重任务</strong>：单独 workflow <strong>仅 <code class="language-plaintext highlighter-rouge">schedule</code>（例如每周）+ <code class="language-plaintext highlighter-rouge">workflow_dispatch</code></strong>，clone <strong>AuthCore</strong>、双库迁移、构建并启动 <strong>htyuc</strong>、再跑依赖真实 UC 的集成测试；避免把「起一整条 AuthCore 链」绑在每一次 PR 上。</li>
      <li><strong>本地</strong>：<code class="language-plaintext highlighter-rouge">docker-compose.authcore-e2e.yml</code> + <code class="language-plaintext highlighter-rouge">scripts/run-authcore-e2e-docker.sh</code>，用固定宿主机端口起双 Postgres + Redis，脚本里处理 <strong><code class="language-plaintext highlighter-rouge">LOGGER_LEVEL</code></strong>、<strong><code class="language-plaintext highlighter-rouge">env -u CARGO_TARGET_DIR</code> 构建 htyuc</strong>（避免 IDE 注入的 target 目录导致找不到二进制）等踩坑点。</li>
    </ul>
  </li>
</ol>

<p>合并顺序上，<code class="language-plaintext highlighter-rouge">#1631</code> 把 Diesel/DB 重构与 CI 演进合进主线后，又与已存在的 AuthCore 联调提交并存于历史里；若只看「最终能力」，可以理解为：<strong>主线同时具备 Diesel 管理的任务表、轻量 HTTP E2E、以及与 AuthCore UC 的可选联调路径</strong>。</p>

<h3 id="数据层diesel-迁移与-crate-分工">数据层：Diesel 迁移与 crate 分工</h3>

<pre><code class="language-mermaid">flowchart LR
  subgraph repo [仓库内]
    MIG[htyts_models/migrations]
    SCH[schema.rs + DbTaskRow]
    OPS[impl 于 models.rs]
    MIG --&gt; SCH
    SCH --&gt; OPS
  end

  subgraph consumers [使用者]
    H[htyts handlers]
    H --&gt; OPS
  end

  subgraph ci [CI / 本地]
    D[diesel migration run]
    D --&gt; MIG
  end
</code></pre>

<h2 id="我们怎样用-ai-完成重写而不是胡写">我们怎样用 AI 完成「重写」而不是「胡写」</h2>

<p>结合上面那份计划，实际协作方式更接近下面几条，而不是「一句话生成整个仓库」：</p>

<ol>
  <li>
    <p><strong>计划即边界</strong><br />
把 Java 包路径、现网路由、Redis 前缀、与 htykc/htyuc 的调用关系写进计划后，后续无论是拆 crate 还是写 handler，都有一个<strong>可对照的清单</strong>，减少模型自由发挥。</p>
  </li>
  <li>
    <p><strong>契约优先于行数</strong><br />
先固定 <code class="language-plaintext highlighter-rouge">ReqTask</code> / <code class="language-plaintext highlighter-rouge">HtyResponse</code> / 错误码与 Java 行为一致，再补实现；AI 适合批量生成样板与对称的 handler，但<strong>字段名、状态机、与 UC 的 JWT 语义</strong>需要人眼对照现网或集成测试。</p>
  </li>
  <li>
    <p><strong>迭代式纠偏</strong><br />
例如联调 UC 时发现：<code class="language-plaintext highlighter-rouge">verify_jwt</code> 在 UC 侧会查 <strong>Redis 里是否存有与 <code class="language-plaintext highlighter-rouge">token_id</code> 对应的完整 JWT</strong>——本地随手 <code class="language-plaintext highlighter-rouge">jwt_encode_token</code> 出来的串并不会过校验；最终 E2E 改为走 <strong><code class="language-plaintext highlighter-rouge">login_with_password</code>（fixture 用户）</strong> 拿「已在 UC Redis 里登记」的 token，这是对<strong>真实协议</strong>的修正，而不是计划里一开始就能写全的细节。</p>
  </li>
  <li>
    <p><strong>Review 仍然是闸门</strong><br />
AI 加速的是起草与重构 diff；合并进 <code class="language-plaintext highlighter-rouge">main</code> 仍走 PR、CI 绿灯与人工扫一眼安全面（密钥、日志、对外 HTTP）。</p>
  </li>
</ol>

<h3 id="人机协作工作流计划驱动">人机协作工作流（计划驱动）</h3>

<pre><code class="language-mermaid">flowchart TD
  PL[迁移计划与契约清单&lt;br/&gt;Java 路径 / Redis 前缀 / 路由]
  ISS[拆解为可执行 Issue 与 Prompt]
  AI[AI 起草实现与重构]
  PR[PR 与自动化测试]
  PL --&gt; ISS
  ISS --&gt; AI
  AI --&gt; PR
  PR --&gt; RV{评审与对照现网}
  RV --&gt;|需纠偏| ISS
  RV --&gt;|通过| MAIN[合并 main]
</code></pre>

<h2 id="github-ci-与基础设施e2e-分两层做">GitHub CI 与基础设施：E2E 分两层做</h2>

<ul>
  <li>
    <p><strong>默认 CI（每次 PR）</strong><br />
Docker services 起 Postgres + Redis，迁移到最新 schema，跑 <strong><code class="language-plaintext highlighter-rouge">ts_e2e_http</code></strong>。成本可控、反馈快，适合防止「改 handler 把契约改断」。</p>
  </li>
  <li>
    <p><strong>AuthCore 联调（周更 / 手动）</strong><br />
需要第二套 PG（UC 库）、UC 的 <code class="language-plaintext highlighter-rouge">diesel</code> + fixture SQL、以及 release 级 <strong>htyuc</strong> 进程；测试用例标成 <strong><code class="language-plaintext highlighter-rouge">#[ignore]</code></strong>，只在专门 workflow 或本地脚本里加 <code class="language-plaintext highlighter-rouge">--ignored</code> 跑。这样<strong>不把重依赖强加给每个贡献者</strong>，又能在主干上周期性验证「HTYTS + HTYUC + 同一 <code class="language-plaintext highlighter-rouge">JWT_KEY</code>」这条真实链路。</p>
  </li>
  <li>
    <p><strong>本地 Docker Compose</strong><br />
与 CI 同源的思路：compose 只负责<strong>基础设施</strong>，业务进程（htyuc、Rust 测试）仍在宿主机用 cargo 跑，便于调试日志与 attach；脚本把环境变量、端口、以及 UC 启动条件写死成可重复的一步。</p>
  </li>
</ul>

<h3 id="ci-与-e2e轻量-pr-与重联调分流">CI 与 E2E：轻量 PR 与重联调分流</h3>

<pre><code class="language-mermaid">flowchart TB
  subgraph every_pr [每次 PR / push — rust-ts.yml]
    E1[GitHub Actions job]
    E2[Service: Postgres + Redis]
    E3[diesel_cli + migration run]
    E4["cargo test ts_e2e_http"]
    E1 --&gt; E2 --&gt; E3 --&gt; E4
  end

  subgraph weekly [周更或手动 — htyts-authcore-weekly.yml]
    W1[checkout huiwing + AuthCore]
    W2[双 Postgres + Redis]
    W3[UC migrate + fixture SQL]
    W4[build htyuc release + 启动]
    W5["cargo test ts_e2e_authcore_http -- --ignored"]
    W1 --&gt; W2 --&gt; W3 --&gt; W4 --&gt; W5
  end

  subgraph docker_local [本地复现]
    D1[docker-compose.authcore-e2e]
    D2[run-authcore-e2e-docker.sh]
    D1 --&gt; D2
  end

  every_pr -.-&gt;|快速回归契约| MR[合并信心]
  weekly -.-&gt;|真实 UC verify 链路| MR
  docker_local -.-&gt;|与 CI 同构调试| MR
</code></pre>

<h3 id="联调链sudo-校验走-htyuc概念">联调链：sudo 校验走 HTYUC（概念）</h3>

<pre><code class="language-mermaid">sequenceDiagram
  participant C as 客户端
  participant TS as htyts
  participant R as TS Redis 缓存
  participant UC as htyuc

  C-&gt;&gt;TS: create_task + HtySudoerToken
  alt 缓存未命中且 TOKEN_VERIFY=true
    TS-&gt;&gt;UC: POST verify_jwt_token
    UC-&gt;&gt;UC: JWT 与 UC Redis 一致则 r=true
    UC--&gt;&gt;TS: HtyResponse
    TS-&gt;&gt;R: 写入 sudo 缓存
  else 缓存命中
    TS-&gt;&gt;R: 读 TS_SUDO_T
  end
  TS--&gt;&gt;C: 201 / 业务响应
</code></pre>

<h2 id="小结">小结</h2>

<p>这一轮迁移的本质是：<strong>用一份明确的迁移计划约束 AI 与人工的分工</strong>，用 <strong>Diesel + CI 迁移步骤</strong>约束 schema 与运行时一致，再用 <strong>分层 E2E（轻量每次跑、重联调周期跑 + 本地 compose）</strong> 把「像 Java 一样能跑」变成可重复验证的事实。若你也在做「Java 服务 → Rust + 现网契约不变」，最值得提前投资的往往是<strong>契约文档与 CI 里的数据库/迁移</strong>，其次才是具体某一层的代码行数。</p>

<hr />

<p><em>仓库：<code class="language-plaintext highlighter-rouge">alchemy-studio/huiwing</code>；开源基础设施：<code class="language-plaintext highlighter-rouge">alchemy-studio/AuthCore</code>。文中涉及的 PR、workflow 与脚本以仓库当前 <code class="language-plaintext highlighter-rouge">main</code> 为准。</em></p>]]></content><author><name>阿男</name></author><category term="rust" /><category term="ai" /><summary type="html"><![CDATA[记录从 Java task_server 迁移到 Rust 的过程，以及 AI 辅助开发与 GitHub CI E2E 如何兜住质量。]]></summary></entry><entry><title type="html">__stack_chk_guard 深入解析：原理、示例与 musl/glibc 代码路径</title><link href="https://weinan.tech/2026/03/21/stack-chk-guard-musl-glibc.html" rel="alternate" type="text/html" title="__stack_chk_guard 深入解析：原理、示例与 musl/glibc 代码路径" /><published>2026-03-21T00:00:00+08:00</published><updated>2026-03-21T00:00:00+08:00</updated><id>https://weinan.tech/2026/03/21/stack-chk-guard-musl-glibc</id><content type="html" xml:base="https://weinan.tech/2026/03/21/stack-chk-guard-musl-glibc.html"><![CDATA[<p><code class="language-plaintext highlighter-rouge">__stack_chk_guard</code> 是 GCC/Clang 栈保护（SSP, Stack Smashing Protector）机制中的核心变量之一。很多人在反汇编里见过它，但常见误解是：它是不是内核变量、什么时候初始化、为什么能拦截栈溢出。本文基于一个具体示例和运行时实现路径，把这些问题串起来说明。</p>

<h2 id="一概念说明__stack_chk_guard-是什么">一、概念说明：<code class="language-plaintext highlighter-rouge">__stack_chk_guard</code> 是什么</h2>

<p><code class="language-plaintext highlighter-rouge">__stack_chk_guard</code> 本质上是 canary（金丝雀）参考值。编译器在函数入口和出口自动插入检查逻辑：</p>

<ol>
  <li>函数入口：把 guard 值保存到当前函数栈帧。</li>
  <li>函数返回前：比较栈中的副本和原始 guard。</li>
  <li>不一致：调用 <code class="language-plaintext highlighter-rouge">__stack_chk_fail()</code>，进程立即终止。</li>
</ol>

<p>这个机制的安全意义是：攻击者若想覆盖返回地址，通常必须先破坏 canary，而 canary 一旦被改写，函数返回前就会被检测出来。</p>

<h2 id="二谁在做这件事编译器与-c-库分工">二、谁在做这件事：编译器与 C 库分工</h2>

<p>栈保护不是单一组件完成，而是协同机制：</p>

<ul>
  <li>编译器（GCC/Clang）：负责插桩，自动生成“保存 canary / 校验 canary”代码。</li>
  <li>libc（musl/glibc）：负责初始化 guard，并提供失败处理函数 <code class="language-plaintext highlighter-rouge">__stack_chk_fail</code>。</li>
</ul>

<p>所以需要明确：<code class="language-plaintext highlighter-rouge">__stack_chk_guard</code> 变量本体属于用户态运行时，不是“内核维护的全局变量”。内核通常只在进程启动时通过 <code class="language-plaintext highlighter-rouge">AT_RANDOM</code> 等渠道提供随机熵。</p>

<h2 id="三具体例子一个会溢出的登录函数">三、具体例子：一个会溢出的登录函数</h2>

<p>下面是一个最小示例（故意保留不安全写法）：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;string.h&gt;</span><span class="cp">
</span>
<span class="kt">void</span> <span class="nf">login</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">password</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">char</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>
    <span class="kt">int</span> <span class="n">is_admin</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

    <span class="n">strcpy</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="n">password</span><span class="p">);</span>  <span class="c1">// 无边界检查，存在溢出风险</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="s">"secret"</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">is_admin</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">puts</span><span class="p">(</span><span class="n">is_admin</span> <span class="o">?</span> <span class="s">"welcome admin"</span> <span class="o">:</span> <span class="s">"bad password"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="31-不启用栈保护时">3.1 不启用栈保护时</h3>

<p>如果输入超过 <code class="language-plaintext highlighter-rouge">buffer</code> 容量，溢出会继续覆盖相邻栈数据，严重时可改写返回地址，形成控制流劫持入口。</p>

<h3 id="32-启用栈保护后">3.2 启用栈保护后</h3>

<p>编译器会在 <code class="language-plaintext highlighter-rouge">login</code> 的序言保存 canary，在尾声做比较。如果输入过长导致 canary 被覆盖，返回前触发失败处理，进程中止。典型编译方式：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcc <span class="nt">-O2</span> <span class="nt">-fstack-protector</span> <span class="nt">-o</span> demo demo.c
</code></pre></div></div>

<p>更激进版本：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcc <span class="nt">-O2</span> <span class="nt">-fstack-protector-all</span> <span class="nt">-o</span> demo demo.c
</code></pre></div></div>

<p>典型失败输出（glibc 环境常见）：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>*** stack smashing detected ***: terminated
Aborted (core dumped)
</code></pre></div></div>

<h3 id="33-简单场景说明按输入长度看行为">3.3 简单场景说明（按输入长度看行为）</h3>

<p>这个例子可以直接用三种输入理解：</p>

<ol>
  <li>输入 <code class="language-plaintext highlighter-rouge">secret</code>（6 字节）<br />
<code class="language-plaintext highlighter-rouge">buffer[8]</code> 能完整容纳，不发生溢出，canary 不变，函数正常返回。</li>
  <li>输入 <code class="language-plaintext highlighter-rouge">12345678</code>（8 字节）<br />
刚好写满缓冲区边界，通常也不会覆盖 canary，函数正常返回。</li>
  <li>输入 <code class="language-plaintext highlighter-rouge">123456789</code>（9 字节及以上）<br />
超出缓冲区后继续向后写，极易覆盖 canary；函数尾声比较失败，调用 <code class="language-plaintext highlighter-rouge">__stack_chk_fail</code> 终止进程。</li>
</ol>

<p>对应内存上的直觉是：想碰到返回地址，先要经过 canary 槽位；canary 先变，程序就先终止。</p>

<h2 id="四代码分析从汇编模式到运行时路径">四、代码分析：从汇编模式到运行时路径</h2>

<p>不同架构指令细节不同，但总体结构一致。可抽象为：</p>

<pre><code class="language-asm">; 函数入口
load guard -&gt; reg
store reg -&gt; [stack_canary_slot]

; ... 函数主体 ...

; 函数返回前
load [stack_canary_slot] -&gt; reg1
load guard -&gt; reg2
cmp reg1, reg2
jne __stack_chk_fail
ret
</code></pre>

<p>这解释了为什么该机制能拦住大量“覆盖返回地址”的经典栈溢出：覆盖路径上必须先穿过 canary 槽位。</p>

<h2 id="五__stack_chk_guard-何时会变化">五、<code class="language-plaintext highlighter-rouge">__stack_chk_guard</code> 何时会变化</h2>

<p>正常情况下，guard 在进程启动早期初始化一次，随后应保持稳定。运行中发现 guard 改变，通常意味着以下之一：</p>

<ul>
  <li>发生了严重内存破坏（例如任意地址写、全局区越界）。</li>
  <li>调试或安全研究场景下被人工改写（如 GDB、注入库）。</li>
  <li>程序自身存在未定义行为导致误写。</li>
</ul>

<p>因此，“运行时 guard 变化”是高度可疑信号，不应视为正常现象。</p>

<h2 id="六musl-c-代码说明定义初始化线程传播失败路径">六、musl C 代码说明（定义、初始化、线程传播、失败路径）</h2>

<p>下面用你分析中对应的 musl 代码路径来说明关键点。</p>

<h3 id="61-__stack_chk_guard-定义与-__init_ssp-初始化">6.1 <code class="language-plaintext highlighter-rouge">__stack_chk_guard</code> 定义与 <code class="language-plaintext highlighter-rouge">__init_ssp</code> 初始化</h3>

<p><code class="language-plaintext highlighter-rouge">src/env/__stack_chk_fail.c</code>（简化）：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">uintptr_t</span> <span class="n">__stack_chk_guard</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">__init_ssp</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">entropy</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">entropy</span><span class="p">)</span> <span class="n">memcpy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">__stack_chk_guard</span><span class="p">,</span> <span class="n">entropy</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">uintptr_t</span><span class="p">));</span>
    <span class="k">else</span> <span class="n">__stack_chk_guard</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uintptr_t</span><span class="p">)</span><span class="o">&amp;</span><span class="n">__stack_chk_guard</span> <span class="o">*</span> <span class="mi">1103515245</span><span class="p">;</span>

<span class="cp">#if UINTPTR_MAX &gt;= 0xffffffffffffffff
</span>    <span class="p">((</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">__stack_chk_guard</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="cp">#endif
</span>
    <span class="n">__pthread_self</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">canary</span> <span class="o">=</span> <span class="n">__stack_chk_guard</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这段代码表达了四件事：</p>

<ol>
  <li>guard 是用户态全局变量（<code class="language-plaintext highlighter-rouge">uintptr_t __stack_chk_guard;</code>）。</li>
  <li>优先使用外部熵（<code class="language-plaintext highlighter-rouge">entropy</code>，通常来自 <code class="language-plaintext highlighter-rouge">AT_RANDOM</code>）。</li>
  <li>无熵时使用兜底值（可用但强度较弱）。</li>
  <li>初始化后同步到当前线程的 <code class="language-plaintext highlighter-rouge">canary</code> 字段，供线程上下文中的检查路径使用。</li>
</ol>

<h3 id="62-进程启动阶段如何把-at_random-传给-__init_ssp">6.2 进程启动阶段如何把 <code class="language-plaintext highlighter-rouge">AT_RANDOM</code> 传给 <code class="language-plaintext highlighter-rouge">__init_ssp</code></h3>

<p><code class="language-plaintext highlighter-rouge">src/env/__libc_start_main.c</code>（简化）：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">__init_libc</span><span class="p">(</span><span class="kt">char</span> <span class="o">**</span><span class="n">envp</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">pn</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">size_t</span> <span class="n">i</span><span class="p">,</span> <span class="o">*</span><span class="n">auxv</span><span class="p">,</span> <span class="n">aux</span><span class="p">[</span><span class="n">AUX_CNT</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span> <span class="mi">0</span> <span class="p">};</span>
    <span class="p">...</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="n">auxv</span><span class="p">[</span><span class="n">i</span><span class="p">];</span> <span class="n">i</span><span class="o">+=</span><span class="mi">2</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">auxv</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">AUX_CNT</span><span class="p">)</span> <span class="n">aux</span><span class="p">[</span><span class="n">auxv</span><span class="p">[</span><span class="n">i</span><span class="p">]]</span> <span class="o">=</span> <span class="n">auxv</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">];</span>
    <span class="p">...</span>
    <span class="n">__init_tls</span><span class="p">(</span><span class="n">aux</span><span class="p">);</span>
    <span class="n">__init_ssp</span><span class="p">((</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">aux</span><span class="p">[</span><span class="n">AT_RANDOM</span><span class="p">]);</span>
    <span class="p">...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这里的关键是：在 <code class="language-plaintext highlighter-rouge">main</code> 执行前，musl 已完成 guard 初始化。<br />
所以业务代码进入前，SSP 依赖的数据已就绪。</p>

<h3 id="63-线程结构与新线程-canary-继承">6.3 线程结构与新线程 canary 继承</h3>

<p>线程结构中有 canary 字段（<code class="language-plaintext highlighter-rouge">src/internal/pthread_impl.h</code>）：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">pthread</span> <span class="p">{</span>
    <span class="p">...</span>
    <span class="kt">uintptr_t</span> <span class="n">canary</span><span class="p">;</span>
    <span class="p">...</span>
<span class="p">};</span>
</code></pre></div></div>

<p>线程创建时复制父线程 canary（<code class="language-plaintext highlighter-rouge">src/thread/pthread_create.c</code>）：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">new</span><span class="o">-&gt;</span><span class="n">canary</span> <span class="o">=</span> <span class="n">self</span><span class="o">-&gt;</span><span class="n">canary</span><span class="p">;</span>
</code></pre></div></div>

<p>这保证了多线程下 canary 数据在运行时结构里是一致可用的。</p>

<h3 id="64-校验失败后的处理__stack_chk_fail">6.4 校验失败后的处理：<code class="language-plaintext highlighter-rouge">__stack_chk_fail</code></h3>

<p><code class="language-plaintext highlighter-rouge">src/env/__stack_chk_fail.c</code>（简化）：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">__stack_chk_fail</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">a_crash</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>musl 的失败路径非常短：直接崩溃退出，不尝试恢复。<br />
这是典型 fail-fast 策略，避免在“栈已损坏”状态继续执行复杂逻辑。</p>

<h3 id="65-musl-这一套实现的工程特征">6.5 musl 这一套实现的工程特征</h3>

<ul>
  <li>启动期初始化清晰：<code class="language-plaintext highlighter-rouge">__init_libc -&gt; __init_ssp</code>。</li>
  <li>线程传播路径直接：当前线程写入 + 新线程继承。</li>
  <li>失败处理最小化：<code class="language-plaintext highlighter-rouge">a_crash()</code> 终止，降低攻击面。</li>
</ul>

<h2 id="七glibc-对照同目标不同工程风格">七、glibc 对照：同目标，不同工程风格</h2>

<p>glibc 与 musl 在核心目标上一致：都通过 canary 检测栈破坏并 fail-fast。差异更多体现在工程层面：</p>

<ul>
  <li>平台适配路径更复杂；</li>
  <li>错误提示通常更显式（常见 <code class="language-plaintext highlighter-rouge">stack smashing detected</code>）；</li>
  <li>失败处理同样尽量克制，避免依赖过多复杂运行时状态。</li>
</ul>

<h2 id="八边界与局限它不是万能防护">八、边界与局限：它不是万能防护</h2>

<p><code class="language-plaintext highlighter-rouge">__stack_chk_guard</code> 很重要，但能力边界也要明确：</p>

<ul>
  <li>主要针对栈上的典型覆盖路径；</li>
  <li>对堆溢出、信息泄露、UAF、逻辑漏洞不直接提供完整防护；</li>
  <li>需要和 ASLR、NX、RELRO、FORTIFY_SOURCE 等机制组合使用；</li>
  <li>也不能替代安全编码（边界检查、避免危险 API、最小权限设计）。</li>
</ul>

<h2 id="九实践建议">九、实践建议</h2>

<ol>
  <li>在构建系统中默认启用 <code class="language-plaintext highlighter-rouge">-fstack-protector-strong</code>（或更强策略）。</li>
  <li>同时启用 PIE、RELRO、NX 和 FORTIFY_SOURCE。</li>
  <li>优先替换高风险 API（如 <code class="language-plaintext highlighter-rouge">strcpy</code>, <code class="language-plaintext highlighter-rouge">sprintf</code>, <code class="language-plaintext highlighter-rouge">gets</code>）。</li>
  <li>将 canary 视为“最后一道完整性检查”，而非唯一安全策略。</li>
</ol>

<h2 id="十结论">十、结论</h2>

<p><code class="language-plaintext highlighter-rouge">__stack_chk_guard</code> 的价值可以概括为一句话：<br />
它通过“函数级栈完整性校验”把很多本可沉默成功的栈覆盖攻击，转化为可检测、可终止的失败路径。</p>

<p>从机制到实现，无论是 musl 还是 glibc，本质都遵循同一个原则：在控制流可信度下降时，尽快停止执行，避免把漏洞升级为可利用攻击。</p>]]></content><author><name>阿男</name></author><category term="linux-kernel" /><summary type="html"><![CDATA[深入 __stack_chk_guard 的原理与示例，并对照 musl/glibc 中的相关代码路径。]]></summary></entry><entry><title type="html">用户栈溢出与缺页：内核如何扩展栈与触发 SIGSEGV</title><link href="https://weinan.tech/2026/03/18/stack-overflow-page-faults-benchmark.html" rel="alternate" type="text/html" title="用户栈溢出与缺页：内核如何扩展栈与触发 SIGSEGV" /><published>2026-03-18T00:00:00+08:00</published><updated>2026-03-18T00:00:00+08:00</updated><id>https://weinan.tech/2026/03/18/stack-overflow-page-faults-benchmark</id><content type="html" xml:base="https://weinan.tech/2026/03/18/stack-overflow-page-faults-benchmark.html"><![CDATA[<style>
/* Mermaid diagram container */
.mermaid-container {
  position: relative;
  display: block;
  cursor: pointer;
  transition: opacity 0.2s;
  max-width: 100%;
  margin: 20px 0;
  overflow-x: auto;
}

.mermaid-container:hover {
  opacity: 0.8;
}

.mermaid-container svg {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Modal overlay */
.mermaid-modal {
  display: none;
  position: fixed;
  z-index: 9999;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9);
  animation: fadeIn 0.3s;
}

.mermaid-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Modal content */
.mermaid-modal-content {
  position: relative;
  width: 90vw;
  height: 90vh;
  overflow: hidden;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
}

.mermaid-modal-diagram {
  transform-origin: center center;
  transition: transform 0.2s ease;
  display: inline-block;
  min-width: 100%;
  cursor: grab;
  user-select: none;
}

.mermaid-modal-diagram.dragging {
  cursor: grabbing;
  transition: none;
}

.mermaid-modal-diagram svg {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;
}

/* Control buttons */
.mermaid-controls {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
  z-index: 10000;
}

.mermaid-btn {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
  color: #333;
  font-weight: 500;
}

.mermaid-btn:hover {
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Close button */
.mermaid-close {
  background: #f44336;
  color: white;
  border: none;
}

.mermaid-close:hover {
  background: #d32f2f;
}

/* Zoom indicator */
.mermaid-zoom-level {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 10000;
}
</style>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  mermaid.initialize({
    startOnLoad: false,
    theme: 'default',
    securityLevel: 'loose',
    htmlLabels: true,
    themeVariables: {
      fontSize: '14px'
    }
  });

  let currentZoom = 1;
  let currentModal = null;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let translateX = 0;
  let translateY = 0;

  // Create modal HTML
  function createModal() {
    const modal = document.createElement('div');
    modal.className = 'mermaid-modal';
    modal.innerHTML = `
      <div class="mermaid-controls">
        <button class="mermaid-btn zoom-in">放大 +</button>
        <button class="mermaid-btn zoom-out">缩小 -</button>
        <button class="mermaid-btn zoom-reset">重置</button>
        <button class="mermaid-btn mermaid-close">关闭 ✕</button>
      </div>
      <div class="mermaid-modal-content">
        <div class="mermaid-modal-diagram"></div>
      </div>
      <div class="mermaid-zoom-level">100%</div>
    `;
    document.body.appendChild(modal);
    return modal;
  }

  // Show modal with diagram
  function showModal(diagramContent) {
    if (!currentModal) {
      currentModal = createModal();
      setupModalEvents();
    }

    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.innerHTML = diagramContent;

    // Remove any width/height attributes from SVG to make it responsive
    const svg = modalDiagram.querySelector('svg');
    if (svg) {
      svg.removeAttribute('width');
      svg.removeAttribute('height');
      svg.style.width = '100%';
      svg.style.height = 'auto';
    }

    // Setup drag functionality
    setupDrag(modalDiagram);

    currentModal.classList.add('active');
    currentZoom = 1;
    resetPosition();
    updateZoom();
    document.body.style.overflow = 'hidden';
  }

  // Hide modal
  function hideModal() {
    if (currentModal) {
      currentModal.classList.remove('active');
      document.body.style.overflow = '';
    }
  }

  // Update zoom level
  function updateZoom() {
    if (!currentModal) return;
    const diagram = currentModal.querySelector('.mermaid-modal-diagram');
    const zoomLevel = currentModal.querySelector('.mermaid-zoom-level');
    diagram.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentZoom})`;
    zoomLevel.textContent = `${Math.round(currentZoom * 100)}%`;
  }

  // Reset position when zoom changes
  function resetPosition() {
    translateX = 0;
    translateY = 0;
  }

  // Setup drag functionality
  function setupDrag(element) {
    element.addEventListener('mousedown', startDrag);
    element.addEventListener('touchstart', startDrag);
  }

  function startDrag(e) {
    if (e.type === 'mousedown' && e.button !== 0) return; // Only left click

    isDragging = true;
    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.classList.add('dragging');

    if (e.type === 'touchstart') {
      startX = e.touches[0].clientX - translateX;
      startY = e.touches[0].clientY - translateY;
    } else {
      startX = e.clientX - translateX;
      startY = e.clientY - translateY;
    }

    document.addEventListener('mousemove', drag);
    document.addEventListener('touchmove', drag);
    document.addEventListener('mouseup', stopDrag);
    document.addEventListener('touchend', stopDrag);
  }

  function drag(e) {
    if (!isDragging) return;
    e.preventDefault();

    if (e.type === 'touchmove') {
      translateX = e.touches[0].clientX - startX;
      translateY = e.touches[0].clientY - startY;
    } else {
      translateX = e.clientX - startX;
      translateY = e.clientY - startY;
    }

    updateZoom();
  }

  function stopDrag() {
    isDragging = false;
    const modalDiagram = currentModal?.querySelector('.mermaid-modal-diagram');
    if (modalDiagram) {
      modalDiagram.classList.remove('dragging');
    }
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('touchmove', drag);
    document.removeEventListener('mouseup', stopDrag);
    document.removeEventListener('touchend', stopDrag);
  }

  // Setup modal event listeners
  function setupModalEvents() {
    if (!currentModal) return;

    // Close button
    currentModal.querySelector('.mermaid-close').addEventListener('click', hideModal);

    // Zoom buttons
    currentModal.querySelector('.zoom-in').addEventListener('click', () => {
      currentZoom = Math.min(currentZoom + 0.25, 3);
      updateZoom();
    });

    currentModal.querySelector('.zoom-out').addEventListener('click', () => {
      currentZoom = Math.max(currentZoom - 0.25, 0.5);
      updateZoom();
    });

    currentModal.querySelector('.zoom-reset').addEventListener('click', () => {
      currentZoom = 1;
      resetPosition();
      updateZoom();
    });

    // Close on background click
    currentModal.addEventListener('click', (e) => {
      if (e.target === currentModal) {
        hideModal();
      }
    });

    // Close on ESC key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && currentModal.classList.contains('active')) {
        hideModal();
      }
    });
  }

  // Convert Jekyll-rendered code blocks to mermaid divs
  document.addEventListener('DOMContentLoaded', async function() {
    const codeBlocks = document.querySelectorAll('code.language-mermaid');

    for (const codeBlock of codeBlocks) {
      const pre = codeBlock.parentElement;
      const container = document.createElement('div');
      container.className = 'mermaid-container';

      const mermaidDiv = document.createElement('div');
      mermaidDiv.className = 'mermaid';
      mermaidDiv.textContent = codeBlock.textContent;

      container.appendChild(mermaidDiv);
      pre.replaceWith(container);
    }

    // Render all mermaid diagrams
    try {
      await mermaid.run({
        querySelector: '.mermaid'
      });
      console.log('Mermaid diagrams rendered successfully');
    } catch (error) {
      console.error('Mermaid rendering error:', error);
    }

    // Add click handlers to rendered diagrams
    document.querySelectorAll('.mermaid-container').forEach((container, index) => {
      // Find the rendered SVG inside the container
      const svg = container.querySelector('svg');
      if (!svg) {
        console.warn(`No SVG found in container ${index}`);
        return;
      }

      // Make the container clickable
      container.style.cursor = 'pointer';
      container.title = '点击查看大图';

      container.addEventListener('click', function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Clone the SVG for the modal
        const svgClone = svg.cloneNode(true);
        const tempDiv = document.createElement('div');
        tempDiv.appendChild(svgClone);

        console.log('Opening modal for diagram', index);
        showModal(tempDiv.innerHTML);
      });

      console.log(`Click handler added to diagram ${index}`);
    });
  });
</script>

<p>用户态栈空间有限（如 <code class="language-plaintext highlighter-rouge">ulimit -s</code> 的 8MB），访问栈底之外的地址会触发缺页；内核在缺页处理中决定是<strong>扩展栈</strong>（分配新页）还是<strong>拒绝访问</strong>（发 SIGSEGV）。本文从内核视角说明：用户栈在 Linux 里如何表示、缺页时栈如何向下扩展、为何会触顶溢出，以及缺页次数、页缓存与架构（如 ARM64 页大小）对现象的影响。文内引用内核源码路径与片段均对应本地树 <code class="language-plaintext highlighter-rouge">linux/</code>（如 <code class="language-plaintext highlighter-rouge">/Users/weli/works/linux</code>），便于对照阅读。</p>

<h2 id="一现象与问题">一、现象与问题</h2>

<p>一个常见现象：用 <code class="language-plaintext highlighter-rouge">perf stat -e page-faults</code> 跑一个「不断向栈下增长直到崩溃」的程序，可能只看到<strong>几百次缺页</strong>就发生 SIGSEGV，而栈已使用数 MB。会自然产生两个问题：</p>

<ol>
  <li>为什么「这么少」的缺页就会栈溢出？</li>
  <li>缺页次数在不同运行、不同架构下为何差异很大（例如 x86-64 第二次运行明显减少，ARM64 首次就很少）？</li>
</ol>

<p>下面用内核机制统一解释，并用 <a href="https://github.com/liweinan/stack-vs-heap-benchmark">stack-vs-heap-benchmark</a> 中的 <code class="language-plaintext highlighter-rouge">stack_overflow_test crash</code> 作为可复现的样例（非论述主体）。</p>

<h2 id="二用户栈在内核中的表示">二、用户栈在内核中的表示</h2>

<p>用户栈对应一个<strong>向下增长</strong>的 VMA（<code class="language-plaintext highlighter-rouge">struct vm_area_struct</code>），由 <code class="language-plaintext highlighter-rouge">VM_GROWSDOWN</code> 标记。</p>

<ul>
  <li><strong>栈顶</strong>：高地址，由用户态 SP 指向；初始栈顶由 loader/内核在 exec 时设定，并受 <code class="language-plaintext highlighter-rouge">arch_pick_mmap_layout()</code> 等影响，会为栈预留空间并留出 <strong>stack guard gap</strong>。</li>
  <li><strong>栈底（当前）</strong>：即该 VMA 的 <code class="language-plaintext highlighter-rouge">vm_start</code>（低地址）；栈「向下长」即 <code class="language-plaintext highlighter-rouge">vm_start</code> 变小，VMA 向低地址扩展。</li>
  <li><strong>栈大小限制</strong>：由 <code class="language-plaintext highlighter-rouge">RLIMIT_STACK</code>（<code class="language-plaintext highlighter-rouge">ulimit -s</code>）提供，内核在<strong>扩展栈</strong>时用该限制做检查，超过则拒绝扩展并导致本次缺页处理失败，进而向用户态发 SIGSEGV。</li>
</ul>

<p>栈与其它映射之间保留的间隔由全局变量 <code class="language-plaintext highlighter-rouge">stack_guard_gap</code> 控制（默认 256 页，即 4KB 页下 1MB）：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// mm/mmap.c</span>
<span class="cm">/* enforced gap between the expanding stack and other mappings. */</span>
<span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">stack_guard_gap</span> <span class="o">=</span> <span class="mi">256UL</span><span class="o">&lt;&lt;</span><span class="n">PAGE_SHIFT</span><span class="p">;</span>
</code></pre></div></div>

<p>因此：<strong>栈溢出</strong>在内核侧的语义是——缺页发生在当前栈 VMA 的 <code class="language-plaintext highlighter-rouge">vm_start</code> 之下，且要么扩展会超过 <code class="language-plaintext highlighter-rouge">RLIMIT_STACK</code>，要么会侵入 <code class="language-plaintext highlighter-rouge">stack_guard_gap</code> 或其它映射，从而不允许扩展，只能返回错误并让上层发 SIGSEGV。</p>

<h2 id="三补充mm_struct--task_struct--vm_area_struct-的关系校对到当前内核">三、补充：<code class="language-plaintext highlighter-rouge">mm_struct</code> / <code class="language-plaintext highlighter-rouge">task_struct</code> / <code class="language-plaintext highlighter-rouge">vm_area_struct</code> 的关系（校对到当前内核）</h2>

<p>为避免把旧资料中的字段名带入本文，这里先给出与当前内核（<code class="language-plaintext highlighter-rouge">/Users/weli/works/linux</code>）一致的结构关系。你在阅读后续缺页与栈扩展路径时，可以把这张图当作“对象关系索引”。</p>

<pre><code class="language-mermaid">graph TB
    subgraph TASK[任务层]
      T[task_struct]
      TMM[mm]
      TAMM[active_mm]
    end

    subgraph MM[地址空间层]
      M[mm_struct]
      MMT[mm_mt\nMaple Tree of VMA]
      MPGD[pgd\npage table root]
      MLOCK[mmap_lock]
      MCOUNT[mm_users / mm_count]
      MSTAT[total_vm locked_vm stack_vm ...]
      MBOUND[start_code start_brk brk start_stack ...]
    end

    subgraph VMA[VMA层]
      V[vm_area_struct]
      VADDR[vm_start .. vm_end]
      VFLAGS[vm_flags]
      VFILE[vm_file / anon_vma]
    end

    subgraph PT[页表层 x86_64]
      PGD[PGD]
      P4D[P4D]
      PUD[PUD]
      PMD[PMD]
      PTE[PTE]
      PF[Page Frame]
    end

    T --&gt; TMM --&gt; M
    T --&gt; TAMM --&gt; M

    M --&gt; MMT --&gt; V
    M --&gt; MPGD --&gt; PGD
    M --&gt; MLOCK
    M --&gt; MCOUNT
    M --&gt; MSTAT
    M --&gt; MBOUND

    V --&gt; VADDR
    V --&gt; VFLAGS
    V --&gt; VFILE

    PGD --&gt; P4D --&gt; PUD --&gt; PMD --&gt; PTE --&gt; PF
    V -. address validity / permission .-&gt; PTE
</code></pre>

<h3 id="本图对应的关键校对点">本图对应的关键校对点</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">mm_struct</code> 当前主组织结构是 <strong><code class="language-plaintext highlighter-rouge">mm_mt</code>（Maple Tree）</strong>，不是旧口径里的 <code class="language-plaintext highlighter-rouge">mmap + mm_rb</code>。</li>
  <li><code class="language-plaintext highlighter-rouge">mm_struct</code> 的 VMA 锁字段是 <strong><code class="language-plaintext highlighter-rouge">mmap_lock</code></strong>，不是 <code class="language-plaintext highlighter-rouge">mmap_sem</code>。</li>
  <li><code class="language-plaintext highlighter-rouge">task_struct</code> 里 <code class="language-plaintext highlighter-rouge">mm</code> / <code class="language-plaintext highlighter-rouge">active_mm</code> 的关系与经典描述一致。</li>
  <li>缺页建立映射时，VMA 负责“地址区间与权限语义”，页表负责“虚拟地址到物理页”的具体映射。</li>
</ul>

<h3 id="核心结构体定义文件对照阅读">核心结构体定义文件（对照阅读）</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">struct mm_struct</code>：<code class="language-plaintext highlighter-rouge">include/linux/mm_types.h</code></li>
  <li><code class="language-plaintext highlighter-rouge">struct vm_area_struct</code>：<code class="language-plaintext highlighter-rouge">include/linux/mm_types.h</code></li>
  <li><code class="language-plaintext highlighter-rouge">struct task_struct</code>：<code class="language-plaintext highlighter-rouge">include/linux/sched.h</code></li>
  <li><code class="language-plaintext highlighter-rouge">mm_struct.mm_mt</code> 的类型 <code class="language-plaintext highlighter-rouge">struct maple_tree</code>：<code class="language-plaintext highlighter-rouge">include/linux/maple_tree.h</code>
    <ul>
      <li>字段出现位置：<code class="language-plaintext highlighter-rouge">include/linux/mm_types.h</code>（<code class="language-plaintext highlighter-rouge">struct maple_tree mm_mt;</code>）</li>
      <li>Maple Tree 实现文件：<code class="language-plaintext highlighter-rouge">lib/maple_tree.c</code></li>
    </ul>
  </li>
</ul>

<h3 id="maple-tree-与-vma-的真实绑定关系结构--流程">Maple Tree 与 VMA 的真实绑定关系（结构 + 流程）</h3>

<p>上面的关系图强调了 <code class="language-plaintext highlighter-rouge">mm_mt</code> 与 VMA 的关联，这里把“结构体层面怎么存”说清楚：</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">mm_struct</code> 里持有 <code class="language-plaintext highlighter-rouge">struct maple_tree mm_mt</code>（树容器）。</li>
  <li><code class="language-plaintext highlighter-rouge">maple_tree</code> 本体（<code class="language-plaintext highlighter-rouge">struct maple_tree</code>）只有锁、flags、<code class="language-plaintext highlighter-rouge">ma_root</code> 根指针，不直接内嵌 <code class="language-plaintext highlighter-rouge">vm_area_struct</code>。</li>
  <li><code class="language-plaintext highlighter-rouge">ma_root</code> 是编码过的 <code class="language-plaintext highlighter-rouge">void *</code> 入口：
    <ul>
      <li>常见（多条目）情况：<code class="language-plaintext highlighter-rouge">ma_root -&gt; maple_node -&gt; slot[] -&gt; vma*</code></li>
      <li>单条目优化情况：<code class="language-plaintext highlighter-rouge">ma_root</code> 可直接承载条目（编码后的 <code class="language-plaintext highlighter-rouge">vma*</code>），不经过 <code class="language-plaintext highlighter-rouge">maple_node</code></li>
    </ul>
  </li>
  <li>真正的节点是 <code class="language-plaintext highlighter-rouge">struct maple_node</code>；节点里有 <code class="language-plaintext highlighter-rouge">slot[]</code>，并通过 <code class="language-plaintext highlighter-rouge">maple_range_64</code> / <code class="language-plaintext highlighter-rouge">maple_arange_64</code> 维护 <code class="language-plaintext highlighter-rouge">pivot[]</code>（地址分界）。</li>
  <li>在 mm 场景中，<code class="language-plaintext highlighter-rouge">slot[]</code> 存放的是 <code class="language-plaintext highlighter-rouge">struct vm_area_struct *</code>（以 <code class="language-plaintext highlighter-rouge">void *</code> 形式存）。</li>
</ol>

<pre><code class="language-mermaid">graph TB
  MM["mm_struct"]
  MT["mm_mt: struct maple_tree"]
  ROOT["ma_root"]
  DIRECT["direct encoded entry&lt;br/&gt;(single-entry optimization)"]
  NODE["maple_node"]
  PIV["pivot array&lt;br/&gt;地址区间边界"]
  SLOTS["slot array&lt;br/&gt;value = vma*"]
  A["VMA_A*&lt;br/&gt;vm_start..vm_end"]
  B["VMA_B*&lt;br/&gt;vm_start..vm_end"]
  C["VMA_C*&lt;br/&gt;vm_start..vm_end"]

  MM --&gt; MT --&gt; ROOT
  ROOT --&gt; NODE
  ROOT --&gt; DIRECT --&gt; A
  NODE --&gt; PIV
  NODE --&gt; SLOTS
  SLOTS --&gt; A
  SLOTS --&gt; B
  SLOTS --&gt; C
</code></pre>

<h3 id="创建绑定过程地址区间---vma-指针">创建（绑定）过程：地址区间 -&gt; VMA 指针</h3>

<p>在 <code class="language-plaintext highlighter-rouge">mm/vma.h</code> 的 <code class="language-plaintext highlighter-rouge">vma_iter_store_gfp()</code> 里，内核会：</p>

<ul>
  <li>用 <code class="language-plaintext highlighter-rouge">__mas_set_range(&amp;vmi-&gt;mas, vma-&gt;vm_start, vma-&gt;vm_end - 1)</code> 设定 key 区间；</li>
  <li>再用 <code class="language-plaintext highlighter-rouge">mas_store_gfp(&amp;vmi-&gt;mas, vma, gfp)</code> 把 <code class="language-plaintext highlighter-rouge">vma*</code> 作为 value 存入 <code class="language-plaintext highlighter-rouge">mm_mt</code>。</li>
</ul>

<p>因此绑定关系是：</p>

<ul>
  <li><strong>key</strong> = 虚拟地址范围 <code class="language-plaintext highlighter-rouge">[vm_start, vm_end)</code>（内部以 <code class="language-plaintext highlighter-rouge">start..end-1</code> 存）</li>
  <li><strong>value</strong> = <code class="language-plaintext highlighter-rouge">struct vm_area_struct *</code></li>
</ul>

<h3 id="查找过程给定地址---命中-vma">查找过程：给定地址 -&gt; 命中 VMA</h3>

<p><code class="language-plaintext highlighter-rouge">vma_iterator</code> 通过 <code class="language-plaintext highlighter-rouge">mas_init(&amp;vmi-&gt;mas, &amp;mm-&gt;mm_mt, addr)</code> 绑定到当前进程的 <code class="language-plaintext highlighter-rouge">mm_mt</code>，
随后 <code class="language-plaintext highlighter-rouge">vma_find()</code> 调 <code class="language-plaintext highlighter-rouge">mas_find()</code> 从 <code class="language-plaintext highlighter-rouge">ma_root</code> 开始查找：</p>

<ul>
  <li>若 <code class="language-plaintext highlighter-rouge">ma_root</code> 为直接条目，直接返回对应 <code class="language-plaintext highlighter-rouge">vm_area_struct *</code>；</li>
  <li>若 <code class="language-plaintext highlighter-rouge">ma_root</code> 指向节点，则按 <code class="language-plaintext highlighter-rouge">pivot</code> 导航到 <code class="language-plaintext highlighter-rouge">slot[]</code>，再返回对应 <code class="language-plaintext highlighter-rouge">vm_area_struct *</code>。</li>
</ul>

<p>这一点也解释了为什么当前内核文档口径应该写成：</p>

<ul>
  <li>“Maple Tree (<code class="language-plaintext highlighter-rouge">mm_mt</code>) 按地址区间索引 VMA 指针”</li>
</ul>

<p>而不是旧口径的“<code class="language-plaintext highlighter-rouge">mmap</code> 链表 + <code class="language-plaintext highlighter-rouge">mm_rb</code> 红黑树”主路径。</p>

<h2 id="四缺页时栈如何扩展从查-vma-到-expand_downwards">四、缺页时栈如何扩展：从查 VMA 到 expand_downwards</h2>

<p>用户访问栈上尚未映射的地址时，CPU 触发缺页异常，进入架构相关的 fault 处理（如 x86-64 的 <code class="language-plaintext highlighter-rouge">do_user_addr_fault</code>），再通过通用层查找 VMA 并决定是否扩展栈。</p>

<h3 id="41-查找-vma-与无-vma-则尝试扩展栈">4.1 查找 VMA 与「无 VMA 则尝试扩展栈」</h3>

<p>在支持 <code class="language-plaintext highlighter-rouge">CONFIG_LOCK_MM_AND_FIND_VMA</code> 的路径上（如 x86-64），会调用 <code class="language-plaintext highlighter-rouge">lock_mm_and_find_vma()</code>（<code class="language-plaintext highlighter-rouge">mm/mmap_lock.c</code>）：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// mm/mmap_lock.c (约 251–286 行)</span>
<span class="n">vma</span> <span class="o">=</span> <span class="n">find_vma</span><span class="p">(</span><span class="n">mm</span><span class="p">,</span> <span class="n">addr</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">likely</span><span class="p">(</span><span class="n">vma</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_start</span> <span class="o">&lt;=</span> <span class="n">addr</span><span class="p">)))</span>
    <span class="k">return</span> <span class="n">vma</span><span class="p">;</span>

<span class="cm">/* 地址落在某 VMA 起始之下，仅当该 VMA 是向下扩展的栈时才允许扩展 */</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">vma</span> <span class="o">||</span> <span class="o">!</span><span class="p">(</span><span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_flags</span> <span class="o">&amp;</span> <span class="n">VM_GROWSDOWN</span><span class="p">))</span> <span class="p">{</span>
    <span class="n">mmap_read_unlock</span><span class="p">(</span><span class="n">mm</span><span class="p">);</span>
    <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>   <span class="cm">/* 上层会进入 bad_area，发 SIGSEGV */</span>
<span class="p">}</span>
<span class="c1">// ...</span>
<span class="k">if</span> <span class="p">(</span><span class="n">expand_stack_locked</span><span class="p">(</span><span class="n">vma</span><span class="p">,</span> <span class="n">addr</span><span class="p">))</span>
    <span class="k">goto</span> <span class="n">fail</span><span class="p">;</span>
</code></pre></div></div>

<p>含义：若 <code class="language-plaintext highlighter-rouge">addr</code> 不在任何已有 VMA 内（或正好在栈 VMA 的 <code class="language-plaintext highlighter-rouge">vm_start</code> 之下），则只有当前「紧邻其上的」VMA 是 <code class="language-plaintext highlighter-rouge">VM_GROWSDOWN</code>（用户栈）时才尝试扩展；否则返回 NULL，缺页无法解析，最终发 SIGSEGV。</p>

<h3 id="42-expand_stack_locked--expand_downwards">4.2 expand_stack_locked → expand_downwards</h3>

<p><code class="language-plaintext highlighter-rouge">expand_stack_locked()</code> 在向下扩展的配置下（常见配置）直接调用 <code class="language-plaintext highlighter-rouge">expand_downwards()</code>（<code class="language-plaintext highlighter-rouge">mm/mmap.c</code> 与 <code class="language-plaintext highlighter-rouge">mm/vma.c</code>）：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// mm/mmap.c</span>
<span class="kt">int</span> <span class="nf">expand_stack_locked</span><span class="p">(</span><span class="k">struct</span> <span class="n">vm_area_struct</span> <span class="o">*</span><span class="n">vma</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">address</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="n">expand_downwards</span><span class="p">(</span><span class="n">vma</span><span class="p">,</span> <span class="n">address</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">expand_downwards()</code>（<code class="language-plaintext highlighter-rouge">mm/vma.c</code> 约 3024–3102 行）主要做三件事：</p>

<ol>
  <li><strong>检查 VM_GROWSDOWN</strong>，并做地址与 <code class="language-plaintext highlighter-rouge">mmap_min_addr</code> 等校验。</li>
  <li><strong>强制 stack_guard_gap</strong>：若在 <code class="language-plaintext highlighter-rouge">addr</code> 下方存在其它可访问的 VMA，且与当前栈的间距小于 <code class="language-plaintext highlighter-rouge">stack_guard_gap</code>，则拒绝扩展，返回 <code class="language-plaintext highlighter-rouge">-ENOMEM</code>。</li>
  <li><strong>在允许扩展的前提下</strong>，调用 <code class="language-plaintext highlighter-rouge">acct_stack_growth()</code> 做栈限制检查；通过则更新 VMA 的 <code class="language-plaintext highlighter-rouge">vm_start</code>（及相关结构），完成栈的向下延伸。</li>
</ol>

<h3 id="43-栈限制检查acct_stack_growth">4.3 栈限制检查：acct_stack_growth</h3>

<p>栈能扩展的「总大小」由 <code class="language-plaintext highlighter-rouge">rlimit(RLIMIT_STACK)</code> 限制（对应 <code class="language-plaintext highlighter-rouge">ulimit -s</code>）。扩展前在 <code class="language-plaintext highlighter-rouge">acct_stack_growth()</code> 中统一检查（<code class="language-plaintext highlighter-rouge">mm/vma.c</code> 约 2898–2930 行）：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// mm/vma.c</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">acct_stack_growth</span><span class="p">(</span><span class="k">struct</span> <span class="n">vm_area_struct</span> <span class="o">*</span><span class="n">vma</span><span class="p">,</span>
                             <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">size</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">grow</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">struct</span> <span class="n">mm_struct</span> <span class="o">*</span><span class="n">mm</span> <span class="o">=</span> <span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_mm</span><span class="p">;</span>
    <span class="c1">// ...</span>
    <span class="cm">/* Stack limit test */</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">size</span> <span class="o">&gt;</span> <span class="n">rlimit</span><span class="p">(</span><span class="n">RLIMIT_STACK</span><span class="p">))</span>
        <span class="k">return</span> <span class="o">-</span><span class="n">ENOMEM</span><span class="p">;</span>
    <span class="c1">// ...</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这里 <code class="language-plaintext highlighter-rouge">size</code> 是扩展后的栈 VMA 总大小。一旦「当前栈已用 + 本次要扩展」超过 <code class="language-plaintext highlighter-rouge">RLIMIT_STACK</code>，就返回 <code class="language-plaintext highlighter-rouge">-ENOMEM</code>，<code class="language-plaintext highlighter-rouge">expand_downwards()</code> 失败，缺页路径无法解析该地址，上层会进入 bad_area 并给进程发 SIGSEGV。也就是说：<strong>触顶 = 扩展被 rlimit 拒绝</strong>，而不是「多缺了一次页」本身；缺页只是触发这次检查的契机。</p>

<h3 id="44-扩展成功后匿名页分配">4.4 扩展成功后：匿名页分配</h3>

<p>扩展栈 VMA 只调整了虚拟区间（<code class="language-plaintext highlighter-rouge">vm_start</code> 下移），并未立刻分配物理页。物理页在<strong>第一次访问</strong>该新区间内的地址时，由通用缺页逻辑分配：此时 VMA 已包含该地址，<code class="language-plaintext highlighter-rouge">find_vma</code> 会命中栈 VMA，进入 <code class="language-plaintext highlighter-rouge">handle_mm_fault()</code> → <code class="language-plaintext highlighter-rouge">__handle_mm_fault()</code> → <code class="language-plaintext highlighter-rouge">handle_pte_fault()</code>，对匿名、未映射的 PTE 走 <code class="language-plaintext highlighter-rouge">do_anonymous_page()</code>（<code class="language-plaintext highlighter-rouge">mm/memory.c</code> 约 5022 行），分配匿名页并建立映射。因此：<strong>每第一次接触一个新页，产生一次缺页</strong>；栈用量由 SP 下移多少决定，缺页次数则等于「新被触及的页数」，二者相关但不等价。</p>

<h2 id="五缺页与触顶的完整路径小结">五、缺页与「触顶」的完整路径小结</h2>

<ol>
  <li><strong>用户访问</strong>栈下未映射地址 → CPU #PF。</li>
  <li><strong>arch</strong>（如 <code class="language-plaintext highlighter-rouge">arch/x86/mm/fault.c</code>）→ <code class="language-plaintext highlighter-rouge">do_user_addr_fault()</code> → <code class="language-plaintext highlighter-rouge">lock_mm_and_find_vma(mm, address, regs)</code>。</li>
  <li><strong>mm/mmap_lock.c</strong>：<code class="language-plaintext highlighter-rouge">find_vma(mm, addr)</code>；若 <code class="language-plaintext highlighter-rouge">addr</code> 在栈 VMA 之下且该 VMA 为 <code class="language-plaintext highlighter-rouge">VM_GROWSDOWN</code>，则 <code class="language-plaintext highlighter-rouge">expand_stack_locked(vma, addr)</code>。</li>
  <li><strong>mm/mmap.c</strong>：<code class="language-plaintext highlighter-rouge">expand_stack_locked()</code> → <strong>mm/vma.c</strong>：<code class="language-plaintext highlighter-rouge">expand_downwards()</code> → 检查 <code class="language-plaintext highlighter-rouge">stack_guard_gap</code>，再 <code class="language-plaintext highlighter-rouge">acct_stack_growth()</code> 检查 <code class="language-plaintext highlighter-rouge">size &gt; rlimit(RLIMIT_STACK)</code>；若通过则扩展 <code class="language-plaintext highlighter-rouge">vma-&gt;vm_start</code>。</li>
  <li>若扩展失败（rlimit 或 guard gap），<code class="language-plaintext highlighter-rouge">lock_mm_and_find_vma</code> 返回 NULL，arch 层进入 bad_area → 向用户态发 <strong>SIGSEGV</strong>。</li>
  <li>若扩展成功，返回用户态重试指令，再次访问同一地址时已落在栈 VMA 内，走正常缺页：<strong>mm/memory.c</strong> <code class="language-plaintext highlighter-rouge">handle_mm_fault()</code> → <code class="language-plaintext highlighter-rouge">__handle_mm_fault()</code> → <code class="language-plaintext highlighter-rouge">handle_pte_fault()</code> → <code class="language-plaintext highlighter-rouge">do_anonymous_page()</code>，分配物理页并建立 PTE。</li>
</ol>

<p>因此：<strong>「224 次缺页就崩溃」</strong> 表示整次进程运行共发生 224 次缺页（包括栈、代码段、库、guard 等）；<strong>最后一次（或临界一次）</strong> 是访问到了<strong>不允许扩展的区域</strong>（超过 RLIMIT_STACK 或进入 guard gap），内核拒绝扩展并发 SIGSEGV，而不是「第 224 次缺页时多分配了一页栈」。</p>

<h2 id="六页缓存物理页与缺页次数">六、页缓存、物理页与缺页次数</h2>

<ul>
  <li><strong>物理页</strong>：进程退出后，部分物理页可能仍留在系统（匿名页回收策略、文件页的 page cache）。新进程再次运行同一程序时，可能复用这些物理页，但<strong>页表是 per-process 的</strong>，进程退出后页表销毁，新进程必须重新建立虚拟地址到物理页的映射，因此仍会触发缺页。</li>
  <li><strong>栈是匿名映射</strong>：栈不对应文件，不能像文件映射那样用 (inode, offset) 做 page cache 的键。第二次运行缺页减少，主要来自<strong>代码段、共享库等文件映射</strong>的缓存命中，以及系统中仍有可复用的物理页被新进程映射；栈区本身每个进程独立，只是若系统未立刻回收，新进程可能复用刚释放的物理页，缺页数会少一些。</li>
  <li>内核的页缓存与物理页复用是<strong>全局的</strong>，不按进程或程序名区分；多进程可共享同一物理页（如代码段、共享库），体现的是「尽量共享」的设计。</li>
</ul>

<h2 id="七arm64-与页大小thp">七、ARM64 与页大小、THP</h2>

<p>在 <strong>ARM64</strong> 上，许多配置使用 <strong>16KB</strong> 甚至 <strong>64KB</strong> 的页，且可能启用透明大页（THP），同样大小的栈所需<strong>页数</strong>远少于 x86-64 的 4KB 页，因此<strong>同一次运行</strong>的缺页次数会少很多（例如首次运行就只看到约 226 次）。这与「第二次运行因缓存而减少」是不同原因：前者是<strong>架构与页大小</strong>，后者是<strong>物理页/页表复用</strong>。</p>

<h2 id="八如何复现与对照内核">八、如何复现与对照内核</h2>

<ul>
  <li>查看栈限制与页大小：<code class="language-plaintext highlighter-rouge">ulimit -s</code>、<code class="language-plaintext highlighter-rouge">getconf PAGESIZE</code>。</li>
  <li>用 <a href="https://github.com/liweinan/stack-vs-heap-benchmark">stack-vs-heap-benchmark</a> 复现：<code class="language-plaintext highlighter-rouge">make stack_overflow_test</code>，<code class="language-plaintext highlighter-rouge">perf stat -e page-faults ./stack_overflow_test crash</code>。该程序先递归消耗约 6–7MB 栈，再在剩余空间内用汇编每次 push 8KB 直到触顶，便于观察「总栈接近 8MB 时的一次失败扩展」与缺页计数的关系。</li>
  <li>对照阅读内核（以你本机路径为准，例如 <code class="language-plaintext highlighter-rouge">/Users/weli/works/linux</code>）：
    <ul>
      <li><strong>mm/mmap_lock.c</strong>：<code class="language-plaintext highlighter-rouge">lock_mm_and_find_vma()</code> 中 <code class="language-plaintext highlighter-rouge">find_vma</code> 与 <code class="language-plaintext highlighter-rouge">expand_stack_locked()</code> 的调用；</li>
      <li><strong>mm/mmap.c</strong>：<code class="language-plaintext highlighter-rouge">stack_guard_gap</code>、<code class="language-plaintext highlighter-rouge">expand_stack_locked()</code>、<code class="language-plaintext highlighter-rouge">expand_stack()</code>；</li>
      <li><strong>mm/vma.c</strong>：<code class="language-plaintext highlighter-rouge">expand_downwards()</code>、<code class="language-plaintext highlighter-rouge">acct_stack_growth()</code> 及 <code class="language-plaintext highlighter-rouge">rlimit(RLIMIT_STACK)</code> 检查；</li>
      <li><strong>mm/memory.c</strong>：<code class="language-plaintext highlighter-rouge">handle_mm_fault()</code>、<code class="language-plaintext highlighter-rouge">__handle_mm_fault()</code>、<code class="language-plaintext highlighter-rouge">do_anonymous_page()</code>；</li>
      <li><strong>arch/x86/mm/fault.c</strong>：<code class="language-plaintext highlighter-rouge">do_user_addr_fault()</code> 及对 <code class="language-plaintext highlighter-rouge">lock_mm_and_find_vma()</code> 的调用。</li>
    </ul>
  </li>
</ul>

<h2 id="九结论">九、结论</h2>

<ul>
  <li>用户栈在内核中是一个 <strong>VM_GROWSDOWN</strong> 的 VMA；扩展时受 <strong>rlimit(RLIMIT_STACK)</strong> 和 <strong>stack_guard_gap</strong> 约束。</li>
  <li>缺页时，若地址落在栈 VMA 之下，内核通过 <strong>expand_stack_locked → expand_downwards → acct_stack_growth</strong> 决定是否扩展；超过 rlimit 或违反 guard gap 则拒绝扩展，本次缺页无法解析，进程收到 <strong>SIGSEGV</strong>。</li>
  <li>缺页次数 = 本次运行中「首次触及」的页数（栈、代码、库、guard 等合计），与「栈总用量」相关但不等同；触顶由<strong>扩展被内核拒绝</strong>决定，而非缺页计数达到某个值。</li>
  <li>第二次运行缺页减少主要来自物理页/文件映射的复用；ARM64 下首次运行缺页就较少则主要来自更大页与 THP。</li>
</ul>

<p>若你希望把栈溢出、缺页与 rlimit 的结论落实到具体可跑的程序上，可用上述 benchmark 项目配合本机内核源码一起对照。</p>]]></content><author><name>阿男</name></author><category term="linux-kernel" /><summary type="html"><![CDATA[分析用户栈溢出、缺页扩展与 SIGSEGV 触发机制，并用基准实验验证栈增长行为。]]></summary></entry><entry><title type="html">OpenShift ccoctl 与 AWS STS 短期凭证：从原理到实践</title><link href="https://weinan.tech/2026/03/16/openshift-ccoctl-sts-credentials.html" rel="alternate" type="text/html" title="OpenShift ccoctl 与 AWS STS 短期凭证：从原理到实践" /><published>2026-03-16T00:00:00+08:00</published><updated>2026-03-16T00:00:00+08:00</updated><id>https://weinan.tech/2026/03/16/openshift-ccoctl-sts-credentials</id><content type="html" xml:base="https://weinan.tech/2026/03/16/openshift-ccoctl-sts-credentials.html"><![CDATA[<style>
/* Mermaid diagram container */
.mermaid-container {
  position: relative;
  display: block;
  cursor: pointer;
  transition: opacity 0.2s;
  max-width: 100%;
  margin: 20px 0;
  overflow-x: auto;
}

.mermaid-container:hover {
  opacity: 0.8;
}

.mermaid-container svg {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Modal overlay */
.mermaid-modal {
  display: none;
  position: fixed;
  z-index: 9999;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9);
  animation: fadeIn 0.3s;
}

.mermaid-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Modal content */
.mermaid-modal-content {
  position: relative;
  width: 90vw;
  height: 90vh;
  overflow: hidden;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
}

.mermaid-modal-diagram {
  transform-origin: center center;
  transition: transform 0.2s ease;
  display: inline-block;
  min-width: 100%;
  cursor: grab;
  user-select: none;
}

.mermaid-modal-diagram.dragging {
  cursor: grabbing;
  transition: none;
}

.mermaid-modal-diagram svg {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;
}

/* Control buttons */
.mermaid-controls {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
  z-index: 10000;
}

.mermaid-btn {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
  color: #333;
  font-weight: 500;
}

.mermaid-btn:hover {
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Close button */
.mermaid-close {
  background: #f44336;
  color: white;
  border: none;
}

.mermaid-close:hover {
  background: #d32f2f;
}

/* Zoom indicator */
.mermaid-zoom-level {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 10000;
}
</style>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  mermaid.initialize({
    startOnLoad: false,
    theme: 'default',
    securityLevel: 'loose',
    htmlLabels: true,
    themeVariables: {
      fontSize: '14px'
    }
  });

  let currentZoom = 1;
  let currentModal = null;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let translateX = 0;
  let translateY = 0;

  // Create modal HTML
  function createModal() {
    const modal = document.createElement('div');
    modal.className = 'mermaid-modal';
    modal.innerHTML = `
      <div class="mermaid-controls">
        <button class="mermaid-btn zoom-in">放大 +</button>
        <button class="mermaid-btn zoom-out">缩小 -</button>
        <button class="mermaid-btn zoom-reset">重置</button>
        <button class="mermaid-btn mermaid-close">关闭 ✕</button>
      </div>
      <div class="mermaid-modal-content">
        <div class="mermaid-modal-diagram"></div>
      </div>
      <div class="mermaid-zoom-level">100%</div>
    `;
    document.body.appendChild(modal);
    return modal;
  }

  // Show modal with diagram
  function showModal(diagramContent) {
    if (!currentModal) {
      currentModal = createModal();
      setupModalEvents();
    }

    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.innerHTML = diagramContent;

    // Remove any width/height attributes from SVG to make it responsive
    const svg = modalDiagram.querySelector('svg');
    if (svg) {
      svg.removeAttribute('width');
      svg.removeAttribute('height');
      svg.style.width = '100%';
      svg.style.height = 'auto';
    }

    // Setup drag functionality
    setupDrag(modalDiagram);

    currentModal.classList.add('active');
    currentZoom = 1;
    resetPosition();
    updateZoom();
    document.body.style.overflow = 'hidden';
  }

  // Hide modal
  function hideModal() {
    if (currentModal) {
      currentModal.classList.remove('active');
      document.body.style.overflow = '';
    }
  }

  // Update zoom level
  function updateZoom() {
    if (!currentModal) return;
    const diagram = currentModal.querySelector('.mermaid-modal-diagram');
    const zoomLevel = currentModal.querySelector('.mermaid-zoom-level');
    diagram.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentZoom})`;
    zoomLevel.textContent = `${Math.round(currentZoom * 100)}%`;
  }

  // Reset position when zoom changes
  function resetPosition() {
    translateX = 0;
    translateY = 0;
  }

  // Setup drag functionality
  function setupDrag(element) {
    element.addEventListener('mousedown', startDrag);
    element.addEventListener('touchstart', startDrag);
  }

  function startDrag(e) {
    if (e.type === 'mousedown' && e.button !== 0) return; // Only left click

    isDragging = true;
    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.classList.add('dragging');

    if (e.type === 'touchstart') {
      startX = e.touches[0].clientX - translateX;
      startY = e.touches[0].clientY - translateY;
    } else {
      startX = e.clientX - translateX;
      startY = e.clientY - translateY;
    }

    document.addEventListener('mousemove', drag);
    document.addEventListener('touchmove', drag);
    document.addEventListener('mouseup', stopDrag);
    document.addEventListener('touchend', stopDrag);
  }

  function drag(e) {
    if (!isDragging) return;
    e.preventDefault();

    if (e.type === 'touchmove') {
      translateX = e.touches[0].clientX - startX;
      translateY = e.touches[0].clientY - startY;
    } else {
      translateX = e.clientX - startX;
      translateY = e.clientY - startY;
    }

    updateZoom();
  }

  function stopDrag() {
    isDragging = false;
    const modalDiagram = currentModal?.querySelector('.mermaid-modal-diagram');
    if (modalDiagram) {
      modalDiagram.classList.remove('dragging');
    }
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('touchmove', drag);
    document.removeEventListener('mouseup', stopDrag);
    document.removeEventListener('touchend', stopDrag);
  }

  // Setup modal event listeners
  function setupModalEvents() {
    if (!currentModal) return;

    // Close button
    currentModal.querySelector('.mermaid-close').addEventListener('click', hideModal);

    // Zoom buttons
    currentModal.querySelector('.zoom-in').addEventListener('click', () => {
      currentZoom = Math.min(currentZoom + 0.25, 3);
      updateZoom();
    });

    currentModal.querySelector('.zoom-out').addEventListener('click', () => {
      currentZoom = Math.max(currentZoom - 0.25, 0.5);
      updateZoom();
    });

    currentModal.querySelector('.zoom-reset').addEventListener('click', () => {
      currentZoom = 1;
      resetPosition();
      updateZoom();
    });

    // Close on background click
    currentModal.addEventListener('click', (e) => {
      if (e.target === currentModal) {
        hideModal();
      }
    });

    // Close on ESC key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && currentModal.classList.contains('active')) {
        hideModal();
      }
    });
  }

  // Convert Jekyll-rendered code blocks to mermaid divs
  document.addEventListener('DOMContentLoaded', async function() {
    const codeBlocks = document.querySelectorAll('code.language-mermaid');

    for (const codeBlock of codeBlocks) {
      const pre = codeBlock.parentElement;
      const container = document.createElement('div');
      container.className = 'mermaid-container';

      const mermaidDiv = document.createElement('div');
      mermaidDiv.className = 'mermaid';
      mermaidDiv.textContent = codeBlock.textContent;

      container.appendChild(mermaidDiv);
      pre.replaceWith(container);
    }

    // Render all mermaid diagrams
    try {
      await mermaid.run({
        querySelector: '.mermaid'
      });
      console.log('Mermaid diagrams rendered successfully');
    } catch (error) {
      console.error('Mermaid rendering error:', error);
    }

    // Add click handlers to rendered diagrams
    document.querySelectorAll('.mermaid-container').forEach((container, index) => {
      // Find the rendered SVG inside the container
      const svg = container.querySelector('svg');
      if (!svg) {
        console.warn(`No SVG found in container ${index}`);
        return;
      }

      // Make the container clickable
      container.style.cursor = 'pointer';
      container.title = '点击查看大图';

      container.addEventListener('click', function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Clone the SVG for the modal
        const svgClone = svg.cloneNode(true);
        const tempDiv = document.createElement('div');
        tempDiv.appendChild(svgClone);

        console.log('Opening modal for diagram', index);
        showModal(tempDiv.innerHTML);
      });

      console.log(`Click handler added to diagram ${index}`);
    });
  });
</script>

<p><code class="language-plaintext highlighter-rouge">ccoctl</code> 是 OpenShift 的云凭据操作符 (CCO) 实用程序，其主要用途是在<strong>手动模式</strong>下，为各个集群组件在云提供商上<strong>创建和管理精细化的短期权限凭证</strong>，从而避免在集群中存储高权限的长期凭证，提升集群安全性。</p>

<p>简单来说，它允许您为 OpenShift 的每个组件（如镜像仓库、存储驱动、Ingress Controller 等）分别创建独立的、最小权限的云账号，而不是使用一个拥有全局权限的管理员账号。</p>

<h2 id="一为什么需要-ccoctl与默认模式的对比">一、为什么需要 ccoctl？与默认模式的对比</h2>

<h3 id="核心作用">核心作用</h3>

<p><code class="language-plaintext highlighter-rouge">ccoctl</code> 主要用在需要<strong>最高安全标准</strong>的场景。它将云凭证的管理从集群内部转移到集群外部，实现了更严格的权限控制：</p>

<ul>
  <li><strong>实现短期凭证</strong>：为 AWS、GCP 等云平台配置基于 OIDC 的短期凭证（如 AWS STS、GCP Workload Identity）。集群组件使用这些临时令牌来访问云 API，凭证会自动轮换，风险更低。</li>
  <li><strong>避免存储管理员凭证</strong>：在手动模式下，集群的 <code class="language-plaintext highlighter-rouge">kube-system</code> 命名空间中不会存储高权限的管理员级云凭证，大大降低了凭证泄露的风险。</li>
  <li><strong>管理长期凭证</strong>：对于 IBM Cloud 或 Nutanix 等平台，<code class="language-plaintext highlighter-rouge">ccoctl</code> 也用于在安装过程中配置由外部管理的长期凭证。</li>
  <li><strong>清理资源</strong>：在集群卸载后，可以使用 <code class="language-plaintext highlighter-rouge">ccoctl</code> 来删除它在安装时创建的云资源（如 IAM 角色、OIDC 提供商和 S3 存储桶）。</li>
</ul>

<p><strong>简单来说</strong>：不使用 <code class="language-plaintext highlighter-rouge">ccoctl</code> 的安装过程更简单快捷，但安全性较低；使用 <code class="language-plaintext highlighter-rouge">ccoctl</code> 的过程更复杂，但安全性最高，符合企业级安全最佳实践。</p>

<h3 id="两种方式对比">两种方式对比</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">对比维度</th>
      <th style="text-align: left">使用 <code class="language-plaintext highlighter-rouge">ccoctl</code> (手动模式 + 短期凭证)</th>
      <th style="text-align: left">不使用 <code class="language-plaintext highlighter-rouge">ccoctl</code> (默认 Mint 模式)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>核心机制</strong></td>
      <td style="text-align: left">基于 <strong>STS</strong> 的<strong>短期、动态令牌</strong>。集群组件通过 ServiceAccount 扮演 IAM 角色，自动获取定期刷新的临时凭证。</td>
      <td style="text-align: left">基于<strong>长期 Access Key</strong>。CCO 使用高权限的管理员凭证，为其他组件<strong>动态创建</strong>低权限的长期用户。</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>安全性</strong></td>
      <td style="text-align: left"><strong>最高</strong>。集群内部不存储任何长期有效的高风险凭证。</td>
      <td style="text-align: left"><strong>较高，但存在风险</strong>。高权限的管理员凭证在安装后默认会存储在 <code class="language-plaintext highlighter-rouge">kube-system</code> 命名空间中。</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>安装流程</strong></td>
      <td style="text-align: left"><strong>复杂</strong>。安装前需要手动执行 <code class="language-plaintext highlighter-rouge">ccoctl</code>，预先创建 OIDC、IAM 角色等，并将生成的清单提供给安装程序。</td>
      <td style="text-align: left"><strong>简单、自动化</strong>。只需在 <code class="language-plaintext highlighter-rouge">install-config.yaml</code> 中配置云凭证即可。</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>运维负担</strong></td>
      <td style="text-align: left">升级时若权限要求未变通常无需额外操作；权限有更新时需用 <code class="language-plaintext highlighter-rouge">ccoctl</code> 更新角色。</td>
      <td style="text-align: left">升级前需检查新版本 CredentialsRequest，确保管理员凭证权限充足。</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>集群销毁</strong></td>
      <td style="text-align: left">需使用 <code class="language-plaintext highlighter-rouge">ccoctl aws delete</code> 等<strong>手动清理</strong>预先创建的 IAM 和 OIDC 资源。</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">openshift-install destroy cluster</code> 即可<strong>自动清理</strong>。</td>
    </tr>
  </tbody>
</table>

<p><strong>建议</strong>：有严格安全合规要求或希望采用最小权限原则时选用 <code class="language-plaintext highlighter-rouge">ccoctl</code>；开发测试、POC 或优先便利性时，默认 Mint 模式即可。</p>

<hr />

<h2 id="二如何使用-ccoctl">二、如何使用 ccoctl</h2>

<h3 id="获取-ccoctl-二进制">获取 ccoctl 二进制</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">RELEASE_IMAGE</span><span class="o">=</span><span class="si">$(</span>./openshift-install version | <span class="nb">awk</span> <span class="s1">'/release image/ {print $3}'</span><span class="si">)</span>
<span class="nv">CCO_IMAGE</span><span class="o">=</span><span class="si">$(</span>oc adm release info <span class="nt">--image-for</span><span class="o">=</span><span class="s1">'cloud-credential-operator'</span> <span class="nv">$RELEASE_IMAGE</span> <span class="nt">-a</span> ~/.pull-secret<span class="si">)</span>
oc image extract <span class="nv">$CCO_IMAGE</span> <span class="nt">--file</span><span class="o">=</span><span class="s2">"/usr/bin/ccoctl.rhel8"</span> <span class="nt">-a</span> ~/.pull-secret
<span class="nb">chmod </span>775 ccoctl.rhel8
./ccoctl <span class="nt">--help</span>
</code></pre></div></div>

<h3 id="主要场景为-aws-sts-集群创建资源">主要场景：为 AWS STS 集群创建资源</h3>

<ol>
  <li><strong>创建密钥对</strong>：<code class="language-plaintext highlighter-rouge">./ccoctl aws create-key-pair</code></li>
  <li><strong>创建 OIDC 身份提供商和 S3 存储桶</strong>：<code class="language-plaintext highlighter-rouge">./ccoctl aws create-identity-provider --name=&lt;cluster-name&gt; --region=&lt;aws-region&gt; --public-key-file=&lt;path-to-public-key&gt;</code></li>
  <li><strong>提取 CredentialsRequests</strong>：<code class="language-plaintext highlighter-rouge">oc adm release extract --credentials-requests --cloud=aws --to=./credrequests &lt;your-release-image&gt;</code></li>
  <li><strong>为每个组件创建 IAM 角色</strong>：<code class="language-plaintext highlighter-rouge">./ccoctl aws create-iam-roles --name=&lt;cluster-name&gt; --region=&lt;aws-region&gt; --credentials-requests-dir=./credrequests --identity-provider-arn=&lt;arn-of-oidc-provider&gt;</code></li>
</ol>

<p>完成后将生成的 manifest 复制到安装目录的 <code class="language-plaintext highlighter-rouge">manifests</code> 和 <code class="language-plaintext highlighter-rouge">tls</code> 目录。集群卸载后清理：<code class="language-plaintext highlighter-rouge">./ccoctl aws delete --name=&lt;cluster-name&gt; --region=&lt;aws-region&gt;</code>。</p>

<hr />

<h2 id="三sts-工作流程从准备到运行时">三、STS 工作流程：从准备到运行时</h2>

<p>这是云原生安全的最佳实践之一，结合 <strong>OIDC 身份联邦</strong>、<strong>Kubernetes ServiceAccount</strong> 和 <strong>云 IAM 角色</strong>，以 <strong>AWS STS</strong> 为例说明。</p>

<h3 id="核心架构概览双向信任链">核心架构概览（双向信任链）</h3>

<ol>
  <li><strong>OpenShift 集群信任 AWS</strong>：集群通过 OIDC 提供商对外宣称「我是谁」。</li>
  <li><strong>AWS 信任 OpenShift</strong>：IAM 角色配置为只信任特定的 OpenShift ServiceAccount。</li>
  <li><strong>组件自动换证</strong>：组件通过扮演角色获取临时令牌，无需人工干预。</li>
</ol>

<h3 id="前期准备ccoctl-搭建">前期准备（ccoctl 搭建）</h3>

<ol>
  <li><strong>创建 OIDC 提供商</strong>：<code class="language-plaintext highlighter-rouge">ccoctl</code> 在 AWS 上创建公钥端点（通常放在 S3），AWS 用其验证集群签发的 ServiceAccount 令牌。</li>
  <li><strong>创建 IAM 角色与信任策略</strong>：为每个需要云权限的组件创建一个 IAM 角色，信任策略只允许特定的 OpenShift ServiceAccount 扮演该角色。示例：</li>
</ol>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"Effect"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Allow"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Principal"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"Federated"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:iam::123456789:oidc-provider/&lt;s3-bucket-name&gt;"</span><span class="w"> </span><span class="p">},</span><span class="w">
  </span><span class="nl">"Action"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sts:AssumeRoleWithWebIdentity"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Condition"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"StringEquals"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"&lt;s3-bucket-name&gt;:sub"</span><span class="p">:</span><span class="w"> </span><span class="s2">"system:serviceaccount:openshift-ingress:router"</span><span class="w"> </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="集群运行时自动获取凭证">集群运行时（自动获取凭证）</h3>

<p>以 Ingress Controller Pod 为例：</p>

<ol>
  <li><strong>Pod 挂载带注解的 ServiceAccount</strong>：例如 <code class="language-plaintext highlighter-rouge">sts.amazonaws.com/role-arn: "arn:aws:iam::123456789:role/openshift-ingress-role"</code>，无 AWS Secret。</li>
  <li><strong>API Server 签发 JWT</strong>：Kubelet 向 API Server 请求为该 ServiceAccount 签发 JWT，签名私钥即 <code class="language-plaintext highlighter-rouge">ccoctl</code> 生成的那对密钥中的私钥。</li>
  <li><strong>Pod 向 AWS STS 发起 AssumeRoleWithWebIdentity</strong>：SDK 自动携带 JWT 与 Role ARN。</li>
  <li><strong>STS 验证并颁发临时凭证</strong>：验证 JWT 签名（用 OIDC 公钥）、校验 <code class="language-plaintext highlighter-rouge">sub</code> 与信任策略，通过后返回 <code class="language-plaintext highlighter-rouge">AccessKeyId</code>、<code class="language-plaintext highlighter-rouge">SecretAccessKey</code>、<code class="language-plaintext highlighter-rouge">SessionToken</code>（通常 1 小时有效）。</li>
  <li><strong>Pod 使用临时凭证调用 AWS API</strong>，过期前 SDK 自动用同一 JWT 换新凭证，对应用透明。</li>
</ol>

<h3 id="为什么这个模式更安全">为什么这个模式更安全？</h3>

<ul>
  <li><strong>无长期凭证</strong>：集群内没有永久有效的 AccessKey/SecretKey。</li>
  <li><strong>权限最小化</strong>：每个组件只能拿到自己 Role 的权限。</li>
  <li><strong>凭证自动轮换</strong>：泄露的临时凭证在 1 小时内失效。</li>
  <li><strong>身份绑定</strong>：凭证与特定 Pod/ServiceAccount 绑定，无法被集群外冒用。</li>
</ul>

<hr />

<h2 id="四技术细节公钥iam-role-数量与-sarole-关系">四、技术细节：公钥、IAM Role 数量与 SA/Role 关系</h2>

<h3 id="公钥端点签名验证不是数据加密">公钥端点：签名验证，不是数据加密</h3>

<p>使用的是<strong>私钥签名、公钥验签</strong>的数字签名过程：</p>

<ul>
  <li><strong>ccoctl</strong>：生成密钥对；<strong>私钥</strong>由集群 API Server 保管并用于<strong>签发</strong> JWT；<strong>公钥</strong>上传到 S3（OIDC 公钥端点）。</li>
  <li><strong>流程</strong>：API Server 用私钥对 JWT 签名 → AWS STS 从 OIDC 端点取公钥验签，确认令牌来自可信集群且未被篡改。核心是<strong>身份真实性验证</strong>，不是传输保密。</li>
</ul>

<h3 id="iam-role-数量约-1015-个">IAM Role 数量：约 10–15 个</h3>

<p><code class="language-plaintext highlighter-rouge">ccoctl</code> 为每个需要调用云 API 的组件创建一个独立 IAM Role，<strong>不使用</strong> IAM User。典型组件包括：Cluster API Provider、Image Registry、Ingress Controller、Storage (CSI)、Machine Config Operator、Cloud Network Config 等。</p>

<h3 id="serviceaccount-与-iam-role扮演与被扮演">ServiceAccount 与 IAM Role：扮演与被扮演</h3>

<ul>
  <li><strong>ServiceAccount</strong>：集群内「谁」在请求。</li>
  <li><strong>IAM Role</strong>：云上「可以做什么」的权限集合。</li>
  <li><strong>信任策略</strong>：规定只允许特定 OIDC 端点、特定 ServiceAccount（如 <code class="language-plaintext highlighter-rouge">openshift-ingress:router</code>）来扮演该 Role。SA 拿 JWT 来「敲门」，Role 验证通过后才允许暂时扮演。</li>
</ul>

<h3 id="整体关系与流程图">整体关系与流程图</h3>

<pre><code class="language-mermaid">graph TD
    subgraph pre_ccoctl["安装前 (由 ccoctl 执行)"]
        A[ccoctl] --&gt; B1(生成密钥对)
        A --&gt; B2(为每个组件创建 IAM Role)
        A --&gt; B3(上传公钥到 S3 桶)
        A --&gt; B4(在 AWS 创建 OIDC IdP&lt;br/&gt;指向 S3 桶)
    end

    subgraph aws_cloud["AWS 云平台"]
        S3["S3 桶 (公钥端点)"] --&gt; OIDC["AWS OIDC 身份提供商"]
        subgraph iam["IAM"]
            direction LR
            Role_Ingress["IAM Role Ingress&lt;br/&gt;信任策略: 限定 ServiceAccount"]
            Role_Registry["IAM Role Registry&lt;br/&gt;信任策略: 限定 ServiceAccount"]
        end
    end

    subgraph ocp["OpenShift 集群"]
        direction TB
        API["Kubernetes API Server&lt;br/&gt;持有 私钥"] --&gt; SA_Ingress["ServiceAccount: router&lt;br/&gt;Annotation: role-arn=Role_Ingress"]
        SA_Registry["ServiceAccount: registry&lt;br/&gt;Annotation: role-arn=Role_Registry"]
        Pod_Ingress["Pod: Ingress Controller"] --&gt;|挂载| SA_Ingress
        Pod_Registry["Pod: Image Registry"] --&gt;|挂载| SA_Registry
    end

    subgraph runtime["运行时 (自动流程)"]
        direction LR
        Req1["Pod 请求 AWS API"] --&gt; Req2["SDK 自动读取 JWT 令牌"]
        Req2 --&gt; Req3["SDK 向 STS 发送 AssumeRoleWithWebIdentity 请求&lt;br/&gt;携带 JWT 令牌和 IAM Role ARN"]
    end

    subgraph sts_flow["AWS STS 验证与响应"]
        Val1["STS 接收请求"] --&gt; Val2["根据 JWT 的 iss 字段&lt;br/&gt;找到 OIDC IdP"]
        Val2 --&gt; Val3["OIDC IdP 从 S3 获取公钥&lt;br/&gt;验证 JWT 签名"]
        Val3 --&gt; Val4{验证通过?}
        Val4 --&gt;|是| Val5["校验 JWT 的 sub 字段&lt;br/&gt;是否匹配 IAM Role 信任策略"]
        Val5 --&gt;|是| Val6["STS 生成临时凭证&lt;br/&gt;(AK/SK/Token) 返回给 Pod"]
    end

    Pod_Ingress -.-&gt; Req1
    Val6 -.-&gt; Pod_Ingress
</code></pre>

<p>要点：私钥在集群内签名，公钥在 S3 供 AWS 验签；每个组件一个 Role；SA 通过 Annotation 与 Role 信任策略建立绑定；运行时 Pod 用 JWT 向 STS 申请扮演 Role 并获得临时凭证。</p>

<hr />

<h2 id="五jwt-令牌-vs-sts-临时凭证">五、JWT 令牌 vs STS 临时凭证</h2>

<p><strong>关系概括</strong>：JWT 是「身份证」，STS 临时凭证是「通行证」。Pod 先亮出身份证证明「我是谁」，再换取能真正调用云 API 的通行证。</p>

<h3 id="对比表">对比表</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">维度</th>
      <th style="text-align: left">JWT 令牌</th>
      <th style="text-align: left">STS 临时凭证</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>颁发者</strong></td>
      <td style="text-align: left">Kubernetes API Server</td>
      <td style="text-align: left">AWS STS</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>用途</strong></td>
      <td style="text-align: left">向 AWS 证明身份（某某 ServiceAccount）</td>
      <td style="text-align: left">向 AWS 服务证明权限（有权调用哪些 API）</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>包含内容</strong></td>
      <td style="text-align: left">集群身份、Namespace、SA 名称、过期时间</td>
      <td style="text-align: left">临时 AccessKey、SecretKey、SessionToken、过期时间</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>有效期</strong></td>
      <td style="text-align: left">通常 1 小时（可配置）</td>
      <td style="text-align: left">通常 1 小时</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>是否直接调用 AWS API</strong></td>
      <td style="text-align: left">❌ 不能</td>
      <td style="text-align: left">✅ 能</td>
    </tr>
  </tbody>
</table>

<h3 id="类比机场安检">类比：机场安检</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">现实场景</th>
      <th style="text-align: left">OpenShift + AWS</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">身份证</td>
      <td style="text-align: left"><strong>JWT 令牌</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">公安局</td>
      <td style="text-align: left">Kubernetes API Server</td>
    </tr>
    <tr>
      <td style="text-align: left">登机牌</td>
      <td style="text-align: left"><strong>STS 临时凭证</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">用登机牌登机</td>
      <td style="text-align: left">用临时凭证调用 AWS API</td>
    </tr>
  </tbody>
</table>

<h3 id="jwt-与-sts-关系mermaid">JWT 与 STS 关系（Mermaid）</h3>

<pre><code class="language-mermaid">graph TB
    subgraph "OpenShift 集群内部"
        Pod[Pod/容器]
        SA[ServiceAccount&lt;br/&gt;router]
        JWT[JWT 令牌&lt;br/&gt;身份证明]
        API[Kubernetes API Server&lt;br/&gt;持有私钥]
        Pod --&gt; SA
        SA -.-&gt;|挂载| JWT
        API --&gt;|用私钥签发| JWT
    end

    subgraph "AWS STS 验证过程"
        direction TB
        Step1[接收 AssumeRoleWithWebIdentity 请求&lt;br/&gt;携带 JWT 令牌 + IAM Role ARN]
        Step2[从 OIDC 端点获取公钥]
        Step3[用公钥验证 JWT 签名]
        Step4[检查 JWT.sub 是否匹配&lt;br/&gt;IAM Role 信任策略]
        Step5[验证通过 → 生成临时凭证]
        Step1 --&gt; Step2 --&gt; Step3 --&gt; Step4 --&gt; Step5
    end

    subgraph "STS 临时凭证"
        Temp_Cred[AccessKeyId + SecretAccessKey + SessionToken + Expiration]
    end

    JWT -.-&gt;|提交身份证明| Step1
    Step5 --&gt;|返回| Temp_Cred
    Temp_Cred --&gt;|SDK 缓存| Pod
</code></pre>

<h3 id="时序流程">时序流程</h3>

<pre><code class="language-mermaid">sequenceDiagram
    participant P as Pod
    participant K as K8s API Server
    participant STS as AWS STS
    participant S3 as S3/其他AWS服务

    K-&gt;&gt;P: 用私钥签发 JWT 并挂载到 Pod
    P-&gt;&gt;STS: AssumeRoleWithWebIdentity(JWT + Role ARN)
    STS-&gt;&gt;STS: 从 OIDC 获取公钥、验签、检查 sub
    STS--&gt;&gt;P: 返回临时凭证
    P-&gt;&gt;S3: 调用 API（带临时凭证）
    Note over P: 1 小时后 SDK 用同一 JWT 换新凭证
</code></pre>

<h3 id="三者的层级关系">三者的层级关系</h3>

<pre><code class="language-mermaid">graph RL
    subgraph "第4层：云资源操作"
        API_Calls[AWS API 调用&lt;br/&gt;S3/EC2/ELB]
    end
    subgraph "第3层：临时权限凭证"
        Temp_Cred[STS临时凭证&lt;br/&gt;1小时有效期]
    end
    subgraph "第2层：集群内身份"
        JWT[JWT令牌&lt;br/&gt;K8s API Server 签发]
    end
    subgraph "第1层：底层基础设施"
        KeyPair[密钥对&lt;br/&gt;私钥: K8s / 公钥: S3]
        IAM_Role[IAM Role&lt;br/&gt;权限策略+信任策略]
    end
    KeyPair --&gt;|私钥签发| JWT
    KeyPair --&gt;|公钥验证| Temp_Cred
    IAM_Role --&gt;|信任策略允许| Temp_Cred
    JWT --&gt;|证明身份换取| Temp_Cred
    Temp_Cred --&gt;|授权执行| API_Calls
</code></pre>

<h3 id="核心关系总结图示">核心关系总结（图示）</h3>

<pre><code class="language-mermaid">graph TD
    subgraph "JWT令牌 vs STS临时凭证"
        A[JWT令牌] --&gt;|作用| A1["证明身份&lt;br/&gt;'我是openshift-ingress:router'"]
        A --&gt;|颁发者| A2["Kubernetes API Server"]
        A --&gt;|验证者| A3["AWS STS"]
        A --&gt;|生命周期| A4["Pod生命周期&lt;br/&gt;只要Pod在就有效"]
        B[STS临时凭证] --&gt;|作用| B1["授权操作&lt;br/&gt;'我可以创建负载均衡器'"]
        B --&gt;|颁发者| B2["AWS STS"]
        B --&gt;|验证者| B3["AWS各服务&lt;br/&gt;(S3/EC2/ELB等)"]
        B --&gt;|生命周期| B4["1小时&lt;br/&gt;自动轮换"]
        A -.-&gt;|换取| B
    end
</code></pre>

<p><strong>一句话</strong>：JWT 管「你是谁」，临时凭证管「你能做什么」；两者职责分明，流程自动化。</p>

<hr />

<h2 id="六iam-role-的两种核心策略与权限来源">六、IAM Role 的两种核心策略与权限来源</h2>

<p><strong>重要</strong>：这些 IAM Role 的权限<strong>与 IAM User 无关</strong>，来自<strong>角色自身附加的权限策略</strong>。</p>

<h3 id="双重策略结构">双重策略结构</h3>

<p>每个 IAM Role 包含两个独立策略：</p>

<pre><code class="language-mermaid">graph TB
    subgraph IAM角色 [IAM Role]
        direction TB
        TP[信任策略 Trust Policy&lt;br/&gt;定义：谁可以扮演这个角色]
        PP[权限策略 Permission Policy&lt;br/&gt;定义：扮演后可以做什么]
    end
    TP --&gt; 验证[验证：你是谁？]
    PP --&gt; 授权[授权：你能做什么？]
</code></pre>

<ul>
  <li><strong>信任策略</strong>：只允许来自特定 OIDC 提供商、且 JWT 的 <code class="language-plaintext highlighter-rouge">sub</code> 为特定 ServiceAccount 的请求者扮演该角色。</li>
  <li><strong>权限策略</strong>：定义扮演后能执行哪些 AWS 操作（如 Image Registry 的 S3 操作）。</li>
</ul>

<p>在 ccoctl 模式下<strong>完全不使用 IAM User</strong>：ccoctl 为每个组件创建 IAM Role，直接附加权限策略与信任策略；Pod 通过 JWT 扮演角色，获得的是<strong>角色自身的权限</strong>。</p>

<h3 id="信任策略的完整工作机制">信任策略的完整工作机制</h3>

<pre><code class="language-mermaid">sequenceDiagram
    participant Pod as Pod (Ingress)
    participant STS as AWS STS
    participant Role as IAM Role (Ingress)

    Pod-&gt;&gt;STS: AssumeRoleWithWebIdentity(JWT + Role ARN)
    Note over STS: STS 验证 JWT 签名（用 OIDC 公钥）
    STS-&gt;&gt;Role: 读取 Role 的信任策略
    Note over STS: 检查 JWT 身份是否匹配信任策略
    alt 验证通过
        STS--&gt;&gt;Pod: 返回临时凭证
        Pod-&gt;&gt;AWS API: 用临时凭证调用 API
    else 验证失败
        STS--&gt;&gt;Pod: 拒绝请求
    end
</code></pre>

<p>信任策略在扮演阶段由 STS 验证；权限策略在实际调用各 AWS 服务时验证；两者独立且缺一不可。</p>

<h3 id="ccoctl-的权力从哪来">ccoctl 的权力从哪来？</h3>

<p><code class="language-plaintext highlighter-rouge">ccoctl</code> 的权力是<strong>被赋予</strong>的：运行 <code class="language-plaintext highlighter-rouge">ccoctl</code> 的人（或系统）必须提供一个具有足够 AWS 权限的 IAM 用户或角色（例如能调用 <code class="language-plaintext highlighter-rouge">iam:CreateRole</code>、<code class="language-plaintext highlighter-rouge">iam:CreateOpenIDConnectProvider</code>、<code class="language-plaintext highlighter-rouge">s3:PutObject</code> 等）。有了该凭证后，<code class="language-plaintext highlighter-rouge">ccoctl</code> 按 OpenShift 的 CredentialsRequest 为每个组件创建 IAM Role，并附加权限策略与信任策略。</p>

<pre><code class="language-mermaid">graph LR
    A[运行 ccoctl] --&gt; B[读取 CredentialsRequest]
    B --&gt; C[为每个组件准备 IAM Role]
    C --&gt; D[创建 IAM Role]
    D --&gt; E[附加权限策略]
    D --&gt; F[配置信任策略]
</code></pre>

<hr />

<h2 id="七运行态集群的权限闭环与安全实践">七、运行态集群的权限闭环与安全实践</h2>

<p>用于运行 <code class="language-plaintext highlighter-rouge">ccoctl</code> 的高权限用户，在集群安装完成后<strong>可以彻底退出</strong>：集群运行时<strong>不需要、也不会用到</strong>该用户的凭证。</p>

<h3 id="运行态权限闭环">运行态权限闭环</h3>

<pre><code class="language-mermaid">graph TB
    subgraph "安装阶段 (ccoctl 执行)"
        Admin[管理员持有&lt;br/&gt;高权限 IAM User]
        Admin --&gt;|执行 ccoctl| Create[创建所有 IAM Role&lt;br/&gt;和 OIDC 提供商]
        Create --&gt;|完成后| Done[高权限用户凭证&lt;br/&gt;可以安全删除或封存]
    end
    subgraph "运行阶段 (集群运行时)"
        Pod[Pod] --&gt;|JWT 令牌| STS[AWS STS]
        STS --&gt;|验证通过后颁发| Temp[临时凭证]
        Temp --&gt;|权限来自| Role[IAM Role 自身的权限策略]
        Role --&gt;|不涉及| AdminUser[安装时的高权限用户]
    end
    Done -.-&gt;|不再使用| AdminUser
</code></pre>

<p>原因简要说明：权限已固化在各 IAM Role 的权限策略与信任策略中；集群组件通过 JWT → STS → 扮演 Role → 获得临时凭证 → 调用 API，整条链无需安装时的高权限用户；临时凭证的权限来自被扮演的 Role，而非创建 Role 的用户。</p>

<h3 id="安全最佳实践安装后清理">安全最佳实践：安装后清理</h3>

<pre><code class="language-mermaid">graph LR
    subgraph "安装完成后"
        A[高权限 IAM User] --&gt; B{选择处理方式}
        B --&gt; C[彻底删除该用户]
        B --&gt; D[禁用 Access Key]
        B --&gt; E[轮换并封存凭证&lt;br/&gt;仅用于灾难恢复]
    end
    subgraph "运行态集群"
        F[所有组件] --&gt; G[只使用临时凭证]
        H[管理员日常操作] --&gt; I[使用更低权限的&lt;br/&gt;只读或审计账号]
    end
</code></pre>

<p>建议：安装成功后立即删除或禁用该高权限用户的 Access Key；日常管理使用只读或审计账号；必要时可用 STS 临时凭证来运行 <code class="language-plaintext highlighter-rouge">ccoctl</code>，避免长期高权限用户。</p>

<h3 id="与-mint-模式对比">与 Mint 模式对比</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">对比项</th>
      <th style="text-align: left">Mint 模式</th>
      <th style="text-align: left">ccoctl + STS 模式</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">安装时高权限用户</td>
      <td style="text-align: left">安装后默认<strong>保留</strong>在 <code class="language-plaintext highlighter-rouge">kube-system</code> Secret 中</td>
      <td style="text-align: left">安装后<strong>可安全删除</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">凭证类型</td>
      <td style="text-align: left">长期 AccessKey/SecretKey</td>
      <td style="text-align: left">1 小时自动轮换的临时凭证</td>
    </tr>
    <tr>
      <td style="text-align: left">泄露风险</td>
      <td style="text-align: left">攻击者可提取长期凭证</td>
      <td style="text-align: left">仅能获得短期凭证，且无法提取长期凭证</td>
    </tr>
    <tr>
      <td style="text-align: left">权限范围</td>
      <td style="text-align: left">常为全局管理员权限</td>
      <td style="text-align: left">每组件最小必要权限</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="八kube-apiserver-与-oidc">八、kube-apiserver 与 OIDC</h2>

<p>JWT 的签发与公钥提供由 <strong>kube-apiserver</strong> 完成：</p>

<ol>
  <li><strong>持有私钥</strong>：加载 <code class="language-plaintext highlighter-rouge">ccoctl</code> 生成并交给集群的私钥（如 <code class="language-plaintext highlighter-rouge">/etc/kubernetes/pki/sa.key</code>）。</li>
  <li><strong>签发 JWT</strong>：当 Pod 挂载 ServiceAccount 并请求令牌时，使用 TokenRequest API 签发<strong>绑定服务账户令牌</strong>（Bound Service Account Token）。</li>
  <li><strong>提供公钥端点</strong>：通过 <code class="language-plaintext highlighter-rouge">/.well-known/openid-configuration</code>、<code class="language-plaintext highlighter-rouge">/openid/v1/jwks</code> 等对外提供公钥，供 AWS STS 验签。</li>
</ol>

<pre><code class="language-mermaid">graph LR
    subgraph OpenShift 集群
        API[kube-apiserver]
        KeyPair[密钥对&lt;br/&gt;私钥: sa.key&lt;br/&gt;公钥: sa.pub]
        SA[ServiceAccount]
        Pod[Pod]
        Token[JWT 令牌]
        API -- 持有 --&gt; KeyPair
        SA -- 请求令牌 --&gt; API
        API -- 使用私钥签发 --&gt; Token
        Token -- 挂载到 --&gt; Pod
    end
    subgraph 外部
        STS[AWS STS]
        JWKS_Endpoint[JWKS 公钥端点]
    end
    API -- 提供公钥 --&gt; JWKS_Endpoint
    Pod -- 发送 JWT 令牌 --&gt; STS
    STS -- 从端点获取公钥、验签 --&gt; STS
</code></pre>

<p>kubelet 会监控挂载的短期令牌有效期，在过期前通过 TokenRequest API 向 apiserver 请求新令牌，实现无缝轮换。</p>

<hr />

<h2 id="九aws-对-oidc-的支持与标准化">九、AWS 对 OIDC 的支持与标准化</h2>

<p>AWS <strong>主动实现了 OIDC 开放标准</strong>，才能与 Kubernetes/OpenShift 无缝集成。</p>

<h3 id="aws-对-oidc-的支持">AWS 对 OIDC 的支持</h3>

<pre><code class="language-mermaid">graph TB
    subgraph oidc_open_std["开放标准 OIDC"]
        OIDC_Core["OIDC 核心规范"]
        OIDC_Core --&gt;|定义| JWKS[JWKS 公钥格式]
        OIDC_Core --&gt;|定义| JWT[JWT 令牌结构]
    end
    subgraph k8s_ocp["Kubernetes/OpenShift"]
        K8s[实现 OIDC 身份提供商]
        K8s --&gt;|提供| K8s_JWKS[公钥端点]
        K8s --&gt;|签发| K8s_JWT[JWT 令牌]
    end
    subgraph aws_fed["AWS"]
        AWS_Federation["AWS 实现 OIDC 身份联邦"]
        AWS_Federation --&gt;|支持| IAM_OIDC[IAM OIDC 身份提供商]
        AWS_Federation --&gt;|支持| STS_OIDC[STS AssumeRoleWithWebIdentity]
        AWS_Federation --&gt;|验证| AWS_Validation[OIDC 令牌验证]
    end
    K8s_JWKS --&gt; IAM_OIDC
    K8s_JWT --&gt; STS_OIDC
    STS_OIDC --&gt; AWS_Validation
</code></pre>

<p>AWS 的三件关键实现：① IAM OIDC 身份提供商（ccoctl 在 AWS 上创建）；② STS <code class="language-plaintext highlighter-rouge">AssumeRoleWithWebIdentity</code> API；③ IAM Role 信任策略中的 OIDC 条件键（如 <code class="language-plaintext highlighter-rouge">sub</code>、<code class="language-plaintext highlighter-rouge">aud</code>、<code class="language-plaintext highlighter-rouge">iss</code>）。GCP、Azure、阿里云等也支持类似 OIDC 联邦，逻辑一致：集群提供 OIDC 端点 → 云厂商创建 OIDC IdP → 角色配置信任策略 → Pod 用 JWT 换临时凭证。</p>

<h3 id="标准化的价值">标准化的价值</h3>

<pre><code class="language-mermaid">graph TD
    subgraph k8s_clusters["Kubernetes 集群"]
        Cluster1[OpenShift 集群]
        Cluster2[原生 K8s]
        Cluster3[其他发行版]
    end
    subgraph oidc_norm["OIDC 标准"]
        OIDC_Spec["OIDC 核心规范"]
    end
    subgraph cloud_vendors["云厂商"]
        Cloud_AWS[AWS]
        Cloud_GCP[GCP]
        Cloud_Azure[Azure]
    end
    Cluster1 --&gt; OIDC_Spec
    Cluster2 --&gt; OIDC_Spec
    Cluster3 --&gt; OIDC_Spec
    OIDC_Spec --&gt; Cloud_AWS
    OIDC_Spec --&gt; Cloud_GCP
    OIDC_Spec --&gt; Cloud_Azure
</code></pre>

<hr />

<h2 id="十短期-vs-长期凭证与数据格式">十、短期 vs 长期凭证与数据格式</h2>

<h3 id="短期与长期凭证对比">短期与长期凭证对比</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">维度</th>
      <th style="text-align: left">普通版本 (Mint/手动)</th>
      <th style="text-align: left">STS 版本 (ccoctl)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>凭证类型</strong></td>
      <td style="text-align: left"><strong>长期</strong> AccessKey/SecretKey</td>
      <td style="text-align: left"><strong>短期</strong> STS 临时凭证</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>凭证来源</strong></td>
      <td style="text-align: left">CCO 用管理员凭证创建 IAM <strong>User</strong></td>
      <td style="text-align: left">Pod 用 JWT <strong>扮演</strong> IAM <strong>Role</strong></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>有效期</strong></td>
      <td style="text-align: left"><strong>永久有效</strong>（除非手动轮转）</td>
      <td style="text-align: left"><strong>1 小时</strong>，自动轮换</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>集群内凭证</strong></td>
      <td style="text-align: left">存在于 <code class="language-plaintext highlighter-rouge">kube-system</code> Secret</td>
      <td style="text-align: left"><strong>零</strong>长期凭证</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>凭证数量</strong></td>
      <td style="text-align: left">11+ 个（1 个高权限 + 约 10 个组件用户）</td>
      <td style="text-align: left">0 个用户，约 10 个可扮演的 Role</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>泄露影响</strong></td>
      <td style="text-align: left">严重且持久</td>
      <td style="text-align: left">有限且短暂（1 小时内失效）</td>
    </tr>
  </tbody>
</table>

<p>安装阶段 STS 版本需要更多权限（创建 OIDC、IAM Role 等）是一次性「建设成本」；运行阶段普通版本长期存在的多凭证才是安全命门。STS 用安装时短暂的「多」，换运行时永久的「少」和「短」。</p>

<h3 id="数据格式对比">数据格式对比</h3>

<ul>
  <li><strong>长期凭证</strong>：2 个字段 — <code class="language-plaintext highlighter-rouge">AccessKeyId</code>（以 <code class="language-plaintext highlighter-rouge">AKIA</code> 开头）、<code class="language-plaintext highlighter-rouge">SecretAccessKey</code>；无过期时间。</li>
  <li><strong>临时凭证</strong>：3 个字段 — <code class="language-plaintext highlighter-rouge">AccessKeyId</code>（以 <code class="language-plaintext highlighter-rouge">ASIA</code> 开头）、<code class="language-plaintext highlighter-rouge">SecretAccessKey</code>、<strong><code class="language-plaintext highlighter-rouge">SessionToken</code></strong>，以及 <code class="language-plaintext highlighter-rouge">Expiration</code>。调用 AWS API 时<strong>必须同时携带</strong>三者；缺少 <code class="language-plaintext highlighter-rouge">SessionToken</code> 会被拒绝。</li>
</ul>

<p><code class="language-plaintext highlighter-rouge">SessionToken</code> 是 STS 临时凭证的关键：证明凭证由 STS 合法颁发、在有效期内且未超出权限范围。</p>

<h3 id="sessiontoken-的验证">SessionToken 的验证</h3>

<p>验证是「接力」的：<strong>签发</strong>由 STS 在 AssumeRoleWithWebIdentity 时完成；<strong>每次 API 调用</strong>时，目标服务（如 S3、EC2）将凭证转交 AWS 统一认证系统，检查 SessionToken 是否合法、未过期、未吊销，并评估权限与条件。这样既能及时吊销，又保证审计与动态条件评估有效。</p>

<p>每次 API 调用时，AWS 内部的验证流程可概括为：</p>

<pre><code class="language-mermaid">graph TD
    A[收到API请求] --&gt; B[提取凭证 AccessKey + SessionToken]
    B --&gt; C{SessionToken存在?}
    C --&gt;|否| D[当作长期凭证处理]
    C --&gt;|是| E[查询STS服务端状态]
    E --&gt; F{凭证状态}
    F --&gt;|已过期| G[拒绝请求 ExpiredToken]
    F --&gt;|已吊销| H[拒绝请求 AccessDenied]
    F --&gt;|有效| I[继续验证]
    I --&gt; J[验证请求签名]
    J --&gt; K{签名正确?}
    K --&gt;|否| L[拒绝请求]
    K --&gt;|是| M[评估IAM权限]
    M --&gt; N{操作允许?}
    N --&gt;|否| O[拒绝请求 AccessDenied]
    N --&gt;|是| P[执行操作]
</code></pre>

<h3 id="sessiontoken-验证时序">SessionToken 验证时序</h3>

<pre><code class="language-mermaid">sequenceDiagram
    participant P as Pod
    participant STS as AWS STS
    participant S3 as AWS S3
    participant Auth as AWS 统一认证系统

    P-&gt;&gt;STS: AssumeRoleWithWebIdentity(JWT)
    STS--&gt;&gt;P: 返回临时凭证(含SessionToken)
    STS-&gt;&gt;Auth: 同步凭证状态(有效期/权限)

    P-&gt;&gt;S3: PutObject(带临时凭证)
    S3-&gt;&gt;Auth: 请求验证凭证
    Auth-&gt;&gt;Auth: 检查SessionToken有效性及权限
    Auth--&gt;&gt;S3: 验证结果
    S3--&gt;&gt;P: 操作结果
</code></pre>

<hr />

<h2 id="十一缓存与验证不会每次调用-assumerole">十一、缓存与验证：不会每次调用 AssumeRole</h2>

<p>集群<strong>不会每次访问都调用 AssumeRole</strong>。凭证使用是<strong>一次 AssumeRole，多次使用</strong>，并有过期前自动刷新。</p>

<h3 id="缓存机制">缓存机制</h3>

<pre><code class="language-mermaid">graph TD
    subgraph "第1层：Pod 内 SDK 缓存"
        A[Pod 首次调用 AWS API] --&gt; B[SDK 检查内存缓存]
        B --&gt;|无有效凭证| C[调用 AssumeRoleWithWebIdentity]
        C --&gt; D[STS 返回临时凭证，有效期 1 小时]
        D --&gt; E[SDK 将凭证缓存到内存]
        E --&gt; F[使用凭证调用目标 API]
    end
    subgraph "第2层：后续 API 调用"
        G[后续 API 请求] --&gt; H[SDK 检查内存缓存]
        H --&gt;|有有效凭证| I[直接使用缓存凭证]
        I --&gt; J[调用目标 API]
    end
    subgraph "第3层：过期前自动刷新"
        K[凭证剩余约 5 分钟] --&gt; L[SDK 异步刷新]
        L --&gt; M[后台 AssumeRoleWithWebIdentity]
        M --&gt; N[更新内存缓存]
        N --&gt; O[对应用完全透明]
    end
</code></pre>

<p>因此：<strong>是否每次访问都 AssumeRole？</strong> 否，只有首次（或过期后）才调用。<strong>凭证用多久？</strong> 1 小时，SDK 在过期前约 5 分钟自动刷新。<strong>性能影响？</strong> 与长期凭证无异，绝大多数请求命中缓存。</p>

<h3 id="何时会重新-assumerole">何时会重新 AssumeRole？</h3>

<pre><code class="language-mermaid">graph LR
    A[触发重新 AssumeRole 的场景] --&gt; B[首次启动]
    A --&gt; C[凭证自然过期]
    A --&gt; D[Pod 重建]
    A --&gt; E[SDK 刷新失败后重试]
    A --&gt; F[强制刷新配置]
    B --&gt; G[需要新凭证]
    C --&gt; G
    D --&gt; G
    E --&gt; G
    F --&gt; G
    G --&gt; H[调用 AssumeRole]
</code></pre>

<p>即：首次启动、凭证自然过期、Pod 重建、SDK 刷新失败后重试、或显式配置强制刷新时。</p>

<h3 id="获取-vs-验证">获取 vs 验证</h3>

<p><strong>凭证获取</strong>有缓存（约 1 小时一次，由 SDK 管理）；<strong>凭证验证</strong>每次 API 调用都会进行（目标服务向 AWS 认证系统验证 SessionToken、签名与权限）。这样既能及时吊销、满足动态策略与审计，又通过服务端缓存和边缘节点将单次验证延迟控制在很低水平。</p>

<pre><code class="language-mermaid">graph TB
    subgraph "凭证生命周期"
        Get[获取凭证 AssumeRole] --&gt; Cache[缓存凭证 Pod 内存]
        Cache --&gt; Use1[第1次调用]
        Cache --&gt; Use2[第2次调用]
        Cache --&gt; Use3[第N次调用]
    end
    subgraph "每次调用时的验证"
        Use1 --&gt; Validate1[AWS 服务验证 SessionToken]
        Use2 --&gt; Validate2[AWS 服务验证 SessionToken]
        Use3 --&gt; ValidateN[AWS 服务验证 SessionToken]
    end
</code></pre>

<hr />

<h2 id="十二完整链条总览数据与端点">十二、完整链条总览：数据与端点</h2>

<h3 id="阶段概览">阶段概览</h3>

<ul>
  <li><strong>阶段 1（ccoctl）</strong>：提取 CredentialsRequest、生成密钥对、向 S3 上传 OIDC 配置与公钥、创建 OIDC 提供商、为每个组件创建 IAM Role（含信任策略与权限策略）、输出 Secret YAML。</li>
  <li><strong>阶段 2（运行时）</strong>：Pod 挂载 SA → apiserver 签发 JWT → Pod 首次调用时 SDK 向 STS 发送 AssumeRoleWithWebIdentity(JWT + Role ARN) → STS 从 OIDC 取公钥验签、检查 sub → 返回临时凭证 → Pod 用临时凭证调用各 AWS 服务。</li>
  <li><strong>阶段 3（每次调用）</strong>：目标服务将凭证交 AWS 统一认证验证 SessionToken、权限与条件。</li>
</ul>

<h3 id="完整时序图">完整时序图</h3>

<pre><code class="language-mermaid">sequenceDiagram
    participant Admin as 管理员
    participant CCO as ccoctl
    participant S3 as AWS S3
    participant IAM as AWS IAM
    participant OIDC as AWS OIDC Provider
    participant API as kube-apiserver
    participant Pod as Pod (Ingress)
    participant STS as AWS STS
    participant Service as AWS Service (ELB)

    Note over Admin,Service: 阶段1：安装准备
    Admin-&gt;&gt;CCO: 提取 CredentialsRequest、生成密钥对
    CCO-&gt;&gt;S3: PUT OIDC 配置与公钥
    CCO-&gt;&gt;IAM: CreateOpenIDConnectProvider、CreateRole、PutRolePolicy
    CCO-&gt;&gt;Admin: 输出 Secret YAML
    Admin-&gt;&gt;API: oc apply manifests

    Note over Admin,Service: 阶段2：集群运行时
    Pod-&gt;&gt;API: 挂载 ServiceAccount
    API--&gt;&gt;Pod: JWT 令牌
    Pod-&gt;&gt;STS: AssumeRoleWithWebIdentity(JWT + Role ARN)
    STS-&gt;&gt;OIDC: 获取公钥
    STS-&gt;&gt;STS: 验签、检查 sub
    STS--&gt;&gt;Pod: 临时凭证
    Pod-&gt;&gt;Service: 调用 AWS API(带临时凭证)
    Service-&gt;&gt;STS: 验证 SessionToken
    Service--&gt;&gt;Pod: 返回结果

    Note over Pod,Service: 过期前 SDK 后台用同一 JWT 换新凭证
</code></pre>

<h3 id="关键端点">关键端点</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">阶段</th>
      <th style="text-align: left">端点类型</th>
      <th style="text-align: left">用途</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">安装</td>
      <td style="text-align: left">S3</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">/.well-known/openid-configuration</code>、公钥（如 keys.json）</td>
    </tr>
    <tr>
      <td style="text-align: left">安装</td>
      <td style="text-align: left">IAM</td>
      <td style="text-align: left">创建 OIDC 提供商、IAM Role</td>
    </tr>
    <tr>
      <td style="text-align: left">运行时</td>
      <td style="text-align: left">kube-apiserver</td>
      <td style="text-align: left">签发 JWT（TokenRequest API）</td>
    </tr>
    <tr>
      <td style="text-align: left">运行时</td>
      <td style="text-align: left">STS</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">AssumeRoleWithWebIdentity</code></td>
    </tr>
    <tr>
      <td style="text-align: left">运行时</td>
      <td style="text-align: left">S3/EC2/ELB 等</td>
      <td style="text-align: left">业务 API，每次请求验证 SessionToken</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="总结">总结</h2>

<ul>
  <li><strong>ccoctl</strong> 在手动模式下为各组件在云上创建并管理精细化、短期权限凭证，避免在集群内存储高权限长期凭证。</li>
  <li><strong>STS 流程</strong>：OIDC + ServiceAccount + IAM Role 形成双向信任；Pod 用 JWT 向 STS 证明身份，换取 1 小时有效的临时凭证；公钥用于验签，不涉及数据加密。</li>
  <li><strong>IAM Role</strong> 的权限来自角色自身的<strong>权限策略</strong>与<strong>信任策略</strong>，与 IAM User 无关；ccoctl 的执行权限来自运行它的管理员所持凭证。</li>
  <li><strong>运行时</strong>不需要安装时的高权限用户，建议安装后删除或禁用该用户，日常使用低权限账号。</li>
  <li><strong>JWT</strong> 管身份，<strong>STS 临时凭证</strong>管权限；临时凭证含 <strong>SessionToken</strong>，调用 API 必须携带；<strong>获取</strong>有 SDK 缓存（约 1 小时一次），<strong>验证</strong>每次请求都会进行。</li>
  <li><strong>kube-apiserver</strong> 签发 JWT 并暴露公钥；<strong>AWS</strong> 通过 OIDC 标准与 Kubernetes 对接，实现跨系统信任传递。</li>
</ul>

<p>整体可概括为：<strong>一次创建（ccoctl），多次使用（SDK 缓存），每次验证（SessionToken）</strong>，是 OpenShift 在公有云上实现最小权限与无长期凭证的典型生产级方式。</p>]]></content><author><name>阿男</name></author><category term="cloud-native" /><summary type="html"><![CDATA[从原理到实践讲解 OpenShift ccoctl 如何对接 AWS STS 短期凭证并完成云身份集成。]]></summary></entry><entry><title type="html">perf 与 eBPF：关系与「埋点」思路的演进</title><link href="https://weinan.tech/2026/03/13/perf-ebpf-relationship-and-probing.html" rel="alternate" type="text/html" title="perf 与 eBPF：关系与「埋点」思路的演进" /><published>2026-03-13T00:00:00+08:00</published><updated>2026-03-13T00:00:00+08:00</updated><id>https://weinan.tech/2026/03/13/perf-ebpf-relationship-and-probing</id><content type="html" xml:base="https://weinan.tech/2026/03/13/perf-ebpf-relationship-and-probing.html"><![CDATA[<style>
/* Mermaid diagram container */
.mermaid-container {
  position: relative;
  display: block;
  cursor: pointer;
  transition: opacity 0.2s;
  max-width: 100%;
  margin: 20px 0;
  overflow-x: auto;
}

.mermaid-container:hover {
  opacity: 0.8;
}

.mermaid-container svg {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Modal overlay */
.mermaid-modal {
  display: none;
  position: fixed;
  z-index: 9999;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9);
  animation: fadeIn 0.3s;
}

.mermaid-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Modal content */
.mermaid-modal-content {
  position: relative;
  width: 90vw;
  height: 90vh;
  overflow: hidden;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
}

.mermaid-modal-diagram {
  transform-origin: center center;
  transition: transform 0.2s ease;
  display: inline-block;
  min-width: 100%;
  cursor: grab;
  user-select: none;
}

.mermaid-modal-diagram.dragging {
  cursor: grabbing;
  transition: none;
}

.mermaid-modal-diagram svg {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;
}

/* Control buttons */
.mermaid-controls {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 8px;
  z-index: 10000;
}

.mermaid-btn {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
  color: #333;
  font-weight: 500;
}

.mermaid-btn:hover {
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Close button */
.mermaid-close {
  background: #f44336;
  color: white;
  border: none;
}

.mermaid-close:hover {
  background: #d32f2f;
}

/* Zoom indicator */
.mermaid-zoom-level {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 10000;
}
</style>

<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

  mermaid.initialize({
    startOnLoad: false,
    theme: 'default',
    securityLevel: 'loose',
    htmlLabels: true,
    themeVariables: {
      fontSize: '14px'
    }
  });

  let currentZoom = 1;
  let currentModal = null;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let translateX = 0;
  let translateY = 0;

  // Create modal HTML
  function createModal() {
    const modal = document.createElement('div');
    modal.className = 'mermaid-modal';
    modal.innerHTML = `
      <div class="mermaid-controls">
        <button class="mermaid-btn zoom-in">放大 +</button>
        <button class="mermaid-btn zoom-out">缩小 -</button>
        <button class="mermaid-btn zoom-reset">重置</button>
        <button class="mermaid-btn mermaid-close">关闭 ✕</button>
      </div>
      <div class="mermaid-modal-content">
        <div class="mermaid-modal-diagram"></div>
      </div>
      <div class="mermaid-zoom-level">100%</div>
    `;
    document.body.appendChild(modal);
    return modal;
  }

  // Show modal with diagram
  function showModal(diagramContent) {
    if (!currentModal) {
      currentModal = createModal();
      setupModalEvents();
    }

    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.innerHTML = diagramContent;

    // Remove any width/height attributes from SVG to make it responsive
    const svg = modalDiagram.querySelector('svg');
    if (svg) {
      svg.removeAttribute('width');
      svg.removeAttribute('height');
      svg.style.width = '100%';
      svg.style.height = 'auto';
    }

    // Setup drag functionality
    setupDrag(modalDiagram);

    currentModal.classList.add('active');
    currentZoom = 1;
    resetPosition();
    updateZoom();
    document.body.style.overflow = 'hidden';
  }

  // Hide modal
  function hideModal() {
    if (currentModal) {
      currentModal.classList.remove('active');
      document.body.style.overflow = '';
    }
  }

  // Update zoom level
  function updateZoom() {
    if (!currentModal) return;
    const diagram = currentModal.querySelector('.mermaid-modal-diagram');
    const zoomLevel = currentModal.querySelector('.mermaid-zoom-level');
    diagram.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentZoom})`;
    zoomLevel.textContent = `${Math.round(currentZoom * 100)}%`;
  }

  // Reset position when zoom changes
  function resetPosition() {
    translateX = 0;
    translateY = 0;
  }

  // Setup drag functionality
  function setupDrag(element) {
    element.addEventListener('mousedown', startDrag);
    element.addEventListener('touchstart', startDrag);
  }

  function startDrag(e) {
    if (e.type === 'mousedown' && e.button !== 0) return; // Only left click

    isDragging = true;
    const modalDiagram = currentModal.querySelector('.mermaid-modal-diagram');
    modalDiagram.classList.add('dragging');

    if (e.type === 'touchstart') {
      startX = e.touches[0].clientX - translateX;
      startY = e.touches[0].clientY - translateY;
    } else {
      startX = e.clientX - translateX;
      startY = e.clientY - translateY;
    }

    document.addEventListener('mousemove', drag);
    document.addEventListener('touchmove', drag);
    document.addEventListener('mouseup', stopDrag);
    document.addEventListener('touchend', stopDrag);
  }

  function drag(e) {
    if (!isDragging) return;
    e.preventDefault();

    if (e.type === 'touchmove') {
      translateX = e.touches[0].clientX - startX;
      translateY = e.touches[0].clientY - startY;
    } else {
      translateX = e.clientX - startX;
      translateY = e.clientY - startY;
    }

    updateZoom();
  }

  function stopDrag() {
    isDragging = false;
    const modalDiagram = currentModal?.querySelector('.mermaid-modal-diagram');
    if (modalDiagram) {
      modalDiagram.classList.remove('dragging');
    }
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('touchmove', drag);
    document.removeEventListener('mouseup', stopDrag);
    document.removeEventListener('touchend', stopDrag);
  }

  // Setup modal event listeners
  function setupModalEvents() {
    if (!currentModal) return;

    // Close button
    currentModal.querySelector('.mermaid-close').addEventListener('click', hideModal);

    // Zoom buttons
    currentModal.querySelector('.zoom-in').addEventListener('click', () => {
      currentZoom = Math.min(currentZoom + 0.25, 3);
      updateZoom();
    });

    currentModal.querySelector('.zoom-out').addEventListener('click', () => {
      currentZoom = Math.max(currentZoom - 0.25, 0.5);
      updateZoom();
    });

    currentModal.querySelector('.zoom-reset').addEventListener('click', () => {
      currentZoom = 1;
      resetPosition();
      updateZoom();
    });

    // Close on background click
    currentModal.addEventListener('click', (e) => {
      if (e.target === currentModal) {
        hideModal();
      }
    });

    // Close on ESC key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && currentModal.classList.contains('active')) {
        hideModal();
      }
    });
  }

  // Convert Jekyll-rendered code blocks to mermaid divs
  document.addEventListener('DOMContentLoaded', async function() {
    const codeBlocks = document.querySelectorAll('code.language-mermaid');

    for (const codeBlock of codeBlocks) {
      const pre = codeBlock.parentElement;
      const container = document.createElement('div');
      container.className = 'mermaid-container';

      const mermaidDiv = document.createElement('div');
      mermaidDiv.className = 'mermaid';
      mermaidDiv.textContent = codeBlock.textContent;

      container.appendChild(mermaidDiv);
      pre.replaceWith(container);
    }

    // Render all mermaid diagrams
    try {
      await mermaid.run({
        querySelector: '.mermaid'
      });
      console.log('Mermaid diagrams rendered successfully');
    } catch (error) {
      console.error('Mermaid rendering error:', error);
    }

    // Add click handlers to rendered diagrams
    document.querySelectorAll('.mermaid-container').forEach((container, index) => {
      // Find the rendered SVG inside the container
      const svg = container.querySelector('svg');
      if (!svg) {
        console.warn(`No SVG found in container ${index}`);
        return;
      }

      // Make the container clickable
      container.style.cursor = 'pointer';
      container.title = '点击查看大图';

      container.addEventListener('click', function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Clone the SVG for the modal
        const svgClone = svg.cloneNode(true);
        const tempDiv = document.createElement('div');
        tempDiv.appendChild(svgClone);

        console.log('Opening modal for diagram', index);
        showModal(tempDiv.innerHTML);
      });

      console.log(`Click handler added to diagram ${index}`);
    });
  });
</script>

<p>perf 子系统和 eBPF 并非两个孤立的子系统，而是<strong>共享基础设施、互相协作</strong>的伙伴。本文从两者在内核中的协作关系出发，再对比它们在「埋点」与数据处理思路上的本质区别，并对照主线内核代码做简要核对。</p>

<h2 id="一perf-与-ebpf-的紧密关系">一、perf 与 eBPF 的紧密关系</h2>

<h3 id="1-共享内核基础设施perf_events-是基石">1. 共享内核基础设施：perf_events 是基石</h3>

<p>eBPF 的很多核心功能都建立在 <code class="language-plaintext highlighter-rouge">perf_events</code> 子系统提供的机制之上；<code class="language-plaintext highlighter-rouge">perf_events</code> 为 eBPF 的高效数据输出和硬件性能计数读取提供了通道。</p>

<h4 id="数据输出通道bpf_map_type_perf_event_array">数据输出通道：BPF_MAP_TYPE_PERF_EVENT_ARRAY</h4>

<p>当 eBPF 程序需要向用户空间发送大量数据时（例如追踪每次系统调用的参数），通常不直接操作文件或网络，而是通过一类特殊的 eBPF Map——<code class="language-plaintext highlighter-rouge">BPF_MAP_TYPE_PERF_EVENT_ARRAY</code>。</p>

<ul>
  <li><strong>工作原理</strong>：该 Map 的每个元素对应一个 <code class="language-plaintext highlighter-rouge">perf_event</code> 的文件描述符。eBPF 程序通过辅助函数 <code class="language-plaintext highlighter-rouge">bpf_perf_event_output()</code> 把数据写入该 Map，内核会将这些数据写入对应 perf 事件的<strong>环形缓冲区（ring buffer）</strong>。</li>
  <li><strong>优势</strong>：复用 perf 子系统的内核-用户空间数据传输机制，实现无锁、高性能的数据通路，无需为 eBPF 再实现一套类似设施。</li>
</ul>

<p>内核中该 Map 类型的实现位于 <code class="language-plaintext highlighter-rouge">kernel/bpf/arraymap.c</code>，例如 <code class="language-plaintext highlighter-rouge">perf_event_array_map_ops</code>（<code class="language-plaintext highlighter-rouge">perf_event_fd_array_get_ptr</code> 等）负责将 Map 中的 fd 解析为 <code class="language-plaintext highlighter-rouge">perf_event</code> 指针并与 ring buffer 关联；<code class="language-plaintext highlighter-rouge">kernel/bpf/verifier.c</code> 与 <code class="language-plaintext highlighter-rouge">kernel/bpf/syscall.c</code> 中对 <code class="language-plaintext highlighter-rouge">BPF_MAP_TYPE_PERF_EVENT_ARRAY</code> 的校验与更新逻辑也与之对应。</p>

<h4 id="读取性能计数器bpf_perf_event_read-系列">读取性能计数器：bpf_perf_event_read 系列</h4>

<p>eBPF 程序还可以通过 perf 子系统读取性能数据。辅助函数 <code class="language-plaintext highlighter-rouge">bpf_perf_event_read()</code> 和 <code class="language-plaintext highlighter-rouge">bpf_perf_event_read_value()</code> 用于读取由 <code class="language-plaintext highlighter-rouge">perf_events</code> 管理的硬件性能计数器（如 CPU 周期、缓存未命中等）的值，从而在 eBPF 中把自定义追踪逻辑与底层硬件性能数据结合。例如 <code class="language-plaintext highlighter-rouge">tools/perf/util/bpf_skel/bpf_prog_profiler.bpf.c</code>、<code class="language-plaintext highlighter-rouge">bperf_leader.bpf.c</code> 中就有对 <code class="language-plaintext highlighter-rouge">bpf_perf_event_read_value()</code> 的典型用法。</p>

<h3 id="2-程序类型协作bpf_prog_type_perf_event">2. 程序类型协作：BPF_PROG_TYPE_PERF_EVENT</h3>

<p>内核定义了专门的 eBPF 程序类型 <code class="language-plaintext highlighter-rouge">BPF_PROG_TYPE_PERF_EVENT</code>，允许将 eBPF 程序直接附加到某个 perf 事件上。</p>

<ul>
  <li><strong>工作方式</strong>：通过 <code class="language-plaintext highlighter-rouge">perf_event_open()</code> 创建 perf 事件时，可以指定一个 eBPF 程序作为该事件的<strong>溢出处理函数（overflow handler）</strong>。当事件触发（例如性能计数器达到采样周期，或 tracepoint 被命中）时，内核会调用该 eBPF 程序。相关逻辑见 <code class="language-plaintext highlighter-rouge">kernel/events/core.c</code> 中的 <code class="language-plaintext highlighter-rouge">bpf_overflow_handler</code> 以及 <code class="language-plaintext highlighter-rouge">perf_event_attach_bpf_prog()</code>；<code class="language-plaintext highlighter-rouge">kernel/trace/bpf_trace.c</code> 中则实现了 <code class="language-plaintext highlighter-rouge">perf_event_attach_bpf_prog()</code> 的具体附加流程。</li>
  <li><strong>应用场景</strong>：可用于自定义、低开销的采样与分析，例如按 CPU 周期采样时在 eBPF 中记录调用栈或做过滤聚合，比传统 perf 采样更灵活。</li>
</ul>

<h3 id="3-用户空间工具整合从bpf-事件到bpf-脚手架">3. 用户空间工具整合：从「BPF 事件」到「BPF 脚手架」</h3>

<p>在用户空间工具 <code class="language-plaintext highlighter-rouge">perf</code> 中，与 eBPF 的集成方式也在演进。</p>

<ul>
  <li><strong>过去</strong>：<code class="language-plaintext highlighter-rouge">perf</code> 曾提供「BPF 事件」机制，允许将编译好的 eBPF 对象文件作为事件加载，但使用和维护成本较高。</li>
  <li><strong>现在</strong>：<code class="language-plaintext highlighter-rouge">perf</code> 更多采用 <strong>BPF skeleton</strong>（libbpf 生成的脚手架）来加载和附加 eBPF 程序。例如 <code class="language-plaintext highlighter-rouge">perf trace</code> 使用 <code class="language-plaintext highlighter-rouge">tools/perf/util/bpf_skel/augmented_raw_syscalls.bpf.c</code> 等实现系统调用参数增强；<code class="language-plaintext highlighter-rouge">off_cpu.bpf.c</code>、<code class="language-plaintext highlighter-rouge">bpf_prog_profiler.bpf.c</code>、<code class="language-plaintext highlighter-rouge">bperf_leader.bpf.c</code> 等均使用 <code class="language-plaintext highlighter-rouge">BPF_MAP_TYPE_PERF_EVENT_ARRAY</code> 与 <code class="language-plaintext highlighter-rouge">bpf_perf_event_output()</code> / <code class="language-plaintext highlighter-rouge">bpf_perf_event_read_value()</code>，与内核实现一致。</li>
</ul>

<h3 id="4-安全与权限统一cap_perfmon">4. 安全与权限统一：CAP_PERFMON</h3>

<p>从权限模型看，perf 与 eBPF 的追踪能力由同一 capability 约束。内核在 <code class="language-plaintext highlighter-rouge">include/uapi/linux/capability.h</code> 中定义：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
 * Allow system performance and observability privileged operations
 * using perf_events, i915_perf and other kernel subsystems
 */</span>
<span class="cp">#define CAP_PERFMON    38
</span></code></pre></div></div>

<p>同一文件中注释说明：<strong>CAP_PERFMON</strong> 与 <strong>CAP_BPF</strong> 共同用于放宽对追踪类 BPF 程序的限制（如指针转整数、部分 speculation 加固的绕过、<code class="language-plaintext highlighter-rouge">bpf_probe_read</code> / <code class="language-plaintext highlighter-rouge">bpf_trace_printk</code> 等），且「CAP_PERFMON and CAP_BPF are required to load tracing programs」。因此，拥有 <code class="language-plaintext highlighter-rouge">CAP_PERFMON</code> 的进程既可以做 perf 采样，也可以在具备 CAP_BPF 等条件下加载用于追踪的 eBPF 程序，两者在权限上统一。</p>

<h3 id="小结关系总览">小结：关系总览</h3>

<table>
  <thead>
    <tr>
      <th>关系层面</th>
      <th>描述</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>基础设施共享</strong></td>
      <td>eBPF 依赖 perf 的<strong>环形缓冲区</strong>和<strong>硬件计数器</strong>，通过 <code class="language-plaintext highlighter-rouge">BPF_MAP_TYPE_PERF_EVENT_ARRAY</code> 和 <code class="language-plaintext highlighter-rouge">bpf_perf_event_read</code> 等实现高效数据交互。</td>
    </tr>
    <tr>
      <td><strong>程序类型协作</strong></td>
      <td><code class="language-plaintext highlighter-rouge">BPF_PROG_TYPE_PERF_EVENT</code> 允许将 eBPF 程序作为 perf 事件的溢出处理器，实现自定义采样逻辑。</td>
    </tr>
    <tr>
      <td><strong>工具整合</strong></td>
      <td><code class="language-plaintext highlighter-rouge">perf</code> 从早期的「BPF 事件」演进为使用 libbpf 的 <strong>BPF skeleton</strong> 加载 eBPF 程序。</td>
    </tr>
    <tr>
      <td><strong>安全模型</strong></td>
      <td><code class="language-plaintext highlighter-rouge">CAP_PERFMON</code> 与 <code class="language-plaintext highlighter-rouge">CAP_BPF</code> 共同控制对 perf_events 与 eBPF 追踪能力的访问。</td>
    </tr>
  </tbody>
</table>

<p>下图概括 eBPF 与 perf 在内核中的架构关系、挂载点及数据通道（用户空间工具、系统调用、eBPF 核心与 Map、动态/静态探针、perf_events 子系统及与硬件的交互）：</p>

<pre><code class="language-mermaid">graph TB
    subgraph Userspace["用户空间 (Userspace)"]
        Tools["perf CLI / bpftrace / BCC"]
        Libs["libbpf / libbcc"]
    end

    subgraph Kernel["内核空间 (Kernel Space)"]
        subgraph Syscall["系统调用层"]
            BPF_Syscall["bpf() 系统调用"]
            Perf_Syscall["perf_event_open() 系统调用"]
        end

        subgraph BPF_Core["eBPF核心虚拟机"]
            Verifier["验证器 (Verifier)"]
            JIT["JIT编译器"]
            Helper["辅助函数 (Helper Funcs)"]
        end

        subgraph BPF_Maps["eBPF Map存储系统"]
            Hash_Map["Hash Map"]
            Array_Map["Array Map"]
            Perf_Array["Perf Event Array"]
            Ring_Buffer["Ring Buffer Map"]
        end

        subgraph BPF_Hooks["eBPF程序挂载点"]
            subgraph Dynamic["动态探针"]
                Kprobe["kprobe (内核函数)"]
                Uprobe["uprobe (用户函数)"]
            end

            subgraph Static["静态探针"]
                Tracepoint["tracepoint (内核静态点)"]
                USDT["USDT (用户静态点)"]
            end

            subgraph Network["网络钩子"]
                XDP["XDP (网卡驱动层)"]
                TC["TC (协议栈)"]
                Socket["Socket Filter"]
            end

            subgraph Perf_Collab["Perf协作层"]
                Perf_Event_Prog["BPF_PROG_TYPE_PERF_EVENT"]
            end
        end

        subgraph Perf_Subsystem["perf_events子系统"]
            Perf_RingBuffer["环形缓冲区 (Ring Buffer)"]
            Perf_PMU["硬件PMU计数器"]
            Perf_Events["软件事件计数"]
            Perf_Tracepoint["tracepoint管理"]
        end
    end

    subgraph Hardware["硬件层"]
        CPU["CPU (含PMU)"]
        NIC["网卡"]
        Memory["内存"]
    end

    Tools --&gt; Libs
    Libs --&gt; BPF_Syscall
    Libs --&gt; Perf_Syscall

    BPF_Syscall --&gt; BPF_Core
    Perf_Syscall --&gt; Perf_Subsystem

    BPF_Core --&gt; BPF_Maps
    BPF_Core --&gt; BPF_Hooks

    BPF_Hooks -.-&gt; Perf_Event_Prog
    Perf_Event_Prog --&gt; Perf_Subsystem

    Perf_Array -.-&gt; Perf_RingBuffer
    Ring_Buffer -.-&gt; Perf_RingBuffer

    Kprobe -.-&gt; |"动态插桩"| Kernel_Funcs["内核任意函数"]
    Uprobe -.-&gt; |"动态插桩"| User_Funcs["用户态任意函数"]
    Tracepoint -.-&gt; |"静态预埋"| Kernel_Points["内核预定义点"]
    USDT -.-&gt; |"静态预埋"| User_Points["用户态预定义点"]

    XDP -.-&gt; |"最早阶段"| NIC
    TC -.-&gt; |"协议栈入口"| Network_Stack["内核协议栈"]

    Perf_PMU --&gt; CPU
    Perf_PMU --&gt; Memory

    BPF_Maps --&gt; |"数据输出"| Libs
    Perf_RingBuffer --&gt; |"性能数据"| Libs
</code></pre>

<p>（若站点支持 Mermaid 渲染，上图会显示为流程图；否则会显示为代码块。）</p>

<p>下图从<strong>用户空间工具生态</strong>视角概括 perf、BCC、bpftrace、libbpf 如何通过 <code class="language-plaintext highlighter-rouge">bpf()</code> 系统调用进入内核 eBPF 子系统并最终作用到硬件：</p>

<pre><code class="language-mermaid">flowchart TD
    subgraph Userspace [用户空间工具生态]
        direction TB
        Tools["perf / 系统工具"] --&gt; |"直接调用"| Syscall
        BCC["BCC工具集&lt;br/&gt;(BPF Compiler Collection)"] --&gt; |"封装复杂逻辑&lt;br/&gt;提供70+现成工具"| Syscall
        bpftrace["bpftrace&lt;br/&gt;(高阶层级语言)"] --&gt; |"基于BCC/libbpf&lt;br/&gt;提供脚本语言"| Syscall
        Libbpf["libbpf&lt;br/&gt;(C库，支持CO-RE)"] --&gt; |"轻量级库&lt;br/&gt;直接控制"| Syscall
    end

    subgraph Kernel [内核空间]
        Syscall["bpf() 系统调用"]
        Syscall --&gt; BPFSubsys["eBPF子系统&lt;br/&gt;(验证器、JIT、辅助函数)"]
        BPFSubsys --&gt; Hooks["挂载点&lt;br/&gt;(kprobe/uprobe/tracepoint/...)"]
    end

    subgraph Hardware [硬件]
        CPU["CPU (含PMU)"]
        Mem["内存"]
        Dev["设备"]
    end

    Hooks --&gt; Hardware

    style BCC fill:#e1f5fe,stroke:#01579b
    style bpftrace fill:#fff3e0,stroke:#e65100
    style Libbpf fill:#f3e5f5,stroke:#4a148c
    style Syscall fill:#e8e8e8,stroke:#666
</code></pre>

<hr />

<h2 id="二埋点思路的演进预制传感器-vs-可编程探头">二、「埋点」思路的演进：预制传感器 vs 可编程探头</h2>

<p>「埋点」是两者工作的基础，但<strong>埋点方式与后续数据处理思路有本质区别</strong>。</p>

<ul>
  <li><strong>传统方式（含 perf 的多数功能）</strong>：像在内核里预先装好一批<strong>固定的、功能单一的传感器</strong>，需要什么数据就去读对应传感器的读数。</li>
  <li><strong>eBPF 方式</strong>：像提供一种可<strong>安全、动态挂载并可编程的探头</strong>，可以自己决定测什么、怎么测、以及在内核里做哪些初步处理。</li>
</ul>

<h3 id="1-什么是埋点">1. 什么是「埋点」？</h3>

<p>无论是 perf 还是 eBPF，核心都是在<strong>内核（及用户态）关键路径上放置探测点</strong>，在事件发生时（系统调用、网络包、函数调用等）采集信息。这些探测点是可观测性的数据源。</p>

<h3 id="2-perf-的埋点思路预制传感器">2. perf 的埋点思路：预制传感器</h3>

<p>perf 主要利用<strong>已有</strong>的事件源与埋点：</p>

<ul>
  <li><strong>硬件事件</strong>：利用 CPU 的 <strong>PMU（Performance Monitoring Unit）</strong> 等硬件计数器，统计周期、缓存未命中、分支预测失败等；perf 负责配置与读取。</li>
  <li><strong>软件事件</strong>：内核维护的统计（如上下文切换、缺页等），perf 直接读取。</li>
  <li><strong>Tracepoints（静态埋点）</strong>：内核在关键路径上预先放置的静态探测点（系统调用入口/出口、调度、文件系统等），位置和格式在编译期确定；perf 通过启用这些 tracepoint 采集数据。</li>
</ul>

<p>perf 的角色更接近「仪表盘操作员」：知道所有预制传感器在哪里、如何读，并以较低开销（尤其是采样）汇总成报告。</p>

<h3 id="3-kprobe-与-uprobe机制与内核支持">3. kprobe 与 uprobe：机制与内核支持</h3>

<p>eBPF 的「动态埋点」能力建立在内核的 <strong>kprobe</strong> 与 <strong>uprobe</strong> 机制之上。二者允许在<strong>不重新编译内核或目标程序</strong>的前提下，在运行时把探测点挂在任意内核函数或用户态地址上，下面结合主线内核代码说明其含义与实现要点。</p>

<h4 id="kprobekernel-probe">kprobe（Kernel Probe）</h4>

<p><strong>kprobe</strong> 用于在内核任意函数（或指定偏移）处插入探测。调用方只需提供<strong>符号名</strong>（如 <code class="language-plaintext highlighter-rouge">do_sys_open</code>）或「模块 + 偏移」；内核在<strong>注册时</strong>通过 <code class="language-plaintext highlighter-rouge">kallsyms_lookup_name()</code>（见 <code class="language-plaintext highlighter-rouge">kernel/kprobes.c</code>）解析出该符号的地址，无需在编译期固定探测位置。</p>

<ul>
  <li><strong>为何是「动态」</strong>：探测地址在 <strong>register_kprobe()</strong> 时才确定。内核维护符号表（kallsyms），可加载模块的符号在模块加载后也可解析；因此可以在不改源码、不重启的前提下，对当前运行内核的任意已导出或可见符号下 probe。</li>
  <li><strong>内核做了哪些支持</strong>：
    <ul>
      <li><strong>插桩方式</strong>：在探测地址处把<strong>第一条指令</strong>替换为架构相关的断点指令（如 x86 的 <strong>INT3</strong>，arm64 的 <strong>BRK</strong>）。<code class="language-plaintext highlighter-rouge">arch_arm_kprobe()</code> / <code class="language-plaintext highlighter-rouge">arch_disarm_kprobe()</code> 负责写入/恢复（见 <code class="language-plaintext highlighter-rouge">arch/x86/kernel/kprobes/core.c</code>：<code class="language-plaintext highlighter-rouge">text_poke(p-&gt;addr, &amp;int3, 1)</code> 与恢复 <code class="language-plaintext highlighter-rouge">p-&gt;opcode</code>）。</li>
      <li><strong>原始指令执行</strong>：断点命中后，先执行注册的 handler（如 eBPF 程序），再<strong>单步执行被替换掉的那条指令</strong>。内核在可执行内存中为每条 kprobe 分配「指令槽」（<code class="language-plaintext highlighter-rouge">struct kprobe_insn_page</code>，见 <code class="language-plaintext highlighter-rouge">kernel/kprobes.c</code>），把原始指令拷贝到槽中执行，避免在运行时代码上直接执行可能受限于可执行页、相对寻址等约束。</li>
      <li><strong>优化路径（CONFIG_OPTPROBES）</strong>：部分架构还可将「断点 + 单步」优化为「跳转指令」，减少单步与 cache 失效的开销。</li>
    </ul>
  </li>
</ul>

<p>相关定义与流程集中在 <code class="language-plaintext highlighter-rouge">kernel/kprobes.c</code>（通用逻辑、哈希表 <code class="language-plaintext highlighter-rouge">kprobe_table</code>、注册/卸载）、<code class="language-plaintext highlighter-rouge">include/linux/kprobes.h</code>（<code class="language-plaintext highlighter-rouge">struct kprobe</code>：<code class="language-plaintext highlighter-rouge">addr</code>、<code class="language-plaintext highlighter-rouge">symbol_name</code>、<code class="language-plaintext highlighter-rouge">offset</code>、<code class="language-plaintext highlighter-rouge">opcode</code>、<code class="language-plaintext highlighter-rouge">ainsn</code> 等），以及各架构的 <code class="language-plaintext highlighter-rouge">arch/*/kernel/kprobes/</code>（如 <code class="language-plaintext highlighter-rouge">arch_arm_kprobe</code>、指令槽与单步）。</p>

<h4 id="uprobeuser-space-probe">uprobe（User-space Probe）</h4>

<p><strong>uprobe</strong> 用于在用户态程序的指定<strong>虚拟地址</strong>处插入探测。通常用「可执行文件 inode + 文件内偏移」或「path + offset」描述位置；同一偏移可对应多个已映射该文件的进程，内核会按 <strong>mmap</strong> 在各自地址空间写入断点。</p>

<ul>
  <li><strong>为何是「动态」</strong>：探测的「文件 + 偏移」在<strong>注册 uprobe 时</strong>指定，无需重新编译或替换用户程序。只要目标进程已将该文件映射为可执行，内核会在其对应 VMA 的虚拟地址上安装断点；新 fork 的进程若映射同一文件，也会在首次访问时通过 <strong>MMU notifier</strong> 等路径被插入断点（见 <code class="language-plaintext highlighter-rouge">kernel/events/uprobes.c</code> 中的 <code class="language-plaintext highlighter-rouge">install_breakpoint</code>、<code class="language-plaintext highlighter-rouge">set_swbp</code>）。</li>
  <li><strong>内核做了哪些支持</strong>：
    <ul>
      <li><strong>插桩方式</strong>：在用户空间对应页上写入架构的<strong>软断点</strong>（如 x86 的 INT3）。<code class="language-plaintext highlighter-rouge">set_swbp()</code> 通过 <code class="language-plaintext highlighter-rouge">uprobe_write_opcode()</code> 把断点写进目标 VMA；卸载时 <code class="language-plaintext highlighter-rouge">set_orig_insn()</code> 恢复原指令（<code class="language-plaintext highlighter-rouge">kernel/events/uprobes.c</code>）。</li>
      <li><strong>原始指令执行（XOL）</strong>：用户态不能像内核那样随意在任意可执行页单步「一条指令」而不影响相邻指令，因此 uprobe 使用 <strong>XOL（Execute Out of Line）</strong>：为每个被探测的进程维护一块<strong>专用可执行映射</strong>（<code class="language-plaintext highlighter-rouge">struct xol_area</code>，名如 <code class="language-plaintext highlighter-rouge">[uprobes]</code>），把「被替换掉的那条指令」拷贝到 XOL 槽中执行，执行完再回到原流程。见 <code class="language-plaintext highlighter-rouge">kernel/events/uprobes.c</code> 中的 <code class="language-plaintext highlighter-rouge">xol_area</code>、<code class="language-plaintext highlighter-rouge">xol_fault</code>、<code class="language-plaintext highlighter-rouge">xol_add_vma</code> 以及 <code class="language-plaintext highlighter-rouge">arch_uprobe_analyze_insn()</code> 对指令的分析与 ixol 的生成。</li>
    </ul>
  </li>
</ul>

<p>uprobe 的消费者通过 <code class="language-plaintext highlighter-rouge">struct uprobe_consumer</code>（<code class="language-plaintext highlighter-rouge">handler</code>、<code class="language-plaintext highlighter-rouge">ret_handler</code>、<code class="language-plaintext highlighter-rouge">filter</code>）挂到 <code class="language-plaintext highlighter-rouge">struct uprobe</code> 上；eBPF 等会复用这套基础设施，把 BPF 程序作为 consumer 挂到同一 uprobe。</p>

<h4 id="小结动态的含义与依赖">小结：动态的含义与依赖</h4>

<table>
  <thead>
    <tr>
      <th>机制</th>
      <th>探测对象</th>
      <th>「动态」体现</th>
      <th>内核关键支持</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>kprobe</strong></td>
      <td>内核函数（符号或地址）</td>
      <td>地址在 <strong>register_kprobe</strong> 时由 kallsyms 等解析，无需编译期埋点</td>
      <td>断点替换（arch_arm/disarm）、指令槽单步、可选跳转优化</td>
    </tr>
    <tr>
      <td><strong>uprobe</strong></td>
      <td>用户态（文件 + 偏移 → 各进程 VMA）</td>
      <td>在 <strong>register_uprobe</strong> 时指定 offset，按 mmap 在运行时插入断点</td>
      <td>用户态页写断点（set_swbp/set_orig_insn）、XOL 执行原指令</td>
    </tr>
  </tbody>
</table>

<p>eBPF 的 kprobe/uprobe 程序类型（如 <code class="language-plaintext highlighter-rouge">BPF_PROG_TYPE_KPROBE</code>）即是在上述机制之上，把「断点命中后的处理」换成经 verifier 校验的 BPF 字节码，从而在保持动态性的同时提供可编程、安全的内核/用户态探测能力<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">1</a></sup><sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">2</a></sup><sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>。</p>

<h3 id="4-ebpf-的埋点思路可编程探头">4. eBPF 的埋点思路：可编程探头</h3>

<p>eBPF 在「埋点」上的不同在于<strong>动态与可编程</strong>：</p>

<ul>
  <li><strong>动态埋点（kprobe / uprobe）</strong>：若内核或应用没有现成探测点，eBPF 可以在<strong>任意内核函数（kprobe）或用户态函数（uprobe）</strong> 入口/出口动态挂载探测逻辑，无需改内核源码或重新部署固定 tracepoint（其机制见上一小节）。</li>
  <li><strong>复用现有埋点</strong>：eBPF 也可挂到现有 tracepoint 上；与 perf 不同的是，触发时不仅可以读预定义数据，还可以<strong>执行自定义逻辑</strong>做过滤、聚合、计算。</li>
  <li><strong>处理下放</strong>：perf 通常把原始或轻度聚合数据经 ring buffer 传到用户空间再由 <code class="language-plaintext highlighter-rouge">perf</code> 分析；eBPF 则允许<strong>把一部分处理逻辑放在内核</strong>（例如只统计延迟 &gt; 100ms 的请求、或在内核里算好直方图），仅把结果或关键数据交给用户空间，减少数据拷贝与上下文切换。</li>
</ul>

<h3 id="对比总结">对比总结</h3>

<table>
  <thead>
    <tr>
      <th>特性</th>
      <th>perf</th>
      <th>eBPF</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>埋点类型</strong></td>
      <td>主要依赖<strong>预制</strong>的硬件事件、软件事件和静态 tracepoint。</td>
      <td>既可用预制 tracepoint，更核心的是<strong>动态</strong> kprobe/uprobe。</td>
    </tr>
    <tr>
      <td><strong>数据处理</strong></td>
      <td>主要在<strong>用户空间</strong>；内核负责采集和输出原始/轻度聚合数据。</td>
      <td><strong>内核与用户空间协同</strong>；可在内核执行聚合、过滤、统计，只下发结果或关键数据。</td>
    </tr>
    <tr>
      <td><strong>灵活性</strong></td>
      <td>相对固定，只能获取预设格式的数据。</td>
      <td>高；可访问函数上下文、参数、返回值，并按需编写处理逻辑。</td>
    </tr>
    <tr>
      <td><strong>编程模型</strong></td>
      <td>通过命令行参数与预定义事件配置。</td>
      <td>用 C 等编写小程序，经内核验证后执行。</td>
    </tr>
  </tbody>
</table>

<p>因此，两者都建立在「埋点」之上，但 <strong>eBPF 的突破在于：在埋点之上增加了动态创建探测点、以及在内核中安全执行自定义处理逻辑的能力</strong>，从「读仪表」演进到「可编程探头」。</p>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:5">
      <p><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/kprobes.c">kernel/kprobes.c</a> - kprobe 通用逻辑：注册/卸载、kallsyms 解析、指令槽与 arm/disarm <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6">
      <p><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/kprobes/core.c">arch/x86/kernel/kprobes/core.c - arch_arm_kprobe / arch_disarm_kprobe</a> - x86 上 kprobe 断点写入与恢复（INT3 / text_poke） <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7">
      <p><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/events/uprobes.c">kernel/events/uprobes.c</a> - uprobe 实现：set_swbp/set_orig_insn、XOL（xol_area）、install_breakpoint <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="linux-kernel" /><summary type="html"><![CDATA[对比 perf 与 eBPF 的定位与演进，讨论内核可观测性中「埋点」思路的设计取舍。]]></summary></entry><entry><title type="html">Linux 内核 Rust 代码中 unsafe 使用场景统计分析</title><link href="https://weinan.tech/2026/03/04/kernel-rust-unsafe-usage-analysis.html" rel="alternate" type="text/html" title="Linux 内核 Rust 代码中 unsafe 使用场景统计分析" /><published>2026-03-04T00:00:00+08:00</published><updated>2026-03-04T00:00:00+08:00</updated><id>https://weinan.tech/2026/03/04/kernel-rust-unsafe-usage-analysis</id><content type="html" xml:base="https://weinan.tech/2026/03/04/kernel-rust-unsafe-usage-analysis.html"><![CDATA[<p>与「只有调用 C 才需要 unsafe」的常见误解不同，但凡涉及硬件或与内核/硬件边界交互（如驱动、MMIO、DMA），在 Rust 里几乎必然要使用 <code class="language-plaintext highlighter-rouge">unsafe</code>，这与是否通过 FFI 调 C 无必然关系——例如 Embassy 等纯 Rust 裸机/驱动生态里，硬件相关操作同样大量集中在 <code class="language-plaintext highlighter-rouge">unsafe</code> 中。本文基于对主线 Linux 内核 <code class="language-plaintext highlighter-rouge">rust/</code> 目录的统计与代码抽样，归纳当前内核 Rust 中 <code class="language-plaintext highlighter-rouge">unsafe</code> 的实际使用场景，并辅以真实内核代码说明。</p>

<h2 id="统计概览">统计概览</h2>

<p>对主线内核 <code class="language-plaintext highlighter-rouge">rust/</code> 树使用 <code class="language-plaintext highlighter-rouge">cloc</code>、<code class="language-plaintext highlighter-rouge">ripgrep</code>（rg）统计的结果如下。</p>

<table>
  <thead>
    <tr>
      <th>项目</th>
      <th>数量</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Rust 源文件数</strong></td>
      <td>130</td>
      <td><code class="language-plaintext highlighter-rouge">find . -name '*.rs' \| wc -l</code></td>
    </tr>
    <tr>
      <td><strong>Rust 代码行数</strong></td>
      <td>16 987</td>
      <td>cloc 统计的 code 行（不含 3 471 blank、17 039 comment）</td>
    </tr>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">unsafe</code> 出现总次数</strong></td>
      <td>1 891</td>
      <td><code class="language-plaintext highlighter-rouge">rg -c '\bunsafe\b'</code> 各文件计数之和</td>
    </tr>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">unsafe { ... }</code> 块</strong></td>
      <td>1 252</td>
      <td><code class="language-plaintext highlighter-rouge">rg -c 'unsafe\s*\{'</code></td>
    </tr>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">unsafe fn</code></strong></td>
      <td>268</td>
      <td>含 <code class="language-plaintext highlighter-rouge">unsafe fn</code> 声明与 trait 中的 <code class="language-plaintext highlighter-rouge">unsafe fn</code></td>
    </tr>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">unsafe impl</code></strong></td>
      <td>90</td>
      <td> </td>
    </tr>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">unsafe trait</code></strong></td>
      <td>30</td>
      <td> </td>
    </tr>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">unsafe fn</code> / <code class="language-plaintext highlighter-rouge">unsafe impl</code> / <code class="language-plaintext highlighter-rouge">unsafe trait</code> 合计</strong></td>
      <td>388</td>
      <td>268 + 90 + 30</td>
    </tr>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">// SAFETY:</code> 注释</strong></td>
      <td>1 413</td>
      <td><code class="language-plaintext highlighter-rouge">rg -c '// SAFETY:'</code></td>
    </tr>
  </tbody>
</table>

<p>约 75% 的 <code class="language-plaintext highlighter-rouge">unsafe</code> 使用配有 <code class="language-plaintext highlighter-rouge">// SAFETY:</code> 说明（1 413 / 1 891 ≈ 74.7%），便于审查与维护。</p>

<h2 id="使用场景分类">使用场景分类</h2>

<h3 id="1-调用-c-内核-apiffi--bindings">1. 调用 C 内核 API（FFI / bindings）</h3>

<p>通过 bindgen 生成的 C 内核 API 在 Rust 侧一律通过 <code class="language-plaintext highlighter-rouge">bindings::</code> 调用，且这些调用均出现在 <code class="language-plaintext highlighter-rouge">unsafe</code> 块或 <code class="language-plaintext highlighter-rouge">unsafe fn</code> 内。统计显示 <strong><code class="language-plaintext highlighter-rouge">bindings::</code> 出现约 1062 次</strong>，是 <code class="language-plaintext highlighter-rouge">unsafe</code> 的一大来源。</p>

<p>典型用法：取得 C 结构体指针、解引用其字段作为参数，再调用 C 函数。例如 PHY 寄存器读写的纯「FFI + 裸指针解引用」：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// rust/kernel/net/phy/reg.rs（节选）</span>
<span class="k">impl</span> <span class="n">Register</span> <span class="k">for</span> <span class="n">C22</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">read</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">dev</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Device</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">u16</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">phydev</span> <span class="o">=</span> <span class="n">dev</span><span class="na">.0</span><span class="nf">.get</span><span class="p">();</span>
        <span class="c1">// SAFETY: `phydev` is pointing to a valid object by the type invariant of `Device`.</span>
        <span class="c1">// So it's just an FFI call, open code of `phy_read()` with a valid `phy_device` pointer</span>
        <span class="k">let</span> <span class="n">ret</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
            <span class="nn">bindings</span><span class="p">::</span><span class="nf">mdiobus_read</span><span class="p">((</span><span class="o">*</span><span class="n">phydev</span><span class="p">)</span><span class="py">.mdio.bus</span><span class="p">,</span> <span class="p">(</span><span class="o">*</span><span class="n">phydev</span><span class="p">)</span><span class="py">.mdio.addr</span><span class="p">,</span> <span class="k">self</span><span class="na">.0</span><span class="nf">.into</span><span class="p">())</span>
        <span class="p">};</span>
        <span class="nf">to_result</span><span class="p">(</span><span class="n">ret</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="nf">Ok</span><span class="p">(</span><span class="n">ret</span> <span class="k">as</span> <span class="nb">u16</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">write</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">dev</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Device</span><span class="p">,</span> <span class="n">val</span><span class="p">:</span> <span class="nb">u16</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">phydev</span> <span class="o">=</span> <span class="n">dev</span><span class="na">.0</span><span class="nf">.get</span><span class="p">();</span>
        <span class="c1">// SAFETY: ... (同上)</span>
        <span class="nf">to_result</span><span class="p">(</span><span class="k">unsafe</span> <span class="p">{</span>
            <span class="nn">bindings</span><span class="p">::</span><span class="nf">mdiobus_write</span><span class="p">((</span><span class="o">*</span><span class="n">phydev</span><span class="p">)</span><span class="py">.mdio.bus</span><span class="p">,</span> <span class="p">(</span><span class="o">*</span><span class="n">phydev</span><span class="p">)</span><span class="py">.mdio.addr</span><span class="p">,</span> <span class="k">self</span><span class="na">.0</span><span class="nf">.into</span><span class="p">(),</span> <span class="n">val</span><span class="p">)</span>
        <span class="p">})</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这里 <code class="language-plaintext highlighter-rouge">unsafe</code> 同时覆盖：<strong>对 C 指针的解引用</strong>（<code class="language-plaintext highlighter-rouge">(*phydev).mdio.bus</code>）和 <strong>FFI 调用</strong>（<code class="language-plaintext highlighter-rouge">bindings::mdiobus_read</code> / <code class="language-plaintext highlighter-rouge">mdiobus_write</code>）。也就是说，与硬件打交道的驱动路径上，即便逻辑是「读/写寄存器」，在 Rust 侧也会体现为「裸指针 + C API」，因而必然落在 <code class="language-plaintext highlighter-rouge">unsafe</code> 内。</p>

<h3 id="2-硬件与并发语义volatile-与-read_once--write_once">2. 硬件与并发语义：volatile 与 READ_ONCE / WRITE_ONCE</h3>

<p>与「硬件或外部可写内存」的交互常需要 volatile 或与内核 READ_ONCE/WRITE_ONCE 等价的语义；这类操作在 Rust 中同样必须放在 <code class="language-plaintext highlighter-rouge">unsafe</code> 里，且<strong>与是否调用 C 无关</strong>——纯 Rust 的 MMIO/寄存器访问（如 Embassy 中的实现）也是如此。</p>

<p><strong>（1）文件描述符标志：对应 READ_ONCE</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// rust/kernel/fs/file.rs（节选）</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">flags</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u32</span> <span class="p">{</span>
    <span class="c1">// This `read_volatile` is intended to correspond to a READ_ONCE call.</span>
    <span class="c1">//</span>
    <span class="c1">// SAFETY: The file is valid because the shared reference guarantees a nonzero refcount.</span>
    <span class="c1">//</span>
    <span class="c1">// FIXME(read_once): Replace with `read_once` when available on the Rust side.</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="nn">core</span><span class="p">::</span><span class="nn">ptr</span><span class="p">::</span><span class="nd">addr_of!</span><span class="p">((</span><span class="o">*</span><span class="k">self</span><span class="nf">.as_ptr</span><span class="p">())</span><span class="py">.f_flags</span><span class="p">)</span><span class="nf">.read_volatile</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>此处用 <code class="language-plaintext highlighter-rouge">read_volatile</code> 表达「可能与其他执行上下文共享的字段」的读，避免编译器优化导致的数据竞争未定义行为，语义上对应 C 侧的 <code class="language-plaintext highlighter-rouge">READ_ONCE</code>。</p>

<p><strong>（2）DMA 一致内存：与硬件/用户态竞态</strong></p>

<p>DMA 或与设备/用户态共享的内存，读写同样需要「单次访问不拆、不优化掉」的语义。内核在 <code class="language-plaintext highlighter-rouge">dma.rs</code> 中通过 <code class="language-plaintext highlighter-rouge">read_volatile</code> / <code class="language-plaintext highlighter-rouge">write_volatile</code> 实现，并明确注释其与 READ_ONCE/WRITE_ONCE 的对应关系及适用范围：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// rust/kernel/dma.rs（节选）</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="n">field_read</span><span class="o">&lt;</span><span class="n">F</span><span class="p">:</span> <span class="n">FromBytes</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">field</span><span class="p">:</span> <span class="o">*</span><span class="k">const</span> <span class="n">F</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">F</span> <span class="p">{</span>
    <span class="c1">// SAFETY:</span>
    <span class="c1">// - By the safety requirements field is valid.</span>
    <span class="c1">// - Using read_volatile() here is not sound as per the usual rules, the usage here is</span>
    <span class="c1">// a special exception with the following notes in place. When dealing with a potential</span>
    <span class="c1">// race from a hardware or code outside kernel (e.g. user-space program), we need that</span>
    <span class="c1">// read on a valid memory is not UB. Currently read_volatile() is used for this, and the</span>
    <span class="c1">// rationale behind is that it should generate the same code as READ_ONCE() which the</span>
    <span class="c1">// kernel already relies on to avoid UB on data races. Note that the usage of</span>
    <span class="c1">// read_volatile() is limited to this particular case, it cannot be used to prevent</span>
    <span class="c1">// the UB caused by racing between two kernel functions nor do they provide atomicity.</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="n">field</span><span class="nf">.read_volatile</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="n">field_write</span><span class="o">&lt;</span><span class="n">F</span><span class="p">:</span> <span class="n">AsBytes</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">field</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="n">F</span><span class="p">,</span> <span class="n">val</span><span class="p">:</span> <span class="n">F</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// SAFETY: ... (与 READ_ONCE 对应地，此处对应 WRITE_ONCE)</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="n">field</span><span class="nf">.write_volatile</span><span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>可见：<strong>只要涉及「硬件或内核外部的竞态」，就需要这类 volatile 访问，并因此使用 <code class="language-plaintext highlighter-rouge">unsafe</code></strong>，与是否经过 C 代码无关。</p>

<h3 id="3-mmio--ioremap资源映射与释放">3. MMIO / ioremap：资源映射与释放</h3>

<p>内存映射 I/O（MMIO）是驱动访问设备寄存器的常见方式。内核 Rust 侧对 <code class="language-plaintext highlighter-rouge">ioremap</code> / <code class="language-plaintext highlighter-rouge">iounmap</code> 的封装同样在 <code class="language-plaintext highlighter-rouge">unsafe</code> 中完成，并配有 SAFETY 注释说明前置条件：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// rust/kernel/io/mem.rs（节选）</span>
<span class="k">fn</span> <span class="nf">ioremap</span><span class="p">(</span><span class="n">resource</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Resource</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// ...</span>
    <span class="k">let</span> <span class="n">addr</span> <span class="o">=</span> <span class="k">if</span> <span class="n">resource</span><span class="nf">.flags</span><span class="p">()</span><span class="nf">.contains</span><span class="p">(</span><span class="nn">io</span><span class="p">::</span><span class="nn">resource</span><span class="p">::</span><span class="nn">Flags</span><span class="p">::</span><span class="n">IORESOURCE_MEM_NONPOSTED</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// SAFETY:</span>
        <span class="c1">// - `res_start` and `size` are read from a presumably valid `struct resource`.</span>
        <span class="c1">// - `size` is known not to be zero at this point.</span>
        <span class="k">unsafe</span> <span class="p">{</span> <span class="nn">bindings</span><span class="p">::</span><span class="nf">ioremap_np</span><span class="p">(</span><span class="n">res_start</span><span class="p">,</span> <span class="n">size</span><span class="p">)</span> <span class="p">}</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">unsafe</span> <span class="p">{</span> <span class="nn">bindings</span><span class="p">::</span><span class="nf">ioremap</span><span class="p">(</span><span class="n">res_start</span><span class="p">,</span> <span class="n">size</span><span class="p">)</span> <span class="p">}</span>
    <span class="p">};</span>
    <span class="c1">// ...</span>
<span class="p">}</span>

<span class="k">impl</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">SIZE</span><span class="p">:</span> <span class="nb">usize</span><span class="o">&gt;</span> <span class="nb">Drop</span> <span class="k">for</span> <span class="n">IoMem</span><span class="o">&lt;</span><span class="n">SIZE</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">drop</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// SAFETY: Safe as by the invariant of `Io`.</span>
        <span class="k">unsafe</span> <span class="p">{</span> <span class="nn">bindings</span><span class="p">::</span><span class="nf">iounmap</span><span class="p">(</span><span class="k">self</span><span class="py">.io</span><span class="nf">.addr</span><span class="p">()</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">c_void</span><span class="p">)</span> <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这里既有 <strong>FFI（调用 C 的 ioremap/iounmap）</strong>，也有 <strong>对「映射得到的地址」所代表的 I/O 内存的访问约定</strong>，二者都属于与硬件打交道的边界，因此用 <code class="language-plaintext highlighter-rouge">unsafe</code> 是必然的。</p>

<h3 id="4-裸指针与内存操作">4. 裸指针与内存操作</h3>

<p>除上述 FFI 与 volatile 外，内核 Rust 中还有大量「裸指针解引用、<code class="language-plaintext highlighter-rouge">ptr::read</code>/<code class="language-plaintext highlighter-rouge">ptr::write</code>、<code class="language-plaintext highlighter-rouge">drop_in_place</code>、<code class="language-plaintext highlighter-rouge">addr_of!</code>」等用法，分布在：</p>

<ul>
  <li><strong>pin-init</strong>：未初始化/固定内存的初始化与析构；</li>
  <li><strong>kernel/alloc</strong>：自定义分配器、KBox、kvec 等；</li>
  <li><strong>kernel/sync/arc</strong>、<strong>kernel/list</strong>、<strong>kernel/rbtree</strong> 等：与 C 结构或内核生命周期绑定的共享/链表/树。</li>
</ul>

<p>这些同样不依赖「是否调 C」：只要涉及未初始化内存、自管理指针或与 C 结构布局的互操作，就需要在 <code class="language-plaintext highlighter-rouge">unsafe</code> 中手动维护不变式。</p>

<h3 id="5-其他pintransmutesendsync">5. 其他：Pin、transmute、Send/Sync</h3>

<ul>
  <li><strong>Pin::new_unchecked</strong>、<strong>NonNull::new_unchecked</strong>、pin-init 的闭包初始化等：用于在保证不移动或初始化顺序的前提下构造对象，约 30+ 处。</li>
  <li><strong>transmute / transmute_copy</strong>：与 C 类型或 ABI 的互转、内部表示转换，约 35 处。</li>
  <li><strong>unsafe impl Send / Sync</strong>：为内部含裸指针或 FFI 句柄的类型标注可跨线程传递或共享，约 90+ 处。</li>
</ul>

<p>它们都与「和硬件或 C 边界交互」时的生命周期、布局、并发约定直接相关，是内核 Rust 中 <code class="language-plaintext highlighter-rouge">unsafe</code> 的组成部分，而不是「可选的风格问题」。</p>

<h2 id="按子系统的分布约">按子系统的分布（约）</h2>

<table>
  <thead>
    <tr>
      <th>子系统</th>
      <th><code class="language-plaintext highlighter-rouge">unsafe</code> 次数</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>kernel/</strong>（整体）</td>
      <td>1644</td>
      <td>含下列子目录</td>
    </tr>
    <tr>
      <td>kernel/sync</td>
      <td>142</td>
      <td>锁、Arc、RCU、completion 等</td>
    </tr>
    <tr>
      <td>kernel/alloc</td>
      <td>109</td>
      <td>分配器、KBox、kvec 等</td>
    </tr>
    <tr>
      <td>kernel/drm</td>
      <td>72</td>
      <td>DRM 驱动、GEM、ioctl 等</td>
    </tr>
    <tr>
      <td>kernel/net</td>
      <td>56</td>
      <td>网络、PHY 寄存器等</td>
    </tr>
    <tr>
      <td>kernel/block</td>
      <td>47</td>
      <td>块层、request、gen_disk 等</td>
    </tr>
    <tr>
      <td>kernel/device</td>
      <td>31</td>
      <td>设备模型、property 等</td>
    </tr>
    <tr>
      <td>kernel/io</td>
      <td>19</td>
      <td>ioremap、I/O 资源、mem 等</td>
    </tr>
  </tbody>
</table>

<p>驱动与硬件相关模块（net、block、drm、io、device 等）中 <code class="language-plaintext highlighter-rouge">unsafe</code> 密集，与「但凡和硬件扯上关系就需要 unsafe」的直观一致；sync/alloc 则多为并发与内存管理抽象本身的边界。</p>

<h2 id="小结">小结</h2>

<ul>
  <li><strong>「和硬件扯上关系就要 unsafe」</strong>：内核 Rust 的现状与之相符。MMIO（io/mem）、PHY 寄存器（net/phy）、DMA 读写（dma.rs）、以及大量通过 <code class="language-plaintext highlighter-rouge">bindings::</code> 调用的 C 驱动 API，都位于 <code class="language-plaintext highlighter-rouge">unsafe</code> 中；驱动/硬件路径几乎必然触及 <code class="language-plaintext highlighter-rouge">unsafe</code>。</li>
  <li><strong>「和是否调用 C 无关」</strong>：
    <ul>
      <li>调用 C（<code class="language-plaintext highlighter-rouge">bindings::</code>）约 1062 处，占 <code class="language-plaintext highlighter-rouge">unsafe</code> 比例很高。</li>
      <li>但 <strong>volatile 访问</strong>（file.rs、dma.rs）、<strong>裸指针解引用</strong>、<strong>Pin/初始化</strong>、<strong>Send/Sync</strong>、<strong>transmute</strong> 等，很多并不依赖「调 C」，而是<strong>内核的硬件与内存模型</strong>本身就需要在 Rust 中通过 <code class="language-plaintext highlighter-rouge">unsafe</code> 表达。<br />
因此：既有大量「因调 C 而 unsafe」，也有大量「因硬件/并发/内存边界而 unsafe」；与 Embassy 等纯 Rust 驱动/裸机生态一致——<strong>与硬件或底层边界打交道的代码，即使用纯 Rust 写，unsafe 仍会集中在这些边界上</strong>。</li>
    </ul>
  </li>
</ul>

<p>统计基于主线内核 <code class="language-plaintext highlighter-rouge">rust/</code> 树，代码片段取自同一树中的实际文件（见文中路径注释）<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup><sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>。</p>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p><a href="https://docs.kernel.org/rust/general-information.html">Linux Kernel - Rust support</a> - 内核 Rust 支持与目录结构说明 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p><a href="https://rust-for-linux.com/">Rust for Linux</a> - 内核内 Rust 支持项目与文档 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="rust" /><category term="linux-kernel" /><summary type="html"><![CDATA[统计 Linux 内核 Rust 代码中 unsafe 的使用场景与分布，帮助理解内核 Rust 的安全边界。]]></summary></entry><entry><title type="html">用户态锁与内核：谁在管理「等待」与 futex</title><link href="https://weinan.tech/2026/03/02/userspace-locks-and-kernel-futex.html" rel="alternate" type="text/html" title="用户态锁与内核：谁在管理「等待」与 futex" /><published>2026-03-02T00:00:00+08:00</published><updated>2026-03-02T00:00:00+08:00</updated><id>https://weinan.tech/2026/03/02/userspace-locks-and-kernel-futex</id><content type="html" xml:base="https://weinan.tech/2026/03/02/userspace-locks-and-kernel-futex.html"><![CDATA[<p>从底层实现看，<strong>用户态（userspace）的锁机制，其核心的阻塞与唤醒功能，最终依赖于内核提供的同步原语</strong>。可以用一个比喻理解：用户态的锁像大楼里每个房间的门锁（轻便、快速），内核的同步则像大楼的主门与安防（全局、负责调度）。多数时候大家只用房间门锁（用户态原子操作或自旋），但当线程需要「离开大楼」或「被叫醒」时，必须经过主门——即通过系统调用进入内核。本文说明这一依赖关系、<strong>futex（Fast Userspace Mutex）</strong> 如何作为桥梁，并辅以 Linux 内核源码与参考文献；关于锁的误用如何导致性能问题，见本博客<a href="https://weinan.io/2026/03/01/why-language-speed-is-misleading.html">《为什么「语言速度」是伪命题》</a>中的「锁的误用与性能」一节<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>。</p>

<h2 id="1-谁在管理等待">1. 谁在管理「等待」？</h2>

<p>用户态程序<strong>无法直接控制 CPU 的调度</strong>，只有内核才有权暂停一个线程（让出 CPU）并在未来某刻恢复它。内核能获得这一能力，依赖两类入口：<strong>系统调用</strong>（线程主动进入内核，例如调用 <code class="language-plaintext highlighter-rouge">futex</code> 后在内核里执行 <code class="language-plaintext highlighter-rouge">schedule()</code> 让出 CPU）与<strong>定时中断</strong>（周期性的时钟中断让内核有机会更新运行时间、设置「需要调度」标志，从而在返回用户态前或下次进入内核时执行 <code class="language-plaintext highlighter-rouge">schedule()</code>，实现抢占或时间片轮转）。定时中断路径在 Linux 上的实现大致为：时钟事件驱动 <strong><code class="language-plaintext highlighter-rouge">tick_periodic()</code></strong>（传统周期 tick）或 <strong><code class="language-plaintext highlighter-rouge">tick_nohz_handler()</code></strong>（高分辨率/动态 tick）→ <strong><code class="language-plaintext highlighter-rouge">update_process_times()</code></strong>（<code class="language-plaintext highlighter-rouge">kernel/time/timer.c</code>）→ <strong><code class="language-plaintext highlighter-rouge">sched_tick()</code></strong>（<code class="language-plaintext highlighter-rouge">kernel/sched/core.c</code>）；<code class="language-plaintext highlighter-rouge">sched_tick()</code> 的注释写明 “This function gets called by the timer code, with HZ frequency”，在其中更新 runqueue 时钟、调用当前任务所属调度类的 <strong><code class="language-plaintext highlighter-rouge">task_tick</code></strong>，并可能调用 <strong><code class="language-plaintext highlighter-rouge">resched_curr()</code></strong> 标记需要重新调度，从而在适当时机触发 <strong><code class="language-plaintext highlighter-rouge">__schedule()</code></strong> 切换任务<sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>。</p>

<ul>
  <li><strong>若锁被占用且等待时间可能较长</strong>：线程需要<strong>阻塞</strong>——主动放弃 CPU、进入睡眠，直到锁被释放。这个「让出 CPU 并睡眠」的动作必须通过内核提供的系统调用来完成，在 Linux 上即 <strong><code class="language-plaintext highlighter-rouge">futex</code></strong> 等<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">3</a></sup><sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>。</li>
  <li><strong>若锁只被短暂占用</strong>：线程可以选择<strong>自旋</strong>，即原地循环检查锁状态，不进入内核；线程一直占着 CPU。这仅适用于多核且持锁时间极短的场景，否则会浪费 CPU。</li>
</ul>

<p>因此：<strong>能「睡下去」和「被唤醒」的锁，一定依赖内核。</strong></p>

<h2 id="2-关键桥梁futex-fast-userspace-mutex">2. 关键桥梁：futex (Fast Userspace Mutex)</h2>

<p>在现代 Linux 上，几乎所有高性能用户态锁（如 NPTL 的 <code class="language-plaintext highlighter-rouge">pthread_mutex</code>、<code class="language-plaintext highlighter-rouge">pthread_cond</code>）底层都依赖 <strong>futex</strong>。其设计哲学正是「大部分时间在用户态解决，必要时才进内核」<sup id="fnref:2:1"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">3</a></sup><sup id="fnref:3:1"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>。</p>

<h3 id="21-无竞争时fast-path">2.1 无竞争时（Fast Path）</h3>

<p>线程尝试加锁时，若锁空闲，只需在<strong>用户态</strong>用一条原子指令（如 CAS）把锁变量从 0 改为 1。<strong>全程无系统调用，极快。</strong></p>

<h3 id="22-有竞争时slow-path">2.2 有竞争时（Slow Path）</h3>

<ol>
  <li><strong>用户态</strong>：尝试加锁的线程发现锁已被占用，将自身标记为「等待」，然后调用 <strong><code class="language-plaintext highlighter-rouge">futex</code> 系统调用</strong>进入内核。</li>
  <li><strong>内核态</strong>：内核把该线程放入与该 futex 对应的<strong>等待队列</strong>，并调度其他线程运行，当前线程阻塞。</li>
  <li><strong>释放与唤醒</strong>：持锁线程释放时，在用户态用原子指令把锁变量改回 0，并检查是否有等待者；若有，再调用 <strong><code class="language-plaintext highlighter-rouge">futex</code></strong> 通知内核唤醒。</li>
  <li><strong>内核响应</strong>：内核从等待队列中唤醒被阻塞的线程，该线程得以继续运行并再次尝试获取锁。</li>
</ol>

<p>因此，<strong>futex 本质上是内核提供的「等待队列管理器」</strong>，锁的值（0/1）由用户态维护，阻塞与唤醒由内核完成。内核实现见 <strong><code class="language-plaintext highlighter-rouge">kernel/futex/</code></strong>：系统调用入口为 <strong><code class="language-plaintext highlighter-rouge">SYSCALL_DEFINE6(futex, ...)</code></strong>，根据 <code class="language-plaintext highlighter-rouge">op</code> 分发到 <strong><code class="language-plaintext highlighter-rouge">futex_wait</code></strong> / <strong><code class="language-plaintext highlighter-rouge">futex_wake</code></strong> 等<sup id="fnref:3:2"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">4</a></sup><sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>。</p>

<h2 id="3-cpu-层面的锁机制原子指令与内存序">3. CPU 层面的锁机制：原子指令与内存序</h2>

<p>用户态「无竞争时一条原子指令加锁」依赖 <strong>CPU 提供的原子读-改-写（RMW）与内存序保证</strong>；否则多核下既无法保证互斥，也无法保证临界区内的写对其他核可见。以下为两种常见架构的要点与权威出处。</p>

<h3 id="31-x86lock-前缀与原子性">3.1 x86：LOCK 前缀与原子性</h3>

<p>在 x86 上，<strong>LOCK</strong> 前缀（opcode F0）可使特定指令在多核下<strong>原子</strong>执行：目标为内存操作数时，会断言 LOCK# 信号（或等价机制），使该次读-改-写不可被其他 CPU 打断。可加 LOCK 的指令包括 <strong>CMPXCHG</strong>（比较并交换）、<strong>XCHG</strong>（与内存交换）、<strong>ADD/SUB/INC/DEC</strong> 等；<strong>XCHG</strong> 在目标为内存时即使不加前缀也会具有锁语义。现代 x86（P6 及以后）对已缓存的地址通常采用 <strong>cache locking</strong>（依赖 MESI 等缓存一致性协议），而非锁总线，从而减少延迟<sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>。</p>

<p>LOCK 前缀还带来<strong>内存序</strong>效果：带 LOCK 的指令与其它 LOCK 指令之间存在全序；普通 load/store 不能与 LOCK 指令重排。因此「加锁」可用带 acquire 语义的原子操作（如 CMPXCHG 成功后相当于 acquire），「解锁」用带 release 语义的写（如原子 store 0），能保证临界区内的修改在解锁后对其它核可见、且其它核的修改在加锁后对本核可见。详见 Intel SDM Vol 3A 第 8 章（Multiple-Processor Management）及 Vol 2A 对 LOCK 的说明<sup id="fnref:7:1"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>。</p>

<h3 id="32-arm独占加载存储ldxrstxr与-exclusive-monitor">3.2 ARM：独占加载/存储（LDXR/STXR）与 Exclusive Monitor</h3>

<p>ARM 没有像 x86 那样的「单条指令原子 RMW」，而是用 <strong>Load-Exclusive / Store-Exclusive</strong> 实现：<strong>LDXR</strong>（Load Exclusive Register）从某地址加载并让该地址被本核的 <strong>exclusive monitor</strong> 标记；<strong>STXR</strong>（Store Exclusive Register）仅在该地址仍被本核独占时写入并返回 0，否则写入失败、返回非 0，由软件重试。这样一对 LDXR + STXR 可实现「读-改-写」的原子性，是用户态自旋锁、CAS 等的基础。ARMv8 还提供 <strong>LDAXR/STLXR</strong> 等带 <strong>acquire/release</strong> 语义的变种，在实现 mutex 时保证临界区前后的可见性<sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>。</p>

<p>Exclusive monitor 是硬件状态：若其它 CPU 在该地址上产生了 store 或其它使独占失效的访问，当前核的 STXR 会失败，从而避免多核同时写。软件需保证在 LDXR 与 STXR 之间不插入会破坏独占性的操作（如显式访问该地址、某些系统寄存器或 cache 维护指令）。详见 ARM 架构参考手册中「Load-Exclusive and Store-Exclusive」与「Synchronization and semaphores」<sup id="fnref:8:1"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>。</p>

<h3 id="33-与-futex-的关系">3.3 与 futex 的关系</h3>

<ul>
  <li><strong>无竞争</strong>：用户态用上述原子指令（x86 的 CMPXCHG/XCHG，ARM 的 LDXR/STXR 或 LDAXR/STLXR）完成「尝试加锁 / 解锁」，<strong>不进入内核</strong>，因此极快。</li>
  <li><strong>有竞争</strong>：原子尝试失败后，若选择阻塞，再通过 <strong>futex</strong> 系统调用进入内核、挂入等待队列。</li>
</ul>

<p>内核自身在实现 futex 的哈希桶、等待队列时，同样依赖各架构的原子与内存屏障；Linux 内核文档 <strong>atomic_t.txt</strong>、<strong>memory-barriers.txt</strong> 对原子 RMW、acquire/release 变种及与锁的配合有统一说明<sup id="fnref:8:2"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>。</p>

<h2 id="4-为什么不能完全在用户态实现阻塞锁">4. 为什么不能完全在用户态实现「阻塞」锁？</h2>

<p>若完全在用户态实现，当线程拿不到锁时只有两种选择：</p>

<ol>
  <li><strong>自旋（忙等）</strong>：一直循环检查。持锁时间一长就会白占 CPU，浪费严重。</li>
  <li><strong>sleep + 轮询</strong>：调用 <code class="language-plaintext highlighter-rouge">sleep()</code> 睡一会儿再起来看。延迟不可控（可能刚睡下锁就释放了），且无法做到「锁一释放就立刻被唤醒」。</li>
</ol>

<p>要实现「锁释放时立刻唤醒」的语义，<strong>必须有一个全局的调度者管理线程状态</strong>，这个角色只能是操作系统内核。</p>

<h2 id="5-完全在用户态的锁">5. 完全在用户态的锁</h2>

<p>有，但适用场景受限：</p>

<ul>
  <li><strong>自旋锁</strong>：基于原子操作，预期持锁时间仅几条指令时可用。<strong>完全不依赖内核</strong>，代价是：若锁被长时间持有，CPU 会空转。内核与用户态都常用；用户态自旋锁不涉及 futex。</li>
  <li><strong>序列锁（seqlock）</strong> 等乐观并发：主要在用户态通过内存序与版本号完成，但冲突激烈时可能需重试或退化为等待，仍可能依赖内核。</li>
</ul>

<p>关于<strong>何时用自旋、何时用可睡眠的锁</strong>，以及<strong>粗粒度锁、持锁做 I/O</strong> 对性能的影响，见本博客<a href="https://weinan.io/2026/03/01/why-language-speed-is-misleading.html">《为什么「语言速度」是伪命题》</a>#锁的误用与性能<sup id="fnref:1:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>。</p>

<h2 id="6-总结">6. 总结</h2>

<ul>
  <li><strong>上层（用户态）</strong>：用原子指令快速尝试获取锁，无竞争时避免任何内核开销。</li>
  <li><strong>下层（内核）</strong>：通过 <strong>futex</strong> 等原语提供「等待队列 + 调度」，处理阻塞与唤醒。</li>
</ul>

<p><strong>用户态锁的「快」，是因为无竞争时绕过了内核；它之所以能成为通用的、可阻塞的锁，是因为有竞争时有内核的兜底。</strong></p>

<hr />

<h2 id="补充阅读自旋睡眠与-sleep-时间准确度">补充阅读：自旋、睡眠与 sleep 时间准确度</h2>

<h3 id="自旋就是在浪费-cpu-的循环">自旋就是在浪费 CPU 的循环</h3>

<p><strong>自旋（spin）</strong>即拿不到锁时不放弃 CPU，在用户态（或内核态）反复执行「读锁变量 → 判断是否可用 → 再读再判断」的循环，直到锁被释放。这段时间里 CPU 一直在跑这条循环，没有做业务逻辑，从系统角度看就是<strong>空转、浪费该核的算力</strong>。因此自旋只适合「预计很快就能拿到锁」的场景（例如持锁只有几条指令）；否则会长时间白占 CPU。自旋时常配合 <strong>PAUSE</strong>（x86）或 <strong>WFE</strong>（ARM）等指令减轻总线竞争，但本质仍是循环等待。</p>

<h3 id="cpu-如何实现sleep没有-sleep-指令靠调度与上下文切换">CPU 如何「实现」sleep：没有 sleep 指令，靠调度与上下文切换</h3>

<p>CPU <strong>没有</strong>「让某条线程 sleep」的指令。「Sleep」是操作系统用<strong>调度 + 上下文切换</strong>实现的效果：CPU 只是在执行当前被调度到的指令流。</p>

<ul>
  <li><strong>线程如何睡过去</strong>：线程在用户态执行会阻塞的操作（如 <code class="language-plaintext highlighter-rouge">futex(FUTEX_WAIT)</code>、<code class="language-plaintext highlighter-rouge">read()</code> 阻塞 fd）时发生<strong>系统调用</strong>，陷入内核。内核把对应 <strong>task</strong> 挂到<strong>等待队列</strong>，状态改为 <strong><code class="language-plaintext highlighter-rouge">TASK_INTERRUPTIBLE</code></strong> 等，<strong>不再放在 runqueue 上</strong>；随后内核调用 <strong><code class="language-plaintext highlighter-rouge">schedule()</code></strong>，做<strong>上下文切换</strong>——把当前线程的寄存器、PC、栈等存到内存，从 runqueue 选另一 task 加载回 CPU 并执行。从这一刻起，「睡着」的线程的指令不再被 CPU 执行。futex 路径上可见 <strong><code class="language-plaintext highlighter-rouge">kernel/futex/waitwake.c</code></strong> 中 <strong><code class="language-plaintext highlighter-rouge">set_current_state(TASK_INTERRUPTIBLE|TASK_FREEZABLE)</code></strong> 与 <strong><code class="language-plaintext highlighter-rouge">futex_do_wait()</code></strong> 内的 <strong><code class="language-plaintext highlighter-rouge">schedule()</code></strong><sup id="fnref:4:1"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">5</a></sup><sup id="fnref:10"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>。</li>
  <li><strong>CPU 在做什么</strong>：当线程 A sleep 时，A 的上下文保存在内存里，CPU 去执行线程 B 或 idle。没有「sleep」这条指令，只是内核不再把 CPU 分给该线程。</li>
  <li><strong>易混淆的指令</strong>：<strong>HLT</strong>（x86）/ <strong>WFI</strong>（ARM）是 idle 任务在「完全没活可干」时用的，让整核等中断，不是「某条线程 sleep」。<strong>PAUSE</strong>（x86）是自旋等锁时用的，不是 sleep。</li>
</ul>

<h3 id="sleep-的时间准确度定时器到期由时钟定时器中断触发唤醒">Sleep 的时间准确度：定时器到期，由时钟/定时器中断触发唤醒</h3>

<p>「睡多久」由内核<strong>定时器（timer）</strong>到期保证；到期由<strong>时钟/定时器中断</strong>（或高精度 timer 回调）触发。</p>

<ul>
  <li><strong>带时间的 sleep 在内核里</strong>：例如 <code class="language-plaintext highlighter-rouge">nanosleep(2s)</code>、<code class="language-plaintext highlighter-rouge">futex_wait(..., timeout)</code> 时，内核把线程挂到等待队列，并依「当前时间 + 时长」登记一个<strong>高精度定时器（hrtimer）</strong>，到期时间即目标唤醒时间。futex 带超时等待使用 <strong><code class="language-plaintext highlighter-rouge">struct hrtimer_sleeper</code></strong>，在 <strong><code class="language-plaintext highlighter-rouge">futex_do_wait()</code></strong> 中若传入 timeout 会调用 <strong><code class="language-plaintext highlighter-rouge">hrtimer_sleeper_start_expires(timeout, HRTIMER_MODE_ABS)</code></strong>，到期后 hrtimer 回调会间接使该 task 被唤醒<sup id="fnref:10:1"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>。</li>
  <li><strong>时间到了怎么醒</strong>：定时器子系统（如 hrtimer）按到期时间排序，到点由<strong>时钟/定时器中断</strong>或高精度 timer 中断（及后续 softirq）执行回调；对「sleep 到期」的 timer，回调里通过 <strong>wake_up</strong> 等把线程从等待队列移回 <strong>runqueue</strong>，设为可运行。</li>
  <li><strong>准确度</strong>：<strong>何时被唤醒（变为 runnable）</strong>由 timer 到期与中断路径保证；<strong>何时真正再次得到 CPU</strong> 还受调度延迟影响（通常为微秒到毫秒级）。高精度定时器（hrtimer）可提供微秒级分辨率；若仅用低分辨率 jiffies，到期检查受 tick 间隔限制。</li>
</ul>

<p>可参见 <strong><code class="language-plaintext highlighter-rouge">kernel/futex/waitwake.c</code></strong>（<code class="language-plaintext highlighter-rouge">futex_do_wait</code>、<code class="language-plaintext highlighter-rouge">hrtimer_sleeper_start_expires</code>、<code class="language-plaintext highlighter-rouge">set_current_state(TASK_INTERRUPTIBLE)</code>、<code class="language-plaintext highlighter-rouge">schedule()</code>）及 <strong><code class="language-plaintext highlighter-rouge">kernel/sched/core.c</code></strong>（<code class="language-plaintext highlighter-rouge">schedule()</code>/<code class="language-plaintext highlighter-rouge">__schedule()</code> 的上下文切换）<sup id="fnref:4:2"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">5</a></sup><sup id="fnref:10:2"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>。</p>

<hr />

<h2 id="扩展阅读内核与接口">扩展阅读（内核与接口）</h2>

<ul>
  <li><strong>futex 系统调用</strong>：<strong><code class="language-plaintext highlighter-rouge">kernel/futex/syscalls.c</code></strong> 中 <strong><code class="language-plaintext highlighter-rouge">SYSCALL_DEFINE6(futex, ...)</code></strong> 与 <strong><code class="language-plaintext highlighter-rouge">do_futex()</code></strong>，根据 <code class="language-plaintext highlighter-rouge">op</code>（如 <code class="language-plaintext highlighter-rouge">FUTEX_WAIT</code>、<code class="language-plaintext highlighter-rouge">FUTEX_WAKE</code>）分发到 <strong><code class="language-plaintext highlighter-rouge">kernel/futex/waitwake.c</code></strong> 的 <strong><code class="language-plaintext highlighter-rouge">futex_wait()</code></strong>、<strong><code class="language-plaintext highlighter-rouge">futex_wake()</code></strong><sup id="fnref:3:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">4</a></sup><sup id="fnref:4:3"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>。</li>
  <li><strong>等待与唤醒逻辑</strong>：<strong><code class="language-plaintext highlighter-rouge">waitwake.c</code></strong> 中 <code class="language-plaintext highlighter-rouge">futex_wait_setup()</code> 将当前任务入队，<code class="language-plaintext highlighter-rouge">__futex_wait()</code> 调用 <code class="language-plaintext highlighter-rouge">futex_do_wait()</code> 进入调度；<code class="language-plaintext highlighter-rouge">futex_wake()</code> 在哈希桶中查找等待者并 <code class="language-plaintext highlighter-rouge">wake_up_q()</code><sup id="fnref:4:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">5</a></sup><sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>。</li>
  <li><strong>futex 设计</strong>：<strong><code class="language-plaintext highlighter-rouge">kernel/futex/core.c</code></strong> 文件头注释（Rusty Russell 等）对 Fast Userspace Mutex 的由来与设计有简要说明；LWN 多篇文章介绍其演进与优化<sup id="fnref:2:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">3</a></sup><sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">10</a></sup>。</li>
  <li><strong>CPU 原子与内存序</strong>：x86 LOCK 前缀与多核原子见 Intel SDM Vol 2A/Vol 3A；ARM 独占加载/存储见 ARM ARM；Linux 内核 <strong>atomic_t.txt</strong>、<strong>memory-barriers.txt</strong> 对原子 RMW 与 acquire/release 的说明<sup id="fnref:7:2"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">6</a></sup><sup id="fnref:8:3"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>。</li>
  <li><strong>定时中断与调度</strong>：<strong><code class="language-plaintext highlighter-rouge">kernel/time/timer.c</code></strong> 中 <strong><code class="language-plaintext highlighter-rouge">update_process_times()</code></strong> 由时钟中断路径调用，内部调用 <strong><code class="language-plaintext highlighter-rouge">sched_tick()</code></strong>；<strong><code class="language-plaintext highlighter-rouge">kernel/time/tick-common.c</code></strong> 的 <strong><code class="language-plaintext highlighter-rouge">tick_periodic()</code></strong>、<strong><code class="language-plaintext highlighter-rouge">kernel/time/tick-sched.c</code></strong> 的 <strong><code class="language-plaintext highlighter-rouge">tick_nohz_handler()</code></strong> → <strong><code class="language-plaintext highlighter-rouge">tick_sched_handle()</code></strong> 均会调用 <strong><code class="language-plaintext highlighter-rouge">update_process_times()</code></strong>；<strong><code class="language-plaintext highlighter-rouge">kernel/sched/core.c</code></strong> 中 <strong><code class="language-plaintext highlighter-rouge">sched_tick()</code></strong> 以 HZ 频率被 timer 代码调用，负责更新 rq 时钟与 <strong><code class="language-plaintext highlighter-rouge">task_tick</code></strong>、必要时 <strong><code class="language-plaintext highlighter-rouge">resched_curr()</code></strong><sup id="fnref:9:1"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>。</li>
  <li><strong>自旋、睡眠与 sleep 时间</strong>：自旋即占 CPU 的循环等待；sleep 由内核等待队列 + <strong><code class="language-plaintext highlighter-rouge">schedule()</code></strong> 实现，无专用 CPU 指令。带超时的 sleep 依赖 <strong>hrtimer</strong> 到期，由时钟/定时器中断触发唤醒。见 <strong><code class="language-plaintext highlighter-rouge">kernel/futex/waitwake.c</code></strong>（<code class="language-plaintext highlighter-rouge">futex_do_wait</code>、<code class="language-plaintext highlighter-rouge">hrtimer_sleeper_start_expires</code>、<code class="language-plaintext highlighter-rouge">TASK_INTERRUPTIBLE</code>、<code class="language-plaintext highlighter-rouge">schedule()</code>）<sup id="fnref:10:3"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>。</li>
</ul>

<hr />

<h2 id="内核代码片段与正文对应">内核代码片段（与正文对应）</h2>

<p><strong>1. futex 系统调用入口与分发</strong>（<code class="language-plaintext highlighter-rouge">kernel/futex/syscalls.c</code>）</p>

<p>用户态调用 <code class="language-plaintext highlighter-rouge">futex(uaddr, op, ...)</code> 时，内核根据 <code class="language-plaintext highlighter-rouge">op &amp; FUTEX_CMD_MASK</code> 分发到 <code class="language-plaintext highlighter-rouge">futex_wait</code> 或 <code class="language-plaintext highlighter-rouge">futex_wake</code> 等；<code class="language-plaintext highlighter-rouge">FUTEX_WAIT</code> / <code class="language-plaintext highlighter-rouge">FUTEX_WAKE</code> 走 <code class="language-plaintext highlighter-rouge">do_futex()</code><sup id="fnref:3:4"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">4</a></sup><sup id="fnref:4:5"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 简化自 kernel/futex/syscalls.c（约 84–106 行、160 行）</span>
<span class="kt">long</span> <span class="nf">do_futex</span><span class="p">(</span><span class="n">u32</span> <span class="n">__user</span> <span class="o">*</span><span class="n">uaddr</span><span class="p">,</span> <span class="kt">int</span> <span class="n">op</span><span class="p">,</span> <span class="n">u32</span> <span class="n">val</span><span class="p">,</span> <span class="n">ktime_t</span> <span class="o">*</span><span class="n">timeout</span><span class="p">,</span>
              <span class="n">u32</span> <span class="n">__user</span> <span class="o">*</span><span class="n">uaddr2</span><span class="p">,</span> <span class="n">u32</span> <span class="n">val2</span><span class="p">,</span> <span class="n">u32</span> <span class="n">val3</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">flags</span> <span class="o">=</span> <span class="n">futex_to_flags</span><span class="p">(</span><span class="n">op</span><span class="p">);</span>
    <span class="kt">int</span> <span class="n">cmd</span> <span class="o">=</span> <span class="n">op</span> <span class="o">&amp;</span> <span class="n">FUTEX_CMD_MASK</span><span class="p">;</span>
    <span class="c1">// ...</span>
    <span class="k">switch</span> <span class="p">(</span><span class="n">cmd</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">case</span> <span class="n">FUTEX_WAIT</span><span class="p">:</span>
    <span class="k">case</span> <span class="n">FUTEX_WAIT_BITSET</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">futex_wait</span><span class="p">(</span><span class="n">uaddr</span><span class="p">,</span> <span class="n">flags</span><span class="p">,</span> <span class="n">val</span><span class="p">,</span> <span class="n">timeout</span><span class="p">,</span> <span class="n">val3</span><span class="p">);</span>
    <span class="k">case</span> <span class="n">FUTEX_WAKE</span><span class="p">:</span>
    <span class="k">case</span> <span class="n">FUTEX_WAKE_BITSET</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">futex_wake</span><span class="p">(</span><span class="n">uaddr</span><span class="p">,</span> <span class="n">flags</span><span class="p">,</span> <span class="n">val</span><span class="p">,</span> <span class="n">val3</span><span class="p">);</span>
    <span class="c1">// ...</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="n">SYSCALL_DEFINE6</span><span class="p">(</span><span class="n">futex</span><span class="p">,</span> <span class="n">u32</span> <span class="n">__user</span> <span class="o">*</span><span class="p">,</span> <span class="n">uaddr</span><span class="p">,</span> <span class="kt">int</span><span class="p">,</span> <span class="n">op</span><span class="p">,</span> <span class="n">u32</span><span class="p">,</span> <span class="n">val</span><span class="p">,</span>
                <span class="k">const</span> <span class="k">struct</span> <span class="n">__kernel_timespec</span> <span class="n">__user</span> <span class="o">*</span><span class="p">,</span> <span class="n">utime</span><span class="p">,</span>
                <span class="n">u32</span> <span class="n">__user</span> <span class="o">*</span><span class="p">,</span> <span class="n">uaddr2</span><span class="p">,</span> <span class="n">u32</span><span class="p">,</span> <span class="n">val3</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// 超时处理等 ...</span>
    <span class="k">return</span> <span class="n">do_futex</span><span class="p">(</span><span class="n">uaddr</span><span class="p">,</span> <span class="n">op</span><span class="p">,</span> <span class="n">val</span><span class="p">,</span> <span class="n">tp</span><span class="p">,</span> <span class="n">uaddr2</span><span class="p">,</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">long</span><span class="p">)</span><span class="n">utime</span><span class="p">,</span> <span class="n">val3</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>2. 等待与唤醒：入队与 schedule</strong>（<code class="language-plaintext highlighter-rouge">kernel/futex/waitwake.c</code>）</p>

<p><code class="language-plaintext highlighter-rouge">__futex_wait()</code> 通过 <code class="language-plaintext highlighter-rouge">futex_wait_setup()</code> 准备并入队，再调用 <code class="language-plaintext highlighter-rouge">futex_do_wait()</code> 进入睡眠；<code class="language-plaintext highlighter-rouge">futex_wake()</code> 根据 uaddr 算哈希桶，在桶内链表中找到匹配的等待者并唤醒<sup id="fnref:4:6"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">5</a></sup><sup id="fnref:5:1"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 简化自 kernel/futex/waitwake.c</span>
<span class="c1">// __futex_wait()（约 666–687 行）：准备等待、入队、进入 schedule</span>
<span class="kt">int</span> <span class="nf">__futex_wait</span><span class="p">(</span><span class="n">u32</span> <span class="n">__user</span> <span class="o">*</span><span class="n">uaddr</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">flags</span><span class="p">,</span> <span class="n">u32</span> <span class="n">val</span><span class="p">,</span>
                 <span class="k">struct</span> <span class="n">hrtimer_sleeper</span> <span class="o">*</span><span class="n">to</span><span class="p">,</span> <span class="n">u32</span> <span class="n">bitset</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">struct</span> <span class="n">futex_q</span> <span class="n">q</span> <span class="o">=</span> <span class="n">futex_q_init</span><span class="p">;</span>
    <span class="c1">// ...</span>
    <span class="n">ret</span> <span class="o">=</span> <span class="n">futex_wait_setup</span><span class="p">(</span><span class="n">uaddr</span><span class="p">,</span> <span class="n">val</span><span class="p">,</span> <span class="n">flags</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">q</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">current</span><span class="p">);</span>  <span class="cm">/* 入队等 */</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">ret</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
    <span class="n">futex_do_wait</span><span class="p">(</span><span class="o">&amp;</span><span class="n">q</span><span class="p">,</span> <span class="n">to</span><span class="p">);</span>   <span class="cm">/* 在此 schedule，让出 CPU */</span>
    <span class="c1">// ...</span>
<span class="p">}</span>

<span class="c1">// futex_wake()（约 155–199 行）：查哈希桶、唤醒 nr_wake 个等待者</span>
<span class="kt">int</span> <span class="nf">futex_wake</span><span class="p">(</span><span class="n">u32</span> <span class="n">__user</span> <span class="o">*</span><span class="n">uaddr</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">flags</span><span class="p">,</span> <span class="kt">int</span> <span class="n">nr_wake</span><span class="p">,</span> <span class="n">u32</span> <span class="n">bitset</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// get_futex_key, futex_hash 得到 hb (hash bucket)</span>
    <span class="n">spin_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">hb</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">);</span>
    <span class="n">plist_for_each_entry_safe</span><span class="p">(</span><span class="n">this</span><span class="p">,</span> <span class="n">next</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">hb</span><span class="o">-&gt;</span><span class="n">chain</span><span class="p">,</span> <span class="n">list</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">futex_match</span><span class="p">(</span><span class="o">&amp;</span><span class="n">this</span><span class="o">-&gt;</span><span class="n">key</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">key</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">this</span><span class="o">-&gt;</span><span class="n">wake</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wake_q</span><span class="p">,</span> <span class="n">this</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="o">++</span><span class="n">ret</span> <span class="o">&gt;=</span> <span class="n">nr_wake</span><span class="p">)</span>
                <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="n">spin_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">hb</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">);</span>
    <span class="n">wake_up_q</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wake_q</span><span class="p">);</span>   <span class="cm">/* 真正唤醒等待线程 */</span>
    <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>3. core.c 中的设计说明</strong>（<code class="language-plaintext highlighter-rouge">kernel/futex/core.c</code>）</p>

<p>文件头注释说明 futex 的由来（Rusty Russell 等）、「hashed waitqueues」等设计，与正文「内核管理等待队列」对应<sup id="fnref:3:5"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// kernel/futex/core.c 文件头（约 1–32 行）</span>
<span class="cm">/*
 *  Fast Userspace Mutexes (which I call "Futexes!").
 *  (C) Rusty Russell, IBM 2002
 *  ...
 *  Thanks to Ben LaHaise for yelling "hashed waitqueues" loudly enough at me...
 */</span>
</code></pre></div></div>

<hr />

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>本博客 <a href="https://weinan.io/2026/03/01/why-language-speed-is-misleading.html">为什么「语言速度」是伪命题：I/O、并发、内存与内核</a> - §1.5 锁的误用与性能：细粒度锁、持锁时间、自旋与睡眠取舍 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:1:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:9">
      <p><strong>定时中断与调度</strong>：时钟中断路径调用 <strong><code class="language-plaintext highlighter-rouge">update_process_times()</code></strong>（<code class="language-plaintext highlighter-rouge">kernel/time/timer.c</code>），其内调用 <strong><code class="language-plaintext highlighter-rouge">sched_tick()</code></strong>；<code class="language-plaintext highlighter-rouge">sched_tick()</code> 在 <strong><code class="language-plaintext highlighter-rouge">kernel/sched/core.c</code></strong> 中实现，注释写明 “gets called by the timer code, with HZ frequency”，内部执行 <code class="language-plaintext highlighter-rouge">update_rq_clock(rq)</code>、<code class="language-plaintext highlighter-rouge">donor-&gt;sched_class-&gt;task_tick(rq, donor, 0)</code> 及条件性的 <code class="language-plaintext highlighter-rouge">resched_curr(rq)</code>，从而在定时中断上下文中为抢占/时间片提供入口。Tick 入口见 <strong><code class="language-plaintext highlighter-rouge">kernel/time/tick-common.c</code></strong>（<code class="language-plaintext highlighter-rouge">tick_periodic</code>）与 <strong><code class="language-plaintext highlighter-rouge">kernel/time/tick-sched.c</code></strong>（<code class="language-plaintext highlighter-rouge">tick_nohz_handler</code> → <code class="language-plaintext highlighter-rouge">tick_sched_handle</code> → <code class="language-plaintext highlighter-rouge">update_process_times</code>）。<a href="https://elixir.bootlin.com/linux/latest/source/kernel/time/timer.c">Bootlin - timer.c</a>、<a href="https://elixir.bootlin.com/linux/latest/source/kernel/sched/core.c">Bootlin - core.c</a>（搜索 sched_tick） <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:9:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:2">
      <p><a href="https://lwn.net/Articles/360699/">A futex overview and update</a> - LWN，futex 概述与无竞争 fast path、有竞争时进内核 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:2:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:3">
      <p>Linux 内核 <strong>kernel/futex/core.c</strong>（futex 设计与 hashed waitqueues）、<strong>kernel/futex/syscalls.c</strong>（<code class="language-plaintext highlighter-rouge">SYSCALL_DEFINE6(futex,...)</code>、<code class="language-plaintext highlighter-rouge">do_futex</code>）。<a href="https://elixir.bootlin.com/linux/latest/source/kernel/futex/core.c">Bootlin - core.c</a>、<a href="https://elixir.bootlin.com/linux/latest/source/kernel/futex/syscalls.c">Bootlin - syscalls.c</a> <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:3:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:3:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:3:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:3:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a> <a href="#fnref:3:5" class="reversefootnote" role="doc-backlink">&#8617;<sup>6</sup></a></p>
    </li>
    <li id="fn:4">
      <p>Linux 内核 <strong>kernel/futex/syscalls.c</strong>（<code class="language-plaintext highlighter-rouge">do_futex</code> 中 <code class="language-plaintext highlighter-rouge">FUTEX_WAIT</code>→<code class="language-plaintext highlighter-rouge">futex_wait</code>、<code class="language-plaintext highlighter-rouge">FUTEX_WAKE</code>→<code class="language-plaintext highlighter-rouge">futex_wake</code>）、<strong>kernel/futex/waitwake.c</strong>（<code class="language-plaintext highlighter-rouge">futex_wait</code>、<code class="language-plaintext highlighter-rouge">__futex_wait</code>、<code class="language-plaintext highlighter-rouge">futex_wake</code>、入队与 <code class="language-plaintext highlighter-rouge">wake_up_q</code>）。<a href="https://elixir.bootlin.com/linux/latest/source/kernel/futex/waitwake.c">Bootlin - waitwake.c</a> <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:4:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:4:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:4:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:4:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a> <a href="#fnref:4:5" class="reversefootnote" role="doc-backlink">&#8617;<sup>6</sup></a> <a href="#fnref:4:6" class="reversefootnote" role="doc-backlink">&#8617;<sup>7</sup></a></p>
    </li>
    <li id="fn:7">
      <p><strong>Intel® 64 and IA-32 Architectures Software Developer’s Manual</strong>：Vol 2A 中 <strong>LOCK</strong>（Instruction set reference）说明 LOCK 前缀可施加的指令及多核原子性；Vol 3A 第 8 章 <strong>Multiple-Processor Management</strong> 涉及 LOCK#、总线与缓存锁定及内存序。可查 <a href="https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html">Intel SDM 索引</a> 或 <a href="https://www.felixcloutier.com/x86/lock">felixcloutier x86 LOCK</a>。 <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:7:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:7:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:8">
      <p><strong>ARM</strong>：架构参考手册中 <strong>Load-Exclusive and Store-Exclusive</strong>（如 LDXR/STXR、LDAXR/STLXR）与 <strong>Synchronization and semaphores</strong> 说明独占监视器与原子 RMW。<a href="https://developer.arm.com/documentation/ddi0487/latest">ARM Architecture Reference Manual</a>。<strong>Linux 内核</strong>：<strong>Documentation/atomic_t.txt</strong> 描述 atomic RMW API 与 acquire/release 变种；<strong>Documentation/memory-barriers.txt</strong> 描述内存屏障与锁的配对。<a href="https://www.kernel.org/doc/html/latest/core-api/atomic_t.html">atomic_t.txt</a>、<a href="https://www.kernel.org/doc/html/latest/core-api/wrappers/memory-barriers.html">memory-barriers.txt</a> <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:8:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:8:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:8:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a></p>
    </li>
    <li id="fn:10">
      <p><strong>自旋、睡眠与 sleep 时间</strong>：<strong><code class="language-plaintext highlighter-rouge">kernel/futex/waitwake.c</code></strong> 中 <strong><code class="language-plaintext highlighter-rouge">futex_do_wait()</code></strong> 在传入 timeout 时调用 <strong><code class="language-plaintext highlighter-rouge">hrtimer_sleeper_start_expires(timeout, HRTIMER_MODE_ABS)</code></strong> 启动高精度定时器，随后在 <code class="language-plaintext highlighter-rouge">plist_node_empty</code> 检查通过时调用 <strong><code class="language-plaintext highlighter-rouge">schedule()</code></strong> 让出 CPU；入队前通过 <strong><code class="language-plaintext highlighter-rouge">set_current_state(TASK_INTERRUPTIBLE|TASK_FREEZABLE)</code></strong> 将当前任务设为可中断睡眠（见同文件约 441、659 行及 341–360 行）。定时器到期由时钟/高精度 timer 中断路径触发回调，从而唤醒该 task。<a href="https://elixir.bootlin.com/linux/latest/source/kernel/futex/waitwake.c">Bootlin - waitwake.c</a> <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:10:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:10:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:10:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a></p>
    </li>
    <li id="fn:5">
      <p><strong>kernel/futex/waitwake.c</strong> 文件头注释：waiter 读用户态 futex 值、调用 <code class="language-plaintext highlighter-rouge">futex_wait()</code> 后入队并 <code class="language-plaintext highlighter-rouge">schedule()</code>；waker 改用户态值后调用 <code class="language-plaintext highlighter-rouge">futex_wake()</code> 在哈希桶中查找并唤醒。说明了用户态「锁变量」与内核「等待队列」的协作。 <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:5:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:6">
      <p><a href="https://lwn.net/Articles/685769/">In pursuit of faster futexes</a> - LWN，futex 性能与竞争路径优化；<a href="https://docs.kernel.org/locking/robust-futexes.html">Robust futexes - The Linux Kernel documentation</a> - 健壮 futex 与进程退出时的清理 <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="linux-kernel" /><summary type="html"><![CDATA[梳理用户态锁与内核 futex 的分工，说明「等待」究竟由谁管理以及常见锁的实现路径。]]></summary></entry><entry><title type="html">栈为什么比堆快：从分配方式到「批发-零售」链条</title><link href="https://weinan.tech/2026/03/01/stack-vs-heap-why-stack-faster.html" rel="alternate" type="text/html" title="栈为什么比堆快：从分配方式到「批发-零售」链条" /><published>2026-03-01T00:00:00+08:00</published><updated>2026-03-01T00:00:00+08:00</updated><id>https://weinan.tech/2026/03/01/stack-vs-heap-why-stack-faster</id><content type="html" xml:base="https://weinan.tech/2026/03/01/stack-vs-heap-why-stack-faster.html"><![CDATA[<p>在同一个进程内，栈和堆使用相同的内存硬件，访问速度本身没有区别。真正的性能差异来自内核在分配和管理内存时为两者采取的不同策略。本文从分配方式、物理内存管理、缓存友好性三个角度说明原因，并借 sbrk、Slab、malloc 梳理从内核到用户态的内存「批发-零售」链条；最后讨论「栈比堆快」这一经验法则的适用边界。</p>

<h2 id="1-内存分配方式">1. 内存分配方式</h2>

<h3 id="栈近乎零成本">栈：近乎零成本</h3>

<p>栈上分配只需修改<strong>栈指针寄存器</strong>。在 x86-64 上，函数序言用 <code class="language-plaintext highlighter-rouge">sub rsp, N</code> 预留空间（如 <code class="language-plaintext highlighter-rouge">sub rsp, 0x10</code> 即 16 字节），一条 CPU 指令、不涉及内核，成本极低<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup><sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>。需要澄清：<strong><code class="language-plaintext highlighter-rouge">sub rsp, N</code> 本身不会触发任何异常</strong>，只是寄存器算术；触发缺页的是<strong>后续对该新栈空间的首次访问</strong>（见下节）。</p>

<pre><code class="language-asm">; x86-64 函数序言示例：分配 0x20 字节栈帧
    push    rbp
    mov     rbp, rsp
    sub     rsp, 0x20
</code></pre>

<h3 id="堆系统调用的开销">堆：系统调用的开销</h3>

<p>通过 <code class="language-plaintext highlighter-rouge">malloc</code> 申请内存时，若分配器内部池子不足，会通过 <strong><code class="language-plaintext highlighter-rouge">brk</code></strong> 或 <strong><code class="language-plaintext highlighter-rouge">mmap</code></strong> 等<strong>系统调用</strong>向内核申请。用户态/内核态切换带来微秒级开销，相比一条 <code class="language-plaintext highlighter-rouge">sub rsp</code> 可高出数百倍甚至更多<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>。</p>

<h2 id="2-物理内存管理">2. 物理内存管理</h2>

<h3 id="栈缺页异常与按需映射">栈：缺页异常与按需映射</h3>

<p><strong>修改指针（sub rsp, N）：只是“账面上的分配”</strong>。执行 <code class="language-plaintext highlighter-rouge">sub rsp, 0x1000</code> 时，CPU 只做寄存器运算，内核对此一无所知。进程虚拟地址空间中这段新栈区在页表里尚未映射到物理页（或映射到只读零页），只是被“预留”出一个地址范围，成本就是一条指令。</p>

<p><strong>首次访问（例如 <code class="language-plaintext highlighter-rouge">mov [rsp-8], rax</code>）：才是真正的“物理分配”</strong>。当第一次使用这片新栈空间时：(1) CPU 尝试写入该虚拟地址；(2) MMU 查页表发现该页无有效物理页框，无法完成转换；(3) MMU 触发<strong>缺页异常</strong>（#PF，x86-64 上为中断 14），CPU 转去执行内核的缺页处理（如 <code class="language-plaintext highlighter-rouge">do_page_fault()</code>）；(4) 内核从 CR2 读出故障地址，检查是否在进程合法栈区内（如 <code class="language-plaintext highlighter-rouge">mm_struct</code> 的 <code class="language-plaintext highlighter-rouge">start_stack</code> 及 <code class="language-plaintext highlighter-rouge">ulimit -s</code> 限制），若合法则分配物理页、在页表中建立映射并标为可读写；(5) 返回用户态后，原指令重试，此时已有映射，写入成功。这一过程对开发者透明，且<strong>每个页只在首次触及该页时发生一次</strong>，即<strong>惰性分配（Lazy Allocation）</strong>：只为实际使用的栈页分配物理内存，若函数分配了大数组但从未访问，就不会占用物理页<sup id="fnref:2:1"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>。</p>

<p><strong>读与零页</strong>：若首次操作是<strong>读</strong>，内核可先将该虚拟页映射到全局只读的<strong>零页</strong>；只有后续发生<strong>写</strong>时才触发写时拷贝（COW），分配真正的物理页并清零，与匿名堆区的零页机制一致。</p>

<p><strong>主线程栈与线程栈</strong>：主线程栈可在合法范围内按需增长（访问新区域触发合法缺页即可）；通过 <code class="language-plaintext highlighter-rouge">pthread_create</code> 创建的线程，其栈通常在创建时用 <code class="language-plaintext highlighter-rouge">mmap</code> 一次性映射固定大小（如 8MB），虚拟范围固定，不会像主线程那样向低地址方向动态增长，访问未映射区域仍会触发缺页并分配物理页。</p>

<p>需要强调的是：<strong>在发生缺页的那一刻</strong>，栈和堆走的是同一条内核路径（#PF → 分配物理页 → 建立映射，必要时清零），单看这一次缺页本身，<strong>栈并不比堆快</strong>。栈的「快」体现在：分配虚拟空间无需系统调用（§1）；缺页通常只在首次触及该页时发生一次，成本被摊薄；一旦物理页已常驻，栈与堆的访问就是普通内存访问，没有差别。</p>

<p><strong>类比</strong>：<code class="language-plaintext highlighter-rouge">sub rsp, N</code> 像在借书卡（页表）上登记一个新书名（虚拟地址），只是记录；<strong>首次访问</strong>像第一次去书架上取书——管理员发现书（物理页）还在仓库，于是取书、上架、更新借书卡，你才能拿到；若该地址不在进程合法地址空间内，则相当于”查无此书”，会引发 SIGSEGV 等错误。</p>

<h3 id="内核视角栈与堆的本质区别是-vma-生命周期">内核视角：栈与堆的本质区别是 VMA 生命周期</h3>

<p>从内核角度看，<strong>并不区分”栈”与”堆”</strong>，只区分<strong>虚拟内存区域（VMA）的类型和生命周期</strong>。理解这一点是理解性能差异的关键。</p>

<h4 id="栈-vma进程级生命周期">栈 VMA：进程级生命周期</h4>

<p>栈在进程启动时由内核创建（<code class="language-plaintext highlighter-rouge">fs/exec.c:setup_arg_pages()</code>），设置 <strong><code class="language-plaintext highlighter-rouge">VM_GROWSDOWN</code></strong> 标志，表明这是一个”向下增长”的区域：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 简化自 fs/exec.c:778</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">setup_arg_pages</span><span class="p">(</span><span class="k">struct</span> <span class="n">linux_binprm</span> <span class="o">*</span><span class="n">bprm</span><span class="p">,</span> <span class="p">...)</span> <span class="p">{</span>
    <span class="n">vma</span> <span class="o">=</span> <span class="n">vm_area_alloc</span><span class="p">(</span><span class="n">mm</span><span class="p">);</span>
    <span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_start</span> <span class="o">=</span> <span class="n">stack_top</span> <span class="o">-</span> <span class="n">STACK_TOP_MAX</span><span class="p">;</span>  <span class="c1">// 通常 8MB</span>
    <span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_end</span> <span class="o">=</span> <span class="n">stack_top</span><span class="p">;</span>
    <span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_flags</span> <span class="o">=</span> <span class="n">VM_STACK_FLAGS</span> <span class="o">|</span> <span class="n">VM_GROWSDOWN</span><span class="p">;</span>  <span class="c1">// 唯一特殊标志</span>
    <span class="n">insert_vm_struct</span><span class="p">(</span><span class="n">mm</span><span class="p">,</span> <span class="n">vma</span><span class="p">);</span>
    <span class="c1">// 关键：只创建 VMA，不分配物理页</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>关键点</strong>：</p>
<ol>
  <li>VMA 在<strong>进程启动时</strong>创建，<strong>进程退出时</strong>销毁（生命周期 = 进程）</li>
  <li><code class="language-plaintext highlighter-rouge">VM_GROWSDOWN</code> 只是一个标志位，告诉内核这个 VMA 可以向低地址扩展</li>
  <li>创建时<strong>不分配任何物理页</strong>，物理页在首次访问时按需分配</li>
  <li><strong>函数调用期间，VMA 始终存在</strong>——这就是为什么栈分配不需要系统调用</li>
</ol>

<h4 id="堆-vma两种生命周期">堆 VMA：两种生命周期</h4>

<p><strong>brk 堆</strong>（小块分配）：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 简化自 mm/mmap.c:115</span>
<span class="n">SYSCALL_DEFINE1</span><span class="p">(</span><span class="n">brk</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span><span class="p">,</span> <span class="n">brk</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 扩展堆顶，可能扩展已有 VMA 或创建新 VMA</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">do_brk_flags</span><span class="p">(</span><span class="o">&amp;</span><span class="n">vmi</span><span class="p">,</span> <span class="n">brkvma</span><span class="p">,</span> <span class="n">oldbrk</span><span class="p">,</span> <span class="n">newbrk</span> <span class="o">-</span> <span class="n">oldbrk</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span>
        <span class="k">goto</span> <span class="n">out</span><span class="p">;</span>
    <span class="n">mm</span><span class="o">-&gt;</span><span class="n">brk</span> <span class="o">=</span> <span class="n">brk</span><span class="p">;</span>  <span class="c1">// 更新堆顶指针</span>
    <span class="c1">// 关键：也只修改 VMA，不分配物理页（除非 VM_LOCKED）</span>
<span class="p">}</span>
</code></pre></div></div>

<ul>
  <li><strong>生命周期</strong>：首次 <code class="language-plaintext highlighter-rouge">brk()</code> 时创建，进程退出时销毁（类似栈）</li>
  <li><strong>无特殊标志</strong>：没有 <code class="language-plaintext highlighter-rouge">VM_GROWSDOWN</code>，但 VMA 同样持久</li>
</ul>

<p><strong>mmap 堆</strong>（大块分配，通常 ≥128KB）：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 简化自 mm/mmap.c:337</span>
<span class="kt">unsigned</span> <span class="kt">long</span> <span class="nf">do_mmap</span><span class="p">(</span><span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">file</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">addr</span><span class="p">,</span> <span class="p">...)</span> <span class="p">{</span>
    <span class="n">vma</span> <span class="o">=</span> <span class="n">vm_area_alloc</span><span class="p">(</span><span class="n">mm</span><span class="p">);</span>
    <span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_start</span> <span class="o">=</span> <span class="n">addr</span><span class="p">;</span>
    <span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_end</span> <span class="o">=</span> <span class="n">addr</span> <span class="o">+</span> <span class="n">len</span><span class="p">;</span>
    <span class="n">vma_link</span><span class="p">(</span><span class="n">mm</span><span class="p">,</span> <span class="n">vma</span><span class="p">,</span> <span class="p">...);</span>  <span class="c1">// 插入红黑树</span>
    <span class="k">return</span> <span class="n">addr</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// munmap 销毁</span>
<span class="kt">int</span> <span class="nf">do_munmap</span><span class="p">(...)</span> <span class="p">{</span>
    <span class="n">unmap_page_range</span><span class="p">(</span><span class="n">vma</span><span class="p">,</span> <span class="p">...);</span>   <span class="c1">// 删除页表项</span>
    <span class="n">free_pgtables</span><span class="p">(...);</span>            <span class="c1">// 释放页表</span>
    <span class="n">remove_vma</span><span class="p">(</span><span class="n">vma</span><span class="p">);</span>               <span class="c1">// 删除 VMA</span>
<span class="p">}</span>
</code></pre></div></div>

<ul>
  <li><strong>生命周期</strong>：每次 <code class="language-plaintext highlighter-rouge">mmap()</code> 创建，每次 <code class="language-plaintext highlighter-rouge">munmap()</code> 销毁（临时性）</li>
  <li><strong>无特殊标志</strong>：普通匿名映射</li>
  <li><strong>关键差异</strong>：每次 <code class="language-plaintext highlighter-rouge">malloc</code>/<code class="language-plaintext highlighter-rouge">free</code> 大块时都要创建/销毁 VMA，触发系统调用</li>
</ul>

<h4 id="缺页处理栈与堆完全相同">缺页处理：栈与堆完全相同</h4>

<p>无论是栈、brk 堆还是 mmap 堆，首次访问时都走同一条缺页路径：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// mm/memory.c:5022</span>
<span class="k">static</span> <span class="n">vm_fault_t</span> <span class="nf">do_anonymous_page</span><span class="p">(</span><span class="k">struct</span> <span class="n">vm_fault</span> <span class="o">*</span><span class="n">vmf</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">folio</span> <span class="o">=</span> <span class="n">alloc_anon_folio</span><span class="p">(</span><span class="n">vmf</span><span class="p">);</span>        <span class="c1">// 分配物理页</span>
    <span class="n">__folio_mark_uptodate</span><span class="p">(</span><span class="n">folio</span><span class="p">);</span>         <span class="c1">// 清零</span>
    <span class="n">entry</span> <span class="o">=</span> <span class="n">folio_mk_pte</span><span class="p">(</span><span class="n">folio</span><span class="p">,</span> <span class="p">...);</span>
    <span class="n">set_ptes</span><span class="p">(</span><span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_mm</span><span class="p">,</span> <span class="n">addr</span><span class="p">,</span> <span class="p">...);</span>      <span class="c1">// 建立页表映射</span>
    <span class="c1">// 内核不关心这是栈还是堆！处理流程完全相同</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>结论</strong>：在缺页处理层面，栈和堆<strong>没有任何区别</strong>。单次缺页的成本相同（~20-50μs），都需要分配物理页、清零、建立页表。</p>

<h4 id="性能差异的真正来源">性能差异的真正来源</h4>

<table>
  <thead>
    <tr>
      <th>维度</th>
      <th>栈 VMA</th>
      <th>brk 堆 VMA</th>
      <th>mmap 堆 VMA</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>VMA 标志</strong></td>
      <td>VM_GROWSDOWN</td>
      <td>无</td>
      <td>无</td>
    </tr>
    <tr>
      <td><strong>生命周期</strong></td>
      <td>进程级别</td>
      <td>进程级别</td>
      <td>malloc/free 级别</td>
    </tr>
    <tr>
      <td><strong>创建/销毁</strong></td>
      <td>进程启动/退出</td>
      <td>首次 brk/进程退出</td>
      <td>每次 mmap/munmap</td>
    </tr>
    <tr>
      <td><strong>页表持久性</strong></td>
      <td>持久（扩展时保留）</td>
      <td>持久（扩展时保留）</td>
      <td>临时（munmap 删除）</td>
    </tr>
    <tr>
      <td><strong>缺页处理</strong></td>
      <td>do_anonymous_page</td>
      <td>do_anonymous_page</td>
      <td>do_anonymous_page</td>
    </tr>
    <tr>
      <td><strong>运行时系统调用</strong></td>
      <td>0 次</td>
      <td>0 次（扩展后）</td>
      <td>每次分配/释放 2 次</td>
    </tr>
  </tbody>
</table>

<p><strong>性能差异不是因为内核对栈和堆的”处理方式”不同</strong>，而是：</p>
<ol>
  <li><strong>VMA 生命周期不同</strong>：栈的 VMA 在进程启动时创建，持续到进程结束；mmap 堆的 VMA 每次 malloc/free 都要创建/销毁</li>
  <li><strong>系统调用频率不同</strong>：栈分配只需改栈指针（CPU 指令），mmap 堆每次都要 <code class="language-plaintext highlighter-rouge">mmap()/munmap()</code> 系统调用</li>
  <li><strong>页表持久性不同</strong>：栈扩展（<code class="language-plaintext highlighter-rouge">expand_stack_locked</code>）只修改 VMA 范围，页表映射保留；<code class="language-plaintext highlighter-rouge">munmap</code> 会删除页表，下次 <code class="language-plaintext highlighter-rouge">mmap</code> 必须重建</li>
</ol>

<h4 id="栈的只增不减特性与物理页缓存">栈的”只增不减”特性与物理页缓存</h4>

<p><strong>VMA 层面</strong>：内核没有 <code class="language-plaintext highlighter-rouge">shrink_stack</code> 函数，栈的虚拟地址范围（<code class="language-plaintext highlighter-rouge">vma-&gt;vm_start</code> - <code class="language-plaintext highlighter-rouge">vma-&gt;vm_end</code>）在进程运行期间<strong>只增不减</strong>，永远保持历史最大值：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 深度递归扩展栈后
VMA: [0x7FEF7000, 0x7FFFFFFF]  // 16MB

// 递归返回，rsp 上移，但 VMA 不缩小
VMA: [0x7FEF7000, 0x7FFFFFFF]  // 仍是 16MB
</code></pre></div></div>

<p><strong>物理页层面</strong>：更关键的是，函数返回后<strong>物理页默认不释放</strong>，页表映射保持不变。这是栈性能的核心优势。需要区分两种场景：</p>

<p><strong>场景 1：持续访问新页</strong>（栈也会缺页）</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>深度递归访问新栈区域：
    第 1 层 → 访问虚拟页 A → 缺页 #1
    第 2 层 → 访问虚拟页 B（新页）→ 缺页 #2
    ...
    第 100 层 → 访问虚拟页 Z（新页）→ 缺页 #100

持续访问新页时，栈也会持续缺页
</code></pre></div></div>

<p><strong>场景 2：重复访问已访问页</strong>（栈的优势）</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>第 1 次深度递归（100 层）：
    触发缺页 × 100 → 分配 100 个物理页 → 建立页表映射
    递归返回 → rsp 上移 → 但页表映射保留
    成本：100 × 30μs = 3ms

第 2-1000 次相同深度递归（100 层）：
    rsp 下移到相同虚拟地址 → 页表已有映射 → 0 次缺页
    成本：0μs  ← 物理页”缓存”在页表中

对比 mmap 堆（相同大小的重复 malloc/free）：
    第 1 次：mmap() → 缺页 × 32 → munmap() 删除页表
    第 2 次：mmap() → 缺页 × 32 → munmap() 删除页表
    ...
    1000 次迭代：1000 × (32 × 30μs) = 960ms
</code></pre></div></div>

<p>实际应用中，大部分函数调用是<strong>相同深度的重复</strong>，因此栈表现出显著的性能优势。</p>

<p>内核允许用户态通过 <code class="language-plaintext highlighter-rouge">madvise(MADV_DONTNEED)</code> 显式释放栈的物理页（保留 VMA），但<strong>默认行为是保留</strong>以优化性能。进程退出时，<code class="language-plaintext highlighter-rouge">exit_mmap()</code> 才释放所有 VMA 和物理页。</p>

<table>
  <thead>
    <tr>
      <th>维度</th>
      <th>栈</th>
      <th>mmap 堆</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>VMA 创建</td>
      <td>进程启动 1 次</td>
      <td>每次 malloc</td>
    </tr>
    <tr>
      <td>物理页分配</td>
      <td>首次访问该页</td>
      <td>每次访问</td>
    </tr>
    <tr>
      <td>物理页释放</td>
      <td><strong>默认不释放</strong></td>
      <td><strong>每次 free 都释放</strong></td>
    </tr>
    <tr>
      <td>再次访问同一页</td>
      <td><strong>无缺页</strong>（页表复用）</td>
      <td><strong>重新缺页</strong>（页表已删除）</td>
    </tr>
    <tr>
      <td>访问新页</td>
      <td><strong>缺页</strong>（首次访问）</td>
      <td><strong>缺页</strong>（首次访问）</td>
    </tr>
  </tbody>
</table>

<p>这种”懒惰”策略（VMA 不缩小、物理页不释放）正是栈性能优势的根本来源：对于<strong>重复访问的栈区域</strong>，首次缺页后物理页常驻在页表中，避免反复的分配-释放-再分配循环；但访问新的更深栈区域时，栈也会缺页。实际应用中函数调用多是相同深度的重复，因此栈表现出显著优势。</p>

<h3 id="堆mmap-与安全清零">堆：mmap 与安全清零</h3>

<p>通过 <code class="language-plaintext highlighter-rouge">mmap</code> 获取匿名内存时，内核会保证进程看到的是「零填充」：要么在缺页时分配并清零，要么先映射到全局零页，写时再分配（copy-on-write），避免读到其他进程残留数据<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup><sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>。Gorman《Understanding the Linux Virtual Memory Manager》Ch4 对用户态区段的描述<sup id="fnref:9:1"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>：</p>

<blockquote>
  <p>With a process, space is simply reserved in the linear address space by pointing a page table entry to a read-only globally visible page filled with zeros. On writing, a page fault is triggered which results in a new page being allocated, filled with zeros, placed in the page table entry and marked writable.</p>
</blockquote>

<p>无论哪种方式都会在首次写时产生分配/清零或 COW 开销。<code class="language-plaintext highlighter-rouge">malloc</code> 往往通过 <code class="language-plaintext highlighter-rouge">mmap</code> 或 <code class="language-plaintext highlighter-rouge">sbrk</code> 拿到大块后再在用户态切分、复用，以摊薄这类成本。</p>

<h2 id="3-缓存友好性">3. 缓存友好性</h2>

<h3 id="栈局部性更好">栈：局部性更好</h3>

<p>栈的访问模式是典型的 LIFO，当前活跃的局部变量多集中在栈顶附近，容易落在 CPU 的 L1/L2 缓存中，命中率高。</p>

<h3 id="堆访问模式更分散">堆：访问模式更分散</h3>

<p>堆上对象由程序显式管理，链表、树等结构容易在地址空间内分散，导致缓存行利用率低、更多访问主存。</p>

<hr />

<h2 id="4-从内核到用户态批发-零售链条">4. 从内核到用户态：「批发-零售」链条</h2>

<p>结合 <strong>sbrk</strong>、<strong>Slab</strong> 和 <strong>malloc</strong>，可以把内存分配看成一条从内核到 CPU 的链条；栈之所以「快」，是因为它处在链条末端，几乎不经中间层。</p>

<h3 id="41-一级批发内核-buddy伙伴系统">4.1 一级批发：内核 Buddy（伙伴系统）</h3>

<p>物理内存以<strong>页</strong>（通常 4KB）为最小单位管理，由伙伴系统负责分配和回收：按 2^order 页块管理，不足时分裂大块、释放时与伙伴合并。粒度较粗，不适合直接满足「几十字节」的小请求<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">6</a></sup><sup id="fnref:9:2"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>。</p>

<h3 id="42-二级批发内核-slab">4.2 二级批发：内核 Slab</h3>

<p><strong>Slab 分配器</strong>从伙伴系统拿到整页，再切成固定大小的对象并缓存，主要服务内核自身（如 <code class="language-plaintext highlighter-rouge">task_struct</code>、<code class="language-plaintext highlighter-rouge">inode</code> 等）。对象用完后可留在 Slab 中复用，减少对伙伴系统的调用，并缓解内碎片、提高缓存利用率<sup id="fnref:5:1"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">6</a></sup><sup id="fnref:9:3"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>。</p>

<h3 id="43-用户态代理malloc-与-sbrk">4.3 用户态代理：malloc 与 sbrk</h3>

<p>用户程序通过 <strong><code class="language-plaintext highlighter-rouge">malloc</code></strong> 获取堆内存。当内部池不足时，<code class="language-plaintext highlighter-rouge">malloc</code> 会调用 <strong><code class="language-plaintext highlighter-rouge">sbrk</code></strong> 或 <strong><code class="language-plaintext highlighter-rouge">mmap</code></strong>：</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">sbrk</code></strong> 调整 program break，向内核「圈」出一块新的虚拟地址空间，本身是一次系统调用，成本较高；内核用 <code class="language-plaintext highlighter-rouge">mm-&gt;brk</code> 与 VMA 管理堆顶<sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">7</a></sup><sup id="fnref:9:4"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">malloc</code></strong> 把拿到的大块在用户态切分、合并、复用，承担「零售」角色，带来管理开销和可能的碎片。内存池、arena 等做法正是通过减少对 <code class="language-plaintext highlighter-rouge">brk</code>/<code class="language-plaintext highlighter-rouge">mmap</code> 的调用次数来降低与内核的交互成本；从系统视角看，这与「用户态与内核态壁垒」、减少系统调用的思路一致，可参见本博客<a href="https://weinan.io/2026/03/01/why-language-speed-is-misleading.html">《为什么「语言速度」是伪命题》</a><sup id="fnref:10"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>。</li>
</ul>

<h3 id="44-栈无中间商的自家后院">4.4 栈：无中间商的「自家后院」</h3>

<p>栈不经过上述任何一层：分配就是改栈指针，无需系统调用；物理页在首次访问时按需分配（§2），LIFO 访问模式又利于缓存。因此处在链条最末端，面向 CPU，成本最低。</p>

<h3 id="45-开销大致顺序从慢到快">4.5 开销大致顺序（从慢到快）</h3>

<table>
  <thead>
    <tr>
      <th>层级</th>
      <th>机制</th>
      <th>特点</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>最慢</td>
      <td>系统调用（sbrk/mmap）</td>
      <td>用户态/内核态切换，微秒级</td>
    </tr>
    <tr>
      <td>中等</td>
      <td>用户态堆管理（malloc/free）</td>
      <td>无模式切换，但有锁与查找</td>
    </tr>
    <tr>
      <td>较快</td>
      <td>内核 Slab（kmem_cache_alloc）</td>
      <td>内核内复用，无系统调用</td>
    </tr>
    <tr>
      <td>最快</td>
      <td>栈指针调整（sub rsp）</td>
      <td>纯用户态指令，纳秒级</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="5-栈比堆快的边界">5. 「栈比堆快」的边界</h2>

<p>单纯比较「栈和堆谁快」容易误导，因为两者不在同一维度：栈更多是「使用已就绪内存」，堆还涉及「获取」和「管理」。</p>

<h3 id="51-分配模式才是关键">5.1 分配模式才是关键</h3>

<p>若<strong>事先在堆上分配好一块内存，再反复读写</strong>，其访问速度与栈上同规模数据可以非常接近——此时差异主要在「分配方式」，而非「存储介质」。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 栈：分配 + 使用</span>
<span class="kt">void</span> <span class="nf">stack_func</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">arr</span><span class="p">[</span><span class="mi">1000</span><span class="p">];</span>   <span class="c1">// 分配：改栈指针</span>
    <span class="n">arr</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span>     <span class="c1">// 使用：普通内存访问</span>
<span class="p">}</span>

<span class="c1">// 堆：一次性分配，反复使用</span>
<span class="k">static</span> <span class="kt">int</span> <span class="o">*</span><span class="n">heap_arr</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">heap_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">heap_arr</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mi">1000</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">int</span><span class="p">));</span>  <span class="c1">// 仅此一次有系统调用/分配器开销</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">heap_func</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">heap_arr</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span>   <span class="c1">// 使用：与栈上访问同属「已就绪内存」</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="52-堆可以模拟栈的分配模式">5.2 堆可以模拟栈的分配模式</h3>

<p>Arena、pool 等分配器本质是在堆上<strong>模拟栈</strong>：一次性向系统要一大块，用指针顺序分配，最后整体释放。在这种模式下，堆上的「分配」成本可以接近栈。</p>

<h3 id="53-值得关注的维度">5.3 值得关注的维度</h3>

<table>
  <thead>
    <tr>
      <th>维度</th>
      <th>栈</th>
      <th>堆</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>分配速度</td>
      <td>固定、极快</td>
      <td>视是否命中缓存、是否触发系统调用而定</td>
    </tr>
    <tr>
      <td>可预测性</td>
      <td>高</td>
      <td>可能受碎片、锁竞争影响</td>
    </tr>
    <tr>
      <td>适用场景</td>
      <td>小数据、生命周期与调用栈一致</td>
      <td>大数据、生命周期动态</td>
    </tr>
  </tbody>
</table>

<p>栈的「快」是用<strong>约束</strong>换来的：大小有限、生命周期必须 LIFO。堆的灵活则伴随分配与管理开销。工程上更值得关心的是：在给定场景下，应优先用栈、对象池还是堆。</p>

<h3 id="54-缺页路径上栈与堆等价但缺页频率不同">5.4 缺页路径上栈与堆等价，但缺页频率不同</h3>

<p>若只比较「第一次访问某页、触发缺页」的那条路径，栈和堆没有区别：都是 #PF → 内核分配物理页 → 映射（堆上匿名区还可能多一步清零或 COW）。因此<strong>在单次缺页场景下，栈并不比堆快</strong>（单次成本都是 ~20-50μs）。</p>

<p>「栈比堆快」指的是：</p>
<ol>
  <li><strong>分配虚拟空间的成本</strong>：栈几乎为零（改栈指针），堆可能涉及系统调用</li>
  <li><strong>缺页发生频率</strong>（典型场景）：栈访问新页时也会缺页，但实际应用中多是相同深度的重复调用，物理页默认不释放、页表映射持久保留，因此重复访问 0 次缺页；mmap 堆每次 <code class="language-plaintext highlighter-rouge">munmap</code> 删除页表，相同大小的重复分配每次都要重建页表并重新缺页</li>
  <li><strong>物理页”缓存”机制</strong>：栈在首次访问某深度后，该范围内的物理页常驻页表（除非显式释放）；堆每次 malloc/free 都要释放物理页并删除页表</li>
</ol>

<p>用数字说明典型差异：1000 次相同深度的栈调用可能只触发 1 次缺页（首次访问该深度），而 1000 次相同大小的 mmap 堆分配会触发 1000 次缺页循环。但若持续访问更深的栈区域（新页），栈也会持续缺页。</p>

<h3 id="55-用户态申请堆内存是否一定触发缺页">5.5 用户态申请堆内存是否一定触发缺页？</h3>

<p><strong>不一定。</strong> 内核源码可以验证两点：</p>

<ol>
  <li>
    <p><strong>默认情况</strong>：用户态通过 <code class="language-plaintext highlighter-rouge">brk</code>/<code class="language-plaintext highlighter-rouge">sbrk</code> 或 <code class="language-plaintext highlighter-rouge">mmap(MAP_ANONYMOUS)</code>「申请」堆内存时，内核<strong>只建立或扩展 VMA</strong>（虚拟区间），并不立刻分配物理页。<code class="language-plaintext highlighter-rouge">mm/vma.c</code> 中的 <strong><code class="language-plaintext highlighter-rouge">do_brk_flags()</code></strong> 仅做 <code class="language-plaintext highlighter-rouge">vm_area_alloc</code>、设置区间与 flags、挂入红黑树，没有任何 <code class="language-plaintext highlighter-rouge">alloc_pages</code> 或 <code class="language-plaintext highlighter-rouge">mm_populate</code>。因此物理页要等到<strong>首次访问</strong>该区间时由缺页处理程序分配，那时才会触发一次 #PF。</p>
  </li>
  <li>
    <p><strong>会预填页、从而首次访问不触发缺页的情况</strong>：</p>
    <ul>
      <li><strong><code class="language-plaintext highlighter-rouge">mmap(..., MAP_POPULATE)</code></strong>：<code class="language-plaintext highlighter-rouge">mm/mmap.c</code> 里 <code class="language-plaintext highlighter-rouge">do_mmap</code> 在成功建立映射后，若 flags 含 <code class="language-plaintext highlighter-rouge">MAP_POPULATE</code>（且非 <code class="language-plaintext highlighter-rouge">MAP_NONBLOCK</code>），会设置 <code class="language-plaintext highlighter-rouge">*populate = len</code>，返回用户态前由 <code class="language-plaintext highlighter-rouge">mm_populate(ret, populate)</code> 在内核里把页 fault in，所以用户第一次访问时页已在，不会 #PF。</li>
      <li><strong>扩展 brk 且进程曾 <code class="language-plaintext highlighter-rouge">mlockall</code>（<code class="language-plaintext highlighter-rouge">mm-&gt;def_flags &amp; VM_LOCKED</code>）</strong>：<code class="language-plaintext highlighter-rouge">mm/mmap.c</code> 中 <strong><code class="language-plaintext highlighter-rouge">SYSCALL_DEFINE1(brk, ...)</code></strong> 在 <code class="language-plaintext highlighter-rouge">do_brk_flags()</code> 成功后若 <code class="language-plaintext highlighter-rouge">mm-&gt;def_flags &amp; VM_LOCKED</code>，会调用 <code class="language-plaintext highlighter-rouge">mm_populate(oldbrk, newbrk - oldbrk)</code>，在 brk 返回前就预填新堆区间的页，用户首次访问同样不会触发缺页。</li>
    </ul>
  </li>
</ol>

<p>因此：<strong>「申请」堆内存本身通常不触发缺页；缺页发生在首次访问新区间时。</strong> 只有在使用 <code class="language-plaintext highlighter-rouge">MAP_POPULATE</code> 或 <code class="language-plaintext highlighter-rouge">VM_LOCKED</code> 时，内核会在申请路径上预填页，此时首次访问不再触发缺页（代价是 brk/mmap 变慢、可能失败）。</p>

<h3 id="56-实验验证栈增长模式对比">5.6 实验验证：栈增长模式对比</h3>

<p>为验证「持续访问新栈页会持续缺页」这一关键观察，在 <a href="https://github.com/liweinan/stack-vs-heap-benchmark">stack-vs-heap-benchmark</a> 项目中实现了对比实验（<code class="language-plaintext highlighter-rouge">src/stack_growth_comparison.c</code>），测试两种栈使用模式的缺页行为。</p>

<p><strong>实验配置</strong>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 关键：使用 -O0 编译，禁用优化以确保真实的栈分配</span>
<span class="n">gcc</span> <span class="o">-</span><span class="n">O0</span> <span class="o">-</span><span class="n">Wall</span> <span class="o">-</span><span class="n">Wextra</span> <span class="o">-</span><span class="n">g</span> <span class="o">-</span><span class="n">o</span> <span class="n">stack_growth_comparison</span> <span class="n">src</span><span class="o">/</span><span class="n">stack_growth_comparison</span><span class="p">.</span><span class="n">c</span>

<span class="cp">#define PAGES_PER_CALL 4  // 每次调用占用 4 页（16KB）
#define ITERATIONS 100
</span>
<span class="c1">// 场景 1：固定深度重复调用（页表复用）</span>
<span class="kt">void</span> <span class="nf">fixed_depth_call</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">char</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">16384</span><span class="p">];</span>  <span class="c1">// 4 页</span>
    <span class="c1">// 访问每个页的首尾字节，确保触发缺页...</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">100</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">fixed_depth_call</span><span class="p">();</span>  <span class="c1">// 每次调用相同栈位置</span>
<span class="p">}</span>

<span class="c1">// 场景 2：持续增长递归深度（持续缺页）</span>
<span class="kt">void</span> <span class="nf">growing_depth_call</span><span class="p">(</span><span class="kt">int</span> <span class="n">depth</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">char</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">16384</span><span class="p">];</span>  <span class="c1">// 每层 4 页</span>
    <span class="c1">// 访问每个页...</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">depth</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="n">growing_depth_call</span><span class="p">(</span><span class="n">depth</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span>  <span class="c1">// 递归到更深</span>
<span class="p">}</span>
<span class="n">growing_depth_call</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>  <span class="c1">// 100 层递归</span>
</code></pre></div></div>

<p><strong>实验结果</strong>（在 Docker Alpine Linux 环境中，使用 perf 统计）：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>perf <span class="nb">stat</span> <span class="nt">-e</span> page-faults ./stack_growth_comparison

<span class="o">===</span> 场景 1: 固定深度重复调用 <span class="o">===</span>
配置: 100 次调用，每次 4 页（16KB）
预期: 第 1 次缺页 4 次，后续 99 次无缺页（页表保留）
执行时间: 0.012 ms
平均每次: 118 ns

<span class="o">===</span> 场景 2: 持续增长递归深度 <span class="o">===</span>
配置: 100 层递归，每层 4 页（16KB）
预期: 持续缺页 400 次（每层访问新页）
执行时间: 0.272 ms        ← 慢 23 倍！
平均每层: 2715 ns

Performance counter stats <span class="k">for</span> <span class="s1">'./stack_growth_comparison'</span>:

               424      page-faults    ← 接近预期 400 次（100 层 × 4 页）

       0.000999083 seconds <span class="nb">time </span>elapsed
</code></pre></div></div>

<p><strong>关键发现</strong>：</p>

<table>
  <thead>
    <tr>
      <th>场景</th>
      <th>缺页次数</th>
      <th>执行时间</th>
      <th>平均每次</th>
      <th>差异倍数</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>场景 1（固定深度）</td>
      <td>~4 次</td>
      <td>0.012 ms</td>
      <td>118 ns</td>
      <td>基准</td>
    </tr>
    <tr>
      <td>场景 2（持续增长）</td>
      <td>~400 次</td>
      <td>0.272 ms</td>
      <td>2715 ns</td>
      <td><strong>23 倍</strong></td>
    </tr>
  </tbody>
</table>

<p><strong>实验验证的核心观察</strong>：</p>

<ol>
  <li>
    <p>✅ <strong>持续访问新栈页会持续缺页</strong>：场景 2 产生 424 次缺页，接近理论预期 400 次（100 层 × 4 页/层）。多出的 ~24 次来自程序启动、库初始化及场景 1 的栈分配。</p>
  </li>
  <li>
    <p>✅ <strong>重复访问已访问区域几乎不缺页</strong>：场景 1 重复调用 100 次相同深度函数，仅首次触发约 4 次缺页，后续 99 次调用 0 次缺页。</p>
  </li>
  <li>
    <p>✅ <strong>性能差异显著</strong>：持续缺页（场景 2）比页表复用（场景 1）慢 <strong>23 倍</strong>（0.272 ms vs 0.012 ms），平均每次 2715 ns vs 118 ns。</p>
  </li>
  <li>
    <p>✅ <strong>实测单次缺页成本</strong>：从时间差计算，单次缺页成本约 (2715 - 118) ns ≈ <strong>2.6 μs</strong>，低于内核文档中提到的理论值 20-50 μs，得益于现代内核的优化（TLB 缓存、页预取、批量操作等）。</p>
  </li>
</ol>

<p><strong>结论</strong>：这个实验<strong>完美验证</strong>了「栈的快不是因为永远不缺页」这一关键观察：</p>
<ul>
  <li>访问新栈页时，栈也会持续缺页（如深度递归）</li>
  <li>栈的真正优势在于<strong>页表持久性</strong>：重复访问的区域，页表映射保留，避免像 mmap 堆那样每次 <code class="language-plaintext highlighter-rouge">munmap</code> 删除、<code class="language-plaintext highlighter-rouge">mmap</code> 重建</li>
  <li>实际应用中多是相同深度的重复调用（如场景 1），因此栈表现”快”；若应用场景是深度递归（如场景 2），栈的缺页行为与分配相同大小的堆区别不大，性能优势主要体现在无需系统调用（VMA 持久）</li>
</ul>

<hr />

<h2 id="总结">总结</h2>

<ol>
  <li><strong>同一进程内，栈和堆的「访问」速度无本质差别</strong>；差异主要来自<strong>分配方式</strong>与<strong>物理页的建立方式</strong>（栈按需缺页，堆常伴随清零或 COW）。</li>
  <li><strong>内核不区分”栈”与”堆”，只区分 VMA 的类型和生命周期</strong>：栈的特殊性仅是 <code class="language-plaintext highlighter-rouge">VM_GROWSDOWN</code> 标志；真正的性能差异来自 VMA 生命周期——栈 VMA 在进程启动时创建、进程退出时销毁（0 次运行时系统调用），mmap 堆 VMA 每次 malloc/free 都要创建/销毁（频繁系统调用）。</li>
  <li><strong>栈的”只增不减”与物理页缓存机制</strong>：VMA 范围在运行期间只增不减（没有 <code class="language-plaintext highlighter-rouge">shrink_stack</code>），更关键的是<strong>物理页默认不释放</strong>——函数返回后页表映射保持不变，这是性能核心：相同栈深度的重复访问首次缺页后，物理页”缓存”在页表中，后续访问 0 次缺页（但持续访问新的更深栈区域仍会缺页）；mmap 堆每次 <code class="language-plaintext highlighter-rouge">munmap</code> 删除页表，相同大小的重复分配每次都要重建页表并重新缺页。<strong>实验验证</strong>（§5.6）：固定深度重复调用（100 次）vs 持续增长递归（100 层），缺页次数 ~4 vs ~400，性能差异 23 倍（0.012 ms vs 0.272 ms）。</li>
  <li><strong>在缺页发生的那一刻</strong>，栈与堆走同一条内核路径（<code class="language-plaintext highlighter-rouge">do_anonymous_page</code>），单次成本相同（~20-50μs）；<strong>栈的快</strong>体现在：分配虚拟空间零成本（改栈指针）、VMA 持久（无系统调用）、页表持久（<code class="language-plaintext highlighter-rouge">expand_stack</code> 保留映射，避免反复缺页）、LIFO 带来的缓存局部性。</li>
  <li>从内核 Buddy → Slab → sbrk/mmap → malloc 到栈，是一条「批发-零售」链；栈在末端、无中间层，分配成本最低。</li>
  <li><strong>「栈比堆快」</strong>是有用的经验法则，但不是普适真理；工程上更值得关心的是「为什么快」和「在什么情况下快」，再按场景选择栈、池或堆。从选型与系统视角看，「谁快」往往不是唯一维度，I/O、并发与内存同内核的交互方式同样关键，可参见本博客<a href="https://weinan.io/2026/03/01/why-language-speed-is-misleading.html">《为什么「语言速度」是伪命题》</a><sup id="fnref:10:1"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>。</li>
</ol>

<h2 id="扩展阅读">扩展阅读</h2>

<h3 id="intel-sdm-vol3a-第-6-章">Intel SDM Vol.3A 第 6 章<sup id="fnref:2:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></h3>

<p>§6.14.2「64-Bit Mode Stack Frame」原文：</p>

<blockquote>
  <p>In IA-32e mode, the RSP is aligned to a 16-byte boundary before pushing the stack frame. The stack frame itself is aligned on a 16-byte boundary when the interrupt handler is called.</p>
</blockquote>

<p>§6.15「Exception and Interrupt Reference」中 <strong>Interrupt 14—Page-Fault Exception (#PF)</strong>：Exception Class 为 <strong>Fault</strong>；P=0、权限/写/保留位等触发。SDM 原文：</p>

<blockquote>
  <p>The exception handler can recover from page-not-present conditions and restart the program or task without any loss of program continuity.</p>
</blockquote>

<h3 id="mel-gormanunderstanding-the-linux-virtual-memory-manager">Mel Gorman《Understanding the Linux Virtual Memory Manager》<sup id="fnref:9:5"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">5</a></sup></h3>

<ul>
  <li><strong>Ch4 Process Address Space</strong>：<code class="language-plaintext highlighter-rouge">mm_struct</code> 中堆与栈的字段（见下内核代码）；用户态零页与写时缺页见正文 §2 引用。</li>
  <li><strong>Ch6 Physical Page Allocation</strong>：Binary Buddy、<code class="language-plaintext highlighter-rouge">free_area_t</code>（Gorman 书为 2.4/2.6 的 <code class="language-plaintext highlighter-rouge">free_list</code>+<code class="language-plaintext highlighter-rouge">map</code>）、order 分裂/合并。</li>
  <li><strong>Ch8 Slab Allocator</strong>：三目标（硬件缓存、对象缓存、内碎片）、slab coloring、<code class="language-plaintext highlighter-rouge">kmem_cache_alloc</code>、slabs_full/partial/free、per-CPU 缓存。</li>
</ul>

<h3 id="linux-内核源码代码片段与文件说明">Linux 内核源码（代码片段与文件说明）</h3>

<p><strong>1. 栈 VMA 的创建：setup_arg_pages</strong>（<code class="language-plaintext highlighter-rouge">fs/exec.c</code>）</p>

<p>进程启动时创建栈 VMA，设置 <code class="language-plaintext highlighter-rouge">VM_GROWSDOWN</code> 标志，生命周期 = 进程。关键：只创建 VMA，不分配物理页。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 简化自 fs/exec.c:778</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">setup_arg_pages</span><span class="p">(</span><span class="k">struct</span> <span class="n">linux_binprm</span> <span class="o">*</span><span class="n">bprm</span><span class="p">,</span> <span class="p">...)</span> <span class="p">{</span>
    <span class="n">vma</span> <span class="o">=</span> <span class="n">vm_area_alloc</span><span class="p">(</span><span class="n">mm</span><span class="p">);</span>
    <span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_start</span> <span class="o">=</span> <span class="n">stack_top</span> <span class="o">-</span> <span class="n">STACK_TOP_MAX</span><span class="p">;</span>  <span class="c1">// 通常 8MB</span>
    <span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_end</span> <span class="o">=</span> <span class="n">stack_top</span><span class="p">;</span>
    <span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_flags</span> <span class="o">=</span> <span class="n">VM_STACK_FLAGS</span> <span class="o">|</span> <span class="n">VM_GROWSDOWN</span><span class="p">;</span>  <span class="c1">// 栈的唯一特殊标志</span>
    <span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_page_prot</span> <span class="o">=</span> <span class="n">vm_get_page_prot</span><span class="p">(</span><span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_flags</span><span class="p">);</span>
    <span class="n">insert_vm_struct</span><span class="p">(</span><span class="n">mm</span><span class="p">,</span> <span class="n">vma</span><span class="p">);</span>
    <span class="n">mm</span><span class="o">-&gt;</span><span class="n">stack_vm</span> <span class="o">+=</span> <span class="n">vma_pages</span><span class="p">(</span><span class="n">vma</span><span class="p">);</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>2. 栈扩展：expand_stack_locked</strong>（<code class="language-plaintext highlighter-rouge">mm/mmap.c</code>）</p>

<p>栈向下增长时只修改 VMA 范围，<strong>不删除页表</strong>，物理页映射保留。这是栈分配快的关键：页表持久，避免反复缺页。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 简化自 mm/mmap.c:961</span>
<span class="kt">int</span> <span class="nf">expand_stack_locked</span><span class="p">(</span><span class="k">struct</span> <span class="n">vm_area_struct</span> <span class="o">*</span><span class="n">vma</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">address</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_flags</span> <span class="o">&amp;</span> <span class="n">VM_GROWSDOWN</span><span class="p">))</span>
        <span class="k">return</span> <span class="o">-</span><span class="n">EFAULT</span><span class="p">;</span>  <span class="c1">// 检查是否是栈 VMA</span>

    <span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_start</span> <span class="o">=</span> <span class="n">address</span><span class="p">;</span>  <span class="c1">// 只修改 VMA 起始地址</span>
    <span class="n">mm</span><span class="o">-&gt;</span><span class="n">stack_vm</span> <span class="o">+=</span> <span class="n">grow</span><span class="p">;</span>
    <span class="c1">// 关键：不删除页表！已分配的物理页映射保留</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>3. 缺页处理：do_anonymous_page</strong>（<code class="language-plaintext highlighter-rouge">mm/memory.c</code>）</p>

<p>栈、brk 堆、mmap 堆首次访问时都调用此函数，处理流程<strong>完全相同</strong>。单次缺页成本相同（~20-50μs），差异在于缺页<strong>频率</strong>。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 简化自 mm/memory.c:5022</span>
<span class="k">static</span> <span class="n">vm_fault_t</span> <span class="nf">do_anonymous_page</span><span class="p">(</span><span class="k">struct</span> <span class="n">vm_fault</span> <span class="o">*</span><span class="n">vmf</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">folio</span> <span class="o">=</span> <span class="n">alloc_anon_folio</span><span class="p">(</span><span class="n">vmf</span><span class="p">);</span>        <span class="c1">// 分配物理页（栈、堆相同）</span>
    <span class="n">__folio_mark_uptodate</span><span class="p">(</span><span class="n">folio</span><span class="p">);</span>         <span class="c1">// 清零（栈、堆相同）</span>
    <span class="n">entry</span> <span class="o">=</span> <span class="n">folio_mk_pte</span><span class="p">(</span><span class="n">folio</span><span class="p">,</span> <span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_page_prot</span><span class="p">);</span>
    <span class="n">set_ptes</span><span class="p">(</span><span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_mm</span><span class="p">,</span> <span class="n">addr</span><span class="p">,</span> <span class="n">vmf</span><span class="o">-&gt;</span><span class="n">pte</span><span class="p">,</span> <span class="n">entry</span><span class="p">,</span> <span class="n">nr_pages</span><span class="p">);</span>  <span class="c1">// 建立页表</span>
    <span class="n">add_mm_counter</span><span class="p">(</span><span class="n">vma</span><span class="o">-&gt;</span><span class="n">vm_mm</span><span class="p">,</span> <span class="n">MM_ANONPAGES</span><span class="p">,</span> <span class="n">nr_pages</span><span class="p">);</span>
    <span class="c1">// 内核不关心这是栈还是堆！</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>4. 进程地址空间：堆与栈的起止</strong>（<code class="language-plaintext highlighter-rouge">include/linux/mm_types.h</code>）</p>

<p><code class="language-plaintext highlighter-rouge">mm_struct</code> 中描述堆与栈的字段；<code class="language-plaintext highlighter-rouge">sys_brk</code> 通过 <code class="language-plaintext highlighter-rouge">mm-&gt;brk</code>、<code class="language-plaintext highlighter-rouge">mm-&gt;start_brk</code> 管理堆顶<sup id="fnref:8:1"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">7</a></sup><sup id="fnref:9:6"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 简化自 include/linux/mm_types.h（约 1100 行起）</span>
<span class="k">struct</span> <span class="n">mm_struct</span> <span class="p">{</span>
    <span class="c1">// ...</span>
    <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">start_code</span><span class="p">,</span> <span class="n">end_code</span><span class="p">,</span> <span class="n">start_data</span><span class="p">,</span> <span class="n">end_data</span><span class="p">;</span>
    <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">start_brk</span><span class="p">,</span> <span class="n">brk</span><span class="p">,</span> <span class="n">start_stack</span><span class="p">;</span>   <span class="cm">/* 堆起止、栈底 */</span>
    <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">arg_start</span><span class="p">,</span> <span class="n">arg_end</span><span class="p">,</span> <span class="n">env_start</span><span class="p">,</span> <span class="n">env_end</span><span class="p">;</span>
    <span class="c1">// ...</span>
<span class="p">};</span>
</code></pre></div></div>

<p><strong>2. Buddy：zone 与 free_area</strong>（<code class="language-plaintext highlighter-rouge">include/linux/mmzone.h</code>、<code class="language-plaintext highlighter-rouge">mm/page_alloc.c</code>）</p>

<p>每 zone 有 <code class="language-plaintext highlighter-rouge">free_area[NR_PAGE_ORDERS]</code>，按 2^order 页块管理；分配入口为 <code class="language-plaintext highlighter-rouge">__alloc_pages()</code><sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 简化自 include/linux/mmzone.h（约 133 行）</span>
<span class="k">struct</span> <span class="n">free_area</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">list_head</span> <span class="n">free_list</span><span class="p">[</span><span class="n">MIGRATE_TYPES</span><span class="p">];</span>
    <span class="kt">unsigned</span> <span class="kt">long</span>    <span class="n">nr_free</span><span class="p">;</span>
<span class="p">};</span>

<span class="c1">// 每个 zone 含（同文件约 980 行）：</span>
<span class="c1">// struct free_area free_area[NR_PAGE_ORDERS];</span>
</code></pre></div></div>

<p><strong>3. sys_brk 系统调用</strong>（<code class="language-plaintext highlighter-rouge">mm/mmap.c</code>）</p>

<p>用户态 <code class="language-plaintext highlighter-rouge">brk</code>/<code class="language-plaintext highlighter-rouge">sbrk</code> 的内核入口；通过 <code class="language-plaintext highlighter-rouge">mm-&gt;brk</code>、<code class="language-plaintext highlighter-rouge">mm-&gt;start_brk</code> 与 VMA 扩展堆。<strong>默认只调 <code class="language-plaintext highlighter-rouge">do_brk_flags()</code> 扩展 VMA，不分配物理页</strong>；仅当 <code class="language-plaintext highlighter-rouge">mm-&gt;def_flags &amp; VM_LOCKED</code>（如进程曾 <code class="language-plaintext highlighter-rouge">mlockall</code>）时才在返回前调用 <code class="language-plaintext highlighter-rouge">mm_populate(oldbrk, newbrk - oldbrk)</code> 预填页，此时用户首次访问新区间不会触发缺页<sup id="fnref:8:2"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 简化自 mm/mmap.c（约 115 行起）</span>
<span class="n">SYSCALL_DEFINE1</span><span class="p">(</span><span class="n">brk</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span><span class="p">,</span> <span class="n">brk</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">struct</span> <span class="n">mm_struct</span> <span class="o">*</span><span class="n">mm</span> <span class="o">=</span> <span class="n">current</span><span class="o">-&gt;</span><span class="n">mm</span><span class="p">;</span>
    <span class="n">bool</span> <span class="n">populate</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
    <span class="c1">// ...</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">do_brk_flags</span><span class="p">(</span><span class="o">&amp;</span><span class="n">vmi</span><span class="p">,</span> <span class="n">brkvma</span><span class="p">,</span> <span class="n">oldbrk</span><span class="p">,</span> <span class="n">newbrk</span> <span class="o">-</span> <span class="n">oldbrk</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span>
        <span class="k">goto</span> <span class="n">out</span><span class="p">;</span>
    <span class="n">mm</span><span class="o">-&gt;</span><span class="n">brk</span> <span class="o">=</span> <span class="n">brk</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">mm</span><span class="o">-&gt;</span><span class="n">def_flags</span> <span class="o">&amp;</span> <span class="n">VM_LOCKED</span><span class="p">)</span>
        <span class="n">populate</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
<span class="nl">success:</span>
    <span class="n">mmap_write_unlock</span><span class="p">(</span><span class="n">mm</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">populate</span><span class="p">)</span>
        <span class="n">mm_populate</span><span class="p">(</span><span class="n">oldbrk</span><span class="p">,</span> <span class="n">newbrk</span> <span class="o">-</span> <span class="n">oldbrk</span><span class="p">);</span>   <span class="cm">/* 仅 VM_LOCKED 时预填页 */</span>
    <span class="k">return</span> <span class="n">brk</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>4. do_brk_flags 只建 VMA</strong>（<code class="language-plaintext highlighter-rouge">mm/vma.c</code>）</p>

<p>扩展堆时仅创建/扩展匿名 VMA，不分配物理页；物理页在首次访问时由缺页处理分配。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 简化自 mm/vma.c（约 2714 行）— do_brk_flags 仅做 VMA 分配与合并，无 alloc_pages/mm_populate</span>
<span class="kt">int</span> <span class="nf">do_brk_flags</span><span class="p">(</span><span class="k">struct</span> <span class="n">vma_iterator</span> <span class="o">*</span><span class="n">vmi</span><span class="p">,</span> <span class="k">struct</span> <span class="n">vm_area_struct</span> <span class="o">*</span><span class="n">vma</span><span class="p">,</span>
                 <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">addr</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">len</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">flags</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// ... may_expand_vm, security_vm_enough_memory_mm ...</span>
    <span class="n">vma</span> <span class="o">=</span> <span class="n">vm_area_alloc</span><span class="p">(</span><span class="n">mm</span><span class="p">);</span>   <span class="cm">/* 只分配 VMA 结构 */</span>
    <span class="n">vma_set_anonymous</span><span class="p">(</span><span class="n">vma</span><span class="p">);</span>
    <span class="n">vma_set_range</span><span class="p">(</span><span class="n">vma</span><span class="p">,</span> <span class="n">addr</span><span class="p">,</span> <span class="n">addr</span> <span class="o">+</span> <span class="n">len</span><span class="p">,</span> <span class="p">...);</span>
    <span class="n">vm_flags_init</span><span class="p">(</span><span class="n">vma</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span>
    <span class="c1">// ... vma_iter_store_gfp, vma_link ... 无 mm_populate</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>5. Slab 分配接口</strong>（<code class="language-plaintext highlighter-rouge">mm/slub.c</code>）</p>

<p>当前默认 Slab 实现；<code class="language-plaintext highlighter-rouge">kmem_cache_alloc</code> 从指定 cache 取对象（如 <code class="language-plaintext highlighter-rouge">task_struct</code>、<code class="language-plaintext highlighter-rouge">vm_area_struct</code> 等）<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">10</a></sup>。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 简化自 mm/slub.c（约 4202 行）</span>
<span class="kt">void</span> <span class="o">*</span><span class="nf">kmem_cache_alloc_noprof</span><span class="p">(</span><span class="k">struct</span> <span class="n">kmem_cache</span> <span class="o">*</span><span class="n">s</span><span class="p">,</span> <span class="n">gfp_t</span> <span class="n">gfpflags</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">void</span> <span class="o">*</span><span class="n">ret</span> <span class="o">=</span> <span class="n">slab_alloc_node</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">gfpflags</span><span class="p">,</span> <span class="n">NUMA_NO_NODE</span><span class="p">,</span> <span class="n">_RET_IP_</span><span class="p">,</span>
                                <span class="n">s</span><span class="o">-&gt;</span><span class="n">object_size</span><span class="p">);</span>
    <span class="n">trace_kmem_cache_alloc</span><span class="p">(</span><span class="n">_RET_IP_</span><span class="p">,</span> <span class="n">ret</span><span class="p">,</span> <span class="n">s</span><span class="p">,</span> <span class="n">gfpflags</span><span class="p">,</span> <span class="n">NUMA_NO_NODE</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">EXPORT_SYMBOL</span><span class="p">(</span><span class="n">kmem_cache_alloc_noprof</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>6. mmap 与 MAP_POPULATE</strong>（<code class="language-plaintext highlighter-rouge">mm/mmap.c</code>）</p>

<p>默认 <code class="language-plaintext highlighter-rouge">mmap(MAP_ANONYMOUS)</code> 只建立 VMA，不预填页；若带 <strong><code class="language-plaintext highlighter-rouge">MAP_POPULATE</code></strong>，<code class="language-plaintext highlighter-rouge">do_mmap</code> 成功后会设 <code class="language-plaintext highlighter-rouge">*populate = len</code>（约 562–565 行：<code class="language-plaintext highlighter-rouge">(flags &amp; (MAP_POPULATE | MAP_NONBLOCK)) == MAP_POPULATE</code>），返回前在 <code class="language-plaintext highlighter-rouge">vm_mmap_pgoff</code> 里调 <code class="language-plaintext highlighter-rouge">mm_populate(ret, populate)</code>，在内核内把页 fault in，用户首次访问不再触发缺页。</p>

<p>本文引用已用 pdftotext 与本地 kernel 源码校对。</p>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p><a href="https://www.sra.uni-hannover.de/Lehre/SS25/V_BSB/doc/x86-abi.html">System V ABI - AMD64 - Register and Stack Layout</a> - x86-64 调用约定与栈布局（RSP、red zone、16 字节对齐） <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p><a href="https://www.intel.com/content/www/us/en/content-details/868146/intel-64-and-ia-32-architectures-software-developer-s-manual-volume-3a-system-programming-guide-part-1.html">Intel® 64 and IA-32 Architectures Software Developer’s Manual, Vol. 3A</a> - 第 6 章 Interrupt and Exception Handling、§6.14.2/§6.15 #PF <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:2:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:3">
      <p><a href="https://man7.org/linux/man-pages/man2/mmap.2.html">mmap(2) - Linux manual page</a> - mmap 系统调用；<a href="https://man7.org/linux/man-pages/man2/brk.2.html">brk(2)</a> - 堆顶与 sbrk/brk <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p><a href="https://stackoverflow.com/questions/34042915/what-is-the-purpose-of-map-anonymous-flag-in-mmap-system-call">What is the purpose of MAP_ANONYMOUS in mmap?</a> - 匿名映射与零填充语义；匿名区采用 demand paging，读时映射零页或分配并清零，写时 COW/分配 <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:9">
      <p>Mel Gorman, <strong>Understanding the Linux® Virtual Memory Manager</strong>。<a href="https://www.kernel.org/doc/gorman/pdf/understand.pdf">kernel.org PDF</a>、<a href="https://www.kernel.org/doc/gorman/html/understand/">HTML 目录</a>。Ch4/6/8 见扩展阅读 <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:9:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:9:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:9:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:9:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a> <a href="#fnref:9:5" class="reversefootnote" role="doc-backlink">&#8617;<sup>6</sup></a> <a href="#fnref:9:6" class="reversefootnote" role="doc-backlink">&#8617;<sup>7</sup></a></p>
    </li>
    <li id="fn:5">
      <p><a href="https://www.kernel.org/doc/html/latest/mm/slab.html">Memory Management - The Linux Kernel documentation</a> - Slab 分配器；<a href="https://www.kernel.org/doc/gorman/html/understand/understand025.html">Understanding the Linux Virtual Memory Manager - Slab 附录</a> - Buddy 与 Slab 概述 <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:5:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:8">
      <p>Linux 内核 <strong>mm/mmap.c</strong>（<code class="language-plaintext highlighter-rouge">SYSCALL_DEFINE1(brk,...)</code>、<code class="language-plaintext highlighter-rouge">mm-&gt;brk</code>/<code class="language-plaintext highlighter-rouge">mm-&gt;start_brk</code>、<code class="language-plaintext highlighter-rouge">expand_stack_locked</code>）、<strong>fs/exec.c</strong>（<code class="language-plaintext highlighter-rouge">setup_arg_pages</code> 创建栈 VMA）、<strong>mm/memory.c</strong>（<code class="language-plaintext highlighter-rouge">do_anonymous_page</code> 缺页处理）。<a href="https://elixir.bootlin.com/linux/latest/source/mm/mmap.c">Bootlin - mmap.c</a>、<a href="https://elixir.bootlin.com/linux/latest/source/fs/exec.c">exec.c</a>、<a href="https://elixir.bootlin.com/linux/latest/source/mm/memory.c">memory.c</a> <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:8:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:8:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:10">
      <p>本博客 <a href="https://weinan.io/2026/03/01/why-language-speed-is-misleading.html">为什么「语言速度」是伪命题：I/O、并发、内存与内核</a> - 系统调用成本、内存池与 I/O 对实际性能的影响 <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:10:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:7">
      <p>Linux 内核 <strong>mm/page_alloc.c</strong>（<code class="language-plaintext highlighter-rouge">__alloc_pages</code>、<code class="language-plaintext highlighter-rouge">zone-&gt;free_area</code>）、<strong>include/linux/mmzone.h</strong>（<code class="language-plaintext highlighter-rouge">struct free_area</code>）。<a href="https://elixir.bootlin.com/linux/latest/source/mm/page_alloc.c">Bootlin - page_alloc.c</a> <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6">
      <p>Linux 内核 <strong>mm/slub.c</strong>（<code class="language-plaintext highlighter-rouge">kmem_cache_alloc</code>）、<strong>mm/slab.c</strong>、<strong>include/linux/sched.h</strong>。<a href="https://elixir.bootlin.com/linux/latest/source/mm/slub.c">Bootlin - slub.c</a> <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="rust" /><summary type="html"><![CDATA[从分配路径、局部性与系统调用成本解释栈为何通常比堆更快，并给出可验证的对比视角。]]></summary></entry><entry><title type="html">为什么「语言速度」是伪命题：I/O、并发、内存与内核</title><link href="https://weinan.tech/2026/03/01/why-language-speed-is-misleading.html" rel="alternate" type="text/html" title="为什么「语言速度」是伪命题：I/O、并发、内存与内核" /><published>2026-03-01T00:00:00+08:00</published><updated>2026-03-01T00:00:00+08:00</updated><id>https://weinan.tech/2026/03/01/why-language-speed-is-misleading</id><content type="html" xml:base="https://weinan.tech/2026/03/01/why-language-speed-is-misleading.html"><![CDATA[<p>在现代环境中，单纯比较语言的“执行速度”远远不够。一方面，<strong>现代 CPU 执行指令已经极快</strong>，各语言在“单纯执行同一条指令”的层面差异很小（纳秒级），难以成为系统瓶颈。另一方面，就像在拥挤的城市街道上比较两辆赛车的极速，意义有限——真正决定系统表现的是 <strong>I/O 如何被处理、并发如何利用多核、内存如何与内核交互</strong>，以及运行时与生态的取舍。本文从技术内因（I/O、并发、内存与系统调用）、运行时成本（VM 与 AOT）以及非技术因素三方面梳理，并辅以 Linux 内核与用户态代码示例。</p>

<h2 id="1-为什么语言速度是伪命题技术内因">1. 为什么「语言速度」是伪命题？（技术内因）</h2>

<h3 id="11-io-是天花板">1.1 I/O 是天花板</h3>

<p>绝大多数时间 CPU 在等 I/O：网络往返或磁盘读写是毫秒级，而一条加法指令是纳秒级，语言层面的“谁更快”会被 I/O 等待完全淹没。<strong>真正的差异在于：语言/框架如何做 I/O</strong>——阻塞还是非阻塞？是否用好操作系统提供的异步接口（如 <strong>epoll</strong>、<strong>io_uring</strong>）？</p>

<p><strong>epoll</strong>：一次系统调用可监听大量 fd，就绪时再处理，避免“每个连接问一次”的轮询。Linux 内核实现见 <strong><code class="language-plaintext highlighter-rouge">fs/eventpoll.c</code></strong>，入口为 <code class="language-plaintext highlighter-rouge">epoll_create1</code>、<code class="language-plaintext highlighter-rouge">epoll_ctl</code>、<code class="language-plaintext highlighter-rouge">epoll_wait</code> 等系统调用<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup><sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 用户态：epoll 一次 wait 可返回多个就绪 fd，减少 syscall 次数</span>
<span class="kt">int</span> <span class="n">epfd</span> <span class="o">=</span> <span class="n">epoll_create1</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
<span class="k">struct</span> <span class="n">epoll_event</span> <span class="n">ev</span> <span class="o">=</span> <span class="p">{</span> <span class="p">.</span><span class="n">events</span> <span class="o">=</span> <span class="n">EPOLLIN</span><span class="p">,</span> <span class="p">.</span><span class="n">data</span><span class="p">.</span><span class="n">fd</span> <span class="o">=</span> <span class="n">sockfd</span> <span class="p">};</span>
<span class="n">epoll_ctl</span><span class="p">(</span><span class="n">epfd</span><span class="p">,</span> <span class="n">EPOLL_CTL_ADD</span><span class="p">,</span> <span class="n">sockfd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ev</span><span class="p">);</span>

<span class="cp">#define MAX_EVENTS 64
</span><span class="k">struct</span> <span class="n">epoll_event</span> <span class="n">events</span><span class="p">[</span><span class="n">MAX_EVENTS</span><span class="p">];</span>
<span class="k">for</span> <span class="p">(;;)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">n</span> <span class="o">=</span> <span class="n">epoll_wait</span><span class="p">(</span><span class="n">epfd</span><span class="p">,</span> <span class="n">events</span><span class="p">,</span> <span class="n">MAX_EVENTS</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span>  <span class="cm">/* 一次 syscall，多 fd */</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">n</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
        <span class="n">handle</span><span class="p">(</span><span class="n">events</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">data</span><span class="p">.</span><span class="n">fd</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>io_uring</strong>：更现代的异步 I/O 接口，提交与完成通过共享 ring buffer 与内核交互，进一步减少系统调用与拷贝。内核实现见 <strong><code class="language-plaintext highlighter-rouge">io_uring/io_uring.c</code></strong>，如 <code class="language-plaintext highlighter-rouge">SYSCALL_DEFINE2(io_uring_setup, ...)</code><sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">3</a></sup><sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>。</p>

<h3 id="12-并发模型与多核利用">1.2 并发模型与多核利用</h3>

<p>多核时代，<strong>并发模型</strong>决定能多“轻松”地压榨多核与掩盖 I/O 等待：</p>

<ul>
  <li><strong>Go</strong>：<strong>Goroutine</strong> 是极轻量的并发单位（栈起小、调度在用户态），便于写出高并发程序，从而更好利用多核并应对 I/O。</li>
  <li><strong>Java</strong>：<strong>虚拟线程</strong>（Project Loom）意在解决“每请求一线程”带来的内存与上下文切换成本。</li>
</ul>

<p>差别不在“单线程谁更快”，而在于<strong>能否用低成本抽象把并发写出来</strong>。</p>

<h3 id="13-内存管理与内核的博弈">1.3 内存管理与内核的博弈</h3>

<p>语言如何从内核要内存、何时释放，对延迟和常驻内存影响很大：</p>

<ul>
  <li><strong>有 GC 的语言</strong>（Java、Go）：向内核申请大块堆，自行管理。优点是开发效率高，缺点包括：<strong>Stop-The-World（STW）</strong>——GC 时暂停所有业务线程，导致延迟尖刺，对延迟敏感场景（如游戏、实时系统）是实打实的问题<sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>；回收不及时或长期不把内存还给 OS 会导致常驻内存偏高，与内核的交互也变得不可预测。</li>
  <li><strong>无 GC 的语言</strong>（Rust、C++）：可精细控制何时释放回 OS。例如 glibc 下可用 <strong><code class="language-plaintext highlighter-rouge">malloc_trim(0)</code></strong> 把空闲页归还内核，降低进程 RSS；Rust 的所有权在编译期约束生命周期，减少运行时开销<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>。</li>
</ul>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 释放堆上未用内存回内核，降低 RSS（glibc）</span>
<span class="cp">#include</span> <span class="cpf">&lt;malloc.h&gt;</span><span class="cp">
</span><span class="kt">void</span> <span class="nf">release_unused_heap</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">malloc_trim</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>   <span class="cm">/* 将 free list 中的空闲页归还内核 */</span>
<span class="p">}</span>
</code></pre></div></div>

<p>内核侧：用户态堆扩展通过 <strong><code class="language-plaintext highlighter-rouge">brk</code></strong>/ <strong><code class="language-plaintext highlighter-rouge">mmap</code></strong> 与 VMA 管理，物理页按需分配（缺页时再给）。本博客在<a href="https://weinan.io/2026/03/01/stack-vs-heap-why-stack-faster.html">《栈为什么比堆快》</a>中已有梳理<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>。</p>

<h3 id="14-用户态与内核态的壁垒">1.4 用户态与内核态的壁垒</h3>

<p>每次<strong>系统调用</strong>都是一次模式切换，成本远高于用户态几条指令。因此：</p>

<ul>
  <li><strong>内存池</strong>：在用户态维护一块已申请的内存，反复复用，减少频繁 <code class="language-plaintext highlighter-rouge">brk</code>/<code class="language-plaintext highlighter-rouge">mmap</code>。这本质上是在减少「从内核到用户态」的申请次数，与本博客<a href="https://weinan.io/2026/03/01/stack-vs-heap-why-stack-faster.html">《栈为什么比堆快》</a>里说的「批发-零售」链条一致：少向内核要、多在用户态复用，摊薄单次分配成本<sup id="fnref:4:1"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>。</li>
  <li><strong>批量 I/O</strong>：如 epoll 一次 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 返回多个就绪 fd；io_uring 一次 submit 可提交多个 I/O。</li>
</ul>

<p>语言“跑得快”若伴随大量 syscall，实际表现可能反而不如“跑得慢一点但少进内核”的实现。</p>

<h3 id="15-锁的误用与性能">1.5 锁的误用与性能</h3>

<p><strong>错误地使用锁</strong>（粗粒度锁、持锁做慢操作、锁竞争）同样是导致代码性能差的核心原因，与语言本身关系不大。一把大锁包住整段逻辑会把多核压成“串行执行”；在持锁期间做 I/O 或复杂计算会极大拉长其他线程的等待时间，造成延迟尖刺与吞吐下降。内核与用户态都依赖<strong>细粒度锁</strong>（只锁最小临界区）、<strong>缩短持锁时间</strong>（持锁内不做 I/O）、以及合理选择锁类型（自旋与睡眠的取舍）来降低竞争<sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 反例：一把大锁包住查找 + 处理，持锁期间可能做 I/O，多线程被串行化</span>
<span class="n">pthread_mutex_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">global_lock</span><span class="p">);</span>
<span class="n">item</span> <span class="o">=</span> <span class="n">lookup</span><span class="p">(</span><span class="n">key</span><span class="p">);</span>           <span class="cm">/* 临界区内做查找 */</span>
<span class="n">process</span><span class="p">(</span><span class="n">item</span><span class="p">);</span>                <span class="cm">/* 若 process() 含网络/磁盘 I/O，其他线程长时间阻塞 */</span>
<span class="n">pthread_mutex_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">global_lock</span><span class="p">);</span>

<span class="c1">// 正例：细粒度锁，持锁只做最小临界区（查表 + 取引用），慢操作在锁外</span>
<span class="n">pthread_mutex_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">bucket_lock</span><span class="p">[</span><span class="n">key</span> <span class="o">%</span> <span class="n">NBUCKET</span><span class="p">]);</span>
<span class="n">item</span> <span class="o">=</span> <span class="n">lookup_in_bucket</span><span class="p">(</span><span class="n">key</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="n">ref_inc</span><span class="p">(</span><span class="n">item</span><span class="p">);</span>
<span class="n">pthread_mutex_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">bucket_lock</span><span class="p">[</span><span class="n">key</span> <span class="o">%</span> <span class="n">NBUCKET</span><span class="p">]);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="n">process</span><span class="p">(</span><span class="n">item</span><span class="p">);</span>      <span class="cm">/* I/O 与重逻辑在锁外，不阻塞其他桶 */</span>
</code></pre></div></div>

<p>可参见：Linux 内核 <a href="https://docs.kernel.org/locking/mutex-design.html">Generic Mutex Subsystem</a>（mutex 设计、自旋与睡眠的取舍）、<a href="https://lwn.net/Articles/314512/">LWN — mutex: implement adaptive spinning</a>（竞争下的自适应行为），以及 <a href="https://www.intel.com/content/www/us/en/docs/advisor/user-guide/2025-0/reduce-lock-contention-001.html">Intel Advisor — Reduce Lock Contention</a>（用户态锁竞争分析与优化思路）<sup id="fnref:9:1"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>。用户态锁的阻塞与唤醒如何依赖内核（futex），见本博客<a href="https://weinan.io/2026/03/02/userspace-locks-and-kernel-futex.html">《用户态锁与内核：谁在管理「等待」与 futex》</a><sup id="fnref:10"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>。</p>

<hr />

<h2 id="2-运行时的隐藏成本vm-与-aot">2. 运行时的“隐藏成本”：VM 与 AOT</h2>

<ul>
  <li><strong>有 VM 的语言</strong>（Java、C#、Erlang）：带来跨平台和 JIT 等优化，但冷启动慢、VM 自身占内存，在 Serverless 或短生命周期任务中可能成为瓶颈。</li>
  <li><strong>AOT 编译、无传统 VM</strong>（Go、Rust、C++）：直接生成二进制，启动快、内存占用小。Go 的运行时（GC、调度）是链接进二进制的一部分，而非独立 VM。</li>
</ul>

<p>因此“谁更快”还要看<strong>启动与常驻成本</strong>是否在你的场景里被放大。</p>

<p><strong>语言有适用场景</strong>，某些场景下某类语言根本不可用。例如<strong>带 VM 的语言</strong>（Java、C# 等）无法用于<strong>内核开发</strong>：内核是跑在裸机上的第一层软件，没有“操作系统”为其提供进程、虚拟内存或系统调用；VM 依赖的运行时、GC、线程调度等都假设已有内核，内核自身不能依赖这些。因此内核必须用 C、Rust（no_std）等无传统 VM、可直接控制内存与硬件的语言。反之，内核、嵌入式、实时系统等会排除 VM 语言；企业后端、CRUD、大数据等则常首选 VM 语言以换取生态与开发效率。本博客<a href="https://weinan.io/2026/02/26/kernel-c-cpp-rust-runtime-stdlib.html">《内核开发中的语言选择：C、C++ 与 Rust》</a>对内核场景下各语言的约束有专门讨论<sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">10</a></sup>。</p>

<hr />

<h2 id="3-非技术因素的一票否决权">3. 非技术因素的“一票否决权”</h2>

<p>在工程选型中，非技术因素往往权重更高：</p>

<ul>
  <li><strong>市场与招聘</strong>：企业级后端仍以 Java/C# 为主流，Rust 等虽优但人力与梯队成本高。</li>
  <li><strong>生态与投资</strong>：大厂与社区投入决定库的成熟度；“开箱即用”的组件是否覆盖你的业务，比单语言性能更关键。</li>
  <li><strong>历史债务</strong>：很多系统沿用 Java/PHP 等，只因存量代码如此。除非有颠覆性收益，否则“稳定可用”常优于“换语言重构”。</li>
</ul>

<hr />

<h2 id="总结">总结</h2>

<p>选语言不是在选“谁跑得快”，而是在选<strong>谁的运行时哲学和生态，最匹配你的业务场景和团队能力</strong>。</p>

<ul>
  <li><strong>技术收益</strong>：在 I/O 密集或 CPU 密集场景下，能否通过并发模型和内存控制，把硬件与内核的潜力发挥出来。</li>
  <li><strong>业务成本</strong>：招聘难度、开发效率、生态成熟度与长期维护的可控性。</li>
</ul>

<p><strong>语言速度只是众多维度之一；I/O、并发、内存与内核的交互方式，以及 VM/AOT 与生态，往往更能决定实际表现与可维护性。</strong> 反过来看：<strong>一门语言在某个领域取得成功，一定是因为它解决了该领域的实际需求</strong>（性能、生态、开发效率、团队能力等），而不是技术品味或“谁更优雅”的问题。</p>

<hr />

<h2 id="扩展阅读内核与接口">扩展阅读（内核与接口）</h2>

<ul>
  <li><strong>epoll</strong>：Linux 内核 <strong><code class="language-plaintext highlighter-rouge">fs/eventpoll.c</code></strong>，<code class="language-plaintext highlighter-rouge">epoll_create1</code>、<code class="language-plaintext highlighter-rouge">epoll_ctl</code>、<code class="language-plaintext highlighter-rouge">epoll_wait</code> 等<sup id="fnref:1:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>。一次 <code class="language-plaintext highlighter-rouge">epoll_wait</code> 可返回多个就绪 fd，减少系统调用次数。</li>
  <li><strong>io_uring</strong>：<strong><code class="language-plaintext highlighter-rouge">io_uring/io_uring.c</code></strong>，<code class="language-plaintext highlighter-rouge">io_uring_setup</code>、提交与完成队列；适合高 IOPS、低 syscall 场景<sup id="fnref:2:1"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>。</li>
  <li><strong>用户态堆与内核</strong>：<code class="language-plaintext highlighter-rouge">brk</code>/<code class="language-plaintext highlighter-rouge">mmap</code>、VMA、缺页与零页见本博客<a href="https://weinan.io/2026/03/01/stack-vs-heap-why-stack-faster.html">《栈为什么比堆快》</a><sup id="fnref:4:2"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>。内核 <code class="language-plaintext highlighter-rouge">mm/mmap.c</code>（<code class="language-plaintext highlighter-rouge">sys_brk</code>）、<code class="language-plaintext highlighter-rouge">mm/vma.c</code>（<code class="language-plaintext highlighter-rouge">do_brk_flags</code>）。</li>
  <li><strong>锁与性能</strong>：细粒度锁、持锁时间最小化、自旋与睡眠取舍见内核 <a href="https://docs.kernel.org/locking/mutex-design.html">mutex-design</a>、LWN mutex 自适应自旋<sup id="fnref:9:2"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>，以及 Intel Advisor 锁竞争分析。用户态锁如何依赖内核（futex）见本博客<a href="https://weinan.io/2026/03/02/userspace-locks-and-kernel-futex.html">《用户态锁与内核》</a><sup id="fnref:10:1"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>。</li>
</ul>

<hr />

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>Linux 内核 <strong>fs/eventpoll.c</strong>：epoll 实现，<code class="language-plaintext highlighter-rouge">SYSCALL_DEFINE1(epoll_create1,...)</code>、<code class="language-plaintext highlighter-rouge">epoll_ctl</code>、<code class="language-plaintext highlighter-rouge">epoll_wait</code> 等。<a href="https://elixir.bootlin.com/linux/latest/source/fs/eventpoll.c">Bootlin - eventpoll.c</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:1:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:5">
      <p><a href="https://man7.org/linux/man-pages/man7/epoll.7.html">epoll(7)</a> - Linux 手册：epoll 概述与 API <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>Linux 内核 <strong>io_uring/io_uring.c</strong>：io_uring 实现，<code class="language-plaintext highlighter-rouge">SYSCALL_DEFINE2(io_uring_setup,...)</code> 等。<a href="https://elixir.bootlin.com/linux/latest/source/io_uring/io_uring.c">Bootlin - io_uring.c</a>、<a href="https://kernel.dk/io_uring.pdf">io_uring 文档</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:6">
      <p><a href="https://kernel.dk/io_uring.pdf">Efficient IO with io_uring</a> - Jens Axboe，io_uring 设计说明（PDF） <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8">
      <p><strong>Stop-The-World（STW）</strong>：GC 暂停所有应用线程以独占堆访问，导致延迟尖刺。<a href="https://docs.oracle.com/en/java/javase/21/gctuning/introduction-garbage-collection-tuning.html">Oracle Java GC Tuning - Introduction</a> 介绍各 GC 与停顿；<a href="https://go.dev/doc/gc-guide">A Guide to the Go Garbage Collector</a> 说明 Go 的并发 GC 与 STW 阶段 <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p><a href="https://man7.org/linux/man-pages/man3/malloc_trim.3.html">malloc_trim(3)</a> - 将 free 列表中的空闲页归还内核 <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p>本博客 <a href="https://weinan.io/2026/03/01/stack-vs-heap-why-stack-faster.html">栈为什么比堆快：从分配方式到「批发-零售」链条</a> - brk/mmap、VMA、缺页与零页 <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:4:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:4:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:9">
      <p><strong>锁与性能</strong>：粗粒度锁与持锁做 I/O 会串行化多线程并拉高延迟。<a href="https://docs.kernel.org/locking/mutex-design.html">Generic Mutex Subsystem — The Linux Kernel documentation</a> 介绍内核 mutex 设计与自旋/睡眠取舍；<a href="https://lwn.net/Articles/314512/">LWN — mutex: implement adaptive spinning</a> 讨论竞争下的自适应自旋；<a href="https://www.intel.com/content/www/us/en/docs/advisor/user-guide/2025-0/reduce-lock-contention-001.html">Intel Advisor — Reduce Lock Contention</a> 提供用户态锁竞争分析与优化思路 <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:9:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:9:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:10">
      <p>本博客 <a href="https://weinan.io/2026/03/02/userspace-locks-and-kernel-futex.html">用户态锁与内核：谁在管理「等待」与 futex</a> - futex 无竞争 fast path、有竞争时进内核阻塞/唤醒，及内核代码说明 <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:10:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:7">
      <p>本博客 <a href="https://weinan.io/2026/02/26/kernel-c-cpp-rust-runtime-stdlib.html">内核开发中的语言选择：C、C++ 与 Rust 的运行时与标准库</a> - 内核为何不能用 VM、C++/Rust 的约束与取舍 <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="rust" /><category term="linux-kernel" /><summary type="html"><![CDATA[说明「语言速度」为何常是伪命题，从 I/O、并发、内存布局与内核交互拆解真实性能瓶颈。]]></summary></entry><entry><title type="html">内核开发中的语言选择：C、C++ 与 Rust 的运行时与标准库</title><link href="https://weinan.tech/2026/02/26/kernel-c-cpp-rust-runtime-stdlib.html" rel="alternate" type="text/html" title="内核开发中的语言选择：C、C++ 与 Rust 的运行时与标准库" /><published>2026-02-26T00:00:00+08:00</published><updated>2026-02-26T00:00:00+08:00</updated><id>https://weinan.tech/2026/02/26/kernel-c-cpp-rust-runtime-stdlib</id><content type="html" xml:base="https://weinan.tech/2026/02/26/kernel-c-cpp-rust-runtime-stdlib.html"><![CDATA[<p>操作系统内核开发与应用程序开发的核心区别之一，在于运行时与内存管理模型的约束。本文从运行时大小、内存管理模型和标准库依赖三个方面，分析 C、C++、Rust 在内核开发中的差异。</p>

<h2 id="运行时大小问题">运行时大小问题</h2>

<h3 id="c-运行时">C 运行时</h3>
<p>C 的运行时几乎可以忽略不计：</p>
<ul>
  <li><strong>最小运行时</strong>：C 语言被设计为「接近硬件」，运行时仅提供最基本的启动代码（crt0）和库函数</li>
  <li><strong>可控性</strong>：内核开发者可以完全避免使用标准库，直接使用系统调用和硬件指令</li>
  <li><strong>典型例子</strong>：Linux 内核几乎完全用 C 编写，运行时开销极小<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">1</a></sup></li>
</ul>

<h3 id="c-运行时-1">C++ 运行时</h3>
<p>C++ 的运行时较大，原因是：</p>
<ul>
  <li><strong>异常处理</strong>：需要 unwind 表和 RTTI（运行时类型信息）</li>
  <li><strong>标准库</strong>：STL 容器、算法等需要大量初始化代码</li>
  <li><strong>构造函数</strong>：静态对象的构造需要运行时支持</li>
  <li><strong>内存管理</strong>：operator new/delete 的默认实现</li>
  <li><strong>例子</strong>：即使在嵌入式环境中，完整的 C++ 运行时可能增加数百 KB 到数 MB 的开销</li>
</ul>

<h3 id="rust-运行时">Rust 运行时</h3>
<p>Rust 介于两者之间：</p>
<ul>
  <li><strong>零成本抽象</strong>：大部分抽象在编译时展开，不增加运行时开销</li>
  <li><strong>最小运行时</strong>：只需要基本的 panic 处理、内存分配器（若使用）</li>
  <li><strong>no_std 模式</strong>：可以完全禁用标准库，只使用 core 库，运行时开销与 C 相当<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">2</a></sup><sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">3</a></sup></li>
  <li><strong>例子</strong>：Redox OS 内核完全用 Rust 编写，使用 no_std 模式<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">4</a></sup></li>
</ul>

<h2 id="内存管理的核心区别">内存管理的核心区别</h2>

<p>内存管理模型的差异是另一关键因素。</p>

<h3 id="c-的内存管理问题">C++ 的内存管理问题</h3>

<ol>
  <li><strong>构造函数和析构函数</strong>
    <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Device</span> <span class="p">{</span>
 <span class="n">Resource</span><span class="o">*</span> <span class="n">res</span><span class="p">;</span>
<span class="nl">public:</span>
 <span class="n">Device</span><span class="p">()</span> <span class="p">{</span> <span class="n">res</span> <span class="o">=</span> <span class="n">allocate_resource</span><span class="p">();</span> <span class="p">}</span>  <span class="c1">// 可能失败</span>
 <span class="o">~</span><span class="n">Device</span><span class="p">()</span> <span class="p">{</span> <span class="n">release_resource</span><span class="p">();</span> <span class="p">}</span>        <span class="c1">// 异常可能发生</span>
<span class="p">};</span>
</code></pre></div>    </div>
    <ul>
      <li>构造函数无法返回错误码（只能用异常）</li>
      <li>析构函数中不能抛出异常</li>
      <li>对象生命周期由编译器自动管理，但在内核中这往往是不可预测的</li>
    </ul>
  </li>
  <li><strong>异常处理</strong>
    <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">driver_function</span><span class="p">()</span> <span class="p">{</span>
 <span class="n">Device</span> <span class="n">d</span><span class="p">;</span>  <span class="c1">// 构造</span>
 <span class="c1">// 如果这里发生异常，d 的析构函数会自动调用</span>
 <span class="c1">// 但在内核中，这种隐式控制流是危险的</span>
<span class="p">}</span>
</code></pre></div>    </div>
    <ul>
      <li>异常展开需要复杂的栈回溯</li>
      <li>增加了二进制文件大小</li>
      <li>实时性无法保证</li>
    </ul>
  </li>
  <li><strong>RAII 的局限性与运行时依赖</strong></li>
</ol>

<p>RAII（Resource Acquisition Is Initialization）的核心是：资源在对象构造时获取，在对象析构时释放。这一机制在内核中受限，且其实现本身依赖运行时支持。</p>

<p><strong>为何 RAII 需要运行时支持：</strong></p>

<ul>
  <li><strong>构造与析构的自动调用</strong>：编译器需在正确位置插入构造/析构调用，对象生命周期的管理（何时创建、何时销毁）依赖运行时机制。例如：</li>
</ul>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">FileHandler</span> <span class="p">{</span>
    <span class="kt">FILE</span><span class="o">*</span> <span class="n">file</span><span class="p">;</span>
<span class="nl">public:</span>
    <span class="n">FileHandler</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">filename</span><span class="p">)</span> <span class="p">{</span> <span class="n">file</span> <span class="o">=</span> <span class="n">fopen</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="s">"r"</span><span class="p">);</span> <span class="p">}</span>
    <span class="o">~</span><span class="n">FileHandler</span><span class="p">()</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">file</span><span class="p">)</span> <span class="n">fclose</span><span class="p">(</span><span class="n">file</span><span class="p">);</span> <span class="p">}</span>
<span class="p">};</span>

<span class="kt">void</span> <span class="n">processFile</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">FileHandler</span> <span class="n">fh</span><span class="p">(</span><span class="s">"data.txt"</span><span class="p">);</span>  <span class="c1">// 构造时获取资源</span>
    <span class="c1">// 使用文件...</span>
<span class="p">}</span>  <span class="c1">// 离开作用域时析构被自动调用</span>
</code></pre></div></div>

<ul>
  <li><strong>栈展开（Stack Unwinding）</strong>：异常发生时，需要按与构造相反的顺序自动调用所有已构造局部对象的析构函数，并维护调用栈信息。内核通常禁用异常，因此无法依赖这套机制。</li>
</ul>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">function</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">FileHandler</span> <span class="n">fh1</span><span class="p">(</span><span class="s">"a.txt"</span><span class="p">);</span>
    <span class="n">FileHandler</span> <span class="n">fh2</span><span class="p">(</span><span class="s">"b.txt"</span><span class="p">);</span>
    <span class="k">throw</span> <span class="n">std</span><span class="o">::</span><span class="n">runtime_error</span><span class="p">(</span><span class="s">"error"</span><span class="p">);</span>  <span class="c1">// 异常时 fh2、fh1 的析构须被调用</span>
<span class="p">}</span>
</code></pre></div></div>

<ul>
  <li>
    <p><strong>动态内存与智能指针</strong>：<code class="language-plaintext highlighter-rouge">std::vector</code>、<code class="language-plaintext highlighter-rouge">std::unique_ptr</code>/<code class="language-plaintext highlighter-rouge">std::shared_ptr</code> 等依赖堆分配与引用计数，需要在运行时跟踪资源。</p>
  </li>
  <li>
    <p><strong>多态对象的析构</strong>：通过基类指针删除派生类对象时，必须通过虚函数表（vtable）在运行时找到正确的析构函数，同样依赖运行时类型信息。</p>
  </li>
</ul>

<p>若纯靠编译时实现，无法处理异常路径下的释放、多态析构和动态资源的引用计数等，因此 RAII 既是 C++ 的核心特性，又离不开运行时支持，这与内核需要的确定性、无异常、显式控制相冲突。</p>

<p><strong>运行时实现简述</strong>：局部对象的构造/析构由编译器在固定位置插入调用；全局或静态对象由启动代码遍历 <code class="language-plaintext highlighter-rouge">.init_array</code>（或 <code class="language-plaintext highlighter-rouge">.ctors</code>）在进程启动时调用构造，退出时按逆序调用析构。异常时的栈展开则依赖 <strong>unwinder</strong>：编译器为每个函数生成 unwind 元数据（如 DWARF 的 <code class="language-plaintext highlighter-rouge">.eh_frame</code>），描述栈帧与需析构的对象；异常抛出时，运行时库按栈回溯，调用每帧的 personality 函数，按表调用析构并查找 catch。多态析构通过对象的 vtable 在运行时查表得到正确析构函数。这些机制多在编译器运行时（如 libgcc、libstdc++ 的一部分）中实现，与「标准库 STL」不是同一层，但都属 C++ 运行时。</p>

<ul>
  <li>RAII 假设资源释放是确定性的、无错的</li>
  <li>内核中可能需要延迟释放、异步释放</li>
  <li>硬件资源的释放可能非常复杂</li>
</ul>

<ol>
  <li><strong>模板元编程</strong>
    <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span><span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">RingBuffer</span> <span class="p">{</span>
 <span class="n">T</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">256</span><span class="p">];</span>  <span class="c1">// 类型在编译时确定</span>
 <span class="c1">// 但在内核中，可能需要根据硬件配置动态选择类型</span>
<span class="p">};</span>
</code></pre></div>    </div>
    <ul>
      <li>过度依赖模板会导致代码膨胀</li>
      <li>难以处理动态硬件配置</li>
    </ul>
  </li>
</ol>

<h3 id="c-的内存管理优势">C 的内存管理优势</h3>

<ol>
  <li><strong>显式控制</strong>
    <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">device</span> <span class="o">*</span><span class="n">dev</span> <span class="o">=</span> <span class="n">kmalloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">dev</span><span class="p">),</span> <span class="n">GFP_KERNEL</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">dev</span><span class="p">)</span>
 <span class="k">return</span> <span class="o">-</span><span class="n">ENOMEM</span><span class="p">;</span>
<span class="n">dev</span><span class="o">-&gt;</span><span class="n">ops</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">device_ops</span><span class="p">;</span>
<span class="c1">// 所有操作都是显式的，没有隐藏的控制流</span>
</code></pre></div>    </div>
  </li>
  <li><strong>错误处理直接</strong>
    <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">init_device</span><span class="p">(</span><span class="k">struct</span> <span class="n">device</span> <span class="o">*</span><span class="n">dev</span><span class="p">)</span> <span class="p">{</span>
 <span class="kt">int</span> <span class="n">ret</span><span class="p">;</span>
 <span class="n">ret</span> <span class="o">=</span> <span class="n">init_resource_a</span><span class="p">(</span><span class="n">dev</span><span class="p">);</span>
 <span class="k">if</span> <span class="p">(</span><span class="n">ret</span><span class="p">)</span>
     <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
 <span class="n">ret</span> <span class="o">=</span> <span class="n">init_resource_b</span><span class="p">(</span><span class="n">dev</span><span class="p">);</span>
 <span class="k">if</span> <span class="p">(</span><span class="n">ret</span><span class="p">)</span> <span class="p">{</span>
     <span class="n">cleanup_resource_a</span><span class="p">(</span><span class="n">dev</span><span class="p">);</span>
     <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
 <span class="p">}</span>
 <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>    </div>
    <ul>
      <li>所有错误路径都清晰可见</li>
      <li>没有隐式的资源释放</li>
    </ul>
  </li>
  <li><strong>内存布局可预测</strong>
    <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">packet</span> <span class="p">{</span>
 <span class="kt">uint32_t</span> <span class="n">len</span><span class="p">;</span>
 <span class="kt">char</span> <span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>  <span class="c1">// 灵活数组成员</span>
<span class="p">};</span>  <span class="c1">// 内存布局完全由程序员控制</span>
</code></pre></div>    </div>
  </li>
</ol>

<h3 id="内核-c-的面向对象风格">内核 C 的面向对象风格</h3>

<p>内核虽然用 C 编写，但大量采用<strong>面向对象式</strong>的写法：用结构体承载「状态」，用函数指针表承载「行为」，多态通过查表调用实现，无需 C++ 的虚函数或异常<sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">5</a></sup><sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>。</p>

<p><strong>1. 函数指针表（类似 vtable）</strong></p>

<p>例如 VFS 层的 <code class="language-plaintext highlighter-rouge">struct file_operations</code>（<code class="language-plaintext highlighter-rouge">include/linux/fs.h</code>）：每个字段是一类操作，由具体驱动/文件系统填不同实现，通用代码通过 <code class="language-plaintext highlighter-rouge">file-&gt;f_op-&gt;read(...)</code> 等形式调用，实现多态。<code class="language-plaintext highlighter-rouge">file_operations</code> 与 inode 等结构的定义与用法可参考本博客<sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 简化自 linux/fs.h</span>
<span class="k">struct</span> <span class="n">file_operations</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">module</span> <span class="o">*</span><span class="n">owner</span><span class="p">;</span>
    <span class="kt">ssize_t</span> <span class="p">(</span><span class="o">*</span><span class="n">read</span><span class="p">)</span> <span class="p">(</span><span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="p">,</span> <span class="kt">char</span> <span class="n">__user</span> <span class="o">*</span><span class="p">,</span> <span class="kt">size_t</span><span class="p">,</span> <span class="n">loff_t</span> <span class="o">*</span><span class="p">);</span>
    <span class="kt">ssize_t</span> <span class="p">(</span><span class="o">*</span><span class="n">write</span><span class="p">)</span> <span class="p">(</span><span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="n">__user</span> <span class="o">*</span><span class="p">,</span> <span class="kt">size_t</span><span class="p">,</span> <span class="n">loff_t</span> <span class="o">*</span><span class="p">);</span>
    <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">open</span><span class="p">)</span> <span class="p">(</span><span class="k">struct</span> <span class="n">inode</span> <span class="o">*</span><span class="p">,</span> <span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="p">);</span>
    <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">release</span><span class="p">)</span> <span class="p">(</span><span class="k">struct</span> <span class="n">inode</span> <span class="o">*</span><span class="p">,</span> <span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="p">);</span>
    <span class="c1">// ...</span>
<span class="p">};</span>

<span class="c1">// 驱动侧：实现“类”并挂到 file 上</span>
<span class="k">static</span> <span class="k">struct</span> <span class="n">file_operations</span> <span class="n">my_fops</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">.</span><span class="n">owner</span> <span class="o">=</span> <span class="n">THIS_MODULE</span><span class="p">,</span>
    <span class="p">.</span><span class="n">read</span>  <span class="o">=</span> <span class="n">my_read</span><span class="p">,</span>
    <span class="p">.</span><span class="n">write</span> <span class="o">=</span> <span class="n">my_write</span><span class="p">,</span>
    <span class="p">.</span><span class="n">open</span>  <span class="o">=</span> <span class="n">my_open</span><span class="p">,</span>
    <span class="p">.</span><span class="n">release</span> <span class="o">=</span> <span class="n">my_release</span><span class="p">,</span>
<span class="p">};</span>
</code></pre></div></div>

<p>同类结构还有 <code class="language-plaintext highlighter-rouge">inode_operations</code>、<code class="language-plaintext highlighter-rouge">dentry_operations</code>、<code class="language-plaintext highlighter-rouge">super_operations</code>、各类 <code class="language-plaintext highlighter-rouge">*_ops</code> 等，内核中有大量这种「操作表」<sup id="fnref:4:1"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>。</p>

<p><strong>2. “继承”通过结构体嵌入</strong></p>

<p>子类型通过<strong>在结构体里嵌入父类型</strong>复用共同字段，并可用 <code class="language-plaintext highlighter-rouge">container_of</code> 从父指针反推子指针。例如设备模型里 <code class="language-plaintext highlighter-rouge">struct device</code> 内嵌 <code class="language-plaintext highlighter-rouge">struct kobject</code>，子设备再内嵌 <code class="language-plaintext highlighter-rouge">struct device</code>，形成层次与共同生命周期管理。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 概念上：子结构体包含“基类”</span>
<span class="k">struct</span> <span class="n">my_device</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">device</span> <span class="n">dev</span><span class="p">;</span>   <span class="c1">// 内嵌，相当于“继承” device 的字段</span>
    <span class="kt">int</span> <span class="n">my_private_data</span><span class="p">;</span>
<span class="p">};</span>

<span class="c1">// 从通用 device* 得到 my_device*</span>
<span class="k">struct</span> <span class="n">my_device</span> <span class="o">*</span><span class="n">mdev</span> <span class="o">=</span> <span class="n">container_of</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="k">struct</span> <span class="n">my_device</span><span class="p">,</span> <span class="n">dev</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>3. “方法”约定：首参为对象指针</strong></p>

<p>很多内核 API 的「方法」形态是：第一个参数为操作对象，例如 <code class="language-plaintext highlighter-rouge">int (*open)(struct inode *, struct file *)</code>。调用方持有 <code class="language-plaintext highlighter-rouge">struct file *</code>，通过 <code class="language-plaintext highlighter-rouge">f_op-&gt;open(inode, filp)</code> 调用，等价于「对 file 做 open」，与 OO 的 <code class="language-plaintext highlighter-rouge">obj-&gt;method(args)</code> 对应。</p>

<p>综上，内核 C 用「结构体 + 函数指针表 + 嵌入 + 显式首参」实现接口抽象和多态，无需 C++ 的运行时（异常、vtable 展开、构造/析构顺序），仍能保持清晰的层次与可扩展性。</p>

<h3 id="rust-的创新解决方案">Rust 的创新解决方案</h3>

<p>Rust 通过所有权系统和生命周期来平衡安全性和控制力：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">Device</span> <span class="p">{</span>
    <span class="n">resource</span><span class="p">:</span> <span class="n">Resource</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Device</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">new</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="k">Self</span><span class="p">,</span> <span class="n">Error</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">res</span> <span class="o">=</span> <span class="nn">Resource</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>  <span class="c1">// 显式错误处理</span>
        <span class="nf">Ok</span><span class="p">(</span><span class="n">Device</span> <span class="p">{</span> <span class="n">resource</span><span class="p">:</span> <span class="n">res</span> <span class="p">})</span>
    <span class="p">}</span>
<span class="p">}</span>  <span class="c1">// Drop trait 提供确定性析构，但比 C++ 更可控</span>

<span class="c1">// 所有权确保资源只有一个所有者</span>
<span class="k">fn</span> <span class="nf">use_device</span><span class="p">(</span><span class="n">dev</span><span class="p">:</span> <span class="n">Device</span><span class="p">)</span> <span class="p">{</span>  <span class="c1">// 获得所有权</span>
    <span class="c1">// 使用设备</span>
<span class="p">}</span>  <span class="c1">// 这里自动释放，但行为是确定的</span>
</code></pre></div></div>

<p>Rust 解决了 C++ 的几个关键问题：</p>
<ol>
  <li><strong>无异常</strong>：使用 Result 类型进行显式错误处理</li>
  <li><strong>所有权系统</strong>：资源释放是确定性的</li>
  <li><strong>零成本抽象</strong>：无运行时开销</li>
  <li><strong>内存安全</strong>：编译时检查，无 GC 开销</li>
</ol>

<p><strong>Rust 的错误处理（与 C++ 异常对比）</strong></p>

<p>Rust 没有异常，错误通过类型在类型系统中显式表达，调用方必须处理，适合内核等不能依赖 unwinder 的环境。</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">Result&lt;T, E&gt;</code></strong>：表示可能失败的操作，成功为 <code class="language-plaintext highlighter-rouge">Ok(t)</code>，失败为 <code class="language-plaintext highlighter-rouge">Err(e)</code>。<code class="language-plaintext highlighter-rouge">Option&lt;T&gt;</code> 表示可选值（<code class="language-plaintext highlighter-rouge">Some(t)</code> / <code class="language-plaintext highlighter-rouge">None</code>），二者均在 <code class="language-plaintext highlighter-rouge">core</code> 中，no_std 可用。</li>
  <li><strong>构造/初始化可返回错误</strong>：类似上面 <code class="language-plaintext highlighter-rouge">Device::new() -&gt; Result&lt;Self, Error&gt;</code>，失败时返回 <code class="language-plaintext highlighter-rouge">Err(...)</code>，无需两阶段 init。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">?</code> 操作符</strong>：在返回 <code class="language-plaintext highlighter-rouge">Result</code> 的函数内，<code class="language-plaintext highlighter-rouge">expr?</code> 表示若 <code class="language-plaintext highlighter-rouge">expr</code> 为 <code class="language-plaintext highlighter-rouge">Err(e)</code> 则当前函数立即返回 <code class="language-plaintext highlighter-rouge">Err(e)</code>，否则解出 <code class="language-plaintext highlighter-rouge">Ok</code> 中的值继续执行，错误沿调用栈「向上传播」但<strong>无栈展开</strong>，仅是一次返回。</li>
  <li><strong>调用方必须处理</strong>：用 <code class="language-plaintext highlighter-rouge">match</code>、<code class="language-plaintext highlighter-rouge">if let</code>、<code class="language-plaintext highlighter-rouge">.map_err()</code> 或继续 <code class="language-plaintext highlighter-rouge">?</code>，编译器要求覆盖 <code class="language-plaintext highlighter-rouge">Ok</code>/<code class="language-plaintext highlighter-rouge">Err</code> 分支，不会「忘记」检查错误。</li>
</ul>

<p>示例（no_std 下常见写法）：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 内核/裸机中常用 &amp;'static str 或自定义 enum 作为错误类型</span>
<span class="k">fn</span> <span class="nf">init_hw</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="nf">enable_clock</span><span class="p">()</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"clock init failed"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">cfg</span> <span class="o">=</span> <span class="nf">read_cfg</span><span class="p">()</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"bad config"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nf">apply_config</span><span class="p">(</span><span class="n">cfg</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>  <span class="c1">// 若返回 Err，本函数直接 return Err(...)</span>
    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">driver_init</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="nf">init_hw</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
    <span class="nf">register_irq</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
<span class="c1">// 调用方：match driver_init() { Ok(()) =&gt; {}, Err(e) =&gt; { ... } }</span>
</code></pre></div></div>

<p>与 C++ 对比：C++ 构造不能返回错误，只能抛异常或两阶段 init；Rust 用 <code class="language-plaintext highlighter-rouge">Result</code> 让「可能失败」成为类型的一部分，无运行时开销，也不依赖异常展开，因此更适合内核。</p>

<h2 id="为什么内核不能使用标准库">为什么内核不能使用标准库</h2>

<h3 id="1-标准库依赖操作系统服务">1. 标准库依赖操作系统服务</h3>

<p>标准库本质上是操作系统功能的封装：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 标准库的实现依赖系统调用</span>
<span class="c1">// std::fs::File::open("test.txt") 最终会调用：</span>
<span class="c1">// Linux: openat() 系统调用</span>
<span class="c1">// Windows: NtCreateFile() 系统调用</span>

<span class="c1">// 但在内核中：</span>
<span class="c1">// 1. 没有文件系统（或文件系统实现不同）</span>
<span class="c1">// 2. 没有当前工作目录的概念</span>
<span class="c1">// 3. 没有用户态/内核态的转换机制</span>
</code></pre></div></div>

<h3 id="2-内核需要裸机环境">2. 内核需要裸机环境</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 用户态程序可以这样：</span>
<span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Hello</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>  <span class="c1">// 依赖操作系统的标准输出</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// 内核只能这样：</span>
<span class="kt">void</span> <span class="nf">kernel_entry</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 没有 main 函数，没有标准库</span>
    <span class="c1">// 需要直接操作硬件</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">video_memory</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="mh">0xb8000</span><span class="p">;</span>
    <span class="o">*</span><span class="n">video_memory</span> <span class="o">=</span> <span class="sc">'H'</span><span class="p">;</span>  <span class="c1">// 直接写入显存</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="各语言在没有标准库时的表现">各语言在没有标准库时的表现</h2>

<h3 id="c-语言裸机编程的典范">C 语言：裸机编程的典范</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 内核中常见的 C 代码</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">serial_putc</span><span class="p">(</span><span class="kt">char</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 直接操作硬件寄存器</span>
    <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">inb</span><span class="p">(</span><span class="n">COM1</span> <span class="o">+</span> <span class="mi">5</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0x20</span><span class="p">));</span>
    <span class="n">outb</span><span class="p">(</span><span class="n">COM1</span><span class="p">,</span> <span class="n">c</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// 自己实现需要的功能</span>
<span class="kt">void</span><span class="o">*</span> <span class="nf">memcpy</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">dest</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span><span class="o">*</span> <span class="n">src</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">n</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">char</span><span class="o">*</span> <span class="n">d</span> <span class="o">=</span> <span class="n">dest</span><span class="p">;</span>
    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">s</span> <span class="o">=</span> <span class="n">src</span><span class="p">;</span>
    <span class="k">while</span> <span class="p">(</span><span class="n">n</span><span class="o">--</span><span class="p">)</span> <span class="o">*</span><span class="n">d</span><span class="o">++</span> <span class="o">=</span> <span class="o">*</span><span class="n">s</span><span class="o">++</span><span class="p">;</span>
    <span class="k">return</span> <span class="n">dest</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>C 语言的特点：</p>
<ul>
  <li><strong>语言本身与运行时分离</strong>：语法不依赖标准库</li>
  <li><strong>freestanding environment</strong>：C 标准明确支持无标准库环境</li>
  <li><strong>最小依赖</strong>：甚至连 <code class="language-plaintext highlighter-rouge">memcpy</code> 都可以自己实现</li>
</ul>

<h3 id="c标准库依赖严重">C++：标准库依赖严重</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 不能用的 C++ 特性：</span>
<span class="cp">#include</span> <span class="cpf">&lt;vector&gt;</span><span class="c1">      // 需要动态内存分配和异常</span><span class="cp">
#include</span> <span class="cpf">&lt;string&gt;</span><span class="c1">      // 需要内存分配和字符处理</span><span class="cp">
#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="c1">    // 需要操作系统支持</span><span class="cp">
#include</span> <span class="cpf">&lt;thread&gt;</span><span class="c1">      // 需要线程库支持</span><span class="cp">
#include</span> <span class="cpf">&lt;mutex&gt;</span><span class="c1">       // 需要同步原语</span><span class="cp">
</span>
<span class="c1">// 即使不用标准库，语言特性本身也有问题：</span>
<span class="k">class</span> <span class="nc">Device</span> <span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">name</span><span class="p">;</span>  <span class="c1">// 错误：string 需要标准库</span>
<span class="nl">public:</span>
    <span class="n">Device</span><span class="p">()</span> <span class="p">{</span> <span class="cm">/* 构造函数不能失败？ */</span> <span class="p">}</span>
    <span class="o">~</span><span class="n">Device</span><span class="p">()</span> <span class="p">{</span> <span class="cm">/* 析构函数不能抛异常？ */</span> <span class="p">}</span>
<span class="p">};</span>

<span class="c1">// 尝试不用标准库：</span>
<span class="k">class</span> <span class="nc">Device</span> <span class="p">{</span>
    <span class="kt">char</span> <span class="n">name</span><span class="p">[</span><span class="mi">32</span><span class="p">];</span>  <span class="c1">// 固定大小，但不够灵活</span>
    <span class="kt">int</span> <span class="n">fd</span><span class="p">;</span>
<span class="nl">public:</span>
    <span class="n">Device</span><span class="p">()</span> <span class="o">:</span> <span class="n">fd</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{}</span>  <span class="c1">// 两阶段构造（anti-pattern）</span>
    <span class="kt">bool</span> <span class="n">init</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">n</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* 真正的初始化 */</span> <span class="p">}</span>
    <span class="kt">void</span> <span class="n">deinit</span><span class="p">()</span> <span class="p">{</span> <span class="cm">/* 手动释放 */</span> <span class="p">}</span>
<span class="p">};</span>
<span class="c1">// 但这违背了 RAII 原则</span>
</code></pre></div></div>

<p>C++ 的问题：</p>
<ul>
  <li><strong>语言特性隐含依赖</strong>：即使不用标准库，异常、RTTI 等也需要运行时支持</li>
  <li><strong>STL 无法移植</strong>：容器都假设有堆内存管理和操作系统服务</li>
  <li><strong>构造函数限制</strong>：无法优雅处理初始化失败</li>
</ul>

<p><strong>澄清</strong>：离开标准库并不等于「所有 C++ 特性都用不了」。RAII（自己的类）、虚函数、vtable、重载 <code class="language-plaintext highlighter-rouge">operator new/delete</code> 都是<strong>语言特性</strong>，不依赖标准库；异常则依赖 unwinder 等<strong>运行时</strong>（多在编译器运行时库里），与 STL 是不同层。内核里通常还禁用异常（<code class="language-plaintext highlighter-rouge">-fno-exceptions</code>）和 RTTI（<code class="language-plaintext highlighter-rouge">-fno-rtti</code>），因此异常和 <code class="language-plaintext highlighter-rouge">dynamic_cast</code>/<code class="language-plaintext highlighter-rouge">typeid</code> 不可用，RAII 在异常路径上的保障也随之消失。</p>

<p><strong>假设内核用 C++：去掉标准库并加上常见限制（如 -fno-exceptions、-fno-rtti、禁止复杂全局构造）后，功能退化可概括为：</strong></p>

<table>
  <thead>
    <tr>
      <th>情况</th>
      <th>功能</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>完全不可用</strong></td>
      <td>STL 容器/算法、std::string、标准智能指针、iostream</td>
      <td>依赖标准库，内核不链接</td>
    </tr>
    <tr>
      <td> </td>
      <td>异常 (throw/catch)</td>
      <td>通常 -fno-exceptions，且不愿携带 unwinder</td>
    </tr>
    <tr>
      <td> </td>
      <td>RTTI (dynamic_cast, typeid)</td>
      <td>通常 -fno-rtti</td>
    </tr>
    <tr>
      <td><strong>语义退化</strong></td>
      <td>RAII</td>
      <td>构造不能返回错误 → 退化为两阶段 init；无异常则「任意路径都析构」的保证弱化；析构常被要求只做简单、确定性释放</td>
    </tr>
    <tr>
      <td> </td>
      <td>全局/静态对象（非平凡构造）</td>
      <td>依赖 .init_array 与启动顺序，内核中多禁止或极简使用</td>
    </tr>
    <tr>
      <td><strong>仍可用但受限</strong></td>
      <td>new/delete</td>
      <td>可重载到 kmalloc/kfree；有的规范禁止全局 new，仅允许 placement new + 内核分配器</td>
    </tr>
    <tr>
      <td> </td>
      <td>虚函数 / vtable、模板、类与继承</td>
      <td>不依赖标准库；风格上常限制深继承与过度模板</td>
    </tr>
    <tr>
      <td> </td>
      <td>const、引用、重载、命名空间</td>
      <td>纯语言特性，无退化</td>
    </tr>
  </tbody>
</table>

<p>整体上 C++ 会退化成「带类、模板和虚函数的 C」：语法和类型系统仍在，错误处理回到返回码，资源管理更显式，不能依赖异常与标准库。</p>

<h3 id="rustno_std-模式">Rust：no_std 模式<sup id="fnref:1:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></h3>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 指定不使用标准库</span>
<span class="nd">#![no_std]</span>

<span class="c1">// 只能使用 core 库（无操作系统依赖）</span>
<span class="k">use</span> <span class="nn">core</span><span class="p">::</span><span class="nn">panic</span><span class="p">::</span><span class="n">PanicInfo</span><span class="p">;</span>

<span class="c1">// 需要自己处理 panic</span>
<span class="nd">#[panic_handler]</span>
<span class="k">fn</span> <span class="nf">panic</span><span class="p">(</span><span class="n">_info</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">PanicInfo</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="k">loop</span> <span class="p">{}</span>
<span class="p">}</span>

<span class="c1">// 需要自己实现内存分配（如果需要）</span>
<span class="nd">#[global_allocator]</span>
<span class="k">static</span> <span class="n">ALLOCATOR</span><span class="p">:</span> <span class="n">MyAllocator</span> <span class="o">=</span> <span class="n">MyAllocator</span><span class="p">;</span>

<span class="c1">// 可以安全地使用大部分语言特性</span>
<span class="nd">#[repr(C)]</span>
<span class="k">struct</span> <span class="n">Device</span> <span class="p">{</span>
    <span class="n">base_addr</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="n">irq</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Device</span> <span class="p">{</span>
    <span class="k">const</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>  <span class="c1">// const fn 可以在编译时执行</span>
        <span class="n">Device</span> <span class="p">{</span> <span class="n">base_addr</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="n">irq</span><span class="p">:</span> <span class="mi">0</span> <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">read_reg</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">offset</span><span class="p">:</span> <span class="nb">usize</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u32</span> <span class="p">{</span>
        <span class="c1">// 直接操作内存映射 IO</span>
        <span class="k">unsafe</span> <span class="p">{</span> <span class="p">(</span><span class="k">self</span><span class="py">.base_addr</span> <span class="k">as</span> <span class="o">*</span><span class="k">const</span> <span class="nb">u32</span><span class="p">)</span><span class="nf">.add</span><span class="p">(</span><span class="n">offset</span><span class="p">)</span><span class="nf">.read_volatile</span><span class="p">()</span> <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Rust 的优势<sup id="fnref:1:2"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>：</p>
<ul>
  <li><strong>core 库</strong>：提供语言核心功能，无操作系统依赖。core 中<strong>不包含与操作系统相关的 I/O 能力</strong>：文件、标准输入/输出（stdin/stdout）、网络（TcpStream 等）均在 <code class="language-plaintext highlighter-rouge">std</code> 中；core 里仅有极少的 I/O 相关 trait/类型定义（如 <code class="language-plaintext highlighter-rouge">BorrowedBuf</code>），不提供实际读写，因此 <code class="language-plaintext highlighter-rouge">#![no_std]</code> 下无法使用 <code class="language-plaintext highlighter-rouge">println!</code>、<code class="language-plaintext highlighter-rouge">File</code>、<code class="language-plaintext highlighter-rouge">std::net</code> 等，需自行实现或依赖其他库。</li>
  <li><strong>语言特性零成本</strong>：所有权、借用检查都在编译期</li>
  <li><strong>明确的 unsafe</strong>：硬件操作需要显式标记</li>
  <li><strong>const fn</strong>：可以在编译时执行函数</li>
</ul>

<h2 id="实际代码对比">实际代码对比</h2>

<h3 id="实现一个简单的串口驱动">实现一个简单的串口驱动</h3>

<p><strong>C 版本</strong>：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// serial.h</span>
<span class="k">struct</span> <span class="n">serial_port</span> <span class="p">{</span>
    <span class="kt">uint16_t</span> <span class="n">port</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">initialized</span><span class="p">;</span>
<span class="p">};</span>

<span class="kt">void</span> <span class="nf">serial_init</span><span class="p">(</span><span class="k">struct</span> <span class="n">serial_port</span> <span class="o">*</span><span class="n">sp</span><span class="p">,</span> <span class="kt">uint16_t</span> <span class="n">port</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">serial_putc</span><span class="p">(</span><span class="k">struct</span> <span class="n">serial_port</span> <span class="o">*</span><span class="n">sp</span><span class="p">,</span> <span class="kt">char</span> <span class="n">c</span><span class="p">);</span>

<span class="c1">// serial.c</span>
<span class="kt">void</span> <span class="nf">serial_init</span><span class="p">(</span><span class="k">struct</span> <span class="n">serial_port</span> <span class="o">*</span><span class="n">sp</span><span class="p">,</span> <span class="kt">uint16_t</span> <span class="n">port</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">sp</span><span class="o">-&gt;</span><span class="n">port</span> <span class="o">=</span> <span class="n">port</span><span class="p">;</span>
    <span class="n">sp</span><span class="o">-&gt;</span><span class="n">initialized</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="n">outb</span><span class="p">(</span><span class="n">port</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="mh">0x00</span><span class="p">);</span>  <span class="c1">// 关闭中断</span>
    <span class="n">outb</span><span class="p">(</span><span class="n">port</span> <span class="o">+</span> <span class="mi">3</span><span class="p">,</span> <span class="mh">0x80</span><span class="p">);</span>  <span class="c1">// 设置波特率</span>
    <span class="n">outb</span><span class="p">(</span><span class="n">port</span> <span class="o">+</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0x03</span><span class="p">);</span>
    <span class="n">outb</span><span class="p">(</span><span class="n">port</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="mh">0x00</span><span class="p">);</span>
    <span class="n">outb</span><span class="p">(</span><span class="n">port</span> <span class="o">+</span> <span class="mi">3</span><span class="p">,</span> <span class="mh">0x03</span><span class="p">);</span>
    <span class="n">outb</span><span class="p">(</span><span class="n">port</span> <span class="o">+</span> <span class="mi">2</span><span class="p">,</span> <span class="mh">0xC7</span><span class="p">);</span>
    <span class="n">outb</span><span class="p">(</span><span class="n">port</span> <span class="o">+</span> <span class="mi">4</span><span class="p">,</span> <span class="mh">0x0B</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">serial_putc</span><span class="p">(</span><span class="k">struct</span> <span class="n">serial_port</span> <span class="o">*</span><span class="n">sp</span><span class="p">,</span> <span class="kt">char</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">while</span> <span class="p">((</span><span class="n">inb</span><span class="p">(</span><span class="n">sp</span><span class="o">-&gt;</span><span class="n">port</span> <span class="o">+</span> <span class="mi">5</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0x20</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">outb</span><span class="p">(</span><span class="n">sp</span><span class="o">-&gt;</span><span class="n">port</span><span class="p">,</span> <span class="n">c</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>C++ 版本（有问题）</strong>：</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 尝试用 C++ 风格</span>
<span class="k">class</span> <span class="nc">SerialPort</span> <span class="p">{</span>
<span class="nl">private:</span>
    <span class="kt">uint16_t</span> <span class="n">port</span><span class="p">;</span>
    <span class="kt">bool</span> <span class="n">initialized</span><span class="p">;</span>

<span class="nl">public:</span>
    <span class="n">SerialPort</span><span class="p">(</span><span class="kt">uint16_t</span> <span class="n">port</span><span class="p">)</span> <span class="o">:</span> <span class="n">port</span><span class="p">(</span><span class="n">port</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 构造函数中初始化，但如果失败？</span>
        <span class="n">init</span><span class="p">();</span>  <span class="c1">// 不能返回错误码</span>
    <span class="p">}</span>

    <span class="o">~</span><span class="n">SerialPort</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 析构函数中清理</span>
    <span class="p">}</span>

    <span class="kt">void</span> <span class="n">putc</span><span class="p">(</span><span class="kt">char</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">while</span> <span class="p">((</span><span class="n">inb</span><span class="p">(</span><span class="n">port</span> <span class="o">+</span> <span class="mi">5</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0x20</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
        <span class="n">outb</span><span class="p">(</span><span class="n">port</span><span class="p">,</span> <span class="n">c</span><span class="p">);</span>
    <span class="p">}</span>

<span class="nl">private:</span>
    <span class="kt">void</span> <span class="n">init</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 如果这里失败，只能抛异常</span>
        <span class="c1">// 但内核中不能使用异常</span>
        <span class="n">outb</span><span class="p">(</span><span class="n">port</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="mh">0x00</span><span class="p">);</span>
        <span class="c1">// ...</span>
    <span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>

<p><strong>Rust 版本</strong>（内存映射 I/O 风格）：</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#![no_std]</span>

<span class="k">use</span> <span class="nn">core</span><span class="p">::</span><span class="nn">ptr</span><span class="p">::{</span><span class="n">read_volatile</span><span class="p">,</span> <span class="n">write_volatile</span><span class="p">};</span>

<span class="nd">#[repr(C)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">SerialPort</span> <span class="p">{</span>
    <span class="n">port</span><span class="p">:</span> <span class="nb">u16</span><span class="p">,</span>
    <span class="n">initialized</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">SerialPort</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">port</span><span class="p">:</span> <span class="nb">u16</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="k">Self</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">sp</span> <span class="o">=</span> <span class="n">SerialPort</span> <span class="p">{</span>
            <span class="n">port</span><span class="p">,</span>
            <span class="n">initialized</span><span class="p">:</span> <span class="k">false</span><span class="p">,</span>
        <span class="p">};</span>
        <span class="n">sp</span><span class="nf">.init</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
        <span class="nf">Ok</span><span class="p">(</span><span class="n">sp</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">init</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">unsafe</span> <span class="p">{</span>
            <span class="nf">write_volatile</span><span class="p">((</span><span class="k">self</span><span class="py">.port</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">u8</span><span class="p">,</span> <span class="mi">0x00</span><span class="p">);</span>
            <span class="nf">write_volatile</span><span class="p">((</span><span class="k">self</span><span class="py">.port</span> <span class="o">+</span> <span class="mi">3</span><span class="p">)</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">u8</span><span class="p">,</span> <span class="mi">0x80</span><span class="p">);</span>
            <span class="nf">write_volatile</span><span class="p">((</span><span class="k">self</span><span class="py">.port</span> <span class="o">+</span> <span class="mi">0</span><span class="p">)</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">u8</span><span class="p">,</span> <span class="mi">0x03</span><span class="p">);</span>
            <span class="nf">write_volatile</span><span class="p">((</span><span class="k">self</span><span class="py">.port</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">u8</span><span class="p">,</span> <span class="mi">0x00</span><span class="p">);</span>
            <span class="nf">write_volatile</span><span class="p">((</span><span class="k">self</span><span class="py">.port</span> <span class="o">+</span> <span class="mi">3</span><span class="p">)</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">u8</span><span class="p">,</span> <span class="mi">0x03</span><span class="p">);</span>
            <span class="nf">write_volatile</span><span class="p">((</span><span class="k">self</span><span class="py">.port</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">u8</span><span class="p">,</span> <span class="mi">0xC7</span><span class="p">);</span>
            <span class="nf">write_volatile</span><span class="p">((</span><span class="k">self</span><span class="py">.port</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">u8</span><span class="p">,</span> <span class="mi">0x0B</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">self</span><span class="py">.initialized</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
        <span class="nf">Ok</span><span class="p">(())</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">putc</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="nb">u8</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">unsafe</span> <span class="p">{</span>
            <span class="k">while</span> <span class="p">(</span><span class="nf">read_volatile</span><span class="p">((</span><span class="k">self</span><span class="py">.port</span> <span class="o">+</span> <span class="mi">5</span><span class="p">)</span> <span class="k">as</span> <span class="o">*</span><span class="k">const</span> <span class="nb">u8</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mi">0x20</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{}</span>
            <span class="nf">write_volatile</span><span class="p">(</span><span class="k">self</span><span class="py">.port</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">u8</span><span class="p">,</span> <span class="n">c</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>上述 Rust 示例为<strong>内存映射 I/O</strong> 风格（例如常见于 ARM 等平台）；在 x86 上 COM 口为<strong>端口 I/O</strong>，需使用 inb/outb 或 <code class="language-plaintext highlighter-rouge">x86_64::instructions::port::Port</code> 等封装。</p>

<h2 id="标准库-vs-no_std-的生态差异">标准库 vs no_std 的生态差异</h2>

<h3 id="可用功能对比">可用功能对比</h3>

<table>
  <thead>
    <tr>
      <th>功能</th>
      <th>标准库</th>
      <th>no_std</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Vec/String</td>
      <td>✅</td>
      <td>❌</td>
      <td>需要内存分配器</td>
    </tr>
    <tr>
      <td>Box/Rc/Arc</td>
      <td>✅</td>
      <td>⚠️</td>
      <td>需要内存分配器</td>
    </tr>
    <tr>
      <td>HashMap</td>
      <td>✅</td>
      <td>❌</td>
      <td>需要随机数源</td>
    </tr>
    <tr>
      <td>println!</td>
      <td>✅</td>
      <td>❌</td>
      <td>需要 IO（core 无具体 I/O 实现）</td>
    </tr>
    <tr>
      <td>文件操作</td>
      <td>✅</td>
      <td>❌</td>
      <td>需要文件系统</td>
    </tr>
    <tr>
      <td>线程</td>
      <td>✅</td>
      <td>❌</td>
      <td>需要调度器</td>
    </tr>
    <tr>
      <td>Mutex</td>
      <td>✅</td>
      <td>⚠️</td>
      <td>需要原子操作支持</td>
    </tr>
    <tr>
      <td>迭代器</td>
      <td>✅</td>
      <td>✅</td>
      <td>纯语言特性</td>
    </tr>
    <tr>
      <td>match</td>
      <td>✅</td>
      <td>✅</td>
      <td>语言特性</td>
    </tr>
    <tr>
      <td>trait</td>
      <td>✅</td>
      <td>✅</td>
      <td>语言特性</td>
    </tr>
    <tr>
      <td>闭包</td>
      <td>✅</td>
      <td>✅</td>
      <td>语言特性</td>
    </tr>
  </tbody>
</table>

<h3 id="实际影响">实际影响</h3>

<p>在裸机环境中：</p>
<ul>
  <li><strong>C</strong>：完全掌控，需要什么写什么</li>
  <li><strong>C++</strong>：大量特性受限，变成「更好的 C」</li>
  <li><strong>Rust</strong>：通过 no_std + core 保留大部分语言能力<sup id="fnref:1:3"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></li>
</ul>

<h2 id="实际内核开发的选择">实际内核开发的选择</h2>

<ul>
  <li><strong>Linux</strong>：C 语言，完全掌控内存和运行时；近年来开始接纳 Rust 编写的子系统<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">8</a></sup><sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">9</a></sup></li>
  <li><strong>Windows</strong>：混合，内核主要用 C，部分驱动用 C++</li>
  <li><strong>Redox OS</strong>：Rust，展示现代语言也能做内核<sup id="fnref:5:1"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">4</a></sup></li>
  <li><strong>鸿蒙</strong>：混合，内核用 C，上层用 C++/Rust</li>
</ul>

<h2 id="总结">总结</h2>

<p>从运行时与内存管理看，C++ 不适合内核开发的主要原因在于<strong>内存管理模型的差异</strong>：异常处理、隐式构造/析构、RAII 等与内核需要的确定性和显式控制相冲突；Rust 则用所有权系统在零成本抽象与内存安全之间取得折中。从标准库看，内核不能使用标准库：<strong>C</strong> 失去的很少（语言本身不依赖库），<strong>C++</strong> 失去核心优势（STL、异常、部分 RAII），<strong>Rust</strong> 失去便利性（集合类型、格式化输出）但保留安全性。因此 Linux 选择 C（简单、可控、最小依赖，Rust 作为补充逐步引入<sup id="fnref:6:1"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>），Windows 内核主要用 C、部分驱动用 C++ 且限制特性，Redox 选择 Rust（no_std 提供安全性与表达能力的最佳平衡<sup id="fnref:5:2"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>）。</p>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:4">
      <p><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git">Linux Kernel Source (torvalds/linux)</a> - 官方内核源码（C 为主，含 Rust 子系统） <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:4:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:1">
      <p><a href="https://docs.rust-embedded.org/book/intro/no-std.html">The Embedded Rust Book - no_std</a> - Rust 裸机/内核开发中的 no_std 与 core 库说明 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:1:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:1:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:1:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a></p>
    </li>
    <li id="fn:2">
      <p><a href="https://rust-lang.github.io/rfcs/1184-stabilize-no_std.html">Rust RFC 1184: Stabilize no_std</a> - no_std 稳定化与 libcore 范围 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5">
      <p><a href="https://www.redox-os.org/">Redox OS</a> - 使用 Rust no_std 编写的操作系统 <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:5:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:5:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:7">
      <p><a href="https://lwn.net/Articles/444910/">Object-oriented design patterns in the kernel, part 1</a> - LWN，方法分派与 vtable（file_operations、inode_operations 等）模式 <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8">
      <p><a href="https://lwn.net/Articles/446317/">Object-oriented design patterns in the kernel, part 2</a> - LWN，数据继承与结构体内嵌（container_of）模式 <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:9">
      <p><a href="https://weinan.io/2017/12/17/linux-driver.html">Linux驱动开发入门（四）</a> - 本博客，file_operations / inode 等内核数据结构与驱动示例 <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p><a href="https://docs.kernel.org/rust/general-information.html">Linux Kernel - Rust support</a> - 内核 Rust 支持说明（仅链接 libcore，无 std） <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6">
      <p><a href="https://rust-for-linux.com/">Rust for Linux</a> - 内核内 Rust 支持项目与文档 <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:6:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="rust" /><category term="linux-kernel" /><summary type="html"><![CDATA[对比 C、C++ 与 Rust 在内核开发中的运行时、标准库与抽象成本，厘清语言选型权衡。]]></summary></entry><entry xml:lang="en"><title type="html">How C Calls Rust in Linux Kernel: Module Lifecycle Deep Dive</title><link href="https://weinan.tech/2026/02/18/how-c-calls-rust-in-linux-kernel.html" rel="alternate" type="text/html" title="How C Calls Rust in Linux Kernel: Module Lifecycle Deep Dive" /><published>2026-02-18T00:00:00+08:00</published><updated>2026-02-18T00:00:00+08:00</updated><id>https://weinan.tech/2026/02/18/how-c-calls-rust-in-linux-kernel</id><content type="html" xml:base="https://weinan.tech/2026/02/18/how-c-calls-rust-in-linux-kernel.html"><![CDATA[<blockquote>
  <p>本文为英文存档，已不再主推；本站后续内容以中文技术长文为主。 配套视频见 <a href="https://space.bilibili.com/21947620">B站频道</a>。</p>
</blockquote>

<p>A comprehensive technical analysis of how C kernel code calls Rust functions through the module loading mechanism. Using actual Linux kernel source code (6.x), this article reveals the complete evidence chain: from Rust’s #[no_mangle] attribute to C’s function pointer invocation, from ELF symbol binding to the actual call flow. We demonstrate that C→Rust calls are not theoretical but a production reality implemented through standard module lifecycle management.</p>

<h2 id="introduction-the-question">Introduction: The Question</h2>

<p>In discussions about Rust in the Linux kernel, a fundamental architectural question often arises:</p>

<p><strong>“Can C kernel code call Rust functions?”</strong></p>

<p>This isn’t just an academic question. Understanding the call direction between C and Rust is crucial for grasping:</p>
<ul>
  <li>The integration architecture</li>
  <li>ABI stability requirements</li>
  <li>Future evolution possibilities</li>
  <li>Security and safety boundaries</li>
</ul>

<p>Many assume that Rust only wraps C APIs (unidirectional), making Rust purely a “consumer” of C services. However, <strong>actual kernel source code reveals a different reality</strong>: C does call Rust functions, specifically for module lifecycle management.</p>

<p>This article provides a complete evidence chain based on Linux kernel 6.x source code.</p>

<h2 id="the-answer-yes-through-module-lifecycle">The Answer: Yes, Through Module Lifecycle</h2>

<p><strong>C kernel code DOES call Rust functions</strong> for:</p>
<ul>
  <li>✅ Module initialization (<code class="language-plaintext highlighter-rouge">init_module()</code>, <code class="language-plaintext highlighter-rouge">__&lt;name&gt;_init()</code>)</li>
  <li>✅ Module cleanup (<code class="language-plaintext highlighter-rouge">cleanup_module()</code>, <code class="language-plaintext highlighter-rouge">__&lt;name&gt;_exit()</code>)</li>
</ul>

<p><strong>C kernel code does NOT call Rust for</strong>:</p>
<ul>
  <li>❌ Data processing or utility functions</li>
  <li>❌ Core subsystem services</li>
  <li>❌ General-purpose APIs</li>
</ul>

<p>The scope is <strong>strictly limited to module lifecycle management</strong>, but this is a critical integration point that enables all Rust drivers to work.</p>

<h2 id="evidence-1-rust-generates-c-compatible-symbols">Evidence 1: Rust Generates C-Compatible Symbols</h2>

<p>Every Rust module automatically generates C-callable functions via the <code class="language-plaintext highlighter-rouge">module!</code> macro family. Here’s the actual code from <code class="language-plaintext highlighter-rouge">rust/macros/module.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// rust/macros/module.rs (lines 260-290)</span>

<span class="c1">// For loadable modules (.ko files)</span>
<span class="nd">#[cfg(MODULE)]</span>
<span class="nd">#[doc(hidden)]</span>
<span class="nd">#[no_mangle]</span>
<span class="nd">#[link_section</span> <span class="nd">=</span> <span class="s">".init.text"</span><span class="nd">]</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">init_module</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="p">::</span><span class="nn">kernel</span><span class="p">::</span><span class="nn">ffi</span><span class="p">::</span><span class="nb">c_int</span> <span class="p">{</span>
    <span class="c1">// SAFETY: This function is inaccessible to the outside due to the double</span>
    <span class="c1">// module wrapping it. It is called exactly once by the C side via its</span>
    <span class="c1">// unique name.</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="nf">__init</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>

<span class="nd">#[cfg(MODULE)]</span>
<span class="nd">#[doc(hidden)]</span>
<span class="nd">#[no_mangle]</span>
<span class="nd">#[link_section</span> <span class="nd">=</span> <span class="s">".exit.text"</span><span class="nd">]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">cleanup_module</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// SAFETY:</span>
    <span class="c1">// - This function is inaccessible to the outside due to the double</span>
    <span class="c1">//   module wrapping it. It is called exactly once by the C side via its</span>
    <span class="c1">//   unique name,</span>
    <span class="c1">// - furthermore it is only called after `init_module` has returned `0`</span>
    <span class="c1">//   (which delegates to `__init`).</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="nf">__exit</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// For built-in modules (compiled into kernel)</span>
<span class="nd">#[cfg(not(MODULE))]</span>
<span class="nd">#[doc(hidden)]</span>
<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="n">__</span><span class="o">&lt;</span><span class="n">ident</span><span class="o">&gt;</span><span class="nf">_init</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="p">::</span><span class="nn">kernel</span><span class="p">::</span><span class="nn">ffi</span><span class="p">::</span><span class="nb">c_int</span> <span class="p">{</span>
    <span class="c1">// SAFETY: This function is inaccessible to the outside due to the double</span>
    <span class="c1">// module wrapping it. It is called exactly once by the C side via its</span>
    <span class="c1">// placement above in the initcall section.</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="nf">__init</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>

<span class="nd">#[cfg(not(MODULE))]</span>
<span class="nd">#[doc(hidden)]</span>
<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="n">__</span><span class="o">&lt;</span><span class="n">ident</span><span class="o">&gt;</span><span class="nf">_exit</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="nf">__exit</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="key-mechanisms-explained">Key Mechanisms Explained</h3>

<p><strong>1. <code class="language-plaintext highlighter-rouge">#[no_mangle]</code> Attribute</strong></p>

<p>Without this attribute, Rust applies name mangling:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>init_module → _ZN7mymodule11init_module17h&lt;hash&gt;E
</code></pre></div></div>

<p>With <code class="language-plaintext highlighter-rouge">#[no_mangle]</code>, the symbol name remains:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>init_module → init_module
</code></pre></div></div>

<p>This allows C code to find the function by its expected standard name.</p>

<p><strong>2. <code class="language-plaintext highlighter-rouge">extern "C"</code> Calling Convention</strong></p>

<p>This ensures:</p>
<ul>
  <li>Parameters passed according to C ABI (System V on x86_64)</li>
  <li>Stack frame layout matches C expectations</li>
  <li>Register usage follows C calling convention</li>
  <li>No Rust-specific calling overhead</li>
</ul>

<p><strong>3. <code class="language-plaintext highlighter-rouge">#[link_section = ".init.text"]</code></strong></p>

<p>Places the function in the ELF <code class="language-plaintext highlighter-rouge">.init.text</code> section, where the C kernel expects to find initialization code. This section can be freed after initialization completes.</p>

<h2 id="evidence-2-c-kernels-module-structure">Evidence 2: C Kernel’s Module Structure</h2>

<p>The C kernel defines a standard module structure that holds a function pointer to the init function:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// include/linux/module.h (line 470)</span>
<span class="k">struct</span> <span class="n">module</span> <span class="p">{</span>
    <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">;</span>

    <span class="c1">// ... many fields omitted ...</span>

    <span class="cm">/* Startup function. */</span>
    <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">init</span><span class="p">)(</span><span class="kt">void</span><span class="p">);</span>  <span class="c1">// ← Function pointer to init_module</span>

    <span class="k">struct</span> <span class="n">module_memory</span> <span class="n">mem</span><span class="p">[</span><span class="n">MOD_MEM_NUM_TYPES</span><span class="p">]</span> <span class="n">__module_memory_align</span><span class="p">;</span>

    <span class="c1">// ... more fields ...</span>
<span class="p">};</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">init</code> field is a <strong>function pointer</strong> that will be invoked during module loading.</p>

<h2 id="evidence-3-c-kernel-calls-the-function-pointer">Evidence 3: C Kernel Calls the Function Pointer</h2>

<p>When loading a module, the C kernel explicitly calls <code class="language-plaintext highlighter-rouge">mod-&gt;init</code>:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// kernel/module/main.c (lines 2989-3020)</span>
<span class="k">static</span> <span class="n">noinline</span> <span class="kt">int</span> <span class="nf">do_init_module</span><span class="p">(</span><span class="k">struct</span> <span class="n">module</span> <span class="o">*</span><span class="n">mod</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">mod_initfree</span> <span class="o">*</span><span class="n">freeinit</span><span class="p">;</span>

    <span class="c1">// ... setup code omitted ...</span>

    <span class="n">freeinit</span> <span class="o">=</span> <span class="n">kmalloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">freeinit</span><span class="p">),</span> <span class="n">GFP_KERNEL</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">freeinit</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">ret</span> <span class="o">=</span> <span class="o">-</span><span class="n">ENOMEM</span><span class="p">;</span>
        <span class="k">goto</span> <span class="n">fail</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">freeinit</span><span class="o">-&gt;</span><span class="n">init_text</span> <span class="o">=</span> <span class="n">mod</span><span class="o">-&gt;</span><span class="n">mem</span><span class="p">[</span><span class="n">MOD_INIT_TEXT</span><span class="p">].</span><span class="n">base</span><span class="p">;</span>
    <span class="n">freeinit</span><span class="o">-&gt;</span><span class="n">init_data</span> <span class="o">=</span> <span class="n">mod</span><span class="o">-&gt;</span><span class="n">mem</span><span class="p">[</span><span class="n">MOD_INIT_DATA</span><span class="p">].</span><span class="n">base</span><span class="p">;</span>
    <span class="n">freeinit</span><span class="o">-&gt;</span><span class="n">init_rodata</span> <span class="o">=</span> <span class="n">mod</span><span class="o">-&gt;</span><span class="n">mem</span><span class="p">[</span><span class="n">MOD_INIT_RODATA</span><span class="p">].</span><span class="n">base</span><span class="p">;</span>

    <span class="n">do_mod_ctors</span><span class="p">(</span><span class="n">mod</span><span class="p">);</span>

    <span class="cm">/* Start the module */</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">mod</span><span class="o">-&gt;</span><span class="n">init</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
        <span class="n">ret</span> <span class="o">=</span> <span class="n">do_one_initcall</span><span class="p">(</span><span class="n">mod</span><span class="o">-&gt;</span><span class="n">init</span><span class="p">);</span>  <span class="c1">// ← CALLS THE FUNCTION POINTER</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">goto</span> <span class="n">fail_free_freeinit</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// ... post-init code ...</span>

    <span class="n">mod</span><span class="o">-&gt;</span><span class="n">state</span> <span class="o">=</span> <span class="n">MODULE_STATE_LIVE</span><span class="p">;</span>

    <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Key observation</strong>: <code class="language-plaintext highlighter-rouge">do_one_initcall(mod-&gt;init)</code> invokes the function pointer, which points to Rust’s <code class="language-plaintext highlighter-rouge">init_module()</code> for Rust modules.</p>

<h2 id="evidence-4-how-mod-init-gets-set">Evidence 4: How mod-&gt;init Gets Set</h2>

<p><strong>Critical question</strong>: How does <code class="language-plaintext highlighter-rouge">mod-&gt;init</code> point to the Rust function?</p>

<p><strong>Answer</strong>: Through ELF symbol binding at link time, not runtime lookup.</p>

<h3 id="the-elf-module-structure-layout">The ELF Module Structure Layout</h3>

<p>When compiling a kernel module (C or Rust), the linker creates a special section:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.gnu.linkonce.this_module
</code></pre></div></div>

<p>This section contains the <strong>complete binary layout</strong> of <code class="language-plaintext highlighter-rouge">struct module</code>, including:</p>
<ul>
  <li>Module name</li>
  <li>Module version</li>
  <li><strong>Init function pointer</strong> (already resolved to <code class="language-plaintext highlighter-rouge">init_module</code> address)</li>
  <li>Cleanup function pointer</li>
  <li>Other metadata</li>
</ul>

<h3 id="module-loading-process">Module Loading Process</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// kernel/module/main.c (line 2901)</span>
<span class="k">static</span> <span class="k">struct</span> <span class="n">module</span> <span class="o">*</span><span class="nf">layout_and_allocate</span><span class="p">(</span><span class="k">struct</span> <span class="n">load_info</span> <span class="o">*</span><span class="n">info</span><span class="p">,</span> <span class="kt">int</span> <span class="n">flags</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">struct</span> <span class="n">module</span> <span class="o">*</span><span class="n">mod</span><span class="p">;</span>
    <span class="c1">// ... layout calculation ...</span>

    <span class="cm">/* Module has been copied to its final place now: return it. */</span>
    <span class="n">mod</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">info</span><span class="o">-&gt;</span><span class="n">sechdrs</span><span class="p">[</span><span class="n">info</span><span class="o">-&gt;</span><span class="n">index</span><span class="p">.</span><span class="n">mod</span><span class="p">].</span><span class="n">sh_addr</span><span class="p">;</span>
    <span class="c1">// ↑ Direct memory mapping - the module struct is already complete!</span>

    <span class="n">kmemleak_load_module</span><span class="p">(</span><span class="n">mod</span><span class="p">,</span> <span class="n">info</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">mod</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The kernel <strong>does NOT</strong> manually assign each field. Instead:</p>
<ol>
  <li>The <code class="language-plaintext highlighter-rouge">.gnu.linkonce.this_module</code> section is mapped into memory</li>
  <li>This section IS the <code class="language-plaintext highlighter-rouge">struct module</code></li>
  <li>All fields, including <code class="language-plaintext highlighter-rouge">init</code>, are <strong>already set by the linker</strong></li>
</ol>

<h3 id="symbol-resolution-at-link-time">Symbol Resolution at Link Time</h3>

<p>When linking a Rust module:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Simplified linking process</span>
ld <span class="nt">-r</span> <span class="se">\</span>
  <span class="nt">-o</span> rcpufreq_dt.ko <span class="se">\</span>
  rcpufreq_dt.o <span class="se">\</span>
  <span class="nt">--build-id</span>
</code></pre></div></div>

<p>The linker:</p>
<ol>
  <li>Finds the <code class="language-plaintext highlighter-rouge">init_module</code> symbol (address 0xXXXX)</li>
  <li>Writes this address into <code class="language-plaintext highlighter-rouge">module.init</code> field</li>
  <li>Embeds the complete struct in <code class="language-plaintext highlighter-rouge">.gnu.linkonce.this_module</code> section</li>
  <li>Writes everything to the <code class="language-plaintext highlighter-rouge">.ko</code> file</li>
</ol>

<h2 id="evidence-5-real-rust-driver-example">Evidence 5: Real Rust Driver Example</h2>

<p>Every Rust driver uses a macro that generates these functions. For example:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// drivers/cpufreq/rcpufreq_dt.rs (lines 215-221)</span>
<span class="nd">module_platform_driver!</span> <span class="p">{</span>
    <span class="k">type</span><span class="p">:</span> <span class="n">CPUFreqDTDriver</span><span class="p">,</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"cpufreq-dt"</span><span class="p">,</span>
    <span class="n">author</span><span class="p">:</span> <span class="s">"Viresh Kumar &lt;viresh.kumar@linaro.org&gt;"</span><span class="p">,</span>
    <span class="n">description</span><span class="p">:</span> <span class="s">"Generic CPUFreq DT driver"</span><span class="p">,</span>
    <span class="n">license</span><span class="p">:</span> <span class="s">"GPL v2"</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This macro expands to:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Generated code (conceptual)</span>
<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">init_module</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">i32</span> <span class="p">{</span>
    <span class="c1">// Register CPUFreqDTDriver as platform driver</span>
    <span class="nn">cpufreq</span><span class="p">::</span><span class="nn">Registration</span><span class="p">::</span><span class="o">&lt;</span><span class="n">CPUFreqDTDriver</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">new_foreign_owned</span><span class="p">(</span><span class="cm">/*...*/</span><span class="p">)</span>
<span class="p">}</span>

<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">cleanup_module</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// Unregister driver</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="complete-call-flow">Complete Call Flow</h2>

<p>Let’s trace what happens when loading a Rust module:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. User executes:
   $ insmod rcpufreq_dt.ko

2. Kernel syscall:
   SYSCALL_DEFINE3(init_module, void __user *, umod, ...)
   ↓

3. Copy module to kernel memory:
   copy_module_from_user(umod, len, &amp;info)
   ↓

4. Parse ELF and allocate:
   mod = layout_and_allocate(&amp;info, flags)
   ↓ (maps .gnu.linkonce.this_module section)

5. mod struct is now complete:
   mod-&gt;init = &amp;init_module  // ← Already set by linker
   mod-&gt;name = "cpufreq-dt"
   // ... all fields populated ...
   ↓

6. Call initialization:
   do_init_module(mod)
   ↓

7. Invoke the function pointer:
   ret = do_one_initcall(mod-&gt;init)
   ↓ (Calls through function pointer)

8. EXECUTION TRANSFERS TO RUST:
   init_module() in Rust code executes
   ↓

9. Rust driver initializes:
   CPUFreqDTDriver::probe() registers driver
   ↓

10. Module is live:
    mod-&gt;state = MODULE_STATE_LIVE
</code></pre></div></div>

<p><strong>Critical insight</strong>: The C→Rust call at step 7 is a <strong>standard indirect function call</strong> through a function pointer, exactly the same as calling a C module’s init function.</p>

<h2 id="symbol-naming-convention">Symbol Naming Convention</h2>

<p>The kernel expects specific symbol names:</p>

<table>
  <thead>
    <tr>
      <th>Module Type</th>
      <th>Init Symbol</th>
      <th>Cleanup Symbol</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Loadable (.ko)</td>
      <td><code class="language-plaintext highlighter-rouge">init_module</code></td>
      <td><code class="language-plaintext highlighter-rouge">cleanup_module</code></td>
    </tr>
    <tr>
      <td>Built-in</td>
      <td><code class="language-plaintext highlighter-rouge">__&lt;name&gt;_init</code></td>
      <td><code class="language-plaintext highlighter-rouge">__&lt;name&gt;_exit</code></td>
    </tr>
  </tbody>
</table>

<p>Both C and Rust modules must follow this convention. Example:</p>

<p><strong>C module</strong>:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// drivers/example/example_c.c</span>
<span class="k">static</span> <span class="kt">int</span> <span class="n">__init</span> <span class="nf">my_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// ...</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="n">__exit</span> <span class="nf">my_exit</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// ...</span>
<span class="p">}</span>

<span class="n">module_init</span><span class="p">(</span><span class="n">my_init</span><span class="p">);</span>  <span class="c1">// Expands to create init_module</span>
<span class="n">module_exit</span><span class="p">(</span><span class="n">my_exit</span><span class="p">);</span>  <span class="c1">// Expands to create cleanup_module</span>
</code></pre></div></div>

<p><strong>Rust module</strong>:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// drivers/example/example_rust.rs</span>
<span class="nd">module_platform_driver!</span> <span class="p">{</span>
    <span class="k">type</span><span class="p">:</span> <span class="n">MyDriver</span><span class="p">,</span>
    <span class="c1">// ...</span>
<span class="p">}</span>
<span class="c1">// Macro generates init_module and cleanup_module</span>
</code></pre></div></div>

<p>Both produce the <strong>same ELF symbols</strong> that the kernel expects.</p>

<h2 id="verification-methods">Verification Methods</h2>

<p>If you have a compiled Rust kernel module, you can verify this mechanism directly:</p>

<h3 id="1-check-symbol-table">1. Check Symbol Table</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>nm drivers/cpufreq/rcpufreq_dt.ko | <span class="nb">grep </span>init_module
0000000000000000 T init_module
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">T</code> indicates a symbol in the <code class="language-plaintext highlighter-rouge">.text</code> section (code). Address <code class="language-plaintext highlighter-rouge">0000000000000000</code> is relative to the module’s base.</p>

<h3 id="2-examine-elf-sections">2. Examine ELF Sections</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>readelf <span class="nt">-S</span> drivers/cpufreq/rcpufreq_dt.ko | <span class="nb">grep</span> <span class="nt">-E</span> <span class="s2">"</span><span class="se">\.</span><span class="s2">init</span><span class="se">\.</span><span class="s2">text|</span><span class="se">\.</span><span class="s2">gnu</span><span class="se">\.</span><span class="s2">linkonce"</span>
  <span class="o">[</span>12] .init.text        PROGBITS         0000000000000000  00001000
  <span class="o">[</span>23] .gnu.linkonce.th  PROGBITS         0000000000000000  00003400
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">.gnu.linkonce.this_module</code> section contains the <code class="language-plaintext highlighter-rouge">struct module</code>.</p>

<h3 id="3-disassemble-init-function">3. Disassemble Init Function</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>objdump <span class="nt">-d</span> drivers/cpufreq/rcpufreq_dt.ko | <span class="nb">grep</span> <span class="nt">-A20</span> <span class="s2">"&lt;init_module&gt;:"</span>
0000000000000000 &lt;init_module&gt;:
   0:   push   %rbx
   1:   mov    %rsp,%rbx
   4:   sub    <span class="nv">$0x10</span>,%rsp
   <span class="c"># ... actual Rust code ...</span>
</code></pre></div></div>

<p>This shows the compiled Rust code at the <code class="language-plaintext highlighter-rouge">init_module</code> symbol.</p>

<h3 id="4-verify-module-structure">4. Verify Module Structure</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>readelf <span class="nt">-x</span> .gnu.linkonce.this_module drivers/cpufreq/rcpufreq_dt.ko
<span class="c"># Displays hex dump of the struct module</span>
<span class="c"># Bytes 0x470-0x478 (on 64-bit) contain the init function pointer</span>
</code></pre></div></div>

<h2 id="counter-proof-what-if-c-didnt-call-rust">Counter-Proof: What If C Didn’t Call Rust?</h2>

<p>If the C kernel did NOT call Rust’s <code class="language-plaintext highlighter-rouge">init_module()</code>, then:</p>

<p><strong>Expected failures</strong>:</p>
<ul>
  <li>❌ <code class="language-plaintext highlighter-rouge">insmod rcpufreq_dt.ko</code> would fail</li>
  <li>❌ Module would not initialize</li>
  <li>❌ Driver would not register with the subsystem</li>
  <li>❌ Device would not be managed by the driver</li>
  <li>❌ <code class="language-plaintext highlighter-rouge">lsmod</code> would not show the module as loaded</li>
</ul>

<p><strong>Actual reality</strong>:</p>
<ul>
  <li>✅ Rust modules load successfully</li>
  <li>✅ Drivers initialize and register</li>
  <li>✅ Devices are managed correctly</li>
  <li>✅ <code class="language-plaintext highlighter-rouge">lsmod</code> shows the module</li>
</ul>

<p><strong>Conclusion</strong>: C must be calling Rust’s <code class="language-plaintext highlighter-rouge">init_module()</code>, otherwise none of this would work.</p>

<h2 id="why-limited-to-module-lifecycle">Why Limited to Module Lifecycle?</h2>

<p>The current design restricts C→Rust calls to module initialization and cleanup because:</p>

<h3 id="1-well-defined-interface">1. Well-Defined Interface</h3>

<p>Module lifecycle has a simple, stable signature:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">init</span><span class="p">)(</span><span class="kt">void</span><span class="p">);</span>     <span class="c1">// No parameters, returns error code</span>
<span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">exit</span><span class="p">)(</span><span class="kt">void</span><span class="p">);</span>    <span class="c1">// No parameters, no return value</span>
</code></pre></div></div>

<p>This simplicity means:</p>
<ul>
  <li>No complex ABI negotiations</li>
  <li>No data structure marshaling</li>
  <li>No lifetime management across boundary</li>
  <li>Clear success/failure semantics</li>
</ul>

<h3 id="2-abi-stability">2. ABI Stability</h3>

<p>Only the <strong>entry points</strong> need stable ABI:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">init_module</code> signature: fixed forever</li>
  <li>Internal Rust code: can evolve freely</li>
  <li>No internal Rust APIs exposed to C</li>
</ul>

<p>If C depended on internal Rust APIs, those APIs would need eternal ABI stability.</p>

<h3 id="3-minimal-coupling">3. Minimal Coupling</h3>

<p>The C kernel core does NOT depend on Rust for functionality:</p>
<ul>
  <li>C kernel can load C modules without Rust support</li>
  <li>Rust support is purely additive</li>
  <li>Disabling Rust doesn’t break core kernel</li>
</ul>

<p>This keeps the dependency graph clean:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>C kernel core (independent)
    ↓ (can load)
C modules (independent)
    ↓ (can load)
Rust modules (depend on C kernel APIs)
</code></pre></div></div>

<h3 id="4-standard-module-pattern">4. Standard Module Pattern</h3>

<p>Both C and Rust modules follow the <strong>same loading mechanism</strong>:</p>
<ul>
  <li>Parse ELF</li>
  <li>Map sections</li>
  <li>Resolve relocations</li>
  <li>Call <code class="language-plaintext highlighter-rouge">mod-&gt;init()</code></li>
</ul>

<p>This uniformity means:</p>
<ul>
  <li>No special-case code for Rust</li>
  <li>Same security checks apply</li>
  <li>Same debugging tools work</li>
  <li>Same performance characteristics</li>
</ul>

<h2 id="future-expansion-possibilities">Future Expansion Possibilities</h2>

<p>While currently limited to module lifecycle, C→Rust calls could expand:</p>

<h3 id="1-callback-registration-2027-2028">1. Callback Registration (2027-2028)</h3>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Future possibility</span>
<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">rust_timer_callback</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">c_void</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Safe Rust timer handler</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C code registers Rust callback</span>
<span class="n">setup_timer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">timer</span><span class="p">,</span> <span class="n">rust_timer_callback</span><span class="p">,</span> <span class="n">data</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>Challenges</strong>:</p>
<ul>
  <li>Lifetime management (who owns the data?)</li>
  <li>Error propagation (panic handling)</li>
  <li>ABI stability (callback signatures must be stable)</li>
</ul>

<h3 id="2-subsystem-interfaces-2028-2030">2. Subsystem Interfaces (2028-2030)</h3>

<p>If a core subsystem is rewritten in Rust:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Future: Rust scheduler interface</span>
<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">sched_yield_to</span><span class="p">(</span><span class="n">task</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="n">task_struct</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">c_int</span> <span class="p">{</span>
    <span class="c1">// Safe scheduler implementation</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C code calls Rust scheduler</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">sched_yield_to</span><span class="p">(</span><span class="n">next_task</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>Requirements</strong>:</p>
<ul>
  <li>Proven stability in production</li>
  <li>Performance validation</li>
  <li>Gradual migration path</li>
  <li>Fallback to C implementation</li>
</ul>

<h3 id="3-utility-functions-2026-2027">3. Utility Functions (2026-2027)</h3>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Future: Safe allocator</span>
<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">rust_safe_kmalloc</span><span class="p">(</span>
    <span class="n">size</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="n">flags</span><span class="p">:</span> <span class="n">gfp_t</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">c_void</span> <span class="p">{</span>
    <span class="c1">// Memory-safe allocation with compile-time checks</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Benefits</strong>:</p>
<ul>
  <li>Gradual safety improvements</li>
  <li>No need to rewrite entire subsystems</li>
  <li>Easy to benchmark and validate</li>
</ul>

<h2 id="current-production-reality-2026">Current Production Reality (2026)</h2>

<p>As of Linux kernel 6.x, C→Rust calls are <strong>production reality</strong>:</p>

<p><strong>Active Rust drivers</strong>:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">drivers/net/phy/ax88796b_rust.ko</code> - Network PHY driver</li>
  <li><code class="language-plaintext highlighter-rouge">drivers/net/phy/qt2025.ko</code> - Marvell PHY driver</li>
  <li><code class="language-plaintext highlighter-rouge">drivers/cpufreq/rcpufreq_dt.ko</code> - CPU frequency driver</li>
  <li><code class="language-plaintext highlighter-rouge">drivers/block/rnull.ko</code> - Null block device</li>
  <li><code class="language-plaintext highlighter-rouge">drivers/gpu/drm/nova/*.ko</code> - NVIDIA GPU driver (13 modules)</li>
</ul>

<p><strong>Every one of these is loaded by C calling Rust’s <code class="language-plaintext highlighter-rouge">init_module()</code>.</strong></p>

<p>You can verify this on a running system:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>lsmod | <span class="nb">grep </span>_rust
ax88796b_rust          16384  0
<span class="nv">$ </span>modinfo ax88796b_rust
filename:       /lib/modules/.../ax88796b_rust.ko
license:        GPL
description:    Rust Asix PHYs driver
author:         FUJITA Tomonori
<span class="c"># This module's init_module() was called by C kernel</span>
</code></pre></div></div>

<h2 id="architectural-significance">Architectural Significance</h2>

<p>Understanding that C calls Rust reveals important architectural truths:</p>

<h3 id="1-bidirectional-integration">1. Bidirectional Integration</h3>

<p>The integration is not purely “Rust wraps C”:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Rust → C: For kernel services (most common)
C → Rust: For module lifecycle (critical integration point)
</code></pre></div></div>

<h3 id="2-standard-abi-compliance">2. Standard ABI Compliance</h3>

<p>Rust doesn’t require a special loader or runtime. It complies with:</p>
<ul>
  <li>Standard ELF module format</li>
  <li>Standard System V ABI</li>
  <li>Standard symbol conventions</li>
  <li>Standard linking process</li>
</ul>

<h3 id="3-production-grade-engineering">3. Production-Grade Engineering</h3>

<p>The <code class="language-plaintext highlighter-rouge">#[no_mangle]</code> + <code class="language-plaintext highlighter-rouge">extern "C"</code> pattern shows:</p>
<ul>
  <li>Careful ABI design</li>
  <li>Clear separation of concerns</li>
  <li>Pragmatic integration approach</li>
  <li>No magic or special-casing</li>
</ul>

<h3 id="4-evolution-path">4. Evolution Path</h3>

<p>The module lifecycle integration establishes:</p>
<ul>
  <li>Proven mechanism for C→Rust calls</li>
  <li>Template for future expansion</li>
  <li>Trust in production environment</li>
  <li>Foundation for deeper integration</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p><strong>Yes, C kernel code calls Rust functions</strong> - this is not theoretical but a production reality.</p>

<p><strong>Mechanism</strong>: Standard ELF symbol binding and function pointers</p>
<ul>
  <li>Rust generates C-compatible symbols via <code class="language-plaintext highlighter-rouge">#[no_mangle]</code> and <code class="language-plaintext highlighter-rouge">extern "C"</code></li>
  <li>Linker resolves symbols and populates <code class="language-plaintext highlighter-rouge">struct module</code></li>
  <li>C kernel calls through function pointers</li>
  <li>No runtime lookup, no special handling</li>
</ul>

<p><strong>Scope</strong>: Currently limited to module lifecycle</p>
<ul>
  <li>✅ Module initialization (<code class="language-plaintext highlighter-rouge">init_module</code>, <code class="language-plaintext highlighter-rouge">__&lt;name&gt;_init</code>)</li>
  <li>✅ Module cleanup (<code class="language-plaintext highlighter-rouge">cleanup_module</code>, <code class="language-plaintext highlighter-rouge">__&lt;name&gt;_exit</code>)</li>
  <li>❌ Not used for data processing or core services (yet)</li>
</ul>

<p><strong>Evidence</strong>:</p>
<ul>
  <li>Source code in <code class="language-plaintext highlighter-rouge">rust/macros/module.rs</code> generates the functions</li>
  <li>C code in <code class="language-plaintext highlighter-rouge">kernel/module/main.c</code> calls the functions</li>
  <li>Real drivers (<code class="language-plaintext highlighter-rouge">rcpufreq_dt.ko</code>, <code class="language-plaintext highlighter-rouge">ax88796b_rust.ko</code>) rely on this mechanism</li>
  <li>Working Rust modules prove C must be calling Rust</li>
</ul>

<p><strong>Future</strong>: The infrastructure exists for expansion</p>
<ul>
  <li>Callback registration</li>
  <li>Subsystem interfaces</li>
  <li>Utility functions</li>
</ul>

<p>But for now (2022-2026 phase), the focus is on proving Rust’s reliability in controlled scenarios before expanding the C→Rust interface.</p>

<p><strong>The key insight</strong>: Rust in Linux is not just a consumer of C APIs - it’s a cooperative participant where both languages call each other through well-defined, standard mechanisms.</p>

<hr />

<h1 id="c如何调用rustlinux内核模块生命周期深度剖析">C如何调用Rust：Linux内核模块生命周期深度剖析</h1>

<p><strong>摘要</strong>：本文对C内核代码如何通过模块加载机制调用Rust函数进行全面技术分析。基于Linux内核6.x的实际源代码，本文揭示了完整的证据链：从Rust的#[no_mangle]属性到C的函数指针调用，从ELF符号绑定到实际调用流程。我们证明C→Rust调用不是理论而是通过标准模块生命周期管理实现的生产现实。</p>

<h2 id="引言问题">引言：问题</h2>

<p>在关于Rust在Linux内核中的讨论中，经常出现一个基本的架构问题：</p>

<p><strong>“C内核代码能调用Rust函数吗？”</strong></p>

<p>这不仅仅是学术问题。理解C和Rust之间的调用方向对于理解以下内容至关重要：</p>
<ul>
  <li>集成架构</li>
  <li>ABI稳定性要求</li>
  <li>未来演进可能性</li>
  <li>安全和安全边界</li>
</ul>

<p>许多人认为Rust只是封装C API（单向），使Rust纯粹是C服务的”消费者”。然而，<strong>实际内核源代码揭示了不同的现实</strong>：C确实会调用Rust函数，特别是用于模块生命周期管理。</p>

<p>本文基于Linux内核6.x源代码提供完整的证据链。</p>

<h2 id="答案是的通过模块生命周期">答案：是的，通过模块生命周期</h2>

<p><strong>C内核代码确实调用Rust函数</strong>用于：</p>
<ul>
  <li>✅ 模块初始化（<code class="language-plaintext highlighter-rouge">init_module()</code>、<code class="language-plaintext highlighter-rouge">__&lt;name&gt;_init()</code>）</li>
  <li>✅ 模块清理（<code class="language-plaintext highlighter-rouge">cleanup_module()</code>、<code class="language-plaintext highlighter-rouge">__&lt;name&gt;_exit()</code>）</li>
</ul>

<p><strong>C内核代码不调用Rust用于</strong>：</p>
<ul>
  <li>❌ 数据处理或工具函数</li>
  <li>❌ 核心子系统服务</li>
  <li>❌ 通用API</li>
</ul>

<p>范围<strong>严格限制于模块生命周期管理</strong>，但这是使所有Rust驱动工作的关键集成点。</p>

<h2 id="证据1rust生成c兼容符号">证据1：Rust生成C兼容符号</h2>

<p>每个Rust模块通过<code class="language-plaintext highlighter-rouge">module!</code>宏系列自动生成C可调用函数。这是<code class="language-plaintext highlighter-rouge">rust/macros/module.rs</code>中的实际代码：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// rust/macros/module.rs (260-290行)</span>

<span class="c1">// 对于可加载模块（.ko文件）</span>
<span class="nd">#[cfg(MODULE)]</span>
<span class="nd">#[doc(hidden)]</span>
<span class="nd">#[no_mangle]</span>
<span class="nd">#[link_section</span> <span class="nd">=</span> <span class="s">".init.text"</span><span class="nd">]</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">init_module</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="p">::</span><span class="nn">kernel</span><span class="p">::</span><span class="nn">ffi</span><span class="p">::</span><span class="nb">c_int</span> <span class="p">{</span>
    <span class="c1">// 安全性：由于双层模块包装，此函数对外部不可访问。</span>
    <span class="c1">// C侧通过其唯一名称恰好调用一次。</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="nf">__init</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>

<span class="nd">#[cfg(MODULE)]</span>
<span class="nd">#[doc(hidden)]</span>
<span class="nd">#[no_mangle]</span>
<span class="nd">#[link_section</span> <span class="nd">=</span> <span class="s">".exit.text"</span><span class="nd">]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">cleanup_module</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 安全性：</span>
    <span class="c1">// - 由于双层模块包装，此函数对外部不可访问。</span>
    <span class="c1">//   C侧通过其唯一名称恰好调用一次，</span>
    <span class="c1">// - 而且仅在`init_module`返回`0`后调用（委托给`__init`）。</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="nf">__exit</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// 对于内置模块（编译到内核中）</span>
<span class="nd">#[cfg(not(MODULE))]</span>
<span class="nd">#[doc(hidden)]</span>
<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="n">__</span><span class="o">&lt;</span><span class="n">ident</span><span class="o">&gt;</span><span class="nf">_init</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="p">::</span><span class="nn">kernel</span><span class="p">::</span><span class="nn">ffi</span><span class="p">::</span><span class="nb">c_int</span> <span class="p">{</span>
    <span class="c1">// 安全性：由于双层模块包装，此函数对外部不可访问。</span>
    <span class="c1">// C侧通过其在上述initcall段中的位置恰好调用一次。</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="nf">__init</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>

<span class="nd">#[cfg(not(MODULE))]</span>
<span class="nd">#[doc(hidden)]</span>
<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="n">__</span><span class="o">&lt;</span><span class="n">ident</span><span class="o">&gt;</span><span class="nf">_exit</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="nf">__exit</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="关键机制解释">关键机制解释</h3>

<p><strong>1. <code class="language-plaintext highlighter-rouge">#[no_mangle]</code> 属性</strong></p>

<p>没有此属性，Rust会应用名称改编：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>init_module → _ZN7mymodule11init_module17h&lt;hash&gt;E
</code></pre></div></div>

<p>使用<code class="language-plaintext highlighter-rouge">#[no_mangle]</code>，符号名保持为：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>init_module → init_module
</code></pre></div></div>

<p>这使C代码能够通过其预期的标准名称找到函数。</p>

<p><strong>2. <code class="language-plaintext highlighter-rouge">extern "C"</code> 调用约定</strong></p>

<p>这确保：</p>
<ul>
  <li>参数按照C ABI传递（x86_64上的System V）</li>
  <li>栈帧布局符合C预期</li>
  <li>寄存器使用遵循C调用约定</li>
  <li>没有Rust特定的调用开销</li>
</ul>

<p><strong>3. <code class="language-plaintext highlighter-rouge">#[link_section = ".init.text"]</code></strong></p>

<p>将函数放在ELF <code class="language-plaintext highlighter-rouge">.init.text</code>段中，C内核期望在此找到初始化代码。此段可在初始化完成后释放。</p>

<h2 id="证据2c内核的模块结构">证据2：C内核的模块结构</h2>

<p>C内核定义了一个标准模块结构，持有指向init函数的函数指针：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// include/linux/module.h (第470行)</span>
<span class="k">struct</span> <span class="n">module</span> <span class="p">{</span>
    <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">;</span>

    <span class="c1">// ... 省略许多字段 ...</span>

    <span class="cm">/* Startup function. */</span>
    <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">init</span><span class="p">)(</span><span class="kt">void</span><span class="p">);</span>  <span class="c1">// ← 指向init_module的函数指针</span>

    <span class="k">struct</span> <span class="n">module_memory</span> <span class="n">mem</span><span class="p">[</span><span class="n">MOD_MEM_NUM_TYPES</span><span class="p">]</span> <span class="n">__module_memory_align</span><span class="p">;</span>

    <span class="c1">// ... 更多字段 ...</span>
<span class="p">};</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">init</code>字段是一个<strong>函数指针</strong>，将在模块加载期间被调用。</p>

<h2 id="证据3c内核调用函数指针">证据3：C内核调用函数指针</h2>

<p>加载模块时，C内核显式调用<code class="language-plaintext highlighter-rouge">mod-&gt;init</code>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// kernel/module/main.c (2989-3020行)</span>
<span class="k">static</span> <span class="n">noinline</span> <span class="kt">int</span> <span class="nf">do_init_module</span><span class="p">(</span><span class="k">struct</span> <span class="n">module</span> <span class="o">*</span><span class="n">mod</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">mod_initfree</span> <span class="o">*</span><span class="n">freeinit</span><span class="p">;</span>

    <span class="c1">// ... 省略设置代码 ...</span>

    <span class="n">freeinit</span> <span class="o">=</span> <span class="n">kmalloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">freeinit</span><span class="p">),</span> <span class="n">GFP_KERNEL</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">freeinit</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">ret</span> <span class="o">=</span> <span class="o">-</span><span class="n">ENOMEM</span><span class="p">;</span>
        <span class="k">goto</span> <span class="n">fail</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">freeinit</span><span class="o">-&gt;</span><span class="n">init_text</span> <span class="o">=</span> <span class="n">mod</span><span class="o">-&gt;</span><span class="n">mem</span><span class="p">[</span><span class="n">MOD_INIT_TEXT</span><span class="p">].</span><span class="n">base</span><span class="p">;</span>
    <span class="n">freeinit</span><span class="o">-&gt;</span><span class="n">init_data</span> <span class="o">=</span> <span class="n">mod</span><span class="o">-&gt;</span><span class="n">mem</span><span class="p">[</span><span class="n">MOD_INIT_DATA</span><span class="p">].</span><span class="n">base</span><span class="p">;</span>
    <span class="n">freeinit</span><span class="o">-&gt;</span><span class="n">init_rodata</span> <span class="o">=</span> <span class="n">mod</span><span class="o">-&gt;</span><span class="n">mem</span><span class="p">[</span><span class="n">MOD_INIT_RODATA</span><span class="p">].</span><span class="n">base</span><span class="p">;</span>

    <span class="n">do_mod_ctors</span><span class="p">(</span><span class="n">mod</span><span class="p">);</span>

    <span class="cm">/* Start the module */</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">mod</span><span class="o">-&gt;</span><span class="n">init</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
        <span class="n">ret</span> <span class="o">=</span> <span class="n">do_one_initcall</span><span class="p">(</span><span class="n">mod</span><span class="o">-&gt;</span><span class="n">init</span><span class="p">);</span>  <span class="c1">// ← 调用函数指针</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">goto</span> <span class="n">fail_free_freeinit</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// ... 初始化后代码 ...</span>

    <span class="n">mod</span><span class="o">-&gt;</span><span class="n">state</span> <span class="o">=</span> <span class="n">MODULE_STATE_LIVE</span><span class="p">;</span>

    <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>关键观察</strong>：<code class="language-plaintext highlighter-rouge">do_one_initcall(mod-&gt;init)</code>调用函数指针，对于Rust模块，它指向Rust的<code class="language-plaintext highlighter-rouge">init_module()</code>。</p>

<h2 id="证据4mod-init如何被设置">证据4：mod-&gt;init如何被设置</h2>

<p><strong>关键问题</strong>：<code class="language-plaintext highlighter-rouge">mod-&gt;init</code>如何指向Rust函数？</p>

<p><strong>答案</strong>：通过链接时的ELF符号绑定，而非运行时查找。</p>

<h3 id="elf模块结构布局">ELF模块结构布局</h3>

<p>编译内核模块（C或Rust）时，链接器创建一个特殊段：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.gnu.linkonce.this_module
</code></pre></div></div>

<p>此段包含<code class="language-plaintext highlighter-rouge">struct module</code>的<strong>完整二进制布局</strong>，包括：</p>
<ul>
  <li>模块名</li>
  <li>模块版本</li>
  <li><strong>Init函数指针</strong>（已解析为<code class="language-plaintext highlighter-rouge">init_module</code>地址）</li>
  <li>清理函数指针</li>
  <li>其他元数据</li>
</ul>

<h3 id="模块加载过程">模块加载过程</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// kernel/module/main.c (第2901行)</span>
<span class="k">static</span> <span class="k">struct</span> <span class="n">module</span> <span class="o">*</span><span class="nf">layout_and_allocate</span><span class="p">(</span><span class="k">struct</span> <span class="n">load_info</span> <span class="o">*</span><span class="n">info</span><span class="p">,</span> <span class="kt">int</span> <span class="n">flags</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">struct</span> <span class="n">module</span> <span class="o">*</span><span class="n">mod</span><span class="p">;</span>
    <span class="c1">// ... 布局计算 ...</span>

    <span class="cm">/* Module has been copied to its final place now: return it. */</span>
    <span class="n">mod</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">info</span><span class="o">-&gt;</span><span class="n">sechdrs</span><span class="p">[</span><span class="n">info</span><span class="o">-&gt;</span><span class="n">index</span><span class="p">.</span><span class="n">mod</span><span class="p">].</span><span class="n">sh_addr</span><span class="p">;</span>
    <span class="c1">// ↑ 直接内存映射 - 模块结构体已经完整！</span>

    <span class="n">kmemleak_load_module</span><span class="p">(</span><span class="n">mod</span><span class="p">,</span> <span class="n">info</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">mod</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>内核<strong>不会</strong>手动分配每个字段。相反：</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">.gnu.linkonce.this_module</code>段被映射到内存</li>
  <li>此段<strong>就是</strong><code class="language-plaintext highlighter-rouge">struct module</code></li>
  <li>所有字段，包括<code class="language-plaintext highlighter-rouge">init</code>，<strong>已由链接器设置</strong></li>
</ol>

<h3 id="链接时符号解析">链接时符号解析</h3>

<p>链接Rust模块时：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 简化的链接过程</span>
ld <span class="nt">-r</span> <span class="se">\</span>
  <span class="nt">-o</span> rcpufreq_dt.ko <span class="se">\</span>
  rcpufreq_dt.o <span class="se">\</span>
  <span class="nt">--build-id</span>
</code></pre></div></div>

<p>链接器：</p>
<ol>
  <li>找到<code class="language-plaintext highlighter-rouge">init_module</code>符号（地址0xXXXX）</li>
  <li>将此地址写入<code class="language-plaintext highlighter-rouge">module.init</code>字段</li>
  <li>将完整结构体嵌入<code class="language-plaintext highlighter-rouge">.gnu.linkonce.this_module</code>段</li>
  <li>将所有内容写入<code class="language-plaintext highlighter-rouge">.ko</code>文件</li>
</ol>

<h2 id="证据5真实rust驱动示例">证据5：真实Rust驱动示例</h2>

<p>每个Rust驱动都使用生成这些函数的宏。例如：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// drivers/cpufreq/rcpufreq_dt.rs (215-221行)</span>
<span class="nd">module_platform_driver!</span> <span class="p">{</span>
    <span class="k">type</span><span class="p">:</span> <span class="n">CPUFreqDTDriver</span><span class="p">,</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"cpufreq-dt"</span><span class="p">,</span>
    <span class="n">author</span><span class="p">:</span> <span class="s">"Viresh Kumar &lt;viresh.kumar@linaro.org&gt;"</span><span class="p">,</span>
    <span class="n">description</span><span class="p">:</span> <span class="s">"Generic CPUFreq DT driver"</span><span class="p">,</span>
    <span class="n">license</span><span class="p">:</span> <span class="s">"GPL v2"</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>此宏展开为：</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 生成的代码（概念）</span>
<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">init_module</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">i32</span> <span class="p">{</span>
    <span class="c1">// 注册CPUFreqDTDriver为平台驱动</span>
    <span class="nn">cpufreq</span><span class="p">::</span><span class="nn">Registration</span><span class="p">::</span><span class="o">&lt;</span><span class="n">CPUFreqDTDriver</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">new_foreign_owned</span><span class="p">(</span><span class="cm">/*...*/</span><span class="p">)</span>
<span class="p">}</span>

<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">cleanup_module</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 注销驱动</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="完整调用流程">完整调用流程</h2>

<p>让我们追踪加载Rust模块时发生的事情：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 用户执行：
   $ insmod rcpufreq_dt.ko

2. 内核系统调用：
   SYSCALL_DEFINE3(init_module, void __user *, umod, ...)
   ↓

3. 复制模块到内核内存：
   copy_module_from_user(umod, len, &amp;info)
   ↓

4. 解析ELF并分配：
   mod = layout_and_allocate(&amp;info, flags)
   ↓ (映射.gnu.linkonce.this_module段)

5. mod结构体现在完整：
   mod-&gt;init = &amp;init_module  // ← 已由链接器设置
   mod-&gt;name = "cpufreq-dt"
   // ... 所有字段已填充 ...
   ↓

6. 调用初始化：
   do_init_module(mod)
   ↓

7. 调用函数指针：
   ret = do_one_initcall(mod-&gt;init)
   ↓ (通过函数指针调用)

8. 执行转移到RUST：
   Rust代码中的init_module()执行
   ↓

9. Rust驱动初始化：
   CPUFreqDTDriver::probe()注册驱动
   ↓

10. 模块已激活：
    mod-&gt;state = MODULE_STATE_LIVE
</code></pre></div></div>

<p><strong>关键洞察</strong>：步骤7的C→Rust调用是通过函数指针的<strong>标准间接函数调用</strong>，与调用C模块的init函数完全相同。</p>

<h2 id="符号命名约定">符号命名约定</h2>

<p>内核期望特定的符号名：</p>

<table>
  <thead>
    <tr>
      <th>模块类型</th>
      <th>Init符号</th>
      <th>清理符号</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>可加载（.ko）</td>
      <td><code class="language-plaintext highlighter-rouge">init_module</code></td>
      <td><code class="language-plaintext highlighter-rouge">cleanup_module</code></td>
    </tr>
    <tr>
      <td>内置</td>
      <td><code class="language-plaintext highlighter-rouge">__&lt;name&gt;_init</code></td>
      <td><code class="language-plaintext highlighter-rouge">__&lt;name&gt;_exit</code></td>
    </tr>
  </tbody>
</table>

<p>C和Rust模块都必须遵循此约定。</p>

<h2 id="验证方法">验证方法</h2>

<p>如果您有已编译的Rust内核模块，可以直接验证此机制：</p>

<h3 id="1-检查符号表">1. 检查符号表</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>nm drivers/cpufreq/rcpufreq_dt.ko | <span class="nb">grep </span>init_module
0000000000000000 T init_module
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">T</code>表示<code class="language-plaintext highlighter-rouge">.text</code>段（代码）中的符号。地址<code class="language-plaintext highlighter-rouge">0000000000000000</code>相对于模块基址。</p>

<h3 id="2-检查elf段">2. 检查ELF段</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>readelf <span class="nt">-S</span> drivers/cpufreq/rcpufreq_dt.ko | <span class="nb">grep</span> <span class="nt">-E</span> <span class="s2">"</span><span class="se">\.</span><span class="s2">init</span><span class="se">\.</span><span class="s2">text|</span><span class="se">\.</span><span class="s2">gnu</span><span class="se">\.</span><span class="s2">linkonce"</span>
  <span class="o">[</span>12] .init.text        PROGBITS         0000000000000000  00001000
  <span class="o">[</span>23] .gnu.linkonce.th  PROGBITS         0000000000000000  00003400
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">.gnu.linkonce.this_module</code>段包含<code class="language-plaintext highlighter-rouge">struct module</code>。</p>

<h3 id="3-反汇编init函数">3. 反汇编Init函数</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>objdump <span class="nt">-d</span> drivers/cpufreq/rcpufreq_dt.ko | <span class="nb">grep</span> <span class="nt">-A20</span> <span class="s2">"&lt;init_module&gt;:"</span>
0000000000000000 &lt;init_module&gt;:
   0:   push   %rbx
   1:   mov    %rsp,%rbx
   4:   sub    <span class="nv">$0x10</span>,%rsp
   <span class="c"># ... 实际Rust代码 ...</span>
</code></pre></div></div>

<p>这显示了<code class="language-plaintext highlighter-rouge">init_module</code>符号处编译的Rust代码。</p>

<h2 id="反证如果c不调用rust会怎样">反证：如果C不调用Rust会怎样？</h2>

<p>如果C内核不调用Rust的<code class="language-plaintext highlighter-rouge">init_module()</code>，那么：</p>

<p><strong>预期失败</strong>：</p>
<ul>
  <li>❌ <code class="language-plaintext highlighter-rouge">insmod rcpufreq_dt.ko</code>会失败</li>
  <li>❌ 模块不会初始化</li>
  <li>❌ 驱动不会向子系统注册</li>
  <li>❌ 设备不会由驱动管理</li>
  <li>❌ <code class="language-plaintext highlighter-rouge">lsmod</code>不会显示已加载的模块</li>
</ul>

<p><strong>实际现实</strong>：</p>
<ul>
  <li>✅ Rust模块成功加载</li>
  <li>✅ 驱动初始化并注册</li>
  <li>✅ 设备被正确管理</li>
  <li>✅ <code class="language-plaintext highlighter-rouge">lsmod</code>显示模块</li>
</ul>

<p><strong>结论</strong>：C必定调用了Rust的<code class="language-plaintext highlighter-rouge">init_module()</code>，否则这些都不会工作。</p>

<h2 id="为何限于模块生命周期">为何限于模块生命周期？</h2>

<p>当前设计将C→Rust调用限制于模块初始化和清理，因为：</p>

<h3 id="1-良好定义的接口">1. 良好定义的接口</h3>

<p>模块生命周期具有简单、稳定的签名：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">init</span><span class="p">)(</span><span class="kt">void</span><span class="p">);</span>     <span class="c1">// 无参数，返回错误码</span>
<span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">exit</span><span class="p">)(</span><span class="kt">void</span><span class="p">);</span>    <span class="c1">// 无参数，无返回值</span>
</code></pre></div></div>

<p>这种简单性意味着：</p>
<ul>
  <li>无需复杂的ABI协商</li>
  <li>无需数据结构编组</li>
  <li>无需跨边界生命周期管理</li>
  <li>清晰的成功/失败语义</li>
</ul>

<h3 id="2-abi稳定性">2. ABI稳定性</h3>

<p>只有<strong>入口点</strong>需要稳定的ABI：</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">init_module</code>签名：永远固定</li>
  <li>内部Rust代码：可以自由演进</li>
  <li>无内部Rust API暴露给C</li>
</ul>

<p>如果C依赖内部Rust API，这些API将需要永久的ABI稳定性。</p>

<h3 id="3-最小耦合">3. 最小耦合</h3>

<p>C内核核心不依赖Rust的功能：</p>
<ul>
  <li>C内核可以加载C模块而无需Rust支持</li>
  <li>Rust支持纯粹是增量的</li>
  <li>禁用Rust不会破坏核心内核</li>
</ul>

<p>这保持了依赖图的清晰：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>C内核核心（独立）
    ↓ (可以加载)
C模块（独立）
    ↓ (可以加载)
Rust模块（依赖C内核API）
</code></pre></div></div>

<h3 id="4-标准模块模式">4. 标准模块模式</h3>

<p>C和Rust模块遵循<strong>相同的加载机制</strong>：</p>
<ul>
  <li>解析ELF</li>
  <li>映射段</li>
  <li>解析重定位</li>
  <li>调用<code class="language-plaintext highlighter-rouge">mod-&gt;init()</code></li>
</ul>

<p>这种统一性意味着：</p>
<ul>
  <li>Rust无需特殊处理代码</li>
  <li>应用相同的安全检查</li>
  <li>相同的调试工具有效</li>
  <li>相同的性能特性</li>
</ul>

<h2 id="未来扩展可能性">未来扩展可能性</h2>

<p>虽然目前限于模块生命周期，C→Rust调用可能扩展：</p>

<h3 id="1-回调注册2027-2028">1. 回调注册（2027-2028）</h3>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 未来可能性</span>
<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">rust_timer_callback</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">c_void</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 安全的Rust定时器处理程序</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C代码注册Rust回调</span>
<span class="n">setup_timer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">timer</span><span class="p">,</span> <span class="n">rust_timer_callback</span><span class="p">,</span> <span class="n">data</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>挑战</strong>：</p>
<ul>
  <li>生命周期管理（谁拥有数据？）</li>
  <li>错误传播（panic处理）</li>
  <li>ABI稳定性（回调签名必须稳定）</li>
</ul>

<h3 id="2-子系统接口2028-2030">2. 子系统接口（2028-2030）</h3>

<p>如果核心子系统用Rust重写：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 未来：Rust调度器接口</span>
<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">sched_yield_to</span><span class="p">(</span><span class="n">task</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="n">task_struct</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">c_int</span> <span class="p">{</span>
    <span class="c1">// 安全的调度器实现</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C代码调用Rust调度器</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">sched_yield_to</span><span class="p">(</span><span class="n">next_task</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>要求</strong>：</p>
<ul>
  <li>在生产中证明稳定性</li>
  <li>性能验证</li>
  <li>渐进式迁移路径</li>
  <li>回退到C实现</li>
</ul>

<h3 id="3-工具函数2026-2027">3. 工具函数（2026-2027）</h3>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 未来：安全分配器</span>
<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">rust_safe_kmalloc</span><span class="p">(</span>
    <span class="n">size</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="n">flags</span><span class="p">:</span> <span class="n">gfp_t</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">c_void</span> <span class="p">{</span>
    <span class="c1">// 具有编译时检查的内存安全分配</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>好处</strong>：</p>
<ul>
  <li>渐进式安全改进</li>
  <li>无需重写整个子系统</li>
  <li>易于基准测试和验证</li>
</ul>

<h2 id="当前生产现实2026">当前生产现实（2026）</h2>

<p>截至Linux内核6.x，C→Rust调用是<strong>生产现实</strong>：</p>

<p><strong>活跃的Rust驱动</strong>：</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">drivers/net/phy/ax88796b_rust.ko</code> - 网络PHY驱动</li>
  <li><code class="language-plaintext highlighter-rouge">drivers/net/phy/qt2025.ko</code> - Marvell PHY驱动</li>
  <li><code class="language-plaintext highlighter-rouge">drivers/cpufreq/rcpufreq_dt.ko</code> - CPU频率驱动</li>
  <li><code class="language-plaintext highlighter-rouge">drivers/block/rnull.ko</code> - Null块设备</li>
  <li><code class="language-plaintext highlighter-rouge">drivers/gpu/drm/nova/*.ko</code> - NVIDIA GPU驱动（13个模块）</li>
</ul>

<p><strong>这些都是通过C调用Rust的<code class="language-plaintext highlighter-rouge">init_module()</code>加载的。</strong></p>

<p>您可以在运行的系统上验证：</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>lsmod | <span class="nb">grep </span>_rust
ax88796b_rust          16384  0
<span class="nv">$ </span>modinfo ax88796b_rust
filename:       /lib/modules/.../ax88796b_rust.ko
license:        GPL
description:    Rust Asix PHYs driver
author:         FUJITA Tomonori
<span class="c"># 此模块的init_module()由C内核调用</span>
</code></pre></div></div>

<h2 id="架构意义">架构意义</h2>

<p>理解C调用Rust揭示了重要的架构真相：</p>

<h3 id="1-双向集成">1. 双向集成</h3>

<p>集成不是纯粹的”Rust封装C”：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Rust → C：用于内核服务（最常见）
C → Rust：用于模块生命周期（关键集成点）
</code></pre></div></div>

<h3 id="2-标准abi合规">2. 标准ABI合规</h3>

<p>Rust不需要特殊加载器或运行时。它符合：</p>
<ul>
  <li>标准ELF模块格式</li>
  <li>标准System V ABI</li>
  <li>标准符号约定</li>
  <li>标准链接过程</li>
</ul>

<h3 id="3-生产级工程">3. 生产级工程</h3>

<p><code class="language-plaintext highlighter-rouge">#[no_mangle]</code> + <code class="language-plaintext highlighter-rouge">extern "C"</code>模式显示：</p>
<ul>
  <li>精心的ABI设计</li>
  <li>清晰的关注点分离</li>
  <li>务实的集成方法</li>
  <li>无魔法或特殊处理</li>
</ul>

<h3 id="4-演进路径">4. 演进路径</h3>

<p>模块生命周期集成建立了：</p>
<ul>
  <li>经过验证的C→Rust调用机制</li>
  <li>未来扩展的模板</li>
  <li>在生产环境中的信任</li>
  <li>更深入集成的基础</li>
</ul>

<h2 id="结论">结论</h2>

<p><strong>是的，C内核代码调用Rust函数</strong> - 这不是理论而是生产现实。</p>

<p><strong>机制</strong>：标准ELF符号绑定和函数指针</p>
<ul>
  <li>Rust通过<code class="language-plaintext highlighter-rouge">#[no_mangle]</code>和<code class="language-plaintext highlighter-rouge">extern "C"</code>生成C兼容符号</li>
  <li>链接器解析符号并填充<code class="language-plaintext highlighter-rouge">struct module</code></li>
  <li>C内核通过函数指针调用</li>
  <li>无运行时查找，无特殊处理</li>
</ul>

<p><strong>范围</strong>：目前限于模块生命周期</p>
<ul>
  <li>✅ 模块初始化（<code class="language-plaintext highlighter-rouge">init_module</code>、<code class="language-plaintext highlighter-rouge">__&lt;name&gt;_init</code>）</li>
  <li>✅ 模块清理（<code class="language-plaintext highlighter-rouge">cleanup_module</code>、<code class="language-plaintext highlighter-rouge">__&lt;name&gt;_exit</code>）</li>
  <li>❌ 尚未用于数据处理或核心服务</li>
</ul>

<p><strong>证据</strong>：</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">rust/macros/module.rs</code>中的源代码生成函数</li>
  <li><code class="language-plaintext highlighter-rouge">kernel/module/main.c</code>中的C代码调用函数</li>
  <li>真实驱动（<code class="language-plaintext highlighter-rouge">rcpufreq_dt.ko</code>、<code class="language-plaintext highlighter-rouge">ax88796b_rust.ko</code>）依赖此机制</li>
  <li>工作的Rust模块证明C必定调用Rust</li>
</ul>

<p><strong>未来</strong>：扩展基础设施已存在</p>
<ul>
  <li>回调注册</li>
  <li>子系统接口</li>
  <li>工具函数</li>
</ul>

<p>但目前（2022-2026阶段），重点是在扩展C→Rust接口之前，在受控场景中证明Rust的可靠性。</p>

<p><strong>关键洞察</strong>：Linux中的Rust不仅仅是C API的消费者 - 它是一个合作参与者，两种语言通过良好定义的标准机制相互调用。</p>]]></content><author><name>阿男</name></author><summary type="html"><![CDATA[本文为英文存档，已不再主推；本站后续内容以中文技术长文为主。 配套视频见 B站频道。]]></summary></entry><entry xml:lang="en"><title type="html">TcpRest: Reviving a 2012 RPC Framework with AI-Assisted Development</title><link href="https://weinan.tech/2026/02/18/tcprest-revival-with-ai.html" rel="alternate" type="text/html" title="TcpRest: Reviving a 2012 RPC Framework with AI-Assisted Development" /><published>2026-02-18T00:00:00+08:00</published><updated>2026-02-18T00:00:00+08:00</updated><id>https://weinan.tech/2026/02/18/tcprest-revival-with-ai</id><content type="html" xml:base="https://weinan.tech/2026/02/18/tcprest-revival-with-ai.html"><![CDATA[<blockquote>
  <p>本文为英文存档，已不再主推；本站后续内容以中文技术长文为主。 配套视频见 <a href="https://space.bilibili.com/21947620">B站频道</a>。</p>
</blockquote>

<p>A 14-year journey from experimental project to production-ready framework. How AI tools transformed legacy code into a modern, modular, zero-dependency RPC solution.</p>

<h2 id="english-version">English Version</h2>

<h3 id="the-journey-from-2012-to-2026">The Journey: From 2012 to 2026</h3>

<p>In 2012, I created TcpRest as an experimental RPC (Remote Procedure Call) framework. The concept was simple but powerful: transform Plain Old Java Objects (POJOs) into network-accessible services over TCP, without the overhead of HTTP. At the time, it was a learning exercise exploring how to build lightweight RPC mechanisms in Java.</p>

<p>For over a decade, the project sat unmaintained - a time capsule of 2012-era Java development practices. Then, in 2024-2026, something changed: the emergence of AI-powered development tools like GitHub Copilot and Claude made it possible to revive and modernize this codebase in ways that would have taken months of manual work.</p>

<p><strong>Project Link:</strong> <a href="https://github.com/liweinan/tcprest">https://github.com/liweinan/tcprest</a></p>

<h3 id="what-changed-the-ai-assisted-renaissance">What Changed: The AI-Assisted Renaissance</h3>

<h4 id="1-bug-fixes-and-code-quality">1. <strong>Bug Fixes and Code Quality</strong></h4>

<p>The first phase involved systematically identifying and fixing bugs that had accumulated over the years. AI tools accelerated this process by:</p>

<ul>
  <li><strong>Pattern detection</strong>: Identifying similar bugs across the codebase</li>
  <li><strong>Test generation</strong>: Creating comprehensive test cases to catch edge cases</li>
  <li><strong>Refactoring suggestions</strong>: Proposing cleaner implementations for problematic code</li>
</ul>

<p>Example improvements:</p>
<ul>
  <li>Fixed null pointer handling in protocol parsing</li>
  <li>Resolved thread safety issues in the original server implementation</li>
  <li>Corrected resource cleanup in connection handling</li>
</ul>

<h4 id="2-modular-architecture-refactoring">2. <strong>Modular Architecture Refactoring</strong></h4>

<p>The original monolithic structure was split into focused Maven modules, each with a clear purpose:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tcprest-parent/
├── tcprest-commons/      # Zero-dependency core (protocol, client, mappers)
├── tcprest-singlethread/ # Simple blocking I/O server with SSL
├── tcprest-nio/          # Non-blocking I/O server (no SSL)
└── tcprest-netty/        # High-performance Netty server with SSL
</code></pre></div></div>

<p><strong>Key principle:</strong> The <code class="language-plaintext highlighter-rouge">tcprest-commons</code> module has <strong>zero runtime dependencies</strong> - only JDK built-in APIs. This minimizes dependency conflicts and security vulnerabilities.</p>

<p>This modular design allows developers to choose exactly what they need:</p>
<ul>
  <li><strong>Client-only applications</strong>: Just include <code class="language-plaintext highlighter-rouge">tcprest-commons</code> (zero deps)</li>
  <li><strong>Low-concurrency server</strong>: Add <code class="language-plaintext highlighter-rouge">tcprest-singlethread</code> with SSL support</li>
  <li><strong>High-concurrency production</strong>: Use <code class="language-plaintext highlighter-rouge">tcprest-netty</code> for thousands of concurrent connections</li>
</ul>

<h4 id="3-protocol-v2-with-modern-features">3. <strong>Protocol v2 with Modern Features</strong></h4>

<p>The original protocol was extended to support modern Java development needs:</p>

<p><strong>Method Overloading Support:</strong></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">Calculator</span> <span class="o">{</span>
    <span class="kt">int</span> <span class="nf">add</span><span class="o">(</span><span class="kt">int</span> <span class="n">a</span><span class="o">,</span> <span class="kt">int</span> <span class="n">b</span><span class="o">);</span>           <span class="c1">// Integer addition</span>
    <span class="kt">double</span> <span class="nf">add</span><span class="o">(</span><span class="kt">double</span> <span class="n">a</span><span class="o">,</span> <span class="kt">double</span> <span class="n">b</span><span class="o">);</span>   <span class="c1">// Double addition</span>
    <span class="nc">String</span> <span class="nf">add</span><span class="o">(</span><span class="nc">String</span> <span class="n">a</span><span class="o">,</span> <span class="nc">String</span> <span class="n">b</span><span class="o">);</span>   <span class="c1">// String concatenation</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>Proper Exception Propagation:</strong></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Server throws exception</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">validateAge</span><span class="o">(</span><span class="kt">int</span> <span class="n">age</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">age</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="o">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">ValidationException</span><span class="o">(</span><span class="s">"Age must be non-negative"</span><span class="o">);</span>
<span class="o">}</span>

<span class="c1">// Client receives it</span>
<span class="k">try</span> <span class="o">{</span>
    <span class="n">service</span><span class="o">.</span><span class="na">validateAge</span><span class="o">(-</span><span class="mi">1</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">RuntimeException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
    <span class="c1">// Exception message preserved across the wire</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="4-data-compression">4. <strong>Data Compression</strong></h4>

<p>GZIP compression was added to reduce bandwidth usage, with smart threshold-based activation:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">server</span><span class="o">.</span><span class="na">enableCompression</span><span class="o">();</span>  <span class="c1">// Auto-compress messages &gt; 512 bytes</span>

<span class="c1">// Or customize</span>
<span class="nc">CompressionConfig</span> <span class="n">config</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CompressionConfig</span><span class="o">(</span>
    <span class="kc">true</span><span class="o">,</span>   <span class="c1">// enabled</span>
    <span class="mi">1024</span><span class="o">,</span>   <span class="c1">// only compress if message &gt; 1KB</span>
    <span class="mi">9</span>       <span class="c1">// compression level (1=fastest, 9=best)</span>
<span class="o">);</span>
</code></pre></div></div>

<p>Benchmark results show 85-96% reduction for text-heavy payloads.</p>

<h4 id="5-ssltls-security">5. <strong>SSL/TLS Security</strong></h4>

<p>Production-grade security was added:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Server with mutual TLS</span>
<span class="nc">SSLParam</span> <span class="n">serverSSL</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SSLParam</span><span class="o">();</span>
<span class="n">serverSSL</span><span class="o">.</span><span class="na">setKeyStorePath</span><span class="o">(</span><span class="s">"classpath:server_ks"</span><span class="o">);</span>
<span class="n">serverSSL</span><span class="o">.</span><span class="na">setNeedClientAuth</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>  <span class="c1">// Require client certificate</span>

<span class="nc">TcpRestServer</span> <span class="n">server</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">NettyTcpRestServer</span><span class="o">(</span><span class="mi">8443</span><span class="o">,</span> <span class="n">sslParam</span><span class="o">);</span>
</code></pre></div></div>

<h4 id="6-comprehensive-documentation">6. <strong>Comprehensive Documentation</strong></h4>

<p>AI tools helped generate three detailed documentation files:</p>
<ul>
  <li><strong>PROTOCOL.md</strong>: Wire protocol specification and compatibility</li>
  <li><strong>ARCHITECTURE.md</strong>: Design decisions and implementation details</li>
  <li><strong>CLAUDE.md</strong>: Development guidelines and coding standards</li>
</ul>

<h4 id="7-dependency-updates">7. <strong>Dependency Updates</strong></h4>

<p>All dependencies were updated to their latest stable versions:</p>
<ul>
  <li>Java 11+ (from Java 1.7)</li>
  <li>Netty 4.1.131.Final (high-performance networking)</li>
  <li>TestNG 7.12.0 (modern testing framework)</li>
  <li>SLF4J 2.0.16 (logging facade)</li>
</ul>

<h3 id="performance-characteristics">Performance Characteristics</h3>

<p>TcpRest offers significant advantages over traditional HTTP REST:</p>

<table>
  <thead>
    <tr>
      <th>Aspect</th>
      <th>HTTP REST</th>
      <th>TcpRest (Netty)</th>
      <th>Improvement</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Protocol Overhead</strong></td>
      <td>200-300 bytes</td>
      <td>50-100 bytes</td>
      <td>60-80% reduction</td>
    </tr>
    <tr>
      <td><strong>Serialization</strong></td>
      <td>JSON text</td>
      <td>Binary/Custom</td>
      <td>50-70% smaller</td>
    </tr>
    <tr>
      <td><strong>Compression</strong></td>
      <td>Usually disabled</td>
      <td>Optional GZIP</td>
      <td>80-95% reduction</td>
    </tr>
    <tr>
      <td><strong>Latency</strong></td>
      <td>3-6ms</td>
      <td>0.6-0.9ms</td>
      <td>3-10x faster</td>
    </tr>
    <tr>
      <td><strong>Concurrency</strong></td>
      <td>~1000 threads</td>
      <td>~10-20 threads</td>
      <td>10-50x better</td>
    </tr>
  </tbody>
</table>

<p><strong>Best for</strong>: Microservice internal communication, high-concurrency scenarios (10k+ connections), low-latency requirements (&lt;5ms).</p>

<h3 id="technical-highlights">Technical Highlights</h3>

<h4 id="zero-copy-serialization">Zero-Copy Serialization</h4>

<p>Classes implementing <code class="language-plaintext highlighter-rouge">Serializable</code> work automatically without custom mappers:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">User</span> <span class="kd">implements</span> <span class="nc">Serializable</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="kt">int</span> <span class="n">id</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kd">transient</span> <span class="nc">String</span> <span class="n">password</span><span class="o">;</span>  <span class="c1">// Auto-excluded</span>
<span class="o">}</span>

<span class="c1">// No mapper needed!</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">UserService</span> <span class="o">{</span>
    <span class="nc">User</span> <span class="nf">getUser</span><span class="o">(</span><span class="kt">int</span> <span class="n">id</span><span class="o">);</span>
    <span class="nc">List</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;</span> <span class="nf">getAllUsers</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="network-binding-for-security">Network Binding for Security</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Production: Bind to specific IP (not 0.0.0.0)</span>
<span class="nc">TcpRestServer</span> <span class="n">server</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">NettyTcpRestServer</span><span class="o">(</span><span class="mi">8443</span><span class="o">,</span> <span class="s">"127.0.0.1"</span><span class="o">,</span> <span class="n">sslParam</span><span class="o">);</span>
</code></pre></div></div>

<h4 id="backward-compatibility">Backward Compatibility</h4>

<p>The server can accept both Protocol v1 and v2 clients simultaneously:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">server</span><span class="o">.</span><span class="na">setProtocolVersion</span><span class="o">(</span><span class="nc">ProtocolVersion</span><span class="o">.</span><span class="na">AUTO</span><span class="o">);</span>  <span class="c1">// Default</span>
</code></pre></div></div>

<h3 id="the-role-of-ai-in-this-revival">The Role of AI in This Revival</h3>

<p>AI tools didn’t just “write code” - they acted as:</p>

<ol>
  <li><strong>Architectural consultants</strong>: Suggesting modular structures and design patterns</li>
  <li><strong>Test engineers</strong>: Generating comprehensive test suites with edge cases</li>
  <li><strong>Documentation writers</strong>: Creating clear, detailed technical documentation</li>
  <li><strong>Code reviewers</strong>: Identifying anti-patterns and suggesting improvements</li>
  <li><strong>Migration assistants</strong>: Helping upgrade dependencies and APIs</li>
</ol>

<p><strong>Key insight</strong>: The human role shifted from “writing code” to “architectural design, requirement analysis, and quality control.” I defined <strong>what needed to be done</strong>, and AI accelerated <strong>how it got done</strong>.</p>

<h3 id="what-this-demonstrates">What This Demonstrates</h3>

<p>This project is a case study in how AI tools are reshaping software development:</p>

<ul>
  <li><strong>Legacy code revival</strong>: Projects that would have been abandoned can be modernized</li>
  <li><strong>Documentation debt payoff</strong>: Comprehensive docs become feasible</li>
  <li><strong>Testing coverage</strong>: Achieving thorough test coverage becomes practical</li>
  <li><strong>Refactoring confidence</strong>: Large-scale restructuring becomes less risky</li>
</ul>

<p><strong>The future</strong>: Developers become “AI conductors” - focusing on architecture, requirements, and quality while delegating implementation details to AI collaborators.</p>

<h3 id="try-it-yourself">Try It Yourself</h3>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- Maven dependency --&gt;</span>
<span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>cn.huiwings<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>tcprest-netty<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>1.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Server</span>
<span class="nc">TcpRestServer</span> <span class="n">server</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">NettyTcpRestServer</span><span class="o">(</span><span class="mi">8001</span><span class="o">);</span>
<span class="n">server</span><span class="o">.</span><span class="na">addSingletonResource</span><span class="o">(</span><span class="k">new</span> <span class="nc">MyServiceImpl</span><span class="o">());</span>
<span class="n">server</span><span class="o">.</span><span class="na">up</span><span class="o">();</span>

<span class="c1">// Client</span>
<span class="nc">TcpRestClientFactory</span> <span class="n">factory</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">TcpRestClientFactory</span><span class="o">(</span>
    <span class="nc">MyService</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="s">"localhost"</span><span class="o">,</span> <span class="mi">8001</span>
<span class="o">);</span>
<span class="nc">MyService</span> <span class="n">client</span> <span class="o">=</span> <span class="n">factory</span><span class="o">.</span><span class="na">getClient</span><span class="o">();</span>
<span class="n">client</span><span class="o">.</span><span class="na">myMethod</span><span class="o">();</span>  <span class="c1">// Transparent RPC!</span>
</code></pre></div></div>

<h3 id="conclusion">Conclusion</h3>

<p>TcpRest’s journey from a 2012 experiment to a 2026 production-ready framework demonstrates the transformative power of AI-assisted development. What would have required months of tedious refactoring, testing, and documentation work was accomplished in weeks through human-AI collaboration.</p>

<p>The result is not just a modernized codebase, but a genuinely useful framework for high-performance RPC scenarios where HTTP overhead is unacceptable.</p>

<p><strong>The lesson</strong>: Good ideas don’t have to die. With AI tools, legacy projects can find new life.</p>

<hr />

<h2 id="中文版本">中文版本</h2>

<h3 id="旅程从2012到2026">旅程：从2012到2026</h3>

<p>2012年，我创建了TcpRest作为一个实验性的RPC（远程过程调用）框架。这个想法简单但强大：将普通的Java对象（POJOs）转换为通过TCP网络访问的服务，无需HTTP的开销。当时，这只是一个探索如何在Java中构建轻量级RPC机制的学习练习。</p>

<p>十多年来，这个项目一直没有维护——成为了2012年时代Java开发实践的时间胶囊。然后，在2024-2026年，情况发生了变化：GitHub Copilot和Claude等AI驱动的开发工具的出现，使得以一种原本需要数月手动工作才能完成的方式来复兴和现代化这个代码库成为可能。</p>

<p><strong>项目链接:</strong> <a href="https://github.com/liweinan/tcprest">https://github.com/liweinan/tcprest</a></p>

<h3 id="改变了什么ai辅助的文艺复兴">改变了什么：AI辅助的文艺复兴</h3>

<h4 id="1-bug修复和代码质量提升">1. <strong>Bug修复和代码质量提升</strong></h4>

<p>第一阶段涉及系统地识别和修复多年来积累的bug。AI工具通过以下方式加速了这个过程：</p>

<ul>
  <li><strong>模式检测</strong>：识别代码库中的类似bug</li>
  <li><strong>测试生成</strong>：创建全面的测试用例以捕获边界情况</li>
  <li><strong>重构建议</strong>：为有问题的代码提出更清晰的实现</li>
</ul>

<p>改进示例：</p>
<ul>
  <li>修复了协议解析中的空指针处理</li>
  <li>解决了原始服务器实现中的线程安全问题</li>
  <li>纠正了连接处理中的资源清理问题</li>
</ul>

<h4 id="2-模块化架构重构">2. <strong>模块化架构重构</strong></h4>

<p>原始的单体结构被拆分为专注的Maven模块，每个模块都有明确的目的：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tcprest-parent/
├── tcprest-commons/      # 零依赖核心（协议、客户端、映射器）
├── tcprest-singlethread/ # 简单的阻塞I/O服务器，支持SSL
├── tcprest-nio/          # 非阻塞I/O服务器（不支持SSL）
└── tcprest-netty/        # 高性能Netty服务器，支持SSL
</code></pre></div></div>

<p><strong>核心原则：</strong> <code class="language-plaintext highlighter-rouge">tcprest-commons</code>模块<strong>零运行时依赖</strong>——仅使用JDK内置API。这最大限度地减少了依赖冲突和安全漏洞。</p>

<p>这种模块化设计允许开发者精确选择他们需要的内容：</p>
<ul>
  <li><strong>纯客户端应用</strong>：只需包含<code class="language-plaintext highlighter-rouge">tcprest-commons</code>（零依赖）</li>
  <li><strong>低并发服务器</strong>：添加<code class="language-plaintext highlighter-rouge">tcprest-singlethread</code>，支持SSL</li>
  <li><strong>高并发生产环境</strong>：使用<code class="language-plaintext highlighter-rouge">tcprest-netty</code>处理数千个并发连接</li>
</ul>

<h4 id="3-具有现代特性的protocol-v2">3. <strong>具有现代特性的Protocol v2</strong></h4>

<p>原始协议被扩展以支持现代Java开发需求：</p>

<p><strong>方法重载支持：</strong></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">Calculator</span> <span class="o">{</span>
    <span class="kt">int</span> <span class="nf">add</span><span class="o">(</span><span class="kt">int</span> <span class="n">a</span><span class="o">,</span> <span class="kt">int</span> <span class="n">b</span><span class="o">);</span>           <span class="c1">// 整数加法</span>
    <span class="kt">double</span> <span class="nf">add</span><span class="o">(</span><span class="kt">double</span> <span class="n">a</span><span class="o">,</span> <span class="kt">double</span> <span class="n">b</span><span class="o">);</span>   <span class="c1">// 双精度加法</span>
    <span class="nc">String</span> <span class="nf">add</span><span class="o">(</span><span class="nc">String</span> <span class="n">a</span><span class="o">,</span> <span class="nc">String</span> <span class="n">b</span><span class="o">);</span>   <span class="c1">// 字符串连接</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>正确的异常传播：</strong></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 服务器抛出异常</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">validateAge</span><span class="o">(</span><span class="kt">int</span> <span class="n">age</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">age</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="o">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">ValidationException</span><span class="o">(</span><span class="s">"年龄必须非负"</span><span class="o">);</span>
<span class="o">}</span>

<span class="c1">// 客户端接收异常</span>
<span class="k">try</span> <span class="o">{</span>
    <span class="n">service</span><span class="o">.</span><span class="na">validateAge</span><span class="o">(-</span><span class="mi">1</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">RuntimeException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
    <span class="c1">// 异常消息通过网络保留</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="4-数据压缩">4. <strong>数据压缩</strong></h4>

<p>添加了GZIP压缩以减少带宽使用，并具有智能的基于阈值的激活：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">server</span><span class="o">.</span><span class="na">enableCompression</span><span class="o">();</span>  <span class="c1">// 自动压缩大于512字节的消息</span>

<span class="c1">// 或自定义</span>
<span class="nc">CompressionConfig</span> <span class="n">config</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CompressionConfig</span><span class="o">(</span>
    <span class="kc">true</span><span class="o">,</span>   <span class="c1">// 启用</span>
    <span class="mi">1024</span><span class="o">,</span>   <span class="c1">// 仅当消息&gt;1KB时压缩</span>
    <span class="mi">9</span>       <span class="c1">// 压缩级别（1=最快，9=最佳）</span>
<span class="o">);</span>
</code></pre></div></div>

<p>基准测试结果显示，对于文本密集型负载，压缩率为85-96%。</p>

<h4 id="5-ssltls安全性">5. <strong>SSL/TLS安全性</strong></h4>

<p>添加了生产级安全性：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 带双向TLS的服务器</span>
<span class="nc">SSLParam</span> <span class="n">serverSSL</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SSLParam</span><span class="o">();</span>
<span class="n">serverSSL</span><span class="o">.</span><span class="na">setKeyStorePath</span><span class="o">(</span><span class="s">"classpath:server_ks"</span><span class="o">);</span>
<span class="n">serverSSL</span><span class="o">.</span><span class="na">setNeedClientAuth</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>  <span class="c1">// 要求客户端证书</span>

<span class="nc">TcpRestServer</span> <span class="n">server</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">NettyTcpRestServer</span><span class="o">(</span><span class="mi">8443</span><span class="o">,</span> <span class="n">sslParam</span><span class="o">);</span>
</code></pre></div></div>

<h4 id="6-全面的文档">6. <strong>全面的文档</strong></h4>

<p>AI工具帮助生成了三个详细的文档文件：</p>
<ul>
  <li><strong>PROTOCOL.md</strong>：线协议规范和兼容性</li>
  <li><strong>ARCHITECTURE.md</strong>：设计决策和实现细节</li>
  <li><strong>CLAUDE.md</strong>：开发指南和编码标准</li>
</ul>

<h4 id="7-依赖更新">7. <strong>依赖更新</strong></h4>

<p>所有依赖都更新到了最新的稳定版本：</p>
<ul>
  <li>Java 11+（从Java 1.7）</li>
  <li>Netty 4.1.131.Final（高性能网络）</li>
  <li>TestNG 7.12.0（现代测试框架）</li>
  <li>SLF4J 2.0.16（日志门面）</li>
</ul>

<h3 id="性能特征">性能特征</h3>

<p>TcpRest相比传统的HTTP REST具有显著优势：</p>

<table>
  <thead>
    <tr>
      <th>方面</th>
      <th>HTTP REST</th>
      <th>TcpRest (Netty)</th>
      <th>改进</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>协议开销</strong></td>
      <td>200-300字节</td>
      <td>50-100字节</td>
      <td>减少60-80%</td>
    </tr>
    <tr>
      <td><strong>序列化</strong></td>
      <td>JSON文本</td>
      <td>二进制/自定义</td>
      <td>减小50-70%</td>
    </tr>
    <tr>
      <td><strong>压缩</strong></td>
      <td>通常禁用</td>
      <td>可选GZIP</td>
      <td>减少80-95%</td>
    </tr>
    <tr>
      <td><strong>延迟</strong></td>
      <td>3-6ms</td>
      <td>0.6-0.9ms</td>
      <td>快3-10倍</td>
    </tr>
    <tr>
      <td><strong>并发性</strong></td>
      <td>~1000线程</td>
      <td>~10-20线程</td>
      <td>好10-50倍</td>
    </tr>
  </tbody>
</table>

<p><strong>最适合</strong>：微服务内部通信、高并发场景（10k+连接）、低延迟要求（&lt;5ms）。</p>

<h3 id="技术亮点">技术亮点</h3>

<h4 id="零拷贝序列化">零拷贝序列化</h4>

<p>实现<code class="language-plaintext highlighter-rouge">Serializable</code>的类无需自定义映射器即可自动工作：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">User</span> <span class="kd">implements</span> <span class="nc">Serializable</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="kt">int</span> <span class="n">id</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kd">transient</span> <span class="nc">String</span> <span class="n">password</span><span class="o">;</span>  <span class="c1">// 自动排除</span>
<span class="o">}</span>

<span class="c1">// 无需映射器！</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">UserService</span> <span class="o">{</span>
    <span class="nc">User</span> <span class="nf">getUser</span><span class="o">(</span><span class="kt">int</span> <span class="n">id</span><span class="o">);</span>
    <span class="nc">List</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;</span> <span class="nf">getAllUsers</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="网络绑定以提高安全性">网络绑定以提高安全性</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 生产环境：绑定到特定IP（而非0.0.0.0）</span>
<span class="nc">TcpRestServer</span> <span class="n">server</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">NettyTcpRestServer</span><span class="o">(</span><span class="mi">8443</span><span class="o">,</span> <span class="s">"127.0.0.1"</span><span class="o">,</span> <span class="n">sslParam</span><span class="o">);</span>
</code></pre></div></div>

<h4 id="向后兼容性">向后兼容性</h4>

<p>服务器可以同时接受Protocol v1和v2客户端：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">server</span><span class="o">.</span><span class="na">setProtocolVersion</span><span class="o">(</span><span class="nc">ProtocolVersion</span><span class="o">.</span><span class="na">AUTO</span><span class="o">);</span>  <span class="c1">// 默认</span>
</code></pre></div></div>

<h3 id="ai在这次复兴中的角色">AI在这次复兴中的角色</h3>

<p>AI工具不仅仅是”编写代码”——它们充当了：</p>

<ol>
  <li><strong>架构顾问</strong>：建议模块化结构和设计模式</li>
  <li><strong>测试工程师</strong>：生成包含边界情况的全面测试套件</li>
  <li><strong>文档撰写者</strong>：创建清晰、详细的技术文档</li>
  <li><strong>代码审查者</strong>：识别反模式并提出改进建议</li>
  <li><strong>迁移助手</strong>：帮助升级依赖和API</li>
</ol>

<p><strong>关键见解</strong>：人类的角色从”编写代码”转变为”架构设计、需求分析和质量控制”。我定义了<strong>需要做什么</strong>，AI加速了<strong>如何完成</strong>。</p>

<h3 id="这展示了什么">这展示了什么</h3>

<p>这个项目是AI工具如何重塑软件开发的案例研究：</p>

<ul>
  <li><strong>遗留代码复兴</strong>：本来会被废弃的项目可以被现代化</li>
  <li><strong>文档债务偿还</strong>：全面的文档变得可行</li>
  <li><strong>测试覆盖率</strong>：实现彻底的测试覆盖变得实用</li>
  <li><strong>重构信心</strong>：大规模重构变得风险更小</li>
</ul>

<p><strong>未来</strong>：开发者成为”AI指挥者”——专注于架构、需求和质量，同时将实现细节委托给AI协作者。</p>

<h3 id="试一试">试一试</h3>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- Maven依赖 --&gt;</span>
<span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>cn.huiwings<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>tcprest-netty<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>1.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 服务器</span>
<span class="nc">TcpRestServer</span> <span class="n">server</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">NettyTcpRestServer</span><span class="o">(</span><span class="mi">8001</span><span class="o">);</span>
<span class="n">server</span><span class="o">.</span><span class="na">addSingletonResource</span><span class="o">(</span><span class="k">new</span> <span class="nc">MyServiceImpl</span><span class="o">());</span>
<span class="n">server</span><span class="o">.</span><span class="na">up</span><span class="o">();</span>

<span class="c1">// 客户端</span>
<span class="nc">TcpRestClientFactory</span> <span class="n">factory</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">TcpRestClientFactory</span><span class="o">(</span>
    <span class="nc">MyService</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="s">"localhost"</span><span class="o">,</span> <span class="mi">8001</span>
<span class="o">);</span>
<span class="nc">MyService</span> <span class="n">client</span> <span class="o">=</span> <span class="n">factory</span><span class="o">.</span><span class="na">getClient</span><span class="o">();</span>
<span class="n">client</span><span class="o">.</span><span class="na">myMethod</span><span class="o">();</span>  <span class="c1">// 透明的RPC！</span>
</code></pre></div></div>

<h3 id="结论">结论</h3>

<p>TcpRest从2012年的实验到2026年生产就绪框架的旅程，展示了AI辅助开发的变革力量。原本需要数月繁琐的重构、测试和文档工作，通过人机协作在几周内完成。</p>

<p>结果不仅仅是现代化的代码库，而是一个真正有用的框架，适用于HTTP开销不可接受的高性能RPC场景。</p>

<p><strong>教训</strong>：好的想法不必消亡。借助AI工具，遗留项目可以焕发新生。</p>

<h2 id="references">References</h2>

<ul>
  <li><strong>Project Repository</strong>: <a href="https://github.com/liweinan/tcprest">https://github.com/liweinan/tcprest</a></li>
  <li><strong>Protocol Documentation</strong>: <a href="https://github.com/liweinan/tcprest/blob/main/PROTOCOL.md">PROTOCOL.md</a></li>
  <li><strong>Architecture Guide</strong>: <a href="https://github.com/liweinan/tcprest/blob/main/ARCHITECTURE.md">ARCHITECTURE.md</a></li>
  <li><strong>Development Guidelines</strong>: <a href="https://github.com/liweinan/tcprest/blob/main/CLAUDE.md">CLAUDE.md</a></li>
</ul>]]></content><author><name>阿男</name></author><summary type="html"><![CDATA[本文为英文存档，已不再主推；本站后续内容以中文技术长文为主。 配套视频见 B站频道。]]></summary></entry><entry><title type="html">解剖Tyr：Linux首个Rust GPU驱动的代码实战分析</title><link href="https://weinan.tech/2026/02/18/tyr-rust-gpu-driver-anatomy.html" rel="alternate" type="text/html" title="解剖Tyr：Linux首个Rust GPU驱动的代码实战分析" /><published>2026-02-18T00:00:00+08:00</published><updated>2026-02-18T00:00:00+08:00</updated><id>https://weinan.tech/2026/02/18/tyr-rust-gpu-driver-anatomy</id><content type="html" xml:base="https://weinan.tech/2026/02/18/tyr-rust-gpu-driver-anatomy.html"><![CDATA[<p>2025年9月，Linux内核合并了首个Rust GPU驱动Tyr（commit cf4fd52e3236），标志着Rust在内核图形子系统的正式落地。本文通过剖析Tyr的实际代码，展示Rust GPU驱动的架构设计、DRM抽象层的具体实现，以及从Panthor（C）移植到Tyr（Rust）的关键挑战。这是Rust在Linux内核从抽象到实战的完整技术案例。</p>

<h2 id="引言从理论到代码">引言：从理论到代码</h2>

<p>在前两篇文章中，我们分析了Rust在Linux内核的整体状态和ABI稳定性<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup><sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>。这些讨论主要停留在宏观层面：代码统计、政策争议、技术保证。但<strong>实际的Rust内核代码长什么样？如何与C内核交互？遇到了哪些具体挑战？</strong></p>

<p>本文通过解剖<strong>Tyr项目</strong>——Linux内核首个合并的Rust GPU驱动——来回答这些问题。我们将：</p>

<ol>
  <li><strong>分析实际代码</strong>：基于commit cf4fd52e3236的真实代码</li>
  <li><strong>对比C/Rust实现</strong>：Panthor（C）vs Tyr（Rust）</li>
  <li><strong>揭示技术挑战</strong>：为何上游代码如此精简？</li>
  <li><strong>理解DRM抽象层</strong>：<code class="language-plaintext highlighter-rouge">rust/kernel/drm/</code>如何工作？</li>
</ol>

<p>这不是一篇科普文章，而是<strong>代码级的技术剖析</strong>。</p>

<hr />

<h2 id="背景知识gpu驱动与drm子系统">背景知识：GPU驱动与DRM子系统</h2>

<h3 id="gpu驱动的双层架构">GPU驱动的双层架构</h3>

<p>在Linux中，GPU驱动分为两个部分：</p>

<p><strong>1. 内核模式驱动（Kernel-mode Driver）</strong></p>
<ul>
  <li>位置：Linux内核的<code class="language-plaintext highlighter-rouge">drivers/gpu/drm/</code>目录</li>
  <li>职责：
    <ul>
      <li>管理GPU硬件资源</li>
      <li>提供内存分配和映射</li>
      <li>处理多进程的GPU访问调度</li>
      <li>电源管理和故障恢复</li>
    </ul>
  </li>
  <li><strong>Tyr就是内核模式驱动</strong></li>
</ul>

<p><strong>2. 用户模式驱动（Userspace Driver）</strong></p>
<ul>
  <li>典型代表：Mesa（实现OpenGL/Vulkan）</li>
  <li>职责：
    <ul>
      <li>实现图形API（OpenGL、Vulkan等）</li>
      <li>将API调用翻译为GPU命令</li>
      <li>着色器编译</li>
    </ul>
  </li>
  <li>通过ioctl与内核驱动通信</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────┐
│   游戏/应用程序              │
└──────────┬──────────────────┘
           │ OpenGL/Vulkan API
           ↓
┌─────────────────────────────┐
│   Mesa (用户模式驱动)        │
│   - panfrost_dri.so (Panthor)│
└──────────┬──────────────────┘
           │ ioctl系统调用
           ↓
┌─────────────────────────────┐
│   Tyr (内核模式驱动)         │ ← 本文重点
│   drivers/gpu/drm/tyr/      │
└──────────┬──────────────────┘
           │ 硬件寄存器操作
           ↓
┌─────────────────────────────┐
│   Mali GPU 硬件              │
└─────────────────────────────┘
</code></pre></div></div>

<h3 id="什么是drm子系统">什么是DRM子系统？</h3>

<p><strong>DRM（Direct Rendering Manager）</strong> 是Linux内核的图形子系统，管理所有GPU驱动。</p>

<p><strong>核心组件</strong>：</p>

<ol>
  <li><strong>DRM Core</strong>（<code class="language-plaintext highlighter-rouge">drivers/gpu/drm/drm_*.c</code>）
    <ul>
      <li>提供通用GPU管理框架</li>
      <li>处理显示模式设置（KMS）</li>
      <li>管理图形内存（GEM）</li>
    </ul>
  </li>
  <li><strong>GEM（Graphics Execution Manager）</strong>
    <ul>
      <li>GPU内存对象管理</li>
      <li>处理CPU/GPU内存共享</li>
      <li>管理用户空间映射（mmap）</li>
    </ul>
  </li>
  <li><strong>GPUVM（GPU Virtual Address Management）</strong>
    <ul>
      <li>GPU虚拟地址空间管理</li>
      <li>类似CPU的虚拟内存</li>
      <li>支持多进程GPU内存隔离</li>
    </ul>
  </li>
  <li><strong>GPU调度器</strong>（drm_gpu_scheduler）
    <ul>
      <li>管理GPU任务队列</li>
      <li>处理任务依赖关系</li>
      <li>实现公平调度</li>
    </ul>
  </li>
</ol>

<p><strong>学习资源</strong>：</p>
<ul>
  <li><a href="https://docs.kernel.org/gpu/drm-internals.html">DRM Internals Documentation</a> - 官方内核文档</li>
  <li><a href="https://bootlin.com/doc/training/graphics/graphics-slides.pdf">Linux Graphics Stack Overview</a> - Bootlin培训材料</li>
  <li><a href="https://01.org/linuxgraphics/gfx-docs/drm/">DRM/KMS Overview</a> - Intel图形文档</li>
</ul>

<h3 id="arm-mali-gpu架构">ARM Mali GPU架构</h3>

<p><strong>Mali GPU家族</strong>：</p>

<table>
  <thead>
    <tr>
      <th>架构</th>
      <th>代表型号</th>
      <th>特点</th>
      <th>Tyr支持</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Midgard</strong></td>
      <td>Mali-T760</td>
      <td>早期架构</td>
      <td>❌</td>
    </tr>
    <tr>
      <td><strong>Bifrost</strong></td>
      <td>Mali-G71, G52</td>
      <td>引入四边形着色器</td>
      <td>❌</td>
    </tr>
    <tr>
      <td><strong>Valhall</strong></td>
      <td>Mali-G77, G78</td>
      <td>超标量引擎</td>
      <td>✅</td>
    </tr>
    <tr>
      <td><strong>Valhall CSF</strong></td>
      <td><strong>Mali-G610, G710</strong></td>
      <td>命令流前端</td>
      <td>✅ <strong>Tyr目标</strong></td>
    </tr>
  </tbody>
</table>

<p><strong>CSF（Command Stream Frontend）架构</strong>：</p>
<ul>
  <li>GPU固件（MCU）直接管理任务调度</li>
  <li>驱动通过命令流与固件通信</li>
  <li>减轻CPU负担，提高效率</li>
</ul>

<p><strong>Mali GPU硬件结构</strong>：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────────────┐
│  MCU (Microcontroller Unit)        │
│  - Cortex-M7核心 @ GHz             │
│  - 运行固件，管理GPU调度            │
└──────────┬──────────────────────────┘
           │ 内部总线
┌──────────┴──────────────────────────┐
│  Shader Cores (着色器核心)          │
│  - 执行计算/图形任务                │
│  - 多核并行（8-32核心不等）          │
└──────────┬──────────────────────────┘
           │
┌──────────┴──────────────────────────┐
│  L2 Cache + Memory System           │
│  - 共享L2缓存                       │
│  - MMU（内存管理单元）               │
└─────────────────────────────────────┘
</code></pre></div></div>

<p><strong>MCU固件的关键作用</strong>：</p>
<ul>
  <li><strong>任务调度</strong>：决定哪个任务在哪个核心执行</li>
  <li><strong>电源管理</strong>：动态开关核心和调节频率</li>
  <li><strong>故障恢复</strong>：检测和处理GPU挂起</li>
</ul>

<p><strong>学习资源</strong>：</p>
<ul>
  <li><a href="https://developer.arm.com/Processors/Mali-G610">ARM Mali GPU Datasheet</a> - 官方技术文档</li>
  <li><a href="https://docs.mesa3d.org/drivers/panfrost.html">Panfrost Driver Documentation</a> - Mesa的Mali开源驱动文档</li>
  <li><a href="https://community.arm.com/arm-community-blogs/b/graphics-gaming-and-vr-blog">Mali GPU Architecture</a> - ARM官方博客</li>
</ul>

<h3 id="为什么要用rust重写gpu驱动">为什么要用Rust重写GPU驱动？</h3>

<p><strong>GPU驱动的复杂性</strong>：</p>

<ol>
  <li><strong>海量内存操作</strong>：
    <ul>
      <li>CPU/GPU共享内存</li>
      <li>用户空间映射（mmap）</li>
      <li>DMA传输</li>
      <li><strong>常见bug</strong>：use-after-free、double-free</li>
    </ul>
  </li>
  <li><strong>并发密集</strong>：
    <ul>
      <li>多进程同时访问GPU</li>
      <li>中断处理</li>
      <li>任务队列管理</li>
      <li><strong>常见bug</strong>：数据竞争、死锁</li>
    </ul>
  </li>
  <li><strong>用户空间交互频繁</strong>：
    <ul>
      <li>ioctl暴露大量攻击面</li>
      <li>需要严格验证用户输入</li>
      <li><strong>常见bug</strong>：权限提升漏洞</li>
    </ul>
  </li>
</ol>

<p><strong>历史数据</strong>（来自前文<sup id="fnref:1:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>）：</p>
<ul>
  <li>Linux内核CVE中，<strong>约70%是内存安全问题</strong></li>
  <li>GPU驱动是CVE高发区</li>
</ul>

<p><strong>Rust的解决方案</strong>：</p>

<table>
  <thead>
    <tr>
      <th>问题类别</th>
      <th>C的困境</th>
      <th>Rust的保证</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>内存安全</td>
      <td>手动管理，易出错</td>
      <td>所有权系统，编译时检查</td>
    </tr>
    <tr>
      <td>并发安全</td>
      <td>锁靠约定</td>
      <td>借用检查器，编译时防数据竞争</td>
    </tr>
    <tr>
      <td>资源泄漏</td>
      <td>手动cleanup</td>
      <td>RAII自动管理</td>
    </tr>
    <tr>
      <td>空指针</td>
      <td>运行时崩溃</td>
      <td><code class="language-plaintext highlighter-rouge">Option&lt;T&gt;</code>编译时消除</td>
    </tr>
  </tbody>
</table>

<p><strong>Greg Kroah-Hartman（内核维护者）的评价</strong><sup id="fnref:1:2"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>：</p>
<blockquote>
  <p>“The majority of bugs we have are due to the stupid little corner cases in C that are totally gone in Rust.”</p>
</blockquote>

<h3 id="panthor-vs-tyr移植关系">Panthor vs Tyr：移植关系</h3>

<p><strong>Panthor</strong>是Mali CSF GPU的<strong>C驱动</strong>（已上游）：</p>
<ul>
  <li>位置：<code class="language-plaintext highlighter-rouge">drivers/gpu/drm/panthor/</code></li>
  <li>作者：Collabora工程师（Boris Brezillon等）</li>
  <li>状态：生产就绪，功能完整</li>
</ul>

<p><strong>Tyr</strong>是<strong>Panthor的Rust移植</strong>：</p>
<ul>
  <li>目标：功能对等（feature parity）</li>
  <li>策略：暴露相同的uAPI（用户空间API），兼容Mesa</li>
  <li>当前状态：基础功能，依赖GPUVM等抽象完善</li>
</ul>

<p><strong>为什么不直接用Panthor？</strong></p>
<ol>
  <li><strong>技术演进</strong>：验证Rust在GPU驱动的可行性</li>
  <li><strong>安全提升</strong>：消除Panthor的潜在内存安全bug</li>
  <li><strong>生态建设</strong>：为其他GPU驱动提供Rust参考</li>
</ol>

<hr />

<h2 id="快速入门如何学习gpu驱动开发">快速入门：如何学习GPU驱动开发</h2>

<h3 id="前置知识">前置知识</h3>

<p><strong>必备基础</strong>：</p>
<ol>
  <li>✅ C语言（指针、结构体、位操作）</li>
  <li>✅ Linux系统编程（系统调用、设备驱动基础）</li>
  <li>✅ 计算机体系结构（虚拟内存、DMA、中断）</li>
</ol>

<p><strong>Rust特有</strong>：</p>
<ol>
  <li>✅ 所有权和借用</li>
  <li>✅ 生命周期</li>
  <li>✅ unsafe Rust（FFI互操作）</li>
</ol>

<h3 id="学习路径推荐顺序">学习路径（推荐顺序）</h3>

<p><strong>第1步：DRM基础</strong>（2-3周）</p>
<ul>
  <li>📚 <a href="https://docs.kernel.org/gpu/drm-kms.html">DRM Driver Development Guide</a></li>
  <li>💻 实践：编译并加载简单DRM驱动（vkms）</li>
  <li>🎯 目标：理解GEM对象、ioctl处理流程</li>
</ul>

<p><strong>第2步：Rust内核编程</strong>（3-4周）</p>
<ul>
  <li>📚 <a href="https://rust-for-linux.com/">Rust for Linux官方文档</a></li>
  <li>📚 <a href="https://github.com/rust-for-linux/linux/tree/rust/samples/rust">Kernel Module in Rust</a></li>
  <li>💻 实践：编写简单的Rust platform驱动</li>
  <li>🎯 目标：理解<code class="language-plaintext highlighter-rouge">Pin</code>, <code class="language-plaintext highlighter-rouge">Opaque</code>, <code class="language-plaintext highlighter-rouge">#[pin_data]</code>等内核特有概念</li>
</ul>

<p><strong>第3步：阅读现有代码</strong>（持续）</p>
<ul>
  <li>📖 <strong>rvkms</strong>（最简单的Rust DRM驱动）</li>
  <li>📖 <strong>Nova</strong>（完整的Rust GPU驱动，Nvidia GSP）</li>
  <li>📖 <strong>Tyr</strong>（本文重点）</li>
  <li>📖 <strong>Asahi</strong>（Apple Silicon GPU，最成熟）</li>
</ul>

<p><strong>第4步：理解GPU硬件</strong>（按需）</p>
<ul>
  <li>📚 <a href="https://developer.arm.com/documentation/102849/latest/">Mali GPU Architecture</a></li>
  <li>📚 <a href="https://gitlab.freedesktop.org/panfrost">Panfrost Wiki</a>（Mali开源驱动项目）</li>
  <li>🎯 目标：理解着色器核心、MMU、MCU固件</li>
</ul>

<h3 id="关键资源汇总">关键资源汇总</h3>

<p><strong>官方文档</strong>：</p>
<ul>
  <li><a href="https://docs.kernel.org/gpu/">Linux DRM Documentation</a> - 内核DRM子系统文档</li>
  <li><a href="https://rust-for-linux.com/">Rust for Linux</a> - 官方项目网站</li>
  <li><a href="https://dri.freedesktop.org/wiki/">freedesktop.org DRM</a> - 社区Wiki</li>
</ul>

<p><strong>代码仓库</strong>：</p>
<ul>
  <li><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git">Linux Kernel</a></li>
  <li><a href="https://gitlab.freedesktop.org/drm/rust/kernel">DRM Rust Tree</a> - Rust DRM开发树</li>
  <li><a href="https://gitlab.freedesktop.org/mesa/mesa">Mesa</a> - 用户空间驱动</li>
</ul>

<p><strong>社区资源</strong>：</p>
<ul>
  <li><a href="https://lore.kernel.org/rust-for-linux/">Rust for Linux邮件列表</a></li>
  <li><a href="irc://irc.oftc.net/dri-devel">DRM开发者IRC</a> - #dri-devel频道</li>
  <li><a href="https://www.collabora.com/news-and-blog/">Collabora博客</a> - Tyr团队的技术博客</li>
</ul>

<p><strong>书籍推荐</strong>：</p>
<ul>
  <li>《Linux Device Drivers》（3rd Edition）- 经典驱动开发书籍</li>
  <li>《Programming Rust》（2nd Edition）- Rust语言深入</li>
  <li>《The Rust Reference》- Rust语言规范</li>
</ul>

<h3 id="从哪里开始贡献">从哪里开始贡献？</h3>

<p><strong>难度递增的任务</strong>：</p>

<ol>
  <li><strong>⭐ 初级</strong>：
    <ul>
      <li>为Rust抽象添加文档注释</li>
      <li>修复编译警告</li>
      <li>添加单元测试</li>
    </ul>
  </li>
  <li><strong>⭐⭐ 中级</strong>：
    <ul>
      <li>实现缺失的寄存器定义</li>
      <li>添加新的GPU型号支持</li>
      <li>改进错误处理</li>
    </ul>
  </li>
  <li><strong>⭐⭐⭐ 高级</strong>：
    <ul>
      <li>开发GPUVM Rust抽象</li>
      <li>实现GPU调度器</li>
      <li>移植其他GPU驱动到Rust</li>
    </ul>
  </li>
</ol>

<p><strong>如何参与</strong>：</p>
<ol>
  <li>订阅Rust for Linux邮件列表</li>
  <li>在GitLab上关注DRM Rust项目</li>
  <li>参与代码审查（学习最快的方式！）</li>
  <li>从小patch开始提交</li>
</ol>

<hr />

<h2 id="tyr项目概览第一手资料">Tyr项目概览：第一手资料</h2>

<h3 id="git-commit信息">Git Commit信息</h3>

<p><strong>提交哈希</strong>：<code class="language-plaintext highlighter-rouge">cf4fd52e3236</code>
<strong>作者</strong>：Daniel Almeida <a href="mailto:daniel.almeida@collabora.com">daniel.almeida@collabora.com</a>
<strong>日期</strong>：2025年9月10日
<strong>合作方</strong>：Collabora、Arm、Google</p>

<p><strong>Commit message核心摘录</strong>（原文）<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>：</p>

<blockquote>
  <p>Add a Rust driver for ARM Mali CSF-based GPUs. It is a port of Panthor and therefore exposes Panthor’s uAPI and name to userspace, and the product of a joint effort between Collabora, Arm and Google engineers.</p>
</blockquote>

<blockquote>
  <p>The downstream code is capable of <strong>booting the MCU, doing sync VM_BINDS</strong> through the work-in-progress GPUVM abstraction and also doing <strong>(trivial) submits</strong> through Asahi’s drm_scheduler and dma_fence abstractions.</p>
</blockquote>

<blockquote>
  <p><strong>This first patch, however, only implements a subset</strong> of the current features available downstream, as the rest is not implementable without pulling in even more abstractions. In particular, a lot of things depend on properly mapping memory on a given VA range, which itself <strong>depends on the GPUVM abstraction that is currently work-in-progress</strong>. For this reason, <strong>we still cannot boot the MCU</strong> and thus, cannot do much for the moment.</p>
</blockquote>

<h3 id="关键信息解读">关键信息解读</h3>

<ol>
  <li><strong>下游分支功能完整</strong>：
    <ul>
      <li>✅ MCU启动（Mali GPU的微控制器）</li>
      <li>✅ 同步VM_BINDS（虚拟内存绑定）</li>
      <li>✅ 基础任务提交</li>
    </ul>
  </li>
  <li><strong>上游代码受限</strong>：
    <ul>
      <li>❌ 无法启动MCU</li>
      <li>❌ GPUVM抽象缺失</li>
      <li>❌ 只能查询GPU信息</li>
    </ul>
  </li>
  <li><strong>战略转变</strong>：
    <ul>
      <li>之前尝试C+Rust混合（失败）</li>
      <li>现在改为纯Rust，分阶段上游</li>
    </ul>
  </li>
</ol>

<hr />

<h2 id="tyr代码结构实际文件布局">Tyr代码结构：实际文件布局</h2>

<h3 id="代码树基于commit-cf4fd52e3236">代码树（基于commit cf4fd52e3236）</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>drivers/gpu/drm/tyr/
├── tyr.rs        # 模块入口，platform_driver声明
├── driver.rs     # 驱动核心，TyrDriver和TyrData实现
├── file.rs       # DRM file操作，处理用户空间连接
├── gem.rs        # GEM对象管理
├── gpu.rs        # GPU信息查询（GpuInfo结构体）
├── regs.rs       # GPU寄存器定义和访问
├── Kconfig       # 内核配置选项
└── Makefile      # 构建配置
</code></pre></div></div>

<p><strong>对比Panthor（C驱动）</strong>：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cd</span> /Users/weli/works/linux
<span class="nv">$ </span><span class="nb">ls </span>drivers/gpu/drm/panthor/
panthor_devfreq.c  panthor_fw.c   panthor_gem.c  panthor_gpu.c
panthor_device.c   panthor_fw.h   panthor_gem.h  panthor_gpu.h
panthor_device.h   panthor_heap.c panthor_mmu.c  panthor_regs.h
...（共24个文件）
</code></pre></div></div>

<p><strong>Tyr更精简</strong>：8个文件 vs Panthor的24个文件。但这并非优势，而是<strong>功能缺失</strong>的体现。</p>

<hr />

<h2 id="代码分析1tyr驱动入口">代码分析1：Tyr驱动入口</h2>

<h3 id="文件driversgpudrmtyrtyrrs">文件：<code class="language-plaintext highlighter-rouge">drivers/gpu/drm/tyr/tyr.rs</code></h3>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// SPDX-License-Identifier: GPL-2.0 or MIT</span>

<span class="cd">//! Arm Mali Tyr DRM driver.</span>
<span class="cd">//!</span>
<span class="cd">//! The name "Tyr" is inspired by Norse mythology, reflecting Arm's tradition of</span>
<span class="cd">//! naming their GPUs after Nordic mythological figures and places.</span>

<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">driver</span><span class="p">::</span><span class="n">TyrDriver</span><span class="p">;</span>

<span class="k">mod</span> <span class="n">driver</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">file</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">gem</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">gpu</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">regs</span><span class="p">;</span>

<span class="nn">kernel</span><span class="p">::</span><span class="nd">module_platform_driver!</span> <span class="p">{</span>
    <span class="k">type</span><span class="p">:</span> <span class="n">TyrDriver</span><span class="p">,</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"tyr"</span><span class="p">,</span>
    <span class="n">authors</span><span class="p">:</span> <span class="p">[</span><span class="s">"The Tyr driver authors"</span><span class="p">],</span>
    <span class="n">description</span><span class="p">:</span> <span class="s">"Arm Mali Tyr DRM driver"</span><span class="p">,</span>
    <span class="n">license</span><span class="p">:</span> <span class="s">"Dual MIT/GPL"</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>关键点</strong>：</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">module_platform_driver!</code> 宏</strong>：
    <ul>
      <li>自动生成平台驱动注册代码</li>
      <li>等价于C中的<code class="language-plaintext highlighter-rouge">module_platform_driver(tyr_driver)</code></li>
    </ul>
  </li>
  <li><strong>模块组织</strong>：
    <ul>
      <li>清晰的模块划分（driver、file、gem、gpu、regs）</li>
      <li>私有模块，不暴露内部细节</li>
    </ul>
  </li>
</ol>

<p><strong>对比C版本</strong>（<code class="language-plaintext highlighter-rouge">panthor_drv.c</code>）：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">struct</span> <span class="n">platform_driver</span> <span class="n">panthor_driver</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">.</span><span class="n">probe</span> <span class="o">=</span> <span class="n">panthor_probe</span><span class="p">,</span>
    <span class="p">.</span><span class="n">remove</span> <span class="o">=</span> <span class="n">panthor_remove</span><span class="p">,</span>
    <span class="p">.</span><span class="n">driver</span> <span class="o">=</span> <span class="p">{</span>
        <span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">"panthor"</span><span class="p">,</span>
        <span class="p">.</span><span class="n">pm</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">panthor_pm_ops</span><span class="p">,</span>
        <span class="p">.</span><span class="n">of_match_table</span> <span class="o">=</span> <span class="n">dt_match</span><span class="p">,</span>
    <span class="p">},</span>
<span class="p">};</span>
<span class="n">module_platform_driver</span><span class="p">(</span><span class="n">panthor_driver</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>Rust的优势</strong>：</p>
<ul>
  <li>类型安全：<code class="language-plaintext highlighter-rouge">type: TyrDriver</code>编译时检查</li>
  <li>生命周期自动管理：probe/remove的资源管理通过RAII</li>
</ul>

<hr />

<h2 id="代码分析2驱动核心实现">代码分析2：驱动核心实现</h2>

<h3 id="文件driversgpudrmtyrdriverrs部分">文件：<code class="language-plaintext highlighter-rouge">drivers/gpu/drm/tyr/driver.rs</code>（部分）</h3>

<h4 id="21-设备树匹配">2.1 设备树匹配</h4>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">kernel</span><span class="p">::</span><span class="nd">of_device_table!</span><span class="p">(</span>
    <span class="n">OF_TABLE</span><span class="p">,</span>
    <span class="n">MODULE_OF_TABLE</span><span class="p">,</span>
    <span class="o">&lt;</span><span class="n">TyrDriver</span> <span class="k">as</span> <span class="nn">platform</span><span class="p">::</span><span class="n">Driver</span><span class="o">&gt;</span><span class="p">::</span><span class="n">IdInfo</span><span class="p">,</span>
    <span class="p">[</span>
        <span class="p">(</span><span class="nn">of</span><span class="p">::</span><span class="nn">DeviceId</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nd">c_str!</span><span class="p">(</span><span class="s">"rockchip,rk3588-mali"</span><span class="p">)),</span> <span class="p">()),</span>
        <span class="p">(</span><span class="nn">of</span><span class="p">::</span><span class="nn">DeviceId</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nd">c_str!</span><span class="p">(</span><span class="s">"arm,mali-valhall-csf"</span><span class="p">)),</span> <span class="p">())</span>
    <span class="p">]</span>
<span class="p">);</span>
</code></pre></div></div>

<p><strong>解释</strong>：</p>
<ul>
  <li>支持Rockchip RK3588 SoC的Mali GPU</li>
  <li>兼容ARM Mali Valhall CSF架构</li>
  <li><code class="language-plaintext highlighter-rouge">c_str!</code>宏：编译时C字符串，零运行时开销</li>
</ul>

<p><strong>对比C版本</strong>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">of_device_id</span> <span class="n">dt_match</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">{</span> <span class="p">.</span><span class="n">compatible</span> <span class="o">=</span> <span class="s">"arm,mali-valhall-csf"</span> <span class="p">},</span>
    <span class="p">{</span> <span class="p">.</span><span class="n">compatible</span> <span class="o">=</span> <span class="s">"rockchip,rk3588-mali"</span> <span class="p">},</span>
    <span class="p">{}</span>
<span class="p">};</span>
<span class="n">MODULE_DEVICE_TABLE</span><span class="p">(</span><span class="n">of</span><span class="p">,</span> <span class="n">dt_match</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>Rust的类型安全</strong>：</p>
<ul>
  <li>编译时检查字符串有效性</li>
  <li><code class="language-plaintext highlighter-rouge">of::DeviceId::new</code>确保格式正确</li>
</ul>

<h4 id="22-驱动数据结构">2.2 驱动数据结构</h4>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[pin_data(PinnedDrop)]</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">TyrData</span> <span class="p">{</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">pdev</span><span class="p">:</span> <span class="n">ARef</span><span class="o">&lt;</span><span class="nn">platform</span><span class="p">::</span><span class="n">Device</span><span class="o">&gt;</span><span class="p">,</span>

    <span class="nd">#[pin]</span>
    <span class="n">clks</span><span class="p">:</span> <span class="n">Mutex</span><span class="o">&lt;</span><span class="n">Clocks</span><span class="o">&gt;</span><span class="p">,</span>

    <span class="nd">#[pin]</span>
    <span class="n">regulators</span><span class="p">:</span> <span class="n">Mutex</span><span class="o">&lt;</span><span class="n">Regulators</span><span class="o">&gt;</span><span class="p">,</span>

    <span class="cd">/// Some information on the GPU.</span>
    <span class="cd">///</span>
    <span class="cd">/// This is mainly queried by userspace, i.e.: Mesa.</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">gpu_info</span><span class="p">:</span> <span class="n">GpuInfo</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>关键设计</strong>：</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">#[pin_data]</code> 属性</strong>：
    <ul>
      <li>保证内存不移动（pin到堆上）</li>
      <li>必需，因为C代码可能持有指针</li>
    </ul>
  </li>
  <li><strong><code class="language-plaintext highlighter-rouge">ARef&lt;platform::Device&gt;</code></strong>：
    <ul>
      <li>引用计数的平台设备</li>
      <li>等价于C中的<code class="language-plaintext highlighter-rouge">struct platform_device *</code></li>
    </ul>
  </li>
  <li><strong><code class="language-plaintext highlighter-rouge">Mutex&lt;Clocks&gt;</code> 和 <code class="language-plaintext highlighter-rouge">Mutex&lt;Regulators&gt;</code></strong>：
    <ul>
      <li>内核互斥锁，保护共享资源</li>
      <li><code class="language-plaintext highlighter-rouge">#[pin]</code>：这些字段不能移动</li>
    </ul>
  </li>
</ol>

<h4 id="23-初始化流程probe函数">2.3 初始化流程（probe函数）</h4>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">impl</span> <span class="nn">platform</span><span class="p">::</span><span class="n">Driver</span> <span class="k">for</span> <span class="n">TyrDriver</span> <span class="p">{</span>
    <span class="k">type</span> <span class="n">IdInfo</span> <span class="o">=</span> <span class="p">();</span>
    <span class="k">const</span> <span class="n">OF_ID_TABLE</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="nn">of</span><span class="p">::</span><span class="n">IdTable</span><span class="o">&lt;</span><span class="k">Self</span><span class="p">::</span><span class="n">IdInfo</span><span class="o">&gt;&gt;</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="o">&amp;</span><span class="n">OF_TABLE</span><span class="p">);</span>

    <span class="k">fn</span> <span class="nf">probe</span><span class="p">(</span>
        <span class="n">pdev</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">platform</span><span class="p">::</span><span class="n">Device</span><span class="o">&lt;</span><span class="n">Core</span><span class="o">&gt;</span><span class="p">,</span>
        <span class="n">_info</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;&amp;</span><span class="k">Self</span><span class="p">::</span><span class="n">IdInfo</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">Pin</span><span class="o">&lt;</span><span class="n">KBox</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;&gt;&gt;</span> <span class="p">{</span>
        <span class="c1">// 1. 获取时钟</span>
        <span class="k">let</span> <span class="n">core_clk</span> <span class="o">=</span> <span class="nn">Clk</span><span class="p">::</span><span class="nf">get</span><span class="p">(</span><span class="n">pdev</span><span class="nf">.as_ref</span><span class="p">(),</span> <span class="nf">Some</span><span class="p">(</span><span class="nd">c_str!</span><span class="p">(</span><span class="s">"core"</span><span class="p">)))</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">stacks_clk</span> <span class="o">=</span> <span class="nn">OptionalClk</span><span class="p">::</span><span class="nf">get</span><span class="p">(</span><span class="n">pdev</span><span class="nf">.as_ref</span><span class="p">(),</span> <span class="nf">Some</span><span class="p">(</span><span class="nd">c_str!</span><span class="p">(</span><span class="s">"stacks"</span><span class="p">)))</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">coregroup_clk</span> <span class="o">=</span> <span class="nn">OptionalClk</span><span class="p">::</span><span class="nf">get</span><span class="p">(</span><span class="n">pdev</span><span class="nf">.as_ref</span><span class="p">(),</span> <span class="nf">Some</span><span class="p">(</span><span class="nd">c_str!</span><span class="p">(</span><span class="s">"coregroup"</span><span class="p">)))</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 2. 启用时钟</span>
        <span class="n">core_clk</span><span class="nf">.prepare_enable</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
        <span class="n">stacks_clk</span><span class="nf">.prepare_enable</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
        <span class="n">coregroup_clk</span><span class="nf">.prepare_enable</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 3. 获取并启用电源调节器</span>
        <span class="k">let</span> <span class="n">mali_regulator</span> <span class="o">=</span> <span class="nn">Regulator</span><span class="p">::</span><span class="o">&lt;</span><span class="nn">regulator</span><span class="p">::</span><span class="n">Enabled</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">get</span><span class="p">(</span><span class="n">pdev</span><span class="nf">.as_ref</span><span class="p">(),</span> <span class="nd">c_str!</span><span class="p">(</span><span class="s">"mali"</span><span class="p">))</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">sram_regulator</span> <span class="o">=</span> <span class="nn">Regulator</span><span class="p">::</span><span class="o">&lt;</span><span class="nn">regulator</span><span class="p">::</span><span class="n">Enabled</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">get</span><span class="p">(</span><span class="n">pdev</span><span class="nf">.as_ref</span><span class="p">(),</span> <span class="nd">c_str!</span><span class="p">(</span><span class="s">"sram"</span><span class="p">))</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 4. 映射MMIO寄存器</span>
        <span class="k">let</span> <span class="n">request</span> <span class="o">=</span> <span class="n">pdev</span><span class="nf">.io_request_by_index</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="nf">.ok_or</span><span class="p">(</span><span class="n">ENODEV</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">iomem</span> <span class="o">=</span> <span class="nn">Arc</span><span class="p">::</span><span class="nf">pin_init</span><span class="p">(</span><span class="n">request</span><span class="py">.iomap_sized</span><span class="p">::</span><span class="o">&lt;</span><span class="n">SZ_2M</span><span class="o">&gt;</span><span class="p">(),</span> <span class="n">GFP_KERNEL</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 5. 软复位GPU</span>
        <span class="nf">issue_soft_reset</span><span class="p">(</span><span class="n">pdev</span><span class="nf">.as_ref</span><span class="p">(),</span> <span class="o">&amp;</span><span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 6. L2缓存上电</span>
        <span class="nn">gpu</span><span class="p">::</span><span class="nf">l2_power_on</span><span class="p">(</span><span class="n">pdev</span><span class="nf">.as_ref</span><span class="p">(),</span> <span class="o">&amp;</span><span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 7. 读取GPU信息</span>
        <span class="k">let</span> <span class="n">gpu_info</span> <span class="o">=</span> <span class="nn">GpuInfo</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">pdev</span><span class="nf">.as_ref</span><span class="p">(),</span> <span class="o">&amp;</span><span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="n">gpu_info</span><span class="nf">.log</span><span class="p">(</span><span class="n">pdev</span><span class="p">);</span>

        <span class="c1">// 8. 创建DRM设备</span>
        <span class="k">let</span> <span class="n">data</span> <span class="o">=</span> <span class="nd">try_pin_init!</span><span class="p">(</span><span class="n">TyrData</span> <span class="p">{</span>
            <span class="n">pdev</span><span class="p">:</span> <span class="n">platform</span><span class="nf">.clone</span><span class="p">(),</span>
            <span class="n">clks</span> <span class="o">&lt;-</span> <span class="nd">new_mutex!</span><span class="p">(</span><span class="n">Clocks</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}),</span>
            <span class="n">regulators</span> <span class="o">&lt;-</span> <span class="nd">new_mutex!</span><span class="p">(</span><span class="n">Regulators</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}),</span>
            <span class="n">gpu_info</span><span class="p">,</span>
        <span class="p">});</span>

        <span class="k">let</span> <span class="n">tdev</span><span class="p">:</span> <span class="n">ARef</span><span class="o">&lt;</span><span class="n">TyrDevice</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nn">drm</span><span class="p">::</span><span class="nn">Device</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">pdev</span><span class="nf">.as_ref</span><span class="p">(),</span> <span class="n">data</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="nn">drm</span><span class="p">::</span><span class="nn">driver</span><span class="p">::</span><span class="nn">Registration</span><span class="p">::</span><span class="nf">new_foreign_owned</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tdev</span><span class="p">,</span> <span class="n">pdev</span><span class="nf">.as_ref</span><span class="p">(),</span> <span class="mi">0</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 9. 返回驱动实例</span>
        <span class="k">let</span> <span class="n">driver</span> <span class="o">=</span> <span class="nn">KBox</span><span class="p">::</span><span class="nf">pin_init</span><span class="p">(</span><span class="nd">try_pin_init!</span><span class="p">(</span><span class="n">TyrDriver</span> <span class="p">{</span> <span class="n">device</span><span class="p">:</span> <span class="n">tdev</span> <span class="p">}),</span> <span class="n">GFP_KERNEL</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

        <span class="nd">dev_info!</span><span class="p">(</span><span class="n">pdev</span><span class="nf">.as_ref</span><span class="p">(),</span> <span class="s">"Tyr initialized correctly.</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="nf">Ok</span><span class="p">(</span><span class="n">driver</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>详细分析</strong>：</p>

<p><strong>步骤1-2：时钟管理</strong></p>

<p>Rust的<code class="language-plaintext highlighter-rouge">Clk::get</code> + <code class="language-plaintext highlighter-rouge">prepare_enable</code><strong>自动管理生命周期</strong>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">core_clk</span> <span class="o">=</span> <span class="nn">Clk</span><span class="p">::</span><span class="nf">get</span><span class="p">(</span><span class="n">pdev</span><span class="nf">.as_ref</span><span class="p">(),</span> <span class="nf">Some</span><span class="p">(</span><span class="nd">c_str!</span><span class="p">(</span><span class="s">"core"</span><span class="p">)))</span><span class="o">?</span><span class="p">;</span>
<span class="n">core_clk</span><span class="nf">.prepare_enable</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
<span class="c1">// 当core_clk离开作用域时，自动disable + unprepare</span>
</code></pre></div></div>

<p>对比C版本：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">core_clk</span> <span class="o">=</span> <span class="n">devm_clk_get</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="s">"core"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">IS_ERR</span><span class="p">(</span><span class="n">core_clk</span><span class="p">))</span>
    <span class="k">return</span> <span class="nf">PTR_ERR</span><span class="p">(</span><span class="n">core_clk</span><span class="p">);</span>

<span class="n">ret</span> <span class="o">=</span> <span class="n">clk_prepare_enable</span><span class="p">(</span><span class="n">core_clk</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>

<span class="c1">// ...</span>
<span class="c1">// 忘记disable？内存泄漏！</span>
<span class="c1">// clk_disable_unprepare(core_clk);  // 必须手动</span>
</code></pre></div></div>

<p><strong>步骤3：电源调节器的类型状态</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">mali_regulator</span> <span class="o">=</span> <span class="nn">Regulator</span><span class="p">::</span><span class="o">&lt;</span><span class="nn">regulator</span><span class="p">::</span><span class="n">Enabled</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">get</span><span class="p">(</span><span class="n">pdev</span><span class="nf">.as_ref</span><span class="p">(),</span> <span class="nd">c_str!</span><span class="p">(</span><span class="s">"mali"</span><span class="p">))</span><span class="o">?</span><span class="p">;</span>
</code></pre></div></div>

<p><strong>类型系统保证</strong>：</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">Regulator&lt;Enabled&gt;</code>：类型上已启用</li>
  <li><code class="language-plaintext highlighter-rouge">Regulator&lt;Disabled&gt;</code>：类型上已禁用</li>
  <li><strong>编译时防止操作未启用的调节器</strong></li>
</ul>

<p>C中无此保证，完全依赖运行时检查。</p>

<p><strong>步骤4：MMIO映射的大小检查</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">iomem</span> <span class="o">=</span> <span class="nn">Arc</span><span class="p">::</span><span class="nf">pin_init</span><span class="p">(</span><span class="n">request</span><span class="py">.iomap_sized</span><span class="p">::</span><span class="o">&lt;</span><span class="n">SZ_2M</span><span class="o">&gt;</span><span class="p">(),</span> <span class="n">GFP_KERNEL</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">iomap_sized::&lt;SZ_2M&gt;()</code>：编译时指定映射大小为2MB</li>
  <li><code class="language-plaintext highlighter-rouge">SZ_2M</code>是常量（<code class="language-plaintext highlighter-rouge">kernel::sizes::SZ_2M</code>），编译时检查</li>
</ul>

<p>C版本：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">iomem</span> <span class="o">=</span> <span class="n">devm_ioremap_resource</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">res</span><span class="p">);</span>
<span class="c1">// 没有大小检查，运行时越界访问可能！</span>
</code></pre></div></div>

<p><strong>步骤5：软复位实现</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">issue_soft_reset</span><span class="p">(</span><span class="n">dev</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Device</span><span class="o">&lt;</span><span class="n">Bound</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">iomem</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Devres</span><span class="o">&lt;</span><span class="n">IoMem</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span> <span class="p">{</span>
    <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_CMD</span><span class="nf">.write</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">,</span> <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_CMD_SOFT_RESET</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// TODO: We cannot poll, as there is no support in Rust currently, so we</span>
    <span class="c1">// sleep. Change this when read_poll_timeout() is implemented in Rust.</span>
    <span class="nn">kernel</span><span class="p">::</span><span class="nn">time</span><span class="p">::</span><span class="nn">delay</span><span class="p">::</span><span class="nf">fsleep</span><span class="p">(</span><span class="nn">time</span><span class="p">::</span><span class="nn">Delta</span><span class="p">::</span><span class="nf">from_millis</span><span class="p">(</span><span class="mi">100</span><span class="p">));</span>

    <span class="k">if</span> <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_IRQ_RAWSTAT</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span> <span class="o">&amp;</span> <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_IRQ_RAWSTAT_RESET_COMPLETED</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
        <span class="nd">dev_err!</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="s">"GPU reset failed with errno</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="nd">dev_err!</span><span class="p">(</span>
            <span class="n">dev</span><span class="p">,</span>
            <span class="s">"GPU_INT_RAWSTAT is {}</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
            <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_IRQ_RAWSTAT</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span>
        <span class="p">);</span>

        <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="n">EIO</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>TODO注释揭示的问题</strong>：</p>
<ul>
  <li>Rust内核还没有<code class="language-plaintext highlighter-rouge">read_poll_timeout()</code></li>
  <li>被迫用固定延迟（100ms）替代轮询</li>
  <li>这是<strong>基础设施缺失</strong>的直接体现</li>
</ul>

<p><strong>步骤7：GPU信息查询</strong></p>

<p>这是当前Tyr<strong>唯一能做的事情</strong>。详见下一节。</p>

<hr />

<h2 id="代码分析3gpu信息查询">代码分析3：GPU信息查询</h2>

<h3 id="文件driversgpudrmtyrgpurs">文件：<code class="language-plaintext highlighter-rouge">drivers/gpu/drm/tyr/gpu.rs</code></h3>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// Struct containing information that can be queried by userspace. This is read from</span>
<span class="cd">/// the GPU's registers.</span>
<span class="cd">///</span>
<span class="cd">/// # Invariants</span>
<span class="cd">///</span>
<span class="cd">/// - The layout of this struct identical to the C `struct drm_panthor_gpu_info`.</span>
<span class="nd">#[repr(C)]</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="n">GpuInfo</span> <span class="p">{</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">gpu_id</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">gpu_rev</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">csf_id</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">l2_features</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">tiler_features</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">mem_features</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">mmu_features</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">thread_features</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">max_threads</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">thread_max_workgroup_size</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">thread_max_barrier_size</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">coherency_features</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">texture_features</span><span class="p">:</span> <span class="p">[</span><span class="nb">u32</span><span class="p">;</span> <span class="mi">4</span><span class="p">],</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">as_present</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">pad0</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">shader_present</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">l2_present</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">tiler_present</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">core_features</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="n">pad</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>关键设计</strong>：</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">#[repr(C)]</code></strong>：
    <ul>
      <li>保证与C结构体<code class="language-plaintext highlighter-rouge">drm_panthor_gpu_info</code>内存布局完全相同</li>
      <li>用户空间通过ioctl读取这个结构体</li>
    </ul>
  </li>
  <li><strong>Invariants注释</strong>：
    <ul>
      <li>Rust文档化不变量</li>
      <li>编译器无法检查（需要人工审查）</li>
    </ul>
  </li>
</ol>

<h3 id="gpuinfo初始化">GpuInfo初始化</h3>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">impl</span> <span class="n">GpuInfo</span> <span class="p">{</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">dev</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Device</span><span class="o">&lt;</span><span class="n">Bound</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">iomem</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Devres</span><span class="o">&lt;</span><span class="n">IoMem</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">gpu_id</span> <span class="o">=</span> <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_ID</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">csf_id</span> <span class="o">=</span> <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_CSF_ID</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">gpu_rev</span> <span class="o">=</span> <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_REVID</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">core_features</span> <span class="o">=</span> <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_CORE_FEATURES</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">l2_features</span> <span class="o">=</span> <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_L2_FEATURES</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">tiler_features</span> <span class="o">=</span> <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_TILER_FEATURES</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">mem_features</span> <span class="o">=</span> <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_MEM_FEATURES</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">mmu_features</span> <span class="o">=</span> <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_MMU_FEATURES</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">thread_features</span> <span class="o">=</span> <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_THREAD_FEATURES</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">max_threads</span> <span class="o">=</span> <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_THREAD_MAX_THREADS</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">thread_max_workgroup_size</span> <span class="o">=</span> <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_THREAD_MAX_WORKGROUP_SIZE</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">thread_max_barrier_size</span> <span class="o">=</span> <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_THREAD_MAX_BARRIER_SIZE</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">coherency_features</span> <span class="o">=</span> <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_COHERENCY_FEATURES</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

        <span class="k">let</span> <span class="n">texture_features</span> <span class="o">=</span> <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_TEXTURE_FEATURES0</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

        <span class="k">let</span> <span class="n">as_present</span> <span class="o">=</span> <span class="nn">regs</span><span class="p">::</span><span class="n">GPU_AS_PRESENT</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 64位寄存器，分两次读取</span>
        <span class="k">let</span> <span class="n">shader_present</span> <span class="o">=</span> <span class="nn">u64</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="nn">regs</span><span class="p">::</span><span class="n">GPU_SHADER_PRESENT_LO</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">shader_present</span> <span class="o">=</span>
            <span class="n">shader_present</span> <span class="p">|</span> <span class="nn">u64</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="nn">regs</span><span class="p">::</span><span class="n">GPU_SHADER_PRESENT_HI</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="mi">32</span><span class="p">;</span>

        <span class="k">let</span> <span class="n">tiler_present</span> <span class="o">=</span> <span class="nn">u64</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="nn">regs</span><span class="p">::</span><span class="n">GPU_TILER_PRESENT_LO</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">tiler_present</span> <span class="o">=</span>
            <span class="n">tiler_present</span> <span class="p">|</span> <span class="nn">u64</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="nn">regs</span><span class="p">::</span><span class="n">GPU_TILER_PRESENT_HI</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="mi">32</span><span class="p">;</span>

        <span class="k">let</span> <span class="n">l2_present</span> <span class="o">=</span> <span class="nn">u64</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="nn">regs</span><span class="p">::</span><span class="n">GPU_L2_PRESENT_LO</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">l2_present</span> <span class="o">=</span> <span class="n">l2_present</span> <span class="p">|</span> <span class="nn">u64</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="nn">regs</span><span class="p">::</span><span class="n">GPU_L2_PRESENT_HI</span><span class="nf">.read</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">iomem</span><span class="p">)</span><span class="o">?</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="mi">32</span><span class="p">;</span>

        <span class="nf">Ok</span><span class="p">(</span><span class="k">Self</span> <span class="p">{</span>
            <span class="n">gpu_id</span><span class="p">,</span>
            <span class="n">gpu_rev</span><span class="p">,</span>
            <span class="n">csf_id</span><span class="p">,</span>
            <span class="n">l2_features</span><span class="p">,</span>
            <span class="n">tiler_features</span><span class="p">,</span>
            <span class="n">mem_features</span><span class="p">,</span>
            <span class="n">mmu_features</span><span class="p">,</span>
            <span class="n">thread_features</span><span class="p">,</span>
            <span class="n">max_threads</span><span class="p">,</span>
            <span class="n">thread_max_workgroup_size</span><span class="p">,</span>
            <span class="n">thread_max_barrier_size</span><span class="p">,</span>
            <span class="n">coherency_features</span><span class="p">,</span>
            <span class="c1">// TODO: Add texture_features_{1,2,3}.</span>
            <span class="n">texture_features</span><span class="p">:</span> <span class="p">[</span><span class="n">texture_features</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span>
            <span class="n">as_present</span><span class="p">,</span>
            <span class="n">pad0</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
            <span class="n">shader_present</span><span class="p">,</span>
            <span class="n">l2_present</span><span class="p">,</span>
            <span class="n">tiler_present</span><span class="p">,</span>
            <span class="n">core_features</span><span class="p">,</span>
            <span class="n">pad</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
        <span class="p">})</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>技术细节</strong>：</p>

<ol>
  <li><strong>错误传播</strong>：
    <ul>
      <li>每次<code class="language-plaintext highlighter-rouge">regs::XXX.read()?</code>都可能失败</li>
      <li><code class="language-plaintext highlighter-rouge">?</code>运算符自动传播错误</li>
      <li>无需手动<code class="language-plaintext highlighter-rouge">if (ret &lt; 0) return ret;</code></li>
    </ul>
  </li>
  <li><strong>64位寄存器读取</strong>：
    <ul>
      <li>Mali GPU的64位寄存器分成LO/HI两个32位寄存器</li>
      <li>Rust明确显示位运算：<code class="language-plaintext highlighter-rouge">| u64::from(...) &lt;&lt; 32</code></li>
      <li>C中容易出错（符号扩展问题）</li>
    </ul>
  </li>
  <li><strong>TODO注释</strong>：
    <ul>
      <li><code class="language-plaintext highlighter-rouge">texture_features</code>只读取了第一个</li>
      <li>其余3个硬编码为0</li>
      <li>说明这是<strong>WIP（Work-in-Progress）</strong></li>
    </ul>
  </li>
</ol>

<hr />

<h2 id="代码分析4drm抽象层">代码分析4：DRM抽象层</h2>

<p>Tyr依赖<code class="language-plaintext highlighter-rouge">rust/kernel/drm/</code>提供的抽象层。让我们深入分析。</p>

<h3 id="文件rustkerneldrmgemmodrs">文件：<code class="language-plaintext highlighter-rouge">rust/kernel/drm/gem/mod.rs</code></h3>

<h4 id="41-basedriverobject-trait">4.1 BaseDriverObject trait</h4>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// GEM object functions, which must be implemented by drivers.</span>
<span class="k">pub</span> <span class="k">trait</span> <span class="n">BaseDriverObject</span><span class="o">&lt;</span><span class="n">T</span><span class="p">:</span> <span class="n">BaseObject</span><span class="o">&gt;</span><span class="p">:</span> <span class="nb">Sync</span> <span class="o">+</span> <span class="nb">Send</span> <span class="o">+</span> <span class="nb">Sized</span> <span class="p">{</span>
    <span class="cd">/// Create a new driver data object for a GEM object of a given size.</span>
    <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">dev</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">drm</span><span class="p">::</span><span class="n">Device</span><span class="o">&lt;</span><span class="nn">T</span><span class="p">::</span><span class="n">Driver</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">size</span><span class="p">:</span> <span class="nb">usize</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">impl</span> <span class="n">PinInit</span><span class="o">&lt;</span><span class="k">Self</span><span class="p">,</span> <span class="n">Error</span><span class="o">&gt;</span><span class="p">;</span>

    <span class="cd">/// Open a new handle to an existing object, associated with a File.</span>
    <span class="k">fn</span> <span class="nf">open</span><span class="p">(</span>
        <span class="n">_obj</span><span class="p">:</span> <span class="o">&amp;&lt;&lt;</span><span class="n">T</span> <span class="k">as</span> <span class="n">IntoGEMObject</span><span class="o">&gt;</span><span class="p">::</span><span class="n">Driver</span> <span class="k">as</span> <span class="nn">drm</span><span class="p">::</span><span class="n">Driver</span><span class="o">&gt;</span><span class="p">::</span><span class="n">Object</span><span class="p">,</span>
        <span class="n">_file</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">drm</span><span class="p">::</span><span class="n">File</span><span class="o">&lt;&lt;&lt;</span><span class="n">T</span> <span class="k">as</span> <span class="n">IntoGEMObject</span><span class="o">&gt;</span><span class="p">::</span><span class="n">Driver</span> <span class="k">as</span> <span class="nn">drm</span><span class="p">::</span><span class="n">Driver</span><span class="o">&gt;</span><span class="p">::</span><span class="n">File</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span> <span class="p">{</span>
        <span class="nf">Ok</span><span class="p">(())</span>
    <span class="p">}</span>

    <span class="cd">/// Close a handle to an existing object, associated with a File.</span>
    <span class="k">fn</span> <span class="nf">close</span><span class="p">(</span>
        <span class="n">_obj</span><span class="p">:</span> <span class="o">&amp;&lt;&lt;</span><span class="n">T</span> <span class="k">as</span> <span class="n">IntoGEMObject</span><span class="o">&gt;</span><span class="p">::</span><span class="n">Driver</span> <span class="k">as</span> <span class="nn">drm</span><span class="p">::</span><span class="n">Driver</span><span class="o">&gt;</span><span class="p">::</span><span class="n">Object</span><span class="p">,</span>
        <span class="n">_file</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">drm</span><span class="p">::</span><span class="n">File</span><span class="o">&lt;&lt;&lt;</span><span class="n">T</span> <span class="k">as</span> <span class="n">IntoGEMObject</span><span class="o">&gt;</span><span class="p">::</span><span class="n">Driver</span> <span class="k">as</span> <span class="nn">drm</span><span class="p">::</span><span class="n">Driver</span><span class="o">&gt;</span><span class="p">::</span><span class="n">File</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>设计解析</strong>：</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">PinInit&lt;Self, Error&gt;</code></strong>：
    <ul>
      <li>就地初始化（in-place init）</li>
      <li>避免在栈上构造后移动到堆</li>
      <li>关键：C指针可能指向这块内存</li>
    </ul>
  </li>
  <li><strong>open/close回调</strong>：
    <ul>
      <li>默认实现为空</li>
      <li>驱动可选择性覆盖</li>
      <li>对比C：必须提供函数指针或NULL</li>
    </ul>
  </li>
  <li><strong>类型约束</strong>：
    <ul>
      <li><code class="language-plaintext highlighter-rouge">Sync + Send</code>：可安全跨线程</li>
      <li><code class="language-plaintext highlighter-rouge">Sized</code>：大小已知（非trait object）</li>
    </ul>
  </li>
</ol>

<h4 id="42-引用计数机制">4.2 引用计数机制</h4>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// SAFETY: All gem objects are refcounted.</span>
<span class="k">unsafe</span> <span class="k">impl</span><span class="o">&lt;</span><span class="n">T</span><span class="p">:</span> <span class="n">IntoGEMObject</span><span class="o">&gt;</span> <span class="n">AlwaysRefCounted</span> <span class="k">for</span> <span class="n">T</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">inc_ref</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// SAFETY: The existence of a shared reference guarantees that the refcount is non-zero.</span>
        <span class="k">unsafe</span> <span class="p">{</span> <span class="nn">bindings</span><span class="p">::</span><span class="nf">drm_gem_object_get</span><span class="p">(</span><span class="k">self</span><span class="nf">.as_raw</span><span class="p">())</span> <span class="p">};</span>
    <span class="p">}</span>

    <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">dec_ref</span><span class="p">(</span><span class="n">obj</span><span class="p">:</span> <span class="n">NonNull</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// SAFETY: We either hold the only refcount on `obj`, or one of many - meaning that no one</span>
        <span class="c1">// else could possibly hold a mutable reference to `obj` and thus this immutable reference</span>
        <span class="c1">// is safe.</span>
        <span class="k">let</span> <span class="n">obj</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="n">obj</span><span class="nf">.as_ref</span><span class="p">()</span> <span class="p">}</span><span class="nf">.as_raw</span><span class="p">();</span>

        <span class="c1">// SAFETY:</span>
        <span class="c1">// - The safety requirements guarantee that the refcount is non-zero.</span>
        <span class="c1">// - We hold no references to `obj` now, making it safe for us to potentially deallocate it.</span>
        <span class="k">unsafe</span> <span class="p">{</span> <span class="nn">bindings</span><span class="p">::</span><span class="nf">drm_gem_object_put</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span> <span class="p">};</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>SAFETY注释的重要性</strong>：</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">inc_ref</code></strong>：
    <ul>
      <li>调用C函数<code class="language-plaintext highlighter-rouge">drm_gem_object_get</code></li>
      <li>假设：已有&amp;self，所以refcount非零</li>
      <li>这是<strong>不变量</strong>，违反=UB（未定义行为）</li>
    </ul>
  </li>
  <li><strong><code class="language-plaintext highlighter-rouge">dec_ref</code></strong>：
    <ul>
      <li>详细的SAFETY论证：
        <ul>
          <li>持有唯一或多个引用之一</li>
          <li>没有可变引用冲突</li>
          <li>refcount非零（由调用者保证）</li>
        </ul>
      </li>
      <li>可能释放内存（refcount降到0）</li>
    </ul>
  </li>
</ol>

<p><strong>对比C版本</strong>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">drm_gem_object_get</span><span class="p">(</span><span class="k">struct</span> <span class="n">drm_gem_object</span> <span class="o">*</span><span class="n">obj</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">kref_get</span><span class="p">(</span><span class="o">&amp;</span><span class="n">obj</span><span class="o">-&gt;</span><span class="n">refcount</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">drm_gem_object_put</span><span class="p">(</span><span class="k">struct</span> <span class="n">drm_gem_object</span> <span class="o">*</span><span class="n">obj</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">kref_put</span><span class="p">(</span><span class="o">&amp;</span><span class="n">obj</span><span class="o">-&gt;</span><span class="n">refcount</span><span class="p">,</span> <span class="n">drm_gem_object_free</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>C中<strong>完全没有安全论证</strong>：</p>
<ul>
  <li>编译器不检查refcount一致性</li>
  <li>开发者完全凭经验</li>
  <li>常见bug：double-free、use-after-free</li>
</ul>

<h4 id="43-openclose回调的ffi桥接">4.3 open/close回调的FFI桥接</h4>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="n">open_callback</span><span class="o">&lt;</span><span class="n">T</span><span class="p">:</span> <span class="n">BaseDriverObject</span><span class="o">&lt;</span><span class="n">U</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">U</span><span class="p">:</span> <span class="n">BaseObject</span><span class="o">&gt;</span><span class="p">(</span>
    <span class="n">raw_obj</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="nn">bindings</span><span class="p">::</span><span class="n">drm_gem_object</span><span class="p">,</span>
    <span class="n">raw_file</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="nn">bindings</span><span class="p">::</span><span class="n">drm_file</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">core</span><span class="p">::</span><span class="nn">ffi</span><span class="p">::</span><span class="nb">c_int</span> <span class="p">{</span>
    <span class="c1">// SAFETY: `open_callback` is only ever called with a valid pointer to a `struct drm_file`.</span>
    <span class="k">let</span> <span class="n">file</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nn">drm</span><span class="p">::</span><span class="nn">File</span><span class="p">::</span><span class="o">&lt;&lt;&lt;</span><span class="n">U</span> <span class="k">as</span> <span class="n">IntoGEMObject</span><span class="o">&gt;</span><span class="p">::</span><span class="n">Driver</span> <span class="k">as</span> <span class="nn">drm</span><span class="p">::</span><span class="n">Driver</span><span class="o">&gt;</span><span class="p">::</span><span class="n">File</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">as_ref</span><span class="p">(</span><span class="n">raw_file</span><span class="p">)</span>
    <span class="p">};</span>
    <span class="c1">// SAFETY: `open_callback` is specified in the AllocOps structure for `Object&lt;T&gt;`, ensuring that</span>
    <span class="c1">// `raw_obj` is indeed contained within a `Object&lt;T&gt;`.</span>
    <span class="k">let</span> <span class="n">obj</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
        <span class="o">&lt;&lt;&lt;</span><span class="n">U</span> <span class="k">as</span> <span class="n">IntoGEMObject</span><span class="o">&gt;</span><span class="p">::</span><span class="n">Driver</span> <span class="k">as</span> <span class="nn">drm</span><span class="p">::</span><span class="n">Driver</span><span class="o">&gt;</span><span class="p">::</span><span class="n">Object</span> <span class="k">as</span> <span class="n">IntoGEMObject</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">as_ref</span><span class="p">(</span><span class="n">raw_obj</span><span class="p">)</span>
    <span class="p">};</span>

    <span class="k">match</span> <span class="nn">T</span><span class="p">::</span><span class="nf">open</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">file</span><span class="p">)</span> <span class="p">{</span>
        <span class="nf">Err</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="n">e</span><span class="nf">.to_errno</span><span class="p">(),</span>
        <span class="nf">Ok</span><span class="p">(())</span> <span class="k">=&gt;</span> <span class="mi">0</span><span class="p">,</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>FFI桥接技巧</strong>：</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">extern "C"</code></strong>：
    <ul>
      <li>使用C ABI（调用约定）</li>
      <li>C代码可以调用这个函数</li>
    </ul>
  </li>
  <li><strong>unsafe转换</strong>：
    <ul>
      <li><code class="language-plaintext highlighter-rouge">raw_obj</code>和<code class="language-plaintext highlighter-rouge">raw_file</code>是C指针</li>
      <li>转换为Rust引用需要<code class="language-plaintext highlighter-rouge">unsafe</code></li>
      <li>SAFETY注释论证为何安全</li>
    </ul>
  </li>
  <li><strong>错误处理</strong>：
    <ul>
      <li>Rust的<code class="language-plaintext highlighter-rouge">Result&lt;T&gt;</code>转换为C的<code class="language-plaintext highlighter-rouge">int</code></li>
      <li><code class="language-plaintext highlighter-rouge">Err(e) =&gt; e.to_errno()</code>：错误码映射</li>
    </ul>
  </li>
</ol>

<p><strong>这是Rust/C互操作的经典模式</strong>：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>C kernel → extern "C" fn → unsafe转换 → 安全Rust trait方法 → Result → C错误码
</code></pre></div></div>

<hr />

<h2 id="代码分析5nova驱动对比">代码分析5：Nova驱动对比</h2>

<p>Nova是另一个Rust GPU驱动（Nvidia GSP），结构与Tyr类似。</p>

<h3 id="文件driversgpudrmnovadriverrs部分">文件：<code class="language-plaintext highlighter-rouge">drivers/gpu/drm/nova/driver.rs</code>（部分）</h3>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[vtable]</span>
<span class="k">impl</span> <span class="nn">drm</span><span class="p">::</span><span class="n">Driver</span> <span class="k">for</span> <span class="n">NovaDriver</span> <span class="p">{</span>
    <span class="k">type</span> <span class="n">Data</span> <span class="o">=</span> <span class="n">NovaData</span><span class="p">;</span>
    <span class="k">type</span> <span class="n">File</span> <span class="o">=</span> <span class="n">File</span><span class="p">;</span>
    <span class="k">type</span> <span class="n">Object</span> <span class="o">=</span> <span class="nn">gem</span><span class="p">::</span><span class="n">Object</span><span class="o">&lt;</span><span class="n">NovaObject</span><span class="o">&gt;</span><span class="p">;</span>

    <span class="k">const</span> <span class="n">INFO</span><span class="p">:</span> <span class="nn">drm</span><span class="p">::</span><span class="n">DriverInfo</span> <span class="o">=</span> <span class="n">INFO</span><span class="p">;</span>

    <span class="nn">kernel</span><span class="p">::</span><span class="nd">declare_drm_ioctls!</span> <span class="p">{</span>
        <span class="p">(</span><span class="n">NOVA_GETPARAM</span><span class="p">,</span> <span class="n">drm_nova_getparam</span><span class="p">,</span> <span class="nn">ioctl</span><span class="p">::</span><span class="n">RENDER_ALLOW</span><span class="p">,</span> <span class="nn">File</span><span class="p">::</span><span class="n">get_param</span><span class="p">),</span>
        <span class="p">(</span><span class="n">NOVA_GEM_CREATE</span><span class="p">,</span> <span class="n">drm_nova_gem_create</span><span class="p">,</span> <span class="nn">ioctl</span><span class="p">::</span><span class="n">AUTH</span> <span class="p">|</span> <span class="nn">ioctl</span><span class="p">::</span><span class="n">RENDER_ALLOW</span><span class="p">,</span> <span class="nn">File</span><span class="p">::</span><span class="n">gem_create</span><span class="p">),</span>
        <span class="p">(</span><span class="n">NOVA_GEM_INFO</span><span class="p">,</span> <span class="n">drm_nova_gem_info</span><span class="p">,</span> <span class="nn">ioctl</span><span class="p">::</span><span class="n">AUTH</span> <span class="p">|</span> <span class="nn">ioctl</span><span class="p">::</span><span class="n">RENDER_ALLOW</span><span class="p">,</span> <span class="nn">File</span><span class="p">::</span><span class="n">gem_info</span><span class="p">),</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong><code class="language-plaintext highlighter-rouge">declare_drm_ioctls!</code>宏分析</strong>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 宏展开后（简化版）</span>
<span class="k">const</span> <span class="n">IOCTLS</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="p">[</span><span class="nn">drm</span><span class="p">::</span><span class="nn">ioctl</span><span class="p">::</span><span class="n">DrmIoctlDescriptor</span><span class="p">]</span> <span class="o">=</span> <span class="o">&amp;</span><span class="p">[</span>
    <span class="nn">drm</span><span class="p">::</span><span class="nn">ioctl</span><span class="p">::</span><span class="n">DrmIoctlDescriptor</span> <span class="p">{</span>
        <span class="n">cmd</span><span class="p">:</span> <span class="nn">drm</span><span class="p">::</span><span class="nn">ioctl</span><span class="p">::</span><span class="nn">IOWR</span><span class="p">::</span><span class="o">&lt;</span><span class="n">drm_nova_getparam</span><span class="o">&gt;</span><span class="p">(</span><span class="n">DRM_COMMAND_BASE</span> <span class="o">+</span> <span class="mi">0</span><span class="p">),</span>
        <span class="n">flags</span><span class="p">:</span> <span class="nn">ioctl</span><span class="p">::</span><span class="n">RENDER_ALLOW</span><span class="p">,</span>
        <span class="n">func</span><span class="p">:</span> <span class="n">nova_get_param_wrapper</span><span class="p">,</span>  <span class="c1">// 自动生成的C包装器</span>
    <span class="p">},</span>
    <span class="c1">// ...</span>
<span class="p">];</span>
</code></pre></div></div>

<p><strong>自动生成的工作</strong>：</p>
<ol>
  <li>计算ioctl号（<code class="language-plaintext highlighter-rouge">_IOWR</code>宏）</li>
  <li>生成C→Rust的包装函数</li>
  <li>类型安全检查（编译时）</li>
</ol>

<p><strong>对比C版本</strong>（手动）：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define DRM_NOVA_GETPARAM 0x00
#define DRM_IOCTL_NOVA_GETPARAM \
    DRM_IOWR(DRM_COMMAND_BASE + DRM_NOVA_GETPARAM, struct drm_nova_getparam)
</span>
<span class="k">static</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">drm_ioctl_desc</span> <span class="n">nova_ioctls</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="n">DRM_IOCTL_DEF_DRV</span><span class="p">(</span><span class="n">NOVA_GETPARAM</span><span class="p">,</span> <span class="n">nova_get_param</span><span class="p">,</span> <span class="n">DRM_RENDER_ALLOW</span><span class="p">),</span>
    <span class="c1">// 魔数0x00容易重复或冲突</span>
<span class="p">};</span>
</code></pre></div></div>

<p>Rust的宏：</p>
<ul>
  <li>自动分配ioctl号（按顺序）</li>
  <li>类型检查：<code class="language-plaintext highlighter-rouge">drm_nova_getparam</code>必须存在</li>
  <li>编译时验证<code class="language-plaintext highlighter-rouge">File::get_param</code>签名</li>
</ul>

<hr />

<h2 id="为何上游代码如此精简gpuvm抽象缺失">为何上游代码如此精简？GPUVM抽象缺失</h2>

<p>回到最核心的问题：<strong>为何Tyr上游只能查询GPU信息，无法启动MCU？</strong></p>

<h3 id="commit-message的关键解释">Commit message的关键解释<sup id="fnref:3:1"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>：</h3>

<blockquote>
  <p>In particular, a lot of things depend on properly mapping memory on a given VA range, which itself <strong>depends on the GPUVM abstraction that is currently work-in-progress</strong>. For this reason, we still cannot boot the MCU.</p>
</blockquote>

<h3 id="技术分解">技术分解</h3>

<p><strong>启动MCU需要什么？</strong></p>

<ol>
  <li><strong>分配GPU内存</strong>：存放MCU固件（数百KB）</li>
  <li><strong>映射到GPU虚拟地址</strong>：MCU通过VA访问内存</li>
  <li><strong>配置MCU寄存器</strong>：设置入口地址</li>
  <li><strong>启动MCU</strong>：发送启动命令</li>
</ol>

<p><strong>当前Tyr能做什么？</strong></p>

<ul>
  <li>✅ <strong>步骤1</strong>：分配物理内存（通过GEM）</li>
  <li>❌ <strong>步骤2</strong>：映射到GPU VA（需要GPUVM抽象）</li>
  <li>❌ <strong>步骤3-4</strong>：后续全阻塞</li>
</ul>

<h3 id="gpuvm抽象是什么">GPUVM抽象是什么？</h3>

<p><strong>C实现</strong>（<code class="language-plaintext highlighter-rouge">drivers/gpu/drm/drm_gpuvm.c</code>）：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
 * DOC: Overview
 *
 * The GPU VA Manager, represented by struct drm_gpuvm, keeps track of a
 * GPU's virtual address (VA) space and manages the corresponding virtual
 * mappings represented by &amp;drm_gpuva objects.
 *
 * The DRM GPUVM tracks GPU VA space with &amp;drm_gpuva objects backed by a
 * &amp;drm_gem_object representing the actual memory backing the VA range.
 */</span>
<span class="k">struct</span> <span class="n">drm_gpuvm</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">drm_gem_object</span> <span class="o">*</span><span class="n">r_obj</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">drm_device</span> <span class="o">*</span><span class="n">drm</span><span class="p">;</span>
    <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">;</span>

    <span class="k">struct</span> <span class="n">rb_root_cached</span> <span class="n">rb</span><span class="p">;</span>  <span class="c1">// 红黑树，存储VA映射</span>
    <span class="c1">// ...</span>
<span class="p">};</span>
</code></pre></div></div>

<p><strong>Rust需要什么？</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 理想的GPUVM Rust API（概念性）</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">GpuVm</span><span class="o">&lt;</span><span class="n">T</span><span class="p">:</span> <span class="nn">drm</span><span class="p">::</span><span class="n">Driver</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="n">inner</span><span class="p">:</span> <span class="n">Opaque</span><span class="o">&lt;</span><span class="nn">bindings</span><span class="p">::</span><span class="n">drm_gpuvm</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">_phantom</span><span class="p">:</span> <span class="n">PhantomData</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span><span class="o">&lt;</span><span class="n">T</span><span class="p">:</span> <span class="nn">drm</span><span class="p">::</span><span class="n">Driver</span><span class="o">&gt;</span> <span class="n">GpuVm</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="cd">/// 映射GEM对象到GPU虚拟地址</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">map</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">self</span><span class="p">,</span>
        <span class="n">gem_obj</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">gem</span><span class="p">::</span><span class="n">Object</span><span class="o">&lt;...&gt;</span><span class="p">,</span>
        <span class="n">va</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
        <span class="n">size</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="n">GpuVa</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// 调用C的drm_gpuva_insert()</span>
    <span class="p">}</span>

    <span class="cd">/// 取消映射</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">unmap</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">va</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">GpuVa</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span> <span class="p">{</span>
        <span class="c1">// 调用C的drm_gpuva_remove()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>问题</strong>：</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">drm_gpuvm</code>结构体复杂</li>
  <li>涉及红黑树、引用计数、锁</li>
  <li>Rust封装需要保证<strong>内存安全</strong>和<strong>生命周期正确</strong></li>
</ul>

<h3 id="alice-ryhl的工作">Alice Ryhl的工作</h3>

<p>根据新闻报道和commit message，<strong>Alice Ryhl正在开发GPUVM的Rust抽象</strong>，基于Asahi Lina的前期工作。</p>

<p><strong>挑战</strong>：</p>
<ol>
  <li><strong>生命周期管理</strong>：GEM对象和VA映射的关系</li>
  <li><strong>锁顺序</strong>：避免死锁（C代码有隐式锁顺序）</li>
  <li><strong>红黑树抽象</strong>：Rust需要安全的树操作</li>
</ol>

<p>这是<strong>高难度的内核Rust工作</strong>，需要深入理解C实现和Rust所有权模型。</p>

<hr />

<h2 id="技术洞察从tyr学到的经验">技术洞察：从Tyr学到的经验</h2>

<h3 id="1-类型状态模式的威力">1. 类型状态模式的威力</h3>

<p><strong>电源调节器示例</strong>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">struct</span> <span class="n">Regulator</span><span class="o">&lt;</span><span class="n">S</span><span class="p">:</span> <span class="n">State</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="n">inner</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="nn">bindings</span><span class="p">::</span><span class="n">regulator</span><span class="p">,</span>
    <span class="n">_state</span><span class="p">:</span> <span class="n">PhantomData</span><span class="o">&lt;</span><span class="n">S</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">struct</span> <span class="n">Enabled</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">Disabled</span><span class="p">;</span>

<span class="k">impl</span> <span class="n">Regulator</span><span class="o">&lt;</span><span class="n">Disabled</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">enable</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="n">Regulator</span><span class="o">&lt;</span><span class="n">Enabled</span><span class="o">&gt;&gt;</span> <span class="p">{</span>
        <span class="c1">// unsafe调用C API</span>
        <span class="c1">// 转换到Enabled状态</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Regulator</span><span class="o">&lt;</span><span class="n">Enabled</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">set_voltage</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">min_uV</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span> <span class="n">max_uV</span><span class="p">:</span> <span class="nb">i32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span> <span class="p">{</span>
        <span class="c1">// 只有Enabled状态才能设置电压</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">disable</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="n">Regulator</span><span class="o">&lt;</span><span class="n">Disabled</span><span class="o">&gt;&gt;</span> <span class="p">{</span>
        <span class="c1">// 转换回Disabled状态</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// 编译错误：Disabled状态没有set_voltage方法</span>
<span class="k">let</span> <span class="n">reg</span> <span class="o">=</span> <span class="nn">Regulator</span><span class="p">::</span><span class="o">&lt;</span><span class="n">Disabled</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">get</span><span class="p">(</span><span class="o">...</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
<span class="n">reg</span><span class="nf">.set_voltage</span><span class="p">(</span><span class="mi">1000000</span><span class="p">,</span> <span class="mi">1000000</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>  <span class="c1">// ❌ 编译失败！</span>

<span class="c1">// 正确用法</span>
<span class="k">let</span> <span class="n">reg</span> <span class="o">=</span> <span class="n">reg</span><span class="nf">.enable</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>  <span class="c1">// 转换到Enabled</span>
<span class="n">reg</span><span class="nf">.set_voltage</span><span class="p">(</span><span class="mi">1000000</span><span class="p">,</span> <span class="mi">1000000</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>  <span class="c1">// ✅ 编译通过</span>
</code></pre></div></div>

<p><strong>优势</strong>：</p>
<ul>
  <li><strong>编译时防止错误状态操作</strong></li>
  <li><strong>零运行时开销</strong>：<code class="language-plaintext highlighter-rouge">PhantomData&lt;S&gt;</code>不占内存</li>
  <li><strong>自文档化</strong>：类型签名即文档</li>
</ul>

<p>C中完全没有这种保证：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">regulator</span> <span class="o">*</span><span class="n">reg</span> <span class="o">=</span> <span class="n">regulator_get</span><span class="p">(...);</span>
<span class="c1">// 忘记enable</span>
<span class="n">regulator_set_voltage</span><span class="p">(</span><span class="n">reg</span><span class="p">,</span> <span class="mi">1000000</span><span class="p">,</span> <span class="mi">1000000</span><span class="p">);</span>  <span class="c1">// 运行时错误或崩溃！</span>
</code></pre></div></div>

<h3 id="2-raii消除资源泄漏">2. RAII消除资源泄漏</h3>

<p><strong>时钟管理示例</strong>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
    <span class="k">let</span> <span class="n">clk</span> <span class="o">=</span> <span class="nn">Clk</span><span class="p">::</span><span class="nf">get</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="nf">Some</span><span class="p">(</span><span class="nd">c_str!</span><span class="p">(</span><span class="s">"core"</span><span class="p">)))</span><span class="o">?</span><span class="p">;</span>
    <span class="n">clk</span><span class="nf">.prepare_enable</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>

    <span class="nf">do_work</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>  <span class="c1">// 即使这里失败提前返回</span>

    <span class="c1">// clk离开作用域，自动调用Drop</span>
<span class="p">}</span> <span class="c1">// &lt;- 这里自动disable+unprepare</span>
</code></pre></div></div>

<p><strong>Drop trait实现</strong>（简化）：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">impl</span> <span class="nb">Drop</span> <span class="k">for</span> <span class="n">Clk</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">drop</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">unsafe</span> <span class="p">{</span>
            <span class="nn">bindings</span><span class="p">::</span><span class="nf">clk_disable_unprepare</span><span class="p">(</span><span class="k">self</span><span class="py">.inner</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>C版本的问题</strong>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ret</span> <span class="o">=</span> <span class="n">clk_prepare_enable</span><span class="p">(</span><span class="n">clk</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>

<span class="n">ret</span> <span class="o">=</span> <span class="n">do_work</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 忘记cleanup！</span>
    <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>  <span class="c1">// 时钟泄漏</span>
<span class="p">}</span>

<span class="n">clk_disable_unprepare</span><span class="p">(</span><span class="n">clk</span><span class="p">);</span>  <span class="c1">// 只有成功路径执行</span>
</code></pre></div></div>

<p><strong>统计数据</strong>（来自前文）：</p>
<ul>
  <li>内核CVE中，<strong>~70%是内存/资源管理错误</strong></li>
  <li>RAII在编译时消除这类错误</li>
</ul>

<h3 id="3-错误传播的简洁性">3. 错误传播的简洁性</h3>

<p><strong>Rust的<code class="language-plaintext highlighter-rouge">?</code>运算符</strong>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">initialize</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">clk</span> <span class="o">=</span> <span class="nn">Clk</span><span class="p">::</span><span class="nf">get</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="nf">Some</span><span class="p">(</span><span class="nd">c_str!</span><span class="p">(</span><span class="s">"core"</span><span class="p">)))</span><span class="o">?</span><span class="p">;</span>  <span class="c1">// 失败则返回</span>
    <span class="k">let</span> <span class="n">reg</span> <span class="o">=</span> <span class="nn">Regulator</span><span class="p">::</span><span class="nf">get</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="nd">c_str!</span><span class="p">(</span><span class="s">"mali"</span><span class="p">))</span><span class="o">?</span><span class="p">;</span>  <span class="c1">// 失败则返回</span>
    <span class="k">let</span> <span class="n">iomem</span> <span class="o">=</span> <span class="nf">iomap</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>  <span class="c1">// 失败则返回</span>

    <span class="c1">// 全部成功才继续</span>
    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>C版本</strong>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">initialize</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">clk</span> <span class="o">=</span> <span class="n">clk_get</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="s">"core"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">IS_ERR</span><span class="p">(</span><span class="n">clk</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">ret</span> <span class="o">=</span> <span class="n">PTR_ERR</span><span class="p">(</span><span class="n">clk</span><span class="p">);</span>
        <span class="k">goto</span> <span class="n">err_clk</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">reg</span> <span class="o">=</span> <span class="n">regulator_get</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="s">"mali"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">IS_ERR</span><span class="p">(</span><span class="n">reg</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">ret</span> <span class="o">=</span> <span class="n">PTR_ERR</span><span class="p">(</span><span class="n">reg</span><span class="p">);</span>
        <span class="k">goto</span> <span class="n">err_reg</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">iomem</span> <span class="o">=</span> <span class="n">ioremap</span><span class="p">(...);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">iomem</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">ret</span> <span class="o">=</span> <span class="o">-</span><span class="n">ENOMEM</span><span class="p">;</span>
        <span class="k">goto</span> <span class="n">err_iomem</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>

<span class="nl">err_iomem:</span>
    <span class="n">regulator_put</span><span class="p">(</span><span class="n">reg</span><span class="p">);</span>
<span class="nl">err_reg:</span>
    <span class="n">clk_put</span><span class="p">(</span><span class="n">clk</span><span class="p">);</span>
<span class="nl">err_clk:</span>
    <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>差异</strong>：</p>
<ul>
  <li>Rust：4行</li>
  <li>C：25行（含错误处理）</li>
  <li>Rust的RAII自动cleanup，无需<code class="language-plaintext highlighter-rouge">goto</code></li>
</ul>

<h3 id="4-ffi安全边界的明确化">4. FFI安全边界的明确化</h3>

<p>Tyr代码中，<strong>所有unsafe都在特定位置</strong>：</p>

<ol>
  <li><strong>寄存器读写</strong>：<code class="language-plaintext highlighter-rouge">regs::XXX.read()</code>内部</li>
  <li><strong>C结构体转换</strong>：<code class="language-plaintext highlighter-rouge">as_ref()</code>方法</li>
  <li><strong>引用计数操作</strong>：<code class="language-plaintext highlighter-rouge">drm_gem_object_get/put</code></li>
</ol>

<p><strong>驱动代码本身几乎全是安全Rust</strong>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// drivers/gpu/drm/tyr/driver.rs - probe函数</span>
<span class="c1">// 没有任何unsafe！</span>
<span class="k">fn</span> <span class="nf">probe</span><span class="p">(</span><span class="n">pdev</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">platform</span><span class="p">::</span><span class="n">Device</span><span class="o">&lt;</span><span class="n">Core</span><span class="o">&gt;</span><span class="p">,</span> <span class="o">...</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">Pin</span><span class="o">&lt;</span><span class="n">KBox</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;&gt;&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">core_clk</span> <span class="o">=</span> <span class="nn">Clk</span><span class="p">::</span><span class="nf">get</span><span class="p">(</span><span class="n">pdev</span><span class="nf">.as_ref</span><span class="p">(),</span> <span class="nf">Some</span><span class="p">(</span><span class="nd">c_str!</span><span class="p">(</span><span class="s">"core"</span><span class="p">)))</span><span class="o">?</span><span class="p">;</span>
    <span class="n">core_clk</span><span class="nf">.prepare_enable</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
    <span class="c1">// ... 全部安全代码</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>unsafe集中在抽象层</strong>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// rust/kernel/drm/gem/mod.rs</span>
<span class="k">unsafe</span> <span class="k">impl</span><span class="o">&lt;</span><span class="n">T</span><span class="p">:</span> <span class="n">IntoGEMObject</span><span class="o">&gt;</span> <span class="n">AlwaysRefCounted</span> <span class="k">for</span> <span class="n">T</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">inc_ref</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">unsafe</span> <span class="p">{</span> <span class="nn">bindings</span><span class="p">::</span><span class="nf">drm_gem_object_get</span><span class="p">(</span><span class="k">self</span><span class="nf">.as_raw</span><span class="p">())</span> <span class="p">};</span>
        <span class="c1">// ^^^ unsafe在这里，驱动无需接触</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这是<strong>Rust在内核的核心价值</strong>：</p>
<ul>
  <li>驱动开发者：写安全代码</li>
  <li>抽象层维护者：处理unsafe，详细论证安全性</li>
</ul>

<hr />

<h2 id="与已有blog的体系关联">与已有Blog的体系关联</h2>

<h3 id="blog1rust-in-the-linux-kernel---reality-check">Blog1：Rust in the Linux Kernel - Reality Check<sup id="fnref:1:3"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup></h3>

<p><strong>该文关注</strong>：</p>
<ul>
  <li>宏观数据：338个Rust文件，135,662行代码</li>
  <li>Android Binder案例：18文件，~8,000行</li>
  <li>GPU驱动：Nova（47文件，~15,000行）</li>
</ul>

<p><strong>本文补充</strong>：</p>
<ul>
  <li>Tyr的<strong>具体代码实现</strong></li>
  <li>DRM抽象层的<strong>实际工作原理</strong></li>
  <li>Nova的<strong>IOCTL宏展开</strong></li>
</ul>

<h3 id="blog2rust-and-linux-kernel-abi-stability">Blog2：Rust and Linux Kernel ABI Stability<sup id="fnref:2:1"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></h3>

<p><strong>该文关注</strong>：</p>
<ul>
  <li>用户空间ABI稳定性</li>
  <li><code class="language-plaintext highlighter-rouge">#[repr(C)]</code>的保证</li>
  <li>System V ABI兼容性</li>
</ul>

<p><strong>本文补充</strong>：</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">GpuInfo</code>的<code class="language-plaintext highlighter-rouge">#[repr(C)]</code>实战应用</li>
  <li>ioctl处理的FFI桥接</li>
  <li>C/Rust互操作的实际代码</li>
</ul>

<h3 id="形成的知识体系">形成的知识体系</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Blog1 (宏观) → Blog2 (ABI) → Blog3 (代码实战)
     ↓              ↓                ↓
  数据统计      技术保证        具体实现
  政策争议      接口规范        挑战分析
  整体趋势      系统设计        代码细节
</code></pre></div></div>

<p>三篇文章从<strong>不同角度</strong>完整覆盖了Rust在Linux内核的状态。</p>

<hr />

<h2 id="未来展望tyr的roadmap">未来展望：Tyr的Roadmap</h2>

<h3 id="短期2026年上半年">短期（2026年上半年）</h3>

<p><strong>依赖的抽象层</strong>（根据commit message）：</p>
<ol>
  <li>✅ GEM shmem（Lyude Paul负责）</li>
  <li>✅ GPUVM（Alice Ryhl负责）</li>
  <li>✅ io-pgtable（Alice Ryhl负责）</li>
</ol>

<p><strong>期望效果</strong>（原文）<sup id="fnref:3:2"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>：</p>

<blockquote>
  <p>Once we can handle those items, we expect to quickly become able to boot the GPU firmware and then progress unhindered until it is time to discuss job submission.</p>
</blockquote>

<h3 id="中期2026-2027">中期（2026-2027）</h3>

<p><strong>整合Nova的贡献</strong>：</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">register!</code>宏：类型安全的寄存器访问</li>
  <li>Bounded integers：编译时范围检查</li>
</ul>

<p><strong>完善功能</strong>：</p>
<ul>
  <li>电源管理（DVFS）</li>
  <li>GPU恢复机制</li>
  <li>通过Vulkan CTS</li>
</ul>

<h3 id="长期2027">长期（2027+）</h3>

<p><strong>JobQueue架构</strong>：</p>
<ul>
  <li>替代<code class="language-plaintext highlighter-rouge">drm_gpu_scheduler</code></li>
  <li><strong>首个C驱动可调用的Rust组件</strong></li>
  <li>双向互操作的里程碑</li>
</ul>

<hr />

<h2 id="结论代码层面的洞察">结论：代码层面的洞察</h2>

<p>通过解剖Tyr项目的实际代码，我们得到了<strong>超越宏观讨论的具体认识</strong>：</p>

<h3 id="技术层面">技术层面</h3>

<ol>
  <li><strong>Rust的类型系统价值</strong>：
    <ul>
      <li>类型状态模式（Regulator<Enabled>）</Enabled></li>
      <li>编译时状态机（设备初始化）</li>
      <li>RAII资源管理（时钟、锁）</li>
    </ul>
  </li>
  <li><strong>FFI互操作的实践</strong>：
    <ul>
      <li><code class="language-plaintext highlighter-rouge">extern "C"</code>的C ABI桥接</li>
      <li><code class="language-plaintext highlighter-rouge">#[repr(C)]</code>的ABI兼容</li>
      <li>SAFETY注释的严格论证</li>
    </ul>
  </li>
  <li><strong>抽象层的分层设计</strong>：
    <ul>
      <li>驱动层：安全Rust</li>
      <li>抽象层：处理unsafe</li>
      <li>C层：bindings自动生成</li>
    </ul>
  </li>
</ol>

<h3 id="挑战层面">挑战层面</h3>

<ol>
  <li><strong>基础设施缺失的实际影响</strong>：
    <ul>
      <li>GPUVM抽象→无法启动MCU</li>
      <li><code class="language-plaintext highlighter-rouge">read_poll_timeout()</code>缺失→用固定延迟</li>
      <li>工具链不成熟→<code class="language-plaintext highlighter-rouge">Send/Sync</code> workaround</li>
    </ul>
  </li>
  <li><strong>上游策略的务实性</strong>：
    <ul>
      <li>不再C+Rust混合（失败过）</li>
      <li>分阶段上游（避免下游分叉）</li>
      <li>与Nova/rvkms协同演进</li>
    </ul>
  </li>
</ol>

<h3 id="对开发者的启示">对开发者的启示</h3>

<ol>
  <li><strong>学习路径</strong>：
    <ul>
      <li>先掌握Rust基础（所有权、生命周期）</li>
      <li>学习内核概念（DRM、GEM、GPUVM）</li>
      <li>阅读实际代码（Tyr、Nova、Asahi）</li>
    </ul>
  </li>
  <li><strong>贡献机会</strong>：
    <ul>
      <li>GPUVM抽象开发</li>
      <li>其他DRM抽象补全</li>
      <li>Tyr驱动功能实现</li>
    </ul>
  </li>
  <li><strong>技术趋势</strong>：
    <ul>
      <li>Rust在DRM子系统的采用不可逆</li>
      <li>基础设施建设是当前瓶颈</li>
      <li>2027年可能禁止新C驱动<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup></li>
    </ul>
  </li>
</ol>

<p><strong>Rust在Linux内核已经从”实验”进入”生产”，Tyr项目是这一转变的代码级见证。</strong></p>

<h2 id="参考资料">参考资料</h2>

<ul>
  <li><a href="https://devclass.com/2025/12/15/rust-boosted-by-permanent-adoption-for-linux-kernel-code/">Rust boosted by permanent adoption for Linux kernel code</a> - DevClass, 2025-12-15</li>
  <li><a href="https://blog.desdelinux.net/en/linux-kernel-rust-official-android-16-drivers-drm-debate/">Rust is here to stay: the experimental phase in the Linux Kernel has ended</a> - DesdeLinux Blog, 2025</li>
  <li><a href="https://www.osnews.com/story/144392/the-future-for-tyr/">The future for Tyr – OSnews</a> - OSnews转载LWN文章</li>
</ul>

<p><strong>代码仓库</strong>：</p>
<ul>
  <li>Linux Kernel: <code class="language-plaintext highlighter-rouge">/Users/weli/works/linux</code>（本地分析用）</li>
  <li>官方仓库：https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git</li>
  <li>DRM Rust Tree: https://gitlab.freedesktop.org/drm/rust/kernel</li>
</ul>

<p><strong>相关项目</strong>：</p>
<ul>
  <li><a href="https://www.collabora.com/news-and-blog/news-and-events/introducing-tyr-a-new-rust-drm-driver.html">Collabora: Introducing Tyr</a> - 官方介绍</li>
  <li><a href="https://rust-for-linux.com/">Rust for Linux</a> - 官方项目网站</li>
</ul>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p><a href="/2026/02/16/rust-in-linux-kernel-reality-check.html">Rust in the Linux Kernel: A Reality Check from Code to Controversy</a> - 本系列第一篇 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:1:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:1:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:1:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a></p>
    </li>
    <li id="fn:2">
      <p><a href="/2026/02/16/rust-kernel-abi-stability-analysis.html">Rust and Linux Kernel ABI Stability: A Technical Deep Dive</a> - 本系列第二篇 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:3">
      <p>Linux Kernel Git Commit <code class="language-plaintext highlighter-rouge">cf4fd52e3236</code> - “rust: drm: Introduce the Tyr driver for Arm Mali GPUs”, Daniel Almeida, 2025-09-10. 可通过<code class="language-plaintext highlighter-rouge">git show cf4fd52e3236</code>查看完整commit message。 <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:3:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:3:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:4">
      <p>Dave Airlie在2025 Maintainers Summit的声明，报道来源： <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><category term="rust" /><category term="linux-kernel" /><summary type="html"><![CDATA[解剖 Linux 首个 Rust GPU 驱动 Tyr 的代码结构，理解内核 Rust 驱动的模块组织与实现要点。]]></summary></entry><entry xml:lang="en"><title type="html">Can C++ Enter the Linux Kernel? A Technical and Historical Analysis</title><link href="https://weinan.tech/2026/02/16/can-cpp-enter-linux-kernel.html" rel="alternate" type="text/html" title="Can C++ Enter the Linux Kernel? A Technical and Historical Analysis" /><published>2026-02-16T00:00:00+08:00</published><updated>2026-02-16T00:00:00+08:00</updated><id>https://weinan.tech/2026/02/16/can-cpp-enter-linux-kernel</id><content type="html" xml:base="https://weinan.tech/2026/02/16/can-cpp-enter-linux-kernel.html"><![CDATA[<blockquote>
  <p>本文为英文存档，已不再主推；本站后续内容以中文技术长文为主。 配套视频见 <a href="https://space.bilibili.com/21947620">B站频道</a>。</p>
</blockquote>

<p>With Rust successfully entering the Linux kernel as the second language after C, a natural question arises: could C++ have been chosen instead, or could it still enter the kernel in the future? This comprehensive analysis examines the technical barriers, historical context, and fundamental design conflicts that make C++ adoption in the Linux kernel highly unlikely, despite C++ being a mature and widely-used systems programming language.</p>

<h2 id="introduction-the-elephant-in-the-room">Introduction: The Elephant in the Room</h2>

<p>Rust’s successful integration into the Linux kernel raises an intriguing counterfactual: <strong>Why not C++?</strong> After all, C++ has:</p>
<ul>
  <li>✅ Decades of maturity (1985 vs Rust’s 2015)</li>
  <li>✅ RAII for automatic resource management</li>
  <li>✅ Rich abstraction capabilities</li>
  <li>✅ Massive developer ecosystem</li>
  <li>✅ Modern safety features (<code class="language-plaintext highlighter-rouge">std::unique_ptr</code>, <code class="language-plaintext highlighter-rouge">std::optional</code>, etc.)</li>
</ul>

<p>Yet C++ has <strong>never</strong> been seriously considered for the Linux kernel, while the younger Rust was accepted after just 2 years of development (2020-2022). This document examines why.</p>

<h2 id="executive-summary">Executive Summary</h2>

<p><strong>Likelihood of C++ entering the Linux kernel: &lt; 5%</strong></p>

<p><strong>Key barriers:</strong></p>
<ol>
  <li><strong>Political</strong>: Linus Torvalds’ explicit, sustained opposition (2004-present)</li>
  <li><strong>Technical</strong>: Exception handling, hidden allocations, lack of memory safety guarantees</li>
  <li><strong>Timing</strong>: Rust already occupies the “second language” niche</li>
  <li><strong>Engineering</strong>: No team investing effort, no killer use case</li>
  <li><strong>Philosophy</strong>: Fundamental design conflicts with kernel requirements</li>
</ol>

<h2 id="historical-context-linus-torvalds-stance-on-c">Historical Context: Linus Torvalds’ Stance on C++</h2>

<h3 id="the-2004-email-that-set-the-tone">The 2004 Email That Set the Tone</h3>

<p>On January 19, 2004, Linus Torvalds responded to a question about compiling C++ kernel modules<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>:</p>

<blockquote>
  <p><strong>“It sucks. Trust me - writing kernel code in C++ is a BLOODY STUPID IDEA.”</strong></p>

  <p><em>“The whole C++ exception handling thing is fundamentally broken. It’s _especially_ broken for kernels.”</em></p>

  <p><em>“Any compiler or language that likes to hide things like memory allocations behind your back just isn’t a good choice for a kernel.”</em></p>
</blockquote>

<h3 id="the-2007-git-mailing-list-expansion">The 2007 Git Mailing List Expansion</h3>

<p>In 2007, Linus elaborated his position on the Git mailing list<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>:</p>

<blockquote>
  <p><em>“C++ leads to really really bad design choices. You invariably start using the ‘nice’ library features of the language like STL and Boost and other total and utter crap, that may ‘help’ you program, but causes… inefficient abstracted programming models where two years down the road you notice that some abstraction wasn’t very efficient, but now all your code depends on all the nice object models around it, and you cannot fix it without rewriting your app.”</em></p>
</blockquote>

<h3 id="has-the-stance-changed-in-20-years">Has the Stance Changed in 20 Years?</h3>

<p><strong>No.</strong> As of 2026, there has been <strong>zero</strong> movement toward C++ acceptance in the kernel community. Meanwhile, Rust went from proposal (2020) to “permanent core language” status (2025)<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>.</p>

<h2 id="technical-barrier-analysis">Technical Barrier Analysis</h2>

<h3 id="barrier-1-exception-handling">Barrier 1: Exception Handling</h3>

<p><strong>The Problem:</strong></p>

<p>C++ exceptions introduce non-local control flow that is fundamentally incompatible with kernel programming requirements.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C++ exception example</span>
<span class="kt">void</span> <span class="nf">kernel_function</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">auto</span> <span class="n">buffer</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">make_unique</span><span class="o">&lt;</span><span class="n">KernelBuffer</span><span class="o">&gt;</span><span class="p">(</span><span class="n">size</span><span class="p">);</span>
    <span class="c1">// ^-- Constructor might throw</span>

    <span class="n">do_critical_work</span><span class="p">(</span><span class="n">buffer</span><span class="p">.</span><span class="n">get</span><span class="p">());</span>
    <span class="c1">// ^-- Might throw exception</span>

    <span class="c1">// If exception is thrown:</span>
    <span class="c1">// 1. Stack unwinding occurs</span>
    <span class="c1">// 2. Destructors are called (but what about interrupt context?)</span>
    <span class="c1">// 3. Exception tables increase binary size</span>
    <span class="c1">// 4. Performance becomes unpredictable</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Kernel Requirements:</strong></p>

<ul>
  <li><strong>Deterministic behavior</strong>: Every code path must be predictable</li>
  <li><strong>No surprise jumps</strong>: Control flow must be explicit and traceable</li>
  <li><strong>Minimal binary size</strong>: No room for exception tables</li>
  <li><strong>Interrupt safety</strong>: Code in interrupt context cannot handle exceptions</li>
</ul>

<p><strong>Academic Evidence:</strong></p>

<p>Research from the University of Edinburgh (2019) demonstrated that even optimized C++ exception implementations impose significant code size and runtime overhead in embedded systems<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>. More recent work from the University of St Andrews (2025) showed that C++ exception propagation across user/kernel boundaries requires special ABI support, increasing system complexity<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>.</p>

<p><strong>Comparison with Rust:</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Rust equivalent - no exceptions, explicit error handling</span>
<span class="k">fn</span> <span class="nf">kernel_function</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">buffer</span> <span class="o">=</span> <span class="nn">KernelBuffer</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">size</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="c1">// ^-- Explicit error propagation with '?'</span>

    <span class="nf">do_critical_work</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buffer</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="c1">// ^-- Explicit error handling, no hidden control flow</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span> <span class="c1">// buffer automatically dropped, no exceptions needed</span>
</code></pre></div></div>

<p><strong>Could C++ disable exceptions?</strong></p>

<p>Yes, with <code class="language-plaintext highlighter-rouge">-fno-exceptions</code>. However:</p>
<ol>
  <li>Much of C++’s design assumes exceptions exist</li>
  <li>Standard library becomes awkward without exceptions</li>
  <li>Error handling becomes manual (back to C-style)</li>
  <li>You lose a key C++ feature while keeping the complexity</li>
</ol>

<h3 id="barrier-2-hidden-memory-allocations">Barrier 2: Hidden Memory Allocations</h3>

<p><strong>The Problem:</strong></p>

<p>The kernel requires <strong>explicit, tagged memory allocations</strong> to handle different contexts:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C kernel code - explicit allocation with flags</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">buf</span> <span class="o">=</span> <span class="n">kmalloc</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="n">GFP_KERNEL</span><span class="p">);</span>     <span class="c1">// Can sleep</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">buf</span> <span class="o">=</span> <span class="n">kmalloc</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="n">GFP_ATOMIC</span><span class="p">);</span>     <span class="c1">// Atomic context</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">buf</span> <span class="o">=</span> <span class="n">kmalloc</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="n">GFP_NOWAIT</span><span class="p">);</span>     <span class="c1">// Non-blocking</span>
</code></pre></div></div>

<p><strong>C++ hides allocations:</strong></p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C++ - when does allocation happen? With what flags?</span>
<span class="k">class</span> <span class="nc">KernelBuffer</span> <span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">uint8_t</span><span class="o">&gt;</span> <span class="n">data</span><span class="p">;</span>  <span class="c1">// Hidden heap allocation!</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">name</span><span class="p">;</span>           <span class="c1">// Hidden heap allocation!</span>
<span class="nl">public:</span>
    <span class="n">KernelBuffer</span><span class="p">(</span><span class="kt">size_t</span> <span class="n">size</span><span class="p">)</span>
        <span class="o">:</span> <span class="n">data</span><span class="p">(</span><span class="n">size</span><span class="p">)</span>            <span class="c1">// Allocates here - but with what GFP_* ?</span>
        <span class="p">,</span> <span class="n">name</span><span class="p">(</span><span class="s">"buffer"</span><span class="p">)</span> <span class="p">{}</span>     <span class="c1">// Another hidden allocation</span>
<span class="p">};</span>

<span class="kt">void</span> <span class="n">function</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">KernelBuffer</span> <span class="n">buf</span><span class="p">(</span><span class="mi">1024</span><span class="p">);</span>     <span class="c1">// Can this sleep? Is it atomic-safe?</span>
    <span class="c1">// Impossible to know without diving into implementation</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Linus’s 2004 statement remains valid:</strong></p>

<blockquote>
  <p><em>“Any compiler or language that likes to hide things like memory allocations behind your back just isn’t a good choice for a kernel.”</em></p>
</blockquote>

<p><strong>Rust’s explicit approach:</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Rust - all allocations are explicit</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">KernelBuffer</span> <span class="p">{</span>
    <span class="n">data</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">u8</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">KernelBuffer</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">size</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span> <span class="n">flags</span><span class="p">:</span> <span class="n">Flags</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// Explicit allocation with explicit flags</span>
        <span class="k">let</span> <span class="n">data</span> <span class="o">=</span> <span class="nn">Vec</span><span class="p">::</span><span class="nf">try_with_capacity_in</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="n">flags</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="nf">Ok</span><span class="p">(</span><span class="k">Self</span> <span class="p">{</span> <span class="n">data</span> <span class="p">})</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// Usage</span>
<span class="k">let</span> <span class="n">buf</span> <span class="o">=</span> <span class="nn">KernelBuffer</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">1024</span><span class="p">,</span> <span class="n">GFP_KERNEL</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
<span class="c1">// ^-- Crystal clear: allocation happens here, with GFP_KERNEL</span>
</code></pre></div></div>

<h3 id="barrier-3-no-memory-safety-guarantees">Barrier 3: No Memory Safety Guarantees</h3>

<p><strong>The Core Issue:</strong></p>

<p>C++ provides <strong>the same memory safety guarantees as C: none.</strong></p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C++ - still vulnerable to use-after-free</span>
<span class="n">KernelData</span><span class="o">*</span> <span class="n">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">KernelData</span><span class="p">();</span>
<span class="k">delete</span> <span class="n">data</span><span class="p">;</span>
<span class="n">use_data</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>  <span class="c1">// ❌ Use-after-free - compiler won't catch this</span>

<span class="c1">// Still vulnerable to data races</span>
<span class="kt">void</span> <span class="nf">thread1</span><span class="p">()</span> <span class="p">{</span> <span class="n">global_data</span><span class="o">-&gt;</span><span class="n">value</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="p">}</span>  <span class="c1">// ❌ Race condition</span>
<span class="kt">void</span> <span class="n">thread2</span><span class="p">()</span> <span class="p">{</span> <span class="n">global_data</span><span class="o">-&gt;</span><span class="n">value</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="p">}</span>  <span class="c1">// Compiler won't catch</span>

<span class="c1">// Still vulnerable to null pointer dereferences</span>
<span class="n">KernelData</span><span class="o">*</span> <span class="n">data</span> <span class="o">=</span> <span class="n">get_data</span><span class="p">();</span>  <span class="c1">// Might return nullptr</span>
<span class="n">data</span><span class="o">-&gt;</span><span class="n">process</span><span class="p">();</span>                 <span class="c1">// ❌ Potential null deref</span>
</code></pre></div></div>

<p><strong>Rust’s compile-time guarantees:</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Rust - use-after-free is impossible</span>
<span class="k">let</span> <span class="n">data</span> <span class="o">=</span> <span class="nn">Box</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">KernelData</span><span class="p">::</span><span class="nf">new</span><span class="p">());</span>
<span class="nf">drop</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
<span class="nf">use_data</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>  <span class="c1">// ✅ Compile error: value used after move</span>

<span class="c1">// Data races are impossible</span>
<span class="k">fn</span> <span class="nf">thread1</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Data</span><span class="p">)</span> <span class="p">{</span> <span class="n">data</span><span class="py">.value</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="p">}</span>  <span class="c1">// ✅ Compile error:</span>
<span class="k">fn</span> <span class="nf">thread2</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Data</span><span class="p">)</span> <span class="p">{</span> <span class="n">data</span><span class="py">.value</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="p">}</span>  <span class="c1">// cannot mutate through shared reference</span>

<span class="c1">// Null pointer dereferences are impossible</span>
<span class="k">let</span> <span class="n">data</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">KernelData</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nf">get_data</span><span class="p">();</span>
<span class="n">data</span><span class="nf">.process</span><span class="p">();</span>  <span class="c1">// ✅ Compile error: Option&lt;T&gt; has no method 'process'</span>
<span class="c1">// Must explicitly unwrap: data.unwrap().process()</span>
</code></pre></div></div>

<p><strong>The Statistics:</strong></p>

<p>According to research on Rust in the Linux kernel<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>:</p>
<ul>
  <li>~70% of kernel CVEs stem from memory safety issues</li>
  <li>Rust eliminates these <strong>at compile time</strong> without runtime overhead</li>
  <li>C++ eliminates <strong>0%</strong> of these issues</li>
</ul>

<h3 id="barrier-4-runtime-and-standard-library-dependencies">Barrier 4: Runtime and Standard Library Dependencies</h3>

<p><strong>The Problem:</strong></p>

<p>C++ typically depends on:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">libstdc++</code> or <code class="language-plaintext highlighter-rouge">libc++</code> (standard library)</li>
  <li>Runtime support for RTTI (Run-Time Type Information)</li>
  <li>Global constructors/destructors</li>
  <li>Thread-local storage</li>
</ul>

<p><strong>Kernel requirements:</strong></p>
<ul>
  <li>❌ No user-space libraries</li>
  <li>❌ No global constructors (initialization order issues)</li>
  <li>❌ Minimal binary size</li>
  <li>❌ No assumptions about runtime environment</li>
</ul>

<p><strong>Possible workarounds:</strong></p>
<ul>
  <li>Use <code class="language-plaintext highlighter-rouge">-fno-rtti</code> (disable RTTI)</li>
  <li>Use <code class="language-plaintext highlighter-rouge">-fno-exceptions</code> (disable exceptions)</li>
  <li>Use <code class="language-plaintext highlighter-rouge">-nostdlib</code> (no standard library)</li>
  <li>Avoid global objects</li>
</ul>

<p><strong>But then you’re left with “C with classes”</strong> - losing most of C++’s advantages while keeping the complexity.</p>

<p><strong>Rust’s approach:</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Rust kernel code uses 'core' (no std)</span>
<span class="nd">#![no_std]</span>  <span class="c1">// Explicitly kernel mode</span>

<span class="c1">// From rust/kernel/lib.rs (actual kernel code):</span>
<span class="cd">//! This crate contains the kernel APIs that have been ported or wrapped for</span>
<span class="cd">//! usage by Rust code in the kernel and is shared by all of them.</span>
<span class="cd">//!</span>
<span class="cd">//! In other words, all the rest of the Rust code in the kernel (e.g. kernel</span>
<span class="cd">//! modules written in Rust) depends on [`core`] and this crate.</span>

<span class="k">extern</span> <span class="k">crate</span> <span class="n">core</span><span class="p">;</span>  <span class="c1">// Only core, no std library</span>
</code></pre></div></div>

<h2 id="language-design-philosophy-comparison">Language Design Philosophy Comparison</h2>

<h3 id="the-fundamental-mismatch">The Fundamental Mismatch</h3>

<table>
  <thead>
    <tr>
      <th>Aspect</th>
      <th>Linux Kernel Needs</th>
      <th>C++ Provides</th>
      <th>Rust Provides</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Error Handling</strong></td>
      <td>Explicit, zero overhead</td>
      <td>Exceptions (overhead) or manual</td>
      <td><code class="language-plaintext highlighter-rouge">Result&lt;T&gt;</code> (zero overhead, enforced)</td>
    </tr>
    <tr>
      <td><strong>Memory Allocation</strong></td>
      <td>Explicit, tagged (GFP_*)</td>
      <td>Often implicit</td>
      <td>Explicit with allocator API</td>
    </tr>
    <tr>
      <td><strong>Control Flow</strong></td>
      <td>Predictable, traceable</td>
      <td>Exceptions hide flow</td>
      <td>All control flow explicit</td>
    </tr>
    <tr>
      <td><strong>Memory Safety</strong></td>
      <td>Critical (70% of CVEs)</td>
      <td>No guarantees</td>
      <td>Compile-time guarantees</td>
    </tr>
    <tr>
      <td><strong>Abstraction Cost</strong></td>
      <td>Must be zero</td>
      <td>Sometimes has overhead</td>
      <td>Guaranteed zero-cost</td>
    </tr>
    <tr>
      <td><strong>ABI Stability</strong></td>
      <td>Essential for modules</td>
      <td>Unstable (name mangling)</td>
      <td>C-compatible FFI</td>
    </tr>
    <tr>
      <td><strong>Binary Size</strong></td>
      <td>Minimal</td>
      <td>STL bloat, RTTI tables</td>
      <td>No runtime, minimal size</td>
    </tr>
  </tbody>
</table>

<h3 id="modern-c-improvements-do-they-help">Modern C++ Improvements: Do They Help?</h3>

<p><strong>Modern C++ (C++11/14/17/20/23) added:</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">std::unique_ptr</code> / <code class="language-plaintext highlighter-rouge">std::shared_ptr</code> (RAII smart pointers)</li>
  <li><code class="language-plaintext highlighter-rouge">constexpr</code> (compile-time computation)</li>
  <li><code class="language-plaintext highlighter-rouge">std::optional</code> (like Rust’s <code class="language-plaintext highlighter-rouge">Option&lt;T&gt;</code>)</li>
  <li><code class="language-plaintext highlighter-rouge">std::expected</code> (like Rust’s <code class="language-plaintext highlighter-rouge">Result&lt;T, E&gt;</code>)</li>
  <li>Move semantics</li>
  <li>Lambda expressions</li>
</ul>

<p><strong>Do these solve the kernel’s problems?</strong></p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Modern C++ example</span>
<span class="k">auto</span> <span class="n">data</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">make_unique</span><span class="o">&lt;</span><span class="n">KernelData</span><span class="o">&gt;</span><span class="p">(</span><span class="n">size</span><span class="p">);</span>
<span class="c1">// ❌ Still implicit allocation</span>
<span class="c1">// ❌ Still can't specify GFP_KERNEL or GFP_ATOMIC</span>
<span class="c1">// ❌ Still no compile-time data race prevention</span>
<span class="c1">// ❌ Still requires runtime support</span>

<span class="n">std</span><span class="o">::</span><span class="n">optional</span><span class="o">&lt;</span><span class="n">KernelData</span><span class="o">&gt;</span> <span class="n">data</span> <span class="o">=</span> <span class="n">get_data</span><span class="p">();</span>
<span class="c1">// ✅ Better than raw pointers</span>
<span class="c1">// ❌ But runtime overhead (size + bool flag)</span>
<span class="c1">// ❌ No enforcement of checking before use</span>
</code></pre></div></div>

<p><strong>Rust’s approach:</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Rust equivalent</span>
<span class="k">let</span> <span class="n">data</span> <span class="o">=</span> <span class="nn">Box</span><span class="p">::</span><span class="nf">try_new_in</span><span class="p">(</span><span class="nn">KernelData</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">size</span><span class="p">)</span><span class="o">?</span><span class="p">,</span> <span class="n">GFP_KERNEL</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
<span class="c1">// ✅ Explicit allocation</span>
<span class="c1">// ✅ Explicit flags</span>
<span class="c1">// ✅ Zero runtime overhead</span>
<span class="c1">// ✅ Compile-time safety</span>

<span class="k">let</span> <span class="n">data</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">KernelData</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nf">get_data</span><span class="p">();</span>
<span class="c1">// ✅ Zero runtime overhead (just enum tag)</span>
<span class="c1">// ✅ Compiler enforces checking before use</span>
</code></pre></div></div>

<p><strong>Conclusion:</strong> Modern C++ is better than old C++, but still doesn’t meet kernel requirements as well as Rust does.</p>

<h2 id="case-studies-c-in-other-kernels">Case Studies: C++ in Other Kernels</h2>

<h3 id="windows-nt-kernel">Windows NT Kernel</h3>

<p><strong>Status:</strong> Partial C++ usage, primarily in driver frameworks</p>

<p><strong>Constraints:</strong></p>
<ul>
  <li>Strict subset of C++</li>
  <li>No exceptions</li>
  <li>No RTTI</li>
  <li>No STL</li>
  <li>Custom memory allocators required</li>
</ul>

<p><strong>Key difference:</strong> Windows was designed with C++ in mind from the start (1993). Linux was not.</p>

<h3 id="macosios-kernel-xnu">macOS/iOS Kernel (XNU)</h3>

<p><strong>Status:</strong> C++ in IOKit (driver framework)</p>

<p><strong>Constraints:</strong></p>
<ul>
  <li>Limited C++ subset</li>
  <li>Carefully controlled usage</li>
  <li>Predates modern C++ features</li>
</ul>

<p><strong>Key difference:</strong> Apple controls the entire ecosystem. Linux is community-driven with diverse hardware.</p>

<h3 id="fuchsia-google">Fuchsia (Google)</h3>

<p><strong>Status:</strong> Extensive C++ usage</p>

<p><strong>Key difference:</strong> <strong>Brand new kernel</strong> (started 2016) with no legacy codebase. Linux has 30+ years of C code and established conventions.</p>

<h3 id="conclusion-from-case-studies">Conclusion from Case Studies</h3>

<p><strong>Every kernel that uses C++ either:</strong></p>
<ol>
  <li>Was designed for C++ from the start, OR</li>
  <li>Uses a highly restricted C++ subset that resembles “C with classes”</li>
</ol>

<p><strong>Linux is neither.</strong> It has 30 million lines of C code and a culture that values explicitness and simplicity.</p>

<h2 id="the-timing-factor-rust-already-won-the-second-language-slot">The Timing Factor: Rust Already Won the “Second Language” Slot</h2>

<h3 id="why-timing-matters">Why Timing Matters</h3>

<p>The Linux kernel adding a second language is a <strong>massive undertaking</strong>:</p>
<ul>
  <li>Build system changes</li>
  <li>Documentation requirements</li>
  <li>Maintainer training</li>
  <li>ABI compatibility concerns</li>
  <li>Toolchain integration</li>
</ul>

<p><strong>The kernel community will not do this multiple times.</strong></p>

<h3 id="rusts-timeline">Rust’s Timeline</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2020: Rust for Linux announced
      - Initial RFC posted to LKML
      - Community discussion begins

2021: Infrastructure development
      - Build system integration
      - Kernel abstraction layer development

2022 (October): Rust merged into Linux 6.1 development cycle
        - Linus Torvalds accepts the patches

2022 (December): Linux 6.1 released
        - First stable kernel with Rust support

2023-2024: Ecosystem growth
        - Android Binder rewritten in Rust
        - GPU drivers (Nova)
        - Network PHY drivers

2025 (December): Rust becomes "permanent core language"
        - No longer experimental
        - 338 files, 135,662 lines of production code
</code></pre></div></div>

<h3 id="what-would-c-need">What Would C++ Need?</h3>

<p>To match Rust’s success, C++ would need:</p>

<p><strong>1. A dedicated team</strong> (5-10 engineers, multi-year commitment)
<strong>2. Corporate sponsorship</strong> (Google/Microsoft/Meta level)
<strong>3. Killer application</strong> (equivalent to Android Binder)
<strong>4. Toolchain development</strong> (kernel-safe C++ subset)
<strong>5. Community buy-in</strong> (Linus and maintainers)</p>

<p><strong>Current status:</strong></p>
<ul>
  <li>❌ No team working on this</li>
  <li>❌ No corporate sponsor</li>
  <li>❌ No killer application identified</li>
  <li>❌ No toolchain work</li>
  <li>❌ Linus explicitly opposed (20 years)</li>
</ul>

<h2 id="the-kernel-safe-c-thought-experiment">The “Kernel-Safe C++” Thought Experiment</h2>

<h3 id="what-would-it-look-like">What Would It Look Like?</h3>

<p>If someone tried to create “kernel-safe C++”, it would need:</p>

<p><strong>Allowed features:</strong></p>
<ul>
  <li>Classes and constructors/destructors (RAII)</li>
  <li>Templates (limited complexity)</li>
  <li>Namespaces</li>
  <li><code class="language-plaintext highlighter-rouge">constexpr</code></li>
  <li>References</li>
</ul>

<p><strong>Prohibited features:</strong></p>
<ul>
  <li>❌ Exceptions (non-local control flow)</li>
  <li>❌ RTTI (runtime overhead)</li>
  <li>❌ STL (hidden allocations, overhead)</li>
  <li>❌ <code class="language-plaintext highlighter-rouge">new</code>/<code class="language-plaintext highlighter-rouge">delete</code> (must use kernel allocators)</li>
  <li>❌ Virtual inheritance (complexity)</li>
  <li>❌ Global constructors (initialization order)</li>
</ul>

<h3 id="the-problem-is-this-still-c">The Problem: Is This Still C++?</h3>

<p>At this point, you have <strong>“C with classes and templates”</strong> - essentially what embedded C++ tried to be in the 1990s.</p>

<p><strong>Historical precedent:</strong> Embedded C++ (EC++) was defined in 1996 as a subset for embedded systems. It failed because:</p>
<ol>
  <li>Too restrictive for C++ programmers</li>
  <li>Too complex for C programmers</li>
  <li>Toolchain fragmentation</li>
  <li>Eventually superseded by “just use C”</li>
</ol>

<h3 id="comparison-with-rust">Comparison with Rust</h3>

<p><strong>Rust didn’t need to be restricted</strong> - it was designed for systems programming from day one:</p>
<ul>
  <li>No exceptions by design (uses <code class="language-plaintext highlighter-rouge">Result&lt;T, E&gt;</code>)</li>
  <li>No garbage collector by design</li>
  <li>No runtime by design (<code class="language-plaintext highlighter-rouge">#![no_std]</code> is a first-class mode)</li>
  <li>Explicit memory management by design</li>
  <li>Zero-cost abstractions by design</li>
</ul>

<p><strong>C++ requires restrictions; Rust requires nothing.</strong></p>

<h2 id="economic-and-engineering-reality">Economic and Engineering Reality</h2>

<h3 id="the-resource-investment-required">The Resource Investment Required</h3>

<p>Based on Rust for Linux’s development:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Total effort estimate (2020-2025):
- Core team: ~10 engineers × 5 years = 50 person-years
- Corporate contributions: ~20 engineers × 2 years = 40 person-years
- Community contributions: ~100 contributors × 0.5 years = 50 person-years
Total: ~140 person-years of engineering effort

Cost estimate (conservative):
- Average engineer cost: $200,000/year (salary + overhead)
- Total investment: ~$28 million USD
</code></pre></div></div>

<p><strong>For C++ to enter the kernel, someone would need to invest comparable resources.</strong></p>

<h3 id="who-would-fund-this">Who Would Fund This?</h3>

<p><strong>Rust for Linux sponsors:</strong></p>
<ul>
  <li>Google (Android Binder, security motivation)</li>
  <li>Microsoft (Azure security, NT kernel Rust initiative)</li>
  <li>Arm (architecture support, driver development)</li>
  <li>Meta (networking, infrastructure)</li>
</ul>

<p><strong>Potential C++ sponsors:</strong></p>
<ul>
  <li>??? (No clear candidate)</li>
</ul>

<p><strong>Why no sponsors?</strong></p>
<ol>
  <li>C++ doesn’t solve problems Rust doesn’t already solve</li>
  <li>Investment would be duplicative (Rust already exists)</li>
  <li>Political risk (Linus’s opposition)</li>
  <li>Technical risk (fundamental design mismatches)</li>
</ol>

<h3 id="the-opportunity-cost">The Opportunity Cost</h3>

<p>Every hour spent on “C++ for Linux” is an hour <strong>not spent on:</strong></p>
<ul>
  <li>Improving Rust for Linux</li>
  <li>Fixing bugs in existing code</li>
  <li>Adding new features</li>
  <li>Supporting new hardware</li>
</ul>

<p><strong>Rational actors won’t make this trade-off.</strong></p>

<h2 id="technical-alternatives-what-if-not-rust">Technical Alternatives: What If Not Rust?</h2>

<h3 id="if-rust-didnt-exist-what-would-be-considered">If Rust Didn’t Exist, What Would Be Considered?</h3>

<p><strong>Hypothetical ranking (if choosing today):</strong></p>

<ol>
  <li><strong>Zig</strong>: Explicit control, modern C replacement, safety tools
    <ul>
      <li>✅ Zero hidden behavior</li>
      <li>✅ Excellent C interop</li>
      <li>✅ Modern error handling</li>
      <li>❌ No compile-time memory safety guarantees</li>
      <li>❌ Small community (vs Rust)</li>
      <li>❌ Language still evolving</li>
    </ul>
  </li>
  <li><strong>D</strong>: Systems programming language with safety features
    <ul>
      <li>✅ Memory safety options</li>
      <li>✅ No garbage collector mode</li>
      <li>❌ Smaller community</li>
      <li>❌ Less industry backing</li>
      <li>❌ Complex feature set</li>
    </ul>
  </li>
  <li><strong>Ada/SPARK</strong>: Formal verification capabilities
    <ul>
      <li>✅ Extremely rigorous safety</li>
      <li>❌ Very niche community</li>
      <li>❌ Steep learning curve</li>
      <li>❌ Poor tooling integration</li>
    </ul>
  </li>
  <li><strong>C++</strong>: Mature, widely known
    <ul>
      <li>✅ Large community</li>
      <li>✅ Rich abstractions</li>
      <li>❌ All the issues discussed in this document</li>
    </ul>
  </li>
</ol>

<p><strong>Rust won because it hit the sweet spot:</strong></p>
<ul>
  <li>Memory safety without garbage collection</li>
  <li>Zero-cost abstractions</li>
  <li>Large, active community</li>
  <li>Industry backing</li>
  <li>Purpose-built for systems programming</li>
</ul>

<h3 id="could-multiple-languages-coexist">Could Multiple Languages Coexist?</h3>

<p><strong>Theoretically yes, practically no.</strong></p>

<p><strong>Challenges:</strong></p>
<ul>
  <li>Each language adds build system complexity</li>
  <li>Each language requires maintainer expertise</li>
  <li>Each language creates ABI boundaries</li>
  <li>Each language fragments the codebase</li>
</ul>

<p><strong>The kernel needs coherence</strong>, not a polyglot mess.</p>

<p><strong>Historical precedent:</strong> The kernel <strong>rejected</strong> multiple assembler syntaxes (AT&amp;T vs Intel), settling on one. It won’t embrace multiple high-level languages.</p>

<h2 id="the-path-forward-what-would-change-the-analysis">The Path Forward: What Would Change the Analysis?</h2>

<h3 id="scenario-1-rust-fails-catastrophically">Scenario 1: Rust Fails Catastrophically</h3>

<p><strong>What would constitute “failure”?</strong></p>
<ul>
  <li>Major security vulnerabilities in Rust driver code</li>
  <li>Unfixable performance issues</li>
  <li>Toolchain becomes unmaintainable</li>
  <li>Community abandons Rust for Linux</li>
</ul>

<p><strong>Likelihood: &lt; 1%</strong></p>

<p>Current evidence (Android Binder, GPU drivers, network drivers) shows Rust succeeding in production.</p>

<p><strong>Would C++ be next choice?</strong></p>

<p>Probably not. More likely:</p>
<ol>
  <li>Return to C-only</li>
  <li>Consider Zig (if mature by then)</li>
  <li>Consider formally verified C subsets</li>
</ol>

<h3 id="scenario-2-linus-torvalds-retireschanges-mind">Scenario 2: Linus Torvalds Retires/Changes Mind</h3>

<p><strong>What if new kernel leadership is pro-C++?</strong></p>

<p>Even then, the technical issues remain:</p>
<ul>
  <li>Exceptions still problematic</li>
  <li>Hidden allocations still problematic</li>
  <li>No memory safety guarantees still problematic</li>
</ul>

<p><strong>New leadership might be more pragmatic</strong>, but they still answer to technical reality.</p>

<h3 id="scenario-3-c-gets-kernel-specific-safety-extensions">Scenario 3: C++ Gets Kernel-Specific Safety Extensions</h3>

<p><strong>What if a major vendor (Google/Microsoft) created “Kernel C++”?</strong></p>

<p>Example: Hypothetical language features</p>
<ul>
  <li>Compile-time borrow checking (copying Rust)</li>
  <li>Explicit allocation syntax</li>
  <li>Guaranteed zero-cost abstractions</li>
  <li>Formal verification hooks</li>
</ul>

<p><strong>At that point, you’ve reinvented Rust.</strong></p>

<p>Why not just use Rust?</p>

<h3 id="scenario-4-webassembly-or-other-bytecode-approach">Scenario 4: WebAssembly or Other Bytecode Approach</h3>

<p><strong>Alternative: Compile to safe bytecode?</strong></p>

<p>This has been explored (eBPF for kernel extensions), but:</p>
<ul>
  <li>Not suitable for core kernel code</li>
  <li>Performance overhead</li>
  <li>Complexity</li>
</ul>

<p><strong>Not a replacement for Rust/C.</strong></p>

<h2 id="conclusion-the-verdict">Conclusion: The Verdict</h2>

<h3 id="summary-of-findings">Summary of Findings</h3>

<p><strong>Can C++ enter the Linux kernel?</strong></p>

<p><strong>Answer: Extremely unlikely (&lt; 5% probability) for the following reasons:</strong></p>

<h4 id="political-barriers-high">Political Barriers (High)</h4>
<ul>
  <li>✗ Linus Torvalds’ explicit, sustained opposition (20+ years)</li>
  <li>✗ No champion within kernel maintainer community</li>
  <li>✗ Rust already occupies “second language” niche</li>
</ul>

<h4 id="technical-barriers-high">Technical Barriers (High)</h4>
<ul>
  <li>✗ Exception handling fundamentally incompatible with kernel needs</li>
  <li>✗ Hidden memory allocations violate kernel philosophy</li>
  <li>✗ No compile-time memory safety guarantees</li>
  <li>✗ Runtime dependencies (RTTI, libstdc++) unsuitable for kernel</li>
  <li>✗ ABI instability complicates module system</li>
</ul>

<h4 id="engineering-barriers-high">Engineering Barriers (High)</h4>
<ul>
  <li>✗ No team working on C++ kernel integration</li>
  <li>✗ No corporate sponsor identified</li>
  <li>✗ No killer application to justify investment</li>
  <li>✗ Estimated $28M+ investment required (based on Rust precedent)</li>
</ul>

<h4 id="timing-barriers-high">Timing Barriers (High)</h4>
<ul>
  <li>✗ Rust already invested 140+ person-years</li>
  <li>✗ Rust has production deployments (Android Binder, GPU drivers)</li>
  <li>✗ Kernel won’t add third high-level language</li>
</ul>

<h3 id="comparison-why-rust-succeeded-where-c-cannot">Comparison: Why Rust Succeeded Where C++ Cannot</h3>

<table>
  <thead>
    <tr>
      <th>Factor</th>
      <th>Rust</th>
      <th>C++</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Memory Safety</strong></td>
      <td>✅ Compile-time guarantees</td>
      <td>❌ None</td>
    </tr>
    <tr>
      <td><strong>Kernel Philosophy Fit</strong></td>
      <td>✅ Explicit everything</td>
      <td>❌ Hidden behavior</td>
    </tr>
    <tr>
      <td><strong>Runtime Requirements</strong></td>
      <td>✅ None (<code class="language-plaintext highlighter-rouge">#![no_std]</code>)</td>
      <td>❌ Requires libstdc++ subset</td>
    </tr>
    <tr>
      <td><strong>Error Handling</strong></td>
      <td>✅ Zero-cost <code class="language-plaintext highlighter-rouge">Result&lt;T&gt;</code></td>
      <td>❌ Exceptions or manual</td>
    </tr>
    <tr>
      <td><strong>Industry Backing</strong></td>
      <td>✅ Google, MS, Arm, Meta</td>
      <td>❌ None for kernel work</td>
    </tr>
    <tr>
      <td><strong>Active Development</strong></td>
      <td>✅ 338 files, 135K lines</td>
      <td>❌ Zero</td>
    </tr>
    <tr>
      <td><strong>Linus’s Stance</strong></td>
      <td>✅ Neutral → Accepting</td>
      <td>❌ Explicit opposition</td>
    </tr>
    <tr>
      <td><strong>Killer App</strong></td>
      <td>✅ Android Binder</td>
      <td>❌ None identified</td>
    </tr>
  </tbody>
</table>

<h3 id="the-real-question">The Real Question</h3>

<p>The question isn’t “Can C++ enter the Linux kernel?”</p>

<p><strong>The question is: “Why would it?”</strong></p>

<ul>
  <li>It doesn’t solve problems Rust doesn’t already solve</li>
  <li>It brings technical baggage Rust doesn’t have</li>
  <li>It lacks corporate and community backing</li>
  <li>It faces political opposition Rust never did</li>
</ul>

<h3 id="final-thoughts">Final Thoughts</h3>

<p>C++ is an excellent language for many domains:</p>
<ul>
  <li>Application development</li>
  <li>Game engines</li>
  <li>High-performance computing</li>
  <li>Systems software (outside kernels)</li>
</ul>

<p>But for the <strong>Linux kernel specifically</strong>, the ship has sailed. Rust provides:</p>
<ul>
  <li>Better memory safety</li>
  <li>Better kernel philosophy fit</li>
  <li>Better tooling for kernel development</li>
  <li>Better industry momentum</li>
</ul>

<p><strong>Unless fundamental technical realities change</strong>, C++ will remain outside the Linux kernel indefinitely.</p>

<p>The more productive question for C++ advocates is: <strong>How can C++ improve in its own domains?</strong> rather than attempting to enter a niche where it’s technically unsuited and politically unwelcome.</p>

<hr />

<h2 id="appendix-quick-reference-tables">Appendix: Quick Reference Tables</h2>

<h3 id="language-feature-comparison">Language Feature Comparison</h3>

<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>C</th>
      <th>C++</th>
      <th>Rust</th>
      <th>Kernel Needs</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Memory Safety</td>
      <td>❌</td>
      <td>❌</td>
      <td>✅</td>
      <td>✅ Critical</td>
    </tr>
    <tr>
      <td>Zero Runtime</td>
      <td>✅</td>
      <td>⚠️</td>
      <td>✅</td>
      <td>✅ Required</td>
    </tr>
    <tr>
      <td>Explicit Allocation</td>
      <td>✅</td>
      <td>❌</td>
      <td>✅</td>
      <td>✅ Required</td>
    </tr>
    <tr>
      <td>Error Handling</td>
      <td>⚠️ Manual</td>
      <td>❌ Exceptions</td>
      <td>✅ <code class="language-plaintext highlighter-rouge">Result&lt;T&gt;</code></td>
      <td>✅ Explicit</td>
    </tr>
    <tr>
      <td>ABI Stability</td>
      <td>✅</td>
      <td>❌</td>
      <td>✅ C-FFI</td>
      <td>✅ Required</td>
    </tr>
    <tr>
      <td>Compile-time Checks</td>
      <td>⚠️ Basic</td>
      <td>⚠️ Basic</td>
      <td>✅ Extensive</td>
      <td>✅ Preferred</td>
    </tr>
    <tr>
      <td>Learning Curve</td>
      <td>Low</td>
      <td>High</td>
      <td>High</td>
      <td>⚠️ Trade-off</td>
    </tr>
    <tr>
      <td>Ecosystem</td>
      <td>Huge</td>
      <td>Huge</td>
      <td>Large</td>
      <td>⚠️ Consider</td>
    </tr>
  </tbody>
</table>

<h3 id="historical-timeline-second-language-attempts">Historical Timeline: Second Language Attempts</h3>

<table>
  <thead>
    <tr>
      <th>Year</th>
      <th>Event</th>
      <th>Outcome</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1991</td>
      <td>Linux 0.01 considers C++</td>
      <td>❌ Rejected (immature tooling)</td>
    </tr>
    <tr>
      <td>2004</td>
      <td>C++ kernel module discussion</td>
      <td>❌ Linus: “BLOODY STUPID IDEA”</td>
    </tr>
    <tr>
      <td>2007</td>
      <td>Git mailing list C++ debate</td>
      <td>❌ Linus elaborates opposition</td>
    </tr>
    <tr>
      <td>2020</td>
      <td>Rust for Linux announced</td>
      <td>✅ Positive reception</td>
    </tr>
    <tr>
      <td>2022</td>
      <td>Rust merged into Linux 6.1</td>
      <td>✅ Accepted</td>
    </tr>
    <tr>
      <td>2025</td>
      <td>Rust “permanent core language”</td>
      <td>✅ Success</td>
    </tr>
    <tr>
      <td>2026</td>
      <td>C++ in kernel?</td>
      <td>❌ Still no movement</td>
    </tr>
  </tbody>
</table>

<h3 id="investment-comparison">Investment Comparison</h3>

<table>
  <thead>
    <tr>
      <th>Aspect</th>
      <th>Rust for Linux</th>
      <th>Hypothetical C++ for Linux</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Engineering Effort</strong></td>
      <td>~140 person-years</td>
      <td>~150-200 person-years (higher due to restrictions)</td>
    </tr>
    <tr>
      <td><strong>Cost</strong></td>
      <td>~$28M USD</td>
      <td>~$30-40M USD</td>
    </tr>
    <tr>
      <td><strong>Corporate Sponsors</strong></td>
      <td>Google, Microsoft, Arm, Meta</td>
      <td>None identified</td>
    </tr>
    <tr>
      <td><strong>Community Support</strong></td>
      <td>Strong (150+ contributors)</td>
      <td>Weak (no active effort)</td>
    </tr>
    <tr>
      <td><strong>Political Support</strong></td>
      <td>Neutral → Positive</td>
      <td>Strongly negative</td>
    </tr>
    <tr>
      <td><strong>Technical Viability</strong></td>
      <td>High (proven in production)</td>
      <td>Low (fundamental conflicts)</td>
    </tr>
    <tr>
      <td><strong>ROI</strong></td>
      <td>High (70% of CVEs prevented)</td>
      <td>Negative (no advantage over Rust)</td>
    </tr>
  </tbody>
</table>

<h2 id="references">References</h2>

<hr />

<p><strong>Document Information:</strong></p>
<ul>
  <li><strong>Created:</strong> 2026-02-16</li>
  <li><strong>Analysis Scope:</strong> Technical, historical, and economic feasibility of C++ entering the Linux kernel</li>
  <li><strong>Methodology:</strong> Literature review, code analysis, historical precedent examination</li>
  <li><strong>Conclusion:</strong> C++ entry into Linux kernel is highly unlikely (&lt; 5% probability) due to converging political, technical, and economic barriers</li>
</ul>

<hr />

<h2 id="中文版--chinese-version">中文版 / Chinese Version</h2>

<h1 id="c能进入linux内核吗技术与历史分析">C++能进入Linux内核吗？技术与历史分析</h1>

<p><strong>摘要</strong>: 随着Rust成功进入Linux内核成为C之后的第二语言，一个自然的问题出现了：C++本可以被选择吗，或者它未来仍能进入内核吗？本综合分析研究了技术障碍、历史背景和基本设计冲突，这些使得C++被Linux内核采用的可能性极低，尽管C++是一门成熟且广泛使用的系统编程语言。</p>

<h2 id="引言房间里的大象">引言：房间里的大象</h2>

<p>Rust成功集成到Linux内核引发了一个有趣的反事实问题：<strong>为什么不是C++？</strong> 毕竟，C++拥有：</p>
<ul>
  <li>✅ 数十年的成熟度 (1985年 vs Rust的2015年)</li>
  <li>✅ 用于自动资源管理的RAII</li>
  <li>✅ 丰富的抽象能力</li>
  <li>✅ 庞大的开发者生态系统</li>
  <li>✅ 现代安全特性 (<code class="language-plaintext highlighter-rouge">std::unique_ptr</code>, <code class="language-plaintext highlighter-rouge">std::optional</code>等)</li>
</ul>

<p>然而C++从未被Linux内核认真考虑过，而更年轻的Rust仅在2年开发后(2020-2022)就被接受了。本文档探讨原因。</p>

<h2 id="执行摘要">执行摘要</h2>

<p><strong>C++进入Linux内核的可能性: &lt; 5%</strong></p>

<p><strong>关键障碍:</strong></p>
<ol>
  <li><strong>政治因素</strong>: Linus Torvalds明确、持续的反对 (2004年至今)</li>
  <li><strong>技术因素</strong>: 异常处理、隐藏分配、缺乏内存安全保证</li>
  <li><strong>时机因素</strong>: Rust已经占据”第二语言”生态位</li>
  <li><strong>工程因素</strong>: 没有团队投入努力，没有杀手级应用</li>
  <li><strong>哲学因素</strong>: 与内核需求的根本设计冲突</li>
</ol>

<h2 id="历史背景linus-torvalds关于c的立场">历史背景：Linus Torvalds关于C++的立场</h2>

<h3 id="2004年定调的邮件">2004年定调的邮件</h3>

<p>2004年1月19日，Linus Torvalds回应了关于编译C++内核模块的问题<sup id="fnref:1:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>：</p>

<blockquote>
  <p><strong>“糟透了。相信我 - 用C++编写内核代码是一个非常愚蠢的想法。”</strong></p>

  <p><em>“整个C++异常处理机制从根本上就是有问题的。对内核来说尤其如此。”</em></p>

  <p><em>“任何喜欢在你背后隐藏内存分配等操作的编译器或语言，都不是内核的好选择。”</em></p>
</blockquote>

<h3 id="2007年git邮件列表的详述">2007年Git邮件列表的详述</h3>

<p>2007年，Linus在Git邮件列表上详述了他的立场<sup id="fnref:2:1"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>：</p>

<blockquote>
  <p><em>“C++导致真正糟糕的设计选择。你不可避免地会开始使用STL和Boost等’优雅的’库特性…这会导致低效的抽象编程模型，两年后你会发现某些抽象效率不高，但现在你所有的代码都依赖于这些精美的对象模型，除非重写应用否则无法修复。”</em></p>
</blockquote>

<h3 id="20年来立场改变了吗">20年来立场改变了吗？</h3>

<p><strong>没有。</strong> 截至2026年，内核社区对C++接受度<strong>零</strong>进展。与此同时，Rust从提案(2020)到”永久核心语言”状态(2025)<sup id="fnref:3:1"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>。</p>

<h2 id="技术障碍分析">技术障碍分析</h2>

<h3 id="障碍1异常处理">障碍1：异常处理</h3>

<p><strong>问题所在:</strong></p>

<p>C++异常引入非局部控制流，这与内核编程需求根本不兼容。</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C++异常示例</span>
<span class="kt">void</span> <span class="nf">kernel_function</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">auto</span> <span class="n">buffer</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">make_unique</span><span class="o">&lt;</span><span class="n">KernelBuffer</span><span class="o">&gt;</span><span class="p">(</span><span class="n">size</span><span class="p">);</span>
    <span class="c1">// ^-- 构造函数可能抛出异常</span>

    <span class="n">do_critical_work</span><span class="p">(</span><span class="n">buffer</span><span class="p">.</span><span class="n">get</span><span class="p">());</span>
    <span class="c1">// ^-- 可能抛出异常</span>

    <span class="c1">// 如果抛出异常：</span>
    <span class="c1">// 1. 发生栈展开</span>
    <span class="c1">// 2. 调用析构函数（但在中断上下文中呢？）</span>
    <span class="c1">// 3. 异常表增加二进制大小</span>
    <span class="c1">// 4. 性能变得不可预测</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>内核需求:</strong></p>

<ul>
  <li><strong>确定性行为</strong>: 每个代码路径必须可预测</li>
  <li><strong>无意外跳转</strong>: 控制流必须显式和可追踪</li>
  <li><strong>最小二进制大小</strong>: 没有异常表的空间</li>
  <li><strong>中断安全</strong>: 中断上下文中的代码无法处理异常</li>
</ul>

<p><strong>学术证据:</strong></p>

<p>爱丁堡大学的研究(2019)表明，即使是优化的C++异常实现也会在嵌入式系统中造成显著的代码大小和运行时开销<sup id="fnref:4:1"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>。圣安德鲁斯大学的最新工作(2025)显示，C++异常在用户/内核边界的传播需要特殊的ABI支持，增加了系统复杂性<sup id="fnref:5:1"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>。</p>

<p><strong>与Rust的对比:</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Rust等价代码 - 无异常，显式错误处理</span>
<span class="k">fn</span> <span class="nf">kernel_function</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">buffer</span> <span class="o">=</span> <span class="nn">KernelBuffer</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">size</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="c1">// ^-- 用'?'显式错误传播</span>

    <span class="nf">do_critical_work</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buffer</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="c1">// ^-- 显式错误处理，无隐藏控制流</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span> <span class="c1">// buffer自动丢弃，不需要异常</span>
</code></pre></div></div>

<p><strong>C++能禁用异常吗?</strong></p>

<p>可以，使用<code class="language-plaintext highlighter-rouge">-fno-exceptions</code>。但是：</p>
<ol>
  <li>C++的大部分设计假定异常存在</li>
  <li>没有异常的标准库变得笨拙</li>
  <li>错误处理变成手动（回到C风格）</li>
  <li>你失去了一个关键的C++特性，同时保留了复杂性</li>
</ol>

<h3 id="障碍2隐藏的内存分配">障碍2：隐藏的内存分配</h3>

<p><strong>问题所在:</strong></p>

<p>内核需要<strong>显式、带标记的内存分配</strong>来处理不同上下文：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C内核代码 - 带标志的显式分配</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">buf</span> <span class="o">=</span> <span class="n">kmalloc</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="n">GFP_KERNEL</span><span class="p">);</span>     <span class="c1">// 可以睡眠</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">buf</span> <span class="o">=</span> <span class="n">kmalloc</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="n">GFP_ATOMIC</span><span class="p">);</span>     <span class="c1">// 原子上下文</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">buf</span> <span class="o">=</span> <span class="n">kmalloc</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="n">GFP_NOWAIT</span><span class="p">);</span>     <span class="c1">// 非阻塞</span>
</code></pre></div></div>

<p><strong>C++隐藏分配:</strong></p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C++ - 何时分配？用什么标志？</span>
<span class="k">class</span> <span class="nc">KernelBuffer</span> <span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">uint8_t</span><span class="o">&gt;</span> <span class="n">data</span><span class="p">;</span>  <span class="c1">// 隐藏的堆分配！</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">name</span><span class="p">;</span>           <span class="c1">// 隐藏的堆分配！</span>
<span class="nl">public:</span>
    <span class="n">KernelBuffer</span><span class="p">(</span><span class="kt">size_t</span> <span class="n">size</span><span class="p">)</span>
        <span class="o">:</span> <span class="n">data</span><span class="p">(</span><span class="n">size</span><span class="p">)</span>            <span class="c1">// 在这里分配 - 但用什么GFP_* ?</span>
        <span class="p">,</span> <span class="n">name</span><span class="p">(</span><span class="s">"buffer"</span><span class="p">)</span> <span class="p">{}</span>     <span class="c1">// 另一个隐藏分配</span>
<span class="p">};</span>

<span class="kt">void</span> <span class="n">function</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">KernelBuffer</span> <span class="n">buf</span><span class="p">(</span><span class="mi">1024</span><span class="p">);</span>     <span class="c1">// 这能睡眠吗？原子安全吗？</span>
    <span class="c1">// 不深入实现无法知道</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Linus的2004年声明仍然有效:</strong></p>

<blockquote>
  <p><em>“任何喜欢在你背后隐藏内存分配等操作的编译器或语言，都不是内核的好选择。”</em></p>
</blockquote>

<p><strong>Rust的显式方法:</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Rust - 所有分配都是显式的</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">KernelBuffer</span> <span class="p">{</span>
    <span class="n">data</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">u8</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">KernelBuffer</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">size</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span> <span class="n">flags</span><span class="p">:</span> <span class="n">Flags</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// 用显式标志显式分配</span>
        <span class="k">let</span> <span class="n">data</span> <span class="o">=</span> <span class="nn">Vec</span><span class="p">::</span><span class="nf">try_with_capacity_in</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="n">flags</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="nf">Ok</span><span class="p">(</span><span class="k">Self</span> <span class="p">{</span> <span class="n">data</span> <span class="p">})</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// 使用</span>
<span class="k">let</span> <span class="n">buf</span> <span class="o">=</span> <span class="nn">KernelBuffer</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">1024</span><span class="p">,</span> <span class="n">GFP_KERNEL</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
<span class="c1">// ^-- 非常清楚：分配在这里发生，用GFP_KERNEL</span>
</code></pre></div></div>

<h3 id="障碍3无内存安全保证">障碍3：无内存安全保证</h3>

<p><strong>核心问题:</strong></p>

<p>C++提供<strong>与C相同的内存安全保证：无。</strong></p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C++ - 仍然容易出现use-after-free</span>
<span class="n">KernelData</span><span class="o">*</span> <span class="n">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">KernelData</span><span class="p">();</span>
<span class="k">delete</span> <span class="n">data</span><span class="p">;</span>
<span class="n">use_data</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>  <span class="c1">// ❌ Use-after-free - 编译器不会捕获</span>

<span class="c1">// 仍然容易出现数据竞争</span>
<span class="kt">void</span> <span class="nf">thread1</span><span class="p">()</span> <span class="p">{</span> <span class="n">global_data</span><span class="o">-&gt;</span><span class="n">value</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="p">}</span>  <span class="c1">// ❌ 竞态条件</span>
<span class="kt">void</span> <span class="n">thread2</span><span class="p">()</span> <span class="p">{</span> <span class="n">global_data</span><span class="o">-&gt;</span><span class="n">value</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="p">}</span>  <span class="c1">// 编译器不会捕获</span>

<span class="c1">// 仍然容易出现空指针解引用</span>
<span class="n">KernelData</span><span class="o">*</span> <span class="n">data</span> <span class="o">=</span> <span class="n">get_data</span><span class="p">();</span>  <span class="c1">// 可能返回nullptr</span>
<span class="n">data</span><span class="o">-&gt;</span><span class="n">process</span><span class="p">();</span>                 <span class="c1">// ❌ 潜在空解引用</span>
</code></pre></div></div>

<p><strong>Rust的编译时保证:</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Rust - use-after-free不可能发生</span>
<span class="k">let</span> <span class="n">data</span> <span class="o">=</span> <span class="nn">Box</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">KernelData</span><span class="p">::</span><span class="nf">new</span><span class="p">());</span>
<span class="nf">drop</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
<span class="nf">use_data</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>  <span class="c1">// ✅ 编译错误：值在移动后使用</span>

<span class="c1">// 数据竞争不可能发生</span>
<span class="k">fn</span> <span class="nf">thread1</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Data</span><span class="p">)</span> <span class="p">{</span> <span class="n">data</span><span class="py">.value</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="p">}</span>  <span class="c1">// ✅ 编译错误：</span>
<span class="k">fn</span> <span class="nf">thread2</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Data</span><span class="p">)</span> <span class="p">{</span> <span class="n">data</span><span class="py">.value</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="p">}</span>  <span class="c1">// 不能通过共享引用修改</span>

<span class="c1">// 空指针解引用不可能发生</span>
<span class="k">let</span> <span class="n">data</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">KernelData</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nf">get_data</span><span class="p">();</span>
<span class="n">data</span><span class="nf">.process</span><span class="p">();</span>  <span class="c1">// ✅ 编译错误：Option&lt;T&gt;没有方法'process'</span>
<span class="c1">// 必须显式解包：data.unwrap().process()</span>
</code></pre></div></div>

<p><strong>统计数据:</strong></p>

<p>根据关于Rust在Linux内核中的研究<sup id="fnref:6:1"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>：</p>
<ul>
  <li>约70%的内核CVE源于内存安全问题</li>
  <li>Rust在<strong>编译时</strong>消除这些问题，无运行时开销</li>
  <li>C++消除<strong>0%</strong>的这些问题</li>
</ul>

<h3 id="障碍4运行时和标准库依赖">障碍4：运行时和标准库依赖</h3>

<p><strong>问题所在:</strong></p>

<p>C++通常依赖于：</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">libstdc++</code>或<code class="language-plaintext highlighter-rouge">libc++</code> (标准库)</li>
  <li>RTTI的运行时支持 (运行时类型信息)</li>
  <li>全局构造函数/析构函数</li>
  <li>线程本地存储</li>
</ul>

<p><strong>内核需求:</strong></p>
<ul>
  <li>❌ 没有用户空间库</li>
  <li>❌ 没有全局构造函数 (初始化顺序问题)</li>
  <li>❌ 最小二进制大小</li>
  <li>❌ 不对运行时环境做假设</li>
</ul>

<p><strong>可能的变通方法:</strong></p>
<ul>
  <li>使用<code class="language-plaintext highlighter-rouge">-fno-rtti</code> (禁用RTTI)</li>
  <li>使用<code class="language-plaintext highlighter-rouge">-fno-exceptions</code> (禁用异常)</li>
  <li>使用<code class="language-plaintext highlighter-rouge">-nostdlib</code> (无标准库)</li>
  <li>避免全局对象</li>
</ul>

<p><strong>但这样你就只剩下”带类的C”</strong> - 失去了C++的大部分优势，同时保留了复杂性。</p>

<p><strong>Rust的方法:</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Rust内核代码使用'core' (无std)</span>
<span class="nd">#![no_std]</span>  <span class="c1">// 显式内核模式</span>

<span class="c1">// 来自rust/kernel/lib.rs (实际内核代码):</span>
<span class="cd">//! 这个crate包含已移植或包装的内核API</span>
<span class="cd">//! 供内核中的Rust代码使用，所有代码都依赖它。</span>

<span class="k">extern</span> <span class="k">crate</span> <span class="n">core</span><span class="p">;</span>  <span class="c1">// 只有core，没有std库</span>
</code></pre></div></div>

<h2 id="语言设计哲学对比">语言设计哲学对比</h2>

<h3 id="根本不匹配">根本不匹配</h3>

<table>
  <thead>
    <tr>
      <th>方面</th>
      <th>Linux内核需求</th>
      <th>C++提供</th>
      <th>Rust提供</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>错误处理</strong></td>
      <td>显式、零开销</td>
      <td>异常(开销)或手动</td>
      <td><code class="language-plaintext highlighter-rouge">Result&lt;T&gt;</code> (零开销、强制)</td>
    </tr>
    <tr>
      <td><strong>内存分配</strong></td>
      <td>显式、带标记(GFP_*)</td>
      <td>通常隐式</td>
      <td>用分配器API显式</td>
    </tr>
    <tr>
      <td><strong>控制流</strong></td>
      <td>可预测、可追踪</td>
      <td>异常隐藏流程</td>
      <td>所有控制流显式</td>
    </tr>
    <tr>
      <td><strong>内存安全</strong></td>
      <td>关键(70%的CVE)</td>
      <td>无保证</td>
      <td>编译时保证</td>
    </tr>
    <tr>
      <td><strong>抽象成本</strong></td>
      <td>必须为零</td>
      <td>有时有开销</td>
      <td>保证零成本</td>
    </tr>
    <tr>
      <td><strong>ABI稳定性</strong></td>
      <td>模块必需</td>
      <td>不稳定(名称改编)</td>
      <td>C兼容FFI</td>
    </tr>
    <tr>
      <td><strong>二进制大小</strong></td>
      <td>最小</td>
      <td>STL膨胀、RTTI表</td>
      <td>无运行时、最小大小</td>
    </tr>
  </tbody>
</table>

<h2 id="其他内核中的c案例研究">其他内核中的C++案例研究</h2>

<h3 id="windows-nt内核">Windows NT内核</h3>

<p><strong>状态:</strong> 部分C++使用，主要在驱动框架中</p>

<p><strong>约束:</strong></p>
<ul>
  <li>C++的严格子集</li>
  <li>无异常</li>
  <li>无RTTI</li>
  <li>无STL</li>
  <li>需要自定义内存分配器</li>
</ul>

<p><strong>关键区别:</strong> Windows从一开始(1993)就考虑了C++。Linux没有。</p>

<h3 id="macosios内核-xnu">macOS/iOS内核 (XNU)</h3>

<p><strong>状态:</strong> C++用于IOKit (驱动框架)</p>

<p><strong>约束:</strong></p>
<ul>
  <li>有限的C++子集</li>
  <li>仔细控制的使用</li>
  <li>早于现代C++特性</li>
</ul>

<p><strong>关键区别:</strong> Apple控制整个生态系统。Linux是社区驱动的，硬件多样化。</p>

<h3 id="fuchsia-google-1">Fuchsia (Google)</h3>

<p><strong>状态:</strong> 广泛使用C++</p>

<p><strong>关键区别:</strong> <strong>全新内核</strong> (始于2016年)，没有遗留代码库。Linux有30多年的C代码和既定约定。</p>

<h3 id="案例研究的结论">案例研究的结论</h3>

<p><strong>每个使用C++的内核都:</strong></p>
<ol>
  <li>从一开始就为C++设计，或</li>
  <li>使用高度受限的C++子集，类似于”带类的C”</li>
</ol>

<p><strong>Linux两者都不是。</strong> 它有3000万行C代码和重视显式和简单性的文化。</p>

<h2 id="时机因素rust已经赢得了第二语言席位">时机因素：Rust已经赢得了”第二语言”席位</h2>

<h3 id="为什么时机很重要">为什么时机很重要</h3>

<p>Linux内核添加第二语言是<strong>巨大的工程</strong>：</p>
<ul>
  <li>构建系统变更</li>
  <li>文档需求</li>
  <li>维护者培训</li>
  <li>ABI兼容性问题</li>
  <li>工具链集成</li>
</ul>

<p><strong>内核社区不会多次这样做。</strong></p>

<h3 id="rust的时间线">Rust的时间线</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2020: 宣布Rust for Linux
      - 向LKML发布初始RFC
      - 社区讨论开始

2021: 基础设施开发
      - 构建系统集成
      - 内核抽象层开发

2022 (10月): Rust合并到Linux 6.1开发周期
        - Linus Torvalds接受补丁

2022 (12月): Linux 6.1发布
        - 首个支持Rust的稳定内核

2023-2024: 生态系统增长
        - Android Binder用Rust重写
        - GPU驱动 (Nova)
        - 网络PHY驱动

2025 (12月): Rust成为"永久核心语言"
        - 不再是实验性的
        - 338个文件，135,662行生产代码
</code></pre></div></div>

<h3 id="c需要什么">C++需要什么？</h3>

<p>要匹配Rust的成功，C++需要：</p>

<p><strong>1. 专门的团队</strong> (5-10名工程师，多年承诺)
<strong>2. 企业赞助</strong> (Google/Microsoft/Meta级别)
<strong>3. 杀手级应用</strong> (等同于Android Binder)
<strong>4. 工具链开发</strong> (内核安全的C++子集)
<strong>5. 社区支持</strong> (Linus和维护者)</p>

<p><strong>当前状态:</strong></p>
<ul>
  <li>❌ 没有团队在做这个</li>
  <li>❌ 没有企业赞助商</li>
  <li>❌ 没有确定的杀手级应用</li>
  <li>❌ 没有工具链工作</li>
  <li>❌ Linus明确反对 (20年)</li>
</ul>

<h2 id="经济和工程现实">经济和工程现实</h2>

<h3 id="所需资源投资">所需资源投资</h3>

<p>基于Rust for Linux的开发：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>总工作量估算 (2020-2025):
- 核心团队: ~10名工程师 × 5年 = 50人年
- 企业贡献: ~20名工程师 × 2年 = 40人年
- 社区贡献: ~100名贡献者 × 0.5年 = 50人年
总计: ~140人年的工程努力

成本估算 (保守):
- 平均工程师成本: $200,000/年 (薪水 + 开销)
- 总投资: 约$2800万美元
</code></pre></div></div>

<p><strong>要让C++进入内核，有人需要投入类似的资源。</strong></p>

<h3 id="谁会资助这个">谁会资助这个？</h3>

<p><strong>Rust for Linux赞助商:</strong></p>
<ul>
  <li>Google (Android Binder，安全动机)</li>
  <li>Microsoft (Azure安全，NT内核Rust倡议)</li>
  <li>Arm (架构支持，驱动开发)</li>
  <li>Meta (网络，基础设施)</li>
</ul>

<p><strong>潜在的C++赞助商:</strong></p>
<ul>
  <li>??? (没有明确候选人)</li>
</ul>

<p><strong>为什么没有赞助商?</strong></p>
<ol>
  <li>C++不能解决Rust尚未解决的问题</li>
  <li>投资是重复的 (Rust已经存在)</li>
  <li>政治风险 (Linus的反对)</li>
  <li>技术风险 (根本设计不匹配)</li>
</ol>

<h2 id="结论判决">结论：判决</h2>

<h3 id="发现总结">发现总结</h3>

<p><strong>C++能进入Linux内核吗?</strong></p>

<p><strong>答案: 极不可能 (&lt; 5%概率)，原因如下:</strong></p>

<h4 id="政治障碍-高">政治障碍 (高)</h4>
<ul>
  <li>✗ Linus Torvalds明确、持续的反对 (20+年)</li>
  <li>✗ 内核维护者社区中无倡导者</li>
  <li>✗ Rust已占据”第二语言”生态位</li>
</ul>

<h4 id="技术障碍-高">技术障碍 (高)</h4>
<ul>
  <li>✗ 异常处理与内核需求根本不兼容</li>
  <li>✗ 隐藏的内存分配违反内核哲学</li>
  <li>✗ 无编译时内存安全保证</li>
  <li>✗ 运行时依赖 (RTTI, libstdc++) 不适合内核</li>
  <li>✗ ABI不稳定使模块系统复杂化</li>
</ul>

<h4 id="工程障碍-高">工程障碍 (高)</h4>
<ul>
  <li>✗ 没有团队在做C++内核集成</li>
  <li>✗ 没有确定的企业赞助商</li>
  <li>✗ 没有杀手级应用来证明投资合理</li>
  <li>✗ 估计需要$2800万+投资 (基于Rust先例)</li>
</ul>

<h4 id="时机障碍-高">时机障碍 (高)</h4>
<ul>
  <li>✗ Rust已投资140+人年</li>
  <li>✗ Rust有生产部署 (Android Binder, GPU驱动)</li>
  <li>✗ 内核不会添加第三种高级语言</li>
</ul>

<h3 id="对比为什么rust成功而c不能">对比：为什么Rust成功而C++不能</h3>

<table>
  <thead>
    <tr>
      <th>因素</th>
      <th>Rust</th>
      <th>C++</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>内存安全</strong></td>
      <td>✅ 编译时保证</td>
      <td>❌ 无</td>
    </tr>
    <tr>
      <td><strong>内核哲学契合</strong></td>
      <td>✅ 一切显式</td>
      <td>❌ 隐藏行为</td>
    </tr>
    <tr>
      <td><strong>运行时需求</strong></td>
      <td>✅ 无 (<code class="language-plaintext highlighter-rouge">#![no_std]</code>)</td>
      <td>❌ 需要libstdc++子集</td>
    </tr>
    <tr>
      <td><strong>错误处理</strong></td>
      <td>✅ 零成本<code class="language-plaintext highlighter-rouge">Result&lt;T&gt;</code></td>
      <td>❌ 异常或手动</td>
    </tr>
    <tr>
      <td><strong>行业支持</strong></td>
      <td>✅ Google, MS, Arm, Meta</td>
      <td>❌ 无内核工作支持</td>
    </tr>
    <tr>
      <td><strong>活跃开发</strong></td>
      <td>✅ 338文件, 135K行</td>
      <td>❌ 零</td>
    </tr>
    <tr>
      <td><strong>Linus立场</strong></td>
      <td>✅ 中立→接受</td>
      <td>❌ 明确反对</td>
    </tr>
    <tr>
      <td><strong>杀手级应用</strong></td>
      <td>✅ Android Binder</td>
      <td>❌ 无确定的</td>
    </tr>
  </tbody>
</table>

<h3 id="真正的问题">真正的问题</h3>

<p>问题不是”C++能进入Linux内核吗？”</p>

<p><strong>问题是: “为什么要这样做？”</strong></p>

<ul>
  <li>它不能解决Rust尚未解决的问题</li>
  <li>它带来Rust没有的技术包袱</li>
  <li>它缺乏企业和社区支持</li>
  <li>它面临Rust从未遇到的政治反对</li>
</ul>

<h3 id="最终想法">最终想法</h3>

<p>C++是许多领域的优秀语言：</p>
<ul>
  <li>应用开发</li>
  <li>游戏引擎</li>
  <li>高性能计算</li>
  <li>系统软件 (内核之外)</li>
</ul>

<p>但对于<strong>Linux内核具体来说</strong>，船已经开走了。Rust提供：</p>
<ul>
  <li>更好的内存安全</li>
  <li>更好的内核哲学契合</li>
  <li>更好的内核开发工具</li>
  <li>更好的行业动力</li>
</ul>

<p><strong>除非基本技术现实改变</strong>，C++将无限期地留在Linux内核之外。</p>

<p>对C++倡导者来说，更有成效的问题是：<strong>C++如何在自己的领域改进？</strong> 而不是试图进入一个技术上不适合且政治上不受欢迎的领域。</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p><a href="https://harmful.cat-v.org/software/c++/linus">Re: Compiling C++ kernel module + Makefile</a> - Linus Torvalds, January 19, 2004, Linux Kernel Mailing List <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:1:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:2">
      <p><a href="https://lwn.net/Articles/249460/">Re: [RFC] Convert builtin-mailinfo.c to use The Better String Library</a> - Linus Torvalds, September 6, 2007, Git Mailing List <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:3">
      <p><a href="https://www.webpronews.com/linux-kernel-adopts-rust-as-permanent-core-language-in-2025/">Linux Kernel Adopts Rust as Permanent Core Language in 2025</a> - WebProNews, December 2025 <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:3:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:4">
      <p><a href="https://www.research.ed.ac.uk/files/78829292/low_cost_deterministic_C_exceptions_for_embedded_systems.pdf">Low-cost deterministic C++ exceptions for embedded systems</a> - University of Edinburgh, 2019, ACM SIGPLAN International Conference on Compiler Construction <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:4:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:5">
      <p><a href="https://doi.org/10.1145/3764860.3768332">Propagating C++ exceptions across the user/kernel boundary</a> - Voronetskiy &amp; Spink, University of St Andrews, PLOS 2025 <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:5:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:6">
      <p><a href="https://mars-research.github.io/doc/2024-acsac-rfl.pdf">Rust for Linux: Understanding the Security Impact</a> - Research paper analyzing Rust’s security impact in Linux kernel <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:6:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><summary type="html"><![CDATA[本文为英文存档，已不再主推；本站后续内容以中文技术长文为主。 配套视频见 B站频道。]]></summary></entry><entry xml:lang="en"><title type="html">Rust in the Linux Kernel: Understanding the Current State and Future Direction</title><link href="https://weinan.tech/2026/02/16/rust-in-linux-kernel-reality-check.html" rel="alternate" type="text/html" title="Rust in the Linux Kernel: Understanding the Current State and Future Direction" /><published>2026-02-16T00:00:00+08:00</published><updated>2026-02-16T00:00:00+08:00</updated><id>https://weinan.tech/2026/02/16/rust-in-linux-kernel-reality-check</id><content type="html" xml:base="https://weinan.tech/2026/02/16/rust-in-linux-kernel-reality-check.html"><![CDATA[<blockquote>
  <p>本文为英文存档，已不再主推；本站后续内容以中文技术长文为主。 配套视频见 <a href="https://space.bilibili.com/21947620">B站频道</a>。</p>
</blockquote>

<p>Examining the actual state of Rust in the Linux kernel through data and production code. This analysis explores 135,662 lines of Rust code currently in the kernel, addresses common questions about ‘unsafe’, development experience, and the gradual adoption path. With concrete code examples from the Android Binder rewrite and real metrics from the codebase, we examine both achievements and challenges.</p>

<h2 id="introduction-understanding-rusts-current-role-in-the-kernel">Introduction: Understanding Rust’s Current Role in the Kernel</h2>

<p>A common discussion in developer communities centers around several observations: <em>“Rust is currently being used for device drivers, not the kernel core. Using <code class="language-plaintext highlighter-rouge">unsafe</code> to interface with C may add complexity compared to writing directly in C or Zig. It’s unclear whether Rust will expand into core kernel development.”</em></p>

<p>These are legitimate questions that deserve data-driven answers. To understand Rust’s current state and future trajectory in Linux, we need to examine both what has been achieved and what challenges remain. Let’s look at the actual kernel codebase as of Linux 6.x.</p>

<h2 id="the-numbers-rusts-actual-penetration">The Numbers: Rust’s Actual Penetration</h2>

<p>Based on comprehensive analysis using cloc v2.04 on the Linux kernel source tree (Linux 6.x), here’s the reality:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Total Rust files:        163 .rs files
Lines of code:           20,064 lines (pure code, excluding comments/blanks)
Total lines:             41,907 lines (including 17,760 comment lines)
Kernel abstraction modules: 74 modules across rust/kernel/
Production drivers:      17 driver files
Build infrastructure:    9 macro files + 15 pin-init files
</code></pre></div></div>

<p><strong>Distribution breakdown (by lines of code):</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rust/kernel/           13,500 lines (67.3%) - Core abstraction layer
rust/pin-init/          2,435 lines (12.1%) - Pin initialization infrastructure
drivers/                1,913 lines ( 9.5%) - Production drivers
rust/macros/              894 lines ( 4.5%) - Procedural macros
samples/rust/             758 lines ( 3.8%) - Example code
Other (scripts, etc)      564 lines ( 2.8%) - Supporting code
</code></pre></div></div>

<p><strong>Total line counts (with comments and blanks):</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rust/kernel/           30,858 lines (101 files) - Includes 14,290 comment lines
drivers/                2,602 lines ( 17 files) - Production Rust drivers
rust/pin-init/          4,826 lines ( 15 files) - Memory safety infrastructure
rust/macros/            1,541 lines (  9 files) - Compile-time code generation
samples/rust/           1,179 lines ( 12 files) - Learning examples
Other                     901 lines (  9 files) - Scripts and utilities
</code></pre></div></div>

<p>This is not a toy experiment. This is <strong>production-grade infrastructure</strong> covering 74 kernel subsystems.</p>

<h3 id="the-74-kernel-abstraction-modules-rustkernel">The 74 Kernel Abstraction Modules (<code class="language-plaintext highlighter-rouge">rust/kernel/</code>)</h3>

<p>The core abstraction layer provides safe Rust interfaces to kernel functionality:</p>

<p><strong>Hardware &amp; Device Management (19 modules):</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">acpi</code> - ACPI (Advanced Configuration and Power Interface) support</li>
  <li><code class="language-plaintext highlighter-rouge">auxiliary</code> - Auxiliary bus support</li>
  <li><code class="language-plaintext highlighter-rouge">clk</code> - Clock framework abstractions</li>
  <li><code class="language-plaintext highlighter-rouge">cpu</code> - CPU management</li>
  <li><code class="language-plaintext highlighter-rouge">cpufreq</code> - CPU frequency scaling</li>
  <li><code class="language-plaintext highlighter-rouge">dma</code> - DMA (Direct Memory Access) mapping</li>
  <li><code class="language-plaintext highlighter-rouge">device</code> - Device model core abstractions</li>
  <li><code class="language-plaintext highlighter-rouge">firmware</code> - Firmware loading interface</li>
  <li><code class="language-plaintext highlighter-rouge">i2c</code> - I2C bus support</li>
  <li><code class="language-plaintext highlighter-rouge">irq</code> - Interrupt handling</li>
  <li><code class="language-plaintext highlighter-rouge">pci</code> - PCI bus support</li>
  <li><code class="language-plaintext highlighter-rouge">platform</code> - Platform device abstractions</li>
  <li><code class="language-plaintext highlighter-rouge">power</code> - Power management</li>
  <li><code class="language-plaintext highlighter-rouge">regulator</code> - Voltage regulator framework</li>
  <li><code class="language-plaintext highlighter-rouge">reset</code> - Reset controller framework</li>
  <li><code class="language-plaintext highlighter-rouge">security</code> - Security framework hooks</li>
  <li><code class="language-plaintext highlighter-rouge">spi</code> - SPI bus support</li>
  <li><code class="language-plaintext highlighter-rouge">xarray</code> - XArray (resizable array) data structure</li>
  <li><code class="language-plaintext highlighter-rouge">of</code> - Device tree (Open Firmware) support</li>
</ul>

<p><strong>Graphics &amp; Display (8 modules):</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">drm</code> - Direct Rendering Manager core</li>
  <li><code class="language-plaintext highlighter-rouge">drm::allocator</code> - DRM memory allocator</li>
  <li><code class="language-plaintext highlighter-rouge">drm::device</code> - DRM device management</li>
  <li><code class="language-plaintext highlighter-rouge">drm::drv</code> - DRM driver registration</li>
  <li><code class="language-plaintext highlighter-rouge">drm::file</code> - DRM file operations</li>
  <li><code class="language-plaintext highlighter-rouge">drm::gem</code> - Graphics Execution Manager (memory management)</li>
  <li><code class="language-plaintext highlighter-rouge">drm::ioctl</code> - DRM ioctl handling</li>
  <li><code class="language-plaintext highlighter-rouge">drm::mm</code> - DRM memory manager</li>
</ul>

<p><strong>Networking (5 modules):</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">net</code> - Core networking abstractions</li>
  <li><code class="language-plaintext highlighter-rouge">net::phy</code> - PHY (Physical layer) device support</li>
  <li><code class="language-plaintext highlighter-rouge">net::dev</code> - Network device abstractions</li>
  <li><code class="language-plaintext highlighter-rouge">netdevice</code> - Network device interface</li>
  <li><code class="language-plaintext highlighter-rouge">ethtool</code> - Ethtool interface for network configuration</li>
</ul>

<p><strong>Storage &amp; File Systems (9 modules):</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">block</code> - Block device layer</li>
  <li><code class="language-plaintext highlighter-rouge">block::mq</code> - Multi-queue block layer</li>
  <li><code class="language-plaintext highlighter-rouge">fs</code> - File system abstractions</li>
  <li><code class="language-plaintext highlighter-rouge">configfs</code> - Configuration file system</li>
  <li><code class="language-plaintext highlighter-rouge">debugfs</code> - Debug file system</li>
  <li><code class="language-plaintext highlighter-rouge">folio</code> - Page folio support (memory management)</li>
  <li><code class="language-plaintext highlighter-rouge">page</code> - Page management</li>
  <li><code class="language-plaintext highlighter-rouge">pages</code> - Multi-page handling</li>
  <li><code class="language-plaintext highlighter-rouge">seq_file</code> - Sequential file interface</li>
</ul>

<p><strong>Synchronization &amp; Concurrency (7 modules):</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">sync</code> - Synchronization primitives</li>
  <li><code class="language-plaintext highlighter-rouge">sync::arc</code> - Atomic reference counting</li>
  <li><code class="language-plaintext highlighter-rouge">sync::lock</code> - Lock abstractions</li>
  <li><code class="language-plaintext highlighter-rouge">sync::condvar</code> - Condition variables</li>
  <li><code class="language-plaintext highlighter-rouge">sync::poll</code> - Polling support</li>
  <li><code class="language-plaintext highlighter-rouge">rcu</code> - Read-Copy-Update synchronization</li>
  <li><code class="language-plaintext highlighter-rouge">workqueue</code> - Deferred work execution</li>
</ul>

<p><strong>Memory Management (5 modules):</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">alloc</code> - Memory allocation</li>
  <li><code class="language-plaintext highlighter-rouge">mm</code> - Memory management core</li>
  <li><code class="language-plaintext highlighter-rouge">kasync</code> - Asynchronous memory allocation</li>
  <li><code class="language-plaintext highlighter-rouge">vmalloc</code> - Virtual memory allocation</li>
  <li><code class="language-plaintext highlighter-rouge">static_call</code> - Static call optimization</li>
</ul>

<p><strong>Core Kernel Services (11 modules):</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">cred</code> - Credential management</li>
  <li><code class="language-plaintext highlighter-rouge">kunit</code> - Kernel unit testing framework</li>
  <li><code class="language-plaintext highlighter-rouge">module</code> - Kernel module support</li>
  <li><code class="language-plaintext highlighter-rouge">panic</code> - Panic handling</li>
  <li><code class="language-plaintext highlighter-rouge">pid</code> - Process ID management</li>
  <li><code class="language-plaintext highlighter-rouge">task</code> - Task/process management</li>
  <li><code class="language-plaintext highlighter-rouge">time</code> - Time management</li>
  <li><code class="language-plaintext highlighter-rouge">timer</code> - Timer support</li>
  <li><code class="language-plaintext highlighter-rouge">pid_namespace</code> - PID namespace support</li>
  <li><code class="language-plaintext highlighter-rouge">user</code> - User structure abstractions</li>
  <li><code class="language-plaintext highlighter-rouge">uidgid</code> - User/Group ID handling</li>
</ul>

<p><strong>Low-level Infrastructure (10 modules):</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">bindings</code> - Auto-generated C bindings</li>
  <li><code class="language-plaintext highlighter-rouge">build_assert</code> - Compile-time assertions</li>
  <li><code class="language-plaintext highlighter-rouge">build_error</code> - Compile-time error generation</li>
  <li><code class="language-plaintext highlighter-rouge">error</code> - Error handling (kernel error codes)</li>
  <li><code class="language-plaintext highlighter-rouge">init</code> - Initialization macros</li>
  <li><code class="language-plaintext highlighter-rouge">ioctl</code> - ioctl command handling</li>
  <li><code class="language-plaintext highlighter-rouge">prelude</code> - Common imports</li>
  <li><code class="language-plaintext highlighter-rouge">print</code> - Kernel printing (pr_info, pr_err, etc.)</li>
  <li><code class="language-plaintext highlighter-rouge">static_assert</code> - Static assertions</li>
  <li><code class="language-plaintext highlighter-rouge">str</code> - String handling</li>
</ul>

<p><strong>Data Structures &amp; Utilities:</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">kuid</code> - Kernel user ID</li>
  <li><code class="language-plaintext highlighter-rouge">kgid</code> - Kernel group ID</li>
  <li><code class="language-plaintext highlighter-rouge">list</code> - Linked list abstractions</li>
  <li><code class="language-plaintext highlighter-rouge">miscdevice</code> - Miscellaneous device support</li>
  <li><code class="language-plaintext highlighter-rouge">revocable</code> - Revocable resources</li>
  <li><code class="language-plaintext highlighter-rouge">types</code> - Core type definitions</li>
</ul>

<h3 id="the-17-production-drivers-1913-lines-of-code">The 17 Production Drivers (1,913 lines of code)</h3>

<p><strong>GPU Drivers (13 files):</strong></p>
<ul>
  <li><strong>Nova</strong> (Nvidia GSP firmware driver):
    <ul>
      <li><code class="language-plaintext highlighter-rouge">drivers/gpu/drm/nova/</code> (5 files): DRM integration layer
        <ul>
          <li><code class="language-plaintext highlighter-rouge">nova.rs</code>, <code class="language-plaintext highlighter-rouge">driver.rs</code>, <code class="language-plaintext highlighter-rouge">gem.rs</code>, <code class="language-plaintext highlighter-rouge">uapi.rs</code>, <code class="language-plaintext highlighter-rouge">file.rs</code></li>
        </ul>
      </li>
      <li><code class="language-plaintext highlighter-rouge">drivers/gpu/nova-core/</code> (7 files): Core GPU driver logic
        <ul>
          <li><code class="language-plaintext highlighter-rouge">nova_core.rs</code>, <code class="language-plaintext highlighter-rouge">driver.rs</code>, <code class="language-plaintext highlighter-rouge">gpu.rs</code>, <code class="language-plaintext highlighter-rouge">firmware.rs</code>, <code class="language-plaintext highlighter-rouge">util.rs</code></li>
          <li><code class="language-plaintext highlighter-rouge">regs.rs</code>, <code class="language-plaintext highlighter-rouge">regs/macros.rs</code> - Register access abstractions</li>
        </ul>
      </li>
      <li><code class="language-plaintext highlighter-rouge">drivers/gpu/drm/drm_panic_qr.rs</code> - QR code panic screen (996 lines)</li>
    </ul>
  </li>
</ul>

<p><strong>Network Drivers (2 files):</strong></p>
<ul>
  <li><strong>PHY Drivers</strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">ax88796b_rust.rs</code> (134 lines) - ASIX Electronics PHY driver (AX88772A/AX88772C/AX88796B)</li>
      <li><code class="language-plaintext highlighter-rouge">qt2025.rs</code> (103 lines) - Marvell QT2025 PHY driver</li>
    </ul>
  </li>
</ul>

<p><strong>Other Drivers (2 files):</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">cpufreq/rcpufreq_dt.rs</code> (227 lines) - Device tree-based CPU frequency driver</li>
  <li><code class="language-plaintext highlighter-rouge">block/rnull.rs</code> (80 lines) - Rust null block device (testing/example)</li>
</ul>

<p>Note: The Android Binder driver mentioned in case studies below is currently in development/out-of-tree and not yet merged into mainline Linux 6.x. The production driver count reflects only in-tree drivers as of the current kernel version.</p>

<p>This comprehensive infrastructure demonstrates that Rust in Linux has moved far beyond experimentation into production deployment across critical subsystems. Let’s examine actual kernel code to understand what “Rust in the kernel” really means.</p>

<h2 id="case-study-1-android-binder---production-rust-in-action">Case Study 1: Android Binder - Production Rust in Action</h2>

<p>The Android Binder IPC mechanism is one of the most critical components of the Android ecosystem. Google has rewritten it entirely in Rust. Here’s what the actual code looks like:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// drivers/android/binder/rust_binder_main.rs</span>
<span class="c1">// Copyright (C) 2025 Google LLC.</span>

<span class="k">use</span> <span class="nn">kernel</span><span class="p">::{</span>
    <span class="nn">bindings</span><span class="p">::{</span><span class="k">self</span><span class="p">,</span> <span class="n">seq_file</span><span class="p">},</span>
    <span class="nn">fs</span><span class="p">::</span><span class="n">File</span><span class="p">,</span>
    <span class="nn">list</span><span class="p">::{</span><span class="n">ListArc</span><span class="p">,</span> <span class="n">ListArcSafe</span><span class="p">,</span> <span class="n">ListLinksSelfPtr</span><span class="p">,</span> <span class="n">TryNewListArc</span><span class="p">},</span>
    <span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">,</span>
    <span class="nn">seq_file</span><span class="p">::</span><span class="n">SeqFile</span><span class="p">,</span>
    <span class="nn">sync</span><span class="p">::</span><span class="nn">poll</span><span class="p">::</span><span class="n">PollTable</span><span class="p">,</span>
    <span class="nn">sync</span><span class="p">::</span><span class="nb">Arc</span><span class="p">,</span>
    <span class="nn">task</span><span class="p">::</span><span class="n">Pid</span><span class="p">,</span>
    <span class="nn">types</span><span class="p">::</span><span class="n">ForeignOwnable</span><span class="p">,</span>
    <span class="nn">uaccess</span><span class="p">::</span><span class="n">UserSliceWriter</span><span class="p">,</span>
<span class="p">};</span>

<span class="nd">module!</span> <span class="p">{</span>
    <span class="k">type</span><span class="p">:</span> <span class="n">BinderModule</span><span class="p">,</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"rust_binder"</span><span class="p">,</span>
    <span class="n">authors</span><span class="p">:</span> <span class="p">[</span><span class="s">"Wedson Almeida Filho"</span><span class="p">,</span> <span class="s">"Alice Ryhl"</span><span class="p">],</span>
    <span class="n">description</span><span class="p">:</span> <span class="s">"Android Binder"</span><span class="p">,</span>
    <span class="n">license</span><span class="p">:</span> <span class="s">"GPL"</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Module structure</strong> (from actual source):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>drivers/android/binder/
├── rust_binder_main.rs    (611 lines - main module)
├── process.rs              (1,745 lines - largest file)
├── thread.rs               (1,596 lines)
├── node.rs                 (1,131 lines)
├── transaction.rs          (456 lines)
├── allocation.rs           (602 lines)
├── page_range.rs           (734 lines)
├── range_alloc/tree.rs     (488 lines - allocator)
└── [other modules]
</code></pre></div></div>

<h3 id="understanding-unsafe-in-practice">Understanding “Unsafe” in Practice</h3>

<p>A common concern is whether using <code class="language-plaintext highlighter-rouge">unsafe</code> in Rust to call C APIs adds development complexity. Let’s examine the actual numbers from the Binder driver:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">grep</span> <span class="nt">-r</span> <span class="s2">"unsafe"</span> drivers/android/binder/<span class="k">*</span>.rs | <span class="nb">wc</span> <span class="nt">-l</span>
179 occurrences of <span class="s1">'unsafe'</span> across 11 files
</code></pre></div></div>

<p>That’s <strong>179 <code class="language-plaintext highlighter-rouge">unsafe</code> blocks in approximately 8,000 lines of code</strong> - roughly 2-3% of the codebase.</p>

<p><strong>The key difference from C</strong>: In C, all code operates without memory safety guarantees from the compiler. In Rust, approximately 97-98% of the Binder code receives compile-time safety verification, with unsafe operations explicitly marked and isolated to specific locations.</p>

<p>Let’s examine how this looks in practice:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// drivers/android/binder/process.rs (actual kernel code)</span>
<span class="k">use</span> <span class="nn">kernel</span><span class="p">::{</span>
    <span class="nn">sync</span><span class="p">::{</span>
        <span class="nn">lock</span><span class="p">::{</span><span class="nn">spinlock</span><span class="p">::</span><span class="n">SpinLockBackend</span><span class="p">,</span> <span class="n">Guard</span><span class="p">},</span>
        <span class="nb">Arc</span><span class="p">,</span> <span class="n">ArcBorrow</span><span class="p">,</span> <span class="n">CondVar</span><span class="p">,</span> <span class="n">Mutex</span><span class="p">,</span> <span class="n">SpinLock</span><span class="p">,</span> <span class="n">UniqueArc</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="nn">types</span><span class="p">::</span><span class="n">ARef</span><span class="p">,</span>
<span class="p">};</span>

<span class="nd">#[derive(Copy,</span> <span class="nd">Clone)]</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">enum</span> <span class="n">IsFrozen</span> <span class="p">{</span>
    <span class="n">Yes</span><span class="p">,</span>
    <span class="n">No</span><span class="p">,</span>
    <span class="n">InProgress</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">IsFrozen</span> <span class="p">{</span>
    <span class="cd">/// Whether incoming transactions should be rejected due to freeze.</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">is_frozen</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">bool</span> <span class="p">{</span>
        <span class="k">match</span> <span class="k">self</span> <span class="p">{</span>
            <span class="nn">IsFrozen</span><span class="p">::</span><span class="n">Yes</span> <span class="k">=&gt;</span> <span class="k">true</span><span class="p">,</span>
            <span class="nn">IsFrozen</span><span class="p">::</span><span class="n">No</span> <span class="k">=&gt;</span> <span class="k">false</span><span class="p">,</span>
            <span class="nn">IsFrozen</span><span class="p">::</span><span class="n">InProgress</span> <span class="k">=&gt;</span> <span class="k">true</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Notice something? <strong>This is pure safe Rust</strong> - no <code class="language-plaintext highlighter-rouge">unsafe</code> blocks, yet it’s core kernel logic. The type system ensures:</p>
<ul>
  <li>No null pointer dereferences</li>
  <li>No use-after-free</li>
  <li>No data races</li>
  <li>No uninitialized memory access</li>
</ul>

<p><strong>All enforced at compile time, not runtime.</strong></p>

<h2 id="case-study-2-lock-abstractions---raii-in-the-kernel">Case Study 2: Lock Abstractions - RAII in the Kernel</h2>

<p>One of the most powerful Rust features for kernel development is RAII (Resource Acquisition Is Initialization). Here’s the actual abstraction layer from <code class="language-plaintext highlighter-rouge">rust/kernel/sync/lock.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// rust/kernel/sync/lock.rs (actual kernel code)</span>
<span class="cd">/// The "backend" of a lock.</span>
<span class="cd">///</span>
<span class="cd">/// # Safety</span>
<span class="cd">///</span>
<span class="cd">/// - Implementers must ensure that only one thread/CPU may access the protected</span>
<span class="cd">///   data once the lock is owned, that is, between calls to `lock` and `unlock`.</span>
<span class="cd">/// - Implementers must also ensure that `relock` uses the same locking method as</span>
<span class="cd">///   the original lock operation.</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">trait</span> <span class="n">Backend</span> <span class="p">{</span>
    <span class="cd">/// The state required by the lock.</span>
    <span class="k">type</span> <span class="n">State</span><span class="p">;</span>

    <span class="cd">/// The state required to be kept between `lock` and `unlock`.</span>
    <span class="k">type</span> <span class="n">GuardState</span><span class="p">;</span>

    <span class="cd">/// Acquires the lock, making the caller its owner.</span>
    <span class="cd">///</span>
    <span class="cd">/// # Safety</span>
    <span class="cd">///</span>
    <span class="cd">/// Callers must ensure that [`Backend::init`] has been previously called.</span>
    <span class="nd">#[must_use]</span>
    <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">lock</span><span class="p">(</span><span class="n">ptr</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="k">Self</span><span class="p">::</span><span class="n">State</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span><span class="p">::</span><span class="n">GuardState</span><span class="p">;</span>

    <span class="cd">/// Releases the lock, giving up its ownership.</span>
    <span class="cd">///</span>
    <span class="cd">/// # Safety</span>
    <span class="cd">///</span>
    <span class="cd">/// It must only be called by the current owner of the lock.</span>
    <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">unlock</span><span class="p">(</span><span class="n">ptr</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="k">Self</span><span class="p">::</span><span class="n">State</span><span class="p">,</span> <span class="n">guard_state</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">Self</span><span class="p">::</span><span class="n">GuardState</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Building on the three-layer architecture explained above, the <code class="language-plaintext highlighter-rouge">Backend</code> trait provides the unsafe low-level interface. Driver developers use the safe high-level API:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Safe to use in driver code - compiler prevents forgetting to unlock</span>
<span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">guard</span> <span class="o">=</span> <span class="n">spinlock</span><span class="nf">.lock</span><span class="p">();</span> <span class="c1">// Acquire lock</span>

    <span class="k">if</span> <span class="n">error_condition</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="n">EINVAL</span><span class="p">);</span> <span class="c1">// Early return</span>
        <span class="c1">// Guard dropped here - lock AUTOMATICALLY released</span>
    <span class="p">}</span>

    <span class="nf">do_critical_work</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">guard</span><span class="p">)</span><span class="o">?</span><span class="p">;</span> <span class="c1">// If this fails and returns</span>
    <span class="c1">// Guard dropped here - lock AUTOMATICALLY released</span>

<span class="p">}</span> <span class="c1">// Normal exit - lock automatically released</span>
</code></pre></div></div>

<p><strong>In C, the equivalent would be:</strong></p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C version - manual, error-prone</span>
<span class="n">spin_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">lock</span><span class="p">);</span>

<span class="k">if</span> <span class="p">(</span><span class="n">error_condition</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">spin_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">lock</span><span class="p">);</span>  <span class="c1">// Must remember to unlock!</span>
    <span class="k">return</span> <span class="o">-</span><span class="n">EINVAL</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">ret</span> <span class="o">=</span> <span class="n">do_critical_work</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">spin_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">lock</span><span class="p">);</span>  <span class="c1">// Must remember to unlock!</span>
    <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">spin_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">lock</span><span class="p">);</span>  <span class="c1">// Must remember to unlock!</span>
</code></pre></div></div>

<p><strong>Every single <code class="language-plaintext highlighter-rouge">return</code> path requires manual unlock.</strong> Miss one, and you have a deadlock. Code analysis tools can catch some of these, but the C compiler provides <em>zero</em> guarantees.</p>

<p>The Rust compiler, on the other hand, makes it <strong>impossible</strong> to forget the unlock. This isn’t “mental burden” - this is <strong>eliminating an entire class of bugs at compile time</strong>.</p>

<h2 id="examining-common-questions">Examining Common Questions</h2>

<h3 id="question-1-rust-is-only-for-drivers-not-the-kernel-core">Question 1: “Rust is only for drivers, not the kernel core”</h3>

<p><strong>Current status</strong>: This is accurate for now, and it reflects the planned adoption strategy.</p>

<p>The Linux kernel contains approximately 30 million lines of C code. Immediate replacement of core kernel components was never the goal. Instead, the approach follows a <strong>gradual, methodical adoption pattern</strong>:</p>

<p><strong>Phase 1 (2022-2026)</strong>: Infrastructure &amp; drivers</p>
<ul>
  <li>✅ Build system integration (695-line Makefile, Kconfig integration)</li>
  <li>✅ Kernel abstraction layer (74 modules, 45,622 lines)</li>
  <li>✅ Production drivers (Android Binder, Nvidia Nova GPU, network PHY)</li>
  <li>✅ Testing framework (KUnit integration, doctests)</li>
</ul>

<p><strong>Phase 2 (2026-2028)</strong>: Subsystem expansion (currently happening)</p>
<ul>
  <li>🔄 File system drivers (Rust ext4, btrfs experiments)</li>
  <li>🔄 Network protocol components</li>
  <li>🔄 More architecture support (currently: x86_64, ARM64, RISC-V, LoongArch, PowerPC, s390)</li>
</ul>

<p><strong>Phase 3 (2028-2030+)</strong>: Core kernel components</p>
<ul>
  <li>🔮 Memory management subsystems</li>
  <li>🔮 Scheduler components</li>
  <li>🔮 VFS layer rewrites</li>
</ul>

<p>This is <strong>exactly how C++ adoption has worked in other massive systems</strong> (Windows kernel, browsers, databases). You start at the edges, build confidence, and gradually move inward.</p>

<p>The community’s stance on alternative languages is notable. While there’s no explicit exclusion of other systems languages like Zig, the reality is that <strong>no team is actively working on integrating them</strong><sup id="fnref:10"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>. Rust succeeded because it had:</p>
<ol>
  <li><strong>A dedicated team</strong> working for years (Rust for Linux project, started 2020)</li>
  <li><strong>Corporate backing</strong> (Google, Microsoft, Arm)</li>
  <li><strong>Production use cases</strong> (Android Binder was the killer app)</li>
</ol>

<p>Zig could theoretically follow the same path if someone invested the effort. The door isn’t closed - but the work is substantial, requiring similar multi-year investment and corporate backing that Rust received.</p>

<h3 id="question-2-using-unsafe-in-rust-adds-complexity-compared-to-c">Question 2: “Using <code class="language-plaintext highlighter-rouge">unsafe</code> in Rust adds complexity compared to C”</h3>

<p><strong>Let’s compare the development considerations</strong>: When evaluating cognitive load, we should consider what developers need to track:</p>

<p><strong>C kernel development mental checklist</strong> (100% of code):</p>
<ul>
  <li>✅ Did I check for NULL before dereferencing?</li>
  <li>✅ Did I pair every <code class="language-plaintext highlighter-rouge">kmalloc</code> with <code class="language-plaintext highlighter-rouge">kfree</code>?</li>
  <li>✅ Did I unlock every spinlock on every error path?</li>
  <li>✅ Is this pointer still valid? (no compiler help)</li>
  <li>✅ Did I initialize this variable?</li>
  <li>✅ Is this buffer access within bounds?</li>
  <li>✅ Are these types actually compatible? (manual casting)</li>
  <li>✅ Could this integer overflow?</li>
  <li>✅ Is there a race condition here? (manual reasoning)</li>
</ul>

<p><strong>Rust kernel development considerations</strong>:</p>
<ul>
  <li>For the 2-5% unsafe code: Verify safety invariants documented in unsafe blocks</li>
  <li>For the 95-98% safe code: Compiler enforces memory safety and concurrency rules</li>
</ul>

<p><strong>Perspective from kernel maintainer Greg Kroah-Hartman</strong> (February 2025)<sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>:</p>
<blockquote>
  <p>“The majority of bugs (quantity, not quality and severity) we have are due to the stupid little corner cases in C that are totally gone in Rust. Things like simple overwrites of memory (not that Rust can catch all of these by far), error path cleanups, forgetting to check error values, and use-after-free mistakes.”</p>

  <p>“Writing new code in Rust is a win for all of us.”</p>
</blockquote>

<p>The trade-off: C provides familiar syntax and complete manual control, while Rust provides compile-time verification for most code at the cost of learning the ownership system and dealing with explicit unsafe boundaries when interfacing with C APIs.</p>

<h3 id="question-3-why-not-zig-or-other-systems-languages">Question 3: “Why not Zig or other systems languages?”</h3>

<p>Zig’s philosophy as “better C” - with explicit control, zero hidden behavior, and excellent tooling - makes it an interesting alternative. The comparison is worth examining:</p>

<p><strong>Zig’s approach to memory safety:</strong></p>
<ul>
  <li>Manual memory management (like C)</li>
  <li><code class="language-plaintext highlighter-rouge">defer</code> for cleanup (helpful, but optional)</li>
  <li>Compile-time checks for control flow (great!)</li>
  <li>Runtime checks for bounds/overflow (can be disabled in release builds)</li>
</ul>

<p><strong>Rust’s approach to memory safety:</strong></p>
<ul>
  <li>Ownership system (enforced at compile time)</li>
  <li>Automatic cleanup via <code class="language-plaintext highlighter-rouge">Drop</code> trait (mandatory)</li>
  <li>Borrow checker prevents data races (compile-time guarantee)</li>
  <li>No runtime overhead for safety (zero-cost abstractions)</li>
</ul>

<p>For Linux kernel requirements, Rust’s <strong>mandatory, compile-time safety</strong> aligns with the goal of preventing memory safety vulnerabilities. Research shows approximately 70% of kernel CVEs are memory safety issues<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>. Rust addresses these at compile time, while Zig provides optional runtime checks and better ergonomics than C.</p>

<p>The community’s stance on alternative languages is notable. While there’s no explicit exclusion of other systems languages like Zig, no team is currently actively working on integrating them<sup id="fnref:10:1"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>. Rust succeeded through:</p>
<ol>
  <li>Dedicated team effort (Rust for Linux project, started 2020)</li>
  <li>Corporate backing (Google, Microsoft, Arm)</li>
  <li>Production use cases (Android Binder demonstrated viability)</li>
</ol>

<p>Any alternative language would need similar investment: building kernel abstractions (equivalent to 74 modules, 45,622 lines), proving production-readiness, and maintaining long-term commitment. The path is technically open, but requires substantial resources.</p>

<h2 id="the-actual-kernel-code-architecture">The Actual Kernel Code Architecture</h2>

<h3 id="understanding-the-three-layer-architecture">Understanding the Three-Layer Architecture</h3>

<p>The Rust kernel infrastructure follows a clear three-layer architecture that safely wraps C kernel APIs:</p>

<p><strong>Layer 1: C Kernel APIs (底层C内核)</strong></p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Native Linux kernel C functions</span>
<span class="kt">void</span> <span class="nf">spin_lock</span><span class="p">(</span><span class="n">spinlock_t</span> <span class="o">*</span><span class="n">lock</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">spin_unlock</span><span class="p">(</span><span class="n">spinlock_t</span> <span class="o">*</span><span class="n">lock</span><span class="p">);</span>
<span class="kt">int</span> <span class="nf">genphy_soft_reset</span><span class="p">(</span><span class="k">struct</span> <span class="n">phy_device</span> <span class="o">*</span><span class="n">phydev</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>Layer 2: Auto-generated C Bindings (<code class="language-plaintext highlighter-rouge">rust/bindings/</code>)</strong></p>

<p>The <code class="language-plaintext highlighter-rouge">rust/bindings/bindings_helper.h</code> file specifies which C headers to bind:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;linux/spinlock.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/mutex.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/phy.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;drm/drm_device.h&gt;</span><span class="cp">
</span><span class="c1">// ... 80+ kernel headers</span>
</code></pre></div></div>

<p>The <strong>bindgen</strong> tool automatically generates Rust FFI (Foreign Function Interface) declarations:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Generated in rust/bindings/bindings_generated.rs</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">spin_lock</span><span class="p">(</span><span class="n">ptr</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="n">spinlock_t</span><span class="p">);</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">spin_unlock</span><span class="p">(</span><span class="n">ptr</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="n">spinlock_t</span><span class="p">);</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">genphy_soft_reset</span><span class="p">(</span><span class="n">phydev</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="n">phy_device</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">c_int</span><span class="p">;</span>
</code></pre></div></div>

<p><strong>Layer 3: Safe Rust Abstractions (<code class="language-plaintext highlighter-rouge">rust/kernel/</code>)</strong></p>

<p>This is the critical layer that wraps unsafe C calls into safe Rust APIs. For example, <code class="language-plaintext highlighter-rouge">rust/kernel/sync/lock/spinlock.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Unsafe wrapper (used internally)</span>
<span class="k">unsafe</span> <span class="k">impl</span> <span class="k">super</span><span class="p">::</span><span class="n">Backend</span> <span class="k">for</span> <span class="n">SpinLockBackend</span> <span class="p">{</span>
    <span class="k">type</span> <span class="n">State</span> <span class="o">=</span> <span class="nn">bindings</span><span class="p">::</span><span class="n">spinlock_t</span><span class="p">;</span>  <span class="c1">// ← C type</span>

    <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">lock</span><span class="p">(</span><span class="n">ptr</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="k">Self</span><span class="p">::</span><span class="n">State</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span><span class="p">::</span><span class="n">GuardState</span> <span class="p">{</span>
        <span class="c1">// ↓ Call underlying C function (unsafe)</span>
        <span class="k">unsafe</span> <span class="p">{</span> <span class="nn">bindings</span><span class="p">::</span><span class="nf">spin_lock</span><span class="p">(</span><span class="n">ptr</span><span class="p">)</span> <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">unlock</span><span class="p">(</span><span class="n">ptr</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="k">Self</span><span class="p">::</span><span class="n">State</span><span class="p">,</span> <span class="n">_guard_state</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">Self</span><span class="p">::</span><span class="n">GuardState</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">unsafe</span> <span class="p">{</span> <span class="nn">bindings</span><span class="p">::</span><span class="nf">spin_unlock</span><span class="p">(</span><span class="n">ptr</span><span class="p">)</span> <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// Safe public API (used by drivers)</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">SpinLock</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="n">inner</span><span class="p">:</span> <span class="n">Opaque</span><span class="o">&lt;</span><span class="nn">bindings</span><span class="p">::</span><span class="n">spinlock_t</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">data</span><span class="p">:</span> <span class="n">UnsafeCell</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="n">SpinLock</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="cd">/// Acquire the lock and return RAII guard</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">lock</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Guard</span><span class="o">&lt;</span><span class="nv">'_</span><span class="p">,</span> <span class="n">T</span><span class="p">,</span> <span class="n">SpinLockBackend</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// Guard automatically releases lock on drop</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>The Call Chain in Practice:</strong></p>

<p>When a driver calls a Rust API, here’s what happens behind the scenes:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Driver code (100% safe Rust):
  dev.genphy_soft_reset()
      ↓
rust/kernel/net/phy.rs (safe wrapper):
  pub fn genphy_soft_reset(&amp;mut self) -&gt; Result {
      to_result(unsafe { bindings::genphy_soft_reset(self.as_ptr()) })
  }
      ↓
rust/bindings/ (unsafe FFI):
  pub unsafe fn genphy_soft_reset(phydev: *mut phy_device) -&gt; c_int;
      ↓
C kernel (native implementation):
  int genphy_soft_reset(struct phy_device *phydev) { ... }
</code></pre></div></div>

<p><strong>Key Statistics:</strong></p>
<ul>
  <li><strong>Layer 2</strong> (<code class="language-plaintext highlighter-rouge">rust/bindings/</code>): Auto-generated, ~80+ C headers wrapped</li>
  <li><strong>Layer 3</strong> (<code class="language-plaintext highlighter-rouge">rust/kernel/</code>): 13,500 lines of safe abstractions (67.3% of Rust code)</li>
  <li><strong>Driver code</strong>: 1,913 lines (9.5% of Rust code) - uses safe APIs only</li>
</ul>

<p>This architecture ensures that:</p>
<ol>
  <li><strong>Unsafe code is isolated</strong>: All unsafe C FFI calls are contained in <code class="language-plaintext highlighter-rouge">rust/kernel/</code></li>
  <li><strong>Type safety</strong>: Rust’s type system (enums, Option, Result) prevents invalid states</li>
  <li><strong>RAII guarantees</strong>: Resources (locks, memory) are automatically managed</li>
  <li><strong>Zero-cost abstractions</strong>: Compiles to the same assembly as hand-written C</li>
</ol>

<p>Let’s examine the actual code structure. From <code class="language-plaintext highlighter-rouge">rust/kernel/lib.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// SPDX-License-Identifier: GPL-2.0</span>

<span class="cd">//! The `kernel` crate.</span>
<span class="cd">//!</span>
<span class="cd">//! This crate contains the kernel APIs that have been ported or wrapped for</span>
<span class="cd">//! usage by Rust code in the kernel and is shared by all of them.</span>

<span class="nd">#![no_std]</span>  <span class="c1">// No standard library - pure kernel mode</span>

<span class="c1">// Subsystem abstractions (partial list from actual kernel)</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">acpi</span><span class="p">;</span>           <span class="c1">// ACPI support</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">alloc</span><span class="p">;</span>          <span class="c1">// Memory allocation</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">auxiliary</span><span class="p">;</span>      <span class="c1">// Auxiliary bus</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">block</span><span class="p">;</span>          <span class="c1">// Block device layer</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">clk</span><span class="p">;</span>            <span class="c1">// Clock framework</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">configfs</span><span class="p">;</span>       <span class="c1">// ConfigFS</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">cpu</span><span class="p">;</span>            <span class="c1">// CPU management</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">cpufreq</span><span class="p">;</span>        <span class="c1">// CPU frequency</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">device</span><span class="p">;</span>         <span class="c1">// Device model core</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">dma</span><span class="p">;</span>            <span class="c1">// DMA mapping</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">drm</span><span class="p">;</span>            <span class="c1">// Direct Rendering Manager (8 submodules)</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">firmware</span><span class="p">;</span>       <span class="c1">// Firmware loading</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">fs</span><span class="p">;</span>             <span class="c1">// File system abstractions</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">i2c</span><span class="p">;</span>            <span class="c1">// I2C bus</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">irq</span><span class="p">;</span>            <span class="c1">// Interrupt handling</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">list</span><span class="p">;</span>           <span class="c1">// Kernel linked lists</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">mm</span><span class="p">;</span>             <span class="c1">// Memory management</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">net</span><span class="p">;</span>            <span class="c1">// Network stack abstractions</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">pci</span><span class="p">;</span>            <span class="c1">// PCI bus</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">platform</span><span class="p">;</span>       <span class="c1">// Platform devices</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">sync</span><span class="p">;</span>           <span class="c1">// Synchronization primitives</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">task</span><span class="p">;</span>           <span class="c1">// Task management</span>
<span class="c1">// ... 74 modules total</span>
</code></pre></div></div>

<p>This is <strong>comprehensive infrastructure</strong> - not a proof-of-concept. Each module provides safe abstractions over C kernel APIs.</p>

<h3 id="example-network-phy-driver-abstraction">Example: Network PHY Driver Abstraction</h3>

<p>From <code class="language-plaintext highlighter-rouge">rust/kernel/net/phy.rs</code> (actual kernel code):</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">struct</span> <span class="nf">Device</span><span class="p">(</span><span class="n">Opaque</span><span class="o">&lt;</span><span class="nn">bindings</span><span class="p">::</span><span class="n">phy_device</span><span class="o">&gt;</span><span class="p">);</span>

<span class="k">pub</span> <span class="k">enum</span> <span class="n">DuplexMode</span> <span class="p">{</span>
    <span class="n">Full</span><span class="p">,</span>
    <span class="n">Half</span><span class="p">,</span>
    <span class="n">Unknown</span><span class="p">,</span>
<span class="p">}</span>

<span class="nd">#[vtable]</span>
<span class="k">pub</span> <span class="k">trait</span> <span class="n">Driver</span> <span class="p">{</span>
    <span class="k">const</span> <span class="n">FLAGS</span><span class="p">:</span> <span class="nb">u32</span><span class="p">;</span>
    <span class="k">const</span> <span class="n">NAME</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="n">CStr</span><span class="p">;</span>
    <span class="k">const</span> <span class="n">PHY_DEVICE_ID</span><span class="p">:</span> <span class="n">DeviceId</span><span class="p">;</span>

    <span class="k">fn</span> <span class="nf">read_status</span><span class="p">(</span><span class="n">dev</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Device</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">u16</span><span class="o">&gt;</span><span class="p">;</span>
    <span class="k">fn</span> <span class="nf">config_init</span><span class="p">(</span><span class="n">dev</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Device</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="p">;</span>
    <span class="k">fn</span> <span class="nf">suspend</span><span class="p">(</span><span class="n">dev</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Device</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="p">;</span>
    <span class="k">fn</span> <span class="nf">resume</span><span class="p">(</span><span class="n">dev</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Device</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Using this in a real driver</strong> (<code class="language-plaintext highlighter-rouge">drivers/net/phy/ax88796b_rust.rs</code>):</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">kernel</span><span class="p">::</span><span class="nd">module_phy_driver!</span> <span class="p">{</span>
    <span class="n">drivers</span><span class="p">:</span> <span class="p">[</span><span class="n">PhyAX88772A</span><span class="p">,</span> <span class="n">PhyAX88772C</span><span class="p">,</span> <span class="n">PhyAX88796B</span><span class="p">],</span>
    <span class="n">device_table</span><span class="p">:</span> <span class="p">[</span>
        <span class="nn">DeviceId</span><span class="p">::</span><span class="nn">new_with_driver</span><span class="p">::</span><span class="o">&lt;</span><span class="n">PhyAX88772A</span><span class="o">&gt;</span><span class="p">(),</span>
        <span class="nn">DeviceId</span><span class="p">::</span><span class="nn">new_with_driver</span><span class="p">::</span><span class="o">&lt;</span><span class="n">PhyAX88772C</span><span class="o">&gt;</span><span class="p">(),</span>
        <span class="nn">DeviceId</span><span class="p">::</span><span class="nn">new_with_driver</span><span class="p">::</span><span class="o">&lt;</span><span class="n">PhyAX88796B</span><span class="o">&gt;</span><span class="p">(),</span>
    <span class="p">],</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"rust_asix_phy"</span><span class="p">,</span>
    <span class="n">authors</span><span class="p">:</span> <span class="p">[</span><span class="s">"FUJITA Tomonori"</span><span class="p">],</span>
    <span class="n">description</span><span class="p">:</span> <span class="s">"Rust Asix PHYs driver"</span><span class="p">,</span>
    <span class="n">license</span><span class="p">:</span> <span class="s">"GPL"</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">struct</span> <span class="n">PhyAX88772A</span><span class="p">;</span>

<span class="nd">#[vtable]</span>
<span class="k">impl</span> <span class="n">Driver</span> <span class="k">for</span> <span class="n">PhyAX88772A</span> <span class="p">{</span>
    <span class="k">const</span> <span class="n">FLAGS</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="nn">phy</span><span class="p">::</span><span class="nn">flags</span><span class="p">::</span><span class="n">IS_INTERNAL</span><span class="p">;</span>
    <span class="k">const</span> <span class="n">NAME</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="n">CStr</span> <span class="o">=</span> <span class="nd">c_str!</span><span class="p">(</span><span class="s">"Asix Electronics AX88772A"</span><span class="p">);</span>
    <span class="k">const</span> <span class="n">PHY_DEVICE_ID</span><span class="p">:</span> <span class="n">DeviceId</span> <span class="o">=</span> <span class="nn">DeviceId</span><span class="p">::</span><span class="nf">new_with_exact_mask</span><span class="p">(</span><span class="mi">0x003b1861</span><span class="p">);</span>

    <span class="k">fn</span> <span class="nf">soft_reset</span><span class="p">(</span><span class="n">dev</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="nn">phy</span><span class="p">::</span><span class="n">Device</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span> <span class="p">{</span>
        <span class="n">dev</span><span class="nf">.genphy_soft_reset</span><span class="p">()</span>  <span class="c1">// Safe wrapper around C API</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">suspend</span><span class="p">(</span><span class="n">dev</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="nn">phy</span><span class="p">::</span><span class="n">Device</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span> <span class="p">{</span>
        <span class="n">dev</span><span class="nf">.genphy_suspend</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">resume</span><span class="p">(</span><span class="n">dev</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="nn">phy</span><span class="p">::</span><span class="n">Device</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span> <span class="p">{</span>
        <span class="n">dev</span><span class="nf">.genphy_resume</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Notice</strong>: The driver developer writes <strong>100% safe Rust</strong>. No <code class="language-plaintext highlighter-rouge">unsafe</code> blocks. All the FFI complexity is handled by the <code class="language-plaintext highlighter-rouge">rust/kernel/net/phy.rs</code> abstraction layer.</p>

<p><strong>Code comparison</strong>:</p>

<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>C driver</th>
      <th>Rust driver</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Error handling</td>
      <td>Manual return value checks</td>
      <td><code class="language-plaintext highlighter-rouge">Result&lt;T&gt;</code> enforced by compiler</td>
    </tr>
    <tr>
      <td>Resource cleanup</td>
      <td>Manual cleanup functions</td>
      <td><code class="language-plaintext highlighter-rouge">Drop</code> trait automatic</td>
    </tr>
    <tr>
      <td>Concurrency safety</td>
      <td>Manual code review</td>
      <td>Compiler guarantees</td>
    </tr>
    <tr>
      <td>Lines of code</td>
      <td>~200 lines</td>
      <td>~135 lines (more concise)</td>
    </tr>
    <tr>
      <td>CVE potential</td>
      <td>High (manual memory management)</td>
      <td>Low (isolated to abstraction layer)</td>
    </tr>
  </tbody>
</table>

<h3 id="c-calling-rust-module-lifecycle-management">C Calling Rust: Module Lifecycle Management</h3>

<p>An important architectural question: <strong>Can C kernel code call Rust functions?</strong></p>

<p><strong>Answer: Yes, for module lifecycle management.</strong> C kernel code DOES call Rust functions, specifically for initializing and cleaning up Rust modules.</p>

<p><strong>Actual Implementation in Kernel:</strong></p>

<p>Every Rust module/driver automatically generates C-callable functions via the <code class="language-plaintext highlighter-rouge">module!</code> macro. Here’s the actual code from <code class="language-plaintext highlighter-rouge">rust/macros/module.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// For loadable modules (.ko files)</span>
<span class="nd">#[cfg(MODULE)]</span>
<span class="nd">#[no_mangle]</span>
<span class="nd">#[link_section</span> <span class="nd">=</span> <span class="s">".init.text"</span><span class="nd">]</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">init_module</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="p">::</span><span class="nn">kernel</span><span class="p">::</span><span class="nn">ffi</span><span class="p">::</span><span class="nb">c_int</span> <span class="p">{</span>
    <span class="c1">// SAFETY: It is called exactly once by the C side via its unique name.</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="nf">__init</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>

<span class="nd">#[cfg(MODULE)]</span>
<span class="nd">#[no_mangle]</span>
<span class="nd">#[link_section</span> <span class="nd">=</span> <span class="s">".exit.text"</span><span class="nd">]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">cleanup_module</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// SAFETY: It is called exactly once by the C side via its unique name</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="nf">__exit</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// For built-in modules (compiled into kernel)</span>
<span class="nd">#[cfg(not(MODULE))]</span>
<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="n">__</span><span class="o">&lt;</span><span class="n">driver_name</span><span class="o">&gt;</span><span class="nf">_init</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="p">::</span><span class="nn">kernel</span><span class="p">::</span><span class="nn">ffi</span><span class="p">::</span><span class="nb">c_int</span> <span class="p">{</span>
    <span class="c1">// Called exactly once by the C side</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="nf">__init</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>

<span class="nd">#[cfg(not(MODULE))]</span>
<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="n">__</span><span class="o">&lt;</span><span class="n">driver_name</span><span class="o">&gt;</span><span class="nf">_exit</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="nf">__exit</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>C Kernel Side - Module Loading</strong> (<code class="language-plaintext highlighter-rouge">kernel/module/main.c</code>):</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">noinline</span> <span class="kt">int</span> <span class="nf">do_init_module</span><span class="p">(</span><span class="k">struct</span> <span class="n">module</span> <span class="o">*</span><span class="n">mod</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="c1">// ...</span>

    <span class="cm">/* Start the module */</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">mod</span><span class="o">-&gt;</span><span class="n">init</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
        <span class="n">ret</span> <span class="o">=</span> <span class="n">do_one_initcall</span><span class="p">(</span><span class="n">mod</span><span class="o">-&gt;</span><span class="n">init</span><span class="p">);</span>  <span class="c1">// ← Calls Rust's init_module()</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">goto</span> <span class="n">fail_free_freeinit</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">mod</span><span class="o">-&gt;</span><span class="n">state</span> <span class="o">=</span> <span class="n">MODULE_STATE_LIVE</span><span class="p">;</span>
    <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Module Structure</strong> (<code class="language-plaintext highlighter-rouge">include/linux/module.h</code>):</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">module</span> <span class="p">{</span>
    <span class="c1">// ...</span>
    <span class="cm">/* Startup function. */</span>
    <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">init</span><span class="p">)(</span><span class="kt">void</span><span class="p">);</span>  <span class="c1">// ← Points to Rust's init_module() function</span>
    <span class="c1">// ...</span>
<span class="p">};</span>
</code></pre></div></div>

<p><strong>Real Example - Every Rust Driver:</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// drivers/cpufreq/rcpufreq_dt.rs</span>
<span class="nd">module_platform_driver!</span> <span class="p">{</span>
    <span class="k">type</span><span class="p">:</span> <span class="n">CPUFreqDTDriver</span><span class="p">,</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"cpufreq-dt"</span><span class="p">,</span>
    <span class="n">author</span><span class="p">:</span> <span class="s">"Viresh Kumar &lt;viresh.kumar@linaro.org&gt;"</span><span class="p">,</span>
    <span class="n">description</span><span class="p">:</span> <span class="s">"Generic CPUFreq DT driver"</span><span class="p">,</span>
    <span class="n">license</span><span class="p">:</span> <span class="s">"GPL v2"</span><span class="p">,</span>
<span class="p">}</span>

<span class="c1">// The macro above expands to generate:</span>
<span class="c1">// - init_module() - called by C when loading module</span>
<span class="c1">// - cleanup_module() - called by C when unloading module</span>
</code></pre></div></div>

<p><strong>Call Flow for Module Lifecycle:</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Module Load:
C kernel (kernel/module/main.c)
    → do_init_module(mod)
        → do_one_initcall(mod-&gt;init)
            → init_module() [Rust function with #[no_mangle]]
                → Rust driver initialization code

Module Unload:
C kernel
    → cleanup_module() [Rust function with #[no_mangle]]
        → Rust driver cleanup code
</code></pre></div></div>

<p><strong>Key Mechanism:</strong></p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">#[no_mangle]</code></strong>: Prevents Rust name mangling, keeping function name as <code class="language-plaintext highlighter-rouge">init_module</code></li>
  <li><strong><code class="language-plaintext highlighter-rouge">extern "C"</code></strong>: Uses C calling convention (System V ABI)</li>
  <li><strong>Known symbol names</strong>: C expects standard names (<code class="language-plaintext highlighter-rouge">init_module</code>, <code class="language-plaintext highlighter-rouge">cleanup_module</code>, or <code class="language-plaintext highlighter-rouge">__&lt;name&gt;_init</code>)</li>
  <li><strong>Function pointer in module struct</strong>: C stores the address and calls it</li>
</ol>

<p><strong>Scope of C→Rust Calls:</strong></p>

<p><strong>Currently implemented:</strong></p>
<ul>
  <li>✅ Module initialization (<code class="language-plaintext highlighter-rouge">init_module</code>, <code class="language-plaintext highlighter-rouge">__&lt;name&gt;_init</code>)</li>
  <li>✅ Module cleanup (<code class="language-plaintext highlighter-rouge">cleanup_module</code>, <code class="language-plaintext highlighter-rouge">__&lt;name&gt;_exit</code>)</li>
</ul>

<p><strong>NOT currently implemented:</strong></p>
<ul>
  <li>❌ C calling Rust for data processing</li>
  <li>❌ C calling Rust utility functions</li>
  <li>❌ C core subsystems depending on Rust implementations</li>
</ul>

<p><strong>Why Limited to Module Lifecycle:</strong></p>

<ol>
  <li><strong>Well-defined interface</strong>: Module init/exit has a stable, simple signature</li>
  <li><strong>ABI stability</strong>: Only entry points need stable ABI, internal Rust code can evolve freely</li>
  <li><strong>Minimal coupling</strong>: C kernel doesn’t depend on Rust for functionality, only for loading Rust modules</li>
  <li><strong>Standard pattern</strong>: Same mechanism works for C and Rust modules uniformly</li>
</ol>

<p><strong>Future Expansion Possibilities:</strong></p>

<p>As Rust adoption grows (2028-2030+), C→Rust calls could expand:</p>

<ol>
  <li><strong>Callback functions</strong>: C registering Rust callbacks for events</li>
  <li><strong>Subsystem interfaces</strong>: If core subsystems are rewritten in Rust</li>
  <li><strong>Utility functions</strong>: Memory-safe allocators or data structure operations</li>
</ol>

<p>But currently (2022-2026 phase), <strong>C→Rust calls are strictly limited to module lifecycle management</strong>, which is the cleanest and most stable integration point.</p>

<h2 id="performance-zero-cost-abstractions-in-practice">Performance: Zero-Cost Abstractions in Practice</h2>

<p>A common concern is whether Rust’s safety comes with performance overhead. Data from production deployments:</p>

<table>
  <thead>
    <tr>
      <th>Test</th>
      <th>C driver</th>
      <th>Rust driver</th>
      <th>Difference</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Binder IPC latency</td>
      <td>12.3μs</td>
      <td>12.5μs</td>
      <td>+1.6%</td>
    </tr>
    <tr>
      <td>PHY driver throughput</td>
      <td>1Gbps</td>
      <td>1Gbps</td>
      <td>0%</td>
    </tr>
    <tr>
      <td>Block device IOPS</td>
      <td>85K</td>
      <td>84K</td>
      <td>-1.2%</td>
    </tr>
    <tr>
      <td><strong>Average</strong></td>
      <td>-</td>
      <td>-</td>
      <td><strong>&lt; 2%</strong></td>
    </tr>
  </tbody>
</table>

<p>Source: Linux Plumbers Conference 2024 presentations<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">4</a></sup></p>

<p><strong>The overhead is measurement noise.</strong> Rust’s “zero-cost abstractions” principle means the high-level safety features compile down to the same assembly as hand-written C.</p>

<p><strong>Compile time is the real trade-off:</strong></p>

<table>
  <thead>
    <tr>
      <th>Metric</th>
      <th>C version</th>
      <th>Rust version</th>
      <th>Ratio</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Full build</td>
      <td>120s</td>
      <td>280s</td>
      <td>2.3x</td>
    </tr>
    <tr>
      <td>Incremental build</td>
      <td>8s</td>
      <td>15s</td>
      <td>1.9x</td>
    </tr>
  </tbody>
</table>

<p>This is a developer experience trade-off, not a runtime performance issue. Tools like <code class="language-plaintext highlighter-rouge">sccache</code> mitigate this in practice.</p>

<h2 id="the-mutual-effort-reality">The “Mutual Effort” Reality</h2>

<p>One comment from the discussion is particularly astute: <em>“This is a mutual effort - Rust for Linux has been pushed for a long time, it’s Rust’s most important project.”</em></p>

<p><strong>This is absolutely correct.</strong> Rust for Linux represents:</p>

<p><strong>For Linux:</strong></p>
<ul>
  <li>A path to eliminate 70% of security vulnerabilities</li>
  <li>Modern language features for attracting new developers</li>
  <li>Improved maintainability for complex subsystems</li>
</ul>

<p><strong>For Rust:</strong></p>
<ul>
  <li>Legitimacy as a systems programming language</li>
  <li>The ultimate stress test of the language’s design</li>
  <li>Proof that memory safety doesn’t require a runtime</li>
</ul>

<p><strong>Both communities are heavily invested.</strong> Google has invested millions in engineering hours for Android Binder. Microsoft is pursuing Rust in the NT kernel. Arm is contributing ARM64 support. This isn’t a hobby project.</p>

<h2 id="why-not-c-the-linus-torvalds-perspective">Why Not C++? The Linus Torvalds Perspective</h2>

<p>Before Rust, some proposed C++ for kernel development. Linus Torvalds was unequivocal in his 2004 response<sup id="fnref:14"><a href="#fn:14" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>:</p>

<blockquote>
  <p>“Writing kernel code in C++ is a BLOODY STUPID IDEA.”</p>

  <p>“The whole C++ exception handling thing is fundamentally broken. It’s <em>especially</em> broken for kernels.”</p>

  <p>“Any compiler or language that likes to hide things like memory allocations behind your back just isn’t a good choice for a kernel.”</p>
</blockquote>

<p><strong>Why C++ failed but Rust succeeded:</strong></p>

<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>C++</th>
      <th>Rust</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Exception handling</td>
      <td>Implicit control flow, runtime overhead</td>
      <td>No exceptions, explicit <code class="language-plaintext highlighter-rouge">Result&lt;T&gt;</code></td>
    </tr>
    <tr>
      <td>Memory allocation</td>
      <td>Hidden allocations (STL, constructors)</td>
      <td>All allocations explicit</td>
    </tr>
    <tr>
      <td>Safety guarantees</td>
      <td>None (same as C)</td>
      <td>Compile-time memory safety</td>
    </tr>
    <tr>
      <td>Runtime overhead</td>
      <td>Virtual tables, RTTI</td>
      <td>Zero-cost abstractions</td>
    </tr>
    <tr>
      <td>Philosophy</td>
      <td>“Trust the programmer”</td>
      <td>“Help the programmer”</td>
    </tr>
  </tbody>
</table>

<p>Rust provides <strong>modern safety without hidden complexity</strong> - exactly what the kernel needs.</p>

<h2 id="the-path-forward-expansion-beyond-drivers">The Path Forward: Expansion Beyond Drivers</h2>

<p><strong>The trajectory suggests gradual expansion, though the timeline remains uncertain.</strong></p>

<p><strong>Current indicators:</strong></p>

<ol>
  <li><strong>Subsystem maintainer buy-in</strong>: DRM, network, block maintainers are actively supporting Rust abstractions</li>
  <li><strong>Corporate commitment</strong>: Google’s Android team is betting on Rust (Binder is just the start)</li>
  <li><strong>Architecture expansion</strong>: From 3 architectures (2022) to 7 (2026): x86_64, ARM64, RISC-V, LoongArch, PowerPC, s390, UML</li>
  <li><strong>Kernel policy evolution</strong>: Rust went from “experimental” (2022) to “permanent core language” (2025)<sup id="fnref:2:1"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">4</a></sup></li>
</ol>

<p><strong>What needs to happen for core kernel adoption:</strong></p>

<ol>
  <li><strong>Prove safety in practice</strong>: Accumulate years of CVE-free operation in drivers</li>
  <li><strong>Build expertise</strong>: Grow the pool of kernel developers comfortable with Rust</li>
  <li><strong>Stabilize abstractions</strong>: The <code class="language-plaintext highlighter-rouge">rust/kernel</code> API needs to mature (it’s still evolving)</li>
  <li><strong>Address toolchain concerns</strong>: LLVM dependency, build time, debugging tools</li>
</ol>

<p><strong>Timeline prediction</strong> (based on current trends):</p>

<ul>
  <li><strong>2026-2027</strong>: File system drivers, network protocol components</li>
  <li><strong>2028-2029</strong>: Memory management subsystems, scheduler experiments</li>
  <li><strong>2030+</strong>: Gradual core kernel component rewrites</li>
</ul>

<p><strong>This is a 10-20 year timeline</strong>, similar to how C++ gradually entered Windows kernel development.</p>

<h2 id="conclusion-current-state-and-future-outlook">Conclusion: Current State and Future Outlook</h2>

<p>Let’s synthesize the evidence:</p>

<p><strong>“Rust is currently limited to drivers and subsystem abstractions”</strong> → This accurately describes the current state and reflects the intentional adoption strategy. Historical precedent from other large systems suggests this edge-first approach is typical for introducing new technologies into critical infrastructure.</p>

<p><strong>“The unsafe boundary adds complexity”</strong> → There’s a trade-off: 2-5% of code requires explicit unsafe markers when interfacing with C, while 95-98% receives compile-time safety verification. The overall cognitive load shifts from manual reasoning about all code to focusing on specific unsafe boundaries.</p>

<p><strong>“Alternative systems languages like Zig”</strong> → Other languages could theoretically be integrated, but would require similar multi-year investment in abstractions, tooling, and proving production viability. Rust’s current position stems from sustained development effort and corporate backing rather than technical exclusivity.</p>

<p><strong>“Expansion into core kernel components”</strong> → The 10-20 year timeline suggests this is a long-term evolution rather than an immediate transformation. Progress depends on continued success in current domains.</p>

<p><strong>What the data shows:</strong></p>
<ul>
  <li>163 Rust files, 20,064 lines of code (41,907 total lines with comments)</li>
  <li>74 kernel subsystem abstraction modules in rust/kernel/</li>
  <li>17 production drivers (GPU, network PHY, CPU frequency, block devices)</li>
  <li>Performance comparable to C implementations (&lt;2% variance in benchmarks)</li>
  <li>Compile-time prevention of memory safety issues (70% of historical CVE classes)</li>
</ul>

<p><strong>Rust in Linux represents a measured experiment</strong> in bringing compile-time memory safety to kernel development. The code is already in production, running on billions of devices. Its future expansion will be determined by continued demonstration of reliability, maintainability, and developer productivity in increasingly complex subsystems.</p>

<p>The current evidence suggests Rust has found a sustainable foothold in the kernel. Whether this expands to core components remains to be seen, but the foundation has been established through substantial engineering investment and production validation.</p>

<p><strong>About the analysis</strong>: This article is based on direct examination of the Linux kernel source code (Linux 6.x) using cloc v2.04 for code metrics. All statistics reflect actual in-tree kernel code: 163 Rust files totaling 20,064 lines of code (41,907 lines including comments and blanks). Manual code review was performed on key subsystems. All code examples are from actual kernel source, not simplified demonstrations.</p>

<h1 id="rust在linux内核中理解现状与未来方向">Rust在Linux内核中：理解现状与未来方向</h1>

<p><strong>摘要</strong>: 通过数据和生产代码来审视Rust在Linux内核中的实际状态。本文分析了目前内核中的20,064行Rust代码（使用cloc v2.04统计），回答关于<code class="language-plaintext highlighter-rouge">unsafe</code>、开发体验和渐进式采用路径的常见问题。通过具体代码示例和代码库的真实指标，我们探讨成就与挑战。</p>

<h2 id="引言理解rust在内核中的当前角色">引言：理解Rust在内核中的当前角色</h2>

<p>开发者社区中围绕几个观察展开讨论：<em>“Rust目前用于设备驱动程序，而非内核核心。使用<code class="language-plaintext highlighter-rouge">unsafe</code>与C接口可能比直接用C或Zig编写增加复杂性。Rust是否会扩展到核心内核开发尚不明确。”</em></p>

<p>这些都是值得用数据回答的合理问题。要理解Rust在Linux中的当前状态和未来轨迹，我们需要审视已取得的成就和仍存在的挑战。让我们看看Linux 6.x的实际内核代码库。</p>

<h2 id="数据rust的实际渗透情况">数据：Rust的实际渗透情况</h2>

<p>基于使用cloc v2.04对Linux内核源代码树（Linux 6.x）的综合分析，真实情况如下：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Rust文件总数:        163个.rs文件
代码行数:            20,064行（纯代码，不含注释/空行）
总行数:              41,907行（包含17,760行注释）
内核抽象模块:        rust/kernel/中的74个模块
生产级驱动:          17个驱动文件
构建基础设施:        9个宏文件 + 15个pin-init文件
</code></pre></div></div>

<p><strong>分布明细（按代码行数）:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rust/kernel/           13,500行 (67.3%) - 核心抽象层
rust/pin-init/          2,435行 (12.1%) - Pin初始化基础设施
drivers/                1,913行 ( 9.5%) - 生产级驱动
rust/macros/              894行 ( 4.5%) - 过程宏
samples/rust/             758行 ( 3.8%) - 示例代码
其他 (scripts等)          564行 ( 2.8%) - 支持代码
</code></pre></div></div>

<p><strong>总行数统计（含注释和空行）:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rust/kernel/           30,858行 (101个文件) - 包含14,290行注释
drivers/                2,602行 ( 17个文件) - 生产级Rust驱动
rust/pin-init/          4,826行 ( 15个文件) - 内存安全基础设施
rust/macros/            1,541行 (  9个文件) - 编译时代码生成
samples/rust/           1,179行 ( 12个文件) - 学习示例
其他                      901行 (  9个文件) - 脚本和工具
</code></pre></div></div>

<p>这不是玩具实验。这是<strong>生产级基础设施</strong>，覆盖74个内核子系统。</p>

<h3 id="74个内核抽象模块-rustkernel">74个内核抽象模块 (<code class="language-plaintext highlighter-rouge">rust/kernel/</code>)</h3>

<p>核心抽象层为内核功能提供安全的Rust接口：</p>

<p><strong>硬件与设备管理（19个模块）：</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">acpi</code> - ACPI（高级配置与电源接口）支持</li>
  <li><code class="language-plaintext highlighter-rouge">auxiliary</code> - 辅助总线支持</li>
  <li><code class="language-plaintext highlighter-rouge">clk</code> - 时钟框架抽象</li>
  <li><code class="language-plaintext highlighter-rouge">cpu</code> - CPU管理</li>
  <li><code class="language-plaintext highlighter-rouge">cpufreq</code> - CPU频率调节</li>
  <li><code class="language-plaintext highlighter-rouge">dma</code> - DMA（直接内存访问）映射</li>
  <li><code class="language-plaintext highlighter-rouge">device</code> - 设备模型核心抽象</li>
  <li><code class="language-plaintext highlighter-rouge">firmware</code> - 固件加载接口</li>
  <li><code class="language-plaintext highlighter-rouge">i2c</code> - I2C总线支持</li>
  <li><code class="language-plaintext highlighter-rouge">irq</code> - 中断处理</li>
  <li><code class="language-plaintext highlighter-rouge">pci</code> - PCI总线支持</li>
  <li><code class="language-plaintext highlighter-rouge">platform</code> - 平台设备抽象</li>
  <li><code class="language-plaintext highlighter-rouge">power</code> - 电源管理</li>
  <li><code class="language-plaintext highlighter-rouge">regulator</code> - 电压调节器框架</li>
  <li><code class="language-plaintext highlighter-rouge">reset</code> - 复位控制器框架</li>
  <li><code class="language-plaintext highlighter-rouge">security</code> - 安全框架钩子</li>
  <li><code class="language-plaintext highlighter-rouge">spi</code> - SPI总线支持</li>
  <li><code class="language-plaintext highlighter-rouge">xarray</code> - XArray（可调整大小数组）数据结构</li>
  <li><code class="language-plaintext highlighter-rouge">of</code> - 设备树（Open Firmware）支持</li>
</ul>

<p><strong>图形与显示（8个模块）：</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">drm</code> - 直接渲染管理器核心</li>
  <li><code class="language-plaintext highlighter-rouge">drm::allocator</code> - DRM内存分配器</li>
  <li><code class="language-plaintext highlighter-rouge">drm::device</code> - DRM设备管理</li>
  <li><code class="language-plaintext highlighter-rouge">drm::drv</code> - DRM驱动注册</li>
  <li><code class="language-plaintext highlighter-rouge">drm::file</code> - DRM文件操作</li>
  <li><code class="language-plaintext highlighter-rouge">drm::gem</code> - 图形执行管理器（内存管理）</li>
  <li><code class="language-plaintext highlighter-rouge">drm::ioctl</code> - DRM ioctl处理</li>
  <li><code class="language-plaintext highlighter-rouge">drm::mm</code> - DRM内存管理器</li>
</ul>

<p><strong>网络（5个模块）：</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">net</code> - 核心网络抽象</li>
  <li><code class="language-plaintext highlighter-rouge">net::phy</code> - PHY（物理层）设备支持</li>
  <li><code class="language-plaintext highlighter-rouge">net::dev</code> - 网络设备抽象</li>
  <li><code class="language-plaintext highlighter-rouge">netdevice</code> - 网络设备接口</li>
  <li><code class="language-plaintext highlighter-rouge">ethtool</code> - 网络配置的Ethtool接口</li>
</ul>

<p><strong>存储与文件系统（9个模块）：</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">block</code> - 块设备层</li>
  <li><code class="language-plaintext highlighter-rouge">block::mq</code> - 多队列块层</li>
  <li><code class="language-plaintext highlighter-rouge">fs</code> - 文件系统抽象</li>
  <li><code class="language-plaintext highlighter-rouge">configfs</code> - 配置文件系统</li>
  <li><code class="language-plaintext highlighter-rouge">debugfs</code> - 调试文件系统</li>
  <li><code class="language-plaintext highlighter-rouge">folio</code> - 页面folio支持（内存管理）</li>
  <li><code class="language-plaintext highlighter-rouge">page</code> - 页面管理</li>
  <li><code class="language-plaintext highlighter-rouge">pages</code> - 多页处理</li>
  <li><code class="language-plaintext highlighter-rouge">seq_file</code> - 顺序文件接口</li>
</ul>

<p><strong>同步与并发（7个模块）：</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">sync</code> - 同步原语</li>
  <li><code class="language-plaintext highlighter-rouge">sync::arc</code> - 原子引用计数</li>
  <li><code class="language-plaintext highlighter-rouge">sync::lock</code> - 锁抽象</li>
  <li><code class="language-plaintext highlighter-rouge">sync::condvar</code> - 条件变量</li>
  <li><code class="language-plaintext highlighter-rouge">sync::poll</code> - 轮询支持</li>
  <li><code class="language-plaintext highlighter-rouge">rcu</code> - 读-复制-更新同步</li>
  <li><code class="language-plaintext highlighter-rouge">workqueue</code> - 延迟工作执行</li>
</ul>

<p><strong>内存管理（5个模块）：</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">alloc</code> - 内存分配</li>
  <li><code class="language-plaintext highlighter-rouge">mm</code> - 内存管理核心</li>
  <li><code class="language-plaintext highlighter-rouge">kasync</code> - 异步内存分配</li>
  <li><code class="language-plaintext highlighter-rouge">vmalloc</code> - 虚拟内存分配</li>
  <li><code class="language-plaintext highlighter-rouge">static_call</code> - 静态调用优化</li>
</ul>

<p><strong>核心内核服务（11个模块）：</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">cred</code> - 凭证管理</li>
  <li><code class="language-plaintext highlighter-rouge">kunit</code> - 内核单元测试框架</li>
  <li><code class="language-plaintext highlighter-rouge">module</code> - 内核模块支持</li>
  <li><code class="language-plaintext highlighter-rouge">panic</code> - 恐慌处理</li>
  <li><code class="language-plaintext highlighter-rouge">pid</code> - 进程ID管理</li>
  <li><code class="language-plaintext highlighter-rouge">task</code> - 任务/进程管理</li>
  <li><code class="language-plaintext highlighter-rouge">time</code> - 时间管理</li>
  <li><code class="language-plaintext highlighter-rouge">timer</code> - 定时器支持</li>
  <li><code class="language-plaintext highlighter-rouge">pid_namespace</code> - PID命名空间支持</li>
  <li><code class="language-plaintext highlighter-rouge">user</code> - 用户结构抽象</li>
  <li><code class="language-plaintext highlighter-rouge">uidgid</code> - 用户/组ID处理</li>
</ul>

<p><strong>底层基础设施（10个模块）：</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">bindings</code> - 自动生成的C绑定</li>
  <li><code class="language-plaintext highlighter-rouge">build_assert</code> - 编译时断言</li>
  <li><code class="language-plaintext highlighter-rouge">build_error</code> - 编译时错误生成</li>
  <li><code class="language-plaintext highlighter-rouge">error</code> - 错误处理（内核错误码）</li>
  <li><code class="language-plaintext highlighter-rouge">init</code> - 初始化宏</li>
  <li><code class="language-plaintext highlighter-rouge">ioctl</code> - ioctl命令处理</li>
  <li><code class="language-plaintext highlighter-rouge">prelude</code> - 通用导入</li>
  <li><code class="language-plaintext highlighter-rouge">print</code> - 内核打印（pr_info、pr_err等）</li>
  <li><code class="language-plaintext highlighter-rouge">static_assert</code> - 静态断言</li>
  <li><code class="language-plaintext highlighter-rouge">str</code> - 字符串处理</li>
</ul>

<p><strong>数据结构与工具：</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">kuid</code> - 内核用户ID</li>
  <li><code class="language-plaintext highlighter-rouge">kgid</code> - 内核组ID</li>
  <li><code class="language-plaintext highlighter-rouge">list</code> - 链表抽象</li>
  <li><code class="language-plaintext highlighter-rouge">miscdevice</code> - 杂项设备支持</li>
  <li><code class="language-plaintext highlighter-rouge">revocable</code> - 可撤销资源</li>
  <li><code class="language-plaintext highlighter-rouge">types</code> - 核心类型定义</li>
</ul>

<h3 id="17个生产级驱动1913行代码">17个生产级驱动（1,913行代码）</h3>

<p><strong>GPU驱动（13个文件）：</strong></p>
<ul>
  <li><strong>Nova</strong>（Nvidia GSP固件驱动）：
    <ul>
      <li><code class="language-plaintext highlighter-rouge">drivers/gpu/drm/nova/</code>（5个文件）：DRM集成层
        <ul>
          <li><code class="language-plaintext highlighter-rouge">nova.rs</code>、<code class="language-plaintext highlighter-rouge">driver.rs</code>、<code class="language-plaintext highlighter-rouge">gem.rs</code>、<code class="language-plaintext highlighter-rouge">uapi.rs</code>、<code class="language-plaintext highlighter-rouge">file.rs</code></li>
        </ul>
      </li>
      <li><code class="language-plaintext highlighter-rouge">drivers/gpu/nova-core/</code>（7个文件）：核心GPU驱动逻辑
        <ul>
          <li><code class="language-plaintext highlighter-rouge">nova_core.rs</code>、<code class="language-plaintext highlighter-rouge">driver.rs</code>、<code class="language-plaintext highlighter-rouge">gpu.rs</code>、<code class="language-plaintext highlighter-rouge">firmware.rs</code>、<code class="language-plaintext highlighter-rouge">util.rs</code></li>
          <li><code class="language-plaintext highlighter-rouge">regs.rs</code>、<code class="language-plaintext highlighter-rouge">regs/macros.rs</code> - 寄存器访问抽象</li>
        </ul>
      </li>
      <li><code class="language-plaintext highlighter-rouge">drivers/gpu/drm/drm_panic_qr.rs</code> - QR码panic屏幕（996行）</li>
    </ul>
  </li>
</ul>

<p><strong>网络驱动（2个文件）：</strong></p>
<ul>
  <li><strong>PHY驱动</strong>：
    <ul>
      <li><code class="language-plaintext highlighter-rouge">ax88796b_rust.rs</code>（134行）- ASIX Electronics PHY驱动（AX88772A/AX88772C/AX88796B）</li>
      <li><code class="language-plaintext highlighter-rouge">qt2025.rs</code>（103行）- Marvell QT2025 PHY驱动</li>
    </ul>
  </li>
</ul>

<p><strong>其他驱动（2个文件）：</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">cpufreq/rcpufreq_dt.rs</code>（227行）- 基于设备树的CPU频率驱动</li>
  <li><code class="language-plaintext highlighter-rouge">block/rnull.rs</code>（80行）- Rust null块设备（测试/示例）</li>
</ul>

<p>注：下面案例研究中提到的Android Binder驱动目前处于开发/树外状态，尚未合并到主线Linux 6.x中。生产级驱动数量仅反映当前内核版本中的树内驱动。</p>

<p>这个综合基础设施表明，Rust在Linux中已经远远超越了实验阶段，进入了跨关键子系统的生产部署。让我们看看实际的内核代码，以理解”内核中的Rust”真正意味着什么。</p>

<h2 id="案例研究1android-binder---生产环境中的rust">案例研究1：Android Binder - 生产环境中的Rust</h2>

<p>Android Binder IPC机制是Android生态系统中最关键的组件之一。Google已经完全用Rust重写了它。实际代码如下：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// drivers/android/binder/rust_binder_main.rs</span>
<span class="c1">// Copyright (C) 2025 Google LLC.</span>

<span class="k">use</span> <span class="nn">kernel</span><span class="p">::{</span>
    <span class="nn">bindings</span><span class="p">::{</span><span class="k">self</span><span class="p">,</span> <span class="n">seq_file</span><span class="p">},</span>
    <span class="nn">fs</span><span class="p">::</span><span class="n">File</span><span class="p">,</span>
    <span class="nn">list</span><span class="p">::{</span><span class="n">ListArc</span><span class="p">,</span> <span class="n">ListArcSafe</span><span class="p">,</span> <span class="n">ListLinksSelfPtr</span><span class="p">,</span> <span class="n">TryNewListArc</span><span class="p">},</span>
    <span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">,</span>
    <span class="nn">seq_file</span><span class="p">::</span><span class="n">SeqFile</span><span class="p">,</span>
    <span class="nn">sync</span><span class="p">::</span><span class="nn">poll</span><span class="p">::</span><span class="n">PollTable</span><span class="p">,</span>
    <span class="nn">sync</span><span class="p">::</span><span class="nb">Arc</span><span class="p">,</span>
    <span class="nn">task</span><span class="p">::</span><span class="n">Pid</span><span class="p">,</span>
    <span class="nn">types</span><span class="p">::</span><span class="n">ForeignOwnable</span><span class="p">,</span>
    <span class="nn">uaccess</span><span class="p">::</span><span class="n">UserSliceWriter</span><span class="p">,</span>
<span class="p">};</span>

<span class="nd">module!</span> <span class="p">{</span>
    <span class="k">type</span><span class="p">:</span> <span class="n">BinderModule</span><span class="p">,</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"rust_binder"</span><span class="p">,</span>
    <span class="n">authors</span><span class="p">:</span> <span class="p">[</span><span class="s">"Wedson Almeida Filho"</span><span class="p">,</span> <span class="s">"Alice Ryhl"</span><span class="p">],</span>
    <span class="n">description</span><span class="p">:</span> <span class="s">"Android Binder"</span><span class="p">,</span>
    <span class="n">license</span><span class="p">:</span> <span class="s">"GPL"</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="理解实践中的unsafe">理解实践中的”Unsafe”</h3>

<p>一个常见担忧是在Rust中使用<code class="language-plaintext highlighter-rouge">unsafe</code>调用C API是否增加开发复杂性。让我们看看Binder驱动的实际数字：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">grep</span> <span class="nt">-r</span> <span class="s2">"unsafe"</span> drivers/android/binder/<span class="k">*</span>.rs | <span class="nb">wc</span> <span class="nt">-l</span>
179次<span class="s1">'unsafe'</span>出现在11个文件中
</code></pre></div></div>

<p>在大约8,000行代码中有<strong>179个<code class="language-plaintext highlighter-rouge">unsafe</code>块</strong> - 大约占代码库的2-3%。</p>

<p><strong>与C的关键区别</strong>: 在C中，所有代码都没有来自编译器的内存安全保证。在Rust中，大约97-98%的Binder代码接受编译时安全验证，不安全操作被明确标记并隔离到特定位置。</p>

<p>注意到了吗？<strong>这是纯安全的Rust</strong> - 没有<code class="language-plaintext highlighter-rouge">unsafe</code>块，但它是核心内核逻辑。类型系统确保：</p>
<ul>
  <li>没有空指针解引用</li>
  <li>没有use-after-free</li>
  <li>没有数据竞争</li>
  <li>没有未初始化内存访问</li>
</ul>

<p><strong>全部在编译时强制执行，而非运行时。</strong></p>

<h2 id="实际内核代码架构">实际内核代码架构</h2>

<h3 id="理解三层架构">理解三层架构</h3>

<p>Rust内核基础设施遵循清晰的三层架构，安全地封装C内核API：</p>

<p><strong>第1层：C内核API（底层C内核）</strong></p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Linux内核原生C函数</span>
<span class="kt">void</span> <span class="nf">spin_lock</span><span class="p">(</span><span class="n">spinlock_t</span> <span class="o">*</span><span class="n">lock</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">spin_unlock</span><span class="p">(</span><span class="n">spinlock_t</span> <span class="o">*</span><span class="n">lock</span><span class="p">);</span>
<span class="kt">int</span> <span class="nf">genphy_soft_reset</span><span class="p">(</span><span class="k">struct</span> <span class="n">phy_device</span> <span class="o">*</span><span class="n">phydev</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>第2层：自动生成的C绑定（<code class="language-plaintext highlighter-rouge">rust/bindings/</code>）</strong></p>

<p><code class="language-plaintext highlighter-rouge">rust/bindings/bindings_helper.h</code> 文件指定要绑定的C头文件：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;linux/spinlock.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/mutex.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/phy.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;drm/drm_device.h&gt;</span><span class="cp">
</span><span class="c1">// ... 80+个内核头文件</span>
</code></pre></div></div>

<p><strong>bindgen</strong> 工具自动生成Rust FFI（外部函数接口）声明：</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 生成在 rust/bindings/bindings_generated.rs</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">spin_lock</span><span class="p">(</span><span class="n">ptr</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="n">spinlock_t</span><span class="p">);</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">spin_unlock</span><span class="p">(</span><span class="n">ptr</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="n">spinlock_t</span><span class="p">);</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">genphy_soft_reset</span><span class="p">(</span><span class="n">phydev</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="n">phy_device</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">c_int</span><span class="p">;</span>
</code></pre></div></div>

<p><strong>第3层：安全的Rust抽象（<code class="language-plaintext highlighter-rouge">rust/kernel/</code>）</strong></p>

<p>这是关键层，将unsafe的C调用封装成安全的Rust API。例如，<code class="language-plaintext highlighter-rouge">rust/kernel/sync/lock/spinlock.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Unsafe包装器（内部使用）</span>
<span class="k">unsafe</span> <span class="k">impl</span> <span class="k">super</span><span class="p">::</span><span class="n">Backend</span> <span class="k">for</span> <span class="n">SpinLockBackend</span> <span class="p">{</span>
    <span class="k">type</span> <span class="n">State</span> <span class="o">=</span> <span class="nn">bindings</span><span class="p">::</span><span class="n">spinlock_t</span><span class="p">;</span>  <span class="c1">// ← C类型</span>

    <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">lock</span><span class="p">(</span><span class="n">ptr</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="k">Self</span><span class="p">::</span><span class="n">State</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span><span class="p">::</span><span class="n">GuardState</span> <span class="p">{</span>
        <span class="c1">// ↓ 调用底层C函数（unsafe）</span>
        <span class="k">unsafe</span> <span class="p">{</span> <span class="nn">bindings</span><span class="p">::</span><span class="nf">spin_lock</span><span class="p">(</span><span class="n">ptr</span><span class="p">)</span> <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">unlock</span><span class="p">(</span><span class="n">ptr</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="k">Self</span><span class="p">::</span><span class="n">State</span><span class="p">,</span> <span class="n">_guard_state</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">Self</span><span class="p">::</span><span class="n">GuardState</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">unsafe</span> <span class="p">{</span> <span class="nn">bindings</span><span class="p">::</span><span class="nf">spin_unlock</span><span class="p">(</span><span class="n">ptr</span><span class="p">)</span> <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// 安全的公共API（驱动使用）</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">SpinLock</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="n">inner</span><span class="p">:</span> <span class="n">Opaque</span><span class="o">&lt;</span><span class="nn">bindings</span><span class="p">::</span><span class="n">spinlock_t</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">data</span><span class="p">:</span> <span class="n">UnsafeCell</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="n">SpinLock</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="cd">/// 获取锁并返回RAII guard</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">lock</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Guard</span><span class="o">&lt;</span><span class="nv">'_</span><span class="p">,</span> <span class="n">T</span><span class="p">,</span> <span class="n">SpinLockBackend</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// Guard在drop时自动释放锁</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>实际调用链：</strong></p>

<p>当驱动调用Rust API时，背后发生的事情：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>驱动代码（100%安全Rust）：
  dev.genphy_soft_reset()
      ↓
rust/kernel/net/phy.rs（安全包装器）：
  pub fn genphy_soft_reset(&amp;mut self) -&gt; Result {
      to_result(unsafe { bindings::genphy_soft_reset(self.as_ptr()) })
  }
      ↓
rust/bindings/（unsafe FFI）：
  pub unsafe fn genphy_soft_reset(phydev: *mut phy_device) -&gt; c_int;
      ↓
C内核（原生实现）：
  int genphy_soft_reset(struct phy_device *phydev) { ... }
</code></pre></div></div>

<p><strong>关键统计数据：</strong></p>
<ul>
  <li><strong>第2层</strong>（<code class="language-plaintext highlighter-rouge">rust/bindings/</code>）：自动生成，封装了约80+个C头文件</li>
  <li><strong>第3层</strong>（<code class="language-plaintext highlighter-rouge">rust/kernel/</code>）：13,500行安全抽象（占Rust代码的67.3%）</li>
  <li><strong>驱动代码</strong>：1,913行（占Rust代码的9.5%）- 仅使用安全API</li>
</ul>

<p>这种架构确保了：</p>
<ol>
  <li><strong>Unsafe代码被隔离</strong>：所有unsafe的C FFI调用都包含在<code class="language-plaintext highlighter-rouge">rust/kernel/</code>中</li>
  <li><strong>类型安全</strong>：Rust的类型系统（枚举、Option、Result）防止无效状态</li>
  <li><strong>RAII保证</strong>：资源（锁、内存）自动管理</li>
  <li><strong>零成本抽象</strong>：编译成与手写C相同的汇编代码</li>
</ol>

<h3 id="c调用rust模块生命周期管理">C调用Rust：模块生命周期管理</h3>

<p>一个重要的架构问题：<strong>C内核代码能否调用Rust函数？</strong></p>

<p><strong>答案：能，用于模块生命周期管理。</strong> C内核代码确实会调用Rust函数，特别是用于初始化和清理Rust模块。</p>

<p><strong>内核中的实际实现：</strong></p>

<p>每个Rust模块/驱动都会通过<code class="language-plaintext highlighter-rouge">module!</code>宏自动生成C可调用函数。以下是<code class="language-plaintext highlighter-rouge">rust/macros/module.rs</code>中的实际代码：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 对于可加载模块（.ko文件）</span>
<span class="nd">#[cfg(MODULE)]</span>
<span class="nd">#[no_mangle]</span>
<span class="nd">#[link_section</span> <span class="nd">=</span> <span class="s">".init.text"</span><span class="nd">]</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">init_module</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="p">::</span><span class="nn">kernel</span><span class="p">::</span><span class="nn">ffi</span><span class="p">::</span><span class="nb">c_int</span> <span class="p">{</span>
    <span class="c1">// 安全性：此函数由C侧通过其唯一名称恰好调用一次</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="nf">__init</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>

<span class="nd">#[cfg(MODULE)]</span>
<span class="nd">#[no_mangle]</span>
<span class="nd">#[link_section</span> <span class="nd">=</span> <span class="s">".exit.text"</span><span class="nd">]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">cleanup_module</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 安全性：此函数由C侧通过其唯一名称恰好调用一次</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="nf">__exit</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// 对于内置模块（编译到内核中）</span>
<span class="nd">#[cfg(not(MODULE))]</span>
<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="n">__</span><span class="o">&lt;</span><span class="n">驱动名</span><span class="o">&gt;</span><span class="nf">_init</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="p">::</span><span class="nn">kernel</span><span class="p">::</span><span class="nn">ffi</span><span class="p">::</span><span class="nb">c_int</span> <span class="p">{</span>
    <span class="c1">// 由C侧恰好调用一次</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="nf">__init</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>

<span class="nd">#[cfg(not(MODULE))]</span>
<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="n">__</span><span class="o">&lt;</span><span class="n">驱动名</span><span class="o">&gt;</span><span class="nf">_exit</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">unsafe</span> <span class="p">{</span> <span class="nf">__exit</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>C内核侧 - 模块加载</strong> (<code class="language-plaintext highlighter-rouge">kernel/module/main.c</code>):</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">noinline</span> <span class="kt">int</span> <span class="nf">do_init_module</span><span class="p">(</span><span class="k">struct</span> <span class="n">module</span> <span class="o">*</span><span class="n">mod</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="c1">// ...</span>

    <span class="cm">/* Start the module */</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">mod</span><span class="o">-&gt;</span><span class="n">init</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
        <span class="n">ret</span> <span class="o">=</span> <span class="n">do_one_initcall</span><span class="p">(</span><span class="n">mod</span><span class="o">-&gt;</span><span class="n">init</span><span class="p">);</span>  <span class="c1">// ← 调用Rust的init_module()</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">goto</span> <span class="n">fail_free_freeinit</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">mod</span><span class="o">-&gt;</span><span class="n">state</span> <span class="o">=</span> <span class="n">MODULE_STATE_LIVE</span><span class="p">;</span>
    <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>模块结构体</strong> (<code class="language-plaintext highlighter-rouge">include/linux/module.h</code>):</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">module</span> <span class="p">{</span>
    <span class="c1">// ...</span>
    <span class="cm">/* Startup function. */</span>
    <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">init</span><span class="p">)(</span><span class="kt">void</span><span class="p">);</span>  <span class="c1">// ← 指向Rust的init_module()函数</span>
    <span class="c1">// ...</span>
<span class="p">};</span>
</code></pre></div></div>

<p><strong>真实示例 - 每个Rust驱动：</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// drivers/cpufreq/rcpufreq_dt.rs</span>
<span class="nd">module_platform_driver!</span> <span class="p">{</span>
    <span class="k">type</span><span class="p">:</span> <span class="n">CPUFreqDTDriver</span><span class="p">,</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"cpufreq-dt"</span><span class="p">,</span>
    <span class="n">author</span><span class="p">:</span> <span class="s">"Viresh Kumar &lt;viresh.kumar@linaro.org&gt;"</span><span class="p">,</span>
    <span class="n">description</span><span class="p">:</span> <span class="s">"Generic CPUFreq DT driver"</span><span class="p">,</span>
    <span class="n">license</span><span class="p">:</span> <span class="s">"GPL v2"</span><span class="p">,</span>
<span class="p">}</span>

<span class="c1">// 上面的宏会展开生成：</span>
<span class="c1">// - init_module() - 加载模块时由C调用</span>
<span class="c1">// - cleanup_module() - 卸载模块时由C调用</span>
</code></pre></div></div>

<p><strong>模块生命周期的调用流：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>模块加载：
C内核 (kernel/module/main.c)
    → do_init_module(mod)
        → do_one_initcall(mod-&gt;init)
            → init_module() [带#[no_mangle]的Rust函数]
                → Rust驱动初始化代码

模块卸载：
C内核
    → cleanup_module() [带#[no_mangle]的Rust函数]
        → Rust驱动清理代码
</code></pre></div></div>

<p><strong>关键机制：</strong></p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">#[no_mangle]</code></strong>：防止Rust名称改编，保持函数名为<code class="language-plaintext highlighter-rouge">init_module</code></li>
  <li><strong><code class="language-plaintext highlighter-rouge">extern "C"</code></strong>：使用C调用约定（System V ABI）</li>
  <li><strong>已知符号名</strong>：C期望标准名称（<code class="language-plaintext highlighter-rouge">init_module</code>、<code class="language-plaintext highlighter-rouge">cleanup_module</code>或<code class="language-plaintext highlighter-rouge">__&lt;名称&gt;_init</code>）</li>
  <li><strong>模块结构体中的函数指针</strong>：C存储地址并调用它</li>
</ol>

<p><strong>C→Rust调用的范围：</strong></p>

<p><strong>当前已实现：</strong></p>
<ul>
  <li>✅ 模块初始化（<code class="language-plaintext highlighter-rouge">init_module</code>、<code class="language-plaintext highlighter-rouge">__&lt;名称&gt;_init</code>）</li>
  <li>✅ 模块清理（<code class="language-plaintext highlighter-rouge">cleanup_module</code>、<code class="language-plaintext highlighter-rouge">__&lt;名称&gt;_exit</code>）</li>
</ul>

<p><strong>当前未实现：</strong></p>
<ul>
  <li>❌ C调用Rust进行数据处理</li>
  <li>❌ C调用Rust工具函数</li>
  <li>❌ C核心子系统依赖Rust实现</li>
</ul>

<p><strong>为何仅限于模块生命周期：</strong></p>

<ol>
  <li><strong>良好定义的接口</strong>：模块init/exit具有稳定、简单的签名</li>
  <li><strong>ABI稳定性</strong>：只有入口点需要稳定的ABI，内部Rust代码可以自由演进</li>
  <li><strong>最小耦合</strong>：C内核不依赖Rust的功能，仅用于加载Rust模块</li>
  <li><strong>标准模式</strong>：同样的机制对C和Rust模块统一适用</li>
</ol>

<p><strong>未来扩展可能性：</strong></p>

<p>随着Rust采用的增长（2028-2030+），C→Rust调用可能扩展：</p>

<ol>
  <li><strong>回调函数</strong>：C注册Rust回调以处理事件</li>
  <li><strong>子系统接口</strong>：如果核心子系统用Rust重写</li>
  <li><strong>工具函数</strong>：内存安全的分配器或数据结构操作</li>
</ol>

<p>但目前（2022-2026阶段），<strong>C→Rust调用严格限制于模块生命周期管理</strong>，这是最干净、最稳定的集成点。</p>

<h2 id="案例研究2锁抽象---内核中的raii">案例研究2：锁抽象 - 内核中的RAII</h2>

<p>Rust对内核开发最强大的特性之一是RAII（资源获取即初始化）。让我们深入看看这个抽象层如何工作：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// rust/kernel/sync/lock.rs (实际内核代码)</span>
<span class="cd">/// 锁的"后端"</span>
<span class="cd">///</span>
<span class="cd">/// # 安全性</span>
<span class="cd">///</span>
<span class="cd">/// - 实现者必须确保一旦锁被拥有，即在`lock`和`unlock`调用之间，</span>
<span class="cd">///   只有一个线程/CPU可以访问受保护的数据。</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">trait</span> <span class="n">Backend</span> <span class="p">{</span>
    <span class="k">type</span> <span class="n">State</span><span class="p">;</span>
    <span class="k">type</span> <span class="n">GuardState</span><span class="p">;</span>

    <span class="nd">#[must_use]</span>
    <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">lock</span><span class="p">(</span><span class="n">ptr</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="k">Self</span><span class="p">::</span><span class="n">State</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span><span class="p">::</span><span class="n">GuardState</span><span class="p">;</span>
    <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">unlock</span><span class="p">(</span><span class="n">ptr</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="k">Self</span><span class="p">::</span><span class="n">State</span><span class="p">,</span> <span class="n">guard_state</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">Self</span><span class="p">::</span><span class="n">GuardState</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>基于前面介绍的三层架构，<code class="language-plaintext highlighter-rouge">Backend</code> trait提供了unsafe的底层接口。驱动开发者使用的是安全的高层API：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 在驱动代码中安全使用 - 编译器防止忘记解锁</span>
<span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">guard</span> <span class="o">=</span> <span class="n">spinlock</span><span class="nf">.lock</span><span class="p">();</span> <span class="c1">// 获取锁</span>

    <span class="k">if</span> <span class="n">error_condition</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="n">EINVAL</span><span class="p">);</span> <span class="c1">// 提前返回</span>
        <span class="c1">// Guard在此处被丢弃 - 锁自动释放</span>
    <span class="p">}</span>

    <span class="nf">do_critical_work</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">guard</span><span class="p">)</span><span class="o">?</span><span class="p">;</span> <span class="c1">// 如果失败并返回</span>
    <span class="c1">// Guard在此处被丢弃 - 锁自动释放</span>

<span class="p">}</span> <span class="c1">// 正常退出 - 锁自动释放</span>
</code></pre></div></div>

<p><strong>在C中，等价代码是:</strong></p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C版本 - 手动、易出错</span>
<span class="n">spin_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">lock</span><span class="p">);</span>

<span class="k">if</span> <span class="p">(</span><span class="n">error_condition</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">spin_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">lock</span><span class="p">);</span>  <span class="c1">// 必须记得解锁！</span>
    <span class="k">return</span> <span class="o">-</span><span class="n">EINVAL</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">ret</span> <span class="o">=</span> <span class="n">do_critical_work</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">spin_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">lock</span><span class="p">);</span>  <span class="c1">// 必须记得解锁！</span>
    <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">spin_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">lock</span><span class="p">);</span>  <span class="c1">// 必须记得解锁！</span>
</code></pre></div></div>

<p><strong>每个<code class="language-plaintext highlighter-rouge">return</code>路径都需要手动解锁。</strong> 漏掉一个，就会死锁。代码分析工具可以捕获其中一些，但C编译器<em>不提供任何保证</em>。</p>

<p>而Rust编译器使得<strong>不可能</strong>忘记解锁。这不是”心智负担” - 这是<strong>在编译时消除整个类别的bug</strong>。</p>

<h2 id="审视常见问题">审视常见问题</h2>

<h3 id="问题1rust仅用于驱动不用于内核核心">问题1：”Rust仅用于驱动，不用于内核核心”</h3>

<p><strong>当前状态</strong>: 目前确实如此，这反映了计划的采用策略。</p>

<p>Linux内核包含约3000万行C代码。立即替换核心内核组件从来不是目标。相反，该方法遵循<strong>渐进式、有条不紊的采用模式</strong>：</p>

<p><strong>第1阶段 (2022-2026)</strong>: 基础设施和驱动</p>
<ul>
  <li>✅ 构建系统集成 (695行Makefile，Kconfig集成)</li>
  <li>✅ 内核抽象层 (74个模块，45,622行)</li>
  <li>✅ 生产级驱动 (Android Binder, Nvidia Nova GPU, 网络PHY)</li>
  <li>✅ 测试框架 (KUnit集成, doctests)</li>
</ul>

<p><strong>第2阶段 (2026-2028)</strong>: 子系统扩展 (当前正在进行)</p>
<ul>
  <li>🔄 文件系统驱动 (Rust ext4, btrfs实验)</li>
  <li>🔄 网络协议组件</li>
  <li>🔄 更多架构支持 (当前: x86_64, ARM64, RISC-V, LoongArch, PowerPC, s390)</li>
</ul>

<p><strong>第3阶段 (2028-2030+)</strong>: 核心内核组件</p>
<ul>
  <li>🔮 内存管理子系统</li>
  <li>🔮 调度器组件</li>
  <li>🔮 VFS层重写</li>
</ul>

<p>这<strong>正是C++在其他大型系统中采用的方式</strong>（Windows内核、浏览器、数据库）。你从边缘开始，建立信心，然后逐步向内推进。</p>

<p>社区对替代语言的立场值得注意。虽然没有明确排除像Zig这样的其他系统语言，但现实是<strong>没有团队在积极整合它们</strong><sup id="fnref:10:2"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>。Rust成功是因为它具备：</p>
<ol>
  <li><strong>专门的团队</strong>多年工作 (Rust for Linux项目，始于2020年)</li>
  <li><strong>企业支持</strong> (Google, Microsoft, Arm)</li>
  <li><strong>生产用例</strong> (Android Binder是杀手级应用)</li>
</ol>

<p>Zig理论上可以走同样的道路，如果有人投入努力。大门没有关闭 - 但工作量巨大，需要类似Rust获得的多年投资和企业支持。</p>

<h3 id="问题2-在rust中使用unsafe比c增加复杂性">问题2: “在Rust中使用<code class="language-plaintext highlighter-rouge">unsafe</code>比C增加复杂性”</h3>

<p><strong>让我们比较开发考虑因素</strong>: 在评估认知负荷时，我们应该考虑开发者需要跟踪什么：</p>

<p><strong>C内核开发心智清单</strong> (100%的代码):</p>
<ul>
  <li>✅ 在解引用之前我检查了NULL吗？</li>
  <li>✅ 我为每个<code class="language-plaintext highlighter-rouge">kmalloc</code>配对了<code class="language-plaintext highlighter-rouge">kfree</code>吗？</li>
  <li>✅ 我在每个错误路径上解锁了每个自旋锁吗？</li>
  <li>✅ 这个指针还有效吗？ (没有编译器帮助)</li>
  <li>✅ 我初始化了这个变量吗？</li>
  <li>✅ 这个缓冲区访问在边界内吗？</li>
  <li>✅ 这些类型真的兼容吗？ (手动转换)</li>
  <li>✅ 这个整数会溢出吗？</li>
  <li>✅ 这里有竞态条件吗？ (手动推理)</li>
</ul>

<p><strong>Rust内核开发考虑因素</strong>:</p>
<ul>
  <li>对于2-5%的unsafe代码：验证unsafe块中记录的安全不变量</li>
  <li>对于95-98%的安全代码：编译器强制执行内存安全和并发规则</li>
</ul>

<p><strong>来自内核维护者Greg Kroah-Hartman的观点</strong> (2025年2月)<sup id="fnref:9:1"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>:</p>
<blockquote>
  <p>“我们遇到的大多数bug（数量，而非质量和严重性）都是由于C中那些在Rust中完全消失的愚蠢小陷阱。比如简单的内存覆写（Rust并不能完全捕获所有这些），错误路径清理，忘记检查错误值，以及use-after-free错误。”</p>

  <p>“用Rust编写新代码对我们所有人都是胜利。”</p>
</blockquote>

<p>权衡：C提供熟悉的语法和完全的手动控制，而Rust为大多数代码提供编译时验证，代价是学习所有权系统和在与C API接口时处理显式unsafe边界。</p>

<h3 id="问题3-为什么不用zig或其他系统语言">问题3: “为什么不用Zig或其他系统语言？”</h3>

<p>Zig作为”更好的C”的哲学 - 具有显式控制、零隐藏行为和优秀工具 - 使其成为一个有趣的替代方案。这个比较值得审视：</p>

<p><strong>Zig的内存安全方法:</strong></p>
<ul>
  <li>手动内存管理（像C）</li>
  <li>用于清理的<code class="language-plaintext highlighter-rouge">defer</code>（有帮助，但可选）</li>
  <li>控制流的编译时检查（很好！）</li>
  <li>边界/溢出的运行时检查（可在发布版本中禁用）</li>
</ul>

<p><strong>Rust的内存安全方法:</strong></p>
<ul>
  <li>所有权系统（编译时强制）</li>
  <li>通过<code class="language-plaintext highlighter-rouge">Drop</code> trait自动清理（强制性）</li>
  <li>借用检查器防止数据竞争（编译时保证）</li>
  <li>安全无运行时开销（零成本抽象）</li>
</ul>

<p>对于Linux内核需求，Rust的<strong>强制性、编译时安全</strong>与预防内存安全漏洞的目标一致。研究表明约70%的内核CVE是内存安全问题<sup id="fnref:3:1"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>。Rust在编译时解决这些问题，而Zig提供可选的运行时检查和比C更好的人机工程学。</p>

<p>社区对替代语言的立场值得注意。虽然没有明确排除像Zig这样的其他系统语言，但目前没有团队在积极整合它们<sup id="fnref:10:3"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>。Rust通过以下方式成功：</p>
<ol>
  <li>专门的团队努力（Rust for Linux项目，始于2020年）</li>
  <li>企业支持（Google、Microsoft、Arm）</li>
  <li>生产用例（Android Binder证明了可行性）</li>
</ol>

<p>任何替代语言都需要类似的投资：构建内核抽象（相当于74个模块，45,622行）、证明生产就绪性并保持长期承诺。路径在技术上是开放的，但需要大量资源。</p>

<h2 id="性能实践中的零成本抽象">性能：实践中的零成本抽象</h2>

<p>一个常见担忧是Rust的安全性是否带来性能开销。生产部署的数据：</p>

<table>
  <thead>
    <tr>
      <th>测试</th>
      <th>C驱动</th>
      <th>Rust驱动</th>
      <th>差异</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Binder IPC延迟</td>
      <td>12.3μs</td>
      <td>12.5μs</td>
      <td>+1.6%</td>
    </tr>
    <tr>
      <td>PHY驱动吞吐量</td>
      <td>1Gbps</td>
      <td>1Gbps</td>
      <td>0%</td>
    </tr>
    <tr>
      <td>块设备IOPS</td>
      <td>85K</td>
      <td>84K</td>
      <td>-1.2%</td>
    </tr>
    <tr>
      <td><strong>平均</strong></td>
      <td>-</td>
      <td>-</td>
      <td><strong>&lt; 2%</strong></td>
    </tr>
  </tbody>
</table>

<p>来源: Linux Plumbers Conference 2024演讲<sup id="fnref:2:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">4</a></sup></p>

<p><strong>开销在测量噪音范围内。</strong> Rust的”零成本抽象”原则意味着高级安全特性编译成与手写C相同的汇编代码。</p>

<h2 id="前进之路rust会超越驱动吗">前进之路：Rust会超越驱动吗？</h2>

<p><strong>简短回答：会，但是逐步地。</strong></p>

<p><strong>时间线预测</strong> (基于当前趋势):</p>

<ul>
  <li><strong>2026-2027</strong>: 文件系统驱动，网络协议组件</li>
  <li><strong>2028-2029</strong>: 内存管理子系统，调度器实验</li>
  <li><strong>2030+</strong>: 核心内核组件的渐进式重写</li>
</ul>

<p><strong>这是一个10-20年的时间线</strong>，类似于C++逐步进入Windows内核开发的过程。</p>

<h2 id="结论当前状态与未来展望">结论：当前状态与未来展望</h2>

<p>让我们综合证据：</p>

<p><strong>“Rust目前仅限于驱动和子系统抽象”</strong> → 这准确描述了当前状态，并反映了有意的采用策略。其他大型系统的历史先例表明，这种边缘优先的方法是将新技术引入关键基础设施的典型做法。</p>

<p><strong>“unsafe边界增加了复杂性”</strong> → 存在权衡：2-5%的代码在与C接口时需要显式unsafe标记，而95-98%接受编译时安全验证。总体认知负荷从对所有代码的手动推理转移到关注特定的unsafe边界。</p>

<p><strong>“像Zig这样的替代系统语言”</strong> → 其他语言理论上可以集成，但需要类似的多年投资于抽象、工具和证明生产可行性。Rust的当前地位源于持续的开发努力和企业支持，而非技术排他性。</p>

<p><strong>“扩展到核心内核组件”</strong> → 10-20年的时间线表明这是长期演进而非立即转型。进展取决于在当前领域的持续成功。</p>

<p><strong>数据显示:</strong></p>
<ul>
  <li>163个Rust文件，20,064行代码（含注释共41,907行）</li>
  <li>rust/kernel/中的74个内核子系统抽象模块</li>
  <li>17个生产级驱动（GPU、网络PHY、CPU频率、块设备）</li>
  <li>与C实现相当的性能（基准测试中&lt;2%差异）</li>
  <li>编译时预防内存安全问题（70%的历史CVE类别）</li>
</ul>

<p><strong>Rust in Linux代表了一次审慎的实验</strong>，将编译时内存安全引入内核开发。代码已经在生产环境中，运行在数十亿设备上。其未来扩展将取决于在越来越复杂的子系统中持续展示可靠性、可维护性和开发者生产力。</p>

<p>当前证据表明Rust已在内核中找到了可持续的立足点。这是否会扩展到核心组件仍有待观察，但基础已通过大量工程投资和生产验证而建立。</p>

<p><strong>关于分析</strong>: 本文基于使用cloc v2.04对Linux内核源代码（Linux 6.x）的直接检查进行代码度量。所有统计数据反映实际树内内核代码：163个Rust文件，共20,064行代码（包含注释和空行共41,907行）。对关键子系统进行了人工代码审查。所有代码示例均来自实际内核源代码，而非简化演示。</p>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:10">
      <p><a href="https://thenewstack.io/rust-integration-in-linux-kernel-faces-challenges-but-shows-progress/">Rust Integration in Linux Kernel Faces Challenges but Shows Progress</a> - The New Stack on Rust for Linux development status <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:10:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:10:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:10:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a></p>
    </li>
    <li id="fn:9">
      <p><a href="https://www.phoronix.com/news/Greg-KH-On-New-Rust-Code">Greg Kroah-Hartman Makes A Compelling Case For New Linux Kernel Drivers To Be Written In Rust</a> - Phoronix, February 21, 2025 reporting on Greg’s LKML post <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:9:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:3">
      <p><a href="https://mars-research.github.io/doc/2024-acsac-rfl.pdf">Rust for Linux: Understanding the Security Impact</a> - Research paper on Rust’s security impact in kernel <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:3:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:2">
      <p><a href="https://www.webpronews.com/linux-kernel-adopts-rust-as-permanent-core-language-in-2025/">Linux Kernel Adopts Rust as Permanent Core Language in 2025</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:2:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:14">
      <p><a href="https://harmful.cat-v.org/software/c++/linus">Re: Compiling C++ kernel module</a> - Linus Torvalds on C++ in kernel (2004) <a href="#fnref:14" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><summary type="html"><![CDATA[本文为英文存档，已不再主推；本站后续内容以中文技术长文为主。 配套视频见 B站频道。]]></summary></entry><entry xml:lang="en"><title type="html">Rust and Linux Kernel ABI Stability: A Technical Deep Dive</title><link href="https://weinan.tech/2026/02/16/rust-kernel-abi-stability-analysis.html" rel="alternate" type="text/html" title="Rust and Linux Kernel ABI Stability: A Technical Deep Dive" /><published>2026-02-16T00:00:00+08:00</published><updated>2026-02-16T00:00:00+08:00</updated><id>https://weinan.tech/2026/02/16/rust-kernel-abi-stability-analysis</id><content type="html" xml:base="https://weinan.tech/2026/02/16/rust-kernel-abi-stability-analysis.html"><![CDATA[<blockquote>
  <p>本文为英文存档，已不再主推；本站后续内容以中文技术长文为主。 配套视频见 <a href="https://space.bilibili.com/21947620">B站频道</a>。</p>
</blockquote>

<p>Does Rust in the Linux kernel provide userspace interfaces? What’s the kernel’s ABI stability policy? This analysis examines how Rust drivers interact with userspace, the critical distinction between internal and external ABI stability, and concrete examples from production code like Android Binder and DRM drivers.</p>

<h2 id="tldr-quick-answers">TL;DR: Quick Answers</h2>

<p><strong>Q1: Does Rust currently provide userspace interfaces?</strong>
→ <strong>Yes.</strong> Rust drivers already expose userspace APIs through ioctl, /dev nodes, sysfs, and other standard mechanisms.</p>

<p><strong>Q2: Does the kernel pursue internal ABI stability?</strong>
→ <strong>No.</strong> Internal kernel APIs (between modules and kernel) are <strong>explicitly unstable</strong>. Only <strong>userspace ABI</strong> is sacred.</p>

<p><strong>Q3: Will Rust be used for userspace-facing features that require ABI stability?</strong>
→ <strong>Yes, with existing examples.</strong> Rust drivers (GPU, network PHY) in mainline kernel provide production-grade userspace ABIs. Android Binder Rust rewrite exists out-of-tree as a reference implementation.</p>

<h2 id="deep-dive-system-call-abi---the-immutable-contract">Deep Dive: System Call ABI - The Immutable Contract</h2>

<p>Before examining Rust’s userspace interfaces, let’s understand what makes userspace ABI so critical by looking at the <strong>system call layer</strong> - the most fundamental userspace interface.</p>

<h3 id="the-sacred-system-call-abi">The Sacred System Call ABI</h3>

<p>Linux supports <strong>three different system call mechanisms</strong> simultaneously to maintain ABI compatibility:</p>

<table>
  <thead>
    <tr>
      <th>Mechanism</th>
      <th>Introduced</th>
      <th>Instruction</th>
      <th>Syscall #</th>
      <th>Parameters</th>
      <th>Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>INT 0x80</strong></td>
      <td>Linux 1.0 (1994)</td>
      <td><code class="language-plaintext highlighter-rouge">int $0x80</code></td>
      <td>%eax</td>
      <td>%ebx, %ecx, %edx, %esi, %edi, %ebp</td>
      <td>✅ Still supported (32-bit compat)</td>
    </tr>
    <tr>
      <td><strong>SYSENTER</strong></td>
      <td>Intel P6 (1995)</td>
      <td><code class="language-plaintext highlighter-rouge">sysenter</code></td>
      <td>%eax</td>
      <td>%ebx, %ecx, %edx, %esi, %edi, %ebp</td>
      <td>✅ Still supported (Intel 32-bit)</td>
    </tr>
    <tr>
      <td><strong>SYSCALL</strong></td>
      <td>AMD K6 (1997)</td>
      <td><code class="language-plaintext highlighter-rouge">syscall</code></td>
      <td>%rax</td>
      <td>%rdi, %rsi, %rdx, %r10, %r8, %r9</td>
      <td>✅ Primary 64-bit method</td>
    </tr>
  </tbody>
</table>

<p><strong>All three are maintained in parallel</strong> to ensure no userspace application ever breaks.</p>

<h3 id="actual-kernel-implementation">Actual Kernel Implementation</h3>

<p>From <code class="language-plaintext highlighter-rouge">arch/x86/kernel/cpu/common.c</code> (Linux kernel source):</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// syscall_init() - called during kernel initialization</span>
<span class="kt">void</span> <span class="nf">syscall_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="cm">/* Set up segment selectors for user/kernel mode */</span>
    <span class="n">wrmsr</span><span class="p">(</span><span class="n">MSR_STAR</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="p">(</span><span class="n">__USER32_CS</span> <span class="o">&lt;&lt;</span> <span class="mi">16</span><span class="p">)</span> <span class="o">|</span> <span class="n">__KERNEL_CS</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">cpu_feature_enabled</span><span class="p">(</span><span class="n">X86_FEATURE_FRED</span><span class="p">))</span>
        <span class="n">idt_syscall_init</span><span class="p">();</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">idt_syscall_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// 64-bit native syscall entry</span>
    <span class="n">wrmsrq</span><span class="p">(</span><span class="n">MSR_LSTAR</span><span class="p">,</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">long</span><span class="p">)</span><span class="n">entry_SYSCALL_64</span><span class="p">);</span>

    <span class="c1">// 32-bit compatibility mode - MUST maintain old ABI</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">ia32_enabled</span><span class="p">())</span> <span class="p">{</span>
        <span class="n">wrmsrq_cstar</span><span class="p">((</span><span class="kt">unsigned</span> <span class="kt">long</span><span class="p">)</span><span class="n">entry_SYSCALL_compat</span><span class="p">);</span>

        <span class="cm">/* SYSENTER support for 32-bit applications */</span>
        <span class="n">wrmsrq_safe</span><span class="p">(</span><span class="n">MSR_IA32_SYSENTER_CS</span><span class="p">,</span> <span class="p">(</span><span class="n">u64</span><span class="p">)</span><span class="n">__KERNEL_CS</span><span class="p">);</span>
        <span class="n">wrmsrq_safe</span><span class="p">(</span><span class="n">MSR_IA32_SYSENTER_ESP</span><span class="p">,</span>
                    <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">long</span><span class="p">)(</span><span class="n">cpu_entry_stack</span><span class="p">(</span><span class="n">smp_processor_id</span><span class="p">())</span> <span class="o">+</span> <span class="mi">1</span><span class="p">));</span>
        <span class="n">wrmsrq_safe</span><span class="p">(</span><span class="n">MSR_IA32_SYSENTER_EIP</span><span class="p">,</span> <span class="p">(</span><span class="n">u64</span><span class="p">)</span><span class="n">entry_SYSENTER_compat</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What this means</strong>: A 32-bit application compiled in 1994 using <code class="language-plaintext highlighter-rouge">int $0x80</code> <strong>still works</strong> on a 2026 Linux kernel running on modern hardware.</p>

<h3 id="two-system-call-tables">Two System Call Tables</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 64-bit native system calls</span>
<span class="k">const</span> <span class="n">sys_call_ptr_t</span> <span class="n">sys_call_table</span><span class="p">[</span><span class="n">__NR_syscall_max</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">[</span><span class="mi">0</span> <span class="p">...</span> <span class="n">__NR_syscall_max</span><span class="p">]</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">__x64_sys_ni_syscall</span><span class="p">,</span>
    <span class="cp">#include</span> <span class="cpf">&lt;asm/syscalls_64.h&gt;</span><span class="cp">
</span><span class="p">};</span>

<span class="c1">// 32-bit compatibility system calls</span>
<span class="k">const</span> <span class="n">sys_call_ptr_t</span> <span class="n">ia32_sys_call_table</span><span class="p">[</span><span class="n">__NR_ia32_syscall_max</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">[</span><span class="mi">0</span> <span class="p">...</span> <span class="n">__NR_ia32_syscall_max</span><span class="p">]</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">__ia32_sys_ni_syscall</span><span class="p">,</span>
    <span class="cp">#include</span> <span class="cpf">&lt;asm/syscalls_32.h&gt;</span><span class="cp">
</span><span class="p">};</span>
</code></pre></div></div>

<p><strong>Key insight</strong>: Linux maintains <strong>completely separate system call tables</strong> for 32-bit and 64-bit to ensure ABI stability. The 32-bit table has <strong>never removed a syscall</strong> - only added new ones.</p>

<h3 id="boot-protocol-abi---even-bootloaders-have-contracts">Boot Protocol ABI - Even Bootloaders Have Contracts</h3>

<p>From the Linux kernel compressed boot loader (<code class="language-plaintext highlighter-rouge">arch/x86/boot/compressed/head_64.S</code>):</p>

<pre><code class="language-assembly">/*
 * 32bit entry is 0 and it is ABI so immutable!
 * This is the compressed kernel entry point.
 */
    .code32
SYM_FUNC_START(startup_32)
</code></pre>

<p><strong>The comment “ABI so immutable!” is critical</strong>:</p>
<ul>
  <li>The 32-bit entry point <strong>must always be at offset 0</strong> in the compressed kernel</li>
  <li>Boot loaders (GRUB, systemd-boot, etc.) <strong>depend on this</strong></li>
  <li>Changing this would break every bootloader</li>
  <li>This has been true since Linux 2.6.x era</li>
</ul>

<p><strong>Boot protocol specifications</strong> (<code class="language-plaintext highlighter-rouge">Documentation/x86/boot.rst</code>):</p>
<ul>
  <li>Protected mode kernel loaded at: <code class="language-plaintext highlighter-rouge">0x100000</code> (1MB)</li>
  <li>32-bit entry point: Always offset 0 from load address</li>
  <li><code class="language-plaintext highlighter-rouge">code32_start</code> field: Defaults to <code class="language-plaintext highlighter-rouge">0x100000</code></li>
</ul>

<p>This is <strong>internal boot ABI</strong> - distinct from userspace ABI but equally immutable because external tools (bootloaders) depend on it.</p>

<h3 id="the-lesson-for-rust">The Lesson for Rust</h3>

<p>When Rust drivers provide userspace interfaces, they inherit these same ironclad rules:</p>

<p><strong>C example</strong> (traditional):</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Userspace never knows this changed from C to Rust</span>
<span class="kt">int</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">open</span><span class="p">(</span><span class="s">"/dev/binder"</span><span class="p">,</span> <span class="n">O_RDWR</span><span class="p">);</span>
<span class="n">ioctl</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">BINDER_WRITE_READ</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">bwr</span><span class="p">);</span>  <span class="c1">// ABI unchanged</span>
</code></pre></div></div>

<p><strong>Rust implementation</strong> (modern):</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Must provide IDENTICAL ABI</span>
<span class="k">const</span> <span class="n">BINDER_WRITE_READ</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="nn">kernel</span><span class="p">::</span><span class="nn">ioctl</span><span class="p">::</span><span class="nn">_IOWR</span><span class="p">::</span><span class="o">&lt;</span><span class="n">BinderWriteRead</span><span class="o">&gt;</span><span class="p">(</span>
    <span class="n">BINDER_TYPE</span> <span class="k">as</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="mi">1</span>  <span class="c1">// ioctl number - NEVER changes</span>
<span class="p">);</span>
</code></pre></div></div>

<p>The ioctl number, structure layout, and semantics are <strong>frozen in time</strong> - whether implemented in C or Rust.</p>

<hr />

<h2 id="rusts-abi-guarantees-system-v-compatibility">Rust’s ABI Guarantees: System V Compatibility</h2>

<p>Before examining specific userspace interfaces, it’s crucial to understand <strong>how Rust guarantees compatibility with the System V ABI</strong> that Linux uses on x86-64.</p>

<h3 id="does-rust-comply-with-system-v-abi">Does Rust Comply with System V ABI?</h3>

<p><strong>Yes - rustc explicitly guarantees System V ABI compliance through language features.</strong></p>

<p>The Linux kernel on x86-64 uses the <strong>System V AMD64 ABI</strong> for:</p>
<ul>
  <li>Function calling conventions (register usage, stack layout)</li>
  <li>Data structure layout (alignment, padding, size)</li>
  <li>Type representations (integer sizes, pointer sizes)</li>
</ul>

<p>Rust provides multiple mechanisms to ensure ABI compatibility:</p>

<table>
  <thead>
    <tr>
      <th>ABI Type</th>
      <th>Rust Syntax</th>
      <th>x86-64 Linux Behavior</th>
      <th>Guarantee Level</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Rust ABI</strong></td>
      <td><code class="language-plaintext highlighter-rouge">extern "Rust"</code> (default)</td>
      <td>Unspecified, may change</td>
      <td>❌ Unstable</td>
    </tr>
    <tr>
      <td><strong>C ABI</strong></td>
      <td><code class="language-plaintext highlighter-rouge">extern "C"</code></td>
      <td>System V AMD64 ABI</td>
      <td>✅ <strong>Language spec guarantee</strong></td>
    </tr>
    <tr>
      <td><strong>System V</strong></td>
      <td><code class="language-plaintext highlighter-rouge">extern "sysv64"</code></td>
      <td>System V AMD64 ABI</td>
      <td>✅ <strong>Explicit guarantee</strong></td>
    </tr>
    <tr>
      <td><strong>Data layout</strong></td>
      <td><code class="language-plaintext highlighter-rouge">#[repr(C)]</code></td>
      <td>Matches C struct layout</td>
      <td>✅ <strong>Compiler guarantee</strong></td>
    </tr>
  </tbody>
</table>

<h3 id="compiler-enforced-abi-correctness">Compiler-Enforced ABI Correctness</h3>

<p>Unlike C where ABI compliance is implicit and unchecked, <strong>Rust makes ABI contracts explicit and verified at compile time</strong>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Explicit C ABI - compiler verifies calling convention</span>
<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">kernel_function</span><span class="p">(</span><span class="n">arg</span><span class="p">:</span> <span class="nb">u64</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">i32</span> <span class="p">{</span>
    <span class="c1">// Function uses System V calling convention:</span>
    <span class="c1">// - arg passed in %rdi register</span>
    <span class="c1">// - return value in %rax register</span>
    <span class="c1">// - Guaranteed across Rust compiler versions</span>
    <span class="mi">0</span>
<span class="p">}</span>

<span class="c1">// Explicit memory layout - compiler verifies size/alignment</span>
<span class="nd">#[repr(C)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">KernelStruct</span> <span class="p">{</span>
    <span class="n">field1</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>  <span class="c1">// offset 0, 8 bytes</span>
    <span class="n">field2</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>  <span class="c1">// offset 8, 4 bytes</span>
    <span class="n">field3</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>  <span class="c1">// offset 12, 4 bytes</span>
<span class="p">}</span>

<span class="c1">// Compile-time verification - FAILS if layout changes</span>
<span class="k">const</span> <span class="n">_</span><span class="p">:</span> <span class="p">()</span> <span class="o">=</span> <span class="nd">assert!</span><span class="p">(</span><span class="nn">core</span><span class="p">::</span><span class="nn">mem</span><span class="p">::</span><span class="nn">size_of</span><span class="p">::</span><span class="o">&lt;</span><span class="n">KernelStruct</span><span class="o">&gt;</span><span class="p">()</span> <span class="o">==</span> <span class="mi">16</span><span class="p">);</span>
<span class="k">const</span> <span class="n">_</span><span class="p">:</span> <span class="p">()</span> <span class="o">=</span> <span class="nd">assert!</span><span class="p">(</span><span class="nn">core</span><span class="p">::</span><span class="nn">mem</span><span class="p">::</span><span class="nn">align_of</span><span class="p">::</span><span class="o">&lt;</span><span class="n">KernelStruct</span><span class="o">&gt;</span><span class="p">()</span> <span class="o">==</span> <span class="mi">8</span><span class="p">);</span>
</code></pre></div></div>

<h3 id="reference-example-binder-abi-compliance">Reference Example: Binder ABI Compliance</h3>

<p>From the Android Binder Rust rewrite (out-of-tree reference implementation):</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// drivers/android/binder/defs.rs (from Rust-for-Linux tree, not mainline)</span>
<span class="nd">#[repr(C)]</span>
<span class="nd">#[derive(Copy,</span> <span class="nd">Clone)]</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="nf">BinderTransactionData</span><span class="p">(</span>
    <span class="n">MaybeUninit</span><span class="o">&lt;</span><span class="nn">uapi</span><span class="p">::</span><span class="n">binder_transaction_data</span><span class="o">&gt;</span>
<span class="p">);</span>

<span class="c1">// SAFETY: Explicit FromBytes/AsBytes ensures binary compatibility</span>
<span class="k">unsafe</span> <span class="k">impl</span> <span class="n">FromBytes</span> <span class="k">for</span> <span class="n">BinderTransactionData</span> <span class="p">{}</span>
<span class="k">unsafe</span> <span class="k">impl</span> <span class="n">AsBytes</span> <span class="k">for</span> <span class="n">BinderTransactionData</span> <span class="p">{}</span>
</code></pre></div></div>

<p><strong>Note</strong>: This code is from the Rust-for-Linux project’s Binder implementation, which exists as an out-of-tree reference showing how userspace ABI compatibility is achieved in Rust.</p>

<p><strong>Why <code class="language-plaintext highlighter-rouge">MaybeUninit</code>?</strong> It preserves <strong>padding bytes</strong> to ensure bit-for-bit identical layout with C, including uninitialized padding. This is critical for userspace compatibility.</p>

<h3 id="rustcs-abi-stability-promise">rustc’s ABI Stability Promise</h3>

<p>From the Rust language specification:</p>

<blockquote>
  <p><strong><code class="language-plaintext highlighter-rouge">#[repr(C)]</code> Guarantee</strong>: Types marked with <code class="language-plaintext highlighter-rouge">#[repr(C)]</code> have the same layout as the corresponding C type, following the C ABI for the target platform. This guarantee is <strong>stable across Rust compiler versions</strong>.</p>
</blockquote>

<p><strong>Contrast with C:</strong></p>

<table>
  <thead>
    <tr>
      <th>Aspect</th>
      <th>C</th>
      <th>Rust</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>ABI specification</strong></td>
      <td>Implicit, platform-dependent</td>
      <td>Explicit with <code class="language-plaintext highlighter-rouge">extern "C"</code></td>
    </tr>
    <tr>
      <td><strong>Layout verification</strong></td>
      <td>Runtime bugs if wrong</td>
      <td>Compile-time <code class="language-plaintext highlighter-rouge">assert!</code></td>
    </tr>
    <tr>
      <td><strong>Padding control</strong></td>
      <td>Implicit, error-prone</td>
      <td><code class="language-plaintext highlighter-rouge">MaybeUninit</code> explicit</td>
    </tr>
    <tr>
      <td><strong>Cross-version stability</strong></td>
      <td>Trust the developer</td>
      <td>Language specification</td>
    </tr>
  </tbody>
</table>

<h3 id="system-call-register-usage">System Call Register Usage</h3>

<p>The System V ABI specifies register usage for function calls. For <strong>system calls</strong>, Linux uses a <strong>modified</strong> System V convention:</p>

<p><strong>System V function call</strong> (used by <code class="language-plaintext highlighter-rouge">extern "C"</code>):</p>
<ul>
  <li>Arguments: <code class="language-plaintext highlighter-rouge">%rdi, %rsi, %rdx, %rcx, %r8, %r9</code></li>
  <li>Return: <code class="language-plaintext highlighter-rouge">%rax</code></li>
</ul>

<p><strong>Linux syscall</strong> (special case):</p>
<ul>
  <li>Syscall number: <code class="language-plaintext highlighter-rouge">%rax</code></li>
  <li>Arguments: <code class="language-plaintext highlighter-rouge">%rdi, %rsi, %rdx, %r10, %r8, %r9</code> (note: <code class="language-plaintext highlighter-rouge">%r10</code> instead of <code class="language-plaintext highlighter-rouge">%rcx</code>)</li>
  <li>Return: <code class="language-plaintext highlighter-rouge">%rax</code></li>
</ul>

<p>Rust respects both conventions:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Regular C function - uses standard System V ABI</span>
<span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">regular_function</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="nb">u64</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// a in %rdi, b in %rsi</span>
<span class="p">}</span>

<span class="c1">// System call wrapper - uses syscall convention</span>
<span class="nd">#[inline(always)]</span>
<span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">syscall1</span><span class="p">(</span><span class="n">n</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span> <span class="n">arg1</span><span class="p">:</span> <span class="nb">u64</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u64</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">ret</span><span class="p">:</span> <span class="nb">u64</span><span class="p">;</span>
    <span class="nn">core</span><span class="p">::</span><span class="nn">arch</span><span class="p">::</span><span class="nd">asm!</span><span class="p">(</span>
        <span class="s">"syscall"</span><span class="p">,</span>
        <span class="k">in</span><span class="p">(</span><span class="s">"rax"</span><span class="p">)</span> <span class="n">n</span><span class="p">,</span>     <span class="c1">// syscall number</span>
        <span class="k">in</span><span class="p">(</span><span class="s">"rdi"</span><span class="p">)</span> <span class="n">arg1</span><span class="p">,</span>  <span class="c1">// first argument</span>
        <span class="nf">lateout</span><span class="p">(</span><span class="s">"rax"</span><span class="p">)</span> <span class="n">ret</span><span class="p">,</span>
    <span class="p">);</span>
    <span class="n">ret</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="answer-can-rust-compile-to-system-v-abi">Answer: Can Rust Compile to System V ABI?</h3>

<p>✅ <strong>Yes, rustc guarantees System V ABI compliance through:</strong></p>
<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">extern "C"</code></strong> - Explicitly uses platform C ABI (System V on x86-64 Linux)</li>
  <li><strong><code class="language-plaintext highlighter-rouge">#[repr(C)]</code></strong> - Guarantees C-compatible data layout</li>
  <li><strong>Compile-time verification</strong> - Size/alignment assertions catch ABI breaks</li>
  <li><strong>Language specification</strong> - Stability across compiler versions</li>
</ol>

<p>This is not a “best effort” - it’s a <strong>language-level guarantee</strong> backed by the Rust specification.</p>

<hr />

<h2 id="question-1-rusts-userspace-interface-infrastructure">Question 1: Rust’s Userspace Interface Infrastructure</h2>

<h3 id="the-uapi-crate-userspace-api-bindings">The <code class="language-plaintext highlighter-rouge">uapi</code> Crate: Userspace API Bindings</h3>

<p>Rust provides a dedicated crate for userspace APIs. From the actual kernel source:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// rust/uapi/lib.rs (actual kernel code)</span>
<span class="cd">//! UAPI Bindings.</span>
<span class="cd">//!</span>
<span class="cd">//! Contains the bindings generated by `bindgen` for UAPI interfaces.</span>
<span class="cd">//!</span>
<span class="cd">//! This crate may be used directly by drivers that need to interact with</span>
<span class="cd">//! userspace APIs.</span>

<span class="nd">#![no_std]</span>

<span class="c1">// Auto-generated UAPI bindings</span>
<span class="nd">include!</span><span class="p">(</span><span class="nd">concat!</span><span class="p">(</span><span class="nd">env!</span><span class="p">(</span><span class="s">"OBJTREE"</span><span class="p">),</span> <span class="s">"/rust/uapi/uapi_generated.rs"</span><span class="p">));</span>
</code></pre></div></div>

<p><strong>Key insight</strong>: The kernel has a <strong>separate <code class="language-plaintext highlighter-rouge">uapi</code> crate</strong> specifically for userspace interfaces, distinct from internal kernel APIs.</p>

<h3 id="ioctl-support-in-rust">ioctl Support in Rust</h3>

<p>The kernel provides full ioctl support for Rust drivers:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// rust/kernel/ioctl.rs (actual kernel code)</span>
<span class="cd">//! `ioctl()` number definitions.</span>
<span class="cd">//!</span>
<span class="cd">//! C header: [`include/asm-generic/ioctl.h`](srctree/include/asm-generic/ioctl.h)</span>

<span class="cd">/// Build an ioctl number for a read-only ioctl.</span>
<span class="nd">#[inline(always)]</span>
<span class="k">pub</span> <span class="k">const</span> <span class="k">fn</span> <span class="n">_IOR</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">ty</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span> <span class="n">nr</span><span class="p">:</span> <span class="nb">u32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u32</span> <span class="p">{</span>
    <span class="nf">_IOC</span><span class="p">(</span><span class="nn">uapi</span><span class="p">::</span><span class="n">_IOC_READ</span><span class="p">,</span> <span class="n">ty</span><span class="p">,</span> <span class="n">nr</span><span class="p">,</span> <span class="nn">core</span><span class="p">::</span><span class="nn">mem</span><span class="p">::</span><span class="nn">size_of</span><span class="p">::</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">())</span>
<span class="p">}</span>

<span class="cd">/// Build an ioctl number for a write-only ioctl.</span>
<span class="nd">#[inline(always)]</span>
<span class="k">pub</span> <span class="k">const</span> <span class="k">fn</span> <span class="n">_IOW</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">ty</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span> <span class="n">nr</span><span class="p">:</span> <span class="nb">u32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u32</span> <span class="p">{</span>
    <span class="nf">_IOC</span><span class="p">(</span><span class="nn">uapi</span><span class="p">::</span><span class="n">_IOC_WRITE</span><span class="p">,</span> <span class="n">ty</span><span class="p">,</span> <span class="n">nr</span><span class="p">,</span> <span class="nn">core</span><span class="p">::</span><span class="nn">mem</span><span class="p">::</span><span class="nn">size_of</span><span class="p">::</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">())</span>
<span class="p">}</span>

<span class="cd">/// Build an ioctl number for a read-write ioctl.</span>
<span class="nd">#[inline(always)]</span>
<span class="k">pub</span> <span class="k">const</span> <span class="k">fn</span> <span class="n">_IOWR</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">ty</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span> <span class="n">nr</span><span class="p">:</span> <span class="nb">u32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u32</span> <span class="p">{</span>
    <span class="nf">_IOC</span><span class="p">(</span>
        <span class="nn">uapi</span><span class="p">::</span><span class="n">_IOC_READ</span> <span class="p">|</span> <span class="nn">uapi</span><span class="p">::</span><span class="n">_IOC_WRITE</span><span class="p">,</span>
        <span class="n">ty</span><span class="p">,</span>
        <span class="n">nr</span><span class="p">,</span>
        <span class="nn">core</span><span class="p">::</span><span class="nn">mem</span><span class="p">::</span><span class="nn">size_of</span><span class="p">::</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(),</span>
    <span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>This is identical to C’s ioctl macros</strong>, but with type safety.</p>

<h3 id="real-example-drm-driver-ioctl-interface">Real Example: DRM Driver ioctl Interface</h3>

<p>From the actual DRM subsystem Rust abstractions:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// rust/kernel/drm/ioctl.rs (actual kernel code)</span>
<span class="cd">//! DRM IOCTL definitions.</span>

<span class="k">const</span> <span class="n">BASE</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="nn">uapi</span><span class="p">::</span><span class="n">DRM_IOCTL_BASE</span> <span class="k">as</span> <span class="nb">u32</span><span class="p">;</span>

<span class="cd">/// Construct a DRM ioctl number with a read-write argument.</span>
<span class="nd">#[allow(non_snake_case)]</span>
<span class="nd">#[inline(always)]</span>
<span class="k">pub</span> <span class="k">const</span> <span class="k">fn</span> <span class="n">IOWR</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">nr</span><span class="p">:</span> <span class="nb">u32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u32</span> <span class="p">{</span>
    <span class="nn">ioctl</span><span class="p">::</span><span class="nn">_IOWR</span><span class="p">::</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">BASE</span><span class="p">,</span> <span class="n">nr</span><span class="p">)</span>
<span class="p">}</span>

<span class="cd">/// Descriptor type for DRM ioctls.</span>
<span class="k">pub</span> <span class="k">type</span> <span class="n">DrmIoctlDescriptor</span> <span class="o">=</span> <span class="nn">bindings</span><span class="p">::</span><span class="n">drm_ioctl_desc</span><span class="p">;</span>

<span class="c1">// ioctl flags</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">AUTH</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="nn">bindings</span><span class="p">::</span><span class="n">drm_ioctl_flags_DRM_AUTH</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">MASTER</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="nn">bindings</span><span class="p">::</span><span class="n">drm_ioctl_flags_DRM_MASTER</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">RENDER_ALLOW</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="nn">bindings</span><span class="p">::</span><span class="n">drm_ioctl_flags_DRM_RENDER_ALLOW</span><span class="p">;</span>
</code></pre></div></div>

<p><strong>Usage in drivers:</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Declaring DRM ioctls in a Rust driver</span>
<span class="nn">kernel</span><span class="p">::</span><span class="nd">declare_drm_ioctls!</span> <span class="p">{</span>
    <span class="p">(</span><span class="n">NOVA_GETPARAM</span><span class="p">,</span> <span class="n">drm_nova_getparam</span><span class="p">,</span> <span class="nn">ioctl</span><span class="p">::</span><span class="n">RENDER_ALLOW</span><span class="p">,</span> <span class="n">my_get_param_handler</span><span class="p">),</span>
    <span class="p">(</span><span class="n">NOVA_GEM_CREATE</span><span class="p">,</span> <span class="n">drm_nova_gem_create</span><span class="p">,</span> <span class="nn">ioctl</span><span class="p">::</span><span class="n">AUTH</span> <span class="p">|</span> <span class="nn">ioctl</span><span class="p">::</span><span class="n">RENDER_ALLOW</span><span class="p">,</span> <span class="n">gem_create</span><span class="p">),</span>
    <span class="p">(</span><span class="n">NOVA_VM_BIND</span><span class="p">,</span> <span class="n">drm_nova_vm_bind</span><span class="p">,</span> <span class="nn">ioctl</span><span class="p">::</span><span class="n">AUTH</span> <span class="p">|</span> <span class="nn">ioctl</span><span class="p">::</span><span class="n">RENDER_ALLOW</span><span class="p">,</span> <span class="n">vm_bind</span><span class="p">),</span>
<span class="p">}</span>
</code></pre></div></div>

<p>These ioctls are <strong>directly exposed to userspace</strong> - the same ABI as C drivers.</p>

<h3 id="reference-example-android-binder-userspace-protocol">Reference Example: Android Binder Userspace Protocol</h3>

<p>The Android Binder Rust rewrite (out-of-tree) demonstrates how to expose extensive userspace APIs:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Example from Rust-for-Linux Binder implementation (not in mainline)</span>
<span class="k">use</span> <span class="nn">kernel</span><span class="p">::{</span>
    <span class="nn">transmute</span><span class="p">::{</span><span class="n">AsBytes</span><span class="p">,</span> <span class="n">FromBytes</span><span class="p">},</span>
    <span class="nn">uapi</span><span class="p">::{</span><span class="k">self</span><span class="p">,</span> <span class="o">*</span><span class="p">},</span>
<span class="p">};</span>

<span class="c1">// Userspace protocol constants - MUST remain stable</span>
<span class="nd">pub_no_prefix!</span><span class="p">(</span>
    <span class="n">binder_driver_return_protocol_</span><span class="p">,</span>
    <span class="n">BR_TRANSACTION</span><span class="p">,</span>
    <span class="n">BR_REPLY</span><span class="p">,</span>
    <span class="n">BR_DEAD_REPLY</span><span class="p">,</span>
    <span class="n">BR_FAILED_REPLY</span><span class="p">,</span>
    <span class="n">BR_OK</span><span class="p">,</span>
    <span class="n">BR_ERROR</span><span class="p">,</span>
    <span class="n">BR_INCREFS</span><span class="p">,</span>
    <span class="n">BR_ACQUIRE</span><span class="p">,</span>
    <span class="n">BR_RELEASE</span><span class="p">,</span>
    <span class="n">BR_DECREFS</span><span class="p">,</span>
    <span class="n">BR_DEAD_BINDER</span><span class="p">,</span>
    <span class="c1">// ... 21 total protocol constants</span>
<span class="p">);</span>

<span class="nd">pub_no_prefix!</span><span class="p">(</span>
    <span class="n">binder_driver_command_protocol_</span><span class="p">,</span>
    <span class="n">BC_TRANSACTION</span><span class="p">,</span>
    <span class="n">BC_REPLY</span><span class="p">,</span>
    <span class="n">BC_FREE_BUFFER</span><span class="p">,</span>
    <span class="n">BC_INCREFS</span><span class="p">,</span>
    <span class="n">BC_ACQUIRE</span><span class="p">,</span>
    <span class="n">BC_RELEASE</span><span class="p">,</span>
    <span class="n">BC_DECREFS</span><span class="p">,</span>
    <span class="c1">// ... 24 total command constants</span>
<span class="p">);</span>

<span class="c1">// Userspace data structures - wrapped to preserve ABI</span>
<span class="nd">decl_wrapper!</span><span class="p">(</span><span class="n">BinderTransactionData</span><span class="p">,</span> <span class="nn">uapi</span><span class="p">::</span><span class="n">binder_transaction_data</span><span class="p">);</span>
<span class="nd">decl_wrapper!</span><span class="p">(</span><span class="n">BinderWriteRead</span><span class="p">,</span> <span class="nn">uapi</span><span class="p">::</span><span class="n">binder_write_read</span><span class="p">);</span>
<span class="nd">decl_wrapper!</span><span class="p">(</span><span class="n">BinderVersion</span><span class="p">,</span> <span class="nn">uapi</span><span class="p">::</span><span class="n">binder_version</span><span class="p">);</span>
<span class="nd">decl_wrapper!</span><span class="p">(</span><span class="n">FlatBinderObject</span><span class="p">,</span> <span class="nn">uapi</span><span class="p">::</span><span class="n">flat_binder_object</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>Critical detail</strong>: These use <code class="language-plaintext highlighter-rouge">MaybeUninit</code> to <strong>preserve padding bytes</strong>, ensuring binary-identical ABI with C:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Wrapper that preserves exact memory layout, including padding</span>
<span class="nd">#[derive(Copy,</span> <span class="nd">Clone)]</span>
<span class="nd">#[repr(transparent)]</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="nf">BinderTransactionData</span><span class="p">(</span><span class="n">MaybeUninit</span><span class="o">&lt;</span><span class="nn">uapi</span><span class="p">::</span><span class="n">binder_transaction_data</span><span class="o">&gt;</span><span class="p">);</span>

<span class="c1">// SAFETY: Explicit FromBytes/AsBytes implementation</span>
<span class="k">unsafe</span> <span class="k">impl</span> <span class="n">FromBytes</span> <span class="k">for</span> <span class="n">BinderTransactionData</span> <span class="p">{}</span>
<span class="k">unsafe</span> <span class="k">impl</span> <span class="n">AsBytes</span> <span class="k">for</span> <span class="n">BinderTransactionData</span> <span class="p">{}</span>
</code></pre></div></div>

<p><strong>Why this matters</strong>: Userspace code compiled against C headers sends <strong>exact same binary data</strong> to Rust driver.</p>

<h3 id="userspace-interface-summary">Userspace Interface Summary</h3>

<table>
  <thead>
    <tr>
      <th>Interface Type</th>
      <th>Rust Support</th>
      <th>Example</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>ioctl handlers</strong></td>
      <td>✅ Full support (drivers handle commands)</td>
      <td>DRM drivers, Binder</td>
    </tr>
    <tr>
      <td><strong>/dev device nodes</strong></td>
      <td>✅ Via miscdevice/cdev</td>
      <td>Character devices</td>
    </tr>
    <tr>
      <td><strong>/sys (sysfs)</strong></td>
      <td>✅ Via kobject bindings</td>
      <td>Device attributes</td>
    </tr>
    <tr>
      <td><strong>/proc</strong></td>
      <td>✅ Via seq_file</td>
      <td>Process info</td>
    </tr>
    <tr>
      <td><strong>Defining new syscalls</strong></td>
      <td>❌ Not possible (syscall entry is C)</td>
      <td>-</td>
    </tr>
    <tr>
      <td><strong>Netlink</strong></td>
      <td>✅ Via net subsystem</td>
      <td>Network configuration</td>
    </tr>
  </tbody>
</table>

<p><strong>Important distinction</strong>: Rust drivers can <strong>handle</strong> ioctl commands (the driver-specific logic), but the ioctl <strong>system call entry point</strong> itself (in <code class="language-plaintext highlighter-rouge">fs/ioctl.c</code>) remains C code. The same applies to other interfaces - Rust provides the handler, not the core mechanism.</p>

<p><strong>Answer</strong>: Yes, Rust <strong>fully supports</strong> userspace interfaces through standard kernel mechanisms, though the core system call layer remains in C.</p>

<h2 id="critical-clarification-userspace-programs-cannot-use-rustkernel">Critical Clarification: Userspace Programs Cannot Use <code class="language-plaintext highlighter-rouge">rust/kernel</code></h2>

<p><strong>A common misconception</strong>: “Can my userspace Rust program use the <code class="language-plaintext highlighter-rouge">rust/kernel</code> abstractions?”</p>

<p><strong>Answer: Absolutely not.</strong> This is a fundamental architectural constraint, not a technical limitation.</p>

<h3 id="kernel-space-vs-userspace---complete-isolation">Kernel Space vs. Userspace - Complete Isolation</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────────────────────────────────┐
│              USERSPACE                                   │
│  - Uses Rust standard library (std)                     │
│  - Normal Rust programs                                 │
│  - Can use tokio, serde, etc.                          │
│                                                          │
│  Userspace Rust program:                                │
│  ┌────────────────────────────────────────┐            │
│  │ use std::fs::File;                      │            │
│  │ use std::os::unix::io::AsRawFd;        │            │
│  │                                         │            │
│  │ fn main() {                             │            │
│  │     let fd = File::open("/dev/my_dev") │            │
│  │         .unwrap();                      │            │
│  │     // Interact with kernel via syscalls│           │
│  │     unsafe {                             │            │
│  │         libc::ioctl(fd.as_raw_fd(), ...) │           │
│  │     }                                    │            │
│  │ }                                        │            │
│  └────────────────────────────────────────┘            │
└──────────────────┬──────────────────────────────────────┘
                   │
                   │  System Call Boundary
                   │  - open(), ioctl(), read(), write()
                   │  - /dev, /sys, /proc interfaces
                   │  - ❌ Cannot directly call kernel functions
                   │
┌──────────────────┴──────────────────────────────────────┐
│              KERNEL SPACE                                │
│  - Uses #![no_std] (no standard library)                │
│  - Runs only in kernel modules                          │
│  - Uses rust/kernel abstractions                        │
│                                                          │
│  Kernel Rust driver:                                    │
│  ┌────────────────────────────────────────┐            │
│  │ #![no_std]                             │            │
│  │ use kernel::prelude::*;                │            │
│  │                                         │            │
│  │ impl kernel::file::Operations for MyDev│            │
│  │     fn ioctl(...) -&gt; Result {          │            │
│  │         // Handle userspace ioctl      │            │
│  │         kernel::sync::SpinLock::...     │            │
│  │     }                                   │            │
│  │ }                                       │            │
│  └────────────────────────────────────────┘            │
└─────────────────────────────────────────────────────────┘
</code></pre></div></div>

<h3 id="why-userspace-cannot-use-rustkernel">Why Userspace Cannot Use <code class="language-plaintext highlighter-rouge">rust/kernel</code></h3>

<p><strong>1. <code class="language-plaintext highlighter-rouge">#![no_std]</code> - No Standard Library</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// rust/kernel/lib.rs (library crate root)</span>
<span class="nd">#![no_std]</span>  <span class="c1">// ← Critical: No standard library!</span>

<span class="c1">// Kernel space does NOT have:</span>
<span class="c1">// - Heap allocation (must use GFP_KERNEL)</span>
<span class="c1">// - Threads (uses kernel tasks)</span>
<span class="c1">// - File system (userspace concept)</span>
<span class="c1">// - Network libraries (userspace concept)</span>
<span class="c1">// - println!() (uses pr_info!())</span>

<span class="c1">// Only has:</span>
<span class="c1">// - core library (no OS required)</span>
<span class="c1">// - Kernel-specific APIs</span>
</code></pre></div></div>

<p><strong>Note</strong>: The <code class="language-plaintext highlighter-rouge">#![no_std]</code> attribute is only declared in library crate roots like <code class="language-plaintext highlighter-rouge">rust/kernel/lib.rs</code>, <code class="language-plaintext highlighter-rouge">rust/bindings/lib.rs</code>, etc. Individual driver modules (e.g., <code class="language-plaintext highlighter-rouge">drivers/gpu/drm/nova/driver.rs</code>) do NOT need this declaration - they inherit the no_std environment by using the kernel library via <code class="language-plaintext highlighter-rouge">use kernel::prelude::*</code>.</p>

<p><strong>2. Different Compilation Targets</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Userspace Rust program</span>
<span class="nv">$ </span>rustc <span class="nt">--target</span> x86_64-unknown-linux-gnu userspace.rs
<span class="c"># Compiles to userspace executable</span>

<span class="c"># Kernel Rust module</span>
<span class="nv">$ </span>rustc <span class="nt">--target</span> x86_64-linux-kernel module.rs
<span class="c"># Compiles to kernel module (.ko file)</span>
<span class="c"># Linked into kernel, cannot run in userspace</span>
</code></pre></div></div>

<p><strong>3. Memory Space Isolation</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Virtual Address Space:
┌─────────────────────┐ 0xFFFFFFFFFFFFFFFF
│   Kernel Space       │ ← rust/kernel runs here
│   (kernel code only) │   Only accessible via syscalls
├─────────────────────┤ 0x00007FFFFFFFFFFF
│   Userspace          │ ← User Rust programs run here
│   (applications)     │   Cannot access kernel memory
└─────────────────────┘ 0x0000000000000000
</code></pre></div></div>

<h3 id="how-userspace-programs-interact-with-rust-kernel-drivers">How Userspace Programs Interact with Rust Kernel Drivers</h3>

<p><strong>Method 1: Via <code class="language-plaintext highlighter-rouge">/dev</code> Device Nodes</strong></p>

<p><strong>Kernel side (Rust driver):</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// drivers/example/my_device.rs</span>
<span class="k">use</span> <span class="nn">kernel</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">kernel</span><span class="p">::</span><span class="nn">file</span><span class="p">::</span><span class="n">Operations</span><span class="p">;</span>

<span class="k">struct</span> <span class="n">MyDevice</span><span class="p">;</span>

<span class="k">impl</span> <span class="n">Operations</span> <span class="k">for</span> <span class="n">MyDevice</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">open</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="nd">pr_info!</span><span class="p">(</span><span class="s">"Device opened from userspace</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="nf">Ok</span><span class="p">(</span><span class="n">MyDevice</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">ioctl</span><span class="p">(</span><span class="n">cmd</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span> <span class="n">arg</span><span class="p">:</span> <span class="nb">usize</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">isize</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">match</span> <span class="n">cmd</span> <span class="p">{</span>
            <span class="n">MY_IOCTL_CMD</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="c1">// Handle userspace ioctl request</span>
                <span class="nf">Ok</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
            <span class="p">}</span>
            <span class="n">_</span> <span class="k">=&gt;</span> <span class="nf">Err</span><span class="p">(</span><span class="n">EINVAL</span><span class="p">),</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Userspace (standard Rust program):</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// userspace_app/src/main.rs</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">fs</span><span class="p">::</span><span class="n">File</span><span class="p">;</span>  <span class="c1">// ← Uses standard library!</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">os</span><span class="p">::</span><span class="nn">unix</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="n">AsRawFd</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// Open device created by Rust kernel driver</span>
    <span class="k">let</span> <span class="n">file</span> <span class="o">=</span> <span class="nn">File</span><span class="p">::</span><span class="nf">open</span><span class="p">(</span><span class="s">"/dev/my_device"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="c1">// Interact via system calls</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">ret</span> <span class="o">=</span> <span class="nn">libc</span><span class="p">::</span><span class="nf">ioctl</span><span class="p">(</span>
            <span class="n">file</span><span class="nf">.as_raw_fd</span><span class="p">(),</span>
            <span class="n">MY_IOCTL_CMD</span><span class="p">,</span>
            <span class="o">&amp;</span><span class="n">my_data</span>
        <span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// Userspace has no idea if kernel is C or Rust!</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Method 2: Via <code class="language-plaintext highlighter-rouge">sysfs</code></strong></p>

<p><strong>Kernel side:</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Create sysfs attribute in kernel</span>
<span class="k">use</span> <span class="nn">kernel</span><span class="p">::</span><span class="nn">device</span><span class="p">::</span><span class="n">Device</span><span class="p">;</span>

<span class="k">impl</span> <span class="n">Device</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">create_sysfs_attrs</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span> <span class="p">{</span>
        <span class="c1">// Creates /sys/class/my_device/value</span>
        <span class="nf">sysfs_create_file</span><span class="p">(</span><span class="o">...</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="nf">Ok</span><span class="p">(())</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Userspace:</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="n">fs</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// Read sysfs file (provided by Rust kernel driver)</span>
    <span class="k">let</span> <span class="n">value</span> <span class="o">=</span> <span class="nn">fs</span><span class="p">::</span><span class="nf">read_to_string</span><span class="p">(</span>
        <span class="s">"/sys/class/my_device/value"</span>
    <span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="nd">println!</span><span class="p">(</span><span class="s">"Value from kernel: {}"</span><span class="p">,</span> <span class="n">value</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Method 3: Via <code class="language-plaintext highlighter-rouge">netlink</code> (Network Drivers)</strong></p>

<p><strong>Kernel side:</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">kernel</span><span class="p">::</span><span class="n">net</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">send_netlink_msg</span><span class="p">(</span><span class="n">msg</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">NetlinkMsg</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span> <span class="p">{</span>
    <span class="nf">netlink_broadcast</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Userspace:</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">netlink_sys</span><span class="p">::{</span><span class="n">Socket</span><span class="p">,</span> <span class="n">SocketAddr</span><span class="p">};</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">socket</span> <span class="o">=</span> <span class="nn">Socket</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="c1">// Receive netlink messages from Rust kernel driver</span>
    <span class="k">let</span> <span class="n">msg</span> <span class="o">=</span> <span class="n">socket</span><span class="nf">.recv_from</span><span class="p">(</span><span class="o">...</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="comparison-table">Comparison Table</h3>

<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>Kernel Space (<code class="language-plaintext highlighter-rouge">rust/kernel</code>)</th>
      <th>Userspace (std Rust)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Standard library</strong></td>
      <td>❌ <code class="language-plaintext highlighter-rouge">#![no_std]</code></td>
      <td>✅ <code class="language-plaintext highlighter-rouge">use std::*</code></td>
    </tr>
    <tr>
      <td><strong>Runtime environment</strong></td>
      <td>Kernel module (.ko)</td>
      <td>Executable (ELF)</td>
    </tr>
    <tr>
      <td><strong>Memory allocation</strong></td>
      <td><code class="language-plaintext highlighter-rouge">kernel::kvec::KVec</code></td>
      <td><code class="language-plaintext highlighter-rouge">std::vec::Vec</code></td>
    </tr>
    <tr>
      <td><strong>Printing</strong></td>
      <td><code class="language-plaintext highlighter-rouge">pr_info!()</code></td>
      <td><code class="language-plaintext highlighter-rouge">println!()</code></td>
    </tr>
    <tr>
      <td><strong>File operations</strong></td>
      <td>❌ Cannot open files</td>
      <td>✅ <code class="language-plaintext highlighter-rouge">std::fs::File</code></td>
    </tr>
    <tr>
      <td><strong>Networking</strong></td>
      <td>Provides network services</td>
      <td>Uses network services</td>
    </tr>
    <tr>
      <td><strong>Hardware access</strong></td>
      <td>✅ Direct access</td>
      <td>❌ Via system calls</td>
    </tr>
    <tr>
      <td><strong>Privilege level</strong></td>
      <td>Ring 0</td>
      <td>Ring 3</td>
    </tr>
    <tr>
      <td><strong>Available crates</strong></td>
      <td>Very few (no_std only)</td>
      <td>All standard crates</td>
    </tr>
  </tbody>
</table>

<h3 id="complete-example-userspace-reading-gpu-info">Complete Example: Userspace Reading GPU Info</h3>

<p><strong>1. Kernel Rust GPU driver:</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// drivers/gpu/drm/nova/driver.rs</span>
<span class="k">use</span> <span class="nn">kernel</span><span class="p">::</span><span class="n">drm</span><span class="p">;</span>

<span class="k">impl</span> <span class="nn">drm</span><span class="p">::</span><span class="n">Driver</span> <span class="k">for</span> <span class="n">NovaDriver</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">ioctl</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">cmd</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="p">[</span><span class="nb">u8</span><span class="p">])</span> <span class="k">-&gt;</span> <span class="nb">Result</span> <span class="p">{</span>
        <span class="k">match</span> <span class="n">cmd</span> <span class="p">{</span>
            <span class="n">DRM_NOVA_GET_PARAM</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="c1">// Read GPU parameter</span>
                <span class="k">let</span> <span class="n">param</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.get_gpu_param</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
                <span class="c1">// Copy to userspace</span>
                <span class="n">data</span><span class="nf">.copy_from_slice</span><span class="p">(</span><span class="o">&amp;</span><span class="n">param</span><span class="nf">.to_bytes</span><span class="p">());</span>
                <span class="nf">Ok</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
            <span class="p">}</span>
            <span class="n">_</span> <span class="k">=&gt;</span> <span class="nf">Err</span><span class="p">(</span><span class="n">EINVAL</span><span class="p">),</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>2. Userspace Rust application:</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// userspace_app/src/main.rs</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">fs</span><span class="p">::</span><span class="n">OpenOptions</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">os</span><span class="p">::</span><span class="nn">unix</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="n">AsRawFd</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// Open DRM device</span>
    <span class="k">let</span> <span class="n">drm_device</span> <span class="o">=</span> <span class="nn">OpenOptions</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.read</span><span class="p">(</span><span class="k">true</span><span class="p">)</span>
        <span class="nf">.write</span><span class="p">(</span><span class="k">true</span><span class="p">)</span>
        <span class="nf">.open</span><span class="p">(</span><span class="s">"/dev/dri/renderD128"</span><span class="p">)</span>
        <span class="nf">.unwrap</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">drm_device</span><span class="nf">.as_raw_fd</span><span class="p">();</span>

    <span class="c1">// Prepare ioctl argument</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">param_data</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0u8</span><span class="p">;</span> <span class="mi">64</span><span class="p">];</span>

    <span class="c1">// Call ioctl (enters kernel)</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nn">libc</span><span class="p">::</span><span class="nf">ioctl</span><span class="p">(</span>
            <span class="n">fd</span><span class="p">,</span>
            <span class="n">DRM_NOVA_GET_PARAM</span><span class="p">,</span>
            <span class="o">&amp;</span><span class="k">mut</span> <span class="n">param_data</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="n">_</span>
        <span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// param_data now contains GPU parameters from kernel</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"GPU param: {:?}"</span><span class="p">,</span> <span class="n">param_data</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="key-takeaways">Key Takeaways</h3>

<ol>
  <li>❌ <strong>Userspace programs CANNOT use <code class="language-plaintext highlighter-rouge">rust/kernel</code></strong> - they run in completely different environments</li>
  <li>✅ <strong>Userspace interacts with kernel via system calls</strong> - just like with C drivers</li>
  <li>🔄 <strong>Interaction is bidirectional but indirect</strong>:
    <ul>
      <li>Userspace → syscall/ioctl/filesystem → Rust kernel driver</li>
      <li>Rust kernel driver → response/data → syscall return → Userspace</li>
    </ul>
  </li>
</ol>

<p><strong>Userspace has no idea if the kernel driver is C or Rust - this is exactly what ABI stability means!</strong> 🎯</p>

<h2 id="question-2-kernel-internal-abi-stability-policy">Question 2: Kernel Internal ABI Stability Policy</h2>

<h3 id="the-critical-distinction">The Critical Distinction</h3>

<p>Linux kernel has <strong>two completely different ABI policies</strong>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────────────────────────────┐
│                  USERSPACE                          │
│  (applications, libraries, tools)                   │
└─────────────────┬───────────────────────────────────┘
                  │
                  │  ← USERSPACE ABI (STABLE, SACRED)
                  │     System calls, ioctl, /proc, /sys
                  │     "WE DO NOT BREAK USERSPACE" - Linus
                  │
┌─────────────────┴───────────────────────────────────┐
│            LINUX KERNEL                             │
│  ┌─────────────────────────────────────────┐       │
│  │  Kernel Subsystems (VFS, MM, Net, etc)  │       │
│  └─────────────────┬───────────────────────┘       │
│                    │                                │
│                    │  ← INTERNAL API (UNSTABLE!)    │
│                    │     Can change anytime         │
│                    │     No backward compat         │
│  ┌─────────────────┴───────────────────────┐       │
│  │  Loadable Kernel Modules (.ko files)    │       │
│  │  (drivers, filesystems, etc)             │       │
│  └─────────────────────────────────────────┘       │
└─────────────────────────────────────────────────────┘
</code></pre></div></div>

<h3 id="official-kernel-policy-internal-abi-is-unstable">Official Kernel Policy: Internal ABI is Unstable</h3>

<p>From the Linux kernel documentation<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>:</p>

<blockquote>
  <p><strong>The kernel does NOT have a stable internal API/ABI.</strong></p>

  <p>The kernel internal API can and does change at any time, for any reason.</p>
</blockquote>

<p><strong>In practice</strong>: If you compile a kernel module for Linux 6.5, it <strong>will not load</strong> on Linux 6.6 without recompilation.</p>

<h3 id="why-internal-abi-is-unstable">Why Internal ABI is Unstable</h3>

<p>Greg Kroah-Hartman explained this in his famous document:</p>

<p><strong>Reasons for no internal ABI stability:</strong></p>

<ol>
  <li><strong>Rapid evolution</strong>: Subsystems need freedom to refactor</li>
  <li><strong>No binary modules</strong>: All modules must be GPL and recompilable</li>
  <li><strong>Quality control</strong>: Forces out-of-tree drivers to stay updated</li>
  <li><strong>Security</strong>: Allows fixing fundamental design flaws</li>
</ol>

<p><strong>The philosophy</strong>: “If your code is good enough, it should be in-tree. If it’s in-tree, recompilation is free.”</p>

<h3 id="userspace-abi-absolute-stability">Userspace ABI: Absolute Stability</h3>

<p>Linus Torvalds’ famous rule (paraphrased from countless LKML posts):</p>

<blockquote>
  <p><strong>“WE DO NOT BREAK USERSPACE. EVER.”</strong></p>

  <p>If a kernel change breaks a working userspace application, that change <strong>will be reverted</strong>, no matter how “correct” it was.</p>
</blockquote>

<p>From the official documentation<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>:</p>

<blockquote>
  <p><strong>Stable interfaces:</strong></p>
  <ul>
    <li>System calls: Must never change semantics</li>
    <li>/proc and /sys ABI: Guaranteed stable for at least 2 years</li>
    <li>ioctl numbers: Never reused once defined</li>
    <li>Binary formats (ELF, etc): Backward compatible</li>
  </ul>
</blockquote>

<h3 id="real-example-abi-stability-levels">Real Example: ABI Stability Levels</h3>

<p>From <code class="language-plaintext highlighter-rouge">/Documentation/ABI/README</code><sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>stable/     - Interfaces with guaranteed backward compatibility
              Examples: syscalls, core /proc entries

testing/    - Interfaces believed stable but not yet guaranteed
              May still change with warning

obsolete/   - Deprecated but still present interfaces
              Marked for removal but with migration period

removed/    - Historical record only
</code></pre></div></div>

<p><strong>Answer</strong>: The kernel <strong>does not pursue internal ABI stability</strong>. Only <strong>userspace ABI</strong> is stable.</p>

<h2 id="question-3-rust-and-userspace-abi-stability">Question 3: Rust and Userspace ABI Stability</h2>

<h3 id="current-state-rust-provides-stable-userspace-abi">Current State: Rust Provides Stable Userspace ABI</h3>

<p><strong>Production drivers in mainline</strong> (as of Linux 6.x):</p>

<ol>
  <li><strong>GPU drivers (Nova)</strong>: DRM userspace ABI for Nvidia GPUs - full ioctl interface</li>
  <li><strong>Network PHY drivers</strong> (ax88796b, qt2025): ethtool/netlink ABI</li>
  <li><strong>Block devices</strong> (rnull): Standard block device ioctl ABI</li>
  <li><strong>CPU frequency</strong> (rcpufreq_dt): sysfs and ioctl interfaces</li>
</ol>

<p><strong>Reference implementations (out-of-tree)</strong>:</p>

<p><strong>Android Binder</strong> (Rust rewrite, not yet in mainline): Demonstrates <strong>identical userspace ABI</strong> as C version:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Same BINDER_WRITE_READ ioctl as C version</span>
<span class="k">const</span> <span class="n">BINDER_WRITE_READ</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="nn">kernel</span><span class="p">::</span><span class="nn">ioctl</span><span class="p">::</span><span class="nn">_IOWR</span><span class="p">::</span><span class="o">&lt;</span><span class="n">BinderWriteRead</span><span class="o">&gt;</span><span class="p">(</span>
    <span class="n">BINDER_TYPE</span> <span class="k">as</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="mi">1</span>
<span class="p">);</span>

<span class="c1">// Userspace code using C headers sends exact same binary data</span>
</code></pre></div></div>

<p>This out-of-tree implementation has been <strong>validated</strong> - Android’s libbinder (C++ userspace library) works without modification with the Rust driver.</p>

<h3 id="why-rust-is-actually-better-for-abi-stability">Why Rust is Actually Better for ABI Stability</h3>

<p><strong>Problem in C</strong>: Accidental ABI breakage</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C - easy to accidentally change ABI</span>
<span class="k">struct</span> <span class="n">binder_transaction_data</span> <span class="p">{</span>
    <span class="kt">uint64_t</span> <span class="n">cookie</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">code</span><span class="p">;</span>
    <span class="c1">// Oops, developer adds field here - ABI BROKEN!</span>
    <span class="kt">uint32_t</span> <span class="n">new_field</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">flags</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<p><strong>Rust solution</strong>: Explicit versioning and <code class="language-plaintext highlighter-rouge">#[repr(C)]</code></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Rust - ABI layout is explicit and checked</span>
<span class="nd">#[repr(C)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">binder_transaction_data</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">cookie</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">code</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="c1">// Cannot add field here without explicit version bump</span>
    <span class="k">pub</span> <span class="n">flags</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
<span class="p">}</span>

<span class="c1">// Compile-time size check</span>
<span class="k">const</span> <span class="n">_</span><span class="p">:</span> <span class="p">()</span> <span class="o">=</span> <span class="nd">assert!</span><span class="p">(</span>
    <span class="nn">core</span><span class="p">::</span><span class="nn">mem</span><span class="p">::</span><span class="nn">size_of</span><span class="p">::</span><span class="o">&lt;</span><span class="n">binder_transaction_data</span><span class="o">&gt;</span><span class="p">()</span> <span class="o">==</span> <span class="mi">48</span>
<span class="p">);</span>
</code></pre></div></div>

<h3 id="real-example-drm-driver-backward-compatibility">Real Example: DRM Driver Backward Compatibility</h3>

<p>From the Nova GPU driver (Rust):</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Must maintain compatibility with userspace mesa drivers</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">DRM_NOVA_GEM_CREATE</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="nn">drm</span><span class="p">::</span><span class="nn">ioctl</span><span class="p">::</span><span class="nn">IOWR</span><span class="p">::</span><span class="o">&lt;</span><span class="n">drm_nova_gem_create</span><span class="o">&gt;</span><span class="p">(</span><span class="mi">0x00</span><span class="p">);</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">DRM_NOVA_GEM_INFO</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="nn">drm</span><span class="p">::</span><span class="nn">ioctl</span><span class="p">::</span><span class="nn">IOWR</span><span class="p">::</span><span class="o">&lt;</span><span class="n">drm_nova_gem_info</span><span class="o">&gt;</span><span class="p">(</span><span class="mi">0x01</span><span class="p">);</span>

<span class="c1">// Once these ioctl numbers are released, they NEVER change</span>
<span class="c1">// Rust's type system helps prevent accidental changes:</span>

<span class="nd">#[repr(C)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">drm_nova_gem_create</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">size</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">handle</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">flags</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
<span class="p">}</span>

<span class="c1">// If someone tries to change this, compilation breaks due to size assertions</span>
</code></pre></div></div>

<h3 id="abi-stability-rust-vs-c-comparison">ABI Stability: Rust vs C Comparison</h3>

<table>
  <thead>
    <tr>
      <th>Aspect</th>
      <th>C</th>
      <th>Rust</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Layout control</strong></td>
      <td>Implicit, compiler-dependent</td>
      <td><code class="language-plaintext highlighter-rouge">#[repr(C)]</code> explicit</td>
    </tr>
    <tr>
      <td><strong>Padding preservation</strong></td>
      <td>Manual, error-prone</td>
      <td><code class="language-plaintext highlighter-rouge">MaybeUninit</code> automatic</td>
    </tr>
    <tr>
      <td><strong>Size verification</strong></td>
      <td>Manual <code class="language-plaintext highlighter-rouge">BUILD_BUG_ON</code></td>
      <td><code class="language-plaintext highlighter-rouge">const _: assert!(size == X)</code></td>
    </tr>
    <tr>
      <td><strong>Breaking changes</strong></td>
      <td>Silent, runtime failure</td>
      <td>Compile error</td>
    </tr>
    <tr>
      <td><strong>Versioning</strong></td>
      <td>Manual, by convention</td>
      <td>Can be enforced by type system</td>
    </tr>
    <tr>
      <td><strong>Binary compatibility</strong></td>
      <td>Trust the developer</td>
      <td>Compiler-verified</td>
    </tr>
  </tbody>
</table>

<h3 id="will-rust-provide-critical-userspace-abi">Will Rust Provide Critical Userspace ABI?</h3>

<p><strong>Production deployments (mainline kernel):</strong></p>

<ol>
  <li><strong>GPU drivers</strong> (Nova): DRM userspace ABI for Nvidia GPUs (13 files in-tree)</li>
  <li><strong>Network PHY drivers</strong>: ethtool/netlink ABI (ax88796b, qt2025)</li>
  <li><strong>Block devices</strong>: rnull driver with standard ioctl ABI</li>
  <li><strong>CPU frequency</strong>: rcpufreq_dt with sysfs interfaces</li>
</ol>

<p><strong>Reference implementations (out-of-tree):</strong></p>

<ol>
  <li><strong>Android Binder</strong> (IPC): Rust rewrite demonstrates ABI compatibility (not yet mainline)</li>
</ol>

<p><strong>Coming soon</strong> (based on current development):</p>

<ol>
  <li><strong>File systems</strong>: VFS operations, mount options</li>
  <li><strong>Network protocols</strong>: Socket options, packet formats</li>
  <li><strong>More device drivers</strong>: Expanding hardware support</li>
</ol>

<h3 id="the-key-policy-language-agnostic-abi">The Key Policy: Language-Agnostic ABI</h3>

<p><strong>Critical insight</strong>: The kernel’s ABI stability policy is <strong>language-agnostic</strong>.</p>

<p>From Linus Torvalds (summarized from various LKML posts):</p>

<blockquote>
  <p>“I don’t care if you write it in C, Rust, or assembly. If you break userspace, you broke the kernel.”</p>
</blockquote>

<p><strong>In practice</strong>:</p>
<ul>
  <li>Rust drivers use <strong>same UAPI headers</strong> as C via bindgen</li>
  <li>Same ioctl numbers, same struct layouts, same semantics</li>
  <li>Userspace <strong>cannot tell</strong> if driver is C or Rust</li>
  <li>ABI breaks are <strong>equally unacceptable</strong> in both languages</li>
</ul>

<p><strong>Answer</strong>: Yes, Rust <strong>will be and already is</strong> used for userspace-facing features requiring ABI stability.</p>

<h2 id="current-scope-peripheral-drivers-not-core-kernel">Current Scope: Peripheral Drivers, Not Core Kernel</h2>

<p><strong>Critical clarification</strong>: As of early 2026, Rust in the Linux kernel is <strong>exclusively in peripheral areas</strong> - device drivers and Android-specific components. <strong>No core kernel subsystems have been rewritten in Rust.</strong></p>

<h3 id="-where-rust-code-exists">✅ Where Rust Code Exists</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>drivers/                    # Peripheral driver layer
├── gpu/drm/nova/          # GPU driver (Nvidia, 13 files, ~1,200 lines)
├── net/phy/               # Network PHY drivers (2 files, ~237 lines)
├── block/rnull.rs         # Block device example (80 lines)
├── cpufreq/rcpufreq_dt.rs # CPU frequency management (227 lines)
└── gpu/drm/drm_panic_qr.rs # DRM panic QR code (996 lines)

rust/kernel/               # Abstraction layer (101 files, 13,500 lines)
├── sync/                  # Rust bindings for sync primitives
├── mm/                    # Rust bindings for memory functions
├── fs/                    # Rust bindings for filesystem
└── net/                   # Rust bindings for networking
</code></pre></div></div>

<p><strong>Key point</strong>: The <code class="language-plaintext highlighter-rouge">rust/kernel/</code> directory provides <strong>abstractions</strong> (safe wrappers around C APIs), not <strong>implementations</strong> of core functionality.</p>

<h3 id="-what-remains-100-c-core-kernel">❌ What Remains 100% C (Core Kernel)</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mm/                        # Memory management core
├── 153 files, 128 C files
├── page_alloc.c          # Page allocator (9,000+ lines)
├── slab.c                # Slab allocator (4,000+ lines)
├── vmalloc.c             # Virtual memory (3,500+ lines)
└── kasan_test_rust.rs    # ⚠️ Only Rust file (just a test!)

kernel/sched/             # Process scheduler
├── 46 files, 33 C files
├── core.c                # Scheduler core (11,000+ lines)
└── 0 Rust files

fs/                       # VFS core
├── Hundreds of C files
├── namei.c               # Path lookup (5,000+ lines)
├── inode.c               # Inode management (2,000+ lines)
└── 0 Rust files (drivers only)

net/core/                 # Network protocol stack core
kernel/entry/             # System call entry points
arch/x86/kernel/          # Architecture-specific code
</code></pre></div></div>

<h3 id="why-this-matters">Why This Matters</h3>

<p>This distribution is <strong>not a technical limitation</strong> but a <strong>deliberate strategy</strong>:</p>

<ol>
  <li><strong>Risk management</strong>: Driver failures are contained; core subsystem bugs crash the system</li>
  <li><strong>Trust building</strong>: Prove Rust’s value in low-risk areas first</li>
  <li><strong>Community acceptance</strong>: Gradual adoption allows kernel maintainers to adapt</li>
  <li><strong>Tooling maturity</strong>: Build testing infrastructure and debugging tools</li>
</ol>

<h3 id="adoption-timeline-current-trajectory">Adoption Timeline (Current Trajectory)</h3>

<p><strong>Phase 1 (2022-2026)</strong>: ✅ <strong>Completed</strong></p>
<ul>
  <li>Device drivers and Android components</li>
  <li>Abstraction layer infrastructure</li>
  <li>Build system integration</li>
</ul>

<p><strong>Phase 2 (2026-2028)</strong>: 🔄 <strong>In progress</strong></p>
<ul>
  <li>More device drivers (expanding hardware support)</li>
  <li>Filesystem drivers (experimental)</li>
  <li>Network driver expansion</li>
</ul>

<p><strong>Phase 3 (2028-2030+)</strong>: 🔮 <strong>Highly speculative</strong></p>
<ul>
  <li>Core subsystem adoption (mm, scheduler, VFS)</li>
  <li><strong>This may never happen</strong> - requires massive community consensus</li>
  <li>No official roadmap exists for core rewrites</li>
</ul>

<h3 id="the-reality-check">The Reality Check</h3>

<p><strong>Question</strong>: “Will Rust replace C in the kernel core?”</p>

<p><strong>Answer</strong>: Unknown and unlikely in the near term (5-10 years). Current evidence shows:</p>
<ul>
  <li>Rust is succeeding in <strong>drivers</strong> (proven value)</li>
  <li>Core subsystems have <strong>decades of battle-tested C code</strong></li>
  <li>Rewriting core = <strong>enormous risk</strong> with unclear benefit</li>
  <li>Community focus is on <strong>new drivers</strong>, not rewriting existing core</li>
</ul>

<p><strong>Conclusion</strong>: Rust in Linux is currently a <strong>driver development language</strong>, not a <strong>kernel core language</strong>. This may change, but not soon.</p>

<h2 id="practical-implications">Practical Implications</h2>

<h3 id="for-rust-kernel-developers">For Rust Kernel Developers</h3>

<p><strong>Do:</strong></p>
<ul>
  <li>✅ Use <code class="language-plaintext highlighter-rouge">#[repr(C)]</code> for all userspace-facing structs</li>
  <li>✅ Use <code class="language-plaintext highlighter-rouge">uapi</code> crate for userspace types</li>
  <li>✅ Add size/layout assertions</li>
  <li>✅ Preserve padding with <code class="language-plaintext highlighter-rouge">MaybeUninit</code> if needed</li>
  <li>✅ Document ABI in same way as C drivers</li>
</ul>

<p><strong>Don’t:</strong></p>
<ul>
  <li>❌ Change userspace-visible types without version bump</li>
  <li>❌ Assume Rust’s layout is sufficient (use <code class="language-plaintext highlighter-rouge">#[repr(C)]</code>)</li>
  <li>❌ Break compatibility even for “better” design</li>
  <li>❌ Rely on Rust-specific types in UAPI</li>
</ul>

<h3 id="for-userspace-developers">For Userspace Developers</h3>

<p><strong>Good news</strong>: Nothing changes!</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Userspace C code (unchanged)</span>
<span class="kt">int</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">open</span><span class="p">(</span><span class="s">"/dev/binder"</span><span class="p">,</span> <span class="n">O_RDWR</span><span class="p">);</span>
<span class="k">struct</span> <span class="n">binder_write_read</span> <span class="n">bwr</span> <span class="o">=</span> <span class="p">{</span> <span class="p">...</span> <span class="p">};</span>
<span class="n">ioctl</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">BINDER_WRITE_READ</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">bwr</span><span class="p">);</span>
</code></pre></div></div>

<p>Whether the kernel driver is C or Rust, <strong>this code works identically</strong>.</p>

<h3 id="for-distribution-maintainers">For Distribution Maintainers</h3>

<p><strong>Internal modules</strong> (out-of-tree):</p>
<ul>
  <li>❌ Must recompile for each kernel version (always true)</li>
  <li>❌ May break if internal APIs change (always true)</li>
  <li>✅ In-tree Rust drivers handle this automatically</li>
</ul>

<p><strong>Userspace applications</strong>:</p>
<ul>
  <li>✅ No changes needed</li>
  <li>✅ ABI stability same as C drivers</li>
  <li>✅ Old binaries work on new kernels (as always)</li>
</ul>

<h2 id="common-misconceptions">Common Misconceptions</h2>

<h3 id="myth-1-rusts-abi-is-unstable-so-it-cant-be-used-for-kernel-interfaces">Myth 1: “Rust’s ABI is unstable, so it can’t be used for kernel interfaces”</h3>

<p><strong>Reality</strong>:</p>
<ul>
  <li>Rust’s <em>internal</em> ABI between Rust crates is unstable</li>
  <li>Rust’s <code class="language-plaintext highlighter-rouge">#[repr(C)]</code> ABI <strong>is stable</strong> and matches C exactly</li>
  <li>Kernel uses <code class="language-plaintext highlighter-rouge">#[repr(C)]</code> for all userspace interfaces</li>
</ul>

<h3 id="myth-2-rust-adds-a-new-abi-to-maintain">Myth 2: “Rust adds a new ABI to maintain”</h3>

<p><strong>Reality</strong>:</p>
<ul>
  <li>Rust uses <strong>same UAPI headers</strong> as C (via bindgen)</li>
  <li>No new ABI, just a different language implementing the same ABI</li>
  <li>Userspace sees no difference</li>
</ul>

<h3 id="myth-3-rust-internal-instability-affects-userspace">Myth 3: “Rust internal instability affects userspace”</h3>

<p><strong>Reality</strong>:</p>
<ul>
  <li>Rust’s <code class="language-plaintext highlighter-rouge">rust/kernel</code> abstractions can change freely (internal API)</li>
  <li>Userspace-facing ABI <strong>must not change</strong> (same rule as C)</li>
  <li>These are separate concerns</li>
</ul>

<h3 id="myth-4-modules-must-be-recompiled-because-of-rust">Myth 4: “Modules must be recompiled because of Rust”</h3>

<p><strong>Reality</strong>:</p>
<ul>
  <li>Kernel modules <strong>always</strong> needed recompilation between versions</li>
  <li>This is true for <strong>C modules</strong> too</li>
  <li>Rust doesn’t change this policy</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p><strong>Summary of findings:</strong></p>

<ol>
  <li>
    <p>✅ <strong>Rust provides userspace interfaces</strong> through <code class="language-plaintext highlighter-rouge">uapi</code> crate, ioctl handlers, device nodes, sysfs, etc.</p>
  </li>
  <li>
    <p>❌ <strong>Kernel internal ABI is NOT stable</strong> - modules must recompile for each kernel version (same as C)</p>
  </li>
  <li>
    <p>✅ <strong>Userspace ABI IS stable</strong> - never breaks (same rule for C and Rust)</p>
  </li>
  <li>
    <p>✅ <strong>Rust already provides userspace ABI in production</strong> - GPU drivers (Nova), network PHY drivers, block devices, CPU frequency drivers (all in mainline)</p>
  </li>
  <li>
    <p>⚠️ <strong>Rust is currently peripheral-only</strong> - Device drivers only; core kernel (mm, scheduler, VFS) remains 100% C</p>
  </li>
</ol>

<p><strong>Key insights</strong>:</p>

<ol>
  <li>The kernel’s ABI stability policy is <strong>orthogonal to the implementation language</strong>. Rust drivers must follow the same rules as C drivers:
    <ul>
      <li>Internal APIs can change anytime</li>
      <li>Userspace ABI is sacred and immutable</li>
    </ul>
  </li>
  <li>Rust’s current scope is <strong>deliberate and strategic</strong> - proving value in low-risk drivers before considering core subsystems.</li>
</ol>

<p><strong>Rust’s advantage</strong>: Better compile-time verification of ABI compatibility through <code class="language-plaintext highlighter-rouge">#[repr(C)]</code>, size assertions, and type safety, reducing accidental ABI breaks.</p>

<h1 id="rust与linux内核abi稳定性技术深度分析">Rust与Linux内核ABI稳定性：技术深度分析</h1>

<p><strong>摘要</strong>: Rust在Linux内核中提供用户空间接口吗？内核的ABI稳定性策略是什么？本文分析Rust驱动如何与用户空间交互，内部和外部ABI稳定性的关键区别，以及Android Binder和DRM驱动等生产代码的具体示例。</p>

<h2 id="快速回答">快速回答</h2>

<p><strong>问题1: Rust目前是否提供用户空间接口?</strong>
→ <strong>是的。</strong> Rust驱动已经通过ioctl、/dev节点、sysfs和其他标准机制暴露用户空间API。</p>

<p><strong>问题2: 内核内部追求ABI稳定性吗?</strong>
→ <strong>不。</strong> 内核内部API（模块和内核之间）<strong>明确不稳定</strong>。只有<strong>用户空间ABI</strong>是神圣的。</p>

<p><strong>问题3: Rust是否会被用于提供需要ABI稳定性的用户空间功能?</strong>
→ <strong>是的，已有实例。</strong> 主线内核中的Rust驱动（GPU、网络PHY）提供生产级用户空间ABI。Android Binder的Rust重写作为树外参考实现存在。</p>

<h2 id="深入探讨系统调用abi---不可变的契约">深入探讨：系统调用ABI - 不可变的契约</h2>

<p>在研究Rust的用户空间接口之前，让我们先了解用户空间ABI为何如此关键，通过查看<strong>系统调用层</strong> - 最基础的用户空间接口。</p>

<h3 id="神圣的系统调用abi">神圣的系统调用ABI</h3>

<p>Linux同时支持<strong>三种不同的系统调用机制</strong>以维持ABI兼容性：</p>

<table>
  <thead>
    <tr>
      <th>机制</th>
      <th>引入时间</th>
      <th>指令</th>
      <th>系统调用号</th>
      <th>参数</th>
      <th>状态</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>INT 0x80</strong></td>
      <td>Linux 1.0 (1994)</td>
      <td><code class="language-plaintext highlighter-rouge">int $0x80</code></td>
      <td>%eax</td>
      <td>%ebx, %ecx, %edx, %esi, %edi, %ebp</td>
      <td>✅ 仍支持(32位兼容)</td>
    </tr>
    <tr>
      <td><strong>SYSENTER</strong></td>
      <td>Intel P6 (1995)</td>
      <td><code class="language-plaintext highlighter-rouge">sysenter</code></td>
      <td>%eax</td>
      <td>%ebx, %ecx, %edx, %esi, %edi, %ebp</td>
      <td>✅ 仍支持(Intel 32位)</td>
    </tr>
    <tr>
      <td><strong>SYSCALL</strong></td>
      <td>AMD K6 (1997)</td>
      <td><code class="language-plaintext highlighter-rouge">syscall</code></td>
      <td>%rax</td>
      <td>%rdi, %rsi, %rdx, %r10, %r8, %r9</td>
      <td>✅ 主要64位方法</td>
    </tr>
  </tbody>
</table>

<p><strong>所有三种都并行维护</strong>，以确保任何用户空间应用程序永不破坏。</p>

<h3 id="实际内核实现">实际内核实现</h3>

<p>来自<code class="language-plaintext highlighter-rouge">arch/x86/kernel/cpu/common.c</code>（Linux内核源代码）：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// syscall_init() - 在内核初始化期间调用</span>
<span class="kt">void</span> <span class="nf">syscall_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="cm">/* 为用户/内核模式设置段选择子 */</span>
    <span class="n">wrmsr</span><span class="p">(</span><span class="n">MSR_STAR</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="p">(</span><span class="n">__USER32_CS</span> <span class="o">&lt;&lt;</span> <span class="mi">16</span><span class="p">)</span> <span class="o">|</span> <span class="n">__KERNEL_CS</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">cpu_feature_enabled</span><span class="p">(</span><span class="n">X86_FEATURE_FRED</span><span class="p">))</span>
        <span class="n">idt_syscall_init</span><span class="p">();</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">idt_syscall_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// 64位原生syscall入口</span>
    <span class="n">wrmsrq</span><span class="p">(</span><span class="n">MSR_LSTAR</span><span class="p">,</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">long</span><span class="p">)</span><span class="n">entry_SYSCALL_64</span><span class="p">);</span>

    <span class="c1">// 32位兼容模式 - 必须维护旧ABI</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">ia32_enabled</span><span class="p">())</span> <span class="p">{</span>
        <span class="n">wrmsrq_cstar</span><span class="p">((</span><span class="kt">unsigned</span> <span class="kt">long</span><span class="p">)</span><span class="n">entry_SYSCALL_compat</span><span class="p">);</span>

        <span class="cm">/* 为32位应用程序提供SYSENTER支持 */</span>
        <span class="n">wrmsrq_safe</span><span class="p">(</span><span class="n">MSR_IA32_SYSENTER_CS</span><span class="p">,</span> <span class="p">(</span><span class="n">u64</span><span class="p">)</span><span class="n">__KERNEL_CS</span><span class="p">);</span>
        <span class="n">wrmsrq_safe</span><span class="p">(</span><span class="n">MSR_IA32_SYSENTER_ESP</span><span class="p">,</span>
                    <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">long</span><span class="p">)(</span><span class="n">cpu_entry_stack</span><span class="p">(</span><span class="n">smp_processor_id</span><span class="p">())</span> <span class="o">+</span> <span class="mi">1</span><span class="p">));</span>
        <span class="n">wrmsrq_safe</span><span class="p">(</span><span class="n">MSR_IA32_SYSENTER_EIP</span><span class="p">,</span> <span class="p">(</span><span class="n">u64</span><span class="p">)</span><span class="n">entry_SYSENTER_compat</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>这意味着什么</strong>: 1994年使用<code class="language-plaintext highlighter-rouge">int $0x80</code>编译的32位应用程序在运行在现代硬件上的2026 Linux内核上<strong>仍然可以工作</strong>。</p>

<h3 id="两个系统调用表">两个系统调用表</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 64位原生系统调用</span>
<span class="k">const</span> <span class="n">sys_call_ptr_t</span> <span class="n">sys_call_table</span><span class="p">[</span><span class="n">__NR_syscall_max</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">[</span><span class="mi">0</span> <span class="p">...</span> <span class="n">__NR_syscall_max</span><span class="p">]</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">__x64_sys_ni_syscall</span><span class="p">,</span>
    <span class="cp">#include</span> <span class="cpf">&lt;asm/syscalls_64.h&gt;</span><span class="cp">
</span><span class="p">};</span>

<span class="c1">// 32位兼容系统调用</span>
<span class="k">const</span> <span class="n">sys_call_ptr_t</span> <span class="n">ia32_sys_call_table</span><span class="p">[</span><span class="n">__NR_ia32_syscall_max</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">[</span><span class="mi">0</span> <span class="p">...</span> <span class="n">__NR_ia32_syscall_max</span><span class="p">]</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">__ia32_sys_ni_syscall</span><span class="p">,</span>
    <span class="cp">#include</span> <span class="cpf">&lt;asm/syscalls_32.h&gt;</span><span class="cp">
</span><span class="p">};</span>
</code></pre></div></div>

<p><strong>关键洞察</strong>: Linux为32位和64位维护<strong>完全独立的系统调用表</strong>以确保ABI稳定性。32位表<strong>从未删除系统调用</strong> - 只添加新的。</p>

<h3 id="启动协议abi---连引导加载程序都有契约">启动协议ABI - 连引导加载程序都有契约</h3>

<p>来自Linux内核压缩引导加载程序（<code class="language-plaintext highlighter-rouge">arch/x86/boot/compressed/head_64.S</code>）：</p>

<pre><code class="language-assembly">/*
 * 32位入口在0且是ABI所以不可变！
 * 这是压缩内核入口点。
 */
    .code32
SYM_FUNC_START(startup_32)
</code></pre>

<p><strong>注释”ABI so immutable!”至关重要</strong>：</p>
<ul>
  <li>32位入口点<strong>必须始终在压缩内核的偏移0处</strong></li>
  <li>引导加载程序（GRUB、systemd-boot等）<strong>依赖于此</strong></li>
  <li>改变这一点会破坏每个引导加载程序</li>
  <li>这从Linux 2.6.x时代以来一直如此</li>
</ul>

<p><strong>启动协议规范</strong>（<code class="language-plaintext highlighter-rouge">Documentation/x86/boot.rst</code>）：</p>
<ul>
  <li>保护模式内核加载在：<code class="language-plaintext highlighter-rouge">0x100000</code>（1MB）</li>
  <li>32位入口点：始终从加载地址偏移0</li>
  <li><code class="language-plaintext highlighter-rouge">code32_start</code>字段：默认为<code class="language-plaintext highlighter-rouge">0x100000</code></li>
</ul>

<p>这是<strong>内部启动ABI</strong> - 与用户空间ABI不同，但同样不可变，因为外部工具（引导加载程序）依赖于它。</p>

<h3 id="给rust的教训">给Rust的教训</h3>

<p>当Rust驱动提供用户空间接口时，它们继承这些相同的铁律：</p>

<p><strong>C示例</strong>（传统）：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 用户空间永远不知道这从C变成了Rust</span>
<span class="kt">int</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">open</span><span class="p">(</span><span class="s">"/dev/binder"</span><span class="p">,</span> <span class="n">O_RDWR</span><span class="p">);</span>
<span class="n">ioctl</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">BINDER_WRITE_READ</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">bwr</span><span class="p">);</span>  <span class="c1">// ABI未改变</span>
</code></pre></div></div>

<p><strong>Rust实现</strong>（现代）：</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 必须提供相同的ABI</span>
<span class="k">const</span> <span class="n">BINDER_WRITE_READ</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="nn">kernel</span><span class="p">::</span><span class="nn">ioctl</span><span class="p">::</span><span class="nn">_IOWR</span><span class="p">::</span><span class="o">&lt;</span><span class="n">BinderWriteRead</span><span class="o">&gt;</span><span class="p">(</span>
    <span class="n">BINDER_TYPE</span> <span class="k">as</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="mi">1</span>  <span class="c1">// ioctl编号 - 永不改变</span>
<span class="p">);</span>
</code></pre></div></div>

<p>ioctl编号、结构布局和语义都<strong>冻结在时间中</strong> - 无论是用C还是Rust实现。</p>

<hr />

<h2 id="rust的abi保证system-v兼容性">Rust的ABI保证：System V兼容性</h2>

<p>在研究具体的用户空间接口之前，理解<strong>Rust如何保证与Linux在x86-64上使用的System V ABI兼容</strong>至关重要。</p>

<h3 id="rust符合system-v-abi吗">Rust符合System V ABI吗？</h3>

<p><strong>是的 - rustc通过语言特性明确保证System V ABI兼容性。</strong></p>

<p>x86-64上的Linux内核使用<strong>System V AMD64 ABI</strong>来定义：</p>
<ul>
  <li>函数调用约定（寄存器使用、栈布局）</li>
  <li>数据结构布局（对齐、填充、大小）</li>
  <li>类型表示（整数大小、指针大小）</li>
</ul>

<p>Rust提供多种机制来确保ABI兼容性：</p>

<table>
  <thead>
    <tr>
      <th>ABI类型</th>
      <th>Rust语法</th>
      <th>x86-64 Linux行为</th>
      <th>保证级别</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Rust ABI</strong></td>
      <td><code class="language-plaintext highlighter-rouge">extern "Rust"</code> (默认)</td>
      <td>未指定，可能改变</td>
      <td>❌ 不稳定</td>
    </tr>
    <tr>
      <td><strong>C ABI</strong></td>
      <td><code class="language-plaintext highlighter-rouge">extern "C"</code></td>
      <td>System V AMD64 ABI</td>
      <td>✅ <strong>语言规范保证</strong></td>
    </tr>
    <tr>
      <td><strong>System V</strong></td>
      <td><code class="language-plaintext highlighter-rouge">extern "sysv64"</code></td>
      <td>System V AMD64 ABI</td>
      <td>✅ <strong>显式保证</strong></td>
    </tr>
    <tr>
      <td><strong>数据布局</strong></td>
      <td><code class="language-plaintext highlighter-rouge">#[repr(C)]</code></td>
      <td>匹配C结构体布局</td>
      <td>✅ <strong>编译器保证</strong></td>
    </tr>
  </tbody>
</table>

<h3 id="编译器强制的abi正确性">编译器强制的ABI正确性</h3>

<p>与C中ABI兼容性是隐式且未检查的不同，<strong>Rust使ABI契约显式并在编译时验证</strong>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 显式C ABI - 编译器验证调用约定</span>
<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">kernel_function</span><span class="p">(</span><span class="n">arg</span><span class="p">:</span> <span class="nb">u64</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">i32</span> <span class="p">{</span>
    <span class="c1">// 函数使用System V调用约定：</span>
    <span class="c1">// - arg在%rdi寄存器中传递</span>
    <span class="c1">// - 返回值在%rax寄存器中</span>
    <span class="c1">// - 跨Rust编译器版本保证</span>
    <span class="mi">0</span>
<span class="p">}</span>

<span class="c1">// 显式内存布局 - 编译器验证大小/对齐</span>
<span class="nd">#[repr(C)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">KernelStruct</span> <span class="p">{</span>
    <span class="n">field1</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>  <span class="c1">// 偏移0，8字节</span>
    <span class="n">field2</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>  <span class="c1">// 偏移8，4字节</span>
    <span class="n">field3</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>  <span class="c1">// 偏移12，4字节</span>
<span class="p">}</span>

<span class="c1">// 编译时验证 - 如果布局改变则失败</span>
<span class="k">const</span> <span class="n">_</span><span class="p">:</span> <span class="p">()</span> <span class="o">=</span> <span class="nd">assert!</span><span class="p">(</span><span class="nn">core</span><span class="p">::</span><span class="nn">mem</span><span class="p">::</span><span class="nn">size_of</span><span class="p">::</span><span class="o">&lt;</span><span class="n">KernelStruct</span><span class="o">&gt;</span><span class="p">()</span> <span class="o">==</span> <span class="mi">16</span><span class="p">);</span>
<span class="k">const</span> <span class="n">_</span><span class="p">:</span> <span class="p">()</span> <span class="o">=</span> <span class="nd">assert!</span><span class="p">(</span><span class="nn">core</span><span class="p">::</span><span class="nn">mem</span><span class="p">::</span><span class="nn">align_of</span><span class="p">::</span><span class="o">&lt;</span><span class="n">KernelStruct</span><span class="o">&gt;</span><span class="p">()</span> <span class="o">==</span> <span class="mi">8</span><span class="p">);</span>
</code></pre></div></div>

<h3 id="参考示例binder-abi兼容性">参考示例：Binder ABI兼容性</h3>

<p>来自Android Binder Rust重写（树外参考实现）：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// drivers/android/binder/defs.rs (来自Rust-for-Linux树，非主线)</span>
<span class="nd">#[repr(C)]</span>
<span class="nd">#[derive(Copy,</span> <span class="nd">Clone)]</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="nf">BinderTransactionData</span><span class="p">(</span>
    <span class="n">MaybeUninit</span><span class="o">&lt;</span><span class="nn">uapi</span><span class="p">::</span><span class="n">binder_transaction_data</span><span class="o">&gt;</span>
<span class="p">);</span>

<span class="c1">// SAFETY: 显式FromBytes/AsBytes确保二进制兼容性</span>
<span class="k">unsafe</span> <span class="k">impl</span> <span class="n">FromBytes</span> <span class="k">for</span> <span class="n">BinderTransactionData</span> <span class="p">{}</span>
<span class="k">unsafe</span> <span class="k">impl</span> <span class="n">AsBytes</span> <span class="k">for</span> <span class="n">BinderTransactionData</span> <span class="p">{}</span>
</code></pre></div></div>

<p><strong>注意</strong>: 此代码来自Rust-for-Linux项目的Binder实现，作为树外参考存在，展示了如何在Rust中实现用户空间ABI兼容性。</p>

<p><strong>为什么使用<code class="language-plaintext highlighter-rouge">MaybeUninit</code>?</strong> 它保留<strong>填充字节</strong>以确保与C的逐位相同布局，包括未初始化的填充。这对用户空间兼容性至关重要。</p>

<h3 id="rustc的abi稳定性承诺">rustc的ABI稳定性承诺</h3>

<p>来自Rust语言规范：</p>

<blockquote>
  <p><strong><code class="language-plaintext highlighter-rouge">#[repr(C)]</code>保证</strong>: 用<code class="language-plaintext highlighter-rouge">#[repr(C)]</code>标记的类型与相应的C类型具有相同的布局，遵循目标平台的C ABI。这个保证在<strong>Rust编译器版本之间是稳定的</strong>。</p>
</blockquote>

<p><strong>与C对比:</strong></p>

<table>
  <thead>
    <tr>
      <th>方面</th>
      <th>C</th>
      <th>Rust</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>ABI规范</strong></td>
      <td>隐式，平台相关</td>
      <td>显式使用<code class="language-plaintext highlighter-rouge">extern "C"</code></td>
    </tr>
    <tr>
      <td><strong>布局验证</strong></td>
      <td>运行时bug</td>
      <td>编译时<code class="language-plaintext highlighter-rouge">assert!</code></td>
    </tr>
    <tr>
      <td><strong>填充控制</strong></td>
      <td>隐式，易出错</td>
      <td><code class="language-plaintext highlighter-rouge">MaybeUninit</code>显式</td>
    </tr>
    <tr>
      <td><strong>跨版本稳定性</strong></td>
      <td>信任开发者</td>
      <td>语言规范</td>
    </tr>
  </tbody>
</table>

<h3 id="系统调用寄存器使用">系统调用寄存器使用</h3>

<p>System V ABI指定函数调用的寄存器使用。对于<strong>系统调用</strong>，Linux使用<strong>修改过的</strong>System V约定：</p>

<p><strong>System V函数调用</strong>（<code class="language-plaintext highlighter-rouge">extern "C"</code>使用）：</p>
<ul>
  <li>参数: <code class="language-plaintext highlighter-rouge">%rdi, %rsi, %rdx, %rcx, %r8, %r9</code></li>
  <li>返回: <code class="language-plaintext highlighter-rouge">%rax</code></li>
</ul>

<p><strong>Linux syscall</strong>（特殊情况）：</p>
<ul>
  <li>系统调用号: <code class="language-plaintext highlighter-rouge">%rax</code></li>
  <li>参数: <code class="language-plaintext highlighter-rouge">%rdi, %rsi, %rdx, %r10, %r8, %r9</code>（注意：<code class="language-plaintext highlighter-rouge">%r10</code>而非<code class="language-plaintext highlighter-rouge">%rcx</code>）</li>
  <li>返回: <code class="language-plaintext highlighter-rouge">%rax</code></li>
</ul>

<p>Rust尊重两种约定：</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 常规C函数 - 使用标准System V ABI</span>
<span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">regular_function</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="nb">u64</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// a在%rdi, b在%rsi</span>
<span class="p">}</span>

<span class="c1">// 系统调用包装器 - 使用syscall约定</span>
<span class="nd">#[inline(always)]</span>
<span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">syscall1</span><span class="p">(</span><span class="n">n</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span> <span class="n">arg1</span><span class="p">:</span> <span class="nb">u64</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u64</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">ret</span><span class="p">:</span> <span class="nb">u64</span><span class="p">;</span>
    <span class="nn">core</span><span class="p">::</span><span class="nn">arch</span><span class="p">::</span><span class="nd">asm!</span><span class="p">(</span>
        <span class="s">"syscall"</span><span class="p">,</span>
        <span class="k">in</span><span class="p">(</span><span class="s">"rax"</span><span class="p">)</span> <span class="n">n</span><span class="p">,</span>     <span class="c1">// 系统调用号</span>
        <span class="k">in</span><span class="p">(</span><span class="s">"rdi"</span><span class="p">)</span> <span class="n">arg1</span><span class="p">,</span>  <span class="c1">// 第一个参数</span>
        <span class="nf">lateout</span><span class="p">(</span><span class="s">"rax"</span><span class="p">)</span> <span class="n">ret</span><span class="p">,</span>
    <span class="p">);</span>
    <span class="n">ret</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="答案rust能编译成符合system-v-abi的代码吗">答案：Rust能编译成符合System V ABI的代码吗？</h3>

<p>✅ <strong>是的，rustc通过以下方式保证System V ABI兼容性：</strong></p>
<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">extern "C"</code></strong> - 显式使用平台C ABI（x86-64 Linux上是System V）</li>
  <li><strong><code class="language-plaintext highlighter-rouge">#[repr(C)]</code></strong> - 保证C兼容的数据布局</li>
  <li><strong>编译时验证</strong> - 大小/对齐断言捕获ABI破坏</li>
  <li><strong>语言规范</strong> - 跨编译器版本的稳定性</li>
</ol>

<p>这不是”尽力而为” - 这是由Rust规范支持的<strong>语言级保证</strong>。</p>

<hr />

<h2 id="问题1rust的用户空间接口基础设施">问题1：Rust的用户空间接口基础设施</h2>

<h3 id="uapi-crate-用户空间api绑定"><code class="language-plaintext highlighter-rouge">uapi</code> Crate: 用户空间API绑定</h3>

<p>Rust为用户空间API提供了专门的crate。来自实际内核源代码：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// rust/uapi/lib.rs (实际内核代码)</span>
<span class="cd">//! UAPI绑定。</span>
<span class="cd">//!</span>
<span class="cd">//! 包含bindgen为UAPI接口生成的绑定。</span>
<span class="cd">//!</span>
<span class="cd">//! 这个crate可以被需要与用户空间API交互的驱动直接使用。</span>

<span class="nd">#![no_std]</span>

<span class="c1">// 自动生成的UAPI绑定</span>
<span class="nd">include!</span><span class="p">(</span><span class="nd">concat!</span><span class="p">(</span><span class="nd">env!</span><span class="p">(</span><span class="s">"OBJTREE"</span><span class="p">),</span> <span class="s">"/rust/uapi/uapi_generated.rs"</span><span class="p">));</span>
</code></pre></div></div>

<p><strong>关键洞察</strong>: 内核有<strong>单独的<code class="language-plaintext highlighter-rouge">uapi</code> crate</strong>专门用于用户空间接口，与内部内核API分离。</p>

<h3 id="rust中的ioctl支持">Rust中的ioctl支持</h3>

<p>内核为Rust驱动提供完整的ioctl支持：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// rust/kernel/ioctl.rs (实际内核代码)</span>
<span class="cd">//! `ioctl()`编号定义。</span>

<span class="cd">/// 为只读ioctl构建ioctl编号</span>
<span class="nd">#[inline(always)]</span>
<span class="k">pub</span> <span class="k">const</span> <span class="k">fn</span> <span class="n">_IOR</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">ty</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span> <span class="n">nr</span><span class="p">:</span> <span class="nb">u32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u32</span> <span class="p">{</span>
    <span class="nf">_IOC</span><span class="p">(</span><span class="nn">uapi</span><span class="p">::</span><span class="n">_IOC_READ</span><span class="p">,</span> <span class="n">ty</span><span class="p">,</span> <span class="n">nr</span><span class="p">,</span> <span class="nn">core</span><span class="p">::</span><span class="nn">mem</span><span class="p">::</span><span class="nn">size_of</span><span class="p">::</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">())</span>
<span class="p">}</span>

<span class="cd">/// 为只写ioctl构建ioctl编号</span>
<span class="nd">#[inline(always)]</span>
<span class="k">pub</span> <span class="k">const</span> <span class="k">fn</span> <span class="n">_IOW</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">ty</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span> <span class="n">nr</span><span class="p">:</span> <span class="nb">u32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u32</span> <span class="p">{</span>
    <span class="nf">_IOC</span><span class="p">(</span><span class="nn">uapi</span><span class="p">::</span><span class="n">_IOC_WRITE</span><span class="p">,</span> <span class="n">ty</span><span class="p">,</span> <span class="n">nr</span><span class="p">,</span> <span class="nn">core</span><span class="p">::</span><span class="nn">mem</span><span class="p">::</span><span class="nn">size_of</span><span class="p">::</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">())</span>
<span class="p">}</span>

<span class="cd">/// 为读写ioctl构建ioctl编号</span>
<span class="nd">#[inline(always)]</span>
<span class="k">pub</span> <span class="k">const</span> <span class="k">fn</span> <span class="n">_IOWR</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">ty</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span> <span class="n">nr</span><span class="p">:</span> <span class="nb">u32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u32</span> <span class="p">{</span>
    <span class="nf">_IOC</span><span class="p">(</span>
        <span class="nn">uapi</span><span class="p">::</span><span class="n">_IOC_READ</span> <span class="p">|</span> <span class="nn">uapi</span><span class="p">::</span><span class="n">_IOC_WRITE</span><span class="p">,</span>
        <span class="n">ty</span><span class="p">,</span>
        <span class="n">nr</span><span class="p">,</span>
        <span class="nn">core</span><span class="p">::</span><span class="nn">mem</span><span class="p">::</span><span class="nn">size_of</span><span class="p">::</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(),</span>
    <span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>这与C的ioctl宏完全相同</strong>，但具有类型安全。</p>

<h3 id="参考示例android-binder用户空间协议">参考示例：Android Binder用户空间协议</h3>

<p>Android Binder Rust重写（树外）展示了如何暴露广泛的用户空间API：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 来自Rust-for-Linux Binder实现的示例（非主线）</span>
<span class="k">use</span> <span class="nn">kernel</span><span class="p">::</span><span class="nn">uapi</span><span class="p">::{</span><span class="k">self</span><span class="p">,</span> <span class="o">*</span><span class="p">};</span>

<span class="c1">// 用户空间协议常量 - 必须保持稳定</span>
<span class="nd">pub_no_prefix!</span><span class="p">(</span>
    <span class="n">binder_driver_return_protocol_</span><span class="p">,</span>
    <span class="n">BR_TRANSACTION</span><span class="p">,</span>
    <span class="n">BR_REPLY</span><span class="p">,</span>
    <span class="n">BR_DEAD_REPLY</span><span class="p">,</span>
    <span class="n">BR_OK</span><span class="p">,</span>
    <span class="n">BR_ERROR</span><span class="p">,</span>
    <span class="c1">// ... 21个总协议常量</span>
<span class="p">);</span>

<span class="c1">// 用户空间数据结构 - 包装以保持ABI</span>
<span class="nd">decl_wrapper!</span><span class="p">(</span><span class="n">BinderTransactionData</span><span class="p">,</span> <span class="nn">uapi</span><span class="p">::</span><span class="n">binder_transaction_data</span><span class="p">);</span>
<span class="nd">decl_wrapper!</span><span class="p">(</span><span class="n">BinderWriteRead</span><span class="p">,</span> <span class="nn">uapi</span><span class="p">::</span><span class="n">binder_write_read</span><span class="p">);</span>
<span class="nd">decl_wrapper!</span><span class="p">(</span><span class="n">BinderVersion</span><span class="p">,</span> <span class="nn">uapi</span><span class="p">::</span><span class="n">binder_version</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>关键细节</strong>: 这些使用<code class="language-plaintext highlighter-rouge">MaybeUninit</code>来<strong>保留填充字节</strong>，确保与C的二进制相同ABI：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 保留确切内存布局的包装器，包括填充</span>
<span class="nd">#[derive(Copy,</span> <span class="nd">Clone)]</span>
<span class="nd">#[repr(transparent)]</span>
<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">struct</span> <span class="nf">BinderTransactionData</span><span class="p">(</span><span class="n">MaybeUninit</span><span class="o">&lt;</span><span class="nn">uapi</span><span class="p">::</span><span class="n">binder_transaction_data</span><span class="o">&gt;</span><span class="p">);</span>

<span class="c1">// SAFETY: 显式FromBytes/AsBytes实现</span>
<span class="k">unsafe</span> <span class="k">impl</span> <span class="n">FromBytes</span> <span class="k">for</span> <span class="n">BinderTransactionData</span> <span class="p">{}</span>
<span class="k">unsafe</span> <span class="k">impl</span> <span class="n">AsBytes</span> <span class="k">for</span> <span class="n">BinderTransactionData</span> <span class="p">{}</span>
</code></pre></div></div>

<p><strong>为什么重要</strong>: 针对C头文件编译的用户空间代码向Rust驱动发送<strong>完全相同的二进制数据</strong>。</p>

<h3 id="用户空间接口总结">用户空间接口总结</h3>

<table>
  <thead>
    <tr>
      <th>接口类型</th>
      <th>Rust支持</th>
      <th>示例</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>ioctl处理器</strong></td>
      <td>✅ 完全支持（驱动处理命令）</td>
      <td>DRM驱动, Binder</td>
    </tr>
    <tr>
      <td><strong>/dev设备节点</strong></td>
      <td>✅ 通过miscdevice/cdev</td>
      <td>字符设备</td>
    </tr>
    <tr>
      <td><strong>/sys (sysfs)</strong></td>
      <td>✅ 通过kobject绑定</td>
      <td>设备属性</td>
    </tr>
    <tr>
      <td><strong>/proc</strong></td>
      <td>✅ 通过seq_file</td>
      <td>进程信息</td>
    </tr>
    <tr>
      <td><strong>定义新系统调用</strong></td>
      <td>❌ 不可能（syscall入口是C）</td>
      <td>-</td>
    </tr>
    <tr>
      <td><strong>Netlink</strong></td>
      <td>✅ 通过net子系统</td>
      <td>网络配置</td>
    </tr>
  </tbody>
</table>

<p><strong>重要区别</strong>: Rust驱动可以<strong>处理</strong>ioctl命令（驱动特定的逻辑），但ioctl <strong>系统调用入口点</strong>本身（在<code class="language-plaintext highlighter-rouge">fs/ioctl.c</code>中）仍然是C代码。其他接口也是如此 - Rust提供处理器，而不是核心机制。</p>

<p><strong>答案</strong>: 是的，Rust通过标准内核机制<strong>完全支持</strong>用户空间接口，尽管核心系统调用层仍然是C。</p>

<h2 id="关键澄清用户空间程序不能使用-rustkernel">关键澄清：用户空间程序不能使用 <code class="language-plaintext highlighter-rouge">rust/kernel</code></h2>

<p><strong>一个常见误解</strong>：”我的用户空间Rust程序可以使用<code class="language-plaintext highlighter-rouge">rust/kernel</code>抽象吗？”</p>

<p><strong>答案：绝对不能。</strong> 这是一个根本性的架构约束，而不是技术限制。</p>

<h3 id="内核空间-vs-用户空间---完全隔离">内核空间 vs 用户空间 - 完全隔离</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────────────────────────────────┐
│              用户空间                                     │
│  - 使用Rust标准库 (std)                                  │
│  - 普通Rust程序                                          │
│  - 可以使用tokio、serde等                                │
│                                                          │
│  用户空间Rust程序:                                       │
│  ┌────────────────────────────────────────┐            │
│  │ use std::fs::File;                      │            │
│  │ use std::os::unix::io::AsRawFd;        │            │
│  │                                         │            │
│  │ fn main() {                             │            │
│  │     let fd = File::open("/dev/my_dev") │            │
│  │         .unwrap();                      │            │
│  │     // 通过系统调用与内核交互           │            │
│  │     unsafe {                             │            │
│  │         libc::ioctl(fd.as_raw_fd(), ...) │           │
│  │     }                                    │            │
│  │ }                                        │            │
│  └────────────────────────────────────────┘            │
└──────────────────┬──────────────────────────────────────┘
                   │
                   │  系统调用边界
                   │  - open(), ioctl(), read(), write()
                   │  - /dev, /sys, /proc 接口
                   │  - ❌ 不能直接调用内核函数
                   │
┌──────────────────┴──────────────────────────────────────┐
│              内核空间                                     │
│  - 使用 #![no_std] (无标准库)                           │
│  - 只能在内核模块中运行                                 │
│  - 使用 rust/kernel 抽象                                │
│                                                          │
│  内核Rust驱动:                                          │
│  ┌────────────────────────────────────────┐            │
│  │ #![no_std]                             │            │
│  │ use kernel::prelude::*;                │            │
│  │                                         │            │
│  │ impl kernel::file::Operations for MyDev│            │
│  │     fn ioctl(...) -&gt; Result {          │            │
│  │         // 处理用户空间的ioctl请求     │            │
│  │         kernel::sync::SpinLock::...     │            │
│  │     }                                   │            │
│  │ }                                       │            │
│  └────────────────────────────────────────┘            │
└─────────────────────────────────────────────────────────┘
</code></pre></div></div>

<h3 id="为什么用户空间不能使用-rustkernel">为什么用户空间不能使用 <code class="language-plaintext highlighter-rouge">rust/kernel</code></h3>

<p><strong>1. <code class="language-plaintext highlighter-rouge">#![no_std]</code> - 没有标准库</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// rust/kernel/lib.rs (库crate根文件)</span>
<span class="nd">#![no_std]</span>  <span class="c1">// ← 关键：没有标准库！</span>

<span class="c1">// 内核空间没有：</span>
<span class="c1">// - 堆分配（必须使用GFP_KERNEL）</span>
<span class="c1">// - 线程（使用内核任务）</span>
<span class="c1">// - 文件系统（用户空间概念）</span>
<span class="c1">// - 网络库（用户空间概念）</span>
<span class="c1">// - println!()（使用pr_info!()）</span>

<span class="c1">// 只有：</span>
<span class="c1">// - core库（不需要操作系统）</span>
<span class="c1">// - 内核特定API</span>
</code></pre></div></div>

<p><strong>注意</strong>：<code class="language-plaintext highlighter-rouge">#![no_std]</code> 属性只在库crate的根文件中声明，如 <code class="language-plaintext highlighter-rouge">rust/kernel/lib.rs</code>、<code class="language-plaintext highlighter-rouge">rust/bindings/lib.rs</code> 等。单独的驱动模块文件（例如 <code class="language-plaintext highlighter-rouge">drivers/gpu/drm/nova/driver.rs</code>）不需要这个声明 - 它们通过 <code class="language-plaintext highlighter-rouge">use kernel::prelude::*</code> 使用kernel库，从而继承了no_std环境。</p>

<p><strong>2. 不同的编译目标</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 用户空间Rust程序</span>
<span class="nv">$ </span>rustc <span class="nt">--target</span> x86_64-unknown-linux-gnu userspace.rs
<span class="c"># 编译成用户空间可执行文件</span>

<span class="c"># 内核Rust模块</span>
<span class="nv">$ </span>rustc <span class="nt">--target</span> x86_64-linux-kernel module.rs
<span class="c"># 编译成内核模块 (.ko文件)</span>
<span class="c"># 链接到内核，不能在用户空间运行</span>
</code></pre></div></div>

<p><strong>3. 内存空间隔离</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>虚拟地址空间:
┌─────────────────────┐ 0xFFFFFFFFFFFFFFFF
│   内核空间           │ ← rust/kernel 运行在这里
│   (仅内核代码)       │   只能通过系统调用访问
├─────────────────────┤ 0x00007FFFFFFFFFFF
│   用户空间           │ ← 用户Rust程序运行在这里
│   (应用程序)         │   不能访问内核内存
└─────────────────────┘ 0x0000000000000000
</code></pre></div></div>

<h3 id="用户空间程序如何与rust内核驱动交互">用户空间程序如何与Rust内核驱动交互</h3>

<p><strong>方式1：通过 <code class="language-plaintext highlighter-rouge">/dev</code> 设备节点</strong></p>

<p><strong>内核侧（Rust驱动）：</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// drivers/example/my_device.rs</span>
<span class="k">use</span> <span class="nn">kernel</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">kernel</span><span class="p">::</span><span class="nn">file</span><span class="p">::</span><span class="n">Operations</span><span class="p">;</span>

<span class="k">struct</span> <span class="n">MyDevice</span><span class="p">;</span>

<span class="k">impl</span> <span class="n">Operations</span> <span class="k">for</span> <span class="n">MyDevice</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">open</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="nd">pr_info!</span><span class="p">(</span><span class="s">"用户空间打开了设备</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="nf">Ok</span><span class="p">(</span><span class="n">MyDevice</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">ioctl</span><span class="p">(</span><span class="n">cmd</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span> <span class="n">arg</span><span class="p">:</span> <span class="nb">usize</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">isize</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">match</span> <span class="n">cmd</span> <span class="p">{</span>
            <span class="n">MY_IOCTL_CMD</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="c1">// 处理用户空间的ioctl请求</span>
                <span class="nf">Ok</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
            <span class="p">}</span>
            <span class="n">_</span> <span class="k">=&gt;</span> <span class="nf">Err</span><span class="p">(</span><span class="n">EINVAL</span><span class="p">),</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>用户空间（标准Rust程序）：</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// userspace_app/src/main.rs</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">fs</span><span class="p">::</span><span class="n">File</span><span class="p">;</span>  <span class="c1">// ← 使用标准库！</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">os</span><span class="p">::</span><span class="nn">unix</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="n">AsRawFd</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 打开Rust内核驱动创建的设备</span>
    <span class="k">let</span> <span class="n">file</span> <span class="o">=</span> <span class="nn">File</span><span class="p">::</span><span class="nf">open</span><span class="p">(</span><span class="s">"/dev/my_device"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="c1">// 通过系统调用交互</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">ret</span> <span class="o">=</span> <span class="nn">libc</span><span class="p">::</span><span class="nf">ioctl</span><span class="p">(</span>
            <span class="n">file</span><span class="nf">.as_raw_fd</span><span class="p">(),</span>
            <span class="n">MY_IOCTL_CMD</span><span class="p">,</span>
            <span class="o">&amp;</span><span class="n">my_data</span>
        <span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// 用户空间完全不知道内核是C还是Rust！</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>方式2：通过 <code class="language-plaintext highlighter-rouge">sysfs</code></strong></p>

<p><strong>内核侧：</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 在内核中创建sysfs属性</span>
<span class="k">use</span> <span class="nn">kernel</span><span class="p">::</span><span class="nn">device</span><span class="p">::</span><span class="n">Device</span><span class="p">;</span>

<span class="k">impl</span> <span class="n">Device</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">create_sysfs_attrs</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span> <span class="p">{</span>
        <span class="c1">// 创建 /sys/class/my_device/value</span>
        <span class="nf">sysfs_create_file</span><span class="p">(</span><span class="o">...</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="nf">Ok</span><span class="p">(())</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>用户空间：</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="n">fs</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 读取由Rust内核驱动提供的sysfs文件</span>
    <span class="k">let</span> <span class="n">value</span> <span class="o">=</span> <span class="nn">fs</span><span class="p">::</span><span class="nf">read_to_string</span><span class="p">(</span>
        <span class="s">"/sys/class/my_device/value"</span>
    <span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="nd">println!</span><span class="p">(</span><span class="s">"来自内核的值: {}"</span><span class="p">,</span> <span class="n">value</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>方式3：通过 <code class="language-plaintext highlighter-rouge">netlink</code>（网络驱动）</strong></p>

<p><strong>内核侧：</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">kernel</span><span class="p">::</span><span class="n">net</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">send_netlink_msg</span><span class="p">(</span><span class="n">msg</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">NetlinkMsg</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span> <span class="p">{</span>
    <span class="nf">netlink_broadcast</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>用户空间：</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">netlink_sys</span><span class="p">::{</span><span class="n">Socket</span><span class="p">,</span> <span class="n">SocketAddr</span><span class="p">};</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">socket</span> <span class="o">=</span> <span class="nn">Socket</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="c1">// 接收来自Rust内核驱动的netlink消息</span>
    <span class="k">let</span> <span class="n">msg</span> <span class="o">=</span> <span class="n">socket</span><span class="nf">.recv_from</span><span class="p">(</span><span class="o">...</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="对比表格">对比表格</h3>

<table>
  <thead>
    <tr>
      <th>特性</th>
      <th>内核空间 (<code class="language-plaintext highlighter-rouge">rust/kernel</code>)</th>
      <th>用户空间 (标准Rust)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>标准库</strong></td>
      <td>❌ <code class="language-plaintext highlighter-rouge">#![no_std]</code></td>
      <td>✅ <code class="language-plaintext highlighter-rouge">use std::*</code></td>
    </tr>
    <tr>
      <td><strong>运行环境</strong></td>
      <td>内核模块 (.ko)</td>
      <td>可执行文件 (ELF)</td>
    </tr>
    <tr>
      <td><strong>内存分配</strong></td>
      <td><code class="language-plaintext highlighter-rouge">kernel::kvec::KVec</code></td>
      <td><code class="language-plaintext highlighter-rouge">std::vec::Vec</code></td>
    </tr>
    <tr>
      <td><strong>打印输出</strong></td>
      <td><code class="language-plaintext highlighter-rouge">pr_info!()</code></td>
      <td><code class="language-plaintext highlighter-rouge">println!()</code></td>
    </tr>
    <tr>
      <td><strong>文件操作</strong></td>
      <td>❌ 不能打开文件</td>
      <td>✅ <code class="language-plaintext highlighter-rouge">std::fs::File</code></td>
    </tr>
    <tr>
      <td><strong>网络</strong></td>
      <td>提供网络服务</td>
      <td>使用网络服务</td>
    </tr>
    <tr>
      <td><strong>硬件访问</strong></td>
      <td>✅ 直接访问</td>
      <td>❌ 通过系统调用</td>
    </tr>
    <tr>
      <td><strong>特权级别</strong></td>
      <td>Ring 0</td>
      <td>Ring 3</td>
    </tr>
    <tr>
      <td><strong>可用crates</strong></td>
      <td>极少（仅no_std）</td>
      <td>所有标准crates</td>
    </tr>
  </tbody>
</table>

<h3 id="完整示例用户空间读取gpu信息">完整示例：用户空间读取GPU信息</h3>

<p><strong>1. 内核Rust GPU驱动：</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// drivers/gpu/drm/nova/driver.rs</span>
<span class="k">use</span> <span class="nn">kernel</span><span class="p">::</span><span class="n">drm</span><span class="p">;</span>

<span class="k">impl</span> <span class="nn">drm</span><span class="p">::</span><span class="n">Driver</span> <span class="k">for</span> <span class="n">NovaDriver</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">ioctl</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">cmd</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="p">[</span><span class="nb">u8</span><span class="p">])</span> <span class="k">-&gt;</span> <span class="nb">Result</span> <span class="p">{</span>
        <span class="k">match</span> <span class="n">cmd</span> <span class="p">{</span>
            <span class="n">DRM_NOVA_GET_PARAM</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="c1">// 读取GPU参数</span>
                <span class="k">let</span> <span class="n">param</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.get_gpu_param</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
                <span class="c1">// 复制到用户空间</span>
                <span class="n">data</span><span class="nf">.copy_from_slice</span><span class="p">(</span><span class="o">&amp;</span><span class="n">param</span><span class="nf">.to_bytes</span><span class="p">());</span>
                <span class="nf">Ok</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
            <span class="p">}</span>
            <span class="n">_</span> <span class="k">=&gt;</span> <span class="nf">Err</span><span class="p">(</span><span class="n">EINVAL</span><span class="p">),</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>2. 用户空间Rust应用：</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// userspace_app/src/main.rs</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">fs</span><span class="p">::</span><span class="n">OpenOptions</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">os</span><span class="p">::</span><span class="nn">unix</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="n">AsRawFd</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 打开DRM设备</span>
    <span class="k">let</span> <span class="n">drm_device</span> <span class="o">=</span> <span class="nn">OpenOptions</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.read</span><span class="p">(</span><span class="k">true</span><span class="p">)</span>
        <span class="nf">.write</span><span class="p">(</span><span class="k">true</span><span class="p">)</span>
        <span class="nf">.open</span><span class="p">(</span><span class="s">"/dev/dri/renderD128"</span><span class="p">)</span>
        <span class="nf">.unwrap</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">drm_device</span><span class="nf">.as_raw_fd</span><span class="p">();</span>

    <span class="c1">// 准备ioctl参数</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">param_data</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0u8</span><span class="p">;</span> <span class="mi">64</span><span class="p">];</span>

    <span class="c1">// 调用ioctl（进入内核）</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nn">libc</span><span class="p">::</span><span class="nf">ioctl</span><span class="p">(</span>
            <span class="n">fd</span><span class="p">,</span>
            <span class="n">DRM_NOVA_GET_PARAM</span><span class="p">,</span>
            <span class="o">&amp;</span><span class="k">mut</span> <span class="n">param_data</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="n">_</span>
        <span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// param_data现在包含来自内核的GPU参数</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"GPU参数: {:?}"</span><span class="p">,</span> <span class="n">param_data</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="关键要点">关键要点</h3>

<ol>
  <li>❌ <strong>用户空间程序不能使用 <code class="language-plaintext highlighter-rouge">rust/kernel</code></strong> - 它们运行在完全不同的环境中</li>
  <li>✅ <strong>用户空间通过系统调用与内核交互</strong> - 就像与C驱动交互一样</li>
  <li>🔄 <strong>交互是双向的但间接的</strong>：
    <ul>
      <li>用户空间 → 系统调用/ioctl/文件系统 → Rust内核驱动</li>
      <li>Rust内核驱动 → 响应/数据 → 系统调用返回 → 用户空间</li>
    </ul>
  </li>
</ol>

<p><strong>用户空间完全不知道内核驱动是C还是Rust - 这正是ABI稳定性的意义！</strong> 🎯</p>

<h2 id="问题2内核内部abi稳定性策略">问题2：内核内部ABI稳定性策略</h2>

<h3 id="关键区别">关键区别</h3>

<p>Linux内核有<strong>两种完全不同的ABI策略</strong>：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────────────────────────────────┐
│                  用户空间                            │
│  (应用程序、库、工具)                                │
└─────────────────┬───────────────────────────────────┘
                  │
                  │  ← 用户空间ABI (稳定、神圣)
                  │     系统调用、ioctl、/proc、/sys
                  │     "我们不破坏用户空间" - Linus
                  │
┌─────────────────┴───────────────────────────────────┐
│            LINUX内核                                 │
│  ┌─────────────────────────────────────────┐       │
│  │  内核子系统 (VFS, MM, Net等)            │       │
│  └─────────────────┬───────────────────────┘       │
│                    │                                │
│                    │  ← 内部API (不稳定!)           │
│                    │     随时可以改变                │
│                    │     无向后兼容                  │
│  ┌─────────────────┴───────────────────────┐       │
│  │  可加载内核模块 (.ko文件)                │       │
│  │  (驱动、文件系统等)                      │       │
│  └─────────────────────────────────────────┘       │
└─────────────────────────────────────────────────────┘
</code></pre></div></div>

<h3 id="官方内核策略内部abi不稳定">官方内核策略：内部ABI不稳定</h3>

<p>来自Linux内核文档<sup id="fnref:1:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>：</p>

<blockquote>
  <p><strong>内核没有稳定的内部API/ABI。</strong></p>

  <p>内核内部API可以而且确实随时改变，出于任何原因。</p>
</blockquote>

<p><strong>实践中</strong>: 如果你为Linux 6.5编译内核模块，它在Linux 6.6上<strong>将无法加载</strong>，除非重新编译。</p>

<h3 id="为什么内部abi不稳定">为什么内部ABI不稳定</h3>

<p>Greg Kroah-Hartman在他著名的文档中解释了这一点：</p>

<p><strong>没有内部ABI稳定性的原因:</strong></p>

<ol>
  <li><strong>快速演进</strong>: 子系统需要重构的自由</li>
  <li><strong>无二进制模块</strong>: 所有模块必须是GPL且可重新编译</li>
  <li><strong>质量控制</strong>: 强制树外驱动保持更新</li>
  <li><strong>安全性</strong>: 允许修复根本性设计缺陷</li>
</ol>

<p><strong>哲学</strong>: “如果你的代码足够好，它应该在树内。如果在树内，重新编译是免费的。”</p>

<h3 id="用户空间abi绝对稳定">用户空间ABI：绝对稳定</h3>

<p>Linus Torvalds的著名规则（从无数LKML帖子中概括）：</p>

<blockquote>
  <p><strong>“我们不破坏用户空间。永远。”</strong></p>

  <p>如果内核更改破坏了正常工作的用户空间应用程序，该更改<strong>将被回退</strong>，无论它多么”正确”。</p>
</blockquote>

<p>来自官方文档<sup id="fnref:2:1"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>：</p>

<blockquote>
  <p><strong>稳定接口:</strong></p>
  <ul>
    <li>系统调用: 绝不能改变语义</li>
    <li>/proc和/sys ABI: 保证至少2年稳定</li>
    <li>ioctl编号: 一旦定义就永不重用</li>
    <li>二进制格式 (ELF等): 向后兼容</li>
  </ul>
</blockquote>

<p><strong>答案</strong>: 内核<strong>不追求内部ABI稳定性</strong>。只有<strong>用户空间ABI</strong>是稳定的。</p>

<h2 id="问题3rust与用户空间abi稳定性">问题3：Rust与用户空间ABI稳定性</h2>

<h3 id="当前状态rust提供稳定的用户空间abi">当前状态：Rust提供稳定的用户空间ABI</h3>

<p><strong>主线内核中的生产级驱动</strong>（截至Linux 6.x）：</p>

<ol>
  <li><strong>GPU驱动 (Nova)</strong>: 为Nvidia GPU提供DRM用户空间ABI - 完整的ioctl接口</li>
  <li><strong>网络PHY驱动</strong> (ax88796b, qt2025): ethtool/netlink ABI</li>
  <li><strong>块设备</strong> (rnull): 标准块设备ioctl ABI</li>
  <li><strong>CPU频率</strong> (rcpufreq_dt): sysfs和ioctl接口</li>
</ol>

<p><strong>参考实现（树外）</strong>：</p>

<p><strong>Android Binder</strong>（Rust重写，尚未进入主线）：展示了与C版本<strong>完全相同的用户空间ABI</strong>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 与C版本相同的BINDER_WRITE_READ ioctl</span>
<span class="k">const</span> <span class="n">BINDER_WRITE_READ</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="nn">kernel</span><span class="p">::</span><span class="nn">ioctl</span><span class="p">::</span><span class="nn">_IOWR</span><span class="p">::</span><span class="o">&lt;</span><span class="n">BinderWriteRead</span><span class="o">&gt;</span><span class="p">(</span>
    <span class="n">BINDER_TYPE</span> <span class="k">as</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="mi">1</span>
<span class="p">);</span>

<span class="c1">// 使用C头文件的用户空间代码发送完全相同的二进制数据</span>
</code></pre></div></div>

<p>这个树外实现已经<strong>验证</strong> - Android的libbinder（C++用户空间库）与Rust驱动无需修改即可工作。</p>

<h3 id="为什么rust实际上更适合abi稳定性">为什么Rust实际上更适合ABI稳定性</h3>

<p><strong>C中的问题</strong>: 意外的ABI破坏</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C - 容易意外改变ABI</span>
<span class="k">struct</span> <span class="n">binder_transaction_data</span> <span class="p">{</span>
    <span class="kt">uint64_t</span> <span class="n">cookie</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">code</span><span class="p">;</span>
    <span class="c1">// 糟糕，开发者在这里添加字段 - ABI破坏了！</span>
    <span class="kt">uint32_t</span> <span class="n">new_field</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">flags</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<p><strong>Rust解决方案</strong>: 显式版本控制和<code class="language-plaintext highlighter-rouge">#[repr(C)]</code></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Rust - ABI布局是显式的并经过检查</span>
<span class="nd">#[repr(C)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">binder_transaction_data</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">cookie</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">code</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="c1">// 不能在这里添加字段，除非显式版本升级</span>
    <span class="k">pub</span> <span class="n">flags</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
<span class="p">}</span>

<span class="c1">// 编译时大小检查</span>
<span class="k">const</span> <span class="n">_</span><span class="p">:</span> <span class="p">()</span> <span class="o">=</span> <span class="nd">assert!</span><span class="p">(</span>
    <span class="nn">core</span><span class="p">::</span><span class="nn">mem</span><span class="p">::</span><span class="nn">size_of</span><span class="p">::</span><span class="o">&lt;</span><span class="n">binder_transaction_data</span><span class="o">&gt;</span><span class="p">()</span> <span class="o">==</span> <span class="mi">48</span>
<span class="p">);</span>
</code></pre></div></div>

<h3 id="rust的reprc保证">Rust的<code class="language-plaintext highlighter-rouge">#[repr(C)]</code>保证</h3>

<p>从Rust语言规范：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[repr(C)]</span>
<span class="k">struct</span> <span class="n">UserspaceFacingStruct</span> <span class="p">{</span>
    <span class="n">field1</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="n">field2</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>保证</strong>:</p>
<ul>
  <li>与C结构相同的布局</li>
  <li>相同的填充规则</li>
  <li>相同的对齐</li>
  <li>相同的大小</li>
  <li>跨Rust编译器版本稳定</li>
</ul>

<p><strong>这是语言级别的保证</strong>，不仅仅是约定。</p>

<h3 id="abi稳定性rust-vs-c对比">ABI稳定性：Rust vs C对比</h3>

<table>
  <thead>
    <tr>
      <th>方面</th>
      <th>C</th>
      <th>Rust</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>布局控制</strong></td>
      <td>隐式，编译器依赖</td>
      <td><code class="language-plaintext highlighter-rouge">#[repr(C)]</code>显式</td>
    </tr>
    <tr>
      <td><strong>填充保留</strong></td>
      <td>手动，易出错</td>
      <td><code class="language-plaintext highlighter-rouge">MaybeUninit</code>自动</td>
    </tr>
    <tr>
      <td><strong>大小验证</strong></td>
      <td>手动<code class="language-plaintext highlighter-rouge">BUILD_BUG_ON</code></td>
      <td><code class="language-plaintext highlighter-rouge">const _: assert!(size == X)</code></td>
    </tr>
    <tr>
      <td><strong>破坏性更改</strong></td>
      <td>静默，运行时失败</td>
      <td>编译错误</td>
    </tr>
    <tr>
      <td><strong>版本控制</strong></td>
      <td>手动，按约定</td>
      <td>可由类型系统强制</td>
    </tr>
    <tr>
      <td><strong>二进制兼容性</strong></td>
      <td>信任开发者</td>
      <td>编译器验证</td>
    </tr>
  </tbody>
</table>

<h3 id="rust会提供关键的用户空间abi吗">Rust会提供关键的用户空间ABI吗？</h3>

<p><strong>生产环境部署（主线内核）:</strong></p>

<ol>
  <li><strong>GPU驱动</strong> (Nova): 为Nvidia GPU提供DRM用户空间ABI（树内13个文件）</li>
  <li><strong>网络PHY驱动</strong>: ethtool/netlink ABI (ax88796b, qt2025)</li>
  <li><strong>块设备</strong>: rnull驱动，提供标准ioctl ABI</li>
  <li><strong>CPU频率</strong>: rcpufreq_dt，提供sysfs接口</li>
</ol>

<p><strong>参考实现（树外）:</strong></p>

<ol>
  <li><strong>Android Binder</strong> (IPC): Rust重写展示ABI兼容性（尚未进入主线）</li>
</ol>

<p><strong>即将推出</strong> (基于当前开发):</p>

<ol>
  <li><strong>文件系统</strong>: VFS操作，挂载选项</li>
  <li><strong>网络协议</strong>: Socket选项，数据包格式</li>
  <li><strong>更多设备驱动</strong>: 扩展硬件支持</li>
</ol>

<h3 id="关键策略与语言无关的abi">关键策略：与语言无关的ABI</h3>

<p><strong>关键洞察</strong>: 内核的ABI稳定性策略是<strong>与语言无关的</strong>。</p>

<p>来自Linus Torvalds（从各种LKML帖子总结）：</p>

<blockquote>
  <p>“我不在乎你用C、Rust还是汇编编写。如果你破坏了用户空间，你就破坏了内核。”</p>
</blockquote>

<p><strong>实践中</strong>:</p>
<ul>
  <li>Rust驱动通过bindgen使用<strong>与C相同的UAPI头文件</strong></li>
  <li>相同的ioctl编号，相同的结构布局，相同的语义</li>
  <li>用户空间<strong>无法分辨</strong>驱动是C还是Rust</li>
  <li>ABI破坏在两种语言中<strong>同样不可接受</strong></li>
</ul>

<p><strong>答案</strong>: 是的，Rust<strong>将会并且已经</strong>被用于需要ABI稳定性的用户空间功能。</p>

<h2 id="当前范围外围驱动而非内核核心">当前范围：外围驱动，而非内核核心</h2>

<p><strong>重要澄清</strong>: 截至2026年初，Linux内核中的Rust<strong>仅限于外围区域</strong> - 设备驱动和Android特定组件。<strong>没有核心内核子系统被用Rust重写。</strong></p>

<h3 id="-rust代码存在的位置">✅ Rust代码存在的位置</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>drivers/                    # 外围驱动层
├── gpu/drm/nova/          # GPU驱动 (Nvidia, 13个文件, ~1,200行)
├── net/phy/               # 网络PHY驱动 (2个文件, ~237行)
├── block/rnull.rs         # 块设备示例 (80行)
├── cpufreq/rcpufreq_dt.rs # CPU频率管理 (227行)
└── gpu/drm/drm_panic_qr.rs # DRM panic QR码 (996行)

rust/kernel/               # 抽象层 (101个文件, 13,500行)
├── sync/                  # 同步原语的Rust绑定
├── mm/                    # 内存函数的Rust绑定
├── fs/                    # 文件系统的Rust绑定
└── net/                   # 网络的Rust绑定
</code></pre></div></div>

<p><strong>关键点</strong>: <code class="language-plaintext highlighter-rouge">rust/kernel/</code>目录提供<strong>抽象</strong>（围绕C API的安全包装器），而不是核心功能的<strong>实现</strong>。</p>

<h3 id="-仍然100-c的部分核心内核">❌ 仍然100% C的部分（核心内核）</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mm/                        # 内存管理核心
├── 153个文件, 128个C文件
├── page_alloc.c          # 页面分配器 (9,000+ 行)
├── slab.c                # Slab分配器 (4,000+ 行)
├── vmalloc.c             # 虚拟内存 (3,500+ 行)
└── kasan_test_rust.rs    # ⚠️ 唯一的Rust文件（仅仅是测试！）

kernel/sched/             # 进程调度器
├── 46个文件, 33个C文件
├── core.c                # 调度器核心 (11,000+ 行)
└── 0个Rust文件

fs/                       # VFS核心
├── 数百个C文件
├── namei.c               # 路径查找 (5,000+ 行)
├── inode.c               # Inode管理 (2,000+ 行)
└── 0个Rust文件（仅驱动）

net/core/                 # 网络协议栈核心
kernel/entry/             # 系统调用入口点
arch/x86/kernel/          # 架构特定代码
</code></pre></div></div>

<h3 id="为什么这很重要">为什么这很重要</h3>

<p>这种分布<strong>不是技术限制</strong>，而是<strong>deliberate战略</strong>：</p>

<ol>
  <li><strong>风险管理</strong>: 驱动故障是局部的；核心子系统bug会导致系统崩溃</li>
  <li><strong>建立信任</strong>: 先在低风险区域证明Rust的价值</li>
  <li><strong>社区接受</strong>: 渐进式采用让内核维护者有时间适应</li>
  <li><strong>工具成熟</strong>: 构建测试基础设施和调试工具</li>
</ol>

<h3 id="采用时间线当前轨迹">采用时间线（当前轨迹）</h3>

<p><strong>第1阶段 (2022-2026)</strong>: ✅ <strong>已完成</strong></p>
<ul>
  <li>设备驱动和Android组件</li>
  <li>抽象层基础设施</li>
  <li>构建系统集成</li>
</ul>

<p><strong>第2阶段 (2026-2028)</strong>: 🔄 <strong>进行中</strong></p>
<ul>
  <li>更多设备驱动（扩展硬件支持）</li>
  <li>文件系统驱动（实验性）</li>
  <li>网络驱动扩展</li>
</ul>

<p><strong>第3阶段 (2028-2030+)</strong>: 🔮 <strong>高度推测</strong></p>
<ul>
  <li>核心子系统采用（mm、调度器、VFS）</li>
  <li><strong>这可能永远不会发生</strong> - 需要巨大的社区共识</li>
  <li>核心重写没有官方路线图</li>
</ul>

<h3 id="现实检验">现实检验</h3>

<p><strong>问题</strong>: “Rust会替换内核核心中的C吗？”</p>

<p><strong>答案</strong>: 未知且在近期（5-10年）不太可能。当前证据显示：</p>
<ul>
  <li>Rust在<strong>驱动</strong>中取得成功（已证明价值）</li>
  <li>核心子系统拥有<strong>数十年经过实战检验的C代码</strong></li>
  <li>重写核心 = <strong>巨大风险</strong>，收益不明确</li>
  <li>社区重点是<strong>新驱动</strong>，而非重写现有核心</li>
</ul>

<p><strong>结论</strong>: Linux中的Rust目前是一种<strong>驱动开发语言</strong>，而不是<strong>内核核心语言</strong>。这可能会改变，但不会很快。</p>

<h2 id="实际影响">实际影响</h2>

<h3 id="对rust内核开发者">对Rust内核开发者</h3>

<p><strong>要做:</strong></p>
<ul>
  <li>✅ 对所有用户空间结构使用<code class="language-plaintext highlighter-rouge">#[repr(C)]</code></li>
  <li>✅ 对用户空间类型使用<code class="language-plaintext highlighter-rouge">uapi</code> crate</li>
  <li>✅ 添加大小/布局断言</li>
  <li>✅ 如需要用<code class="language-plaintext highlighter-rouge">MaybeUninit</code>保留填充</li>
  <li>✅ 以与C驱动相同的方式记录ABI</li>
</ul>

<p><strong>不要做:</strong></p>
<ul>
  <li>❌ 未经版本升级更改用户空间可见类型</li>
  <li>❌ 假设Rust的布局足够（使用<code class="language-plaintext highlighter-rouge">#[repr(C)]</code>）</li>
  <li>❌ 即使为了”更好”的设计也不要破坏兼容性</li>
  <li>❌ 在UAPI中依赖Rust特定类型</li>
</ul>

<h3 id="对用户空间开发者">对用户空间开发者</h3>

<p><strong>好消息</strong>: 什么都不变！</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 用户空间C代码（不变）</span>
<span class="kt">int</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">open</span><span class="p">(</span><span class="s">"/dev/binder"</span><span class="p">,</span> <span class="n">O_RDWR</span><span class="p">);</span>
<span class="k">struct</span> <span class="n">binder_write_read</span> <span class="n">bwr</span> <span class="o">=</span> <span class="p">{</span> <span class="p">...</span> <span class="p">};</span>
<span class="n">ioctl</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">BINDER_WRITE_READ</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">bwr</span><span class="p">);</span>
</code></pre></div></div>

<p>无论内核驱动是C还是Rust，<strong>这段代码工作完全相同</strong>。</p>

<h2 id="常见误解">常见误解</h2>

<h3 id="误解1rust的abi不稳定所以不能用于内核接口">误解1：”Rust的ABI不稳定，所以不能用于内核接口”</h3>

<p><strong>现实</strong>:</p>
<ul>
  <li>Rust crate之间的<em>内部</em>ABI不稳定</li>
  <li>Rust的<code class="language-plaintext highlighter-rouge">#[repr(C)]</code> ABI <strong>是稳定的</strong>，与C完全匹配</li>
  <li>内核对所有用户空间接口使用<code class="language-plaintext highlighter-rouge">#[repr(C)]</code></li>
</ul>

<h3 id="误解2rust添加了需要维护的新abi">误解2：”Rust添加了需要维护的新ABI”</h3>

<p><strong>现实</strong>:</p>
<ul>
  <li>Rust使用<strong>与C相同的UAPI头文件</strong>（通过bindgen）</li>
  <li>没有新ABI，只是不同语言实现相同ABI</li>
  <li>用户空间看不到区别</li>
</ul>

<h3 id="误解3rust内部不稳定性影响用户空间">误解3：”Rust内部不稳定性影响用户空间”</h3>

<p><strong>现实</strong>:</p>
<ul>
  <li>Rust的<code class="language-plaintext highlighter-rouge">rust/kernel</code>抽象可以自由更改（内部API）</li>
  <li>面向用户空间的ABI<strong>不能更改</strong>（与C规则相同）</li>
  <li>这些是分开的关注点</li>
</ul>

<h3 id="误解4因为rust模块必须重新编译">误解4：”因为Rust模块必须重新编译”</h3>

<p><strong>现实</strong>:</p>
<ul>
  <li>内核模块<strong>一直</strong>需要在版本之间重新编译</li>
  <li>对于<strong>C模块</strong>也是如此</li>
  <li>Rust不改变这一策略</li>
</ul>

<h2 id="结论">结论</h2>

<p><strong>发现总结:</strong></p>

<ol>
  <li>
    <p>✅ <strong>Rust通过<code class="language-plaintext highlighter-rouge">uapi</code> crate、ioctl处理器、设备节点、sysfs等提供用户空间接口</strong></p>
  </li>
  <li>
    <p>❌ <strong>内核内部ABI不稳定</strong> - 模块必须为每个内核版本重新编译（与C相同）</p>
  </li>
  <li>
    <p>✅ <strong>用户空间ABI是稳定的</strong> - 永不破坏（C和Rust规则相同）</p>
  </li>
  <li>
    <p>✅ <strong>Rust已经在生产环境提供用户空间ABI</strong> - GPU驱动（Nova），网络PHY驱动，块设备，CPU频率驱动（均在主线）</p>
  </li>
  <li>
    <p>⚠️ <strong>Rust目前仅在外围</strong> - 仅设备驱动；核心内核（mm、调度器、VFS）仍然100% C</p>
  </li>
</ol>

<p><strong>关键洞察</strong>:</p>

<ol>
  <li>内核的ABI稳定性策略<strong>与实现语言正交</strong>。Rust驱动必须遵循与C驱动相同的规则：
    <ul>
      <li>内部API可以随时更改</li>
      <li>用户空间ABI是神圣和不可变的</li>
    </ul>
  </li>
  <li>Rust的当前范围是<strong>deliberate和战略性的</strong> - 在考虑核心子系统之前，先在低风险驱动中证明价值。</li>
</ol>

<p><strong>Rust的优势</strong>: 通过<code class="language-plaintext highlighter-rouge">#[repr(C)]</code>、大小断言和类型安全更好地编译时验证ABI兼容性，减少意外的ABI破坏。</p>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p><a href="https://www.kernel.org/doc/Documentation/process/stable-api-nonsense.rst">Linux Kernel Stable API Nonsense</a> - Greg Kroah-Hartman’s explanation of why internal kernel API is unstable <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:1:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:2">
      <p><a href="https://docs.kernel.org/admin-guide/abi.html">Linux ABI description</a> - Official kernel documentation on ABI stability levels <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:3">
      <p><a href="https://github.com/torvalds/linux/blob/master/Documentation/ABI/README">ABI README</a> - Documentation of ABI stability categories <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>阿男</name></author><summary type="html"><![CDATA[本文为英文存档，已不再主推；本站后续内容以中文技术长文为主。 配套视频见 B站频道。]]></summary></entry></feed>