*ユーザープログラムのパラメーターが3ページ(最後のページ)のメモリの一部を指している場合、カーネルは常にSyscall中にこのまったく同じページ内にRAMページをマッピングするため、競合があります。したがって、プログラムのパラメーターにアクセスするために、ユーザーのページ3を2ページ(3ページ)に再マップします。もちろん、パラメーターがポインターである場合、それらは変更されて新しい仮想アドレスを指すようにします(言い換えれば、ポインターは16kBで差し引かれ、2ページを指すようにします)。
上記のように編成されたMMU/メモリマッパーを持たないZ80ベースのコンピューターに熱心な8ビットOSをポートできるようにするために、カーネルにはmenuconfig :no-MMUを介して選択できる新しいモードがあります。
このモードでは、OSコードは、 0x0000から0x3FFFまでのメモリの最初の16kbでマッピングされると予想され、残りはRAMであると予想されます。
理想的には、48kbのRAMを0x4000からマッピングし、 0xFFFFに上がる必要がありますが、実際には、カーネルを構成するように構成することができます。そのためには、 menuconfigの2つのエントリを適切に構成する必要があります。
KERNEL_STACK_ADDR :これはカーネルRAM領域の端をマークし、その名前が述べているように、カーネルスタックの底になります。KERNEL_RAM_START :これにより、カーネルRAMのスタートアドレス、カーネル、およびドライバーが使用するすべての変数が保存されます。もちろん、これらすべてのデータを保存するのに十分な大きさでなければなりません。詳細については、現在のカーネルBSSセクションサイズは約1kbです。スタックの深さは、ターゲットドライバーの実装に依存します。スタックに1kbを割り当てることは、ノー(大きな)バッファーが保存されている限り、十分に大きくなければなりません。カーネルRAMに少なくとも3kBの全体的な割り当ては、安全で将来の根拠が必要です。要約すると、メモリの使用を示す図が次のとおりです。
ユーザープログラムに関しては、スタックアドレスは、実行前にカーネルによって常にKERNEL_RAM_START - 1に設定されます。また、使用可能なアドレススペースで使用可能な最後のバイトのアドレスにも対応しています。これは、プログラムがSP - 0x4000を実行することにより、利用可能なRAMのサイズを決定できることを意味します。
ld hl, 0
add hl, sp
ld bc, -0x4000
add hl, bc
; HL contains the size of the available RAM for the program, which includes the program's code and its stack.
Z80は複数の汎用レジスタを提示しますが、それらのすべてがカーネルで使用されているわけではありません。ここにそれぞれの範囲があります。
| 登録する | 範囲 |
|---|---|
| AF、BC、DE、HL | システムとアプリケーション |
| af '、bc'、de '、hl' | 割り込みハンドラー |
| IX、IY | アプリケーション(OSで未使用) |
これは、OSがIXおよびIYレジスタを変更しないため、アプリケーションで自由に使用できることを意味します。
代替レジスタ(その後の名前' )は、割り込みハンドラー1でのみ使用できます1 。アプリケーションはこれらのレジスタを使用しないでください。何らかの理由で、それらを使用する必要がある場合は、使用されている間に割り込みを無効にすることを検討してください。
my_routine:
di ; disable interrupt
ex af, af' ; exchange af with alternate af' registers
[...] ; use af'
ex af, af' ; exchange them back
ei ; re-enable interrupts
システムがハードウェア(タイマー、キーボード、gpios ...)から信号を受け取らないため、あまりにも長い間割り込みを無効にすることは有害である可能性があることに留意してください。
Z80は、システムが常にメモリの最初の仮想ページに保存されることを意図しているため、8つの異なるリセットベクターを提供します。これらはすべてOS用に予約されています。
| ベクター | 使用法 |
|---|---|
| $ 00 | ソフトウェアリセット |
| $ 08 | syscall |
| 10ドル | HLのアドレスにジャンプします(HLを呼び出すために使用できます) |
| 18ドル | 未使用 |
| 20ドル | 未使用 |
| 28ドル | 未使用 |
| 30ドル | 未使用 |
| 38ドル | 割り込みモード1用に予約されており、ターゲットの実装で使用可能 |
ユーザープログラムが実行されると、カーネルは3ページのRAM(48kb)を割り当て、バイナリファイルを読み取り、デフォルトで仮想アドレス0x4000で実行してロードします。このエントリポイント仮想アドレスは、オプションKERNEL_INIT_EXECUTABLE_ADDRを使用してmenuconfig介して構成できますが、実行時に再配置できないため、既存のプログラムが再コンパイルされなくても機能しなくなることに留意してください。
以下で説明するように、 exec syscallは、実行するバイナリファイル名とパラメーターの2つのパラメーターを使用します。
このパラメーターはDEコピーされてバイナリに送信されるヌル終端文字列である必要がありますBC
DEは文字列のアドレスが含まれています。この文字列は、通常はスタックの上部にある新しいプログラムのメモリスペースにコピーされます。BCは、その文字列の長さが含まれています(したがって、null-byteを除く)。 BCが0の場合、 DEユーザープログラムによって破棄される必要があります。 システムは、ユーザープログラムとカーネルの間でリクエストを実行するためにSyscallsに依存しています。したがって、これはハードウェアで操作を実行する方法です。考えられる操作は、以下の表にリストされています。
| num | 名前 | パラメーション。 1 | パラメーション。 2 | パラメーション。 3 |
|---|---|---|---|---|
| 0 | 読む | U8開発 | U16 BUF | U16サイズ |
| 1 | 書く | U8開発 | U16 BUF | U16サイズ |
| 2 | 開ける | U16名 | U8フラグ | |
| 3 | 近い | U8開発 | ||
| 4 | dstat | U8開発 | U16 DST | |
| 5 | 統計 | U16名 | U16 DST | |
| 6 | 求める | U8開発 | U32オフセット | u8hance |
| 7 | ioctl | U8開発 | U8 CMD | U16 arg |
| 8 | mkdir | U16パス | ||
| 9 | Chdir | U16パス | ||
| 10 | カルディール | U16パス | ||
| 11 | オペンディル | U16パス | ||
| 12 | readdir | U8開発 | U16 DST | |
| 13 | rm | U16パス | ||
| 14 | マウント | U8開発 | U8文字 | U8 FS |
| 15 | 出口 | U8コード | ||
| 16 | exec | U16名 | U16 Argv | |
| 17 | dup | U8開発 | u8 ndev | |
| 18 | 眠っている | U16期間 | ||
| 19 | 入植 | U8 ID | U16時間 | |
| 20 | GetTime | U8 ID | U16時間 | |
| 21 | setDate | U16日付 | ||
| 22 | getDate | U16日付 | ||
| 23 | 地図 | U16 DST | U24 SRC | |
| 24 | スワップ | U8開発 | u8 ndev |
これらの各呼び出しとそのパラメーターの詳細については、以下のセクションを確認してください。
注:一部のシステムは実装されていない場合があります。たとえば、ディレクトリがサポートされていないコンピューターでは、ディレクトリ関連のsyscallsが省略される場合があります。
syscallを実行するには、操作番号をレジスタLに保存する必要があります。これらのルールに従ってパラメーターを保存する必要があります。
| APIのパラメーター名 | Z80レジスタ |
|---|---|
| U8開発 | H |
| u8 ndev | E |
| U8フラグ | H |
| U8 CMD | C |
| U8文字 | D |
| U8コード | H |
| U8 FS | E |
| U8 ID | H |
| u8hance | A |
| U16 BUF | DE |
| U16サイズ | BC |
| U16名 | BC |
| U16 DST | DE |
| U16 arg | DE |
| U16パス | DE |
| U16 Argv | DE |
| U16期間 | DE |
| U16時間 | DE |
| U16日付 | DE |
| U24 SRC | HBC |
| U32オフセット | BCDE |
そして最後に、コードはRST $08命令を実行する必要があります(リセットベクターを確認してください)。
返された値はAに配置されます。その値の意味は各呼び出しに固有です。詳細については、関係するルーチンのドキュメントを確認してください。
ユーザープログラムを最大化するために、カーネルがMMUモードでコンパイルされているかNO-MMUモードでコンパイルされているかに関係なく、熱心な8ビットOSカーネルとの互換性を最大化するために、Syscallsパラメーターの制約は同じです。
syscallに渡されたバッファーは、16kbの仮想ページを横断してはなりません
言い換えれば、サイズnのバッファーbuf仮想ページiにある場合、 buf + n - 1で指された最後のバイトも、まったく同じページiに配置する必要があります。
たとえば、syscallがreadのように呼び出された場合:
DE = 0x4000およびBC = 0x1000 、パラメーターは正しいです。DE DE指されたバッファーが1ページ( 0x4000から0x7FFF )に適合するためDE = 0x4000 DE BC = 0x4000 、パラメーター0x4000正しいです。 0x7FFFDE = 0x7FFFおよびBC = 0x2では、deが指すバッファーは1ページとページ2の間にあるため、パラメーターは正しくありません。exec Zeal 8ビットOSはモノタスクオペレーティングシステムですが、いくつかのプログラムをメモリに実行して維持できます。プログラムaがexec syscallのおかげでプログラムbを実行する場合、 EXEC_OVERRIDE_PROGRAMまたはEXEC_PRESERVE_PROGRAMのいずれかにできるmodeパラメーターを提供するものとします。
EXEC_OVERRIDE_PROGRAM :このオプションは、プログラムAを実行する必要がなくなる必要がないことをカーネルに伝えているため、プログラムBはプログラムAと同じアドレススペースにロードされます。つまり、プログラムBはプログラムAと同じRAMページ内にロードされ、上書きされます。EXEC_PRESERVE_PROGRAM :このオプションは、プログラムBが実行を終了し、 exit syscallを呼び出すまで、プログラムAをRAMに保持する必要があることをカーネルに指示します。そのために、カーネルは3つの新しいメモリページ( 16KB * 3 = 48KB )を割り当てます。このプログラムBを格納すると、プログラムBが終了すると、カーネルはプログラムBの以前に割り当てられたページをフリーし、プログラムAのメモリページを再マップし、プログラムAに手を返します。実行ツリーの深さは、オプションCONFIG_KERNEL_MAX_NESTED_PROGRAMSのおかげで、 menuconfigで定義されています。一度にRAMに保存できるプログラムの最大数を表します。たとえば、深さが3の場合、プログラムAはプログラムBを呼び出すことができ、プログラムBはプログラムCを呼び出すことができますが、プログラムCは他のプログラムを呼び出すことはできません。ただし、プログラムがEXEC_OVERRIDE_PROGRAMを使用してexecを呼び出す場合、ロードする新しいプログラムが現在のプログラムをオーバーライドするため、深さは増加しません。そのため、前の例を取り戻した場合、プログラムCは、 EXEC_OVERRIDE_PROGRAMモードでexec syscallを呼び出す場合にのみプログラムを呼び出すことができます。
サブプログラムを実行するときは、オープンしたデバイステーブル(ファイル、ディレクトリ、ドライバーを含む)を実行する場合、現在のディレクトリ、およびCPUレジスタが共有されます。
これは、プログラムAが記述子3を使用してファイルを開くと、プログラムBがこのインデックスを継承し、したがって、その記述子を読み取り、書き込み、または閉じることさえできることを意味します。相互に、Bがファイル、ディレクトリ、またはドライバーを閉じることなく出口を開いた場合、プログラムAもアクセスできます。そのため、従うべき一般的なガイドラインは、終了する前に、プログラムが開いた記述子を常に閉じる必要があるということです。開いたデバイスと現在のディレクトリのテーブルがリセットされる唯一の瞬間は、初期プログラム(前の例のプログラムA)が終了する場合です。その場合、カーネルは、開いたデバイステーブルのすべての記述子を閉じ、標準の入力と出力を再開し、初期プログラムをリロードします。
これはまた、アセンブリプログラムでexec syscallを呼び出す場合、成功については、HLを除くすべてのレジスタを、サブプログラムで使用するため、変更されると見なされる必要があることを意味します。したがって、 AF 、 BC 、 DE 、 IX 、またはIYを保存したい場合は、 execを呼び出す前にスタックにプッシュする必要があります。
syscallsはすべて、アセンブリとCの両方に提供されるヘッダーファイルに文書化されています。これらのヘッダーファイルはkernel_headers/ディレクトリにあります。詳細については、そのreadmeファイルを確認してください。
ドライバーは、以下を含む構造で構成されています。
SER0 、 SER1 、 I2C0など。非ASCII文字は許可されていますが、アドバイスは許可されていません。initルーチンのアドレス。readルーチンのアドレス。パラメーターと返信アドレスは、Syscallテーブルと同じです。writeルーチンのアドレス。openルーチンのアドレス。closeルーチンのアドレス。seekルーチンのアドレス。ioctlルーチンのアドレス。deinitルーチンのアドレス。簡単なドライバー登録の例は次のとおりです。
my_driver0_init:
; Register itself to the VFS
; Do something
xor a ; Success
ret
my_driver0_read:
; Do something
ret
my_driver0_write :
; Do something
ret
my_driver0_open :
; Do something
ret
my_driver0_close :
; Do something
ret
my_driver0_seek :
; Do something
ret
my_driver0_ioctl :
; Do something
ret
my_driver0_deinit :
; Do something
ret
SECTION DRV_VECTORS
DEFB "DRV0"
DEFW my_driver0_init
DEFW my_driver0_read
DEFW my_driver0_write
DEFW my_driver0_open
DEFW my_driver0_close
DEFW my_driver0_seek
DEFW my_driver0_ioctl
DEFW my_driver0_deinitドライバーの登録は、 DRV_VECTORSというセクション内にこの情報(構造)を配置することになります。ドライバーの依存関係はコンパイル時に解決されるため、順序は非常に重要です。たとえば、ドライバーAドライバーBに依存する場合、 Bの構造は、 DRV_VECTORSセクションのAの前に配置する必要があります。
ブートでは、 driverコンポーネントがDRV_VECTORSセクション全体を閲覧し、 initルーチンを呼び出すことでドライバーを1つずつ初期化します。このルーチンがERR_SUCCESS返すと、ドライバーが登録され、ユーザープログラムが開くこと、読み取り、書き込み、ioctlなどを開くことができます...
ドライバーはプログラムに隠すことができます。これは、カーネルのファイルシステムレイヤーによってのみアクセスする必要があるディスクドライバーに便利です。そのためには、 initルーチンはERR_DRIVER_HIDDEN返す必要があります。
アプリケーションとハードウェアの間の通信はすべて上記のsyscallsを介して行われるため、ユーザーアプリケーションとカーネルの間にレイヤーが必要であるため、ドライバーまたはファイルシステムを呼び出す必要があるかどうかを判断します。このようなアーキテクチャの階層を表示する前に、ディスクとドライバーについて話しましょう。
さまざまなレイヤーがこのように見ることができます:
フローチャートTD;
アプリ(ユーザープログラム)
VFS(仮想ファイルシステム)
DSK(ディスクモジュール)
DRV(ドライバーの実装:ビデオ、キーボード、シリアルなど...)
FS(ファイルシステム)
Sysdis(Syscall Dispatcher)
HW(ハードウェア)
時間(時間と日付モジュール)
MEM(メモリモジュール)
ローダー(ローダーモジュール)
App -Syscall/rst 8-> sysdis;
sysdis -getDate/time-> time;
sysdis - マウント - > dsk;
sysdis-> vfs;
sysdis - map-> mem;
sysdis- exec/exit-> loader;
VFS-> DSK&DRV;
dsk <-> fs;
fs-> drv;
DRV-> HW;
熱心な8ビットOSは、一度に最大26個のディスクをサポートします。ディスクは、AからZまでの文字で示されます。システム内のディスクをどこに取り付けるかを決定するのはディスクドライバーの責任です。
最初のドライブAは、システムが好みや構成を探すものであるため、特別です。
アプリケーションでは、 path次のとおりです。
my_dir2/file1.txt/my_dir1/my_dir2/file1.txtB:/your_dir1/your_dir2/file2.txt OSは完全にロム可能で、起動するファイルシステムまたはディスクは必要ありませんが、 init.binと呼ばれる初期プログラムをロードしようとするとすぐに、デフォルトのディスクをチェックしてそのファイルを要求します。したがって、最も基本的なストレージでさえ、ファイルシステム、または同様のものが必要です。
すでに実装されている最初の「ファイルシステム」は、「RawTable」と呼ばれます。その名前が述べているように、それはディレクトリではなく、ストレージデバイスでディレクトリではなくファイルの連続を順調に表しています。ファイル名のサイズの制限は、オプションを含む16文字と同じです.および拡張機能。 Cコードと比較したい場合は、各ファイルを定義する構造の配列で、その後に同じ順序でファイルのコンテンツが続きます。 Romdisk Packerソースコードは、 packer/このレポのルートで利用できます。詳細については、そのreadmeを確認してください。
実装されている2番目のファイルシステムは、Zealfsという名前です。その主な目的は、8kbから64kbまでの非常に小さな貯蔵庫に埋め込むことです。読みやすく、書き込み可能で、ファイルとディレクトリをサポートしています。専用のリポジトリの詳細については、詳細を確認してください。
熱心な8ビットOSで有益な3番目のファイルシステムはFAT16です。非常に有名で、既にすべてのデスクトップオペレーティングシステムでサポートされており、CompactFlashやSDカードでも使用できますが、これはほとんど必須です。まだ実装されていませんが、計画されています。 FAT16は小さなストレージに適合していないため、完璧ではありません。これがZealFSが必要な理由です。
熱の8ビットOSは、カーネルとターゲットコードの2つの主要なコンポーネントに基づいています。カーネルだけでは何もしません。ターゲットは、ドライバー、カーネル内で使用されるMMUマクロとリンカースクリプトを実装する必要があります。リンカースクリプトは非常にシンプルで、 z80asmアセンブラーによる最終バイナリにリンクする必要がある順序でセクションをリストします。
カーネルは現在、次のセクションを使用しています。これは、任意のリンカースクリプトに含める必要があります。
RST_VECTORS :リセットベクトルが含まれていますSYSCALL_TABLE :syscall iルーチンアドレスがインデックスiに保存されるテーブルが含まれています。SYSCALL_ROUTINES :リセットベクトルから呼び出されるsyscallディスパッチャーが含まれていますKERNEL_TEXT :カーネルコードが含まれていますKERNEL_STRLIB :カーネルで使用される文字列関連のルーチンが含まれていますKERNEL_DRV_VECTORS :初期化するドライバーの配列を表します。詳細については、ドライバーセクションを確認してください。KERNEL_BSS :カーネルコードで使用されるデータが含まれている必要があります。DRIVER_BSS :カーネルで直接使用されていないため、ドライバーで定義および使用するものとします。カーネルはブートで0Sに設定します、それは2バイトより大きくなければなりません前述のように、熱心な8ビットコンピューターのサポートは依然として部分的ですが、コマンドラインプログラムを実行するには十分です。 romdiskはカーネルがビルドする前に作成されます。これは、 target/zeal8bit/unit.mkで指定されたscript.shで行われます。
そのスクリプトは、 init.binプログラムをコンパイルし、コンパイルされたOSバイナリに連結するromdisk内に埋め込みます。最終的なバイナリは、NORフラッシュに直接フラッシュできます。
まだ順番に実装する必要があるもの:
TRS-80モデルIコンピューターへのクイックポートが作成され、MMUを持たないターゲットに熱心な8ビットOSをポートおよび構成する方法を示しました。
このポートは、画面にブートバナーを表示するだけで、それ以上のものはありません。そのためには、テキストモード用のビデオドライバーのみが実装されます。
より興味深いポートを持つには、次の機能を実装する必要があります。
init.bin /romdiskを保存するディスクは読み取り専用であるため、ROMに保存できますShawn Sijnstraによって書かれ、維持されているEZ80搭載のアゴンライトへのポート。アゴン固有のバグ/リクエストには、そのフォークを自由に使用してください。これは、非MMUカーネルを使用し、熱心な8ビットコンピューターの実装がサポートする機能のほとんどを実装しています。
このポートには、バイナリを正しい場所から保存および実行するためのローダーが必要です。バイナリはOsbootzで、こちらから入手できます。
ポートは端子モードを使用してキーボードI/Oを簡素化することに注意してください。これは、日付関数が利用できないことも意味します。
その他の注目すべき機能:
8ビットOS MMUバージョンを別のマシンにポートするには、MMUバージョンのZ80の64kbアドレススペースを4ページの16kbに分割するメモリマッパーが最初にあることを確認してください。
NO-MMUのZeal 8ビットOSをポートするには、仮想アドレス0x4000以上からRAMが利用可能であることを確認してください。 ROMを持つ最も理想的なケースは、OSの最初の16kbと、ユーザープログラムとカーネルRAMの残りの48kbの最初の16kbです。
ターゲットが互換性がある場合は、指示に従ってください。
Kconfigファイルを開き、 config TARGETおよびconfig COMPILATION_TARGETオプションへのエントリを追加します。すでに存在するものを例として取ります。target/ターゲットの新しいディレクトリを作成すると、名前は新しいconfig TARGETオプションで指定されているものと同じでなければなりません。unit.mkファイルを作成します。これは、アセンブルするすべてのソースファイルまたは含めるファイルを含むファイルです。unit.mkファイルを入力してください。これを行うには、次のようmake変数を入力できます。SRCS :アセンブルするファイルのリスト。通常、これらはドライバーです(必須)INCLUDES :含めることができるヘッダーファイルを含むディレクトリPRECMD :カーネルが構築を開始する前に実行されるバッシュコマンドPOSTCMD :カーネルが建物を仕上げた後に実行されるバッシュコマンドmmu_h.asmファイルを作成します。ファイルtarget/zeal8bit/include/mmu_h.asmを確認して、どのように見えるかを確認してください。init.binファイルを含むルーチンzos_disks_mountを使用して、ディスクをマウントする少なくとも1つのドライバーを必ず用意してください。zos_vfs_set_stdoutを使用して、少なくとも1人のドライバーを標準アウト(STDOUT)として登録するようにしてください。完全な変更ログについては、リリースページを確認してください。
貢献は大歓迎です!あなたが見たり遭遇したりするかもしれないバグを修正したり、重要だと思われる機能を実装したりしてください。
貢献する:
(*)良いコミットメッセージは次のとおりです。
Module: add/fix/remove a from b
Explanation on what/how/why
例えば:
Disks: implement a get_default_disk routine
It is now possible to retrieve the default disk of the system.
Apache 2.0ライセンスの下で配布。詳細については、 LICENSEファイルを参照してください。
個人的および商業用に使用するために自由に使用できます。各ファイルに存在するボイラープレートを削除してはなりません。
提案やリクエストについては、[at] zeal8bit [dot] comに連絡して連絡できます。
機能リクエストの場合、問題やプルリクエストを開くこともできます。
それにもかかわらず、それらは不揮発性と見なされないものとします。言い換えれば、割り込みハンドラーは、代替レジスタ内で書いたデータが次に呼び出されるまで保持されると仮定してはなりません。 ↩