Real Time Operating Systems
Johan Dams
Email: jd@puv.fi
INTRODUCTION
RTOS ???
– Real Time Operating System
Where used?
– Satellite systems
– Measuring equipment
– GSM (antennas and devices)
– Cars (e.g. ABS)
– Cameras
– Medical instruments
– Motor control
– Network adapters
– Robots
– Fax machines, copiers
– Printers, terminals, scanners, modems
– Switches and routers
– Flight systems, weapon systems
– Micro-wave systems, dishwashers, thermostats
– ...
MicroC/OS-II (Jean J Labrosse)
Free for educational use
Easy as entry level
Can handle 64 tasks (-8 for system)
64 priority levels
Price: $60 book+disk
Implementations exist for:
– Win95/98
– 68HC11
– 80x86
– M16C (!)
– …
Foreground/Background systems
Foreground
– = "Interrupt level"
– based on Interrupt Service Routines (ISR)
Background
– = "Task level"
– Most used
Real-Time Systems Concept
Definitions
"Critical Section of Code": has to be treated uninterrupted
Resource (printer, keyboard, …but also variable, structure...)
Shared Resource: can be used by more then one task. Mutual
Exclusion !!!
Multitasking: process in which CPU-time is divided under multiple tasks
Task or thread :
– Thinks it has got the CPU for itself.
– Has a priority
Five conditions in which a task can be:
– DORMANT
• exists in memory
• not yet offered to the multitasking kernel
– READY
• can be executed
• has lower priority then running tasks
– RUNNING
• if the task has control of the CPU
Definitions
– WAITING
• waits for an event (e.g. I/O)
• waits for resource to be available
• wait till certain time elapses
– ISR (Interrupt Service Routine)
• if there is an interrupt
Context switch
• Context ? See fig.
• Task Control Block gets
exchanged by OS with Registers
• More CPU registers => more
overhead
Definitions
Kernel
– Piece of a multitasking system that controls tasks
– Primary task = context switching
– Extra ROM and RAM needed -> problem for single-chip processors
– RAM gets eaten
– Extra CPU-time needed (2 tot 5%)
Scheduler (or: dispatcher)
– controls which task will run
– Mostly priority based
– Priority-based kernel = gives highest priority task that is ready to run
control of the CPU
Preemptive and non-preemptive
kernels
non-preemptive kernel
– also: "cooperative multitasking"
– A task has to give up the CPU itself
– releasing the CPU has to be done fast
– small interrupt latency
– every task can run completely
before the CPU is released
– less danger for corruption
(non-reentrant functions)
– bad response on high-priority
tasks
– crash of a task, or endless
loop --> ??
Preemptive and non-preemptive
kernels
Preemptive kernel
– Used when response time important
– eg. microC/OS-II
– Highest priority task ALWAYS gets control
– Highest priority tasks gets
immediate control
– response time very short
– Non-reentrant functions
cannot be used!
– After execution of task
continue with highest priority
task (not interrupted task)
Reentrant functions
Can be used by multiple tasks at the same time
Examples:
int Temp;
void swap(int *x, int *y)
{
Temp = *x;
*x = *y;
*y = Temp;
}
void strcpy(char *dest, char *src)
{
while(*dest++ = *src++) {
*dest = NULL;
}
}
Round-Robin scheduling
When: if 2 or more tasks have the same priority
Every task gets fixed amount of time: a quantum
= time slicing
Alternative: Task priority. Highest priority gets CPU
Static and Dynamic priorities
Static: priority does not change
Dynamic : priority changes during execution
Dynamic is needed to prevent priority inversions
Priority inversions
One of the problems when using real time kernels
Situation: 3 tasks with different priority (H-M-L)
Task 1 (High priority) needs a semaphore (resource),
occupied by task 3
problem: task 1 (H)
has to wait too long
to start running
Priority inversions
Solution: give task 3 a very high priority For as long as it
occupies the resource
Needed: a kernel
that can change the
priority itself
Assigning priorities to tasks
SOFT real-time system: tasks don’t have to be completed
within a certain time
HARD real-time system: tasks have to be completed
correctly within time
Method: RMS (Rate Monotonic Scheduling)
– Most executed tasks -> highest priority
– Demands:
• tasks occur regularly
• tasks don’t exchange anything
• preemptive scheduling needed
– real-time deadlines are met when
Ei
---- n 2 1 / n – 1
Ti
-
i
– Ei maximum time needed for task i
– Ti time between i and i+1
– n = number of tasks
Assigning task priorities
– Conclusion
NUMBER OF TASKS n 2 1 /n – 1
1 1.000
2 0.828
3 0.779
4 0.756
5 0.743
... ...
0.693
– Thus: never use more then 60% to 70% CPU time
Mutual Exclusion
Demand: exclusive use of a resource
How?
– Disable interrupts
• Used on critical sections of code
• For very short time only
– Test-and-set operations
• For important sections
• Needs more time
• Principle:
disable interrupt
test test-and-set variable
Set test-and-set variable to 1
enable interrupt
Access resource
disable interrupt
Set test-and-set variable to 0
enable interrupt
Mutual Exclusion
– Disable scheduling
• Normal: after interrupt, processing goes to highest priority task
• Now: after interrupt, processing goes back to interrupted task
– Use of semaphores
• Better method than disabling scheduling
• Invented mid 1960
• Principle
– Access control on shared resource
– Event gets clearly flagged
– Two tasks can synchronise
• = key you need in order to continue processing
• If semaphore in use, keep WAITING until it gets available
• ”Give me the key. If it is in use, I’ll keep waiting."
Mutual Exclusion
• Binary semaphore: two conditions, used or not
• Counting semaphore:from 0 tot 2^8, 2^16 of 2^32
– => semaphore counts downwards (!)
Three semaphore operations
INITIALIZE (or CREATE)
– initial waiting list is empty
WAIT (or PEND)
– a task requests a resource
– If semaphore = 0,
• next task goes in WAIT
• next task is put on waitinglist
– If semaphore > 0,
• tasks begins execution
• semaphore gets decremented
SIGNAL (or POST)
– a task releases the semaphore
– no task waiting: semaphore incremented
– task waiting: key is given to new task
– new task is
• first task that asked for semaphore (FIFO)
• highest priority task
Three semaphore operations
Example
Encapsulated semaphores
Principle:
– task does not have to control semaphore
– Resource controls the semaphore
Typical: driver of the resource solves semaphores
Release of a semaphore is done through a timeout
eg. Network card, serial port, ...
Advantages:
– tasks just use the driver
– programmer does not have to deal with the semaphores
Deadlocks (Deadly embrace)
What ?
– Two tasks waiting for each others semaphore
Solutions
– acquire all resources before proceeding
– always acquire resources in the same order and
– always release in reverse order
– use of time-outs on semaphores
Synchronisation
What ?
– Synchronisation between tasks
– Synchronisation between tasks and ISR
How ?
– Use of semaphores
– "unilateral rendezvous"
– semaphore signals an event (flag, NOT a key)
Synchronisation
– counting semaphores for accumulation of events not processed yet
– More than one task can be waiting for event:
• highest task first
• first task first (FIFO)
– "bilateral rendezvous"
• synchronising two tasks (not task + ISR) with two semaphores
Event Flags
When ?
– A task has to synchronise with multiple events
Disjunctive synchronisation
– logical OR
– synchronisation if one of the events occurred
Conjunctive synchronisation
– logical AND
– synchronisation if all
events occurred
Inter task communication
What ?
– ISR or task sends information to other task
How ?
– Global variables
– sending messages
Global variables
– Make sure you have exclusive access
• by disabling and enabling interrupts
• by use of semaphores
– Alterations are passed on by
• use of semaphores
• polling of the results
– Better methods: "message mailbox" and "message queues"
Message mailboxes
Principle:
– interchanging messages through the kernel
– typical pointer size variable
– Kernel provides a mailbox service
– Task or ISR drops a message (pointer) in the mailbox
– pointer can point to any data
– transmitter and receiver know what data the pointer holds
Waiting list
– every mailbox can have a waiting list
– used if more tasks are waiting the arrival of a message in one mailbox
Task asking message from empty mailbox
– goes in SUSPEND (WAIT)
– goes active as soon a message is present
• highest priority first
• first task first serve (FIFO)
– in general: timeout is specified
– after timeout: error code
Message mailboxes
– I-beam = message box
– hourglass "10" = number of clock ticks before time-out
Typical operations on a mailbox
– init of mailbox, can have a message initially
– deposit message in mailbox (POST)
– get message from mailbox, wait if no available (PEND)
– get message from mailbox, don't wait if no available (ACCEPT)
Message Queues
Principle
– used to send one or more messages to a task
– typical an array of mailboxes
– a task or ISR can deliver a message (pointer) in hte message queue
– one or more tasks can receive messages from the queue through the
kernel
– sender and receiver know what kind of data is behind the pointer
typical FIFO, also LIFO and highest priority possible
Also here:
– task can go in WAIT if queue is empty
– task can specify time out
– square = queue - "10" = max number of messages in queue - "0" =
time-out
Interrupts
What?
– hardware mechanism for asynchronous event
If interrupt is recognised
– CPU saves (part of) local variables (CPU-Registers)
– CPU jumps to special part of memory (ISR)
At the end of the interrupt, the program continues with:
– the background (foreground/background system)
– the interrupted task (non-preemptive kernel)
– the highest priority task ready to run (preemptive kernel)
Allow to run process events when they happen
no POLLING needed
Disabling interrupts
– possible, but highly not recommended
– in real-time systems, as short as possible
– interrupts are NOT Queued
• interrupts can be missed
Interrupt Latency
Important RTOS specification:
– Amount of time interrupts are disabled
– Disabling is needed for critical sections
– During disabling: interrupt latency
Interrupt latency?
– Interrupt latency = maximum amount of time interrupts are disabled +
Time to start executing the first instruction of the ISR
Interrupt response time
– = time between the reception of an interrupt and the start of user
code that handles the interrupt.
– For foreground/background systems AND non-preemptive kernels
• = interrupt latency + time needed to save CPU context
– For preemptive kernels
• = interrupt latency + time needed to save CPU context
+ execution time of the kernel ISR entry function
Interrupt Latency
Interrupt Recovery
– For foreground/background systems AND non-preemptive kernels
• = time to put CPU context back + time for RTI instruction
– For preemptive kernels
• = Time to determine if a higher priority task is ready
+ time to put CPU context back
+ time for RTI instruction
Non-maskable interrupts
– Cannot be disabled
– Mostly in hardware
– Only for important tasks
– Eg. To save data during power-down.
Clock tick
= special interrupt
– Periodical
= RTOS heartbeat
Typical 1 tot 200 ms
Higher clock tick leads to more overhead
Delaying a task: “n” clock ticks
Memory usage of a RTOS
Foreground-background systems
– Only dependant on application size
RTOS
– Extra room needed for kernel
– for mControllers: 1 tot 100 KBytes for the RTOS
– MicroC-OS/II : 3KB
– Extra RAM needed for ISR and context switching of tasks
ad- and disadvantages of RTOS
Advantages:
– Real time applicaties are easier to expand
– Timecritical applications are dealt with more efficiently
– Important services
• semaforen
• mailboxes
• queues
• fixed time delays
• time-out handling
Disadvantages:
– More CPU-time (2 to 5%), RAM and ROM needed
– Extra cost for the RTOS
MicroC/OS II
Kernel structure: critical sections
Critical sections
– During critical moments : disable interrupts
– Usercode can also indicate critical sections
• OS_ENTER_CRITICAL()
• OS_EXIT_CRITICAL()
– These macros are in OS_CPU.H
Kernel structuur: Tasks
Tasks
– Result = void
– Structure
• void JustAnotherTask(void *pdata)
{
User Code;
System Call;
User Code;
}
Kernel structure: Tasks
Example of an infinite task
void JustAnotherTask(void *pdata)
{
for (;;)
{
User Code;
OSMboxPend(); of
OSQPend(); of
OSSemPend(); of
OSTaskDel(OS_PRIO_SELF); of
OSTaskSuspend(OS_PRIO_SELF); of
OSTimeDly(); of
OSTimeDlyHMSM();
User Code;
}
}
Kernel structure: Tasks
Example of a temporary task
void MyTask(void *pdata)
{
User Code;
OsTaskDel(OS_PRIO_SELF);
}
Function of void *pdata
– “void” can be any kind of data
– pdata is given to the task during startup
– eg. Number of a serial port
• 1 task can handle all serial ports
Kernel structure: priorities
Priorities
– Max 64 priority levels
– 8 levels reserved for OS
– 56 for user
• from 0 tot OS_LOWEST_PRIO-2
– Low number = high priority
– Highest priority is dealt with first
Creation of tasks
– Task must be offered to Kernel
• Memory address and parameters are given
• OSTaskCreate() or OSTaskCreateExt()
Kernel structure: states of a task
States of a task
– DORMANT
• Not yet offered to kernel
– READY
• Task offered with OSTaskCreate() or OSTaskCreateExt()
• Back to dormant by OSTaskDel()
– RUNNING
• After multitasking has been started (see also further)
• Highest priority first
– WAITING
• A task can delay itself with OSTimeDly()or OSTimeDlyHMSM()
• A task can also be waiting for an event, like
– OSSemPend(); OSMboxPend(), OSQPend()
– ISR state
• When interrupt has occurred
Kernel structure: states of the kernel
States of the kernel
– Multitasking is started with OSStart()
– A WAITING task gets back activated with OSTimeTick()
– If all tasks are waiting for an event: idle task OSTaskIdle()
Kernel structure: Task Control Blocks
Task Control Blocks (OS_TCBs)
– A task that has been created, gets an OS_TCB
typedef struct os_tcb {
OS_STK *OSTCBStkPtr;
void *OSTCBExtPtr;
OS_STK *osTCBStkBottom;
INT32U OSTCBStkSize;
INT16U OSTCBOpt;
INT16U OSTCBId;
struct os_tcb *OSTCBNext;
struct os_tcb *OSTCBPrev;
OS_EVENT *oSTCBEventPtr;
void *OSTCBMsg;
INT16U OSTCBDly;
INT8U OSTCBStat;
INT8U OSTCBPrio;
INT8U OSTCBX;
INT8U OSTCBY;
INT8U OSTCBBitX;
INT8U OSTCBBitY;
BOOLEAN OSTCBDelReq;
} OS_TCB;
Kernel structure: Task Control Blocks
Explanations
– OSTCBStkPtr
• Every task has its own stack
– OSTCBExtPtr
• Pointer to own part of TCB
– OSTCBStkBottom
• Points to the end of the TCB
– OSTCBDly
• Number of clock ticks the task needs to be delayed
– OSTCBStat
• Contains current task status
• 0 = READY, see uCOS_II.H file
– OSTCBPrio
• Contains task priority, low number = high priority
– OSTCBDelReq
• Request to be deleted
Kernel structure: Task Control Blocks
Some constants
– OS_MAX_TASKS
• Max number of tasks available
• Can be reduced less memory needed
• Reduce to the actual number used
– List of OS_TCBs is made in advance
• Notice list structure
– OS_LOWEST_PRIO
• Lowest applicable priority
• Note! Number of priorities can be higher than number of tasks
Kernel structure: Locking
Scheduler
– Will let highest priority task run
– Lower priority task gets interrupted
Locking the scheduler
– Using OSSchedLock() allows scheduler to be stopped
– Low priority tasks keeps in control
– Stays like that until OSSchedUnlock() gets called
When?
– Normally: never
– Exceptions, eg. If a task has to post a lot of messages
• multiple mailboxes, queues, semaphores
• messages have to be dealt with simultaneous synchronisation
Kernel structure: Statistics
What?
– Keeps the amount (% ) of CPU in use
– Resolution = 1%
How?
– Call in the beginning OSStatInit()
• Set a variablein configuration file: OS_TASK_STAT_EN
– Every second, the task OSTaskStat() will be started.
Result is kept in variable OSCPUUsage
OSIdleCtr
OSCPUUsage 100 1
OSIdleCtrM ax
– with: OSIdleCtr : gets +1 if OSTaskIdle() gets called
OSIdleCtrMax : calculated once during Init
Kernel structure: starting program
How?
– A program in C starts with
void main(void)
{
/* own code */
}
– In this routine the following has to happen:
• Initialisation of the RTOS : OSInit()
• Creation of a task by OSTaskCreate() or OSTaskCreateExt()
• Starting of the RTOS : OSStart()
– Example:
void main(void) {
OSInit()
OSTaskCreate(TaskStart,(void *) 0,(void *)
&TaskStartStk[TASK_STK_SIZE-1],0);
OSStart();
}
Kernel structure: starting program
Structure of TaskStart()
– Is the actual program
– General structure:
void TaskStart(void *pdata) {
Install and Init OS Ticker
OSStatInit(); // initialises statistics Task
Install tasks that comprise your program
for (;;) {
your own code
OSTimeDlyHMSM(0,0,1,0);
}
Kernel structure: OS Ticker
Remember
– OS Ticker is the 1..200ms heartbeat
BAD way of starting
void main (void) {
OSInit()
InitTick();
OSStart()
}
Reason:
– Tick interrupt can start before your first task
• Enabling of TICKER is done before OSStart()
– Program can crash
Kernel structure: OS Ticker
GOOD way of starting
void TaskStart (void *pdata) {
OS_ENTER_CRITICAL()
InitTick();
OS_EXIT_CRITICAL()
Start other tasks
FOR (;;) {
other stuff
}
}
Task Management
General form of your program
– TASK = 1) Infinite loop, or
2) Task that deletes itself
– C program
– Return value = void
void My_Task(void *pdata) void My_Task(void *pdata)
{ {
for (;;) { /* own code here */
/* own code here */ OSTaskDel(OS_PRIO_SELF);
one of following calls: }
OSMBoxPend();
OSQPend();
OSSemPend();
OSTaskDel(OS_PRIO_SELF);
OSTaskSuspend(OS_PRIO_SELF);
OSTimeDly();
OSTimDlyHMSM();
}
}
Creation of a Task
Task is offeres to the kernel
– OSTaskCreate()
– OSTaskCreateExt(): has extra options
• Stack checking: if stack should get too big
• User-defined data area
• User-defined checks on data
At least one task needed before OSStart()can run
Every task has its own STACK space
– Can be expanded dynamically with malloc()
/* other code */
pstk = (OS_STK *) malloc(stack_size);
if (pstk != (OS_STK *)0) ; see if enough memory
{
/* create task */
}
/* Other code */
– Problem: fragmentation of memory
Deleting a Task
Done by OSTaskDel()
– Code still exists
– Goes back to DORMANT state
– = “hard” delete
Request to delete: OSTaskDelReq()
– = “soft” delete
– Task gets chance to clear semaphores and memory
– Task has to check for OSTaskDelReq !
/* other code */
if (OSTaskDelReq(OS_PRIO_SELF) == OS_TASK_DEL_REQ) }
/* Release all resources */
/* De-allocate memory */
OSTaskDel(OS_PRIO_SELF);
}
else {
/* Other code */
}
Other operations on tasks
Changing the priority
– OSTaskChangePrio()
Suspend
– Starts after OSTaskSuspend()
– Can only be undone by OSTaskResume()
Time Management
Delaying a task using OSTimeDly()
– OSTimeDly(ticks)
– creates context switch
– Delay of 1 to 65535 system Ticks
Delaying a task using OSTimeDlyHMSM()
– OSTimeDlyHMSM(hours,minutes,seconds,milli)
– HMSM = Hours – Minutes – Seconds – Milliseconds
– Can go till 256 hours (around 11 days)
Resuming a delayed task with OSTimeDlyResume()
Requesting time with OSTimeGet()
– Result in Ticks
– Size = INT32U
Communication and synchronisation
Use of ECB’s (Event Control Blocks)
2 forms of communication
– a) Task or ISR gives flag to a Task
– b) A Task Waits for an event
• only Tasks can wait
• !! An ISR cannot wait
Time-out possible
A ECB can be
– a semaphore
• Task keeps wachten, or
• Task can be flagged
– a message mailbox
– a message queue
Use of semaphores
Link between a Task, ISR en semaphore:
– Creation of the semaphore with OSSemCreate()
– A flag passed to a semaphore: OSSemPost()
– Waiting for a semaphore: OSSemPend()
– Accepting a semaphore without waiting: OSSemAccept()
– Requesting status of a semaphore: OSSemQuery()
Use of Message Mailboxes
Link between een Task, ISR en Message Mailbox:
– Creation of a mailbox with OSMboxCreate()
– Posting a message in a mailbox: OSMboxPost()
– Waiting for a message in a mailbox: OSMboxPend()
– Getting a mailbox without waiting: OSMboxAccept()
– Request status of a mailbox: OSMboxQuery()
Use of Message Queues
Queue is comprised of Queue Control Blocks
Use of Message Queues
A Message Queue is a circular buffer
Use of Message Queues
Link between Task, ISR en Message Queue:
– Creation of the Message Queue using OSQCreate()
– Posting a message in the queue (FIFO): OSQPost()
– Posting a message in the queue (LIFO): OSQPostFront()
– Waiting for a message in the queue: OSQPend()
– Requesting a queue without waiting: OSQAccept()
– Requesting status of a queue: OSQQuery()
Memory Management
How does C work ?
– malloc() Function for requesting memory
– free() Function for releasing memory
Problems
– In RTOS danger for fragmentation
– Quick shortage of continues memory blocks
RTOS solution
– Use of memory blocks with fixed size
– All blocks same size
– Size of blocks is configurable
Memory Management
Creation of memorypartition by use of OSMemCreate()
– Has to be done in advance
– eg. creation of 100 blocks of 32 bytes each
OS_MEM *CommTxBuf; // create pointer to buffer
INT8U CommTxPart[100][32]; // allocate memory
void main(void)
{
/* Other code */
CommTxBuf = OSMemCreate(CommTxPart,100,32,&err);
/* Check for errors using err */
/* Other code */
OSStart();
}
Memory Management
Requesting a memory block OSMemGet()
releasing a memory block OSMemPut()
Bigger blocks get linked