<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Posts on 大飞的博客</title>
    <link>https://www.dafei.me/posts/</link>
    <description>Recent content in Posts on 大飞的博客</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <lastBuildDate>Wed, 03 Jun 2026 10:00:00 +0000</lastBuildDate>
    <atom:link href="https://www.dafei.me/posts/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>LLMLingua：用小模型压缩 Prompt，省 95% Token</title>
      <link>https://www.dafei.me/posts/llmlingua-prompt-compression/</link>
      <pubDate>Wed, 03 Jun 2026 10:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/llmlingua-prompt-compression/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;调研范围&lt;/strong&gt;：LLMLingua 系列（v1 / LongLLMLingua / LLMLingua-2）项目背景、技术演进、性能数据、横向对比、工程落地&lt;br&gt;
&lt;strong&gt;主要文献&lt;/strong&gt;：arXiv:2310.05736（EMNLP 2023）、arXiv:2310.06839（ACL 2024）、arXiv:2403.12968（ACL Findings 2024）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一项目背景与核心原理&#34;&gt;一、项目背景与核心原理&lt;/h2&gt;
&lt;h3 id=&#34;11-动机prompt-膨胀是-llm-落地的成本瓶颈&#34;&gt;1.1 动机：Prompt 膨胀是 LLM 落地的成本瓶颈&lt;/h3&gt;
&lt;p&gt;CoT、ICL、RAG 等技术的普及使 prompt 长度从几百 token 迅速膨胀到数万 token。按 GPT-4 计费标准，一次请求仅 prompt 成本就可能达 $0.3–$1+。企业级应用中，&lt;strong&gt;80% 的 token 开销往往集中在冗余的上下文内容里&lt;/strong&gt;。&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;技术范式&lt;/th&gt;
          &lt;th&gt;对 Prompt 的影响&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;In-Context Learning (ICL)&lt;/td&gt;
          &lt;td&gt;注入多个 few-shot 示例，每个示例 300–600 token&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Chain-of-Thought (CoT)&lt;/td&gt;
          &lt;td&gt;完整推理链进一步增大示例体积&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;RAG（检索增强生成）&lt;/td&gt;
          &lt;td&gt;每次请求注入若干检索文档段落&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Agent / 工具调用&lt;/td&gt;
          &lt;td&gt;System prompt + 对话历史 + 工具描述累积&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;三类影响&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;成本&lt;/strong&gt;：Token 按量计费，压缩 20× 可节省约 95% 的 input token 费用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;延迟&lt;/strong&gt;：prefill 阶段与输入长度正相关，长 prompt 直接拉高首 token 延迟（TTFT）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;准确率&lt;/strong&gt;：超长上下文有 &amp;ldquo;lost-in-the-middle&amp;rdquo; 问题——LLM 对中间位置的信息关注度显著低于首尾&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;12-核心洞察用小模型-ppl-代理信息量&#34;&gt;1.2 核心洞察：用小模型 PPL 代理信息量&lt;/h3&gt;
&lt;p&gt;LLMLingua 的根本思路是：&lt;strong&gt;用廉价的小语言模型（GPT-2/LLaMA-7B）计算 token 的困惑度（Perplexity），以此作为信息重要性的代理&lt;/strong&gt;。&lt;/p&gt;</description>
    </item>
    <item>
      <title>自制 x86-64 内核（46）：mmap 文件映射</title>
      <link>https://www.dafei.me/posts/os-46-mmap/</link>
      <pubDate>Wed, 03 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-46-mmap/</guid>
      <description>&lt;p&gt;上一章实现了 TCP 服务端，这章来实现一个经典的内存管理功能：&lt;strong&gt;mmap 文件映射&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;什么是-mmap-文件映射&#34;&gt;什么是 mmap 文件映射&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;mmap&lt;/code&gt; 把文件的一段内容映射到进程的虚拟地址空间。映射后可以像读内存一样读文件——不需要 &lt;code&gt;read()&lt;/code&gt; 系统调用，直接解引用指针。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; fd &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;open&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/bin/busybox&amp;#34;&lt;/span&gt;, O_RDONLY);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;p &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;mmap&lt;/span&gt;(NULL, &lt;span style=&#34;color:#ae81ff&#34;&gt;4096&lt;/span&gt;, PROT_READ, MAP_PRIVATE, fd, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// p[0..3] 就是文件的前 4 字节（ELF magic: 7f 45 4c 46）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;关键设计vma-和-lazy-分配&#34;&gt;关键设计：VMA 和 Lazy 分配&lt;/h2&gt;
&lt;p&gt;mmap 不会立刻分配物理内存，而是&lt;strong&gt;登记一个 VMA（虚拟内存区域）&lt;/strong&gt;，等到进程真正访问这段地址时，才通过&lt;strong&gt;缺页异常&lt;/strong&gt;按需读取文件内容并建立页表映射。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;mmap(fd) → 注册 VMA（vaddr, len, inum, offset） → 返回虚拟地址
                        ↓ 进程访问该地址
              #PF not-present → 找到 VMA → 分配物理页
                              → ext2_read(inum, offset, page)
                              → 建立页表 → 重试指令
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这就是操作系统课上说的&amp;quot;按需分页&amp;quot;（demand paging）。&lt;/p&gt;
&lt;h2 id=&#34;实现步骤&#34;&gt;实现步骤&lt;/h2&gt;
&lt;h3 id=&#34;1-定义-vma-结构&#34;&gt;1. 定义 VMA 结构&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;process_t&lt;/code&gt; 中加入 VMA 数组：&lt;/p&gt;</description>
    </item>
    <item>
      <title>自制 x86-64 内核（47）：动态链接器基础</title>
      <link>https://www.dafei.me/posts/os-47-dynlink/</link>
      <pubDate>Wed, 03 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-47-dynlink/</guid>
      <description>&lt;p&gt;这一章实现了对 PIE（Position-Independent Executable）程序的支持，也就是不加 &lt;code&gt;-static&lt;/code&gt; 编译出来的&amp;quot;动态&amp;quot;程序。&lt;/p&gt;
&lt;h2 id=&#34;问题pie-和静态-elf-的区别&#34;&gt;问题：PIE 和静态 ELF 的区别&lt;/h2&gt;
&lt;p&gt;用 musl-gcc 编译一个简单的 hello world：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 静态（之前一直用的）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;musl-gcc -static -O2 -o hello hello.c
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 动态（PIE）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;musl-gcc -O2 -o hello_pie hello.c
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;file&lt;/code&gt; 查看：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;hello:     ELF 64-bit LSB executable, x86-64, statically linked
hello_pie: ELF 64-bit LSB pie executable, x86-64, dynamically linked
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;区别在于：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;属性&lt;/th&gt;
          &lt;th&gt;静态 (ET_EXEC)&lt;/th&gt;
          &lt;th&gt;PIE (ET_DYN)&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;e_type&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;虚地址&lt;/td&gt;
          &lt;td&gt;固定（如 0x400000）&lt;/td&gt;
          &lt;td&gt;从 0 开始，需加载时指定 base&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;重定位&lt;/td&gt;
          &lt;td&gt;无&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;.rela.dyn&lt;/code&gt; 表&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;外部依赖&lt;/td&gt;
          &lt;td&gt;无&lt;/td&gt;
          &lt;td&gt;通常有（但 musl PIE 已内嵌 libc）&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;为什么-musl-pie-没有真正的动态链接&#34;&gt;为什么 musl PIE 没有&amp;quot;真正的&amp;quot;动态链接？&lt;/h2&gt;
&lt;p&gt;musl-gcc 默认把所有 libc 代码&lt;strong&gt;静态链接&lt;/strong&gt;进去，只是编译为 PIE 格式（支持 ASLR）。所以运行时：&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（四十五）：TCP 服务端——bind/listen/accept</title>
      <link>https://www.dafei.me/posts/os-45-tcp-server/</link>
      <pubDate>Tue, 02 Jun 2026 11:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-45-tcp-server/</guid>
      <description>&lt;p&gt;前面几章都是做 TCP 客户端——发 SYN、连接、发请求、收响应。这章反过来，让内核能&lt;strong&gt;作为 TCP 服务端&lt;/strong&gt;：监听端口，接受连接，响应请求。&lt;/p&gt;
