2010년 2월 14일 일요일

__use_no_semihosting_swiエラー対策



セミホスティングとはアプリケーションによるサービス要求を、デバッガを通してホストコンピュータで動作する機能です。サービス要求方法としては標準ライブラリ関数を呼び出すことによる間接的な方法もありますが、SWI命令語を直接呼び出す方法もあります。

例えば、ある関数で時間を得るためにtime関数を呼び出したとします。time関数は以下のようになっています。

time
    (省略)
    0x00008028: ef123456 V4.. SVC #0x123456 ; formerly SWI
    (省略)

標準ライブラリの大半は上記のようにSVCを呼び出してホストにサービス要求を依頼します。これは通常デバッグ環境で必要であり、出荷するバージョンだと困ります。スタンドーアロンで動作できるように、すなわちそれ自体の持っている機能だけで動作しなければなりません。

解決方法としてセミホスティング機能を使っている箇所を見つけ、直接実装する方法があります。
以下のように明示的にセミホスティングを使わないように指定できます。

C言語の場合
#pragma import(__use_no_semihosting_swi)

アセンブリの場合
IMPORT __use_no_semihosting_swi

再度ビルディングすると以下のようなエラーが発生します。

Error: L6915E: Library reports error: __use_no_semihosting_swi was requested, but time was referenced

エラーは“セミホスティングを使用しないと宣言したのにどこかでtimeを参照している”という意味です。
さて、どこで参照しているでしょうか?希望が大きいプロジェクトだとソースコードが多くて見つけるのも大変です。またサードーパーティー会社から提供して頂いたライブラリが参照しているかも知れません。

以下のようにリンカーオプションを追加しますと参照している箇所を教えてくれます。

armlink (オプション省略) --list info.map --verbose

そして生成されたinfo.mapファイルから「__I_use_semihosting」を検索します。

Loading member sys_time.o from c_4.l.
    definition: time
    reference : __I_use_semihosting

すなわち、time関数でセミホスティングの機能を使っていました。
セミホスティング機能を使わないようにtime関数を直接実装して解決できます。

2010년 2월 12일 금요일

コンテキストスイッチング速度の測定方法



リアルタイムOSをターゲットに移植(ポーティング)するためにはいくつかのプロセッサ依存コードが必要です。例えば、タスク間に切り替えを行うためには現在動作している最中のタスクのコンテキストを保存します。この後、何らかの理由(例。セマフォに待つ)でディスパッチャはレディー状態になったより優先順位が低いタスクのコンテキストを復帰させます。このようになタスク間の切り替えを“タスクレベルのコンテキストスイッチング”といいます。

一般的にC言語ではプロセッサのレジスタを自由自在にシステムスタックに保存したり、復帰することは不可能です。こういう制限を解決するためには直接プロセッサに依存するアセンブリコードを書くしかありません。プロセッサに依存する部分を“ポート”といいます。ポートを開発するためにはプロセッサの知識とリアルタイムOSに関する知識が必要であり、単純な作業ではありません。

ここではポートの一部であるタスクレベルのコンテキストスイッチングを測定する方法について説明します。 外部から観察するためには特定GPIOとオシロスコープが必要です。まず次のように優先度が高いタスクと低いタスクを用意します。

void HPT_Task(void)
{
    LED_ClrLED0();
    for(;;) {
        OS_Suspend();
        LED_ClrLED0(); // D
    }
}

void LPT_Task(void)
{
    for(;;) {
        LED_SetLED0(); // A
        LED_ClrLED0(); // B

        LED_SetLED0(); // C
        OS_Resume( &HPTaskTcb );
    }
}

1.リアルタイムOSが起動すると優先順位が高いHPT_Taskを動作させます。
2.HPT_TaskはOS_Suspend関数を呼び出して待機状態になります。
3.カネルのディスパッチャにより次の優先度を持つLPT_Taskを動作させます。
4.LPT_TaskはLED_SetLED0関数を呼び出して特定GPIOをハイレベルにしておき、 HPT_TaskのTCBを引数としてOS_Resume関数を呼び出します。再びディスパッチャによりHPT_Taskが 動作されます。
5.HPT_TaskはOS_Suspend関数の続きから実行し、今度は特定GPIOをローレベルにします。

オシロスコープで各空間を測定しますと(A-B)はGPIO出力関数にかかる時間になり、(C-D)はタスクレベルのスイッチングにかかる時間になります。(C-D)-(A-B)を計算したら、タスクレベルのコンテキストスイッチングにかかる正確な時間を確認できます。