<?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>ATA on 大飞的博客</title>
    <link>https://www.dafei.me/tags/ata/</link>
    <description>Recent content in ATA on 大飞的博客</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <lastBuildDate>Fri, 15 May 2026 02:00:00 +0000</lastBuildDate>
    <atom:link href="https://www.dafei.me/tags/ata/index.xml" rel="self" type="application/rss+xml" />
    <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（十五）：挂载 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>
  </channel>
</rss>