&lt;h2 id=&#34;客户端-vs-服务端的区别&#34;&gt;客户端 vs 服务端的区别&lt;/h2&gt;
&lt;p&gt;客户端：主动发 SYN，等待 SYN-ACK。&lt;/p&gt;
&lt;p&gt;服务端：被动等待 SYN，收到后发 SYN-ACK，等 ACK 完成三次握手，然后通过 &lt;code&gt;accept()&lt;/code&gt; 把新连接交给应用层。&lt;/p&gt;
&lt;p&gt;关键区别在于：服务端有两种 socket 角色——&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;角色&lt;/th&gt;
          &lt;th&gt;职责&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;监听 socket（listen fd）&lt;/td&gt;
          &lt;td&gt;绑定端口，等待连接请求&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;连接 socket（accept fd）&lt;/td&gt;
          &lt;td&gt;每个客户端连接对应一个，负责实际数据收发&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;三步接口&#34;&gt;三步接口&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;bind&lt;/span&gt;(fd, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;sa, len)    &lt;span style=&#34;color:#75715e&#34;&gt;// 绑定本地端口
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;listen&lt;/span&gt;(fd, backlog)   &lt;span style=&#34;color:#75715e&#34;&gt;// 进入监听状态
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;accept&lt;/span&gt;(fd, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;sa, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;len) &lt;span style=&#34;color:#75715e&#34;&gt;// 阻塞，等到有连接，返回新 fd
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;数据结构&#34;&gt;数据结构&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;tcp_sock_t&lt;/code&gt; 加了几个字段：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;  is_listener;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;  accept_queue[&lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;];   &lt;span style=&#34;color:#75715e&#34;&gt;// 存已完成三次握手的连接 index
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; accept_head, accept_tail;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;还新增了两个状态：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TCP_LISTEN&lt;/code&gt;：监听中，等待 SYN&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TCP_SYN_RECV&lt;/code&gt;：收到 SYN 已回 SYN-ACK，等最终 ACK&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;核心流程&#34;&gt;核心流程&lt;/h2&gt;
&lt;h3 id=&#34;1-收到-syn&#34;&gt;1. 收到 SYN&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;tcp_handle()&lt;/code&gt; 里单独处理 SYN：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ((tcp&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;flags &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; TCP_SYN) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;(tcp&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;flags &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; TCP_ACK)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 找到监听该端口的 socket
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 分配新连接 slot
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ns&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;state       &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; TCP_SYN_RECV;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ns&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;local_port  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; dport;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ns&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;remote_port &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sport;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ns&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;remote_ip   &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; src_ip;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ns&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ack         &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ntohl&lt;/span&gt;(tcp&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;seq) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;  &lt;span style=&#34;color:#75715e&#34;&gt;// 下次要 ACK 的序号
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ns&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;syn_seq     &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0xABCD1234&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ns&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;snd_una &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ns&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;snd_nxt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ns&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;syn_seq;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;tcp_send_seg&lt;/span&gt;(ns, ns&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;syn_seq, TCP_SYN &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; TCP_ACK, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;2-收到最终-ack完成三次握手&#34;&gt;2. 收到最终 ACK（完成三次握手）&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (s&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;state &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; TCP_SYN_RECV &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; (tcp&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;flags &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; TCP_ACK)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (ack_val &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; s&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;syn_seq &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        s&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;snd_una &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; s&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;snd_nxt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ack_val;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        s&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;state &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; TCP_ESTABLISHED;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 放入监听 socket 的 accept queue
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ls&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;accept_queue[ls&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;accept_tail &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; i;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ls&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;accept_tail&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;3-accept-取连接&#34;&gt;3. accept() 取连接&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tcp_accept&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; s, ...) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;tcp_sock_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;ls &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;tcp_socks[s];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (ls&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;accept_head &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; ls&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;accept_tail) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        sti; &lt;span style=&#34;color:#a6e22e&#34;&gt;net_poll&lt;/span&gt;(); cli;   &lt;span style=&#34;color:#75715e&#34;&gt;// 阻塞等待
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; ni &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ls&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;accept_queue[ls&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;accept_head&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; ni;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;测试&#34;&gt;测试&lt;/h2&gt;
&lt;p&gt;写了一个简单的 HTTP 服务端程序：&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（四十四）：UDP Socket &#43; DNS 解析</title>
      <link>https://www.dafei.me/posts/os-44-udp-dns/</link>
      <pubDate>Tue, 02 Jun 2026 10:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-44-udp-dns/</guid>
      <description>&lt;p&gt;上一章（四十三）把 TCP 重传做好了。这一章加两个新功能：&lt;strong&gt;UDP socket 用户空间接口&lt;/strong&gt; 和 &lt;strong&gt;DNS 解析 syscall&lt;/strong&gt;，让用户程序可以用域名来连接服务器。&lt;/p&gt;
&lt;h2 id=&#34;目标&#34;&gt;目标&lt;/h2&gt;
&lt;p&gt;内核里其实早就有 &lt;code&gt;udp_send&lt;/code&gt; 和 &lt;code&gt;dns_resolve&lt;/code&gt; 这两个函数了，但用户程序用不到，因为没有对应的 syscall。这章要做的就是把这两个内核能力&amp;quot;打通&amp;quot;到用户空间：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;socket(AF_INET, SOCK_DGRAM, 0)&lt;/code&gt; → 分配一个 UDP socket，返回 fd 200+&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sendto&lt;/code&gt; / &lt;code&gt;recvfrom&lt;/code&gt; → 通过 UDP socket 收发数据&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SYS_DNS_RESOLVE&lt;/code&gt;（自定义 syscall 500）→ 通过域名查 IP&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;udp-socket-设计&#34;&gt;UDP Socket 设计&lt;/h2&gt;
&lt;p&gt;UDP 比 TCP 简单很多：无连接、无状态机、无重传。核心是一个&lt;strong&gt;接收队列&lt;/strong&gt;：内核收到 UDP 包时，把它放进对应 socket 的队列，用户程序再来取。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; src_ip;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint16_t&lt;/span&gt; src_port;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint16_t&lt;/span&gt; len;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt;  data[&lt;span style=&#34;color:#ae81ff&#34;&gt;512&lt;/span&gt;];      &lt;span style=&#34;color:#75715e&#34;&gt;// 每个包最多 512 字节
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;udp_pkt_entry_t&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;              used;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint16_t&lt;/span&gt;         local_port;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;udp_pkt_entry_t&lt;/span&gt;  queue[&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;];  &lt;span style=&#34;color:#75715e&#34;&gt;// 最多暂存 4 个包
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt;         qhead, qtail;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;udp_sock_t&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;队列用无限增长的 &lt;code&gt;qhead/qtail&lt;/code&gt; 计数（不是环形下标），访问时用 &lt;code&gt;% UDP_PKT_MAX&lt;/code&gt;，满了就丢包：&lt;/p&gt;</description>
    </item>
    <item>
      <title>源码分析（一）：Netflix Headroom 是怎么把 LLM 账单砍掉一半的</title>
      <link>https://www.dafei.me/posts/oss-01-headroom/</link>
      <pubDate>Tue, 02 Jun 2026 10:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/oss-01-headroom/</guid>
      <description>&lt;p&gt;最近看到一个叫 &lt;strong&gt;Headroom&lt;/strong&gt; 的项目，Netflix 高级工程师 Tejas Chopra 个人开源的，号称能帮你把发给 LLM 的 token 减少 30-70%，而且不丢信息。&lt;/p&gt;
&lt;p&gt;我把源码读了一遍，发现里面有几个设计很有意思，记下来。&lt;/p&gt;
&lt;p&gt;项目地址：https://github.com/chopratejas/headroom&lt;/p&gt;
&lt;p&gt;本文分析的主要源文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/chopratejas/headroom/blob/main/headroom/transforms/content_router.py&#34;&gt;&lt;code&gt;headroom/transforms/content_router.py&lt;/code&gt;&lt;/a&gt; — 内容识别与路由&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/chopratejas/headroom/blob/main/headroom/transforms/content_detector.py&#34;&gt;&lt;code&gt;headroom/transforms/content_detector.py&lt;/code&gt;&lt;/a&gt; — 内容类型检测&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/chopratejas/headroom/tree/main/crates/headroom-core/src/transforms/smart_crusher&#34;&gt;&lt;code&gt;crates/headroom-core/src/transforms/smart_crusher/&lt;/code&gt;&lt;/a&gt; — SmartCrusher Rust 实现&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/chopratejas/headroom/blob/main/crates/headroom-core/src/transforms/log_compressor.rs&#34;&gt;&lt;code&gt;crates/headroom-core/src/transforms/log_compressor.rs&lt;/code&gt;&lt;/a&gt; — 日志压缩 Rust 实现&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/chopratejas/headroom/blob/main/headroom/transforms/cache_aligner.py&#34;&gt;&lt;code&gt;headroom/transforms/cache_aligner.py&lt;/code&gt;&lt;/a&gt; — CacheAligner&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/chopratejas/headroom/blob/main/headroom/ccr/tool_injection.py&#34;&gt;&lt;code&gt;headroom/ccr/tool_injection.py&lt;/code&gt;&lt;/a&gt; — CCR 工具注入&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;它解决的是什么问题&#34;&gt;它解决的是什么问题&lt;/h2&gt;
&lt;p&gt;你在用 Claude 或 GPT 做 agent 的时候，工具调用（tool call）的返回结果会吃掉大量 token。&lt;/p&gt;
&lt;p&gt;比如你让 agent 查数据库，返回了 500 条记录，每条有 15 个字段。但其中 12 个字段在所有记录里都是完全相同的值，真正有用的只有 3 个字段。你把 500 × 15 的数据全塞给 LLM，它实际只需要 500 × 3。&lt;/p&gt;
&lt;p&gt;这就是浪费。Headroom 做的事，就是在你把数据发给 LLM 之前，先把这些废话压掉。&lt;/p&gt;</description>
    </item>
    <item>
      <title>tclaw（五）：接入飞书、语音、图片</title>
      <link>https://www.dafei.me/posts/tclaw-05-im-multimodal/</link>
      <pubDate>Tue, 02 Jun 2026 05:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/tclaw-05-im-multimodal/</guid>
      <description>&lt;h2 id=&#34;让-ai-融入日常&#34;&gt;让 AI 融入日常&lt;/h2&gt;
&lt;p&gt;桌面应用之外，tclaw 还支持通过飞书和微信直接和 agent 对话。&lt;/p&gt;
&lt;h2 id=&#34;飞书&#34;&gt;飞书&lt;/h2&gt;
&lt;p&gt;飞书有开放平台，可以创建自己的机器人。tclaw 接入之后，在飞书里和 tclaw 对话，就相当于在桌面应用里聊天，但可以用手机。&lt;/p&gt;
&lt;p&gt;支持的能力：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;发文字消息，agent 回复&lt;/li&gt;
&lt;li&gt;发图片给 agent，agent 可以分析图片内容&lt;/li&gt;
&lt;li&gt;agent 可以把生成的图片发回飞书&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;微信&#34;&gt;微信&lt;/h2&gt;
&lt;p&gt;微信这边不需要额外配置，在设置里开启微信，直接扫页面上的二维码登录，之后就可以在微信里和 agent 对话了。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;微信设置&#34; loading=&#34;lazy&#34; src=&#34;https://github.com/tongpengfei/tclaw-releases/raw/main/docs/screenshots/tclaw10_setting_wx.png&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;图片输入与截图&#34;&gt;图片输入与截图&lt;/h2&gt;
&lt;p&gt;桌面端也补全了多模态能力：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;聊天框可以直接粘贴或拖入图片发给 agent&lt;/li&gt;
&lt;li&gt;内置截图工具，可以截当前屏幕，裁剪、标注之后直接发给 agent&lt;/li&gt;
&lt;li&gt;agent 可以调用图片生成工具，生成的图片直接显示在对话里&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;图片生成&#34;&gt;图片生成&lt;/h2&gt;
&lt;p&gt;接了两个图片生成后端：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ollama&lt;/strong&gt;：本地跑，不花钱，速度慢一点&lt;/li&gt;
&lt;li&gt;外部 API：质量更好，按量计费&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;agent 可以在任务里直接调用图片生成，比如写完一篇文章顺手配一张封面图。&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;tclaw 提供 Mac、Linux、Windows 版下载，Mac 版支持最好，感兴趣可以去 &lt;a href=&#34;https://github.com/tongpengfei/tclaw-releases&#34;&gt;tclaw-releases&lt;/a&gt; 体验。&lt;/p&gt;
&lt;p&gt;也可以先玩玩 tclaw 用 AI 做的几个小游戏：&lt;a href=&#34;https://www.dafei.me/games/&#34;&gt;→ 点这里玩&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>tclaw（四）：从能用到好用</title>
      <link>https://www.dafei.me/posts/tclaw-04-polish/</link>
      <pubDate>Tue, 02 Jun 2026 04:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/tclaw-04-polish/</guid>
      <description>&lt;h2 id=&#34;能用只是开始&#34;&gt;能用只是开始&lt;/h2&gt;
&lt;p&gt;内核稳定之后，tclaw 已经能干活了。但&amp;quot;能用&amp;quot;和&amp;quot;好用&amp;quot;之间，有很长一段路。&lt;/p&gt;
&lt;p&gt;用着用着，各种不顺手的地方就冒出来了。agent 生成的文档没法方便地看；上下文长了之后 token 烧得很快；只有命令行，想给别人用很难；文件多了之后找东西很费劲……&lt;/p&gt;
&lt;p&gt;接下来花了差不多一个月的时间，一件一件地打磨。&lt;/p&gt;
&lt;h2 id=&#34;wails-桌面应用&#34;&gt;Wails 桌面应用&lt;/h2&gt;
&lt;p&gt;首先是 GUI。自己用命令行没问题，但如果想让更多人用，得有个像样的界面。&lt;/p&gt;
&lt;p&gt;调研了一圈，选了 &lt;strong&gt;Wails&lt;/strong&gt;——用 Go 写后端，前端是普通的 HTML/JS，打出来是一个原生桌面应用，体积小，对 Go 友好。最终打包出来的 Mac 应用只有几十 MB，不需要用户装任何运行时。&lt;/p&gt;
&lt;p&gt;同一套 HTML，既可以跑在 Wails 桌面里，也可以用浏览器直接访问。&lt;/p&gt;
&lt;h2 id=&#34;预览面板&#34;&gt;预览面板&lt;/h2&gt;
&lt;p&gt;agent 经常帮我生成文档、写 HTML 页面、画 mermaid 流程图。生成完要看效果，之前要自己去找文件打开，很麻烦。&lt;/p&gt;
&lt;p&gt;做了一个右侧预览面板：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Markdown 文件直接渲染，支持 mermaid 图表&lt;/li&gt;
&lt;li&gt;HTML 文件直接在面板里预览网页效果&lt;/li&gt;
&lt;li&gt;图片直接显示&lt;/li&gt;
&lt;li&gt;还可以在预览内容上框选区域，加标注，直接发给 AI 问问题&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&#34;预览面板&#34; loading=&#34;lazy&#34; src=&#34;https://github.com/tongpengfei/tclaw-releases/raw/main/docs/screenshots/tclaw03_stock01.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;HTML 预览&#34; loading=&#34;lazy&#34; src=&#34;https://github.com/tongpengfei/tclaw-releases/raw/main/docs/screenshots/tclaw07_code01.png&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;文件浏览器&#34;&gt;文件浏览器&lt;/h2&gt;
&lt;p&gt;本地文件多了之后，找东西很头疼。做了一个内置的文件浏览器，可以快速搜索文件，并且直接在里面预览：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Excel 打开直接显示表格&lt;/li&gt;
&lt;li&gt;JSON/XML 显示树形结构，可以展开折叠&lt;/li&gt;
&lt;li&gt;zip/tar 显示压缩包内容列表&lt;/li&gt;
&lt;li&gt;SVG 可以缩放拖拽&lt;/li&gt;
&lt;li&gt;PDF、图片、音视频都能预览&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;还加了文件格式转换和压缩功能，平时用得上的小工具基本都有了。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;文件浏览器&#34; loading=&#34;lazy&#34; src=&#34;https://github.com/tongpengfei/tclaw-releases/raw/main/docs/screenshots/tclaw04_file_browser01.png&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;上下文管理&#34;&gt;上下文管理&lt;/h2&gt;
&lt;p&gt;用 LLM 最头疼的问题之一是上下文窗口。对话长了，token 烧得很快，而且超出窗口就报错。&lt;/p&gt;
&lt;p&gt;做了几件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;自动剪裁&lt;/strong&gt;：上下文快满的时候，自动把旧的 tool result 压缩，保留关键内容&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;保留读取位置&lt;/strong&gt;：被剪裁的内容不是直接丢掉，而是记录偏移量，agent 需要的时候还能用 offset 继续读&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cache 优化&lt;/strong&gt;：加了多个 cache breakpoint，反复用的内容不重复计费&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;token 看板&lt;/strong&gt;：侧边栏可以实时看每轮的 token 消耗，一眼就知道上下文用了多少&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;vscode-风格布局&#34;&gt;VSCode 风格布局&lt;/h2&gt;
&lt;p&gt;界面改版了一次，改成 VSCode 风格的 Activity Bar + Side Panel 布局：&lt;/p&gt;</description>
    </item>
    <item>
      <title>tclaw（三）：重构——换一种方式让 Agent 协作</title>
      <link>https://www.dafei.me/posts/tclaw-03-refactor-subagent/</link>
      <pubDate>Tue, 02 Jun 2026 03:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/tclaw-03-refactor-subagent/</guid>
      <description>&lt;h2 id=&#34;问题出在哪里&#34;&gt;问题出在哪里&lt;/h2&gt;
&lt;p&gt;消息总线那套方案放弃之后，我坐下来想了一下，问题到底出在哪。&lt;/p&gt;
&lt;p&gt;根本原因是：消息总线是对等的，每个 bot 都可以给任何人发消息。这种自由度在人类团队里没问题，因为人有判断力，知道什么时候该说话、什么时候不该插嘴。但 LLM 不一样，它的&amp;quot;判断&amp;quot;是概率性的，今天遵守规则，明天又忘了。你加再多提示词约束它，它该乱的时候还是会乱。&lt;/p&gt;
&lt;p&gt;既然对等通信管不住，那就换成单向调用——上级调下级，下级只干自己的事，不主动找别人。&lt;/p&gt;
&lt;h2 id=&#34;新的设计&#34;&gt;新的设计&lt;/h2&gt;
&lt;p&gt;重构后的 tclaw，多 agent 协作的方式变成了这样：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;list_categories → list_agents → run_agent
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;用户或者顶层 agent 先用 &lt;code&gt;list_categories&lt;/code&gt; 看看现在有哪些领域，再用 &lt;code&gt;list_agents&lt;/code&gt; 列出某个领域下的所有 agent，然后用 &lt;code&gt;run_agent&lt;/code&gt; 调用具体的 agent 去做事。&lt;/p&gt;
&lt;p&gt;就像公司里的层级结构：你要找人做事，先找到对应的部门，再找到对应的人，直接下任务。这个人完成任务后把结果交回来，不会自己跑去找别的部门。&lt;/p&gt;
&lt;p&gt;这样一来，协作的控制权始终在调用方手里，被调用的 agent 只负责完成自己的任务，不能主动发起新的协作链路。&lt;/p&gt;
&lt;h2 id=&#34;pcclaw--tclaw&#34;&gt;pcclaw → tclaw&lt;/h2&gt;
&lt;p&gt;这次重构等于把整个架构重新来过，代码改动很大，干脆起了个新名字：&lt;strong&gt;tclaw&lt;/strong&gt;。t 是我名字的首字母。&lt;/p&gt;
&lt;p&gt;2026 年 4 月 21 日，tclaw 第一次提交。从 pcclaw 的第一次提交到现在，差不多过了五周。&lt;/p&gt;
&lt;p&gt;新架构稳定多了。agent 之间的协作变得可预测，出了问题也知道去哪里找。当然也不是完美的——有时候顶层 agent 该调用专业 agent 的时候，它自己就把事情做了，没有委托出去。但比起消息总线那套，已经好太多了。&lt;/p&gt;
&lt;h2 id=&#34;同期做的其他事&#34;&gt;同期做的其他事&lt;/h2&gt;
&lt;p&gt;重构完内核，顺手把周边也整理了一遍：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;把引擎抽成独立包，方便后续扩展&lt;/li&gt;
&lt;li&gt;加了 WebSocket server，可以用浏览器访问&lt;/li&gt;
&lt;li&gt;做了 WebGUI，有 tool call 展示&lt;/li&gt;
&lt;li&gt;加了 session 历史侧边栏&lt;/li&gt;
&lt;li&gt;做了 onboarding wizard，引导新用户配置&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从只能在命令行用的小工具，开始变成一个像样的产品。&lt;/p&gt;</description>
    </item>
    <item>
      <title>tclaw（二）：多 Agent 协作，以及那三周的噩梦</title>
      <link>https://www.dafei.me/posts/tclaw-02-multiagent-bus/</link>
      <pubDate>Tue, 02 Jun 2026 02:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/tclaw-02-multiagent-bus/</guid>
      <description>&lt;h2 id=&#34;一个-agent-不够用&#34;&gt;一个 agent 不够用&lt;/h2&gt;
&lt;p&gt;pcclaw 能跑起来之后，用起来还挺顺手的。让它帮我写代码、查文档、跑脚本，基本的事情都能做。&lt;/p&gt;
&lt;p&gt;我们平时需要专业的事交给专业的 agent，写代码有 coder，做设计有 designer，管任务有 captain。但问题是每次都要自己指定去找哪个 agent，用起来很麻烦。&lt;/p&gt;
&lt;p&gt;能不能让 agent 自己知道该找谁？&lt;/p&gt;
&lt;h2 id=&#34;消息总线的设计&#34;&gt;消息总线的设计&lt;/h2&gt;
&lt;p&gt;当时的想法是：做一个消息总线，每个 bot 都挂在上面，bot 之间通过发消息协作。想让某个 bot 做事，就发一条消息 @ 它。&lt;/p&gt;
&lt;p&gt;角色大概是这样分的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;captain&lt;/strong&gt;：大管家，负责整理思路、拆解任务、协调全局&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;coder&lt;/strong&gt;：负责开发，下面还有 architect、implement、review、tester 等子 agent&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;design&lt;/strong&gt;：负责设计文档&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;content&lt;/strong&gt;：负责内容创作&lt;/li&gt;
&lt;li&gt;……&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这套东西用起来有段时间感觉相当爽。我可以在开车的时候通过飞书和 captain 对话，让它帮我整理一个想法，captain 觉得需要技术方案就会 @ coder，coder 内部再分工，architect 写设计、implement 写代码、review 检查。我只需要说一句需求，后面的事 captain 去协调。&lt;/p&gt;
&lt;p&gt;有段时间我迷上了让 pcclaw 开发 pcclaw。感觉挺厉害，但其实还是 claude 更强些。不过自家的孩子，总是最好的。&lt;/p&gt;
&lt;p&gt;后来我给 pcclaw 取名叫&amp;quot;咖啡&amp;quot;——一边喝咖啡，咖啡自己就把活干完了的意思。&lt;/p&gt;
&lt;p&gt;那段时间每天早上 6 点起床弄咖啡，弄到 8 点再洗漱吃饭，时间卡得很紧。路上也闲不住，一边开车一边用飞书和咖啡聊，想到什么就说，让它做调研、出方案，顺手 PUA 它让它好好干活。&lt;/p&gt;
&lt;p&gt;那段时间晚上 1 点多才睡，第二天 4 点 20 就自然醒了——也不敢起来，怕一起来就睡不着了，毕竟还得上班。就躺着，脑子里转的全是给 pcclaw 加什么功能。&lt;/p&gt;
&lt;p&gt;直到有天早上开车时候睡着了。感觉睡了 2 秒钟，眼睛睁开车已经偏了，还好刚下高速，车速不快，但还是挺后怕的。然后就老实了，7:30 起床，保命要紧。&lt;/p&gt;</description>
    </item>
    <item>
      <title>tclaw（一）：过年玩 OpenClaw，玩着玩着就自己造了一个</title>
      <link>https://www.dafei.me/posts/tclaw-01-origin/</link>
      <pubDate>Tue, 02 Jun 2026 01:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/tclaw-01-origin/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;终端界面&#34; loading=&#34;lazy&#34; src=&#34;https://github.com/tongpengfei/tclaw-releases/raw/main/docs/screenshots/tclaw01_cli01.png&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;过年&#34;&gt;过年&lt;/h2&gt;
&lt;p&gt;2026 年春节，OpenClaw 火了。&lt;/p&gt;
&lt;p&gt;其实年前就已经有很多人在玩了，但我那时候忙，没时间搭理。正好赶上过年，难得有几天清闲，就把 OpenClaw 装上玩了一下。&lt;/p&gt;
&lt;p&gt;这一玩就收不住了。整个过年，家人吃饭我在玩，大年初几我也忘了，反正就是一直在玩。&lt;/p&gt;
&lt;p&gt;玩了一段时间，自然就开始好奇它内部是怎么工作的：这东西怎么知道该调用哪个工具？记忆是怎么管理的？提示词是怎么组织的？就让 AI 帮我分析了一下源码。&lt;/p&gt;
&lt;h2 id=&#34;发现-pi-mono&#34;&gt;发现 pi-mono&lt;/h2&gt;
&lt;p&gt;顺着 OpenClaw 的源码往里翻，发现它的内核依赖一个叫 &lt;strong&gt;pi-mono&lt;/strong&gt; 的东西。&lt;/p&gt;
&lt;p&gt;去看了 pi-mono 的代码，感觉设计很精干。它不是一个框架，只提供必要的东西：一个 Agent 循环、工具调用、会话管理，仅此而已。没有编排 DSL，没有 workflow 引擎，没有各种抽象层。核心思想就是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;把工具给大模型，让它自己决定怎么解决问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;然后看了一下 pi-mono 的作者背景，发现是游戏开发者出身。我自己以前也做过游戏，看到这里有点感慨——牛人到哪都是牛人。&lt;/p&gt;
&lt;h2 id=&#34;pcclaw-诞生&#34;&gt;pcclaw 诞生&lt;/h2&gt;
&lt;p&gt;年后大家都在玩 OpenClaw，不知道怎么抽风了，想用 Go 实现一下 pi-mono 内核，看看它到底怎么运行的。&lt;/p&gt;
&lt;p&gt;我平时写 Go，OpenClaw 是 JS 的，语言上的迁移借助 AI 不是太大的问题，主要还是要把原理搞清楚。2026 年 3 月 15 日，&lt;code&gt;pcclaw v0.0.1&lt;/code&gt; 第一次提交。&lt;/p&gt;
&lt;p&gt;pcclaw，tclaw 的前身。&lt;/p&gt;
&lt;p&gt;架构上参考了 OpenClaw 的分层思路：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;tool → command → skill → agent
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;tool&lt;/strong&gt;：最底层，封装基础操作（bash 执行、文件读写、网络请求等）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;command&lt;/strong&gt;：把 tool 组合成更高层的操作&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;skill&lt;/strong&gt;：可复用的能力单元，agent 通过调用 skill 完成任务&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;agent&lt;/strong&gt;：顶层，接收用户指令，通过 skill 和 tool 解决问题&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Provider 层支持多种协议——OpenAI compatible、Anthropic 等，可以接不同的模型。内核沿用 pi-mono 的思路：LLM 发现问题 → 调用 tool → 检查结果 → 继续或结束。就是一个循环，没有更多了。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（四十三）：TCP 重传 —— 丢包了也能传完</title>
      <link>https://www.dafei.me/posts/os-43-tcp-retransmit/</link>
      <pubDate>Mon, 01 Jun 2026 10:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-43-tcp-retransmit/</guid>
      <description>&lt;p&gt;上一章（四十二）修了一堆 busybox 相关的 bug，让 &lt;code&gt;ls&lt;/code&gt;/&lt;code&gt;exec&lt;/code&gt;/&lt;code&gt;wait&lt;/code&gt; 都能正常工作，&lt;code&gt;wget_test&lt;/code&gt; 也能跑通 HTTP。但那时的 TCP 实现有个隐患：&lt;strong&gt;一旦丢包，传输就会永远卡住&lt;/strong&gt;。这一章把重传机制做完整。&lt;/p&gt;
&lt;h2 id=&#34;原代码的问题&#34;&gt;原代码的问题&lt;/h2&gt;
&lt;p&gt;ch42 的 &lt;code&gt;tcp_send_raw&lt;/code&gt; 函数身兼数职：构造 TCP 包、发送、存入发送缓冲区、推进 &lt;code&gt;snd_nxt&lt;/code&gt;。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tcp_send_raw&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;tcp_sock_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;s, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; flags, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;data, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint16_t&lt;/span&gt; dlen) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ...&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;发包&lt;/span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (dlen) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ...&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;存&lt;/span&gt; sbuf...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        s&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;snd_nxt &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; dlen;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (flags &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; (TCP_SYN &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; TCP_FIN)) s&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;seq&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;;   &lt;span style=&#34;color:#75715e&#34;&gt;// 推进 seq
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (dlen) s&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;seq &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; dlen;                     &lt;span style=&#34;color:#75715e&#34;&gt;// 又推进一次！
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;问题一：&lt;code&gt;seq&lt;/code&gt; 和 &lt;code&gt;snd_nxt&lt;/code&gt; 是两个字段，但都在追踪&amp;quot;下一个要发的序号&amp;quot;，语义重复，而且 &lt;strong&gt;&lt;code&gt;seq&lt;/code&gt; 被推进了两次&lt;/strong&gt;（SYN/FIN 一次、数据一次）。&lt;/p&gt;
&lt;p&gt;问题二：重传时的代码非常丑陋：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; saved_seq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; s&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;seq;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; saved_nxt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; s&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;snd_nxt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;s&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;seq     &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; s&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;snd_una;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;s&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;snd_nxt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; s&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;snd_una;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;tcp_send_raw&lt;/span&gt;(s, TCP_PSH &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; TCP_ACK, rtbuf, chunk);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;s&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;snd_nxt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; saved_nxt;   &lt;span style=&#34;color:#75715e&#34;&gt;// 手动恢复
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;s&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;seq     &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; saved_seq;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这种&amp;quot;临时改状态再恢复&amp;quot;的方式容易出错，而且没有指数退避。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（四十二）：让 busybox 跑起来 —— 符号链接、fork/exec、调度器 cpu_pin bug</title>
      <link>https://www.dafei.me/posts/os-42-tcp-retransmit/</link>
      <pubDate>Mon, 01 Jun 2026 08:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-42-tcp-retransmit/</guid>
      <description>&lt;p&gt;上一章实现了 TCP/IP 栈，用户程序能发 HTTP 请求了。但验证时发现 &lt;code&gt;/bin/ls&lt;/code&gt; 完全没反应，&lt;code&gt;wget_test&lt;/code&gt; 也跑不起来。这一章记录排查过程——一共修了 6 个 bug，涉及 ext2 fast symlink、VFS 路径解析、内核栈溢出、缺失 syscall，以及两个调度器 &lt;code&gt;cpu_pin&lt;/code&gt; 问题。&lt;/p&gt;
&lt;p&gt;最终效果：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/ # ls
bin         etc         lost+found
/ # cd bin
/bin # ls
busybox    cp         kill       mv         pwd        sh         wget_test
cat        echo       ls         ps         rm         umount
/bin # wget_test
wget_test start
connecting...
connected!
request sent
HTTP/1.0 200 OK
...
DONE
/bin #
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id=&#34;背景busybox-的目录结构&#34;&gt;背景：busybox 的目录结构&lt;/h2&gt;
&lt;p&gt;Makefile 用这种方式制作 ext2 镜像：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-makefile&#34; data-lang=&#34;makefile&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;sudo&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;cp&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;busybox-x86_64&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/tmp/ext2mnt/bin/busybox&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;sudo&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;cp&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;busybox-x86_64&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/tmp/ext2mnt/bin/sh&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;# sh 是真实复制
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;cmd&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;ls&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;cat&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;pwd&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;...;&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;sudo&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;ln&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-sf&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/bin/busybox&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/tmp/ext2mnt/bin/$cmd&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# 其他命令是符号链接
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;/bin/sh&lt;/code&gt; 是真实文件，&lt;code&gt;/bin/ls&lt;/code&gt; 等是指向 &lt;code&gt;/bin/busybox&lt;/code&gt; 的&lt;strong&gt;符号链接&lt;/strong&gt;。&lt;/p&gt;</description>
    </item>
    <item>
      <title>用 tclaw 做了 6 个小游戏</title>
      <link>https://www.dafei.me/posts/tclaw-game-center/</link>
      <pubDate>Mon, 01 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/tclaw-game-center/</guid>
      <description>&lt;p&gt;最近在用自己写的 AI 工具 &lt;a href=&#34;https://github.com/tongpengfei/tclaw&#34;&gt;tclaw&lt;/a&gt; 做一些实验，让它帮我写了几个纯 HTML 小游戏。&lt;/p&gt;
&lt;p&gt;没想到效果挺不错的——贪吃蛇、俄罗斯方块、2048、Flappy Bird、太空侵略者、青蛙过马路，全部跑在浏览器里，不需要安装任何东西。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://www.dafei.me/games/&#34;&gt;→ 点这里玩&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这几个游戏是通过 tbrain 多 agent 协作完成的：tbrain 负责把任务拆解成子任务，分配给不同的 agent 并行开发，最后汇总结果。整个过程我只提了一句需求，剩下的规划、开发、测试都是 agent 自己跑完的。&lt;/p&gt;
&lt;p&gt;所有游戏都是单个 HTML 文件，JS/CSS 全部内联，纯静态，没有服务器逻辑。基本没有人工修改。&lt;/p&gt;
&lt;p&gt;有点感慨：以前写这种东西要花好几个小时，现在十几分钟出来六个。&lt;/p&gt;</description>
    </item>
    <item>
      <title>GitHub 开源爬虫与信息订阅工具全览</title>
      <link>https://www.dafei.me/posts/info-tools-01-crawlers/</link>
      <pubDate>Tue, 26 May 2026 10:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/info-tools-01-crawlers/</guid>
      <description>&lt;p&gt;想爬数据、订阅信息源、聚合内容？这里整理了 GitHub 上常见的开源爬虫与信息订阅工具，按类别分组，标注了是否需要登录、实现原理、可获取的内容，方便选型。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;爬虫框架&#34;&gt;爬虫框架&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;项目&lt;/th&gt;
          &lt;th&gt;语言&lt;/th&gt;
          &lt;th&gt;实现原理&lt;/th&gt;
          &lt;th&gt;可爬取内容&lt;/th&gt;
          &lt;th&gt;是否需要登录&lt;/th&gt;
          &lt;th&gt;登录方式&lt;/th&gt;
          &lt;th&gt;Stars&lt;/th&gt;
          &lt;th&gt;维护状态&lt;/th&gt;
          &lt;th&gt;部署难度&lt;/th&gt;
          &lt;th&gt;适用场景&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/microsoft/playwright&#34;&gt;Playwright&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;TS/Python/Java&lt;/td&gt;
          &lt;td&gt;控制真实浏览器，完整JS渲染&lt;/td&gt;
          &lt;td&gt;任意网页内容，含动态渲染（自定义）&lt;/td&gt;
          &lt;td&gt;❌ 不需要&lt;/td&gt;
          &lt;td&gt;视目标站而定&lt;/td&gt;
          &lt;td&gt;89k&lt;/td&gt;
          &lt;td&gt;活跃&lt;/td&gt;
          &lt;td&gt;低&lt;/td&gt;
          &lt;td&gt;JS渲染页面、模拟用户行为、绕过反爬&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/scrapy/scrapy&#34;&gt;Scrapy&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Python&lt;/td&gt;
          &lt;td&gt;Twisted异步，XPath/CSS选择器，中间件+Pipeline架构&lt;/td&gt;
          &lt;td&gt;任意结构化网页数据（自定义）&lt;/td&gt;
          &lt;td&gt;❌ 不需要&lt;/td&gt;
          &lt;td&gt;视目标站而定&lt;/td&gt;
          &lt;td&gt;62k&lt;/td&gt;
          &lt;td&gt;活跃&lt;/td&gt;
          &lt;td&gt;中&lt;/td&gt;
          &lt;td&gt;大规模结构化数据采集&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/apify/crawlee&#34;&gt;Crawlee&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;TypeScript&lt;/td&gt;
          &lt;td&gt;封装Playwright/Puppeteer/Cheerio，内置请求队列+代理轮换&lt;/td&gt;
          &lt;td&gt;任意网页内容，含SPA（自定义）&lt;/td&gt;
          &lt;td&gt;❌ 不需要&lt;/td&gt;
          &lt;td&gt;视目标站而定&lt;/td&gt;
          &lt;td&gt;23k&lt;/td&gt;
          &lt;td&gt;活跃&lt;/td&gt;
          &lt;td&gt;低&lt;/td&gt;
          &lt;td&gt;Node.js生态爬虫，SPA网站采集&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;社交媒体爬虫&#34;&gt;社交媒体爬虫&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;项目&lt;/th&gt;
          &lt;th&gt;语言&lt;/th&gt;
          &lt;th&gt;实现原理&lt;/th&gt;
          &lt;th&gt;可爬取内容&lt;/th&gt;
          &lt;th&gt;是否需要登录&lt;/th&gt;
          &lt;th&gt;登录方式&lt;/th&gt;
          &lt;th&gt;Stars&lt;/th&gt;
          &lt;th&gt;维护状态&lt;/th&gt;
          &lt;th&gt;部署难度&lt;/th&gt;
          &lt;th&gt;适用场景&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/NanmiCoder/MediaCrawler&#34;&gt;MediaCrawler&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Python&lt;/td&gt;
          &lt;td&gt;Playwright控制浏览器，逆向平台API签名，异步并发&lt;/td&gt;
          &lt;td&gt;帖子/视频标题、正文、图片、视频、点赞/收藏/转发数、评论（含回复）、用户信息、话题标签&lt;/td&gt;
          &lt;td&gt;⚠️ 部分需要&lt;/td&gt;
          &lt;td&gt;扫码/Cookie注入&lt;/td&gt;
          &lt;td&gt;50k&lt;/td&gt;
          &lt;td&gt;活跃&lt;/td&gt;
          &lt;td&gt;中&lt;/td&gt;
          &lt;td&gt;社交媒体内容+评论批量采集、舆情分析&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/Evil0ctal/Douyin_TikTok_Download_API&#34;&gt;Douyin_TikTok_Download_API&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Python&lt;/td&gt;
          &lt;td&gt;逆向抖音/TikTok API签名算法，FastAPI对外提供接口&lt;/td&gt;
          &lt;td&gt;视频（无水印）、封面、描述、点赞/评论/分享数、作者信息、音乐信息&lt;/td&gt;
          &lt;td&gt;⚠️ 部分需要&lt;/td&gt;
          &lt;td&gt;Cookie注入&lt;/td&gt;
          &lt;td&gt;18k&lt;/td&gt;
          &lt;td&gt;活跃&lt;/td&gt;
          &lt;td&gt;低&lt;/td&gt;
          &lt;td&gt;视频无水印下载，API集成&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/dataabc/weibo-crawler&#34;&gt;dataabc/weibo-crawler&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Python&lt;/td&gt;
          &lt;td&gt;请求微博移动端API，解析JSON，多种存储后端&lt;/td&gt;
          &lt;td&gt;微博正文、图片、视频、发布时间、点赞/转发/评论数、用户信息、话题&lt;/td&gt;
          &lt;td&gt;⚠️ 部分需要&lt;/td&gt;
          &lt;td&gt;Cookie注入&lt;/td&gt;
          &lt;td&gt;4.5k&lt;/td&gt;
          &lt;td&gt;较活跃&lt;/td&gt;
          &lt;td&gt;低&lt;/td&gt;
          &lt;td&gt;微博用户内容存档&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/CharlesPikachu/videodl&#34;&gt;videodl&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Python&lt;/td&gt;
          &lt;td&gt;逆向各平台API获取真实视频地址，FFmpeg合并&lt;/td&gt;
          &lt;td&gt;视频文件（无水印）、封面图、视频标题&lt;/td&gt;
          &lt;td&gt;⚠️ 部分需要&lt;/td&gt;
          &lt;td&gt;Cookie注入&lt;/td&gt;
          &lt;td&gt;2.1k&lt;/td&gt;
          &lt;td&gt;一般&lt;/td&gt;
          &lt;td&gt;低&lt;/td&gt;
          &lt;td&gt;多平台视频存档&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/lixi5338619/lxSpider&#34;&gt;lxSpider&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Python&lt;/td&gt;
          &lt;td&gt;各平台独立实现，含Requests/Selenium/Scrapy多方案&lt;/td&gt;
          &lt;td&gt;各平台商品/评论/用户/内容数据（视具体案例）&lt;/td&gt;
          &lt;td&gt;⚠️ 部分需要&lt;/td&gt;
          &lt;td&gt;各平台不同&lt;/td&gt;
          &lt;td&gt;2k&lt;/td&gt;
          &lt;td&gt;一般&lt;/td&gt;
          &lt;td&gt;低&lt;/td&gt;
          &lt;td&gt;爬虫学习参考&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;微信公众号&#34;&gt;微信公众号&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;项目&lt;/th&gt;
          &lt;th&gt;语言&lt;/th&gt;
          &lt;th&gt;实现原理&lt;/th&gt;
          &lt;th&gt;可爬取内容&lt;/th&gt;
          &lt;th&gt;是否需要登录&lt;/th&gt;
          &lt;th&gt;登录方式&lt;/th&gt;
          &lt;th&gt;Stars&lt;/th&gt;
          &lt;th&gt;维护状态&lt;/th&gt;
          &lt;th&gt;部署难度&lt;/th&gt;
          &lt;th&gt;适用场景&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/TonyChen56/WeChatRobot&#34;&gt;WeChatRobot&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;C++&lt;/td&gt;
          &lt;td&gt;Windows DLL注入Hook微信进程，访问本地加密数据库&lt;/td&gt;
          &lt;td&gt;聊天记录、公众号文章、联系人、群组、本地加密数据库&lt;/td&gt;
          &lt;td&gt;✅ 需要&lt;/td&gt;
          &lt;td&gt;微信客户端登录（仅Windows）&lt;/td&gt;
          &lt;td&gt;7.1k&lt;/td&gt;
          &lt;td&gt;一般&lt;/td&gt;
          &lt;td&gt;极高&lt;/td&gt;
          &lt;td&gt;微信数据深度采集、机器人开发&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/chyroc/WechatSogou&#34;&gt;WechatSogou&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Python&lt;/td&gt;
          &lt;td&gt;HTTP请求搜狗微信公开接口，BeautifulSoup解析&lt;/td&gt;
          &lt;td&gt;公众号名称/简介/头像、文章标题/摘要/链接/发布时间&lt;/td&gt;
          &lt;td&gt;❌ 不需要&lt;/td&gt;
          &lt;td&gt;-&lt;/td&gt;
          &lt;td&gt;6.3k&lt;/td&gt;
          &lt;td&gt;停止维护&lt;/td&gt;
          &lt;td&gt;低&lt;/td&gt;
          &lt;td&gt;公众号内容搜索聚合&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/wnma3mz/wechat_articles_spider&#34;&gt;wechat_articles_spider&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Python&lt;/td&gt;
          &lt;td&gt;mitmproxy中间人代理拦截微信客户端流量&lt;/td&gt;
          &lt;td&gt;公众号历史文章全量（标题、正文、发布时间、阅读数、点赞数、原文链接）&lt;/td&gt;
          &lt;td&gt;✅ 需要&lt;/td&gt;
          &lt;td&gt;微信客户端登录+抓包&lt;/td&gt;
          &lt;td&gt;3.4k&lt;/td&gt;
          &lt;td&gt;一般&lt;/td&gt;
          &lt;td&gt;高&lt;/td&gt;
          &lt;td&gt;公众号历史文章全量存档&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/rachelos/we-mp-rss&#34;&gt;we-mp-rss&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Python&lt;/td&gt;
          &lt;td&gt;微信账号授权后调用公众号接口，转换RSS输出&lt;/td&gt;
          &lt;td&gt;公众号文章标题、摘要、正文、发布时间、封面图，转为RSS Feed&lt;/td&gt;
          &lt;td&gt;✅ 需要&lt;/td&gt;
          &lt;td&gt;微信账号授权&lt;/td&gt;
          &lt;td&gt;3.2k&lt;/td&gt;
          &lt;td&gt;活跃&lt;/td&gt;
          &lt;td&gt;中&lt;/td&gt;
          &lt;td&gt;将公众号纳入RSS阅读器统一订阅&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;新闻爬虫&#34;&gt;新闻爬虫&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;项目&lt;/th&gt;
          &lt;th&gt;语言&lt;/th&gt;
          &lt;th&gt;实现原理&lt;/th&gt;
          &lt;th&gt;可爬取内容&lt;/th&gt;
          &lt;th&gt;是否需要登录&lt;/th&gt;
          &lt;th&gt;登录方式&lt;/th&gt;
          &lt;th&gt;Stars&lt;/th&gt;
          &lt;th&gt;维护状态&lt;/th&gt;
          &lt;th&gt;部署难度&lt;/th&gt;
          &lt;th&gt;适用场景&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/codelucas/newspaper&#34;&gt;newspaper3k&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Python&lt;/td&gt;
          &lt;td&gt;HTTP请求+lxml解析正文，NLP提取摘要/关键词&lt;/td&gt;
          &lt;td&gt;文章正文、标题、作者、发布时间、摘要、关键词&lt;/td&gt;
          &lt;td&gt;❌ 不需要&lt;/td&gt;
          &lt;td&gt;-&lt;/td&gt;
          &lt;td&gt;15k&lt;/td&gt;
          &lt;td&gt;停止维护&lt;/td&gt;
          &lt;td&gt;低&lt;/td&gt;
          &lt;td&gt;学习参考，生产建议用4k&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/fhamborg/news-please&#34;&gt;news-please&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Python&lt;/td&gt;
          &lt;td&gt;Scrapy驱动，自动解析RSS/Sitemap，结构化存储&lt;/td&gt;
          &lt;td&gt;文章正文、标题、作者、发布时间、描述、图片、语言、来源域名&lt;/td&gt;
          &lt;td&gt;❌ 不需要&lt;/td&gt;
          &lt;td&gt;-&lt;/td&gt;
          &lt;td&gt;2.5k&lt;/td&gt;
          &lt;td&gt;较活跃&lt;/td&gt;
          &lt;td&gt;中&lt;/td&gt;
          &lt;td&gt;大规模新闻数据集构建、学术研究&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/casual-silva/NewsCrawl&#34;&gt;NewsCrawl&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Python&lt;/td&gt;
          &lt;td&gt;Scrapy+Redis+Celery分布式，定时任务+监控面板&lt;/td&gt;
          &lt;td&gt;文章标题、正文、发布时间、来源、分类&lt;/td&gt;
          &lt;td&gt;❌ 不需要&lt;/td&gt;
          &lt;td&gt;-&lt;/td&gt;
          &lt;td&gt;671&lt;/td&gt;
          &lt;td&gt;一般&lt;/td&gt;
          &lt;td&gt;高&lt;/td&gt;
          &lt;td&gt;企业级舆情监控&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/NanmiCoder/NewsCrawler&#34;&gt;NewsCrawler&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Python&lt;/td&gt;
          &lt;td&gt;Playwright浏览器自动化，覆盖中外媒体&lt;/td&gt;
          &lt;td&gt;文章标题、正文、发布时间、作者、来源平台&lt;/td&gt;
          &lt;td&gt;⚠️ 部分需要&lt;/td&gt;
          &lt;td&gt;Cookie注入&lt;/td&gt;
          &lt;td&gt;417&lt;/td&gt;
          &lt;td&gt;活跃&lt;/td&gt;
          &lt;td&gt;中&lt;/td&gt;
          &lt;td&gt;中外媒体跨语言新闻聚合&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/AndyTheFactory/newspaper4k&#34;&gt;newspaper4k&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Python&lt;/td&gt;
          &lt;td&gt;HTTP请求+lxml解析，NLP提取，支持异步&lt;/td&gt;
          &lt;td&gt;文章正文、标题、作者、发布时间、摘要、关键词、顶部图片&lt;/td&gt;
          &lt;td&gt;❌ 不需要&lt;/td&gt;
          &lt;td&gt;-&lt;/td&gt;
          &lt;td&gt;1.1k&lt;/td&gt;
          &lt;td&gt;活跃&lt;/td&gt;
          &lt;td&gt;低&lt;/td&gt;
          &lt;td&gt;快速提取任意新闻正文&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;rss-阅读器--信息聚合&#34;&gt;RSS 阅读器 / 信息聚合&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;项目&lt;/th&gt;
          &lt;th&gt;语言&lt;/th&gt;
          &lt;th&gt;实现原理&lt;/th&gt;
          &lt;th&gt;可爬取内容&lt;/th&gt;
          &lt;th&gt;是否需要登录&lt;/th&gt;
          &lt;th&gt;登录方式&lt;/th&gt;
          &lt;th&gt;Stars&lt;/th&gt;
          &lt;th&gt;维护状态&lt;/th&gt;
          &lt;th&gt;部署难度&lt;/th&gt;
          &lt;th&gt;适用场景&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/glanceapp/glance&#34;&gt;Glance&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Go&lt;/td&gt;
          &lt;td&gt;单二进制，定时拉取各平台API，无数据库&lt;/td&gt;
          &lt;td&gt;RSS文章、Reddit帖子、HN热帖、GitHub Release、YouTube视频、天气、股票&lt;/td&gt;
          &lt;td&gt;❌ 不需要&lt;/td&gt;
          &lt;td&gt;部分源需API Key&lt;/td&gt;
          &lt;td&gt;34k&lt;/td&gt;
          &lt;td&gt;活跃&lt;/td&gt;
          &lt;td&gt;极低&lt;/td&gt;
          &lt;td&gt;个人信息看板&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/FreshRSS/FreshRSS&#34;&gt;FreshRSS&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;PHP&lt;/td&gt;
          &lt;td&gt;定时拉取RSS/Atom feed，兼容Google Reader/Fever API&lt;/td&gt;
          &lt;td&gt;RSS/Atom订阅源的文章标题、正文、发布时间、作者、链接&lt;/td&gt;
          &lt;td&gt;❌ 不需要&lt;/td&gt;
          &lt;td&gt;-&lt;/td&gt;
          &lt;td&gt;15k&lt;/td&gt;
          &lt;td&gt;活跃&lt;/td&gt;
          &lt;td&gt;中&lt;/td&gt;
          &lt;td&gt;自托管RSS服务，多用户共享&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/miniflux/v2&#34;&gt;Miniflux&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Go&lt;/td&gt;
          &lt;td&gt;轻量HTTP服务+PostgreSQL，定时抓取RSS&lt;/td&gt;
          &lt;td&gt;RSS/Atom订阅源的文章标题、正文、发布时间、作者、链接、附件&lt;/td&gt;
          &lt;td&gt;❌ 不需要&lt;/td&gt;
          &lt;td&gt;-&lt;/td&gt;
          &lt;td&gt;9.3k&lt;/td&gt;
          &lt;td&gt;活跃&lt;/td&gt;
          &lt;td&gt;低&lt;/td&gt;
          &lt;td&gt;极简高性能RSS，键盘流用户&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/yinan-c/RSSbrew&#34;&gt;RSSbrew&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Python&lt;/td&gt;
          &lt;td&gt;拉取RSS后过滤+调用OpenAI生成摘要，输出新Feed&lt;/td&gt;
          &lt;td&gt;RSS源文章，经过滤/AI摘要后重新输出为新RSS&lt;/td&gt;
          &lt;td&gt;❌ 不需要&lt;/td&gt;
          &lt;td&gt;OpenAI API Key&lt;/td&gt;
          &lt;td&gt;287&lt;/td&gt;
          &lt;td&gt;较活跃&lt;/td&gt;
          &lt;td&gt;低&lt;/td&gt;
          &lt;td&gt;RSS内容过滤与AI摘要提炼&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;选型建议&#34;&gt;选型建议&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;爬取国内社交媒体&lt;/strong&gt;（小红书/抖音/微博/B站）→ &lt;a href=&#34;https://github.com/NanmiCoder/MediaCrawler&#34;&gt;MediaCrawler&lt;/a&gt;，覆盖最全，维护最活跃&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;订阅微信公众号&lt;/strong&gt; → &lt;a href=&#34;https://github.com/rachelos/we-mp-rss&#34;&gt;we-mp-rss&lt;/a&gt; 转成 RSS，配合 Miniflux 或 FreshRSS 统一阅读&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自建 RSS 服务&lt;/strong&gt; → 极简选 &lt;a href=&#34;https://github.com/miniflux/v2&#34;&gt;Miniflux&lt;/a&gt;，功能全选 &lt;a href=&#34;https://github.com/FreshRSS/FreshRSS&#34;&gt;FreshRSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;个人信息聚合看板&lt;/strong&gt; → &lt;a href=&#34;https://github.com/glanceapp/glance&#34;&gt;Glance&lt;/a&gt;，一个 Docker 命令搞定&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提取任意新闻正文&lt;/strong&gt; → &lt;a href=&#34;https://github.com/AndyTheFactory/newspaper4k&#34;&gt;newspaper4k&lt;/a&gt;，3 行代码搞定&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自定义爬虫开发&lt;/strong&gt; → Python 用 &lt;a href=&#34;https://github.com/scrapy/scrapy&#34;&gt;Scrapy&lt;/a&gt;，JS 网站加 &lt;a href=&#34;https://github.com/microsoft/playwright&#34;&gt;Playwright&lt;/a&gt;；Node.js 用 &lt;a href=&#34;https://github.com/apify/crawlee&#34;&gt;Crawlee&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;下载抖音/TikTok 视频&lt;/strong&gt; → &lt;a href=&#34;https://github.com/Evil0ctal/Douyin_TikTok_Download_API&#34;&gt;Douyin_TikTok_Download_API&lt;/a&gt;，有完整 REST API&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    <item>
      <title>语音克隆（二）：主流开源项目全景对比</title>
      <link>https://www.dafei.me/posts/voice-02-overview/</link>
      <pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/voice-02-overview/</guid>
      <description>&lt;p&gt;目前开源语音克隆项目百花齐放，本文整理了 GitHub 上最热门的项目，对比各自的架构、语言支持、适用场景，帮你快速选型。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;stars-排行榜&#34;&gt;Stars 排行榜&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;排名&lt;/th&gt;
          &lt;th&gt;项目&lt;/th&gt;
          &lt;th&gt;Stars&lt;/th&gt;
          &lt;th&gt;核心特点&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/CorentinJ/Real-Time-Voice-Cloning&#34;&gt;Real-Time-Voice-Cloning&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;~60k&lt;/td&gt;
          &lt;td&gt;经典三阶段流水线，最早流行的克隆项目&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/RVC-Boss/GPT-SoVITS&#34;&gt;GPT-SoVITS&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;~58k&lt;/td&gt;
          &lt;td&gt;中文最强，1分钟数据即可微调&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;3&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/coqui-ai/TTS&#34;&gt;Coqui TTS (XTTS)&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;~45k&lt;/td&gt;
          &lt;td&gt;工业级框架，17种语言，3秒零样本&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;4&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/2noise/ChatTTS&#34;&gt;ChatTTS&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;~39k&lt;/td&gt;
          &lt;td&gt;中文对话语音质量领先，支持笑声停顿等&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;5&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/suno-ai/bark&#34;&gt;Bark&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;~39k&lt;/td&gt;
          &lt;td&gt;创意音频，可生成笑声/哭声/背景音效&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;6&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/babysor/MockingBird&#34;&gt;MockingBird&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;~37k&lt;/td&gt;
          &lt;td&gt;中文版 Real-Time-Voice-Cloning&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;7&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/myshell-ai/OpenVoice&#34;&gt;OpenVoice&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;~37k&lt;/td&gt;
          &lt;td&gt;音色迁移架构，推理极快&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;8&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/RVC-Project/Retrieval-based-Voice-Conversion-WebUI&#34;&gt;RVC&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;~36k&lt;/td&gt;
          &lt;td&gt;歌声转换首选，支持实时变声&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;9&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/fishaudio/fish-speech&#34;&gt;Fish Speech&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;~31k&lt;/td&gt;
          &lt;td&gt;端到端，多语言，工业级质量&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;10&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/svc-develop-team/so-vits-svc&#34;&gt;so-vits-svc&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;~28k&lt;/td&gt;
          &lt;td&gt;AI翻唱鼻祖，歌声克隆质量极高&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;11&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/resemble-ai/chatterbox&#34;&gt;Chatterbox&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;~25k&lt;/td&gt;
          &lt;td&gt;Resemble AI开源，情感可控&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;12&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/FunAudioLLM/CosyVoice&#34;&gt;CosyVoice&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;~21k&lt;/td&gt;
          &lt;td&gt;阿里出品，LLM+流匹配，5种语言&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;13&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/index-tts/index-tts&#34;&gt;Index-TTS&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;~21k&lt;/td&gt;
          &lt;td&gt;bilibili出品，工业级稳定&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;14&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/SWivid/F5-TTS&#34;&gt;F5-TTS&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;~15k&lt;/td&gt;
          &lt;td&gt;流匹配，非自回归，推理快&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;15&lt;/td&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/k2-fsa/OmniVoice&#34;&gt;OmniVoice&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;~6.6k&lt;/td&gt;
          &lt;td&gt;支持600+语言，扩散语言模型&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;完整对比表&#34;&gt;完整对比表&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;项目&lt;/th&gt;
          &lt;th&gt;架构&lt;/th&gt;
          &lt;th&gt;支持语言&lt;/th&gt;
          &lt;th&gt;零样本克隆&lt;/th&gt;
          &lt;th&gt;推理速度&lt;/th&gt;
          &lt;th&gt;需要训练&lt;/th&gt;
          &lt;th&gt;最适合场景&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/CorentinJ/Real-Time-Voice-Cloning&#34;&gt;Real-Time-Voice-Cloning&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;三阶段流水线&lt;/td&gt;
          &lt;td&gt;英文&lt;/td&gt;
          &lt;td&gt;是（5s）&lt;/td&gt;
          &lt;td&gt;接近实时&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;学习/原型验证&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/RVC-Boss/GPT-SoVITS&#34;&gt;GPT-SoVITS&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;GPT + VITS&lt;/td&gt;
          &lt;td&gt;中/英/日&lt;/td&gt;
          &lt;td&gt;是（可微调）&lt;/td&gt;
          &lt;td&gt;中等&lt;/td&gt;
          &lt;td&gt;可选&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;中文配音首选&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/coqui-ai/TTS&#34;&gt;Coqui TTS&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;多架构框架&lt;/td&gt;
          &lt;td&gt;17种&lt;/td&gt;
          &lt;td&gt;是（3s）&lt;/td&gt;
          &lt;td&gt;中等&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;多语言生产部署&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/suno-ai/bark&#34;&gt;Bark&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;自回归LM&lt;/td&gt;
          &lt;td&gt;13种&lt;/td&gt;
          &lt;td&gt;弱&lt;/td&gt;
          &lt;td&gt;慢&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;创意/情感音频&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/2noise/ChatTTS&#34;&gt;ChatTTS&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;生成式LM&lt;/td&gt;
          &lt;td&gt;中/英&lt;/td&gt;
          &lt;td&gt;是&lt;/td&gt;
          &lt;td&gt;中等&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;中文对话TTS&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/babysor/MockingBird&#34;&gt;MockingBird&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;三阶段流水线&lt;/td&gt;
          &lt;td&gt;中/英&lt;/td&gt;
          &lt;td&gt;是（5s）&lt;/td&gt;
          &lt;td&gt;接近实时&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;中文克隆&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/myshell-ai/OpenVoice&#34;&gt;OpenVoice&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;音色迁移&lt;/td&gt;
          &lt;td&gt;6种&lt;/td&gt;
          &lt;td&gt;是&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;极快&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;快速克隆部署&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/RVC-Project/Retrieval-based-Voice-Conversion-WebUI&#34;&gt;RVC&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;VITS + 特征检索&lt;/td&gt;
          &lt;td&gt;语言无关&lt;/td&gt;
          &lt;td&gt;需少量训练&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;实时&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;是（&amp;lt;10min）&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;歌声/实时变声&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/fishaudio/fish-speech&#34;&gt;Fish Speech&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;端到端AR + VQGAN&lt;/td&gt;
          &lt;td&gt;8+种&lt;/td&gt;
          &lt;td&gt;是（10s）&lt;/td&gt;
          &lt;td&gt;快&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;高质量多语言&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/svc-develop-team/so-vits-svc&#34;&gt;so-vits-svc&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;SoftVC + VITS&lt;/td&gt;
          &lt;td&gt;语言无关&lt;/td&gt;
          &lt;td&gt;需训练&lt;/td&gt;
          &lt;td&gt;中等&lt;/td&gt;
          &lt;td&gt;是&lt;/td&gt;
          &lt;td&gt;AI翻唱&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/resemble-ai/chatterbox&#34;&gt;Chatterbox&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;s3gen + 扩散&lt;/td&gt;
          &lt;td&gt;英文&lt;/td&gt;
          &lt;td&gt;是&lt;/td&gt;
          &lt;td&gt;快&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;英语情感配音&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/FunAudioLLM/CosyVoice&#34;&gt;CosyVoice&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;LLM + 流匹配&lt;/td&gt;
          &lt;td&gt;中/英/日/粤/韩&lt;/td&gt;
          &lt;td&gt;是（3-10s）&lt;/td&gt;
          &lt;td&gt;快&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;商业TTS服务&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/index-tts/index-tts&#34;&gt;Index-TTS&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;GPT-style&lt;/td&gt;
          &lt;td&gt;中/英&lt;/td&gt;
          &lt;td&gt;是&lt;/td&gt;
          &lt;td&gt;快&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;工业生产&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/neonbjb/tortoise-tts&#34;&gt;Tortoise TTS&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;自回归 + 扩散&lt;/td&gt;
          &lt;td&gt;英文&lt;/td&gt;
          &lt;td&gt;是&lt;/td&gt;
          &lt;td&gt;慢&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;高质量英语有声书&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/SWivid/F5-TTS&#34;&gt;F5-TTS&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;流匹配 DiT&lt;/td&gt;
          &lt;td&gt;中/英&lt;/td&gt;
          &lt;td&gt;是&lt;/td&gt;
          &lt;td&gt;快&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;快速零样本克隆&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/yl4579/StyleTTS2&#34;&gt;StyleTTS2&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;扩散 + SLM&lt;/td&gt;
          &lt;td&gt;英文&lt;/td&gt;
          &lt;td&gt;是&lt;/td&gt;
          &lt;td&gt;中等&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;英语质量基准&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/Plachtaa/VALL-E-X&#34;&gt;VALL-E-X&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;神经编解码LM&lt;/td&gt;
          &lt;td&gt;英/中/日&lt;/td&gt;
          &lt;td&gt;是&lt;/td&gt;
          &lt;td&gt;中等&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;跨语言克隆&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/hexgrad/kokoro&#34;&gt;Kokoro&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;StyleTTS2变体&lt;/td&gt;
          &lt;td&gt;英+中日韩法&lt;/td&gt;
          &lt;td&gt;否（固定音色）&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;极快&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;本地轻量部署&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/k2-fsa/OmniVoice&#34;&gt;OmniVoice&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;扩散语言模型&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;600+种&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;是&lt;/td&gt;
          &lt;td&gt;快&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;超多语言&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/netease-youdao/EmotiVoice&#34;&gt;EmotiVoice&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;BERT + TTS&lt;/td&gt;
          &lt;td&gt;中/英&lt;/td&gt;
          &lt;td&gt;有限&lt;/td&gt;
          &lt;td&gt;中等&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;情感化中文TTS&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/jasonppy/VoiceCraft&#34;&gt;VoiceCraft&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;自回归 + 码书重排&lt;/td&gt;
          &lt;td&gt;英文&lt;/td&gt;
          &lt;td&gt;是&lt;/td&gt;
          &lt;td&gt;中等&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;语音片段编辑&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/QwenLM/Qwen3-TTS&#34;&gt;Qwen3-TTS&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;LLM-native&lt;/td&gt;
          &lt;td&gt;多语言&lt;/td&gt;
          &lt;td&gt;是&lt;/td&gt;
          &lt;td&gt;中等&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;与LLM集成&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/metavoiceio/metavoice-src&#34;&gt;MetaVoice&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;自回归 + 扩散(1B)&lt;/td&gt;
          &lt;td&gt;英文&lt;/td&gt;
          &lt;td&gt;是（30s）&lt;/td&gt;
          &lt;td&gt;中等&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;高质量英语克隆&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/huggingface/parler-tts&#34;&gt;Parler-TTS&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;描述控制TTS&lt;/td&gt;
          &lt;td&gt;英文&lt;/td&gt;
          &lt;td&gt;文本描述控制&lt;/td&gt;
          &lt;td&gt;中等&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;可控TTS研究&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/k2-fsa/ZipVoice&#34;&gt;ZipVoice&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;流匹配 + 蒸馏&lt;/td&gt;
          &lt;td&gt;中/英&lt;/td&gt;
          &lt;td&gt;是&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;极快&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;否&lt;/td&gt;
          &lt;td&gt;低延迟实时TTS&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://github.com/espnet/espnet&#34;&gt;ESPnet&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;多架构工具包&lt;/td&gt;
          &lt;td&gt;多语言&lt;/td&gt;
          &lt;td&gt;有&lt;/td&gt;
          &lt;td&gt;依模型&lt;/td&gt;
          &lt;td&gt;通常需要&lt;/td&gt;
          &lt;td&gt;学术研究&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;按使用场景推荐&#34;&gt;按使用场景推荐&lt;/h2&gt;
&lt;h3 id=&#34;中文语音克隆&#34;&gt;中文语音克隆&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;GPT-SoVITS &amp;gt; ChatTTS &amp;gt; CosyVoice &amp;gt; Index-TTS&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>语音克隆（一）：用 OmniVoice 克隆任意声音</title>
      <link>https://www.dafei.me/posts/voice-01-omnivoice/</link>
      <pubDate>Mon, 25 May 2026 00:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/voice-01-omnivoice/</guid>
      <description>&lt;p&gt;想让 AI 用你的声音说话？只需要一段 3 到 10 秒的录音，OmniVoice 就能克隆你的声音，说出任意文字。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;omnivoice-是什么&#34;&gt;OmniVoice 是什么&lt;/h2&gt;
&lt;p&gt;OmniVoice 是小米 / k2-fsa 团队开发的开源零样本 TTS 模型，支持 600+ 种语言，基于&lt;strong&gt;扩散语言模型&lt;/strong&gt;架构，推理速度极快（RTF 最低 0.025，比实时快 40 倍）。&lt;/p&gt;
&lt;p&gt;支持三种模式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;语音克隆&lt;/strong&gt;：提供参考音频，克隆声音说出任意文字&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;语音设计&lt;/strong&gt;：用文字描述声音（性别、年龄、音调、口音等）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动语音&lt;/strong&gt;：模型自动选择声音&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;环境准备&#34;&gt;环境准备&lt;/h2&gt;
&lt;h3 id=&#34;安装-conda-环境&#34;&gt;安装 conda 环境&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;conda create -n omnivoice python&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;3.11 -y
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;conda activate omnivoice
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;安装-pytorch&#34;&gt;安装 PyTorch&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Apple Silicon&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pip install torch&lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt;2.8.0 torchaudio&lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt;2.8.0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# NVIDIA GPU（以 CUDA 12.8 为例）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pip install torch&lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt;2.8.0+cu128 torchaudio&lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt;2.8.0+cu128 --extra-index-url https://download.pytorch.org/whl/cu128
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;安装-omnivoice&#34;&gt;安装 OmniVoice&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git clone https://github.com/k2-fsa/OmniVoice.git
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd OmniVoice
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pip install -e .
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;下载模型&#34;&gt;下载模型&lt;/h2&gt;
&lt;p&gt;第一次运行会自动下载。如果在国内连不上 HuggingFace，设置镜像：&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（三十七）：网络栈 —— e1000 驱动 &#43; ARP/IP/TCP &#43; Socket</title>
      <link>https://www.dafei.me/posts/os-37-network/</link>
      <pubDate>Fri, 22 May 2026 08:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-37-network/</guid>
      <description>&lt;p&gt;文件系统能读写了，这一章加网络支持。目标是实现一个最小 TCP/IP 栈，让用户程序能通过 socket 发送 HTTP 请求并收到响应。&lt;/p&gt;
&lt;p&gt;验证方式：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/ # wget_test
wget_test start
connecting...
connected!
request sent
HTTP/1.0 200 OK
...
DONE
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;架构概览&#34;&gt;架构概览&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;用户程序（wget_test）
  ↕ syscall: socket/connect/write/read/close
内核 socket 层（net.c）
  ↕ tcp_connect / tcp_send / tcp_recv / tcp_close
TCP 层（net.c）
  ↕ 以太网帧收发
ARP 层（net.c）
  ↕ e1000_send / e1000_recv
e1000 网卡驱动（e1000.c）
  ↕ MMIO + DMA
QEMU e1000 虚拟网卡（-netdev user,id=net0 -device e1000,netdev=net0）
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;pci-枚举&#34;&gt;PCI 枚举&lt;/h2&gt;
&lt;p&gt;e1000 通过 PCI 总线连接。内核在启动时枚举 PCI 设备：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pci_enumerate&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (bus&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; bus&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;256&lt;/span&gt;; bus&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (dev&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; dev&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;32&lt;/span&gt;; dev&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (fn&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; fn&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;; fn&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;uint16_t&lt;/span&gt; vendor &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pci_read16&lt;/span&gt;(bus, dev, fn, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (vendor &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0xFFFF&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 记录 vendor/device/bar0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;e1000 的 &lt;code&gt;vendor=0x8086&lt;/code&gt;，&lt;code&gt;device=0x100E&lt;/code&gt;。BAR0 是 MMIO 基地址（通常 &lt;code&gt;0xFEBC0000&lt;/code&gt;）。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（三十六）：ext2 写操作 —— 文件创建、引用计数与 tty 字符设备</title>
      <link>https://www.dafei.me/posts/os-36-ext2-write/</link>
      <pubDate>Fri, 22 May 2026 07:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-36-ext2-write/</guid>
      <description>&lt;p&gt;前几章文件系统都是只读的。这一章实现 ext2 的写路径，让 &lt;code&gt;echo hello &amp;gt; /tmp/a.txt &amp;amp;&amp;amp; cat /tmp/a.txt&lt;/code&gt; 能正常工作，且重启后文件仍然存在。过程中还修了 fd 引用计数和 tty 字符设备两个问题。&lt;/p&gt;
&lt;h2 id=&#34;ext2-磁盘结构回顾&#34;&gt;ext2 磁盘结构回顾&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;块组 0:
  [超级块][组描述符][block bitmap][inode bitmap][inode table][数据块...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;关键字段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sb-&amp;gt;s_free_inodes_count&lt;/code&gt; / &lt;code&gt;gd-&amp;gt;bg_free_inodes_count&lt;/code&gt;：空闲 inode 计数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sb-&amp;gt;s_free_blocks_count&lt;/code&gt; / &lt;code&gt;gd-&amp;gt;bg_free_blocks_count&lt;/code&gt;：空闲 block 计数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gd-&amp;gt;bg_inode_bitmap&lt;/code&gt;：inode bitmap 所在块号&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gd-&amp;gt;bg_block_bitmap&lt;/code&gt;：block bitmap 所在块号&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gd-&amp;gt;bg_inode_table&lt;/code&gt;：inode table 起始块号&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;inode 号从 1 开始；前 10 个是系统保留的（root=2，lost+found=11）。&lt;/p&gt;
&lt;h2 id=&#34;alloc_inode--alloc_block&#34;&gt;alloc_inode / alloc_block&lt;/h2&gt;
&lt;h3 id=&#34;alloc_inode&#34;&gt;alloc_inode&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;alloc_inode&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 1. 读 inode bitmap 块
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 2. 找第一个为 0 的位（bit i → inode i+1）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 3. 置位，写回 bitmap
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 4. 更新超级块和组描述符的 free_inodes_count
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 5. 返回 inode 号（从 1 开始）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;alloc_block&#34;&gt;alloc_block&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;alloc_block&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 1. 读 block bitmap 块
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 2. 找第一个为 0 的位，跳过 block 0（保留）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 3. 置位，写回 bitmap
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 4. 更新超级块和组描述符的 free_blocks_count
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 5. 清零新分配的块（避免垃圾数据）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 6. 返回块号
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意：block bitmap 的 bit 0 对应 block 0，必须跳过（block 0 不存在）。实际第一个可分配的块由文件系统布局决定（本项目中是 610）。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（三十五）：getdents64 &#43; chdir &#43; 内存屏障 Bug</title>
      <link>https://www.dafei.me/posts/os-35-getdents/</link>
      <pubDate>Fri, 22 May 2026 06:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-35-getdents/</guid>
      <description>&lt;p&gt;上一章 &lt;code&gt;ls&lt;/code&gt; 能运行了，但打印出来的目录内容是空的——因为还没有实现 &lt;code&gt;getdents64&lt;/code&gt;。这一章把目录列表、&lt;code&gt;cd&lt;/code&gt;、&lt;code&gt;pwd&lt;/code&gt; 全部补齐，同时碰到了一个 &lt;code&gt;-O2&lt;/code&gt; 下内存乱序导致的调度器死循环 Bug。&lt;/p&gt;
&lt;h2 id=&#34;sys_getdents64&#34;&gt;SYS_GETDENTS64&lt;/h2&gt;
&lt;h3 id=&#34;linux-接口&#34;&gt;Linux 接口&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getdents64&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; fd, &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; linux_dirent64 &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;dirp, &lt;span style=&#34;color:#66d9ef&#34;&gt;unsigned&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; count);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;每个条目的结构：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; linux_dirent64 {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; d_ino;       &lt;span style=&#34;color:#75715e&#34;&gt;// inode 号
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int64_t&lt;/span&gt;  d_off;       &lt;span style=&#34;color:#75715e&#34;&gt;// 到下一条目的偏移（可用递增序号代替）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint16_t&lt;/span&gt; d_reclen;    &lt;span style=&#34;color:#75715e&#34;&gt;// 本条目总字节数（含填充，8 字节对齐）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt;  d_type;      &lt;span style=&#34;color:#75715e&#34;&gt;// 文件类型（4=目录，8=普通文件）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt;     d_name[];    &lt;span style=&#34;color:#75715e&#34;&gt;// 文件名（null 结尾）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;d_reclen&lt;/code&gt; 必须 8 字节对齐，计算方式：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;uint16_t&lt;/span&gt; reclen &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;uint16_t&lt;/span&gt;)((&lt;span style=&#34;color:#ae81ff&#34;&gt;19&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; namelen &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;~&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;//                             ↑固定头部  ↑名字  ↑null  ↑对齐
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;内核实现&#34;&gt;内核实现&lt;/h3&gt;
&lt;p&gt;在 VFS 层实现 &lt;code&gt;vfs_getdents&lt;/code&gt;，遍历 ext2 目录的所有条目，逐个填写 &lt;code&gt;linux_dirent64&lt;/code&gt; 并写入用户缓冲区：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;vfs_getdents&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; fd, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; uva, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; count) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 1. 检查 fd 是否为目录
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 2. 从 offset 开始遍历 ext2 目录
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 3. 跳过 &amp;#34;.&amp;#34; 和 &amp;#34;..&amp;#34;（busybox ls 不显示它们）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 4. 每个条目：填 d_ino、d_type、d_name、d_reclen
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 5. 通过 copy_to_user 写入用户空间
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 6. 更新 file-&amp;gt;offset，返回写入的总字节数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;关键：返回 0 表示目录已读完（EOF），busybox ls 据此停止调用。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（三十四）：阻塞式管道与调度器的两个 Bug</title>
      <link>https://www.dafei.me/posts/os-34-blocking-pipe/</link>
      <pubDate>Fri, 22 May 2026 05:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-34-blocking-pipe/</guid>
      <description>&lt;p&gt;上一章实现了阻塞式 TTY，这一章在同样的思路上让 pipe 的 read 也变成阻塞式，同时修复了调度器里隐藏的两个 Bug，让 fork + exec + wait4 的完整流程跑通。&lt;/p&gt;
&lt;h2 id=&#34;问题非阻塞-pipe-read-的后果&#34;&gt;问题：非阻塞 pipe read 的后果&lt;/h2&gt;
&lt;p&gt;ch33 之前，&lt;code&gt;vfs_read&lt;/code&gt; 对 pipe 的实现是：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;pipe_buf_empty&lt;/span&gt;(p)) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;   &lt;span style=&#34;color:#75715e&#34;&gt;// 管道空 → 直接返回 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;对于 shell 管道命令，busybox sh 会：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;fork 出子进程执行左侧命令，写 pipe&lt;/li&gt;
&lt;li&gt;父进程（或另一子进程）读 pipe，处理右侧命令&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果读端在写端还没写入时就读到 0，shell 认为 EOF，管道提前关闭，命令输出丢失。&lt;/p&gt;
&lt;h2 id=&#34;解决方案stihlt-等待&#34;&gt;解决方案：sti/hlt 等待&lt;/h2&gt;
&lt;p&gt;与 ch33 阻塞式 TTY read 相同的思路：在内核态用 &lt;code&gt;sti; hlt&lt;/code&gt; 轮询等待。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// pipe read：等待数据或写端关闭
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;pipe_buf_empty&lt;/span&gt;(p) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pipe_has_writer&lt;/span&gt;(p)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    __asm__ &lt;span style=&#34;color:#66d9ef&#34;&gt;volatile&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sti&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:::&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;memory&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    __asm__ &lt;span style=&#34;color:#a6e22e&#34;&gt;volatile&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hlt&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:::&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;memory&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    __asm__ &lt;span style=&#34;color:#a6e22e&#34;&gt;volatile&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cli&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:::&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;memory&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;pipe_buf_empty&lt;/span&gt;(p)) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;   &lt;span style=&#34;color:#75715e&#34;&gt;// 写端已关闭且无数据 → EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;关键点：&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（三十三）：阻塞式 TTY —— read 不再忙等</title>
      <link>https://www.dafei.me/posts/os-33-blocking-tty/</link>
      <pubDate>Fri, 22 May 2026 04:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-33-blocking-tty/</guid>
      <description>&lt;p&gt;上一章 busybox sh 成功显示了 &lt;code&gt;/ #&lt;/code&gt; 提示符，但 shell 拿不到任何输入——因为 &lt;code&gt;tty_read&lt;/code&gt; 是忙轮询的，没有字符就直接返回 0，shell 以为收到 EOF，立刻退出。这一章实现真正的阻塞式 TTY，让 shell 能等待用户输入。&lt;/p&gt;
&lt;h2 id=&#34;之前的问题&#34;&gt;之前的问题&lt;/h2&gt;
&lt;p&gt;之前的 &lt;code&gt;tty_read&lt;/code&gt; 大概是这样：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tty_read&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;buf, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; len) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 轮询串口状态寄存器
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;inb&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0x3F8&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;   &lt;span style=&#34;color:#75715e&#34;&gt;// 没数据就返回 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;buf &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;inb&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0x3F8&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这有两个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;没有输入时返回 0，上层程序（shell）以为是 EOF&lt;/li&gt;
&lt;li&gt;如果上层在循环里调这个，CPU 100% 占用&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;正确做法：没有输入时&lt;strong&gt;挂起进程&lt;/strong&gt;，等串口中断来了再唤醒。&lt;/p&gt;
&lt;h2 id=&#34;串口中断irq4&#34;&gt;串口中断（IRQ4）&lt;/h2&gt;
&lt;p&gt;COM1 对应 IRQ4，接在 PIC 主片的 IR4 引脚。要用串口中断，需要两步：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 在 PIC 上 unmask IRQ4&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// PIC 主片 IMR 寄存器：0 表示开放，1 表示屏蔽
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; mask &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;inb&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0x21&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mask &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;~&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;);   &lt;span style=&#34;color:#75715e&#34;&gt;// 清除 bit4，开放 IRQ4
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;outb&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0x21&lt;/span&gt;, mask);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;2. 开启串口的接收中断&lt;/strong&gt;：&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（三十二）：启动 busybox sh —— 用户指针与内核页表的陷阱</title>
      <link>https://www.dafei.me/posts/os-32-busybox-init/</link>
      <pubDate>Fri, 22 May 2026 03:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-32-busybox-init/</guid>
      <description>&lt;p&gt;ch31 已经能跑 musl libc 的 hello world 了，这一章目标更高：启动 busybox sh，看到 &lt;code&gt;/ #&lt;/code&gt; 提示符。busybox 是个真正的程序，碰到的问题也更真实。&lt;/p&gt;
&lt;h2 id=&#34;准备工作&#34;&gt;准备工作&lt;/h2&gt;
&lt;h3 id=&#34;获取静态编译的-busybox&#34;&gt;获取静态编译的 busybox&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;wget https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chmod +x busybox
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;file busybox
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# busybox: ELF 64-bit LSB executable, x86-64, statically linked&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;更新-ext2-镜像&#34;&gt;更新 ext2 镜像&lt;/h3&gt;
&lt;p&gt;busybox 需要 &lt;code&gt;/bin/sh&lt;/code&gt;、&lt;code&gt;/etc/passwd&lt;/code&gt;、&lt;code&gt;/etc/group&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-makefile&#34; data-lang=&#34;makefile&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;sudo&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;mkdir&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-p&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/tmp/ext2mnt/bin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;sudo&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;mkdir&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-p&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/tmp/ext2mnt/etc&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;sudo&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;cp&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;BUSYBOX&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/tmp/ext2mnt/bin/busybox&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;sudo&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;cp&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;BUSYBOX&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/tmp/ext2mnt/bin/sh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;printf &amp;#39;root&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;x:0:0:root:/root:/bin/sh\n&amp;#39; | sudo tee /tmp/ext2mnt/etc/passwd &amp;gt; /dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;printf &amp;#39;root&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;x:0:\n&amp;#39; | sudo tee /tmp/ext2mnt/etc/group &amp;gt; /dev/null
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;bug-1gdt-tss-描述符溢出&#34;&gt;Bug 1：GDT TSS 描述符溢出&lt;/h2&gt;
&lt;p&gt;x86_64 下 TSS 描述符是 &lt;strong&gt;16 字节&lt;/strong&gt;（两个 qword），GDT 必须为它预留两个连续槽位。之前只预留了一个，导致 TSS 描述符的高 8 字节覆盖了相邻的全局变量（恰好是 &lt;code&gt;mmap_next&lt;/code&gt;），进程一分配匿名内存就跳到奇怪的地址。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（三十一）：运行 musl libc 程序 —— 两个隐藏的寄存器 Bug</title>
      <link>https://www.dafei.me/posts/os-31-musl-libc/</link>
      <pubDate>Fri, 22 May 2026 02:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-31-musl-libc/</guid>
      <description>&lt;p&gt;上一章对齐了 Linux syscall ABI，现在试着跑一个真正用 musl libc 静态编译的 C 程序。目标很简单：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; argc, &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;argv) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;printf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hello from musl libc!&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;printf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;argc=%d&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, argc);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;编译：&lt;code&gt;musl-gcc -static -O2 -o hello.elf hello.c&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;结果踩了两个隐藏很深的寄存器 bug，花了不少时间才找出来。&lt;/p&gt;
&lt;h2 id=&#34;新增的-syscall&#34;&gt;新增的 syscall&lt;/h2&gt;
&lt;p&gt;musl libc 启动时会调用一批 syscall，其中有三个是绕不过的：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;syscall&lt;/th&gt;
          &lt;th&gt;编号&lt;/th&gt;
          &lt;th&gt;说明&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;arch_prctl(ARCH_SET_FS, addr)&lt;/td&gt;
          &lt;td&gt;158&lt;/td&gt;
          &lt;td&gt;设置 TLS base（FS 寄存器）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;set_tid_address(tidptr)&lt;/td&gt;
          &lt;td&gt;218&lt;/td&gt;
          &lt;td&gt;设置线程 ID 地址，返回 pid&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;writev(fd, iov, iovcnt)&lt;/td&gt;
          &lt;td&gt;20&lt;/td&gt;
          &lt;td&gt;向量写，musl 的 stdio 用它输出&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;arch_prctl&lt;/code&gt; 需要写 MSR &lt;code&gt;0xC0000100&lt;/code&gt;（FS.base），这是 TLS 的基地址，musl 用来存放线程局部变量：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; SYS_ARCH_PRCTL:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (a1 &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; ARCH_SET_FS) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;wrmsr&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0xC0000100&lt;/span&gt;, a2);   &lt;span style=&#34;color:#75715e&#34;&gt;// FS.base MSR
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt;)(&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;EINVAL);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;writev&lt;/code&gt; 需要遍历 iov 数组，把多段数据拼起来写到 tty：&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（三十）：对齐 Linux syscall ABI，让 musl libc 能跑起来</title>
      <link>https://www.dafei.me/posts/os-30-syscall-abi/</link>
      <pubDate>Fri, 22 May 2026 01:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-30-syscall-abi/</guid>
      <description>&lt;p&gt;musl libc 编译出来的程序会直接用 &lt;code&gt;syscall&lt;/code&gt; 指令，传的是 Linux x86-64 标准调用号。我们之前的调用号是自己编的，如果不对齐，musl 程序一个 syscall 都跑不通。这一章把内核的 syscall 接口全面对齐 Linux ABI。&lt;/p&gt;
