トロンフォーラム

第6回 ミドルウェア

ミドルウェアとは

今回はミドルウェアについて説明します。
ミドルウェア(middleware)とは、RTOSとアプリケーションの中間(ミドル)に位置するソフトウェアです。 具体的にはファイルシステムやネットワークのプロトコルスタック(TCP/IP)のように、RTOS に機能を追加するモジュールのことです。 デバイスドライバはミドルウェアに含めない場合もありますが、共通点も多いため、ここでは含めて考えることにします。

組込み開発ではRTOSとミドルウェアの上にアプリケーションを開発する形になり ますが、組込み機器のハードウェアは一般にその機器に固有のものですので、ミドルウェアやデバイスドライバ自体もユーザが自分で開発したり移植したりするケースが少なくありません。 今回は、ミドルウェアのうち、前半ではT-Kernelにおけるデバイスドライバの動作や仕組みについて説明し、後半ではT-Kernel用に開発されたオープンソースの基本ミドルウェアである T2EX を取り上げます。

デバイスドライバの呼び出し

デバイスドライバとは

組込み機器には、例えば、表1に示すようにさまざまなデバイスが組み込まれています。 このようなデバイスのハードウェアを制御するソフトウェアを、デバイスドライバ と呼びます(以下、単に「ドライバ」と呼ぶ場合があります)。 各ドライバは、組込み機器全体を制御するアプリケーションから呼ばれて動作します※1

表1: 組込み機器で使用されるデバイスの例
(a)入力デバイス ボタン、タッチパネル、マイクなど
(b)出力デバイス LED、液晶画面、スピーカーなど
(c)通信デバイス 無線 LAN、Bluetooth、シリアルポート(RS-232C) など
(d)ストレージデバイス フラッシュメモリ、ハードディスクなど
デバイスドライバの仕様書の作成

組込み機器の開発では、その機器に特有のハードウェアやデバイスを制御することが多いため、アプリケーションプログラムに加えて、デバイスドライバ自体も自分で開発するケースがあります。そうなると、ドライバの開発に取りかかる前に、まずドライバの仕様書を作成する必要があります。

デバイスドライバを呼び出すためのAPI(Application Program Interface)はT-Kernelの仕様書で定められていますが、これはどのデバイスにも共通となる「枠組み」にあたる部分であり、個々のデバイスに依存した仕様はデバイス毎に決める必要があります※2

T-Kernelのデバイスドライバでは、デバイスに依存した特殊な処理を指定するために、「属性データ番号」と呼ばれる負の整数値を使用します。デバイスからの読み込みを行うtk_rea_dev、tk_srea_devやデバイスへの書き込みを行うtk_wri_dev、tk_swri_devなどのAPIには、読み込みや書き込みの開始位置を示すstartというパラメータがありますが、ここに属性データ番号を指定することにより、デバイスに特有の処理を行います。

したがって、デバイスドライバの仕様を決めることは、そのデバイスで使用する属性データ番号の値と、その値に対するデバイス固有の処理を定義することに相当します。また、そのデバイスをオープンするときの名称として使用するデバイス名も決める必要があります。これらの定義をデバイスドライバの仕様書として文書化するとともに、属性データ番号には分かりやすいニモニックを付け、プログラム中で使用するヘッダファイルを作成します。デバイスドライバの仕様やヘッダファイルを明確にすることにより、アプリケーションの開発とデバイスドライバの開発を分担して作業できるようになります。

ここでは、比較的シンプルなデバイスドライバの例として、LEDの点灯を制御するドライバを開発します。表2に、このデバイスドライバの外部仕様書の例を示します。

表2: LED ドライバの外部仕様書の例 (要点のみ抜粋)
デバイス名 "LED"
属性データ番号 DN_LED_CHIP = -100 : 16ビット (UH 型) データ
各ビットがチップ LED の状態に対応
DN_LED_SEG = -101 : 16ビット (UH 型) データ
7セグメント LED に表示する数字に対応
デバイスドライバのAPI

