线程云集(四)——嵌入式任务线程(xwr1843)

 

Task有5种状态分别是运行running, 就绪Ready,阻塞blocked, terminated终止,inactive停用;前面的状态好理解,停用表示将优先级设为-1,该task不会工作直到其它task将其优先级上升。并发是将处理器从一项任务切换到另一项任务实现的

通过调用某些Task函数以及由信号量或事件模块之类的其他模块提供的函数来暂停当前任务的执行。当前任务还可以终止其自身的执行。 无论哪种情况,处理器都将切换到可以运行的最高优先级任务。

xwr1843存在3个线程:

  • Hwi: API is callable from a Hwi thread.
  • Swi: API is callable from a Swi thread.
  • Task: API is callable from a Task thread.
  • Main: API is callable during any of these phases:
    • In your module startup after this module is started (e.g. Task_Module_startupDone() returns TRUE).
    • During xdc.runtime.Startup.lastFxns.
    • During main().
    • During BIOS.startupFxns.
  • Startup: API is callable during any of these phases:
    • During xdc.runtime.Startup.firstFxns.
    • In your module startup before this module is started (e.g. Task_Module_startupDone() returns FALSE).

任务优先级

  1. 当前正在运行的任务的优先级永远不会低于任何就绪任务的优先级
  2. 相同优先级任务规则:先进先服务
  3. 反过来,只要存在一些优先级更高的就绪任务,就会抢占正在运行的任务并重新安排其执行时间。

任务抢占

创建任务时,将为其提供自己的运行时堆栈,用于存储局部变量以及进一步嵌套函数调用每个堆栈必须足够大以处理正常的子例程调用和一个任务抢占上下文,它是当一个任务由于中断线程为准备就绪的更高优先级的任务而抢占另一个任务时保存的上下文。某些系统配置设置将导致任务堆栈需要足够大以吸收两个中断上下文。

删除任务

可以使用delete API删除任何不在Task_Mode_RUNNING状态下的动态创建的任务(即不是当前正在运行的任务)。

Task_delete()从所有内部队列中删除任务,并调用Memory_free()来释放任务对象及其堆栈.Memory_free()必须在执行操作之前获取对内存的锁定。如果另一个任务已经对该内存进行了锁定,则执行删除操作的线程将被阻塞直到内存被解锁。

注:

  1. Task_delete()的范围仅限于释放Task对象本身,释放任务的堆栈内存(如果在创建时已分配)以及从任何SYS / BIOS内部删除任务 状态结构。
  2. SYS / BIOS不会跟踪任务在其生存期内可能已获取或使用的任何资源。
  3. 在删除任务之前,应用程序有责任保证任务间协同关系的完整性。例如,如果任务获得了对资源的独占访问权,则删除该任务将使该资源永远不可用。
  4. Task_delete()将引用的任务句柄设置为NULL。 使用该空任务句柄对Task实例API进行的任何后续调用都将无法正常运行,通常会导致应用程序崩溃。假设任务在调用Task_exit()之前完全清除,那么只有在任务处于“ Task_Mode_TERMINATED”状态时才使用Task_delete()是最安全的。
  5. 删除挂钩:指定删除应用程序范围内挂钩功能,这些hook功能在删除任务时运行。
  6. 无法从swi或者hwi种调用删除任务
  7. 不执行任何检查来防止Task_delete用于静态创建的对象。 如果程序尝试删除静态创建的任务对象,则Memory_free()调用将导致其相应的堆管理器中的断言失败,从而导致应用程序退出。

Hook functions: 钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。对每种类型的钩子由系统来维护一个钩子链,最近安装的钩子放在链的开始,而最先安装的钩子放在最后,也就是后加入的先获得控制权。

堆栈对齐

静态和动态任务的堆栈大小参数均四舍五入为特定于目标的对齐要求的最接近整数倍。对于使用用户提供的堆栈创建的Task,基址和stackSize均要对齐。 基地址增加到最接近的对齐地址。 堆栈大小会相应减小,然后向下舍入为目标特定所需对齐方式的最接近整数倍

钩子函数

为什么要使用hook函数: 做一些定制性工作,比如在任务终止前,调用hook functions可以将有用的信息记录到nvm中。

