Operating System

Z80の組み込みOSを検討してみようと思います。

ターゲットのデバイスは、Super AKI-80です。

いきなりμITRONとかは敷居が高いので、ラウンドロビンスケジュールリング(もどき)から入ろうと思います。

一応マルチタスクにしたいので、ラウンドロビンスケジューリングを意識されてネットに公開されていたコードを拝借しました。

理屈はともかく、まず動かすことから始めます。

【開発要件】

①各タスクが自動的に切り替わる(ループする)こと

 タスクIDの管理

②各タスク毎の汎用レジスタコンテキストスイッチが出来ること

    iy, ix, hl, de, bc, af, pc

③各タスク毎に変数領域が確保されること

補足:

ノンプリエンプティブマルチタスク
マルチタスクのひとつ。一度に複数のアプリケーションソフトを実行する際、各アプリケーションソフトがCPUの空き時間を他のアプリケーションソフトに開放することで同時に実行する。Windows 3.1や初期のMac OSが採用。⇔プリエンプティブマルチタスク

◇和製語。ノンプリエンプティブ(non-preemptive)+ マルチタスク(multitask)。「協調的マルチタスク」「疑似マルチタスク」ともいう。

 

OSのコアの部分ですが、

以下のヘッダーファイルtask.hとtask.cから成ります。

【3/30 update】

/**********************************************************************

** Header fileto support the round robin like scheduling

** Non preemptive scheduling ** modified & written by jk1brk

** Last update 2019/3/30

*********************************************************************/

#ifndef TASK_H
#define TASK_H

#define MAX_TASK 3

#define STACK_SIZE 64
static char reg_stack[MAX_TASK][STACK_SIZE];

/* Simple TCB */

typedef struct {
   uint current_id;                   // Current task ID
   uint reg_sp[MAX_TASK+1]; // Pointer to REGs stack area
} TASK_DATA_t;

static TASK_DATA_t task_data;

typedef struct {
  uint iy;
  uint ix;
  uint hl;
  uint de;
  uint bc;
  uint af;
  uint pc;
} STACK_t;

#define INIT_STACK(stack) (stack+STACK_SIZE-(sizeof(STACK_t)))

#endif //TASK_H

 

以下task.cですが、タスクの起動/起床機能とタスクの切り替え機能から成るシンプルな構成です。

/**********************************************************************
** Functins to support the round robin like scheduling

** Non preemptive scheduling ** modified & written by jk1brk

** Last update 2019/3/30
*********************************************************************/
#include <stdio.h>
#include <stdbool.h>
#include "myint.h"
#include "z80.h"
#include "aki80.h"

#include "task.h"

bool init_task(id,func)

uchar id;

void (*func)(void);
{
  uint *p;

  if(id==0)
  return false;

  p = (uint*)INIT_STACK(reg_stack[id - 1]);
  task_data.reg_sp[id] = (uint)p;
  *p++ = 0; // IY
  *p++ = 0; // IX
  *p++ = 0; // HL
  *p++ = 0; // DE
  *p++ = 0; // BC
  *p++ = 0; // AF
  *p = (uint*)func;  // PC

return true;
}

/* Return next task REGs pointer */

uint switch_reg(reg_sp)

uint reg_sp;
{
  uchar next_id = task_data.current_id + 1;
  if(next_id > MAX_TASK)
    next_id = 0;
  task_data.reg_sp[task_data.current_id] = reg_sp;
  task_data.current_id = next_id;
  return task_data.reg_sp[next_id];
}

task_switch()
{
#asm
  di
  push AF            ; Save current REGs for context switching
  push BC
  push DE
  push HL
  push IX
  push IY
;
  ld IY,0                  ; Get current SP as the REGs stack pointer
  add IY,sp
  push IY
  call _switch_reg  ; Save current SP and return next task's SP 
  ld L,C                  ; Set return value into SP
  ld H,B
  ld SP,HL
;
  pop IY                ; Restore next task's REGs for context switching
  pop IX
  pop HL
  pop DE
  pop BC
  pop AF
#endasm
}

Download 機能を追加しました。 ( その他コンピュータ ) - jk1brk/wi2yのブログ - Yahoo!ブログ

で紹介しているモニタプログラムから起動して動作させている様子です。↓

f:id:wi2y59:20190321175608j:plain

【注】

以上のコードは、ほぼデッドコピーです。(笑)

私の思い込みで書いているので、理解が深まるごとに今後アップデートして行きます。

【3/23追記】

f:id:wi2y59:20190324213959j:plain
各タスク毎に独自のスタックエリアを持たせます。task_switch()でレジスタをpushする時とpopするときのspを切り替えています。このことからコンテキストの切り替えが可能となります。すなわち現在のタスクのspにpushしてpopする時は、次のタスクのspをセットしてからpopして次のタスクに処理を移行します。task_switch()から戻るpc(プログラムカウンタ)も次のタスクのものになります。

この操作を繰り返すことで、Non preemptiveなスケジュールでRound Robinすることになります。