关于RTOS的全部真相。 第30条。 Nucleus SE初始化和启动过程



任何操作系统都有特定的启动机制。 每个系统的这种机制的工作原理是不同的。 通常他们说系统启动(Eng。Boot),这是“ bootstrap”的缩写,它是指“用靴子将自己拉过篱笆”(越过篱笆,用鞋带拉自己)。系统如何独立地从内存为空的状态( 译者注:如果绝对准确,则为垃圾 )移动到稳定的程序执行。 传统上,程序的一小部分被加载到内存中;它可以存储在ROM中。 过去,可以使用计算机正面的开关输入该密码。 该引导加载程序读取了一个已经加载了操作系统的更复杂的引导程序。 今天,台式计算机的启动方式如下:BIOS代码查找可以从中启动的设备(硬盘驱动器,CD-ROM,USB记忆棒),然后操作系统启动。

嵌入式系统的OS也可以用类似的方式初始化。 实际上,已经加载了基于桌面操作系统开发的嵌入式操作系统。 但是在大多数“经典” RTOS中,使用的方法要简单得多(因此更快)。

操作系统是软件的一部分。 如果此软件已经存在于内存中(例如,以ROM的一种形式或另一种形式),则只需确保复位后的CPU命令序列以执行OS初始化代码结束。 这就是大多数RTOS的工作方式,包括Nucleus SE( 译者注:这也适用于我们的RTOS MAX )。

大多数嵌入式软件开发工具都包含必要的启动代码,以处理CPU复位并将控制转移到main()函数中的Entry Point函数。 Nucleus SE可再发行代码不处理此过程,因为它必须尽可能地可移植。 相反,它包含main()函数,该函数控制CPU并初始化并启动OS。 此功能将在下面详细讨论。


内存初始化


Nucleus SE代码中所有静态变量的声明均以ROMRAM前缀开头,以指示应将其放置在何处。 这两个#define指令在nuse_types.h文件中定义,并且必须在配置时考虑所使用的一组开发工具(编译器和链接器)的详细信息。 通常, ROM应该是const类型( 译者注:根据我的经验, const并不总是足够的, static更好 ),而RAM是空值。

所有ROM变量都是静态初始化的,这是合乎逻辑的。 RAM变量不是静态初始化的(因为这仅适用于某些配置为自动从ROM复制到RAM的工具箱); 一个明确的初始化代码包含在应用程序中,下面将对其进行详细描述。

Nucleus SE不会在RAM中存储“恒定”数据,而在小型系统中,RAM通常供不应求。 无需使用复杂的数据结构来描述内核对象,而是使用表(数组)集,这些表集可以根据需要轻松地放置在ROM或RAM中。

主要()函数


以下是Nucleus SE的main()函数的完整代码:

void main(void) { NUSE_Init(); /* initialize kernel data */ /* user initialization code here */ NUSE_Scheduler(); /* start tasks */ } 

操作顺序非常简单:

  • 首先, 调用NUSE_Init()函数。 它会初始化所有Nucleus SE数据结构,下面将详细介绍。
  • 然后,用户可以插入将在任务计划程序启动之前执行的任何应用程序初始化代码。 有关此代码可以实现的更多信息,请参见本文后面。
  • 最后,Nucleus SE调度程序( NUSE_Scheduler() )启动。 本文后面还将对此进行详细讨论。

NUSE_Init()函数


该函数初始化所有Nucleus SE内核变量和数据结构。

