Blog:
Windows Embedded Compact 的实时任务调度器

2016年2月18日星期四
Windows

WindowsWindows Embedded Compact 同其他 Windows 操作系统之间关键的不同点在于它的实时调度器。这是一个在开发嵌入式系统时需要考虑的重要功能。在工程环境中,最好总是记住关于实时的定义,这就是为什么引用下面的我最喜欢的定义,出自 Phillip A. Laplante 所著的 《Real-Time System Design and Analysis》:

"A real-time system is a system that must satisfy explicit (bounded) response-time constraints or risk severe consequences, including failure."

- Real-Time System Design and Analysis (Phillip A. Laplante)

因此,一个实时系统执行代码,能够给出精确的结果,并且在固定时间范围内进行上述任务。否则,将会导致系统出现错误。实时并不是指快速处理的能力。

Windows Embedded Compact 能够符合上述的定义运行。那么,我们首先需要理解的是任务调度器是如何工作的? WinCE 遵循两个简单的规则调度任务。任务调度器每隔 1 ms 检查系统中存在的任务,根据下面的规则决定执行那个任务:

  1. 高优先级的任务需要首先执行。
  2. 相同优先级的任务需要在 100 ms 的时间分片(或者根据 Task Quantum 的定义)内轮转执行。

关于第一个规则,WinCE 具有 256 个优先级,较大的值代表更低的优先级,较小的是值代表更高的优先级。优先级使用说明可以在下面的文章中看到: Real-Time Priority System Levels (Windows Embedded CE 6.0).

关于第二条规则,当多个任务以相同的优先级执行时,系统将会把每个任务执行 100 ms(这个时间可以根据 Thread Quantum 设置)。一旦一个任务已经执行了特定的时间,内核将挂起这个任务,允许其他相同优先级的任务在特定的时间内运行(在 WinCE 中这个时间片称为 Quantum)。据此,如果在列队中有第三个相同优先级的任务,那么调度器将会挂起第二个任务,并执行第三个任务,直到所有相同优先级的任务都执行一次。一旦所有任务都被执行一个 quantum 的时间,那么调度器将会再一次启动第一个任务。一个线程只能运行一个 quantum 的时间,除非更高优先级的中断使用 CPU 资源。在这种情况下,线程被调度器提前清空。

Computer Hardware- Colibri VF61, Iris Module Carrier Board
图片 1: Toradex Colibri VF61 Iris 模块和载板组成的计算机硬件

我将会使用两个例程来演示说明上述规则。第一个例程用于测试规则 2。我将会启动两个不同的任务,第一个会把模块的数字输出引脚设置为高电平,另外一个则将同一个输出设置为低电平。我将使用基于 NXP®/Freescale Vybrid 处理器的 Toradex Colibri VF61 256MB 系统模块,以及Iris Carrier Board(图 1)。Colibri VF61 处理器具有 500 MHz 主频的 Arm® Cortex-A5 内核,配备 256 MB 内存和 512 MB Flash。操作系统采用由 Toradex 提供易于用户使用的 Windows Embedded Compact 6.0。WinCE 授权费用已经包含在售价中,很酷吧?以下是我写的代码。

/// @file         Gpio_Demo.c
/// @copyright    Copyright (c) 2014 Toradex AG
/// $Author: guilherme.fernandes $
/// $Revision: 2908 $
/// $Date: 2015-08-10 08:29:50 +0200 (Mo, 10 Aug 2015) $
/// @brief        Program to show how to use the Gpio Library 
///               
/// @target       Colibri VFxx,Txx,iMx Modules
/// @test         Tested on:  VFxx
/// @caveats      None

#include  
#include "gpio.h"

// === define constant pins / gpios ===
uIo io1 = COLIBRI_PIN(101);  
HANDLE hGpio;
HANDLE hThreadON, hThreadOFF;

DWORD WINAPI ThreadON (void){
	//Set Thread Priority
	CeSetThreadPriority(GetCurrentThread(), 100);
	Sleep(5); //Allow the other Thread to configure its PRIO
	//INFINITE LOCKING LOOP
	while ( 1 ){
		//Set GPIO logic high
		Gpio_SetLevel(hGpio, io1, ioHigh);
    }
    return 0;
}


