我正试图绕过合作多任务系统的概念,以及它在单线程应用程序中的确切工作方式。
我的理解是,这是一种“多任务处理形式,其中多个任务通过在每个任务中的程序员定义的点上自动将控制权交给其他任务来执行。”
因此,如果您有一个任务列表并且正在执行一个任务,那么如何确定将执行传递给另一个任务?当你将执行恢复到之前的任务时,如何从以前的位置恢复?
我发现这有点令人困惑,因为我不明白如果没有多线程应用程序可以实现这一点。
任何建议都会非常有用:)
由于
答案 0 :(得分:7)
在单个进程(或执行线程)使用协作式多任务处理的特定方案中,您可以使用Windows的光纤或POSIX setcontext系列函数。我将在这里使用术语“纤维”。
基本上,当一根光纤完成执行大量工作并希望自愿允许其他光纤运行(因此称为“合作”术语)时,它会手动切换到其他光纤的上下文,或者更典型地它会执行某种产量跳转到调度程序上下文的()或scheduler()调用,然后调度程序找到要运行的新光纤并切换到该光纤的上下文。
这里的背景是什么意思?基本上是堆栈和寄存器。堆栈没有什么神奇之处,它只是堆栈指针恰好指向的内存块。程序计数器也没什么神奇之处,只是指向下一条执行指令。切换上下文只是将当前寄存器保存在某处,将堆栈指针更改为不同的内存块,将程序计数器更新为不同的指令流,将上下文保存的寄存器复制到CPU中,然后进行跳转。 Bam,您现在正在使用不同的堆栈执行不同的指令。通常,上下文切换代码是在程序集中编写的,该程序集以不修改当前堆栈或退出更改的方式调用,在任何情况下它都不会在堆栈或寄存器中留下任何痕迹,因此当代码恢复执行时它具有不知道发生了什么。 (同样,主题:我们假设方法调用寄存器,将参数推送到堆栈,移动堆栈指针等,但这只是C调用约定。没有什么要求你维护堆栈或任何特定的方法调用在堆栈上留下自己的任何痕迹。)
由于每个堆栈是独立的,因此您没有一些看似随机的方法调用的连续链最终溢出堆栈(如果您天真地尝试使用不断相互调用的标准C方法来实现此方案,则可能会产生这种结果) 。您可以使用状态机手动实现此操作,其中每个光纤保持状态机的工作位置,定期返回到调用调度程序的方法,但为什么在实际的光纤/协同常规支持广泛可用时呢?
还要记住,协作式多任务处理与进程,受保护的内存,地址空间等正交。见证Mac OS 9或Windows 3.x.他们支持单独流程的想法。但是当你屈服时,上下文被更改为OS上下文,允许OS调度程序运行,然后可能选择另一个进程切换到。理论上,您可以拥有一个完全受保护的虚拟内存操作系统,仍然使用协作式多任务处理在这些系统中,如果错误的进程从未产生,则OS调度程序从不运行,因此系统中的所有其他进程都被冻结。 **
下一个自然的问题是什么让事情先发制人......答案是操作系统安排一个中断计时器与CPU停止当前正在执行的任务并切换回OS调度程序的上下文,无论当前任务是否关心是否释放CPU,从而“先发制人”。 如果操作系统使用CPU权限级别,则(内核配置的)计时器不能被较低级别(用户模式)代码取消,但理论上如果操作系统不使用此类保护,则错误任务可能会屏蔽或取消中断计时器,劫持CPU。还有其他一些场景,比如IO调用,调度程序可以在定时器之外调用,调度程序可以决定没有其他进程具有更高的优先级,并且在没有交换机的情况下将控制权返回到同一进程......实际上大多数操作系统都没有在这里做一个真正的上下文切换,因为它很昂贵,调度程序代码在任何正在执行的进程的上下文中运行,所以必须非常小心不要踩到堆栈,保存寄存器状态等。
**你可能会问,如果在一段时间内没有调用产量,为什么不直接启动计时器。答案在于多线程同步。在合作系统中,您不必费心去取锁,担心重新进入等等,因为只有当事物处于已知良好状态时才会屈服。如果这个神话计时器触发,你现在可能已经破坏了被中断的程序的状态。如果必须编写程序来处理这个问题,恭喜......你现在有了一个半先出的先发制人多任务系统。不妨做正确的事!如果你正在改变事物,也可以添加线程,受保护的内存等等。这几乎就是主要操作系统的历史。
答案 1 :(得分:2)
协同多任务处理的基本思想是信任 - 每个子任务都会及时放弃控制,以避免处理器时间的其他任务。这就是为什么合作多任务系统中的任务需要进行非常彻底的测试,并且在某些情况下需要经过认证才能使用。
我并不认为自己是专家,但我认为合作任务可以作为状态机实现,将控制权交给任务将使其运行绝对最小时间它需要取得任何进展。例如,文件阅读器可能会读取文件的下几个字节,解析器可能会解析文档的下一行,或者传感器控制器可能会读取一次,然后将控制权返回给合作调度程序,这将检查任务完成。
每个任务都必须将其内部状态保持在堆上(在对象级别),而不是像传统的阻塞函数或线程那样在堆栈帧(在函数级别)。
与传统的多任务处理不同,传统的多任务处理依赖于硬件计时器来触发上下文切换,而协作式多任务依赖于编写代码,使得每个长时间运行的任务的每一步都保证以可接受的小完成时间量。
答案 2 :(得分:2)
任务将执行显式的等待或暂停或 yield 操作,该操作将调度给调度程序。在重度计算中等待IO完成或显式产生可能有不同的操作。在应用程序任务的主循环中,它可以具有* wait_for_event *调用而不是繁忙轮询。这将暂停任务,直到它有输入处理。
可能还有一个用于捕获失控任务的超时机制,但它不是切换的主要方式(否则它不会是合作)。
答案 3 :(得分:2)
考虑协作式多任务处理的一种方法是将任务拆分为步骤(或状态)。每个任务都会跟踪它需要执行的下一步。当轮到任务时,它只执行一步并返回。这样,在程序的主循环中,您只需按顺序调用每个任务,并且由于每个任务只占用一小部分时间来完成一个步骤,我们最终得到的系统允许所有任务分享cpu时间(即合作)。