<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://blog.yotio.jp/feed.xml" rel="self" type="application/atom+xml" /><link href="https://blog.yotio.jp/" rel="alternate" type="text/html" /><updated>2026-06-08T00:40:50+09:00</updated><id>https://blog.yotio.jp/feed.xml</id><title type="html">為せばnull</title><subtitle>yotioのブログ</subtitle><entry><title type="html">RustでOS開発#4 exit()/wait()の実装など</title><link href="https://blog.yotio.jp/2026/05/31/rust-os-dev-4.html" rel="alternate" type="text/html" title="RustでOS開発#4 exit()/wait()の実装など" /><published>2026-05-31T00:00:00+09:00</published><updated>2026-05-31T00:00:00+09:00</updated><id>https://blog.yotio.jp/2026/05/31/rust-os-dev-4</id><content type="html" xml:base="https://blog.yotio.jp/2026/05/31/rust-os-dev-4.html"><![CDATA[<p>自作 OS「FerriOS」の開発日記、第4回です。前回は ELF バイナリが動くようにして、必要最低限のシステムコールを実装しました。</p>

<p>今回は残りのシステムコールを実装していこうと思います。ズバリ今回実装するシステムコールは：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">exit()</code></li>
  <li><code class="language-plaintext highlighter-rouge">wait()</code></li>
  <li><code class="language-plaintext highlighter-rouge">kill()</code></li>
  <li><code class="language-plaintext highlighter-rouge">sbrk()</code></li>
  <li><code class="language-plaintext highlighter-rouge">uptime()</code></li>
</ul>

<p>の5つです。</p>

<p>まだファイルシステムは実装していないので <code class="language-plaintext highlighter-rouge">open()</code> や <code class="language-plaintext highlighter-rouge">read()</code> などは実装していません。そちらは次回以降。</p>

<p><img src="../../../assets/img/post/2026-05-31-rust-os-dev-4/image-20260531053840914.webp" alt="image-20260531053840914" /></p>

<!--more-->

<h2 id="rustでos開発シリーズ">RustでOS開発シリーズ</h2>

<ul>
  <li>#1 <a href="../../../2026/02/23/rust-os-dev-1.html">スケジューラとカーネルスレッドの実装</a></li>
  <li>#2 <a href="../../../2026/03/22/rust-os-dev-2.html">ユーザプロセスを実装する</a></li>
  <li>#3 <a href="../../../2026/05/05/rust-os-dev-3.html">ELFとシステムコールを実装する</a></li>
  <li>#4 <a href="../../../2026/05/31/rust-os-dev-4.html">exit()/wait()の実装など</a>（今回）</li>
</ul>

<h1 id="今回のゴール">今回のゴール</h1>

<ul>
  <li><code class="language-plaintext highlighter-rouge">exit()</code> / <code class="language-plaintext highlighter-rouge">wait()</code> を実装する</li>
  <li>子プロセスの終了ステータスを親プロセスで受け取る</li>
  <li><code class="language-plaintext highlighter-rouge">kill()</code> で対象プロセスを終了させる</li>
  <li><code class="language-plaintext highlighter-rouge">sbrk()</code> でユーザプロセスのヒープを伸ばせるようにする</li>
</ul>

<p>今回のメインは <code class="language-plaintext highlighter-rouge">exit()</code> / <code class="language-plaintext highlighter-rouge">wait()</code> です。前回 <code class="language-plaintext highlighter-rouge">fork()</code> と <code class="language-plaintext highlighter-rouge">exec()</code> は実装しましたが、子プロセスを終了させたり、親プロセスが子プロセスの終了を待ったりする仕組みはまだありませんでした。</p>

<p>ついでに、ファイルシステム以外の主要なシステムコールとして <code class="language-plaintext highlighter-rouge">kill()</code>、<code class="language-plaintext highlighter-rouge">sbrk()</code>、<code class="language-plaintext highlighter-rouge">uptime()</code> も実装しました。</p>

<h1 id="前準備">前準備</h1>

<h2 id="システムコール番号の追加">システムコール番号の追加</h2>

<p>まずは <code class="language-plaintext highlighter-rouge">abi</code> クレートにシステムコール番号を追加しておきます。</p>

<p><code class="language-plaintext highlighter-rouge">abi/src/lib.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">const</span> <span class="n">SYS_UPTIME</span><span class="p">:</span> <span class="n">SyscallNum</span> <span class="o">=</span> <span class="mi">6</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">SYS_EXIT</span><span class="p">:</span> <span class="n">SyscallNum</span> <span class="o">=</span> <span class="mi">7</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">SYS_WAIT</span><span class="p">:</span> <span class="n">SyscallNum</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">SYS_KILL</span><span class="p">:</span> <span class="n">SyscallNum</span> <span class="o">=</span> <span class="mi">9</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">SYS_SBRK</span><span class="p">:</span> <span class="n">SyscallNum</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>
</code></pre></div></div>

<h2 id="processstate-の追加">ProcessState の追加</h2>

<p><code class="language-plaintext highlighter-rouge">exit()</code> / <code class="language-plaintext highlighter-rouge">wait()</code> を実装するにあたり、まず悩んだのがプロセスの状態をどこに持たせるかです。</p>

<p>これまでも <code class="language-plaintext highlighter-rouge">ThreadState</code> はありました。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">enum</span> <span class="n">ThreadState</span> <span class="p">{</span>
    <span class="n">Unused</span><span class="p">,</span>
    <span class="n">Embryo</span><span class="p">,</span>
    <span class="n">Sleeping</span><span class="p">,</span>
    <span class="n">Runnable</span><span class="p">,</span>
    <span class="n">Running</span><span class="p">,</span>
    <span class="n">Zombie</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>ただし、<code class="language-plaintext highlighter-rouge">Running</code> や <code class="language-plaintext highlighter-rouge">Runnable</code> は CPU に載る単位であるスレッドの状態です。一方で、親が <code class="language-plaintext highlighter-rouge">wait()</code> で見るのは「子プロセスが終了済みかどうか」です。</p>

<p>そこで、プロセス側にも状態を持たせます。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/thread/uprocess/mod.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">enum</span> <span class="n">ProcessState</span> <span class="p">{</span>
    <span class="n">Unused</span><span class="p">,</span>
    <span class="n">Alive</span><span class="p">,</span>
    <span class="n">Zombie</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">ThreadState</code> はスケジューラが見る実行状態、<code class="language-plaintext highlighter-rouge">ProcessState</code> はプロセスの寿命状態、という分け方です。</p>

<h2 id="process-構造体の拡張">Process 構造体の拡張</h2>

<p><code class="language-plaintext highlighter-rouge">Process</code> には、親プロセス、終了ステータス、kill フラグ、ヒープサイズを追加しました。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">struct</span> <span class="n">Process</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">pid</span><span class="p">:</span> <span class="n">ProcessID</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">ppid</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">ProcessID</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">state</span><span class="p">:</span> <span class="n">ProcessState</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">threads</span><span class="p">:</span> <span class="p">[</span><span class="nb">Option</span><span class="o">&lt;</span><span class="nb">usize</span><span class="o">&gt;</span><span class="p">;</span> <span class="n">NTHREAD_PER_PROCESS</span><span class="p">],</span>
    <span class="k">pub</span> <span class="n">nthread</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">page_table</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">PhysFrame</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">exit_status</span><span class="p">:</span> <span class="nn">abi</span><span class="p">::</span><span class="n">RetValue</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">killed</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">heap_size</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">ppid</code> は <code class="language-plaintext highlighter-rouge">wait()</code> で「自分の子プロセスかどうか」を判定するために使います。<code class="language-plaintext highlighter-rouge">exit_status</code> は <code class="language-plaintext highlighter-rouge">exit(status)</code> で保存し、親が <code class="language-plaintext highlighter-rouge">wait()</code> で受け取る値です。</p>

<p><code class="language-plaintext highlighter-rouge">killed</code> と <code class="language-plaintext highlighter-rouge">heap_size</code> は後ほど <code class="language-plaintext highlighter-rouge">kill()</code> と <code class="language-plaintext highlighter-rouge">sbrk()</code> で使います。</p>

<h1 id="wait-と-exit-の実装">wait() と exit() の実装</h1>

<h2 id="exit-の実装">exit() の実装</h2>

<p><code class="language-plaintext highlighter-rouge">exit(status)</code> を実装するにあたって気にするべきは、子プロセスが返すステータス値をどう親プロセスに渡すかです。親プロセスがあとで <code class="language-plaintext highlighter-rouge">wait()</code> したときに読めるよう、終了ステータスを <code class="language-plaintext highlighter-rouge">Process</code> 構造体の <code class="language-plaintext highlighter-rouge">exit_status</code> に保存しています。</p>

<p>大まかな流れはこんな感じです。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">exit</span><span class="p">(</span><span class="n">ret_value</span><span class="p">:</span> <span class="nn">abi</span><span class="p">::</span><span class="n">RetValue</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">pid</span> <span class="o">=</span> <span class="nn">cpu</span><span class="p">::</span><span class="n">CPU</span><span class="nf">.lock</span><span class="p">()</span><span class="nf">.current_pid</span><span class="p">()</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"no process"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="p">{</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">process_table</span> <span class="o">=</span> <span class="n">PROCESS_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">process</span> <span class="o">=</span> <span class="n">process_table</span><span class="p">[</span><span class="n">pid</span><span class="p">]</span><span class="nf">.as_mut</span><span class="p">()</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"no process"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="n">process</span><span class="py">.state</span> <span class="o">=</span> <span class="nn">ProcessState</span><span class="p">::</span><span class="n">Zombie</span><span class="p">;</span>
        <span class="n">process</span><span class="py">.exit_status</span> <span class="o">=</span> <span class="n">ret_value</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// 所属スレッドを Zombie にする</span>
    <span class="c1">// スケジューラに戻る</span>
    <span class="nf">yield_from_syscall_context</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>ここでは終了ステータスを設定しプロセスのステータスを <code class="language-plaintext highlighter-rouge">Zombie</code> に設定しておきます。<code class="language-plaintext highlighter-rouge">Zombie</code> プロセスは後ほど、カーネル・ユーザの間で切り替わる際に exit 処理が走ります。</p>

<p>また、<code class="language-plaintext highlighter-rouge">exit()</code> したプロセスのスレッドはスケジューラが実行しないようにしなければならないので、所属スレッドも <code class="language-plaintext highlighter-rouge">ThreadState::Zombie</code> に変更しておきます。</p>

<h2 id="wait-の実装">wait() の実装</h2>

<p><code class="language-plaintext highlighter-rouge">wait()</code> は、現在のプロセスの子プロセスを探し、その中に <code class="language-plaintext highlighter-rouge">Zombie</code> 状態のものがあれば回収します。すなわち、プロセスのメモリを解放して、プロセス構造体を初期化します。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">wait</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(</span><span class="n">ProcessID</span><span class="p">,</span> <span class="nn">abi</span><span class="p">::</span><span class="n">RetValue</span><span class="p">),</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">process</span> <span class="o">=</span> <span class="nn">cpu</span><span class="p">::</span><span class="n">CPU</span><span class="nf">.lock</span><span class="p">()</span><span class="nf">.current_process</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"no pid"</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">pid</span> <span class="o">=</span> <span class="n">process</span><span class="py">.pid</span><span class="p">;</span>

    <span class="k">loop</span> <span class="p">{</span>
        <span class="k">let</span> <span class="p">(</span><span class="k">mut</span> <span class="n">zombie_child</span><span class="p">,</span> <span class="n">havekids</span><span class="p">)</span> <span class="o">=</span> <span class="p">{</span>
            <span class="k">let</span> <span class="k">mut</span> <span class="n">process_table</span> <span class="o">=</span> <span class="n">PROCESS_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
            <span class="k">let</span> <span class="k">mut</span> <span class="n">zombie_child</span> <span class="o">=</span> <span class="nb">None</span><span class="p">;</span>
            <span class="k">let</span> <span class="k">mut</span> <span class="n">havekids</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>

            <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="n">NPROCESS</span><span class="o">-</span><span class="mi">1</span> <span class="p">{</span>
                <span class="k">let</span> <span class="n">is_child</span> <span class="o">=</span> <span class="n">process_table</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
                    <span class="nf">.as_ref</span><span class="p">()</span>
                    <span class="nf">.is_some_and</span><span class="p">(|</span><span class="n">child</span><span class="p">|</span> <span class="n">child</span><span class="py">.ppid</span> <span class="o">==</span> <span class="nf">Some</span><span class="p">(</span><span class="n">pid</span><span class="p">));</span>

                <span class="k">if</span> <span class="o">!</span><span class="n">is_child</span> <span class="p">{</span>
                    <span class="k">continue</span><span class="p">;</span>
                <span class="p">}</span>

                <span class="n">havekids</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
                <span class="k">if</span> <span class="n">process_table</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.is_some_and</span><span class="p">(|</span><span class="n">child</span><span class="p">|</span> <span class="n">child</span><span class="py">.state</span> <span class="o">==</span> <span class="nn">ProcessState</span><span class="p">::</span><span class="n">Zombie</span><span class="p">)</span> <span class="p">{</span>
                    <span class="n">zombie_child</span> <span class="o">=</span> <span class="n">process_table</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="nf">.take</span><span class="p">();</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span>

            <span class="p">(</span><span class="n">zombie_child</span><span class="p">,</span> <span class="n">havekids</span><span class="p">)</span>
        <span class="p">};</span>

        <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">child</span><span class="p">)</span> <span class="o">=</span> <span class="n">zombie_child</span><span class="nf">.as_mut</span><span class="p">()</span> <span class="p">{</span>
            <span class="nf">free_process</span><span class="p">(</span><span class="n">child</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
            <span class="k">return</span> <span class="nf">Ok</span><span class="p">((</span><span class="n">child</span><span class="py">.pid</span><span class="p">,</span> <span class="n">child</span><span class="py">.exit_status</span><span class="p">));</span>
        <span class="p">}</span>

        <span class="k">if</span> <span class="o">!</span><span class="n">havekids</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="s">"no child process"</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="nf">yield_from_syscall_context</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>本当は子プロセスがまだ生きている場合、親を <code class="language-plaintext highlighter-rouge">Sleeping</code> にして、子の <code class="language-plaintext highlighter-rouge">exit()</code> で wakeup するようにしたいのですが、まだ sleep/wakeup は実装していないので、今回は一度スケジューラに戻る方式にしています。</p>

<h2 id="free_process-の実装">free_process() の実装</h2>

<p>プロセスメモリなどの解放は <code class="language-plaintext highlighter-rouge">free_process()</code> に実装しています。<code class="language-plaintext highlighter-rouge">wait()</code> で Zombie の子を見つけたら、そのプロセスのリソースを解放します。</p>

<p>まずはプロセスが持っているスレッドを走査し、カーネルスタックを解放します。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">tid</span> <span class="k">in</span> <span class="n">process</span><span class="py">.threads</span><span class="nf">.into_iter</span><span class="p">()</span><span class="nf">.flatten</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">thread</span> <span class="o">=</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">thread_table</span><span class="p">[</span><span class="n">tid</span><span class="p">];</span>

    <span class="k">if</span> <span class="n">thread</span><span class="py">.kstack</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">layout</span> <span class="o">=</span> <span class="nn">alloc</span><span class="p">::</span><span class="nn">alloc</span><span class="p">::</span><span class="nn">Layout</span><span class="p">::</span><span class="nf">from_size_align</span><span class="p">(</span><span class="nn">memory</span><span class="p">::</span><span class="n">STACK_SIZE</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">kstack_base</span> <span class="o">=</span> <span class="p">(</span><span class="n">thread</span><span class="py">.kstack</span> <span class="o">-</span> <span class="nn">memory</span><span class="p">::</span><span class="n">STACK_SIZE</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">)</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">u8</span><span class="p">;</span>
        <span class="k">unsafe</span> <span class="p">{</span>
            <span class="nn">alloc</span><span class="p">::</span><span class="nn">alloc</span><span class="p">::</span><span class="nf">dealloc</span><span class="p">(</span><span class="n">kstack_base</span><span class="p">,</span> <span class="n">layout</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="o">*</span><span class="n">thread</span> <span class="o">=</span> <span class="nn">Thread</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>続いて、ユーザ空間のページテーブルも解放します。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">page_table</span><span class="p">)</span> <span class="o">=</span> <span class="n">process</span><span class="py">.page_table</span> <span class="p">{</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">current_page_table</span><span class="p">,</span> <span class="n">_</span><span class="p">)</span> <span class="o">=</span> <span class="nn">Cr3</span><span class="p">::</span><span class="nf">read</span><span class="p">();</span>
    <span class="k">if</span> <span class="n">current_page_table</span> <span class="o">==</span> <span class="n">page_table</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="s">"cannot free cr3 active page table"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="n">process</span><span class="py">.page_table</span> <span class="o">=</span> <span class="nb">None</span><span class="p">;</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">frame_allocator_guard</span> <span class="o">=</span> <span class="nn">memory</span><span class="p">::</span><span class="n">FRAME_ALLOCATOR</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">frame_allocator</span> <span class="o">=</span> <span class="n">frame_allocator_guard</span><span class="nf">.as_mut</span><span class="p">()</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"FRAME_ALLOCATOR not initialized"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nn">memory</span><span class="p">::</span><span class="nn">umem</span><span class="p">::</span><span class="nf">free_uvm</span><span class="p">(</span><span class="n">page_table</span><span class="p">,</span> <span class="n">frame_allocator</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>ここでは、現在 CR3 に入っているページテーブルを解放しないようにチェックしています。</p>

<h2 id="物理フレームの返却">物理フレームの返却</h2>

<p>プロセスのページテーブルを解放するために物理フレームの返却処理の実装も必要です。</p>

<p>Writing an OS in Rust で実装している <code class="language-plaintext highlighter-rouge">BootInfoFrameAllocator</code> は、ブートローダのメモリマップから順番に frame を配る仕組みのみ実装しており、フレームの返却と再割り当て処理は実装していません。そこで、返却済み frame を保持する <code class="language-plaintext highlighter-rouge">recycled_frames</code> を追加します。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">struct</span> <span class="n">BootInfoFrameAllocator</span> <span class="p">{</span>
    <span class="n">memory_map</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="n">MemoryRegions</span><span class="p">,</span>
    <span class="n">next</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="n">recycled_frames</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">PhysFrame</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">allocate_frame()</code> では、まず返却済み frame を優先して再利用します。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">allocate_frame</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">PhysFrame</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">frame</span><span class="p">)</span> <span class="o">=</span> <span class="k">self</span><span class="py">.recycled_frames</span><span class="nf">.pop</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nf">Some</span><span class="p">(</span><span class="n">frame</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">let</span> <span class="n">frame</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.usable_frames</span><span class="p">()</span><span class="nf">.nth</span><span class="p">(</span><span class="k">self</span><span class="py">.next</span><span class="p">);</span>
    <span class="k">self</span><span class="py">.next</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="n">frame</span>
<span class="p">}</span>
</code></pre></div></div>

<p>そして <code class="language-plaintext highlighter-rouge">FrameDeallocator</code> も実装しておきます。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">impl</span> <span class="n">FrameDeallocator</span><span class="o">&lt;</span><span class="n">Size4KiB</span><span class="o">&gt;</span> <span class="k">for</span> <span class="n">BootInfoFrameAllocator</span> <span class="p">{</span>
    <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">deallocate_frame</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">frame</span><span class="p">:</span> <span class="n">PhysFrame</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.recycled_frames</span><span class="nf">.push</span><span class="p">(</span><span class="n">frame</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="free_uvm-の実装">free_uvm() の実装</h2>

<p>ユーザ空間全体を解放するため、<code class="language-plaintext highlighter-rouge">free_uvm()</code> を実装しました。</p>

<p>解放順序はこんな漢字：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>leaf data frame
→ PT frame
→ PD frame
→ PDPT frame
→ PML4 frame
</code></pre></div></div>

<p>ページテーブル階層を walk して順次畳んでいく形です。leaf frame を返却してから、空になった page table の frame も返却します。</p>

<h2 id="waitstatus-と-copy_to_user">wait(status) と copy_to_user()</h2>

<p><code class="language-plaintext highlighter-rouge">wait()</code> では、子プロセスの PID だけでなく終了ステータスも親に返すようにしたいです。</p>

<p>ユーザ側 API (userlib) はこうしました。</p>

<p><code class="language-plaintext highlighter-rouge">userlib/src/syscalls.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">wait</span><span class="p">(</span><span class="n">status_ptr</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;&amp;</span><span class="k">mut</span> <span class="nn">abi</span><span class="p">::</span><span class="n">RetValue</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">SysRet</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">status_ptr</span> <span class="o">=</span> <span class="k">match</span> <span class="n">status_ptr</span> <span class="p">{</span>
        <span class="nf">Some</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="n">p</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="nn">abi</span><span class="p">::</span><span class="n">RetValue</span> <span class="k">as</span> <span class="nb">i64</span><span class="p">,</span>
        <span class="nb">None</span> <span class="k">=&gt;</span> <span class="mi">0</span><span class="p">,</span>
    <span class="p">};</span>

    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nf">syscall</span><span class="p">(</span><span class="n">SYS_WAIT</span><span class="p">,</span> <span class="n">status_ptr</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Rust 側では <code class="language-plaintext highlighter-rouge">Option&lt;&amp;mut RetValue&gt;</code> として扱い、syscall ABI では null pointer を <code class="language-plaintext highlighter-rouge">0</code> として渡します。</p>

<p>カーネル側では <code class="language-plaintext highlighter-rouge">sys_wait()</code> が <code class="language-plaintext highlighter-rouge">wait()</code> の結果を受け取り、ステータスの書き込み先が指定されていれば <code class="language-plaintext highlighter-rouge">copy_to_user()</code> でユーザ空間にコピーします。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="n">status_ptr</span> <span class="o">!=</span> <span class="nn">abi</span><span class="p">::</span><span class="n">NULL_POINTER</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">bytes</span> <span class="o">=</span> <span class="n">exit_status</span><span class="nf">.to_ne_bytes</span><span class="p">();</span>
    <span class="k">if</span> <span class="nf">copy_to_user</span><span class="p">(</span><span class="n">status_ptr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">bytes</span><span class="p">)</span><span class="nf">.is_err</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nn">abi</span><span class="p">::</span><span class="n">RET_ERROR</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">copy_to_user()</code> では、ユーザ仮想アドレスをページテーブルで引き、<code class="language-plaintext highlighter-rouge">PRESENT</code>、<code class="language-plaintext highlighter-rouge">USER_ACCESSIBLE</code>、<code class="language-plaintext highlighter-rouge">WRITABLE</code> を確認してからコピーしています。</p>

<h2 id="syscall-中の-yield">syscall 中の yield</h2>

<p><code class="language-plaintext highlighter-rouge">wait()</code> は、子プロセスがまだ終了していない場合にスケジューラへ戻ります。そこで最初は普通の <code class="language-plaintext highlighter-rouge">yield_from_context()</code> を使っていました。</p>

<p>が、ここで page fault しました。</p>

<p>原因は <code class="language-plaintext highlighter-rouge">SYSCALL</code> 経路ではすでに <code class="language-plaintext highlighter-rouge">swapgs</code> 済みであることです。その状態のまま通常の yield と同じように別のユーザコンテキストへ切り替えると、<code class="language-plaintext highlighter-rouge">GS</code> / <code class="language-plaintext highlighter-rouge">KernelGsBase</code> が合わなくなります。</p>

<p>そこで syscall 実行中専用の yield を追加しました。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/scheduler/mod.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">yield_from_syscall_context</span><span class="p">()</span> <span class="p">{</span>
    <span class="nf">get_scheduler</span><span class="p">()</span><span class="nf">.on_syscall_yield</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">RoundRobin</code> 側では、scheduler に戻る前後で <code class="language-plaintext highlighter-rouge">swapgs</code> します。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">unsafe</span> <span class="p">{</span>
    <span class="nn">core</span><span class="p">::</span><span class="nn">arch</span><span class="p">::</span><span class="nd">asm!</span><span class="p">(</span><span class="s">"swapgs"</span><span class="p">);</span>
    <span class="nf">switch_context</span><span class="p">(</span><span class="n">old_context</span><span class="p">,</span> <span class="n">new_context</span><span class="p">);</span>
    <span class="nn">core</span><span class="p">::</span><span class="nn">arch</span><span class="p">::</span><span class="nd">asm!</span><span class="p">(</span><span class="s">"swapgs"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="saved_user_rsp-の修正">saved_user_rsp の修正</h2>

<p>syscall 中 yield を入れた後、別の問題も出ました。</p>

<p>親プロセスが <code class="language-plaintext highlighter-rouge">wait()</code> syscall の途中で yield し、その間に子プロセスが syscall を実行すると、<code class="language-plaintext highlighter-rouge">cpu.saved_user_rsp</code> が子の値で上書きされます。その後、親が syscall から戻るとき、親のコードに戻っているのにスタックだけ子のものになってしまいました。</p>

<p>結果として <code class="language-plaintext highlighter-rouge">RIP=0</code> に飛んで page fault します。</p>

<p>そこで、<code class="language-plaintext highlighter-rouge">saved_user_rsp</code> の実体は thread ごとの <code class="language-plaintext highlighter-rouge">context.rsp3</code> に持たせるようにしました。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">table</span><span class="p">[</span><span class="n">current_tid</span><span class="p">]</span><span class="py">.context.rsp3</span> <span class="o">=</span> <span class="n">cpu</span><span class="py">.saved_user_rsp</span><span class="p">;</span>
</code></pre></div></div>

<p>次のスレッドを選ぶときは、そのスレッドの <code class="language-plaintext highlighter-rouge">rsp3</code> を CPU 側へ戻します。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">cpu</span><span class="py">.saved_user_rsp</span> <span class="o">=</span> <span class="n">table</span><span class="p">[</span><span class="n">next_tid</span><span class="p">]</span><span class="py">.context.rsp3</span><span class="p">;</span>
</code></pre></div></div>

<h1 id="ticks-と-uptime-の実装">ticks と uptime() の実装</h1>

<p><code class="language-plaintext highlighter-rouge">kill()</code> のテストなどで少し待つ処理が欲しかったので、先に <code class="language-plaintext highlighter-rouge">uptime()</code> も追加しました。</p>

<p>これまでは PIT を明示的に設定していませんでした。今回は 1秒 = 100 ticks として扱いたかったので、PIT を 100Hz に設定しています。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/interrupts.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="n">PIT_BASE_FREQUENCY</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="mi">1_193_182</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">TIMER_FREQUENCY_HZ</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span>

<span class="k">static</span> <span class="n">TICKS</span><span class="p">:</span> <span class="n">Mutex</span><span class="o">&lt;</span><span class="nb">u64</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nn">Mutex</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
</code></pre></div></div>

<p>タイマ割り込みごとに <code class="language-plaintext highlighter-rouge">TICKS</code> をインクリメントします。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">countup_ticks</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">ticks</span> <span class="o">=</span> <span class="n">TICKS</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="o">*</span><span class="n">ticks</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">get_ticks</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">u64</span> <span class="p">{</span>
    <span class="nn">x86_64</span><span class="p">::</span><span class="nn">instructions</span><span class="p">::</span><span class="nn">interrupts</span><span class="p">::</span><span class="nf">without_interrupts</span><span class="p">(||</span> <span class="o">*</span><span class="n">TICKS</span><span class="nf">.lock</span><span class="p">())</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">MutexGuard</code> をそのまま足そうとして少しハマりました。<code class="language-plaintext highlighter-rouge">TICKS.lock()</code> の中身を更新するには <code class="language-plaintext highlighter-rouge">*ticks += 1</code> として中身を参照する必要があります。また、<code class="language-plaintext highlighter-rouge">get_ticks()</code> 側ではタイマ割り込み中に同じロックを取りに行くと困るので、割り込みを一時的に無効化しています。</p>

<h1 id="kill-の実装">kill() の実装</h1>

<p>FerriOS の実装の参考にしている xv6 の <code class="language-plaintext highlighter-rouge">kill()</code> は、対象プロセスをその場で破壊するわけではありません。対象プロセスの <code class="language-plaintext highlighter-rouge">killed</code> フラグを立てておき、対象プロセス自身がユーザ空間へ戻る直前などの安全なタイミングで <code class="language-plaintext highlighter-rouge">exit()</code> します。FerriOS でも同じ方針にしました。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">kill</span><span class="p">(</span><span class="n">pid</span><span class="p">:</span> <span class="nn">abi</span><span class="p">::</span><span class="n">ProcessID</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">process_table</span> <span class="o">=</span> <span class="n">PROCESS_TABLE</span><span class="nf">.lock</span><span class="p">();</span>

    <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="n">NPROCESS</span><span class="o">-</span><span class="mi">1</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">is_target</span> <span class="o">=</span> <span class="n">process_table</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
            <span class="nf">.as_ref</span><span class="p">()</span>
            <span class="nf">.is_some_and</span><span class="p">(|</span><span class="n">proc</span><span class="p">|</span> <span class="n">proc</span><span class="py">.pid</span> <span class="o">==</span> <span class="n">pid</span><span class="p">);</span>

        <span class="k">if</span> <span class="o">!</span><span class="n">is_target</span> <span class="p">{</span>
            <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="k">let</span> <span class="k">mut</span> <span class="n">proc</span> <span class="o">=</span> <span class="n">process_table</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="nf">.take</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
        <span class="n">proc</span><span class="py">.killed</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
        <span class="n">process_table</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="n">proc</span><span class="p">);</span>

        <span class="k">return</span> <span class="nf">Ok</span><span class="p">(());</span>
    <span class="p">}</span>

    <span class="nf">Err</span><span class="p">(</span><span class="s">"no process"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>syscall の入口と出口で現在のプロセスが kill 済みか確認し、kill されたプロセスであれば exit します。そこから先の処理は <code class="language-plaintext highlighter-rouge">exit()</code> システムコールと同じです。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">exit_if_current_process_killed</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">process</span> <span class="o">=</span> <span class="nn">cpu</span><span class="p">::</span><span class="n">CPU</span><span class="nf">.lock</span><span class="p">()</span><span class="nf">.current_process</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"no process"</span><span class="p">);</span>
    <span class="k">if</span> <span class="n">process</span><span class="py">.killed</span> <span class="p">{</span>
        <span class="nn">ksyscall</span><span class="p">::</span><span class="nf">exit</span><span class="p">(</span><span class="nn">abi</span><span class="p">::</span><span class="n">RET_ERROR</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="fork-の復帰処理修正">fork() の復帰処理修正</h2>

<p><code class="language-plaintext highlighter-rouge">kill()</code> のテスト中、<code class="language-plaintext highlighter-rouge">kill()</code> を呼ぶ前に子プロセスが <code class="language-plaintext highlighter-rouge">RIP=0</code> で page fault する問題がありました。</p>

<p>調べてみると原因は <code class="language-plaintext highlighter-rouge">kill()</code> ではなく、<code class="language-plaintext highlighter-rouge">fork()</code> の子プロセスの復帰処理でした。</p>

<p>これまでは fork した子も、新規プロセス用の trampoline でユーザ空間へ入っていました。しかし fork の子は、新規に <code class="language-plaintext highlighter-rouge">main()</code> から始まるわけではありません。親が <code class="language-plaintext highlighter-rouge">fork()</code> syscall から戻るのと同じ場所に、レジスタ状態を復元して戻る必要があります。</p>

<p>そこで fork 子専用の <code class="language-plaintext highlighter-rouge">fork_return_trampoline()</code> を追加しました。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span><span class="p">(</span><span class="k">super</span><span class="p">)</span> <span class="k">unsafe</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">fork_return_trampoline</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="c1">// コピー済み TrapFrame からレジスタを復元して iretq</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">fork()</code> 側では、親の TrapFrame を子にコピーし、子の <code class="language-plaintext highlighter-rouge">rax</code> だけ 0 にします。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">*</span><span class="n">child</span><span class="py">.tf</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"no trapframe"</span><span class="p">)</span> <span class="o">=</span> <span class="n">parent_tf</span><span class="p">;</span>
<span class="p">(</span><span class="o">*</span><span class="n">child_tf</span><span class="p">)</span><span class="py">.rax</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">child</span><span class="py">.context.rip</span> <span class="o">=</span> <span class="n">fork_return_trampoline</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">;</span>
</code></pre></div></div>

<h1 id="sbrk-の実装">sbrk() の実装</h1>

<p>最後に <code class="language-plaintext highlighter-rouge">sbrk()</code> です。</p>

<p><code class="language-plaintext highlighter-rouge">sbrk(n)</code> は、プロセスの user memory end を <code class="language-plaintext highlighter-rouge">n</code> バイト分増減させ、変更前のアドレスを返すシステムコールです。xv6 の <code class="language-plaintext highlighter-rouge">proc-&gt;sz</code> に相当する値として、FerriOS では <code class="language-plaintext highlighter-rouge">Process.heap_size</code> を使います。</p>

<p>まず、<code class="language-plaintext highlighter-rouge">exec()</code> で ELF の LOAD セグメントを読み込んだ後、その末尾を page round up して初期 <code class="language-plaintext highlighter-rouge">heap_size</code> にします。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">process</span><span class="py">.heap_size</span> <span class="o">=</span> <span class="n">prepared</span><span class="py">.heap_size</span><span class="p">;</span>
</code></pre></div></div>

<p>次に、ユーザ空間を伸ばす <code class="language-plaintext highlighter-rouge">alloc_uvm()</code> と、縮める <code class="language-plaintext highlighter-rouge">dealloc_uvm()</code> を実装しました。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">alloc_uvm</span><span class="p">(</span>
    <span class="n">pml4</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">PageTable</span><span class="p">,</span>
    <span class="n">oldsz</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="n">newsz</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="n">frame_allocator</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="p">(</span><span class="k">impl</span> <span class="n">FrameAllocator</span><span class="o">&lt;</span><span class="n">Size4KiB</span><span class="o">&gt;</span> <span class="o">+</span> <span class="n">FrameDeallocator</span><span class="o">&lt;</span><span class="n">Size4KiB</span><span class="o">&gt;</span><span class="p">),</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">usize</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// oldsz..newsz を page 単位で map する</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">dealloc_uvm</span><span class="p">(</span>
    <span class="n">pml4</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">PageTable</span><span class="p">,</span>
    <span class="n">oldsz</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="n">newsz</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="n">frame_deallocator</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="p">(</span><span class="k">impl</span> <span class="n">FrameAllocator</span><span class="o">&lt;</span><span class="n">Size4KiB</span><span class="o">&gt;</span> <span class="o">+</span> <span class="n">FrameDeallocator</span><span class="o">&lt;</span><span class="n">Size4KiB</span><span class="o">&gt;</span><span class="p">),</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">usize</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// oldsz..newsz を page 単位で unmap する</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">sbrk()</code> 自体は、現在の <code class="language-plaintext highlighter-rouge">heap_size</code> を保存してから <code class="language-plaintext highlighter-rouge">grow_process_heap()</code> を呼び、成功したら古いアドレスを返します。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">sbrk</span><span class="p">(</span><span class="n">n</span><span class="p">:</span> <span class="nb">isize</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nn">abi</span><span class="p">::</span><span class="n">UserAddress</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">pid</span> <span class="o">=</span> <span class="nn">cpu</span><span class="p">::</span><span class="n">CPU</span><span class="nf">.lock</span><span class="p">()</span><span class="nf">.current_pid</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"no process"</span><span class="p">);</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">process_table</span> <span class="o">=</span> <span class="n">PROCESS_TABLE</span><span class="nf">.lock</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">address</span> <span class="o">=</span> <span class="n">process_table</span><span class="p">[</span><span class="n">pid</span><span class="p">]</span><span class="nf">.unwrap</span><span class="p">()</span><span class="py">.heap_size</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">;</span>
    <span class="nf">grow_process_heap</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">process_table</span><span class="p">[</span><span class="n">pid</span><span class="p">]</span><span class="nf">.unwrap</span><span class="p">())</span><span class="o">?</span><span class="p">;</span>

    <span class="nf">Ok</span><span class="p">(</span><span class="n">address</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">sbrk()</code> の実行フローはこんな感じ。</p>

<p><img src="../../../assets/img/post/2026-05-31-rust-os-dev-4/sbrk-implementation-flow.svg" alt="sbrk-implementation-flow" /></p>

<p><code class="language-plaintext highlighter-rouge">n&gt;0</code> の場合は <code class="language-plaintext highlighter-rouge">alloc_uvm()</code> を呼び出してフレームを取得し、<code class="language-plaintext highlighter-rouge">n&lt;0</code> の場合は <code class="language-plaintext highlighter-rouge">dealloc_uvm()</code> でフレームを返却します。alloc 時にエラーが起きた際にもフレームを返却します。<code class="language-plaintext highlighter-rouge">n=0</code> の場合は何もしません。</p>

<h1 id="ユーザライブラリ実装の改良">ユーザライブラリ実装の改良</h1>

<h2 id="ユーザランタイムの実装">ユーザランタイムの実装</h2>

<p>…とその前に、userlib 側に main 関数用のランタイムを実装しておきます。</p>

<p>以前は <code class="language-plaintext highlighter-rouge">#![no_main]</code> と <code class="language-plaintext highlighter-rouge">#[unsafe(no_mangle)]</code> を使って <code class="language-plaintext highlighter-rouge">main</code> シンボルを直接出していましたが、これらを不要にします。</p>

<p><code class="language-plaintext highlighter-rouge">userlib</code> 側に <code class="language-plaintext highlighter-rouge">#[lang = "start"]</code> と <code class="language-plaintext highlighter-rouge">#[lang = "termination"]</code> を用意しました。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[lang</span> <span class="nd">=</span> <span class="s">"termination"</span><span class="nd">]</span>
<span class="k">pub</span> <span class="k">trait</span> <span class="n">Termination</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">report</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">RetValue</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Termination</span> <span class="k">for</span> <span class="p">()</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">report</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">RetValue</span> <span class="p">{</span>
        <span class="n">RET_SUCCESS</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Termination</span> <span class="k">for</span> <span class="n">RetValue</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">report</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">RetValue</span> <span class="p">{</span>
        <span class="k">self</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nd">#[lang</span> <span class="nd">=</span> <span class="s">"start"</span><span class="nd">]</span>
<span class="k">fn</span> <span class="n">lang_start</span><span class="o">&lt;</span><span class="n">T</span><span class="p">:</span> <span class="n">Termination</span> <span class="o">+</span> <span class="k">'static</span><span class="o">&gt;</span><span class="p">(</span>
    <span class="n">main</span><span class="p">:</span> <span class="k">fn</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="n">T</span><span class="p">,</span>
    <span class="n">argc</span><span class="p">:</span> <span class="nb">isize</span><span class="p">,</span>
    <span class="n">argv</span><span class="p">:</span> <span class="o">*</span><span class="k">const</span> <span class="o">*</span><span class="k">const</span> <span class="nb">u8</span><span class="p">,</span>
    <span class="n">sigpipe</span><span class="p">:</span> <span class="nb">u8</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">isize</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">_</span> <span class="o">=</span> <span class="n">sigpipe</span><span class="p">;</span>

    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nn">args</span><span class="p">::</span><span class="n">ARGC</span> <span class="o">=</span> <span class="n">argc</span> <span class="k">as</span> <span class="nb">usize</span><span class="p">;</span>
        <span class="nn">args</span><span class="p">::</span><span class="n">ARGV</span> <span class="o">=</span> <span class="n">argv</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">let</span> <span class="n">ret</span> <span class="o">=</span> <span class="nf">main</span><span class="p">()</span><span class="nf">.report</span><span class="p">();</span>
    <span class="nf">exit</span><span class="p">(</span><span class="n">ret</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">lang_start()</code> はユーザアプリケーションのエントリポイントを示します。</p>

<p><code class="language-plaintext highlighter-rouge">fn main() -&gt; ()</code> 向けの Termination と <code class="language-plaintext highlighter-rouge">fn main() -&gt; RetValue</code> (RetValue は u64) 向けの Termination を実装することで、戻り値ありの main() と戻り値なしの main() を実装できるようにしています。</p>

<p>これでユーザアプリは以下のように書けます。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#![no_std]</span>

<span class="k">use</span> <span class="nn">userlib</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="n">RetValue</span> <span class="p">{</span>
    <span class="mi">42</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">main()</code> の戻り値は <code class="language-plaintext highlighter-rouge">exit()</code> に渡されます。これでアプリ側の見た目は普通の Rust コードっぽくなりましたね（<code class="language-plaintext highlighter-rouge">#![no_std]</code> からは目を背ける）。</p>

<h2 id="引数を渡す">引数を渡す</h2>

<p><code class="language-plaintext highlighter-rouge">exec()</code> に引数を渡せるようにしたので、ユーザアプリ側から <code class="language-plaintext highlighter-rouge">args()</code> で読めるようにしました。とりあえず <code class="language-plaintext highlighter-rouge">userlib::args()</code> で引数を取得できる形で実装。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">args</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="n">Args</span> <span class="p">{</span>
    <span class="n">Args</span> <span class="p">{</span>
        <span class="n">argc</span><span class="p">:</span> <span class="nf">argc</span><span class="p">(),</span>
        <span class="n">argv</span><span class="p">:</span> <span class="nf">argv</span><span class="p">(),</span>
        <span class="n">index</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Args</code> は iterator として実装しています。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">impl</span> <span class="nb">Iterator</span> <span class="k">for</span> <span class="n">Args</span> <span class="p">{</span>
    <span class="k">type</span> <span class="n">Item</span> <span class="o">=</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>また、現状の <code class="language-plaintext highlighter-rouge">exec()</code> システムコールは配列で引数を渡す必要がある（Linux でいうところの <code class="language-plaintext highlighter-rouge">execv()</code> みたいな感じ）のですが、<code class="language-plaintext highlighter-rouge">execl()</code> のように不特定数の引数を渡せるようにしたかったので、userlib 側にマクロとして <code class="language-plaintext highlighter-rouge">execl!()</code> を実装しました。C言語なら不特定数の引数を関数で実装できますが、Rust の関数だと引数の数が厳密にチェックされるのでマクロとして実装する必要があるのですね。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[macro_export]</span>
<span class="nd">macro_rules!</span> <span class="n">execl</span> <span class="p">{</span>
    <span class="p">(</span><span class="nv">$path:expr</span> <span class="nv">$</span><span class="p">(,</span> <span class="nv">$arg:expr</span><span class="p">)</span><span class="o">*</span> <span class="nv">$</span><span class="p">(,)</span><span class="o">?</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>これで親プロセスから子プロセスを起動するときは、以下のように書けます。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">execl!</span><span class="p">(</span><span class="s">"/child"</span><span class="p">,</span> <span class="s">"abc"</span><span class="p">,</span> <span class="s">"def"</span><span class="p">);</span>
</code></pre></div></div>

<h1 id="動作確認">動作確認</h1>

<p>今回の確認用ユーザアプリは、まず <code class="language-plaintext highlighter-rouge">fork()</code> して子プロセスで <code class="language-plaintext highlighter-rouge">/child</code> を <code class="language-plaintext highlighter-rouge">exec()</code> します。子プロセスには引数として “abc”, “def” を渡し、<code class="language-plaintext highlighter-rouge">wait()</code> で子プロセスの終了を待って戻り値を受け取ります。</p>

<p>次に <code class="language-plaintext highlighter-rouge">sbrk()</code> でメモリを確保しつつ、もう1つ子プロセスを生成して、今度は子プロセスを kill() します。</p>

<p><code class="language-plaintext highlighter-rouge">user/init/src/main.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#![no_std]</span>

<span class="k">use</span> <span class="nn">userlib</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 1st child</span>
    <span class="k">let</span> <span class="n">ret</span> <span class="o">=</span> <span class="nf">fork</span><span class="p">();</span>
    <span class="k">if</span> <span class="n">ret</span> <span class="o">==</span> <span class="n">RET_ERROR</span> <span class="p">{</span>
        <span class="nd">panic!</span><span class="p">(</span><span class="s">"failed to call fork()"</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="n">ret</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
        <span class="c1">// on the child process</span>
        <span class="k">let</span> <span class="n">ret</span> <span class="o">=</span> <span class="nd">execl!</span><span class="p">(</span><span class="s">"/child"</span><span class="p">,</span> <span class="s">"abc"</span><span class="p">,</span> <span class="s">"def"</span><span class="p">);</span>
        <span class="k">if</span> <span class="n">ret</span> <span class="o">==</span> <span class="n">RET_ERROR</span> <span class="p">{</span>
            <span class="nd">panic!</span><span class="p">(</span><span class="s">"failed to call exec()"</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// on the parent process</span>
    <span class="k">let</span> <span class="n">pid</span> <span class="o">=</span> <span class="nf">getpid</span><span class="p">();</span>

    <span class="nd">print_fmt!</span><span class="p">(</span><span class="s">"[parent] waiting child process..."</span><span class="p">);</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">status</span><span class="p">:</span> <span class="n">RetValue</span> <span class="o">=</span> <span class="n">RET_SUCCESS</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">child_pid</span> <span class="o">=</span> <span class="nf">wait</span><span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">status</span><span class="p">));</span>
    <span class="nd">print_fmt!</span><span class="p">(</span><span class="s">"[parent (pid={})] child process has exited; child's pid is {} and ret value is {}"</span><span class="p">,</span> <span class="n">pid</span><span class="p">,</span> <span class="n">child_pid</span><span class="p">,</span> <span class="n">status</span><span class="p">);</span>

    <span class="c1">// 2nd child</span>
    <span class="k">let</span> <span class="n">alloced_memory</span> <span class="o">=</span> <span class="nf">sbrk</span><span class="p">(</span><span class="mi">1234</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">addr</span> <span class="o">=</span> <span class="n">alloced_memory</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">u64</span><span class="p">;</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="o">*</span><span class="n">addr</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">let</span> <span class="n">ret</span> <span class="o">=</span> <span class="nf">fork</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">start</span> <span class="o">=</span> <span class="nf">uptime</span><span class="p">();</span>
    <span class="k">if</span> <span class="n">ret</span> <span class="o">==</span> <span class="n">RET_ERROR</span> <span class="p">{</span>
        <span class="nd">panic!</span><span class="p">(</span><span class="s">"failed to call fork()"</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="n">ret</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
        <span class="c1">// on the child process</span>
        <span class="k">unsafe</span> <span class="p">{</span>
            <span class="o">*</span><span class="n">addr</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">loop</span> <span class="p">{</span>
            <span class="k">unsafe</span> <span class="p">{</span>
                <span class="o">*</span><span class="n">addr</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
                <span class="nd">print_fmt!</span><span class="p">(</span><span class="s">"[child (pid = {})] *addr = {}"</span><span class="p">,</span> <span class="n">pid</span><span class="p">,</span> <span class="o">*</span><span class="n">addr</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// on the parent process</span>
    <span class="c1">// wait a moment..</span>
    <span class="k">loop</span> <span class="p">{</span>
        <span class="k">if</span> <span class="nf">uptime</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span> <span class="o">&gt;</span> <span class="mi">5</span> <span class="p">{</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// kill it</span>
    <span class="nd">print_fmt!</span><span class="p">(</span><span class="s">"[parent] pid = {} kill pid {}"</span><span class="p">,</span> <span class="n">pid</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span>
    <span class="nf">kill</span><span class="p">(</span><span class="n">ret</span> <span class="k">as</span> <span class="n">ProcessID</span><span class="p">);</span>
    <span class="nd">print_fmt!</span><span class="p">(</span><span class="s">"[parent] pid = {} done."</span><span class="p">,</span> <span class="n">pid</span><span class="p">);</span>

    <span class="c1">// sbrk test</span>
    <span class="k">if</span> <span class="n">alloced_memory</span> <span class="o">==</span> <span class="n">RET_ERROR</span> <span class="p">{</span>
        <span class="nd">print_fmt!</span><span class="p">(</span><span class="s">"[parent] pid = {} alloc failed"</span><span class="p">,</span> <span class="n">pid</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">else</span> <span class="p">{</span>
        <span class="k">unsafe</span> <span class="p">{</span>
            <span class="nd">print_fmt!</span><span class="p">(</span><span class="s">"[parent (pid = {})] *addr = {}"</span><span class="p">,</span> <span class="n">pid</span><span class="p">,</span> <span class="o">*</span><span class="n">addr</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">loop</span> <span class="p">{</span>
    <span class="p">}</span>
<span class="p">}</span>

</code></pre></div></div>

<p>1つ目の <code class="language-plaintext highlighter-rouge">child</code> 側では <code class="language-plaintext highlighter-rouge">args()</code> を表示し、ループを回して <code class="language-plaintext highlighter-rouge">uptime()</code> で取得される ticks 値を足していきながら、最後に <code class="language-plaintext highlighter-rouge">RetValue</code> を返します。</p>

<p><code class="language-plaintext highlighter-rouge">user/child/src/main.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="n">RetValue</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">pid</span> <span class="o">=</span> <span class="nf">getpid</span><span class="p">();</span>

    <span class="k">for</span> <span class="n">arg</span> <span class="k">in</span> <span class="nf">args</span><span class="p">()</span> <span class="p">{</span>
        <span class="nd">print_fmt!</span><span class="p">(</span><span class="s">"[child (pid={})] arg: {}"</span><span class="p">,</span> <span class="n">pid</span><span class="p">,</span> <span class="n">arg</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">let</span> <span class="k">mut</span> <span class="n">ret</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">for</span> <span class="n">_</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="mi">60</span> <span class="p">{</span>
        <span class="n">ret</span> <span class="o">+=</span> <span class="nf">uptime</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="n">ret</span>
<span class="p">}</span>
</code></pre></div></div>

<p>2つ目の子プロセスでは <code class="language-plaintext highlighter-rouge">sbrk()</code> で確保した領域を親子でアクセスつつ（親子間で分離されていることの確認）、子プロセス側では無限ループを実行する中で、親から <code class="language-plaintext highlighter-rouge">kill()</code> して止めるテストもしています。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">alloced_memory</span> <span class="o">=</span> <span class="nf">sbrk</span><span class="p">(</span><span class="mi">1234</span><span class="p">);</span>
<span class="k">let</span> <span class="n">addr</span> <span class="o">=</span> <span class="n">alloced_memory</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">u64</span><span class="p">;</span>

<span class="k">let</span> <span class="n">ret</span> <span class="o">=</span> <span class="nf">fork</span><span class="p">();</span>
<span class="k">if</span> <span class="n">ret</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
    <span class="k">loop</span> <span class="p">{</span>
        <span class="k">unsafe</span> <span class="p">{</span>
            <span class="o">*</span><span class="n">addr</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
            <span class="nd">print_fmt!</span><span class="p">(</span><span class="s">"[child (pid = {})] *addr = {}"</span><span class="p">,</span> <span class="nf">getpid</span><span class="p">(),</span> <span class="o">*</span><span class="n">addr</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nf">kill</span><span class="p">(</span><span class="n">ret</span> <span class="k">as</span> <span class="n">ProcessID</span><span class="p">);</span>
</code></pre></div></div>

<p>これでプロセスを作って、実行して、終了させて、親プロセスが子プロセスの exit status を受け取るところまで来ました。</p>

<h2 id="実行結果">実行結果</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Welcome to FerriOS!

Initializing..done.
console-mode: Both
initializing memory.. initializing heap memory.. <span class="k">done</span><span class="nb">.</span>
Starting kernel threads.. <span class="k">done</span><span class="nb">.</span>

<span class="o">[</span>parent] waiting child process...
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> arg: abc
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> arg: def
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 2
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 4
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 6
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 8
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 10
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 12
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 14
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 16
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 18
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 20
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 22
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 24
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 26
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 28
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 30
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 32
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 34
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 36
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 38
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 40
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 42
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 44
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 46
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 2 ret <span class="o">=</span> 48
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 52
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 56
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 60
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 64
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 68
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 72
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 76
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 80
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 84
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 88
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 92
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 96
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 100
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 104
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 108
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 112
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 116
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 120
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 124
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 128
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 4 ret <span class="o">=</span> 132
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 6 ret <span class="o">=</span> 138
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 6 ret <span class="o">=</span> 144
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 6 ret <span class="o">=</span> 150
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 6 ret <span class="o">=</span> 156
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 6 ret <span class="o">=</span> 162
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 6 ret <span class="o">=</span> 168
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 6 ret <span class="o">=</span> 174
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 6 ret <span class="o">=</span> 180
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 6 ret <span class="o">=</span> 186
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 6 ret <span class="o">=</span> 192
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 8 ret <span class="o">=</span> 200
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 8 ret <span class="o">=</span> 208
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 8 ret <span class="o">=</span> 216
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 8 ret <span class="o">=</span> 224
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> ticks <span class="o">=</span> 8 ret <span class="o">=</span> 232
<span class="o">[</span>child <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>1<span class="o">)]</span> exiting..
<span class="o">[</span>parent <span class="o">(</span><span class="nv">pid</span><span class="o">=</span>0<span class="o">)]</span> child process has exited<span class="p">;</span> child<span class="s1">'s pid is 1 and ret value is 232
[child (pid = 2)] *addr = 1
[child (pid = 2)] *addr = 2
[child (pid = 2)] *addr = 3
[child (pid = 2)] *addr = 4
[child (pid = 2)] *addr = 5
[child (pid = 2)] *addr = 6
[child (pid = 2)] *addr = 7
[child (pid = 2)] *addr = 8
[child (pid = 2)] *addr = 9
[child (pid = 2)] *addr = 10
[child (pid = 2)] *addr = 11
[child (pid = 2)] *addr = 12
[child (pid = 2)] *addr = 13
[child (pid = 2)] *addr = 14
[child (pid = 2)] *addr = 15
[child (pid = 2)] *addr = 16
[child (pid = 2)] *addr = 17
[child (pid = 2)] *addr = 18
[child (pid = 2)] *addr = 19
[child (pid = 2)] *addr = 20
[child (pid = 2)] *addr = 21
[child (pid = 2)] *addr = 22
[child (pid = 2)] *addr = 23
[child (pid = 2)] *addr = 24
[child (pid = 2)] *addr = 25
[child (pid = 2)] *addr = 26
[child (pid = 2)] *addr = 27
[child (pid = 2)] *addr = 28
[child (pid = 2)] *addr = 29
[parent] pid = 0 kill pid 2
[parent] pid = 0 done.
[parent (pid = 0)] *addr = 0

</span></code></pre></div></div>

<p>うん。子プロセスの戻り値を親プロセス側で受け取れていますね。ユーザ空間内の <code class="language-plaintext highlighter-rouge">sbrk()</code> で確保した領域も親子間で分離できていて、値の更新ができています。最後に pid 2 の子プロセスを kill() して止めることにも成功しています。</p>

<h1 id="おわりに">おわりに</h1>

<p>今回は <code class="language-plaintext highlighter-rouge">exit()</code> / <code class="language-plaintext highlighter-rouge">wait()</code> を中心に、<code class="language-plaintext highlighter-rouge">kill()</code>、<code class="language-plaintext highlighter-rouge">sbrk()</code>、<code class="language-plaintext highlighter-rouge">uptime()</code>、userlib の改善などを実装しました。</p>

<p>これで xv6 基準でいえばファイルシステム以外の部分は出揃ったのかな、と思っています。ということで、次回からはいよいよファイルシステムの実装ですかね。沼りそうな予感しかしないなぁ。</p>]]></content><author><name></name></author><category term="Rust" /><category term="OS自作入門" /><category term="FerriOS" /><summary type="html"><![CDATA[自作 OS「FerriOS」の開発日記、第4回です。前回は ELF バイナリが動くようにして、必要最低限のシステムコールを実装しました。 今回は残りのシステムコールを実装していこうと思います。ズバリ今回実装するシステムコールは： exit() wait() kill() sbrk() uptime() の5つです。 まだファイルシステムは実装していないので open() や read() などは実装していません。そちらは次回以降。]]></summary></entry><entry><title type="html">RustでOS開発#3 ELFとシステムコールを実装する</title><link href="https://blog.yotio.jp/2026/05/05/rust-os-dev-3.html" rel="alternate" type="text/html" title="RustでOS開発#3 ELFとシステムコールを実装する" /><published>2026-05-05T00:00:00+09:00</published><updated>2026-05-05T00:00:00+09:00</updated><id>https://blog.yotio.jp/2026/05/05/rust-os-dev-3</id><content type="html" xml:base="https://blog.yotio.jp/2026/05/05/rust-os-dev-3.html"><![CDATA[<p>自作 OS「FerriOS」の開発日記、第3回です。前回はユーザプロセスを実装して機械語コードでループさせてみて、なんとなく動いていそうだなぁというところまでできました。</p>

<p><img src="../../../assets/img/post/2026-03-07-rust-os-dev-2/image-20260322220019960.webp" alt="image-20260322220019960" /></p>

<p>しかし、これじゃユーザプロセスからはなんの文字出力もできないですし、使い物になりませんよね。</p>

<p>なので今回は簡単なシステムコールを実装して、ユーザアプリケーションからカーネルに対して文字出力を実行させることで、ユーザプロセスが動いている様を確認できるようにしたいと思います。今回実装するシステムコールは下記の4つ。</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">fork()</code></li>
  <li><code class="language-plaintext highlighter-rouge">exec()</code></li>
  <li><code class="language-plaintext highlighter-rouge">getpid()</code></li>
  <li><code class="language-plaintext highlighter-rouge">print_num()</code>：数値表示用</li>
  <li><code class="language-plaintext highlighter-rouge">print_str()</code>：文字列表示用</li>
</ul>

<p><code class="language-plaintext highlighter-rouge">print_num()</code>、<code class="language-plaintext highlighter-rouge">print_str()</code> は、<code class="language-plaintext highlighter-rouge">write()</code> システムコールを実装するまでの暫定的なシステムコールとして実装しています。実際のところは Linux などでは <code class="language-plaintext highlighter-rouge">write()</code> システムコールを通して VGA なりデバイスファイルに文字を書き込むことで文字列を表示しているのですが、FerriOS にはまだファイルシステムを全く実装しておりませんので、文字表示専用のシステムコールを用意しています。</p>

<p>また、今回はユーザアプリケーションを ELF アプリケーションとしてビルドし、<code class="language-plaintext highlighter-rouge">exec()</code> で ELF からユーザ空間にコードをロードして実行できるようにします。お楽しみに！</p>

<p><img src="../../../assets/img/post/2026-05-05-rust-os-dev-3/image-20260505224735070.webp" alt="image-20260505224735070" /></p>

<!--more-->

<h2 id="rustでos開発シリーズ">RustでOS開発シリーズ</h2>

<ul>
  <li>#1 <a href="../../../2026/02/23/rust-os-dev-1.html">スケジューラとカーネルスレッドの実装</a></li>
  <li>#2 <a href="../../../2026/03/22/rust-os-dev-2.html">ユーザプロセスを実装する</a></li>
  <li>#3 <a href="../../../2026/05/05/rust-os-dev-3.html">ELFとシステムコールを実装する</a>（今回）</li>
  <li>#4 <a href="../../../2026/05/31/rust-os-dev-4.html">exit()/wait()の実装など</a></li>
</ul>

<h1 id="今回のゴール">今回のゴール</h1>

<ul>
  <li>システムコールを実装する</li>
  <li>ELF アプリケーションのビルドと実行を可能にする</li>
</ul>

<p>最終的なゴールとしては、親プロセスで <code class="language-plaintext highlighter-rouge">fork()</code> して子プロセスで <code class="language-plaintext highlighter-rouge">exec()</code> で ELF アプリケーションをロードし、それぞれの PID を <code class="language-plaintext highlighter-rouge">getpid()</code> で表示させるところまで行きたいと思います。</p>

<p>今回の実装内容はこちら ↓ で公開しています。</p>

<ul>
  <li><a href="https://github.com/yotiosoft/FerriOS/tree/03-elf-syscalls" target="_blank">https://github.com/yotiosoft/FerriOS/tree/03-elf-syscalls</a></li>
</ul>

<h1 id="abi-定数の定義">abi 定数の定義</h1>

<p>システムコールを実装する前に、システムコール番号や戻り値などを定数で定義しておきましょう。カーネルからもユーザからも参照できるよう、独立した <code class="language-plaintext highlighter-rouge">abi</code> クレートとして実装しています。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo new abi <span class="nt">--lib</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">abi/src/lib.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#![no_std]</span>

<span class="cd">/// type: syscall number</span>
<span class="k">pub</span> <span class="k">type</span> <span class="n">SyscallNum</span> <span class="o">=</span> <span class="nb">i64</span><span class="p">;</span>

<span class="cd">/// type: return value</span>
<span class="k">pub</span> <span class="k">type</span> <span class="n">SysRet</span> <span class="o">=</span> <span class="nb">i64</span><span class="p">;</span>

<span class="cd">/// syscall numbers</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">SYS_PRINT_NUM</span><span class="p">:</span> <span class="n">SyscallNum</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">SYS_PRINT_STR</span><span class="p">:</span> <span class="n">SyscallNum</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">SYS_FORK</span><span class="p">:</span> <span class="n">SyscallNum</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">SYS_EXEC</span><span class="p">:</span> <span class="n">SyscallNum</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">SYS_GETPID</span><span class="p">:</span> <span class="n">SyscallNum</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>

<span class="cd">/// return values</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">RET_SUCCESS</span><span class="p">:</span> <span class="n">SysRet</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">RET_ERROR</span><span class="p">:</span> <span class="n">SysRet</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
</code></pre></div></div>

<p>型が一意になるように <code class="language-plaintext highlighter-rouge">SyscallNum</code>、<code class="language-plaintext highlighter-rouge">SysRet</code> 型をそれぞれ定義しています。とりあえず今は <code class="language-plaintext highlighter-rouge">i64</code> にしました。</p>

<p>続いてシステムコール番号。これらの番号を <code class="language-plaintext highlighter-rouge">SYSCALL</code> 命令実行時にシステムコールエントリに渡します。</p>

<p>最後に return value。システムコールからユーザへの戻り値として、<code class="language-plaintext highlighter-rouge">RET_SUCCESS</code> と <code class="language-plaintext highlighter-rouge">RET_ERROR</code> を定義しておきました。</p>

<p>カーネルから <code class="language-plaintext highlighter-rouge">abi</code> クレートを参照できるよう、<code class="language-plaintext highlighter-rouge">kernel/Cargo.toml</code> も更新しておきます。</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">...</span>
<span class="nn">[dependencies]</span>
<span class="py">bootloader_api</span> <span class="p">=</span> <span class="s">"0.11"</span>
<span class="py">volatile</span> <span class="p">=</span> <span class="s">"0.2.6"</span>
<span class="py">spin</span> <span class="p">=</span> <span class="s">"0.5.2"</span>
<span class="py">x86_64</span> <span class="p">=</span> <span class="s">"0.14.2"</span>
<span class="py">uart_16550</span> <span class="p">=</span> <span class="s">"0.2.0"</span>
<span class="py">pic8259</span> <span class="p">=</span> <span class="s">"0.10.1"</span>
<span class="py">pc-keyboard</span> <span class="p">=</span> <span class="s">"0.7.0"</span>
<span class="py">linked_list_allocator</span> <span class="p">=</span> <span class="s">"0.9.0"</span>
<span class="nn">noto-sans-mono-bitmap</span> <span class="o">=</span> <span class="p">{</span> <span class="py">version</span> <span class="p">=</span> <span class="s">"0.3.2"</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="nn">["bold"]</span> <span class="p">}</span>
<span class="nn">abi</span> <span class="o">=</span> <span class="p">{</span> <span class="py">path</span> <span class="p">=</span> <span class="s">"../abi"</span> <span class="p">}</span>
<span class="err">...</span>
</code></pre></div></div>

<h1 id="システムコールの実装">システムコールの実装</h1>

<h2 id="エントリポイントの実装">エントリポイントの実装</h2>

<p>CPU で <code class="language-plaintext highlighter-rouge">SYSCALL</code> 命令が実行されたときに飛ぶ先としてのシステムコールエントリポイントを作成しておきます。</p>

<p>まずは <code class="language-plaintext highlighter-rouge">kernel/src/syscall/mod.rs</code> を用意。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/syscall/mod.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">x86_64</span><span class="p">::</span><span class="nn">registers</span><span class="p">::</span><span class="nn">model_specific</span><span class="p">::{</span><span class="n">Efer</span><span class="p">,</span> <span class="n">EferFlags</span><span class="p">,</span> <span class="n">LStar</span><span class="p">,</span> <span class="n">Star</span><span class="p">,</span> <span class="n">SFMask</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">x86_64</span><span class="p">::</span><span class="n">VirtAddr</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">core</span><span class="p">::</span><span class="nn">arch</span><span class="p">::</span><span class="n">naked_asm</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">core</span><span class="p">::</span><span class="n">cmp</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">core</span><span class="p">::</span><span class="nn">mem</span><span class="p">::</span><span class="n">offset_of</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">alloc</span><span class="p">::</span><span class="nn">vec</span><span class="p">::</span><span class="nb">Vec</span><span class="p">;</span>

<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="n">gdt</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">cpu</span><span class="p">::</span><span class="n">Cpu</span><span class="p">;</span>

<span class="k">mod</span> <span class="n">ksyscall</span><span class="p">;</span>

<span class="k">const</span> <span class="n">OFFSET_SAVED_USER_RSP</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="nd">offset_of!</span><span class="p">(</span><span class="n">Cpu</span><span class="p">,</span> <span class="n">saved_user_rsp</span><span class="p">);</span>
<span class="k">const</span> <span class="n">OFFSET_KERNEL_SYSCALL_RSP</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="nd">offset_of!</span><span class="p">(</span><span class="n">Cpu</span><span class="p">,</span> <span class="n">kernel_syscall_rsp</span><span class="p">);</span>
<span class="k">const</span> <span class="n">OFFSET_SAVED_RAX</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">72</span><span class="p">;</span>

<span class="nd">#[unsafe(naked)]</span>
<span class="k">unsafe</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">syscall_entry</span><span class="p">()</span> <span class="p">{</span>
    <span class="nd">naked_asm!</span><span class="p">(</span>
        <span class="c1">// カーネル用 GS に切り替え</span>
        <span class="s">"swapgs"</span><span class="p">,</span>

        <span class="c1">// ユーザ RSP を退避し、カーネルスタックに切り替え</span>
        <span class="s">"mov gs:[{saved_user_rsp}], rsp"</span><span class="p">,</span>
        <span class="s">"mov rsp, gs:[{kernel_syscall_rsp}]"</span><span class="p">,</span>

        <span class="c1">// レジスタを退避</span>
        <span class="s">"push rcx"</span><span class="p">,</span>   <span class="c1">// sysretq 用 RIP</span>
        <span class="s">"push r11"</span><span class="p">,</span>   <span class="c1">// sysretq 用 RFLAGS</span>
        <span class="s">"push rax"</span><span class="p">,</span>   <span class="c1">// syscall 番号</span>
        <span class="s">"push rdi"</span><span class="p">,</span>
        <span class="s">"push rsi"</span><span class="p">,</span>
        <span class="s">"push rdx"</span><span class="p">,</span>
        <span class="s">"push rbx"</span><span class="p">,</span>
        <span class="s">"push rbp"</span><span class="p">,</span>
        <span class="s">"push r12"</span><span class="p">,</span>
        <span class="s">"push r13"</span><span class="p">,</span>
        <span class="s">"push r14"</span><span class="p">,</span>
        <span class="s">"push r15"</span><span class="p">,</span>

        <span class="c1">// syscall_dispatch(number=rax, arg0=rdi, arg1=rsi, arg2=rdx)</span>
        <span class="c1">// 引数は rdi, rsi, rdx に入っている</span>
        <span class="s">"mov r10, rax"</span><span class="p">,</span>
        <span class="s">"mov r8,  rsp"</span><span class="p">,</span>
        <span class="s">"mov rcx, rdx"</span><span class="p">,</span>
        <span class="s">"mov rdx, rsi"</span><span class="p">,</span>
        <span class="s">"mov rsi, rdi"</span><span class="p">,</span>
        <span class="s">"mov rdi, r10"</span><span class="p">,</span>
        <span class="c1">// rsi, rdx はユーザが設定した値がそのまま残っている</span>
        <span class="s">"call {syscall_dispatch}"</span><span class="p">,</span>
        <span class="c1">// syscall_dispatch の戻り値を保存済み rax スロットへ反映する</span>
        <span class="s">"mov [rsp + {saved_rax_offset}], rax"</span><span class="p">,</span>

        <span class="c1">// レジスタを復元</span>
        <span class="s">"pop r15"</span><span class="p">,</span>
        <span class="s">"pop r14"</span><span class="p">,</span>
        <span class="s">"pop r13"</span><span class="p">,</span>
        <span class="s">"pop r12"</span><span class="p">,</span>
        <span class="s">"pop rbp"</span><span class="p">,</span>
        <span class="s">"pop rbx"</span><span class="p">,</span>
        <span class="s">"pop rdx"</span><span class="p">,</span>
        <span class="s">"pop rsi"</span><span class="p">,</span>
        <span class="s">"pop rdi"</span><span class="p">,</span>
        <span class="s">"pop rax"</span><span class="p">,</span>
        <span class="s">"pop r11"</span><span class="p">,</span>
        <span class="s">"pop rcx"</span><span class="p">,</span>

        <span class="c1">// ユーザ RSP を復元</span>
        <span class="s">"mov rsp, gs:[{saved_user_rsp}]"</span><span class="p">,</span>

        <span class="c1">// ユーザ用 GS に戻す</span>
        <span class="s">"swapgs"</span><span class="p">,</span>

        <span class="c1">// ユーザモードに戻る</span>
        <span class="s">"sysretq"</span><span class="p">,</span>

        <span class="n">saved_user_rsp</span>     <span class="o">=</span> <span class="k">const</span> <span class="n">OFFSET_SAVED_USER_RSP</span><span class="p">,</span>
        <span class="n">kernel_syscall_rsp</span> <span class="o">=</span> <span class="k">const</span> <span class="n">OFFSET_KERNEL_SYSCALL_RSP</span><span class="p">,</span>
        <span class="n">saved_rax_offset</span>   <span class="o">=</span> <span class="k">const</span> <span class="n">OFFSET_SAVED_RAX</span><span class="p">,</span>
        <span class="n">syscall_dispatch</span>   <span class="o">=</span> <span class="n">sym</span> <span class="nn">ksyscall</span><span class="p">::</span><span class="n">syscall_dispatch</span><span class="p">,</span>
    <span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>ここでは、カーネルスタックに切り替えてレジスタをコンテキストに退避させ、後に実装する <code class="language-plaintext highlighter-rouge">syscall_dispatch()</code> という関数にジャンプさせます。このとき、呼び出し先のシステムコール番号は <code class="language-plaintext highlighter-rouge">RAX</code> レジスタに、第1引数～第3引数はそれぞれ <code class="language-plaintext highlighter-rouge">RDI</code>、<code class="language-plaintext highlighter-rouge">RSI</code>、<code class="language-plaintext highlighter-rouge">RDX</code> レジスタに格納されていますので、それらを引数で渡します。また、<code class="language-plaintext highlighter-rouge">R8</code> には実行中のスレッドの Trap Frame を渡します。これは、Thread 構造体に「今この syscall を実行しているスレッドの、カーネルスタック上の保存レジスタ領域」を記録させるためです。代入処理は <code class="language-plaintext highlighter-rouge">syscall_dispatch()</code> で行っています。</p>

<p>システムコールの処理が完了したら、コンテキストからレジスタの値を復元し、ユーザスタックに戻した後にユーザモードに戻ります。</p>

<p>要するに <code class="language-plaintext highlighter-rouge">syscall_entry()</code> は、ユーザ空間から見れば「関数呼び出しの入口」ですが、カーネル側から見ると「特権レベルを切り替えつつ、ユーザの実行状態を丸ごと保存するための薄いラッパー」です。ここでレジスタをきちんと退避しておかないと、<code class="language-plaintext highlighter-rouge">fork()</code> のように「後でその時点のユーザ状態をコピーしたい」処理や、<code class="language-plaintext highlighter-rouge">exec()</code> のように「復帰先の RIP を差し替えたい」処理がうまく実装できません。</p>

<p>また、<code class="language-plaintext highlighter-rouge">SYSCALL</code> 命令が実行されたときに、上記の <code class="language-plaintext highlighter-rouge">syscall_entry()</code> が呼ばれるよう、初期設定もしておきます。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/syscall/mod.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">init</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nn">Efer</span><span class="p">::</span><span class="nf">update</span><span class="p">(|</span><span class="n">flags</span><span class="p">|</span> <span class="o">*</span><span class="n">flags</span> <span class="p">|</span><span class="o">=</span> <span class="nn">EferFlags</span><span class="p">::</span><span class="n">SYSTEM_CALL_EXTENSIONS</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// syscall handler のアドレスを LSTAR に登録</span>
    <span class="nn">LStar</span><span class="p">::</span><span class="nf">write</span><span class="p">(</span><span class="nn">VirtAddr</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">syscall_entry</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">));</span>

    <span class="c1">// CC/SS セグメントを STAR に設定</span>
    <span class="nn">Star</span><span class="p">::</span><span class="nf">write</span><span class="p">(</span>
        <span class="nn">gdt</span><span class="p">::</span><span class="n">GDT</span><span class="na">.1</span><span class="py">.user_code_selector</span><span class="p">,</span>
        <span class="nn">gdt</span><span class="p">::</span><span class="n">GDT</span><span class="na">.1</span><span class="py">.user_data_selector</span><span class="p">,</span>
        <span class="nn">gdt</span><span class="p">::</span><span class="n">GDT</span><span class="na">.1</span><span class="py">.kernel_code_selector</span><span class="p">,</span>
        <span class="nn">gdt</span><span class="p">::</span><span class="n">GDT</span><span class="na">.1</span><span class="py">.kernel_data_selector</span><span class="p">,</span>
    <span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// syscall 呼び出し時に IF をクリアさせる</span>
    <span class="nn">SFMask</span><span class="p">::</span><span class="nf">write</span><span class="p">(</span><span class="nn">x86_64</span><span class="p">::</span><span class="nn">registers</span><span class="p">::</span><span class="nn">rflags</span><span class="p">::</span><span class="nn">RFlags</span><span class="p">::</span><span class="n">INTERRUPT_FLAG</span><span class="p">);</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</code></pre></div></div>

<p>こちらの <code class="language-plaintext highlighter-rouge">syscall::init()</code> は <code class="language-plaintext highlighter-rouge">kernel_main()</code> から呼び出される <code class="language-plaintext highlighter-rouge">init::init()</code> から呼び出されます。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/libbackend/init.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// init</span>
<span class="cd">/// IDT の初期化</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">init</span><span class="p">()</span> <span class="p">{</span>
    <span class="nn">gdt</span><span class="p">::</span><span class="nf">init</span><span class="p">();</span>
    <span class="nn">cpu</span><span class="p">::</span><span class="nf">init</span><span class="p">();</span>
    <span class="nn">syscall</span><span class="p">::</span><span class="nf">init</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"syscall init error"</span><span class="p">);</span>      <span class="c1">// これ</span>
    <span class="nn">interrupts</span><span class="p">::</span><span class="nf">init_idt</span><span class="p">();</span>
    <span class="o">...</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="システムコールの実装-1">システムコールの実装</h2>

<p>まずは <code class="language-plaintext highlighter-rouge">syscall_entry()</code> から呼び出され、各システムコールのエントリポイント関数を呼び出す <code class="language-plaintext highlighter-rouge">syscall_dispatch()</code> の実装から。</p>

<p><code class="language-plaintext highlighter-rouge">src/syscall/ksyscall.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="n">println</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="n">thread</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="n">exec</span><span class="p">;</span>

<span class="k">use</span> <span class="nn">abi</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="cd">/// Rustから呼ばれるディスパッチャ</span>
<span class="cd">/// 戻り値はRAXに入る</span>
<span class="nd">#[unsafe(no_mangle)]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">syscall_dispatch</span><span class="p">(</span><span class="n">syscall_num</span><span class="p">:</span> <span class="n">SyscallNum</span><span class="p">,</span> <span class="n">arg1</span><span class="p">:</span> <span class="nb">i64</span><span class="p">,</span> <span class="n">arg2</span><span class="p">:</span> <span class="nb">i64</span><span class="p">,</span> <span class="n">arg3</span><span class="p">:</span> <span class="nb">i64</span><span class="p">,</span> <span class="n">tf</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="nn">thread</span><span class="p">::</span><span class="nn">trapframe</span><span class="p">::</span><span class="n">TrapFrame</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">SysRet</span> <span class="p">{</span>
    <span class="nf">set_trapframe</span><span class="p">(</span><span class="n">tf</span><span class="p">);</span>
    
    <span class="k">match</span> <span class="n">syscall_num</span> <span class="p">{</span>
        <span class="nn">abi</span><span class="p">::</span><span class="n">SYS_PRINT_NUM</span> <span class="k">=&gt;</span> <span class="nf">sys_print_num</span><span class="p">(</span><span class="n">arg1</span><span class="p">),</span>
        <span class="nn">abi</span><span class="p">::</span><span class="n">SYS_PRINT_STR</span> <span class="k">=&gt;</span> <span class="nf">sys_print_str</span><span class="p">(</span><span class="n">arg1</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">,</span> <span class="n">arg2</span><span class="p">),</span>
        <span class="nn">abi</span><span class="p">::</span><span class="n">SYS_FORK</span> <span class="k">=&gt;</span> <span class="nf">sys_fork</span><span class="p">(),</span>
        <span class="nn">abi</span><span class="p">::</span><span class="n">SYS_EXEC</span> <span class="k">=&gt;</span> <span class="nf">sys_exec</span><span class="p">(</span><span class="n">arg1</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">,</span> <span class="n">arg2</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">),</span>
        <span class="nn">abi</span><span class="p">::</span><span class="n">SYS_GETPID</span> <span class="k">=&gt;</span> <span class="nf">sys_getpid</span><span class="p">(),</span>
        <span class="n">_</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="k">crate</span><span class="p">::</span><span class="nd">println!</span><span class="p">(</span><span class="s">"[syscall] unknown syscall: {}"</span><span class="p">,</span> <span class="n">syscall_num</span><span class="p">);</span>
            <span class="nn">SysRet</span><span class="p">::</span><span class="n">MAX</span>  <span class="c1">// エラー</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="cd">/// TrapFrame をセットする</span>
<span class="nd">#[unsafe(no_mangle)]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">set_trapframe</span><span class="p">(</span><span class="n">tf_ptr</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="nn">thread</span><span class="p">::</span><span class="nn">trapframe</span><span class="p">::</span><span class="n">TrapFrame</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">tid</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">cpu</span> <span class="o">=</span> <span class="k">crate</span><span class="p">::</span><span class="nn">cpu</span><span class="p">::</span><span class="n">CPU</span><span class="nf">.lock</span><span class="p">();</span>
        <span class="n">cpu</span><span class="py">.current_tid</span>
    <span class="p">}</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"no current thread"</span><span class="p">);</span>
    
    <span class="k">let</span> <span class="k">mut</span> <span class="n">table</span> <span class="o">=</span> <span class="k">crate</span><span class="p">::</span><span class="nn">thread</span><span class="p">::</span><span class="n">THREAD_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="n">table</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span><span class="py">.tf</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="n">tf_ptr</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>ここでは先に、<code class="language-plaintext highlighter-rouge">set_trapframe()</code> を呼び出して実行中のスレッドの Thread Table 上のメンバ変数 <code class="language-plaintext highlighter-rouge">tf</code> に Trap Frame を記録しておき、後にユーザに戻る際にコンテキストを復元できるようにします。</p>

<p>その後、 <code class="language-plaintext highlighter-rouge">syscall_num</code> に従って各システムコールのエントリポイント関数を呼び出します。実行結果は <code class="language-plaintext highlighter-rouge">RAX</code> レジスタに保存されます。</p>

<p>ここで <code class="language-plaintext highlighter-rouge">tf</code> を Thread 構造体に保存しているのは、後で <code class="language-plaintext highlighter-rouge">fork()</code> や <code class="language-plaintext highlighter-rouge">exec()</code> を実装するうえで効いてきます。たとえば <code class="language-plaintext highlighter-rouge">fork()</code> では「親が syscall から戻る直前のレジスタ状態」をそのまま子にコピーし、子だけ <code class="language-plaintext highlighter-rouge">RAX = 0</code> に書き換えたいですし、<code class="language-plaintext highlighter-rouge">exec()</code> ではユーザに戻る直前の <code class="language-plaintext highlighter-rouge">RIP</code> や <code class="language-plaintext highlighter-rouge">RSP</code> を新しいプログラム向けに差し替えたいです。そのため、今まさに syscall を実行中のスレッドがどの Trap Frame を使っているかを、Thread Table から辿れるようにしておく必要があります。</p>

<h3 id="sys_print_num"><code class="language-plaintext highlighter-rouge">sys_print_num()</code></h3>

<p><code class="language-plaintext highlighter-rouge">sys_print_num()</code> は数値を表示するだけのシステムコールです。<code class="language-plaintext highlighter-rouge">write()</code> システムコールの代わりとして暫定的に実装しています（ファイルシステムを実装したら削除予定）。</p>

<p>実装はこんな感じです。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/syscall/ksyscall.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// 数値を表示する</span>
<span class="k">fn</span> <span class="nf">sys_print_num</span><span class="p">(</span><span class="n">n</span><span class="p">:</span> <span class="nb">i64</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">SysRet</span> <span class="p">{</span>
    <span class="k">crate</span><span class="p">::</span><span class="nd">println!</span><span class="p">(</span><span class="s">"[syscall] print_num: {}"</span><span class="p">,</span> <span class="n">n</span><span class="p">);</span>
    <span class="nn">abi</span><span class="p">::</span><span class="n">RET_SUCCESS</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="sys_print_str"><code class="language-plaintext highlighter-rouge">sys_print_str()</code></h3>

<p><code class="language-plaintext highlighter-rouge">sys_print_str()</code> は文字列を表示するシステムコールです。こちらも <code class="language-plaintext highlighter-rouge">write()</code> システムコールの代わりとして暫定的に実装しています。</p>

<p><code class="language-plaintext highlighter-rouge">sys_print_num()</code> のように引数で数値を渡すだけならレジスタを介して渡せばいいので楽なんですが、文字列となるとそうは行きません。レジスタで渡せるのは数値やポインタだけなので、ユーザ→カーネルに文字列をコピーするなり、ユーザ空間上のアドレスから文字列を取得するなりの処理が必要です。</p>

<p>というわけで、先にユーザ→カーネルに文字列をコピーするための処理を実装します。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/syscall/mod.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// user から kernel に string をコピー</span>
<span class="k">fn</span> <span class="nf">copy_cstr_from_user</span><span class="p">(</span><span class="n">ptr</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span> <span class="n">max_len</span><span class="p">:</span> <span class="nb">usize</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">u8</span><span class="o">&gt;</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">if</span> <span class="n">ptr</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="s">"exec: null argument pointer"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">let</span> <span class="k">mut</span> <span class="n">bytes</span> <span class="o">=</span> <span class="nn">Vec</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="n">max_len</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">byte</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="o">*</span><span class="p">((</span><span class="n">ptr</span> <span class="k">as</span> <span class="o">*</span><span class="k">const</span> <span class="nb">u8</span><span class="p">)</span><span class="nf">.add</span><span class="p">(</span><span class="n">i</span><span class="p">))</span> <span class="p">};</span>
        <span class="k">if</span> <span class="n">byte</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nf">Ok</span><span class="p">(</span><span class="n">bytes</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="n">bytes</span><span class="nf">.push</span><span class="p">(</span><span class="n">byte</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nf">Err</span><span class="p">(</span><span class="s">"exec: argument is too long"</span><span class="p">)</span>
<span class="p">}</span>

</code></pre></div></div>

<p>この関数はカーネルで実行されるので、カーネル空間上に Vec が作成され、そこにユーザ空間上のアドレス（<code class="language-plaintext highlighter-rouge">ptr</code>）から文字列をコピーしています。</p>

<p>もちろん実際には「ユーザが渡してきたポインタをそのまま信用してはいけない」ので、もう少し厳密にはユーザページテーブルを辿って、そのアドレスが本当にマップされているか、ユーザアクセス可能か、といった検証も必要です。が、現時点では割愛。</p>

<p>これで下準備は整いました。<code class="language-plaintext highlighter-rouge">sys_print_str()</code> を実装していきましょう。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/syscall/ksyscall.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// 文字列を表示する（ポインタ + 長さ）</span>
<span class="k">fn</span> <span class="nf">sys_print_str</span><span class="p">(</span><span class="n">ptr</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span> <span class="n">len</span><span class="p">:</span> <span class="nb">i64</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">SysRet</span> <span class="p">{</span>
    <span class="c1">// ユーザポインタの検証（今は簡易版）</span>
    <span class="k">if</span> <span class="n">len</span> <span class="o">&gt;</span> <span class="mi">256</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nn">SysRet</span><span class="p">::</span><span class="n">MAX</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">let</span> <span class="n">bytes</span> <span class="o">=</span> <span class="k">match</span> <span class="k">super</span><span class="p">::</span><span class="nf">copy_bytes_from_user</span><span class="p">(</span><span class="n">ptr</span><span class="p">,</span> <span class="n">len</span> <span class="k">as</span> <span class="nb">usize</span><span class="p">)</span> <span class="p">{</span>
        <span class="nf">Ok</span><span class="p">(</span><span class="n">bytes</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="n">bytes</span><span class="p">,</span>
        <span class="nf">Err</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="k">crate</span><span class="p">::</span><span class="nd">println!</span><span class="p">(</span><span class="s">"[syscall] print_str copy error: {}"</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
            <span class="k">return</span> <span class="nn">SysRet</span><span class="p">::</span><span class="n">MAX</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">};</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nf">Ok</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="o">=</span> <span class="nn">core</span><span class="p">::</span><span class="nn">str</span><span class="p">::</span><span class="nf">from_utf8</span><span class="p">(</span><span class="o">&amp;</span><span class="n">bytes</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">crate</span><span class="p">::</span><span class="nd">println!</span><span class="p">(</span><span class="s">"[syscall] print_str: {}"</span><span class="p">,</span> <span class="n">s</span><span class="p">);</span>
        <span class="nn">abi</span><span class="p">::</span><span class="n">RET_SUCCESS</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nn">SysRet</span><span class="p">::</span><span class="n">MAX</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="sys_getpid"><code class="language-plaintext highlighter-rouge">sys_getpid()</code></h3>

<p>Linux でもおなじみの <code class="language-plaintext highlighter-rouge">getpid()</code> システムコールです。こちらはプロセスから PID を取得して数値で返すだけなので比較的簡単です。</p>

<p>まずはエントリポイント関数から。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/syscall/ksyscall.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// getpid</span>
<span class="k">fn</span> <span class="nf">sys_getpid</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="n">SysRet</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">pid</span> <span class="o">=</span> <span class="nn">thread</span><span class="p">::</span><span class="nn">uprocess</span><span class="p">::</span><span class="nn">syscalls</span><span class="p">::</span><span class="nf">getpid</span><span class="p">();</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nf">Ok</span><span class="p">(</span><span class="n">pid</span><span class="p">)</span> <span class="o">=</span> <span class="n">pid</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">pid</span> <span class="k">as</span> <span class="n">SysRet</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nn">abi</span><span class="p">::</span><span class="n">RET_ERROR</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>つづいて本体となる実装関数。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/thread/uprocess/syscalls.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">getpid</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">usize</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">cpu</span> <span class="o">=</span> <span class="nn">cpu</span><span class="p">::</span><span class="n">CPU</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">pid</span> <span class="o">=</span> <span class="n">cpu</span><span class="nf">.current_pid</span><span class="p">();</span>

    <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">pid</span><span class="p">)</span> <span class="o">=</span> <span class="n">pid</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nf">Ok</span><span class="p">(</span><span class="n">pid</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="s">"no process"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>もともと <code class="language-plaintext highlighter-rouge">cpu</code> 構造体に <code class="language-plaintext highlighter-rouge">current_pid()</code> を実装しているので、そこから数値を取得して返すだけです。</p>

<h3 id="sys_fork"><code class="language-plaintext highlighter-rouge">sys_fork()</code></h3>

<p>ここからが若干ややこしいです。<code class="language-plaintext highlighter-rouge">fork()</code> もおなじみのプロセス生成のシステムコールで、親プロセスをコピーして子プロセスを生成します。</p>

<p>実装は xv6 を参考に作成しています。まずはエントリポイント関数から。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/syscall/ksyscall.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// fork</span>
<span class="k">fn</span> <span class="nf">sys_fork</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="n">SysRet</span> <span class="p">{</span>
    <span class="k">match</span> <span class="nn">thread</span><span class="p">::</span><span class="nn">uprocess</span><span class="p">::</span><span class="nn">syscalls</span><span class="p">::</span><span class="nf">fork</span><span class="p">()</span> <span class="p">{</span>
        <span class="nf">Ok</span><span class="p">(</span><span class="n">child_pid</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="n">child_pid</span> <span class="k">as</span> <span class="n">SysRet</span><span class="p">,</span>
        <span class="nf">Err</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="nd">println!</span><span class="p">(</span><span class="s">"{}"</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
            <span class="nn">abi</span><span class="p">::</span><span class="n">RET_ERROR</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>続いて本体。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/thread/uprocess/syscalls.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">fork</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">usize</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// プロセス割り当て</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">process</span> <span class="o">=</span> <span class="k">super</span><span class="p">::</span><span class="nf">alloc_proc</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// frame allocator を取得</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">guard</span> <span class="o">=</span> <span class="nn">memory</span><span class="p">::</span><span class="n">FRAME_ALLOCATOR</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">frame_allocator</span> <span class="o">=</span> <span class="n">guard</span><span class="nf">.as_mut</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"FRAME_ALLOCATOR not initialized"</span><span class="p">);</span>

    <span class="c1">// 現在のプロセスの PML4 page table を取得</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">current_process_pml4</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">PageTable</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">cpu</span> <span class="o">=</span> <span class="nn">cpu</span><span class="p">::</span><span class="n">CPU</span><span class="nf">.lock</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">phys_frame</span> <span class="o">=</span> <span class="n">cpu</span><span class="nf">.current_process</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"process not found"</span><span class="p">)</span><span class="py">.page_table</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"no page table"</span><span class="p">);</span>

        <span class="k">let</span> <span class="n">physical_memory_offset</span> <span class="o">=</span> <span class="nn">memory</span><span class="p">::</span><span class="n">PHYSICAL_MEMORY_OFFSET</span><span class="nf">.lock</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"physical memory offset not initialized"</span><span class="p">);</span>

        <span class="c1">// PhysFrame → 仮想アドレス → &amp;mut PageTable</span>
        <span class="k">let</span> <span class="n">virt</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
            <span class="nn">memory</span><span class="p">::</span><span class="nn">va</span><span class="p">::</span><span class="nf">phys_to_virt</span><span class="p">(</span><span class="n">phys_frame</span><span class="nf">.start_address</span><span class="p">(),</span> <span class="n">physical_memory_offset</span><span class="p">)</span>
        <span class="p">};</span>
        <span class="k">unsafe</span> <span class="p">{</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="o">*</span><span class="n">virt</span><span class="py">.as_mut_ptr</span><span class="p">::</span><span class="o">&lt;</span><span class="n">PageTable</span><span class="o">&gt;</span><span class="p">()</span> <span class="p">}</span>
    <span class="p">};</span>

    <span class="c1">// proces state (page table) をコピー</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">_</span><span class="p">,</span> <span class="n">page_table</span><span class="p">)</span> <span class="o">=</span> <span class="nn">memory</span><span class="p">::</span><span class="nn">umem</span><span class="p">::</span><span class="nf">copy_uvm</span><span class="p">(</span><span class="n">frame_allocator</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">current_process_pml4</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// ページテーブル設定</span>
    <span class="n">process</span><span class="py">.page_table</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="n">page_table</span><span class="p">);</span>

    <span class="c1">// 親プロセスの trapframe とユーザ復帰情報をコピー</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">parent_tf</span><span class="p">,</span> <span class="n">parent_user_rsp</span><span class="p">):</span> <span class="p">(</span><span class="nn">thread</span><span class="p">::</span><span class="nn">trapframe</span><span class="p">::</span><span class="n">TrapFrame</span><span class="p">,</span> <span class="nb">u64</span><span class="p">)</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">cpu</span> <span class="o">=</span> <span class="k">crate</span><span class="p">::</span><span class="nn">cpu</span><span class="p">::</span><span class="n">CPU</span><span class="nf">.lock</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">tid</span> <span class="o">=</span> <span class="n">cpu</span><span class="py">.current_tid</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"no current thread"</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">saved_user_rsp</span> <span class="o">=</span> <span class="n">cpu</span><span class="py">.saved_user_rsp</span><span class="p">;</span>
        <span class="nf">drop</span><span class="p">(</span><span class="n">cpu</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">thread_table</span> <span class="o">=</span> <span class="k">crate</span><span class="p">::</span><span class="nn">thread</span><span class="p">::</span><span class="n">THREAD_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
        <span class="p">(</span><span class="k">unsafe</span> <span class="p">{</span> <span class="o">*</span><span class="n">thread_table</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span><span class="py">.tf</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"no trapframe"</span><span class="p">)</span> <span class="p">},</span> <span class="n">saved_user_rsp</span><span class="p">)</span>
    <span class="p">};</span>
    <span class="p">{</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">table</span> <span class="o">=</span> <span class="k">crate</span><span class="p">::</span><span class="nn">thread</span><span class="p">::</span><span class="n">THREAD_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">child_tid</span> <span class="o">=</span> <span class="n">process</span><span class="py">.threads</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"no child thread"</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">child</span> <span class="o">=</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">table</span><span class="p">[</span><span class="n">child_tid</span><span class="p">];</span>

        <span class="c1">// xv6: *np-&gt;tf = *proc-&gt;tf</span>
        <span class="k">unsafe</span> <span class="p">{</span> <span class="o">*</span><span class="n">child</span><span class="py">.tf</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"no trapframe"</span><span class="p">)</span> <span class="o">=</span> <span class="n">parent_tf</span><span class="p">;</span> <span class="p">}</span>

        <span class="c1">// xv6: np-&gt;tf-&gt;eax = 0 (子の fork 戻り値を 0 に)</span>
        <span class="k">unsafe</span> <span class="p">{</span> <span class="p">(</span><span class="o">*</span><span class="n">child</span><span class="py">.tf</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"no trapframe"</span><span class="p">))</span><span class="py">.rax</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span>

        <span class="c1">// 子は fork から復帰する形で最初にユーザ空間へ入る</span>
        <span class="n">child</span><span class="py">.context.rsp3</span> <span class="o">=</span> <span class="n">parent_user_rsp</span><span class="p">;</span>
        <span class="n">child</span><span class="py">.context.user_rip</span> <span class="o">=</span> <span class="n">parent_tf</span><span class="py">.rcx</span><span class="p">;</span>
        <span class="n">child</span><span class="py">.context.user_rdi</span> <span class="o">=</span> <span class="n">parent_tf</span><span class="py">.rdi</span><span class="p">;</span>
        <span class="n">child</span><span class="py">.context.user_rsi</span> <span class="o">=</span> <span class="n">parent_tf</span><span class="py">.rsi</span><span class="p">;</span>
        
        <span class="k">crate</span><span class="p">::</span><span class="nd">println!</span><span class="p">(</span>
            <span class="s">"[fork] child pid={}, tid={}, user_rip={:#x}, rsp3={:#x}, rax={:#x}"</span><span class="p">,</span>
            <span class="n">process</span><span class="py">.pid</span><span class="p">,</span>
            <span class="n">child_tid</span><span class="p">,</span>
            <span class="n">child</span><span class="py">.context.user_rip</span><span class="p">,</span>
            <span class="n">child</span><span class="py">.context.rsp3</span><span class="p">,</span>
            <span class="k">unsafe</span> <span class="p">{</span> <span class="p">(</span><span class="o">*</span><span class="n">child</span><span class="py">.tf</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"no trapframe"</span><span class="p">))</span><span class="py">.rax</span> <span class="p">},</span>
        <span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// ステータスの設定</span>

    <span class="c1">// process_table に追加</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">process_table</span> <span class="o">=</span> <span class="k">super</span><span class="p">::</span><span class="n">PROCESS_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="n">process_table</span><span class="p">[</span><span class="n">process</span><span class="py">.pid</span><span class="p">]</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="n">process</span><span class="p">);</span>
    
    <span class="c1">// runnable に設定</span>
    <span class="k">super</span><span class="p">::</span><span class="nf">mark_threads_as_runnable</span><span class="p">(</span><span class="n">process</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="nf">Ok</span><span class="p">(</span><span class="n">process</span><span class="py">.pid</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>基本的には</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">Process</code> 構造体を作成し</li>
  <li>現在のプロセス = 親プロセスのページテーブルをコピーして</li>
  <li>子プロセスにコピーしたページテーブルを設定</li>
  <li>親プロセスの Trap Frame と user rsp もコピー（fork 完了後の復帰先を設定）</li>
  <li>子プロセスの戻り値を 0 になるようにする（親プロセスの場合は子プロセスの PID）</li>
  <li>子プロセスを Process Table に追加</li>
</ol>

<p>といった形です。</p>

<p><code class="language-plaintext highlighter-rouge">fork()</code> が難しいのは、単に <code class="language-plaintext highlighter-rouge">Process</code> 構造体を複製すればよいわけではない点です。親と子で「見えているメモリ空間」と「ユーザ空間に戻る瞬間のレジスタ状態」の両方を、それぞれ整合が取れるように複製しなければなりません。</p>

<p>これに際して <code class="language-plaintext highlighter-rouge">copy_uvm()</code> といった一連のページテーブル操作関数も実装しました。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/memory/umem.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// 親プロセスのユーザ空間 [0]..[255] を子プロセスにコピー</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">copy_uvm</span><span class="p">(</span><span class="n">frame_allocator</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="k">impl</span> <span class="n">FrameAllocator</span><span class="o">&lt;</span><span class="n">Size4KiB</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">parent_pml4</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">PageTable</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(</span><span class="n">OffsetPageTable</span><span class="o">&lt;</span><span class="k">'static</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">PhysFrame</span><span class="p">),</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// physical_memory_offset</span>
    <span class="k">let</span> <span class="n">physical_memory_offset</span> <span class="o">=</span> <span class="n">PHYSICAL_MEMORY_OFFSET</span><span class="nf">.lock</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"physical memory offset not initialized"</span><span class="p">);</span>

    <span class="c1">// 子の PML4 を作成</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">child_offset_table</span><span class="p">,</span> <span class="n">child_pml4_frame</span><span class="p">)</span> <span class="o">=</span> <span class="nf">new_uvm</span><span class="p">(</span><span class="n">frame_allocator</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 子の PML4 への生ポインタを取得</span>
    <span class="k">let</span> <span class="n">child_pml4_virt</span> <span class="o">=</span> <span class="n">physical_memory_offset</span> <span class="o">+</span> <span class="n">child_pml4_frame</span><span class="nf">.start_address</span><span class="p">()</span><span class="nf">.as_u64</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">child_pml4</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">PageTable</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="o">*</span><span class="n">child_pml4_virt</span><span class="nf">.as_mut_ptr</span><span class="p">()</span> <span class="p">};</span>

    <span class="c1">// ユーザ空間 PML4 エントリ (index 0..255) を走査</span>
    <span class="k">for</span> <span class="n">pml4_idx</span> <span class="k">in</span> <span class="n">PAGETABLE_USER_SPACE_START</span><span class="o">..</span><span class="n">PAGETABLE_USER_SPACE_END</span> <span class="p">{</span> <span class="c1">// Iterate 0 to 255 (exclusive of 256)</span>
        <span class="k">if</span> <span class="o">!</span><span class="n">parent_pml4</span><span class="p">[</span><span class="n">pml4_idx</span><span class="p">]</span><span class="nf">.flags</span><span class="p">()</span><span class="nf">.contains</span><span class="p">(</span><span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">PRESENT</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// 子の PDPT を新規割り当て</span>
        <span class="k">let</span> <span class="n">child_pdpt_frame</span> <span class="o">=</span> <span class="n">frame_allocator</span><span class="nf">.allocate_frame</span><span class="p">()</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"copy_uvm: failed to allocate PDPT frame"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="nn">va</span><span class="p">::</span><span class="nf">init_page_table</span><span class="p">(</span><span class="n">child_pdpt_frame</span><span class="p">,</span> <span class="n">physical_memory_offset</span><span class="p">);</span>

        <span class="c1">// 子の PML4 エントリに書き込む</span>
        <span class="k">let</span> <span class="n">parent_pdpt_flags</span> <span class="o">=</span> <span class="n">parent_pml4</span><span class="p">[</span><span class="n">pml4_idx</span><span class="p">]</span><span class="nf">.flags</span><span class="p">();</span>
        <span class="n">child_pml4</span><span class="p">[</span><span class="n">pml4_idx</span><span class="p">]</span><span class="nf">.set_frame</span><span class="p">(</span><span class="n">child_pdpt_frame</span><span class="p">,</span> <span class="n">parent_pdpt_flags</span><span class="p">);</span>

        <span class="c1">// 親の PDPT を取得</span>
        <span class="k">let</span> <span class="n">parent_pdpt</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
            <span class="nn">va</span><span class="p">::</span><span class="nf">table_from_entry</span><span class="p">(</span><span class="o">&amp;</span><span class="n">parent_pml4</span><span class="p">[</span><span class="n">pml4_idx</span><span class="p">],</span> <span class="n">physical_memory_offset</span><span class="p">)</span>
        <span class="p">};</span>
        <span class="k">let</span> <span class="n">child_pdpt</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
            <span class="nn">va</span><span class="p">::</span><span class="nf">table_from_frame</span><span class="p">(</span><span class="n">child_pdpt_frame</span><span class="p">,</span> <span class="n">physical_memory_offset</span><span class="p">)</span>
        <span class="p">};</span>

        <span class="c1">// PDPT エントリを走査</span>
        <span class="k">for</span> <span class="n">pdpt_idx</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="mi">512usize</span> <span class="p">{</span>
            <span class="k">if</span> <span class="o">!</span><span class="n">parent_pdpt</span><span class="p">[</span><span class="n">pdpt_idx</span><span class="p">]</span><span class="nf">.flags</span><span class="p">()</span><span class="nf">.contains</span><span class="p">(</span><span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">PRESENT</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">continue</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="c1">// 子の PD を新規割り当て</span>
            <span class="k">let</span> <span class="n">child_pd_frame</span> <span class="o">=</span> <span class="n">frame_allocator</span><span class="nf">.allocate_frame</span><span class="p">()</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"copy_uvm: failed to allocate PD frame"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
            <span class="nn">va</span><span class="p">::</span><span class="nf">init_page_table</span><span class="p">(</span><span class="n">child_pd_frame</span><span class="p">,</span> <span class="n">physical_memory_offset</span><span class="p">);</span>

            <span class="k">let</span> <span class="n">parent_pd_flags</span> <span class="o">=</span> <span class="n">parent_pdpt</span><span class="p">[</span><span class="n">pdpt_idx</span><span class="p">]</span><span class="nf">.flags</span><span class="p">();</span>
            <span class="n">child_pdpt</span><span class="p">[</span><span class="n">pdpt_idx</span><span class="p">]</span><span class="nf">.set_frame</span><span class="p">(</span><span class="n">child_pd_frame</span><span class="p">,</span> <span class="n">parent_pd_flags</span><span class="p">);</span>

            <span class="k">let</span> <span class="n">parent_pd</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
                <span class="nn">va</span><span class="p">::</span><span class="nf">table_from_entry</span><span class="p">(</span><span class="o">&amp;</span><span class="n">parent_pdpt</span><span class="p">[</span><span class="n">pdpt_idx</span><span class="p">],</span> <span class="n">physical_memory_offset</span><span class="p">)</span>
            <span class="p">};</span>
            <span class="k">let</span> <span class="n">child_pd</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
                <span class="nn">va</span><span class="p">::</span><span class="nf">table_from_frame</span><span class="p">(</span><span class="n">child_pd_frame</span><span class="p">,</span> <span class="n">physical_memory_offset</span><span class="p">)</span>
            <span class="p">};</span>

            <span class="c1">// PD エントリを走査</span>
            <span class="k">for</span> <span class="n">pd_idx</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="mi">512usize</span> <span class="p">{</span>
                <span class="k">if</span> <span class="o">!</span><span class="n">parent_pd</span><span class="p">[</span><span class="n">pd_idx</span><span class="p">]</span><span class="nf">.flags</span><span class="p">()</span><span class="nf">.contains</span><span class="p">(</span><span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">PRESENT</span><span class="p">)</span> <span class="p">{</span>
                    <span class="k">continue</span><span class="p">;</span>
                <span class="p">}</span>

                <span class="c1">// 子の PT を新規割り当て</span>
                <span class="k">let</span> <span class="n">child_pt_frame</span> <span class="o">=</span> <span class="n">frame_allocator</span><span class="nf">.allocate_frame</span><span class="p">()</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"copy_uvm: failed to allocate PT frame"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
                <span class="nn">va</span><span class="p">::</span><span class="nf">init_page_table</span><span class="p">(</span><span class="n">child_pt_frame</span><span class="p">,</span> <span class="n">physical_memory_offset</span><span class="p">);</span>

                <span class="k">let</span> <span class="n">parent_pt_flags</span> <span class="o">=</span> <span class="n">parent_pd</span><span class="p">[</span><span class="n">pd_idx</span><span class="p">]</span><span class="nf">.flags</span><span class="p">();</span>
                <span class="n">child_pd</span><span class="p">[</span><span class="n">pd_idx</span><span class="p">]</span><span class="nf">.set_frame</span><span class="p">(</span><span class="n">child_pt_frame</span><span class="p">,</span> <span class="n">parent_pt_flags</span><span class="p">);</span>

                <span class="k">let</span> <span class="n">parent_pt</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
                    <span class="nn">va</span><span class="p">::</span><span class="nf">table_from_entry</span><span class="p">(</span><span class="o">&amp;</span><span class="n">parent_pd</span><span class="p">[</span><span class="n">pd_idx</span><span class="p">],</span> <span class="n">physical_memory_offset</span><span class="p">)</span>
                <span class="p">};</span>
                <span class="k">let</span> <span class="n">child_pt</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
                    <span class="nn">va</span><span class="p">::</span><span class="nf">table_from_frame</span><span class="p">(</span><span class="n">child_pt_frame</span><span class="p">,</span> <span class="n">physical_memory_offset</span><span class="p">)</span>
                <span class="p">};</span>

                <span class="c1">// PT エントリを走査</span>
                <span class="k">for</span> <span class="n">pt_idx</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="mi">512usize</span> <span class="p">{</span>
                    <span class="k">let</span> <span class="n">parent_pte</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">parent_pt</span><span class="p">[</span><span class="n">pt_idx</span><span class="p">];</span>
                    <span class="k">if</span> <span class="o">!</span><span class="n">parent_pte</span><span class="nf">.flags</span><span class="p">()</span><span class="nf">.contains</span><span class="p">(</span><span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">PRESENT</span><span class="p">)</span> <span class="p">{</span>
                        <span class="k">continue</span><span class="p">;</span>
                    <span class="p">}</span>

                    <span class="c1">// 新しい物理フレームを確保</span>
                    <span class="k">let</span> <span class="n">new_frame</span> <span class="o">=</span> <span class="n">frame_allocator</span>
                        <span class="nf">.allocate_frame</span><span class="p">()</span>
                        <span class="nf">.ok_or</span><span class="p">(</span><span class="s">"copy_uvm: failed to allocate data frame"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

                    <span class="k">let</span> <span class="n">src_virt</span> <span class="o">=</span> <span class="n">physical_memory_offset</span> <span class="o">+</span> <span class="n">parent_pte</span><span class="nf">.addr</span><span class="p">()</span><span class="nf">.as_u64</span><span class="p">();</span>
                    <span class="k">let</span> <span class="n">dst_virt</span> <span class="o">=</span> <span class="n">physical_memory_offset</span> <span class="o">+</span> <span class="n">new_frame</span><span class="nf">.start_address</span><span class="p">()</span><span class="nf">.as_u64</span><span class="p">();</span>

                    <span class="c1">// ページをコピー</span>
                    <span class="k">unsafe</span> <span class="p">{</span>
                        <span class="nn">core</span><span class="p">::</span><span class="nn">ptr</span><span class="p">::</span><span class="nf">copy_nonoverlapping</span><span class="p">(</span>
                            <span class="n">src_virt</span><span class="py">.as_ptr</span><span class="p">::</span><span class="o">&lt;</span><span class="nb">u8</span><span class="o">&gt;</span><span class="p">(),</span>
                            <span class="n">dst_virt</span><span class="py">.as_mut_ptr</span><span class="p">::</span><span class="o">&lt;</span><span class="nb">u8</span><span class="o">&gt;</span><span class="p">(),</span>
                            <span class="k">super</span><span class="p">::</span><span class="n">PAGE_SIZE</span><span class="p">,</span>
                        <span class="p">);</span>
                    <span class="p">}</span>

                    <span class="c1">// 子の PT エントリに新フレームを書き込む</span>
                    <span class="n">child_pt</span><span class="p">[</span><span class="n">pt_idx</span><span class="p">]</span><span class="nf">.set_frame</span><span class="p">(</span><span class="n">new_frame</span><span class="p">,</span> <span class="n">parent_pte</span><span class="nf">.flags</span><span class="p">());</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nf">Ok</span><span class="p">((</span><span class="n">child_offset_table</span><span class="p">,</span> <span class="n">child_pml4_frame</span><span class="p">))</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">new_uvm</span><span class="p">(</span><span class="n">frame_allocator</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="k">impl</span> <span class="n">FrameAllocator</span><span class="o">&lt;</span><span class="n">Size4KiB</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(</span><span class="n">OffsetPageTable</span><span class="o">&lt;</span><span class="k">'static</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">PhysFrame</span><span class="p">),</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// physical_memory_offset</span>
    <span class="k">let</span> <span class="n">physical_memory_offset</span> <span class="o">=</span> <span class="n">PHYSICAL_MEMORY_OFFSET</span><span class="nf">.lock</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"physical memory offset not initialized"</span><span class="p">);</span>

    <span class="c1">// 新しい level-4 フレームを allocate</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">new_frame</span><span class="p">,</span> <span class="n">new_table_ptr</span><span class="p">)</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nn">kmem</span><span class="p">::</span><span class="nf">setup_kvm</span><span class="p">(</span><span class="n">frame_allocator</span><span class="p">,</span> <span class="n">physical_memory_offset</span><span class="p">)</span>
    <span class="p">}</span><span class="o">?</span><span class="p">;</span>

    <span class="k">let</span> <span class="n">new_table</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="o">*</span><span class="n">new_table_ptr</span>
    <span class="p">};</span>

    <span class="c1">// ユーザ空間のエントリのみクリア</span>
    <span class="k">let</span> <span class="n">user_code_l4_index</span> <span class="o">=</span> <span class="p">(</span><span class="k">crate</span><span class="p">::</span><span class="nn">thread</span><span class="p">::</span><span class="nn">uprocess</span><span class="p">::</span><span class="n">USER_CODE_START</span> <span class="o">&gt;&gt;</span> <span class="mi">39</span><span class="p">)</span> <span class="k">as</span> <span class="nb">usize</span> <span class="o">&amp;</span> <span class="mi">0x1FF</span><span class="p">;</span>   <span class="c1">// 32</span>
    <span class="k">let</span> <span class="n">user_stack_l4_index</span> <span class="o">=</span> <span class="p">(</span><span class="k">crate</span><span class="p">::</span><span class="nn">thread</span><span class="p">::</span><span class="nn">uprocess</span><span class="p">::</span><span class="n">USER_STACK_TOP</span> <span class="o">&gt;&gt;</span> <span class="mi">39</span><span class="p">)</span> <span class="k">as</span> <span class="nb">usize</span> <span class="o">&amp;</span> <span class="mi">0x1FF</span><span class="p">;</span>   <span class="c1">// 64</span>
    <span class="n">new_table</span><span class="p">[</span><span class="n">user_code_l4_index</span><span class="p">]</span><span class="nf">.set_unused</span><span class="p">();</span>
    <span class="n">new_table</span><span class="p">[</span><span class="n">user_stack_l4_index</span><span class="p">]</span><span class="nf">.set_unused</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">new_page_table</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nn">OffsetPageTable</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="o">*</span><span class="n">new_table_ptr</span><span class="p">,</span> <span class="n">physical_memory_offset</span><span class="p">)</span>
    <span class="p">};</span>

    <span class="nf">Ok</span><span class="p">((</span><span class="n">new_page_table</span><span class="p">,</span> <span class="n">new_frame</span><span class="p">))</span>
<span class="p">}</span>
</code></pre></div></div>

<p>が、このへんは説明がむずい…。基本的には xv6 と同じような実装で、親プロセスを PML4 から順に走査していって、エントリがあればページをコピーする、とった実装になっています。なんでこんな多重入れ子ループになっているかというと、前回の記事でも掲載した通り x86_64 のページテーブルが 4-level paging を採用しているからです。</p>

<p><img src="../../../assets/img/post/2026-03-07-rust-os-dev-2/image-20260322212543703.webp" alt="image-20260322212543703" style="zoom:50%;" /></p>

<h1 id="elf64-アプリケーションの実装">ELF64 アプリケーションの実装</h1>

<p>まだ <code class="language-plaintext highlighter-rouge">sys_exec()</code> システムコールが残っていますが、先に ELF アプリケーションの実装から紹介したほうが良さそうです。</p>

<p>FerriOS でもユーザアプリケーションをポータブルにできるよう、ELF64 形式でアプリケーションをビルドし、それを <code class="language-plaintext highlighter-rouge">exec()</code> でロードして実行できるようにしています。</p>

<p>ELF のフォーマットについてはこちらの記事が参考になります。</p>

<ul>
  <li><a href="https://qiita.com/ktamido/items/4c04175cfeb90a0dd6f0" target="_blank">ELF (Executable and Linkable Format) ファイルとは #kernel - Qiita</a></li>
</ul>

<h2 id="カーネル側elf64-ローダの実装">カーネル側：ELF64 ローダの実装</h2>

<p>まずは ELF64 フォーマットに従いヘッダを実装していきます。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/exec/mod.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[derive(Clone,</span> <span class="nd">Copy)]</span>
<span class="nd">#[repr(C)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">Elf64Header</span> <span class="p">{</span>
    <span class="n">ident</span><span class="p">:</span> <span class="p">[</span><span class="nb">u8</span><span class="p">;</span> <span class="mi">16</span><span class="p">],</span>
    <span class="n">elf_type</span><span class="p">:</span> <span class="nb">u16</span><span class="p">,</span>
    <span class="n">machine</span><span class="p">:</span> <span class="nb">u16</span><span class="p">,</span>
    <span class="n">version</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="n">entry</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="n">phoff</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="n">shoff</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="n">flags</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="n">ehsize</span><span class="p">:</span> <span class="nb">u16</span><span class="p">,</span>
    <span class="n">phentsize</span><span class="p">:</span> <span class="nb">u16</span><span class="p">,</span>
    <span class="n">phnum</span><span class="p">:</span> <span class="nb">u16</span><span class="p">,</span>
    <span class="n">shentsize</span><span class="p">:</span> <span class="nb">u16</span><span class="p">,</span>
    <span class="n">shnum</span><span class="p">:</span> <span class="nb">u16</span><span class="p">,</span>
    <span class="n">shstrndx</span><span class="p">:</span> <span class="nb">u16</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Elf64Header</span> <span class="p">{</span>
    <span class="cd">/// ELF 識別子先頭4バイトを magic 値として返す</span>
    <span class="k">fn</span> <span class="nf">magic</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u32</span> <span class="p">{</span>
        <span class="nn">u32</span><span class="p">::</span><span class="nf">from_le_bytes</span><span class="p">([</span><span class="k">self</span><span class="py">.ident</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="k">self</span><span class="py">.ident</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="k">self</span><span class="py">.ident</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="k">self</span><span class="py">.ident</span><span class="p">[</span><span class="mi">3</span><span class="p">]])</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nd">#[derive(Clone,</span> <span class="nd">Copy)]</span>
<span class="nd">#[repr(C)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">Elf64ProgramHeader</span> <span class="p">{</span>
    <span class="n">prog_type</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="n">flags</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="n">offset</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="n">vaddr</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="n">paddr</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="n">filesz</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="n">memsz</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="n">align</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>続いてマジックナンバーなど定数も定義しておきます。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/exec/mod.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="n">ELF_MAGIC_NUM</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="mi">0x464C457F</span><span class="p">;</span>
<span class="k">const</span> <span class="n">ELF_CLASS_64</span><span class="p">:</span> <span class="nb">u8</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
<span class="k">const</span> <span class="n">ELF_DATA_LE</span><span class="p">:</span> <span class="nb">u8</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">const</span> <span class="n">ELF_TYPE_EXEC</span><span class="p">:</span> <span class="nb">u16</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
<span class="k">const</span> <span class="n">ELF_MACHINE_X86_64</span><span class="p">:</span> <span class="nb">u16</span> <span class="o">=</span> <span class="mi">0x3E</span><span class="p">;</span>
<span class="k">const</span> <span class="n">ELF_PROG_LOAD</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</code></pre></div></div>

<p>ELF ヘッダを読み取る処理がこちら。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/exec/mod.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// ELF ヘッダを読み取り、対象アーキテクチャなどを検証する</span>
<span class="k">fn</span> <span class="nf">read_elf_header</span><span class="p">(</span><span class="n">image</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="nb">u8</span><span class="p">])</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="n">Elf64Header</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">if</span> <span class="n">image</span><span class="nf">.len</span><span class="p">()</span> <span class="o">&lt;</span> <span class="nn">size_of</span><span class="p">::</span><span class="o">&lt;</span><span class="n">Elf64Header</span><span class="o">&gt;</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="s">"exec: ELF header is truncated"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">let</span> <span class="n">elf</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nn">ptr</span><span class="p">::</span><span class="nf">read_unaligned</span><span class="p">(</span><span class="n">image</span><span class="nf">.as_ptr</span><span class="p">()</span> <span class="k">as</span> <span class="o">*</span><span class="k">const</span> <span class="n">Elf64Header</span><span class="p">)</span>
    <span class="p">};</span>

    <span class="k">if</span> <span class="n">elf</span><span class="py">.ident</span><span class="p">[</span><span class="mi">0</span><span class="o">..</span><span class="mi">4</span><span class="p">]</span> <span class="o">!=</span> <span class="p">[</span><span class="mi">0x7F</span><span class="p">,</span> <span class="sc">b'E'</span><span class="p">,</span> <span class="sc">b'L'</span><span class="p">,</span> <span class="sc">b'F'</span><span class="p">]</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="s">"exec: bad ELF magic"</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="n">elf</span><span class="py">.ident</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">!=</span> <span class="n">ELF_CLASS_64</span> <span class="p">||</span> <span class="n">elf</span><span class="py">.ident</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">!=</span> <span class="n">ELF_DATA_LE</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="s">"exec: unsupported ELF class"</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="n">elf</span><span class="py">.elf_type</span> <span class="o">!=</span> <span class="n">ELF_TYPE_EXEC</span> <span class="p">||</span> <span class="n">elf</span><span class="py">.machine</span> <span class="o">!=</span> <span class="n">ELF_MACHINE_X86_64</span> <span class="p">||</span> <span class="n">elf</span><span class="py">.version</span> <span class="o">!=</span> <span class="mi">1</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="s">"exec: unsupported ELF target"</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="n">elf</span><span class="nf">.magic</span><span class="p">()</span> <span class="o">!=</span> <span class="n">ELF_MAGIC_NUM</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="s">"exec: bad ELF magic"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nf">Ok</span><span class="p">(</span><span class="n">elf</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>ELF ファイルのうち、<code class="language-plaintext highlighter-rouge">LOAD</code> セグメントを読み取ってユーザページテーブルにマップする処理がこちら。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/exec/mod.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// LOAD セグメントをユーザページテーブルへマップして内容を配置する</span>
<span class="k">fn</span> <span class="nf">load_elf_segments</span><span class="p">(</span><span class="n">image</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="nb">u8</span><span class="p">],</span> <span class="n">elf</span><span class="p">:</span> <span class="n">Elf64Header</span><span class="p">,</span> <span class="n">pml4</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">PageTable</span><span class="p">,</span> <span class="n">user_mapper</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">OffsetPageTable</span><span class="o">&lt;</span><span class="k">'static</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">frame_allocator</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="k">impl</span> <span class="n">FrameAllocator</span><span class="o">&lt;</span><span class="n">Size4KiB</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">pa_offset</span> <span class="o">=</span> <span class="nn">usize</span><span class="p">::</span><span class="nf">try_from</span><span class="p">(</span><span class="n">elf</span><span class="py">.phoff</span><span class="p">)</span><span class="nf">.map_err</span><span class="p">(|</span><span class="n">_</span><span class="p">|</span> <span class="s">"exec: invalid phoff"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">pa_entry_size</span> <span class="o">=</span> <span class="nn">usize</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="n">elf</span><span class="py">.phentsize</span><span class="p">);</span>
    <span class="k">if</span> <span class="n">pa_entry_size</span> <span class="o">!=</span> <span class="nn">size_of</span><span class="p">::</span><span class="o">&lt;</span><span class="n">Elf64ProgramHeader</span><span class="o">&gt;</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="s">"exec: unexpected program header size"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="nn">usize</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="n">elf</span><span class="py">.phnum</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">start</span> <span class="o">=</span> <span class="n">pa_offset</span><span class="nf">.checked_add</span><span class="p">(</span><span class="n">i</span><span class="nf">.checked_mul</span><span class="p">(</span><span class="n">pa_entry_size</span><span class="p">)</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"exec: program header overflow"</span><span class="p">)</span><span class="o">?</span><span class="p">)</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"exec: program header overflow"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">end</span> <span class="o">=</span> <span class="n">start</span><span class="nf">.checked_add</span><span class="p">(</span><span class="n">pa_entry_size</span><span class="p">)</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"exec: program header overflow"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">if</span> <span class="n">end</span> <span class="o">&gt;</span> <span class="n">image</span><span class="nf">.len</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="s">"exec: truncated program header"</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="k">let</span> <span class="n">program_header</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
            <span class="nn">ptr</span><span class="p">::</span><span class="nf">read_unaligned</span><span class="p">(</span><span class="n">image</span><span class="p">[</span><span class="n">start</span><span class="o">..</span><span class="n">end</span><span class="p">]</span><span class="nf">.as_ptr</span><span class="p">()</span> <span class="k">as</span> <span class="o">*</span><span class="k">const</span> <span class="n">Elf64ProgramHeader</span><span class="p">)</span>
        <span class="p">};</span>
        <span class="k">if</span> <span class="n">program_header</span><span class="py">.prog_type</span> <span class="o">!=</span> <span class="n">ELF_PROG_LOAD</span> <span class="p">{</span>
            <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">crate</span><span class="p">::</span><span class="nd">println!</span><span class="p">(</span>
            <span class="s">"[exec] LOAD vaddr={:#x}, filesz={:#x}, memsz={:#x}, flags={:#x}"</span><span class="p">,</span>
            <span class="n">program_header</span><span class="py">.vaddr</span><span class="p">,</span>
            <span class="n">program_header</span><span class="py">.filesz</span><span class="p">,</span>
            <span class="n">program_header</span><span class="py">.memsz</span><span class="p">,</span>
            <span class="n">program_header</span><span class="py">.flags</span><span class="p">,</span>
        <span class="p">);</span>
        <span class="k">if</span> <span class="n">program_header</span><span class="py">.memsz</span> <span class="o">&lt;</span> <span class="n">program_header</span><span class="py">.filesz</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="s">"exec: invalid LOAD segment sizes"</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="n">program_header</span><span class="py">.memsz</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
            <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="k">let</span> <span class="n">file_start</span> <span class="o">=</span> <span class="nn">usize</span><span class="p">::</span><span class="nf">try_from</span><span class="p">(</span><span class="n">program_header</span><span class="py">.offset</span><span class="p">)</span><span class="nf">.map_err</span><span class="p">(|</span><span class="n">_</span><span class="p">|</span> <span class="s">"exec: invalid segment offset"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">file_size</span> <span class="o">=</span> <span class="nn">usize</span><span class="p">::</span><span class="nf">try_from</span><span class="p">(</span><span class="n">program_header</span><span class="py">.filesz</span><span class="p">)</span><span class="nf">.map_err</span><span class="p">(|</span><span class="n">_</span><span class="p">|</span> <span class="s">"exec: invalid segment size"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">file_end</span> <span class="o">=</span> <span class="n">file_start</span><span class="nf">.checked_add</span><span class="p">(</span><span class="n">file_size</span><span class="p">)</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"exec: segment overflow"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">if</span> <span class="n">file_end</span> <span class="o">&gt;</span> <span class="n">image</span><span class="nf">.len</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="s">"exec: truncated LOAD segment"</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="k">let</span> <span class="n">segment_start</span> <span class="o">=</span> <span class="nn">VirtAddr</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">program_header</span><span class="py">.vaddr</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">segment_end</span> <span class="o">=</span> <span class="nn">VirtAddr</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">program_header</span><span class="py">.vaddr</span><span class="nf">.checked_add</span><span class="p">(</span><span class="n">program_header</span><span class="py">.memsz</span><span class="p">)</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"exec: invalid segment address"</span><span class="p">)</span><span class="o">?</span><span class="p">);</span>

        <span class="k">let</span> <span class="n">start_page</span> <span class="o">=</span> <span class="nn">Page</span><span class="p">::</span><span class="nf">containing_address</span><span class="p">(</span><span class="n">segment_start</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">end_page</span> <span class="o">=</span> <span class="nn">Page</span><span class="p">::</span><span class="nf">containing_address</span><span class="p">(</span><span class="n">segment_end</span> <span class="o">-</span> <span class="mi">1u64</span><span class="p">);</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">flags</span> <span class="o">=</span> <span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">PRESENT</span> <span class="p">|</span> <span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">USER_ACCESSIBLE</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">program_header</span><span class="py">.flags</span> <span class="o">&amp;</span> <span class="mi">0x2</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">{</span>
            <span class="n">flags</span> <span class="p">|</span><span class="o">=</span> <span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">WRITABLE</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="k">for</span> <span class="n">page</span> <span class="k">in</span> <span class="nn">Page</span><span class="p">::</span><span class="nf">range_inclusive</span><span class="p">(</span><span class="n">start_page</span><span class="p">,</span> <span class="n">end_page</span><span class="p">)</span> <span class="p">{</span>
            <span class="nf">ensure_user_page_mapping</span><span class="p">(</span><span class="n">pml4</span><span class="p">,</span> <span class="n">user_mapper</span><span class="p">,</span> <span class="n">frame_allocator</span><span class="p">,</span> <span class="n">page</span><span class="p">,</span> <span class="n">flags</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="nf">zero_user_range</span><span class="p">(</span><span class="n">pml4</span><span class="p">,</span> <span class="n">frame_allocator</span><span class="p">,</span> <span class="n">program_header</span><span class="py">.vaddr</span><span class="p">,</span> <span class="n">program_header</span><span class="py">.memsz</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="nf">copy_to_user_pagetable</span><span class="p">(</span><span class="n">pml4</span><span class="p">,</span> <span class="n">frame_allocator</span><span class="p">,</span> <span class="n">program_header</span><span class="py">.vaddr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">image</span><span class="p">[</span><span class="n">file_start</span><span class="o">..</span><span class="n">file_end</span><span class="p">])</span><span class="o">?</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">ensure_user_page_mapping</span><span class="p">(</span>
    <span class="n">pml4</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">PageTable</span><span class="p">,</span>
    <span class="n">user_mapper</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">OffsetPageTable</span><span class="o">&lt;</span><span class="k">'static</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">frame_allocator</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="k">impl</span> <span class="n">FrameAllocator</span><span class="o">&lt;</span><span class="n">Size4KiB</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">page</span><span class="p">:</span> <span class="n">Page</span><span class="p">,</span>
    <span class="n">flags</span><span class="p">:</span> <span class="n">PageTableFlags</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">physical_memory_offset</span> <span class="o">=</span> <span class="nn">memory</span><span class="p">::</span><span class="n">PHYSICAL_MEMORY_OFFSET</span>
        <span class="nf">.lock</span><span class="p">()</span>
        <span class="nf">.expect</span><span class="p">(</span><span class="s">"PHYSICAL_MEMORY_OFFSET not initialized"</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">pte</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nn">memory</span><span class="p">::</span><span class="nn">va</span><span class="p">::</span><span class="nf">walk_pagetable</span><span class="p">(</span>
            <span class="n">pml4</span><span class="p">,</span>
            <span class="n">page</span><span class="nf">.start_address</span><span class="p">(),</span>
            <span class="k">false</span><span class="p">,</span>
            <span class="n">physical_memory_offset</span><span class="p">,</span>
            <span class="n">frame_allocator</span><span class="p">,</span>
        <span class="p">)</span>
    <span class="p">};</span>

    <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">entry</span><span class="p">)</span> <span class="o">=</span> <span class="n">pte</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">entry</span><span class="nf">.flags</span><span class="p">()</span><span class="nf">.contains</span><span class="p">(</span><span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">PRESENT</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">entry</span><span class="nf">.set_flags</span><span class="p">(</span><span class="n">entry</span><span class="nf">.flags</span><span class="p">()</span> <span class="p">|</span> <span class="n">flags</span><span class="p">);</span>
            <span class="k">return</span> <span class="nf">Ok</span><span class="p">(());</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nn">memory</span><span class="p">::</span><span class="nn">va</span><span class="p">::</span><span class="nf">map_page</span><span class="p">(</span><span class="n">user_mapper</span><span class="p">,</span> <span class="n">frame_allocator</span><span class="p">,</span> <span class="n">page</span><span class="p">,</span> <span class="n">flags</span><span class="p">)</span>
<span class="p">}</span>

</code></pre></div></div>

<p>また、これらの ELF イメージのロードを経て、ページテーブルやユーザスタックポインタなどを保持ししておくための構造体 <code class="language-plaintext highlighter-rouge">Exec</code> を用意する処理がこちら。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/exec/mod.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">struct</span> <span class="n">Exec</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">page_table</span><span class="p">:</span> <span class="n">PhysFrame</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">entry</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">user_sp</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">argc</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">argv_user_ptr</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
<span class="p">}</span>

<span class="cd">/// ELF と argv から新しい実行イメージを準備する</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">prepare_exec_image</span><span class="p">(</span><span class="n">elf_image</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="nb">u8</span><span class="p">],</span> <span class="n">argv</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">u8</span><span class="o">&gt;</span><span class="p">])</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="n">Exec</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">if</span> <span class="n">elf_image</span><span class="nf">.len</span><span class="p">()</span> <span class="o">&lt;</span> <span class="nn">size_of</span><span class="p">::</span><span class="o">&lt;</span><span class="n">Elf64Header</span><span class="o">&gt;</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="s">"exec: invalid ELF image"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">let</span> <span class="n">elf</span> <span class="o">=</span> <span class="nf">read_elf_header</span><span class="p">(</span><span class="n">elf_image</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="k">let</span> <span class="k">mut</span> <span class="n">guard</span> <span class="o">=</span> <span class="nn">memory</span><span class="p">::</span><span class="n">FRAME_ALLOCATOR</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">frame_allocator</span> <span class="o">=</span> <span class="n">guard</span><span class="nf">.as_mut</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"FRAME_ALLOCATOR not initialized"</span><span class="p">);</span>
    <span class="k">let</span> <span class="p">(</span><span class="k">mut</span> <span class="n">user_mapper</span><span class="p">,</span> <span class="n">page_table</span><span class="p">)</span> <span class="o">=</span> <span class="nn">memory</span><span class="p">::</span><span class="nn">umem</span><span class="p">::</span><span class="nf">new_uvm</span><span class="p">(</span><span class="n">frame_allocator</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="k">let</span> <span class="n">physical_memory_offset</span> <span class="o">=</span> <span class="nn">memory</span><span class="p">::</span><span class="n">PHYSICAL_MEMORY_OFFSET</span><span class="nf">.lock</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"PHYSICAL_MEMORY_OFFSET not initialized"</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">pml4</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="o">*</span><span class="p">(</span><span class="nn">memory</span><span class="p">::</span><span class="nn">va</span><span class="p">::</span><span class="nf">phys_to_virt</span><span class="p">(</span><span class="n">page_table</span><span class="nf">.start_address</span><span class="p">(),</span> <span class="n">physical_memory_offset</span><span class="p">)</span><span class="py">.as_mut_ptr</span><span class="p">::</span><span class="o">&lt;</span><span class="n">PageTable</span><span class="o">&gt;</span><span class="p">())</span>
    <span class="p">};</span>
    <span class="nf">load_elf_segments</span><span class="p">(</span><span class="n">elf_image</span><span class="p">,</span> <span class="n">elf</span><span class="p">,</span> <span class="n">pml4</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">user_mapper</span><span class="p">,</span> <span class="n">frame_allocator</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// ユーザスタック範囲を計算し、先頭側にガードページを置く</span>
    <span class="k">let</span> <span class="n">stack_top</span> <span class="o">=</span> <span class="n">USER_STACK_TOP</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">stack_pages</span> <span class="o">=</span> <span class="nn">memory</span><span class="p">::</span><span class="n">STACK_PAGES</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">stack_bytes</span> <span class="o">=</span> <span class="n">stack_pages</span>
        <span class="nf">.checked_mul</span><span class="p">(</span><span class="nn">memory</span><span class="p">::</span><span class="n">PAGE_SIZE</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">)</span>
        <span class="nf">.ok_or</span><span class="p">(</span><span class="s">"exec: stack size overflow"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">stack_start</span> <span class="o">=</span> <span class="n">stack_top</span><span class="nf">.checked_sub</span><span class="p">(</span><span class="n">stack_bytes</span><span class="p">)</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"exec: stack overflow"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">guard_page_start</span> <span class="o">=</span> <span class="n">stack_start</span>
        <span class="nf">.checked_sub</span><span class="p">(</span><span class="nn">memory</span><span class="p">::</span><span class="n">PAGE_SIZE</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">)</span>
        <span class="nf">.ok_or</span><span class="p">(</span><span class="s">"exec: guard page overflow"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="k">let</span> <span class="n">guard_page</span> <span class="o">=</span> <span class="nn">Page</span><span class="p">::</span><span class="nf">containing_address</span><span class="p">(</span><span class="nn">VirtAddr</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">guard_page_start</span><span class="p">));</span>
    <span class="k">let</span> <span class="n">stack_start_page</span> <span class="o">=</span> <span class="nn">Page</span><span class="p">::</span><span class="nf">containing_address</span><span class="p">(</span><span class="nn">VirtAddr</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">stack_start</span><span class="p">));</span>
    <span class="c1">// ガードページ（U=0）を1枚確保</span>
    <span class="nn">memory</span><span class="p">::</span><span class="nn">va</span><span class="p">::</span><span class="nf">map_page</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">user_mapper</span><span class="p">,</span>
        <span class="n">frame_allocator</span><span class="p">,</span>
        <span class="n">guard_page</span><span class="p">,</span>
        <span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">PRESENT</span> <span class="p">|</span> <span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">WRITABLE</span><span class="p">,</span>
    <span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="c1">// 実際のユーザスタックを複数ページで確保</span>
    <span class="nn">memory</span><span class="p">::</span><span class="nn">va</span><span class="p">::</span><span class="nf">map_pages</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">user_mapper</span><span class="p">,</span>
        <span class="n">frame_allocator</span><span class="p">,</span>
        <span class="n">stack_start_page</span><span class="p">,</span>
        <span class="n">stack_pages</span><span class="p">,</span>
        <span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">PRESENT</span> <span class="p">|</span> <span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">WRITABLE</span> <span class="p">|</span> <span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">USER_ACCESSIBLE</span><span class="p">,</span>
    <span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="k">let</span> <span class="p">(</span><span class="n">user_sp</span><span class="p">,</span> <span class="n">argc</span><span class="p">,</span> <span class="n">argv_user_ptr</span><span class="p">)</span> <span class="o">=</span> <span class="nf">setup_user_stack</span><span class="p">(</span><span class="n">pml4</span><span class="p">,</span> <span class="n">frame_allocator</span><span class="p">,</span> <span class="n">argv</span><span class="p">,</span> <span class="n">stack_top</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="nf">Ok</span><span class="p">(</span><span class="n">Exec</span> <span class="p">{</span>
        <span class="n">page_table</span><span class="p">,</span>
        <span class="n">entry</span><span class="p">:</span> <span class="n">elf</span><span class="py">.entry</span><span class="p">,</span>
        <span class="n">user_sp</span><span class="p">,</span>
        <span class="n">argc</span><span class="p">,</span>
        <span class="n">argv_user_ptr</span>
    <span class="p">})</span>
<span class="p">}</span>
</code></pre></div></div>

<p>ここでやっていることは、ELF ローダの最小実装です。ELF にはセクションヘッダもありますが、実行に本当に必要なのは <code class="language-plaintext highlighter-rouge">LOAD</code> セグメントのほうです。つまり「このファイルの何バイト目から何バイト目までを、ユーザ空間のどの仮想アドレスへ、どの権限で配置するか」という情報だけを取り出して、ページテーブルと実メモリに反映しています。</p>

<p>また、実際に実装してみると、複数の <code class="language-plaintext highlighter-rouge">LOAD</code> セグメントが同じページを共有するケースが普通に出てきます。たとえば <code class="language-plaintext highlighter-rouge">.rodata</code> と <code class="language-plaintext highlighter-rouge">.got</code> が同じ 4KiB ページ内に並ぶことがあり、その場合は単純に <code class="language-plaintext highlighter-rouge">map_to()</code> を2回呼ぶと失敗します。そのため、すでにマップ済みのページならフラグだけをマージし、未マップのページだけを新しくマップする、という処理も必要です。</p>

<p>最後に、実際に ELF アプリケーションを実行する処理がこちら。<code class="language-plaintext highlighter-rouge">exec()</code> システムコールが呼び出されたときは、ここで実装している <code class="language-plaintext highlighter-rouge">exec()</code> が実行されます。 <code class="language-plaintext highlighter-rouge">commit_exec()</code> で ELF イメージを実行させるんですが、スレッドの Trap Frame に反映させていく過程で失敗した場合にロールバックできる形にしています。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/exec/mod.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// パスからユーザプログラムを解決して exec を完了する</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">exec</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">,</span> <span class="n">argv</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">u8</span><span class="o">&gt;</span><span class="p">])</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">elf_image</span> <span class="o">=</span> <span class="nn">user_programs</span><span class="p">::</span><span class="nf">lookup</span><span class="p">(</span><span class="n">path</span><span class="p">)</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"exec: program not found"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">prepared</span> <span class="o">=</span> <span class="nf">prepare_exec_image</span><span class="p">(</span><span class="n">elf_image</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">argv</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nf">commit_exec</span><span class="p">(</span><span class="n">prepared</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>

<span class="cd">/// 準備済み実行イメージを現在プロセス/スレッドへ反映する</span>
<span class="k">fn</span> <span class="nf">commit_exec</span><span class="p">(</span><span class="n">prepared</span><span class="p">:</span> <span class="n">Exec</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">current_tid</span><span class="p">,</span> <span class="n">current_pid</span><span class="p">)</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">cpu</span> <span class="o">=</span> <span class="nn">cpu</span><span class="p">::</span><span class="n">CPU</span><span class="nf">.lock</span><span class="p">();</span>
        <span class="p">(</span>
            <span class="n">cpu</span><span class="nf">.current_tid</span><span class="p">()</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"exec: no current thread id"</span><span class="p">)</span><span class="o">?</span><span class="p">,</span>
            <span class="n">cpu</span><span class="nf">.current_pid</span><span class="p">()</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"exec: no current process id"</span><span class="p">)</span><span class="o">?</span><span class="p">,</span>
        <span class="p">)</span>
    <span class="p">};</span>

    <span class="c1">// ロールバック用のスナップショットを作成しておく</span>
    <span class="k">let</span> <span class="n">old_page_table</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">process_table</span> <span class="o">=</span> <span class="nn">thread</span><span class="p">::</span><span class="nn">uprocess</span><span class="p">::</span><span class="n">PROCESS_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">process</span> <span class="o">=</span> <span class="n">process_table</span>
            <span class="nf">.get</span><span class="p">(</span><span class="n">current_pid</span><span class="p">)</span>
            <span class="nf">.and_then</span><span class="p">(|</span><span class="n">p</span><span class="p">|</span> <span class="n">p</span><span class="nf">.as_ref</span><span class="p">())</span>
            <span class="nf">.ok_or</span><span class="p">(</span><span class="s">"exec: process table entry missing"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="n">process</span><span class="py">.page_table</span>
    <span class="p">};</span>

    <span class="k">let</span> <span class="p">(</span><span class="n">old_context</span><span class="p">,</span> <span class="n">old_trap_frame</span><span class="p">,</span> <span class="n">old_saved_user_rsp</span><span class="p">)</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">thread_table</span> <span class="o">=</span> <span class="nn">thread</span><span class="p">::</span><span class="n">THREAD_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">thread</span> <span class="o">=</span> <span class="n">thread_table</span>
            <span class="nf">.get</span><span class="p">(</span><span class="n">current_tid</span><span class="p">)</span>
            <span class="nf">.ok_or</span><span class="p">(</span><span class="s">"exec: thread table entry missing"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">trap_frame</span> <span class="o">=</span> <span class="n">thread</span><span class="py">.tf</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"exec: no trapframe"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">old_tf</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="o">*</span><span class="n">trap_frame</span> <span class="p">};</span>
        <span class="k">let</span> <span class="n">old_context</span> <span class="o">=</span> <span class="n">thread</span><span class="py">.context</span><span class="p">;</span>
        <span class="nf">drop</span><span class="p">(</span><span class="n">thread_table</span><span class="p">);</span>

        <span class="k">let</span> <span class="n">cpu</span> <span class="o">=</span> <span class="nn">cpu</span><span class="p">::</span><span class="n">CPU</span><span class="nf">.lock</span><span class="p">();</span>
        <span class="p">(</span><span class="n">old_context</span><span class="p">,</span> <span class="n">old_tf</span><span class="p">,</span> <span class="n">cpu</span><span class="py">.saved_user_rsp</span><span class="p">)</span>
    <span class="p">};</span>

    <span class="k">let</span> <span class="p">(</span><span class="n">old_cr3</span><span class="p">,</span> <span class="n">old_cr3_flags</span><span class="p">)</span> <span class="o">=</span> <span class="nn">control</span><span class="p">::</span><span class="nn">Cr3</span><span class="p">::</span><span class="nf">read</span><span class="p">();</span>

    <span class="c1">// 反映本体（途中で失敗したら下でロールバックする）</span>
    <span class="k">let</span> <span class="n">commit_result</span> <span class="o">=</span> <span class="p">(||</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="p">{</span>
            <span class="k">let</span> <span class="k">mut</span> <span class="n">process_table</span> <span class="o">=</span> <span class="nn">thread</span><span class="p">::</span><span class="nn">uprocess</span><span class="p">::</span><span class="n">PROCESS_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
            <span class="k">let</span> <span class="n">process</span> <span class="o">=</span> <span class="n">process_table</span>
                <span class="nf">.get_mut</span><span class="p">(</span><span class="n">current_pid</span><span class="p">)</span>
                <span class="nf">.and_then</span><span class="p">(|</span><span class="n">p</span><span class="p">|</span> <span class="n">p</span><span class="nf">.as_mut</span><span class="p">())</span>
                <span class="nf">.ok_or</span><span class="p">(</span><span class="s">"exec: process table entry missing"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
            <span class="n">process</span><span class="py">.page_table</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="n">prepared</span><span class="py">.page_table</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="p">{</span>
            <span class="k">let</span> <span class="k">mut</span> <span class="n">thread_table</span> <span class="o">=</span> <span class="nn">thread</span><span class="p">::</span><span class="n">THREAD_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
            <span class="k">let</span> <span class="n">thread</span> <span class="o">=</span> <span class="n">thread_table</span>
                <span class="nf">.get_mut</span><span class="p">(</span><span class="n">current_tid</span><span class="p">)</span>
                <span class="nf">.ok_or</span><span class="p">(</span><span class="s">"exec: thread table entry missing"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
            <span class="n">thread</span><span class="py">.context.rsp3</span> <span class="o">=</span> <span class="n">prepared</span><span class="py">.user_sp</span><span class="p">;</span>
            <span class="n">thread</span><span class="py">.context.user_rip</span> <span class="o">=</span> <span class="n">prepared</span><span class="py">.entry</span><span class="p">;</span>
            <span class="n">thread</span><span class="py">.context.user_rdi</span> <span class="o">=</span> <span class="n">prepared</span><span class="py">.argc</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">;</span>
            <span class="n">thread</span><span class="py">.context.user_rsi</span> <span class="o">=</span> <span class="n">prepared</span><span class="py">.argv_user_ptr</span><span class="p">;</span>

            <span class="k">let</span> <span class="n">trap_frame</span> <span class="o">=</span> <span class="n">thread</span><span class="py">.tf</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"exec: no trapframe"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
            <span class="k">unsafe</span> <span class="p">{</span>
                <span class="p">(</span><span class="o">*</span><span class="n">trap_frame</span><span class="p">)</span><span class="py">.rax</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
                <span class="p">(</span><span class="o">*</span><span class="n">trap_frame</span><span class="p">)</span><span class="py">.rdi</span> <span class="o">=</span> <span class="n">prepared</span><span class="py">.argc</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">;</span>
                <span class="p">(</span><span class="o">*</span><span class="n">trap_frame</span><span class="p">)</span><span class="py">.rsi</span> <span class="o">=</span> <span class="n">prepared</span><span class="py">.argv_user_ptr</span><span class="p">;</span>
                <span class="p">(</span><span class="o">*</span><span class="n">trap_frame</span><span class="p">)</span><span class="py">.rcx</span> <span class="o">=</span> <span class="n">prepared</span><span class="py">.entry</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="p">{</span>
            <span class="k">let</span> <span class="k">mut</span> <span class="n">cpu</span> <span class="o">=</span> <span class="nn">cpu</span><span class="p">::</span><span class="n">CPU</span><span class="nf">.lock</span><span class="p">();</span>
            <span class="n">cpu</span><span class="py">.saved_user_rsp</span> <span class="o">=</span> <span class="n">prepared</span><span class="py">.user_sp</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="k">unsafe</span> <span class="p">{</span>
            <span class="nn">control</span><span class="p">::</span><span class="nn">Cr3</span><span class="p">::</span><span class="nf">write</span><span class="p">(</span><span class="n">prepared</span><span class="py">.page_table</span><span class="p">,</span> <span class="nn">control</span><span class="p">::</span><span class="nn">Cr3Flags</span><span class="p">::</span><span class="nf">empty</span><span class="p">());</span>
        <span class="p">}</span>
        <span class="nf">Ok</span><span class="p">(())</span>
    <span class="p">})();</span>

    <span class="k">if</span> <span class="n">commit_result</span><span class="nf">.is_err</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 途中失敗時はロールバック</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">old_pt</span><span class="p">)</span> <span class="o">=</span> <span class="n">old_page_table</span> <span class="p">{</span>
            <span class="k">let</span> <span class="k">mut</span> <span class="n">process_table</span> <span class="o">=</span> <span class="nn">thread</span><span class="p">::</span><span class="nn">uprocess</span><span class="p">::</span><span class="n">PROCESS_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">process</span><span class="p">)</span> <span class="o">=</span> <span class="n">process_table</span><span class="nf">.get_mut</span><span class="p">(</span><span class="n">current_pid</span><span class="p">)</span><span class="nf">.and_then</span><span class="p">(|</span><span class="n">p</span><span class="p">|</span> <span class="n">p</span><span class="nf">.as_mut</span><span class="p">())</span> <span class="p">{</span>
                <span class="n">process</span><span class="py">.page_table</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="n">old_pt</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="p">{</span>
            <span class="k">let</span> <span class="k">mut</span> <span class="n">thread_table</span> <span class="o">=</span> <span class="nn">thread</span><span class="p">::</span><span class="n">THREAD_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">thread</span><span class="p">)</span> <span class="o">=</span> <span class="n">thread_table</span><span class="nf">.get_mut</span><span class="p">(</span><span class="n">current_tid</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">thread</span><span class="py">.context</span> <span class="o">=</span> <span class="n">old_context</span><span class="p">;</span>
                <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">trap_frame</span><span class="p">)</span> <span class="o">=</span> <span class="n">thread</span><span class="py">.tf</span> <span class="p">{</span>
                    <span class="k">unsafe</span> <span class="p">{</span>
                        <span class="o">*</span><span class="n">trap_frame</span> <span class="o">=</span> <span class="n">old_trap_frame</span><span class="p">;</span>
                    <span class="p">}</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="p">{</span>
            <span class="k">let</span> <span class="k">mut</span> <span class="n">cpu</span> <span class="o">=</span> <span class="nn">cpu</span><span class="p">::</span><span class="n">CPU</span><span class="nf">.lock</span><span class="p">();</span>
            <span class="n">cpu</span><span class="py">.saved_user_rsp</span> <span class="o">=</span> <span class="n">old_saved_user_rsp</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="k">unsafe</span> <span class="p">{</span>
            <span class="nn">control</span><span class="p">::</span><span class="nn">Cr3</span><span class="p">::</span><span class="nf">write</span><span class="p">(</span><span class="n">old_cr3</span><span class="p">,</span> <span class="n">old_cr3_flags</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="n">commit_result</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">exec()</code> は「新しいプロセスを作る」システムコールではなく、「今動いているプロセスの実行イメージを丸ごと差し替える」システムコールです。なので、<code class="language-plaintext highlighter-rouge">fork()</code> で子を増やした後に、その子側で <code class="language-plaintext highlighter-rouge">exec()</code> を呼ぶことで「親はそのまま、子だけ別プログラムに入れ替える」という Unix らしい流れができます。</p>

<p>今回の実装でも、<code class="language-plaintext highlighter-rouge">commit_exec()</code> では現在の <code class="language-plaintext highlighter-rouge">process.page_table</code>、<code class="language-plaintext highlighter-rouge">thread.context</code>、Trap Frame の <code class="language-plaintext highlighter-rouge">RIP/RSP</code> 相当をまとめて新しいプログラムのものへ差し替えています。途中で失敗したらロールバックするのは、これらの情報が中途半端に書き換わると、その後のコンテキストスイッチや <code class="language-plaintext highlighter-rouge">sysretq</code> で簡単にカーネルごと落ちるからです。</p>

<p>ほかにもユーザスタックの構築、ページテーブルへの書き込み、ユーザ空間のゼロクリア、…といった処理がありますが、長くなるのでここでの掲載は割愛します。</p>

<h2 id="ユーザライブラリの用意">ユーザライブラリの用意</h2>

<p>ユーザから簡単にシステムコールを呼び出せるよう、ラップ関数をライブラリとして実装しておきます。Linux でいうところの <code class="language-plaintext highlighter-rouge">linux/syscalls.h</code> みたいな感じです。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo new userlib <span class="nt">--lib</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">userlib/src/lib.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#![no_std]</span>

<span class="k">use</span> <span class="nn">core</span><span class="p">::</span><span class="nn">arch</span><span class="p">::</span><span class="n">asm</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">core</span><span class="p">::</span><span class="nn">fmt</span><span class="p">::{</span><span class="k">self</span><span class="p">,</span> <span class="n">Write</span><span class="p">};</span>
<span class="k">pub</span> <span class="k">use</span> <span class="nn">abi</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">const</span> <span class="n">EXEC_MAX_ARGC</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">16</span><span class="p">;</span>
<span class="k">const</span> <span class="n">EXEC_MAX_ARG_LEN</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">256</span><span class="p">;</span>
<span class="k">const</span> <span class="n">PRINT_FMT_BUF_LEN</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">256</span><span class="p">;</span>

<span class="nd">#[unsafe(no_mangle)]</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">syscall</span><span class="p">(</span><span class="n">num</span><span class="p">:</span> <span class="n">SyscallNum</span><span class="p">,</span> <span class="n">arg1</span><span class="p">:</span> <span class="nb">i64</span><span class="p">,</span> <span class="n">arg2</span><span class="p">:</span> <span class="nb">i64</span><span class="p">,</span> <span class="n">arg3</span><span class="p">:</span> <span class="nb">i64</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">SysRet</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">ret</span><span class="p">:</span> <span class="n">SysRet</span><span class="p">;</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nd">asm!</span><span class="p">(</span>
            <span class="s">"mov rax, rdi"</span><span class="p">,</span>
            <span class="s">"mov rdi, rsi"</span><span class="p">,</span>
            <span class="s">"mov rsi, rdx"</span><span class="p">,</span>
            <span class="s">"mov rdx, rcx"</span><span class="p">,</span>
            <span class="s">"syscall"</span><span class="p">,</span>
            <span class="nf">inlateout</span><span class="p">(</span><span class="s">"rdi"</span><span class="p">)</span> <span class="n">num</span> <span class="k">=&gt;</span> <span class="n">_</span><span class="p">,</span>
            <span class="nf">inlateout</span><span class="p">(</span><span class="s">"rsi"</span><span class="p">)</span> <span class="n">arg1</span> <span class="k">=&gt;</span> <span class="n">_</span><span class="p">,</span>
            <span class="nf">inlateout</span><span class="p">(</span><span class="s">"rdx"</span><span class="p">)</span> <span class="n">arg2</span> <span class="k">=&gt;</span> <span class="n">_</span><span class="p">,</span>
            <span class="nf">inlateout</span><span class="p">(</span><span class="s">"rcx"</span><span class="p">)</span> <span class="n">arg3</span> <span class="k">=&gt;</span> <span class="n">_</span><span class="p">,</span>
            <span class="nf">lateout</span><span class="p">(</span><span class="s">"rax"</span><span class="p">)</span> <span class="n">ret</span><span class="p">,</span>
            <span class="nf">lateout</span><span class="p">(</span><span class="s">"r11"</span><span class="p">)</span> <span class="n">_</span><span class="p">,</span>
            <span class="nf">options</span><span class="p">(</span><span class="n">nostack</span><span class="p">),</span>
        <span class="p">);</span>
    <span class="p">}</span>
    <span class="n">ret</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">print_num</span><span class="p">(</span><span class="n">num</span><span class="p">:</span> <span class="nb">i64</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">SysRet</span> <span class="p">{</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nf">syscall</span><span class="p">(</span><span class="n">SYS_PRINT_NUM</span><span class="p">,</span> <span class="n">num</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">print_str</span><span class="p">(</span><span class="n">s</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">SysRet</span> <span class="p">{</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nf">syscall</span><span class="p">(</span><span class="n">SYS_PRINT_STR</span><span class="p">,</span> <span class="n">s</span><span class="nf">.as_ptr</span><span class="p">()</span> <span class="k">as</span> <span class="nb">i64</span><span class="p">,</span> <span class="n">s</span><span class="nf">.len</span><span class="p">()</span> <span class="k">as</span> <span class="nb">i64</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">fork</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="n">SysRet</span> <span class="p">{</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nf">syscall</span><span class="p">(</span><span class="n">SYS_FORK</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">exec</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">,</span> <span class="n">argv</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="o">&amp;</span><span class="nb">str</span><span class="p">])</span> <span class="k">-&gt;</span> <span class="n">SysRet</span> <span class="p">{</span>
    <span class="k">if</span> <span class="n">argv</span><span class="nf">.len</span><span class="p">()</span> <span class="o">&gt;</span> <span class="n">EXEC_MAX_ARGC</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">RET_ERROR</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">let</span> <span class="k">mut</span> <span class="n">path_buf</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0u8</span><span class="p">;</span> <span class="n">EXEC_MAX_ARG_LEN</span> <span class="o">+</span> <span class="mi">1</span><span class="p">];</span>
    <span class="k">if</span> <span class="nf">copy_c_string</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">path_buf</span><span class="p">)</span><span class="nf">.is_err</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">RET_ERROR</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">let</span> <span class="k">mut</span> <span class="n">arg_bufs</span> <span class="o">=</span> <span class="p">[[</span><span class="mi">0u8</span><span class="p">;</span> <span class="n">EXEC_MAX_ARG_LEN</span> <span class="o">+</span> <span class="mi">1</span><span class="p">];</span> <span class="n">EXEC_MAX_ARGC</span><span class="p">];</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">argv_ptrs</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0u64</span><span class="p">;</span> <span class="n">EXEC_MAX_ARGC</span> <span class="o">+</span> <span class="mi">1</span><span class="p">];</span>

    <span class="k">for</span> <span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">arg</span><span class="p">)</span> <span class="k">in</span> <span class="n">argv</span><span class="nf">.iter</span><span class="p">()</span><span class="nf">.enumerate</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="nf">copy_c_string</span><span class="p">(</span><span class="n">arg</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">arg_bufs</span><span class="p">[</span><span class="n">i</span><span class="p">])</span><span class="nf">.is_err</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">return</span> <span class="n">RET_ERROR</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">argv_ptrs</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">arg_bufs</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="nf">.as_ptr</span><span class="p">()</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nf">syscall</span><span class="p">(</span><span class="n">SYS_EXEC</span><span class="p">,</span> <span class="n">path_buf</span><span class="nf">.as_ptr</span><span class="p">()</span> <span class="k">as</span> <span class="nb">i64</span><span class="p">,</span> <span class="n">argv_ptrs</span><span class="nf">.as_ptr</span><span class="p">()</span> <span class="k">as</span> <span class="nb">i64</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">getpid</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="n">SysRet</span> <span class="p">{</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nf">syscall</span><span class="p">(</span><span class="n">SYS_GETPID</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">copy_c_string</span><span class="p">(</span><span class="n">src</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">,</span> <span class="n">dst</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="p">[</span><span class="nb">u8</span><span class="p">;</span> <span class="n">EXEC_MAX_ARG_LEN</span> <span class="o">+</span> <span class="mi">1</span><span class="p">])</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">bytes</span> <span class="o">=</span> <span class="n">src</span><span class="nf">.as_bytes</span><span class="p">();</span>
    <span class="k">if</span> <span class="n">bytes</span><span class="nf">.len</span><span class="p">()</span> <span class="o">&gt;</span> <span class="n">EXEC_MAX_ARG_LEN</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nf">Err</span><span class="p">(());</span>
    <span class="p">}</span>

    <span class="n">dst</span><span class="p">[</span><span class="o">..</span><span class="n">bytes</span><span class="nf">.len</span><span class="p">()]</span><span class="nf">.copy_from_slice</span><span class="p">(</span><span class="n">bytes</span><span class="p">);</span>
    <span class="n">dst</span><span class="p">[</span><span class="n">bytes</span><span class="nf">.len</span><span class="p">()]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</code></pre></div></div>

<p>基本的には各システムコールに対応する形でラップ関数を用意し、内部で <code class="language-plaintext highlighter-rouge">syscall()</code> を呼び出して指定したシステムコールを呼び出します。システムコール呼び出し処理は <code class="language-plaintext highlighter-rouge">syscall()</code> にアセンブリで実装しています。</p>

<p>この userlib を用意した理由は、ユーザアプリケーション側に毎回 <code class="language-plaintext highlighter-rouge">asm!("syscall")</code> を直書きしたくなかったからです。<code class="language-plaintext highlighter-rouge">print_num(123)</code> や <code class="language-plaintext highlighter-rouge">getpid()</code> のような普通の関数呼び出しに見せておけば、アプリケーション側は syscall 番号やレジスタ割り当てを意識しなくて済みます。</p>

<p>また、<code class="language-plaintext highlighter-rouge">println!()</code> みたいな使い勝手で文字列を表示できるよう、<code class="language-plaintext highlighter-rouge">print_fmt!()</code> マクロもユーザライブラリに実装しています。内部的には <code class="language-plaintext highlighter-rouge">print_str()</code> システムコールを利用しています。</p>

<p><code class="language-plaintext highlighter-rouge">userlib/src/lib.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">print_fmt</span><span class="p">(</span><span class="n">args</span><span class="p">:</span> <span class="nn">fmt</span><span class="p">::</span><span class="n">Arguments</span><span class="o">&lt;</span><span class="nv">'_</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">SysRet</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">buf</span> <span class="o">=</span> <span class="nn">StackBuf</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="k">if</span> <span class="n">buf</span><span class="nf">.write_fmt</span><span class="p">(</span><span class="n">args</span><span class="p">)</span><span class="nf">.is_err</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">RET_ERROR</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="nf">print_str</span><span class="p">(</span><span class="n">buf</span><span class="nf">.as_str</span><span class="p">())</span>
<span class="p">}</span>

<span class="k">struct</span> <span class="n">StackBuf</span> <span class="p">{</span>
    <span class="n">bytes</span><span class="p">:</span> <span class="p">[</span><span class="nb">u8</span><span class="p">;</span> <span class="n">PRINT_FMT_BUF_LEN</span><span class="p">],</span>
    <span class="n">len</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">StackBuf</span> <span class="p">{</span>
    <span class="k">const</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">bytes</span><span class="p">:</span> <span class="p">[</span><span class="mi">0</span><span class="p">;</span> <span class="n">PRINT_FMT_BUF_LEN</span><span class="p">],</span>
            <span class="n">len</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">as_str</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">&amp;</span><span class="nb">str</span> <span class="p">{</span>
        <span class="nn">core</span><span class="p">::</span><span class="nn">str</span><span class="p">::</span><span class="nf">from_utf8</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="py">.bytes</span><span class="p">[</span><span class="o">..</span><span class="k">self</span><span class="py">.len</span><span class="p">])</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="s">""</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Write</span> <span class="k">for</span> <span class="n">StackBuf</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">write_str</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">s</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">fmt</span><span class="p">::</span><span class="nb">Result</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">bytes</span> <span class="o">=</span> <span class="n">s</span><span class="nf">.as_bytes</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">new_len</span> <span class="o">=</span> <span class="k">self</span><span class="py">.len</span><span class="nf">.checked_add</span><span class="p">(</span><span class="n">bytes</span><span class="nf">.len</span><span class="p">())</span><span class="nf">.ok_or</span><span class="p">(</span><span class="nn">fmt</span><span class="p">::</span><span class="n">Error</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">if</span> <span class="n">new_len</span> <span class="o">&gt;</span> <span class="k">self</span><span class="py">.bytes</span><span class="nf">.len</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="nn">fmt</span><span class="p">::</span><span class="n">Error</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="k">self</span><span class="py">.bytes</span><span class="p">[</span><span class="k">self</span><span class="py">.len</span><span class="o">..</span><span class="n">new_len</span><span class="p">]</span><span class="nf">.copy_from_slice</span><span class="p">(</span><span class="n">bytes</span><span class="p">);</span>
        <span class="k">self</span><span class="py">.len</span> <span class="o">=</span> <span class="n">new_len</span><span class="p">;</span>
        <span class="nf">Ok</span><span class="p">(())</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nd">#[macro_export]</span>
<span class="nd">macro_rules!</span> <span class="n">print_fmt</span> <span class="p">{</span>
    <span class="p">(</span><span class="nv">$</span><span class="p">(</span><span class="nv">$arg:tt</span><span class="p">)</span><span class="o">*</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="ユーザ側elf-アプリケーションのビルド環境の準備">ユーザ側：ELF アプリケーションのビルド環境の準備</h2>

<p>ユーザアプリケーションたるもの、カーネルからは独立して別個にビルドできるようにすることが望ましいです。というわけで、こんな構成にしています。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.
├── Cargo.lock
├── Cargo.toml
├── README.md
├── abi
│   ├── Cargo.toml
│   └── src
├── user
│   ├── child
│   └── init
├── build.rs
├── build.sh
├── kernel
│   ├── Cargo.toml
│   ├── build.rs
│   ├── src
│   └── tests
...
</code></pre></div></div>

<p>重要なのは <code class="language-plaintext highlighter-rouge">user</code> ディレクトリです。ここにそれぞれ <code class="language-plaintext highlighter-rouge">child</code>、<code class="language-plaintext highlighter-rouge">init</code> という名のユーザアプリケーションを配置しています。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./user
├── child
│   ├── Cargo.toml
│   ├── build.rs
│   ├── linker.ld
│   └── src
│       └── main.rs
└── init
    ├── Cargo.toml
    ├── build.rs
    ├── linker.ld
    └── src
        └── main.rs
</code></pre></div></div>

<p>各ユーザクレートにはリンカースクリプト <code class="language-plaintext highlighter-rouge">linker.ld</code> を用意し、ELF アプリケーションに対応する形でセクションを定義しています。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ENTRY(main)

SECTIONS
{
  . = 0x0000100000000000;

  .text : ALIGN(4K) {
    *(.text .text.*)
  }

  .rodata : ALIGN(4K) {
    *(.rodata .rodata.*)
  }

  .data : ALIGN(4K) {
    *(.data .data.*)
  }

  .bss : ALIGN(4K) {
    *(.bss .bss.*)
    *(COMMON)
  }
}
</code></pre></div></div>

<p>また、このリンカースクリプトを cargo が参照するよう、各ユーザクレートの <code class="language-plaintext highlighter-rouge">build.rs</code> で <code class="language-plaintext highlighter-rouge">rustc-link-arg</code> オプションを指定しています。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">path</span><span class="p">::</span><span class="n">PathBuf</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">manifest_dir</span> <span class="o">=</span> <span class="nn">PathBuf</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="nn">std</span><span class="p">::</span><span class="nn">env</span><span class="p">::</span><span class="nf">var</span><span class="p">(</span><span class="s">"CARGO_MANIFEST_DIR"</span><span class="p">)</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"CARGO_MANIFEST_DIR not set"</span><span class="p">));</span>
    <span class="k">let</span> <span class="n">linker_script</span> <span class="o">=</span> <span class="n">manifest_dir</span><span class="nf">.join</span><span class="p">(</span><span class="s">"linker.ld"</span><span class="p">);</span>

    <span class="nd">println!</span><span class="p">(</span><span class="s">"cargo:rustc-link-arg=-T{}"</span><span class="p">,</span> <span class="n">linker_script</span><span class="nf">.display</span><span class="p">());</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"cargo:rerun-if-changed={}"</span><span class="p">,</span> <span class="n">linker_script</span><span class="nf">.display</span><span class="p">());</span>
<span class="p">}</span>
</code></pre></div></div>

<p>あとは通常通り、ユーザアプリケーションを Rust コードで実装します。</p>

<p><code class="language-plaintext highlighter-rouge">init</code> アプリケーションはこんな感じ。<code class="language-plaintext highlighter-rouge">fork()</code> で子プロセスを生成し、子プロセス側では <code class="language-plaintext highlighter-rouge">child</code> アプリケーションの ELF ファイルを読ませて実行させています。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#![no_std]</span>
<span class="nd">#![no_main]</span>

<span class="k">use</span> <span class="nn">core</span><span class="p">::</span><span class="nn">panic</span><span class="p">::</span><span class="n">PanicInfo</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">userlib</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="nd">#[unsafe(no_mangle)]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">ret</span> <span class="o">=</span> <span class="nf">fork</span><span class="p">();</span>
    <span class="k">if</span> <span class="n">ret</span> <span class="o">==</span> <span class="n">RET_ERROR</span> <span class="p">{</span>
        <span class="nd">panic!</span><span class="p">(</span><span class="s">"failed to call fork()"</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="n">ret</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
        <span class="c1">// on the child process</span>
        <span class="k">let</span> <span class="n">ret</span> <span class="o">=</span> <span class="nf">exec</span><span class="p">(</span><span class="s">"/child"</span><span class="p">,</span> <span class="o">&amp;</span><span class="p">[]);</span>
        <span class="k">if</span> <span class="n">ret</span> <span class="o">==</span> <span class="n">RET_ERROR</span> <span class="p">{</span>
            <span class="nd">panic!</span><span class="p">(</span><span class="s">"failed to call exec()"</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// on the parent process</span>
    <span class="k">let</span> <span class="n">pid</span> <span class="o">=</span> <span class="nf">getpid</span><span class="p">();</span>
    <span class="k">loop</span> <span class="p">{</span>
        <span class="nd">print_fmt!</span><span class="p">(</span><span class="s">"[parent] pid = {}"</span><span class="p">,</span> <span class="n">pid</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nd">#[panic_handler]</span>
<span class="k">fn</span> <span class="nf">panic</span><span class="p">(</span><span class="n">_info</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">PanicInfo</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="k">loop</span> <span class="p">{</span> <span class="nn">core</span><span class="p">::</span><span class="nn">hint</span><span class="p">::</span><span class="nf">spin_loop</span><span class="p">();</span> <span class="p">}</span>
<span class="p">}</span>

</code></pre></div></div>

<p>こちらは <code class="language-plaintext highlighter-rouge">child</code> アプリケーションで、親プロセス（<code class="language-plaintext highlighter-rouge">init</code> アプリケーション）から fork された子プロセスで <code class="language-plaintext highlighter-rouge">getpid()</code> で自身の PID を取得し、永久ループを回して PID を表示させています。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#![no_std]</span>
<span class="nd">#![no_main]</span>

<span class="k">use</span> <span class="nn">core</span><span class="p">::</span><span class="nn">panic</span><span class="p">::</span><span class="n">PanicInfo</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">userlib</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="nd">#[unsafe(no_mangle)]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">pid</span> <span class="o">=</span> <span class="nf">getpid</span><span class="p">();</span>
    <span class="k">loop</span> <span class="p">{</span>
        <span class="nd">print_fmt!</span><span class="p">(</span><span class="s">"[child] pid = {}"</span><span class="p">,</span> <span class="n">pid</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nd">#[panic_handler]</span>
<span class="k">fn</span> <span class="nf">panic</span><span class="p">(</span><span class="n">_info</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">PanicInfo</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="k">loop</span> <span class="p">{</span> <span class="nn">core</span><span class="p">::</span><span class="nn">hint</span><span class="p">::</span><span class="nf">spin_loop</span><span class="p">();</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>また、これらのユーザアプリケーションがビルドされるように、ルートディレクトリの <code class="language-plaintext highlighter-rouge">build.rs</code> も更新したんですが、長くなるのでざっくり何をしているかだけ書いておきます。ルートの <code class="language-plaintext highlighter-rouge">build.rs</code> では、まず <code class="language-plaintext highlighter-rouge">user</code> ディレクトリ以下の各アプリを custom target 向けに ELF としてビルドします。その後、「実行時のパス」と「生成された ELF のファイルパス」を manifest にまとめて kernel 側 build script に渡し、kernel 側でそれらを <code class="language-plaintext highlighter-rouge">include_bytes!()</code> できる形にコード生成しています。</p>

<p>つまり cargo build を一回叩くと、内部的には</p>

<ol>
  <li>user アプリ群のビルド</li>
  <li>manifest の生成</li>
  <li>kernel 側で ELF を取り込み</li>
  <li>最後に kernel 本体をビルド</li>
</ol>

<p>という順番で処理が進んでいます。ファイルシステムが未実装なうちは少し泥臭いですが、「いまは静的に埋め込んでおき、あとで本物のファイルシステムに置き換える」という方針にしておくと、<code class="language-plaintext highlighter-rouge">exec("/child")</code> のようなインターフェース自体は先に固められるのが良いところです。</p>

<h2 id="ユーザアプリケーションをカーネルに埋め込ませる">ユーザアプリケーションをカーネルに埋め込ませる</h2>

<p>上記の通り、現時点では FerriOS にはまだファイルシステムを実装していないので、ファイルとして ELF ファイルを読み込ませることができません。なので、ユーザアプリケーションとしてビルドした ELF バイナリを、カーネル側にハードコーディングという形でビルド時に埋め込ませます。つまり、今の構成は「user app を事前に ELF 化して、kernel に <code class="language-plaintext highlighter-rouge">include_bytes!()</code> で埋め込む」方式になっています。</p>

<p>具体的な手順としては下記の通りです。</p>

<ul>
  <li>root の build.rs が user app を見つけて、先に ELF としてビルドする</li>
</ul>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">apps</span> <span class="o">=</span> <span class="nf">discover_apps</span><span class="p">(</span><span class="o">&amp;</span><span class="n">apps_root</span><span class="p">);</span>

<span class="k">for</span> <span class="n">app</span> <span class="k">in</span> <span class="o">&amp;</span><span class="n">apps</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">app_status</span> <span class="o">=</span> <span class="nn">Command</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"cargo"</span><span class="p">)</span>
        <span class="nf">.args</span><span class="p">(</span><span class="o">&amp;</span><span class="n">app_args</span><span class="p">)</span>
        <span class="nf">.status</span><span class="p">()</span>
        <span class="nf">.expect</span><span class="p">(</span><span class="s">"failed to build app"</span><span class="p">);</span>
    <span class="nd">assert!</span><span class="p">(</span><span class="n">app_status</span><span class="nf">.success</span><span class="p">(),</span> <span class="s">"app build failed: {}"</span><span class="p">,</span> <span class="n">app</span><span class="py">.dir_name</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<ul>
  <li>各 app の「実行時パス」と「生成された ELF の場所」を manifest にまとめる</li>
</ul>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">manifest_lines</span><span class="nf">.push</span><span class="p">(</span><span class="nd">format!</span><span class="p">(</span>
    <span class="s">"{}</span><span class="se">\t</span><span class="s">{}</span><span class="se">\t</span><span class="s">{}"</span><span class="p">,</span>
    <span class="n">runtime_path</span><span class="p">,</span>
    <span class="n">app</span><span class="py">.package_name</span><span class="p">,</span>
    <span class="n">elf_path</span><span class="nf">.display</span><span class="p">()</span>
<span class="p">));</span>
</code></pre></div></div>

<ul>
  <li>その manifest のパスを USER_APPS_MANIFEST 環境変数で kernel 側 build script に渡す</li>
</ul>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">status</span> <span class="o">=</span> <span class="nn">Command</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"cargo"</span><span class="p">)</span>
    <span class="nf">.env</span><span class="p">(</span><span class="s">"USER_APPS_MANIFEST"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">apps_manifest</span><span class="p">)</span>
    <span class="nf">.args</span><span class="p">(</span><span class="o">&amp;</span><span class="n">args</span><span class="p">)</span>
    <span class="nf">.status</span><span class="p">()</span>
    <span class="nf">.expect</span><span class="p">(</span><span class="s">"failed to build kernel"</span><span class="p">);</span>
</code></pre></div></div>

<ul>
  <li>kernel の build.rs は manifest を読んで、各 ELF を OUT_DIR にコピーする</li>
</ul>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">dst</span> <span class="o">=</span> <span class="n">out_dir</span><span class="nf">.join</span><span class="p">(</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{file_stem}.elf"</span><span class="p">));</span>
<span class="nn">fs</span><span class="p">::</span><span class="nf">copy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">elf_src</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">dst</span><span class="p">)</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"failed to copy app elf"</span><span class="p">);</span>
</code></pre></div></div>

<ul>
  <li>
    <p>include_bytes! 用の Rust コードを自動生成する</p>
  </li>
  <li>
    <p>kernel 本体はその生成コードを include! して、パスから ELF を引けるようにする</p>
  </li>
</ul>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">include!</span><span class="p">(</span><span class="nd">concat!</span><span class="p">(</span><span class="nd">env!</span><span class="p">(</span><span class="s">"OUT_DIR"</span><span class="p">),</span> <span class="s">"/user_programs.rs"</span><span class="p">));</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">lookup</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;&amp;</span><span class="k">'static</span> <span class="p">[</span><span class="nb">u8</span><span class="p">]</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="n">PROGRAMS</span><span class="nf">.iter</span><span class="p">()</span><span class="nf">.find</span><span class="p">(|</span><span class="n">program</span><span class="p">|</span> <span class="n">program</span><span class="py">.path</span> <span class="o">==</span> <span class="n">path</span><span class="p">)</span><span class="nf">.map</span><span class="p">(|</span><span class="n">program</span><span class="p">|</span> <span class="n">program</span><span class="py">.elf</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>この方式のおかげで、<code class="language-plaintext highlighter-rouge">exec()</code> の実装は「まずパスから ELF バイト列を取ってくる」「それを ELF ローダに渡す」という素直な形で書けています。将来的にファイルシステムを実装したら、<code class="language-plaintext highlighter-rouge">lookup(path)</code> の中身をファイル読み出しに置き換えるだけで、<code class="language-plaintext highlighter-rouge">exec()</code> の大部分はそのまま流用できるはずです。</p>

<h1 id="実行結果">実行結果</h1>

<p><code class="language-plaintext highlighter-rouge">kernel_main()</code> からユーザプロセスで <code class="language-plaintext highlighter-rouge">init</code> アプリケーションを実行させるよう実装しています。</p>

<p><code class="language-plaintext highlighter-rouge">kernel/src/main.rs</code>：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// エントリポイント</span>
<span class="k">fn</span> <span class="nf">kernel_main</span><span class="p">(</span><span class="n">boot_info</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="k">mut</span> <span class="n">BootInfo</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="o">...</span>
    <span class="c1">// ユーザプロセス作成</span>
    <span class="nn">thread</span><span class="p">::</span><span class="nn">uprocess</span><span class="p">::</span><span class="nf">create_user_process_from_path</span><span class="p">(</span><span class="s">"/init"</span><span class="p">)</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"failed to create user process"</span><span class="p">);</span>
    <span class="o">...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>実行結果がこちら。</p>

<p><img src="../../../assets/img/post/2026-05-05-rust-os-dev-3/image-20260505224735070.webp" alt="image-20260505224735070" /></p>

<p>うん。うまく動いていますね。親プロセス（pid 0）と子プロセス（pid 1）が交互に実行されていて、それぞれの PID を表示できている様子が確認できます。</p>

<p><code class="language-plaintext highlighter-rouge">Ring 3 confirmed!</code> と書かれているのはコンテキストスイッチ時に表示させているデバッグ出力です。コンテキストスイッチに応じて親子プロセスがそれぞれ交互に実行されています。<code class="language-plaintext highlighter-rouge">fork()</code> できてますし、<code class="language-plaintext highlighter-rouge">exec()</code> で ELF バイナリを読み込めているし、<code class="language-plaintext highlighter-rouge">getpid()</code> で PID を取得できているし、<code class="language-plaintext highlighter-rouge">print_str()</code> で文字列表示もできています。ようやく自作 OS らしくなってきましたね。</p>

<p>ELF バイナリの中身も見てみました。きちんと ELF64 フォーマットになっていますね。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ytani@ytani-Virtual-Machine:~/git/ferrios<span class="nv">$ </span>readelf <span class="nt">-h</span> target/apps-build/x86_64-ferrios/release/init
ELF ヘッダ:
  マジック:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  クラス:                            ELF64
  データ:                            2 の補数、リトルエンディアン
  Version:                           1 <span class="o">(</span>current<span class="o">)</span>
  OS/ABI:                            UNIX - System V
  ABI バージョン:                    0
  型:                                EXEC <span class="o">(</span>実行可能ファイル<span class="o">)</span>
  マシン:                            Advanced Micro Devices X86-64
  バージョン:                        0x1
  エントリポイントアドレス:               0x100000000010
  プログラムヘッダ始点:          64 <span class="o">(</span>バイト<span class="o">)</span>
  セクションヘッダ始点:          20488 <span class="o">(</span>バイト<span class="o">)</span>
  フラグ:                            0x0
  Size of this header:               64 <span class="o">(</span>bytes<span class="o">)</span>
  Size of program headers:           56 <span class="o">(</span>bytes<span class="o">)</span>
  Number of program headers:         7
  Size of section headers:           64 <span class="o">(</span>bytes<span class="o">)</span>
  Number of section headers:         11
  Section header string table index: 9
ytani@ytani-Virtual-Machine:~/git/ferrios<span class="nv">$ </span>readelf <span class="nt">-l</span> target/apps-build/x86_64-ferrios/release/init

Elf ファイルタイプは EXEC <span class="o">(</span>実行可能ファイル<span class="o">)</span> です
エントリポイント 0x100000000010
There are 7 program headers, starting at offset 64

プログラムヘッダ:
  タイプ        オフセット          仮想Addr           物理Addr
                 ファイルサイズ        メモリサイズ         フラグ 整列
  LOAD           0x0000000000001000 0x0000100000000000 0x0000100000000000
                 0x00000000000016e5 0x00000000000016e5  R E    0x1000
  LOAD           0x0000000000003000 0x0000100000002000 0x0000100000002000
                 0x0000000000000304 0x0000000000000304  R      0x1000
  LOAD           0x0000000000003308 0x0000100000002308 0x0000100000002308
                 0x0000000000000010 0x0000000000000010  RW     0x1000
  LOAD           0x0000000000004000 0x0000100000003000 0x0000100000003000
                 0x0000000000000078 0x0000000000000078  RW     0x1000
  GNU_RELRO      0x0000000000003308 0x0000100000002308 0x0000100000002308
                 0x0000000000000010 0x0000000000000010  R      0x1
  GNU_EH_FRAME   0x00000000000032dc 0x00001000000022dc 0x00001000000022dc
                 0x000000000000000c 0x000000000000000c  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x0

 セグメントマッピングへのセクション:
  セグメントセクション...
   00     .text
   01     .rodata .eh_frame_hdr .eh_frame
   02     .got
   03     .data
   04     .got
   05     .eh_frame_hdr
   06
</code></pre></div></div>

<h1 id="おわりに">おわりに</h1>

<p>今回はシステムコール実装と ELF バイナリのロード処理の実装を行いました。任意のユーザアプリケーションが動かせるようになったことで、結構 xv6 に近づいてきたんじゃないかなと思っています。でもまだファイルシステムがありませんね。ELF 実装もなかなかの沼でしたが、次の沼はファイルシステムかな。</p>

<p>FerriOS はこちら ↓ の GitHub リポジトリで開発中のものを公開しております。よかったらご覧ください。</p>

<ul>
  <li><a href="https://github.com/yotiosoft/FerriOS/tree/03-elf-syscalls" target="_blank">https://github.com/yotiosoft/FerriOS/tree/03-elf-syscalls</a></li>
</ul>]]></content><author><name></name></author><category term="Rust" /><category term="OS自作入門" /><category term="FerriOS" /><summary type="html"><![CDATA[自作 OS「FerriOS」の開発日記、第3回です。前回はユーザプロセスを実装して機械語コードでループさせてみて、なんとなく動いていそうだなぁというところまでできました。 しかし、これじゃユーザプロセスからはなんの文字出力もできないですし、使い物になりませんよね。 なので今回は簡単なシステムコールを実装して、ユーザアプリケーションからカーネルに対して文字出力を実行させることで、ユーザプロセスが動いている様を確認できるようにしたいと思います。今回実装するシステムコールは下記の4つ。 fork() exec() getpid() print_num()：数値表示用 print_str()：文字列表示用 print_num()、print_str() は、write() システムコールを実装するまでの暫定的なシステムコールとして実装しています。実際のところは Linux などでは write() システムコールを通して VGA なりデバイスファイルに文字を書き込むことで文字列を表示しているのですが、FerriOS にはまだファイルシステムを全く実装しておりませんので、文字表示専用のシステムコールを用意しています。 また、今回はユーザアプリケーションを ELF アプリケーションとしてビルドし、exec() で ELF からユーザ空間にコードをロードして実行できるようにします。お楽しみに！]]></summary></entry><entry><title type="html">adbでAndroid端末の写真をLinux端末にバックアップする</title><link href="https://blog.yotio.jp/2026/04/15/adb-copy-pictures.html" rel="alternate" type="text/html" title="adbでAndroid端末の写真をLinux端末にバックアップする" /><published>2026-04-15T00:00:00+09:00</published><updated>2026-04-15T00:00:00+09:00</updated><id>https://blog.yotio.jp/2026/04/15/adb-copy-pictures</id><content type="html" xml:base="https://blog.yotio.jp/2026/04/15/adb-copy-pictures.html"><![CDATA[<p>ストレージ容量の少ない Android スマホを使っていると頻繁に写真や動画データをバックアップしなければならなくなるのですが、バックアップ中に起こりがちなのがバックアップ中の切断。おま環かもしれないですが、物理的には接続されているのにコピー中にビジー状態となって勝手に切断されたり、何らかの理由でコピーに失敗することが多いです。</p>

<!--more-->

<h1 id="今回の方針">今回の方針</h1>

<p>adb (Android Debug Bridge) を使ってコピーさせてみます。自分の環境では adb であれば数十 GB 程度のコピーでも安定して実行できました。</p>

<h1 id="実行環境">実行環境</h1>

<ul>
  <li>スマートフォン
    <ul>
      <li>Pixel 7 128GB</li>
      <li>Android 16</li>
    </ul>
  </li>
  <li>バックアップ先
    <ul>
      <li>Raspberry Pi 5</li>
      <li>Ubuntu 24.04.3 LTS</li>
      <li>Linux 6.8.0</li>
      <li>adb: Android Debug Bridge version 1.0.41</li>
    </ul>
  </li>
</ul>

<h1 id="手順">手順</h1>

<h2 id="android-デバイス側usb-デバッグを-on-にする">Android デバイス側：USB デバッグを ON にする</h2>

<p>Android デバイスの「設定」→「システム」→「開発者向けオプション」で</p>

<ul>
  <li>「開発者向けオプションを使用」</li>
  <li>「USB デバッグ」</li>
</ul>

<p>を ON にします。</p>

<p><img src="../../../assets/img/post/2026-01-04-adb-copy/Screenshot_20260415-215813.webp" alt="Screenshot_20260415-215813" style="zoom:50%;" /></p>

<p>「USB デバッグを許可しますか？」と聞かれたら「許可」をタップします。</p>

<p><img src="../../../assets/img/post/2026-01-04-adb-copy/Screenshot_20260415-223107.webp" alt="Screenshot_20260415-223107" style="zoom:50%;" /></p>

<h2 id="バックアップ先準備">バックアップ先：準備</h2>

<p>まずは <code class="language-plaintext highlighter-rouge">adb</code> をインストール。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install </span>adb
</code></pre></div></div>

<p>インストールできたら Android デバイスをバックアップ先端末（Linux）に接続します。</p>

<h2 id="バックアップ先デバイス探索">バックアップ先：デバイス探索</h2>

<p><code class="language-plaintext highlighter-rouge">adb devices -l</code> でデバイスの ID を取得します。（念のため一部隠してます）</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>adb devices <span class="nt">-l</span>
<span class="k">*</span> daemon not running<span class="p">;</span> starting now at tcp:5037
<span class="k">*</span> daemon started successfully
List of devices attached
29051■■■■■■766         unauthorized usb:4-2 transport_id:1
</code></pre></div></div>

<h2 id="バックアップ先コピー">バックアップ先：コピー</h2>

<p>ファイルコピーは次のようなコマンドで実行できます。</p>

<p><code class="language-plaintext highlighter-rouge">$DEVICE_ID</code> は上記で得た Android デバイスのデバイス ID、<code class="language-plaintext highlighter-rouge">$SRC</code> は Android デバイス上のコピー元ディレクトリ、<code class="language-plaintext highlighter-rouge">$DST</code> はバックアップ先のコピー先ディレクトリです。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb <span class="nt">-s</span> <span class="s2">"</span><span class="nv">$DEVICE_ID</span><span class="s2">"</span> pull <span class="s2">"</span><span class="nv">$SRC</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$DST</span><span class="s2">"</span>
</code></pre></div></div>

<p>コピー元ディレクトリは端末によりますが、自分の端末（Pixel7）の場合は写真の保存先は <code class="language-plaintext highlighter-rouge">/sdcard/DCIM/Camera</code> でした。</p>

<p>するとディレクトリ内の全ファイルのコピーが始まります。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>adb <span class="nt">-s</span> 29051■■■■■■766 pull /sdcard/DCIM/Camera /home/ytani/backup/pictures/pixel7
<span class="o">[</span>  0%] /sdcard/DCIM/Camera/PXL_20260321_234739537.jpg: 7%
</code></pre></div></div>

<p>コピーが終わったら忘れずに USB デバッグモードを OFF にしましょう。</p>

<h1 id="おまけコピー自動化スクリプト">おまけ：コピー自動化スクリプト</h1>

<p>デバイス ID 取得からコピーまでを自動化したスクリプトを作成しました。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># !/bin/bash</span>
<span class="c"># usage: ./copy_with_dst.sh ${SRC} ${DST}</span>

<span class="nb">set</span> <span class="nt">-euo</span> pipefail

<span class="nv">SRC</span><span class="o">=</span><span class="nv">$1</span>
<span class="nv">DST</span><span class="o">=</span><span class="nv">$2</span>

<span class="k">if</span> <span class="o">[[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="k">${</span><span class="nv">SRC</span><span class="k">}</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"[ERROR] Please set SRC dir"</span>
  <span class="nb">exit </span>1
<span class="k">fi

if</span> <span class="o">[[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="k">${</span><span class="nv">DST</span><span class="k">}</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"[ERROR] Please set DST dir"</span>
  <span class="nb">exit </span>1
<span class="k">fi

</span><span class="nb">echo</span> <span class="s2">"[*] Detecting adb device..."</span>

<span class="nv">DEVICE_ID</span><span class="o">=</span><span class="si">$(</span>adb devices <span class="nt">-l</span> | <span class="nb">awk</span> <span class="s1">'
  NR&gt;1 &amp;&amp; $2=="device" &amp;&amp; $1 !~ /^emulator/ { print $1; exit }
'</span><span class="si">)</span>

<span class="k">if</span> <span class="o">[[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="k">${</span><span class="nv">DEVICE_ID</span><span class="k">}</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"[ERROR] No usable adb device found."</span>
  adb devices <span class="nt">-l</span>
  <span class="nb">exit </span>1
<span class="k">fi

</span><span class="nb">echo</span> <span class="s2">"[OK] Using device: </span><span class="nv">$DEVICE_ID</span><span class="s2">"</span>

<span class="nb">echo</span> <span class="s2">"[*] Creating destination directory:"</span>
<span class="nb">echo</span> <span class="s2">"    </span><span class="nv">$DST</span><span class="s2">"</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> <span class="s2">"</span><span class="nv">$DST</span><span class="s2">"</span>

<span class="nb">echo</span> <span class="s2">"[*] Copying files..."</span>
adb <span class="nt">-s</span> <span class="s2">"</span><span class="nv">$DEVICE_ID</span><span class="s2">"</span> pull <span class="s2">"</span><span class="nv">$SRC</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$DST</span><span class="s2">"</span>

<span class="nb">echo</span> <span class="s2">"[OK] Copy completed successfully."</span>
</code></pre></div></div>

<p>使い方：第1引数にコピー元、第2引数にコピー先を指定してください。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>./copy_with_dst.sh /sdcard/DCIM/Camera /home/ytani/backup/pictures/pixel7
<span class="o">[</span><span class="k">*</span><span class="o">]</span> Detecting adb device...
<span class="o">[</span>OK] Using device: 29051■■■■■■766
<span class="o">[</span><span class="k">*</span><span class="o">]</span> Creating destination directory:
    <span class="nb">.</span>
<span class="o">[</span><span class="k">*</span><span class="o">]</span> Copying files...
<span class="o">[</span>  0%] /sdcard/DCIM/Camera/PXL_20260320_102006823.jpg: 2%
</code></pre></div></div>]]></content><author><name></name></author><category term="Linux" /><category term="Android" /><summary type="html"><![CDATA[ストレージ容量の少ない Android スマホを使っていると頻繁に写真や動画データをバックアップしなければならなくなるのですが、バックアップ中に起こりがちなのがバックアップ中の切断。おま環かもしれないですが、物理的には接続されているのにコピー中にビジー状態となって勝手に切断されたり、何らかの理由でコピーに失敗することが多いです。]]></summary></entry><entry><title type="html">RustでOS開発#2 ユーザプロセスを実装する</title><link href="https://blog.yotio.jp/2026/03/22/rust-os-dev-2.html" rel="alternate" type="text/html" title="RustでOS開発#2 ユーザプロセスを実装する" /><published>2026-03-22T00:00:00+09:00</published><updated>2026-03-22T00:00:00+09:00</updated><id>https://blog.yotio.jp/2026/03/22/rust-os-dev-2</id><content type="html" xml:base="https://blog.yotio.jp/2026/03/22/rust-os-dev-2.html"><![CDATA[<p>自作 OS「FerriOS」の開発日記、第二回です。前回はスケジューラとカーネルスレッドを実装し、カーネルモードでスレッドが動いている様子を確認できました。今回はユーザプロセスを実装し、カーネルスレッドと同じようにスケジューラで順次実行できる状態にしていきたいと思います。</p>

<!--more-->

<h2 id="rustでos開発シリーズ">RustでOS開発シリーズ</h2>

<ul>
  <li>#1 <a href="../../../2026/02/23/rust-os-dev-1.html">スケジューラとカーネルスレッドの実装</a></li>
  <li>#2 <a href="../../../2026/03/22/rust-os-dev-2.html">ユーザプロセスを実装する</a>（今回）</li>
  <li>#3 <a href="../../../2026/05/05/rust-os-dev-3.html">ELFとシステムコールを実装する</a></li>
  <li>#4 <a href="../../../2026/05/31/rust-os-dev-4.html">exit()/wait()の実装など</a></li>
</ul>

<h1 id="今回のゴール">今回のゴール</h1>

<ul>
  <li>ユーザプロセスを動かせるようにする</li>
</ul>

<p>とりあえずユーザプロセスを実装して、動いていそうなところを確認するまでが今回のゴールです。システムコール実装は次回にします。</p>

<h1 id="ユーザモードの実装">ユーザモードの実装</h1>

<p>ユーザプロセスを実行できるようにするために、まずはユーザモードを実装していきましょう。</p>

<p>現状はカーネルモードにしか対応していませんので、まずはユーザモードへの対応が必要です。</p>

<h2 id="gdt-にセグメントを追加">GDT にセグメントを追加</h2>

<p>GDT (Global Descriptor Table) に <code class="language-plaintext highlighter-rouge">user_code_selector</code> と <code class="language-plaintext highlighter-rouge">user_data_selector</code> を追加し、ユーザ空間のコードセグメントとデータセグメントに対応させていきます。</p>

<p><code class="language-plaintext highlighter-rouge">gdt.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">struct</span> <span class="n">Selectors</span> <span class="p">{</span>
    <span class="n">kernel_code_selector</span><span class="p">:</span> <span class="n">SegmentSelector</span><span class="p">,</span>
    <span class="n">kernel_data_selector</span><span class="p">:</span> <span class="n">SegmentSelector</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">user_code_selector</span><span class="p">:</span> <span class="n">SegmentSelector</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">user_data_selector</span><span class="p">:</span> <span class="n">SegmentSelector</span><span class="p">,</span>
    <span class="n">tss_selector</span><span class="p">:</span> <span class="n">SegmentSelector</span><span class="p">,</span>
<span class="p">}</span>

<span class="nd">lazy_static!</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">static</span> <span class="k">ref</span> <span class="n">GDT</span><span class="p">:</span> <span class="p">(</span><span class="n">GlobalDescriptorTable</span><span class="p">,</span> <span class="n">Selectors</span><span class="p">)</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">gdt</span> <span class="o">=</span> <span class="nn">GlobalDescriptorTable</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">kernel_code_selector</span> <span class="o">=</span> <span class="n">gdt</span><span class="nf">.add_entry</span><span class="p">(</span><span class="nn">Descriptor</span><span class="p">::</span><span class="nf">kernel_code_segment</span><span class="p">());</span>
        <span class="k">let</span> <span class="n">kernel_data_selector</span> <span class="o">=</span> <span class="n">gdt</span><span class="nf">.add_entry</span><span class="p">(</span><span class="nn">Descriptor</span><span class="p">::</span><span class="nf">kernel_data_segment</span><span class="p">());</span>
        <span class="k">let</span> <span class="n">user_code_selector</span> <span class="o">=</span> <span class="n">gdt</span><span class="nf">.add_entry</span><span class="p">(</span><span class="nn">Descriptor</span><span class="p">::</span><span class="nf">user_code_segment</span><span class="p">());</span>
        <span class="k">let</span> <span class="n">user_data_selector</span> <span class="o">=</span> <span class="n">gdt</span><span class="nf">.add_entry</span><span class="p">(</span><span class="nn">Descriptor</span><span class="p">::</span><span class="nf">user_data_segment</span><span class="p">());</span>
        <span class="k">let</span> <span class="n">tss_selector</span> <span class="o">=</span> <span class="n">gdt</span><span class="nf">.add_entry</span><span class="p">(</span><span class="nn">Descriptor</span><span class="p">::</span><span class="nf">tss_segment</span><span class="p">(</span><span class="o">&amp;</span><span class="n">TSS</span><span class="p">));</span>
        <span class="p">(</span><span class="n">gdt</span><span class="p">,</span> <span class="n">Selectors</span> <span class="p">{</span> <span class="n">kernel_code_selector</span><span class="p">,</span> <span class="n">kernel_data_selector</span><span class="p">,</span> <span class="n">user_code_selector</span><span class="p">,</span> <span class="n">user_data_selector</span><span class="p">,</span> <span class="n">tss_selector</span> <span class="p">})</span>
    <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="tss-にカーネルスタックを追加">TSS にカーネルスタックを追加</h2>

<p>TSS (Task State Segment) の <code class="language-plaintext highlighter-rouge">privilege_stack_table</code> を確保するように変更します。このスタックはユーザモードである Ring 3 からカーネルモードである Ring 0 に遷移するときに使用されます。</p>

<p><code class="language-plaintext highlighter-rouge">gdt.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">lazy_static!</span> <span class="p">{</span>
    <span class="k">static</span> <span class="k">ref</span> <span class="n">TSS</span><span class="p">:</span> <span class="n">TaskStateSegment</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">tss</span> <span class="o">=</span> <span class="nn">TaskStateSegment</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
        
        <span class="n">tss</span><span class="py">.interrupt_stack_table</span><span class="p">[</span><span class="n">DOUBLE_FAULT_IST_INDEX</span> <span class="k">as</span> <span class="nb">usize</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
            <span class="k">const</span> <span class="n">STACK_SIZE</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">4096</span> <span class="o">*</span> <span class="mi">5</span><span class="p">;</span>
            <span class="k">static</span> <span class="k">mut</span> <span class="n">STACK</span><span class="p">:</span> <span class="p">[</span><span class="nb">u8</span><span class="p">;</span> <span class="n">STACK_SIZE</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">;</span> <span class="n">STACK_SIZE</span><span class="p">];</span>

            <span class="k">let</span> <span class="n">stack_start</span> <span class="o">=</span> <span class="nn">VirtAddr</span><span class="p">::</span><span class="nf">from_ptr</span><span class="p">(</span><span class="o">&amp;</span><span class="n">raw</span> <span class="k">const</span> <span class="n">STACK</span><span class="p">);</span>
            <span class="k">let</span> <span class="n">stack_end</span> <span class="o">=</span> <span class="n">stack_start</span> <span class="o">+</span> <span class="n">STACK_SIZE</span><span class="p">;</span>
            <span class="n">stack_end</span>
        <span class="p">};</span>
        
        <span class="n">tss</span><span class="py">.privilege_stack_table</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
            <span class="k">const</span> <span class="n">STACK_SIZE</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">4096</span> <span class="o">*</span> <span class="mi">5</span><span class="p">;</span>
            <span class="k">static</span> <span class="k">mut</span> <span class="n">STACK</span><span class="p">:</span> <span class="p">[</span><span class="nb">u8</span><span class="p">;</span> <span class="n">STACK_SIZE</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">;</span> <span class="n">STACK_SIZE</span><span class="p">];</span>

            <span class="k">let</span> <span class="n">stack_start</span> <span class="o">=</span> <span class="nn">VirtAddr</span><span class="p">::</span><span class="nf">from_ptr</span><span class="p">(</span><span class="o">&amp;</span><span class="n">raw</span> <span class="k">const</span> <span class="n">STACK</span><span class="p">);</span>
            <span class="k">let</span> <span class="n">stack_end</span> <span class="o">=</span> <span class="n">stack_start</span> <span class="o">+</span> <span class="n">STACK_SIZE</span><span class="p">;</span>
            <span class="n">stack_end</span>
        <span class="p">};</span>
        
        <span class="n">tss</span>
    <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="ユーザプロセスの実装">ユーザプロセスの実装</h1>

<p>前回実装した <code class="language-plaintext highlighter-rouge">Thread</code> を拡張し、ユーザプロセス用の構造体を実装していきます。</p>

<p>そもそもスレッドとプロセスの違いはなんぞやというと、スレッドは他のスレッドとアドレス空間を共有するタスクで、プロセスは独立したアドレス空間を保有するタスクです。プロセスは1つ以上のスレッドを持ち、1プロセスに属する各スレッドはプロセスのアドレス空間を共有します。</p>

<p>カーネルで動くタスクはカーネル空間を共有して動作するので「カーネルスレッド」、ユーザ向けに作成するタスクは独立したユーザ空間を持つので「ユーザプロセス」と呼び分けています。</p>

<h2 id="ファイルの整理">ファイルの整理</h2>

<p>前回はカーネルスレッドを <code class="language-plaintext highlighter-rouge">thread/mod.rs</code> に実装していましたが、ユーザ向けの処理とごちゃ混ぜになってしまうのでファイルを分けましょう。</p>

<p>まず、 <code class="language-plaintext highlighter-rouge">thread/kthread.rs</code> にカーネルスレッド用の処理を移動させます。</p>

<p><code class="language-plaintext highlighter-rouge">thread/kthread.rs</code></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="k">super</span><span class="p">::{</span> <span class="n">STACK_SIZE</span><span class="p">,</span> <span class="n">THREAD_TABLE</span><span class="p">,</span> <span class="n">ThreadState</span> <span class="p">};</span>

<span class="k">pub</span> <span class="k">const</span> <span class="n">NTHREAD</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">64</span><span class="p">;</span>

<span class="cd">/// カーネルスレッド作成</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">create_kernel_thread</span><span class="p">(</span><span class="n">entry</span><span class="p">:</span> <span class="k">fn</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="o">!</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// スレッド ID を確保</span>
    <span class="k">let</span> <span class="n">tid</span> <span class="o">=</span> <span class="nf">next_tid</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"Thread table is full"</span><span class="p">);</span>

    <span class="c1">// スタックを作成</span>
    <span class="k">let</span> <span class="n">stack</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">layout</span> <span class="o">=</span> <span class="nn">alloc</span><span class="p">::</span><span class="nn">alloc</span><span class="p">::</span><span class="nn">Layout</span><span class="p">::</span><span class="nf">from_size_align</span><span class="p">(</span><span class="n">STACK_SIZE</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
        <span class="nn">alloc</span><span class="p">::</span><span class="nn">alloc</span><span class="p">::</span><span class="nf">alloc</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="p">};</span>
    <span class="k">let</span> <span class="n">stack_top</span> <span class="o">=</span> <span class="n">stack</span> <span class="k">as</span> <span class="nb">u64</span> <span class="o">+</span> <span class="n">STACK_SIZE</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">;</span>

    <span class="k">let</span> <span class="k">mut</span> <span class="n">table</span> <span class="o">=</span> <span class="n">THREAD_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="n">table</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span><span class="py">.tid</span> <span class="o">=</span> <span class="n">tid</span><span class="p">;</span>
    <span class="n">table</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span><span class="py">.state</span> <span class="o">=</span> <span class="nn">ThreadState</span><span class="p">::</span><span class="n">Runnable</span><span class="p">;</span>
    <span class="n">table</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span><span class="py">.kstack</span> <span class="o">=</span> <span class="n">stack_top</span><span class="p">;</span>

    <span class="c1">// コンテキストを初期化する</span>
    <span class="n">table</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span><span class="py">.context.rsp</span> <span class="o">=</span> <span class="n">stack_top</span><span class="p">;</span>
    <span class="n">table</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span><span class="py">.context.rip</span> <span class="o">=</span> <span class="n">entry</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">;</span>
    <span class="n">table</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span><span class="py">.context.rflags</span> <span class="o">=</span> <span class="mi">0x200</span><span class="p">;</span>  <span class="c1">// IF (Interrupt Flag) を有効化</span>
<span class="p">}</span>

<span class="cd">/// スレッド ID 決定</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">next_tid</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="nb">usize</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">table</span> <span class="o">=</span> <span class="n">THREAD_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="n">NTHREAD</span><span class="o">-</span><span class="mi">1</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">table</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="py">.state</span> <span class="o">==</span> <span class="nn">ThreadState</span><span class="p">::</span><span class="n">Unused</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nf">Some</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="nb">None</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">thread/mod.rs</code> にはカーネル・ユーザ共通の <code class="language-plaintext highlighter-rouge">Thread</code> 構造体や <code class="language-plaintext highlighter-rouge">ThreadState</code> のみを残しておきます。</p>

<p><code class="language-plaintext highlighter-rouge">thread/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="n">scheduler</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">scheduler</span><span class="p">::</span><span class="nn">context</span><span class="p">::</span><span class="n">Context</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="n">cpu</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">mod</span> <span class="n">kthread</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">uprocess</span><span class="p">;</span>

<span class="k">extern</span> <span class="k">crate</span> <span class="n">alloc</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">static</span> <span class="n">STACK_SIZE</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">4096</span> <span class="o">*</span> <span class="mi">4</span><span class="p">;</span>

<span class="nd">#[derive(Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy,</span> <span class="nd">PartialEq,</span> <span class="nd">Eq)]</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">ThreadState</span> <span class="p">{</span>
    <span class="o">...</span>
<span class="p">}</span>

<span class="cd">/// Process Control Block</span>
<span class="nd">#[derive(Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">Thread</span> <span class="p">{</span>
    <span class="o">...</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Thread</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="o">...</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">const</span> <span class="n">NTHREAD</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">64</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">spin</span><span class="p">::</span><span class="n">Mutex</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">lazy_static</span><span class="p">::</span><span class="n">lazy_static</span><span class="p">;</span>

<span class="nd">lazy_static!</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">static</span> <span class="k">ref</span> <span class="n">THREAD_TABLE</span><span class="p">:</span> <span class="n">Mutex</span><span class="o">&lt;</span><span class="p">[</span><span class="n">Thread</span><span class="p">;</span> <span class="n">NTHREAD</span><span class="p">]</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">{</span>
        <span class="nn">Mutex</span><span class="p">::</span><span class="nf">new</span><span class="p">([</span><span class="nn">Thread</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span> <span class="n">NTHREAD</span><span class="p">])</span>
    <span class="p">};</span>
<span class="p">}</span>

<span class="cd">/// スレッド ID 決定</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">next_tid</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="nb">usize</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="o">...</span>
<span class="p">}</span>

<span class="cd">/// 現在実行中のスレッドの tid を取得</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">current_tid</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="nb">usize</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="o">...</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="ユーザプロセス構造体の実装">ユーザプロセス構造体の実装</h2>

<p>いよいよ、ユーザプロセスの実装です。</p>

<p>まずは <code class="language-plaintext highlighter-rouge">thread/uprocess/mod.rs</code> を作成し、そこに1つ以上のスレッドをメンバとして持つユーザプロセス構造体 <code class="language-plaintext highlighter-rouge">Process</code> を作成します。</p>

<p><code class="language-plaintext highlighter-rouge">thread/uprocess/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">spin</span><span class="p">::</span><span class="n">Mutex</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">x86_64</span><span class="p">::{</span> <span class="n">VirtAddr</span><span class="p">,</span> <span class="nn">structures</span><span class="p">::</span><span class="nn">paging</span><span class="p">::{</span> <span class="n">FrameAllocator</span><span class="p">,</span> <span class="n">Mapper</span><span class="p">,</span> <span class="n">Page</span><span class="p">,</span> <span class="n">PageTableFlags</span><span class="p">,</span> <span class="n">Size4KiB</span> <span class="p">}</span> <span class="p">};</span>
<span class="k">use</span> <span class="nn">lazy_static</span><span class="p">::</span><span class="n">lazy_static</span><span class="p">;</span>

<span class="k">use</span> <span class="k">super</span><span class="p">::{</span> <span class="n">STACK_SIZE</span><span class="p">,</span> <span class="n">THREAD_TABLE</span><span class="p">,</span> <span class="n">ThreadState</span> <span class="p">};</span>

<span class="k">mod</span> <span class="n">uthread</span><span class="p">;</span>

<span class="cd">/// ユーザコード</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">USER_CODE_START</span><span class="p">:</span> <span class="nb">u64</span> <span class="o">=</span> <span class="mi">0x0000_1000_0000_0000</span><span class="p">;</span>

<span class="cd">/// ユーザスタック</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">USER_STACK_TOP</span><span class="p">:</span> <span class="nb">u64</span> <span class="o">=</span> <span class="mi">0x0000_2000_0000_0000</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">USER_STACK_PAGES</span><span class="p">:</span> <span class="nb">u64</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>

<span class="cd">/// 最大プロセス数</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">NPROCESS</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">16</span><span class="p">;</span>

<span class="cd">/// 1プロセスあたりの最大スレッド数</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">NTHREAD_PER_PROCESS</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span>

<span class="cd">/// Process Control Block (PCB)</span>
<span class="nd">#[derive(Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">Process</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">pid</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">threads</span><span class="p">:</span> <span class="p">[</span><span class="nb">Option</span><span class="o">&lt;</span><span class="nb">usize</span><span class="o">&gt;</span><span class="p">;</span> <span class="n">NTHREAD_PER_PROCESS</span><span class="p">],</span>
    <span class="k">pub</span> <span class="n">nthread</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Process</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="n">Process</span> <span class="p">{</span>
            <span class="n">pid</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
            <span class="n">threads</span><span class="p">:</span> <span class="p">[</span><span class="nb">None</span><span class="p">;</span> <span class="n">NTHREAD_PER_PROCESS</span><span class="p">],</span>
            <span class="n">nthread</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="cd">/// スレッドをプロセスに追加</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">add_thread</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">tid</span><span class="p">:</span> <span class="nb">usize</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">self</span><span class="py">.nthread</span> <span class="o">&gt;=</span> <span class="n">NTHREAD_PER_PROCESS</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="s">"too many threads in process"</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">self</span><span class="py">.threads</span><span class="p">[</span><span class="k">self</span><span class="py">.nthread</span><span class="p">]</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="n">tid</span><span class="p">);</span>
        <span class="k">self</span><span class="py">.nthread</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
        <span class="nf">Ok</span><span class="p">(())</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>上から説明していきます。</p>

<p><code class="language-plaintext highlighter-rouge">USER_CODE_START</code>、<code class="language-plaintext highlighter-rouge">USER_STACK_TOP</code> はそれぞれユーザ空間内のコード領域、スタック領域の始点アドレスです。ここは任意のアドレスで OK ですが、コードサイズやスタックサイズは考慮しておくべきです。<code class="language-plaintext highlighter-rouge">USER_STACK_PAGES</code> は4ページ分とし、4KB分確保しておきます。</p>

<p>プロセス数 <code class="language-plaintext highlighter-rouge">NPROCESS</code> は最大16としています。ここは最大スレッド数 <code class="language-plaintext highlighter-rouge">NTHREAD = 64</code> 以下にしておく必要があります。1プロセスあたりの最大スレッド数 <code class="language-plaintext highlighter-rouge">NTHREAD_PER_PROCESS</code> は8としています。</p>

<p><code class="language-plaintext highlighter-rouge">Process</code> 構造体はプロセス ID <code class="language-plaintext highlighter-rouge">pid</code>、スレッド番号を保持する配列 <code class="language-plaintext highlighter-rouge">threads[]</code>、スレッド数 <code class="language-plaintext highlighter-rouge">nthread</code> を持ちます。</p>

<p><code class="language-plaintext highlighter-rouge">Process</code> には作成時の初期化処理 <code class="language-plaintext highlighter-rouge">new()</code> メソッドと、スレッドを追加する <code class="language-plaintext highlighter-rouge">add_thread()</code> メソッドを実装しました。</p>

<p>次に、この <code class="language-plaintext highlighter-rouge">Process</code> を管理しておく <code class="language-plaintext highlighter-rouge">PROCESS_TABLE</code> 配列を定義しておきます。今のところ、空き PID を取得するためだけに使用しています。</p>

<p><code class="language-plaintext highlighter-rouge">thread/uprocess/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">lazy_static!</span> <span class="p">{</span>
    <span class="cd">/// Process Table</span>
    <span class="k">pub</span> <span class="k">static</span> <span class="k">ref</span> <span class="n">PROCESS_TABLE</span><span class="p">:</span> <span class="n">Mutex</span><span class="o">&lt;</span><span class="p">[</span><span class="nb">Option</span><span class="o">&lt;</span><span class="n">Process</span><span class="o">&gt;</span><span class="p">;</span> <span class="n">NPROCESS</span><span class="p">]</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nn">Mutex</span><span class="p">::</span><span class="nf">new</span><span class="p">([</span><span class="nb">None</span><span class="p">;</span> <span class="n">NPROCESS</span><span class="p">]);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>また、ユーザプロセス作成用のメソッドも定義しておきます。</p>

<p><code class="language-plaintext highlighter-rouge">thread/uprocess/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">create_user_process</span><span class="p">(</span><span class="n">code</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="nb">u8</span><span class="p">],</span> <span class="n">mapper</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="k">impl</span> <span class="n">Mapper</span><span class="o">&lt;</span><span class="n">Size4KiB</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">frame_allocator</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="k">impl</span> <span class="n">FrameAllocator</span><span class="o">&lt;</span><span class="n">Size4KiB</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// ユーザページのフラグ</span>
    <span class="k">let</span> <span class="n">user_flags</span> <span class="o">=</span> <span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">PRESENT</span> <span class="p">|</span> <span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">WRITABLE</span> <span class="p">|</span> <span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">USER_ACCESSIBLE</span><span class="p">;</span>

    <span class="c1">// コードページ用領域を用意</span>
    <span class="k">let</span> <span class="n">code_page</span> <span class="o">=</span> <span class="nn">Page</span><span class="p">::</span><span class="nf">containing_address</span><span class="p">(</span><span class="nn">VirtAddr</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">USER_CODE_START</span><span class="p">));</span>
    <span class="k">let</span> <span class="n">code_frame</span> <span class="o">=</span> <span class="n">frame_allocator</span><span class="nf">.allocate_frame</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"frame alloc failed"</span><span class="p">);</span>
    
    <span class="c1">// コードページにユーザコードをコピー</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="n">mapper</span><span class="nf">.map_to</span><span class="p">(</span><span class="n">code_page</span><span class="p">,</span> <span class="n">code_frame</span><span class="p">,</span> <span class="n">user_flags</span><span class="p">,</span> <span class="n">frame_allocator</span><span class="p">)</span><span class="nf">.map_err</span><span class="p">(|</span><span class="n">_</span><span class="p">|</span> <span class="s">"code map_to failed"</span><span class="p">)</span><span class="o">?</span><span class="nf">.flush</span><span class="p">();</span>
        <span class="nn">core</span><span class="p">::</span><span class="nn">ptr</span><span class="p">::</span><span class="nf">copy_nonoverlapping</span><span class="p">(</span><span class="n">code</span><span class="nf">.as_ptr</span><span class="p">(),</span> <span class="n">USER_CODE_START</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">u8</span><span class="p">,</span> <span class="n">code</span><span class="nf">.len</span><span class="p">());</span>
    <span class="p">}</span>

    <span class="c1">// ユーザスタック用領域を用意</span>
    <span class="k">let</span> <span class="n">stack_start</span> <span class="o">=</span> <span class="n">USER_STACK_TOP</span> <span class="o">-</span> <span class="n">USER_STACK_PAGES</span> <span class="o">*</span> <span class="mi">4096</span><span class="p">;</span>
    <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="n">USER_STACK_PAGES</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">page</span> <span class="o">=</span> <span class="nn">Page</span><span class="p">::</span><span class="nf">containing_address</span><span class="p">(</span><span class="nn">VirtAddr</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">stack_start</span> <span class="o">+</span> <span class="n">i</span> <span class="o">*</span> <span class="mi">4096</span><span class="p">));</span>
        <span class="k">let</span> <span class="n">frame</span> <span class="o">=</span> <span class="n">frame_allocator</span><span class="nf">.allocate_frame</span><span class="p">()</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"frame alloc failed"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">unsafe</span> <span class="p">{</span>
            <span class="n">mapper</span><span class="nf">.map_to</span><span class="p">(</span><span class="n">page</span><span class="p">,</span> <span class="n">frame</span><span class="p">,</span> <span class="n">user_flags</span><span class="p">,</span> <span class="n">frame_allocator</span><span class="p">)</span><span class="nf">.map_err</span><span class="p">(|</span><span class="n">_</span><span class="p">|</span> <span class="s">"stack map_to failed"</span><span class="p">)</span><span class="o">?</span><span class="nf">.flush</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// カーネルスタックを作成</span>
    <span class="k">let</span> <span class="n">kstack</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">layout</span> <span class="o">=</span> <span class="nn">alloc</span><span class="p">::</span><span class="nn">alloc</span><span class="p">::</span><span class="nn">Layout</span><span class="p">::</span><span class="nf">from_size_align</span><span class="p">(</span><span class="n">STACK_SIZE</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
        <span class="nn">alloc</span><span class="p">::</span><span class="nn">alloc</span><span class="p">::</span><span class="nf">alloc</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="p">};</span>
    <span class="k">let</span> <span class="n">kstack_top</span> <span class="o">=</span> <span class="n">kstack</span> <span class="k">as</span> <span class="nb">u64</span> <span class="o">+</span> <span class="n">STACK_SIZE</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">;</span>

    <span class="c1">// init thread を作成</span>
    <span class="k">let</span> <span class="n">thread</span> <span class="o">=</span> <span class="nn">uthread</span><span class="p">::</span><span class="nf">create_user_thread</span><span class="p">(</span><span class="n">kstack_top</span><span class="p">);</span>

    <span class="c1">// Thread Table に追加</span>
    <span class="k">let</span> <span class="n">tid</span> <span class="o">=</span> <span class="n">thread</span><span class="py">.tid</span><span class="p">;</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">thread_table</span> <span class="o">=</span> <span class="n">THREAD_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="n">thread_table</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span> <span class="o">=</span> <span class="n">thread</span><span class="p">;</span>

    <span class="c1">// Process ID を決定</span>
    <span class="k">let</span> <span class="n">pid</span> <span class="o">=</span> <span class="nf">next_pid</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// Process 構造体を作成</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">process</span> <span class="o">=</span> <span class="n">Process</span> <span class="p">{</span>
        <span class="n">pid</span><span class="p">:</span> <span class="n">pid</span><span class="p">,</span>
        <span class="n">threads</span><span class="p">:</span> <span class="p">[</span><span class="nb">None</span><span class="p">;</span> <span class="mi">8</span><span class="p">],</span>
        <span class="n">nthread</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
    <span class="p">};</span>
    <span class="n">process</span><span class="nf">.add_thread</span><span class="p">(</span><span class="n">tid</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// Process Table に追加</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">process_table</span> <span class="o">=</span> <span class="n">PROCESS_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="n">process_table</span><span class="p">[</span><span class="n">pid</span><span class="p">]</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="n">process</span><span class="p">);</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</code></pre></div></div>

<p>ここではコード領域用のページを用意し、コード領域にマップします。その後、ユーザで動かす機械語コードを受け取り、それをユーザ空間内のコード領域にコピーします。</p>

<p>次にスタック領域用のページを用意し、スタック領域にマップします。</p>

<p>カーネルに戻った時用にカーネル側のスタックも必要なので、そちらも用意しておきます。</p>

<p>init thread と書いてあるように、ユーザプロセス内の最初のユーザスレッドを後に実装する <code class="language-plaintext highlighter-rouge">uthread::create_user_thread()</code> で生成しておき、カーネルスレッドと同じように Thread Table に登録します。</p>

<p><code class="language-plaintext highlighter-rouge">next_pid()</code> で空き PID を取得し、ようやく <code class="language-plaintext highlighter-rouge">Process</code> 構造体が作成可能になります。作成した <code class="language-plaintext highlighter-rouge">Process</code> 構造体は <code class="language-plaintext highlighter-rouge">PROCESS_TABLE</code> に追加します。</p>

<p><code class="language-plaintext highlighter-rouge">next_pid()</code> では <code class="language-plaintext highlighter-rouge">PROCESS_TABLE</code> を走査し、空いている最小の PID を取得します。</p>

<p><code class="language-plaintext highlighter-rouge">thread/uprocess.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">next_pid</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">usize</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">table</span> <span class="o">=</span> <span class="n">PROCESS_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="n">NPROCESS</span><span class="o">-</span><span class="mi">1</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">table</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="nf">.is_none</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nf">Ok</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="nf">Err</span><span class="p">(</span><span class="s">"Process table is full"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>ユーザスレッドについては、見やすいように別のファイル <code class="language-plaintext highlighter-rouge">thread/uprocess/uthread.rs</code> に実装しました。</p>

<p><code class="language-plaintext highlighter-rouge">thread/uprocess/uthread.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="k">super</span><span class="p">::{</span> <span class="n">THREAD_TABLE</span><span class="p">,</span> <span class="n">USER_STACK_TOP</span><span class="p">,</span> <span class="n">USER_CODE_START</span><span class="p">,</span> <span class="n">ThreadState</span> <span class="p">};</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::{</span><span class="n">gdt</span><span class="p">,</span> <span class="nn">thread</span><span class="p">::</span><span class="n">Thread</span><span class="p">};</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">create_user_thread</span><span class="p">(</span><span class="n">kstack_top</span><span class="p">:</span> <span class="nb">u64</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Thread</span> <span class="p">{</span>
    <span class="c1">// スレッド ID を確保</span>
    <span class="k">let</span> <span class="n">tid</span> <span class="o">=</span> <span class="k">super</span><span class="p">::</span><span class="k">super</span><span class="p">::</span><span class="nf">next_tid</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"Thread table is full"</span><span class="p">);</span>

    <span class="c1">// スレッドテーブルに追加</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">thread</span> <span class="o">=</span> <span class="nn">Thread</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="n">thread</span><span class="py">.tid</span> <span class="o">=</span> <span class="n">tid</span><span class="p">;</span>
    <span class="n">thread</span><span class="py">.state</span> <span class="o">=</span> <span class="nn">ThreadState</span><span class="p">::</span><span class="n">Runnable</span><span class="p">;</span>
    <span class="n">thread</span><span class="py">.kstack</span> <span class="o">=</span> <span class="n">kstack_top</span><span class="p">;</span>

    <span class="c1">// コンテキストを初期化する</span>
    <span class="n">thread</span><span class="py">.context.rsp</span> <span class="o">=</span> <span class="n">kstack_top</span><span class="p">;</span>
    <span class="n">thread</span><span class="py">.context.rip</span> <span class="o">=</span> <span class="n">ring3_entry_trampoline</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">;</span>
    <span class="n">thread</span><span class="py">.context.rflags</span> <span class="o">=</span> <span class="mi">0x200</span><span class="p">;</span>  <span class="c1">// IF (Interrupt Flag) を有効化</span>
    <span class="n">thread</span><span class="py">.context.cs</span> <span class="o">=</span> <span class="nn">gdt</span><span class="p">::</span><span class="n">GDT</span><span class="na">.1</span><span class="py">.user_code_selector</span><span class="na">.0</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">;</span>
    <span class="n">thread</span><span class="py">.context.ss</span> <span class="o">=</span> <span class="nn">gdt</span><span class="p">::</span><span class="n">GDT</span><span class="na">.1</span><span class="py">.user_data_selector</span><span class="na">.0</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">;</span>
    <span class="n">thread</span><span class="py">.context.rsp3</span> <span class="o">=</span> <span class="n">USER_STACK_TOP</span><span class="p">;</span>

    <span class="n">thread</span>
<span class="p">}</span>
</code></pre></div></div>

<p>実行内容はカーネルスレッド作成とほぼ同じですが、カーネルスレッド生成時の処理に加えて、CS レジスタ、SS レジスタ、RSP3 レジスタ用のコンテキストも初期化しています。</p>

<p><code class="language-plaintext highlighter-rouge">ring3_entry_trampoline()</code> を RIP レジスタに登録しておき、ここをユーザスレッドのエントリポイントとしています。ここでコンテキストに登録した各値をレジスタに設定しています。</p>

<p><code class="language-plaintext highlighter-rouge">thread/uprocess/uthread.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">unsafe</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">ring3_entry_trampoline</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">cs</span><span class="p">,</span> <span class="n">ss</span><span class="p">,</span> <span class="n">rsp3</span><span class="p">,</span> <span class="n">rip</span><span class="p">)</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">table</span> <span class="o">=</span> <span class="n">THREAD_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">ctx</span> <span class="o">=&amp;</span><span class="n">table</span><span class="p">[</span><span class="k">super</span><span class="p">::</span><span class="k">super</span><span class="p">::</span><span class="nf">current_tid</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"No running thread"</span><span class="p">)]</span><span class="py">.context</span><span class="p">;</span>
        <span class="p">(</span><span class="n">ctx</span><span class="py">.cs</span><span class="p">,</span> <span class="n">ctx</span><span class="py">.ss</span><span class="p">,</span> <span class="n">ctx</span><span class="py">.rsp3</span><span class="p">,</span> <span class="n">USER_CODE_START</span><span class="p">)</span>
    <span class="p">};</span>

    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nn">core</span><span class="p">::</span><span class="nn">arch</span><span class="p">::</span><span class="nd">asm!</span><span class="p">(</span>
            <span class="s">"mov ds, ax"</span><span class="p">,</span>
            <span class="s">"mov es, ax"</span><span class="p">,</span>
            <span class="s">"push rax"</span><span class="p">,</span>
            <span class="s">"push {rsp3}"</span><span class="p">,</span>
            <span class="s">"push {rflags}"</span><span class="p">,</span>
            <span class="s">"push {cs}"</span><span class="p">,</span>
            <span class="s">"push {rip}"</span><span class="p">,</span>
            <span class="s">"iretq"</span><span class="p">,</span>            <span class="c1">// switch: cs, ss, rsp, rflags</span>
            <span class="nf">inout</span><span class="p">(</span><span class="s">"ax"</span><span class="p">)</span> <span class="n">ss</span> <span class="k">=&gt;</span> <span class="n">_</span><span class="p">,</span>
            <span class="n">cs</span> <span class="o">=</span> <span class="k">in</span><span class="p">(</span><span class="n">reg</span><span class="p">)</span> <span class="n">cs</span><span class="p">,</span>
            <span class="n">rsp3</span> <span class="o">=</span> <span class="k">in</span><span class="p">(</span><span class="n">reg</span><span class="p">)</span> <span class="n">rsp3</span><span class="p">,</span>
            <span class="n">rflags</span> <span class="o">=</span> <span class="k">in</span><span class="p">(</span><span class="n">reg</span><span class="p">)</span> <span class="mi">0x202u64</span><span class="p">,</span>
            <span class="n">rip</span> <span class="o">=</span> <span class="k">in</span><span class="p">(</span><span class="n">reg</span><span class="p">)</span> <span class="n">rip</span><span class="p">,</span>
        <span class="p">);</span>
    <span class="p">}</span>

    <span class="k">loop</span> <span class="p">{}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="ユーザ空間用ページテーブルの用意">ユーザ空間用ページテーブルの用意</h2>

<p>今のままではユーザプロセスがカーネル空間にアクセスできてしまいますので、それは避けなければなりません。</p>

<p>というわけでユーザ空間用のページテーブルを作成し、そちらに切り替えてユーザプロセスを実行させます。</p>

<p>まずはユーザプロセス作成用の関数を定義します。</p>

<p><code class="language-plaintext highlighter-rouge">memory.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// ユーザ用ページテーブルを作成する</span>
<span class="cd">/// カーネル領域は現在（カーネル）のページテーブルからコピーする</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">create_user_page_table</span><span class="p">(</span><span class="n">frame_allocator</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="k">impl</span> <span class="n">FrameAllocator</span><span class="o">&lt;</span><span class="n">Size4KiB</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">physical_memory_offset</span><span class="p">:</span> <span class="n">VirtAddr</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="p">(</span><span class="n">OffsetPageTable</span><span class="o">&lt;</span><span class="k">'static</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">PhysFrame</span><span class="p">)</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// 新しい level-4 フレームを allocate</span>
    <span class="k">let</span> <span class="n">new_frame</span> <span class="o">=</span> <span class="n">frame_allocator</span><span class="nf">.allocate_frame</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 新しいページテーブルを初期化</span>
    <span class="k">let</span> <span class="n">new_table_va</span> <span class="o">=</span> <span class="n">physical_memory_offset</span> <span class="o">+</span> <span class="n">new_frame</span><span class="nf">.start_address</span><span class="p">()</span><span class="nf">.as_u64</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">new_table_ptr</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="n">PageTable</span> <span class="o">=</span> <span class="n">new_table_va</span><span class="nf">.as_mut_ptr</span><span class="p">();</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="n">new_table_ptr</span><span class="nf">.write</span><span class="p">(</span><span class="nn">PageTable</span><span class="p">::</span><span class="nf">new</span><span class="p">());</span>
    <span class="p">}</span>

    <span class="c1">// カーネル用領域 をコピー</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">current_frame</span><span class="p">,</span> <span class="n">_</span><span class="p">)</span> <span class="o">=</span> <span class="nn">x86_64</span><span class="p">::</span><span class="nn">registers</span><span class="p">::</span><span class="nn">control</span><span class="p">::</span><span class="nn">Cr3</span><span class="p">::</span><span class="nf">read</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">current_va</span> <span class="o">=</span> <span class="n">physical_memory_offset</span> <span class="o">+</span> <span class="n">current_frame</span><span class="nf">.start_address</span><span class="p">()</span><span class="nf">.as_u64</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">current_table_ptr</span><span class="p">:</span> <span class="o">*</span><span class="k">const</span> <span class="n">PageTable</span> <span class="o">=</span> <span class="n">current_va</span><span class="nf">.as_ptr</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">current_table</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
        <span class="o">&amp;*</span><span class="n">current_table_ptr</span>
    <span class="p">};</span>
    <span class="k">let</span> <span class="n">new_table</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="o">*</span><span class="n">new_table_ptr</span>
    <span class="p">};</span>
	
    <span class="c1">// カーネル空間をコピー</span>
    <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">256</span><span class="o">..</span><span class="mi">511</span> <span class="p">{</span>
        <span class="n">new_table</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">current_table</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="nf">.clone</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="c1">// ユーザ空間のエントリのみクリア</span>
    <span class="k">let</span> <span class="n">user_code_l4_index</span> <span class="o">=</span> <span class="p">(</span><span class="k">crate</span><span class="p">::</span><span class="nn">thread</span><span class="p">::</span><span class="nn">uprocess</span><span class="p">::</span><span class="n">USER_CODE_START</span> <span class="o">&gt;&gt;</span> <span class="mi">39</span><span class="p">)</span> <span class="k">as</span> <span class="nb">usize</span> <span class="o">&amp;</span> <span class="mi">0x1FF</span><span class="p">;</span>   <span class="c1">// 32</span>
    <span class="k">let</span> <span class="n">user_stack_l4_index</span> <span class="o">=</span> <span class="p">(</span><span class="k">crate</span><span class="p">::</span><span class="nn">thread</span><span class="p">::</span><span class="nn">uprocess</span><span class="p">::</span><span class="n">USER_STACK_TOP</span> <span class="o">&gt;&gt;</span> <span class="mi">39</span><span class="p">)</span> <span class="k">as</span> <span class="nb">usize</span> <span class="o">&amp;</span> <span class="mi">0x1FF</span><span class="p">;</span>   <span class="c1">// 64</span>
    <span class="n">new_table</span><span class="p">[</span><span class="n">user_code_l4_index</span><span class="p">]</span><span class="nf">.set_unused</span><span class="p">();</span>
    <span class="n">new_table</span><span class="p">[</span><span class="n">user_stack_l4_index</span><span class="p">]</span><span class="nf">.set_unused</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">new_page_table</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nn">OffsetPageTable</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="o">*</span><span class="n">new_table_ptr</span><span class="p">,</span> <span class="n">physical_memory_offset</span><span class="p">)</span>
    <span class="p">};</span>

    <span class="nf">Some</span><span class="p">((</span><span class="n">new_page_table</span><span class="p">,</span> <span class="n">new_frame</span><span class="p">))</span>
<span class="p">}</span>
</code></pre></div></div>

<p>ここではユーザページテーブルを作成する先の物理アドレスを引数で受け取り、まずは空のページテーブルを用意します。</p>

<p>そして、カーネル空間内の PML4 エントリ [256]～[511] をマップしていきます。これは、ユーザプロセスがシステムコールを発行したりコンテキストスイッチを行うときに、カーネルモードでカーネルのメモリオブジェクトにアクセスできるようにするためです。Intel アーキテクチャの 4-level paging の場合、ページは4次元配列のような形で4レベルのディレクトリ構成で管理されていて、PML4 (Page Map Level 4) Table は最上位にあたります。</p>

<p><img src="../../../assets/img/post/2026-03-07-rust-os-dev-2/image-20260322212543703.webp" alt="image-20260322212543703" style="zoom:50%;" /></p>

<p>近年はセキュリティ上の理由（主に Meltdown 脆弱性対策）から、カーネル空間とユーザ空間を別々のページテーブルに分離した KPTI (Kernel Page-Table Isolation) が一般的ですが、今回はそこまで考慮していないので、とりあえず従来通りユーザ用のページテーブルにもカーネル空間をマップしていきます。カーネル空間のマップ先は [256]～[511] としています。[0]～[255] はユーザ空間用に空けておきます。</p>

<p>作成したユーザプロセス用ページテーブルは <code class="language-plaintext highlighter-rouge">Process</code> 構造体に持たせておきます。</p>

<p><code class="language-plaintext highlighter-rouge">thread/uprocess.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">create_user_process</span><span class="p">(</span><span class="n">code</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="nb">u8</span><span class="p">],</span> <span class="n">mapper</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="k">impl</span> <span class="n">Mapper</span><span class="o">&lt;</span><span class="n">Size4KiB</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">frame_allocator</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="k">impl</span> <span class="n">FrameAllocator</span><span class="o">&lt;</span><span class="n">Size4KiB</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// ユーザページのフラグ</span>
    <span class="k">let</span> <span class="n">user_flags</span> <span class="o">=</span> <span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">PRESENT</span> <span class="p">|</span> <span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">WRITABLE</span> <span class="p">|</span> <span class="nn">PageTableFlags</span><span class="p">::</span><span class="n">USER_ACCESSIBLE</span><span class="p">;</span>

    <span class="c1">// ユーザページテーブルを作成</span>
    <span class="k">let</span> <span class="n">physical_memory_offset</span> <span class="o">=</span> <span class="nn">memory</span><span class="p">::</span><span class="n">PHYSICAL_MEMORY_OFFSET</span><span class="nf">.lock</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"physical memory offset not initialized"</span><span class="p">);</span>
    <span class="k">let</span> <span class="p">(</span><span class="k">mut</span> <span class="n">user_mapper</span><span class="p">,</span> <span class="n">page_table</span><span class="p">)</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nn">memory</span><span class="p">::</span><span class="nf">create_user_page_table</span><span class="p">(</span><span class="n">frame_allocator</span><span class="p">,</span> <span class="n">physical_memory_offset</span><span class="p">)</span>
    <span class="p">}</span><span class="nf">.ok_or</span><span class="p">(</span><span class="s">"failed to allocate page table frame"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="o">...</span>
    <span class="c1">// Process 構造体を作成</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">process</span> <span class="o">=</span> <span class="n">Process</span> <span class="p">{</span>
        <span class="n">pid</span><span class="p">:</span> <span class="n">pid</span><span class="p">,</span>
        <span class="n">threads</span><span class="p">:</span> <span class="p">[</span><span class="nb">None</span><span class="p">;</span> <span class="mi">8</span><span class="p">],</span>
        <span class="n">nthread</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
        <span class="n">page_table</span><span class="p">:</span> <span class="nf">Some</span><span class="p">(</span><span class="n">page_table</span><span class="p">),</span>
    <span class="p">};</span>
    <span class="n">process</span><span class="nf">.add_thread</span><span class="p">(</span><span class="n">tid</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// Process Table に追加</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">process_table</span> <span class="o">=</span> <span class="n">PROCESS_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="n">process_table</span><span class="p">[</span><span class="n">pid</span><span class="p">]</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="n">process</span><span class="p">);</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="ユーザカーネル-ページテーブル切り替えの実装">ユーザ/カーネル ページテーブル切り替えの実装</h2>

<p>次に、カーネルページテーブル、ユーザページテーブルそれぞれに切り替える用の関数を用意します。まずはカーネル空間用から。</p>

<p><code class="language-plaintext highlighter-rouge">memory.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// カーネルページテーブルに切り替え</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">switch_to_kernel_page_table</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">kernel_frame</span> <span class="o">=</span> <span class="n">KERNEL_PAGE_TABLE_FRAME</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">frame</span><span class="p">)</span> <span class="o">=</span> <span class="o">*</span><span class="n">kernel_frame</span> <span class="p">{</span>
        <span class="k">unsafe</span> <span class="p">{</span>
            <span class="nn">x86_64</span><span class="p">::</span><span class="nn">registers</span><span class="p">::</span><span class="nn">control</span><span class="p">::</span><span class="nn">Cr3</span><span class="p">::</span><span class="nf">write</span><span class="p">(</span><span class="n">frame</span><span class="p">,</span> <span class="nn">Cr3Flags</span><span class="p">::</span><span class="nf">empty</span><span class="p">());</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>ここでは、 <code class="language-plaintext highlighter-rouge">x86_64::registers::control::Cr3::write()</code> を呼び出して CR3 レジスタにページテーブルフレームのアドレスを書き込んでいます。CR3 レジスタは MMU が参照すべきページテーブルを設定するレジスタです。</p>

<p>つづいてユーザ空間用。こちらも同様の処理が入りますが、ユーザプロセスごとに切り替え先が異なるので、スレッド構造体 <code class="language-plaintext highlighter-rouge">Thread</code> を受け取って、そこからユーザプロセスのページテーブルを取得し切り替えます。</p>

<p><code class="language-plaintext highlighter-rouge">memory.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// ユーザプロセスのページテーブルに切り替え</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">switch_to_user_page_table</span><span class="p">(</span><span class="n">thread</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">thread</span><span class="p">::</span><span class="n">Thread</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">pid</span><span class="p">)</span> <span class="o">=</span> <span class="n">thread</span><span class="py">.pid</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">process_table</span> <span class="o">=</span> <span class="nn">thread</span><span class="p">::</span><span class="nn">uprocess</span><span class="p">::</span><span class="n">PROCESS_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">process</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">process_table</span><span class="p">[</span><span class="n">pid</span><span class="p">]</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"this process does not have page table yet"</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">page_table</span> <span class="o">=</span> <span class="n">process</span><span class="py">.page_table</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"this process is not in the process_table"</span><span class="p">);</span>

        <span class="k">unsafe</span> <span class="p">{</span>
            <span class="nn">x86_64</span><span class="p">::</span><span class="nn">registers</span><span class="p">::</span><span class="nn">control</span><span class="p">::</span><span class="nn">Cr3</span><span class="p">::</span><span class="nf">write</span><span class="p">(</span><span class="n">page_table</span><span class="p">,</span> <span class="nn">x86_64</span><span class="p">::</span><span class="nn">registers</span><span class="p">::</span><span class="nn">control</span><span class="p">::</span><span class="nn">Cr3Flags</span><span class="p">::</span><span class="nf">empty</span><span class="p">());</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">else</span> <span class="p">{</span>
        <span class="nd">panic!</span><span class="p">(</span><span class="s">"this process does not have pid"</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>最後に、ページテーブル切り替えをスケジューラから呼び出します。</p>

<p><code class="language-plaintext highlighter-rouge">scheduler/round_robin.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">impl</span> <span class="k">super</span><span class="p">::</span><span class="n">Scheduler</span> <span class="k">for</span> <span class="n">RoundRobin</span> <span class="p">{</span>
    <span class="o">...</span>
                    <span class="nf">Some</span><span class="p">(</span><span class="n">next_tid</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
                    <span class="k">let</span> <span class="p">(</span><span class="n">old_context</span><span class="p">,</span> <span class="n">new_context</span><span class="p">)</span> <span class="o">=</span> <span class="p">{</span>
                        <span class="c1">// スレッド状態を更新</span>
                        <span class="n">table</span><span class="p">[</span><span class="n">next_tid</span><span class="p">]</span><span class="py">.state</span> <span class="o">=</span> <span class="nn">ThreadState</span><span class="p">::</span><span class="n">Running</span><span class="p">;</span>
                        <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">current_tid</span><span class="p">)</span> <span class="o">=</span> <span class="n">cpu</span><span class="py">.current_tid</span> <span class="p">{</span>
                            <span class="k">if</span> <span class="n">table</span><span class="p">[</span><span class="n">current_tid</span><span class="p">]</span><span class="py">.state</span> <span class="o">==</span> <span class="nn">ThreadState</span><span class="p">::</span><span class="n">Running</span> <span class="p">{</span>
                                <span class="n">table</span><span class="p">[</span><span class="n">current_tid</span><span class="p">]</span><span class="py">.state</span> <span class="o">=</span> <span class="nn">ThreadState</span><span class="p">::</span><span class="n">Runnable</span><span class="p">;</span>
                            <span class="p">}</span>
                        <span class="p">}</span>
                        
                        <span class="c1">// CPU で実行中のスレッド ID を更新</span>
                        <span class="n">cpu</span><span class="py">.current_tid</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="n">next_tid</span><span class="p">);</span>
                        
                        <span class="c1">// CR3 page table switch</span>
                        <span class="k">if</span> <span class="n">table</span><span class="p">[</span><span class="n">next_tid</span><span class="p">]</span><span class="py">.pid</span><span class="nf">.is_some</span><span class="p">()</span> <span class="p">{</span>
                            <span class="c1">// ユーザスレッドの場合：プロセスのユーザページテーブルに切り替え</span>
                            <span class="k">unsafe</span> <span class="p">{</span>
                                <span class="nn">memory</span><span class="p">::</span><span class="nf">switch_to_user_page_table</span><span class="p">(</span><span class="o">&amp;</span><span class="n">table</span><span class="p">[</span><span class="n">next_tid</span><span class="p">]);</span>
                            <span class="p">}</span>
                        <span class="p">}</span>
                        <span class="k">else</span> <span class="p">{</span>
                            <span class="c1">// カーネルスレッドの場合：カーネルページテーブルに切り替え</span>
                            <span class="k">unsafe</span> <span class="p">{</span>
                                <span class="nn">memory</span><span class="p">::</span><span class="nf">switch_to_kernel_page_table</span><span class="p">();</span>
                            <span class="p">}</span>
                        <span class="p">}</span>
                        
                        <span class="k">let</span> <span class="n">old_context</span> <span class="o">=</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">cpu</span><span class="py">.scheduler</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="n">Context</span><span class="p">;</span>
                        <span class="k">let</span> <span class="n">new_context</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">table</span><span class="p">[</span><span class="n">next_tid</span><span class="p">]</span><span class="py">.context</span> <span class="k">as</span> <span class="o">*</span><span class="k">const</span> <span class="n">Context</span><span class="p">;</span>

                        <span class="nf">drop</span><span class="p">(</span><span class="n">cpu</span><span class="p">);</span>
                        <span class="nf">drop</span><span class="p">(</span><span class="n">table</span><span class="p">);</span>

                        <span class="p">(</span><span class="n">old_context</span><span class="p">,</span> <span class="n">new_context</span><span class="p">)</span>
                    <span class="p">};</span>
                    
                    <span class="k">unsafe</span> <span class="p">{</span>
                        <span class="nf">switch_context</span><span class="p">(</span><span class="n">old_context</span><span class="p">,</span> <span class="n">new_context</span><span class="p">);</span>
                    <span class="p">}</span>
    <span class="o">...</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="ここで問題">ここで問題…</h2>

<p>これでうまくいくはず！…と踏んでいたのですが、ページフォルトが発生。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>EXCEPTION: PAGE FAULT
Accessed Address: VirtAddr<span class="o">(</span>0x100000000000<span class="o">)</span>
Error Code: PageFaultErrorCode<span class="o">(</span>USER_MODE | INSTRUCTION_FETCH<span class="o">)</span>
InterruptStackFrame <span class="o">{</span>
    instruction_pointer: VirtAddr<span class="o">(</span>
        0x100000000000,
    <span class="o">)</span>,
    code_segment: 27,
    cpu_flags: 0x202,
    stack_pointer: VirtAddr<span class="o">(</span>
        0x200000000000,
    <span class="o">)</span>,
    stack_segment: 35,
<span class="o">}</span>
</code></pre></div></div>

<p>デバッグを続けると、カーネル領域が [0]～[255] にマップされているっぽいことを確認しました。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Starting kernel threads..done.
entry[0]: <span class="nv">current</span><span class="o">=</span>0x2000 <span class="nv">new</span><span class="o">=</span>0x2000
entry[2]: <span class="nv">current</span><span class="o">=</span>0x4ec000 <span class="nv">new</span><span class="o">=</span>0x4ec000
entry[3]: <span class="nv">current</span><span class="o">=</span>0x4f4000 <span class="nv">new</span><span class="o">=</span>0x4f4000
entry[31]: <span class="nv">current</span><span class="o">=</span>0x4f0000 <span class="nv">new</span><span class="o">=</span>0x4f0000
entry[136]: <span class="nv">current</span><span class="o">=</span>0x288000 <span class="nv">new</span><span class="o">=</span>0x288000
Starting the scheduler..
</code></pre></div></div>

<p>現時点ではユーザプロセス用のページを確保するメソッドを用意していませんが、将来的にユーザプロセス用にページを作成するとなったとき、ユーザとカーネルの PML4 エントリが混在する状況は避けるべきです。そうしないと、どれがカーネル用の PML4 エントリでどれがユーザ用の PML4 エントリかの判別がつかなくなり、どの PML4 エントリをユーザプロセスのページテーブルにマップするべきかが分からなくなるからです。</p>

<p>xv6 などでは、PML4 テーブルのうち前半のエントリはユーザ用で後半はカーネル用、といった形に分けています。今回は xv6 に倣って PML4 の後半 [256]～[511] にカーネル領域をマップされるようにしましょう。つまり、前半 [0]～[255] はユーザ空間用に空けておくべきです。</p>

<p>ただ、カーネルスタックやカーネルコードが PML4 エントリのどこにマップされるかについては、どうやらブートローダ側が自動で決定しているらしく…。FerriOS のブートローダは <code class="language-plaintext highlighter-rouge">bootloader</code> crate (<a href="https://github.com/rust-osdev/bootloader" target="_blank">GitHub</a>) に丸投げしているのですが、ドキュメントを探した限り、すくなくとも現在使用している bootloader v0.9 にはカーネルスタックやカーネルコードなどのカーネル領域のマップ先の指定に関するオプションが見つかりませんでした。（読み飛ばしただけかもしれませんが…）</p>

<p>正確に言えば、カーネルスタックアドレスのオフセットは指定できるのです。ただ、カーネルコード領域のオフセットを指定するオプションが見つかりませんでした。</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[package.metadata.bootloader]</span>
<span class="py">kernel-stack-address</span> <span class="p">=</span> <span class="s">"0xFFFFFF8000000000"</span>
<span class="py">physical-memory-offset</span> <span class="p">=</span> <span class="s">"0xFFFF800000000000"</span>
</code></pre></div></div>

<h2 id="bootloader-を-v011-にする">bootloader を v0.11 にする</h2>

<p><code class="language-plaintext highlighter-rouge">bootloader</code> v0.11 にはカーネルコード領域を始めとする各エントリのオフセットを指定するオプションが追加されているようです。というわけでこちらに切り替えました。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">BOOTLOADER_CONFIG</span><span class="p">:</span> <span class="n">BootloaderConfig</span> <span class="o">=</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">config</span> <span class="o">=</span> <span class="nn">BootloaderConfig</span><span class="p">::</span><span class="nf">new_default</span><span class="p">();</span>
    <span class="n">config</span><span class="py">.mappings.physical_memory</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="nn">Mapping</span><span class="p">::</span><span class="nf">FixedAddress</span><span class="p">(</span><span class="mi">0xFFFF_A000_0000_0000</span><span class="p">));</span> <span class="c1">// index 308</span>
    <span class="n">config</span><span class="py">.mappings.kernel_base</span> <span class="o">=</span> <span class="nn">Mapping</span><span class="p">::</span><span class="nf">FixedAddress</span><span class="p">(</span><span class="mi">0xFFFF_8000_0000_0000</span><span class="p">);</span>           <span class="c1">// index 256</span>
    <span class="n">config</span><span class="py">.mappings.kernel_stack</span> <span class="o">=</span> <span class="nn">Mapping</span><span class="p">::</span><span class="nf">FixedAddress</span><span class="p">(</span><span class="mi">0xFFFF_9000_0000_0000</span><span class="p">);</span>          <span class="c1">// index 288</span>
    <span class="n">config</span><span class="py">.mappings.framebuffer</span> <span class="o">=</span> <span class="nn">Mapping</span><span class="p">::</span><span class="nf">FixedAddress</span><span class="p">(</span><span class="mi">0xFFFF_B000_0000_0000</span><span class="p">);</span>           <span class="c1">// index 324</span>
    <span class="n">config</span><span class="py">.mappings.boot_info</span> <span class="o">=</span> <span class="nn">Mapping</span><span class="p">::</span><span class="nf">FixedAddress</span><span class="p">(</span><span class="mi">0xFFFF_C000_0000_0000</span><span class="p">);</span> 
    <span class="n">config</span>
<span class="p">};</span>

<span class="nd">entry_point!</span><span class="p">(</span><span class="n">kernel_main</span><span class="p">,</span> <span class="n">config</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">BOOTLOADER_CONFIG</span><span class="p">);</span>
</code></pre></div></div>

<p>ただ、v0.9 と v0.11 の間には<strong>数々の破壊的な仕様変更</strong>が入っており、移行作業は容易ではありません。v0.9 から v0.11 に移行するうえでの注意点やヒントなどについては公式のリポジトリにドキュメントとして公開されています。</p>

<ul>
  <li><a href="https://github.com/rust-osdev/bootloader/blob/v0.11.15/docs/migration/v0.9.md">https://github.com/rust-osdev/bootloader/blob/v0.11.15/docs/migration/v0.9.md</a></li>
</ul>

<p>実はこの移行作業で一ヶ月くらい奮闘していたので、もはやどこからどこまでを修正したのか分からなくなっており（適当な開発ですみません）、ここから先の説明は少し雑になってしまいますがご了承ください。</p>

<p>思いつく限りで移行作業の過程で修正した点：</p>

<ul>
  <li>
    <p>ビルドシステム</p>

    <ul>
      <li>
        <p><a href="https://docs.rs/crate/bootloader/0.11.15/source/README.md">bootloader v0.11 の推奨構成</a>では、カーネルコードは <code class="language-plaintext highlighter-rouge">kernel/</code> ディレクトリに移動してサブクレートとし、ルートディレクトリにはイメージビルド用のクレートを定義するというワークスペース方式に切り替わっているようです。つまり、こんな構成になります：</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ferrios/
├── Cargo.toml          ← ワークスペース定義 + os クレート (build-dep に bootloader)
├── Cargo.lock
├── build.rs            ← bios.img を生成
├── src/
│   └── main.rs         ← cargo run で QEMU を起動
├── .cargo/
│   └── config.toml
├── rust-toolchain.toml
└── kernel/             ← カーネル本体（元々の src/ を移動）
    ├── Cargo.toml      ← bootloader_api を依存
    ├── src/
    └── .cargo/
        └── config.toml
</code></pre></div>        </div>
      </li>
      <li>
        <p>これだけでも如何にめんどくさいか分かるでしょう…</p>
      </li>
    </ul>
  </li>
  <li>
    <p>シンボル名の修正</p>

    <ul>
      <li>bootloader v0.11 では、ブートローダ本体は <code class="language-plaintext highlighter-rouge">bootloader</code> に、カーネル側が依存する実装は <code class="language-plaintext highlighter-rouge">bootloader_api</code> に分離されました。つまり、 <code class="language-plaintext highlighter-rouge">use</code> などで定義しているシンボル名を従来の <code class="language-plaintext highlighter-rouge">bootloader</code> から、一部は <code class="language-plaintext highlighter-rouge">bootloader_api</code> に変更する必要があります。</li>
    </ul>
  </li>
  <li>
    <p>ブートローダ設定 (<code class="language-plaintext highlighter-rouge">BootloaderConfig</code>) の定義</p>

    <ul>
      <li>v0.9 では <code class="language-plaintext highlighter-rouge">entry_point!()</code> マクロでエントリポイントを定義していましたが、v0.11 では <code class="language-plaintext highlighter-rouge">entry_point!()</code> にエントリポイントだけでなく、ブートローダ設定も渡す必要があります。これによってカーネルコード領域のアドレスなど、様々な設定を定義可能になりました。
        <div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">BOOTLOADER_CONFIG</span><span class="p">:</span> <span class="n">BootloaderConfig</span> <span class="o">=</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">config</span> <span class="o">=</span> <span class="nn">BootloaderConfig</span><span class="p">::</span><span class="nf">new_default</span><span class="p">();</span>
    <span class="n">config</span><span class="py">.mappings.physical_memory</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="nn">Mapping</span><span class="p">::</span><span class="nf">FixedAddress</span><span class="p">(</span><span class="mi">0xFFFF_A000_0000_0000</span><span class="p">));</span> <span class="c1">// index 308</span>
    <span class="n">config</span><span class="py">.mappings.kernel_base</span> <span class="o">=</span> <span class="nn">Mapping</span><span class="p">::</span><span class="nf">FixedAddress</span><span class="p">(</span><span class="mi">0xFFFF_8000_0000_0000</span><span class="p">);</span>           <span class="c1">// index 256</span>
    <span class="n">config</span><span class="py">.mappings.kernel_stack</span> <span class="o">=</span> <span class="nn">Mapping</span><span class="p">::</span><span class="nf">FixedAddress</span><span class="p">(</span><span class="mi">0xFFFF_9000_0000_0000</span><span class="p">);</span>          <span class="c1">// index 288</span>
    <span class="n">config</span><span class="py">.mappings.framebuffer</span> <span class="o">=</span> <span class="nn">Mapping</span><span class="p">::</span><span class="nf">FixedAddress</span><span class="p">(</span><span class="mi">0xFFFF_B000_0000_0000</span><span class="p">);</span>           <span class="c1">// index 324</span>
    <span class="n">config</span><span class="py">.mappings.boot_info</span> <span class="o">=</span> <span class="nn">Mapping</span><span class="p">::</span><span class="nf">FixedAddress</span><span class="p">(</span><span class="mi">0xFFFF_C000_0000_0000</span><span class="p">);</span> 
    <span class="n">config</span>
<span class="p">};</span>
    
<span class="nd">entry_point!</span><span class="p">(</span><span class="n">kernel_main</span><span class="p">,</span> <span class="n">config</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">BOOTLOADER_CONFIG</span><span class="p">);</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>VGA text mode から frame buffer への移行</p>

    <ul>
      <li>v0.9 では VGA text mode でテキスト出力を比較的簡単に実装できていましたが、v0.11 では frame buffer を使ってくださいとのことです。つまり、フォントのビットマップデータを埋め込んでおき、frame buffer にピクセル単位で描画する必要があります。公式のサンプルは<a href="https://github.com/rust-osdev/bootloader/blob/main/common/src/framebuffer.rs">こちら</a>にあります。</li>
      <li>これもかなり面倒くさい！ですが、好みのフォントを埋め込めるという利点もあります。今回、FerriOS には noto-sans (<a href="https://crates.io/crates/noto-sans-mono-bitmap">noto-sans-mono-bitmap</a>) を埋め込んでみました。</li>
      <li>たぶん GUI の実装も同じ要領でできそう。ゆくゆくは GUI も実装したいですね。</li>
    </ul>
  </li>
</ul>

<p>移行作業でかなりのコードを修正したのですが、ひとつひとつ解説するのは正直骨が折れます。こちらに GitHub で比較して修正前後の diff の URL を載せておきますので、ご興味があれば御覧ください。</p>

<ul>
  <li><a href="https://github.com/yotiosoft/FerriOS/compare/dc569db..b3cb826">https://github.com/yotiosoft/FerriOS/compare/dc569db..b3cb826</a></li>
</ul>

<h3 id="frame-buffer-への対応">frame buffer への対応</h3>

<p>一つだけ解説しがいがありそうなのは frame buffer への対応です。bootloader v0.11 への移行の過程で、VGA text mode の代わりとして実装しました。</p>

<p>結局ここのコードは<a href="https://github.com/rust-osdev/bootloader/blob/main/common/src/framebuffer.rs">公式のサンプル</a>をパクっ…参考にして作ったものなのですが（あ、元のコードは MIT ライセンスですよ）、ピクセル単位で frame buffer に文字を書き込んでいくという操作を実装しています。</p>

<p><code class="language-plaintext highlighter-rouge">console/framebuffer.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Refer to: https://github.com/rust-osdev/bootloader/blob/1d4166141c22c9aaab0136f20305a26093c9a981/common/src/framebuffer.rs</span>

<span class="k">use</span> <span class="nn">bootloader_api</span><span class="p">::</span><span class="nn">info</span><span class="p">::{</span><span class="n">FrameBufferInfo</span><span class="p">,</span> <span class="n">PixelFormat</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">noto_sans_mono_bitmap</span><span class="p">::{</span><span class="n">get_raster</span><span class="p">,</span> <span class="n">FontWeight</span><span class="p">,</span> <span class="n">RasterHeight</span><span class="p">};</span>

<span class="cd">/// フォントの設定</span>
<span class="k">const</span> <span class="n">FONT_WEIGHT</span><span class="p">:</span> <span class="n">FontWeight</span> <span class="o">=</span> <span class="nn">FontWeight</span><span class="p">::</span><span class="n">Bold</span><span class="p">;</span>
<span class="k">const</span> <span class="n">RASTER_HEIGHT</span><span class="p">:</span> <span class="n">RasterHeight</span> <span class="o">=</span> <span class="nn">RasterHeight</span><span class="p">::</span><span class="n">Size16</span><span class="p">;</span>
<span class="k">const</span> <span class="n">CHAR_WIDTH</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span>
<span class="k">const</span> <span class="n">LINE_HEIGHT</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">16</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">struct</span> <span class="n">FrameBufferWriter</span> <span class="p">{</span>
    <span class="n">buffer</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="k">mut</span> <span class="p">[</span><span class="nb">u8</span><span class="p">],</span>
    <span class="n">info</span><span class="p">:</span> <span class="n">FrameBufferInfo</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">x_pos</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">y_pos</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">color</span><span class="p">:</span> <span class="p">[</span><span class="nb">u8</span><span class="p">;</span> <span class="mi">3</span><span class="p">],</span>
    <span class="k">pub</span> <span class="n">weight</span><span class="p">:</span> <span class="n">FontWeight</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">FrameBufferWriter</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">buffer</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="k">mut</span> <span class="p">[</span><span class="nb">u8</span><span class="p">],</span> <span class="n">info</span><span class="p">:</span> <span class="n">FrameBufferInfo</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">buffer</span><span class="p">,</span>
            <span class="n">info</span><span class="p">,</span>
            <span class="n">x_pos</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
            <span class="n">y_pos</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
            <span class="n">color</span><span class="p">:</span> <span class="p">[</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span>
            <span class="n">weight</span><span class="p">:</span> <span class="n">FONT_WEIGHT</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">buffer_mut</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="p">[</span><span class="nb">u8</span><span class="p">]</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.buffer</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">newline</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.x_pos</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="k">self</span><span class="py">.y_pos</span> <span class="o">+=</span> <span class="n">LINE_HEIGHT</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">clear</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.x_pos</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="k">self</span><span class="py">.y_pos</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="k">self</span><span class="py">.buffer</span><span class="nf">.fill</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">write_char</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="nb">char</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">match</span> <span class="n">c</span> <span class="p">{</span>
            <span class="sc">'\n'</span> <span class="k">=&gt;</span> <span class="k">self</span><span class="nf">.newline</span><span class="p">(),</span>
            <span class="n">c</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="k">if</span> <span class="k">self</span><span class="py">.x_pos</span> <span class="o">+</span> <span class="n">CHAR_WIDTH</span> <span class="o">&gt;</span> <span class="k">self</span><span class="py">.info.width</span> <span class="p">{</span>
                    <span class="k">self</span><span class="nf">.newline</span><span class="p">();</span>
                <span class="p">}</span>
                
                <span class="k">self</span><span class="nf">.draw_char</span><span class="p">(</span><span class="n">c</span><span class="p">);</span>
                <span class="k">self</span><span class="py">.x_pos</span> <span class="o">+=</span> <span class="n">CHAR_WIDTH</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">draw_char</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="nb">char</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">char_raster</span> <span class="o">=</span> <span class="nf">get_raster</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="k">self</span><span class="py">.weight</span><span class="p">,</span> <span class="n">RASTER_HEIGHT</span><span class="p">)</span>
            <span class="nf">.unwrap_or_else</span><span class="p">(||</span> <span class="nf">get_raster</span><span class="p">(</span><span class="sc">' '</span><span class="p">,</span> <span class="k">self</span><span class="py">.weight</span><span class="p">,</span> <span class="n">RASTER_HEIGHT</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">());</span>

        <span class="k">for</span> <span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">row</span><span class="p">)</span> <span class="k">in</span> <span class="n">char_raster</span><span class="nf">.raster</span><span class="p">()</span><span class="nf">.iter</span><span class="p">()</span><span class="nf">.enumerate</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">for</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">intensity</span><span class="p">)</span> <span class="k">in</span> <span class="n">row</span><span class="nf">.iter</span><span class="p">()</span><span class="nf">.enumerate</span><span class="p">()</span> <span class="p">{</span>
                <span class="k">self</span><span class="nf">.write_pixel</span><span class="p">(</span><span class="k">self</span><span class="py">.x_pos</span> <span class="o">+</span> <span class="n">x</span><span class="p">,</span> <span class="k">self</span><span class="py">.y_pos</span> <span class="o">+</span> <span class="n">y</span><span class="p">,</span> <span class="o">*</span><span class="n">intensity</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">write_pixel</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">x</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span> <span class="n">intensity</span><span class="p">:</span> <span class="nb">u8</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">pixel_offset</span> <span class="o">=</span> <span class="n">y</span> <span class="o">*</span> <span class="k">self</span><span class="py">.info.stride</span> <span class="o">+</span> <span class="n">x</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">r</span> <span class="o">=</span> <span class="p">(</span><span class="k">self</span><span class="py">.color</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="k">as</span> <span class="nb">u16</span> <span class="o">*</span> <span class="n">intensity</span> <span class="k">as</span> <span class="nb">u16</span> <span class="o">/</span> <span class="mi">255</span><span class="p">)</span> <span class="k">as</span> <span class="nb">u8</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">g</span> <span class="o">=</span> <span class="p">(</span><span class="k">self</span><span class="py">.color</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">as</span> <span class="nb">u16</span> <span class="o">*</span> <span class="n">intensity</span> <span class="k">as</span> <span class="nb">u16</span> <span class="o">/</span> <span class="mi">255</span><span class="p">)</span> <span class="k">as</span> <span class="nb">u8</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">b</span> <span class="o">=</span> <span class="p">(</span><span class="k">self</span><span class="py">.color</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="k">as</span> <span class="nb">u16</span> <span class="o">*</span> <span class="n">intensity</span> <span class="k">as</span> <span class="nb">u16</span> <span class="o">/</span> <span class="mi">255</span><span class="p">)</span> <span class="k">as</span> <span class="nb">u8</span><span class="p">;</span>

        <span class="k">let</span> <span class="n">color_bytes</span> <span class="o">=</span> <span class="k">match</span> <span class="k">self</span><span class="py">.info.pixel_format</span> <span class="p">{</span>
            <span class="nn">PixelFormat</span><span class="p">::</span><span class="n">Rgb</span> <span class="k">=&gt;</span> <span class="p">[</span><span class="n">r</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span>
            <span class="nn">PixelFormat</span><span class="p">::</span><span class="n">Bgr</span> <span class="k">=&gt;</span> <span class="p">[</span><span class="n">b</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="n">r</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span>
            <span class="nn">PixelFormat</span><span class="p">::</span><span class="n">U8</span> <span class="k">=&gt;</span> <span class="p">[</span><span class="n">intensity</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span>
            <span class="n">_</span> <span class="k">=&gt;</span> <span class="p">[</span><span class="n">r</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span>
        <span class="p">};</span>

        <span class="k">let</span> <span class="n">bytes_per_pixel</span> <span class="o">=</span> <span class="k">self</span><span class="py">.info.bytes_per_pixel</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">byte_offset</span> <span class="o">=</span> <span class="n">pixel_offset</span> <span class="o">*</span> <span class="n">bytes_per_pixel</span><span class="p">;</span>
        
        <span class="k">if</span> <span class="n">byte_offset</span> <span class="o">+</span> <span class="n">bytes_per_pixel</span> <span class="o">&lt;=</span> <span class="k">self</span><span class="py">.buffer</span><span class="nf">.len</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">self</span><span class="py">.buffer</span><span class="p">[</span><span class="n">byte_offset</span><span class="o">..</span><span class="p">(</span><span class="n">byte_offset</span> <span class="o">+</span> <span class="n">bytes_per_pixel</span><span class="p">)]</span>
                <span class="nf">.copy_from_slice</span><span class="p">(</span><span class="o">&amp;</span><span class="n">color_bytes</span><span class="p">[</span><span class="o">..</span><span class="n">bytes_per_pixel</span><span class="p">]);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">draw_char()</code> で文字のラスタデータを読み込むときに、 <code class="language-plaintext highlighter-rouge">noto_sans_mono_bitmap</code> クレートから noto-sans フォントのラスタデータを渡すようにしています。結果として、FerriOS は QEMU 上に Noto Sans フォントを表示するようになります。</p>

<p><img src="../../../assets/img/post/2026-03-07-rust-os-dev-2/image-20260322221625749.webp" alt="image-20260322221625749" /></p>

<h2 id="カーネル空間を-pml4-の-256511-にマップさせる">カーネル空間を PML4 の [256]～[511] にマップさせる</h2>

<p>話が逸れてしまいましたが、bootloader v0.11 に移行したのはこれを実現するためです。</p>

<p><code class="language-plaintext highlighter-rouge">entry_point!()</code> で渡す <code class="language-plaintext highlighter-rouge">BootloaderConfig</code> を下記のように定義します。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">BOOTLOADER_CONFIG</span><span class="p">:</span> <span class="n">BootloaderConfig</span> <span class="o">=</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">config</span> <span class="o">=</span> <span class="nn">BootloaderConfig</span><span class="p">::</span><span class="nf">new_default</span><span class="p">();</span>
    <span class="n">config</span><span class="py">.mappings.physical_memory</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="nn">Mapping</span><span class="p">::</span><span class="nf">FixedAddress</span><span class="p">(</span><span class="mi">0xFFFF_A000_0000_0000</span><span class="p">));</span> <span class="c1">// index 308</span>
    <span class="n">config</span><span class="py">.mappings.kernel_base</span> <span class="o">=</span> <span class="nn">Mapping</span><span class="p">::</span><span class="nf">FixedAddress</span><span class="p">(</span><span class="mi">0xFFFF_8000_0000_0000</span><span class="p">);</span>           <span class="c1">// index 256</span>
    <span class="n">config</span><span class="py">.mappings.kernel_stack</span> <span class="o">=</span> <span class="nn">Mapping</span><span class="p">::</span><span class="nf">FixedAddress</span><span class="p">(</span><span class="mi">0xFFFF_9000_0000_0000</span><span class="p">);</span>          <span class="c1">// index 288</span>
    <span class="n">config</span><span class="py">.mappings.framebuffer</span> <span class="o">=</span> <span class="nn">Mapping</span><span class="p">::</span><span class="nf">FixedAddress</span><span class="p">(</span><span class="mi">0xFFFF_B000_0000_0000</span><span class="p">);</span>           <span class="c1">// index 324</span>
    <span class="n">config</span><span class="py">.mappings.boot_info</span> <span class="o">=</span> <span class="nn">Mapping</span><span class="p">::</span><span class="nf">FixedAddress</span><span class="p">(</span><span class="mi">0xFFFF_C000_0000_0000</span><span class="p">);</span> 
    <span class="n">config</span>
<span class="p">};</span>

<span class="nd">entry_point!</span><span class="p">(</span><span class="n">kernel_main</span><span class="p">,</span> <span class="n">config</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">BOOTLOADER_CONFIG</span><span class="p">);</span>
</code></pre></div></div>

<p>カーネルが利用する領域の PML4 エントリはいずれも 256 以降になるようにしています。</p>

<p>結果：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Starting kernel threads..done.
entry[0]: <span class="nv">current</span><span class="o">=</span>0x107f000 <span class="nv">new</span><span class="o">=</span>0x0
entry[256]: <span class="nv">current</span><span class="o">=</span>0x1047000 <span class="nv">new</span><span class="o">=</span>0x1047000
entry[273]: <span class="nv">current</span><span class="o">=</span>0x1000 <span class="nv">new</span><span class="o">=</span>0x1000
entry[288]: <span class="nv">current</span><span class="o">=</span>0x1069000 <span class="nv">new</span><span class="o">=</span>0x1069000
entry[320]: <span class="nv">current</span><span class="o">=</span>0x1088000 <span class="nv">new</span><span class="o">=</span>0x1088000
entry[352]: <span class="nv">current</span><span class="o">=</span>0x1084000 <span class="nv">new</span><span class="o">=</span>0x1084000
entry[384]: <span class="nv">current</span><span class="o">=</span>0x108e000 <span class="nv">new</span><span class="o">=</span>0x108e000
Starting the scheduler..
</code></pre></div></div>

<p>entry[0] が残っていますが、これは GDT なのでマップしなくても問題はないでしょう。というわけで、カーネル空間の [256]～[511] だけマップすればユーザプロセスが動くようになりました。</p>

<p>（これがしたいだけなのに、かなり遠回りしたような気も…）</p>

<h2 id="ユーザプログラムを動かす">ユーザプログラムを動かす</h2>

<p>まだシステムコールを実装していないので、文字を表示させたりはできませんが、とりあえず何らかのスレッドを動かしてみます。</p>

<p>ELF ファイルにも対応していないので、一旦ユーザプログラムはハードコーディングで記述します。ここでは単に RAX レジスタを初期化し、インクリメントしてループさせているだけです。</p>

<p><code class="language-plaintext highlighter-rouge">main.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// エントリポイント</span>
<span class="k">fn</span> <span class="nf">kernel_main</span><span class="p">(</span><span class="n">boot_info</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="n">BootInfo</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="o">...</span>
    <span class="c1">// ユーザプロセス作成</span>
    <span class="k">const</span> <span class="n">USER_CODE</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="nb">u8</span><span class="p">]</span> <span class="o">=</span> <span class="o">&amp;</span><span class="p">[</span>
        <span class="mi">0x48</span><span class="p">,</span> <span class="mi">0x31</span><span class="p">,</span> <span class="mi">0xC0</span><span class="p">,</span>           <span class="c1">// xor rax, rax</span>
        <span class="mi">0x48</span><span class="p">,</span> <span class="mi">0xFF</span><span class="p">,</span> <span class="mi">0xC0</span><span class="p">,</span>           <span class="c1">// inc rax</span>
        <span class="mi">0xEB</span><span class="p">,</span> <span class="mi">0xFB</span><span class="p">,</span>                 <span class="c1">// jmp -5</span>
    <span class="p">];</span>

    <span class="nn">thread</span><span class="p">::</span><span class="nn">uprocess</span><span class="p">::</span><span class="nf">create_user_process</span><span class="p">(</span><span class="n">USER_CODE</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">mapper</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">frame_allocator</span><span class="p">)</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"failed to create user process"</span><span class="p">);</span>
    
    <span class="nd">println!</span><span class="p">(</span><span class="s">"Starting the scheduler.."</span><span class="p">);</span>
    <span class="nn">scheduler</span><span class="p">::</span><span class="nf">scheduler</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>ただ、現状ユーザプロセスが動いていることをユーザプログラム側からデバッグ出力する術がないので、とりあえずタイマ割り込み発生時に、ユーザプロセスであれば RIP の実行アドレスを表示するように修正してみます。</p>

<p><code class="language-plaintext highlighter-rouge">interrupts.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// タイマ割り込みハンドラ</span>
<span class="k">extern</span> <span class="s">"x86-interrupt"</span> <span class="k">fn</span> <span class="nf">timer_interrupt_handler</span><span class="p">(</span><span class="n">stack_frame</span><span class="p">:</span> <span class="n">InterruptStackFrame</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="n">PICS</span><span class="nf">.lock</span><span class="p">()</span><span class="nf">.notify_end_of_interrupt</span><span class="p">(</span><span class="nn">InterruptIndex</span><span class="p">::</span><span class="n">Timer</span><span class="nf">.as_u8</span><span class="p">());</span>
    <span class="p">}</span>

    <span class="c1">// CS の下位2ビットが CPL（現在の特権レベル）</span>
    <span class="k">let</span> <span class="n">cpl</span> <span class="o">=</span> <span class="n">stack_frame</span><span class="py">.code_segment</span> <span class="o">&amp;</span> <span class="mi">0b11</span><span class="p">;</span>
    <span class="k">if</span> <span class="n">cpl</span> <span class="o">==</span> <span class="mi">3</span> <span class="p">{</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"Ring 3 confirmed! rip={:#x}"</span><span class="p">,</span> <span class="n">stack_frame</span><span class="py">.instruction_pointer</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="k">if</span> <span class="nn">scheduler</span><span class="p">::</span><span class="n">SCHEDULER_STARTED</span> <span class="p">{</span>
            <span class="nn">scheduler</span><span class="p">::</span><span class="nf">yield_from_context</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><img src="../../../assets/img/post/2026-03-07-rust-os-dev-2/image-20260322220019960.webp" alt="image-20260322220019960" /></p>

<p>とりあえず、なんとなく動いていてループできてそうだなぁと言うところまで確認できました。</p>

<h1 id="おわりに">おわりに</h1>

<p>今回はここまで。ユーザプログラムを動かして、なんとなく動いていそうな様子を確認しました。思いがけぬ bootloader の最新版へのアップデートで手間取りましたが、frame buffer 実装によってよりグラフィック出力の自由度が上がりました。</p>

<p>ただ、まだシステムコールを実装していないので、ユーザプログラムのデバッグができません…。次回はシステムコールを実装して、ユーザプログラムから文字出力できるようにしたいと思います。</p>]]></content><author><name></name></author><category term="Rust" /><category term="OS自作入門" /><category term="FerriOS" /><summary type="html"><![CDATA[自作 OS「FerriOS」の開発日記、第二回です。前回はスケジューラとカーネルスレッドを実装し、カーネルモードでスレッドが動いている様子を確認できました。今回はユーザプロセスを実装し、カーネルスレッドと同じようにスケジューラで順次実行できる状態にしていきたいと思います。]]></summary></entry><entry><title type="html">Rustで自作OS #1 スケジューラとカーネルスレッドの実装</title><link href="https://blog.yotio.jp/2026/02/23/rust-os-dev-1.html" rel="alternate" type="text/html" title="Rustで自作OS #1 スケジューラとカーネルスレッドの実装" /><published>2026-02-23T00:00:00+09:00</published><updated>2026-02-23T00:00:00+09:00</updated><id>https://blog.yotio.jp/2026/02/23/rust-os-dev-1</id><content type="html" xml:base="https://blog.yotio.jp/2026/02/23/rust-os-dev-1.html"><![CDATA[<p><img src="../../../assets/img/post/2026-02-14-rust-os-dev-1/image-20260218030340264.webp" alt="image-20260218030340264" /></p>

<p>どこまで続くかわからないけど、熱意が冷めないうちに…</p>

<p>Writing an OS in Rust を一通り写経したので、そこから先を実装する個人的なプロジェクトを始めていきたいと思います。</p>

<p>動機は完全なる趣味・興味です。Writing an OS in Rust は Async/Await の章までしか書かれていないので、そこから先、できれば xv6 と同等以上のレベルにまでは仕上げていきたいと思っています。</p>

<!--more-->

<h2 id="rustでos開発シリーズ">RustでOS開発シリーズ</h2>

<ul>
  <li>#1 <a href="../../../2026/02/23/rust-os-dev-1.html">スケジューラとカーネルスレッドの実装</a>（今回）</li>
  <li>#2 <a href="../../../2026/03/22/rust-os-dev-2.html">ユーザプロセスを実装する</a></li>
  <li>#3 <a href="../../../2026/05/05/rust-os-dev-3.html">ELFとシステムコールを実装する</a></li>
  <li>#4 <a href="../../../2026/05/31/rust-os-dev-4.html">exit()/wait()の実装など</a></li>
</ul>

<h1 id="命名">命名</h1>

<p>せっかくなので自作 OS に名前をということで、<strong>「FerriOS」（フェリオス）</strong>と命名しました。</p>

<p>Rust の蟹である Ferris に寄せています。また、Rust が「錆」という意味なので、それに関連して「鉄」を表す Ferrous に近い発音を選びました。</p>

<h1 id="リポジトリ">リポジトリ</h1>

<p>リポジトリはこちら。適宜更新していきます。</p>

<ul>
  <li><a href="https://github.com/yotiosoft/FerriOS">https://github.com/yotiosoft/FerriOS</a></li>
</ul>

<h1 id="今後のロードマップ">今後のロードマップ</h1>

<p>今のところ、下記の手順で進めていきたいと思っています。</p>

<ul>
  <li>カーネルスレッド実装（今回）
    <ul>
      <li>コンテキスト実装</li>
      <li>スレッドテーブル実装</li>
      <li>コンテキストスイッチ実装</li>
      <li>スケジューラ実装</li>
    </ul>
  </li>
  <li>ユーザスレッド実装
    <ul>
      <li>ユーザモード対</li>
      <li>ユーザスタック実装</li>
    </ul>
  </li>
  <li>システムコール</li>
  <li>マルチコア対応</li>
  <li>…</li>
</ul>

<h1 id="カーネルスレッドの実装">カーネルスレッドの実装</h1>

<p>今回の本題です。</p>

<p>Writing an OS in Rust の12章「<a href="https://os.phil-opp.com/ja/async-await/">Async/Await</a>」では協調的マルチタスクを実装しました。これは各タスクが必要な処理を終えたら自発的に CPU を手放す（yield）マルチタスク処理で、複数のスレッド間で CPU を共有している場合、次のタスクに CPU が移譲されます。しかし、協調的という言葉にも現れている通り、これは各タスクが適度に CPU を手放すことを前提としたアプローチであり、中には無制限に CPU を使いたがるタスクもあるかもしれません。また、ハードウェアとのやり取りや他の外的要因によって想定以上に実行時間が長くなってしまったり、バグで無限ループやブロッキングに陥ってしまった場合、CPU はそのタスクによって独占され続けるので、他のタスクは永遠に処理を続けることができなくなります。</p>

<p>そこで今回実装したいのは非協調的マルチタスクです。各タスクに一定時間ずつ CPU を割り当てていき、時間になったら強制的に CPU を取り上げて別のタスクに CPU を割り当てるアプローチです。これを実装するには、下記の2つが必要になります。</p>

<ul>
  <li>コンテキスト</li>
  <li>スケジューラ</li>
</ul>

<p>コンテキストは、プログラムが中断された時点の各タスクの情報（汎用レジスタ、プログラムカウンタ、スタックポインタなど）を次回再開するときのために保存しておくための領域です。</p>

<p>スケジューラは、一定時間おきに各タスクに順々に処理を割り当てていくためのアルゴリズムです。</p>

<h2 id="タイマ割り込みを利用する">タイマ割り込みを利用する</h2>

<p>タイマ割り込みを利用すると、CPU に一定時間おきに割り込みを発生させてもらうことができます。スケジューラはこのタイマ割り込みを利用して、一定時間おきにタスクに順に CPU を割り当てていきます。タイマ割り込みが発生したときの処理を実装しているプログラムを「割り込みハンドラ」と呼びます。</p>

<p>Writing an OS in Rust では、既にタイマ割り込みハンドラ自体は実装されています。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// タイマ割り込みハンドラ</span>
<span class="k">extern</span> <span class="s">"x86-interrupt"</span> <span class="k">fn</span> <span class="nf">timer_interrupt_handler</span><span class="p">(</span><span class="n">_stack_frame</span><span class="p">:</span> <span class="n">InterruptStackFrame</span><span class="p">)</span> <span class="p">{</span>
    <span class="nd">print!</span><span class="p">(</span><span class="s">"."</span><span class="p">);</span>

    <span class="c1">// End of interrupt (割り込み終了)</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="n">PICS</span><span class="nf">.lock</span><span class="p">()</span><span class="nf">.notify_end_of_interrupt</span><span class="p">(</span><span class="nn">InterruptIndex</span><span class="p">::</span><span class="n">Timer</span><span class="nf">.as_u8</span><span class="p">());</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>現時点では単に “.” をタイマ割り込みが発生するたびに表示させているだけです。結果として、起動させたときに ↓ のように一定時間おきに延々と “.” が表示され続けます。</p>

<p><img src="../../../assets/img/post/2026-02-14-writing-an-os/image-20260214210407327.webp" alt="image-20260214210407327" /></p>

<p>このタイマ割り込みハンドラに、後ほど実装するスケジューラを追加していきます。</p>

<h2 id="コンテキストの実装">コンテキストの実装</h2>

<p>では、タスクの状態を保存しておくコンテキストを実装していきます。</p>

<p>まずは <code class="language-plaintext highlighter-rouge">src/</code> の下に <code class="language-plaintext highlighter-rouge">thread/mod.rs</code> と <code class="language-plaintext highlighter-rouge">thread/context.rs</code> を作成します。</p>

<p><code class="language-plaintext highlighter-rouge">thread/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">mod</span> <span class="n">context</span><span class="p">;</span>
</code></pre></div></div>

<p>また、<code class="language-plaintext highlighter-rouge">lib.rs</code> から <code class="language-plaintext highlighter-rouge">thread</code> モジュールを参照できるようにしておきます。</p>

<p><code class="language-plaintext highlighter-rouge">lib.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">mod</span> <span class="n">thread</span><span class="p">;</span>
</code></pre></div></div>

<p>では、コンテキストを実装していきましょう。</p>

<p><code class="language-plaintext highlighter-rouge">thread/context.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// コンテキスト構造体</span>
<span class="cd">/// コンテキストスイッチ時の保存/復元に利用</span>
<span class="nd">#[derive(Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy)]</span>
<span class="nd">#[repr(C)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">Context</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">r15</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">r14</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">r13</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">r12</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">rbx</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">rbp</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">rsp</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">rip</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Context</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="n">Context</span> <span class="p">{</span>
            <span class="n">r15</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
            <span class="n">r14</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
            <span class="n">r13</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
            <span class="n">r12</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
            <span class="n">rbx</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
            <span class="n">rbp</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
            <span class="n">rsp</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
            <span class="n">rip</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>x86_64 で用いる汎用レジスタ r12, r13, r14, r15、rbx、rbp と、スタックポインタである rsp、そして return address を保持する rip を保存しておくための変数を用意しました。64 bit レジスタなので当然、<code class="language-plaintext highlighter-rouge">u64</code> です。rax、rcx、rdx、rsi、rdi、r8、r9、r10、r11 は呼び出し元が使用するレジスタなので退避対象外としています。</p>

<h2 id="スレッド構造体の実装">スレッド構造体の実装</h2>

<p><code class="language-plaintext highlighter-rouge">thread/mod.rs</code> にスレッド構造体と関連する構造体を実装していきます。</p>

<p>まずはスレッドの状態を示す enum から：</p>

<p><code class="language-plaintext highlighter-rouge">thread/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[derive(Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy,</span> <span class="nd">PartialEq,</span> <span class="nd">Eq)]</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">ThreadState</span> <span class="p">{</span>
    <span class="n">Unused</span><span class="p">,</span>
    <span class="n">Embryo</span><span class="p">,</span>
    <span class="n">Sleeping</span><span class="p">,</span>
    <span class="n">Runnable</span><span class="p">,</span>
    <span class="n">Running</span><span class="p">,</span>
    <span class="n">Zombie</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>次に、スレッド構造体（PCB）を実装していきます。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">context</span><span class="p">::</span><span class="n">Context</span><span class="p">;</span>

<span class="cd">/// Thread Control Block</span>
<span class="nd">#[derive(Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">Thread</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">tid</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>             <span class="c1">// Thread ID</span>
    <span class="k">pub</span> <span class="n">state</span><span class="p">:</span> <span class="n">ThreadState</span><span class="p">,</span>     <span class="c1">// スレッドの状態</span>
    <span class="k">pub</span> <span class="n">context</span><span class="p">:</span> <span class="n">Context</span><span class="p">,</span>       <span class="c1">// スレッドのコンテキスト</span>
    <span class="k">pub</span> <span class="n">kstack</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>            <span class="c1">// このスレッド用のカーネルスタック</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Thread</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="n">Thread</span> <span class="p">{</span>
            <span class="n">tid</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
            <span class="n">state</span><span class="p">:</span> <span class="nn">ThreadState</span><span class="p">::</span><span class="n">Unused</span><span class="p">,</span>
            <span class="n">context</span><span class="p">:</span> <span class="nn">Context</span><span class="p">::</span><span class="nf">new</span><span class="p">(),</span>
            <span class="n">kstack</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>とりあえず最低限カーネルスレッドを実行するのに必要な PID、ThreadState、コンテキスト、カーネルスタックだけ用意しておきました。</p>

<h2 id="スレッドテーブルの実装">スレッドテーブルの実装</h2>

<p>現在存在するスレッドを管理するためのスレッドテーブルを作成しておきます。グローバル変数として扱う必要があるので、初期化処理が1度だけ行われるよう <code class="language-plaintext highlighter-rouge">lazy_static</code> を使いましょう。</p>

<p><code class="language-plaintext highlighter-rouge">thread/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">const</span> <span class="n">NPROC</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">64</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">spin</span><span class="p">::</span><span class="n">Mutex</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">lazy_static</span><span class="p">::</span><span class="n">lazy_static</span><span class="p">;</span>

<span class="nd">lazy_static!</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">static</span> <span class="k">ref</span> <span class="n">PROCESS_TABLE</span><span class="p">:</span> <span class="n">Mutex</span><span class="o">&lt;</span><span class="p">[</span><span class="n">Thread</span><span class="p">;</span> <span class="n">NPROC</span><span class="p">]</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">{</span>
        <span class="nn">Mutex</span><span class="p">::</span><span class="nf">new</span><span class="p">([</span><span class="nn">Thread</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span> <span class="n">NPROC</span><span class="p">])</span>
    <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<p>スレッドの最大数は xv6 と同じ 64 としておきます。<code class="language-plaintext highlighter-rouge">lazy_static</code> を利用することで、最初のアクセス時にサイズ64のスレッドテーブルとして <code class="language-plaintext highlighter-rouge">Thread</code> 型のグローバル配列が初期化されます。</p>

<h2 id="コンテキストスイッチの実装">コンテキストスイッチの実装</h2>

<p>CPU が処理するタスクを切り替えるためのコンテキストスイッチを実装します。</p>

<p><code class="language-plaintext highlighter-rouge">thread/context.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">core</span><span class="p">::</span><span class="nn">arch</span><span class="p">::</span><span class="n">global_asm</span><span class="p">;</span>

<span class="k">unsafe</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">switch_context</span><span class="p">(</span><span class="n">old</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="n">Context</span><span class="p">,</span> <span class="n">new</span><span class="p">:</span> <span class="o">*</span><span class="k">const</span> <span class="n">Context</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// コンテキストスイッチ</span>
<span class="nd">global_asm!</span><span class="p">(</span>
<span class="s">r#"
.globl switch_context
switch_context:
    # 現在のコンテキストを保存
    mov [rdi + 0], r15
    mov [rdi + 8], r14
    mov [rdi + 16], r13
    mov [rdi + 24], r12
    mov [rdi + 32], rbx
    mov [rdi + 40], rbp
    
    # RSP を保存
    lea rax, [rsp + 8]
    mov [rdi + 48], rax
    
    # RIP を保存
    mov rax, [rsp]
    mov [rdi + 56], rax
    
    # RFLAGS を保存
    pushfq
    pop rax
    mov [rdi + 64], rax
    
    # 新しいコンテキストを復元
    mov r15, [rsi + 0]
    mov r14, [rsi + 8]
    mov r13, [rsi + 16]
    mov r12, [rsi + 24]
    mov rbx, [rsi + 32]
    mov rbp, [rsi + 40]
    mov rsp, [rsi + 48]
    
    # RFLAGS を復元
    mov rax, [rsi + 64]
    push rax
    popfq
    
    # 新しいスレッドへ jump
    push qword ptr [rsi + 56]
    ret
"#</span>
<span class="p">);</span>
</code></pre></div></div>

<p>中身はアセンブリの処理です。<code class="language-plaintext highlighter-rouge">old</code> コンテキストから <code class="language-plaintext highlighter-rouge">new</code> コンテキストへの CPU の各レジスタを切り替えていきます。</p>

<p>引数の順序的に、<code class="language-plaintext highlighter-rouge">old</code> の先頭アドレスは <code class="language-plaintext highlighter-rouge">rdi</code> レジスタに、<code class="language-plaintext highlighter-rouge">new</code> の先頭アドレスは <code class="language-plaintext highlighter-rouge">rsi</code> レジスタに記録されます。ですので、このレジスタの値からアドレスを計算していき、まずは各レジスタの値を <code class="language-plaintext highlighter-rouge">old</code> の構造体上に記録し、その後 <code class="language-plaintext highlighter-rouge">new</code> 構造体の値を各レジスタに復元していきます。</p>

<p>最後に、<code class="language-plaintext highlighter-rouge">new</code> 構造体に記録されている <code class="language-plaintext highlighter-rouge">rip</code> レジスタが指すプログラムカウンタに <code class="language-plaintext highlighter-rouge">ret</code> 命令でジャンプします。</p>

<h2 id="cpu-固有の構造体の実装">CPU 固有の構造体の実装</h2>

<p>後のスケジューラ実装に向けて、CPU ごとに固有の変数などを保持する <code class="language-plaintext highlighter-rouge">Cpu</code> 構造体を実装しておきます。</p>

<p>現状、ここには cpuid と、CPU 固有のスケジューラ用のコンテキスト構造体、そして CPU が現在実行しているスレッド ID を保持する <code class="language-plaintext highlighter-rouge">current_tid</code> を用意しておきます。</p>

<p><code class="language-plaintext highlighter-rouge">cpu.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">thread</span><span class="p">::</span><span class="n">context</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">struct</span> <span class="n">Cpu</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">id</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>                      <span class="c1">// CPU ID</span>
    <span class="k">pub</span> <span class="n">scheduler</span><span class="p">:</span> <span class="nn">context</span><span class="p">::</span><span class="n">Context</span><span class="p">,</span>    <span class="c1">// スケジューラ用コンテキスト</span>
    <span class="k">pub</span> <span class="n">current_tid</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="nb">usize</span><span class="o">&gt;</span><span class="p">,</span>     <span class="c1">// 現在実行中のスレッド ID</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Cpu</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">cpu_id</span><span class="p">:</span> <span class="nb">usize</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="n">Cpu</span> <span class="p">{</span>
            <span class="n">id</span><span class="p">:</span> <span class="n">cpu_id</span><span class="p">,</span>
            <span class="n">scheduler</span><span class="p">:</span> <span class="nn">context</span><span class="p">::</span><span class="nn">Context</span><span class="p">::</span><span class="nf">new</span><span class="p">(),</span>
            <span class="n">current_tid</span><span class="p">:</span> <span class="nb">None</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>現在は CPU はシングルコアなので考慮する必要はありませんが、将来的に FerriOS をマルチコアに対応させるとき、この <code class="language-plaintext highlighter-rouge">Cpu</code> 構造体はコアごとに別々に保持するよう修正する必要があります。</p>

<h2 id="スケジューラの実装">スケジューラの実装</h2>

<p>シンプルなラウンドロビン方式のスケジューラを実装します。スケジューラの実装は xv6 を参考にしました。</p>

<p><code class="language-plaintext highlighter-rouge">thread/scheduler.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="k">super</span><span class="p">::{</span> <span class="n">ThreadState</span><span class="p">,</span> <span class="n">PROCESS_TABLE</span><span class="p">,</span> <span class="n">NPROC</span> <span class="p">};</span>
<span class="k">use</span> <span class="k">super</span><span class="p">::</span><span class="nn">context</span><span class="p">::{</span> <span class="n">Context</span><span class="p">,</span> <span class="n">switch_context</span> <span class="p">};</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="n">cpu</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">lazy_static</span><span class="p">::</span><span class="n">lazy_static</span><span class="p">;</span>

<span class="k">static</span> <span class="k">mut</span> <span class="n">CURRENT_PID</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">static</span> <span class="k">mut</span> <span class="n">SCHEDULER_STARTED</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>

<span class="nd">lazy_static!</span> <span class="p">{</span>
    <span class="k">static</span> <span class="k">ref</span> <span class="n">CPU</span><span class="p">:</span> <span class="nn">spin</span><span class="p">::</span><span class="n">Mutex</span><span class="o">&lt;</span><span class="nn">cpu</span><span class="p">::</span><span class="n">Cpu</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nn">spin</span><span class="p">::</span><span class="nn">Mutex</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">cpu</span><span class="p">::</span><span class="nn">Cpu</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">));</span>
<span class="p">}</span>

<span class="cd">/// スケジューラ</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">scheduler</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">SCHEDULER_STARTED</span> <span class="p">{</span>
            <span class="nd">panic!</span><span class="p">(</span><span class="s">"Scheduler already started"</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="n">SCHEDULER_STARTED</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">loop</span> <span class="p">{</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">table</span> <span class="o">=</span> <span class="n">PROCESS_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">cpu</span> <span class="o">=</span> <span class="n">CPU</span><span class="nf">.lock</span><span class="p">();</span>
        
        <span class="c1">// 次に実行するスレッドの決定</span>
        <span class="k">let</span> <span class="n">next_tid</span> <span class="o">=</span> <span class="p">{</span>
            <span class="nf">find_next_runnable_thread</span><span class="p">(</span><span class="o">&amp;</span><span class="n">table</span><span class="p">,</span> <span class="n">cpu</span><span class="py">.current_tid</span><span class="p">)</span>
        <span class="p">};</span>

        <span class="k">match</span> <span class="n">next_tid</span> <span class="p">{</span>
            <span class="nb">None</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="nn">x86_64</span><span class="p">::</span><span class="nn">instructions</span><span class="p">::</span><span class="nn">interrupts</span><span class="p">::</span><span class="nf">enable_and_hlt</span><span class="p">();</span>
                <span class="nf">drop</span><span class="p">(</span><span class="n">cpu</span><span class="p">);</span>
                <span class="nf">drop</span><span class="p">(</span><span class="n">table</span><span class="p">);</span>
                <span class="k">continue</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="nf">Some</span><span class="p">(</span><span class="n">next_tid</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="k">let</span> <span class="p">(</span><span class="n">old_context</span><span class="p">,</span> <span class="n">new_context</span><span class="p">)</span> <span class="o">=</span> <span class="p">{</span>
                    <span class="c1">// スレッド状態を更新</span>
                    <span class="n">table</span><span class="p">[</span><span class="n">next_tid</span><span class="p">]</span><span class="py">.state</span> <span class="o">=</span> <span class="nn">ThreadState</span><span class="p">::</span><span class="n">Running</span><span class="p">;</span>
                    <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">current_tid</span><span class="p">)</span> <span class="o">=</span> <span class="n">cpu</span><span class="py">.current_tid</span> <span class="p">{</span>
                        <span class="k">if</span> <span class="n">table</span><span class="p">[</span><span class="n">current_tid</span><span class="p">]</span><span class="py">.state</span> <span class="o">==</span> <span class="nn">ThreadState</span><span class="p">::</span><span class="n">Running</span> <span class="p">{</span>
                            <span class="n">table</span><span class="p">[</span><span class="n">current_tid</span><span class="p">]</span><span class="py">.state</span> <span class="o">=</span> <span class="nn">ThreadState</span><span class="p">::</span><span class="n">Runnable</span><span class="p">;</span>
                        <span class="p">}</span>
                    <span class="p">}</span>
                    
                    <span class="c1">// CPU で実行中のスレッド ID を更新</span>
                    <span class="n">cpu</span><span class="py">.current_tid</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="n">next_tid</span><span class="p">);</span>
                    
                    <span class="k">let</span> <span class="n">old_context</span> <span class="o">=</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">cpu</span><span class="py">.scheduler</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="n">Context</span><span class="p">;</span>
                    <span class="k">let</span> <span class="n">new_context</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">table</span><span class="p">[</span><span class="n">next_tid</span><span class="p">]</span><span class="py">.context</span> <span class="k">as</span> <span class="o">*</span><span class="k">const</span> <span class="n">Context</span><span class="p">;</span>

                    <span class="nf">drop</span><span class="p">(</span><span class="n">cpu</span><span class="p">);</span>
                    <span class="nf">drop</span><span class="p">(</span><span class="n">table</span><span class="p">);</span>

                    <span class="p">(</span><span class="n">old_context</span><span class="p">,</span> <span class="n">new_context</span><span class="p">)</span>
                <span class="p">};</span>

                <span class="k">unsafe</span> <span class="p">{</span>
                    <span class="nn">x86_64</span><span class="p">::</span><span class="nn">instructions</span><span class="p">::</span><span class="nn">interrupts</span><span class="p">::</span><span class="nf">enable</span><span class="p">();</span>
                    <span class="c1">//crate::println!("switch");</span>
                    <span class="nf">switch_context</span><span class="p">(</span><span class="n">old_context</span><span class="p">,</span> <span class="n">new_context</span><span class="p">);</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">find_next_runnable_thread</span><span class="p">(</span><span class="n">table</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="n">Thread</span><span class="p">;</span> <span class="n">NPROC</span><span class="p">],</span> <span class="n">current_tid</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="nb">usize</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="nb">usize</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">current_tid</span> <span class="o">=</span> <span class="n">current_tid</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">1</span><span class="o">..</span><span class="n">NPROC</span><span class="o">+</span><span class="mi">1</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">tid</span> <span class="o">=</span> <span class="p">(</span><span class="n">current_tid</span> <span class="o">+</span> <span class="n">i</span><span class="p">)</span> <span class="o">%</span> <span class="n">NPROC</span><span class="p">;</span>
        <span class="k">if</span> <span class="n">table</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span><span class="py">.state</span> <span class="o">==</span> <span class="nn">ThreadState</span><span class="p">::</span><span class="n">Runnable</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nf">Some</span><span class="p">(</span><span class="n">tid</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="nb">None</span>
<span class="p">}</span>
</code></pre></div></div>

<p>ラウンドロビン方式のスケジューラは単に、スレッドテーブルを走査していって実行可能 (Runnable) なスレッドを見つけ、それを順番に実行していく方式です。<code class="language-plaintext highlighter-rouge">find_next_runnable_thread()</code> で現在 CPU が実行しているスレッドの PID を取得し（<code class="language-plaintext highlighter-rouge">cpu.current_tid</code>）、PID + 1 から順に次に実行するスレッドを見つけていきます。Runnable なスレッドが見つかったらそれを次の実行スレッドとして選びます。その後、次に実行するスレッドの状態を Running に変更し、 <code class="language-plaintext highlighter-rouge">cpu.current_tid</code> を更新して、<code class="language-plaintext highlighter-rouge">switch_context()</code> でコンテキストスイッチします。</p>

<p>ここでコンテキストスイッチする元となるコンテキストは、前回実行していたスレッドではなくスケジューラです。<code class="language-plaintext highlighter-rouge">cpu.scheduler</code> にスケジューラのコンテキストを保存しておき、次のコンテキストスイッチが起きたときに再びこのスケジューラに処理が戻ってくるようにしています。</p>

<p>スケジューラ→スレッドに切り替えるコンテキストスイッチ処理を実装したということは、逆にスレッド→スケジューラに戻ってくるためのコンテキストスイッチ処理の実装も必要です。そこで下記の <code class="language-plaintext highlighter-rouge">yield_from_context()</code> を <code class="language-plaintext highlighter-rouge">thread/scheduler.rs</code> に実装します。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">yield_from_context</span><span class="p">()</span> <span class="p">{</span>
    <span class="nn">x86_64</span><span class="p">::</span><span class="nn">instructions</span><span class="p">::</span><span class="nn">interrupts</span><span class="p">::</span><span class="nf">disable</span><span class="p">();</span>

    <span class="k">let</span> <span class="k">mut</span> <span class="n">table</span> <span class="o">=</span> <span class="n">PROCESS_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">cpu</span> <span class="o">=</span> <span class="n">CPU</span><span class="nf">.lock</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">current_tid</span> <span class="o">=</span> <span class="n">cpu</span><span class="py">.current_tid</span><span class="p">;</span>
    <span class="k">if</span> <span class="n">current_tid</span><span class="nf">.is_none</span><span class="p">()</span> <span class="p">{</span>
        <span class="nn">x86_64</span><span class="p">::</span><span class="nn">instructions</span><span class="p">::</span><span class="nn">interrupts</span><span class="p">::</span><span class="nf">enable</span><span class="p">();</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">let</span> <span class="n">current_tid</span> <span class="o">=</span> <span class="n">current_tid</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="k">if</span> <span class="n">table</span><span class="p">[</span><span class="n">current_tid</span><span class="p">]</span><span class="py">.state</span> <span class="o">!=</span> <span class="nn">ThreadState</span><span class="p">::</span><span class="n">Running</span> <span class="p">{</span>
        <span class="nd">panic!</span><span class="p">(</span><span class="s">"CPU has current_tid but the thread is not Running"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">let</span> <span class="p">(</span><span class="n">old_context</span><span class="p">,</span> <span class="n">new_context</span><span class="p">)</span> <span class="o">=</span> <span class="p">{</span>
        <span class="c1">// Runnable に変更</span>
        <span class="n">table</span><span class="p">[</span><span class="n">current_tid</span><span class="p">]</span><span class="py">.state</span> <span class="o">=</span> <span class="nn">ThreadState</span><span class="p">::</span><span class="n">Runnable</span><span class="p">;</span>

        <span class="c1">// スケジューラへコンテキストスイッチ</span>
        <span class="k">let</span> <span class="n">old_context</span> <span class="o">=</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">table</span><span class="p">[</span><span class="n">current_tid</span><span class="p">]</span><span class="py">.context</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="n">Context</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">new_context</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">cpu</span><span class="py">.scheduler</span> <span class="k">as</span> <span class="o">*</span><span class="k">const</span> <span class="n">Context</span><span class="p">;</span>

        <span class="nf">drop</span><span class="p">(</span><span class="n">cpu</span><span class="p">);</span>
        <span class="nf">drop</span><span class="p">(</span><span class="n">table</span><span class="p">);</span>

        <span class="p">(</span><span class="n">old_context</span><span class="p">,</span> <span class="n">new_context</span><span class="p">)</span>
    <span class="p">};</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="nn">x86_64</span><span class="p">::</span><span class="nn">instructions</span><span class="p">::</span><span class="nn">interrupts</span><span class="p">::</span><span class="nf">enable</span><span class="p">();</span>
        <span class="nf">switch_context</span><span class="p">(</span><span class="n">old_context</span><span class="p">,</span> <span class="n">new_context</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>この <code class="language-plaintext highlighter-rouge">yield_from_context()</code> はタイマ割り込みハンドラから一定時間おきに呼び出され、実行中のスレッドから CPU 固有のスケジューラに処理を戻します。また同時に、実行していたスレッドを Runnable 状態に戻します。ここで <code class="language-plaintext highlighter-rouge">switch_context()</code> が実行されると、先程の <code class="language-plaintext highlighter-rouge">scheduler()</code> 内の <code class="language-plaintext highlighter-rouge">switch_context()</code> の直後に CPU の処理が戻ってくるわけです。</p>

<p>最後に、<code class="language-plaintext highlighter-rouge">thread/mod.rs</code> で <code class="language-plaintext highlighter-rouge">thread</code> から <code class="language-plaintext highlighter-rouge">scheduler</code> モジュールを参照できるようにしておきます。</p>

<p><code class="language-plaintext highlighter-rouge">thread/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">mod</span> <span class="n">scheduler</span><span class="p">;</span>
</code></pre></div></div>

<h2 id="タイマ割り込みハンドラからスケジューラを呼び出す">タイマ割り込みハンドラからスケジューラを呼び出す</h2>

<p>スケジューラの実装まで完了しましたので、タイマ割り込みハンドラから <code class="language-plaintext highlighter-rouge">yield_from_context()</code> を呼び出すように実装します。これにより、各スレッドは一定時間おきにスケジューラに遷移し、スケジューラを経て別のスレッドに CPU を譲るようになります。</p>

<p><code class="language-plaintext highlighter-rouge">interrupts.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">thread</span><span class="p">::</span><span class="n">scheduler</span><span class="p">;</span>

<span class="cd">/// タイマ割り込みハンドラ</span>
<span class="k">extern</span> <span class="s">"x86-interrupt"</span> <span class="k">fn</span> <span class="nf">timer_interrupt_handler</span><span class="p">(</span><span class="n">_stack_frame</span><span class="p">:</span> <span class="n">InterruptStackFrame</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="n">PICS</span><span class="nf">.lock</span><span class="p">()</span><span class="nf">.notify_end_of_interrupt</span><span class="p">(</span><span class="nn">InterruptIndex</span><span class="p">::</span><span class="n">Timer</span><span class="nf">.as_u8</span><span class="p">());</span>
    <span class="p">}</span>

    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">crate</span><span class="p">::</span><span class="nn">thread</span><span class="p">::</span><span class="nn">scheduler</span><span class="p">::</span><span class="n">SCHEDULER_STARTED</span> <span class="p">{</span>
            <span class="nn">scheduler</span><span class="p">::</span><span class="nf">yield_from_context</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">notify_end_of_interrupt()</code> は割り込みハンドラに割り込み完了 (EOI: End Of Interrupt) を伝えるための処理です。PIC は EOI を受け取るまで、タイマ割り込みハンドラと同等以下の優先度の割り込みを中断します。ここで yield より先に EOI を送信しているのは、yield に遷移するとコンテキストスイッチが行われるため、ここに戻って来る保証がないためです。</p>

<h2 id="main-関数からもスケジューラを呼び出す">main 関数からもスケジューラを呼び出す</h2>

<p>初回のスケジューラ呼び出しは <code class="language-plaintext highlighter-rouge">kernel_main()</code> から呼び出します。以降、このスケジューラが延々とループし続けます。</p>

<p><code class="language-plaintext highlighter-rouge">main.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// エントリポイント</span>
<span class="k">fn</span> <span class="nf">kernel_main</span><span class="p">(</span><span class="n">boot_info</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="n">BootInfo</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="o">...</span>
    <span class="nn">thread</span><span class="p">::</span><span class="nn">scheduler</span><span class="p">::</span><span class="nf">scheduler</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="カーネルスレッドを作成してみる">カーネルスレッドを作成してみる</h2>

<p>現状はまだユーザモードを実装していませんので、ひとまずカーネルで動作するスレッドを生成して、動くかどうかを試してみましょう。</p>

<p>まずはカーネルスレッド作成用のメソッドを用意しておきます。</p>

<p><code class="language-plaintext highlighter-rouge">thread/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">STACK_SIZE</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">4096</span> <span class="o">*</span> <span class="mi">4</span><span class="p">;</span>

<span class="cd">/// カーネルスレッド作成</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">create_kernel_thread</span><span class="p">(</span><span class="n">entry</span><span class="p">:</span> <span class="k">fn</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="o">!</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// スレッド ID を確保</span>
    <span class="k">let</span> <span class="n">tid</span> <span class="o">=</span> <span class="nf">next_tid</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"Thread table is full"</span><span class="p">);</span>

    <span class="c1">// スタックを作成</span>
    <span class="k">let</span> <span class="n">stack</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">layout</span> <span class="o">=</span> <span class="nn">alloc</span><span class="p">::</span><span class="nn">alloc</span><span class="p">::</span><span class="nn">Layout</span><span class="p">::</span><span class="nf">from_size_align</span><span class="p">(</span><span class="n">STACK_SIZE</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
        <span class="nn">alloc</span><span class="p">::</span><span class="nn">alloc</span><span class="p">::</span><span class="nf">alloc</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="p">};</span>
    <span class="k">let</span> <span class="n">stack_top</span> <span class="o">=</span> <span class="n">stack</span> <span class="k">as</span> <span class="nb">u64</span> <span class="o">+</span> <span class="n">STACK_SIZE</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">;</span>

    <span class="k">let</span> <span class="k">mut</span> <span class="n">table</span> <span class="o">=</span> <span class="n">PROCESS_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="n">table</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span><span class="py">.tid</span> <span class="o">=</span> <span class="n">tid</span><span class="p">;</span>
    <span class="n">table</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span><span class="py">.state</span> <span class="o">=</span> <span class="nn">ThreadState</span><span class="p">::</span><span class="n">Runnable</span><span class="p">;</span>
    <span class="n">table</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span><span class="py">.kstack</span> <span class="o">=</span> <span class="n">stack_top</span><span class="p">;</span>

    <span class="c1">// コンテキストを初期化する</span>
    <span class="n">table</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span><span class="py">.context.rsp</span> <span class="o">=</span> <span class="n">stack_top</span><span class="p">;</span>
    <span class="n">table</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span><span class="py">.context.rip</span> <span class="o">=</span> <span class="n">entry</span> <span class="k">as</span> <span class="nb">u64</span><span class="p">;</span>
    <span class="n">table</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span><span class="py">.context.rflags</span> <span class="o">=</span> <span class="mi">0x200</span><span class="p">;</span>  <span class="c1">// IF (Interrupt Flag) を有効化</span>
<span class="p">}</span>

<span class="cd">/// スレッド ID 決定</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">next_tid</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="nb">usize</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">table</span> <span class="o">=</span> <span class="n">PROCESS_TABLE</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="n">NPROC</span><span class="o">-</span><span class="mi">1</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">table</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="py">.state</span> <span class="o">==</span> <span class="nn">ThreadState</span><span class="p">::</span><span class="n">Unused</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nf">Some</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="nb">None</span>
<span class="p">}</span>
</code></pre></div></div>

<p>ここでは 4KB 分のスタックを用意しておき、その先頭アドレスを新しく生成するスレッドのコンテキストの <code class="language-plaintext highlighter-rouge">rsp</code> レジスタにセットしておきます。スレッドの状態は Runnable としておきます。また、割り込みを有効化するために <code class="language-plaintext highlighter-rouge">rflags</code> には <code class="language-plaintext highlighter-rouge">0x200</code> （IF ビットを立てる）を設定しておきます。</p>

<p>スレッド ID は空いているものを <code class="language-plaintext highlighter-rouge">next_tid()</code> で確保します。もし空きが無い場合、Thread Table は満杯ですので、panic で終了します。</p>

<p>これでいよいよ準備完了です。<code class="language-plaintext highlighter-rouge">main.rs</code> に、カーネルスレッドで実行するプログラムを書いていきましょう。</p>

<p><code class="language-plaintext highlighter-rouge">main.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// カーネルスレッド</span>
<span class="k">fn</span> <span class="nf">kernel_thread_0</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">loop</span> <span class="p">{</span>
        <span class="c1">// 割り込みが有効か確認</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"Thread 0 running: {}"</span><span class="p">,</span> <span class="n">count</span><span class="p">);</span>
        <span class="n">count</span> <span class="o">=</span> <span class="n">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
        
        <span class="k">for</span> <span class="n">_</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="mi">1000000</span> <span class="p">{</span>
            <span class="k">unsafe</span> <span class="p">{</span> <span class="nn">core</span><span class="p">::</span><span class="nn">arch</span><span class="p">::</span><span class="nd">asm!</span><span class="p">(</span><span class="s">"nop"</span><span class="p">);</span> <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="k">fn</span> <span class="nf">kernel_thread_1</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">loop</span> <span class="p">{</span>
        <span class="c1">// 割り込みが有効か確認</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"Thread 1 running: {}"</span><span class="p">,</span> <span class="n">count</span><span class="p">);</span>
        <span class="n">count</span> <span class="o">=</span> <span class="n">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
        
        <span class="k">for</span> <span class="n">_</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="mi">1000000</span> <span class="p">{</span>
            <span class="k">unsafe</span> <span class="p">{</span> <span class="nn">core</span><span class="p">::</span><span class="nn">arch</span><span class="p">::</span><span class="nd">asm!</span><span class="p">(</span><span class="s">"nop"</span><span class="p">);</span> <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>動作確認のため2つの関数を用意しました。どちらも適当に表示して1ずつカウントアップしていくだけの簡単な処理です。一応、表示頻度を抑えるために <code class="language-plaintext highlighter-rouge">nop</code> を毎ループで100万回実行させています（これも早く sleep を実装したい！）。</p>

<p>そしてこれらを実行するスレッドを <code class="language-plaintext highlighter-rouge">kernel_main()</code> 内で生成します。</p>

<p><code class="language-plaintext highlighter-rouge">main.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// エントリポイント</span>
<span class="k">fn</span> <span class="nf">kernel_main</span><span class="p">(</span><span class="n">boot_info</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="n">BootInfo</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="o">...</span>
    <span class="c1">// カーネルスレッド作成</span>
    <span class="nn">thread</span><span class="p">::</span><span class="nf">create_kernel_thread</span><span class="p">(</span><span class="n">kernel_thread_0</span><span class="p">);</span>
    <span class="nn">thread</span><span class="p">::</span><span class="nf">create_kernel_thread</span><span class="p">(</span><span class="n">kernel_thread_1</span><span class="p">);</span>

    <span class="nn">thread</span><span class="p">::</span><span class="nn">scheduler</span><span class="p">::</span><span class="nf">scheduler</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>さて、動かしてみましょう。</p>

<p><img src="../../../assets/img/post/2026-02-14-rust-os-dev-1/image-20260216220107696.webp" alt="image-20260216220107696" /></p>

<p>うまく動きました！スレッド 0 とスレッド 1 が（ほぼ）交互に表示されています。若干タイミングのズレで 0 が2連続になったり 1 が2連続になったりしていますが、スケジューラによって交互に実行されているはずです。</p>

<p>また、それぞれで別々に数値がカウントアップされていることから、各スレッド用のスタックもうまく機能していることが分かります。</p>

<h2 id="キーボード割り込み">キーボード割り込み</h2>

<p>Writing an OS in Rust の「Async/Await」の章では、キーボード割り込みを Task（協調的マルチタスク）として実装していました。ただ、スケジューラが延々と実行されるようになってしまった今、Task の Executor を無限ループで回すのは困難です。</p>

<p>とりあえず、キーボード割り込みのためのタスクもカーネルスレッドとして実行しておきましょう。</p>

<p><code class="language-plaintext highlighter-rouge">src/main.rs</code></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// エントリポイント</span>
<span class="k">fn</span> <span class="nf">kernel_main</span><span class="p">(</span><span class="n">boot_info</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="n">BootInfo</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="o">...</span>
    <span class="c1">// カーネルスレッド作成</span>
    <span class="nn">thread</span><span class="p">::</span><span class="nf">create_kernel_thread</span><span class="p">(</span><span class="n">kernel_thread_0</span><span class="p">);</span>
    <span class="nn">thread</span><span class="p">::</span><span class="nf">create_kernel_thread</span><span class="p">(</span><span class="n">kernel_thread_1</span><span class="p">);</span>
    <span class="nn">thread</span><span class="p">::</span><span class="nf">create_kernel_thread</span><span class="p">(</span><span class="n">keyboard_thread</span><span class="p">);</span>

    <span class="nn">thread</span><span class="p">::</span><span class="nn">scheduler</span><span class="p">::</span><span class="nf">scheduler</span><span class="p">();</span>
<span class="p">}</span>

<span class="c1">// キーボード割り込み用スレッド</span>
<span class="k">fn</span> <span class="nf">keyboard_thread</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">executor</span> <span class="o">=</span> <span class="nn">Executor</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="n">executor</span><span class="nf">.spawn</span><span class="p">(</span><span class="nn">Task</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">keyboard</span><span class="p">::</span><span class="nf">print_keypresses</span><span class="p">()));</span>
    <span class="n">executor</span><span class="nf">.run</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>これでカーネル起動後に何らかのキーを押すと、それに応じて押されたキーが表示されるはずです。</p>

<p><img src="../../../assets/img/post/2026-02-14-rust-os-dev-1/image-20260216220949462.webp" alt="image-20260216220949462" /></p>

<p>ただ、非常に遅い…。もっと良い実装方法はないか模索していきたいと思います。</p>

<h2 id="スケジューラをトレイトとして実装する">スケジューラをトレイトとして実装する</h2>

<p>現在はラウンドロビンスケジューラしか実装していませんが、将来的には他のスケジューラも実装するかもしれません。必須ではありませんが、拡張しやすくするためにトレイトとしての実装に変更したいと思います。</p>

<p>まずは <code class="language-plaintext highlighter-rouge">thread/scheduler/mod.rs</code> を作成し、トレイトを定義しておきます。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="k">super</span><span class="p">::{</span> <span class="n">Thread</span><span class="p">,</span> <span class="n">ThreadState</span><span class="p">,</span> <span class="n">PROCESS_TABLE</span><span class="p">,</span> <span class="n">NPROC</span> <span class="p">};</span>
<span class="k">use</span> <span class="k">super</span><span class="p">::</span><span class="n">context</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="n">cpu</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">lazy_static</span><span class="p">::</span><span class="n">lazy_static</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">conquer_once</span><span class="p">::</span><span class="nn">spin</span><span class="p">::</span><span class="n">OnceCell</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">alloc</span><span class="p">::</span><span class="nn">boxed</span><span class="p">::</span><span class="nb">Box</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">mod</span> <span class="n">round_robin</span><span class="p">;</span>

<span class="nd">lazy_static!</span> <span class="p">{</span>
    <span class="k">static</span> <span class="k">ref</span> <span class="n">CPU</span><span class="p">:</span> <span class="nn">spin</span><span class="p">::</span><span class="n">Mutex</span><span class="o">&lt;</span><span class="nn">cpu</span><span class="p">::</span><span class="n">Cpu</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nn">spin</span><span class="p">::</span><span class="nn">Mutex</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">cpu</span><span class="p">::</span><span class="nn">Cpu</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">pub</span> <span class="k">static</span> <span class="n">SCHEDULER</span><span class="p">:</span> <span class="n">OnceCell</span><span class="o">&lt;</span><span class="nb">Box</span><span class="o">&lt;</span><span class="k">dyn</span> <span class="n">Scheduler</span> <span class="o">+</span> <span class="nb">Send</span> <span class="o">+</span> <span class="nb">Sync</span><span class="o">&gt;&gt;</span> <span class="o">=</span> <span class="nn">OnceCell</span><span class="p">::</span><span class="nf">uninit</span><span class="p">();</span>
<span class="k">pub</span> <span class="k">static</span> <span class="k">mut</span> <span class="n">SCHEDULER_STARTED</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">init</span><span class="p">(</span><span class="n">scheduler</span><span class="p">:</span> <span class="nb">Box</span><span class="o">&lt;</span><span class="k">dyn</span> <span class="n">Scheduler</span> <span class="o">+</span> <span class="nb">Send</span> <span class="o">+</span> <span class="nb">Sync</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">SCHEDULER</span><span class="nf">.init_once</span><span class="p">(||</span> <span class="n">scheduler</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">trait</span> <span class="n">Scheduler</span><span class="p">:</span> <span class="nb">Send</span> <span class="o">+</span> <span class="nb">Sync</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">scheduler</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">!</span><span class="p">;</span>
    <span class="k">fn</span> <span class="nf">on_yield</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">get_scheduler</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="k">dyn</span> <span class="n">Scheduler</span> <span class="p">{</span>
    <span class="n">SCHEDULER</span><span class="nf">.get</span><span class="p">()</span>
        <span class="nf">.expect</span><span class="p">(</span><span class="s">"Scheduler not initialized"</span><span class="p">)</span>
        <span class="nf">.as_ref</span><span class="p">()</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">scheduler</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="nf">get_scheduler</span><span class="p">()</span><span class="nf">.scheduler</span><span class="p">();</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">yield_from_context</span><span class="p">()</span> <span class="p">{</span>
    <span class="nf">get_scheduler</span><span class="p">()</span><span class="nf">.on_yield</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>今回は、スケジューラは <code class="language-plaintext highlighter-rouge">scheduler::init()</code> で初期化します。このとき、どのスケジューラを使用するかは引数で渡します。使用するスケジューラは <code class="language-plaintext highlighter-rouge">SCHEDULER</code> 変数に保持しておき、いずれのスレッドからでも参照できるよう <code class="language-plaintext highlighter-rouge">OnceCell&lt;Box&lt;..&gt;&gt;</code> として宣言しています。<code class="language-plaintext highlighter-rouge">OnceCell</code> を使う制約として<code class="language-plaintext highlighter-rouge">Scheduler</code> トレイトは <code class="language-plaintext highlighter-rouge">&amp;mut self</code> なメソッドを定義できなくなりますが、メンバ変数に <code class="language-plaintext highlighter-rouge">RwLock</code> や <code class="language-plaintext highlighter-rouge">Mutex</code> を使うことで内部可変性を持たせることができます。</p>

<p>トレイト <code class="language-plaintext highlighter-rouge">Scheduler</code> には <code class="language-plaintext highlighter-rouge">Send</code> と <code class="language-plaintext highlighter-rouge">Sync</code> を継承させています。<code class="language-plaintext highlighter-rouge">Send</code> はスケジューラを別の CPU コアでも実行できるようにするための所有権移動の安全性、<code class="language-plaintext highlighter-rouge">Sync</code> は複数の割り込みハンドラから同時にアクセスできるようにするための共有参照の安全性を保証するために使用しており、いずれも将来的なマルチコア対応のためのスレッド安全性に向けたものです。</p>

<p><code class="language-plaintext highlighter-rouge">schduler()</code> はスケジューラ自体の実装、<code class="language-plaintext highlighter-rouge">on_yield()</code> はスケジューラにコンテキストを戻すときの処理を実装します。</p>

<p>続いてラウンドロビンスケジューラの本体実装。こちらは実装自体は変更していません。</p>

<p><code class="language-plaintext highlighter-rouge">thread/scheduler/round_robin.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="k">super</span><span class="p">::{</span> <span class="n">Thread</span><span class="p">,</span> <span class="n">ThreadState</span><span class="p">,</span> <span class="n">PROCESS_TABLE</span><span class="p">,</span> <span class="n">NPROC</span><span class="p">,</span> <span class="n">CPU</span><span class="p">,</span> <span class="n">SCHEDULER_STARTED</span> <span class="p">};</span>
<span class="k">use</span> <span class="k">super</span><span class="p">::</span><span class="nn">context</span><span class="p">::{</span> <span class="n">Context</span><span class="p">,</span> <span class="n">switch_context</span> <span class="p">};</span>

<span class="k">pub</span> <span class="k">struct</span> <span class="n">RoundRobin</span><span class="p">;</span>

<span class="k">impl</span> <span class="k">super</span><span class="p">::</span><span class="n">Scheduler</span> <span class="k">for</span> <span class="n">RoundRobin</span> <span class="p">{</span>
    <span class="cd">/// スケジューラ</span>
    <span class="k">fn</span> <span class="nf">scheduler</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
        <span class="o">...</span>  <span class="c1">// 以前の実装と同じ</span>
    <span class="p">}</span>
    
    <span class="cd">/// スレッドからスケジューラに戻る</span>
    <span class="k">fn</span> <span class="nf">on_yield</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="p">{</span>
        <span class="o">...</span>  <span class="c1">// 以前の yield_from_context() の実装と同じ</span>
    <span class="p">}</span>   
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">find_next_runnable_thread</span><span class="p">(</span><span class="n">table</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="n">Thread</span><span class="p">;</span> <span class="n">NPROC</span><span class="p">],</span> <span class="n">current_tid</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="nb">usize</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="nb">usize</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// 以前の実装と同じ</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">find_next_runnable_thread()</code> のみトレイトの実装から外れるので、private メソッドとして定義しました。</p>

<p>これで準備は整いました。スケジューラおよび yield を呼び出す側も修正していきます。</p>

<p><code class="language-plaintext highlighter-rouge">main.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">ferrios</span><span class="p">::</span><span class="nn">thread</span><span class="p">::</span><span class="n">scheduler</span><span class="p">;</span>

<span class="cd">/// エントリポイント</span>
<span class="k">fn</span> <span class="nf">kernel_main</span><span class="p">(</span><span class="n">boot_info</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="n">BootInfo</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="o">...</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"Starting the scheduler.."</span><span class="p">);</span>
    <span class="nn">thread</span><span class="p">::</span><span class="nn">scheduler</span><span class="p">::</span><span class="nf">scheduler</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">interrupts.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// タイマ割り込みハンドラ</span>
<span class="k">extern</span> <span class="s">"x86-interrupt"</span> <span class="k">fn</span> <span class="nf">timer_interrupt_handler</span><span class="p">(</span><span class="n">_stack_frame</span><span class="p">:</span> <span class="n">InterruptStackFrame</span><span class="p">)</span> <span class="p">{</span>
    <span class="o">...</span>
    <span class="k">unsafe</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">crate</span><span class="p">::</span><span class="nn">thread</span><span class="p">::</span><span class="nn">scheduler</span><span class="p">::</span><span class="n">SCHEDULER_STARTED</span> <span class="p">{</span>
            <span class="nn">scheduler</span><span class="p">::</span><span class="nf">yield_from_context</span><span class="p">();</span>
			<span class="o">...</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>これにて完了。無事、スケジューラが実行できていることも確認できました。</p>

<h1 id="コンソールの改善おまけ">コンソールの改善（おまけ）</h1>

<p>本題からは逸れますが、今後開発しやすくするために、コンソールの改善もここで行っておきます。</p>

<p>Writing an OS in Rust で実装したコンソールの出力先は VGA とシリアル出力の2つがあります。QEMU をグラフィックモードで起動した場合は VGA が表示されますが、<code class="language-plaintext highlighter-rouge">--nogprahic</code> で起動した場合はコンソール出力が表示されます。</p>

<p>現状、<code class="language-plaintext highlighter-rouge">kernel_main()</code> や各スレッドが表示するのに利用している <code class="language-plaintext highlighter-rouge">print!()</code>、<code class="language-plaintext highlighter-rouge">println!()</code> は VGA 出力です。QEMU を no-graphic モードで起動すると表示できません。</p>

<p>ということで、どちらからでも常時表示できるように、VGA とシリアル出力の両方に対応したコンソールを実装しました。このコンソールを利用すると、コンソール出力に未対応の環境では VGA のみ、コンソール対応環境の場合は VGA とコンソールの両方を同時に出力するようになります。</p>

<p>まずは <code class="language-plaintext highlighter-rouge">console</code> ディレクトリを作成し、そこに既に実装した <code class="language-plaintext highlighter-rouge">vga_buffer.rs</code> と <code class="language-plaintext highlighter-rouge">serial.rs</code> を移動します。さらに、<code class="language-plaintext highlighter-rouge">mod.rs</code> も作成しておきます。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>src/console/
├── mod.rs
├── serial.rs
└── vga_buffer.rs
</code></pre></div></div>

<p>まずは <code class="language-plaintext highlighter-rouge">mod.rs</code> に <code class="language-plaintext highlighter-rouge">Console</code> 構造体を実装していきます。</p>

<p><code class="language-plaintext highlighter-rouge">console/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">core</span><span class="p">::</span><span class="n">fmt</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">spin</span><span class="p">::</span><span class="n">Mutex</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">lazy_static</span><span class="p">::</span><span class="n">lazy_static</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">mod</span> <span class="n">serial</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">vga_buffer</span><span class="p">;</span>

<span class="nd">#[derive(Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy,</span> <span class="nd">PartialEq,</span> <span class="nd">Eq)]</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">ConsoleMode</span> <span class="p">{</span>
    <span class="n">Serial</span><span class="p">,</span>
    <span class="n">Vga</span><span class="p">,</span>
    <span class="n">Both</span><span class="p">,</span>
<span class="p">}</span>

<span class="cd">/// コンソール構造体</span>
<span class="nd">#[derive(Debug)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">Console</span> <span class="p">{</span>
    <span class="n">mode</span><span class="p">:</span> <span class="n">ConsoleMode</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Console</span> <span class="p">{</span>
    <span class="k">const</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="n">Console</span> <span class="p">{</span>
            <span class="n">mode</span><span class="p">:</span> <span class="nn">ConsoleMode</span><span class="p">::</span><span class="n">Both</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">set</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">mode</span><span class="p">:</span> <span class="n">ConsoleMode</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.mode</span> <span class="o">=</span> <span class="n">mode</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">get</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">ConsoleMode</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.mode</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">is_serial_avaiable</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">bool</span> <span class="p">{</span>
        <span class="k">use</span> <span class="nn">x86_64</span><span class="p">::</span><span class="nn">instructions</span><span class="p">::</span><span class="nn">port</span><span class="p">::</span><span class="n">Port</span><span class="p">;</span>

        <span class="k">unsafe</span> <span class="p">{</span>
            <span class="c1">// Line Status Register</span>
            <span class="k">let</span> <span class="k">mut</span> <span class="n">port</span> <span class="o">=</span> <span class="nn">Port</span><span class="p">::</span><span class="o">&lt;</span><span class="nb">u8</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0x3FD</span><span class="p">);</span>
            <span class="k">let</span> <span class="n">status</span> <span class="o">=</span> <span class="n">port</span><span class="nf">.read</span><span class="p">();</span>
            <span class="c1">// bit-5 と bit-6 が立っていればシリアルポートが存在する</span>
            <span class="p">(</span><span class="n">status</span> <span class="o">&amp;</span> <span class="mi">0x60</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">update_mode</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.mode</span> <span class="o">=</span> <span class="k">if</span> <span class="k">Self</span><span class="p">::</span><span class="nf">is_serial_avaiable</span><span class="p">()</span> <span class="p">{</span>
            <span class="nn">ConsoleMode</span><span class="p">::</span><span class="n">Both</span>
        <span class="p">}</span>
        <span class="k">else</span> <span class="p">{</span>
            <span class="nn">ConsoleMode</span><span class="p">::</span><span class="n">Vga</span>
        <span class="p">};</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>この <code class="language-plaintext highlighter-rouge">Console</code> 構造体は、コンソールのモード <code class="language-plaintext highlighter-rouge">ConsoleMode</code> を保持します。<code class="language-plaintext highlighter-rouge">is_serial_avaiable()</code> で現在の環境がシリアル出力に対応しているか確認し、<code class="language-plaintext highlighter-rouge">update_mode()</code> で現在の環境に適したモードを選択します。<code class="language-plaintext highlighter-rouge">is_serial_avaiable()</code> では Line Status Register を確認し、そのレジスタの5ビット目および6ビット目が立っているかどうかでシリアル出力の対応の有無を確認しています。</p>

<p>さらに、グローバル変数として現在の環境に適したコンソールモードを保持する <code class="language-plaintext highlighter-rouge">CONSOLE</code> 変数を用意しておきます。</p>

<p><code class="language-plaintext highlighter-rouge">console/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">lazy_static!</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">static</span> <span class="k">ref</span> <span class="n">CONSOLE</span><span class="p">:</span> <span class="n">Mutex</span><span class="o">&lt;</span><span class="n">Console</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nn">Mutex</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">Console</span><span class="p">::</span><span class="nf">new</span><span class="p">());</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">init</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">console</span> <span class="o">=</span> <span class="n">CONSOLE</span><span class="nf">.lock</span><span class="p">();</span>
    <span class="n">console</span><span class="nf">.update_mode</span><span class="p">();</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">_print</span><span class="p">(</span><span class="n">args</span><span class="p">:</span> <span class="nn">fmt</span><span class="p">::</span><span class="n">Arguments</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">use</span> <span class="nn">x86_64</span><span class="p">::</span><span class="nn">instructions</span><span class="p">::</span><span class="n">interrupts</span><span class="p">;</span>

    <span class="c1">// ロック中の割り込みを防止</span>
    <span class="nn">interrupts</span><span class="p">::</span><span class="nf">without_interrupts</span><span class="p">(||</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">console</span> <span class="o">=</span> <span class="n">CONSOLE</span><span class="nf">.lock</span><span class="p">();</span>

        <span class="k">match</span> <span class="n">console</span><span class="py">.mode</span> <span class="p">{</span>
            <span class="nn">ConsoleMode</span><span class="p">::</span><span class="n">Serial</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="nn">serial</span><span class="p">::</span><span class="nf">_print</span><span class="p">(</span><span class="n">args</span><span class="p">);</span>
            <span class="p">},</span>
            <span class="nn">ConsoleMode</span><span class="p">::</span><span class="n">Vga</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="nn">vga_buffer</span><span class="p">::</span><span class="nf">_print</span><span class="p">(</span><span class="n">args</span><span class="p">);</span>
            <span class="p">},</span>
            <span class="nn">ConsoleMode</span><span class="p">::</span><span class="n">Both</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="nn">serial</span><span class="p">::</span><span class="nf">_print</span><span class="p">(</span><span class="n">args</span><span class="p">);</span>
                <span class="nn">vga_buffer</span><span class="p">::</span><span class="nf">_print</span><span class="p">(</span><span class="n">args</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">});</span>
<span class="p">}</span>

<span class="nd">#[macro_export]</span>
<span class="nd">macro_rules!</span> <span class="n">print</span> <span class="p">{</span>
    <span class="p">(</span><span class="nv">$</span><span class="p">(</span><span class="nv">$arg:tt</span><span class="p">)</span><span class="o">*</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
        <span class="nv">$crate</span><span class="p">::</span><span class="nn">console</span><span class="p">::</span><span class="nf">_print</span><span class="p">(</span><span class="nd">format_args!</span><span class="p">(</span><span class="nv">$</span><span class="p">(</span><span class="nv">$arg</span><span class="p">)</span><span class="o">*</span><span class="p">))</span>
    <span class="p">};</span>
<span class="p">}</span>

<span class="nd">#[macro_export]</span>
<span class="nd">macro_rules!</span> <span class="n">println</span> <span class="p">{</span>
    <span class="p">()</span> <span class="k">=&gt;</span> <span class="p">(</span><span class="nv">$crate</span><span class="p">::</span><span class="nn">console</span><span class="p">::</span><span class="nf">_print</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">));</span>
    <span class="p">(</span><span class="nv">$fmt:expr</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">(</span><span class="nv">$crate</span><span class="p">::</span><span class="nd">print!</span><span class="p">(</span><span class="nd">concat!</span><span class="p">(</span><span class="nv">$fmt</span><span class="p">,</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)));</span>
    <span class="p">(</span><span class="nv">$fmt:expr</span><span class="p">,</span> <span class="nv">$</span><span class="p">(</span><span class="nv">$arg:tt</span><span class="p">)</span><span class="o">*</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">(</span><span class="nv">$crate</span><span class="p">::</span><span class="nd">print!</span><span class="p">(</span><span class="nd">concat!</span><span class="p">(</span><span class="nv">$fmt</span><span class="p">,</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">),</span> <span class="nv">$</span><span class="p">(</span><span class="nv">$arg</span><span class="p">)</span><span class="o">*</span><span class="p">));</span>
<span class="p">}</span>

</code></pre></div></div>

<p>例によって <code class="language-plaintext highlighter-rouge">lazy_static</code> です。<code class="language-plaintext highlighter-rouge">kernel_main()</code> から呼び出される <code class="language-plaintext highlighter-rouge">console::init()</code> で <code class="language-plaintext highlighter-rouge">update_mode()</code> を呼び出して初期化します。また、この <code class="language-plaintext highlighter-rouge">CONSOLE</code> の状態に応じて、シリアルか VGA かあるいは両方の <code class="language-plaintext highlighter-rouge">_print()</code> を呼び出す <code class="language-plaintext highlighter-rouge">_print()</code> メソッドと、それを呼び出すためのマクロ <code class="language-plaintext highlighter-rouge">print!()</code>、<code class="language-plaintext highlighter-rouge">println!()</code> も用意しました。</p>

<p>シリアル側、VGA 側の <code class="language-plaintext highlighter-rouge">print!()</code>、<code class="language-plaintext highlighter-rouge">println!()</code> もマクロとして呼び出せるよう、それぞれ改名しておきます。</p>

<p><code class="language-plaintext highlighter-rouge">console/serial.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// シリアルポートに文字列を書き込む</span>
<span class="nd">#[macro_export]</span>
<span class="nd">macro_rules!</span> <span class="n">serial_print</span> <span class="p">{</span>
    <span class="p">(</span><span class="nv">$</span><span class="p">(</span><span class="nv">$arg:tt</span><span class="p">)</span><span class="o">*</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
        <span class="nv">$crate</span><span class="p">::</span><span class="nn">console</span><span class="p">::</span><span class="nn">serial</span><span class="p">::</span><span class="nf">_print</span><span class="p">(</span><span class="nd">format_args!</span><span class="p">(</span><span class="nv">$</span><span class="p">(</span><span class="nv">$arg</span><span class="p">)</span><span class="o">*</span><span class="p">))</span>
    <span class="p">};</span>
<span class="p">}</span>

<span class="cd">/// シリアルポートに文字列を書き込み、改行する</span>
<span class="nd">#[macro_export]</span>
<span class="nd">macro_rules!</span> <span class="n">serial_println</span> <span class="p">{</span>
    <span class="p">()</span> <span class="k">=&gt;</span> <span class="p">(</span><span class="nv">$crate</span><span class="p">::</span><span class="nd">serial_print!</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">));</span>
    <span class="p">(</span><span class="nv">$fmt:expr</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">(</span><span class="nv">$crate</span><span class="p">::</span><span class="nd">serial_print!</span><span class="p">(</span><span class="nd">concat!</span><span class="p">(</span><span class="nv">$fmt</span><span class="p">,</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)));</span>
    <span class="p">(</span><span class="nv">$fmt:expr</span><span class="p">,</span> <span class="nv">$</span><span class="p">(</span><span class="nv">$arg:tt</span><span class="p">)</span><span class="o">*</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">(</span><span class="nv">$crate</span><span class="p">::</span><span class="nd">serial_print!</span><span class="p">(</span><span class="nd">concat!</span><span class="p">(</span><span class="nv">$fmt</span><span class="p">,</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">),</span> <span class="nv">$</span><span class="p">(</span><span class="nv">$arg</span><span class="p">)</span><span class="o">*</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">console/vga_buffer.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// println!, print! マクロの実装</span>
<span class="nd">#[macro_export]</span>
<span class="nd">macro_rules!</span> <span class="n">vga_print</span> <span class="p">{</span>
    <span class="p">(</span><span class="nv">$</span><span class="p">(</span><span class="nv">$arg:tt</span><span class="p">)</span><span class="o">*</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">(</span><span class="nv">$crate</span><span class="p">::</span><span class="nn">console</span><span class="p">::</span><span class="nn">vga_buffer</span><span class="p">::</span><span class="nf">_print</span><span class="p">(</span><span class="nd">format_args!</span><span class="p">(</span><span class="nv">$</span><span class="p">(</span><span class="nv">$arg</span><span class="p">)</span><span class="o">*</span><span class="p">)));</span>
<span class="p">}</span>
<span class="nd">#[macro_export]</span>
<span class="nd">macro_rules!</span> <span class="n">vga_println</span> <span class="p">{</span>
    <span class="p">()</span> <span class="k">=&gt;</span> <span class="p">(</span><span class="nv">$crate</span><span class="p">::</span><span class="nd">print!</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">));</span>
    <span class="p">(</span><span class="nv">$</span><span class="p">(</span><span class="nv">$arg:tt</span><span class="p">)</span><span class="o">*</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">(</span><span class="nv">$crate</span><span class="p">::</span><span class="nd">print!</span><span class="p">(</span><span class="s">"{}</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="nd">format_args!</span><span class="p">(</span><span class="nv">$</span><span class="p">(</span><span class="nv">$arg</span><span class="p">)</span><span class="o">*</span><span class="p">)));</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="タブ文字への対応">タブ文字への対応</h2>

<p>コンソール側は問題ないのですが、VGA 側でタブ文字（\t）がうまく表示できていなかったので、空白4文字に置き換えるよう修正しました。</p>

<p><code class="language-plaintext highlighter-rouge">console/vga_buffer.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">impl</span> <span class="n">Writer</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">write_byte</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">byte</span><span class="p">:</span> <span class="nb">u8</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">match</span> <span class="n">byte</span> <span class="p">{</span>
            <span class="sc">b'\n'</span> <span class="k">=&gt;</span> <span class="k">self</span><span class="nf">.new_line</span><span class="p">(),</span>
            <span class="sc">b'\t'</span> <span class="k">=&gt;</span> <span class="p">{</span>				<span class="c1">// ここ</span>
                <span class="k">for</span> <span class="n">_</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="mi">4</span> <span class="p">{</span>
                    <span class="k">self</span><span class="nf">.write_byte</span><span class="p">(</span><span class="sc">b' '</span><span class="p">);</span>
                <span class="p">}</span>
            <span class="p">}</span>
            <span class="n">byte</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="o">...</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="o">...</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">write_string</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">s</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">for</span> <span class="n">byte</span> <span class="k">in</span> <span class="n">s</span><span class="nf">.bytes</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">match</span> <span class="n">byte</span> <span class="p">{</span>
                <span class="c1">// 出力可能な ASCII byte または改行コード</span>
                <span class="mi">0x20</span><span class="o">..=</span><span class="mi">0x7e</span> <span class="p">|</span> <span class="sc">b'\n'</span> <span class="p">|</span> <span class="sc">b'\t'</span> <span class="k">=&gt;</span> <span class="k">self</span><span class="nf">.write_byte</span><span class="p">(</span><span class="n">byte</span><span class="p">),</span>	<span class="c1">// ここも</span>
                <span class="c1">// 出力不可な文字 -&gt; 特定の文字に置き換え</span>
                <span class="n">_</span> <span class="k">=&gt;</span> <span class="k">self</span><span class="nf">.write_byte</span><span class="p">(</span><span class="mi">0xfe</span><span class="p">),</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="動作確認">動作確認</h2>

<p>動作確認にあたり、<code class="language-plaintext highlighter-rouge">kernel_main()</code> を少し修正。現在のコンソールモードを表示するようにしました。また、見やすいようにタブ文字で各セクション（仮想メモリの動作確認とか）を表示するよう変更しています。</p>

<p><code class="language-plaintext highlighter-rouge">main.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/// エントリポイント</span>
<span class="k">fn</span> <span class="nf">kernel_main</span><span class="p">(</span><span class="n">boot_info</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="n">BootInfo</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="o">...</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"Welcome to FerriOS!"</span><span class="p">);</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">""</span><span class="p">);</span>

    <span class="nd">print!</span><span class="p">(</span><span class="s">"Initializing.."</span><span class="p">);</span>
    <span class="nn">ferrios</span><span class="p">::</span><span class="nf">init</span><span class="p">();</span>
    <span class="nn">console</span><span class="p">::</span><span class="nf">init</span><span class="p">();</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"done."</span><span class="p">);</span>
    
    <span class="k">let</span> <span class="n">console_mode</span> <span class="o">=</span> <span class="nn">console</span><span class="p">::</span><span class="n">CONSOLE</span><span class="nf">.lock</span><span class="p">()</span><span class="nf">.get</span><span class="p">();</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"console-mode: {:?}"</span><span class="p">,</span> <span class="n">console_mode</span><span class="p">);</span>
    <span class="o">...</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"Checking Virtual Memory.."</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">phys_mem_offset</span> <span class="o">=</span> <span class="nn">VirtAddr</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">boot_info</span><span class="py">.physical_memory_offset</span><span class="p">);</span>
    <span class="o">...</span>
    <span class="k">for</span> <span class="o">&amp;</span><span class="n">address</span> <span class="k">in</span> <span class="o">&amp;</span><span class="n">addresses</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">virt</span> <span class="o">=</span> <span class="nn">VirtAddr</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">address</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">phys</span> <span class="o">=</span> <span class="n">mapper</span><span class="nf">.translate_addr</span><span class="p">(</span><span class="n">virt</span><span class="p">);</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"</span><span class="se">\t</span><span class="s">{:?} -&gt; {:?}"</span><span class="p">,</span> <span class="n">virt</span><span class="p">,</span> <span class="n">phys</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"done."</span><span class="p">);</span>
    <span class="o">...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>まずは QEMU no-graphic モードで実行した場合から。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Welcome to FerriOS!

Initializing..done.
console-mode: Both
Checking Virtual Memory..
        VirtAddr<span class="o">(</span>0xb8000<span class="o">)</span> -&gt; Some<span class="o">(</span>PhysAddr<span class="o">(</span>0xb8000<span class="o">))</span>
        VirtAddr<span class="o">(</span>0x201008<span class="o">)</span> -&gt; Some<span class="o">(</span>PhysAddr<span class="o">(</span>0x401008<span class="o">))</span>
        VirtAddr<span class="o">(</span>0x10000201a10<span class="o">)</span> -&gt; Some<span class="o">(</span>PhysAddr<span class="o">(</span>0x27fa10<span class="o">))</span>
        VirtAddr<span class="o">(</span>0x18000000000<span class="o">)</span> -&gt; Some<span class="o">(</span>PhysAddr<span class="o">(</span>0x0<span class="o">))</span>
<span class="k">done</span><span class="nb">.</span>
Initializing heap memory..
        heap_value at 0x444444440000
        vec at Pointer <span class="o">{</span> addr: 0x444444440800, metadata: 500 <span class="o">}</span>
        current reference count is 2
        reference count is 1 now
<span class="k">done</span><span class="nb">.</span>
Starting kernel threads..done.
Starting the scheduler..
Thread 1 running: 0
Thread 0 running: 0
Thread 0 running: 1
</code></pre></div></div>

<p>うまくいきました。QEMU no-graphic モードで実行した場合は Both モード、つまり VGA とシリアルの両方に出力されています。VGA には対応していないはずですが、現状、コンソール対応の場合は無条件に Both モードにしています（どうやって VGA 対応の有無を検知するかは迷い中…）。</p>

<p>続いて QEMU の graphic モードの場合。</p>

<p><img src="../../../assets/img/post/2026-02-14-rust-os-dev-1/image-20260218030340264.webp" alt="image-20260218030340264" /></p>

<p>こちらも Both モードになっていました。結局 graphic モードでも no-graphic モードでも Both モードになるという結果に。とりあえずどちらのモードでも表示できるようになりましたが、両方出力することによるオーバーヘッドは気になります。今後改善していきたい点です。</p>

<h1 id="おわりに">おわりに</h1>

<p>自作 OS の第一歩として、まずはコンテキストとスケジューラを実装し、カーネルスレッドを動かせるようにしました。ついでにコンソールも VGA とシリアルポートの双方に出力できるようにしました。</p>

<p>次回はユーザモードに対応し、ユーザスレッドを作成できるようにしていきたいと思っています。</p>]]></content><author><name></name></author><category term="Rust" /><category term="OS自作" /><category term="FerriOS" /><summary type="html"><![CDATA[どこまで続くかわからないけど、熱意が冷めないうちに… Writing an OS in Rust を一通り写経したので、そこから先を実装する個人的なプロジェクトを始めていきたいと思います。 動機は完全なる趣味・興味です。Writing an OS in Rust は Async/Await の章までしか書かれていないので、そこから先、できれば xv6 と同等以上のレベルにまでは仕上げていきたいと思っています。]]></summary></entry><entry><title type="html">Writing an OS in Rustの写経を終えて</title><link href="https://blog.yotio.jp/2026/02/14/writing-an-os.html" rel="alternate" type="text/html" title="Writing an OS in Rustの写経を終えて" /><published>2026-02-14T00:00:00+09:00</published><updated>2026-02-14T00:00:00+09:00</updated><id>https://blog.yotio.jp/2026/02/14/writing-an-os</id><content type="html" xml:base="https://blog.yotio.jp/2026/02/14/writing-an-os.html"><![CDATA[<ul>
  <li>Writing an OS in Rust
    <ul>
      <li>https://os.phil-opp.com</li>
    </ul>
  </li>
</ul>

<p>Writing an OS in Rust の写経を始めて1年4ヶ月、ようやく全章読み終えました。</p>

<p>読み終えた、といっても Writing an OS in Rust は途中の章 <code class="language-plaintext highlighter-rouge">Async/Await</code> までしか書かれていないので、まだ完成とは言えませんけどもね。</p>

<p><img src="../../../assets/img/post/2026-02-14-writing-an-os/image-20260214210407327.webp" alt="image-20260214210407327" /></p>

<!--more-->

<h1 id="感想">感想</h1>

<p>自分は大学院時代に <a href="https://github.com/mit-pdos/xv6-riscv">xv6</a> という、MIT 謹製の C 言語で書かれたミニ OS のソースコードを一通り読んでいるのですが、xv6 に比べるとシンプルな実装になっているなー、というのが一つの感想です。xv6 だけアセンブリで CPU を初期化して、ブートストラップから起動して、…と main 関数にたどり着くまでの処理はアセンブリを読むことが必須ですが、この Writing an OS in Rust で実装する blog OS に関しては <code class="language-plaintext highlighter-rouge">x86_64</code> クレートを利用しており（なんと Writing an OS in Rust の著者様がメンテナらしい）、一番低レイヤな部分は抽象化されている印象です。お陰様で Rust でのカーネル実装に集中できます。</p>

<p>Rust の面白いところは、普段書いてるような普通のユーザアプリケーションであれ、今回実装したような nostd なカーネルであれ、crates.io から手軽に必要なライブラリを手に入れることができるところですね。<code class="language-plaintext highlighter-rouge">x86_64</code> はもちろん、ブートローダーを実装した <code class="language-plaintext highlighter-rouge">bootloader</code>、割り込みハンドラ用のクレート <code class="language-plaintext highlighter-rouge">pic8259</code>、キー入力に対応した <code class="language-plaintext highlighter-rouge">pc-keyboard</code> などなど。この辺の実装も xv6 では全部自分で書いているので、自作 OS の敷居が下がった良い時代になったなと思います。もちろんそのあたりを学びたい人には自前でアセンブリでの実装も良い経験になるかと思いますが、OS 全体の動きの大枠を捉えるにはある程度の完成品を組み合わせながら学んでいくのも良い教材なのではないでしょうか。</p>

<p>一方、Rust で書くにあたって、やはり borrow check だったり型チェックだったりの制約があるのでコンパイラエラーを避けるためにこれをしなければならない、などなど Rust ならではの注意点もいくつか見られました。C 言語なら気にしなくて良い点であっても Rust だと気にしなければならない分、実装効率が若干下がるケースもあるのかなという印象もありますが、まあ Rust はメモリ安全側に全振りがコンセプトなので受け入れるしかないでしょう。むしろ、Rust によってカーネル内でのメモリ脆弱性の発生頻度が格段に下がることを歓迎すべきです。</p>

<h1 id="実装内容">実装内容</h1>

<h2 id="これまで実装した内容">これまで実装した内容</h2>

<ul>
  <li>文字列出力（VGA）</li>
  <li>カーネルテスト</li>
  <li>CPU 割り込み</li>
  <li>例外ハンドラ</li>
  <li>キーボード入力</li>
  <li>ページング</li>
  <li>ヒープ割り当て</li>
  <li>協調的同期処理（Async/Await）</li>
</ul>

<h2 id="まだ実装できていない内容xv6-と対比して">まだ実装できていない内容（xv6 と対比して）</h2>

<p>先述の通り、Writing an OS in Rust はまだ書きかけのブログです。Async/Await 以降の章の更新も今後予定されています。（5年くらい更新されてないけど）</p>

<p>xv6 にあって blog OS にない内容は、思いつく限りだと</p>

<ul>
  <li>マルチタスク</li>
  <li>ユーザモード</li>
  <li>プロセス</li>
  <li>スケジューリング</li>
  <li>システムコール</li>
  <li>ユーザアプリケーション（シェルなど）</li>
  <li>ファイルシステム</li>
  <li>ページフォルトハンドラ</li>
</ul>

<p>といったところですかね。まだカーネルモードでの起動しかできませんし、Async/Await の章でタスクは実装されてますけど並行処理であって並列処理には対応していなかったり、プロセスの概念がなかったり、スケジューリングによる非協調的マルチタスクへの対応ができていません。</p>

<h1 id="今後の予定">今後の予定</h1>

<p>せっかくここまで実装したので、せめて xv6 と同じレベルまで実装を続けたいと思っています。できれば Unix ライクな OS にしたいですね。</p>

<p>実は Writing an OS in Rust の続きというコンセプトで記事を書いてくださっている方もいまして、これを参考に実装を続けるという手もあります。</p>

<ul>
  <li>EurailOS
    <ul>
      <li><a href="https://github.com/bendudson/EuraliOS">https://github.com/bendudson/EuraliOS</a></li>
    </ul>
  </li>
</ul>

<p>しかし、どうせなら自分で実装したいです。xv6 の実装を参考にしつつ、さらに先に進めていければなと思っています。</p>]]></content><author><name></name></author><category term="Rust" /><category term="OS自作入門" /><summary type="html"><![CDATA[Writing an OS in Rust https://os.phil-opp.com Writing an OS in Rust の写経を始めて1年4ヶ月、ようやく全章読み終えました。 読み終えた、といっても Writing an OS in Rust は途中の章 Async/Await までしか書かれていないので、まだ完成とは言えませんけどもね。]]></summary></entry><entry><title type="html">2026年もよろしくお願いいたします</title><link href="https://blog.yotio.jp/2026/01/03/new-year.html" rel="alternate" type="text/html" title="2026年もよろしくお願いいたします" /><published>2026-01-03T00:00:00+09:00</published><updated>2026-01-03T00:00:00+09:00</updated><id>https://blog.yotio.jp/2026/01/03/new-year</id><content type="html" xml:base="https://blog.yotio.jp/2026/01/03/new-year.html"><![CDATA[<p>遅ればせながらあけましておめでとうございます。2026年もよろしくお願いいたします。</p>

<p><img src="../../../assets/img/post/2026-01-03-new-year/IMG_4397.webp" alt="IMG_4397" /></p>

<p>写真は羽田沖で撮影した今年の初日の出です。今年の元旦は雲が多く、7時すぎにようやく太陽が姿を表してくれました。</p>

<!--more-->

<h1 id="去年の反省点">去年の反省点</h1>

<p>去年は振り返り記事を書いていなかったので、ここで少し振り返りたいと思います。</p>

<p>去年は、YotioSoft としては</p>

<ul>
  <li>個人ホームページ yotio.jp の公開</li>
  <li>Rust で Windows のプロセス情報を取得する「winprocinfo」の公開</li>
  <li>Rust 製自作画像処理ツール「rusimg」の公開</li>
  <li>dptran の開発・更新</li>
</ul>

<p>…と、近年にしてはそれなりに YotioSoft としての活動を続けられていたのではないかなと思います。</p>

<p>個人的なイベントとしても</p>

<ul>
  <li>修論発表</li>
  <li>修士課程修了</li>
  <li>就職</li>
  <li>研修・発表</li>
  <li>旅行</li>
</ul>

<p>と、そこそこ忙しい年ではありました。特に就職という大きなイベントがあり、周囲の環境は全部変わりました。幸い、職場の人間関係に大変恵まれていますし、（社名はここでは出せませんが）世界最先端の技術と競争力を持ち、かつ誰もが知る企業なので、誇りを持って楽しく仕事に取り組むことができています。</p>

<p>ただ、去年の前半は良かったものの、後半が惰性で生きてしまったなぁという反省点はあります。個人ホームページの公開、自作ツールの新規公開…も全て前半ですし、修了にしろ就職にしろすべて春以前の出来事です。リアルの方のイベントで新しいことを始めるのは時機の関係もありますが、せめて YotioSoft はじめネット上の活動はもっと新しいことをしていきたいですね。</p>

<p>あとは趣味の開発の方も、今年も相変わらず Rust に Rust、CLI ツールに CLI ツール…と偏ってしまっています。たまには OpenSiv3D や HSP3 に回帰して、GUI アプリをもっと書いていきたいものです。実は去年の暮に開発環境を整えていたので、そろそろいい加減 Cities Box などの開発も再開していきたいなぁと考えています。</p>

<h1 id="今年実現したいことyotio-として">今年実現したいこと（yotio として）</h1>

<ul>
  <li>個人ホームページのコンテンツを拡充する</li>
  <li>引き続き現存の開発を続ける</li>
  <li>mprotect-rs を公開する</li>
  <li>Rust 以外の言語にも手を出してみる</li>
  <li>OpenSiv3D などによる GUI アプリ開発に回帰する</li>
  <li>Yapps の拡充・収益化する</li>
</ul>

<p>こんなところですかね。個人ホームページ（yotio.jp）の方はあまりコンテンツを拡充できていないので、もっとプログラミング以外の趣味のページを作っていきたいですね。</p>

<p>現存の開発、mprotect-rs、まあこのへんは Rust 開発の話です。ただ偏りすぎないよう、Rust 以外の言語も始めてみたりとか、Cities Box など GUI アプリ開発も再開していきたいなと思っています。</p>

<p>Yapps に関しては、月間 1.5 万 PV くらいになってきて、さすがに収益化しないのも勿体ないなと思っており、そろそろ広告を貼らせていただきたいなと思っています。（あ、嫌だったら広告ブロックを使っていただいて構いません。）Claude などを用いた Web アプリ開発の自動化も軌道に乗っているので、さらにアプリを拡充させていきたいなと考えています。</p>

<h1 id="今年実現したいこと個人">今年実現したいこと（個人）</h1>

<ul>
  <li>一人暮らしを始める</li>
  <li>何らかの新しい日課を始める</li>
  <li>美容院デビューする</li>
  <li>もっとイベントに参加する</li>
  <li>仕事面での向上</li>
</ul>

<p>一人暮らしは本当は去年中に始めたいと思っていたのですが、あまりにも東京近辺の家賃が高騰しすぎて結局出られずにいました。今の実家からでも通えなくはないんですが、遠目の郊外で職場まで片道1時間半かかるというのと、いい加減自分で生活できるようになりたいというのもあり、今年こそ一人暮らしを始めたいと思っています。</p>

<p>仕事面に関しては社外秘の情報も含むのであまり詳しくは書けませんが、近年中にはプロジェクトリーダー級の仕事が任せられるよう、日々切磋琢磨していきたいと思っています。</p>

<h1 id="おわりに">おわりに</h1>

<p>去年は社会人になり、周囲の環境も何から何まで変わり…と大変な一年でしたが、同時に充実感のある一年でした。今年も引き続き、リアルでの生活とネット上の活動を両立できればと思っています（実を言うと、大学院時代より社会人のほうが時間にも余裕があります…）。</p>

<p>今年は YotioSoft 開設 12 周年です。12年目も変わらず活動を続けていきますので、よろしくお願いいたします。</p>]]></content><author><name></name></author><category term="雑談" /><summary type="html"><![CDATA[遅ればせながらあけましておめでとうございます。2026年もよろしくお願いいたします。 写真は羽田沖で撮影した今年の初日の出です。今年の元旦は雲が多く、7時すぎにようやく太陽が姿を表してくれました。]]></summary></entry><entry><title type="html">Rust向け自作アクセス権制御ライブラリ「mprotect-rs」の紹介</title><link href="https://blog.yotio.jp/2025/12/24/mprotect-rs.html" rel="alternate" type="text/html" title="Rust向け自作アクセス権制御ライブラリ「mprotect-rs」の紹介" /><published>2025-12-24T00:00:00+09:00</published><updated>2025-12-24T00:00:00+09:00</updated><id>https://blog.yotio.jp/2025/12/24/mprotect-rs</id><content type="html" xml:base="https://blog.yotio.jp/2025/12/24/mprotect-rs.html"><![CDATA[<p>メモリークリスマス！
<a href="/2025/12/14/intel-mpk.html">前回</a>に引き続き、今回も Intel MPK/PKU 関連のお話です。
自分はハードウェア支援のメモリ安全性やアクセス制御に興味があり、昨今、Rust 向けにこんなライブラリを試作しています。</p>

<ul>
  <li><a href="https://github.com/yotiosoft/mprotect-rs" target="_blank">yotiosoft/mprotect-rs: An implementation of mprotect() and pkey_mprotect() for Rust. This enables Rust to set access rights to each pages, using PTE flags or Intel MPK (Memory Protection Keys).</a></li>
</ul>

<p>まだまだ開発途中で未完成ですが、今回はこのライブラリの簡単な紹介と、今後目指す理想像についてお話したいと思います。</p>

<!--more-->

<h1 id="実現したいこと">実現したいこと</h1>

<p>このライブラリで実現したいことは、ざっくりまとめるとこんな感じです。</p>

<ul>
  <li>ユーザ空間アプリケーションのアクセス権制御（read/write アクセス権の設定）を実現したい</li>
  <li>もし不正なアクセス（read-only なメモリ領域への write アクセスなど）が起きたら、それを未然に防止する仕組みを実現したい</li>
  <li>以上の操作を Rust で簡単に実現できるようにしたい</li>
</ul>

<p><img src="../../../assets/img/post/2025-12-24-mprotect-rs/rw_251224.svg" alt="rw_251224" style="zoom:50%;" /></p>

<h1 id="目的とアプローチ">目的とアプローチ</h1>

<p>このライブラリの目的は、Linux 環境の Rust 製ユーザアプリケーションに <code class="language-plaintext highlighter-rouge">mprotect()</code> や <code class="language-plaintext highlighter-rouge">mprotect_pkey()</code> といったハードウェア支援のアクセス制御を容易に導入できるようにし、不正なメモリアクセスを防止することにあります。</p>

<p>Rust をご存知の方は、こんなことを疑問に思ったかもしれません。「Rust って既にメモリ安全言語じゃないか」と。
その疑問は正しいです。ただ、Rust のメモリ安全性はあくまでもコンパイル時点でコンパイラによって担保できる範囲内であり、unsafe code やレガシー言語による外部ライブラリなど、rustc による検証が及ばない範囲もあります。</p>

<p>そこで、機密データを置くメモリ領域をあらかじめ保護メモリドメインに設定しておき、正しい safe code が実行されている間だけアクセスを許可することで、仮に unsafe code や外部ライブラリでバグにより脆弱性が発生したとしても、その脆弱性の悪用、あるいはバグにより意図せず生じる不正なメモリアクセスをハードウェア的に防止するための仕組みが重要になると考えています。</p>

<p>同じような思想は、EuroSys’22 で発表されたこちらの「PKRU-safe」という論文にも現れています。こちらの論文では、レガシーな言語（C など）で書かれた外部ライブラリを Untrusted memory として Trusted memory から切り離してしまおうというアプローチを取っており、その実現に Intel PKS を利用しています。</p>

<ul>
  <li><a href="https://dl.acm.org/doi/10.1145/3492321.3519582" target="_blank">PKRU-safe | Proceedings of the Seventeenth European Conference on Computer Systems</a></li>
</ul>

<p><code class="language-plaintext highlighter-rouge">mprotect()</code> と <code class="language-plaintext highlighter-rouge">mprotect_pkey()</code> の違いは、前者はカーネルが PTE (Page Table Entry) のアクセス権限を更新することでアクセス制御を実現するのに対し、後者は Intel PKU といった PTE 更新を必要としないハードウェア機能を使ってアクセス制御を実現します。
Intel x86_64 アーキテクチャの場合、<code class="language-plaintext highlighter-rouge">mprotect_pkey()</code> には Intel PKU が利用されます。Intel PKU では、ユーザモードで PKRU というレジスタを更新することでアクセス制御を実現します。
Intel PKU はハードウェア依存で x86_64 の Skylake 世代以降のみ利用可能ですが、アクセス制御にカーネルの介入や PTE 更新が必要ない分、より高速なアクセス制御を実現できます。</p>

<h2 id="intel-mpk--intel-pku-とは">Intel MPK / Intel PKU とは</h2>

<p>Intel x86_64 アーキテクチャで提供されている、ハードウェアレベルのメモリ保護機能です。</p>

<p>Intel MPK にはユーザ空間向けの Intel PKU (Protection Keys for Userspace) とカーネル空間向けの Intel PKS (Protection Keys for supervisor) があり、今回は前者の Intel PKU を扱います。
詳しくは前回の記事をご覧ください。</p>

<ul>
  <li><a href="https://blog.yotio.jp/2025/12/14/intel-mpk.html">x86_64のメモリ保護機能「Intel MPK」で遊ぼう | 為せばnull</a></li>
</ul>

<h2 id="x86_64-以外のアーキテクチャについて">x86_64 以外のアーキテクチャについて</h2>

<ul>
  <li><a href="https://docs.kernel.org/core-api/protection-keys.html" target="_blank">Memory Protection Keys — The Linux Kernel documentation</a></li>
</ul>

<p>によれば、Arm では FEAT_S1POE という機能がバックエンドとして利用されるようです。AMD に関しては future works といったところでしょうか。</p>

<p>今回は x86_64 の Intel PKU 前提で話を進めている点、ご承知おきください。</p>

<h1 id="実現したこと">実現したこと</h1>

<h2 id="コンパイラによるアクセス正当性チェックを実現">コンパイラによるアクセス正当性チェックを実現</h2>

<p>別に <code class="language-plaintext highlighter-rouge">mprotect()</code> や <code class="language-plaintext highlighter-rouge">mprotect_pkey()</code> (Intel PKU) は、ライブラリ無しでも利用できます。前者は Linux システムコールですし、後者は非特権 CPU 命令です。
ですが、<code class="language-plaintext highlighter-rouge">mprotect()</code> や Intel PKU はある意味では安全であり、ある意味では危険です。「安全」な点は、メモリ安全、つまり不正なアクセスをトラップして防止できるという点にありますが、「危険」な点は、実際に不正なアクセスが発生してしまうと、プロセスが Segmentation fault を起こしてクラッシュするという点です。</p>

<p>これはフェイルセーフの観点から正しい動作ではありますが、アプリケーションとしてはクラッシュするのは極力避けたい側面もあります。もしコンパイル段階で静的解析によって safe code 内に不正なアクセス操作があること、例えば、read-only アクセスのメモリ領域に write しようとしているコードがあることが分かっているのであれば、コンパイル段階でコンパイルエラーとして扱った方が嬉しいでしょう。</p>

<p>ですので、mprotect-rs では借用チェックやトレイトを活用し、「read-only で宣言したスマートポインタは、read アクセスしかできない」「read-write で宣言したスマートポインタは、read/write アクセスが可能」といったように、ポインタレベルで read/write アクセスを制御し、違反するアクセスに関しては rustc が検知できるようにしています。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="c1">// mutable な参照を取得し値を書き込む. read/write 可</span>
    <span class="p">{</span>
        <span class="c1">// 参照を取得</span>
        <span class="c1">// このとき、Protection Key のアクセス権が ReadWrite に変更され、RegionGuard への read/write アクセスが許可される</span>
        <span class="k">let</span> <span class="n">write_guard</span> <span class="o">=</span> <span class="n">associated_region</span><span class="py">.set_access_rights</span><span class="p">::</span><span class="o">&lt;</span><span class="nn">PkeyPermissions</span><span class="p">::</span><span class="n">ReadWrite</span><span class="o">&gt;</span><span class="p">()</span><span class="nf">.map_err</span><span class="p">(</span><span class="nn">RuntimeError</span><span class="p">::</span><span class="n">MprotectError</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">mut_ref_guard</span> <span class="o">=</span> <span class="n">write_guard</span><span class="nf">.mut_ref_guard</span><span class="p">()</span><span class="nf">.map_err</span><span class="p">(|</span><span class="n">e</span><span class="p">|</span> <span class="nn">RuntimeError</span><span class="p">::</span><span class="nf">PkeyGuardError</span><span class="p">(</span><span class="n">e</span><span class="p">))</span><span class="o">?</span><span class="p">;</span>
        <span class="c1">// write</span>
        <span class="o">*</span><span class="n">mut_ref_guard</span> <span class="o">=</span> <span class="mi">123</span><span class="p">;</span>
        <span class="c1">// read</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"Value written via associated region deref(): {}"</span><span class="p">,</span> <span class="o">*</span><span class="n">mut_ref_guard</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="c1">// immutable な参照を取得し値を読む. read のみ可</span>
    <span class="p">{</span>
        <span class="c1">// 参照を取得</span>
        <span class="c1">// このとき、Protection Key のアクセス権が ReadOnly に変更され、RegionGuard への read アクセスのみが許可される</span>
        <span class="k">let</span> <span class="n">read_guard</span> <span class="o">=</span> <span class="n">associated_region</span><span class="py">.set_access_rights</span><span class="p">::</span><span class="o">&lt;</span><span class="nn">PkeyPermissions</span><span class="p">::</span><span class="n">ReadOnly</span><span class="o">&gt;</span><span class="p">()</span><span class="nf">.map_err</span><span class="p">(</span><span class="nn">RuntimeError</span><span class="p">::</span><span class="n">MprotectError</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">ref_guard</span> <span class="o">=</span> <span class="n">read_guard</span><span class="nf">.ref_guard</span><span class="p">()</span><span class="nf">.map_err</span><span class="p">(|</span><span class="n">e</span><span class="p">|</span> <span class="nn">RuntimeError</span><span class="p">::</span><span class="nf">PkeyGuardError</span><span class="p">(</span><span class="n">e</span><span class="p">))</span><span class="o">?</span><span class="p">;</span>
        <span class="c1">// read</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"Value read via associated region deref(): {}"</span><span class="p">,</span> <span class="o">*</span><span class="n">ref_guard</span><span class="p">);</span>
        
        <span class="c1">// write は borrow checker によりコンパイルエラーになる（ここ重要）</span>
        <span class="c1">//*ref_guard = 456;</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>具体的には、それぞれ read 操作しかトレイト実装していないスマートポインタを <code class="language-plaintext highlighter-rouge">&amp;T</code> で、read 操作と write 操作をトレイト実装したスマートポインタを <code class="language-plaintext highlighter-rouge">&amp;mut T</code> で返すようにしています。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// read-only で返すスマートポインタ (GuardRef)</span>
<span class="k">impl</span><span class="o">&lt;</span><span class="nv">'a</span><span class="p">,</span> <span class="n">A</span><span class="p">:</span> <span class="nn">allocator</span><span class="p">::</span><span class="n">Allocator</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">T</span><span class="o">&gt;</span> <span class="n">Deref</span> <span class="k">for</span> <span class="n">GuardRef</span><span class="o">&lt;</span><span class="nv">'a</span><span class="p">,</span> <span class="n">A</span><span class="p">,</span> <span class="n">T</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">type</span> <span class="n">Target</span> <span class="o">=</span> <span class="n">T</span><span class="p">;</span>
    <span class="cd">/// Dereferences the guarded reference if valid, panicking otherwise.</span>
    <span class="k">fn</span> <span class="nf">deref</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">&amp;</span><span class="k">Self</span><span class="p">::</span><span class="n">Target</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">self</span><span class="nf">.is_valid</span><span class="p">()</span> <span class="p">{</span>
            <span class="o">&amp;*</span><span class="k">self</span><span class="py">.ptr</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nd">panic!</span><span class="p">(</span><span class="s">"Failed to deref GuardRef: invalid generation"</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// read/write で返すスマートポインタ (GuardRefMut)</span>
<span class="k">impl</span><span class="o">&lt;</span><span class="nv">'a</span><span class="p">,</span> <span class="n">A</span><span class="p">:</span> <span class="nn">allocator</span><span class="p">::</span><span class="n">Allocator</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">T</span><span class="o">&gt;</span> <span class="n">Deref</span> <span class="k">for</span> <span class="n">GuardRefMut</span><span class="o">&lt;</span><span class="nv">'a</span><span class="p">,</span> <span class="n">A</span><span class="p">,</span> <span class="n">T</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">type</span> <span class="n">Target</span> <span class="o">=</span> <span class="n">T</span><span class="p">;</span>
    <span class="k">fn</span> <span class="nf">deref</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">&amp;</span><span class="k">Self</span><span class="p">::</span><span class="n">Target</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">self</span><span class="nf">.is_valid</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">unsafe</span> <span class="p">{</span> <span class="o">&amp;*</span><span class="k">self</span><span class="py">.ptr</span> <span class="p">}</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nd">panic!</span><span class="p">(</span><span class="s">"Failed to deref GuardRefMut: invalid generation"</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="k">impl</span><span class="o">&lt;</span><span class="nv">'a</span><span class="p">,</span> <span class="n">A</span><span class="p">:</span> <span class="nn">allocator</span><span class="p">::</span><span class="n">Allocator</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">T</span><span class="o">&gt;</span> <span class="n">DerefMut</span> <span class="k">for</span> <span class="n">GuardRefMut</span><span class="o">&lt;</span><span class="nv">'a</span><span class="p">,</span> <span class="n">A</span><span class="p">,</span> <span class="n">T</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">deref_mut</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="k">Self</span><span class="p">::</span><span class="n">Target</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">self</span><span class="nf">.is_valid</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">unsafe</span> <span class="p">{</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="o">*</span><span class="k">self</span><span class="py">.ptr</span> <span class="p">}</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nd">panic!</span><span class="p">(</span><span class="s">"Failed to deref_mut GuardRefMut: invalid generation"</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>もし read-only で参照を取得した場合、immutable なスマートポインタしか実装されていませんので、write 操作しようとすると下の画像のようにコンパイルエラーになります。</p>

<p><img src="../../../assets/img/post/2025-12-24-mprotect-rs/image-20251224003858351.webp" alt="image-20251224003858351" /></p>

<p>これによって、コンパイル段階で <code class="language-plaintext highlighter-rouge">mprotect()</code> や Intel PKU による Segmentation fault の発生を予防できるわけですね。</p>

<p>では、コンパイラで事前に検知できるのなら <code class="language-plaintext highlighter-rouge">mprotect()</code> や Intel PKU がいらないのではないか？といえばそんなことはありません。unsafe code や、C言語などで書かれた外部依存ライブラリに不正にアクセスできてしまうコードが含まれていた場合、それらは rustc による安全性チェックが行われませんので、unsafe code での不正アクセス発生時はやむを得ず <code class="language-plaintext highlighter-rouge">mprotect()</code> や Intel PKU によって Segmentation fault を起こすようにしています。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="c1">// immutable な参照を取得し値を読む. read のみ可</span>
    <span class="p">{</span>
        <span class="c1">// 参照を取得</span>
        <span class="c1">// このとき、Protection Key のアクセス権が ReadOnly に変更され、RegionGuard への read アクセスのみが許可される</span>
        <span class="k">let</span> <span class="n">read_guard</span> <span class="o">=</span> <span class="n">associated_region</span><span class="py">.set_access_rights</span><span class="p">::</span><span class="o">&lt;</span><span class="nn">PkeyPermissions</span><span class="p">::</span><span class="n">ReadOnly</span><span class="o">&gt;</span><span class="p">()</span><span class="nf">.map_err</span><span class="p">(</span><span class="nn">RuntimeError</span><span class="p">::</span><span class="n">MprotectError</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">ref_guard</span> <span class="o">=</span> <span class="n">read_guard</span><span class="nf">.ref_guard</span><span class="p">()</span><span class="nf">.map_err</span><span class="p">(|</span><span class="n">e</span><span class="p">|</span> <span class="nn">RuntimeError</span><span class="p">::</span><span class="nf">PkeyGuardError</span><span class="p">(</span><span class="n">e</span><span class="p">))</span><span class="o">?</span><span class="p">;</span>
        <span class="c1">// read</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"Value read via associated region deref(): {}"</span><span class="p">,</span> <span class="o">*</span><span class="n">ref_guard</span><span class="p">);</span>
        <span class="c1">// write は borrow checker によりコンパイルエラーになる</span>
        <span class="c1">//*ref_guard = 456;</span>

        <span class="c1">// unsafe code で無理やり mutable な参照を取得しようとすると、実行時に segmentation fault になる</span>
        <span class="k">unsafe</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">mut_ref</span> <span class="o">=</span> <span class="n">ref_guard</span><span class="nf">.ptr</span><span class="p">()</span> <span class="k">as</span> <span class="o">*</span><span class="k">const</span> <span class="nb">u32</span> <span class="k">as</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">u32</span><span class="p">;</span>
            <span class="nd">println!</span><span class="p">(</span><span class="s">"Attempt to write via unsafe mutable reference..."</span><span class="p">);</span>
            <span class="o">*</span><span class="n">mut_ref</span> <span class="o">=</span> <span class="mi">789</span><span class="p">;</span>  <span class="c1">// ここで segmentation fault になる</span>
        <span class="p">}</span>
</code></pre></div></div>

<p>こちらのコードでは、read-only アクセスに向けて immutable で取得した参照に対して、unsafe code で無理やり値を書き込もうとしています。</p>

<p><img src="../../../assets/img/post/2025-12-24-mprotect-rs/image-20251224005614578.webp" alt="image-20251224005614578" /></p>

<p>コンパイルによる検証が実行されないのでコンパイル自体は通りますが、実行結果は、もちろん Segmentation fault です。不正なアクセスが Intel PKU によって防止できていることが確認できますね。</p>

<p>まとめると、</p>

<ul>
  <li>safe code 内で起きる不正なアクセスはコンパイル段階で検知しよう</li>
  <li>unsafe code や 外部ライブラリの FFI などで起きうる不正なアクセスは <code class="language-plaintext highlighter-rouge">mprotect()</code> や Intel PKU に任せよう</li>
</ul>

<p>という設計方針です。</p>

<h2 id="システムコールを呼び出さないアクセス制御を実現intel-pku-のみ">システムコールを呼び出さないアクセス制御を実現（Intel PKU のみ）</h2>

<p><code class="language-plaintext highlighter-rouge">mprotect()</code> はシステムコール呼び出しが必要、かつその都度 PTE を更新しなければならないので、それなりにランタイムオーバーヘッドがかさみます。
一方、Intel PKU はユーザモードで PKRU レジスタを更新するだけでアクセス権の変更が完了しますので、カーネルの介入、PTE の更新によるランタイムオーバーヘッドは生じません。</p>

<p>mprotect-rs では、この特徴を最大限に生かすために、Intel PKU のアクセス制御で済む操作はユーザ側で完結するような設計にしています。
Intel PKU では、各メモリ領域に対して最低でも一度は <code class="language-plaintext highlighter-rouge">mprotect_pkey()</code> システムコールの呼び出しが必要です。このシステムコールは PTE と Protection Key を紐づけるために呼び出す操作です。一方で、一度紐づければそれ以降はユーザ空間で PKRU レジスタを更新すればアクセス制御が完了できます。この特性を最大限に活かすべく、アクセス制御の度に <code class="language-plaintext highlighter-rouge">mprotect_pkey()</code> を呼び出さなくて済むよう、PKEY との紐づけは一度だけ実施し、それ以降は PKRU レジスタを書き換えるだけでアクセス権を変更できるようにしています。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// mprotect で確保したメモリ領域を持つ RegionGuard を生成</span>
<span class="c1">// デフォルトアクセス権は ReadWrite. 後に Intel PKU によりアクセス権を制御する</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">region</span> <span class="o">=</span> <span class="nn">RegionGuard</span><span class="p">::</span><span class="o">&lt;</span><span class="nn">allocator</span><span class="p">::</span><span class="n">Mmap</span><span class="p">,</span> <span class="nb">u32</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">AccessPermissions</span><span class="p">::</span><span class="n">ReadWrite</span><span class="p">)</span><span class="nf">.map_err</span><span class="p">(</span><span class="nn">RuntimeError</span><span class="p">::</span><span class="n">MprotectError</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
<span class="c1">// Intel PKU の Protection Key を生成</span>
<span class="k">let</span> <span class="n">pkey</span> <span class="o">=</span> <span class="nn">PkeyGuard</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">PkeyPermissions</span><span class="p">::</span><span class="n">NoAccess</span><span class="p">)</span><span class="nf">.map_err</span><span class="p">(</span><span class="nn">RuntimeError</span><span class="p">::</span><span class="n">MprotectError</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
<span class="c1">// RegionGuard と Protection Key を関連付ける</span>
<span class="c1">// 以降、RegionGuard のアクセス権は Protection Key により制御される</span>
<span class="c1">// 初期状態は NoAccess (-/-). アクセス不可</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">associated_region</span> <span class="o">=</span> <span class="n">pkey</span><span class="py">.associate</span><span class="p">::</span><span class="o">&lt;</span><span class="nn">PkeyPermissions</span><span class="p">::</span><span class="n">NoAccess</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">region</span><span class="p">)</span><span class="nf">.map_err</span><span class="p">(</span><span class="nn">RuntimeError</span><span class="p">::</span><span class="n">MprotectError</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

<span class="o">...</span>

<span class="c1">// mutable な参照を取得</span>
<span class="c1">// このとき、Protection Key のアクセス権が ReadWrite に変更され、RegionGuard への read/write アクセスが許可される</span>
<span class="k">let</span> <span class="n">write_guard</span> <span class="o">=</span> <span class="n">associated_region</span><span class="py">.set_access_rights</span><span class="p">::</span><span class="o">&lt;</span><span class="nn">PkeyPermissions</span><span class="p">::</span><span class="n">ReadWrite</span><span class="o">&gt;</span><span class="p">()</span><span class="nf">.map_err</span><span class="p">(</span><span class="nn">RuntimeError</span><span class="p">::</span><span class="n">MprotectError</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

<span class="o">...</span>

<span class="c1">// immutable な参照を取得</span>
<span class="c1">// 参照を取得</span>
<span class="c1">// このとき、Protection Key のアクセス権が ReadOnly に変更され、RegionGuard への read アクセスのみが許可される</span>
<span class="k">let</span> <span class="n">read_guard</span> <span class="o">=</span> <span class="n">associated_region</span><span class="py">.set_access_rights</span><span class="p">::</span><span class="o">&lt;</span><span class="nn">PkeyPermissions</span><span class="p">::</span><span class="n">ReadOnly</span><span class="o">&gt;</span><span class="p">()</span><span class="nf">.map_err</span><span class="p">(</span><span class="nn">RuntimeError</span><span class="p">::</span><span class="n">MprotectError</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</code></pre></div></div>

<p>内部的には、<code class="language-plaintext highlighter-rouge">set_access_rights()</code> で PKRU レジスタを更新します。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">impl</span><span class="o">&lt;</span><span class="nv">'a</span><span class="p">,</span> <span class="nv">'p</span><span class="p">,</span> <span class="n">A</span><span class="p">:</span> <span class="nn">allocator</span><span class="p">::</span><span class="n">Allocator</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">T</span><span class="p">,</span> <span class="n">Rights</span><span class="o">&gt;</span> <span class="n">AssociatedRegionHandler</span><span class="o">&lt;</span><span class="nv">'p</span><span class="p">,</span> <span class="n">A</span><span class="p">,</span> <span class="n">T</span><span class="p">,</span> <span class="n">Rights</span><span class="o">&gt;</span>
<span class="k">where</span> 
    <span class="n">Rights</span><span class="p">:</span> <span class="nn">access_rights</span><span class="p">::</span><span class="n">Access</span><span class="p">,</span>
<span class="p">{</span>
    <span class="o">...</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="n">set_access_rights</span><span class="o">&lt;</span><span class="n">NewRights</span><span class="p">:</span> <span class="nn">access_rights</span><span class="p">::</span><span class="n">Access</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="nv">'a</span> <span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="n">AssociatedRegion</span><span class="o">&lt;</span><span class="nv">'a</span><span class="p">,</span> <span class="n">A</span><span class="p">,</span> <span class="n">T</span><span class="p">,</span> <span class="n">NewRights</span><span class="o">&gt;</span><span class="p">,</span> <span class="k">super</span><span class="p">::</span><span class="n">MprotectError</span><span class="o">&gt;</span> 
    <span class="k">where</span>
        <span class="n">NewRights</span><span class="p">:</span> <span class="nn">access_rights</span><span class="p">::</span><span class="n">Access</span><span class="p">,</span>
    <span class="p">{</span>
        <span class="c1">// Apply new hardware access rights via PKRU</span>
        <span class="k">unsafe</span> <span class="p">{</span>
            <span class="k">self</span><span class="py">.pkey_guard.pkey</span><span class="nf">.set_access_rights</span><span class="p">(</span><span class="nn">NewRights</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span><span class="nf">.value</span><span class="p">())</span><span class="o">?</span><span class="p">;</span>  <span class="c1">// ここで PKRU を更新</span>
        <span class="p">}</span>
        <span class="o">...</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="o">...</span>

<span class="c1">// PKRU レジスタを更新</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">set_access_rights</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">access</span><span class="p">:</span> <span class="n">PkeyAccessRights</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="k">super</span><span class="p">::</span><span class="n">MprotectError</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">pkru_value</span> <span class="o">=</span> <span class="nn">pkru</span><span class="p">::</span><span class="nf">rdpkru</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">new_pkru_bits</span> <span class="o">=</span> <span class="k">match</span> <span class="n">access</span> <span class="p">{</span>
        <span class="nn">PkeyAccessRights</span><span class="p">::</span><span class="n">EnableAccessWrite</span> <span class="k">=&gt;</span> <span class="mi">0b00</span><span class="p">,</span>
        <span class="nn">PkeyAccessRights</span><span class="p">::</span><span class="n">DisableAccess</span> <span class="k">=&gt;</span> <span class="mi">0b01</span><span class="p">,</span>
        <span class="nn">PkeyAccessRights</span><span class="p">::</span><span class="n">DisableWrite</span> <span class="k">=&gt;</span> <span class="mi">0b10</span><span class="p">,</span>
    <span class="p">}</span> <span class="o">&lt;&lt;</span> <span class="p">(</span><span class="k">self</span><span class="py">.key</span> <span class="o">*</span> <span class="mi">2</span><span class="p">);</span>

    <span class="k">let</span> <span class="n">new_pkru_value</span> <span class="o">=</span> <span class="n">pkru_value</span> <span class="o">&amp;</span> <span class="o">!</span><span class="p">(</span><span class="mi">0b11</span> <span class="o">&lt;&lt;</span> <span class="p">(</span><span class="k">self</span><span class="py">.key</span> <span class="o">*</span> <span class="mi">2</span><span class="p">))</span> <span class="p">|</span> <span class="n">new_pkru_bits</span><span class="p">;</span>
    <span class="nn">pkru</span><span class="p">::</span><span class="nf">wrpkru</span><span class="p">(</span><span class="n">new_pkru_value</span><span class="p">);</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="入れ子スコープに対応">入れ子スコープに対応</h2>

<p>スコープごとにアクセス制御できるような設計にしています。つまり、現在のアクセス権は現在のスコープ内でのみ有効であり、スコープから出たらアクセス権は無効になります。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
    <span class="c1">// ここでは read/write 可</span>
    <span class="k">let</span> <span class="n">assoc_rw</span> <span class="o">=</span> <span class="n">assoc_for_mem</span><span class="py">.set_access_rights</span><span class="p">::</span><span class="o">&lt;</span><span class="nn">PkeyPermissions</span><span class="p">::</span><span class="n">ReadWrite</span><span class="o">&gt;</span><span class="p">()</span><span class="nf">.map_err</span><span class="p">(</span><span class="nn">RuntimeError</span><span class="p">::</span><span class="n">MprotectError</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">value</span> <span class="o">=</span> <span class="n">assoc_rw</span><span class="nf">.mut_ref_guard</span><span class="p">()</span><span class="nf">.map_err</span><span class="p">(</span><span class="nn">RuntimeError</span><span class="p">::</span><span class="n">PkeyGuardError</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"</span><span class="se">\t</span><span class="s">Value read via associated region deref(): {}"</span><span class="p">,</span> <span class="o">*</span><span class="n">value</span><span class="p">);</span>
    <span class="p">{</span>
        <span class="c1">// ここでは read-only</span>
        <span class="k">let</span> <span class="n">assoc2_r</span> <span class="o">=</span> <span class="n">assoc2_for_mem2</span><span class="py">.set_access_rights</span><span class="p">::</span><span class="o">&lt;</span><span class="nn">PkeyPermissions</span><span class="p">::</span><span class="n">ReadOnly</span><span class="o">&gt;</span><span class="p">()</span><span class="nf">.map_err</span><span class="p">(</span><span class="nn">RuntimeError</span><span class="p">::</span><span class="n">MprotectError</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">value</span> <span class="o">=</span> <span class="n">assoc2_r</span><span class="nf">.ref_guard</span><span class="p">()</span><span class="nf">.map_err</span><span class="p">(</span><span class="nn">RuntimeError</span><span class="p">::</span><span class="n">PkeyGuardError</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"</span><span class="se">\t</span><span class="s">Value read via second associated region deref(): {}"</span><span class="p">,</span> <span class="o">*</span><span class="n">value</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="c1">// ここから再び read/write 可</span>
    <span class="o">*</span><span class="n">value</span> <span class="o">=</span> <span class="mi">168</span><span class="p">;</span>  <span class="c1">// ok</span>
<span class="p">}</span>
<span class="c1">// ここからは read も write も不可</span>
</code></pre></div></div>

<p>入れ子状態のスコープに対応するために、アクセス権の変遷はメモリ領域ごとにスタックで管理しています。子のスコープから親のスコープに戻ったら、スタックを pull してアクセス権を親スコープの状態に戻すような設計です。</p>

<h1 id="今後の展望">今後の展望</h1>

<p>現状、<code class="language-plaintext highlighter-rouge">mprotect()</code> を使う場合と <code class="language-plaintext highlighter-rouge">mprotect_pkey()</code> を使う場合とで異なるトレイト、異なる API 体系を利用する形になっています。しかし、実際の利用環境を考えると、必ずしもバイナリの配布先が Intel PKU をはじめ <code class="language-plaintext highlighter-rouge">mprotect_pkey()</code> に対応しているという保証はありません。古い Intel CPU で実行されているかもしれませんし、Intel PKU が無効化された環境かもしれませんし、あるいは <code class="language-plaintext highlighter-rouge">mprotect_pkey()</code> を利用できない別のアーキテクチャかもしれません。</p>

<p>その場合に、<code class="language-plaintext highlighter-rouge">mprotect_pkey()</code> の対応チェックを実施し、未対応なら <code class="language-plaintext highlighter-rouge">mprotect()</code> を使いたい、というケースもあるでしょう。こういったユースケースに対応するため、<code class="language-plaintext highlighter-rouge">mprotect()</code> を使う場合と Intel PKU を使う場合とで統一のインターフェイスを利用できるようにしたいと考えています。</p>

<p>まだまだこのライブラリは未完成です。来年中には一旦完成させてリリースしたいなと考えています。</p>]]></content><author><name></name></author><category term="x86_64" /><category term="Linux" /><category term="Ubuntu" /><category term="Rust" /><category term="CPU" /><summary type="html"><![CDATA[メモリークリスマス！ 前回に引き続き、今回も Intel MPK/PKU 関連のお話です。 自分はハードウェア支援のメモリ安全性やアクセス制御に興味があり、昨今、Rust 向けにこんなライブラリを試作しています。 yotiosoft/mprotect-rs: An implementation of mprotect() and pkey_mprotect() for Rust. This enables Rust to set access rights to each pages, using PTE flags or Intel MPK (Memory Protection Keys). まだまだ開発途中で未完成ですが、今回はこのライブラリの簡単な紹介と、今後目指す理想像についてお話したいと思います。]]></summary></entry><entry><title type="html">x86_64のメモリ保護機能「Intel MPK」で遊ぼう</title><link href="https://blog.yotio.jp/2025/12/14/intel-mpk.html" rel="alternate" type="text/html" title="x86_64のメモリ保護機能「Intel MPK」で遊ぼう" /><published>2025-12-14T00:00:00+09:00</published><updated>2025-12-14T00:00:00+09:00</updated><id>https://blog.yotio.jp/2025/12/14/intel-mpk</id><content type="html" xml:base="https://blog.yotio.jp/2025/12/14/intel-mpk.html"><![CDATA[<p>今回はハードウェアレベルでメモリ保護を実現する Intel MPK (Memory Protection Keys) のユーザ空間向けの機能、Intel PKU (Protection Keys for Userspace) で遊んでみたという内容になります。</p>

<p>Intel PKU を使うと、ユーザ空間内に複数個のサンドボックスを作成でき、なおかつハードウェア制御により高速なアクセス制御が可能になります。Intel PKU は登場してから10年たった今でも論文などで様々な利用方法が提案されている非常に奥の深い機能なんですが（あまり実用例は聞かないけど）、今回は導入編として、ゆるく触ってみる程度にしたいと思います。</p>

<!--more-->

<h1 id="背景">背景</h1>

<p>C言語をはじめ、非メモリ安全な言語でプログラミングをやっていると付き物なのが、メモリリークやスタックオーバーフローをはじめとするメモリ脆弱性です。コンピュータが登場してから数十年たった今もなお頻発しているバグであり、Linux カーネルや各種ライブラリをはじめ、多くのソフトウェアで脆弱性の第一要因になっています。</p>

<p>近年は Rust をはじめとするコンパイラレベルのメモリ安全な言語が出て、メモリ脆弱性を引き起こすリスクは大幅に減りました。しかし、Rust で書かれたソフトウェアも結局はC言語などといったレガシーな言語で書かれたライブラリを FFI としてインポートしていたり、FFI や低レイヤプログラミングにおいて unsafe code を書かなければならなかったりして、メモリ脆弱性から完全に逃れられるわけではありません。</p>

<h2 id="メモリ隔離技術">メモリ隔離技術</h2>

<p>そこで大事になってくるのが、間違ったメモリにアクセスしてしまったら即座に動作を停止させることでメモリ脆弱性を未然に防止する策、いわば「フェイルセーフな設計」です。あらかじめプログラムがアクセスできるメモリ領域を限定（アクセス制御）しておき、領域外にアクセスしたらアクセスをトラップさせます。いわゆる「メモリ分離技術」などと呼ばれる手法です。</p>

<p>メモリ分離技術の方式はいくつかあり、有名なものだと「SFI (Software Fault Isolation)」「コンテナ」「VM」があります。コンテナと VM は説明不要と思います。SFI は一つのアドレス空間内でソフトウェアレベルでアクセス制御をする手法です。</p>

<p>対して、今回紹介する Intel MPK はハードウェアレベルでアクセス制御を行います。ハードウェア、すなわち CPU や MMU によるメモリ保護機能を利用して、一つのアドレス空間内に複数のサンドボックスを作成します。主な特長は2つあり、1つはユーザ空間内に複数個のサンドボックスを作成できるので高速なアクセス制御が可能であること、もう1つはハードウェアレベルでの強力なアクセス制御が利用できることが挙げられます。</p>

<h1 id="intel-mpk">Intel MPK</h1>

<p>ではハードウェアメモリ保護を利用するにはどうすれば良いのでしょうか？</p>

<p>ハードウェアメモリ保護機能は基本的にハードウェア依存です。Intel アーキテクチャには Intel アーキテクチャ用のものが、Arm アーキテクチャには Arm アーキテクチャ用のものがあります。</p>

<p>今回は Intel 向けのハードウェアメモリ保護である「<strong>Intel MPK (Memory Protection Keys)</strong>」を扱います。正確には、ユーザ空間向けの「<strong>Intel PKU (Protection Keys for Userspace)</strong>」です。近年の Intel CPU（2015年発売の Skylake 世代移行）であれば基本的に搭載されている機能ですので、実は Intel CPU であれば気軽に遊べます。</p>

<h2 id="protection-key">Protection Key</h2>

<p>Intel PKU では「Protection Key (PKEY)」と呼ばれるメモリドメインが16個提供されています。メモリドメインとはアクセス制御の単位です。</p>

<p>例えば、あるメモリ領域 0x10000000～0x11000000 に対して Protection key（仮に PKEY 1）を割り当てたとき、そのメモリ領域を一括で read-only にしたり、read/write を許可したり、read/write のいずれも禁止したりすることができます。もし read/write 禁止にしたうえで 0x10000000 のメモリオブジェクトへのアクセスしようとした場合、メモリアクセス命令が実行される段階でハードウェアが実行をトラップし、不正なメモリアクセスを未然に防止することができるわけです。もちろん、バッファオーバーフローや out-of-bounds access などにも有効ですし、それらのメモリ脆弱性を突いた攻撃による影響を緩和できます（コードを書き換える攻撃など、完全に防げるわけではないことに注意）。</p>

<p><img src="../../../assets/img/post/2025-12-14-intel-mpk/mpk1.svg" alt="mpk1" style="zoom:50%;" /></p>

<p>Intel PKU の良いところは、メモリドメイン1つ1つが連続領域である必要がないという点です。上の図のように、離れたメモリ領域同士を同じ Protection Key に設定してメモリドメインを生成できます。また、仮にメモリ領域が離れていたとしても、それによってアクセス権限変更のためのオーバーヘッドが増加することはありません。</p>

<h3 id="protection-key-の設定方法">Protection Key の設定方法</h3>

<p>Protection Key を任意のメモリ領域に設定するには、Linux であれば <code class="language-plaintext highlighter-rouge">pkey_mprotect()</code> システムコールで簡単に設定できます。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pkey_mprotect</span><span class="p">(</span><span class="n">pointer</span><span class="p">,</span> <span class="n">size</span><span class="p">,</span> <span class="n">PROT_READ</span> <span class="o">|</span> <span class="n">PROT_WRITE</span><span class="p">,</span> <span class="n">pkey</span><span class="p">)</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">pkey_mprotect()</code> システムコールは、PTE のアクセス権フラグを設定する <code class="language-plaintext highlighter-rouge">mprotect()</code> システムコールに引数 <code class="language-plaintext highlighter-rouge">pkey</code> を加えたものです。ちょっと紛らわしいんですが、 <code class="language-plaintext highlighter-rouge">PROT_READ | PROT_WRITE</code> のところは <code class="language-plaintext highlighter-rouge">mprotect()</code> 由来の PTE フラグの設定で、Intel MPK とは関係ありません。Intel MPK に関連のあるところで説明すると、上記の例ではポインタ <code class="language-plaintext highlighter-rouge">pointer</code> が指しているアドレスから <code class="language-plaintext highlighter-rouge">size</code> 分のメモリ領域に <code class="language-plaintext highlighter-rouge">pkey</code> 番目の Protection Key を紐づけています。</p>

<h2 id="アクセス権の変更">アクセス権の変更</h2>

<p>Intel PKU ではアクセス権の変更は Protection Key ごとに行います。Preotection Key に紐づけられた各メモリ領域は、自身の PTE フラグのアクセス権 &amp; Protection Key のアクセス権といった論理積で最終的なアクセス権限が決まります。</p>

<p>Intel PKU のアクセス権を操作するフラグとして、各 Protection Key に対して AD (Access-Disabled) と WD (Write-Disabled) の2つのフラグがあり、AD を 1 にすると read/write アクセスが禁止、WD を 1 にすると write アクセスが禁止になります。AD も WD も 0 とすると、read/write アクセスの両方が許可される仕組みです。</p>

<table>
  <thead>
    <tr>
      <th>AD</th>
      <th>WD</th>
      <th>アクセス権</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td>0</td>
      <td>r/w</td>
    </tr>
    <tr>
      <td>0</td>
      <td>1</td>
      <td>r/-</td>
    </tr>
    <tr>
      <td>1</td>
      <td>0</td>
      <td>-/-</td>
    </tr>
    <tr>
      <td>1</td>
      <td>1</td>
      <td>-/-</td>
    </tr>
  </tbody>
</table>

<p>ちなみに実行権限は Intel PKU では操作できません。実行権限を変更したいなら PTE flag を触る必要があります。</p>

<p>各 Protection Key に紐づけられたページのアクセス権を変更するには、<code class="language-plaintext highlighter-rouge">WRPKRU</code> 命令を使用しますが、glibc では <code class="language-plaintext highlighter-rouge">WRPKRU</code> が <code class="language-plaintext highlighter-rouge">pkey_set()</code> としてラッパ関数が提供されており、これを使うと各 Protection Key に対して簡単にアクセス権を変更できます。</p>

<ul>
  <li><a href="https://www.gnu.org/software/libc/manual/html_node/Memory-Protection.html">Memory Protection (The GNU C Library)</a></li>
</ul>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 読み書き許可 (AD=0, WD=0) -&gt; r/w</span>
<span class="n">pkey_set</span><span class="p">(</span><span class="n">pkey</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="c1">// 書き込み禁止 (AD=0, WD=1) -&gt; r/-</span>
<span class="n">pkey_set</span><span class="p">(</span><span class="n">pkey</span><span class="p">,</span> <span class="n">PKEY_DISABLE_WRITE</span><span class="p">);</span>
<span class="c1">// アクセス許可 (AD=1, WD=0) -&gt; -/-</span>
<span class="n">pkey_set</span><span class="p">(</span><span class="n">pkey</span><span class="p">,</span> <span class="n">PKEY_DISABLE_ACCESS</span><span class="p">);</span>
</code></pre></div></div>

<p>Intel PKU の特徴の一つとして、<strong>非特権モード（user mode）のままアクセス権を変更できる</strong>という点があります。要は、システムコール呼び出しやカーネルへのコンテキストスイッチを行わずに、ユーザ空間内のアクセス権を設定できるということです。アクセス権の変更にカーネルが介入すると特権スイッチやコンテキストスイッチといったランタイムオーバーヘッドが発生しますので、ユーザモード内でアクセス権の切り替えが完結するのは非常に嬉しい点です。</p>

<h1 id="intel-pku-を試してみる">Intel PKU を試してみる</h1>

<p>お使いの PC の CPU が Intel PKU に対応していて、OS が Intel PKU に向けたページテーブル更新をサポートしており、かつ CPU で Intel PKU が有効化されているのであれば、ネイティブ環境で動かすことが可能です。</p>

<p>ですが、基本的には QEMU で Linux 仮想環境を用意したうえで試すのが良いでしょう。理由は3つあります。</p>

<ul>
  <li>Windows では Intel MPK はサポートされていません。現状、Windows で利用するための API が用意されていませんし、CPU で有効化されているかどうかを確認する術もありません。</li>
  <li>Mac は ARM アーキテクチャなので利用不可です。</li>
  <li>Linux では Intel MPK がサポートされています。上記で示したように、<code class="language-plaintext highlighter-rouge">pkey_mprotect()</code> といった Intel PKU に向けられたシステムコールが提供されていますし、Intel PKU の有効化・無効化設定が可能です。ただ、WSL2 や Hyper-V といった VM では利用できない場合がほとんどです。一方、QEMU は Intel PKU および Intel PKS の仮想化機能を提供しています。</li>
</ul>

<p>…というわけで、実用環境においては、ほぼ Linux ネイティブ環境専用になってしまっているのが現状です。個人的には Windows でも利用できるようになってもっと実用化されてほしいのですが、ハードウェア依存だったりしてなかなか普及していないのでしょう。</p>

<p>ここでは、QEMU で Linux 仮想環境を作って Intel PKU を利用する方法について書きます。</p>

<h2 id="実行環境">実行環境</h2>

<ul>
  <li>Ubuntu 24.04.3</li>
  <li>Linux 6.x (QEMU 仮想環境; ネイティブ環境で動かせる場合は必須ではない）</li>
  <li>Intel PKU が有効化された環境</li>
</ul>

<h2 id="qemu-仮想環境の用意">QEMU 仮想環境の用意</h2>

<p>※ご利用の環境で直に Intel PKU が利用できる場合は不要です</p>

<p>Intel PKU が動く環境を用意するために、QEMU 仮想環境を用意しましょう。Intel PKU がサポートされている x86_64 の Linux 4.9 以降なら何でも良いです。以前、Ubuntu の CUI 環境を QEMU で用意するための手順を別の記事にまとめていますので、必要に応じてご参照ください。</p>

<ul>
  <li><a href="https://blog.yotio.jp/2024/11/07/qemu-ubuntu.html" taret="_blank">nographicなCUI環境のQEMUにUbuntuをインストールする</a></li>
</ul>

<p>また、Intel PKU を仮想環境で利用するには、<code class="language-plaintext highlighter-rouge">qemu-system-x86_64</code> のオプションで <code class="language-plaintext highlighter-rouge">-cpu max</code> を指定する必要があります。サンプルを下記に示します（イメージファイルが <code class="language-plaintext highlighter-rouge">vm.qcow2</code> の場合）。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>

<span class="nv">VM_IMG</span><span class="o">=</span>vm.qcow2
qemu-system-x86_64 <span class="se">\</span>
    <span class="nt">-cpu</span> max <span class="se">\</span>
    <span class="nt">-m</span> 4G <span class="nt">-smp</span> 4 <span class="se">\</span>
    <span class="nt">-hda</span> <span class="nv">$VM_IMG</span> <span class="se">\</span>
    <span class="nt">-net</span> nic <span class="nt">-net</span> user <span class="se">\</span>
    <span class="nt">-nographic</span>
</code></pre></div></div>

<h2 id="intel-pku-が有効かどうかのチェック">Intel PKU が有効かどうかのチェック</h2>

<p>ご利用の Linux 環境で Intel PKU が利用可能かどうかをチェックします。<code class="language-plaintext highlighter-rouge">/proc/cpuinfo</code> に <code class="language-plaintext highlighter-rouge">pku</code> フラグがあれば有効化されており利用可能な状態です。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">grep</span> <span class="nt">-o</span> pku /proc/cpuinfo
pku
pku
pku
pku
</code></pre></div></div>

<p>もし無効ならば、<code class="language-plaintext highlighter-rouge">pku</code> フラグは現れず何もヒットしません。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">grep </span>pku /proc/cpuinfo
</code></pre></div></div>

<p># ちなみに筆者の Ubuntu on Hyper-V 環境 (x86_64) ではサポートしていませんでした…。なので今回は QEMU on Ubuntu on Hyper-V で実行しています。</p>

<h2 id="簡単なアクセス制御の実装">簡単なアクセス制御の実装</h2>

<p>Intel PKU が有効であることを確認できたら、下記のプログラムをコンパイルして実行します。</p>

<p>ここでは、① 空き PKEY を取得し、② メモリを動的確保し、③ PKEY とメモリを紐づけ、④ アクセス権を Write Disable (read-only) に変更、その後 ⑤ read、⑥ write アクセスをしています。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define _GNU_SOURCE
#include</span> <span class="cpf">&lt;err.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;unistd.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/mman.h&gt;</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">pkru_value</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

    <span class="c1">// ① PKEY を割り当てる</span>
    <span class="k">const</span> <span class="kt">int</span> <span class="n">pkey</span> <span class="o">=</span> <span class="n">pkey_alloc</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">pkey</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">perror</span><span class="p">(</span><span class="s">"pkey_alloc failed"</span><span class="p">);</span>
        <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Allocated PKEY: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">pkey</span><span class="p">);</span>

    <span class="c1">// ② メモリを動的確保する</span>
    <span class="k">const</span> <span class="kt">int</span> <span class="n">page_size</span> <span class="o">=</span> <span class="n">getpagesize</span><span class="p">();</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">mem_obj</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="n">mmap</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">page_size</span><span class="p">,</span> <span class="n">PROT_READ</span> <span class="o">|</span> <span class="n">PROT_WRITE</span><span class="p">,</span>
                         <span class="n">MAP_ANONYMOUS</span> <span class="o">|</span> <span class="n">MAP_PRIVATE</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">mem_obj</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">perror</span><span class="p">(</span><span class="s">"Memory allocation failed"</span><span class="p">);</span>
        <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Memory object allocated</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    
    <span class="c1">// ③ メモリオブジェクトに PKEY を紐づけ</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">pkey_mprotect</span><span class="p">(</span><span class="n">mem_obj</span><span class="p">,</span> <span class="n">page_size</span><span class="p">,</span> <span class="n">PROT_READ</span> <span class="o">|</span> <span class="n">PROT_WRITE</span><span class="p">,</span> <span class="n">pkey</span><span class="p">)</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">perror</span><span class="p">(</span><span class="s">"pkey_mprotect failed"</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">munmap</span><span class="p">(</span><span class="n">mem_obj</span><span class="p">,</span> <span class="n">page_size</span><span class="p">)</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">perror</span><span class="p">(</span><span class="s">"munmap failed"</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"The memory object associated with PKEY %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">pkey</span><span class="p">);</span>

    <span class="c1">// ④ PKEY の write アクセスを無効化 (AD=0, WD=1)</span>
    <span class="n">pkey_set</span><span class="p">(</span><span class="n">pkey</span><span class="p">,</span> <span class="n">PKEY_DISABLE_WRITE</span><span class="p">);</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"PKEY %d has been set to Write-Disabled (WD)</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">pkey</span><span class="p">);</span>

    <span class="c1">// ⑤ 値を読み込む</span>
    <span class="k">volatile</span> <span class="kt">char</span> <span class="n">value</span> <span class="o">=</span> <span class="n">mem_obj</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span> <span class="c1">// 読み込みは成功する</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Read value: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">value</span><span class="p">);</span>

    <span class="c1">// ⑥ 書き込みを試みる (ここでセグメンテーションフォルトが発生するはず)</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Attempting to write to memory object with PKEY %d...</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">pkey</span><span class="p">);</span>
    <span class="n">mem_obj</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">123</span><span class="p">;</span> <span class="c1">// ここでアクセス違反が発生するはず</span>

    <span class="c1">// PKU が有効の限り、ここに到達することはないはず..</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Write succeeded unexpectedly!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">munmap</span><span class="p">(</span><span class="n">mem_obj</span><span class="p">,</span> <span class="n">page_size</span><span class="p">)</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">perror</span><span class="p">(</span><span class="s">"munmap failed"</span><span class="p">);</span>
        <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Intel PKU が正しく動作していれば、⑤ は成功し、一方で ⑥ は失敗するはずです。</p>

<h2 id="実行してみる">実行してみる</h2>

<p>このプログラムを実行してみます。こんな画面になっていれば成功です。</p>

<p><img src="../../../assets/img/post/2025-11-12-introduce-mprotect-rs/image-20251214001712842.webp" alt="image-20251214001712842" /></p>

<p>順を追って見ていきましょう。</p>

<ol>
  <li>カーネルから割り当てられた PKEY 番号は 1</li>
  <li>メモリオブジェクト用のメモリが割り当てられた</li>
  <li>メモリオブジェクトは PKEY 1 と紐づけられた（＝ PTE に PKEY 1 が設定された）</li>
  <li>PKEY 1 は Write Disabled (WD) になった</li>
  <li>メモリオブジェクトから read した。値は 0 だった</li>
  <li>メモリオブジェクトに書き込もうとした</li>
  <li><strong>Segmentation fault (core dumped) 発生</strong></li>
</ol>

<p>この Segmentation fault の発生が重要で、このメモリオブジェクトは事前に PKEY 1 に紐づけられ、かつ、PKEY 1 が Write Disabled、すなわち read アクセスだけが許可される状態になったため、<strong>書き込もうとした時にアクセス違反が発生し、例外エラーによって実行が中断された</strong>のです。</p>

<h2 id="アクセス権を変更してみる">アクセス権を変更してみる</h2>

<p>では、アクセス権を変更したらどうなるでしょうか？</p>

<p>まずは read アクセスも禁止にしてみましょう。<code class="language-plaintext highlighter-rouge">pkey_set()</code> のアクセス権を <code class="language-plaintext highlighter-rouge">PKEY_DISABLE_ACCESS</code> に変更してみます。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="c1">// ④' PKEY の read/write アクセスを禁止 (AD=1, WD=0)</span>
    <span class="n">pkey_set</span><span class="p">(</span><span class="n">pkey</span><span class="p">,</span> <span class="n">PKEY_DISABLE_ACCESS</span><span class="p">);</span>  <span class="c1">// 変更</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"PKEY %d has been set to Access-Disabled (AD)</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">pkey</span><span class="p">);</span>
</code></pre></div></div>

<p>すると、read アクセスの時点で Segmentation fault が発生しました。そう、Access Disabled では read/write アクセスの両方が禁止されるからですね。</p>

<p><img src="../../../assets/img/post/2025-12-14-intel-mpk/image-20251214190605709.webp" alt="image-20251214190605709" /></p>

<p>今度は read/write アクセスを許可するために、<code class="language-plaintext highlighter-rouge">pkey_set()</code> のアクセス権を 0 に置き換えてみます。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="c1">// ④'' PKEY の read/write アクセスを許可 (AD=0, WD=0)</span>
    <span class="n">pkey_set</span><span class="p">(</span><span class="n">pkey</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>  <span class="c1">// 変更</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"PKEY %d has been set to Write-Disabled (WD)</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">pkey</span><span class="p">);</span>
<span class="p">...</span>
    <span class="c1">// 書き込みに成功し、ここまで到達する</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Write succeeded: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">mem_obj</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">munmap</span><span class="p">(</span><span class="n">mem_obj</span><span class="p">,</span> <span class="n">page_size</span><span class="p">)</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">perror</span><span class="p">(</span><span class="s">"munmap failed"</span><span class="p">);</span>
        <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="err">}</span>
</code></pre></div></div>

<p>今度は書き込みに成功しました。Intel MPK によって書き込みアクセスが許可された様子です。</p>

<p><img src="../../../assets/img/post/2025-12-14-intel-mpk/スクリーンショット 2025-12-14 185608.webp" alt="スクリーンショット 2025-12-14 185608" /></p>

<h1 id="結局何が嬉しいのか">結局、何が嬉しいのか？</h1>

<p>これで Intel PKU によって PKEY ごとにアクセス権限を制御できることが確認できました。では、Intel PKU を使ってアクセス制御するのと、これまでの別の方法（mprotect など）で制御するのとでは何が違うのでしょうか？</p>

<h2 id="嬉しい点-1-アクセス権限の変更にはシステムコールいらず">嬉しい点 1. アクセス権限の変更にはシステムコールいらず</h2>

<p>これは冒頭でも書きましたが、Intel PKU の一つの特長は、<strong>アクセス権限の変更のためのシステムコール呼び出しが必要ない</strong>という点です。</p>

<p>Intel PKU では、事前処理として PTE と Protection Key の紐づけが必要ですが、一度紐づけてしまえば、以降はアクセス権変更のためにシステムコールを呼び出すことはありません。アクセス権限変更に使う <code class="language-plaintext highlighter-rouge">pkey_set()</code> 関数は glib で提供されているライブラリ関数で、ユーザ側で処理が完結します。なぜならアクセス権限を設定する <code class="language-plaintext highlighter-rouge">WRPKRU</code> 命令はユーザ権限で実行可能だからです。</p>

<p>このように、カーネルの介入を必要とせず、ユーザアプリケーション内でアクセス権限の制御が完結するので、<strong>システムコール発行に伴うランタイムオーバーヘッド（特権モードスイッチ、コンテキストスイッチなど）を回避できる</strong>というのも Intel PKU の嬉しい点です。</p>

<h2 id="嬉しい点-2-tlb-flush-が発生しない">嬉しい点 2. TLB flush が発生しない</h2>

<p>OS に詳しい人であれば、<code class="language-plaintext highlighter-rouge">mprotect()</code> などを使ってメモリ領域のアクセス権限を変更したとき、TLB flush が行われることをご存知だと思います。TLB (Translation Lookaside Buffer) は物理アドレスと仮想アドレスのマッピング状態を一時的に記憶しておくキャッシュです。仮想アドレスから物理アドレスへの変換作業を行うために Page Walk と呼ばれるページテーブルを順に辿っていく操作が実行されるのですが、これを毎回実施するのはメモリアクセスのたびに多大なオーバーヘッドを発生させます。そこで、あらかじめよく使うメモリマップ関係をキャッシュとして記録しておき、Page Walk を回避して高速化するという手法が取られているのです。</p>

<p>しかし、TLB は PTE (Page Table Entry) の状態を記憶しておきますので、<code class="language-plaintext highlighter-rouge">mprotect()</code> などで PTE のアクセス権限フラグを変更した場合、それを反映させるために TLB のキャッシュをクリアしなければなりません。これが頻繁に起きてしまうと、TLB による高速化が利用できず低速化してしまうのです。</p>

<p>一方で、Intel MPK (PKU) では TLB flush は発生しません。なぜならアクセス権限は PTE ではなく PKRU といった専用のレジスタに記録されており、MMU が PKRU を直接参照するからです。よって、PKU 側で <code class="language-plaintext highlighter-rouge">pkey_set()</code> でアクセス権限を変更しても、TLB flush の必要がなく、引き続き TLB のキャッシュが利用可能なのです。</p>

<p>※ ただし、PKEY 番号の変更（<code class="language-plaintext highlighter-rouge">pkey_alloc()</code>）には PTE の更新が必要ですので TLB flush を伴います。あくまでも PKEY を変更せずにアクセス権限を変更する場合に TLB flush が発生しない、という話です。</p>

<h1 id="活用例改善例">活用例・改善例</h1>

<p>今回は PKEY 1つしか利用していませんが、最大16個まで利用できますので、ユーザ空間内にサンドボックスのような形で複数のメモリオブジェクトを保護することが可能です。ここでは活用例としていくつか紹介しておきます。</p>

<ul>
  <li><a href="https://dl.acm.org/doi/10.1145/3492321.3519582" target="_blank">PKRU-safe: automatically locking down the heap between safe and unsafe languages</a>
    <ul>
      <li>Rust はメモリ安全言語ですが、unsafe なコードの記述も可能です。現に低レイヤのコードやレガシーなライブラリはポインタを使った操作が必要になったりし、ここがセキュリティホールとなりかねません。そこで、PKRU-safe では Rust アプリケーションを untrusted なコードから守るために、Intel PKU を使って trusted 領域と untrusted 領域を隔離しています。</li>
    </ul>
  </li>
  <li><a href="https://dl.acm.org/doi/10.5555/3489146.3489173" target="_blank">Harmonizing performance and isolation in microkernels with efficient intra-kernel isolation and communication</a>
    <ul>
      <li>マイクロカーネルは OS コンポーネントごとに仮想アドレス空間によって隔離されるので、Linux のようなモノリシックカーネルに比べて安全です。しかし、仮想アドレス空間の間でのスイッチや、コンポーネント同士の IPC（プロセス間通信）が大量に発生し、大幅なオーバーヘッドを伴うことが長年の課題となっています。そこで UnderBridge では、頻繁に使う OS コンポーネントを特別にカーネル空間で実行し、各コンポーネント同士を Intel PKU を使って隔離するというアプローチをとっています。Intel PKU によるアクセス制御は仮想アドレス空間のそれと比べて高速ですので、この方法によってマイクロカーネルを高速化できることが示されています。</li>
    </ul>
  </li>
  <li><a href="https://www.usenix.org/conference/atc19/presentation/park-soyeon" target="_blank">libmpk: Software Abstraction for Intel Memory Protection Keys (Intel MPK)</a>
    <ul>
      <li>Intel PKU は PKEY が16個までだが、それを無数個用意できるようにしたという話。具体的には <code class="language-plaintext highlighter-rouge">mprotect()</code> などを組み合わせていて、うまい具合に常時16個までの PKEY に収まるようにし、溢れた分は <code class="language-plaintext highlighter-rouge">mproct()</code> などでアクセス制御します。</li>
    </ul>
  </li>
  <li><a href="https://dl.acm.org/doi/10.1145/3575693.3575735" target="_blank">VDom: Fast and Unlimited Virtual Domains on Multiple Architectures</a>
    <ul>
      <li>課題設定は libmpk と同じですが、こちらは複数個の仮想アドレス空間を用意しておき、それらと組み合わせて無数個の PKEY を用意するというアプローチです。</li>
    </ul>
  </li>
  <li><a href="https://www.usenix.org/conference/usenixsecurity19/presentation/vahldiek-oberwagner" target="_blank">ERIM: Secure, Efficient In-process Isolation with Protection Keys (MPK)</a>
    <ul>
      <li>Intel PKU は強制力のあるアクセス権限制御を提供してくれますが、攻撃者によってアクセス権限設定のコードまで変更されてしまうと、アクセス制御の回避ができてしまいます。ERIM ではこれを防ぐための方法を提案しています。</li>
    </ul>
  </li>
</ul>

<h1 id="おわりに">おわりに</h1>

<p>Intel PKU はユーザ空間内で高速なアクセス権限制御を実現する仕組みです。まだアカデミックな分野での活用例しか見られないので、もっと多くの分野で使われればいいのになぁなんて思っています。少しでも多くの方に知っていただければ幸いです。</p>

<h1 id="補足情報">補足情報</h1>

<h2 id="intel-pks">Intel PKS</h2>

<p>今回紹介したのはユーザ空間向けの Intel PKU (Protection Keys for Userspace) ですが、Intel MPK にはもう一つ、カーネル空間向けの Intel PKS (Protection Keys for Supervisor) の2つがあります。どちらも同様の機能を提供する物ですが、ユーザアプリケーションで利用できるのは前者の Intel PKU のみです（Intel PKS を利用するには、カーネル特権が必要です）。今回は Intel PKU にフォーカスを絞っている点、ご承知おきください。</p>

<h2 id="自分でカーネルに-intel-mpk-を実装したい場合上級者向け">自分でカーネルに Intel MPK を実装したい場合（上級者向け）</h2>

<p>Linux では Intel MPK に対する操作がシステムコールによって抽象化されているので簡単に操作できるんですが、自作 OS や xv6 などで Intel MPK を使いたい、あるいは Linux カーネルを改造して自分で実装したいという<del>変態の</del>方々に向けて詳細な仕様についてもメモしておきます。</p>

<h3 id="intel-mpk-を有効化する">Intel MPK を有効化する</h3>

<p>Intel MPK はデフォルトでは無効化されています。有効化するには CR4 レジスタの PKE フラグを1にする必要があります。この PKE フラグは CR4 レジスタの22ビット目です。</p>

<p>アセンブリコードでの実装例：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">mov</span> <span class="o">%</span><span class="n">cr4</span><span class="p">,</span> <span class="o">%</span><span class="n">eax</span>
<span class="n">bts</span> <span class="err">$</span><span class="mi">22</span><span class="p">,</span> <span class="o">%</span><span class="n">eax</span>
<span class="n">mov</span> <span class="o">%</span><span class="n">eax</span><span class="p">,</span> <span class="o">%</span><span class="n">cr4</span>
</code></pre></div></div>

<h3 id="protection-key-の設定方法-1">Protection Key の設定方法</h3>

<p>Protection Key はページごとに設定する必要があります。具体的には、Page Table Entry の Protection Key field に PKEY 番号（0～15）を 4bit で設定します。この PKEY field は Page Table Entry の 59～62 bit 目と定められています。</p>

<h3 id="アクセス権の変更読み取り">アクセス権の変更・読み取り</h3>

<p>直接 <code class="language-plaintext highlighter-rouge">WRPKRU</code> 命令を呼び出すには、アセンブリでこんな感じで書きます。
EAX レジスタには更新後の PKRU の値を代入しておき、ECX, EDX は 0 としておきます。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"># EAX レジスタに更新後の値を代入する
</span><span class="n">movl</span>  <span class="err">$</span><span class="p">[</span><span class="n">PKU</span><span class="err">に書き込む値</span><span class="p">],</span> <span class="o">%</span><span class="n">eax</span>
<span class="cp"># ecx, edx レジスタの初期化
</span><span class="n">movl</span>  <span class="err">$</span><span class="mh">0x0</span><span class="p">,</span> <span class="o">%</span><span class="n">ecx</span>
<span class="n">movl</span>  <span class="err">$</span><span class="mh">0x0</span><span class="p">,</span> <span class="o">%</span><span class="n">edx</span>
<span class="cp"># wrpkru の実行
</span><span class="n">wrpkru</span>
</code></pre></div></div>

<p>ちなみに PKRU から値を読み出す <code class="language-plaintext highlighter-rouge">RDPKRU</code> 命令を実行すると、EAX レジスタに PKRU の内容が代入され、EDX レジスタは 0 にセットされます。
実行前に ECX レジスタは 0 にセットしておく必要があります。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"># レジスタのクリア
</span><span class="n">movl</span>  <span class="err">$</span><span class="mh">0x0</span><span class="p">,</span> <span class="o">%</span><span class="n">ecx</span>
<span class="cp"># rspkru の実行
</span><span class="n">rdpkru</span>
<span class="cp"># EAX &lt;- PKRU
# EDX &lt;- 0
</span></code></pre></div></div>

<h3 id="intel-pks-を利用したい場合">Intel PKS を利用したい場合</h3>

<p><code class="language-plaintext highlighter-rouge">WRPKRU</code>/<code class="language-plaintext highlighter-rouge">RDPKRU</code> 命令の操作はユーザ空間に対してのみ有効です。</p>

<p>カーネル空間に対して Protection Key を割り当てた場合、カーネル空間向けの Intel PKS の Protection Key とみなされ、<code class="language-plaintext highlighter-rouge">WRPKRU</code>/<code class="language-plaintext highlighter-rouge">RDPKRU</code> では操作できなくなります。カーネル空間で利用したい場合、Intel PKS の有効化（<code class="language-plaintext highlighter-rouge">CR4.PKS=1</code>）、および <code class="language-plaintext highlighter-rouge">WRMSR</code> 命令を使ってアクセス権の変更を行う必要があります。 <code class="language-plaintext highlighter-rouge">WRMSR</code> を呼び出すときは、ECX レジスタに <code class="language-plaintext highlighter-rouge">0x6e1</code> を代入して PKS 用の MSR レジスタをターゲットに指定します。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"># EAX レジスタに更新後の値を代入する
</span><span class="n">movl</span>  <span class="err">$</span><span class="p">[</span><span class="n">PKS</span><span class="err">に書き込む値</span><span class="p">],</span> <span class="o">%</span><span class="n">eax</span>
<span class="cp"># ecx, edx レジスタの初期化
</span><span class="n">movl</span>  <span class="err">$</span><span class="mh">0x6E1</span><span class="p">,</span> <span class="o">%</span><span class="n">ecx</span>
<span class="n">movl</span>  <span class="err">$</span><span class="mh">0x0</span><span class="p">,</span> <span class="o">%</span><span class="n">edx</span>
<span class="cp"># wrmsr 命令の実行
</span><span class="n">wrmsr</span>
</code></pre></div></div>

<h1 id="参考資料">参考資料</h1>

<ul>
  <li><a href="https://cdrdv2.intel.com/v1/dl/getContent/671447" target="_blank">Intel® 64 and IA-32 Architectures Software Developer Manuals - Volume 3A</a></li>
</ul>]]></content><author><name></name></author><category term="x86_64" /><category term="Linux" /><category term="Ubuntu" /><category term="C/C++" /><category term="CPU" /><summary type="html"><![CDATA[今回はハードウェアレベルでメモリ保護を実現する Intel MPK (Memory Protection Keys) のユーザ空間向けの機能、Intel PKU (Protection Keys for Userspace) で遊んでみたという内容になります。 Intel PKU を使うと、ユーザ空間内に複数個のサンドボックスを作成でき、なおかつハードウェア制御により高速なアクセス制御が可能になります。Intel PKU は登場してから10年たった今でも論文などで様々な利用方法が提案されている非常に奥の深い機能なんですが（あまり実用例は聞かないけど）、今回は導入編として、ゆるく触ってみる程度にしたいと思います。]]></summary></entry><entry><title type="html">Raspberry Pi 5でもRTCで自動起動できるようにする</title><link href="https://blog.yotio.jp/2025/12/04/raspi5-cron-wakeup.html" rel="alternate" type="text/html" title="Raspberry Pi 5でもRTCで自動起動できるようにする" /><published>2025-12-04T00:00:00+09:00</published><updated>2025-12-04T00:00:00+09:00</updated><id>https://blog.yotio.jp/2025/12/04/raspi5-cron-wakeup</id><content type="html" xml:base="https://blog.yotio.jp/2025/12/04/raspi5-cron-wakeup.html"><![CDATA[<p>以前、cron と rtcwake を使って通常の Linux PC で決まった時間に自動起動させる方法についてまとめました。</p>

<ul>
  <li><a href="https://blog.yotio.jp/2025/10/19/linux-cron-rtcwake.html">cronとrtcwakeでLinux PCが決まった時間帯だけ起動するようにする | 為せばnull</a></li>
</ul>

<p>自宅の Raspberry Pi 5 サーバでもこれができないかなと思ったんですが、ラズパイは設定先が異なっていたのでメモしておきます。</p>

<!--more-->

<h1 id="実行環境">実行環境</h1>

<ul>
  <li>サーバ
    <ul>
      <li>Raspberry Pi 5</li>
      <li>Ubuntu 24.04.3 LTS (Linux 6.8.0)</li>
    </ul>
  </li>
</ul>

<h1 id="raspberry-pi-5-には-rtc-がある">Raspberry Pi 5 には RTC がある</h1>

<p>まず、意外にも（？）Raspberry Pi 5 はきちんと RTC を備え付けています。</p>

<p>ただし RTC の電池パックは付属していません。ラズパイの公式の RTC 用電池パックは別売り1000円。ちょっとお高い…と悩んでいたのですが、今回の利用用途では<strong>電池パックは必要ありません。</strong>今回は電源に繋いだ状態でシャットダウン状態から復帰させたいだけですので、電池パックは買わずとも USB-C 端子から RTC に電力供給されます。</p>

<h1 id="設定先">設定先</h1>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/sys/class/rtc/rtc0/wakealarm
</code></pre></div></div>

<p>ここに UNIX TIME で自動起動させたい時刻を書き込めばおkです。</p>

<p>ちなみに UNIX TIME の代わりに、例えば  <code class="language-plaintext highlighter-rouge">+600</code> と書くと 600秒後（10分後）に起動時間がセットされるらしい。</p>

<h1 id="決まった時間にシャットダウン決まった時間に起動">決まった時間にシャットダウン＆決まった時間に起動</h1>

<p>cron と組み合わせれば簡単に実現できます。例えば深夜2時にシャットダウンして、翌朝（当日）6時に起動させたい場合。</p>

<p>まずは crontab を開いて…</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>crontab <span class="nt">-e</span>
</code></pre></div></div>

<p>下記の行を追加します。（明日の時刻を指定したい場合は、today を tomorrow に変更してください）</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># m h  dom mon dow   command
00 02 * * * /usr/bin/date -d 'today 06:00' +\%s | tee /sys/class/rtc/rtc0/wakealarm &amp;&amp; /usr/sbin/shutdown
</code></pre></div></div>

<p>これを保存し、cron を再起動することで反映されます。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>systemctl restart cron
</code></pre></div></div>

<p>最後に、一応 <code class="language-plaintext highlighter-rouge">crontab -l</code> で設定内容を確認しておくとよいでしょう。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>crontab <span class="nt">-l</span>
<span class="c"># Edit this file to introduce tasks to be run by cron.</span>
<span class="c"># </span>
<span class="c"># Each task to run has to be defined through a single line</span>
<span class="c"># indicating with different fields when the task will be run</span>
<span class="c"># and what command to run for the task</span>
<span class="c"># </span>
<span class="c"># To define the time you can provide concrete values for</span>
<span class="c"># minute (m), hour (h), day of month (dom), month (mon),</span>
<span class="c"># and day of week (dow) or use '*' in these fields (for 'any').</span>
<span class="c"># </span>
<span class="c"># Notice that tasks will be started based on the cron's system</span>
<span class="c"># daemon's notion of time and timezones.</span>
<span class="c"># </span>
<span class="c"># Output of the crontab jobs (including errors) is sent through</span>
<span class="c"># email to the user the crontab file belongs to (unless redirected).</span>
<span class="c"># </span>
<span class="c"># For example, you can run a backup of all your user accounts</span>
<span class="c"># at 5 a.m every week with:</span>
<span class="c"># 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/</span>
<span class="c"># </span>
<span class="c"># For more information see the manual pages of crontab(5) and cron(8)</span>
<span class="c"># </span>
<span class="c"># m h  dom mon dow   command</span>
30 03 <span class="k">*</span> <span class="k">*</span> <span class="k">*</span> /usr/bin/date <span class="nt">-d</span> <span class="s1">'today 06:45'</span> +<span class="se">\%</span>s | <span class="nb">tee</span> /sys/class/rtc/rtc0/wakealarm <span class="o">&amp;&amp;</span> /usr/sbin/shutdown
</code></pre></div></div>

<p>以上です。</p>]]></content><author><name></name></author><category term="Linux" /><category term="Ubuntu" /><category term="Raspberry Pi" /><summary type="html"><![CDATA[以前、cron と rtcwake を使って通常の Linux PC で決まった時間に自動起動させる方法についてまとめました。 cronとrtcwakeでLinux PCが決まった時間帯だけ起動するようにする | 為せばnull 自宅の Raspberry Pi 5 サーバでもこれができないかなと思ったんですが、ラズパイは設定先が異なっていたのでメモしておきます。]]></summary></entry></feed>