One of the key strengths of 𝐮𝐂/𝐎𝐒-𝐈𝐈 lies in its lightweight and independent task model. Each task executes specific functions or operations, allowing for efficient task switching and context switching.
Let’s start by unraveling the software file structure, which sets the foundation for a deep dive into the workings of 𝐎𝐒_𝐈𝐧𝐢𝐭(). We’ll delve into the initialization process of the uC/OS-II kernel, with a special focus on the 𝐎𝐒_𝐈𝐧𝐢𝐭() function. This crucial function initializes the uC/OS-II kernel and prepares the system for multitasking.
uC/OS-II is a compact, portable, and highly efficient RTOS designed specifically for embedded systems. It provides a robust framework for multitasking, task scheduling, inter-task communication, and resource management, making it an excellent choice for applications with real-time constraints.
uC/OS-II is built upon a preemptive, priority-based scheduling algorithm. This means that tasks with higher priorities are executed before lower-priority tasks, ensuring that time-critical operations receive immediate attention. The kernel’s small footprint and low interrupt latency make it suitable for a wide range of resource-constrained microcontrollers and microprocessors.
Tasks in uC/OS-II are lightweight and independent entities that execute specific functions or operations. Each task has its own stack space and execution context, allowing for efficient task switching and context switching. The kernel provides services such as task creation, deletion, suspension, and synchronization to facilitate task management. Following is the Software file structure followed in uC/OS-II continued with the explanation of an example on the working of OS_Init() in uC/OS-II.
The Software file structure in μ𝐂/𝐎𝐒-𝐈𝐈

