TI-RTOS Kernel(SYS/BIOS)---线程模块

本章描述了SYS/BIOS程序可以使用的线程类型

SYS/BIOS 启动流程

SYS/BIOS在启动流程上逻辑地分为两个阶段—在调用应用程序的“main()”函数之前执行的操作和在调用应用程序的"main()"函数之后执行的操作。在两个启动序列的不同位置都提供了控制点,以便插入用户启动函数。

"before main"启动序列完全由XDCtools runtime package来控制。XDCtools运行启动序列如下:

  1. CPU复位后,立即执行特定于目标/设备的CPU初始化(从c_int00开始)
  2. 在cinit()之前,运行"reset functions"表(xdc.runtime.Reset模块提供了这个hook)。在Reset.fxns[]数组指定的函数被调用。这些重置函数仅在运行程序之前执行重置的平台上调用。
  3. 运行cinit()来初始化C运行环境
  4. 运行用户提供的"first functions"(xdc.runtime.Startup模块提供了这个hook)
  5. 运行所有模块的初始化函数
  6. 运行用户提供的"last functions"(xdc.runtime.Startup模块提供了这个hook)
  7. 运行pinit()
  8. 运行main()

"after main"启动序列由SYS/BIOS控制,并在应用程序main()函数的末尾显式调用BIOS_start()函数来启动。调用BIOS_start()时,SYS/BIOS的启动顺序如下:

  1. Start Functions。运行用户提供的"启动函数"。如果系统支持定时器,所有静态创建的定时器都在此时使用它们的静态配置进行初始化。如果一个计时器被配置为"自动"启动,那么它将启动
  2. Enable Hardware Interrupts
  3. Enable Software Interrupts。如果系统支持软件中断(Swis),那么SYS/BIOS启动顺序在此时启用了Swis
  4. Task Startup。如果系统支持任务,那么任务调度从这里开始。如果系统中没有静态或动态创建的任务,则直接进入空闲循环

下面配置脚本片段在启动序列的每个可能的控制点安装用户提供的启动函数。配置脚本的文件扩展名为".cfg"文件,用于配置模块和对象

/*get handle to xdc Reset module*/
Reset = xdc.useModule('xdc.runtime.Reset');

/*install a "reset function"*/
Reset.fxns[Reset.fxns.length++] = '&myReset';

/*get handle to xdc Startup module*/
var Startup = xdc.userModule('xdc.runtime.Startup');

/*install a "first function"*/
Startup.firstFxns[Startup.firstFxns.length++] = '&myFirst';

/*install a "last function"*/
Startup.lastFxns[Startup.lastFxns.length++] = '&myLast';

/*get handle ti BIOS module*/
var BIOS = xdc.useModule('ti.sysbios.BIOS');

/*install a BIOS startup function*/
BIOS.addUserStartupFunction('&myBiosStartup');

线程模块概览

许多实时应用程序必须同时执行许多看起来不相关的功能,通常是为了响应外部事件,如数据的可用性或控制信号的存在。函数执行和何时执行都很重要

这些函数被称为线程。不同系统对线程的定义也不同。在SYS/BIOS中,这个术语被定义为包括处理器执行的任何独立指令流。线程是一个可以激活函数调用或中断服务例程(ISR)的单控制点

SYS/BIOS使应用程序可以构成一个线程集合,每个线程执行一个模块化的功能。多线程程序在单个处理器上运行,它允许高优先级线程抢占低优先级线程,并允许线程之间进行各种类型的交互,包括阻塞、通信和同步

以这种模块化方式组织的实时应用程序(而不是单一的、集中的轮询循环)更容易设计、实现和维护

SYS/BIOS为具有不同优先级的几种类型的程序提供支持。每种线程类型都有不同的执行和抢占特征。线程的类型(优先级由高到低)为:

  • Hardware interrupts(Hwi),包括定时器函数
  • Software interrupts(Swi),包括时钟函数
  • Tasks(Task)
  • Background thread(Idle)

线程类型

在SYS/BIOS程序中,线程的四种主要类型是:

  • Hardware interrupt (Hwi) threads. Hwi线程(也称为中断服务例程或ISRs)是在SYS/BIOS应用程序中具有最高优先级的线程。Hwi线程用于执行时间紧迫的任务,这些任务受到严格的最后期限的限制。它们是在响应实时环境中发生的外部异步事件(中断)时触发的。如果使能,Hwi线程总是运行时完成,但可以被其他中断触发的Hwi线程临时抢占。
  • Software interrupt (Swi) threads. 在硬件中断(Hwi)模块化之后,软件中断线程在Hwi线程和Task线程之间提供额外的优先级。与由硬件中断触发的Hwis不同,Swis通过调用某些Swi模块API以编程方式触发。Swis处理的线程受到时间限制,不能作为任务运行,但它们的最后期限不像硬件ISRs那样严格。和Hwi一样,Swi的线程总是运行到完成。Swis允许Hwis将不太重要的处理延迟延迟到低优先级的线程,从而最小化CPU在中断服务例程中花费的时间,在此期间其他Hwis可以被禁用。Swis只需要足够的空间来为每个Swi中断优先级级别保存上下文,而Task要为每个线程使用单独的堆栈。
  • Task (Task) threads. Task线程的优先级比后台(空闲)线程高,比软件中断的优先级低。Tasks与软件中断不同,它们可以在执行期间等待(阻塞),直到必要的资源是可用的。Task要求每个线程都需要一个单独的堆栈。SYS/BIOS提供了许多可以用于任务间通信和同步的机制。其中包括信号量、事件、消息队列和邮箱。
  • Idle Loop (Idle) thread. 在SYS/BIOS应用程序中,空闲线程以最低优先级执行,并在一个连续循环(Idle lopp)中一个接一个地执行。main返回之后,SYS/BIOS应用程序调用每个SYS/BIOS模块的启动例程,然后进入Idle Loop。每个线程在再次调用之前必须等待所有其他线程完成执行。Idle Loop持续执行,除非它被高优先级线程抢占。只有没有硬性截止日期的函数才应该在Idle循环中执行。

另一种类型的线程是时钟线程,它运行在Swi线程的上下文中,Swi线程由重复计时器外围中断调用的Hwi线程触发

线程的选择使用

要为应用程序中的每个线程选择的类型和优先级级别会影响线程是否按时间调度和正确执行。SYS/BIOS静态配置使得将线程从一种类型更改为另一种类型变得很容易

一个程序可以使用多种类型的线程。下面是一些规则,用于决定程序执行的每个线程使用哪种类型的对象。

  • Swi or Task versus Hwi. 只在硬件中断服务程序中执行关键处理。应该考虑使用Hwis来处理截止日期在5us范围内的硬件中断(IRQ),特别是如果没有满足截止日期,数据可能被覆盖的情况下。对于期限较长的事件(大约100us或更多),应考虑Swis或Task。Hwi功能应该post Swis或任务来执行较低优先级的处理。使用低优先级线程最小化中断被禁用的时间长度(中断延迟),允许其他硬件中断发生。
  • Swi versus Task. 如果函数具有相对简单到的相互依赖关系和数据共享需求,则使用Swis。如果需求比较复杂,则使用Task。虽然高优先级线程可以抢占低优先级线程,但只有任务可以等待另一个时间,比如资源可用性。在使用共享数据时,Task也比Swis有更多的选择。当程序发布Swi时,Swi函数所需的所有所有输入都应该准备好。Swi对象的触发器结构提供了一种确定资源何时可用的方法。Swis的内存效率更高,因为它们都是在一个堆栈中运行的
  • Idle. 创建空闲线程以在不需要其他处理时执行非关键的内务管理任务。空闲线程通常没有硬性的截止日期。相反,它们在系统有未使用的处理器时间时运行。空闲线程以相同的优先级顺序运行。当不执行其他处理时,可以使用空闲线程来减少功率需求。在这种情况下,您不应该依赖于在功率减少期间执行任务处理
  • Clock. 当希望某个函数以驱动时钟滴答的外围设备的中断速率的倍数运行时,要使用时钟函数。时钟功能可以配置为周期性执行或只执行一次。这些函数作为Swi函数运行
  • Clock versus Swi. 所有时钟功能运行在相同的Swi优先级,因此一个时钟功能不能抢占另一个。然而,时钟函数可以发布较低优先级的Swi线程以进行冗长的处理。当下一个系统滴答和时钟Swi再次发布时,这确保时钟Swi可以抢占哪些功能。
  • Timer. 计时器线程在Hwi线程的上下文中运行。因此,它继承了相应的Timer中断的优先级。它们以编程好的定时器周期的速率被调用。定时器线程应该做绝对最小的需求来完成任务的要求。如果需要更多的处理时间,可以考虑发送一个Swi来完成这项工作,或者发送一个Semaphore以供以后的任务处理,这样可以有效地管理CPU时间。

线程特征的比较

下面是SYS/BIOS支持的线程类型的比较(线程特性的比较)

在这里插入图片描述

线程优先级

在SYS/BIOS中,硬件中断具有最高的优先级。Hwi对象集合中的优先级不是由SYS/BIOS隐式维护的。Hwi对象集合中的优先级不是由SYS/BIOS隐式维护的。Hwi优先级只适用于在给定CPU周期内准备好的多个中断被CPU服务的顺序。除非中断被全局禁用或特定的中断被单独禁用,那么硬件中断被另一个中断抢占

Swis的优先级低于Hwis。Swis有多达32个优先级击级别(默认为16)。MSP430和C28x的最大优先级级别数位16。Swis可以被优先级更高的Swi或任何Hwi抢占,Swis不能阻塞。

Tasks比Swis有更低的优先级。最多具有32个Tasks的优先级。Tasks可以被任何高优先级的线程抢占。Tasks在等待资源可用性和低优先级线程时可能被阻塞。

对于Swis和Tasks,数字越大,优先级越高。也就是说,0是Swis集合和Tasks集合中的最低优先级级别。

background Idle Loop是所有线程中优先级最低的线程。当CPU不忙于运行另一个线程时,它在一个循环中运行。当任务被启动时,Idle Loop将作为唯一优先级为0的任务运行。当tasks被禁用时,Idle循环将在应用程序的"main()"函数被调用后进入。

在这里插入图片描述

释放和抢占

SYS/BIOS线程调度程序运行准备运行的最高优先级(最高优先级)的线程,除了以下情况:

  • 正在运行的线程使用Hwi_disable()或Hwi_disableInterrupt()暂时禁用部分或所有硬件中断,阻止硬件ISRs运行
  • 正在运行的线程使用Swi_disable()临时禁用Swis。这可以防止任何高优先级的Swi抢占当前线程。它不能阻止Hwis抢占当前线程
  • 正在运行的线程使用Task_disable()临时禁用task调度
  • 如果较低优先级的任务与较高优先级的任务共享门限资源并将其状态更改为pending,则更高优先级的任务可以有效地将其优先级设置为较低优先级任务的优先级。这样被称为优先级反转

Hwis和Swis都可以与SYS/BIOS任务调度的程序交互。当task被阻塞时,通常是因为任务在一个不可用的信号量上挂起。信号量可以从Hwis和Swis以及其他tasks中释放。如果一个Hwi或Swi释放一个信号量来解除挂起任务的阻塞,如果该任务的优先级高于正在运行的任务(在Hwi或Swi完成后),处理器就会切换到该任务。