可以为任务模块指定挂钩函数集,每个集合可以包含以下挂钩函数,它只能被静态配置:

  1. Register:在运行时初始化任何静态创建的任务之前调用的函数。 在启动时在main()之前和启用中断之前调用注册挂钩。
  2. Create: 创建任务时调用的函数,包括静态创建的任务以及使用create或Constructor动态创建的任务;对于静态创建的任务,在main()之前和启用中断之前调用create hook。 对于动态创建或构造的任务,在创建或构造任务的同一上下文中调用创建钩子,即如果在main()中创建任务,则在主上下文中调用创建钩子,如果在另一个任务中创建任务,则为 在任务上下文中调用。 在将任务添加到就绪列表之前,在Task_disable / enable块之外调用create挂钩。
  3. Ready: 当任务准备好运行时调用的函数。在线程取消阻塞任务的上下文中调用ready钩子,因此可以在Hwi,Swi或Task上下文中调用它。如果Swi或Hwi发布了解除阻塞的信号量 一个任务,准备挂钩将在Swi或Hwi的上下文中被调用。 从启用了中断的Task_disable / enable块中调用ready挂钩
  4. Switch: 在任务切换发生之前调用的功能。 “上一个”和“下一个”任务句柄被传递到开关钩。 对于在SYS / BIOS启动期间发生的初始任务切换,“ prev”设置为NULL。 在要从中切换任务的上下文中(即上一个任务),从启用了中断的Task_disable / enable块中调用切换钩子。
  5. Exit: 当任务使用exit退出时调用的函数。在退出任务的上下文中调用该函数。 退出挂钩通过退出任务的句柄。 退出挂钩在Task_disable / enable块之外并在从内核列表中删除任务之前被调用
  6. Delete: 在使用delete在运行时删除任何任务时调用的函数。 如果将Delete TerminatedTasks设置为true,则在空闲taskcontext中调用delete钩子。 否则,它将在正在删除另一个任务的任务的上下文中调用。delete钩子在Task_disable / enable块之外调用

下面举两个函数的具体介绍与使用

  • 提供了Register函数,以允许hook集存储其挂钩集ID。 可以将此ID传递给setHookContext和getHookContext来设置或获取特定于钩子集的上下文。 如果挂钩实现需要使用setHookContext或getHookContext,则必须指定Register函数.registerFxn挂钩函数在启用中断之前的系统初始化期间被调用void myRegisterFxn(Int id);
  • 每当创建或删除任务时,都会调用create和delete函数。 在启用中断的情况下调用它们(除非在启动时或从main()调用)
typedef enum Task_Mode { 

    Task_Mode_RUNNING, 
    // Task is currently executing

    Task_Mode_READY, 
    // Task is scheduled for execution

    Task_Mode_BLOCKED, 
    // Task is suspended from execution

    Task_Mode_TERMINATED, 
    // Task is terminated from execution

    Task_Mode_INACTIVE 
    // Task is on inactive task list
} Task_Mode; 

 

断言使用


//原型
macro Void Assert_isTrue(Bool expr, Assert_Id id); 
//使用
#include 
  Assert_isTrue(count > 0, NULL);

TASk使用

//disable和restore使用
key = Task_disable();
      `critical section`
Task_restore(key);
函数 含义

Void Task_exit();

终止当前任务,切换模式Mode_Running->Mode_TERMINATED,只能在Task线程调用

Task_Handle Task_getIdleTask();

返回空闲task的句柄

Task_Handle Task_self();

返回当前执行task对象的句柄

Void Task_sleep(UInt32 nticks);

延迟n次ticks, task模式从Mode_RUNNING ->Mode_BLOCKED;真实时间由Clock_tickPeriod确定。经过指定时间后返回到Mode_Ready状态

Void Task_yield();

如果有同等级任务,使用该函数进行任务切换。

Task_Params{

Uint affinity;}

指定在某个核上运行的任务,如果需要将任务固定到特定内核,则将“亲和力”设置为相应的coreid将强制任务仅在该内核上运行。

UInt Task_setPri(Task_Handle handle, Int newpri);

设置任务A优先级,任务优先级默认最多16,该函数返回旧的优先级。当存在其它任务B时,设置A优先级低于B可实现互斥操作。

Void Task_stat(Task_Handle handle, Task_Stat *statbuf);

检索任务的状态

Task.addHookSet(Task.HookSet hook) returns Void

*.cfg
// Hook Set 1
  Task.addHookSet({
     registerFxn: '&myRegister1',
     createFxn:   '&myCreate1',
     readyFxn:    '&myReady1',
     switchFxn:   '&mySwitch1',
     exitFxn:     '&myExit1',
     deleteFxn:   '&myDelete1'
  });

