Rtos Lec Notes
Short Description
Descripción: real time operating systems lecture notes...
Description
Real-Time Operating Systems – Part 2
Design & Analysis for Embedded & Time-Critical Systems Dr. Tom Clarke
tjwc - 17-Mar-09
Real-Time Operating Systems
2.1
RTOS Course Structure
1.1 Introduction Tasks
PART 1 Real-Time Applications
1.2 Task Control in FreeRTOS
PART 2 - Implementation
Hardware Background/foreground systems Interrupts vs tasks
Task states FreeRTOS API
1.3 Shared Data Access 1.4 Semaphores Theory and applications
1.6 Synchronisation
Theory and applications
tjwc - 17-Mar-09
2.4 Timing Functions Clock Tick Task Delay implementation
1.8 & 1.9 Scheduling Problems Deadlock, starvation & livelock Priority inversion
2.3 Context Switching AVR example Source code
1.7 Scheduling Theory Priority scheduling Round-robin scheduling RMA, extended RMA, & deadlines
2.2 RTOS Task Lists Linked List implementation Bit-mapped task list implementation
1.5 Message Queues Theory and applications
2.1 Interrupts & Exceptions
2.5 RTOS Objects Semaphores Message Queues
Real-Time Operating Systems
2.2
Anatomy of an RTOS RTOS Design Goals Portability Minimise code specific to a given architecture/compiler
Scalability Make feature-set configurable, so the same system can be configured for very small, or large, systems
Hardware interface Interrupt handling Task switching Clock tick implementation
Scheduling Communication & Synchronisation Memory Allocation Timers & Delay Implementation tjwc - 17-Mar-09
Performance Low interrupt-level & task-level latency
Interested in maximum limit, not average
Low RAM use
Rich Feature-set? Ways to deal with priority-inversion Deadline scheduling Rich set of comms & synch primitives
Real-Time Operating Systems
2.3
Part 2 - RTOS Implementation
Interrupts Simple foreground/background systems How the RTOS responds to events Polling vs timer ISR vs ISR
Interrupt latency Task or interrupt?
RTOS Implementation Task lists & scheduler Linked lists vs bit-mapped array
Pre-emption & context switch Task creation RTOS startup
Timing & delays Clock tick Delay functions
Calling scheduler from tasks
Semaphores Queues tjwc - 17-Mar-09
vTaskIncrementTick() vPortYieldFromTick() vPreemptiveTick() prvCheckDelayedTasks() vTaskDelay() vTaskSwitchContext() portSAVE_CONTEXT() taskYIELD() xTaskCreate() vTaskStartScheduler() xPortStartScheduler vPortISRStartFirstTask() xQueueSend() prvUnlockQueue() prvLockQueue() prvCopyQueueData() xQueueReceive()
Real-Time Operating Systems
2.4
Lecture 2.1 - Interrupts
There cannot be greater rudeness than to interrupt another in the current of his discourse John Locke Interrupts are the fundamental device which allows sequential computer programs to Provide a very fast respond to real-time events response to a hardware Interrupts require hardware support event ALL CPUs provide some way of implementing interrupts Boundary between hardware & software support is variable As a minimum save/switch of PC must be implemented in hardware
Interrupt processing
Stop code currently executing Branch to Interrupt Service Routine (ISR) Execute ISR Return transparently to previous executing code
tjwc - 17-Mar-09
Timer timeout I/O request
Note that ISR is very different from a task – no state is saved from one invocation to the next Can't implement infinite loop Can't block
Real-Time Operating Systems
2.5
Interrupt Priority Three prioritised ISRs 3
2 1 Background code
Time
Microprocessors provide prioritised execution of interrupts During interrupt priority n, all lower priority interrupts are disabled Disabled interrupts will be enabled and executed at the first possible time when the current priority level drops below that of the disabled interrupt
Hardware support can implement branch to the correct ISR for each interrupt Vectored priority interrupt controller
Many different combinations of hardware/software possible. tjwc - 17-Mar-09
Real-Time Operating Systems
2.6
Rate Monotonic Analysis for Interrupts
Prioritised interrupts provide response to deadlines very similar to prioritised tasks implementing jobs
Interrupt priority n Hardware event initiates computation
RMA analysis can be used to determine whether all ISRs will terminate before next ISR invocation
ISR n Cn Tn
Interrupt Task
tjwc - 17-Mar-09
Interrupt n occurs
ISR n runs
ISR n returns
Task n becomes ready
Task n runs
Task n blocks
Real-Time Operating Systems
2.7
Forground/Background System Design
Real-time systems can be implemented without an RTOS using foreground/background design Time-critical computation implemented via interrupt-driven ISR (foreground) Non-critical computation implemented in background loop Note only one background loop is possible Non-critical hardware can be serviced from background loop using polling
Loop length is variable depending on which devices need to be serviced Worst-case loop length determines latency for polled devices
Advantages Simple More efficient than RTOS
Disadvantages More computation must be implemented in ISR – coding more complex & difficult to debug Can't use tasks to simplify & modularise design
tjwc - 17-Mar-09
Real-Time Operating Systems
2.8
Polling from Timer interrupts
Interrupt-driven paradigm locates computation in specific ISRs executed in response to hardware events
BackgroundLoop() { for (;;) { if (device A is ready) [ Service Device A ]; if (device B is ready) [ Service Device B ]; } }
Timer-interrupt ISR polls multiple devices at regular intervals provides guaranteed response with less hardware overhead than device-specific hardware interrupts Simpler to code than multiple separate ISRs
Timer1_ISR() // device C or D { if (device C is ready) [ Service Device C ]; if (device D is ready) [ Service Device D ]; [ update system clock ] }
Background loop also implements polled computation – but time between successive polls cannot easily be determined.
DeviceE_ISR() { [ Service Device E ]; /*NB polling not needed*/ }
Interrupt-driven computation in ISR trigerred by hardware event Response time is limited by CPU-determined interrupt time + execution time of any higher or equal priority interrupts
Cf time-interrupt polling & background loop polling
tjwc - 17-Mar-09
Real-Time Operating Systems
2.9
Response-time: Timer ISR polling vs background loop
Can use one or more timer ISRs running at different speeds – e.g. 1ms and 10ms – to implement half-way house between polled & interrupt-driven paradigms 1ms interrupt runs higher priority than 10ms interrupt 1ms interrupt provides response-time (latency) of 1ms worst case, but total length of ISR must be previous=pxp; pxp->next=pxn; px->container->numberofitems--; px->container = NULL;/* if px matters */ } tjwc - 17-Mar-09
Real-Time Operating Systems
container numberofitems
index itemvalue next
previous 2.23
List operations - InsertAfter px
pz
pxn
O(1) – constant time itemvalue
itemvalue
itemvalue
next
next
next
previous
previous
previous
owner
owner
container
container
InsertAfter(px, pz) owner { pxn = px->next; container px->next = pz; pxn->previous=pz; pz->next=pxn; pz->previous=px; pz->container=px->container; px->container->numberofitems++; }
numberofitems
index itemvalue next
previous tjwc - 17-Mar-09
Real-Time Operating Systems
2.24
List operations - InsertSorted
Insert new item px into list pxl in sorted position
O(list length) – linear time
pxl->listend 3
10
22 15
22
99 22
MAX 101
InsertSorted( px, pxl) { /*pxit is list item pointer (iterator)*/ v = px->itemvalue; /*assume v < MAX*/ for( pxit = pxl->listend; pxit->next->itemvalue next ) { /* There is nothing to do here, we are just iterating to the wanted insertion position. */ } InsertAfter( pxit, px); } tjwc - 17-Mar-09
Real-Time Operating Systems
2.25
Why doubly-linked circular list?
Doubly-linked list means removing an item is easy Circular list means start & end of list is not a special case Dummy (sentinel) item means that:
List operations have no special cases and are simple to implement Tasks can remove themselves from lists – removal is O(1) Linked list structure is very flexible – no constraints on task priorities or number of tasks List nodes separate allow standard package of list functions to be used
Empty list is not a special case List start/end is marked
Links from list items to list header needed for header update on removal of item Implementing list items as part of TCB mean that task data structures can be accessed from list items and list items accessed from tasks with no overhead. tjwc - 17-Mar-09
Advantages
Storage for list nodes is allocated as part of TCB structure
Disadvantages Sorted Insertion operation takes typical time O(length of list) Extra pointers are an overhead
Real-Time Operating Systems
2.26
Why is time important?
RTOS operations: e.g. tasks waking up and blocking involve changes to lists Worst case time for operation is important RTOS performance characteristic Deterministic time – e.g. time does not depend on what other tasks are doing – is desirable For this doubly-linked list implementation: Insert, InsertAfter, InsertStart, InsertEnd, Remove O(1)=>Deterministic
InsertSorted Depends on length of list and insert position Worst case scales as number of tasks in system
tjwc - 17-Mar-09
No list can be longer than total number of tasks Real-Time Operating Systems
2.27
FreeRTOS lists module
FreeRTOS implements in lists.h a set of doubly linked list operations, using C functions & macros, which are used throughout the RTOS void vListInitialise( xList *pxList ) void vListInsertEnd( xList *pxList, xListItem *pxNewListItem ) void vListInsert( xList *pxList, xListItem *pxNewListItem ) void vListRemove( xListItem *pxItemToRemove ) listSET_LIST_ITEM_OWNER( pxListItem, pxOwner ) listSET_LIST_ITEM_VALUE( pxListItem, xValue ) listGET_LIST_ITEM_VALUE( pxListItem ) listLIST_IS_EMPTY( pxList ) listCURRENT_LIST_LENGTH( pxList ) listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) listGET_OWNER_OF_HEAD_ENTRY( pxList ) listIS_CONTAINED_WITHIN( pxList, pxListItem ) tjwc - 17-Mar-09
Real-Time Operating Systems
2.28
FreeRTOS Ready List optimisation
The Ready list changes whenever a task becomes READY or blocked Efficient implementation is important
Instead of having a single list the ready list is implemented as an array of lists, indexed by task priority Number of tasks in each list is smaller (normally only one!) Correct list for a given task can easily be found pxReadyTasksLists[]
tjwc - 17-Mar-09
31 30 29 ... 1 0
configMAX_PRIORITIES Doubly linked list
Real-Time Operating Systems
2.29
Optimisation (2)
Task Addition is now much faster, but finding the highest priority ready task means scanning the array to find highest non-empty location. Use extra global variable to store top priority currently in ready list Highest priority task normally from topReadyPriority list If this is empty search downwards in array until non-empty list is found Update uxTopReadyPriority
This trades lower cost on access for higher cost on update Good policy if either the information is accessed more often than it is changed Or if calculating the change is quicker than calculating the information from scratch 31 Often "incremental claculation" is quick Empty 30 lists …. 2 uxTopReadyPriority READY 1 tasks 0 tjwc - 17-Mar-09
Real-Time Operating Systems
2.30
MicroC/OS-II Ready List optimisation
MicroC/OS-II uses a completely different and clever implementation for all its priority ordered task lists. Suppose each task has a unique priority – so tasks can be identified by their priority, and the priorities lie in a small range. We will assume priority P satisfies: 0 P < 64 Any power of 2 can equally well be used One byte stores 8 array locations tjwc - 17-Mar-09
A task list can be represented as a array with 1 location for each possible priority (and hence task). Locations set to 1 mean that tasks are in the list. Each item in array needs only 1 bit, so pack 8 items into each byte as shown below This allows highly efficient operations
Here tasks 5,4, and 1 are in list 7 6 5 4 3 2 1 0 0 0 1 1 0 0 1 0
Real-Time Operating Systems
2.31
OSRdyTbl
OSRdyTbl
Bit-mapped array: 1 bit per task Max 8 bytes => 64 bits 1=> task is waiting
[0]
Each row is a single byte, containing 8 bits with the task priorities shown in the diagram Total 8 bytes required for 64 bits of table To access bit, first select correct byte (index 0-7), then select correct bit Very space efficient
tjwc - 17-Mar-09
7
1
0
[1]
15 14 13 12 11 10 9
8
[2]
23 22 21 20 19 18 17 16
[3]
31 30 29 28 27 26 25 24
[4]
39 38 37 35 35 34 33 32
[5]
47 46 45 44 43 42 41 40
[6]
55 54 53 52 51 50 49 48
[7]
63 62 61 60 59 58 57 56
Real-Time Operating Systems
6
5
4
3
2
2.32
Bit-mapped array characteristics
Sorting is not required since each task is recorded at a position based on priority Finding highest priority task requires linear search of table We will see later that this can be very efficient
Inserting or removing a task setting or clearing a bit at a predefined position in the array determined by the task priority This is clearly O(1) and very fast
To speed up the search for highest priority task present we need one more data structure See next slide
tjwc - 17-Mar-09
Real-Time Operating Systems
2.33
OSRdyGrp
OSRdyGrp is a summary byte. Each bit is the OR of all the bits in one byte (row) of OSRdyTbl This doubles the time of insert & remove operations, but they are still fast.
OSRdyGrp is used to implement very fast searching.
tjwc - 17-Mar-09
1
1
0
1
0
0
1
1
OSRdyTbl 0 1 2 3 4 5 6 7
0
0
1
0
0
0
1
0
1
0
0
0
1
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
0
0
1
1
0
0
1
0
Y
X
Real-Time Operating Systems
2.34
How to find highest non-zero bit in a byte?
Not simple operation One byte has only 256 distinct values Use byte value to index a 256 byte constant array Store bit number of highest '1' bit of index in each array element
Trades space (table size) for faster task switch time For small systems table size can be reduced since it depends on number of tasks
tjwc - 17-Mar-09
255: 11111111 .....
8: 7: 6: 5: 4: 3: 2: 1: 0:
7 ...
00001000 3 00000111 2 00000110 2 00000101 2 00000100 2 00000011 1 00000010 1 00000001 0 00000000 not used 76543210 OSUnMapTbl index
Real-Time Operating Systems
2.35
Table-driven search for highest priority task {
}
y = OSUnMapTbl[OSRdyGrp]; // x = OSUnMapTbl[OSRdyTbl[y]];// prio = ( y usStackDepth - 1 ); } #else { pxTopOfStack = pxNewTCB->pxStack; } #endif
FreeRTOS Task Creation
/* Initialize the TCB stack to look as if the task was already running, but had been interrupted by the scheduler. The return address is set to the start of the task function. Once the stack has been initialised the top of stack variable is updated. */
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pvTaskCode, pvParameters );
/* We are going to manipulate the task queues to add this task to a ready list, so must make sure no interrupts occur. */ portENTER_CRITICAL(); {
uxCurrentNumberOfTasks++; if( uxCurrentNumberOfTasks == ( unsigned portBASE_TYPE ) 1 ) {
/* As this is the first task it must also be the current task. pxCurrentTCB = pxNewTCB*/ /* This is the first task to be created so do the preliminary initialisation required. We will not recover if this call fails, but we will report the failure. */ } else {
}
prvInitialiseTaskLists();
/* If the scheduler is not already running, make this task the current task if it is the highest priority task to be created so far. */ if( xSchedulerRunning == pdFALSE ) { if( pxCurrentTCB->uxPriority uxPriority > uxTopUsedPriority ) { uxTopUsedPriority = pxNewTCB->uxPriority; }
/* Add a counter into the TCB for tracing only. */ pxNewTCB->uxTCBNumber = uxTaskNumber; uxTaskNumber++; prvAddTaskToReadyQueue( pxNewTCB ); xReturn = pdPASS;
} portEXIT_CRITICAL(); } else { xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY; } if( xReturn == pdPASS ) { if( ( void * ) pxCreatedTask != NULL ) {
/* Pass the TCB out - in an anonymous way. The calling function/ task can use this as a handle to delete the task later if required.*/
}
*pxCreatedTask = ( xTaskHandle ) pxNewTCB;
if( xSchedulerRunning != pdFALSE ) {
/* If the created task is higher priority than the current task then it should run now. */
}
}
if( pxCurrentTCB->uxPriority < uxPriority ) { taskYIELD(); }
} return xReturn;
FreeRTOS Task Creation (3)
void vTaskStartScheduler( void ) { portBASE_TYPE xReturn;
FreeRTOS Startup
/* Add the idle task at the lowest priority. */ xReturn = xTaskCreate( prvIdleTask, ( signed portCHAR * ) "IDLE",
tskIDLE_STACK_SIZE, (void *) NULL, tskIDLE_PRIORITY, (xTaskHandle *) NULL ); if( xReturn == pdPASS ) {
/* Interrupts are turned off here, to ensure a tick does not occur before or during the call to xPortStartScheduler(). The stacks of the created tasks contain a status word with interrupts switched on so interrupts will automatically get re-enabled when the first task starts to run.*/ xSchedulerRunning = pdTRUE; xTickCount = ( portTickType ) 0;
/*Setting up the timer tick is hardware specific and in the portable interface. */ if( xPortStartScheduler() ) {
/* Should not reach here as if scheduler is running function will not return. */ } else {
/* Should only reach here if a task calls xTaskEndScheduler(). */ }
}
}
FreeRTOS Startup Portable Code portBASE_TYPE xPortStartScheduler( void ) {
/* Start the timer that generates the tick ISR. */ prvSetupTimerInterrupt(); /* Start the first task. This is done from portISR.c as ARM mode must be used. */ vPortISRStartFirstTask();
/* Should not get here! */ }
return 0;
void vPortISRStartFirstTask( void ) {
/* Simply start the scheduler. This is included here as it can only be called from ARM mode. */
}
portRESTORE_CONTEXT();
Stack changes on context switching
Stack top during execution of f2()
Task1 stack
f2
Task2 stack
Task2 context
f3
Task2 stack top when suspended Stack top on restart of f3()
Task1 Stack top when suspended
Task2 stack
Task2 context
f3
f1 Task2
1: save current context on Task1() stack 2: restore Task2() context from Task2() stack tjwc - 17-Mar-09
Task1 context
f2
f1 Stack Task1 base
Task1 stack
2
1 PC R0 …. R15 PSW
Stack Task1 base
Real-Time Operating Systems
Task2
CPU context
2.49
FreeRTOS 8086 Task switching in detail
As well as context switching, task switching involves some additional work changing RTOS state to reflect the task change We will go through the task switch process in detail for the simple case of an AVR architecture CPU This illustrates the necessary steps in a simple form
ARM code is more messy because ARM ISA shadow registers complicate things FreeRTOS is designed so that maximum possible amount of work can be done in C Of course the actual context switch can't be done in a high level language
tjwc - 17-Mar-09
Real-Time Operating Systems
2.50
Tick interrupt
We will consider the case of a task TaskA() which is executing when a system clock tick interrupt happens. The interrupt results in a higher priority task TaskB() waking up & preempting execution. The overall result is a task switch.
The SAVE_CONTEXT & RESTORE_CONTEXT macros do the difficult part of the work. tjwc - 17-Mar-09
/* Interrupt Service Routine for the RTOS tick. */ void SIG_OUTPUT_COMPARE1A( void ) { vPortYieldFromTick(); asm volatile ( "reti" ); } /*--------------------------------------*/ Compiled "naked" with nothing saved by compiler
void vPortYieldFromTick( void ) { portSAVE_CONTEXT(); vTaskIncrementTick(); /* wakes up TaskB() */ vTaskSwitchContext(); /* schedules TaskB()*/ portRESTORE_CONTEXT(); asm volatile ( "ret" ); return to ISR } /*--------------------------------------*/
Real-Time Operating Systems
2.51
0: AVR CPU context in TaskA() 8 bits
pxCurrentTCB
TaskA TCB
32 registers
R0 (A) R1 (A) R30 (A) R31 (A)
TaskA Code
LDI R0,0 LDI, R1,1 ADD R0, R1
SREG (A) RTOS state
PCH (A)
PCL (A)
SPH (A)
SPL (A)
TaskA data
16 bits NB – AVR has downward-growing stack! tjwc - 17-Mar-09
Real-Time Operating Systems
TaskA Stack
8 bits 2.52
1: CPU context after Tick Interrupt 8 bits
32 registers
R0 (A) R1 (A) R30 (A) R31 (A)
Timer ISR JSR vPortYieldfromTick
PUSH R0 MOV R0, SR PUSH R0
SREG (A) PCH (A)
PCL (A)
SPH (A)
SPL (A)
16 bits
tjwc - 17-Mar-09
start of SAVECONTEXT macro
TaskA Stack
TaskA data PCL PCH
8 bits Real-Time Operating Systems
2.53
2: CPU context after portSAVE_CONTEXT() 8 bits
32 registers
PCH (A) SPH (A)
R1 (A)
Timer ISR
SAVE…
R30 (A) R31 (A)
PCL (A) SPL (A)
16 bits
TaskA Stack TaskA data
PCL (A) PCH (A) PCL (ISR) PCH (ISR) R0 (A) SREG (A) R1 (A) …. R31 (A)
PUSHed by interrupt PUSHed by function call Pushed by SAVE_CONTEXT()
Copy of SP (A) tjwc - 17-Mar-09
TaskA TCB
pxCurrentTCB Real-Time Operating Systems
2.54
3: RTOS tick time increment
At this point the entire context of taskA has been pushed onto TaskA stack It can be retrieved later via the stored SP in taskA TCB
The ISR is still executing, using TaskA stack – extra items can be pushed temporarily without harming the stored context – any change in SP now will not affect TaskA stored SP vTaskIncrementTick() will alter system time and, we assume, wake up TaskB TaskB moved to READY list
vTaskSwitchContext() will see that TaskB is now the highest priority task in the READY list and select it as the next task. pxCurrentTCB := TaskB TCB
tjwc - 17-Mar-09
Real-Time Operating Systems
2.55
4: Start of portRESTORE_CONTEXT() 8 bits
32 registers
R1 (A)
MOV SPL,R0 MOV SPH, R1 TaskB
R30 (A) R31 (A)
PCH SPH (B)
Timer ISR
PCL SPL (B)
16 bits Copy of SP (B) tjwc - 17-Mar-09
TaskB TCB
Stack
TaskB data
PCL (B) PCH (B) PCL (ISR) PCH (ISR) R0 (B) SREG (B) R1 (B) …. R31 (B)
PUSHed by interrupt PUSHed by function call Pushed by SAVE_CONTEXT()
pxCurrentTCB Real-Time Operating Systems
2.56
5: End of portRESTORE_CONTEXT() 8 bits
32 registers
R0 (B) R1 (B) R30 (B) R31 (B) SREG (B)
PCH
PCL
SPH (B)
SPL (B)
Timer ISR RET RETI
TaskB Stack
TaskB data PCL (B) PCH (B) PCL (ISR) PCH (ISR)
PUSHed by interrupt PUSHed by function call
16 bits 8 bits tjwc - 17-Mar-09
Real-Time Operating Systems
2.57
6: Restart of taskB 8 bits
32 registers
R0 (B) R1 (B) R30 (B) R31 (B) SREG (B)
PCH (B)
PCL (B)
SPH (B)
SPL (B)
Timer ISR
Final instruction Of ISR
RET SUB R0,R1
Next TaskB instr
TaskB data
TaskB Stack
16 bits 8 bits tjwc - 17-Mar-09
Real-Time Operating Systems
2.58
AVR portSAVE_CONTEXT ; interrupts are disabled on entry ; PCH,PCL are pushed onto stack by interrupt push r0 ;push r0 first to allow SREG to be loaded in r0, __SREG_ ; r0 := SREG push r0 ; push SREG push r1 clr r1 ; needed since compiler expects 0 in r1 push r2 Push all ; push r3-r29 other push r30 registers push r31 lds r26, pxCurrentTCB ;x := pxCurrentTCB lds r27, pxCurrentTCB + 1 in r0, 0x3d ;r0 := SPL st x+, r0 ;[x]:=r0, x := x+1 in r0, 0x3e ;r0 := SPH st x+, r0 ;[x]:=r0, x := x+1
/* C code here expects r1=0 */ tjwc - 17-Mar-09
Real-Time Operating Systems
2.59
RTOS state during task switch
The RTOS data structures that have changed during the task switch are: pxCurrentTCB – always points to current task TCB Changes from TaskA to TaskB
pxReadyTasksLists[] – TaskB is added to ready list See lecture 2.2 For implementation of READY lists
TaskA TCB: SP is updated to correct value for saved TaskA context
tjwc - 17-Mar-09
Real-Time Operating Systems
2.60
Porting the RTOS context switch
The code which saves & restores context is written in assembler & depends on CPU, and (to a lesser extent) compiler. The ARM7 port has very convoluted save & restore code because ARM processor switches to a different IRQ-mode stack while processing an interrupt. The saved PC must be extracted from this stack and saved on the Task stack. ARM code will be shown here – with detailed description of how the ARM stacks are managed during portSAVE_CONTEXT() Restore operation (not shown here) is very similar but in reverse
tjwc - 17-Mar-09
Real-Time Operating Systems
2.61
ARM CPU context after IRQ interrupts TaskA
TASK A SP
R0 R1 …. R12 SP R13^ LR R14^ PC R15 TaskA registers (System mode) CPSR
tjwc - 17-Mar-09
STM/LDM instructions can transfer either system mode or current (IRQ) mode registers ^ indicates system mode R0-R12 are shared SP R13 LR R14 IRQ mode shadow registers
IRQ stack TaskA Return address
SPSR
Real-Time Operating Systems
2.62
ARM7 portSAVE_CONTEXT() STMDB STMDB NOP SUB LDMIA
SP!, {R0} SP,{SP}^
STMDB MOV LDMIA
R0!, {LR} LR, R0 SP!, {R0}
/* Push the return address onto the TaskA stack.*/ /* Now we have saved LR can use as SP for TaskA stack.*/ /* Pop R0 so we can save it onto the system mode stack*/
STMDB NOP SUB MRS STMDB
LR,{R0-LR}^
/* Push all the system mode registers onto the task stack*/
LR, LR, #60 R0, SPSR LR!, {R0}
/* Adjust stack pointer */ /* Push the SPSR (saved TaskA PSR) onto the task stack.*/
LDR LDR STMDB
R0, =ulCriticalNesting R0, [R0] LR!, {R0}
LDR LDR STR
R0, =pxCurrentTCB R1, [R0] LR, [R1]
SP, SP, #4 SP!,{R0}
tjwc - 17-Mar-09
/* Store R0 on IRQ stack temporarily as we need to use it*/ /* Set R0 to point to the task stack pointer.*/
See next slide
Real-Time Operating Systems
2.63
Messing with ARM stacks On entry ARM SP (R13) points to IRQ-mode stack – this is a shadow register STMDB
SP!, {R0}
Push R0 on IRQ stack temporarily as we need to use a register
STMDB SP,{SP}^
Store user-mode SP on IRQ stack. don’t change stack pointer – ARM does not allow a push from user-mode SP
NOP
wait for result pipelining
SUB
SP, SP, #4
LDMIA SP!,{R0}
tjwc - 17-Mar-09
- needed because of
decrement stack pointer by one item to complete push of user-mode SP pop user-mode SP from IRQ stack into R0 Real-Time Operating Systems
2.64
Lecture 2.3 Summary
Context-switch is implemented at interrupt-level – this allows interrupts to wake up a task Multiple nested interrupts must unwind to the outermost before the task-switch is implemented All user interrupts that wake RTOS tasks must call RTOS code at entry & exit to inform RTOS
Task-level switch is handled by software interrupt, followed by interrupt-level switch Context-switch involves saving & restoring register values to (from) task stacks, and changing RTOS current task info.
tjwc - 17-Mar-09
Real-Time Operating Systems
2.65
Lecture 2.3 Review Questions
tjwc - 17-Mar-09
Real-Time Operating Systems
2.66
Lecture 2.4 – FreeRTOS Timing functions
High resolution timing in an RTOS requires use of a dedicated hardware interval timer, with interrupt to signal timeout Highly system-specific, not part of this course
All RTOS provide lower resolution timing functions based on a clock-tick interrupt. Typically 1ms Period may be configured but should be >> clock tick processing time
Implementation of clock-tick requires: Update system clock Wake up tasks which are waiting on a delay or timeout
tjwc - 17-Mar-09
Real-Time Operating Systems
2.67
Overview: FreeRTOS Source Organisation
FreeRTOS tasks.c
Also croutine.c for non-preemptive scheduling
Core task control code
list.c Core list package
queue.c Queue package
Portable Arm-Keil port port.c – task-level portable code portisr.c – ISR level portable code portmacro.h – portable macro function definitions tjwc - 17-Mar-09
Include tasks.h – interface to tasks.c list.h – interface to lists.c queue.h – interface to queue.c FreeRTOS.h – includes: projdefs.h – basic definitions
including port specification
portable.h – portable definitions
One file for all ports – includes correct portmacro.h
Contains any other stuff
FreeRTOSconfig.h
App-specific definitions
Real-Time Operating Systems
2.68
Overview: FreeRTOS Task States Task state
Eventlist
Genericlist
READY
no
ReadyList
Waiting on Queue
Queue Waiter list
DelayedTaskList
Waiting on Queue
Queue Waiter list
SuspendedTaskList
Delayed
no
DelayedTaskList
Suspended
no
SuspendedTaskList
xTicksToWait = portMAX_DELAY
=> no timeout
tjwc - 17-Mar-09
Real-Time Operating Systems
2.69
Overview of clock-tick code System-specific code sets up hardware timer to provide an interrupt at clock tick frequency (1000Hz). Timer ISR performs clock tick operations:
1. Save current task context 2. vTaskIncrementTick() Perform clock tick processing (inline function to reduce entry/exit overhead)
3. Call Scheduler 4. Restore selected task context 5. Return from interrupt to selected task tjwc - 17-Mar-09
Real-Time Operating Systems
2.70
Clock-tick operation
vTaskIncrementTick() -- Clock tick processing 1. Increment system clock 2. prvChkDelayedTasks() Check all delayed tasks for possible wakeup – implemented as macro for highest speed
tjwc - 17-Mar-09
Real-Time Operating Systems
2.71
ARM port of timer ISR (in portISR.c)
This is the code used for preemptive scheduling The actual C code is different from this (conditional compilation) if RTOS is configured for nonpreemptive scheduling void vPreemptiveTick( void ) __task {
/* Save the context of the current task. */ portSAVE_CONTEXT(); /* Increment the tick count - this may make a delayed task ready to run.*/ vTaskIncrementTick();
/* Find the highest priority task that is ready to run. */ vTaskSwitchContext(); /* Ready for the next interrupt. */
T0IR = portTIMER_MATCH_ISR_BIT; VICVectAddr = portCLEAR_VIC_INTERRUPT;
/* Restore the context of the highest priority task that is ready to run. */ }
portRESTORE_CONTEXT();
tjwc - 17-Mar-09
Real-Time Operating Systems
2.72
Delayed Clock Ticks
FreeRTOS clock tick code must deal with delayed ticks When the scheduler is disabled (preemption lock) clock ticks (which cause tasks to wake up) are not executed They are recorded for later execution
When the scheduler is enabled again any clock-ticks recorded are executed immediately as delayed ticks
vTaskSuspendAll() xTaskResumeAll()
This may wake up tasks, cause preemption, etc.
tjwc - 17-Mar-09
Real-Time Operating Systems
2.73
Data Structures used by FreeRTOS clock tick uxMissedTicks xTickCount pxDelayedTaskList pxOverflowDelayedTaskList uxSchedulerSuspended
pxReadyTasksLists[0] pxReadyTasksLists[1] pxReadyTasksLists[2] ….
tjwc - 17-Mar-09
Ready Queue: Array of task lists indexed by task priority See prvAddTaskToReadyQueue()
Real-Time Operating Systems
2.74
inline void vTaskIncrementTick( void ) { if ( uxSchedulerSuspended == pdFALSE ) { ++xTickCount; /* increment system clock */ if( xTickCount == ( portTickType ) 0 ) { [ deal with tick count overflow – see 2.77] }
Normal Case
/* See if this tick has made a timeout expire.*/
}
prvCheckDelayedTasks();/*Real work,(next slide)*/ } else { /* if scheduler is suspended */ ++uxMissedTicks; Scheduler locked [ tick hook ] } /* end if */ if (uxMissedTicks==0) { /* if tick hook needed */ [ tick hook ] #if ( configUSE_TICK_HOOK == 1 ) } { extern void vApplicationTickHook( void ); vApplicationTickHook();
} #endif
#define prvCheckDelayedTasks() { register tskTCB *pxTCB;
\ \ \ \ while ( ( pxTCB = ( tskTCB * ) listGET_OWNER_OF_HEAD_ENTRY( \ pxDelayedTaskList ) ) != NULL ) \ { \ if(xTickCount < listGET_LIST_ITEM_VALUE(&(pxTCB->xGenericListItem) ) ) \ { \ break; /*this item and all after will wake up in the future, so exit */ \ } \ vListRemove( &( pxTCB->xGenericListItem ) );/*remove from delayed task list*/\ /* Is the task waiting on an event also? */ \ if( pxTCB->xEventListItem.pvContainer ) \ { \ /* if so remove it from the event waiters list as well \ vListRemove( &( pxTCB->xEventListItem ) ); \ } \ /* add task to ready queue for possible scheduling */ \ prvAddTaskToReadyQueue( pxTCB ); \ } \ }
Tick count overflow
If ( xTickCount == ( portTickType ) 0 ) { xList *pxTemp;
/* Tick count has overflowed so we need to swap the delay lists. If there are any items in pxDelayedTaskList here then there is an error! */
}
pxTemp = pxDelayedTaskList; pxDelayedTaskList = pxOverflowDelayedTaskList; pxOverflowDelayedTaskList = pxTemp;
tjwc - 17-Mar-09
Real-Time Operating Systems
2.77
#if ( INCLUDE_vTaskDelay == 1 )
TaskDelay API function
void vTaskDelay( portTickType xTicksToDelay ) { portTickType xTimeToWake; signed portBASE_TYPE xAlreadyYielded = pdFALSE;
/* A delay time of zero just forces a reschedule. */ if( xTicksToDelay > ( portTickType ) 0 ) { vTaskSuspendAll(); [ Delay the current task – see next slide ] xAlreadyYielded = xTaskResumeAll(); }
/* Force a reschedule if xTaskResumeAll has not already done so, we may have put ourselves to sleep.*/
}
if( !xAlreadyYielded ) { taskYIELD(); }
#endif
A task that is removed from the event list while the scheduler is suspended will not get placed in the ready list or removed from the blocked list until the schedule is resumed. This task cannot be in an event list as it is the currently executing task.
/* Calculate the time to wake - this may overflow but this is not a problem. */ xTimeToWake = xTickCount + xTicksToDelay;
/* We must remove ourselves from the ready list before adding ourselves to the blocked list as the same list item is used for both lists. */
vListRemove( ( xListItem * ) &( pxCurrentTCB->xGenericListItem ) );
/* The list item will be inserted in wake time order. */
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xGenericListItem ), xTimeToWake ); if( xTimeToWake < xTickCount ) {
/* Wake time has overflowed. Place this item in the overflow list. */
}
vListInsert( ( xList * ) pxOverflowDelayedTaskList, ( xListItem * ) &( pxCurrentTCB->xGenericListItem ) );
else {
Delaying the current task
/* The wake time has not overflowed, so we use normal list. */
}
vListInsert( ( xList * ) pxDelayedTaskList, ( xListItem * ) &( pxCurrentTCB->xGenericListItem ) );
TaskResumeAll() API function
NB – TaskSuspendAll() increments uxSchedulerSuspended and does nothing else!
signed portBASE_TYPE xTaskResumeAll( void ) { It is possible that an ISR caused a task to be removed from an event register tskTCB *pxTCB; signed portBASE_TYPE list while the scheduler was suspended. If this was the case then the xAlreadyYielded = pdFALSE; removed task will have been added to the xPendingReadyList. Once {
portENTER_CRITICAL();
--uxSchedulerSuspended;
the scheduler has been resumed it is safe to move all the pending ready tasks from this list into their appropriate ready list.
if( uxSchedulerSuspended == ( unsigned portBASE_TYPE ) pdFALSE ) { if( uxCurrentNumberOfTasks > ( unsigned portBASE_TYPE ) 0 ) { portBASE_TYPE xYieldRequired = pdFALSE; /* Move any readied tasks from the pending list into the appropriate ready list. */ while( ( pxTCB = ( tskTCB * ) listGET_OWNER_OF_HEAD_ENTRY( ( ( xList * )&xPendingReadyList ) ) ) != NULL ) { vListRemove( &( pxTCB->xEventListItem ) ); vListRemove( &( pxTCB->xGenericListItem ) ); prvAddTaskToReadyQueue( pxTCB );
}
/* If we have moved a task that has a priority higher than the current task then we should yield. */ if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) { xYieldRequired = pdTRUE; Cont’d next slide }
if( uxMissedTicks > ( unsigned portBASE_TYPE ) 0 ) { while( uxMissedTicks > ( unsigned portBASE_TYPE ) 0 ) { If any ticks occurred while the scheduler vTaskIncrementTick(); was suspended then they should be processed --uxMissedTicks; now. This ensures the tick count does not } slip, and that any delayed tasks are resumed at the correct time.
}
}
}
/* As we have processed some ticks it is appropriate to yield to ensure the highest priority task that is ready to run is the task actually running. */ xYieldRequired = pdTRUE;
if( ( xYieldRequired == pdTRUE ) || ( xMissedYield == pdTRUE ) ) { xAlreadyYielded = pdTRUE; xMissedYield = pdFALSE; taskYIELD(); }
} portEXIT_CRITICAL();
}
return xAlreadyYielded;
Calling the scheduler from task API
taskYIELD() is the FreeRTOS macro that invokes the scheduler Called from task level Performs task switch to new task if current task is not highest priority ready task Typically called from task code that has just altered task state: taskYIELD() return means Either no task-switch Or the task has suspended and woken up again
taskYIELD can be called from inside or outside a critical section, on return the critical section will be as before – though if task switch happened interrupts will be enabled while the task is suspended & therefore criticality broken Implemented as a software interrupt (simulates real interrupt) tjwc - 17-Mar-09
Real-Time Operating Systems
2.82
Other techniques: Method 1
MicroC/OS-II uses a different strategy to implement task delay, which avoids having to consider task overflow Each delayed task is put on the delayed task list with a field that indicates "time to wakeup" = wakeup time – current time Order of tasks in list is not significant "time to wakeup" precision can be shorter than system clock precision, so reducing TCB size (e.g. 2 bytes instead of 4 bytes)
The clock tick chains through the entire delayed task list decrementing each "time to wakeup" If "time to wakeup" = 0 then wake up task tjwc - 17-Mar-09
Real-Time Operating Systems
2.83
This strategy has the big disadvantage that the clock-tick code is proportional to number of delayed tasks Typically all of the tasks in a system may be delayed (except the running task) since blocking on events has an optional timeout. This makes clock tick processing high overhead
tjwc - 17-Mar-09
Real-Time Operating Systems
2.84
Other Techniques: method 2
Method 2: eliminate possibility of overflow A 64 bit integer for portTickType would mean that with any possible tick rates overflow will take more than 100 years Ok to ignore overflow & so simplify the code? Computing or comparing time delay requires a 64 bit subtraction TCB size is larger
tjwc - 17-Mar-09
Real-Time Operating Systems
2.85
Lecture 2.4 - Summary
FreeRTOS divides source into port-independent & port-specific files Clock tick ISR implements system clock, task delays & timeouts. System clock is incremented once per clock tick Basic function to wake up tasks in delayed task list whose wakeup time is equal to that of system clock Future clock ticks can be stacked up for later execution when scheduling is locked. System clock overflow is dealt with via an overflow delayed task list This is necessitated by possibility of wrap-around in system clock making time ordering incorrect Alternative solution is to store time-till-wakeup with each task that is in delayed task list. Cleaner, but makes clock tick considerably slower if there are many delayed tasks
tjwc - 17-Mar-09
Real-Time Operating Systems
2.86
Lecture 2.5 – RTOS Objects
The framework we have already examined makes it easy to write RTOS code that implements synchronisation & communication operations. We will look at algorithms for implementing two common constructs which illustrate the principles. Use FreeRTOS as concrete example
Objects considered: Semaphores Message Queues
Implementing new objects is one of the easier RTOS design problems. Most of the code – event lists of waiting tasks – is common to all objects and can be reused.
tjwc - 17-Mar-09
Real-Time Operating Systems
2.87
Semaphore Implementation
Semaphore is implemented using a variable to indicate the number of tokens held by the semaphore Semaphore Take operation Decrement this value, or if it is zero suspend the taking task
Semaphore Give operation Increment this value, or if there are waiting tasks (and therefore the token count = 0) wake up the highest priority such leaving token count unchanged.
Semaphore Create operation Allocate semaphore control block
Set initial number of tokens
Counting & binary semaphore can have identical implementation – only difference is initial token value
Semaphore timeouts A Taking task can specify optional timeout to be used if it blocks Waking from timeout returns from Take operation with timeout return value tjwc - 17-Mar-09
Real-Time Operating Systems
2.88
Semaphore Event States
Each arrow corresponds to some semaphore code inside SemaGive() or SemaTake() which must execute. 2a is made up of SemaGive() from signalling task + SemaTake() from unblocked task 2b is made up of clock interrupt code + SemaTake() from unblocked task
tjwc - 17-Mar-09
Task not waiting
(1) Task waits on semaphore
(3) Task is scheduled to run
Real-Time Operating Systems
Task waiting
(2b) Timeout
ISR
(2a) Signal
Task is ready to run & re-starting
2.89
Pseudocode: Semaphore Creation & Semaphore Control Block typedef struct { int count; /*number of tokens*/ taskList eventWaiterList; } semaSCB;
Semaphore Control Block
semaSCB *SemaCreate( int initialCount); { semaSCB *s = [ allocate memory block size of semaSCB ]; s->count=initialCount; [ initialise empty Task list on s->eventwaiterList ] return s; }
tjwc - 17-Mar-09
Real-Time Operating Systems
2.90
Pseudocode: Semaphore Take Int SemaTake( semaSCB *s, int timeout); { int rc; [ enter critical section ] if (s->count > 0) { s->count--; [ exit critical section ] return semaTAKE_OK; } currentTCB->wakeupCode = semaTIMEOUT; /* set default value */ [ remove current task from READY list ] 1 [ add current task to s->EventWaiterList ] [ add current task to DelayedTasksList with timeout delay ] [ exit critical section ] taskYIELD() – /*task switch is certain */ 2 return currentTCB->wakeupCode; } tjwc - 17-Mar-09
Real-Time Operating Systems
2.91
Pseudocode – Semaphore Give void SemaGive( semaSCB *s, int timeout); { tskTCB *tcb; [ enter critical section ] tcb = [ extract highest priority task in s->eventWaitersList ] if ( tcb == NULL] ) { s->count++; [ exit critical section ] } else { tcb->wakeupCode=semaEVENT_WAKE; [ remove tcb from DelayedTasksList ] [ add tcb to READY list ] 2a [ exit critical section ] taskYIELD() ; /*preempt to run the woken task
}
if necessary*/
}
tjwc - 17-Mar-09
Real-Time Operating Systems
2.92
Semaphore Implementation Notes
A task waiting on a semaphore must know whether it was woken up from timeout or (as normal) from a semaphore Give. Use a return value from the SemaTake() API function The necessary information cannot be stored in the SCB since more than one waiting task could be woken up before any of the woken tasks execute. Therefore must use a dedicated field in the Task Control Block Providing information about whether return was immediate or after waiting is not normally necessary, but done here for completeness.
The size of the return code could be shorter than an int to reduce TCB size on 8-bit systems. For simplicity this is ignored here
The semaphore count could be one or two bytes only to save space in the SCB. If there is any possibility of semaphore count overflow this should be detected by the SemaGive() API function and result in an error code returned Not implemented, for simplicity, here.
tjwc - 17-Mar-09
Real-Time Operating Systems
2.93
FreeRTOS Semaphores
In line with a minimal footprint policy FreeRTOS implements binary semaphores only, using the existing queue mechanism with queue size 0 (i.e. no space is used for the queue items) A queue length 1 implements a binary semaphore Queue items represent tokens held by the semaphore To initialise queue with token count of 1 it is necessary to call xQueueSend() once during initialisation.
Could queues be used to implement counting semaphores? How would they be initialised?
tjwc - 17-Mar-09
Real-Time Operating Systems
2.94
FreeRTOS Message Queues
A message queue must suspend & wake up tasks in a similar way to a semaphore. It must also support a FIFO data structure for the queue itself We will look at the precise C code used by FreeRTOS to implement message queues. The FreeRTOS code depends on code to implement task lists. This is NOT described here in detail.
FreeRTOS queues allow either sending or receiving tasks to block The code for basic send & receive functions therefore has separate cases depending on queue state. Queue can block SEND if full, and RECEIVE if empty.
tjwc - 17-Mar-09
Real-Time Operating Systems
2.95
Queue Event States
Each arrow corresponds to some code inside QueueSend() and/or QueueReceive() which must execute. 2Sa is made up of QueueReceive() from signalling task 2Sb is made up of clock interrupt code QueueSend() from unblocked task
2S is followed by 3S, in which QueueSend() returns tjwc - 17-Mar-09
(1S) Task Sends to a full queue Task not waiting
(1R) Task Receives from empty queue ISR Receive ISR Send
Task waiting
(2Sb,2Rb) Timeout (3S, 3R) Task is scheduled to run
Real-Time Operating Systems
(2Sa) Receive (2Ra) Send
Task is ready to run & re-starting
2.96
Simple Pseudocode xQueueSend(message) This code works, but has a { [ enter critical section ] very long critical section which if ( [ queue is full ] ) { disables interrupts. Alternative: if (xTicksToWait > 0) { use separate queue-locks to [ delete task from ready list ] protect queue buffer from 1S [ add task to queue event waiter list ] interrupts [ add task to delayed tasks list ] taskYIELD() } } taskYield() will result in task if [ queue is full ] { switch since current task is no [ exit critical section ] longer READY [ return error ] 3S } else { /*there is room on queue*/ [ copy message to queue ] [ update queue pointers ] 2Ra [ wake up the highest priority receiving task ] } [ exit critical section ] [ return OK ] } tjwc - 17-Mar-09
Real-Time Operating Systems
2.97
Analysis
This code has a subtle problem, illustrated here. QueueReceive() may terminate early with a failure when two different tasks are both reading the same Queue. This is because waking up a task and the task picking up the message can be separated in time. There is an identical problem with two senders blocking on a full Queue. V4.05 FreeRTOS has this behaviour tjwc - 17-Mar-09
1. Queue is initially empty 2. TaskB (Prio=1) calls QueueReceive() and blocks waiting for a message 3. TaskA (Prio=3) wakes up & runs
4. TaskA (Prio=3) sends a message to the queue, this wakes up TaskB 5. TaskA continues to run & wakes up TaskC (prio=2) 6. When TaskA blocks, TaskC runs, and calls QueueReceive(). It gets the previously posted message in the queue. 7. When TaskC blocks, TaskB runs &
expects to find a message, but finds none & QueueReceive() returns with an error. Real-Time Operating Systems
2.98
typedef struct QueueDefinition { signed portCHAR *pcHead; signed portCHAR *pcTail;
Message FIFO
Queue Data Structures /*< Points to the beginning of the queue storage area. */ /*< Points to the byte at the end of the queue storage area. Once more byte is allocated than necessary to store the queue items, this is used as a marker. */
signed portCHAR *pcWriteTo; /*< Points to the free next place in the storage area. */ signed portCHAR *pcReadFrom; /*< Points to the last place that a queued item was read from.*/
/*< List of tasks that are this queue. Stored in Waiter Lists xList xTasksWaitingToReceive; /*< List of tasks that are this queue. Stored in xList xTasksWaitingToSend;
blocked priority blocked priority
waiting to post onto order. */ waiting to read from order. */
unsigned portBASE_TYPE uxMessagesWaiting;/*< The number of items currently in the queue. */ unsigned portBASE_TYPE uxLength; /*< The length of the queue defined as the
Queue Sizes
number of items it will hold, not the number of bytes.*/ unsigned portBASE_TYPE uxItemSize; /*< The size of each items that the queue will hold. */ signed portBASE_TYPE xRxLock;
Locks
signed portBASE_TYPE xTxLock; } xQUEUE;
/*< Indicates if one or more items received from the queue (removed from the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */ /*< Indicates if one or more items transmitted to the queue (added to the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */
Queue Locks
QueueSend & QueueReceive can allow ISRs as long as these do not disturb event lists of waiters Queue operations from ISR must be allowed to send/receive items Solution Separate send/receive of item from consequent waking task When queue is locked allow items to be added/removed but delay any task wakeup When queue is unlocked check if item was added (removed) and if so wake one task.
What if more than one message posted from ISRs?
Only one task will be woken OK if only one task waits on queue OK if both items on queue should be received by same task Does not allow two tasks to wake and receive one item each FreeRTOS ignores this case!
tjwc - 17-Mar-09
Real-Time Operating Systems
2.100
Queue data structures Queue item storage
xQUEUE
Task list Task list
pcHead pcReadFrom pcWriteTo pcTail uxMessagesWaiting uxLength uxItemSize xTxLock xTasksWaitingToReceive xRxLock xTasksWaitingToSend
item posted from ISR while Q locked queue locked queue unlocked tjwc - 17-Mar-09
3 items in queue
>queueUNLOCKED+1 queueUNLOCKED+1 queueUNLOCKED Real-Time Operating Systems
2.101
#define prvCopyQueueData( pxQueue, pvItemToQueue ) { memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, (unsigned) pxQueue->uxItemSize ); ++( pxQueue->uxMessagesWaiting ); pxQueue->pcWriteTo += pxQueue->uxItemSize; if( pxQueue->pcWriteTo >= pxQueue->pcTail ) { pxQueue->pcWriteTo = pxQueue->pcHead; } Macro that copies an item into the queue. This is done by } copying the item byte for byte, not by reference. Updates the queue state to ensure it's integrity after the copy.
\ \ \ \ \ \ \ \ \ \
#define prvLockQueue( pxQueue ) { taskENTER_CRITICAL(); ++( pxQueue->xRxLock ); ++( pxQueue->xTxLock ); taskEXIT_CRITICAL(); } Macro to mark a queue as locked. Locking a queue prevents an ISR from accessing the queue event lists.
\ \ \ \ \ \
prvUnlockQueue() - 1 While the queue is locked ISR queue operations may change data buffers and the queue pointers, but not the event lists. This function unlocks the queue, and alters event queues waking up (one) task if necessary. static signed portBASE_TYPE prvUnlockQueue(xQueueHandle pxQueue) { signed portBASE_TYPE xYieldRequired = pdFALSE; /* THIS FUNCTION MUST BE CALLED WITH THE SCHEDULER SUSPENDED. */ [ unlock Tx Queue, waking a receive task if necessary ] [ unlock RxQueue, waking a send task if necessary ]
}
return xYieldRequired; /*if any task of higher priority than current has been woken, return pdTRUE*/
taskENTER_CRITICAL(); { --( pxQueue->xTxLock ); /*unlock the queue */
/* See if data was added to the queue while it was locked. */ if( pxQueue->xTxLock > queueUNLOCKED ) { /*data was added */ pxQueue->xTxLock = queueUNLOCKED;
/* Data was posted while the queue was locked. Are any tasks blocked waiting for data to become available? */
if( !listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) ) { /* If so unblock one of the waiting tasks */
/* Tasks that are removed from the event list will get added to the pending ready list as the scheduler is still suspended. */ if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ) {
/* The task waiting has a higher priority so record that a context switch is required. */ xYieldRequired = pdTRUE;
}
prvUnlockQueue() - 2
} } /* end if !listLIST_IS_EMPTY(…)*/ Code to process outstanding Tx operations
} taskEXIT_CRITICAL();
RxLock code is similar
signed portBASE_TYPE xQueueSend( xQueueHandle pxQueue, const void *pvItemToQueue, portTickType xTicksToWait ) { signed portBASE_TYPE xReturn; Timeout: 0 no waiting allowed vTaskSuspendAll(); prvLockQueue( pxQueue ); if( prvIsQueueFull( pxQueue ) ) { /* If the queue is full we may have to block. */ /* The queue is full - do we want to block or just leave without posting? */ if( xTicksToWait > ( portTickType ) 0 ) { vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait ); taskENTER_CRITICAL(); taskYIELD may exit critical section { if new task did not suspend in one prvUnlockQueue( pxQueue ); Note that CS will be resumed with if( !xTaskResumeAll() ) this task 1S { taskYIELD(); /* this will result in a context switch */ } 3S vTaskSuspendAll(); prvLockQueue( pxQueue ); XQueueSend() - 1 } taskEXIT_CRITICAL(); } /* end (xTicksToWait > 0) } /* end if prvIsQueueFull(pxQueue) */ Send a message from a task to
a Queue
/* When we are here it is possible that we unblocked as space became available on the queue. It is also possible that an ISR posted to the queue since we left the critical section, so it may be that again there is no space. This would only happen if a task and ISR post onto the same queue. */ taskENTER_CRITICAL(); { if( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) { 3S
/* There is room in the queue, copy the data into the queue. */ prvCopyQueueData( pxQueue, pvItemToQueue ); xReturn = pdPASS;
/* Update the TxLock count so prvUnlockQueue knows to check for tasks waiting for data to become available in the queue. */ ++( pxQueue->xTxLock );
} else { xReturn = errQUEUE_FULL; }
} taskEXIT_CRITICAL();
XQueueSend() - 2 Send a message from a task to a Queue
/* We no longer require exclusive access to the queue. prvUnlockQueue will remove any tasks suspended on a receive if either this function or an ISR has posted onto the queue. */ if( prvUnlockQueue( pxQueue ) ) {
/* Resume the scheduler - making ready any tasks that were woken by an event while the scheduler was locked. Resuming the scheduler may cause a yield, in which case there is no point yielding again here. */ if( !xTaskResumeAll() ) { taskYIELD(); }
3S
} else {
XQueueSend() - 3
Send a message from a task to a Queue
/* Resume the scheduler - making ready any tasks that were woken by an event while the scheduler was locked. */
} }
xTaskResumeAll();
return xReturn;
FreeRTOS QueueReceive()
QueueReceive() is an exact parallel to QueueSend(), with full queue & empty queue reversed and message removed instead of added. Compare the code with that for QueueSend() Note that the message copy is done with C library block copy function memcpy() in this case, rather than a separately defined macro function.
tjwc - 17-Mar-09
Real-Time Operating Systems
2.108
signed portBASE_TYPE xQueueReceive( xQueueHandle pxQueue, void *pvBuffer, portTickType xTicksToWait ) { signed portBASE_TYPE xReturn; vTaskSuspendAll(); prvLockQueue( pxQueue ); /* If there are no messages in the queue we may have to block. */ if( prvIsQueueEmpty( pxQueue ) ) { if( xTicksToWait > ( portTickType ) 0 ) { vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait ); taskENTER_CRITICAL(); { prvUnlockQueue( pxQueue ); if( !xTaskResumeAll() ) { taskYIELD(); } XQueueReceive() - 1 vTaskSuspendAll(); prvLockQueue( pxQueue ); Task receive a message from a } queue taskEXIT_CRITICAL(); } }
taskENTER_CRITICAL(); { if( pxQueue->uxMessagesWaiting > ( unsigned portBASE_TYPE ) 0 ) { pxQueue->pcReadFrom += pxQueue->uxItemSize; if( pxQueue->pcReadFrom >= pxQueue->pcTail ) { pxQueue->pcReadFrom = pxQueue->pcHead; } --( pxQueue->uxMessagesWaiting ); memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->pcReadFrom, ( unsigned ) pxQueue->uxItemSize );
/* Increment the lock count so prvUnlockQueue knows to check for tasks waiting for space to become available on the queue. */ ++( pxQueue->xRxLock ); xReturn = pdPASS;
} else { xReturn = pdFAIL; }
} taskEXIT_CRITICAL();
XQueueReceive() - 2
Task receive a message from a queue
/* We no longer require exclusive access to the queue. */ if( prvUnlockQueue( pxQueue ) ) { if( !xTaskResumeAll() ) { taskYIELD(); } } else { xTaskResumeAll(); } }
return xReturn;
XQueueReceive() - 3 Task receive a message from a queue
ISR Queue Send (1) signed portBASE_TYPE xQueueSendFromISR( xQueueHandle pxQueue, const void *pvItemToQueue, signed portBASE_TYPE xTaskPreviouslyWoken ) {
/* Similar to xQueueSend, except we don't block if there is no room in the queue. Also we don't directly wake a task that was blocked on a queue read, instead we return a flag to say whether a context switch is required or not (i.e. has a task with a higher priority than us been woken by this post). */ if( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) { prvCopyQueueData( pxQueue, pvItemToQueue );
/* code to wake up task, if required, or record future wakeup */
see next slide } }
return xTaskPreviouslyWoken;
/* If the queue is locked we do not alter the event list. This will be done when the queue is unlocked later. */ if( pxQueue->xTxLock == queueUNLOCKED ) {
/* We only want to wake one task per ISR, so check that a task has not already been woken. */
if ( !xTaskPreviouslyWoken ) { if ( !listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) ) { if ( xTaskRemoveFromEventList(&( pxQueue->xTasksWaitingToReceive )) != pdFALSE ) {
/* The task waiting has a higher priority so record that a context switch is required. */
}
}
return pdTRUE;
} } else /* the queue is locked */ {
ISR Queue Send (2)
/* Increment the lock count so the task that unlocks the queue knows that data was posted while it was locked. */
}
++( pxQueue->xTxLock );
Lecture 2.5 - Summary
FreeRTOS implements a single, very flexible, message queue object Semaphores are special case of message queue
Message queues can block both on sending and receiving data, and implement both FIFO and LIFO queueing. The mechanism for waking up tasks is complex, and has a “bug” in early versions ( task context switch Tasks lists & scheduler
Priority-based preemptive scheduling essential Co-routines possible? round-robin time-slicing possible? EDF etc possible?
Task->task context switch (taskYield) Task delay Wait on object (message queue, semaphore) Memory allocation RTOS can use different allocation algorithms Lecture 1.2 gives an example
Critical section implementation Interrupt lock Preemption (scheduler) lock tjwc - 17-Mar-09
Real-Time Operating Systems
2.115
Design Aims Revisited
Portability Minimise code specific to a given architecture/compiler
Scalability Make feature-set configurable, so the same system can be configured for very small, or large, systems
Performance Low interrupt-level & task-level latency Interested in maximum limit, not average
Small RAM & ROM footprint
Rich Feature-set? Ways to deal with priority-inversion Deadline scheduling Rich set of comms & synch primitives
tjwc - 17-Mar-09
Real-Time Operating Systems
2.116
Overview: FreeRTOS design
FreeRTOS has very minimal port-specific code Interrupt enable/disable Context switch Task startup stack initialisation
FreeRTOS is highly configurable Size of system clock is adjustable (32/16/8 bits). Kernel API functions can be individually omitted
Task suspend Message Queue write from ISR TaskDelay() TaskDelayUntil()
FreeRTOS aim is to be clean and have a very small footprint Reuse generic tasks lists throughout RTOS Reuse generic queues as semaphores
Optimised linked list implementation leads to fast & flexible task lists Ready list is optimised with array based on priorties Task lists are doubly linked for efficient task removal
FreeRTOS has fairly small feature-set tjwc - 17-Mar-09
Real-Time Operating Systems
2.117
Common RTOS Design Decisions
Write RTOS in C Use C preprocessor to switch features on/off Use C macros for efficient implementation of hardwaredependent primitives
Use global memory space – ignore hardware memory management Appropriate for small & mediumsized embedded systems
Use a single periodic timer interrupt "clock tick" to provide RTOS timing functions Aids portability – machinedependent code is localised
Use dynamic task creation Use priority-based preemptive scheduling Provide (at least) semaphores, message queues
Implement RTOS objects, tasks, semaphores, etc, via control blocks as C structures: TCB – task control block SCB – semaphore control block
tjwc - 17-Mar-09
Real-Time Operating Systems
2.118
Lecture 2.6 – Conclusion
Analysis Real-time introduces a wide variety of problems RMA is best quantitative technique to ensure correctness- works with prioritised system Liveness problems are a big issue in real-time systems which can only be addressed through careful attention at application design stage Deadlock – crucial but straightforward Starvation – easy to mend but more difficult to detect Livelock – even more difficult to detect Priority inversion – must be fully understood. A variety of RTOSspecific solutions are available
tjwc - 17-Mar-09
Implementation RTOS implementation is still relatively immature, with competing methods for the various internal functions Unlike other OS, scalability is essential, with ability to run on both very resource-constrained systems and relatively large systems a merit To achieve this RTOS must be configurable with code size dependent on selected feature-set RTOS can relatively easily be ported to new architectures – port depends on details of CPU, C compiler, and assembler. It is sometimes possible to use “inline assembly code” embedded in C functions and avoid using a separate assembler
Real-Time Operating Systems
2.119
Exam
One compulsory question with many small parts (40 marks) Three optional questions, must complete two (30 marks each) Exam notes will be published on the first day of the Summer Term and should be studied before the exam They will contain background material & C code specific to exam questions They will contain all relevant reference material on FreeRTOS API
Types of question: Analysis of given RTS RMA & extended RMA liveness analysis
Use of API primitives to implement synchronisation & communication Pseudocode C code with correct use of API
RTOS implementation
Compare & contrast different implementations of specific constructs Explain precisely operation of specific RTOS code in given situation Write pseudocode for implementation of specific RTOS constructs Write pseudocode for and/or analyse foreground/background implementations of RTS
Something relating to your coursework tjwc - 17-Mar-09
Real-Time Operating Systems
2.120
View more...
Comments