トップ «前の日記(2014-09-14) 最新 次の日記(2014-09-16)» 編集

ヨタの日々

2001|08|09|10|11|12|
2002|01|02|03|04|05|06|07|08|09|10|11|12|
2003|01|02|03|04|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|12|
2024|01|02|03|

2014-09-15 :-)

_ 午前

0930 起床

1020 おひる。うどん

1040 ご注文はコーヒーですか?

DSC00012

DSC00013

_ 午後

1200 アニメ消化

1400 散歩

1600 アニメ消化

DSC00016

_

1700 しましょしましょ

2130 飯。ゴーヤーチャンプルー

DSC00020

DSC00022

_ 買い物

iTunes Store

_ [μ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);
}
本日のツッコミ(全4件) [ツッコミを入れる]
_ エモエモ (2014-09-16 20:55)

大学院受かったZE☆

_ みわ (2014-09-17 00:11)

おめー

_ 酔漢 (2015-03-22 15:40)

こんにちは<br>rcv_dtqでコメントにクエスチョンマークが付いているところですが、これは「また入れている」訳ではありません。この部分は「キューが一杯で待ちに入っていたタスク」への手当です。<br>snd_dtqによってデータがキューに入れられますが、データキューに空きが無い場合、snd_dtqの内部でタスクは待ち状態になります。recevie_data_swaitはそのようなタスク(send wait状態)のTCBと、投入すべきデータを引数に返します。<br>一杯だったデータキューにrcv_dtqによって空きが生じると、それまで送信待ち状態であったタスクのうち、最初にキューに入れる権利を持つタスクがデータを投入します。コメントの?マークの直下のenque_data()はまさにそれを行っています。

_ みわ (2015-03-22 18:08)

酔漢さん: 指摘ありがとうございます。receive_data_swait の定義に「送信待ちキューの先頭タスクからのデータ受信」とコメントがありました。そういう意味だったのですね。