SSブログ

ColdFire V1でコンテキストスイッチ [ColdFire (ColdeFire) V1]このエントリーを含むはてなブックマーク#

ColdFire V1を使って、コンテキストスイッチを行うプログラムを作成してみました。モデルは、OS-9/68000です。

//**************************************************************
// $Id: main.c,v 1.3 2008/01/10 15:04:18 noritan Exp $
//
// Multi tasking Practice
//**************************************************************
#include <hidef.h> /* for EnableInterrupts macro */
#include "derivative.h" /* include peripheral declarations */

定数の定義です。タスクの数、スタックのサイズ、ステートマシンの状態コードを定義しています。スタックサイズは、4Byte(Long Word)単位の数です。

#define N_TASK (10)
#define N_SSTACK (30)
#define N_USTACK (30)

#define ST_NULL (0)
#define ST_SLEEP (1)
#define ST_ACTIVE (2)
#define ST_RUN (3)

TaskDescriptor は、OS-9では、Process Descriptor と呼ばれていました。
他のオペレーティング・システムでは、 Task Control Block と呼ばれることもあります。
OS-9/68000 の Process Descriptor は、2KByteもありましたが、ここでは必要最小限としてあります。
count は、状況によって使い分ける万能カウンタです。

struct TaskDescriptor {
  const struct ModuleDescriptor *module;
  dword *workarea;
  dword *ssp;
  dword *usp;
  dword status;
  dword count;
  dword sStack[N_SSTACK];
};

ModuleDescriptor は、プログラム単位を定義しています。
OS-9/68000 では、ModuleDescriptor に指定された大きさの作業領域などを動的に割り当てることが出来るのですが、今回は、初期化と実行部のエントリだけ定義しています。

struct ModuleDescriptor {
  void (*init)(
    struct TaskDescriptor *task,
     int argc, char **argv
  );
  void (*proc)(void);
};

typedef struct TaskDescriptor TaskDescriptor;
typedef struct ModuleDescriptor ModuleDescriptor;

TaskDescriptor は、同時実行可能なタスクの数だけ必要です。
thisTaskは、現在実行中のタスクを指します。

TaskDescriptor taskTable[N_TASK];

TaskDescriptor *thisTask = 0;

各タスクに必要な作業領域は、初期化ルーチンで確保します。
ユーザモードでのスタックは、ここに確保します。
作業領域は、割り当てるけど開放しないいいかげん方式です。

typedef struct WorkArea {
  dword toggle;
  dword period;
  dword uStack[N_USTACK];
} WorkArea;

dword workareaUsed = 0;
WorkArea workareaTable[N_TASK];

ModuleDescriptor を元に新しいタスクを作成し、実行中タスクリストに登録します。
もっとも、現在の実装では、リストを作成しているわけではなく、
タスクの状態を "ACTIVE" にするだけで、
あとはスケジューラが解決してくれています。

