2014-09-15 :-)
_ 買い物
iTunes Store
- ウラオモテ・フォーチュン 月刊少女野崎くん ED
- 花ハ踊レヤいろはにほ ハナヤマタ ED
- ミライファンファーレ 未来少女たち 普通の女子校生が【ろこどる】やってみた。OP/ED
_ [μITRON][TOPPERS/JSP][コードリーディング]μITRON 実装 TOPPERS/JSP を読む
コードはこちら
TOPPERSプロジェクト/ダウンロード の 完全版 を貰う。
eclipse にインポートしてコードリーディング
なおダウンロードしたソースコードは文字エンコードが EUC-JP のようなので eclipse ではデフォルトだとデコードできず文字化けする。
プロジェクト - プロパティ - リソース - テキストファイルエンコーディングを EUC-JP と記入する suz-lab - blog: EclipseでEUC-JPを使うには
_ [セマフォ][μITRON][TOPPERS/JSP][コードリーディング]μITRON 実装 TOPPERS/JSP を読む - セマフォ
セマフォには
- バイナリセマフォ
- 計数セマフォ
の 2 つがある。
みんな大好き wdic より
バイナリセマフォ
バイナリセマフォは0か1のどちらかの値しか持たないセマフォで、重要度の高い処理のブロックに用いられる。
フラグは初期化によって最初1はセットされ、特定のプロセスが処理開始時にAPIを呼びフラグをデクリメント(−1)し、処理の終了と共にAPIを呼びインクリメント(+1)する。
他のプロセスがこれを同時に処理しようとすると、フラグが0になっているためにデクリメントに失敗して処理がブロックされ、そのプロセスはOSレベルで休止状態にされる。後に処理が完了しフラグが1に戻ると、プロセスキューに溜められた休止中のプロセスがOSによって順にチェックされ、処理が再開される。
汎用セマフォ
汎用セマフォはカウンターであり、資源に一定の空き数がある場合に利用される。
基本的に値は資源の空き数に設定され、特定のプロセスが処理開始時に値のデクリメント(−1)を行ない、処理終了時にインクリメント(+1)を行なう。空き数を使い切り、デクリメントに失敗した場合のブロッキング処理はバイナリセマフォと同様である。
バイナリセマフォは同期処理によく使われる。
汎用セマフォは資源を独占処理するときに使われる。計数セマフォとかカウンティングセマフォなどともいう。
セマフォの実装 kernel\semaphore.c
この 3 つの処理を読む。cre_sem どこだよ
- semaphore_initialize セマフォ初期化
- wai_sem セマフォ獲得
- sig_sem セマフォ開放
汎用セマフォだろうとバイナリセマフォだろうとセマフォの実装は同一である。セマフォカウンタ値の初期値によって汎用セマフォなのかバイナリセマフォなのかを切り替える。初期値が 1 ならばバイナリセマフォ。そうでなければ汎用セマフォ。たぶん。
しかし初期値をどうやって設定するのかが分からん。「コンフィギュレーション」とやらがあるらしいので TOPPERS/JSP をファームウェアとして利用するときにユーザーの環境ごとに何か設定するようだ。
doc/user.txt より
3.4 同期・通信機能 3.4.1 セマフォ セマフォの最大資源数は,UINT型(unsigned int型に定義している)で表現で きる数値の範囲内である.すなわち,unsigned int型が 32ビットの場合は (2^32 - 1),16ビットの場合は (2^16 - 1) = 65535 である.TMAX_MAXSEM は 定義していない. (1) CRE_SEM セマフォの生成(静的API) (2) sig_sem, isig_sem セマフォ資源の返却 (3) wai_sem セマフォ資源の獲得 (4) pol_sem セマフォ資源の獲得(ポーリング) (5) twai_sem セマフォ資源の獲得(タイムアウトあり)
cxx_sample2.cfg というファイルに以下の記述がある。たぶんこれが初期値として使われる?
CRE_SEM(1, { TA_TFIFO, 1, 1 }); CRE_SEM(2, { TA_TFIFO, 1, 1 }); CRE_SEM(3, { TA_TFIFO, 1, 1 }); CRE_SEM(4, { TA_TFIFO, 1, 1 }); CRE_SEM(5, { TA_TFIFO, 1, 1 });
セマフォのデータ構造を見ていく。
/* * キューのデータ構造の定義 */ typedef struct queue { struct queue *next; /* 次エントリへのポインタ */ struct queue *prev; /* 前エントリへのポインタ */ } QUEUE;
/* * セマフォ初期化ブロック */ typedef struct semaphore_initialization_block { ATR sematr; /* セマフォ属性 */ UINT isemcnt; /* セマフォの資源数の初期値 */ UINT maxsem; /* セマフォの最大資源数 */ } SEMINIB;
/* * セマフォ管理ブロック */ typedef struct semaphore_control_block { QUEUE wait_queue; /* セマフォ待ちキュー */ const SEMINIB *seminib; /* セマフォ初期化ブロックへのポインタ */ UINT semcnt; /* セマフォ現在カウント値 */ } SEMCB;
/* * セマフォ管理ブロックのエリア(kernel_cfg.c) */ extern SEMCB semcb_table[];
ここからがセマフォのコード。
セマフォ初期化。ここの isemcnt が↑で登場した CRE_SEM に指定した初期値だろう。
void semaphore_initialize() { UINT i; SEMCB *semcb; for (semcb = semcb_table, i = 0; i < TNUM_SEM; semcb++, i++) { queue_initialize(&(semcb->wait_queue)); semcb->seminib = &(seminib_table[i]); semcb->semcnt = semcb->seminib->isemcnt; } }
セマフォ獲得
SYSCALL ER wai_sem(ID semid) { SEMCB *semcb; WINFO_WOBJ winfo; ER ercd; // CPUによっては空っぽ LOG_WAI_SEM_ENTER(semid); // ディスパッチ保留状態でないかのチェック CHECK_DISPATCH(); // セマフォIDが有効範囲内か確認 CHECK_SEMID(semid); // セマフォテーブルからIDに該当するセマフォを取得 semcb = get_semcb(semid); // CPUをロック。ここからの処理は不可分。CPUごとに実装が異なる。 t_lock_cpu(); // セマフォカウント値が 1 以上の場合はデクリメントするだけ if (semcb->semcnt >= 1) { semcb->semcnt -= 1; ercd = E_OK; } // セマフォ計数が 0 以下の場合は待機する else { wobj_make_wait((WOBJCB *) semcb, &winfo); // 最高優先度のタスクへディスパッチ dispatch(); ercd = winfo.winfo.wercd; } t_unlock_cpu(); exit: LOG_WAI_SEM_LEAVE(ercd); return(ercd); }
セマフォの返却
SYSCALL ER sig_sem(ID semid) { SEMCB *semcb; TCB *tcb; ER ercd; LOG_SIG_SEM_ENTER(semid); CHECK_TSKCTX_UNL(); CHECK_SEMID(semid); semcb = get_semcb(semid); // CPUをロック。ここからの処理は不可分。CPUごとに実装が異なる。 t_lock_cpu(); // セマフォ開放待ちのキューがある場合 if (!(queue_empty(&(semcb->wait_queue)))) { // キューの次のエントリを取り出す。ようするに deque している。 tcb = (TCB *) queue_delete_next(&(semcb->wait_queue)); // タスクの待機状態を解除。ディスパッチが必要な場合はタスクをディスパッチする。 if (wait_complete(tcb)) { dispatch(); } ercd = E_OK; } // 現在のセマフォカウント値が最大値を超えてなければカウントをインクリメント else if (semcb->semcnt < semcb->seminib->maxsem) { semcb->semcnt += 1; ercd = E_OK; } else { ercd = E_QOVR; } t_unlock_cpu(); exit: LOG_SIG_SEM_LEAVE(ercd); return(ercd); }
↑の関数で使われているマクロとかいろいろ
/* * ディスパッチ保留状態でないかのチェック(E_CTX) */ #define CHECK_DISPATCH() { \ if (sense_context() || t_sense_lock() || !(enadsp)) { \ ercd = E_CTX; \ goto exit; \ } \ }
#define CHECK_SEMID(semid) { \ if (!VALID_SEMID(semid)) { \ ercd = E_ID; \ goto exit; \ } \ }
#define VALID_SEMID(semid) \ (TMIN_SEMID <= (semid) && (semid) <= tmax_semid)
/* * セマフォIDからセマフォ管理ブロックを取り出すためのマクロ */ #define INDEX_SEM(semid) ((UINT)((semid) - TMIN_SEMID)) #define get_semcb(semid) (&(semcb_table[INDEX_SEM(semid)]))
/* * 同期・通信オブジェクトに対する待ち状態への移行 * * 実行中のタスクを待ち状態に移行させ,同期・通信オブジェクトの待ちキュー * につなぐ.また,待ち情報ブロック(WINFO)の wobjcb を設定する. * wobj_make_wait_tmout は,タイムイベントブロックの登録も行う. */ extern void wobj_make_wait(WOBJCB *wobjcb, WINFO_WOBJ *winfo); extern void wobj_make_wait_tmout(WOBJCB *wobjcb, WINFO_WOBJ *winfo, TMEVTB *tmevtb, TMO tmout);
/* * 最高優先順位タスクへのディスパッチ(cpu_support.S) * * dispatch は,タスクコンテキストから呼び出されたサービスコール処理 * 内で,CPUロック状態で呼び出さなければならない. */ extern void dispatch(void);
_ [イベントフラグ][μITRON][TOPPERS/JSP][コードリーディング]μITRON 実装 TOPPERS/JSP を読む - イベントフラグ
イベントの通知とその管理。イベントの種類をビットで管理する。
イベント個数が既知ならばこれで足りる。しかしイベント個数がユーザーでも拡張可能の場合(上限不明)はこれでは実現できない。たとえばフラグが 32 ビットだとすると高々 32 個ぶんのイベントしか管理できない。.NET の event 等のようなものと同じ機能であり、それをビットで管理している。
doc\user.txt より。1 つのイベントで複数タスクが待てないんだそうだ。
3.4.2 イベントフラグ 一つのイベントフラグで複数のタスクが待ち状態になれる機能はサポートして いない. FLGPTN型は,unsigned int型に定義している.よって TBIT_FLGPTN は, unsigned int型が 32ビットの場合は 32,16ビットの場合は 16 になる. (1) CRE_FLG イベントフラグの生成(静的API) flgatr に TA_WMUL が指定された場合の機能(イベントフラグで複数のタスク が待ち状態になれる)はサポートしていない. (2) set_flg, iset_flg イベントフラグのセット (3) clr_flg イベントフラグのクリア (4) wai_flg イベントフラグ待ち (5) pol_flg イベントフラグ待ち(ポーリング) (6) twai_flg イベントフラグ待ち(タイムアウトあり)
コードを読む kernel\eventflag.c
ソースファイル冒頭にデータ構造等がある。セマフォにあったデータ構造と処理に似ている。
イベントフラグがイベント ID ごとのテーブルになっており、イベントフラグごとにそのイベント待機するタスクキューがある。
/* * イベントフラグIDの最大値(kernel_cfg.c) */ extern const ID tmax_flgid; /* * イベントフラグ初期化ブロックのエリア(kernel_cfg.c) */ extern const FLGINIB flginib_table[]; /* * イベントフラグ管理ブロックのエリア(kernel_cfg.c) */ extern FLGCB flgcb_table[]; /* * イベントフラグの数 */ #define TNUM_FLG ((UINT)(tmax_flgid - TMIN_FLGID + 1)) /* * イベントフラグIDからイベントフラグ管理ブロックを取り出すためのマクロ */ #define INDEX_FLG(flgid) ((UINT)((flgid) - TMIN_FLGID)) #define get_flgcb(flgid) (&(flgcb_table[INDEX_FLG(flgid)])) /* * イベントフラグ待ち情報ブロックの定義 * * flgptn は,waiptn および wfmode と同時に使うことはないため,union * を使えばメモリを節約することが可能である. */ typedef struct eventflag_waiting_information { WINFO winfo; /* 標準の待ち情報ブロック */ WOBJCB *wobjcb; /* 待ちオブジェクトの管理ブロック */ FLGPTN waiptn; /* 待ちパターン */ MODE wfmode; /* 待ちモード */ FLGPTN flgptn; /* 待ち解除時のパターン */ } WINFO_FLG;
初期化の処理。これもセマフォで見たコードだ。
void eventflag_initialize(void) { UINT i; FLGCB *flgcb; for (flgcb = flgcb_table, i = 0; i < TNUM_FLG; flgcb++, i++) { queue_initialize(&(flgcb->wait_queue)); flgcb->flginib = &(flginib_table[i]); flgcb->flgptn = flgcb->flginib->iflgptn; } }
フラグの型は以下のとおり。UINT は 32 ビット以上なので 32 種類のイベントを管理できる。
typedef UINT FLGPTN; /* イベントフラグのビットパターン */
フラグセット。つまりイベント発火。
set_flg(ID flgid, FLGPTN setptn) { FLGCB *flgcb; TCB *tcb; WINFO_FLG *winfo; ER ercd; LOG_SET_FLG_ENTER(flgid, setptn); CHECK_TSKCTX_UNL(); CHECK_FLGID(flgid); flgcb = get_flgcb(flgid); t_lock_cpu(); // イベントのビットを立てる flgcb->flgptn |= setptn; // イベントを待っているタスクキューがある場合 if (!(queue_empty(&(flgcb->wait_queue)))) { // イベントのタスクキューを取り出してそのタスクの待機を解除しディスパッチする tcb = (TCB *)(flgcb->wait_queue.next); winfo = (WINFO_FLG *)(tcb->winfo); if (eventflag_cond(flgcb, winfo->waiptn, winfo->wfmode, &(winfo->flgptn))) { queue_delete(&(tcb->task_queue)); if (wait_complete(tcb)) { dispatch(); } } } ercd = E_OK; t_unlock_cpu(); exit: LOG_SET_FLG_LEAVE(ercd); return(ercd); }
イベントフラグを待機する。
SYSCALL ER wai_flg(ID flgid, FLGPTN waiptn, MODE wfmode, FLGPTN *p_flgptn) { FLGCB *flgcb; WINFO_FLG winfo; ER ercd; LOG_WAI_FLG_ENTER(flgid, waiptn, wfmode, p_flgptn); CHECK_DISPATCH(); CHECK_FLGID(flgid); CHECK_PAR(waiptn != 0); CHECK_PAR((wfmode & ~TWF_ORW) == 0); flgcb = get_flgcb(flgid); t_lock_cpu(); // イベントのタスクキューが空でないとダメです if (!(queue_empty(&(flgcb->wait_queue)))) { ercd = E_ILUSE; } else if (eventflag_cond(flgcb, waiptn, wfmode, p_flgptn)) { ercd = E_OK; } else { winfo.waiptn = waiptn; winfo.wfmode = wfmode; // イベント待ち。現在のタスクを待機状態へ移行 wobj_make_wait((WOBJCB *) flgcb, (WINFO_WOBJ *) &winfo); // 最高優先度のタスクをディスパッチ dispatch(); ercd = winfo.winfo.wercd; if (ercd == E_OK) { *p_flgptn = winfo.flgptn; } } t_unlock_cpu(); exit: LOG_WAI_FLG_LEAVE(ercd, *p_flgptn); return(ercd); }
イベントのビットをクリア。& してるので操作するビットを間違えるとアウチなことになる。
SYSCALL ER clr_flg(ID flgid, FLGPTN clrptn) { FLGCB *flgcb; ER ercd; LOG_CLR_FLG_ENTER(flgid, clrptn); CHECK_TSKCTX_UNL(); CHECK_FLGID(flgid); flgcb = get_flgcb(flgid); t_lock_cpu(); flgcb->flgptn &= clrptn; ercd = E_OK; t_unlock_cpu(); exit: LOG_CLR_FLG_LEAVE(ercd); return(ercd); }
_ [データキュー][μITRON][TOPPERS/JSP][コードリーディング]μITRON 実装 TOPPERS/JSP を読む - データキュー
1 ワードのデータを送受信するためのキュー。1 ワードだけどポインタを乗せれば何でもイケる。
コードはこれ kernel\dataqueue.c
データキュー初期化
void dataqueue_initialize(void) { UINT i; DTQCB *dtqcb; for (dtqcb = dtqcb_table, i = 0; i < TNUM_DTQ; dtqcb++, i++) { queue_initialize(&(dtqcb->swait_queue)); dtqcb->dtqinib = &(dtqinib_table[i]); queue_initialize(&(dtqcb->rwait_queue)); dtqcb->count = 0; dtqcb->head = 0; dtqcb->tail = 0; } }
データキューへ送信
SYSCALL ER snd_dtq(ID dtqid, VP_INT data) { DTQCB *dtqcb; WINFO_DTQ winfo; TCB *tcb; ER ercd; LOG_SND_DTQ_ENTER(dtqid, data); CHECK_DISPATCH(); CHECK_DTQID(dtqid); dtqcb = get_dtqcb(dtqid); t_lock_cpu(); // データ受信待ちのタスクキューがあれば送信する。最高優先度のタスクをディスパッチ if ((tcb = send_data_rwait(dtqcb, data)) != NULL) { if (wait_complete(tcb)) { dispatch(); } ercd = E_OK; } // キューに積む else if (enqueue_data(dtqcb, data)) { ercd = E_OK; } // データ受信されるまでタスクを待機状態へする。最高優先度のタスクをディスパッチ else { winfo.data = data; wobj_make_wait((WOBJCB *) dtqcb, (WINFO_WOBJ *) &winfo); dispatch(); ercd = winfo.winfo.wercd; } t_unlock_cpu(); exit: LOG_SND_DTQ_LEAVE(ercd); return(ercd); }
データキューから受信。最初の処理の意図が分からん。
SYSCALL ER rcv_dtq(ID dtqid, VP_INT *p_data) { DTQCB *dtqcb; WINFO_DTQ winfo; TCB *tcb; VP_INT data; ER ercd; LOG_RCV_DTQ_ENTER(dtqid, p_data); CHECK_DISPATCH(); CHECK_DTQID(dtqid); dtqcb = get_dtqcb(dtqid); t_lock_cpu(); // データキューがあれば if (dequeue_data(dtqcb, p_data)) { // データを受信 if ((tcb = receive_data_swait(dtqcb, &data)) != NULL) { // またデータキューへ入れる? enqueue_data(dtqcb, data); if (wait_complete(tcb)) { // タスクディスパッチはいつもの流れ dispatch(); } } ercd = E_OK; } // データ受信 else if ((tcb = receive_data_swait(dtqcb, p_data)) != NULL) { if (wait_complete(tcb)) { dispatch(); } ercd = E_OK; } // データ無ければ受信待ちキューへ入れて待機状態へ移行 else { runtsk->tstat = (TS_WAITING | TS_WAIT_WOBJ); make_wait(&(winfo.winfo)); queue_insert_prev(&(dtqcb->rwait_queue), &(runtsk->task_queue)); winfo.wobjcb = (WOBJCB *) dtqcb; LOG_TSKSTAT(runtsk); dispatch(); ercd = winfo.winfo.wercd; if (ercd == E_OK) { *p_data = winfo.data; } } t_unlock_cpu(); exit: LOG_RCV_DTQ_LEAVE(ercd, *p_data); return(ercd); }
大学院受かったZE☆
おめー
こんにちは<br>rcv_dtqでコメントにクエスチョンマークが付いているところですが、これは「また入れている」訳ではありません。この部分は「キューが一杯で待ちに入っていたタスク」への手当です。<br>snd_dtqによってデータがキューに入れられますが、データキューに空きが無い場合、snd_dtqの内部でタスクは待ち状態になります。recevie_data_swaitはそのようなタスク(send wait状態)のTCBと、投入すべきデータを引数に返します。<br>一杯だったデータキューにrcv_dtqによって空きが生じると、それまで送信待ち状態であったタスクのうち、最初にキューに入れる権利を持つタスクがデータを投入します。コメントの?マークの直下のenque_data()はまさにそれを行っています。
酔漢さん: 指摘ありがとうございます。receive_data_swait の定義に「送信待ちキューの先頭タスクからのデータ受信」とコメントがありました。そういう意味だったのですね。