RTOSベース設計での非リエントラント関数の扱い
著者:Jean J. Labrosse氏、RTOSエキスパート
リエントラント関数vs非リエントラント関数
関数がローカル変数のみを使用する場合、通常、その関数はリエントラントです(コンパイラ自身がリエントラントコードを生成すると仮定した場合)。
一方、非リエントラント関数は、関数の状態を追跡するために最低1個のグローバル変数を使用します。図1は、IARツールチェーンのオンラインドキュメントに記載されている非リエントラント関数の一覧です。
図1 IAR 標準ライブラリで提供される非リエントラント関数の一覧
非リエントラント関数のわかりやすい例として、DLIBランタイムCライブラリのstring.h: strtok()が挙げられます。この関数はASCII文字列を解析して「トークン」に分けるのに使用されます。図2に、IARドキュメントから引用したstrtok()に関する説明を示します。
図2 非リエントラント関数strtok()の説明
- 図3に示したシンプルなコード(の一部分)の例を使って、strtok()の使い方を説明します。ptokenは、string[]から抽出された次のトークンの始まり(すなわち、動物名の1つ)を示すポインタです。
- 1回目にstrtok()を呼び出すときには、解析するASCII文字列(string[])とトークン区切り文字(この例では「,」(単一のカンマ))のリストを指定します(区切り文字は2つ以上でも可)。関数は、string[]中の最初の区切り文字を見つけてNULLに置き換えるとすぐに戻り、その後、strtok()は内部ポインタをstring[]内の次の文字(次のトークン)に置きます。
- 2回目(およびそれ以降)の呼び出しに対して、1番目の引数としてNULLポインタ(ここでは0を使用)をstrtok()に指定します。2番目の引数は、トークン区切り文字を含んでいます。この区切り文字は、別のものを探す必要がある場合は変えても構いません。ここでは、カンマのままにしました。
- 再度、次のトークンを抽出するために、2回目以降のコールに対して、1番目の引数としてNULLポインタ(ここでは0を使用)をstrtok()に指定します。2番目の引数は、トークン区切り文字を含んでいます。
- NULLポインタと適当な区切り文字を、strtok()がNULLを戻すまで渡し続けます。この時点で、string[]にトークンは1つもなくなります。
1) char *ptoken;
char string[80] = “Lion,Tiger,Panther”;
2) ptoken = strtok(string, “,”);
ptoken contains “Lion”
The internal pointer points to the T in Tiger.
3) ptoken = strtok(0, “,”);
ptoken contains “Tiger”
The internal pointer points to the P in Panther.
4) ptoken = strtok(0, “,”);
ptoken contains “Panther”
The internal pointer points to the NUL at the end of string[].
5) ptoken = strtok(0, “,”);
ptoken contains a NULL pointer because there are no more tokens
図3 strtok()の呼び出し手順
トークンが多そうな場合は、strtok()をループで実装することもあるでしょう。
非リエントラント関数をRTOSアプリケーションで使用する:
ここまでの説明で最も強調したいのは、strtok()(および他の非リエントラント関数)は、内部ポインタを保持して次のトークンを追跡するという点です。
基本的に、RTOSアプリケーションでは非リエントラント関数を使わないことを推奨します。ただし、非リエントラント関数によって提供される機能が必要な場合は、RTOSアプリケーションのコードは、次のいずれかを守って作成する必要があります。
- 必要な非リエントラント関数のリエントラントバージョンを実装する。
- 1個のタスクでstrtok()(他の非リエントラント関数でも同様)のみを使用する。ただし、各タスクは、独自の非リエントラント関数も持つことが可能(複数可)。
- 非リエントラント関数は、ミューテックスを使って保護する必要のある共有リソースとして扱う。
図4の疑似コードスニペットに、ミューテックスを使って非リエントラント関数へのアクセスを保護する方法を示します。
// Allocate storage for the mutex
// Initialize the mutex before any functions that uses strtok()
void MyTask (void)
{
// Initialize task variables and I/Os as needed
while (1) {
// Wait for the event to wake up ‘MyTask’
:
:
// Get string with tokens
AcquireMutex(..);
// Extract token(s) from desired string;
ReleaseMutex(..);
:
:
}
}
図4 非リエントラント関数への排他的アクセスを確実に行う方法
strtok()を使用するタスクは、上記のようにミューテックスを獲得/解放する必要があります。あるいは、トークンを解析してトークン配列にする関数(すなわち、アプリケーションコードに対するサービス)を実装する方法もあります。そのような関数を使えば、ミューテックス実装の詳細が隠れるため、いずれのタスクでも、非リエントラント関数について気にすることなくトークンを抽出することができます。
なお、本稿ではミューテックスを使用するように指定しましたが、これはセマフォを使用する際に起こり得る上限のない優先度逆転を避けるためです。
著者について
本稿は、RTOSを使用したアプリケーション開発をテーマとしたシリーズの一部です。
Jean Labrosse(ジーン・ラブロス)氏は、Micriumの創設者であり、広く普及しているuC/OS-IIおよびuC/OS-IIIカーネルの作成者です。組込みソフトウェアのuC/ラインの発展のために積極的に取り組んでいます。
組込みシステム市場での豊富な経験を有し、市場を知り尽くしているLabrosse氏は、Weston Embedded Solutionsの主席アドバイザおよびコンサルタントとして、現行のRTOS製品の将来的な方向性の策定に尽力しています。Weston Embedded Solutionsは、Micriumのコードベースから生まれた信頼性の高いCesium RTOSファミリー製品のサポートと開発を専門としています。