version 1.1.1.1, 2008/06/03 10:38:46 |
version 1.1.1.1.2.1, 2008/08/13 17:12:33 |
|
|
*/ |
*/ |
|
|
/* |
/* |
* A mutex is used to protect un-sharable resources. |
* A mutex is used to protect un-sharable resources. A thread |
* A thread can use mutex_lock() to ensure that global resource is not |
* can use mutex_lock() to ensure that global resource is not |
* accessed by other thread. The mutex is effective only the threads |
* accessed by other thread. The mutex is effective only the |
* belonging to the same task. |
* threads belonging to the same task. |
* |
* |
* Prex will change the thread priority to prevent priority inversion. |
* Prex will change the thread priority to prevent priority inversion. |
* |
* |
* <Priority inheritance> |
* <Priority inheritance> |
* The priority is changed at the following conditions. |
* The priority is changed at the following conditions. |
* |
* |
* 1. When the current thread can not lock the mutex and its mutex |
* 1. When the current thread can not lock the mutex and its |
* owner has lower priority than current thread, the priority |
* mutex owner has lower priority than current thread, the |
* of mutex owner is boosted to same priority with current thread. |
* priority of mutex owner is boosted to same priority with |
* If this mutex owner is waiting for another mutex, such related |
* current thread. If this mutex owner is waiting for another |
* mutexes are also processed. |
* mutex, such related mutexes are also processed. |
* |
* |
* 2. When the current thread unlocks the mutex and its priority |
* 2. When the current thread unlocks the mutex and its priority |
* has already been inherited, the current priority is reset. |
* has already been inherited, the current priority is reset. |
|
|
* |
* |
* 2. Even if thread is killed with mutex waiting, the related |
* 2. Even if thread is killed with mutex waiting, the related |
* priority is not adjusted. |
* priority is not adjusted. |
* |
|
* <Important> |
|
* Since this implementation does not support recursive lock, a thread |
|
* can not lock the same mutex twice. |
|
*/ |
*/ |
|
|
#include <kernel.h> |
#include <kernel.h> |
|
|
#define MAXINHERIT 10 |
#define MAXINHERIT 10 |
|
|
/* forward declarations */ |
/* forward declarations */ |
static int prio_inherit(thread_t th); |
static int prio_inherit(thread_t th); |
static void prio_uninherit(thread_t th); |
static void prio_uninherit(thread_t th); |
|
|
/* |
/* |
* Initialize a mutex. |
* Initialize a mutex. |
* |
* |
* If an initialized mutex is reinitialized, undefined behavior |
* If an initialized mutex is reinitialized, undefined |
* results. Technically, we can not detect such error condition |
* behavior results. Technically, we can not detect such |
* here because we can not touch the passed object in kernel. |
* error condition here because we can not touch the passed |
|
* object in kernel. |
*/ |
*/ |
int |
int |
mutex_init(mutex_t *mtx) |
mutex_init(mutex_t *mtx) |
|
|
sched_lock(); |
sched_lock(); |
if (umem_copyin(mtx, &m, sizeof(mutex_t))) { |
if (umem_copyin(mtx, &m, sizeof(mutex_t))) { |
err = EFAULT; |
err = EFAULT; |
} else if (!mutex_valid(m)) { |
goto out; |
|
} |
|
if (!mutex_valid(m)) { |
err = EINVAL; |
err = EINVAL; |
} else if (m->owner || event_waiting(&m->event)) { |
goto out; |
|
} |
|
if (m->owner || event_waiting(&m->event)) { |
err = EBUSY; |
err = EBUSY; |
} else { |
goto out; |
m->magic = 0; |
|
kmem_free(m); |
|
} |
} |
|
|
|
m->magic = 0; |
|
kmem_free(m); |
|
out: |
sched_unlock(); |
sched_unlock(); |
ASSERT(err == 0); |
ASSERT(err == 0); |
return err; |
return err; |
|
|
/* |
/* |
* Copy mutex from user space. |
* Copy mutex from user space. |
* If it is not initialized, create new mutex. |
* If it is not initialized, create new mutex. |
* @umtx: pointer to mutex in user space. |
|
* @kmtx: pointer to mutex in kernel space. |
|
*/ |
*/ |
static int |
static int |
mutex_copyin(mutex_t *umtx, mutex_t *kmtx) |
mutex_copyin(mutex_t *umtx, mutex_t *kmtx) |
|
|
|
|
if (m == MUTEX_INITIALIZER) { |
if (m == MUTEX_INITIALIZER) { |
/* |
/* |
* Allocate mutex. |
* Allocate new mutex, and retreive its id |
|
* from the user space. |
*/ |
*/ |
if ((err = mutex_init(umtx))) |
if ((err = mutex_init(umtx))) |
return err; |
return err; |
|
|
/* |
/* |
* Lock a mutex. |
* Lock a mutex. |
* |
* |
* A current thread is blocked if the mutex has already been locked. |
* A current thread is blocked if the mutex has already been |
* If current thread receives any exception while waiting mutex, this |
* locked. If current thread receives any exception while |
* routine returns with EINTR in order to invoke exception handler. |
* waiting mutex, this routine returns with EINTR in order to |
* But, POSIX thread assumes this function does NOT return with EINTR. |
* invoke exception handler. But, POSIX thread assumes this |
* So, system call stub routine in library must call this again if |
* function does NOT return with EINTR. So, system call stub |
* it gets EINTR. |
* routine in library must call this again if it gets EINTR. |
*/ |
*/ |
int |
int |
mutex_lock(mutex_t *mtx) |
mutex_lock(mutex_t *mtx) |
|
|
/* |
/* |
* Recursive lock |
* Recursive lock |
*/ |
*/ |
m->lock_count++; |
m->locks++; |
|
ASSERT(m->locks != 0); |
} else { |
} else { |
/* |
/* |
* Check whether a target mutex is locked. |
* Check whether a target mutex is locked. |
|
|
goto out; |
goto out; |
} |
} |
} |
} |
m->lock_count = 1; |
m->locks = 1; |
} |
} |
m->owner = cur_thread; |
m->owner = cur_thread; |
list_insert(&cur_thread->mutexes, &m->link); |
list_insert(&cur_thread->mutexes, &m->link); |
|
|
if ((err = mutex_copyin(mtx, &m))) |
if ((err = mutex_copyin(mtx, &m))) |
goto out; |
goto out; |
if (m->owner == cur_thread) |
if (m->owner == cur_thread) |
m->lock_count++; |
m->locks++; |
else { |
else { |
if (m->owner != NULL) |
if (m->owner != NULL) |
err = EBUSY; |
err = EBUSY; |
else { |
else { |
m->lock_count = 1; |
m->locks = 1; |
m->owner = cur_thread; |
m->owner = cur_thread; |
list_insert(&cur_thread->mutexes, &m->link); |
list_insert(&cur_thread->mutexes, &m->link); |
} |
} |
|
|
sched_lock(); |
sched_lock(); |
if ((err = mutex_copyin(mtx, &m))) |
if ((err = mutex_copyin(mtx, &m))) |
goto out; |
goto out; |
if (m->owner != cur_thread || m->lock_count <= 0) { |
if (m->owner != cur_thread || m->locks <= 0) { |
err = EPERM; |
err = EPERM; |
goto out; |
goto out; |
} |
} |
if (--m->lock_count == 0) { |
if (--m->locks == 0) { |
list_remove(&m->link); |
list_remove(&m->link); |
prio_uninherit(cur_thread); |
prio_uninherit(cur_thread); |
/* |
/* |
|
|
/* |
/* |
* Clean up mutex. |
* Clean up mutex. |
* |
* |
* This is called with scheduling locked when thread is terminated. |
* This is called with scheduling locked when thread is |
* If a thread is terminated with mutex hold, all waiting threads |
* terminated. If a thread is terminated with mutex hold, all |
* keeps waiting forever. So, all mutex locked by terminated thread |
* waiting threads keeps waiting forever. So, all mutex locked by |
* must be unlocked. Even if the terminated thread is waiting some |
* terminated thread must be unlocked. Even if the terminated |
* mutex, the inherited priority of other mutex owner is not adjusted. |
* thread is waiting some mutex, the inherited priority of other |
|
* mutex owner is not adjusted. |
*/ |
*/ |
void |
void |
mutex_cleanup(thread_t th) |
mutex_cleanup(thread_t th) |
|
|
* Release locked mutex. |
* Release locked mutex. |
*/ |
*/ |
m = list_entry(list_first(head), struct mutex, link); |
m = list_entry(list_first(head), struct mutex, link); |
m->lock_count = 0; |
m->locks = 0; |
list_remove(&m->link); |
list_remove(&m->link); |
/* |
/* |
* Change the mutex owner if other thread |
* Change the mutex owner if other thread |
|
|
owner = sched_wakeone(&m->event); |
owner = sched_wakeone(&m->event); |
if (owner) { |
if (owner) { |
owner->wait_mutex = NULL; |
owner->wait_mutex = NULL; |
m->lock_count = 1; |
m->locks = 1; |
list_insert(&owner->mutexes, &m->link); |
list_insert(&owner->mutexes, &m->link); |
} |
} |
m->owner = owner; |
m->owner = owner; |
|
|
|
|
/* |
/* |
* Inherit priority. |
* Inherit priority. |
* @waiter: thread that is about to wait a mutex. |
|
* |
* |
* To prevent priority inversion, we must ensure the higher priority |
* To prevent priority inversion, we must ensure the higher |
* thread does not wait other lower priority thread. So, raise the |
* priority thread does not wait other lower priority thread. So, |
* priority of mutex owner which blocks the "waiter" thread. If such |
* raise the priority of mutex owner which blocks the "waiter" |
* mutex owner is also waiting for other mutex, that mutex is also |
* thread. If such mutex owner is also waiting for other mutex, |
* processed. |
* that mutex is also processed. Returns EDEALK if it finds |
* Returns EDEALK if it finds deadlock condition. |
* deadlock condition. |
*/ |
*/ |
static int |
static int |
prio_inherit(thread_t waiter) |
prio_inherit(thread_t waiter) |
|
|
* causes a deadlock. |
* causes a deadlock. |
*/ |
*/ |
if (owner == waiter) { |
if (owner == waiter) { |
printk("Deadlock! mutex=%x owner=%x waiter=%x\n", |
DPRINTF(("Deadlock! mutex=%x owner=%x waiter=%x\n", |
m, owner, waiter); |
m, owner, waiter)); |
return EDEADLK; |
return EDEADLK; |
} |
} |
/* |
/* |
|
|
* owner's priority. |
* owner's priority. |
*/ |
*/ |
if (owner->prio > waiter->prio) { |
if (owner->prio > waiter->prio) { |
sched_setprio(owner, owner->base_prio, waiter->prio); |
sched_setprio(owner, owner->baseprio, waiter->prio); |
m->prio = waiter->prio; |
m->prio = waiter->prio; |
} |
} |
/* |
/* |
|
|
/* |
/* |
* Un-inherit priority |
* Un-inherit priority |
* |
* |
* The priority of specified thread is reset to the base priority. |
* The priority of specified thread is reset to the base |
* If specified thread locks other mutex and higher priority thread |
* priority. If specified thread locks other mutex and higher |
* is waiting for it, the priority is kept to that level. |
* priority thread is waiting for it, the priority is kept to |
|
* that level. |
*/ |
*/ |
static void |
static void |
prio_uninherit(thread_t th) |
prio_uninherit(thread_t th) |
|
|
mutex_t m; |
mutex_t m; |
|
|
/* Check if the priority is inherited. */ |
/* Check if the priority is inherited. */ |
if (th->prio == th->base_prio) |
if (th->prio == th->baseprio) |
return; |
return; |
|
|
top_prio = th->base_prio; |
top_prio = th->baseprio; |
/* |
/* |
* Find the highest priority thread that is waiting |
* Find the highest priority thread that is waiting |
* for the thread. This is done by checking all mutexes |
* for the thread. This is done by checking all mutexes |
|
|
if (m->prio < top_prio) |
if (m->prio < top_prio) |
top_prio = m->prio; |
top_prio = m->prio; |
} |
} |
sched_setprio(th, th->base_prio, top_prio); |
sched_setprio(th, th->baseprio, top_prio); |
} |
} |