Task_FuncPtr Task_getFunc(Task_Handle handle, UArg *arg0, UArg *arg1);

通过任务句柄获取任务函数和任务参数

Semaphore使用

信号量管理器提供了一组操作信号量对象的功能, 信号量可用于任务同步和互斥。其实我们学习了QSemaphore,了解这个也是大同小异了。

信号量可以是计数信号量或二值制信号量。 信号量计数跟踪使用post()发布信号量的次数。 例如,有一组在任务之间共享的资源, 这些任务可能会在使用一个之前调用pend()来查看aresource是否可用。

    mailbox模块内部使用计数信号量来管理空闲(或全部)mailbox元素。 计数信号量的另一个示例是ISR,它可能会填充多个数据缓冲区以供任务使用,填充每个缓冲区后,ISR将缓冲区放在队列中并调用post(),等待数据的任务将调用pend(),这将仅减少信号量计数,如果计数为0,则返回或阻塞。

pend() 用于等待信号量,超时参数允许任务等待直到超时/无限期等待/无需等待,返回值用于指示semaphore是否成功发射信号
post ()

用于发信号量, 如果任务正在等待这些信号量,则post()将任务从信号量队列中删除,并将其放入就绪队列中。 如果没有任务在等待,则post()仅增加信号量计数并返回, 对于二值信号量,计数始终设置为1。

Void Semaphore_registerEvent(Semaphore_Handle handle, Event_Handle event, UInt eventId);

使用信号量注册一个事件对象,Event_post(event, eventId)和Event_pend(event, eventId, 0, timeout)将会在semaphore_post(), semaphore_pend()调用

Semaphore_Handle Semaphore_create(Int count, const Semaphore_Params *params, Error_Block *eb);

Void Semaphore_construct(Semaphore_Struct *structP, Int count, const Semaphore_Params *params);

创建一个semaphore对象

Void Semaphore_reset(Semaphore_Handle handle, Int count);

重置count计数

与task,hook一样需要在.cfg文件中定义:

var Semaphore = xdc.useModule('ti.sysbios.knl.Semaphore'); 

module-wide constants & types

    values of type Semaphore.Mode 

        const Semaphore.Mode_COUNTING; 

        const Semaphore.Mode_BINARY; 

        const Semaphore.Mode_COUNTING_PRIORITY; 

        const Semaphore.Mode_BINARY_PRIORITY; 

module-wide config parameters

    Semaphore.A_badContext = Assert.Desc { 

        msg: "A_badContext: bad calling context. Must be called from a Task." 

    }; 

    Semaphore.A_noEvents = Assert.Desc { 

        msg: "A_noEvents: The Event.supportsEvents flag is disabled." 

    }; 

    Semaphore.A_overflow = Assert.Desc { 

        msg: "A_overflow: Count has exceeded 65535 and rolled over." 

    }; 

    Semaphore.A_pendTaskDisabled = Assert.Desc { 

        msg: "A_pendTaskDisabled: Cannot call Semaphore_pend() while the Task or Swi scheduler is disabled." 

    }; 

    Semaphore.E_objectNotInKernelSpace = Error.Desc { 

        msg: "E_objectNotInKernelSpace: Semaphore object passed not in Kernel address space." 

    }; 

    Semaphore.LM_pend = Log.EventDesc { 

        mask: Diags.USER1 | Diags.USER2, 

        msg: "LM_pend: sem: 0x%x, count: %d, timeout: %d" 

    }; 

    Semaphore.LM_post = Log.EventDesc { 

        mask: Diags.USER1 | Diags.USER2, 

        msg: "LM_post: sem: 0x%x, count: %d" 

    }; 

    Semaphore.supportsEvents = Bool false; 

    Semaphore.supportsPriority = Bool true; 

 

    Semaphore.common$ = Types.Common$ undefined; 

per-instance config parameters

    var params = new Semaphore.Params; 

        params.event = Event.Handle null; 

        params.eventId = UInt 1; 

        params.mode = Semaphore.Mode Semaphore.Mode_COUNTING; 

per-instance creation

    var inst = Semaphore.create(Int count, params);

Malibox使用方法

mailbox模块提供了一组函数,这些函数可操纵通过Mailbox_Handle类型的句柄访问的mailbox对象。

pend():用于从mailbox中等待一个消息,允许任务等待直到系统时钟指定的超时参数。

当mailbox配置readerEvent事件对象,并且从Event.pend()返回了带有相应的readerEventId的任务时,BIOS_NO_WAIT应该传递给Mailbox_pend()来检索消息。

