<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>中断 on 大飞的博客</title>
    <link>https://www.dafei.me/tags/%E4%B8%AD%E6%96%AD/</link>
    <description>Recent content in 中断 on 大飞的博客</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <lastBuildDate>Thu, 14 May 2026 05:00:00 +0000</lastBuildDate>
    <atom:link href="https://www.dafei.me/tags/%E4%B8%AD%E6%96%AD/index.xml" rel="self" type="application/rss+xml" />
    <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（五）：中断，内核的神经系统</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>
  </channel>
</rss>
