*如果用户程序的参数指向第3页(最后一页)中的一部分内存,则存在冲突,因为内核将始终在SYSCALL期间在完全相同的页面内映射其RAM页面。因此,它将将用户的第3页重新映射到第2页(第三页)中以访问程序的参数。当然,如果参数是指示符,则将对其进行修改以指向新的虚拟地址(换句话说,指针将由16KB减去,以使其指向第2页)。
为了能够将热情8位OS移植到没有MMU/内存映射器的基于Z80的计算机,如上所述,内核具有新模式,可以通过menuconfig :noto-noth-mmu选择。
在此模式下,OS代码仍预计将在内存的第一个16KB中映射,从0x0000到0x3FFF ,其余的预计将为RAM。
理想情况下,应从0x4000开始映射48KB的RAM,并将上升到0xFFFF ,但实际上,可以配置内核的期望少于此。为此,必须适当配置menuconfig中的两个条目:
KERNEL_STACK_ADDR :这标志着内核RAM区域的末端,顾名思义,将是内核堆栈的底部。KERNEL_RAM_START :这标志着堆栈,内核和驱动程序使用的所有变量和驱动程序的所有变量都将存储。当然,它必须足够大才能存储所有这些数据。有关信息,当前内核BSS部分大小约为1KB。堆栈深度取决于目标驱动程序的实现。只要没有(大)缓冲区存储在其中,为堆栈分配1KB应该足够长。总体将至少3KB分配给内核RAM应该是安全的,未来的。总而言之,这是显示内存使用段的图:
关于用户程序,堆栈地址将始终设置为KERNEL_RAM_START - 1 。它还对应于其在其可用地址空间中可用的最后一个字节的地址。这意味着程序可以通过执行SP - 0x4000来确定可用RAM的大小,该sp -0x4000在汇编中给出:
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 | 应用程序(在操作系统中未使用) |
这意味着操作系统不会更改IX和IY寄存器,因此可以在应用程序中自由使用。
备用寄存器(名称后跟' )只能在中断处理程序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开始执行并加载其加载。该入口点虚拟地址可通过menuconfig配置,并具有选项KERNEL_INIT_EXECUTABLE_ADDR ,但请记住,现有程序在运行时无法重新定位,因此现有程序将不再工作。
如下所述, exec SYSCALL采用两个参数:要执行的二进制文件名和一个参数。
该参数必须是一个无效的终止字符串,将被复制并传输到二进制文件以通过DE和BC寄存器执行:
DE包含字符串的地址。该字符串将被复制到新程序的内存空间,通常在堆栈顶部。BC包含该字符串的长度(因此,不包括空字节)。如果BC为0,则必须由用户程序丢弃DE 。 该系统依靠SYSCALL来执行用户程序和内核之间的请求。因此,这将是在硬件上执行操作的方法。可能的操作在下表中列出。
| num | 姓名 | 参数。 1 | 参数。 2 | 参数。 3 |
|---|---|---|---|---|
| 0 | 读 | U8 DEV | U16 buf | U16尺寸 |
| 1 | 写 | U8 DEV | U16 buf | U16尺寸 |
| 2 | 打开 | U16名称 | U8标志 | |
| 3 | 关闭 | U8 DEV | ||
| 4 | DSTAT | U8 DEV | U16 DST | |
| 5 | 统计 | U16名称 | U16 DST | |
| 6 | 寻找 | U8 DEV | U32偏移 | u8从那里 |
| 7 | ioctl | U8 DEV | U8 CMD | U16 arg |
| 8 | Mkdir | U16路径 | ||
| 9 | Chdir | U16路径 | ||
| 10 | Curdir | U16路径 | ||
| 11 | OPENDIR | U16路径 | ||
| 12 | readdir | U8 DEV | U16 DST | |
| 13 | RM | U16路径 | ||
| 14 | 山 | U8 DEV | U8字母 | U8 fs |
| 15 | 出口 | U8代码 | ||
| 16 | exec | U16名称 | U16 argv | |
| 17 | DUP | U8 DEV | U8 NDEV | |
| 18 | msleep | U16持续时间 | ||
| 19 | Settime | U8 ID | U16时间 | |
| 20 | GetTime | U8 ID | U16时间 | |
| 21 | 设定 | U16日期 | ||
| 22 | GetDate | U16日期 | ||
| 23 | 地图 | U16 DST | U24 SRC | |
| 24 | 交换 | U8 DEV | U8 NDEV |
请检查以下部分以获取有关这些呼叫及其参数的更多信息。
注意:某些syscall可能会毫不费力。例如,在不支持目录的计算机上,可能会省略与目录相关的SYSCALL。
为了执行SYSCALL,必须将操作编号存储在寄存器L中,必须按照以下规则存储参数:
| API中的参数名称 | Z80注册 |
|---|---|
| U8 DEV | H |
| U8 NDEV | E |
| U8标志 | H |
| U8 CMD | C |
| U8字母 | D |
| U8代码 | H |
| U8 fs | E |
| U8 ID | H |
| u8从那里 | 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中。该值的含义是每个调用的特定的,请检查有关例程的文档以获取更多信息。
为了最大化用户程序与Zeal 8位OS内核的兼容性,无论是以MMU还是No-MMU模式编译的内核,SYSCALLS参数约束都相同:
任何传递给Syscall的缓冲区均不得穿越16KB虚拟页面
换句话说,如果尺寸n的buf位于虚拟页i中,其最后一个字节,由buf + n - 1指向,也必须位于完全相同的i上。
例如,如果read syscall,请使用:
DE = 0x4000和BC = 0x1000 ,参数是正确的,因为DE指向第1页的缓冲区(从0x4000到0x7FFF )DE = 0x4000和BC = 0x4000 ,参数是正确的,因为DE指向第1页的缓冲区(从0x4000到0x7FFF )DE = 0x7FFF和BC = 0x2 ,参数不正确,因为DE指向的缓冲区在第1页和第2页之间。exec即使热情8位OS是一个单一任务操作系统,它也可以执行并保留多个程序的内存。当程序A执行A程序B时,要归功于exec SYSCALL,它将提供一个mode参数,可以是EXEC_OVERRIDE_PROGRAM或EXEC_PRESERVE_PROGRAM :
EXEC_OVERRIDE_PROGRAM :此选项告诉内核不再需要执行程序A,因此程序B将加载到与程序A相同的地址空间中。EXEC_PRESERVE_PROGRAM :此选项告诉内核需要将程序保存在RAM中,直到程序B完成执行并调用exit syscall。为此,内核将分配3个新的内存页面( 16KB * 3 = 48KB ),其中它存储了新加载的程序B。BertonB退出后,内核释放了程序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之前将它们推入堆栈上。
SYSCALL都记录在为汇编和C提供的标头文件中,您将在kernel_headers/ Directory中找到这些标头文件,请检查其doodme文件以获取更多信息。
驾驶员由包含的结构组成:
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的结构A DRV_VECTORS部分中。
在启动时, driver组件将浏览整个DRV_VECTORS部分,并通过调用其init例程来一个一个一个一个初始化驱动程序。如果此例程返回ERR_SUCCESS ,则将注册驱动程序,用户程序可以打开,读,写,ioctl等...
驱动程序可以隐藏在程序中,这对于仅由内核文件系统层访问的磁盘驱动程序来说很方便。为此, init例程应返回ERR_DRIVER_HIDDEN 。
由于应用程序和硬件之间的通信都是通过上述SYSCALL完成的,因此我们需要在用户应用程序和内核之间进行一层,这将确定我们是否需要调用驱动程序或文件系统。在展示此类建筑的层次结构之前,让我们谈谈磁盘和驱动程序。
可以这样看到不同的层:
流程图TD;
应用程序(用户程序)
VFS(虚拟文件系统)
DSK(磁盘模块)
DRV(驱动程序实现:视频,键盘,串行等...)
FS(文件系统)
Sysdis(Syscall调度员)
HW(硬件)
时间(时间和日期模块)
mem(内存模块)
加载器(装载机模块)
应用 - syscall/rst 8-> sysdis;
sysdis - getDate/time->时间;
sysdis - 安装 - > dsk;
sysdis-> vfs;
sysdis - map-> mem;
sysdis- ex/exit-> loader;
vfs-> dsk&drv;
DSK <-> fs;
fs-> drv;
drv-> hw;
热情8位操作系统最多可立即支撑26个磁盘。磁盘用字母(从A到Z)表示。决定将磁盘安装在系统中的位置是磁盘驾驶员的责任。
第一个驱动器A很特别,因为它是系统将寻找首选项或配置的驱动器。
在应用程序中, path可能是:
my_dir2/file1.txt/my_dir1/my_dir2/file1.txt file1.txtB:/your_dir1/your_dir2/file2.txt 即使操作系统完全可恢复,并且不需要任何文件系统或磁盘即可启动,但它将在尝试加载初始程序(默认情况下称为init.bin时,它将检查默认磁盘并请求该文件。因此,即使是最基本的存储也需要一个文件系统或类似的内容。
已经实现的第一个“文件系统”称为“原始table”。顾名思义,它代表文件的连续性,而不是目录,在存储设备中,没有特别的顺序。文件名大小限制与内核的限制相同:16个字符,包括可选.和扩展。如果要将其与C代码进行比较,它将是定义每个文件的结构数组,然后按照相同的顺序进行文件内容。 romdisk包装器源代码可在packer/此存储库的根部提供。检查其读书文件以获取有关它的更多信息。
也实现的第二个文件系统被命名为Zealfs。它的主要目的是将其嵌入非常小的储藏室中,从8KB到64KB。它是可读和可写的,它支持文件和目录。在专用存储库中有关它的更多信息。
第三个在8位OS上使用的文件系统是FAT16。非常著名的,几乎所有桌面操作系统都支持,可在CompactFlash甚至SD卡上使用,这几乎是必备的。它尚未实施,但已计划。 FAT16并不完美,因为它不适合小型存储,这就是为什么需要Zealfs的原因。
热情8位OS基于两个主要组件:内核和目标代码。单独的内核无济于事。目标需要实现驱动程序,内核内使用的一些MMU宏和链接脚本。链接器脚本相当简单,它以z80asm汇编器中必须在最终二进制中链接的顺序列出了这些部分。
内核当前使用以下各节,必须包含在任何链接脚本中:
RST_VECTORS :包含重置向量SYSCALL_TABLE :包含一个表Syscall i例程地址在索引i上的表格,必须在256上对齐SYSCALL_ROUTINES :包含从重置向量调用的Syscall调度程序KERNEL_TEXT :包含内核代码KERNEL_STRLIB :包含内核中使用的与字符串相关的例程KERNEL_DRV_VECTORS :代表一个驱动程序来初始化的驱动程序,请检查驱动程序部分以获取更多详细信息。KERNEL_BSS :包含内核代码使用的数据,必须在RAM中DRIVER_BSS :不直接由内核使用,应在驱动程序中定义并使用。内核将其设置为启动时的0,必须大于2个字节如前所述,热情的8位计算机支持仍然是部分的,但足以使命令行计划运行。 romdisk是在内核构建之前创建的,这是在target/zeal8bit/unit.mk中指定的script.sh中完成的。
该脚本将编译init.bin程序,并将其嵌入将串联到编译的OS二进制中的romdisk中。最终的二进制可以直接闪烁到Nor Flash。
仍然需要以特殊顺序实施的是:
已经建立了一个快速到TRS-80 Model-I计算机的端口,以显示如何对没有MMU的目标进行端口和配置Zeal 8位OS。
此端口很简单,因为它只是在屏幕上显示了引导横幅,仅此而已。为此,仅实现了用于文本模式的视频驱动程序。
要拥有一个更有趣的端口,需要实现以下功能:
init.bin /romdisk的磁盘,只能读取,因此可以存储在ROM上Shawn Sijnstra撰写和维护的EZ80驱动Agon Light的港口。随意将叉子用于特定的错误/请求。这使用了非MMU内核,并实现了Zeal 8位计算机实现支持的大多数功能。
此端口需要一个从正确位置存储和执行二进制的装载程序。二进制是Osbootz,可在此处提供。
请注意,端口使用终端模式来简化键盘I/O。这也意味着日期函数不可用。
其他值得注意的功能:
要向另一台计算机端口的热情8位OS MMU版本,请确保首先有一个内存映射器,将Z80的64KB地址空间划分为MMU版本的16KB的4页。
要端口No-Mmu Zeal 8位OS,请确保可以从虚拟地址0x4000及更高版本获得RAM。最理想的案例是ROM是用户程序和内核RAM剩余48KB中OS和RAM的第一个16KB。
如果您的目标兼容,请按照说明:
Kconfig文件,然后将条目添加到config TARGET和config COMPILATION_TARGET选项中。以已经存在的例子为例。target/目标中创建一个新目录,名称必须与新config TARGET选项中指定的名称相同。unit.mk文件。这是包含所有要组装的源文件或要包含的文件的文件。unit.mk文件,为此,您可以填充以下make :SRCS :要组装的文件列表。通常,这些是驱动程序(强制性)INCLUDES :包含可以包括的标头文件的目录PRECMD :在内核开始构建之前要执行的bash命令POSTCMD :bash命令将在内核完成后执行mmu_h.asm文件,该文件将包含在内核中以配置和使用MMU。检查文件target/zeal8bit/include/mmu_h.asm以查看其外观。zos_disks_mount ,该驱动程序包含一个init.bin文件,由内核在启动时加载和执行。zos_vfs_set_stdout 。有关完整的ChangElog,请检查发布页面。
欢迎捐款!请随意修复您可能看到或遇到的任何错误,或实现您发现重要的任何功能。
贡献:
(*)一个很好的提交信息如下:
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文件。
您可以自由地将其用于个人和商业用途,因此不能删除每个文件中存在的样板。
对于任何建议或请求,您可以在Contact [at] zeal8bit [dot] com上与我联系
对于功能请求,您也可以打开问题或拉动请求。
尽管如此,它们仍不被视为非易失性。换句话说,中断处理程序不得假设其在任何替代寄存器中编写的数据都将保留在下次调用之前。 ↩