以下是完整的功能代码:
 void NUSE_Init(void) { U8 index; /* global data */ NUSE_Task_Active = 0; NUSE_Task_State = NUSE_STARTUP_CONTEXT; #if NUSE_SYSTEM_TIME_SUPPORT NUSE_Tick_Clock = 0; #endif #if NUSE_SCHEDULER_TYPE == NUSE_TIME_SLICE_SCHEDULER NUSE_Time_Slice_Ticks = NUSE_TIME_SLICE_TICKS; #endif /* tasks */ #if ((NUSE_SCHEDULER_TYPE != NUSE_RUN_TO_COMPLETION_SCHEDULER) || NUSE_SIGNAL_SUPPORT || NUSE_TASK_SLEEP || NUSE_SUSPEND_ENABLE || NUSE_SCHEDULE_COUNT_SUPPORT) for (index=0; index<NUSE_TASK_NUMBER; index++) { NUSE_Init_Task(index); } #endif /* partition pools */ #if NUSE_PARTITION_POOL_NUMBER != 0 for (index=0; index<NUSE_PARTITION_POOL_NUMBER; index++) { NUSE_Init_Partition_Pool(index); } #endif /* mailboxes */ #if NUSE_MAILBOX_NUMBER != 0 for (index=0; index<NUSE_MAILBOX_NUMBER; index++) { NUSE_Init_Mailbox(index); } #endif /* queues */ #if NUSE_QUEUE_NUMBER != 0 for (index=0; index<NUSE_QUEUE_NUMBER; index++) { NUSE_Init_Queue(index); } #endif /* pipes */ #if NUSE_PIPE_NUMBER != 0 for (index=0; index<NUSE_PIPE_NUMBER; index++) { NUSE_Init_Pipe(index); } #endif /* semaphores */ #if NUSE_SEMAPHORE_NUMBER != 0 for (index=0; index<NUSE_SEMAPHORE_NUMBER; index++) { NUSE_Init_Semaphore(index); } #endif /* event groups */ #if NUSE_EVENT_GROUP_NUMBER != 0 for (index=0; index<NUSE_EVENT_GROUP_NUMBER; index++) { NUSE_Init_Event_Group(index); } #endif /* timers */ #if NUSE_TIMER_NUMBER != 0 for (index=0; index<NUSE_TIMER_NUMBER; index++) { NUSE_Init_Timer(index); } #endif } 


首先,初始化全局变量:
  • NUSE_Task_Active-活动任务的索引,初始化为零; 稍后这可能会更改调度程序。
  • NUSE_Task_State-使用值NUSE_STARTUP_CONTEXT初始化,该值限制了任何后续应用程序初始化代码的API功能。
  • 如果激活了系统时间支持,则NUSE_Tick_Clock设置为零。
  • 如果激活了时间片调度程序, 则会为NUSE_Time_Slice_Ticks分配配置值NUSE_TIME_SLICE_TICKS

然后调用函数以初始化内核对象:

  • 调用NUSE_Init_Task()初始化每个任务的数据结构。 仅在使用“运行至完成”调度程序且未配置信号,任务暂停和调度计数器的情况下才跳过此调用(由于此功能组合将导致RAM中没有这些任务结构,因此将不执行初始化)。
  • 调用NUSE_Init_Partition_Pool()初始化每个分区池对象。 如果未配置分区池,则将跳过这些调用。
  • 调用NUSE_Init_Mailbox()初始化每个邮箱对象。 如果没有配置的邮箱,将跳过这些呼叫。
  • 调用NUSE_Init_Queue()初始化每个队列对象。 如果未配置队列,将跳过这些调用。
  • 调用NUSE_Init_Pipe()初始化每个通道对象。 如果未配置任何通道,将跳过这些呼叫。
  • 调用NUSE_Init_Semaphore()初始化每个信号量对象。 如果未配置任何信号灯,则将跳过这些调用。
  • 调用NUSE_Init_Event_Group()初始化每个事件组对象。 如果没有配置的事件组,将跳过这些调用。
  • 调用NUSE_Init_Timer()初始化每个计时器对象。 如果未配置任何计时器,将跳过这些调用。

任务初始化


以下是NUSE_Init_Task()函数的完整代码:
 void NUSE_Init_Task(NUSE_TASK task) { #if NUSE_SCHEDULER_TYPE != NUSE_RUN_TO_COMPLETION_SCHEDULER NUSE_Task_Context[task][15] = /* SR */ NUSE_STATUS_REGISTER; NUSE_Task_Context[task][16] = /* PC */ NUSE_Task_Start_Address[task]; NUSE_Task_Context[task][17] = /* SP */ (U32 *)NUSE_Task_Stack_Base[task] + NUSE_Task_Stack_Size[task]; #endif #if NUSE_SIGNAL_SUPPORT || NUSE_INCLUDE_EVERYTHING NUSE_Task_Signal_Flags[task] = 0; #endif #if NUSE_TASK_SLEEP || NUSE_INCLUDE_EVERYTHING NUSE_Task_Timeout_Counter[task] = 0; #endif #if NUSE_SUSPEND_ENABLE || NUSE_INCLUDE_EVERYTHING #if NUSE_INITIAL_TASK_STATE_SUPPORT || NUSE_INCLUDE_EVERYTHING NUSE_Task_Status[task] = NUSE_Task_Initial_State[task]; #else NUSE_Task_Status[task] = NUSE_READY; #endif #endif #if NUSE_SCHEDULE_COUNT_SUPPORT || NUSE_INCLUDE_EVERYTHING NUSE_Task_Schedule_Count[task] = 0; #endif } 


如果尚未配置“运行至完成”调度程序,则将初始化NUSE_Task_Context [task] []任务的上下文块。 大多数元素未分配值,因为它们代表公用的机器寄存器,在启动任务时应具有中间值。 在Nucleus SE实现的示例(飞思卡尔ColdFire)中(但对于其他处理器,该机制类似),最后三个条目是明确设置的:

  • NUSE_Task_Context [任务] [15]包含状态寄存器( SR ,状态寄存器),并具有#define NUSE_STATUS_REGISTER指令的值。
  • NUSE_Task_Context [task] [16]包含程序计数器( PC ,程序计数器),并具有任务代码输入点的地址值: NUSE_Task_Start_Address [task]
  • NUSE_Task_Context [task] [17]包含堆栈指针( SP ,堆栈指针),并使用作为任务堆栈的地址( NUSE_Task_Stack_Base [task] )和任务堆栈的大小( NUSE_Task_Stack_Size [task] )的总和进行初始化。

如果激活了信号支持, 则将任务信号标志( NUSE_Task_Signal_Flags [task] )设置为零。

如果激活了任务挂起(即NUSE_Task_Sleep() API服务调用),则任务超时计数器( NUSE_Task_Timeout_Counter [task] )设置为零。

如果激活了任务挂起状态,则将初始化任务状态( NUSE_Task_Status [task] )。 如果激活了对任务初始状态的支持,则该初始值由用户设置(在NUSE_Task_Initial_State [task]中 )。 否则,将状态分配给NUSE_READY

如果激活了计划计数器,则任务计数器( NUSE_Task_Schedule_Count [task] )设置为零。

初始化分区池


以下是NUSE_Init_Partition_Pool()函数的完整代码:

 void NUSE_Init_Partition_Pool(NUSE_PARTITION_POOL pool) { NUSE_Partition_Pool_Partition_Used[pool] = 0; #if NUSE_BLOCKING_ENABLE NUSE_Partition_Pool_Blocking_Count[pool] = 0; #endif } 

“已用”分区池计数器( NUSE_Partition_Pool__Partition_Used [pool] )设置为零。

如果激活了任务锁定,则分区池( NUSE_Partition_Pool_Blocking_Count [pool])的阻止任务计数器将设置为零。

初始化邮箱


以下是完整的NUSE_Init_Mailbox()代码:

 void NUSE_Init_Mailbox(NUSE_MAILBOX mailbox) { NUSE_Mailbox_Data[mailbox] = 0; NUSE_Mailbox_Status[mailbox] = 0; #if NUSE_BLOCKING_ENABLE NUSE_Mailbox_Blocking_Count[mailbox] = 0; #endif } 

邮箱数据存储( NUSE_Mailbox_Data [mailbox] )设置为零,状态( NUSE_Mailbox_Status [mailbox] )变为“未使用”(即零)。

如果激活了任务锁定,则阻止的邮箱任务的计数器( NUSE_Mailbox_Blocking_Count [mailbox] )设置为零。

队列初始化


以下是NUSE_Init_Queue()函数的完整代码:

 void NUSE_Init_Queue(NUSE_QUEUE queue) { NUSE_Queue_Head[queue] = 0; NUSE_Queue_Tail[queue] = 0; NUSE_Queue_Items[queue] = 0; #if NUSE_BLOCKING_ENABLE NUSE_Queue_Blocking_Count[queue] = 0; #endif } 

指向队列起点和终点的指针(实际上,它们是索引NUSE_Queue_Head [queue ]和NUSE_Queue_Tail [queue] )被分配了一些值,这些值指示队列的数据区域的起点(即,它们的值为零)。 队列中的计数器( NUSE_Queue_Items [queue] )也设置为零。

如果激活了任务锁定,则阻塞队列任务的计数器( NUSE_Queue_Blocking_Count [queue] )设置为零。

通道初始化


以下是NUSE_Init_Pipe()函数的完整代码:

 void NUSE_Init_Pipe(NUSE_PIPE pipe) { NUSE_Pipe_Head[pipe] = 0; NUSE_Pipe_Tail[pipe] = 0; NUSE_Pipe_Items[pipe] = 0; #if NUSE_BLOCKING_ENABLE NUSE_Pipe_Blocking_Count[pipe] = 0; #endif } 

指向通道起点和终点的指针(实际上是索引-NUSE_Pipe_Head [pipe]NUSE_Pipe_Tail [pipe] )被分配了一个值,该值指示通道数据区域的起点(即,它们为空值)。 通道计数器NUSE_Pipe_Items [pipe] )也设置为零。

如果激活了任务锁定,则通道的阻止任务计数器( NUSE_Pipe_Blocking_Count [pipe] )设置为零。

信号量初始化


以下是NUSE_Init_Semaphore()函数的完整代码:

 void NUSE_Init_Semaphore(NUSE_SEMAPHORE semaphore) { NUSE_Semaphore_Counter[semaphore] = NUSE_Semaphore_Initial_Value[semaphore]; #if NUSE_BLOCKING_ENABLE NUSE_Semaphore_Blocking_Count[semaphore] = 0; #endif } 

信号量计数器( NUSE_Semaphore_Counter [semaphore] )使用用户设置的值( NUSE_Semaphore_Initial_Value [semaphore] )进行初始化。

如果激活了任务锁定,则锁定的信号量任务计数器( NUSE_Semaphore_Blocking_Count [semaphore] )设置为零。

初始化事件组


以下是NUSE_Init_Event_Group()函数的完整代码:

 void NUSE_Init_Event_Group(NUSE_EVENT_GROUP group) { NUSE_Event_Group_Data[group] = 0; #if NUSE_BLOCKING_ENABLE NUSE_Event_Group_Blocking_Count[group] = 0; #endif } 

事件组的标志被重置,即 NUSE_Event_Group_Data [group]分配了一个空值。

如果激活了任务锁定,则事件标志组( NUSE_Event_Group_Blocking_Count [group] )的已阻止任务计数器将设置为零。

计时器初始化


下面是NUSE_Init_Timer()的完整代码;

 void NUSE_Init_Timer(NUSE_TIMER timer) { NUSE_Timer_Status[timer] = FALSE; NUSE_Timer_Value[timer] = NUSE_Timer_Initial_Time[timer]; NUSE_Timer_Expirations_Counter[timer] = 0; } 

计时器的状态( NUSE_Timer_Status [timer] )设置为“未使用”,即

倒计时值( NUSE_Timer_Value [timer ])由用户设置的值( NUSE_Timer_Initial_Time [timer ])初始化。

完成计数器( NUSE_Timer_Expirations_Counter [timer] )设置为零。

初始化应用程序代码


在Nucleus SE数据结构初始化之后,可以在开始任务之前执行负责初始化应用程序的代码。 此功能对于以下任务可能有用:

  • 应用程序数据结构的初始化。 与静态变量的自动初始化相比,显式填充数据结构更容易理解和调试。
  • 内核对象的分配。 假设所有内核对象都是在构建阶段静态创建的,并使用索引值进行标识,则分配“所有者”或确定这些对象的使用可能很有用。 可以使用#define指令完成此操作,但是,如果有多个任务实例,则最好通过全局数组(由任务ID索引)分配对象索引。
  • 设备初始化。 这对于外围设备的初始安装很有用。

显然,在Nucleus SE初始化之前,可以实现许多目标,但是此处应用程序代码位置的优势在于,现在您可以使用内核服务(API调用)。 例如,队列或邮箱可能预填充了任务开始时需要处理的数据。

API调用有一个限制:禁止通常导致激活调度程序的所有操作(例如,暂停/阻止任务)。 全局变量NUSE_Task_State已设置为NUSE_STARTUP_CONTEXT指示此限制。

启动调度程序


初始化完成后,仅需运行调度程序即可开始执行应用程序代码-任务。 在上一篇文章( #9 )中详细描述了调度程序的配置和各种类型的调度程序的工作,因此这里仅需要简要概述。
关键步骤的顺序如下:

  • 全局变量NUSE_Task_State设置为 NUSE_TASK_CONTEXT
  • 选择要运行的第一个任务的索引。 如果激活了对初始任务的支持,则将执行对第一个完成的任务的搜索,否则,将使用零值。
  • 该调度程序称为-NUSE_Scheduler()

在最后一步中确切发生什么取决于选择哪个调度程序。 使用“运行至完成”计划程序时,计划周期开始,并且依次调用任务。 使用其他调度程序时,将加载第一个任务的上下文,并将控制权转移到该任务。

下面的文章将讨论诊断和错误检查。

关于作者: Colin Walls在电子行业工作了30多年,大部分时间用于固件。 他现在是Mentor Embedded(Mentor Graphics的一个部门)的固件工程师。 Colin Walls经常在会议和研讨会上发表演讲,他撰写了许多技术文章并撰写了两本有关固件的书。 居住在英国。 Colin的专业博客 ,电子邮件:colin_walls@mentor.com。

Source: https://habr.com/ru/post/zh-CN460617/


All Articles