Mutual exclusivity lock functions
[Pre-emptive, soft real-time scheduler]

Locks are guaranteeing mutually exclusive access to some resource. More...

Data Structures

struct  BX_LOCK
 Lock object. More...

Defines

#define BX_LOCK_INIT(policy, ceiling)
 Static initialiser for a lock.

Functions

int BX_LockInit (BX_LOCK *lock, BX_POLICY policy, unsigned ceilprio)
 Initialises a mutual exclusivity lock.
int BX_LockObtain (BX_LOCK *lock, unsigned timeout)
 Obtains a lock.
int BX_LockRelease (BX_LOCK *lock)
 Releases a lock.

Detailed Description

Locks are guaranteeing mutually exclusive access to some resource.

If you want to use the resource, you first obtain the lock. If the lock is already owned by an other process, you will be suspended until the other process releases it. Once you own the lock, you know that no other process can get it until you release it.
A lock is also known as a mutex or a binary semaphore.

If more than one process waits for a lock, the question arises which one of the waiting processes gets it when the current owner releases the lock. The kernel supports two policies, one is FIFO, where the processes are served according to their request order. The other is by priority, in which case the highest priority waiting process will get the lock.

There is an interesting phenomenon, though, that warrants consideration. Let's assume that we have two processes, a low priority one that we'll call L and a high priority one, H. Let L take the lock. Then, if H wakes up, it will suspend L as H has higher priority. Now if H wants to access the resource protected by the lock, it tries to obtain the lock. Since it is already owned by L, H will be suspended, L resumes execution and when it finishes with the resource and releases the lock, it will immediately be given to H. Thus, H becomes ready, L gets suspended and H can chug away doing whatever it has to.
Let us introduce a third process, M, which has a priority between that of L and H. As before, L takes the lock, H wakes up, tries to obtain the lock, gets suspended. However, before L could finish with the resource, M wakes up and starts a lengthy operation that has nothing to do with the protected resources. Since M has higher priority than L, L will be suspended. Since L is suspended, it can not release the lock. Therefore H is also suspended as long as M is running, even though they do not compete for the resource. In effect, a high priority process is held up by a lower priority one. This phenomena is known as priority inversion. There are two methods commonly used to avoid it.

While these methods seem simple enough, they get a bit complicated when you have multiple locks. Let us have two locks, A and B, using priority inheritance. If a process takes A and then goes to take B as well but B is already taken, then the process gets suspended on B. Naturally, B's owner's priority will be raised to match that of this process. Now if a third process with a priority even higher goes to wait on A, then the priority of the process that owns A is raised. That process waits on B, thus the priority of the owner of B must also be raised. So the kernel has to evaluate the ownership chain. The situation is even more complex when you have timeouts, because if a high priority process ceases to wait for a lock, you have to check if you can lower the priority of the lock owner.

Since the priority ceiling and inheritance mechanisms are orthogonal concepts to the ordering of the waiting queue, the locks support 6 different policies:

Locks, like any other kernel object, are allocated by you in your memory. Before you can use a lock, you must initialise it, using either the BX_LOCK_INIT() static initialiser macro or the BX_LockInit() function.


Define Documentation

#define BX_LOCK_INIT ( policy,
ceiling   ) 
Value:
{                                                                   \
    .pmut = {                                                       \
        .wait = {                                                   \
            .type = BX_TYPE_LOCK,                                   \
            .head = NULL,                                           \
            .tail = NULL,                                           \
            .ftab = (struct _bx_poli *) (                           \
                    (policy) == BX_POLICY_PRIO ? &_bx_poli_prio :   \
                    (policy) == BX_POLICY_FPCL ? &_bx_poli_fpcl :   \
                    (policy) == BX_POLICY_FPIN ? &_bx_poli_fpin :   \
                    (policy) == BX_POLICY_PPCL ? &_bx_poli_ppcl :   \
                    (policy) == BX_POLICY_PPIN ? &_bx_poli_ppin :   \
                    &_bx_poli_fifo )                                \
        },                                                          \
        .ceil = (ceiling),                                          \
        .next = NULL,                                               \
        .prev = NULL,                                               \
        .proc = NULL,                                               \
        .prio = 0                                                   \
    }                                                               \
}

