<?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>Syscall on 大飞的博客</title>
    <link>https://www.dafei.me/tags/syscall/</link>
    <description>Recent content in Syscall on 大飞的博客</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <lastBuildDate>Tue, 02 Jun 2026 10:00:00 +0000</lastBuildDate>
    <atom:link href="https://www.dafei.me/tags/syscall/index.xml" rel="self" type="application/rss+xml" />
    <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>从零写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（二十四）：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（二十二）：管道 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（十）：系统调用，用户和内核的边界</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>
  </channel>
</rss>
