FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

RISC-Vのbenchmarkパタンセットを動作させる

前回、RISC-Vのパタンセットを動作させるためにecallを変更したのだが、変更が間違っていた。mstatusの下位だけでなく、上位ビットもずらしてしまっていた。これでは動作モード以外に、MMUのモードも変わってしまう。 だからSV32が選択されていたのか。

msyksphinz.hatenablog.com

diff --git a/src/inst_riscv.cpp b/src/inst_riscv.cpp
index 672728e..5d8b1b7 100644
--- a/src/inst_riscv.cpp
+++ b/src/inst_riscv.cpp
@@ -2297,7 +2297,8 @@ void InstEnv::RISCV_INST_ECALL (Word_t inst_hex)

   UDWord_t curr_mstatus;
   m_env->CSRRead (SYSREG_ADDR_MSTATUS, &curr_mstatus);
-  UDWord_t next_mstatus = (curr_mstatus << 3) | (static_cast<UDWord_t>(curr_priv) << 1);
+  UDWord_t next_mstatus = ((curr_mstatus << 3) | (static_cast<UDWord_t>(curr_priv) << 1)) & 0xffff;
+  next_mstatus = next_mstatus | (curr_mstatus & 0xffff0000);
   m_env->CSRWrite (SYSREG_ADDR_MSTATUS, next_mstatus);

   m_env->SetPC (jmp_addr);

これでほとんどのパタンが動作するようになった。これ以外に、以下のパタンが落ちている。なんか増えてる気がするぞ。困ったなあ。

github.com

The following tests FAILED:
          1 - rv32mi-p-csr (Failed)
          8 - rv32mi-p-timer (Failed)
          9 - rv32si-p-csr (Failed)
         47 - rv32ui-pm-lrsc (Failed)
        125 - rv64mi-p-csr (Failed)
        126 - rv64mi-p-dirty (Failed)
        130 - rv64mi-p-mcsr (Failed)
        134 - rv64mi-p-timer (Failed)
        136 - rv64si-p-csr (Failed)
        146 - rv64uf-p-fcvt (Failed)
        147 - rv64uf-p-fcvt_w (Failed)
        148 - rv64uf-p-fdiv (Failed)
        159 - rv64uf-pt-fcvt (Failed)
        160 - rv64uf-pt-fcvt_w (Failed)
        161 - rv64uf-pt-fdiv (Failed)
        172 - rv64uf-v-fcvt (Failed)
        173 - rv64uf-v-fcvt_w (Failed)
        174 - rv64uf-v-fdiv (Failed)
        190 - rv64ui-p-amomax_d (Failed)
        194 - rv64ui-p-amomin_d (Failed)
        230 - rv64ui-pm-lrsc (Failed)
        233 - rv64ui-p-mulhsu (Failed)
        273 - rv64ui-pt-amomax_d (Failed)
        277 - rv64ui-pt-amomin_d (Failed)
        315 - rv64ui-pt-mulhsu (Failed)
        359 - rv64ui-v-amomax_d (Failed)
        363 - rv64ui-v-amomin_d (Failed)
        401 - rv64ui-v-mulhsu (Failed)
        436 - median (Failed)
        443 - spmv (Failed)

RISC-Vのbenchmarkパタンセットを動作させる(2)

引き続きRISC-VのISSをメンテナンスして、OSのポーティングと調整を行っているのだが、そういえばベンチマークセットの動作確認をやっている途中で忘れていた。

ISSの調整ついでに、失敗したパタンについて何が起きているのかを見ていこう。

msyksphinz.hatenablog.com

Dhrystoneベンチマークを動作させる

dhrystoneは、riscv-testsリポジトリ内に格納されている。

github.com

