In a multi-task application based on Micrium's µC/OS-II or µC/OS-III, the C stack is used in
main() and task stacks are used thereafter.
When writing application code for one of Micrium's real-time kernels, developers must declare a stack for each of their tasks. These task stacks are used by the kernel to save context (CPU registers) when passing control of the CPU from one task to another, and they are also ultimately used by the tasks' own code during function calls, in place of the C stack. Does the presence of these stacks, then, mean that the C stack is not needed in a system that incorporates µC/OS-II or µC/OS-III? In the following post, we will answer this question and, in doing so, we will examine some important consequences that the kernels' stack usage holds for application code.
The short answer to the question of whether or not the C stack is necessary is yes, it is used by the C code that executes prior to the start of multi-tasking. There may be a variety of task stacks in your kernel-based application, but when your processor is executing in
main(), it's normally the case that none of these stacks is being used. It is only when an application's first task begins to run that stack usage shifts, at least in part, from the C stack.
Since stack conventions vary across microcontrollers—with many 32-bit devices now supporting more than one stack—the details of the transition from the C stack to task stacks are hardware dependent. ARM Cortex-M cores, for example, feature two stack pointers, to the cores' Main and Process stacks. In a system based on µC/OS-III, the Main stack is in use prior to the start of multi-tasking, during which time it references the stack area defined in the system's linker command file. When application code calls the kernel API function
OSStart() in order to initiate multi-tasking, the Process stack pointer is set to the top of the first task's stack, and a CPU control register is written in order to enable that stack to be used when the task begins to run. The Main stack pointer, meanwhile, is made to point to µC/OS-III's own interrupt stack,
OSCfg_ISRStk. For the remainder of the system's execution, this stack will be used exclusively in interrupt and exception handlers, while the task stacks will be used elsewhere.
One result of the reliance on the C stack in
main() is that, as a developer, you must make sure sufficient space is set aside for this stack. As indicated above, µC/OS-III users must also be aware of the size of
OSCfg_ISRStk. (This additional stack does not exist in µC/OS-II, the ports for which take a slightly different approach to interrupt stack usage than that described for the Cortex-M cores.)
OSCfg_ISRStk is simply an array, much like a task stack, and its size is determined by the kernel configuration constant
OS_CFG_ISR_STK_SIZE. The size of the C stack, on the other hand, is typically provided directly to the linker, so the mechanism for making changes to this stack are not uniform across development environments.
In IAR Embedded Workbench for ARM cores, the stack used during
main() on Cortex-M-based systems is typically named
CSTACK and is defined in a linker command file having an icf extension. The size of this stack can be modified through a simple, one-line edit to the file, as indicated below. With other versions of Embedded Workbench, such as that for the Renesas RX, stack configuration is possible through IDE menus, making direct changes to the linker file unnecessary.
Although the size of the C stack is not entirely unworthy of developer attention, it's unlikely that you'll need to make any changes to this memory space unless your implementation of
main() deviates substantially from those provided with Micriµm's example projects. Each of these projects typically allocates more than enough space for the function calls that
main() makes. The examples' definition of the interrupt stack size,
OS_CFG_ISR_STK_SIZE, may necessitate a bit of tweaking, however. For architectures that utilize the interrupt stack, the examples' default size should be fine as long as you're not planning to supplement the provided interrupt handlers with any relatively complex functions of your own. Not all µC/OS-III kernel ports utilize the interrupt stack, though. On the Renesas RX, for example, the C stack is used for interrupts following the completion of
main(). In this case, a value of
0 would work perfectly well for
OS_CFG_ISR_STK_SIZE—and would be preferred in order to minimize memory usage.
One additional stack-related concern that bears mentioning involves the impact that the presence of multiple task stacks can have on debugging tools. Within IAR Embedded Workbench, there are kernel-awareness packages for µC/OS-II and µC/OS-III that are capable of displaying a variety of task-specific statistics, including the high-water mark for each of a system's task stacks. The IDE also, however, provides a simple stack checker that produces warning messages anytime the stack pointer currently in use by the CPU exceeds bounds derived from the current project's linker file. This feature will regularly result in warnings in healthy (non-malfunctioning) kernel-based projects, because, as we've seen, the linker-defined stack is used only over a limited span of code in such projects.
Fortunately, if you view the false-positive stack warnings as an annoyance, they are relatively easy to suppress. Embedded Workbench's IDE Options dialog, which is accessible via the Options… entry in the Tools menu, provides a means of adjusting a variety of parameters that affect the appearance and functionality of the IDE. To eliminate the warning messages for the stack, you should simply clear the box for Warn when stack pointer is out of bounds within this dialog's Stack options, as shown below.
Of course, the procedure for eliminating any unwarranted messages that might originate from other development environments offering a stack checker would differ from that applicable to IAR. What's important to keep in mind, though, is that such messages are simply a reflection of the stack arrangements that exist for systems built around µC/OS-II and µC/OS-III. In general, these systems rely on the C stack over the course of
main(), and they switch to task stacks once
OSStart() is called in order to begin multi-tasking. For additional information on the way that µC/OS-II and µC/OS-III utilize stacks, you can consult the comprehensive kernel documentation that Micriµm provides online. Upcoming versions of µC/OS-III will offer enhanced mechanisms for managing stacks, and you can always count on such changes to be reflected in the online documentation.