add no pi flag in mutex

This commit is contained in:
zhao maosheng 2024-08-28 16:57:22 +08:00
parent bb91502465
commit 01b7309c80
2 changed files with 317 additions and 96 deletions

View File

@ -1059,16 +1059,20 @@ struct rt_mutex
{
struct rt_ipc_object parent; /**< inherit from ipc_object */
/* not used in RT_MUTEX_NO_PI mutex */
rt_uint8_t ceiling_priority; /**< the priority ceiling of mutexe */
rt_uint8_t priority; /**< the maximal priority for pending thread */
rt_uint8_t hold; /**< numbers of thread hold the mutex */
rt_uint8_t reserved; /**< reserved field */
rt_uint8_t flag; /**< flag of mutex */
struct rt_thread *owner; /**< current owner of mutex */
rt_list_t taken_list; /**< the object list taken by thread */
struct rt_spinlock spinlock;
};
typedef struct rt_mutex *rt_mutex_t;
#define RT_MUTEX_NO_PI 0x02
#endif /* RT_USING_MUTEX */
/**@}*/

407
src/ipc.c
View File

@ -957,7 +957,7 @@ static void _mutex_before_delete_detach(rt_mutex_t mutex)
rt_list_remove(&mutex->taken_list);
/* whether change the thread priority */
if (mutex->owner)
if (mutex->owner && !(mutex->flag & RT_MUTEX_NO_PI))
{
need_schedule = _check_and_update_prio(mutex->owner, mutex);
}
@ -1022,6 +1022,7 @@ rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag)
mutex->priority = 0xFF;
mutex->hold = 0;
mutex->ceiling_priority = 0xFF;
mutex->flag = flag;
rt_list_init(&(mutex->taken_list));
/* flag can only be RT_IPC_FLAG_PRIO. RT_IPC_FLAG_FIFO cannot solve the unbounded priority inversion problem */
@ -1095,43 +1096,46 @@ void rt_mutex_drop_thread(rt_mutex_t mutex, rt_thread_t thread)
/* detach from suspended list */
rt_list_remove(&RT_THREAD_LIST_NODE(thread));
/**
* Should change the priority of mutex owner thread
* Note: After current thread is detached from mutex pending list, there is
* a chance that the mutex owner has been released the mutex. Which
* means mutex->owner can be NULL at this point. If that happened,
* it had already reset its priority. So it's okay to skip
*/
if (mutex->owner && rt_sched_thread_get_curr_prio(mutex->owner) ==
if (!(mutex->flag & RT_MUTEX_NO_PI))
{
/**
* Should change the priority of mutex owner thread
* Note: After current thread is detached from mutex pending list, there is
* a chance that the mutex owner has been released the mutex. Which
* means mutex->owner can be NULL at this point. If that happened,
* it had already reset its priority. So it's okay to skip
*/
if (mutex->owner && rt_sched_thread_get_curr_prio(mutex->owner) ==
rt_sched_thread_get_curr_prio(thread))
{
need_update = RT_TRUE;
}
/* update the priority of mutex */
if (!rt_list_isempty(&mutex->parent.suspend_thread))
{
/* more thread suspended in the list */
struct rt_thread *th;
th = RT_THREAD_LIST_NODE_ENTRY(mutex->parent.suspend_thread.next);
/* update the priority of mutex */
mutex->priority = rt_sched_thread_get_curr_prio(th);
}
else
{
/* set mutex priority to maximal priority */
mutex->priority = 0xff;
}
/* try to change the priority of mutex owner thread */
if (need_update)
{
/* get the maximal priority of mutex in thread */
priority = _thread_get_mutex_priority(mutex->owner);
if (priority != rt_sched_thread_get_curr_prio(mutex->owner))
{
_thread_update_priority(mutex->owner, priority, RT_UNINTERRUPTIBLE);
need_update = RT_TRUE;
}
/* update the priority of mutex */
if (!rt_list_isempty(&mutex->parent.suspend_thread))
{
/* more thread suspended in the list */
struct rt_thread *th;
th = RT_THREAD_LIST_NODE_ENTRY(mutex->parent.suspend_thread.next);
/* update the priority of mutex */
mutex->priority = rt_sched_thread_get_curr_prio(th);
}
else
{
/* set mutex priority to maximal priority */
mutex->priority = 0xff;
}
/* try to change the priority of mutex owner thread */
if (need_update)
{
/* get the maximal priority of mutex in thread */
priority = _thread_get_mutex_priority(mutex->owner);
if (priority != rt_sched_thread_get_curr_prio(mutex->owner))
{
_thread_update_priority(mutex->owner, priority, RT_UNINTERRUPTIBLE);
}
}
}
@ -1299,56 +1303,14 @@ rt_err_t rt_mutex_delete(rt_mutex_t mutex)
RTM_EXPORT(rt_mutex_delete);
#endif /* RT_USING_HEAP */
/**
* @brief This function will take a mutex, if the mutex is unavailable, the thread shall wait for
* the mutex up to a specified time.
*
* @note When this function is called, the count value of the mutex->value will decrease 1 until it is equal to 0.
* When the mutex->value is 0, it means that the mutex is unavailable. At this time, it will suspend the
* thread preparing to take the mutex.
* On the contrary, the rt_mutex_release() function will increase the count value of mutex->value by 1 each time.
*
* @see rt_mutex_trytake()
*
* @param mutex is a pointer to a mutex object.
*
* @param timeout is a timeout period (unit: an OS tick). If the mutex is unavailable, the thread will wait for
* the mutex up to the amount of time specified by the argument.
* NOTE: Generally, we set this parameter to RT_WAITING_FOREVER, which means that when the mutex is unavailable,
* the thread will be waitting forever.
*
* @return Return the operation status. ONLY When the return value is RT_EOK, the operation is successful.
* If the return value is any other values, it means that the mutex take failed.
*
* @warning This function can ONLY be called in the thread context. It MUST NOT BE called in interrupt context.
*/
static rt_err_t _rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout, int suspend_flag)
static rt_err_t _rt_mutex_take_pi(rt_mutex_t mutex, rt_thread_t thread, rt_int32_t timeout, int suspend_flag)
{
struct rt_thread *thread;
rt_err_t ret;
/* this function must not be used in interrupt even if time = 0 */
/* current context checking */
RT_DEBUG_SCHEDULER_AVAILABLE(RT_TRUE);
/* parameter check */
RT_ASSERT(mutex != RT_NULL);
RT_ASSERT(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex);
/* get current thread */
thread = rt_thread_self();
rt_spin_lock(&(mutex->spinlock));
RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(mutex->parent.parent)));
LOG_D("mutex_take: current thread %s, hold: %d",
thread->parent.name, mutex->hold);
/* reset thread error */
thread->error = RT_EOK;
if (mutex->owner == thread)
{
if (mutex->hold < RT_MUTEX_HOLD_MAX)
@ -1534,6 +1496,157 @@ static rt_err_t _rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout, int suspend
return RT_EOK;
}
static rt_err_t _rt_mutex_take_nopi(rt_mutex_t mutex, rt_thread_t thread, rt_int32_t timeout, int suspend_flag)
{
rt_err_t ret = RT_EOK;
rt_spin_lock(&(mutex->spinlock));
RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(mutex->parent.parent)));
/* take success */
if (mutex->owner == RT_NULL)
{
RT_ASSERT(mutex->hold == 0);
mutex->owner = thread;
mutex->hold = 1;
rt_spin_unlock(&(mutex->spinlock));
return RT_EOK;
}
/* recursive take */
if (mutex->owner == thread)
{
if(mutex->hold < RT_MUTEX_HOLD_MAX)
{
mutex->hold ++;
}
else
{
ret = -RT_EFULL;
}
rt_spin_unlock(&(mutex->spinlock));
return ret;
}
/* no waiting, return with timeout */
if (timeout == 0)
{
/* set error as timeout */
thread->error = RT_ETIMEOUT;
rt_spin_unlock(&(mutex->spinlock));
return -RT_ETIMEOUT;
}
/* waiting */
/* mutex is unavailable, push to suspend list */
LOG_D("mutex_take: suspend thread: %s", thread->parent.name);
/* suspend current thread */
ret = rt_thread_suspend_to_list(thread, &(mutex->parent.suspend_thread),
mutex->parent.parent.flag, suspend_flag);
if (ret != RT_EOK)
{
rt_spin_unlock(&(mutex->spinlock));
return ret;
}
/* set pending object in thread to this mutex */
thread->pending_object = &(mutex->parent.parent);
/* has waiting time, start thread timer */
if (timeout > 0)
{
LOG_D("mutex_take: start the timer of thread:%s", thread->parent.name);
/* reset the timeout of thread timer and start it */
rt_timer_control(&(thread->thread_timer),
RT_TIMER_CTRL_SET_TIME,
&timeout);
rt_timer_start(&(thread->thread_timer));
}
rt_spin_unlock(&(mutex->spinlock));
/* do schedule */
rt_schedule();
if (mutex->owner == thread)
{
/**
* get mutex successfully
*/
RT_ASSERT(thread->error == RT_EOK);
thread->pending_object = RT_NULL;
}
else
{
/* the mutex has not been taken and thread has detach from the pending list. */
/* get value first before calling to other APIs */
ret = thread->error;
}
/* fix thread error number to negative value and return */
return ret > 0 ? -ret : ret;
}
/**
* @brief This function will take a mutex, if the mutex is unavailable, the thread shall wait for
* the mutex up to a specified time.
*
* @note When this function is called, the count value of the mutex->value will decrease 1 until it is equal to 0.
* When the mutex->value is 0, it means that the mutex is unavailable. At this time, it will suspend the
* thread preparing to take the mutex.
* On the contrary, the rt_mutex_release() function will increase the count value of mutex->value by 1 each time.
*
* @see rt_mutex_trytake()
*
* @param mutex is a pointer to a mutex object.
*
* @param timeout is a timeout period (unit: an OS tick). If the mutex is unavailable, the thread will wait for
* the mutex up to the amount of time specified by the argument.
* NOTE: Generally, we set this parameter to RT_WAITING_FOREVER, which means that when the mutex is unavailable,
* the thread will be waitting forever.
*
* @return Return the operation status. ONLY When the return value is RT_EOK, the operation is successful.
* If the return value is any other values, it means that the mutex take failed.
*
* @warning This function can ONLY be called in the thread context. It MUST NOT BE called in interrupt context.
*/
static rt_err_t _rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout, int suspend_flag)
{
rt_thread_t thread;
/* this function must not be used in interrupt even if time = 0 */
/* current context checking */
RT_DEBUG_SCHEDULER_AVAILABLE(RT_TRUE);
/* parameter check */
RT_ASSERT(mutex != RT_NULL);
RT_ASSERT(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex);
/* get current thread */
thread = rt_thread_self();
LOG_D("mutex_take: current thread %s, hold: %d",
thread->parent.name, mutex->hold);
/* reset thread error */
thread->error = RT_EOK;
if (mutex->flag & RT_MUTEX_NO_PI)
{
return _rt_mutex_take_nopi(mutex, thread, timeout, suspend_flag);
}
else
{
return _rt_mutex_take_pi(mutex, thread, timeout, suspend_flag);
}
}
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)
{
return _rt_mutex_take(mutex, time, RT_UNINTERRUPTIBLE);
@ -1586,29 +1699,15 @@ RTM_EXPORT(rt_mutex_trytake);
* @return Return the operation status. When the return value is RT_EOK, the operation is successful.
* If the return value is any other values, it means that the mutex release failed.
*/
rt_err_t rt_mutex_release(rt_mutex_t mutex)
static rt_err_t _rt_mutex_release_pi(rt_mutex_t mutex, rt_thread_t thread)
{
rt_sched_lock_level_t slvl;
struct rt_thread *thread;
rt_bool_t need_schedule;
/* parameter check */
RT_ASSERT(mutex != RT_NULL);
RT_ASSERT(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex);
need_schedule = RT_FALSE;
/* only thread could release mutex because we need test the ownership */
RT_DEBUG_IN_THREAD_CONTEXT;
/* get current thread */
thread = rt_thread_self();
rt_spin_lock(&(mutex->spinlock));
LOG_D("mutex_release:current thread %s, hold: %d",
thread->parent.name, mutex->hold);
RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mutex->parent.parent)));
/* mutex only can be released by owner */
@ -1713,8 +1812,126 @@ rt_err_t rt_mutex_release(rt_mutex_t mutex)
return RT_EOK;
}
RTM_EXPORT(rt_mutex_release);
static rt_err_t _rt_mutex_release_nopi(rt_mutex_t mutex, rt_thread_t thread)
{
rt_sched_lock_level_t slvl;
rt_bool_t need_schedule;
need_schedule = RT_FALSE;
rt_spin_lock(&(mutex->spinlock));
RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mutex->parent.parent)));
/* mutex only can be released by owner */
if (thread != mutex->owner)
{
thread->error = -RT_ERROR;
rt_spin_unlock(&(mutex->spinlock));
return -RT_ERROR;
}
/* decrease hold */
mutex->hold --;
/* if no hold */
if (mutex->hold == 0)
{
/* remove mutex from thread's taken list */
rt_list_remove(&mutex->taken_list);
rt_sched_lock(&slvl);
/* wakeup suspended thread */
if (!rt_list_isempty(&mutex->parent.suspend_thread))
{
struct rt_thread *next_thread;
do
{
/* get the first suspended thread */
next_thread = RT_THREAD_LIST_NODE_ENTRY(mutex->parent.suspend_thread.next);
RT_ASSERT(rt_sched_thread_is_suspended(next_thread));
/* remove the thread from the suspended list of mutex */
rt_list_remove(&RT_THREAD_LIST_NODE(next_thread));
/* resume thread to ready queue */
if (rt_sched_thread_ready(next_thread) != RT_EOK)
{
/**
* a timeout timer had triggered while we try. So we skip
* this thread and try again.
*/
next_thread = RT_NULL;
}
} while (!next_thread && !rt_list_isempty(&mutex->parent.suspend_thread));
if (next_thread)
{
LOG_D("mutex_release: resume thread: %s",
next_thread->parent.name);
/* set new owner and put mutex into taken list of thread */
mutex->owner = next_thread;
mutex->hold = 1;
rt_list_insert_after(&next_thread->taken_object_list, &mutex->taken_list);
/* cleanup pending object */
next_thread->pending_object = RT_NULL;
need_schedule = RT_TRUE;
}
else
{
/* no waiting thread is woke up, clear owner */
mutex->owner = RT_NULL;
}
}
else
{
/* clear owner */
mutex->owner = RT_NULL;
}
rt_sched_unlock(slvl);
}
rt_spin_unlock(&(mutex->spinlock));
/* perform a schedule */
if (need_schedule == RT_TRUE)
rt_schedule();
return RT_EOK;
}
rt_err_t rt_mutex_release(rt_mutex_t mutex)
{
rt_thread_t thread;
/* parameter check */
RT_ASSERT(mutex != RT_NULL);
RT_ASSERT(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex);
/* only thread could release mutex because we need test the ownership */
RT_DEBUG_IN_THREAD_CONTEXT;
thread = rt_thread_self();
LOG_D("mutex_release:current thread %s, hold: %d",
thread->parent.name, mutex->hold);
if (mutex->flag & RT_MUTEX_NO_PI)
{
return _rt_mutex_release_nopi(mutex, thread);
}
else
{
return _rt_mutex_release_pi(mutex, thread);
}
}
RTM_EXPORT(rt_mutex_release);
/**
* @brief This function will set some extra attributions of a mutex object.