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