RISC-V版xv6のswtch.Sを利用して,RISC-V版xv6上で動作するユーザレベルスレッドライブラリ(ユーザ空間で動作するスレッドライブラリ)を作成せよ.
レベル1は必須,レベル2以降はオプション(任意)とする. レベル1〜3についてはカーネルの変更は行わないこと. レベル1については uttest1.c 以外のテストプログラムを作成し,その実行結果もレポートに載せること. レベル2以降についてはそれぞれ適当なテスト用プログラムを作成し,その実行結果もレポートに載せること.
スレッドの切り替えを関数 yield によって陽に行うようなユーザレベルスレッドライブラリを,以下の関数として作成せよ.
// 関数funを実行するスレッドを作成する.funは停止しないものとする.
// 作成したスレッドの番号(スレッドID)を返値とする.
// スレッドの作成ができない場合は-1を返値とする.
// 作成したスレッドはstart_threadsを呼び出すまで実行しないこと.
int make_uthread(void (*fun)());
// 作成されたスレッドを起動する.スレッドの実行中は戻らない.
void start_uthreads();
// 他のスレッドに実行を譲る.
// この関数はスレッド内で実行される関数内でのみ呼び出される.
void yield();
// 呼び出したスレッドの番号(スレッドID)を返す.
// この関数はスレッド内で実行される関数内でのみ呼び出される.
int mytid();
作成したスレッドライブラリで以下のテストプログラム(uttest1.c)が動作することを確認せよ.
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "user/uthreads.h"
void foo() {
int c = 0;
for (;;) {
printf("foo (tid=%d): %d\n", mytid(), c);
c += 1;
yield();
}
}
void bar() {
int c = 0;
for (;;) {
printf("bar (tid=%d): %d\n", mytid(), c);
yield();
c += 2;
}
}
void baz_sub(int *cp) {
printf("baz (tid=%d): %d\n", mytid(), *cp);
yield();
*cp += 3;
}
void baz() {
int c = 0;
for (;;) {
baz_sub(&c);
baz_sub(&c);
}
}
int main() {
make_uthread(foo);
make_uthread(bar);
make_uthread(baz);
start_uthreads();
exit(0);
}
上記プログラムを実行すると以下のようになるはずである.
$ uttest1
foo (tid=0): 0
bar (tid=1): 0
baz (tid=2): 0
foo (tid=0): 1
bar (tid=1): 2
baz (tid=2): 3
foo (tid=0): 2
bar (tid=1): 4
baz (tid=2): 6
foo (tid=0): 3
bar (tid=1): 6
baz (tid=2): 9
...
xv6ではctrl-Cでプログラムを停止することはできないため,uttest1のような止まらないプログラムを実行したときは ctrl-A x(ctrl-Aに続いてxをタイプする)でQEMUを抜けること.
以下のようなスレッドの停止機能を追加せよ.
- 呼び出したスレッドを停止する関数
uthread_exit(型は以下の通り)を定義せよ.void uthread_exit();
- レベル1では
make_uthreadの引数として指定した関数は停止しないことになっていた.この条件を外して,関数を最後まで実行するか,あるいはreturnで終了した場合にスレッドを停止させるようにせよ. - 全てのスレッドの実行が終了したときに
start_uthreadsから戻るようにせよ. - (オプション)
start_uthreadsから戻ったあとに再度start_uthreadsを呼び出したときに,スレッドを最初から実行できるようにせよ
スレッドの同期をおこなうための以下のような関数を定義せよ.
void uthread_wait(void *a);void uthread_notify(int tid, void *a);void uthread_notify_all(void *a);
uthread_wait を実行したスレッドは,他のスレッドからuthread_notify あるいはnotify_all で起こされるまで UT_SLEEP 状態で待たされる.
待たされている間は,他の UT_READY 状態なスレッドが順に実行される.
引数 a は条件変数の役割を担う任意のデータとする.xv6のproc.cを参考にすること.
(1) プリエンプティブスケジューリング
yield を使用しなくても複数のユーザレベルスレッドが同時に動作できるようにせよ.
これはユーザレベルでタイマー割り込みに相当する割り込みを扱う機構を実現するため,カーネルに手を入れる必要がある.
(2) ノンブロッキングI/O
xv6の入出力システムコールは動作が終わるまでプロセスをブロックする.したがってあるユーザレベルスレッド内でそのようなシステムコール(例えば read)を実行すると,それが終わるまで他のスレッドを含めたプロセス全体が待たされる.
select や kqueue などに相当するシステムコールを作成してノンブロッキングI/Oを実現し,ユーザレベルスレッド内で複数のI/Oを同時に実行できるようにせよ.
課題1のときと同様に,
本講義で用意しているxv6-riscvリポジトリ(https://github.com/titech-os/xv6-riscv)にこの課題のためのブランチ kadai21 を用意した.
まずこれを手元の作業ディレクトリ(xv6-riscv とする)でチェックアウトする.
% cd xv6-riscv
% git checkout kadai21チェックアウトの際,作業中のファイルがある場合はコミットするか git stash で退避しておくとよい.
上記の代わりに,現在の作業ディレクトリとは別の場所(現在の作業ディレクトリの外とすること)に kadai21 ブランチの内容をクローンしてもよい.
% git clone -b kadai21 https://github.com/titech-os/xv6-riscv.git xv6-kadai21
% cd xv6-kadai21ここでは kadai21 ブランチの内容を xv6-kadai21 という名前のディレクトリにクローンしている.
念のためこの時点でビルドしてxv6上で uttest1 を実行できることを確認すること(何も出力されない).
課題用ブランチ kadai21 には,ユーザレベルスレッドライブラリを実装するためのファイル user/uthreads.c,user/uthreads.h およびテスト用プログラム user/uttest1.c が用意されている.
また,これらをビルドしてxv6内で実行できるよう,Makefileに変更が加えられている.
user/uthreads.c および user/uthreads.h の
FILL YOUR CODE HERE とあるコメントを削除して自分のコードを書き込み,ユーザレベルスレッドライブラリを完成させること.
もちろん他に必要な関数,変数,マクロ等の定義を付け加えてもかまわない.
上記レベル1〜3についてはカーネルに変更を加えないこと.
以下の提出物を 学籍番号_名前(苗字のローマ字)という名前のディレクトリ(フォルダ)に入れること.このディレクトリをzip等で圧縮してT2SCHOLAで提出すること.
- レポート:PDFあるいはプレインテキスト(markdown等も可)とする.ファイル名(拡張子除く)は上位フォルダ名と同じとする.
- 作成したプログラムのソースコード:srcというサブディレクトリを作り,作成したプログラムのソースコードおよびビルドに必要なファイル(Makefile等)一式を入れること.ビルドと実行に必要な情報(使用した言語,言語処理系とそのバージョンおよび入手方法,ビルドに必要なツール,ライブラリ等のバージョンと入手方法およびビルド・実行方法,開発・テストに使用した環境等)をテキストファイルREADME.txtに明記すること.
- 変更したファイル:srcというサブディレクトリを作り,変更したファイルのみをそこに入れること.その際に,xv6-riscvのディレクトリ構成に従うこと.例えば,Makefileとuser下のuthreads.c, uthreads.hを変更し,uttest2.cというファイルを追加した場合は,以下のような構成になる.
19B99999_Watanabe
├── 19B99999_Watanabe.pdf
└── src
├── Makefile
└── user
├── uthreads.c
├── uthreads.h
└── uttest2.c