&lt;h2 id=&#34;linux-x86-64-syscall-约定&#34;&gt;Linux x86-64 syscall 约定&lt;/h2&gt;
&lt;p&gt;Linux 的 x86-64 syscall 约定：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;调用：&lt;code&gt;rax&lt;/code&gt;=syscall号，&lt;code&gt;rdi&lt;/code&gt;=a1，&lt;code&gt;rsi&lt;/code&gt;=a2，&lt;code&gt;rdx&lt;/code&gt;=a3，&lt;code&gt;r10&lt;/code&gt;=a4，&lt;code&gt;r8&lt;/code&gt;=a5，&lt;code&gt;r9&lt;/code&gt;=a6&lt;/li&gt;
&lt;li&gt;返回：&lt;code&gt;rax&lt;/code&gt;（负数表示错误，如 &lt;code&gt;-ENOENT&lt;/code&gt; = -2）&lt;/li&gt;
&lt;li&gt;CPU 自动保存：&lt;code&gt;rcx&lt;/code&gt;=用户 rip，&lt;code&gt;r11&lt;/code&gt;=用户 rflags&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意第 4 个参数是 &lt;code&gt;r10&lt;/code&gt;，不是 &lt;code&gt;rcx&lt;/code&gt;。原因是 &lt;code&gt;syscall&lt;/code&gt; 指令会把用户 &lt;code&gt;rip&lt;/code&gt; 存进 &lt;code&gt;rcx&lt;/code&gt;，所以 &lt;code&gt;r10&lt;/code&gt; 顶替了 &lt;code&gt;rcx&lt;/code&gt; 的位置。这一点非常容易踩坑。&lt;/p&gt;
&lt;h2 id=&#34;关键改动&#34;&gt;关键改动&lt;/h2&gt;
&lt;h3 id=&#34;1-syscall-号全部换成-linux-标准&#34;&gt;1. syscall 号全部换成 Linux 标准&lt;/h3&gt;
&lt;p&gt;之前的调用号是自己定义的，现在全部换成 Linux 标准值：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;功能&lt;/th&gt;
          &lt;th&gt;之前&lt;/th&gt;
          &lt;th&gt;现在&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;read&lt;/td&gt;
          &lt;td&gt;6&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;write&lt;/td&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;1 ✓&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;open&lt;/td&gt;
          &lt;td&gt;5&lt;/td&gt;
          &lt;td&gt;2&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;close&lt;/td&gt;
          &lt;td&gt;7&lt;/td&gt;
          &lt;td&gt;3&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;fork&lt;/td&gt;
          &lt;td&gt;3&lt;/td&gt;
          &lt;td&gt;57&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;exit&lt;/td&gt;
          &lt;td&gt;2&lt;/td&gt;
          &lt;td&gt;60&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;kill&lt;/td&gt;
          &lt;td&gt;12&lt;/td&gt;
          &lt;td&gt;62&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;write 碰巧没变，其他大部分都不一样。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（二十九）：block cache —— 给磁盘加一层缓存</title>
      <link>https://www.dafei.me/posts/os-29-block-cache/</link>
      <pubDate>Fri, 15 May 2026 02:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-29-block-cache/</guid>
      <description>&lt;p&gt;Linux 内核里有个叫 page cache（以前叫 buffer cache）的东西，所有磁盘 IO 都要经过它。为什么？因为磁盘太慢了——ATA PIO 读一个扇区要等几毫秒，而内存访问只要几纳秒。把最近访问过的扇区留在内存里，下次再访问直接从内存读，速度提升几千倍。&lt;/p&gt;
