Introduction to Real Time Systems

Download Report

Transcript Introduction to Real Time Systems

Resource Management
Akos Ledeczi
EECE 354, Fall 2012
Vanderbilt University
Resource Sharing
• Disable/Enable interrupts: only where critical
region is very quick. Should be used sparingly in
RT systems.
• Lock/Unlock scheduler: for quick critical sections
still.
• Semaphore: when affected tasks do not have
hard deadlines (priority inversion)
• Mutex: preferred method. Largest overhead
though because it does extra work to avoid
priority inversion
Disable/Enable Interrupts
void OSFunction()
{
CPU_SR_ALLOC();
CPU_CRITICAL_ENTER();
:
/* critical section */
:
CPU_CRITICAL_EXIT();
}
•
•
•
•
OS uses this method (even when implementing the other methods)
Allocates storage for the interrupt disable status
Enter() saves status, disables interrupts
Exit() restores status
• The only way to share a resource between a task and an ISR!
Lock/Unlock Scheduler
void Function()
{
OS_ERR err;
OSSchedLock(&err);
if (err = OS_ERR_NONE) {
:
/* critical section */
:
OSSchedUnlock(&err);
/* check err */
}
else {
/* check err */
}
}
• Interrupts are not disabled, but ISR returns to this task even if
higher priority tasks are ready: behaves as a non-preemptive kernel
• Lock/Unlock can be nested
• Unlock runs scheduler (only when nesting is unwound)
• Must not make blocking calls while the scheduler is locked
• Behaves as if current task had the highest priority
Semaphores
•
•
•
•
•
•
Dijkstra in 1959
“Key” to “locked code.” You need to acquire it to access the code.
Semaphore operations are “atomic.”
Binary and counting semaphores
Can be used for resource sharing and synchronization
Functions:
OSSemCreate()
OSSemPend()
OSSemPost()
OSSemDel()
OSSemSet()
OSSemPendAbort()
Binary Semaphores
• Accessing a printer
• Hiding behind an API
Binary Semaphores cont’d.
OS_SEM MySem;
void main()
{
OS_ERR err;
…
OSSemCreate(&MySem, ”My Semaphore”, 1, &err);
…
}
void Task1 (void *p_arg)
OS_ERR err;
CPU_TS ts;
while (1) {
…
OSSemPend(&MySem, 0, OS_OPT_PEND_BLOCKING, &ts, &err);
switch(err)
case OS_ERR_NONE:
/*critical section */
OSSemPost(&MySem, OS_OPT_POST_1, &err);
/* check err */
break;
case OS_ERR_PEND_ABORT:
…
case OS_ERR_OBJ_DEL:
…
default:
/* other errors */
}
}
}
Counting Semaphores
• When multiple resources/resource
elements are available
• E.g., memory pool or circular buffer
• Initialize semaphore to the number of
items available
• Need to manage consumed/available
items
• Pend() waits on 0, otherwise,
decrements counter and returns
• Post() increments counter
Notes
• Semaphores do not increase interrupt latency
• Note that while the OS handles the semaphore
itself, interrupts are disabled
• Hence, protecting access to a single variable, for
example, is much faster using interrupt
disable/enable directly. Must be careful (e.g., a
floating point operation on a CPU with no FPU
can take very long)!
• Application can have as many semaphores as
necessary
Notes too
• OSSemPend():
– If ctr > 0, ctr is decremented, fn returns immediately
– If ctr == 0
• OS_OPT_PEND_NON_BLOCKING: returns immediately with appropriate error
code. Task can do something else and check back later.
• OS_OPT_PEND_BLOCKING: blocks, gets inserted in pend list, highest priority
ready task gets the CPU
– Non-zero timeout:
•
•
•
•
prevents waiting forever
inserted in tick list
when expired, error code specifies that timeout expired
not the best way to break deadlock
• OSSemPost():
– If waiting task(s), gets highest priority task and switches to it
– If not, increments counter
– Option allows not to run scheduler (in case task wants multiple posts)
Semaphore Internals
typedef
struct
struct os_sem {
OS_OBJ_TYPE
CPU_CHAR
OS_PEND_LIST
OS_SEM_CTR
CPU_TS
};
os_sem
OS_SEM;
Type;
*NamePtr;
PendList;
Ctr;
TS;
/* Should be set to OS_OBJ_TYPE_SEM */
• No distinction between binary and counting semaphores (see
mutex later)
• Type is needed to distinguish between different kernel objects
when using the generic OS_PEND_OBJECT type
• Pend list is needed as multiple tasks may be waiting on a
semaphore
• Timestamp indicating the last Post() operation
• Do not access fields directly, use API
Priority Inversion with Semaphores
• A medium priority task
can have the CPU when a
lower priority holds a
semaphore that a high
priority task is waiting for
Mutual Exclusion Semaphore: Mutex
• Binary semaphore with
special handling to avoid
priority inversion
• Changes task priorities when
necessary: called priority
inheritance: if a higher
priority task requests a
resource, the priority of the
current owner will be raised
to that of the requester
Mutex
OSMutexCreate()
OSMutexPend()
OSMutexPost()
OSMutexDel()
OSMutexPendAbort()
• Can be nested: it is only available when Post() was called as
many times as Pend()
• Can be blocking or non-blocking
• Check return value/error code of Pend() to see why it
returned (timeout, abort, delete, etc.)
• Do not make function calls in critical sections
Mutex Internals
typedef
struct
os_mutex
struct os_mutex {
OS_OBJ_TYPE
CPU_CHAR
OS_PEND_LIST
OS_TCB
OS_PRIO
OS_NESTING_CTR
CPU_TS
};
•
•
•
•
•
•
OS_MUTEX;
Type;
/* Should be set to OS_OBJ_TYPE_MUTEX */
*NamePtr;
PendList;
*OwnerTCBPtr;
OwnerOriginalPrio;
OwnerNestingCtr;
/* Mutex is available when the counter is 0 */
TS;
Type is needed to distinguish between different kernel objects when using the generic
OS_PEND_OBJECT type
Pend list is needed as multiple tasks may be waiting on a semaphore
Timestamp indicating the last Post() operation
Owner task and its original priority need to be stored
Do not access fields directly, use API
Post()
–
–
gets timestamp
decrements the nesting counter and if it reaches 0
•
•
•
resets priority if necessary
sets owner to 0
Gets highest priority task from pend list
Mutex Notes
• Same task can issue multiple Pend()-s, it will not
block if the first succeeds (unlike regular
Semaphores in uCos-III). It will simply increment
the nesting counter.
• If Pend() blocks, the priority of the current owner
is raised to the blocked task if it is higher than
that of the current task
• Post() decrements the nesting counter. If counter
is non-zero it returns OS_ERR_MUTEX_NESTING.
Last Post() (i.e., when nesting counter reaches 0)
resets the priority of the task to the original
value and calls the scheduler
Deadlock
Void Task1(void *p_arg)
{
while (1) {
…
Acquire M1
Access R1
…
/* INTERRUPT */
…
Acquire M2
Access R2
Release M2
Release M1
}
}
Void Task2(void *p_arg)
{
while (1) {
…
Acquire M2
Access R2
…
…
Acquire M1
Access R1
Release M1
Release M2
}
}
Deadlock Avoidance
Void Task1(void *p_arg)
{
while (1) {
…
Acquire M1
Acquire M2
Access R1
Access R2
…
Release M1
Release M2
}
}
Void Task2(void *p_arg)
{
while (1) {
…
Acquire M1
Acquire M2
Access R1
Access R2
…
Release M1
Release M2
}
}
Tips:
–
–
–
–
Try not to acquire more than one mutex at a time
Try not to acquire a mutex directly (hide them in functions)
Acquire all resources needed before proceeding
Always acquire resources in the same order