論理アドレスはリニアアドレスと同意である。 また、リニアアドレスは、セグメントアドレス (セグメントのオフセットアドレス)と等しくなる。
論理アドレス = リニアアドレス = セグメントアドレス
論理アドレス、物理アドレスを特に区別していない場合、 アドレスは論理アドレスを示す。
メモリーモデルは 4GB のフラットメモリーモデルとする。 セグメントは使用しない。 ただし、内部的には以下の 7 つのセグメントが存在する。
- CODE0
ベースアドレス 0x00000000
セグメントリミット 0xffffffff
保護レベル 0 アクセス制限 実行可・読み取り不可 コンフォーミング False ワードサイズ 32ビット - CODE0C,CODE1C
ベースアドレス 0x00000000
セグメントリミット CODE0 と同じ値 保護レベル 0, 1 アクセス制限 実行可・読み取り不可 コンフォーミング True ワードサイズ 32ビット - DATA0〜3
ベースアドレス 0x00000000
セグメントリミット CODE0 と同じ値 保護レベル 0, 1, 2, 3 アクセス制限 読み取り可・書き込み可 伸張方向 上方伸張 ワードサイズ 32ビット
コードセグメントは、3 種類ある。
保護レベル0 / 非コンフォーミング属性の CODE0 は、ITRON 自身、 割込・例外ハンドラ、保護レベル0で実行されるタスクが使用する。
保護レベル1 / コンフォーミング属性の CODE1C は、 保護レベル1〜3で実行されるタスクが使用する。
保護レベル0・コンフォーミング属性の CODE0C は、 一部の例外ハンドラなどで利用する。
データセグメントは保護レベル別に 4 つあり、
スタックセグメントとしても使用する。
これらのセグメントは、すべてのタスク/ハンドラで共有する。
アプリケーションは、
これらのセグメントの属性 (ディスクリプタの内容)
を変更することはできない (変更してはいけない)。
また、他のセグメントを追加することもできない。
上記の様に、 各セグメントはすべて同じアドレス空間を指している。 したがって、 どのセグメントレジスターをベースにしても、 同一のオフセットアドレスは同じメモリーを示すことになる。 また、このことによりいずれの保護レベルからでも すべてのメモリーにアクセスすることが可能である (メモリー保護は働かない)。
これらのセグメントは、アプリケーションからは見えない。
また、セグメントレジスタをアプリケーションで変更することはできない
(変更してはいけない)。
セグメントレジスタはタスク付属ではない。
タスクスイッチ時にも (データ) セグメントレジスタは保存・復帰されない。
したがって、セグメントレジスタを変更すると、
他のタスクの動作が異常となったり、
一般保護例外が発生する場合があり、最悪システムダウンとなることもある。
タスク実行時には、CS,SS レジスタには、
タスク生成時に指定された保護レベルと
等しい保護レベルのセグメントが設定される。
ただし、CS には前述のように CODE0 または CODE1C が設定され、
下位 2 ビット (CPL) が対応する保護レベルに一致するように設定される。
ハンドラ実行時には、CS, SS レジスタには、
保護レベル 0 のセグメント (CODE0,DATA0) が設定される。
ただし、コンフォーミング指定された例外ハンドラでは、
CS レジスタにはコンフォーミング属性のセグメント (CODE0C) が設定され、
割り込まれたときの保護レベルで実行される。
また、SS レジスタは割り込まれたときのままとなる。
FS レジスタには、
タスク生成時に保護レベル 3 のデータセグメント (DATA3) が設定される。
拡張 SVC を呼び出すと、FS レジスタの最下位2ビット (RPL) は、
拡張 SVC を呼び出したときの保護レベルを示すように変更される。
(6.8 def_svc()
参照)
DS, ES, GS レジスタには、
保護レベル 3 のデータセグメント (DATA3)
がシステム (ITRON) 初期化時に設定され、
その後はシステム (ITRON) では変更しない。
タスクスイッチ時にもタスクのコンテキストとしての保存・復帰は行われない
(上述の様に、アプリケーションで変更した場合の動作は保証されない)。
用語に関する注意:
ITRON は、タスクスイッチによってページテーブルを切り替える。 ページテーブルの内容およびページフォルト例外などは ITRON では関知しない。
上位レベル (ITRON の外) でページング機能を利用することが可能である。 ページング機能を利用してメモリー保護を行うことも可能である。 ただし、 ITRON 内部 (クリティカルセクション内) を実行中にページフォルト例外や一般保護例外などが発生してはいけない。
タスクスイッチによって切り替えるページテーブルは、 ページディレクトリの先頭の 64 エントリー分である。 したがって、アドレスの 0〜256MB までがタスク固有空間となり、 残りの 256MB〜4GB は共有空間となる。
各タスクの固有空間の分のページテーブル (64 エントリー分 = 256B) は、
アプリケーション側で用意し、タスク生成時に指定する
(6.11 cre_tsk()
の項を参照)。
ページテーブルの切り替えは次の様な方法をとる。
実際に CPU に与えるページテーブルは1つであり、 その一部 (固有空間部分) にタスク生成時に指定された固有空間分のページテーブルをコピーする。 コピーはタスクスイッチごとに行われ、 タスクごとのページテーブルから CPU のページテーブルへコピーする。 CPU のページテーブルからタスクごとのページテーブルへ書き戻すことはしない。 なお、コピーするのはページディレクトリの部分だけであり、 その内容には関知しない。 したがって、現在実行中のタスクのページテーブルを書き換えるときは、 タスクごとのページテーブルと、 CPU のページテーブル (ページディレクトリ) の両方を変更する必要がある。 共有空間のページテーブルは CPU のページテーブルにしか存在しないため、 CPU のページテーブルのみを変更すればよい。 CPU のページテーブルの位置は、 PDBR (CR3) レジスタを読み出すことで得られる。
I/O 保護モードは次の様に設定され、 すべてのタスク/ハンドラで共通となる。 アプリケーションでこれらの設定を変更することはできない (変更してはいけない)。
I/O 保護レベル (EFLAGS:IOPL) = 1 TSS I/O 許可ビットマップ = すべて禁止(1)
つまり、保護レベル0および1で実行されているときは、 すべての I/O にアクセス可能。 逆に、保護レベル 2 および 3 で実行されているときは、 すべての I/O にアクセスできない。
ベクター番号 内容 0 除算エラー 1 デバッグ例外 2 NMI 割込 3 ブレークポイント 4 INTO 検出のオーバーフロー 5 BOUND の範囲外 6 無効オペコード 7 デバイスが使用不可 ☆ 8 ダブルフォルト 9 コプロセッサのセグメントオーバーラン 10 無効タスクステートセグメント 11 セグメント不在 ☆ 12 スタックフォルト 13 一般保護 14 ページフォルト 15 インテル予約 16 浮動小数点エラー 17 アライメントチェック 18 マシンチェック 19〜31 インテル予約 32〜111 DOS 予約 112〜119 外部割込 PIC No.2
112 IRQ8 リアルタイムクロック 113 IRQ9 予備 (リダイレクト IRQ2) 114 IRQ10 予備 115 IRQ11 予備 116 IRQ12 マウス 117 IRQ13 数値演算プロセッサ 118 IRQ14 ハードディスク 119 IRQ15 予備 120〜127 外部割込 PIC No.1
120 IRQ0 タイマー ☆ 121 IRQ1 キーボード 122 IRQ2 カスケード PIC No.2 123 IRQ3 シリアル2 124 IRQ4 シリアル1 125 IRQ5 パラレル3 126 IRQ6 フロッピーディスク 127 IRQ7 パラレル1, パラレル2 128 ITRON ディスパッチャ呼び出し ☆ 129 ITRON システムコール/拡張 SVC 呼び出し ☆ 130 ITRON ret_int
専用呼び出し☆ 131〜143 予備 144〜255 未使用
☆印は、ITRON が利用している割込である。
ret_int
専用呼び出し int 130
システムコール呼び出しインターフェースは、次の例のようになる。
アセンブラから呼び出す場合にも、
C 言語の呼び出し規則に則って、
スタック上にパラメータを積み、call xxx_yyy
の様に呼び出す。
または、上記の様に eax に機能コードとパラメータ数、
edx にパラメータの先頭アドレスをセットして
int 129 を直接呼び出してもよい。
ただし、レジスタの保存規則などはC 言語の場合と同じである。
eax < 0
ならシステムコール、
eax >= 0
なら拡張 SVC となる。
拡張 SVC の呼び出しも、 システムコールと同じ呼び出しシーケンスとなる。 ただし、eax には拡張機能コードを設定する。 パラメータ数を含めるか否かは、 拡張 SVC を提供する側で決定する事項なので、ITRON では規定しない。 eax の下位 8 ビットが拡張 SVC 番号となる。 残りの上位部分は最上位ビット (MSB) を除いて無視する。 最上位ビット (MSB) は 0 でなければならない。
def_svc()
拡張 SVC ハンドラ定義情報は、次の様になる。 特に、i386 特有の拡張はない。
typedef struct t_dsvc { ATR svcatr; /* 拡張SVCハンドラ属性 */ FP svchdr; /* 拡張SVCハンドラアドレス */ } T_DSVC;
TA_HLNG
を指定しない場合にも、
実際には高級言語対応ルーチンが使用される。
拡張 SVC ハンドラは、C 言語の関数の形式とする。
ハンドラ属性に TA_ASM を指定しても TA_HLNG
と同じシーケンスで呼び出されるので、
C 言語の関数の規則にしたがってハンドラを作成する。
拡張 SVC ハンドラは、保護レベル0で実行される。
拡張 SVC を呼び出した時の保護レベルを FS レジスタの最下位2ビット(RPL) に設定する。 残りの上位ビットは DATA3 セグメント (保護レベル 3 のデータセグメント) を示す。 拡張 SVC から戻る時 FS を元に戻す。 この機能は、拡張 SVC のパラメータとしてアドレスが渡されたときの、 アドレスチェック (メモリー保護の確認) のために利用できる。
拡張 SVC ハンドラは、次の様な形式となる。
ER svchdr( VP para, FN s_fncd )拡張 SVC ハンドラへ渡す引数は、すべてスタックに積まれる。
para
はその引数のリストであり、
スタック上へ積まれた引数へのポインタとなっている。
スタックに積まれた引数の構造は、
C 言語で関数の引数をスタックに積むときと同じ形式である。
下記の例のように、構造体として取り出すことができる。
引数の型・個数には特に制限はなく、可変個数の引数でもよい。
(例) 次の様な拡張 SVC を作成したい場合
ER svc_func( W p1, W p2, W p3, W p4, W p5, W p6 )
- [呼び出し側]
er = svc_func(1,2,3,4,5,6);- [インターフェースライブラリ]
svc_func: movl s_fncd, %eax // 拡張機能コード leal 4(%esp), %edx // パラメータ先頭アドレス int $129 ret- [受け付け側]
struct svc_func_para { W p1; W p2; W p3; W p4; W p5; W p6; }; ER svc_func_hdr( struct svc_func_para *para, FN s_fncd ) { p1 = para->p1; の様に引数を取り出せる return er; }
def_int()
typedef struct t_dint { ATR intatr; /* 割込みハンドラ属性 */ FP inthdr; /* 割込みハンドラアドレス */ } T_DINT;
dintno
は、
外部割込以外も含めたすべての例外/割込のベクター番号(0〜143)
を指定できる。その内、外部割込は 112〜127 である。
TRON Chip 用に拡張された TA_EITATR
属性は使用しない。
i386 用に次の属性が追加されている。
#define TA_CONFORM 0x00040000 /* 保護レベル継承 */ #define TA_INTGATE 0x00000000 /* 割込ゲート使用 */ #define TA_TRAPGATE 0x00080000 /* トラップゲート使用 */
TA_CONFORM
TA_INTGATE
TA_TRAPGATE
外部割込の場合も、ITRON は割込コントローラに対する処理は何も行わない。 EOI コマンド等は、割込ハンドラが処理しなければならない。
TA_HLNG
を指定した場合割込ハンドラのエントリーは次の様になる。
void int_hdr( INT vecno, INT *esp ) vecno 発生した割込/例外のベクター番号 esp 割込/トラップゲートを通過した直後のスタックポインタの値
エラーコードをスタックに積む例外では、
*esp
を取り出すことで、エラーコードを得ることができる。
それ以外の場合は esp
は使用しない。
TA_HLNG
指定と TA_CONFORM
指定は同時に指定できない。
高級言語対応ルーチンが、保護レベル0で実行することを必要とするので、
それ以外の保護レベルで実行される可能性のある TA_CONFORM
指定は使用できない。
割込ハンドラ実行中は、 タスク独立部と判定され、遅延ディスパッチの原則が適用される。
TA_ASM
を指定した場合割込ハンドラは、割込/トラップゲートから直接呼び出される。
OS (ITRON) 内を通過しないため、タスク独立部と判定されない。
そのため、遅延ディスパッチの原則も適用されない。
ただし、割込ゲート (TA_INTGATE
) を使用し、割込ハンドラ内では常に割込禁止
(EFLAGS:IF=0
) とすることで、遅延ディスパッチが保証される。
この場合、多重割込は不可能となる。
逆に、トラップゲート (TA_TRAPGATE
) を使用し、
割込許可 (EFLAGS:IF=1
) としておくことで、
準タスク部的 (準タスク部とは判定されない) またはサブルーチン的に使用することもできる。
正確には割込が発生した時点の状態が保持される。 つまり、タスク独立部実行中に割込が発生した場合は、タスク独立部と判定される。 タスク部実行中に割込が発生した場合には、タスク部と判定される。
TA_CONFORM
指定を組み合わせて使用することが可能である。
高級言語対応ルーチンを使用した場合には、ret_int()
は必要ない。
単に return
することで、高級言語対応ルーチン内で自動的に行われる。
あえて ret_int()
を明示的に使用したい場合には、次の様にするとよい。
#define ret_int() return
高級言語対応ルーチンを使用しない場合で、
ディスパッチを伴う可能性がある場合には、
必ず ret_int()
を呼び出す必要がある。
ただし、
割込ハンドラを準タスク部的/サブルーチン的に使用している場合には、
無理に ret_int()
を呼び出す必要はない。
ret_int()
の呼び出しは、ソフトウエア割込 int 130 で行う。
他のシステムコールのようなサブルーチン形式の呼び出しはない。
int $130 // ret_int() 呼び出し (戻ってこない)
ret_int()
(int 130) を呼び出す時点では、
iret
命令で割り込まれたところへ戻れるように、
レジスタおよびスタックを復帰させておかなければならない。
基本的には、 割込ハンドラへ入ったときと同じ状態にレジスタ およびスタックを復帰させておけばいいのであるが、 エラーコードをスタックへ格納するタイプの例外では、 そのエラーコードは捨てておかなければならない点に注意が必要である。
ret_int()
を使用しない場合は、単に iret 命令で戻ればよい。
割込ハンドラ以外から ret_int()
を呼び出した場合の動作は保証されない。
cre_tsk()
ユーザースタックポインタ、 およびタスク固有空間ページテーブルの設定機能が追加されている。
typedef struct t_ctsk { VP exinf; /* 拡張情報 */ ATR tskatr; /* タスク属性 */ FP task; /* タスク起動アドレス */ PRI itskpri; /* タスク起動時優先度 */ INT stksz; /* ユーザスタックサイズ */ INT sstksz; /* システムスタックサイズ */ VP stkptr; /* ユーザースタックポインタ */ VW uatb; /* タスク固有空間ページテーブル */ VW lsid; /* タスク固有空間 ID */ } T_CTSK; #define TA_USERSTACK 0x00080000 /* ユーザースタックを指定 */ #define TA_TASKSPACE 0x00100000 /* タスク固有空間を指定 */
TA_USERSTACK
stkptr
を有効にする。
ユーザースタック領域をアプリケーション側で用意し、
スタックポインタの初期値を stkptr
に設定する。この時、
stksz
には 0 を設定しておかなければならない。
生成するタスクのユーザースタックポインタに stkptr
が設定される。
TA_USERSTACK
の指定がない場合は、ユーザースタック
も OS (ITRON)
が用意する。
TA_TASKSPACE
uatb
と lsid
を有効にする。
これによりタスク固有空間が設定される。
uatb
タスク固有空間のページテーブルの論理アドレス
uatb
を先頭とするページディレクトリの 64
エントリー分のメモリー領域を固有空間のページテーブルとする。
このメモリーは、
タスクが削除されるまで保持されていなければならない。
また、常に OS からアクセス可能でなければならない。
uatb = 0
はタスク固有空間がないことを意味する。
タスク固有空間 ID (0〜4095)
タスク固有空間を識別するための番号。異なる
uatb
には異なる lsid
を設定し、
同じ uatb
には同じ lsid
を設定する。
TA_TASKSPACE
を指定しない場合には、タスク固有空間がない状態になる。
この場合でも、vset_tsp()
を使用してタスク固有空間を後から設定することは可能である。
ページングを使用しない ( CR0:PG=0
)場合は、
タスク固有空間を設定してはいけない。
ページング不使用( CR0:PG=0
)
の状態でタスク固有空間を設定した場合の動作は保証されない。
浮動小数点演算コプロセッサ (FPU) を使用するタスクの場合には、
TA_COP0
属性を指定する。
タスク起動時には、FINIT
命令相当の FPU の初期化が行われる
( 実際には、TA_COP0
の指定の有無に関係なく FPU は使用可能であるが、
FPU を使用するタスクは TA_COP0 を指定することが望ましい)。
高級言語対応ルーチンは、スタックポインタを設定し、
パラメータに起動コードと拡張情報をセットするだけで、
それ以外には何も行わない。実際には TA_HLNG
の指定がなくても、この処理は行われる
(実際には、スタックフレーム上でレジスタなどの初期値を設定するだけで、
高級言語対応ルーチンというプログラムの実体はない)。
タスクの終了は、ext_tsk()
または exd_tsk()
を用いる。
TA_HLNG
を指定した場合でも、
単に return
したのではタスクの終了とはならない
(暴走または例外発生となる)。
vget_tsp() / vset_tsp()
タスク固有空間の取得/設定を行う。
typedef struct { VW uatb; /* タスク固有空間ページテーブルのアドレス */ VW lsid; /* タスク固有空間 ID */ } T_TSKSPC;
uatb
, lsid
は cre_tsk()
での指定と同じ。
タスク固有空間の取得
ER vget_tsp( T_TSKSPC *pk_tskspc, ID tskid )
tskid
で指定したタスクの固有空間情報を *pk_tskspc
に取り出す。tskid = TSK_SELF
指定可能。
タスク固有空間の設定
ER vset_tsp( ID tskid, T_TSKSPC *pk_tskspc )
tskid
で指定したタスクへ *pk_tskspc
の固有空間情報を設定する。tskid = TSK_SELF
指定可能。vset_tsp()
を行うと、
動作が異常になる場合がある。
OS (ITRON) はタスク固有空間を切り替えたことで
発生するいかなる問題にも関知しない。
アプリケーション側で保証しなければならない。
vget_reg() / vset_reg()
参照/設定できるレジスタは以下のもの。
typedef struct { VW eax; /* アキュムレータ */ VW ecx; /* カウンタレジスタ */ VW edx; /* データレジスタ */ VW ebx; /* ベースレジスタ */ VW ebp; /* ベースポインタ */ VW esi; /* ソースインデックス */ VW edi; /* デスティネーションインデックス */ } T_REGS; typedef struct { UW eflags; /* フラグ */ VP eip; /* 命令ポインタ */ UH cs; /* コードセグメント */ UH fs; /* フレームセグメント */ } T_EIT; typedef struct { VP esp0; /* システムスタックポインタの初期値 */ VP ssp; /* システムスタックポインタの現在値 */ VP usp; /* ユーザースタックポインタの現在値 */ VW uatb; /* タスク固有空間ページテーブルのアドレス */ VW lsid; /* タスク固有空間 ID */ } T_CREGS;
データセグメントレジスタの内 DS,ES,GS はタスク付属ではないため、 参照/ 設定できない。
SS レジスタは、参照/設定できない。
eflags
の以下のビットは変更できない。
下記の規定値以外を設定しようとした場合には E_PAR
となる。
bit 規定値 VM : 仮想86モード 17 0 NT : ネストタスク 14 0 IOPL: I/O 保護レベル 12-13 1
esp0
は参照できるが、設定は出来ない。設定は無視される。
usp
は、保護レベル 0 で動作するタスクでは意味を持たない。
参照される値は不定となる。設定は無視される。
vget_cpr() / vset_cpr()
ER vget_cpr( T_COPREG *pk_copreg, ID tskid, INT copno ) ER vset_cpr( ID tskid, INT copno, T_COPREG *pk_copreg ) typedef struct { UH cw; /* Control Word */ UH rsv1; UH sw; /* Status Word */ UH rsv2; UH tw; /* Tag Word */ UH rsv3; UW ip; /* IP */ UH cs; /* CS */ UH op; /* opcode */ UW of; /* operand offset */ UH os; /* operand selector */ UH rsv4; UB st[8][10]; /* st(0)〜st(7) */ } T_COP0REG; typedef union { T_COP0REG cop0; #if 0 T_COP1REG cop1; T_COP2REG cop2; T_COP3REG cop3; T_COP4REG cop4; T_COP5REG cop5; T_COP6REG cop6; T_COP7REG cop7; #endif } T_COPREG;
コプロセッサは FPU 1 つのみ。copno = 0
のみ有効。
tskid
に自タスクを指定することはできない。
タスク独立部から呼び出すことはできない。
ref_cfg()
typedef struct t_rcfg { INT timer_period; /* タイマー割込間隔 (ms) */ INT default_slice_time; /* デフォルト連続実行時間 (ms) */ UW cpu_type; /* CPU タイプ */ } T_RCFG;
timer_period
タイマー割込の発生間隔を示す。
default_slice_time
同一優先度のタスクは、ラウンドロビンスケジューリングが行われる。
default_slice_time
がデフォルトのスライス間隔(ミリ秒)となる。
cpu_type
現在システムが動作している CPU に関する情報が設定される。下位 16bit
は、ITRON 仕様で定める CPU コードであり、
上位 16bit にインプリメント依存の付加情報が追加される。
get_ver()
で得られる CPU 情報が OS を実行可能な CPU タイプであるのに対し、
ref_cfg()
では現在動作している CPU タイプを返す。
#define TCPU_unknown 0 /* CPU 未定義 */ #define TCPU_G100 0x00000009 /* Gmicro/100 */ #define TCPU_G200 0x0000000a /* Gmicro/200 */ #define TCPU_G300 0x0000000b /* Gmicro/300 */ #define TCPU_G400 0x00000018 /* Gmicro/400 */ #define TCPU_G500 0x00000019 /* Gmicro/500 */ #define TCPU_ix86 0x00000060 /* Intel x86 系 */ #define TCPU_i386 0x00000063 /* i386 SX/DX シリーズ */ #define TCPU_i486 0x00000064 /* i486 SX/DX シリーズ */ #define TCPU_Pentium 0x00000065 /* Pentium シリーズ */ #define TCPU_PentiumPro 0x00000066 /* PentiumPro シリーズ */ #define TCPU_FPU 0x00010000 /* FPU 付 */
浮動小数点演算コプロセッサ(FPU)が搭載されている場合に TCPU_FPU
が OR される。i486DX や Pentium では FPU が内蔵されているので、必ず OR
されていることになる。
ハンドラは、下記のエントリールーチンを持つ必要がある。
hll_fexchdr_startup: xchgl (%esp), %eax // 汎用レジスタ保存 pushl %ecx pushl %edx pushfl // フラグ保存 pushl %eax // fexcode call fexchdr // call fexchdr(fexcode) addl $4, %esp popfl // フラグ復帰 popl %edx // 汎用レジスタ復帰 popl %ecx popl %eax ret // 割り込まれた場所へ戻る
ITRON では下記のハードウエアを使用している。
割込コントローラ
PIC #1 | 0x20, 0x21 |
PIC #2 | 0xa0, 0xa1 |
#1 | #2 | |
---|---|---|
ICW1 | 0x11 | 0x11 |
ICW2 | 0x78 | 0x70 |
ICW3 | 0x04 | 0x02 |
ICW4 | 0x11 | 0x01 |
インターバルタイマー
I/O ポート | 0x40, 0x43 |
使用カウンタ | #0 |
モード | mode 3, 16 bit binary |
割込周期 | 10 ミリ秒 |