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())。本文后面还将更详细地研究这一点。
The NUSE_Init() Function

此函数用于初始化所有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
}
 

First, some global variables are initialized:

  • NUSE_Task_Active – the index of the currently active task – is set to zero; this may be modified by the scheduler in due course.
  • NUSE_Task_State is set to NUSE_STARTUP_CONTEXT , which indicates the limited API functionality to any following application initialization code.
  • If system time support is enabled, NUSE_Tick_Clock is set to zero.
  • If the time slice scheduler has been enabled, NUSE_Time_Slice_Ticks is set up to the configured time slice value, NUSE_TIME_SLICE_TICKS .

Then, a series of functions are called to initialize kernel objects:

  • NUSE_Init_Task() is called to initialize data structures for each task. This call is only omitted if the Run to Completion scheduler is selected and signals, task suspend, and schedule counting are all not configured (as this combination would result in there being no RAM data structures appertaining to tasks and, hence, no initialization to be done).
  • NUSE_Init_Partition_Pool() is called to initialize each partition pool object. The calls are omitted if no partition pools have been configured.
  • NUSE_Init_Mailbox() is called to initialize each mailbox object. The calls are omitted if no mailboxes have been configured.
  • NUSE_Init_Queue() is called to initialize each queue object. The calls are omitted if no queues have been configured.
  • NUSE_Init_Pipe() is called to initialize each pipe object. The calls are omitted if no pipes have been configured.
  • NUSE_Init_Semaphore() is called to initialize each semaphore object. The calls are omitted if no semaphores have been configured.
  • NUSE_Init_Event_Group() is called to initialize each event group object. The calls are omitted if no event groups have been configured.
  • NUSE_Init_Timer() is called to initialize each timer object. The calls are omitted if no timers have been configured.

 

Initializing Tasks

Here is the complete code for 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]
     holds the status register (SR ) and is set to the value in the #define symbol NUSE_STATUS_REGISTER .
  • ·  NUSE_Task_Context[task][16] holds the program counter (PC ) and is set to the address of the entry point of the task’s code: NUSE_Task_Start_Address[task] .
  • ·  NUSE_Task_Context[task][17] holds the stack pointer (SP), which is initialized to a value computed by adding the address of the task’s stack base (NUSE_Task_Stack_Base[task] ) to the task’s stack size (NUSE_Task_Stack_Size[task] ).

如果启用信号支持,任务的信号标志(NUSE_task_signal_flags[task])设为零。             

如果启用了任务休眠(即API调用NUSE_task_sleep()),则任务的超时计数器(NUSE_task_timeout_counter[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,进入调度循环并按顺序调用任务。对于其他调度程序类型,将加载第一个任务的上下文并将控制权传递给该任务。

posted @ 2020-07-06 08:16  吴建明wujianming  阅读(250)  评论(0编辑  收藏  举报