連載26-OSレスでSOLIDコアサービスを使うー3
前々回、前回と、統合開発環境であるSOLIDからTOPPERSカーネルを取り外しています。
今回は、前回できなかった機能のうち、ぜひご紹介したい機能「ローダー機能」について書いていきます。
1. ローダー機能
ローダー機能も、TOPPERSカーネルがなくとも使えます。
「SOLID コアサービス」が持っている機能なので、OSに依存していません。
本連載第18回でご紹介した内容を試してみましょう。
https://note.com/yn_2022/n/n3185aeeb8878
詳細な手順等はこのURLを参照して頂ければ幸いです。
DLL形式のロードモジュールを作成し、ロードしてみます。
① DLLロードする側の準備
MMUの仮想アドレスにDLLロードに必要な領域をアサインし、以下URLのプログラムをコピー&ペーストしました。
https://solid.kmckk.com/SOLID/doc/latest/tutorial/create_loadable_app_project.html#id45
#include <solid_log.h>
#include "sample1.h"
#include "solid_loader.h"
void test_loader()
{
int ercd = 0;
SOLID_ADDRESS addr;
int ret = 0;
/* モジュールロード (ロード時に名前を付けます 例では “DLL1”), 領域はmemory_map.smm の DLLAREA */
ercd = SOLID_LDR_LoadDLL("DLL1", "\\ROM\\0x22000000, 0x24b00", SOLID_LDR_ADDR_DLLAREA);
if (ercd == SOLID_ERR_OK)
{
/* ロードした DLL が実行可能かチェック */
ret = SOLID_LDR_CanExec("DLL1", &addr);
if (ret > 0)
{
/* 呼び出したい関数のアドレスをDLL名とシンボル名から検索 (例では DllFunc) */
ret = SOLID_LDR_GetDllAddr("DLL1", "DllFunc", &addr);
if (ret == SOLID_ERR_OK)
{
void (*pFunc)() = (void (*)())addr; /* アドレスを関数ポインタに型変換 */
pFunc(); /* ロードしたコードを実行 */
}
else
{
SOLID_LOG_printf("DllFunc() NOT found\n");
}
}
}
}
void nonos_main(void)
{
SOLID_LOG_printf("nonos_main() is running...\n");
test_loader();
:
:
② ロードモジュールの準備
DLL方式のロードモジュールを準備しました。
プロジェクト作成時、「ソリューションプロパティファイル」を選択する際には、先ほどのOSレスソリューションのプロパティファイルを選択しました。
そして、ロードモジュール側のソースコードテンプレートでkernel.hがインクルードされているので、削除します。
ロードモジュール側の関数は以下です。
#include "dll.h"
#include "solid_log.h"
/**
* DLL関数
*/
DLL_EXPORT void DllFunc(void)
{
SOLID_LOG_printf("DllFunc.\n");
}
コールされたら「DllFunc.」と表示します。
③ ロードするための準備
以下URLの内容を行います。
https://note.com/yn_2022/n/n3185aeeb8878#eb59f610-a8e7-4824-a33a-ba99db3e08a5
④ ロードモジュールプロジェクトから実行
実行し、ブレークしました。
ロードモジュールが実行され。関数内でブレークしています。
シリアルターミナルも見てみましょう。
DllFunc. と表示されました。
⑤ ロードするメインプロジェクトから実行
SOLIDを一旦終了し、メインプロジェクトで再起動します。
実行し、適当なところでブレークの後、ロードモジュールの関数に遷移しました。
ロード機能が動作できていることがわかります。
2. サービスコールを使ったやりとり
OSカーネルが存在しないので、svc命令を自由に使えます。
svcハンドラを実装し登録しておけば、svc命令によって例外を発生させることで、svcハンドラを起動することが可能なはず。
2.1 メインアプリとロードモジュール間のやりとりおさらい
ちょっとその前に、ここでメインアプリとロードモジュール間のやりとりおさらいしてみましょう。
① メインアプリからローダモージュールの関数を呼び出し
ローダモージュールがメインアプリケーションから呼び出される際、呼び出したい関数のシンボルをエクスポートしておく必要があります。
先程使用したDLL方式の場合、対象の関数にDLL_EXPORTを定義してエクスポートします。
もう一つの方法、SOLID独自方式の場合、APP_EXP.txtファイルにあらかじめ記載しておきます。
こうすることで、メインアプリケーションがロードモジュールをロードする際に、シンボルを読み込む事ができます。
② ローダモージュールからメインアプリの関数を呼び出し
考え方は①と同じですが、ローダ側でひと手間必要です。
SOLID_LDR_RegisterSymbol API で、メインアプリのシンボルをローダーに登録することで、ローダブルアプリケーションにIMPORTできます。
ご参考:
https://solid.kmckk.com/SOLID/doc/latest/tutorial/create_loadable_app_project.html#export-import
基本的には、そうなのですが。
OSカーネルが存在しない今、モジュール間のやりとり方法として、サービスコールを使用してみましょう。
2.2 サービスコールを使うには
サ―ビルコールの使い方をまとめてみます。
① svcハンドラ登録例
以下のようなコードで可能です。
// SVCハンドラの登録。登録時には、有効なSVC番号の範囲を指定すること
vector_info_svc.func = vector_svc;
vector_info_svc.param = NULL;
vector_info_svc.startSvcNo = 0;
vector_info_svc.endSvcNo = 100;
ret = SOLID_SVC_Register(&vector_info_svc);
if (SOLID_ERR_OK != ret) {
solid_cs_assert(false);
}
② svcハンドラ実装例
以下のように実装できます。
SOLID_SVC_HANDLER vector_info_svc;
// SVCベクタ関数。コアサービス内のsync abortベクタからコールバックされる
void vector_svc(void *param, int svc_no, SOLID_CPU_CONTEXT *context)
{
// このハンドラは割込み禁止状態で呼び出される。
SOLID_LOG_printf("Calld SVC, No=%d\n", svc_no);
return;
}
③ svc命令発行
asm("svc #0");
を書けばOK。
svcハンドラを実装し登録、それをsvc命令によって呼び出す道筋が立ちました。
svcハンドラはメインアプリ上に実装できるので、
・svc命令により実行される側:メインアプリ
・svc命令を発行する側:ロードモジュール
となりますね。
2.3 試してみる
では、試してみましょう。
メインアプリのプログラムはこうなりました。
#include <solid_log.h>
#include "sample1.h"
#include "solid_loader.h"
#include <solid_vector.h>
#include <solid_cs_assert.h>
#include <stdbool.h>
// 本変数の領域はスタックへの確保は NG。静的割当、もしくは mallocした有効な領域であること
SOLID_SVC_HANDLER vector_info_svc;
// SVCベクタ関数。コアサービス内のsync abortベクタからコールバックされる
void vector_svc(void *param, int svc_no, SOLID_CPU_CONTEXT *context)
{
// このハンドラは割込み禁止状態で呼び出される。
SOLID_LOG_printf("Calld SVC, No=%d\n", svc_no);
return;
}
void test_loader()
{
int ercd = 0;
SOLID_ADDRESS addr;
int ret = 0;
/* モジュールロード (ロード時に名前を付けます 例では “DLL1”), 領域はmemory_map.smm の DLLAREA */
ercd = SOLID_LDR_LoadDLL("DLL1", "\\ROM\\0x22000000, 0x24b00", SOLID_LDR_ADDR_DLLAREA);
if (ercd == SOLID_ERR_OK)
{
/* ロードした DLL が実行可能かチェック */
ret = SOLID_LDR_CanExec("DLL1", &addr);
if (ret > 0)
{
/* 呼び出したい関数のアドレスをDLL名とシンボル名から検索 (例では DllFunc) */
ret = SOLID_LDR_GetDllAddr("DLL1", "DllFunc", &addr);
if (ret == SOLID_ERR_OK)
{
void (*pFunc)() = (void (*)())addr; /* アドレスを関数ポインタに型変換 */
pFunc(); /* ロードしたコードを実行 */
}
else
{
SOLID_LOG_printf("DllFunc() NOT found\n");
}
}
}
}
void nonos_main(void)
{
int ret;
SOLID_LOG_printf("nonos_main() is running...\n");
// SVCハンドラの登録。登録時には、有効なSVC番号の範囲を指定すること
vector_info_svc.func = vector_svc;
vector_info_svc.param = NULL;
vector_info_svc.startSvcNo = 0;
vector_info_svc.endSvcNo = 100;
ret = SOLID_SVC_Register(&vector_info_svc);
if (SOLID_ERR_OK != ret)
{
solid_cs_assert(false);
}
test_loader();
while (1)
{
}
}
簡単に言うと、
・svcベクタ関数を準備。この中で"Calld SVC, No=<svc番号>”を表示する
・そのsvcベクタを登録。
です。
では次、ロードモジュールでsvc命令を番号0で発行させてみましょう。
ロードモジュールのソースコードは、こうなりました。
#include "dll.h"
#include "solid_log.h"
/**
* DLL関数
*/
DLL_EXPORT void DllFunc(void)
{
SOLID_LOG_printf("DllFunc.\n");
asm("svc #0");
}
では実行してみましょう。
ロードモジュール側のワークスペースを起動して確認してみます。
メインアプリからDLL内の関数がコールされたところで一旦ブレークしてみます。
では、svc命令を実行してみましょう。
シリアルターミナル上に、"Calld SVC, No=0”と表示されました。
ロードモジュールでのsvc命令発行により、メインアプリのsvcベクタ関数が呼び出されたことがわかります。
メインアプリのsvcベクタ関数で、svc番号もきっちり受け取れているので、あとはsvcベクタ関数に処理を追加していけば、メインアプリの好きな関数をコールすることができますね。
ロードモジュール側のワークスペースから試してみましたが、もちろんメインアプリ側のワークスペースを起動して動作することも可能です。
メインアプリ側のワークスペースから試してみます。
svcベクタに飛んできました。
svc番号も0をきちんと受け取っています。
3.まとめ
OSカーネルを取り外した状態で、ロード機能を使用してみました。
OSのオーバーヘッドがものすごく気になるような超絶クリティカルなシステムや、CortexAの性能を余すことなく意図する方向に引き出したいような場合、RTOSを使わずに実装するこのやり方の方が、適しているかもしれませんね。
さて、次回はコアサービスも取り外してみる予定です。
こんなにいろいろ取り外して良いものかと筆者的には思うのですが、次回はいよいよベアメタルです。