DWORD WINAPI ThreadOFF (void){
	//Set Thread Priority
	CeSetThreadPriority(GetCurrentThread(), 100);
	//INFINITE LOCKING LOOP
	while ( 1 ){
		//Set GPIO logic low
		Gpio_SetLevel(hGpio, io1, ioLow);
    }
    return 0;
}


//=============================================================================
// Application Entry Point
// 
// The simple error handling using ASSERT statements is only effective when 
// the application is run as a debug version.
//=============================================================================
int wmain(int argc, _TCHAR* argv[])   
{ 
    //HANDLE hGpio = NULL;            ///< handle to the GPIO library
    BOOL success;

    // === Initialize GPIO library. ===
    // We don't use registry-based  configuration, thus we can 
    // pass NULL go Gpio_Init()
    hGpio = Gpio_Init(NULL);   
    ASSERT(hGpio != 0);
    success = Gpio_Open(hGpio);
    ASSERT (success);

    // === Io Manipulation ===
    // Configure the pin to act as GPIO (as opposed to an Alternate function)
    // Set it to Output,  High
    Gpio_ConfigureAsGpio(hGpio, io1);
    Gpio_SetDir         (hGpio, io1, ioOutput);
    Gpio_SetLevel       (hGpio, io1, ioHigh);

	CeSetThreadPriority(GetCurrentThread(), 99);
	
	//Create two concorrent Threads, one set GPIO to High and other to Low
	hThreadON = CreateThread (0, 0, ThreadON, 0, 0, 0);
	hThreadOFF = CreateThread (0, 0, ThreadOFF, 0, 0, 0);

	//Time to finish the Program
	Sleep(3000);

    return(TRUE);
}

线程具有两个不同的入口函数:ThreadON 和 ThreadOFF。每个函数只包含将同一个 GPIO 引脚设置为高电平或者低电平的指令。另外需要说明的是,每一个函数都会在后台无限运行。因此,在使用其他线程或者程序时,请释放 CPU 资源。

主函数开启线程,我们在图 2 中可以看到示波器测量的结果。正如我们预期的,只要任务具有相同的优先级,它们会根据轮询调度机制切换,每个任务在 100 ms 时间分片内运行。因此,这个简单的例程演示了规则 2 的实际效果。

Two threads with same priority level
图 2:运行两个相同优先级的线程,第一个引脚置高,第二个清零

为了测试规则 1,我们在代码里做如下修改。首先在 ThreadON 函数中添加 Sleep(5) ,使其自己挂起,并每隔 30 ms 发送请求给调度器恢复任务。

DWORD WINAPI ThreadON (void){
	//Set Thread Priority
	CeSetThreadPriority(GetCurrentThread(), 99);
Sleep(5); //Allow the other Thread to configure its PRIO
	//INFINITE LOCKING LOOP
	while ( 1 ){
		//Set GPIO logic high
		Gpio_SetLevel(hGpio, io1, ioHigh);
		Sleep(5);
    }
    return 0;
}

我同样提升 ThreadON 优先级从 100 到 99(黄色高亮部分)。

所以,ThreadON 具有比 ThreadOFF 更高的优先级。在以下示波器的图片中可以看到结果。每当高优先级线程被唤醒,内核都会停止低优先级线程,允许高优先的线程执行。

Threads with different priorities
图 3:不同优先级线程。高优先级线程中断低优先级线程执行。

如果你使用多核系统,那么“Hands-up” 是一个重要的信号。WinCE 从 7.0 版本开始支持多核处理器。新的属性 affinity 可以配置哪一个内核执行线程。如果你使用 WinCE 7.0 在多核系统上,且不指定两个线程运行在同一个内核上,那么运行上面的例程的结果将出现不同,因为线程在不同的内核上运行。通常,设置 affinity 并不是一个好的方法,因为在不设置的情况下,我们能允许调度器在第一个可用内核上执行线程,从而缩短延时。

如今不少设备需要实时特性,包括工业自动化的嵌入式系统、机器人嵌入式系统或者嵌入式医疗设备上的应用。了解如何使用线程和 WinCE 调度器工作机制,使得你能够获得应用执行的确定结果,并使用 Windows Embedded Compact 开发实时系统。

参考

本博文最初使用葡萄牙语在 Embarcados.com 发表,点击这里查看。

作者: Guilherme Fernandes, CEO, Toradex Brasil

评论

Please login to leave a comment!
Have a Question?