Aros/开发者/文档/资源/内核
kernel.resource 包含 AROS 微内核。它是最低级别的组件,负责处理 CPU 和主板。对于托管端口,kernel.resource 包含一个虚拟机。
kernel.resource 目前提供以下功能
- 任务调度
- KrnSetScheduler()
- KrnGetScheduler()
- KrnCause()
- KrnDispatch()
- KrnSwitch()
- KrnSchedule()
- 中断管理
- KrnAddIRQHandler()
- KrnRemIRQHandler()
- KrnCli()
- KrnSti()
- CPU 管理
- KrnIsSuper()
- KrnAddExceptionHandler()
- KrnRemExceptionHandler()
- KrnCreateContext()
- KrnDeleteContext()
- MMU 管理
- KrnMapGlobal()
- KrnUnmapGlobal()
- KrnVirtualToPhysical()
- KrnAllocPages()
- KrnFreePages()
- 调试
- KrnBug()
- KrnPutChar()
- KrnMayGetChar()
- KrnRegisterModule()
- KrnUnregisterModule()
- KrnDecodeLocation()
- 系统信息和控制
- KrnGetBootInfo()
- KrnGetSystemAttr()
- KrnSetSystemAttr()
大多数这些函数旨在用于系统自身的用途,而不是用户软件。它们由 AROS 的其他各种组件调用,并为其提供硬件抽象层。所有函数都描述在自动文档中。下面将给出更详细的说明,其中讨论了实现细节。
目前,kernel.resource 实现了原始的 AmigaOS(tm) 兼容的优先级循环调度器。调度器包含三个在 supervisor 特权级别运行的函数
- core_Schedule() - 检查就绪任务列表,并通知当前任务是否需要切换
- core_Switch() - 从当前任务切换出去
- core_Dispatch() - 选择一个新的任务执行
通常,它们在以指定顺序处理 CPU 中断后执行。也可以通过三个相应的 API 入口点显式调用调度器:KrnSchedule()、KrnSwitch() 和 KrnDispatch()。
KrnSchedule() 是调度器的主要手动入口点。当 AROS 任务认为应该将 CPU 时间让给其他任务时,它会调用此函数。该函数会导致 core_Schedule() 在 supervisor 级别运行。如果 core_Schedule() 发现当前任务可以继续运行,则返回 FALSE。否则,它将任务放入就绪任务列表 (SysBase->TaskReady) 并返回 TRUE。这意味着 core_Switch() 和 core_Dispatch() 应该接下来进行。
KrnSwitch() 和 KrnDispatch() 是进入调度器的中间点入口。KrnSwitch() 在当前任务已放入 exec.library 列表之一,并且需要立即停止时被调用。目前,这在 exec.library/Wait() 内部完成,该函数将任务放入 SysBase->TaskWait 列表并将任务设置为等待状态。KrnDispatch() 由 exec.library/RemTask() 调用,以便在删除当前任务时完成其执行。
core_Switch() 和 core_Dispatch() 实际上执行任务切换。core_Switch() 保存当前任务的状态,core_Dispatch() 选择一个新的任务并恢复其状态。这些例程实际上是 CPU 相关的,因此它们永远不会被直接使用。相反,应在 CPU 特定代码中实现名为 cpu_Switch() 和 cpu_Dispatch() 的 CPU 特定包装器。
cpu_Switch() 很简单。它将任务的上下文(CPU 寄存器)保存到内部 ETask 结构中,然后跳转到 core_Switch()。core_Switch() 只是通用部分,它包括保存 SysBase->IDNestCnt 以及根据需要调用任务的 tc_Switch 通知向量。
cpu_Dispatch() 是一个棘手的地方。除了恢复 CPU 上下文外,还应处理 CPU 空闲状态和任务异常。当 core_Dispatch() 返回 NULL 时,应进入空闲状态。这意味着没有剩余的就绪运行任务。要么所有现有的任务都在等待某些信号,要么根本没有更多任务。在空闲状态下,唯一要做的就是处理中断。
以下是一个带有典型空闲循环实现的 cpu_Dispatch() 示例(针对 x86-64 CPU,改编自旧代码)
void cpu_Dispatch(regs_t *regs) { struct Task *task; struct AROSCPUContext *ctx; IPTR sse_ctx; while (!(task = core_Dispatch())) { /* * We enter here every time when there are no ready tasks. * All accounting (ExecBase state maintenance and IdleCount increment) * is already done by core_Dispatch() */ /* Explicitly enable interrupts and halt the CPU */ __asm__ __volatile__("sti; hlt; cli"); /* * At this point interrupt(s) have been already processed, but * the scheduler was not called since interrupted context was * a supervisor one (see description of core_ExitInterrupt() below). * Interrupt handlers could possibly call exec.library/Cause() in * order to queue some software interrupts. In AmigaOS(tm) (and in AROS) * software interrupts have the lowest priority and are processed after * all hardware ones. So, we need to pick up queued software interrupts * here. Software interrupts are described below in details. */ if (SysBase->SysFlags & SFF_SoftInt) core_Cause(INTB_SOFTINT); /* Repeat everything over and over again until some interrupt wakes up some task */ } /* We've got a new task, restore CPU context now */ ctx = GetIntETask(task)->iet_Context; /* * TODO: This example misses task exception handling. * More about task exceptions see below. */ /* Restore CPU registers */ bcopy(ctx, regs, sizeof(regs_t)); /* Restore FPU and SSE */ sse_ctx = ((IPTR)ctx + sizeof(regs_t) + 15) & ~15; asm volatile("fxrstor (%0)"::"D"(sse_ctx)); /* * Jump to the new context. core_LeaveInterrupt() is assemblef function which * sets stack pointer to the specified context, pops all registers and returns * from the interrupt. */ core_LeaveInterrupt(regs); /* core_LeaveInterrupt() does not return here */ }
请注意,我们对寄存器帧使用两种类型:regs_t 和 struct AROSCPUContext。有什么区别?regs_t 是在中断进入时保存在堆栈上的内容。在本示例中,regs_t 只包含 CPU 寄存器。在中断中,我们通常不会处理 SSE 或 MMX,因此没有理由在那里处理它们。但是,struct AROSCPUContext 更完整,它包括 SSE 缓冲区,因为不同的 AROS 任务可能会使用浮点或向量数学。
托管端口也一样。在那里,regs_t 将是主机操作系统提供的异常帧,而 struct AROSCPUContext 是我们在 AROS 中存储的内容。
将来,struct AROSCPUContext 将根据 CPU 系列进行统一。这将使从外部软件(如调试器)操作 CPU 上下文成为可能。目前它仍然是一个私有定义。
上面描述的三个 API 入口点(KrnSchedule()、KrnSwitch() 和 KrnDispatch())是内部的。唯一相对安全的调用函数是 KrnSchedule(),但是它被 exec.library/Reschedule() 包装成更友好的 exec 形式。KrnSwitch() 和 KrnDispatch() 纯粹是内部的,从应用程序内部调用它们不是一个好主意。如果调用其中一个,当前任务将简单地丢失,并且不会获得控制权。
还要注意,exec.library/Reschedule() 可能无法跨 Amiga(tm) 系列的不同系统移植。最初它作为私有函数出现在 AmigaOS(tm) 中。不能保证它会在 MorphOS 和/或 AmigaOS(tm) v4 中保留下来。
简而言之:这些函数是内部函数,请勿使用它们。操作系统本身拥有良好的任务调度程序,您无需教它如何处理您的任务。当然,在 AROS 特定应用程序中,如果确实要放弃 CPU 时间,您可以调用 Reschedule(FindTask(NULL))。但仅此而已。
所描述的调度序列可以运行
- 从软件中断(系统调用)中明确运行
- 在中断处理结束时。
(1) 情况很简单。以下是一个典型的系统调用处理程序实现
switch(num) { case SC_SCHEDULE: /* Call scheduler. Simply return if it doesn't want to change anything */ if (!core_Schedule()) break; /* core_Schedule() returned TRUE, fallthrough here */ case SC_SWITCH: /* The task is already in some list. Switch to another one no matter what */ cpu_Switch(regs); /* Fallthrough again, SysBase->ThisTask is invalid here */ case SC_DISPATCH: /* Select the new task to run */ cpu_Dispatch(regs); /* Done, so break here */ break; case SC_CAUSE: /* * A software interrupt is requested by exec via KrnCause(). * Call the scheduler no matter what. Explained below. */ if (regs->ds != KERNEL_DS) core_ExitInterrupt(regs); break; /* Other syscalls can be handled here if needed */ }
(2) 稍微复杂一些。首先,中断可以(通常是)嵌套的(优先级更高的),您应该只调用一次调度程序。其次,您需要确保任务切换实际上是被 exec.library 允许的。
为此,kernel_intr.c 中有一个实用函数 core_ExitInterrupt()。它执行几乎所有必要的检查,并且仅在实际允许时才运行调度序列。此外,它还处理待处理的 exec 软件中断(如果有)。
该函数仅缺少与 CPU 相关的中断嵌套检查。您应该使用任何方法来检查从该中断实际上是否正在返回用户模式。例如,x86-64 内核可以检查异常帧的 DS 寄存器,以确定它将返回什么模式。
if (regs->ds != KERNEL_DS) core_ExitInterrupt(regs); core_LeaveInterrupt(regs);
exec.library 中的软件中断处理由 SysBase 中的 INTB_SOFTINT 向量完成。kernel.resource 负责调用它。软件中断可能会改变不同任务的状态,因此在处理完它们之后需要重新调度。这就是为什么在处理 SC_CAUSE 时我们需要执行完整的调度序列。
exec 软件中断与 CPU 软件中断(系统调用)不同。请明确区分它们,不要被类似的名称混淆。为了避免以后的混淆,让我们将这些中断称为 SoftInts。排队一个 SoftInt 意味着只是将一个节点添加到内部列表中。然后在 SysBase->SysFlags 中设置 SFF_SoftInt 位,并调用 KrnCause()。
如果在 SoftInt 排队时中断被禁用,exec.library 可以延迟调用 KrnCause()。KrnCause() 仅在中断启用时才会被调用。
KrnCause() 反过来会在 CPU 上调用一个软件中断。此中断应以与实际硬件中断相同的方式处理。如果合适,需要调用 core_ExitInterrupt()。
实际的 SoftInt 处理是在 core_ExitInterrupt() 函数内部完成的,因为 exec 软件中断需要在所有实际硬件中断都处理完之后运行(如上所述,core_ExitInterrupt() 仅在退出所有中断时调用一次)。它查看 SFF_SoftInt 标志,如果设置了该标志,则调用 exec 的 SoftInt 向量。其余部分(包括标志重置)预计由 exec.library 完成。
请注意,在 cpu_Dispatch() 中的空闲循环内,需要对 SFF_SoftInt 标志进行显式检查,并在中断到达后进行检查。这是因为空闲循环在超级用户模式下运行,因此在中断处理完后不会调用 core_ExitInterrupt()(根据上面的要求)。
exec.library 中的任务异常提供了一种异步响应某些信号的方法。您无需显式等待异常。当信号到达时,您的任务将被中断并跳转到异常处理程序。处理程序返回后,任务将恢复其正常执行,就像什么也没发生一样。
再次,请不要将任务异常与 CPU 异常混淆。它们是完全不同的东西。exec 中的 CPU 异常称为陷阱。任务异常在用户模式下运行,调度和中断已启用,就像正常代码一样。
任务异常需要 kernel.resource 端的 cpu_Dispatch() 函数支持。
当 exec 向一个任务抛出异常时,它会在其 tc_Flags 中设置 TF_EXCEPT 位并导致重新调度。调度程序应该注意到这一点,并将任务定向到一个特殊的例程。当前的调度程序实现将任务设置为 READY 状态,与其剩余的时间片无关,并在 cpu_Dispatch() 中执行其余的处理。
cpu_Dispatch() 应该检查 TF_EXCEPT 位,如果设置了该位,则执行以下操作
- 将任务的上下文保存到某处。
- 调整任务的上下文,使其指向异常处理程序例程。
- 跳转到已调整的上下文。
在此之后,异常处理程序开始起作用。通常它需要调用 exec.library/Exception()。此函数会自行完成所有处理。Exception() 返回后,处理程序应该获取在步骤 (1) 中保存的原始任务上下文,并跳转到该上下文。
目前只有 Windows 主机端口正确地实现了任务异常。它的代码比较复杂,使用了某些技巧(因为它是在主机上运行的),因此不能作为很好的示例。
kernel.resource 中的调度程序被设计为面向未来且可扩展的。为此提供了两个函数:KrnGetScheduler() 和 KrnSetScheduler()。它们的目的是为不同的任务调度算法提供支持。这些函数可以从用户应用程序中安全调用。但是请记住,它们会影响整个系统,因此网页浏览器(例如)不是使用它们的合适工具。它们更适合于某些系统维护实用程序(如 AmigaOS(tm) 的 Executive)。
KrnSetScheduler() 改变当前的调度算法。KrnGetScheduler() 通知当前选择了什么算法。目前这些函数是保留的。KrnSetScheduler() 实际上什么也不做,而 KrnGetScheduler() 始终返回 SCHED_RR,它是唯一可用的调度程序。
为了实现新的调度算法,应该修改以下部分
- core_Schedule() - 它是调度程序代码的 99%。该函数实际上决定任务将运行多长时间以及何时再次运行。它可以使用任何它想要的东西来做出决定,包括以下信息通常可从 exec.library 获取
- SFF_QuantumOver - 当任务的时间片(时间片)结束时,此标志在 SysBase->SysFlags 中被设置。时间片计数由 exec.library 作为 VBlank 中断处理的一部分完成。
- TF_EXCEPT - 当任务有待处理的异常时,此标志由 exec 在任务的 tc_Flags 中被设置。通常的做法是对该标志设置任务为 READY 状态,并将实际的异常处理留给 cpu_Dispatch()。但是理论上它可以通过任何其他方式完成。
- 任务在 tc_Node.ln_Pri 中的优先级。
- core_Dispatch() - 该例程负责从 TaskReady 列表中选取一个新任务,并为它分配时间片。时间片是通过设置 SysBase->Elapsed 的值来分配的。此值实际上是 VBlank 滴答次数,它决定了如果没有外部事件打扰任务,它将运行多长时间。原始的 RoundRobin 调度程序在其中放置了一个在 SysBase->Quantum 中指定的不变的默认值。事实上,此值是用户的首选项。
exec.library 不是为了在 m68k 基于的 Amiga(tm) 以外的其他系统上运行而设计的。在保持统一的处理方式的同时,将其适应不同的硬件非常困难。为了克服这些问题,kernel.resource 提供了自己的 API 来处理和控制机器的硬件中断。
请注意,这里的“中断”指的是真正的中断,CPU 从实际硬件中接收这些中断。不要将它们与上一章中描述的 exec SoftInts 混淆。为了与以后的明确区分,我们将使用广泛采用的 IRQ(中断请求)缩写。还要明确区分 IRQ 和 CPU 异常。它们在 kernel.resource 中不是一回事,尽管 IRQ 实际上是在 CPU 异常之上实现的,并且与之密切相关。事实上,IRQ 通过中断控制器映射到一个(或多个)CPU 异常。
托管的 AROS 端口也使用 IRQ 来表示各种外部事件。例如,在 UNIX 主机 AROS 中,IRQ 表示 UNIX 信号,在 Windows 主机 AROS 中,IRQ 是用于与主机 I/O 线程通信的模拟中断。
IRQ 由数字指定,从 0 到 255。其分配是特定于机器的。除非您确切知道是什么硬件生成了它,否则您无法确定特定的 IRQ 的含义。由于这种 API 在此处描述的通常仅对硬件驱动程序有用。
为了利用 IRQ,驱动程序需要两个 kernel.resource 函数
- KrnAddIRQHandler()
- 此函数安装 IRQ 处理程序。它采用 IRQ 编号、指向处理程序函数的指针以及两个任意值,这些值将被传递到
- 处理程序。它返回一些不透明的值,实际上是指向描述处理程序的内部结构的指针。不要尝试戳它
- 以某种方式!在 kernel.resource 的某些实现中,内部数据可能驻留在受保护的内存中,因此您只会得到垃圾或崩溃。
- KrnRemIRQHandler()
- 此函数删除之前安装的处理程序。它采用 KrnRemIRQHandler() 之前返回的指针。
请注意,每个 IRQ 都可以安装任意数量的处理程序。安装新的处理程序不会删除和/或禁用之前的处理程序。处理程序之间没有任何影响,当 IRQ 到达时,它们会依次调用。处理程序没有优先级。
在某些情况下,软件需要控制中断。为此目的,存在两个 exec.library 函数:Disable() 和 Enable()。在 AROS 中,这些函数在 kernel.resource 中有它们的低级姐妹:KrnCli() 和 KrnSti()。它们执行相同的操作,除了内核函数直接对 CPU 操作。它们立即执行其操作,并且不会以某种方式修改 exec.library 状态。因此,它们没有嵌套计数。
这两个函数是为了提供 exec.library 的抽象层。不要从用户应用程序中调用它们,它们不会比使用 exec.library 函数有任何优势。由于它们没有任何状态跟踪,因此它们会很容易地扰乱系统。
中断处理既依赖于 CPU 又依赖于硬件,并且在不同的机器上有所不同。基本 kernel.resource 代码只提供一个函数 - krnRunIRQHandlers()。它接受 IRQ 编号的单个参数,并执行所有已安装的处理程序。其余代码是特定于机器的,并且为每台受支持的机器单独编写。
根据特定 IRQ 是否使用,您可能希望启用或禁用单个 IRQ(如果硬件提供此功能)。基本代码期望您为此实现两个函数
- ictl_enable_irq(n)
- 启用 IRQ 编号 n
- ictl_disable_irq(n)
- 禁用 IRQ 编号 n
为了实现这两个函数,您需要在您的 kernel_arch.h 中声明它们的原型(有关详细信息,请参阅 移植)。默认情况下,它们被 #define 为空代码,因此它们实际上被省略了。
基本代码不期望这些函数有任何返回值。
AROS 需要至少一个周期性计时器中断才能运行。它用于定期运行任务调度程序并测量系统时间。从 AROS 方面来说,对计时器的频率没有任何实际要求。但是许多 Amiga(tm) 应用程序期望 exec.library 以 50 或 60 Hz 的频率提供 VBlank(显示垂直消隐)中断。它们用它来延迟。因此,实现 exec VBlank 中断是绝对必要的(与其他特定于经典 Amiga(tm) 硬件的中断不同)。另外,exec.library 在内部使用 VBlank 来测量任务时间片(SysBase->Elapsed 在 VBlank 处理程序中递减)。
AROS 运行在各种不同的硬件上,并非所有硬件(截至 2010 年 9 月 30 日,实际上没有)能够以给定的频率生成显示垂直消隐中断。由于这种 exec VBlank 中断在 AROS 中被模拟。由于实际的 VBlank 可能来自各种来源,因此预计它将通过调用 exec 的 INTB_VBLANK 向量来从外部驱动。kernel.resource 可以使用 core_Cause(INTB_VBLANK) 调用来执行此操作。
还有第二个 AROS 组件也需要硬件计时器,它是 timer.device。但是某些机器只有一个硬件计时器,需要在这两个组件之间共享。这意味着它们中只有一个可以控制计时器。有两种可能的情况
- 计时器由 kernel.resource 控制。在这种情况下,kernel.resource 将硬件计时器设置为固定频率,并在每次计时器中断时调用 exec VBlank 向量。计时器频率由 exec.library 在 SysBase->VBlankFrequency 中指定,默认为 50 Hz,但可以通过内核命令行“vblank=XX”参数覆盖。Amiga(tm) 软件预期的两个常见值是 50(对于 PAL Amiga)和 60(对于 NTSC Amiga)Hz,但实验用户可以在这里尝试任何其他值。然后,timer.device 可以将自身挂钩到 VBlank 中断并使用它来进行时间核算。这种情况通常在开发新端口时实现,此时 timer.device 尚未移植到硬件上。
- 计时器由 timer.device 控制。在这种情况下,它很可能被重新编程,并且 kernel.resource 停止对它的频率有正确的了解。在这种情况下,timer.device 负责自行驱动 VBlank 中断。为了告诉 kernel.resource 计时器已被接管,它应该使用 KrnSetSystemAttr(KATTR_VBlankEnable, FALSE) 调用。这将关闭内核的内置 VBlank 模拟。另外,timer.device 应该负责在 SysBase->ex_EClockFrequency 中设置正确的值;这很可能反映硬件的主时钟频率。
在某些硬件(如经典的 Amiga(tm) 或 MiniMig 等克隆)上,可以使用真实的视频消隐中断。在这种情况下,kernel.resource 可能根本不实现 VBlank 模拟,而只是初始化相应的硬件。当然,它仍然应该在相应的 IRQ 到达时负责调用 exec VBlank 中断向量。请记住,exec 仍然运行在 kernel.resource 之上,我们仍然需要驱动它所有的中断向量!
在 VBlank 中断之上运行的 timer.device 可能存在低精度。有时中断被禁用,如果它们被禁用足够长的时间,计时器中断就会丢失。这会导致系统时间下降和变慢。为了减轻这种影响,kernel.resource 可以提供一个高频周期性计时器。此计时器预计以 VBlank 的整数倍频率运行。这对系统时间的粒度有积极影响,但对时间的精度可能会有不同的影响(它在很大程度上取决于实际的系统)。
为了提供高频计时器,内核必须使用正确的 IRQ 编号提供 KATTR_TimerIRQ 属性。另外,kernel.resource 预计将实际计时器频率放入 SysBase->ex_EClockFrequency。如果 timer.device 不使用实际的硬件,它将使用此频率作为主时钟。
基本 kernel.resource 代码在 core_TimerTick() 函数中包含对高频计时器的支持。您应该在每次计时器 IRQ 时调用它。它将负责计数滴答并以正确的周期自行调用 exec VBlank 向量。
然后,用户可以通过“eclock=XX”命令行参数指定所需的 EClock 频率。此参数在 exec.library/PrepareExecBase() 中处理,并在内核的初始化代码中进行微调。
请注意,高频计时器是纯粹的内部事物!它仅用于支持通用的 timer.device 实现。不要在任何第三方组件和/或应用程序中依赖它。在 timer.device 接管并重新编程计时器后,无法知道计时器的频率。也无法确定 timer.device 使用此计时器还是其他计时器。没有人承诺你 SysBase->ex_EClockFrequency 将包含此计时器的频率,因为 timer.device 可能会更改此字段。你只能通过查询 KATTR_VBlankEnable 属性的值来查询内核自身 VBlank 模拟的状态,但仅此而已。因此,最好远离它。
kernel.resource 中的 CPU 支持可以被认为是不完整且实验性的。
目前,kernel.resource 只提供一个函数:KrnIsSuper()。它告诉调用者是否以超级用户模式运行。返回值应该被视为布尔值,并与零进行比较。非零表示您当前处于超级用户模式。
此函数被 exec.library 广泛用于延迟任务调度程序调用。
一些 kernel.resource 实现可能在受保护的内存中分配内部数据。目前这包括 IRQ 处理程序节点、异常处理程序节点和 CPU 上下文数据。为了使这能够正常工作,需要实现以下内部函数
- goSuper() - 将当前 CPU 特权级别设置为超级用户,并返回一些描述原始特权级别的东西。原始级别存储在
cpumode_t 类型的变量中。
- goUser() - 将当前 CPU 特权级别设置为用户
- goBack(mode) - 将当前 CPU 特权级别设置为 goSuper() 返回的值
另外(尽管与之没有直接关系),需要重新定义 krnAllocMem() 和 krnFreeMem() 以从受保护区域分配内存。默认情况下,它们是宏,扩展到 exec 的 AllocMem() 和 FreeMem()(请参阅 kernel_memory.h)。这也是在 移植 章节中讨论的。
exec.library 的 CPU 异常处理仅针对 m68k CPU 家族。虽然 AROS 仍然支持 exec 陷阱,但可能需要为特定 CPU 异常安装单独的处理程序。在 API 端,CPU 异常支持由两个函数表示
- KrnAddExceptionHandler()
- KrnRemExceptionHandler()
这些函数的工作方式类似于 KrnAddIRQHandler() 和 KrnRemIRQHandler(),但它们对原始 CPU 异常进行操作。异常处理程序会将指向已保存 CPU 上下文的指针作为第三个参数,并且预计会返回一个值。如果某个处理程序返回非零值,kernel.resource 不会调用 exec 陷阱处理程序(预计会停止任务并调出软件故障请求程序)。任务的执行将继续。
这些函数对于实现 AROS 调试器非常有用。但是,kernel.resource 的这部分可以被认为是不完整且正在开发中。这些函数的规范可能会随时更改。此外,CPU 上下文结构应在每个 CPU 家族中统一并公开,以便使这些函数真正有用。
在实现方面,基础代码提供了单个 krnRunExceptionHandlers() 函数。类似于 krnRunIRQHandlers(),它将为指定的异常运行已安装的处理程序,但将返回一个值。如果所有处理程序都返回零,则它将为零。
Exec 陷阱实际上是模拟的。特定于 CPU 的代码负责将本机 CPU 异常号转换为 exec 陷阱号,并调用 SysBase->ThisTask->tc_TrapCode。相同的规则适用于托管 AROS,唯一的例外是 CPU 异常实际上也是模拟的,它们是从主机操作系统提供的信息中重建的。这会导致一个副作用,即并非所有 CPU 异常都可以被捕获并正确处理。由于 kernel.resource 中异常处理的不完整性,托管 AROS 端口上的异常处理代码是最具实验性和未完成的部分之一。
CPU 上下文的实际格式因使用的 CPU 而异,即使是同一家族的 CPU 也是如此。例如,32 位 x86 CPU 可能会使用不同的上下文格式,具体取决于可用的扩展寄存器集(FPU、SSE 等)。托管端口也可能会将主机特定信息存储为上下文的一部分(例如主机操作系统的 errno 值)。
为了帮助 exec.library 处理这些差异,kernel.resource 提供了两个 API 函数
- KrnCreateContext()
- 分配并初始化 CPU 上下文存储区
- KrnDeleteContext()
- 释放 CPU 上下文存储区
这两个函数由 exec.library 在创建和删除任务时使用。通常不需要从用户应用程序调用它们。
如果使用内存保护,这些函数将在受保护区域分配内存。只有在具有超级用户权限的情况下才允许访问此区域。请将上下文存储区视为私有,特别是考虑到上下文结构目前也是私有的。当此问题解决后,文档将更新。
总而言之,kernel.resource 中的 CPU 管理 API 可以被认为是不完整的。以下是可以被认为是缺失的功能列表
- CPU 上下文修改
- 可能的解决方案:在各种 CPU 家族中统一 CPU 上下文格式。可能引入 KrnGetTaskContext() 和 KrnSetTaskContext() 等函数,以便从用户应用程序(如调试器)访问 CPU 上下文。
- CPU 异常处理
- 审查异常处理程序机制。可能处理程序应该被优先级化。可能它们应该以某种方式了解彼此。
- CPU 权限级别切换
- exec.library 为此目的提供了三个调用:Supervisor()、SuperState() 和 UserState()。也许它们应该以某种方式在 kernel.resource 公开 API 的基础上运行。当前内核的实现基于原始 PPC 本机代码,可能不适合其他 CPU。
- CPU 缓存操作
- 清除 CPU 缓存的方式取决于使用的 CPU 模型。在 DMA 前后需要采取的措施取决于 CPU 和主板硬件。也许 kernel.resource 应该在 exec.library 调用应该运行的一些缓存控制 API 的基础上。
此列表不代表任何最终决定,甚至不代表任何最终意见。这些主题是开放讨论的。
kernel.resource 的这部分正在积极开发中。文档将随后提供。
这部分代表了用于低级系统调试的各种支持函数。相关函数可以分为两个子组
KrnPutChar() 和 KrnMayGetChar() 与 exec.library 中的对应函数 RawPutChar() 和 RawMayGetChar() 执行相同的操作。它们只是为了提供 exec.library 的硬件抽象。第三个函数 KrnBug() 主要是在满足内核自身需求时添加的。它接受的格式字符串符合 C 标准(不符合 RawDoFmt() 标准)。KrnBug() 有一个特殊之处:它应该能够与 NULL KernelBase 一起工作。这是因为它可以从内核启动代码中调用,而此时还没有设置任何 KernelBase。
调试输出的默认实现最终会进入 krnPutC(),它在基础代码中是空的。在您的内核实现中,您需要用某个低级函数替换它,该函数将给定字符输出到某个可靠的位置,例如串行端口。此函数应该尽可能健壮:它不应依赖任何库,它应该能够在中断中工作,并且如前所述,它应该即使在没有 KernelBase 的情况下也能工作。这是 AROS 代码中为数不多的允许使用忙等待循环的地方之一,因为您没有其他功能机制。即使在系统崩溃状态下,此函数也会被调用,以输出警报信息。
KrnMayGetChar() 也有类似的要求,只是它不需要使用 NULL KernelBase。
托管 AROS 可能会选择将 KrnBug() 重新路由到主机 vprintf(),以简化操作。
这是 AROS 最有趣和最具创新性的部分。这个 API 允许应用程序获取有关程序在内存中的布局信息。
- KrnRegisterModule()
- 当新的可执行文件被加载到内存中时,必须调用此函数。通常,它由 dos.library/InternalLoadSeg() 调用,因此您不必担心它。但是,如果您正在编写将代码直接存储到内存中的编译器,您可能希望为刚刚构建的模块调用此函数。
- 目前,只有 ELF 文件被 AROS 完全支持,因此只支持 DEBUG_ELF 类型的信息。但是,预计在某个时候,将实现对在 AmigaDOS hunk 文件中找到的调试信息的 supports。.
- KrnUnregisterModule()
- 必须在可执行模块从内存中卸载之前调用此函数。同样,这通常由 dos.library/UnLoadSeg() 完成。
- KrnDecodeLocation()
- 这是最主要的函数。它允许调用者在已注册模块列表中查找地址。目前,提供以下信息
- 模块名称
- 段号、名称、起始地址和结束地址
- 符号(函数)名称、起始地址和结束地址
- 此列表不是最终的。将来可能会支持更多属性(例如,可能能够查询源文件名和行号)。
- KrnDecodeLocation() 目前由内置的 exec.library 崩溃处理程序用于显示崩溃位置数据。
这是最后一组公共内核函数,目前包含三个条目
- KrnGetBootInfo()
- 此函数返回指向引导程序提供给 kickstart 的标记列表的指针。此列表的标记在 aros/kernel.h 中定义
- KRN_KernelBase
- 待记录
- KRN_KernelLowest
- 内存中加载的 kickstart 映像的起始地址
- KRN_KernelHighest
- 内存中加载的 kickstart 映像的结束地址
- KRN_KernelBss
- 指向 BSS 段描述符数组的指针(struct KernelBSS)。该数组以 addr 字段值为 NULL 的条目结束。kickstart 执行热重启时,需要清除所有记录的 BSS 段。__clear_bss() 实用函数可以用于此目的。
- KRN_GDT
- 待记录
- KRN_IDT
- 待记录
- KRN_PL4
- 待记录
- KRN_VBEModeInfo
- 指向 VESA 视频模式信息结构的指针(在 aros/multiboot.h 中描述的 struct vbe_mode),描述当前视频模式。VESA 显示驱动程序需要此信息。
- KRN_VBEControllerInfo
- 指向 VESA 视频显示控制器信息结构的指针(struct vbe_controller)。此标记与 KRN_VBEModeInfo 配对。
- KRN_MMAPAddress
- 指向机器内存映射的指针,采用 Multiboot 格式(struct mb_mmap)。
- KRN_MMAPLength
- 提供的 multiboot 内存映射的长度。
- KRN_CmdLine
- 指向内核命令行参数的以零结尾的字符串的指针。
- KRN_ProtAreaStart
- 待记录
- KRN_ProtAreaEnd
- 待记录
- KRN_VBEMode
- 如果引导程序执行了模式切换,则指定实际的 VESA 视频模式号。如果指定了此标记,则其值将覆盖由 KRN_VBEControllerInfo 提供的 struct vbe_controller 中的 vbe_mode 字段。
- 此标记可能是多余的,并且可能会被删除。如果您正在编写具有 VESA 支持的自己的引导程序,请考虑设置 vbe_mode 为有效值,而不是使用此标记。
- KRN_VBEPaletteWidth
- VESA 调色板的宽度(以位为单位)(6 或 8)。对于直接颜色视频模式(24 位和 32 位),可能不存在。
- KRN_MEMLower
- 待记录
- KRN_MEMUpper
- 待记录
- KRN_OpenFirmwareTree
- 指向扁平化的 OpenFirmware 设备树的指针。数据格式文档需要添加。
- KRN_HostInterface
- 指向托管 AROS 的主机操作系统接口结构体的指针。此结构体指定了 AROS 与主机操作系统通信所需的回调函数(例如,链接到其共享库)。该结构体本身被认为是私有的且特定于端口的,它仅在引导程序、内核和 hostlib.resource 之间共享。
- KRN_DebugInfo
- 指向单链表的指针,该链表包含启动程序调试数据结构(struct debug_seginfo 或 dbg_seg_t)。这些结构体描述了启动程序模块在内存中的布局,并由 KrnDecodeLocation() 函数使用以提供数据。它们被认为是持久且只读的,并且在热重启期间保持不变。
- 启动程序调试信息是可选的,省略它可以节省内存,但在这种情况下,KrnDecodeLocation() 将无法提供有关启动程序内部发生崩溃的详细信息。
- KRN_BootLoader
- 指向描述主引导程序本身的 ASCII 字符串的指针。用于提供信息。
- 允许读取和检查此标签列表的内容,但是其中一些信息可以通过 bootloader.resource 以更抽象和更合适的形式访问。建议尽可能使用 bootloader.resource。
- 另外请注意,在某些架构(如经典 Amiga(tm))上,内核可以直接启动,无需外部引导程序的帮助。在这种情况下,KrnGetBootInfo() 可能会返回 NULL。
- KrnGetSystemAttr()
- 此函数提供有关 AROS 运行的系统的一些信息。此信息大多是内部的,之前已经讨论过。但是,有一个 KATTR_Architecture 属性在各种地方可能很有用。它的值是形式为“系统-cpu”的 ASCII 字符串,例如“pc-x86_64”或“mingw32-i386”。它在各种系统信息实用程序中以及在依赖于特定架构的硬件驱动程序中可能很有用(尤其是托管驱动程序,它们高度依赖于它们为其编译的主机,尽管它们是基于磁盘的)。例如,为 Linux 编译的 tap.device 与为 FreeBSD 编译的 tap.device 在二进制上不兼容(因为底层系统在二进制上不兼容),建议在这样的组件中检查系统的架构以避免混淆的崩溃。
- KrnSetSystemAttr()
- 此函数提供 KrnGetSystemAttr() 的反向功能,允许更改某些属性的值。目前,只有 KATTR_VBlankEnable 可设置,它已在 系统定时器处理和 VBlank 模拟 章中详细讨论。
内部实用程序函数
[edit | edit source]这些函数没有在 kernel.resource API 中体现。它们是基础代码提供的内部函数,用于提供代码重用并帮助将 AROS 移植到其他架构。
内存管理
[edit | edit source]kernel.resource 代码提供两个用于分配和释放内存的函数,这些函数只能由 kernel.resource 使用
- krnAllocMem(length)
- krnFreeMem(address)
这些函数在可替换的 kernel_memory.h 头文件中描述。默认情况下,它们是包装到 exec AllocMem() 和 FreeMem() 的宏。但是,如果您正在实现内存保护,您可以重新实现这些函数以从受保护区域分配内存。请记住,CPU 上下文也将由 KrnCreateContext() 函数存储在那里。
系统启动支持
[edit | edit source]这些函数在系统早期启动期间提供有用的帮助。
- krnRomTagScanner()(在 kernel_romtags.h 中声明)
- 扫描给定的地址范围以查找有效的 ROMTags 并构建已发现的 ROMTags 列表。返回值需要放置在 SysBase->ResModules 中。请注意,此函数本身依赖于有效的 AllocMem() 和 FreeMem(),这意味着它需要在 PrepareExecBase() 之后调用。
- __clear_bss()(在 kernel_base.h 中声明)
- 清除提供的描述符数组中列出的 BSS 段。实际上,这应该是您启动内核时要做的第一件事。请记住,全局变量可能存储在 BSS 部分本身,因此只有在您调用此函数之后才能存储它们的值!通常,您会在初始引导程序标签列表解析期间调用此函数。
- PrepareExecBase()
- 实际上,这是 exec.library 函数,但它与 kernel.resource 关系密切,因此非常适合放在这里。此函数获取第一个 MemHeader 的地址,并在其中构建 SysBase。但是,您需要设置 MemHeader 本身。完全由您来发现可用内存。
- 此函数是目前从 exec.library 静态链接的唯一函数。将来这可能会改变,允许 kernel.resource 和 exec.library 完全分离,在这种情况下,规范将更新。
- 此函数需要手动声明
extern struct ExecBase *PrepareExecBase(struct MemHeader *, char *, struct HostInterface *);
- 第二个和第三个参数是指向内核命令行(通过 KRN_CmdLine 标签传递)和 HostInterface 结构体的指针。第三个参数是可选的,它是否需要由 exec.library 中的特定于架构的代码确定。在某些端口(如 Windows 托管)上,exec.library 在此早期阶段需要访问主机操作系统。