内容
カーネルにモジュールを埋め込む
残念ながら、InfernoにはCで実装されたモジュールを動的にロードする方法がないため、OSカーネルに直接組み込む必要があります。 (変更ごとにInfernoを再構築する必要があるため、これにより開発が少し複雑になります。幸いなことに、必要な部分的な再構築には約10秒かかります。)
モジュールを埋め込むには、いくつかのファイルを変更する必要があります:
libinterp/mkfile
、
module/runt.m
、
emu/Linux/emu
および
emu/Linux/emu-g
。 また、新しいモジュールはそれぞれ同じ場所にある同じファイルに統合しようとするため、ユーザーはそのようなモジュールをいくつか、未知の順序で追加したい場合があるため、標準
patch
コマンドは必要な変更を加えることができません。 彼女は1つまたは2つのモジュールを追加しますが、次のことで問題が発生します これらのファイルの編集可能な場所は、彼女が見ると予想しているものと大きく異なり始めます。
この問題を解決するために、Perlでスクリプトをスケッチしました。ほとんどの場合、行に追加するモジュールの名前を変更するだけで十分です。
my $MODNAME = 'CJSON';
また、OS Infernoカーネルにモジュールを埋め込むことにより、上記のすべてのファイルに必要な変更を加えます。 より複雑な場合、たとえば、追加のC / C ++ライブラリをInfernoに接続する必要がある場合、このスクリプトをニーズに合わせて変更する必要があります(C ++ライブラリre2を接続するためのこのような変更の例はRe2モジュールで確認できます )。
-R
オプションを使用してスクリプトを実行すると、変更をロールバックできます。
そのため、スクリプトをダウンロードし、
$INFERNO_ROOT
入れて、名前を
$INFERNO_ROOT
に変更し、モジュール名を「Example」に変更して実行します。 これで(まだ存在しない)Exampleモジュールがカーネルに接続され、それを作成してInfernoを再構築します。
まず、2つのファイルを作成します。
-
module/example.m
Example: module { PATH: con "$Example"; };
-
libinterp/example.c
#include <lib9.h> #include <isa.h> #include <interp.h> #include "runt.h" #include "examplemod.h" void examplemodinit(void) { builtinmod("$Example", Examplemodtab, Examplemodlen); }
OS Infernoの再構築を開始します。
$ (cd libinterp/; mk nuke) $ rm Linux/386/bin/emu # work around "text file busy" error $ mk install
これで、モジュールを正常にロードするプログラムをLimboで作成できますが、これまでのところ何の役にも立ちません。
-
testexample.b
implement TestExample; include "sys.m"; include "draw.m"; include "example.m"; TestExample: module { init: fn(nil: ref Draw->Context, nil: list of string); }; init(nil: ref Draw->Context, nil: list of string) { sys := load Sys Sys->PATH; example := load Example Example->PATH; if(example == nil) sys->print("fail to load Example: %r\n"); else sys->print("Example module loaded\n"); }
$ emu ; limbo testexample.b ; testexample Example module loaded ;
仕組み
アセンブリプロセス中に、
module/example.m
ファイルが分析され、このモジュールを記述する必要なC構造が生成されます—別の
libinterp/examplemod.h
—そのパブリックインターフェイス全体(定数、adt-shki、関数)が
libinterp/runt.h
ファイルに追加されますすべてのCモジュールに関する情報を含む
libinterp/runt.h
。 これらの2つの.hファイルは、すでに
libinterp/example.c
接続されてい
libinterp/example.c
。
さらに、OS Infernoのブートプロセス中に
examplemodinit()
関数が1回呼び出され、モジュールのグローバルデータ(存在する場合)を初期化し、(
builtinmod(…)
呼び出して
builtinmod(…)
Infernoコアに接続する必要があります。
builtinmod()
の呼び出しは、モジュールと、
load
コマンドでこのモジュールをロードするときにLimboから使用される
PATH
定数で指定された$ Example疑似パスとの接続を確立します。
関数:パラメーターを受け取り、結果を返す
数字
リンクを扱う例が複雑にならないように、単純なデータ型から始めましょう。
-
module/example.m
Example: module { ... increment: fn(i: int): int; };
-
libinterp/example.c
... void Example_increment(void *fp) { F_Example_increment *f; int i; f = fp; i = f->i; *f->ret = i + 1; }
Infernoを再構築します。
-
testexample.b
... init(nil: ref Draw->Context, nil: list of string) { ... sys->print("increment(5) = %d\n", example->increment(5)); }
サンプルを実行する前に
emu
を再起動することを忘れないでください。
現在実行中の
emu
は、変更されたCモジュールが含まれていません。
$ emu ; limbo testexample.b ; testexample Example module loaded increment(5) = 6 ;
仕組み
アセンブリ中に、
module/example.m
にある
increment()
関数について、この関数の説明、パラメーター、および戻り値がファイル
libinterp/runt.h
に自動的に追加されました。
void Example_increment(void*); typedef struct F_Example_increment F_Example_increment; struct F_Example_increment { WORD regs[NREG-1]; WORD* ret; uchar temps[12]; WORD i; };
私はまだ
regs
何であるかを理解していません。 アライメントのために明示的に追加されたtemp;
ret
は戻り値へのポインタです。 そして、
i
は私たちのパラメータです。
行
-
module/example.m
Example: module { ... say: fn(s: string); };
-
libinterp/example.c
... void Example_say(void *fp) { F_Example_say *f; String *s; char *str; f = fp; s = f->s; str = string2c(s); print("%s\n", str); }
-
testexample.b
... init(nil: ref Draw->Context, nil: list of string) { ... example->say("Hello!"); }
$ emu ; limbo testexample.b ; testexample Example module loaded increment(5) = 6 Hello! ;
仕組み
libinterp/runt.h
取得したものを
libinterp/runt.h
ます。
void Example_say(void*); typedef struct F_Example_say F_Example_say; struct F_Example_say { WORD regs[NREG-1]; WORD noret; uchar temps[12]; String* s; };
noret
では、
ret
代わりにすべてが明確であり、
say()
関数は何も返しません。
String*
タイプは、Limbo文字列のC実装です。
struct String
は
include/interp.h
、文字列を操作する関数
string2c()
この例で使用されている
string2c()
など)は
libinterp/string.c
ます。
他のLimboデータ型を使用した作業は、
Array*
、
List*
などを介して同様の方法で実装されます。 すべての構造体に文字列を操作するための既製の補助関数があるわけではありませんが、
libinterp/xec.c
仮想マシンのオペコードの実装で十分な例を見つけることができます(たとえば、配列のスライスの操作方法)。
module/example.m
宣言されたカスタムadtは、通常のC-shy構造体に変換され
module/example.m
(そして、adtをunionに選択します)。 タプルも通常の構造体に変換されます。
ほとんどの場合
module/example.m
libinterp/runt.h
を
libinterp/runt.h
を
libinterp/runt.h
するためにアセンブリを開始する必要があります(これは誤って失敗します)。データ用に作成された構造を正確に確認し、
libinterp/example.c
でそれらを使用して実装する方法を理解し
libinterp/example.c
。
例外
例外をスローするには、単に
error()
関数を呼び出します。
raise.h
を
raise.h
、
libinterp/raise.c
説明されている標準エラーを返すか、同じ方法で
libinterp/example.c
で独自のエラーを宣言できます。
もちろん、
malloc()
使用して自分でメモリを割り当てた場合、
error()
を呼び出す前にこのメモリを解放する必要があります。そうしないとリークが発生します。 ヒープを介して標準的な方法で割り当てられたオブジェクト(
String*
や
Array*
)は、少し後でガベージコレクタによって検出および削除されるため、解放する必要はありません。 ( パート2のヒープおよびガベージコレクターの動作の詳細。 )
戻るリンク
関数から結果を返す暗黙の瞬間の1つは、
*f->ret
、正常に完了した後に関数の実行結果が配置されるメモリセルを物理的に指すという事実に関連しています。 これから2つの結果が続きます。
- 最初に結果を
*f->ret
に入れてから、エラーが発生して例外をスローすると判断した場合、Limboの観点からは不可能なことが起こります。And関数は値Andを返し、例外をスローします。 - 関数の結果が返される変数にすでに値が含まれている場合(この変数の型は関数によって返される値と同じであるため、もちろん参照でもあります)、メモリから解放する必要がありますあなたとこのリンクを書き換える方法。
increment()
このように:
-
libinterp/example.c
... void Example_increment(void *fp) { ... *f->ret = i + 1; error("some error"); }
-
testexample.b
... init(nil: ref Draw->Context, nil: list of string) { ... i := 0; { i = example->increment(5); } exception e { "*" => sys->print("catched: %s\n", e); } sys->print("i = %d\n", i); }
; testexample ... catched: some error i = 6 ;
C関数の2番目の問題を解決するには、戻り値を
*f->ret
に保存する前に、現在の値を解放する必要があります。 これは通常、次のように行われます。
destroy(*f->ret); *f->ret = new_value;
または(
H
はLimbo-nils
nil
のC番目の類似体です):
void *tmp; ... tmp = *f->ret; *f->ret = H; destroy(tmp); ... *f->ret = new_value;
私が理解しているように、現時点ではこれらのオプションに違いはありませんが、Disが複数のCPU /コアで同時に動作するように書き換えられた場合、2番目のオプションは正しく動作しますが、最初のオプションは動作しません。
ブロックを解除
InfernoはグローバルDisロックを使用します(おそらくPythonで広く知られているGILと同様です)。 C-shnyh関数は、ロックが設定された状態で呼び出されます。 Disロック(つまり、C関数のパラメーターと戻り値を含むLimboからアクセス可能な値と変数)を設定ロックのみで操作しても安全です。
ただし、関数が何らかの長い操作を実行する必要がある場合(たとえば、外部ライブラリからの「重い」関数の読み取り/書き込みまたは呼び出し、または長い計算の実行)、この操作の前に、Disが別のスレッドで実行されるよう
release()
ロックを
release()
する必要があります関数と並行して、
acquire()
再度配置します(そうしないと、結果を返してこの関数を呼び出したLimboコードに戻ることができなくなります)。 例は、
emu/port/inferno.c
sys->read()
実装にあり
emu/port/inferno.c
。
void Sys_read(void *fp) { ... release(); *f->ret = kread(fdchk(f->fd), f->buf->data, n); acquire(); }
パート2