【问题标题】:Force Win32 thread scheduling to a defined sequence based on priority根据优先级强制 Win32 线程调度到定义的顺序
【发布时间】:2010-11-18 18:54:44
【问题描述】:

我是一名嵌入式程序员,尝试使用 Visual Studio 2010 和 MingW(作为两个独立的构建环境)在 Win32 环境中模拟实时抢占式调度程序。我对 Win32 调度环境非常熟悉,并且我正在尝试做的事情已经碰壁了。我不是试图实现实时行为——只是为了让模拟任务以与在真实目标硬件上相同的顺序和顺序运行。

正在模拟的实时调度程序有一个简单的目标 - 始终执行能够运行的最高优先级任务(线程)。一旦任务能够运行 - 如果它的优先级高于当前运行的任务,它必须抢占当前运行的任务。由于正在等待的外部事件,或者超时/阻塞时间/睡眠时间到期,任务可能会变得能够运行 - 带有生成时基的滴答中断。

除了这种先发制人的行为,一个任务可以让步或自愿放弃它的时间片,因为它正在执行一个睡眠或等待类型的函数。

我通过为正在模拟的实时调度程序创建的每个任务创建一个低优先级 Win32 线程来模拟这一点(该线程有效地执行调度程序将在真实嵌入式目标上执行的上下文切换),中等优先级 Win32线程作为伪中断处理程序(处理模拟滴答中断和使用 Win32 事件对象向其发送信号的产生请求),以及更高优先级的 Win32 线程来模拟生成滴答中断的外围设备。

当伪中断处理程序确定应该发生任务切换时,它使用 SuspendThread() 挂起当前正在执行的线程,并使用 ResumeThread() 恢复执行新选择的任务的线程。在可能创建的许多任务及其相关联的 Win32 线程中,只有一个管理该任务的线程在任何时候都会脱离挂起状态。

重要的是,挂起的线程在调用 SuspendThread() 时立即挂起,并且伪中断处理线程在通知它中断挂起的事件发出信号后立即执行 - 但这不是我的行为看到。

作为一个我已经解决的示例问题:当任务/线程产生时,产生事件被锁定在变量中,并且中断处理线程发出信号,因为存在需要处理的伪中断(产生) .现在在我习惯于编程的实时系统中,我希望中断处理线程在收到信号后立即执行,因为它比发出信号的线程具有更高的优先级。我在 Win32 环境中看到的是,发出高优先级线程信号的线程在被挂起之前会持续一段时间——要么是因为在发出信号的高优先级线程开始执行之前需要一些时间,要么是因为挂起需要一些时间实际停止运行的任务 - 我不确定哪个。在任何情况下,通过在发出 Win32 中断处理线程信号后使信号量上的信号量 Win32 线程阻塞,并让中断处理 Win32 线程在完成其功能(握手)时解除阻塞线程,这可以很容易地纠正。有效地使用线程同步来强制调度模式符合我的需要。为此,我正在使用 SignalObjectAndWait()。

使用这种技术,当被模拟的实时调度程序在协作模式下运行时,模拟工作完美 - 但不是(根据需要)在抢占模式下。

抢占式任务切换的问题我猜是一样的,任务在被告知暂停后会继续执行一段时间,然后才真正停止运行,因此无法保证系统在运行时保持一致状态运行任务的线程挂起。但在抢占式情况下,由于任务不知道它何时会发生,因此无法使用使用信号量来阻止 Win32 线程继续直到下一次恢复的相同技术。

有没有人把这篇文章写到这么远 - 很抱歉它的长度!

那么我的问题是:

  • 如何强制 Win32 (XP) 调度立即启动和停止调用挂起和恢复线程函数的任务 - 或者 - 我如何强制更高优先级的 Win32 线程立即开始执行它能够这样做(它被阻止的对象发出信号)。有效地强制 Win32 重新安排其正在运行的进程。

  • 当任务不在任务/线程顺序执行路径中时,是否有某种方法可以异步停止任务以等待事件。

  • 模拟器在使用 POSIX 信号有效中断线程的 Linux 环境中运行良好 - 在 Win32 中是否有等效项?

感谢所有花时间阅读这篇长文的人,特别感谢任何可以牵着我的“实时工程师”手穿过这个 Win32 迷宫的人。

