Real-time systems primarily include: multitasking scheduling (using priority preemption), task synchronization, and inter-process communication mechanisms.
A multitasking environment allows real-time applications to be constructed in a set of independent tasks, each with its own execution thread and a set of system resources. The inter-process communication mechanism allows these tasks to synchronize and coordinate their behaviors. Wind uses interrupt-driven and priority-based methods. This shortens the time overhead of context switching and the delay of interrupts. In VxWorks, any routine can be launched as an independent task, with its own context and stack. There are also other task mechanisms that allow tasks to suspend, resume, delete, delay, or change priority.
Another important aspect is hardware interrupt handling. Hardware generates interrupts, notifying the system to call the corresponding interrupt service routine (ISR). To ensure the system responds as quickly as possible, the ISR runs in its own independent context and stack. Its priority is higher than any task priority.
Interrupt Latency refers to the time from when a hardware interrupt occurs to when the first instruction of the interrupt handler begins execution.
Priority-Driven means that in a multitasking system, the currently running task always has the highest priority among ready tasks.
Multitasking Scheduling
There are two methods: Preemptive Priority and Round-Robin Scheduling.
Preemptive Priority: Each task has a priority, and the system kernel ensures that the highest priority task runs on the CPU. If a task with a higher priority than the current task’s priority is present, the system immediately saves the current task’s context and switches to the higher priority context.
Preemption: Preemption means that when the system is running in kernel mode, task rescheduling is allowed. In other words, the currently executing task can be interrupted to allow another task to run. Preemption improves the application’s responsiveness to asynchronous events. The operating system kernel can be preempted, but this does not mean that task scheduling can occur at any time. For example, when a task is accessing shared data through a system call, rescheduling and interrupts are prohibited.
Task Context: The task context refers to the environment in which the task runs. For example, for x86 CPUs, the task context may include the program counter, stack pointer, and the contents of general-purpose registers.
Context Switching: In a multitasking system, context switching refers to the event when the CPU control is transferred from the running task to another ready task, where the currently running task transitions to a ready (or suspended, deleted) state, and another selected ready task becomes the current task. Context switching includes saving the running environment of the current task and restoring the running environment of the task to be executed. The contents of the context depend on the specific CPU.
Round-Robin Scheduling: This allows all tasks with the same priority and a ready state to fairly share the CPU (allocated a certain time slice, allowing each task flow to enjoy the CPU). The system has 256 priority levels, from 0 to 255, with 0 being the highest and 255 the lowest. The priority is set when a task is created. The taskPrioritySet() function can also be used to change task priority.
The main states of a task: READY, PEND, DELAY, SUSPEND…
ready-------->pended -----------semTake()/msgQReceive()-other tasks ready-------->delayed-----------taskDelay()ready-------->suspended---------taskSuspend()pended------->ready-------------semaGive()/msgQSend()-other tasks pended------->suspended---------taskSuspend()delayed------>ready-------------expired delay delayed------>suspended---------taskSuspend()suspended---->ready-------------taskResume()/taskActivate() suspended---->pended------------taskResume() suspended---->delayed-----------taskResume()
Round-Robin Scheduling can be extended to the preemptive method; when multiple tasks have the same priority, the round-robin scheduling algorithm allows tasks to run on the CPU for equal time slices, sharing the CPU. This avoids a single task monopolizing the CPU for an extended period, preventing other tasks from running. The kernelTimeSlice() function can be used to define the time length. taskLock() and taskUnlock() can be used to cancel and restore the preemptive method.
Note: A task can call taskDelete() to delete another task, but if a currently running task is deleted, its memory is not released, and other tasks remain unaware, waiting, which can lead to the system stopping. Use taskSafe() and taskUnsafe() to ensure that the currently running task is not deleted.
Usage example:
taskSafe (); semTake (semId, WAIT_FOREVER); /* Block until semaphore is available */ . . . . critical region . semGive (semId);semGive (semId); /* Release semaphore */ taskUnsafe ();
Task Synchronization and Inter-Process Coordination
Semaphores serve as a mechanism for task synchronization and mutual exclusion. In the Wind kernel, there are several types of semaphores tailored to different application needs: binary semaphores, counting semaphores, mutex semaphores, and POSIX semaphores. All of these semaphores are fast and efficient, widely used not only in the development design process but also in high-level applications of VxWorks.
Several methods for task synchronization and inter-process coordination:
-
Shared Memory, for simple data sharing.
-
Semaphore, basic mutual exclusion and synchronization.
-
Message queues and Pipes, for information transfer between tasks within a single CPU.
-
Sockets and Remote Procedure Calls, for communication between tasks over a network.
-
Signals, for error handling.
-
Mutual Exclusion
-
Mutual exclusion is used to control the serial access of multiple tasks to shared data. In multitasking applications, when two or more tasks access shared data simultaneously, data corruption may occur. Mutual exclusion allows them to access data serially, thereby protecting the data.
Several methods to resolve mutual exclusion:
-
Disabling interrupts (intLock): This can resolve mutual exclusion between tasks and interrupt ISRs.
funcA () { int lock = intLock(); . . critical region that cannot be interrupted . intUnlock (lock); }
However, using this method in real-time systems can affect the system’s ability to respond to and handle external interrupts in a timely manner.
-
Disabling system priorities (taskLock): Disabling system priorities ensures that while the current task is executing, no other higher-priority tasks can preempt the CPU, affecting the current program’s execution.
funcA () { taskLock (); . . critical region that cannot be interrupted . taskUnlock (); }
This method prevents higher-priority tasks from running prematurely, which is also unsuitable for real-time systems unless the time for which priority is disabled is particularly short.
-
Semaphore: Semaphores are the best method for resolving mutual exclusion and synchronizing processes.
SEM_ID semMutex; semMutex = semBCreate (SEM_Q_PRIORITY, SEM_FULL); ......... semTake (semMutex, WAIT_FOREVER); . . critical region, only accessible by a single task at a time . semGive (semMutex); semBCreat(SEM_Q_FIFO,SEM_EMPTY), SEM_EMPTY indicates it is used for task synchronization. /* includes */ #include "vxWorks.h" #include "semLib.h" SEM_ID syncSem; /* ID of sync semaphore */ init ( int someIntNum ) { /* connect interrupt service routine */ intConnect (INUM_TO_IVEC (someIntNum), eventInterruptSvcRout, 0); /* create semaphore */ syncSem = semBCreate (SEM_Q_FIFO, SEM_EMPTY); /* spawn task used for synchronization. */ taskSpawn ("sample", 100, 0, 20000, task1, 0,0,0,0,0,0,0,0,0,0); } task1 (void) { ... semTake (syncSem, WAIT_FOREVER); /* wait for event to occur */ printf ("task 1 got the semaphore\n"); ... /* process event */ } eventInterruptSvcRout (void) { ... semGive (syncSem); /* let task 1 process event */ ... }
Function introduction:
semTake(semID,time out)——–If the semaphore is free, it takes it; if not, it is defined by time out, and if it times out, it proceeds.
-
Priority Inversion occurs when a task is blocked waiting for a resource to be released by a lower-priority task, and if there is a medium-priority ready task, the blocking can worsen. Priority inheritance techniques can be used to resolve the priority inversion problem.
-
Priority Inheritance can be used to resolve priority inversion problems. When priority inversion occurs, the lower-priority task is temporarily raised to a higher priority so that it can execute as quickly as possible and release the resources needed by the higher-priority task.
Mutex semaphores have the option SEM_INVERSION_SAFE, which supports the priority inheritance algorithm. The priority inheritance protocol ensures that tasks holding resources execute at the highest priority of the task that is blocked on that resource. Once a task’s priority is raised, it will remain at the higher level until all mutex semaphores held by the task are released; then the task returns to its normal or standard priority. Thus, the “inheriting” task is not preempted by any intermediate priority tasks. This option must be used with priority queues (SEM_Q_priority).