部内向けスキルアップ研修
「組込みOS自作入門」
2013年12月
8thステップ担当:松元
本日の内容
• スレッドを実装します
– スレッドとは?
– OSとは?
• もくもく会
– スレッドで動作するコマンド応答プログラム
• 必要に応じてプログラムの説明
たとえばこんな動作をさせる
• 以下の機能を持つ
– 0.1秒ごとにLED点滅
– 1秒ごとに液晶パネルに時刻表示 – シリアルからのコマンドに応答
• 一定時間ごとにサービスを実行できるか チェックする(ビジー・ループ)
8.1.1 OSはなぜ必要なのか?
メイン・ループ
0.1秒経過?
1秒経過?
シリアル受信?
LEDを点滅
液晶パネルに時刻表示
入力コマンドを実行
ダンプ出力 を追加
8.1.1 OSはなぜ必要なのか?
問題点
• ダンプするデータが大きい場合、処理に 時間がかかる
• LED点滅、時計表示がとまってしまう
• 一定回数ごとにメイン・ループに戻る
8.1.1 OSはなぜ必要なのか?
ダンプ処理
ダンプ処理中?
一定回数?
データ分繰り返す
前回の状態を復旧して再開
データ出力
状態を保存 メイン・ループより呼び出し
メイン・ループへ メイン・ループへ
データがなくなるまで
dump関数
• 定期的にメイン・ループに戻る
• 戻る前に実行中の状態を保存
• 関数の先頭で、前回の中断状態からの 再開か調べる
• 再開の場合、保存された状態を復旧
8.1.1 OSはなぜ必要なのか?
まとめると・・・
• 時間のかかる処理
• 回数が特定できないループ処理 では、中断・再開処理が必要
処理が複雑化してバグが発生しやすい
8.1.1 OSはなぜ必要なのか?
やりたいこと
• 定期的な処理の切り替え
• 処理状態の中断・保存・再開
サービス単位に区切る(タスク)
処理を共通化 スレッド化
8.1.2 タスクとスレッド
スレッドとは
• タスクごとに独立して動作させる
→ 並行処理
• CPUの処理時間を非常に短い単位に分割 し、複数のスレッドに順番に割り当てる ことによって、複数の処理を同時に行っ ているようにみせる
8.1.2 タスクとスレッド
用語
• システム・コール
– OSに対するサービス要求
• スケジューリング
– 次に動作すべきスレッドを選択
• ディスパッチ
– 選択されたスレッドの処理再開
8.1.2 タスクとスレッド
ディスパッチ
• 中断時に処理状態を保存
– スタックはスレッドごとに持つ
• 再開時にスタック・ポインタを 再開する スレッドのスタックのものに切り替える
8.1.2 タスクとスレッド
用語
• コンテキスト/コンテキスト情報
– スレッドの処理中断時に保存が必要なCPUの 状態
– スタック・ポインタ、プログラム・カウンタ などの各種レジスタ値
• コンテキスト切替え/ コンテキスト・
チェンジ
– スレッドのディスパッチのために、そのス レッドのコンテキストを読み込むこと
8.1.2 タスクとスレッド
用語
• アプリケーション・プログラム
– OS上でタスクとして独立して動作するプログ ラム
• コア/カーネル
– OSの中核
– 基本プログラム
8.1.3 アプリケーション・プログラムとは何か?
OS/割込み/スレッド
• OSは割込みの延長で動作し、スレッドを 管理する
1. 割込みハンドラからスレッドのコンテキス ト保存
2. スケジューリング
3. スレッドのコンテキストを復旧(ディス パッチ)
8.1.4 スレッドの切替え契機
システム・コール
• OSは資源を管理するための機能を持つ
– OSは割込みの延長
– スレッドとは切り離されている
• スレッド側から明示的に割込みを発生
• システム・コール命令を実行
→ システム・コール割込みが発生
• システム・コール割込みハンドラで応答
8.1.5 システム・コール
もくもく会の前に
• kz_start()
スレッド生成用スレッドを開始
• レディー・キュー
8.2 OSの実装
レディー・キュー
• スレッドごとにタスク・コントロール・
ブロック(TCB)を生成
→ タスクの情報を格納
• 実行可能なスレッドのタスク・コント ロール・ブロックをつなぐ(レディー・
キュー)
head tail readyque
nextポインタ 各種パラメータ
TCB nextポインタ
各種パラメータ
TCB nextポインタ
各種パラメータ
TCB NULL
8.2 OSの実装
レディー・キュー
• 実行中のスレッド(カレント・スレッ ド)の実行に区切りがついたら、レ ディー・キューの末尾に追加
• ラウンドロビン・スケジューリング
head tail readyque
nextポインタ 各種パラメータ
TCB
nextポインタ 各種パラメータ
TCB
nextポインタ 各種パラメータ
TCB NULL カレント
スレッド
NULL
8.2 OSの実装
用語
• キュー/先入れ先出し/FIFO
– 最初に入ってきたものを最初に処理する構造
• キューイング/エンキュー
– キューへ挿入
• デキュー
– キューからの抜き出し
• スタック:後入れ先出し/LIFO
8.2 OSの実装
もくもく会
• スレッドで動作するコマンド応答プログ ラム
– 教科書p.290~のソースコードリストを参照 – ブート・ローダー
• ld.scr、intr.S、startup.s を修正
– OS
• kozos.h、kozos.c、syscall.h、syscall.c、
test08_1.c を追加
• ld.scr、startup.s、defines.h、main.c、
Makefile を修正
8.2 OSの実装
プログラムの実行
• ブートローダー
– make → make image → make write
• OS
– make
• コンソールで確認
– load → kozosを転送 – run
– echo test – exit
8.3 プログラムの実行
コードを追ってみよう
• startup.sよりmain()が呼ばれる(P.325)
• kz_start(
start_threads,
"start", 0x100, 0, NULL);
(P.317)
– 割込みハンドラの登録
• setintr(SOFTVEC_TYPE_SYSCALL, syscall_intr);
• setintr(SOFTVEC_TYPE_SOFTERR, softerr_intr);
– current = (kz_thread *)thread_run(func, name,
stacksize, argc, argv);
– dispatch(¤t->context);
割込みハンドラの登録 (P.313)
• setintr(SOFTVEC_TYPE_SYSCALL, syscall_intr);
• setintr(SOFTVEC_TYPE_SOFTERR, softerr_intr);
– 割込みベクタの登録
softvec_setintr(type, thread_intr);(P.263)
• SOFTVECS[type] = handler;
– 割込みハンドラの登録
handlers[type] = handler;
コードを追ってみよう
• startup.sよりmain()が呼ばれる(P.325)
• kz_start(start_threads,
"start", 0x100, 0, NULL);
(P.317)
– 割込みハンドラの登録
• setintr(SOFTVEC_TYPE_SYSCALL, syscall_intr);
• setintr(SOFTVEC_TYPE_SOFTERR, softerr_intr);
– current = (kz_thread *)thread_run(func, name,
stacksize, argc, argv);
– dispatch(¤t->context);
thread_run() (P.306)
• タスク・コントロール・ブロックを設定
– thp->init.func = func;
• スタックを設定
– スレッドの戻り先:thread_end() – スレッドの開始位置:thread_init() – 汎用レジスタ
– thread_init()に渡す引数(ER0):thp
• レディー・キューに接続
コードを追ってみよう
• startup.sよりmain()が呼ばれる(P.325)
• kz_start(start_threads,
"start", 0x100, 0, NULL);
(P.317)
– 割込みハンドラの登録
• setintr(SOFTVEC_TYPE_SYSCALL, syscall_intr);
• setintr(SOFTVEC_TYPE_SOFTERR, softerr_intr);
– current = (kz_thread *)thread_run(func, name,
stacksize, argc, argv);
– dispatch(¤t->context);
thread_run() (P.306)
• タスク・コントロール・ブロックを設定
– thp->init.func = func; ← start_threads
• スタックを設定
– スレッドの戻り先:thread_end() – スレッドの開始位置:thread_init() – 汎用レジスタ
– thread_init()に渡す引数(ER0):thp
• レディー・キューに接続
ER0:引数 ER1
・
・
・
ER6 開始位置
戻り先
コードを追ってみよう
• startup.sよりmain()が呼ばれる(P.325)
• kz_start(start_threads,
"start", 0x100, 0, NULL);
(P.317)
– 割込みハンドラの登録
• setintr(SOFTVEC_TYPE_SYSCALL, syscall_intr);
• setintr(SOFTVEC_TYPE_SOFTERR, softerr_intr);
– current = (kz_thread *)thread_run(func, name,
stacksize, argc, argv); → レディー・キューに登録
– dispatch(¤t->context);
→ スレッドを実行
スレッドを実行
• dispatch() (P.296)
– 引数:スレッドのスタック・ポインタ(ER0) – 汎用レジスタの値を復旧(ER0~ER6)
– rte→プログラム・カウンタとCCRの値を復旧
もう一度 thread_run() (P.306)
• タスク・コントロール・ブロックを設定
– thp->init.func = func; ← start_threads
• スタックを設定
– スレッドの戻り先:thread_end() – スレッドの開始位置:thread_init() – 汎用レジスタ
– thread_init()に渡す引数(ER0):thp
• レディー・キューに接続
ER0:引数 ER1
・
・
・
ER6 開始位置
戻り先
スレッドを実行
• thread_init() (P.306)
– thp->init.func(thp->init.argc,thp->init.argv);
→ start_threads – thread_end();
• start_threads() (P.325)
– kz_run(test08_1_main, “command”, 0x100, 0, NULL);
kz_run() (P.322)
• パラメータを設定
– param.un.run.func = func;
↑test08_1_main
• kz_syscall(KZ_SYSCALL_TYPE_RUN,
¶m); (P.318)
– current->syscall.type = type;
– current->syscall.param = param;
– asm volatile(“trapa #0”);
→ トラップ割込みを発行
スレッドを実行
• thread_init() (P.306)
– thp->init.func(thp->init.argc,thp->init.argv);
→ start_threads – thread_end();
• start_threads() (P.325)
– kz_run(test08_1_main, “command”, 0x100, 0, NULL);
thread_end() (P.305)
• kz_exit() (P.323)
• kz_syscall(KZ_SYSCALL_TYPE_EXIT,
NULL); (P.318)
– current->syscall.type = type;
– current->syscall.param = param;
– asm volatile(“trapa #0”);
→ トラップ割込みを発行
トラップ命令による割込み
• 割込みベクタ
もう一度 割込みハンドラの登録
• setintr(SOFTVEC_TYPE_SYSCALL, syscall_intr);
• setintr(SOFTVEC_TYPE_SOFTERR, softerr_intr);
– 割込みベクタの登録
softvec_setintr(type, thread_intr);
• SOFTVECS[type] = handler;
– 割込みハンドラの登録
handlers[type] = handler;
thread_intr() (P.316)
• handlers[type](); 割込みハンドラ実行
– SOFTVEC_TYPE_SYSCALL → syscall_intr – SOFTVEC_TYPE_SOFTERR → softerr_intr
• schedule();
• dispatch();
syscall_intr() (P.315)
• syscall_proc(current->syscall.type, current->syscall.param); (P.314)
– getcurrent();
– call_functions();
• call_functions(type, p); (P.314)
– KZ_SYSCALL_TYPE_RUNの場合
→ thread_run(p->un.run.func, ...);
– KZ_SYSCALL_TYPE_EXITの場合
→ thread_exit();
もう一度 thread_run() (P.306)
• タスク・コントロール・ブロックを設定
– thp->init.func = func; ← ???
• スタックを設定
– スレッドの戻り先:thread_end() – スレッドの開始位置:thread_init() – 汎用レジスタ
– thread_init()に渡す引数(ER0):thp
• レディー・キューに接続
ER0:引数 ER1
・
・
・
ER6 開始位置
戻り先
syscall_intr() (P.315)
• syscall_proc(current->syscall.type, current->syscall.param); (P.314)
– getcurrent();
– call_functions();
• call_functions(type, p); (P.314)
– KZ_SYSCALL_TYPE_RUNの場合
→ thread_run(p->un.run.func, ...);
– KZ_SYSCALL_TYPE_EXITの場合
→ thread_exit();
kz_run() (P.322)
• パラメータを設定
– param.un.run.func = func;
↑test08_1_main
• kz_syscall(KZ_SYSCALL_TYPE_RUN,
¶m); (P.318)
– current->syscall.type = type;
– current->syscall.param = param;
– asm volatile(“trapa #0”);
→ トラップ割込みを発行
もう一度 thread_run() (P.306)
• タスク・コントロール・ブロックを設定
– thp->init.func = func; ← test08_1_main
• スタックを設定
– スレッドの戻り先:thread_end() – スレッドの開始位置:thread_init() – 汎用レジスタ
– thread_init()に渡す引数(ER0):thp
• レディー・キューに接続
ER0:引数 ER1
・
・
・
ER6 開始位置
戻り先
thread_intr() (P.316)
• handlers[type](); 割込みハンドラ実行
– SOFTVEC_TYPE_SYSCALL → syscall_intr – SOFTVEC_TYPE_SOFTERR → softerr_intr
• schedule(); → スケジューリング
• dispatch(); → スレッドを実行
スレッドを実行
• thread_init() (P.306)
– thp->init.func(thp->init.argc,thp->init.argv);
→ test08_1_main
• test08_1_main() (P.326)
– コンソールからのコマンドに応答する アプリケーション・プログラム
もうひとつのトラップ割込み
thread_end() (P.305)
• kz_exit() (P.323)
• kz_syscall(KZ_SYSCALL_TYPE_EXIT,
NULL); (P.318)
– current->syscall.type = type;
– current->syscall.param = param;
– asm volatile(“trapa #0”);
→ トラップ割込みを発行
syscall_intr() (P.315)
• syscall_proc(current->syscall.type, current->syscall.param); (P.314)
– call_functions();
• call_functions(type, p); (P.314)
– KZ_SYSCALL_TYPE_RUNの場合
→ thread_run(p->un.run.func, ...);
– KZ_SYSCALL_TYPE_EXITの場合
→ thread_exit(); (P.307) 終了メッセージを表示
タスク・コントロール・ブロックをクリア
まとめ
• OSの仕組み(スレッド)
• スレッドで動作するコマンド応答プログ ラムの作成
• OSの原型が完成!
次回の開催予定
• 9thステップ「優先度スケジューリング」
– 日時:1月15日(水)13:00~
– 場所:技術支援室 – 担当:谷口さん