当运行Hwi或Swi时,SYS/BIOS使用一个专用的系统中断栈,称为系统堆栈(有时称为ISR堆栈)。每个任务使用自己的私有堆栈。因此,如果系统中没有Task,则所有线程共享相同的系统堆栈。出于性能原因,有时将系统堆栈放在系统堆栈中。

下表显示了一种类型的线程正在运行(顶行)和另一种线程正在准备运行时(左列)会发生什么。显示的行为是新释放的(准备运行)线程
在这里插入图片描述
在下图中,在默认情况下,启用Swis和Hwis,当中断发生时,一个Hwi释放一个优先级高于正在运行的Swi的Swi。另外,当第一个ISR运行时,会出现第二个Hwi,抢占第一个ISR。
在这里插入图片描述
低优先级的Swis被Hwis异步抢占。第一个Hwi释放一个更高优先级的Swi,在两个Hwis完成执行后执行

下面是上图中的伪代码示例:

backgroundThread()
{
	Swi_post(Swi_B) /*priority = 5*/
}

Hwi_1()
{
	…
}

Hwi_2()
{
	Swi_post(Swi_A) /*priority = 7*/
}

钩子

Hwi、Swi和Tasks要求线程可选地在线程生命周期中提供点,以插入用户代码,用于检测、监视或统计收集目的。每个代码点被称为"钩子",为钩子提供的用户函数被称为"钩子函数"。

下列的钩子函数能被设置为各种各样的线程类型:
在这里插入图片描述
钩子被声明为一组称为"钩子集"的钩子函数。不需要在一个集合中定义所有的钩子函数,只需要定义应用程序需要的钩子函数

钩子函数只能静态声明(在配置脚本中),以便在提供钩子函数时可以有效地调用它们,并且在没有提供钩子函数时不会导致运行时开销

除了Register钩子外,所有钩子函数都是与线程关联的对象句柄(即Hwi对象、Swi对象或Task对象)作为参数调用的。还为一些特定于线程类型的钩子函数提供了其他参数

还可以根据需要为应用程序定义任意多的钩子集合。当定义了多个钩子集时,对于特定的钩子类型,将按照钩子ID顺序调用每个钩子集中的各个钩子函数。例如,在Task_create()期间,每个Task钩子子集内的Create hook的调用与最初定义的Task钩子子集的顺序相同

对于一个线程的寄存器钩子(只调用一次)的参数是一个索引(“钩子ID”),指示钩子集在钩子函数调用序列中的相对顺序。

每一个钩子函数都有一个唯一的关联的"钩子上下文指针"。这个通用指针本身可以用来保存钩子集的特定信息,也可以初始化它,指向由钩子集内的Create钩子函数分配的内存块,如果一个特定的应用程序需要更多的空间。

单个钩子函数通过以下线程类型特定的API获取其关联上下文指针的值:Hwi_getHookContext(),Swi_getHookContext(),和Task_getHookContext()。也提供了初始化上下文指针的相应API:Hwi_setHookContext(),Swi_setHookContext(),和Task_setHookContext()。这些APIs都将钩子ID作为参数。

下图显示了一个带有三个Hwi钩子子集的应用程序

在这里插入图片描述
使用Hwi_getHookContext()访问钩子上下文指针,使用提供给三个Register钩子函数的索引

在调用ISR函数之前,Begin Hook函数的调用顺序如下:

  1. beginHookFunc0();
  2. beginHookFunc1();
  3. beginHookFunc2();

同样地,当ISR函数返回时,End Hook函数按以下顺序调用:

  1. endHookFunc0();
  2. endHookFunc1();
  3. endHookFunc2();

在SMP系统上使用SYS/BIOS

SYS/BIOS可用于对称多处理(Symmetric Multiprocessing,SMP)系统,如几种TI SoC设备上的双核ARM CortexM3/M4和多核Cortex-A15子系统。这样的系统由两个或多个相同的处理器核心组成,它们对内存和外设有相同的看法。SMP/BIOS是支持这种系统的SYS/BIOS的一种运行模式

在BIOS,Core,Task,Idle,和Hwi模块中的API和配置参数允许控制SMP/BIOS的多核行为

硬件中断

硬件中断(Hwis)处理应用程序在响应外部异步事件时必须执行关键处理。特定于SYS/BIOS目标/设备的Hwi模块用于管理硬件中断

在典型的嵌入式系统中,硬件中断要么由设备上的外围设备触发,要么由处理器外部的设备触发。在这两种情况下,中断导致处理器向ISR地址向量

任何可能调用SYS/BIOS APIs的中断处理,影响Swi和Task调度,必须用C或C++编写。在早期版本的SYS/BIOS中提供的用于汇编语言ISR的HWH_enter()/HWI_exit()宏不再提供

不与SYS/BIOS交互的汇编ISR可以通过Hwi_plug()指定。这样的ISR必须进行自己的上下文保存。它们可以使用"interrupt"关键字、C函数或汇编语言函数

所有硬件中断运行到完成。如果一个Hwi在ISR有机会在运行之前被释放了多次,那么ISR只运行一次。由于这个原因,应该尽量减少Hwi函数执行的代码量

如果中断是全局启用的—也就是说,通过Hwi_enable()—ISR可以被任何已启用的中断抢占

Hwis不能对目标使用CSL(Chip Support Library)

通过创建Hwi对象,可以将ISR函数与特定的中断关联起来

Creating Hwi Objects

Hwi模块维护了一个指向Hwi对象的指针表,其中包含了由调度管理器的每个Hwi的信息(或者在没有提供Hwi调度器的平台上生成的中断存根)。要动态创建Hwi对象,可以使用如下调用:

Hwi_Handle hwi0;
Hwi_Params hwiParams;
Error_Block eb;

Error_init(&eb);
Hwi_Params_init(&hwiParams);

hwiParams.arg = 5;
Hwi0 = Hwi_create(id,hwiFunc,&hwiParams,&eb);
if(hwi0 == NULL){
	System_abort("Hwi create failed");
}

Hwi0是创建的Hwi对象句柄,id是正在定义的中断号,hwiFunc是与Hwi关联的函数名称,hwiParams是一个结构,包含Hwi实例参数(启用/恢复掩码,Hwi函数参数等)。这里hwiParams.arg被设置为5。如果传递的是NULL,而不是指向实际Hwi_Params结构体的指针,则使用默认的参数集。"eb"是一个错误块,可以用它来处理Hwi对象创建过程中可能发生的错误。

对应的静态配置Hwi对象创建语法为:

var Hwi = xdc.useModule('ti.sysbios.hal.Hwi');
var hwiParams = new Hwi.Params;

hwiParams.arg = 5;
Program.global.hwi0 = Hwi.create(id,'&hwiFunc',hwiParams);

“hwiParams = new Hwi.Params”语句的作用相当于用默认值创建和初始化hwiParams结构。在静态配置中,"create"函数不需要错误块(eb)。“Program.global.hwi0” name称为静态创建的Hwi对象的一个可运行时访问的句柄(符号name=“hwi0”)

硬件中断嵌套和系统堆栈大小

当Hwi运行时,它的函数是使用系统堆栈调用。在最坏的情况下,每个Hwi都会有调度函数的嵌套(也就始说,优先级最低的Hwi会被下一个优先级最高的Hwi抢占,而下一个优先级最高最高的Hwi又会抢占,…)。这导致实际使用的每个Hwi优先级都需要增加堆栈大小

默认的堆栈大小是4096字节。可以通过下列的配置脚本进行系统堆栈大小设计:

Program.stack = yourStackSize;

下表显示了吸收最坏情况Hwi中断嵌套所需的系统堆栈数量。第一个数字是在一个目标上第一优先级所需的系统堆栈空间,第二个数字显示应用程序中使用的每个后续优先级所需的堆栈空间量

在这里插入图片描述

Hwi钩子

Hwi模块支持下列一组的Hook函数:

  • Register. 在运行时初始化任何静态创建的Hwis之前调用的函数。register钩子在启动时在main()和中断被启用之前被调用。
  • Create. 创建Hwi时调用的函数。这包括静态创建的Hwis和使用Hwi_create()动态创建的Hwis。
  • Begin. 在运行Hwi ISR函数之前调用的函数
  • End. 在Hwi ISR函数完成后调用的函数
  • Delete. 当Hwi在运行时被删除时,调用Hwi_delete()函数

下面的HookSet结构类型定义封装了Hwi模块支持的钩子函数

typedef struct Hwi_HookSet{
	Void (*registerFxn)(Int);					/*Register Hook*/
	Void (*createFxn)(Handle,Error.Block *);	/*Create Hook*/
	Void (*beginFxn)(Handle);					/*Begin Hook*/
	Void (*endFxn)(Handle);						/*End Hook*/
	Void (*deleteFxn)(Handle);					/*Delete Hook*/
};

Hwi Hook功能只能静态配置

注册函数

提供register函数是为了允许钩子集合存储其相应的钩子ID。这个ID可以传递给Hwi_setHookContext()和Hwi_getHookContext()来设置或获取钩子特定的上下文。如果钩子实现需要使用Hwi_setHookContext()或Hwi_getHookContext(),则必须指定Register函数

在启动中断之前,在系统初始化期间调用registerFxn钩子函数

Register函数具有以下的签名:

Void registerFxn(Int id);

创建和删除函数

每当创建或删除Hwi时,都会调用Create和Delete函数。Create函数被传递一个Error_Block,该Error_Block将传递给需要额外上下文存储空间的应用程序Memory_alloc()

在启用中断的情况下调用createFxn和deleteFxn函数(除非在引导时或从main()调用)

这些函数有下列的签名:

Void createFxn(Hwi_Handle hwi,Error.Block *eb);
Void deleteFxn(Hwi_Handle hwi)

开始和结束函数

在全局禁用中断的情况下调用Begin和End钩子函数。因此,任何钩子处理函数都会影响整个系统的中断响应延迟。为了减少这种影响,要考虑Hwi beginFxn或endFxn钩子函数所花费的处理时间。

在调用ISR函数之前调用beginFxn。从ISR函数返回后立即调用endFxn

这些函数有下列的签名:

Void beginFxn(Hwi_Handle hwi);
Void endFxn(Hwi_Handle hwi);

当定义了多个钩子集时,将按照钩子ID顺序调用公共类型的单个钩子函数

Hwi钩子案例

下面的示例应用程序使用两个Hwi钩子集。与静态创建的Timer关联的Hwi用于执行Hwi钩子函数。这个例子演示了如何读写与每个钩子集关联的钩子上下文指针。

下列C代码的例子:

/* ======== HwiHookExample.c ========
 * This example demonstrates basic Hwi hook usage. */
#include <xdc/std.h>
#include <xdc/runtime/Error.h>
#include <xdc/runtime/System.h>
#include <xdc/runtime/Timestamp.h>
#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/knl/Task.h>
#include <ti/sysbios/hal/Timer.h>
#include <ti/sysbios/hal/Hwi.h>
extern Timer_Handle myTimer;
volatile Bool myEnd2Flag = FALSE;
Int myHookSetId1, myHookSetId2;
Error_Block eb;
Error_init(&eb);
/* HookSet 1 functions */
/* ======== myRegister1 ========
 * invoked during Hwi module startup before main()
 * for each HookSet */