&lt;p&gt;这一章给我们的 ext2 文件系统加上这层缓存，同时顺手修了一个隐藏很深的调度器 bug。&lt;/p&gt;
&lt;h2 id=&#34;设计lru-write-back-缓存&#34;&gt;设计：LRU write-back 缓存&lt;/h2&gt;
&lt;p&gt;最简单够用的设计：&lt;strong&gt;固定 64 个 slot，每个 slot 缓存一个 512 字节扇区，LRU 淘汰，write-back 写回&lt;/strong&gt;。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; lba;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt;  data[&lt;span style=&#34;color:#ae81ff&#34;&gt;512&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt;  valid;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt;  dirty;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; lru_time;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;bcache_slot_t&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bcache_slot_t&lt;/span&gt; slots[&lt;span style=&#34;color:#ae81ff&#34;&gt;64&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt;      clock &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;lru_time&lt;/code&gt; 用一个全局 clock 计数器实现——每次访问 clock++，命中的 slot 拿到最新值，淘汰时找 lru_time 最小的那个。&lt;/p&gt;
&lt;h3 id=&#34;bcache_getlba&#34;&gt;bcache_get(lba)&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;bcache_get&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; lba) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    clock&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 命中？
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;64&lt;/span&gt;; i&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (slots[i].valid &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; slots[i].lba &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; lba) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            slots[i].lru_time &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; clock;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; slots[i].data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 找 LRU victim
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; victim &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;64&lt;/span&gt;; i&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;slots[i].valid) { victim &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; i; &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (slots[i].lru_time &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; slots[victim].lru_time) victim &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; i;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// victim 是 dirty 的？先写回磁盘
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (slots[victim].valid &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; slots[victim].dirty)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;ata_write_sector&lt;/span&gt;(slots[victim].lba, slots[victim].data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 读新扇区
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ata_read_sector&lt;/span&gt;(lba, slots[victim].data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    slots[victim] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;bcache_slot_t&lt;/span&gt;){ lba, ..., valid&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, dirty&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, lru_time&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;clock };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; slots[victim].data;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;返回的是指向缓存数据的指针，调用方可以直接读写这块内存。写完后调 &lt;code&gt;bcache_dirty(lba)&lt;/code&gt; 标记为脏。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（二十八）：timer —— 让进程睡一会儿</title>
      <link>https://www.dafei.me/posts/os-28-timer/</link>
      <pubDate>Fri, 15 May 2026 01:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-28-timer/</guid>
      <description>&lt;p&gt;Linux 里的 &lt;code&gt;sleep(2)&lt;/code&gt; 背后是什么？&lt;/p&gt;
&lt;p&gt;进程调用 &lt;code&gt;sleep&lt;/code&gt;，内核把它标记为&amp;quot;阻塞&amp;quot;，让出 CPU 给其他进程，等时间到了再唤醒它。实现这个功能需要两个组件配合：&lt;strong&gt;定时器&lt;/strong&gt;（知道时间到了）+ &lt;strong&gt;调度器&lt;/strong&gt;（切换进程）。&lt;/p&gt;
&lt;p&gt;好消息是这两个我们都有了：PIT 100Hz 时钟 + round-robin 调度器。这一章只需要把它们接起来。&lt;/p&gt;
&lt;h2 id=&#34;核心思路&#34;&gt;核心思路&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;SYS_SLEEP(ms)
  → sleep_until = pit_get_ticks() + ms/10
  → state = PROC_BLOCKED

IRQ0 handler（每 10ms）
  → pit_tick()      // ticks++
  → timer_tick()    // 扫描所有进程，到期则唤醒
  → sched_tick()    // 调度下一个 READY 进程
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;进程睡觉时设好&amp;quot;闹钟&amp;quot;（&lt;code&gt;sleep_until&lt;/code&gt;），然后自己进入 &lt;code&gt;PROC_BLOCKED&lt;/code&gt;。
每次时钟中断，&lt;code&gt;timer_tick&lt;/code&gt; 扫一遍所有进程，到期的改回 &lt;code&gt;PROC_READY&lt;/code&gt;，下次调度就能运行了。&lt;/p&gt;
&lt;h2 id=&#34;实现&#34;&gt;实现&lt;/h2&gt;
&lt;h3 id=&#34;1-process_t-加一个字段&#34;&gt;1. process_t 加一个字段&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ... 原有字段
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; sleep_until;   &lt;span style=&#34;color:#75715e&#34;&gt;// 睡到哪个 tick，0 表示没在睡
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;process_t&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;2-timerc&#34;&gt;2. timer.c&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;timer_tick&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; now &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pit_get_ticks&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; MAX_PROCS; i&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (procs[i].state &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; PROC_BLOCKED &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; procs[i].sleep_until &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (now &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; procs[i].sleep_until) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                procs[i].sleep_until &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                procs[i].state &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; PROC_READY;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;逻辑极简：遍历，到期，唤醒。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（二十七）：字符设备 —— /dev/null 和 /dev/zero</title>
      <link>https://www.dafei.me/posts/os-27-chardev/</link>
      <pubDate>Thu, 14 May 2026 07:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-27-chardev/</guid>
      <description>&lt;p&gt;Linux 里有个特殊目录 &lt;code&gt;/dev/&lt;/code&gt;，里面住着各种设备文件：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/dev/null     ← 写什么扔什么，读永远 EOF
/dev/zero     ← 读出来全是 \0
/dev/random   ← 读出来是随机字节
/dev/tty      ← 当前终端
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对用户程序来说，它们和普通文件没区别：&lt;code&gt;open&lt;/code&gt;、&lt;code&gt;read&lt;/code&gt;、&lt;code&gt;write&lt;/code&gt;、&lt;code&gt;close&lt;/code&gt;，完全一样的接口。&lt;/p&gt;
&lt;p&gt;这一章实现字符设备（char device）框架，让 VFS 能路由 &lt;code&gt;/dev/&lt;/code&gt; 路径，并内置 &lt;code&gt;null&lt;/code&gt; 和 &lt;code&gt;zero&lt;/code&gt; 两个最基础的设备。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;什么是字符设备&#34;&gt;什么是字符设备&lt;/h2&gt;
&lt;p&gt;字符设备（character device）的特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;按字节读写&lt;/strong&gt;，没有块/扇区的概念&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有随机寻址&lt;/strong&gt;（不像磁盘文件可以 seek）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;读写是即时的&lt;/strong&gt;：写进去就消失（null）或立刻可读（zero/tty）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;与之对应的是块设备（block device），如硬盘，以固定大小的块为单位操作。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;设备注册表&#34;&gt;设备注册表&lt;/h2&gt;
&lt;p&gt;核心数据结构 &lt;code&gt;cdev_t&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt;    name[&lt;span style=&#34;color:#ae81ff&#34;&gt;16&lt;/span&gt;];              &lt;span style=&#34;color:#75715e&#34;&gt;// 设备名，如 &amp;#34;null&amp;#34;、&amp;#34;zero&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;     (&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;open) (&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;     (&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;read) (&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;buf, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; len);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;     (&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;write)(&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;buf, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; len);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;    (&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;close)(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; fd);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;     used;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;cdev_t&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;cdev_t&lt;/span&gt; devs[CDEV_MAX];     &lt;span style=&#34;color:#75715e&#34;&gt;// 全局设备表
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;通过函数指针实现多态——不同设备注册不同的 read/write 实现。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;chardev_register&lt;/code&gt; 向 &lt;code&gt;devs[]&lt;/code&gt; 添加一个设备，&lt;code&gt;chardev_open&lt;/code&gt; 按名字查找并调用 &lt;code&gt;open()&lt;/code&gt;。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（二十六）：poll —— 同时等待多个 fd</title>
      <link>https://www.dafei.me/posts/os-26-poll/</link>
      <pubDate>Thu, 14 May 2026 06:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-26-poll/</guid>
      <description>&lt;p&gt;上一章实现了 TTY 输入，但用户程序只能这样等输入：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-nasm&#34; data-lang=&#34;nasm&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.read_loop:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;syscall&lt;/span&gt; SYS_READ(fd&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, buf, &lt;span style=&#34;color:#ae81ff&#34;&gt;64&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;cmp&lt;/span&gt; rax, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;jle&lt;/span&gt; .read_loop
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;问题很明显：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;CPU 全速空转&lt;/strong&gt;：没有输入时 &lt;code&gt;SYS_READ&lt;/code&gt; 每次返回 0，进程白白占用 CPU&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无法同时等多个 fd&lt;/strong&gt;：如果既要等 stdin，又要等 pipe，必须串行轮询，任一 fd 都可能饿死&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;poll&lt;/code&gt; 解决这两个问题。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;poll-是什么&#34;&gt;poll 是什么&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;poll(fds[], nfds, timeout)&lt;/code&gt; 是一个系统调用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;传入一组 &lt;code&gt;pollfd_t&lt;/code&gt; 结构，每个描述&amp;quot;我关心 fd X 的什么事件&amp;quot;&lt;/li&gt;
&lt;li&gt;内核检查每个 fd 是否满足条件&lt;/li&gt;
&lt;li&gt;返回就绪的 fd 数量，并在 &lt;code&gt;revents&lt;/code&gt; 里标记哪些事件发生了&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; pollfd {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;   fd;       &lt;span style=&#34;color:#75715e&#34;&gt;// 监听哪个 fd
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;short&lt;/span&gt; events;   &lt;span style=&#34;color:#75715e&#34;&gt;// 关心：POLLIN（可读）/ POLLOUT（可写）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;short&lt;/span&gt; revents;  &lt;span style=&#34;color:#75715e&#34;&gt;// 内核填写：实际发生了什么
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;poll&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; pollfd &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;fds, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; nfds, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; timeout_ms);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;有了 poll，程序可以：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;poll([{fd=0, POLLIN}, {fd=pipe_r, POLLIN}], 2, -1)
  → 等到任意一个有数据，才返回
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id=&#34;实现架构&#34;&gt;实现架构&lt;/h2&gt;
&lt;p&gt;整体分四层：&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（二十五）：TTY 终端输入</title>
      <link>https://www.dafei.me/posts/os-25-tty/</link>
      <pubDate>Thu, 14 May 2026 05:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-25-tty/</guid>
      <description>&lt;p&gt;到目前为止，进程只能输出，没有办法接收用户输入。&lt;code&gt;SYS_READ&lt;/code&gt; 形同虚设，键盘按了也没反应。&lt;/p&gt;
&lt;p&gt;这一章实现 &lt;strong&gt;TTY&lt;/strong&gt;——Unix 对终端设备的最初抽象，让进程能以行为单位从串口读取输入。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;tty-是什么&#34;&gt;TTY 是什么&lt;/h2&gt;
&lt;p&gt;TTY 原本是 teletypewriter（电传打字机）的缩写。在 Unix 里，它泛指&amp;quot;终端&amp;quot;——一个字符设备，既能接收键盘输入，又能输出文字。&lt;/p&gt;
&lt;p&gt;现代 Linux 里的 &lt;code&gt;/dev/tty&lt;/code&gt;、&lt;code&gt;/dev/pts/0&lt;/code&gt; 都是 TTY 的后代。我们这里用串口 COM1 模拟一个最简单的 TTY。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;整体架构&#34;&gt;整体架构&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;用户键盘输入
   ↓ 串口硬件触发 IRQ4
isr_handler → tty_recv(c)   ← 回显 + 写入 ring buffer
                ↓ 遇到 &amp;#39;\n&amp;#39;
           line_ready = 1
                ↓ 进程 syscall SYS_READ(fd=0)
           tty_read(buf, len)  ← 把一行搬到用户 buf
                ↓
           用户程序处理输入
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;三个关键组件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Ring Buffer&lt;/strong&gt;：固定大小的环形缓冲区，存放还未被读走的字符&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;行规程（line discipline）&lt;/strong&gt;：积累字符，遇 &lt;code&gt;\n&lt;/code&gt; 才通知&amp;quot;行就绪&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IRQ4 中断处理&lt;/strong&gt;：从串口读一个字节，交给 TTY&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;ring-buffer&#34;&gt;Ring Buffer&lt;/h2&gt;
&lt;p&gt;环形缓冲区是一个固定数组，加上读指针和写指针：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[ _ _ _ _ h e l l o \n _ _ ]
           ↑                 ↑
         rx_read          rx_write
rx_len = 6
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;写字符：&lt;code&gt;rx_buf[rx_write] = c; rx_write = (rx_write + 1) % TTY_BUF_SIZE; rx_len++&lt;/code&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（二十四）：signal 信号机制</title>
      <link>https://www.dafei.me/posts/os-24-signal/</link>
      <pubDate>Thu, 14 May 2026 04:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-24-signal/</guid>
      <description>&lt;p&gt;到目前为止，进程只能顺序跑完，没有&amp;quot;被打断&amp;quot;的能力。按 Ctrl+C 没有反应，子进程崩溃父进程不知道，&lt;code&gt;kill&lt;/code&gt; 命令更无从谈起。&lt;/p&gt;
&lt;p&gt;这一章实现 &lt;strong&gt;signal&lt;/strong&gt;——内核向进程发送异步通知的机制。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;信号是什么&#34;&gt;信号是什么&lt;/h2&gt;
&lt;p&gt;信号是一个整数编号，内核用它告诉进程&amp;quot;发生了某件事&amp;quot;：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;信号&lt;/th&gt;
          &lt;th&gt;编号&lt;/th&gt;
          &lt;th&gt;默认行为&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;SIGUSR1&lt;/td&gt;
          &lt;td&gt;10&lt;/td&gt;
          &lt;td&gt;终止&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;SIGKILL&lt;/td&gt;
          &lt;td&gt;9&lt;/td&gt;
          &lt;td&gt;终止（不可捕获）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;SIGTERM&lt;/td&gt;
          &lt;td&gt;15&lt;/td&gt;
          &lt;td&gt;终止&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;SIGCHLD&lt;/td&gt;
          &lt;td&gt;17&lt;/td&gt;
          &lt;td&gt;忽略&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;进程可以用 &lt;code&gt;signal(sig, handler)&lt;/code&gt; 注册自定义处理函数，也可以接受默认行为。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;关键信号不立刻打断进程&#34;&gt;关键：信号不立刻打断进程&lt;/h2&gt;
&lt;p&gt;信号发送时只是设置一个 &lt;strong&gt;pending 位&lt;/strong&gt;，不立刻跳转。等进程下次从内核态返回用户态时，才检查并派发：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;SYS_KILL → pending_signals |= (1 &amp;lt;&amp;lt; sig)

syscall 处理完 → 准备 sysret
                    ↓
          检查 pending_signals
                    ↓
        有信号 → 操纵 user_rip/user_rsp → sysret 跳到 handler
        handler 执行完 ret → 回到原来被中断的位置
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这和硬件中断不同——硬件中断可以在任意时刻打断 CPU，信号只在内核→用户的切换点才生效。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;派发机制trampoline&#34;&gt;派发机制（trampoline）&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;signal_dispatch&lt;/code&gt; 收到两个指针：内核栈上保存的 &lt;code&gt;user_rip&lt;/code&gt;（原来准备 sysret 到的地址）和 &lt;code&gt;user_rsp&lt;/code&gt;。它做的事：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;1. user_rsp -= 8
2. *(user_rsp) = user_rip    // 原返回地址压到用户栈
3. user_rip = handler_addr   // sysret 改跳到 handler
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;sysret 跳到 handler，handler 执行完 &lt;code&gt;ret&lt;/code&gt;，弹出用户栈上的原 rip，回到 kill syscall 之后继续。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（二十三）：procfs 进程文件系统</title>
      <link>https://www.dafei.me/posts/os-23-procfs/</link>
      <pubDate>Thu, 14 May 2026 03:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-23-procfs/</guid>
      <description>&lt;p&gt;上一章有了 pipe 和 fd，现在进程可以互相传数据了。但还有一个问题：用户程序没有办法查询&amp;quot;系统里现在有哪些进程&amp;quot;——这类信息只存在内核里，外面拿不到。&lt;/p&gt;
&lt;p&gt;加专用 syscall 当然可以，但 Linux 选择了一个更优雅的方案：&lt;strong&gt;procfs&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一切皆文件&#34;&gt;一切皆文件&lt;/h2&gt;
&lt;p&gt;procfs 的思路是：把内核状态&lt;strong&gt;伪装成文件&lt;/strong&gt;，挂在 &lt;code&gt;/proc/&lt;/code&gt; 目录下。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/proc/0/status   → 进程 0 的信息
/proc/1/status   → 进程 1 的信息
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;用户程序用完全一样的 &lt;code&gt;open/read/close&lt;/code&gt; 就能读到，不需要学新 API：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; fd &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;open&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/proc/1/status&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;read&lt;/span&gt;(fd, buf, &lt;span style=&#34;color:#ae81ff&#34;&gt;128&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// buf 里是 &amp;#34;pid: 1\nstate: running\nparent: 0\n&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这就是 Linux &amp;ldquo;一切皆文件&amp;rdquo; 哲学的体现——统一接口，背后实现可以完全不同。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;虚拟文件和真实文件的区别&#34;&gt;虚拟文件和真实文件的区别&lt;/h2&gt;
&lt;p&gt;普通文件的 &lt;code&gt;read&lt;/code&gt;：从磁盘读数据。&lt;/p&gt;
&lt;p&gt;procfs 文件的 &lt;code&gt;read&lt;/code&gt;：&lt;strong&gt;内核现场生成内容&lt;/strong&gt;，没有磁盘，没有 inode，数据就是 &lt;code&gt;procs[]&lt;/code&gt; 数组里的字段格式化出来的字符串。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;open(&amp;#34;/proc/1/status&amp;#34;)
  → vfs_open 识别 &amp;#34;/proc/&amp;#34; 前缀
  → procfs_open 解析 pid，格式化状态字符串存入内核 buf
  → 返回 VFile（type=VFILE_PROC）

read(fd, buf, len)
  → vfs_read 识别 VFILE_PROC
  → 从内核 buf 按 offset 拷贝给用户
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id=&#34;实现&#34;&gt;实现&lt;/h2&gt;
&lt;h3 id=&#34;procfs-内核结构&#34;&gt;procfs 内核结构&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;      used;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; pid;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt;     buf[&lt;span style=&#34;color:#ae81ff&#34;&gt;256&lt;/span&gt;];   &lt;span style=&#34;color:#75715e&#34;&gt;// open 时格式化好的字符串
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; buf_len;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; offset;     &lt;span style=&#34;color:#75715e&#34;&gt;// 支持分段 read
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;proc_fd_t&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;open&lt;/code&gt; 时一次性生成完整字符串，后续 &lt;code&gt;read&lt;/code&gt; 只是按 offset 切片拷贝——和普通文件行为完全一致，可以多次 read。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（二十二）：管道 pipe 与 dup2</title>
      <link>https://www.dafei.me/posts/os-22-pipe/</link>
      <pubDate>Thu, 14 May 2026 02:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-22-pipe/</guid>
      <description>&lt;p&gt;上一章实现了文件描述符，进程可以用 &lt;code&gt;open/read/close&lt;/code&gt; 访问 ext2 文件。但两个进程之间还没有办法通信。shell pipeline &lt;code&gt;cmd1 | cmd2&lt;/code&gt; 的本质是：cmd1 的输出直接流进 cmd2 的输入，中间不落磁盘。&lt;/p&gt;
&lt;p&gt;这一章实现 &lt;strong&gt;pipe&lt;/strong&gt; 和 &lt;strong&gt;dup2&lt;/strong&gt;，打通进程间通信的最小路径。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;pipe-是什么&#34;&gt;pipe 是什么&lt;/h2&gt;
&lt;p&gt;pipe 是内核里的一块环形字节缓冲区。系统调用 &lt;code&gt;pipe(fds)&lt;/code&gt; 返回两个文件描述符：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;fds[0] = 读端    fds[1] = 写端

写进程 → fds[1] → [内核 ring buffer 256字节] → fds[0] → 读进程
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;和普通文件一样，管道也走 fd → VFS → 底层数据 这三层，只不过底层数据不是磁盘，而是内核里的 &lt;code&gt;pipe_t&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;dup2-是什么&#34;&gt;dup2 是什么&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;dup2(oldfd, newfd)&lt;/code&gt; 让 &lt;code&gt;newfd&lt;/code&gt; 指向和 &lt;code&gt;oldfd&lt;/code&gt; 同一个内核文件描述符，如果 &lt;code&gt;newfd&lt;/code&gt; 已经打开则先关掉它。&lt;/p&gt;
&lt;p&gt;shell pipeline 的核心操作就是 dup2 + exec：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 子进程（cmd1），stdout → pipe 写端
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;dup2&lt;/span&gt;(fds[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;], &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;close&lt;/span&gt;(fds[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;close&lt;/span&gt;(fds[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;exec&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cmd1&amp;#34;&lt;/span&gt;);    &lt;span style=&#34;color:#75715e&#34;&gt;// cmd1 以为自己在写 stdout，实际写进了管道
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 父进程（cmd2），stdin → pipe 读端
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;dup2&lt;/span&gt;(fds[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;close&lt;/span&gt;(fds[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;close&lt;/span&gt;(fds[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]);   &lt;span style=&#34;color:#75715e&#34;&gt;// 关掉写端，否则 cmd2 永远等不到 EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;exec&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cmd2&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;pipe-的-eof-语义&#34;&gt;pipe 的 EOF 语义&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;read&lt;/code&gt; 管道什么时候返回 0（EOF）？&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（二十一）：文件描述符 fd</title>
      <link>https://www.dafei.me/posts/os-21-fd/</link>
      <pubDate>Thu, 14 May 2026 01:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-21-fd/</guid>
      <description>&lt;p&gt;前几章的用户程序如果想读文件，得直接传 inode 号给内核——&lt;code&gt;read(ino, offset, buf, len)&lt;/code&gt;。这意味着用户程序要自己记当前读到哪了，而且根本不知道文件名，只有一个数字。&lt;/p&gt;
&lt;p&gt;这一章引入 &lt;strong&gt;文件描述符（fd）&lt;/strong&gt;，让用户程序用熟悉的方式操作文件：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; fd &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;open&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hello.txt&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; n  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;read&lt;/span&gt;(fd, buf, &lt;span style=&#34;color:#ae81ff&#34;&gt;64&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;close&lt;/span&gt;(fd);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;fd-是什么&#34;&gt;fd 是什么&lt;/h2&gt;
&lt;p&gt;fd 是一个&lt;strong&gt;进程内的整数&lt;/strong&gt;，通常从 0 开始分配（0=stdin，1=stdout，2=stderr，之后是普通文件）。&lt;/p&gt;
&lt;p&gt;它背后有三层：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;进程 fd_table[]          内核 open_file             磁盘
  fd=3  ──────────→   { ino=42, offset=0 }  ──→  hello.txt
  fd=4  ──────────→   { ino=43, offset=0 }  ──→  readme.txt
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;fd 本身只是下标&lt;/strong&gt;，真正存 offset 的是内核里的&amp;quot;打开文件&amp;quot;结构。这样的好处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;offset 由内核自动推进，用户不用管&lt;/li&gt;
&lt;li&gt;fork 后父子可以共享同一个打开文件（共享 offset）&lt;/li&gt;
&lt;li&gt;文件、管道、设备用同一套接口，背后实现不同&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;实现每个进程一张-fd-表&#34;&gt;实现：每个进程一张 fd 表&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;process_t&lt;/code&gt; 里加一个数组：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int32_t&lt;/span&gt; fd_table[PROC_MAX_FD];  &lt;span style=&#34;color:#75715e&#34;&gt;// fd → vfs_fd，-1 表示空槽
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;process_t&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;fd_table[fd]&lt;/code&gt; 存的是 VFS 层的内部 fd（VFile 数组下标）。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（二十）：wait / exit 完整语义</title>
      <link>https://www.dafei.me/posts/os-20-wait-exit/</link>
      <pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-20-wait-exit/</guid>
      <description>&lt;p&gt;上一章实现了 &lt;code&gt;exec()&lt;/code&gt;，用户程序能跑起来了。但进程退出时只是把进程表项清掉，父进程拿不到退出码，也没有办法等待。这一章实现完整的 &lt;code&gt;exit&lt;/code&gt; / &lt;code&gt;wait&lt;/code&gt; 语义。&lt;/p&gt;
&lt;h2 id=&#34;为什么进程退出后不能直接消失&#34;&gt;为什么进程退出后不能直接消失&lt;/h2&gt;
&lt;p&gt;直觉上，进程退出就应该立刻清掉进程表项。但这里有个问题：&lt;strong&gt;父进程怎么拿到子进程的退出码？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果进程表项直接清掉，退出码就丢了。父进程调用 &lt;code&gt;wait()&lt;/code&gt; 的时机可能比子进程 &lt;code&gt;exit()&lt;/code&gt; 晚，也可能早——无论哪种情况，父进程都需要能读到那个退出码。&lt;/p&gt;
&lt;p&gt;Linux 的解法是引入**僵尸（zombie）**状态：进程 &lt;code&gt;exit()&lt;/code&gt; 后不立即消失，保留退出码，等父进程 &lt;code&gt;wait()&lt;/code&gt; 读取后才彻底释放。&lt;/p&gt;
&lt;p&gt;这就是为什么 &lt;code&gt;ps&lt;/code&gt; 输出里偶尔会出现状态为 &lt;code&gt;Z&lt;/code&gt; 的进程——它已经退出了，只是在等父进程来&amp;quot;收尸&amp;quot;。&lt;/p&gt;
&lt;h2 id=&#34;进程状态机的变化&#34;&gt;进程状态机的变化&lt;/h2&gt;
&lt;p&gt;原来进程只有三个状态：UNUSED、READY、RUNNING。这一章加两个：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;UNUSED → READY → RUNNING
                    ↓ exit()
                 ZOMBIE   ← 已退出，等父进程 wait
                    ↑ 唤醒
                 BLOCKED  ← 父进程 wait 时挂起
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;调度器只挑 READY 的进程运行，ZOMBIE 和 BLOCKED 自然就不会被调度到，不需要改调度器的逻辑。&lt;/p&gt;
&lt;h2 id=&#34;exit-和-wait-的实现思路&#34;&gt;exit 和 wait 的实现思路&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;exit&lt;/strong&gt; 做三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把退出码存到进程表项，状态改为 ZOMBIE&lt;/li&gt;
&lt;li&gt;如果父进程正在 BLOCKED 等待，把它改回 READY（唤醒）&lt;/li&gt;
&lt;li&gt;切回内核栈和内核页表，&lt;code&gt;hlt&lt;/code&gt; 循环等待被回收&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;wait&lt;/strong&gt; 做的事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;扫描进程表，找有没有自己的子进程且状态是 ZOMBIE&lt;/li&gt;
&lt;li&gt;找到了：读退出码，把它设为 UNUSED，返回退出码&lt;/li&gt;
&lt;li&gt;没找到但有活着的子进程：挂起等待（ring3）或返回 -2 让调用方重试（ring0）&lt;/li&gt;
&lt;li&gt;没有子进程：返回 -1&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;parent_pid&lt;/code&gt; 需要在 &lt;code&gt;exec&lt;/code&gt; / &lt;code&gt;fork&lt;/code&gt; 时记下来，这样 &lt;code&gt;exit&lt;/code&gt; 才知道该唤醒谁，&lt;code&gt;wait&lt;/code&gt; 才知道哪些进程是自己的子进程。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（十九）：exec 与用户程序</title>
      <link>https://www.dafei.me/posts/os-19-exec/</link>
      <pubDate>Sat, 09 May 2026 00:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-19-exec/</guid>
      <description>&lt;p&gt;之前所有代码都跑在内核态（ring0）。这一章第一次让用户程序在 ring3 里运行——加载 ELF 文件，跳到用户态执行，用户程序通过 syscall 和内核通信。&lt;/p&gt;
&lt;p&gt;这是操作系统最核心的一个边界：内核和用户程序之间的隔离。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;exec-做什么&#34;&gt;exec 做什么&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;exec&lt;/code&gt; 的语义是&amp;quot;用一个新程序替换当前进程&amp;quot;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从文件系统读 ELF 文件&lt;/li&gt;
&lt;li&gt;解析 ELF header，找到各个段的加载地址&lt;/li&gt;
&lt;li&gt;创建新的用户页表，把各段加载进去&lt;/li&gt;
&lt;li&gt;分配用户栈&lt;/li&gt;
&lt;li&gt;设置进程的 rip = 入口地址，rsp = 栈顶，cs/ss = 用户段，放入就绪队列&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;调度器下次选中这个进程，就会跳到用户态开始执行。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;进入用户态iretq&#34;&gt;进入用户态：iretq&lt;/h2&gt;
&lt;p&gt;第一次进入用户态不能用 &lt;code&gt;sysret&lt;/code&gt;——没有对应的 &lt;code&gt;syscall&lt;/code&gt;。用 &lt;code&gt;iretq&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;enter_usermode&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; rip, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; cs,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; rflags, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; rsp, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; ss) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    __asm__ &lt;span style=&#34;color:#66d9ef&#34;&gt;volatile&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;push %4&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;// ss
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;push %3&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;// rsp
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;push %2&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;// rflags（IF=1，开中断）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;push %1&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;// cs（0x23，ring3 代码段）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;push %0&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;// rip（entry point）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;iretq&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;r&amp;#34;&lt;/span&gt;(rip), &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;r&amp;#34;&lt;/span&gt;(cs), &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;r&amp;#34;&lt;/span&gt;(rflags), &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;r&amp;#34;&lt;/span&gt;(rsp), &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;r&amp;#34;&lt;/span&gt;(ss)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;iretq&lt;/code&gt; 弹出这 5 个字段，CPU 检查 cs 的 RPL 发现是 ring3，自动完成特权级切换。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（十八）：fork 与 Copy-on-Write</title>
      <link>https://www.dafei.me/posts/os-18-fork-cow/</link>
      <pubDate>Fri, 08 May 2026 00:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-18-fork-cow/</guid>
      <description>&lt;p&gt;上一章每个进程有了自己的地址空间。这一章实现 &lt;code&gt;fork()&lt;/code&gt;——创建一个子进程，继承父进程的全部内存。&lt;/p&gt;
&lt;h2 id=&#34;最朴素的-fork-实现&#34;&gt;最朴素的 fork 实现&lt;/h2&gt;
&lt;p&gt;fork 的语义是&amp;quot;完整复制当前进程&amp;quot;。最直接的做法：遍历父进程页表，找到每一个物理页，分配新页，复制 4096 字节内容，给子进程建新映射。&lt;/p&gt;
&lt;p&gt;能用，但很浪费。大多数 &lt;code&gt;fork()&lt;/code&gt; 之后会紧接着 &lt;code&gt;exec()&lt;/code&gt;——旧内存根本用不上，全拷了白拷。进程堆如果有几十 MB，每次 fork 都要等好几毫秒。&lt;/p&gt;
&lt;h2 id=&#34;copy-on-write先共享写了再分&#34;&gt;Copy-on-Write：先共享，写了再分&lt;/h2&gt;
&lt;p&gt;CoW 的思路是：&lt;strong&gt;fork 时不复制，让父子共享同一批物理页；等到谁要写，再给他一份新的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;实现上分两步：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步：fork 时打标记&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;遍历父进程用户页表，对每一页做两件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;清掉 WRITABLE 位，变成只读&lt;/li&gt;
&lt;li&gt;打上 PAGE_COW 标志（借用 x86 页表的 bit 9，这一位 CPU 不使用，留给软件自定义）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;子进程页表复制同一个物理页地址，同样只读 + CoW。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;fork 后：
  父进程 → PT → 物理页 0xA000  (只读, CoW)
                    ↑ 共享
  子进程 → PT → 物理页 0xA000  (只读, CoW)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;注意一个关键细节：&lt;strong&gt;PDPT/PD/PT 这三级结构页必须为子进程单独分配&lt;/strong&gt;。如果父子共用同一棵结构树，后续 &lt;code&gt;vmm_map_page&lt;/code&gt; 修改子进程时会把父进程的 PT 一起改掉，隔离失效。数据页可以共享，结构页不能。&lt;/p&gt;
&lt;p&gt;另一个细节：改完父进程页表后必须刷新 TLB。否则 CPU 缓存里还是旧的可写映射，父进程写该页不会触发 fault，CoW 形同虚设。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（十七）：每个进程有自己的地址空间</title>
      <link>https://www.dafei.me/posts/os-17-address-space/</link>
      <pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-17-address-space/</guid>
      <description>&lt;p&gt;前几章的进程共享同一张页表——所有进程看到的是同一片内存。这意味着进程 A 知道进程 B 的地址，就能直接读写它的数据。一个 bug 就能破坏整个系统。&lt;/p&gt;
&lt;p&gt;这一章解决这个问题：&lt;strong&gt;给每个进程一张自己的页表，互相看不见彼此的内存&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;切换页表就是切换世界&#34;&gt;切换页表就是切换世界&lt;/h2&gt;
&lt;p&gt;x86-64 的虚拟地址翻译规则写在页表里，页表的根地址放在 &lt;strong&gt;CR3 寄存器&lt;/strong&gt;里。&lt;/p&gt;
&lt;p&gt;这意味着：写 CR3 就是切换地址空间。进程 A 跑的时候 CR3 指向 A 的页表，进程 B 跑的时候 CR3 指向 B 的页表。同一个虚拟地址 &lt;code&gt;0x400000&lt;/code&gt;，在 A 里翻译到物理页 X，在 B 里翻译到物理页 Y——两边完全隔离，互不干扰。&lt;/p&gt;
&lt;p&gt;切换进程时，只需要一行：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;vmm_switch&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;pml4) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    __asm__ &lt;span style=&#34;color:#66d9ef&#34;&gt;volatile&lt;/span&gt; (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mov %0, %%cr3&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;r&amp;#34;&lt;/span&gt;((&lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt;)pml4) &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;memory&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;硬件帮我们做了全部翻译工作。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;创建新页表&#34;&gt;创建新页表&lt;/h2&gt;
&lt;p&gt;每个进程需要自己的 PML4。但不能从空白开始——内核代码的映射必须保留，否则切过去之后 CPU 取不到内核指令，立刻 Page Fault。&lt;/p&gt;
&lt;p&gt;做法：把内核的 &lt;code&gt;kernel_pml4&lt;/code&gt; 整张复制一份作为起点，然后再往里加用户空间的映射。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;vmm_create_page_table&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;new_pml4 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color:#a6e22e&#34;&gt;pmm_alloc&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;512&lt;/span&gt;; i&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        new_pml4[i] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; kernel_pml4[i];  &lt;span style=&#34;color:#75715e&#34;&gt;// 继承内核映射
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; new_pml4;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;往指定页表里建映射&#34;&gt;往指定页表里建映射&lt;/h2&gt;
&lt;p&gt;之前的 &lt;code&gt;map_page&lt;/code&gt; 只能操作当前 CR3 指向的页表。现在需要给进程建映射，但不想先切换过去，所以新接口接受一个显式的 &lt;code&gt;pml4&lt;/code&gt; 参数：&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（十六）：ELF 加载器，运行第一个用户程序</title>
      <link>https://www.dafei.me/posts/os-16-elf/</link>
      <pubDate>Wed, 06 May 2026 16:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-16-elf/</guid>
      <description>&lt;p&gt;这一章做完，我们的系统就有了完整的用户程序执行链路：写一个独立的程序，编译成 ELF，放到磁盘上，Shell 里输入 &lt;code&gt;run hello.elf&lt;/code&gt;，内核加载并执行它。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;elf-是什么&#34;&gt;ELF 是什么&lt;/h2&gt;
&lt;p&gt;ELF（Executable and Linkable Format）是 Linux 可执行文件的格式。你编译出来的每个程序、&lt;code&gt;/bin/ls&lt;/code&gt;、&lt;code&gt;/usr/bin/python&lt;/code&gt; 都是 ELF 文件。&lt;/p&gt;
&lt;p&gt;结构很简单：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ELF Header        → 魔数、架构、入口地址
Program Headers   → 每个段加载到内存哪里、从文件哪里读
.text             → 代码
.data             → 数据
.bss              → 未初始化数据（文件里不占空间，加载时清零）
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;加载一个 ELF，本质上就是：&lt;strong&gt;按 Program Header 的指示，把文件里的数据复制到内存里，然后跳到入口地址&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;先写用户程序&#34;&gt;先写用户程序&lt;/h2&gt;
&lt;p&gt;用户程序不能调内核函数，只能通过 &lt;code&gt;syscall&lt;/code&gt; 和内核通信：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-asm&#34; data-lang=&#34;asm&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;BITS&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;section&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;.text&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;global&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;_start&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_start:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;mov&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;rax&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;              &lt;span style=&#34;color:#75715e&#34;&gt;; syscall 1 = print
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;lea&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;rdi&lt;/span&gt;, [&lt;span style=&#34;color:#66d9ef&#34;&gt;rel&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;msg&lt;/span&gt;]      &lt;span style=&#34;color:#75715e&#34;&gt;; RIP 相对寻址
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;syscall&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;mov&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;rax&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;              &lt;span style=&#34;color:#75715e&#34;&gt;; syscall 2 = exit
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;syscall&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;section&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;.data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;msg: &lt;span style=&#34;color:#a6e22e&#34;&gt;db&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Hello&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ELF&lt;/span&gt;!&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;13&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;链接时指定加载地址 &lt;code&gt;0x400000&lt;/code&gt;（避开内核占用的低地址区域）：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-ld&#34; data-lang=&#34;ld&#34;&gt;ENTRY(_start)
SECTIONS {
    . = 0x400000;
    .text : { *(.text) }
    .data : { *(.data) }
}
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id=&#34;elf-加载器&#34;&gt;ELF 加载器&lt;/h2&gt;
&lt;p&gt;加载流程：&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（十五）：挂载 ext2，读真实文件系统</title>
      <link>https://www.dafei.me/posts/os-15-ext2/</link>
      <pubDate>Wed, 06 May 2026 15:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-15-ext2/</guid>
      <description>&lt;p&gt;前几章的文件系统是存在内存里的——重启数据就没了，文件名也是硬编码的。这一章做真实的：挂载一个 ext2 磁盘镜像，让 Shell 能读取里面的文件。&lt;/p&gt;
&lt;p&gt;上一章已经有了 ATA 驱动，能按扇区号读磁盘。现在的问题是：磁盘上的数据是怎么组织的？&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;ext2-的结构&#34;&gt;ext2 的结构&lt;/h2&gt;
&lt;p&gt;ext2 是 Linux 最经典的文件系统，ext3/ext4 都是在它基础上演化来的。理解 ext2，基本上就理解了现代文件系统的核心思路。&lt;/p&gt;
&lt;p&gt;磁盘从偏移 1024 字节开始是 &lt;strong&gt;Superblock&lt;/strong&gt;，存整个文件系统的基本参数：block 大小是多少、有多少 inode、magic number 是 &lt;code&gt;0xEF53&lt;/code&gt;（用来确认这确实是 ext2）。&lt;/p&gt;
&lt;p&gt;接下来是 &lt;strong&gt;Group Descriptor&lt;/strong&gt;，告诉你 inode table 在哪个 block。&lt;/p&gt;
&lt;p&gt;然后才是真正的数据区：inode table 和数据块。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inode&lt;/strong&gt; 是文件的&amp;quot;身份证&amp;quot;。每个文件有一个唯一的 inode 号，inode 里存着文件大小、权限，以及最重要的——&lt;code&gt;i_block[0..11]&lt;/code&gt;，12 个直接指向数据块的指针。想读文件内容，就顺着这些指针去读对应的数据块。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;目录&lt;/strong&gt;也是文件，它的数据块里存的是一条条 &lt;code&gt;ext2_dir_entry&lt;/code&gt;：每条记录包含 inode 号、文件名长度、文件名。&lt;code&gt;ls&lt;/code&gt; 就是读根目录的数据块，遍历这些记录。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;读文件的完整路径&#34;&gt;读文件的完整路径&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Superblock  → 拿到 block_size、inodes_per_group
GroupDesc   → 拿到 inode table 的起始 block 号
Inode[ino]  → 拿到文件大小和 i_block[]
DataBlock   → 真正的文件内容
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;读目录（ls）就在最后一步多做一件事：把数据块里的 &lt;code&gt;ext2_dir_entry&lt;/code&gt; 链遍历一遍。&lt;/p&gt;
&lt;p&gt;每一步都需要从磁盘读若干个扇区——这正是上一章 ATA 驱动的用武之地。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（十四）：ATA 驱动，让内核能读磁盘</title>
      <link>https://www.dafei.me/posts/os-14-ata/</link>
      <pubDate>Wed, 06 May 2026 14:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-14-ata/</guid>
      <description>&lt;p&gt;前面的文件系统都是存在内存里的——重启数据就没了。要读真实磁盘，得先搞清楚操作系统怎么和磁盘&amp;quot;说话&amp;quot;。这一章做 ATA 磁盘驱动。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;磁盘和内核怎么通信&#34;&gt;磁盘和内核怎么通信&lt;/h2&gt;
&lt;p&gt;硬盘插在主板上，操作系统通过 &lt;strong&gt;ATA 协议&lt;/strong&gt;和它通信。ATA 协议规定了一组固定的 x86 I/O 端口，内核用 &lt;code&gt;in&lt;/code&gt;/&lt;code&gt;out&lt;/code&gt; 指令直接操作这些端口，就能控制磁盘。&lt;/p&gt;
&lt;p&gt;这叫 &lt;strong&gt;PIO 模式&lt;/strong&gt;（Programmed I/O）——CPU 亲自一个字搬一个字地读数据。慢，但实现只需要几十行代码，是学习的最佳起点。&lt;/p&gt;
&lt;p&gt;真实生产内核用 DMA（磁盘直接写内存，CPU 不搬数据），那是后话。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;磁盘的最小单位扇区&#34;&gt;磁盘的最小单位：扇区&lt;/h2&gt;
&lt;p&gt;磁盘被切成 512 字节的&lt;strong&gt;扇区&lt;/strong&gt;，每个扇区有一个编号，叫 &lt;strong&gt;LBA&lt;/strong&gt;（Logical Block Address），从 0 开始数。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;LBA=0 → 前 512 字节（Boot Sector）
LBA=1 → 512~1023 字节
LBA=2 → 1024~1535 字节（ext2 Superblock 就在这里）
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;要读文件系统里偏移 1024 字节的内容，就是读 &lt;code&gt;LBA = 1024 / 512 = 2&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;ata-端口&#34;&gt;ATA 端口&lt;/h2&gt;
&lt;p&gt;ATA 协议设计于 1980 年代，把控制磁盘的所有操作映射到一组固定的 I/O 端口号上——这是当时 PC 硬件的惯例，如今这些端口号已经写死在无数设备里，成了不能改的&amp;quot;历史遗产&amp;quot;。&lt;/p&gt;
&lt;p&gt;Primary ATA 控制器的端口：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;端口&lt;/th&gt;
          &lt;th&gt;用途&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;0x1F0&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;数据（读写 16-bit）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;0x1F2&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;要读几个扇区&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;0x1F3&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;LBA[7:0]&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;0x1F4&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;LBA[15:8]&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;0x1F5&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;LBA[23:16]&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;0x1F6&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;选盘 + LBA[27:24]&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;0x1F7&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;命令（写）/ 状态（读）&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;状态寄存器的两个关键位：&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（十三）：Shell，把所有东西串起来</title>
      <link>https://www.dafei.me/posts/os-13-shell/</link>
      <pubDate>Wed, 06 May 2026 13:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-13-shell/</guid>
      <description>&lt;p&gt;前十二章，内核从一个 512 字节的 Bootloader 长成了有进程调度、文件系统、VFS 的微型系统。但用户还没有办法和它交互。&lt;/p&gt;
&lt;p&gt;这一章做 &lt;strong&gt;Shell&lt;/strong&gt;——一个可以敲命令操作文件的命令行。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;先把概念搞清楚&#34;&gt;先把概念搞清楚&lt;/h2&gt;
&lt;h3 id=&#34;shell-是普通进程&#34;&gt;Shell 是普通进程&lt;/h3&gt;
&lt;p&gt;Shell 不是内核的一部分。它是一个运行在用户态的普通进程，通过系统调用和内核打交道，和 &lt;code&gt;ls&lt;/code&gt;、&lt;code&gt;cat&lt;/code&gt; 这些程序的地位完全一样。&lt;/p&gt;
&lt;p&gt;真实系统里，Shell 用 &lt;code&gt;fork + exec&lt;/code&gt; 启动外部命令。我们这里简化：四条命令直接内嵌在 Shell 进程里，不做 fork/exec。&lt;/p&gt;
&lt;h3 id=&#34;tokenize命令行的第一步&#34;&gt;tokenize：命令行的第一步&lt;/h3&gt;
&lt;p&gt;用户输入一行字符串，Shell 要把它拆成命令名和参数：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;#34;write hello.txt world&amp;#34;
        ↓ tokenize
argv[0] = &amp;#34;write&amp;#34;
argv[1] = &amp;#34;hello.txt&amp;#34;
argv[2] = &amp;#34;world&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;做法是遍历字符串，遇到空格就写入 &lt;code&gt;\0&lt;/code&gt; 截断，记录每段的起始地址。不需要任何库函数，20 行搞定。&lt;/p&gt;
&lt;h3 id=&#34;串口-io&#34;&gt;串口 I/O&lt;/h3&gt;
&lt;p&gt;键盘输入通过串口读取（x86 端口 &lt;code&gt;0x3F8&lt;/code&gt;）。QEMU 的 &lt;code&gt;-serial mon:stdio&lt;/code&gt; 把宿主机终端直接映射到串口，你在终端敲的每个字符都会被 &lt;code&gt;in&lt;/code&gt; 指令读到。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;实现了什么&#34;&gt;实现了什么&lt;/h2&gt;
&lt;p&gt;四条命令：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;命令&lt;/th&gt;
          &lt;th&gt;功能&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;write &amp;lt;文件&amp;gt; &amp;lt;内容&amp;gt;&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;创建文件并写入内容&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;read &amp;lt;文件&amp;gt;&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;读取文件内容并打印&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;ls&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;列出根目录所有文件&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;help&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;打印帮助&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;关键代码&#34;&gt;关键代码&lt;/h2&gt;
&lt;h3 id=&#34;readline读一行支持-backspace&#34;&gt;readline：读一行，支持 backspace&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;readline&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;buf, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; maxlen) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; maxlen &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; c &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;serial_getchar&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (c &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\r&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; c &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\n&amp;#39;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;serial_print&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (c &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;127&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; c &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\b&amp;#39;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) { i&lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;serial_print&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\b&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\b&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;); }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        buf[i&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; c;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; echo[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {c, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;serial_print&lt;/span&gt;(echo);   &lt;span style=&#34;color:#75715e&#34;&gt;// 回显给用户
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    buf[i] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\0&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; i;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;\b \b&lt;/code&gt; 是终端删除字符的标准做法：退格、打空格覆盖、再退格。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（十二）：VFS，让系统调用不认识具体文件系统</title>
      <link>https://www.dafei.me/posts/os-12-vfs/</link>
      <pubDate>Wed, 06 May 2026 12:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-12-vfs/</guid>
      <description>&lt;p&gt;上一章我们写了一个 SimpleFS，可以创建文件、读写内容。但现在有个问题：&lt;code&gt;open()&lt;/code&gt; 系统调用直接调的是 &lt;code&gt;fs_create&lt;/code&gt;、&lt;code&gt;fs_read&lt;/code&gt; 这些 SimpleFS 专属函数。&lt;/p&gt;
&lt;p&gt;如果哪天要支持 FAT32，就得去改系统调用的代码。这显然不对。&lt;/p&gt;
&lt;p&gt;这一章加一层 &lt;strong&gt;VFS（Virtual File System，虚拟文件系统）&lt;/strong&gt;，把系统调用和具体文件系统隔开。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;先把概念搞清楚&#34;&gt;先把概念搞清楚&lt;/h2&gt;
&lt;h3 id=&#34;间接层解耦&#34;&gt;间接层解耦&lt;/h3&gt;
&lt;p&gt;软件里有句老话：&lt;strong&gt;任何问题都可以通过加一层间接层解决&lt;/strong&gt;。VFS 就是这层间接层。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;用户进程
   ↓  open / read / write
  VFS（统一接口）
   ↓              ↓
SimpleFS         FAT32
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;系统调用只跟 VFS 说话，VFS 再转发给具体 FS。新增一种文件系统，只需要实现 VFS 要求的那几个函数，不动系统调用层。&lt;/p&gt;
&lt;h3 id=&#34;file_operationsc-语言的多态&#34;&gt;file_operations：C 语言的多态&lt;/h3&gt;
&lt;p&gt;VFS 要求每种文件系统提供一张&lt;strong&gt;函数指针表&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;read) (...);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;write)(...);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;lookup)(...);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;create)(...);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} FileOps;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;VFS 调 &lt;code&gt;fops-&amp;gt;read(...)&lt;/code&gt; 时，实际执行的是哪个函数，取决于挂载时注册的是哪张表。这是 C 语言实现&amp;quot;多态&amp;quot;的标准手法，Linux 内核里到处都是这个模式。&lt;/p&gt;
&lt;h3 id=&#34;vnodevfs-的通用-inode&#34;&gt;vnode：VFS 的通用 inode&lt;/h3&gt;
&lt;p&gt;具体 FS 有自己的 inode，VFS 层包一层 &lt;code&gt;vnode&lt;/code&gt;，里面放着具体 FS 的 inode 号和对应的函数指针表。对上层完全屏蔽了底层差异。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（十一）：文件系统，从磁盘到文件名</title>
      <link>https://www.dafei.me/posts/os-11-filesystem/</link>
      <pubDate>Wed, 06 May 2026 11:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-11-filesystem/</guid>
      <description>&lt;p&gt;到目前为止，内核能跑进程、能做系统调用，但所有数据都在内存里——进程一死，什么都没了。&lt;/p&gt;
&lt;p&gt;这一章做文件系统：把数据写到&amp;quot;磁盘&amp;quot;（我们用内存模拟），下次还能读回来。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;先把概念搞清楚&#34;&gt;先把概念搞清楚&lt;/h2&gt;
&lt;p&gt;动手之前，先理解五件事。&lt;/p&gt;
&lt;h3 id=&#34;为什么需要文件系统&#34;&gt;为什么需要文件系统&lt;/h3&gt;
&lt;p&gt;磁盘本质上就是一个大字节数组。没有文件系统，你只能说&amp;quot;读第 1234 字节&amp;quot;，没法说&amp;quot;读 /etc/passwd&amp;quot;。&lt;/p&gt;
&lt;p&gt;文件系统做的事就是在这个字节数组上建立一套&lt;strong&gt;命名和组织规则&lt;/strong&gt;，让你可以用路径找到数据，而不是手动记偏移量。&lt;/p&gt;
&lt;h3 id=&#34;inode文件名和内容分离&#34;&gt;inode：文件名和内容分离&lt;/h3&gt;
&lt;p&gt;Unix 最重要的设计之一：&lt;strong&gt;inode 描述文件内容，目录存文件名&lt;/strong&gt;，两者分开。&lt;/p&gt;
&lt;p&gt;inode 记录的是&amp;quot;这个文件是什么&amp;quot;——大小、权限、数据在磁盘哪几块——但不存文件名。文件名只是一个指向 inode 的标签，存在目录里。&lt;/p&gt;
&lt;p&gt;这意味着同一个 inode 可以被多个名字指向，这就是&lt;strong&gt;硬链接&lt;/strong&gt;。重命名文件也不需要移动任何数据，只改目录项。&lt;/p&gt;
&lt;h3 id=&#34;超级块文件系统的自我描述&#34;&gt;超级块：文件系统的自我描述&lt;/h3&gt;
&lt;p&gt;挂载一块磁盘时，内核第一件事是读&lt;strong&gt;超级块&lt;/strong&gt;。超级块告诉内核这个文件系统的结构：inode 区从哪里开始、数据块从哪里开始、总共多少块、还有多少空闲。&lt;/p&gt;
&lt;p&gt;超级块损坏 = 整个文件系统不可读。所以 ext4 会在磁盘多个位置备份超级块。&lt;/p&gt;
&lt;h3 id=&#34;目录是普通文件&#34;&gt;目录是普通文件&lt;/h3&gt;
&lt;p&gt;目录没有什么神奇的内部结构，它就是一个普通文件，内容是一张表：&lt;strong&gt;文件名 → inode 号&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ls&lt;/code&gt; 的本质是读这张表然后打印。路径解析 &lt;code&gt;/a/b/c&lt;/code&gt; 就是：读根目录找 &lt;code&gt;a&lt;/code&gt; 的 inode → 把 &lt;code&gt;a&lt;/code&gt; 当目录读，找 &lt;code&gt;b&lt;/code&gt; 的 inode → 把 &lt;code&gt;b&lt;/code&gt; 当目录读，找 &lt;code&gt;c&lt;/code&gt; 的 inode。&lt;/p&gt;
&lt;h3 id=&#34;空闲管理位图&#34;&gt;空闲管理：位图&lt;/h3&gt;
&lt;p&gt;磁盘上哪些块被占用、哪些空闲，用&lt;strong&gt;位图&lt;/strong&gt;记录——1 个 bit 对应 1 个块，0 表示空闲，1 表示已用。分配空间就是找第一个 0 位翻成 1。&lt;/p&gt;
&lt;p&gt;这五个概念搞清楚，下面的代码就是它们的直接翻译。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;文件系统解决什么问题&#34;&gt;文件系统解决什么问题&lt;/h2&gt;
&lt;p&gt;内存是易失的，文件系统负责两件事：&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（十）：系统调用，用户和内核的边界</title>
      <link>https://www.dafei.me/posts/os-10-syscall/</link>
      <pubDate>Wed, 06 May 2026 10:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-10-syscall/</guid>
      <description>&lt;p&gt;前几章的&amp;quot;进程&amp;quot;其实是假的——它们直接跑在内核态，和内核同等权限，可以随意读写任何内存、操作任何硬件。&lt;/p&gt;
&lt;p&gt;真实的操作系统里，用户程序跑在&lt;strong&gt;用户态&lt;/strong&gt;（Ring 3），权限受限，不能直接操作硬件。需要内核帮忙时，必须通过&lt;strong&gt;系统调用&lt;/strong&gt;这扇受控的门进入内核，做完事再回去。&lt;/p&gt;
&lt;p&gt;这一章实现 &lt;code&gt;syscall&lt;/code&gt; / &lt;code&gt;sysret&lt;/code&gt;：用户态和内核态之间最快速的切换机制。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;两种特权级&#34;&gt;两种特权级&lt;/h2&gt;
&lt;p&gt;x86-64 有 4 个特权级（Ring 0～3），操作系统只用两个：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ring 0（内核态）&lt;/strong&gt;：可以执行任何指令，访问任何地址，操作 CR3、MSR 等特权寄存器&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ring 3（用户态）&lt;/strong&gt;：不能执行特权指令，访问不属于自己的内存会触发 #GP 或 Page Fault&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CS 段寄存器的低 2 位（&lt;strong&gt;CPL&lt;/strong&gt;，Current Privilege Level）表示当前特权级。&lt;code&gt;syscall&lt;/code&gt; 指令把 CPL 从 3 切到 0，&lt;code&gt;sysret&lt;/code&gt; 把 CPL 从 0 切回 3。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;为什么用-syscall-而不是中断&#34;&gt;为什么用 syscall 而不是中断&lt;/h2&gt;
&lt;p&gt;早期 Linux 用 &lt;code&gt;int 0x80&lt;/code&gt; 触发系统调用——软件中断，要保存完整的中断栈帧，走 IDT 查表，开销大。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;syscall&lt;/code&gt; / &lt;code&gt;sysret&lt;/code&gt; 是专门为系统调用设计的快速路径：不走 IDT，入口地址直接写在 MSR 里，省去了大量压栈操作。现代 x86-64 系统全部用这对指令。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;配置-msr&#34;&gt;配置 MSR&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;MSR&lt;/strong&gt;（Model Specific Register，型号特定寄存器，CPU 内部的一组控制寄存器，用 &lt;code&gt;rdmsr&lt;/code&gt; / &lt;code&gt;wrmsr&lt;/code&gt; 访问）控制 &lt;code&gt;syscall&lt;/code&gt; 的行为。需要配置 4 个：&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（九）：进程调度，CPU 的分时复用</title>
      <link>https://www.dafei.me/posts/os-09-process/</link>
      <pubDate>Wed, 06 May 2026 09:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-09-process/</guid>
      <description>&lt;p&gt;到目前为止，内核一直是单线程跑到底——一件事没做完，别的什么都不能干。&lt;/p&gt;
&lt;p&gt;这一章让内核同时跑多个进程。每隔一段时间，时钟中断打断当前进程，把 CPU 交给下一个，轮流执行。这就是&lt;strong&gt;多任务&lt;/strong&gt;的核心机制。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;上下文是什么&#34;&gt;上下文是什么&lt;/h2&gt;
&lt;p&gt;进程被打断之后，下次恢复时要从断点继续执行，不能出错。这意味着必须把 CPU 的状态完整保存下来——这份状态就叫&lt;strong&gt;上下文&lt;/strong&gt;（Context）。&lt;/p&gt;
&lt;p&gt;x86-64 ABI 规定了 &lt;strong&gt;callee-saved 寄存器&lt;/strong&gt;（被调用者负责保存的寄存器，C 函数调用约定中，这些寄存器的值在函数调用前后必须保持不变）：&lt;code&gt;rbx&lt;/code&gt;、&lt;code&gt;rbp&lt;/code&gt;、&lt;code&gt;r12&lt;/code&gt;~&lt;code&gt;r15&lt;/code&gt;。加上程序指针 &lt;code&gt;rip&lt;/code&gt; 和栈指针 &lt;code&gt;rsp&lt;/code&gt;，这些就是切换时需要保存和恢复的全部。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; r15, r14, r13, r12;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; rbx, rbp;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; rip;   &lt;span style=&#34;color:#75715e&#34;&gt;// 下次从哪里继续执行
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; rsp;   &lt;span style=&#34;color:#75715e&#34;&gt;// 栈在哪里
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;context_t&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;每个进程一份 context，切换时：把当前进程的寄存器存入 &lt;code&gt;ctx&lt;/code&gt;，再把下一个进程 &lt;code&gt;ctx&lt;/code&gt; 里的值写回寄存器——CPU 就&amp;quot;变身&amp;quot;成另一个进程了。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;进程结构&#34;&gt;进程结构&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define MAX_PROCS  8
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define STACK_SIZE 8192   &lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 每个进程 8KB 内核栈
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PROC_UNUSED &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PROC_READY,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PROC_RUNNING,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;proc_state_t&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;context_t&lt;/span&gt;    ctx;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt;     &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;stack;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;proc_state_t&lt;/span&gt; state;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt;     pid;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;process_t&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;process_t&lt;/span&gt; procs[MAX_PROCS];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; current_proc &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;状态机很简单：&lt;code&gt;UNUSED&lt;/code&gt;（槽位空闲）→ &lt;code&gt;READY&lt;/code&gt;（等待调度）→ &lt;code&gt;RUNNING&lt;/code&gt;（正在跑）→ 被打断后回到 &lt;code&gt;READY&lt;/code&gt;。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（八）：堆分配器，内核的 malloc</title>
      <link>https://www.dafei.me/posts/os-08-heap/</link>
      <pubDate>Wed, 06 May 2026 08:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-08-heap/</guid>
      <description>&lt;p&gt;PMM 负责整页分配，每次给你 4KB。但内核里经常需要分配几十、几百字节的小块——存一个结构体、一条字符串。每次都要一整页，太浪费。&lt;/p&gt;
&lt;p&gt;这一章实现 &lt;code&gt;kmalloc&lt;/code&gt; / &lt;code&gt;kfree&lt;/code&gt;：按需分配任意大小的内存块，用完归还，可以复用。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;设计空闲链表&#34;&gt;设计：空闲链表&lt;/h2&gt;
&lt;p&gt;最经典的堆实现：&lt;strong&gt;空闲链表&lt;/strong&gt;（Free List）。&lt;/p&gt;
&lt;p&gt;在堆空间里，每块内存前面加一个&lt;strong&gt;块头&lt;/strong&gt;（header）记录元数据，所有块串成双向链表：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; block {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt;      size;     &lt;span style=&#34;color:#75715e&#34;&gt;// 这块数据区的大小（不含 header 本身）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt;       is_free;  &lt;span style=&#34;color:#75715e&#34;&gt;// 1=空闲，0=已分配
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; block &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;next;     &lt;span style=&#34;color:#75715e&#34;&gt;// 链表后继
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; block &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;prev;     &lt;span style=&#34;color:#75715e&#34;&gt;// 链表前驱
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;block_t&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;内存布局长这样：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[block_t header | ← size → 数据区 ][block_t header | ← size → 数据区 ] ...
 ↑                                   ↑
 kmalloc 返回这个指针之前的地址        下一块 header
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;kmalloc&lt;/code&gt; 返回的指针指向数据区起始，而不是 header。&lt;code&gt;kfree&lt;/code&gt; 时把指针往前偏移 &lt;code&gt;sizeof(block_t)&lt;/code&gt;，找回 header，再改 &lt;code&gt;is_free = 1&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;堆在哪里&#34;&gt;堆在哪里&lt;/h2&gt;
&lt;p&gt;堆从虚拟地址 &lt;code&gt;0x30000&lt;/code&gt; 开始，链表头指针存在 &lt;code&gt;0x29000&lt;/code&gt;（一个固定位置，方便任何地方都能找到它）：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define HEAP_START      0x30000ULL
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define HEAP_HEAD_ADDR  0x29000ULL
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define heap_head  (*(block_t **)HEAP_HEAD_ADDR)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;初始化时预先映射 4 页（16KB）作为起始堆空间，不够了再动态扩展。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（七）：虚拟内存，给每个程序一个假的地址空间</title>
      <link>https://www.dafei.me/posts/os-07-vmm/</link>
      <pubDate>Wed, 06 May 2026 07:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-07-vmm/</guid>
      <description>&lt;p&gt;上一章内核能分配物理页了，但用的全是物理地址。&lt;/p&gt;
&lt;p&gt;物理地址有个问题：全局唯一，谁都能访问。进程 A 如果知道进程 B 的物理地址，直接就能读写它的数据。这不行。&lt;/p&gt;
&lt;p&gt;解决方案是&lt;strong&gt;虚拟内存&lt;/strong&gt;：每个程序看到的地址都是&amp;quot;假的&amp;quot;，CPU 访问时由硬件自动翻译成真实的物理地址。程序互相隔离，谁也看不见谁。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;地址翻译的硬件机制四级页表&#34;&gt;地址翻译的硬件机制：四级页表&lt;/h2&gt;
&lt;p&gt;x86-64 的地址翻译靠 &lt;strong&gt;MMU&lt;/strong&gt;（Memory Management Unit，内存管理单元，CPU 内部硬件，负责把虚拟地址翻译成物理地址）完成，翻译规则写在页表里，页表的根地址放在 CR3 寄存器里。&lt;/p&gt;
&lt;p&gt;一个 64 位虚拟地址被这样拆开：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;63        48 47    39 38    30 29    21 20    12 11       0
[  符号扩展  | PML4_IDX | PDPT_IDX | PD_IDX | PT_IDX | 页内偏移 ]
               9 bit      9 bit      9 bit    9 bit    12 bit
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;翻译过程是四级查表：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;CR3 → PML4[PML4_IDX] → PDPT[PDPT_IDX] → PD[PD_IDX] → PT[PT_IDX] → 物理页帧
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;每级页表是一个 512 项的数组，每项 8 字节，刚好占满一个 4KB 页。每项的低 12 位是 flags，高位是下一级页表（或最终物理页）的地址。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（六）：内存管理，知道自己有多少地可种</title>
      <link>https://www.dafei.me/posts/os-06-memory/</link>
      <pubDate>Wed, 06 May 2026 06:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-06-memory/</guid>
      <description>&lt;p&gt;内核跑起来之后，第一个绕不开的问题就是内存。&lt;/p&gt;
&lt;p&gt;你不知道这台机器有多少内存，哪些地址段可以用，哪些是硬件保留的。如果随便往一个地址写数据，轻则数据损坏，重则触发异常直接重启。&lt;/p&gt;
&lt;p&gt;这一章解决一件事：&lt;strong&gt;让内核知道内存的全貌，然后有序地分配和回收物理页&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;先问-bios你有多少内存&#34;&gt;先问 BIOS：你有多少内存？&lt;/h2&gt;
&lt;p&gt;我们用 &lt;strong&gt;E820&lt;/strong&gt;（BIOS INT 0x15 AX=E820h，一种标准接口，用来查询物理内存的分布和类型）来探测内存。&lt;/p&gt;
&lt;p&gt;这事必须在实模式下做——切换到长模式之后就再也访问不到 BIOS 服务了。所以探测代码放在 &lt;code&gt;boot.asm&lt;/code&gt; 里，在进入长模式之前完成。&lt;/p&gt;
&lt;p&gt;每次调用 INT 0x15，BIOS 填一条 24 字节的记录：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;base    (8字节) — 这段内存的起始物理地址
length  (8字节) — 这段内存的长度（字节）
type    (4字节) — 类型：1=可用，2=保留，其它=别动
ACPI    (4字节) — 扩展属性，一般忽略
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;循环调用直到 EBX 变成 0，表示枚举完毕。所有记录写入固定地址 &lt;code&gt;0x8000&lt;/code&gt;，第一个 4 字节存条目数量。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-asm&#34; data-lang=&#34;asm&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;mov&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;di&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;MMAP_ADDR&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;; 从 0x8004 开始存条目
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;xor&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ebx&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;ebx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;xor&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bp&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;bp&lt;/span&gt;              &lt;span style=&#34;color:#75715e&#34;&gt;; bp 记条目数量
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.e820_loop:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;mov&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;eax&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0xE820&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;mov&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ecx&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;24&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;mov&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;edx&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x534D4150&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;; &amp;#34;SMAP&amp;#34; 魔数，BIOS 验证用
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0x15&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;jc&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;.e820_done&lt;/span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;; CF=1 表示出错或结束
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;inc&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;add&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;di&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;24&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;test&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ebx&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;ebx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;jz&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;.e820_done&lt;/span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;; ebx=0 表示最后一条
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;jmp&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;.e820_loop&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.e820_done:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;mov&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;dword&lt;/span&gt; [&lt;span style=&#34;color:#66d9ef&#34;&gt;MMAP_ADDR&lt;/span&gt;], &lt;span style=&#34;color:#66d9ef&#34;&gt;ebp&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;; 把条目数量写到 0x8000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;QEMU 上跑出来的内存地图一般长这样：&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（五）：中断，内核的神经系统</title>
      <link>https://www.dafei.me/posts/os-05-idt/</link>
      <pubDate>Wed, 06 May 2026 05:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-05-idt/</guid>
      <description>&lt;p&gt;键盘按下一个键，CPU 是怎么知道的？&lt;/p&gt;
&lt;p&gt;不是轮询——CPU 不会没事一直问&amp;quot;键盘有没有按键&amp;quot;。而是靠&lt;strong&gt;中断&lt;/strong&gt;（Interrupt，硬件或软件触发的信号，让 CPU 暂停当前任务去处理紧急事件）。发生了什么事，硬件主动通知 CPU，CPU 暂停手头的事，跳去处理，处理完再回来。&lt;/p&gt;
&lt;p&gt;这一章要让内核有能力响应中断。没有这个机制，内核什么都干不了。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;中断的分类&#34;&gt;中断的分类&lt;/h2&gt;
&lt;p&gt;x86 的 256 个中断号分三段：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;范围&lt;/th&gt;
          &lt;th&gt;类型&lt;/th&gt;
          &lt;th&gt;例子&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;0～31&lt;/td&gt;
          &lt;td&gt;CPU 异常&lt;/td&gt;
          &lt;td&gt;除零、缺页、非法指令&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;32～47&lt;/td&gt;
          &lt;td&gt;硬件中断&lt;/td&gt;
          &lt;td&gt;时钟、键盘&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;48～255&lt;/td&gt;
          &lt;td&gt;软件中断&lt;/td&gt;
          &lt;td&gt;系统调用&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;CPU 异常是 CPU 自己触发的，比如访问了不该访问的内存，CPU 就抛一个&lt;strong&gt;缺页异常&lt;/strong&gt;（Page Fault，访问未映射的虚拟地址时 CPU 触发的异常，操作系统据此做内存按需分配）。&lt;/p&gt;
&lt;p&gt;硬件中断是外设触发的，经过 &lt;strong&gt;PIC&lt;/strong&gt;（Programmable Interrupt Controller，可编程中断控制器，负责管理外设中断请求并转发给 CPU）发给 CPU。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;idt中断的路由表&#34;&gt;IDT：中断的路由表&lt;/h2&gt;
&lt;p&gt;要处理中断，首先要告诉 CPU：每种中断发生时，去哪个函数处理？&lt;/p&gt;
&lt;p&gt;这张路由表叫 &lt;strong&gt;IDT&lt;/strong&gt;（Interrupt Descriptor Table，中断描述符表，256 项，每项对应一个中断号及其处理函数地址）。跟之前的 GDT 一样，是内存里的一张表，用 &lt;code&gt;lidt&lt;/code&gt; 指令告诉 CPU 位置。&lt;/p&gt;
&lt;p&gt;每个表项里存的是处理函数的地址，还有权限信息。CPU 一旦收到中断，就查这张表，跳到对应函数去执行。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;中断处理的细节&#34;&gt;中断处理的细节&lt;/h2&gt;
&lt;p&gt;CPU 进入中断处理函数之前，会自动把当前的执行状态压栈：&lt;code&gt;rip&lt;/code&gt;（下一条指令地址）、&lt;code&gt;cs&lt;/code&gt;、&lt;code&gt;rflags&lt;/code&gt;、&lt;code&gt;rsp&lt;/code&gt;、&lt;code&gt;ss&lt;/code&gt;。处理完之后用 &lt;code&gt;iretq&lt;/code&gt;（64 位中断返回指令）恢复状态，接着跑。&lt;/p&gt;
&lt;p&gt;有一个坑：某些异常 CPU 会&lt;strong&gt;自动压入错误码&lt;/strong&gt;（比如缺页异常），某些不会（比如除零）。为了让处理函数收到的栈结构一致，对没有错误码的异常，要手动压一个 0 占位。这件事用汇编宏做，每种异常一行，干净利落。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第一个测试故意除以零&#34;&gt;第一个测试：故意除以零&lt;/h2&gt;
&lt;p&gt;IDT 装好之后，最简单的验证方式是&lt;strong&gt;故意触发一个异常&lt;/strong&gt;，看内核能不能捕获到：&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（四）：汇编交棒给 C，最关键的一跳</title>
      <link>https://www.dafei.me/posts/os-04-load-kernel/</link>
      <pubDate>Wed, 06 May 2026 04:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-04-load-kernel/</guid>
      <description>&lt;p&gt;前三章全是汇编。到这里，CPU 已经跑在 64 位长模式下了，但还是什么都做不了。&lt;/p&gt;
&lt;p&gt;这一章要完成最关键的一步：&lt;strong&gt;把控制权从汇编交给 C&lt;/strong&gt;。之后的内核全部用 C 写，汇编只在最必要的地方出现。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;磁盘要怎么布局&#34;&gt;磁盘要怎么布局&lt;/h2&gt;
&lt;p&gt;512 字节的 Bootloader 放不下 C 内核，所以内核单独编译成一个二进制文件，放在磁盘的后续扇区（&lt;strong&gt;扇区&lt;/strong&gt;，Sector，磁盘读写的最小单位，传统上每个 512 字节）：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;扇区 0（512B）：Bootloader
扇区 1～8     ：内核二进制
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Bootloader 启动后，用 BIOS 的磁盘读取中断把扇区 1 开始的内容读到内存地址 &lt;code&gt;0x10000&lt;/code&gt;，然后跳过去执行。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一个容易踩的坑内核入口不在-0x10000&#34;&gt;一个容易踩的坑：内核入口不在 0x10000&lt;/h2&gt;
&lt;p&gt;把内核编译好、加载到 &lt;code&gt;0x10000&lt;/code&gt;，然后 &lt;code&gt;call 0x10000&lt;/code&gt;——看起来没问题，实际上很可能直接崩。&lt;/p&gt;
&lt;p&gt;用 &lt;code&gt;objdump&lt;/code&gt;（反汇编工具，可以查看二进制文件的结构和函数地址）一查才发现：&lt;code&gt;kernel_main&lt;/code&gt; 的实际地址是 &lt;code&gt;0x10109&lt;/code&gt;，不是 &lt;code&gt;0x10000&lt;/code&gt;。&lt;code&gt;0x10000&lt;/code&gt; 开头是另一个函数 &lt;code&gt;outb&lt;/code&gt;，跳过去执行完全是错的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案&lt;/strong&gt;：加一个 &lt;code&gt;entry.asm&lt;/code&gt; 作为内核最开头，内容只有一行：跳转到 &lt;code&gt;kernel_main&lt;/code&gt;。然后在&lt;strong&gt;链接脚本&lt;/strong&gt;（Linker Script，告诉链接器如何拼装各个目标文件、每段放在哪个地址）里把 &lt;code&gt;entry.o&lt;/code&gt; 排在最前面，保证 &lt;code&gt;_start&lt;/code&gt; 恰好落在 &lt;code&gt;0x10000&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这样 Bootloader 跳到 &lt;code&gt;0x10000&lt;/code&gt;，第一条指令就是 &lt;code&gt;jmp kernel_main&lt;/code&gt;，稳了。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;还有一个坑栈&#34;&gt;还有一个坑：栈&lt;/h2&gt;
&lt;p&gt;进入 64 位模式之后，&lt;code&gt;rsp&lt;/code&gt;（栈顶指针寄存器）是未初始化的。C 函数一用栈就崩。&lt;/p&gt;
&lt;p&gt;在跳入内核之前，要先设好栈：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-nasm&#34; data-lang=&#34;nasm&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;mov&lt;/span&gt; rsp, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x90000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;选 &lt;code&gt;0x90000&lt;/code&gt; 是因为这块内存没有被占用，往下增长也不会覆盖内核代码。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;内核编译参数&#34;&gt;内核编译参数&lt;/h2&gt;
&lt;p&gt;内核不能用普通方式编译，要加几个关键选项：&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（三）：进入 64 位，CPU 真正醒了</title>
      <link>https://www.dafei.me/posts/os-03-long-mode/</link>
      <pubDate>Wed, 06 May 2026 03:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-03-long-mode/</guid>
      <description>&lt;p&gt;上一章切到了保护模式，32 位，最多 4GB 内存。&lt;/p&gt;
&lt;p&gt;听起来挺多，但现代系统动不动就几十 GB 内存，4GB 根本不够用。所以还得再往前走一步：进入&lt;strong&gt;长模式&lt;/strong&gt;（Long Mode，x86_64 的 64 位工作模式，支持 128TB 虚拟地址空间）。&lt;/p&gt;
&lt;p&gt;现在跑在你电脑上的 Linux、macOS、Windows，全都在长模式里。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;长模式比保护模式多了什么&#34;&gt;长模式比保护模式多了什么&lt;/h2&gt;
&lt;p&gt;最直接的：寻址空间从 4GB 扩展到理论上的 16EB（16 × 10¹⁸ 字节），实际当前 CPU 支持 128TB，够用很久了。&lt;/p&gt;
&lt;p&gt;但更重要的变化是强制引入了&lt;strong&gt;分页&lt;/strong&gt;（Paging，把物理内存切成固定大小的页，通过页表做虚拟地址到物理地址的映射）。&lt;/p&gt;
&lt;p&gt;保护模式下分页是可选的，长模式下分页是必须开启的。没有页表，CPU 根本不让你进长模式。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;先建页表&#34;&gt;先建页表&lt;/h2&gt;
&lt;p&gt;切换之前，必须在内存里建好&lt;strong&gt;页表&lt;/strong&gt;（Page Table，记录虚拟地址到物理地址映射关系的数据结构）。&lt;/p&gt;
&lt;p&gt;x86_64 用四级页表，一个虚拟地址的翻译路径是：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;虚拟地址 → PML4 → PDPT → PD → PT → 物理地址
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;每级都是一张表，每张表 512 项，每项 8 字节。&lt;/p&gt;
&lt;p&gt;启动阶段不需要映射所有内存，先映射前 2MB 就够了——把四张表放在物理地址 &lt;code&gt;0x1000&lt;/code&gt; 开始的位置，让虚拟地址和物理地址一一对应（&lt;strong&gt;恒等映射&lt;/strong&gt;，虚拟地址和物理地址相同，是启动阶段最简单的映射策略）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;切换步骤&#34;&gt;切换步骤&lt;/h2&gt;
&lt;p&gt;在 32 位保护模式下，按顺序操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;建好页表&lt;/strong&gt;，把 PML4 地址写入 &lt;code&gt;CR3&lt;/code&gt;（页表根寄存器）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开启 PAE&lt;/strong&gt;（Physical Address Extension，物理地址扩展，让 32 位系统支持超过 4GB 的物理地址，长模式必须开启）：&lt;code&gt;CR4.PAE=1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;设置 EFER.LME=1&lt;/strong&gt;（通过 &lt;strong&gt;MSR&lt;/strong&gt; 寄存器（Model Specific Register，CPU 内部的特殊寄存器，用 rdmsr/wrmsr 读写）开启长模式使能位）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开启分页&lt;/strong&gt;：&lt;code&gt;CR0.PG=1&lt;/code&gt;，CPU 正式进入长模式兼容态&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;far jump&lt;/strong&gt; 跳入 64 位代码段，彻底进入 64 位模式&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;顺序不能乱。尤其是第 3 步必须在开启分页之前，不然 CPU 会拒绝。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（二）：跨过这道门，CPU 才算醒了</title>
      <link>https://www.dafei.me/posts/os-02-protected-mode/</link>
      <pubDate>Wed, 06 May 2026 02:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-02-protected-mode/</guid>
      <description>&lt;p&gt;上一章我们让机器打出了第一行字。但那时候 CPU 还处于一种&amp;quot;出厂状态&amp;quot;——&lt;strong&gt;实模式&lt;/strong&gt;，最多只能用 1MB 内存，没有任何权限隔离，任何代码想写哪块内存就写哪块。&lt;/p&gt;
&lt;p&gt;这对一个操作系统来说是不可接受的。&lt;/p&gt;
&lt;p&gt;所以这一章要做一件事：把 CPU 从实模式切换到&lt;strong&gt;保护模式&lt;/strong&gt;（Protected Mode，32 位工作模式，支持内存保护和权限隔离）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;为什么叫保护模式&#34;&gt;为什么叫&amp;quot;保护&amp;quot;模式&lt;/h2&gt;
&lt;p&gt;名字来源很直接——这个模式下，内存是&lt;strong&gt;被保护的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;实模式里，程序可以随意读写任何内存地址，一个 bug 就能把整个系统搞崩。保护模式引入了两个机制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;内存段的权限描述&lt;/strong&gt;：每块内存都有自己的访问权限，越界访问直接触发异常&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特权级别（Ring）&lt;/strong&gt;：内核跑在 Ring 0，用户程序跑在 Ring 3，用户程序没法直接碰内核内存&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;现代操作系统全都建立在保护模式之上。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;切换的关键gdt&#34;&gt;切换的关键：GDT&lt;/h2&gt;
&lt;p&gt;切到保护模式之前，必须先准备好 &lt;strong&gt;GDT&lt;/strong&gt;（Global Descriptor Table，全局描述符表，内存里的一张表，描述每个内存段的地址、大小和权限）。&lt;/p&gt;
&lt;p&gt;可以把 GDT 理解成一个&amp;quot;内存段登记簿&amp;quot;。CPU 进入保护模式后，所有内存访问都要查这张表，看看你有没有权限。&lt;/p&gt;
&lt;p&gt;GDT 最少需要三项：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;索引&lt;/th&gt;
          &lt;th&gt;用途&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;空描述符（CPU 规定必须全零，不能用）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;代码段（可执行，Ring 0）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2&lt;/td&gt;
          &lt;td&gt;数据段（可读写，Ring 0）&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;GDT 写好之后，用 &lt;code&gt;lgdt&lt;/code&gt; 指令告诉 CPU 表在哪里，CPU 才知道去哪查。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;切换步骤&#34;&gt;切换步骤&lt;/h2&gt;
&lt;p&gt;整个切换过程就四步，缺一不可：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;关中断&lt;/strong&gt;（&lt;code&gt;cli&lt;/code&gt;）—— 切换过程不能被打断，否则 CPU 状态会乱&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加载 GDT&lt;/strong&gt;（&lt;code&gt;lgdt&lt;/code&gt;）—— 告诉 CPU 描述符表在哪&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;设置 CR0 寄存器&lt;/strong&gt;（&lt;code&gt;CR0.PE=1&lt;/code&gt;）—— &lt;strong&gt;CR0&lt;/strong&gt;（控制寄存器0，控制CPU工作模式的关键寄存器）第0位置1，CPU 正式进入保护模式&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;far jump 刷新流水线&lt;/strong&gt; —— 跳转的同时加载新的代码段选择子，清空 CPU 的指令预取缓存&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最后这一步很容易漏掉。CPU 有指令流水线，切换模式后如果不强制刷新，可能还在用旧模式的指令译码方式，导致莫名其妙的错误。&lt;/p&gt;</description>
    </item>
    <item>
      <title>从零写OS（一）：让机器听你的第一句话</title>
      <link>https://www.dafei.me/posts/os-01-hello/</link>
      <pubDate>Wed, 06 May 2026 01:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/os-01-hello/</guid>
      <description>&lt;p&gt;电脑开机之后，CPU 做的第一件事是什么？&lt;/p&gt;
&lt;p&gt;不是运行 Linux，不是加载驱动，甚至不是执行你写的任何代码——而是执行一段固化在主板上的程序：&lt;strong&gt;BIOS&lt;/strong&gt;（Basic Input/Output System，主板上的固件，负责硬件自检和引导启动）。&lt;/p&gt;
&lt;p&gt;BIOS 做完硬件自检之后，会去找一个可以启动的磁盘。找到之后，它做了一件很朴素的事：把磁盘最开头的 512 字节复制到内存地址 &lt;code&gt;0x7C00&lt;/code&gt;，然后跳过去执行。&lt;/p&gt;
&lt;p&gt;就这样。没有操作系统，没有文件系统，没有任何框架。你写的代码，就从这 512 字节开始。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;512-字节能干什么&#34;&gt;512 字节能干什么&lt;/h2&gt;
&lt;p&gt;说实话，不多。但够用来在屏幕上打一行字。&lt;/p&gt;
&lt;p&gt;这一章的目标很简单：从磁盘启动，打印 &lt;code&gt;Hello, OS!&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这段代码叫 &lt;strong&gt;Bootloader&lt;/strong&gt;（引导程序，操作系统启动前最先运行的代码），是你第一次真正掌控机器的代码。&lt;/p&gt;
&lt;p&gt;有两个硬性要求必须满足，BIOS 才会认你：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;代码必须放在内存 &lt;code&gt;0x7C00&lt;/code&gt; 开始的位置&lt;/li&gt;
&lt;li&gt;这 512 字节的最后两个字节必须是 &lt;code&gt;0x55 0xAA&lt;/code&gt;（引导魔数，BIOS 用来识别可启动扇区的标志）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;少一个，BIOS 直接忽略你。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;实模式cpu-刚醒来的状态&#34;&gt;实模式：CPU 刚醒来的状态&lt;/h2&gt;
&lt;p&gt;启动的时候，CPU 处于&lt;strong&gt;实模式&lt;/strong&gt;（Real Mode，x86 最原始的工作模式，16 位寻址，最多访问 1MB 内存）。&lt;/p&gt;
&lt;p&gt;实模式有一个好处：可以直接调用 BIOS 提供的&lt;strong&gt;中断服务&lt;/strong&gt;（INT，软中断，通过中断号调用 BIOS 内置功能）。打印字符用的是 &lt;code&gt;int 0x10&lt;/code&gt;，往 &lt;code&gt;ah&lt;/code&gt; 里放功能号 &lt;code&gt;0x0E&lt;/code&gt;，往 &lt;code&gt;al&lt;/code&gt; 里放字符，中断一触发，字符就出现在屏幕上。&lt;/p&gt;
&lt;p&gt;这是整个计算机世界里最&amp;quot;低级&amp;quot;的打印方式，但也是最直接的。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;两个坑&#34;&gt;两个坑&lt;/h2&gt;
&lt;p&gt;第一次写这段代码，我卡在两个地方：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;段寄存器没初始化。&lt;/strong&gt; 实模式下内存寻址是通过&amp;quot;段:偏移&amp;quot;来算的，&lt;code&gt;ds&lt;/code&gt;、&lt;code&gt;es&lt;/code&gt;、&lt;code&gt;ss&lt;/code&gt;（数据段、附加段、栈段寄存器）这些上来没初始化，地址算出来是错的。在代码开头手动清零就好。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;栈没设置。&lt;/strong&gt; 汇编代码里调用任何东西之前，&lt;strong&gt;栈&lt;/strong&gt;（Stack，后进先出的内存区域，用于保存临时数据和返回地址）要先指到一个安全的地方。把 &lt;code&gt;sp&lt;/code&gt;（栈顶指针）设成 &lt;code&gt;0x7C00&lt;/code&gt; 就够了——往下增长，不会覆盖代码。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;跑起来&#34;&gt;跑起来&lt;/h2&gt;
&lt;p&gt;写好汇编，用 &lt;strong&gt;NASM&lt;/strong&gt;（汇编器，把汇编代码编译成机器码）编译成二进制，扔给 &lt;strong&gt;QEMU&lt;/strong&gt;（开源的硬件模拟器，可以模拟一台完整的 x86 计算机）模拟器：&lt;/p&gt;</description>
    </item>
    <item>
      <title>你好，大飞的博客</title>
      <link>https://www.dafei.me/posts/hello-world/</link>
      <pubDate>Wed, 06 May 2026 00:00:00 +0000</pubDate>
      <guid>https://www.dafei.me/posts/hello-world/</guid>
      <description>&lt;p&gt;这是第一篇文章。&lt;/p&gt;
&lt;p&gt;这个博客记录我用 AI 学技术的过程——从零写 Linux 内核、理解操作系统原理、各种踩坑实录。&lt;/p&gt;
&lt;p&gt;内容不追求完美，追求真实。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