これをそのまま実行すると、パタンが正常に終了してくれないという問題があった。通常のテストパタンセットでは、mtohostシステムレジスタに1を書き込むと成功、それ以外を書き込むと失敗となっている。 mtohostには、よくわからない値が格納されていて終了していた。

    151988:M:MBar:[00000cf0] 00e7b023 : sd         tp,sp,0x00|zero      tp=>0000000000024ac0 sp=>0000000000000040 (0000000000024ac0)<=00000040
    151989:M:MBar:[00000cf4] 06063703 : ld         sp,s10,0x060         s10=>0000000000024b50 (0000000000024bb0)=>0000002e sp<=000000000000002e
    151990:M:MBar:[00000cf8] 00a7b423 : sd         tp,s8,0x00|s6        tp=>0000000000024ac0 s8=>0000000000000001 (0000000000024ac8)<=00000001
    151991:M:MBar:[00000cfc] 00d7b823 : sd         tp,s11,0x00|v0       tp=>0000000000024ac0 s11=>0000000000005280 (0000000000024ad0)<=00000080
    151992:M:MBar:[00000d00] 00e7bc23 : sd         tp,sp,0x00|a6        tp=>0000000000024ac0 sp=>000000000000002e (0000000000024ad8)<=0000002e
    151993:M:MBar:[00000d04] 0330000f : fence
    151994:M:MBar:[00000d08] 78079073 : csrrw      zero,0x780,tp        mtohost=>0000000000000000 tp=>0000000000024ac0 mtohost<=0000000000024ac0

これ、ルーチンを追いかけていくと、printfなどを利用するとプログラム内でmtohostにアクセスするらしい。なのでこのmtohostへの書き込みは終了コードを出しているわけではなく、普通にprintfをしているようだった。 ちょっとわかりにくいが、とりあえずmtohostで0か1が出力される以外は終了しないようにしておこう。

その中で、プログラムを追いかけていたのだが、ちゃんとeretでユーザモードに戻ってくれない場所がある。

RISC-Vのeret命令について

eret命令は、スーパバイザコードや割り込みによって割り込みコードに分岐した場合、ユーザコードに戻るために利用される命令だ。eret命令は、mstatusのbitを読み取り、PRV,IEのモード情報をmstatus内からポップする。 より具体的には、mstatusには複数割り込みに備えてPRV,IEビットがスタックできるようになっており、eretはそのスタックをポップしてユーザモードに戻るわけだ。

あれ、よく考えると、eretの逆、つまり特権モードに移るための命令であるecallには、mstatusにpushする操作を追加していたかな?

f:id:msyksphinz:20160410175955p:plain

確認すると追加されていなかったので、ecallのルーチンにmstatusを操作するコードを追加した。 これで、ecallでモード情報をmstatusにpush、eretでモード情報をmstatusからpop、という操作が実現される。

  UDWord_t curr_mstatus;
  m_env->CSRRead (SYSREG_ADDR_MSTATUS, &curr_mstatus);
  UDWord_t next_mstatus = (curr_mstatus << 3) | (static_cast<UDWord_t>(curr_priv) << 1);
  m_env->CSRWrite (SYSREG_ADDR_MSTATUS, next_mstatus);

Dhrystoneベンチマークの修正

上記の修正を行って改めてdhrystoneを走らせていたのだが、どうやら終了しない。調べてみると実機のタイマー情報を利用して終了判定をしているようだ。

    /**************/
    /* Stop timer */
    /**************/

    Stop_Timer();
    setStats(0);

    User_Time = End_Time - Begin_Time;

    if (User_Time < Too_Small_Time)
    {
      printf("Measured time too small to obtain meaningful results\n");
      Number_Of_Runs = Number_Of_Runs * 10;
      printf("\n");
    } else Done = true;
  }

タイマー情報を使われるとシミュレーションにもかなりの長い時間が使われてしまうため、ここはベンチマークを書き換えて終了するようにした。

    /**************/
    /* Stop timer */
    /**************/

    Stop_Timer();
    setStats(0);

    User_Time = End_Time - Begin_Time;

    // if (User_Time < Too_Small_Time)
    // {
    //   printf("Measured time too small to obtain meaningful results\n");
    //   Number_Of_Runs = Number_Of_Runs * 10;
    //   printf("\n");
    // } else Done = true;
    Done = true;
  }