まず、このドライバを使う側となるアプリケーション開発者の立場から、T-KernelのAPIを使ったドライバの呼び出し方法を見てみましょう(リスト1)。

  1. tk_opn_dev (T-Kernel open device の意味) でドライバをオープンします。 文字列 "LED" はデバイス名です。
  2. tk_srea_dev (T-Kernel synchronous read device) でデバイスからデータを読み込むか、または tk_swri_dev (T-Kernel synchronous write device) でデバイスにデータを書き込みます。 定数 DN_LED_SEG は属性データ番号です。
  3. tk_cls_dev (T-Kernel close device) でドライバをクローズします。
同期型と非同期型

ここでtk_srea_dev や tk_swri_dev の "s" は同期型(synchronous)を意味します。 この場合、ドライバ内部で読み書きが終了するまで、アプリケーションは実行が中断します。 一方、"s" のつかない tk_rea_dev や tk_wri_dev は非同期型(asynchronous)で、ドライバ内部で読み書きを行っている間も、アプリケーションは並行して動作します。 この場合は、tk_wai_dev (T-Kernel wait device) によって読み書きの完了を待ちます。

図1 ドライバ呼び出しの同期型と非同期型

図1 ドライバ呼び出しの同期型と非同期型

非同期型を活用すると、CPU を効率よく使って性能を向上できる場合があります。 例えば圧縮された音声データを展開しながら出力する場合、ドライバでの音声出力中も、DMA転送等でCPUが空いている時間を利用して、その間にアプリケーション側で先回りして次の音声データを展開しておくことができます。

デバイスドライバの内部構造

デバイスドライバの3層構造

次に、デバイスドライバを開発する側の立場から、ドライバの内部構造を見てみましょう。ドライバは図2のような3層構造になっています。

図2 デバイスドライバの内部構造

図2 デバイスドライバの内部構造

  1. アプリケーションから読み込みや書き込みの要求を出すと、T-Kernel/SM (System Manager) を経由して、ドライバのインタフェース層が要求を受け付けます。
  2. インタフェース層では、複数の要求を排他制御しながら、論理層を呼び出します。
  3. 論理層では、デバイスのハードウェアの詳細に依存しない共通処理を行います。論理層は物理層を呼び出します。
  4. 物理層では、デバイスのハードウェアを直接制御します。

基本的な考え方として、ドライバの論理層はデバイスの種類ごとに実装し、物理層は個々のハードウェアごとに実装します。 例えばLANドライバの場合、LANドライバとして共通の処理を行う部分を論理層として実装し、個々のNIC(Network Interface Card)やLANの制御チップに依存する部分は物理層として実装します。

インタフェース層の選択

インタフェース層としては、T-Kernel用のライブラリとして次の2種類が提供されていますので、通常はそのどちらかを選んでそのまま利用します。

  • SDI (単純デバイスドライバインタフェース層: simple device driver interface layer)
    ドライバに渡された要求パケットは、その場ですぐに処理されます。 論理層は関数呼び出しの構造になります。 ドライバ内で不定期の待ちに入ることはできず、すみやかに読み書きが終わることが前提です。 渡された要求に対して、処理を途中で中止することはできません。 読み書きは同期型のみです※3
  • GDI (汎用デバイスドライバインタフェース層: general device driver interface layer)
    ドライバに渡された要求パケットは、いったんキューに格納されます。 ドライバ内のタスクが、キューから要求パケットを取り出して処理します。 ドライバ内で不定期の待ちに入ることが可能で、待ちに入った要求の中止も可能です。 同期型だけでなく非同期型の読み書きもできます。

「不定期の待ち」というのは、デバイスや通信相手などの状況の変化を待つことによって発生する待ち状態のことです。例えば無線 LAN ドライバ内での受信処理では、通信相手からの送信データが到着しないと処理が進みませんので、実際のデータ到着まで不定期の待ちに入ります。このようなケースでは、GDI を使ってドライバを実装します。

これに対して、例えば今回の例で説明するLEDドライバの処理は、LEDが接続されたメモリアドレスへのデータ設定を行うだけです。すなわち、ドライバ側のプログラムが一方的に処理を進めるだけでよく、デバイス側の状態変化を待つ必要はありません。このようなデバイスドライバは、SDI で実装可能です。