//----------------------------------------------
//  Create a TASK with a MODULE and PARAMETER
//  and add to ACTIVE list.
//----------------------------------------------
TaskDescriptor *tsk_start(
    const ModuleDescriptor * module,
     int argc,
     char * *  argv
) {
  int i;
  struct TaskDescriptor *task;
  
  for (i = 0, task = &taskTable[0]; i < N_TASK; i++, task++) {
    if (task->status == ST_NULL) {
      task->module = module;
      task->ssp = &(task->sStack[N_SSTACK]);
      task->status = ST_ACTIVE;
      task->count = 0;
      (task->module->init)(task, argc, argv);
      return task;
    }
  }
  return 0;
}
「何もしないプログラム」の初期化部分です。 作業領域を確保し、スタックフレームを構築しています。 スタックフレームは、例外が発生した時のイメージを再現しています。
void idle_task_init(
    TaskDescriptor *  task,
    int argc,
    char **argv
) {
#pragma unused (argc, argv)
  WorkArea *workarea = &workareaTable[workareaUsed++];
  register dword *usp = &(workarea->uStack[N_USTACK]);
  register dword *ssp;
  dword *_A5;
  dword i;
  void idle_task_proc();
  void tsk_exit(void);

#pragma warn_any_ptr_int_conv off  
  // Establish DUMMY user stack
  *(--usp) = (word)tsk_exit;

  // Establish a stack frame
  ssp = task->ssp;
  *(--ssp) = (dword)idle_task_proc;    // PC
  *(--ssp) = 0x40000000;              // Vector & SR
  *(--ssp) = (dword)usp;  // A6
  __asm {
    move a5,_A5
  }
  *(--ssp) = (dword)_A5;  // copy of current A5
  for (i = 0; i < 13; i++) {
    *(--ssp) = 0;  // D0-D7/A0-A4
  }
#pragma warn_any_ptr_int_conv reset  

  task->workarea = (dword *)workarea;
  task->usp = usp;
  task->ssp = ssp;
}
「何もしないプログラム」の実行部です。 犬にえさを与え続けます。
void idle_task_proc(void) {
  for (;;) {
    // Do nothing
    __RESET_WATCHDOG(); /* feeds the dog */
  }
}
「何もしないプログラム」モジュールを定義します。 モジュールは、ROMに配置されます。
const ModuleDescriptor idle_module = {
  idle_task_init,
  idle_task_proc
};
メインルーチンです。 マイコンの初期化を行い、 「何もしないプログラム」モジュールを使って二つの「何もしないタスク」を作成し、 "trap #0" でコンテキストを切り替えます。
void main(void) {
  TaskDescriptor *idle_task;

  // Configure one-time-write SOPT1
  SOPT1 = 0
    | SOPT1_WAITE_MASK    // Enable WAIT instruction
    | SOPT1_RSTOPE_MASK   // Enable RSTO* port
    | SOPT1_BKGDPE_MASK   // Enable BKGD port
    | SOPT1_RSTPE_MASK ;  // Enable RESET* port

  // Configure RTC module
  RTCSC_RTCLKS = 0;       // 1kHz OSC
  RTCSC_RTCPS  = 11;      // 10msec
  RTCMOD       = 50-1;    // 50counts (500msec)
  RTCSC_RTIE   = 1;       // Enable interrupts.

  // Invoke two tasks
  idle_task = tsk_start(&idle_module, 0, 0);
  idle_task = tsk_start(&idle_module, 0, 0);
  __asm {
    // yeild to context switch
    trap #0
  }

  EnableInterrupts; /* enable interrupts */
  for(;;) {
    __RESET_WATCHDOG(); /* feeds the dog */
  } /* loop forever */
  /* please make sure that you never leave main */
}
ユーザモードの実行部プログラムが終了したら、"tsk_exit" にやってきます。 今回は、使われません。 あれ? "trap #0" が必要なはずだぞ。
void tsk_exit(void) {
  thisTask->status = ST_NULL;
}
次に実行すべきタスクを選択します。 OS-9/68000 がそうであるように、 タスクリストの中から、もっとも待ち時間が長い実行状態タスクを選びます。 待ち時間は、 "count" で数えていて、 "schedule" で選択されないと インクリメントされます。
void schedule(void) {
  int i;
  struct TaskDescriptor *task;
  dword maxCount = 0;
  struct TaskDescriptor *taskSelected = 0;
  
  for (i = 0, task = &taskTable[0]; i < N_TASK; i++, task++) {
    if (task->status == ST_ACTIVE) {
      task->count++;
      if (task->count > maxCount) {
        taskSelected = task;
        maxCount = task->count;
      }
    }
  }
  thisTask = taskSelected;
  if (thisTask) {
    thisTask->count = 0;
  }
}
"trap #0" 例外は、現在実行中のタスクを中断して、 別のタスクに切り替える機能があります。 この部分は、 タスク選択の部分だけは"schedule"関数を呼び出していますが、 スタックの深さが重要になってくるので、 残りはフルにアセンブラで記述しています。
asm interrupt VectorNumber_Vtrap0 trap0_isr(void) {
  // Save the stack frame == task context
    lea -60(a7),a7
    movem d0-d7/a0-a6,(a7)
  // Save SSP/USP to the current TaskDescriptor
    move.l thisTask,a0
    move.l a7,struct(TaskDescriptor.ssp)(a0)
    move.l usp,a1
    move.l a1,struct(TaskDescriptor.usp)(a0)
  jsr schedule  
  // Restore SSP/USP from the selected TaskDescriptor
    move.l thisTask,a0
    move.l struct(TaskDescriptor.usp)(a0),a1
    move.l a1,usp
    move.l struct(TaskDescriptor.ssp)(a0),a7
  // Restore the stack frame
    movem (a7),d0-d7/a0-a6
    lea 60(a7),a7
    rte
}
タイマ割り込みのISRです。 基本的に"trap #0"と同じですが、 タイマフラグをクリアするシーケンス"rtc_isr_content"を 呼び出しています。
void rtc_isr_content(void) {
    RTCSC_RTIF = 1;         // Clear the flag.
}

asm interrupt VectorNumber_Vrtc rtc_isr(void) {
  // Save the stack frame == task context
    lea -60(a7),a7
    movem d0-d7/a0-a6,(a7)
  // Save SSP/USP to the current TaskDescriptor
    move.l thisTask,a0
    move.l a7,struct(TaskDescriptor.ssp)(a0)
    move.l usp,a1
    move.l a1,struct(TaskDescriptor.usp)(a0)
  // Do ISR specific task    
    jsr rtc_isr_content
  // Do task scheduling
    jsr schedule  
  // Restore SSP/USP from the selected TaskDescriptor
    move.l thisTask,a0
    move.l struct(TaskDescriptor.usp)(a0),a1
    move.l a1,usp
    move.l struct(TaskDescriptor.ssp)(a0),a7
  // Restore the stack frame
    movem (a7),d0-d7/a0-a6
    lea 60(a7),a7
    rte
}

//**************************************************************
//  $Log: main.c,v $
//  Revision 1.3  2008/01/10 15:04:18  noritan
//  Completed... maybe
//
//**************************************************************

参考文献

OS‐9/68000 (OSシリーズ)

OS‐9/68000 (OSシリーズ)

  • 作者: 高沢 嘉光
  • 出版社/メーカー: 共立出版
  • 発売日: 1989/01
  • メディア: 単行本

nice!(0)  コメント(0)  トラックバック(0)  このエントリーを含むはてなブックマーク#

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

トラックバックの受付は締め切りました