Introduction to uC/OS-II http://www.micrium.com/
uC/OS-II Real-Time Systems Concepts Kernel Structure Task Management Time Management Intertask Communication & Synchronization Memory Management 06:10 /27
Real-Time Systems Concepts Multitasking Kernel Scheduling Mutual Exclusion Message Passing Interrupt 06:10 /27
Foreground/Background Systems Interrupt Level Task Level 06:10 /27
Multitasking Multitasking Related issues A process of scheduling and switching CPU between several tasks. Related issues Context Switch (Task Switch) Resource Sharing Critical Section 06:10 /27
Multitasking 06:10 /27
Task A task, or called a thread, a simple program that thinks it has the CPU all to itself. Each task is assigned a priority, its own set of CPU registers, and its own stack area . Each task typically is an infinite loop that can be in any one of five states. Ready, Running, Waiting, ISR, Dormant 06:10 /27
Task States 06:10 /27
Non-Preemptive Kernel Kernel is the part of a multitasking system responsible for the management of tasks. Context switching is the fundamental service of a kernel. Non-Preemptive Kernel v.s. Preemptive Kernel 06:10 /27
Non-Preemptive Kernel Cooperative multitasking A non-preemptive kernel allows each task to run until it voluntarily gives up control of the CPU. An ISR can make a higher priority task ready to run, but the ISR always returns to the interrupted task. Linux 2.4 is non-preemptive. Linux 2.6 is preemptive. 06:10 /27
Non-Preemptive Kernel 06:10 /27
Preemptive Kernel A preemptive kernel always executes the highest priority task that is ready to run. µC/OS-II and most commercial real-time kernels are preemptive. Much better response time. Should not use non-reentrant functions, unless the functions are mutual exclusive. 06:10 /27
Preemptive Kernel 06:10 /27
Function Reentrancy A reentrant function is a function that can be used by more than one task without fear of data corruption. Reentrant functions either use local variables or protected global variables. OS_ENTER_CRITICAL() OS_EXIT_CRITICAL() 06:10 /27
Function Reentrancy Non-Reentrant Function Reentrant Function static int Temp; void swap(int *x, int *y) { Temp = *x; *x = *y; *y = Temp; } Reentrant Function void strcpy(char *dest, char *src) { while (*dest++ = *src++) { ; } *dest = NULL; } 06:10 /27
Scheduling Round-Robin Scheduling Task Priority Assignment Tasks executed sequentially Task Priority Assignment Static priority Rate Monotonic (RM) Dynamic priority Earliest-Deadline First (EDF) Time-Derived Scheduling Pinwheel 06:10 /27
Round-Robin Kernel gives control to next task if the current task has no work to do completes reaches the end of time-slice Not supported in uC/OS-II O(1) priority preemptive scheduling 06:10 /27
Priority Inversion Problem 06:10 /27
Priority Inheritance 06:10 /27
Mutual Exclusion Protected shared data of processes. Exclusive access implementation Disabling and enabling interrupts Test-and-Set Disabling and enabling scheduler Semaphores Simple Semaphore Counting Semaphore Deadlock – set timeout for a semaphore 06:10 /27
Using Semaphore 06:10 /27
Synchronization Synchronization mechanism is used between tasks or task to ISR. Unilateral rendezvous Bilateral rendezvous 06:10 /27
Unilateral rendezvous 06:10 /27
Bilateral rendezvous 06:10 /27
Event Flags 06:10 /27
Message Passing Intertask Communication Using global variables or sending messages Only communicate to ISR through global variables Tasks are not aware when the global variables is changed unless task polls the content. 06:10 /27
Message Mailboxes A Message Mailbox, also called a message exchange, is typically a pointer size variable. Operations Initial POST PEND necessary to wait for the message being deposited ACCEPT Acknowledgement 06:10 /27
Message Queues A message queue is used to send one or more messages to a task. A message queue is an array of message mailboxes. Generally, FIFO is used. µC/OS-II allows a task to get messages Last-In-First-Out (LIFO). 06:10 /27
Interrupt An interrupt is a hardware mechanism used to inform the CPU that an asynchronous event has occurred. Interrupt Latency Interrupt Response Interrupt Recovery 06:10 /27
Interrupt Nesting 06:10 /27
Interrupt Latency Disabling interrupts affects interrupt latency. All real-time systems disable interrupts to manipulate critical sections of code. Re-enable interrupts when the critical section has executed. Interrupt latency = Maximum time to disable interrupts + Time to start the first instruction in ISR. 06:10 /27
Interrupt Response Interrupt Response means time between the reception of the interrupt and the start of the user code which will handle the interrupt. Interrupt response = Interrupt latency + Time to save CPU context The worst case for interrupt response is adopted. 06:10 /27
Interrupt Recovery Interrupt recovery is defined as the time required for the processor to return to the interrupted code. Interrupt recovery = Time to determine if a high priority task is ready + Time to restore the CPU context of the highest priority task + time of executing return-from-interrupt. 06:10 /27
Non-preemptive Kernel 06:10 /27
Preemptive Kernel 06:10 /27
Non-Maskable Interrupts (NMIs) Service the most important time-critical ISR Can not be disabled Interrupt latency = Time to execution the longest instruction + Time to start execution the NMI ISR Interrupt response = Interrupt latency + Time to save CPU context Interrupt recovery = Time to restore CPU context + time of executing return-from-interrupt 06:10 /27
Clock Tick A periodical interrupt Be viewed as heartbeat Application specific and is generally between 10ms and 200ms Faster timer causes higher overhead Delay problem should be considered in real-time kernel. 06:10 /27
Delaying a Task for 1 Tick Tick Interrupt Tick ISR All higher priority tasks Delayed Task t1 t2 t3 20 mS (6 mS) (19 mS) (27 mS) Call to delay 1 tick (20 mS) 06:10 /27
Clock Tick Solve the problem Increase the clock rate of your microprocessor. Increase the time between tick interrupts. Rearrange task priorities. Avoid using floating-point math. Get a compiler that performs better code optimization. Write time-critical code in assembly language. If possible, upgrade to a faster microprocessor in the same family. 06:10 /27
Memory Requirement Be careful to avoid large RAM requirement Large local arrays Nested/Recursive function Interrupt nesting Stack used by libraries Too many function arguments 06:10 /27
Real-Time Kernels Real-time OS allows real-time application to be designed and expanded easily. The use of an RTOS simplifies the design process by splitting the application into separate tasks. Real-time kernel requires more ROM/RAM and 2 to 4 percent overhead. 06:10 /27
uC/OS-II Real-Time Systems Concepts Kernel Structure Task Management Time Management Intertask Communication & Synchronization Memory Management 06:10 /27
Kernel Structure Task Control Blocks Ready List Task Scheduling Interrupt under uC/OS-II 06:10 /27
Critical Sections Archive this by disabling interrupt OS_CPU.H OS_ENTER_CRITICAL() OS_EXIT_CRITICAL() 06:10 /27
Tasks Up to 64 tasks Two tasks for system use(idle and statistic) Priorities 0, 1, 2, 3, OS_LOWEST_PRIO-3, OS_LOWEST_PRIO-2, OS_LOWEST_PRIO-1, OS_LOWEST_PRIO for future use The lower the priority number, the higher the priority of the task. The task priority is also the task identifier. 06:10 /27
Task States 06:10 /27
Task Control Blocks 06:10 /27 typedef struct os_tcb { OS_STK *OSTCBStkPtr; #if OS_TASK_CREATE_EXT_EN void *OSTCBExtPtr; OS_STK *OSTCBStkBottom; INT32U OSTCBStkSize; INT16U OSTCBOpt; INT16U OSTCBId; #endif struct os_tcb *OSTCBNext; struct os_tcb *OSTCBPrev; #if (OS_Q_EN && (OS_MAX_QS >= 2)) || OS_MBOX_EN || OS_SEM_EN OS_EVENT *OSTCBEventPtr; #if (OS_Q_EN && (OS_MAX_QS >= 2)) || OS_MBOX_EN void *OSTCBMsg; INT16U OSTCBDly; INT8U OSTCBStat; INT8U OSTCBPrio; INT8U OSTCBX; INT8U OSTCBY; INT8U OSTCBBitX; INT8U OSTCBBitY; #if OS_TASK_DEL_EN BOOLEAN OSTCBDelReq; } OS_TCB; 06:10 /27
OS_TCB Lists TCBs store in OSTCBTbl[] All TCBs are initialized and linked when uC/OS-II is initialized 06:10 /27
Ready List 06:10 /27
OSRdyGrp and OSRdyTbl[] Bit 0 in OSRdyGrp is 1 when any bit in OSRdyTbl[0] is 1. Bit 1 in OSRdyGrp is 1 when any bit in OSRdyTbl[1] is 1. Bit 2 in OSRdyGrp is 1 when any bit in OSRdyTbl[2] is 1. Bit 3 in OSRdyGrp is 1 when any bit in OSRdyTbl[3] is 1. Bit 4 in OSRdyGrp is 1 when any bit in OSRdyTbl[4] is 1. Bit 5 in OSRdyGrp is 1 when any bit in OSRdyTbl[5] is 1. Bit 6 in OSRdyGrp is 1 when any bit in OSRdyTbl[6] is 1. Bit 7 in OSRdyGrp is 1 when any bit in OSRdyTbl[7] is 1. 06:10 /27
Making a Task Ready to Run OSRdyGrp |= OSMapTbl[prio >> 3]; OSRdyTbl[prio >> 3] |= OSMapTbl[prio & 0x07]; Index Bit mask (Binary) 00000001 1 00000010 2 00000100 3 00001000 4 00010000 5 00100000 6 01000000 7 10000000 Task’s Priority Y X 06:10 /27
Removing a Task from the Ready List if ((OSRdyTbl[prio >> 3] &= ~OSMapTbl[prio & 0x07]) == 0) OSRdyGrp &= ~OSMapTbl[prio >> 3]; 06:10 /27
Finding the Highest Priority Task 76543210 OSUnMapTbl[] 00000000 0 00000001 0 00000010 1 00000011 0 00000100 2 00000101 0 00000110 1 00000111 0 00001000 3 00001001 0 00001010 1 00001011 0 00001100 2 00001101 0 00001110 1 00001111 0 … y = OSUnMapTbl[OSRdyGrp]; x = OSUnMapTbl[OSRdyTbl[y]]; prio = (y << 3) + x; INT8U const OSUnMapTbl[] = { 0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 }; 06:10 /27
Task Scheduler void OSSched (void) { INT8U y; OS_ENTER_CRITICAL(); if ((OSLockNesting | OSIntNesting) == 0) { (1) y = OSUnMapTbl[OSRdyGrp]; (2) OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]); (2) if (OSPrioHighRdy != OSPrioCur) { (3) OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; (4) OSCtxSwCtr++; (5) OS_TASK_SW(); (6) } OS_EXIT_CRITICAL(); 06:10 /27
Locking The Scheduler void OSSchedLock (void) { if (OSRunning == TRUE) { OS_ENTER_CRITICAL(); OSLockNesting++; OS_EXIT_CRITICAL(); } } 06:10 /27
Unlocking The Scheduler void OSSchedUnlock (void) { if (OSRunning == TRUE) { OS_ENTER_CRITICAL(); if (OSLockNesting > 0) { OSLockNesting--; if ((OSLockNesting | OSIntNesting) == 0) { (1) OS_EXIT_CRITICAL(); OSSched(); (2) } else { OS_EXIT_CRITICAL(); } } else { OS_EXIT_CRITICAL(); } } } 06:10 /27
Idle Task OSTaskIdle() Lowest priority, OS_LOWEST_PRIO void OSTaskIdle (void *pdata) { pdata = pdata; for (;;) { OS_ENTER_CRITICAL(); OSIdleCtr++; OS_EXIT_CRITICAL(); } } 06:10 /27
Statistics Task 06:10 /27
Initializing the Statistic Task void main (void) { OSInit(); /* Initialize uC/OS-II (1)*/ /* Install uC/OS-II's context switch vector */ /* Create your startup task (for sake of discussion, TaskStart()) (2)*/ OSStart(); /* Start multitasking (3)*/ } void TaskStart (void *pdata) { /* Install and initialize µC/OS-II’s ticker (4)*/ OSStatInit(); /* Initialize statistics task (5)*/ /* Create your application task(s) */ for (;;) { /* Code for TaskStart() goes here! */ } } 06:10 /27
Initializing the Statistic Task void OSStatInit (void) { OSTimeDly(2); OS_ENTER_CRITICAL(); OSIdleCtr = 0L; OS_EXIT_CRITICAL(); OSTimeDly(OS_TICKS_PER_SEC); OSIdleCtrMax = OSIdleCtr; OSStatRdy = TRUE; } 06:10 /27
Statistics Task 06:10 /27 void OSTaskStat (void *pdata) { INT32U run; INT8S usage; pdata = pdata; while (OSStatRdy == FALSE) { (1) OSTimeDly(2 * OS_TICKS_PER_SEC); } for (;;) { OS_ENTER_CRITICAL(); OSIdleCtrRun = OSIdleCtr; run = OSIdleCtr; OSIdleCtr = 0L; OS_EXIT_CRITICAL(); if (OSIdleCtrMax > 0L) { usage = (INT8S)(100L - 100L * run / OSIdleCtrMax); (2) if (usage > 100) { OSCPUUsage = 100; } else if (usage < 0) { OSCPUUsage = 0; } else { OSCPUUsage = usage; OSTaskStatHook(); (3) OSTimeDly(OS_TICKS_PER_SEC); 06:10 /27
Interrupts under uC/OS-II Be written in assembly language or C code with in-line assembly. YourISR: Save all CPU registers; (1) Call OSIntEnter() or, increment OSIntNesting directly; (2) Execute user code to service ISR; (3) Call OSIntExit(); (4) Restore all CPU registers; (5) Execute a return from interrupt instruction; 06:10 /27
Servicing an Interrupt 06:10 /27
Beginning an ISR void OSIntEnter (void) { OS_ENTER_CRITICAL(); OSIntNesting++; OS_EXIT_CRITICAL(); } 06:10 /27
Leaving an ISR void OSIntExit (void) { OS_ENTER_CRITICAL(); (1) if ((--OSIntNesting | OSLockNesting) == 0) { (2) OSIntExitY = OSUnMapTbl[OSRdyGrp]; (3) OSPrioHighRdy = (INT8U)((OSIntExitY << 3) + OSUnMapTbl[OSRdyTbl[OSIntExitY]]); if (OSPrioHighRdy != OSPrioCur) { OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; OSCtxSwCtr++; OSIntCtxSw(); (4) } OS_EXIT_CRITICAL(); 06:10 /27
Pseudo Code for Tick ISR void OSTickISR(void) { Save processor registers; Call OSIntEnter() or increment OSIntNesting; Call OSTimeTick(); Call OSIntExit(); Restore processor registers; Execute a return from interrupt instruction; } 06:10 /27
Service a Tick void OSTimeTick (void) { OS_TCB *ptcb; OSTimeTickHook(); (1) ptcb = OSTCBList; (2) while (ptcb->OSTCBPrio != OS_IDLE_PRIO) { (3) OS_ENTER_CRITICAL(); if (ptcb->OSTCBDly != 0) { if (--ptcb->OSTCBDly == 0) { if (!(ptcb->OSTCBStat & OS_STAT_SUSPEND)) { (5) OSRdyGrp |= ptcb->OSTCBBitY; (4) OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX; } else { ptcb->OSTCBDly = 1; } } } ptcb = ptcb->OSTCBNext; OS_EXIT_CRITICAL(); } OS_ENTER_CRITICAL(); (7) OSTime++; (6) OS_EXIT_CRITICAL(); } 06:10 /27
uC/OS-II Initialization Initialize variables and data structures Create the idle task OSTaskIdle() Create the statistic task OSTaskStat() 06:10 /27
After calling OSInit() 06:10 /27
Starting uC/OS-II void main (void) { OSInit(); /* Initialize uC/OS-II */ . . Create at least 1 task using either OSTaskCreate() or OSTaskCreateExt(); . . OSStart(); /* Start multitasking! OSStart() will not return */ } void OSStart (void) { INT8U y; INT8U x; if (OSRunning == FALSE) { y = OSUnMapTbl[OSRdyGrp]; x = OSUnMapTbl[OSRdyTbl[y]]; OSPrioHighRdy = (INT8U)((y << 3) + x); OSPrioCur = OSPrioHighRdy; OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; (1) OSTCBCur = OSTCBHighRdy; OSStartHighRdy(); (2) } } 06:10 /27
uC/OS-II Real-Time Systems Concepts Kernel Structure Task Management Time Management Intertask Communication & Synchronization Memory Management 06:10 /27
Task Management Task prototype Create a task Task stacks Stack checking Delete a task Change a task’s priority Suspend a task Resume a task Query task information 06:10 /27
Task Prototype void YourTask (void *pdata) { for (;;) { /* USER CODE */ Call one of uC/OS-II’s services: /* USER CODE */ } } void YourTask (void *pdata) { /* USER CODE */ OSTaskDel(OS_PRIO_SELF); } 06:10 /27
OSTaskCreate() Check that the task priority is valid Check that the task is unique Set up the task stack OSTaskStkInit() Obtain and initialize OS_TCB from TCB pool OSTCBInit() Call OSTaskCreateHook() to extend the functionality Call OSSched() when OSTaskCreate() is called from a task 06:10 /27
INT8U OSTaskCreate (void (. task)(void. pd), void. pdata, OS_STK INT8U OSTaskCreate (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT8U prio) { void *psp; INT8U err; if (prio > OS_LOWEST_PRIO) { (1) return (OS_PRIO_INVALID); } OS_ENTER_CRITICAL(); if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { (2) OSTCBPrioTbl[prio] = (OS_TCB *)1; (3) OS_EXIT_CRITICAL(); (4) psp = (void *)OSTaskStkInit(task, pdata, ptos, 0); (5) err = OSTCBInit(prio, psp, (void *)0, 0, 0, (void *)0, 0); (6) if (err == OS_NO_ERR) { (7) OSTaskCtr++; (8) OSTaskCreateHook(OSTCBPrioTbl[prio]); (9) OS_EXIT_CRITICAL(); if (OSRunning) { (10) OSSched(); (11) } else { OSTCBPrioTbl[prio] = (OS_TCB *)0; (12) return (err); return (OS_PRIO_EXIST); OSTaskCreate() 06:10 /27
OSTaskCreateExt() More flexibility, at the expense of overhead The first four arguments are the same as OSTaskCreate() id, assign a unique identifier for the task pbos, a pointer that can perform stack checking stk_size, specifies the size of the stack pext, a pointer to extend the OS_TCB opt, specifies whether stack checking is performed 06:10 /27
OSTaskCreateExt() INT8U OSTaskCreateExt (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT8U prio, INT16U id, OS_STK *pbos, INT32U stk_size, void *pext, INT16U opt) { void *psp; INT8U err; INT16U i; OS_STK *pfill; if (prio > OS_LOWEST_PRIO) { (1) return (OS_PRIO_INVALID); } OS_ENTER_CRITICAL(); if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { (2) OSTCBPrioTbl[prio] = (OS_TCB *)1; (3) OS_EXIT_CRITICAL(); (4) … return (err); } else { OS_EXIT_CRITICAL(); return (OS_PRIO_EXIST); OSTaskCreateExt() 06:10 /27
Task Stacks or Using malloc() to allocate stack space Static OS_STK MyTaskStack[stack_size]; or OS_STK MyTaskStack[stack_size]; Using malloc() to allocate stack space OS_STK *pstk; pstk = (OS_STK *)malloc(stack_size); if (pstk != (OS_STK *)0) { /* Make sure malloc() had enough space */ Create the task; } 06:10 /27
Fragmentation 06:10 /27
OSTaskStkChk() Computes the amount of free stack space by “walking” from the bottom of the stack until a nonzero value is found 06:10 /27
Stack Checking 06:10 /27
OSTaskDel() Return to the DORMANT state The code for the task will not be deleted Procedures Prevent from deleting and idle task Prevent from deleting a task from within an ISR Verify that the task to be deleted does exist Remove the OS_TCB Remove the task from ready list or other lists Set .OSTCBStat to OS_STAT_RDY Call OSDummy() Call OSTaskDelHook() Remove the OS_TCB from priority table Call OSSched() 06:10 /27
OSTaskDelReq() Tell the task that owns memory buffers or semaphore to delete itself Procedures Check the task’s priority If the task’s priority is OS_PRIO_SELF Return the flag of OSTCBDelReq Otherwise Set OSTCBDelReq of the task to OS_TASK_DEL_REQ 06:10 /27
OSTaskDelReq() Example void RequestorTask (void *pdata) { for (;;) { /* Application code */ if (‘TaskToBeDeleted()’ needs to be deleted) { while (OSTaskDelReq(TASK_TO_DEL_PRIO) != OS_TASK_NOT_EXIST) { OSTimeDly(1); } } /* Application code */ } } void TaskToBeDeleted (void *pdata) { for (;;) { /* Application code */ if (OSTaskDelReq(OS_PRIO_SELF) == OS_TASK_DEL_REQ) { Release any owned resources; De-allocate any dynamic memory; OSTaskDel(OS_PRIO_SELF); } else { /* Application code */ } } } 06:10 /27
OSTaskChangePrio() Cannot change the priority of any idle task Procedures Reserve the new priority by OSTCBPrioTbl[newprio] = (OS_TCB *) 1; Remove the task from the priority table Insert the task into new location of the priority table Change the OS_TCB of the task Call OSSched() 06:10 /27
OSTaskSuspend() Procedures Check the input priority Remove the task from the ready list Set the OS_STAT_SUSPEND flag in OS_TCB Call OSSched() 06:10 /27
OSTaskResume() Procedures Check the input priority Clear the OS_STAT_SUSPEND bit in the OSTCBStat field Set OSTCBDly to 0 Call OSSched() 06:10 /27
OSTaskQuery() INT8U OSTaskQuery (INT8U prio, OS_TCB *pdata) { OS_TCB *ptcb; if (prio > OS_LOWEST_PRIO && prio != OS_PRIO_SELF) { (1) return (OS_PRIO_INVALID); } OS_ENTER_CRITICAL(); if (prio == OS_PRIO_SELF) { (2) prio = OSTCBCur->OSTCBPrio; if ((ptcb = OSTCBPrioTbl[prio]) == (OS_TCB *)0) { (3) OS_EXIT_CRITICAL(); return (OS_PRIO_ERR); *pdata = *ptcb; (4) return (OS_NO_ERR); 06:10 /27
uC/OS-II Real-Time Systems Concepts Kernel Structure Task Management Time Management Intertask Communication & Synchronization Memory Management 06:10 /27
Time Management OSTimeDly() OSTimeDlyHMSM() OSTimeDlyResume() OSTimeGet() OSTimeSet() 06:10 /27
OSTimeDly() void OSTimeDly (INT16U ticks) { if (ticks > 0) { if ((OSRdyTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) { OSRdyGrp &= ~OSTCBCur->OSTCBBitY; } OSTCBCur->OSTCBDly = ticks; OSSched(); } } 06:10 /27
OSTimeDlyHMSM() INT8U OSTimeDlyHMSM (INT8U hours, INT8U minutes, INT8U seconds, INT16U milli) { { INT32U ticks; INT16U loops; if (hours > 0 || minutes > 0 || seconds > 0 || milli > 0) { if (minutes > 59) { return (OS_TIME_INVALID_MINUTES); } if (seconds > 59) { return (OS_TIME_INVALID_SECONDS); if (milli > 999) { return (OS_TIME_INVALID_MILLI); ticks = (INT32U)hours * 3600L * OS_TICKS_PER_SEC + (INT32U)minutes * 60L * OS_TICKS_PER_SEC + (INT32U)seconds) * OS_TICKS_PER_SEC + OS_TICKS_PER_SEC * ((INT32U)milli + 500L / OS_TICKS_PER_SEC) / 1000L; loops = ticks / 65536L; ticks = ticks % 65536L; OSTimeDly(ticks); while (loops > 0) { OSTimeDly(32768); loops--; } return (OS_NO_ERR); } else { return (OS_TIME_ZERO_DLY); } 06:10 /27
OSTimeDlyResume() INT8U OSTimeDlyResume (INT8U prio) { ptcb = (OS_TCB *)OSTCBPrioTbl[prio]; if (ptcb != (OS_TCB *)0) { if (ptcb->OSTCBDly != 0) { ptcb->OSTCBDly = 0; if (!(ptcb->OSTCBStat & OS_STAT_SUSPEND)) { OSRdyGrp |= ptcb->OSTCBBitY; OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX; OSSched(); } return (OS_NO_ERR); } else { return (OS_TIME_NOT_DLY); } } else { return (OS_TASK_NOT_EXIST); } } 06:10 /27
OSTimeGet() & OSTimeSet() INT32U OSTimeGet (void) { ticks = OSTime; return (ticks); } void OSTimeSet (INT32U ticks) { OSTime = ticks; 06:10 /27
uC/OS-II Real-Time Systems Concepts Kernel Structure Task Management Time Management Intertask Communication & Synchronization Memory Management 06:10 /27
Intertask Communication & Synchronization OS_ENTER_CRITICAL() OS_EXIT_CRITICAL() OSSchedLock() OSSchedUnlock() Semaphore Message mailbox Message queue 06:10 /27
OS_ENTER_CRITICAL() & OS_EXIT_CRITICAL() #define OS_CRITICAL_METHOD 2 #if OS_CRITICAL_METHOD == 1 #define OS_ENTER_CRITICAL() asm CLI #define OS_EXIT_CRITICAL() asm STI #endif #if OS_CRITICAL_METHOD == 2 #define OS_ENTER_CRITICAL() asm {PUSHF; CLI} #define OS_EXIT_CRITICAL() asm POPF CLI … STI 06:10 /27
Linux Example /* include/asm-i386/system.h */ /* interrupt control.. */ #define __save_flags(x) __asm__ __volatile__("pushfl ; popl %0":"=g" (x):) #define __restore_flags(x) __asm__ __volatile__("pushl %0; popfl": /* no output */ :"g" (x):"memory", "cc") #define __cli() __asm__ __volatile__("cli": : :"memory") #define __sti() __asm__ __volatile__("sti": : :"memory") /* For spinlocks etc */ #define local_irq_save(x) __asm__ __volatile__("pushfl; popl %0; cli":"=g" (x): :"memory") #define local_irq_restore(x) __restore_flags(x) #define local_irq_disable() __cli() #define local_irq_enable() __sti() 06:10 /27
Locking and Unlocking the Scheduler void OSSchedLock (void) { if (OSRunning == TRUE) { OSLockNesting++; } } void OSSchedUnlock (void) { if (OSLockNesting > 0) { OSLockNesting--; if ((OSLockNesting | OSIntNesting) == 0) { OSSched(); } 06:10 /27
ECB (Event Control Block) 06:10 /27
Event Control Block typedef struct { void *OSEventPtr; /* Ptr to message or queue structure */ INT8U OSEventTbl[OS_EVENT_TBL_SIZE]; /* Wait list for event to occur */ INT16U OSEventCnt; /* Count (when event is a semaphore) */ INT8U OSEventType; /* Event type */ INT8U OSEventGrp; /* Group for wait list */ } OS_EVENT; 06:10 /27
List of Free ECBs 06:10 /27
Wait Queue Functions void OSEventTaskRdy (OS_EVENT *pevent, void *msg, INT8U msk) { y = OSUnMapTbl[pevent->OSEventGrp]; bity = OSMapTbl[y]; x = OSUnMapTbl[pevent->OSEventTbl[y]]; bitx = OSMapTbl[x]; prio = (INT8U)((y << 3) + x); if ((pevent->OSEventTbl[y] &= ~bitx) == 0) { pevent->OSEventGrp &= ~bity; } ptcb = OSTCBPrioTbl[prio]; ptcb->OSTCBDly = 0; ptcb->OSTCBEventPtr = (OS_EVENT *)0; ptcb->OSTCBMsg = msg; ptcb->OSTCBStat &= ~msk; if (ptcb->OSTCBStat == OS_STAT_RDY) { OSRdyGrp |= bity; OSRdyTbl[y] |= bitx; } } 06:10 /27
Wait Queue Functions void OSEventTaskWait (OS_EVENT *pevent) { OSTCBCur->OSTCBEventPtr = pevent; if ((OSRdyTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) { OSRdyGrp &= ~OSTCBCur->OSTCBBitY; } pevent->OSEventTbl[OSTCBCur->OSTCBY] |= OSTCBCur->OSTCBBitX; pevent->OSEventGrp |= OSTCBCur->OSTCBBitY; } void OSEventTO (OS_EVENT *pevent) { if ((pevent->OSEventTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) { pevent->OSEventGrp &= ~OSTCBCur->OSTCBBitY; } OSTCBCur->OSTCBStat = OS_STAT_RDY; OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; 06:10 /27
Semaphore 06:10 /27
Creating a Semaphore OS_EVENT *OSSemCreate (INT16U cnt) { pevent = OSEventFreeList; if (OSEventFreeList != (OS_EVENT *)0) { OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr; } if (pevent != (OS_EVENT *)0) { pevent->OSEventType = OS_EVENT_TYPE_SEM; pevent->OSEventCnt = cnt; OSEventWaitListInit(pevent); } return (pevent); } 06:10 /27
Waiting for a Semaphore void OSSemPend (OS_EVENT *pevent, INT16U timeout, INT8U *err) { if (pevent->OSEventCnt > 0) { pevent->OSEventCnt--; } else if (OSIntNesting > 0) { *err = OS_ERR_PEND_ISR; } else { OSTCBCur->OSTCBStat |= OS_STAT_SEM; OSTCBCur->OSTCBDly = timeout; OSEventTaskWait(pevent); OSSched(); if (OSTCBCur->OSTCBStat & OS_STAT_SEM) { OSEventTO(pevent); OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; } } 06:10 /27
Signaling a Semaphore INT8U OSSemPost (OS_EVENT *pevent) { if (pevent->OSEventGrp) { OSEventTaskRdy(pevent, (void *)0, OS_STAT_SEM); OSSched(); return (OS_NO_ERR); } else { if (pevent->OSEventCnt < 65535) { pevent->OSEventCnt++; return (OS_SEM_OVF); } } 06:10 /27
Getting a Semaphore without Waiting INT16U OSSemAccept (OS_EVENT *pevent) { cnt = pevent->OSEventCnt; if (cnt > 0) { pevent->OSEventCnt--; } return (cnt); 06:10 /27
Obtaining the Status of a Semaphore INT8U OSSemQuery (OS_EVENT *pevent, OS_SEM_DATA *pdata) { pdata->OSEventGrp = pevent->OSEventGrp; psrc = &pevent->OSEventTbl[0]; pdest = &pdata->OSEventTbl[0]; for (i = 0; i < OS_EVENT_TBL_SIZE; i++) { *pdest++ = *psrc++; } pdata->OSCnt = pevent->OSEventCnt; return (OS_NO_ERR); } 06:10 /27
Message Mailbox 06:10 /27
Creating a Mailbox OS_EVENT *OSMboxCreate (void *msg) { pevent = OSEventFreeList; if (OSEventFreeList != (OS_EVENT *)0) { OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr; } if (pevent != (OS_EVENT *)0) { pevent->OSEventType = OS_EVENT_TYPE_MBOX; pevent->OSEventPtr = msg; OSEventWaitListInit(pevent); return (pevent); 06:10 /27
Waiting for a Message to Arrive at a Mailbox void *OSMboxPend (OS_EVENT *pevent, INT16U timeout, INT8U *err) { msg = pevent->OSEventPtr; if (msg != (void *)0) { pevent->OSEventPtr = (void *)0; } else if (OSIntNesting > 0) { *err = OS_ERR_PEND_ISR; } else { OSTCBCur->OSTCBStat |= OS_STAT_MBOX; OSTCBCur->OSTCBDly = timeout; OSEventTaskWait(pevent); OSSched(); if ((msg = OSTCBCur->OSTCBMsg) != (void *)0) { OSTCBCur->OSTCBMsg = (void *)0; OSTCBCur->OSTCBStat = OS_STAT_RDY; OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; } else if (OSTCBCur->OSTCBStat & OS_STAT_MBOX) { OSEventTO(pevent); } return (msg); 06:10 /27
Depositing a Message in a Mailbox INT8U OSMboxPost (OS_EVENT *pevent, void *msg) { if (pevent->OSEventGrp) { OSEventTaskRdy(pevent, msg, OS_STAT_MBOX); OSSched(); return (OS_NO_ERR); } else { if (pevent->OSEventPtr != (void *)0) { return (OS_MBOX_FULL); pevent->OSEventPtr = msg; return (OS_NO_ERR); } } 06:10 /27
Getting a Message without Waiting void *OSMboxAccept (OS_EVENT *pevent) { msg = pevent->OSEventPtr; if (msg != (void *)0) { pevent->OSEventPtr = (void *)0; } return (msg); } 06:10 /27
Obtaining the Status of a Mailbox INT8U OSMboxQuery (OS_EVENT *pevent, OS_MBOX_DATA *pdata) { pdata->OSEventGrp = pevent->OSEventGrp; psrc = &pevent->OSEventTbl[0]; pdest = &pdata->OSEventTbl[0]; for (i = 0; i < OS_EVENT_TBL_SIZE; i++) { *pdest++ = *psrc++; } pdata->OSMsg = pevent->OSEventPtr; return (OS_NO_ERR); } 06:10 /27
Using a Mailbox as a Binary Semaphore void Task1 (void *pdata) { for (;;) { OSMboxPend(MboxSem, 0, &err); /* Obtain access to resource(s) */ . . /* Task has semaphore, access resource(s) */ OSMboxPost(MboxSem, (void )1); /* Release access to resource(s) */ } 06:10 /27
Using a Mailbox as a Time Delay void Task1 (void *pdata) { for (;;) { OSMboxPend(MboxTimeDly, TIMEOUT, &err); /* Delay task */ . . /* Code executed after time delay */ } void Task2 (void *pdata) { OSMboxPost(MboxTimeDly, (void *)1); /* Cancel delay for Task1 */ 06:10 /27
Message Queues OSQCreate() OSQPend() OSQPost() OSQPostFront() OSQAccept() OSQFlush() OSQQuery() 06:10 /27
Message Queues A message queue Allows a task or an ISR to send pointer size variables to another task. Each pointer points a specific data structure containing a ‘message’. Looks like a mailbox with multiple entries. Is like an array of mailboxes except that there is only one wait list. 06:10 /27
Message Queues (Cont.) 06:10 /27
Data Structures in a Message Queue 06:10 /27
Queue Control Block A queue control block contains following fields OSQPtr OSQStart OSQEnd OSQIn OSQOut OSQSize OSQEntries 06:10 /27
List of Free Queue Control Blocks 06:10 /27
Message Queue Is a Circular Buffer 06:10 /27
Creating a Queue Specify the number of entries. OSQCreate() requires that you allocate an array of pointers that hold the message. The array must be declared as an array of pointers to void. Once a message queue has been created, it cannot be deleted. 06:10 /27
OSQCreate() OS_EVENT *OSQCreate (void **start, INT16U size) { pevent = OSEventFreeList; if (OSEventFreeList != (OS_EVENT *)0) { OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr; } if (pevent != (OS_EVENT *)0) { pq = OSQFreeList; if (OSQFreeList != (OS_Q *)0) { OSQFreeList = OSQFreeList->OSQPtr; if (pq != (OS_Q *)0) { pevent->OSEventType = OS_EVENT_TYPE_Q; pevent->OSEventPtr = pq; OSEventWaitListInit(pevent); } else { pevent->OSEventPtr = (void *)OSEventFreeList; OSEventFreeList = pevent; pevent = (OS_EVENT *)0; return (pevent); 06:10 /27
OSQPend() (1) void *OSQPend (OS_EVENT *pevent, INT16U timeout, INT8U *err) { pq = pevent->OSEventPtr; if (pq->OSQEntries != 0) { msg = *pq->OSQOut++; pq->OSQEntries--; if (pq->OSQOut == pq->OSQEnd) { pq->OSQOut = pq->OSQStart; } *err = OS_NO_ERR; } else if (OSIntNesting > 0) { *err = OS_ERR_PEND_ISR; } else { OSTCBCur->OSTCBStat |= OS_STAT_Q; OSTCBCur->OSTCBDly = timeout; OSEventTaskWait(pevent); OSSched(); if ((msg = OSTCBCur->OSTCBMsg) != (void *)0) { OSTCBCur->OSTCBMsg = (void *)0; OSTCBCur->OSTCBStat = OS_STAT_RDY; OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; *err = OS_NO_ERR; 06:10 /27
OSQPend() (2) } else if (OSTCBCur->OSTCBStat & OS_STAT_Q) { OSEventTO(pevent); msg = (void *)0; *err = OS_TIMEOUT; } else { msg = *pq->OSQOut++; pq->OSQEntries--; if (pq->OSQOut == pq->OSQEnd) { pq->OSQOut = pq->OSQStart; } OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; *err = OS_NO_ERR; return (msg); 06:10 /27
OSQPost() INT8U OSQPost (OS_EVENT *pevent, void *msg) { if (pevent->OSEventGrp) { OSEventTaskRdy(pevent, msg, OS_STAT_Q); OSSched(); return (OS_NO_ERR); } else { pq = pevent->OSEventPtr; if (pq->OSQEntries >= pq->OSQSize) return (OS_Q_FULL); else { *pq->OSQIn++ = msg; pq->OSQEntries++; if (pq->OSQIn == pq->OSQEnd) { pq->OSQIn = pq->OSQStart; } 06:10 /27
OSQPostFront() OSQPostFront() Is basically identical to OSQPost(). Uses OSQOut instead of OSQIn as the pointer to the next entry. 06:10 /27
OSQPostFront() (Cont.) INT8U OSQPostFront (OS_EVENT *pevent, void *msg){ if (pevent->OSEventGrp) { OSEventTaskRdy(pevent, msg, OS_STAT_Q); OSSched(); return (OS_NO_ERR); } else { pq = pevent->OSEventPtr; if (pq->OSQEntries >= pq->OSQSize) return (OS_Q_FULL); else { if (pq->OSQOut == pq->OSQStart) { pq->OSQOut = pq->OSQEnd; } pq->OSQOut--; *pq->OSQOut = msg; pq->OSQEntries++; 06:10 /27
OSQAccept() void *OSQAccept (OS_EVENT *pevent) { pq = pevent->OSEventPtr; if (pq->OSQEntries != 0) { msg = *pq->OSQOut++; pq->OSQEntries--; if (pq->OSQOut == pq->OSQEnd) { pq->OSQOut = pq->OSQStart; } } else { msg = (void *)0; return (msg); 06:10 /27
OSQFlush() INT8U OSQFlush (OS_EVENT *pevent) { pq = pevent->OSEventPtr; pq->OSQIn = pq->OSQStart; pq->OSQOut = pq->OSQStart; pq->OSQEntries = 0; return (OS_NO_ERR); } 06:10 /27
OSQQuery() Pass a OS_Q_DATA structure to query. OS_Q_DATA contains following fields OSMsg OSNMsgs OSQSize OSEventTbl[] OSEventGrp 06:10 /27
OSQQuery() (Cont.) INT8U OSQQuery (OS_EVENT *pevent, OS_Q_DATA *pdata){ pdata->OSEventGrp = pevent->OSEventGrp; psrc = &pevent->OSEventTbl[0]; pdest = &pdata->OSEventTbl[0]; for (i = 0; i < OS_EVENT_TBL_SIZE; i++) { *pdest++ = *psrc++; } pq = (OS_Q *)pevent->OSEventPtr; if (pq->OSQEntries > 0) { pdata->OSMsg = pq->OSQOut; } else { pdata->OSMsg = (void *)0; pdata->OSNMsgs = pq->OSQEntries; pdata->OSQSize = pq->OSQSize; return (OS_NO_ERR); 06:10 /27
Using a Message Queue When Reading Analog Inputs 06:10 /27
Using a Queue as a Counting Semaphore void main (void){ OSInit(); … QSem = OSQCreate(&QMsgTbl[0], N_RESOURCES); for (i = 0; i < N_RESOURCES; i++) { OSQPost(Qsem, (void *)1); } OSTaskCreate(Task1, .., .., ..); OSStart(); void Task1 (void *pdata) { for (;;) { OSQPend(&QSem, 0, &err); /* Obtain access to resource(s) */ Task has semaphore, access resource(s) OSMQPost(QSem, (void )1); /* Release access to resource(s) */ 06:10 /27
uC/OS-II Real-Time Systems Concepts Kernel Structure Task Management Time Management Intertask Communication & Synchronization Memory Management 06:10 /27
Memory Management Overview Memory Control Blocks OSMemCreate() OSMemGet() OSMemPut() OSMemQuery() Examples 06:10 /27
ANSI C malloc() and free() Dangerous in an embedded real-time system. Fragmentation. Non-deterministic. 06:10 /27
µC/OS-II malloc() and free() Obtain a contiguous memory area. Memory blocks are the same size. Partition contains an integral number of blocks. Allocation and de-allocation is deterministic. More than one memory partition can exist. Application can obtain memory blocks of different sizes. Memory block must always be returned to the partition from which it came from. 06:10 /27
Multiple Memory Partitions 06:10 /27
Memory Control Blocks Keeps track of memory partitions through MCB. Each memory partition requires its own memory control block. Initialization is done by OSMemInit(). Number of memory partitions must be set to at least 2. 06:10 /27
Memory Control Block Data Structure typedef struct { void *OSMemAddr; void *OSMemFreeList; INT32U OSMemBlkSize; INT32U OSMemNBlks; INT32U OSMemNFree; } OS_MEM; 06:10 /27
List of Free Memory Control Blocks 06:10 /27
OSMemCreate() (1) 06:10 /27
OSMemCreate() (2) OS_MEM *OSMemCreate (void *addr, INT32U nblks, INT32U blksize, INT8U *err) { pmem = OSMemFreeList; if (OSMemFreeList != (OS_MEM *)0) { OSMemFreeList = (OS_MEM *)OSMemFreeList>OSMemFreeList; } if (pmem == (OS_MEM *)0) { *err = OS_MEM_INVALID_PART; return ((OS_MEM *)0); plink = (void **)addr; pblk = (INT8U *)addr + blksize; for (i = 0; i < (nblks - 1); i++) { *plink = (void *)pblk; plink = (void **)pblk; pblk = pblk + blksize; *plink = (void *)0; pmem->OSMemAddr = addr; pmem->OSMemFreeList = addr; pmem->OSMemNFree = nblks; pmem->OSMemNBlks = nblks; pmem->OSMemBlkSize = blksize; *err = OS_NO_ERR; return (pmem); >2 entry, entry size>a pointer 06:10 /27
OSMemGet() void *OSMemGet (OS_MEM *pmem, INT8U *err) { if (pmem->OSMemNFree > 0) { pblk = pmem->OSMemFreeList; pmem->OSMemFreeList = *(void **)pblk; pmem->OSMemNFree--; *err = OS_NO_ERR; return (pblk); } else { *err = OS_MEM_NO_FREE_BLKS; return ((void *)0); } 06:10 /27
Returning a Memory Block INT8U OSMemPut (OS_MEM *pmem, void *pblk) { if (pmem->OSMemNFree >= pmem->OSMemNBlks) return (OS_MEM_FULL); *(void **)pblk = pmem->OSMemFreeList; pmem->OSMemFreeList = pblk; pmem->OSMemNFree++; return (OS_NO_ERR); } 06:10 /27
Obtaining Status about Memory Partition Pass a OS_MEM_DATA structure to query. OS_MEM_DATA contains following fields OSAddr OSFreeList OSBlkSize OSNBlks OSNFree OSNUsed 06:10 /27
OSMemQuery() INT8U OSMemQuery (OS_MEM *pmem, OS_MEM_DATA *pdata) { pdata->OSAddr = pmem->OSMemAddr; (1) pdata->OSFreeList = pmem->OSMemFreeList; pdata->OSBlkSize = pmem->OSMemBlkSize; pdata->OSNBlks = pmem->OSMemNBlks; pdata->OSNFree = pmem->OSMemNFree; pdata->OSNUsed = pdata->OSNBlks - pdata->OSNFree; (2) return (OS_NO_ERR); } 06:10 /27
Using Dynamic Memory Allocation 06:10 /27
Waiting for Memory Blocks (1) Sometimes it’s useful to have a task wait for a memory block in case a partition runs out of blocks. µC/OS-II doesn’t support ‘pending’ on a partitions. Can support this requirement by adding a counting semaphore. 06:10 /27
Waiting for Memory Blocks (2) void main (void) { … SemaphorePtr = OSSemCreate(100); PartitionPtr = OSMemCreate(Partition, 100, 32, &err); OSTaskCreate(Task, (void *)0, &TaskStk[999], &err); OSStart(); } 06:10 /27
Waiting for Memory Blocks (3) void Task (void *pdata) { INT8U err; INT8U *pblock; for (;;) { OSSemPend(SemaphorePtr, 0, &err); pblock = OSMemGet(PartitionPtr, &err); /* Use the memory block */ OSMemPut(PartitionPtr, pblock); OSSemPost(SemaphorePtr); } 06:10 /27