とりあえずこれで良し。

と思ったらSupervisorのアドレッシングモードがまじめに動き出したのでユーザモードを使うパタンが落ちだしたなあ。。。修正しないと。

xv6を移植するときに書き換えるルーチンについて調査(5)

前回の続きで、xv6を移植するときのルーチンを修正していこう。現在は、ようやくほとんどすべてのソースコードコンパイルできるようになっている。

さらに、usertests.cでコンパイルエラーが発生したが、これは何のことだかよくわからない。

usertests.c: In function 'validateint':
usertests.c:1553:3: error: unknown register name 'ebx' in 'asm'
   asm("mov %%esp, %%ebx\n\t"
   ^

当該部分はusertests.cの部分だ。突然x86のコードが現れており、なんだか良く分からない。

@@ -1549,14 +1549,14 @@ sbrktest(void)
 void
 validateint(int *p)
 {
-  int res;
-  asm("mov %%esp, %%ebx\n\t"
-      "mov %3, %%esp\n\t"
-      "int %2\n\t"
-      "mov %%ebx, %%esp" :
-      "=a" (res) :
-      "a" (SYS_sleep), "n" (T_SYSCALL), "c" (p) :
-      "ebx");
+  // int res;
+  // asm("mov %%esp, %%ebx\n\t"
+  //     "mov %3, %%esp\n\t"
+  //     "int %2\n\t"
+  //     "mov %%ebx, %%esp" :
+  //     "=a" (res) :
+  //     "a" (SYS_sleep), "n" (T_SYSCALL), "c" (p) :
+  //     "ebx");

とりあえずは現在は省略ということにした。xv6の解説書を読んでも記述がなくよく分からなかった。

github.com

これでとりあえずすべてのソースコードコンパイルできるようになったが、残っているのはBootcodeの部分だ。xv6のブートの部分をどうしようか迷っているのだが、x86の場合はまずはBootcodeと呼ばれるものが走り、起動に最小限必要なルーチンがメモリにコピーされる。それからブート処理が始まるという流れだ。

これをどのようにしてRISC-V版でも対応させるか、という話になる。組込みシステムなどではHDDなど最初からついていないので、SDカードからデータを読み込んで起動しているものと思う。 まずはbootcodeをRISC-Vで記述して、スタートアップのコードを作るしかない。これはxv6ではbootasm.Sとして記述されており、x86アセンブラで記述してあるため、これをRISC-Vに移植する。

まず、RISC-Vはリセット後どこから起動するかというと、それはリセットベクタとして定義されている。

f:id:msyksphinz:20160410141318p:plain

上位アドレスに張り付くモードと、下位アドレスに張り付くモードが存在しているが、とりあえず下位に張り付くモードを使っていてよいと思う。

そんでもってxv6.imgをダンプさせると、その部分にはオブジェクトファイルのヘッダが張り付いているため、うまくブートできていない。

$ hexdump xv6.img (RISC-V版)
0000000 0000 0000 0000 0000 0000 0000 0000 0000
*
0000200 457f 464c 0101 0001 0000 0000 0000 0000
0000210 0002 00f3 0001 0000 0000 8010 0034 0000
0000220 3448 0003 0000 0000 0034 0020 0001 0028
0000230 0014 0011 0001 0000 1000 0000 0000 8010
0000240 0000 0010 d0f8 0000 6bac 0001 0007 0000
0000250 1000 0000 0000 0000 0000 0000 0000 0000
0000260 0000 0000 0000 0000 0000 0000 0000 0000
*
0001200 f073 3000 e117 0000 0113 18c1 5297 0000
0001210 8293 e882 8067 0002 0013 0000 0113 fe01
0001220 2e23 0011 2c23 0081 0413 0201 a7b7 8010
...

まずは、この部分をなんとかしなければならない。x86コンパイルしたxv6にも挿入されている。

$ hexdump xv6.img (x86版)
0000180 144b 438b 8310 0cc4 c139 0c76 c701 c129
0000190 00b8 0000 fc00 aaf3 c383 3920 77de ffd1
00001a0 1815 0100 8d00 f465 5e5b 5d5f 00c3 0000
00001b0 0000 0000 0000 0000 0000 0000 0000 0000
*
00001f0 0000 0000 0000 0000 0000 0000 0000 aa55
0000200 457f 464c 0101 0001 0000 0000 0000 0000
0000210 0002 0003 0001 0000 000c 0010 0034 0000
0000220 5cc0 0002 0000 0000 0034 0020 0002 0028
0000230 0011 000e 0001 0000 1000 0000 0000 8010
0000240 0000 0010 b5b6 0000 525c 0001 0007 0000
0000250 1000 0000 e551 6474 0000 0000 0000 0000
0000260 0000 0000 0000 0000 0000 0000 0007 0000
0000270 0010 0000 0000 0000 0000 0000 0000 0000
0000280 0000 0000 0000 0000 0000 0000 0000 0000
*
0001200 b002 1bad 0000 0000 4ffe e452 200f 83e0
0001210 10c8 220f b8e0 a000 0010 220f 0fd8 c020
...

これを除去して、ISSでxv6が起動できるように持っていきたい。

xv6を移植するときに書き換えるルーチンについて調査(4)

引き続き、RISC-Vにxv6を移植するための情報を調査していこう。

github.com

initcode.Sは一番最初のプロセスとして実行される。 引数を設定し、システムコールを呼び出す。

RISC-Vにて、システムコールを発生させるには以下の命令を使う。

f:id:msyksphinz:20160407232030p:plain

ecallは、システムコールを発生させ、マシンモードに遷移する。どの例外ベクタに飛ぶかについては、どのモードからecall命令を実行したかに依存している。 まず、mtvecレジスタにベクトルベースレジスタを設定し、それから、以下の表に基づいてベクタアドレスにジャンプする。

f:id:msyksphinz:20160408015153p:plain

f:id:msyksphinz:20160408015209p:plain

まずはRISC-Vの例外ベクタを作成しないといけないのだが、この部分はとりあえず例外ベクタのテンプレートだけ用意しておこう。 ちなみに、ecallを実行すると、mcauseレジスタには以下のようなルールで値が書き込まれる(つまり8)、これで、どこから、何の例外が発生したかを確認するわけだ。

f:id:msyksphinz:20160408020102p:plain

さらに、リンカスクリプトを修正する。現在はi386用になっているが、これをRISC-V用に変更した。

OUTPUT_FORMAT("elf32-littleriscv")
OUTPUT_ARCH(riscv)

コンパイルをしていると、_binary_entryother_startや、binary_entryother_sizeがないと怒られる。これは、entryother.Sから来ているものらしいのだが、xv6のマニュアルを読んでも、それらしき説明がない。 いろいろコードを見ていると、スタートアップ時の初期設定をメインのCPUが実行し、それ以外のCPUは待機状態に入る。このためのルーチンらしい。 とりあえずシングルコアで動かす予定なので、この部分は今の時点では実相を省略する。

さらに、usys.Sでは、システムコールを発生させるためのルーチンを定義している。これも、RISC-Vのものに書き換えよう。

#include "syscall.h"
#include "traps.h"

#define SYSCALL(name) \
  .globl name; \
name: \
    li  x16, SYS_ ## name; \
    ecall; \
    eret

キャッシュコヒーレンシ理解のために資料を漁ったら昔のスライドが出てきたので公開する

ちょっと事情があって、マルチプロセッサのキャッシュコヒーレンシについて復習していたら、そういえばこれ研究室の輪講で発表したな。死ぬほど長い輪講資料作ったけどどこに行ったんだろうと思ってGoogle Drive探してたら見つかったので、せっかくだしディスクの肥やしにするのももったいないので公開することにした。

それにしても、過去の自分が作ったとはいえ、死ぬほど長い。 過去の自分は、こんなにも長い資料が作れていたんだなあ。。。

www.slideshare.net

ヘネシー&パターソン コンピュータアーキテクチャ 定量的アプローチ 第5版

ヘネシー&パターソン コンピュータアーキテクチャ 定量的アプローチ 第5版

キャッシュコヒーレンシとは

マルチプロセッサ上には、書くコアにキャッシュが載っていて、直近で利用しているデータはキャッシュにたまっており、メモリアクセスのバンド幅を抑え、レイテンシを小さくする役割を持っている。 ただし、同じ場所のデータを複数のコアがキャッシュに持っていて、一方を書き換えてしまった場合、矛盾が生じてしまう。これをどう解決するの?という問題。

f:id:msyksphinz:20160407022222p:plain

まずCPU-Aが主記憶からアドレスXを読み込んでくるのだけれども、次にCPU-BもアドレスXの値を主記憶から読み込む。 ここで、CPU-AがアドレスXに0を書き込んでしまうと、CPU-Bはその事実を知らないものだから、アドレスXの値をずっと0と勘違いして進んでしまうことになる。

さて、これをどう解消しようかね、という話なのだが、資料の中ではキャッシュスヌーピングプロトコルというものを紹介している。 ようするに、キャッシュからメモリへ書き戻す操作をそれぞれのキャッシュがスヌーピング(盗み見)ており、自分が持っているデータが書き換わったら、自分の持っているキャッシュの内容を破棄して、矛盾が生じないようにする、という訳だ。

f:id:msyksphinz:20160407022629p:plain

この問題を解決するための方法の一つとして、Writeインバリデートプロトコルというものが存在する。 これは、各コアが同じアドレスXのデータを持っていたとすると、あるコアがそのアドレスにデータを書き込むと、すべてのコアがXを保持しているキャッシュのラインをインバリデート(削除)する、という方法だ。これで、矛盾は発生しない。コア0以外は、もう一度アドレスXが必要になった場合は主記憶にもう一度取りに行きなさい、という訳だ。

この時に、別のコアがアドレスXに対してリードリクエストを出した時に、どこから真の値を読み出すかというのは問題になる。

ライトスルーキャッシュなら問題ない。真の値は常に主記憶上に存在するからだ。 しかし、ライトバックキャッシュの場合は、真の値はキャッシュ上に残っている可能性がある。そうすると、別のコアのリクエストに対して、そのキャッシュ自身が反応することがあり得る。 ただし、これが一般的なのかはよく知らない。

f:id:msyksphinz:20160407023309p:plain

f:id:msyksphinz:20160407023333p:plain

次に、コヒーレンシをとるためのプロトコルについてみていく。ヘネパタでは、以下のステートマシンが紹介されている。

f:id:msyksphinz:20160407023444p:plain

例えば、各コアがあるアドレスの値を共有している状態であったとしても、その値を読み込む分には何の問題もない。

f:id:msyksphinz:20160407023530p:plain

P1とP3が同じ値を共有していたとして、P1がその値に書き込みを行うと、バス上にインバリデートが発行され、P3の値は無効化されてしまう。

f:id:msyksphinz:20160407023702p:plain

このとき、P3はバスからのリクエストに応じて、該当するキャッシュラインをインバリデートする訳だ。

f:id:msyksphinz:20160407024021p:plain

こういうのはさらに厄介だ。すでにP3がキャッシュに対して書き込みを起こしているのに、別のP1が同じアドレスに対して書き込みを起こしてしまった。 すると、P1はP3にライトインバリデートを伝える。するとP3は当該アドレスのラインを主記憶に書き戻し、そのデータをP1が拾って、さらにModified状態に変更する。

f:id:msyksphinz:20160407024151p:plain

そこから先も、パフォーマンスについていろいろ書いてあるので読んでほしい。僕はもう疲れた。

とにかく結論は、

f:id:msyksphinz:20160407024407p:plain

ふーん。

xv6を移植するときに書き換えるルーチンについて調査(3)

まずは一折ビルドしていきながら、コンパイルエラーになったところを順番に直していくという作業だった。

trap.cでいくつかのエラーが出てくる。やはり、トラップフレームを書き換えたことによるものだ。

github.com

x86の版では、例外の要因を確かめるために、tf->trapnoという変数を参照している。

//PAGEBREAK: 41
void
trap(struct trapframe *tf)
{
  if(tf->trapno == T_SYSCALL){

これはRISC-Vになってトラップフレームから削除してしまったので、RISC-Vのシステムレジスタであるmcauseを参照するように変更する。

//PAGEBREAK: 41
void
trap(struct trapframe *tf)
{
  uint exccode = tf->mcause & CAUSE_CODE;
  if(exccode == T_SYSCALL){

このあたりのトラップ処理の修正を一つ一つ実施していった。とりあえずtrap.cは修正したが、今度は例外ベクトルの編集をしなければならない。

例外ベクトルは、オリジナルのxv6ではvector.plにより自動的に生成されるようになっている。しかしRISC-Vではそのようにする訳にはいかないので、riscv-testsの例外ベクタをコピーして、実装することにした。

github.com

handle_exception:                                                       \
        /* we don't know how to handle whatever the exception was */    \
  other_exception:                                                      \
        /* some unhandlable exception occurred */                       \
  1:    ori TESTNUM, TESTNUM, 1337;                                     \
  write_tohost:                                                         \
        csrw mtohost, TESTNUM;                                          \
        j write_tohost;                                                 \
reset_vector:                                                           \
        RISCV_MULTICORE_DISABLE;                                        \
        CHECK_XLEN;                                                     \
        li TESTNUM, 0;                                                  \
        /* if an stvec_handler is defined, delegate exceptions to it */ \
        la t0, stvec_handler;                                           \
        beqz t0, 1f;                                                    \
        csrw stvec, t0;                                          
...

って今見てみたら僕が使っているコードからまたアップデートがかかっているな。後でチェックしよう。

さらにentry.Sも修正してみる。これはmain()を呼ぶだけだ。

        .globl _start
_start:
        csrci   mstatus, STATUS_IE

        la      sp, stack + KSTACKSIZE

        # Jump to main()
        la      t0, main
        jr      t0
        nop

まだまだ修正する部分はある。続く。

xv6を移植するときに書き換えるルーチンについて調査(2)

xv6はx86用に実装されているのだが、それを他のアーキテクチャに移植する場合に、何が必要かについて調査している。 最近は仕事も忙しくて、まったく調査できていなかったのだが、少し時間ができたので、続きをやっていこう。

msyksphinz.hatenablog.com

cpuステータス変数について、コンパイル時にハマったこと

proc.hには、以下のように定義されている。これに気が付かずに、結構長い時間ハマってしまった。

  • proc.h
// Per-CPU state
struct cpu {
  uchar id;                    // Local APIC ID; index into cpus[] below
  struct context *scheduler;   // swtch() here to enter scheduler
  struct taskstate ts;         // Used by x86 to find stack for interrupt
  struct segdesc gdt[NSEGS];   // x86 global descriptor table
  volatile uint started;       // Has the CPU started?
  int ncli;                    // Depth of pushcli nesting.
  int intena;                  // Were interrupts enabled before pushcli?

  // Cpu-local storage variables; see below
  struct cpu *cpu;
  struct proc *proc;           // The currently-running process.
};

// Per-CPU variables, holding pointers to the
// current cpu and to the current process.
// The asm suffix tells gcc to use "%gs:0" to refer to cpu
// and "%gs:4" to refer to proc.  seginit sets up the
// %gs segment register so that %gs refers to the memory
// holding those two variables in the local cpu's struct cpu.
// This is similar to how thread-local variables are implemented
// in thread libraries such as Linux pthreads.
extern struct cpu *cpu asm("%gs:0");       // &cpus[cpunum()]
extern struct proc *proc asm("%gs:4");     // cpus[cpunum()].proc

GSというのは、Gセグメントの略称らしい。GはFの次、ということ?

X86アセンブラ/x86アーキテクチャ - Wikibooks

FSやGSはこの決まりの例外となっており、スレッド独自のデータを指し示すのに使われる。

このgsレジスタが各コア用のcpu変数を持っている。さらにオフセットを4つずらすことで、procを参照している?そんな馬鹿な。

Gnu Assembler

%gs:foo [gs]:[foo] セッションは"%gs"で示され、オフセットは変数"foo"の内容で示される。

とりあえず、移植するときはgsは省略するようにした。

extern struct cpu *cpu;       // &cpus[cpunum()]
extern struct proc *proc;     // cpus[cpunum()].proc

trapframeの移植について

trapframeはx86用に定義されている。これをRISC-V用に移植するとどうなるだろう。

//PAGEBREAK: 36
// Layout of the trap frame built on the stack by the
// hardware and by trapasm.S, and passed to trap().
struct trapframe {
  // registers as pushed by pusha
  uint edi;
  uint esi;
  uint ebp;
  uint oesp;      // useless & ignored
  uint ebx;
  uint edx;
  uint ecx;
  uint eax;

  // rest of trap frame
  ushort gs;
  ushort padding1;
  ushort fs;
  ushort padding2;
  ushort es;
  ushort padding3;
  ushort ds;
  ushort padding4;
  uint trapno;

  // below here defined by x86 hardware
  uint err;
  uint eip;
  ushort cs;
  ushort padding5;
  uint eflags;

  // below here only when crossing rings, such as from user to kernel
  uint esp;
  ushort ss;
  ushort padding6;
};

すべてのレジスタを定義して、プロセスが切り替わるときに退避するときに利用するとしたらこうなるだろうか。

//PAGEBREAK: 36
// Layout of the trap frame built on the stack by the
// hardware and by trapasm.S, and passed to trap().
struct trapframe {
  // registers as pushed by pusha
  uint ra;
  uint s0;
  uint s1;
  uint s2;
  uint s3;
  uint s4;
  uint s5;
  uint s6;
  uint s7;
  uint s8;
  uint s9;
  uint s10;
  uint s11;
  uint sp;
  uint tp;
  uint v0;
  uint v1;
  uint a0;
  uint a1;
  uint a2;
  uint a3;
  uint a4;
  uint a5;
  uint a6;
  uint a7;
  uint t0;
  uint t1;
  uint t2;
  uint t3;
  uint t4;
  uint gp;

  uint mepc;
  uint mcause;
  uint mstatus;
};

このときに、mepc, mcause, mstatusを対比しているが、ここは勉強不足で疑問が残る。mxxxレジスタはマシンモード時のステートを保存するためのシステムレジスタだが、実際にプロセスが実行されるのはユーザモードの時ではないのか?そうすると、ユーザモードの状態を保持しているシステムレジスタを保存するのが適切ではないだろうか。 あるいは、すべてのレジスタを保存しておくべきかなあ。

xchgの代替にはamoswapを使う?

xchgについても、まだ勉強不足で分かっていないのだが、同様の機能を持っているものとしたらRISC-Vだとamoswapが使えるかもしれない。

static inline uint
xchg(volatile uint *addr, uint newval)
{
  uint result;

  // The + in "+m" denotes a read-modify-write operand.
  asm volatile("lock; xchgl %0, %1" :
               "+m" (*addr), "=a" (result) :
               "1" (newval) :
               "cc");
  return result;
}
  • riscv.h
static inline uint
xchg(volatile uint *addr, uint newval)
{
  uint result;
  asm volatile ("amoswap.w %0, %0, (%1)":
                "=r" (result), "+r" (*addr) :
                "r" (newval));
  return result;
}