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. | |
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 BX_LOCK_INIT | ( | policy, | |||
| ceiling | ) |
{ \
.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.
| 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. |
Initialises a mutual exclusivity lock.
| 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. |
| >0 | Success | |
| BX_ERR_PARAM | A lock doesn't support the given policy |
| 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.
| 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. |
| >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. |
| 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.
| lock | Pointer to the lock to be released |
| >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 |
1.6.3