Tasks and scheduling
All task use a common address space !!
A task can be in the following states :
- READY : waiting for CPU
- PEND : blocked due unavailability of some resource other than CPU
- DELAY : asleep for some time
- SUSPEND : disabled for running, however the task can still change from
states PEND/DELAY to READY when being disabled
- PEND + T : PEND with timeout
- state + i : state + inherited priority
PEND - READY - DELAY
\ /
SUSPEND
- priority 0 highest
- priority 255 lowest
- priority applications : 100-250
- priority drivers : 51-99 (netTask 50, for debugging with tornado)
- taskPrioritySet()
-
set priority
- task(Un)Lock()
-
disables task rescheduling, but do not lock out interrupts
However if a task blocks the scheduler runs another task.
- int(Un)Lock()
-
taskLock with disabling interrupts
Can be used for mutual exclusion, but it also locks out
higher priority task without any relation to the
critical region -> bad real-time response
Scheduling types :
- preemptive priority-based scheduling (default)
A task of a specified priority can only preempted by a higher priority
task, another task of the same priority will only run when this task is
blocked or willingly goes to sleep. This means that if a single task
is never blocked, it never gives a change to another equal-priority
task to run. Round-robin scheduling solves this problem.
In vxworks this is the default scheduling algorithm.
- round-robin scheduling
Like preemptive priority-based scheduling but it also attempts the
share the CPU fairly among all tasks of the same priority using the
so called time-slicing technique. In time-slicing each task can run
freely until its preempted by a higher priority task or its time-slice
has ended. In the latter case another equal priority task is scheduled
to run. Thus the equal-priority task rotate, each executing for an equal
interval of time.
In vxworks one can activate round-robin scheduling with the function
kernelTimeSlice() with the specified timeslice.
- Problem : Priority inversion
-
When a low priority task takes a mutex, it can block out a higher
priority task which wants to take this mutex in a later instance. The higher
priority task may run when the lower priority task releases the mutex.
Normally this happens, but sometimes the lower priority task is preempted
by a middle priority task. This middle priority task can keep the high
priority task from running for a long long time. You could say that this
task has virtually a higher priority : priority inversion!
- Solution : Priority Inheritance
-
The priority-inheritance protocol assures that a task that holds a resource
executes at the priority of the highest-priority task blocked on that
resource.
This means that our lower priority task holding the mutex, will be elevated
to the priority of the higher priority task when this wants to take the
mutex. When the lower priority task is running with higher priority it cannot
be preempted by the middle priority task!! Thus it can quickly end its task,
and release the mutex without being preempted by any task with a priority less
than the higher priority. When releasing the mutex the lower priority task
is set back to its lower priority, and the higher priority task is scheduled
to run.
- Why is priority inversion a problem?
-
With priority inheritance enabled one can calculate the worst case performance
time of the higher priority task by taking the worst case execution time of
the code it has to run plus the worst case execution time of the code between
taking and releasing the mutex in the lower priority task.
Without priority inheritance this would be impossible to calculate. Thus,
priority inheritance makes scheduling calculations possible!!
In vxworks we can activate the priority-inheritance protocol by using
a mutual-exclusion semaphore with the option SEM_INVERSION_SAFE enabled.
(see below)
Task control
A task has a name and a ID.
- taskSpawn() creates and activates a task
- taskInit() initializes a new task
- taskActivate() activates an initialized task
- checkStack() checks the space on the stack
- taskName() gets name by ID
- taskNameToId() gets ID by name
- exit() exits current task,REMARK memory allocated by task is not freed
- taskDelete() deletes another task, REMARK memory allocated by task is not freed
- task(Un)Safe() protects task from deletion
- taskSuspend
- taskResume
- taskRestart
- taskDelay( ticks)
- sysClkRateGet() ticks per seconds
It is also possible to add hook routines to create,switch and delete task events.
Errors and exceptions
Many functions return only the status values OK(0) or ERROR(-1).
Functions that return a pointer usually NULL(0) to indicate an error.
- errno
-
On error sometimes also a specific error code is set in "errno" variable.
This global variable is never cleared by vxworks routines, thus it value
always specify the last error status set.
- printErrno()
-
A string constant associated with "errno" can be displayed with
printErrno() if it has a string set in the error status symbol table statSymTbl.
Note : each interrupt and task has its own "errno" variable!!
- hardware exceptions
-
are handled by the default exception handler in vxworks which suspends the
task that caused the exception and save the state of the task. The kernel and
other tasks continue uninterrupted. A description of the exceprion is send
to the tornado IDE.
Task can also attach their own handlers for certain hardware exceptions
through the signal facility.
Shared code
Shared code by different tasks must be reentrant. This code should use
reentrent functions. Most routines in vxworks are reentrant. You should
assume that if xx_r() and xx() exist xx is not reentrant and xx_r is
reentrant. Vxworks I/O and driver routines are reentrant, but require
careful application design.
The majority of vxworks routines use the following reentrancy techniques
- dynamic stack variables
- global and static variables guarded by semaphores
- task variables (value switch on task switch to each tasks own value!)
System tasks
- Root task : tUsrRoot
-
The root task is the first task executed by the kernel. It initializes vxWorks
and than terminates.
- Logging Task : tLogTask
-
The log task is used by VxWorks modules to log system messages
without to having perform I/O in the current task context.
- Exception Task: tExcTask
-
The Exception task supports the vxWorks exception handling package by perfomring
functions that cannot be performed occur at interrupt level. It is also for
actions that cannot be performed in the current task's context, such as task
suicide. It must have the highest priority in the system.
- Network Task: tNetTask
-
The tNetTask daemon handles the task-level functions required by the vxworks
network.
- Target Agent Task: tWdbTask
-
The target agent task is created if the target agent is set to run in task mode.
Optional tasks are tShell (target shell), tRlogind (rsh server),
tTelnetd (telnet server), tPortmapd (RPC server).
Intertask communication on single CPU
Shared data
all task use the same address space
Prevent resource corruption with mutual exclusion
- Disabling interrupts : IntLock()
- semaphore
- binary semaphore (semBCreate) :
fastest, just simple binary semaphore
- mutual-exclusion semaphores (semMCreate).
Its behavior is the same as binary semaphore but with :
- option
SEM_INVERSION_SAFE
enable priority-inheritance algorithm to prevent
priority-inversion problems :
that a task that holds a resource executes at the priority of the
highest-priority task blocked on that resource
- option
SEM_DELETE_SAFE
protect the executing task from unexpected deletion
- recursive resource access
mutual-exclusion semaphores can be taken recursively. That means
a semaphore can be taken more than once by the task that holds it.
(in case of recursive algorithms)
semaphore operations :
- semBCreate() create binary semaphore
- semMCreate() create mutual-exclusion semaphore
- semCCreate() create counting semaphore
- semTake() take semaphore
- semGive() release semaphore
- semDelete() deletes semaphore
Coordinating multiple access to multiple copies of a resource
- counting semaphore
works like the binary semaphore except that it keeps track of the number
of times a semaphore is given
special semaphore options :
- queueing can be FIFO or priority order
- timeout can be used with semTake()
Synchronization task execution by Signaling
- with a binary semaphore
- a semaphore can represent a condition or a event that a task is waiting for
- semFlush() Does broadcast synchronization which unblocks all processes that are blocked
on the same semaphore atomically. (Does multiple semGive()'s until all tasks
blocked on the semaphore (with semtake) are unblocked)
- with vxworks events
- vxworks events are means of communication between tasks and interrupt
routines, between tasks and other tasks, or between tasks and vxworks
objects called resources. Examples of such resources are semaphores and
message queues.
- Events are synchronous (unlike sigals), meaning that a receiving task
must block or pend while waiting for the events to occur. When an event
is send to a task which is not waiting for it, the event will be lost.
- A task can receive an event from another task or interrupt service routine
(ISR) without having to register for it. The sending task or ISR simply
has to know of the task't interest in receiving the events.
- In order for a task to receive events from a resource, the task must
register with the resource.
- In order for the resources to send events,
the resource must be free :
- an event is send when semaphore is released (semGive) on no other
tasks are pending on it
- or a message is added to the message queue (msgQSend) and no tasks are
pending on it .
- Note: only one task can be registered with a semaphore or a message queue
at any give time.
- The meaning of each event "eventX" differs for each task.
When an event of type "eventX" is already received, the event is ignored
when it is sent again to the same task. Consequently it is not possible
to track the number of times each event has been sent to a task.
- Each task has its own events field or container, referred to as the task
events register, which is 32 bit field used to store the events that a
task receives.
- Operations:
- eventSend() send event (set into register)
- eventReceive() receive event (removed from register)
- eventClear() clears the content of the event register
- semEvStart()/semEvStop() register/unregister task to semaphore event
- msgQEvStart()/msgQEvStop() register/unregister task to semaphore event
Compare binary semaphores with events
- binary semaphores are faster and simpler
- events can be sent to multiple receivers, binary semaphore only to one
- sending of event is builtin to semaphores and message queues, which
makes programming easier.
Communicating information which can also be used for synchronization task execution
- Linked List
- implemented by lstLib which contains routines to use a linked list
- Mutual exclusion and synchronization are NOT built-in. This must be
provided by the user!
- Ring buffers
- like a FIFO message queue but than end and beginning are matched
- implemented by rngLib which contains routines to use a ring buffer
- Mutual exclusion is not required if there is only one reader and one writer
otherwise the user must provide this.
- Synchronization is NOT built-in.
- message queues
- variable number of messages (bounded by max)
- message can have a variable length (bounded by max)
- full-duplex communication between two tasks requires two message queues
- the queue can be ordered either by task priority of FIFO
- also message can also have a message priority; normal priority messages
are added to the tail of the list, priority messages are added to the
head of the list
- both send and receive operations can take timeout parameters
- reading from a empty queue blocks the reading task
- writing to a queue blocks the writing task if not enough space for message
in buffer (notice that this depends on message size!)
- Operations :
- msgQcreate() allocates and initializes a message queue
- msgQDelete() terminates and frees a message queue
- msgQSend() sends a message to a message queue
- msgQReceive() receives a message from a message queue
- synchronization and mutual exclusion are built-in!
- pipes
- alternative interface to the message queue facility that goes through
the VxWorks I/O system. Pipes are virtual I/O devices managed by the
driver "pipeDrv". One task can write to a pipe. The pipe buffers
this information. Another task can read from this pipe retreiving the
information put into the pipe by the other task. Blocking :
- Reading from a empty pipe blocks the reading task
- Writing to a pipe blocks the writing task if the pipe's buffer is full
- Operations :
- pipeDevCreate("/pipe/name",max_msgs,max_length)
- read("/pipe/name")
- write("/pipe/name")
- low level it is the same as the message queue facility so
synchronisation and mutual exclusion are built-in!
note: message queues and pipes only usable within a single CPU
Signal facility
Signals are more appropriate for error and exception handling than as a
general purpose intertask communication. Because signals are asynchronous,
it is difficult to predict which resources might be unavailable when a
particular signal is raised. Therefore it is advisable to use synchronous
intertask communications primitives like semaphores, message queues and
events instead.
Signal handlers should be treated like ISR, so to be perfectly safe call only
those routines that can safely be called from an ISR.
A signal asynchronously alters the control flow of a task. Any task or ISR can
raise a signal. The task being signaled immediately suspends its current thread
of execution and executes the task-specified signal handler routine the next
time it is scheduled to run!
The signal handler executes in the receiving task's context and makes use of
the task's stack.
The signal handler is even invoked if the task is blocked.
operations
- signal specifies the signal handle associate with a signal
- kill sends a signal to a task
- raise sends a signal to yourself
- sigsuspend suspends a task until a signal is delivered
Network-transparant intertask communication
- tcp/udp network sockets
In usage socket is almost the same as a pipe, except that it has different
endpoint for writing (writing socket) as for reading (reading socket).
Thus data is sent from the writing socket to reading socket.
There are different transport mechanisms available for sockets. Vxworks
support the TCP and UDP network protocols as transport mechanisms for
sockets.Sockets are homogeneous : they are independent of language, machine
hardware, operating systems etc..
- remote procedure calls
A facility that allows a process on one machine to call a procedure that
is executed by another process on either the same machine or a remote
machine. Internally, vxwork's RPC uses sockets as the underlying
communication mechanism.
note: TCP/IP can be used to communicate across networks, but it is low level
and not intended for real-time use -> use distributed message queues for
real time intertask communication between multiple nodes
Interrupt service code
Interrupts are used for informing the system in quickly manner of external
events. (e.g. sensor detected something) For the fastest possible response to
interrupts, vxworks runs interrupt service routines (ISRs) in a special
context outside of any task's context. Thus, interrupt handling involves no
task context switch.
All ISRs use the same interrupt stack, which is allocated and initialized at
system startup. It must be large enough to handle the worst possible
combination of nested interrupts. Use the checkstack() facility during
development to see how far it is used.
ISRs must NOT invoke routines that might cause the ISR to block!
interrupt routines
- intConnect() connects a c routine to an interrupt vector
- intContext() returns true if called from interrupt level
- intCount() returns interrupt nesting depth
- int(Un)Lock() disable/enable interrupts
routines allowed to be called from interrupts
- bLib all routines
- errnoLib errnoGet,errnoSet
- intLib intContext,intConnect,intVecSet,intVecGet
- intArchLib intLock,intUnlock
- logLib logMsg
- lstLib all routines except lstFree
- msgQlib msgQSend
- pipeDrv write
- semLib semGive
- sigLib kill
- taskLib taskSuspend,taskResume,taskPrioritySet,taskPriorityGet, etc..
...
see vxworks programmer's guide for full list (chapter 2)
Interrupt-to-task Communication
The following techniques can be used to communicate from ISRs to task-level
code :
- Shared memory and ring buffers (also linked list, except lstfree operation)
- Semaphores : semGive
- Message Queues: msgQSend
- Pipes : write
- Signals : kill
Watchdog Timers
Functions invoked by watchdog timers execute as interrupt service code at the
the interrupt level of the system clock. However if the kernel is unable to
execute it immediately (e.g. previous interrupt or kernel state) the
function is placed on the tExcTask work queue (priority of TExcTask usually 0)
operations :
- wdCreate allocates and initializes a watchdog timer
- wdDelete terminates and deallocates a watchdog timer
- wdStart starts a watchdog timer
- wdCancel cancels a running watchdog timer
Let's compare the basic vxworks clock/timer libraries with the POSIX versions
supported :
Vxworks :
- clock :
- syslib : system dependent library : get/set the system clock rate ( sysClkRate(Get|Set) ) + not time related stuff
- tickLib - clock tick support library : get/set ticks
- timer
POSIX :
- clock :
- clockLib - set,get clock time/resolution
- timer :
Note: Watchdog is normally run at interrupt but otherwise run at priority of
tExcTask usually 0. A POSIX timer is by sending a SIGALARM signal to the task,
thus is handled when the task is scheduled. So watchdog events are must faster
handled!
VxWorks tips
Write from vxworks client to ftp access on server (windows pc)
FILE * fp;
variabele = 3;
printf("hello world\n");
fp = fopen ("host:/test2.txt", "wb");
fputc(fp, 61);
fclose(fp);
Let log function vxworks write on ftp server
fdl = open("host:/log7-1.txt",O_CREAT|O_WRONLY, 0644);
if (fdl == ERROR) {
printf("Log file niet geopend\n");
} else {
printf("Log file is open\n");
logFdSet(fdl);
}
# works the same as printf
logMsg("canOpen() failed with error %d!\n", retvalue);
Script to load CAN drivers
# cat C:\tornado2.2\host\x86-win32\bin\ldcan
cd "C:/Tornado2.2/driverdisk_CAN_VW55/PENTIUM"
ld < c200i.sys
ld < c200iini
ld < ntcan.o
ld < cantest
c200iStart
APPENDIX
intertask communication between multiple CPU's
- shared memory objects (supplied by VxMP optional vxworks component)
system objects that can be accessed by tasks running on different processors.
They are called shared-memory objects because the object's data structures
must reside in memory accessible by all processors. (local objects are
only available to tasks on single processor)
- shared semaphores
- shared message queues
- shared memory-partitions
intertask communication between multiple CPU's on multiple nodes
- distributed message queues (supplied by optional vxworks component VxFusion)
- lightweight distribution mechanism
- allowing distributed systems to effectively exchange data over any
transport
- safeguard against a single point of failure by replicating a database of
known objects on every node in the multi-node system
note: TCP/IP can be used to communicate across networks, but it is low level
and not intended for real-time use