Nucleus SE RTOS初始化和启动
Nucleus SE RTOS初始化和启动
Nucleus SE RTOS initialization and start-up
对于任何类型的操作系统,都有某种类型的启动机制。具体的工作方式因系统而异。通常说操作系统会“启动”。这是“bootstrap”的缩写,它描述了CPU如何从一个完全没有内存的内存中获得稳定的程序执行状态。传统上,一小片软件被加载到内存中;它可以简单地保存在ROM中。在过去,它可能是通过电脑前面板上的开关输入的。这个“引导加载器”会读入一个更复杂的引导程序,而这个程序又会加载并启动操作系统。这就是桌面计算机今天开始运行的过程;BIOS中的代码寻找可引导的设备(硬盘驱动器或CD-rom),从中可以加载引导程序,从而加载操作系统。
嵌入式系统的操作系统也可以用这种方式初始化。操作系统确实是从桌面操作系统衍生出来的。但对于大多数“经典”rtos,使用的是更简单(因此更快)的过程。
操作系统只是一个软件。如果这个软件已经在内存中了——比如说某种形式的ROM——那就是简单地安排CPU的重置顺序,最终执行操作系统的初始化代码。这就是大多数RTOS的工作方式,Nucleus SE也不例外。
大多数嵌入式软件开发工具包都包含必要的启动代码,以处理CPU重置并到达main()函数的入口点。Nucleus SE发行版代码本身与此过程无关,因为它的目的是尽可能地便于移植。相反,它提供了main()函数,该函数控制CPU并初始化和启动操作系统;稍后将对此进行详细描述。
内存初始化
Nucleus SE代码中所有静态变量的声明都以ROM或RAM作为前缀,以指示它们可能位于哪里。这两个define符号是在nuse_types.h中定义的,应该进行设置以适应正在使用的开发工具包(编译器和链接器)的功能。通常,ROM可以设置为const,RAM留空。
所有的ROM变量都是静态初始化的,这是合乎逻辑的。没有RAM变量是静态初始化的(因为这只适用于某些工具箱,这些工具箱安排从ROM到RAM的自动复制);包含显式初始化代码,本文将详细介绍这些代码。
Nucleus SE不在RAM中保存任何“恒定”的数据,在小型系统中,RAM可能供不应求。不使用复杂的数据结构来描述内核对象,而是使用一系列表(数组),这些表(数组)可以很容易地在ROM或RAM中找到。
main()函数
以下是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_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的示例(Freescale ColdFire)实现中(这对于任何处理器都是类似的),最后三个条目是显式设置的:
NUSE_Task_Context[Task][15]保存状态寄存器(SR),并设置为#define symbol NUSE_status_寄存器中的值。
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])设为零。
如果启用了任务休眠(即API调用NUSE_task_sleep()),则任务的超时计数器(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
}
分区池的“used”计数器(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
}
队列的head和tail指针(实际上,它们是索引–NUSE_queue_head[queue]和NUSE_queue_tail[queue])被设置为指向队列数据区域的开始(即给定值0)。队列的项目计数器(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])被设置为指向管道数据区域的开始(即给定值0)。管道的项目计数器(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[信号量])设置为零。
初始化事件组
以下是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])设置为“未使用”;即FALSE。
其倒计时值(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以反映此限制。
启动计划程序
初始化完成后,只剩下启动调度程序来开始执行应用程序代码-任务。绍了调度程序的选项和各种类型的调度程序的操作,因此这里只需要简要总结一下。
顺序中的关键点是:
将全局变量NUSE_Task_State设置为NUSE_Task_CONTEXT。
选择要运行的第一个任务的索引。如果启用了对初始任务状态的支持,将对第一个就绪任务执行搜索;否则将使用值0。
调用调度程序–NUSE_scheduler()。
在最后一步中到底发生了什么取决于选择了哪种调度程序类型。对于Run-to-Completion,进入调度循环并按顺序调用任务。对于其他调度程序类型,将加载第一个任务的上下文并将控制权传递给该任务。