An improved method for processing uC/OS-II Task Stack
Abstract:In the μC/OS-II kernel, different tasks use independent stack space, the size of the stack is defined by the maximum stack depth required by each task, this method may cause a waste of stack space. This article describes how to share the continuous storage space among multiple tasks in RTOs as a task stack method, and compares the advantages and disadvantages of the two and their applicability in detail.
Keywords:μC/OS-II Task Stack RTOS shared space Stack
There are many articles about the real-time kernel of μC/OS-II and its application. For those who learn RTOS, this system is a good learning start point. Although the source code in the document [1] does not have a line number or function name cross-index table, it may cause some difficulties in reading the source code (you can use the grep search function of bc31 to improve reading efficiency ), fortunately, the Code is not very long, and there are detailed Chinese instructions. For those who have some x86 compilation and C language basics, they can still master it for a long time.
μC/OS-II kernel is a preemptive kernel, can switch between tasks, can also let a task not get a certain resource sleep for a certain period of time and then continue to run; provides signal lights for shared resource management, message queues and mailboxes for Process Communication, and even provides a memory management mechanism, a comprehensive system.
The μC/OS-II kernel is still worth improving in some places, for example, the system does not support time slice scheduling. If there is an endless loop code (or conditional loop Code) in a task, the code will be executed permanently (or for a long time) Here, And the Scheduler cannot control it, other tasks cannot be executed in a timely manner. This preemptive and non-preemptive system has the same problem. Of course, if this Code does not have a bug, the problem can be solved. In a preemptive system that does not provide time slice scheduling, the signal lights are generally used, or the method of task active sleep (for μC/OS-II, it is easy to transform to support time slice scheduling, as long as the Scheduled interruption service program calls osintctxsw () function ); A non-preemptive system generally uses a finite state machine instead of such a long loop of code. However, in any case, for RTOs users, this will make the coding of task functions as they wish.
Another improvement in the javasc/OS-II kernel is its job stack management approach. In the μC/OS-II kernel, different tasks use independent stack space, the size of the stack is defined by the maximum stack depth required by each task, this method may cause a waste of stack space. The following describes how to share a continuous storage space as a silly stack for multiple tasks in RTOs.
1. Switch the data to be saved
Simply put, a task can be considered as a running C function. For preemptive RTOS, various field data of the current task should be saved during task switching. Field data includes local variables, various CPU registers, stack pointers, and task pointers that are aborted by the program. CPU registers are used by any task code. For local variables, the general compiler arranges other tasks in the stack space, and the stack pointer is also common to all tasks. Therefore, you also need to save them.
For global variables, because they are usually fixed in the memory, the space occupied by each task is completely independent, so they do not need to be saved.
In the x86 environment, there are 14 16-bit CPU registers to be saved; 8 general-purpose registers (ax, BX, CX, dx, SP, BP, Si, Bi) four segment registers (CS, DS, es, SS), one instruction pointer IP address and one flag register Fr.
2 C compiler variable position in stack
For a C program with nested function calls, most compilers place the passed parameters and local variables of the function in the stack, and the compiler automatically generates a push) and pop code to save the running register of the upper-level function.
Assume that the function main () calls funl (), while funl () calls fun2 (), the stack image 1 (x86 CPU) is shown when the code in fun2 () is executed ).
For RTOS software, the various data in the stack is a task. Generally, the CPU has only one stack pointer sp. During task switching, the stack content used by the suspended task must be saved so that the task can continue to run from the original place when it is awakened next time.
Processing Method and defect of 3 μC/OS-II for Task Stack
μC/OS-II defines an array variable for each task as a stack to save the data in the task stack, and points the CPU Stack pointer SP to an element in the array when the task switches, that is, the top of the stack, as shown in figure 2.
For example, the task stack Statement defined in its ex21.c file is:
OS _stk taskstartstk [task_stk_size];/* Start the task stack */
OS _stk taskclkstk [task_stk_size];/* Clock Task Stack */
OS _stk tasklstk [task_stk_size];/* Task 1 #, task stack */
......
The preceding Task Stack Array variables are assigned to the ostcbstkptr variable of the task control block OS _tcb in the initialization function ostcbinit. During task switching, the μC/OS-II calls the osctxsw assembly process (OS _cpu_a.asm file) and points the SP pointer of the CPU to this variable so that each task uses an independent job stack.
Les BX, dword ptr ds: _ ostcbcur
; Save the stack pointer SP of the pending task
MoV ES: [bx + 2], SS
MoV ES: [bx + 0], SP
......
Lesb X, dword ptr ds: _ ostcbhighrdy; Switch SP to the stack space to run the task
MoV SS, ES: [bx + 2]
MoV sp, ES: [BX]
......
In the code, the values of the variable ostcbhighrdy (ostcbcur) and the stack pointer variable ostcbstkptr are the same, because ostcbstkptr is the first variable in the structure ostcbhighrdy.
The disadvantage of this method is that it may cause a waste of space. This is because a task cannot run if its stack is full, even if the stacks of other tasks are available. Of course, the advantage of this method is that the task stack switching time is very short and only a few commands are needed.
4. Stack Processing Method for shared space
(1) stack shared continuous storage space
If multiple tasks use the same continuous space as the stack, each stack can be used together. As mentioned earlier, the problem with shared space is that a task cannot destroy the stack data of other tasks during running. For simplicity, first look at the two tasks shown in figure 3.
Assume that task stack is empty when Task 1 is run for the first time. After running for a period of time, Task 2 runs, and the stack space continues to grow. You do not need to modify the sp value of the CPU for this task switch, but you need to write down Task 1's top stack position SP1 (in figure 3 ).
After Task 2 runs for a while, RTOS switches to Task 1 again. During switching, you cannot simply change the SP pointer back to the SP1 value, because the data in Task 2 stack will be damaged when the stack grows up. The solution is to move the data stored in the previous 1 Service Stack to the top of the stack, and move the data in Task 2 stack to the bottom of the stack, the stack pointer SP does not need to be modified (figure 3 right ).
Considering the more general situation, there are n tasks, the current running task is K, and the next running task is J. The tasks that must be done when sharing the task stack are:
* Define two stack pointers for each task: Stack top and stack bottom;
* During task switching, the stack content of task J to be run is moved to the top position of the stack. At the same time, the task stack above the stack is moved down to modify the Task Stack pointer of the stack to be moved.
Assume that the defined stack space and the stack pointer variable of the task are:
Void taskstk [max_stk_len];/* task stack space */
Typedef struct taskstkpoint {
Int taskid;
Int ptopstk;
Int pbottomstk;
} Task_stk_point;
Task_stk_point ptaskstk [max_task_num];/* store the stack top and bottom pointer of each task */
The number of elements in the Task Stack pointer array ptaskstk is the same as the number of tasks. For stack switching, another temporary storage space is required. Its size can be defined according to the maximum length of a single task stack, which is used to transfer stack switching content. The stack content exchange pseudo C algorithm can be written as follows:
Stkeechange (INT curtaskid, int runtaskid)
{/* The two parameters are the current running task number and the next running task number */
Void tempstk [max_per_stk_len];/* Note that the variable length can be less than taskstk */
L = the stack length of the task runtasktd;
① Move the L-byte at the top of taskstk to tempstk;
② Move the stack content of the runtaskid task to the top of taskstk;
③ Move all content above the runtaskid stack (before moving) down to 1 byte;
④ Modify all Task Stack top and bottom pointer (ptaskstk variable) above the runtask stack (before moving );
};
The average time complexity of this algorithm can be calculated as follows:
O (t) = SL/2 + SL/2 + SL × n/2
In formula, the first and second items are steps 1 and 2, and the third item is Step 3. SL indicates the maximum length of each stack (max_per_stk_len), and N indicates the number of tasks.
If the SL value is 64 bytes and the number of tasks is 16, the average number of data items is 576. Assuming that the instruction time is 2 μs, the moving time of a task stack is about 1 ms. Therefore, when using this method, we should carefully repeat the encoding to minimize the execution time.
In terms of space, the shared task stack is superior to the Independent Task Stack. Assuming that each stack space in the independent task stack method is K and the number of tasks is N, the total stack space of the independent task stack method is n × K. When sharing the task stack and considering the complementary tasks, the taskstk variable does not need to be defined as n × K length, and may be defined as 1/2 or smaller.
In addition, this method does not need to modify the SP pointer of the CPU During task switching.
(2) work stack and Task Stack
In the previous section, the stack content exchange algorithm for task switching is complex and takes a long time. In addition, a work stack is designed to be used by the currently running task. During task switching, the stack content is exchanged for another storage space, this space can be dynamically applied, and its size can be as needed.
This method looks similar to the independent task stack method. It requires n + 1 storage space, one of which is used for the work stack space. Compared with the Independent Task Stack, there are two differences:
① The SP pointer always points to the same storage space, that is, the Work stack;
② The size of each task stack does not need to be defined by the maximum space. You can dynamically allocate space from the memory according to the actual size.
For a 8031 processor structure, the stack pointer can only point to its internal memory, and the size is very limited. In this way, you can set the work stack to internal RAM, set the task stack to external ram, and expand the stack space.
Compared with the previous stack sharing method, the switching time of this method is shorter, and the time complexity is about 1.5 times the maximum Task Stack length.
5. Summary
The independent task stack method is suitable for scenarios with sufficient memory, frequent task switching, and high requirement on task switching time. It is generally used in a 16-bit or 32-bit microprocessor platform environment. It is worth noting that, in some microprocessor, although the available data storage can be designed to be large, the memory available for the stack is limited. For example, for the 8031 series memory, the stack can only use the internal 128-byte data storage. Even if the system has 64 K bytes of external data storage, the total space of the task stack cannot exceed 128 bytes. This type of processor uses the RTOS with the shared task stack structure, which is better.
Because the shared task stack system requires a long task switching time and is not suitable for scenarios with frequent task switching, in many embedded systems, only a few tasks will be running for a long time, other tasks run under specific conditions. RTOS users can also divide tasks appropriately to reduce the task switching time.
Regardless of the method used, the length of the task stack should be calculated carefully when the storage space is limited. The calculation is based on the number of function embeddings in the task and the length of the local variable of the function. For shared task stacks, you must also consider the maximum number of tasks in both running and suspended states. Some compilers can generate the stack overflow check code. During debugging, you can turn on the compile switch to test the actual stack length.