|
|||||||||||
|
|||||||||||
Tics Realtime Definitions
Refers to the task that is currently running. Multi-tasking kernels
need to know the identity of the currently running task so that it can know where to save
the task's context when the kernel performs a task switch. For
example, a global variable called ActiveTcb might point to the tcb
of the active task. The kernel would save the active task's context on the active task's
stack (i.e. the current stack) Designates the active state of an electrical signal. For example, raising a normally 0
volt CPU input control pin to 5 volts asserts the line. Similarly, pulling down a
normally 5 volt CPU input control pin to 0 volts asserts the line. In either of the
above examples, the line is said to be asserted. When an input put is asserted, it
performs its function. For example, asserting the interrupt pin on the x86 generates an interrupt. A task is blocked when it attempts to acquire a kernel entity that is not available.
The task will suspend until the desired entity is available. A task is blocked when: it
attempts to acquire a semaphore that is unavailable, or when it waits for a message that
is not in its queue, or while it waits for a pause to expire, or when, in a
time-slicing environment, its time-slice has expired, to give a few examples. The startup code that is stored in ROM; this code runs at power-on / reset time. "Bootup" refers to this process. Derived from the old story about a man trying to pull himself up by his own boot straps. Note that code in ROM is also referred to as "firmware". In some embedded systems the entire software set resides in ROM; in others, the the ROM only contains enough code to load the rest of the program in from another source (hard disk drive, another computer via a network, etc.). Once the boot code has loaded the rest of the program into memory, it jumps to the first instruction of the loaded code, and its job is done. The typical PC works this way (the ROM has boot code that loads the OS from a disk drive). See loader for more details about program loading. A program breaks when a breakpoint is reached. A breakpoint is an instruction address
at which a debugger has been commanded to stop program execution,
and execute the debugger so that the user can enter debugger commands. At some point after
breaking the user typically executes a debugger command like "continue" which
continues program execution from the point at which the program was stopped.
while (TRUE) {
if (switchDown) {
lampOn();
break;
}
}
The code stays in the loop until the condition is met. Contrast with event driven code. A CPU's context typically refers to all its registers (including IP and SP) and status
register(s). When a task switch occurs, its context is saved, and the context of the next
task to run is restored. The saving of the current task's context and the restoring of the
context of the next task to run is called a context switch. (Special Note: In a more
complete sense context also includes the task's local variables and its subroutine nesting
information (for example, the task may be 7 subroutine calls deep when the context switch
is made, and when the task is eventually resumed the task's code must be able to return
from all 7 subroutines). The local variables and subroutine nesting level are preserved by
giving each task its own private stack when the task is created, since subroutine return
information and local variables are stored on the stack.). A group of tasks run cooperatively when each must voluntarily relinquish control
so that other tasks can run. In other words, a task will run forever, thus starving other
tasks, unless it voluntarily gives up control. Control can be relinquished explicitly by a
yield, pause, or suspend; or implicitly by waiting for an event.
Contrast with priority based preemptive scheduling and time-slicing. A system is crashed when the CPU has halted to due a catastrophic error, however, the
word can be used in the same sense has hung.
void main(void)
{
for (;;) {
task0();
task1();
task2();
/* and so on... */
}
}
In this scenario, each task must do its work quickly, save its state if necessary, and
return back to the main loop. The advantages with this approach are small size, no kernel
required, requires only 1 stack, and easy to understand and control. The disadvantages are
no priorities, tasks must retain their states, more responsibility is placed on the
programmer, and the possibility of excessive loop latency when there are many tasks. High
priority events generate interrupts and are handled in the background by isr's.
This technique is sometimes referred to as foreground/background. A debugger is a software program used to break program execution
at various locations in an application program after which the user is presented with a
debugger command prompt that will allow him to enter debugger commands that will allow for
setting breakpoints, displaying or changing memory, single stepping, and so forth. The term can have various meanings, but typically, a deadlock is a condition in which a
task will remain forever suspended waiting for a resource that it
can never acquire. Consider a system comprised of a keyboard and a display, with a
separate semaphore for each. In order for a task to interact with the user it must acquire
the "console" (keyboard and display) and therefore must acquire both the
keyboard and the display semaphores. If more than one task decides to interact with the
user at the same time a condition can arise where taskA acquires the keyboard semaphore
and taskB acquires the display semaphore. Now taskA will wait forever for the display
semaphore and taskB will wait forever for the keyboard semaphore. The solution in this
example is to treat the keyboard and display as a single resource (console). Deadlocks can
occur for a variety of reasons, but the end result is typically the same: the task will
wait forever for a resource that it can never acquire. A method of managing timers such that only 1 timer count is decremented regardless of
the number of pending timers. Consider 3 pending timers with durations of 50 ms, 20 ms,
and 5 ms respectively. The timers are sorted and stored in a list with the lowest timer
first: 5, 20, 50. Then the timer durations are replaced with the difference of the timer
duration and the preceding duration, i.e., 5, 20, 50, is replaced with 5, (20-5), (50-20),
which equals 5, 15, 30. This allows for only the first duration (5 ms in this example) to
be decremented. Thus, when the first duration of 5 decrements down to 0, there are only 15
ms required to meet the 20 ms duration, and when the 50 ms duration is to be started,
already 5 + 15 = 20 ms have expired, so only a count of 30 ms is required. This technique
is attractive because the timer counts are typically decremented inside the timer isr and
time inside any isr should be kept to a minimum. Refers to the transfer of executable code from a host to a target, typically using an RS-232 serial line, parallel printer cable,
or Ethernet. Download either requires an ICE, or the target
must have resident software (e.g., FLASH) that can read the incoming data, translate it if
necessary (the file format may be ASCII hex for example) and load the code into RAM
memory. If the target board has no resident software, then an ICE is
required. Priorities that can be changed at run-time. Contrast with fixed
priorities. RAM that requires a periodic refresh (i.e., a write) to maintain data integrity. Code that remains dormant until an event occurs. The following is an example.
while (TRUE) {
msg = waitMsg(MOTOR_ON);
freeMsg(msg);
turnMotorOn();
}
Until the message MOTOR_ON is received, the function waitMsg will suspend the
current thread of execution until the message is received. The receipt of the message is
the event that causes the ask to be scheduled and eventually run. Contrast with busy waiting. A software interrupt generated by the CPU when an error or fault
is detected. A concept which dictates that if a task runs too long, its priority is lowered so that
other tasks can get their fair share of the CPU. This concept is repugnant to real-time
principles and is used chiefly in multi-user systems with the
intent of disallowing one user from monopolizing the CPU for long compiles and the like. A fault is an exception that is generated when the current
instruction needs help before it can be executed. For example, supposed in a virtual memory system, a memory access is made to a page that is not in memory. In this case, a fault is generated which
vectors to an isr that will read in the page. The fault exception is different in the way
it returns from interrupt in that control is returned to the instruction that caused the
fault, not the following instruction as with a normal interrupt. This allows the
instruction to access the memory again, and this time succeed. A memory allocation/deallocation scheme in which a number of memory pools of fixed
sized blocks is created. For example, 3 pools are created as follows: pool 1 contains 100
64 byte blocks, pool 2 contains 50 256 byte blocks, and pool 3 contains 10 1K byte blocks.
When a user wants memory he takes and returns blocks from/to a pool. For example, if the
user needed a 256 byte block of memory he would take a block from pool 2. Similarly, if he
needed a 100 byte block he would still have to take a block from pool 2 even though bytes
are wasted. The advantage is no fragmentation and high speed.
Priorities that are set once, typically at compile time, and unalterable at run-time.
Contrast with dynamic priorities. FLASH programmer In a kernel or language which allows for memory allocation and deallocation, the
available free memory can eventually become fragmented, i.e., non-contiguous, if memory is
allocated from one large pool. For example after allocating and deallocating many
different size blocks of memory, there may be 12K available, but it is not contiguous, and
therefore, if software needs 12K it cannot use it, even though it is available. Contrast
with fixed block memory allocation. A system is hung when it does not respond to external input (e.g. keyboard) but has not
completely crashed. A hung system is typically spinning in an endless
loop. Hard real-time refers to the strict definition of real-time. See real-time.
The time it takes for the isr to be entered, once the processor interrupt pin is
asserted. Most linkers have the ability to generate a hex file. This is an
ascii readable (you can view it with your text editor) version of the executable file. The
hex file is useful when you need to write a loader program. Intercept an interrupt by saving the current interrupt vector and writing a new interrupt
vector. When the new interrupt service routine is finished it calls
the old interrupt service routine before returning. See target. An ICE is essentially a hardware box that connects a PC to an embedded system.
Typically the ICE vendor or a third party supplies debug software that turns the PC
into a debugging station. The ICE requires an IO port (typically the parallel
printer port) on the PC and an industry standard JTAG connector and JTAG support on the target board; if you do not design JTAG support into your target
system, then an ICE cannot be used (unless you build your own custom ICE). Breakpoints,
single stepping, variable display, etc. are all supported. One of the most important
features provided by the ICE is the ability to download software into the target system memory. The ICE allows for trapping the following types
of activities: read/write to an address range, read/write a specific value from an address
range, setting a breakpoint in ROM, mapping emulator memory to target board memory, and
other similar features. It also turns the host (a PC for example) into a debug station
with supplied software. This allows for debugging of the target board even though the
target does not have a keyboard, screen, or disk. For more information, see articles 1, 2, and 3. A kernel owned task that is created and scheduled during system initialization. It runs
at the lowest possible priority. The idle task has the lowest possible priority and runs
only when no other tasks are scheduled. When any other task is scheduled, the idle task is
preempted. The idle task is typically a "do nothing" tight loop. Its only
purpose is to run when no other tasks run. It is a convenience mechanism in that no
special kernel code is required to handle the case of all tasks being idle; the kernel
sees the idle task like any other task. The key is that the idle task has a lower priority
than any other task, and even though it is scheduled to run and occupies a place in
the ready queue, it will not run until all other tasks are idle. And, conversely, when any
other task is scheduled, the idle task is preempted. To start multiple instances of a task. Identical to hook except that the old isr is not called before
returning. A routine that is invoked when an interrupt occurs. It is necessary to save the flags on entering an isr and restore them on exit, otherwise, the isr may change the flags, which can cause problems. The 8086 automatically saves flags on the stack prior to calling the isr, and restores the flags before returning using the IRET instruction. The reason for saving the flags can be understood by considering the following code fragment. ; Assume that AX = 0. CMP AX,0 JE LABEL7 The CMP instruction executes, and because AX = 0, the Z bit in the flags register is set by the CPU. Let us say that after the compare instruction, an interrupt occurs and the isr is jumped to which executes the following instruction. ; Assume BX = 1. CMP BX,0 The above instruction will clear the Z bit because BX is not 0. The isr will eventually return to the instruction above JE LABEL7. If a standard RET instruction is used instead of IRET, the flags will not be restored, and the instruction JE LABEL7 will not perform the jump as it should because the Z bit was cleared in the isr. For this reason flags must be saved before entering an isr and restored prior to returning from an isr. Some CPU's handle with an interrupt vector table. On the 8086, the interrupt vector
table is a 1K long table starting at address 0, which will support 256 interrupts numbered
0 through 255. The numbers 0 through 255 are referred to as "vector numbers" or
"interrupt numbers". The table contains addresses of interrupt service routines,
which are simply subroutines that are called when the interrupt occurs. The only
difference between an interrupt service routine and a subroutine is that on entry to the
isr the CPU flags are stored on the stack in addition to the return address; for an
explanation of why the flags must be saved, see the definition. This
means that an interrupt service routine must return with a special return instruction
(IRET on the 8086) that pops the CPU flags off the stack and restores them before
returning. On the 8086, an address is specified by a segment and an offset, and therefore
each interrupt vector table entry uses 4 bytes; two bytes for the CS register and 2 bytes
for the IP register (IP is the lower word in the vector table and CS is the higher word).
An interrupt service routine can be invoked by software using the 8086 INT instruction,
which in addition to the return address pushes the CPU flags onto the stack. An interrupt
service routine can also be invoked by software using a standard CALL instruction as long
as the CPU flags are first pushed onto the stack. For details of how an interrupt is
generated and serviced, see interrupts. Software that provides interfaces between application software
and hardware and in general controls and programs all system hardware. A multi-tasking
kernel provides for multi-tasking in the form of threads, processes, or both. A real-time kernel provides is a multi-tasking
kernel that has predictable worst case tasks switch times and priorities so that high
priority events can be serviced successfully. The compiler/assembler translates high level language code into a binary machine language file called an object file. The start address for the code in the object file is always address 0. The linker program allows multiple object files to be combined into a single executable file. Since each object file starts at address 0, the linker must reassign addresses so that there are no conflicts. The linker also resolves references. For example, if fileA.c calls a function named functionB that is defined in fileB.c, the compiler leaves the address of functionB blank in fileA.obj. When the linker links fileA.obj and fileB.obj together into one executable file, the linker fills in the correct address for functionB. Any blank addresses that remain blank after linking are flagged as "unresolved external references" by the linker. Embedded systems linkers also have a "locate" feature. This allows the programmer the ability to tell the linker where to locate code in memory. This feature is necessary because, for example, certain pieces of code reside be at certain addresses. For example, the boot code must be at the start address that the microprocessor jumps to on power-up. See reset for details. The locator also allows the programmer to name sections of his code and locate the contents at specific addresses. For example, in each of the C/C++ files (call them fileA.c, fileB.c, and fileC.c) that contain code that is to reside in FLASH the programmer might code something like this at the top of the file. // Each of the C files should contain the following line at the top of the file. #pragma section FLASH // Write C/C++ code here that is to be located in FLASH. FILES fileA.obj fileB.obj fileC.obj ; These are the files to link together. LOCATE FLASH AT 9000000H ; Locate program section "FLASH" starting at 90000000h. How the above is done will vary from linker to linker.
Machine language, or machine code, is the binary language that the microprocessor executes. For example, the Intel x86 instruction to disable interrupts is a byte with the following binary value 11111010. The machine code instruction for a short jump is two bytes long. The first byte is 11101011 and the second byte represents how far to jump from the address of current instruction (displacement). This second byte is added to the address of the current instruction (which is contained in the program counter register, (PC), also called the instruction pointer register (IP)). In order the jump forward or backward, the second byte of the short jump instruction is a signed number. Here is a small machine language program. Memory
Address Instruction Comment
0000h 11111010 ; Disable interrupts.
0001h 11101011 ; First byte of jump instruction.
0002h 00000000 ; Second byte of jump instruction (displacement).
Now lets see how this program executes. First we must set the program counter register (PC) to 0 so that the microprocessor will execute the instruction at address 0. Let's assume that when the microprocessor powers up it automatically sets the program counter register to address 0, so that after power up we execute the disable interrupt instruction at address 0. After the instruction is executed, the program counter is incremented by 1 so that it points to the next instruction at address 0001h. The processor fetches the instruction at address 0001h and finds that it is a short jump instruction. The processor knows that the byte after the jump instruction byte contains the displacement for the jump, so it reads the displacement value at address 0002h, which is 0, and adds it to the current contents of the program counter register. However, since the displacement is 0, the contents of the program counter register will remain at 0001h. Since the microprocessor always fetches the next instruction at the address contained in the program counter, it will again fetch the instruction at address 0001h which is again the jump instruction. The microprocessor will execute this endless loop forever. An assembler is a program that translates English language instructions into binary code. The above machine language program would be written in assembly language as shown below.
CLI ; Disable interrupts (clear interrupt flag).
LABEL:
JUMP SHORT LABEL ; Jump back to myself.
If the above code were put into a file and submitted to the assembler program, the assembler would output an "object" file that would essentially be the binary code shown above. High level languages like C take the assembler one step further in that for each C statement one or more machine code instructions are generated. For the most part, the assembler generates one machine language statement for each assembly statement. Note that some C compilers actually generate assembly language that is then passed to an assembler to generate machine code. Most modern microprocessor systems use "memory mapping" to allow the microprocessor easy access to external hardware. Memory mapping means that all hardware in the system, not just standard memory, can be accessed through standard microprocessor reads and writes. A simple memory map is shown below. So for example, referring to the table below, any reads or writes in the range 0000h to FFFFh address DRAM. Note that the "type" of memory for DRAM is "R/W" which means we can read or write DRAM. If we read from address 2000ch we will read a byte whose 8 bits represent the states of the 8 DIP switches (this memory type is read only). Writing out a binary 11111111h to address 20004h will light up all 8 of the bank # 1 LED's, and writing a 00000001h will turn them all off except for the last one. Note also that the hardware engineer could have designed the system so that writing a 11111111h to the LED bank would turn them all off. The memory map is generated by the hardware engineer and is typically included in his design document. Microprocessor Address Type Hardware 0000h to FFFFh R/W DRAM 10000h to 1FFFFh R FLASH 20000h W Motor control register 20004h W LED bank # 1 (8 LED's) 20008h W LED bank # 2 (8 LED's) 2000ch R DIP switch (8 rocker switches)
memory map - software /dt> Refers to a kernel, or a very minimal kernel used for debugging,
typically written by the programmer for a particular project, or supplied on ROM from the
board manufacturer. A data structure that is passed to a task by placing a pointer to the structure into a
queue. The queue is a doubly linked list that is known to the task. Kernel functions are
supplied to remove messages from the queue. A technique by which the sender of the message changes the "sender" field in
the message structure before sending it so that the receiver of the message will reply to
a task other than the actual sender. Messages are queued, mail is not. New mail data may optionally over-write old, which is
advantageous for certain applications. Consider taskA that updates a display and receives
the latest data on a periodic basis from taskB. Assume further that the system is busy to
the point that taskB sends taskA two data updates before taskA can resume execution. At
this point the old data is useless, since taskA only displays the latest value. In this
situation, queuing (using messages) is undesirable, and mail with over-write enabled is
preferred. A term that describes an environment in which 2 or more CPU's are used to distribute
the load of a single application. A term that describes a single computer that is connected to multiple users, each with
his own keyboard and display. Also referred to as a time-sharing system. Contrast with multi-tasking. Non-reentrant code cannot be interrupted and then reentered. Reentrancy can occur for
example, when a function is running and an interrupt occurs which transfers control to an
interrupt service routine, and the interrupt service routine calls the function that was
interrupted. If the function is not reentrant, the preceding scenario will cause problems,
typically a crash. The problem typically occurs when the function makes use of global
data. To illustrate, consider a function that performs disk accesses and uses a global
variable called "NextSectorToRead", which is set according to a parameter passed
to the function. Consider the following scenario. The function is called with a parameter
of 5, and "NextSectorToRead" is set to 5. The function is then interrupted and
control is transferred to an isr which calls the function with a parameter of 7, which
results in "NextSectorToRead" being set to 7. The sector is read, control is
returned to the isr, the isr performs a return from interrupt, and the function resumes
with an erroneous value of 7 instead of 5 in "NextSectorToRead". This problem
can be avoided by only using stack variables. A unit of memory, typically around 4K, which is used as the smallest granule of memory
in virtual memory systems. preemption - causes of Preemption occurs when the current thread of execution is stopped and execution is
resumed at a different location. Preemption can occur under software (kernel) or hardware
(interrupt) control. See priority based preemptive scheduling.
A number attached to a task that determines when the task will run.
See also scheduling. A multi-tasking scheduling policy by which a higher priority task will preempt a lower
priority task when the higher priority task has work to do. Contrast with cooperative scheduling and time-sliced
scheduling. An inappropriately named condition created as follows. Consider three tasks: taskA,
taskB, and taskC, taskA having a high priority, taskB having a middle priority, and taskC
having a low priority. taskA and taskB are suspended waiting for a timer to expire, and
therefore taskC runs. taskC then acquires semaphoreA, and is subsequently preempted by
taskA before it releases semaphoreA. taskA then attempts to acquire semaphoreA, and blocks since the semaphore is in use by taskC. taskB now runs, because
it is of a higher priority than taskC and its timer has expired. Now, taskB can run until
it decides to give up control, creating a condition where a high priority task (taskA)
cannot run because a lower priority task has a resource that it needs. taskA will remain
blocked until the lower priority task runs and releases the semaphore. This situation can
be avoided by proper coding, or by using a server task instead
of a semaphore. A process is a single executable module that runs concurrently with other executable
modules. For example, in a multi-tasking environment that supports processes, like
Microsoft Windows, a word processor, an internet browser, and a data base, are separate
processes and can run concurrently. Processes are separate executable, loadable
modules as opposed to threads which are not loadable. Multiple
threads of execution may occur within a process. For example, from within a data base
application, a user may start both a spell check and a time consuming sort. In order to
continue to accept further input from the user, the active thread could start two other
concurrent threads of execution, one for the spell check and one for the sort. Contrast
this with multiple .EXE files (processes) like a word processor, a data base, and internet
browser, multi-tasking under Microsoft Windows for example. The range of memory that a program can address. Through a scheme involving special hardware and kernel software, tasks can be
restricted to access only certain portions of memory. If a program attempts to jump to a
code location outside its restricted area or if it attempts access data memory outside its
restricted area, a protection violation occurs. The protection violation generates
an interrupt to an interrupt service routine. The interrupt service routine will typically
terminate the task that caused the violation and schedule the next ready task. When the
interrupt service routine returns, it returns to the next ready to run task. Special
hardware is needed because each code or data access must be checked. This is essentially
done through special hardware tables that kernel software can write to. For example, when
a task is created, the kernel assigns a hardware table to the task (each task is assigned
a different hardware table). The kernel then writes into this table the lower limit and
the upper limit of memory that the task is allowed to access. When a task starts, a
special hardware register is set to point to the hardware table for the task so that the
special memory management hardware (which may be on the CPU chip, or external to it) will
know which table to use when making access checks. Each time the task accesses memory, the
memory management hardware checks the requested memory access against the allowable
limits, and generates a protection violation if necessary. Memory that can only be read; it cannot be written. See FLASH as
an example. Note that although FLASH is writable as well as readable, it is still referred
to ROM or FLASH ROM. True ROM is read only. A true ROM is typically manufactured in large
quantity at low cost for high volume manufacturing where write-ability is typically not
useful anyway. A ROM is typically used to store boot code and startup
data like the model number etc. The model number and similar data is often used by generic
ROM software that handles many different models of the product. In a strict sense, real-time refers to applications that have a time critical nature.
Consider a data acquisition and control program for an automobile engine. Assume that the
data must be collected and processed once each revolution of the engine shaft. This means
that data must be read and processed before the shaft rotates another revolution,
otherwise the sampling rate will be compromised and inaccurate calculations may result.
Contrast this with a program that prints payroll checks. The speed at which computations
are made has no bearing on the accuracy of the results. Payroll checks will be generated
with perfect results regardless of how long it takes to compute net pay and deductions.
See also hard real-time and softRealtime.
A real-time kernel is a set of software calls that provide for the creation of
independent tasks, timer management, inter-task communication, memory management, and
resource management. A real-time kernel plus command line interpreter, file
system, and other typical OS utilities. The process by which the active task voluntarily gives up
control of the CPU by notifying the kernel of its wish to do so. A task can voluntarily
relinquish control explicitly with a yield, pause, or suspend; or
implicitly with waitMsg, waitMail,
etc. and the actual system calls will vary from kernel to kernel. When the active task
relinquishes control, the task referenced by the node at the front of the ready queue becomes the new active task. The ready queue is a doubly linked list of pointers to tcb's
of tasks that are waiting to run. When the currently active task relinquishes control,
either voluntarily, or involuntarily, (for example by an interrupt service routine which
schedules a higher priority task), the kernel chooses the task at the front of the ready
queue as the next task to run. Describes a task switching policy that is consistent with real-time requirements.
Specifically, when ever it is determined that a higher priority task needs to run, it runs
immediately. Contrast this with a policy in which the higher priority task is scheduled
when it is determined that it must run, but the active lower priority task continues to
run until a system scheduler task interrupts it and then switches to the higher priority
task. Microprocessor Memory Address Contents of
Microprocessor Memory Address Microprocessor address 0000 contains the address of the subroutine (isr) that will be jumped to when reset occurs. So, on power-up, address 5080 is jumped to, i.e., instruction execution begins at address 5080. But don't we have a problem here? On power up, all memory is blank, and there will be no code at address 5080, right? Wrong. If we design our hardware system so that microprocessor address 5080 is actually the first address of FLASH, then all is OK, as long as we have preprogrammed the FLASH with our program. This is referred to as memory mapping. A resource is a general term used to describe a physical device or software data
structure that can only be accessed by one task at a time. Examples of physical devices
include printer, screen, disk, keyboard, tape, etc. If, for example, access to the printer
is not managed, various tasks can print to the printer and inter-leave their printout.
This problem is typically handled by a server task whose job is
to accept messages from various tasks to print files. In this way access to the printer is
serialized and files are printed in an orderly fashion. Consider a software data structure
that contains data and the date and time at which the data was written. If tasks are
allowed to read and write to this structure at random, then one task may read the
structure during the time that another task is updating the structure, with the result
that the data may not be time stamped correctly. This type of problem may also be solved
by creating a server task to manage access to the structure. To run again a suspended task at the point where it was
suspended. Formally, the process by which the active task is suspended and a context switch is performed to
the previously suspended task. A multi-tasking scheduling policy in which all tasks are run in their turn, one after
the other. When all tasks have run, the cycle is repeated. Note that various scheduling
policies are run in round robin fashion, e.g., cooperative
scheduling, time-sliced scheduling, and cyclical
scheduling. Cyclical and round robin scheduling are used inter-changeably. A scheduling mechanism that allows for creating and scheduling new tasks and changing
task priorities during execution. See also scheduling and static scheduling. A scheduling mechanism in which all tasks and task priorities are described and bound
at compile-time; they cannot be changed during execution. See also scheduling
and dynamic scheduling. A task can be in any of the following states: (1) dormant (inactive, usually waiting
for an event - a message for example), (2) waiting to run (a task that was non-existent or
dormant was scheduled to run), (3) running (on a single CPU system, only one task
at a time can be in the running state. A dormant task is scheduled by notifying the
kernel/OS that the task is now ready to run. There are various scheduling policies: see cooperative scheduling, time-sliced
scheduling, cyclical scheduling, and priority
based preemptive scheduling. For software implementation details see the article HowTasks Work - Part 2. This term has several meanings. In its simplest form, it is a global flag - a byte of
global memory in a multi-tasking system that has the value of 1 or 0. In multi-tasking
systems, however, a simple global flag used in the normal manner can cause time dependent errors unless the flag is handled by a special
test and set instruction like the 80x86 XCHG instruction. A semaphore
is typically used to manage a resource in a multi-tasking
environment - a printer for example. See XCHG for a code example. In
another form, the semaphore may have a count associated with it, instead of the simple
flag values of 1 or 0. When the semaphore is acquired, its count is made available to the
caller, and can be used for various purposes. For example, consider a system that has
three printers numbered 1, 2, and 3. A semaphore could be created with a count of 3. Each
time the semaphore is acquired its count is decremented. When the count is 0, the
semaphore is not available. When the semaphore is released, its count is incremented. In
this way a pool of 3 printers can be shared among multiple tasks. The caller uses the
semaphore count value returned to him to determine which numbered resource (e.g., printer
1, 2 or 3) he is allowed to use. Numerous variations on this theme can be created. Compare
also with server task. The advantage of a server task is that it
does not block the calling task if the resource is not available, thus avoiding priority inversion. In general semaphores should be avoided;
furthermore they are unnecessary in a message based OS. The server task approach to
resource management is preferred over semaphores as it is cleaner and less error prone. A piece of test equipment that is connected in series with a serial line for the
purpose of displaying and monitoring two way serial communications. The term usually
refers only to RS-232 serial lines. A task dedicated to the management of a specific resource. A
server task accepts requests in the form of messages from other tasks. For example, a
server task could be created to manage access to a single printer in a multi-tasking
environment. Tasks that require printing send the server a message that contains the name
of the file to print. Note that the server task approach does not block
the calling task like a semaphore will when the resource is
unavailable; instead the message is queued to the server task, and the requesting task
continues executing. This is an important consideration when
is a concern. A debugger command that allows an application program to execute one line of the
program, which can either be a single assembly language instruction, or a single high
level language instruction. There are typically two distinct single step commands - one
that will single step "into" subroutine calls, and one that will step
"across" them (i.e., enter the routine, but do not show its instructions
executed on the screen). The latter command is useful, otherwise many unwanted levels of
subroutines would be entered while single stepping. Refers to applications that are not of a time critical nature. Contrast with hard real-time. See also real-time. RAM that, unlike DRAM, does not requires a periodic refresh (write) to maintain data
integrity. Unlike FLASH, however, if power is removed, memory data is lost. The process by which a software system is put under heavy load and demanding conditions
in an attempt to make it fail. Refers to the scenario in which subroutineA calls subroutineB which calls
subroutineC... which calls subroutineN. This is relevant for real-time systems because if
a task is 7 subroutines deep, and the task is preempted, the return
addresses must be preserved so that when the task is later resumed, it can return back up
the chain. The most practical way of preserving subroutine nesting is by assigning each
task its own stack. This way when a task is resumed, the SP is first set to the task's
stack. The immediate cessation of a thread, preceded by the saving of its context.
Sometimes one task must wait for another ask to finish before it can proceed. Consider
a data acquisition application with 2 tasks: taskA that acquires data and taskB that
displays data. taskB cannot display new data until taskA fills in a global data structure
with all the new data values. taskA and taskB are typically synchronized as
follows. (1) taskB waits for a message from taskA, and
since no message is available, taskB suspends. When taskA runs and updates the data
structure, it sends a message to taskB which schedules taskB to run, at which time it
displays the new data, then again waits for a message, and the scenario is repeated. A thread of execution that can be suspended, and later
resumed. Data corruption due to preemption. Consider a preemptive multi-tasking environment in which tasks use a global flag to gain access to a printer. The following code will not have the desired effect. tryAgain: CMP flag,1 ; Printer available? JE tryAgain ; No, then try again. MOV flag,1 ; Printer was available. Set it as busy. ...use printer... MOV flag,0 ; Set printer as available. The problem is that after the CMP instruction, the current task can be preempted to run
another task which also checks the flag, sees that it is available, sets the flag, does
some printing, and is then preempted to run the original task, which resumes at the JE
instruction. Since the 80x86 flags register (not to be confused with the flag
variable), which contains the status of the last CMP instruction, is restored with the
original task's context, the JE will fail, since the
flag was 0. The original task will now acquire the flag and interleave its printing with
the other task's printing. This problem can be overcome by using a read-modify-write
instruction like XCHG or by managing the printer with a server task. Time dependent errors can also occur when preemption
is used with non-reentrant code. Consider a
graphics library that is non-reentrant. If taskA calls a non-reentrant graphics function
and is then preempted to run taskB which calls the same function, errors will result.
Sometimes the non-reentrancy problem is not so obvious. Consider a non-reentrant floating
software library that is called invisibly by the compiler. For example the statement y =
5.72 * x; would make a call to the floating point software multiplication function. If the
current task were preempted while executing this instruction, and the next task to run
also performed a multiply, then errors can again result. Note that floating point
time-dependent errors could still occur even if a floating point chip was used instead of
a floating point software library. An error could occur if the floating point CPU's
context context is not saved by the kernel. A task control block is a data structure that contains information about the task. For
example, task name, start address, a pointer to the task's instance data structure, stack
pointer, stack top, stack bottom, task number, message queue, etc. A single generic task can be created to handle multiple instances of a device. Consider 5 analog channels that must be read and processed every "n" milliseconds, the number "n" being different for each channel. A generic task can be created that is channel independent through the use of instance data. Instance data is a C structure that in this case contains information specific to each channel, i.e., the IO port address of the channel, the sample interval in milliseconds, etc. The task code is written so that it references all channel specific data through the instance data structure. When the task is created a pointer to the task's task control block is returned. One of the fields of the task control block is a user available void pointer named "ptr". After starting the task, the "ptr" field is set to an instance data structure that contains the sample rate, IO port address, etc. for that task instance.
for (i = 0; i < 5; i++) {
tcb = makeTask(genericAnalogTask, i);
tcb->ptr = &InstanceDataArray[i];
startTask(tcb);
}
The code above shows how 5 instances of the same task are created, and each given a unique identity via tcb->ptr which points to the instance data.
void genericAnalogTask(void) {
typeAnalogData * d;
d = (typeAnalogData *) ActiveTcb->ptr;
while (TRUE) {
pause(d->sampleIntervalInMs);
readAndProcessChannel(d->channelIOPortAddress);
}
}
The code above shows what the generic task might look like. For further information see
the example. A thread is a task that runs concurrently with other tasks within a single executable
file (e.g., within a single MS-DOS EXE file). Unlike processes,
threads have access to common data through global variables. A sequence of CPU instructions that can be or have been executed. Most modern microprocessors have built in timers. A timer works like a stop watch. For example, a timer could be set to zero, and set to count up to 10,000 after which an interrupt is generated, the counter reset to zero, and the count started again. This is referred to as setting the timer counter in repeat or continuous mode. Many other system dependent features are generally available. For example, the timer may have an option that will allow the counter to start based upon an external signal going high and stopping on the external signal going low, after which an interrupt is generated. This would allow for timing of external events. Each task is allotted a certain number of time units, typically milliseconds, during
which it has exclusive control of the processor. After the time-slice has expired the task
is preempted and the next task at the same priority, for which time-slicing is enabled,
runs. This continues in a round-robin fashion. This means that a
group of time-sliced high priority tasks will starve other lower priority tasks. For
example, in a 10 task system, there are 3 high priority tasks and 7 normal priority tasks.
The 3 high priority tasks have time-slicing enabled. As each high priority task's
time-slice expires, the next high priority task is run for its time slice, and so on. The
high priority tasks are run forever, (each in its turn until its time-slice expires), and
the low priority tasks will never run. If however, all high priority task waits for an
event, (for example each pauses for 100 ms), then lower priority tasks can run. The
behavior of tasks in a time-slicing environment is kernel dependent; the behavior outlined
above is only one of many possibilities, but is typically the way time-sliced tasks should
behave in a real-time system. Some kernels may implement the concept of fairness to handle this situation, however, fairness is repugnant to
real-time principles as it compromises the concept of priority. An ICE command that will save the most recent "n"
instructions executed. The trace can also be conditional, e.g., trace only those
instructions that access memory between 0 and 1023. A buffer in ICE memory that stores the last "n"
instructions executed. It is useful while debugging as it shows a history of what has
occurred. A technique in which a large memory space is simulated with a small amount of RAM, a
disk, and special paging hardware. For example, a virtual 1 megabyte
address space can be simulated with 64K of RAM, a 2 megabyte disk, and paging hardware.
The paging hardware translates all memory accesses through a page table. If the page that
contains the memory access is not loaded into memory a fault is
generated which results in the least used page being written to disk and the desired page
being read from disk and loaded into memory over the least used page. The 80x86 XCHG instruction can be used in a single processor, preemptive multi-tasking environment to create a flag that is shared between different tasks. A simple global byte cannot be used as a flag in this type of environment since tasks may collide when attempting to acquire the flag. The XCHG instruction is typically used as follows: tryAgain: MOV AL,1 XCHG AL,flag CMP AL,0 JNE tryAgain flag is a byte in RAM. If the flag is 1, then the XCHG instruction will place a 1 into AL. If AL is 1 after the XCHG then the loop must continue until finally AL is 0. When AL is 0 the following is known: (1) The flag was 0 and therefore available, and (2) the flag is now 1 due to the exchange, meaning that the flag has been acquired. The effect is that with a single instruction, a task can check, and possibly acquire the flag, thus eliminating the possibility of a collision with another task. When using multiple processors with the flag in shared RAM, the above solution will not work, since although the instruction is indivisible for the processor that executes it, it is not indivisible with respect to the shared RAM data bus, and therefore, the shared RAM bus arbitration logic must provide a method by which one processor can lock out the others while the XCHG executes. This is typically done with the 80x86 LOCK instruction as shown below. tryAgain: MOV AL,1 LOCK XCHG AL,flag CMP AL,0 JNE tryAgain Note that the LOCK instruction provides information only. It is up the shared RAM bus
arbitration logic to incorporate the LOCK* pin into its logic. Newer processors like the
80386 incorporate the LOCK implicitly into the XCHG instruction so the LOCK instruction is
unnecessary. The process by which a task voluntarily relinquishes control (via a kernel call) so that other tasks can run; the task will run again in its turn (according to its priority). Copyright © 2000, Tics Realtime |
|||||||||||