デッドコピーで終わらせないぞ!

https://jk1brk.hatenablog.com/entry/2019/03/29/201005

https://jk1brk.hatenablog.com/entry/2019/03/21/170834

の続きです。自分なりにコードを分析しました。今行っていることは、かっこ良くいうとリバースエンジニアリングです。

Copyright, Copyleft, GPL等々使用許諾条件なく公開されているソースコードは、いわゆるパブリックドメインと判断して、個人の趣味に利用させていただいております。

厳密に突き詰めて、もっと慎重に扱った方が良いのではと一方では思いますが。。。

逆のケースでは、使用許諾条件なく公開されているソースコード でも自分で改変して公開する場合は、なるべくコードの先頭部分に

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

** Header file to support the round robin like scheduling

** Non preemptive scheduling

** modified & written by jk1brk

** Last update 2019/3/23

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

の様に記述する様にしています。公のものを趣味であっても私的に流用する分けですから、過去の作者/編者に敬意を払うべきだと思うからです。

公開されているプログラムのソースコードに限定される話でもありませんが。

Context switchの様子

今回は、文字が少ないです。次回から解説したいと思います。

f:id:wi2y59:20190329201031j:plain

RR Scheduling

 main()は、task0()に相当

各関数内にfor()の無限ループの記述がありますが、context switchを行うことで、無限ループから抜けて、続くタスクに処理を移しています。スタックとそのポインタってこの様にも使うのだと今更目からうろこです。

かつてPICのOSを弄っていた時期もありましたが、そこまで突っ込んでいませんでした。

 

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

* Sample program using round robin like scheduling *

* modified & written by jk1brk                                     *

* Last update 2019/3/24                                             *

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

#include <stdio.h>

#include <stdbool.h>

#include "myint.h"

#include "z80.h"

#include "aki80.h"

 

#include "task.h"

 

#define putchr(c) SIOB_putc(c)     

#define transmitNow(void) SIOB_transmitNow(void)

// Declare functions regarding task

extern bool init_task(uchar no,void (*func)(void));

extern unsigned int switch_reg(unsigned int reg_sp);

extern void task_switch(void);

static int gCount;                                              // Counter for showing #

 

void SIOB_putc(c)

char c;

{

              volatile unsigned char status;

              do{

                  status=inp(SIOB_CMD);              // RR0

              }while((status & 0x04)==0);              // Check send buf. empty

              outp(SIOB_DATA, c);

}

 

bool SIOB_transmitNow(void)

{

              volatile unsigned char status2;

              outp(SIOB_CMD, SIO_CMD_Null | 1);      // Select WR1

              status2=inp(SIOB_CMD);

              if(status2 & 0x01)             return 0;            // All char.s not sent yet

              else                                  return 1;            // Ready to send

}

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

* each task                                                            *

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

void task1(void)

{

              static unsigned char lCount_1 = 1;

              for(;;){

                            printf("task1 %d\n\r", gCount++);

                            putbyt(lCount_1++);

                            putnwl();

                            task_switch();

              // PC is for the next task after task_switch()

              }

}

 

void task2(void)

{

              static unsigned char lCount_2 = 2;

              for(;;){

                            printf("task2 %d\n\r", gCount++);

                            putbyt(lCount_2++);

                            putnwl();

                            task_switch();

              // PC is for the next task after task_switch()

              }

}

 

void task3(void)

{

              static unsigned char lCount_3 = 3;

              for(;;){

                            printf("task3 %d\n\r", gCount++);

                            putbyt(lCount_3++);

                            putnwl();

                            task_switch();

              // PC is for the next task after task_switch()

              }

}

 

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

* main()                                                                 *

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

main(void)

{

              unsigned char c;

              gCount = 0;        

              printf("Multi task sample program Ver. 1.4\n\r");

 

              init_task(1, task1);

              init_task(2, task2);

              init_task(3, task3);

 

              for(c = 0; c < MAX_TASK+1; c++) {

//            for(;;) {

                            printf("main %d\n\r", gCount++);

                            task_switch();

                            __EI();

              }          

              ;

              return    printf("Return to monitor\n\r");

}

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

putwrd(n)

int n;

{

              char c, buf[5];

              int i;

              for(i = 3; i >= 0; i--){

                            c = n & 15;

                            n = (n >> 4 ) & 4095;

                            if(c < 10) buf[i] = c + 48;

                                 else buf[i] = c + 55;

              }

              buf[4] = 0;

              putstr(buf);

}

 

putbyt(n)

char n;

{

              char c,buf[3];

              int i;

              for(i = 1; i >= 0; i--){

                            c = n & 15;

                            n = (n >> 4 ) & 4095;

                            if(c < 10) buf[i] = c + 48;

                                 else buf[i] = c + 55;

              }

              buf[2] = 0;

              putstr(buf);

}

 

putnwl( )

{

              putstr("\15\12");

}

 

putstr(b)

char *b;

{

              while(*b)

                            putchr(*b++);

}

16 chのロジアナを調達しました!

8chでは、もの足りないので16chにしました。

chあたりの購入単価は、約倍になりますが。。。

f:id:wi2y59:20190324091345j:plain

f:id:wi2y59:20190324091851j:plain

 海外のサイトから購入しました。

英語での注文、本当に届くのか のリスクを負った分納得のいく価格で購入出来ました。

USBのドライバの最新版は、

www.qdkingst.com

からロード出来ました。pulseviewでは、まだこの製品に対応(2019/3/24現在)していない様です。

なので、pulseviewのUSBドライバとのコンフリクトを避ける意味で、pulseviewのそれは事前に削除しました。

またトリガーコンディションの設定が分かりにくいですね。

本体にあるStatus LEDの点滅から、トリガーコンディションの設定状態にあることも判断出来ます。

 

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することになります。