Static initialiser for a lock.

Parameters:
policy The locks waiting policy, one of BX_POLICY_FIFO, BX_POLICY_PRIO, BX_POLICY_FPCL, BX_POLICY_FPIN, BX_POLICY_PPCL, BX_POLICY_PPIN as defined by BX_POLICY .
ceiling The ceiling priority if the policy is BX_POLICY_FPCL or BX_POLICY_PPCL. In any other case its value is ignored.

Function Documentation

int BX_LockInit ( BX_LOCK lock,
BX_POLICY  policy,
unsigned  ceilprio 
)

Initialises a mutual exclusivity lock.

Parameters:
lock Pointer to the lock to initialise
policy The process queueing policy of the lock. It must be one of BX_POLICY_FIFO, BX_POLICY_PRIO, BX_POLICY_PPCL, BX_POLICY_PPIN, BX_POLICY_FPCL or BX_POLICY_FPIN.
ceilprio The priority of the lock if it implements priority ceiling. For all other policies this argument is ignored.
Return values:
>0 Success
BX_ERR_PARAM A lock doesn't support the given policy
Note:
If the lock uses priority ceiling or priority inheritance policy, quite a lot of code (and runtime) is needed to maintain the priorities through inheritance chains. Of course, if you do not use such policies, the relevant routines will not be called thus the runtime penalty associated with dynamic priorities will not hit you. The code size is a different issue. The kernel is a library, therefore only what is used will be linked into your code. If you statically initialise all your locks, and none of the uses the above mentioned policies, then the dynamic policy code will not be linked into your application. If, however, you call LockInit(), it can not know at compile time if you will pass it a policy that requires that code or not, therefore it will be linked into your code, even if you will never use it. It is a few KBs, so if you are a bit low on code space, you might be better off using static inits than calling LockInit().
Attention:
This function does not suspend the caller. This function should not be called from an ISR or delayed ISR. Note, however, that the function does not check this criteria, to allow initialisation of locks before the kernel is started (which must happen in IRQ context). In addition, during the initialisation interrupts are not disabled, therefore you must make sure that nothing will touch the lock while this function is running.
int BX_LockObtain ( BX_LOCK lock,
unsigned  timeout 
)

Obtains a lock.

This function obtains a lock. If the lock is available, then the caller will own the lock. If the lock is not available, then the caller will be suspended and placed on the waiting queue of the lock.

Parameters:
lock Pointer to the lock to get
timeout Timeout value. If it is 0xffffffff, that is, -1 cast to unsigned, then there is not time limit in waiting for the lock. If it is any other value, then it is the timeout in microseconds. If the caller can not get the lock within that time, then the call will return and indicate timeout. If the timeout is 0, then the caller will not be suspended and the function returns immediately if the lock is not available.
Return values:
>0 The lock was obtained by the caller
0 Timeout, the lock was not taken
BX_ERR_ISOWNER The lock was already owned by the caller
BX_ERR_CONTEXT The function was called from an ISR context
BX_ERR_OBJECT The passed object is not a lock or it was not initialised.
BX_ERR_SUSPEND The caller should be suspended but can not because its protection levels is higher than BX_PROT_COOP.
Note:
Although the timeout value is specified in microseconds, the actual resolution depends on the frequency of calls to the BX_TimerTick() function.
Attention:
This function should not be called from an ISR or delayed ISR. This function can suspend the caller.
int BX_LockRelease ( BX_LOCK lock  ) 

Releases a lock.

The function can not be called from kernel or interrupt context. The caller must own the lock.

Parameters:
lock Pointer to the lock to be released
Return values:
>0 Success.
BX_ERR_OBJECT The pointer does not point to a lock object.
BX_ERR_CONTEXT The caller is not a user thread
BX_ERR_NOTOWNER The calling process does not own the lock
Attention:
This function can only be called from user context. This function does not suspend the caller but can cause a context switch if a higher priority process is woken up by the call.
Generated on Tue Jul 13 16:51:45 2010 by  doxygen 1.6.3