SDI を使ったデバイスドライバの実装

論理層の実装

SDI を使ったドライバの実装例として、LEDドライバの論理層のプログラムの主要部分をリスト2に示します。 コールバック関数として、読み込みを担当するリード関数と、書き込みを担当するライト関数を定義して、 SDefDevice (SDI define device) で登録しています。 リード関数とライト関数では、アプリケーションから渡された属性データ番号に対応する物理層を呼び出します。

物理層の実装

物理層では、上記の論理層から呼ばれる各ルーチン (init_chip、init_seg、read_chip、write_chip、read_seg、write_seg) を実装します。 この部分は LED のハードウェアの詳細に依存します。

リスト3は、あるハードウェアでの write_seg (7セグメントLED書き込み) の一例です。 7セグメントLEDは、図3のようにaからgの7個(小数点を含めて8個)のセグメントで構成されます。 例えば「4」を表示する場合は、b,c,f,gを点灯し、残りのa,d,e,小数点を消灯します。 この実装例では、点灯と消灯のパターンを、メモリ空間上にマップされた I/O の特定アドレスに out_w で書き込むことで、LEDに出力しています。

図3 7セグメントLEDの各セグメント

図3 7セグメントLEDの各セグメント

GDI を使ったデバイスドライバの実装

要求処理タスク

GDI を使ったドライバの実装例として、無線 LAN などの通信系デバイスのドライバの例を図4に示します。
ここでは割込みとイベントフラグを使っています。
イベントフラグの詳細は本連載の「第3回 タスク間同期・通信機能1」をご参照ください。 また割込みについては本連載の第10回で取り上げる予定です。

図4 GDI を使ったデバイスドライバの内部構造の例

図4 GDI を使ったデバイスドライバの内部構造の例

  1. ドライバの論理層では、「要求処理タスク」をアプリケーションと並行動作させます。
  2. 要求処理タスクは GDI_Accept で要求パケットを1つずつキューから取り出し、要求パケットの内容に応じて、対応する物理層を呼び出します。なお、要求パケットの中には、要求の種類(読み込み/書き込みの区別)や、tk_rea_devなどのAPIのパラメータとしてアプリケーションから渡された情報が入っています。
  3. 物理層では、不定期の待ちに入ることが可能です。 例えば受信要求を処理する場合、通信相手からのデータが来たときに割込みが発生するように設定した上で、イベントフラグのいずれかのビットがセットされるまで (TWF_ORW を指定)、tk_wai_flg で待ちに入ります。
  4. データが到着して割込みが発生したら、割込みハンドラの中で tk_set_flg を発行し、イベントフラグをセットします。
  5. tk_wai_flg の待ちが解除されますので、要求処理タスクが受信されたデータを取り出します。
  6. 要求処理タスクは、GDI_Reply でアプリケーションに受信データを返します。
要求の中止

一方、受信待ちをしている間にそのデバイスがクローズされたり、アプリケーション側に終了要求などのタスク例外が発生した場合は、ドライバ内の待ちを中止し、要求元のアプリケーションにエラーを戻す必要があります。GDI では、中止の処理を使って、こういった状況に対応します。中止の処理は次のように実装します。

  1. 割込みが発生する前に中止要求があった場合は、tk_set_flg でイベントフラグの別のビットをセットします。
  2. これによって、要求処理タスクのtk_wai_flgの待ちが解除されます。イベントフラグのビットパターンを参照することにより、データを受信できたのか、中止要求があったのかが分かります。
  3. 要求処理タスクは、GDI_Reply でアプリケーションに中止のエラーを返します。
  4. ここまでのまとめ

    アプリケーションからは、オープン/リード/ライト型の API でデバイスドライバを呼び出します。
    デバイスドライバを実装するには、インタフェース層として SDI または GDI を選択します。
    SDI を使う場合は、コールバック関数の定義だけでドライバを実装できます。 一方 GDI を使う場合は、ドライバ内の要求処理タスクが処理を行います。 このため、ドライバ内で待ちに入ることができるなど、柔軟な処理を実現できます。

Return Top