Void myRegister1(Int hookSetId)
{
    
    
    System_printf("myRegister1: assigned hookSet Id = %d\n", hookSetId);
    myHookSetId1 = hookSetId;
}
/* ======== myCreate1 ========
 * invoked during Hwi module startup before main()
 * for statically created Hwis */
Void myCreate1(Hwi_Handle hwi, Error_Block *eb)
{
    
    
    Ptr pEnv;
    pEnv = Hwi_getHookContext(hwi, myHookSetId1);
    /* pEnv should be 0 at this point. If not, there's a bug. */
    System_printf("myCreate1: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
    Hwi_setHookContext(hwi, myHookSetId1, (Ptr)0xdead1);
}
/* ======== myBegin1 ========
 * invoked before Timer Hwi func */
Void myBegin1(Hwi_Handle hwi)
{
    
    
    Ptr pEnv;
    pEnv = Hwi_getHookContext(hwi, myHookSetId1);
    System_printf("myBegin1: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
    Hwi_setHookContext(hwi, myHookSetId1, (Ptr)0xbeef1);
}
/* ======== myEnd1 ========
 * invoked after Timer Hwi func */
Void myEnd1(Hwi_Handle hwi)
{
    
    
    Ptr pEnv;
    pEnv = Hwi_getHookContext(hwi, myHookSetId1);
    System_printf("myEnd1: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
    Hwi_setHookContext(hwi, myHookSetId1, (Ptr)0xc0de1);
}
/* HookSet 2 functions */
/* ======== myRegister2 ========
 * invoked during Hwi module startup before main
 * for each HookSet */
Void myRegister2(Int hookSetId)
{
    
    
    System_printf("myRegister2: assigned hookSet Id = %d\n", hookSetId);
    myHookSetId2 = hookSetId;
}
/* ======== myCreate2 ========
 * invoked during Hwi module startup before main
 * for statically created Hwis */
Void myCreate2(Hwi_Handle hwi, Error_Block *eb)
{
    
    
    Ptr pEnv;
    pEnv = Hwi_getHookContext(hwi, myHookSetId2);
    /* pEnv should be 0 at this point. If not, there's a bug. */
    System_printf("myCreate2: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
    Hwi_setHookContext(hwi, myHookSetId2, (Ptr)0xdead2);
}
/* ======== myBegin2 ========
 * invoked before Timer Hwi func */
Void myBegin2(Hwi_Handle hwi)
{
    
    
    Ptr pEnv;
    pEnv = Hwi_getHookContext(hwi, myHookSetId2);
    System_printf("myBegin2: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
    Hwi_setHookContext(hwi, myHookSetId2, (Ptr)0xbeef2);
}
/* ======== myEnd2 ========
 * invoked after Timer Hwi func */
Void myEnd2(Hwi_Handle hwi)
{
    
    
    Ptr pEnv;
    pEnv = Hwi_getHookContext(hwi, myHookSetId2);
    System_printf("myEnd2: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
    Hwi_setHookContext(hwi, myHookSetId2, (Ptr)0xc0de2);
    myEnd2Flag = TRUE;
}
/* ======== myTimerFunc ========
 * Timer interrupt handler */
Void myTimerFunc(UArg arg)
{
    
    
    System_printf("Entering myTimerHwi\n");
}
/* ======== myTaskFunc ======== */
Void myTaskFunc(UArg arg0, UArg arg1)
{
    
    
    System_printf("Entering myTask.\n");
    Timer_start(myTimer);
    /* wait for timer interrupt and myEnd2 to complete */
    while (!myEnd2Flag) {
    
    
       ;
    }
    System_printf("myTask exiting ...\n");
}
/* ======== myIdleFunc ======== */
Void myIdleFunc()
{
    
    
    System_printf("Entering myIdleFunc().\n");
    System_exit(0);
}
/* ======== main ======== */
Int main(Int argc, Char* argv[])
{
    
    
    System_printf("Starting HwiHookExample...\n");
    BIOS_start();
    return (0);
}

下面是例子的配置脚本

/* pull in Timestamp to print time in hook functions */
xdc.useModule('xdc.runtime.Timestamp');
/* Disable Clock so that ours is the only Timer allocated */
var BIOS = xdc.useModule('ti.sysbios.BIOS');
BIOS.clockEnabled = false;
var Idle = xdc.useModule('ti.sysbios.knl.Idle');
Idle.addFunc('&myIdleFunc');
/* Create myTask with default task params */
var Task = xdc.useModule('ti.sysbios.knl.Task');
var taskParams = new Task.Params();
Program.global.myTask = Task.create('&myTaskFunc', taskParams);
/* Create myTimer as source of Hwi */
var Timer = xdc.useModule('ti.sysbios.hal.Timer');
var timerParams = new Timer.Params();
timerParams.startMode = Timer.StartMode_USER;
timerParams.runMode = Timer.RunMode_ONESHOT;
timerParams.period = 1000; // 1ms
Program.global.myTimer = Timer.create(Timer.ANY, "&myTimerFunc", timerParams);
/* Define and add two Hwi HookSets
 * Notice, no deleteFxn is provided.
 */
var Hwi = xdc.useModule('ti.sysbios.hal.Hwi');
/* Hook Set 1 */
Hwi.addHookSet({
    
    
      registerFxn: '&myRegister1',
      createFxn: '&myCreate1',
      beginFxn: '&myBegin1',
      endFxn: '&myEnd1',
});
/* Hook Set 2 */
Hwi.addHookSet({
    
    
      registerFxn: '&myRegister2',
      createFxn: '&myCreate2',
      beginFxn: '&myBegin2',
      endFxn: '&myEnd2',
});

程序的输出如下:

myRegister1: assigned hookSet Id = 0
myRegister2: assigned hookSet Id = 1
myCreate1: pEnv = 0x0, time = 0
myCreate2: pEnv = 0x0, time = 0
Starting HwiHookExample...
Entering myTask.
myBegin1: pEnv = 0xdead1, time = 75415
myBegin2: pEnv = 0xdead2, time = 75834
Entering myTimerHwi
myEnd1: pEnv = 0xbeef1, time = 76427
myEnd2: pEnv = 0xbeef2, time = 76830
myTask exiting ...
Entering myIdleFunc().

软件中断

软件中断是以硬件isr为模式的。SYS/BIOS中的Swi模块提供软件中断的能力。软件中断是通过编程方式触发的,通过调用SYS/BIOS API,比如Swi_post()。Swi优先级高于Task,但低于Hwi。

与Hwis相比,Swi线程适合处理速度较慢的应用程序任务,或者受到的实时截止时间较短的任务。

可以触发或释放Swi的SYS/BIOS API有:

  • Swi_andn()
  • Swi_dec()
  • Swi_inc()
  • Swi_or()
  • Swi_post()

Swi管理器控制所有Swi功能的执行。当应用程序调用上述API之一时,Swi管理器会安排与指定Swi对应的函数执行。为了处理Swi函数,Swi管理器使用Swi对象。

如果一个Swi被释放,它只在所有挂起的Hwis运行之后运行。正在运行的Swi函数可以在任何时候被Hwi抢占。在Swi功能恢复之前,Hwi完成。另一方面,Swi函数总是抢占任务。所有挂起的Swis在最高优先级的Task被允许运行之前运行。实际上,Swi就像一个优先级高于所有普通任务的任务

创建Swi对象

与许多其他SYS/BIOS对象一样,可以动态地创建Swi对象(调用Swi_create())或在配置中静态地创建Swi对象。动态创建地Swis也可以在程序执行期间删除。

为了向配置中添加一个新的Swi,需要在配置脚本中创建一个新的Swi对象。为每个Swi设置function属性,以便在应用程序触发对象时运行函数。还可以配置最多两个参数来传递每个Swi函数

与所有具有实例的模块一样,可以确定从哪个内存段分配Swi对象。当为了执行Swis要被释放和调度时,Swi管理器将访问Swi对象

为了动态创建一个Swi对象,使用以下语法调用:

Swi_Handle swi0;
Swi_Params swiParams;
Error_Block eb;
Error_init(&eb);
Swi_Params_init(&swiParams);
swi0 = Swi_create(swiFunc, &swiParams, &eb);
if (swi0 == NULL) {
    
    
    System_abort("Swi create failed");
}

swi0是创建Swi对象的句柄,swiFunc是与Swi关联的函数名,swiParam是Swi_Param类型的结构,包含Swi实例的参数(priority,arg0,arg1)。如果传递的是NULL,而不是指向实际Swi_Params结构体的指针,则使用默认的参数集。"eb"是一个错误块,可以用来处理Swi对象创建过程中可能发生的错误。

在配置文件中创建Swi对象,使用如下语句:

var Swi = xdc.useModule('ti.sysbios.knl.Swi');
var swiParams = new Swi.Params();
program.global.swi0 = Swi.create(swiParams);

设置软件中断的优先级

在Swis中有不同的优先级。可以为每个优先级级别创建尽可能多的内存约束。对于处理实时较短的线程的Swi,可以选择较高的优先级(较高的优先级数字),对于处理执行截止日期较短的线程的Swi,可以选择较低的优先级。

一个应用程序中支持的Swi优先级的数量可配置为最多32个。MSP430和C28x的最大优先级为16。缺省情况下,优先级为16。最低优先级为0。因此,默认情况下,最高优先级为15。

不能在单个优先级级别内对Swis进行排序。它们是按照释放顺序提供服务的。

软件中断优先级和系统堆栈大小

当Swi被释放后,它关联的Swi函数将使用系统堆栈进行调用。虽然可以在一些设备上,可以有多达32个Swi优先级等级,切记,在最坏情况下,每个Swi优先级等级可以导致Swi调度函数的嵌套(即最低优先级Swi由下一个最高优先级Swi被抢占,反过来,由下一个最高抢占,…)。这导致实际使用的每个Swi优先级都需要增加堆栈大小。因此,就堆栈大小而言,给予Swis相同的优先级比给予每个Swi单独的优先级更有效。

默认系统堆栈大小为4096字节。可以在配置脚本中添加下列代码来设置系统堆栈大小:

Program.stack = yourStackSize;

下表显示了吸收最坏情况Swi中断嵌套所需的系统堆栈数量。第一个数字是在一个目标上第一优先级所需的系统堆栈空间,第二个数字显示应用程序中使用的每个后续优先级所需的堆栈空间量

System Stack Use for Swi Nesting by Target Family
在这里插入图片描述

软件中断的执行

Swis可用通过调用Swi_andn(),Swi_dec(),Swi_inc(),Swi_or()和Swi_post()来调度执行。这些调度几乎可以在程序的任何地方使用—Hwi功能,Clock功能,Idle功能,或其他的Swi功能

当一个Swi被释放后,Swi管理器将它添加到一个释放的正在等待执行的Swi列表中。Swi管理器检查当前是否启用了Swis。如果它们不是,就像Hwi函数中的情况一样,Swi管理器将控制权返回给当前线程

如果Swis被启用,Swi管理器将根据当前运行的线程的优先级检查已释放的Swi对象的优先级。如果当前运行的线程是background Idle Loop,a Task,或这a lower priority Swi,则Swi管理器将从已经释放的Swi对象列表中删除Swi并切换当前线程的CPU控制,以开始执行已经释放的Swi函数

如果当前运行的线程是具有相同或更高优先级的Swi,则Swi管理器将控制权返回给当前线程,而已释放的Swi函数将在所有其他具有更高优先级或与之前释放的相同优先级的Swi完成执行后运行。

当多个具有相同优先级的Swis被释放后,它们各自的Swi函数将按照释放的Swis顺序执行

关于Swi有两个点需要被记住

  • 当Swi开始执行时,它必须不阻塞地运行到完成
  • 当从硬件ISR中调用时,调用任何Swi函数(可以触发或释放Swi)的代码必须由Hwi调度器调用(或由在没有提供Hwi调度器的平台上生成的中断存根调用,如MSP430)。也就是说,Swi必须由Hwi对象调用的函数触发。

Swi函数可以被更高优先级的线程抢占(例如Hwi或更高优先级的Swi)。然而,Swi函数不能阻塞。当Swi等待某个东西(比如设备)准备就绪时,不能挂起它

如果一个Swi在Swi管理器将它从已经释放的Swi列表中移除它之前释放了多次,它的Swi函数只执行一次,就像CPU在中断标志寄存器中清除对应的中断标志位之前Hwi将被触发多次,那么Hwi也仅仅执行一次,

应用程序不应该对同等优先级的Swi函数的调用顺序做任何假设。然而,Swi函数可以安全地释放自己(由另一个中断释放)。如果有多个挂起,则在任何运行之前调用所有地Swi函数。

使用一个Swi对象的触发变量

每个Swi对象都有一个用于C6x目标的32位触发器变量和一个用于C5x、C28x和MSP430目标的16位触发器变量。这用于确定是释放Swi还是提供可以在Swi函数中计算的值

Swi_post,Swi_or,和Swi_inc无条件的释放一个Swi对象

  • 当使用Swi_post()释放Swi时,他不会修改Swi对象中触发器的值
  • Swi_or设置触发器中由作为参数传递的掩码确定的位,然后释放Swi
  • 在释放Swi对象之前,Swi_inc()将Swi的触发器的值增加1
  • Swi_andn()和Swi_dec()只在触发器的值为0时释放一个Swi对象
  • Swi_andn()清除触发器中由作为参数传递的掩码确定的位
  • Swi_dec()将触发器的值减少1

下表总结了这些函数之间的区别
在这里插入图片描述
Swi触发器允许可以更严格地控制应导致释放Swi函数的条件,或一旦释放Swi并计划执行Swi函数应执行的次数

为了访问其触发器的值,Swi函数可以条用Swi_getTrigger()。Swi_getTrigger()只能从Swi对象的函数中调用。Swi_getTrigger()返回的值是在从释放的Swi队列中删除的Swi对象和计划执行Swi函数之前的触发器的值

当Swi管理器从释放的Swi对象的队列中删除一个挂起的Swi对象时,它的触发器被重置为初始值。触发器的初始值在应用程序的配置脚本中被设置。如果Swi函数正在执行时,Swi再次被释放,则相应地更新触发器的值。然而,当Swi函数执行时,这并不影响Swi_getTrigger()返回的值。也就是说,当从挂起的Swi列表中删除Swi时,Swi_getTrigger()返回的的触发器的值是锁存器中的值。然而,Swi触发器会在Swi从待定的Swi列表中删除并计划执行后立即重置。这使应用程序能够在出现新的释放时会不断更新Swi触发器的值,即使Swi函数还没有完成其的执行

例如,如果一个Swi对象在从已经释放的Swis队列中删除之前被释放了多次,那么Swi管理器将调度其函数只执行一次。然而,如果一个Swi函数必须在Swi对象被释放多次后运行多次,那么应该使用Swi_inc()来释放Swi

在这里插入图片描述
当使用Swi_inc()对Swi释放,一旦Swi管理器调用相应的Swi函数执行,Swi函数可以访问Swi对象触发之前知道这是多少次释放调度运行,并继续执行相同的函数,次数与触发器的值相同

如果一个给定的Swi要触发多个事件,应该使用Swi_andn()来释放相应的Swi对象,如下图所示,例如,如果一个Swi必须等待来自两个不同设备的输入数据才能继续,那么,在配置Swi对象时,触发器应该被设置为两个set位。当两个提供输入数据的函数都完成了它们的任务时,它们都应该使用互补的位掩码来调用Swi_andn(),以清除Swi触发器默认值中设置的每个位。。因此,只有当两个进程的数据都准备好了时,才会释放Swi。
在这里插入图片描述

如果程序执行要求在释放Swi之前必须发生多次相同的事件,应该使用Swi_dec()来释放Swi,如下图所示。通过配置Swi触发器,使其等于在Swi被释放之前的事件发生的次数,并在每次事件发生时调用Swi_dec(),Swi仅在其触发器到达0后被释放,也就是说,事件发生后的次数等于触发器的值

在这里插入图片描述
在某些情况下,Swi函数可以根据释放它的事件调用不同的函数。在这种情况下,程序可以使用Swi_or()在事件发生时无条件地释放Swi对象。如下图所示,Swi_or()使用地位掩码值编码了触发了释放操作的事件类型,Swi函数可以使用它作为标识事件的标志,并选择要执行的函数。

在这里插入图片描述

优点和折衷

使用Swis而不是Hwis有几个好处:

  • 通过修改Swi函数中的共享数据结构而不是Hwi,当Task访问共享数据结构时,可以通过禁用Swis来获得互斥。这允许系统使用Hwis实时响应。相反,如果Hwi函数直接修改了共享数据结构,Task将需要禁用Hwis以互斥的方式访问数据结构。显然,禁用Hwis会降低实时系统的性能。

将长的ISR分成两部分通常是有意义的。Hwi处理时间非常紧迫的操作,并通过在Hwi功能中释放Swi来将不太重要的处理推迟的Swi功能。

记住Swi函数必须在任何被阻塞的Task被允许之前完成

同步Swi函数

在Idle、Task或Swi函数中,可以通过调用Swi_disable()来临时阻止更高优先级的Swi抢占,该函数禁用所有的Swi抢占。要重新启用Swi抢占,要调用Swi_restore()。Swis作为一个组启用或禁用。单个的Swi不能单独启用或禁用

当SYS/BIOS完成初始化并在调用第一个任务之前,Swis已经启用。如果一个应用程序希望禁用Swis,调用Swi_disable()如下:

key = Swi_disable()

对应的enable函数是Swi_restore(),其中key是Swi模块使用的一个值,用于确定Swi_disable()是否被调用多次

Swi_restore(key);

这允许Swi_disable()/Swi_restore()调用的嵌套,因为只有最外层的Swi_restore()调用实际上启用了Swis。换句话说,task可以禁用和启用Swis,而不必确实是否已经在其他地方调用了Swi_disable()

当Swis被禁用时,释放的Swi函数在那个时候不能运行。中断在软件中是"锁存"的,当Swis启用时运行,并且它是准备运行的最高优先级线程

要删除动态创建的Swi,使用Swi_delete()。与Swi相关的内存也要被释放。Swi_delete()只能从task级别调用

Swi钩子

Swi模块支持以下的钩子函数集

  • Register. 在运行时初始化任何静态创建的Swi之前调用的函数。register钩子在启动时在main()和中断被启用之前被调用
  • Create. 创建Swi时调用的函数。包括静态创建Swi和使用Swi_create()动态创建Swis
  • Ready. 当任何Swi准备好运行时调用的函数
  • Begin. 在运行Swi函数之前调用的函数
  • End. 从Swi函数返回后立即调用的函数
  • Delete. 在运行时使用Swi_delete()删除Swi时调用的函数

下面的Swi_HookSet结构类型定义封装了Swi模块支持的钩子函数

typedef struct Swi_HookSet {
    Void (*registerFxn)(Int);                 /* Register Hook */
    Void (*createFxn)(Handle, Error.Block *); /* Create Hook */
    Void (*readyFxn)(Handle);                 /* Ready Hook */
    Void (*beginFxn)(Handle);                 /* Begin Hook */
    Void (*endFxn)(Handle);                   /* End Hook */
    Void (*deleteFxn)(Handle);                /* Delete Hook */
};

Swi功能只能静态配置

当定义了多个钩子集时,将按照钩子ID顺序调用公共类型的单个钩子函数

Register 函数

Register 函数允许钩子集存储其对应的钩子ID。这个ID可以传递给Swi_setHookContext()和Swi_getHookContext()来设置或获取钩子指定的上下文。如果钩子实现需要使用Swi_setHookContext()或Swi_getHookContext(),则必须指定Register函数

registerFxn函数在启动中断之前,在系统初始化期间被调用

register函数有下列的签名:

Void registerFxn(Int id);

Create和Delete函数

每当创建或删除Swi时,都会调用Create和Delete函数。Create函数被传递一个Error_block,该Error_block将被传递给需要额外存储空间的应用程序Memory_alloc()

在启用中断的情况下调用createFxn和deleteFxn函数(除非在引导时或从main()调用)

这些函数有下面的签名:

Void createFxn(Swi_Handle swi, Error_Block *eb);
Void deleteFxn(Swi_Handle swi);

Ready,Begin和End函数

在启用中断的情况下调用Ready,Begin和End钩子函数。readyFxn函数在释放Swi并准备好运行时调用。在运行与给定Swi关联的函数之前调用beginFxn函数。从Swi函数返回后立即调用endFxn函数

提供了readyFxn和beginFxn钩子,因为Swi可能已经被释放并准备就绪,但在高优先级线程完成时仍处于挂起状态。

这些函数有下列的签名:

Void readyFxn(Swi_Handle swi);
Void beginFxn(Swi_Handle swi);
Void endFxn(Swi_Handle swi);

Swi钩子例子

下面的示例应用程序使用两个Swi钩子集。这个例子演示了如何读写与每个钩子集关联的钩子上下文指针

下面时C代码:

/* ======== SwiHookExample.c ========
 * This example demonstrates basic Swi hook usage */
#include <xdc/std.h>
#include <xdc/runtime/Error.h>
#include <xdc/runtime/System.h>
#include <xdc/runtime/Timestamp.h>
#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/knl/Task.h>
#include <ti/sysbios/hal/Timer.h>
#include <ti/sysbios/knl/Swi.h>
Swi_Handle mySwi;
Int myHookSetId1, myHookSetId2;
/* HookSet 1 functions */
/* ======== myRegister1 ========
 * invoked during Swi module startup before main
 * for each HookSet */
Void myRegister1(Int hookSetId)
{
    
    
    System_printf("myRegister1: assigned hookSet Id = %d\n", hookSetId);
    myHookSetId1 = hookSetId;
}
/* ======== myCreate1 ========
 * invoked during Swi_create for dynamically created Swis */
Void myCreate1(Swi_Handle swi, Error_Block *eb)
{
    
    
    Ptr pEnv;
    pEnv = Swi_getHookContext(swi, myHookSetId1);
    /* pEnv should be 0 at this point. If not, there's a bug. */
    System_printf("myCreate1: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
    Swi_setHookContext(swi, myHookSetId1, (Ptr)0xdead1);
}
//* ======== myReady1 ========
 * invoked when Swi is posted */
Void myReady1(Swi_Handle swi)
{
    
    
    Ptr pEnv;
    pEnv = Swi_getHookContext(swi, myHookSetId1);
    System_printf("myReady1: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
    Swi_setHookContext(swi, myHookSetId1, (Ptr)0xbeef1);
}
/* ======== myBegin1 ========
 * invoked just before Swi func is run */
Void myBegin1(Swi_Handle swi)
{
    
    
    Ptr pEnv;
    pEnv = Swi_getHookContext(swi, myHookSetId1);
    System_printf("myBegin1: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
    Swi_setHookContext(swi, myHookSetId1, (Ptr)0xfeeb1);
}
/* ======== myEnd1 ========
 * invoked after Swi func returns */
Void myEnd1(Swi_Handle swi)
{
    
    
    Ptr pEnv;
    pEnv = Swi_getHookContext(swi, myHookSetId1);
    System_printf("myEnd1: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
    Swi_setHookContext(swi, myHookSetId1, (Ptr)0xc0de1);
}
/* ======== myDelete1 ========
 * invoked upon Swi deletion */
Void myDelete1(Swi_Handle swi)
{
    
    
    Ptr pEnv;
    pEnv = Swi_getHookContext(swi, myHookSetId1);
    System_printf("myDelete1: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
}
/* HookSet 2 functions */
/* ======== myRegister2 ========
 * invoked during Swi module startup before main
 * for each HookSet */
Void myRegister2(Int hookSetId)
{
    
    
    System_printf("myRegister2: assigned hookSet Id = %d\n", hookSetId);
    myHookSetId2 = hookSetId;
}
/* ======== myCreate2 ========
 * invoked during Swi_create for dynamically created Swis */
Void myCreate2(Swi_Handle swi, Error_Block *eb)
{
    
    
    Ptr pEnv;
    pEnv = Swi_getHookContext(swi, myHookSetId2);
    /* pEnv should be 0 at this point. If not, there's a bug. */
    System_printf("myCreate2: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
    Swi_setHookContext(swi, myHookSetId2, (Ptr)0xdead2);
}
/* ======== myReady2 ========
 * invoked when Swi is posted */
Void myReady2(Swi_Handle swi)
{
    
    
    Ptr pEnv;
    pEnv = Swi_getHookContext(swi, myHookSetId2);
    System_printf("myReady2: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
    Swi_setHookContext(swi, myHookSetId2, (Ptr)0xbeef2);
}
/* ======== myBegin2 ========
 * invoked just before Swi func is run */
Void myBegin2(Swi_Handle swi)
{
    
    
    Ptr pEnv;
    pEnv = Swi_getHookContext(swi, myHookSetId2);
    System_printf("myBegin2: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
    Swi_setHookContext(swi, myHookSetId2, (Ptr)0xfeeb2);
}
/* ======== myEnd2 ========
 * invoked after Swi func returns */
Void myEnd2(Swi_Handle swi)
{
    
    
    Ptr pEnv;
    pEnv = Swi_getHookContext(swi, myHookSetId2);
    System_printf("myEnd2: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
    Swi_setHookContext(swi, myHookSetId2, (Ptr)0xc0de2);
}
/* ======== myDelete2 ========
 * invoked upon Swi deletion */
Void myDelete2(Swi_Handle swi)
{
    
    
    Ptr pEnv;
    pEnv = Swi_getHookContext(swi, myHookSetId2);
    System_printf("myDelete2: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
}
/* ======== mySwiFunc ======== */
Void mySwiFunc(UArg arg0, UArg arg1)
{
    
    
    System_printf("Entering mySwi.\n");
}
/* ======== myTaskFunc ======== */
Void myTaskFunc(UArg arg0, UArg arg1)
{
    
    
    System_printf("Entering myTask.\n");
    System_printf("Posting mySwi.\n");
    Swi_post(mySwi);
    System_printf("Deleting mySwi.\n");
    Swi_delete(&mySwi);
    System_printf("myTask exiting ...\n");
}
/* ======== myIdleFunc ======== */
Void myIdleFunc()
{
    
    
    System_printf("Entering myIdleFunc().\n");
    System_exit(0);
}
/* ======== main ======== */
Int main(Int argc, Char* argv[])
{
    
    
    Error_Block eb;
    Error_init(&eb);
    System_printf("Starting SwiHookExample...\n");
    /* Create mySwi with default params 
     * to exercise Swi Hook Functions    */
    mySwi = Swi_create(mySwiFunc, NULL, &eb);
    if (mySwi == NULL) {
    
    
        System_abort("Swi create failed");
    }
    BIOS_start();
    return (0);
}

下面是这个例子的配置脚本:

/* pull in Timestamp to print time in hook functions */
xdc.useModule('xdc.runtime.Timestamp');
/* Disable Clock so that ours is the only Swi in the application */
var BIOS = xdc.useModule('ti.sysbios.BIOS');
BIOS.clockEnabled = false;
var Idle = xdc.useModule('ti.sysbios.knl.Idle');
Idle.addFunc('&myIdleFunc');
/* Create myTask with default task params */
var Task = xdc.useModule('ti.sysbios.knl.Task');
var taskParams = new Task.Params();
Program.global.myTask = Task.create('&myTaskFunc', taskParams);
/* Define and add two Swi Hook Sets */
var Swi = xdc.useModule("ti.sysbios.knl.Swi");
/* Hook Set 1 */
Swi.addHookSet({
    
    
      registerFxn: '&myRegister1',
      createFxn: '&myCreate1',
      readyFxn: '&myReady1',
      beginFxn: '&myBegin1',
      endFxn: '&myEnd1',
      deleteFxn: '&myDelete1'
});
/* Hook Set 2 */
Swi.addHookSet({
    
    
      registerFxn: '&myRegister2',
      createFxn: '&myCreate2',
      readyFxn: '&myReady2',
      beginFxn: '&myBegin2',
      endFxn: '&myEnd2',
      deleteFxn: '&myDelete2'
});

下面是应用程序的输出:

myRegister1: assigned hookSet Id = 0
myRegister2: assigned hookSet Id = 1
Starting SwiHookExample...
myCreate1: pEnv = 0x0, time = 315
myCreate2: pEnv = 0x0, time = 650
Entering myTask.
Posting mySwi.
myReady1: pEnv = 0xdead1, time = 1275
myReady2: pEnv = 0xdead2, time = 1678
myBegin1: pEnv = 0xbeef1, time = 2093
myBegin2: pEnv = 0xbeef2, time = 2496
Entering mySwi.
myEnd1: pEnv = 0xfeeb1, time = 3033
myEnd2: pEnv = 0xfeeb2, time = 3421
Deleting mySwi.
myDelete1: pEnv = 0xc0de1, time = 3957
myDelete2: pEnv = 0xc0de2, time = 4366
myTask exiting ...
Entering myIdleFunc().

任务

SYS/BIOS task对象是由Task模块管理的线程。Task的优先级高于Idle Loop,而低于硬件中断和软件中断

Task模块根据任务的优先级和任务当前执行的状态,动态调度和抢占任务。这确保了处理器总是被分配给准备运行的最高优先级的线程。task有多达32个优先级级别,默认级别为16。MSP430和C28x的最大优先级为16。最低优先级(0)保留运行Idle Loop

Task模块提供了一组操作task对象的函数。它们通过类型为Task_Handle的句柄来访问Task对象

内核为每个task对象维护一个处理器寄存器的副本。每个task都有自己运行时堆栈,用于存储局部变量以进一步嵌套函数调用

在一个程序中执行的所有任务共享一组通用的全局变量,这些全局变量是根据C函数定义的标准作用域规则访问的

创建任务

可以通过调用Task_create()动态地创建Task对象,也可以在静态配置中创建。动态创建的Task,可以在程序执行期间进行删除。

动态地创建和删除任务

可以通过调用函数Task_create()来生成SYS/BIOS任务,它的参数包括一个C函数地地址,在这个函数中新任务开始了它的执行。Task_create()返回的值的类型为Task_Handle的句柄,然后可以将其作为参数传递给其他Task函数

下面的C程序创建了一个任务:

Task_Params taskParams;
Task_Handle task0;
Error_Block eb;
Error_init(&eb);
/* Create 1 task with priority 15 */
Task_Params_init(&taskParams);
taskParams.stackSize = 512;
taskParams.priority = 15;
task0 = Task_create((Task_FuncPtr)hiPriTask, &taskParams, &eb);
if (task0 == NULL) {
    
    
    System_abort("Task create failed");
}

如果传递的是NULL,而不是指向Task_Params结构体的指针,则使用默认的参数集。"eb"是一个错误块,可以使用它来处理在创建Task对象期间可能发生的错误。

当Task被创建后,它就会成为活动的,如果他有更高的优先级,他就会抢占当前正在运行的任务。

Task对象和栈使用的内存可以通过调用Task_delete()来回收。Task_delete()从所有内部队列中删除任务,并释放任务对象和堆栈。

任务所持有的任何信号量或其他资源都不会被释放。删除包含此类资源的任务通常是应用程序设计错误,尽管不一定如此。在大多数情况下,应该在删除这些任务之前释放这些资源。只有在Task终止或非激活状态下删除Task才安全。

Void Task_delete(Task_Handle *task);

静态地创建任务

还可以在配置脚本中静态的创建任务。该配置允许可以为每个Task和Task管理器本身设置许多属性。

在运行时,静态创建的任务的行为与使用Task_create()创建的任务完全相同。不能使用Task_delete()函数来删除静态创建的任务

Task模块自动创建Task_Idle任务,并给他最低的task优先级(0)。当没有高优先级的Hwi、Swi或Task运行时,它运行为Idle对象定义的函数。

当配置的任务具有相同的优先级时,将按照在配置脚本中创建的顺序进行调度。Task最多可以有32个优先级级别,默认为16。MSP430和C28x的最大优先级为16。最高级别是定义的优先级数减1,最低级别是0。优先级为0是为系统空闲任务保留。不能通过设置order属性对单个优先级级别内的任务进行排序

如果想让一个任务在刚刚开始的时候是不活跃的,那么将它的优先级设置为-1。在运行时提高这些任务的优先级之前,不会被调度运行。

任务执行状态和调度

每个Task对象总是下列四种可能的执行状态之一:

  • Task_Mode_RUNNING,这意味着该任务是在系统处理器上实际执行的任务。
  • Task_Mode_READY, 这意味着根据处理器可用性调度任务的执行。
  • Task_Mode_BLOCKED, 这意味着任务在系统种发生的特定事件之前不能执行。
  • Task_Mode_TERMINATED, 这意味着任务被"终止",不再执行。
  • Task_Mode_INACTIVE, 这意味着该任务的优先级为-1,处于pre-Ready状态。这个优先级可以在创建任务时设置,或者在运行时调用Task_setPri() API。

Task根据应用程序分配的优先级安排执行,不能有超过一个正在运行的任务。通常,没有就绪任务的优先级高于当前正在运行的任务的优先级,因为Task抢占了正在运行的任务,而优先级更高的就绪任务。与许多分时操作系统不同的是,当有更高优先级的任务准备运行时,SYS/BIOS会立即抢占当前任务

最大的优先级是Task_numPriorities-1(默认=15,最大值=31)。最小的优先级是1。如果优先级小于0,该任务将被禁止进一步执行,直到它的优先级稍后会被另一个任务提高。如果优先级为Task_numPriorities-1,则该任务不能被其他任务抢占。最高优先级的任务仍然可以调用Semaphore_pend()、Task_sleep()或其他阻塞来允许低优先级的任务运行。一个Task的优先级可以在运行时通过调用Task_setPr()来改变

在程序的运行过程中,每个任务的执行模式可能会因许多原因而改变

下图展示了执行模式如何改变
在这里插入图片描述
Task、Semaphore、Event和Mailbox模块中的函数改变任务对象的执行状态:阻塞或终止当前正在运行的任务,准备先前挂起的任务,重新安排当前任务

有一个任务的执行模式是Task_Mode_RUNNING。如果所有的程序任务都被阻塞,并且没有Hwi或Swi正在运行,则Task会执行优先级低于系统中其他所有任务的Task_idle任务。当一个任务被Hwi或Swi抢占时,Task_stat()为该任务返回的任务模式仍然是Task_Mode_RUNNING,因为任务将在抢占结束时运行。

当Task_Mode_RUNNING任务转换到其他三种状态中的任何一种时,控制切换到准备运行的最高优先级任务(即,其模式为Task_Mode_READY)。Task_Mode_RUNNING任务以以下方式转换到其他模式之一:

  • 通过调用Task_exit(),正在运行的任务变成Task_MODE_TERMINATED,当任务从顶级函数返回时,会自动调用Task_exit()。在所有任务都返回后,Task管理器通过调用状态码为0的System_exit()来终止程序的执行。
  • 当正在运行的任务调用导致当前任务挂起执行的函数(Semaphore_pend()或Task_sleep())时,它将变成Task_Mode_BLOCKED;当任务正在执行某些I/O操作、等待某些共享资源可用或空闲时,它们就会进入这种状态。
  • 正在运行的任务变成了Task_Mode_READY,并且在其他一些优先级更高的任务准备运行时被抢占。如果当前任务的优先级不再是系统中最高的,则Task_setPri()会导致这种类型转换。一个任务也可以使用Task_yield()来让步给其他具有相同优先级的任务。让步的任务变成了准备运行

当前处于Task_Mode_BLOCKED状态的任务将转变为就绪状态,以响应特定的事件:I/O操作的完成、共享资源的可用性、指定时间段的结束等。通过变成Task_Mode_READY,这个任务会根据它的优先级被调度执行;当然,如果该任务的优先级高于当前正在执行的任务,则该任务会立即转换为运行状态。不要以先到先得的原则来安排同等优先的任务。

任务堆栈

内核为每个Task对象维护一个处理器的寄存器版本。每个Task都有自己的运行时堆栈,用于存储局部变量以及进一步嵌套函数调用

当动态或者静态创建Task对象时,可以为每个Task对象分别指定堆栈大小

每个task的栈足够大,以处理其正常的函数调用和两个完整的中断Hwi上下文

下表中的"最大堆栈消耗"列显示了吸收量最坏情况中断嵌套所需的堆栈空间量。这些数字表示两个完整的Hwi中断上下文,加上任务调度程序为其局部变量所使用的空间。附加的嵌套中断上下文被推到公共系统堆栈上。

注意,当配置内核时,没有配置Task Hooks(Task.minimizeLatency=false和BIOS.logsEnabled=false),除了任务调度程序的局部变量外,每个任务堆栈必须只支持一个完整的Hwi中断上下文。这个配置等于"Minimum Stack Consumed"

在这里插入图片描述
当Task被抢占时,一个task堆栈可能需要包含两个中断Hwi上下文(如果Task被一个Hwi抢占)或者一个中断Hwi上下文和一个Task抢占上下文(如果Task被高优先级Task抢占)。因为Hwi的上下文比Task的上下文大,所以给出的数字是两个Hwi上下文。如果Task阻塞,只有C函数必须保存的寄存器才会保存到任务堆栈中。

另一种找到正确堆栈大小的方法是使堆栈大小变大,然后使用Code Composer Studio软件找到实际使用的堆栈大小

测试堆栈溢出

当一个task使用的内存超过其堆栈分配的内存时,它可以写入另一个task或data使用的内存区域。这将导致不可预测的和潜在的致命后果。因此,一种检查堆栈溢出的方法是很有用的。

默认情况下,Task模块检查每个Task交换机上的Task堆栈是否溢出。为了提高Task交换延迟,可以禁用这个功能,将Task.checkStackFlag属性设置为false。

函数Task_stat()可以用来查看堆栈的大小。Task_stat()返回的结构既包含其堆栈的大小,也包含其堆栈上使用过的最大MAUs数量,所以下面的代码可以用来警告堆栈几乎已满

Task_Stat statbuf;               /* declare buffer */
Task_stat(Task_self(), &statbuf); /* call func to get status */
if (statbuf.used > (statbuf.stackSize * 9 / 10)) {
    
    
   System_printf("Over 90% of task's stack is in use.\n")
}

可以使用运行时对象视图(ROV)来检查运行时Task堆栈的使用情况

Tasks钩子

Task模块支持以下一组的Hook函数

  • Register. 在运行时初始化任何静态创建的Task请求之前调用的函数。register钩子在启动时在main()和中断被启用之前被调用
  • Create. 创建Task时调用的函数。这包括静态创建的Task和使用Task_create()或Task_construct()动态创建的Task。Create钩子在Task_disable/enable块的外部调用,并且在Task被添加到就绪列表之前调用
  • Ready. 当Task准备运行时调用的函数。在启用中断的Task_disable/enable块中调用ready钩子
  • Switch. 在task切换之前调用的函数。‘prev’和’next’ task句柄被传递给Switch钩子。‘prev’被设置为NULL,用于在SYS/BIOS启动过程中发生的初始任务切换。Switch钩子在启用中断的Task_disable/enable块中调用
  • Exit. 当task退出时使用Task_exit()调用的函数。退出钩子被传递给退出任务的句柄。exit钩子在Task_disable/enable块外部调用,并且在任务从内核列表中删除之前调用
  • Delete. 当运行时使用Task_delete()删除任务时调用的函数

下面的Hookset结构类型定义了封装了Task模块支持的钩子函数:

typedef struct Task_HookSet {
    Void (*registerFxn)(Int);                 /* Register Hook */
    Void (*createFxn)(Handle, Error.Block *); /* Create Hook */
    Void (*readyFxn)(Handle);                 /* Ready Hook */
    Void (*switchFxn)(Handle, Handle);        /* Switch Hook */
    Void (*exitFxn)(Handle);                  /* Exit Hook */
    Void (*deleteFxn)(Handle);                /* Delete Hook */
};

当定义了多个钩子集时,将按钩子ID顺序调用公共类型的单个钩子函数

Task钩子函数只能静态配置

Register函数

Register函数允许钩子集存储其对应的钩子ID。这个ID可以传递给Task_setHookContext()和Task_getHookContext()来设置或获取钩子特定的上下文。如果钩子需要实现使用Task_setHookContext()或Task_getHookContext(),则必须指定Register函数

registerFxn函数在启动中断之前,在系统初始化期间被调用

Register函数有下面的签名:

Void registerFxn(Int id);

Create和Delete函数

当Task创建或删除时,就会调用Create和Delete函数。Create函数被传递一个Error_Block,该Error_Block将被传递给需要额外上下文存储空间的应用程序Memory_alloc()

在启用中断的情况下调用createFxn和deleteFxn函数(除非在引导时或从main()调用)

这些函数有下列的签名:

Void createFxn(Task_Handle task, Error_Block *eb);
Void deleteFxn(Task_Handle task);

Switch函数

如果指定了Switch函数,则在将新任务切换到之前调用该函数。当中断开启时调用switch函数

这个函数可以用于保存/恢复额外的任务上下文(例如,外部硬件寄存器)、检查任务堆栈移除以及监视每个任务使用的时间等目的

switchFxn有下列的签名:

Void switchFxn(Task_Handle prev, Task_Handle next);

Ready函数

如果指定了就绪函数,则在任务准备好运行时调用该函数。在启用中断的情况下调用Ready函数(除非在引导时或从main()调用)

readyFxn有下列的签名:

Void readyFxn(Task_Handle task);

Exit函数

如果指定了exit函数,则当任务退出时(通过调用Task_exit()或当任务从其main函数返回时)调用该函数。exit在启用中断时被调用

exitFxn有下列的签名:

Void exitFxn(Task_Handle task);

Task钩子例子

下面的示例应用程序使用一个Task钩子集。这个例子演示了如何读写与每个钩子集关联的钩子上下文指针

下面是C程序的示例:

/* ======== TaskHookExample.c ========
 * This example demonstrates basic task hook processing
 * operation for dynamically created tasks. */
#include <xdc/std.h>
#include <xdc/runtime/Error.h>
#include <xdc/runtime/Memory.h>
#include <xdc/runtime/System.h>
#include <xdc/runtime/Types.h>
#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/knl/Task.h>
Task_Handle myTsk0, myTsk1, myTsk2;
Int myHookSetId, myHookSetId2;
/* HookSet functions */
/* ======== myRegister ========
 * invoked during Swi module startup before main()
 * for each HookSet */
Void myRegister(Int hookSetId)
{
    
    
    System_printf("myRegister: assigned HookSet Id = %d\n", hookSetId);
    myHookSetId = hookSetId;
}
/* ======== myCreate ========
 * invoked during Task_create for dynamically 
 * created Tasks */
Void myCreate(Task_Handle task, Error_Block *eb)
{
    
    
    String name;
    Ptr pEnv;
    name = Task_Handle_name(task);
    pEnv = Task_getHookContext(task, myHookSetId);
        System_printf("myCreate: task name = '%s', pEnv = 0x%x\n", name, pEnv);
    Task_setHookContext(task, myHookSetId, (Ptr)0xdead);
}
/* ======== myReady ========
 * invoked when Task is made ready to run */
Void myReady(Task_Handle task)
{
    
    
    String name;
    Ptr pEnv;
    name = Task_Handle_name(task);
    pEnv = Task_getHookContext(task, myHookSetId);
    System_printf("myReady: task name = '%s', pEnv = 0x%x\n", name, pEnv);
    Task_setHookContext(task, myHookSetId, (Ptr)0xc0de);
}
/* ======== mySwitch ========
 * invoked whenever a Task switch occurs/is made ready to run */
Void mySwitch(Task_Handle prev, Task_Handle next)
{
    
    
    String prevName;
    String nextName;
    Ptr pPrevEnv;
    Ptr pNextEnv;
    if (prev == NULL) {
    
    
       System_printf("mySwitch: ignoring dummy 1st prev Task\n");
    }
    else {
    
    
       prevName = Task_Handle_name(prev);
       pPrevEnv = Task_getHookContext(prev, myHookSetId);
       System_printf("mySwitch: prev name = '%s', pPrevEnv = 0x%x\n", 
               prevName, pPrevEnv);
       Task_setHookContext(prev, myHookSetId, (Ptr)0xcafec0de);
    }
    nextName = Task_Handle_name(next);
    pNextEnv = Task_getHookContext(next, myHookSetId);
    System_printf("          next name = '%s', pNextEnv = 0x%x\n",
                  nextName, pNextEnv);
    Task_setHookContext(next, myHookSetId, (Ptr)0xc001c0de);
}
/* ======== myExit ========
 * invoked whenever a Task calls Task_exit() or falls through
 * the bottom of its task function. */
Void myExit(Task_Handle task)
{
    
    
    Task_Handle curTask = task;
    String name;
    Ptr pEnv;
    name = Task_Handle_name(curTask);
    pEnv = Task_getHookContext(curTask, myHookSetId);
    System_printf("myExit: curTask name = '%s', pEnv = 0x%x\n", name, pEnv);
    Task_setHookContext(curTask, myHookSetId, (Ptr)0xdeadbeef);
}
/* ======== myDelete ========
 * invoked upon Task deletion */
Void myDelete(Task_Handle task)
{
    
    
    String name;
    Ptr pEnv;
    name = Task_Handle_name(task);
    pEnv = Task_getHookContext(task, myHookSetId);
    System_printf("myDelete: task name = '%s', pEnv = 0x%x\n", name, pEnv);
}
/* Define 3 identical tasks */
Void myTsk0Func(UArg arg0, UArg arg1)
{
    
    
    System_printf("myTsk0 Entering\n");
    System_printf("myTsk0 Calling Task_yield\n");
    Task_yield();
    System_printf("myTsk0 Exiting\n");
}
Void myTsk1Func(UArg arg0, UArg arg1)
{
    
    
    System_printf("myTsk1 Entering\n");
    System_printf("myTsk1 Calling Task_yield\n");
    Task_yield();
    System_printf("myTsk1 Exiting\n");
}
Void myTsk2Func(UArg arg0, UArg arg1)
{
    
    
    System_printf("myTsk2 Entering\n");
    System_printf("myTsk2 Calling Task_yield\n");
    Task_yield();
    System_printf("myTsk2 Exiting\n");
}
/* ======== main ======== */
Int main(Int argc, Char* argv[])
{
    
    
    Task_Params params;
    Error_Block eb;
    Error_init(&eb);
    Task_Params_init(&params);
 
    params.instance->name = "myTsk0";
    myTsk0 = Task_create(myTsk0Func, &params, &eb);
    if (myTsk0 == NULL) {
    
    
        System_abort("myTsk0 create failed");
    }
    params.instance->name = "myTsk1";
    myTsk1 = Task_create(myTsk1Func, &params, &eb);
    if (myTsk1 == NULL) {
    
    
        System_abort("myTsk1 create failed");
    }
    params.instance->name = "myTsk2";
    myTsk2 = Task_create(myTsk2Func, &params, &eb);
    if (myTsk2 == NULL) {
    
    
        System_abort("myTsk2 create failed");
    }
    BIOS_start();
    return (0);
}
/* ======== myIdleFunc ======== */
Void myIdleFunc()
{
    
    
    System_printf("Entering idleFunc().\n");
    Task_delete(&myTsk0);
    Task_delete(&myTsk1);
    Task_delete(&myTsk2);
    System_exit(0);
}

配置脚本如下:

/* Lots of System_printf() output requires a bigger bufSize */
SysMin = xdc.useModule('xdc.runtime.SysMin');
SysMin.bufSize = 4096;
var Idle = xdc.useModule('ti.sysbios.knl.Idle');
Idle.addFunc('&myIdleFunc');
var Task = xdc.useModule('ti.sysbios.knl.Task');
/* Enable instance names */
Task.common$.namedInstance = true;
/* Define and add one Task Hook Set */
Task.addHookSet({
    registerFxn: '&myRegister',
    createFxn: '&myCreate',
    readyFxn: '&myReady',
    switchFxn: '&mySwitch',
    exitFxn: '&myExit',
    deleteFxn: '&myDelete',
});

程序输出如下:

myRegister: assigned HookSet Id = 0
myCreate: task name = 'ti.sysbios.knl.Task.IdleTask', pEnv = 0x0
myReady: task name = 'ti.sysbios.knl.Task.IdleTask', pEnv = 0xdead
myCreate: task name = 'myTsk0', pEnv = 0x0
myReady: task name = 'myTsk0', pEnv = 0xdead
myCreate: task name = 'myTsk1', pEnv = 0x0
myReady: task name = 'myTsk1', pEnv = 0xdead
myCreate: task name = 'myTsk2', pEnv = 0x0
myReady: task name = 'myTsk2', pEnv = 0xdead
mySwitch: ignoring dummy 1st prev Task
          next name = 'myTsk0', pNextEnv = 0xc0de
myTsk0 Entering
myTsk0 Calling Task_yield
mySwitch: prev name = 'myTsk0', pPrevEnv = 0xc001c0de
          next name = 'myTsk1', pNextEnv = 0xc0de
myTsk1 Entering
myTsk1 Calling Task_yield
mySwitch: prev name = 'myTsk1', pPrevEnv = 0xc001c0de
          next name = 'myTsk2', pNextEnv = 0xc0de
myTsk2 Entering
myTsk2 Calling Task_yield
mySwitch: prev name = 'myTsk2', pPrevEnv = 0xc001c0de
          next name = 'myTsk0', pNextEnv = 0xcafec0de
myTsk0 Exiting
myExit: curTask name = 'myTsk0', pEnv = 0xc001c0de
mySwitch: prev name = 'myTsk0', pPrevEnv = 0xdeadbeef
          next name = 'myTsk1', pNextEnv = 0xcafec0de
myTsk1 Exiting
myExit: curTask name = 'myTsk1', pEnv = 0xc001c0de
mySwitch: prev name = 'myTsk1', pPrevEnv = 0xdeadbeef
          next name = 'myTsk2', pNextEnv = 0xcafec0de
myTsk2 Exiting
myExit: curTask name = 'myTsk2', pEnv = 0xc001c0de
mySwitch: prev name = 'myTsk2', pPrevEnv = 0xdeadbeef
          next name = 'ti.sysbios.knl.Task.IdleTask', pNextEnv = 0xc0de
Entering idleFunc().
myDelete: task name = 'myTsk0', pEnv = 0xcafec0de
myDelete: task name = 'myTsk1', pEnv = 0xcafec0de
myDelete: task name = 'myTsk2', pEnv = 0xcafec0de

用于时间片调度的任务让步

下面例子演示了一个用户可以管理的时间切片调度模型。该模型是抢占的,不需要任务的任何合作(即代码)。这些任务的编程就好像它们是唯一正在运行的线程一样。尽管在任何给定的应用程序中都可以存在不同的优先级的SYS/BIOS任务,但时间片切片模型只适用于同等优先级的任务

在本例中,一个周期性的Clock对象被配置为运行一个简单的函数,该函数每4个时钟滴答一次就调用Task_yield()函数。另一个周期性的Clock对象是运行一个简单的函数,该函数每16毫秒调用一次Semaphore_post()函数

下面是代码:

/*
 * ======== slice.c ========
 * This example utilizes time-slice scheduling among three
 * tasks of equal priority. A fourth task of higher
 * priority periodically preempts execution.
 *
 * A periodic Clock object drives the time-slice scheduling. 
 * Every 4 milliseconds, the Clock object calls Task_yield()
 * which forces the current task to relinquish access to
 * to the CPU.
 *
 * Because a task is always ready to run, this program
 * does not spend time in the idle loop. Calls to Idle_run()
 * are added to give time to the Idle loop functions 
 * occasionally. The call to Idle_run() is within a 
 * Task_disable(), Task_restore() block because the call 
 * to Idle_run() is not reentrant.
 */
#include <xdc/std.h>
#include <xdc/runtime/System.h>
#include <xdc/runtime/Error.h>
#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/knl/Semaphore.h>
#include <ti/sysbios/knl/Clock.h>
#include <ti/sysbios/knl/Clock.h>
#include <ti/sysbios/knl/Idle.h>
#include <ti/sysbios/knl/Task.h>
#include <xdc/cfg/global.h>
Void hiPriTask(UArg arg0, UArg arg1);
Void task(UArg arg0, UArg arg1);
Void clockHandler1(UArg arg);
Void clockHandler2(UArg arg);
Semaphore_Handle sem;
/* ======== main ======== */
Void main()
{
    
    
    Task_Params taskParams;
    Task_Handle myTsk0, myTski;
    Clock_Params clockParams;
    Clock_Handle myClk0, myClk1;
    Error_Block eb;
    UInt i;
    System_printf("Slice example started!\n");
    Error_init(&eb);
    /* Create 1 task with priority 15 */
    Task_Params_init(&taskParams);
    taskParams.stackSize = 512; 
                   // Note: Larger stack needed for some targets, including ’C6748
    taskParams.priority = 15;
    myTsk0 = Task_create((Task_FuncPtr)hiPriTask, &taskParams, &eb);
    if (myTsk0 == NULL) {
    
    
        System_abort("hiPriTask create failed");
    }
    /* Create 3 tasks with priority 1 */
    /* re-uses taskParams */
    taskParams.priority = 1;
    for (i = 0; i < 3; i++) {
    
    
        taskParams.arg0 = i;
        myTski = Task_create((Task_FuncPtr)task, &taskParams, &eb);
        if (myTski == NULL) {
    
    
            System_abort("LoPri Task %d create failed", i);
        }
    }
    /* 
     * Create clock that calls Task_yield() every 4 Clock ticks 
     */
    Clock_Params_init(&clockParams);
    clockParams.period = 4;/* every 4 Clock ticks */
    clockParams.startFlag = TRUE;/* start immediately */
    myClk0 = Clock_create((Clock_FuncPtr)clockHandler1, 4, &clockParams, &eb);
    if (myClk0 == NULL) {
    
    
        System_abort("Clock0 create failed");
    }
    /* 
     * Create clock that calls Semaphore_post() every 
     * 16 Clock ticks 
     */
    clockParams.period = 16;/* every 16 Clock ticks */
    clockParams.startFlag = TRUE;/* start immediately */
    myClk1 = Clock_create((Clock_FuncPtr)clockHandler2, 16, &clockParams, &eb);
    if (myClk1 == NULL) {
    
    
        System_abort("Clock1 create failed");
    }
        /* 
     * Create semaphore with initial count = 0 and default params
     */
    sem = Semaphore_create(0, NULL, &eb);
    if (sem == NULL) {
    
    
        System_abort("Semaphore create failed");
    }
    /* Start SYS/BIOS */
    BIOS_start();
}
/* ======== clockHandler1 ======== */
Void clockHandler1(UArg arg)
{
    
    
    /* Call Task_yield every 4 ms */
    Task_yield();
}
/* ======== clockHandler2 ======== */
Void clockHandler2(UArg arg)
{
    
    
    /* Call Semaphore_post every 16 ms */
    Semaphore_post(sem);
}
/* ======== task ======== */
Void task(UArg arg0, UArg arg1)
{
    
    
    Int time;
    Int prevtime = -1;
    UInt taskKey;
    /* While loop simulates work load of time-sharing tasks */
    while (1) {
    
    
        time = Clock_getTicks();
        /* print time once per clock tick */
        if (time >= prevtime + 1) {
    
    
            prevtime = time;         
            System_printf("Task %d: time is %d\n", (Int)arg0, time);
        }
        /* check for rollover */
        if (prevtime > time) {
    
    
            prevtime = time; 
        }
       	/* Process the Idle Loop functions */
        taskKey = Task_disable();      
        Idle_run();      
        Task_restore(taskKey);
    }
}
/* ======== hiPriTask ======== */
Void hiPriTask(UArg arg0, UArg arg1)
{
    
    
    static Int numTimes = 0;
    while (1) {
    
    
        System_printf("hiPriTask here\n");
        if (++numTimes < 3) {
    
    
            Semaphore_pend(sem, BIOS_WAIT_FOREVER);
        }
        else {
    
    
            System_printf("Slice example ending.\n");
            System_exit(0);
        }
    }
}

例子的输出情况:

Slice example started! 
hiPriTask here 
Task 0: time is 0 
Task 0: time is 1 
Task 0: time is 2 
Task 0: time is 3 
Task 1: time is 4 
Task 1: time is 5 
Task 1: time is 6 
Task 1: time is 7 
Task 2: time is 8 
Task 2: time is 9 
Task 2: time is 10 
Task 2: time is 11 
Task 0: time is 12 
Task 0: time is 13 
Task 0: time is 14 
Task 0: time is 15 
hiPriTask here 
Task 1: time is 16 
Task 1: time is 17 
Task 1: time is 18 
Task 1: time is 19 
Task 2: time is 20 
Task 2: time is 21 
Task 2: time is 22 
Task 2: time is 23 
Task 0: time is 24 
Task 0: time is 25 
Task 0: time is 26 
Task 0: time is 27 
Task 1: time is 28 
Task 1: time is 29 
Task 1: time is 30 
Task 1: time is 31 
hiPriTask here 
Slice example ending.

Idle Loop

Idle Loop是SYS/BIOS的后台线程,当没有Hwi、Swi或Task运行时,它会持续运行。任务线程都可以在任何时候抢占Idle Loop。

Idle管理器允许插入在Idle循环中的执行函数。Idle Loop运行用户配置的Idle功能。Idle_Loop每次调用与每个Idle对象相关联的哈数,然后在一个连续循环中重新开始。

所有的空闲线程都以相同的优先级顺序运行。函数的调用顺序与它们的创建顺序相同。Idle函数必须运行到完成后,下一个Idle函数才能开始运行。当最后一个Idle函数完成时,Idle Loop再次启动第一个空闲函数

Idle Loop函数通常用于轮询不(或不能)生成中断、监控系统状态或执行其他后台活动的非实时设备。

Idle Loop是SYS/BIOS应用程序中优先级最低的线程。Idle Loop函数只在没有Hwis、Swis、Task运行时运行

CPU负载和线程负载在Idle循环函数中计算(目标和主机之间的数据传输由低优先级任务处理)

如果配置Task.enableIdle Task为false,则不创建Idle任务,并且不运行Idle函数。如果你想要一个函数在没有其他线程运行时运行,可以使用Task.allBlockedFunc指定这样一个函数

如果希望在不创建专用Idle task的情况下不运行Idle Loop,可以禁用Task.enableTask和配置Task.allBlockedFunc如下,这些语句导致使用最后一个Task等待的堆栈运行Idle函数

Task.enableIdleTask = false;
Task.allBlockedFunc = Idle.run;

使用Hwi,Swi,和Task线程的用例

这个例子描述了SYS/BIOS时钟模块设计的一个程式化版本。它使用Hwi、Swi和Task线程的组合。

周期性定时器中断释放一个处理时钟对象列表的Swi。时钟对象列表中的每个条目都有自己的周期和时钟功能。当对象的周期过期时,将调用Clock函数并重新启动周期。

由于可以放置在列表中的时钟对象数量没有限制,而且无法确定每个时钟函数开销,所以用于服务所有时钟对象的时间长度是不确定的。因此,在计时器的Hwi线程中服务对象是不切实际的。与使用Task相比,为这个函数使用Swi是一个相对轻量级的解决方案。

案例的C代码:

/*
 * ======== HwiSwiTaskExample.c ========
 */
 
#include <xdc/std.h>
#include <xdc/runtime/System.h>
#include <xdc/runtime/Error.h>
#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/hal/Timer.h>
#include <ti/sysbios/knl/Semaphore.h>
#include <ti/sysbios/knl/Swi.h>
#include <ti/sysbios/knl/Task.h>
#include <ti/sysbios/knl/Queue.h>
#include <xdc/cfg/global.h>
typedef struct {
    
    
    Queue_Elem elem;
    UInt32 timeout;
    UInt32 period;
    Void (*fxn)(UArg);
    UArg arg;
} Clock_Object;
Clock_Object clk1, clk2;
Timer_Handle timer;
Semaphore_Handle sem;
Swi_Handle swi;
Task_Handle task;
Queue_Handle clockQueue;
/* Here on Timer interrupt */
Void hwiFxn(UArg arg)
{
    
    
    Swi_post(swi);
}
/* Swi thread to handle Timer interrupt */
Void swiFxn(UArg arg1, UArg arg2)
{
    
    
    Queue_Elem *elem;
    Clock_Object *obj;
    
    /* point to first clock object in the clockQueue */
    elem = Queue_next((Queue_Elem *)clockQueue);
    /* service all the Clock Objects in the clockQueue */
    while (elem != (Queue_Elem *)clockQueue) {
    
    
        obj = (Clock_Object *)elem;
        
        /* decrement the timeout counter */
        obj->timeout -= 1;
        
        /* if period has expired, refresh the timeout
         * value and invoke the clock func */
        if (obj->timeout == 0) {
    
    
            obj->timeout = obj->period;
            (obj->fxn)(obj->arg);
        }
        
        /* advance to next clock object in clockQueue */
        elem = Queue_next(elem);
    }
}
/* Task thread pends on Semaphore posted by Clock thread */
Void taskFxn(UArg arg1, UArg arg2)
{
    
    
    System_printf("In taskFxn pending on Sempahore.\n");
    Semaphore_pend(sem, BIOS_WAIT_FOREVER);
    System_printf("In taskFxn returned from Sempahore.\n");
    System_exit(0);
}
/* First Clock function, invoked every 5 timer interrupts */
Void clk1Fxn(UArg arg)
{
    
    
    System_printf("In clk1Fxn, arg = %d.\n", arg);
    clk1.arg++;
}
/* Second Clock function, invoked every 20 timer interrupts */
Void clk2Fxn(UArg sem)
{
    
    
    System_printf("In clk2Fxn, posting Semaphore.\n");
    Semaphore_post((Semaphore_Object *)sem);
}
/* main() */
Int main(Int argc, char* argv[])
{
    
    
    Timer_Params timerParams;
    Task_Params taskParams;
    Error_Block eb;
    System_printf("Starting HwiSwiTask example.\n");
    
    Error_init(&eb);
    Timer_Params_init(&timerParams);
    Task_Params_init(&taskParams);
    
    /* Create a Swi with default priority (15).
     * Swi handler is 'swiFxn' which runs as a Swi thread. */
    swi = Swi_create(swiFxn, NULL, &eb);
    if (swi == NULL) {
    
    
        System_abort("Swi create failed");
    }
    /* Create a Task with priority 3.
     * Task function is 'taskFxn' which runs as a Task thread. */
    taskParams.priority = 3;
    task = Task_create(taskFxn, &taskParams, &eb);
    if (task == NULL) {
    
    
        System_abort("Task create failed");
    }
    /* Create a binary Semaphore for example task to pend on */
    sem = Semaphore_create(0, NULL, &eb);
    if (sem == NULL) {
    
    
        System_abort("Semaphore create failed");
    }
    
    /* Create a Queue to hold the Clock Objects on */
    clockQueue = Queue_create(NULL, &eb);
    if (clockQueue == NULL) {
    
    
        System_abort("Queue create failed");
    }
    
    /* setup clk1 to go off every 5 timer interrupts. */
    clk1.fxn = clk1Fxn;
    clk1.period = 5;
    clk1.timeout = 5;
    clk1.arg = 1;
    /* add the Clock object to the clockQueue */
    Queue_put(clockQueue, &clk1.elem);
    
    /* setup clk2 to go off every 20 timer interrupts. */
    clk2.fxn = clk2Fxn;
    clk2.period = 20;
    clk2.timeout = 20;
    clk2.arg = (UArg)sem;
    /* add the Clock object to the clockQueue */
    Queue_put(clockQueue, &clk2.elem);
    /* Configure a periodic interrupt using any available Timer
     * with a 1000 microsecond (1ms) interrupt period.
     *
     * The Timer interrupt will be handled by 'hwiFxn' which
     * will run as a Hwi thread.
     */
    timerParams.period = 1000;
    timer = Timer_create(Timer_ANY, hwiFxn, &timerParams, &eb);
    if (timer == NULL) {
    
    
        System_abort("Timer create failed");
    }
    
    BIOS_start();
    
    return(0);
}

配置脚本如下:

/* ======== HwiSwiTaskExample.cfg ======== */
var Defaults = xdc.useModule('xdc.runtime.Defaults');
var Diags = xdc.useModule('xdc.runtime.Diags');
var Error = xdc.useModule('xdc.runtime.Error');
var Log = xdc.useModule('xdc.runtime.Log');
var LoggerBuf = xdc.useModule('xdc.runtime.LoggerBuf');
var Main = xdc.useModule('xdc.runtime.Main');
var Memory = xdc.useModule('xdc.runtime.Memory')
var SysMin = xdc.useModule('xdc.runtime.SysMin');
var System = xdc.useModule('xdc.runtime.System');
var Text = xdc.useModule('xdc.runtime.Text');
var BIOS = xdc.useModule('ti.sysbios.BIOS');
var Clock = xdc.useModule('ti.sysbios.knl.Clock');
var Task = xdc.useModule('ti.sysbios.knl.Task');
var Semaphore = xdc.useModule('ti.sysbios.knl.Semaphore');
var Queue = xdc.useModule('ti.sysbios.knl.Queue');
var Hwi = xdc.useModule('ti.sysbios.hal.Hwi');
var HeapMem = xdc.useModule('ti.sysbios.heaps.HeapMem');
Program.argSize = 0x0;
System.maxAtexitHandlers = 4;       
BIOS.heapSize = 0x2000;
/* System stack size (used by ISRs and Swis) */
Program.stack = 0x1000;
/* Circular buffer size for System_printf() */
SysMin.bufSize = 0x400;
/* Create and install logger for the whole system */
var loggerBufParams = new LoggerBuf.Params();
loggerBufParams.numEntries = 32;
var logger0 = LoggerBuf.create(loggerBufParams);
Defaults.common$.logger = logger0;
Main.common$.diags_INFO = Diags.ALWAYS_ON;
System.SupportProxy = SysMin;
BIOS.libType = BIOS.LibType_Custom;

程序的输出如下:

Starting HwiSwiTask example.
In taskFxn pending on Semaphore.
In clk1Fxn, arg = 1.
In clk1Fxn, arg = 2.
In clk1Fxn, arg = 3.
In clk1Fxn, arg = 4.
In clk2Fxn, posting Semaphore.
In taskFxn returned from Semaphore

参考文献:

  1. 《TI-RTOS Kernel (SYS/BIOS) User’s Guide》

猜你喜欢

转载自blog.csdn.net/Xiao_Jie123/article/details/120490806