μ𝐂/𝐎𝐒-𝐈𝐈 provides two macro functions to disable and enable the interrupts.
OS_ENTER_CRITICAL() and OS_EXIT_CRITICAL(), present in OS_CPU.h.
These are used a wrap whenever there is a need to access critical code section.
Pseudo Syntax:
#include”headers.h”
#define TASK1_STACK_SIZE 128
#define TASK2_STACK_SIZE 128
void task1(void *pdata)
{
while(1){
….
}
}
void task2(void *pdata)
{
while(1){
….
}
}
int main(void)
{
OSInit();
OSTaskCreate(task1,NULL,&task1_stack[TASK1_STACK_SIZE-1],0);
OSTaskCreate(task2,NULL,&task2_stack[TASK2_STACK_SIZE-1],0);
OSStart();
return 0;
}
OS_Init() initialized the uC/OS-II kernel and sets up the system for multitasking. This function must be called prior to creating any uC/OS-II object prior to calling OSStart(). OS_Init() is the first function that is called in main.
void OSInit (void)
{
#if OS_TASK_CREATE_EXT_EN > 0u
#if defined(OS_TLS_TBL_SIZE) && (OS_TLS_TBL_SIZE > 0u)
INT8U err;
#endif
#endif
OSInitHookBegin(); /* Call port specific initialization code */
OS_InitMisc(); /* Initialize miscellaneous variables */
OS_InitRdyList(); /* Initialize the Ready List */
OS_InitTCBList(); /* Initialize the free list of OS_TCBs */
OS_InitEventList(); /* Initialize the free list of OS_EVENTs */
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
OS_FlagInit(); /* Initialize the event flag structures */
#endif
#if (OS_MEM_EN > 0u) && (OS_MAX_MEM_PART > 0u)
OS_MemInit(); /* Initialize the memory manager */
#endif
#if (OS_Q_EN > 0u) && (OS_MAX_QS > 0u)
OS_QInit(); /* Initialize the message queue structures */
#endif
#if OS_TASK_CREATE_EXT_EN > 0u
#if defined(OS_TLS_TBL_SIZE) && (OS_TLS_TBL_SIZE > 0u)
OS_TLS_Init(&err); /* Initialize TLS, before creating tasks */
if (err != OS_ERR_NONE) {
return;
}
#endif
#endif
OS_InitTaskIdle(); /* Create the Idle Task */
#if OS_TASK_STAT_EN > 0u
OS_InitTaskStat(); /* Create the Statistic Task */
#endif
#if OS_TMR_EN > 0u
OSTmr_Init(); /* Initialize the Timer Manager */
#endif
OSInitHookEnd(); /* Call port specific init. code */
#if OS_DEBUG_EN > 0u
OSDebugInit();
#endif
}
1) The Kernel data structure is initialized by calling OSInitHookBegin(). This function is a user-defined hook that can be implemented to perform any additional initialization or setup required by the application.
- The OSInitHookBegin() calls the CPU_IntInit() that initializes the critical section objects.
- The Critical section is created by calling the CreateMutex() in CPU_IntInit(). The parameters for CreateMutex() by default are set to NULL,FALSE,NULL. Here CreateMutex() is a Windows API to create a mutex. The first parameter (Security attributes) is set to NULL indicating default security settings. The second parameter defines initial ownership state of the mutex, when set to FALSE, it is unowned. The third parameter is the mutex name, set to NULL indicating that the mutex is unnamed. It returns an object.
- After this, InitializeCriticalSection() is called to initialize the critical section. This is a windows API. The parameter it takes is the address of the object created by the CreateMutex() call.
2) OS_InitMisc() is called to initialize miscellaneous variables. It performs various initialization tasks related to miscellaneous features and options within the uC/OS-II kernel. The actions that are performed are:
a) Clear the 32-bit OS System clock.
b) Clear the interrupt nesting counter.
c) Clear the scheduling lock counter.
d) Clear the number of tasks.
e) Indicate the OS that the multitasking has not started.
f) Clear the context switch counter.
g) Let know the kernel that Statistic task is not ready.
h) Initialize the OSSafetyCriticalStartFlag(Related to windows) to FALSE indicating safety functionality is not yet enabled or active.
i) Initialize the task register ID.
3) OS_InitRdyList() is called to initialize the ready list. The ready list is the task of priorities maintained by the kernel. It keeps tracks of which tasks are ready to run based on the priority levels. It is called to ensure that the ready list is in clean state before the scheduler starts scheduling. All the values in the ready table is set to 0 by this function. The Ready table size is defined by the macro OS_RDY_TBL_SIZE in uC/OS-II . It is defined as:
#define OS_RDY_TBL_SIZE ((OS_LOWEST/PRIO) / 8u +1u). The OS_LOWEST is 63 that can be found in os_cfg_r.h of uC/OS-II source code.
4) OS_InitTCBList() is called to initialize the free list of OS_TCBs. The initialization process typically involves setting up the necessary data structures and variables to manage the free list. This includes linking the TCBs together in the list and configuring any associated fields or flags within the TCBs.
The free list of TCBs acts as a pool from which the uC/OS-II kernel can allocate TCBs for new tasks during runtime. When a new task is created, the kernel retrieves a TCB from the free list and initializes it with the necessary task-specific information, such as the task’s entry point, stack size, priority, and other relevant attributes.
Once a task has completed or is deleted, its TCB is returned to the free list, making it available for reuse by other tasks in the future. This allows for efficient task management and dynamic allocation of TCBs as needed.
5) OS_InitEventList() is called to initialize the free list of OS_EVENTs. It initializes the free list of event control blocks.
The free list of OS_EVENTs acts as a pool from which the uC/OS-II kernel can allocate OS_EVENTs for various synchronization objects during runtime. When a synchronization object (such as a semaphore or message queue) is created, the kernel retrieves an OS_EVENT from the free list and initializes it with the necessary information specific to the synchronization mechanism.
Once a synchronization object is no longer needed or is deleted, its corresponding OS_EVENT is returned to the free list, making it available for reuse by other synchronization objects in the future. This allows for efficient management and dynamic allocation of OS_EVENTs as needed.
The initialization of the free event control blocks starts by clearing the event table by calling the OS_MemCLr() function.
6) OS_FlagInit() is called to initialize the flag structures. Event flags allow tasks to wait for specific combinations of events to occur before proceeding with their execution. Tasks can wait for specific events by specifying a pattern or mask of event flags they are interested in. When the desired combination of event flags matches the specified pattern, the waiting task is signaled and can resume execution.
The event flag structures managed by OS_FlagInit() facilitate this synchronization mechanism. They provide the necessary storage and control mechanisms for event flag operations, such as setting, clearing, and testing event flags, as well as managing the tasks waiting for specific event flag patterns.
If the OS_MAX_FLAGS is set to 1 then only one event flag group is set. If more than 1 is set, then the function first clears the flag group table by calling the OS_MemClr() on OSFlagTbl. Once it’s done, the names of the flags are set to unknown (?) until given.
The structure used for this is:
typedef struct os_flag_grp { /* Event Flag
Group */
INT8U OSFlagType; /* Should be set to OS_EVENT_TYPE_FLAG */
void *OSFlagWaitList; /* Pointer to first NODE of task waiting on event flag */
OS_FLAGS OSFlagFlags; /* 8, 16 or 32 bit flags */
#if OS_FLAG_NAME_EN > 0u
INT8U *OSFlagName;
#endif
} OS_FLAG_GRP;
7) OS_MemInit() is called to Initialize the memory partition manager. This prepares the memory management subsystem for dynamic memory allocation and deallocation. If the OS_MAX_MEM_PART is set to more than 1, then. It clears the memory partition table for each memory partition, it sets the OSMemFreeList pointer to the next memory partition in the table, creating a linked list of free partitions. If memory partition names are enabled, it sets the name of each partition to “?”.
At last, Finally, the code sets OSMemFreeList to point to the beginning of the free list, which is the first memory partition in the table.
The structure used for this is:
struct os_mem { /* MEMORY CONTROL BLOCK */
#if (OS_OBJ_TYPE_REQ == DEF_ENABLED)
OS_OBJ_TYPE Type; /* Should be set to OS_OBJ_TYPE_MEM */
#endif
#if (OS_CFG_DBG_EN == DEF_ENABLED)
CPU_CHAR *NamePtr;
#endif
void *AddrPtr; /* Pointer to beginning of memory partition */
void *FreeListPtr; /* Pointer to list of free memory blocks */
OS_MEM_SIZE BlkSize; /* Size (in bytes) of each block of memory */
OS_MEM_QTY NbrMax; /* Total number of blocks in this partition */
OS_MEM_QTY NbrFree; /* Number of memory blocks remaining in this partition */
#if (OS_CFG_DBG_EN == DEF_ENABLED)
OS_MEM *DbgPrevPtr;
OS_MEM *DbgNextPtr;
#endif
#if (defined(OS_CFG_TRACE_EN) && (OS_CFG_TRACE_EN == DEF_ENABLED))
CPU_INT16U MemID; /* Unique ID for third-party debuggers and tracers. */
#endif
};
8) OS_QInit(): It is called to initialize the message queue module. A loop is executed to initialize the list of free QUEUE control blocks. For each queue in the table except the last one, it sets the OSQPtr field of the current queue (pq1) to point to the next queue (pq2). This establishes a linked list of free queues.
After the loop completes, the last queue in the table is handled separately. The OSQPtr field of the last queue is set to (OS_Q *)0, indicating the end of the linked list.
The OSQFreeList is set to point to the first element of the queue table, which represents the head of the free list of queues.
The structure used by this function is :
struct os_q { /* Message
Queue */
/* ------------------ GENERIC MEMBERS ------------------ */
#if (OS_OBJ_TYPE_REQ == DEF_ENABLED)
OS_OBJ_TYPE Type; /* Should be set to OS_OBJ_TYPE_Q */
#endif
#if (OS_CFG_DBG_EN == DEF_ENABLED)
CPU_CHAR *NamePtr; /* Pointer to Message Queue Name (NUL terminated ASCII) */
#endif
OS_PEND_LIST PendList; /* List of tasks waiting on message queue */
#if (OS_CFG_DBG_EN == DEF_ENABLED)
OS_Q *DbgPrevPtr;
OS_Q *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
/* ------------------ SPECIFIC MEMBERS ------------------ */
OS_MSG_Q MsgQ; /* List of messages */
};
9) OS_TLS_Init(): It is called to Initialize TLS before creating a task. TLS is nothing is Thread Local Storage. The purpose of initializing TLS before creating tasks is to ensure that TLS is set up correctly and ready to be used by tasks when they are created. TLS allows each task to have its own unique data that is local to that task and not shared with other tasks. By initializing TLS early in the system initialization process, the tasks can rely on having access to TLS when they start running.
10) OSInitTaskIdle(): It is called to create an ideal task. To ensure that the CPU is never left idle, an idle task is created by uC/OS-II using this function call. The function begins by checking OS_TASK_NAME_EN is enabled. If OS_TASK_CREATE_EXT_EN is defined/enabled, the idle task is created by using OSTaskCreateExt(), else it is created by using OSTaskCreate(). The entry point of the function is set to OS_TaskIdle.
OS_TaskIdle() is a function that runs when no other higher priority tasks execute . It allocates storage for CPU status register by using OS_CPU_SR type. It also initializes the tasks and prevents the compiler warning for not using p_arg. The stack for the idle task is specified based on the OS_STK_GROWTH configuration option. If OS_STK_GROWTH is set to 1, the stack pointer is set to the last element of the OSTaskIdleStk array. The idle task priority is set to OS_TASK_IDLE_PRIO which is the lowest. The final functionality is, if setting task names is enabled, set the name of the task by calling OSTaskNameSet() function.
11) OS_InitTaskStat() It is called to create the static task. It allows the programmer to gather the information about the execution time and other statistics of each task in the system. It first checks if the task static feature is enabled by comparing the macro OS_TASK_STAT_EN >0 . When enabled, it sets up the necessary data structures like OSTaskStatTbl[]. The priority is set to one higher than idle task by using the macro OS_TASK_STAT_PRIO, sets task stat id, sets bottom of the stack by passing address of OSTASKStatStk[0], no TCB extension and enabling stack checking and clearing as parameters to OSTaskCreate()/OSTaskCreateExt(). To collect the task statistics of a specific task, OSTCBCtrPtr should be made to set in the OS_TCB.
12) OSTmr_Init(): It is called to initialize the free list of the timer manager. (OS_TMR). This function uses the timer data type as seen below.
struct os_tmr {
#if (OS_OBJ_TYPE_REQ == DEF_ENABLED)
OS_OBJ_TYPE Type;
#endif
#if (OS_CFG_DBG_EN == DEF_ENABLED)
CPU_CHAR *NamePtr; /* Name to give the timer */
#endif
OS_TMR_CALLBACK_PTR CallbackPtr; /* Function to call when timer expires */
void *CallbackPtrArg; /* Argument to pass to function when timer expires */
OS_TMR *NextPtr; /* Double link list pointers */
OS_TMR *PrevPtr;
OS_TICK Remain; /* Amount of time remaining before timer expires */
OS_TICK Dly; /* Delay before start of repeat */
OS_TICK Period; /* Period to repeat timer */
OS_OPT Opt; /* Options (see OS_OPT_TMR_xxx) */
OS_STATE State;
#if (OS_CFG_DBG_EN == DEF_ENABLED)
OS_TMR *DbgPrevPtr;
OS_TMR *DbgNextPtr;
#endif
};
It is used to create and manage software timers in our application. Functions such as OSTmrStart(), OSTmrStop() and OSTmrSet() can be used to start, stop and set the timer.
The actions performed by this function include:
- Initializes the software timer tick counter (OSTmrTickCtr) to 0.
- Initializes the software timer list (OSTmrList), which is a linked list that holds all the active software timers.
- Initializes the software timer task control block (OSTmrTaskTCB), which represents the timer task responsible for managing the software timers.
- Creates the timer task by calling OSTaskCreate() or OSTaskCreateExt(), depending on the configuration (OS_TASK_CREATE_EXT_EN).
- Sets the timer task priority and stack size.
- Sets up the timer task stack (OSTmrTaskStk) with the specified size.
- If task names are enabled, it assigns a name to the timer task using OSTaskNameSet().
- Starts the timer task by calling OSTaskResume().
13) OSInitHookEnd(): Is called to call port specific initialization code and is called at the end of OSInit().
14) OSDebuInit(): It is called in the end if OS_DEBUG_EN is set or enabled. It is used to make sure that the debug variables that are unused in the application are not optimized away. This function can be optional and can be used for debugging.
This ends the working of OSInit() function that is called first and then begins the execution of tasks.
References:
uC/OS-II source code and user manual.
Environment:
Eclipse IDE on Ubuntu VM.
Article By: Yashwanth Naidu Tikkisetty
Follow #oswithyash on LinkedIn(T Yashwanth Naidu)to get updates related to Embedded Systems, Space and Technology.