【问题讨论】:

    标签: c multithreading winapi visual-studio-2010


    【解决方案1】:

    如果您需要自己进行调度,那么您可以考虑使用fibers 而不是线程。纤程类似于线程,因为它们是独立的可执行代码块,但是纤程可以在用户代码中调度,而线程仅由操作系统调度。单个线程可以托管和管理多个纤程的调度,纤程甚至可以相互调度。

    【讨论】:

    • 但光纤不会同时在多个核心上运行。
    • 感谢您的提示。明天我会查一些关于纤维的参考资料。
    • @Zan:单线程可以运行多条光纤,也可以多线程运行,每个核心一个。
    • 是的,每个线程可以有多个线程 + 多个光纤。但是,您无法从线程外部控制在线程上运行的光纤。
    • 可能不是实时的,但您通常可以在外部调度光纤。一个线程负责调度自己的光纤,运行在同一个线程中的光纤可以相互调度。要告诉线程调度特定的纤程,只需向线程发布某种通知(线程消息、队列等),然后线程(或该线程中的纤程)可以在通知时调度请求的纤程已处理。
    【解决方案2】:

    首先,您为线程使用了哪些优先级值?

    如果您将高优先级线程设置为THREAD_PRIORITY_TIME_CRITICAL,它应该会立即运行 --- 只有那些与实时进程相关联的线程才会具有更高的优先级。

    其次,你怎么知道暂停和恢复没有立即发生?你确定这是问题所在?

    你不能在不暂停线程注入等待代码的情况下强制线程等待外部的东西;如果SuspendThread 不适合您,那么这将无济于事。

    最接近信号的可能是QueueUserAPC,它会在线程下次进入“alertable wait state”时安排回调运行,例如通过调用 SleepExWaitForSingleObjectEx 或类似名称。

    【讨论】:

      【解决方案3】:

      @Anthony W - 感谢您的建议。我在 THREAD_PRIORITY_ABOVE_NORMAL 运行模拟实时任务的 Win32 线程,在 THREAD_PRIORITY_HIGHEST 运行伪中断处理程序和滴答中断生成器的线程。我将暂停的线程更改为 THREAD_PRIORITY_IDLE 以防万一。我刚刚尝试了您使用 THREAD_PRIORITY_TIME_CRITICAL 的建议,但不幸的是它没有任何区别。

      关于您的问题,我确定暂停和恢复没有立即发生是问题 - 不,我不是。在我不熟悉的环境中,这是我最好的猜测。我对暂停和恢复工作失败的想法立即源于我对任务产生时的观察。如果我调用 yield (信号 [使用 Win32 事件] 更高优先级的 Win32 线程切换到下一个实时任务),我可以在 yield 之后放置一个断点,并在更高优先级的断点之前命中线。目前尚不清楚是否延迟发出事件信号和更高优先级的任务运行,或者延迟暂停线程和实际停止运行的线程导致了这种情况 - 但肯定观察到了这种行为。已使用信号量握手解决了此问题,但对于由滴答中断引起的抢占,无法做到这一点。

      我知道模拟没有按预期运行,因为一组检查实时任务调度顺序的测试失败了。调度程序总是可能有问题,或者测试有问题,但是测试将运行数周而不会在实时目标上失败,所以我倾向于认为测试和调度程序都可以。一个很大的区别是实时目标上的滴答频率是 1 毫秒,而在 Win32 模拟目标上它是 15 毫秒,即使在那时也有很大的变化。

      @Remy - 我今天读了很多关于纤维的文章,我的结论是,对于在协作模式下模拟调度程序,它们将是完美的。但是,据我所知,它们只能由光纤本身调用 SwitchToFiber() 函数来调度。是否可以让线程在计时器上阻塞或休眠,以便它定期运行,有效地抢占当时正在运行的光纤?从我读到的答案是否定的,因为阻塞一根光纤将阻塞线程中运行的所有光纤。如果它可以工作,那么周期性执行的光纤是否可以调用 SwitchToFiber() 函数来选择下一个要运行的光纤,然后再次休眠一段固定的时间?我再次认为答案是否定的,因为一旦它切换到另一根光纤,它将不再执行,因此在下一次执行光纤切换回它之前不会真正调用 Sleep() 函数。如果我对纤维的工作原理有错误的认识,请在此处更正我的逻辑。

      我认为如果周期性功能可以保留在自己的线程中,与执行纤维的线程分开,它可能会起作用 - 但是(再次从我读过的内容来看)我不认为一个线程可以影响执行纤维在不同的线程中运行。如果我的结论是错误的,我将再次感谢您在这里纠正我的结论。

      【讨论】:

        【解决方案4】:

        [编辑] - 比下面的 hack 更简单 - 似乎只是确保所有线程在同一个 CPU 内核上运行也可以解决问题 :o) 毕竟。唯一的问题是 CPU 几乎 100% 运行,我不确定热量是否会损坏它。 [/编辑]

        啊哈!我想我可以解决这个问题 - 但它很难看。不过,丑陋的东西保留在端口层。

        我现在所做的是在每次创建线程以运行任务时存储线程 ID(为每个创建的实时任务创建一个 Win32 线程)。然后我添加了下面的函数 - 使用跟踪宏调用。跟踪宏可以定义为做任何你想做的事情,并且在这种情况下被证明非常有用。下面代码中的 cmets 进行了解释。模拟并不完美,所有这些都是在线程调度已经偏离实时调度时纠正它,而我希望它一开始不要出错,但是跟踪宏的定位使代码包含该解决方案通过了所有测试:

        void vPortCheckCorrectThreadIsRunning( void )
        {
        xThreadState *pxThreadState;
        
            /* When switching threads, Windows does not always seem to run the selected
            thread immediately.  This function can be called to check if the thread
            that is currently running is the thread that is responsible for executing
            the task selected by the real time scheduler.  The demo project for the Win32
            port calls this function from the trace macros which are seeded throughout 
            the real time kernel code at points where something significant occurs.
            Adding this functionality allows all the standard tests to pass, but users
            should still be aware that extra calls to this function could be required
            if their application requires absolute fixes and predictable sequencing (as
            the port tests do).  This is still a simulation - not the real thing! */
            if( xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED )
            {
                /* Obtain the real time task to Win32 mapping state information. */
                pxThreadState = ( xThreadState * ) *( ( unsigned long * ) pxCurrentTCB );
        
                if( GetCurrentThreadId() != pxThreadState->ulThreadId )
                {
                    SwitchToThread();
                }
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2012-07-10
          • 2021-09-15
          • 1970-01-01
          • 1970-01-01
          • 2011-06-22
          • 1970-01-01
          • 1970-01-01
          • 2010-10-12
          • 1970-01-01
          相关资源
          最近更新 更多