У любой операционной системы существует определенный механизм запуска. Принцип работы этого механизма у каждой системы свой.
Но в большинстве «классических» ОСРВ используется гораздо более простой (а, следовательно, более быстрый) способ, чем настольные ПК.
Операционная система – часть программного обеспечения. Если она уже находится в памяти (например, в том или ином виде ПЗУ), то требуется всего лишь сделать так, чтобы последовательность команд ЦП после сброса заканчивалась выполнением кода инициализации ОС. Именно так работают большинство ОСРВ, в том числе и Nucleus SE, и наша ОСРВ МАКС.
Объявления всех статических переменных в коде Nucleus SE начинаются с префикса ROM или RAM, чтобы показать, где их следует размещать. Эти две директивы #define определены в файле nuse_types.h и должны быть сконфигурированы с учетом особенностей используемого набора инструментов для разработки (компилятор и компоновщик). Обычно ROM должен иметь тип const (прим. переводчика: из моего опыта, const – не всегда достаточно, лучше – static const), а RAM – пустое значение.
Все переменные ROM инициализируются статически, что логично. Переменные RAM не инициализируются статически (так как это работает только с определенными наборами инструментов, которые настроены на автоматическое копирование из ПЗУ в ОЗУ); явный код инициализации включен в приложение и будет подробно описан ниже.
Nucleus SE не хранит «константных» данных в ОЗУ, которая обычно в дефиците у небольших систем. Вместо использования сложных структур данных для описания объектов ядра используются наборы таблиц (массивов), которые без проблем размещаются в ПЗУ или ОЗУ, в зависимости от необходимости.
void main(void)
{
NUSE_Init(); /* initialize kernel data */
/* user initialization code here */
NUSE_Scheduler(); /* start tasks */
}
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
}
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
}
Если планировщик Run to Completion не был сконфигурирован, инициализируется контекстный блок для задачи NUSE_Task_Context[task][]. Большинству элементов не присваиваются значения, так как они представляют общие машинные регистры, которые должны иметь промежуточное значение при запуске задачи. В примере (Freescale ColdFire) реализации Nucleus SE (но и у других процессоров механизм будет аналогичным) последние три записи заданы явным образом:
Если активирована поддержка сигналов, флагам сигналов задачи (NUSE_Task_Signal_Flags[task]) присваивается нулевое значение.
Если активирована приостановка задачи (т.е. служебный вызов API NUSE_Task_Sleep()), счетчику таймаута задачи (NUSE_Task_Timeout_Counter[task]) присваивается нулевое значение.
Если активировано состояние ожидания задачи (task suspend), статус задачи (NUSE_Task_Status[task]) инициализируется. Это начальное значение задается пользователем (в NUSE_Task_Initial_State[task]), если активирована поддержка начального состояния задачи. В противном случае состоянию присваивается NUSE_READY.
Если активирован счетчик планировок, счетчику задачи (NUSE_Task_Schedule_Count[task]) присваивается нулевое значение.
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]) присваивается нулевое значение.
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]) присваивается нулевое значение.
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]) присваивается нулевое значение.
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]) присваивается нулевое значение.
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]) присваивается нулевое значение.
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]) присваивается нулевое значение.
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 были инициализированы, появляется возможность выполнить код, отвечающий за инициализацию приложения до начала выполнения задачи. Это возможность может пригодиться для следующих задач:
Очевидно, многие из этих целей могут быть достигнуты и до инициализации Nucleus SE, но преимущество в расположении кода приложения здесь заключается в том, что теперь можно использовать службы ядра (вызовы API). Например, очередь или почтовый ящик могут быть предварительно заполнены данными, которые нужно будет обработать, когда задача запустится.
У вызовов API есть ограничение: все действия, которые обычно приводят к активации планировщика, запрещены (например, приостановка/блокировка задач). Глобальной переменной NUSE_Task_State было присвоено значение NUSE_STARTUP_CONTEXT, чтобы отметить это ограничение.
После того, как инициализация была завершена, остается только запустить планировщик, чтобы приступить к выполнению кода приложения – задач. Конфигурация планировщика и работа различных видов планировщиков была подробно описана в одной из предыдущих статей (#9), так что здесь потребуется лишь краткий итог.
Что именно происходит на последнем шаге зависит от того, какой планировщик выбран. При использовании планировщика Run to Completion запускается цикл планировки и задачи вызываются последовательно. При использовании других планировщиков загружается контекст первой задачи и управление передается задаче. В следующей статье будет рассматриваться диагностика и проверка ошибок.
Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он – инженер в области встроенного ПО в Mentor Embedded ( подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина: https://blogs.mentor.com/colinwalls/, e-mail: colin_walls@mentor.com