10

我正在研究各种操作系统设计,希望为 DCPU-16 编写一个简单的多任务操作系统。然而,我读到的所有关于抢占式多任务实现的内容都以中断为中心。听起来在 16 位硬件和软件的时代,协同多任务处理更为普遍,但这要求编写每个程序时都要考虑到多任务处理。

有没有办法在无中断架构上实现抢占式多任务处理?我能想到的只是一个可以动态切换任务的解释器,但这会对性能产生巨大的影响(如果它必须解析每个操作并且不让任何东西在本机运行,那么它可能会降低 10-20x+ 的数量级,我是想象)。

4

4 回答 4

4

抢占式多任务通常通过中断例程将状态更改/感兴趣的事件发布到调度程序来实现,调度程序决定暂停哪些任务,以及根据优先级启动/继续哪些新任务。但是,当正在运行的任务调用 OS 例程时,可能会发生其他有趣的事件,这可能具有相同的效果。

但重要的是某处记录了某个事件,调度程序决定运行谁。因此,您可以使所有此类事件信号/调度仅在操作系统调用时发生。

您可以在各种任务应用程序代码中的“方便”点添加对调度程序的异常调用,以使您的系统更频繁地切换。无论是只是切换,还是使用一些背景信息,例如自上次调用以来经过的时间,都是调度程序的详细信息。

您的系统不会像由中断驱动的系统那样响应迅速,但您已经通过选择您所做的 CPU 放弃了这一点。

于 2012-04-09T05:18:24.340 回答
4

其实,是。最有效的方法是简单地修补加载程序中的运行时。内核/守护进程的东西可以有自定义补丁以获得更好的响应能力。更好的是,如果您可以访问所有源代码,则可以在编译器中打补丁。

补丁可以由各种分布式调度程序组成。每个程序都可以被修补以具有非常低的延迟计时器;在加载时,它将设置计时器,并且在调度程序的每次返回时,它将重置它。一个简单的方法将允许代码简单地做一个

if (timer - start_timer) yield to scheduler;

这不会产生太大的性能影响。主要的麻烦是找到将它们弹出的好点。在每个函数调用之间都是一个开始,检测循环并插入它们是原始的,但如果你真的需要响应式抢占,那么它是有效的。

它并不完美,但它会起作用。

主要问题是确保计时器返回是低延迟的;这样它只是一个比较和分支。此外,以某种方式处理异常——例如导致无限循环的代码错误。从技术上讲,您可以使用一个相当简单的硬件看门狗定时器并在 CPU 上断言一个复位,而无需清除任何 RAM;内存中的例程将是 RESET 向量指向的位置,它将检查堆栈并将其展开回程序调用(从而使程序崩溃但保留其他所有内容)。这有点像蛮力 if-all-else-fails 使程序崩溃。或者您可以通过这种方式将其更改为多任务,将 RESET 作为中断,但这要困难得多。

所以……是的。这是可能的,但很复杂;使用来自 JIT 编译器和动态翻译器的技术(模拟器使用它们)。

这是一个有点混乱的解释,我知道,但我很累。如果还不够清楚,我明天可以回来清理。

顺便说一句,在 CPU 中间程序上断言复位听起来很疯狂,但这是一项历史悠久且经过验证的技术。早期版本的 Windows 甚至正确地在 386 上运行兼容模式,因为没有办法从 16 位模式切换回 32 位。其他处理器和操作系统也这样做了。

编辑:所以我对 DCPU 做了一些研究,哈哈。它不是真正的 CPU。我不知道你是否可以在 Notch 的模拟器中断言重置,我会问他。方便的技术,就是这样。

于 2012-04-09T05:31:11.420 回答
2

我认为你的评估是正确的。如果调度程序可以中断(在非屈折的字典意义上)正在运行的任务并自主切换到另一个任务,则会发生抢占式多任务。所以必须有某种类型的参与者来促使调度程序采取行动。如果没有中断设备(在变形的技术意义上),那么您通常无能为力。

然而,与其切换到一个完整的解释器,一个想法只是动态地重新编程提供的程序代码。因此,在进入进程之前,调度程序知道完整的进程状态,包括它将进入的程序计数器值。然后它可以从那里向前扫描,例如用第二十条指令代码或下一个不在程序计数器处的跳转指令代码替换为跳转回调度程序。当进程返回时,调度程序将原始指令放回原处。如果它是一个跳转(条件或其他),那么它也会适当地影响跳转。

当然,这种方案只有在程序代码不动态修改自身的情况下才有效。在这种情况下,您可以对其进行预处理,以便在没有线性搜索的情况下提前知道跳转的位置。如果它愿意指定所有可能被修改的地址,那么从技术上讲,您可以允许编写良好的自修改代码,从而绝对可以避免在调度程序的动态修改中使用这些地址。

你最终会运行一个解释器,但只是为了跳转。

于 2012-04-09T05:28:07.757 回答
0

另一种方法是保留基于事件队列的小任务(如当前的 GUI 应用程序)

这也是合作的,但效果是不需要操作系统调用,您只需从任务返回,然后它将继续执行下一个任务

如果您需要继续执行任务,则需要将下一个“函数”和指向您需要的数据的指针传递给任务队列

于 2012-04-09T14:20:27.820 回答