post()用于向邮箱发送消息。Mailbox_post的timeout参数指定邮箱已满时调用任务等待的时间

Bool Mailbox_pend(Mailbox_Handle handle, Ptr msg, UInt32 timeout);

如果邮箱不为空,则Mailbox_pend将第一条消息复制到msg中并返回TRUE。 否则,Mailbox_pend会中止当前任务的执行,直到调用Mailbox_post或超时为止

Bool Mailbox_post(Mailbox_Handle handle, Ptr msg, UInt32 timeout);

Mailbox_post在将msg复制到邮箱之前检查是否有空闲的消息槽。 Mailbox_post准备在邮箱上等待的第一个任务(如果有)。 如果邮箱已满并且指定了超时,则该任务将保持挂起状态,直到调用Mailbox_pend或超时为止

Event使用方法

SYS / BIOS事件是Task其他线程(例如Hwis,Swis和其他Task)之间,或Task其它SYS / BIOS对象之间进行通信的一种方式,其中SYS / BIOS对象包括信号量,邮箱,消息队列等。 而任务,Hwis,Swis或SYS / BIOS对象可以发布它们。

  • 要使得一个任务被SYS/BIOS对象通知到,首先需要注册事件对象,因此SYS/BIOS对象各自模块中提供了独立的API函数
  • 事件本质上是同步的,在等待事件发生时,接收任务将阻塞或暂停。 收到所需事件后,挂起的任务将继续执行
  • 任务也可以等待没有链接到SYS / BIOS对象的事件,这些事件是从其他线程(例如task,Swis或Hwis)显式发布的, 任务没有注册以接收这些事件; 发送线程仅将其事件发布到任务pending的事件对象上,这种情况类似于ISR posting一个信号量
  • 一个任务可以等待来自多个资源或线程的事件,它可以等待信号量的posted 、在消息队列的消息、ISR线程中的信号,表示已发生事件。与信号量不同,单任务只能请求(pend)一个事件对象。
  • pend用于等待事件,andMask和orMask确定从pend() 返回之前必须发生的事件。 timeout参数允许任务等待直到超时,无限期等待或不等待立即返回。 返回值为零表示等待事件已发生超时,在任务收到阻塞时的事件集返回非零值。
  • 事件是二进制的。 在eventId的事件Event_post()后变为available,并且在每个有效的Event_pend()后变为non-available(consumed)。从pend返回时,orMask中存在的所有活动事件都会被消耗(即从事件对象中删除)。 只有当andMask中存在的所有事件都处于活动状态时,它们才会从pend返回时消耗。
  • 当将事件与信号量或mailbox对象结合使用时隐式发布事件,然后在调用Event_pend()并耗匹配Event_ID pended on。事件对象将通过调用Semaphore_pend()或Mailbox_pend / post()进行更新,反映了相应信号量或mailbox对象的当前可用性状态

Bool Event_Module_startupDone();

检测该模块是否完全启动

Event_Handle Event_handle(Event_Struct *structP);

将事件结构体指针转换为事件实例句柄

Event_Struct *Event_struct(Event_Handle handle);

将事件实例句柄转换为结构体指针

Event_Handle Event_create(const Event_Params *params, Error_Block *eb);

分配并初始化一个新的实例返回句柄

Void Event_construct(Event_Struct *structP, const Event_Params *params);

在提供的结构体内初始化一个事件实例

UInt Event_pend(Event_Handle handle, UInt andMask, UInt orMask, UInt32 timeout);

等待事件,MASK表示事件ID

Queue的使用方法

Queue模块提供了一组函数来处理通过Queue_Handle类型的句柄访问的对象的队列。每个队列包含零个或多个链接元素的序列,这些序列通过Queue_Elem类型的变量引用,它被嵌入为结构中的第一个字段。

在Queue API描述中,在修改Queue之前禁止中断的API称作automic

Queue可以看作是双向列表,Queue_next或者Queue_pre可以连续循环出队列。

总结:Task通过事件与其它BIOS对象如Semaphore,mailbox,message queues 及其它任务对象通信,其中Semaphore用于任务间的同步,message queues用于填充消息,mailbox管理消息队列queues。在实际开发中,TI开发了如mailbox的驱动程序隐藏了EVENT和queues的细节,给出更加安全可靠的核间通信API,仍然使用Semaphore来同步任务,但是理解内核的功能函数对我们理解工程十分有益。

 

你可能感兴趣的:(嵌入式,多线程)