More Related Content
Similar to 【学習メモ#11th】12ステップで作る組込みOS自作入門 (20)
【学習メモ#11th】12ステップで作る組込みOS自作入門
- 5. タスク間通信
● 組込みOSのカーネルはできるだけコンパクトに
しておきたい
– 高機能やリアルタイム性を求められるため
● だから、デバイス・ドライバや、ファイル・シ
ステムなどといったリアルタイム性を保証でき
ないサービスは、カーネルの外でスレッドとし
て実装する
● とはいえ、そういったアプリは一般に単体で動
作できるものは少ない
– 機能別にスレッドに分けることが多い
– そこでスレッド同士でデータのやり取りができるタ
スク間通信をできるようにする
- 6. システム・タスク
● 基本サービスを行うスレッドのことを一般にシ
ステム・タスクと呼ぶ
– 任意サイズのメモリ管理を実装するとして、OSの機
能として実装すると空きメモリの検索時間が保証で
きない
– そこでメモリ管理を行うようなスレッドを作成し、
任意サイズ必要な場合はそのスレッドにタスク間通
信で依頼して領域を割り当ててもらう、などといっ
た実装ができる
● こういったOSのカーネルとセットで利用するス
レッドがシステム・タスク
- 7. ユーザ・タスク
● OSのユーザ側で作成するアプリケーション・プ
ログラムは、ユーザ・タスクと呼ぶ
– ユーザが使う目的のスレッドと考えれば良いかな
● OSからみればシステム・タスクもユーザ・タス
クもOSで動作しているスレッドにすぎない
– 両者の違いは使われ方の違いなだけ
- 9. 関数の再入
● タスク間通信が必要な理由はもうひとつ関数の
再入を避けるというものがある
● 関数の再入とは、あるスレッドが特定の関数を
実行している最中に割込みによって別スレッド
が動作し、その別スレッドがその関数を実行し
てしまうこと
– 別々のスレッドで同じタイミングで同じ関数を実行
してしまうようなことかな
● 関数内でstaticなデータをやり取りしている場
合、内容を上書きし合うといった問題が起きる
- 10. 再入問題の回避
● 回避する手段はいくつかの方法がある
– 仮想メモリを実装し、タスクをスレッドでなくプロ
セス化する
– サービスをOS内部に実装し、システム・コールを利
用して呼び出すようにする
– 関数をリエントラントな構造にする
– 関数内の排他が必要な部分に履いた処理を入れる
(割込み禁止など)
– 再入が発生しない設計にする
– サービスをスレッド化する
- 11. 仮想メモリを実装
● 関数の再入はメモリを共通資源として複数のス
レッドが扱っていることに問題がある
● そこでタスクごとに独立した仮想的なメモリ空
間を割り当てれば良い
● この仮想的なメモリを仮想メモリ、そのメモリ
のアドレスを仮想メモリと呼ぶ
● 仮想メモリにより動作するアドレス空間が独立
しているタスクをプロセスと呼ぶ
– スレッドはアドレス空間は共通なので、他のスレッ
ドの変数などにアクセスできるが、プロセスではそ
ういったことはできない
● スレッド・モデル、プロセス・モデルなどとし
て区別される
- 12. MMU
● 仮想メモリではMMUと呼ばれる専用ハードウェ
アが仮想アドレスから実際のメモリアドレスに
アドレス変換をした上で行われる
– 実際のメモリを物理アドレスと呼ぶ
● タスクごとにメモリ空間は独立するので、結果
的にメモリ保護を行うこともできる
● メリットはいろいろあるが組込みOSには向かな
い
– H8にはMMUが無い
– リアルタイム性や高速性に向いていない
– そもそも組込みOSではメモリ保護は必要ない
- 13. サービスをOS内部に実装
● OSの機能としてカーネル内部に実装する方法が
ある
● システム・コールによってOSに依頼する形
● とはいえ、OSの肥大化に繋がるし、再入を防止
したいサービスを全てカーネルに含ませるとい
うポリシーの無い設計になってしまうので、あ
まり良い手段とはいえない
- 14. 関数をリエントラントな構造にする
● 関数が再入されても問題が発生しない構造に
なっていることをリエントラント(再入可能)と
言う
– リエントラントにするには、たとえば静的変数では
なく自動変数を利用するなどするといった手段があ
る
● ただし、関数内部でさらに別の関数を呼び出し
ている場合は、その関数もリエントラントにし
なきゃいけない
● つまりライブラリ関数全体をリエントラントに
する必要がある
- 15. 排他処理を入れる
● 静的変数を操作している部分で割込み禁止にす
ることで、スレッドのディスパッチを防止する
ことで再入されなくしてしまう方法もある
– INTR_DISABLEやINTR_ENABLEとか使う
void log_output(char *message)
{
static char buf[256];
time_t t;
time(&t)
INTR_DISABLE;
strcpy(buf, ctime(&t));
strcpy(buf + strlen(buf), message);
puts(buf);
INTR_ENABLE;
}
- 16. 排他
● ある資源にアクセスしている最中に他の処理が
その資源にアクセスしないようほ保証すること
を排他と言う
● 排他には割込み禁止/許可の他に、ロック、セ
マフォ、ミューテックスと呼ばれる機能を利用
する方法がある
● ロックをアプリケーション・レベルで実現しよ
うとすると様々な問題があるので、一般にセマ
フォという排他の仕組みをOS側で提供する
- 17. セマフォ
● セマフォとはロックをOS側で行うような仕組み
獲得・解放をカウンタで管理することで、複数
のスレッドが再入する場合のスレッド数の上限
を管理することができる
● 上限が1のセマフォはバイナリ・セマフォと呼
ばれ、常にひとつのスレッドしか実行できない
● ミューテックスはバイナリ・セマフォに近い
void log_output(char *message)
{
.
.
kz_getsem(ID); ←セマフォ獲得
strcpy(buf, ctime(&t));
strcpy(buf + strlen(buf), message);
puts(buf);
kz_relsem(ID); ←セマフォ解放
}
- 18. 割込み禁止でする排他の話
● 割込み禁止から有効に戻すまでの間を割り込み
禁止区画と呼ぶ
– INTR_DISABLEからINTR_ENABLEの間
● この方法での排他は割込みの遅延(遅延割込み)
が発生する問題がある
– 割込み禁止中に割込みが発生した場合、現在行なっ
ている割込み処理を終えてから発生するので、割込
みが遅延する形になる
– 割込み禁止区画が長いとキーボードを叩いても反応
が鈍いとか、マウスの動きが鈍くなったりする
● 一般に「割込み処理は短く」「割込み禁止は最
小に」が鉄則
- 19. 再入が発生しない設計にする
● スレッドの優先度を工夫して優先度の大小の関
係で動作が奪われないようにしたりできるが
● 優先度が動作に依存するため慎重に設計する必
要がある
● 現実的には割込み禁止にするなど確実で小回り
の効く対策が人気
● 排他が不要な構成にするというのは理想だけ
ど、難しいな
- 20. サービスをスレッド化する
● 再入の根本的な原因はひとつの資源を複数のス
レッドから操作してしまっていること
● 簡単な解決策は資源を管理するスレッドを実装
して、このスレッドを通して資源を操作する
● これができるようにするには、スレッド同士の
通信ができるようにならなきゃいけない
– つまりタスク間通信が必要
● 組込みOSはカーネルをコンパクトにしたいので
基本的にサービスはスレッドとして実装する
– サービスを利用する場合はタスク通信でスレッドに
依頼する
- 22. メッセージ
● KOZOSのタスク間通信はOS内部からシステム・
コールを発行することで、送信と受信の間に構
造体をかませてやりとりするになっている
– この構造体をメッセージ・ボックスと本書では呼ん
でいる
– また、KOZOSのタスク間通信をメッセージと呼ぶ
- 23. メッセージの仕組み
● メッセージのやり取りは送信用のシステム・
コールを呼び出して、受信側でメッセージ受信
用のシステム・コールを呼び出して受け取る
● 受信用のシステム・コールを呼び出してもメッ
セージが送信側で送信されていない場合は、ス
リープして待ち合わせる
– 待ち合わせによりスリープすることを一般に
ブロックと言う
● 受信用のシステム・コールを読んでいないのに
送信側が複数送信を行ったときは、メッセージ
はキューに蓄えられる
- 24. メッセージのシステム・コール
● kz_send()とkz_recv()
– どちらも第1引数にメッセージID、第2と第3
で整数値とchar型のポインタを持つ
● 第2と第3でint型のデータ(2バイト)とchar型の
ポインタ(4バイト)を渡せるようにしている
– バッファのサイズとアドレスを渡すと
か、argc、argv[]形式で任意のデータを渡
す、といった融通が効くから
- 25. メッセージで同期的処理もできる
● スレッド同士で情報のやりとりだけではなく、
動作タイミングを取り合うこともできる
– あるスレッドの処理が完了してからもう一方
のスレッドで処理を開始するといったことが
可能
● スレッドの動作状態と関係無く別の動作が行わ
れることを非同期処理と言う
- 27. プログラムの修正と追加
● 修正ファイル
– defines.h...メッセージIDの定義
– syscall.h,syscall.c...システム・コール追加
– kozos.h,kozos.c...システム・コール追加
– main.c...起動するスレッドの修正
– Makefile
● 追加ファイル
● test11_1.c,test11_2.c
- 28. メッセージ送受信
● メッセージのやり取りはシステム・コールに
よって行い、メッセージボックスの構造体を間
に挟んで行う
● 特に難しい処理はないが、メッセージボックス
はキューになっている
– リンクリスト構造で、headとtailのポインタを持つ
– tailポインタを持つのはリンクリストの最後尾を毎
回検索するのが無駄であるため
- 29. メッセージ送受信の流れ
● 送信側は基本的にメッセージボックスにメッ
セージ(構造体)を保存するだけのことをする
– 受信待ちスレッドがある場合は受信処理を行い、カ
レントスレッドをレディ・キューに繋ぎ直す
● 受信側は受信するメッセージがない場合はス
リープ状態に入る
– スリープ状態で送信処理が行われたときに、そちら
で受信処理が行われるわけ
● スリープする必要がなければメッセージを取得
する
– メッセージボックスは複数のメッセージを持つ場合
にキューとなり、メッセージ取得は順番に取得が為
される
- 31. プログラムの実行
/Users/sandai/12step/src/11/os% sudo cu -l /dev/tty.usbserial-FTG6PQ4H
.
.
kzload> run
starting from entry point: ffc020
kozos boot succeed!
test11_1 started.
test11_1 recv in.
test11_2 started.
test11_2 send in.
test11_1 recv out.
static memory
.
.
.
test11_2 recv out.
allocated memory
test11_2 exit.
test11_2 EXIT.
- 33. まとめ
● タスク間通信を実装したことでOSの重い処理を
システム・タスクとしてスレッド化できるよう
になった
● OSの機能をコンパクトにする設計をマイクロ・
カーネル、様々な機能を全て詰め込んだものを
モノリシック・カーネルと呼ぶ