デッドコピーで終わらせないぞ!
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の様子
今回は、文字が少ないです。次回から解説したいと思います。
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あたりの購入単価は、約倍になりますが。。。
海外のサイトから購入しました。
英語での注文、本当に届くのか のリスクを負った分納得のいく価格で購入出来ました。
USBのドライバの最新版は、
からロード出来ました。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!ブログ
で紹介しているモニタプログラムから起動して動作させている様子です。↓
【注】
以上のコードは、ほぼデッドコピーです。(笑)
私の思い込みで書いているので、理解が深まるごとに今後アップデートして行きます。
【3/23追記】
各タスク毎に独自のスタックエリアを持たせます。task_switch()でレジスタをpushする時とpopするときのspを切り替えています。このことからコンテキストの切り替えが可能となります。すなわち現在のタスクのspにpushしてpopする時は、次のタスクのspをセットしてからpopして次のタスクに処理を移行します。task_switch()から戻るpc(プログラムカウンタ)も次のタスクのものになります。
この操作を繰り返すことで、Non preemptiveなスケジュールでRound Robinすることになります。