RT-Thread内核源码分析-信号量实现原理
⽬录
概念
信号量机概念是由荷兰科学家Dijkstr(就是解决了最短路径问题的那哥们)引⼊,它是⽤来协调不同进程间的数据对象的,信号量本质上⽽⾔是⼀个计数器, 记录了某个资源的存取情况, ⽤来为多个进程共享的数据结构提供受控访问。 在不同的操作系统平台,都有信号量的实现, 虽然各个平台在实现上和概念上有些区别, 却始终离不开如下⼏个功能:
1. 信号量初始化
创建信号量对象,初始化计数器值
2. 获取信号量
获取信号量相当于获取了对共享资源的访问权限
3. 释放信号量
释放信号量相当于释放了对共享资源的访问权限
接下来就以RT-Thread平台信号量的实现进⾏说明
信号量基本操作
信号量初始化
rt_err_t rt_sem_init(rt_sem_t    sem,
const char *name,
rt_uint32_t value,
rt_uint8_t  flag)
{
RT_ASSERT(sem != RT_NULL);
RT_ASSERT(value < 0x10000U);
/
* init object */
rt_object_init(&(sem->parent.parent), RT_Object_Class_Semaphore, name);
/* init ipc object */
rt_ipc_object_init(&(sem->parent));
/* set init value */
sem->value = (rt_uint16_t)value;//信号量-计数器的初始值,通常设置为1
/* set parent */
sem->parent.parent.flag = flag;
return RT_EOK;
}
信号量的初始化的核⼼就是创建了⼀个计数器value, rtthread为了⽅便管理系统资源(信号量也是系统资源),定义了⼀套对象管理⽅法,所有的对象都需要继承⾃rt_object,⽅便进⾏管理
获取信号量
对于像信号量这种稀缺资源, 并不是随时都能获取到, ⽽在linux平台,获取系统资源的操作通常都会设置⼀个最⼤等待时间, 在这个时间超时后依然⽆法获取资源,则返回,同样的,RT-Thread也有这样的考虑,获取信号量接⼝中含有⼀个等待时间,如下:
rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time)
{
register rt_base_t temp;
struct rt_thread *thread;
/* parameter check */
RT_ASSERT(sem != RT_NULL);
RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore);    RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(sem->parent.parent)));
/
* disable interrupt */
temp = rt_hw_interrupt_disable();// 关中断
RT_DEBUG_LOG(RT_DEBUG_IPC, ("thread %s take sem:%s, which value is: %d\n",
rt_thread_self()->name,
((struct rt_object *)sem)->name,
sem->value));
if (sem->value > 0)//如果计数器⼤于0,则可以获取计数器
{
/* semaphore is available */
sem->value --;
/* enable interrupt */
rt_hw_interrupt_enable(temp);//打开中断
}
else
{//计数器<=0,⽆法获取信号量
/* no waiting, return with timeout */
if (time == 0)
{//如果希望阻塞的时间为0,则⽴即返回
rt_hw_interrupt_enable(temp);
return -RT_ETIMEOUT;
}
else
{// 获取不到信号量,且希望阻塞的时间>0,则需要挂起等待
/* current context checking */
RT_DEBUG_IN_THREAD_CONTEXT;
/* semaphore is unavailable, push to suspend list */
/* get current thread */
thread = rt_thread_self();
/* reset thread error number */
thread->error = RT_EOK;timeout on t2 timer
RT_DEBUG_LOG(RT_DEBUG_IPC, ("sem take: suspend thread - %s\n",
thread->name));
/* suspend thread */
rt_ipc_list_suspend(&(sem->parent.suspend_thread),
thread,
sem->parent.parent.flag);//挂起线程
/* has waiting time, start thread timer */
if (time > 0)
{//我们需要根据等待的时间启动定时器,以便在等待时间结束后能够被定时器唤醒
RT_DEBUG_LOG(RT_DEBUG_IPC, ("set thread:%s to timer list\n",
thread->name));
/* reset the timeout of thread timer and start it */
rt_timer_control(&(thread->thread_timer),
RT_TIMER_CTRL_SET_TIME,
&time);
&time);
rt_timer_start(&(thread->thread_timer));
}
/* enable interrupt */
rt_hw_interrupt_enable(temp);//开中断
/* do schedule */
rt_schedule();//重新调度
if (thread->error != RT_EOK)
{
return thread->error;
}
}
}
RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(sem->parent.parent)));
return RT_EOK;
}
信号量计数器也是多线程共享资源, 使⽤前后需要开关中断.
细⼼的朋友会发现这⾥有⼀个问题,上述代码可能会导致优先级反转。 我们在中有过分析, 因获取系统资源导致挂起有可能会引发优先级反转,显然, rt_sem_take可能会导致优先级反转。事实上,这也正是信号量的优点所在,相⽐于互斥量,信号量没有所有者的概念, 实现简单,⼩巧,灵活,效率⾼,常⽤在对互斥要求不严格的系统中。
释放信号量
rt_err_t rt_sem_release(rt_sem_t sem)
{
register rt_base_t temp;
register rt_bool_t need_schedule;
/* parameter check */
RT_ASSERT(sem != RT_NULL);
RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore);
RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(sem->parent.parent)));
need_schedule = RT_FALSE;
/* disable interrupt */
temp = rt_hw_interrupt_disable();//关中断
RT_DEBUG_LOG(RT_DEBUG_IPC, ("thread %s releases sem:%s, which value is: %d\n",
rt_thread_self()->name,
((struct rt_object *)sem)->name,
sem->value));
if (!rt_list_isempty(&sem->parent.suspend_thread))
{
/* resume the suspended thread */
rt_ipc_list_resume(&(sem->parent.suspend_thread));
need_schedule = RT_TRUE;
}
else
sem->value ++; /* increase value */
/* enable interrupt */
rt_hw_interrupt_enable(temp);
/* resume a thread, re-schedule */
if (need_schedule == RT_TRUE)
rt_schedule();
return RT_EOK;
}
释放信号量的本质是对计数器进⾏++操作,释放信号量可能会导致线程调度,即便当前没有更⾼优先级线程就绪, 释放信号量也会导致⽴即调度被信号量挂起的线程。
这⾥有⼀个问题, 在rt_sem_release中,如果有被挂起到该信号量的线程的话,将会执⾏rt_schedule进⾏调度(可以肯定的是,此时此刻,当前线程和系统就绪线程队列中的线程相⽐,当前线程的优先级
是最⾼的, 也就是当前系统的就绪队列中是没有⽐当前线程优先级更⾼的线程存在), ⽽rt_sem_release的实现中,如果需要调度,则不再执⾏sem->value++,那么这可能会导致⼀个问题, 如果执⾏
rt_schedule,发现此时此刻就绪队列中的线程优先级都⽐当前线程优先级低,则将不再执⾏调度, 则当前线程就会继续运⾏,但是却没有执⾏sem->value++,这岂不是会导致问题,事实上,不会产⽣任何问题, 原因就在于rt_sem_take的实现,下⾯是对rt_sem_take和
rt_sem_release的过程分析,去除了⼀些不必要的代码:
rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time)
{
...
if (sem->value > 0)
{
sem->value --;
...
}
else
{//⽆法获取信号量,线程被函数rt_sem_take挂起,这⾥就不再执⾏sem->value--操作,这个和rt_sem_release是呼应的,当挂起的线程在re_sem_release中被唤        if (time == 0)
{
...
}
else
{
...
rt_schedule();
...
}
}
...
}
rt_err_t rt_sem_release(rt_sem_t sem)
{
...
if (!rt_list_isempty(&sem->parent.suspend_thread))
{//存在被该信号量挂起的线程,则把挂起链表中第⼀个线程唤醒(设置为就绪态,即便设置为就绪态,该线程依然可能因为优先级低⽽得不到执⾏),不再执⾏sem->        /* resume the suspended thread */
rt_ipc_list_resume(&(sem->parent.suspend_thread));
need_schedule = RT_TRUE;
}
else
sem->value ++;
...
if (need_schedule == RT_TRUE)
rt_schedule();
return RT_EOK;
}
总结
信号量本质上是⼀个计数器, 计数器位于临界资源中, 操作计数器时也需要开关中断实现。 信号量实现简单,相⽐于互斥量,信号量没有
所有者的概念, 实现简单,⼩巧,灵活,效率⾼,但是可能会导致优先级反转,常⽤在对互斥要求不严格的系统中。另外,即便当前没有
更⾼优先级线程需要调度, 释放信号量(mutex)也会触发调度器执⾏线程调度。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。