<?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>Shell on 大飞的博客</title>
    <link>https://www.dafei.me/tags/shell/</link>
    <description>Recent content in Shell on 大飞的博客</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <lastBuildDate>Wed, 06 May 2026 13:00:00 +0000</lastBuildDate>
    <atom:link href="https://www.dafei.me/tags/shell/index.xml" rel="self" type="application/rss+xml" />
    <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>
  </channel>
</rss>
