いろんなものはつながっている

組み込みOS自作入門(9)スレッドを実装

スレッド

複数の処理を行う際にメインループからその都度、該当の処理を呼び出す、という方法ではなく、それぞれ処理が独立したプログラムであるように独立して動作できるようにしたものがスレッド。例えば、メイン関数に戻るのではなく、以下のように動画を再生するプログラム(movie_player_main)、コマンドを実行するプログラム(command_main)のスレッドが作成されそれぞれ実行されるというイメージ。

int entry_point(int argc, char* argv[])
{
    kz_run(movie_player_main, "movie",   1, ...); /* 動画を再生するスレッドを生成*/
    kz_run(command_main     , "command", 2, ...); /* コマンド実行のスレッドを作成*/
    .....
   
    while(1)
    {
        asm volatile ("sleep");
    }
}

int movie_player_main()  /* 動画を再生するスレッド */
{
    while(1) {
        play(file);
        ....
    }
}

int command_main()       /* コマンド実行のスレッド*/
{
    while(1) {
        command();
        ....
    }
}

それぞれの処理の実行の中断と切り替えはOSが適当なタイミングで行う。次に実行する処理を決め(スケジューリング)、選択された処理を再開(ディスパッチ)する。

処理の切り替え

処理の切り替えは、
①実行していた処理の関する情報をいったん退避して
②次に実行する処理に関する情報をセットし実行
③1.に戻る
のように行われる。

処理を再開させるには具体的にはどんな情報か必要かというと、どのアドレスにある命令を実行しているか(プログラムカウンタ)、どのアドレスをスタック領域として使っているか(スタックポインタ)等々がある。これらの情報をコンテキスト情報という。

スレッドの切り替えは割り込み処理で行われる。割り込み処理は現在の処理内容を一時中断して別の処理をを行うものなので、この処理をそのまま使えば、処理の切り替えを行える。

①の対応は割り込みハンドラのなかですでに実装しているのでそれを利用する。一方、②の対応は、割り込み処理から復帰する際にrteを実行すると、スタックにつんでいたプログラムカウンタとCCR(コンディションコードレジスタ)がレジスタに復帰し、プログラムカウンタに設定されたアドレス値の処理が実行(処理が再開)されることを利用する。

sp = (スタックポインタ)
*(--sp) = (uint32)func; /*プログラムカウンタにあたる */
*(--sp) = 0;    /* ER6 */
*(--sp) = 0;    /* ER5 */
*(--sp) = 0;    /* ER4 */
*(--sp) = 0;    /* ER3 */
*(--sp) = 0;    /* ER2 */
*(--sp) = 0;    /* ER1 */
*(--sp) = arg1; /* ER0 関数funcに渡す引数*/ 

というようにスタックに、対象処理の関数のポインタ(func)を設定し、レジスタの初期値、引数等々のコンテキスト情報も設定する。その状態で、

_dispatch:
	mov.l	@er0,er7
	mov.l	@er7+,er0
	mov.l	@er7+,er1
	mov.l	@er7+,er2
	mov.l	@er7+,er3
	mov.l	@er7+,er4
	mov.l	@er7+,er5
	mov.l	@er7+,er6
	rte

dispatchを呼び出すと、スタックにつんだコンテキスト情報がレジスタに設定され、rteを実行することでスタックにある関数のアドレス値(上の例であるとfunc)がプログラムカウンタに設定されたfuncが実行される。
スレッド1

スレッドの管理

スレッドはタスクという単位で管理される。タスクは構造体の形で必要な情報が管理され、スレッドのメイン関数やコンテキスト情報が保持されているスタックのアドレスなどが含まれる。OSはタスクを順々に選択しスレッドの処理を実行する。

/* タスク・コントロール・ブロック  */
typedef struct _kz_thread {
	struct _kz_thread *next;
	char name[THREAD_NAME_SIZE + 1];
	char *stack; /* スレッドのスタック */
	
	struct { /* スレッドのスタートアップに渡すパラメータ */
		kz_func_t func;	/* スレッドのメイン関数 */
		int argc;
		char **argv;
	} init;
	
	struct { /* システムコール用のバッファ */
		kz_syscall_type_t type;
		kz_syscall_param_t *param;
	} syscall;
	
	kz_context context; /* スレッドのコンテキスト情報の保存領域 */
} kz_thread;

スケジューリング

タスクの切り替えは割り込みが発生したときに実行される。割り込みが発生すると、まわりまわって、thread_intr()が呼び出され、その関数のなかでスケジューリングが行われる。
スレッド2

関連記事

コメント

  1. この記事へのコメントはありません。

  1. この記事へのトラックバックはありません。

スポンサード リンク

カテゴリー

スポンサード リンク