Start the UCOS task creation procedure:
1. initialize the task stack.
2. initialize the task control block.
3. Set the created task to the ready state (that is, the ready table)
We have already mentioned the task stack, control block, and ready table mentioned above. Let's look at the Code directly below.
Int8u ostaskcreate (void (* task) (void * PD), void * p_arg, OS _stk * ptos, int8u PRIO)
{
OS _stk * PSP;
Int8u err;
# If OS _critical_method = 3/* allocate storage for CPU Status Register */
OS _cpu_sr cpu_sr;
Cpu_sr = 0;/* prevent compiler warning */
# Endif
# If OS _arg_chk_en> 0
If (PRIO> OS _lowest_prio) {/* Make sure priority is within allowable range */
Return (OS _prio_invalid );
}
# Endif
OS _enter_critical (); // close the interrupt
If (osintnesting> 0) {/* Make sure we don't create the task from within an ISR */
OS _exit_critical ();
Return (OS _err_task_create_isr );
}
If (ostcbpriotbl [PRIO] = (OS _tcb *) 0) {/* Make sure task doesn't already exist at this priority */
Ostcbpriotbl [PRIO] = (OS _tcb *) 1;/* reserve the priority to prevent others from doing ...*/
/*... The same thing until task is created .*/
OS _exit_critical ();
PSP = (OS _stk *) ostaskstkinit (task, p_arg, ptos, 0);/* initialize the task's stack * // initialize the stack of the task
Err = OS _tcbinit (PRIO, PSP, (OS _stk *) 0, 0, 0, (void *) 0, 0 );
If (ERR = OS _no_err ){
If (osrunning = true) {/* Find highest priority task if multitasking has started */
OS _sched (); // If UCOS has started task scheduling when creating a task, you need to schedule the task after creating the task.
}
} Else {
OS _enter_critical ();
Ostcbpriotbl [PRIO] = (OS _tcb *) 0;/* Make this priority available to others */
OS _exit_critical ();
}
Return (ERR );
}
OS _exit_critical ();
Return (OS _prio_exist );
}
The following is the stack initialization function:
OS _stk * ostaskstkinit (void (* task) (void * PD), void * pdata, OS _stk * ptos, int16u OPT)
{// The function requires four parameters: a pointer to the task function, parameters required during task running, stack pointer, and extended parameters.
Unsigned int * STK;
STK = (unsigned int *) ptos;/* load Stack pointer */
// Use_arg (OPT );
Opt ++;
/* Build a stack for the new task */
* -- STK = (unsigned INT) task;/* pC */
* -- STK = (unsigned INT) task;/* LR */
* -- STK = 12;/* R12 */
* -- STK = 11;/* R11 */
* -- STK = 10;/* R10 */
* -- STK = 9;/* R9 */
* -- STK = 8;/* R8 */
* -- STK = 7;/* R7 */
* -- STK = 6;/* R6 */
* -- STK = 5;/* R5 */
* -- STK = 4;/* R4 */
* -- STK = 3;/* R3 */
* -- STK = 2;/* R2 */
* -- STK = 1;/* R1 */
* -- STK = (unsigned INT) pdata;/* R0 */
* -- STK = (supmode);/* CPSR */
* -- STK = (supmode);/* spsr */
Return (OS _stk *) STK );
}
We have already mentioned about the stack. Here, the full stack is used in descending order.
A task is actually an infinite loop function. How can we control its operation? This is what the operating system needs to do, the operating system schedules and switches tasks based on scheduling algorithms. Multiple tasks are shared with the CPU.
We already know the importance of stack to tasks. 1. Stack space is required for C language execution. 2. When a task is switched, you need to save the running scene to the task stack.
That is to say, the task stack should save the function call and the status information when the task is interrupted. But the problem arises. When we create a task, this task has not been run. This is easy to handle. We will simulate the signs that the task has been interrupted. If the task has not been executed, the function call stack frame will no longer exist. We only simulate saving the function runtime environment. Looking at the code above, we first save PC and LR, because the task function has not been executed, so this PC and LR should be the first address of the function, that is, the name pointer of the function. For example, you have defined a task function.
Void task1 (void * ID)
{
For (;;){
Printf ("Run task1 \ n ");
Ostimedly (1000 );
}
}
Then assign the task1 function pointer to PC and LR. Next is R1 ~ R12: Since the functions have not been executed, the values of these general registers are not very important. You can assign values as you like. Here, we assign values to R1: 1, assign 2 to r2 ............ Assign 12 values to R12. Of course, you can also assign other values to these registers, which is irrelevant, but when the task runs, it is necessary to save the program execution site according to the regulations, that is to say, when these registers are switched, the values in them should be saved. The CPSR and spsr will be initialized below. These two values should be interrupted when the task is running in the processor mode according to your operating system. You assign the CPSR value to supmode (the macro value is 0x13), that is, the task is in SVC mode and is interrupted. We have said that CPSR Interruption During UCOS Initialization is always closed, and the CPSR interrupt bit is that it is interrupted when the highest priority task is running.
In a word, initializing the Task Stack is a precaution.
Therefore, the "initialization stack" is actually a "precaution. There are two points in this process that must be carefully considered. One is how to locate the PC, and the other is how to handle other CPU registers (except the PC. First, because the task is run for the first time, and the task is essentially a piece of code, the PC pointer should be located at the first line of the Code, that is, the so-called entry point address, which is saved by the task pointer, so you can assign the pointer value to the PC. Second, this code has not been executed, so the variables in the Code and other registers of the CPU has no relationship, so the R0-R12, R14 can be random value, or no value can also be, keeping these registers with their original values is obviously easier for the latter. Finally, assign a value to CPSR. You can run the system in system mode or management mode as needed.
With regard to the initialization task control block code, I think we should be easy to understand with the previous knowledge about TCB. We say that we have created a task, and its unique identifier is to create a new task control block. The initialization operation of this task control block is to assign the corresponding values to each option. As for the specific meaning of each option, the source code has a detailed description. If you do not understand it, you can refer to Shao Beibei's translation book.
After the task is created, set the task to ready. I don't want to talk about how to implement a ready table. The implementation principle of the ready table described above should be carefully checked. If you understand the principle, the implementation is to use the knowledge of bit operations in C language. Including or operations, and operations. Check the source code!
Now we know how to create a task.
Let's assume that we have created four tasks, one system-level task, two user tasks, and one idle task.
Next we will explain how UCOS started multi-task scheduling.
This system-level task is the task with the highest priority. During UCOS scheduling, the task with the highest priority is found and started to run. This system-level task has its own special task, namely, one, set osrunning to true, which indicates that UCOS starts scheduling. 2. Enable the timer and the timer interrupt response.
This is the code for running the most system-level task:
Static void sys_task (void * ID)
{
Osrunning = true; // begin OS
Uhalr_installsystemtimer ();
Printk ("START system task. \ n ");
For (;;)
{
Ostimedly (1000 );
}
}
However, this task will not be executed after it is created, but will not be executed until the UCOS operating system schedules the task.
After the task is created, osstart is called to start the function of multi-task scheduling.
Void osstart (void)
{
Int8u y;
Int8u X;
If (osrunning = false ){
Y = osunmaptbl [osrdygrp];/* Find highest priority's task priority number */
X = osunmaptbl [osrdytbl [y];
Ospriohighrdy = (int8u) (Y <3) + x );
Ospriocur = ospriohighrdy;
Ostcbhighrdy = ostcbpriotbl [ospriohighrdy];/* point to highest priority task ready to run */
Ostcbcur = ostcbhighrdy;
Osstarthighrdy ();/* execute target specific code to start task */
}
}
The main task of this function is to find the control block of the task with the highest priority, and then restore the content in the stack to each register based on the stack content when the task is created, the task with the highest priority starts to run. At this time, the UCOS operating system has mastered the overall operation.
The highest priority is found based on the two variables osrdygrp and osrdytbl []. As for the method, we have explained in the related content section of the ready table. If you know the highest priority, you will know the TCB of the highest priority task. If you know the TCB, you will get the task stack. Restores the task stack of this task to various registers, and the task starts to run.
Osstarthighrdy (); this function is written in a sink. It is very easy to recover the register field.
Osstarthighrdy
LDR R4, addr_ostcbcur; get current task TCB address
LDR R5, addr_ostcbhighrdy; get highest priority task TCB address
LDR R5, [R5]; get Stack pointer
LDR sp, [R5]; switch to the new stack
STR R5, [R4]; set new current task TCB address
Ldmfd SP !, {R4}; yyy
MSR spsr_cxsf, r4
Ldmfd SP !, {R4}; get new State from top of the stack
MSR cpsr_cxsf, R4; CPSR shoshould be svc32mode
Ldmfd SP !, {R0-r12, LR, PC}; start the new task
The Code should be analyzed by yourself.
Now, the task with the highest priority has started to run. What should it do? The Code has already been posted.
Let's talk about this timer.
As mentioned above, the core of the UCOS operating system is the timer, and the timer is the heart of UCOS.
There are many reasons for process switching. If a process has to wait for a resource but the resource has not yet appeared, the process will be suspended and another process will be executed. The most common cause of task switching is described. Time, there are many controls in the operating system based on time. For example, if you have no resources to execute a task, I cannot keep you running. If you keep running, it violates the original operating system design. Therefore, any task has a time limit. Switch as soon as the time is reached. Who remembers that time? Of course, it is a timer, and the timer generates corresponding interruptions at a fixed interval to prompt how long the operating system has been running. The clock interruption is like the heartbeat of the operating system.
In our current state, the created task does not need to wait for other resources except the processor. One reason for scheduling is that the task voluntarily gives up CPU usage, at this time, task-level task switching occurs. For example, if there is a task latency function in UCOS, this function will suspend the current task and delay the specified time. Who records the time, of course, is a timer. When the time is reached, task switching will occur. Of course, this is an interruption-level task switching.
Since the timer is so important, let's see how UCOS uses the timer interrupt service program to implement these functions.
First, the timer interrupt entry is the arm's External Interrupt entry. When the timer time is reached, a timer interrupt occurs. By judging the content of the interrupt timer, the timer jumps to the specified interrupt service program for execution. As for how many interrupt sources use an entry, how can we identify which interrupt has come? This is the relevant chapter of the arm interrupt controller. Datasheet Chapter 1 interrupt controllor.
As mentioned above, in the UCOS project, all external interrupt service programs are written into different interrupt structures for unification. These interrupt structs have some simple initialization during UCOS initialization.
A system-level task calls such a function:
Setisr_interrupt (irq_timer4, timertickhandle, null );
This function initializes the interrupt service program related to the timer, and opens the mask register flag of the timer interrupt. We know that the interruption of the CPSR of our arm is forbidden at the beginning, which is opened when the system-level task is running (remember the contents of the stack CPSR when the plan is coming soon ). Now the mask register flag of the timer interrupt is enabled again. Well, the original dual-layer protection is enabled. From now on, UCOS starts to respond to the timer interrupt. What have you done to interrupt the service program?
See the following code,
Unsigned int IRQ = getisroffsetclr (); // get the offset address of the interrupt vector
IRQ = fixup_irq (IRQ );
If (IRQ> = nr_irqs)
Return;
If (interruptfunc [IRQ]. interrupthandlers = NULL ){
Interruptfunc [IRQ]. ack_irq (IRQ); // clear pending
Return;
}
Osintenter ();
// Call interrupt service routine
Interruptfunc [IRQ]. interrupthandlers (IRQ, interruptfunc [IRQ]. data );
Interruptfunc [IRQ]. ack_irq (IRQ); // clear pending
Osintexit ();
}
Get the offset of the interrupted service program, that is, determine the external interrupt. Then execute the response interrupt service program, and then enter the UCOS interrupt osintenter ();, there is nothing to do here, just add the variable osintnesting. Then osintexit (); there is more work here. If a higher priority task is ready, it is directly switched to a higher priority task for execution, that is, the interruption-level task switching occurs here.
The last release was interrupted.
What are the specific operations of the timer to interrupt the Service Program (that is, the ostimetick () function?
The following is some code:
Ptcb = ostcblist;/* point at first TCB in TCB list */
While (ptcb-> ostcbprio! = OS _idle_prio) {/* go through all TCBs in TCB list */
OS _enter_critical ();
If (ptcb-> ostcbdly! = 0) {/* No, delayed or waiting for event with */
If (-- ptcb-> ostcbdly = 0) {/* decrement NBR of ticks to end of delay */
/* Check for timeout */
If (ptcb-> ostcbstat & OS _stat_pend_any )! = OS _stat_rdy ){
Ptcb-> ostcbstat & = ~ OS _stat_pend_any;/* Yes, clear status flag */
Ptcb-> ostcbpendto = true;/* Indicate pend timeout */
} Else {
Ptcb-> ostcbpendto = false;
}
If (ptcb-> ostcbstat & OS _stat_suspend) = OS _stat_rdy) {/* is task suincluded? */
Osrdygrp | = ptcb-> ostcbbity;/* No, make ready */
Osrdytbl [ptcb-> ostcby] | = ptcb-> ostcbbitx;
}
}
}
Ptcb = ptcb-> ostcbnext;/* point at next TCB in TCB list */
OS _exit_critical ();
Through Reading and simple analysis, you can know that the timer interrupts the service program, that is, the ostcbdly is reduced by one for all the TCB tasks that call the delay function. The ostimedly function suspends the current task and assigns a value to ostcbdly in TCB of the task. When this value is reduced to one, it indicates that the delay time of the task is reached. The task is set to ready and waiting for scheduling.
To sum up, we know. The tasks we create now are all self-called ostimedly (); the function automatically gives up the CPU right, and then interrupts the function timing through the timer. When the delay time arrives, the task is set to ready, wait for scheduling and switch.
In this example, there are two reasons for scheduling: 1. A task voluntarily calls ostimedly () to schedule and switch a task, and the timer interrupt service program is responsible for timing the delay, when the time arrives, the table is set to ready, and task scheduling and switching are generated.
For the running status of these four tasks, refer to the code I printed using the program to describe the analysis:
Multi-task scheduling instance. txt
It can be found that the program has been interrupted by the timer, and the timer CPU is occupied by idle tasks for most of the time. Because our three tasks are delayed after a piece of code is executed. Idle tasks are not delayed. It is forced to give up the CPU usage right in the timer interrupt service program. Please carefully analyze this multi-task scheduling instance to form a macro phenomenon in your mind, which is conducive to the overall understanding of UCOS.
In the next chapter, we will carefully analyze how to implement the job-level task switching and interrupt-level task switching on ARM9. because these two functions are both written in assembly, there are some relationships with hardware, which may be hard to understand. We will give a detailed explanation. See the following chapter.