How to design a small embedded operating system

Foreword

The purpose of this paper is to design a simple embedded operating system that only implements the functionality of a basic task scheduler. Although it can not be called an operating system, it has embodied the essence of a small embedded operating system, which can be used to see the face of the operating system.

First, multi-task mechanism

In fact, in the case of a single CPU, there is no real multi-tasking mechanism. Only the different tasks use the CPU in turn, so it is essentially single-task. But because the CPU executes very fast, and the task switching is very frequent and the switching is fast, we feel like there are many tasks running at the same time. This is the so-called multitasking mechanism.

The real-time system is characterized by a delay that is predictable and can react to certain signals for a specified period of time (usually at the ms level).

Second, the status of the task

The task has the following characteristics: the task is not always ready to run, and an already running task does not guarantee that the CPU will remain in use until it is finished. Generally there are ready state, running state, suspended state, and so on.

Run state: A running task is a task that is using a CPU. There is one and only one running task at any time.

Ready state: A ready state task is operational, waiting for the task that holds the CPU to release the CPU.

Suspended state: A state in which certain conditions are not met and hangs and cannot be run.

Third, how to transform into a ready state

INT32U OSRdyTbl; /* Ready Task Table */ Define a 32-bit variable above, each representing a task, 0 for the pending state and 1 for the ready state. It records the ready or not status of each task and calls it the ready list. OSRdyTbl is defined as a 32-bit variable for 32 tasks. Of course, if you define 64-bit, you can support up to 64 tasks. In this way, you can define two macros to change the state of the task to ready or suspended.

/* Register the ready task in the ready list */#define OSSetPrioRdy(prio) { OSRdyTbl |= 0x01<

/* Remove the task from the ready list */#define OSDelPrioRdy(prio) { OSRdyTbl &= ~(0x01<

Tasks are independent of each other and there is no relationship to each other. All tasks are logically equal. Since the tasks are invisible to each other, the transmission of information between them cannot be done in person. This requires various communication mechanisms such as semaphores, message mailboxes, queues, etc. to achieve.

Fourth, what is preemptive scheduling?

The concept of scheduling, in layman's terms, is that the system selects the appropriate task execution among multiple tasks. How does the system know when to perform which task? Each task can be assigned a unique priority level, and when multiple tasks are ready at the same time, the higher priority tasks are prioritized. At the same time, the priority of the task is also used as the unique identification number of the task. The code is all about the identification number to complete the operation of the task.

The so-called "preemptive scheduling" means that once a task with a higher priority appears in the ready state, the running right of the current task is immediately deprived, and the CPU is assigned to a higher priority task. This way the CPU always executes the task with the highest priority in the ready state.

V. Time management of multitasking system

Like humans, multitasking systems also need a "heartbeat" to keep them running. This heartbeat is called a clock beat and is usually played by a timer that produces a fixed-cycle interrupt.

The OSTimeDly function is delayed based on the clock beat (in the interrupt service function of the clock, the number of delay beats of each delay task is decremented by one in turn. If the number of delay beats of a task becomes 0, then Set it from the suspended state to the ready state.). The function completion function is very simple. It is to suspend the current task, set the number of delay beats, and then perform task switching. After the specified number of clock beats arrives, the current task is restored to the ready state. Tasks must give up CPU usage (delay or wait events) via OSTimeDly or OSTaskSuspend, giving lower priority tasks a chance to run.

How to achieve multi-tasking?

How can I implement multiple independent programs at the same time with only one CPU? To achieve multitasking, the condition is that each task is independent of each other. How can people be independent and have their own private property. The same is true for tasks. If a task has its own CPU, stack, program code, and data store, then this task is an independent task. (CPU is obtained through multitasking mechanism, others need you to allocate)

TIPS:

If a task is running a public function (such as Printf) and is preempted by another high-priority task, then when the high-priority task also calls the same public function, it is very likely to destroy the data of the original task. Because two tasks may share a set of data. To prevent this from happening, two measures are often used: reentrant design and mutual exclusion calls.

All variables in the reentrant function are local variables. Local variables allocate space temporarily when they are called. Therefore, when different tasks call the function at different times, the storage space allocated by the same local variable is not the same ( In the task private stack), do not interfere with each other. In addition, if the reentrant function calls other functions, the called functions must also be reentrant functions.

Methods for implementing mutually exclusive (exclusive) access are related to interrupts, off scheduling, mutual semaphores, counting semaphores, and so on.

6.1 How does a task have its own program code?

For how to implement multitasking, the first is the program code. The program code of each task is the same as the function. Like the streaking program of 51, each task is a big loop. Then there is the data storage area. Since the global variables are shared by the system, each task is shared, not the task is private. So the data storage area here refers to the private variable of the task. How to become private? Local variables are also. The compiler saves local variables on the stack, so it's easy to do so, as long as the task has a private stack.

TIPS: A critical resource is a shared resource that is allowed to be used by only one task at a time. The procedure for accessing critical resources in each task is called the critical section.

In a multitasking system, in order to ensure the reliability and integrity of data, shared resources must be mutually exclusive (exclusive) access, so global variables (except read-only) cannot have multiple tasks at the same time, that is, when a task is accessed. Interrupted by other tasks. Shared resources are a critical resource.

6.2 How a task has its own stack, data storage area

The role of the private stack is to store local variables, the parameters of the function, which is a linear space, so you can apply a static array, point the top pointer SP to the first element of the stack's array (increment stack) or the last element (decrement stack) ). You can create an artificial stack. Each task also has a variable that records its own top-of-stack pointer and is stored in the Task Control Block (TCB).

What is a task control block?

Each task in the system has a task control block, and the task control block records the environment in which the task is executed. The task control block here is relatively simple, and only contains the stack pointer of the task and the number of task delay beats. The task control block is the ID of the task. It links the program of the task to the data and finds it to get all the resources of the task.

6.3 How does a task have its own CPU?

Finally, let's see how the task "owns" its own CPU. There is only one CPU, each task is shared and used in turn. How can it be achieved? Let's first take a look at the interrupt process. When the interrupt comes, the CPU saves the current program's running address, register and other field data (usually stored on the stack), and then jumps to the interrupt service routine. After the execution is completed, the previously saved data is loaded back to the CPU and returned to the original program execution. This allows crossover of two different programs.

Can you achieve multitasking with this kind of thinking? The task switching operation can be realized by simulating the interrupt process. When the task is switched, the live data of the current task is saved in its own task stack, and the data of the task to be run is loaded from the task stack to the CPU, and the CPU, SP, and register of the CPU are changed. It can be said that the switching of tasks is the switching of the task running environment. The task's runtime environment is stored in the task stack. That is, the key to task switching is to assign the task's private stack pointer to the processor's stack pointer SP.

Create a task. It receives three parameters, the entry address of the task, the first address of the task stack, and the priority of the task. After calling this function, the system initializes the task stack according to the parameters given by the user, and saves the stack top pointer to the task control block, and marks the task as ready in the task ready list. Finally return, such a task is created successfully.

When a task is about to run, it is popped into the appropriate location on the CPU by fetching its stack pointer (saved in the task control block).

6.4 How to implement preemptive scheduling?

Preemptive scheduling based on task priority, that is, once the highest priority task is in the ready state, it immediately preempts the processor resources of the running low priority task. In order to ensure that the CPU always executes the task with the highest priority under the ready condition, whenever the task status changes, it is judged whether the currently running task is the highest priority in the ready task, otherwise the task is switched.

When will the mission status change? There are two cases:

1. A high-priority task requires a certain resource or delay to actively suspend the request and suspend the processor. At this time, the low-priority task in the ready state is executed. This scheduling is called task-level switching. This is the case if the task executes OSTimeDly() or OSTaskSuspend() to suspend itself.

2. High-priority tasks Because the clock tick comes, or after the interrupt processing ends, the kernel finds that the higher-priority task has obtained the execution condition (such as the delayed clock arrival), then directly switches to the higher priority after the interrupt. Task execution. This scheduling is also referred to as interrupt level switching.

6.5 Suspend/Resume Task

1 suspend the task

A task can be suspended by OSTaskSuspend(). OSTaskSuspend() will remove the task from the task ready list and finally restart the system schedule. This function can suspend the task itself or suspend other tasks.

2 recovery task (OSTaskResume ())

Tasks suspended by OSTaskSuspend or OSTimeDly can be restored to the ready state and then task scheduled.

lens hood

Lens Hood,Lens Accessories,Camera Lens Hood,Camera Hood

Shaoxing Shangyu Kenuo Photographic Equipment Factory , https://www.kernelphoto.com